html
html copied to clipboard
Navigation and session history rewrite
This monster completely rewrites everything to do with navigation and traversal.
It introduces the "navigable" and "traversable navigable" concepts, which take on many of the roles that browsing contexts previously did, but better. A navigable can present a sequence of browsing contexts, which to the user seem to all be the same, but due to browsing context group switches, have different WindowProxys and are allocated in different agent clusters. A traversable navigable manages the session history for itself and all its descendant navigables, providing a synchronization point and source of truth.
The general flow of navigation and traversal is now geared toward creating a session history entry, populated with the appropriate document, before finally applying the history "step". The step concept for session history, managed by the traversable, replaces the previous idea of joint session history, which was a sort of deduplicated union of individual session histories for each browsing context within a top-level browsing context.
Notable things to still do before merging:
- [x] Replace more browsing context references with navigables, with special care and attention to tree traversal and relationships
- [x] Write nice introductions and overviews of the flow
- [x] Rearrange everything into logical sections.
- [ ] Insert lots of "Jake diagrams" to illustrate tricky cases.
- [ ] Finish up remaining minor TODOs left in the text.
- [x] Compare old IDs with new ones and preserve them where it makes sense. (Audit)
- [ ] Address outstanding issues in Navigation & session history rewrite notes
- [ ] Write or update tests, especially for cases called out in the above issues where we now have a clear answer. (We don't plan to be completely exhaustive yet; adding tests will be an ongoing effort.) (Audit)
Notable things we won't tackle this round, but are much easier to tackle in the future:
- Iframe restoration on (non-bfcache) history traversal.
- Overlapping navigations and traversals (see #6927) are probably not perfect yet (although this PR makes them better).
- Browsing context names (see #313) are not perfect yet, although this PR makes them better.
Closes #854 by clarifying the javascript: URL origin and origin-checking setup.
Closes #1073 by properly resetting active-ness of documents when they are removed.
Closes #1130 by removing the source browsing context concept, using a sourceDocument argument instead, and taking source snapshot params at the appropriate early time.
Closes #1191 by properly sharing document state across documents, as well as overlapping same-document navigations plus cross-document traversals.
Closes #1336 by properly handling child browsing contexts.
Closes #1382 by only unloading after we are sure we have a new document (i.e., not a 204 or download).
Closes #1454 by rewriting session history closer to what implementations do, with the nested history concept in particular taking care of the issues discussed there.
Closes #1524 by introducing the POST data concept and storing it in the document state.
Closes #2436 by rewriting the spec for history.go() to be clear about the results.
Closes #2566 by introducing an explicit "history object" definition.
Closes #2649 through clear creation of srcdoc documents, including during history traversal.
Closes #3215 by preserving POST data and reusing it on reloads.
Closes #3447 by specifying a precise mechanism (the ongoing navigation) for canceling navigations, and the points at which that mechanism is consulted. It also stops queuing a task for hyperlink navigations.
Closes #3497 by posting appropriate tasks for cross-event-loop navigations.
Closes #3615 by rewriting traverse a history by a delta, which eventually calls into apply the history step, to navigate all relevant navigables.
Closes #3625 by storing information in the document state (not just the URL), so that future traversals can reconstruct the request appropriately.
Closes #3730 by doing proper task queuing for navigation, including one for javascript: URLs but not including one for normal same-frame navigations. TODO convert these into test cases.
Closes #3734 by rewriting the definition of script-closable to use well-defined concepts.
Closes #3812 by removing all uses of "active document" as a predicate instead of a property.
Closes #4054 by introducing the session history traversal queue and renaming the previous "history traversal task source" to "navigation and traversal task source".
Closes #4121 by doing the "allowed to navigate" check at the top of apply the history step.
Closes #4428 by keeping a strong reference from documents (including bfcached documents) to their containing browsing context.
Closes #4782 by introducing the top-level traversable and navigable concepts.
Closes #4838 by doing sandbox checking in a much more precise manner, in particular snapshotting the relevant flags early in any traversals.
Closes #4852 by using document state (in particular history policy container, request referrer, and request referrer policy) in reloads.
Closes #5103 by properly restoring scroll positions for everything that is traversed, as part of properly traversing more than one navigable.
Closes #5350 by properly restoring window names across browsing context group switches, and going back to the same browsing context as was previously there when traversing back across a BCG switch boundary. (Implementations could create new browsing contexts, as long as they restore the WindowProxy scripting relationships and other browsing context features; the result is observably equivalent.)
Closes #5597 by rewriting "allowed to download" to just take booleans, derived from the appropriate snapshotted or computed sandboxing flags.
Closes #5767, modulo bugs and oversights we made, by rewriting everything :).
Closes #5877 by respecifying "fully active" in terms of navigables, instead of browsing contexts.
Closes #6446 by properly firing beforeunload to all descendant navigables, although whether or not they actually prompt still allows implementation leeway.
Closes #6483 by introducing the distinction between current session history entry and active session history entry.
Closes #6514 by settling on using a single origin for these checks.
Closes #6628 by storing window.name values in the document state, so even in strange splitting situations like described there, they remain.
Closes #6652 by no longer changing history.state when reactivating a document from bfcache ("restore the history object state" is called only when entryChanged is true).
Closes #6773 by having careful handling of synchronous navigations during traversals. TODO update tests.
Closes #6798 by treating javascript: URL navigations as replacements.
Works towards #6809 by storing srcdoc resources in the document state.
Closes #6813 by storing referrer in the document state.
Closes #7107 by clearing history state on redirects and when origin changes by other means, such as CSP.
Closes #7441 by making window.blur() a no-op because that was simpler than updating it to operate on navigables.
/browsers.html ( diff ) /browsing-the-web.html ( diff ) /canvas.html ( diff ) /common-microsyntaxes.html ( diff ) /comms.html ( diff ) /custom-elements.html ( diff ) /dnd.html ( diff ) /dom.html ( diff ) /dynamic-markup-insertion.html ( diff ) /embedded-content-other.html ( diff ) /embedded-content.html ( diff ) /form-control-infrastructure.html ( diff ) /form-elements.html ( diff ) /iana.html ( diff ) /iframe-embed-object.html ( diff ) /imagebitmap-and-animations.html ( diff ) /images.html ( diff ) /index.html ( diff ) /indices.html ( diff ) /infrastructure.html ( diff ) /input.html ( diff ) /interaction.html ( diff ) /interactive-elements.html ( diff ) /introduction.html ( diff ) /links.html ( diff ) /media.html ( diff ) /microdata.html ( diff ) /obsolete.html ( diff ) /parsing.html ( diff ) /references.html ( diff ) /rendering.html ( diff ) /scripting.html ( diff ) /sections.html ( diff ) /semantics-other.html ( diff ) /semantics.html ( diff ) /server-sent-events.html ( diff ) /structured-data.html ( diff ) /syntax.html ( diff ) /system-state.html ( diff ) /timers-and-user-prompts.html ( diff ) /urls-and-fetching.html ( diff ) /web-messaging.html ( diff ) /webappapis.html ( diff ) /webstorage.html ( diff ) /workers.html ( diff ) /worklets.html ( diff ) /document-lifecycle.html ( diff ) /document-sequences.html ( diff ) /nav-history-apis.html ( diff )
I'd be interested in seeing the browsing session/navigable/session history diagram
Yeah, I want to create some table-like diagrams, like in the "Specifying the history as a 'timeline'" section of "https://github.com/whatwg/html/issues/5767.
Since we seem to be arriving at some kind of consensus in https://github.com/whatwg/html/issues/6356, I'm going to refactor some stuff. @domenic, shout up if any of this sounds bad:
Currently a "browsing session" has a navigable, and some additional state. Instead, I'm going to make it a subclass of navigable, a "top level navigable". It will have the same state as browsing session currently has, except a navigable, since it'd be that navigable.
A "browsing session" will have associated session storage, as it's currently defined. A browsing context group will have a browsing session that cannot change throughout the life of the group. Multiple browsing context groups may share the same session.
Manual navigations to cross-origin URLs will always create a new browsing context group with a new session. Other navigations may create a new browsing context group (depending on cross-origin isolation) but they will have the same session.
I got into a bit of a mess with this in terms of which threads could read & write from which data.
I now think that the top-level navigable needs to have its own copy of session history as a list of trees, so it can figure out things like history length without having to post tasks with every navigable to read that information.
This session history copy will be serialisable, which we need anyway for restoring session history to iframes on reload.
The copy will become temporarily out of date when session history is modified in a synchronous way, but a parallel task will be queued to keep it up to date. Here are the synchronous things:
- Adding a nested navigable (doesn't change history length or step, but just needs added to the tree)
- Removing a nested navigable (may change history length, but not step)
- Hash change /
pushState
navigations (changes history length & step)
I just need to make sure that, if a navigable makes 5 synchronous changes, it ignores all the synchronisations from the top level until it gets to the one that concerns its latest synchronous change.
@domenic these two copies with strict rules around which thread can access which should make it easier for your new history API to have synchronous access to its own session history.
I now think that the top-level navigable needs to have its own copy of session history as a list of trees, so it can figure out things like history length without having to post tasks with every navigable to read that information.
Interesting. My understanding of how this is implemented, generally, is that the "browser process" has the list of trees, and individual frames' processes only have the length
value. All navigations go through the central browser process which pushes updates (presumably with some races) to length
, state
, etc.
/cc @natechapin for his thoughts, as he's been looking at this recently and can correct me if I've got this wrong.
I just need to make sure that, if a navigable makes 5 synchronous changes, it ignores all the synchronisations from the top level until it gets to the one that concerns its latest synchronous change.
This seems reminiscent of the interop issues in this document.
@domenic these two copies with strict rules around which thread can access which should make it easier for your new history API to have synchronous access to its own session history.
I agree in theory, although I worry about the potential mismatch with implementation in practice...
I agree in theory, although I worry about the potential mismatch with implementation in practice...
Ok, I won't over-index on those requirements. We can figure out what should be copied across & when some other time.
I've been playing around with some pseudocode to get a feel for the shape of the algorithms. Right now it's roughly like this:
New navigations select a target context then go to navigate.
History traversals go to traverse the history by a delta.
Navigate handles:
- Perform synchronous navigations via navigate to a fragment, then traverses the history to the new entry. Otherwise:
- Prompt to unload, if necessary.
- Fetch the target url (if necessary) and process the response. Failures here either no-op (203) or create an error page.
- Unload the current document, create the new history entry.
- Traverse the history to the new entry.
Traverse the history to an entry handles:
- If the entry's document is null, then navigate it. Otherwise:
- Introduce the new page (visibility events and such)
Traverse the history by a delta handles:
- If the target history entry has a document, and it's different to the current document, prompt to unload (if necessary), and unload.
- Traverse the history to the target entry.
Issues with this approach:
- The way navigate calls traverse which can call navigate is weird and circular. It never loops in practice, but it makes for confusing reading.
- Navigate handles all "create a new document for a history entry" cases, but in reality we need to handle the failure cases for "link click" and "back button" differently.
- "Traverse the history by a delta" assumes that a history traversal will only impact one navigable.
- Unloading happens in multiple places, and preventing it happening twice for a single document is fragile (and kinda wrong in the current spec).
Here's what I'm going to try and do:
"Navigate":
- Check if the navigable can be navigated by the initiator
- If the navigation should be synchronous, then:
- Create new entry, change it synchronously
- Update history length/index synchronously in the current agent
- Queue a session history task to:
- Add/replace the new entry to the session history tree.
- "sync to a history step" (this updates history length elsewhere and resolves conflicts)
- Otherwise:
- Check for an unload prompt. If one is requested, show it. If the user cancels, then return, otherwise mark the document as "cleared for navigation".
- Create new history entry.
- "Attempt populate history entry". If it doesn't create a document or an error page, abandon the navigation.
- Queue a session history task to:
- Add/replace the new entry to the session history tree.
- "sync to a history step" (this updates history length elsewhere and resolves conflicts)
"Attempt populate history entry":
- Fetch the target url (if necessary) and process the response.
- If the result is
Content-Disposition
, start the download and return. - If the result is a 203/4, return.
- If the result is a network error, populate the history entry with an error and return.
- Populate the history entry with a document and return.
"Sync to a history step":
- Look at tree to determine history length and index.
- Queue tasks with active documents:
- Update local history length and index.
- If this navigable does not need to change history entry, abort these steps.
- If the traversal will result in changing documents:
- If the document isn't "cleared for navigation", check if the navigable can be navigated by the initiator, and check for unload prompt and show it if necessary. If the user cancels the prompt, then do whatever we decide in https://github.com/whatwg/html/issues/6446.
- If doc in new entry is null, then "attempt populate history entry" for the entry. If it doesn't create a document or an error page, do whatever we decide in https://github.com/whatwg/html/issues/6483. Otherwise:
- Unload current doc.
- Introduce new doc.
Any red flags there?
Generally no red flags. I must admit I'm a bit sad that just after I feel like I've got the navigation algorithm straight in my head, you plan to change it dramatically, but your "issues with this approach" (2)-(4) are compelling.
Check if the navigable can be navigated by the initiator
You might get yourself into trouble here with "initiator": that's https://github.com/whatwg/html/issues/1130. Maybe you can just preserve the existing brokenness there, or maybe I can try to fix some of it to prepare the way.
Yeah, I saw the hand-waving with "initiator" 😞. I was going to leave it vague in this first pass. Or maybe it'll bother me too much. Thanks for the link, I didn't realise there was an existing issue for this!
@domenic I've just done my read-through & tweaking of "clicking on a hash-navigation link" if you want to review that path too.
I'll let you know as other paths are ready for review.
I'm currently looking at rearranging the path of "navigate" a bit.
Right now the spec follows this pattern:
Navigate unloads the current document, then, calls out to process a navigate fetch, process a navigate response or process a navigate URL scheme.
Process a navigate fetch handles redirects and such, and calls process a navigate response.
Process a navigate response handles CSP, 204, 205, download triggering, then calling out to the correct handler for the type.
Process a navigate URL scheme is a little hand-wavey and broken.
The handler for the type (eg plain text file) takes care of creating the document, then calling update the session history.
Creating the document selects the browsing context (following COOP), records navigation timing, and handles declarative refreshing.
Update the session history does what you'd expect.
It feels odd that this is pretty much a sequence of calls. Because of this it requires a bag of state to be passed around. You also get weirdness like the document creation steps handling navigation timing.
I'm going to try and make it like this:
Navigate:
- Unload the current documents.
- Create a history entry and pass it to populate history item if input is a fetch URL, otherwise populate history item with response if input is a response.
- If the history entry's document is null (indicating failure to create something renderable, eg 204), return.
- Wait for document to start parsing, but before scripts.
- Queue into the traversables session history queue, update the history tree, and apply the history step.
Populate history item fetches the entry's url, handles CSP, 205, 205, and downloads, otherwise calls populate history item with response with the history entry and response.
Populate history item with response creates the document depending on the response type, and records navigation timing, switches the browsing context in the history entry if needed, and handles declarative refreshing.
In this case it feels like "navigate" handles the navigation, and calls out to other things for part of that, rather than it being a sequence of calls that each hand off to another.
I'm hoping this will reduce the amount of state that needs to be passed around, making things easier to follow. It might even turn out that the history entry itself is enough state.
Hmm. I have a bit of status quo bias because I've spent a good amount of time fixing up and understanding the existing algorithms, but I don't quite see why your proposed alternative is better.
In particular, what I find intuitive about the current spec is that, at a high level, there's a pipeline: input -> response -> document -> update history. Each step feels nicely separated and feeds into the next, as e.g. we can see by how "process a navigate response" gets reused.
Your new proposal seems confusing to me in that it starts creating history entries before we even have a response, much less a document. It seems like it's overloading the concept of history entry to be two things: a bag of state that may one day turn into a real history entry, and then eventually a real history entry. In other words, I like how in the current spec history entries are only ever created right before they're put into a real session history list.
Also just on a naming level, I think "populate history item" is much less clear than "process a navigate fetch".
I'll give it some more thought. It isn't uncommon to have a history entry that doesn't have a document, it happens a lot during traversal. "Populate history entry" will need to exist anyway for that reason.
I could name it something to do with fetch, but it won't always involve fetch, eg srcdoc.
What I do want to keep a good separation between navigation and traversal, while allowing shared steps to be shared.
The main difference between the two is navigation does all the document creation work before modifying the history tree, and may not modify the history tree at all (eg 204, 205, download, before unload cancel).
I could probably keep the algorithms sequential though.
I still have a preference for the navigate algorithm completing the navigation (which, to me, ends with committing the change to the history tree), but I'll stick with the sequential thing. This might become a problem if a caller wants to know when the navigation completes, as it's more state that we'll need to pass through the sequence (a promise object I guess), but maybe that's fine.
However, I'd like to go forward with using a history entry as the thing to populate. This already needs to happen for traversals, and it's very common for the history entry to exist without a document, and therefore before the fetch that creates the document (if there even needs to be a fetch, which isn't the case with srcdoc). As such the history entry has the following state:
- Document
- Browsing context
- POST data / srcdoc data
- Nested history state
Things that happen during the population of this history entry can mutate this state. Eg, a redirect may clear the POST data. The presence or lack of isolation headers may change the browsing context.
I'd like to avoid branching this behaviour for traversal vs navigation.
Yes, that might involve creating a history entry that doesn't get used, but only in the navigation case where the response is 204/205/download. Even in the error case we create an error document and commit the history entry.
Some notes to figure out how to make this sequential:
Creating about:blank
documents in new iframes/windows happens in creating a new browsing context, and completely bypasses initialise the document object and therefore also bypasses update the session history. @domenic are you happy for these to remain outside the pipeline?
That leaves "navigation", "traversal" and "reload" as things that can go through the pipeline of:
- (maybe) fetch resource
- Handle response
- Create document
- Modify the session history tree
Unfortunately this means "traversal" and "reload" land on step 4, even though they don't modify the history tree (they deal with a single existing entry). I can patch this by passing some state through that means "don't modify the tree". This is probably just a modification of navigation params' history handling.
I still have a preference for the navigate algorithm completing the navigation (which, to me, ends with committing the change to the history tree), but I'll stick with the sequential thing.
I don't quite understand the distinction you're making here. By sequential are you referring to my description of the current spec, which is "input -> response -> document -> update history"? That seems to include committing the change to the history tree.
However, I'd like to go forward with using a history entry as the thing to populate.
I'm convinced!
@domenic are you happy for these to remain outside the pipeline?
This has always been a bit weird, and unifying them might be a good idea. (In particular, having to remember to update two places for document/window creation is fragile.) On the other hand, I worry that a unified result might be confusing. In the sense that to understand what's going on for what should be a very simple case (the initial about:blank document), you might have to end up stepping through many algorithms, most of which do nothing or special-case that particular step. So... I don't know; I guess I trust your judgment?
Unfortunately this means "traversal" and "reload" land on step 4, even though they don't modify the history tree (they deal with a single existing entry).
I mean, they don't modify the shape of the tree necessarily, but they modify aspects of the tree, right? Traversal modifies the current pointer, and reload modifies browsing context names, and maybe nested histories? But yeah, in general this kind of branching sounds like what the current spec's history handling behavior is about.
By sequential are you referring to my description of the current spec, which is "input -> response -> document -> update history"? That seems to include committing the change to the history tree.
The current spec is in this form:
function navigate(...args) {
const bagOfState = new BagOfState();
// …Some checks…
inParallel(() => {
if (needsFetch) {
processNavigateFetch(bagOfState);
} else {
processNavigateResponse(bagOfState);
}
});
}
function processNavigateFetch(bagOfState) {
// Do the fetching then:
processNavigateResponse(bagOfState);
}
function processNavigateResponse(bagOfState) {
if (nothingToDisplay) return;
switch (typeOfResponse) {
case "txt":
handlePlainText(bagOfState);
}
}
function handlePlainText(bagOfState) {
const doc = createDocument(bagOfState);
whenDocIsReady(doc).then(() => updateSessionHistory(bagOfState))
}
function updateSessionHistory(bagOfState) {
// Look at bagOfState and figure out how to update session history
}
The bit that seems odd to me is that navigate
ends long before the navigate ends. updateSessionHistory
commits the navigation to the tree. My proposal was to make it:
function navigate(...args) {
// …Some checks…
inParallel(async () => {
const historyEntry = new HistoryEntry();
if (needsFetch) {
populateHistoryEntry(historyEntry);
} else {
populateHistoryEntry(historyEntry, response);
}
if (!historyEntry.document) return;
await whenDocIsReady(historyEntry.document);
// Look at input & resulting historyEntry and figure out how to update session history
});
}
function populateHistoryEntry(historyEntry, response) {
if (!response) {
// Create a response (eg fetch)
}
if (nothingToDisplay) return;
switch (typeOfResponse) {
case "txt":
// Create document and add it to historyEntry
}
}
You lose the 'pipeline', but it means a navigation begins and ends in the navigation algorithm. You also don't need to pass the bag of state around, since 'navigate' will contain the state relating to modifying the history tree, and the history entry provides the rest of the state. populateHistoryEntry
can be reused in traversal when going back to a history entry after the document has been discarded.
This has always been a bit weird, and unifying them might be a good idea. (In particular, having to remember to update two places for document/window creation is fragile.) On the other hand, I worry that a unified result might be confusing. In the sense that to understand what's going on for what should be a very simple case (the initial about:blank document), you might have to end up stepping through many algorithms, most of which do nothing or special-case that particular step. So... I don't know; I guess I trust your judgment?
I agree with all of this. Maybe the answer will become clearer later.
Unfortunately this means "traversal" and "reload" land on step 4, even though they don't modify the history tree (they deal with a single existing entry).
I mean, they don't modify the shape of the tree necessarily, but they modify aspects of the tree, right? Traversal modifies the current pointer, and reload modifies browsing context names, and maybe nested histories?
Hm, I need to think more about reload here, as it behaves differently in a bunch of situations. I'll create an issue for that.
@domenic is it possible for two session histories to have the same document but have different policy containers?
It seems not, so I've moved the policy container from the session history entry to the session history entry's document state (which is shared between entries that share a document).
I haven't closely reviewed how the policy container is passed around between entries in this PR, as I'm getting to that bit sortly and I'll review it then.
is it possible for two session histories to have the same document but have different policy containers?
I am 95% sure this is not possible. (Or if it is somehow possible, it's a spec bug derived from combining the many esoteric tools available to us into some monstrously-confusing situation.) @antosart may be able to confirm.
is it possible for two session histories to have the same document but have different policy containers?
I am 95% sure this is not possible. (Or if it is somehow possible, it's a spec bug derived from combining the many esoteric tools available to us into some monstrously-confusing situation.)
I would confirm, although I must admit I do not fully understand how history is specced. The idea is that the policy container contains a snapshot of the document's policies at document's creation time, so same document => same policies.
The bit that seems odd to me is that navigate ends long before the navigate ends. updateSessionHistory commits the navigation to the tree. My proposal was to make it:
When you write it out like this, it looks pretty nice. I guess if we're losing the property that history entries only exist inside a session history list, then maybe we should just go all the way to your preferred algorithm, since we're anyway giving up one of main benefits of the current setup in how it separates history-related stuff from navigation-related stuff.
we're anyway giving up one of main benefits of the current setup in how it separates history-related stuff from navigation-related stuff.
Sort-of separates it. There's already the about:blank
stuff which sits elsewhere.
But yeah, that's fair, although we're gaining a lot by having the history entry being the source of truth for how a document should be created. Right now it's split/duplicated across navigate and traverse, and usually results in the history entry not having enough information to recreate the document on traversal, as we've seen with POST, srcdoc, referrer etc etc.
Thinking more about this, the steps in https://whatpr.org/html/6315/browsing-the-web.html#navigate-fragid that actually modify the history tree are likely to happen in navigate too, and maybe elsewhere. I'll put them in a helper algorithm, so there's a central place where adding/removing/replacing in the tree happens.
Thing to make sure we fix, noted by @rakina: "fully active"'s current definition doesn't work in bfcache/browsing context group swap cases, at least given how Chrome is currently architectured (and I suspect how we've written the spec in this rewrite). Because after a BCG swap, we just leave the BC's active document as-is; it's the navigable's active document that changes, along with the navigable's active browsing context.
The removal of the “the” in “refer to the indicated part” below seems like it was probably unintentional. Couldn’t figure out how to attach a comment inline on Github (which won’t load stuff due to a size limit), but it’s visible in the rendered iana.html output & the suggested change would be:
<p><span data-x="concept-url-fragment">Fragments</span> used with <code>text/html</code> resources
- either refer to <span>indicated part</span> of the corresponding <code>Document</code>, or provide
+ either refer to the <span>indicated part</span> of the corresponding <code>Document</code>, or provide
state information for in-page scripts.</p>
(Also: this is some PR, wow!)
Yeah it looks like this PR removed the "the" from the definition (see https://whatpr.org/html/6315/browsing-the-web.html#the-indicated-part-of-the-document vs https://html.spec.whatwg.org/multipage/browsing-the-web.html#the-indicated-part-of-the-document, and as a result of fixing up the references, we forgot to move the "the" outside of the reference usage. Fixed now.