Add support for site-per-process

Issue #2498 resolved
Marshall Greenblatt created an issue

What steps will reproduce the problem?

The site-per-process mode is enabled for Chromium be default in M70 (https://crbug.com/856734). To test with CEF:

  1. Create a build of CEF that returns true from CefContentBrowserClient::ShouldEnableStrictSiteIsolation.

What is the expected output? What do you see instead?

All tests should succeed. Instead, V8Test.*, FrameTest.*, NavigationTest.* and SchemeHandlerTest.* become flaky.

This mode appears to break tests that rely on command-line arguments passed to the renderer process. It looks like the first renderer process is getting all of the callbacks despite multiple renderer processes being launched. For example, V8RendererTest::OnBrowserCreated appears to get the same kV8TestCmdArg value twice when running with --gtest_filter=V8Test.ContextEvalCspBypassUnsafeEval:V8Test.ContextEntered.

Official response

  • Marshall Greenblatt reporter

    Documentation on the site-per-process command-line flag:

    // Enforces a one-site-per-process security policy:
    //  * Each renderer process, for its whole lifetime, is dedicated to rendering
    //    pages for just one site.
    //  * Thus, pages from different sites are never in the same process.
    //  * A renderer process's access rights are restricted based on its site.
    //  * All cross-site navigations force process swaps.
    //  * <iframe>s are rendered out-of-process whenever the src= is cross-site.
    //
    // More details here:
    // - https://www.chromium.org/developers/design-documents/site-isolation
    // - https://www.chromium.org/developers/design-documents/process-models
    // - The class comment in site_instance.h, listing the supported process models.
    

    Supporting site-per-process in CEF will involve the following changes:

    • Move the SendProcessMessage method from CefBrowser (WebContentsObserver) to CefFrame (RenderFrameHost). (DONE)
    • Add a new CefFrame parameter to OnProcessMessageReceived. (DONE)
    • Update helpers like CefMessageRouter to work with the new model. (DONE)
    • Enable strict site isolation by default. The CefContentBrowserClient::ShouldEnableStrictSiteIsolation method will only be called to determine the default state for site isolation if both the --site-per-process (to enable) and --disable-site-isolation-trials (to disable) command-line flags are unspecified. There's also a ShouldDisableSiteIsolation method which currently returns false in //content and true in //chrome for low-memory systems. (DONE)
    • Add support for chrome://process-internals (DONE)
    • Add unit tests to verify behaviors specific to site isolation and OOPIFs.
    • Add support for --enable-features=OutOfBlinkCors (see issue #2716 for details).
    • Add support for --enable-features=MimeHandlerViewInCrossProcessFrame (see issue #2727 for details).

Comments (31)

  1. Salvador Diaz Fau

    We have detected a related issue when we use the --site-per-process switch.

    Steps to reproduce :

    • Load a web page with several frames like youtube.com
    • Load a web page without frames like google.com
    • Call browser.GetFrameNames while you are in google.com and the returned names include names that only existed in the first page.

    If you don't use the --site-per-process switch or you visit google.com in the first place then browser.GetFrameNames works perfectly.

    We tested this using a CEF4Delphi demo with CEF 3.3538.1848.g1d1fe01 which includes Chromium 70.0.3538.77

  2. Dmitry Azaraev

    I'm note, that current CEF API call SendProcessMessage should be tied to CefFrame rather than CefBrowser to sending messages into correct process. This basically tied to out-of-process iframes, but site-per-process effectively uses this feature.

  3. Salvador Diaz Fau

    Another CEF4Delphi user detected an additional issue while using OSR browsers with cross-domain iframes and the --site-per-process switch.

    Steps to reproduce :

    • Run cefclient with these parameters : cefclient.exe --off-screen-rendering-enabled --off-screen-frame-rate=30 --site-per-process
    • Visit a web page that includes a cross domain iframe. The iframe has this JS code to show the screen size :

    HTML :

    <html><head></head><body><p id="demo"></p><script>document.getElementById("demo").innerHTML = "Screen width is " + screen.width;</script></body></html>

    #!HTML
    
    * cefclient will show the real screen width instead of the browser width, ignoring what was set in CefRenderHandler::GetScreenInfo

    Workaround : remove the "--site-per-process" switch and cefclient will show the correct screen size.

    Tested with CEF 3.3538.1852.gcb937fc

  4. Marshall Greenblatt reporter

    Documentation on the site-per-process command-line flag:

    // Enforces a one-site-per-process security policy:
    //  * Each renderer process, for its whole lifetime, is dedicated to rendering
    //    pages for just one site.
    //  * Thus, pages from different sites are never in the same process.
    //  * A renderer process's access rights are restricted based on its site.
    //  * All cross-site navigations force process swaps.
    //  * <iframe>s are rendered out-of-process whenever the src= is cross-site.
    //
    // More details here:
    // - https://www.chromium.org/developers/design-documents/site-isolation
    // - https://www.chromium.org/developers/design-documents/process-models
    // - The class comment in site_instance.h, listing the supported process models.
    

    Supporting site-per-process in CEF will involve the following changes:

    • Move the SendProcessMessage method from CefBrowser (WebContentsObserver) to CefFrame (RenderFrameHost). (DONE)
    • Add a new CefFrame parameter to OnProcessMessageReceived. (DONE)
    • Update helpers like CefMessageRouter to work with the new model. (DONE)
    • Enable strict site isolation by default. The CefContentBrowserClient::ShouldEnableStrictSiteIsolation method will only be called to determine the default state for site isolation if both the --site-per-process (to enable) and --disable-site-isolation-trials (to disable) command-line flags are unspecified. There's also a ShouldDisableSiteIsolation method which currently returns false in //content and true in //chrome for low-memory systems. (DONE)
    • Add support for chrome://process-internals (DONE)
    • Add unit tests to verify behaviors specific to site isolation and OOPIFs.
    • Add support for --enable-features=OutOfBlinkCors (see issue #2716 for details).
    • Add support for --enable-features=MimeHandlerViewInCrossProcessFrame (see issue #2727 for details).

  5. Masako Toda

    I’m very interested in site-per-process option and have been poking around the code. I made it work partially and so I’d like to mention something I noticed:

    • I had to add CefContentClient::CanSendWhileSwappedOut to allow to send CefHostMsg_* messages.
    • I had to add CefMsg_Request etc. to CefBrowserHostImpl::OnMessageReceived
    • When the frames are “swapped”, the original frame was “Detached” and had to tell browser process to remove them from the frames list maintained in browser process.
  6. Marshall Greenblatt reporter

    Move message routing from CefBrowser to CefFrame (see issue #2498).

    This change moves the SendProcessMessage method from CefBrowser to CefFrame and adds CefBrowser parameters to OnProcessMessageReceived and OnDraggableRegionsChanged.

    The internal implementation has changed as follows: - Frame IDs are now a 64-bit combination of the 32-bit render_process_id and render_routing_id values that uniquely identify a RenderFrameHost (RFH). - CefFrameHostImpl objects are now managed by CefBrowserInfo with life span tied to RFH expectations. Specifically, a CefFrameHostImpl object representing a sub-frame will be created when a RenderFrame is created in the renderer process and detached when the associated RenderFrame is deleted or the renderer process in which it runs has died. - The CefFrameHostImpl object representing the main frame will always be valid but the underlying RFH (and associated frame ID) may change over time as a result of cross-origin navigations. Despite these changes calling LoadURL on the main frame object in the browser process will always navigate as expected. - Speculative RFHs, which may be created as a result of a cross-origin navigation and discarded if that navigation is not committed, are now handled correctly (e.g. ignored in most cases until they're committed). - It is less likely, but still possible, to receive a CefFrame object with an invalid frame ID (ID < 0). This can happen in cases where a RFH has not yet been created for a sub-frame. For example, when OnBeforeBrowse is called before initiating navigation in a previously nonexisting sub-frame.

    To test: All tests pass with NetworkService enabled and disabled.

    → <<cset 241941a44a26>>

  7. Marshall Greenblatt reporter

    Fix issues with request callbacks during browser shutdown (see issue #2622).

    The behavior has changed as follows with NetworkService enabled: - All pending and in-progress requests will now be aborted when the CEF context or associated browser is destroyed. The OnResourceLoadComplete callback will now also be called in this case for in-progress requests that have a handler. - The CefResourceHandler::Cancel method will now always be called when resource handling is complete, irrespective of whether handling completed successfully. - Request callbacks that arrive after the OnBeforeClose callback for the associated browser (which may happen for in-progress requests that are aborted on browser destruction) will now always have a non-nullptr CefBrowser parameter. - Allow empty parameters to CefRequest and CefResponse methods where it makes sense (e.g. resetting default response state, or clearing a referrer value). - Fixed a reference loop that was keeping CefResourceHandler objects from being destroyed if they were holding a callback reference (from ProcessRequest, ReadResponse, etc.) during CEF context or associated browser destruction. - Fixed an issue where the main frame was not detached on browser destruction which could cause a crash due to RFH use-after-free (see issue #2498).

    To test: All unit tests pass as expected.

    → <<cset fa5268fa2d87>>

  8. Marshall Greenblatt reporter

    With site-per-process enabled a spare renderer process will be created for use with a future browser or navigation.

    For example, consider a case where 2 browsers are created with different origins. 3 renderer processes in total will be created ([1], [2] and [3]) . [1] and [2] are created at the same time as the 1st browser creation/navigation. [3] is created at the same time as the 2nd browser creation/navigation. Navigation and callbacks for the 1st browser will occur in the 1st renderer process (created via [1]). Navigation and callbacks for the 2nd browser will occur in the 2nd renderer process (created via [2]). The 3rd renderer process (created via [3]) remains spare, presumably to be used for the next browser or navigation.

    Consequently, because a new browser will use an existing renderer process, we can no longer rely on the timing of ContentRendererClient::RenderThreadConnected and CefRenderProcessHandler::OnRenderThreadCreated aligning with a newly created browser. From a practical standpoint this means that we can’t use OnRenderThreadCreated to set state for the currently executing test case (because it will be called when the renderer process is created during execution of the previous test case).

    [1]
    >   content.dll!content::RenderProcessHostImpl::RenderProcessHostImpl(content::BrowserContext * browser_context, content::StoragePartitionImpl * storage_partition_impl, bool is_for_guests_only) Line 1472 C++
        content.dll!content::RenderProcessHostImpl::CreateRenderProcessHost(content::BrowserContext * browser_context, content::StoragePartitionImpl * storage_partition_impl, content::SiteInstance * site_instance, bool is_for_guests_only) Line 1413    C++
        content.dll!content::RenderProcessHostImpl::GetProcessHostForSiteInstance(content::SiteInstanceImpl * site_instance) Line 3999  C++
        content.dll!content::SiteInstanceImpl::GetProcess() Line 267    C++
        content.dll!content::WebContentsImpl::Init(const content::WebContents::CreateParams & params) Line 2124 C++
        content.dll!content::WebContentsImpl::CreateWithOpener(const content::WebContents::CreateParams & params, content::RenderFrameHostImpl * opener_rfh) Line 799   C++
        content.dll!content::WebContents::Create(const content::WebContents::CreateParams & params) Line 308    C++
        libcef.dll!CefBrowserHostImpl::Create(CefBrowserHostImpl::CreateParams & create_params) Line 365    C++
    
     [2]
     >  content.dll!content::RenderProcessHostImpl::RenderProcessHostImpl(content::BrowserContext * browser_context, content::StoragePartitionImpl * storage_partition_impl, bool is_for_guests_only) Line 1472 C++
        content.dll!content::RenderProcessHostImpl::CreateRenderProcessHost(content::BrowserContext * browser_context, content::StoragePartitionImpl * storage_partition_impl, content::SiteInstance * site_instance, bool is_for_guests_only) Line 1413    C++
        content.dll!content::`anonymous namespace'::SpareRenderProcessHostManager::WarmupSpareRenderProcessHost(content::BrowserContext * browser_context) Line 514 C++
        content.dll!content::`anonymous namespace'::SpareRenderProcessHostManager::PrepareForFutureRequests(content::BrowserContext * browser_context) Line 603 C++
        content.dll!content::RenderProcessHostImpl::NotifySpareManagerAboutRecentlyUsedBrowserContext(content::BrowserContext * browser_context) Line 2728  C++
        content.dll!content::RenderFrameHostManager::GetSiteInstanceForNavigation(const GURL & dest_url, content::SiteInstanceImpl * source_instance, content::SiteInstanceImpl * dest_instance, content::SiteInstanceImpl * candidate_instance, ui::PageTransition transition, bool is_failure, bool dest_is_restore, bool dest_is_view_source_mode, bool was_server_redirect) Line 1309   C++
        content.dll!content::RenderFrameHostManager::GetSiteInstanceForNavigationRequest(const content::NavigationRequest & request) Line 2295  C++
        content.dll!content::RenderFrameHostManager::GetFrameHostForNavigation(const content::NavigationRequest & request) Line 664 C++
        content.dll!content::RenderFrameHostManager::DidCreateNavigationRequest(content::NavigationRequest * request) Line 631  C++
        content.dll!content::FrameTreeNode::CreatedNavigationRequest(std::__1::unique_ptr<content::NavigationRequest,std::__1::default_delete<content::NavigationRequest>> navigation_request) Line 445 C++
        content.dll!content::NavigatorImpl::Navigate(std::__1::unique_ptr<content::NavigationRequest,std::__1::default_delete<content::NavigationRequest>> request, content::ReloadType reload_type, content::RestoreType restore_type) Line 374    C++
        content.dll!content::NavigationControllerImpl::NavigateWithoutEntry(const content::NavigationController::LoadURLParams & params) Line 2876  C++
        content.dll!content::NavigationControllerImpl::LoadURLWithParams(const content::NavigationController::LoadURLParams & params) Line 956  C++
        content.dll!content::NavigationControllerImpl::LoadURL(const GURL & url, const content::Referrer & referrer, ui::PageTransition transition, const std::__1::basic_string<char,std::__1::char_traits<char>,std::__1::allocator<char>> & extra_headers) Line 919  C++
        libcef.dll!CefBrowserHostImpl::LoadMainFrameURL(const std::__1::basic_string<char,std::__1::char_traits<char>,std::__1::allocator<char>> & url, const content::Referrer & referrer, ui::PageTransition transition, const std::__1::basic_string<char,std::__1::char_traits<char>,std::__1::allocator<char>> & extra_headers) Line 1641  C++
    
     [3]
     >  content.dll!content::RenderProcessHostImpl::RenderProcessHostImpl(content::BrowserContext * browser_context, content::StoragePartitionImpl * storage_partition_impl, bool is_for_guests_only) Line 1472 C++
        content.dll!content::RenderProcessHostImpl::CreateRenderProcessHost(content::BrowserContext * browser_context, content::StoragePartitionImpl * storage_partition_impl, content::SiteInstance * site_instance, bool is_for_guests_only) Line 1413    C++
        content.dll!content::`anonymous namespace'::SpareRenderProcessHostManager::WarmupSpareRenderProcessHost(content::BrowserContext * browser_context) Line 514 C++
        content.dll!content::`anonymous namespace'::SpareRenderProcessHostManager::PrepareForFutureRequests(content::BrowserContext * browser_context) Line 603 C++
        content.dll!content::RenderProcessHostImpl::GetProcessHostForSiteInstance(content::SiteInstanceImpl * site_instance) Line 4013  C++
        content.dll!content::SiteInstanceImpl::GetProcess() Line 267    C++
        content.dll!content::WebContentsImpl::Init(const content::WebContents::CreateParams & params) Line 2124 C++
        content.dll!content::WebContentsImpl::CreateWithOpener(const content::WebContents::CreateParams & params, content::RenderFrameHostImpl * opener_rfh) Line 799   C++
        content.dll!content::WebContents::Create(const content::WebContents::CreateParams & params) Line 308    C++
        libcef.dll!CefBrowserHostImpl::Create(CefBrowserHostImpl::CreateParams & create_params) Line 365    C++
    

  9. Marshall Greenblatt reporter

    Other behavior changes with site-per-process enabled (as identified by test cases):

    • Cross-origin navigations will again receive a new renderer process, as expected. This is tested by NavigationTest.LoadCrossOrigin*.
    • Calling LoadURL from the renderer process with a different origin will result in the renderer process being terminated with “bad IPC message” reason INVALID_INITIATOR_ORIGIN (213). Previously, this navigation would be allowed in the same renderer process. This is tested by RequestHandlerTest.NotificationsCrossOriginDelayedRenderer. Call stack for this check:
    [1003/160738.540:ERROR:bad_message.cc(27)] Terminating renderer for bad IPC message, reason 213
    
    >   content.dll!content::bad_message::`anonymous namespace'::LogBadMessage(content::bad_message::BadMessageReason reason) Line 27   C++
        content.dll!content::bad_message::ReceivedBadMessage(int render_process_id, content::bad_message::BadMessageReason reason) Line 56  C++
        content.dll!content::`anonymous namespace'::VerifyInitiatorOrigin(int process_id, const url::Origin & initiator_origin) Line 59 C++
        content.dll!content::VerifyBeginNavigationCommonParams(content::SiteInstance * site_instance, content::mojom::CommonNavigationParams * common_params) Line 172  C++
        content.dll!content::RenderFrameHostImpl::BeginNavigation(mojo::StructPtr<content::mojom::CommonNavigationParams> common_params, mojo::StructPtr<content::mojom::BeginNavigationParams> begin_params, mojo::PendingRemote<blink::mojom::BlobURLToken> blob_url_token, mojo::AssociatedInterfacePtrInfo<content::mojom::NavigationClient> navigation_client, mojo::PendingRemote<blink::mojom::NavigationInitiator> navigation_initiator) Line 4181  C++
        content.dll!content::mojom::FrameHostStubDispatch::Accept(content::mojom::FrameHost * impl, mojo::Message * message) Line 5884  C++
    

  10. Marshall Greenblatt reporter

    Enable strict site isolation by default (see issue #2498)

    This restores the default site isolation mode for Chromium on desktop platforms. Unit tests have been updated to reflect the new behavior expectations.

    Known behavior changes in CEF are as follows: - A spare renderer process may be created on initial browser creation or cross- origin navigation. This spare process may be used with a future cross-origin navigation or discarded on application shutdown. As a result CefRenderProcessHandler::OnRenderThreadCreated, which is called shortly after renderer process creation, can no longer be used to reliably transfer state for the currently in-progress navigation. Unit tests have been updated to use the CreateBrowser/OnBeforePopup |extra_info| value for transferring test state to CefRenderProcessHandler::OnBrowserCreated which will be called in the correct/expected renderer process. - Cross-origin navigations will again receive a new renderer process, as expected. This behavior had briefly regressed in M78 due to the ProcessSharingWithDefaultSiteInstances feature becoming enabled by default. - Cross-origin navigations initiated by calling LoadURL in the renderer process will now crash that process with "bad IPC message" reason INVALID_INITIATOR_ORIGIN (213). This is a security feature implemented in Chromium. - A DevTools browser created using CefBrowserHost::ShowDevTools will receive the same CefRenderProcessHandler::OnBrowserCreated |extra_info| value that was set via CreateBrowser/OnBeforePopup for the parent browser.

    → <<cset eea1f6be6319>>

  11. Marshall Greenblatt reporter

    Enable strict site isolation by default (see issue #2498)

    This restores the default site isolation mode for Chromium on desktop platforms. Unit tests have been updated to reflect the new behavior expectations.

    Known behavior changes in CEF are as follows: - A spare renderer process may be created on initial browser creation or cross- origin navigation. This spare process may be used with a future cross-origin navigation or discarded on application shutdown. As a result CefRenderProcessHandler::OnRenderThreadCreated, which is called shortly after renderer process creation, can no longer be used to reliably transfer state for the currently in-progress navigation. Unit tests have been updated to use the CreateBrowser/OnBeforePopup |extra_info| value for transferring test state to CefRenderProcessHandler::OnBrowserCreated which will be called in the correct/expected renderer process. - Cross-origin navigations will again receive a new renderer process, as expected. This behavior had briefly regressed in M78 due to the ProcessSharingWithDefaultSiteInstances feature becoming enabled by default. - Cross-origin navigations initiated by calling LoadURL in the renderer process will now crash that process with "bad IPC message" reason INVALID_INITIATOR_ORIGIN (213). This is a security feature implemented in Chromium. - A DevTools browser created using CefBrowserHost::ShowDevTools will receive the same CefRenderProcessHandler::OnBrowserCreated |extra_info| value that was set via CreateBrowser/OnBeforePopup for the parent browser.

    → <<cset 6103725369b6>>

  12. Salvador Diaz Fau

    I just tested #2653 again with CEF 77.1.7+gc7dbc2f+chromium-77.0.3865.90 and it’s fixed.
    Sorry for not testing that before.

  13. Marshall Greenblatt reporter

    @Elad Bahar Yes, OSR should work. The unit tests are passing. If you find any issues please let us know.

  14. Marshall Greenblatt reporter

    Marking this issue as Resolved because the work specific to this issue is complete. Related issues have their own tickets. Please file new bugs for any newly-discovered issues.

  15. Marshall Greenblatt reporter

    Remove render thread created callbacks (see issue #2498)

    With site-per-process enabled a spare renderer process will be created for use with a future browser or navigation. Consequently the |extra_info| parameter populated in OnRenderProcessThreadCreated will no longer be delivered to OnRenderThreadCreated in the expected renderer process. To avoid confusion these callbacks have been removed completely.

    After this change CefRenderProcessHandler::OnWebKitInitialized should be used for startup tasks in the render process, and OnBrowserCreated should be used in the render process to recieve |extra_info| passed from CefBrowserHost::CreateBrowser or CefLifeSpanHandler::OnBeforePopup.

    → <<cset 280c9127c1fc>>

  16. Alex Maitland

    It’s my understanding that site-per-process is the default process model in both CEF and Chrome. See https://www.chromium.org/Home/chromium-security/site-isolation

    This documentation predates the launch of Site Isolation and should be updated

    As per https://www.chromium.org/developers/design-documents/process-models

    It’s my understanding this document is outdated and the statement regarding process-per-site-instance being the default in Chrome is no longer correct.

    Also noting that site-per-process and process-per-site are two distinct process models, the names are slightly confusing.

  17. Log in to comment