Chromium Code Reviews| 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", ListBuilder().Append(kAllHostsPermission)) | |
| 93 .Build()) | |
|
not at google - send to devlin
2014/05/21 20:10:20
don't need the Build()
Devlin
2014/05/21 23:16:07
Done.
| |
| 94 .SetLocation(Manifest::INTERNAL) | |
| 95 .SetID(kId) | |
| 96 .Build(); | |
| 97 | |
| 98 ExtensionRegistry* registry = ExtensionRegistry::Get(profile()); | |
| 99 registry->AddEnabled(extension); | |
| 100 | |
| 101 // Make sure the extension is correctly added. If not, return NULL. | |
|
not at google - send to devlin
2014/05/21 20:10:20
how can this possibly fail..? ExtensionRegistry ha
Devlin
2014/05/21 23:16:07
Fair. :)
| |
| 102 if (!registry->enabled_extensions().GetByID(kId)) | |
| 103 return NULL; | |
| 104 | |
| 105 return extension; | |
| 106 } | |
| 107 | |
| 108 int ActiveScriptControllerUnitTest::GetPageId() { | |
| 109 content::NavigationEntry* navigation_entry = | |
| 110 web_contents()->GetController().GetVisibleEntry(); | |
| 111 DCHECK(navigation_entry); // This should never be NULL. | |
| 112 return navigation_entry->GetPageID(); | |
| 113 } | |
| 114 | |
| 115 base::Closure ActiveScriptControllerUnitTest::GetExecutionCallbackForExtension( | |
| 116 const std::string& extension_id) { | |
| 117 // We use base unretained here, but if this ever gets executed outside of | |
| 118 // this test's lifetime, we have a major problem anyway. | |
| 119 return base::Bind(&ActiveScriptControllerUnitTest::IncrementExecutionCount, | |
| 120 base::Unretained(this), | |
| 121 extension_id); | |
| 122 } | |
| 123 | |
| 124 size_t ActiveScriptControllerUnitTest::GetExecutionCountForExtension( | |
| 125 const std::string& extension_id) const { | |
| 126 std::map<std::string, int>::const_iterator iter = | |
| 127 extension_executions_.find(extension_id); | |
| 128 if (iter != extension_executions_.end()) | |
| 129 return iter->second; | |
| 130 return 0u; | |
| 131 } | |
| 132 | |
| 133 void ActiveScriptControllerUnitTest::IncrementExecutionCount( | |
| 134 const std::string& extension_id) { | |
| 135 std::map<std::string, int>::iterator iter = | |
| 136 extension_executions_.find(extension_id); | |
| 137 if (iter != extension_executions_.end()) | |
| 138 iter->second += 1; | |
| 139 else | |
| 140 extension_executions_[extension_id] = 1u; | |
|
not at google - send to devlin
2014/05/21 20:10:20
numbers are initialised to 0, so you just need "++
Devlin
2014/05/21 23:16:07
Done.
| |
| 141 } | |
| 142 | |
| 143 void ActiveScriptControllerUnitTest::SetUp() { | |
| 144 ChromeRenderViewHostTestHarness::SetUp(); | |
| 145 | |
| 146 TabHelper::CreateForWebContents(web_contents()); | |
| 147 TabHelper* tab_helper = TabHelper::FromWebContents(web_contents()); | |
| 148 // None of these should ever be NULL. | |
| 149 DCHECK(tab_helper); | |
| 150 DCHECK(tab_helper->location_bar_controller()); | |
| 151 active_script_controller_ = | |
| 152 tab_helper->location_bar_controller()->active_script_controller(); | |
| 153 DCHECK(active_script_controller_); | |
| 154 } | |
| 155 | |
| 156 // Test that extensions with all_hosts require permission to execute, and, once | |
| 157 // that permission is granted, do execute. | |
| 158 TEST_F(ActiveScriptControllerUnitTest, RequestPermissionAndExecute) { | |
| 159 const Extension* extension = AddExtension(); | |
| 160 ASSERT_TRUE(extension); | |
| 161 | |
| 162 NavigateAndCommit(GURL("https://www.google.com")); | |
| 163 | |
| 164 // Ensure that there aren't any executions pending. | |
| 165 ASSERT_EQ(0u, GetExecutionCountForExtension(extension->id())); | |
| 166 ExtensionAction* action = controller()->GetActionForExtension(extension); | |
| 167 ASSERT_FALSE(action); | |
|
not at google - send to devlin
2014/05/21 20:10:20
holding onto |action| here looks odd. just ASSERT_
Devlin
2014/05/21 23:16:07
Yeah, noticed that after I had uploaded. Original
| |
| 168 | |
| 169 // Since the extension requests all_hosts, we should require user consent. | |
| 170 EXPECT_TRUE( | |
| 171 controller()->RequiresUserConsentForScriptInjection(extension)); | |
| 172 | |
| 173 // Request an injection. There should be an action visible, but no executions. | |
| 174 controller()->RequestScriptInjection( | |
| 175 extension, | |
| 176 GetPageId(), | |
| 177 GetExecutionCallbackForExtension(extension->id())); | |
| 178 action = controller()->GetActionForExtension(extension); | |
| 179 EXPECT_TRUE(action); | |
| 180 EXPECT_EQ(0u, GetExecutionCountForExtension(extension->id())); | |
| 181 | |
| 182 // Click to accept the extension executing. | |
| 183 controller()->OnClicked(extension); | |
| 184 | |
| 185 // The extension should execute, and the action should go away. | |
| 186 EXPECT_EQ(1u, GetExecutionCountForExtension(extension->id())); | |
| 187 action = controller()->GetActionForExtension(extension); | |
| 188 EXPECT_FALSE(action); | |
| 189 | |
| 190 // Since we already executed on the given page, we shouldn't need permission | |
| 191 // for a second time. | |
| 192 EXPECT_FALSE( | |
| 193 controller()->RequiresUserConsentForScriptInjection(extension)); | |
| 194 | |
| 195 // Navigating again should clear those permissions, and we should again | |
| 196 // require user consent. | |
| 197 NavigateAndCommit(GURL("https://www.google.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 ExtensionAction* action = controller()->GetActionForExtension(extension); | |
| 218 EXPECT_TRUE(action); | |
| 219 EXPECT_EQ(0u, GetExecutionCountForExtension(extension->id())); | |
| 220 | |
| 221 // Navigate away. This should remove the pending injection, and we should not | |
| 222 // execute anything. | |
| 223 NavigateAndCommit(GURL("https://www.google.com")); | |
|
not at google - send to devlin
2014/05/21 20:10:20
you could also Reload() if that's any easier.
wou
Devlin
2014/05/21 23:16:07
Done.
| |
| 224 action = controller()->GetActionForExtension(extension); | |
| 225 EXPECT_FALSE(action); | |
| 226 EXPECT_EQ(0u, GetExecutionCountForExtension(extension->id())); | |
| 227 | |
| 228 // Request and accept a new injection. | |
| 229 controller()->RequestScriptInjection( | |
| 230 extension, | |
| 231 GetPageId(), | |
| 232 GetExecutionCallbackForExtension(extension->id())); | |
| 233 controller()->OnClicked(extension); | |
| 234 | |
| 235 // The extension should only have executed once, even though a grand total | |
| 236 // of two executions were requested. | |
| 237 EXPECT_EQ(1u, GetExecutionCountForExtension(extension->id())); | |
| 238 action = controller()->GetActionForExtension(extension); | |
| 239 EXPECT_FALSE(action); | |
| 240 } | |
| 241 | |
| 242 // Test that queueing multiple pending injections, and then accepting, triggers | |
| 243 // them all. | |
| 244 TEST_F(ActiveScriptControllerUnitTest, MultiplePendingInjection) { | |
| 245 const Extension* extension = AddExtension(); | |
| 246 ASSERT_TRUE(extension); | |
| 247 NavigateAndCommit(GURL("https://www.google.com")); | |
| 248 | |
| 249 ASSERT_EQ(0u, GetExecutionCountForExtension(extension->id())); | |
| 250 | |
| 251 const size_t kNumInjections = 3u; | |
| 252 // Queue multiple pending injections. | |
| 253 for (size_t i = 0u; i < kNumInjections; ++i) { | |
| 254 controller()->RequestScriptInjection( | |
| 255 extension, | |
| 256 GetPageId(), | |
| 257 GetExecutionCallbackForExtension(extension->id())); | |
| 258 } | |
| 259 EXPECT_EQ(0u, GetExecutionCountForExtension(extension->id())); | |
| 260 | |
| 261 controller()->OnClicked(extension); | |
| 262 | |
| 263 // All pending injections should have executed. | |
| 264 EXPECT_EQ(3u, GetExecutionCountForExtension(extension->id())); | |
|
not at google - send to devlin
2014/05/21 20:10:20
kNumInjections?
Devlin
2014/05/21 23:16:07
heh, whoops. Done.
| |
| 265 ExtensionAction* action = controller()->GetActionForExtension(extension); | |
| 266 EXPECT_FALSE(action); | |
| 267 } | |
| 268 | |
| 269 TEST_F(ActiveScriptControllerUnitTest, ActiveScriptsUseActiveTabPermissions) { | |
| 270 const Extension* extension = AddExtension(); | |
| 271 NavigateAndCommit(GURL("https://www.google.com")); | |
| 272 | |
| 273 ActiveTabPermissionGranter* active_tab_permission_granter = | |
| 274 TabHelper::FromWebContents(web_contents()) | |
| 275 ->active_tab_permission_granter(); | |
| 276 ASSERT_TRUE(active_tab_permission_granter); | |
| 277 // Grant the extension active tab permissions. This normally happens, e.g., | |
| 278 // if the user clicks on a browser action. | |
| 279 active_tab_permission_granter->GrantIfRequested(extension); | |
| 280 | |
| 281 // Since we have active tab permissions, we shouldn't need user consent | |
| 282 // anymore. | |
| 283 EXPECT_FALSE( | |
| 284 controller()->RequiresUserConsentForScriptInjection(extension)); | |
| 285 | |
| 286 // TODO(rdevlin.cronin): We should also implement/test that granting active | |
| 287 // tab permissions automatically runs any pending injections. | |
| 288 } | |
| 289 | |
| 290 } // namespace extensions | |
| OLD | NEW |