This page provides an overview of Chromium's upcoming support for out-of-process iframes (OOPIFs). This is a massive refactoring effort motivated by the Site Isolation project, which allows the browser process to restrict which sites are loaded in each renderer process for security. OOPIFs are a general mechanism, though, and can be used for other features than security (e.g., the <webview> tag in Chrome Apps).
At a high level, supporting OOPIFs requires the browser process to track subframes directly, and many features in Chromium must now combine information from frames in multiple processes when operating on a page. Most code in Chromium is affected, including painting, input events, navigation, and many others.
We are nearing the initial launch stages for OOPIFs, and we encourage Chromium developers and advanced users to dogfood and provide feedback. The --isolate-extensions command line flag will enable OOPIFs for isolating web content from extensions, while the --site-per-process flag will more broadly enable OOPIFs for isolating all sites from each other. The latter can also be enabled from chrome://flags using "Enable out of process iframes." Please file any bugs observed using the Cr=Internals-Sandbox-SiteIsolation label in https://crbug.com.
Given the large architecture changes needed to support OOPIFs (and opportunities for regressions), we will first enable OOPIFs in limited scenarios that offer high security value and low user impact. The first stage of launch will be isolating web iframes inside extension processes (and vice versa), as implemented with the --isolate-extensions flag. This is a relatively rare case for users, but it helps prevent web content from compromising processes with extension privileges.
Future stages of launch will start to use OOPIFs for other Site Isolation scenarios, with the goal of isolating a subset of (possibly user-specific) high value web sites from other web sites. It would be possible to isolate all web sites from each other (as implemented with the --site-per-process flag), but we are unlikely to launch this due to the number of processes it would require. In addition to a subset of high value sites, we are considering isolating the Chrome Web Store (which has elevated privileges) and sites flagged by Safe Browsing if a user clicks through to them (since they are known to be risky).
OOPIFs can be used for other goals than security as well. They allow frames to be rendered in a different process than the main frame, so they may improve parallelism if used for third party iframes. We are investigating the tradeoff between the extra parallelism and memory overhead to determine if there are cases this is worthwhile. Note that OOPIFs cannot be used for same-site frames (which may synchronously script each other), and we have no current plans to use them for cross-thread frames in the same process.
Much of the logic in the content module has moved from being tab-specific to frame-specific, since each frame may be rendered in different processes over its lifetime.
To support cross-process interactions like postMessage on a document's DOMWindow, Chromium must keep a proxy versions of the DOMWindow in each of the other processes that can reach it. As shown in the diagram at right, this allows a document from site A to find a proxy in its own process for a DOMWindow that is currently active on site B. The proxy can then forward the postMessage call to the browser and then to the correct document in the process for site B.
OOPIFs require each renderer process to keep track of proxy DOMWindows for all reachable frames, both main frames and subframes.
In the browser process, Chromium keeps track of the full frame tree for each tab. WebContents now hosts a tree of FrameTreeNode objects, mirroring the frame tree of the current page. Each FrameTreeNode contains frame-specific information (e.g., the frame's name, origin, etc). Its RenderFrameHostManager is responsible for cross-process navigations in the frame, and it supports routing messages from proxies in other processes to the active frame.
We are pulling the frame-specific logic out of the content module's RenderView and RenderViewHost classes into routable RenderFrame and RenderFrameHost classes. We have one full RenderFrame (in some process) for every frame in a page, and we have a corresponding but slimmed down RenderFrameProxy as a placeholder in the other processes that can reference it. These proxies are shown with dashed lines in the diagram below, which depicts one BrowsingInstance (i.e., group of related windows) with two tabs, containing two subframes each.
LocalFrame and RemoteFrame inherit from the Frame interface. While downcasts from Frame to LocalFrame are possible, this will likely cause bugs with OOPIFs unless extra care is taken. LocalFrame corresponds to WebLocalFrame (in the public API) and content::RenderFrame, while RemoteFrame corresponds to WebRemoteFrame and content::RenderFrameProxy.
Blink has the ability to swap any frame between the local and remote versions. (This replaces the old "swapped out RenderViewHost" implementation that Chromium used for cross-process navigations.)
It is worth noting that the <webview> implementation is being migrated to work on top of the new OOPIF infrastructure. For the most part, Blink code will be able to treat a <webview> similar to an <iframe>. However, there is one important difference: the parent frame of an <iframe> is the document that contains the <iframe> element, while the root frame of a <webview> has no parent and is itself a main frame. It will likely live in a separate frame tree.
This support for OOPIFs and <webview> has several major implications for Blink:
Some earlier information on the refactoring goals can be found in the FrameHandle design doc, however that is largely obsolete.
Note: We are attempting to minimize the memory requirements of RemoteFrames and RemoteDOMWindows, because there will be many more than in Chromium before OOPIFs. Before, the space required for swapped out RenderViewHosts was O(tabs * processes) within a BrowsingInstance, and most BrowsingInstances only contain 1 or 2 tabs. OOPIFs will require O(frames * processes) space for proxies. This could be much higher, because the number of frames can be much larger than the number of tabs, and because the number of processes will increase based on cross-site frames. Fortunately, RemoteFrames require far less memory than LocalFrames, and not all cross-site iframes will require separate processes.
Chromium now has support for cross-process navigations within subframes when using the --isolate-extensions or --site-per-process flags. Rather than letting the renderer process intercept the navigation and decide if the browser process should handle it, all navigations will be intercepted in the browser process's network stack. If the navigation crosses a site boundary that requires isolation (according to our Site Isolation policy), the browser process will swap the frame's renderer process. This can be done because the browser process knows the full frame tree, as described above. Until PlzNavigate launches, this is implemented using CrossSiteResourceHandler to transfer navigations to a different process when needed.
A tab's session history also becomes more complicated when subframes may be rendered by different processes. Currently, Blink takes care of tracking the frame tree in each HistoryItem in the renderer process, and the browser process just tracks each back/forward entry using NavigationEntry. We are removing the frame tracking logic from Blink's HistoryController to keep track of each frame's navigations in the browser process directly.
We will also change the representation of a tab's session history to more closely match the HTML5 spec. Rather than cloning the frame tree for each HistoryItem, we will keep track of each frame's session history separately in the browser process, and we will use a separate "joint session history" list for back and forward navigations. Each entry in this list will have a tree of pointers to each frame's corresponding session history item. We expect this to require changes to the session restore logic as well.
All details of navigation refactoring are described in this design document.
To render an iframe in a different process than its parent frame, the browser process passes information back and forth between the renderer processes and helps the GPU process composite the images together in the correct sizes and locations. We use the Surfaces implementation to maintain a set of textures from multiple renderer processes, compositing them into a single output image. More details are available in this design document.
Similar to rendering, we use the Surfaces implementation to do hit testing in the browser process to deliver input events directly to the intended frame's renderer process. We are also starting to manage focus in the browser process to send keyboard events directly to the renderer process of the focused frame. More details are available in this design document.