WebCore:
[WebKit-https.git] / WebCore / dom / StyledElement.cpp
1 /**
2  * This file is part of the DOM implementation for KDE.
3  *
4  * Copyright (C) 1999 Lars Knoll (knoll@kde.org)
5  *           (C) 1999 Antti Koivisto (koivisto@kde.org)
6  *           (C) 2001 Peter Kelly (pmk@post.com)
7  *           (C) 2001 Dirk Mueller (mueller@kde.org)
8  * Copyright (C) 2004, 2005, 2006 Apple Computer, Inc.
9  *
10  * This library is free software; you can redistribute it and/or
11  * modify it under the terms of the GNU Library General Public
12  * License as published by the Free Software Foundation; either
13  * version 2 of the License, or (at your option) any later version.
14  *
15  * This library is distributed in the hope that it will be useful,
16  * but WITHOUT ANY WARRANTY; without even the implied warranty of
17  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
18  * Library General Public License for more details.
19  *
20  * You should have received a copy of the GNU Library General Public License
21  * along with this library; see the file COPYING.LIB.  If not, write to
22  * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
23  * Boston, MA 02110-1301, USA.
24  */
25
26 #include "config.h"
27 #include "StyledElement.h"
28
29 #include "CSSStyleSelector.h"
30 #include "CSSStyleSheet.h"
31 #include "CSSValueKeywords.h"
32 #include "Document.h"
33 #include "HTMLNames.h"
34
35 using namespace std;
36
37 namespace WebCore {
38
39 using namespace HTMLNames;
40
41 struct MappedAttributeKey {
42     uint16_t type;
43     StringImpl* name;
44     StringImpl* value;
45     MappedAttributeKey(MappedAttributeEntry t = eNone, StringImpl* n = 0, StringImpl* v = 0)
46         : type(t), name(n), value(v) { }
47 };
48
49 static inline bool operator==(const MappedAttributeKey& a, const MappedAttributeKey& b)
50     { return a.type == b.type && a.name == b.name && a.value == b.value; } 
51
52 struct MappedAttributeKeyTraits : WTF::GenericHashTraits<MappedAttributeKey> {
53     static const bool emptyValueIsZero = true;
54     static const bool needsDestruction = false;
55     static MappedAttributeKey deletedValue() { return eLastEntry; }
56 };
57
58 struct MappedAttributeHash {
59     static unsigned hash(const MappedAttributeKey&);
60     static bool equal(const MappedAttributeKey& a, const MappedAttributeKey& b) { return a == b; }
61     static const bool safeToCompareToEmptyOrDeleted = true;
62 };
63
64 typedef HashMap<MappedAttributeKey, CSSMappedAttributeDeclaration*, MappedAttributeHash, MappedAttributeKeyTraits> MappedAttributeDecls;
65
66 static MappedAttributeDecls* mappedAttributeDecls = 0;
67
68 CSSMappedAttributeDeclaration* StyledElement::getMappedAttributeDecl(MappedAttributeEntry entryType, Attribute* attr)
69 {
70     if (!mappedAttributeDecls)
71         return 0;
72     return mappedAttributeDecls->get(MappedAttributeKey(entryType, attr->name().localName().impl(), attr->value().impl()));
73 }
74
75 void StyledElement::setMappedAttributeDecl(MappedAttributeEntry entryType, Attribute* attr, CSSMappedAttributeDeclaration* decl)
76 {
77     if (!mappedAttributeDecls)
78         mappedAttributeDecls = new MappedAttributeDecls;
79     mappedAttributeDecls->set(MappedAttributeKey(entryType, attr->name().localName().impl(), attr->value().impl()), decl);
80 }
81
82 void StyledElement::removeMappedAttributeDecl(MappedAttributeEntry entryType,
83                                                   const QualifiedName& attrName, const AtomicString& attrValue)
84 {
85     if (!mappedAttributeDecls)
86         return;
87     mappedAttributeDecls->remove(MappedAttributeKey(entryType, attrName.localName().impl(), attrValue.impl()));
88 }
89
90 void StyledElement::invalidateStyleAttribute()
91 {
92     m_isStyleAttributeValid = false;
93 }
94
95 void StyledElement::updateStyleAttributeIfNeeded() const
96 {
97     if (!m_isStyleAttributeValid) {
98         m_isStyleAttributeValid = true;
99         m_synchronizingStyleAttribute = true;
100         if (m_inlineStyleDecl)
101             const_cast<StyledElement*>(this)->setAttribute(styleAttr, m_inlineStyleDecl->cssText());
102         m_synchronizingStyleAttribute = false;
103     }
104 }
105
106 inline static bool isClassWhitespace(UChar c)
107 {
108     return c == ' ' || c == '\r' || c == '\n' || c == '\t';
109 }
110
111 StyledElement::StyledElement(const QualifiedName& name, Document *doc)
112     : Element(name, doc)
113 {
114     m_isStyleAttributeValid = true;
115     m_synchronizingStyleAttribute = false;
116 }
117
118 StyledElement::~StyledElement()
119 {
120     destroyInlineStyleDecl();
121 }
122
123 Attribute* StyledElement::createAttribute(const QualifiedName& name, StringImpl* value)
124 {
125     return new MappedAttribute(name, value);
126 }
127
128 void StyledElement::createInlineStyleDecl()
129 {
130     m_inlineStyleDecl = new CSSMutableStyleDeclaration;
131     m_inlineStyleDecl->setParent(document()->elementSheet());
132     m_inlineStyleDecl->setNode(this);
133     m_inlineStyleDecl->setStrictParsing(isHTMLElement() && !document()->inCompatMode());
134 }
135
136 void StyledElement::destroyInlineStyleDecl()
137 {
138     if (m_inlineStyleDecl) {
139         m_inlineStyleDecl->setNode(0);
140         m_inlineStyleDecl->setParent(0);
141         m_inlineStyleDecl = 0;
142     }
143 }
144
145 void StyledElement::attributeChanged(Attribute* attr, bool preserveDecls)
146 {
147     MappedAttribute* mappedAttr = static_cast<MappedAttribute*>(attr);
148     if (mappedAttr->decl() && !preserveDecls) {
149         mappedAttr->setDecl(0);
150         setChanged();
151         if (namedAttrMap)
152             mappedAttributes()->declRemoved();
153     }
154
155     bool checkDecl = true;
156     MappedAttributeEntry entry;
157     bool needToParse = mapToEntry(attr->name(), entry);
158     if (preserveDecls) {
159         if (mappedAttr->decl()) {
160             setChanged();
161             if (namedAttrMap)
162                 mappedAttributes()->declAdded();
163             checkDecl = false;
164         }
165     }
166     else if (!attr->isNull() && entry != eNone) {
167         CSSMappedAttributeDeclaration* decl = getMappedAttributeDecl(entry, attr);
168         if (decl) {
169             mappedAttr->setDecl(decl);
170             setChanged();
171             if (namedAttrMap)
172                 mappedAttributes()->declAdded();
173             checkDecl = false;
174         } else
175             needToParse = true;
176     }
177
178     if (needToParse)
179         parseMappedAttribute(mappedAttr);
180
181     if (entry == eNone && ownerDocument()->styleSelector()->hasSelectorForAttribute(attr->name().localName()))
182         setChanged();
183
184     if (checkDecl && mappedAttr->decl()) {
185         // Add the decl to the table in the appropriate spot.
186         setMappedAttributeDecl(entry, attr, mappedAttr->decl());
187         mappedAttr->decl()->setMappedState(entry, attr->name(), attr->value());
188         mappedAttr->decl()->setParent(0);
189         mappedAttr->decl()->setNode(0);
190         if (namedAttrMap)
191             mappedAttributes()->declAdded();
192     }
193 }
194
195 bool StyledElement::mapToEntry(const QualifiedName& attrName, MappedAttributeEntry& result) const
196 {
197     result = eNone;
198     if (attrName == styleAttr)
199         return !m_synchronizingStyleAttribute;
200     return true;
201 }
202
203 void StyledElement::parseMappedAttribute(MappedAttribute *attr)
204 {
205     if (attr->name() == idAttr) {
206         // unique id
207         setHasID(!attr->isNull());
208         if (namedAttrMap) {
209             if (attr->isNull())
210                 namedAttrMap->setID(nullAtom);
211             else if (document()->inCompatMode() && !attr->value().impl()->isLower())
212                 namedAttrMap->setID(AtomicString(attr->value().domString().lower()));
213             else
214                 namedAttrMap->setID(attr->value());
215         }
216         setChanged();
217     } else if (attr->name() == classAttr) {
218         // class
219         bool hasClass = false;
220         if (!attr->isEmpty()) {
221             const AtomicString& value = attr->value();
222             unsigned len = value.length();
223             for (unsigned i = 0; i < len; ++i) {
224                 if (!isClassWhitespace(value[i])) {
225                     hasClass = true;
226                     break;
227                 }
228             }
229         }
230         setHasClass(hasClass);
231         if (namedAttrMap)
232             mappedAttributes()->parseClassAttribute(attr->value());
233         setChanged();
234     } else if (attr->name() == styleAttr) {
235         if (attr->isNull())
236             destroyInlineStyleDecl();
237         else
238             getInlineStyleDecl()->parseDeclaration(attr->value());
239         m_isStyleAttributeValid = true;
240         setChanged();
241     }
242 }
243
244 void StyledElement::createAttributeMap() const
245 {
246     namedAttrMap = new NamedMappedAttrMap(const_cast<StyledElement*>(this));
247 }
248
249 CSSMutableStyleDeclaration* StyledElement::getInlineStyleDecl()
250 {
251     if (!m_inlineStyleDecl)
252         createInlineStyleDecl();
253     return m_inlineStyleDecl.get();
254 }
255
256 CSSStyleDeclaration* StyledElement::style()
257 {
258     return getInlineStyleDecl();
259 }
260
261 CSSMutableStyleDeclaration* StyledElement::additionalAttributeStyleDecl()
262 {
263     return 0;
264 }
265
266 const AtomicStringList* StyledElement::getClassList() const
267 {
268     return namedAttrMap ? mappedAttributes()->getClassList() : 0;
269 }
270
271 static inline int toHex(UChar c) {
272     return ((c >= '0' && c <= '9') ? (c - '0')
273         : ((c >= 'a' && c <= 'f') ? (c - 'a' + 10)
274         : (( c >= 'A' && c <= 'F') ? (c - 'A' + 10)
275         : -1)));
276 }
277
278 void StyledElement::addCSSProperty(MappedAttribute* attr, int id, const String &value)
279 {
280     if (!attr->decl()) createMappedDecl(attr);
281     attr->decl()->setProperty(id, value, false);
282 }
283
284 void StyledElement::addCSSProperty(MappedAttribute* attr, int id, int value)
285 {
286     if (!attr->decl()) createMappedDecl(attr);
287     attr->decl()->setProperty(id, value, false);
288 }
289
290 void StyledElement::addCSSStringProperty(MappedAttribute* attr, int id, const String &value, CSSPrimitiveValue::UnitTypes type)
291 {
292     if (!attr->decl()) createMappedDecl(attr);
293     attr->decl()->setStringProperty(id, value, type, false);
294 }
295
296 void StyledElement::addCSSImageProperty(MappedAttribute* attr, int id, const String &URL)
297 {
298     if (!attr->decl()) createMappedDecl(attr);
299     attr->decl()->setImageProperty(id, URL, false);
300 }
301
302 void StyledElement::addCSSLength(MappedAttribute* attr, int id, const String &value)
303 {
304     // FIXME: This function should not spin up the CSS parser, but should instead just figure out the correct
305     // length unit and make the appropriate parsed value.
306     if (!attr->decl()) createMappedDecl(attr);
307
308     // strip attribute garbage..
309     StringImpl* v = value.impl();
310     if (v) {
311         unsigned int l = 0;
312         
313         while (l < v->length() && (*v)[l] <= ' ')
314             l++;
315         
316         for (; l < v->length(); l++) {
317             UChar cc = (*v)[l];
318             if (cc > '9' || (cc < '0' && cc != '*' && cc != '%' && cc != '.'))
319                 break;
320         }
321
322         if (l != v->length()) {
323             attr->decl()->setLengthProperty(id, v->substring(0, l), false);
324             return;
325         }
326     }
327     
328     attr->decl()->setLengthProperty(id, value, false);
329 }
330
331 /* color parsing that tries to match as close as possible IE 6. */
332 void StyledElement::addCSSColor(MappedAttribute* attr, int id, const String &c)
333 {
334     // this is the only case no color gets applied in IE.
335     if (!c.length())
336         return;
337
338     if (!attr->decl())
339         createMappedDecl(attr);
340     
341     if (attr->decl()->setProperty(id, c, false))
342         return;
343     
344     String color = c.copy();
345     // not something that fits the specs.
346     
347     // we're emulating IEs color parser here. It maps transparent to black, otherwise it tries to build a rgb value
348     // out of everyhting you put in. The algorithm is experimentally determined, but seems to work for all test cases I have.
349     
350     // the length of the color value is rounded up to the next
351     // multiple of 3. each part of the rgb triple then gets one third
352     // of the length.
353     //
354     // Each triplet is parsed byte by byte, mapping
355     // each number to a hex value (0-9a-fA-F to their values
356     // everything else to 0).
357     //
358     // The highest non zero digit in all triplets is remembered, and
359     // used as a normalization point to normalize to values between 0
360     // and 255.
361     
362     if (color.lower() != "transparent") {
363         if (color[0] == '#')
364             color.remove(0, 1);
365         int basicLength = (color.length() + 2) / 3;
366         if (basicLength > 1) {
367             // IE ignores colors with three digits or less
368             int colors[3] = { 0, 0, 0 };
369             int component = 0;
370             int pos = 0;
371             int maxDigit = basicLength-1;
372             while (component < 3) {
373                 // search forward for digits in the string
374                 int numDigits = 0;
375                 while (pos < (int)color.length() && numDigits < basicLength) {
376                     int hex = toHex(color[pos]);
377                     colors[component] = (colors[component] << 4);
378                     if (hex > 0) {
379                         colors[component] += hex;
380                         maxDigit = min(maxDigit, numDigits);
381                     }
382                     numDigits++;
383                     pos++;
384                 }
385                 while (numDigits++ < basicLength)
386                     colors[component] <<= 4;
387                 component++;
388             }
389             maxDigit = basicLength - maxDigit;
390             
391             // normalize to 00-ff. The highest filled digit counts, minimum is 2 digits
392             maxDigit -= 2;
393             colors[0] >>= 4*maxDigit;
394             colors[1] >>= 4*maxDigit;
395             colors[2] >>= 4*maxDigit;
396             // ASSERT(colors[0] < 0x100 && colors[1] < 0x100 && colors[2] < 0x100);
397             
398             color = String::format("#%02x%02x%02x", colors[0], colors[1], colors[2]);
399             if (attr->decl()->setProperty(id, color, false))
400                 return;
401         }
402     }
403     attr->decl()->setProperty(id, CSS_VAL_BLACK, false);
404 }
405
406 void StyledElement::createMappedDecl(MappedAttribute* attr)
407 {
408     CSSMappedAttributeDeclaration* decl = new CSSMappedAttributeDeclaration(0);
409     attr->setDecl(decl);
410     decl->setParent(document()->elementSheet());
411     decl->setNode(this);
412     decl->setStrictParsing(false); // Mapped attributes are just always quirky.
413 }
414
415 // Golden ratio - arbitrary start value to avoid mapping all 0's to all 0's
416 // or anything like that.
417 const unsigned PHI = 0x9e3779b9U;
418
419 // Paul Hsieh's SuperFastHash
420 // http://www.azillionmonkeys.com/qed/hash.html
421 unsigned MappedAttributeHash::hash(const MappedAttributeKey& key)
422 {
423     uint32_t hash = PHI;
424     uint32_t tmp;
425
426     const uint16_t* p;
427
428     p = reinterpret_cast<const uint16_t*>(&key.name);
429     hash += p[0];
430     tmp = (p[1] << 11) ^ hash;
431     hash = (hash << 16) ^ tmp;
432     hash += hash >> 11;
433     ASSERT(sizeof(key.name) == 4 || sizeof(key.name) == 8);
434     if (sizeof(key.name) == 8) {
435         p += 2;
436         hash += p[0];
437         tmp = (p[1] << 11) ^ hash;
438         hash = (hash << 16) ^ tmp;
439         hash += hash >> 11;
440     }
441
442     p = reinterpret_cast<const uint16_t*>(&key.value);
443     hash += p[0];
444     tmp = (p[1] << 11) ^ hash;
445     hash = (hash << 16) ^ tmp;
446     hash += hash >> 11;
447     ASSERT(sizeof(key.value) == 4 || sizeof(key.value) == 8);
448     if (sizeof(key.value) == 8) {
449         p += 2;
450         hash += p[0];
451         tmp = (p[1] << 11) ^ hash;
452         hash = (hash << 16) ^ tmp;
453         hash += hash >> 11;
454     }
455
456     // Handle end case
457     hash += key.type;
458     hash ^= hash << 11;
459     hash += hash >> 17;
460
461     // Force "avalanching" of final 127 bits
462     hash ^= hash << 3;
463     hash += hash >> 5;
464     hash ^= hash << 2;
465     hash += hash >> 15;
466     hash ^= hash << 10;
467
468     // This avoids ever returning a hash code of 0, since that is used to
469     // signal "hash not computed yet", using a value that is likely to be
470     // effectively the same as 0 when the low bits are masked
471     if (hash == 0)
472         hash = 0x80000000;
473
474     return hash;
475 }
476
477 void StyledElement::copyNonAttributeProperties(const Element *sourceElement)
478 {
479     const StyledElement* source = static_cast<const StyledElement*>(sourceElement);
480     if (!source->m_inlineStyleDecl)
481         return;
482
483     *getInlineStyleDecl() = *source->m_inlineStyleDecl;
484     m_isStyleAttributeValid = source->m_isStyleAttributeValid;
485     m_synchronizingStyleAttribute = source->m_synchronizingStyleAttribute;
486     
487     Element::copyNonAttributeProperties(sourceElement);
488 }
489
490 }