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 35namespace WebCore { 36 37InspectorStyleTextEditor::InspectorStyleTextEditor(Vector<InspectorStyleProperty>* allProperties, Vector<InspectorStyleProperty>* disabledProperties, const String& styleText, const NewLineAndWhitespace& format) 38 : m_allProperties(allProperties) 39 , m_disabledProperties(disabledProperties) 40 , m_styleText(styleText) 41 , m_format(format) 42{ 43} 44 45void InspectorStyleTextEditor::insertProperty(unsigned index, const String& propertyText, unsigned styleBodyLength) 46{ 47 long propertyStart = 0; 48 49 bool insertLast = true; 50 if (index < m_allProperties->size()) { 51 const InspectorStyleProperty& property = m_allProperties->at(index); 52 if (property.hasSource) { 53 propertyStart = property.sourceData.range.start; 54 // If inserting before a disabled property, it should be shifted, too. 55 insertLast = false; 56 } 57 } 58 59 bool insertFirstInSource = true; 60 for (unsigned i = 0, size = m_allProperties->size(); i < index && i < size; ++i) { 61 const InspectorStyleProperty& property = m_allProperties->at(i); 62 if (property.hasSource && !property.disabled) { 63 insertFirstInSource = false; 64 break; 65 } 66 } 67 68 bool insertLastInSource = true; 69 for (unsigned i = index, size = m_allProperties->size(); i < size; ++i) { 70 const InspectorStyleProperty& property = m_allProperties->at(i); 71 if (property.hasSource && !property.disabled) { 72 insertLastInSource = false; 73 break; 74 } 75 } 76 77 String textToSet = propertyText; 78 79 int formattingPrependOffset = 0; 80 if (insertLast && !insertFirstInSource) { 81 propertyStart = styleBodyLength; 82 if (propertyStart && textToSet.length()) { 83 const UChar* characters = m_styleText.characters(); 84 85 long curPos = propertyStart - 1; // The last position of style declaration, since propertyStart points past one. 86 while (curPos && isHTMLSpace(characters[curPos])) 87 --curPos; 88 if (curPos && characters[curPos] != ';') { 89 // Prepend a ";" to the property text if appending to a style declaration where 90 // the last property has no trailing ";". 91 textToSet.insert(";", 0); 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 const UChar* characters = m_styleText.characters(); 232 long newTextLength = newText.length(); 233 String finalNewText = newText; 234 235 // Removing a property - remove preceding prefix. 236 String fullPrefix = m_format.first + m_format.second; 237 long fullPrefixLength = fullPrefix.length(); 238 if (!newTextLength && fullPrefixLength) { 239 if (replaceRangeStart >= fullPrefixLength && m_styleText.substring(replaceRangeStart - fullPrefixLength, fullPrefixLength) == fullPrefix) 240 replaceRangeStart -= fullPrefixLength; 241 } else if (newTextLength) { 242 if (isHTMLLineBreak(newText.characters()[newTextLength - 1])) { 243 // Coalesce newlines of the original and new property values (to avoid a lot of blank lines while incrementally applying property values). 244 bool foundNewline = false; 245 bool isLastNewline = false; 246 int i; 247 int textLength = m_styleText.length(); 248 for (i = replaceRangeEnd; i < textLength && isSpaceOrNewline(characters[i]); ++i) { 249 isLastNewline = isHTMLLineBreak(characters[i]); 250 if (isLastNewline) 251 foundNewline = true; 252 else if (foundNewline && !isLastNewline) { 253 replaceRangeEnd = i; 254 break; 255 } 256 } 257 if (foundNewline && isLastNewline) 258 replaceRangeEnd = i; 259 } 260 261 if (fullPrefixLength > replaceRangeStart || m_styleText.substring(replaceRangeStart - fullPrefixLength, fullPrefixLength) != fullPrefix) 262 finalNewText.insert(fullPrefix, 0); 263 } 264 265 int replacedLength = replaceRangeEnd - replaceRangeStart; 266 m_styleText.replace(replaceRangeStart, replacedLength, finalNewText); 267 *removedRange = SourceRange(replaceRangeStart, replaceRangeEnd); 268 *insertedLength = finalNewText.length(); 269} 270 271} // namespace WebCore 272 273#endif // ENABLE(INSPECTOR) 274