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