summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authormpcomplete@chromium.org <mpcomplete@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2009-08-25 23:45:58 +0000
committermpcomplete@chromium.org <mpcomplete@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2009-08-25 23:45:58 +0000
commiteaee0069eb73370de3c540096ef88632da15ff49 (patch)
treed9b331cf4d566c792b3ffa765909abdb30bc6139
parent75b44e4c2e995aa14c918ebe12ae74621b40d864 (diff)
downloadchromium_src-eaee0069eb73370de3c540096ef88632da15ff49.zip
chromium_src-eaee0069eb73370de3c540096ef88632da15ff49.tar.gz
chromium_src-eaee0069eb73370de3c540096ef88632da15ff49.tar.bz2
Delay request loading until all user scripts that request depends on are ready.
BUG=11547 TEST=See bug. Review URL: http://codereview.chromium.org/173239 git-svn-id: svn://svn.chromium.org/chrome/trunk/src@24372 0039d316-1c4b-4281-b951-d872f2087c98
-rw-r--r--chrome/browser/extensions/extensions_service.cc1
-rw-r--r--chrome/browser/extensions/user_script_listener.cc166
-rw-r--r--chrome/browser/extensions/user_script_listener.h90
-rw-r--r--chrome/browser/extensions/user_script_listener_unittest.cc306
-rw-r--r--chrome/browser/extensions/user_script_master.h4
-rw-r--r--chrome/browser/renderer_host/resource_dispatcher_host.cc10
-rw-r--r--chrome/browser/renderer_host/resource_dispatcher_host.h3
-rw-r--r--chrome/chrome.gyp3
8 files changed, 580 insertions, 3 deletions
diff --git a/chrome/browser/extensions/extensions_service.cc b/chrome/browser/extensions/extensions_service.cc
index 377423f..9b19ba9 100644
--- a/chrome/browser/extensions/extensions_service.cc
+++ b/chrome/browser/extensions/extensions_service.cc
@@ -585,7 +585,6 @@ void ExtensionsServiceBackend::LoadSingleExtension(
ReportExtensionLoaded(extension);
}
-
void ExtensionsServiceBackend::ReportExtensionLoadError(
const FilePath& extension_path, const std::string &error) {
::ReportExtensionLoadError(extension_path, error, alert_on_error_);
diff --git a/chrome/browser/extensions/user_script_listener.cc b/chrome/browser/extensions/user_script_listener.cc
new file mode 100644
index 0000000..94b58bf
--- /dev/null
+++ b/chrome/browser/extensions/user_script_listener.cc
@@ -0,0 +1,166 @@
+// Copyright (c) 2009 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/extensions/user_script_listener.h"
+
+#include "base/message_loop.h"
+#include "chrome/browser/extensions/extensions_service.h"
+#include "chrome/common/extensions/extension.h"
+#include "chrome/common/notification_service.h"
+#include "net/url_request/url_request.h"
+
+UserScriptListener::UserScriptListener(MessageLoop* ui_loop,
+ MessageLoop* io_loop,
+ ResourceDispatcherHost* rdh)
+ : ui_loop_(ui_loop),
+ io_loop_(io_loop),
+ resource_dispatcher_host_(rdh),
+ user_scripts_ready_(false) {
+ DCHECK(ui_loop_);
+ DCHECK_EQ(ui_loop_, MessageLoop::current());
+ DCHECK(resource_dispatcher_host_);
+
+ // IO loop can be NULL in unit tests.
+ if (!io_loop_)
+ io_loop_ = ui_loop;
+
+ registrar_.Add(this, NotificationType::EXTENSION_LOADED,
+ NotificationService::AllSources());
+ registrar_.Add(this, NotificationType::EXTENSION_UNLOADED,
+ NotificationService::AllSources());
+ registrar_.Add(this, NotificationType::USER_SCRIPTS_UPDATED,
+ NotificationService::AllSources());
+}
+
+bool UserScriptListener::ShouldStartRequest(URLRequest* request) {
+ DCHECK_EQ(io_loop_, MessageLoop::current());
+
+ // If it's a frame load, then we need to check the URL against the list of
+ // user scripts to see if we need to wait.
+ ResourceDispatcherHost::ExtraRequestInfo* info =
+ ResourceDispatcherHost::ExtraInfoForRequest(request);
+ DCHECK(info);
+
+ if (info->resource_type != ResourceType::MAIN_FRAME &&
+ info->resource_type != ResourceType::SUB_FRAME) {
+ return true;
+ }
+
+ if (user_scripts_ready_)
+ return true;
+
+ // User scripts aren't ready yet. If one of them wants to inject into this
+ // request, we'll need to wait for it before we can start this request.
+ bool found_match = false;
+ for (URLPatterns::iterator it = url_patterns_.begin();
+ it != url_patterns_.end(); ++it) {
+ if ((*it).MatchesUrl(request->url())) {
+ found_match = true;
+ break;
+ }
+ }
+
+ if (!found_match)
+ return true;
+
+ // Queue this request up.
+ delayed_request_ids_.push_front(ResourceDispatcherHost::GlobalRequestID(
+ info->process_id, info->request_id));
+ return false;
+}
+
+void UserScriptListener::StartDelayedRequests() {
+ DCHECK_EQ(io_loop_, MessageLoop::current());
+
+ user_scripts_ready_ = true;
+
+ if (resource_dispatcher_host_) {
+ for (DelayedRequests::iterator it = delayed_request_ids_.begin();
+ it != delayed_request_ids_.end(); ++it) {
+ URLRequest* request = resource_dispatcher_host_->GetURLRequest(*it);
+ if (request) {
+ // The request shouldn't have started (SUCCESS is the initial state).
+ DCHECK(request->status().status() == URLRequestStatus::SUCCESS);
+ request->Start();
+ }
+ }
+ }
+
+ delayed_request_ids_.clear();
+}
+
+void UserScriptListener::AppendNewURLPatterns(const URLPatterns& new_patterns) {
+ DCHECK_EQ(io_loop_, MessageLoop::current());
+
+ user_scripts_ready_ = false;
+ url_patterns_.insert(url_patterns_.end(),
+ new_patterns.begin(), new_patterns.end());
+}
+
+void UserScriptListener::ReplaceURLPatterns(const URLPatterns& patterns) {
+ DCHECK_EQ(io_loop_, MessageLoop::current());
+ url_patterns_ = patterns;
+}
+
+void UserScriptListener::CollectURLPatterns(Extension* extension,
+ URLPatterns* patterns) {
+ DCHECK_EQ(ui_loop_, MessageLoop::current());
+
+ const UserScriptList& scripts = extension->content_scripts();
+ for (UserScriptList::const_iterator iter = scripts.begin();
+ iter != scripts.end(); ++iter) {
+ patterns->insert(patterns->end(),
+ (*iter).url_patterns().begin(),
+ (*iter).url_patterns().end());
+ }
+}
+
+void UserScriptListener::Observe(NotificationType type,
+ const NotificationSource& source,
+ const NotificationDetails& details) {
+ DCHECK_EQ(ui_loop_, MessageLoop::current());
+
+ switch (type.value) {
+ case NotificationType::EXTENSION_LOADED: {
+ Extension* extension = Details<Extension>(details).ptr();
+ if (extension->content_scripts().empty())
+ return; // no new patterns from this extension.
+
+ URLPatterns new_patterns;
+ CollectURLPatterns(Details<Extension>(details).ptr(), &new_patterns);
+ if (!new_patterns.empty()) {
+ io_loop_->PostTask(FROM_HERE, NewRunnableMethod(this,
+ &UserScriptListener::AppendNewURLPatterns, new_patterns));
+ }
+ break;
+ }
+
+ case NotificationType::EXTENSION_UNLOADED: {
+ Extension* unloaded_extension = Details<Extension>(details).ptr();
+ if (unloaded_extension->content_scripts().empty())
+ return; // no patterns to delete for this extension.
+
+ // Clear all our patterns and reregister all the still-loaded extensions.
+ URLPatterns new_patterns;
+ ExtensionsService* service = Source<ExtensionsService>(source).ptr();
+ for (ExtensionList::const_iterator it = service->extensions()->begin();
+ it != service->extensions()->end(); ++it) {
+ if (*it != unloaded_extension)
+ CollectURLPatterns(*it, &new_patterns);
+ }
+ io_loop_->PostTask(FROM_HERE, NewRunnableMethod(this,
+ &UserScriptListener::ReplaceURLPatterns, new_patterns));
+ break;
+ }
+
+ case NotificationType::USER_SCRIPTS_UPDATED: {
+ io_loop_->PostTask(FROM_HERE,
+ NewRunnableMethod(this, &UserScriptListener::StartDelayedRequests));
+ break;
+ }
+
+ default:
+ NOTREACHED();
+ }
+}
diff --git a/chrome/browser/extensions/user_script_listener.h b/chrome/browser/extensions/user_script_listener.h
new file mode 100644
index 0000000..a742d3a
--- /dev/null
+++ b/chrome/browser/extensions/user_script_listener.h
@@ -0,0 +1,90 @@
+// Copyright (c) 2009 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_BROWSER_EXTENSIONS_USER_SCRIPT_LISTENER_H_
+#define CHROME_BROWSER_EXTENSIONS_USER_SCRIPT_LISTENER_H_
+
+#include <list>
+
+#include "base/ref_counted.h"
+#include "chrome/browser/renderer_host/resource_dispatcher_host.h"
+#include "chrome/common/extensions/url_pattern.h"
+#include "chrome/common/notification_registrar.h"
+
+class Extension;
+class MessageLoop;
+class URLRequest;
+
+// This class handles delaying of resource loads that depend on unloaded user
+// scripts. For each request that comes in, we check if it depends on a user
+// script, and if so, whether that user script is ready; if not, we delay the
+// request.
+//
+// This class lives mostly on the IO thread. It listens on the UI thread for
+// updates to loaded extensions.
+class UserScriptListener
+ : public base::RefCountedThreadSafe<UserScriptListener>,
+ public NotificationObserver {
+ public:
+ UserScriptListener(MessageLoop* ui_loop,
+ MessageLoop* io_loop,
+ ResourceDispatcherHost* rdh);
+
+ void OnResourceDispatcherHostGone() { resource_dispatcher_host_ = NULL; }
+
+ // Returns true if we're ready to service the request. Otherwise, if the
+ // request URL depends on any user scripts that haven't been loaded yet, we
+ // will delay the request until we're ready.
+ bool ShouldStartRequest(URLRequest* request);
+
+ private:
+ typedef std::list<URLPattern> URLPatterns;
+
+ // Resume any requests that we delayed in order to wait for user scripts.
+ void StartDelayedRequests();
+
+ // Appends new url patterns to our list, also setting user_scripts_ready_
+ // to false.
+ void AppendNewURLPatterns(const URLPatterns& new_patterns);
+
+ // Replaces our url pattern list. This is only used when patterns have been
+ // deleted, so user_scripts_ready_ remains unchanged.
+ void ReplaceURLPatterns(const URLPatterns& patterns);
+
+ MessageLoop* ui_loop_;
+ MessageLoop* io_loop_;
+ ResourceDispatcherHost* resource_dispatcher_host_;
+
+ // A list of every request that we delayed. Will be flushed when user scripts
+ // are ready.
+ typedef std::list<ResourceDispatcherHost::GlobalRequestID> DelayedRequests;
+ DelayedRequests delayed_request_ids_;
+
+ // TODO(mpcomplete): the rest of this stuff should really be per-profile, but
+ // the complexity doesn't seem worth it at this point.
+
+ // True if the user scripts contained in |url_patterns_| are ready for
+ // injection.
+ bool user_scripts_ready_;
+
+ // A list of URL patterns that have will have user scripts applied to them.
+ URLPatterns url_patterns_;
+
+ // --- UI thread:
+
+ // Helper to collect the extension's user script URL patterns in a list and
+ // return it.
+ void CollectURLPatterns(Extension* extension, URLPatterns* patterns);
+
+ // NotificationObserver
+ virtual void Observe(NotificationType type,
+ const NotificationSource& source,
+ const NotificationDetails& details);
+
+ NotificationRegistrar registrar_;
+
+ DISALLOW_EVIL_CONSTRUCTORS(UserScriptListener);
+};
+
+#endif // CHROME_BROWSER_EXTENSIONS_USER_SCRIPT_LISTENER_H_
diff --git a/chrome/browser/extensions/user_script_listener_unittest.cc b/chrome/browser/extensions/user_script_listener_unittest.cc
new file mode 100644
index 0000000..db49886
--- /dev/null
+++ b/chrome/browser/extensions/user_script_listener_unittest.cc
@@ -0,0 +1,306 @@
+// Copyright (c) 2009 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include <algorithm>
+#include <vector>
+
+#include "base/message_loop.h"
+#include "base/thread.h"
+#include "chrome/browser/extensions/extensions_service.h"
+#include "chrome/browser/renderer_host/resource_dispatcher_host.h"
+#include "chrome/common/chrome_paths.h"
+#include "chrome/common/chrome_plugin_lib.h"
+#include "chrome/common/notification_service.h"
+#include "chrome/common/render_messages.h"
+#include "chrome/test/testing_profile.h"
+#include "ipc/ipc_message.h"
+#include "net/url_request/url_request.h"
+#include "net/url_request/url_request_test_job.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+// A simple test URLRequestJob. We don't care what it does, only that whether it
+// starts and finishes.
+class SimpleTestJob : public URLRequestTestJob {
+ public:
+ explicit SimpleTestJob(URLRequest* request)
+ : URLRequestTestJob(request, test_headers(), test_data_1(), true) {}
+};
+
+class MockUserScriptMaster : public UserScriptMaster {
+public:
+ MockUserScriptMaster(MessageLoop* worker, const FilePath& script_dir) :
+ UserScriptMaster(worker, script_dir) { }
+
+ virtual void StartScan() {
+ // Do nothing. We want to manually control when scans occur.
+ }
+ void TestStartScan() {
+ UserScriptMaster::StartScan();
+ }
+};
+
+class MockIOThread : public base::Thread {
+ public:
+ MockIOThread() : base::Thread("IO") {}
+ virtual ~MockIOThread() { Stop(); }
+
+ private:
+ virtual void Init() {
+ service_.reset(new NotificationService());
+ }
+ virtual void CleanUp() {
+ ChromePluginLib::UnloadAllPlugins();
+ service_.reset();
+ }
+
+ scoped_ptr<NotificationService> service_;
+};
+
+// A class to help with making and handling resource requests.
+class ResourceDispatcherHostTester :
+ public ResourceDispatcherHost::Receiver,
+ public URLRequest::Interceptor,
+ public base::RefCountedThreadSafe<ResourceDispatcherHostTester> {
+ public:
+ ResourceDispatcherHostTester(MessageLoop* io_loop)
+ : Receiver(ChildProcessInfo::RENDER_PROCESS),
+ host_(io_loop),
+ ui_loop_(MessageLoop::current()),
+ io_loop_(io_loop) {
+ URLRequest::RegisterRequestInterceptor(this);
+ }
+ virtual ~ResourceDispatcherHostTester() {
+ URLRequest::UnregisterRequestInterceptor(this);
+ }
+
+ void MakeTestRequest(int request_id, const GURL& url) {
+ io_loop_->PostTask(FROM_HERE, NewRunnableMethod(
+ this, &ResourceDispatcherHostTester::MakeTestRequestOnIOThread,
+ request_id, url));
+ MessageLoop::current()->Run(); // wait for Quit from IO thread
+ }
+
+ void WaitForScan(MockUserScriptMaster* master) {
+ master->TestStartScan();
+ MessageLoop::current()->RunAllPending(); // run the scan
+ io_loop_->PostTask(FROM_HERE, NewRunnableMethod(
+ this, &ResourceDispatcherHostTester::RunPending));
+ MessageLoop::current()->Run(); // wait for Quit from IO thread
+ }
+
+ bool IsRequestStarted(int request_id) {
+ return std::find(started_requests_.begin(), started_requests_.end(),
+ request_id) != started_requests_.end();
+ }
+
+ bool IsRequestComplete(int request_id) {
+ return std::find(completed_requests_.begin(), completed_requests_.end(),
+ request_id) != completed_requests_.end();
+ }
+
+ private:
+ // URLRequest::Interceptor
+ virtual URLRequestJob* MaybeIntercept(URLRequest* request) {
+ return new SimpleTestJob(request);
+ }
+
+ // ResourceDispatcherHost::Receiver implementation
+ virtual bool Send(IPC::Message* msg) {
+ IPC_BEGIN_MESSAGE_MAP(ResourceDispatcherHostTester, *msg)
+ IPC_MESSAGE_HANDLER(ViewMsg_Resource_ReceivedResponse, OnReceivedResponse)
+ IPC_MESSAGE_HANDLER(ViewMsg_Resource_RequestComplete, OnRequestComplete)
+ IPC_END_MESSAGE_MAP()
+ delete msg;
+ return true;
+ }
+
+ URLRequestContext* GetRequestContext(
+ uint32 request_id,
+ const ViewHostMsg_Resource_Request& request_data) {
+ return NULL;
+ }
+
+ // TODO(mpcomplete): Some of this stuff is copied from
+ // resource_dispatcher_host_unittest.cc. Consider sharing.
+ static ViewHostMsg_Resource_Request CreateResourceRequest(const char* method,
+ const GURL& url) {
+ ViewHostMsg_Resource_Request request;
+ request.method = std::string(method);
+ request.url = url;
+ request.first_party_for_cookies = url; // bypass third-party cookie
+ // blocking
+ request.resource_type = ResourceType::MAIN_FRAME;
+ request.load_flags = 0;
+ // init the rest to default values to prevent getting UMR.
+ request.frame_origin = "null";
+ request.main_frame_origin = "null";
+ request.origin_pid = 0;
+ request.request_context = 0;
+ return request;
+ }
+
+ void RunPending() {
+ MessageLoop::current()->SetNestableTasksAllowed(true);
+ MessageLoop::current()->RunAllPending();
+ MessageLoop::current()->SetNestableTasksAllowed(false);
+
+ // return control to UI thread.
+ ui_loop_->PostTask(FROM_HERE, new MessageLoop::QuitTask());
+ }
+
+ void MakeTestRequestOnIOThread(int request_id, const GURL& url) {
+ ViewHostMsg_Resource_Request request = CreateResourceRequest("GET", url);
+ ViewHostMsg_RequestResource msg(0, request_id, request);
+ bool msg_was_ok;
+ host_.OnMessageReceived(msg, this, &msg_was_ok);
+ RunPending();
+ }
+
+ void OnReceivedResponse(int request_id,
+ const ResourceResponseHead& response_head) {
+ started_requests_.push_back(request_id);
+ }
+
+ void OnRequestComplete(int request_id,
+ const URLRequestStatus& status,
+ const std::string& security_info) {
+ completed_requests_.push_back(request_id);
+ }
+
+ ResourceDispatcherHost host_;
+ MessageLoop* ui_loop_;
+ MessageLoop* io_loop_;
+
+ // Note: these variables are accessed on both threads, but since we only
+ // one thread executes at a time, they are safe.
+ std::vector<int> started_requests_;
+ std::vector<int> completed_requests_;
+};
+
+class UserScriptListenerTest : public testing::Test {
+ public:
+ virtual void SetUp() {
+ io_thread_.reset(new MockIOThread());
+ base::Thread::Options options(MessageLoop::TYPE_IO, 0);
+ io_thread_->StartWithOptions(options);
+ DCHECK(io_thread_->message_loop());
+ DCHECK(io_thread_->IsRunning());
+
+ FilePath install_dir = profile_.GetPath()
+ .AppendASCII(ExtensionsService::kInstallDirectoryName);
+
+ resource_tester_ =
+ new ResourceDispatcherHostTester(io_thread_->message_loop());
+
+ master_ = new MockUserScriptMaster(&loop_, install_dir);
+
+ service_ = new ExtensionsService(&profile_,
+ CommandLine::ForCurrentProcess(),
+ profile_.GetPrefs(),
+ install_dir,
+ &loop_,
+ &loop_,
+ false);
+ service_->set_extensions_enabled(true);
+ service_->set_show_extensions_prompts(false);
+ service_->ClearProvidersForTesting();
+ service_->Init();
+ }
+
+ virtual void TearDown() {
+ io_thread_.reset();
+ resource_tester_ = NULL;
+ }
+
+ protected:
+ TestingProfile profile_;
+ MessageLoopForUI loop_;
+ scoped_ptr<MockIOThread> io_thread_;
+ scoped_refptr<ResourceDispatcherHostTester> resource_tester_;
+ scoped_refptr<MockUserScriptMaster> master_;
+ scoped_refptr<ExtensionsService> service_;
+};
+
+// Loads a single extension and ensures that requests to URLs with content
+// scripts are delayed.
+TEST_F(UserScriptListenerTest, SingleExtension) {
+ FilePath extensions_path;
+ ASSERT_TRUE(PathService::Get(chrome::DIR_TEST_DATA, &extensions_path));
+ extensions_path = extensions_path.AppendASCII("extensions");
+ FilePath ext1 = extensions_path
+ .AppendASCII("good")
+ .AppendASCII("Extensions")
+ .AppendASCII("behllobkkfkfnphdnhnkndlbkcpglgmj")
+ .AppendASCII("1.0.0.0");
+ service_->LoadExtension(ext1);
+ loop_.RunAllPending();
+ ASSERT_EQ(1u, service_->extensions()->size());
+
+ // Our extension has a content script on google.com. That request should be
+ // delayed.
+ resource_tester_->MakeTestRequest(0, GURL("http://google.com/"));
+ resource_tester_->MakeTestRequest(1, GURL("http://yahoo.com/"));
+
+ EXPECT_FALSE(resource_tester_->IsRequestStarted(0));
+ EXPECT_TRUE(resource_tester_->IsRequestStarted(1));
+ EXPECT_TRUE(resource_tester_->IsRequestComplete(1));
+
+ // After scanning, the user scripts should be ready and the request can
+ // go through.
+ resource_tester_->WaitForScan(master_.get());
+
+ EXPECT_TRUE(resource_tester_->IsRequestStarted(0));
+ EXPECT_TRUE(resource_tester_->IsRequestComplete(0));
+}
+
+// Loads a single extension and ensures that requests to URLs with content
+// scripts are delayed.
+TEST_F(UserScriptListenerTest, UnloadExtension) {
+ FilePath extensions_path;
+ ASSERT_TRUE(PathService::Get(chrome::DIR_TEST_DATA, &extensions_path));
+ extensions_path = extensions_path.AppendASCII("extensions");
+
+ FilePath ext1 = extensions_path
+ .AppendASCII("good")
+ .AppendASCII("Extensions")
+ .AppendASCII("behllobkkfkfnphdnhnkndlbkcpglgmj")
+ .AppendASCII("1.0.0.0");
+ service_->LoadExtension(ext1);
+ loop_.RunAllPending();
+ ASSERT_EQ(1u, service_->extensions()->size());
+
+ FilePath ext2 = extensions_path
+ .AppendASCII("good")
+ .AppendASCII("Extensions")
+ .AppendASCII("bjafgdebaacbbbecmhlhpofkepfkgcpa")
+ .AppendASCII("1.0");
+ service_->LoadExtension(ext2);
+ loop_.RunAllPending();
+ ASSERT_EQ(2u, service_->extensions()->size());
+
+ // Our extension has a content script on google.com. That request should be
+ // delayed.
+ resource_tester_->MakeTestRequest(0, GURL("http://google.com/"));
+ resource_tester_->MakeTestRequest(1, GURL("http://yahoo.com/"));
+
+ EXPECT_FALSE(resource_tester_->IsRequestStarted(0));
+ EXPECT_TRUE(resource_tester_->IsRequestStarted(1));
+ EXPECT_TRUE(resource_tester_->IsRequestComplete(1));
+
+ // Unload the first extension and run a scan. Request should complete.
+ service_->UnloadExtension("behllobkkfkfnphdnhnkndlbkcpglgmj");
+ resource_tester_->WaitForScan(master_.get());
+
+ EXPECT_TRUE(resource_tester_->IsRequestStarted(0));
+ EXPECT_TRUE(resource_tester_->IsRequestComplete(0));
+
+ // Make the same requests, and they should complete instantly.
+ resource_tester_->MakeTestRequest(2, GURL("http://google.com/"));
+ resource_tester_->MakeTestRequest(3, GURL("http://yahoo.com/"));
+
+ EXPECT_TRUE(resource_tester_->IsRequestStarted(2));
+ EXPECT_TRUE(resource_tester_->IsRequestComplete(2));
+ EXPECT_TRUE(resource_tester_->IsRequestStarted(3));
+ EXPECT_TRUE(resource_tester_->IsRequestComplete(3));
+}
diff --git a/chrome/browser/extensions/user_script_master.h b/chrome/browser/extensions/user_script_master.h
index 557032e..56aab84 100644
--- a/chrome/browser/extensions/user_script_master.h
+++ b/chrome/browser/extensions/user_script_master.h
@@ -28,7 +28,7 @@ class UserScriptMaster : public base::RefCounted<UserScriptMaster>,
// script-reloading worker on as well as the path the scripts live in.
// These are normally the file thread and a directory inside the profile.
UserScriptMaster(MessageLoop* worker, const FilePath& script_dir);
- ~UserScriptMaster();
+ virtual ~UserScriptMaster();
// Add a watched directory. All scripts will be reloaded when any file in
// this directory changes.
@@ -36,7 +36,7 @@ class UserScriptMaster : public base::RefCounted<UserScriptMaster>,
// Kicks off a process on the file thread to reload scripts from disk
// into a new chunk of shared memory and notify renderers.
- void StartScan();
+ virtual void StartScan();
// Gets the segment of shared memory for the scripts.
base::SharedMemory* GetSharedMemory() const {
diff --git a/chrome/browser/renderer_host/resource_dispatcher_host.cc b/chrome/browser/renderer_host/resource_dispatcher_host.cc
index 0066ad6..b205fd0 100644
--- a/chrome/browser/renderer_host/resource_dispatcher_host.cc
+++ b/chrome/browser/renderer_host/resource_dispatcher_host.cc
@@ -21,6 +21,7 @@
#include "chrome/browser/download/download_manager.h"
#include "chrome/browser/download/download_request_manager.h"
#include "chrome/browser/download/save_file_manager.h"
+#include "chrome/browser/extensions/user_script_listener.h"
#include "chrome/browser/external_protocol_handler.h"
#include "chrome/browser/in_process_webkit/webkit_thread.h"
#include "chrome/browser/login_prompt.h"
@@ -252,6 +253,8 @@ ResourceDispatcherHost::ResourceDispatcherHost(MessageLoop* io_loop)
download_request_manager_(new DownloadRequestManager(io_loop, ui_loop_)),
ALLOW_THIS_IN_INITIALIZER_LIST(
save_file_manager_(new SaveFileManager(ui_loop_, io_loop, this))),
+ ALLOW_THIS_IN_INITIALIZER_LIST(user_script_listener_(
+ new UserScriptListener(ui_loop_, io_loop, this))),
safe_browsing_(new SafeBrowsingService),
webkit_thread_(new WebKitThread),
request_id_(-1),
@@ -282,6 +285,8 @@ ResourceDispatcherHost::~ResourceDispatcherHost() {
iter != ids.end(); ++iter) {
CancelBlockedRequestsForRoute(iter->first, iter->second);
}
+
+ user_script_listener_->OnResourceDispatcherHostGone();
}
void ResourceDispatcherHost::Initialize() {
@@ -1247,6 +1252,11 @@ void ResourceDispatcherHost::BeginRequestInternal(URLRequest* request) {
// its content should be filtered) and start it itself.
return;
}
+ if (!user_script_listener_->ShouldStartRequest(request)) {
+ // This request depends on some user scripts that haven't loaded yet. The
+ // UserScriptListener will resume the request when they're ready.
+ return;
+ }
request->Start();
// Make sure we have the load state monitor running
diff --git a/chrome/browser/renderer_host/resource_dispatcher_host.h b/chrome/browser/renderer_host/resource_dispatcher_host.h
index f7dc92e..5fff4b9 100644
--- a/chrome/browser/renderer_host/resource_dispatcher_host.h
+++ b/chrome/browser/renderer_host/resource_dispatcher_host.h
@@ -37,6 +37,7 @@ class PluginService;
class SafeBrowsingService;
class SaveFileManager;
class SSLClientAuthHandler;
+class UserScriptListener;
class URLRequestContext;
class WebKitThread;
struct ViewHostMsg_Resource_Request;
@@ -537,6 +538,8 @@ class ResourceDispatcherHost : public URLRequest::Delegate {
// We own the save file manager.
scoped_refptr<SaveFileManager> save_file_manager_;
+ scoped_refptr<UserScriptListener> user_script_listener_;
+
scoped_refptr<SafeBrowsingService> safe_browsing_;
// We own the WebKit thread and see to its destruction.
diff --git a/chrome/chrome.gyp b/chrome/chrome.gyp
index c53efdc..6fc8676 100644
--- a/chrome/chrome.gyp
+++ b/chrome/chrome.gyp
@@ -1116,6 +1116,8 @@
'browser/extensions/sandboxed_extension_unpacker.h',
'browser/extensions/theme_preview_infobar_delegate.cc',
'browser/extensions/theme_preview_infobar_delegate.h',
+ 'browser/extensions/user_script_listener.cc',
+ 'browser/extensions/user_script_listener.h',
'browser/extensions/user_script_master.cc',
'browser/extensions/user_script_master.h',
'browser/external_protocol_handler.cc',
@@ -3970,6 +3972,7 @@
'browser/extensions/extension_ui_unittest.cc',
'browser/extensions/extension_updater_unittest.cc',
'browser/extensions/extensions_service_unittest.cc',
+ 'browser/extensions/user_script_listener_unittest.cc',
'browser/extensions/user_script_master_unittest.cc',
'browser/find_backend_unittest.cc',
'browser/first_run_migration_mac_unittest.mm',