PropertySetCSSStyleDeclaration.cpp   [plain text]


/*
 * (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"

namespace WebCore {

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 (localCopyStyleDecl->parentElement())
            InspectorInstrumentation::didInvalidateStyleAttr(localCopyStyleDecl->parentElement()->document(), *localCopyStyleDecl->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;
    AtomicString 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) ? ASCIILiteral("important") : emptyString();

    CSSPropertyID propertyID = cssPropertyID(propertyName);
    if (!propertyID)
        return String();
    return m_propertySet->propertyIsImportant(propertyID) ? ASCIILiteral("important") : 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 = equalIgnoringASCIICase(priority, "important");
    if (!important && !priority.isEmpty())
        return { };

    bool changed;
    if (propertyID == CSSPropertyCustom)
        changed = m_propertySet->setCustomProperty(propertyName, value, important, cssParserContext());
    else
        changed = m_propertySet->setProperty(propertyID, value, important, cssParserContext());

    didMutate(changed ? PropertyChanged : NoChanges);

    if (changed) {
        // CSS DOM requires raising SYNTAX_ERR 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 WTFMove(result);
}

RefPtr<CSSValue> PropertySetCSSStyleDeclaration::getPropertyCSSValueInternal(CSSPropertyID propertyID)
{
    return m_propertySet->getPropertyCSSValue(propertyID);
}

String PropertySetCSSStyleDeclaration::getPropertyValueInternal(CSSPropertyID propertyID)
{
    String value = m_propertySet->getPropertyValue(propertyID);
    if (!value.isEmpty())
        return value;

    return String();
}

ExceptionOr<bool> PropertySetCSSStyleDeclaration::setPropertyInternal(CSSPropertyID propertyID, const String& value, bool important)
{ 
    StyleAttributeMutationScope mutationScope(this);
    if (!willMutate())
        return false;

    bool changed = m_propertySet->setProperty(propertyID, value, important, cssParserContext());

    didMutate(changed ? PropertyChanged : NoChanges);

    if (changed)
        mutationScope.enqueueMutationRecord();
    return changed;
}

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.
    if (!m_cssomValueWrappers)
        m_cssomValueWrappers = std::make_unique<HashMap<CSSValue*, RefPtr<DeprecatedCSSOMValue>>>();
    
    RefPtr<DeprecatedCSSOMValue>& clonedValue = m_cssomValueWrappers->add(internalValue, RefPtr<DeprecatedCSSOMValue>()).iterator->value;
    if (!clonedValue)
        clonedValue = internalValue->createDeprecatedCSSOMWrapper();
    return clonedValue.get();
}

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_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();

    return styleSheet->parserContext();
}

void StyleRuleCSSStyleDeclaration::reattach(MutableStyleProperties& propertySet)
{
    m_propertySet->deref();
    m_propertySet = &propertySet;
    m_propertySet->ref();
}

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