Home | History | Annotate | Download | only in dom
      1 /*
      2  * Copyright (C) 1999 Lars Knoll (knoll (at) kde.org)
      3  *           (C) 1999 Antti Koivisto (koivisto (at) kde.org)
      4  *           (C) 2001 Peter Kelly (pmk (at) post.com)
      5  *           (C) 2001 Dirk Mueller (mueller (at) 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;
     49     MappedAttributeKey(MappedAttributeEntry t = eNone, StringImpl* n = 0, StringImpl* v = 0)
     50         : type(t), name(n), value(v) { }
     51 };
     52 
     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;
     59     static void constructDeletedValue(MappedAttributeKey& slot) { slot.type = eLastEntry; }
     60     static bool isDeletedValue(const MappedAttributeKey& value) { return value.type == eLastEntry; }
     61 };
     62 
     63 struct MappedAttributeHash {
     64     static unsigned hash(const MappedAttributeKey&);
     65     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 
     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 
     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 
     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 
     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 
    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 
    108 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 
    118 StyledElement::~StyledElement()
    119 {
    120     destroyInlineStyleDecl();
    121 }
    122 
    123 PassRefPtr<Attribute> StyledElement::createAttribute(const QualifiedName& name, const AtomicString& value)
    124 {
    125     return Attribute::createMapped(name, value);
    126 }
    127 
    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 
    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 
    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 
    205 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 
    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 
    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 
    250 CSSMutableStyleDeclaration* StyledElement::getInlineStyleDecl()
    251 {
    252     if (!m_inlineStyleDecl)
    253         createInlineStyleDecl();
    254     return m_inlineStyleDecl.get();
    255 }
    256 
    257 CSSStyleDeclaration* StyledElement::style()
    258 {
    259     return getInlineStyleDecl();
    260 }
    261 
    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 
    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 
    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 
    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. */
    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 
    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 
    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 
    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 
    435 void StyledElement::addSubresourceAttributeURLs(ListHashSet<KURL>& urls) const
    436 {
    437     if (CSSMutableStyleDeclaration* style = inlineStyleDecl())
    438         style->addSubresourceStyleURLs(urls);
    439 }
    440 
    441 
    442 void StyledElement::didMoveToNewOwnerDocument()
    443 {
    444     if (m_inlineStyleDecl)
    445         m_inlineStyleDecl->setParent(document()->elementSheet());
    446 
    447     Element::didMoveToNewOwnerDocument();
    448 }
    449 
    450 }
    451