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::attributeChanged(Attribute* attr, bool preserveDecls)
132 if (attr->name() == HTMLNames::nameAttr)
133 setHasName(!attr->isNull());
135 if (!attr->isMappedAttribute()) {
136 Element::attributeChanged(attr, preserveDecls);
140 if (attr->decl() && !preserveDecls) {
142 setNeedsStyleRecalc();
145 bool checkDecl = true;
146 MappedAttributeEntry entry;
147 bool needToParse = mapToEntry(attr->name(), entry);
150 setNeedsStyleRecalc();
153 } else if (!attr->isNull() && entry != eNone) {
154 CSSMappedAttributeDeclaration* decl = getMappedAttributeDecl(entry, attr);
157 setNeedsStyleRecalc();
163 // parseMappedAttribute() might create a CSSMappedAttributeDeclaration on the attribute.
164 // Normally we would be concerned about reseting the parent of those declarations in StyledElement::didMoveToNewDocument().
165 // But currently we always clear its parent and node below when adding it to the decl table.
166 // If that changes for some reason moving between documents will be buggy.
167 // webarchive/adopt-attribute-styled-node-webarchive.html should catch any resulting crashes.
169 parseMappedAttribute(attr);
172 recalcStyleIfNeededAfterAttributeChanged(attr);
174 if (checkDecl && attr->decl()) {
175 // Add the decl to the table in the appropriate spot.
176 setMappedAttributeDecl(entry, attr, attr->decl());
177 attr->decl()->setMappedState(entry, attr->name(), attr->value());
180 updateAfterAttributeChanged(attr);
183 bool StyledElement::mapToEntry(const QualifiedName& attrName, MappedAttributeEntry& result) const
186 if (attrName == styleAttr)
187 return !isSynchronizingStyleAttribute();
191 void StyledElement::classAttributeChanged(const AtomicString& newClassString)
193 const UChar* characters = newClassString.characters();
194 unsigned length = newClassString.length();
196 for (i = 0; i < length; ++i) {
197 if (isNotHTMLSpace(characters[i]))
200 bool hasClass = i < length;
201 setHasClass(hasClass);
203 const bool shouldFoldCase = document()->inQuirksMode();
204 ensureAttributeData()->setClass(newClassString, shouldFoldCase);
205 if (DOMTokenList* classList = optionalClassList())
206 static_cast<ClassList*>(classList)->reset(newClassString);
207 } else if (attributeData())
208 attributeData()->clearClass();
209 setNeedsStyleRecalc();
210 dispatchSubtreeModifiedEvent();
213 void StyledElement::parseMappedAttribute(Attribute* attr)
215 if (isIdAttributeName(attr->name()))
216 idAttributeChanged(attr);
217 else if (attr->name() == classAttr)
218 classAttributeChanged(attr->value());
219 else if (attr->name() == styleAttr) {
221 destroyInlineStyleDecl();
222 else if (document()->contentSecurityPolicy()->allowInlineStyle())
223 ensureInlineStyleDecl()->parseDeclaration(attr->value());
224 setIsStyleAttributeValid();
225 setNeedsStyleRecalc();
229 void StyledElement::removeCSSProperty(Attribute* attribute, int id)
231 if (!attribute->decl())
232 createMappedDecl(attribute);
233 attribute->decl()->removeMappedProperty(this, id);
236 void StyledElement::addCSSProperty(Attribute* attribute, int id, const String &value)
238 if (!attribute->decl())
239 createMappedDecl(attribute);
240 attribute->decl()->setMappedProperty(this, id, value);
243 void StyledElement::addCSSProperty(Attribute* attribute, int id, int value)
245 if (!attribute->decl())
246 createMappedDecl(attribute);
247 attribute->decl()->setMappedProperty(this, id, value);
250 void StyledElement::addCSSImageProperty(Attribute* attribute, int id, const String& url)
252 if (!attribute->decl())
253 createMappedDecl(attribute);
254 attribute->decl()->setMappedImageProperty(this, id, url);
257 void StyledElement::addCSSLength(Attribute* attribute, int id, const String &value)
259 // FIXME: This function should not spin up the CSS parser, but should instead just figure out the correct
260 // length unit and make the appropriate parsed value.
261 if (!attribute->decl())
262 createMappedDecl(attribute);
264 // strip attribute garbage..
265 StringImpl* v = value.impl();
269 while (l < v->length() && (*v)[l] <= ' ')
272 for (; l < v->length(); l++) {
277 if (cc == '%' || cc == '*')
284 if (l != v->length()) {
285 attribute->decl()->setMappedLengthProperty(this, id, v->substring(0, l));
290 attribute->decl()->setMappedLengthProperty(this, id, value);
293 static String parseColorStringWithCrazyLegacyRules(const String& colorString)
295 // Per spec, only look at the first 128 digits of the string.
296 const size_t maxColorLength = 128;
297 // We'll pad the buffer with two extra 0s later, so reserve two more than the max.
298 Vector<char, maxColorLength+2> digitBuffer;
302 if (colorString[0] == '#')
305 // Grab the first 128 characters, replacing non-hex characters with 0.
306 // Non-BMP characters are replaced with "00" due to them appearing as two "characters" in the String.
307 for (; i < colorString.length() && digitBuffer.size() < maxColorLength; i++) {
308 if (!isASCIIHexDigit(colorString[i]))
309 digitBuffer.append('0');
311 digitBuffer.append(colorString[i]);
314 if (!digitBuffer.size())
317 // Pad the buffer out to at least the next multiple of three in size.
318 digitBuffer.append('0');
319 digitBuffer.append('0');
321 if (digitBuffer.size() < 6)
322 return String::format("#0%c0%c0%c", digitBuffer[0], digitBuffer[1], digitBuffer[2]);
324 // Split the digits into three components, then search the last 8 digits of each component.
325 ASSERT(digitBuffer.size() >= 6);
326 size_t componentLength = digitBuffer.size() / 3;
327 size_t componentSearchWindowLength = min<size_t>(componentLength, 8);
328 size_t redIndex = componentLength - componentSearchWindowLength;
329 size_t greenIndex = componentLength * 2 - componentSearchWindowLength;
330 size_t blueIndex = componentLength * 3 - componentSearchWindowLength;
331 // Skip digits until one of them is non-zero, or we've only got two digits left in the component.
332 while (digitBuffer[redIndex] == '0' && digitBuffer[greenIndex] == '0' && digitBuffer[blueIndex] == '0' && (componentLength - redIndex) > 2) {
337 ASSERT(redIndex + 1 < componentLength);
338 ASSERT(greenIndex >= componentLength);
339 ASSERT(greenIndex + 1 < componentLength * 2);
340 ASSERT(blueIndex >= componentLength * 2);
341 ASSERT(blueIndex + 1 < digitBuffer.size());
342 return String::format("#%c%c%c%c%c%c", digitBuffer[redIndex], digitBuffer[redIndex + 1], digitBuffer[greenIndex], digitBuffer[greenIndex + 1], digitBuffer[blueIndex], digitBuffer[blueIndex + 1]);
345 // Color parsing that matches HTML's "rules for parsing a legacy color value"
346 void StyledElement::addCSSColor(Attribute* attribute, int id, const String& attributeValue)
348 // An empty string doesn't apply a color. (One containing only whitespace does, which is why this check occurs before stripping.)
349 if (attributeValue.isEmpty())
352 String colorString = attributeValue.stripWhiteSpace();
354 // "transparent" doesn't apply a color either.
355 if (equalIgnoringCase(colorString, "transparent"))
358 if (!attribute->decl())
359 createMappedDecl(attribute);
361 // If the string is a named CSS color or a 3/6-digit hex color, use that.
362 Color parsedColor(colorString);
363 if (parsedColor.isValid()) {
364 attribute->decl()->setMappedProperty(this, id, colorString);
368 attribute->decl()->setMappedProperty(this, id, parseColorStringWithCrazyLegacyRules(colorString));
371 void StyledElement::createMappedDecl(Attribute* attr)
373 RefPtr<CSSMappedAttributeDeclaration> decl = CSSMappedAttributeDeclaration::create();
375 ASSERT(!decl->useStrictParsing());
378 unsigned MappedAttributeHash::hash(const MappedAttributeKey& key)
380 COMPILE_ASSERT(sizeof(key.name) == 4 || sizeof(key.name) == 8, key_name_size);
381 COMPILE_ASSERT(sizeof(key.value) == 4 || sizeof(key.value) == 8, key_value_size);
386 data = reinterpret_cast<const UChar*>(&key.name);
387 hasher.addCharacters(data[0], data[1]);
388 if (sizeof(key.name) == 8)
389 hasher.addCharacters(data[2], data[3]);
391 data = reinterpret_cast<const UChar*>(&key.value);
392 hasher.addCharacters(data[0], data[1]);
393 if (sizeof(key.value) == 8)
394 hasher.addCharacters(data[2], data[3]);
396 return hasher.hash();
399 void StyledElement::copyNonAttributeProperties(const Element* sourceElement)
401 ASSERT(sourceElement);
402 ASSERT(sourceElement->isStyledElement());
404 const StyledElement* source = static_cast<const StyledElement*>(sourceElement);
405 if (!source->inlineStyleDecl())
408 CSSMutableStyleDeclaration* inlineStyle = ensureInlineStyleDecl();
409 inlineStyle->copyPropertiesFrom(*source->inlineStyleDecl());
410 inlineStyle->setStrictParsing(source->inlineStyleDecl()->useStrictParsing());
412 setIsStyleAttributeValid(source->isStyleAttributeValid());
413 setIsSynchronizingStyleAttribute(source->isSynchronizingStyleAttribute());
415 Element::copyNonAttributeProperties(sourceElement);
418 void StyledElement::addSubresourceAttributeURLs(ListHashSet<KURL>& urls) const
420 if (CSSMutableStyleDeclaration* inlineStyle = inlineStyleDecl())
421 inlineStyle->addSubresourceStyleURLs(urls);