1/*
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.
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
39using namespace std;
40
41namespace WebCore {
42
43using namespace HTMLNames;
44
45struct 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
53static inline bool operator==(const MappedAttributeKey& a, const MappedAttributeKey& b)
54    { return a.type == b.type && a.name == b.name && a.value == b.value; }
55
56struct 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
63struct 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
69typedef HashMap<MappedAttributeKey, CSSMappedAttributeDeclaration*, MappedAttributeHash, MappedAttributeKeyTraits> MappedAttributeDecls;
70
71static MappedAttributeDecls* mappedAttributeDecls = 0;
72
73CSSMappedAttributeDeclaration* 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
80CSSMappedAttributeDeclaration* 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
87void 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
94void 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
101void 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
108void 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
118StyledElement::~StyledElement()
119{
120    destroyInlineStyleDecl();
121}
122
123PassRefPtr<Attribute> StyledElement::createAttribute(const QualifiedName& name, const AtomicString& value)
124{
125    return Attribute::createMapped(name, value);
126}
127
128void 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
136void StyledElement::destroyInlineStyleDecl()
137{
138    if (m_inlineStyleDecl) {
139        m_inlineStyleDecl->setNode(0);
140        m_inlineStyleDecl->setParent(0);
141        m_inlineStyleDecl = 0;
142    }
143}
144
145void 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
205bool StyledElement::mapToEntry(const QualifiedName& attrName, MappedAttributeEntry& result) const
206{
207    result = eNone;
208    if (attrName == styleAttr)
209        return !isSynchronizingStyleAttribute();
210    return true;
211}
212
213void 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
234void 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
250CSSMutableStyleDeclaration* StyledElement::getInlineStyleDecl()
251{
252    if (!m_inlineStyleDecl)
253        createInlineStyleDecl();
254    return m_inlineStyleDecl.get();
255}
256
257CSSStyleDeclaration* StyledElement::style()
258{
259    return getInlineStyleDecl();
260}
261
262void 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
269void 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
276void 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
283void 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. */
320void 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
392void 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
401unsigned 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
422void 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
435void StyledElement::addSubresourceAttributeURLs(ListHashSet<KURL>& urls) const
436{
437    if (CSSMutableStyleDeclaration* style = inlineStyleDecl())
438        style->addSubresourceStyleURLs(urls);
439}
440
441
442void StyledElement::didMoveToNewOwnerDocument()
443{
444    if (m_inlineStyleDecl)
445        m_inlineStyleDecl->setParent(document()->elementSheet());
446
447    Element::didMoveToNewOwnerDocument();
448}
449
450}
451