| // Copyright 2020 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/counter_style_map.h" |
| |
| #include "third_party/blink/renderer/core/css/css_default_style_sheets.h" |
| #include "third_party/blink/renderer/core/css/resolver/scoped_style_resolver.h" |
| #include "third_party/blink/renderer/core/css/rule_set.h" |
| #include "third_party/blink/renderer/core/css/style_engine.h" |
| #include "third_party/blink/renderer/core/css/style_rule_counter_style.h" |
| #include "third_party/blink/renderer/core/dom/document.h" |
| #include "third_party/blink/renderer/platform/instrumentation/tracing/trace_event.h" |
| |
| namespace blink { |
| |
| namespace { |
| |
| const char* predefined_symbol_markers[] = { |
| "disc", "square", "circle", "disclosure-open", "disclosure-closed"}; |
| |
| CounterStyleMap* CreateUACounterStyleMap() { |
| CounterStyleMap* map = |
| MakeGarbageCollected<CounterStyleMap>(nullptr, nullptr); |
| map->AddCounterStyles(*CSSDefaultStyleSheets::Instance().DefaultStyle()); |
| map->SetIsPredefined(); |
| for (const char* symbol_marker : predefined_symbol_markers) { |
| map->FindCounterStyleAcrossScopes(symbol_marker) |
| ->SetIsPredefinedSymbolMarker(); |
| } |
| HeapHashSet<Member<CounterStyleMap>> dummy_visited; |
| map->ResolveReferences(dummy_visited); |
| return map; |
| } |
| |
| } // namespace |
| |
| void CounterStyleMap::SetIsPredefined() { |
| for (CounterStyle* counter_style : counter_styles_.Values()) |
| counter_style->SetIsPredefined(); |
| } |
| |
| // static |
| CounterStyleMap* CounterStyleMap::GetUACounterStyleMap() { |
| DEFINE_STATIC_LOCAL(Persistent<CounterStyleMap>, ua_counter_style_map, |
| (CreateUACounterStyleMap())); |
| return ua_counter_style_map; |
| } |
| |
| // static |
| CounterStyleMap* CounterStyleMap::GetUserCounterStyleMap(Document& document) { |
| return document.GetStyleEngine().GetUserCounterStyleMap(); |
| } |
| |
| // static |
| CounterStyleMap* CounterStyleMap::GetAuthorCounterStyleMap( |
| const TreeScope& scope) { |
| if (!scope.GetScopedStyleResolver()) |
| return nullptr; |
| return scope.GetScopedStyleResolver()->GetCounterStyleMap(); |
| } |
| |
| // static |
| CounterStyleMap* CounterStyleMap::CreateUserCounterStyleMap( |
| Document& document) { |
| return MakeGarbageCollected<CounterStyleMap>(&document, nullptr); |
| } |
| |
| // static |
| CounterStyleMap* CounterStyleMap::CreateAuthorCounterStyleMap( |
| TreeScope& tree_scope) { |
| return MakeGarbageCollected<CounterStyleMap>(&tree_scope.GetDocument(), |
| &tree_scope); |
| } |
| |
| CounterStyleMap::CounterStyleMap(Document* document, TreeScope* tree_scope) |
| : owner_document_(document), tree_scope_(tree_scope) { |
| #if DCHECK_IS_ON() |
| if (tree_scope) |
| DCHECK_EQ(document, &tree_scope->GetDocument()); |
| #endif |
| } |
| |
| void CounterStyleMap::AddCounterStyles(const RuleSet& rule_set) { |
| if (!rule_set.CounterStyleRules().size()) |
| return; |
| |
| for (StyleRuleCounterStyle* rule : rule_set.CounterStyleRules()) { |
| CounterStyle* counter_style = CounterStyle::Create(*rule); |
| if (!counter_style) { |
| DCHECK(owner_document_) << "Predefined counter style " << rule->GetName() |
| << " has invalid symbols"; |
| continue; |
| } |
| AtomicString name = rule->GetName(); |
| if (CounterStyle* replaced = counter_styles_.at(name)) |
| replaced->SetIsDirty(); |
| counter_styles_.Set(rule->GetName(), counter_style); |
| } |
| |
| if (owner_document_) |
| owner_document_->GetStyleEngine().MarkCounterStylesNeedUpdate(); |
| } |
| |
| CounterStyleMap* CounterStyleMap::GetAncestorMap() const { |
| if (tree_scope_) { |
| // Resursively walk up to parent scope to find an author CounterStyleMap. |
| for (TreeScope* scope = tree_scope_->ParentTreeScope(); scope; |
| scope = scope->ParentTreeScope()) { |
| if (CounterStyleMap* map = GetAuthorCounterStyleMap(*scope)) |
| return map; |
| } |
| |
| // Fallback to user counter style map |
| if (CounterStyleMap* user_map = GetUserCounterStyleMap(*owner_document_)) |
| return user_map; |
| } |
| |
| // Author and user counter style maps fall back to UA |
| if (owner_document_) |
| return GetUACounterStyleMap(); |
| |
| // UA counter style map doesn't have any fallback |
| return nullptr; |
| } |
| |
| CounterStyle* CounterStyleMap::FindCounterStyleAcrossScopes( |
| const AtomicString& name) const { |
| if (CounterStyle* style = counter_styles_.at(name)) |
| return style; |
| |
| if (CounterStyleMap* ancestor_map = GetAncestorMap()) |
| return ancestor_map->FindCounterStyleAcrossScopes(name); |
| |
| return nullptr; |
| } |
| |
| void CounterStyleMap::ResolveExtendsFor(CounterStyle& counter_style) { |
| DCHECK(counter_style.HasUnresolvedExtends()); |
| |
| HeapVector<Member<CounterStyle>, 2> extends_chain; |
| HeapHashSet<Member<CounterStyle>> unresolved_styles; |
| extends_chain.push_back(&counter_style); |
| do { |
| unresolved_styles.insert(extends_chain.back()); |
| AtomicString extends_name = extends_chain.back()->GetExtendsName(); |
| extends_chain.push_back(FindCounterStyleAcrossScopes(extends_name)); |
| } while (extends_chain.back() && |
| extends_chain.back()->HasUnresolvedExtends() && |
| !unresolved_styles.Contains(extends_chain.back())); |
| |
| // If one or more @counter-style rules form a cycle with their extends values, |
| // all of the counter styles participating in the cycle must be treated as if |
| // they were extending the 'decimal' counter style instead. |
| if (extends_chain.back() && extends_chain.back()->HasUnresolvedExtends()) { |
| CounterStyle* cycle_start = extends_chain.back(); |
| do { |
| extends_chain.back()->ResolveExtends(CounterStyle::GetDecimal()); |
| extends_chain.pop_back(); |
| } while (extends_chain.back() != cycle_start); |
| } |
| |
| CounterStyle* next = extends_chain.back(); |
| while (extends_chain.size() > 1u) { |
| extends_chain.pop_back(); |
| if (next) { |
| extends_chain.back()->ResolveExtends(*next); |
| } else { |
| extends_chain.back()->ResolveExtends(CounterStyle::GetDecimal()); |
| extends_chain.back()->SetHasInexistentReferences(); |
| } |
| |
| next = extends_chain.back(); |
| } |
| } |
| |
| void CounterStyleMap::ResolveFallbackFor(CounterStyle& counter_style) { |
| DCHECK(counter_style.HasUnresolvedFallback()); |
| AtomicString fallback_name = counter_style.GetFallbackName(); |
| CounterStyle* fallback_style = FindCounterStyleAcrossScopes(fallback_name); |
| if (fallback_style) { |
| counter_style.ResolveFallback(*fallback_style); |
| } else { |
| counter_style.ResolveFallback(CounterStyle::GetDecimal()); |
| counter_style.SetHasInexistentReferences(); |
| } |
| } |
| |
| void CounterStyleMap::ResolveReferences( |
| HeapHashSet<Member<CounterStyleMap>>& visited_maps) { |
| if (visited_maps.Contains(this)) |
| return; |
| visited_maps.insert(this); |
| |
| // References in ancestor scopes must be resolved first. |
| if (CounterStyleMap* ancestor_map = GetAncestorMap()) |
| ancestor_map->ResolveReferences(visited_maps); |
| |
| for (CounterStyle* counter_style : counter_styles_.Values()) { |
| if (counter_style->HasUnresolvedExtends()) |
| ResolveExtendsFor(*counter_style); |
| if (counter_style->HasUnresolvedFallback()) |
| ResolveFallbackFor(*counter_style); |
| } |
| } |
| |
| void CounterStyleMap::MarkDirtyCounterStyles( |
| HeapHashSet<Member<CounterStyle>>& visited_counter_styles) { |
| for (CounterStyle* counter_style : counter_styles_.Values()) |
| counter_style->TraverseAndMarkDirtyIfNeeded(visited_counter_styles); |
| |
| // Replace dirty CounterStyles by clean ones with unresolved references. |
| for (Member<CounterStyle>& counter_style_ref : counter_styles_.Values()) { |
| if (counter_style_ref->IsDirty()) { |
| CounterStyle* clean_style = |
| MakeGarbageCollected<CounterStyle>(counter_style_ref->GetStyleRule()); |
| counter_style_ref = clean_style; |
| } |
| } |
| } |
| |
| // static |
| void CounterStyleMap::MarkAllDirtyCounterStyles( |
| Document& document, |
| const HeapHashSet<Member<TreeScope>>& active_tree_scopes) { |
| // Traverse all CounterStyle objects in the document to mark dirtiness. |
| // We assume that there are not too many CounterStyle objects, so this won't |
| // be a performance bottleneck. |
| TRACE_EVENT0("blink", "CounterStyleMap::MarkAllDirtyCounterStyles"); |
| |
| HeapHashSet<Member<CounterStyle>> visited_counter_styles; |
| |
| if (CounterStyleMap* user_map = GetUserCounterStyleMap(document)) |
| user_map->MarkDirtyCounterStyles(visited_counter_styles); |
| |
| if (CounterStyleMap* document_map = GetAuthorCounterStyleMap(document)) |
| document_map->MarkDirtyCounterStyles(visited_counter_styles); |
| |
| for (const TreeScope* scope : active_tree_scopes) { |
| if (CounterStyleMap* scoped_map = GetAuthorCounterStyleMap(*scope)) |
| scoped_map->MarkDirtyCounterStyles(visited_counter_styles); |
| } |
| } |
| |
| // static |
| void CounterStyleMap::ResolveAllReferences( |
| Document& document, |
| const HeapHashSet<Member<TreeScope>>& active_tree_scopes) { |
| // Traverse all counter style maps to find and update CounterStyles that are |
| // dirty or have unresolved references. We assume there are not too many |
| // CounterStyles, so that this won't be a performance bottleneck. |
| TRACE_EVENT0("blink", "CounterStyleMap::ResolveAllReferences"); |
| |
| HeapHashSet<Member<CounterStyleMap>> visited_maps; |
| visited_maps.insert(GetUACounterStyleMap()); |
| |
| if (CounterStyleMap* user_map = GetUserCounterStyleMap(document)) |
| user_map->ResolveReferences(visited_maps); |
| |
| if (CounterStyleMap* document_map = GetAuthorCounterStyleMap(document)) |
| document_map->ResolveReferences(visited_maps); |
| |
| for (const TreeScope* scope : active_tree_scopes) { |
| if (CounterStyleMap* scoped_map = GetAuthorCounterStyleMap(*scope)) { |
| scoped_map->ResolveReferences(visited_maps); |
| |
| #if DCHECK_IS_ON() |
| for (CounterStyle* counter_style : scoped_map->counter_styles_.Values()) { |
| DCHECK(!counter_style->IsDirty()); |
| DCHECK(!counter_style->HasUnresolvedExtends()); |
| DCHECK(!counter_style->HasUnresolvedFallback()); |
| } |
| #endif |
| } |
| } |
| } |
| |
| void CounterStyleMap::Dispose() { |
| if (!counter_styles_.size()) |
| return; |
| |
| for (CounterStyle* counter_style : counter_styles_.Values()) |
| counter_style->SetIsDirty(); |
| counter_styles_.clear(); |
| |
| if (owner_document_) |
| owner_document_->GetStyleEngine().MarkCounterStylesNeedUpdate(); |
| } |
| |
| void CounterStyleMap::Trace(Visitor* visitor) const { |
| visitor->Trace(owner_document_); |
| visitor->Trace(tree_scope_); |
| visitor->Trace(counter_styles_); |
| } |
| |
| } // namespace blink |