blob: 348528e8ec77924872fbdc3ccb83a75a4ecfb218 [file] [log] [blame]
/*
* Copyright (C) 1999 Lars Knoll (knoll@kde.org)
* (C) 1999 Antti Koivisto (koivisto@kde.org)
* (C) 2001 Dirk Mueller (mueller@kde.org)
* (C) 2006 Alexey Proskuryakov (ap@webkit.org)
* Copyright (C) 2004, 2005, 2006, 2007, 2008, 2009, 2011, 2012 Apple Inc. All
* rights reserved.
* Copyright (C) 2008, 2009 Torch Mobile Inc. All rights reserved.
* (http://www.torchmobile.com/)
* Copyright (C) 2008, 2009, 2011, 2012 Google Inc. All rights reserved.
* Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies)
* Copyright (C) Research In Motion Limited 2010-2011. All rights reserved.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public License
* along with this library; see the file COPYING.LIB. If not, write to
* the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
#include "third_party/blink/renderer/core/css/style_engine.h"
#include "third_party/blink/public/mojom/frame/color_scheme.mojom-blink.h"
#include "third_party/blink/public/platform/platform.h"
#include "third_party/blink/public/platform/web_theme_engine.h"
#include "third_party/blink/renderer/core/css/container_query_evaluator.h"
#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/css_font_family_value.h"
#include "third_party/blink/renderer/core/css/css_font_selector.h"
#include "third_party/blink/renderer/core/css/css_identifier_value.h"
#include "third_party/blink/renderer/core/css/css_style_sheet.h"
#include "third_party/blink/renderer/core/css/css_uri_value.h"
#include "third_party/blink/renderer/core/css/css_value_list.h"
#include "third_party/blink/renderer/core/css/document_style_environment_variables.h"
#include "third_party/blink/renderer/core/css/document_style_sheet_collector.h"
#include "third_party/blink/renderer/core/css/font_face_cache.h"
#include "third_party/blink/renderer/core/css/invalidation/invalidation_set.h"
#include "third_party/blink/renderer/core/css/media_feature_overrides.h"
#include "third_party/blink/renderer/core/css/media_values.h"
#include "third_party/blink/renderer/core/css/property_registration.h"
#include "third_party/blink/renderer/core/css/property_registry.h"
#include "third_party/blink/renderer/core/css/resolver/scoped_style_resolver.h"
#include "third_party/blink/renderer/core/css/resolver/selector_filter_parent_scope.h"
#include "third_party/blink/renderer/core/css/resolver/style_rule_usage_tracker.h"
#include "third_party/blink/renderer/core/css/resolver/viewport_style_resolver.h"
#include "third_party/blink/renderer/core/css/shadow_tree_style_sheet_collection.h"
#include "third_party/blink/renderer/core/css/style_change_reason.h"
#include "third_party/blink/renderer/core/css/style_environment_variables.h"
#include "third_party/blink/renderer/core/css/style_sheet_contents.h"
#include "third_party/blink/renderer/core/css/vision_deficiency.h"
#include "third_party/blink/renderer/core/display_lock/display_lock_utilities.h"
#include "third_party/blink/renderer/core/dom/document_lifecycle.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/flat_tree_traversal.h"
#include "third_party/blink/renderer/core/dom/layout_tree_builder_traversal.h"
#include "third_party/blink/renderer/core/dom/node_computed_style.h"
#include "third_party/blink/renderer/core/dom/nth_index_cache.h"
#include "third_party/blink/renderer/core/dom/processing_instruction.h"
#include "third_party/blink/renderer/core/dom/shadow_root.h"
#include "third_party/blink/renderer/core/dom/text.h"
#include "third_party/blink/renderer/core/frame/settings.h"
#include "third_party/blink/renderer/core/html/html_body_element.h"
#include "third_party/blink/renderer/core/html/html_html_element.h"
#include "third_party/blink/renderer/core/html/html_iframe_element.h"
#include "third_party/blink/renderer/core/html/html_link_element.h"
#include "third_party/blink/renderer/core/html/html_slot_element.h"
#include "third_party/blink/renderer/core/html/imports/html_imports_controller.h"
#include "third_party/blink/renderer/core/html_names.h"
#include "third_party/blink/renderer/core/layout/geometry/logical_size.h"
#include "third_party/blink/renderer/core/layout/geometry/physical_size.h"
#include "third_party/blink/renderer/core/layout/layout_object.h"
#include "third_party/blink/renderer/core/layout/layout_theme.h"
#include "third_party/blink/renderer/core/layout/layout_view.h"
#include "third_party/blink/renderer/core/page/page.h"
#include "third_party/blink/renderer/core/page/page_popup_controller.h"
#include "third_party/blink/renderer/core/paint/paint_layer.h"
#include "third_party/blink/renderer/core/probe/core_probes.h"
#include "third_party/blink/renderer/core/style/computed_style.h"
#include "third_party/blink/renderer/core/style/filter_operations.h"
#include "third_party/blink/renderer/core/style/style_initial_data.h"
#include "third_party/blink/renderer/core/svg/svg_resource.h"
#include "third_party/blink/renderer/core/svg/svg_style_element.h"
#include "third_party/blink/renderer/platform/fonts/font_cache.h"
#include "third_party/blink/renderer/platform/fonts/font_selector.h"
#include "third_party/blink/renderer/platform/heap/heap.h"
#include "third_party/blink/renderer/platform/instrumentation/histogram.h"
#include "third_party/blink/renderer/platform/instrumentation/tracing/trace_event.h"
#include "third_party/blink/renderer/platform/wtf/vector.h"
namespace blink {
namespace {
CSSFontSelector* CreateCSSFontSelectorFor(Document& document) {
DCHECK(document.GetFrame());
if (UNLIKELY(document.GetFrame()->PagePopupOwner()))
return PagePopupController::CreateCSSFontSelector(document);
return MakeGarbageCollected<CSSFontSelector>(document);
}
} // namespace
StyleEngine::StyleEngine(Document& document)
: document_(&document),
is_html_import_(document.IsHTMLImport()),
document_style_sheet_collection_(
MakeGarbageCollected<DocumentStyleSheetCollection>(document)),
owner_color_scheme_(mojom::blink::ColorScheme::kLight) {
if (document.GetFrame()) {
// We don't need to create CSSFontSelector for imported document or
// HTMLTemplateElement's document, because those documents have no frame.
// Likewise for the StyleResolver.
font_selector_ = CreateCSSFontSelectorFor(document);
font_selector_->RegisterForInvalidationCallbacks(this);
resolver_ = MakeGarbageCollected<StyleResolver>(document);
if (const auto* owner = document.GetFrame()->Owner())
owner_color_scheme_ = owner->GetColorScheme();
}
if (document.IsInMainFrame())
viewport_resolver_ = MakeGarbageCollected<ViewportStyleResolver>(document);
if (!IsHTMLImport())
global_rule_set_ = MakeGarbageCollected<CSSGlobalRuleSet>();
if (auto* settings = GetDocument().GetSettings()) {
if (!settings->GetForceDarkModeEnabled())
preferred_color_scheme_ = settings->GetPreferredColorScheme();
UpdateColorSchemeMetrics();
}
if (Platform::Current() && Platform::Current()->ThemeEngine())
forced_colors_ = Platform::Current()->ThemeEngine()->GetForcedColors();
UpdateForcedBackgroundColor();
}
StyleEngine::~StyleEngine() = default;
inline Document* StyleEngine::HTMLImportRootDocument() {
if (!IsHTMLImport())
return document_;
HTMLImportsController* import = GetDocument().ImportsController();
// Document::ImportsController() can return null while executing its
// destructor.
if (!import)
return nullptr;
return import->TreeRoot();
}
TreeScopeStyleSheetCollection& StyleEngine::EnsureStyleSheetCollectionFor(
TreeScope& tree_scope) {
if (tree_scope == document_)
return GetDocumentStyleSheetCollection();
StyleSheetCollectionMap::AddResult result =
style_sheet_collection_map_.insert(&tree_scope, nullptr);
if (result.is_new_entry) {
result.stored_value->value =
MakeGarbageCollected<ShadowTreeStyleSheetCollection>(
To<ShadowRoot>(tree_scope));
}
return *result.stored_value->value.Get();
}
TreeScopeStyleSheetCollection* StyleEngine::StyleSheetCollectionFor(
TreeScope& tree_scope) {
if (tree_scope == document_)
return &GetDocumentStyleSheetCollection();
StyleSheetCollectionMap::iterator it =
style_sheet_collection_map_.find(&tree_scope);
if (it == style_sheet_collection_map_.end())
return nullptr;
return it->value.Get();
}
const HeapVector<Member<StyleSheet>>& StyleEngine::StyleSheetsForStyleSheetList(
TreeScope& tree_scope) {
DCHECK(HTMLImportRootDocument());
TreeScopeStyleSheetCollection& collection =
EnsureStyleSheetCollectionFor(tree_scope);
if (HTMLImportRootDocument()->IsActive())
collection.UpdateStyleSheetList();
return collection.StyleSheetsForStyleSheetList();
}
void StyleEngine::InjectSheet(const StyleSheetKey& key,
StyleSheetContents* sheet,
WebDocument::CSSOrigin origin) {
HeapVector<std::pair<StyleSheetKey, Member<CSSStyleSheet>>>&
injected_style_sheets =
origin == WebDocument::kUserOrigin ? injected_user_style_sheets_
: injected_author_style_sheets_;
injected_style_sheets.push_back(std::make_pair(
key, MakeGarbageCollected<CSSStyleSheet>(sheet, *document_)));
if (origin == WebDocument::kUserOrigin)
MarkUserStyleDirty();
else
MarkDocumentDirty();
}
void StyleEngine::RemoveInjectedSheet(const StyleSheetKey& key,
WebDocument::CSSOrigin origin) {
HeapVector<std::pair<StyleSheetKey, Member<CSSStyleSheet>>>&
injected_style_sheets =
origin == WebDocument::kUserOrigin ? injected_user_style_sheets_
: injected_author_style_sheets_;
// Remove the last sheet that matches.
const auto& it = std::find_if(injected_style_sheets.rbegin(),
injected_style_sheets.rend(),
[&key](const auto& item) {
return item.first == key;
});
if (it != injected_style_sheets.rend()) {
injected_style_sheets.erase(std::next(it).base());
if (origin == WebDocument::kUserOrigin)
MarkUserStyleDirty();
else
MarkDocumentDirty();
}
}
CSSStyleSheet& StyleEngine::EnsureInspectorStyleSheet() {
if (inspector_style_sheet_)
return *inspector_style_sheet_;
auto* contents = MakeGarbageCollected<StyleSheetContents>(
MakeGarbageCollected<CSSParserContext>(*document_));
inspector_style_sheet_ =
MakeGarbageCollected<CSSStyleSheet>(contents, *document_);
MarkDocumentDirty();
// TODO(futhark@chromium.org): Making the active stylesheets up-to-date here
// is required by some inspector tests, at least. I theory this should not be
// necessary. Need to investigate to figure out if/why.
UpdateActiveStyle();
return *inspector_style_sheet_;
}
void StyleEngine::AddPendingSheet(StyleEngineContext& context) {
pending_script_blocking_stylesheets_++;
context.AddingPendingSheet(GetDocument());
if (context.AddedPendingSheetBeforeBody() &&
!RuntimeEnabledFeatures::BlockHTMLParserOnStyleSheetsEnabled()) {
pending_render_blocking_stylesheets_++;
} else {
pending_parser_blocking_stylesheets_++;
GetDocument().DidAddPendingParserBlockingStylesheet();
}
}
// This method is called whenever a top-level stylesheet has finished loading.
void StyleEngine::RemovePendingSheet(Node& style_sheet_candidate_node,
const StyleEngineContext& context) {
if (style_sheet_candidate_node.isConnected())
SetNeedsActiveStyleUpdate(style_sheet_candidate_node.GetTreeScope());
if (context.AddedPendingSheetBeforeBody() &&
!RuntimeEnabledFeatures::BlockHTMLParserOnStyleSheetsEnabled()) {
DCHECK_GT(pending_render_blocking_stylesheets_, 0);
pending_render_blocking_stylesheets_--;
} else {
DCHECK_GT(pending_parser_blocking_stylesheets_, 0);
pending_parser_blocking_stylesheets_--;
if (!pending_parser_blocking_stylesheets_)
GetDocument().DidLoadAllPendingParserBlockingStylesheets();
}
// Make sure we knew this sheet was pending, and that our count isn't out of
// sync.
DCHECK_GT(pending_script_blocking_stylesheets_, 0);
pending_script_blocking_stylesheets_--;
if (pending_script_blocking_stylesheets_)
return;
GetDocument().DidRemoveAllPendingStylesheets();
}
void StyleEngine::SetNeedsActiveStyleUpdate(TreeScope& tree_scope) {
DCHECK(tree_scope.RootNode().isConnected());
if (GetDocument().IsActive() || IsHTMLImport())
MarkTreeScopeDirty(tree_scope);
}
void StyleEngine::AddStyleSheetCandidateNode(Node& node) {
if (!node.isConnected() || GetDocument().IsDetached())
return;
DCHECK(!IsXSLStyleSheet(node));
TreeScope& tree_scope = node.GetTreeScope();
EnsureStyleSheetCollectionFor(tree_scope).AddStyleSheetCandidateNode(node);
SetNeedsActiveStyleUpdate(tree_scope);
if (tree_scope != document_)
active_tree_scopes_.insert(&tree_scope);
}
void StyleEngine::RemoveStyleSheetCandidateNode(
Node& node,
ContainerNode& insertion_point) {
DCHECK(!IsXSLStyleSheet(node));
DCHECK(insertion_point.isConnected());
ShadowRoot* shadow_root = node.ContainingShadowRoot();
if (!shadow_root)
shadow_root = insertion_point.ContainingShadowRoot();
static_assert(std::is_base_of<TreeScope, ShadowRoot>::value,
"The ShadowRoot must be subclass of TreeScope.");
TreeScope& tree_scope =
shadow_root ? static_cast<TreeScope&>(*shadow_root) : GetDocument();
TreeScopeStyleSheetCollection* collection =
StyleSheetCollectionFor(tree_scope);
// After detaching document, collection could be null. In the case,
// we should not update anything. Instead, just return.
if (!collection)
return;
collection->RemoveStyleSheetCandidateNode(node);
SetNeedsActiveStyleUpdate(tree_scope);
}
void StyleEngine::ModifiedStyleSheetCandidateNode(Node& node) {
if (node.isConnected())
SetNeedsActiveStyleUpdate(node.GetTreeScope());
}
void StyleEngine::AdoptedStyleSheetsWillChange(
TreeScope& tree_scope,
const HeapVector<Member<CSSStyleSheet>>& old_sheets,
const HeapVector<Member<CSSStyleSheet>>& new_sheets) {
if (GetDocument().IsDetached())
return;
unsigned old_sheets_count = old_sheets.size();
unsigned new_sheets_count = new_sheets.size();
unsigned min_count = std::min(old_sheets_count, new_sheets_count);
unsigned index = 0;
while (index < min_count && old_sheets[index] == new_sheets[index]) {
index++;
}
if (old_sheets_count == new_sheets_count && index == old_sheets_count)
return;
for (unsigned i = index; i < old_sheets_count; ++i) {
old_sheets[i]->RemovedAdoptedFromTreeScope(tree_scope);
}
for (unsigned i = index; i < new_sheets_count; ++i) {
new_sheets[i]->AddedAdoptedToTreeScope(tree_scope);
}
if (!tree_scope.RootNode().isConnected())
return;
if (new_sheets_count) {
EnsureStyleSheetCollectionFor(tree_scope);
if (tree_scope != document_)
active_tree_scopes_.insert(&tree_scope);
} else if (!StyleSheetCollectionFor(tree_scope)) {
return;
}
SetNeedsActiveStyleUpdate(tree_scope);
}
void StyleEngine::AddedCustomElementDefaultStyles(
const HeapVector<Member<CSSStyleSheet>>& default_styles) {
if (!RuntimeEnabledFeatures::CustomElementDefaultStyleEnabled() ||
GetDocument().IsDetached())
return;
for (CSSStyleSheet* sheet : default_styles)
custom_element_default_style_sheets_.insert(sheet);
global_rule_set_->MarkDirty();
}
void StyleEngine::MediaQueryAffectingValueChanged(TreeScope& tree_scope,
MediaValueChange change) {
auto* collection = StyleSheetCollectionFor(tree_scope);
DCHECK(collection);
if (AffectedByMediaValueChange(collection->ActiveStyleSheets(), change))
SetNeedsActiveStyleUpdate(tree_scope);
}
void StyleEngine::WatchedSelectorsChanged() {
DCHECK(!IsHTMLImport());
DCHECK(global_rule_set_);
global_rule_set_->InitWatchedSelectorsRuleSet(GetDocument());
// TODO(futhark@chromium.org): Should be able to use RuleSetInvalidation here.
MarkAllElementsForStyleRecalc(StyleChangeReasonForTracing::Create(
style_change_reason::kDeclarativeContent));
}
bool StyleEngine::ShouldUpdateDocumentStyleSheetCollection() const {
return document_scope_dirty_;
}
bool StyleEngine::ShouldUpdateShadowTreeStyleSheetCollection() const {
return !dirty_tree_scopes_.IsEmpty();
}
void StyleEngine::MediaQueryAffectingValueChanged(
UnorderedTreeScopeSet& tree_scopes,
MediaValueChange change) {
for (TreeScope* tree_scope : tree_scopes) {
DCHECK(tree_scope != document_);
MediaQueryAffectingValueChanged(*tree_scope, change);
}
}
void StyleEngine::AddTextTrack(TextTrack* text_track) {
text_tracks_.insert(text_track);
}
void StyleEngine::RemoveTextTrack(TextTrack* text_track) {
text_tracks_.erase(text_track);
}
Element* StyleEngine::EnsureVTTOriginatingElement() {
if (!vtt_originating_element_) {
vtt_originating_element_ = MakeGarbageCollected<Element>(
QualifiedName(g_null_atom, g_empty_atom, g_empty_atom), document_);
}
return vtt_originating_element_;
}
void StyleEngine::MediaQueryAffectingValueChanged(
HeapHashSet<Member<TextTrack>>& text_tracks,
MediaValueChange change) {
if (text_tracks.IsEmpty())
return;
for (auto text_track : text_tracks) {
bool style_needs_recalc = false;
auto style_sheets = text_track->GetCSSStyleSheets();
for (const auto& sheet : style_sheets) {
StyleSheetContents* contents = sheet->Contents();
if (contents->HasMediaQueries()) {
style_needs_recalc = true;
contents->ClearRuleSet();
}
}
if (style_needs_recalc && text_track->Owner()) {
// Use kSubtreeTreeStyleChange instead of RuleSet style invalidation
// because it won't be expensive for tracks and we won't have dynamic
// changes.
text_track->Owner()->SetNeedsStyleRecalc(
kSubtreeStyleChange,
StyleChangeReasonForTracing::Create(style_change_reason::kShadow));
}
}
}
void StyleEngine::MediaQueryAffectingValueChanged(MediaValueChange change) {
if (AffectedByMediaValueChange(active_user_style_sheets_, change))
MarkUserStyleDirty();
MediaQueryAffectingValueChanged(GetDocument(), change);
MediaQueryAffectingValueChanged(active_tree_scopes_, change);
MediaQueryAffectingValueChanged(text_tracks_, change);
if (resolver_)
resolver_->UpdateMediaType();
}
void StyleEngine::UpdateActiveStyleSheetsInImport(
StyleEngine& root_engine,
DocumentStyleSheetCollector& parent_collector) {
DCHECK(RuntimeEnabledFeatures::HTMLImportsEnabled());
DCHECK(IsHTMLImport());
HeapVector<Member<StyleSheet>> sheets_for_list;
ImportedDocumentStyleSheetCollector subcollector(parent_collector,
sheets_for_list);
GetDocumentStyleSheetCollection().CollectStyleSheets(root_engine,
subcollector);
GetDocumentStyleSheetCollection().SwapSheetsForSheetList(sheets_for_list);
// Mark false for consistency. It is never checked for import documents.
document_scope_dirty_ = false;
}
void StyleEngine::UpdateActiveStyleSheetsInShadow(
TreeScope* tree_scope,
UnorderedTreeScopeSet& tree_scopes_removed) {
DCHECK_NE(tree_scope, document_);
auto* collection =
To<ShadowTreeStyleSheetCollection>(StyleSheetCollectionFor(*tree_scope));
DCHECK(collection);
collection->UpdateActiveStyleSheets(*this);
if (!collection->HasStyleSheetCandidateNodes() &&
!tree_scope->HasAdoptedStyleSheets()) {
tree_scopes_removed.insert(tree_scope);
// When removing TreeScope from ActiveTreeScopes,
// its resolver should be destroyed by invoking resetAuthorStyle.
DCHECK(!tree_scope->GetScopedStyleResolver());
}
}
void StyleEngine::UpdateActiveUserStyleSheets() {
DCHECK(user_style_dirty_);
ActiveStyleSheetVector new_active_sheets;
for (auto& sheet : injected_user_style_sheets_) {
if (RuleSet* rule_set = RuleSetForSheet(*sheet.second))
new_active_sheets.push_back(std::make_pair(sheet.second, rule_set));
}
ApplyUserRuleSetChanges(active_user_style_sheets_, new_active_sheets);
new_active_sheets.swap(active_user_style_sheets_);
}
void StyleEngine::UpdateActiveStyleSheets() {
if (!NeedsActiveStyleSheetUpdate())
return;
DCHECK(!IsHTMLImport());
DCHECK(!GetDocument().InStyleRecalc());
DCHECK(GetDocument().IsActive());
TRACE_EVENT0("blink,blink_style", "StyleEngine::updateActiveStyleSheets");
if (user_style_dirty_)
UpdateActiveUserStyleSheets();
if (ShouldUpdateDocumentStyleSheetCollection())
GetDocumentStyleSheetCollection().UpdateActiveStyleSheets(*this);
if (ShouldUpdateShadowTreeStyleSheetCollection()) {
UnorderedTreeScopeSet tree_scopes_removed;
for (TreeScope* tree_scope : dirty_tree_scopes_)
UpdateActiveStyleSheetsInShadow(tree_scope, tree_scopes_removed);
for (TreeScope* tree_scope : tree_scopes_removed)
active_tree_scopes_.erase(tree_scope);
}
if (RuntimeEnabledFeatures::CSSAtRuleCounterStyleEnabled()) {
// TODO(crbug.com/687225): We initialize the predefined counter styles here.
// Moving the initialization to other places causes test failures, which
// needs investigation and fixing.
CounterStyleMap::GetUACounterStyleMap();
}
probe::ActiveStyleSheetsUpdated(document_);
dirty_tree_scopes_.clear();
document_scope_dirty_ = false;
tree_scopes_removed_ = false;
user_style_dirty_ = false;
}
void StyleEngine::UpdateCounterStyles() {
if (!counter_styles_need_update_)
return;
DCHECK(RuntimeEnabledFeatures::CSSAtRuleCounterStyleEnabled());
CounterStyleMap::MarkAllDirtyCounterStyles(GetDocument(),
active_tree_scopes_);
CounterStyleMap::ResolveAllReferences(GetDocument(), active_tree_scopes_);
counter_styles_need_update_ = false;
}
void StyleEngine::UpdateViewport() {
if (viewport_resolver_)
viewport_resolver_->UpdateViewport(GetDocumentStyleSheetCollection());
}
bool StyleEngine::NeedsActiveStyleUpdate() const {
return (viewport_resolver_ && viewport_resolver_->NeedsUpdate()) ||
NeedsActiveStyleSheetUpdate() ||
(global_rule_set_ && global_rule_set_->IsDirty());
}
void StyleEngine::UpdateActiveStyle() {
DCHECK(GetDocument().IsActive());
DCHECK(IsMainThread());
TRACE_EVENT0("blink", "Document::updateActiveStyle");
UpdateViewport();
UpdateActiveStyleSheets();
UpdateGlobalRuleSet();
}
const ActiveStyleSheetVector StyleEngine::ActiveStyleSheetsForInspector() {
if (GetDocument().IsActive())
UpdateActiveStyle();
if (active_tree_scopes_.IsEmpty())
return GetDocumentStyleSheetCollection().ActiveStyleSheets();
ActiveStyleSheetVector active_style_sheets;
active_style_sheets.AppendVector(
GetDocumentStyleSheetCollection().ActiveStyleSheets());
for (TreeScope* tree_scope : active_tree_scopes_) {
if (TreeScopeStyleSheetCollection* collection =
style_sheet_collection_map_.at(tree_scope))
active_style_sheets.AppendVector(collection->ActiveStyleSheets());
}
// FIXME: Inspector needs a vector which has all active stylesheets.
// However, creating such a large vector might cause performance regression.
// Need to implement some smarter solution.
return active_style_sheets;
}
void StyleEngine::ShadowRootInsertedToDocument(ShadowRoot& shadow_root) {
DCHECK(shadow_root.isConnected());
if (GetDocument().IsDetached() || !shadow_root.HasAdoptedStyleSheets())
return;
EnsureStyleSheetCollectionFor(shadow_root);
SetNeedsActiveStyleUpdate(shadow_root);
active_tree_scopes_.insert(&shadow_root);
}
void StyleEngine::ShadowRootRemovedFromDocument(ShadowRoot* shadow_root) {
style_sheet_collection_map_.erase(shadow_root);
active_tree_scopes_.erase(shadow_root);
dirty_tree_scopes_.erase(shadow_root);
tree_scopes_removed_ = true;
ResetAuthorStyle(*shadow_root);
}
void StyleEngine::ResetAuthorStyle(TreeScope& tree_scope) {
ScopedStyleResolver* scoped_resolver = tree_scope.GetScopedStyleResolver();
if (!scoped_resolver)
return;
if (global_rule_set_)
global_rule_set_->MarkDirty();
if (tree_scope.RootNode().IsDocumentNode()) {
scoped_resolver->ResetStyle();
return;
}
tree_scope.ClearScopedStyleResolver();
}
void StyleEngine::SetRuleUsageTracker(StyleRuleUsageTracker* tracker) {
tracker_ = tracker;
if (resolver_)
resolver_->SetRuleUsageTracker(tracker_);
}
RuleSet* StyleEngine::RuleSetForSheet(CSSStyleSheet& sheet) {
if (!sheet.MatchesMediaQueries(EnsureMediaQueryEvaluator()))
return nullptr;
AddRuleFlags add_rule_flags = kRuleHasNoSpecialState;
if (document_->GetExecutionContext()->GetSecurityOrigin()->CanRequest(
sheet.BaseURL())) {
add_rule_flags = kRuleHasDocumentSecurityOrigin;
}
return &sheet.Contents()->EnsureRuleSet(*media_query_evaluator_,
add_rule_flags);
}
void StyleEngine::ClearResolvers() {
DCHECK(!GetDocument().InStyleRecalc());
DCHECK(!IsHTMLImport() || !resolver_);
GetDocument().ClearScopedStyleResolver();
for (TreeScope* tree_scope : active_tree_scopes_)
tree_scope->ClearScopedStyleResolver();
if (resolver_) {
TRACE_EVENT1("blink", "StyleEngine::clearResolver", "frame",
ToTraceValue(GetDocument().GetFrame()));
resolver_->Dispose();
resolver_.Clear();
}
}
void StyleEngine::DidDetach() {
ClearResolvers();
if (global_rule_set_)
global_rule_set_->Dispose();
global_rule_set_ = nullptr;
dirty_tree_scopes_.clear();
active_tree_scopes_.clear();
viewport_resolver_ = nullptr;
media_query_evaluator_ = nullptr;
style_invalidation_root_.Clear();
style_recalc_root_.Clear();
layout_tree_rebuild_root_.Clear();
if (font_selector_)
font_selector_->GetFontFaceCache()->ClearAll();
font_selector_ = nullptr;
if (environment_variables_)
environment_variables_->DetachFromParent();
environment_variables_ = nullptr;
}
bool StyleEngine::ClearFontFaceCacheAndAddUserFonts(
const ActiveStyleSheetVector& user_sheets) {
bool fonts_changed = false;
if (font_selector_ &&
font_selector_->GetFontFaceCache()->ClearCSSConnected()) {
fonts_changed = true;
if (resolver_)
resolver_->InvalidateMatchedPropertiesCache();
}
// Rebuild the font cache with @font-face rules from user style sheets.
for (unsigned i = 0; i < user_sheets.size(); ++i) {
DCHECK(user_sheets[i].second);
if (AddUserFontFaceRules(*user_sheets[i].second))
fonts_changed = true;
}
return fonts_changed;
}
void StyleEngine::UpdateGenericFontFamilySettings() {
// FIXME: we should not update generic font family settings when
// document is inactive.
DCHECK(GetDocument().IsActive());
if (!font_selector_)
return;
font_selector_->UpdateGenericFontFamilySettings(*document_);
if (resolver_)
resolver_->InvalidateMatchedPropertiesCache();
FontCache::GetFontCache()->InvalidateShapeCache();
}
void StyleEngine::RemoveFontFaceRules(
const HeapVector<Member<const StyleRuleFontFace>>& font_face_rules) {
if (!font_selector_)
return;
FontFaceCache* cache = font_selector_->GetFontFaceCache();
for (const auto& rule : font_face_rules)
cache->Remove(rule);
if (resolver_)
resolver_->InvalidateMatchedPropertiesCache();
}
void StyleEngine::MarkTreeScopeDirty(TreeScope& scope) {
if (scope == document_) {
MarkDocumentDirty();
return;
}
TreeScopeStyleSheetCollection* collection = StyleSheetCollectionFor(scope);
DCHECK(collection);
collection->MarkSheetListDirty();
dirty_tree_scopes_.insert(&scope);
GetDocument().ScheduleLayoutTreeUpdateIfNeeded();
}
void StyleEngine::MarkDocumentDirty() {
document_scope_dirty_ = true;
document_style_sheet_collection_->MarkSheetListDirty();
if (GetDocument().ImportLoader())
GetDocument().TreeRootDocument().GetStyleEngine().MarkDocumentDirty();
else
GetDocument().ScheduleLayoutTreeUpdateIfNeeded();
}
void StyleEngine::MarkUserStyleDirty() {
user_style_dirty_ = true;
GetDocument().ScheduleLayoutTreeUpdateIfNeeded();
}
void StyleEngine::MarkViewportStyleDirty() {
viewport_style_dirty_ = true;
GetDocument().ScheduleLayoutTreeUpdateIfNeeded();
}
CSSStyleSheet* StyleEngine::CreateSheet(Element& element,
const String& text,
TextPosition start_position,
StyleEngineContext& context) {
DCHECK(element.GetDocument() == GetDocument());
CSSStyleSheet* style_sheet = nullptr;
AddPendingSheet(context);
AtomicString text_content(text);
auto result = text_to_sheet_cache_.insert(text_content, nullptr);
StyleSheetContents* contents = result.stored_value->value;
if (result.is_new_entry || !contents ||
!contents->IsCacheableForStyleElement()) {
result.stored_value->value = nullptr;
style_sheet = ParseSheet(element, text, start_position);
if (style_sheet->Contents()->IsCacheableForStyleElement()) {
result.stored_value->value = style_sheet->Contents();
sheet_to_text_cache_.insert(style_sheet->Contents(), text_content);
}
} else {
DCHECK(contents);
DCHECK(contents->IsCacheableForStyleElement());
DCHECK(contents->HasSingleOwnerDocument());
contents->SetIsUsedFromTextCache();
style_sheet =
CSSStyleSheet::CreateInline(contents, element, start_position);
}
DCHECK(style_sheet);
if (!element.IsInShadowTree()) {
String title = element.title();
if (!title.IsEmpty()) {
style_sheet->SetTitle(title);
SetPreferredStylesheetSetNameIfNotSet(title);
}
}
return style_sheet;
}
CSSStyleSheet* StyleEngine::ParseSheet(Element& element,
const String& text,
TextPosition start_position) {
CSSStyleSheet* style_sheet = nullptr;
style_sheet = CSSStyleSheet::CreateInline(element, NullURL(), start_position,
GetDocument().Encoding());
style_sheet->Contents()->ParseStringAtPosition(text, start_position);
return style_sheet;
}
void StyleEngine::CollectUserStyleFeaturesTo(RuleFeatureSet& features) const {
for (unsigned i = 0; i < active_user_style_sheets_.size(); ++i) {
CSSStyleSheet* sheet = active_user_style_sheets_[i].first;
features.ViewportDependentMediaQueryResults().AppendVector(
sheet->ViewportDependentMediaQueryResults());
features.DeviceDependentMediaQueryResults().AppendVector(
sheet->DeviceDependentMediaQueryResults());
DCHECK(sheet->Contents()->HasRuleSet());
features.Add(sheet->Contents()->GetRuleSet().Features());
}
}
void StyleEngine::CollectScopedStyleFeaturesTo(RuleFeatureSet& features) const {
HeapHashSet<Member<const StyleSheetContents>>
visited_shared_style_sheet_contents;
if (GetDocument().GetScopedStyleResolver()) {
GetDocument().GetScopedStyleResolver()->CollectFeaturesTo(
features, visited_shared_style_sheet_contents);
}
for (TreeScope* tree_scope : active_tree_scopes_) {
if (ScopedStyleResolver* resolver = tree_scope->GetScopedStyleResolver()) {
resolver->CollectFeaturesTo(features,
visited_shared_style_sheet_contents);
}
}
}
void StyleEngine::InvalidateStyleAndLayoutForFontUpdates() {
if (!fonts_need_update_)
return;
TRACE_EVENT0("blink", "StyleEngine::InvalidateStyleAndLayoutForFontUpdates");
fonts_need_update_ = false;
if (Element* root = GetDocument().documentElement()) {
TRACE_EVENT0("blink", "Node::MarkSubtreeNeedsStyleRecalcForFontUpdates");
root->MarkSubtreeNeedsStyleRecalcForFontUpdates();
}
// TODO(xiaochengh): Move layout invalidation after style update.
if (LayoutView* layout_view = GetDocument().GetLayoutView()) {
TRACE_EVENT0("blink", "LayoutObject::InvalidateSubtreeForFontUpdates");
layout_view->InvalidateSubtreeLayoutForFontUpdates();
}
}
void StyleEngine::MarkFontsNeedUpdate() {
fonts_need_update_ = true;
GetDocument().ScheduleLayoutTreeUpdateIfNeeded();
}
void StyleEngine::MarkCounterStylesNeedUpdate() {
counter_styles_need_update_ = true;
if (LayoutView* layout_view = GetDocument().GetLayoutView())
layout_view->SetNeedsMarkerOrCounterUpdate();
GetDocument().ScheduleLayoutTreeUpdateIfNeeded();
}
void StyleEngine::FontsNeedUpdate(FontSelector*, FontInvalidationReason) {
if (!GetDocument().IsActive())
return;
if (resolver_)
resolver_->InvalidateMatchedPropertiesCache();
MarkViewportStyleDirty();
MarkFontsNeedUpdate();
probe::FontsUpdated(document_->GetExecutionContext(), nullptr, String(),
nullptr);
}
void StyleEngine::PlatformColorsChanged() {
UpdateForcedBackgroundColor();
UpdateColorSchemeBackground(/* color_scheme_changed */ true);
if (resolver_)
resolver_->InvalidateMatchedPropertiesCache();
MarkAllElementsForStyleRecalc(StyleChangeReasonForTracing::Create(
style_change_reason::kPlatformColorChange));
}
bool StyleEngine::ShouldSkipInvalidationFor(const Element& element) const {
if (!element.InActiveDocument())
return true;
if (GetDocument().InStyleRecalc()) {
#if DCHECK_IS_ON()
// TODO(futhark): The InStyleRecalc() if-guard above should have been a
// DCHECK(!InStyleRecalc()), but there are a couple of cases where we try to
// invalidate style from style recalc:
//
// 1. We may animate the class attribute of an SVG element and change it
// during style recalc when applying the animation effect.
// 2. We may call SetInlineStyle on elements in a UA shadow tree as part of
// style recalc. For instance from HTMLImageFallbackHelper.
//
// If there are more cases, we need to adjust the DCHECKs below, but ideally
// The origin of these invalidations should be fixed.
if (!element.IsSVGElement()) {
DCHECK(element.ContainingShadowRoot());
DCHECK(element.ContainingShadowRoot()->IsUserAgent());
}
#endif // DCHECK_IS_ON()
return true;
}
if (GetDocument().GetStyleChangeType() == kSubtreeStyleChange)
return true;
Element* root = GetDocument().documentElement();
if (!root || root->GetStyleChangeType() == kSubtreeStyleChange)
return true;
if (!element.parentNode())
return true;
return element.parentNode()->GetStyleChangeType() == kSubtreeStyleChange;
}
void StyleEngine::ClassChangedForElement(
const SpaceSplitString& changed_classes,
Element& element) {
if (ShouldSkipInvalidationFor(element))
return;
InvalidationLists invalidation_lists;
unsigned changed_size = changed_classes.size();
const RuleFeatureSet& features = GetRuleFeatureSet();
for (unsigned i = 0; i < changed_size; ++i) {
features.CollectInvalidationSetsForClass(invalidation_lists, element,
changed_classes[i]);
}
pending_invalidations_.ScheduleInvalidationSetsForNode(invalidation_lists,
element);
}
void StyleEngine::ClassChangedForElement(const SpaceSplitString& old_classes,
const SpaceSplitString& new_classes,
Element& element) {
if (ShouldSkipInvalidationFor(element))
return;
if (!old_classes.size()) {
ClassChangedForElement(new_classes, element);
return;
}
// Class vectors tend to be very short. This is faster than using a hash
// table.
WTF::Vector<bool> remaining_class_bits(old_classes.size());
InvalidationLists invalidation_lists;
const RuleFeatureSet& features = GetRuleFeatureSet();
for (unsigned i = 0; i < new_classes.size(); ++i) {
bool found = false;
for (unsigned j = 0; j < old_classes.size(); ++j) {
if (new_classes[i] == old_classes[j]) {
// Mark each class that is still in the newClasses so we can skip doing
// an n^2 search below when looking for removals. We can't break from
// this loop early since a class can appear more than once.
remaining_class_bits[j] = true;
found = true;
}
}
// Class was added.
if (!found) {
features.CollectInvalidationSetsForClass(invalidation_lists, element,
new_classes[i]);
}
}
for (unsigned i = 0; i < old_classes.size(); ++i) {
if (remaining_class_bits[i])
continue;
// Class was removed.
features.CollectInvalidationSetsForClass(invalidation_lists, element,
old_classes[i]);
}
pending_invalidations_.ScheduleInvalidationSetsForNode(invalidation_lists,
element);
}
namespace {
bool HasAttributeDependentGeneratedContent(const Element& element) {
if (PseudoElement* before = element.GetPseudoElement(kPseudoIdBefore)) {
const ComputedStyle* style = before->GetComputedStyle();
if (style && style->HasAttrContent())
return true;
}
if (PseudoElement* after = element.GetPseudoElement(kPseudoIdAfter)) {
const ComputedStyle* style = after->GetComputedStyle();
if (style && style->HasAttrContent())
return true;
}
return false;
}
} // namespace
void StyleEngine::AttributeChangedForElement(
const QualifiedName& attribute_name,
Element& element) {
if (ShouldSkipInvalidationFor(element))
return;
InvalidationLists invalidation_lists;
GetRuleFeatureSet().CollectInvalidationSetsForAttribute(
invalidation_lists, element, attribute_name);
pending_invalidations_.ScheduleInvalidationSetsForNode(invalidation_lists,
element);
if (!element.NeedsStyleRecalc() &&
HasAttributeDependentGeneratedContent(element)) {
element.SetNeedsStyleRecalc(
kLocalStyleChange,
StyleChangeReasonForTracing::FromAttribute(attribute_name));
}
}
void StyleEngine::IdChangedForElement(const AtomicString& old_id,
const AtomicString& new_id,
Element& element) {
if (ShouldSkipInvalidationFor(element))
return;
InvalidationLists invalidation_lists;
const RuleFeatureSet& features = GetRuleFeatureSet();
if (!old_id.IsEmpty())
features.CollectInvalidationSetsForId(invalidation_lists, element, old_id);
if (!new_id.IsEmpty())
features.CollectInvalidationSetsForId(invalidation_lists, element, new_id);
pending_invalidations_.ScheduleInvalidationSetsForNode(invalidation_lists,
element);
}
void StyleEngine::PseudoStateChangedForElement(
CSSSelector::PseudoType pseudo_type,
Element& element) {
if (ShouldSkipInvalidationFor(element))
return;
InvalidationLists invalidation_lists;
GetRuleFeatureSet().CollectInvalidationSetsForPseudoClass(
invalidation_lists, element, pseudo_type);
pending_invalidations_.ScheduleInvalidationSetsForNode(invalidation_lists,
element);
}
void StyleEngine::PartChangedForElement(Element& element) {
if (ShouldSkipInvalidationFor(element))
return;
if (element.GetTreeScope() == document_)
return;
if (!GetRuleFeatureSet().InvalidatesParts())
return;
element.SetNeedsStyleRecalc(
kLocalStyleChange,
StyleChangeReasonForTracing::FromAttribute(html_names::kPartAttr));
}
void StyleEngine::ExportpartsChangedForElement(Element& element) {
if (ShouldSkipInvalidationFor(element))
return;
if (!element.GetShadowRoot())
return;
InvalidationLists invalidation_lists;
GetRuleFeatureSet().CollectPartInvalidationSet(invalidation_lists);
pending_invalidations_.ScheduleInvalidationSetsForNode(invalidation_lists,
element);
}
void StyleEngine::ScheduleSiblingInvalidationsForElement(
Element& element,
ContainerNode& scheduling_parent,
unsigned min_direct_adjacent) {
DCHECK(min_direct_adjacent);
InvalidationLists invalidation_lists;
const RuleFeatureSet& features = GetRuleFeatureSet();
if (element.HasID()) {
features.CollectSiblingInvalidationSetForId(invalidation_lists, element,
element.IdForStyleResolution(),
min_direct_adjacent);
}
if (element.HasClass()) {
const SpaceSplitString& class_names = element.ClassNames();
for (wtf_size_t i = 0; i < class_names.size(); i++) {
features.CollectSiblingInvalidationSetForClass(
invalidation_lists, element, class_names[i], min_direct_adjacent);
}
}
for (const Attribute& attribute : element.Attributes()) {
features.CollectSiblingInvalidationSetForAttribute(
invalidation_lists, element, attribute.GetName(), min_direct_adjacent);
}
features.CollectUniversalSiblingInvalidationSet(invalidation_lists,
min_direct_adjacent);
pending_invalidations_.ScheduleSiblingInvalidationsAsDescendants(
invalidation_lists, scheduling_parent);
}
void StyleEngine::ScheduleInvalidationsForInsertedSibling(
Element* before_element,
Element& inserted_element) {
unsigned affected_siblings =
inserted_element.parentNode()->ChildrenAffectedByIndirectAdjacentRules()
? SiblingInvalidationSet::kDirectAdjacentMax
: MaxDirectAdjacentSelectors();
ContainerNode* scheduling_parent =
inserted_element.ParentElementOrShadowRoot();
if (!scheduling_parent)
return;
ScheduleSiblingInvalidationsForElement(inserted_element, *scheduling_parent,
1);
for (unsigned i = 1; before_element && i <= affected_siblings;
i++, before_element =
ElementTraversal::PreviousSibling(*before_element)) {
ScheduleSiblingInvalidationsForElement(*before_element, *scheduling_parent,
i);
}
}
void StyleEngine::ScheduleInvalidationsForRemovedSibling(
Element* before_element,
Element& removed_element,
Element& after_element) {
unsigned affected_siblings =
after_element.parentNode()->ChildrenAffectedByIndirectAdjacentRules()
? SiblingInvalidationSet::kDirectAdjacentMax
: MaxDirectAdjacentSelectors();
ContainerNode* scheduling_parent = after_element.ParentElementOrShadowRoot();
if (!scheduling_parent)
return;
ScheduleSiblingInvalidationsForElement(removed_element, *scheduling_parent,
1);
for (unsigned i = 1; before_element && i <= affected_siblings;
i++, before_element =
ElementTraversal::PreviousSibling(*before_element)) {
ScheduleSiblingInvalidationsForElement(*before_element, *scheduling_parent,
i);
}
}
void StyleEngine::ScheduleNthPseudoInvalidations(ContainerNode& nth_parent) {
InvalidationLists invalidation_lists;
GetRuleFeatureSet().CollectNthInvalidationSet(invalidation_lists);
pending_invalidations_.ScheduleInvalidationSetsForNode(invalidation_lists,
nth_parent);
}
void StyleEngine::ScheduleRuleSetInvalidationsForElement(
Element& element,
const HeapHashSet<Member<RuleSet>>& rule_sets) {
AtomicString id;
const SpaceSplitString* class_names = nullptr;
if (element.HasID())
id = element.IdForStyleResolution();
if (element.HasClass())
class_names = &element.ClassNames();
InvalidationLists invalidation_lists;
for (const auto& rule_set : rule_sets) {
if (!id.IsNull()) {
rule_set->Features().CollectInvalidationSetsForId(invalidation_lists,
element, id);
}
if (class_names) {
wtf_size_t class_name_count = class_names->size();
for (wtf_size_t i = 0; i < class_name_count; i++) {
rule_set->Features().CollectInvalidationSetsForClass(
invalidation_lists, element, (*class_names)[i]);
}
}
for (const Attribute& attribute : element.Attributes()) {
rule_set->Features().CollectInvalidationSetsForAttribute(
invalidation_lists, element, attribute.GetName());
}
}
pending_invalidations_.ScheduleInvalidationSetsForNode(invalidation_lists,
element);
}
void StyleEngine::ScheduleTypeRuleSetInvalidations(
ContainerNode& node,
const HeapHashSet<Member<RuleSet>>& rule_sets) {
InvalidationLists invalidation_lists;
for (const auto& rule_set : rule_sets) {
rule_set->Features().CollectTypeRuleInvalidationSet(invalidation_lists,
node);
}
DCHECK(invalidation_lists.siblings.IsEmpty());
pending_invalidations_.ScheduleInvalidationSetsForNode(invalidation_lists,
node);
auto* shadow_root = DynamicTo<ShadowRoot>(node);
if (!shadow_root)
return;
Element& host = shadow_root->host();
if (host.NeedsStyleRecalc())
return;
for (auto& invalidation_set : invalidation_lists.descendants) {
if (invalidation_set->InvalidatesTagName(host)) {
host.SetNeedsStyleRecalc(kLocalStyleChange,
StyleChangeReasonForTracing::Create(
style_change_reason::kStyleSheetChange));
return;
}
}
}
void StyleEngine::ScheduleCustomElementInvalidations(
HashSet<AtomicString> tag_names) {
scoped_refptr<DescendantInvalidationSet> invalidation_set =
DescendantInvalidationSet::Create();
for (auto& tag_name : tag_names) {
invalidation_set->AddTagName(tag_name);
}
invalidation_set->SetTreeBoundaryCrossing();
InvalidationLists invalidation_lists;
invalidation_lists.descendants.push_back(invalidation_set);
pending_invalidations_.ScheduleInvalidationSetsForNode(invalidation_lists,
*document_);
}
void StyleEngine::InvalidateStyle() {
StyleInvalidator style_invalidator(
pending_invalidations_.GetPendingInvalidationMap());
style_invalidator.Invalidate(GetDocument(),
style_invalidation_root_.RootElement());
style_invalidation_root_.Clear();
}
void StyleEngine::InvalidateSlottedElements(HTMLSlotElement& slot) {
for (auto& node : slot.FlattenedAssignedNodes()) {
if (node->IsElementNode()) {
node->SetNeedsStyleRecalc(kLocalStyleChange,
StyleChangeReasonForTracing::Create(
style_change_reason::kStyleSheetChange));
}
}
}
void StyleEngine::ScheduleInvalidationsForRuleSets(
TreeScope& tree_scope,
const HeapHashSet<Member<RuleSet>>& rule_sets,
InvalidationScope invalidation_scope) {
#if DCHECK_IS_ON()
// Full scope recalcs should be handled while collecting the rule sets before
// calling this method.
for (auto rule_set : rule_sets)
DCHECK(!rule_set->Features().NeedsFullRecalcForRuleSetInvalidation());
#endif // DCHECK_IS_ON()
TRACE_EVENT0("blink,blink_style",
"StyleEngine::scheduleInvalidationsForRuleSets");
ScheduleTypeRuleSetInvalidations(tree_scope.RootNode(), rule_sets);
bool invalidate_slotted = false;
if (auto* shadow_root = DynamicTo<ShadowRoot>(&tree_scope.RootNode())) {
Element& host = shadow_root->host();
ScheduleRuleSetInvalidationsForElement(host, rule_sets);
if (host.GetStyleChangeType() == kSubtreeStyleChange)
return;
for (auto rule_set : rule_sets) {
if (rule_set->HasSlottedRules()) {
invalidate_slotted = true;
break;
}
}
}
Node* stay_within = &tree_scope.RootNode();
Element* element = ElementTraversal::FirstChild(*stay_within);
while (element) {
ScheduleRuleSetInvalidationsForElement(*element, rule_sets);
auto* html_slot_element = DynamicTo<HTMLSlotElement>(element);
if (html_slot_element && invalidate_slotted)
InvalidateSlottedElements(*html_slot_element);
if (invalidation_scope == kInvalidateAllScopes) {
if (ShadowRoot* shadow_root = element->GetShadowRoot()) {
ScheduleInvalidationsForRuleSets(*shadow_root, rule_sets,
kInvalidateAllScopes);
}
}
if (element->GetStyleChangeType() < kSubtreeStyleChange &&
element->GetComputedStyle()) {
element = ElementTraversal::Next(*element, stay_within);
} else {
element = ElementTraversal::NextSkippingChildren(*element, stay_within);
}
}
}
void StyleEngine::SetStatsEnabled(bool enabled) {
if (!enabled) {
style_resolver_stats_ = nullptr;
return;
}
if (!style_resolver_stats_)
style_resolver_stats_ = std::make_unique<StyleResolverStats>();
else
style_resolver_stats_->Reset();
}
void StyleEngine::SetPreferredStylesheetSetNameIfNotSet(const String& name) {
DCHECK(!name.IsEmpty());
if (!preferred_stylesheet_set_name_.IsEmpty())
return;
preferred_stylesheet_set_name_ = name;
MarkDocumentDirty();
}
void StyleEngine::SetHttpDefaultStyle(const String& content) {
if (!content.IsEmpty())
SetPreferredStylesheetSetNameIfNotSet(content);
}
void StyleEngine::EnsureUAStyleForXrOverlay() {
DCHECK(!IsHTMLImport());
DCHECK(global_rule_set_);
if (CSSDefaultStyleSheets::Instance().EnsureDefaultStyleSheetForXrOverlay()) {
global_rule_set_->MarkDirty();
UpdateActiveStyle();
}
}
void StyleEngine::EnsureUAStyleForFullscreen() {
DCHECK(!IsHTMLImport());
DCHECK(global_rule_set_);
if (global_rule_set_->HasFullscreenUAStyle())
return;
CSSDefaultStyleSheets::Instance().EnsureDefaultStyleSheetForFullscreen();
global_rule_set_->MarkDirty();
UpdateActiveStyle();
}
void StyleEngine::EnsureUAStyleForElement(const Element& element) {
DCHECK(!IsHTMLImport());
DCHECK(global_rule_set_);
if (CSSDefaultStyleSheets::Instance().EnsureDefaultStyleSheetsForElement(
element)) {
global_rule_set_->MarkDirty();
UpdateActiveStyle();
}
}
void StyleEngine::EnsureUAStyleForPseudoElement(PseudoId pseudo_id) {
DCHECK(!IsHTMLImport());
DCHECK(global_rule_set_);
if (CSSDefaultStyleSheets::Instance()
.EnsureDefaultStyleSheetsForPseudoElement(pseudo_id)) {
global_rule_set_->MarkDirty();
UpdateActiveStyle();
}
}
void StyleEngine::EnsureUAStyleForForcedColors() {
DCHECK(!IsHTMLImport());
DCHECK(global_rule_set_);
if (CSSDefaultStyleSheets::Instance()
.EnsureDefaultStyleSheetForForcedColors()) {
global_rule_set_->MarkDirty();
if (GetDocument().IsActive())
UpdateActiveStyle();
}
}
bool StyleEngine::HasRulesForId(const AtomicString& id) const {
DCHECK(!IsHTMLImport());
DCHECK(global_rule_set_);
return global_rule_set_->GetRuleFeatureSet().HasSelectorForId(id);
}
void StyleEngine::InitialStyleChanged() {
if (viewport_resolver_)
viewport_resolver_->InitialStyleChanged();
// Media queries may rely on the initial font size relative lengths which may
// have changed.
MediaQueryAffectingValueChanged(MediaValueChange::kOther);
MarkViewportStyleDirty();
MarkAllElementsForStyleRecalc(
StyleChangeReasonForTracing::Create(style_change_reason::kSettings));
}
void StyleEngine::InitialViewportChanged() {
if (viewport_resolver_)
viewport_resolver_->InitialViewportChanged();
}
void StyleEngine::ViewportRulesChanged() {
if (viewport_resolver_)
viewport_resolver_->SetNeedsCollectRules();
}
void StyleEngine::HtmlImportAddedOrRemoved() {
if (GetDocument().ImportLoader()) {
GetDocument()
.TreeRootDocument()
.GetStyleEngine()
.HtmlImportAddedOrRemoved();
return;
}
// When we remove an import link and re-insert it into the document, the
// import Document and CSSStyleSheet pointers are persisted. That means the
// comparison of active stylesheets is not able to figure out that the order
// of the stylesheets have changed after insertion.
//
// This is also the case when we import the same document twice where the
// last inserted document is inserted before the first one in dom order where
// the last would take precedence.
//
// Fall back to re-add all sheets to the scoped resolver and recalculate style
// for the whole document when we remove or insert an import document.
if (ScopedStyleResolver* resolver = GetDocument().GetScopedStyleResolver()) {
MarkDocumentDirty();
resolver->SetNeedsAppendAllSheets();
MarkAllElementsForStyleRecalc(StyleChangeReasonForTracing::Create(
style_change_reason::kActiveStylesheetsUpdate));
}
}
namespace {
enum RuleSetFlags {
kFontFaceRules = 1 << 0,
kKeyframesRules = 1 << 1,
kFullRecalcRules = 1 << 2,
kPropertyRules = 1 << 3,
kScrollTimelineRules = 1 << 4,
kCounterStyleRules = 1 << 5,
};
unsigned GetRuleSetFlags(const HeapHashSet<Member<RuleSet>> rule_sets) {
unsigned flags = 0;
for (auto& rule_set : rule_sets) {
rule_set->CompactRulesIfNeeded();
if (!rule_set->KeyframesRules().IsEmpty())
flags |= kKeyframesRules;
if (!rule_set->FontFaceRules().IsEmpty())
flags |= kFontFaceRules;
if (rule_set->NeedsFullRecalcForRuleSetInvalidation())
flags |= kFullRecalcRules;
if (!rule_set->PropertyRules().IsEmpty())
flags |= kPropertyRules;
if (!rule_set->CounterStyleRules().IsEmpty())
flags |= kCounterStyleRules;
if (!rule_set->ScrollTimelineRules().IsEmpty())
flags |= kScrollTimelineRules;
}
return flags;
}
} // namespace
void StyleEngine::InvalidateForRuleSetChanges(
TreeScope& tree_scope,
const HeapHashSet<Member<RuleSet>>& changed_rule_sets,
unsigned changed_rule_flags,
InvalidationScope invalidation_scope) {
if (tree_scope.GetDocument().HasPendingForcedStyleRecalc())
return;
if (!tree_scope.GetDocument().documentElement())
return;
if (changed_rule_sets.IsEmpty())
return;
Element& invalidation_root =
ScopedStyleResolver::InvalidationRootForTreeScope(tree_scope);
if (invalidation_root.GetStyleChangeType() == kSubtreeStyleChange)
return;
if (changed_rule_flags & kFullRecalcRules) {
invalidation_root.SetNeedsStyleRecalc(
kSubtreeStyleChange,
StyleChangeReasonForTracing::Create(
style_change_reason::kActiveStylesheetsUpdate));
return;
}
if (changed_rule_sets.IsEmpty())
return;
ScheduleInvalidationsForRuleSets(tree_scope, changed_rule_sets,
invalidation_scope);
}
void StyleEngine::InvalidateInitialData() {
initial_data_ = nullptr;
}
void StyleEngine::ApplyUserRuleSetChanges(
const ActiveStyleSheetVector& old_style_sheets,
const ActiveStyleSheetVector& new_style_sheets) {
DCHECK(!IsHTMLImport());
DCHECK(global_rule_set_);
HeapHashSet<Member<RuleSet>> changed_rule_sets;
ActiveSheetsChange change = CompareActiveStyleSheets(
old_style_sheets, new_style_sheets, changed_rule_sets);
if (change == kNoActiveSheetsChanged)
return;
// With rules added or removed, we need to re-aggregate rule meta data.
global_rule_set_->MarkDirty();
unsigned changed_rule_flags = GetRuleSetFlags(changed_rule_sets);
bool has_rebuilt_font_face_cache = false;
if (changed_rule_flags & kFontFaceRules) {
if (ScopedStyleResolver* scoped_resolver =
GetDocument().GetScopedStyleResolver()) {
// User style and document scope author style shares the font cache. If
// @font-face rules are added/removed from user stylesheets, we need to
// reconstruct the font cache because @font-face rules from author style
// need to be added to the cache after user rules.
scoped_resolver->SetNeedsAppendAllSheets();
MarkDocumentDirty();
} else {
has_rebuilt_font_face_cache =
ClearFontFaceCacheAndAddUserFonts(new_style_sheets);
}
}
if (changed_rule_flags & kKeyframesRules) {
if (change == kActiveSheetsChanged)
ClearKeyframeRules();
for (auto* it = new_style_sheets.begin(); it != new_style_sheets.end();
it++) {
DCHECK(it->second);
AddUserKeyframeRules(*it->second);
}
ScopedStyleResolver::KeyframesRulesAdded(GetDocument());
}
if (changed_rule_flags & kCounterStyleRules) {
if (change == kActiveSheetsChanged && user_counter_style_map_)
user_counter_style_map_->Dispose();
for (auto* it = new_style_sheets.begin(); it != new_style_sheets.end();
it++) {
DCHECK(it->second);
if (!it->second->CounterStyleRules().IsEmpty())
EnsureUserCounterStyleMap().AddCounterStyles(*it->second);
}
MarkCounterStylesNeedUpdate();
}
if (changed_rule_flags & (kPropertyRules | kScrollTimelineRules)) {
if (changed_rule_flags & kPropertyRules) {
ClearPropertyRules();
AddPropertyRulesFromSheets(new_style_sheets);
}
if (changed_rule_flags & kScrollTimelineRules) {
ClearScrollTimelineRules();
AddScrollTimelineRulesFromSheets(new_style_sheets);
}
// We just cleared all the rules, which includes any author rules. They
// must be forcibly re-added.
if (ScopedStyleResolver* scoped_resolver =
GetDocument().GetScopedStyleResolver()) {
scoped_resolver->SetNeedsAppendAllSheets();
MarkDocumentDirty();
}
}
if ((changed_rule_flags & kFontFaceRules) || has_rebuilt_font_face_cache) {
GetFontSelector()->FontFaceInvalidated(
FontInvalidationReason::kGeneralInvalidation);
}
InvalidateForRuleSetChanges(GetDocument(), changed_rule_sets,
changed_rule_flags, kInvalidateAllScopes);
}
void StyleEngine::ApplyRuleSetChanges(
TreeScope& tree_scope,
const ActiveStyleSheetVector& old_style_sheets,
const ActiveStyleSheetVector& new_style_sheets) {
DCHECK(!IsHTMLImport());
DCHECK(global_rule_set_);
HeapHashSet<Member<RuleSet>> changed_rule_sets;
ActiveSheetsChange change = CompareActiveStyleSheets(
old_style_sheets, new_style_sheets, changed_rule_sets);
unsigned changed_rule_flags = GetRuleSetFlags(changed_rule_sets);
bool rebuild_font_face_cache = change == kActiveSheetsChanged &&
(changed_rule_flags & kFontFaceRules) &&
tree_scope.RootNode().IsDocumentNode();
bool rebuild_at_property_registry = false;
bool rebuild_at_scroll_timeline_map = false;
ScopedStyleResolver* scoped_resolver = tree_scope.GetScopedStyleResolver();
if (scoped_resolver && scoped_resolver->NeedsAppendAllSheets()) {
rebuild_font_face_cache = true;
rebuild_at_property_registry = true;
rebuild_at_scroll_timeline_map = true;
change = kActiveSheetsChanged;
}
if (change == kNoActiveSheetsChanged)
return;
// With rules added or removed, we need to re-aggregate rule meta data.
global_rule_set_->MarkDirty();
if (changed_rule_flags & kKeyframesRules)
ScopedStyleResolver::KeyframesRulesAdded(tree_scope);
if (changed_rule_flags & kCounterStyleRules)
MarkCounterStylesNeedUpdate();
if ((changed_rule_flags & kPropertyRules) || rebuild_at_property_registry) {
// @property rules are (for now) ignored in shadow trees, per spec.
// https://drafts.css-houdini.org/css-properties-values-api-1/#at-property-rule
if (tree_scope.RootNode().IsDocumentNode()) {
ClearPropertyRules();
AddPropertyRulesFromSheets(active_user_style_sheets_);
AddPropertyRulesFromSheets(new_style_sheets);
}
}
if ((changed_rule_flags & kScrollTimelineRules) ||
rebuild_at_scroll_timeline_map) {
// @scroll-timeline rules are currently not allowed in shadow trees.
// https://drafts.csswg.org/scroll-animations-1/#scroll-timeline-at-rule
if (tree_scope.RootNode().IsDocumentNode()) {
ClearScrollTimelineRules();
AddScrollTimelineRulesFromSheets(active_user_style_sheets_);
AddScrollTimelineRulesFromSheets(new_style_sheets);
}
}
bool has_rebuilt_font_face_cache = false;
if (rebuild_font_face_cache) {
has_rebuilt_font_face_cache =
ClearFontFaceCacheAndAddUserFonts(active_user_style_sheets_);
}
unsigned append_start_index = 0;
if (scoped_resolver) {
// - If all sheets were removed, we remove the ScopedStyleResolver.
// - If new sheets were appended to existing ones, start appending after the
// common prefix.
// - For other diffs, reset author style and re-add all sheets for the
// TreeScope.
if (new_style_sheets.IsEmpty())
ResetAuthorStyle(tree_scope);
else if (change == kActiveSheetsAppended)
append_start_index = old_style_sheets.size();
else
scoped_resolver->ResetStyle();
}
if (!new_style_sheets.IsEmpty()) {
tree_scope.EnsureScopedStyleResolver().AppendActiveStyleSheets(
append_start_index, new_style_sheets);
}
if (tree_scope.RootNode().IsDocumentNode()) {
if ((changed_rule_flags & kFontFaceRules) || has_rebuilt_font_face_cache) {
GetFontSelector()->FontFaceInvalidated(
FontInvalidationReason::kGeneralInvalidation);
}
}
InvalidateForRuleSetChanges(tree_scope, changed_rule_sets, changed_rule_flags,
kInvalidateCurrentScope);
}
void StyleEngine::LoadVisionDeficiencyFilter() {
VisionDeficiency old_vision_deficiency = vision_deficiency_;
vision_deficiency_ = GetDocument().GetPage()->GetVisionDeficiency();
if (vision_deficiency_ == old_vision_deficiency)
return;
if (vision_deficiency_ == VisionDeficiency::kNoVisionDeficiency) {
vision_deficiency_filter_ = nullptr;
} else {
AtomicString url = CreateVisionDeficiencyFilterUrl(vision_deficiency_);
cssvalue::CSSURIValue css_uri_value(url);
SVGResource* svg_resource = css_uri_value.EnsureResourceReference();
// Note: The fact that we're using data: URLs here is an
// implementation detail. Emulating vision deficiencies should still
// work even if the Document's Content-Security-Policy disallows
// data: URLs.
svg_resource->LoadWithoutCSP(GetDocument());
vision_deficiency_filter_ =
MakeGarbageCollected<ReferenceFilterOperation>(url, svg_resource);
}
}
void StyleEngine::VisionDeficiencyChanged() {
MarkViewportStyleDirty();
}
void StyleEngine::ApplyVisionDeficiencyStyle(
scoped_refptr<ComputedStyle> layout_view_style) {
LoadVisionDeficiencyFilter();
if (vision_deficiency_filter_) {
FilterOperations ops;
ops.Operations().push_back(vision_deficiency_filter_);
layout_view_style->SetFilter(ops);
}
}
const MediaQueryEvaluator& StyleEngine::EnsureMediaQueryEvaluator() {
if (!media_query_evaluator_) {
if (GetDocument().GetFrame()) {
media_query_evaluator_ =
MakeGarbageCollected<MediaQueryEvaluator>(GetDocument().GetFrame());
} else {
media_query_evaluator_ = MakeGarbageCollected<MediaQueryEvaluator>("all");
}
}
return *media_query_evaluator_;
}
bool StyleEngine::MediaQueryAffectedByViewportChange() {
DCHECK(!IsHTMLImport());
DCHECK(global_rule_set_);
return EnsureMediaQueryEvaluator().DidResultsChange(
global_rule_set_->GetRuleFeatureSet()
.ViewportDependentMediaQueryResults());
}
bool StyleEngine::MediaQueryAffectedByDeviceChange() {
DCHECK(!IsHTMLImport());
DCHECK(global_rule_set_);
return EnsureMediaQueryEvaluator().DidResultsChange(
global_rule_set_->GetRuleFeatureSet().DeviceDependentMediaQueryResults());
}
bool StyleEngine::UpdateRemUnits(const ComputedStyle* old_root_style,
const ComputedStyle* new_root_style) {
if (!new_root_style || !UsesRemUnits())
return false;
if (!old_root_style || old_root_style->SpecifiedFontSize() !=
new_root_style->SpecifiedFontSize()) {
// Resolved rem units are stored in the matched properties cache so we need
// to make sure to invalidate the cache if the documentElement font size
// changes.
GetStyleResolver().InvalidateMatchedPropertiesCache();
return true;
}
return false;
}
void StyleEngine::PropertyRegistryChanged() {
// TODO(timloh): Invalidate only elements with this custom property set
MarkAllElementsForStyleRecalc(StyleChangeReasonForTracing::Create(
style_change_reason::kPropertyRegistration));
if (resolver_)
resolver_->InvalidateMatchedPropertiesCache();
InvalidateInitialData();
}
void StyleEngine::EnvironmentVariableChanged() {
MarkAllElementsForStyleRecalc(StyleChangeReasonForTracing::Create(
style_change_reason::kPropertyRegistration));
if (resolver_)
resolver_->InvalidateMatchedPropertiesCache();
}
void StyleEngine::MarkForWhitespaceReattachment() {
DCHECK(GetDocument().InStyleRecalc());
for (auto element : whitespace_reattach_set_) {
if (element->NeedsReattachLayoutTree() || !element->GetLayoutObject())
continue;
// This element might be located inside a display locked subtree, so we
// might mark it for ReattachLayoutTree later on instead.
if (Element* locked_ancestor =
DisplayLockUtilities::NearestLockedInclusiveAncestor(*element)) {
locked_ancestor->GetDisplayLockContext()->AddToWhitespaceReattachSet(
*element);
continue;
}
DCHECK(!element->NeedsStyleRecalc());
DCHECK(!element->ChildNeedsStyleRecalc());
if (Node* first_child = LayoutTreeBuilderTraversal::FirstChild(*element))
first_child->MarkAncestorsWithChildNeedsReattachLayoutTree();
}
}
void StyleEngine::NodeWillBeRemoved(Node& node) {
if (auto* element = DynamicTo<Element>(node)) {
pending_invalidations_.RescheduleSiblingInvalidationsAsDescendants(
*element);
}
// Mark closest ancestor with with LayoutObject to have all whitespace
// children being considered for re-attachment during the layout tree build.
LayoutObject* layout_object = node.GetLayoutObject();
// The removed node does not have a layout object. No sibling whitespace nodes
// will change rendering.
if (!layout_object)
return;
// Floating or out-of-flow elements do not affect whitespace siblings.
if (!layout_object->AffectsWhitespaceSiblings())
return;
layout_object = layout_object->Parent();
while (layout_object->IsAnonymous())
layout_object = layout_object->Parent();
DCHECK(layout_object);
DCHECK(layout_object->GetNode());
if (auto* layout_object_element =
DynamicTo<Element>(layout_object->GetNode())) {
whitespace_reattach_set_.insert(layout_object_element);
GetDocument().ScheduleLayoutTreeUpdateIfNeeded();
}
}
void StyleEngine::ChildrenRemoved(ContainerNode& parent) {
if (!parent.isConnected())
return;
if (InDOMRemoval()) {
// This is necessary for nested removals. There are elements which
// removes parts of its UA shadow DOM as part of being removed which means
// we do a removal from within another removal where isConnected() is not
// completely up to date which would confuse this code. Instead we will
// clean traversal roots properly when we are called from the outer remove.
// TODO(crbug.com/882869): MediaControlLoadingPanelElement
// TODO(crbug.com/888448): TextFieldInputType::ListAttributeTargetChanged
return;
}
style_invalidation_root_.SubtreeModified(parent);
style_recalc_root_.SubtreeModified(parent);
DCHECK(!layout_tree_rebuild_root_.GetRootNode());
layout_tree_rebuild_root_.SubtreeModified(parent);
}
void StyleEngine::CollectMatchingUserRules(
ElementRuleCollector& collector) const {
for (unsigned i = 0; i < active_user_style_sheets_.size(); ++i) {
DCHECK(active_user_style_sheets_[i].second);
collector.CollectMatchingRules(
MatchRequest(active_user_style_sheets_[i].second, nullptr,
active_user_style_sheets_[i].first, i));
}
}
void StyleEngine::ClearPropertyRules() {
PropertyRegistration::RemoveDeclaredProperties(GetDocument());
}
void StyleEngine::ClearScrollTimelineRules() {
scroll_timeline_map_.clear();
}
void StyleEngine::AddPropertyRulesFromSheets(
const ActiveStyleSheetVector& sheets) {
for (const ActiveStyleSheet& active_sheet : sheets) {
if (RuleSet* rule_set = active_sheet.second)
AddPropertyRules(*rule_set);
}
}
void StyleEngine::AddScrollTimelineRulesFromSheets(
const ActiveStyleSheetVector& sheets) {
for (const ActiveStyleSheet& active_sheet : sheets) {
if (RuleSet* rule_set = active_sheet.second)
AddScrollTimelineRules(*rule_set);
}
}
bool StyleEngine::AddUserFontFaceRules(const RuleSet& rule_set) {
if (!font_selector_)
return false;
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))
font_selector_->GetFontFaceCache()->Add(font_face_rule, font_face);
}
if (resolver_ && font_face_rules.size())
resolver_->InvalidateMatchedPropertiesCache();
return font_face_rules.size();
}
void StyleEngine::AddUserKeyframeRules(const RuleSet& rule_set) {
const HeapVector<Member<StyleRuleKeyframes>> keyframes_rules =
rule_set.KeyframesRules();
for (unsigned i = 0; i < keyframes_rules.size(); ++i)
AddUserKeyframeStyle(keyframes_rules[i]);
}
void StyleEngine::AddUserKeyframeStyle(StyleRuleKeyframes* rule) {
AtomicString animation_name(rule->GetName());
if (rule->IsVendorPrefixed()) {
KeyframesRuleMap::iterator it = keyframes_rule_map_.find(animation_name);
if (it == keyframes_rule_map_.end())
keyframes_rule_map_.Set(animation_name, rule);
else if (it->value->IsVendorPrefixed())
keyframes_rule_map_.Set(animation_name, rule);
} else {
keyframes_rule_map_.Set(animation_name, rule);
}
}
void StyleEngine::AddPropertyRules(const RuleSet& rule_set) {
const HeapVector<Member<StyleRuleProperty>> property_rules =
rule_set.PropertyRules();
for (unsigned i = 0; i < property_rules.size(); ++i) {
StyleRuleProperty* rule = property_rules[i];
AtomicString name(rule->GetName());
PropertyRegistration::DeclareProperty(GetDocument(), name, *rule);
}
}
void StyleEngine::AddScrollTimelineRules(const RuleSet& rule_set) {
const HeapVector<Member<StyleRuleScrollTimeline>> scroll_timeline_rules =
rule_set.ScrollTimelineRules();
if (scroll_timeline_rules.IsEmpty())
return;
for (const auto& rule : scroll_timeline_rules)
scroll_timeline_map_.Set(AtomicString(rule->GetName()), rule);
MarkAllElementsForStyleRecalc(StyleChangeReasonForTracing::Create(
style_change_reason::kScrollTimeline));
}
StyleRuleKeyframes* StyleEngine::KeyframeStylesForAnimation(
const AtomicString& 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();
}
StyleRuleScrollTimeline* StyleEngine::FindScrollTimelineRule(
const AtomicString& name) {
return scroll_timeline_map_.at(name);
}
DocumentStyleEnvironmentVariables& StyleEngine::EnsureEnvironmentVariables() {
if (!environment_variables_) {
environment_variables_ = DocumentStyleEnvironmentVariables::Create(
StyleEnvironmentVariables::GetRootInstance(), *document_);
}
return *environment_variables_.get();
}
scoped_refptr<StyleInitialData> StyleEngine::MaybeCreateAndGetInitialData() {
if (initial_data_)
return initial_data_;
if (const PropertyRegistry* registry = document_->GetPropertyRegistry()) {
if (!registry->IsEmpty())
initial_data_ = StyleInitialData::Create(*registry);
}
return initial_data_;
}
void StyleEngine::UpdateStyleAndLayoutTreeForContainer(
Element& container,
const LogicalSize& logical_size,
LogicalAxes contained_axes) {
DCHECK(!style_recalc_root_.GetRootNode());
DCHECK(!container.NeedsStyleRecalc());
DCHECK(!in_container_query_style_recalc_);
base::AutoReset<bool> cq_recalc(&in_container_query_style_recalc_, true);
WritingMode writing_mode = container.ComputedStyleRef().GetWritingMode();
PhysicalSize physical_size = ToPhysicalSize(logical_size, writing_mode);
PhysicalAxes physical_axes = ToPhysicalAxes(contained_axes, writing_mode);
if (auto* evaluator = container.GetContainerQueryEvaluator()) {
if (!evaluator->ContainerChanged(physical_size, physical_axes))
return;
} else {
container.SetContainerQueryEvaluator(
MakeGarbageCollected<ContainerQueryEvaluator>(physical_size,
physical_axes));
}
style_recalc_root_.Update(nullptr, &container);
RecalcStyle({StyleRecalcChange::kRecalcContainerQueryDependent},
StyleRecalcContext());
// Nodes are marked for whitespace reattachment for DOM removal only. This set
// should have been cleared before layout.
DCHECK(!NeedsWhitespaceReattachment());
if (container.ChildNeedsReattachLayoutTree()) {
DCHECK(layout_tree_rebuild_root_.GetRootNode());
if (layout_tree_rebuild_root_.GetRootNode()->IsDocumentNode()) {
// Avoid traversing from outside the container root. We know none of the
// elements outside the subtree should be marked dirty in this pass, but
// we may have fallen back to the document root.
layout_tree_rebuild_root_.Clear();
layout_tree_rebuild_root_.Update(nullptr, &container);
}
RebuildLayoutTree();
}
GetDocument().GetLayoutView()->UpdateMarkersAndCountersAfterStyleChange();
}
void StyleEngine::RecalcStyle(StyleRecalcChange change,
const StyleRecalcContext& style_recalc_context) {
DCHECK(GetDocument().documentElement());
Element& root_element = style_recalc_root_.RootElement();
Element* parent = FlatTreeTraversal::ParentElement(root_element);
SelectorFilterRootScope filter_scope(parent);
root_element.RecalcStyle(change, style_recalc_context);
for (ContainerNode* ancestor = root_element.GetStyleRecalcParent(); ancestor;
ancestor = ancestor->GetStyleRecalcParent()) {
if (auto* ancestor_element = DynamicTo<Element>(ancestor))
ancestor_element->RecalcStyleForTraversalRootAncestor();
ancestor->ClearChildNeedsStyleRecalc();
}
style_recalc_root_.Clear();
if (!parent || IsA<HTMLBodyElement>(root_element))
PropagateWritingModeAndDirectionToHTMLRoot();
}
void StyleEngine::ClearEnsuredDescendantStyles(Element& root) {
Node* current = &root;
while (current) {
if (auto* element = DynamicTo<Element>(current)) {
if (const auto* style = element->GetComputedStyle()) {
DCHECK(style->IsEnsuredOutsideFlatTree());
element->SetComputedStyle(nullptr);
element->ClearNeedsStyleRecalc();
element->ClearChildNeedsStyleRecalc();
current = FlatTreeTraversal::Next(*current, &root);
continue;
}
}
current = FlatTreeTraversal::NextSkippingChildren(*current, &root);
}
}
void StyleEngine::RebuildLayoutTree() {
DCHECK(GetDocument().documentElement());
DCHECK(!InRebuildLayoutTree());
base::AutoReset<bool> rebuild_scope(&in_layout_tree_rebuild_, true);
// We need a root scope here in case we recalc style for ::first-letter
// elements as part of UpdateFirstLetterPseudoElement.
SelectorFilterRootScope filter_scope(nullptr);
Element& root_element = layout_tree_rebuild_root_.RootElement();
{
WhitespaceAttacher whitespace_attacher;
root_element.RebuildLayoutTree(whitespace_attacher);
}
for (ContainerNode* ancestor = root_element.GetReattachParent(); ancestor;
ancestor = ancestor->GetReattachParent()) {
if (auto* ancestor_element = DynamicTo<Element>(ancestor))
ancestor_element->RebuildLayoutTreeForTraversalRootAncestor();
ancestor->ClearChildNeedsStyleRecalc();
ancestor->ClearChildNeedsReattachLayoutTree();
}
layout_tree_rebuild_root_.Clear();
}
void StyleEngine::UpdateStyleAndLayoutTree() {
// All of layout tree dirtiness and rebuilding needs to happen on a stable
// flat tree. We have an invariant that all of that happens in this method
// as a result of style recalc and the following layout tree rebuild.
//
// NeedsReattachLayoutTree() marks dirty up the flat tree ancestors. Re-
// slotting on a dirty tree could break ancestor chains and fail to update the
// tree properly.
DCHECK(!NeedsLayoutTreeRebuild());
UpdateViewportStyle();
if (Element* document_element = GetDocument().documentElement()) {
NthIndexCache nth_index_cache(GetDocument());
if (NeedsStyleRecalc()) {
TRACE_EVENT0("blink,blink_style", "Document::recalcStyle");
SCOPED_BLINK_UMA_HISTOGRAM_TIMER_HIGHRES("Style.RecalcTime");
Element* viewport_defining = GetDocument().ViewportDefiningElement();
RecalcStyle();
if (viewport_defining != GetDocument().ViewportDefiningElement())
ViewportDefiningElementDidChange();
}
MarkForWhitespaceReattachment();
if (NeedsLayoutTreeRebuild()) {
TRACE_EVENT0("blink,blink_style", "Document::rebuildLayoutTree");
SCOPED_BLINK_UMA_HISTOGRAM_TIMER_HIGHRES("Style.RebuildLayoutTreeTime");
RebuildLayoutTree();
}
} else {
style_recalc_root_.Clear();
}
ClearWhitespaceReattachSet();
UpdateColorSchemeBackground();
}
void StyleEngine::ViewportDefiningElementDidChange() {
// Guarded by if-test in UpdateStyleAndLayoutTree().
DCHECK(GetDocument().documentElement());
if (GetDocument().documentElement()->NeedsReattachLayoutTree())
return;
HTMLBodyElement* body = GetDocument().FirstBodyElement();
if (!body || body->NeedsReattachLayoutTree())
return;
LayoutObject* layout_object = body->GetLayoutObject();
if (layout_object && layout_object->IsLayoutBlock()) {
// When the overflow style for documentElement changes to or from visible,
// it changes whether the body element's box should have scrollable overflow
// on its own box or propagated to the viewport. If the body style did not
// need a recalc, this will not be updated as its done as part of setting
// ComputedStyle on the LayoutObject. Force a SetStyle for body when the
// ViewportDefiningElement changes in order to trigger an update of
// IsScrollContainer() and the PaintLayer in StyleDidChange().
layout_object->SetStyle(ComputedStyle::Clone(*layout_object->Style()));
}
}
void StyleEngine::UpdateStyleInvalidationRoot(ContainerNode* ancestor,
Node* dirty_node) {
DCHECK(!IsHTMLImport());
if (GetDocument().IsActive()) {
if (InDOMRemoval()) {
ancestor = nullptr;
dirty_node = document_;
}
style_invalidation_root_.Update(ancestor, dirty_node);
}
}
void StyleEngine::UpdateStyleRecalcRoot(ContainerNode* ancestor,
Node* dirty_node) {
if (!GetDocument().IsActive())
return;
// We have at least one instance where we mark style dirty from style recalc
// (from LayoutTextControl::StyleDidChange()). That means we are in the
// process of traversing down the tree from the recalc root. Any updates to
// the style recalc root will be cleared after the style recalc traversal
// finishes and updating it may just trigger sanity DCHECKs in
// StyleTraversalRoot. Just return here instead.
if (GetDocument().InStyleRecalc()) {
DCHECK(allow_mark_style_dirty_from_recalc_);
return;
}
DCHECK(!InRebuildLayoutTree());
if (InDOMRemoval()) {
ancestor = nullptr;
dirty_node = document_;
}
style_recalc_root_.Update(ancestor, dirty_node);
}
void StyleEngine::UpdateLayoutTreeRebuildRoot(ContainerNode* ancestor,
Node* dirty_node) {
DCHECK(!InDOMRemoval());
if (!GetDocument().IsActive())
return;
if (InRebuildLayoutTree()) {
DCHECK(allow_mark_for_reattach_from_rebuild_layout_tree_);
return;
}
DCHECK(GetDocument().InStyleRecalc());
DCHECK(dirty_node);
if (!ancestor && !dirty_node->NeedsReattachLayoutTree() &&
!dirty_node->ChildNeedsReattachLayoutTree()) {
// The StyleTraversalRoot requires the root node to be dirty or child-dirty.
// When we mark for whitespace re-attachment, we only mark the ancestor
// chain. Use the parent as the dirty node if the dirty_node is not dirty.
dirty_node = dirty_node->GetReattachParent();
DCHECK(dirty_node && dirty_node->ChildNeedsReattachLayoutTree());
}
layout_tree_rebuild_root_.Update(ancestor, dirty_node);
}
bool StyleEngine::SupportsDarkColorScheme() {
if (!meta_color_scheme_)
return false;
bool has_light = false;
bool has_dark = false;
if (const auto* scheme_list = DynamicTo<CSSValueList>(*meta_color_scheme_)) {
for (auto& item : *scheme_list) {
if (const auto* ident = DynamicTo<CSSIdentifierValue>(*item)) {
if (ident->GetValueID() == CSSValueID::kDark)
has_dark = true;
else if (ident->GetValueID() == CSSValueID::kLight)
has_light = true;
}
}
}
return has_dark &&
(!has_light ||
preferred_color_scheme_ == mojom::blink::PreferredColorScheme::kDark);
}
void StyleEngine::UpdateColorScheme() {
auto* settings = GetDocument().GetSettings();
auto* web_theme_engine =
Platform::Current() ? Platform::Current()->ThemeEngine() : nullptr;
if (!settings || !web_theme_engine)
return;
ForcedColors old_forced_colors = forced_colors_;
forced_colors_ = web_theme_engine->GetForcedColors();
mojom::blink::PreferredColorScheme old_preferred_color_scheme =
preferred_color_scheme_;
preferred_color_scheme_ = settings->GetPreferredColorScheme();
if (const auto* overrides =
GetDocument().GetPage()->GetMediaFeatureOverrides()) {
MediaQueryExpValue value = overrides->GetOverride("prefers-color-scheme");
if (value.IsValid())
preferred_color_scheme_ = CSSValueIDToPreferredColorScheme(value.id);
}
if (!SupportsDarkColorScheme() && settings->GetForceDarkModeEnabled()) {
// Make sure we don't match (prefers-color-scheme: dark) when forced
// darkening is enabled.
preferred_color_scheme_ = mojom::blink::PreferredColorScheme::kLight;
}
if (GetDocument().Printing())
preferred_color_scheme_ = mojom::blink::PreferredColorScheme::kLight;
if (forced_colors_ != old_forced_colors ||
preferred_color_scheme_ != old_preferred_color_scheme) {
PlatformColorsChanged();
}
UpdateColorSchemeMetrics();
}
void StyleEngine::UpdateColorSchemeMetrics() {
auto* settings = GetDocument().GetSettings();
if (settings->GetForceDarkModeEnabled())
UseCounter::Count(GetDocument(), WebFeature::kForcedDarkMode);
// True if the preferred color scheme will match dark.
if (preferred_color_scheme_ == mojom::blink::PreferredColorScheme::kDark)
UseCounter::Count(GetDocument(), WebFeature::kPreferredColorSchemeDark);
// This is equal to kPreferredColorSchemeDark in most cases, but can differ
// with forced dark mode. With the system in dark mode and forced dark mode
// enabled, the preferred color scheme can be light while the setting is dark.
if (settings->GetPreferredColorScheme() ==
mojom::blink::PreferredColorScheme::kDark) {
UseCounter::Count(GetDocument(),
WebFeature::kPreferredColorSchemeDarkSetting);
}
// Record kColorSchemeDarkSupportedOnRoot if the meta color-scheme contains
// dark (though dark may not be used). This metric is also recorded in
// longhands_custom.cc (see: ColorScheme::ApplyValue) if the root style
// color-scheme contains dark.
if (meta_color_scheme_) {
const auto* scheme_list = DynamicTo<CSSValueList>(*meta_color_scheme_);
if (scheme_list) {
for (auto& item : *scheme_list) {
const auto* ident = DynamicTo<CSSIdentifierValue>(*item);
if (ident && ident->GetValueID() == CSSValueID::kDark) {
UseCounter::Count(GetDocument(),
WebFeature::kColorSchemeDarkSupportedOnRoot);
}
}
}
}
}
void StyleEngine::ColorSchemeChanged() {
UpdateColorScheme();
}
void StyleEngine::SetColorSchemeFromMeta(const CSSValue* color_scheme) {
meta_color_scheme_ = color_scheme;
DCHECK(GetDocument().documentElement());
GetDocument().documentElement()->SetNeedsStyleRecalc(
kLocalStyleChange, StyleChangeReasonForTracing::Create(
style_change_reason::kPlatformColorChange));
UpdateColorScheme();
}
void StyleEngine::UpdateColorSchemeBackground(bool color_scheme_changed) {
LocalFrameView* view = GetDocument().View();
if (!view)
return;
LocalFrameView::UseColorAdjustBackground use_color_adjust_background =
LocalFrameView::UseColorAdjustBackground::kNo;
if (forced_colors_ != ForcedColors::kNone) {
if (GetDocument().IsInMainFrame()) {
use_color_adjust_background =
LocalFrameView::UseColorAdjustBackground::kIfBaseNotTransparent;
}
} else {
// Find out if we should use a canvas color that is different from the
// view's base background color in order to match the root element color-
// scheme. See spec:
// https://drafts.csswg.org/css-color-adjust/#color-scheme-effect
mojom::blink::ColorScheme root_color_scheme =
mojom::blink::ColorScheme::kLight;
if (auto* root_element = GetDocument().documentElement()) {
if (const ComputedStyle* style = root_element->GetComputedStyle())
root_color_scheme = style->UsedColorSchemeForInitialColors();
else if (SupportsDarkColorScheme())
root_color_scheme = mojom::blink::ColorScheme::kDark;
}
color_scheme_background_ =
root_color_scheme == mojom::blink::ColorScheme::kLight
? Color::kWhite
: Color(0x12, 0x12, 0x12);
if (GetDocument().IsInMainFrame()) {
if (root_color_scheme == mojom::blink::ColorScheme::kDark) {
use_color_adjust_background =
LocalFrameView::UseColorAdjustBackground::kIfBaseNotTransparent;
}
} else if (root_color_scheme != owner_color_scheme_) {
// Iframes should paint a solid background if the embedding iframe has a
// used color-scheme different from the used color-scheme of the embedded
// root element. Normally, iframes as transparent by default.
use_color_adjust_background =
LocalFrameView::UseColorAdjustBackground::kYes;
}
}
view->SetUseColorAdjustBackground(use_color_adjust_background,
color_scheme_changed);
}
void StyleEngine::SetOwnerColorScheme(mojom::blink::ColorScheme color_scheme) {
DCHECK(!GetDocument().IsInMainFrame());
if (owner_color_scheme_ == color_scheme)
return;
owner_color_scheme_ = color_scheme;
UpdateColorSchemeBackground(true);
}
void StyleEngine::UpdateForcedBackgroundColor() {
forced_background_color_ = LayoutTheme::GetTheme().SystemColor(
CSSValueID::kCanvas, mojom::blink::ColorScheme::kLight);
}
Color StyleEngine::ColorAdjustBackgroundColor() const {
if (forced_colors_ != ForcedColors::kNone)
return ForcedBackgroundColor();
return color_scheme_background_;
}
void StyleEngine::MarkAllElementsForStyleRecalc(
const StyleChangeReasonForTracing& reason) {
if (Element* root = GetDocument().documentElement())
root->SetNeedsStyleRecalc(kSubtreeStyleChange, reason);
}
void StyleEngine::UpdateViewportStyle() {
if (!viewport_style_dirty_)
return;
viewport_style_dirty_ = false;
scoped_refptr<ComputedStyle> viewport_style = resolver_->StyleForViewport();
if (ComputedStyle::ComputeDifference(
viewport_style.get(), GetDocument().GetLayoutView()->Style()) !=
ComputedStyle::Difference::kEqual) {
GetDocument().GetLayoutView()->SetStyle(std::move(viewport_style));
}
}
bool StyleEngine::NeedsFullStyleUpdate() const {
return NeedsActiveStyleUpdate() || NeedsWhitespaceReattachment() ||
IsViewportStyleDirty();
}
void StyleEngine::PropagateWritingModeAndDirectionToHTMLRoot() {
if (HTMLHtmlElement* root_element =
DynamicTo<HTMLHtmlElement>(GetDocument().documentElement()))
root_element->PropagateWritingModeAndDirectionFromBody();
}
CounterStyleMap& StyleEngine::EnsureUserCounterStyleMap() {
if (!user_counter_style_map_) {
user_counter_style_map_ =
CounterStyleMap::CreateUserCounterStyleMap(GetDocument());
}
return *user_counter_style_map_;
}
const CounterStyle& StyleEngine::FindCounterStyleAcrossScopes(
const AtomicString& name,
const TreeScope* scope) const {
CounterStyleMap* target_map = nullptr;
while (scope) {
if (CounterStyleMap* map =
CounterStyleMap::GetAuthorCounterStyleMap(*scope)) {
target_map = map;
break;
}
scope = scope->ParentTreeScope();
}
if (!target_map && user_counter_style_map_)
target_map = user_counter_style_map_;
if (!target_map)
target_map = CounterStyleMap::GetUACounterStyleMap();
if (CounterStyle* result = target_map->FindCounterStyleAcrossScopes(name))
return *result;
return CounterStyle::GetDecimal();
}
void StyleEngine::Trace(Visitor* visitor) const {
visitor->Trace(document_);
visitor->Trace(injected_user_style_sheets_);
visitor->Trace(injected_author_style_sheets_);
visitor->Trace(active_user_style_sheets_);
visitor->Trace(custom_element_default_style_sheets_);
visitor->Trace(keyframes_rule_map_);
visitor->Trace(user_counter_style_map_);
visitor->Trace(scroll_timeline_map_);
visitor->Trace(inspector_style_sheet_);
visitor->Trace(document_style_sheet_collection_);
visitor->Trace(style_sheet_collection_map_);
visitor->Trace(dirty_tree_scopes_);
visitor->Trace(active_tree_scopes_);
visitor->Trace(resolver_);
visitor->Trace(vision_deficiency_filter_);
visitor->Trace(viewport_resolver_);
visitor->Trace(media_query_evaluator_);
visitor->Trace(global_rule_set_);
visitor->Trace(pending_invalidations_);
visitor->Trace(style_invalidation_root_);
visitor->Trace(style_recalc_root_);
visitor->Trace(layout_tree_rebuild_root_);
visitor->Trace(whitespace_reattach_set_);
visitor->Trace(font_selector_);
visitor->Trace(text_to_sheet_cache_);
visitor->Trace(sheet_to_text_cache_);
visitor->Trace(tracker_);
visitor->Trace(meta_color_scheme_);
visitor->Trace(text_tracks_);
visitor->Trace(vtt_originating_element_);
FontSelectorClient::Trace(visitor);
}
} // namespace blink