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",