1/*
2 * Copyright (C) 2011, Google Inc. All rights reserved.
3 *
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions
6 * are met:
7 * 1.  Redistributions of source code must retain the above copyright
8 *     notice, this list of conditions and the following disclaimer.
9 * 2.  Redistributions in binary form must reproduce the above copyright
10 *     notice, this list of conditions and the following disclaimer in the
11 *     documentation and/or other materials provided with the distribution.
12 *
13 * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS'' AND ANY
14 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
15 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
16 * DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS BE LIABLE FOR ANY
17 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
18 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
19 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
20 * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
21 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
22 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
23 */
24
25#include "config.h"
26
27#if ENABLE(INSPECTOR)
28
29#include "InspectorStyleTextEditor.h"
30
31#include "CSSPropertySourceData.h"
32#include "HTMLParserIdioms.h"
33#include "InspectorStyleSheet.h"
34
35using namespace Inspector;
36
37namespace WebCore {
38
39InspectorStyleTextEditor::InspectorStyleTextEditor(Vector<InspectorStyleProperty>* allProperties, Vector<InspectorStyleProperty>* disabledProperties, const String& styleText, const NewLineAndWhitespace& format)
40    : m_allProperties(allProperties)
41    , m_disabledProperties(disabledProperties)
42    , m_styleText(styleText)
43    , m_format(format)
44{
45}
46
47void InspectorStyleTextEditor::insertProperty(unsigned index, const String& propertyText, unsigned styleBodyLength)
48{
49    long propertyStart = 0;
50
51    bool insertLast = true;
52    if (index < m_allProperties->size()) {
53        const InspectorStyleProperty& property = m_allProperties->at(index);
54        if (property.hasSource) {
55            propertyStart = property.sourceData.range.start;
56            // If inserting before a disabled property, it should be shifted, too.
57            insertLast = false;
58        }
59    }
60
61    bool insertFirstInSource = true;
62    for (unsigned i = 0, size = m_allProperties->size(); i < index && i < size; ++i) {
63        const InspectorStyleProperty& property = m_allProperties->at(i);
64        if (property.hasSource && !property.disabled) {
65            insertFirstInSource = false;
66            break;
67        }
68    }
69
70    bool insertLastInSource = true;
71    for (unsigned i = index, size = m_allProperties->size(); i < size; ++i) {
72      const InspectorStyleProperty& property = m_allProperties->at(i);
73        if (property.hasSource && !property.disabled) {
74            insertLastInSource = false;
75            break;
76        }
77    }
78
79    String textToSet = propertyText;
80
81    int formattingPrependOffset = 0;
82    if (insertLast && !insertFirstInSource) {
83        propertyStart = styleBodyLength;
84        if (propertyStart && textToSet.length()) {
85            long curPos = propertyStart - 1; // The last position of style declaration, since propertyStart points past one.
86            while (curPos && isHTMLSpace(m_styleText[curPos]))
87                --curPos;
88            if (curPos && m_styleText[curPos] != ';') {
89                // Prepend a ";" to the property text if appending to a style declaration where
90                // the last property has no trailing ";".
91                textToSet = makeString(';', textToSet);
92                formattingPrependOffset = 1;
93            }
94        }
95    }
96
97    const String& formatLineFeed = m_format.first;
98    const String& formatPropertyPrefix = m_format.second;
99    if (insertLastInSource) {
100        long formatPropertyPrefixLength = formatPropertyPrefix.length();
101        if (!formattingPrependOffset && (propertyStart < formatPropertyPrefixLength || m_styleText.substring(propertyStart - formatPropertyPrefixLength, formatPropertyPrefixLength) != formatPropertyPrefix)) {
102            textToSet.insert(formatPropertyPrefix, formattingPrependOffset);
103            if (!propertyStart || !isHTMLLineBreak(m_styleText[propertyStart - 1]))
104                textToSet.insert(formatLineFeed, formattingPrependOffset);
105        }
106        if (!isHTMLLineBreak(m_styleText[propertyStart]))
107            textToSet.append(formatLineFeed);
108    } else {
109        String fullPrefix = formatLineFeed + formatPropertyPrefix;
110        long fullPrefixLength = fullPrefix.length();
111        textToSet.append(fullPrefix);
112        if (insertFirstInSource && (propertyStart < fullPrefixLength || m_styleText.substring(propertyStart - fullPrefixLength, fullPrefixLength) != fullPrefix))
113            textToSet.insert(fullPrefix, formattingPrependOffset);
114    }
115    m_styleText.insert(textToSet, propertyStart);
116
117    // Recompute disabled property ranges after an inserted property.
118    long propertyLengthDelta = textToSet.length();
119    shiftDisabledProperties(disabledIndexByOrdinal(index, true), propertyLengthDelta);
120}
121
122void InspectorStyleTextEditor::replaceProperty(unsigned index, const String& newText)
123{
124    ASSERT_WITH_SECURITY_IMPLICATION(index < m_allProperties->size());
125
126    const InspectorStyleProperty& property = m_allProperties->at(index);
127    long propertyStart = property.sourceData.range.start;
128    long propertyEnd = property.sourceData.range.end;
129    long oldLength = propertyEnd - propertyStart;
130    long newLength = newText.length();
131    long propertyLengthDelta = newLength - oldLength;
132
133    if (!property.disabled) {
134        SourceRange overwrittenRange;
135        unsigned insertedLength;
136        internalReplaceProperty(property, newText, &overwrittenRange, &insertedLength);
137        propertyLengthDelta = static_cast<long>(insertedLength) - static_cast<long>(overwrittenRange.length());
138
139        // Recompute subsequent disabled property ranges if acting on a non-disabled property.
140        shiftDisabledProperties(disabledIndexByOrdinal(index, true), propertyLengthDelta);
141    } else {
142        long textLength = newText.length();
143        unsigned disabledIndex = disabledIndexByOrdinal(index, false);
144        if (!textLength) {
145            // Delete disabled property.
146            m_disabledProperties->remove(disabledIndex);
147        } else {
148            // Patch disabled property text.
149            m_disabledProperties->at(disabledIndex).rawText = newText;
150        }
151    }
152}
153
154void InspectorStyleTextEditor::removeProperty(unsigned index)
155{
156    replaceProperty(index, "");
157}
158
159void InspectorStyleTextEditor::enableProperty(unsigned index)
160{
161    ASSERT(m_allProperties->at(index).disabled);
162
163    unsigned disabledIndex = disabledIndexByOrdinal(index, false);
164    ASSERT(disabledIndex != UINT_MAX);
165
166    InspectorStyleProperty disabledProperty = m_disabledProperties->at(disabledIndex);
167    m_disabledProperties->remove(disabledIndex);
168    SourceRange removedRange;
169    unsigned insertedLength;
170    internalReplaceProperty(disabledProperty, disabledProperty.rawText, &removedRange, &insertedLength);
171    shiftDisabledProperties(disabledIndex, static_cast<long>(insertedLength) - static_cast<long>(removedRange.length()));
172}
173
174void InspectorStyleTextEditor::disableProperty(unsigned index)
175{
176    ASSERT(!m_allProperties->at(index).disabled);
177
178    const InspectorStyleProperty& property = m_allProperties->at(index);
179    InspectorStyleProperty disabledProperty(property);
180    disabledProperty.setRawTextFromStyleDeclaration(m_styleText);
181    disabledProperty.disabled = true;
182
183    SourceRange removedRange;
184    unsigned insertedLength;
185    internalReplaceProperty(property, "", &removedRange, &insertedLength);
186
187    // If some preceding formatting has been removed, move the property to the start of the removed range.
188    if (property.sourceData.range.start > removedRange.start)
189        disabledProperty.sourceData.range.start = removedRange.start;
190    disabledProperty.sourceData.range.end = disabledProperty.sourceData.range.start;
191
192    // Add disabled property at correct position.
193    unsigned insertionIndex = disabledIndexByOrdinal(index, true);
194    if (insertionIndex == UINT_MAX)
195        m_disabledProperties->append(disabledProperty);
196    else {
197        m_disabledProperties->insert(insertionIndex, disabledProperty);
198        long styleLengthDelta = -(static_cast<long>(removedRange.length()));
199        shiftDisabledProperties(insertionIndex + 1, styleLengthDelta); // Property removed from text - shift these back.
200    }
201}
202
203unsigned InspectorStyleTextEditor::disabledIndexByOrdinal(unsigned ordinal, bool canUseSubsequent)
204{
205    unsigned disabledIndex = 0;
206    for (unsigned i = 0, size = m_allProperties->size(); i < size; ++i) {
207        if (m_allProperties->at(i).disabled) {
208            if (i == ordinal || (canUseSubsequent && i > ordinal))
209                return disabledIndex;
210            ++disabledIndex;
211        }
212    }
213
214    return UINT_MAX;
215}
216
217void InspectorStyleTextEditor::shiftDisabledProperties(unsigned fromIndex, long delta)
218{
219    for (unsigned i = fromIndex, size = m_disabledProperties->size(); i < size; ++i) {
220        SourceRange& range = m_disabledProperties->at(i).sourceData.range;
221        range.start += delta;
222        range.end += delta;
223    }
224}
225
226void InspectorStyleTextEditor::internalReplaceProperty(const InspectorStyleProperty& property, const String& newText, SourceRange* removedRange, unsigned* insertedLength)
227{
228    const SourceRange& range = property.sourceData.range;
229    long replaceRangeStart = range.start;
230    long replaceRangeEnd = range.end;
231    long newTextLength = newText.length();
232    String finalNewText = newText;
233
234    // Removing a property - remove preceding prefix.
235    String fullPrefix = m_format.first + m_format.second;
236    long fullPrefixLength = fullPrefix.length();
237    if (!newTextLength && fullPrefixLength) {
238        if (replaceRangeStart >= fullPrefixLength && m_styleText.substring(replaceRangeStart - fullPrefixLength, fullPrefixLength) == fullPrefix)
239            replaceRangeStart -= fullPrefixLength;
240    } else if (newTextLength) {
241        if (isHTMLLineBreak(newText[newTextLength - 1])) {
242            // Coalesce newlines of the original and new property values (to avoid a lot of blank lines while incrementally applying property values).
243            bool foundNewline = false;
244            bool isLastNewline = false;
245            int i;
246            int textLength = m_styleText.length();
247            for (i = replaceRangeEnd; i < textLength && isSpaceOrNewline(m_styleText[i]); ++i) {
248                isLastNewline = isHTMLLineBreak(m_styleText[i]);
249                if (isLastNewline)
250                    foundNewline = true;
251                else if (foundNewline && !isLastNewline) {
252                    replaceRangeEnd = i;
253                    break;
254                }
255            }
256            if (foundNewline && isLastNewline)
257                replaceRangeEnd = i;
258        }
259
260        if (fullPrefixLength > replaceRangeStart || m_styleText.substring(replaceRangeStart - fullPrefixLength, fullPrefixLength) != fullPrefix)
261            finalNewText = fullPrefix + finalNewText;
262    }
263
264    int replacedLength = replaceRangeEnd - replaceRangeStart;
265    m_styleText.replace(replaceRangeStart, replacedLength, finalNewText);
266    *removedRange = SourceRange(replaceRangeStart, replaceRangeEnd);
267    *insertedLength = finalNewText.length();
268}
269
270} // namespace WebCore
271
272#endif // ENABLE(INSPECTOR)
273