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