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 (CSSMutableStyleDeclaration* inlineStyle = inlineStyleDecl())
116 const_cast<StyledElement*>(this)->setAttribute(styleAttr, inlineStyle->asText());
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 ASSERT(!m_inlineStyleDecl);
133 m_inlineStyleDecl = CSSMutableStyleDeclaration::createInline(this);
134 m_inlineStyleDecl->setStrictParsing(isHTMLElement() && !document()->inQuirksMode());
137 void StyledElement::destroyInlineStyleDecl()
139 if (!m_inlineStyleDecl)
141 m_inlineStyleDecl->clearParentElement();
142 m_inlineStyleDecl = 0;
145 void StyledElement::attributeChanged(Attribute* attr, bool preserveDecls)
147 if (attr->name() == HTMLNames::nameAttr)
148 setHasName(!attr->isNull());
150 if (!attr->isMappedAttribute()) {
151 Element::attributeChanged(attr, preserveDecls);
155 if (attr->decl() && !preserveDecls) {
157 setNeedsStyleRecalc();
160 bool checkDecl = true;
161 MappedAttributeEntry entry;
162 bool needToParse = mapToEntry(attr->name(), entry);
165 setNeedsStyleRecalc();
168 } else if (!attr->isNull() && entry != eNone) {
169 CSSMappedAttributeDeclaration* decl = getMappedAttributeDecl(entry, attr);
172 setNeedsStyleRecalc();
178 // parseMappedAttribute() might create a CSSMappedAttributeDeclaration on the attribute.
179 // Normally we would be concerned about reseting the parent of those declarations in StyledElement::didMoveToNewDocument().
180 // But currently we always clear its parent and node below when adding it to the decl table.
181 // If that changes for some reason moving between documents will be buggy.
182 // webarchive/adopt-attribute-styled-node-webarchive.html should catch any resulting crashes.
184 parseMappedAttribute(attr);
187 recalcStyleIfNeededAfterAttributeChanged(attr);
189 if (checkDecl && attr->decl()) {
190 // Add the decl to the table in the appropriate spot.
191 setMappedAttributeDecl(entry, attr, attr->decl());
192 attr->decl()->setMappedState(entry, attr->name(), attr->value());
195 updateAfterAttributeChanged(attr);
198 bool StyledElement::mapToEntry(const QualifiedName& attrName, MappedAttributeEntry& result) const
201 if (attrName == styleAttr)
202 return !isSynchronizingStyleAttribute();
206 void StyledElement::classAttributeChanged(const AtomicString& newClassString)
208 const UChar* characters = newClassString.characters();
209 unsigned length = newClassString.length();
211 for (i = 0; i < length; ++i) {
212 if (isNotHTMLSpace(characters[i]))
215 bool hasClass = i < length;
216 setHasClass(hasClass);
218 const bool shouldFoldCase = document()->inQuirksMode();
219 ensureAttributeData()->setClass(newClassString, shouldFoldCase);
220 if (DOMTokenList* classList = optionalClassList())
221 static_cast<ClassList*>(classList)->reset(newClassString);
222 } else if (attributeData())
223 attributeData()->clearClass();
224 setNeedsStyleRecalc();
225 dispatchSubtreeModifiedEvent();
228 void StyledElement::parseMappedAttribute(Attribute* attr)
230 if (isIdAttributeName(attr->name()))
231 idAttributeChanged(attr);
232 else if (attr->name() == classAttr)
233 classAttributeChanged(attr->value());
234 else if (attr->name() == styleAttr) {
236 destroyInlineStyleDecl();
237 else if (document()->contentSecurityPolicy()->allowInlineStyle())
238 ensureInlineStyleDecl()->parseDeclaration(attr->value());
239 setIsStyleAttributeValid();
240 setNeedsStyleRecalc();
244 CSSMutableStyleDeclaration* StyledElement::ensureInlineStyleDecl()
246 if (!m_inlineStyleDecl)
247 createInlineStyleDecl();
248 return m_inlineStyleDecl.get();
251 CSSStyleDeclaration* StyledElement::style()
253 return ensureInlineStyleDecl();
256 void StyledElement::removeCSSProperty(Attribute* attribute, int id)
258 if (!attribute->decl())
259 createMappedDecl(attribute);
260 attribute->decl()->removeMappedProperty(this, id);
263 void StyledElement::addCSSProperty(Attribute* attribute, int id, const String &value)
265 if (!attribute->decl())
266 createMappedDecl(attribute);
267 attribute->decl()->setMappedProperty(this, id, value);
270 void StyledElement::addCSSProperty(Attribute* attribute, int id, int value)
272 if (!attribute->decl())
273 createMappedDecl(attribute);
274 attribute->decl()->setMappedProperty(this, id, value);
277 void StyledElement::addCSSImageProperty(Attribute* attribute, int id, const String& url)
279 if (!attribute->decl())
280 createMappedDecl(attribute);
281 attribute->decl()->setMappedImageProperty(this, id, url);
284 void StyledElement::addCSSLength(Attribute* attribute, int id, const String &value)
286 // FIXME: This function should not spin up the CSS parser, but should instead just figure out the correct
287 // length unit and make the appropriate parsed value.
288 if (!attribute->decl())
289 createMappedDecl(attribute);
291 // strip attribute garbage..
292 StringImpl* v = value.impl();
296 while (l < v->length() && (*v)[l] <= ' ')
299 for (; l < v->length(); l++) {
304 if (cc == '%' || cc == '*')
311 if (l != v->length()) {
312 attribute->decl()->setMappedLengthProperty(this, id, v->substring(0, l));
317 attribute->decl()->setMappedLengthProperty(this, id, value);
320 static String parseColorStringWithCrazyLegacyRules(const String& colorString)
322 // Per spec, only look at the first 128 digits of the string.
323 const size_t maxColorLength = 128;
324 // We'll pad the buffer with two extra 0s later, so reserve two more than the max.
325 Vector<char, maxColorLength+2> digitBuffer;
329 if (colorString[0] == '#')
332 // Grab the first 128 characters, replacing non-hex characters with 0.
333 // Non-BMP characters are replaced with "00" due to them appearing as two "characters" in the String.
334 for (; i < colorString.length() && digitBuffer.size() < maxColorLength; i++) {
335 if (!isASCIIHexDigit(colorString[i]))
336 digitBuffer.append('0');
338 digitBuffer.append(colorString[i]);
341 if (!digitBuffer.size())
344 // Pad the buffer out to at least the next multiple of three in size.
345 digitBuffer.append('0');
346 digitBuffer.append('0');
348 if (digitBuffer.size() < 6)
349 return String::format("#0%c0%c0%c", digitBuffer[0], digitBuffer[1], digitBuffer[2]);
351 // Split the digits into three components, then search the last 8 digits of each component.
352 ASSERT(digitBuffer.size() >= 6);
353 size_t componentLength = digitBuffer.size() / 3;
354 size_t componentSearchWindowLength = min<size_t>(componentLength, 8);
355 size_t redIndex = componentLength - componentSearchWindowLength;
356 size_t greenIndex = componentLength * 2 - componentSearchWindowLength;
357 size_t blueIndex = componentLength * 3 - componentSearchWindowLength;
358 // Skip digits until one of them is non-zero, or we've only got two digits left in the component.
359 while (digitBuffer[redIndex] == '0' && digitBuffer[greenIndex] == '0' && digitBuffer[blueIndex] == '0' && (componentLength - redIndex) > 2) {
364 ASSERT(redIndex + 1 < componentLength);
365 ASSERT(greenIndex >= componentLength);
366 ASSERT(greenIndex + 1 < componentLength * 2);
367 ASSERT(blueIndex >= componentLength * 2);
368 ASSERT(blueIndex + 1 < digitBuffer.size());
369 return String::format("#%c%c%c%c%c%c", digitBuffer[redIndex], digitBuffer[redIndex + 1], digitBuffer[greenIndex], digitBuffer[greenIndex + 1], digitBuffer[blueIndex], digitBuffer[blueIndex + 1]);
372 // Color parsing that matches HTML's "rules for parsing a legacy color value"
373 void StyledElement::addCSSColor(Attribute* attribute, int id, const String& attributeValue)
375 // An empty string doesn't apply a color. (One containing only whitespace does, which is why this check occurs before stripping.)
376 if (attributeValue.isEmpty())
379 String colorString = attributeValue.stripWhiteSpace();
381 // "transparent" doesn't apply a color either.
382 if (equalIgnoringCase(colorString, "transparent"))
385 if (!attribute->decl())
386 createMappedDecl(attribute);
388 // If the string is a named CSS color or a 3/6-digit hex color, use that.
389 Color parsedColor(colorString);
390 if (parsedColor.isValid()) {
391 attribute->decl()->setMappedProperty(this, id, colorString);
395 attribute->decl()->setMappedProperty(this, id, parseColorStringWithCrazyLegacyRules(colorString));
398 void StyledElement::createMappedDecl(Attribute* attr)
400 RefPtr<CSSMappedAttributeDeclaration> decl = CSSMappedAttributeDeclaration::create();
402 ASSERT(!decl->useStrictParsing());
405 unsigned MappedAttributeHash::hash(const MappedAttributeKey& key)
407 COMPILE_ASSERT(sizeof(key.name) == 4 || sizeof(key.name) == 8, key_name_size);
408 COMPILE_ASSERT(sizeof(key.value) == 4 || sizeof(key.value) == 8, key_value_size);
413 data = reinterpret_cast<const UChar*>(&key.name);
414 hasher.addCharacters(data[0], data[1]);
415 if (sizeof(key.name) == 8)
416 hasher.addCharacters(data[2], data[3]);
418 data = reinterpret_cast<const UChar*>(&key.value);
419 hasher.addCharacters(data[0], data[1]);
420 if (sizeof(key.value) == 8)
421 hasher.addCharacters(data[2], data[3]);
423 return hasher.hash();
426 void StyledElement::copyNonAttributeProperties(const Element* sourceElement)
428 ASSERT(sourceElement);
429 ASSERT(sourceElement->isStyledElement());
431 const StyledElement* source = static_cast<const StyledElement*>(sourceElement);
432 if (!source->inlineStyleDecl())
435 CSSMutableStyleDeclaration* inlineStyle = ensureInlineStyleDecl();
436 inlineStyle->copyPropertiesFrom(*source->inlineStyleDecl());
437 inlineStyle->setStrictParsing(source->inlineStyleDecl()->useStrictParsing());
439 setIsStyleAttributeValid(source->isStyleAttributeValid());
440 setIsSynchronizingStyleAttribute(source->isSynchronizingStyleAttribute());
442 Element::copyNonAttributeProperties(sourceElement);
445 void StyledElement::addSubresourceAttributeURLs(ListHashSet<KURL>& urls) const
447 if (!m_inlineStyleDecl)
449 m_inlineStyleDecl->addSubresourceStyleURLs(urls);