From 1269f0da85e37edbe4a935e5c19c813c1bdb911c Mon Sep 17 00:00:00 2001 From: Domenic Denicola Date: Fri, 5 Mar 2021 15:46:16 -0500 Subject: [PATCH 01/18] Initial draft of navigate event spec --- spec.bs | 217 +++++++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 216 insertions(+), 1 deletion(-) diff --git a/spec.bs b/spec.bs index ef14616..c9892d2 100644 --- a/spec.bs +++ b/spec.bs @@ -16,6 +16,19 @@ Complain About: accidental-2119 yes, missing-example-ids yes Indent: 2 Default Biblio Status: current Markup Shorthands: markdown yes +Assume Explicit For: yes + + + +
+spec: html; type: dfn; urlPrefix: https://html.spec.whatwg.org/multipage/
+  text: serialized state; url: history.html#serialized-state
+  for: URL and history update steps
+    text: serializedData; url: history.html#uhus-serializeddata
+    text: title; url: history.html#uhus-title
+    text: isPush; url: history.html#uhus-ispush
 
-TODO write some spec text! +

The {{AppHistory}} interface

+ + +partial interface Window { + readonly attribute AppHistory appHistory; +}; + + +Each {{Window}} object has an associated app history, which is a new {{AppHistory}} instance created alongside the {{Window}}. + +The appHistory getter steps are to return [=this=]'s [=Window/app history=]. + + +[Exposed=Window] +interface AppHistory : EventTarget { + attribute EventHandler onnavigate; + attribute EventHandler onnavigatesuccess; + attribute EventHandler onnavigateerror; +}; + + +The following are the [=event handlers=] (and their corresponding [=event handler event types=]) that must be supported, as [=event handler IDL attributes=], by objects implementing the {{AppHistory}} interface: + + + + + + + +
[=Event handler=] + [=Event handler event type=] +
onnavigate + navigate +
onnavigatesuccess + navigatesuccess +
onnavigateerror + navigateerror +
+ + + + +[Exposed=Window] +interface AppHistoryNavigateEvent : Event { + constructor(DOMString type, optional AppHistoryNavigateEventInit eventInit = {}); + + readonly attribute boolean canRespond; + readonly attribute boolean userInitiated; + readonly attribute boolean hashChange; +// readonly attribute AppHistoryEntry destination; + readonly attribute AbortSignal signal; + readonly attribute FormData? formData; + readonly attribute any info; + + undefined respondWith(Promise<undefined> newNavigationAction); +}; + +dictionary AppHistoryNavigateEventInit : EventInit { + boolean canRespond = false; + boolean userInitiated = false; + boolean hashChange = false; +// required AppHistoryEntry destination; + required AbortSignal signal; + FormData? formData = null; + any info = null; +}; + + +
+
event . {{AppHistoryNavigateEvent/canRespond}} +
+

True if {{AppHistoryNavigateEvent/respondWith()}} can be called to convert this navigation into a single-page navigation; false otherwise. + +

Generally speaking, this will be true whenever the destination URL is [=rewritable=] relative to the page's current URL, except for cross-document back/forward navigations, where it will always be false. +

+ +
event . {{AppHistoryNavigateEvent/userInitiated}} +
+

True if this navigation was due to a user clicking on an <{a}> element, submitting a <{form}> element, or using the browser UI to navigate; false otherwise. +

+ +
event . {{AppHistoryNavigateEvent/hashChange}} +
+

True if this navigation is a fragment navigation; false otherwise. +

+ +
event . {{AppHistoryNavigateEvent/signal}} +
+

An {{AbortSignal}} which will become aborted if the navigation gets canceled, e.g. by the user pressing their browser's "Stop" button, or another higher-priority navigation interrupting this one. + +

The expected pattern is for developers to pass this along to any async operations, such as {{WindowOrWorkerGlobalScope/fetch()}}, which they perform as part of handling this navigation. +

+ +
event . {{AppHistoryNavigateEvent/formData}} +
+

The {{FormData}} representing the submitted form entries for this navigation, if this navigation was a form submission; null otherwise. +

+ +
event . {{AppHistoryNavigateEvent/info}} +
+

An arbitrary JavaScript value passed via other app history APIs that initiated this navigation, or null if the navigation was initiated by the user or via a non-app history API. +

+ +
event . {{AppHistoryNavigateEvent/respondWith()|respondWith}}( |newNavigationAction| ) +
+

Synchronously converts this navigation into a same-document navigation to the destination URL. + +

The given |newNavigationAction| promise is used to signal the duration, and success or failure, of the navigation. After it settles, the browser signals to the user (e.g. via a loading spinner UI, or assistive technology) that the navigation is finished. Additionally, it fires {{AppHistory/navigatesuccess}} or {{AppHistory/navigateerror}} events as appropriate, which other parts of the web application can respond to. + +

This method will throw a "{{SecurityError}}" {{DOMException}} if {{AppHistoryNavigateEvent/canRespond}} is false, or if {{Event/isTrusted}} is false. It will throw an "{{InvalidStateError}}" {{DOMException}} if not called synchronously, during event dispatch. +

+
+ +The canRespond, userInitiated, hashChange, signal, formData, and info getter steps are to return the value that the corresponding attribute was initialized to. + +An {{AppHistoryNavigateEvent}} has an associated [=URL=] destination URL, an associated boolean is push, an associated [=serialized state=]-or-null classic history API serialized data, and an optional string-or-null classic history API title. All of these are set when the event is [=fire a navigate event|fired=]. + +
+ The respondWith(|newNavigationAction|) method steps are: + + 1. If [=this=]'s {{Event/isTrusted}} attribute was initialized to false, then throw a "{{SecurityError}}" {{DOMException}}. + 1. If [=this=]'s {{AppHistoryNavigateEvent/canRespond}} attribute was initialized to false, then throw a "{{SecurityError}}" {{DOMException}}. + 1. If [=this=]'s [=Event/dispatch flag=] is unset, then throw an "{{InvalidStateError}}" {{DOMException}}. + 1. If [=this=]'s [=Event/canceled flag=] is unset, then throw an "{{InvalidStateError}}" {{DOMException}}. + 1. Set [=this=]'s [=Event/canceled flag=]. + 1. Run the URL and history update steps given [=this=]'s [=relevant global object=]'s [=associated document=] and [=this=]'s [=AppHistoryNavigateEvent/destination URL=], with [=URL and history update steps/serializedData=] set to [=this=]'s [=AppHistoryNavigateEvent/classic history API serialized data=], [=URL and history update steps/title=] set to [=this=]'s [=AppHistoryNavigateEvent/classic history API title=], and [=URL and history update steps/isPush=] set to [=this=]'s [=AppHistoryNavigateEvent/is push=]. + 1. Let |appHistory| be [=this=]'s [=relevant global object=]'s [=Window/app history=]. + 1. [=promise/React=] to |newNavigationAction| with the following fulfillment steps: + 1. [=Fire an event=] named {{AppHistory/navigatesuccess}} at |appHistory|. + and the following rejection steps given reason |rejectionReason|: + 1. [=Fire an event=] named {{AppHistory/navigateerror}} at |appHistory| using {{ErrorEvent}}, with {{ErrorEvent/error}} initialized to |rejectionReason|, and {{ErrorEvent/message}}, {{ErrorEvent/filename}}, {{ErrorEvent/lineno}}, and {{ErrorEvent/colno}} initialized to appropriate values that can be extracted from |rejectionReason| in the same underspecified way the user agent typically does for the report an exception algorithm. +
+ +
+ +
+ To fire a `navigate` event at an {{AppHistory}} |appHistory| given a [=URL=] |destinationURL|, a boolean |isPush|, a boolean |isCrossDocument|, a boolean |isBackForward|, a boolean |isBrowserUI|, a boolean |isUserInitiated|, a boolean |isFragmentNavigation|, an optional value |navigateInfo| (default null), an optional [=list=] of [=FormData/entries=] |formDataEntryList|, an optional [=serialized state=]-or-null |classicHistoryAPISerializedData| (default null), and an optional string-or-null |classicHistoryAPITitle| (default null): + + 1. Let |event| be the result of [=creating an event=] given {{AppHistoryNavigateEvent}}, in the [=relevant Realm=] of |appHistory|. + 1. Set |event|'s [=AppHistoryNavigateEvent/destination URL=] to |destinationURL|. + 1. Set |event|'s [=AppHistoryNavigateEvent/is push=] to |isPush|. + 1. Set |event|'s [=AppHistoryNavigateEvent/classic history API serialized data=] to |classicHistoryAPISerializedData|. + 1. Set |event|'s [=AppHistoryNavigateEvent/classic history API title=] to |classicHistoryAPITitle|. + 1. Initialize |event|'s {{Event/type}} to {{AppHistory/navigate}}. + 1. Initialize |event|'s {{AppHistoryNavigateEvent/userInitiated}} to |isUserInitiated|. + 1. Initialize |event|'s {{AppHistoryNavigateEvent/hashChange}} to |isFragmentNavigation|. + 1. Initialize |event|'s {{AppHistoryNavigateEvent/signal}} to a [=new=] {{AbortSignal}} created in the [=relevant Realm=] of |appHistory|. + 1. Initialize |event|'s {{AppHistoryNavigateEvent/info}} to |navigateInfo|. + 1. Let |currentURL| be |appHistory|'s [=relevant global object=]'s [=associated document=]'s [=Document/URL=]. + 1. If |destinationURL| is [=rewritable=] relative to |currentURL|, and either |isCrossDocument| is false or |isBackForward| is false, then initialize |event|'s {{AppHistoryNavigateEvent/canRespond}} to true. Otherwise, initialize it to false. + 1. If either |isBrowserUI| is false or |isBackForward| is false, then initialize |event|'s {{Event/cancelable}} to true. + 1. If |formDataEntryList| is given, then initialize |event|'s {{AppHistoryNavigateEvent/formData}} to a [=new=] {{FormData}} created in the [=relevant Realm=] of |appHistory|, associated to |formDataEntryList|. Otherwise, initialize it to null. + 1. Return the result of [=dispatching=] |event| at |appHistory|. +
+ +TODO should canRespond become false after calling respondWith? Should it incorporate the other error conditions? + + +A [=URL=] is rewritable relative to another [=URL=] if they differ in only the [=url/path=], [=url/query=], or [=url/fragment=] components. + +
+ `https://example.com/foo?bar#baz` is rewritable relative to `https://example.com/qux`. + + However, the concept is not the same as the two URLs' [=url/origins=] being [=same origin|the same=]: `https://user:password@example.com/qux` is not rewritable relative to `https://example.com/qux`. + + Similarly, `about:blank` or `blob:` URLs are not rewritable relative to `https:` URLs, despite there being cases where a `https`:-[=Document/URL=] {{Document}} is [=same origin=] with an `about:blank` or `blob:`-derived {{Document}}. +
+ + + +The following section details monkeypatches to [[!HTML]] that cause the {{AppHistory/navigate}} event to be fired appropriately, and for canceling the event to cancel the navigation. + +TODO! From 804edd10ae1471da77e019a4e172d9d232b955db Mon Sep 17 00:00:00 2001 From: Domenic Denicola Date: Fri, 5 Mar 2021 16:45:26 -0500 Subject: [PATCH 02/18] Fill out the easy parts of the monkeypatches --- spec.bs | 57 ++++++++++++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 54 insertions(+), 3 deletions(-) diff --git a/spec.bs b/spec.bs index c9892d2..b93f5bc 100644 --- a/spec.bs +++ b/spec.bs @@ -29,6 +29,10 @@ spec: html; type: dfn; urlPrefix: https://html.spec.whatwg.org/multipage/ text: serializedData; url: history.html#uhus-serializeddata text: title; url: history.html#uhus-title text: isPush; url: history.html#uhus-ispush + for: session history entry + text: document; url: history.html#she-document + for: history handling behavior + text: default; url: browsing-the-web.html#hh-default