#include "chrome/browser/extensions/active_script_controller.h"
#include "chrome/browser/extensions/active_tab_permission_granter.h"
#include "chrome/browser/extensions/extension_util.h"
+#include "chrome/browser/extensions/permissions_updater.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 "extensions/common/feature_switch.h"
#include "extensions/common/id_util.h"
#include "extensions/common/manifest.h"
+#include "extensions/common/user_script.h"
#include "extensions/common/value_builder.h"
namespace extensions {
// Creates an extension with all hosts permission and adds it to the registry.
const Extension* AddExtension();
- // Returns the current page id.
- int GetPageId();
+ // Reloads |extension_| by removing it from the registry and recreating it.
+ const Extension* ReloadExtension();
- // Returns a closure to use as a script execution for a given extension.
- base::Closure GetExecutionCallbackForExtension(
- const std::string& extension_id);
+ // Returns true if the |extension| requires user consent before injecting
+ // a script.
+ bool RequiresUserConsent(const Extension* extension) const;
+
+ // Request an injection for the given |extension|.
+ void RequestInjection(const Extension* extension);
// 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_; }
+ ActiveScriptController* controller() const {
+ return active_script_controller_;
+ }
private:
+ // Returns a closure to use as a script execution for a given extension.
+ base::Closure GetExecutionCallbackForExtension(
+ const std::string& extension_id);
+
// Increment the number of executions for the given |extension_id|.
void IncrementExecutionCount(const std::string& extension_id);
// The map of observed executions, keyed by extension id.
std::map<std::string, int> extension_executions_;
+
+ scoped_refptr<const Extension> extension_;
};
ActiveScriptControllerUnitTest::ActiveScriptControllerUnitTest()
const Extension* ActiveScriptControllerUnitTest::AddExtension() {
const std::string kId = id_util::GenerateId("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;
+ 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_);
+ PermissionsUpdater(profile()).InitializePermissions(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();
+const Extension* ActiveScriptControllerUnitTest::ReloadExtension() {
+ ExtensionRegistry::Get(profile())->RemoveEnabled(extension_->id());
+ return AddExtension();
}
-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);
+bool ActiveScriptControllerUnitTest::RequiresUserConsent(
+ const Extension* extension) const {
+ PermissionsData::AccessType access_type =
+ controller()->RequiresUserConsentForScriptInjectionForTesting(
+ extension, UserScript::PROGRAMMATIC_SCRIPT);
+ // We should never downright refuse access in these tests.
+ DCHECK_NE(PermissionsData::ACCESS_DENIED, access_type);
+ return access_type == PermissionsData::ACCESS_WITHHELD;
+}
+
+void ActiveScriptControllerUnitTest::RequestInjection(
+ const Extension* extension) {
+ controller()->RequestScriptInjectionForTesting(
+ extension,
+ GetExecutionCallbackForExtension(extension->id()));
}
size_t ActiveScriptControllerUnitTest::GetExecutionCountForExtension(
return 0u;
}
+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);
+}
+
void ActiveScriptControllerUnitTest::IncrementExecutionCount(
const std::string& extension_id) {
++extension_executions_[extension_id];
ASSERT_FALSE(controller()->GetActionForExtension(extension));
// Since the extension requests all_hosts, we should require user consent.
- EXPECT_TRUE(
- controller()->RequiresUserConsentForScriptInjection(extension));
+ EXPECT_TRUE(RequiresUserConsent(extension));
// Request an injection. There should be an action visible, but no executions.
- controller()->RequestScriptInjection(
- extension,
- GetPageId(),
- GetExecutionCallbackForExtension(extension->id()));
+ RequestInjection(extension);
EXPECT_TRUE(controller()->GetActionForExtension(extension));
EXPECT_EQ(0u, GetExecutionCountForExtension(extension->id()));
// Since we already executed on the given page, we shouldn't need permission
// for a second time.
- EXPECT_FALSE(
- controller()->RequiresUserConsentForScriptInjection(extension));
+ EXPECT_FALSE(RequiresUserConsent(extension));
// Reloading should clear those permissions, and we should again require user
// consent.
Reload();
- EXPECT_TRUE(
- controller()->RequiresUserConsentForScriptInjection(extension));
+ EXPECT_TRUE(RequiresUserConsent(extension));
// Grant access.
- controller()->RequestScriptInjection(
- extension,
- GetPageId(),
- GetExecutionCallbackForExtension(extension->id()));
+ RequestInjection(extension);
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));
+ EXPECT_TRUE(RequiresUserConsent(extension));
}
// Test that injections that are not executed by the time the user navigates are
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()));
+ RequestInjection(extension);
EXPECT_TRUE(controller()->GetActionForExtension(extension));
EXPECT_EQ(0u, GetExecutionCountForExtension(extension->id()));
EXPECT_EQ(0u, GetExecutionCountForExtension(extension->id()));
// Request and accept a new injection.
- controller()->RequestScriptInjection(
- extension,
- GetPageId(),
- GetExecutionCallbackForExtension(extension->id()));
+ RequestInjection(extension);
controller()->OnClicked(extension);
// The extension should only have executed once, even though a grand total
const size_t kNumInjections = 3u;
// Queue multiple pending injections.
- for (size_t i = 0u; i < kNumInjections; ++i) {
- controller()->RequestScriptInjection(
- extension,
- GetPageId(),
- GetExecutionCallbackForExtension(extension->id()));
- }
+ for (size_t i = 0u; i < kNumInjections; ++i)
+ RequestInjection(extension);
+
EXPECT_EQ(0u, GetExecutionCountForExtension(extension->id()));
controller()->OnClicked(extension);
// Since we have active tab permissions, we shouldn't need user consent
// anymore.
- EXPECT_FALSE(controller()->RequiresUserConsentForScriptInjection(extension));
+ EXPECT_FALSE(RequiresUserConsent(extension));
// Also test that granting active tab runs any pending tasks.
Reload();
// Navigating should mean we need permission again.
- EXPECT_TRUE(controller()->RequiresUserConsentForScriptInjection(extension));
+ EXPECT_TRUE(RequiresUserConsent(extension));
- controller()->RequestScriptInjection(
- extension,
- GetPageId(),
- GetExecutionCallbackForExtension(extension->id()));
+ RequestInjection(extension);
EXPECT_TRUE(controller()->GetActionForExtension(extension));
EXPECT_EQ(0u, GetExecutionCountForExtension(extension->id()));
ASSERT_TRUE(extension);
NavigateAndCommit(GURL("https://www.google.com"));
- EXPECT_TRUE(controller()->RequiresUserConsentForScriptInjection(extension));
+ EXPECT_TRUE(RequiresUserConsent(extension));
// Enable the extension on all urls.
util::SetAllowedScriptingOnAllUrls(extension->id(), profile(), true);
- EXPECT_FALSE(controller()->RequiresUserConsentForScriptInjection(extension));
+ EXPECT_FALSE(RequiresUserConsent(extension));
// This should carry across navigations, and websites.
NavigateAndCommit(GURL("http://www.foo.com"));
- EXPECT_FALSE(controller()->RequiresUserConsentForScriptInjection(extension));
+ EXPECT_FALSE(RequiresUserConsent(extension));
// Turning off the preference should have instant effect.
util::SetAllowedScriptingOnAllUrls(extension->id(), profile(), false);
- EXPECT_TRUE(controller()->RequiresUserConsentForScriptInjection(extension));
+ EXPECT_TRUE(RequiresUserConsent(extension));
// And should also persist across navigations and websites.
NavigateAndCommit(GURL("http://www.bar.com"));
- EXPECT_TRUE(controller()->RequiresUserConsentForScriptInjection(extension));
+ EXPECT_TRUE(RequiresUserConsent(extension));
+}
+
+TEST_F(ActiveScriptControllerUnitTest, TestAlwaysRun) {
+ const Extension* extension = AddExtension();
+ ASSERT_TRUE(extension);
+
+ NavigateAndCommit(GURL("https://www.google.com/?gws_rd=ssl"));
+
+ // 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(RequiresUserConsent(extension));
+
+ // Request an injection. There should be an action visible, but no executions.
+ RequestInjection(extension);
+ EXPECT_TRUE(controller()->GetActionForExtension(extension));
+ EXPECT_EQ(0u, GetExecutionCountForExtension(extension->id()));
+
+ // Allow the extension to always run on this origin.
+ controller()->AlwaysRunOnVisibleOrigin(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(RequiresUserConsent(extension));
+
+ // Navigating to another site that hasn't been granted a persisted permission
+ // should necessitate user consent.
+ NavigateAndCommit(GURL("https://www.foo.com/bar"));
+ EXPECT_TRUE(RequiresUserConsent(extension));
+
+ // We shouldn't need user permission upon returning to the original origin.
+ NavigateAndCommit(GURL("https://www.google.com/foo/bar"));
+ EXPECT_FALSE(RequiresUserConsent(extension));
+
+ // Reloading the extension should not clear any granted host permissions.
+ extension = ReloadExtension();
+ Reload();
+ EXPECT_FALSE(RequiresUserConsent(extension));
+
+ // Different host...
+ NavigateAndCommit(GURL("https://www.foo.com/bar"));
+ EXPECT_TRUE(RequiresUserConsent(extension));
+ // Different scheme...
+ NavigateAndCommit(GURL("http://www.google.com/foo/bar"));
+ EXPECT_TRUE(RequiresUserConsent(extension));
+ // Different subdomain...
+ NavigateAndCommit(GURL("https://en.google.com/foo/bar"));
+ EXPECT_TRUE(RequiresUserConsent(extension));
+ // Only the "always run" origin should be allowed to run without user consent.
+ NavigateAndCommit(GURL("https://www.google.com/foo/bar"));
+ EXPECT_FALSE(RequiresUserConsent(extension));
}
} // namespace extensions