Make elements that don't have attributes smaller.
[WebKit-https.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 "CSSMutableStyleDeclaration.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 <wtf/HashFunctions.h>
40
41 using namespace std;
42
43 namespace WebCore {
44
45 using namespace HTMLNames;
46
47 struct MappedAttributeKey {
48     uint16_t type;
49     StringImpl* name;
50     StringImpl* value;
51     MappedAttributeKey(MappedAttributeEntry t = eNone, StringImpl* n = 0, StringImpl* v = 0)
52         : type(t), name(n), value(v) { }
53 };
54
55 static inline bool operator==(const MappedAttributeKey& a, const MappedAttributeKey& b)
56     { return a.type == b.type && a.name == b.name && a.value == b.value; } 
57
58 struct MappedAttributeKeyTraits : WTF::GenericHashTraits<MappedAttributeKey> {
59     static const bool emptyValueIsZero = true;
60     static const bool needsDestruction = false;
61     static void constructDeletedValue(MappedAttributeKey& slot) { slot.type = eLastEntry; }
62     static bool isDeletedValue(const MappedAttributeKey& value) { return value.type == eLastEntry; }
63 };
64
65 struct MappedAttributeHash {
66     static unsigned hash(const MappedAttributeKey&);
67     static bool equal(const MappedAttributeKey& a, const MappedAttributeKey& b) { return a == b; }
68     static const bool safeToCompareToEmptyOrDeleted = true;
69 };
70
71 typedef HashMap<MappedAttributeKey, CSSMappedAttributeDeclaration*, MappedAttributeHash, MappedAttributeKeyTraits> MappedAttributeDecls;
72
73 static MappedAttributeDecls* mappedAttributeDecls = 0;
74
75 CSSMappedAttributeDeclaration* StyledElement::getMappedAttributeDecl(MappedAttributeEntry entryType, Attribute* attr)
76 {
77     if (!mappedAttributeDecls)
78         return 0;
79     return mappedAttributeDecls->get(MappedAttributeKey(entryType, attr->name().localName().impl(), attr->value().impl()));
80 }
81
82 CSSMappedAttributeDeclaration* StyledElement::getMappedAttributeDecl(MappedAttributeEntry type, const QualifiedName& name, const AtomicString& value)
83 {
84     if (!mappedAttributeDecls)
85         return 0;
86     return mappedAttributeDecls->get(MappedAttributeKey(type, name.localName().impl(), value.impl()));
87 }
88
89 void StyledElement::setMappedAttributeDecl(MappedAttributeEntry entryType, Attribute* attr, CSSMappedAttributeDeclaration* decl)
90 {
91     if (!mappedAttributeDecls)
92         mappedAttributeDecls = new MappedAttributeDecls;
93     mappedAttributeDecls->set(MappedAttributeKey(entryType, attr->name().localName().impl(), attr->value().impl()), decl);
94 }
95
96 void StyledElement::setMappedAttributeDecl(MappedAttributeEntry entryType, const QualifiedName& name, const AtomicString& value, CSSMappedAttributeDeclaration* decl)
97 {
98     if (!mappedAttributeDecls)
99         mappedAttributeDecls = new MappedAttributeDecls;
100     mappedAttributeDecls->set(MappedAttributeKey(entryType, name.localName().impl(), value.impl()), decl);
101 }
102
103 void StyledElement::removeMappedAttributeDecl(MappedAttributeEntry entryType, const QualifiedName& attrName, const AtomicString& attrValue)
104 {
105     if (!mappedAttributeDecls)
106         return;
107     mappedAttributeDecls->remove(MappedAttributeKey(entryType, attrName.localName().impl(), attrValue.impl()));
108 }
109
110 void StyledElement::updateStyleAttribute() const
111 {
112     ASSERT(!isStyleAttributeValid());
113     setIsStyleAttributeValid();
114     setIsSynchronizingStyleAttribute();
115     if (CSSMutableStyleDeclaration* inlineStyle = inlineStyleDecl())
116         const_cast<StyledElement*>(this)->setAttribute(styleAttr, inlineStyle->asText());
117     clearIsSynchronizingStyleAttribute();
118 }
119
120 StyledElement::~StyledElement()
121 {
122     destroyInlineStyleDecl();
123 }
124
125 PassRefPtr<Attribute> StyledElement::createAttribute(const QualifiedName& name, const AtomicString& value)
126 {
127     return Attribute::createMapped(name, value);
128 }
129
130 void StyledElement::attributeChanged(Attribute* attr, bool preserveDecls)
131 {
132     if (attr->name() == HTMLNames::nameAttr)
133         setHasName(!attr->isNull());
134
135     if (!attr->isMappedAttribute()) {
136         Element::attributeChanged(attr, preserveDecls);
137         return;
138     }
139  
140     if (attr->decl() && !preserveDecls) {
141         attr->setDecl(0);
142         setNeedsStyleRecalc();
143     }
144
145     bool checkDecl = true;
146     MappedAttributeEntry entry;
147     bool needToParse = mapToEntry(attr->name(), entry);
148     if (preserveDecls) {
149         if (attr->decl()) {
150             setNeedsStyleRecalc();
151             checkDecl = false;
152         }
153     } else if (!attr->isNull() && entry != eNone) {
154         CSSMappedAttributeDeclaration* decl = getMappedAttributeDecl(entry, attr);
155         if (decl) {
156             attr->setDecl(decl);
157             setNeedsStyleRecalc();
158             checkDecl = false;
159         } else
160             needToParse = true;
161     }
162
163     // parseMappedAttribute() might create a CSSMappedAttributeDeclaration on the attribute.  
164     // Normally we would be concerned about reseting the parent of those declarations in StyledElement::didMoveToNewDocument().
165     // But currently we always clear its parent and node below when adding it to the decl table.  
166     // If that changes for some reason moving between documents will be buggy.
167     // webarchive/adopt-attribute-styled-node-webarchive.html should catch any resulting crashes.
168     if (needToParse)
169         parseMappedAttribute(attr);
170
171     if (entry == eNone)
172         recalcStyleIfNeededAfterAttributeChanged(attr);
173
174     if (checkDecl && attr->decl()) {
175         // Add the decl to the table in the appropriate spot.
176         setMappedAttributeDecl(entry, attr, attr->decl());
177         attr->decl()->setMappedState(entry, attr->name(), attr->value());
178     }
179
180     updateAfterAttributeChanged(attr);
181 }
182
183 bool StyledElement::mapToEntry(const QualifiedName& attrName, MappedAttributeEntry& result) const
184 {
185     result = eNone;
186     if (attrName == styleAttr)
187         return !isSynchronizingStyleAttribute();
188     return true;
189 }
190
191 void StyledElement::classAttributeChanged(const AtomicString& newClassString)
192 {
193     const UChar* characters = newClassString.characters();
194     unsigned length = newClassString.length();
195     unsigned i;
196     for (i = 0; i < length; ++i) {
197         if (isNotHTMLSpace(characters[i]))
198             break;
199     }
200     bool hasClass = i < length;
201     setHasClass(hasClass);
202     if (hasClass) {
203         const bool shouldFoldCase = document()->inQuirksMode();
204         ensureAttributeData()->setClass(newClassString, shouldFoldCase);
205         if (DOMTokenList* classList = optionalClassList())
206             static_cast<ClassList*>(classList)->reset(newClassString);
207     } else if (attributeData())
208         attributeData()->clearClass();
209     setNeedsStyleRecalc();
210     dispatchSubtreeModifiedEvent();
211 }
212
213 void StyledElement::parseMappedAttribute(Attribute* attr)
214 {
215     if (isIdAttributeName(attr->name()))
216         idAttributeChanged(attr);
217     else if (attr->name() == classAttr)
218         classAttributeChanged(attr->value());
219     else if (attr->name() == styleAttr) {
220         if (attr->isNull())
221             destroyInlineStyleDecl();
222         else if (document()->contentSecurityPolicy()->allowInlineStyle())
223             ensureInlineStyleDecl()->parseDeclaration(attr->value());
224         setIsStyleAttributeValid();
225         setNeedsStyleRecalc();
226     }
227 }
228
229 void StyledElement::removeCSSProperty(Attribute* attribute, int id)
230 {
231     if (!attribute->decl())
232         createMappedDecl(attribute);
233     attribute->decl()->removeMappedProperty(this, id);
234 }
235
236 void StyledElement::addCSSProperty(Attribute* attribute, int id, const String &value)
237 {
238     if (!attribute->decl())
239         createMappedDecl(attribute);
240     attribute->decl()->setMappedProperty(this, id, value);
241 }
242
243 void StyledElement::addCSSProperty(Attribute* attribute, int id, int value)
244 {
245     if (!attribute->decl())
246         createMappedDecl(attribute);
247     attribute->decl()->setMappedProperty(this, id, value);
248 }
249
250 void StyledElement::addCSSImageProperty(Attribute* attribute, int id, const String& url)
251 {
252     if (!attribute->decl())
253         createMappedDecl(attribute);
254     attribute->decl()->setMappedImageProperty(this, id, url);
255 }
256
257 void StyledElement::addCSSLength(Attribute* attribute, int id, const String &value)
258 {
259     // FIXME: This function should not spin up the CSS parser, but should instead just figure out the correct
260     // length unit and make the appropriate parsed value.
261     if (!attribute->decl())
262         createMappedDecl(attribute);
263
264     // strip attribute garbage..
265     StringImpl* v = value.impl();
266     if (v) {
267         unsigned int l = 0;
268         
269         while (l < v->length() && (*v)[l] <= ' ')
270             l++;
271         
272         for (; l < v->length(); l++) {
273             UChar cc = (*v)[l];
274             if (cc > '9')
275                 break;
276             if (cc < '0') {
277                 if (cc == '%' || cc == '*')
278                     l++;
279                 if (cc != '.')
280                     break;
281             }
282         }
283
284         if (l != v->length()) {
285             attribute->decl()->setMappedLengthProperty(this, id, v->substring(0, l));
286             return;
287         }
288     }
289
290     attribute->decl()->setMappedLengthProperty(this, id, value);
291 }
292
293 static String parseColorStringWithCrazyLegacyRules(const String& colorString)
294 {
295     // Per spec, only look at the first 128 digits of the string.
296     const size_t maxColorLength = 128;
297     // We'll pad the buffer with two extra 0s later, so reserve two more than the max.
298     Vector<char, maxColorLength+2> digitBuffer;
299     
300     size_t i = 0;
301     // Skip a leading #.
302     if (colorString[0] == '#')
303         i = 1;
304     
305     // Grab the first 128 characters, replacing non-hex characters with 0.
306     // Non-BMP characters are replaced with "00" due to them appearing as two "characters" in the String.
307     for (; i < colorString.length() && digitBuffer.size() < maxColorLength; i++) {
308         if (!isASCIIHexDigit(colorString[i]))
309             digitBuffer.append('0');
310         else
311             digitBuffer.append(colorString[i]);
312     }
313
314     if (!digitBuffer.size())
315         return "#000000";
316
317     // Pad the buffer out to at least the next multiple of three in size.
318     digitBuffer.append('0');
319     digitBuffer.append('0');
320
321     if (digitBuffer.size() < 6)
322         return String::format("#0%c0%c0%c", digitBuffer[0], digitBuffer[1], digitBuffer[2]);
323     
324     // Split the digits into three components, then search the last 8 digits of each component.
325     ASSERT(digitBuffer.size() >= 6);
326     size_t componentLength = digitBuffer.size() / 3;
327     size_t componentSearchWindowLength = min<size_t>(componentLength, 8);
328     size_t redIndex = componentLength - componentSearchWindowLength;
329     size_t greenIndex = componentLength * 2 - componentSearchWindowLength;
330     size_t blueIndex = componentLength * 3 - componentSearchWindowLength;
331     // Skip digits until one of them is non-zero, or we've only got two digits left in the component.
332     while (digitBuffer[redIndex] == '0' && digitBuffer[greenIndex] == '0' && digitBuffer[blueIndex] == '0' && (componentLength - redIndex) > 2) {
333         redIndex++;
334         greenIndex++;
335         blueIndex++;
336     }
337     ASSERT(redIndex + 1 < componentLength);
338     ASSERT(greenIndex >= componentLength);
339     ASSERT(greenIndex + 1 < componentLength * 2);
340     ASSERT(blueIndex >= componentLength * 2);
341     ASSERT(blueIndex + 1 < digitBuffer.size());
342     return String::format("#%c%c%c%c%c%c", digitBuffer[redIndex], digitBuffer[redIndex + 1], digitBuffer[greenIndex], digitBuffer[greenIndex + 1], digitBuffer[blueIndex], digitBuffer[blueIndex + 1]);   
343 }
344
345 // Color parsing that matches HTML's "rules for parsing a legacy color value"
346 void StyledElement::addCSSColor(Attribute* attribute, int id, const String& attributeValue)
347 {
348     // An empty string doesn't apply a color. (One containing only whitespace does, which is why this check occurs before stripping.)
349     if (attributeValue.isEmpty())
350         return;
351
352     String colorString = attributeValue.stripWhiteSpace();
353
354     // "transparent" doesn't apply a color either.
355     if (equalIgnoringCase(colorString, "transparent"))
356         return;
357
358     if (!attribute->decl())
359         createMappedDecl(attribute);
360
361     // If the string is a named CSS color or a 3/6-digit hex color, use that.
362     Color parsedColor(colorString);
363     if (parsedColor.isValid()) {
364         attribute->decl()->setMappedProperty(this, id, colorString);
365         return;
366     }
367
368     attribute->decl()->setMappedProperty(this, id, parseColorStringWithCrazyLegacyRules(colorString));
369 }
370
371 void StyledElement::createMappedDecl(Attribute* attr)
372 {
373     RefPtr<CSSMappedAttributeDeclaration> decl = CSSMappedAttributeDeclaration::create();
374     attr->setDecl(decl);
375     ASSERT(!decl->useStrictParsing());
376 }
377
378 unsigned MappedAttributeHash::hash(const MappedAttributeKey& key)
379 {
380     COMPILE_ASSERT(sizeof(key.name) == 4 || sizeof(key.name) == 8, key_name_size);
381     COMPILE_ASSERT(sizeof(key.value) == 4 || sizeof(key.value) == 8, key_value_size);
382
383     StringHasher hasher;
384     const UChar* data;
385
386     data = reinterpret_cast<const UChar*>(&key.name);
387     hasher.addCharacters(data[0], data[1]);
388     if (sizeof(key.name) == 8)
389         hasher.addCharacters(data[2], data[3]);
390
391     data = reinterpret_cast<const UChar*>(&key.value);
392     hasher.addCharacters(data[0], data[1]);
393     if (sizeof(key.value) == 8)
394         hasher.addCharacters(data[2], data[3]);
395
396     return hasher.hash();
397 }
398
399 void StyledElement::copyNonAttributeProperties(const Element* sourceElement)
400 {
401     ASSERT(sourceElement);
402     ASSERT(sourceElement->isStyledElement());
403
404     const StyledElement* source = static_cast<const StyledElement*>(sourceElement);
405     if (!source->inlineStyleDecl())
406         return;
407
408     CSSMutableStyleDeclaration* inlineStyle = ensureInlineStyleDecl();
409     inlineStyle->copyPropertiesFrom(*source->inlineStyleDecl());
410     inlineStyle->setStrictParsing(source->inlineStyleDecl()->useStrictParsing());
411
412     setIsStyleAttributeValid(source->isStyleAttributeValid());
413     setIsSynchronizingStyleAttribute(source->isSynchronizingStyleAttribute());
414     
415     Element::copyNonAttributeProperties(sourceElement);
416 }
417
418 void StyledElement::addSubresourceAttributeURLs(ListHashSet<KURL>& urls) const
419 {
420     if (CSSMutableStyleDeclaration* inlineStyle = inlineStyleDecl())
421         inlineStyle->addSubresourceStyleURLs(urls);
422 }
423
424 }