/* * Copyright (C) 1999 Lars Knoll (knoll@kde.org) * (C) 1999 Antti Koivisto (koivisto@kde.org) * (C) 2001 Peter Kelly (pmk@post.com) * (C) 2001 Dirk Mueller (mueller@kde.org) * Copyright (C) 2004, 2005, 2006, 2008, 2010 Apple Inc. All rights reserved. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "config.h" #include "StyledElement.h" #include "Attribute.h" #include "CSSMutableStyleDeclaration.h" #include "CSSStyleSelector.h" #include "CSSStyleSheet.h" #include "CSSValueKeywords.h" #include "ClassList.h" #include "DOMTokenList.h" #include "Document.h" #include "HTMLNames.h" #include "HTMLParserIdioms.h" #include <wtf/HashFunctions.h> using namespace std; namespace WebCore { using namespace HTMLNames; struct MappedAttributeKey { uint16_t type; StringImpl* name; StringImpl* value; MappedAttributeKey(MappedAttributeEntry t = eNone, StringImpl* n = 0, StringImpl* v = 0) : type(t), name(n), value(v) { } }; static inline bool operator==(const MappedAttributeKey& a, const MappedAttributeKey& b) { return a.type == b.type && a.name == b.name && a.value == b.value; } struct MappedAttributeKeyTraits : WTF::GenericHashTraits<MappedAttributeKey> { static const bool emptyValueIsZero = true; static const bool needsDestruction = false; static void constructDeletedValue(MappedAttributeKey& slot) { slot.type = eLastEntry; } static bool isDeletedValue(const MappedAttributeKey& value) { return value.type == eLastEntry; } }; struct MappedAttributeHash { static unsigned hash(const MappedAttributeKey&); static bool equal(const MappedAttributeKey& a, const MappedAttributeKey& b) { return a == b; } static const bool safeToCompareToEmptyOrDeleted = true; }; typedef HashMap<MappedAttributeKey, CSSMappedAttributeDeclaration*, MappedAttributeHash, MappedAttributeKeyTraits> MappedAttributeDecls; static MappedAttributeDecls* mappedAttributeDecls = 0; CSSMappedAttributeDeclaration* StyledElement::getMappedAttributeDecl(MappedAttributeEntry entryType, Attribute* attr) { if (!mappedAttributeDecls) return 0; return mappedAttributeDecls->get(MappedAttributeKey(entryType, attr->name().localName().impl(), attr->value().impl())); } CSSMappedAttributeDeclaration* StyledElement::getMappedAttributeDecl(MappedAttributeEntry type, const QualifiedName& name, const AtomicString& value) { if (!mappedAttributeDecls) return 0; return mappedAttributeDecls->get(MappedAttributeKey(type, name.localName().impl(), value.impl())); } void StyledElement::setMappedAttributeDecl(MappedAttributeEntry entryType, Attribute* attr, CSSMappedAttributeDeclaration* decl) { if (!mappedAttributeDecls) mappedAttributeDecls = new MappedAttributeDecls; mappedAttributeDecls->set(MappedAttributeKey(entryType, attr->name().localName().impl(), attr->value().impl()), decl); } void StyledElement::setMappedAttributeDecl(MappedAttributeEntry entryType, const QualifiedName& name, const AtomicString& value, CSSMappedAttributeDeclaration* decl) { if (!mappedAttributeDecls) mappedAttributeDecls = new MappedAttributeDecls; mappedAttributeDecls->set(MappedAttributeKey(entryType, name.localName().impl(), value.impl()), decl); } void StyledElement::removeMappedAttributeDecl(MappedAttributeEntry entryType, const QualifiedName& attrName, const AtomicString& attrValue) { if (!mappedAttributeDecls) return; mappedAttributeDecls->remove(MappedAttributeKey(entryType, attrName.localName().impl(), attrValue.impl())); } void StyledElement::updateStyleAttribute() const { ASSERT(!isStyleAttributeValid()); setIsStyleAttributeValid(); setIsSynchronizingStyleAttribute(); if (m_inlineStyleDecl) const_cast<StyledElement*>(this)->setAttribute(styleAttr, m_inlineStyleDecl->cssText()); clearIsSynchronizingStyleAttribute(); } StyledElement::~StyledElement() { destroyInlineStyleDecl(); } PassRefPtr<Attribute> StyledElement::createAttribute(const QualifiedName& name, const AtomicString& value) { return Attribute::createMapped(name, value); } void StyledElement::createInlineStyleDecl() { m_inlineStyleDecl = CSSMutableStyleDeclaration::create(); m_inlineStyleDecl->setParent(document()->elementSheet()); m_inlineStyleDecl->setNode(this); m_inlineStyleDecl->setStrictParsing(isHTMLElement() && !document()->inQuirksMode()); } void StyledElement::destroyInlineStyleDecl() { if (m_inlineStyleDecl) { m_inlineStyleDecl->setNode(0); m_inlineStyleDecl->setParent(0); m_inlineStyleDecl = 0; } } void StyledElement::attributeChanged(Attribute* attr, bool preserveDecls) { if (!attr->isMappedAttribute()) { Element::attributeChanged(attr, preserveDecls); return; } if (attr->decl() && !preserveDecls) { attr->setDecl(0); setNeedsStyleRecalc(); if (attributeMap()) attributeMap()->declRemoved(); } bool checkDecl = true; MappedAttributeEntry entry; bool needToParse = mapToEntry(attr->name(), entry); if (preserveDecls) { if (attr->decl()) { setNeedsStyleRecalc(); if (attributeMap()) attributeMap()->declAdded(); checkDecl = false; } } else if (!attr->isNull() && entry != eNone) { CSSMappedAttributeDeclaration* decl = getMappedAttributeDecl(entry, attr); if (decl) { attr->setDecl(decl); setNeedsStyleRecalc(); if (attributeMap()) attributeMap()->declAdded(); checkDecl = false; } else needToParse = true; } // parseMappedAttribute() might create a CSSMappedAttributeDeclaration on the attribute. // Normally we would be concerned about reseting the parent of those declarations in StyledElement::didMoveToNewOwnerDocument(). // But currently we always clear its parent and node below when adding it to the decl table. // If that changes for some reason moving between documents will be buggy. // webarchive/adopt-attribute-styled-node-webarchive.html should catch any resulting crashes. if (needToParse) parseMappedAttribute(attr); if (entry == eNone) recalcStyleIfNeededAfterAttributeChanged(attr); if (checkDecl && attr->decl()) { // Add the decl to the table in the appropriate spot. setMappedAttributeDecl(entry, attr, attr->decl()); attr->decl()->setMappedState(entry, attr->name(), attr->value()); attr->decl()->setParent(0); attr->decl()->setNode(0); if (attributeMap()) attributeMap()->declAdded(); } updateAfterAttributeChanged(attr); } bool StyledElement::mapToEntry(const QualifiedName& attrName, MappedAttributeEntry& result) const { result = eNone; if (attrName == styleAttr) return !isSynchronizingStyleAttribute(); return true; } void StyledElement::classAttributeChanged(const AtomicString& newClassString) { const UChar* characters = newClassString.characters(); unsigned length = newClassString.length(); unsigned i; for (i = 0; i < length; ++i) { if (isNotHTMLSpace(characters[i])) break; } bool hasClass = i < length; setHasClass(hasClass); if (hasClass) { attributes()->setClass(newClassString); if (DOMTokenList* classList = optionalClassList()) static_cast<ClassList*>(classList)->reset(newClassString); } else if (attributeMap()) attributeMap()->clearClass(); setNeedsStyleRecalc(); dispatchSubtreeModifiedEvent(); } void StyledElement::parseMappedAttribute(Attribute* attr) { if (isIdAttributeName(attr->name())) idAttributeChanged(attr); else if (attr->name() == classAttr) classAttributeChanged(attr->value()); else if (attr->name() == styleAttr) { if (attr->isNull()) destroyInlineStyleDecl(); else getInlineStyleDecl()->parseDeclaration(attr->value()); setIsStyleAttributeValid(); setNeedsStyleRecalc(); } } CSSMutableStyleDeclaration* StyledElement::getInlineStyleDecl() { if (!m_inlineStyleDecl) createInlineStyleDecl(); return m_inlineStyleDecl.get(); } CSSStyleDeclaration* StyledElement::style() { return getInlineStyleDecl(); } void StyledElement::addCSSProperty(Attribute* attribute, int id, const String &value) { if (!attribute->decl()) createMappedDecl(attribute); attribute->decl()->setProperty(id, value, false); } void StyledElement::addCSSProperty(Attribute* attribute, int id, int value) { if (!attribute->decl()) createMappedDecl(attribute); attribute->decl()->setProperty(id, value, false); } void StyledElement::addCSSImageProperty(Attribute* attribute, int id, const String& url) { if (!attribute->decl()) createMappedDecl(attribute); attribute->decl()->setImageProperty(id, url, false); } void StyledElement::addCSSLength(Attribute* attr, int id, const String &value) { // FIXME: This function should not spin up the CSS parser, but should instead just figure out the correct // length unit and make the appropriate parsed value. if (!attr->decl()) createMappedDecl(attr); // strip attribute garbage.. StringImpl* v = value.impl(); if (v) { unsigned int l = 0; while (l < v->length() && (*v)[l] <= ' ') l++; for (; l < v->length(); l++) { UChar cc = (*v)[l]; if (cc > '9') break; if (cc < '0') { if (cc == '%' || cc == '*') l++; if (cc != '.') break; } } if (l != v->length()) { attr->decl()->setLengthProperty(id, v->substring(0, l), false); return; } } attr->decl()->setLengthProperty(id, value, false); } /* color parsing that tries to match as close as possible IE 6. */ void StyledElement::addCSSColor(Attribute* attr, int id, const String& c) { // this is the only case no color gets applied in IE. if (!c.length()) return; if (!attr->decl()) createMappedDecl(attr); if (attr->decl()->setProperty(id, c, false)) return; String color = c; // not something that fits the specs. // we're emulating IEs color parser here. It maps transparent to black, otherwise it tries to build a rgb value // out of everything you put in. The algorithm is experimentally determined, but seems to work for all test cases I have. // the length of the color value is rounded up to the next // multiple of 3. each part of the rgb triple then gets one third // of the length. // // Each triplet is parsed byte by byte, mapping // each number to a hex value (0-9a-fA-F to their values // everything else to 0). // // The highest non zero digit in all triplets is remembered, and // used as a normalization point to normalize to values between 0 // and 255. if (!equalIgnoringCase(color, "transparent")) { if (color[0] == '#') color.remove(0, 1); int basicLength = (color.length() + 2) / 3; if (basicLength > 1) { // IE ignores colors with three digits or less int colors[3] = { 0, 0, 0 }; int component = 0; int pos = 0; int maxDigit = basicLength-1; while (component < 3) { // search forward for digits in the string int numDigits = 0; while (pos < (int)color.length() && numDigits < basicLength) { colors[component] <<= 4; if (isASCIIHexDigit(color[pos])) { colors[component] += toASCIIHexValue(color[pos]); maxDigit = min(maxDigit, numDigits); } numDigits++; pos++; } while (numDigits++ < basicLength) colors[component] <<= 4; component++; } maxDigit = basicLength - maxDigit; // normalize to 00-ff. The highest filled digit counts, minimum is 2 digits maxDigit -= 2; colors[0] >>= 4 * maxDigit; colors[1] >>= 4 * maxDigit; colors[2] >>= 4 * maxDigit; color = String::format("#%02x%02x%02x", colors[0], colors[1], colors[2]); if (attr->decl()->setProperty(id, color, false)) return; } } attr->decl()->setProperty(id, CSSValueBlack, false); } void StyledElement::createMappedDecl(Attribute* attr) { RefPtr<CSSMappedAttributeDeclaration> decl = CSSMappedAttributeDeclaration::create(); attr->setDecl(decl); decl->setParent(document()->elementSheet()); decl->setNode(this); decl->setStrictParsing(false); // Mapped attributes are just always quirky. } unsigned MappedAttributeHash::hash(const MappedAttributeKey& key) { COMPILE_ASSERT(sizeof(key.name) == 4 || sizeof(key.name) == 8, key_name_size); COMPILE_ASSERT(sizeof(key.value) == 4 || sizeof(key.value) == 8, key_value_size); StringHasher hasher; const UChar* data; data = reinterpret_cast<const UChar*>(&key.name); hasher.addCharacters(data[0], data[1]); if (sizeof(key.name) == 8) hasher.addCharacters(data[2], data[3]); data = reinterpret_cast<const UChar*>(&key.value); hasher.addCharacters(data[0], data[1]); if (sizeof(key.value) == 8) hasher.addCharacters(data[2], data[3]); return hasher.hash(); } void StyledElement::copyNonAttributeProperties(const Element *sourceElement) { const StyledElement* source = static_cast<const StyledElement*>(sourceElement); if (!source->m_inlineStyleDecl) return; *getInlineStyleDecl() = *source->m_inlineStyleDecl; setIsStyleAttributeValid(source->isStyleAttributeValid()); setIsSynchronizingStyleAttribute(source->isSynchronizingStyleAttribute()); Element::copyNonAttributeProperties(sourceElement); } void StyledElement::addSubresourceAttributeURLs(ListHashSet<KURL>& urls) const { if (CSSMutableStyleDeclaration* style = inlineStyleDecl()) style->addSubresourceStyleURLs(urls); } void StyledElement::didMoveToNewOwnerDocument() { if (m_inlineStyleDecl) m_inlineStyleDecl->setParent(document()->elementSheet()); Element::didMoveToNewOwnerDocument(); } }