|  | // Copyright 2023 The Chromium Authors | 
|  | // Use of this source code is governed by a BSD-style license that can be | 
|  | // found in the LICENSE file. | 
|  |  | 
|  | #include "extensions/browser/process_map.h" | 
|  |  | 
|  | #include <memory> | 
|  | #include <string_view> | 
|  | #include <vector> | 
|  |  | 
|  | #include "base/strings/stringprintf.h" | 
|  | #include "chrome/browser/extensions/extension_browsertest.h" | 
|  | #include "chrome/browser/profiles/profile.h" | 
|  | #include "chrome/browser/ui/browser.h" | 
|  | #include "content/public/browser/render_frame_host.h" | 
|  | #include "content/public/browser/render_process_host.h" | 
|  | #include "content/public/browser/site_isolation_policy.h" | 
|  | #include "content/public/browser/web_contents.h" | 
|  | #include "content/public/common/content_features.h" | 
|  | #include "content/public/test/browser_test.h" | 
|  | #include "content/public/test/browser_test_utils.h" | 
|  | #include "content/public/test/test_navigation_observer.h" | 
|  | #include "extensions/browser/app_window/app_window.h" | 
|  | #include "extensions/browser/app_window/app_window_registry.h" | 
|  | #include "extensions/browser/guest_view/web_view/web_view_guest.h" | 
|  | #include "extensions/common/constants.h" | 
|  | #include "extensions/common/mojom/context_type.mojom.h" | 
|  | #include "extensions/test/extension_test_message_listener.h" | 
|  | #include "extensions/test/test_extension_dir.h" | 
|  | #include "net/dns/mock_host_resolver.h" | 
|  | #include "testing/gtest/include/gtest/gtest.h" | 
|  |  | 
|  | namespace extensions { | 
|  |  | 
|  | class ProcessMapBrowserTest : public ExtensionBrowserTest { | 
|  | public: | 
|  | ProcessMapBrowserTest() = default; | 
|  | ProcessMapBrowserTest(const ProcessMapBrowserTest&) = delete; | 
|  | ProcessMapBrowserTest& operator=(const ProcessMapBrowserTest&) = delete; | 
|  | ~ProcessMapBrowserTest() override = default; | 
|  |  | 
|  | void SetUpOnMainThread() override { | 
|  | ExtensionBrowserTest::SetUpOnMainThread(); | 
|  | host_resolver()->AddRule("*", "127.0.0.1"); | 
|  | ASSERT_TRUE(embedded_test_server()->Start()); | 
|  | } | 
|  |  | 
|  | content::RenderProcessHost& GetActiveMainFrameProcess() { | 
|  | return *GetActiveWebContents()->GetPrimaryMainFrame()->GetProcess(); | 
|  | } | 
|  |  | 
|  | int GetActiveMainFrameProcessID() { | 
|  | return GetActiveMainFrameProcess().GetDeprecatedID(); | 
|  | } | 
|  |  | 
|  | // Adds a new extension with the given `extension_name` and host permission to | 
|  | // the given `host_pattern`. | 
|  | const Extension* AddExtensionWithHostPermission( | 
|  | std::string_view extension_name, | 
|  | std::string_view host_pattern) { | 
|  | static constexpr char kManifestTemplate[] = | 
|  | R"({ | 
|  | "name": "%s", | 
|  | "manifest_version": 3, | 
|  | "version": "0.1", | 
|  | "host_permissions": ["%s"] | 
|  | })"; | 
|  | auto extension_dir = std::make_unique<TestExtensionDir>(); | 
|  | extension_dir->WriteManifest(base::StringPrintf( | 
|  | kManifestTemplate, extension_name.data(), host_pattern.data())); | 
|  | const Extension* extension = LoadExtension(extension_dir->UnpackedPath()); | 
|  | extension_dirs_.push_back(std::move(extension_dir)); | 
|  | return extension; | 
|  | } | 
|  |  | 
|  | // Adds a new extension with the given `extension_name` and a content script | 
|  | // that runs on `content_script_pattern`, sending a message when the script | 
|  | // injects. | 
|  | const Extension* AddExtensionWithContentScript( | 
|  | std::string_view extension_name, | 
|  | std::string_view content_script_pattern) { | 
|  | static constexpr char kManifestTemplate[] = | 
|  | R"({ | 
|  | "name": "%s", | 
|  | "manifest_version": 3, | 
|  | "version": "0.1", | 
|  | "content_scripts": [{ | 
|  | "matches": ["%s"], | 
|  | "js": ["script.js"] | 
|  | }] | 
|  | })"; | 
|  | auto extension_dir = std::make_unique<TestExtensionDir>(); | 
|  | extension_dir->WriteManifest( | 
|  | base::StringPrintf(kManifestTemplate, extension_name.data(), | 
|  | content_script_pattern.data())); | 
|  | extension_dir->WriteFile(FILE_PATH_LITERAL("script.js"), | 
|  | "chrome.test.sendMessage('script injected');"); | 
|  | const Extension* extension = LoadExtension(extension_dir->UnpackedPath()); | 
|  | extension_dirs_.push_back(std::move(extension_dir)); | 
|  | return extension; | 
|  | } | 
|  |  | 
|  | void ExecuteUserScriptInActiveTab(const ExtensionId& extension_id) { | 
|  | base::RunLoop run_loop; | 
|  | content::WebContents* web_contents = GetActiveWebContents(); | 
|  | // TODO(crbug.com/40262660): Add a utility method for user script | 
|  | // injection in browser tests. | 
|  | ScriptExecutor script_executor(web_contents); | 
|  | std::vector<mojom::JSSourcePtr> sources; | 
|  | sources.push_back( | 
|  | mojom::JSSource::New("document.title = 'injected';", GURL())); | 
|  | script_executor.ExecuteScript( | 
|  | mojom::HostID(mojom::HostID::HostType::kExtensions, extension_id), | 
|  | mojom::CodeInjection::NewJs(mojom::JSInjection::New( | 
|  | std::move(sources), mojom::ExecutionWorld::kUserScript, | 
|  | /*world_id=*/std::nullopt, | 
|  | blink::mojom::WantResultOption::kWantResult, | 
|  | blink::mojom::UserActivationOption::kDoNotActivate, | 
|  | blink::mojom::PromiseResultOption::kAwait)), | 
|  | ScriptExecutor::SPECIFIED_FRAMES, {ExtensionApiFrameIdMap::kTopFrameId}, | 
|  | mojom::MatchOriginAsFallbackBehavior::kNever, | 
|  | mojom::RunLocation::kDocumentIdle, ScriptExecutor::DEFAULT_PROCESS, | 
|  | GURL() /* webview_src */, | 
|  | base::IgnoreArgs<std::vector<ScriptExecutor::FrameResult>>( | 
|  | run_loop.QuitWhenIdleClosure())); | 
|  |  | 
|  | run_loop.Run(); | 
|  |  | 
|  | EXPECT_EQ(u"injected", web_contents->GetTitle()); | 
|  | } | 
|  |  | 
|  | // Helper function to define the test body for tests that use | 
|  | // AddExtensionWithSandboxedWebpage, defined below so it's near the tests that | 
|  | // use it. | 
|  | void VerifyWhetherSubframesAreIsolated( | 
|  | const GURL& frame_url, | 
|  | const std::string& content, | 
|  | bool expect_subframes_isolated_from_each_other, | 
|  | bool expect_sandboxed_subframe_isolated_from_extension_page, | 
|  | bool expect_non_sandboxed_subframe_isolated_from_extension_page); | 
|  |  | 
|  | // Helper function for data: and srcdoc tests regarding resource access from | 
|  | // sandboxed frames, defined below so it's near the tests that use it. | 
|  | // Expects that `parent_script_template` contains a `%s` which this function | 
|  | // will replace with the extension origin. `is_subframe_data_url` should be | 
|  | // true if the `parent_script_template` is for a data url frame, so that this | 
|  | // function doesn't have to infer that from the template. | 
|  | void VerifySandboxedSubframeHasResourceAccessButMaybeApiAccess( | 
|  | const Extension* extension, | 
|  | std::string_view parent_script, | 
|  | const bool is_subframe_data_url, | 
|  | const bool expects_api_access); | 
|  |  | 
|  | // Adds a new extension with a parent frame that in turn loads `url` in two | 
|  | // iframes, one of which is sandboxed. If `url` is about:srcdoc, then the | 
|  | // srcdoc attribute is set instead using the value contained in `content`. | 
|  | const Extension* AddExtensionWithSandboxedWebpage( | 
|  | const GURL& url, | 
|  | const std::string& content) { | 
|  | static constexpr char kManifest[] = | 
|  | R"({ | 
|  | "name": "Sandboxed Page", | 
|  | "manifest_version": 3, | 
|  | "version": "0.1" | 
|  | })"; | 
|  | auto extension_dir = std::make_unique<TestExtensionDir>(); | 
|  | extension_dir->WriteManifest(kManifest); | 
|  | std::string page_content; | 
|  | if (url.IsAboutSrcdoc()) { | 
|  | page_content = base::StringPrintf( | 
|  | R"(<html> | 
|  | <iframe sandbox srcdoc="%s"></iframe> | 
|  | <iframe srcdoc="%s"></iframe> | 
|  | </html>)", | 
|  | content.c_str(), content.c_str()); | 
|  | } else { | 
|  | page_content = base::StringPrintf( | 
|  | R"(<html> | 
|  | <iframe sandbox src="%s"></iframe> | 
|  | <iframe src="%s"></iframe> | 
|  | </html>)", | 
|  | url.spec().c_str(), url.spec().c_str()); | 
|  | } | 
|  | extension_dir->WriteFile(FILE_PATH_LITERAL("parent.html"), page_content); | 
|  | const Extension* extension = LoadExtension(extension_dir->UnpackedPath()); | 
|  | extension_dirs_.push_back(std::move(extension_dir)); | 
|  | return extension; | 
|  | } | 
|  |  | 
|  | // Create an extension with a page that loads a non-extension page, which in | 
|  | // turn contains an about:srcdoc subframe. | 
|  | const Extension* AddExtensionWithNonExtensionSubframeWithSrcdocSubframe( | 
|  | bool srcdoc_is_sandboxed) { | 
|  | static constexpr char kManifest[] = | 
|  | R"({ | 
|  | "name": "Sandboxed Page", | 
|  | "manifest_version": 3, | 
|  | "version": "0.1" | 
|  | })"; | 
|  | auto extension_dir = std::make_unique<TestExtensionDir>(); | 
|  | extension_dir->WriteManifest(kManifest); | 
|  |  | 
|  | GURL non_extension_url = embedded_test_server()->GetURL( | 
|  | "example.com", srcdoc_is_sandboxed ? "/iframe_sandboxed_srcdoc.html" | 
|  | : "/iframe_srcdoc.html"); | 
|  | const char kPageContentTemplate[] = | 
|  | R"(<html> | 
|  | <body> | 
|  | <iframe src="%s"></iframe> | 
|  | </body> | 
|  | </html>)"; | 
|  | extension_dir->WriteFile( | 
|  | FILE_PATH_LITERAL("parent.html"), | 
|  | base::StringPrintf(kPageContentTemplate, | 
|  | non_extension_url.spec().c_str())); | 
|  | // Including a non-web-accessible extension resource for testing access. | 
|  | extension_dir->WriteFile(FILE_PATH_LITERAL("data.json"), | 
|  | "{ \"answer\" : 42 }"); | 
|  | const Extension* extension = LoadExtension(extension_dir->UnpackedPath()); | 
|  | extension_dirs_.push_back(std::move(extension_dir)); | 
|  | return extension; | 
|  | } | 
|  |  | 
|  | // Adds an extension with a page with a sandboxed subframe (that can be | 
|  | // manipulated by individual tests), and a simple resource that the subframe | 
|  | // might load. | 
|  | const Extension* AddExtensionWithResource() { | 
|  | static constexpr char kManifest[] = | 
|  | R"({ | 
|  | "name": "Page With Sandboxed Subframe and Resource To Load", | 
|  | "manifest_version": 3, | 
|  | "version": "0.1" | 
|  | })"; | 
|  | auto extension_dir = std::make_unique<TestExtensionDir>(); | 
|  | extension_dir->WriteManifest(kManifest); | 
|  | std::string page_content = | 
|  | R"(<html> | 
|  | <iframe id='test_frame' sandbox="allow-scripts"></iframe> | 
|  | </html>)"; | 
|  | extension_dir->WriteFile(FILE_PATH_LITERAL("parent.html"), page_content); | 
|  | std::string resource_js = R"(let foo = "bar";)"; | 
|  | extension_dir->WriteFile(FILE_PATH_LITERAL("resource.js"), resource_js); | 
|  | std::string page_requesting_resource_content = | 
|  | R"(<script src="resource.js"></script>)"; | 
|  | extension_dir->WriteFile(FILE_PATH_LITERAL("page_requesting_resource.html"), | 
|  | page_requesting_resource_content); | 
|  | const Extension* extension = LoadExtension(extension_dir->UnpackedPath()); | 
|  | extension_dirs_.push_back(std::move(extension_dir)); | 
|  | return extension; | 
|  | } | 
|  |  | 
|  | // Create a pair of nested extensions, where `page.html` from the first | 
|  | // extension is nested inside `parent.html` from the second extension. | 
|  | std::pair<const Extension*, const Extension*> AddNestedExtensions() { | 
|  | const Extension* extension1 = nullptr; | 
|  | { | 
|  | static constexpr char kManifestTemplate[] = | 
|  | R"({ | 
|  | "name": "Extension1", | 
|  | "manifest_version": 3, | 
|  | "version": "0.1", | 
|  | "web_accessible_resources": [ | 
|  | { | 
|  | "resources": [ "page.html" ], | 
|  | "matches": [ "%s://*/*" ] | 
|  | } | 
|  | ] | 
|  | })"; | 
|  | auto extension_dir = std::make_unique<TestExtensionDir>(); | 
|  | extension_dir->WriteManifest( | 
|  | base::StringPrintf(kManifestTemplate, kExtensionScheme)); | 
|  | extension_dir->WriteFile(FILE_PATH_LITERAL("page.html"), | 
|  | R"(<html>E1</html>)"); | 
|  | extension1 = LoadExtension(extension_dir->UnpackedPath()); | 
|  | extension_dirs_.push_back(std::move(extension_dir)); | 
|  | } | 
|  | GURL e1_page_url = extension1->GetResourceURL("page.html"); | 
|  |  | 
|  | const Extension* extension2 = nullptr; | 
|  | { | 
|  | static constexpr char kManifest[] = | 
|  | R"({ | 
|  | "name": "Extension2", | 
|  | "manifest_version": 3, | 
|  | "version": "0.1" | 
|  | })"; | 
|  | auto extension_dir = std::make_unique<TestExtensionDir>(); | 
|  | extension_dir->WriteManifest(kManifest); | 
|  | static constexpr char kPageContent[] = | 
|  | R"(<html>E2 | 
|  | <iframe sandbox="allow-scripts" src="%s"></iframe> | 
|  | </html>)"; | 
|  | extension_dir->WriteFile( | 
|  | FILE_PATH_LITERAL("parent.html"), | 
|  | base::StringPrintf(kPageContent, e1_page_url.spec().c_str())); | 
|  | // Create a page that is not listed as a web_accessible_resource. | 
|  | extension_dir->WriteFile(FILE_PATH_LITERAL("private_page.html"), | 
|  | R"(<html>E2 Private</html>)"); | 
|  | extension2 = LoadExtension(extension_dir->UnpackedPath()); | 
|  | extension_dirs_.push_back(std::move(extension_dir)); | 
|  | } | 
|  |  | 
|  | return std::make_pair(extension1, extension2); | 
|  | } | 
|  |  | 
|  | // Adds a new extension with two sandboxed frames, `sandboxed.html` and | 
|  | // `sandboxed2.html`, and a parent page, `parent.html` to host it. | 
|  | // Having two manifest-sandboxed pages facilitates testing that there is | 
|  | // just one sandbox process per extension. | 
|  | const Extension* AddExtensionWithSandboxedFrame() { | 
|  | static constexpr char kManifest[] = | 
|  | R"({ | 
|  | "name": "Sandboxed Page", | 
|  | "manifest_version": 3, | 
|  | "version": "0.1", | 
|  | "sandbox": { | 
|  | "pages": [ "sandboxed.html", "sandboxed2.html" ] | 
|  | } | 
|  | })"; | 
|  | auto extension_dir = std::make_unique<TestExtensionDir>(); | 
|  | extension_dir->WriteManifest(kManifest); | 
|  | extension_dir->WriteFile(FILE_PATH_LITERAL("sandboxed.html"), | 
|  | "<html>Sandboxed</html>"); | 
|  | extension_dir->WriteFile(FILE_PATH_LITERAL("sandboxed2.html"), | 
|  | "<html>Sandboxed 2</html>"); | 
|  | extension_dir->WriteFile(FILE_PATH_LITERAL("parent.html"), | 
|  | R"(<html> | 
|  | <iframe src="sandboxed.html"></iframe> | 
|  | <iframe src="sandboxed2.html"></iframe> | 
|  | </html>)"); | 
|  | const Extension* extension = LoadExtension(extension_dir->UnpackedPath()); | 
|  | extension_dirs_.push_back(std::move(extension_dir)); | 
|  | return extension; | 
|  | } | 
|  |  | 
|  | const Extension* AddExtensionWithWebViewAndOpen() { | 
|  | static constexpr char kManifest[] = | 
|  | R"({ | 
|  | "name": "Web View", | 
|  | "manifest_version": 2, | 
|  | "version": "0.1", | 
|  | "app": { | 
|  | "background": { "scripts": ["background.js"] } | 
|  | }, | 
|  | "webview": { | 
|  | "partitions": [{ | 
|  | "name": "foo", | 
|  | "accessible_resources": ["accessible.html"] | 
|  | }] | 
|  | }, | 
|  | "permissions": ["webview"] | 
|  | })"; | 
|  | static constexpr char kBackgroundJs[] = | 
|  | R"(chrome.app.runtime.onLaunched.addListener(() => { | 
|  | chrome.app.window.create('embedder.html', {}, function () {}); | 
|  | });)"; | 
|  | static constexpr char kEmbedderHtml[] = | 
|  | R"(<html> | 
|  | <body> | 
|  | <webview partition="foo"></webview> | 
|  | <script src="embedder.js"></script> | 
|  | </body> | 
|  | </html>)"; | 
|  | static constexpr char kEmbedderJs[] = | 
|  | R"(onload = () => { | 
|  | let webview = document.querySelector('webview'); | 
|  | webview.addEventListener('loadstop', () => { | 
|  | chrome.test.sendMessage('webview loaded'); | 
|  | }); | 
|  | webview.addEventListener('loadabort', (e) => { | 
|  | console.error('Webview aborted load: ' + e.toString()); | 
|  | }); | 
|  | webview.src = 'accessible.html'; | 
|  | };)"; | 
|  | auto extension_dir = std::make_unique<TestExtensionDir>(); | 
|  | extension_dir->WriteManifest(kManifest); | 
|  | extension_dir->WriteFile(FILE_PATH_LITERAL("background.js"), kBackgroundJs); | 
|  | extension_dir->WriteFile(FILE_PATH_LITERAL("embedder.html"), kEmbedderHtml); | 
|  | extension_dir->WriteFile(FILE_PATH_LITERAL("embedder.js"), kEmbedderJs); | 
|  | extension_dir->WriteFile(FILE_PATH_LITERAL("accessible.html"), "hello"); | 
|  |  | 
|  | ExtensionTestMessageListener webview_listener("webview loaded"); | 
|  | const Extension* extension = LoadAndLaunchApp(extension_dir->UnpackedPath(), | 
|  | /*uses_guest_view=*/true); | 
|  | extension_dirs_.push_back(std::move(extension_dir)); | 
|  | EXPECT_TRUE(webview_listener.WaitUntilSatisfied()); | 
|  |  | 
|  | return extension; | 
|  | } | 
|  |  | 
|  | content::WebContents* GetAppWindowContents() { | 
|  | AppWindowRegistry* registry = AppWindowRegistry::Get(profile()); | 
|  | if (registry->app_windows().size() != 1) { | 
|  | ADD_FAILURE() << "Incorrect number of app windows: " | 
|  | << registry->app_windows().size(); | 
|  | return nullptr; | 
|  | } | 
|  |  | 
|  | return (*registry->app_windows().begin())->web_contents(); | 
|  | } | 
|  |  | 
|  | content::WebContents* GetWebViewFromEmbedder(content::WebContents* embedder) { | 
|  | std::vector<content::WebContents*> inner_web_contents = | 
|  | embedder->GetInnerWebContents(); | 
|  | if (inner_web_contents.size() != 1) { | 
|  | ADD_FAILURE() << "Unexpected number of inner web contents: " | 
|  | << inner_web_contents.size(); | 
|  | return nullptr; | 
|  | } | 
|  |  | 
|  | content::WebContents* inner_contents = inner_web_contents[0]; | 
|  | if (!WebViewGuest::FromWebContents(inner_contents)) { | 
|  | return nullptr; | 
|  | } | 
|  |  | 
|  | return inner_contents; | 
|  | } | 
|  |  | 
|  | // Opens a new tab to the given `domain`. | 
|  | void OpenDomain(std::string_view domain) { | 
|  | ASSERT_TRUE( | 
|  | NavigateToURL(GetActiveWebContents(), | 
|  | embedded_test_server()->GetURL(domain, "/simple.html"))); | 
|  | } | 
|  |  | 
|  | // Opens a new tab to a Web UI page. | 
|  | void OpenWebUi() { | 
|  | ASSERT_TRUE( | 
|  | NavigateToURL(GetActiveWebContents(), GURL("chrome://settings"))); | 
|  | } | 
|  |  | 
|  | // Opens a new tab to a page in the given `extension`. | 
|  | void OpenExtensionPage(const Extension& extension) { | 
|  | ASSERT_TRUE(NavigateToURL(GetActiveWebContents(), | 
|  | extension.GetResourceURL("manifest.json"))); | 
|  | } | 
|  |  | 
|  | // Opens a new tab to the given `domain` and waits for a content script to | 
|  | // inject. | 
|  | void OpenDomainAndWaitForContentScript(std::string_view domain) { | 
|  | ExtensionTestMessageListener listener("script injected"); | 
|  | OpenDomain(domain); | 
|  | ASSERT_TRUE(listener.WaitUntilSatisfied()); | 
|  | } | 
|  |  | 
|  | // Opens a new tab to the page with a sandboxed frame in the given | 
|  | // `extension`. | 
|  | void OpenExtensionPageWithSandboxedFrame(const Extension& extension) { | 
|  | ASSERT_TRUE(NavigateToURL(GetActiveWebContents(), | 
|  | extension.GetResourceURL("parent.html"))); | 
|  | } | 
|  |  | 
|  | // Determines if a given `frame` is sandboxed. Sandboxed frames don't | 
|  | // have access to any special extension APIs, even those that require no | 
|  | // specific permissions (like chrome.tabs). | 
|  | bool ExtensionFrameIsSandboxed(content::RenderFrameHost* frame) { | 
|  | EXPECT_TRUE(frame->GetLastCommittedURL().SchemeIs(kExtensionScheme)); | 
|  |  | 
|  | return FrameHasOriginRestrictedSandboxed(frame) && | 
|  | !FrameHasAccessToExtensionApis(frame); | 
|  | } | 
|  |  | 
|  | bool FrameHasOriginRestrictedSandboxed(content::RenderFrameHost* frame) { | 
|  | return frame->IsSandboxed(network::mojom::WebSandboxFlags::kOrigin); | 
|  | } | 
|  |  | 
|  | bool FrameHasAccessToExtensionApis(content::RenderFrameHost* frame) { | 
|  | // Verify extension api access by actually running a simple api function. | 
|  | static constexpr char api_access_script[] = | 
|  | R"( | 
|  | (async function hasAccessToExtensionAPIs() { | 
|  | try { | 
|  | let tabs = await chrome.tabs.query({}); | 
|  | return tabs && tabs.length && tabs.length != 0; | 
|  | } catch(err) { | 
|  | return false; | 
|  | } | 
|  | })(); | 
|  | )"; | 
|  | // Note: Calling ExtractBool on EvalJsResult below is expected to be safe as | 
|  | // the script above will always return a boolean. But, if called on a | 
|  | // sandboxed frame without 'allow-scripts' it will throw a CHECK. | 
|  | return content::EvalJs(frame, api_access_script).ExtractBool(); | 
|  | } | 
|  |  | 
|  | // Iterates over every context type and checks if it could be hosted given the | 
|  | // pairing of `extension` and `process`, expecting it to be allowed if and | 
|  | // only if the context type is in `allowed_contexts`. `debug_string` is used | 
|  | // in a scoped trace to make test failures more meaningful. | 
|  | void RunCanProcessHostContextTypeChecks( | 
|  | const Extension* extension, | 
|  | const content::RenderProcessHost& process, | 
|  | const std::vector<mojom::ContextType>& allowed_contexts, | 
|  | std::string_view debug_string) { | 
|  | std::vector<mojom::ContextType> all_types = { | 
|  | mojom::ContextType::kUnspecified, | 
|  | mojom::ContextType::kPrivilegedExtension, | 
|  | mojom::ContextType::kUnprivilegedExtension, | 
|  | mojom::ContextType::kContentScript, | 
|  | mojom::ContextType::kWebPage, | 
|  | mojom::ContextType::kPrivilegedWebPage, | 
|  | mojom::ContextType::kWebUi, | 
|  | mojom::ContextType::kUntrustedWebUi, | 
|  | mojom::ContextType::kOffscreenExtension, | 
|  | mojom::ContextType::kUserScript, | 
|  | }; | 
|  |  | 
|  | for (auto context_type : all_types) { | 
|  | SCOPED_TRACE(testing::Message() | 
|  | << "Testing Context Type: " << context_type | 
|  | << ", Extension: " | 
|  | << (extension ? extension->name() : "<no extension>") | 
|  | << ", Debug String: " << debug_string); | 
|  | bool expected_to_be_allowed = | 
|  | base::Contains(allowed_contexts, context_type); | 
|  | EXPECT_EQ(expected_to_be_allowed, | 
|  | process_map()->CanProcessHostContextType(extension, process, | 
|  | context_type)); | 
|  | } | 
|  | } | 
|  |  | 
|  | ProcessMap* process_map() { return ProcessMap::Get(profile()); } | 
|  |  | 
|  | private: | 
|  | // Dirs for our test extensions; these have to stay in-scope for the duration | 
|  | // of the test. | 
|  | std::vector<std::unique_ptr<TestExtensionDir>> extension_dirs_; | 
|  | }; | 
|  |  | 
|  | // Verify that an injected content script can successfully use dynamic imports | 
|  | // when operating in a sandboxed srcdoc iframe. | 
|  | IN_PROC_BROWSER_TEST_F(ProcessMapBrowserTest, | 
|  | ContentScriptDynamicImportsWorkInSandboxedSrcdocFrames) { | 
|  | // Create extension with a content script that relies on dynamic imports. | 
|  | static constexpr char kManifest[] = R"( | 
|  | { | 
|  | "name": "Test Dynamic Import", | 
|  | "manifest_version": 2, | 
|  | "version": "1.0", | 
|  | "web_accessible_resources": [ | 
|  | "content-import.js" | 
|  | ], | 
|  | "content_scripts": [{ | 
|  | "matches": [ "*://*/*" ], | 
|  | "all_frames": true, | 
|  | "js": [ "content-script.js" ], | 
|  | "match_origin_as_fallback": true | 
|  | }] | 
|  | })"; | 
|  |  | 
|  | TestExtensionDir dir; | 
|  | dir.WriteManifest(kManifest); | 
|  | dir.WriteFile(FILE_PATH_LITERAL("content-import.js"), | 
|  | R"(export function main() { | 
|  | document.body.innerHTML = | 
|  | document.body.innerHTML.replace('sandboxed', | 
|  | 'SANDBOXED'); | 
|  | chrome.test.sendMessage('dynamic import success'); | 
|  | })"); | 
|  | dir.WriteFile(FILE_PATH_LITERAL("content-script.js"), R"( | 
|  | const src = chrome.runtime.getURL("content-import.js"); | 
|  | import(src).then((contentImport) => { | 
|  | contentImport.main(); | 
|  | }).catch ((error) => { | 
|  | console.log('Error: import failed: ' + error.message); | 
|  | }); | 
|  | )"); | 
|  | const Extension* extension = LoadExtension(dir.UnpackedPath()); | 
|  | ASSERT_TRUE(extension); | 
|  |  | 
|  | // Load a page and give it a sandboxed-srcdoc frame. | 
|  | ExtensionTestMessageListener listener_mainframe("dynamic import success"); | 
|  | content::WebContents* web_contents = GetActiveWebContents(); | 
|  | ASSERT_TRUE(NavigateToURL( | 
|  | web_contents, embedded_test_server()->GetURL("a.test", "/simple.html"))); | 
|  | ASSERT_TRUE(listener_mainframe.WaitUntilSatisfied()); | 
|  |  | 
|  | content::RenderFrameHost* main_frame = web_contents->GetPrimaryMainFrame(); | 
|  | content::TestNavigationObserver observer(web_contents, 1); | 
|  | ExtensionTestMessageListener listener_subframe("dynamic import success"); | 
|  | EXPECT_TRUE(ExecJs(main_frame, R"( | 
|  | let frame = document.createElement('iframe'); | 
|  | frame.sandbox = ''; | 
|  | frame.srcdoc = '<html><body>sandboxed</body></html>'; | 
|  | document.body.appendChild(frame); | 
|  | )")); | 
|  | observer.Wait(); | 
|  |  | 
|  | // Wait for the injected content script to complete. | 
|  | ASSERT_TRUE(listener_subframe.WaitUntilSatisfied()); | 
|  |  | 
|  | // Verify that the action performed by the dynamically imported code was | 
|  | // successful. | 
|  | content::RenderFrameHost* sandboxed_child_frame = | 
|  | content::ChildFrameAt(main_frame, 0); | 
|  | EXPECT_TRUE(content::EvalJs(sandboxed_child_frame, | 
|  | "document.body.innerHTML == 'SANDBOXED';") | 
|  | .ExtractBool()); | 
|  | } | 
|  |  | 
|  | // Check that when an extension frame is inadvertently loaded as sandboxed | 
|  | // because it inherits sandbox flags from its parent, the extension frame can | 
|  | // still use extension messaging APIs without triggering a renderer kill due | 
|  | // to sandboxed frame checks in ChildProcessSecurityPolicy. | 
|  | IN_PROC_BROWSER_TEST_F(ProcessMapBrowserTest, SandboxedWebPageEmbedsExtension) { | 
|  | GURL sandboxed_url = | 
|  | embedded_test_server()->GetURL("a.test", "/csp-sandbox.html"); | 
|  |  | 
|  | ASSERT_TRUE(NavigateToURL(GetActiveWebContents(), sandboxed_url)); | 
|  | content::WebContents* web_contents = GetActiveWebContents(); | 
|  | content::RenderFrameHost* sandboxed_main_frame = | 
|  | web_contents->GetPrimaryMainFrame(); | 
|  | ASSERT_TRUE(sandboxed_main_frame->IsSandboxed( | 
|  | network::mojom::WebSandboxFlags::kOrigin)); | 
|  |  | 
|  | // Set up an extension with a web-accessible page that sends a message to a | 
|  | // background worker and waits for a response. | 
|  | static constexpr char kManifest[] = R"( | 
|  | { | 
|  | "name": "Foo", | 
|  | "version": "1.0", | 
|  | "web_accessible_resources": [{ | 
|  | "resources": ["foo.html"], | 
|  | "matches": ["*://*/*"] | 
|  | }], | 
|  | "manifest_version": 3, | 
|  | "background": { "service_worker": "worker.js" } | 
|  | })"; | 
|  |  | 
|  | TestExtensionDir dir; | 
|  | dir.WriteManifest(kManifest); | 
|  | dir.WriteFile(FILE_PATH_LITERAL("foo.html"), | 
|  | R"(<script src="foo.js"></script>)"); | 
|  | dir.WriteFile(FILE_PATH_LITERAL("foo.js"), R"( | 
|  | (async function() { | 
|  | const response = await chrome.runtime.sendMessage('ping'); | 
|  | chrome.test.assertEq('pong', response); | 
|  | chrome.test.sendMessage('done'); | 
|  | })(); | 
|  | )"); | 
|  |  | 
|  | dir.WriteFile(FILE_PATH_LITERAL("worker.js"), R"( | 
|  | chrome.runtime.onMessage.addListener((request, sender, sendResponse) => { | 
|  | if (request == 'ping') { | 
|  | sendResponse('pong'); | 
|  | } | 
|  | }); | 
|  | )"); | 
|  |  | 
|  | const Extension* extension = LoadExtension(dir.UnpackedPath()); | 
|  | GURL extension_url = extension->GetResourceURL("foo.html"); | 
|  |  | 
|  | // Insert an extension subframe into the sandboxed main frame and ensure that | 
|  | // the the sendMessage exchange finishes successfully. | 
|  | const char kAddFrameScript[] = | 
|  | R"( | 
|  | let f = document.createElement('iframe'); | 
|  | f.src = $1; | 
|  | document.body.appendChild(f); | 
|  | )"; | 
|  |  | 
|  | ExtensionTestMessageListener listener("done"); | 
|  | content::TestNavigationObserver observer(web_contents, 1); | 
|  | EXPECT_TRUE(ExecJs(sandboxed_main_frame, | 
|  | content::JsReplace(kAddFrameScript, extension_url))); | 
|  | observer.Wait(); | 
|  |  | 
|  | // Double-check that the extension frame was sandboxed but maintained access | 
|  | // to extension APIs. | 
|  | content::RenderFrameHost* sandboxed_extension_frame = | 
|  | content::ChildFrameAt(sandboxed_main_frame, 0); | 
|  | EXPECT_TRUE(sandboxed_extension_frame->IsSandboxed( | 
|  | network::mojom::WebSandboxFlags::kOrigin)); | 
|  | EXPECT_TRUE(FrameHasAccessToExtensionApis(sandboxed_extension_frame)); | 
|  | EXPECT_TRUE(listener.WaitUntilSatisfied()); | 
|  | } | 
|  |  | 
|  | // Tests that extension E1 containing a sandboxed webpage A which then contains | 
|  | // extension E2 in a subframe results in the E2 frame being sandboxed. | 
|  | IN_PROC_BROWSER_TEST_F( | 
|  | ProcessMapBrowserTest, | 
|  | ExtensionFrameContainingSandboxedFrameContainingOtherExtensionFrame) { | 
|  | GURL a_frame_url = | 
|  | embedded_test_server()->GetURL("example.com", "/iframe_blank.html"); | 
|  | // Create extension 1 (E1). | 
|  | static constexpr char kManifestE1[] = | 
|  | R"({ | 
|  | "name": "E1", | 
|  | "manifest_version": 3, | 
|  | "version": "0.1" | 
|  | })"; | 
|  | TestExtensionDir extension_dir1; | 
|  | extension_dir1.WriteManifest(kManifestE1); | 
|  | static constexpr char kPageWithSandboxedFrame[] = | 
|  | R"(<html> | 
|  | <h1>E1</h1> | 
|  | <iframe sandbox="allow-scripts" src="%s"></iframe> | 
|  | </html>)"; | 
|  | extension_dir1.WriteFile( | 
|  | FILE_PATH_LITERAL("main.html"), | 
|  | base::StringPrintf(kPageWithSandboxedFrame, a_frame_url.spec().c_str())); | 
|  | const Extension* extension1 = LoadExtension(extension_dir1.UnpackedPath()); | 
|  |  | 
|  | // Create extension 2 (E2). | 
|  | static constexpr char kManifestE2[] = | 
|  | R"({ | 
|  | "name": "E2", | 
|  | "manifest_version": 3, | 
|  | "version": "0.1", | 
|  | "web_accessible_resources": [ | 
|  | { | 
|  | "resources": [ "main.html" ], | 
|  | "matches": [ "*://*/*" ] | 
|  | } | 
|  | ] | 
|  | })"; | 
|  | TestExtensionDir extension_dir2; | 
|  | extension_dir2.WriteManifest(kManifestE2); | 
|  | extension_dir2.WriteFile(FILE_PATH_LITERAL("main.html"), | 
|  | "<html><h1>E2</h2></html>"); | 
|  | const Extension* extension2 = LoadExtension(extension_dir2.UnpackedPath()); | 
|  |  | 
|  | // Load E1. | 
|  | content::WebContents* web_contents = GetActiveWebContents(); | 
|  | ASSERT_TRUE( | 
|  | NavigateToURL(web_contents, extension1->GetResourceURL("main.html"))); | 
|  | content::RenderFrameHost* main_frame = web_contents->GetPrimaryMainFrame(); | 
|  | content::RenderFrameHost* sandboxed_a_frame = | 
|  | content::ChildFrameAt(main_frame, 0); | 
|  |  | 
|  | // Navigate frame in A's subframe to E2. | 
|  | GURL e2_main_url = extension2->GetResourceURL("main.html"); | 
|  | content::TestNavigationObserver observer(web_contents); | 
|  | static constexpr char kScriptE2Load[] = | 
|  | R"( | 
|  | document.getElementById('test').src = $1; | 
|  | )"; | 
|  | EXPECT_TRUE(content::ExecJs(sandboxed_a_frame, | 
|  | content::JsReplace(kScriptE2Load, e2_main_url))); | 
|  | observer.Wait(); | 
|  |  | 
|  | // Verify that the E2 is sandboxed. | 
|  | content::RenderFrameHost* sandboxed_E2_frame = | 
|  | content::ChildFrameAt(sandboxed_a_frame, 0); | 
|  | ASSERT_NE(nullptr, sandboxed_E2_frame); | 
|  | // The E2 frame has an origin-restricted sandbox flag. | 
|  | EXPECT_TRUE(FrameHasOriginRestrictedSandboxed(sandboxed_E2_frame)); | 
|  | EXPECT_TRUE(sandboxed_E2_frame->GetLastCommittedOrigin().opaque()); | 
|  | EXPECT_TRUE(content::EvalJs(sandboxed_E2_frame, "window.origin == 'null';") | 
|  | .ExtractBool()); | 
|  | // The E2 frame has access to extension APIs. | 
|  | EXPECT_TRUE(process_map()->Contains( | 
|  | sandboxed_E2_frame->GetProcess()->GetDeprecatedID())); | 
|  | EXPECT_TRUE(FrameHasAccessToExtensionApis(sandboxed_E2_frame)); | 
|  | // The E2 frame is sandboxed by virtue of being loaded in an iframe with | 
|  | // a sandbox attribute set, but it is not a manifest-sandboxed frame. As such, | 
|  | // it gets placed in the main extension process, has access to extension APIs | 
|  | // and is not places in a sandboxed SiteInstance. | 
|  | EXPECT_FALSE(content::HasSandboxedSiteInstance(sandboxed_E2_frame)); | 
|  |  | 
|  | // Each frame will be in a separate process due to site isolation. | 
|  | EXPECT_NE(main_frame->GetProcess(), sandboxed_a_frame->GetProcess()); | 
|  | EXPECT_NE(main_frame->GetProcess(), sandboxed_E2_frame->GetProcess()); | 
|  | EXPECT_NE(sandboxed_a_frame->GetProcess(), sandboxed_E2_frame->GetProcess()); | 
|  | } | 
|  |  | 
|  | // Tests that web pages are not considered privileged extension processes. | 
|  | IN_PROC_BROWSER_TEST_F(ProcessMapBrowserTest, | 
|  | IsPrivilegedExtensionProcess_WebPages) { | 
|  | // For fun, make sure an extension with access to the given web page is | 
|  | // loaded (just to validate we're not doing anything related to | 
|  | // extension permissions in our calculations). | 
|  | const Extension* extension = | 
|  | AddExtensionWithHostPermission("test", "*://example.com/*"); | 
|  | ASSERT_TRUE(extension); | 
|  |  | 
|  | OpenDomain("example.com"); | 
|  |  | 
|  | EXPECT_FALSE(process_map()->IsPrivilegedExtensionProcess( | 
|  | *extension, GetActiveMainFrameProcessID())); | 
|  | } | 
|  |  | 
|  | // Tests the type of contexts that can be hosted in web page processes. | 
|  | IN_PROC_BROWSER_TEST_F(ProcessMapBrowserTest, CanHostContextType_WebPages) { | 
|  | // For fun, make sure an extension with access to the given web page is | 
|  | // loaded (just to validate we're not doing anything related to | 
|  | // extension permissions in our calculations). | 
|  | const Extension* extension = | 
|  | AddExtensionWithHostPermission("test", "*://example.com/*"); | 
|  | ASSERT_TRUE(extension); | 
|  |  | 
|  | OpenDomain("example.com"); | 
|  | content::RenderProcessHost& web_page_process = GetActiveMainFrameProcess(); | 
|  |  | 
|  | RunCanProcessHostContextTypeChecks(extension, web_page_process, | 
|  | {mojom::ContextType::kContentScript}, | 
|  | "web page with extension passed"); | 
|  | RunCanProcessHostContextTypeChecks( | 
|  | nullptr, web_page_process, | 
|  | {mojom::ContextType::kWebPage, mojom::ContextType::kUntrustedWebUi}, | 
|  | "web page without extension passed"); | 
|  | } | 
|  |  | 
|  | // Tests that web ui pages are not considered privileged extension processes. | 
|  | IN_PROC_BROWSER_TEST_F(ProcessMapBrowserTest, | 
|  | IsPrivilegedExtensionProcess_WebUiPages) { | 
|  | const Extension* extension = | 
|  | AddExtensionWithHostPermission("test", "*://example.com/*"); | 
|  | ASSERT_TRUE(extension); | 
|  |  | 
|  | OpenWebUi(); | 
|  |  | 
|  | EXPECT_FALSE(process_map()->IsPrivilegedExtensionProcess( | 
|  | *extension, GetActiveMainFrameProcessID())); | 
|  | } | 
|  |  | 
|  | // Tests the type of processes that can be hosted in web ui processes. | 
|  | IN_PROC_BROWSER_TEST_F(ProcessMapBrowserTest, CanHostContextType_WebUiPages) { | 
|  | const Extension* extension = | 
|  | AddExtensionWithHostPermission("test", "*://example.com/*"); | 
|  | ASSERT_TRUE(extension); | 
|  |  | 
|  | OpenWebUi(); | 
|  | content::RenderProcessHost& webui_process = GetActiveMainFrameProcess(); | 
|  |  | 
|  | RunCanProcessHostContextTypeChecks(extension, webui_process, | 
|  | {mojom::ContextType::kContentScript}, | 
|  | "webui page with extension passed"); | 
|  | RunCanProcessHostContextTypeChecks(nullptr, webui_process, | 
|  | {mojom::ContextType::kWebUi}, | 
|  | "webui page without extension passed"); | 
|  | } | 
|  |  | 
|  | // Tests that normal extension pages are considered privileged extension | 
|  | // processes. | 
|  | IN_PROC_BROWSER_TEST_F(ProcessMapBrowserTest, | 
|  | IsPrivilegedExtensionProcess_ExtensionPages) { | 
|  | // Load up two extensions, each with the same permissions. | 
|  | const Extension* extension1 = | 
|  | AddExtensionWithHostPermission("test1", "*://example.com/*"); | 
|  | const Extension* extension2 = | 
|  | AddExtensionWithHostPermission("test2", "*://example.com/*"); | 
|  | ASSERT_TRUE(extension1); | 
|  | ASSERT_TRUE(extension2); | 
|  |  | 
|  | // Navigate to a page within the first extension. It should be a privileged | 
|  | // page for that extension, but not the other. | 
|  | OpenExtensionPage(*extension1); | 
|  | EXPECT_TRUE(process_map()->IsPrivilegedExtensionProcess( | 
|  | *extension1, GetActiveMainFrameProcessID())); | 
|  | EXPECT_FALSE(process_map()->IsPrivilegedExtensionProcess( | 
|  | *extension2, GetActiveMainFrameProcessID())); | 
|  |  | 
|  | // Inversion: Navigate to the page of the second extension. It should be a | 
|  | // privileged page in the second, but not the first. | 
|  | OpenExtensionPage(*extension2); | 
|  | EXPECT_FALSE(process_map()->IsPrivilegedExtensionProcess( | 
|  | *extension1, GetActiveMainFrameProcessID())); | 
|  | EXPECT_TRUE(process_map()->IsPrivilegedExtensionProcess( | 
|  | *extension2, GetActiveMainFrameProcessID())); | 
|  | } | 
|  |  | 
|  | // Tests the type of contexts that can be hosted in regular extension processes. | 
|  | IN_PROC_BROWSER_TEST_F(ProcessMapBrowserTest, | 
|  | CanHostContextType_ExtensionPages) { | 
|  | // Load up two extensions, each with the same permissions. | 
|  | const Extension* extension1 = | 
|  | AddExtensionWithHostPermission("test1", "*://example.com/*"); | 
|  | const Extension* extension2 = | 
|  | AddExtensionWithHostPermission("test2", "*://example.com/*"); | 
|  | ASSERT_TRUE(extension1); | 
|  | ASSERT_TRUE(extension2); | 
|  |  | 
|  | // Navigate to a page within the first extension. It should be a privileged | 
|  | // page for that extension, but not the other. | 
|  | OpenExtensionPage(*extension1); | 
|  |  | 
|  | content::RenderProcessHost& extension1_process = GetActiveMainFrameProcess(); | 
|  |  | 
|  | RunCanProcessHostContextTypeChecks(extension1, extension1_process, | 
|  | {mojom::ContextType::kContentScript, | 
|  | mojom::ContextType::kPrivilegedExtension, | 
|  | mojom::ContextType::kOffscreenExtension}, | 
|  | "extension1 page with extension1 passed"); | 
|  | RunCanProcessHostContextTypeChecks(extension2, extension1_process, | 
|  | {mojom::ContextType::kContentScript}, | 
|  | "extension1 page with extension2 passed"); | 
|  | RunCanProcessHostContextTypeChecks( | 
|  | nullptr, extension1_process, {}, | 
|  | "extension1 page without extension passed"); | 
|  |  | 
|  | // Inversion: Navigate to the page of the second extension. It should be a | 
|  | // privileged page in the second, but not the first. | 
|  | OpenExtensionPage(*extension2); | 
|  |  | 
|  | content::RenderProcessHost& extension2_process = GetActiveMainFrameProcess(); | 
|  |  | 
|  | RunCanProcessHostContextTypeChecks(extension2, extension2_process, | 
|  | {mojom::ContextType::kContentScript, | 
|  | mojom::ContextType::kPrivilegedExtension, | 
|  | mojom::ContextType::kOffscreenExtension}, | 
|  | "extension2 page with extension2 passed"); | 
|  | RunCanProcessHostContextTypeChecks(extension1, extension2_process, | 
|  | {mojom::ContextType::kContentScript}, | 
|  | "extension2 page with extension1 passed"); | 
|  | RunCanProcessHostContextTypeChecks( | 
|  | nullptr, extension2_process, {}, | 
|  | "extension2 page without extension passed"); | 
|  | } | 
|  |  | 
|  | // Tests that a web page with injected content scripts is not considered a | 
|  | // privileged extension process. | 
|  | IN_PROC_BROWSER_TEST_F( | 
|  | ProcessMapBrowserTest, | 
|  | IsPrivilegedExtensionProcess_WebPagesWithContentScripts) { | 
|  | const Extension* extension = | 
|  | AddExtensionWithContentScript("test", "*://example.com/*"); | 
|  | ASSERT_TRUE(extension); | 
|  |  | 
|  | // Navigate to a web page and wait for the content script to inject. | 
|  | OpenDomainAndWaitForContentScript("example.com"); | 
|  |  | 
|  | EXPECT_FALSE(process_map()->IsPrivilegedExtensionProcess( | 
|  | *extension, GetActiveMainFrameProcessID())); | 
|  | } | 
|  |  | 
|  | // Tests the type of contexts that can be hosted in a web page process that has | 
|  | // had a content script injected in it. | 
|  | IN_PROC_BROWSER_TEST_F(ProcessMapBrowserTest, | 
|  | CanHostContextType_WebPagesWithContentScripts) { | 
|  | const Extension* extension = | 
|  | AddExtensionWithContentScript("test", "*://example.com/*"); | 
|  | ASSERT_TRUE(extension); | 
|  |  | 
|  | // Navigate to a web page and wait for the content script to inject. | 
|  | OpenDomainAndWaitForContentScript("example.com"); | 
|  | content::RenderProcessHost& page_process = GetActiveMainFrameProcess(); | 
|  |  | 
|  | RunCanProcessHostContextTypeChecks(extension, page_process, | 
|  | {mojom::ContextType::kContentScript}, | 
|  | "web page with extension passed"); | 
|  | RunCanProcessHostContextTypeChecks( | 
|  | nullptr, page_process, | 
|  | {mojom::ContextType::kWebPage, mojom::ContextType::kUntrustedWebUi}, | 
|  | "web page without extension passed"); | 
|  | } | 
|  |  | 
|  | // The following defines a common test body used by the | 
|  | // Sandboxed*Are*Isolated tests that follow. `frame_url` defines the page to | 
|  | // be loaded, and may be an regular (http/s) page, a data url, or an | 
|  | // about:srcdoc url. If it's about:srcdoc, the iframe srcdoc attribute will be | 
|  | // used, and set to the value of `content`. `expect_isolated_from_each_other` | 
|  | // indicates whether the subframes are expected to be isolated from each other, | 
|  | // and if the sandboxed frame should have a sandboxed SiteInstance. | 
|  | // `expect_sandboxed_subframe_isolated_from_extension_page` indicates we | 
|  | // expect the sandboxed frame to be isolated from the extension mainframe, | 
|  | // and `expect_non_sandboxed_subframe_isolated_from_extension_page` indicates | 
|  | // that we expect the non-sandboxed subframe to be process isolated from the | 
|  | // extension mainframe. This function is defined here to keep it close to the | 
|  | // tests that use it, for easier reference. | 
|  | void ProcessMapBrowserTest::VerifyWhetherSubframesAreIsolated( | 
|  | const GURL& frame_url, | 
|  | const std::string& content, | 
|  | bool expect_subframes_isolated_from_each_other, | 
|  | bool expect_sandboxed_subframe_isolated_from_extension_page, | 
|  | bool expect_non_sandboxed_subframe_isolated_from_extension_page) { | 
|  | const Extension* extension = | 
|  | AddExtensionWithSandboxedWebpage(frame_url, content); | 
|  | ASSERT_TRUE(extension); | 
|  |  | 
|  | content::WebContents* web_contents = GetActiveWebContents(); | 
|  | ASSERT_TRUE( | 
|  | NavigateToURL(web_contents, extension->GetResourceURL("parent.html"))); | 
|  |  | 
|  | content::RenderFrameHost* main_frame = web_contents->GetPrimaryMainFrame(); | 
|  | content::RenderFrameHost* sandboxed_child_frame = | 
|  | content::ChildFrameAt(main_frame, 0); | 
|  | content::RenderFrameHost* non_sandboxed_child_frame = | 
|  | content::ChildFrameAt(main_frame, 1); | 
|  |  | 
|  | EXPECT_FALSE(ExtensionFrameIsSandboxed(main_frame)); | 
|  |  | 
|  | int main_frame_process_id = main_frame->GetProcess()->GetDeprecatedID(); | 
|  | int sandboxed_frame_process_id = | 
|  | sandboxed_child_frame->GetProcess()->GetDeprecatedID(); | 
|  | int non_sandboxed_frame_process_id = | 
|  | non_sandboxed_child_frame->GetProcess()->GetDeprecatedID(); | 
|  |  | 
|  | if (expect_subframes_isolated_from_each_other) { | 
|  | EXPECT_NE(sandboxed_frame_process_id, non_sandboxed_frame_process_id); | 
|  | EXPECT_TRUE(content::HasSandboxedSiteInstance(sandboxed_child_frame)); | 
|  | } else { | 
|  | EXPECT_EQ(sandboxed_frame_process_id, non_sandboxed_frame_process_id); | 
|  | EXPECT_FALSE(content::HasSandboxedSiteInstance(sandboxed_child_frame)); | 
|  | } | 
|  | if (expect_sandboxed_subframe_isolated_from_extension_page) { | 
|  | EXPECT_NE(main_frame_process_id, sandboxed_frame_process_id); | 
|  | } else { | 
|  | EXPECT_EQ(main_frame_process_id, sandboxed_frame_process_id); | 
|  | } | 
|  | if (expect_non_sandboxed_subframe_isolated_from_extension_page) { | 
|  | EXPECT_NE(main_frame_process_id, non_sandboxed_frame_process_id); | 
|  | } else { | 
|  | EXPECT_EQ(main_frame_process_id, non_sandboxed_frame_process_id); | 
|  | } | 
|  | EXPECT_FALSE(ExtensionFrameIsSandboxed(main_frame)); | 
|  | EXPECT_FALSE(content::HasSandboxedSiteInstance(non_sandboxed_child_frame)); | 
|  | } | 
|  |  | 
|  | // Tests that web pages loaded in sandboxed iframes inside an extension are | 
|  | // isolated from the extension and from non-sandboxed iframes of the same web | 
|  | // origin, if IsolateSandboxedIframes is enabled. There are three variations, | 
|  | // one for a web url, one for a data: url, and one for about:srcdoc. | 
|  | IN_PROC_BROWSER_TEST_F(ProcessMapBrowserTest, | 
|  | SandboxedNonExtensionWebPagesAreIsolated) { | 
|  | GURL frame_url = | 
|  | embedded_test_server()->GetURL("example.com", "/simple.html"); | 
|  | bool expect_subframes_isolated_from_each_other = | 
|  | content::SiteIsolationPolicy::AreIsolatedSandboxedIframesEnabled(); | 
|  | // The subframes should be cross-process to each other, and the sandboxed | 
|  | // frame should be in a sandboxed SiteInstance. Web-based content inside an | 
|  | // extension is always cross-process to the extension frame that contains it. | 
|  | VerifyWhetherSubframesAreIsolated( | 
|  | frame_url, /*content=*/std::string(), | 
|  | expect_subframes_isolated_from_each_other, | 
|  | /*expect_sandboxed_subframe_isolated_from_extension_page=*/true, | 
|  | /*expect_non_sandboxed_subframe_isolated_from_extension_page=*/true); | 
|  | } | 
|  |  | 
|  | IN_PROC_BROWSER_TEST_F(ProcessMapBrowserTest, | 
|  | SandboxedDataFramesAreMaybeIsolated) { | 
|  | GURL frame_url("data:text/html, foo"); | 
|  | // Srcdoc/data-url content inside a sandboxed frame in an extension is | 
|  | // same-process to the extension frame that contains it, unless | 
|  | // IsolateSandboxedIframes is enabled, in which case it is cross-process. | 
|  | bool expect_subframes_isolated_from_each_other = | 
|  | content::SiteIsolationPolicy::AreIsolatedSandboxedIframesEnabled(); | 
|  | bool expect_sandboxed_subframe_isolated_from_extension_page = | 
|  | content::SiteIsolationPolicy::AreIsolatedSandboxedIframesEnabled(); | 
|  | VerifyWhetherSubframesAreIsolated( | 
|  | frame_url, /*content=*/std::string(), | 
|  | expect_subframes_isolated_from_each_other, | 
|  | expect_sandboxed_subframe_isolated_from_extension_page, | 
|  | /*expect_non_sandboxed_subframe_isolated_from_extension_page=*/false); | 
|  | } | 
|  |  | 
|  | IN_PROC_BROWSER_TEST_F(ProcessMapBrowserTest, | 
|  | SandboxedSrcdocFramesAreMaybeIsolated) { | 
|  | GURL frame_url("about:srcdoc"); | 
|  | // Srcdoc/data-url content inside a sandboxed frame in an extension is | 
|  | // same-process to the extension frame that contains it, unless | 
|  | // IsolateSandboxedIframes is enabled, in which case it is cross-process. | 
|  | bool expect_subframes_isolated_from_each_other = | 
|  | content::SiteIsolationPolicy::AreIsolatedSandboxedIframesEnabled(); | 
|  | bool expect_sandboxed_subframe_isolated_from_extension_page = | 
|  | content::SiteIsolationPolicy::AreIsolatedSandboxedIframesEnabled(); | 
|  | VerifyWhetherSubframesAreIsolated( | 
|  | frame_url, /*content=*/std::string("foo"), | 
|  | expect_subframes_isolated_from_each_other, | 
|  | expect_sandboxed_subframe_isolated_from_extension_page, | 
|  | /*expect_non_sandboxed_subframe_isolated_from_extension_page=*/false); | 
|  | } | 
|  |  | 
|  | // Function implementation defined here to be close to the tests that use it. | 
|  | void ProcessMapBrowserTest:: | 
|  | VerifySandboxedSubframeHasResourceAccessButMaybeApiAccess( | 
|  | const Extension* extension, | 
|  | std::string_view parent_script, | 
|  | const bool is_subframe_data_url, | 
|  | const bool expects_api_access) { | 
|  | content::WebContents* web_contents = GetActiveWebContents(); | 
|  | ASSERT_TRUE( | 
|  | NavigateToURL(web_contents, extension->GetResourceURL("parent.html"))); | 
|  |  | 
|  | content::RenderFrameHost* main_frame = web_contents->GetPrimaryMainFrame(); | 
|  | // Use JS to add content to the child frame. | 
|  | content::TestNavigationObserver observer(web_contents); | 
|  | EXPECT_TRUE(content::ExecJs(main_frame, parent_script)); | 
|  | observer.Wait(); | 
|  |  | 
|  | content::RenderFrameHost* sandboxed_child_frame = | 
|  | content::ChildFrameAt(main_frame, 0); | 
|  | int sandboxed_frame_process_id = | 
|  | sandboxed_child_frame->GetProcess()->GetDeprecatedID(); | 
|  | // Sandboxed extension frames should still have access to other extension | 
|  | // resources. Verify the extension script (resource.js) was properly loaded | 
|  | // by looking for foo variable. | 
|  | EXPECT_EQ("bar", | 
|  | content::EvalJs(sandboxed_child_frame, "foo;").ExtractString()); | 
|  | // Sandboxed data and about:srcdoc frames, as well as manifest-sandboxed | 
|  | // extension pages, do not expect API access. As such, they are placed in | 
|  | // a non-privileged process. Extension pages that are sandboxed, but not | 
|  | // listed as sandboxed in the manifest, do get API access and are placed in an | 
|  | // privileged extension process. | 
|  | EXPECT_EQ(expects_api_access, process_map()->IsPrivilegedExtensionProcess( | 
|  | *extension, sandboxed_frame_process_id)); | 
|  |  | 
|  | // Verify expected api access. | 
|  | EXPECT_EQ(expects_api_access, | 
|  | FrameHasAccessToExtensionApis(sandboxed_child_frame)); | 
|  | } | 
|  |  | 
|  | // Tests that a data: url in a sandboxed frame in an extension still has access | 
|  | // to resources. | 
|  | IN_PROC_BROWSER_TEST_F(ProcessMapBrowserTest, | 
|  | SandboxedDataUrlStillHasAccessToExtensionResources) { | 
|  | const Extension* extension = AddExtensionWithResource(); | 
|  | ASSERT_TRUE(extension); | 
|  |  | 
|  | // The %s in the string below will be filled in with the extension's origin by | 
|  | // VerifySandboxedSubframeHasResourceAccessButMaybeApiAccess. | 
|  | std::string parent_script = base::StrCat({ | 
|  | R"(let test_frame = document.getElementById('test_frame'); | 
|  | test_frame.src = 'data:text/html, <script src=")", | 
|  | extension->origin().GetURL().spec(), R"(resource.js"></script>';)"}); | 
|  | VerifySandboxedSubframeHasResourceAccessButMaybeApiAccess( | 
|  | extension, parent_script, /*is_subframe_data_url=*/true, | 
|  | /*expects_api_access=*/false); | 
|  | } | 
|  |  | 
|  | // Tests that a srcdoc in a sandboxed frame in an extension still has access to | 
|  | // resources. | 
|  | IN_PROC_BROWSER_TEST_F(ProcessMapBrowserTest, | 
|  | SandboxedSrcdocStillHasAccessToExtensionResources) { | 
|  | const Extension* extension = AddExtensionWithResource(); | 
|  | ASSERT_TRUE(extension); | 
|  |  | 
|  | // The %s in the string below will be filled in with the extension's origin by | 
|  | // VerifySandboxedSubframeHasResourceAccessButMaybeApiAccess. | 
|  | std::string parent_script = base::StrCat({ | 
|  | R"(let test_frame = document.getElementById('test_frame'); | 
|  | test_frame.srcdoc = '<script src=")", | 
|  | extension->origin().GetURL().spec(), R"(resource.js"></script>';)"}); | 
|  | VerifySandboxedSubframeHasResourceAccessButMaybeApiAccess( | 
|  | extension, parent_script, /*is_subframe_data_url=*/false, | 
|  | /*expects_api_access=*/false); | 
|  | } | 
|  |  | 
|  | // Tests that an extension page in a sandboxed frame in an extension still has | 
|  | // access to resources. | 
|  | IN_PROC_BROWSER_TEST_F( | 
|  | ProcessMapBrowserTest, | 
|  | SandboxedExtensionPageStillHasAccessToExtensionResources) { | 
|  | const Extension* extension = AddExtensionWithResource(); | 
|  | ASSERT_TRUE(extension); | 
|  |  | 
|  | VerifySandboxedSubframeHasResourceAccessButMaybeApiAccess( | 
|  | extension, R"(let test_frame = document.getElementById('test_frame'); | 
|  | test_frame.src = 'page_requesting_resource.html';)", | 
|  | /*is_subframe_data_url=*/false, | 
|  | /*expects_api_access=*/true); | 
|  | } | 
|  |  | 
|  | // Tests that an extension inside a sandboxed subframe of another extension | 
|  | // still has privileges. It will be process isolated regardless of the sandbox | 
|  | // attribute since extensions are isolated from one another. | 
|  | IN_PROC_BROWSER_TEST_F(ProcessMapBrowserTest, | 
|  | SandboxedSubframeExtensionHasPrivilege) { | 
|  | std::pair<const Extension*, const Extension*> nested_extensions = | 
|  | AddNestedExtensions(); | 
|  | const Extension* extension1 = nested_extensions.first; | 
|  | const Extension* extension2 = nested_extensions.second; | 
|  | ASSERT_TRUE(extension1); | 
|  | ASSERT_TRUE(extension2); | 
|  |  | 
|  | content::WebContents* web_contents = GetActiveWebContents(); | 
|  | ASSERT_TRUE( | 
|  | NavigateToURL(web_contents, extension2->GetResourceURL("parent.html"))); | 
|  |  | 
|  | content::RenderFrameHost* main_frame = web_contents->GetPrimaryMainFrame(); | 
|  | content::RenderFrameHost* sandboxed_child_frame = | 
|  | content::ChildFrameAt(main_frame, 0); | 
|  |  | 
|  | int main_frame_process_id = main_frame->GetProcess()->GetDeprecatedID(); | 
|  | int sandboxed_frame_process_id = | 
|  | sandboxed_child_frame->GetProcess()->GetDeprecatedID(); | 
|  |  | 
|  | // Since we normally process-isolate E1 from E2, placing E1 in a sandboxed | 
|  | // iframe will make no difference. | 
|  | EXPECT_NE(main_frame_process_id, sandboxed_frame_process_id); | 
|  | EXPECT_TRUE(process_map()->IsPrivilegedExtensionProcess( | 
|  | *extension2, main_frame_process_id)); | 
|  | EXPECT_TRUE(process_map()->IsPrivilegedExtensionProcess( | 
|  | *extension1, sandboxed_frame_process_id)); | 
|  | // From an extensions point of view, applying 'sandbox' to the child iframe | 
|  | // in the manifest prevents it from having access to extension APIs, and | 
|  | // also places it in a non-privileged process if IsolateSandboxedFrames is | 
|  | // enabled. | 
|  | EXPECT_FALSE(ExtensionFrameIsSandboxed(main_frame)); | 
|  | EXPECT_FALSE(ExtensionFrameIsSandboxed(sandboxed_child_frame)); | 
|  |  | 
|  | // Attempt to have `extension1` (in `sandboxed_child_frame`) load a | 
|  | // non-web-accessible resource from `extension2`. This should fail. The fact | 
|  | // that the child is sandboxed doesn't matter. | 
|  | GURL e2_private_page_url = extension2->GetResourceURL("private_page.html"); | 
|  | const char kJsScript[] = | 
|  | R"( | 
|  | frm = document.createElement('iframe'); | 
|  | frm.src = $1; | 
|  | document.body.appendChild(frm); | 
|  | )"; | 
|  | content::TestNavigationObserver observer(GetActiveWebContents(), 1); | 
|  | EXPECT_TRUE(ExecJs(sandboxed_child_frame, | 
|  | content::JsReplace(kJsScript, e2_private_page_url))); | 
|  | observer.Wait(); | 
|  |  | 
|  | EXPECT_FALSE(observer.last_navigation_succeeded()); | 
|  | EXPECT_EQ(net::ERR_BLOCKED_BY_CLIENT, observer.last_net_error_code()); | 
|  | content::RenderFrameHost* grand_child_frame = | 
|  | content::ChildFrameAt(sandboxed_child_frame, 0); | 
|  | EXPECT_NE(nullptr, grand_child_frame); | 
|  | EXPECT_EQ(e2_private_page_url, grand_child_frame->GetLastCommittedURL()); | 
|  | } | 
|  |  | 
|  | // At present, the default mode is IsolatedSandboxedIframes mode (which isolates | 
|  | // manifest-sandboxed extension pages in a different process that is not | 
|  | // privileged). If there are multiple manifest-sandboxed extension pages, | 
|  | // they will share a SiteInstance and non-privileged process. This test verifies | 
|  | // that all manifest-sandboxed frames load into the same (non-privileged) | 
|  | // process. | 
|  | IN_PROC_BROWSER_TEST_F(ProcessMapBrowserTest, | 
|  | IsPrivilegedExtensionProcess_SandboxedExtensionFrame) { | 
|  | const Extension* extension = AddExtensionWithSandboxedFrame(); | 
|  | ASSERT_TRUE(extension); | 
|  |  | 
|  | OpenExtensionPageWithSandboxedFrame(*extension); | 
|  |  | 
|  | content::WebContents* web_contents = GetActiveWebContents(); | 
|  | content::RenderFrameHost* main_frame = web_contents->GetPrimaryMainFrame(); | 
|  | content::RenderFrameHost* sandboxed_frame = | 
|  | content::ChildFrameAt(main_frame, 0); | 
|  | content::RenderFrameHost* other_sandboxed_frame = | 
|  | content::ChildFrameAt(main_frame, 1); | 
|  |  | 
|  | EXPECT_FALSE(ExtensionFrameIsSandboxed(main_frame)); | 
|  | EXPECT_TRUE(ExtensionFrameIsSandboxed(sandboxed_frame)); | 
|  | EXPECT_TRUE(ExtensionFrameIsSandboxed(other_sandboxed_frame)); | 
|  |  | 
|  | int main_frame_process_id = main_frame->GetProcess()->GetDeprecatedID(); | 
|  | int sandboxed_frame_process_id = | 
|  | sandboxed_frame->GetProcess()->GetDeprecatedID(); | 
|  | int other_sandboxed_frame_process_id = | 
|  | other_sandboxed_frame->GetProcess()->GetDeprecatedID(); | 
|  |  | 
|  | // The two manifest-sandboxed frames will be in the same process, regardless | 
|  | // of whether IsolateSandboxedIframes is enabled or not. | 
|  | EXPECT_EQ(other_sandboxed_frame_process_id, sandboxed_frame_process_id); | 
|  | if (content::SiteIsolationPolicy::AreIsolatedSandboxedIframesEnabled()) { | 
|  | EXPECT_NE(main_frame_process_id, sandboxed_frame_process_id); | 
|  | EXPECT_FALSE(process_map()->IsPrivilegedExtensionProcess( | 
|  | *extension, sandboxed_frame_process_id)); | 
|  | } else { | 
|  | EXPECT_EQ(main_frame_process_id, sandboxed_frame_process_id); | 
|  | EXPECT_TRUE(process_map()->IsPrivilegedExtensionProcess( | 
|  | *extension, sandboxed_frame_process_id)); | 
|  | } | 
|  |  | 
|  | EXPECT_TRUE(process_map()->IsPrivilegedExtensionProcess( | 
|  | *extension, main_frame_process_id)); | 
|  | } | 
|  |  | 
|  | // Test class to run tests both with and without sandboxing. | 
|  | class ProcessMapAboutSrcdocBrowserTest | 
|  | : public ProcessMapBrowserTest, | 
|  | public ::testing::WithParamInterface<bool> { | 
|  | public: | 
|  | ProcessMapAboutSrcdocBrowserTest() = default; | 
|  | }; | 
|  |  | 
|  | // This test verifies that an about:srcdoc frame with a non-extension parent | 
|  | // cannot inherit an extension precursor origin that allows it to incorrectly | 
|  | // access extension resources. The srcdoc frame should also not inherit the | 
|  | // base URI of the extension. | 
|  | IN_PROC_BROWSER_TEST_P(ProcessMapAboutSrcdocBrowserTest, | 
|  | ExtensionCannotNavigateAboutSrcdocGrandchild) { | 
|  | bool srcdoc_is_sandboxed = GetParam(); | 
|  | const Extension* extension = | 
|  | AddExtensionWithNonExtensionSubframeWithSrcdocSubframe( | 
|  | srcdoc_is_sandboxed); | 
|  | ASSERT_TRUE(extension); | 
|  |  | 
|  | content::WebContents* web_contents = GetActiveWebContents(); | 
|  | ASSERT_TRUE( | 
|  | NavigateToURL(web_contents, extension->GetResourceURL("parent.html"))); | 
|  |  | 
|  | content::RenderFrameHost* extension_frame = | 
|  | web_contents->GetPrimaryMainFrame(); | 
|  | content::RenderFrameHost* non_extension_frame = | 
|  | content::ChildFrameAt(extension_frame, 0); | 
|  | content::RenderFrameHost* srcdoc_frame = | 
|  | content::ChildFrameAt(non_extension_frame, 0); | 
|  |  | 
|  | // Verify that srcdoc frame has baseURI from it's parent. | 
|  | std::string extension_base_uri = | 
|  | EvalJs(extension_frame, "document.baseURI").ExtractString(); | 
|  | std::string non_extension_base_uri = | 
|  | EvalJs(non_extension_frame, "document.baseURI").ExtractString(); | 
|  | std::string srcdoc_base_uri = | 
|  | EvalJs(srcdoc_frame, "document.baseURI").ExtractString(); | 
|  | EXPECT_EQ(non_extension_base_uri, srcdoc_base_uri); | 
|  |  | 
|  | // Attempt to have `extension_frame` navigate the srcdoc frame to | 
|  | // about:srcdoc. | 
|  | content::TestNavigationObserver observer(web_contents, 1); | 
|  | EXPECT_TRUE( | 
|  | ExecJs(extension_frame, "frames[0][0].location.href = 'about:srcdoc';")); | 
|  | observer.Wait(); | 
|  |  | 
|  | // Verify that the srcdoc frame doesn't have the access to the extension's | 
|  | // origin, or any privileges. | 
|  | srcdoc_frame = content::ChildFrameAt(non_extension_frame, 0); | 
|  | std::string new_srcdoc_base_uri = | 
|  | EvalJs(srcdoc_frame, "document.baseURI").ExtractString(); | 
|  | // The srcdoc gets a baseURI for an error page, but at least it's not the | 
|  | // extension's baseURI. | 
|  | EXPECT_NE(extension_base_uri, new_srcdoc_base_uri); | 
|  | EXPECT_FALSE(content::EvalJs(srcdoc_frame, "!!chrome && !!chrome.tabs;") | 
|  | .ExtractBool()); | 
|  |  | 
|  | EXPECT_FALSE( | 
|  | process_map()->Contains(srcdoc_frame->GetProcess()->GetDeprecatedID())); | 
|  |  | 
|  | // Make sure the resulting srcdoc frame cannot fetch() extension resources. | 
|  | // The only way `success` in the JS below can become true is if the fetch() | 
|  | // fails and the error is 'Failed to fetch'. If the fetch() succeeds and | 
|  | // a response is received, the test fails. | 
|  | const char jsTemplate[] = R"( | 
|  | (async () => { | 
|  | success = await fetch($1, { mode: 'no-cors'}) | 
|  | .then(response => { return false; }) | 
|  | .catch(err => { | 
|  | return (err instanceof TypeError) && | 
|  | (err.message == 'Failed to fetch'); | 
|  | }); | 
|  | return success; | 
|  | })(); | 
|  | )"; | 
|  | GURL json_resource_url = extension->GetResourceURL("data.json"); | 
|  | EXPECT_TRUE( | 
|  | EvalJs(srcdoc_frame, content::JsReplace(jsTemplate, json_resource_url)) | 
|  | .ExtractBool()); | 
|  | } | 
|  |  | 
|  | INSTANTIATE_TEST_SUITE_P( | 
|  | All, | 
|  | ProcessMapAboutSrcdocBrowserTest, | 
|  | testing::Values(true, false), | 
|  | [](const testing::TestParamInfo<bool>& info) { | 
|  | bool srcdoc_is_sandboxed = info.param; | 
|  | std::string label = base::StringPrintf( | 
|  | "kBlockCrossOriginInitiatedAboutSrcdocNavigation_%s", | 
|  | srcdoc_is_sandboxed ? "Sandboxed" : "NotSandboxed"); | 
|  | return label; | 
|  | }); | 
|  |  | 
|  | // Tests the type of contexts that can be hosted in extension processes with | 
|  | // a sandboxed process frame. | 
|  | IN_PROC_BROWSER_TEST_F(ProcessMapBrowserTest, | 
|  | CanHostContextType_SandboxedExtensionFrame) { | 
|  | const Extension* extension = AddExtensionWithSandboxedFrame(); | 
|  | ASSERT_TRUE(extension); | 
|  |  | 
|  | OpenExtensionPageWithSandboxedFrame(*extension); | 
|  |  | 
|  | content::WebContents* web_contents = GetActiveWebContents(); | 
|  | content::RenderFrameHost* main_frame = web_contents->GetPrimaryMainFrame(); | 
|  | content::RenderFrameHost* sandboxed_frame = | 
|  | content::ChildFrameAt(main_frame, 0); | 
|  |  | 
|  | EXPECT_FALSE(ExtensionFrameIsSandboxed(main_frame)); | 
|  | EXPECT_TRUE(ExtensionFrameIsSandboxed(sandboxed_frame)); | 
|  |  | 
|  | content::RenderProcessHost& main_frame_process = *main_frame->GetProcess(); | 
|  | content::RenderProcessHost& sandboxed_frame_process = | 
|  | *sandboxed_frame->GetProcess(); | 
|  |  | 
|  | if (content::SiteIsolationPolicy::AreIsolatedSandboxedIframesEnabled()) { | 
|  | EXPECT_NE(main_frame_process.GetDeprecatedID(), | 
|  | sandboxed_frame_process.GetDeprecatedID()); | 
|  | } else { | 
|  | EXPECT_EQ(main_frame_process.GetDeprecatedID(), | 
|  | sandboxed_frame_process.GetDeprecatedID()); | 
|  | } | 
|  |  | 
|  | RunCanProcessHostContextTypeChecks( | 
|  | extension, main_frame_process, | 
|  | {mojom::ContextType::kContentScript, | 
|  | mojom::ContextType::kPrivilegedExtension, | 
|  | mojom::ContextType::kOffscreenExtension}, | 
|  | "main frame process with extension passed"); | 
|  | RunCanProcessHostContextTypeChecks( | 
|  | nullptr, main_frame_process, {}, | 
|  | "main frame process without extension passed"); | 
|  |  | 
|  | if (content::SiteIsolationPolicy::AreIsolatedSandboxedIframesEnabled()) { | 
|  | RunCanProcessHostContextTypeChecks( | 
|  | extension, sandboxed_frame_process, | 
|  | {mojom::ContextType::kContentScript}, | 
|  | "sandboxed frame process with extension passed"); | 
|  | RunCanProcessHostContextTypeChecks( | 
|  | nullptr, sandboxed_frame_process, | 
|  | {mojom::ContextType::kWebPage, mojom::ContextType::kUntrustedWebUi}, | 
|  | "sandboxed frame process without extension passed"); | 
|  | } else { | 
|  | RunCanProcessHostContextTypeChecks( | 
|  | extension, sandboxed_frame_process, | 
|  | {mojom::ContextType::kContentScript, | 
|  | mojom::ContextType::kPrivilegedExtension, | 
|  | mojom::ContextType::kOffscreenExtension}, | 
|  | "sandboxed frame process with extension passed"); | 
|  | RunCanProcessHostContextTypeChecks( | 
|  | nullptr, sandboxed_frame_process, {}, | 
|  | "sandboxed frame process without extension passed"); | 
|  | } | 
|  | } | 
|  |  | 
|  | IN_PROC_BROWSER_TEST_F(ProcessMapBrowserTest, | 
|  | IsPrivilegedExtensionProcess_WebViews) { | 
|  | const Extension* extension = AddExtensionWithWebViewAndOpen(); | 
|  | ASSERT_TRUE(extension); | 
|  |  | 
|  | content::WebContents* embedder = GetAppWindowContents(); | 
|  | ASSERT_TRUE(embedder); | 
|  |  | 
|  | content::WebContents* webview = GetWebViewFromEmbedder(embedder); | 
|  | ASSERT_TRUE(webview); | 
|  |  | 
|  | // The embedder (the app window) should be a privileged extension process, | 
|  | // but the webview should not. | 
|  | EXPECT_TRUE(process_map()->IsPrivilegedExtensionProcess( | 
|  | *extension, | 
|  | embedder->GetPrimaryMainFrame()->GetProcess()->GetDeprecatedID())); | 
|  | EXPECT_FALSE(process_map()->IsPrivilegedExtensionProcess( | 
|  | *extension, | 
|  | webview->GetPrimaryMainFrame()->GetProcess()->GetDeprecatedID())); | 
|  | } | 
|  |  | 
|  | IN_PROC_BROWSER_TEST_F(ProcessMapBrowserTest, CanHostContextType_WebViews) { | 
|  | const Extension* extension = AddExtensionWithWebViewAndOpen(); | 
|  | ASSERT_TRUE(extension); | 
|  |  | 
|  | content::WebContents* embedder = GetAppWindowContents(); | 
|  | ASSERT_TRUE(embedder); | 
|  |  | 
|  | content::WebContents* webview = GetWebViewFromEmbedder(embedder); | 
|  | ASSERT_TRUE(webview); | 
|  |  | 
|  | // The embedder (the app window) can host any kind of extension context | 
|  | // except an unprivileged extension context (which is only available to | 
|  | // webviews). | 
|  | RunCanProcessHostContextTypeChecks( | 
|  | extension, *embedder->GetPrimaryMainFrame()->GetProcess(), | 
|  | {mojom::ContextType::kContentScript, | 
|  | mojom::ContextType::kPrivilegedExtension, | 
|  | mojom::ContextType::kOffscreenExtension}, | 
|  | "embedder process"); | 
|  |  | 
|  | // The webview can only host content scripts, user scripts, and | 
|  | // unprivileged extension contexts (accessible resources). | 
|  | RunCanProcessHostContextTypeChecks( | 
|  | extension, *webview->GetPrimaryMainFrame()->GetProcess(), | 
|  | {mojom::ContextType::kContentScript, | 
|  | mojom::ContextType::kUnprivilegedExtension}, | 
|  | "webview process with extension passed"); | 
|  |  | 
|  | // If the extension isn't associated with the call, the webview could only | 
|  | // possibly contain web pages and untrusted web ui. | 
|  | RunCanProcessHostContextTypeChecks( | 
|  | nullptr, *webview->GetPrimaryMainFrame()->GetProcess(), | 
|  | {mojom::ContextType::kWebPage, mojom::ContextType::kUntrustedWebUi}, | 
|  | "webview process without extension passed"); | 
|  | } | 
|  |  | 
|  | IN_PROC_BROWSER_TEST_F(ProcessMapBrowserTest, | 
|  | IsPrivilegedExtensionProcess_UserScripts) { | 
|  | const Extension* extension = | 
|  | AddExtensionWithHostPermission("test", "*://example.com/*"); | 
|  | ASSERT_TRUE(extension); | 
|  |  | 
|  | OpenDomain("example.com"); | 
|  | ExecuteUserScriptInActiveTab(extension->id()); | 
|  |  | 
|  | EXPECT_FALSE(process_map()->IsPrivilegedExtensionProcess( | 
|  | *extension, GetActiveMainFrameProcessID())); | 
|  | } | 
|  |  | 
|  | IN_PROC_BROWSER_TEST_F(ProcessMapBrowserTest, CanHostContextType_UserScripts) { | 
|  | const Extension* extension = | 
|  | AddExtensionWithHostPermission("test", "*://example.com/*"); | 
|  | ASSERT_TRUE(extension); | 
|  |  | 
|  | OpenDomain("example.com"); | 
|  | ExecuteUserScriptInActiveTab(extension->id()); | 
|  |  | 
|  | content::RenderProcessHost& web_page_process = GetActiveMainFrameProcess(); | 
|  |  | 
|  | RunCanProcessHostContextTypeChecks( | 
|  | extension, web_page_process, | 
|  | {mojom::ContextType::kContentScript, mojom::ContextType::kUserScript}, | 
|  | "page with injected user script with extension passed"); | 
|  | RunCanProcessHostContextTypeChecks( | 
|  | nullptr, web_page_process, | 
|  | {mojom::ContextType::kWebPage, mojom::ContextType::kUntrustedWebUi}, | 
|  | "page with injected user script without extension passed"); | 
|  | } | 
|  |  | 
|  | }  // namespace extensions |