/* * (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 "HTMLNames.h" #include "InspectorInstrumentation.h" #include "MutationObserverInterestGroup.h" #include "MutationRecord.h" #include "StyleProperties.h" #include "StyleSheetContents.h" #include "StyledElement.h" #include 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 m_mutationRecipients; AtomString m_oldValue; RefPtr 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 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 PropertySetCSSStyleDeclaration::getPropertyCSSValue(const String& propertyName) { if (isCustomPropertyName(propertyName)) { RefPtr 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 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 = equalIgnoringASCIICase(priority, "important"); 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 . mutationScope.enqueueMutationRecord(); } return { }; } ExceptionOr 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 PropertySetCSSStyleDeclaration::getPropertyCSSValueInternal(CSSPropertyID propertyID) { return m_propertySet->getPropertyCSSValue(propertyID); } String PropertySetCSSStyleDeclaration::getPropertyValueInternal(CSSPropertyID propertyID) { String value = m_propertySet->getPropertyValue(propertyID); CSSPropertyID relatedPropertyID = getRelatedPropertyId(propertyID); String relatedValue = m_propertySet->getPropertyValue(relatedPropertyID); if (!value.isEmpty()) return value; if (!relatedValue.isEmpty()) return relatedValue; return { }; } ExceptionOr 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 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. if (!m_cssomValueWrappers) m_cssomValueWrappers = makeUnique>>(); auto& clonedValue = m_cssomValueWrappers->add(internalValue, WeakPtr()).iterator->value; if (clonedValue) return clonedValue.get(); RefPtr wrapper = internalValue->createDeprecatedCSSOMWrapper(*this); clonedValue = makeWeakPtr(wrapper.get()); return wrapper; } StyleSheetContents* PropertySetCSSStyleDeclaration::contextStyleSheet() const { CSSStyleSheet* cssStyleSheet = parentStyleSheet(); return cssStyleSheet ? &cssStyleSheet->contents() : nullptr; } CSSParserContext PropertySetCSSStyleDeclaration::cssParserContext() const { return CSSParserContext(m_propertySet->cssParserMode()); } Ref PropertySetCSSStyleDeclaration::copyProperties() const { return m_propertySet->mutableCopy(); } StyleRuleCSSStyleDeclaration::StyleRuleCSSStyleDeclaration(MutableStyleProperties& propertySet, CSSRule& parentRule) : PropertySetCSSStyleDeclaration(propertySet) , m_refCount(1) , m_parentRuleType(static_cast(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 = nullptr; // 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 = nullptr; 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