| /* |
| * (C) 1999-2003 Lars Knoll (knoll@kde.org) |
| * Copyright (C) 2004, 2005, 2006, 2007, 2008, 2009, 2010, 2012 Apple Inc. All rights reserved. |
| * Copyright (C) 2011 Research In Motion Limited. All rights reserved. |
| * |
| * This library is free software; you can redistribute it and/or |
| * modify it under the terms of the GNU Library General Public |
| * License as published by the Free Software Foundation; either |
| * version 2 of the License, or (at your option) any later version. |
| * |
| * This library is distributed in the hope that it will be useful, |
| * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
| * Library General Public License for more details. |
| * |
| * You should have received a copy of the GNU Library General Public License |
| * along with this library; see the file COPYING.LIB. If not, write to |
| * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, |
| * Boston, MA 02110-1301, USA. |
| */ |
| |
| #include "config.h" |
| #include "PropertySetCSSStyleDeclaration.h" |
| |
| #include "CSSPropertyParser.h" |
| #include "CSSRule.h" |
| #include "CSSStyleSheet.h" |
| #include "CustomElementReactionQueue.h" |
| #include "DOMWindow.h" |
| #include "HTMLNames.h" |
| #include "InspectorInstrumentation.h" |
| #include "JSDOMGlobalObject.h" |
| #include "JSDOMWindowBase.h" |
| #include "MutationObserverInterestGroup.h" |
| #include "MutationRecord.h" |
| #include "StyleProperties.h" |
| #include "StyleSheetContents.h" |
| #include "StyledElement.h" |
| #include <wtf/IsoMallocInlines.h> |
| |
| namespace WebCore { |
| |
| WTF_MAKE_ISO_ALLOCATED_IMPL(PropertySetCSSStyleDeclaration); |
| WTF_MAKE_ISO_ALLOCATED_IMPL(StyleRuleCSSStyleDeclaration); |
| WTF_MAKE_ISO_ALLOCATED_IMPL(InlineCSSStyleDeclaration); |
| |
| class StyleAttributeMutationScope { |
| WTF_MAKE_NONCOPYABLE(StyleAttributeMutationScope); |
| public: |
| StyleAttributeMutationScope(PropertySetCSSStyleDeclaration* decl) |
| { |
| ++s_scopeCount; |
| |
| if (s_scopeCount != 1) { |
| ASSERT(s_currentDecl == decl); |
| return; |
| } |
| |
| ASSERT(!s_currentDecl); |
| s_currentDecl = decl; |
| |
| auto* element = s_currentDecl->parentElement(); |
| if (!element) |
| return; |
| |
| bool shouldReadOldValue = false; |
| |
| m_mutationRecipients = MutationObserverInterestGroup::createForAttributesMutation(*s_currentDecl->parentElement(), HTMLNames::styleAttr); |
| if (m_mutationRecipients && m_mutationRecipients->isOldValueRequested()) |
| shouldReadOldValue = true; |
| |
| if (UNLIKELY(element->isDefinedCustomElement())) { |
| auto* reactionQueue = element->reactionQueue(); |
| if (reactionQueue && reactionQueue->observesStyleAttribute()) { |
| m_customElement = element; |
| shouldReadOldValue = true; |
| } |
| } |
| |
| if (shouldReadOldValue) |
| m_oldValue = s_currentDecl->parentElement()->getAttribute(HTMLNames::styleAttr); |
| } |
| |
| ~StyleAttributeMutationScope() |
| { |
| --s_scopeCount; |
| if (s_scopeCount) |
| return; |
| |
| if (s_shouldDeliver) { |
| if (m_mutationRecipients) { |
| auto mutation = MutationRecord::createAttributes(*s_currentDecl->parentElement(), HTMLNames::styleAttr, m_oldValue); |
| m_mutationRecipients->enqueueMutationRecord(WTFMove(mutation)); |
| } |
| if (m_customElement) { |
| auto& newValue = m_customElement->getAttribute(HTMLNames::styleAttr); |
| CustomElementReactionQueue::enqueueAttributeChangedCallbackIfNeeded(*m_customElement, HTMLNames::styleAttr, m_oldValue, newValue); |
| } |
| } |
| |
| s_shouldDeliver = false; |
| if (!s_shouldNotifyInspector) { |
| s_currentDecl = nullptr; |
| return; |
| } |
| // We have to clear internal state before calling Inspector's code. |
| PropertySetCSSStyleDeclaration* localCopyStyleDecl = s_currentDecl; |
| s_currentDecl = nullptr; |
| s_shouldNotifyInspector = false; |
| |
| if (auto* parentElement = localCopyStyleDecl->parentElement()) |
| InspectorInstrumentation::didInvalidateStyleAttr(*parentElement); |
| } |
| |
| void enqueueMutationRecord() |
| { |
| s_shouldDeliver = true; |
| } |
| |
| void didInvalidateStyleAttr() |
| { |
| s_shouldNotifyInspector = true; |
| } |
| |
| private: |
| static unsigned s_scopeCount; |
| static PropertySetCSSStyleDeclaration* s_currentDecl; |
| static bool s_shouldNotifyInspector; |
| static bool s_shouldDeliver; |
| |
| std::unique_ptr<MutationObserverInterestGroup> m_mutationRecipients; |
| AtomString m_oldValue; |
| RefPtr<Element> m_customElement; |
| }; |
| |
| unsigned StyleAttributeMutationScope::s_scopeCount = 0; |
| PropertySetCSSStyleDeclaration* StyleAttributeMutationScope::s_currentDecl = nullptr; |
| bool StyleAttributeMutationScope::s_shouldNotifyInspector = false; |
| bool StyleAttributeMutationScope::s_shouldDeliver = false; |
| |
| void PropertySetCSSStyleDeclaration::ref() |
| { |
| m_propertySet->ref(); |
| } |
| |
| void PropertySetCSSStyleDeclaration::deref() |
| { |
| m_propertySet->deref(); |
| } |
| |
| unsigned PropertySetCSSStyleDeclaration::length() const |
| { |
| return m_propertySet->propertyCount(); |
| } |
| |
| String PropertySetCSSStyleDeclaration::item(unsigned i) const |
| { |
| if (i >= m_propertySet->propertyCount()) |
| return String(); |
| return m_propertySet->propertyAt(i).cssName(); |
| } |
| |
| String PropertySetCSSStyleDeclaration::cssText() const |
| { |
| return m_propertySet->asText(); |
| } |
| |
| ExceptionOr<void> PropertySetCSSStyleDeclaration::setCssText(const String& text) |
| { |
| StyleAttributeMutationScope mutationScope(this); |
| if (!willMutate()) |
| return { }; |
| |
| bool changed = m_propertySet->parseDeclaration(text, cssParserContext()); |
| |
| didMutate(changed ? PropertyChanged : NoChanges); |
| |
| mutationScope.enqueueMutationRecord(); |
| return { }; |
| } |
| |
| RefPtr<DeprecatedCSSOMValue> PropertySetCSSStyleDeclaration::getPropertyCSSValue(const String& propertyName) |
| { |
| if (isCustomPropertyName(propertyName)) { |
| RefPtr<CSSValue> value = m_propertySet->getCustomPropertyCSSValue(propertyName); |
| if (!value) |
| return nullptr; |
| return wrapForDeprecatedCSSOM(value.get()); |
| } |
| |
| CSSPropertyID propertyID = cssPropertyID(propertyName); |
| if (!propertyID) |
| return nullptr; |
| return wrapForDeprecatedCSSOM(getPropertyCSSValueInternal(propertyID).get()); |
| } |
| |
| String PropertySetCSSStyleDeclaration::getPropertyValue(const String& propertyName) |
| { |
| if (isCustomPropertyName(propertyName)) |
| return m_propertySet->getCustomPropertyValue(propertyName); |
| |
| CSSPropertyID propertyID = cssPropertyID(propertyName); |
| if (!propertyID) |
| return String(); |
| return getPropertyValueInternal(propertyID); |
| } |
| |
| String PropertySetCSSStyleDeclaration::getPropertyPriority(const String& propertyName) |
| { |
| if (isCustomPropertyName(propertyName)) |
| return m_propertySet->customPropertyIsImportant(propertyName) ? "important"_s : emptyString(); |
| |
| CSSPropertyID propertyID = cssPropertyID(propertyName); |
| if (!propertyID) |
| return emptyString(); |
| return m_propertySet->propertyIsImportant(propertyID) ? "important"_s : emptyString(); |
| } |
| |
| String PropertySetCSSStyleDeclaration::getPropertyShorthand(const String& propertyName) |
| { |
| CSSPropertyID propertyID = cssPropertyID(propertyName); |
| if (!propertyID) |
| return String(); |
| return m_propertySet->getPropertyShorthand(propertyID); |
| } |
| |
| bool PropertySetCSSStyleDeclaration::isPropertyImplicit(const String& propertyName) |
| { |
| CSSPropertyID propertyID = cssPropertyID(propertyName); |
| if (!propertyID) |
| return false; |
| return m_propertySet->isPropertyImplicit(propertyID); |
| } |
| |
| ExceptionOr<void> PropertySetCSSStyleDeclaration::setProperty(const String& propertyName, const String& value, const String& priority) |
| { |
| StyleAttributeMutationScope mutationScope(this); |
| |
| CSSPropertyID propertyID = cssPropertyID(propertyName); |
| if (isCustomPropertyName(propertyName)) |
| propertyID = CSSPropertyCustom; |
| if (!propertyID) |
| return { }; |
| |
| if (!willMutate()) |
| return { }; |
| |
| bool important = equalLettersIgnoringASCIICase(priority, "important"_s); |
| if (!important && !priority.isEmpty()) |
| return { }; |
| |
| bool changed; |
| if (UNLIKELY(propertyID == CSSPropertyCustom)) { |
| Document* document = nullptr; |
| |
| if (parentElement()) |
| document = &parentElement()->document(); |
| else if (parentStyleSheet()) |
| document = parentStyleSheet()->ownerDocument(); |
| |
| changed = m_propertySet->setCustomProperty(document, propertyName, value, important, cssParserContext()); |
| } else |
| changed = m_propertySet->setProperty(propertyID, value, important, cssParserContext()); |
| |
| didMutate(changed ? PropertyChanged : NoChanges); |
| |
| if (changed) { |
| // CSS DOM requires raising SyntaxError of parsing failed, but this is too dangerous for compatibility, |
| // see <http://bugs.webkit.org/show_bug.cgi?id=7296>. |
| mutationScope.enqueueMutationRecord(); |
| } |
| |
| return { }; |
| } |
| |
| ExceptionOr<String> PropertySetCSSStyleDeclaration::removeProperty(const String& propertyName) |
| { |
| StyleAttributeMutationScope mutationScope(this); |
| CSSPropertyID propertyID = cssPropertyID(propertyName); |
| if (isCustomPropertyName(propertyName)) |
| propertyID = CSSPropertyCustom; |
| if (!propertyID) |
| return String(); |
| |
| if (!willMutate()) |
| return String(); |
| |
| String result; |
| bool changed = propertyID != CSSPropertyCustom ? m_propertySet->removeProperty(propertyID, &result) : m_propertySet->removeCustomProperty(propertyName, &result); |
| |
| didMutate(changed ? PropertyChanged : NoChanges); |
| |
| if (changed) |
| mutationScope.enqueueMutationRecord(); |
| return result; |
| } |
| |
| RefPtr<CSSValue> PropertySetCSSStyleDeclaration::getPropertyCSSValueInternal(CSSPropertyID propertyID) |
| { |
| return m_propertySet->getPropertyCSSValue(propertyID); |
| } |
| |
| String PropertySetCSSStyleDeclaration::getPropertyValueInternal(CSSPropertyID propertyID) |
| { |
| Document* doc = nullptr; |
| JSDOMObject* wrap = wrapper(); |
| if (wrap) { |
| JSDOMGlobalObject* global = wrap->globalObject(); |
| if (global) { |
| DOMWindow& window = activeDOMWindow(*global); |
| doc = window.document(); |
| } |
| } |
| String value = m_propertySet->getPropertyValue(propertyID, doc); |
| |
| if (!value.isEmpty()) |
| return value; |
| |
| return { }; |
| } |
| |
| ExceptionOr<void> PropertySetCSSStyleDeclaration::setPropertyInternal(CSSPropertyID propertyID, const String& value, bool important) |
| { |
| StyleAttributeMutationScope mutationScope { this }; |
| if (!willMutate()) |
| return { }; |
| |
| if (m_propertySet->setProperty(propertyID, value, important, cssParserContext())) { |
| didMutate(PropertyChanged); |
| mutationScope.enqueueMutationRecord(); |
| } else |
| didMutate(NoChanges); |
| |
| return { }; |
| } |
| |
| RefPtr<DeprecatedCSSOMValue> PropertySetCSSStyleDeclaration::wrapForDeprecatedCSSOM(CSSValue* internalValue) |
| { |
| if (!internalValue) |
| return nullptr; |
| |
| // The map is here to maintain the object identity of the CSSValues over multiple invocations. |
| // FIXME: It is likely that the identity is not important for web compatibility and this code should be removed. |
| auto& clonedValue = m_cssomValueWrappers.add(internalValue, WeakPtr<DeprecatedCSSOMValue>()).iterator->value; |
| if (clonedValue) |
| return clonedValue.get(); |
| |
| auto wrapper = internalValue->createDeprecatedCSSOMWrapper(*this); |
| clonedValue = wrapper; |
| return wrapper; |
| } |
| |
| StyleSheetContents* PropertySetCSSStyleDeclaration::contextStyleSheet() const |
| { |
| CSSStyleSheet* cssStyleSheet = parentStyleSheet(); |
| return cssStyleSheet ? &cssStyleSheet->contents() : nullptr; |
| } |
| |
| CSSParserContext PropertySetCSSStyleDeclaration::cssParserContext() const |
| { |
| return CSSParserContext(m_propertySet->cssParserMode()); |
| } |
| |
| Ref<MutableStyleProperties> PropertySetCSSStyleDeclaration::copyProperties() const |
| { |
| return m_propertySet->mutableCopy(); |
| } |
| |
| StyleRuleCSSStyleDeclaration::StyleRuleCSSStyleDeclaration(MutableStyleProperties& propertySet, CSSRule& parentRule) |
| : PropertySetCSSStyleDeclaration(propertySet) |
| , m_refCount(1) |
| , m_parentRuleType(static_cast<StyleRuleType>(parentRule.type())) |
| , m_parentRule(&parentRule) |
| { |
| m_propertySet->ref(); |
| } |
| |
| StyleRuleCSSStyleDeclaration::~StyleRuleCSSStyleDeclaration() |
| { |
| m_propertySet->deref(); |
| } |
| |
| void StyleRuleCSSStyleDeclaration::ref() |
| { |
| ++m_refCount; |
| } |
| |
| void StyleRuleCSSStyleDeclaration::deref() |
| { |
| ASSERT(m_refCount); |
| if (!--m_refCount) |
| delete this; |
| } |
| |
| bool StyleRuleCSSStyleDeclaration::willMutate() |
| { |
| if (!m_parentRule || !m_parentRule->parentStyleSheet()) |
| return false; |
| m_parentRule->parentStyleSheet()->willMutateRules(); |
| return true; |
| } |
| |
| void StyleRuleCSSStyleDeclaration::didMutate(MutationType type) |
| { |
| ASSERT(m_parentRule); |
| ASSERT(m_parentRule->parentStyleSheet()); |
| |
| if (type == PropertyChanged) |
| m_cssomValueWrappers.clear(); |
| |
| // Style sheet mutation needs to be signaled even if the change failed. willMutate*/didMutate* must pair. |
| m_parentRule->parentStyleSheet()->didMutateRuleFromCSSStyleDeclaration(); |
| } |
| |
| CSSStyleSheet* StyleRuleCSSStyleDeclaration::parentStyleSheet() const |
| { |
| return m_parentRule ? m_parentRule->parentStyleSheet() : nullptr; |
| } |
| |
| CSSParserContext StyleRuleCSSStyleDeclaration::cssParserContext() const |
| { |
| auto* styleSheet = contextStyleSheet(); |
| if (!styleSheet) |
| return PropertySetCSSStyleDeclaration::cssParserContext(); |
| |
| auto context = styleSheet->parserContext(); |
| context.enclosingRuleType = m_parentRuleType; |
| |
| return context; |
| } |
| |
| void StyleRuleCSSStyleDeclaration::reattach(MutableStyleProperties& propertySet) |
| { |
| m_propertySet->deref(); |
| m_propertySet = &propertySet; |
| m_propertySet->ref(); |
| } |
| |
| bool InlineCSSStyleDeclaration::willMutate() |
| { |
| if (m_parentElement) |
| InspectorInstrumentation::willInvalidateStyleAttr(*m_parentElement); |
| return true; |
| } |
| |
| void InlineCSSStyleDeclaration::didMutate(MutationType type) |
| { |
| if (type == NoChanges) |
| return; |
| |
| m_cssomValueWrappers.clear(); |
| |
| if (!m_parentElement) |
| return; |
| |
| m_parentElement->invalidateStyleAttribute(); |
| StyleAttributeMutationScope(this).didInvalidateStyleAttr(); |
| } |
| |
| CSSStyleSheet* InlineCSSStyleDeclaration::parentStyleSheet() const |
| { |
| return nullptr; |
| } |
| |
| CSSParserContext InlineCSSStyleDeclaration::cssParserContext() const |
| { |
| if (!m_parentElement) |
| return PropertySetCSSStyleDeclaration::cssParserContext(); |
| |
| CSSParserContext context(m_parentElement->document()); |
| context.mode = m_propertySet->cssParserMode(); |
| return context; |
| } |
| |
| } // namespace WebCore |