LibWeb: Avoid media rule reevaluation for matchMedia
Separate MediaQueryList change reporting from stylesheet media rule invalidation. Creating matchMedia() objects evaluates their own baseline state, but should not make the next style update walk all active stylesheets when the media environment has not changed. This avoids continuous stylesheet media query reevaluation during YouTube video playback, where repeated matchMedia() creation can make style flushes do unnecessary work.
This commit is contained in:
parent
2c33de1583
commit
49730156ae
6 changed files with 81 additions and 37 deletions
|
|
@ -44,6 +44,8 @@ static void invalidate_style_after_media_rule_changes(DOM::Node& root, MediaQuer
|
|||
|
||||
void evaluate_media_rules_and_invalidate_style(DOM::Document& document)
|
||||
{
|
||||
++document.style_invalidation_counters().media_rule_evaluations;
|
||||
|
||||
bool document_media_queries_changed_match_state = false;
|
||||
MediaQueryRuleInvalidation document_invalidation;
|
||||
document.style_scope().for_each_active_css_style_sheet([&](CSS::CSSStyleSheet& style_sheet) {
|
||||
|
|
|
|||
|
|
@ -237,7 +237,7 @@ static Optional<u64> s_style_invalidation_counter_dump_interval;
|
|||
static void dump_style_invalidation_counters(Document const& document)
|
||||
{
|
||||
auto const& counters = document.style_invalidation_counters();
|
||||
dbgln("Style invalidation counters for {}: styleInvalidations={}, fullStyleInvalidations={}, elementStyleRecomputations={}, elementStyleNoopRecomputations={}, elementInheritedStyleRecomputations={}, elementInheritedStyleNoopRecomputations={}, previousSiblingInvalidationWalkVisits={}, hasAncestorWalkInvocations={}, hasAncestorWalkVisits={}, hasAncestorSiblingElementChecks={}, hasInvalidationMetadataCandidates={}, hasMatchInvocations={}, hasResultCacheHits={}, hasResultCacheMisses={}",
|
||||
dbgln("Style invalidation counters for {}: styleInvalidations={}, fullStyleInvalidations={}, elementStyleRecomputations={}, elementStyleNoopRecomputations={}, elementInheritedStyleRecomputations={}, elementInheritedStyleNoopRecomputations={}, previousSiblingInvalidationWalkVisits={}, mediaRuleEvaluations={}, hasAncestorWalkInvocations={}, hasAncestorWalkVisits={}, hasAncestorSiblingElementChecks={}, hasInvalidationMetadataCandidates={}, hasMatchInvocations={}, hasResultCacheHits={}, hasResultCacheMisses={}",
|
||||
document.url_string(),
|
||||
counters.style_invalidations,
|
||||
counters.full_style_invalidations,
|
||||
|
|
@ -246,6 +246,7 @@ static void dump_style_invalidation_counters(Document const& document)
|
|||
counters.element_inherited_style_recomputations,
|
||||
counters.element_inherited_style_noop_recomputations,
|
||||
counters.previous_sibling_invalidation_walk_visits,
|
||||
counters.media_rule_evaluations,
|
||||
counters.has_ancestor_walk_invocations,
|
||||
counters.has_ancestor_walk_visits,
|
||||
counters.has_ancestor_sibling_element_checks,
|
||||
|
|
@ -2201,14 +2202,14 @@ void Document::update_style()
|
|||
CSS::Invalidation::invalidate_style_for_pending_has_mutations(*this);
|
||||
}
|
||||
|
||||
if (!m_style_invalidator->has_pending_invalidations() && !needs_full_style_update() && !needs_style_update() && !child_needs_style_update() && !m_needs_media_query_evaluation)
|
||||
if (!m_style_invalidator->has_pending_invalidations() && !needs_full_style_update() && !needs_style_update() && !child_needs_style_update() && !m_needs_media_rule_evaluation)
|
||||
return;
|
||||
|
||||
// NOTE: If this is a document hosting <template> contents, style update is unnecessary.
|
||||
if (m_created_for_appropriate_template_contents)
|
||||
return;
|
||||
|
||||
if (m_needs_media_query_evaluation)
|
||||
if (m_needs_media_rule_evaluation)
|
||||
evaluate_media_rules();
|
||||
|
||||
if (!m_style_invalidator->has_pending_invalidations() && !needs_full_style_update() && !needs_style_update() && !child_needs_style_update())
|
||||
|
|
@ -2327,7 +2328,7 @@ CSS::ComputedProperties const* Document::update_style_for_element(AbstractElemen
|
|||
|
||||
// Media query evaluation can enqueue normal style invalidations, so do it before deciding whether the full
|
||||
// style traversal needs to run.
|
||||
if (m_needs_media_query_evaluation)
|
||||
if (m_needs_media_rule_evaluation)
|
||||
evaluate_media_rules();
|
||||
|
||||
if (!m_is_running_update_layout
|
||||
|
|
@ -2451,7 +2452,7 @@ bool Document::element_needs_style_update(AbstractElement const& abstract_elemen
|
|||
return true;
|
||||
if (m_needs_invalidation_of_elements_affected_by_has)
|
||||
return true;
|
||||
if (m_needs_media_query_evaluation)
|
||||
if (m_needs_media_rule_evaluation)
|
||||
return true;
|
||||
if (m_style_invalidator->has_pending_invalidations())
|
||||
return true;
|
||||
|
|
@ -4671,55 +4672,61 @@ void Document::run_the_scroll_steps()
|
|||
|
||||
void Document::add_media_query_list(GC::Ref<CSS::MediaQueryList> media_query_list)
|
||||
{
|
||||
m_needs_media_query_evaluation = true;
|
||||
m_media_query_lists.append(media_query_list);
|
||||
m_needs_media_query_list_evaluation = true;
|
||||
}
|
||||
|
||||
// https://drafts.csswg.org/cssom-view/#evaluate-media-queries-and-report-changes
|
||||
void Document::evaluate_media_queries_and_report_changes()
|
||||
{
|
||||
if (!m_needs_media_query_evaluation)
|
||||
if (!m_needs_media_query_list_evaluation && !m_needs_media_rule_evaluation)
|
||||
return;
|
||||
m_needs_media_query_evaluation = false;
|
||||
|
||||
// NOTE: Not in the spec, but we take this opportunity to prune null WeakPtrs.
|
||||
m_media_query_lists.remove_all_matching([](auto& it) {
|
||||
return !it;
|
||||
});
|
||||
bool evaluate_media_query_lists = m_needs_media_query_list_evaluation;
|
||||
m_needs_media_query_list_evaluation = false;
|
||||
|
||||
// 1. For each MediaQueryList object target that has doc as its document,
|
||||
// in the order they were created, oldest first, run these substeps:
|
||||
for (auto& media_query_list_ptr : m_media_query_lists) {
|
||||
// 1. If target’s matches state has changed since the last time these steps
|
||||
// were run, fire an event at target using the MediaQueryListEvent constructor,
|
||||
// with its type attribute initialized to change, its isTrusted attribute
|
||||
// initialized to true, its media attribute initialized to target’s media,
|
||||
// and its matches attribute initialized to target’s matches state.
|
||||
if (!media_query_list_ptr)
|
||||
continue;
|
||||
GC::Ptr<CSS::MediaQueryList> media_query_list = media_query_list_ptr.ptr();
|
||||
bool did_match = media_query_list->matches();
|
||||
bool now_matches = media_query_list->evaluate();
|
||||
if (evaluate_media_query_lists) {
|
||||
// NOTE: Not in the spec, but we take this opportunity to prune null WeakPtrs.
|
||||
m_media_query_lists.remove_all_matching([](auto& it) {
|
||||
return !it;
|
||||
});
|
||||
|
||||
auto did_change_internally = media_query_list->has_changed_state();
|
||||
media_query_list->set_has_changed_state(false);
|
||||
// 1. For each MediaQueryList object target that has doc as its document,
|
||||
// in the order they were created, oldest first, run these substeps:
|
||||
for (auto& media_query_list_ptr : m_media_query_lists) {
|
||||
// 1. If target’s matches state has changed since the last time these steps
|
||||
// were run, fire an event at target using the MediaQueryListEvent constructor,
|
||||
// with its type attribute initialized to change, its isTrusted attribute
|
||||
// initialized to true, its media attribute initialized to target’s media,
|
||||
// and its matches attribute initialized to target’s matches state.
|
||||
if (!media_query_list_ptr)
|
||||
continue;
|
||||
GC::Ptr<CSS::MediaQueryList> media_query_list = media_query_list_ptr.ptr();
|
||||
bool did_match = media_query_list->matches();
|
||||
bool now_matches = media_query_list->evaluate();
|
||||
|
||||
if (did_change_internally == true || did_match != now_matches) {
|
||||
Bindings::MediaQueryListEventInit init;
|
||||
init.media = media_query_list->media();
|
||||
init.matches = now_matches;
|
||||
auto event = CSS::MediaQueryListEvent::create(realm(), HTML::EventNames::change, init);
|
||||
event->set_is_trusted(true);
|
||||
media_query_list->dispatch_event(*event);
|
||||
auto did_change_internally = media_query_list->has_changed_state();
|
||||
media_query_list->set_has_changed_state(false);
|
||||
|
||||
if (did_change_internally == true || did_match != now_matches) {
|
||||
Bindings::MediaQueryListEventInit init;
|
||||
init.media = media_query_list->media();
|
||||
init.matches = now_matches;
|
||||
auto event = CSS::MediaQueryListEvent::create(realm(), HTML::EventNames::change, init);
|
||||
event->set_is_trusted(true);
|
||||
media_query_list->dispatch_event(*event);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Also not in the spec, but this is as good a place as any to evaluate @media rules!
|
||||
evaluate_media_rules();
|
||||
if (m_needs_media_rule_evaluation)
|
||||
evaluate_media_rules();
|
||||
}
|
||||
|
||||
void Document::evaluate_media_rules()
|
||||
{
|
||||
m_needs_media_rule_evaluation = false;
|
||||
CSS::Invalidation::evaluate_media_rules_and_invalidate_style(*this);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -629,7 +629,11 @@ public:
|
|||
void run_the_scroll_steps();
|
||||
|
||||
void evaluate_media_queries_and_report_changes();
|
||||
void set_needs_media_query_evaluation() { m_needs_media_query_evaluation = true; }
|
||||
void set_needs_media_query_evaluation()
|
||||
{
|
||||
m_needs_media_query_list_evaluation = true;
|
||||
m_needs_media_rule_evaluation = true;
|
||||
}
|
||||
void add_media_query_list(GC::Ref<CSS::MediaQueryList>);
|
||||
|
||||
GC::Ref<CSS::VisualViewport> visual_viewport();
|
||||
|
|
@ -907,6 +911,7 @@ public:
|
|||
u64 element_inherited_style_noop_recomputations { 0 };
|
||||
u64 previous_sibling_invalidation_walk_visits { 0 };
|
||||
u64 descendant_slot_invalidation_subtree_scans { 0 };
|
||||
u64 media_rule_evaluations { 0 };
|
||||
};
|
||||
StyleInvalidationCounters& style_invalidation_counters() const { return m_style_invalidation_counters; }
|
||||
void reset_style_invalidation_counters() const;
|
||||
|
|
@ -1364,7 +1369,8 @@ private:
|
|||
Vector<PendingScrollEvent> m_pending_scroll_events;
|
||||
|
||||
// Used by evaluate_media_queries_and_report_changes().
|
||||
bool m_needs_media_query_evaluation { false };
|
||||
bool m_needs_media_query_list_evaluation { false };
|
||||
bool m_needs_media_rule_evaluation { false };
|
||||
Vector<GC::Weak<CSS::MediaQueryList>> m_media_query_lists;
|
||||
|
||||
bool m_needs_full_style_update { false };
|
||||
|
|
|
|||
|
|
@ -751,6 +751,7 @@ JS::Object* Internals::get_style_invalidation_counters()
|
|||
object->define_direct_property("elementInheritedStyleNoopRecomputations"_utf16_fly_string, JS::Value(counters.element_inherited_style_noop_recomputations), JS::default_attributes);
|
||||
object->define_direct_property("previousSiblingInvalidationWalkVisits"_utf16_fly_string, JS::Value(counters.previous_sibling_invalidation_walk_visits), JS::default_attributes);
|
||||
object->define_direct_property("descendantSlotInvalidationSubtreeScans"_utf16_fly_string, JS::Value(counters.descendant_slot_invalidation_subtree_scans), JS::default_attributes);
|
||||
object->define_direct_property("mediaRuleEvaluations"_utf16_fly_string, JS::Value(counters.media_rule_evaluations), JS::default_attributes);
|
||||
return object;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1 @@
|
|||
media rule evaluations after matchMedia: 0
|
||||
|
|
@ -0,0 +1,27 @@
|
|||
<!DOCTYPE html>
|
||||
<meta charset="utf-8">
|
||||
<script src="../../include.js"></script>
|
||||
<script>
|
||||
test(() => {
|
||||
const style = document.createElement("style");
|
||||
style.textContent = `
|
||||
@media (min-width: 1px) {
|
||||
body { color: rgb(1, 2, 3); }
|
||||
}
|
||||
`;
|
||||
document.head.appendChild(style);
|
||||
document.body.offsetWidth;
|
||||
|
||||
internals.resetStyleInvalidationCounters();
|
||||
|
||||
for (let i = 0; i < 100; ++i) {
|
||||
const mediaQueryList = matchMedia("(min-width: 1px)");
|
||||
if (!mediaQueryList.matches)
|
||||
throw new Error("MediaQueryList should match current viewport");
|
||||
internals.updateStyle();
|
||||
}
|
||||
|
||||
const counters = internals.getStyleInvalidationCounters();
|
||||
println(`media rule evaluations after matchMedia: ${counters.mediaRuleEvaluations}`);
|
||||
});
|
||||
</script>
|
||||
Loading…
Reference in a new issue