|  | // Copyright (c) 2012 The Chromium Authors. All rights reserved. | 
|  | // Use of this source code is governed by a BSD-style license that can be | 
|  | // found in the LICENSE file. | 
|  |  | 
|  | #include <stddef.h> | 
|  |  | 
|  | #include <memory> | 
|  | #include "base/bind.h" | 
|  | #include "base/bind_helpers.h" | 
|  | #include "base/command_line.h" | 
|  | #include "base/strings/stringprintf.h" | 
|  | #include "base/strings/utf_string_conversions.h" | 
|  | #include "base/test/thread_test_helper.h" | 
|  | #include "build/build_config.h" | 
|  | #include "chrome/browser/extensions/extension_apitest.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/browser_thread.h" | 
|  | #include "content/public/browser/storage_partition.h" | 
|  | #include "extensions/browser/api/declarative/rules_registry_service.h" | 
|  | #include "extensions/browser/api/declarative_webrequest/webrequest_constants.h" | 
|  | #include "extensions/browser/api/declarative_webrequest/webrequest_rules_registry.h" | 
|  | #include "extensions/browser/extension_prefs.h" | 
|  | #include "extensions/common/extension.h" | 
|  | #include "extensions/test/extension_test_message_listener.h" | 
|  | #include "extensions/test/test_extension_dir.h" | 
|  |  | 
|  | using content::BrowserThread; | 
|  |  | 
|  | namespace extensions { | 
|  |  | 
|  | namespace { | 
|  |  | 
|  | const char kArbitraryUrl[] = "http://www.example.com";  // Must be http://. | 
|  |  | 
|  | // The extension in "declarative/redirect_to_data" redirects every navigation to | 
|  | // a page with title |kTestTitle|. | 
|  | #define TEST_TITLE_STRING ":TEST:" | 
|  | const char kTestTitle[] = TEST_TITLE_STRING; | 
|  |  | 
|  | // All methods and constands below containing "RedirectToData" in their names | 
|  | // are parts of a test extension "Redirect to 'data:'". | 
|  | std::string GetRedirectToDataManifestWithVersion(unsigned version) { | 
|  | return base::StringPrintf( | 
|  | "{\n" | 
|  | "  \"name\": \"Redirect to 'data:' (declarative apitest)\",\n" | 
|  | "  \"version\": \"%d\",\n" | 
|  | "  \"manifest_version\": 2,\n" | 
|  | "  \"description\": \"Redirects all requests to a fixed data: URI.\",\n" | 
|  | "  \"background\": {\n" | 
|  | "    \"scripts\": [\"background.js\"]\n" | 
|  | "  },\n" | 
|  | "  \"permissions\": [\n" | 
|  | "    \"declarativeWebRequest\", \"<all_urls>\"\n" | 
|  | "  ]\n" | 
|  | "}\n", | 
|  | version); | 
|  | } | 
|  |  | 
|  | const char kRedirectToDataConstants[] = | 
|  | "var redirectDataURI =\n" | 
|  | "    'data:text/html;charset=utf-8,<html><head><title>' +\n" | 
|  | "    '" TEST_TITLE_STRING "' +\n" | 
|  | "    '<%2Ftitle><%2Fhtml>';\n"; | 
|  | #undef TEST_TITLE_STRING | 
|  |  | 
|  | const char kRedirectToDataRules[] = | 
|  | "var rules = [{\n" | 
|  | "  conditions: [\n" | 
|  | "    new chrome.declarativeWebRequest.RequestMatcher({\n" | 
|  | "        url: {schemes: ['http']}})\n" | 
|  | "  ],\n" | 
|  | "  actions: [\n" | 
|  | "    new chrome.declarativeWebRequest.RedirectRequest({\n" | 
|  | "      redirectUrl: redirectDataURI\n" | 
|  | "    })\n" | 
|  | "  ]\n" | 
|  | "}];\n"; | 
|  |  | 
|  | const char kRedirectToDataInstallRules[] = | 
|  | "function report(details) {\n" | 
|  | "  if (chrome.extension.lastError) {\n" | 
|  | "    chrome.test.log(chrome.extension.lastError.message);\n" | 
|  | "  } else {\n" | 
|  | "    chrome.test.sendMessage(\"ready\", function(reply) {})\n" | 
|  | "  }\n" | 
|  | "}\n" | 
|  | "\n" | 
|  | "chrome.runtime.onInstalled.addListener(function(details) {\n" | 
|  | "  if (details.reason == 'install')\n" | 
|  | "    chrome.declarativeWebRequest.onRequest.addRules(rules, report);\n" | 
|  | "});\n"; | 
|  |  | 
|  | const char kRedirectToDataNoRules[] = | 
|  | "chrome.runtime.onInstalled.addListener(function(details) {\n" | 
|  | "  chrome.test.sendMessage(\"ready\", function(reply) {})\n" | 
|  | "});\n"; | 
|  |  | 
|  | }  // namespace | 
|  |  | 
|  | class DeclarativeApiTest : public ExtensionApiTest { | 
|  | public: | 
|  | std::string GetTitle() { | 
|  | base::string16 title( | 
|  | browser()->tab_strip_model()->GetActiveWebContents()->GetTitle()); | 
|  | return base::UTF16ToUTF8(title); | 
|  | } | 
|  |  | 
|  | // Reports the number of rules registered for the |extension_id| with the | 
|  | // non-webview rules registry. | 
|  | size_t NumberOfRegisteredRules(const std::string& extension_id) { | 
|  | RulesRegistryService* rules_registry_service = | 
|  | extensions::RulesRegistryService::Get(browser()->profile()); | 
|  | scoped_refptr<RulesRegistry> rules_registry = | 
|  | rules_registry_service->GetRulesRegistry( | 
|  | RulesRegistryService::kDefaultRulesRegistryID, | 
|  | extensions::declarative_webrequest_constants::kOnRequest); | 
|  |  | 
|  | std::vector<linked_ptr<api::events::Rule>> rules; | 
|  | BrowserThread::PostTask( | 
|  | BrowserThread::IO, FROM_HERE, | 
|  | base::BindOnce(&RulesRegistry::GetAllRules, rules_registry, | 
|  | extension_id, &rules)); | 
|  | scoped_refptr<base::ThreadTestHelper> io_helper(new base::ThreadTestHelper( | 
|  | BrowserThread::GetTaskRunnerForThread(BrowserThread::IO).get())); | 
|  | EXPECT_TRUE(io_helper->Run()); | 
|  | return rules.size(); | 
|  | } | 
|  | }; | 
|  |  | 
|  | IN_PROC_BROWSER_TEST_F(DeclarativeApiTest, DeclarativeApi) { | 
|  | ASSERT_TRUE(RunExtensionTest("declarative/api")) << message_; | 
|  |  | 
|  | // Check that uninstalling the extension has removed all rules. | 
|  | std::string extension_id = GetSingleLoadedExtension()->id(); | 
|  | UninstallExtension(extension_id); | 
|  |  | 
|  | // UnloadExtension posts a task to the owner thread of the extension | 
|  | // to process this unloading. The next task to retrive all rules | 
|  | // is therefore processed after the UnloadExtension task has been executed. | 
|  | EXPECT_EQ(0u, NumberOfRegisteredRules(extension_id)); | 
|  | } | 
|  |  | 
|  | // PersistRules test first installs an extension, which registers some rules. | 
|  | // Then after browser restart, it checks that the rules are still in effect. | 
|  | IN_PROC_BROWSER_TEST_F(DeclarativeApiTest, PRE_PersistRules) { | 
|  | // Note that we cannot use an extension generated by *GetRedirectToData* | 
|  | // helpers in a TestExtensionDir, because we need the extension to persist | 
|  | // until the PersistRules test is run. | 
|  | ASSERT_TRUE(RunExtensionTest("declarative/redirect_to_data")) << message_; | 
|  | } | 
|  |  | 
|  | // Disabled on Linux for flakiness: https://crbug.com/347159 | 
|  | #if defined(OS_LINUX) | 
|  | #define MAYBE_PersistRules DISABLED_PersistRules | 
|  | #else | 
|  | #define MAYBE_PersistRules PersistRules | 
|  | #endif | 
|  | IN_PROC_BROWSER_TEST_F(DeclarativeApiTest, MAYBE_PersistRules) { | 
|  | ui_test_utils::NavigateToURL(browser(), GURL(kArbitraryUrl)); | 
|  | EXPECT_EQ(kTestTitle, GetTitle()); | 
|  | } | 
|  |  | 
|  | // Disabled for flakiness: http://crbug.com/851854 | 
|  | #if defined(OS_MACOSX) && defined(ADDRESS_SANITIZER) | 
|  | #define MAYBE_ExtensionLifetimeRulesHandling \ | 
|  | DISABLED_ExtensionLifetimeRulesHandling | 
|  | #else | 
|  | #define MAYBE_ExtensionLifetimeRulesHandling ExtensionLifetimeRulesHandling | 
|  | #endif | 
|  |  | 
|  | // Test that the rules are correctly persisted and (de)activated during | 
|  | // changing the "installed" and "enabled" status of an extension. | 
|  | IN_PROC_BROWSER_TEST_F(DeclarativeApiTest, | 
|  | MAYBE_ExtensionLifetimeRulesHandling) { | 
|  | TestExtensionDir ext_dir; | 
|  |  | 
|  | // 1. Install the extension. Rules should become active. | 
|  | ext_dir.WriteManifest(GetRedirectToDataManifestWithVersion(1)); | 
|  | ext_dir.WriteFile(FILE_PATH_LITERAL("background.js"), | 
|  | base::StringPrintf("%s%s%s", | 
|  | kRedirectToDataConstants, | 
|  | kRedirectToDataRules, | 
|  | kRedirectToDataInstallRules)); | 
|  | ExtensionTestMessageListener ready("ready", /*will_reply=*/false); | 
|  | const Extension* extension = InstallExtensionWithUIAutoConfirm( | 
|  | ext_dir.Pack(), 1 /*+1 installed extension*/, browser()); | 
|  | ASSERT_TRUE(extension); | 
|  | // Wait for declarative rules to be set up. | 
|  | content::BrowserContext::GetDefaultStoragePartition(profile()) | 
|  | ->FlushNetworkInterfaceForTesting(); | 
|  | std::string extension_id(extension->id()); | 
|  | ASSERT_TRUE(ready.WaitUntilSatisfied()); | 
|  | ui_test_utils::NavigateToURL(browser(), GURL(kArbitraryUrl)); | 
|  | EXPECT_EQ(kTestTitle, GetTitle()); | 
|  | EXPECT_EQ(1u, NumberOfRegisteredRules(extension_id)); | 
|  |  | 
|  | // 2. Disable the extension. Rules are no longer active, but are still | 
|  | // registered. | 
|  | DisableExtension(extension_id); | 
|  | ui_test_utils::NavigateToURL(browser(), GURL(kArbitraryUrl)); | 
|  | EXPECT_NE(kTestTitle, GetTitle()); | 
|  | EXPECT_EQ(1u, NumberOfRegisteredRules(extension_id)); | 
|  |  | 
|  | // 3. Enable the extension again. Rules are active again. | 
|  | EnableExtension(extension_id); | 
|  | ui_test_utils::NavigateToURL(browser(), GURL(kArbitraryUrl)); | 
|  | EXPECT_EQ(kTestTitle, GetTitle()); | 
|  | EXPECT_EQ(1u, NumberOfRegisteredRules(extension_id)); | 
|  |  | 
|  | // 4. Bump the version and update, without the code to add the rules. Rules | 
|  | // are still active, because the registry does not drop them unless the | 
|  | // extension gets uninstalled. | 
|  | ext_dir.WriteManifest(GetRedirectToDataManifestWithVersion(2)); | 
|  | ext_dir.WriteFile( | 
|  | FILE_PATH_LITERAL("background.js"), | 
|  | base::StringPrintf( | 
|  | "%s%s", kRedirectToDataConstants, kRedirectToDataNoRules)); | 
|  | ExtensionTestMessageListener ready_after_update("ready", | 
|  | /*will_reply=*/false); | 
|  | EXPECT_TRUE(UpdateExtension( | 
|  | extension_id, ext_dir.Pack(), 0 /*no new installed extension*/)); | 
|  | ASSERT_TRUE(ready_after_update.WaitUntilSatisfied()); | 
|  | ui_test_utils::NavigateToURL(browser(), GURL(kArbitraryUrl)); | 
|  | EXPECT_EQ(kTestTitle, GetTitle()); | 
|  | EXPECT_EQ(1u, NumberOfRegisteredRules(extension_id)); | 
|  |  | 
|  | // 5. Reload the extension. Rules remain active. | 
|  | ReloadExtension(extension_id); | 
|  | ui_test_utils::NavigateToURL(browser(), GURL(kArbitraryUrl)); | 
|  | EXPECT_EQ(kTestTitle, GetTitle()); | 
|  | EXPECT_EQ(1u, NumberOfRegisteredRules(extension_id)); | 
|  |  | 
|  | // 6. Uninstall the extension. Rules are gone. | 
|  | UninstallExtension(extension_id); | 
|  | ui_test_utils::NavigateToURL(browser(), GURL(kArbitraryUrl)); | 
|  | EXPECT_NE(kTestTitle, GetTitle()); | 
|  | EXPECT_EQ(0u, NumberOfRegisteredRules(extension_id)); | 
|  | } | 
|  |  | 
|  | // Disabled for flakiness: http://crbug.com/851854 | 
|  | #if defined(OS_MACOSX) && defined(ADDRESS_SANITIZER) | 
|  | #define MAYBE_NoTracesAfterUninstalling DISABLED_NoTracesAfterUninstalling | 
|  | #else | 
|  | #define MAYBE_NoTracesAfterUninstalling NoTracesAfterUninstalling | 
|  | #endif | 
|  |  | 
|  | // When an extension is uninstalled, the state store deletes all preferences | 
|  | // stored for that extension. We need to make sure we don't store anything after | 
|  | // that deletion occurs. | 
|  | IN_PROC_BROWSER_TEST_F(DeclarativeApiTest, MAYBE_NoTracesAfterUninstalling) { | 
|  | TestExtensionDir ext_dir; | 
|  |  | 
|  | // 1. Install the extension. Verify that rules become active and some prefs | 
|  | // are stored. | 
|  | ext_dir.WriteManifest(GetRedirectToDataManifestWithVersion(1)); | 
|  | ext_dir.WriteFile(FILE_PATH_LITERAL("background.js"), | 
|  | base::StringPrintf("%s%s%s", | 
|  | kRedirectToDataConstants, | 
|  | kRedirectToDataRules, | 
|  | kRedirectToDataInstallRules)); | 
|  | ExtensionTestMessageListener ready("ready", /*will_reply=*/false); | 
|  | const Extension* extension = InstallExtensionWithUIAutoConfirm( | 
|  | ext_dir.Pack(), 1 /*+1 installed extension*/, browser()); | 
|  | ASSERT_TRUE(extension); | 
|  | // Wait for declarative rules to be set up. | 
|  | content::BrowserContext::GetDefaultStoragePartition(profile()) | 
|  | ->FlushNetworkInterfaceForTesting(); | 
|  | std::string extension_id(extension->id()); | 
|  | ASSERT_TRUE(ready.WaitUntilSatisfied()); | 
|  | ui_test_utils::NavigateToURL(browser(), GURL(kArbitraryUrl)); | 
|  | EXPECT_EQ(kTestTitle, GetTitle()); | 
|  | EXPECT_EQ(1u, NumberOfRegisteredRules(extension_id)); | 
|  | ExtensionPrefs* extension_prefs = ExtensionPrefs::Get(browser()->profile()); | 
|  | EXPECT_TRUE(extension_prefs->HasPrefForExtension(extension_id)); | 
|  |  | 
|  | // 2. Uninstall the extension. Rules are gone and preferences should be empty. | 
|  | UninstallExtension(extension_id); | 
|  | // Wait for declarative rules to be removed. | 
|  | content::BrowserContext::GetDefaultStoragePartition(profile()) | 
|  | ->FlushNetworkInterfaceForTesting(); | 
|  | ui_test_utils::NavigateToURL(browser(), GURL(kArbitraryUrl)); | 
|  | EXPECT_NE(kTestTitle, GetTitle()); | 
|  | EXPECT_EQ(0u, NumberOfRegisteredRules(extension_id)); | 
|  | EXPECT_FALSE(extension_prefs->HasPrefForExtension(extension_id)); | 
|  | } | 
|  |  | 
|  | }  // namespace extensions |