| // Copyright 2012 The Chromium Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "base/functional/bind.h" |
| #include "base/functional/callback_helpers.h" |
| #include "base/strings/stringprintf.h" |
| #include "base/strings/to_string.h" |
| #include "base/strings/utf_string_conversions.h" |
| #include "build/build_config.h" |
| #include "build/chromeos_buildflags.h" |
| #include "chrome/browser/bookmarks/bookmark_model_factory.h" |
| #include "chrome/browser/extensions/chrome_extension_test_notification_observer.h" |
| #include "chrome/browser/extensions/chrome_test_extension_loader.h" |
| #include "chrome/browser/extensions/extension_action_test_util.h" |
| #include "chrome/browser/extensions/extension_apitest.h" |
| #include "chrome/browser/extensions/extension_service.h" |
| #include "chrome/browser/extensions/extension_tab_util.h" |
| #include "chrome/browser/profiles/profile.h" |
| #include "components/bookmarks/browser/bookmark_model.h" |
| #include "components/bookmarks/common/bookmark_metrics.h" |
| #include "components/sessions/content/session_tab_helper.h" |
| #include "content/public/browser/storage_partition.h" |
| #include "content/public/test/browser_test.h" |
| #include "content/public/test/browser_test_utils.h" |
| #include "extensions/browser/api/declarative/rules_registry.h" |
| #include "extensions/browser/api/declarative/rules_registry_service.h" |
| #include "extensions/browser/api/declarative_webrequest/webrequest_constants.h" |
| #include "extensions/browser/browsertest_util.h" |
| #include "extensions/browser/extension_action.h" |
| #include "extensions/browser/extension_action_manager.h" |
| #include "extensions/browser/extension_registrar.h" |
| #include "extensions/browser/extension_registry.h" |
| #include "extensions/browser/extension_system.h" |
| #include "extensions/browser/rules_registry_ids.h" |
| #include "extensions/browser/test_extension_registry_observer.h" |
| #include "extensions/buildflags/buildflags.h" |
| #include "extensions/common/api/extension_action/action_info.h" |
| #include "extensions/common/api/extension_action/action_info_test_util.h" |
| #include "extensions/test/extension_test_message_listener.h" |
| #include "extensions/test/test_extension_dir.h" |
| #include "testing/gmock/include/gmock/gmock.h" |
| |
| static_assert(BUILDFLAG(ENABLE_EXTENSIONS_CORE)); |
| |
| namespace extensions { |
| namespace { |
| |
| static constexpr char kDeclarativeContentManifest[] = |
| R"({ |
| "name": "Declarative Content apitest", |
| "version": "0.1", |
| "manifest_version": 2, |
| "description": "end-to-end browser test for the declarative Content API", |
| "background": { |
| "scripts": ["background.js"], |
| "persistent": true |
| }, |
| "page_action": {}, |
| "permissions": ["declarativeContent", "bookmarks"], |
| "incognito": "%s" |
| })"; |
| |
| constexpr char kIncognitoSpecificBackground[] = |
| R"(var PageStateMatcher = chrome.declarativeContent.PageStateMatcher; |
| var ShowAction = chrome.declarativeContent.ShowAction; |
| var inIncognitoContext = chrome.extension.inIncognitoContext; |
| |
| var hostPrefix = chrome.extension.inIncognitoContext ? |
| 'test_split' : 'test_normal'; |
| var rule = { |
| conditions: [new PageStateMatcher({ |
| pageUrl: {hostPrefix: hostPrefix}})], |
| actions: [new ShowAction()] |
| }; |
| |
| var onPageChanged = chrome.declarativeContent.onPageChanged; |
| onPageChanged.removeRules(undefined, function() { |
| onPageChanged.addRules([rule], function() { |
| chrome.test.sendMessage( |
| !inIncognitoContext ? 'ready' : 'ready (split)'); |
| }); |
| });)"; |
| |
| constexpr char kBackgroundHelpers[] = |
| R"(var PageStateMatcher = chrome.declarativeContent.PageStateMatcher; |
| var ShowAction = chrome.declarativeContent.ShowAction; |
| var onPageChanged = chrome.declarativeContent.onPageChanged; |
| |
| function setRulesInPageEnvironment(rules, responseString) { |
| return new Promise(resolve => { |
| onPageChanged.removeRules(undefined, function() { |
| onPageChanged.addRules(rules, function() { |
| if (chrome.runtime.lastError) { |
| resolve(chrome.runtime.lastError.message); |
| return; |
| } |
| resolve(responseString); |
| }); |
| }); |
| }); |
| }; |
| |
| function setRules(rules, responseString) { |
| onPageChanged.removeRules(undefined, function() { |
| onPageChanged.addRules(rules, function() { |
| if (chrome.runtime.lastError) { |
| chrome.test.sendScriptResult(chrome.runtime.lastError.message); |
| return; |
| } |
| chrome.test.sendScriptResult(responseString); |
| }); |
| }); |
| }; |
| |
| function addRules(rules, responseString) { |
| onPageChanged.addRules(rules, function() { |
| if (chrome.runtime.lastError) { |
| chrome.test.sendScriptResult(chrome.runtime.lastError.message); |
| return; |
| } |
| chrome.test.sendScriptResult(responseString); |
| }); |
| }; |
| |
| function removeRule(id, responseString) { |
| onPageChanged.removeRules([id], function() { |
| if (chrome.runtime.lastError) { |
| chrome.test.sendScriptResult(chrome.runtime.lastError.message); |
| return; |
| } |
| chrome.test.sendScriptResult(responseString); |
| }); |
| };)"; |
| |
| #if BUILDFLAG(ENABLE_EXTENSIONS) |
| constexpr char kRulesExtensionName[] = |
| "Declarative content persistence apitest"; |
| #endif |
| |
| using ContextType = extensions::browser_test_util::ContextType; |
| |
| class DeclarativeContentApiTest : public ExtensionApiTest { |
| public: |
| explicit DeclarativeContentApiTest( |
| ContextType context_type = ContextType::kNone) |
| : ExtensionApiTest(context_type) {} |
| |
| DeclarativeContentApiTest(const DeclarativeContentApiTest&) = delete; |
| DeclarativeContentApiTest& operator=(const DeclarativeContentApiTest&) = |
| delete; |
| |
| protected: |
| enum IncognitoMode { SPANNING, SPLIT }; |
| |
| // Checks that the rules are correctly evaluated for an extension in incognito |
| // mode |mode| when the extension's incognito enable state is |
| // |is_enabled_in_incognito|. |
| void CheckIncognito(IncognitoMode mode, bool is_enabled_in_incognito); |
| |
| // Checks that the rules matching a bookmarked state of |is_bookmarked| are |
| // correctly evaluated on bookmark events. |
| void CheckBookmarkEvents(bool is_bookmarked); |
| |
| static std::string FormatManifest(IncognitoMode mode); |
| |
| TestExtensionDir ext_dir_; |
| }; |
| |
| std::string DeclarativeContentApiTest::FormatManifest(IncognitoMode mode) { |
| const char* const mode_string = mode == SPANNING ? "spanning" : "split"; |
| return base::StringPrintf(kDeclarativeContentManifest, mode_string); |
| } |
| |
| void DeclarativeContentApiTest::CheckIncognito(IncognitoMode mode, |
| bool is_enabled_in_incognito) { |
| std::string manifest = FormatManifest(mode); |
| ext_dir_.WriteManifest(manifest); |
| ext_dir_.WriteFile(FILE_PATH_LITERAL("background.js"), |
| kIncognitoSpecificBackground); |
| |
| ExtensionTestMessageListener ready("ready"); |
| ExtensionTestMessageListener ready_incognito("ready (split)"); |
| |
| const Extension* extension = LoadExtension( |
| ext_dir_.UnpackedPath(), {.allow_in_incognito = is_enabled_in_incognito}); |
| ASSERT_TRUE(extension); |
| |
| // Cache the profile and active tab before opening the incognito tab. |
| Profile* regular_profile = profile(); |
| ASSERT_FALSE(regular_profile->IsOffTheRecord()); |
| content::WebContents* regular_tab = GetActiveWebContents(); |
| ASSERT_FALSE(regular_tab->GetBrowserContext()->IsOffTheRecord()); |
| |
| content::WebContents* incognito_tab = |
| PlatformOpenURLOffTheRecord(regular_profile, GURL("about:blank")); |
| const ExtensionAction* incognito_action = |
| ExtensionActionManager::Get(incognito_tab->GetBrowserContext()) |
| ->GetExtensionAction(*extension); |
| ASSERT_TRUE(incognito_action); |
| |
| ASSERT_TRUE(ready.WaitUntilSatisfied()); |
| if (is_enabled_in_incognito && mode == SPLIT) |
| ASSERT_TRUE(ready_incognito.WaitUntilSatisfied()); |
| |
| const int incognito_tab_id = ExtensionTabUtil::GetTabId(incognito_tab); |
| |
| EXPECT_FALSE(incognito_action->GetIsVisible(incognito_tab_id)); |
| |
| // TODO(crbug.com/40764017): Understand why these are EXPECT_FALSE. |
| EXPECT_FALSE(NavigateInRenderer(incognito_tab, GURL("http://test_split/"))); |
| if (mode == SPLIT) { |
| EXPECT_EQ(is_enabled_in_incognito, |
| incognito_action->GetIsVisible(incognito_tab_id)); |
| } else { |
| EXPECT_FALSE(incognito_action->GetIsVisible(incognito_tab_id)); |
| } |
| |
| EXPECT_FALSE(NavigateInRenderer(incognito_tab, GURL("http://test_normal/"))); |
| if (mode != SPLIT) { |
| EXPECT_EQ(is_enabled_in_incognito, |
| incognito_action->GetIsVisible(incognito_tab_id)); |
| } else { |
| EXPECT_FALSE(incognito_action->GetIsVisible(incognito_tab_id)); |
| } |
| |
| // Verify that everything works as expected in the non-incognito browser. |
| const ExtensionAction* action = ExtensionActionManager::Get(regular_profile) |
| ->GetExtensionAction(*extension); |
| const int tab_id = ExtensionTabUtil::GetTabId(regular_tab); |
| |
| EXPECT_FALSE(action->GetIsVisible(tab_id)); |
| |
| EXPECT_FALSE(NavigateInRenderer(regular_tab, GURL("http://test_normal/"))); |
| EXPECT_TRUE(action->GetIsVisible(tab_id)); |
| |
| EXPECT_FALSE(NavigateInRenderer(regular_tab, GURL("http://test_split/"))); |
| EXPECT_FALSE(action->GetIsVisible(tab_id)); |
| } |
| |
| void DeclarativeContentApiTest::CheckBookmarkEvents(bool match_is_bookmarked) { |
| ext_dir_.WriteManifest(FormatManifest(SPANNING)); |
| ext_dir_.WriteFile(FILE_PATH_LITERAL("background.js"), kBackgroundHelpers); |
| |
| content::WebContents* const tab = GetActiveWebContents(); |
| const int tab_id = ExtensionTabUtil::GetTabId(tab); |
| |
| const Extension* extension = LoadExtension(ext_dir_.UnpackedPath()); |
| ASSERT_TRUE(extension); |
| const ExtensionAction* action = |
| ExtensionActionManager::Get(profile())->GetExtensionAction(*extension); |
| ASSERT_TRUE(action); |
| |
| // TODO(crbug.com/40764017): Understand why these are EXPECT_FALSE. |
| EXPECT_FALSE(NavigateInRenderer(tab, GURL("http://test1/"))); |
| EXPECT_FALSE(action->GetIsVisible(tab_id)); |
| |
| static constexpr char kSetIsBookmarkedRule[] = |
| R"(setRules([{ |
| conditions: [new PageStateMatcher({isBookmarked: %s})], |
| actions: [new ShowAction()] |
| }], 'test_rule');)"; |
| |
| EXPECT_EQ("test_rule", |
| ExecuteScriptInBackgroundPage( |
| extension->id(), |
| base::StringPrintf(kSetIsBookmarkedRule, |
| base::ToString(match_is_bookmarked)))); |
| EXPECT_EQ(!match_is_bookmarked, action->GetIsVisible(tab_id)); |
| |
| // Check rule evaluation on add/remove bookmark. |
| bookmarks::BookmarkModel* bookmark_model = |
| BookmarkModelFactory::GetForBrowserContext(profile()); |
| const bookmarks::BookmarkNode* node = bookmark_model->AddURL( |
| bookmark_model->other_node(), 0, u"title", GURL("http://test1/")); |
| EXPECT_EQ(match_is_bookmarked, action->GetIsVisible(tab_id)); |
| |
| bookmark_model->Remove( |
| node, bookmarks::metrics::BookmarkEditSource::kExtension, FROM_HERE); |
| EXPECT_EQ(!match_is_bookmarked, action->GetIsVisible(tab_id)); |
| |
| // Check rule evaluation on navigate to bookmarked and non-bookmarked URL. |
| bookmark_model->AddURL(bookmark_model->other_node(), 0, u"title", |
| GURL("http://test2/")); |
| |
| EXPECT_FALSE(NavigateInRenderer(tab, GURL("http://test2/"))); |
| EXPECT_EQ(match_is_bookmarked, action->GetIsVisible(tab_id)); |
| |
| EXPECT_FALSE(NavigateInRenderer(tab, GURL("http://test3/"))); |
| EXPECT_EQ(!match_is_bookmarked, action->GetIsVisible(tab_id)); |
| } |
| |
| class DeclarativeContentApiTestWithContextType |
| : public DeclarativeContentApiTest, |
| public testing::WithParamInterface<ContextType> { |
| public: |
| DeclarativeContentApiTestWithContextType() |
| : DeclarativeContentApiTest(GetParam()) {} |
| |
| DeclarativeContentApiTestWithContextType( |
| const DeclarativeContentApiTestWithContextType&) = delete; |
| DeclarativeContentApiTestWithContextType& operator=( |
| const DeclarativeContentApiTestWithContextType&) = delete; |
| }; |
| |
| #if !BUILDFLAG(IS_ANDROID) |
| // Android only supports service worker. |
| INSTANTIATE_TEST_SUITE_P(PersistentBackground, |
| DeclarativeContentApiTestWithContextType, |
| ::testing::Values(ContextType::kPersistentBackground)); |
| #endif |
| // These tests use page_action, which is unavailable in MV3. |
| INSTANTIATE_TEST_SUITE_P(ServiceWorker, |
| DeclarativeContentApiTestWithContextType, |
| ::testing::Values(ContextType::kServiceWorkerMV2)); |
| |
| // TODO(crbug.com/40260777): Convert this to run in both modes. |
| IN_PROC_BROWSER_TEST_F(DeclarativeContentApiTest, Overview) { |
| ext_dir_.WriteManifest(FormatManifest(SPANNING)); |
| ext_dir_.WriteFile( |
| FILE_PATH_LITERAL("background.js"), |
| R"(var declarative = chrome.declarative; |
| |
| var PageStateMatcher = chrome.declarativeContent.PageStateMatcher; |
| var ShowAction = chrome.declarativeContent.ShowAction; |
| |
| var rule = { |
| conditions: [new PageStateMatcher({pageUrl: {hostPrefix: 'test1'}}), |
| new PageStateMatcher({ |
| css: ["input[type='password']"]})], |
| actions: [new ShowAction()] |
| } |
| |
| var testEvent = chrome.declarativeContent.onPageChanged; |
| |
| testEvent.removeRules(undefined, function() { |
| testEvent.addRules([rule], function() { |
| chrome.test.sendMessage('ready') |
| }); |
| });)"); |
| ExtensionTestMessageListener ready("ready"); |
| const Extension* extension = LoadExtension(ext_dir_.UnpackedPath()); |
| ASSERT_TRUE(extension); |
| const ExtensionAction* action = |
| ExtensionActionManager::Get(profile())->GetExtensionAction(*extension); |
| ASSERT_TRUE(action); |
| |
| ASSERT_TRUE(ready.WaitUntilSatisfied()); |
| content::WebContents* const tab = GetActiveWebContents(); |
| const int tab_id = ExtensionTabUtil::GetTabId(tab); |
| |
| // Observer to track page action visibility. This helps avoid |
| // flakes by waiting to check visibility until there is an |
| // actual update to the page action. |
| ChromeExtensionTestNotificationObserver test_observer(profile()); |
| |
| // TODO(crbug.com/40764017): Understand why these are EXPECT_FALSE. |
| EXPECT_FALSE(NavigateInRenderer(tab, GURL("http://test1/"))); |
| // The declarative API should show the page action instantly, rather |
| // than waiting for the extension to run. |
| test_observer.WaitForPageActionVisibilityChangeTo(tab, 1); |
| EXPECT_TRUE(action->GetIsVisible(tab_id)); |
| |
| // Make sure leaving a matching page unshows the page action. |
| EXPECT_FALSE(NavigateInRenderer(tab, GURL("http://not_checked/"))); |
| test_observer.WaitForPageActionVisibilityChangeTo(tab, 0); |
| EXPECT_FALSE(action->GetIsVisible(tab_id)); |
| |
| // Insert a password field to make sure that's noticed. |
| // Notice that we touch offsetTop to force a synchronous layout. |
| ASSERT_TRUE(content::ExecJs( |
| tab, R"(document.body.innerHTML = '<input type="password">'; |
| document.body.offsetTop;)")); |
| |
| test_observer.WaitForPageActionVisibilityChangeTo(tab, 1); |
| EXPECT_TRUE(action->GetIsVisible(tab_id)) |
| << "Adding a matching element should show the page action."; |
| |
| // Remove it again to make sure that reverts the action. |
| // Notice that we touch offsetTop to force a synchronous layout. |
| ASSERT_TRUE(content::ExecJs(tab, R"(document.body.innerHTML = 'Hello world'; |
| document.body.offsetTop;)")); |
| |
| test_observer.WaitForPageActionVisibilityChangeTo(tab, 0); |
| EXPECT_FALSE(action->GetIsVisible(tab_id)) |
| << "Removing the matching element should hide the page action again."; |
| } |
| |
| // Test that adds two rules pointing to single action instance. |
| // Regression test for http://crbug.com/574149. |
| IN_PROC_BROWSER_TEST_P(DeclarativeContentApiTestWithContextType, |
| ReusedActionInstance) { |
| static constexpr char kBackgroundScript[] = |
| R"(var declarative = chrome.declarative; |
| |
| var PageStateMatcher = chrome.declarativeContent.PageStateMatcher; |
| var ShowAction = chrome.declarativeContent.ShowAction; |
| var actionInstance = new ShowAction(); |
| |
| var rule1 = { |
| conditions: [new PageStateMatcher({pageUrl: {hostPrefix: 'test1'}})], |
| actions: [actionInstance] |
| }; |
| var rule2 = { |
| conditions: [new PageStateMatcher({pageUrl: {hostPrefix: 'test'}})], |
| actions: [actionInstance] |
| }; |
| |
| var testEvent = chrome.declarativeContent.onPageChanged; |
| |
| testEvent.removeRules(undefined, function() { |
| testEvent.addRules([rule1, rule2], function() { |
| chrome.test.sendMessage('ready'); |
| }); |
| });)"; |
| ext_dir_.WriteManifest(FormatManifest(SPANNING)); |
| ext_dir_.WriteFile(FILE_PATH_LITERAL("background.js"), kBackgroundScript); |
| |
| ExtensionTestMessageListener ready("ready"); |
| const Extension* extension = LoadExtension(ext_dir_.UnpackedPath()); |
| ASSERT_TRUE(extension); |
| // Wait for declarative rules to be set up. |
| profile()->GetDefaultStoragePartition()->FlushNetworkInterfaceForTesting(); |
| const ExtensionAction* action = |
| ExtensionActionManager::Get(profile())->GetExtensionAction(*extension); |
| ASSERT_TRUE(action); |
| |
| ASSERT_TRUE(ready.WaitUntilSatisfied()); |
| content::WebContents* const tab = GetActiveWebContents(); |
| const int tab_id = ExtensionTabUtil::GetTabId(tab); |
| |
| // This navigation matches both rules. |
| // TODO(crbug.com/40764017): Understand why this is EXPECT_FALSE. |
| EXPECT_FALSE(NavigateInRenderer(tab, GURL("http://test1/"))); |
| |
| EXPECT_TRUE(action->GetIsVisible(tab_id)); |
| } |
| |
| // Tests that the rules are evaluated at the time they are added or removed. |
| IN_PROC_BROWSER_TEST_P(DeclarativeContentApiTestWithContextType, |
| RulesEvaluatedOnAddRemove) { |
| ext_dir_.WriteManifest(FormatManifest(SPANNING)); |
| ext_dir_.WriteFile(FILE_PATH_LITERAL("background.js"), kBackgroundHelpers); |
| const Extension* extension = LoadExtension(ext_dir_.UnpackedPath()); |
| ASSERT_TRUE(extension); |
| const ExtensionAction* action = |
| ExtensionActionManager::Get(profile())->GetExtensionAction(*extension); |
| ASSERT_TRUE(action); |
| |
| content::WebContents* const tab = GetActiveWebContents(); |
| const int tab_id = ExtensionTabUtil::GetTabId(tab); |
| |
| // TODO(crbug.com/40764017): Understand why these are EXPECT_FALSE. |
| EXPECT_FALSE(NavigateInRenderer(tab, GURL("http://test1/"))); |
| |
| const std::string kAddTestRules = |
| R"(addRules([{ |
| id: '1', |
| conditions: [new PageStateMatcher({pageUrl: {hostPrefix: 'test1'}})], |
| actions: [new ShowAction()] |
| }, { |
| id: '2', |
| conditions: [new PageStateMatcher({pageUrl: {hostPrefix: 'test2'}})], |
| actions: [new ShowAction()] |
| }], 'add_rules');)"; |
| EXPECT_EQ("add_rules", |
| ExecuteScriptInBackgroundPage(extension->id(), kAddTestRules)); |
| |
| EXPECT_TRUE(action->GetIsVisible(tab_id)); |
| |
| const std::string kRemoveTestRule1 = "removeRule('1', 'remove_rule1');"; |
| EXPECT_EQ("remove_rule1", |
| ExecuteScriptInBackgroundPage(extension->id(), kRemoveTestRule1)); |
| |
| EXPECT_FALSE(action->GetIsVisible(tab_id)); |
| |
| EXPECT_FALSE(NavigateInRenderer(tab, GURL("http://test2/"))); |
| |
| EXPECT_TRUE(action->GetIsVisible(tab_id)); |
| |
| const std::string kRemoveTestRule2 = "removeRule('2', 'remove_rule2');"; |
| EXPECT_EQ("remove_rule2", |
| ExecuteScriptInBackgroundPage(extension->id(), kRemoveTestRule2)); |
| |
| EXPECT_FALSE(action->GetIsVisible(tab_id)); |
| } |
| |
| struct ShowActionParams { |
| constexpr ShowActionParams(const char* show_type, ContextType context_type) |
| : show_type(show_type), context_type(context_type) {} |
| const char* show_type; |
| ContextType context_type; |
| }; |
| |
| class ParameterizedShowActionDeclarativeContentApiTest |
| : public DeclarativeContentApiTest, |
| public testing::WithParamInterface<ShowActionParams> { |
| public: |
| ParameterizedShowActionDeclarativeContentApiTest() |
| : DeclarativeContentApiTest(GetParam().context_type) {} |
| |
| ParameterizedShowActionDeclarativeContentApiTest( |
| const ParameterizedShowActionDeclarativeContentApiTest&) = delete; |
| ParameterizedShowActionDeclarativeContentApiTest& operator=( |
| const ParameterizedShowActionDeclarativeContentApiTest&) = delete; |
| |
| ~ParameterizedShowActionDeclarativeContentApiTest() override = default; |
| |
| void TestShowAction(std::optional<ActionInfo::Type> action_type); |
| }; |
| |
| void ParameterizedShowActionDeclarativeContentApiTest::TestShowAction( |
| std::optional<ActionInfo::Type> action_type) { |
| static constexpr char kManifestTemplate[] = |
| R"({ |
| "name": "Declarative Content Show Action", |
| "version": "0.1", |
| "manifest_version": %d, |
| %s |
| "permissions": ["declarativeContent"] |
| })"; |
| std::string action_declaration; |
| int manifest_version = 2; |
| if (action_type) { |
| action_declaration = base::StringPrintf( |
| R"("%s": {},)", ActionInfo::GetManifestKeyForActionType(*action_type)); |
| manifest_version = GetManifestVersionForActionType(*action_type); |
| } |
| |
| // Since this test uses the action API (which is restricted to MV3), we drive |
| // the interaction through pages, rather than the background script. |
| ext_dir_.WriteManifest(base::StringPrintf(kManifestTemplate, manifest_version, |
| action_declaration.c_str())); |
| ext_dir_.WriteFile(FILE_PATH_LITERAL("page.html"), |
| R"("<html><script src="page.js"></script></html>)"); |
| ext_dir_.WriteFile(FILE_PATH_LITERAL("page.js"), kBackgroundHelpers); |
| |
| ChromeTestExtensionLoader loader(profile()); |
| scoped_refptr<const Extension> extension = |
| loader.LoadExtension(ext_dir_.UnpackedPath()); |
| ASSERT_TRUE(extension); |
| // Wait for declarative rules to be set up. |
| profile()->GetDefaultStoragePartition()->FlushNetworkInterfaceForTesting(); |
| |
| ExtensionAction* action = |
| ExtensionActionManager::Get(profile())->GetExtensionAction(*extension); |
| // Extensions that don't provide an action are given a page action by default |
| // (for visibility reasons). |
| ASSERT_TRUE(action); |
| |
| // Ensure actions are hidden (so that the ShowAction() rule has an effect). |
| if (action->default_state() == ActionInfo::DefaultState::kDisabled) { |
| action->SetIsVisible(ExtensionAction::kDefaultTabId, false); |
| } |
| |
| // Open the tab to invoke the APIs, as well as test the action visibility. |
| content::WebContents* tab = GetActiveWebContents(); |
| ASSERT_TRUE(NavigateToURL(tab, extension->GetResourceURL("page.html"))); |
| |
| static constexpr char kScript[] = |
| R"(setRulesInPageEnvironment([{ |
| conditions: [new PageStateMatcher({pageUrl: {hostPrefix: 'test'}})], |
| actions: [new chrome.declarativeContent.%s()] |
| }], 'test_rule');)"; |
| static constexpr char kSuccessStr[] = "test_rule"; |
| |
| std::string result = |
| content::EvalJs(tab, base::StringPrintf(kScript, GetParam().show_type)) |
| .ExtractString(); |
| |
| // Since extensions with no action provided are given a page action by default |
| // (for visibility reasons) and ShowAction() should also work with |
| // browser actions, both of these should pass. |
| EXPECT_THAT(result, testing::HasSubstr(kSuccessStr)); |
| |
| // TODO(crbug.com/40764017): Understand why this is EXPECT_FALSE. |
| EXPECT_FALSE(NavigateInRenderer(tab, GURL("http://test/"))); |
| |
| const int tab_id = sessions::SessionTabHelper::IdForTab(tab).id(); |
| EXPECT_TRUE(action->GetIsVisible(tab_id)); |
| |
| // If an extension had no action specified in the manifest, it will get a |
| // synthesized page action. |
| ActionInfo::Type expected_type = |
| action_type.value_or(ActionInfo::Type::kPage); |
| EXPECT_EQ(expected_type, action->action_type()); |
| EXPECT_EQ(expected_type == ActionInfo::Type::kPage ? 1u : 0u, |
| extension_action_test_util::GetActivePageActionCount(tab)); |
| } |
| |
| IN_PROC_BROWSER_TEST_P(ParameterizedShowActionDeclarativeContentApiTest, |
| NoActionInManifest) { |
| TestShowAction(std::nullopt); |
| } |
| |
| IN_PROC_BROWSER_TEST_P(ParameterizedShowActionDeclarativeContentApiTest, |
| PageActionInManifest) { |
| TestShowAction(ActionInfo::Type::kPage); |
| } |
| |
| IN_PROC_BROWSER_TEST_P(ParameterizedShowActionDeclarativeContentApiTest, |
| BrowserActionInManifest) { |
| TestShowAction(ActionInfo::Type::kBrowser); |
| } |
| |
| IN_PROC_BROWSER_TEST_P(ParameterizedShowActionDeclarativeContentApiTest, |
| ActionInManifest) { |
| TestShowAction(ActionInfo::Type::kAction); |
| } |
| |
| // Tests that rules from manifest are added and evaluated properly. |
| IN_PROC_BROWSER_TEST_P(ParameterizedShowActionDeclarativeContentApiTest, |
| RulesAddedFromManifest) { |
| static constexpr char manifest[] = |
| R"({ |
| "name": "Declarative Content apitest", |
| "version": "0.1", |
| "manifest_version": 2, |
| "page_action": {}, |
| "permissions": ["declarativeContent"], |
| "event_rules": [{ |
| "event": "declarativeContent.onPageChanged", |
| "actions": [{ |
| "type": "declarativeContent.%s" |
| }], |
| "conditions": [{ |
| "type": "declarativeContent.PageStateMatcher", |
| "pageUrl": {"hostPrefix": "test1"} |
| }] |
| }] |
| })"; |
| ext_dir_.WriteManifest(base::StringPrintf(manifest, GetParam().show_type)); |
| const Extension* extension = LoadExtension(ext_dir_.UnpackedPath()); |
| ASSERT_TRUE(extension); |
| const ExtensionAction* action = |
| ExtensionActionManager::Get(profile())->GetExtensionAction(*extension); |
| ASSERT_TRUE(action); |
| |
| content::WebContents* const tab = GetActiveWebContents(); |
| const int tab_id = ExtensionTabUtil::GetTabId(tab); |
| |
| // TODO(crbug.com/40764017): Understand why these are EXPECT_FALSE. |
| EXPECT_FALSE(NavigateInRenderer(tab, GURL("http://blank/"))); |
| EXPECT_FALSE(action->GetIsVisible(tab_id)); |
| EXPECT_FALSE(NavigateInRenderer(tab, GURL("http://test1/"))); |
| EXPECT_TRUE(action->GetIsVisible(tab_id)); |
| EXPECT_FALSE(NavigateInRenderer(tab, GURL("http://test2/"))); |
| EXPECT_FALSE(action->GetIsVisible(tab_id)); |
| } |
| |
| #if !BUILDFLAG(IS_ANDROID) |
| // ShowPageAction() is deprecated and isn't tested on Android. |
| INSTANTIATE_TEST_SUITE_P( |
| LegacyShowActionKey_PB, |
| ParameterizedShowActionDeclarativeContentApiTest, |
| ::testing::Values(ShowActionParams("ShowPageAction", |
| ContextType::kPersistentBackground))); |
| #endif |
| |
| INSTANTIATE_TEST_SUITE_P( |
| ModernShowActionKey_PB, |
| ParameterizedShowActionDeclarativeContentApiTest, |
| ::testing::Values(ShowActionParams("ShowAction", |
| ContextType::kPersistentBackground))); |
| |
| // Tests that rules are not evaluated in incognito browser windows when the |
| // extension specifies spanning incognito mode but is not enabled for incognito. |
| IN_PROC_BROWSER_TEST_P(DeclarativeContentApiTestWithContextType, |
| DisabledForSpanningIncognito) { |
| CheckIncognito(SPANNING, false); |
| } |
| |
| // Tests that rules are evaluated in incognito browser windows when the |
| // extension specifies spanning incognito mode and is enabled for incognito. |
| IN_PROC_BROWSER_TEST_P(DeclarativeContentApiTestWithContextType, |
| EnabledForSpanningIncognito) { |
| CheckIncognito(SPANNING, true); |
| } |
| |
| // Tests that rules are not evaluated in incognito browser windows when the |
| // extension specifies split incognito mode but is not enabled for incognito. |
| IN_PROC_BROWSER_TEST_P(DeclarativeContentApiTestWithContextType, |
| DisabledForSplitIncognito) { |
| CheckIncognito(SPLIT, false); |
| } |
| |
| // Tests that rules are evaluated in incognito browser windows when the |
| // extension specifies split incognito mode and is enabled for incognito. |
| IN_PROC_BROWSER_TEST_P(DeclarativeContentApiTestWithContextType, |
| EnabledForSplitIncognito) { |
| CheckIncognito(SPLIT, true); |
| } |
| |
| // Tests that rules are evaluated for an incognito tab that exists at the time |
| // the rules are added. |
| IN_PROC_BROWSER_TEST_P(DeclarativeContentApiTestWithContextType, |
| RulesEvaluatedForExistingIncognitoTab) { |
| content::WebContents* const incognito_tab = |
| PlatformOpenURLOffTheRecord(profile(), GURL("about:blank")); |
| const int incognito_tab_id = ExtensionTabUtil::GetTabId(incognito_tab); |
| |
| // TODO(crbug.com/40764017): Understand why this is EXPECT_FALSE. |
| EXPECT_FALSE(NavigateInRenderer(incognito_tab, GURL("http://test_normal/"))); |
| |
| ext_dir_.WriteManifest(FormatManifest(SPANNING)); |
| ext_dir_.WriteFile(FILE_PATH_LITERAL("background.js"), |
| kIncognitoSpecificBackground); |
| ExtensionTestMessageListener ready("ready"); |
| const Extension* extension = |
| LoadExtension(ext_dir_.UnpackedPath(), {.allow_in_incognito = true}); |
| ASSERT_TRUE(extension); |
| ASSERT_TRUE(ready.WaitUntilSatisfied()); |
| |
| const ExtensionAction* incognito_action = |
| ExtensionActionManager::Get(incognito_tab->GetBrowserContext()) |
| ->GetExtensionAction(*extension); |
| ASSERT_TRUE(incognito_action); |
| |
| // The page action should be shown. |
| EXPECT_TRUE(incognito_action->GetIsVisible(incognito_tab_id)); |
| } |
| |
| // TODO(crbug.com/40488499): Android does not support PRE_ steps. |
| #if BUILDFLAG(ENABLE_EXTENSIONS) |
| // TODO(crbug.com/41189874): Flaky on Windows release builds. |
| #if BUILDFLAG(IS_WIN) && defined(NDEBUG) |
| #define MAYBE_PRE_RulesPersistence DISABLED_PRE_RulesPersistence |
| #else |
| #define MAYBE_PRE_RulesPersistence PRE_RulesPersistence |
| #endif |
| // Sets up rules matching http://test1/ in a normal and incognito browser. |
| IN_PROC_BROWSER_TEST_P(DeclarativeContentApiTestWithContextType, |
| MAYBE_PRE_RulesPersistence) { |
| ExtensionTestMessageListener ready("ready"); |
| ExtensionTestMessageListener ready_split("ready (split)"); |
| // An on-disk extension is required so that it can be reloaded later in the |
| // RulesPersistence test. |
| const Extension* extension = |
| LoadExtension(test_data_dir_.AppendASCII("declarative_content") |
| .AppendASCII("persistence"), |
| {.allow_in_incognito = true}); |
| ASSERT_TRUE(extension); |
| ASSERT_EQ(kRulesExtensionName, extension->name()); |
| ASSERT_TRUE(ready.WaitUntilSatisfied()); |
| |
| PlatformOpenURLOffTheRecord(profile(), GURL("about:blank")); |
| ASSERT_TRUE(ready_split.WaitUntilSatisfied()); |
| } |
| |
| // TODO(crbug.com/41189874): Flaky on Windows release builds. |
| #if BUILDFLAG(IS_WIN) && defined(NDEBUG) |
| #define MAYBE_RulesPersistence DISABLED_RulesPersistence |
| #else |
| #define MAYBE_RulesPersistence RulesPersistence |
| #endif |
| // Reloads the extension from PRE_RulesPersistence and checks that the rules |
| // continue to work as expected after being persisted and reloaded. |
| IN_PROC_BROWSER_TEST_P(DeclarativeContentApiTestWithContextType, |
| MAYBE_RulesPersistence) { |
| const Extension* extension = GetSingleLoadedExtension(); |
| // TODO(crbug.com/40200835): On desktop Android this assert fails because the |
| // extension was not loaded. It's not clear why. |
| ASSERT_TRUE(extension) << message_; |
| ASSERT_EQ(kRulesExtensionName, extension->name()); |
| |
| // Check non-incognito browser. |
| content::WebContents* const tab = GetActiveWebContents(); |
| const int tab_id = ExtensionTabUtil::GetTabId(tab); |
| |
| // Observer to track page action visibility. This helps avoid |
| // flakes by waiting to check visibility until there is an |
| // actual update to the page action. |
| ChromeExtensionTestNotificationObserver test_observer(profile()); |
| |
| const ExtensionAction* action = |
| ExtensionActionManager::Get(profile())->GetExtensionAction(*extension); |
| ASSERT_TRUE(action); |
| EXPECT_FALSE(action->GetIsVisible(tab_id)); |
| |
| // TODO(crbug.com/40764017): Understand why these are EXPECT_FALSE. |
| EXPECT_FALSE(NavigateInRenderer(tab, GURL("http://test_normal/"))); |
| test_observer.WaitForPageActionVisibilityChangeTo(tab, 1); |
| EXPECT_TRUE(action->GetIsVisible(tab_id)); |
| |
| EXPECT_FALSE(NavigateInRenderer(tab, GURL("http://test_split/"))); |
| test_observer.WaitForPageActionVisibilityChangeTo(tab, 0); |
| EXPECT_FALSE(action->GetIsVisible(tab_id)); |
| |
| ExtensionTestMessageListener ready_split("second run ready (split)"); |
| |
| // Check incognito browser. |
| content::WebContents* const incognito_tab = |
| PlatformOpenURLOffTheRecord(profile(), GURL("about:blank")); |
| ASSERT_TRUE(ready_split.WaitUntilSatisfied()); |
| const int incognito_tab_id = ExtensionTabUtil::GetTabId(incognito_tab); |
| |
| ChromeExtensionTestNotificationObserver incognito_test_observer( |
| incognito_tab->GetBrowserContext()); |
| |
| const ExtensionAction* incognito_action = |
| ExtensionActionManager::Get(incognito_tab->GetBrowserContext()) |
| ->GetExtensionAction(*extension); |
| ASSERT_TRUE(incognito_action); |
| |
| EXPECT_FALSE(NavigateInRenderer(incognito_tab, GURL("http://test_split/"))); |
| incognito_test_observer.WaitForPageActionVisibilityChangeTo(incognito_tab, 1); |
| EXPECT_TRUE(incognito_action->GetIsVisible(incognito_tab_id)); |
| |
| EXPECT_FALSE(NavigateInRenderer(incognito_tab, GURL("http://test_normal/"))); |
| incognito_test_observer.WaitForPageActionVisibilityChangeTo(incognito_tab, 0); |
| EXPECT_FALSE(incognito_action->GetIsVisible(incognito_tab_id)); |
| } |
| #endif // BUILDFLAG(ENABLE_EXTENSIONS) |
| |
| // http://crbug.com/304373 |
| IN_PROC_BROWSER_TEST_P(DeclarativeContentApiTestWithContextType, |
| UninstallWhileActivePageAction) { |
| ext_dir_.WriteManifest(FormatManifest(SPANNING)); |
| std::string script = |
| kBackgroundHelpers + std::string("\nchrome.test.sendMessage('ready');"); |
| ext_dir_.WriteFile(FILE_PATH_LITERAL("background.js"), script); |
| ExtensionTestMessageListener ready_listener("ready"); |
| const Extension* extension = LoadExtension(ext_dir_.UnpackedPath()); |
| ASSERT_TRUE(extension); |
| ASSERT_TRUE(ready_listener.WaitUntilSatisfied()); |
| |
| const std::string extension_id = extension->id(); |
| const ExtensionAction* action = |
| ExtensionActionManager::Get(profile())->GetExtensionAction(*extension); |
| ASSERT_TRUE(action); |
| |
| const std::string kTestRule = |
| R"(setRules([{ |
| conditions: [new PageStateMatcher({pageUrl: {hostPrefix: 'test'}})], |
| actions: [new ShowAction()] |
| }], 'test_rule');)"; |
| EXPECT_EQ("test_rule", |
| ExecuteScriptInBackgroundPage(extension_id, kTestRule)); |
| |
| content::WebContents* const tab = GetActiveWebContents(); |
| const int tab_id = ExtensionTabUtil::GetTabId(tab); |
| |
| // TODO(crbug.com/40764017): Understand why these are EXPECT_FALSE. |
| EXPECT_FALSE(NavigateInRenderer(tab, GURL("http://test/"))); |
| |
| EXPECT_TRUE(action->GetIsVisible(tab_id)); |
| EXPECT_TRUE(WaitForPageActionVisibilityChangeTo(1)); |
| EXPECT_EQ(1u, extension_action_test_util::GetActivePageActionCount(tab)); |
| EXPECT_EQ(1u, extension_action_test_util::GetTotalPageActionCount(tab)); |
| |
| ExtensionTestMessageListener reload_ready_listener("ready"); |
| ReloadExtension(extension_id); // Invalidates action and extension. |
| ASSERT_TRUE(reload_ready_listener.WaitUntilSatisfied()); |
| |
| EXPECT_EQ("test_rule", |
| ExecuteScriptInBackgroundPage(extension_id, kTestRule)); |
| // TODO(jyasskin): Apply new rules to existing tabs, without waiting for a |
| // navigation. |
| EXPECT_FALSE(NavigateInRenderer(tab, GURL("http://test/"))); |
| EXPECT_TRUE(WaitForPageActionVisibilityChangeTo(1)); |
| EXPECT_EQ(1u, extension_action_test_util::GetActivePageActionCount(tab)); |
| EXPECT_EQ(1u, extension_action_test_util::GetTotalPageActionCount(tab)); |
| |
| UnloadExtension(extension_id); |
| // Wait for declarative rules to be removed. |
| profile()->GetDefaultStoragePartition()->FlushNetworkInterfaceForTesting(); |
| EXPECT_FALSE(NavigateInRenderer(tab, GURL("http://test/"))); |
| EXPECT_TRUE(WaitForPageActionVisibilityChangeTo(0)); |
| EXPECT_EQ(0u, extension_action_test_util::GetActivePageActionCount(tab)); |
| EXPECT_EQ(0u, extension_action_test_util::GetTotalPageActionCount(tab)); |
| } |
| |
| // This tests against a renderer crash that was present during development. |
| // TODO(crbug.com/40260777): Convert this to run in both modes. |
| IN_PROC_BROWSER_TEST_F(DeclarativeContentApiTest, |
| AddExtensionMatchingExistingTabWithDeadFrames) { |
| ext_dir_.WriteManifest(FormatManifest(SPANNING)); |
| ext_dir_.WriteFile(FILE_PATH_LITERAL("background.js"), kBackgroundHelpers); |
| content::WebContents* const tab = GetActiveWebContents(); |
| const int tab_id = ExtensionTabUtil::GetTabId(tab); |
| |
| ASSERT_TRUE(content::ExecJs( |
| tab, R"(document.body.innerHTML = '<iframe src="http://test2">';)")); |
| // Replace the iframe to destroy its WebFrame. |
| ASSERT_TRUE(content::ExecJs( |
| tab, R"(document.body.innerHTML = '<span class="foo">';)")); |
| |
| // Observer to track page action visibility. This helps avoid flakes by |
| // waiting to check visibility until there is an update to the page action. |
| ChromeExtensionTestNotificationObserver test_observer(profile()); |
| |
| const Extension* extension = LoadExtension(ext_dir_.UnpackedPath()); |
| ASSERT_TRUE(extension); |
| const ExtensionAction* action = |
| ExtensionActionManager::Get(profile())->GetExtensionAction(*extension); |
| ASSERT_TRUE(action); |
| EXPECT_FALSE(action->GetIsVisible(tab_id)); |
| |
| static constexpr char kRuleScript[] = |
| R"(setRules([{ |
| conditions: [new PageStateMatcher({css: ["span[class=foo]"]})], |
| actions: [new ShowAction()] |
| }], 'rule0');)"; |
| EXPECT_EQ("rule0", |
| ExecuteScriptInBackgroundPage(extension->id(), kRuleScript)); |
| |
| test_observer.WaitForPageActionVisibilityChangeTo(tab, 1); |
| EXPECT_FALSE(tab->IsCrashed()); |
| EXPECT_TRUE(action->GetIsVisible(tab_id)) |
| << "Loading an extension when an open page matches its rules " |
| << "should show the page action."; |
| |
| static constexpr char kRemoveScript[] = |
| R"(onPageChanged.removeRules(undefined, function() { |
| chrome.test.sendScriptResult('removed'); |
| });)"; |
| EXPECT_EQ("removed", |
| ExecuteScriptInBackgroundPage(extension->id(), kRemoveScript)); |
| test_observer.WaitForPageActionVisibilityChangeTo(tab, 0); |
| EXPECT_FALSE(action->GetIsVisible(tab_id)); |
| } |
| |
| // TODO(crbug.com/40260777): Convert this to run in both modes. |
| IN_PROC_BROWSER_TEST_F(DeclarativeContentApiTest, |
| CanonicalizesPageStateMatcherCss) { |
| ext_dir_.WriteManifest(FormatManifest(SPANNING)); |
| ext_dir_.WriteFile( |
| FILE_PATH_LITERAL("background.js"), |
| R"(var PageStateMatcher = chrome.declarativeContent.PageStateMatcher; |
| function Return(obj) { |
| chrome.test.sendScriptResult('' + obj); |
| })"); |
| const Extension* extension = LoadExtension(ext_dir_.UnpackedPath()); |
| ASSERT_TRUE(extension); |
| |
| static constexpr char kValidSelectorScript[] = |
| R"(var psm = new PageStateMatcher({css: ["input[type='password']"]}); |
| Return(psm.css);)"; |
| EXPECT_EQ( |
| R"(input[type="password"])", |
| ExecuteScriptInBackgroundPage(extension->id(), kValidSelectorScript)); |
| |
| static constexpr char kSelectorNotAnArrayScript[] = |
| R"(try { |
| new PageStateMatcher({css: 'Not-an-array'}); |
| Return('Failed to throw'); |
| } catch (e) { |
| Return(e.message); |
| })"; |
| base::Value result = |
| ExecuteScriptInBackgroundPage(extension->id(), kSelectorNotAnArrayScript); |
| ASSERT_TRUE(result.is_string()); |
| EXPECT_THAT(result.GetString(), |
| testing::ContainsRegex("css.*xpected '?array'?")); |
| |
| // CSS selector is not a string. |
| static constexpr char kSelectorNotStringScript[] = |
| R"(try { |
| new PageStateMatcher({css: [null]}); |
| Return('Failed to throw'); |
| } catch (e) { |
| Return(e.message); |
| })"; |
| result = |
| ExecuteScriptInBackgroundPage(extension->id(), kSelectorNotStringScript); |
| ASSERT_TRUE(result.is_string()); |
| EXPECT_THAT(result.GetString(), |
| testing::ContainsRegex("css.*0.*xpected '?string'?")); |
| |
| // Invalid CSS selector. |
| static constexpr char kInvalidSelectorScript[] = |
| R"(try { |
| new PageStateMatcher({css: ["input''"]}); |
| Return('Failed to throw'); |
| } catch (e) { |
| Return(e.message); |
| })"; |
| result = |
| ExecuteScriptInBackgroundPage(extension->id(), kInvalidSelectorScript); |
| ASSERT_TRUE(result.is_string()); |
| EXPECT_THAT(result.GetString(), testing::ContainsRegex("valid.*: input''$")); |
| |
| // "Complex" CSS selector. |
| static constexpr char kComplexSelectorScript[] = |
| R"(try { |
| new PageStateMatcher({css: ['div input']}); |
| Return('Failed to throw'); |
| } catch (e) { |
| Return(e.message); |
| })"; |
| result = |
| ExecuteScriptInBackgroundPage(extension->id(), kComplexSelectorScript); |
| ASSERT_TRUE(result.is_string()); |
| EXPECT_THAT(result.GetString(), |
| testing::ContainsRegex("selector.*: div input$")); |
| } |
| |
| // Tests that the rules with isBookmarked: true are evaluated when handling |
| // bookmarking events. |
| IN_PROC_BROWSER_TEST_P(DeclarativeContentApiTestWithContextType, |
| IsBookmarkedRulesEvaluatedOnBookmarkEvents) { |
| CheckBookmarkEvents(true); |
| } |
| |
| // Tests that the rules with isBookmarked: false are evaluated when handling |
| // bookmarking events. |
| IN_PROC_BROWSER_TEST_P(DeclarativeContentApiTestWithContextType, |
| NotBookmarkedRulesEvaluatedOnBookmarkEvents) { |
| CheckBookmarkEvents(false); |
| } |
| |
| // https://crbug.com/497586 |
| IN_PROC_BROWSER_TEST_P(DeclarativeContentApiTestWithContextType, |
| WebContentsWithoutTabAddedNotificationAtOnLoaded) { |
| // Add a web contents to the tab strip in a way that doesn't trigger |
| // NOTIFICATION_TAB_ADDED. |
| ASSERT_TRUE(NavigateToURLInNewTab(GURL("about:blank"))); |
| ASSERT_TRUE(content::WaitForLoadStop(GetActiveWebContents())); |
| |
| // The actual extension contents don't matter here -- we're just looking to |
| // trigger OnExtensionLoaded. |
| ext_dir_.WriteManifest(FormatManifest(SPANNING)); |
| ext_dir_.WriteFile(FILE_PATH_LITERAL("background.js"), kBackgroundHelpers); |
| ASSERT_TRUE(LoadExtension(ext_dir_.UnpackedPath())); |
| } |
| |
| // https://crbug.com/501225 |
| IN_PROC_BROWSER_TEST_P(DeclarativeContentApiTestWithContextType, |
| PendingWebContentsClearedOnRemoveRules) { |
| ext_dir_.WriteManifest(FormatManifest(SPANNING)); |
| ext_dir_.WriteFile(FILE_PATH_LITERAL("background.js"), kBackgroundHelpers); |
| const Extension* extension = LoadExtension(ext_dir_.UnpackedPath()); |
| ASSERT_TRUE(extension); |
| const ExtensionAction* action = |
| ExtensionActionManager::Get(profile())->GetExtensionAction(*extension); |
| ASSERT_TRUE(action); |
| |
| // Create two tabs. |
| content::WebContents* const tab1 = GetActiveWebContents(); |
| |
| content::RenderFrameHost* rfh = NavigateToURLInNewTab(GURL("http://test2/")); |
| content::WebContents* tab2 = content::WebContents::FromRenderFrameHost(rfh); |
| |
| // Add a rule matching the second tab. |
| const std::string kAddTestRules = |
| R"(addRules([{ |
| id: '1', |
| conditions: [new PageStateMatcher({pageUrl: {hostPrefix: 'test1'}})], |
| actions: [new ShowAction()] |
| }, { |
| id: '2', |
| conditions: [new PageStateMatcher({pageUrl: {hostPrefix: 'test2'}})], |
| actions: [new ShowAction()] |
| }], 'add_rules');)"; |
| EXPECT_EQ("add_rules", |
| ExecuteScriptInBackgroundPage(extension->id(), kAddTestRules)); |
| EXPECT_TRUE(action->GetIsVisible(ExtensionTabUtil::GetTabId(tab2))); |
| |
| // Remove the rule. |
| const std::string kRemoveTestRule1 = "removeRule('2', 'remove_rule1');"; |
| EXPECT_EQ("remove_rule1", |
| ExecuteScriptInBackgroundPage(extension->id(), kRemoveTestRule1)); |
| |
| // Remove the second tab, then trigger a rule evaluation for the remaining |
| // tab. |
| CloseTabForWebContents(tab2); |
| // TODO(crbug.com/40764017): Understand why this is EXPECT_FALSE. |
| EXPECT_FALSE(NavigateInRenderer(tab1, GURL("http://test1/"))); |
| EXPECT_TRUE(action->GetIsVisible(ExtensionTabUtil::GetTabId(tab1))); |
| } |
| |
| // https://crbug.com/517492 |
| IN_PROC_BROWSER_TEST_P(DeclarativeContentApiTestWithContextType, |
| RemoveAllRulesAfterExtensionUninstall) { |
| ext_dir_.WriteManifest(FormatManifest(SPANNING)); |
| ext_dir_.WriteFile(FILE_PATH_LITERAL("background.js"), kBackgroundHelpers); |
| |
| // Load the extension, add a rule, then uninstall the extension. |
| const Extension* extension = LoadExtension(ext_dir_.UnpackedPath()); |
| ASSERT_TRUE(extension); |
| |
| const std::string kAddTestRule = |
| R"(addRules([{ |
| id: '1', |
| conditions: [], |
| actions: [new ShowAction()] |
| }], 'add_rule');)"; |
| EXPECT_EQ("add_rule", |
| ExecuteScriptInBackgroundPage(extension->id(), kAddTestRule)); |
| |
| std::u16string error; |
| ASSERT_TRUE(extension_registrar()->UninstallExtension( |
| extension->id(), UNINSTALL_REASON_FOR_TESTING, &error)); |
| ASSERT_EQ(u"", error); |
| |
| // Reload the extension, then add and remove a rule. |
| extension = LoadExtension(ext_dir_.UnpackedPath()); |
| ASSERT_TRUE(extension); |
| |
| EXPECT_EQ("add_rule", |
| ExecuteScriptInBackgroundPage(extension->id(), kAddTestRule)); |
| |
| const std::string kRemoveTestRule1 = "removeRule('1', 'remove_rule1');"; |
| EXPECT_EQ("remove_rule1", |
| ExecuteScriptInBackgroundPage(extension->id(), kRemoveTestRule1)); |
| } |
| |
| // Test that an extension with a RequestContentScript rule in its manifest can |
| // be loaded. |
| // Regression for crbug.com/1211316, which could cause this test to flake if |
| // RulesRegistry::OnExtensionLoaded() was called before |
| // UserScriptManager::OnExtensionLoaded(). |
| IN_PROC_BROWSER_TEST_P(DeclarativeContentApiTestWithContextType, |
| RequestContentScriptRule) { |
| constexpr char kManifest[] = R"( |
| { |
| "name": "Declarative Content apitest", |
| "version": "0.1", |
| "manifest_version": 2, |
| "background": { |
| "scripts": ["background.js"], |
| "persistent": true |
| }, |
| "page_action": {}, |
| "permissions": [ |
| "declarativeContent" |
| ], |
| "event_rules": [{ |
| "event": "declarativeContent.onPageChanged", |
| "actions": [{ |
| "type": "declarativeContent.RequestContentScript", |
| "js": ["asdf.js"] |
| }], |
| "conditions": [{ |
| "type": "declarativeContent.PageStateMatcher", |
| "pageUrl": {"hostPrefix": "test1"} |
| }] |
| }] |
| } |
| )"; |
| |
| ext_dir_.WriteFile(FILE_PATH_LITERAL("background.js"), ""); |
| ext_dir_.WriteManifest(kManifest); |
| const Extension* extension = LoadExtension(ext_dir_.UnpackedPath()); |
| ASSERT_TRUE(extension); |
| |
| RulesRegistryService* rules_registry_service = |
| extensions::RulesRegistryService::Get(profile()); |
| scoped_refptr<RulesRegistry> rules_registry = |
| rules_registry_service->GetRulesRegistry( |
| rules_registry_ids::kDefaultRulesRegistryID, |
| "declarativeContent.onPageChanged"); |
| DCHECK(rules_registry); |
| |
| std::vector<const api::events::Rule*> rules; |
| rules_registry->GetAllRules(extension->id(), &rules); |
| EXPECT_EQ(1u, rules.size()); |
| } |
| |
| // TODO(wittman): Once ChromeContentRulesRegistry operates on condition and |
| // action interfaces, add a test that checks that a navigation always evaluates |
| // consistent URL state for all conditions. i.e.: if condition1 evaluates to |
| // false on url0 and true on url1, and condition2 evaluates to true on url0 and |
| // false on url1, navigate from url0 to url1 and validate that no action is |
| // triggered. Do the same when navigating back to url0. This kind of test is |
| // unfortunately not feasible with the current implementation and the existing |
| // supported conditions and actions. |
| |
| } // namespace |
| } // namespace extensions |