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 "StyleProperties.h"
32#include "StyledElement.h"
33
34namespace WebCore {
35
36namespace {
37
38class StyleAttributeMutationScope {
39    WTF_MAKE_NONCOPYABLE(StyleAttributeMutationScope);
40public:
41    StyleAttributeMutationScope(PropertySetCSSStyleDeclaration* decl)
42    {
43        ++s_scopeCount;
44
45        if (s_scopeCount != 1) {
46            ASSERT(s_currentDecl == decl);
47            return;
48        }
49
50        ASSERT(!s_currentDecl);
51        s_currentDecl = decl;
52
53        if (!s_currentDecl->parentElement())
54            return;
55
56        bool shouldReadOldValue = false;
57
58        m_mutationRecipients = MutationObserverInterestGroup::createForAttributesMutation(*s_currentDecl->parentElement(), HTMLNames::styleAttr);
59        if (m_mutationRecipients && m_mutationRecipients->isOldValueRequested())
60            shouldReadOldValue = true;
61
62        AtomicString oldValue;
63        if (shouldReadOldValue)
64            oldValue = s_currentDecl->parentElement()->getAttribute(HTMLNames::styleAttr);
65
66        if (m_mutationRecipients) {
67            AtomicString requestedOldValue = m_mutationRecipients->isOldValueRequested() ? oldValue : nullAtom;
68            m_mutation = MutationRecord::createAttributes(*s_currentDecl->parentElement(), HTMLNames::styleAttr, requestedOldValue);
69        }
70    }
71
72    ~StyleAttributeMutationScope()
73    {
74        --s_scopeCount;
75        if (s_scopeCount)
76            return;
77
78        if (m_mutation && s_shouldDeliver)
79            m_mutationRecipients->enqueueMutationRecord(m_mutation);
80
81        s_shouldDeliver = false;
82        if (!s_shouldNotifyInspector) {
83            s_currentDecl = 0;
84            return;
85        }
86        // We have to clear internal state before calling Inspector's code.
87        PropertySetCSSStyleDeclaration* localCopyStyleDecl = s_currentDecl;
88        s_currentDecl = 0;
89        s_shouldNotifyInspector = false;
90        if (localCopyStyleDecl->parentElement())
91            InspectorInstrumentation::didInvalidateStyleAttr(&localCopyStyleDecl->parentElement()->document(), localCopyStyleDecl->parentElement());
92    }
93
94    void enqueueMutationRecord()
95    {
96        s_shouldDeliver = true;
97    }
98
99    void didInvalidateStyleAttr()
100    {
101        s_shouldNotifyInspector = true;
102    }
103
104private:
105    static unsigned s_scopeCount;
106    static PropertySetCSSStyleDeclaration* s_currentDecl;
107    static bool s_shouldNotifyInspector;
108    static bool s_shouldDeliver;
109
110    std::unique_ptr<MutationObserverInterestGroup> m_mutationRecipients;
111    RefPtr<MutationRecord> m_mutation;
112};
113
114unsigned StyleAttributeMutationScope::s_scopeCount = 0;
115PropertySetCSSStyleDeclaration* StyleAttributeMutationScope::s_currentDecl = 0;
116bool StyleAttributeMutationScope::s_shouldNotifyInspector = false;
117bool StyleAttributeMutationScope::s_shouldDeliver = false;
118
119} // namespace
120
121void PropertySetCSSStyleDeclaration::ref()
122{
123    m_propertySet->ref();
124}
125
126void PropertySetCSSStyleDeclaration::deref()
127{
128    m_propertySet->deref();
129}
130
131unsigned PropertySetCSSStyleDeclaration::length() const
132{
133    return m_propertySet->propertyCount();
134}
135
136String PropertySetCSSStyleDeclaration::item(unsigned i) const
137{
138    if (i >= m_propertySet->propertyCount())
139        return "";
140    return m_propertySet->propertyAt(i).cssName();
141}
142
143String PropertySetCSSStyleDeclaration::cssText() const
144{
145    return m_propertySet->asText();
146}
147
148void PropertySetCSSStyleDeclaration::setCssText(const String& text, ExceptionCode& ec)
149{
150    StyleAttributeMutationScope mutationScope(this);
151    if (!willMutate())
152        return;
153
154    ec = 0;
155    // FIXME: Detect syntax errors and set ec.
156    m_propertySet->parseDeclaration(text, contextStyleSheet());
157
158    didMutate(PropertyChanged);
159
160    mutationScope.enqueueMutationRecord();
161}
162
163PassRefPtr<CSSValue> PropertySetCSSStyleDeclaration::getPropertyCSSValue(const String& propertyName)
164{
165    CSSPropertyID propertyID = cssPropertyID(propertyName);
166    if (!propertyID)
167        return 0;
168    return cloneAndCacheForCSSOM(m_propertySet->getPropertyCSSValue(propertyID).get());
169}
170
171String PropertySetCSSStyleDeclaration::getPropertyValue(const String &propertyName)
172{
173    CSSPropertyID propertyID = cssPropertyID(propertyName);
174    if (!propertyID)
175        return String();
176    return m_propertySet->getPropertyValue(propertyID);
177}
178
179String PropertySetCSSStyleDeclaration::getPropertyPriority(const String& propertyName)
180{
181    CSSPropertyID propertyID = cssPropertyID(propertyName);
182    if (!propertyID)
183        return String();
184    return m_propertySet->propertyIsImportant(propertyID) ? "important" : "";
185}
186
187String PropertySetCSSStyleDeclaration::getPropertyShorthand(const String& propertyName)
188{
189    CSSPropertyID propertyID = cssPropertyID(propertyName);
190    if (!propertyID)
191        return String();
192    return m_propertySet->getPropertyShorthand(propertyID);
193}
194
195bool PropertySetCSSStyleDeclaration::isPropertyImplicit(const String& propertyName)
196{
197    CSSPropertyID propertyID = cssPropertyID(propertyName);
198    if (!propertyID)
199        return false;
200    return m_propertySet->isPropertyImplicit(propertyID);
201}
202
203void PropertySetCSSStyleDeclaration::setProperty(const String& propertyName, const String& value, const String& priority, ExceptionCode& ec)
204{
205    StyleAttributeMutationScope mutationScope(this);
206    CSSPropertyID propertyID = cssPropertyID(propertyName);
207    if (!propertyID)
208        return;
209
210    if (!willMutate())
211        return;
212
213    bool important = priority.find("important", 0, false) != notFound;
214
215    ec = 0;
216    bool changed = m_propertySet->setProperty(propertyID, value, important, contextStyleSheet());
217
218    didMutate(changed ? PropertyChanged : NoChanges);
219
220    if (changed) {
221        // CSS DOM requires raising SYNTAX_ERR of parsing failed, but this is too dangerous for compatibility,
222        // see <http://bugs.webkit.org/show_bug.cgi?id=7296>.
223        mutationScope.enqueueMutationRecord();
224    }
225}
226
227String PropertySetCSSStyleDeclaration::removeProperty(const String& propertyName, ExceptionCode& ec)
228{
229    StyleAttributeMutationScope mutationScope(this);
230    CSSPropertyID propertyID = cssPropertyID(propertyName);
231    if (!propertyID)
232        return String();
233
234    if (!willMutate())
235        return String();
236
237    ec = 0;
238    String result;
239    bool changed = m_propertySet->removeProperty(propertyID, &result);
240
241    didMutate(changed ? PropertyChanged : NoChanges);
242
243    if (changed)
244        mutationScope.enqueueMutationRecord();
245    return result;
246}
247
248PassRefPtr<CSSValue> PropertySetCSSStyleDeclaration::getPropertyCSSValueInternal(CSSPropertyID propertyID)
249{
250    return m_propertySet->getPropertyCSSValue(propertyID);
251}
252
253String PropertySetCSSStyleDeclaration::getPropertyValueInternal(CSSPropertyID propertyID)
254{
255    return m_propertySet->getPropertyValue(propertyID);
256}
257
258void PropertySetCSSStyleDeclaration::setPropertyInternal(CSSPropertyID propertyID, const String& value, bool important, ExceptionCode& ec)
259{
260    StyleAttributeMutationScope mutationScope(this);
261    if (!willMutate())
262        return;
263
264    ec = 0;
265    bool changed = m_propertySet->setProperty(propertyID, value, important, contextStyleSheet());
266
267    didMutate(changed ? PropertyChanged : NoChanges);
268
269    if (changed)
270        mutationScope.enqueueMutationRecord();
271}
272
273CSSValue* PropertySetCSSStyleDeclaration::cloneAndCacheForCSSOM(CSSValue* internalValue)
274{
275    if (!internalValue)
276        return 0;
277
278    // The map is here to maintain the object identity of the CSSValues over multiple invocations.
279    // FIXME: It is likely that the identity is not important for web compatibility and this code should be removed.
280    if (!m_cssomCSSValueClones)
281        m_cssomCSSValueClones = std::make_unique<HashMap<CSSValue*, RefPtr<CSSValue>>>();
282
283    RefPtr<CSSValue>& clonedValue = m_cssomCSSValueClones->add(internalValue, RefPtr<CSSValue>()).iterator->value;
284    if (!clonedValue)
285        clonedValue = internalValue->cloneForCSSOM();
286    return clonedValue.get();
287}
288
289StyleSheetContents* PropertySetCSSStyleDeclaration::contextStyleSheet() const
290{
291    CSSStyleSheet* cssStyleSheet = parentStyleSheet();
292    return cssStyleSheet ? &cssStyleSheet->contents() : 0;
293}
294
295PassRef<MutableStyleProperties> PropertySetCSSStyleDeclaration::copyProperties() const
296{
297    return m_propertySet->mutableCopy();
298}
299
300StyleRuleCSSStyleDeclaration::StyleRuleCSSStyleDeclaration(MutableStyleProperties& propertySet, CSSRule& parentRule)
301    : PropertySetCSSStyleDeclaration(&propertySet)
302    , m_refCount(1)
303    , m_parentRule(&parentRule)
304{
305    m_propertySet->ref();
306}
307
308StyleRuleCSSStyleDeclaration::~StyleRuleCSSStyleDeclaration()
309{
310    m_propertySet->deref();
311}
312
313void StyleRuleCSSStyleDeclaration::ref()
314{
315    ++m_refCount;
316}
317
318void StyleRuleCSSStyleDeclaration::deref()
319{
320    ASSERT(m_refCount);
321    if (!--m_refCount)
322        delete this;
323}
324
325bool StyleRuleCSSStyleDeclaration::willMutate()
326{
327    if (!m_parentRule || !m_parentRule->parentStyleSheet())
328        return false;
329    m_parentRule->parentStyleSheet()->willMutateRules();
330    return true;
331}
332
333void StyleRuleCSSStyleDeclaration::didMutate(MutationType type)
334{
335    ASSERT(m_parentRule);
336    ASSERT(m_parentRule->parentStyleSheet());
337
338    if (type == PropertyChanged)
339        m_cssomCSSValueClones = nullptr;
340
341    // Style sheet mutation needs to be signaled even if the change failed. willMutate*/didMutate* must pair.
342    m_parentRule->parentStyleSheet()->didMutateRuleFromCSSStyleDeclaration();
343}
344
345CSSStyleSheet* StyleRuleCSSStyleDeclaration::parentStyleSheet() const
346{
347    return m_parentRule ? m_parentRule->parentStyleSheet() : 0;
348}
349
350void StyleRuleCSSStyleDeclaration::reattach(MutableStyleProperties& propertySet)
351{
352    m_propertySet->deref();
353    m_propertySet = &propertySet;
354    m_propertySet->ref();
355}
356
357void InlineCSSStyleDeclaration::didMutate(MutationType type)
358{
359    if (type == NoChanges)
360        return;
361
362    m_cssomCSSValueClones = nullptr;
363
364    if (!m_parentElement)
365        return;
366
367    m_parentElement->setNeedsStyleRecalc(InlineStyleChange);
368    m_parentElement->invalidateStyleAttribute();
369    StyleAttributeMutationScope(this).didInvalidateStyleAttr();
370}
371
372CSSStyleSheet* InlineCSSStyleDeclaration::parentStyleSheet() const
373{
374    return m_parentElement ? &m_parentElement->document().elementSheet() : 0;
375}
376
377} // namespace WebCore
378