Commit 5c5abd24 authored by Devlin Cronin's avatar Devlin Cronin Committed by Commit Bot

[Extensions Click-to-Script] (Hopefully) de-flake action runner tests

ExtensionActionRunnerBrowserTest.ActiveScriptsAreDisplayedAndDelayExecution
has been flaky on Windows. Looking at the test, this could be caused by
not waiting a sufficient amount of time after loading the extension for
the extension background page to register a listener for the tab update.
This, in turn, would result in the extension not appearing to want to
run when it should.

Solve this potential issue by dispatching a "ready" message from the
extension once it has registered the listener, and waiting for that
message in the C++ before creating the new tab. Also explicitly wait
for the script to be injected in the case of automatic injection, and
wait for the blocked action to be added for the case of requiring
consent.

Additionally, break apart the test from being a single test that
exercised four different expectations to four tests, each managing a
single expectation. This eliminates the chance of any of the test steps
interfering with one another.

Bug: 867882

Change-Id: I2859bd35573415080e3c70a3e08bf2108430ee28
Reviewed-on: https://chromium-review.googlesource.com/1195844
Commit-Queue: Devlin <rdevlin.cronin@chromium.org>
Reviewed-by: default avatarKaran Bhatia <karandeepb@chromium.org>
Cr-Commit-Position: refs/heads/master@{#587822}
parent 7f98222a
...@@ -67,6 +67,7 @@ ExtensionActionRunner::ExtensionActionRunner(content::WebContents* web_contents) ...@@ -67,6 +67,7 @@ ExtensionActionRunner::ExtensionActionRunner(content::WebContents* web_contents)
browser_context_(web_contents->GetBrowserContext()), browser_context_(web_contents->GetBrowserContext()),
was_used_on_page_(false), was_used_on_page_(false),
ignore_active_tab_granted_(false), ignore_active_tab_granted_(false),
test_observer_(nullptr),
extension_registry_observer_(this), extension_registry_observer_(this),
weak_factory_(this) { weak_factory_(this) {
CHECK(web_contents); CHECK(web_contents);
...@@ -152,6 +153,8 @@ void ExtensionActionRunner::OnActiveTabPermissionGranted( ...@@ -152,6 +153,8 @@ void ExtensionActionRunner::OnActiveTabPermissionGranted(
void ExtensionActionRunner::OnWebRequestBlocked(const Extension* extension) { void ExtensionActionRunner::OnWebRequestBlocked(const Extension* extension) {
web_request_blocked_.insert(extension->id()); web_request_blocked_.insert(extension->id());
if (test_observer_)
test_observer_->OnBlockedActionAdded();
} }
int ExtensionActionRunner::GetBlockedActions(const Extension* extension) { int ExtensionActionRunner::GetBlockedActions(const Extension* extension) {
...@@ -231,6 +234,9 @@ void ExtensionActionRunner::RequestScriptInjection( ...@@ -231,6 +234,9 @@ void ExtensionActionRunner::RequestScriptInjection(
NotifyChange(extension); NotifyChange(extension);
was_used_on_page_ = true; was_used_on_page_ = true;
if (test_observer_)
test_observer_->OnBlockedActionAdded();
} }
void ExtensionActionRunner::RunPendingScriptsForExtension( void ExtensionActionRunner::RunPendingScriptsForExtension(
......
...@@ -43,6 +43,11 @@ class ExtensionRegistry; ...@@ -43,6 +43,11 @@ class ExtensionRegistry;
class ExtensionActionRunner : public content::WebContentsObserver, class ExtensionActionRunner : public content::WebContentsObserver,
public ExtensionRegistryObserver { public ExtensionRegistryObserver {
public: public:
class TestObserver {
public:
virtual void OnBlockedActionAdded() = 0;
};
explicit ExtensionActionRunner(content::WebContents* web_contents); explicit ExtensionActionRunner(content::WebContents* web_contents);
~ExtensionActionRunner() override; ~ExtensionActionRunner() override;
...@@ -88,6 +93,9 @@ class ExtensionActionRunner : public content::WebContentsObserver, ...@@ -88,6 +93,9 @@ class ExtensionActionRunner : public content::WebContentsObserver,
std::unique_ptr<ToolbarActionsBarBubbleDelegate::CloseAction> action) { std::unique_ptr<ToolbarActionsBarBubbleDelegate::CloseAction> action) {
default_bubble_close_action_for_testing_ = std::move(action); default_bubble_close_action_for_testing_ = std::move(action);
} }
void set_observer_for_testing(TestObserver* observer) {
test_observer_ = observer;
}
#if defined(UNIT_TEST) #if defined(UNIT_TEST)
// Only used in tests. // Only used in tests.
...@@ -208,6 +216,8 @@ class ExtensionActionRunner : public content::WebContentsObserver, ...@@ -208,6 +216,8 @@ class ExtensionActionRunner : public content::WebContentsObserver,
std::unique_ptr<ToolbarActionsBarBubbleDelegate::CloseAction> std::unique_ptr<ToolbarActionsBarBubbleDelegate::CloseAction>
default_bubble_close_action_for_testing_; default_bubble_close_action_for_testing_;
TestObserver* test_observer_;
ScopedObserver<ExtensionRegistry, ExtensionRegistryObserver> ScopedObserver<ExtensionRegistry, ExtensionRegistryObserver>
extension_registry_observer_; extension_registry_observer_;
......
...@@ -46,7 +46,8 @@ const char kBackgroundScriptSource[] = ...@@ -46,7 +46,8 @@ const char kBackgroundScriptSource[] =
" code: \"chrome.test.sendMessage('inject succeeded');\"\n" " code: \"chrome.test.sendMessage('inject succeeded');\"\n"
" });" " });"
"};\n" "};\n"
"chrome.tabs.onUpdated.addListener(listener);"; "chrome.tabs.onUpdated.addListener(listener);\n"
"chrome.test.sendMessage('ready');";
const char kContentScriptSource[] = const char kContentScriptSource[] =
"chrome.test.sendMessage('inject succeeded');"; "chrome.test.sendMessage('inject succeeded');";
...@@ -102,6 +103,12 @@ class ExtensionActionRunnerBrowserTest : public ExtensionBrowserTest { ...@@ -102,6 +103,12 @@ class ExtensionActionRunnerBrowserTest : public ExtensionBrowserTest {
InjectionType injection_type, InjectionType injection_type,
WithholdPermissions withhold_permissions); WithholdPermissions withhold_permissions);
void RunActiveScriptsTest(const std::string& name,
HostType host_type,
InjectionType injection_type,
WithholdPermissions withhold_permissions,
RequiresConsent requires_consent);
private: private:
std::vector<std::unique_ptr<TestExtensionDir>> test_extension_dirs_; std::vector<std::unique_ptr<TestExtensionDir>> test_extension_dirs_;
std::vector<const Extension*> extensions_; std::vector<const Extension*> extensions_;
...@@ -165,7 +172,15 @@ const Extension* ExtensionActionRunnerBrowserTest::CreateExtension( ...@@ -165,7 +172,15 @@ const Extension* ExtensionActionRunnerBrowserTest::CreateExtension(
injection_type == CONTENT_SCRIPT ? kContentScriptSource injection_type == CONTENT_SCRIPT ? kContentScriptSource
: kBackgroundScriptSource); : kBackgroundScriptSource);
const Extension* extension = LoadExtension(dir->UnpackedPath()); const Extension* extension = nullptr;
if (injection_type == CONTENT_SCRIPT) {
extension = LoadExtension(dir->UnpackedPath());
} else {
ExtensionTestMessageListener listener("ready", false);
extension = LoadExtension(dir->UnpackedPath());
EXPECT_TRUE(listener.WaitUntilSatisfied());
}
if (extension) { if (extension) {
test_extension_dirs_.push_back(std::move(dir)); test_extension_dirs_.push_back(std::move(dir));
extensions_.push_back(extension); extensions_.push_back(extension);
...@@ -181,176 +196,112 @@ const Extension* ExtensionActionRunnerBrowserTest::CreateExtension( ...@@ -181,176 +196,112 @@ const Extension* ExtensionActionRunnerBrowserTest::CreateExtension(
return extension; return extension;
} }
class ActiveScriptTester { void ExtensionActionRunnerBrowserTest::RunActiveScriptsTest(
public: const std::string& name,
ActiveScriptTester(const std::string& name, HostType host_type,
const Extension* extension, InjectionType injection_type,
Browser* browser, WithholdPermissions withhold_permissions,
RequiresConsent requires_consent, RequiresConsent requires_consent) {
InjectionType type); ASSERT_TRUE(embedded_test_server()->Start());
~ActiveScriptTester();
testing::AssertionResult Verify();
std::string name() const;
private:
// Returns the ExtensionActionRunner, or null if one does not exist.
ExtensionActionRunner* GetExtensionActionRunner();
// Returns true if the extension has a pending request with the
// ExtensionActionRunner.
bool WantsToRun();
// The name of the extension, and also the message it sends.
std::string name_;
// The extension associated with this tester.
const Extension* extension_;
// The browser the tester is running in. const Extension* extension =
Browser* browser_; CreateExtension(host_type, injection_type, withhold_permissions);
ASSERT_TRUE(extension);
// Whether or not the extension has permission to run the script without content::WebContents* web_contents =
// asking the user. browser()->tab_strip_model()->GetActiveWebContents();
RequiresConsent requires_consent_; ASSERT_TRUE(web_contents);
// All of these extensions should inject a script (either through content ExtensionActionRunner* runner =
// scripts or through chrome.tabs.executeScript()) that sends a message with ExtensionActionRunner::GetForWebContents(web_contents);
// the |kInjectSucceeded| message. ASSERT_TRUE(runner);
std::unique_ptr<ExtensionTestMessageListener> inject_success_listener_;
};
ActiveScriptTester::ActiveScriptTester(const std::string& name, const bool will_reply = false;
const Extension* extension, ExtensionTestMessageListener inject_success_listener(kInjectSucceeded,
Browser* browser, will_reply);
RequiresConsent requires_consent, auto navigate = [this]() {
InjectionType type) // Navigate to an URL (which matches the explicit host specified in the
: name_(name), // extension content_scripts_explicit_hosts). All extensions should
extension_(extension), // inject the script.
browser_(browser), ui_test_utils::NavigateToURL(browser(), embedded_test_server()->GetURL(
requires_consent_(requires_consent), "/extensions/test_file.html"));
inject_success_listener_( };
new ExtensionTestMessageListener(kInjectSucceeded,
false /* won't reply */)) { if (requires_consent == DOES_NOT_REQUIRE_CONSENT) {
inject_success_listener_->set_extension_id(extension->id()); // If the extension doesn't require explicit consent, it should inject
} // automatically right away.
navigate();
EXPECT_FALSE(runner->WantsToRun(extension));
EXPECT_TRUE(inject_success_listener.WaitUntilSatisfied());
EXPECT_FALSE(runner->WantsToRun(extension));
return;
}
ActiveScriptTester::~ActiveScriptTester() {} ASSERT_EQ(REQUIRES_CONSENT, requires_consent);
std::string ActiveScriptTester::name() const { class BlockedActionWaiter : public ExtensionActionRunner::TestObserver {
return name_; public:
} explicit BlockedActionWaiter(ExtensionActionRunner* runner)
: runner_(runner) {
runner_->set_observer_for_testing(this);
}
~BlockedActionWaiter() { runner_->set_observer_for_testing(nullptr); }
testing::AssertionResult ActiveScriptTester::Verify() { void Wait() { run_loop_.Run(); }
if (!extension_)
return testing::AssertionFailure() << "Could not load extension: " << name_;
content::WebContents* web_contents = private:
browser_ ? browser_->tab_strip_model()->GetActiveWebContents() : NULL; // ExtensionActionRunner::TestObserver:
if (!web_contents) void OnBlockedActionAdded() override { run_loop_.Quit(); }
return testing::AssertionFailure() << "No web contents.";
// Give the extension plenty of time to inject.
if (!RunAllPendingInRenderer(web_contents))
return testing::AssertionFailure() << "Could not run pending in renderer.";
// Make sure all running tasks are complete.
content::RunAllPendingInMessageLoop();
ExtensionActionRunner* runner = GetExtensionActionRunner();
if (!runner)
return testing::AssertionFailure() << "Could not find runner.";
bool wants_to_run = WantsToRun();
// An extension should have an action displayed iff it requires user consent.
if ((requires_consent_ == REQUIRES_CONSENT && !wants_to_run) ||
(requires_consent_ == DOES_NOT_REQUIRE_CONSENT && wants_to_run)) {
return testing::AssertionFailure()
<< "Improper wants to run for " << name_ << ": expected "
<< (requires_consent_ == REQUIRES_CONSENT) << ", found "
<< wants_to_run;
}
// If the extension has permission, we should be able to simply wait for it ExtensionActionRunner* runner_;
// to execute. base::RunLoop run_loop_;
if (requires_consent_ == DOES_NOT_REQUIRE_CONSENT) {
EXPECT_TRUE(inject_success_listener_->WaitUntilSatisfied());
return testing::AssertionSuccess();
}
// Otherwise, we don't have permission, and have to grant it. Ensure the DISALLOW_COPY_AND_ASSIGN(BlockedActionWaiter);
// script has *not* already executed. };
if (inject_success_listener_->was_satisfied()) {
return testing::AssertionFailure() << name_
<< "'s script ran without permission.";
}
// If we reach this point, we should always want to run. BlockedActionWaiter waiter(runner);
DCHECK(wants_to_run); navigate();
waiter.Wait();
EXPECT_TRUE(runner->WantsToRun(extension));
EXPECT_FALSE(inject_success_listener.was_satisfied());
// Grant permission by clicking on the extension action. // Grant permission by clicking on the extension action.
runner->RunAction(extension_, true); runner->RunAction(extension, true);
// Now, the extension should be able to inject the script. // Now, the extension should be able to inject the script.
EXPECT_TRUE(inject_success_listener_->WaitUntilSatisfied()); EXPECT_TRUE(inject_success_listener.WaitUntilSatisfied());
// The extension should no longer want to run. // The extension should no longer want to run.
wants_to_run = WantsToRun(); EXPECT_FALSE(runner->WantsToRun(extension));
if (wants_to_run) {
return testing::AssertionFailure()
<< "Extension " << name_ << " still wants to run after injecting.";
}
return testing::AssertionSuccess();
} }
ExtensionActionRunner* ActiveScriptTester::GetExtensionActionRunner() { // Load up different combinations of extensions, and verify that script
return ExtensionActionRunner::GetForWebContents( // injection is properly withheld and indicated to the user.
browser_ ? browser_->tab_strip_model()->GetActiveWebContents() : nullptr); // NOTE: Though these could be parameterized test cases, there's enough
// bits here that just having a helper method is quite a bit more readable.
IN_PROC_BROWSER_TEST_F(
ExtensionActionRunnerBrowserTest,
ActiveScriptsAreDisplayedAndDelayExecution_ExecuteScripts_AllHosts) {
RunActiveScriptsTest("execute_scripts_all_hosts", ALL_HOSTS, EXECUTE_SCRIPT,
WITHHOLD_PERMISSIONS, REQUIRES_CONSENT);
} }
IN_PROC_BROWSER_TEST_F(
bool ActiveScriptTester::WantsToRun() { ExtensionActionRunnerBrowserTest,
ExtensionActionRunner* runner = GetExtensionActionRunner(); ActiveScriptsAreDisplayedAndDelayExecution_ExecuteScripts_ExplicitHosts) {
return runner ? runner->WantsToRun(extension_) : false; RunActiveScriptsTest("execute_scripts_explicit_hosts", EXPLICIT_HOSTS,
EXECUTE_SCRIPT, WITHHOLD_PERMISSIONS, REQUIRES_CONSENT);
} }
IN_PROC_BROWSER_TEST_F(
IN_PROC_BROWSER_TEST_F(ExtensionActionRunnerBrowserTest, ExtensionActionRunnerBrowserTest,
ActiveScriptsAreDisplayedAndDelayExecution) { ActiveScriptsAreDisplayedAndDelayExecution_ContentScripts_AllHosts) {
// First, we load up four extensions: RunActiveScriptsTest("content_scripts_all_hosts", ALL_HOSTS, CONTENT_SCRIPT,
// - An extension that injects scripts into all hosts, WITHHOLD_PERMISSIONS, REQUIRES_CONSENT);
// - An extension that injects scripts into explicit hosts, }
// - An extension with a content script that runs on all hosts, IN_PROC_BROWSER_TEST_F(
// - An extension with a content script that runs on explicit hosts. ExtensionActionRunnerBrowserTest,
// All extensions should require user consent. ActiveScriptsAreDisplayedAndDelayExecution_ContentScripts_ExplicitHosts) {
std::vector<std::unique_ptr<ActiveScriptTester>> testers; RunActiveScriptsTest("content_scripts_explicit_hosts", EXPLICIT_HOSTS,
testers.push_back(std::make_unique<ActiveScriptTester>( CONTENT_SCRIPT, WITHHOLD_PERMISSIONS, REQUIRES_CONSENT);
"inject_scripts_all_hosts",
CreateExtension(ALL_HOSTS, EXECUTE_SCRIPT, WITHHOLD_PERMISSIONS),
browser(), REQUIRES_CONSENT, EXECUTE_SCRIPT));
testers.push_back(std::make_unique<ActiveScriptTester>(
"inject_scripts_explicit_hosts",
CreateExtension(EXPLICIT_HOSTS, EXECUTE_SCRIPT, WITHHOLD_PERMISSIONS),
browser(), REQUIRES_CONSENT, EXECUTE_SCRIPT));
testers.push_back(std::make_unique<ActiveScriptTester>(
"content_scripts_all_hosts",
CreateExtension(ALL_HOSTS, CONTENT_SCRIPT, WITHHOLD_PERMISSIONS),
browser(), REQUIRES_CONSENT, CONTENT_SCRIPT));
testers.push_back(std::make_unique<ActiveScriptTester>(
"content_scripts_explicit_hosts",
CreateExtension(EXPLICIT_HOSTS, CONTENT_SCRIPT, WITHHOLD_PERMISSIONS),
browser(), REQUIRES_CONSENT, CONTENT_SCRIPT));
// Navigate to an URL (which matches the explicit host specified in the
// extension content_scripts_explicit_hosts). All four extensions should
// inject the script.
ASSERT_TRUE(embedded_test_server()->Start());
ui_test_utils::NavigateToURL(
browser(), embedded_test_server()->GetURL("/extensions/test_file.html"));
for (const auto& tester : testers)
EXPECT_TRUE(tester->Verify()) << tester->name();
} }
// Test that removing an extension with pending injections a) removes the // Test that removing an extension with pending injections a) removes the
...@@ -546,25 +497,16 @@ IN_PROC_BROWSER_TEST_F(ExtensionActionRunnerBrowserTest, ...@@ -546,25 +497,16 @@ IN_PROC_BROWSER_TEST_F(ExtensionActionRunnerBrowserTest,
web_contents->GetController().GetLastCommittedEntry()->GetUniqueID()); web_contents->GetController().GetLastCommittedEntry()->GetUniqueID());
} }
// If we don't withhold permissions, extensions should execute normally.
IN_PROC_BROWSER_TEST_F(ExtensionActionRunnerBrowserTest, IN_PROC_BROWSER_TEST_F(ExtensionActionRunnerBrowserTest,
ScriptsExecuteWhenNoPermissionsWithheld) { ScriptsExecuteWhenNoPermissionsWithheld_ContentScripts) {
// If we don't withhold permissions, extensions should execute normally. RunActiveScriptsTest("content_scripts_all_hosts", ALL_HOSTS, CONTENT_SCRIPT,
std::vector<std::unique_ptr<ActiveScriptTester>> testers; DONT_WITHHOLD_PERMISSIONS, DOES_NOT_REQUIRE_CONSENT);
testers.push_back(std::make_unique<ActiveScriptTester>( }
"content_scripts_all_hosts", IN_PROC_BROWSER_TEST_F(ExtensionActionRunnerBrowserTest,
CreateExtension(ALL_HOSTS, CONTENT_SCRIPT, DONT_WITHHOLD_PERMISSIONS), ScriptsExecuteWhenNoPermissionsWithheld_ExecuteScripts) {
browser(), DOES_NOT_REQUIRE_CONSENT, CONTENT_SCRIPT)); RunActiveScriptsTest("execute_scripts_all_hosts", ALL_HOSTS, EXECUTE_SCRIPT,
testers.push_back(std::make_unique<ActiveScriptTester>( DONT_WITHHOLD_PERMISSIONS, DOES_NOT_REQUIRE_CONSENT);
"inject_scripts_all_hosts",
CreateExtension(ALL_HOSTS, EXECUTE_SCRIPT, DONT_WITHHOLD_PERMISSIONS),
browser(), DOES_NOT_REQUIRE_CONSENT, EXECUTE_SCRIPT));
ASSERT_TRUE(embedded_test_server()->Start());
ui_test_utils::NavigateToURL(
browser(), embedded_test_server()->GetURL("/extensions/test_file.html"));
for (const auto& tester : testers)
EXPECT_TRUE(tester->Verify()) << tester->name();
} }
// A version of the test with the flag off, in order to test that everything // A version of the test with the flag off, in order to test that everything
...@@ -579,23 +521,14 @@ class FlagOffExtensionActionRunnerBrowserTest ...@@ -579,23 +521,14 @@ class FlagOffExtensionActionRunnerBrowserTest
}; };
IN_PROC_BROWSER_TEST_F(FlagOffExtensionActionRunnerBrowserTest, IN_PROC_BROWSER_TEST_F(FlagOffExtensionActionRunnerBrowserTest,
ScriptsExecuteWhenFlagAbsent) { ScriptsExecuteWhenFlagAbsent_ContentScripts) {
std::vector<std::unique_ptr<ActiveScriptTester>> testers; RunActiveScriptsTest("content_scripts_all_hosts", ALL_HOSTS, CONTENT_SCRIPT,
testers.push_back(std::make_unique<ActiveScriptTester>( DONT_WITHHOLD_PERMISSIONS, DOES_NOT_REQUIRE_CONSENT);
"content_scripts_all_hosts", }
CreateExtension(ALL_HOSTS, CONTENT_SCRIPT, DONT_WITHHOLD_PERMISSIONS), IN_PROC_BROWSER_TEST_F(FlagOffExtensionActionRunnerBrowserTest,
browser(), DOES_NOT_REQUIRE_CONSENT, CONTENT_SCRIPT)); ScriptsExecuteWhenFlagAbsent_ExecuteScripts) {
testers.push_back(std::make_unique<ActiveScriptTester>( RunActiveScriptsTest("execute_scripts_all_hosts", ALL_HOSTS, EXECUTE_SCRIPT,
"inject_scripts_all_hosts", DONT_WITHHOLD_PERMISSIONS, DOES_NOT_REQUIRE_CONSENT);
CreateExtension(ALL_HOSTS, EXECUTE_SCRIPT, DONT_WITHHOLD_PERMISSIONS),
browser(), DOES_NOT_REQUIRE_CONSENT, EXECUTE_SCRIPT));
ASSERT_TRUE(embedded_test_server()->Start());
ui_test_utils::NavigateToURL(
browser(), embedded_test_server()->GetURL("/extensions/test_file.html"));
for (const auto& tester : testers)
EXPECT_TRUE(tester->Verify()) << tester->name();
} }
} // namespace extensions } // namespace extensions
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment