04d47cc908d201014e161878587ede3fb7bd5a86
[WebKit.git] / Source / WebCore / dom / StyledElement.cpp
1 /*
2  * Copyright (C) 1999 Lars Knoll (knoll@kde.org)
3  *           (C) 1999 Antti Koivisto (koivisto@kde.org)
4  *           (C) 2001 Peter Kelly (pmk@post.com)
5  *           (C) 2001 Dirk Mueller (mueller@kde.org)
6  * Copyright (C) 2004, 2005, 2006, 2008, 2010 Apple Inc. All rights reserved.
7  *
8  * This library is free software; you can redistribute it and/or
9  * modify it under the terms of the GNU Library General Public
10  * License as published by the Free Software Foundation; either
11  * version 2 of the License, or (at your option) any later version.
12  *
13  * This library is distributed in the hope that it will be useful,
14  * but WITHOUT ANY WARRANTY; without even the implied warranty of
15  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
16  * Library General Public License for more details.
17  *
18  * You should have received a copy of the GNU Library General Public License
19  * along with this library; see the file COPYING.LIB.  If not, write to
20  * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
21  * Boston, MA 02110-1301, USA.
22  */
23
24 #include "config.h"
25 #include "StyledElement.h"
26
27 #include "Attribute.h"
28 #include "CSSImageValue.h"
29 #include "CSSStyleSelector.h"
30 #include "CSSStyleSheet.h"
31 #include "CSSValueKeywords.h"
32 #include "Color.h"
33 #include "ClassList.h"
34 #include "ContentSecurityPolicy.h"
35 #include "DOMTokenList.h"
36 #include "Document.h"
37 #include "HTMLNames.h"
38 #include "HTMLParserIdioms.h"
39 #include "StylePropertySet.h"
40 #include <wtf/HashFunctions.h>
41
42 using namespace std;
43
44 namespace WebCore {
45
46 using namespace HTMLNames;
47
48 void StyledElement::updateStyleAttribute() const
49 {
50     ASSERT(!isStyleAttributeValid());
51     setIsStyleAttributeValid();
52     setIsSynchronizingStyleAttribute();
53     if (StylePropertySet* inlineStyle = inlineStyleDecl())
54         const_cast<StyledElement*>(this)->setAttribute(styleAttr, inlineStyle->asText());
55     clearIsSynchronizingStyleAttribute();
56 }
57
58 StyledElement::~StyledElement()
59 {
60     destroyInlineStyleDecl();
61 }
62
63 PassRefPtr<Attribute> StyledElement::createAttribute(const QualifiedName& name, const AtomicString& value)
64 {
65     return Attribute::createMapped(name, value);
66 }
67
68 void StyledElement::attributeChanged(Attribute* attr)
69 {
70     if (attr->name() == HTMLNames::nameAttr)
71         setHasName(!attr->isNull());
72
73     if (!attr->isMappedAttribute()) {
74         Element::attributeChanged(attr);
75         return;
76     }
77
78     if (!(attr->name() == styleAttr && isSynchronizingStyleAttribute()))
79         parseMappedAttribute(attr);
80
81     recalcStyleIfNeededAfterAttributeChanged(attr);
82     updateAfterAttributeChanged(attr);
83 }
84
85 void StyledElement::classAttributeChanged(const AtomicString& newClassString)
86 {
87     const UChar* characters = newClassString.characters();
88     unsigned length = newClassString.length();
89     unsigned i;
90     for (i = 0; i < length; ++i) {
91         if (isNotHTMLSpace(characters[i]))
92             break;
93     }
94     bool hasClass = i < length;
95     setHasClass(hasClass);
96     if (hasClass) {
97         const bool shouldFoldCase = document()->inQuirksMode();
98         ensureAttributeData()->setClass(newClassString, shouldFoldCase);
99         if (DOMTokenList* classList = optionalClassList())
100             static_cast<ClassList*>(classList)->reset(newClassString);
101     } else if (attributeData())
102         attributeData()->clearClass();
103     setNeedsStyleRecalc();
104     dispatchSubtreeModifiedEvent();
105 }
106
107 void StyledElement::parseMappedAttribute(Attribute* attr)
108 {
109     if (isIdAttributeName(attr->name()))
110         idAttributeChanged(attr);
111     else if (attr->name() == classAttr)
112         classAttributeChanged(attr->value());
113     else if (attr->name() == styleAttr) {
114         if (attr->isNull())
115             destroyInlineStyleDecl();
116         else if (document()->contentSecurityPolicy()->allowInlineStyle())
117             ensureInlineStyleDecl()->parseDeclaration(attr->value());
118         setIsStyleAttributeValid();
119         setNeedsStyleRecalc();
120     }
121 }
122
123 void StyledElement::removeCSSProperties(int id1, int id2, int id3, int id4, int id5, int id6, int id7, int id8)
124 {
125     StylePropertySet* style = attributeStyle();
126     if (!style)
127         return;
128
129     setNeedsStyleRecalc(FullStyleChange);
130
131     ASSERT(id1 != CSSPropertyInvalid);
132     style->removeProperty(id1);
133
134     if (id2 == CSSPropertyInvalid)
135         return;
136     style->removeProperty(id2);
137     if (id3 == CSSPropertyInvalid)
138         return;
139     style->removeProperty(id3);
140     if (id4 == CSSPropertyInvalid)
141         return;
142     style->removeProperty(id4);
143     if (id5 == CSSPropertyInvalid)
144         return;
145     style->removeProperty(id5);
146     if (id6 == CSSPropertyInvalid)
147         return;
148     style->removeProperty(id6);
149     if (id7 == CSSPropertyInvalid)
150         return;
151     style->removeProperty(id7);
152     if (id8 == CSSPropertyInvalid)
153         return;
154     style->removeProperty(id8);
155 }
156
157 void StyledElement::addCSSProperty(int id, const String &value)
158 {
159     if (!ensureAttributeStyle()->setProperty(id, value))
160         removeCSSProperty(id);
161     else
162         setNeedsStyleRecalc(FullStyleChange);
163 }
164
165 void StyledElement::addCSSProperty(int id, int value)
166 {
167     ensureAttributeStyle()->setProperty(id, value);
168     setNeedsStyleRecalc(FullStyleChange);
169 }
170
171 void StyledElement::addCSSImageProperty(int id, const String& url)
172 {
173     ensureAttributeStyle()->setProperty(CSSProperty(id, CSSImageValue::create(url)));
174     setNeedsStyleRecalc(FullStyleChange);
175 }
176
177 void StyledElement::addCSSLength(int id, const String &value)
178 {
179     // FIXME: This function should not spin up the CSS parser, but should instead just figure out the correct
180     // length unit and make the appropriate parsed value.
181
182     // strip attribute garbage..
183     StringImpl* v = value.impl();
184     if (v) {
185         unsigned int l = 0;
186         
187         while (l < v->length() && (*v)[l] <= ' ')
188             l++;
189         
190         for (; l < v->length(); l++) {
191             UChar cc = (*v)[l];
192             if (cc > '9')
193                 break;
194             if (cc < '0') {
195                 if (cc == '%' || cc == '*')
196                     l++;
197                 if (cc != '.')
198                     break;
199             }
200         }
201
202         if (l != v->length()) {
203             addCSSProperty(id, v->substring(0, l));
204             return;
205         }
206     }
207
208     addCSSProperty(id, value);
209 }
210
211 static String parseColorStringWithCrazyLegacyRules(const String& colorString)
212 {
213     // Per spec, only look at the first 128 digits of the string.
214     const size_t maxColorLength = 128;
215     // We'll pad the buffer with two extra 0s later, so reserve two more than the max.
216     Vector<char, maxColorLength+2> digitBuffer;
217     
218     size_t i = 0;
219     // Skip a leading #.
220     if (colorString[0] == '#')
221         i = 1;
222     
223     // Grab the first 128 characters, replacing non-hex characters with 0.
224     // Non-BMP characters are replaced with "00" due to them appearing as two "characters" in the String.
225     for (; i < colorString.length() && digitBuffer.size() < maxColorLength; i++) {
226         if (!isASCIIHexDigit(colorString[i]))
227             digitBuffer.append('0');
228         else
229             digitBuffer.append(colorString[i]);
230     }
231
232     if (!digitBuffer.size())
233         return "#000000";
234
235     // Pad the buffer out to at least the next multiple of three in size.
236     digitBuffer.append('0');
237     digitBuffer.append('0');
238
239     if (digitBuffer.size() < 6)
240         return String::format("#0%c0%c0%c", digitBuffer[0], digitBuffer[1], digitBuffer[2]);
241     
242     // Split the digits into three components, then search the last 8 digits of each component.
243     ASSERT(digitBuffer.size() >= 6);
244     size_t componentLength = digitBuffer.size() / 3;
245     size_t componentSearchWindowLength = min<size_t>(componentLength, 8);
246     size_t redIndex = componentLength - componentSearchWindowLength;
247     size_t greenIndex = componentLength * 2 - componentSearchWindowLength;
248     size_t blueIndex = componentLength * 3 - componentSearchWindowLength;
249     // Skip digits until one of them is non-zero, or we've only got two digits left in the component.
250     while (digitBuffer[redIndex] == '0' && digitBuffer[greenIndex] == '0' && digitBuffer[blueIndex] == '0' && (componentLength - redIndex) > 2) {
251         redIndex++;
252         greenIndex++;
253         blueIndex++;
254     }
255     ASSERT(redIndex + 1 < componentLength);
256     ASSERT(greenIndex >= componentLength);
257     ASSERT(greenIndex + 1 < componentLength * 2);
258     ASSERT(blueIndex >= componentLength * 2);
259     ASSERT(blueIndex + 1 < digitBuffer.size());
260     return String::format("#%c%c%c%c%c%c", digitBuffer[redIndex], digitBuffer[redIndex + 1], digitBuffer[greenIndex], digitBuffer[greenIndex + 1], digitBuffer[blueIndex], digitBuffer[blueIndex + 1]);   
261 }
262
263 // Color parsing that matches HTML's "rules for parsing a legacy color value"
264 void StyledElement::addCSSColor(int id, const String& attributeValue)
265 {
266     // An empty string doesn't apply a color. (One containing only whitespace does, which is why this check occurs before stripping.)
267     if (attributeValue.isEmpty()) {
268         removeCSSProperty(id);
269         return;
270     }
271
272     String colorString = attributeValue.stripWhiteSpace();
273
274     // "transparent" doesn't apply a color either.
275     if (equalIgnoringCase(colorString, "transparent")) {
276         removeCSSProperty(id);
277         return;
278     }
279
280     // If the string is a named CSS color or a 3/6-digit hex color, use that.
281     Color parsedColor(colorString);
282     if (parsedColor.isValid()) {
283         addCSSProperty(id, colorString);
284         return;
285     }
286
287     addCSSProperty(id, parseColorStringWithCrazyLegacyRules(colorString));
288 }
289
290 void StyledElement::copyNonAttributeProperties(const Element* sourceElement)
291 {
292     ASSERT(sourceElement);
293     ASSERT(sourceElement->isStyledElement());
294
295     const StyledElement* source = static_cast<const StyledElement*>(sourceElement);
296     if (!source->inlineStyleDecl())
297         return;
298
299     StylePropertySet* inlineStyle = ensureInlineStyleDecl();
300     inlineStyle->copyPropertiesFrom(*source->inlineStyleDecl());
301     inlineStyle->setStrictParsing(source->inlineStyleDecl()->useStrictParsing());
302
303     setIsStyleAttributeValid(source->isStyleAttributeValid());
304     setIsSynchronizingStyleAttribute(source->isSynchronizingStyleAttribute());
305     
306     Element::copyNonAttributeProperties(sourceElement);
307 }
308
309 void StyledElement::addSubresourceAttributeURLs(ListHashSet<KURL>& urls) const
310 {
311     if (StylePropertySet* inlineStyle = inlineStyleDecl())
312         inlineStyle->addSubresourceStyleURLs(urls);
313 }
314
315 }