blob: 5341004566b9fe48d9347c16ad77cdaf467d5df5 [file] [log] [blame]
/*
* Copyright (C) 1999 Lars Knoll (knoll@kde.org)
* (C) 2004-2005 Allan Sandfeld Jensen (kde@carewolf.com)
* Copyright (C) 2006, 2007 Nicholas Shanks (webkit@nickshanks.com)
* Copyright (C) 2005, 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013 Apple Inc.
* All rights reserved.
* Copyright (C) 2007 Alexey Proskuryakov <ap@webkit.org>
* Copyright (C) 2007, 2008 Eric Seidel <eric@webkit.org>
* Copyright (C) 2008, 2009 Torch Mobile Inc. All rights reserved.
* (http://www.torchmobile.com/)
* Copyright (c) 2011, Code Aurora Forum. All rights reserved.
* Copyright (C) Research In Motion Limited 2011. All rights reserved.
* Copyright (C) 2012 Google 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 "core/css/resolver/StyleResolver.h"
#include "core/CSSPropertyNames.h"
#include "core/HTMLNames.h"
#include "core/MediaTypeNames.h"
#include "core/StylePropertyShorthand.h"
#include "core/animation/AnimationTimeline.h"
#include "core/animation/ElementAnimations.h"
#include "core/animation/InterpolationEnvironment.h"
#include "core/animation/InvalidatableInterpolation.h"
#include "core/animation/KeyframeEffect.h"
#include "core/animation/StyleInterpolation.h"
#include "core/animation/animatable/AnimatableValue.h"
#include "core/animation/css/CSSAnimatableValueFactory.h"
#include "core/animation/css/CSSAnimations.h"
#include "core/css/CSSCalculationValue.h"
#include "core/css/CSSCustomIdentValue.h"
#include "core/css/CSSDefaultStyleSheets.h"
#include "core/css/CSSFontSelector.h"
#include "core/css/CSSIdentifierValue.h"
#include "core/css/CSSKeyframeRule.h"
#include "core/css/CSSKeyframesRule.h"
#include "core/css/CSSReflectValue.h"
#include "core/css/CSSRuleList.h"
#include "core/css/CSSSelector.h"
#include "core/css/CSSStyleRule.h"
#include "core/css/CSSValueList.h"
#include "core/css/ElementRuleCollector.h"
#include "core/css/FontFace.h"
#include "core/css/MediaQueryEvaluator.h"
#include "core/css/PageRuleCollector.h"
#include "core/css/StylePropertySet.h"
#include "core/css/StyleRuleImport.h"
#include "core/css/StyleSheetContents.h"
#include "core/css/resolver/AnimatedStyleBuilder.h"
#include "core/css/resolver/CSSVariableResolver.h"
#include "core/css/resolver/MatchResult.h"
#include "core/css/resolver/MediaQueryResult.h"
#include "core/css/resolver/ScopedStyleResolver.h"
#include "core/css/resolver/SelectorFilterParentScope.h"
#include "core/css/resolver/SharedStyleFinder.h"
#include "core/css/resolver/StyleAdjuster.h"
#include "core/css/resolver/StyleResolverState.h"
#include "core/css/resolver/StyleResolverStats.h"
#include "core/css/resolver/ViewportStyleResolver.h"
#include "core/dom/CSSSelectorWatch.h"
#include "core/dom/FirstLetterPseudoElement.h"
#include "core/dom/NodeComputedStyle.h"
#include "core/dom/StyleEngine.h"
#include "core/dom/Text.h"
#include "core/dom/shadow/ElementShadow.h"
#include "core/dom/shadow/ShadowRoot.h"
#include "core/frame/FrameView.h"
#include "core/frame/LocalFrame.h"
#include "core/frame/Settings.h"
#include "core/frame/UseCounter.h"
#include "core/html/HTMLIFrameElement.h"
#include "core/html/HTMLSlotElement.h"
#include "core/inspector/InspectorInstrumentation.h"
#include "core/layout/GeneratedChildren.h"
#include "core/layout/api/LayoutViewItem.h"
#include "core/style/StyleInheritedVariables.h"
#include "core/svg/SVGDocumentExtensions.h"
#include "core/svg/SVGElement.h"
#include "platform/RuntimeEnabledFeatures.h"
#include "wtf/StdLibExtras.h"
namespace {
using namespace blink;
void setAnimationUpdateIfNeeded(StyleResolverState& state, Element& element) {
// If any changes to CSS Animations were detected, stash the update away for
// application after the layout object is updated if we're in the appropriate
// scope.
if (!state.animationUpdate().isEmpty())
element.ensureElementAnimations().cssAnimations().setPendingUpdate(
state.animationUpdate());
}
// Returns whether any @apply rule sets a custom property
bool cacheCustomPropertiesForApplyAtRules(StyleResolverState& state,
const MatchedPropertiesRange& range) {
bool ruleSetsCustomProperty = false;
// TODO(timloh): @apply should also work with properties registered as
// non-inherited.
if (!state.style()->inheritedVariables())
return false;
for (const auto& matchedProperties : range) {
const StylePropertySet& properties = *matchedProperties.properties;
unsigned propertyCount = properties.propertyCount();
for (unsigned i = 0; i < propertyCount; ++i) {
StylePropertySet::PropertyReference current = properties.propertyAt(i);
if (current.id() != CSSPropertyApplyAtRule)
continue;
AtomicString name(toCSSCustomIdentValue(current.value()).value());
CSSVariableData* variableData =
state.style()->inheritedVariables()->getVariable(name);
if (!variableData)
continue;
StylePropertySet* customPropertySet = variableData->propertySet();
if (!customPropertySet)
continue;
if (customPropertySet->findPropertyIndex(CSSPropertyVariable) != -1)
ruleSetsCustomProperty = true;
state.setCustomPropertySetForApplyAtRule(name, customPropertySet);
}
}
return ruleSetsCustomProperty;
}
} // namespace
namespace blink {
using namespace HTMLNames;
ComputedStyle* StyleResolver::s_styleNotYetAvailable;
static StylePropertySet* leftToRightDeclaration() {
DEFINE_STATIC_LOCAL(MutableStylePropertySet, leftToRightDecl,
(MutableStylePropertySet::create(HTMLQuirksMode)));
if (leftToRightDecl.isEmpty())
leftToRightDecl.setProperty(CSSPropertyDirection, CSSValueLtr);
return &leftToRightDecl;
}
static StylePropertySet* rightToLeftDeclaration() {
DEFINE_STATIC_LOCAL(MutableStylePropertySet, rightToLeftDecl,
(MutableStylePropertySet::create(HTMLQuirksMode)));
if (rightToLeftDecl.isEmpty())
rightToLeftDecl.setProperty(CSSPropertyDirection, CSSValueRtl);
return &rightToLeftDecl;
}
static void collectScopedResolversForHostedShadowTrees(
const Element& element,
HeapVector<Member<ScopedStyleResolver>, 8>& resolvers) {
ElementShadow* shadow = element.shadow();
if (!shadow)
return;
// Adding scoped resolver for active shadow roots for shadow host styling.
for (ShadowRoot* shadowRoot = &shadow->youngestShadowRoot(); shadowRoot;
shadowRoot = shadowRoot->olderShadowRoot()) {
if (shadowRoot->numberOfStyles() > 0) {
if (ScopedStyleResolver* resolver = shadowRoot->scopedStyleResolver())
resolvers.append(resolver);
}
}
}
StyleResolver::StyleResolver(Document& document)
: m_document(document),
m_viewportStyleResolver(ViewportStyleResolver::create(document)),
m_needCollectFeatures(false),
m_printMediaType(false),
m_styleSharingDepth(0) {
FrameView* view = document.view();
if (view) {
m_medium = new MediaQueryEvaluator(&view->frame());
m_printMediaType =
equalIgnoringCase(view->mediaType(), MediaTypeNames::print);
} else {
m_medium = new MediaQueryEvaluator("all");
}
initWatchedSelectorRules();
}
StyleResolver::~StyleResolver() {}
void StyleResolver::dispose() {
m_matchedPropertiesCache.clear();
}
void StyleResolver::initWatchedSelectorRules() {
m_watchedSelectorsRules = nullptr;
CSSSelectorWatch* watch = CSSSelectorWatch::fromIfExists(*m_document);
if (!watch)
return;
const HeapVector<Member<StyleRule>>& watchedSelectors =
watch->watchedCallbackSelectors();
if (!watchedSelectors.size())
return;
m_watchedSelectorsRules = RuleSet::create();
for (unsigned i = 0; i < watchedSelectors.size(); ++i)
m_watchedSelectorsRules->addStyleRule(watchedSelectors[i].get(),
RuleHasNoSpecialState);
}
void StyleResolver::lazyAppendAuthorStyleSheets(
unsigned firstNew,
const HeapVector<Member<CSSStyleSheet>>& styleSheets) {
unsigned size = styleSheets.size();
for (unsigned i = firstNew; i < size; ++i)
m_pendingStyleSheets.add(styleSheets[i].get());
}
void StyleResolver::removePendingAuthorStyleSheets(
const HeapVector<Member<CSSStyleSheet>>& styleSheets) {
for (unsigned i = 0; i < styleSheets.size(); ++i)
m_pendingStyleSheets.remove(styleSheets[i].get());
}
void StyleResolver::appendCSSStyleSheet(CSSStyleSheet& cssSheet) {
DCHECK(!cssSheet.disabled());
DCHECK(cssSheet.ownerDocument());
DCHECK(cssSheet.ownerNode());
DCHECK(isHTMLStyleElement(cssSheet.ownerNode()) ||
isSVGStyleElement(cssSheet.ownerNode()) ||
cssSheet.ownerNode()->isConnected());
if (cssSheet.mediaQueries() &&
!m_medium->eval(cssSheet.mediaQueries(),
&m_viewportDependentMediaQueryResults,
&m_deviceDependentMediaQueryResults))
return;
TreeScope* treeScope = &cssSheet.ownerNode()->treeScope();
// TODO(rune@opera.com): This is a workaround for crbug.com/559292
// when we're in the middle of removing a subtree with a style element
// and the treescope has been changed but inDocument and isInShadowTree
// are not.
//
// This check can be removed when crbug.com/567021 is fixed.
if (cssSheet.ownerNode()->isInShadowTree() &&
treeScope->rootNode().isDocumentNode())
return;
// Sheets in the document scope of HTML imports apply to the main document
// (m_document), so we override it for all document scoped sheets.
if (treeScope->rootNode().isDocumentNode())
treeScope = m_document;
treeScope->ensureScopedStyleResolver().appendCSSStyleSheet(cssSheet,
*m_medium);
}
void StyleResolver::appendPendingAuthorStyleSheets() {
for (const auto& styleSheet : m_pendingStyleSheets)
appendCSSStyleSheet(*styleSheet);
m_pendingStyleSheets.clear();
finishAppendAuthorStyleSheets();
}
void StyleResolver::appendAuthorStyleSheets(
const HeapVector<Member<CSSStyleSheet>>& styleSheets) {
// This handles sheets added to the end of the stylesheet list only. In other
// cases the style resolver needs to be reconstructed. To handle insertions
// too the rule order numbers would need to be updated.
for (const auto& styleSheet : styleSheets)
appendCSSStyleSheet(*styleSheet);
}
void StyleResolver::finishAppendAuthorStyleSheets() {
collectFeatures();
if (!document().layoutViewItem().isNull() &&
document().layoutViewItem().style())
document().layoutViewItem().style()->font().update(
document().styleEngine().fontSelector());
m_viewportStyleResolver->collectViewportRules();
document().styleEngine().resetCSSFeatureFlags(m_features);
}
void StyleResolver::resetRuleFeatures() {
// Need to recreate RuleFeatureSet.
m_features.clear();
m_siblingRuleSet.clear();
m_uncommonAttributeRuleSet.clear();
m_needCollectFeatures = true;
}
void StyleResolver::addTreeBoundaryCrossingScope(ContainerNode& scope) {
m_treeBoundaryCrossingScopes.add(&scope);
}
void StyleResolver::resetAuthorStyle(TreeScope& treeScope) {
m_treeBoundaryCrossingScopes.remove(&treeScope.rootNode());
ScopedStyleResolver* resolver = treeScope.scopedStyleResolver();
if (!resolver)
return;
resetRuleFeatures();
if (treeScope.rootNode().isDocumentNode()) {
resolver->resetAuthorStyle();
return;
}
// resolver is going to be freed below.
treeScope.clearScopedStyleResolver();
}
static RuleSet* makeRuleSet(const HeapVector<RuleFeature>& rules) {
size_t size = rules.size();
if (!size)
return nullptr;
RuleSet* ruleSet = RuleSet::create();
for (size_t i = 0; i < size; ++i)
ruleSet->addRule(rules[i].rule, rules[i].selectorIndex,
rules[i].hasDocumentSecurityOrigin
? RuleHasDocumentSecurityOrigin
: RuleHasNoSpecialState);
return ruleSet;
}
void StyleResolver::collectFeatures() {
m_features.clear();
// Collect all ids and rules using sibling selectors (:first-child and
// similar) in the current set of stylesheets. Style sharing code uses this
// information to reject sharing candidates.
CSSDefaultStyleSheets& defaultStyleSheets = CSSDefaultStyleSheets::instance();
if (defaultStyleSheets.defaultStyle()) {
m_features.add(defaultStyleSheets.defaultStyle()->features());
m_hasFullscreenUAStyle = defaultStyleSheets.fullscreenStyleSheet();
}
if (document().isViewSource())
m_features.add(defaultStyleSheets.defaultViewSourceStyle()->features());
if (m_watchedSelectorsRules)
m_features.add(m_watchedSelectorsRules->features());
document().styleEngine().collectScopedStyleFeaturesTo(m_features);
m_siblingRuleSet = makeRuleSet(m_features.siblingRules);
m_uncommonAttributeRuleSet = makeRuleSet(m_features.uncommonAttributeRules);
m_needCollectFeatures = false;
}
bool StyleResolver::hasRulesForId(const AtomicString& id) const {
return m_features.hasSelectorForId(id);
}
void StyleResolver::addToStyleSharingList(Element& element) {
ASSERT(RuntimeEnabledFeatures::styleSharingEnabled());
// Never add elements to the style sharing list if we're not in a recalcStyle,
// otherwise we could leave stale pointers in there.
if (!document().inStyleRecalc())
return;
INCREMENT_STYLE_STATS_COUNTER(document().styleEngine(), sharedStyleCandidates,
1);
StyleSharingList& list = styleSharingList();
if (list.size() >= styleSharingListSize)
list.removeLast();
list.prepend(&element);
}
StyleSharingList& StyleResolver::styleSharingList() {
m_styleSharingLists.resize(styleSharingMaxDepth);
// We never put things at depth 0 into the list since that's only the <html>
// element and it has no siblings or cousins to share with.
unsigned depth =
std::max(std::min(m_styleSharingDepth, styleSharingMaxDepth), 1u) - 1u;
if (!m_styleSharingLists[depth])
m_styleSharingLists[depth] = new StyleSharingList;
return *m_styleSharingLists[depth];
}
void StyleResolver::clearStyleSharingList() {
m_styleSharingLists.resize(0);
}
static inline ScopedStyleResolver* scopedResolverFor(const Element& element) {
// Ideally, returning element->treeScope().scopedStyleResolver() should be
// enough, but ::cue and custom pseudo elements like ::-webkit-meter-bar
// pierce through a shadow dom boundary, yet they are not part of
// m_treeBoundaryCrossingScopes. The assumption here is that these rules only
// pierce through one boundary and that the scope of these elements do not
// have a style resolver due to the fact that VTT scopes and UA shadow trees
// don't have <style> elements. This is backed up by the ASSERTs below.
//
// FIXME: Make ::cue and custom pseudo elements part of boundary crossing
// rules when moving those rules to ScopedStyleResolver as part of issue
// 401359.
TreeScope* treeScope = &element.treeScope();
if (ScopedStyleResolver* resolver = treeScope->scopedStyleResolver()) {
ASSERT(element.shadowPseudoId().isEmpty());
ASSERT(!element.isVTTElement());
return resolver;
}
treeScope = treeScope->parentTreeScope();
if (!treeScope)
return nullptr;
if (element.shadowPseudoId().isEmpty() && !element.isVTTElement())
return nullptr;
return treeScope->scopedStyleResolver();
}
static void matchHostRules(const Element& element,
ElementRuleCollector& collector) {
ElementShadow* shadow = element.shadow();
if (!shadow)
return;
for (ShadowRoot* shadowRoot = &shadow->oldestShadowRoot(); shadowRoot;
shadowRoot = shadowRoot->youngerShadowRoot()) {
if (!shadowRoot->numberOfStyles())
continue;
if (ScopedStyleResolver* resolver = shadowRoot->scopedStyleResolver()) {
collector.clearMatchedRules();
resolver->collectMatchingShadowHostRules(collector);
collector.sortAndTransferMatchedRules();
collector.finishAddingAuthorRulesForTreeScope();
}
}
}
static void matchSlottedRules(const Element& element,
ElementRuleCollector& collector) {
HTMLSlotElement* slot = element.assignedSlot();
if (!slot)
return;
HeapVector<Member<ScopedStyleResolver>> resolvers;
for (; slot; slot = slot->assignedSlot()) {
if (ScopedStyleResolver* resolver = slot->treeScope().scopedStyleResolver())
resolvers.append(resolver);
}
for (auto it = resolvers.rbegin(); it != resolvers.rend(); ++it) {
collector.clearMatchedRules();
(*it)->collectMatchingTreeBoundaryCrossingRules(collector);
collector.sortAndTransferMatchedRules();
collector.finishAddingAuthorRulesForTreeScope();
}
}
static void matchElementScopeRules(const Element& element,
ScopedStyleResolver* elementScopeResolver,
ElementRuleCollector& collector) {
if (elementScopeResolver) {
collector.clearMatchedRules();
elementScopeResolver->collectMatchingAuthorRules(collector);
elementScopeResolver->collectMatchingTreeBoundaryCrossingRules(collector);
collector.sortAndTransferMatchedRules();
}
if (element.isStyledElement() && element.inlineStyle() &&
!collector.isCollectingForPseudoElement()) {
// Inline style is immutable as long as there is no CSSOM wrapper.
bool isInlineStyleCacheable = !element.inlineStyle()->isMutable();
collector.addElementStyleProperties(element.inlineStyle(),
isInlineStyleCacheable);
}
collector.finishAddingAuthorRulesForTreeScope();
}
static bool shouldCheckScope(const Element& element,
const Node& scopingNode,
bool isInnerTreeScope) {
if (isInnerTreeScope && element.treeScope() != scopingNode.treeScope()) {
// Check if |element| may be affected by a ::content rule in |scopingNode|'s
// style. If |element| is a descendant of a shadow host which is ancestral
// to |scopingNode|, the |element| should be included for rule collection.
// Skip otherwise.
const TreeScope* scope = &scopingNode.treeScope();
while (scope && scope->parentTreeScope() != &element.treeScope())
scope = scope->parentTreeScope();
Element* shadowHost = scope ? scope->rootNode().ownerShadowHost() : nullptr;
return shadowHost && element.isDescendantOf(shadowHost);
}
// When |element| can be distributed to |scopingNode| via <shadow>, ::content
// rule can match, thus the case should be included.
if (!isInnerTreeScope &&
scopingNode.parentOrShadowHostNode() ==
element.treeScope().rootNode().parentOrShadowHostNode())
return true;
// Obviously cases when ancestor scope has /deep/ or ::shadow rule should be
// included. Skip otherwise.
return scopingNode.treeScope()
.scopedStyleResolver()
->hasDeepOrShadowSelector();
}
void StyleResolver::matchScopedRules(const Element& element,
ElementRuleCollector& collector) {
// Match rules from treeScopes in the reverse tree-of-trees order, since the
// cascading order for normal rules is such that when comparing rules from
// different shadow trees, the rule from the tree which comes first in the
// tree-of-trees order wins. From other treeScopes than the element's own
// scope, only tree-boundary-crossing rules may match.
ScopedStyleResolver* elementScopeResolver = scopedResolverFor(element);
if (!document().mayContainV0Shadow()) {
matchSlottedRules(element, collector);
matchElementScopeRules(element, elementScopeResolver, collector);
return;
}
bool matchElementScopeDone = !elementScopeResolver && !element.inlineStyle();
for (auto it = m_treeBoundaryCrossingScopes.rbegin();
it != m_treeBoundaryCrossingScopes.rend(); ++it) {
const TreeScope& scope = (*it)->containingTreeScope();
ScopedStyleResolver* resolver = scope.scopedStyleResolver();
ASSERT(resolver);
bool isInnerTreeScope =
element.containingTreeScope().isInclusiveAncestorOf(scope);
if (!shouldCheckScope(element, **it, isInnerTreeScope))
continue;
if (!matchElementScopeDone &&
scope.isInclusiveAncestorOf(element.containingTreeScope())) {
matchElementScopeDone = true;
// At this point, the iterator has either encountered the scope for the
// element itself (if that scope has boundary-crossing rules), or the
// iterator has moved to a scope which appears before the element's scope
// in the tree-of-trees order. Try to match all rules from the element's
// scope.
matchElementScopeRules(element, elementScopeResolver, collector);
if (resolver == elementScopeResolver) {
// Boundary-crossing rules already collected in matchElementScopeRules.
continue;
}
}
collector.clearMatchedRules();
resolver->collectMatchingTreeBoundaryCrossingRules(collector);
collector.sortAndTransferMatchedRules();
collector.finishAddingAuthorRulesForTreeScope();
}
if (!matchElementScopeDone)
matchElementScopeRules(element, elementScopeResolver, collector);
}
void StyleResolver::matchAuthorRules(const Element& element,
ElementRuleCollector& collector) {
if (document().shadowCascadeOrder() != ShadowCascadeOrder::ShadowCascadeV1) {
matchAuthorRulesV0(element, collector);
return;
}
ASSERT(RuntimeEnabledFeatures::shadowDOMV1Enabled());
matchHostRules(element, collector);
matchScopedRules(element, collector);
}
void StyleResolver::matchAuthorRulesV0(const Element& element,
ElementRuleCollector& collector) {
collector.clearMatchedRules();
CascadeOrder cascadeOrder = 0;
HeapVector<Member<ScopedStyleResolver>, 8> resolversInShadowTree;
collectScopedResolversForHostedShadowTrees(element, resolversInShadowTree);
// Apply :host and :host-context rules from inner scopes.
for (int j = resolversInShadowTree.size() - 1; j >= 0; --j)
resolversInShadowTree.at(j)->collectMatchingShadowHostRules(collector,
++cascadeOrder);
// Apply normal rules from element scope.
if (ScopedStyleResolver* resolver = scopedResolverFor(element))
resolver->collectMatchingAuthorRules(collector, ++cascadeOrder);
// Apply /deep/ and ::shadow rules from outer scopes, and ::content from
// inner.
collectTreeBoundaryCrossingRules(element, collector);
collector.sortAndTransferMatchedRules();
}
void StyleResolver::matchUARules(ElementRuleCollector& collector) {
collector.setMatchingUARules(true);
CSSDefaultStyleSheets& defaultStyleSheets = CSSDefaultStyleSheets::instance();
RuleSet* userAgentStyleSheet = m_printMediaType
? defaultStyleSheets.defaultPrintStyle()
: defaultStyleSheets.defaultStyle();
matchRuleSet(collector, userAgentStyleSheet);
// In quirks mode, we match rules from the quirks user agent sheet.
if (document().inQuirksMode())
matchRuleSet(collector, defaultStyleSheets.defaultQuirksStyle());
// If document uses view source styles (in view source mode or in xml viewer
// mode), then we match rules from the view source style sheet.
if (document().isViewSource())
matchRuleSet(collector, defaultStyleSheets.defaultViewSourceStyle());
collector.finishAddingUARules();
collector.setMatchingUARules(false);
}
void StyleResolver::matchRuleSet(ElementRuleCollector& collector,
RuleSet* rules) {
collector.clearMatchedRules();
collector.collectMatchingRules(MatchRequest(rules));
collector.sortAndTransferMatchedRules();
}
void StyleResolver::matchAllRules(StyleResolverState& state,
ElementRuleCollector& collector,
bool includeSMILProperties) {
matchUARules(collector);
// Now check author rules, beginning first with presentational attributes
// mapped from HTML.
if (state.element()->isStyledElement()) {
collector.addElementStyleProperties(
state.element()->presentationAttributeStyle());
// Now we check additional mapped declarations.
// Tables and table cells share an additional mapped rule that must be
// applied after all attributes, since their mapped style depends on the
// values of multiple attributes.
collector.addElementStyleProperties(
state.element()->additionalPresentationAttributeStyle());
if (state.element()->isHTMLElement()) {
bool isAuto;
TextDirection textDirection =
toHTMLElement(state.element())
->directionalityIfhasDirAutoAttribute(isAuto);
if (isAuto) {
state.setHasDirAutoAttribute(true);
collector.addElementStyleProperties(textDirection == LTR
? leftToRightDeclaration()
: rightToLeftDeclaration());
}
}
}
matchAuthorRules(*state.element(), collector);
if (state.element()->isStyledElement()) {
// For Shadow DOM V1, inline style is already collected in
// matchScopedRules().
if (document().shadowCascadeOrder() !=
ShadowCascadeOrder::ShadowCascadeV1 &&
state.element()->inlineStyle()) {
// Inline style is immutable as long as there is no CSSOM wrapper.
bool isInlineStyleCacheable =
!state.element()->inlineStyle()->isMutable();
collector.addElementStyleProperties(state.element()->inlineStyle(),
isInlineStyleCacheable);
}
// Now check SMIL animation override style.
if (includeSMILProperties && state.element()->isSVGElement())
collector.addElementStyleProperties(
toSVGElement(state.element())->animatedSMILStyleProperties(),
false /* isCacheable */);
}
collector.finishAddingAuthorRulesForTreeScope();
}
void StyleResolver::collectTreeBoundaryCrossingRules(
const Element& element,
ElementRuleCollector& collector) {
if (m_treeBoundaryCrossingScopes.isEmpty())
return;
// When comparing rules declared in outer treescopes, outer's rules win.
CascadeOrder outerCascadeOrder = m_treeBoundaryCrossingScopes.size() * 2;
// When comparing rules declared in inner treescopes, inner's rules win.
CascadeOrder innerCascadeOrder = m_treeBoundaryCrossingScopes.size();
for (const auto& scopingNode : m_treeBoundaryCrossingScopes) {
// Skip rule collection for element when tree boundary crossing rules of
// scopingNode's scope can never apply to it.
bool isInnerTreeScope = element.containingTreeScope().isInclusiveAncestorOf(
scopingNode->containingTreeScope());
if (!shouldCheckScope(element, *scopingNode, isInnerTreeScope))
continue;
CascadeOrder cascadeOrder =
isInnerTreeScope ? innerCascadeOrder : outerCascadeOrder;
scopingNode->treeScope()
.scopedStyleResolver()
->collectMatchingTreeBoundaryCrossingRules(collector, cascadeOrder);
++innerCascadeOrder;
--outerCascadeOrder;
}
}
PassRefPtr<ComputedStyle> StyleResolver::styleForDocument(Document& document) {
const LocalFrame* frame = document.frame();
RefPtr<ComputedStyle> documentStyle = ComputedStyle::create();
documentStyle->setRTLOrdering(document.visuallyOrdered() ? VisualOrder
: LogicalOrder);
documentStyle->setZoom(frame && !document.printing() ? frame->pageZoomFactor()
: 1);
FontDescription documentFontDescription = documentStyle->getFontDescription();
documentFontDescription.setLocale(
LayoutLocale::get(document.contentLanguage()));
documentStyle->setFontDescription(documentFontDescription);
documentStyle->setZIndex(0);
documentStyle->setIsStackingContext(true);
documentStyle->setUserModify(document.inDesignMode() ? READ_WRITE
: READ_ONLY);
// These are designed to match the user-agent stylesheet values for the
// document element so that the common case doesn't need to create a new
// ComputedStyle in Document::inheritHtmlAndBodyElementStyles.
documentStyle->setDisplay(EDisplay::Block);
documentStyle->setPosition(AbsolutePosition);
document.setupFontBuilder(*documentStyle);
return documentStyle.release();
}
void StyleResolver::adjustComputedStyle(StyleResolverState& state,
Element* element) {
StyleAdjuster::adjustComputedStyle(state.mutableStyleRef(),
*state.parentStyle(), element);
}
// Start loading resources referenced by this style.
void StyleResolver::loadPendingResources(StyleResolverState& state) {
state.elementStyleResources().loadPendingResources(state.style());
}
PassRefPtr<ComputedStyle> StyleResolver::styleForElement(
Element* element,
const ComputedStyle* defaultParent,
StyleSharingBehavior sharingBehavior,
RuleMatchingBehavior matchingBehavior) {
DCHECK(document().frame());
ASSERT(document().settings());
ASSERT(!hasPendingAuthorStyleSheets());
ASSERT(!m_needCollectFeatures);
// Once an element has a layoutObject, we don't try to destroy it, since
// otherwise the layoutObject will vanish if a style recalc happens during
// loading.
if (sharingBehavior == AllowStyleSharing && !document().isRenderingReady() &&
!element->layoutObject()) {
if (!s_styleNotYetAvailable) {
s_styleNotYetAvailable = ComputedStyle::create().leakRef();
s_styleNotYetAvailable->setDisplay(EDisplay::None);
s_styleNotYetAvailable->font().update(
document().styleEngine().fontSelector());
}
document().setHasNodesWithPlaceholderStyle();
return s_styleNotYetAvailable;
}
document().styleEngine().incStyleForElementCount();
INCREMENT_STYLE_STATS_COUNTER(document().styleEngine(), elementsStyled, 1);
SelectorFilterParentScope::ensureParentStackIsPushed();
ElementResolveContext elementContext(*element);
if (RuntimeEnabledFeatures::styleSharingEnabled() &&
sharingBehavior == AllowStyleSharing &&
(defaultParent || elementContext.parentStyle())) {
SharedStyleFinder styleFinder(elementContext, m_features,
m_siblingRuleSet.get(),
m_uncommonAttributeRuleSet.get(), *this);
if (RefPtr<ComputedStyle> sharedStyle = styleFinder.findSharedStyle())
return sharedStyle.release();
}
StyleResolverState state(document(), elementContext, defaultParent);
ElementAnimations* elementAnimations = element->elementAnimations();
const ComputedStyle* baseComputedStyle =
elementAnimations ? elementAnimations->baseComputedStyle() : nullptr;
if (baseComputedStyle) {
state.setStyle(ComputedStyle::clone(*baseComputedStyle));
if (!state.parentStyle())
state.setParentStyle(initialStyleForElement());
} else {
if (state.parentStyle()) {
RefPtr<ComputedStyle> style = ComputedStyle::create();
style->inheritFrom(*state.parentStyle(),
isAtShadowBoundary(element)
? ComputedStyleBase::AtShadowBoundary
: ComputedStyleBase::NotAtShadowBoundary);
state.setStyle(style.release());
} else {
state.setStyle(initialStyleForElement());
state.setParentStyle(ComputedStyle::clone(*state.style()));
}
}
// contenteditable attribute (implemented by -webkit-user-modify) should
// be propagated from shadow host to distributed node.
if (state.distributedToInsertionPoint()) {
if (Element* parent = element->parentElement()) {
if (ComputedStyle* styleOfShadowHost = parent->mutableComputedStyle())
state.style()->setUserModify(styleOfShadowHost->userModify());
}
}
if (element->isLink()) {
state.style()->setIsLink(true);
EInsideLink linkState = state.elementLinkState();
if (linkState != NotInsideLink) {
bool forceVisited = InspectorInstrumentation::forcePseudoState(
element, CSSSelector::PseudoVisited);
if (forceVisited)
linkState = InsideVisitedLink;
}
state.style()->setInsideLink(linkState);
}
if (!baseComputedStyle) {
bool needsCollection = false;
CSSDefaultStyleSheets::instance().ensureDefaultStyleSheetsForElement(
*element, needsCollection);
if (needsCollection)
collectFeatures();
ElementRuleCollector collector(state.elementContext(), m_selectorFilter,
state.style());
matchAllRules(state, collector,
matchingBehavior != MatchAllRulesExcludingSMIL);
// TODO(dominicc): Remove this counter when Issue 590014 is fixed.
if (element->hasTagName(HTMLNames::summaryTag)) {
MatchedPropertiesRange properties =
collector.matchedResult().authorRules();
for (auto it = properties.begin(); it != properties.end(); ++it) {
const CSSValue* value =
it->properties->getPropertyCSSValue(CSSPropertyDisplay);
if (value && value->isIdentifierValue() &&
toCSSIdentifierValue(*value).getValueID() == CSSValueBlock)
UseCounter::count(
element->document(),
UseCounter::SummaryElementWithDisplayBlockAuthorRule);
}
}
if (element->computedStyle() &&
element->computedStyle()->textAutosizingMultiplier() !=
state.style()->textAutosizingMultiplier()) {
// Preserve the text autosizing multiplier on style recalc. Autosizer will
// update it during layout if needed.
// NOTE: this must occur before applyMatchedProperties for correct
// computation of font-relative lengths.
state.style()->setTextAutosizingMultiplier(
element->computedStyle()->textAutosizingMultiplier());
state.style()->setUnique();
}
if (state.hasDirAutoAttribute())
state.style()->setSelfOrAncestorHasDirAutoAttribute(true);
applyMatchedProperties(state, collector.matchedResult());
applyCallbackSelectors(state);
// Cache our original display.
state.style()->setOriginalDisplay(state.style()->display());
adjustComputedStyle(state, element);
if (elementAnimations)
elementAnimations->updateBaseComputedStyle(state.style());
} else {
INCREMENT_STYLE_STATS_COUNTER(document().styleEngine(), baseStylesUsed, 1);
}
// FIXME: The CSSWG wants to specify that the effects of animations are
// applied before important rules, but this currently happens here as we
// require adjustment to have happened before deciding which properties to
// transition.
if (applyAnimatedProperties(state, element)) {
INCREMENT_STYLE_STATS_COUNTER(document().styleEngine(), stylesAnimated, 1);
adjustComputedStyle(state, element);
}
if (isHTMLBodyElement(*element))
document().textLinkColors().setTextColor(state.style()->color());
setAnimationUpdateIfNeeded(state, *element);
if (state.style()->hasViewportUnits())
document().setHasViewportUnits();
if (state.style()->hasRemUnits())
document().styleEngine().setUsesRemUnit(true);
// Now return the style.
return state.takeStyle();
}
// TODO(alancutter): Create compositor keyframe values directly instead of
// intermediate AnimatableValues.
PassRefPtr<AnimatableValue> StyleResolver::createAnimatableValueSnapshot(
Element& element,
const ComputedStyle& baseStyle,
const ComputedStyle* parentStyle,
CSSPropertyID property,
const CSSValue* value) {
// TODO(alancutter): Avoid creating a StyleResolverState just to apply a
// single value on a ComputedStyle.
StyleResolverState state(element.document(), &element, parentStyle);
state.setStyle(ComputedStyle::clone(baseStyle));
if (value) {
StyleBuilder::applyProperty(property, state, *value);
state.fontBuilder().createFont(
state.document().styleEngine().fontSelector(), state.mutableStyleRef());
}
return CSSAnimatableValueFactory::create(property, *state.style());
}
PseudoElement* StyleResolver::createPseudoElement(Element* parent,
PseudoId pseudoId) {
if (pseudoId == PseudoIdFirstLetter)
return FirstLetterPseudoElement::create(parent);
return PseudoElement::create(parent, pseudoId);
}
PseudoElement* StyleResolver::createPseudoElementIfNeeded(Element& parent,
PseudoId pseudoId) {
LayoutObject* parentLayoutObject = parent.layoutObject();
if (!parentLayoutObject)
return nullptr;
// The first letter pseudo element has to look up the tree and see if any
// of the ancestors are first letter.
if (pseudoId < FirstInternalPseudoId && pseudoId != PseudoIdFirstLetter &&
!parentLayoutObject->style()->hasPseudoStyle(pseudoId))
return nullptr;
if (pseudoId == PseudoIdBackdrop && !parent.isInTopLayer())
return nullptr;
if (pseudoId == PseudoIdFirstLetter &&
(parent.isSVGElement() ||
!FirstLetterPseudoElement::firstLetterTextLayoutObject(parent)))
return nullptr;
if (!canHaveGeneratedChildren(*parentLayoutObject))
return nullptr;
ComputedStyle* parentStyle = parentLayoutObject->mutableStyle();
if (ComputedStyle* cachedStyle =
parentStyle->getCachedPseudoStyle(pseudoId)) {
if (!pseudoElementLayoutObjectIsNeeded(cachedStyle))
return nullptr;
return createPseudoElement(&parent, pseudoId);
}
StyleResolverState state(document(), &parent, parentStyle);
if (!pseudoStyleForElementInternal(parent, pseudoId, parentStyle, state))
return nullptr;
RefPtr<ComputedStyle> style = state.takeStyle();
ASSERT(style);
parentStyle->addCachedPseudoStyle(style);
if (!pseudoElementLayoutObjectIsNeeded(style.get()))
return nullptr;
PseudoElement* pseudo = createPseudoElement(&parent, pseudoId);
setAnimationUpdateIfNeeded(state, *pseudo);
if (ElementAnimations* elementAnimations = pseudo->elementAnimations())
elementAnimations->cssAnimations().maybeApplyPendingUpdate(pseudo);
return pseudo;
}
bool StyleResolver::pseudoStyleForElementInternal(
Element& element,
const PseudoStyleRequest& pseudoStyleRequest,
const ComputedStyle* parentStyle,
StyleResolverState& state) {
ASSERT(document().frame());
ASSERT(document().settings());
ASSERT(pseudoStyleRequest.pseudoId != PseudoIdFirstLineInherited);
ASSERT(state.parentStyle());
SelectorFilterParentScope::ensureParentStackIsPushed();
Element* pseudoElement = element.pseudoElement(pseudoStyleRequest.pseudoId);
ElementAnimations* elementAnimations =
pseudoElement ? pseudoElement->elementAnimations() : nullptr;
const ComputedStyle* baseComputedStyle =
elementAnimations ? elementAnimations->baseComputedStyle() : nullptr;
if (baseComputedStyle) {
state.setStyle(ComputedStyle::clone(*baseComputedStyle));
} else if (pseudoStyleRequest.allowsInheritance(state.parentStyle())) {
RefPtr<ComputedStyle> style = ComputedStyle::create();
style->inheritFrom(*state.parentStyle());
state.setStyle(style.release());
} else {
state.setStyle(initialStyleForElement());
state.setParentStyle(ComputedStyle::clone(*state.style()));
}
state.style()->setStyleType(pseudoStyleRequest.pseudoId);
// Since we don't use pseudo-elements in any of our quirk/print
// user agent rules, don't waste time walking those rules.
if (!baseComputedStyle) {
// Check UA, user and author rules.
ElementRuleCollector collector(state.elementContext(), m_selectorFilter,
state.style());
collector.setPseudoStyleRequest(pseudoStyleRequest);
matchUARules(collector);
matchAuthorRules(*state.element(), collector);
collector.finishAddingAuthorRulesForTreeScope();
if (!collector.matchedResult().hasMatchedProperties())
return false;
applyMatchedProperties(state, collector.matchedResult());
applyCallbackSelectors(state);
// Cache our original display.
state.style()->setOriginalDisplay(state.style()->display());
// FIXME: Passing 0 as the Element* introduces a lot of complexity
// in the adjustComputedStyle code.
adjustComputedStyle(state, 0);
if (elementAnimations)
elementAnimations->updateBaseComputedStyle(state.style());
}
// FIXME: The CSSWG wants to specify that the effects of animations are
// applied before important rules, but this currently happens here as we
// require adjustment to have happened before deciding which properties to
// transition.
if (applyAnimatedProperties(state, pseudoElement))
adjustComputedStyle(state, 0);
document().styleEngine().incStyleForElementCount();
INCREMENT_STYLE_STATS_COUNTER(document().styleEngine(), pseudoElementsStyled,
1);
if (state.style()->hasViewportUnits())
document().setHasViewportUnits();
return true;
}
PassRefPtr<ComputedStyle> StyleResolver::pseudoStyleForElement(
Element* element,
const PseudoStyleRequest& pseudoStyleRequest,
const ComputedStyle* parentStyle) {
ASSERT(parentStyle);
if (!element)
return nullptr;
StyleResolverState state(document(), element, parentStyle);
if (!pseudoStyleForElementInternal(*element, pseudoStyleRequest, parentStyle,
state)) {
if (pseudoStyleRequest.type == PseudoStyleRequest::ForRenderer)
return nullptr;
return state.takeStyle();
}
if (PseudoElement* pseudoElement =
element->pseudoElement(pseudoStyleRequest.pseudoId))
setAnimationUpdateIfNeeded(state, *pseudoElement);
// Now return the style.
return state.takeStyle();
}
PassRefPtr<ComputedStyle> StyleResolver::styleForPage(int pageIndex) {
ASSERT(!hasPendingAuthorStyleSheets());
// m_rootElementStyle will be set to the document style.
StyleResolverState state(document(), document().documentElement());
RefPtr<ComputedStyle> style = ComputedStyle::create();
const ComputedStyle* rootElementStyle = state.rootElementStyle()
? state.rootElementStyle()
: document().computedStyle();
ASSERT(rootElementStyle);
style->inheritFrom(*rootElementStyle);
state.setStyle(style.release());
PageRuleCollector collector(rootElementStyle, pageIndex);
collector.matchPageRules(
CSSDefaultStyleSheets::instance().defaultPrintStyle());
if (ScopedStyleResolver* scopedResolver = document().scopedStyleResolver())
scopedResolver->matchPageRules(collector);
bool inheritedOnly = false;
const MatchResult& result = collector.matchedResult();
applyMatchedProperties<HighPropertyPriority>(state, result.allRules(), false,
inheritedOnly);
// If our font got dirtied, go ahead and update it now.
updateFont(state);
applyMatchedProperties<LowPropertyPriority>(state, result.allRules(), false,
inheritedOnly);
loadPendingResources(state);
// Now return the style.
return state.takeStyle();
}
PassRefPtr<ComputedStyle> StyleResolver::initialStyleForElement() {
RefPtr<ComputedStyle> style = ComputedStyle::create();
FontBuilder fontBuilder(document());
fontBuilder.setInitial(style->effectiveZoom());
fontBuilder.createFont(document().styleEngine().fontSelector(), *style);
return style.release();
}
PassRefPtr<ComputedStyle> StyleResolver::styleForText(Text* textNode) {
ASSERT(textNode);
Node* parentNode = LayoutTreeBuilderTraversal::parent(*textNode);
if (!parentNode || !parentNode->computedStyle())
return initialStyleForElement();
return parentNode->mutableComputedStyle();
}
void StyleResolver::updateFont(StyleResolverState& state) {
state.fontBuilder().createFont(document().styleEngine().fontSelector(),
state.mutableStyleRef());
state.setConversionFontSizes(CSSToLengthConversionData::FontSizes(
state.style(), state.rootElementStyle()));
state.setConversionZoom(state.style()->effectiveZoom());
}
StyleRuleList* StyleResolver::styleRulesForElement(Element* element,
unsigned rulesToInclude) {
ASSERT(element);
StyleResolverState state(document(), element);
ElementRuleCollector collector(state.elementContext(), m_selectorFilter,
state.style());
collector.setMode(SelectorChecker::CollectingStyleRules);
collectPseudoRulesForElement(*element, collector, PseudoIdNone,
rulesToInclude);
return collector.matchedStyleRuleList();
}
CSSRuleList* StyleResolver::pseudoCSSRulesForElement(Element* element,
PseudoId pseudoId,
unsigned rulesToInclude) {
ASSERT(element);
StyleResolverState state(document(), element);
ElementRuleCollector collector(state.elementContext(), m_selectorFilter,
state.style());
collector.setMode(SelectorChecker::CollectingCSSRules);
collectPseudoRulesForElement(*element, collector, pseudoId, rulesToInclude);
return collector.matchedCSSRuleList();
}
CSSRuleList* StyleResolver::cssRulesForElement(Element* element,
unsigned rulesToInclude) {
return pseudoCSSRulesForElement(element, PseudoIdNone, rulesToInclude);
}
void StyleResolver::collectPseudoRulesForElement(
const Element& element,
ElementRuleCollector& collector,
PseudoId pseudoId,
unsigned rulesToInclude) {
collector.setPseudoStyleRequest(PseudoStyleRequest(pseudoId));
if (rulesToInclude & UAAndUserCSSRules)
matchUARules(collector);
if (rulesToInclude & AuthorCSSRules) {
collector.setSameOriginOnly(!(rulesToInclude & CrossOriginCSSRules));
collector.setIncludeEmptyRules(rulesToInclude & EmptyCSSRules);
matchAuthorRules(element, collector);
}
}
bool StyleResolver::applyAnimatedProperties(StyleResolverState& state,
const Element* animatingElement) {
Element* element = state.element();
ASSERT(element);
// The animating element may be this element, or its pseudo element. It is
// null when calculating the style for a potential pseudo element that has
// yet to be created.
ASSERT(animatingElement == element || !animatingElement ||
animatingElement->parentOrShadowHostElement() == element);
if (!(animatingElement && animatingElement->hasAnimations()) &&
!state.style()->transitions() && !state.style()->animations())
return false;
CSSAnimations::calculateUpdate(animatingElement, *element, *state.style(),
state.parentStyle(), state.animationUpdate(),
this);
if (state.animationUpdate().isEmpty())
return false;
CSSAnimations::snapshotCompositorKeyframes(
*element, state.animationUpdate(), *state.style(), state.parentStyle());
if (state.style()->insideLink() != NotInsideLink) {
ASSERT(state.applyPropertyToRegularStyle());
state.setApplyPropertyToVisitedLinkStyle(true);
}
const ActiveInterpolationsMap& activeInterpolationsMapForAnimations =
state.animationUpdate().activeInterpolationsForAnimations();
const ActiveInterpolationsMap& activeInterpolationsMapForTransitions =
state.animationUpdate().activeInterpolationsForTransitions();
// TODO(crbug.com/644148): Apply animations on custom properties.
applyAnimatedProperties<HighPropertyPriority>(
state, activeInterpolationsMapForAnimations);
applyAnimatedProperties<HighPropertyPriority>(
state, activeInterpolationsMapForTransitions);
updateFont(state);
applyAnimatedProperties<LowPropertyPriority>(
state, activeInterpolationsMapForAnimations);
applyAnimatedProperties<LowPropertyPriority>(
state, activeInterpolationsMapForTransitions);
// Start loading resources used by animations.
loadPendingResources(state);
ASSERT(!state.fontBuilder().fontDirty());
state.setApplyPropertyToVisitedLinkStyle(false);
return true;
}
StyleRuleKeyframes* StyleResolver::findKeyframesRule(
const Element* element,
const AtomicString& animationName) {
HeapVector<Member<ScopedStyleResolver>, 8> resolvers;
collectScopedResolversForHostedShadowTrees(*element, resolvers);
if (ScopedStyleResolver* scopedResolver =
element->treeScope().scopedStyleResolver())
resolvers.append(scopedResolver);
for (auto& resolver : resolvers) {
if (StyleRuleKeyframes* keyframesRule =
resolver->keyframeStylesForAnimation(animationName.impl()))
return keyframesRule;
}
for (auto& resolver : resolvers)
resolver->setHasUnresolvedKeyframesRule();
return nullptr;
}
template <CSSPropertyPriority priority>
void StyleResolver::applyAnimatedProperties(
StyleResolverState& state,
const ActiveInterpolationsMap& activeInterpolationsMap) {
// TODO(alancutter): Don't apply presentation attribute animations here,
// they should instead apply in
// SVGElement::collectStyleForPresentationAttribute().
for (const auto& entry : activeInterpolationsMap) {
CSSPropertyID property = entry.key.isCSSProperty()
? entry.key.cssProperty()
: entry.key.presentationAttribute();
if (!CSSPropertyPriorityData<priority>::propertyHasPriority(property))
continue;
const Interpolation& interpolation = *entry.value.first();
if (interpolation.isInvalidatableInterpolation()) {
InterpolationEnvironment environment(state);
InvalidatableInterpolation::applyStack(entry.value, environment);
} else {
// TODO(alancutter): Remove this old code path once animations have
// completely migrated to InterpolationTypes.
toStyleInterpolation(interpolation).apply(state);
}
}
}
static inline bool isValidCueStyleProperty(CSSPropertyID id) {
switch (id) {
case CSSPropertyBackground:
case CSSPropertyBackgroundAttachment:
case CSSPropertyBackgroundClip:
case CSSPropertyBackgroundColor:
case CSSPropertyBackgroundImage:
case CSSPropertyBackgroundOrigin:
case CSSPropertyBackgroundPosition:
case CSSPropertyBackgroundPositionX:
case CSSPropertyBackgroundPositionY:
case CSSPropertyBackgroundRepeat:
case CSSPropertyBackgroundRepeatX:
case CSSPropertyBackgroundRepeatY:
case CSSPropertyBackgroundSize:
case CSSPropertyColor:
case CSSPropertyFont:
case CSSPropertyFontFamily:
case CSSPropertyFontSize:
case CSSPropertyFontStretch:
case CSSPropertyFontStyle:
case CSSPropertyFontVariant:
case CSSPropertyFontWeight:
case CSSPropertyLineHeight:
case CSSPropertyOpacity:
case CSSPropertyOutline:
case CSSPropertyOutlineColor:
case CSSPropertyOutlineOffset:
case CSSPropertyOutlineStyle:
case CSSPropertyOutlineWidth:
case CSSPropertyVisibility:
case CSSPropertyWhiteSpace:
// FIXME: 'text-decoration' shorthand to be handled when available.
// See https://chromiumcodereview.appspot.com/19516002 for details.
case CSSPropertyTextDecoration:
case CSSPropertyTextShadow:
case CSSPropertyBorderStyle:
return true;
case CSSPropertyTextDecorationLine:
case CSSPropertyTextDecorationStyle:
case CSSPropertyTextDecorationColor:
case CSSPropertyTextDecorationSkip:
return RuntimeEnabledFeatures::css3TextDecorationsEnabled();
default:
break;
}
return false;
}
static inline bool isValidFirstLetterStyleProperty(CSSPropertyID id) {
switch (id) {
// Valid ::first-letter properties listed in spec:
// http://www.w3.org/TR/css3-selectors/#application-in-css
case CSSPropertyBackgroundAttachment:
case CSSPropertyBackgroundBlendMode:
case CSSPropertyBackgroundClip:
case CSSPropertyBackgroundColor:
case CSSPropertyBackgroundImage:
case CSSPropertyBackgroundOrigin:
case CSSPropertyBackgroundPosition:
case CSSPropertyBackgroundPositionX:
case CSSPropertyBackgroundPositionY:
case CSSPropertyBackgroundRepeat:
case CSSPropertyBackgroundRepeatX:
case CSSPropertyBackgroundRepeatY:
case CSSPropertyBackgroundSize:
case CSSPropertyBorderBottomColor:
case CSSPropertyBorderBottomLeftRadius:
case CSSPropertyBorderBottomRightRadius:
case CSSPropertyBorderBottomStyle:
case CSSPropertyBorderBottomWidth:
case CSSPropertyBorderImageOutset:
case CSSPropertyBorderImageRepeat:
case CSSPropertyBorderImageSlice:
case CSSPropertyBorderImageSource:
case CSSPropertyBorderImageWidth:
case CSSPropertyBorderLeftColor:
case CSSPropertyBorderLeftStyle:
case CSSPropertyBorderLeftWidth:
case CSSPropertyBorderRightColor:
case CSSPropertyBorderRightStyle:
case CSSPropertyBorderRightWidth:
case CSSPropertyBorderTopColor:
case CSSPropertyBorderTopLeftRadius:
case CSSPropertyBorderTopRightRadius:
case CSSPropertyBorderTopStyle:
case CSSPropertyBorderTopWidth:
case CSSPropertyColor:
case CSSPropertyFloat:
case CSSPropertyFont:
case CSSPropertyFontFamily:
case CSSPropertyFontKerning:
case CSSPropertyFontSize:
case CSSPropertyFontStretch:
case CSSPropertyFontStyle:
case CSSPropertyFontVariant:
case CSSPropertyFontVariantCaps:
case CSSPropertyFontVariantLigatures:
case CSSPropertyFontVariantNumeric:
case CSSPropertyFontWeight:
case CSSPropertyLetterSpacing:
case CSSPropertyLineHeight:
case CSSPropertyMarginBottom:
case CSSPropertyMarginLeft:
case CSSPropertyMarginRight:
case CSSPropertyMarginTop:
case CSSPropertyPaddingBottom:
case CSSPropertyPaddingLeft:
case CSSPropertyPaddingRight:
case CSSPropertyPaddingTop:
case CSSPropertyTextTransform:
case CSSPropertyVerticalAlign:
case CSSPropertyWebkitBackgroundClip:
case CSSPropertyWebkitBackgroundOrigin:
case CSSPropertyWebkitBorderAfter:
case CSSPropertyWebkitBorderAfterColor:
case CSSPropertyWebkitBorderAfterStyle:
case CSSPropertyWebkitBorderAfterWidth:
case CSSPropertyWebkitBorderBefore:
case CSSPropertyWebkitBorderBeforeColor:
case CSSPropertyWebkitBorderBeforeStyle:
case CSSPropertyWebkitBorderBeforeWidth:
case CSSPropertyWebkitBorderEnd:
case CSSPropertyWebkitBorderEndColor:
case CSSPropertyWebkitBorderEndStyle:
case CSSPropertyWebkitBorderEndWidth:
case CSSPropertyWebkitBorderHorizontalSpacing:
case CSSPropertyWebkitBorderImage:
case CSSPropertyWebkitBorderStart:
case CSSPropertyWebkitBorderStartColor:
case CSSPropertyWebkitBorderStartStyle:
case CSSPropertyWebkitBorderStartWidth:
case CSSPropertyWebkitBorderVerticalSpacing:
case CSSPropertyWebkitFontSmoothing:
case CSSPropertyWebkitMarginAfter:
case CSSPropertyWebkitMarginAfterCollapse:
case CSSPropertyWebkitMarginBefore:
case CSSPropertyWebkitMarginBeforeCollapse:
case CSSPropertyWebkitMarginBottomCollapse:
case CSSPropertyWebkitMarginCollapse:
case CSSPropertyWebkitMarginEnd:
case CSSPropertyWebkitMarginStart:
case CSSPropertyWebkitMarginTopCollapse:
case CSSPropertyWordSpacing:
return true;
case CSSPropertyTextDecoration:
ASSERT(!RuntimeEnabledFeatures::css3TextDecorationsEnabled());
return true;
case CSSPropertyTextDecorationColor:
case CSSPropertyTextDecorationLine:
case CSSPropertyTextDecorationStyle:
case CSSPropertyTextDecorationSkip:
ASSERT(RuntimeEnabledFeatures::css3TextDecorationsEnabled());
return true;
// text-shadow added in text decoration spec:
// http://www.w3.org/TR/css-text-decor-3/#text-shadow-property
case CSSPropertyTextShadow:
// box-shadox added in CSS3 backgrounds spec:
// http://www.w3.org/TR/css3-background/#placement
case CSSPropertyBoxShadow:
// Properties that we currently support outside of spec.
case CSSPropertyVisibility:
return true;
default:
return false;
}
}
static bool shouldIgnoreTextTrackAuthorStyle(const Document& document) {
Settings* settings = document.settings();
if (!settings)
return false;
// Ignore author specified settings for text tracks when any of the user
// settings are present.
if (!settings->textTrackBackgroundColor().isEmpty() ||
!settings->textTrackFontFamily().isEmpty() ||
!settings->textTrackFontStyle().isEmpty() ||
!settings->textTrackFontVariant().isEmpty() ||
!settings->textTrackTextColor().isEmpty() ||
!settings->textTrackTextShadow().isEmpty() ||
!settings->textTrackTextSize().isEmpty())
return true;
return false;
}
static inline bool isPropertyInWhitelist(
PropertyWhitelistType propertyWhitelistType,
CSSPropertyID property,
const Document& document) {
if (propertyWhitelistType == PropertyWhitelistNone)
return true; // Early bail for the by far most common case.
if (propertyWhitelistType == PropertyWhitelistFirstLetter)
return isValidFirstLetterStyleProperty(property);
if (propertyWhitelistType == PropertyWhitelistCue)
return isValidCueStyleProperty(property) &&
!shouldIgnoreTextTrackAuthorStyle(document);
ASSERT_NOT_REACHED();
return true;
}
// This method expands the 'all' shorthand property to longhand properties
// and applies the expanded longhand properties.
template <CSSPropertyPriority priority>
void StyleResolver::applyAllProperty(
StyleResolverState& state,
const CSSValue& allValue,
bool inheritedOnly,
PropertyWhitelistType propertyWhitelistType) {
// The 'all' property doesn't apply to variables:
// https://drafts.csswg.org/css-variables/#defining-variables
if (priority == ResolveVariables)
return;
unsigned startCSSProperty = CSSPropertyPriorityData<priority>::first();
unsigned endCSSProperty = CSSPropertyPriorityData<priority>::last();
for (unsigned i = startCSSProperty; i <= endCSSProperty; ++i) {
CSSPropertyID propertyId = static_cast<CSSPropertyID>(i);
// StyleBuilder does not allow any expanded shorthands.
if (isShorthandProperty(propertyId))
continue;
// all shorthand spec says:
// The all property is a shorthand that resets all CSS properties
// except direction and unicode-bidi.
// c.f. http://dev.w3.org/csswg/css-cascade/#all-shorthand
// We skip applyProperty when a given property is unicode-bidi or
// direction.
if (!CSSProperty::isAffectedByAllProperty(propertyId))
continue;
if (!isPropertyInWhitelist(propertyWhitelistType, propertyId, document()))
continue;
// When hitting matched properties' cache, only inherited properties will be
// applied.
if (inheritedOnly && !CSSPropertyMetadata::isInheritedProperty(propertyId))
continue;
StyleBuilder::applyProperty(propertyId, state, allValue);
}
}
template <CSSPropertyPriority priority>
void StyleResolver::applyPropertiesForApplyAtRule(
StyleResolverState& state,
const CSSValue& value,
bool isImportant,
PropertyWhitelistType propertyWhitelistType) {
state.style()->setHasVariableReferenceFromNonInheritedProperty();
if (!state.style()->inheritedVariables())
return;
const String& name = toCSSCustomIdentValue(value).value();
const StylePropertySet* propertySet =
state.customPropertySetForApplyAtRule(name);
bool inheritedOnly = false;
if (propertySet)
applyProperties<priority>(state, propertySet, isImportant, inheritedOnly,
propertyWhitelistType);
}
template <CSSPropertyPriority priority>
void StyleResolver::applyProperties(
StyleResolverState& state,
const StylePropertySet* properties,
bool isImportant,
bool inheritedOnly,
PropertyWhitelistType propertyWhitelistType) {
unsigned propertyCount = properties->propertyCount();
for (unsigned i = 0; i < propertyCount; ++i) {
StylePropertySet::PropertyReference current = properties->propertyAt(i);
CSSPropertyID property = current.id();
if (property == CSSPropertyApplyAtRule) {
DCHECK(!inheritedOnly);
applyPropertiesForApplyAtRule<priority>(
state, current.value(), isImportant, propertyWhitelistType);
continue;
}
if (isImportant != current.isImportant())
continue;
if (property == CSSPropertyAll) {
applyAllProperty<priority>(state, current.value(), inheritedOnly,
propertyWhitelistType);
continue;
}
if (!isPropertyInWhitelist(propertyWhitelistType, property, document()))
continue;
if (inheritedOnly && !current.isInherited()) {
// If the property value is explicitly inherited, we need to apply further
// non-inherited properties as they might override the value inherited
// here. For this reason we don't allow declarations with explicitly
// inherited properties to be cached.
DCHECK(!current.value().isInheritedValue());
continue;
}
if (!CSSPropertyPriorityData<priority>::propertyHasPriority(property))
continue;
StyleBuilder::applyProperty(current.id(), state, current.value());
}
}
template <CSSPropertyPriority priority>
void StyleResolver::applyMatchedProperties(StyleResolverState& state,
const MatchedPropertiesRange& range,
bool isImportant,
bool inheritedOnly) {
if (range.isEmpty())
return;
if (state.style()->insideLink() != NotInsideLink) {
for (const auto& matchedProperties : range) {
unsigned linkMatchType = matchedProperties.m_types.linkMatchType;
// FIXME: It would be nicer to pass these as arguments but that requires
// changes in many places.
state.setApplyPropertyToRegularStyle(linkMatchType &
CSSSelector::MatchLink);
state.setApplyPropertyToVisitedLinkStyle(linkMatchType &
CSSSelector::MatchVisited);
applyProperties<priority>(state, matchedProperties.properties.get(),
isImportant, inheritedOnly,
static_cast<PropertyWhitelistType>(
matchedProperties.m_types.whitelistType));
}
state.setApplyPropertyToRegularStyle(true);
state.setApplyPropertyToVisitedLinkStyle(false);
return;
}
for (const auto& matchedProperties : range)
applyProperties<priority>(state, matchedProperties.properties.get(),
isImportant, inheritedOnly,
static_cast<PropertyWhitelistType>(
matchedProperties.m_types.whitelistType));
}
static unsigned computeMatchedPropertiesHash(
const MatchedProperties* properties,
unsigned size) {
return StringHasher::hashMemory(properties, sizeof(MatchedProperties) * size);
}
void StyleResolver::invalidateMatchedPropertiesCache() {
m_matchedPropertiesCache.clear();
}
void StyleResolver::notifyResizeForViewportUnits() {
m_viewportStyleResolver->collectViewportRules();
m_matchedPropertiesCache.clearViewportDependent();
}
void StyleResolver::applyMatchedProperties(StyleResolverState& state,
const MatchResult& matchResult) {
const Element* element = state.element();
ASSERT(element);
INCREMENT_STYLE_STATS_COUNTER(document().styleEngine(), matchedPropertyApply,
1);
unsigned cacheHash =
RuntimeEnabledFeatures::styleMatchedPropertiesCacheEnabled() &&
matchResult.isCacheable()
? computeMatchedPropertiesHash(matchResult.matchedProperties().data(),
matchResult.matchedProperties().size())
: 0;
bool applyInheritedOnly = false;
const CachedMatchedProperties* cachedMatchedProperties =
cacheHash
? m_matchedPropertiesCache.find(cacheHash, state,
matchResult.matchedProperties())
: nullptr;
if (cachedMatchedProperties && MatchedPropertiesCache::isCacheable(state)) {
INCREMENT_STYLE_STATS_COUNTER(document().styleEngine(),
matchedPropertyCacheHit, 1);
// We can build up the style by copying non-inherited properties from an
// earlier style object built using the same exact style declarations. We
// then only need to apply the inherited properties, if any, as their values
// can depend on the element context. This is fast and saves memory by
// reusing the style data structures.
state.style()->copyNonInheritedFromCached(
*cachedMatchedProperties->computedStyle);
if (state.parentStyle()->inheritedDataShared(
*cachedMatchedProperties->parentComputedStyle) &&
!isAtShadowBoundary(element) &&
(!state.distributedToInsertionPoint() ||
state.style()->userModify() == READ_ONLY)) {
INCREMENT_STYLE_STATS_COUNTER(document().styleEngine(),
matchedPropertyCacheInheritedHit, 1);
EInsideLink linkStatus = state.style()->insideLink();
// If the cache item parent style has identical inherited properties to
// the current parent style then the resulting style will be identical
// too. We copy the inherited properties over from the cache and are done.
state.style()->inheritFrom(*cachedMatchedProperties->computedStyle);
// Unfortunately the link status is treated like an inherited property. We
// need to explicitly restore it.
state.style()->setInsideLink(linkStatus);
updateFont(state);
return;
}
applyInheritedOnly = true;
}
// TODO(leviw): We need the proper bit for tracking whether we need to do this
// work.
applyMatchedProperties<ResolveVariables>(state, matchResult.authorRules(),
false, applyInheritedOnly);
applyMatchedProperties<ResolveVariables>(state, matchResult.authorRules(),
true, applyInheritedOnly);
// TODO(leviw): stop recalculating every time
CSSVariableResolver::resolveVariableDefinitions(state);
if (RuntimeEnabledFeatures::cssApplyAtRulesEnabled()) {
if (cacheCustomPropertiesForApplyAtRules(state,
matchResult.authorRules())) {
applyMatchedProperties<ResolveVariables>(state, matchResult.authorRules(),
false, applyInheritedOnly);
applyMatchedProperties<ResolveVariables>(state, matchResult.authorRules(),
true, applyInheritedOnly);
CSSVariableResolver::resolveVariableDefinitions(state);
}
}
// Now we have all of the matched rules in the appropriate order. Walk the
// rules and apply high-priority properties first, i.e., those properties that
// other properties depend on. The order is (1) high-priority not important,
// (2) high-priority important, (3) normal not important and (4) normal
// important.
applyMatchedProperties<HighPropertyPriority>(state, matchResult.allRules(),
false, applyInheritedOnly);
for (auto range : ImportantAuthorRanges(matchResult))
applyMatchedProperties<HighPropertyPriority>(state, range, true,
applyInheritedOnly);
applyMatchedProperties<HighPropertyPriority>(state, matchResult.uaRules(),
true, applyInheritedOnly);
if (UNLIKELY(isSVGForeignObjectElement(element))) {
// LayoutSVGRoot handles zooming for the whole SVG subtree, so foreignObject
// content should not be scaled again.
//
// FIXME: The following hijacks the zoom property for foreignObject so that
// children of foreignObject get the correct font-size in case of zooming.
// 'zoom' has HighPropertyPriority, along with other font-related properties
// used as input to the FontBuilder, so resetting it here may cause the
// FontBuilder to recompute the font used as inheritable font for
// foreignObject content. If we want to support zoom on foreignObject we'll
// need to find another way of handling the SVG zoom model.
state.setEffectiveZoom(ComputedStyle::initialZoom());
}
if (cachedMatchedProperties &&
cachedMatchedProperties->computedStyle->effectiveZoom() !=
state.style()->effectiveZoom()) {
state.fontBuilder().didChangeEffectiveZoom();
applyInheritedOnly = false;
}
// If our font got dirtied, go ahead and update it now.
updateFont(state);
// Many properties depend on the font. If it changes we just apply all
// properties.
if (cachedMatchedProperties &&
cachedMatchedProperties->computedStyle->getFontDescription() !=
state.style()->getFontDescription())
applyInheritedOnly = false;
// Registered custom properties are computed after high priority properties.
CSSVariableResolver::computeRegisteredVariables(state);
// Now do the normal priority UA properties.
applyMatchedProperties<LowPropertyPriority>(state, matchResult.uaRules(),
false, applyInheritedOnly);
// Cache the UA properties to pass them to LayoutTheme in adjustComputedStyle.
state.cacheUserAgentBorderAndBackground();
// Now do the author and user normal priority properties and all the
// !important properties.
applyMatchedProperties<LowPropertyPriority>(state, matchResult.authorRules(),
false, applyInheritedOnly);
for (auto range : ImportantAuthorRanges(matchResult))
applyMatchedProperties<LowPropertyPriority>(state, range, true,
applyInheritedOnly);
applyMatchedProperties<LowPropertyPriority>(state, matchResult.uaRules(),
true, applyInheritedOnly);
if (state.style()->hasAppearance() && !applyInheritedOnly) {
// Check whether the final border and background differs from the cached UA
// ones. When there is a partial match in the MatchedPropertiesCache, these
// flags will already be set correctly and the value stored in
// cacheUserAgentBorderAndBackground is incorrect, so doing this check again
// would give the wrong answer.
state.style()->setHasAuthorBackground(hasAuthorBackground(state));
state.style()->setHasAuthorBorder(hasAuthorBorder(state));
}
loadPendingResources(state);
if (!cachedMatchedProperties && cacheHash &&
MatchedPropertiesCache::isCacheable(state)) {
ASSERT(RuntimeEnabledFeatures::styleMatchedPropertiesCacheEnabled());
INCREMENT_STYLE_STATS_COUNTER(document().styleEngine(),
matchedPropertyCacheAdded, 1);
m_matchedPropertiesCache.add(*state.style(), *state.parentStyle(),
cacheHash, matchResult.matchedProperties());
}
ASSERT(!state.fontBuilder().fontDirty());
}
bool StyleResolver::hasAuthorBackground(const StyleResolverState& state) {
const CachedUAStyle* cachedUAStyle = state.cachedUAStyle();
if (!cachedUAStyle)
return false;
FillLayer oldFill = cachedUAStyle->backgroundLayers;
FillLayer newFill = state.style()->backgroundLayers();
// Exclude background-repeat from comparison by resetting it.
oldFill.setRepeatX(NoRepeatFill);
oldFill.setRepeatY(NoRepeatFill);
newFill.setRepeatX(NoRepeatFill);
newFill.setRepeatY(NoRepeatFill);
return (oldFill != newFill ||
cachedUAStyle->backgroundColor != state.style()->backgroundColor());
}
bool StyleResolver::hasAuthorBorder(const StyleResolverState& state) {
const CachedUAStyle* cachedUAStyle = state.cachedUAStyle();
return cachedUAStyle && (cachedUAStyle->border != state.style()->border());
}
void StyleResolver::applyCallbackSelectors(StyleResolverState& state) {
if (!m_watchedSelectorsRules)
return;
ElementRuleCollector collector(state.elementContext(), m_selectorFilter,
state.style());
collector.setMode(SelectorChecker::CollectingStyleRules);
collector.setIncludeEmptyRules(true);
MatchRequest matchRequest(m_watchedSelectorsRules.get());
collector.collectMatchingRules(matchRequest);
collector.sortAndTransferMatchedRules();
StyleRuleList* rules = collector.matchedStyleRuleList();
if (!rules)
return;
for (size_t i = 0; i < rules->size(); i++)
state.style()->addCallbackSelector(
rules->at(i)->selectorList().selectorsText());
}
void StyleResolver::computeFont(ComputedStyle* style,
const StylePropertySet& propertySet) {
CSSPropertyID properties[] = {
CSSPropertyFontSize, CSSPropertyFontFamily, CSSPropertyFontStretch,
CSSPropertyFontStyle, CSSPropertyFontVariantCaps, CSSPropertyFontWeight,
CSSPropertyLineHeight,
};
// TODO(timloh): This is weird, the style is being used as its own parent
StyleResolverState state(document(), nullptr, style);
state.setStyle(style);
for (CSSPropertyID property : properties) {
if (property == CSSPropertyLineHeight)
updateFont(state);
StyleBuilder::applyProperty(property, state,
*propertySet.getPropertyCSSValue(property));
}
}
void StyleResolver::addViewportDependentMediaQueries(
const MediaQueryResultList& list) {
for (size_t i = 0; i < list.size(); ++i)
m_viewportDependentMediaQueryResults.append(list[i]);
}
void StyleResolver::addDeviceDependentMediaQueries(
const MediaQueryResultList& list) {
for (size_t i = 0; i < list.size(); ++i)
m_deviceDependentMediaQueryResults.append(list[i]);
}
bool StyleResolver::mediaQueryAffectedByViewportChange() const {
for (unsigned i = 0; i < m_viewportDependentMediaQueryResults.size(); ++i) {
if (m_medium->eval(m_viewportDependentMediaQueryResults[i]->expression()) !=
m_viewportDependentMediaQueryResults[i]->result())
return true;
}
return false;
}
bool StyleResolver::mediaQueryAffectedByDeviceChange() const {
for (unsigned i = 0; i < m_deviceDependentMediaQueryResults.size(); ++i) {
if (m_medium->eval(m_deviceDependentMediaQueryResults[i]->expression()) !=
m_deviceDependentMediaQueryResults[i]->result())
return true;
}
return false;
}
DEFINE_TRACE(StyleResolver) {
visitor->trace(m_matchedPropertiesCache);
visitor->trace(m_medium);
visitor->trace(m_viewportDependentMediaQueryResults);
visitor->trace(m_deviceDependentMediaQueryResults);
visitor->trace(m_selectorFilter);
visitor->trace(m_viewportStyleResolver);
visitor->trace(m_features);
visitor->trace(m_siblingRuleSet);
visitor->trace(m_uncommonAttributeRuleSet);
visitor->trace(m_watchedSelectorsRules);
visitor->trace(m_treeBoundaryCrossingScopes);
visitor->trace(m_styleSharingLists);
visitor->trace(m_pendingStyleSheets);
visitor->trace(m_document);
}
} // namespace blink