Skip to content

Commit

Permalink
LibWeb: Don't lose change events on MediaQueryList internal state change
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
awesomekling committed Feb 13, 2025
1 parent 6fd24c2 commit c9cd795
Show file tree
Hide file tree
Showing 7 changed files with 78 additions and 23 deletions.
20 changes: 17 additions & 3 deletions Libraries/LibWeb/CSS/MediaQueryList.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -54,19 +54,33 @@ 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()) {
container_document->update_layout();
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()
Expand Down
5 changes: 5 additions & 0 deletions Libraries/LibWeb/CSS/MediaQueryList.h
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,9 @@ class MediaQueryList final : public DOM::EventTarget {
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>>&&);

Expand All @@ -40,6 +43,8 @@ class MediaQueryList final : public DOM::EventTarget {

GC::Ref<DOM::Document> m_document;
Vector<NonnullRefPtr<MediaQuery>> m_media;

mutable Optional<bool> m_has_changed_state { false };
};

}
5 changes: 4 additions & 1 deletion Libraries/LibWeb/DOM/Document.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
3 changes: 3 additions & 0 deletions Libraries/LibWeb/HTML/NavigableContainer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand Down

0 comments on commit c9cd795

Please sign in to comment.