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.
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.
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.
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.
25 #include "StyledElement.h"
27 #include "Attribute.h"
28 #include "CSSMutableStyleDeclaration.h"
29 #include "CSSStyleSelector.h"
30 #include "CSSStyleSheet.h"
31 #include "CSSValueKeywords.h"
33 #include "ClassList.h"
34 #include "ContentSecurityPolicy.h"
35 #include "DOMTokenList.h"
37 #include "HTMLNames.h"
38 #include "HTMLParserIdioms.h"
39 #include <wtf/HashFunctions.h>
45 using namespace HTMLNames;
47 struct MappedAttributeKey {
51 MappedAttributeKey(MappedAttributeEntry t = eNone, StringImpl* n = 0, StringImpl* v = 0)
52 : type(t), name(n), value(v) { }
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; }
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; }
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;
71 typedef HashMap<MappedAttributeKey, CSSMappedAttributeDeclaration*, MappedAttributeHash, MappedAttributeKeyTraits> MappedAttributeDecls;
73 static MappedAttributeDecls* mappedAttributeDecls = 0;
75 CSSMappedAttributeDeclaration* StyledElement::getMappedAttributeDecl(MappedAttributeEntry entryType, Attribute* attr)
77 if (!mappedAttributeDecls)
79 return mappedAttributeDecls->get(MappedAttributeKey(entryType, attr->name().localName().impl(), attr->value().impl()));
82 CSSMappedAttributeDeclaration* StyledElement::getMappedAttributeDecl(MappedAttributeEntry type, const QualifiedName& name, const AtomicString& value)
84 if (!mappedAttributeDecls)
86 return mappedAttributeDecls->get(MappedAttributeKey(type, name.localName().impl(), value.impl()));
89 void StyledElement::setMappedAttributeDecl(MappedAttributeEntry entryType, Attribute* attr, CSSMappedAttributeDeclaration* decl)
91 if (!mappedAttributeDecls)
92 mappedAttributeDecls = new MappedAttributeDecls;
93 mappedAttributeDecls->set(MappedAttributeKey(entryType, attr->name().localName().impl(), attr->value().impl()), decl);
96 void StyledElement::setMappedAttributeDecl(MappedAttributeEntry entryType, const QualifiedName& name, const AtomicString& value, CSSMappedAttributeDeclaration* decl)
98 if (!mappedAttributeDecls)
99 mappedAttributeDecls = new MappedAttributeDecls;
100 mappedAttributeDecls->set(MappedAttributeKey(entryType, name.localName().impl(), value.impl()), decl);
103 void StyledElement::removeMappedAttributeDecl(MappedAttributeEntry entryType, const QualifiedName& attrName, const AtomicString& attrValue)
105 if (!mappedAttributeDecls)
107 mappedAttributeDecls->remove(MappedAttributeKey(entryType, attrName.localName().impl(), attrValue.impl()));
110 void StyledElement::updateStyleAttribute() const
112 ASSERT(!isStyleAttributeValid());
113 setIsStyleAttributeValid();
114 setIsSynchronizingStyleAttribute();
115 if (m_inlineStyleDecl)
116 const_cast<StyledElement*>(this)->setAttribute(styleAttr, m_inlineStyleDecl->cssText());
117 clearIsSynchronizingStyleAttribute();
120 StyledElement::~StyledElement()
122 destroyInlineStyleDecl();
125 PassRefPtr<Attribute> StyledElement::createAttribute(const QualifiedName& name, const AtomicString& value)
127 return Attribute::createMapped(name, value);
130 void StyledElement::createInlineStyleDecl()
132 m_inlineStyleDecl = CSSMutableStyleDeclaration::create();
133 m_inlineStyleDecl->setParent(document()->elementSheet());
134 m_inlineStyleDecl->setNode(this);
135 m_inlineStyleDecl->setStrictParsing(isHTMLElement() && !document()->inQuirksMode());
138 void StyledElement::destroyInlineStyleDecl()
140 if (m_inlineStyleDecl) {
141 m_inlineStyleDecl->setNode(0);
142 m_inlineStyleDecl->setParent(0);
143 m_inlineStyleDecl = 0;
147 void StyledElement::attributeChanged(Attribute* attr, bool preserveDecls)
149 if (!attr->isMappedAttribute()) {
150 Element::attributeChanged(attr, preserveDecls);
154 if (attr->decl() && !preserveDecls) {
156 setNeedsStyleRecalc();
158 attributeMap()->declRemoved();
161 bool checkDecl = true;
162 MappedAttributeEntry entry;
163 bool needToParse = mapToEntry(attr->name(), entry);
166 setNeedsStyleRecalc();
168 attributeMap()->declAdded();
171 } else if (!attr->isNull() && entry != eNone) {
172 CSSMappedAttributeDeclaration* decl = getMappedAttributeDecl(entry, attr);
175 setNeedsStyleRecalc();
177 attributeMap()->declAdded();
183 // parseMappedAttribute() might create a CSSMappedAttributeDeclaration on the attribute.
184 // Normally we would be concerned about reseting the parent of those declarations in StyledElement::didMoveToNewOwnerDocument().
185 // But currently we always clear its parent and node below when adding it to the decl table.
186 // If that changes for some reason moving between documents will be buggy.
187 // webarchive/adopt-attribute-styled-node-webarchive.html should catch any resulting crashes.
189 parseMappedAttribute(attr);
192 recalcStyleIfNeededAfterAttributeChanged(attr);
194 if (checkDecl && attr->decl()) {
195 // Add the decl to the table in the appropriate spot.
196 setMappedAttributeDecl(entry, attr, attr->decl());
197 attr->decl()->setMappedState(entry, attr->name(), attr->value());
198 attr->decl()->setParent(0);
199 attr->decl()->setNode(0);
201 attributeMap()->declAdded();
204 updateAfterAttributeChanged(attr);
207 bool StyledElement::mapToEntry(const QualifiedName& attrName, MappedAttributeEntry& result) const
210 if (attrName == styleAttr)
211 return !isSynchronizingStyleAttribute();
215 void StyledElement::classAttributeChanged(const AtomicString& newClassString)
217 const UChar* characters = newClassString.characters();
218 unsigned length = newClassString.length();
220 for (i = 0; i < length; ++i) {
221 if (isNotHTMLSpace(characters[i]))
224 bool hasClass = i < length;
225 setHasClass(hasClass);
227 attributes()->setClass(newClassString);
228 if (DOMTokenList* classList = optionalClassList())
229 static_cast<ClassList*>(classList)->reset(newClassString);
230 } else if (attributeMap())
231 attributeMap()->clearClass();
232 setNeedsStyleRecalc();
233 dispatchSubtreeModifiedEvent();
236 void StyledElement::parseMappedAttribute(Attribute* attr)
238 if (isIdAttributeName(attr->name()))
239 idAttributeChanged(attr);
240 else if (attr->name() == classAttr)
241 classAttributeChanged(attr->value());
242 else if (attr->name() == styleAttr) {
244 destroyInlineStyleDecl();
245 else if (document()->contentSecurityPolicy()->allowInlineStyle())
246 getInlineStyleDecl()->parseDeclaration(attr->value());
247 setIsStyleAttributeValid();
248 setNeedsStyleRecalc();
252 CSSMutableStyleDeclaration* StyledElement::getInlineStyleDecl()
254 if (!m_inlineStyleDecl)
255 createInlineStyleDecl();
256 return m_inlineStyleDecl.get();
259 CSSStyleDeclaration* StyledElement::style()
261 return getInlineStyleDecl();
264 void StyledElement::addCSSProperty(Attribute* attribute, int id, const String &value)
266 if (!attribute->decl())
267 createMappedDecl(attribute);
268 attribute->decl()->setProperty(id, value, false);
271 void StyledElement::addCSSProperty(Attribute* attribute, int id, int value)
273 if (!attribute->decl())
274 createMappedDecl(attribute);
275 attribute->decl()->setProperty(id, value, false);
278 void StyledElement::addCSSImageProperty(Attribute* attribute, int id, const String& url)
280 if (!attribute->decl())
281 createMappedDecl(attribute);
282 attribute->decl()->setImageProperty(id, url, false);
285 void StyledElement::addCSSLength(Attribute* attr, int id, const String &value)
287 // FIXME: This function should not spin up the CSS parser, but should instead just figure out the correct
288 // length unit and make the appropriate parsed value.
290 createMappedDecl(attr);
292 // strip attribute garbage..
293 StringImpl* v = value.impl();
297 while (l < v->length() && (*v)[l] <= ' ')
300 for (; l < v->length(); l++) {
305 if (cc == '%' || cc == '*')
312 if (l != v->length()) {
313 attr->decl()->setLengthProperty(id, v->substring(0, l), false);
318 attr->decl()->setLengthProperty(id, value, false);
321 static String parseColorStringWithCrazyLegacyRules(const String& colorString)
323 // Per spec, only look at the first 128 digits of the string.
324 const size_t maxColorLength = 128;
325 // We'll pad the buffer with two extra 0s later, so reserve two more than the max.
326 Vector<char, maxColorLength+2> digitBuffer;
330 if (colorString[0] == '#')
333 // Grab the first 128 characters, replacing non-hex characters with 0.
334 // Non-BMP characters are replaced with "00" due to them appearing as two "characters" in the String.
335 for (; i < colorString.length() && digitBuffer.size() < maxColorLength; i++) {
336 if (!isASCIIHexDigit(colorString[i]))
337 digitBuffer.append('0');
339 digitBuffer.append(colorString[i]);
342 if (!digitBuffer.size())
345 // Pad the buffer out to at least the next multiple of three in size.
346 digitBuffer.append('0');
347 digitBuffer.append('0');
349 if (digitBuffer.size() < 6)
350 return String::format("#0%c0%c0%c", digitBuffer[0], digitBuffer[1], digitBuffer[2]);
352 // Split the digits into three components, then search the last 8 digits of each component.
353 ASSERT(digitBuffer.size() >= 6);
354 size_t componentLength = digitBuffer.size() / 3;
355 size_t componentSearchWindowLength = min<size_t>(componentLength, 8);
356 size_t redIndex = componentLength - componentSearchWindowLength;
357 size_t greenIndex = componentLength * 2 - componentSearchWindowLength;
358 size_t blueIndex = componentLength * 3 - componentSearchWindowLength;
359 // Skip digits until one of them is non-zero, or we've only got two digits left in the component.
360 while (digitBuffer[redIndex] == '0' && digitBuffer[greenIndex] == '0' && digitBuffer[blueIndex] == '0' && (componentLength - redIndex) > 2) {
365 ASSERT(redIndex + 1 < componentLength);
366 ASSERT(greenIndex >= componentLength);
367 ASSERT(greenIndex + 1 < componentLength * 2);
368 ASSERT(blueIndex >= componentLength * 2);
369 ASSERT(blueIndex + 1 < digitBuffer.size());
370 return String::format("#%c%c%c%c%c%c", digitBuffer[redIndex], digitBuffer[redIndex + 1], digitBuffer[greenIndex], digitBuffer[greenIndex + 1], digitBuffer[blueIndex], digitBuffer[blueIndex + 1]);
373 // Color parsing that matches HTML's "rules for parsing a legacy color value"
374 void StyledElement::addCSSColor(Attribute* attribute, int id, const String& attributeValue)
376 // The empty string doesn't apply a color. (Just whitespace does, which is why this check occurs before trimming.)
377 if (!attributeValue.length())
380 String color = attributeValue.stripWhiteSpace();
382 // "transparent" doesn't apply a color either.
383 if (equalIgnoringCase(color, "transparent"))
386 if (!attribute->decl())
387 createMappedDecl(attribute);
389 // If the string is a named CSS color, use that color.
391 foundColor.setNamedColor(color);
392 if (foundColor.isValid()) {
393 attribute->decl()->setProperty(id, color, false);
397 // If the string is a 3 or 6-digit hex color, use that color.
398 if (color[0] == '#' && (color.length() == 4 || color.length() == 7) && attribute->decl()->setProperty(id, color, false))
401 attribute->decl()->setProperty(id, parseColorStringWithCrazyLegacyRules(color), false);
404 void StyledElement::createMappedDecl(Attribute* attr)
406 RefPtr<CSSMappedAttributeDeclaration> decl = CSSMappedAttributeDeclaration::create();
408 decl->setParent(document()->elementSheet());
410 decl->setStrictParsing(false); // Mapped attributes are just always quirky.
413 unsigned MappedAttributeHash::hash(const MappedAttributeKey& key)
415 COMPILE_ASSERT(sizeof(key.name) == 4 || sizeof(key.name) == 8, key_name_size);
416 COMPILE_ASSERT(sizeof(key.value) == 4 || sizeof(key.value) == 8, key_value_size);
421 data = reinterpret_cast<const UChar*>(&key.name);
422 hasher.addCharacters(data[0], data[1]);
423 if (sizeof(key.name) == 8)
424 hasher.addCharacters(data[2], data[3]);
426 data = reinterpret_cast<const UChar*>(&key.value);
427 hasher.addCharacters(data[0], data[1]);
428 if (sizeof(key.value) == 8)
429 hasher.addCharacters(data[2], data[3]);
431 return hasher.hash();
434 void StyledElement::copyNonAttributeProperties(const Element *sourceElement)
436 const StyledElement* source = static_cast<const StyledElement*>(sourceElement);
437 if (!source->m_inlineStyleDecl)
440 *getInlineStyleDecl() = *source->m_inlineStyleDecl;
441 setIsStyleAttributeValid(source->isStyleAttributeValid());
442 setIsSynchronizingStyleAttribute(source->isSynchronizingStyleAttribute());
444 Element::copyNonAttributeProperties(sourceElement);
447 void StyledElement::addSubresourceAttributeURLs(ListHashSet<KURL>& urls) const
449 if (CSSMutableStyleDeclaration* style = inlineStyleDecl())
450 style->addSubresourceStyleURLs(urls);
454 void StyledElement::didMoveToNewOwnerDocument()
456 if (m_inlineStyleDecl)
457 m_inlineStyleDecl->setParent(document()->elementSheet());
459 Element::didMoveToNewOwnerDocument();