| /* |
| * 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 "third_party/blink/renderer/core/css/resolver/scoped_style_resolver.h" |
| |
| #include "third_party/blink/renderer/core/animation/document_timeline.h" |
| #include "third_party/blink/renderer/core/css/css_font_selector.h" |
| #include "third_party/blink/renderer/core/css/css_style_sheet.h" |
| #include "third_party/blink/renderer/core/css/font_face.h" |
| #include "third_party/blink/renderer/core/css/page_rule_collector.h" |
| #include "third_party/blink/renderer/core/css/part_names.h" |
| #include "third_party/blink/renderer/core/css/resolver/match_request.h" |
| #include "third_party/blink/renderer/core/css/rule_feature_set.h" |
| #include "third_party/blink/renderer/core/css/style_change_reason.h" |
| #include "third_party/blink/renderer/core/css/style_engine.h" |
| #include "third_party/blink/renderer/core/css/style_rule.h" |
| #include "third_party/blink/renderer/core/css/style_sheet_contents.h" |
| #include "third_party/blink/renderer/core/dom/document.h" |
| #include "third_party/blink/renderer/core/dom/shadow_root.h" |
| #include "third_party/blink/renderer/core/html/html_style_element.h" |
| #include "third_party/blink/renderer/core/html_names.h" |
| #include "third_party/blink/renderer/core/svg/svg_style_element.h" |
| |
| namespace blink { |
| |
| ScopedStyleResolver* ScopedStyleResolver::Parent() const { |
| for (TreeScope* scope = GetTreeScope().ParentTreeScope(); scope; |
| scope = scope->ParentTreeScope()) { |
| if (ScopedStyleResolver* resolver = scope->GetScopedStyleResolver()) |
| return resolver; |
| } |
| return nullptr; |
| } |
| |
| void ScopedStyleResolver::AddKeyframeRules(const RuleSet& rule_set) { |
| const HeapVector<Member<StyleRuleKeyframes>> keyframes_rules = |
| rule_set.KeyframesRules(); |
| for (auto rule : keyframes_rules) |
| AddKeyframeStyle(rule); |
| } |
| |
| void ScopedStyleResolver::AddFontFaceRules(const RuleSet& rule_set) { |
| // FIXME(BUG 72461): We don't add @font-face rules of scoped style sheets for |
| // the moment. |
| if (!GetTreeScope().RootNode().IsDocumentNode()) |
| return; |
| |
| Document& document = GetTreeScope().GetDocument(); |
| CSSFontSelector* css_font_selector = |
| document.GetStyleEngine().GetFontSelector(); |
| const HeapVector<Member<StyleRuleFontFace>> font_face_rules = |
| rule_set.FontFaceRules(); |
| for (auto& font_face_rule : font_face_rules) { |
| if (FontFace* font_face = FontFace::Create(&document, font_face_rule)) |
| css_font_selector->GetFontFaceCache()->Add(font_face_rule, font_face); |
| } |
| if (font_face_rules.size() && document.GetStyleResolver()) |
| document.GetStyleResolver()->InvalidateMatchedPropertiesCache(); |
| |
| for (const auto& rule : rule_set.FontFeatureValuesRules()) |
| document.GetStyleEngine().AddDefaultFontDisplay(rule); |
| } |
| |
| void ScopedStyleResolver::AppendActiveStyleSheets( |
| unsigned index, |
| const ActiveStyleSheetVector& active_sheets) { |
| for (auto* active_iterator = active_sheets.begin() + index; |
| active_iterator != active_sheets.end(); active_iterator++) { |
| CSSStyleSheet* sheet = active_iterator->first; |
| viewport_dependent_media_query_results_.AppendVector( |
| sheet->ViewportDependentMediaQueryResults()); |
| device_dependent_media_query_results_.AppendVector( |
| sheet->DeviceDependentMediaQueryResults()); |
| if (!active_iterator->second) |
| continue; |
| const RuleSet& rule_set = *active_iterator->second; |
| author_style_sheets_.push_back(sheet); |
| AddKeyframeRules(rule_set); |
| AddFontFaceRules(rule_set); |
| AddTreeBoundaryCrossingRules(rule_set, sheet, index); |
| AddSlottedRules(rule_set, sheet, index++); |
| } |
| } |
| |
| void ScopedStyleResolver::CollectFeaturesTo( |
| RuleFeatureSet& features, |
| HeapHashSet<Member<const StyleSheetContents>>& |
| visited_shared_style_sheet_contents) const { |
| features.ViewportDependentMediaQueryResults().AppendVector( |
| viewport_dependent_media_query_results_); |
| features.DeviceDependentMediaQueryResults().AppendVector( |
| device_dependent_media_query_results_); |
| |
| for (auto sheet : author_style_sheets_) { |
| DCHECK(sheet->ownerNode() || sheet->IsConstructed()); |
| StyleSheetContents* contents = sheet->Contents(); |
| if (contents->HasOneClient() || |
| visited_shared_style_sheet_contents.insert(contents).is_new_entry) |
| features.Add(contents->GetRuleSet().Features()); |
| } |
| |
| if (tree_boundary_crossing_rule_set_) { |
| for (const auto& rules : *tree_boundary_crossing_rule_set_) |
| features.Add(rules->rule_set_->Features()); |
| } |
| if (slotted_rule_set_) { |
| for (const auto& rules : *slotted_rule_set_) |
| features.Add(rules->rule_set_->Features()); |
| } |
| } |
| |
| void ScopedStyleResolver::ResetAuthorStyle() { |
| author_style_sheets_.clear(); |
| viewport_dependent_media_query_results_.clear(); |
| device_dependent_media_query_results_.clear(); |
| keyframes_rule_map_.clear(); |
| tree_boundary_crossing_rule_set_ = nullptr; |
| slotted_rule_set_ = nullptr; |
| has_deep_or_shadow_selector_ = false; |
| needs_append_all_sheets_ = false; |
| } |
| |
| StyleRuleKeyframes* ScopedStyleResolver::KeyframeStylesForAnimation( |
| const StringImpl* animation_name) { |
| if (keyframes_rule_map_.IsEmpty()) |
| return nullptr; |
| |
| KeyframesRuleMap::iterator it = keyframes_rule_map_.find(animation_name); |
| if (it == keyframes_rule_map_.end()) |
| return nullptr; |
| |
| return it->value.Get(); |
| } |
| |
| void ScopedStyleResolver::AddKeyframeStyle(StyleRuleKeyframes* rule) { |
| AtomicString s(rule->GetName()); |
| |
| if (rule->IsVendorPrefixed()) { |
| KeyframesRuleMap::iterator it = keyframes_rule_map_.find(s.Impl()); |
| if (it == keyframes_rule_map_.end()) |
| keyframes_rule_map_.Set(s.Impl(), rule); |
| else if (it->value->IsVendorPrefixed()) |
| keyframes_rule_map_.Set(s.Impl(), rule); |
| } else { |
| keyframes_rule_map_.Set(s.Impl(), rule); |
| } |
| } |
| |
| ContainerNode& ScopedStyleResolver::InvalidationRootForTreeScope( |
| const TreeScope& tree_scope) { |
| if (tree_scope.RootNode() == tree_scope.GetDocument()) |
| return tree_scope.GetDocument(); |
| return ToShadowRoot(tree_scope.RootNode()).host(); |
| } |
| |
| void ScopedStyleResolver::KeyframesRulesAdded(const TreeScope& tree_scope) { |
| // Called when @keyframes rules are about to be added/removed from a |
| // TreeScope. @keyframes rules may apply to animations on elements in the |
| // same TreeScope as the stylesheet, or the host element in the parent |
| // TreeScope if the TreeScope is a shadow tree. |
| |
| ScopedStyleResolver* resolver = tree_scope.GetScopedStyleResolver(); |
| ScopedStyleResolver* parent_resolver = |
| tree_scope.ParentTreeScope() |
| ? tree_scope.ParentTreeScope()->GetScopedStyleResolver() |
| : nullptr; |
| |
| bool had_unresolved_keyframes = false; |
| if (resolver && resolver->has_unresolved_keyframes_rule_) { |
| resolver->has_unresolved_keyframes_rule_ = false; |
| had_unresolved_keyframes = true; |
| } |
| if (parent_resolver && parent_resolver->has_unresolved_keyframes_rule_) { |
| parent_resolver->has_unresolved_keyframes_rule_ = false; |
| had_unresolved_keyframes = true; |
| } |
| |
| if (had_unresolved_keyframes) { |
| // If an animation ended up not being started because no @keyframes |
| // rules were found for the animation-name, we need to recalculate style |
| // for the elements in the scope, including its shadow host if |
| // applicable. |
| InvalidationRootForTreeScope(tree_scope) |
| .SetNeedsStyleRecalc(kSubtreeStyleChange, |
| StyleChangeReasonForTracing::Create( |
| style_change_reason::kStyleSheetChange)); |
| return; |
| } |
| |
| // If we have animations running, added/removed @keyframes may affect these. |
| tree_scope.GetDocument().Timeline().InvalidateKeyframeEffects(tree_scope); |
| } |
| |
| void ScopedStyleResolver::CollectMatchingAuthorRules( |
| ElementRuleCollector& collector, |
| ShadowV0CascadeOrder cascade_order) { |
| wtf_size_t sheet_index = 0; |
| for (auto sheet : author_style_sheets_) { |
| DCHECK(sheet->ownerNode() || sheet->IsConstructed()); |
| MatchRequest match_request(&sheet->Contents()->GetRuleSet(), |
| &scope_->RootNode(), sheet, sheet_index++); |
| collector.CollectMatchingRules(match_request, cascade_order); |
| } |
| } |
| |
| void ScopedStyleResolver::CollectMatchingShadowHostRules( |
| ElementRuleCollector& collector, |
| ShadowV0CascadeOrder cascade_order) { |
| wtf_size_t sheet_index = 0; |
| for (auto sheet : author_style_sheets_) { |
| DCHECK(sheet->ownerNode() || sheet->IsConstructed()); |
| MatchRequest match_request(&sheet->Contents()->GetRuleSet(), |
| &scope_->RootNode(), sheet, sheet_index++); |
| collector.CollectMatchingShadowHostRules(match_request, cascade_order); |
| } |
| } |
| |
| void ScopedStyleResolver::CollectMatchingSlottedRules( |
| ElementRuleCollector& collector, |
| ShadowV0CascadeOrder cascade_order) { |
| if (!slotted_rule_set_) |
| return; |
| |
| for (const auto& rules : *slotted_rule_set_) { |
| MatchRequest request(rules->rule_set_.Get(), &GetTreeScope().RootNode(), |
| rules->parent_style_sheet_, rules->parent_index_); |
| collector.CollectMatchingRules(request, cascade_order, true); |
| } |
| } |
| |
| void ScopedStyleResolver::CollectMatchingTreeBoundaryCrossingRules( |
| ElementRuleCollector& collector, |
| ShadowV0CascadeOrder cascade_order) { |
| if (!tree_boundary_crossing_rule_set_) |
| return; |
| |
| for (const auto& rules : *tree_boundary_crossing_rule_set_) { |
| MatchRequest request(rules->rule_set_.Get(), &GetTreeScope().RootNode(), |
| rules->parent_style_sheet_, rules->parent_index_); |
| collector.CollectMatchingRules(request, cascade_order, true); |
| } |
| } |
| |
| void ScopedStyleResolver::CollectMatchingPartPseudoRules( |
| ElementRuleCollector& collector, |
| PartNames& part_names, |
| ShadowV0CascadeOrder cascade_order) { |
| if (!RuntimeEnabledFeatures::CSSPartPseudoElementEnabled()) |
| return; |
| wtf_size_t sheet_index = 0; |
| for (auto sheet : author_style_sheets_) { |
| DCHECK(sheet->ownerNode() || sheet->IsConstructed()); |
| MatchRequest match_request(&sheet->Contents()->GetRuleSet(), |
| &scope_->RootNode(), sheet, sheet_index++); |
| collector.CollectMatchingPartPseudoRules(match_request, part_names, |
| cascade_order); |
| } |
| } |
| |
| void ScopedStyleResolver::MatchPageRules(PageRuleCollector& collector) { |
| // Only consider the global author RuleSet for @page rules, as per the HTML5 |
| // spec. |
| DCHECK(scope_->RootNode().IsDocumentNode()); |
| for (auto sheet : author_style_sheets_) |
| collector.MatchPageRules(&sheet->Contents()->GetRuleSet()); |
| } |
| |
| void ScopedStyleResolver::Trace(blink::Visitor* visitor) { |
| visitor->Trace(scope_); |
| visitor->Trace(author_style_sheets_); |
| visitor->Trace(keyframes_rule_map_); |
| visitor->Trace(tree_boundary_crossing_rule_set_); |
| visitor->Trace(slotted_rule_set_); |
| } |
| |
| static void AddRules(RuleSet* rule_set, |
| const HeapVector<MinimalRuleData>& rules) { |
| for (const auto& info : rules) |
| rule_set->AddRule(info.rule_, info.selector_index_, info.flags_); |
| } |
| |
| void ScopedStyleResolver::AddTreeBoundaryCrossingRules( |
| const RuleSet& author_rules, |
| CSSStyleSheet* parent_style_sheet, |
| unsigned sheet_index) { |
| bool is_document_scope = GetTreeScope().RootNode().IsDocumentNode(); |
| if (author_rules.DeepCombinatorOrShadowPseudoRules().IsEmpty() && |
| (is_document_scope || |
| (author_rules.ContentPseudoElementRules().IsEmpty()))) |
| return; |
| |
| if (!author_rules.DeepCombinatorOrShadowPseudoRules().IsEmpty()) |
| has_deep_or_shadow_selector_ = true; |
| |
| RuleSet* rule_set_for_scope = RuleSet::Create(); |
| AddRules(rule_set_for_scope, |
| author_rules.DeepCombinatorOrShadowPseudoRules()); |
| |
| if (!is_document_scope) |
| AddRules(rule_set_for_scope, author_rules.ContentPseudoElementRules()); |
| |
| if (!tree_boundary_crossing_rule_set_) { |
| tree_boundary_crossing_rule_set_ = |
| MakeGarbageCollected<CSSStyleSheetRuleSubSet>(); |
| GetTreeScope().GetDocument().GetStyleEngine().AddTreeBoundaryCrossingScope( |
| GetTreeScope()); |
| } |
| |
| tree_boundary_crossing_rule_set_->push_back( |
| RuleSubSet::Create(parent_style_sheet, sheet_index, rule_set_for_scope)); |
| } |
| |
| void ScopedStyleResolver::V0ShadowAddedOnV1Document() { |
| // See the comment in AddSlottedRules(). |
| if (!slotted_rule_set_) |
| return; |
| |
| if (!tree_boundary_crossing_rule_set_) { |
| tree_boundary_crossing_rule_set_ = |
| MakeGarbageCollected<CSSStyleSheetRuleSubSet>(); |
| GetTreeScope().GetDocument().GetStyleEngine().AddTreeBoundaryCrossingScope( |
| GetTreeScope()); |
| } |
| tree_boundary_crossing_rule_set_->AppendVector(*slotted_rule_set_); |
| slotted_rule_set_ = nullptr; |
| } |
| |
| void ScopedStyleResolver::AddSlottedRules(const RuleSet& author_rules, |
| CSSStyleSheet* parent_style_sheet, |
| unsigned sheet_index) { |
| bool is_document_scope = GetTreeScope().RootNode().IsDocumentNode(); |
| if (is_document_scope || author_rules.SlottedPseudoElementRules().IsEmpty()) |
| return; |
| |
| RuleSet* slotted_rule_set = RuleSet::Create(); |
| AddRules(slotted_rule_set, author_rules.SlottedPseudoElementRules()); |
| |
| // In case ::slotted rule is used in V0/V1 mixed document, put ::slotted |
| // rules in tree boundary crossing rules as the pure v1 fast path in |
| // StyleResolver misses them. |
| // Adding this tree scope to tree boundary crossing scopes may end up in |
| // O(N^2) where N is number of scopes which has ::slotted() rules. |
| // Once the document-wide cascade order flag downgrades from V1 to V0, |
| // these slotted rules have to be moved back to tree boundary crossing |
| // rule sets. See V0ShadowAddedOnV1Document(). |
| if (GetTreeScope().GetDocument().MayContainV0Shadow()) { |
| if (!tree_boundary_crossing_rule_set_) { |
| tree_boundary_crossing_rule_set_ = |
| MakeGarbageCollected<CSSStyleSheetRuleSubSet>(); |
| GetTreeScope() |
| .GetDocument() |
| .GetStyleEngine() |
| .AddTreeBoundaryCrossingScope(GetTreeScope()); |
| } |
| tree_boundary_crossing_rule_set_->push_back( |
| RuleSubSet::Create(parent_style_sheet, sheet_index, slotted_rule_set)); |
| return; |
| } |
| if (!slotted_rule_set_) |
| slotted_rule_set_ = MakeGarbageCollected<CSSStyleSheetRuleSubSet>(); |
| slotted_rule_set_->push_back( |
| RuleSubSet::Create(parent_style_sheet, sheet_index, slotted_rule_set)); |
| } |
| |
| void ScopedStyleResolver::RuleSubSet::Trace(blink::Visitor* visitor) { |
| visitor->Trace(parent_style_sheet_); |
| visitor->Trace(rule_set_); |
| } |
| |
| } // namespace blink |