blob: 39db4a6abd6307ecc808897371a9419e39a2fe94 [file] [log] [blame]
/*
* Copyright (C) 1999 Lars Knoll ([email protected])
* (C) 1999 Antti Koivisto ([email protected])
* (C) 2001 Peter Kelly ([email protected])
* (C) 2001 Dirk Mueller ([email protected])
* Copyright (C) 2004-2018 Apple Inc. 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 "StyledElement.h"
#include "AttributeChangeInvalidation.h"
#include "CSSComputedStyleDeclaration.h"
#include "CSSImageValue.h"
#include "CSSParser.h"
#include "CSSPrimitiveValue.h"
#include "CSSPropertyParser.h"
#include "CSSSerializationContext.h"
#include "CSSStyleProperties.h"
#include "CSSStyleSheet.h"
#include "CSSUnparsedValue.h"
#include "CSSValuePool.h"
#include "CachedResource.h"
#include "ContentSecurityPolicy.h"
#include "DOMTokenList.h"
#include "ElementInlines.h"
#include "ElementRareData.h"
#include "HTMLElement.h"
#include "HTMLParserIdioms.h"
#include "InlineStylePropertyMap.h"
#include "InspectorInstrumentation.h"
#include "MutableStyleProperties.h"
#include "SVGElement.h"
#include "ScriptableDocumentParser.h"
#include "StylePropertyMap.h"
#include "StylePropertyShorthand.h"
#include "StyleResolver.h"
#include <wtf/HashFunctions.h>
#include <wtf/TZoneMallocInlines.h>
namespace WebCore {
WTF_MAKE_TZONE_ALLOCATED_IMPL(StyledElement);
static_assert(sizeof(StyledElement) == sizeof(Element), "styledelement should remain same size as element");
using namespace HTMLNames;
void StyledElement::synchronizeStyleAttributeInternalImpl()
{
ASSERT(elementData());
ASSERT(elementData()->styleAttributeIsDirty());
elementData()->setStyleAttributeIsDirty(false);
if (RefPtr<const StyleProperties> inlineStyle = this->inlineStyle())
setSynchronizedLazyAttribute(styleAttr, inlineStyle->asTextAtom(CSS::defaultSerializationContext()));
}
StyledElement::~StyledElement() = default;
CSSStyleProperties& StyledElement::cssomStyle()
{
return ensureMutableInlineStyle()->ensureInlineCSSStyleProperties(*this);
}
StylePropertyMap& StyledElement::ensureAttributeStyleMap()
{
if (!attributeStyleMap())
setAttributeStyleMap(InlineStylePropertyMap::create(*this));
return *attributeStyleMap();
}
Ref<MutableStyleProperties> StyledElement::ensureMutableInlineStyle()
{
RefPtr<StyleProperties>& inlineStyle = ensureUniqueElementData().m_inlineStyle;
if (!inlineStyle) {
Ref mutableProperties = MutableStyleProperties::create(strictToCSSParserMode(isHTMLElement() && !document().inQuirksMode()));
inlineStyle = mutableProperties.copyRef();
return mutableProperties;
}
if (RefPtr mutableProperties = dynamicDowncast<MutableStyleProperties>(*inlineStyle))
return *mutableProperties;
Ref mutableProperties = inlineStyle->mutableCopy();
inlineStyle = mutableProperties.copyRef();
return mutableProperties;
}
void StyledElement::attributeChanged(const QualifiedName& name, const AtomString& oldValue, const AtomString& newValue, AttributeModificationReason reason)
{
Element::attributeChanged(name, oldValue, newValue, reason);
if (oldValue != newValue) {
if (name == styleAttr)
styleAttributeChanged(newValue, reason);
else if (hasPresentationalHintsForAttribute(name)) {
elementData()->setPresentationalHintStyleIsDirty(true);
invalidateStyleInternal();
}
}
}
CSSStyleProperties* StyledElement::inlineStyleCSSOMWrapper()
{
if (!inlineStyle() || !inlineStyle()->hasCSSOMWrapper())
return 0;
SUPPRESS_UNCOUNTED_LOCAL auto* cssomWrapper = ensureMutableInlineStyle()->cssStyleProperties();
ASSERT(cssomWrapper && cssomWrapper->parentElement() == this);
return cssomWrapper;
}
static bool usesStyleBasedEditability(const StyleProperties& properties)
{
return properties.getPropertyCSSValue(CSSPropertyWebkitUserModify);
}
void StyledElement::setInlineStyleFromString(const AtomString& newStyleString)
{
auto& inlineStyle = elementData()->m_inlineStyle;
// Avoid redundant work if we're using shared attribute data with already parsed inline style.
if (inlineStyle && !elementData()->isUnique())
return;
// We reconstruct the property set instead of mutating if there is no CSSOM wrapper.
// This makes wrapperless property sets immutable and so cacheable.
if (RefPtr mutableStyleProperties = dynamicDowncast<MutableStyleProperties>(inlineStyle))
mutableStyleProperties->parseDeclaration(newStyleString, protectedDocument().get());
else
inlineStyle = CSSParser::parseInlineStyleDeclaration(newStyleString, *this);
if (usesStyleBasedEditability(*inlineStyle))
protectedDocument()->setHasElementUsingStyleBasedEditability();
}
void StyledElement::styleAttributeChanged(const AtomString& newStyleString, AttributeModificationReason reason)
{
Ref document = this->document();
auto startLineNumber = OrdinalNumber::beforeFirst();
if (document->scriptableDocumentParser() && !document->isInDocumentWrite())
startLineNumber = document->scriptableDocumentParser()->textPosition().m_line;
if (newStyleString.isNull())
ensureMutableInlineStyle()->clear();
else if (reason == AttributeModificationReason::ByCloning || document->checkedContentSecurityPolicy()->allowInlineStyle(document->url().string(), startLineNumber, newStyleString.string(), CheckUnsafeHashes::Yes, *this, nonce(), isInUserAgentShadowTree()))
setInlineStyleFromString(newStyleString);
elementData()->setStyleAttributeIsDirty(false);
Node::invalidateStyle(Style::Validity::InlineStyleInvalid);
InspectorInstrumentation::didInvalidateStyleAttr(*this);
}
void StyledElement::dirtyStyleAttribute()
{
elementData()->setStyleAttributeIsDirty(true);
if (styleResolver().ruleSets().selectorsForStyleAttribute() != Style::SelectorsForStyleAttribute::None) {
if (RefPtr inlineStyle = this->inlineStyle()) {
elementData()->setStyleAttributeIsDirty(false);
auto newValue = inlineStyle->asTextAtom(CSS::defaultSerializationContext());
Style::AttributeChangeInvalidation styleInvalidation(*this, styleAttr, attributeWithoutSynchronization(styleAttr), newValue);
setSynchronizedLazyAttribute(styleAttr, newValue);
}
}
}
void StyledElement::invalidateStyleAttribute()
{
if (RefPtr inlineStyle = this->inlineStyle()) {
if (usesStyleBasedEditability(*inlineStyle))
protectedDocument()->setHasElementUsingStyleBasedEditability();
}
elementData()->setStyleAttributeIsDirty(true);
// Inline style invalidation optimization does not work if there are selectors targeting the style attribute
// as some rule may start or stop matching.
auto selectorsForStyleAttribute = styleResolver().ruleSets().selectorsForStyleAttribute();
auto validity = selectorsForStyleAttribute == Style::SelectorsForStyleAttribute::None ? Style::Validity::InlineStyleInvalid : Style::Validity::ElementInvalid;
Node::invalidateStyle(validity);
if (isSVGElement()) {
if (auto* svgElement = dynamicDowncast<SVGElement>(this))
svgElement->invalidateInstances();
}
// In the rare case of selectors like "[style] ~ div" we need to synchronize immediately to invalidate.
if (selectorsForStyleAttribute == Style::SelectorsForStyleAttribute::NonSubjectPosition) {
if (RefPtr inlineStyle = this->inlineStyle()) {
elementData()->setStyleAttributeIsDirty(false);
auto newValue = inlineStyle->asTextAtom(CSS::defaultSerializationContext());
Style::AttributeChangeInvalidation styleInvalidation(*this, styleAttr, attributeWithoutSynchronization(styleAttr), newValue);
setSynchronizedLazyAttribute(styleAttr, newValue);
}
}
}
RefPtr<StyleProperties> StyledElement::protectedInlineStyle() const
{
return elementData() ? elementData()->m_inlineStyle : nullptr;
}
void StyledElement::inlineStyleChanged()
{
setDidMutateSubtreeAfterSetInnerHTMLOnAncestors();
invalidateStyleAttribute();
InspectorInstrumentation::didInvalidateStyleAttr(*this);
}
bool StyledElement::setInlineStyleProperty(CSSPropertyID propertyID, CSSValueID identifier, IsImportant important)
{
ensureMutableInlineStyle()->setProperty(propertyID, CSSPrimitiveValue::create(identifier), important);
inlineStyleChanged();
return true;
}
bool StyledElement::setInlineStyleProperty(CSSPropertyID propertyID, CSSPropertyID identifier, IsImportant important)
{
ensureMutableInlineStyle()->setProperty(propertyID, CSSPrimitiveValue::create(identifier), important);
inlineStyleChanged();
return true;
}
bool StyledElement::setInlineStyleProperty(CSSPropertyID propertyID, double value, CSSUnitType unit, IsImportant important)
{
ensureMutableInlineStyle()->setProperty(propertyID, CSSPrimitiveValue::create(value, unit), important);
inlineStyleChanged();
return true;
}
bool StyledElement::setInlineStyleProperty(CSSPropertyID propertyID, Ref<CSSValue>&& value, IsImportant important)
{
ensureMutableInlineStyle()->setProperty(propertyID, WTF::move(value), important);
inlineStyleChanged();
return true;
}
bool StyledElement::setInlineStyleProperty(CSSPropertyID propertyID, const String& value, IsImportant important, bool* didFailParsing)
{
bool changes = ensureMutableInlineStyle()->setProperty(propertyID, value, CSSParserContext(document()), important, didFailParsing);
if (changes)
inlineStyleChanged();
return changes;
}
bool StyledElement::setInlineStyleCustomProperty(const AtomString& property, const String& value, IsImportant important)
{
bool changes = ensureMutableInlineStyle()->setCustomProperty(property.string(), value, CSSParserContext(document()), important);
if (changes)
inlineStyleChanged();
return changes;
}
bool StyledElement::setInlineStyleCustomProperty(Ref<CSSValue>&& customPropertyValue, IsImportant important)
{
ensureMutableInlineStyle()->addParsedProperty(CSSProperty(CSSPropertyCustom, WTF::move(customPropertyValue), important));
inlineStyleChanged();
return true;
}
bool StyledElement::removeInlineStyleProperty(CSSPropertyID propertyID)
{
if (!inlineStyle())
return false;
bool changes = ensureMutableInlineStyle()->removeProperty(propertyID);
if (changes)
inlineStyleChanged();
return changes;
}
bool StyledElement::removeInlineStyleCustomProperty(const AtomString& property)
{
if (!inlineStyle())
return false;
bool changes = ensureMutableInlineStyle()->removeCustomProperty(property);
if (changes)
inlineStyleChanged();
return changes;
}
void StyledElement::removeAllInlineStyleProperties()
{
if (!inlineStyle() || inlineStyle()->isEmpty())
return;
ensureMutableInlineStyle()->clear();
inlineStyleChanged();
}
void StyledElement::addSubresourceAttributeURLs(ListHashSet<URL>& urls) const
{
RefPtr inlineStyle = this->inlineStyle();
if (!inlineStyle)
return;
inlineStyle->traverseSubresources([&] (auto& resource) {
urls.add(resource.url());
return false;
});
}
Attribute StyledElement::replaceURLsInAttributeValue(const Attribute& attribute, const CSS::SerializationContext& serializationContext) const
{
if (serializationContext.replacementURLStrings.isEmpty())
return attribute;
if (attribute.name() != styleAttr)
return attribute;
RefPtr properties = this->inlineStyle();
if (!properties)
return attribute;
return Attribute { styleAttr, properties->asTextAtom(serializationContext) };
}
const ImmutableStyleProperties* StyledElement::presentationalHintStyle() const
{
if (!elementData())
return nullptr;
if (elementData()->presentationalHintStyleIsDirty())
const_cast<StyledElement&>(*this).rebuildPresentationalHintStyle();
return elementData()->presentationalHintStyle();
}
void StyledElement::rebuildPresentationalHintStyle()
{
bool isSVG = isSVGElement();
auto style = MutableStyleProperties::create(isSVG ? SVGAttributeMode : HTMLQuirksMode);
for (auto& attribute : attributes())
collectPresentationalHintsForAttribute(attribute.name(), attribute.value(), style);
collectExtraStyleForPresentationalHints(style);
// ShareableElementData doesn't store presentation attribute style, so make sure we have a UniqueElementData.
Ref elementData = ensureUniqueElementData();
elementData->setPresentationalHintStyleIsDirty(false);
if (style->isEmpty()) {
elementData->m_presentationalHintStyle = nullptr;
return;
}
auto shouldDeduplicate = [&] {
auto hasNonZeroProperty = [&](auto property) {
auto value = dynamicDowncast<CSSPrimitiveValue>(style->getPropertyCSSValue(property));
return value && !value->isZero().value_or(false);
};
if (isSVG) {
// SVG style tends to have unique x/y properties. Don't spend effort trying to deduplicate them.
if (hasNonZeroProperty(CSSPropertyX))
return false;
if (hasNonZeroProperty(CSSPropertyY))
return false;
if (hasNonZeroProperty(CSSPropertyCx))
return false;
if (hasNonZeroProperty(CSSPropertyCy))
return false;
}
return true;
}();
elementData->m_presentationalHintStyle = shouldDeduplicate ? style->immutableDeduplicatedCopy() : style->immutableCopy();
}
void StyledElement::addPropertyToPresentationalHintStyle(MutableStyleProperties& style, CSSPropertyID propertyID, CSSValueID identifier)
{
style.setProperty(propertyID, CSSPrimitiveValue::create(identifier));
}
void StyledElement::addPropertyToPresentationalHintStyle(MutableStyleProperties& style, CSSPropertyID propertyID, double value, CSSUnitType unit)
{
style.setProperty(propertyID, CSSPrimitiveValue::create(value, unit));
}
void StyledElement::addPropertyToPresentationalHintStyle(MutableStyleProperties& style, CSSPropertyID propertyID, const String& value)
{
style.setProperty(propertyID, value, protectedDocument()->cssParserContext());
}
void StyledElement::addPropertyToPresentationalHintStyle(MutableStyleProperties& style, CSSPropertyID propertyID, Ref<CSSValue>&& value)
{
style.setProperty(propertyID, WTF::move(value));
}
}