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