| // 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 <memory> |
| #include <vector> |
| |
| #include "base/strings/string_piece.h" |
| #include "base/strings/stringprintf.h" |
| #include "chrome/browser/extensions/extension_browsertest.h" |
| #include "chrome/browser/ui/browser.h" |
| #include "chrome/browser/ui/tabs/tab_strip_model.h" |
| #include "chrome/test/base/ui_test_utils.h" |
| #include "content/public/browser/render_frame_host.h" |
| #include "content/public/browser/render_process_host.h" |
| #include "content/public/browser/web_contents.h" |
| #include "content/public/test/browser_test.h" |
| #include "content/public/test/browser_test_utils.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/browser/process_map.h" |
| #include "extensions/common/constants.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()); |
| } |
| |
| // Returns the WebContents of the currently-active tab. |
| content::WebContents* GetActiveTab() { |
| return browser()->tab_strip_model()->GetActiveWebContents(); |
| } |
| |
| content::RenderProcessHost& GetActiveMainFrameProcess() { |
| return *GetActiveTab()->GetPrimaryMainFrame()->GetProcess(); |
| } |
| |
| int GetActiveMainFrameProcessID() { |
| return GetActiveMainFrameProcess().GetID(); |
| } |
| |
| // Adds a new extension with the given `extension_name` and host permission to |
| // the given `host_pattern`. |
| const Extension* AddExtensionWithHostPermission( |
| base::StringPiece extension_name, |
| base::StringPiece 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( |
| base::StringPiece extension_name, |
| base::StringPiece 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 = GetActiveTab(); |
| // TODO(https://crbug.com/1429408): 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, |
| blink::mojom::WantResultOption::kWantResult, |
| blink::mojom::UserActivationOption::kDoNotActivate, |
| blink::mojom::PromiseResultOption::kAwait)), |
| ScriptExecutor::SPECIFIED_FRAMES, {ExtensionApiFrameIdMap::kTopFrameId}, |
| ScriptExecutor::DONT_MATCH_ABOUT_BLANK, |
| 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()); |
| } |
| |
| // Adds a new extension with a sandboxed frame, `sandboxed.html`, and a parent |
| // page, `parent.html` to host it. |
| const Extension* AddExtensionWithSandboxedFrame() { |
| static constexpr char kManifest[] = |
| R"({ |
| "name": "Sandboxed Page", |
| "manifest_version": 3, |
| "version": "0.1", |
| "sandbox": { |
| "pages": [ "sandboxed.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("parent.html"), |
| R"(<html><iframe src="sandboxed.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(base::StringPiece domain) { |
| ASSERT_TRUE(ui_test_utils::NavigateToURL( |
| browser(), embedded_test_server()->GetURL(domain, "/simple.html"))); |
| } |
| |
| // Opens a new tab to a Web UI page. |
| void OpenWebUi() { |
| ASSERT_TRUE( |
| ui_test_utils::NavigateToURL(browser(), GURL("chrome://settings"))); |
| } |
| |
| // Opens a new tab to a page in the given `extension`. |
| void OpenExtensionPage(const Extension& extension) { |
| ASSERT_TRUE(ui_test_utils::NavigateToURL( |
| browser(), extension.GetResourceURL("manifest.json"))); |
| } |
| |
| // Opens a new tab to the given `domain` and waits for a content script to |
| // inject. |
| void OpenDomainAndWaitForContentScript(base::StringPiece 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(ui_test_utils::NavigateToURL( |
| browser(), 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)); |
| |
| // Note: it's okay for `chrome` to be defined; it has various |
| // unstandardized, non-extension-process stuff (like chrome.csi). We just |
| // require the special APIs to be undefined. |
| return content::EvalJs(frame, "!chrome || !chrome.tabs;").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<Feature::Context>& allowed_contexts, |
| base::StringPiece debug_string) { |
| std::vector<Feature::Context> all_types = { |
| Feature::UNSPECIFIED_CONTEXT, |
| Feature::BLESSED_EXTENSION_CONTEXT, |
| Feature::UNBLESSED_EXTENSION_CONTEXT, |
| Feature::CONTENT_SCRIPT_CONTEXT, |
| Feature::WEB_PAGE_CONTEXT, |
| Feature::BLESSED_WEB_PAGE_CONTEXT, |
| Feature::WEBUI_CONTEXT, |
| Feature::WEBUI_UNTRUSTED_CONTEXT, |
| Feature::LOCK_SCREEN_EXTENSION_CONTEXT, |
| Feature::OFFSCREEN_EXTENSION_CONTEXT, |
| Feature::USER_SCRIPT_CONTEXT, |
| }; |
| |
| 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_; |
| }; |
| |
| // 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, |
| {Feature::CONTENT_SCRIPT_CONTEXT}, |
| "web page with extension passed"); |
| RunCanProcessHostContextTypeChecks( |
| nullptr, web_page_process, |
| {Feature::WEB_PAGE_CONTEXT, Feature::WEBUI_UNTRUSTED_CONTEXT}, |
| "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, |
| {Feature::CONTENT_SCRIPT_CONTEXT}, |
| "webui page with extension passed"); |
| RunCanProcessHostContextTypeChecks(nullptr, webui_process, |
| {Feature::WEBUI_CONTEXT}, |
| "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, |
| {Feature::CONTENT_SCRIPT_CONTEXT, Feature::BLESSED_EXTENSION_CONTEXT, |
| Feature::OFFSCREEN_EXTENSION_CONTEXT}, |
| "extension1 page with extension1 passed"); |
| RunCanProcessHostContextTypeChecks(extension2, extension1_process, |
| {Feature::CONTENT_SCRIPT_CONTEXT}, |
| "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, |
| {Feature::CONTENT_SCRIPT_CONTEXT, Feature::BLESSED_EXTENSION_CONTEXT, |
| Feature::OFFSCREEN_EXTENSION_CONTEXT}, |
| "extension2 page with extension2 passed"); |
| RunCanProcessHostContextTypeChecks(extension1, extension2_process, |
| {Feature::CONTENT_SCRIPT_CONTEXT}, |
| "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, |
| {Feature::CONTENT_SCRIPT_CONTEXT}, |
| "web page with extension passed"); |
| RunCanProcessHostContextTypeChecks( |
| nullptr, page_process, |
| {Feature::WEB_PAGE_CONTEXT, Feature::WEBUI_UNTRUSTED_CONTEXT}, |
| "web page without extension passed"); |
| } |
| |
| // Tests that sandboxed extension frames are considered privileged |
| // extension processes, since they execute within the same process (even |
| // though they don't have direct API access). This isn't a security bug |
| // since any compromised renderer could just access an un-sandboxed context. |
| // TODO(https://crbug.com/510122): This could change with out-of-process- |
| // sandboxed-iframes. |
| IN_PROC_BROWSER_TEST_F(ProcessMapBrowserTest, |
| IsPrivilegedExtensionProcess_SandboxedExtensionFrame) { |
| const Extension* extension = AddExtensionWithSandboxedFrame(); |
| ASSERT_TRUE(extension); |
| |
| OpenExtensionPageWithSandboxedFrame(*extension); |
| |
| content::WebContents* web_contents = GetActiveTab(); |
| 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)); |
| |
| int main_frame_process_id = main_frame->GetProcess()->GetID(); |
| int sandboxed_frame_process_id = sandboxed_frame->GetProcess()->GetID(); |
| |
| EXPECT_EQ(main_frame_process_id, sandboxed_frame_process_id); |
| EXPECT_TRUE(process_map()->IsPrivilegedExtensionProcess( |
| *extension, main_frame_process_id)); |
| EXPECT_TRUE(process_map()->IsPrivilegedExtensionProcess( |
| *extension, sandboxed_frame_process_id)); |
| } |
| |
| // 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 = GetActiveTab(); |
| 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(); |
| |
| EXPECT_EQ(main_frame_process.GetID(), sandboxed_frame_process.GetID()); |
| |
| RunCanProcessHostContextTypeChecks( |
| extension, main_frame_process, |
| {Feature::CONTENT_SCRIPT_CONTEXT, Feature::BLESSED_EXTENSION_CONTEXT, |
| Feature::OFFSCREEN_EXTENSION_CONTEXT}, |
| "main frame process with extension passed"); |
| RunCanProcessHostContextTypeChecks( |
| nullptr, main_frame_process, {}, |
| "main frame process without extension passed"); |
| |
| RunCanProcessHostContextTypeChecks( |
| extension, sandboxed_frame_process, |
| {Feature::CONTENT_SCRIPT_CONTEXT, Feature::BLESSED_EXTENSION_CONTEXT, |
| Feature::OFFSCREEN_EXTENSION_CONTEXT}, |
| "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()->GetID())); |
| EXPECT_FALSE(process_map()->IsPrivilegedExtensionProcess( |
| *extension, webview->GetPrimaryMainFrame()->GetProcess()->GetID())); |
| } |
| |
| 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 unblessed extension context (which is only available to |
| // webviews). |
| RunCanProcessHostContextTypeChecks( |
| extension, *embedder->GetPrimaryMainFrame()->GetProcess(), |
| {Feature::CONTENT_SCRIPT_CONTEXT, Feature::BLESSED_EXTENSION_CONTEXT, |
| Feature::OFFSCREEN_EXTENSION_CONTEXT}, |
| "embedder process"); |
| |
| // The webview can only host content scripts, user scripts, and |
| // unblessed extension contexts (accessible resources). |
| RunCanProcessHostContextTypeChecks( |
| extension, *webview->GetPrimaryMainFrame()->GetProcess(), |
| {Feature::CONTENT_SCRIPT_CONTEXT, Feature::UNBLESSED_EXTENSION_CONTEXT}, |
| "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(), |
| {Feature::WEB_PAGE_CONTEXT, Feature::WEBUI_UNTRUSTED_CONTEXT}, |
| "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, |
| {Feature::CONTENT_SCRIPT_CONTEXT, Feature::USER_SCRIPT_CONTEXT}, |
| "page with injected user script with extension passed"); |
| RunCanProcessHostContextTypeChecks( |
| nullptr, web_page_process, |
| {Feature::WEB_PAGE_CONTEXT, Feature::WEBUI_UNTRUSTED_CONTEXT}, |
| "page with injected user script without extension passed"); |
| } |
| |
| } // namespace extensions |