blob: 56e9459498831d6beaf4b9fc053ed2bdbe940f2c [file] [log] [blame] [view]
Charlie Reisb33534992020-06-26 05:28:171# Navigation Concepts
2
3This documentation covers a set of important topics to understand related to
4navigation. For a timeline of how a given navigation proceeds, see [Life of a
5Navigation](navigation.md).
6
7[TOC]
8
9
10## Same-Document and Cross-Document Navigations
11
12Chromium defines two types of navigations based on whether the navigation
13results in a new document or not. A _cross-document_ navigation is one that
14results in creating a new document to replace an existing document. This is
15the type of navigation that most users are familiar with. A _same-document_
16navigation does not create a new document, but rather keeps the same document
17and changes state associated with it. A same-document navigation does create a
18new session history entry, even though the same document remains active. This
19can be the result of one of the following cases:
20
21* Navigating to a fragment within an existing document (e.g.
22 `https://foo.com/1.html#fragment`).
23* A document calling the `history.pushState()` or `history.replaceState()` APIs.
Charlie Reisef219c82021-03-17 23:53:5924* A new document created via `document.open()`, which may change the URL to
25 match the document that initiated the call (possibly from another frame).
Charlie Reisb33534992020-06-26 05:28:1726* A session history navigation that stays in the same document, such as going
27 back/forward to an existing entry for the same document.
28
29
30## Browser-Initiated and Renderer-Initiated Navigations
31
32Chromium also defines two types of navigations based on which process started
33the navigation: _browser-initiated_ and _renderer-initiated_. This distinction
34is useful when making decisions about navigations, for example whether an
35ongoing navigation needs to be cancelled or not when a new navigation is
36starting. It is also used for some security decisions, such as whether to
37display the target URL of the navigation in the address bar or not.
38Browser-initiated navigations are more trustworthy, as they are usually in
39response to a user interaction with the UI of the browser. Renderer-initiated
40navigations originate in the renderer process, which may be under the control of
41an attacker. Note that some renderer-initiated navigations may be considered
42user-initiated, if they were performed with a [user
43activation](https://mustaqahmed.github.io/user-activation-v2/) (e.g., links),
44while others are not user-initiated (e.g., script navigations).
45
46
47## Last Committed, Pending, and Visible URLs
48
49Many features care about the URL or Origin of a given document, or about a
50pending navigation, or about what is showing in the address bar. These are all
51different concepts with different security implications, so be sure to use the
52correct value for your use case.
53
54See [Origin vs URL](security/origin-vs-url.md) when deciding whether to check
55the Origin or URL. In many cases that care about the security context, Origin
56should be preferred.
57
58The _last committed_ URL or Origin represents the document that is currently in
59the frame, regardless of what is showing in the address bar. This is almost
60always what should be used for feature-related state, unless a feature is
Keren Zhudc8454f2024-04-08 15:47:5761explicitly tied to the address bar (e.g., padlock icon). This is empty if no
62navigation is ever committed. e.g. if a tab is newly open for a navigation but
63then the navigation got cancelled. See
Charlie Reisb33534992020-06-26 05:28:1764`RenderFrameHost::GetLastCommittedOrigin` (or URL) and
65`NavigationController::GetLastCommittedEntry`.
66
67The _pending_ URL exists when a main frame navigation has started but has not
68yet committed. This URL is only sometimes shown to the user in the address bar;
69see the description of visible URLs below. Features should rarely need to care
70about the pending URL, unless they are probing for a navigation they expect to
71have started. See `NavigationController::GetPendingEntry`.
72
73The _visible_ URL is what the address bar displays. This is carefully managed to
74show the main frame's last committed URL in most cases, and the pending URL in
75cases where it is safe and unlikely to be abused for a _URL spoof attack_ (where
76an attacker is able to display content as if it came from a victim URL). In
77general, the visible URL is:
78
79 * The pending URL for browser-initiated navigations like typed URLs or
Keren Zhudc8454f2024-04-08 15:47:5780 bookmarks, excluding session history navigations. This becomes empty if the
81 navigation is cancelled.
Charlie Reisb33534992020-06-26 05:28:1782 * The last committed URL for renderer-initiated navigations, where an attacker
83 might have control over the contents of the document and the pending URL.
Keren Zhudc8454f2024-04-08 15:47:5784 This is also used when there is no ongoing navigations, and it is empty when
85 no navigation is ever committed.
Charlie Reisb33534992020-06-26 05:28:1786 * A renderer-initiated navigation's URL is only visible while pending if it
87 opens in a new unmodified tab (so that an unhelpful `about:blank` URL is not
88 displayed), but only until another document tries to access the initial empty
89 document of the new tab. For example, an attacker window might open a new tab
90 to a slow victim URL, then inject content into the initial `about:blank`
91 document as if the slow URL had committed. If that occurs, the visible URL
92 reverts to `about:blank` to avoid a URL spoof scenario. Once the initial
93 navigation commits in the new tab, pending renderer-initiated navigation URLs
94 are no longer displayed.
95
96
97## Virtual URLs
98
99Virtual URLs are a way for features to change how certain URLs are displayed to
100the user (whether visible or committed). They are generally implemented using
101BrowserURLHandlers. Examples include:
102
103 * View Source URLs, where the `view-source:` prefix is not present in the
104 actual committed URL.
105 * DOM Distiller URLs, where the original URL is displayed to the user rather
106 than the more complex distiller URL.
107
108
109## Redirects
110
111Navigations can redirect to other URLs in two different ways.
112
113A _server redirect_ happens when the browser receives a 300-level HTTP response
114code before the document commits, telling it to request a different URL,
115possibly cross-origin. The new request will usually be an HTTP GET request,
116unless the redirect is triggered by a 307 or 308 response code, which preserves
117the original request method and body. Server redirects are managed by a single
118NavigationRequest. No document is committed to session history, but the original
119URL remains in the redirect chain.
120
121In contrast, a _client redirect_ happens after a document has committed, when
122the HTML in the document instructs the browser to request a new document (e.g.,
123via meta tags or JavaScript). Blink classifies the navigation as a client
124redirect based partly on how much time has passed. In this case, a session
125history item is created for the redirecting document, but it gets replaced when
126the actual destination document commits. A separate NavigationRequest is used
127for the second navigation.
128
129
130## Concurrent Navigations
131
132Many navigations can be in progress simultaneously. In general, every frame is
133considered independent and may have its own navigations(s), with each tracked by
134a NavigationRequest. Within a frame, it is possible to have multiple concurrent
135navigations:
136
137 * **A cross-document navigation waiting for its final response (at most one per
138 frame).** The NavigationRequest is owned by FrameTreeNode during this stage,
139 which can take several seconds. Some special case navigations do not use a
140 network request and skip this stage (e.g., `about:blank`, `about:srcdoc`,
141 MHTML).
142 * **A queue of cross-document navigations that are between "ready to commit"
143 and "commit," while the browser process waits for a commit acknowledgement
144 from the renderer process.** While rare, it is possible for multiple
145 navigations to be in this stage concurrently if the renderer process is slow.
146 The NavigationRequests are owned by the RenderFrameHost during this stage,
147 which is usually short-lived.
148 * **Same-document navigations.** These can be:
149 * Renderer-initiated (e.g., `pushState`, fragment link click). In this case,
150 the browser process creates and destroys a NavigationRequest in the same
151 task.
152 * Browser-initiated (e.g., omnibox fragment change). In this case, the
153 browser process creates a NavigationRequest owned by the RenderFrameHost
154 and immediately tells the renderer to commit.
155
156Note that the navigation code is not re-entrant. Callers must not start a new
157navigation while a call to `NavigateWithoutEntry` or
158`NavigateToExistingPendingEntry` is on the stack, to avoid a CHECK that guards
159against use-after-free for `pending_entry_`.
160
161
162## Rules for Canceling Navigations
163
164We generally do not want an abusive page to prevent the user from navigating
165away, such as by endlessly starting new navigations that interrupt or cancel the
166user's attempts. Generally, a new navigation will cancel an existing one in a
167frame, but we make the following exception: a renderer-initiated navigation is
168ignored iff there is an ongoing browser-initiated navigation and the new
169navigation lacks a user activation. (This is implemented in
170`Navigator::ShouldIgnoreIncomingRendererRequest`.)
171
172NavigationThrottles also have an ability to cancel navigations when desired by a
173feature. Keep in mind that it is problematic to simulate a redirect by canceling
174a navigation and starting a new one, since this may lose relevant context from
175the original navigation (e.g., ReloadType, CSP state, Sec-Fetch-Metadata state,
176redirect chain, etc), and it will lead to unexpected observer events and metrics
177(e.g., extra navigation starts, inflated numbers of canceled navigations, etc).
178Feature authors that want to simulate redirects may want to consider using a
179URLLoaderRequestInterceptor instead.
180
181
182## Error Pages
183
184There are several types of error pages that can be displayed when a navigation
185is not successful.
186
187The server can return a custom error page, such as a 400 or 500 level HTTP
188response code page. These pages are rendered much like a successful navigation
189to the site (and go into an appropriate process for that site), but the error
190code is available and `NavigationHandle::IsErrorPage()` is true.
191
192If the navigation fails to get a response from the server (e.g., the DNS lookup
193fails), then Chromium will display an error page. For main frames, this error
194page will be in a special error page process, not affiliated with any site or
195containing any untrustworthy content from the web. In these failed cases,
196NetErrorHelperCore may try to reload the URL at a later time (e.g., if a network
197connection comes back online), to load the document in an appropriate process.
198
199If instead the navigation is blocked (e.g., by an extension API or a
200NavigationThrottle), then Chromium will similarly display an error page in a
201special error page process. However, in blocked cases, Chromium will not attempt
202to reload the URL at a later time.
203
204
205## Interstitial Pages
206
207Interstitial pages are implemented as committed error pages. (Prior to
208[issue 448486](https://crbug.com/448486), they were implemented as overlays.)
209The original in-progress navigation is canceled when the interstitial is
210displayed, and Chromium repeats the navigation if the user chooses to proceed.
211
212Note that some interstitials can be shown after a page has committed (e.g., when
213a subresource load triggers a Safe Browsing error). In this case, Chromium
214navigates away from the original page to the interstitial page, with the intent
215of replacing the original NavigationEntry. However, the original NavigationEntry
216is preserved in `NavigationControllerImpl::entry_replaced_by_post_commit_error_`
217in case the user chooses to dismiss the interstitial and return to the original
218page.