OLD | NEW |
(Empty) | |
| 1 // Copyright 2014 The Chromium Authors. All rights reserved. |
| 2 // Use of this source code is governed by a BSD-style license that can be |
| 3 // found in the LICENSE file. |
| 4 |
| 5 #include <map> |
| 6 |
| 7 #include "base/values.h" |
| 8 #include "chrome/browser/extensions/active_script_controller.h" |
| 9 #include "chrome/browser/extensions/active_tab_permission_granter.h" |
| 10 #include "chrome/browser/extensions/tab_helper.h" |
| 11 #include "chrome/test/base/chrome_render_view_host_test_harness.h" |
| 12 #include "chrome/test/base/testing_profile.h" |
| 13 #include "content/public/browser/navigation_controller.h" |
| 14 #include "content/public/browser/navigation_entry.h" |
| 15 #include "content/public/browser/web_contents.h" |
| 16 #include "extensions/browser/extension_registry.h" |
| 17 #include "extensions/common/extension.h" |
| 18 #include "extensions/common/extension_builder.h" |
| 19 #include "extensions/common/feature_switch.h" |
| 20 #include "extensions/common/manifest.h" |
| 21 #include "extensions/common/value_builder.h" |
| 22 |
| 23 namespace extensions { |
| 24 |
| 25 namespace { |
| 26 |
| 27 const char kAllHostsPermission[] = "*://*/*"; |
| 28 |
| 29 } // namespace |
| 30 |
| 31 // Unittests for the ActiveScriptController mostly test the internal logic |
| 32 // of the controller itself (when to allow/deny extension script injection). |
| 33 // Testing real injection is allowed/denied as expected (i.e., that the |
| 34 // ActiveScriptController correctly interfaces in the system) is done in the |
| 35 // ActiveScriptControllerBrowserTests. |
| 36 class ActiveScriptControllerUnitTest : public ChromeRenderViewHostTestHarness { |
| 37 protected: |
| 38 ActiveScriptControllerUnitTest(); |
| 39 virtual ~ActiveScriptControllerUnitTest(); |
| 40 |
| 41 // Creates an extension with all hosts permission and adds it to the registry. |
| 42 const Extension* AddExtension(); |
| 43 |
| 44 // Returns the current page id. |
| 45 int GetPageId(); |
| 46 |
| 47 // Returns a closure to use as a script execution for a given extension. |
| 48 base::Closure GetExecutionCallbackForExtension( |
| 49 const std::string& extension_id); |
| 50 |
| 51 // Returns the number of times a given extension has had a script execute. |
| 52 size_t GetExecutionCountForExtension(const std::string& extension_id) const; |
| 53 |
| 54 ActiveScriptController* controller() { return active_script_controller_; } |
| 55 |
| 56 private: |
| 57 // Increment the number of executions for the given |extension_id|. |
| 58 void IncrementExecutionCount(const std::string& extension_id); |
| 59 |
| 60 virtual void SetUp() OVERRIDE; |
| 61 |
| 62 // Since ActiveScriptController's behavior is behind a flag, override the |
| 63 // feature switch. |
| 64 FeatureSwitch::ScopedOverride feature_override_; |
| 65 |
| 66 // The associated ActiveScriptController. |
| 67 ActiveScriptController* active_script_controller_; |
| 68 |
| 69 // The map of observed executions, keyed by extension id. |
| 70 std::map<std::string, int> extension_executions_; |
| 71 }; |
| 72 |
| 73 ActiveScriptControllerUnitTest::ActiveScriptControllerUnitTest() |
| 74 : feature_override_(FeatureSwitch::scripts_require_action(), |
| 75 FeatureSwitch::OVERRIDE_ENABLED), |
| 76 active_script_controller_(NULL) { |
| 77 } |
| 78 |
| 79 ActiveScriptControllerUnitTest::~ActiveScriptControllerUnitTest() { |
| 80 } |
| 81 |
| 82 const Extension* ActiveScriptControllerUnitTest::AddExtension() { |
| 83 static const char kId[] = "all_hosts_extension"; |
| 84 scoped_refptr<const Extension> extension = |
| 85 ExtensionBuilder() |
| 86 .SetManifest( |
| 87 DictionaryBuilder() |
| 88 .Set("name", "all_hosts_extension") |
| 89 .Set("description", "an extension") |
| 90 .Set("manifest_version", 2) |
| 91 .Set("version", "1.0.0") |
| 92 .Set("permissions", |
| 93 ListBuilder().Append(kAllHostsPermission))) |
| 94 .SetLocation(Manifest::INTERNAL) |
| 95 .SetID(kId) |
| 96 .Build(); |
| 97 |
| 98 ExtensionRegistry::Get(profile())->AddEnabled(extension); |
| 99 return extension; |
| 100 } |
| 101 |
| 102 int ActiveScriptControllerUnitTest::GetPageId() { |
| 103 content::NavigationEntry* navigation_entry = |
| 104 web_contents()->GetController().GetVisibleEntry(); |
| 105 DCHECK(navigation_entry); // This should never be NULL. |
| 106 return navigation_entry->GetPageID(); |
| 107 } |
| 108 |
| 109 base::Closure ActiveScriptControllerUnitTest::GetExecutionCallbackForExtension( |
| 110 const std::string& extension_id) { |
| 111 // We use base unretained here, but if this ever gets executed outside of |
| 112 // this test's lifetime, we have a major problem anyway. |
| 113 return base::Bind(&ActiveScriptControllerUnitTest::IncrementExecutionCount, |
| 114 base::Unretained(this), |
| 115 extension_id); |
| 116 } |
| 117 |
| 118 size_t ActiveScriptControllerUnitTest::GetExecutionCountForExtension( |
| 119 const std::string& extension_id) const { |
| 120 std::map<std::string, int>::const_iterator iter = |
| 121 extension_executions_.find(extension_id); |
| 122 if (iter != extension_executions_.end()) |
| 123 return iter->second; |
| 124 return 0u; |
| 125 } |
| 126 |
| 127 void ActiveScriptControllerUnitTest::IncrementExecutionCount( |
| 128 const std::string& extension_id) { |
| 129 ++extension_executions_[extension_id]; |
| 130 } |
| 131 |
| 132 void ActiveScriptControllerUnitTest::SetUp() { |
| 133 ChromeRenderViewHostTestHarness::SetUp(); |
| 134 |
| 135 TabHelper::CreateForWebContents(web_contents()); |
| 136 TabHelper* tab_helper = TabHelper::FromWebContents(web_contents()); |
| 137 // None of these should ever be NULL. |
| 138 DCHECK(tab_helper); |
| 139 DCHECK(tab_helper->location_bar_controller()); |
| 140 active_script_controller_ = |
| 141 tab_helper->location_bar_controller()->active_script_controller(); |
| 142 DCHECK(active_script_controller_); |
| 143 } |
| 144 |
| 145 // Test that extensions with all_hosts require permission to execute, and, once |
| 146 // that permission is granted, do execute. |
| 147 TEST_F(ActiveScriptControllerUnitTest, RequestPermissionAndExecute) { |
| 148 const Extension* extension = AddExtension(); |
| 149 ASSERT_TRUE(extension); |
| 150 |
| 151 NavigateAndCommit(GURL("https://www.google.com")); |
| 152 |
| 153 // Ensure that there aren't any executions pending. |
| 154 ASSERT_EQ(0u, GetExecutionCountForExtension(extension->id())); |
| 155 ASSERT_FALSE(controller()->GetActionForExtension(extension)); |
| 156 |
| 157 // Since the extension requests all_hosts, we should require user consent. |
| 158 EXPECT_TRUE( |
| 159 controller()->RequiresUserConsentForScriptInjection(extension)); |
| 160 |
| 161 // Request an injection. There should be an action visible, but no executions. |
| 162 controller()->RequestScriptInjection( |
| 163 extension, |
| 164 GetPageId(), |
| 165 GetExecutionCallbackForExtension(extension->id())); |
| 166 EXPECT_TRUE(controller()->GetActionForExtension(extension)); |
| 167 EXPECT_EQ(0u, GetExecutionCountForExtension(extension->id())); |
| 168 |
| 169 // Click to accept the extension executing. |
| 170 controller()->OnClicked(extension); |
| 171 |
| 172 // The extension should execute, and the action should go away. |
| 173 EXPECT_EQ(1u, GetExecutionCountForExtension(extension->id())); |
| 174 EXPECT_FALSE(controller()->GetActionForExtension(extension)); |
| 175 |
| 176 // Since we already executed on the given page, we shouldn't need permission |
| 177 // for a second time. |
| 178 EXPECT_FALSE( |
| 179 controller()->RequiresUserConsentForScriptInjection(extension)); |
| 180 |
| 181 // Reloading should clear those permissions, and we should again require user |
| 182 // consent. |
| 183 Reload(); |
| 184 EXPECT_TRUE( |
| 185 controller()->RequiresUserConsentForScriptInjection(extension)); |
| 186 |
| 187 // Grant access. |
| 188 controller()->RequestScriptInjection( |
| 189 extension, |
| 190 GetPageId(), |
| 191 GetExecutionCallbackForExtension(extension->id())); |
| 192 controller()->OnClicked(extension); |
| 193 EXPECT_EQ(2u, GetExecutionCountForExtension(extension->id())); |
| 194 EXPECT_FALSE(controller()->GetActionForExtension(extension)); |
| 195 |
| 196 // Navigating to another site should also clear the permissions. |
| 197 NavigateAndCommit(GURL("https://www.foo.com")); |
| 198 EXPECT_TRUE( |
| 199 controller()->RequiresUserConsentForScriptInjection(extension)); |
| 200 } |
| 201 |
| 202 // Test that injections that are not executed by the time the user navigates are |
| 203 // ignored and never execute. |
| 204 TEST_F(ActiveScriptControllerUnitTest, PendingInjectionsRemovedAtNavigation) { |
| 205 const Extension* extension = AddExtension(); |
| 206 ASSERT_TRUE(extension); |
| 207 |
| 208 NavigateAndCommit(GURL("https://www.google.com")); |
| 209 |
| 210 ASSERT_EQ(0u, GetExecutionCountForExtension(extension->id())); |
| 211 |
| 212 // Request an injection. There should be an action visible, but no executions. |
| 213 controller()->RequestScriptInjection( |
| 214 extension, |
| 215 GetPageId(), |
| 216 GetExecutionCallbackForExtension(extension->id())); |
| 217 EXPECT_TRUE(controller()->GetActionForExtension(extension)); |
| 218 EXPECT_EQ(0u, GetExecutionCountForExtension(extension->id())); |
| 219 |
| 220 // Navigate away. This should remove the pending injection, and we should not |
| 221 // execute anything. |
| 222 NavigateAndCommit(GURL("https://www.google.com")); |
| 223 EXPECT_FALSE(controller()->GetActionForExtension(extension)); |
| 224 EXPECT_EQ(0u, GetExecutionCountForExtension(extension->id())); |
| 225 |
| 226 // Request and accept a new injection. |
| 227 controller()->RequestScriptInjection( |
| 228 extension, |
| 229 GetPageId(), |
| 230 GetExecutionCallbackForExtension(extension->id())); |
| 231 controller()->OnClicked(extension); |
| 232 |
| 233 // The extension should only have executed once, even though a grand total |
| 234 // of two executions were requested. |
| 235 EXPECT_EQ(1u, GetExecutionCountForExtension(extension->id())); |
| 236 EXPECT_FALSE(controller()->GetActionForExtension(extension)); |
| 237 } |
| 238 |
| 239 // Test that queueing multiple pending injections, and then accepting, triggers |
| 240 // them all. |
| 241 TEST_F(ActiveScriptControllerUnitTest, MultiplePendingInjection) { |
| 242 const Extension* extension = AddExtension(); |
| 243 ASSERT_TRUE(extension); |
| 244 NavigateAndCommit(GURL("https://www.google.com")); |
| 245 |
| 246 ASSERT_EQ(0u, GetExecutionCountForExtension(extension->id())); |
| 247 |
| 248 const size_t kNumInjections = 3u; |
| 249 // Queue multiple pending injections. |
| 250 for (size_t i = 0u; i < kNumInjections; ++i) { |
| 251 controller()->RequestScriptInjection( |
| 252 extension, |
| 253 GetPageId(), |
| 254 GetExecutionCallbackForExtension(extension->id())); |
| 255 } |
| 256 EXPECT_EQ(0u, GetExecutionCountForExtension(extension->id())); |
| 257 |
| 258 controller()->OnClicked(extension); |
| 259 |
| 260 // All pending injections should have executed. |
| 261 EXPECT_EQ(kNumInjections, GetExecutionCountForExtension(extension->id())); |
| 262 EXPECT_FALSE(controller()->GetActionForExtension(extension)); |
| 263 } |
| 264 |
| 265 TEST_F(ActiveScriptControllerUnitTest, ActiveScriptsUseActiveTabPermissions) { |
| 266 const Extension* extension = AddExtension(); |
| 267 NavigateAndCommit(GURL("https://www.google.com")); |
| 268 |
| 269 ActiveTabPermissionGranter* active_tab_permission_granter = |
| 270 TabHelper::FromWebContents(web_contents()) |
| 271 ->active_tab_permission_granter(); |
| 272 ASSERT_TRUE(active_tab_permission_granter); |
| 273 // Grant the extension active tab permissions. This normally happens, e.g., |
| 274 // if the user clicks on a browser action. |
| 275 active_tab_permission_granter->GrantIfRequested(extension); |
| 276 |
| 277 // Since we have active tab permissions, we shouldn't need user consent |
| 278 // anymore. |
| 279 EXPECT_FALSE( |
| 280 controller()->RequiresUserConsentForScriptInjection(extension)); |
| 281 |
| 282 // TODO(rdevlin.cronin): We should also implement/test that granting active |
| 283 // tab permissions automatically runs any pending injections. |
| 284 } |
| 285 |
| 286 } // namespace extensions |
OLD | NEW |