1/*
2 * (C) 1999-2003 Lars Knoll (knoll@kde.org)
3 * Copyright (C) 2004, 2005, 2006, 2007, 2008, 2009, 2010, 2012 Apple Inc. All rights reserved.
4 * Copyright (C) 2011 Research In Motion Limited. All rights reserved.
5 *
6 * This library is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU Library General Public
8 * License as published by the Free Software Foundation; either
9 * version 2 of the License, or (at your option) any later version.
10 *
11 * This library is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14 * Library General Public License for more details.
15 *
16 * You should have received a copy of the GNU Library General Public License
17 * along with this library; see the file COPYING.LIB.  If not, write to
18 * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
19 * Boston, MA 02110-1301, USA.
20 */
21
22#include "config.h"
23#include "PropertySetCSSStyleDeclaration.h"
24
25#include "CSSParser.h"
26#include "CSSStyleSheet.h"
27#include "HTMLNames.h"
28#include "InspectorInstrumentation.h"
29#include "MutationObserverInterestGroup.h"
30#include "MutationRecord.h"
31#include "StylePropertySet.h"
32#include "StyledElement.h"
33
34using namespace std;
35
36namespace WebCore {
37
38namespace {
39
40class StyleAttributeMutationScope {
41    WTF_MAKE_NONCOPYABLE(StyleAttributeMutationScope);
42public:
43    StyleAttributeMutationScope(PropertySetCSSStyleDeclaration* decl)
44    {
45        ++s_scopeCount;
46
47        if (s_scopeCount != 1) {
48            ASSERT(s_currentDecl == decl);
49            return;
50        }
51
52        ASSERT(!s_currentDecl);
53        s_currentDecl = decl;
54
55        if (!s_currentDecl->parentElement())
56            return;
57
58        bool shouldReadOldValue = false;
59
60        m_mutationRecipients = MutationObserverInterestGroup::createForAttributesMutation(s_currentDecl->parentElement(), HTMLNames::styleAttr);
61        if (m_mutationRecipients && m_mutationRecipients->isOldValueRequested())
62            shouldReadOldValue = true;
63
64        AtomicString oldValue;
65        if (shouldReadOldValue)
66            oldValue = s_currentDecl->parentElement()->getAttribute(HTMLNames::styleAttr);
67
68        if (m_mutationRecipients) {
69            AtomicString requestedOldValue = m_mutationRecipients->isOldValueRequested() ? oldValue : nullAtom;
70            m_mutation = MutationRecord::createAttributes(s_currentDecl->parentElement(), HTMLNames::styleAttr, requestedOldValue);
71        }
72    }
73
74    ~StyleAttributeMutationScope()
75    {
76        --s_scopeCount;
77        if (s_scopeCount)
78            return;
79
80        if (m_mutation && s_shouldDeliver)
81            m_mutationRecipients->enqueueMutationRecord(m_mutation);
82
83        s_shouldDeliver = false;
84        if (!s_shouldNotifyInspector) {
85            s_currentDecl = 0;
86            return;
87        }
88        // We have to clear internal state before calling Inspector's code.
89        PropertySetCSSStyleDeclaration* localCopyStyleDecl = s_currentDecl;
90        s_currentDecl = 0;
91        s_shouldNotifyInspector = false;
92        if (localCopyStyleDecl->parentElement() && localCopyStyleDecl->parentElement()->document())
93            InspectorInstrumentation::didInvalidateStyleAttr(localCopyStyleDecl->parentElement()->document(), localCopyStyleDecl->parentElement());
94    }
95
96    void enqueueMutationRecord()
97    {
98        s_shouldDeliver = true;
99    }
100
101    void didInvalidateStyleAttr()
102    {
103        s_shouldNotifyInspector = true;
104    }
105
106private:
107    static unsigned s_scopeCount;
108    static PropertySetCSSStyleDeclaration* s_currentDecl;
109    static bool s_shouldNotifyInspector;
110    static bool s_shouldDeliver;
111
112    OwnPtr<MutationObserverInterestGroup> m_mutationRecipients;
113    RefPtr<MutationRecord> m_mutation;
114};
115
116unsigned StyleAttributeMutationScope::s_scopeCount = 0;
117PropertySetCSSStyleDeclaration* StyleAttributeMutationScope::s_currentDecl = 0;
118bool StyleAttributeMutationScope::s_shouldNotifyInspector = false;
119bool StyleAttributeMutationScope::s_shouldDeliver = false;
120
121} // namespace
122
123void PropertySetCSSStyleDeclaration::ref()
124{
125    m_propertySet->ref();
126}
127
128void PropertySetCSSStyleDeclaration::deref()
129{
130    m_propertySet->deref();
131}
132
133unsigned PropertySetCSSStyleDeclaration::length() const
134{
135    return m_propertySet->propertyCount();
136}
137
138String PropertySetCSSStyleDeclaration::item(unsigned i) const
139{
140    if (i >= m_propertySet->propertyCount())
141        return "";
142    return m_propertySet->propertyAt(i).cssName();
143}
144
145String PropertySetCSSStyleDeclaration::cssText() const
146{
147    return m_propertySet->asText();
148}
149
150void PropertySetCSSStyleDeclaration::setCssText(const String& text, ExceptionCode& ec)
151{
152    StyleAttributeMutationScope mutationScope(this);
153    if (!willMutate())
154        return;
155
156    ec = 0;
157    // FIXME: Detect syntax errors and set ec.
158    m_propertySet->parseDeclaration(text, contextStyleSheet());
159
160    didMutate(PropertyChanged);
161
162    mutationScope.enqueueMutationRecord();
163}
164
165PassRefPtr<CSSValue> PropertySetCSSStyleDeclaration::getPropertyCSSValue(const String& propertyName)
166{
167    CSSPropertyID propertyID = cssPropertyID(propertyName);
168    if (!propertyID)
169        return 0;
170    return cloneAndCacheForCSSOM(m_propertySet->getPropertyCSSValue(propertyID).get());
171}
172
173String PropertySetCSSStyleDeclaration::getPropertyValue(const String &propertyName)
174{
175    CSSPropertyID propertyID = cssPropertyID(propertyName);
176    if (!propertyID)
177        return String();
178    return m_propertySet->getPropertyValue(propertyID);
179}
180
181String PropertySetCSSStyleDeclaration::getPropertyPriority(const String& propertyName)
182{
183    CSSPropertyID propertyID = cssPropertyID(propertyName);
184    if (!propertyID)
185        return String();
186    return m_propertySet->propertyIsImportant(propertyID) ? "important" : "";
187}
188
189String PropertySetCSSStyleDeclaration::getPropertyShorthand(const String& propertyName)
190{
191    CSSPropertyID propertyID = cssPropertyID(propertyName);
192    if (!propertyID)
193        return String();
194    return m_propertySet->getPropertyShorthand(propertyID);
195}
196
197bool PropertySetCSSStyleDeclaration::isPropertyImplicit(const String& propertyName)
198{
199    CSSPropertyID propertyID = cssPropertyID(propertyName);
200    if (!propertyID)
201        return false;
202    return m_propertySet->isPropertyImplicit(propertyID);
203}
204
205void PropertySetCSSStyleDeclaration::setProperty(const String& propertyName, const String& value, const String& priority, ExceptionCode& ec)
206{
207    StyleAttributeMutationScope mutationScope(this);
208    CSSPropertyID propertyID = cssPropertyID(propertyName);
209    if (!propertyID)
210        return;
211
212    if (!willMutate())
213        return;
214
215    bool important = priority.find("important", 0, false) != notFound;
216
217    ec = 0;
218    bool changed = m_propertySet->setProperty(propertyID, value, important, contextStyleSheet());
219
220    didMutate(changed ? PropertyChanged : NoChanges);
221
222    if (changed) {
223        // CSS DOM requires raising SYNTAX_ERR of parsing failed, but this is too dangerous for compatibility,
224        // see <http://bugs.webkit.org/show_bug.cgi?id=7296>.
225        mutationScope.enqueueMutationRecord();
226    }
227}
228
229String PropertySetCSSStyleDeclaration::removeProperty(const String& propertyName, ExceptionCode& ec)
230{
231    StyleAttributeMutationScope mutationScope(this);
232    CSSPropertyID propertyID = cssPropertyID(propertyName);
233    if (!propertyID)
234        return String();
235
236    if (!willMutate())
237        return String();
238
239    ec = 0;
240    String result;
241    bool changed = m_propertySet->removeProperty(propertyID, &result);
242
243    didMutate(changed ? PropertyChanged : NoChanges);
244
245    if (changed)
246        mutationScope.enqueueMutationRecord();
247    return result;
248}
249
250PassRefPtr<CSSValue> PropertySetCSSStyleDeclaration::getPropertyCSSValueInternal(CSSPropertyID propertyID)
251{
252    return m_propertySet->getPropertyCSSValue(propertyID);
253}
254
255String PropertySetCSSStyleDeclaration::getPropertyValueInternal(CSSPropertyID propertyID)
256{
257    return m_propertySet->getPropertyValue(propertyID);
258}
259
260void PropertySetCSSStyleDeclaration::setPropertyInternal(CSSPropertyID propertyID, const String& value, bool important, ExceptionCode& ec)
261{
262    StyleAttributeMutationScope mutationScope(this);
263    if (!willMutate())
264        return;
265
266    ec = 0;
267    bool changed = m_propertySet->setProperty(propertyID, value, important, contextStyleSheet());
268
269    didMutate(changed ? PropertyChanged : NoChanges);
270
271    if (changed)
272        mutationScope.enqueueMutationRecord();
273}
274
275CSSValue* PropertySetCSSStyleDeclaration::cloneAndCacheForCSSOM(CSSValue* internalValue)
276{
277    if (!internalValue)
278        return 0;
279
280    // The map is here to maintain the object identity of the CSSValues over multiple invocations.
281    // FIXME: It is likely that the identity is not important for web compatibility and this code should be removed.
282    if (!m_cssomCSSValueClones)
283        m_cssomCSSValueClones = adoptPtr(new HashMap<CSSValue*, RefPtr<CSSValue> >);
284
285    RefPtr<CSSValue>& clonedValue = m_cssomCSSValueClones->add(internalValue, RefPtr<CSSValue>()).iterator->value;
286    if (!clonedValue)
287        clonedValue = internalValue->cloneForCSSOM();
288    return clonedValue.get();
289}
290
291StyleSheetContents* PropertySetCSSStyleDeclaration::contextStyleSheet() const
292{
293    CSSStyleSheet* cssStyleSheet = parentStyleSheet();
294    return cssStyleSheet ? cssStyleSheet->contents() : 0;
295}
296
297PassRefPtr<MutableStylePropertySet> PropertySetCSSStyleDeclaration::copyProperties() const
298{
299    return m_propertySet->mutableCopy();
300}
301
302StyleRuleCSSStyleDeclaration::StyleRuleCSSStyleDeclaration(MutableStylePropertySet* propertySet, CSSRule* parentRule)
303    : PropertySetCSSStyleDeclaration(propertySet)
304    , m_refCount(1)
305    , m_parentRule(parentRule)
306{
307    m_propertySet->ref();
308}
309
310StyleRuleCSSStyleDeclaration::~StyleRuleCSSStyleDeclaration()
311{
312    m_propertySet->deref();
313}
314
315void StyleRuleCSSStyleDeclaration::ref()
316{
317    ++m_refCount;
318}
319
320void StyleRuleCSSStyleDeclaration::deref()
321{
322    ASSERT(m_refCount);
323    if (!--m_refCount)
324        delete this;
325}
326
327bool StyleRuleCSSStyleDeclaration::willMutate()
328{
329    if (!m_parentRule || !m_parentRule->parentStyleSheet())
330        return false;
331    m_parentRule->parentStyleSheet()->willMutateRules();
332    return true;
333}
334
335void StyleRuleCSSStyleDeclaration::didMutate(MutationType type)
336{
337    ASSERT(m_parentRule);
338    ASSERT(m_parentRule->parentStyleSheet());
339
340    if (type == PropertyChanged)
341        m_cssomCSSValueClones.clear();
342
343    // Style sheet mutation needs to be signaled even if the change failed. willMutate*/didMutate* must pair.
344    m_parentRule->parentStyleSheet()->didMutateRuleFromCSSStyleDeclaration();
345}
346
347CSSStyleSheet* StyleRuleCSSStyleDeclaration::parentStyleSheet() const
348{
349    return m_parentRule ? m_parentRule->parentStyleSheet() : 0;
350}
351
352void StyleRuleCSSStyleDeclaration::reattach(MutableStylePropertySet* propertySet)
353{
354    ASSERT(propertySet);
355    m_propertySet->deref();
356    m_propertySet = propertySet;
357    m_propertySet->ref();
358}
359
360void InlineCSSStyleDeclaration::didMutate(MutationType type)
361{
362    if (type == NoChanges)
363        return;
364
365    m_cssomCSSValueClones.clear();
366
367    if (!m_parentElement)
368        return;
369
370    m_parentElement->setNeedsStyleRecalc(InlineStyleChange);
371    m_parentElement->invalidateStyleAttribute();
372    StyleAttributeMutationScope(this).didInvalidateStyleAttr();
373}
374
375CSSStyleSheet* InlineCSSStyleDeclaration::parentStyleSheet() const
376{
377    return m_parentElement ? m_parentElement->document()->elementSheet() : 0;
378}
379
380} // namespace WebCore
381