Index: chrome/browser/extensions/active_script_controller_unittest.cc |
diff --git a/chrome/browser/extensions/active_script_controller_unittest.cc b/chrome/browser/extensions/active_script_controller_unittest.cc |
new file mode 100644 |
index 0000000000000000000000000000000000000000..ef3dda80769ce98a252e20dc12d1638d0ede49bd |
--- /dev/null |
+++ b/chrome/browser/extensions/active_script_controller_unittest.cc |
@@ -0,0 +1,286 @@ |
+// Copyright 2014 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 <map> |
+ |
+#include "base/values.h" |
+#include "chrome/browser/extensions/active_script_controller.h" |
+#include "chrome/browser/extensions/active_tab_permission_granter.h" |
+#include "chrome/browser/extensions/tab_helper.h" |
+#include "chrome/test/base/chrome_render_view_host_test_harness.h" |
+#include "chrome/test/base/testing_profile.h" |
+#include "content/public/browser/navigation_controller.h" |
+#include "content/public/browser/navigation_entry.h" |
+#include "content/public/browser/web_contents.h" |
+#include "extensions/browser/extension_registry.h" |
+#include "extensions/common/extension.h" |
+#include "extensions/common/extension_builder.h" |
+#include "extensions/common/feature_switch.h" |
+#include "extensions/common/manifest.h" |
+#include "extensions/common/value_builder.h" |
+ |
+namespace extensions { |
+ |
+namespace { |
+ |
+const char kAllHostsPermission[] = "*://*/*"; |
+ |
+} // namespace |
+ |
+// Unittests for the ActiveScriptController mostly test the internal logic |
+// of the controller itself (when to allow/deny extension script injection). |
+// Testing real injection is allowed/denied as expected (i.e., that the |
+// ActiveScriptController correctly interfaces in the system) is done in the |
+// ActiveScriptControllerBrowserTests. |
+class ActiveScriptControllerUnitTest : public ChromeRenderViewHostTestHarness { |
+ protected: |
+ ActiveScriptControllerUnitTest(); |
+ virtual ~ActiveScriptControllerUnitTest(); |
+ |
+ // Creates an extension with all hosts permission and adds it to the registry. |
+ const Extension* AddExtension(); |
+ |
+ // Returns the current page id. |
+ int GetPageId(); |
+ |
+ // Returns a closure to use as a script execution for a given extension. |
+ base::Closure GetExecutionCallbackForExtension( |
+ const std::string& extension_id); |
+ |
+ // Returns the number of times a given extension has had a script execute. |
+ size_t GetExecutionCountForExtension(const std::string& extension_id) const; |
+ |
+ ActiveScriptController* controller() { return active_script_controller_; } |
+ |
+ private: |
+ // Increment the number of executions for the given |extension_id|. |
+ void IncrementExecutionCount(const std::string& extension_id); |
+ |
+ virtual void SetUp() OVERRIDE; |
+ |
+ // Since ActiveScriptController's behavior is behind a flag, override the |
+ // feature switch. |
+ FeatureSwitch::ScopedOverride feature_override_; |
+ |
+ // The associated ActiveScriptController. |
+ ActiveScriptController* active_script_controller_; |
+ |
+ // The map of observed executions, keyed by extension id. |
+ std::map<std::string, int> extension_executions_; |
+}; |
+ |
+ActiveScriptControllerUnitTest::ActiveScriptControllerUnitTest() |
+ : feature_override_(FeatureSwitch::scripts_require_action(), |
+ FeatureSwitch::OVERRIDE_ENABLED), |
+ active_script_controller_(NULL) { |
+} |
+ |
+ActiveScriptControllerUnitTest::~ActiveScriptControllerUnitTest() { |
+} |
+ |
+const Extension* ActiveScriptControllerUnitTest::AddExtension() { |
+ static const char kId[] = "all_hosts_extension"; |
+ scoped_refptr<const Extension> extension = |
+ ExtensionBuilder() |
+ .SetManifest( |
+ DictionaryBuilder() |
+ .Set("name", "all_hosts_extension") |
+ .Set("description", "an extension") |
+ .Set("manifest_version", 2) |
+ .Set("version", "1.0.0") |
+ .Set("permissions", |
+ ListBuilder().Append(kAllHostsPermission))) |
+ .SetLocation(Manifest::INTERNAL) |
+ .SetID(kId) |
+ .Build(); |
+ |
+ ExtensionRegistry::Get(profile())->AddEnabled(extension); |
+ return extension; |
+} |
+ |
+int ActiveScriptControllerUnitTest::GetPageId() { |
+ content::NavigationEntry* navigation_entry = |
+ web_contents()->GetController().GetVisibleEntry(); |
+ DCHECK(navigation_entry); // This should never be NULL. |
+ return navigation_entry->GetPageID(); |
+} |
+ |
+base::Closure ActiveScriptControllerUnitTest::GetExecutionCallbackForExtension( |
+ const std::string& extension_id) { |
+ // We use base unretained here, but if this ever gets executed outside of |
+ // this test's lifetime, we have a major problem anyway. |
+ return base::Bind(&ActiveScriptControllerUnitTest::IncrementExecutionCount, |
+ base::Unretained(this), |
+ extension_id); |
+} |
+ |
+size_t ActiveScriptControllerUnitTest::GetExecutionCountForExtension( |
+ const std::string& extension_id) const { |
+ std::map<std::string, int>::const_iterator iter = |
+ extension_executions_.find(extension_id); |
+ if (iter != extension_executions_.end()) |
+ return iter->second; |
+ return 0u; |
+} |
+ |
+void ActiveScriptControllerUnitTest::IncrementExecutionCount( |
+ const std::string& extension_id) { |
+ ++extension_executions_[extension_id]; |
+} |
+ |
+void ActiveScriptControllerUnitTest::SetUp() { |
+ ChromeRenderViewHostTestHarness::SetUp(); |
+ |
+ TabHelper::CreateForWebContents(web_contents()); |
+ TabHelper* tab_helper = TabHelper::FromWebContents(web_contents()); |
+ // None of these should ever be NULL. |
+ DCHECK(tab_helper); |
+ DCHECK(tab_helper->location_bar_controller()); |
+ active_script_controller_ = |
+ tab_helper->location_bar_controller()->active_script_controller(); |
+ DCHECK(active_script_controller_); |
+} |
+ |
+// Test that extensions with all_hosts require permission to execute, and, once |
+// that permission is granted, do execute. |
+TEST_F(ActiveScriptControllerUnitTest, RequestPermissionAndExecute) { |
+ const Extension* extension = AddExtension(); |
+ ASSERT_TRUE(extension); |
+ |
+ NavigateAndCommit(GURL("https://www.google.com")); |
+ |
+ // Ensure that there aren't any executions pending. |
+ ASSERT_EQ(0u, GetExecutionCountForExtension(extension->id())); |
+ ASSERT_FALSE(controller()->GetActionForExtension(extension)); |
+ |
+ // Since the extension requests all_hosts, we should require user consent. |
+ EXPECT_TRUE( |
+ controller()->RequiresUserConsentForScriptInjection(extension)); |
+ |
+ // Request an injection. There should be an action visible, but no executions. |
+ controller()->RequestScriptInjection( |
+ extension, |
+ GetPageId(), |
+ GetExecutionCallbackForExtension(extension->id())); |
+ EXPECT_TRUE(controller()->GetActionForExtension(extension)); |
+ EXPECT_EQ(0u, GetExecutionCountForExtension(extension->id())); |
+ |
+ // Click to accept the extension executing. |
+ controller()->OnClicked(extension); |
+ |
+ // The extension should execute, and the action should go away. |
+ EXPECT_EQ(1u, GetExecutionCountForExtension(extension->id())); |
+ EXPECT_FALSE(controller()->GetActionForExtension(extension)); |
+ |
+ // Since we already executed on the given page, we shouldn't need permission |
+ // for a second time. |
+ EXPECT_FALSE( |
+ controller()->RequiresUserConsentForScriptInjection(extension)); |
+ |
+ // Reloading should clear those permissions, and we should again require user |
+ // consent. |
+ Reload(); |
+ EXPECT_TRUE( |
+ controller()->RequiresUserConsentForScriptInjection(extension)); |
+ |
+ // Grant access. |
+ controller()->RequestScriptInjection( |
+ extension, |
+ GetPageId(), |
+ GetExecutionCallbackForExtension(extension->id())); |
+ controller()->OnClicked(extension); |
+ EXPECT_EQ(2u, GetExecutionCountForExtension(extension->id())); |
+ EXPECT_FALSE(controller()->GetActionForExtension(extension)); |
+ |
+ // Navigating to another site should also clear the permissions. |
+ NavigateAndCommit(GURL("https://www.foo.com")); |
+ EXPECT_TRUE( |
+ controller()->RequiresUserConsentForScriptInjection(extension)); |
+} |
+ |
+// Test that injections that are not executed by the time the user navigates are |
+// ignored and never execute. |
+TEST_F(ActiveScriptControllerUnitTest, PendingInjectionsRemovedAtNavigation) { |
+ const Extension* extension = AddExtension(); |
+ ASSERT_TRUE(extension); |
+ |
+ NavigateAndCommit(GURL("https://www.google.com")); |
+ |
+ ASSERT_EQ(0u, GetExecutionCountForExtension(extension->id())); |
+ |
+ // Request an injection. There should be an action visible, but no executions. |
+ controller()->RequestScriptInjection( |
+ extension, |
+ GetPageId(), |
+ GetExecutionCallbackForExtension(extension->id())); |
+ EXPECT_TRUE(controller()->GetActionForExtension(extension)); |
+ EXPECT_EQ(0u, GetExecutionCountForExtension(extension->id())); |
+ |
+ // Navigate away. This should remove the pending injection, and we should not |
+ // execute anything. |
+ NavigateAndCommit(GURL("https://www.google.com")); |
+ EXPECT_FALSE(controller()->GetActionForExtension(extension)); |
+ EXPECT_EQ(0u, GetExecutionCountForExtension(extension->id())); |
+ |
+ // Request and accept a new injection. |
+ controller()->RequestScriptInjection( |
+ extension, |
+ GetPageId(), |
+ GetExecutionCallbackForExtension(extension->id())); |
+ controller()->OnClicked(extension); |
+ |
+ // The extension should only have executed once, even though a grand total |
+ // of two executions were requested. |
+ EXPECT_EQ(1u, GetExecutionCountForExtension(extension->id())); |
+ EXPECT_FALSE(controller()->GetActionForExtension(extension)); |
+} |
+ |
+// Test that queueing multiple pending injections, and then accepting, triggers |
+// them all. |
+TEST_F(ActiveScriptControllerUnitTest, MultiplePendingInjection) { |
+ const Extension* extension = AddExtension(); |
+ ASSERT_TRUE(extension); |
+ NavigateAndCommit(GURL("https://www.google.com")); |
+ |
+ ASSERT_EQ(0u, GetExecutionCountForExtension(extension->id())); |
+ |
+ const size_t kNumInjections = 3u; |
+ // Queue multiple pending injections. |
+ for (size_t i = 0u; i < kNumInjections; ++i) { |
+ controller()->RequestScriptInjection( |
+ extension, |
+ GetPageId(), |
+ GetExecutionCallbackForExtension(extension->id())); |
+ } |
+ EXPECT_EQ(0u, GetExecutionCountForExtension(extension->id())); |
+ |
+ controller()->OnClicked(extension); |
+ |
+ // All pending injections should have executed. |
+ EXPECT_EQ(kNumInjections, GetExecutionCountForExtension(extension->id())); |
+ EXPECT_FALSE(controller()->GetActionForExtension(extension)); |
+} |
+ |
+TEST_F(ActiveScriptControllerUnitTest, ActiveScriptsUseActiveTabPermissions) { |
+ const Extension* extension = AddExtension(); |
+ NavigateAndCommit(GURL("https://www.google.com")); |
+ |
+ ActiveTabPermissionGranter* active_tab_permission_granter = |
+ TabHelper::FromWebContents(web_contents()) |
+ ->active_tab_permission_granter(); |
+ ASSERT_TRUE(active_tab_permission_granter); |
+ // Grant the extension active tab permissions. This normally happens, e.g., |
+ // if the user clicks on a browser action. |
+ active_tab_permission_granter->GrantIfRequested(extension); |
+ |
+ // Since we have active tab permissions, we shouldn't need user consent |
+ // anymore. |
+ EXPECT_FALSE( |
+ controller()->RequiresUserConsentForScriptInjection(extension)); |
+ |
+ // TODO(rdevlin.cronin): We should also implement/test that granting active |
+ // tab permissions automatically runs any pending injections. |
+} |
+ |
+} // namespace extensions |