| // Copyright 2014 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "third_party/blink/renderer/core/css/invalidation/style_invalidator.h" |
| |
| #include "third_party/blink/renderer/core/css/invalidation/invalidation_set.h" |
| #include "third_party/blink/renderer/core/css/style_change_reason.h" |
| #include "third_party/blink/renderer/core/dom/document.h" |
| #include "third_party/blink/renderer/core/dom/element.h" |
| #include "third_party/blink/renderer/core/dom/element_traversal.h" |
| #include "third_party/blink/renderer/core/dom/shadow_root.h" |
| #include "third_party/blink/renderer/core/html/html_slot_element.h" |
| #include "third_party/blink/renderer/core/inspector/inspector_trace_events.h" |
| #include "third_party/blink/renderer/core/layout/layout_object.h" |
| |
| namespace blink { |
| |
| // StyleInvalidator methods are super sensitive to performance benchmarks. |
| // We easily get 1% regression per additional if statement on recursive |
| // invalidate methods. |
| // To minimize performance impact, we wrap trace events with a lookup of |
| // cached flag. The cached flag is made "static const" and is not shared |
| // with InvalidationSet to avoid additional GOT lookup cost. |
| static const unsigned char* g_style_invalidator_tracing_enabled = nullptr; |
| |
| #define TRACE_STYLE_INVALIDATOR_INVALIDATION_IF_ENABLED(element, reason) \ |
| if (UNLIKELY(*g_style_invalidator_tracing_enabled)) \ |
| TRACE_STYLE_INVALIDATOR_INVALIDATION(element, reason); |
| |
| void StyleInvalidator::Invalidate(Document& document) { |
| SiblingData sibling_data; |
| if (UNLIKELY(document.NeedsStyleInvalidation())) |
| PushInvalidationSetsForContainerNode(document, sibling_data); |
| if (Element* document_element = document.documentElement()) |
| Invalidate(*document_element, sibling_data); |
| document.ClearChildNeedsStyleInvalidation(); |
| document.ClearNeedsStyleInvalidation(); |
| pending_invalidation_map_.clear(); |
| } |
| |
| StyleInvalidator::StyleInvalidator( |
| PendingInvalidationMap& pending_invalidation_map) |
| : pending_invalidation_map_(pending_invalidation_map) { |
| g_style_invalidator_tracing_enabled = |
| TRACE_EVENT_API_GET_CATEGORY_GROUP_ENABLED( |
| TRACE_DISABLED_BY_DEFAULT("devtools.timeline.invalidationTracking")); |
| } |
| |
| StyleInvalidator::~StyleInvalidator() = default; |
| |
| void StyleInvalidator::PushInvalidationSet( |
| const InvalidationSet& invalidation_set) { |
| DCHECK(!invalidation_flags_.WholeSubtreeInvalid()); |
| DCHECK(!invalidation_set.WholeSubtreeInvalid()); |
| DCHECK(!invalidation_set.IsEmpty()); |
| if (invalidation_set.CustomPseudoInvalid()) |
| invalidation_flags_.SetInvalidateCustomPseudo(true); |
| if (invalidation_set.TreeBoundaryCrossing()) |
| invalidation_flags_.SetTreeBoundaryCrossing(true); |
| if (invalidation_set.InsertionPointCrossing()) |
| invalidation_flags_.SetInsertionPointCrossing(true); |
| if (invalidation_set.InvalidatesSlotted()) |
| invalidation_flags_.SetInvalidatesSlotted(true); |
| if (invalidation_set.InvalidatesParts()) |
| invalidation_flags_.SetInvalidatesParts(true); |
| invalidation_sets_.push_back(&invalidation_set); |
| } |
| |
| ALWAYS_INLINE bool StyleInvalidator::MatchesCurrentInvalidationSets( |
| Element& element) const { |
| if (invalidation_flags_.InvalidateCustomPseudo() && |
| element.ShadowPseudoId() != g_null_atom) { |
| TRACE_STYLE_INVALIDATOR_INVALIDATION_IF_ENABLED(element, |
| kInvalidateCustomPseudo); |
| return true; |
| } |
| |
| if (invalidation_flags_.InsertionPointCrossing() && |
| element.IsV0InsertionPoint()) |
| return true; |
| |
| for (auto* const invalidation_set : invalidation_sets_) { |
| if (invalidation_set->InvalidatesElement(element)) |
| return true; |
| } |
| |
| return false; |
| } |
| |
| bool StyleInvalidator::MatchesCurrentInvalidationSetsAsSlotted( |
| Element& element) const { |
| DCHECK(invalidation_flags_.InvalidatesSlotted()); |
| |
| for (auto* const invalidation_set : invalidation_sets_) { |
| if (!invalidation_set->InvalidatesSlotted()) |
| continue; |
| if (invalidation_set->InvalidatesElement(element)) |
| return true; |
| } |
| return false; |
| } |
| |
| bool StyleInvalidator::MatchesCurrentInvalidationSetsAsParts( |
| Element& element) const { |
| DCHECK(invalidation_flags_.InvalidatesParts()); |
| |
| for (auto* const invalidation_set : invalidation_sets_) { |
| if (!invalidation_set->InvalidatesParts()) |
| continue; |
| if (invalidation_set->InvalidatesElement(element)) |
| return true; |
| } |
| return false; |
| } |
| |
| void StyleInvalidator::SiblingData::PushInvalidationSet( |
| const SiblingInvalidationSet& invalidation_set) { |
| unsigned invalidation_limit; |
| if (invalidation_set.MaxDirectAdjacentSelectors() == UINT_MAX) |
| invalidation_limit = UINT_MAX; |
| else |
| invalidation_limit = |
| element_index_ + invalidation_set.MaxDirectAdjacentSelectors(); |
| invalidation_entries_.push_back(Entry(&invalidation_set, invalidation_limit)); |
| } |
| |
| bool StyleInvalidator::SiblingData::MatchCurrentInvalidationSets( |
| Element& element, |
| StyleInvalidator& style_invalidator) { |
| bool this_element_needs_style_recalc = false; |
| DCHECK(!style_invalidator.WholeSubtreeInvalid()); |
| |
| unsigned index = 0; |
| while (index < invalidation_entries_.size()) { |
| if (element_index_ > invalidation_entries_[index].invalidation_limit_) { |
| // invalidation_entries_[index] only applies to earlier siblings. Remove |
| // it. |
| invalidation_entries_[index] = invalidation_entries_.back(); |
| invalidation_entries_.pop_back(); |
| continue; |
| } |
| |
| const SiblingInvalidationSet& invalidation_set = |
| *invalidation_entries_[index].invalidation_set_; |
| ++index; |
| if (!invalidation_set.InvalidatesElement(element)) |
| continue; |
| |
| if (invalidation_set.InvalidatesSelf()) |
| this_element_needs_style_recalc = true; |
| |
| if (const DescendantInvalidationSet* descendants = |
| invalidation_set.SiblingDescendants()) { |
| if (descendants->WholeSubtreeInvalid()) { |
| element.SetNeedsStyleRecalc(kSubtreeStyleChange, |
| StyleChangeReasonForTracing::Create( |
| StyleChangeReason::kStyleInvalidator)); |
| return true; |
| } |
| |
| if (!descendants->IsEmpty()) |
| style_invalidator.PushInvalidationSet(*descendants); |
| } |
| } |
| return this_element_needs_style_recalc; |
| } |
| |
| void StyleInvalidator::PushInvalidationSetsForContainerNode( |
| ContainerNode& node, |
| SiblingData& sibling_data) { |
| auto pending_invalidations_iterator = pending_invalidation_map_.find(&node); |
| DCHECK(pending_invalidations_iterator != pending_invalidation_map_.end()); |
| NodeInvalidationSets& pending_invalidations = |
| pending_invalidations_iterator->value; |
| |
| for (const auto& invalidation_set : pending_invalidations.Siblings()) { |
| CHECK(invalidation_set->IsAlive()); |
| sibling_data.PushInvalidationSet( |
| ToSiblingInvalidationSet(*invalidation_set)); |
| } |
| |
| if (node.GetStyleChangeType() >= kSubtreeStyleChange) |
| return; |
| |
| if (!pending_invalidations.Descendants().IsEmpty()) { |
| for (const auto& invalidation_set : pending_invalidations.Descendants()) { |
| CHECK(invalidation_set->IsAlive()); |
| PushInvalidationSet(*invalidation_set); |
| } |
| if (UNLIKELY(*g_style_invalidator_tracing_enabled)) { |
| TRACE_EVENT_INSTANT1( |
| TRACE_DISABLED_BY_DEFAULT("devtools.timeline.invalidationTracking"), |
| "StyleInvalidatorInvalidationTracking", TRACE_EVENT_SCOPE_THREAD, |
| "data", |
| InspectorStyleInvalidatorInvalidateEvent::InvalidationList( |
| node, pending_invalidations.Descendants())); |
| } |
| } |
| } |
| |
| ALWAYS_INLINE bool StyleInvalidator::CheckInvalidationSetsAgainstElement( |
| Element& element, |
| SiblingData& sibling_data) { |
| // We need to call both because the sibling data may invalidate the whole |
| // subtree at which point we can stop recursing. |
| bool matches_current = MatchesCurrentInvalidationSets(element); |
| bool matches_sibling = |
| UNLIKELY(!sibling_data.IsEmpty()) && |
| sibling_data.MatchCurrentInvalidationSets(element, *this); |
| return matches_current || matches_sibling; |
| } |
| |
| void StyleInvalidator::InvalidateShadowRootChildren(Element& element) { |
| if (ShadowRoot* root = element.GetShadowRoot()) { |
| if (!TreeBoundaryCrossing() && !root->ChildNeedsStyleInvalidation() && |
| !root->NeedsStyleInvalidation()) |
| return; |
| RecursionCheckpoint checkpoint(this); |
| SiblingData sibling_data; |
| if (!WholeSubtreeInvalid()) { |
| if (UNLIKELY(root->NeedsStyleInvalidation())) { |
| PushInvalidationSetsForContainerNode(*root, sibling_data); |
| } |
| } |
| for (Element* child = ElementTraversal::FirstChild(*root); child; |
| child = ElementTraversal::NextSibling(*child)) { |
| Invalidate(*child, sibling_data); |
| } |
| root->ClearChildNeedsStyleInvalidation(); |
| root->ClearNeedsStyleInvalidation(); |
| } |
| } |
| |
| void StyleInvalidator::InvalidateChildren(Element& element) { |
| SiblingData sibling_data; |
| if (UNLIKELY(!!element.GetShadowRoot())) { |
| InvalidateShadowRootChildren(element); |
| } |
| |
| for (Element* child = ElementTraversal::FirstChild(element); child; |
| child = ElementTraversal::NextSibling(*child)) { |
| Invalidate(*child, sibling_data); |
| } |
| } |
| |
| void StyleInvalidator::Invalidate(Element& element, SiblingData& sibling_data) { |
| sibling_data.Advance(); |
| RecursionCheckpoint checkpoint(this); |
| |
| if (!WholeSubtreeInvalid()) { |
| if (element.GetStyleChangeType() >= kSubtreeStyleChange) { |
| SetWholeSubtreeInvalid(); |
| } else if (CheckInvalidationSetsAgainstElement(element, sibling_data)) { |
| element.SetNeedsStyleRecalc(kLocalStyleChange, |
| StyleChangeReasonForTracing::Create( |
| StyleChangeReason::kStyleInvalidator)); |
| } |
| if (UNLIKELY(element.NeedsStyleInvalidation())) |
| PushInvalidationSetsForContainerNode(element, sibling_data); |
| } |
| |
| if (HasInvalidationSets() || element.ChildNeedsStyleInvalidation()) |
| InvalidateChildren(element); |
| |
| if (InsertionPointCrossing() && element.IsV0InsertionPoint()) |
| element.SetNeedsStyleRecalc(kSubtreeStyleChange, |
| StyleChangeReasonForTracing::Create( |
| StyleChangeReason::kStyleInvalidator)); |
| if (InvalidatesSlotted() && IsHTMLSlotElement(element)) |
| InvalidateSlotDistributedElements(ToHTMLSlotElement(element)); |
| |
| element.ClearChildNeedsStyleInvalidation(); |
| element.ClearNeedsStyleInvalidation(); |
| } |
| |
| void StyleInvalidator::InvalidateSlotDistributedElements( |
| HTMLSlotElement& slot) const { |
| for (auto& distributed_node : slot.FlattenedAssignedNodes()) { |
| if (distributed_node->NeedsStyleRecalc()) |
| continue; |
| if (!distributed_node->IsElementNode()) |
| continue; |
| if (MatchesCurrentInvalidationSetsAsSlotted(ToElement(*distributed_node))) |
| distributed_node->SetNeedsStyleRecalc( |
| kLocalStyleChange, StyleChangeReasonForTracing::Create( |
| StyleChangeReason::kStyleInvalidator)); |
| } |
| } |
| |
| } // namespace blink |