| /* |
| * Copyright (C) 1999 Lars Knoll (knoll@kde.org) |
| * Copyright (C) 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010, 2011 Apple Inc. All rights reserved. |
| * Copyright (C) 2012 Google Inc. All rights reserved. |
| * |
| * Redistribution and use in source and binary forms, with or without |
| * modification, are permitted provided that the following conditions |
| * are met: |
| * 1. Redistributions of source code must retain the above copyright |
| * notice, this list of conditions and the following disclaimer. |
| * 2. Redistributions in binary form must reproduce the above copyright |
| * notice, this list of conditions and the following disclaimer in the |
| * documentation and/or other materials provided with the distribution. |
| * |
| * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS'' AND ANY |
| * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED |
| * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE |
| * DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS BE LIABLE FOR ANY |
| * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES |
| * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; |
| * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON |
| * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
| * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS |
| * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
| */ |
| |
| #include "config.h" |
| #include "StyleScopeResolver.h" |
| |
| #if ENABLE(SHADOW_DOM) |
| |
| #include "CSSStyleRule.h" |
| #include "CSSStyleSheet.h" |
| #include "ContentDistributor.h" |
| #include "HTMLNames.h" |
| #include "HTMLStyleElement.h" |
| #include "RuleFeature.h" |
| #include "RuleSet.h" |
| #include "ShadowRoot.h" |
| |
| namespace WebCore { |
| |
| StyleScopeResolver::StyleScopeResolver() |
| : m_stackParent(0) |
| , m_stackParentBoundsIndex(0) |
| { |
| } |
| |
| StyleScopeResolver::~StyleScopeResolver() |
| { |
| } |
| |
| const ContainerNode* StyleScopeResolver::scopeFor(const CSSStyleSheet* sheet) |
| { |
| ASSERT(sheet); |
| |
| Document* document = sheet->ownerDocument(); |
| if (!document) |
| return 0; |
| Node* ownerNode = sheet->ownerNode(); |
| if (!ownerNode || !ownerNode->isHTMLElement() || !isHTMLStyleElement(ownerNode)) |
| return 0; |
| |
| HTMLStyleElement* styleElement = toHTMLStyleElement(ownerNode); |
| if (!styleElement->scoped()) |
| return styleElement->isInShadowTree() ? styleElement->containingShadowRoot() : 0; |
| |
| ContainerNode* parent = styleElement->parentNode(); |
| if (!parent) |
| return 0; |
| |
| return (parent->isElementNode() || parent->isShadowRoot()) ? parent : 0; |
| } |
| |
| inline RuleSet* StyleScopeResolver::ruleSetFor(const ContainerNode* scope) const |
| { |
| if (!scope->hasScopedHTMLStyleChild()) |
| return 0; |
| ScopedRuleSetMap::const_iterator it = m_authorStyles.find(scope); |
| return it != m_authorStyles.end() ? it->value.get() : 0; |
| } |
| |
| RuleSet* StyleScopeResolver::ensureRuleSetFor(const ContainerNode* scope) |
| { |
| ScopedRuleSetMap::AddResult addResult = m_authorStyles.add(scope, nullptr); |
| if (addResult.isNewEntry) |
| addResult.iterator->value = RuleSet::create(); |
| return addResult.iterator->value.get(); |
| } |
| |
| void StyleScopeResolver::setupStack(const ContainerNode* parent) |
| { |
| // The scoping element stack shouldn't be used if <style scoped> isn't used anywhere. |
| ASSERT(!m_authorStyles.isEmpty()); |
| |
| m_stack.shrink(0); |
| int authorStyleBoundsIndex = 0; |
| for (const ContainerNode* scope = parent; scope; scope = scope->parentOrShadowHostNode()) { |
| RuleSet* ruleSet = ruleSetFor(scope); |
| if (ruleSet) |
| m_stack.append(StackFrame(scope, authorStyleBoundsIndex, ruleSet)); |
| if (scope->isShadowRoot() && !toShadowRoot(scope)->applyAuthorStyles()) |
| --authorStyleBoundsIndex; |
| } |
| |
| m_stack.reverse(); |
| m_stackParent = parent; |
| m_stackParentBoundsIndex = 0; |
| } |
| |
| void StyleScopeResolver::push(const ContainerNode* scope, const ContainerNode* scopeParent) |
| { |
| // Shortcut: Don't bother with the scoping element stack if <style scoped> isn't used anywhere. |
| if (m_authorStyles.isEmpty()) { |
| ASSERT(!m_stackParent); |
| ASSERT(m_stack.isEmpty()); |
| return; |
| } |
| |
| // In some wacky cases during style resolve we may get invoked for random elements. |
| // Recreate the whole scoping element stack in such cases. |
| if (!stackIsConsistent(scopeParent)) { |
| setupStack(scope); |
| return; |
| } |
| |
| if (scope->isShadowRoot() && !toShadowRoot(scope)->applyAuthorStyles()) |
| ++m_stackParentBoundsIndex; |
| // Otherwise just push the parent onto the stack. |
| RuleSet* ruleSet = ruleSetFor(scope); |
| if (ruleSet) |
| m_stack.append(StackFrame(scope, m_stackParentBoundsIndex, ruleSet)); |
| m_stackParent = scope; |
| } |
| |
| void StyleScopeResolver::pop(const ContainerNode* scope) |
| { |
| // Only bother to update the scoping element stack if it is consistent. |
| if (stackIsConsistent(scope)) { |
| if (!m_stack.isEmpty() && m_stack.last().m_scope == scope) |
| m_stack.removeLast(); |
| if (scope->isShadowRoot() && !toShadowRoot(scope)->applyAuthorStyles()) |
| --m_stackParentBoundsIndex; |
| m_stackParent = scope->parentOrShadowHostNode(); |
| } |
| } |
| |
| void StyleScopeResolver::collectFeaturesTo(RuleFeatureSet& features) |
| { |
| for (ScopedRuleSetMap::iterator it = m_authorStyles.begin(); it != m_authorStyles.end(); ++it) |
| features.add(it->value->features()); |
| #if ENABLE(SHADOW_DOM) |
| for (ScopedRuleSetMap::iterator it = m_atHostRules.begin(); it != m_atHostRules.end(); ++it) |
| features.add(it->value->features()); |
| #endif |
| } |
| |
| inline RuleSet* StyleScopeResolver::ensureAtHostRuleSetFor(const ShadowRoot* shadowRoot) |
| { |
| ScopedRuleSetMap::AddResult addResult = m_atHostRules.add(shadowRoot, nullptr); |
| if (addResult.isNewEntry) |
| addResult.iterator->value = RuleSet::create(); |
| return addResult.iterator->value.get(); |
| } |
| |
| inline RuleSet* StyleScopeResolver::atHostRuleSetFor(const ShadowRoot* shadowRoot) const |
| { |
| ScopedRuleSetMap::const_iterator it = m_atHostRules.find(shadowRoot); |
| return it != m_atHostRules.end() ? it->value.get() : 0; |
| } |
| |
| #if ENABLE(SHADOW_DOM) |
| void StyleScopeResolver::addHostRule(StyleRuleHost* hostRule, bool hasDocumentSecurityOrigin, const ContainerNode* scope) |
| { |
| if (!scope || !scope->isInShadowTree()) |
| return; |
| |
| ShadowRoot* shadowRoot = scope->containingShadowRoot(); |
| if (!shadowRoot || !shadowRoot->host()) |
| return; |
| |
| RuleSet* rule = ensureAtHostRuleSetFor(shadowRoot); |
| |
| const Vector<RefPtr<StyleRuleBase> >& childRules = hostRule->childRules(); |
| AddRuleFlags addRuleFlags = hasDocumentSecurityOrigin ? RuleHasDocumentSecurityOrigin : RuleHasNoSpecialState; |
| addRuleFlags = static_cast<AddRuleFlags>(addRuleFlags | RuleCanUseFastCheckSelector); |
| for (unsigned i = 0; i < childRules.size(); ++i) { |
| StyleRuleBase* hostStylingRule = childRules[i].get(); |
| if (hostStylingRule->isStyleRule()) |
| rule->addStyleRule(static_cast<StyleRule*>(hostStylingRule), addRuleFlags); |
| } |
| } |
| #endif |
| |
| bool StyleScopeResolver::styleSharingCandidateMatchesHostRules(const Element* element) |
| { |
| if (m_atHostRules.isEmpty()) |
| return false; |
| |
| // FIXME(99827): https://bugs.webkit.org/show_bug.cgi?id=99827 |
| // add a new flag to ElementShadow and cache whether any@host @-rules are |
| // applied to the element or not. So we can avoid always traversing |
| // shadow roots. |
| if (ShadowRoot* shadowRoot = element->shadowRoot()) { |
| if (atHostRuleSetFor(shadowRoot)) |
| return true; |
| } |
| return false; |
| } |
| |
| void StyleScopeResolver::matchHostRules(const Element* element, Vector<RuleSet*>& matchedRules) |
| { |
| if (m_atHostRules.isEmpty()) |
| return; |
| |
| // FIXME(99827): https://bugs.webkit.org/show_bug.cgi?id=99827 |
| // add a new flag to ElementShadow and cache whether any @host @-rules are |
| // applied to the element or not. So we can quickly exit this method |
| // by using the flag. |
| if (ShadowRoot* shadowRoot = element->shadowRoot()) { |
| if (RuleSet* ruleSet = atHostRuleSetFor(shadowRoot)) |
| matchedRules.append(ruleSet); |
| } |
| } |
| |
| } |
| |
| #endif // ENABLE(SHADOW_DOM) |