diff --git a/components/script/dom/htmlformelement.rs b/components/script/dom/htmlformelement.rs index 4a186530ed2..20e6233caec 100644 --- a/components/script/dom/htmlformelement.rs +++ b/components/script/dom/htmlformelement.rs @@ -82,9 +82,6 @@ use crate::links::{LinkRelations, get_element_target}; use crate::script_runtime::CanGc; use crate::script_thread::ScriptThread; -#[derive(Clone, Copy, JSTraceable, MallocSizeOf, PartialEq)] -pub(crate) struct GenerationId(u32); - #[dom_struct] pub(crate) struct HTMLFormElement { htmlelement: HTMLElement, @@ -92,7 +89,6 @@ pub(crate) struct HTMLFormElement { /// constructing_entry_list: Cell, elements: DomOnceCell, - generation_id: Cell, controls: DomRefCell>>, #[allow(clippy::type_complexity)] @@ -104,6 +100,9 @@ pub(crate) struct HTMLFormElement { firing_submission_events: Cell, rel_list: MutNullableDom, + /// + planned_navigation: Cell, + #[no_trace] relations: Cell, } @@ -124,12 +123,12 @@ impl HTMLFormElement { marked_for_reset: Cell::new(false), constructing_entry_list: Cell::new(false), elements: Default::default(), - generation_id: Cell::new(GenerationId(0)), controls: DomRefCell::new(Vec::new()), past_names_map: DomRefCell::new(HashMapTracedValues::new()), current_name_generation: Cell::new(0), firing_submission_events: Cell::new(false), rel_list: Default::default(), + planned_navigation: Default::default(), relations: Cell::new(LinkRelations::empty()), } } @@ -1004,14 +1003,10 @@ impl HTMLFormElement { /// [Planned navigation](https://html.spec.whatwg.org/multipage/#planned-navigation) fn plan_to_navigate(&self, mut load_data: LoadData, target: &Window) { - // Step 1 - // Each planned navigation task is tagged with a generation ID, and - // before the task is handled, it first checks whether the HTMLFormElement's - // generation ID is the same as its own generation ID. - let generation_id = GenerationId(self.generation_id.get().0 + 1); - self.generation_id.set(generation_id); - - // Step 2 + // 1. Let referrerPolicy be the empty string. + // 2. If the form element's link types include the noreferrer keyword, + // then set referrerPolicy to "no-referrer". + // Note: both steps done below. let elem = self.upcast::(); let referrer = match elem.get_attribute(&ns!(), &local_name!("rel")) { Some(ref link_types) if link_types.Value().contains("noreferrer") => { @@ -1020,18 +1015,53 @@ impl HTMLFormElement { _ => target.as_global_scope().get_referrer(), }; + // 3. If the form has a non-null planned navigation, remove it from its task queue. + // Note: done by incrementing `planned_navigation`. + self.planned_navigation + .set(self.planned_navigation.get().wrapping_add(1)); + let planned_navigation = self.planned_navigation.get(); + + // Note: we start to use + // the beginnings of an `ongoing_navigation` concept, + // to cancel planned navigations as part of + // + // + // The concept of ongoing navigation must be separated from the form's + // planned navigation concept, because each planned navigation cancels the previous one + // for a given form, whereas an ongoing navigation is a per navigable (read: window for now) + // concept. + // + // Setting the ongoing navigation now means the navigation could be cancelled + // even if the below task has not run yet. This is not how the spec is written: it + // seems instead to imply that a `window.stop` should only cancel the navigation + // that has already started (here the task is queued, but the navigation starts only + // in the task). See . + let ongoing_navigation = target.set_ongoing_navigation(); + let referrer_policy = target.Document().get_referrer_policy(); load_data.creator_pipeline_id = Some(target.pipeline_id()); load_data.referrer = referrer; load_data.referrer_policy = referrer_policy; - // Step 4. - let this = Trusted::new(self); + // 4. Queue an element task on the DOM manipulation task source + // given the form element and the following steps: + let form = Trusted::new(self); let window = Trusted::new(target); let task = task!(navigate_to_form_planned_navigation: move || { - if generation_id != this.root().generation_id.get() { + // 4.1 Set the form's planned navigation to null. + // Note: we implement the equivalent by incrementing the counter above, + // and checking it here. + if planned_navigation != form.root().planned_navigation.get() { return; } + + // Note: we also check if the navigation has been cancelled, + // see https://github.com/whatwg/html/issues/11562 + if ongoing_navigation != window.root().ongoing_navigation() { + return; + } + + // 4.2 Navigate targetNavigable to url window .root() .load_url( @@ -1042,7 +1072,10 @@ impl HTMLFormElement { ); }); - // Step 3. + // 5. Set the form's planned navigation to the just-queued task. + // Done above as part of incrementing the planned navigation counter. + + // Note: task queued here. target .global() .task_manager() diff --git a/components/script/dom/window.rs b/components/script/dom/window.rs index 229a2df9d87..0b7a03d5aae 100644 --- a/components/script/dom/window.rs +++ b/components/script/dom/window.rs @@ -227,6 +227,11 @@ impl LayoutBlocker { } } +/// An id used to cancel navigations; for now only used for planned form navigations. +/// Loosely based on . +#[derive(Clone, Copy, Debug, Default, JSTraceable, MallocSizeOf, PartialEq)] +pub(crate) struct OngoingNavigation(u32); + type PendingImageRasterizationKey = (PendingImageId, DeviceIntSize); #[dom_struct] @@ -267,6 +272,10 @@ pub(crate) struct Window { status: DomRefCell, trusted_types: MutNullableDom, + /// The start of something resembling + /// + ongoing_navigation: Cell, + /// For sending timeline markers. Will be ignored if /// no devtools server #[no_trace] @@ -713,6 +722,49 @@ impl Window { pub(crate) fn font_context(&self) -> &Arc { &self.font_context } + + pub(crate) fn ongoing_navigation(&self) -> OngoingNavigation { + self.ongoing_navigation.get() + } + + /// + pub(crate) fn set_ongoing_navigation(&self) -> OngoingNavigation { + // Note: since this value, for now, is only used in a single `ScriptThread`, + // we just increment it (it is not a uuid), which implies not + // using a `newValue` variable. + let new_value = self.ongoing_navigation.get().0.wrapping_add(1); + + // 1. If navigable's ongoing navigation is equal to newValue, then return. + // Note: cannot happen in the way it is currently used. + + // TODO: 2. Inform the navigation API about aborting navigation given navigable. + + // 3. Set navigable's ongoing navigation to newValue. + self.ongoing_navigation.set(OngoingNavigation(new_value)); + + // Note: Return the ongoing navigation for the caller to use. + OngoingNavigation(new_value) + } + + /// + fn stop_loading(&self, can_gc: CanGc) { + // 1. Let document be navigable's active document. + let doc = self.Document(); + + // 2. If document's unload counter is 0, + // and navigable's ongoing navigation is a navigation ID, + // then set the ongoing navigation for navigable to null. + // + // Note: since the concept of `navigable` is nascent in Servo, + // for now we do two things: + // - increment the `ongoing_navigation`(preventing planned form navigations). + // - Send a `AbortLoadUrl` message(in case the navigation + // already started at the constellation). + self.set_ongoing_navigation(); + + // 3. Abort a document and its descendants given document. + doc.abort(can_gc); + } } // https://html.spec.whatwg.org/multipage/#atob @@ -868,9 +920,11 @@ impl WindowMethods for Window { // https://html.spec.whatwg.org/multipage/#dom-window-stop fn Stop(&self, can_gc: CanGc) { - // TODO: Cancel ongoing navigation. - let doc = self.Document(); - doc.abort(can_gc); + // 1. If this's navigable is null, then return. + // Note: Servo doesn't have a concept of navigable yet. + + // 2. Stop loading this's navigable. + self.stop_loading(can_gc); } /// @@ -3097,6 +3151,7 @@ impl Window { inherited_secure_context, unminify_js, ), + ongoing_navigation: Default::default(), script_chan, layout: RefCell::new(layout), font_context, diff --git a/tests/wpt/meta/MANIFEST.json b/tests/wpt/meta/MANIFEST.json index 3807f666032..2ac5d3d05b2 100644 --- a/tests/wpt/meta/MANIFEST.json +++ b/tests/wpt/meta/MANIFEST.json @@ -698765,6 +698765,13 @@ {} ] ], + "form-submit-and-window-stop.html": [ + "b1659d0d2686e8c538f4286b7f88d851ea532f09", + [ + null, + {} + ] + ], "grandparent_location_aboutsrcdoc.sub.window.js": [ "c182c29cfc7d7562de13257c74ff3e8085d110a9", [ diff --git a/tests/wpt/tests/html/browsers/browsing-the-web/navigating-across-documents/form-submit-and-window-stop.html b/tests/wpt/tests/html/browsers/browsing-the-web/navigating-across-documents/form-submit-and-window-stop.html new file mode 100644 index 00000000000..b1659d0d268 --- /dev/null +++ b/tests/wpt/tests/html/browsers/browsing-the-web/navigating-across-documents/form-submit-and-window-stop.html @@ -0,0 +1,29 @@ + + +The planned navigation of a form should be cancelled by a window stop + in the same task as the form submission + + + + + + + +
\ No newline at end of file