RenderDocument
Concept: Vocabulary that names a phenomenon.
Chromium’s one-document-per-RenderFrameHost invariant for cross-document navigation, and the review rule that separates document identity from process identity.
RenderDocument sounds like rendering work, but the boundary is navigation lifetime. A browser-side RenderFrameHost should correspond to one document, and a cross-document navigation should move to a new host rather than silently reusing the old one.
What It Is
RenderDocument is Chromium’s project to make document identity explicit in the browser process. Under the old lifetime model, a RenderFrameHost could survive a same-process cross-document navigation and host a different document after commit. That reuse made some code cheaper, but it also let browser-side objects carry document-scoped data across a boundary where the document had changed.
The new rule is narrower and easier to audit. A cross-document navigation gets a new RenderFrameHost. Same-document navigation, such as a fragment jump or history.pushState(), keeps the same document and therefore the same host. Renderer-process identity and SiteInstance identity may stay the same across a same-site cross-document navigation; document identity still changes, so the browser-side host changes with it.
The distinction sits below the process model. Site Isolation decides when a navigation must cross a process boundary. RenderDocument decides when the browser-side object representing the current document must change, even if no process swap happens. The result is a more precise lifetime rule: process reuse is allowed where Chromium’s process model permits it, but document-scoped browser state cannot ride along by accident.
In code, RenderFrameHost is not a durable synonym for “the frame forever.” It is the host for a specific document at a specific lifecycle point. A frame tree node may outlive many documents. A renderer process may host many documents over time. The current document’s RenderFrameHost is the object that owns document-scoped authority now.
Why It Matters
The security value is direct. Wrong-document bugs happen when browser code reuses data or capabilities from a document that is no longer current: origin checks, cookie-access state, loader factories, permissions, policy containers, navigation handles, or storage keys. Under a reused-host model, those bugs can hide inside an object with the same C++ address after navigation. RenderDocument removes that ambiguity by making the host lifetime match the document lifetime.
The concept also sharpens the Navigation Commit Pipeline. A navigation does not become security-relevant only when it crosses sites. A same-site cross-document navigation can keep the renderer process and SiteInstance, yet still change the committed document, origin, policy container, and document-scoped request authority. Code that reasons only in process terms misses that boundary. RenderDocument gives reviewers the smaller unit they need.
Back/forward cache (BFCache) behavior makes the distinction visible. A document that the user leaves may not be destroyed. It may be frozen with its DOM and JavaScript heap, then restored later. The current frame now points to another RenderFrameHost, while the old one remains alive but inactive. Code that holds a raw host pointer and later treats it as the current document has crossed into the wrong lifetime.
For downstream Chromium-based products, the hazard is common in customization layers. Enterprise policy hooks, WebView2 embedder logic, Electron navigation handlers, custom scheme handlers, and metrics code often cache browser-side objects to connect asynchronous work back to a frame. RenderDocument changes the safe pattern: cache an identity token when necessary, then reacquire and revalidate the current host at the point of use. A cached pointer does not prove the document is still current.
How to Recognize It
The official docs/render_document.md page names the invariant directly: a new document should get a new RenderFrameHost. It distinguishes same-document navigation from cross-document navigation and explains why host reuse can carry the wrong document’s data or capabilities forward.
The public API has the same shape. content/public/browser/render_frame_host.h tells embedders to store a GlobalRenderFrameHostToken rather than a raw pointer, then use RenderFrameHost::FromFrameToken() when they need the live object. It exposes GetDocumentRef() and GetWeakDocumentPtr() for document-scoped references, and it names pending cross-document navigation as a first-class lifecycle state.
Lifecycle APIs are another signal. RenderFrameHost::GetLifecycleState() can report active, pending commit, prerendering, cached in BFCache, or pending deletion. The current document is only one member of that lifecycle set. A host in BFCache or pending deletion may still exist and still receive some events, but it isn’t the active authority for the frame.
The transition is still visible through migration vocabulary. content/public/common/content_features.cc defines the kRenderDocument feature as enabled by default, and RenderFrameHost::ShouldChangeRenderFrameHostOnSameSiteNavigation() remains as a temporary hook for code that still needs to ask whether a same-site navigation should swap hosts. The hook’s presence is a warning sign: code near it is probably at the boundary between the old reused-host model and the new one-host-per-document model.
How It Plays Out
A downstream browser fork adds enterprise policy that attaches a per-document decision to the current main-frame RenderFrameHost. The first implementation stores a raw pointer when navigation starts and applies the decision after an asynchronous policy fetch returns. During the fetch, the frame commits a same-site cross-document navigation. Under RenderDocument, the current document now has a different host. Applying the old decision to the old pointer either targets an inactive document or reads state from the prior document. The corrected implementation stores a frame token and a document reference, reacquires the current host at completion, and drops the result if the document changed.
A feature team wires a custom URLLoaderFactory for subresource requests. The code works in a single navigation test, then fails when a same-site navigation commits without a renderer-process swap. The bug is not in the Network Service. The factory was derived from the previous document’s host and reused after commit. The fix is to build the factory from the active document’s browser-side state after the cross-document navigation has committed, keeping the URLLoaderFactory Trust Boundary attached to the document that will issue requests.
A metrics owner records a “page has cookie access” bit on a RenderFrameHost and reads it later during BFCache restore. Some restores report stale access because the old host remained alive while another document became current. The right question is not whether the pointer is non-null. The right question is whether the host’s lifecycle state and document reference still match the document whose metric is being recorded. The Storage Partition Boundary depends on that answer.
Consequences
The benefit is a tighter authority model. Browser-side document state now has a lifetime that matches the document, which makes origin checks, loader factories, policy containers, storage keys, cookie access, and navigation state easier to audit. A reviewer can ask whether the code is using the current document host, and the answer is concrete.
The model also makes process reuse less misleading. Same-process navigation is a process fact, not a document fact. Chromium can reuse a renderer process where the process model allows it while still replacing the browser-side document host. This lets process economy and document authority move independently instead of forcing one lifetime to stand in for the other.
The cost is lifecycle complexity. Code that used to cache RenderFrameHost* for convenience has to store tokens, weak document references, or frame identifiers and re-check them later. Tests that assumed one host object survives a same-site navigation have to change. BFCache, prerendering, pending commit, and pending deletion all become normal states that code must handle rather than rare edge cases.
The migration surface is long because RenderFrameHost is a central API. Some code still carries compatibility hooks while old assumptions are removed. That makes the invariant more important, not less: new code should not copy the transitional shape. It should use document-scoped APIs and treat host reuse across cross-document navigation as the legacy behavior being retired.
Notes for Agent Context
When writing Chromium code near navigation, do not cache a raw RenderFrameHost* across asynchronous work and assume it still names the current document. Store a GlobalRenderFrameHostToken, DocumentRef, or WeakDocumentPtr as the API requires, then reacquire the live host and verify both the document reference and lifecycle state before using document-scoped state.
Treat same-document navigation and cross-document navigation as different lifetimes. Fragment changes and history.pushState() keep the document; cross-document navigation gets a new RenderFrameHost under RenderDocument, even when the renderer process and SiteInstance stay the same. Derive permissions, policy containers, loader factories, storage keys, cookie access, and metrics from the active document host at the point of use, not from a host captured before commit.
When reviewing code that calls ShouldChangeRenderFrameHostOnSameSiteNavigation() or touches pending cross-document navigations, assume it sits on the RenderDocument migration boundary. Do not add new same-site navigation shortcuts that depend on reusing a host for a different document.
Related Articles
Complements: Back/Forward Cache Eligibility Gate — A replaced RenderFrameHost may enter the back/forward cache, unload, or wait for deletion while another host becomes current.
Complements: Browser-Renderer Privilege Split — The browser process owns document identity, so renderer claims about which document is current remain inputs rather than authority.
Complements: Site Isolation — Site Isolation separates sites by process; RenderDocument separates documents inside the browser-side frame-host lifetime model.
Informs: Storage Partition Boundary — Storage keys, cookie access, and document policy are attached to the current document context, so RenderDocument affects which browser-side host owns those facts.
Informs: URLLoaderFactory Trust Boundary — Document-scoped loader factories must be derived from the current document host, not from a stale RenderFrameHost cached before navigation.
Refines: Navigation Commit Pipeline — RenderDocument refines the navigation commit pipeline by making cross-document commit select a fresh browser-side document host, even when the renderer process can be reused.
Sources
The Chromium project’s docs/render_document.md page is the canonical source for the one-RenderFrameHost-per-document invariant, the same-document versus cross-document distinction, and the wrong-document data/capability bug class the project is removing. docs/navigation_concepts.md supplies the navigation vocabulary the invariant depends on, especially the split between same-document navigation and cross-document navigation. The public RenderFrameHost API comments in content/public/browser/render_frame_host.h record the embedder-facing lifetime guidance: store tokens instead of raw pointers, use document-scoped references, account for BFCache, and check lifecycle state. The kRenderDocument feature definition in content/public/common/content_features.cc shows the feature is enabled by default and still has source-level migration hooks. The Chromium multi-process architecture design document supplies the broader process-model context: document identity and process identity are related, but not the same boundary.
Technical Drill-Down
• docs/render_document.md (pinned 0d17940) — project overview for the RenderDocument migration, including the one-host-per-document invariant and the wrong-document bug class.
• docs/navigation_concepts.md (pinned 0d17940) — same-document and cross-document navigation vocabulary used to decide when document identity changes.
• content/public/browser/render_frame_host.h (pinned 0d17940) — public lifecycle API, including DocumentRef, WeakDocumentPtr, pending cross-document navigations, lifecycle-state checks, and same-site host-change hooks.
• content/public/common/content_features.cc (pinned 0d17940) — kRenderDocument feature definition and source comment for default-on behavior.
• Chromium multi-process architecture design document — the original process-model context that RenderDocument refines at document lifetime scale.