Commit dee92446 authored by Devlin Cronin's avatar Devlin Cronin Committed by Commit Bot

[Extensions] Add a test for webRequestBlocking listeners that are killed

Add a test that exercises behavior for a webRequestBlocking listener
that never returns, and then the tab is closed. Do this with two
flavors:
- A listener in the extension's background page, where the
  RenderProcessHost is shutdown.
- A listener in an extension tab, which is then closed.

Verify that in both cases, navigation is unblocked when the extension
listener goes away.

Inspired by, but not in any way a fix for, https://crbug.com/877543.

Change-Id: Id918d6d5f7bf5d8235f1386ef48838bffaa1b50c
Reviewed-on: https://chromium-review.googlesource.com/1188942
Commit-Queue: Devlin <rdevlin.cronin@chromium.org>
Reviewed-by: default avatarNasko Oskov <nasko@chromium.org>
Reviewed-by: default avatarKaran Bhatia <karandeepb@chromium.org>
Cr-Commit-Position: refs/heads/master@{#594509}
parent 5b0a138d
...@@ -43,6 +43,7 @@ ...@@ -43,6 +43,7 @@
#include "chrome/browser/search_engines/template_url_service_factory.h" #include "chrome/browser/search_engines/template_url_service_factory.h"
#include "chrome/browser/ui/browser.h" #include "chrome/browser/ui/browser.h"
#include "chrome/browser/ui/browser_navigator_params.h" #include "chrome/browser/ui/browser_navigator_params.h"
#include "chrome/browser/ui/browser_tabstrip.h"
#include "chrome/browser/ui/login/login_handler.h" #include "chrome/browser/ui/login/login_handler.h"
#include "chrome/browser/ui/search/local_ntp_test_utils.h" #include "chrome/browser/ui/search/local_ntp_test_utils.h"
#include "chrome/browser/ui/tabs/tab_strip_model.h" #include "chrome/browser/ui/tabs/tab_strip_model.h"
...@@ -99,6 +100,7 @@ ...@@ -99,6 +100,7 @@
#include "services/network/public/cpp/simple_url_loader.h" #include "services/network/public/cpp/simple_url_loader.h"
#include "services/network/test/test_url_loader_client.h" #include "services/network/test/test_url_loader_client.h"
#include "third_party/blink/public/platform/web_input_event.h" #include "third_party/blink/public/platform/web_input_event.h"
#include "url/url_constants.h"
#if defined(OS_CHROMEOS) #if defined(OS_CHROMEOS)
#include "chromeos/login/login_state.h" #include "chromeos/login/login_state.h"
...@@ -2177,4 +2179,207 @@ IN_PROC_BROWSER_TEST_F(ExtensionWebRequestMockedClockTest, ...@@ -2177,4 +2179,207 @@ IN_PROC_BROWSER_TEST_F(ExtensionWebRequestMockedClockTest,
redirect_successful_listener.extension_id_for_message()); redirect_successful_listener.extension_id_for_message());
} }
enum class ListenerType {
kBackgroundPage,
kTab,
};
class WebRequestUnresponsiveListenerTest
: public ExtensionWebRequestApiTest,
public ::testing::WithParamInterface<ListenerType> {
public:
WebRequestUnresponsiveListenerTest() {}
~WebRequestUnresponsiveListenerTest() override = default;
// ExtensionWebRequestApiTest:
void SetUpOnMainThread() override;
// Creates a test extension with a blocking webRequest listener.
const Extension* CreateExtension();
// Waits for the extension to report the listener is ready (possibly opening
// the listener's context).
void WaitForListenerReady(const Extension* extension);
// Ends the listener's process.
void EndListenerProcess(const Extension* extension);
private:
std::unique_ptr<TestExtensionDir> test_dir_;
std::unique_ptr<ExtensionTestMessageListener> listener_;
DISALLOW_COPY_AND_ASSIGN(WebRequestUnresponsiveListenerTest);
};
void WebRequestUnresponsiveListenerTest::SetUpOnMainThread() {
ExtensionWebRequestApiTest::SetUpOnMainThread();
test_dir_ = std::make_unique<TestExtensionDir>();
constexpr bool will_reply = false;
listener_ =
std::make_unique<ExtensionTestMessageListener>("ready", will_reply);
}
const Extension* WebRequestUnresponsiveListenerTest::CreateExtension() {
constexpr char kScript[] =
R"(chrome.webRequest.onBeforeRequest.addListener((request) => {
let i = 0;
while (true) { ++i; }
}, {urls: ['<all_urls>']}, ['blocking']);
chrome.test.sendMessage('ready');)";
constexpr char kManifest[] =
R"({
"name": "never gonna end",
"version": "0.1",
"manifest_version": 2,
"permissions": ["webRequest", "webRequestBlocking", "<all_urls>"]
%s
})";
constexpr char kManifestBackground[] =
R"(, "background": { "scripts" : ["script.js"] })";
ListenerType listener_type = GetParam();
test_dir_->WriteManifest(base::StringPrintf(
kManifest, listener_type == ListenerType::kBackgroundPage
? kManifestBackground
: ""));
if (listener_type == ListenerType::kTab) {
test_dir_->WriteFile(FILE_PATH_LITERAL("page.html"),
R"(<html><script src="script.js"></script></html>)");
}
test_dir_->WriteFile(FILE_PATH_LITERAL("script.js"), kScript);
return LoadExtension(test_dir_->UnpackedPath());
}
void WebRequestUnresponsiveListenerTest::WaitForListenerReady(
const Extension* extension) {
ListenerType listener_type = GetParam();
if (listener_type == ListenerType::kTab) {
GURL page_url = extension->GetResourceURL("page.html");
ui_test_utils::NavigateToURLWithDisposition(
browser(), page_url, WindowOpenDisposition::NEW_FOREGROUND_TAB,
ui_test_utils::BROWSER_TEST_WAIT_FOR_NAVIGATION);
EXPECT_EQ(2, browser()->tab_strip_model()->count());
// Refocus the original tab.
browser()->tab_strip_model()->ActivateTabAt(0, true);
}
EXPECT_TRUE(listener_->WaitUntilSatisfied());
}
void WebRequestUnresponsiveListenerTest::EndListenerProcess(
const Extension* extension) {
ListenerType listener_type = GetParam();
if (listener_type == ListenerType::kTab) {
content::WebContents* web_contents =
browser()->tab_strip_model()->GetWebContentsAt(1);
ASSERT_TRUE(web_contents);
EXPECT_EQ(extension->id(), web_contents->GetLastCommittedURL().host());
chrome::CloseWebContents(browser(), web_contents, true);
} else {
ExtensionHost* host =
ProcessManager::Get(profile())->GetBackgroundHostForExtension(
extension->id());
ASSERT_TRUE(host);
constexpr int exit_code = 0;
ASSERT_TRUE(host->render_process_host()->Shutdown(exit_code));
}
}
// Verify that if an extension has a blocking listener that never responds, and
// then the extension dies (due to the context closing, the process crashing,
// etc), the request is unblocked.
IN_PROC_BROWSER_TEST_P(WebRequestUnresponsiveListenerTest,
WebRequestListenerNeverResponds) {
ASSERT_TRUE(StartEmbeddedTestServer());
content::BrowserContext* browser_context = profile();
auto get_web_request_listener_count = [browser_context]() {
size_t result = 0;
base::RunLoop run_loop;
base::PostTaskWithTraitsAndReply(
FROM_HERE, {content::BrowserThread::IO},
base::BindOnce(
[](content::BrowserContext* browser_context, size_t* result) {
*result = ExtensionWebRequestEventRouter::GetInstance()
->GetListenerCountForTesting(
browser_context, "webRequest.onBeforeRequest");
},
browser_context, &result),
run_loop.QuitClosure());
run_loop.Run();
return result;
};
EXPECT_EQ(0u, get_web_request_listener_count());
const Extension* extension = CreateExtension();
ASSERT_TRUE(extension);
content::WebContents* first_web_contents =
browser()->tab_strip_model()->GetActiveWebContents();
const GURL initial_url(url::kAboutBlankURL);
EXPECT_EQ(initial_url, first_web_contents->GetLastCommittedURL());
WaitForListenerReady(extension);
EXPECT_EQ(1u, get_web_request_listener_count());
EXPECT_EQ(first_web_contents,
browser()->tab_strip_model()->GetActiveWebContents());
GURL example_com = embedded_test_server()->GetURL(
"example.com", "/extensions/test_file.html");
content::TestNavigationManager navigation_manager(first_web_contents,
example_com);
// Start the navigation to example.com. This should be intercepted by the
// extension, which will spin forever.
ui_test_utils::NavigateToURLWithDisposition(
browser(), example_com, WindowOpenDisposition::CURRENT_TAB,
ui_test_utils::BROWSER_TEST_NONE);
// Wait for navigation start, and make sure all systems receive the
// notification.
EXPECT_TRUE(navigation_manager.WaitForRequestStart());
navigation_manager.ResumeNavigation();
base::RunLoop().RunUntilIdle();
{
bool is_blocking_navigation = false;
base::RunLoop run_loop;
base::PostTaskWithTraitsAndReply(
FROM_HERE, {content::BrowserThread::IO},
base::BindOnce(
[](const GURL& url, bool* is_blocking_navigation) {
*is_blocking_navigation =
ExtensionWebRequestEventRouter::GetInstance()
->IsBlockingRequestForTesting(url);
},
example_com, &is_blocking_navigation),
run_loop.QuitClosure());
run_loop.Run();
EXPECT_TRUE(is_blocking_navigation);
}
// Verify the load started, but did not complete.
EXPECT_EQ(initial_url, first_web_contents->GetLastCommittedURL());
content::NavigationEntry* pending_entry =
first_web_contents->GetController().GetPendingEntry();
ASSERT_TRUE(pending_entry);
EXPECT_EQ(example_com, pending_entry->GetURL());
// Ending the listener process should let the navigation resume.
EndListenerProcess(extension);
navigation_manager.WaitForNavigationFinished();
EXPECT_EQ(example_com, first_web_contents->GetLastCommittedURL());
}
INSTANTIATE_TEST_CASE_P(,
WebRequestUnresponsiveListenerTest,
::testing::Values(ListenerType::kTab,
ListenerType::kBackgroundPage));
} // namespace extensions } // namespace extensions
...@@ -1533,12 +1533,6 @@ bool ExtensionWebRequestEventRouter::AddEventListener( ...@@ -1533,12 +1533,6 @@ bool ExtensionWebRequestEventRouter::AddEventListener(
return true; return true;
} }
size_t ExtensionWebRequestEventRouter::GetListenerCountForTesting(
void* browser_context,
const std::string& event_name) {
return listeners_[browser_context][event_name].size();
}
ExtensionWebRequestEventRouter::EventListener* ExtensionWebRequestEventRouter::EventListener*
ExtensionWebRequestEventRouter::FindEventListener(const EventListener::ID& id) { ExtensionWebRequestEventRouter::FindEventListener(const EventListener::ID& id) {
DCHECK_CURRENTLY_ON(BrowserThread::IO); DCHECK_CURRENTLY_ON(BrowserThread::IO);
...@@ -1635,6 +1629,21 @@ void ExtensionWebRequestEventRouter::AddCallbackForPageLoad( ...@@ -1635,6 +1629,21 @@ void ExtensionWebRequestEventRouter::AddCallbackForPageLoad(
callbacks_for_page_load_.push_back(callback); callbacks_for_page_load_.push_back(callback);
} }
bool ExtensionWebRequestEventRouter::IsBlockingRequestForTesting(
const GURL& url) {
for (const auto& iter : blocked_requests_) {
if (iter.second.request->url == url)
return true;
}
return false;
}
size_t ExtensionWebRequestEventRouter::GetListenerCountForTesting(
void* browser_context,
const std::string& event_name) {
return listeners_[browser_context][event_name].size();
}
bool ExtensionWebRequestEventRouter::IsPageLoad( bool ExtensionWebRequestEventRouter::IsPageLoad(
const WebRequestInfo& request) const { const WebRequestInfo& request) const {
return request.type == content::RESOURCE_TYPE_MAIN_FRAME; return request.type == content::RESOURCE_TYPE_MAIN_FRAME;
......
...@@ -468,6 +468,13 @@ class ExtensionWebRequestEventRouter { ...@@ -468,6 +468,13 @@ class ExtensionWebRequestEventRouter {
// The callback is then deleted. // The callback is then deleted.
void AddCallbackForPageLoad(const base::Closure& callback); void AddCallbackForPageLoad(const base::Closure& callback);
// Returns true if a request with the given |url| is being blocked by an
// extension with the webRequest API.
bool IsBlockingRequestForTesting(const GURL& url);
// Get the number of listeners - for testing only.
size_t GetListenerCountForTesting(void* browser_context,
const std::string& event_name);
private: private:
friend class WebRequestAPI; friend class WebRequestAPI;
FRIEND_TEST_ALL_PREFIXES(ExtensionWebRequestTest, FRIEND_TEST_ALL_PREFIXES(ExtensionWebRequestTest,
...@@ -673,10 +680,6 @@ class ExtensionWebRequestEventRouter { ...@@ -673,10 +680,6 @@ class ExtensionWebRequestEventRouter {
// Returns true if |request| was already signaled to some event handlers. // Returns true if |request| was already signaled to some event handlers.
bool WasSignaled(const WebRequestInfo& request) const; bool WasSignaled(const WebRequestInfo& request) const;
// Get the number of listeners - for testing only.
size_t GetListenerCountForTesting(void* browser_context,
const std::string& event_name);
// A map for each browser_context that maps an event name to a set of // A map for each browser_context that maps an event name to a set of
// extensions that are listening to that event. // extensions that are listening to that event.
ListenerMap listeners_; ListenerMap listeners_;
......
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