1/*
2 * Copyright (C) 1999 Lars Knoll (knoll@kde.org)
3 * Copyright (C) 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010, 2011 Apple Inc. All rights reserved.
4 * Copyright (C) 2012 Google Inc. All rights reserved.
5 *
6 * Redistribution and use in source and binary forms, with or without
7 * modification, are permitted provided that the following conditions
8 * are met:
9 * 1.  Redistributions of source code must retain the above copyright
10 *     notice, this list of conditions and the following disclaimer.
11 * 2.  Redistributions in binary form must reproduce the above copyright
12 *     notice, this list of conditions and the following disclaimer in the
13 *     documentation and/or other materials provided with the distribution.
14 *
15 * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS'' AND ANY
16 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
17 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
18 * DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS BE LIABLE FOR ANY
19 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
20 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
21 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
22 * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
23 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
24 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
25 */
26
27#include "config.h"
28#include "StyleScopeResolver.h"
29
30#if ENABLE(STYLE_SCOPED) || ENABLE(SHADOW_DOM)
31
32#include "CSSStyleRule.h"
33#include "CSSStyleSheet.h"
34#include "ContentDistributor.h"
35#include "ContextFeatures.h"
36#include "ElementShadow.h"
37#include "HTMLNames.h"
38#include "HTMLStyleElement.h"
39#include "RuleFeature.h"
40#include "RuleSet.h"
41#include "ShadowRoot.h"
42
43namespace WebCore {
44
45StyleScopeResolver::StyleScopeResolver()
46    : m_stackParent(0)
47    , m_stackParentBoundsIndex(0)
48{
49}
50
51StyleScopeResolver::~StyleScopeResolver()
52{
53}
54
55const ContainerNode* StyleScopeResolver::scopeFor(const CSSStyleSheet* sheet)
56{
57    ASSERT(sheet);
58
59    Document* document = sheet->ownerDocument();
60    if (!document)
61        return 0;
62    Node* ownerNode = sheet->ownerNode();
63    if (!ownerNode || !ownerNode->isHTMLElement() || !ownerNode->hasTagName(HTMLNames::styleTag))
64        return 0;
65
66    HTMLStyleElement* styleElement = static_cast<HTMLStyleElement*>(ownerNode);
67    if (!styleElement->scoped())
68        return styleElement->isInShadowTree() ? styleElement->containingShadowRoot() : 0;
69
70    ContainerNode* parent = styleElement->parentNode();
71    if (!parent)
72        return 0;
73
74    return (parent->isElementNode() || parent->isShadowRoot()) ? parent : 0;
75}
76
77inline RuleSet* StyleScopeResolver::ruleSetFor(const ContainerNode* scope) const
78{
79    if (!scope->hasScopedHTMLStyleChild())
80        return 0;
81    ScopedRuleSetMap::const_iterator it = m_authorStyles.find(scope);
82    return it != m_authorStyles.end() ? it->value.get() : 0;
83}
84
85RuleSet* StyleScopeResolver::ensureRuleSetFor(const ContainerNode* scope)
86{
87    ScopedRuleSetMap::AddResult addResult = m_authorStyles.add(scope, nullptr);
88    if (addResult.isNewEntry)
89        addResult.iterator->value = RuleSet::create();
90    return addResult.iterator->value.get();
91}
92
93void StyleScopeResolver::setupStack(const ContainerNode* parent)
94{
95    // The scoping element stack shouldn't be used if <style scoped> isn't used anywhere.
96    ASSERT(!m_authorStyles.isEmpty());
97
98    m_stack.shrink(0);
99    int authorStyleBoundsIndex = 0;
100    for (const ContainerNode* scope = parent; scope; scope = scope->parentOrShadowHostNode()) {
101        RuleSet* ruleSet = ruleSetFor(scope);
102        if (ruleSet)
103            m_stack.append(StackFrame(scope, authorStyleBoundsIndex, ruleSet));
104        if (scope->isShadowRoot() && !toShadowRoot(scope)->applyAuthorStyles())
105            --authorStyleBoundsIndex;
106    }
107
108    m_stack.reverse();
109    m_stackParent = parent;
110    m_stackParentBoundsIndex = 0;
111}
112
113void StyleScopeResolver::push(const ContainerNode* scope, const ContainerNode* scopeParent)
114{
115    // Shortcut: Don't bother with the scoping element stack if <style scoped> isn't used anywhere.
116    if (m_authorStyles.isEmpty()) {
117        ASSERT(!m_stackParent);
118        ASSERT(m_stack.isEmpty());
119        return;
120    }
121
122    // In some wacky cases during style resolve we may get invoked for random elements.
123    // Recreate the whole scoping element stack in such cases.
124    if (!stackIsConsistent(scopeParent)) {
125        setupStack(scope);
126        return;
127    }
128
129    if (scope->isShadowRoot() && !toShadowRoot(scope)->applyAuthorStyles())
130        ++m_stackParentBoundsIndex;
131    // Otherwise just push the parent onto the stack.
132    RuleSet* ruleSet = ruleSetFor(scope);
133    if (ruleSet)
134        m_stack.append(StackFrame(scope, m_stackParentBoundsIndex, ruleSet));
135    m_stackParent = scope;
136}
137
138void StyleScopeResolver::pop(const ContainerNode* scope)
139{
140    // Only bother to update the scoping element stack if it is consistent.
141    if (stackIsConsistent(scope)) {
142        if (!m_stack.isEmpty() && m_stack.last().m_scope == scope)
143            m_stack.removeLast();
144        if (scope->isShadowRoot() && !toShadowRoot(scope)->applyAuthorStyles())
145            --m_stackParentBoundsIndex;
146        m_stackParent = scope->parentOrShadowHostNode();
147    }
148}
149
150void StyleScopeResolver::collectFeaturesTo(RuleFeatureSet& features)
151{
152    for (ScopedRuleSetMap::iterator it = m_authorStyles.begin(); it != m_authorStyles.end(); ++it)
153        features.add(it->value->features());
154#if ENABLE(SHADOW_DOM)
155    for (ScopedRuleSetMap::iterator it = m_atHostRules.begin(); it != m_atHostRules.end(); ++it)
156        features.add(it->value->features());
157#endif
158}
159
160inline RuleSet* StyleScopeResolver::ensureAtHostRuleSetFor(const ShadowRoot* shadowRoot)
161{
162    ScopedRuleSetMap::AddResult addResult = m_atHostRules.add(shadowRoot, nullptr);
163    if (addResult.isNewEntry)
164        addResult.iterator->value = RuleSet::create();
165    return addResult.iterator->value.get();
166}
167
168inline RuleSet* StyleScopeResolver::atHostRuleSetFor(const ShadowRoot* shadowRoot) const
169{
170    ScopedRuleSetMap::const_iterator it = m_atHostRules.find(shadowRoot);
171    return it != m_atHostRules.end() ? it->value.get() : 0;
172}
173
174#if ENABLE(SHADOW_DOM)
175void StyleScopeResolver::addHostRule(StyleRuleHost* hostRule, bool hasDocumentSecurityOrigin, const ContainerNode* scope)
176{
177    if (!scope || !scope->isInShadowTree())
178        return;
179
180    ShadowRoot* shadowRoot = scope->containingShadowRoot();
181    if (!shadowRoot || !shadowRoot->host())
182        return;
183
184    RuleSet* rule = ensureAtHostRuleSetFor(shadowRoot);
185
186    const Vector<RefPtr<StyleRuleBase> >& childRules = hostRule->childRules();
187    AddRuleFlags addRuleFlags = hasDocumentSecurityOrigin ? RuleHasDocumentSecurityOrigin : RuleHasNoSpecialState;
188    addRuleFlags = static_cast<AddRuleFlags>(addRuleFlags | RuleCanUseFastCheckSelector);
189    for (unsigned i = 0; i < childRules.size(); ++i) {
190        StyleRuleBase* hostStylingRule = childRules[i].get();
191        if (hostStylingRule->isStyleRule())
192            rule->addStyleRule(static_cast<StyleRule*>(hostStylingRule), addRuleFlags);
193    }
194}
195#endif
196
197bool StyleScopeResolver::styleSharingCandidateMatchesHostRules(const Element* element)
198{
199    if (m_atHostRules.isEmpty())
200        return false;
201
202    ElementShadow* shadow = element->shadow();
203    if (!shadow)
204        return false;
205
206    // FIXME(99827): https://bugs.webkit.org/show_bug.cgi?id=99827
207    // add a new flag to ElementShadow and cache whether any@host @-rules are
208    // applied to the element or not. So we can avoid always traversing
209    // shadow roots.
210    if (ShadowRoot* shadowRoot = shadow->shadowRoot()) {
211        if (atHostRuleSetFor(shadowRoot))
212            return true;
213    }
214    return false;
215}
216
217void StyleScopeResolver::matchHostRules(const Element* element, Vector<RuleSet*>& matchedRules)
218{
219    if (m_atHostRules.isEmpty())
220        return;
221
222    ElementShadow* shadow = element->shadow();
223    if (!shadow)
224        return;
225
226    // FIXME(99827): https://bugs.webkit.org/show_bug.cgi?id=99827
227    // add a new flag to ElementShadow and cache whether any @host @-rules are
228    // applied to the element or not. So we can quickly exit this method
229    // by using the flag.
230    if (ShadowRoot* shadowRoot = shadow->shadowRoot()) {
231        if (RuleSet* ruleSet = atHostRuleSetFor(shadowRoot))
232            matchedRules.append(ruleSet);
233    }
234}
235
236}
237
238#endif // ENABLE(STYLE_SCOPED)
239