From 40bac8c3df1d139a7248f2246cb5de9ca0faa9fc Mon Sep 17 00:00:00 2001 From: "Ngo Iok Ui (Wu Yu Wei)" Date: Fri, 12 Jul 2024 17:32:25 +0900 Subject: [PATCH] script: `document.visibilityState` and `document.hidden` (#32635) * Squashed commit of the following: commit 5e0ea9996cb0d8137c3e1cd04487a1065b61289d Author: Wu Yu Wei Date: Thu Jul 11 13:37:51 2024 +0900 Move lint to new_inherited Signed-off-by: Wu Yu Wei commit a8cbfb1eef2650d153ef41c232d9e80e1118fc37 Merge: b819968f3e 7a33f8f008 Author: Wu Yu Wei Date: Thu Jul 11 13:32:18 2024 +0900 Merge branch 'main' into visibility commit b819968f3eaa361c8a2cf3af679943ae2065ab32 Merge: eda2ec4c22 1c6b74e1f1 Author: Wu Wayne Date: Tue Jul 9 14:26:43 2024 +0900 Merge branch 'main' into visibility commit eda2ec4c225c63236d6851ea525455cad8874ce5 Author: Wu Wayne Date: Thu Jul 4 14:25:05 2024 +0900 Include page-visibility tests to wpt commit 9da7b4ee39b141e59e4a21a64445c4b08499463f Author: Wu Yu Wei Date: Thu Jul 4 12:50:40 2024 +0900 Add TODO comment for future update commit 11f55fea3ead0c8fa07f16557a63cc6a77c15c3f Author: Wu Yu Wei Date: Wed Jul 3 11:01:51 2024 +0900 Add spaces between steps commit 408c3e51f25867e85f894cd77a6355bc32f2aa00 Author: Wu Wayne Date: Tue Jul 2 18:33:53 2024 +0900 Update MANIFEST.json commit 679fe4ffdd28554b11b4018395fac22a08ccbc34 Author: Wu Wayne Date: Tue Jul 2 18:27:02 2024 +0900 Add VisibilityStateEntry to mozilla/interfaces.html commit 4a456a2b4a473fa795274edf56ecf660616f90eb Author: Wu Wayne Date: Tue Jul 2 14:28:11 2024 +0900 Update meta results commit cd191447ff61de392526e00c13f765c2df7a269e Merge: 7ff480a698 ad01342f00 Author: Ngo Iok Ui (Wu Yu Wei) Date: Tue Jul 2 13:47:49 2024 +0900 Merge branch 'main' into visibility commit 7ff480a698413ac5526edfb1b8731373bb9d04ae Author: Wu Yu Wei Date: Tue Jul 2 13:12:44 2024 +0900 Update description text of update_visibility_state commit 0e496b7bce4fd5476a1919737b00e8f0c9e2fdc8 Author: Wu Yu Wei Date: Tue Jul 2 12:59:40 2024 +0900 Add specification link to VisibilityStateEntry methods commit 3e4a061450621bd17f19ff81099dd4daaeaea478 Author: Wu Yu Wei Date: Tue Jul 2 12:31:53 2024 +0900 Add descriptive text to each step commit 8bbdfcae97db5002b09e5f5ecec6ae80d080dc95 Author: Wu Wayne Date: Fri Jun 28 19:15:34 2024 +0900 mach fmt commit dc1c7a4aec6aba56af7afcfee6feadbee242a643 Author: Wu Wayne Date: Fri Jun 28 19:01:00 2024 +0900 Add update_visibility_state commit 6aa18143319044dc084a9585ab064cd853bccc21 Author: Wu Wayne Date: Fri Jun 28 16:06:25 2024 +0900 Add VisibilityStateEntry.webidl commit 638ae3cd563004334d35cc3fbdc1f918d29833d0 Author: Wu Yu Wei Date: Fri Jun 28 13:39:29 2024 +0900 Add visibilityState and hidden in Document.webidl Signed-off-by: Wu Yu Wei * Move creation into reflect call Signed-off-by: Wu Yu Wei --------- Signed-off-by: Wu Yu Wei --- components/script/dom/document.rs | 72 ++++++++++++++++-- components/script/dom/mod.rs | 1 + components/script/dom/visibilitystateentry.rs | 73 +++++++++++++++++++ components/script/dom/webidls/Document.webidl | 3 + .../dom/webidls/VisibilityStateEntry.webidl | 13 ++++ tests/wpt/include.ini | 2 + .../meta/html/dom/idlharness.https.html.ini | 54 -------------- .../iframe-session-history.html.ini | 3 + .../page-visibility/iframe-unload.html.ini | 4 + .../onvisibilitychange.html.ini | 4 + .../test_child_document.html.ini | 9 +++ .../page-visibility/unload-bubbles.html.ini | 4 + .../wpt/meta/page-visibility/unload.html.ini | 4 + tests/wpt/mozilla/meta/MANIFEST.json | 2 +- .../wpt/mozilla/tests/mozilla/interfaces.html | 1 + 15 files changed, 186 insertions(+), 63 deletions(-) create mode 100644 components/script/dom/visibilitystateentry.rs create mode 100644 components/script/dom/webidls/VisibilityStateEntry.webidl create mode 100644 tests/wpt/meta/page-visibility/iframe-session-history.html.ini create mode 100644 tests/wpt/meta/page-visibility/iframe-unload.html.ini create mode 100644 tests/wpt/meta/page-visibility/onvisibilitychange.html.ini create mode 100644 tests/wpt/meta/page-visibility/test_child_document.html.ini create mode 100644 tests/wpt/meta/page-visibility/unload-bubbles.html.ini create mode 100644 tests/wpt/meta/page-visibility/unload.html.ini diff --git a/components/script/dom/document.rs b/components/script/dom/document.rs index ddf95e976cf..4f6d2ab0354 100644 --- a/components/script/dom/document.rs +++ b/components/script/dom/document.rs @@ -67,7 +67,6 @@ use url::Host; use uuid::Uuid; use webrender_api::units::DeviceIntRect; -use super::bindings::trace::{HashMapTracedValues, NoTrace}; use crate::animation_timeline::AnimationTimeline; use crate::animations::Animations; use crate::document_loader::{DocumentLoader, LoadType}; @@ -77,7 +76,7 @@ use crate::dom::bindings::callback::ExceptionHandling; use crate::dom::bindings::cell::{ref_filter_map, DomRefCell, Ref, RefMut}; use crate::dom::bindings::codegen::Bindings::BeforeUnloadEventBinding::BeforeUnloadEvent_Binding::BeforeUnloadEventMethods; use crate::dom::bindings::codegen::Bindings::DocumentBinding::{ - DocumentMethods, DocumentReadyState, NamedPropertyValue, + DocumentMethods, DocumentReadyState, DocumentVisibilityState, NamedPropertyValue, }; use crate::dom::bindings::codegen::Bindings::EventBinding::Event_Binding::EventMethods; use crate::dom::bindings::codegen::Bindings::HTMLIFrameElementBinding::HTMLIFrameElement_Binding::HTMLIFrameElementMethods; @@ -100,6 +99,7 @@ use crate::dom::bindings::refcounted::{Trusted, TrustedPromise}; use crate::dom::bindings::reflector::{reflect_dom_object_with_proto, DomObject}; use crate::dom::bindings::root::{Dom, DomRoot, DomSlice, LayoutDom, MutNullableDom}; use crate::dom::bindings::str::{DOMString, USVString}; +use crate::dom::bindings::trace::{HashMapTracedValues, NoTrace}; use crate::dom::bindings::xmlname::XMLName::Invalid; use crate::dom::bindings::xmlname::{ namespace_from_domstring, validate_and_extract, xml_name_type, @@ -153,6 +153,7 @@ use crate::dom::node::{ use crate::dom::nodeiterator::NodeIterator; use crate::dom::nodelist::NodeList; use crate::dom::pagetransitionevent::PageTransitionEvent; +use crate::dom::performanceentry::PerformanceEntry; use crate::dom::processinginstruction::ProcessingInstruction; use crate::dom::promise::Promise; use crate::dom::range::Range; @@ -167,6 +168,7 @@ use crate::dom::touch::Touch; use crate::dom::touchevent::TouchEvent; use crate::dom::touchlist::TouchList; use crate::dom::treewalker::TreeWalker; +use crate::dom::types::VisibilityStateEntry; use crate::dom::uievent::UIEvent; use crate::dom::virtualmethods::vtable_for; use crate::dom::webglrenderingcontext::WebGLRenderingContext; @@ -470,6 +472,8 @@ pub struct Document { /// The set of all fonts loaded by this document. /// fonts: MutNullableDom, + /// + visibility_state: Cell, } #[derive(JSTraceable, MallocSizeOf)] @@ -708,8 +712,9 @@ impl Document { return; } - // html.spec.whatwg.org/multipage/#history-traversal - // Step 4.6 + // This step used to be Step 4.6 in html.spec.whatwg.org/multipage/#history-traversal + // But it's now Step 4 in https://html.spec.whatwg.org/multipage/#reactivate-a-document + // TODO: See #32687 for more information. let document = Trusted::new(self); self.window .task_manager() @@ -722,9 +727,12 @@ impl Document { if document.page_showing.get() { return; } - // Step 4.6.2 + // Step 4.6.2 Set document's page showing flag to true. document.page_showing.set(true); - // Step 4.6.4 + // Step 4.6.3 Update the visibility state of document to "visible". + document.update_visibility_state(DocumentVisibilityState::Visible); + // Step 4.6.4 Fire a page transition event named pageshow at document's relevant + // global object with true. let event = PageTransitionEvent::new( window, atom!("pageshow"), @@ -2220,9 +2228,12 @@ impl Document { // TODO: Step 1, increase the event loop's termination nesting level by 1. // Step 2 self.incr_ignore_opens_during_unload_counter(); - // Step 3-6 + // Step 3-6 If oldDocument's page showing is true: if self.page_showing.get() { + // Set oldDocument's page showing to false. self.page_showing.set(false); + // Fire a page transition event named pagehide at oldDocument's relevant global object with oldDocument's + // salvageable state. let event = PageTransitionEvent::new( &self.window, atom!("pagehide"), @@ -2233,7 +2244,8 @@ impl Document { let event = event.upcast::(); event.set_trusted(true); let _ = self.window.dispatch_event_with_target_override(event); - // TODO Step 6, document visibility steps. + // Step 6 Update the visibility state of oldDocument to "hidden". + self.update_visibility_state(DocumentVisibilityState::Hidden); } // Step 7 if !self.fired_unload.get() { @@ -3297,6 +3309,7 @@ impl Document { mouse_move_event_index: Default::default(), resize_observers: Default::default(), fonts: Default::default(), + visibility_state: Cell::new(DocumentVisibilityState::Hidden), } } @@ -4083,6 +4096,39 @@ impl Document { pub(crate) fn set_declarative_refresh(&self, refresh: DeclarativeRefresh) { *self.declarative_refresh.borrow_mut() = Some(refresh); } + + /// + fn update_visibility_state(&self, visibility_state: DocumentVisibilityState) { + // Step 1 If document's visibility state equals visibilityState, then return. + if self.visibility_state.get() == visibility_state { + return; + } + // Step 2 Set document's visibility state to visibilityState. + self.visibility_state.set(visibility_state); + // Step 3 Queue a new VisibilityStateEntry whose visibility state is visibilityState and whose timestamp is + // the current high resolution time given document's relevant global object. + let entry = VisibilityStateEntry::new( + &self.global(), + visibility_state, + *self.global().performance().Now(), + ); + self.window + .Performance() + .queue_entry(entry.upcast::()); + + // Step 4 Run the screen orientation change steps with document. + // TODO ScreenOrientation hasn't implemented yet + + // Step 5 Run the view transition page visibility change steps with document. + // TODO ViewTransition hasn't implemented yet + + // Step 6 Run any page visibility change steps which may be defined in other specifications, with visibility + // state and document. Any other specs' visibility steps will go here. + + // Step 7 Fire an event named visibilitychange at document, with its bubbles attribute initialized to true. + self.upcast::() + .fire_bubbling_event(atom!("visibilitychange")); + } } impl ProfilerMetadataFactory for Document { @@ -5376,6 +5422,16 @@ impl DocumentMethods for Document { self.fonts .or_init(|| FontFaceSet::new(&self.global(), None)) } + + /// + fn Hidden(&self) -> bool { + self.visibility_state.get() == DocumentVisibilityState::Hidden + } + + /// + fn VisibilityState(&self) -> DocumentVisibilityState { + self.visibility_state.get() + } } fn update_with_current_time_ms(marker: &Cell) { diff --git a/components/script/dom/mod.rs b/components/script/dom/mod.rs index a4c4f2f3230..76723e8f2ce 100644 --- a/components/script/dom/mod.rs +++ b/components/script/dom/mod.rs @@ -580,6 +580,7 @@ pub mod vertexarrayobject; pub mod videotrack; pub mod videotracklist; pub mod virtualmethods; +pub mod visibilitystateentry; pub mod vttcue; pub mod vttregion; pub mod webgl2renderingcontext; diff --git a/components/script/dom/visibilitystateentry.rs b/components/script/dom/visibilitystateentry.rs new file mode 100644 index 00000000000..9dcf74a58e6 --- /dev/null +++ b/components/script/dom/visibilitystateentry.rs @@ -0,0 +1,73 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ + +use std::ops::Deref; + +use dom_struct::dom_struct; + +use crate::dom::bindings::codegen::Bindings::DocumentBinding::DocumentVisibilityState; +use crate::dom::bindings::codegen::Bindings::PerformanceEntryBinding::PerformanceEntryMethods; +use crate::dom::bindings::codegen::Bindings::VisibilityStateEntryBinding::VisibilityStateEntryMethods; +use crate::dom::bindings::num::Finite; +use crate::dom::bindings::reflector::reflect_dom_object; +use crate::dom::bindings::root::DomRoot; +use crate::dom::bindings::str::DOMString; +use crate::dom::globalscope::GlobalScope; +use crate::dom::performanceentry::PerformanceEntry; + +#[dom_struct] +pub struct VisibilityStateEntry { + entry: PerformanceEntry, +} + +impl VisibilityStateEntry { + #[allow(crown::unrooted_must_root)] + fn new_inherited(state: DocumentVisibilityState, timestamp: f64) -> VisibilityStateEntry { + let name = match state { + DocumentVisibilityState::Visible => DOMString::from("visible"), + DocumentVisibilityState::Hidden => DOMString::from("hidden"), + }; + VisibilityStateEntry { + entry: PerformanceEntry::new_inherited( + name, + DOMString::from("visibility-state"), + timestamp, + 0., + ), + } + } + + pub fn new( + global: &GlobalScope, + state: DocumentVisibilityState, + timestamp: f64, + ) -> DomRoot { + reflect_dom_object( + Box::new(VisibilityStateEntry::new_inherited(state, timestamp)), + global, + ) + } +} + +impl VisibilityStateEntryMethods for VisibilityStateEntry { + /// + fn Name(&self) -> DOMString { + self.entry.Name() + } + + /// + fn EntryType(&self) -> DOMString { + self.entry.EntryType() + } + + /// + fn StartTime(&self) -> Finite { + self.entry.StartTime() + } + + /// + fn Duration(&self) -> u32 { + *self.entry.Duration().deref() as u32 + } +} diff --git a/components/script/dom/webidls/Document.webidl b/components/script/dom/webidls/Document.webidl index 00cc5d81506..6131dbd15c7 100644 --- a/components/script/dom/webidls/Document.webidl +++ b/components/script/dom/webidls/Document.webidl @@ -78,6 +78,7 @@ Document includes NonElementParentNode; Document includes ParentNode; enum DocumentReadyState { "loading", "interactive", "complete" }; +enum DocumentVisibilityState { "visible", "hidden" }; dictionary ElementCreationOptions { DOMString is; @@ -144,6 +145,8 @@ partial /*sealed*/ interface Document { // boolean queryCommandState(DOMString commandId); boolean queryCommandSupported(DOMString commandId); // DOMString queryCommandValue(DOMString commandId); + readonly attribute boolean hidden; + readonly attribute DocumentVisibilityState visibilityState; // special event handler IDL attributes that only apply to Document objects [LegacyLenientThis] attribute EventHandler onreadystatechange; diff --git a/components/script/dom/webidls/VisibilityStateEntry.webidl b/components/script/dom/webidls/VisibilityStateEntry.webidl new file mode 100644 index 00000000000..12e85541be5 --- /dev/null +++ b/components/script/dom/webidls/VisibilityStateEntry.webidl @@ -0,0 +1,13 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ + +// https://html.spec.whatwg.org/multipage/#visibilitystateentry + +[Exposed=(Window)] +interface VisibilityStateEntry : PerformanceEntry { + readonly attribute DOMString name; // shadows inherited name + readonly attribute DOMString entryType; // shadows inherited entryType + readonly attribute DOMHighResTimeStamp startTime; // shadows inherited startTime + readonly attribute unsigned long duration; // shadows inherited duration +}; diff --git a/tests/wpt/include.ini b/tests/wpt/include.ini index a35ceddd26e..ba172da8623 100644 --- a/tests/wpt/include.ini +++ b/tests/wpt/include.ini @@ -190,6 +190,8 @@ skip: true skip: true [submission] skip: true +[page-visibility] + skip: false [performance-timeline] skip: false [permissions] diff --git a/tests/wpt/meta/html/dom/idlharness.https.html.ini b/tests/wpt/meta/html/dom/idlharness.https.html.ini index f4c3673aaab..2bc8cb01296 100644 --- a/tests/wpt/meta/html/dom/idlharness.https.html.ini +++ b/tests/wpt/meta/html/dom/idlharness.https.html.ini @@ -1436,36 +1436,6 @@ [SVGElement interface: attribute onbeforetoggle] expected: FAIL - [VisibilityStateEntry interface: existence and properties of interface object] - expected: FAIL - - [VisibilityStateEntry interface object length] - expected: FAIL - - [VisibilityStateEntry interface object name] - expected: FAIL - - [VisibilityStateEntry interface: existence and properties of interface prototype object] - expected: FAIL - - [VisibilityStateEntry interface: existence and properties of interface prototype object's "constructor" property] - expected: FAIL - - [VisibilityStateEntry interface: existence and properties of interface prototype object's @@unscopables property] - expected: FAIL - - [VisibilityStateEntry interface: attribute name] - expected: FAIL - - [VisibilityStateEntry interface: attribute entryType] - expected: FAIL - - [VisibilityStateEntry interface: attribute startTime] - expected: FAIL - - [VisibilityStateEntry interface: attribute duration] - expected: FAIL - [ImageBitmap interface: existence and properties of interface object] expected: FAIL @@ -2452,12 +2422,6 @@ [Window interface: calling structuredClone(any, optional StructuredSerializeOptions) on window with too few arguments must throw TypeError] expected: FAIL - [Document interface: attribute hidden] - expected: FAIL - - [Document interface: attribute visibilityState] - expected: FAIL - [Document interface: attribute onvisibilitychange] expected: FAIL @@ -2467,12 +2431,6 @@ [Document interface: attribute oncontextrestored] expected: FAIL - [Document interface: iframe.contentDocument must inherit property "hidden" with the proper type] - expected: FAIL - - [Document interface: iframe.contentDocument must inherit property "visibilityState" with the proper type] - expected: FAIL - [Document interface: iframe.contentDocument must inherit property "onvisibilitychange" with the proper type] expected: FAIL @@ -2482,12 +2440,6 @@ [Document interface: iframe.contentDocument must inherit property "oncontextrestored" with the proper type] expected: FAIL - [Document interface: new Document() must inherit property "hidden" with the proper type] - expected: FAIL - - [Document interface: new Document() must inherit property "visibilityState" with the proper type] - expected: FAIL - [Document interface: new Document() must inherit property "onvisibilitychange" with the proper type] expected: FAIL @@ -2497,12 +2449,6 @@ [Document interface: new Document() must inherit property "oncontextrestored" with the proper type] expected: FAIL - [Document interface: documentWithHandlers must inherit property "hidden" with the proper type] - expected: FAIL - - [Document interface: documentWithHandlers must inherit property "visibilityState" with the proper type] - expected: FAIL - [Document interface: documentWithHandlers must inherit property "onvisibilitychange" with the proper type] expected: FAIL diff --git a/tests/wpt/meta/page-visibility/iframe-session-history.html.ini b/tests/wpt/meta/page-visibility/iframe-session-history.html.ini new file mode 100644 index 00000000000..64437c7c804 --- /dev/null +++ b/tests/wpt/meta/page-visibility/iframe-session-history.html.ini @@ -0,0 +1,3 @@ +[iframe-session-history.html] + [pagehide should be called before visibilitychange, and visibilityState should be set to hidden at the right time] + expected: FAIL diff --git a/tests/wpt/meta/page-visibility/iframe-unload.html.ini b/tests/wpt/meta/page-visibility/iframe-unload.html.ini new file mode 100644 index 00000000000..4df96d83d37 --- /dev/null +++ b/tests/wpt/meta/page-visibility/iframe-unload.html.ini @@ -0,0 +1,4 @@ +[iframe-unload.html] + expected: TIMEOUT + [visibilitychange fires on unload with iframes] + expected: TIMEOUT diff --git a/tests/wpt/meta/page-visibility/onvisibilitychange.html.ini b/tests/wpt/meta/page-visibility/onvisibilitychange.html.ini new file mode 100644 index 00000000000..4998c11da7b --- /dev/null +++ b/tests/wpt/meta/page-visibility/onvisibilitychange.html.ini @@ -0,0 +1,4 @@ +[onvisibilitychange.html] + expected: TIMEOUT + [onvisibilitychange attribute is a proper event handler] + expected: NOTRUN diff --git a/tests/wpt/meta/page-visibility/test_child_document.html.ini b/tests/wpt/meta/page-visibility/test_child_document.html.ini new file mode 100644 index 00000000000..9e77ac067f4 --- /dev/null +++ b/tests/wpt/meta/page-visibility/test_child_document.html.ini @@ -0,0 +1,9 @@ +[test_child_document.html] + [document.visibilityState for frame with no style attribute == visible] + expected: FAIL + + [document.visibilityState for frame with 'display:none' style == visible] + expected: FAIL + + [document.visibilityState for frame with 'visibility:hidden' style == visible] + expected: FAIL diff --git a/tests/wpt/meta/page-visibility/unload-bubbles.html.ini b/tests/wpt/meta/page-visibility/unload-bubbles.html.ini new file mode 100644 index 00000000000..c4af1f5f836 --- /dev/null +++ b/tests/wpt/meta/page-visibility/unload-bubbles.html.ini @@ -0,0 +1,4 @@ +[unload-bubbles.html] + expected: TIMEOUT + [visibilitychange event bubbles when fired on unload] + expected: TIMEOUT diff --git a/tests/wpt/meta/page-visibility/unload.html.ini b/tests/wpt/meta/page-visibility/unload.html.ini new file mode 100644 index 00000000000..6e9a94351a0 --- /dev/null +++ b/tests/wpt/meta/page-visibility/unload.html.ini @@ -0,0 +1,4 @@ +[unload.html] + expected: TIMEOUT + [visibilitychange fires on unload] + expected: TIMEOUT diff --git a/tests/wpt/mozilla/meta/MANIFEST.json b/tests/wpt/mozilla/meta/MANIFEST.json index fbb91fa1a9c..bac1655cabf 100644 --- a/tests/wpt/mozilla/meta/MANIFEST.json +++ b/tests/wpt/mozilla/meta/MANIFEST.json @@ -13434,7 +13434,7 @@ ] ], "interfaces.html": [ - "6f8b7beaa36046d8ade72a13aee82edd4fabe314", + "d4daa95cfe84f1ffd77d0b631e67deb778db5fc3", [ null, {} diff --git a/tests/wpt/mozilla/tests/mozilla/interfaces.html b/tests/wpt/mozilla/tests/mozilla/interfaces.html index 6f8b7beaa36..d4daa95cfe8 100644 --- a/tests/wpt/mozilla/tests/mozilla/interfaces.html +++ b/tests/wpt/mozilla/tests/mozilla/interfaces.html @@ -252,6 +252,7 @@ test_interfaces([ "ValidityState", "VideoTrack", "VideoTrackList", + "VisibilityStateEntry", "WebAssembly", "WebGLRenderingContext", "WebGLUniformLocation",