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 "core/css/PropertySetCSSStyleDeclaration.h"
24
25#include "bindings/core/v8/ExceptionState.h"
26#include "core/HTMLNames.h"
27#include "core/css/CSSStyleSheet.h"
28#include "core/css/StylePropertySet.h"
29#include "core/css/parser/CSSParser.h"
30#include "core/dom/Element.h"
31#include "core/dom/MutationObserverInterestGroup.h"
32#include "core/dom/MutationRecord.h"
33#include "core/inspector/InspectorInstrumentation.h"
34#include "platform/RuntimeEnabledFeatures.h"
35
36namespace blink {
37
38namespace {
39
40class StyleAttributeMutationScope {
41    WTF_MAKE_NONCOPYABLE(StyleAttributeMutationScope);
42    STACK_ALLOCATED();
43public:
44    StyleAttributeMutationScope(AbstractPropertySetCSSStyleDeclaration* decl)
45    {
46        InspectorInstrumentation::willMutateStyle(decl);
47        ++s_scopeCount;
48
49        if (s_scopeCount != 1) {
50            ASSERT(s_currentDecl == decl);
51            return;
52        }
53
54        ASSERT(!s_currentDecl);
55        s_currentDecl = decl;
56
57        if (!s_currentDecl->parentElement())
58            return;
59
60        bool shouldReadOldValue = false;
61
62        m_mutationRecipients = MutationObserverInterestGroup::createForAttributesMutation(*s_currentDecl->parentElement(), HTMLNames::styleAttr);
63        if (m_mutationRecipients && m_mutationRecipients->isOldValueRequested())
64            shouldReadOldValue = true;
65
66        AtomicString oldValue;
67        if (shouldReadOldValue)
68            oldValue = s_currentDecl->parentElement()->getAttribute(HTMLNames::styleAttr);
69
70        if (m_mutationRecipients) {
71            AtomicString requestedOldValue = m_mutationRecipients->isOldValueRequested() ? oldValue : nullAtom;
72            m_mutation = MutationRecord::createAttributes(s_currentDecl->parentElement(), HTMLNames::styleAttr, requestedOldValue);
73        }
74    }
75
76    ~StyleAttributeMutationScope()
77    {
78        --s_scopeCount;
79        if (s_scopeCount)
80            return;
81
82        if (m_mutation && s_shouldDeliver)
83            m_mutationRecipients->enqueueMutationRecord(m_mutation);
84
85        s_shouldDeliver = false;
86
87        // We have to clear internal state before calling Inspector's code.
88        AbstractPropertySetCSSStyleDeclaration* localCopyStyleDecl = s_currentDecl;
89        s_currentDecl = 0;
90        InspectorInstrumentation::didMutateStyle(localCopyStyleDecl, localCopyStyleDecl->parentElement());
91
92        if (!s_shouldNotifyInspector)
93            return;
94
95        s_shouldNotifyInspector = false;
96        if (localCopyStyleDecl->parentElement())
97            InspectorInstrumentation::didInvalidateStyleAttr(localCopyStyleDecl->parentElement());
98    }
99
100    void enqueueMutationRecord()
101    {
102        s_shouldDeliver = true;
103    }
104
105    void didInvalidateStyleAttr()
106    {
107        s_shouldNotifyInspector = true;
108    }
109
110private:
111    static unsigned s_scopeCount;
112    static AbstractPropertySetCSSStyleDeclaration* s_currentDecl;
113    static bool s_shouldNotifyInspector;
114    static bool s_shouldDeliver;
115
116    OwnPtrWillBeMember<MutationObserverInterestGroup> m_mutationRecipients;
117    RefPtrWillBeMember<MutationRecord> m_mutation;
118};
119
120unsigned StyleAttributeMutationScope::s_scopeCount = 0;
121AbstractPropertySetCSSStyleDeclaration* StyleAttributeMutationScope::s_currentDecl = 0;
122bool StyleAttributeMutationScope::s_shouldNotifyInspector = false;
123bool StyleAttributeMutationScope::s_shouldDeliver = false;
124
125} // namespace
126
127#if !ENABLE(OILPAN)
128void PropertySetCSSStyleDeclaration::ref()
129{
130    m_propertySet->ref();
131}
132
133void PropertySetCSSStyleDeclaration::deref()
134{
135    m_propertySet->deref();
136}
137#endif
138
139void PropertySetCSSStyleDeclaration::trace(Visitor* visitor)
140{
141    visitor->trace(m_propertySet);
142    AbstractPropertySetCSSStyleDeclaration::trace(visitor);
143}
144
145unsigned AbstractPropertySetCSSStyleDeclaration::length() const
146{
147    return propertySet().propertyCount();
148}
149
150String AbstractPropertySetCSSStyleDeclaration::item(unsigned i) const
151{
152    if (i >= propertySet().propertyCount())
153        return "";
154    return propertySet().propertyAt(i).cssName();
155}
156
157String AbstractPropertySetCSSStyleDeclaration::cssText() const
158{
159    return propertySet().asText();
160}
161
162void AbstractPropertySetCSSStyleDeclaration::setCSSText(const String& text, ExceptionState& exceptionState)
163{
164    StyleAttributeMutationScope mutationScope(this);
165    willMutate();
166
167    // FIXME: Detect syntax errors and set exceptionState.
168    propertySet().parseDeclaration(text, contextStyleSheet());
169
170    didMutate(PropertyChanged);
171
172    mutationScope.enqueueMutationRecord();
173}
174
175PassRefPtrWillBeRawPtr<CSSValue> AbstractPropertySetCSSStyleDeclaration::getPropertyCSSValue(const String& propertyName)
176{
177    CSSPropertyID propertyID = cssPropertyID(propertyName);
178    if (!propertyID)
179        return nullptr;
180    return cloneAndCacheForCSSOM(propertySet().getPropertyCSSValue(propertyID).get());
181}
182
183String AbstractPropertySetCSSStyleDeclaration::getPropertyValue(const String &propertyName)
184{
185    CSSPropertyID propertyID = cssPropertyID(propertyName);
186    if (!propertyID)
187        return String();
188    return propertySet().getPropertyValue(propertyID);
189}
190
191String AbstractPropertySetCSSStyleDeclaration::getPropertyPriority(const String& propertyName)
192{
193    CSSPropertyID propertyID = cssPropertyID(propertyName);
194    if (!propertyID)
195        return String();
196    return propertySet().propertyIsImportant(propertyID) ? "important" : "";
197}
198
199String AbstractPropertySetCSSStyleDeclaration::getPropertyShorthand(const String& propertyName)
200{
201    CSSPropertyID propertyID = cssPropertyID(propertyName);
202    if (!propertyID)
203        return String();
204    CSSPropertyID shorthandID = propertySet().getPropertyShorthand(propertyID);
205    if (!shorthandID)
206        return String();
207    return getPropertyNameString(shorthandID);
208}
209
210bool AbstractPropertySetCSSStyleDeclaration::isPropertyImplicit(const String& propertyName)
211{
212    CSSPropertyID propertyID = cssPropertyID(propertyName);
213    if (!propertyID)
214        return false;
215    return propertySet().isPropertyImplicit(propertyID);
216}
217
218void AbstractPropertySetCSSStyleDeclaration::setProperty(const String& propertyName, const String& value, const String& priority, ExceptionState& exceptionState)
219{
220    CSSPropertyID propertyID = cssPropertyID(propertyName);
221    if (!propertyID)
222        return;
223
224    bool important = equalIgnoringCase(priority, "important");
225    if (!important && !priority.isEmpty())
226        return;
227
228    setPropertyInternal(propertyID, value, important, exceptionState);
229}
230
231String AbstractPropertySetCSSStyleDeclaration::removeProperty(const String& propertyName, ExceptionState& exceptionState)
232{
233    StyleAttributeMutationScope mutationScope(this);
234    CSSPropertyID propertyID = cssPropertyID(propertyName);
235    if (!propertyID)
236        return String();
237
238    willMutate();
239
240    String result;
241    bool changed = propertySet().removeProperty(propertyID, &result);
242
243    didMutate(changed ? PropertyChanged : NoChanges);
244
245    if (changed)
246        mutationScope.enqueueMutationRecord();
247    return result;
248}
249
250PassRefPtrWillBeRawPtr<CSSValue> AbstractPropertySetCSSStyleDeclaration::getPropertyCSSValueInternal(CSSPropertyID propertyID)
251{
252    return propertySet().getPropertyCSSValue(propertyID);
253}
254
255String AbstractPropertySetCSSStyleDeclaration::getPropertyValueInternal(CSSPropertyID propertyID)
256{
257    return propertySet().getPropertyValue(propertyID);
258}
259
260void AbstractPropertySetCSSStyleDeclaration::setPropertyInternal(CSSPropertyID propertyID, const String& value, bool important, ExceptionState&)
261{
262    StyleAttributeMutationScope mutationScope(this);
263    willMutate();
264
265    bool changed = propertySet().setProperty(propertyID, value, important, contextStyleSheet());
266
267    didMutate(changed ? PropertyChanged : NoChanges);
268
269    if (changed)
270        mutationScope.enqueueMutationRecord();
271}
272
273CSSValue* AbstractPropertySetCSSStyleDeclaration::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 = adoptPtrWillBeNoop(new WillBeHeapHashMap<RawPtrWillBeMember<CSSValue>, RefPtrWillBeMember<CSSValue> >);
282
283    RefPtrWillBeMember<CSSValue>& clonedValue = m_cssomCSSValueClones->add(internalValue, RefPtrWillBeMember<CSSValue>()).storedValue->value;
284    if (!clonedValue)
285        clonedValue = internalValue->cloneForCSSOM();
286    return clonedValue.get();
287}
288
289StyleSheetContents* AbstractPropertySetCSSStyleDeclaration::contextStyleSheet() const
290{
291    CSSStyleSheet* cssStyleSheet = parentStyleSheet();
292    return cssStyleSheet ? cssStyleSheet->contents() : 0;
293}
294
295PassRefPtrWillBeRawPtr<MutableStylePropertySet> AbstractPropertySetCSSStyleDeclaration::copyProperties() const
296{
297    return propertySet().mutableCopy();
298}
299
300bool AbstractPropertySetCSSStyleDeclaration::cssPropertyMatches(CSSPropertyID propertyID, const CSSValue* propertyValue) const
301{
302    return propertySet().propertyMatches(propertyID, propertyValue);
303}
304
305void AbstractPropertySetCSSStyleDeclaration::trace(Visitor* visitor)
306{
307#if ENABLE(OILPAN)
308    visitor->trace(m_cssomCSSValueClones);
309#endif
310    CSSStyleDeclaration::trace(visitor);
311}
312
313StyleRuleCSSStyleDeclaration::StyleRuleCSSStyleDeclaration(MutableStylePropertySet& propertySetArg, CSSRule* parentRule)
314    : PropertySetCSSStyleDeclaration(propertySetArg)
315#if !ENABLE(OILPAN)
316    , m_refCount(1)
317#endif
318    , m_parentRule(parentRule)
319{
320#if !ENABLE(OILPAN)
321    m_propertySet->ref();
322#endif
323}
324
325StyleRuleCSSStyleDeclaration::~StyleRuleCSSStyleDeclaration()
326{
327#if !ENABLE(OILPAN)
328    m_propertySet->deref();
329#endif
330}
331
332#if !ENABLE(OILPAN)
333void StyleRuleCSSStyleDeclaration::ref()
334{
335    ++m_refCount;
336}
337
338void StyleRuleCSSStyleDeclaration::deref()
339{
340    ASSERT(m_refCount);
341    if (!--m_refCount)
342        delete this;
343}
344#endif
345
346void StyleRuleCSSStyleDeclaration::willMutate()
347{
348    if (m_parentRule && m_parentRule->parentStyleSheet())
349        m_parentRule->parentStyleSheet()->willMutateRules();
350}
351
352void StyleRuleCSSStyleDeclaration::didMutate(MutationType type)
353{
354    if (type == PropertyChanged)
355        m_cssomCSSValueClones.clear();
356
357    // Style sheet mutation needs to be signaled even if the change failed. willMutateRules/didMutateRules must pair.
358    if (m_parentRule && m_parentRule->parentStyleSheet())
359        m_parentRule->parentStyleSheet()->didMutateRules();
360}
361
362CSSStyleSheet* StyleRuleCSSStyleDeclaration::parentStyleSheet() const
363{
364    return m_parentRule ? m_parentRule->parentStyleSheet() : 0;
365}
366
367void StyleRuleCSSStyleDeclaration::reattach(MutableStylePropertySet& propertySet)
368{
369#if !ENABLE(OILPAN)
370    m_propertySet->deref();
371#endif
372    m_propertySet = &propertySet;
373#if !ENABLE(OILPAN)
374    m_propertySet->ref();
375#endif
376}
377
378void StyleRuleCSSStyleDeclaration::trace(Visitor* visitor)
379{
380    visitor->trace(m_parentRule);
381    PropertySetCSSStyleDeclaration::trace(visitor);
382}
383
384MutableStylePropertySet& InlineCSSStyleDeclaration::propertySet() const
385{
386    return m_parentElement->ensureMutableInlineStyle();
387}
388
389void InlineCSSStyleDeclaration::didMutate(MutationType type)
390{
391    if (type == NoChanges)
392        return;
393
394    m_cssomCSSValueClones.clear();
395
396    if (!m_parentElement)
397        return;
398
399    m_parentElement->clearMutableInlineStyleIfEmpty();
400    m_parentElement->setNeedsStyleRecalc(LocalStyleChange);
401    m_parentElement->invalidateStyleAttribute();
402    StyleAttributeMutationScope(this).didInvalidateStyleAttr();
403}
404
405CSSStyleSheet* InlineCSSStyleDeclaration::parentStyleSheet() const
406{
407    return m_parentElement ? &m_parentElement->document().elementSheet() : 0;
408}
409
410#if !ENABLE(OILPAN)
411void InlineCSSStyleDeclaration::ref()
412{
413    m_parentElement->ref();
414}
415
416void InlineCSSStyleDeclaration::deref()
417{
418    m_parentElement->deref();
419}
420#endif
421
422void InlineCSSStyleDeclaration::trace(Visitor* visitor)
423{
424    visitor->trace(m_parentElement);
425    AbstractPropertySetCSSStyleDeclaration::trace(visitor);
426}
427
428} // namespace blink
429