| // Copyright 2014 The Chromium Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "chrome/browser/extensions/extension_action_runner.h" |
| |
| #include <stddef.h> |
| |
| #include <memory> |
| #include <utility> |
| #include <vector> |
| |
| #include "base/files/file_path.h" |
| #include "base/memory/raw_ptr.h" |
| #include "base/run_loop.h" |
| #include "base/strings/stringprintf.h" |
| #include "base/test/scoped_feature_list.h" |
| #include "chrome/browser/extensions/extension_browsertest.h" |
| #include "chrome/browser/extensions/scripting_permissions_modifier.h" |
| #include "chrome/browser/extensions/site_permissions_helper.h" |
| #include "chrome/browser/extensions/tab_helper.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/navigation_entry.h" |
| #include "content/public/browser/web_contents.h" |
| #include "content/public/test/browser_test.h" |
| #include "content/public/test/browser_test_utils.h" |
| #include "content/public/test/fenced_frame_test_util.h" |
| #include "extensions/browser/extension_action.h" |
| #include "extensions/browser/permissions_manager.h" |
| #include "extensions/common/extension_features.h" |
| #include "extensions/test/extension_test_message_listener.h" |
| #include "extensions/test/permissions_manager_waiter.h" |
| #include "extensions/test/test_extension_dir.h" |
| #include "net/dns/mock_host_resolver.h" |
| #include "testing/gtest/include/gtest/gtest.h" |
| |
| namespace extensions { |
| |
| namespace { |
| |
| using SiteAccess = SitePermissionsHelper::SiteAccess; |
| using UserSiteSetting = PermissionsManager::UserSiteSetting; |
| |
| const char kAllHostsScheme[] = "*://*/*"; |
| const char kExplicitHostsScheme[] = "http://127.0.0.1/*"; |
| const char kBackgroundScript[] = |
| "\"background\": {\"scripts\": [\"script.js\"]}"; |
| const char kBackgroundScriptSource[] = |
| "var listener = function(tabId) {\n" |
| " chrome.tabs.onUpdated.removeListener(listener);\n" |
| " chrome.tabs.executeScript(tabId, {\n" |
| " code: \"chrome.test.sendMessage('inject succeeded');\"\n" |
| " });" |
| "};\n" |
| "chrome.tabs.onUpdated.addListener(listener);\n" |
| "chrome.test.sendMessage('ready');"; |
| const char kContentScriptSource[] = |
| "chrome.test.sendMessage('inject succeeded');"; |
| |
| const char kInjectSucceeded[] = "inject succeeded"; |
| |
| enum InjectionType { CONTENT_SCRIPT, EXECUTE_SCRIPT }; |
| |
| enum HostType { ALL_HOSTS, EXPLICIT_HOSTS }; |
| |
| enum RequiresConsent { REQUIRES_CONSENT, DOES_NOT_REQUIRE_CONSENT }; |
| |
| enum WithholdPermissions { WITHHOLD_PERMISSIONS, DONT_WITHHOLD_PERMISSIONS }; |
| |
| // Runs all pending tasks in the renderer associated with |web_contents|. |
| // Returns true on success. |
| bool RunAllPendingInRenderer(content::WebContents* web_contents) { |
| // This is slight hack to achieve a RunPendingInRenderer() method. Since IPCs |
| // are sent synchronously, anything started prior to this method will finish |
| // before this method returns (as content::ExecuteScript() is synchronous). |
| return content::ExecuteScript(web_contents, "1 == 1;"); |
| } |
| |
| // Returns whether the extension injected a script by checking the document |
| // title. |
| bool DidInjectScript(content::WebContents* web_contents) { |
| const std::u16string& title = web_contents->GetTitle(); |
| if (title == u"success") |
| return true; |
| // The original page title is "OK"; this indicates the script didn't |
| // inject. |
| if (title == u"OK") |
| return false; |
| ADD_FAILURE() << "Unexpected page title found: " << title; |
| return false; |
| } |
| |
| } // namespace |
| |
| class ExtensionActionRunnerBrowserTest : public ExtensionBrowserTest { |
| public: |
| ExtensionActionRunnerBrowserTest() {} |
| |
| void TearDownOnMainThread() override; |
| |
| // Returns an extension with the given |host_type| and |injection_type|. If |
| // one already exists, the existing extension will be returned. Othewrwise, |
| // one will be created. |
| // This could potentially return NULL if LoadExtension() fails. |
| const Extension* CreateExtension(HostType host_type, |
| InjectionType injection_type, |
| WithholdPermissions withhold_permissions); |
| |
| void RunActiveScriptsTest(const std::string& name, |
| HostType host_type, |
| InjectionType injection_type, |
| WithholdPermissions withhold_permissions, |
| RequiresConsent requires_consent); |
| |
| private: |
| std::vector<std::unique_ptr<TestExtensionDir>> test_extension_dirs_; |
| std::vector<const Extension*> extensions_; |
| }; |
| |
| void ExtensionActionRunnerBrowserTest::TearDownOnMainThread() { |
| test_extension_dirs_.clear(); |
| } |
| |
| const Extension* ExtensionActionRunnerBrowserTest::CreateExtension( |
| HostType host_type, |
| InjectionType injection_type, |
| WithholdPermissions withhold_permissions) { |
| std::string name = base::StringPrintf( |
| "%s %s", |
| injection_type == CONTENT_SCRIPT ? "content_script" : "execute_script", |
| host_type == ALL_HOSTS ? "all_hosts" : "explicit_hosts"); |
| |
| const char* const permission_scheme = |
| host_type == ALL_HOSTS ? kAllHostsScheme : kExplicitHostsScheme; |
| |
| std::string permissions = base::StringPrintf( |
| "\"permissions\": [\"tabs\", \"%s\"]", permission_scheme); |
| |
| std::string scripts; |
| std::string script_source; |
| if (injection_type == CONTENT_SCRIPT) { |
| scripts = base::StringPrintf( |
| "\"content_scripts\": [" |
| " {" |
| " \"matches\": [\"%s\"]," |
| " \"js\": [\"script.js\"]," |
| " \"run_at\": \"document_end\"" |
| " }" |
| "]", |
| permission_scheme); |
| } else { |
| scripts = kBackgroundScript; |
| } |
| |
| std::string manifest = base::StringPrintf( |
| "{" |
| " \"name\": \"%s\"," |
| " \"version\": \"1.0\"," |
| " \"manifest_version\": 2," |
| " %s," |
| " %s" |
| "}", |
| name.c_str(), permissions.c_str(), scripts.c_str()); |
| |
| std::unique_ptr<TestExtensionDir> dir(new TestExtensionDir); |
| dir->WriteManifest(manifest); |
| dir->WriteFile(FILE_PATH_LITERAL("script.js"), |
| injection_type == CONTENT_SCRIPT ? kContentScriptSource |
| : kBackgroundScriptSource); |
| |
| const Extension* extension = nullptr; |
| if (injection_type == CONTENT_SCRIPT) { |
| extension = LoadExtension(dir->UnpackedPath()); |
| } else { |
| ExtensionTestMessageListener listener("ready"); |
| extension = LoadExtension(dir->UnpackedPath()); |
| EXPECT_TRUE(listener.WaitUntilSatisfied()); |
| } |
| |
| if (extension) { |
| test_extension_dirs_.push_back(std::move(dir)); |
| extensions_.push_back(extension); |
| |
| if (withhold_permissions == WITHHOLD_PERMISSIONS && |
| PermissionsManager::Get(profile())->CanAffectExtension(*extension)) { |
| ScriptingPermissionsModifier(profile(), extension) |
| .SetWithholdHostPermissions(true); |
| } |
| } |
| |
| // If extension is NULL here, it will be caught later in the test. |
| return extension; |
| } |
| |
| void ExtensionActionRunnerBrowserTest::RunActiveScriptsTest( |
| const std::string& name, |
| HostType host_type, |
| InjectionType injection_type, |
| WithholdPermissions withhold_permissions, |
| RequiresConsent requires_consent) { |
| ASSERT_TRUE(embedded_test_server()->Start()); |
| |
| const Extension* extension = |
| CreateExtension(host_type, injection_type, withhold_permissions); |
| ASSERT_TRUE(extension); |
| |
| content::WebContents* web_contents = |
| browser()->tab_strip_model()->GetActiveWebContents(); |
| ASSERT_TRUE(web_contents); |
| |
| ExtensionActionRunner* runner = |
| ExtensionActionRunner::GetForWebContents(web_contents); |
| ASSERT_TRUE(runner); |
| |
| ExtensionTestMessageListener inject_success_listener(kInjectSucceeded); |
| auto navigate = [this]() { |
| // Navigate to an URL (which matches the explicit host specified in the |
| // extension content_scripts_explicit_hosts). All extensions should |
| // inject the script. |
| ASSERT_TRUE(ui_test_utils::NavigateToURL( |
| browser(), |
| embedded_test_server()->GetURL("/extensions/test_file.html"))); |
| }; |
| |
| if (requires_consent == DOES_NOT_REQUIRE_CONSENT) { |
| // If the extension doesn't require explicit consent, it should inject |
| // automatically right away. |
| navigate(); |
| EXPECT_FALSE(runner->WantsToRun(extension)); |
| EXPECT_TRUE(inject_success_listener.WaitUntilSatisfied()); |
| EXPECT_FALSE(runner->WantsToRun(extension)); |
| return; |
| } |
| |
| ASSERT_EQ(REQUIRES_CONSENT, requires_consent); |
| |
| class BlockedActionWaiter : public ExtensionActionRunner::TestObserver { |
| public: |
| explicit BlockedActionWaiter(ExtensionActionRunner* runner) |
| : runner_(runner) { |
| runner_->set_observer_for_testing(this); |
| } |
| |
| BlockedActionWaiter(const BlockedActionWaiter&) = delete; |
| BlockedActionWaiter& operator=(const BlockedActionWaiter&) = delete; |
| |
| ~BlockedActionWaiter() { runner_->set_observer_for_testing(nullptr); } |
| |
| void Wait() { run_loop_.Run(); } |
| |
| private: |
| // ExtensionActionRunner::TestObserver: |
| void OnBlockedActionAdded() override { run_loop_.Quit(); } |
| |
| raw_ptr<ExtensionActionRunner> runner_; |
| base::RunLoop run_loop_; |
| }; |
| |
| BlockedActionWaiter waiter(runner); |
| navigate(); |
| waiter.Wait(); |
| EXPECT_TRUE(runner->WantsToRun(extension)); |
| EXPECT_FALSE(inject_success_listener.was_satisfied()); |
| |
| // Grant permission by clicking on the extension action. |
| runner->RunAction(extension, true); |
| |
| // Now, the extension should be able to inject the script. |
| EXPECT_TRUE(inject_success_listener.WaitUntilSatisfied()); |
| |
| // The extension should no longer want to run. |
| EXPECT_FALSE(runner->WantsToRun(extension)); |
| } |
| |
| // Load up different combinations of extensions, and verify that script |
| // injection is properly withheld and indicated to the user. |
| // NOTE: Though these could be parameterized test cases, there's enough |
| // bits here that just having a helper method is quite a bit more readable. |
| IN_PROC_BROWSER_TEST_F( |
| ExtensionActionRunnerBrowserTest, |
| ActiveScriptsAreDisplayedAndDelayExecution_ExecuteScripts_AllHosts) { |
| RunActiveScriptsTest("execute_scripts_all_hosts", ALL_HOSTS, EXECUTE_SCRIPT, |
| WITHHOLD_PERMISSIONS, REQUIRES_CONSENT); |
| } |
| IN_PROC_BROWSER_TEST_F( |
| ExtensionActionRunnerBrowserTest, |
| ActiveScriptsAreDisplayedAndDelayExecution_ExecuteScripts_ExplicitHosts) { |
| RunActiveScriptsTest("execute_scripts_explicit_hosts", EXPLICIT_HOSTS, |
| EXECUTE_SCRIPT, WITHHOLD_PERMISSIONS, REQUIRES_CONSENT); |
| } |
| IN_PROC_BROWSER_TEST_F( |
| ExtensionActionRunnerBrowserTest, |
| ActiveScriptsAreDisplayedAndDelayExecution_ContentScripts_AllHosts) { |
| RunActiveScriptsTest("content_scripts_all_hosts", ALL_HOSTS, CONTENT_SCRIPT, |
| WITHHOLD_PERMISSIONS, REQUIRES_CONSENT); |
| } |
| IN_PROC_BROWSER_TEST_F( |
| ExtensionActionRunnerBrowserTest, |
| ActiveScriptsAreDisplayedAndDelayExecution_ContentScripts_ExplicitHosts) { |
| RunActiveScriptsTest("content_scripts_explicit_hosts", EXPLICIT_HOSTS, |
| CONTENT_SCRIPT, WITHHOLD_PERMISSIONS, REQUIRES_CONSENT); |
| } |
| |
| // Test that removing an extension with pending injections a) removes the |
| // pending injections for that extension, and b) does not affect pending |
| // injections for other extensions. |
| IN_PROC_BROWSER_TEST_F(ExtensionActionRunnerBrowserTest, |
| RemoveExtensionWithPendingInjections) { |
| // Load up two extensions, each with content scripts. |
| scoped_refptr<const Extension> extension1 = |
| CreateExtension(ALL_HOSTS, CONTENT_SCRIPT, WITHHOLD_PERMISSIONS); |
| ASSERT_TRUE(extension1); |
| scoped_refptr<const Extension> extension2 = |
| CreateExtension(ALL_HOSTS, CONTENT_SCRIPT, WITHHOLD_PERMISSIONS); |
| ASSERT_TRUE(extension2); |
| |
| ASSERT_NE(extension1->id(), extension2->id()); |
| |
| content::WebContents* web_contents = |
| browser()->tab_strip_model()->GetActiveWebContents(); |
| ASSERT_TRUE(web_contents); |
| ExtensionActionRunner* action_runner = |
| ExtensionActionRunner::GetForWebContents(web_contents); |
| ASSERT_TRUE(action_runner); |
| |
| ASSERT_TRUE(embedded_test_server()->Start()); |
| ASSERT_TRUE(ui_test_utils::NavigateToURL( |
| browser(), embedded_test_server()->GetURL("/extensions/test_file.html"))); |
| |
| // Both extensions should have pending requests. |
| EXPECT_TRUE(action_runner->WantsToRun(extension1.get())); |
| EXPECT_TRUE(action_runner->WantsToRun(extension2.get())); |
| |
| // Unload one of the extensions. |
| UnloadExtension(extension2->id()); |
| |
| EXPECT_TRUE(RunAllPendingInRenderer(web_contents)); |
| |
| // We should have pending requests for extension1, but not the removed |
| // extension2. |
| EXPECT_TRUE(action_runner->WantsToRun(extension1.get())); |
| EXPECT_FALSE(action_runner->WantsToRun(extension2.get())); |
| |
| // We should still be able to run the request for extension1. |
| ExtensionTestMessageListener inject_success_listener(kInjectSucceeded); |
| inject_success_listener.set_extension_id(extension1->id()); |
| action_runner->RunAction(extension1.get(), true); |
| EXPECT_TRUE(inject_success_listener.WaitUntilSatisfied()); |
| } |
| |
| // Test that granting the extension all urls permission allows it to run on |
| // pages, and that the permission update is sent to existing renderers. |
| IN_PROC_BROWSER_TEST_F(ExtensionActionRunnerBrowserTest, |
| GrantExtensionAllUrlsPermission) { |
| // Loadup an extension and navigate. |
| const Extension* extension = |
| CreateExtension(ALL_HOSTS, CONTENT_SCRIPT, WITHHOLD_PERMISSIONS); |
| ASSERT_TRUE(extension); |
| |
| content::WebContents* web_contents = |
| browser()->tab_strip_model()->GetActiveWebContents(); |
| ASSERT_TRUE(web_contents); |
| ExtensionActionRunner* action_runner = |
| ExtensionActionRunner::GetForWebContents(web_contents); |
| ASSERT_TRUE(action_runner); |
| |
| ExtensionTestMessageListener inject_success_listener(kInjectSucceeded); |
| inject_success_listener.set_extension_id(extension->id()); |
| |
| ASSERT_TRUE(embedded_test_server()->Start()); |
| GURL url = embedded_test_server()->GetURL("/extensions/test_file.html"); |
| ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), url)); |
| |
| // The extension shouldn't be allowed to run. |
| EXPECT_TRUE(action_runner->WantsToRun(extension)); |
| EXPECT_EQ(1, action_runner->num_page_requests()); |
| EXPECT_FALSE(inject_success_listener.was_satisfied()); |
| |
| // Enable the extension to run on all urls. |
| ScriptingPermissionsModifier modifier(profile(), extension); |
| modifier.SetWithholdHostPermissions(false); |
| EXPECT_TRUE(RunAllPendingInRenderer(web_contents)); |
| |
| // Navigate again - this time, the extension should execute immediately (and |
| // should not need to ask the script controller for permission). |
| ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), url)); |
| EXPECT_FALSE(action_runner->WantsToRun(extension)); |
| EXPECT_EQ(0, action_runner->num_page_requests()); |
| EXPECT_TRUE(inject_success_listener.WaitUntilSatisfied()); |
| |
| // Revoke all urls permissions. |
| inject_success_listener.Reset(); |
| modifier.SetWithholdHostPermissions(true); |
| EXPECT_TRUE(RunAllPendingInRenderer(web_contents)); |
| |
| // Re-navigate; the extension should again need permission to run. |
| ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), url)); |
| EXPECT_TRUE(action_runner->WantsToRun(extension)); |
| EXPECT_EQ(1, action_runner->num_page_requests()); |
| EXPECT_FALSE(inject_success_listener.was_satisfied()); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(ExtensionActionRunnerBrowserTest, RunAction) { |
| // Load an extension that wants to run on every page at document start, and |
| // load a test page. |
| ASSERT_TRUE(embedded_test_server()->Start()); |
| const Extension* extension = LoadExtension( |
| test_data_dir_.AppendASCII("blocked_actions/content_scripts")); |
| ASSERT_TRUE(extension); |
| ScriptingPermissionsModifier(profile(), extension) |
| .SetWithholdHostPermissions(true); |
| |
| // Navigate to a page where the extension wants to run. |
| const GURL url = embedded_test_server()->GetURL("/simple.html"); |
| ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), url)); |
| content::WebContents* web_contents = |
| browser()->tab_strip_model()->GetActiveWebContents(); |
| EXPECT_TRUE(content::WaitForLoadStop(web_contents)); |
| content::NavigationController& web_controller = web_contents->GetController(); |
| const int nav_id = web_controller.GetLastCommittedEntry()->GetUniqueID(); |
| |
| // The extension should want to run on the page, should not have |
| // injected, and should have "on click" access. |
| ExtensionActionRunner* runner = |
| ExtensionActionRunner::GetForWebContents(web_contents); |
| ASSERT_TRUE(runner); |
| EXPECT_TRUE(runner->WantsToRun(extension)); |
| EXPECT_FALSE(DidInjectScript(web_contents)); |
| SitePermissionsHelper permissions(profile()); |
| EXPECT_EQ(permissions.GetSiteAccess(*extension, url), SiteAccess::kOnClick); |
| |
| // Run the action without changing permissions, and reject the bubble |
| // prompting for page reload. |
| runner->accept_bubble_for_testing(false); |
| runner->RunAction(extension, true); |
| base::RunLoop().RunUntilIdle(); |
| EXPECT_TRUE(content::WaitForLoadStop(web_contents)); |
| |
| // Nothing should happen, because the user didn't agree to reload the page. |
| // The extension should still want to run. |
| EXPECT_EQ(web_controller.GetLastCommittedEntry()->GetUniqueID(), nav_id); |
| EXPECT_FALSE(DidInjectScript(web_contents)); |
| EXPECT_TRUE(runner->WantsToRun(extension)); |
| |
| // Run the action without changing permissions, and accept the bubble |
| // prompting for page reload. |
| runner->accept_bubble_for_testing(true); |
| runner->RunAction(extension, true); |
| base::RunLoop().RunUntilIdle(); |
| EXPECT_TRUE(content::WaitForLoadStop(web_contents)); |
| |
| // Since we automatically accepted the bubble prompting us, the page should |
| // have reload, the extension should have injected at document start and |
| // the site access should still be "on click". |
| EXPECT_GE(web_controller.GetLastCommittedEntry()->GetUniqueID(), nav_id); |
| EXPECT_TRUE(DidInjectScript(web_contents)); |
| EXPECT_FALSE(runner->WantsToRun(extension)); |
| EXPECT_EQ(permissions.GetSiteAccess(*extension, url), SiteAccess::kOnClick); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(ExtensionActionRunnerBrowserTest, |
| HandlePageAccessModified) { |
| // Load an extension that wants to run on every page at document start, and |
| // load a test page. |
| ASSERT_TRUE(embedded_test_server()->Start()); |
| const Extension* extension = LoadExtension( |
| test_data_dir_.AppendASCII("blocked_actions/content_scripts")); |
| ASSERT_TRUE(extension); |
| ScriptingPermissionsModifier(profile(), extension) |
| .SetWithholdHostPermissions(true); |
| |
| // Navigate to a page where the extension wants to run. |
| const GURL url = embedded_test_server()->GetURL("/simple.html"); |
| ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), url)); |
| content::WebContents* web_contents = |
| browser()->tab_strip_model()->GetActiveWebContents(); |
| EXPECT_TRUE(content::WaitForLoadStop(web_contents)); |
| content::NavigationController& web_controller = web_contents->GetController(); |
| const int nav_id = web_controller.GetLastCommittedEntry()->GetUniqueID(); |
| |
| // The extension should want to run on the page, should not have |
| // injected, and should have "on click" access. |
| ExtensionActionRunner* runner = |
| ExtensionActionRunner::GetForWebContents(web_contents); |
| ASSERT_TRUE(runner); |
| EXPECT_TRUE(runner->WantsToRun(extension)); |
| EXPECT_FALSE(DidInjectScript(web_contents)); |
| SitePermissionsHelper permissions(profile()); |
| EXPECT_EQ(permissions.GetSiteAccess(*extension, url), SiteAccess::kOnClick); |
| |
| // Request a permission increase, and accept the bubble prompting for page |
| // refresh. |
| runner->accept_bubble_for_testing(true); |
| runner->HandlePageAccessModified(extension, SiteAccess::kOnClick, |
| SiteAccess::kOnSite); |
| base::RunLoop().RunUntilIdle(); |
| EXPECT_TRUE(content::WaitForLoadStop(web_contents)); |
| |
| // Since we automatically accepted the bubble prompting us, the page should |
| // have refreshed, the extension should have injected at document start and |
| // the site access should now be "on site". |
| EXPECT_GE(web_controller.GetLastCommittedEntry()->GetUniqueID(), nav_id); |
| EXPECT_TRUE(DidInjectScript(web_contents)); |
| EXPECT_FALSE(runner->WantsToRun(extension)); |
| EXPECT_EQ(permissions.GetSiteAccess(*extension, url), SiteAccess::kOnSite); |
| |
| // Request a permission decrease, and accept the blocked action bubble |
| // prompting for page refresh. |
| runner->accept_bubble_for_testing(true); |
| runner->HandlePageAccessModified(extension, SiteAccess::kOnSite, |
| SiteAccess::kOnClick); |
| base::RunLoop().RunUntilIdle(); |
| EXPECT_TRUE(content::WaitForLoadStop(web_contents)); |
| |
| // Since we automatically accepted the bubble prompting us, the page should |
| // have refreshed, the extension should not have injected at document start |
| // and the site access should now be "on click". |
| EXPECT_GE(web_controller.GetLastCommittedEntry()->GetUniqueID(), nav_id); |
| EXPECT_FALSE(DidInjectScript(web_contents)); |
| EXPECT_TRUE(runner->WantsToRun(extension)); |
| EXPECT_EQ(permissions.GetSiteAccess(*extension, url), SiteAccess::kOnClick); |
| } |
| |
| // If we don't withhold permissions, extensions should execute normally. |
| IN_PROC_BROWSER_TEST_F(ExtensionActionRunnerBrowserTest, |
| ScriptsExecuteWhenNoPermissionsWithheld_ContentScripts) { |
| RunActiveScriptsTest("content_scripts_all_hosts", ALL_HOSTS, CONTENT_SCRIPT, |
| DONT_WITHHOLD_PERMISSIONS, DOES_NOT_REQUIRE_CONSENT); |
| } |
| IN_PROC_BROWSER_TEST_F(ExtensionActionRunnerBrowserTest, |
| ScriptsExecuteWhenNoPermissionsWithheld_ExecuteScripts) { |
| RunActiveScriptsTest("execute_scripts_all_hosts", ALL_HOSTS, EXECUTE_SCRIPT, |
| DONT_WITHHOLD_PERMISSIONS, DOES_NOT_REQUIRE_CONSENT); |
| } |
| |
| // A version of the test with the flag off, in order to test that everything |
| // still works as expected. |
| class FlagOffExtensionActionRunnerBrowserTest |
| : public ExtensionActionRunnerBrowserTest { |
| private: |
| // Simply don't append the flag. |
| void SetUpCommandLine(base::CommandLine* command_line) override { |
| ExtensionBrowserTest::SetUpCommandLine(command_line); |
| } |
| }; |
| |
| IN_PROC_BROWSER_TEST_F(FlagOffExtensionActionRunnerBrowserTest, |
| ScriptsExecuteWhenFlagAbsent_ContentScripts) { |
| RunActiveScriptsTest("content_scripts_all_hosts", ALL_HOSTS, CONTENT_SCRIPT, |
| DONT_WITHHOLD_PERMISSIONS, DOES_NOT_REQUIRE_CONSENT); |
| } |
| IN_PROC_BROWSER_TEST_F(FlagOffExtensionActionRunnerBrowserTest, |
| ScriptsExecuteWhenFlagAbsent_ExecuteScripts) { |
| RunActiveScriptsTest("execute_scripts_all_hosts", ALL_HOSTS, EXECUTE_SCRIPT, |
| DONT_WITHHOLD_PERMISSIONS, DOES_NOT_REQUIRE_CONSENT); |
| } |
| |
| class ExtensionActionRunnerFencedFrameBrowserTest |
| : public ExtensionActionRunnerBrowserTest { |
| public: |
| ExtensionActionRunnerFencedFrameBrowserTest() = default; |
| ~ExtensionActionRunnerFencedFrameBrowserTest() override = default; |
| |
| ExtensionActionRunnerFencedFrameBrowserTest( |
| const ExtensionActionRunnerFencedFrameBrowserTest&) = delete; |
| ExtensionActionRunnerFencedFrameBrowserTest& operator=( |
| const ExtensionActionRunnerFencedFrameBrowserTest&) = delete; |
| |
| void SetUpOnMainThread() override { |
| host_resolver()->AddRule("*", "127.0.0.1"); |
| ASSERT_TRUE(embedded_test_server()->Start()); |
| ExtensionActionRunnerBrowserTest::SetUpOnMainThread(); |
| } |
| |
| protected: |
| content::test::FencedFrameTestHelper fenced_frame_helper_; |
| }; |
| |
| // Tests that a fenced frame doesn't clear active extensions. |
| IN_PROC_BROWSER_TEST_F(ExtensionActionRunnerFencedFrameBrowserTest, |
| FencedFrameDoesNotClearActiveExtensions) { |
| // Set a situation that |granted_extensions_| of ActiveTabPermissionGranter is |
| // not empty to test a fenced frame doesn't clear active extensions. |
| const Extension* extension = LoadExtension( |
| test_data_dir_.AppendASCII("blocked_actions/content_scripts")); |
| ASSERT_TRUE(extension); |
| ScriptingPermissionsModifier(profile(), extension) |
| .SetWithholdHostPermissions(true); |
| |
| GURL initial_url = embedded_test_server()->GetURL("a.com", "/simple.html"); |
| ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), initial_url)); |
| content::WebContents* web_contents = |
| browser()->tab_strip_model()->GetActiveWebContents(); |
| |
| ExtensionActionRunner* runner = |
| ExtensionActionRunner::GetForWebContents(web_contents); |
| ASSERT_TRUE(runner); |
| |
| runner->accept_bubble_for_testing(true); |
| |
| content::NavigationEntry* entry = |
| web_contents->GetController().GetLastCommittedEntry(); |
| ASSERT_TRUE(entry); |
| const int first_nav_id = entry->GetUniqueID(); |
| |
| runner->RunAction(extension, true); |
| base::RunLoop().RunUntilIdle(); |
| EXPECT_TRUE(content::WaitForLoadStop(web_contents)); |
| entry = web_contents->GetController().GetLastCommittedEntry(); |
| ASSERT_TRUE(entry); |
| EXPECT_GE(entry->GetUniqueID(), first_nav_id); |
| EXPECT_TRUE(DidInjectScript(web_contents)); |
| EXPECT_FALSE(runner->WantsToRun(extension)); |
| |
| ActiveTabPermissionGranter* active_tab_granter = |
| TabHelper::FromWebContents(web_contents)->active_tab_permission_granter(); |
| ASSERT_TRUE(active_tab_granter); |
| EXPECT_EQ(active_tab_granter->granted_extensions_.size(), 1U); |
| |
| // The origin of |url| and |fenced_frame_url| should be different because |
| // ActiveTabPermissionGranter::DidFinishNavigation is only able to clear |
| // active extensions when the origins are different. |
| GURL fenced_frame_url = |
| embedded_test_server()->GetURL("b.com", "/fenced_frames/title1.html"); |
| // Create a fenced frame and load the test url. Active extensions should not |
| // be cleared by the fenced frame navigation. |
| content::RenderFrameHost* fenced_frame_host = |
| fenced_frame_helper_.CreateFencedFrame( |
| web_contents->GetPrimaryMainFrame(), fenced_frame_url); |
| ASSERT_TRUE(fenced_frame_host); |
| EXPECT_EQ(active_tab_granter->granted_extensions_.size(), 1U); |
| |
| // Active extensions should be cleared after navigating a test url on the |
| // primary main frame. |
| GURL test_url = embedded_test_server()->GetURL("c.com", "/simple.html"); |
| ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), test_url)); |
| EXPECT_EQ(active_tab_granter->granted_extensions_.size(), 0U); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(ExtensionActionRunnerFencedFrameBrowserTest, |
| DoNotResetExtensionActionRunner) { |
| // Loadup an extension and navigate to test that a fenced frame doesn't reset |
| // ExtensionActionRunner's member variables. |
| const Extension* extension = |
| CreateExtension(ALL_HOSTS, CONTENT_SCRIPT, WITHHOLD_PERMISSIONS); |
| ASSERT_TRUE(extension); |
| |
| content::WebContents* web_contents = |
| browser()->tab_strip_model()->GetActiveWebContents(); |
| ASSERT_TRUE(web_contents); |
| ExtensionActionRunner* action_runner = |
| ExtensionActionRunner::GetForWebContents(web_contents); |
| ASSERT_TRUE(action_runner); |
| |
| ExtensionTestMessageListener inject_success_listener(kInjectSucceeded); |
| inject_success_listener.set_extension_id(extension->id()); |
| |
| GURL url = embedded_test_server()->GetURL("/extensions/test_file.html"); |
| ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), url)); |
| |
| ScriptingPermissionsModifier modifier(profile(), extension); |
| modifier.SetWithholdHostPermissions(false); |
| EXPECT_TRUE(RunAllPendingInRenderer(web_contents)); |
| |
| // Create a fenced frame and navigate the fenced frame url. |
| GURL fenced_frame_url = |
| embedded_test_server()->GetURL("/fenced_frames/title1.html"); |
| content::RenderFrameHost* fenced_frame_host = |
| fenced_frame_helper_.CreateFencedFrame( |
| web_contents->GetPrimaryMainFrame(), fenced_frame_url); |
| ASSERT_TRUE(fenced_frame_host); |
| // Fenced frame doesn't clear pending script injection requests and the |
| // scripts. |
| EXPECT_EQ(1, action_runner->num_page_requests()); |
| EXPECT_EQ(1U, action_runner->pending_scripts_.size()); |
| |
| // Navigate again on the primary main frame. Pending script injection requests |
| // and scripts should be cleared. |
| ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), url)); |
| EXPECT_EQ(0, action_runner->num_page_requests()); |
| EXPECT_EQ(0U, action_runner->pending_scripts_.size()); |
| } |
| |
| class ExtensionActionRunnerWithUserHostControlsBrowserTest |
| : public ExtensionActionRunnerBrowserTest { |
| public: |
| ExtensionActionRunnerWithUserHostControlsBrowserTest() { |
| feature_list_.InitAndEnableFeature( |
| extensions_features::kExtensionsMenuAccessControl); |
| } |
| ~ExtensionActionRunnerWithUserHostControlsBrowserTest() override = default; |
| |
| void SetUpOnMainThread() override { |
| ExtensionActionRunnerBrowserTest::SetUpOnMainThread(); |
| ASSERT_TRUE(embedded_test_server()->Start()); |
| |
| permissions_manager_ = PermissionsManager::Get(browser()->profile()); |
| ASSERT_TRUE(permissions_manager_); |
| } |
| |
| void HandleUserSiteSettingModified(const ExtensionId& extension_id, |
| const url::Origin url_origin, |
| UserSiteSetting user_site_setting, |
| bool accept_bubble) { |
| ExtensionActionRunner* runner = |
| ExtensionActionRunner::GetForWebContents(web_contents()); |
| ASSERT_TRUE(runner); |
| runner->accept_bubble_for_testing(accept_bubble); |
| |
| if (accept_bubble) { |
| PermissionsManagerWaiter waiter( |
| extensions::PermissionsManager::Get(profile())); |
| runner->HandleUserSiteSettingModified({extension_id}, url_origin, |
| user_site_setting); |
| waiter.WaitForUserPermissionsSettingsChange(); |
| |
| // Wait for page reload. |
| EXPECT_TRUE(content::WaitForLoadStop(web_contents())); |
| } else { |
| runner->HandleUserSiteSettingModified({extension_id}, url_origin, |
| user_site_setting); |
| // Make sure we tried to modified user site settings by verifying waiting |
| // for the task to be posted. |
| base::RunLoop().RunUntilIdle(); |
| EXPECT_TRUE(content::WaitForLoadStop(web_contents())); |
| } |
| } |
| |
| content::WebContents* web_contents() { |
| return browser()->tab_strip_model()->GetActiveWebContents(); |
| } |
| PermissionsManager* permissions_manager() { return permissions_manager_; } |
| |
| private: |
| base::test::ScopedFeatureList feature_list_; |
| raw_ptr<PermissionsManager, DanglingUntriaged> permissions_manager_; |
| }; |
| |
| // Tests changing user site settings when the extension has site access (which |
| // is either 'on all sites' or 'on site'). Note that we don't check if extension |
| // `WantsToRun` because on user-restricted sites, actions are blocked rather |
| // than withheld. |
| // TODO(crbug.com/1363781): Flaky on Win 7 and Mac 12. |
| #if BUILDFLAG(IS_WIN) || BUILDFLAG(IS_MAC) |
| #define MAYBE_HandleUserSiteSettingModified_ExtensionHasAccess \ |
| DISABLED_HandleUserSiteSettingModified_ExtensionHasAccess |
| #else |
| #define MAYBE_HandleUserSiteSettingModified_ExtensionHasAccess \ |
| HandleUserSiteSettingModified_ExtensionHasAccess |
| #endif |
| IN_PROC_BROWSER_TEST_F(ExtensionActionRunnerWithUserHostControlsBrowserTest, |
| MAYBE_HandleUserSiteSettingModified_ExtensionHasAccess) { |
| // Load an extension that wants to run on every page at document start. |
| const Extension* extension = LoadExtension( |
| test_data_dir_.AppendASCII("blocked_actions/content_scripts")); |
| ASSERT_TRUE(extension); |
| |
| // Navigate to a page where the extension can run. |
| const GURL url = embedded_test_server()->GetURL("/simple.html"); |
| const url::Origin url_origin = url::Origin::Create(url); |
| ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), url)); |
| |
| // By default, the page should have "customize by extension" user site |
| // setting and the extensions should have "on all sites" site access. The |
| // extension should have injected. |
| EXPECT_EQ(permissions_manager()->GetUserSiteSetting(url_origin), |
| UserSiteSetting::kCustomizeByExtension); |
| EXPECT_EQ(SitePermissionsHelper(profile()).GetSiteAccess(*extension, url), |
| SiteAccess::kOnAllSites); |
| EXPECT_TRUE(DidInjectScript(web_contents())); |
| |
| // "customize by extension (on site) -> "grant all extensions": |
| // maintains current site access and keeps the script injected. No refresh is |
| // required. |
| HandleUserSiteSettingModified(extension->id(), url_origin, |
| UserSiteSetting::kGrantAllExtensions, false); |
| EXPECT_EQ(permissions_manager()->GetUserSiteSetting(url_origin), |
| UserSiteSetting::kGrantAllExtensions); |
| EXPECT_TRUE(DidInjectScript(web_contents())); |
| |
| // "grant all extensions" -> "block all extensions": |
| // not accepting the page reload bubble maintains the same user site |
| // setting and keeps the script injected. |
| HandleUserSiteSettingModified(extension->id(), url_origin, |
| UserSiteSetting::kBlockAllExtensions, false); |
| EXPECT_EQ(permissions_manager()->GetUserSiteSetting(url_origin), |
| UserSiteSetting::kGrantAllExtensions); |
| EXPECT_TRUE(DidInjectScript(web_contents())); |
| |
| // "grant all extensions" -> "block all extensions": |
| // accepting the page reload bubble revokes site access, refreshes the page |
| // and does not inject the script. (Note: we will accept all the next reload |
| // page bubbles since we previously checked the behavior of not accepting the |
| // dialog). |
| HandleUserSiteSettingModified(extension->id(), url_origin, |
| UserSiteSetting::kBlockAllExtensions, true); |
| EXPECT_EQ(permissions_manager()->GetUserSiteSetting(url_origin), |
| UserSiteSetting::kBlockAllExtensions); |
| EXPECT_FALSE(DidInjectScript(web_contents())); |
| |
| // "block all extensions" -> "customize by extension (on site)": |
| // grants site access, refreshes the page and injects the script. |
| HandleUserSiteSettingModified(extension->id(), url_origin, |
| UserSiteSetting::kCustomizeByExtension, true); |
| EXPECT_EQ(permissions_manager()->GetUserSiteSetting(url_origin), |
| UserSiteSetting::kCustomizeByExtension); |
| EXPECT_TRUE(DidInjectScript(web_contents())); |
| |
| // "customize by extension (on site)" -> "block all extensions": |
| // revokes site access, refreshes the page and does not inject |
| // the script. |
| HandleUserSiteSettingModified(extension->id(), url_origin, |
| UserSiteSetting::kBlockAllExtensions, true); |
| EXPECT_EQ(permissions_manager()->GetUserSiteSetting(url_origin), |
| UserSiteSetting::kBlockAllExtensions); |
| EXPECT_FALSE(DidInjectScript(web_contents())); |
| |
| // "block all extensions" -> "grant all extensions": |
| // grants site access, refreshes the page and injects the script. |
| HandleUserSiteSettingModified(extension->id(), url_origin, |
| UserSiteSetting::kGrantAllExtensions, true); |
| EXPECT_EQ(permissions_manager()->GetUserSiteSetting(url_origin), |
| UserSiteSetting::kGrantAllExtensions); |
| EXPECT_TRUE(DidInjectScript(web_contents())); |
| |
| // "grant all extensions" -> "customize by extension (on site)": |
| // maintains current site access and keeps the script injected. No refresh is |
| // required. |
| HandleUserSiteSettingModified(extension->id(), url_origin, |
| UserSiteSetting::kCustomizeByExtension, false); |
| EXPECT_EQ(permissions_manager()->GetUserSiteSetting(url_origin), |
| UserSiteSetting::kCustomizeByExtension); |
| EXPECT_TRUE(DidInjectScript(web_contents())); |
| } |
| |
| // Tests changing user site settings when the extension does not have site |
| // access ('on click'). Note that we don't check if extension |
| // `WantsToRun` because on user-restricted sites, actions are blocked rather |
| // than withheld. |
| IN_PROC_BROWSER_TEST_F(ExtensionActionRunnerWithUserHostControlsBrowserTest, |
| HandleUserSiteSettingModified_ExtensionHasNoAccess) { |
| // Load an extension that wants to run on every page at document start. |
| const Extension* extension = LoadExtension( |
| test_data_dir_.AppendASCII("blocked_actions/content_scripts")); |
| ASSERT_TRUE(extension); |
| |
| // Withheld extension's host permission, so extension has "on click" site |
| // access. |
| ScriptingPermissionsModifier(profile(), extension) |
| .SetWithholdHostPermissions(true); |
| |
| // Navigate to a page where the extension wants to run. |
| const GURL url = embedded_test_server()->GetURL("/simple.html"); |
| const url::Origin url_origin = url::Origin::Create(url); |
| ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), url)); |
| |
| // The page should have "customize by extension" user site setting and "on |
| // click" site access. The extension should not have injected. |
| EXPECT_EQ(permissions_manager()->GetUserSiteSetting(url_origin), |
| UserSiteSetting::kCustomizeByExtension); |
| EXPECT_EQ(SitePermissionsHelper(profile()).GetSiteAccess(*extension, url), |
| SiteAccess::kOnClick); |
| EXPECT_FALSE(DidInjectScript(web_contents())); |
| |
| // "customize by extension (on click) -> "grant all extensions": |
| // grants site access, refreshes the page and injects the script. |
| HandleUserSiteSettingModified(extension->id(), url_origin, |
| UserSiteSetting::kGrantAllExtensions, true); |
| EXPECT_EQ(permissions_manager()->GetUserSiteSetting(url_origin), |
| UserSiteSetting::kGrantAllExtensions); |
| EXPECT_TRUE(DidInjectScript(web_contents())); |
| |
| // "grant all extensions" -> "block all extensions": |
| // not accepting the page reload bubble maintains the same user site |
| // setting and keeps the script injected. |
| HandleUserSiteSettingModified(extension->id(), url_origin, |
| UserSiteSetting::kBlockAllExtensions, false); |
| EXPECT_EQ(permissions_manager()->GetUserSiteSetting(url_origin), |
| UserSiteSetting::kGrantAllExtensions); |
| EXPECT_TRUE(DidInjectScript(web_contents())); |
| |
| // "grant all extensions" -> "block all extensions": |
| // accepting the page reload bubble revokes site access, refreshes the page |
| // and does not inject the script. (Note: we will accept all the next reload |
| // page bubbles since we previously checked the behavior of not accepting the |
| // dialog). |
| HandleUserSiteSettingModified(extension->id(), url_origin, |
| UserSiteSetting::kBlockAllExtensions, true); |
| EXPECT_EQ(permissions_manager()->GetUserSiteSetting(url_origin), |
| UserSiteSetting::kBlockAllExtensions); |
| EXPECT_FALSE(DidInjectScript(web_contents())); |
| |
| // "block all extensions" -> "customize by extension (on click)": |
| // maintains current site access, refreshes the page and still does not inject |
| // the script. |
| HandleUserSiteSettingModified(extension->id(), url_origin, |
| UserSiteSetting::kCustomizeByExtension, true); |
| EXPECT_EQ(permissions_manager()->GetUserSiteSetting(url_origin), |
| UserSiteSetting::kCustomizeByExtension); |
| EXPECT_FALSE(DidInjectScript(web_contents())); |
| |
| // "customize by extension (on click)" -> "block all extensions": |
| // maintains current site access, refreshes the page and still does not inject |
| // the script. |
| HandleUserSiteSettingModified(extension->id(), url_origin, |
| UserSiteSetting::kBlockAllExtensions, true); |
| EXPECT_EQ(permissions_manager()->GetUserSiteSetting(url_origin), |
| UserSiteSetting::kBlockAllExtensions); |
| EXPECT_FALSE(DidInjectScript(web_contents())); |
| |
| // "block all extensions" -> "grant all extensions": |
| // grants site access, refreshes the page and injects the script. |
| HandleUserSiteSettingModified(extension->id(), url_origin, |
| UserSiteSetting::kGrantAllExtensions, true); |
| EXPECT_EQ(permissions_manager()->GetUserSiteSetting(url_origin), |
| UserSiteSetting::kGrantAllExtensions); |
| EXPECT_TRUE(DidInjectScript(web_contents())); |
| |
| // "grant all extensions" -> "customize by extension (on click)": |
| // revokes site access, refreshes tha page and does not inject the script. |
| HandleUserSiteSettingModified(extension->id(), url_origin, |
| UserSiteSetting::kCustomizeByExtension, true); |
| EXPECT_EQ(permissions_manager()->GetUserSiteSetting(url_origin), |
| UserSiteSetting::kCustomizeByExtension); |
| EXPECT_FALSE(DidInjectScript(web_contents())); |
| } |
| |
| } // namespace extensions |