LibWeb: Don't lose change events on MediaQueryList internal state change

MediaQueryList will now remember if a state change occurred when
evaluating its match state. This memory can then be used by the document
later on when it's updating all queries, to ensure that we don't forget
to fire at least one change event.

This also required plumbing the system visibility state to initial
about:blank documents, since otherwise they would be stuck in "hidden"
state indefinitely and never evaluate their media queries.
This commit is contained in:
Andreas Kling 2025-02-13 14:09:39 +01:00 committed by Andreas Kling
parent 6fd24c2a68
commit c9cd795257
Notes: github-actions[bot] 2025-02-13 19:53:28 +00:00
7 changed files with 78 additions and 23 deletions

View file

@ -54,6 +54,14 @@ bool MediaQueryList::matches() const
if (m_media.is_empty())
return true;
bool did_match = false;
for (auto const& media : m_media) {
if (media->matches()) {
did_match = true;
break;
}
}
// NOTE: If our document is inside a frame, we need to update layout
// since that may cause our frame (and thus viewport) to resize.
if (auto container_document = m_document->container_document()) {
@ -61,12 +69,18 @@ bool MediaQueryList::matches() const
const_cast<MediaQueryList*>(this)->evaluate();
}
bool now_matches = false;
for (auto& media : m_media) {
if (media->matches())
return true;
if (media->matches()) {
now_matches = true;
break;
}
}
return false;
if (did_match != now_matches)
m_has_changed_state = true;
return now_matches;
}
bool MediaQueryList::evaluate()

View file

@ -32,6 +32,9 @@ public:
void set_onchange(WebIDL::CallbackType*);
WebIDL::CallbackType* onchange();
[[nodiscard]] Optional<bool> const& has_changed_state() const { return m_has_changed_state; }
void set_has_changed_state(bool has_changed_state) { m_has_changed_state = has_changed_state; }
private:
MediaQueryList(DOM::Document&, Vector<NonnullRefPtr<MediaQuery>>&&);
@ -40,6 +43,8 @@ private:
GC::Ref<DOM::Document> m_document;
Vector<NonnullRefPtr<MediaQuery>> m_media;
mutable Optional<bool> m_has_changed_state { false };
};
}

View file

@ -3293,7 +3293,10 @@ void Document::evaluate_media_queries_and_report_changes()
bool did_match = media_query_list->matches();
bool now_matches = media_query_list->evaluate();
if (did_match != now_matches) {
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) {
CSS::MediaQueryListEventInit init;
init.media = media_query_list->media();
init.matches = now_matches;

View file

@ -109,6 +109,9 @@ WebIDL::ExceptionOr<void> NavigableContainer::create_new_child_navigable(GC::Ptr
// 11. Let traversable be parentNavigable's traversable navigable.
auto traversable = parent_navigable->traversable_navigable();
// AD-HOC: Let the initial about:blank document inherit the system visibility state from traversable.
document->update_the_visibility_state(traversable->system_visibility_state());
// 12. Append the following session history traversal steps to traversable:
traversable->append_session_history_traversal_steps(GC::create_function(heap(), [traversable, navigable, parent_navigable, history_entry, after_session_history_update] {
// 1. Let parentDocState be parentNavigable's active session history entry's document state.

View file

@ -13,16 +13,46 @@ Viewport <#document> at (0,0) content-size 800x600 children: not-inline
frag 7 from NavigableContainerViewport start: 0, length: 0, rect: [18,228 10x10] baseline: 30
frag 8 from NavigableContainerViewport start: 0, length: 0, rect: [18,258 10x10] baseline: 30
frag 9 from NavigableContainerViewport start: 0, length: 0, rect: [18,288 10x10] baseline: 30
NavigableContainerViewport <iframe> at (18,18) content-size 10x10 children: not-inline (url: about:blank)
NavigableContainerViewport <iframe> at (18,48) content-size 10x10 children: not-inline (url: about:blank)
NavigableContainerViewport <iframe> at (18,78) content-size 10x10 children: not-inline (url: about:blank)
NavigableContainerViewport <iframe> at (18,108) content-size 10x10 children: not-inline (url: about:blank)
NavigableContainerViewport <iframe> at (18,138) content-size 10x10 children: not-inline (url: about:blank)
NavigableContainerViewport <iframe> at (18,168) content-size 10x10 children: not-inline (url: about:blank)
NavigableContainerViewport <iframe> at (18,198) content-size 10x10 children: not-inline (url: about:blank)
NavigableContainerViewport <iframe> at (18,228) content-size 10x10 children: not-inline (url: about:blank)
NavigableContainerViewport <iframe> at (18,258) content-size 10x10 children: not-inline (url: about:blank)
NavigableContainerViewport <iframe> at (18,288) content-size 10x10 children: not-inline (url: about:blank)
NavigableContainerViewport <iframe> at (18,18) content-size 10x10 children: not-inline (url: about:blank) Viewport <#document> at (0,0) content-size 10x10 children: not-inline
BlockContainer <html> at (0,0) content-size 10x16 [BFC] children: not-inline
BlockContainer <body> at (8,8) content-size 0x0 children: not-inline
NavigableContainerViewport <iframe> at (18,48) content-size 10x10 children: not-inline (url: about:blank) Viewport <#document> at (0,0) content-size 10x10 children: not-inline
BlockContainer <html> at (0,0) content-size 10x16 [BFC] children: not-inline
BlockContainer <body> at (8,8) content-size 0x0 children: not-inline
NavigableContainerViewport <iframe> at (18,78) content-size 10x10 children: not-inline (url: about:blank) Viewport <#document> at (0,0) content-size 10x10 children: not-inline
BlockContainer <html> at (0,0) content-size 10x16 [BFC] children: not-inline
BlockContainer <body> at (8,8) content-size 0x0 children: not-inline
NavigableContainerViewport <iframe> at (18,108) content-size 10x10 children: not-inline (url: about:blank) Viewport <#document> at (0,0) content-size 10x10 children: not-inline
BlockContainer <html> at (0,0) content-size 10x16 [BFC] children: not-inline
BlockContainer <body> at (8,8) content-size 0x0 children: not-inline
NavigableContainerViewport <iframe> at (18,138) content-size 10x10 children: not-inline (url: about:blank) Viewport <#document> at (0,0) content-size 10x10 children: not-inline
BlockContainer <html> at (0,0) content-size 10x16 [BFC] children: not-inline
BlockContainer <body> at (8,8) content-size 0x0 children: not-inline
NavigableContainerViewport <iframe> at (18,168) content-size 10x10 children: not-inline (url: about:blank) Viewport <#document> at (0,0) content-size 10x10 children: not-inline
BlockContainer <html> at (0,0) content-size 10x16 [BFC] children: not-inline
BlockContainer <body> at (8,8) content-size 0x0 children: not-inline
NavigableContainerViewport <iframe> at (18,198) content-size 10x10 children: not-inline (url: about:blank) Viewport <#document> at (0,0) content-size 10x10 children: not-inline
BlockContainer <html> at (0,0) content-size 10x16 [BFC] children: not-inline
BlockContainer <body> at (8,8) content-size 0x0 children: not-inline
NavigableContainerViewport <iframe> at (18,228) content-size 10x10 children: not-inline (url: about:blank) Viewport <#document> at (0,0) content-size 10x10 children: not-inline
BlockContainer <html> at (0,0) content-size 10x16 [BFC] children: not-inline
BlockContainer <body> at (8,8) content-size 0x0 children: not-inline
NavigableContainerViewport <iframe> at (18,258) content-size 10x10 children: not-inline (url: about:blank) Viewport <#document> at (0,0) content-size 10x10 children: not-inline
BlockContainer <html> at (0,0) content-size 10x16 [BFC] children: not-inline
BlockContainer <body> at (8,8) content-size 0x0 children: not-inline
NavigableContainerViewport <iframe> at (18,288) content-size 10x10 children: not-inline (url: about:blank) Viewport <#document> at (0,0) content-size 10x10 children: not-inline
BlockContainer <html> at (0,0) content-size 10x16 [BFC] children: not-inline
BlockContainer <body> at (8,8) content-size 0x0 children: not-inline
ViewportPaintable (Viewport<#document>) [0,0 800x600]
PaintableWithLines (BlockContainer<HTML>) [0,0 800x16]

View file

@ -2,5 +2,5 @@ Harness status: OK
Found 1 tests
1 Fail
Fail MediaQueryList.changed is correct for all lists in the document even during a change event handler
1 Pass
Pass MediaQueryList.changed is correct for all lists in the document even during a change event handler

View file

@ -2,8 +2,8 @@ Harness status: OK
Found 22 tests
12 Pass
10 Fail
17 Pass
5 Fail
Pass matchMedia('(max-width: 150px)').matches should update immediately
Pass matchMedia('(width: 100px)').matches should update immediately
Pass matchMedia('(orientation: portrait)').matches should update immediately
@ -16,11 +16,11 @@ Pass matchMedia('(min-height: 150px)').matches should update immediately
Pass matchMedia('(aspect-ratio: 1/2)').matches should update immediately
Pass matchMedia('(min-width: 150px)').matches should update immediately
Pass matchMedia('(min-aspect-ratio: 4/3)').matches should update immediately
Fail matchMedia('(max-width: 150px)') should not receive a change event until update the rendering step of HTML5 event loop
Fail matchMedia('(width: 100px)') should not receive a change event until update the rendering step of HTML5 event loop
Fail matchMedia('(orientation: portrait)') should not receive a change event until update the rendering step of HTML5 event loop
Fail matchMedia('(aspect-ratio: 1/1)') should not receive a change event until update the rendering step of HTML5 event loop
Fail matchMedia('(max-aspect-ratio: 4/3)') should not receive a change event until update the rendering step of HTML5 event loop
Pass matchMedia('(max-width: 150px)') should not receive a change event until update the rendering step of HTML5 event loop
Pass matchMedia('(width: 100px)') should not receive a change event until update the rendering step of HTML5 event loop
Pass matchMedia('(orientation: portrait)') should not receive a change event until update the rendering step of HTML5 event loop
Pass matchMedia('(aspect-ratio: 1/1)') should not receive a change event until update the rendering step of HTML5 event loop
Pass matchMedia('(max-aspect-ratio: 4/3)') should not receive a change event until update the rendering step of HTML5 event loop
Fail matchMedia('(max-width: 150px)') should receive a change event after resize event on the window but before a requestAnimationFrame callback is called
Fail matchMedia('(width: 100px)') should receive a change event after resize event on the window but before a requestAnimationFrame callback is called
Fail matchMedia('(orientation: portrait)') should receive a change event after resize event on the window but before a requestAnimationFrame callback is called