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