Adding instrumentation to locate the source of jankiness
[chromium-blink-merge.git] / chrome / browser / extensions / active_script_controller_browsertest.cc
blobff1dd414f3889349d9f21566754c939ef5f38b36
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.
5 #include "base/files/file_path.h"
6 #include "base/macros.h"
7 #include "base/strings/stringprintf.h"
8 #include "chrome/browser/extensions/active_script_controller.h"
9 #include "chrome/browser/extensions/extension_action.h"
10 #include "chrome/browser/extensions/extension_browsertest.h"
11 #include "chrome/browser/extensions/tab_helper.h"
12 #include "chrome/browser/extensions/test_extension_dir.h"
13 #include "chrome/browser/ui/browser.h"
14 #include "chrome/browser/ui/tabs/tab_strip_model.h"
15 #include "chrome/test/base/ui_test_utils.h"
16 #include "content/public/test/browser_test_utils.h"
17 #include "extensions/common/feature_switch.h"
18 #include "extensions/common/switches.h"
19 #include "extensions/test/extension_test_message_listener.h"
20 #include "net/test/embedded_test_server/embedded_test_server.h"
21 #include "testing/gtest/include/gtest/gtest.h"
23 namespace extensions {
25 namespace {
27 const char kAllHostsScheme[] = "*://*/*";
28 const char kExplicitHostsScheme[] = "http://127.0.0.1/*";
29 const char kBackgroundScript[] =
30 "\"background\": {\"scripts\": [\"script.js\"]}";
31 const char kBackgroundScriptSource[] =
32 "var listener = function(tabId) {\n"
33 " chrome.tabs.onUpdated.removeListener(listener);\n"
34 " chrome.tabs.executeScript(tabId, {\n"
35 " code: \"chrome.test.sendMessage('inject succeeded');\"\n"
36 " });"
37 "};\n"
38 "chrome.tabs.onUpdated.addListener(listener);";
39 const char kContentScriptSource[] =
40 "chrome.test.sendMessage('inject succeeded');";
42 const char kInjectSucceeded[] = "inject succeeded";
44 enum InjectionType {
45 CONTENT_SCRIPT,
46 EXECUTE_SCRIPT
49 enum HostType {
50 ALL_HOSTS,
51 EXPLICIT_HOSTS
54 enum RequiresConsent {
55 REQUIRES_CONSENT,
56 DOES_NOT_REQUIRE_CONSENT
59 // Runs all pending tasks in the renderer associated with |web_contents|.
60 // Returns true on success.
61 bool RunAllPendingInRenderer(content::WebContents* web_contents) {
62 // This is slight hack to achieve a RunPendingInRenderer() method. Since IPCs
63 // are sent synchronously, anything started prior to this method will finish
64 // before this method returns (as content::ExecuteScript() is synchronous).
65 return content::ExecuteScript(web_contents, "1 == 1;");
68 } // namespace
70 class ActiveScriptControllerBrowserTest : public ExtensionBrowserTest {
71 public:
72 ActiveScriptControllerBrowserTest() {}
74 virtual void SetUpCommandLine(base::CommandLine* command_line) override;
75 virtual void TearDownOnMainThread() override;
77 // Returns an extension with the given |host_type| and |injection_type|. If
78 // one already exists, the existing extension will be returned. Othewrwise,
79 // one will be created.
80 // This could potentially return NULL if LoadExtension() fails.
81 const Extension* CreateExtension(HostType host_type,
82 InjectionType injection_type);
84 private:
85 ScopedVector<TestExtensionDir> test_extension_dirs_;
86 std::vector<const Extension*> extensions_;
89 void ActiveScriptControllerBrowserTest::SetUpCommandLine(
90 base::CommandLine* command_line) {
91 ExtensionBrowserTest::SetUpCommandLine(command_line);
92 // We append the actual switch to the commandline because it needs to be
93 // passed over to the renderer, which a FeatureSwitch::ScopedOverride will
94 // not do.
95 command_line->AppendSwitch(switches::kEnableScriptsRequireAction);
98 void ActiveScriptControllerBrowserTest::TearDownOnMainThread() {
99 test_extension_dirs_.clear();
102 const Extension* ActiveScriptControllerBrowserTest::CreateExtension(
103 HostType host_type, InjectionType injection_type) {
104 std::string name =
105 base::StringPrintf(
106 "%s %s",
107 injection_type == CONTENT_SCRIPT ?
108 "content_script" : "execute_script",
109 host_type == ALL_HOSTS ? "all_hosts" : "explicit_hosts");
111 const char* permission_scheme =
112 host_type == ALL_HOSTS ? kAllHostsScheme : kExplicitHostsScheme;
114 std::string permissions = base::StringPrintf(
115 "\"permissions\": [\"tabs\", \"%s\"]", permission_scheme);
117 std::string scripts;
118 std::string script_source;
119 if (injection_type == CONTENT_SCRIPT) {
120 scripts = base::StringPrintf(
121 "\"content_scripts\": ["
122 " {"
123 " \"matches\": [\"%s\"],"
124 " \"js\": [\"script.js\"],"
125 " \"run_at\": \"document_start\""
126 " }"
127 "]",
128 permission_scheme);
129 } else {
130 scripts = kBackgroundScript;
133 std::string manifest = base::StringPrintf(
135 " \"name\": \"%s\","
136 " \"version\": \"1.0\","
137 " \"manifest_version\": 2,"
138 " %s,"
139 " %s"
140 "}",
141 name.c_str(),
142 permissions.c_str(),
143 scripts.c_str());
145 scoped_ptr<TestExtensionDir> dir(new TestExtensionDir);
146 dir->WriteManifest(manifest);
147 dir->WriteFile(FILE_PATH_LITERAL("script.js"),
148 injection_type == CONTENT_SCRIPT ? kContentScriptSource :
149 kBackgroundScriptSource);
151 const Extension* extension = LoadExtension(dir->unpacked_path());
152 if (extension) {
153 test_extension_dirs_.push_back(dir.release());
154 extensions_.push_back(extension);
157 // If extension is NULL here, it will be caught later in the test.
158 return extension;
161 class ActiveScriptTester {
162 public:
163 ActiveScriptTester(const std::string& name,
164 const Extension* extension,
165 Browser* browser,
166 RequiresConsent requires_consent,
167 InjectionType type);
168 ~ActiveScriptTester();
170 testing::AssertionResult Verify();
172 private:
173 // Returns the active script controller, or NULL if one does not exist.
174 ActiveScriptController* GetActiveScriptController();
176 // Returns true if the extension has a pending request with the active script
177 // controller.
178 bool WantsToRun();
180 // The name of the extension, and also the message it sends.
181 std::string name_;
183 // The extension associated with this tester.
184 const Extension* extension_;
186 // The browser the tester is running in.
187 Browser* browser_;
189 // Whether or not the extension has permission to run the script without
190 // asking the user.
191 RequiresConsent requires_consent_;
193 // The type of injection this tester uses.
194 InjectionType type_;
196 // All of these extensions should inject a script (either through content
197 // scripts or through chrome.tabs.executeScript()) that sends a message with
198 // the |kInjectSucceeded| message.
199 linked_ptr<ExtensionTestMessageListener> inject_success_listener_;
202 ActiveScriptTester::ActiveScriptTester(const std::string& name,
203 const Extension* extension,
204 Browser* browser,
205 RequiresConsent requires_consent,
206 InjectionType type)
207 : name_(name),
208 extension_(extension),
209 browser_(browser),
210 requires_consent_(requires_consent),
211 type_(type),
212 inject_success_listener_(
213 new ExtensionTestMessageListener(kInjectSucceeded,
214 false /* won't reply */)) {
215 inject_success_listener_->set_extension_id(extension->id());
218 ActiveScriptTester::~ActiveScriptTester() {
221 testing::AssertionResult ActiveScriptTester::Verify() {
222 if (!extension_)
223 return testing::AssertionFailure() << "Could not load extension: " << name_;
225 content::WebContents* web_contents =
226 browser_ ? browser_->tab_strip_model()->GetActiveWebContents() : NULL;
227 if (!web_contents)
228 return testing::AssertionFailure() << "No web contents.";
230 // Give the extension plenty of time to inject.
231 if (!RunAllPendingInRenderer(web_contents))
232 return testing::AssertionFailure() << "Could not run pending in renderer.";
234 // Make sure all running tasks are complete.
235 content::RunAllPendingInMessageLoop();
237 ActiveScriptController* controller = GetActiveScriptController();
238 if (!controller)
239 return testing::AssertionFailure() << "Could not find controller.";
241 bool wants_to_run = WantsToRun();
243 // An extension should have an action displayed iff it requires user consent.
244 if ((requires_consent_ == REQUIRES_CONSENT && !wants_to_run) ||
245 (requires_consent_ == DOES_NOT_REQUIRE_CONSENT && wants_to_run)) {
246 return testing::AssertionFailure()
247 << "Improper wants to run for " << name_ << ": expected "
248 << (requires_consent_ == REQUIRES_CONSENT) << ", found "
249 << wants_to_run;
252 // If the extension has permission, we should be able to simply wait for it
253 // to execute.
254 if (requires_consent_ == DOES_NOT_REQUIRE_CONSENT) {
255 inject_success_listener_->WaitUntilSatisfied();
256 return testing::AssertionSuccess();
259 // Otherwise, we don't have permission, and have to grant it. Ensure the
260 // script has *not* already executed.
261 if (inject_success_listener_->was_satisfied()) {
262 return testing::AssertionFailure() <<
263 name_ << "'s script ran without permission.";
266 // If we reach this point, we should always want to run.
267 DCHECK(wants_to_run);
269 // Grant permission by clicking on the extension action.
270 controller->OnClicked(extension_);
272 // Now, the extension should be able to inject the script.
273 inject_success_listener_->WaitUntilSatisfied();
275 // The extension should no longer want to run.
276 wants_to_run = WantsToRun();
277 if (wants_to_run) {
278 return testing::AssertionFailure()
279 << "Extension " << name_ << " still wants to run after injecting.";
282 return testing::AssertionSuccess();
285 ActiveScriptController* ActiveScriptTester::GetActiveScriptController() {
286 content::WebContents* web_contents =
287 browser_ ? browser_->tab_strip_model()->GetActiveWebContents() : NULL;
289 if (!web_contents)
290 return NULL;
292 TabHelper* tab_helper = TabHelper::FromWebContents(web_contents);
293 return tab_helper ? tab_helper->active_script_controller() : NULL;
296 bool ActiveScriptTester::WantsToRun() {
297 ActiveScriptController* controller = GetActiveScriptController();
298 return controller ? controller->WantsToRun(extension_) : false;
301 IN_PROC_BROWSER_TEST_F(ActiveScriptControllerBrowserTest,
302 ActiveScriptsAreDisplayedAndDelayExecution) {
303 base::FilePath active_script_path =
304 test_data_dir_.AppendASCII("active_script");
306 const char* kExtensionNames[] = {
307 "inject_scripts_all_hosts",
308 "inject_scripts_explicit_hosts",
309 "content_scripts_all_hosts",
310 "content_scripts_explicit_hosts"
313 // First, we load up three extensions:
314 // - An extension that injects scripts into all hosts,
315 // - An extension that injects scripts into explicit hosts,
316 // - An extension with a content script that runs on all hosts,
317 // - An extension with a content script that runs on explicit hosts.
318 // The extensions that operate on explicit hosts have permission; the ones
319 // that request all hosts require user consent.
320 ActiveScriptTester testers[] = {
321 ActiveScriptTester(
322 kExtensionNames[0],
323 CreateExtension(ALL_HOSTS, EXECUTE_SCRIPT),
324 browser(),
325 REQUIRES_CONSENT,
326 EXECUTE_SCRIPT),
327 ActiveScriptTester(
328 kExtensionNames[1],
329 CreateExtension(EXPLICIT_HOSTS, EXECUTE_SCRIPT),
330 browser(),
331 DOES_NOT_REQUIRE_CONSENT,
332 EXECUTE_SCRIPT),
333 ActiveScriptTester(
334 kExtensionNames[2],
335 CreateExtension(ALL_HOSTS, CONTENT_SCRIPT),
336 browser(),
337 REQUIRES_CONSENT,
338 CONTENT_SCRIPT),
339 ActiveScriptTester(
340 kExtensionNames[3],
341 CreateExtension(EXPLICIT_HOSTS, CONTENT_SCRIPT),
342 browser(),
343 DOES_NOT_REQUIRE_CONSENT,
344 CONTENT_SCRIPT),
347 // Navigate to an URL (which matches the explicit host specified in the
348 // extension content_scripts_explicit_hosts). All four extensions should
349 // inject the script.
350 ASSERT_TRUE(embedded_test_server()->InitializeAndWaitUntilReady());
351 ui_test_utils::NavigateToURL(
352 browser(), embedded_test_server()->GetURL("/extensions/test_file.html"));
354 for (size_t i = 0u; i < arraysize(testers); ++i)
355 EXPECT_TRUE(testers[i].Verify()) << kExtensionNames[i];
358 // Test that removing an extension with pending injections a) removes the
359 // pending injections for that extension, and b) does not affect pending
360 // injections for other extensions.
361 IN_PROC_BROWSER_TEST_F(ActiveScriptControllerBrowserTest,
362 RemoveExtensionWithPendingInjections) {
363 // Load up two extensions, each with content scripts.
364 const Extension* extension1 = CreateExtension(ALL_HOSTS, CONTENT_SCRIPT);
365 ASSERT_TRUE(extension1);
366 const Extension* extension2 = CreateExtension(ALL_HOSTS, CONTENT_SCRIPT);
367 ASSERT_TRUE(extension2);
369 ASSERT_NE(extension1->id(), extension2->id());
371 content::WebContents* web_contents =
372 browser()->tab_strip_model()->GetActiveWebContents();
373 ASSERT_TRUE(web_contents);
374 ActiveScriptController* active_script_controller =
375 ActiveScriptController::GetForWebContents(web_contents);
376 ASSERT_TRUE(active_script_controller);
378 ASSERT_TRUE(embedded_test_server()->InitializeAndWaitUntilReady());
379 ui_test_utils::NavigateToURL(
380 browser(), embedded_test_server()->GetURL("/extensions/test_file.html"));
382 // Both extensions should have pending requests.
383 EXPECT_TRUE(active_script_controller->WantsToRun(extension1));
384 EXPECT_TRUE(active_script_controller->WantsToRun(extension2));
386 // Unload one of the extensions.
387 UnloadExtension(extension2->id());
389 EXPECT_TRUE(RunAllPendingInRenderer(web_contents));
391 // We should have pending requests for extension1, but not the removed
392 // extension2.
393 EXPECT_TRUE(active_script_controller->WantsToRun(extension1));
394 EXPECT_FALSE(active_script_controller->WantsToRun(extension2));
396 // We should still be able to run the request for extension1.
397 ExtensionTestMessageListener inject_success_listener(
398 new ExtensionTestMessageListener(kInjectSucceeded,
399 false /* won't reply */));
400 inject_success_listener.set_extension_id(extension1->id());
401 active_script_controller->OnClicked(extension1);
402 inject_success_listener.WaitUntilSatisfied();
405 // A version of the test with the flag off, in order to test that everything
406 // still works as expected.
407 class FlagOffActiveScriptControllerBrowserTest
408 : public ActiveScriptControllerBrowserTest {
409 private:
410 // Simply don't append the flag.
411 virtual void SetUpCommandLine(base::CommandLine* command_line) override {
412 ExtensionBrowserTest::SetUpCommandLine(command_line);
416 IN_PROC_BROWSER_TEST_F(FlagOffActiveScriptControllerBrowserTest,
417 ScriptsExecuteWhenFlagAbsent) {
418 const char* kExtensionNames[] = {
419 "content_scripts_all_hosts",
420 "inject_scripts_all_hosts",
422 ActiveScriptTester testers[] = {
423 ActiveScriptTester(
424 kExtensionNames[0],
425 CreateExtension(ALL_HOSTS, CONTENT_SCRIPT),
426 browser(),
427 DOES_NOT_REQUIRE_CONSENT,
428 CONTENT_SCRIPT),
429 ActiveScriptTester(
430 kExtensionNames[1],
431 CreateExtension(ALL_HOSTS, EXECUTE_SCRIPT),
432 browser(),
433 DOES_NOT_REQUIRE_CONSENT,
434 EXECUTE_SCRIPT),
437 ASSERT_TRUE(embedded_test_server()->InitializeAndWaitUntilReady());
438 ui_test_utils::NavigateToURL(
439 browser(), embedded_test_server()->GetURL("/extensions/test_file.html"));
441 for (size_t i = 0u; i < arraysize(testers); ++i)
442 EXPECT_TRUE(testers[i].Verify()) << kExtensionNames[i];
445 } // namespace extensions