Remove mapped vs non-mapped attribute distinction.
[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 void StyledElement::attributeChanged(Attribute* attr)
64 {
65     if (!(attr->name() == styleAttr && isSynchronizingStyleAttribute()))
66         parseMappedAttribute(attr);
67
68     Element::attributeChanged(attr);
69 }
70
71 void StyledElement::classAttributeChanged(const AtomicString& newClassString)
72 {
73     const UChar* characters = newClassString.characters();
74     unsigned length = newClassString.length();
75     unsigned i;
76     for (i = 0; i < length; ++i) {
77         if (isNotHTMLSpace(characters[i]))
78             break;
79     }
80     bool hasClass = i < length;
81     setHasClass(hasClass);
82     if (hasClass) {
83         const bool shouldFoldCase = document()->inQuirksMode();
84         ensureAttributeData()->setClass(newClassString, shouldFoldCase);
85         if (DOMTokenList* classList = optionalClassList())
86             static_cast<ClassList*>(classList)->reset(newClassString);
87     } else if (attributeData())
88         attributeData()->clearClass();
89     setNeedsStyleRecalc();
90     dispatchSubtreeModifiedEvent();
91 }
92
93 void StyledElement::parseMappedAttribute(Attribute* attr)
94 {
95     if (attr->name() == classAttr)
96         classAttributeChanged(attr->value());
97     else if (attr->name() == styleAttr) {
98         if (attr->isNull())
99             destroyInlineStyleDecl();
100         else if (document()->contentSecurityPolicy()->allowInlineStyle())
101             ensureInlineStyleDecl()->parseDeclaration(attr->value());
102         setIsStyleAttributeValid();
103         setNeedsStyleRecalc();
104     }
105 }
106
107 void StyledElement::removeCSSProperties(int id1, int id2, int id3, int id4, int id5, int id6, int id7, int id8)
108 {
109     StylePropertySet* style = attributeStyle();
110     if (!style)
111         return;
112
113     setNeedsStyleRecalc(FullStyleChange);
114
115     ASSERT(id1 != CSSPropertyInvalid);
116     style->removeProperty(id1);
117
118     if (id2 == CSSPropertyInvalid)
119         return;
120     style->removeProperty(id2);
121     if (id3 == CSSPropertyInvalid)
122         return;
123     style->removeProperty(id3);
124     if (id4 == CSSPropertyInvalid)
125         return;
126     style->removeProperty(id4);
127     if (id5 == CSSPropertyInvalid)
128         return;
129     style->removeProperty(id5);
130     if (id6 == CSSPropertyInvalid)
131         return;
132     style->removeProperty(id6);
133     if (id7 == CSSPropertyInvalid)
134         return;
135     style->removeProperty(id7);
136     if (id8 == CSSPropertyInvalid)
137         return;
138     style->removeProperty(id8);
139 }
140
141 void StyledElement::addCSSProperty(int id, const String &value)
142 {
143     if (!ensureAttributeStyle()->setProperty(id, value))
144         removeCSSProperty(id);
145     else
146         setNeedsStyleRecalc(FullStyleChange);
147 }
148
149 void StyledElement::addCSSProperty(int id, int value)
150 {
151     ensureAttributeStyle()->setProperty(id, value);
152     setNeedsStyleRecalc(FullStyleChange);
153 }
154
155 void StyledElement::addCSSImageProperty(int id, const String& url)
156 {
157     ensureAttributeStyle()->setProperty(CSSProperty(id, CSSImageValue::create(url)));
158     setNeedsStyleRecalc(FullStyleChange);
159 }
160
161 void StyledElement::addCSSLength(int id, const String &value)
162 {
163     // FIXME: This function should not spin up the CSS parser, but should instead just figure out the correct
164     // length unit and make the appropriate parsed value.
165
166     // strip attribute garbage..
167     StringImpl* v = value.impl();
168     if (v) {
169         unsigned int l = 0;
170         
171         while (l < v->length() && (*v)[l] <= ' ')
172             l++;
173         
174         for (; l < v->length(); l++) {
175             UChar cc = (*v)[l];
176             if (cc > '9')
177                 break;
178             if (cc < '0') {
179                 if (cc == '%' || cc == '*')
180                     l++;
181                 if (cc != '.')
182                     break;
183             }
184         }
185
186         if (l != v->length()) {
187             addCSSProperty(id, v->substring(0, l));
188             return;
189         }
190     }
191
192     addCSSProperty(id, value);
193 }
194
195 static String parseColorStringWithCrazyLegacyRules(const String& colorString)
196 {
197     // Per spec, only look at the first 128 digits of the string.
198     const size_t maxColorLength = 128;
199     // We'll pad the buffer with two extra 0s later, so reserve two more than the max.
200     Vector<char, maxColorLength+2> digitBuffer;
201     
202     size_t i = 0;
203     // Skip a leading #.
204     if (colorString[0] == '#')
205         i = 1;
206     
207     // Grab the first 128 characters, replacing non-hex characters with 0.
208     // Non-BMP characters are replaced with "00" due to them appearing as two "characters" in the String.
209     for (; i < colorString.length() && digitBuffer.size() < maxColorLength; i++) {
210         if (!isASCIIHexDigit(colorString[i]))
211             digitBuffer.append('0');
212         else
213             digitBuffer.append(colorString[i]);
214     }
215
216     if (!digitBuffer.size())
217         return "#000000";
218
219     // Pad the buffer out to at least the next multiple of three in size.
220     digitBuffer.append('0');
221     digitBuffer.append('0');
222
223     if (digitBuffer.size() < 6)
224         return String::format("#0%c0%c0%c", digitBuffer[0], digitBuffer[1], digitBuffer[2]);
225     
226     // Split the digits into three components, then search the last 8 digits of each component.
227     ASSERT(digitBuffer.size() >= 6);
228     size_t componentLength = digitBuffer.size() / 3;
229     size_t componentSearchWindowLength = min<size_t>(componentLength, 8);
230     size_t redIndex = componentLength - componentSearchWindowLength;
231     size_t greenIndex = componentLength * 2 - componentSearchWindowLength;
232     size_t blueIndex = componentLength * 3 - componentSearchWindowLength;
233     // Skip digits until one of them is non-zero, or we've only got two digits left in the component.
234     while (digitBuffer[redIndex] == '0' && digitBuffer[greenIndex] == '0' && digitBuffer[blueIndex] == '0' && (componentLength - redIndex) > 2) {
235         redIndex++;
236         greenIndex++;
237         blueIndex++;
238     }
239     ASSERT(redIndex + 1 < componentLength);
240     ASSERT(greenIndex >= componentLength);
241     ASSERT(greenIndex + 1 < componentLength * 2);
242     ASSERT(blueIndex >= componentLength * 2);
243     ASSERT(blueIndex + 1 < digitBuffer.size());
244     return String::format("#%c%c%c%c%c%c", digitBuffer[redIndex], digitBuffer[redIndex + 1], digitBuffer[greenIndex], digitBuffer[greenIndex + 1], digitBuffer[blueIndex], digitBuffer[blueIndex + 1]);   
245 }
246
247 // Color parsing that matches HTML's "rules for parsing a legacy color value"
248 void StyledElement::addCSSColor(int id, const String& attributeValue)
249 {
250     // An empty string doesn't apply a color. (One containing only whitespace does, which is why this check occurs before stripping.)
251     if (attributeValue.isEmpty()) {
252         removeCSSProperty(id);
253         return;
254     }
255
256     String colorString = attributeValue.stripWhiteSpace();
257
258     // "transparent" doesn't apply a color either.
259     if (equalIgnoringCase(colorString, "transparent")) {
260         removeCSSProperty(id);
261         return;
262     }
263
264     // If the string is a named CSS color or a 3/6-digit hex color, use that.
265     Color parsedColor(colorString);
266     if (parsedColor.isValid()) {
267         addCSSProperty(id, colorString);
268         return;
269     }
270
271     addCSSProperty(id, parseColorStringWithCrazyLegacyRules(colorString));
272 }
273
274 void StyledElement::copyNonAttributeProperties(const Element* sourceElement)
275 {
276     ASSERT(sourceElement);
277     ASSERT(sourceElement->isStyledElement());
278
279     const StyledElement* source = static_cast<const StyledElement*>(sourceElement);
280     if (!source->inlineStyleDecl())
281         return;
282
283     StylePropertySet* inlineStyle = ensureInlineStyleDecl();
284     inlineStyle->copyPropertiesFrom(*source->inlineStyleDecl());
285     inlineStyle->setStrictParsing(source->inlineStyleDecl()->useStrictParsing());
286
287     setIsStyleAttributeValid(source->isStyleAttributeValid());
288     setIsSynchronizingStyleAttribute(source->isSynchronizingStyleAttribute());
289     
290     Element::copyNonAttributeProperties(sourceElement);
291 }
292
293 void StyledElement::addSubresourceAttributeURLs(ListHashSet<KURL>& urls) const
294 {
295     if (StylePropertySet* inlineStyle = inlineStyleDecl())
296         inlineStyle->addSubresourceStyleURLs(urls);
297 }
298
299 }