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 "ClassList.h"
33 #include "DOMTokenList.h"
34 #include "Document.h"
35 #include "HTMLNames.h"
36 #include "HTMLParserIdioms.h"
37 #include <wtf/HashFunctions.h>
38
39 using namespace std;
40
41 namespace WebCore {
42
43 using namespace HTMLNames;
44
45 struct MappedAttributeKey {
46 uint16_t type;
47 StringImpl* name;
48 StringImpl* value;
MappedAttributeKeyWebCore::MappedAttributeKey49 MappedAttributeKey(MappedAttributeEntry t = eNone, StringImpl* n = 0, StringImpl* v = 0)
50 : type(t), name(n), value(v) { }
51 };
52
operator ==(const MappedAttributeKey & a,const MappedAttributeKey & b)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 = false;
constructDeletedValueWebCore::MappedAttributeKeyTraits59 static void constructDeletedValue(MappedAttributeKey& slot) { slot.type = eLastEntry; }
isDeletedValueWebCore::MappedAttributeKeyTraits60 static bool isDeletedValue(const MappedAttributeKey& value) { return value.type == eLastEntry; }
61 };
62
63 struct MappedAttributeHash {
64 static unsigned hash(const MappedAttributeKey&);
equalWebCore::MappedAttributeHash65 static bool equal(const MappedAttributeKey& a, const MappedAttributeKey& b) { return a == b; }
66 static const bool safeToCompareToEmptyOrDeleted = true;
67 };
68
69 typedef HashMap<MappedAttributeKey, CSSMappedAttributeDeclaration*, MappedAttributeHash, MappedAttributeKeyTraits> MappedAttributeDecls;
70
71 static MappedAttributeDecls* mappedAttributeDecls = 0;
72
getMappedAttributeDecl(MappedAttributeEntry entryType,Attribute * attr)73 CSSMappedAttributeDeclaration* StyledElement::getMappedAttributeDecl(MappedAttributeEntry entryType, Attribute* attr)
74 {
75 if (!mappedAttributeDecls)
76 return 0;
77 return mappedAttributeDecls->get(MappedAttributeKey(entryType, attr->name().localName().impl(), attr->value().impl()));
78 }
79
getMappedAttributeDecl(MappedAttributeEntry type,const QualifiedName & name,const AtomicString & value)80 CSSMappedAttributeDeclaration* StyledElement::getMappedAttributeDecl(MappedAttributeEntry type, const QualifiedName& name, const AtomicString& value)
81 {
82 if (!mappedAttributeDecls)
83 return 0;
84 return mappedAttributeDecls->get(MappedAttributeKey(type, name.localName().impl(), value.impl()));
85 }
86
setMappedAttributeDecl(MappedAttributeEntry entryType,Attribute * attr,CSSMappedAttributeDeclaration * decl)87 void StyledElement::setMappedAttributeDecl(MappedAttributeEntry entryType, Attribute* attr, CSSMappedAttributeDeclaration* decl)
88 {
89 if (!mappedAttributeDecls)
90 mappedAttributeDecls = new MappedAttributeDecls;
91 mappedAttributeDecls->set(MappedAttributeKey(entryType, attr->name().localName().impl(), attr->value().impl()), decl);
92 }
93
setMappedAttributeDecl(MappedAttributeEntry entryType,const QualifiedName & name,const AtomicString & value,CSSMappedAttributeDeclaration * decl)94 void StyledElement::setMappedAttributeDecl(MappedAttributeEntry entryType, const QualifiedName& name, const AtomicString& value, CSSMappedAttributeDeclaration* decl)
95 {
96 if (!mappedAttributeDecls)
97 mappedAttributeDecls = new MappedAttributeDecls;
98 mappedAttributeDecls->set(MappedAttributeKey(entryType, name.localName().impl(), value.impl()), decl);
99 }
100
removeMappedAttributeDecl(MappedAttributeEntry entryType,const QualifiedName & attrName,const AtomicString & attrValue)101 void StyledElement::removeMappedAttributeDecl(MappedAttributeEntry entryType, const QualifiedName& attrName, const AtomicString& attrValue)
102 {
103 if (!mappedAttributeDecls)
104 return;
105 mappedAttributeDecls->remove(MappedAttributeKey(entryType, attrName.localName().impl(), attrValue.impl()));
106 }
107
updateStyleAttribute() const108 void StyledElement::updateStyleAttribute() const
109 {
110 ASSERT(!isStyleAttributeValid());
111 setIsStyleAttributeValid();
112 setIsSynchronizingStyleAttribute();
113 if (m_inlineStyleDecl)
114 const_cast<StyledElement*>(this)->setAttribute(styleAttr, m_inlineStyleDecl->cssText());
115 clearIsSynchronizingStyleAttribute();
116 }
117
~StyledElement()118 StyledElement::~StyledElement()
119 {
120 destroyInlineStyleDecl();
121 }
122
createAttribute(const QualifiedName & name,const AtomicString & value)123 PassRefPtr<Attribute> StyledElement::createAttribute(const QualifiedName& name, const AtomicString& value)
124 {
125 return Attribute::createMapped(name, value);
126 }
127
createInlineStyleDecl()128 void StyledElement::createInlineStyleDecl()
129 {
130 m_inlineStyleDecl = CSSMutableStyleDeclaration::create();
131 m_inlineStyleDecl->setParent(document()->elementSheet());
132 m_inlineStyleDecl->setNode(this);
133 m_inlineStyleDecl->setStrictParsing(isHTMLElement() && !document()->inQuirksMode());
134 }
135
destroyInlineStyleDecl()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
attributeChanged(Attribute * attr,bool preserveDecls)145 void StyledElement::attributeChanged(Attribute* attr, bool preserveDecls)
146 {
147 if (!attr->isMappedAttribute()) {
148 Element::attributeChanged(attr, preserveDecls);
149 return;
150 }
151
152 if (attr->decl() && !preserveDecls) {
153 attr->setDecl(0);
154 setNeedsStyleRecalc();
155 if (attributeMap())
156 attributeMap()->declRemoved();
157 }
158
159 bool checkDecl = true;
160 MappedAttributeEntry entry;
161 bool needToParse = mapToEntry(attr->name(), entry);
162 if (preserveDecls) {
163 if (attr->decl()) {
164 setNeedsStyleRecalc();
165 if (attributeMap())
166 attributeMap()->declAdded();
167 checkDecl = false;
168 }
169 } else if (!attr->isNull() && entry != eNone) {
170 CSSMappedAttributeDeclaration* decl = getMappedAttributeDecl(entry, attr);
171 if (decl) {
172 attr->setDecl(decl);
173 setNeedsStyleRecalc();
174 if (attributeMap())
175 attributeMap()->declAdded();
176 checkDecl = false;
177 } else
178 needToParse = true;
179 }
180
181 // parseMappedAttribute() might create a CSSMappedAttributeDeclaration on the attribute.
182 // Normally we would be concerned about reseting the parent of those declarations in StyledElement::didMoveToNewOwnerDocument().
183 // But currently we always clear its parent and node below when adding it to the decl table.
184 // If that changes for some reason moving between documents will be buggy.
185 // webarchive/adopt-attribute-styled-node-webarchive.html should catch any resulting crashes.
186 if (needToParse)
187 parseMappedAttribute(attr);
188
189 if (entry == eNone)
190 recalcStyleIfNeededAfterAttributeChanged(attr);
191
192 if (checkDecl && attr->decl()) {
193 // Add the decl to the table in the appropriate spot.
194 setMappedAttributeDecl(entry, attr, attr->decl());
195 attr->decl()->setMappedState(entry, attr->name(), attr->value());
196 attr->decl()->setParent(0);
197 attr->decl()->setNode(0);
198 if (attributeMap())
199 attributeMap()->declAdded();
200 }
201
202 updateAfterAttributeChanged(attr);
203 }
204
mapToEntry(const QualifiedName & attrName,MappedAttributeEntry & result) const205 bool StyledElement::mapToEntry(const QualifiedName& attrName, MappedAttributeEntry& result) const
206 {
207 result = eNone;
208 if (attrName == styleAttr)
209 return !isSynchronizingStyleAttribute();
210 return true;
211 }
212
classAttributeChanged(const AtomicString & newClassString)213 void StyledElement::classAttributeChanged(const AtomicString& newClassString)
214 {
215 const UChar* characters = newClassString.characters();
216 unsigned length = newClassString.length();
217 unsigned i;
218 for (i = 0; i < length; ++i) {
219 if (isNotHTMLSpace(characters[i]))
220 break;
221 }
222 bool hasClass = i < length;
223 setHasClass(hasClass);
224 if (hasClass) {
225 attributes()->setClass(newClassString);
226 if (DOMTokenList* classList = optionalClassList())
227 static_cast<ClassList*>(classList)->reset(newClassString);
228 } else if (attributeMap())
229 attributeMap()->clearClass();
230 setNeedsStyleRecalc();
231 dispatchSubtreeModifiedEvent();
232 }
233
parseMappedAttribute(Attribute * attr)234 void StyledElement::parseMappedAttribute(Attribute* attr)
235 {
236 if (isIdAttributeName(attr->name()))
237 idAttributeChanged(attr);
238 else if (attr->name() == classAttr)
239 classAttributeChanged(attr->value());
240 else if (attr->name() == styleAttr) {
241 if (attr->isNull())
242 destroyInlineStyleDecl();
243 else
244 getInlineStyleDecl()->parseDeclaration(attr->value());
245 setIsStyleAttributeValid();
246 setNeedsStyleRecalc();
247 }
248 }
249
getInlineStyleDecl()250 CSSMutableStyleDeclaration* StyledElement::getInlineStyleDecl()
251 {
252 if (!m_inlineStyleDecl)
253 createInlineStyleDecl();
254 return m_inlineStyleDecl.get();
255 }
256
style()257 CSSStyleDeclaration* StyledElement::style()
258 {
259 return getInlineStyleDecl();
260 }
261
addCSSProperty(Attribute * attribute,int id,const String & value)262 void StyledElement::addCSSProperty(Attribute* attribute, int id, const String &value)
263 {
264 if (!attribute->decl())
265 createMappedDecl(attribute);
266 attribute->decl()->setProperty(id, value, false);
267 }
268
addCSSProperty(Attribute * attribute,int id,int value)269 void StyledElement::addCSSProperty(Attribute* attribute, int id, int value)
270 {
271 if (!attribute->decl())
272 createMappedDecl(attribute);
273 attribute->decl()->setProperty(id, value, false);
274 }
275
addCSSImageProperty(Attribute * attribute,int id,const String & url)276 void StyledElement::addCSSImageProperty(Attribute* attribute, int id, const String& url)
277 {
278 if (!attribute->decl())
279 createMappedDecl(attribute);
280 attribute->decl()->setImageProperty(id, url, false);
281 }
282
addCSSLength(Attribute * attr,int id,const String & value)283 void StyledElement::addCSSLength(Attribute* attr, int id, const String &value)
284 {
285 // FIXME: This function should not spin up the CSS parser, but should instead just figure out the correct
286 // length unit and make the appropriate parsed value.
287 if (!attr->decl())
288 createMappedDecl(attr);
289
290 // strip attribute garbage..
291 StringImpl* v = value.impl();
292 if (v) {
293 unsigned int l = 0;
294
295 while (l < v->length() && (*v)[l] <= ' ')
296 l++;
297
298 for (; l < v->length(); l++) {
299 UChar cc = (*v)[l];
300 if (cc > '9')
301 break;
302 if (cc < '0') {
303 if (cc == '%' || cc == '*')
304 l++;
305 if (cc != '.')
306 break;
307 }
308 }
309
310 if (l != v->length()) {
311 attr->decl()->setLengthProperty(id, v->substring(0, l), false);
312 return;
313 }
314 }
315
316 attr->decl()->setLengthProperty(id, value, false);
317 }
318
319 /* color parsing that tries to match as close as possible IE 6. */
addCSSColor(Attribute * attr,int id,const String & c)320 void StyledElement::addCSSColor(Attribute* attr, int id, const String& c)
321 {
322 // this is the only case no color gets applied in IE.
323 if (!c.length())
324 return;
325
326 if (!attr->decl())
327 createMappedDecl(attr);
328
329 if (attr->decl()->setProperty(id, c, false))
330 return;
331
332 String color = c;
333 // not something that fits the specs.
334
335 // we're emulating IEs color parser here. It maps transparent to black, otherwise it tries to build a rgb value
336 // out of everything you put in. The algorithm is experimentally determined, but seems to work for all test cases I have.
337
338 // the length of the color value is rounded up to the next
339 // multiple of 3. each part of the rgb triple then gets one third
340 // of the length.
341 //
342 // Each triplet is parsed byte by byte, mapping
343 // each number to a hex value (0-9a-fA-F to their values
344 // everything else to 0).
345 //
346 // The highest non zero digit in all triplets is remembered, and
347 // used as a normalization point to normalize to values between 0
348 // and 255.
349
350 if (!equalIgnoringCase(color, "transparent")) {
351 if (color[0] == '#')
352 color.remove(0, 1);
353 int basicLength = (color.length() + 2) / 3;
354 if (basicLength > 1) {
355 // IE ignores colors with three digits or less
356 int colors[3] = { 0, 0, 0 };
357 int component = 0;
358 int pos = 0;
359 int maxDigit = basicLength-1;
360 while (component < 3) {
361 // search forward for digits in the string
362 int numDigits = 0;
363 while (pos < (int)color.length() && numDigits < basicLength) {
364 colors[component] <<= 4;
365 if (isASCIIHexDigit(color[pos])) {
366 colors[component] += toASCIIHexValue(color[pos]);
367 maxDigit = min(maxDigit, numDigits);
368 }
369 numDigits++;
370 pos++;
371 }
372 while (numDigits++ < basicLength)
373 colors[component] <<= 4;
374 component++;
375 }
376 maxDigit = basicLength - maxDigit;
377
378 // normalize to 00-ff. The highest filled digit counts, minimum is 2 digits
379 maxDigit -= 2;
380 colors[0] >>= 4 * maxDigit;
381 colors[1] >>= 4 * maxDigit;
382 colors[2] >>= 4 * maxDigit;
383
384 color = String::format("#%02x%02x%02x", colors[0], colors[1], colors[2]);
385 if (attr->decl()->setProperty(id, color, false))
386 return;
387 }
388 }
389 attr->decl()->setProperty(id, CSSValueBlack, false);
390 }
391
createMappedDecl(Attribute * attr)392 void StyledElement::createMappedDecl(Attribute* attr)
393 {
394 RefPtr<CSSMappedAttributeDeclaration> decl = CSSMappedAttributeDeclaration::create();
395 attr->setDecl(decl);
396 decl->setParent(document()->elementSheet());
397 decl->setNode(this);
398 decl->setStrictParsing(false); // Mapped attributes are just always quirky.
399 }
400
hash(const MappedAttributeKey & key)401 unsigned MappedAttributeHash::hash(const MappedAttributeKey& key)
402 {
403 COMPILE_ASSERT(sizeof(key.name) == 4 || sizeof(key.name) == 8, key_name_size);
404 COMPILE_ASSERT(sizeof(key.value) == 4 || sizeof(key.value) == 8, key_value_size);
405
406 StringHasher hasher;
407 const UChar* data;
408
409 data = reinterpret_cast<const UChar*>(&key.name);
410 hasher.addCharacters(data[0], data[1]);
411 if (sizeof(key.name) == 8)
412 hasher.addCharacters(data[2], data[3]);
413
414 data = reinterpret_cast<const UChar*>(&key.value);
415 hasher.addCharacters(data[0], data[1]);
416 if (sizeof(key.value) == 8)
417 hasher.addCharacters(data[2], data[3]);
418
419 return hasher.hash();
420 }
421
copyNonAttributeProperties(const Element * sourceElement)422 void StyledElement::copyNonAttributeProperties(const Element *sourceElement)
423 {
424 const StyledElement* source = static_cast<const StyledElement*>(sourceElement);
425 if (!source->m_inlineStyleDecl)
426 return;
427
428 *getInlineStyleDecl() = *source->m_inlineStyleDecl;
429 setIsStyleAttributeValid(source->isStyleAttributeValid());
430 setIsSynchronizingStyleAttribute(source->isSynchronizingStyleAttribute());
431
432 Element::copyNonAttributeProperties(sourceElement);
433 }
434
addSubresourceAttributeURLs(ListHashSet<KURL> & urls) const435 void StyledElement::addSubresourceAttributeURLs(ListHashSet<KURL>& urls) const
436 {
437 if (CSSMutableStyleDeclaration* style = inlineStyleDecl())
438 style->addSubresourceStyleURLs(urls);
439 }
440
441
didMoveToNewOwnerDocument()442 void StyledElement::didMoveToNewOwnerDocument()
443 {
444 if (m_inlineStyleDecl)
445 m_inlineStyleDecl->setParent(document()->elementSheet());
446
447 Element::didMoveToNewOwnerDocument();
448 }
449
450 }
451