diff options
author | Jocelyn Turcotte <jocelyn.turcotte@digia.com> | 2014-08-08 14:30:41 +0200 |
---|---|---|
committer | Jocelyn Turcotte <jocelyn.turcotte@digia.com> | 2014-08-12 13:49:54 +0200 |
commit | ab0a50979b9eb4dfa3320eff7e187e41efedf7a9 (patch) | |
tree | 498dfb8a97ff3361a9f7486863a52bb4e26bb898 /chromium/content/browser/service_worker | |
parent | 4ce69f7403811819800e7c5ae1318b2647e778d1 (diff) |
Update Chromium to beta version 37.0.2062.68
Change-Id: I188e3b5aff1bec75566014291b654eb19f5bc8ca
Reviewed-by: Andras Becsi <andras.becsi@digia.com>
Diffstat (limited to 'chromium/content/browser/service_worker')
82 files changed, 14961 insertions, 1307 deletions
diff --git a/chromium/content/browser/service_worker/BUILD.gn b/chromium/content/browser/service_worker/BUILD.gn new file mode 100644 index 00000000000..d9f206615fb --- /dev/null +++ b/chromium/content/browser/service_worker/BUILD.gn @@ -0,0 +1,12 @@ +# Copyright 2014 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. + +import("//third_party/protobuf/proto_library.gni") + +proto_library("database_proto") { + sources = [ + "service_worker_database.proto", + ] +} + diff --git a/chromium/content/browser/service_worker/DEPS b/chromium/content/browser/service_worker/DEPS new file mode 100644 index 00000000000..743a2f3baec --- /dev/null +++ b/chromium/content/browser/service_worker/DEPS @@ -0,0 +1,3 @@ +include_rules = [ + "+third_party/leveldatabase", +] diff --git a/chromium/content/browser/service_worker/OWNERS b/chromium/content/browser/service_worker/OWNERS index 633b8a4fd96..f9916062308 100644 --- a/chromium/content/browser/service_worker/OWNERS +++ b/chromium/content/browser/service_worker/OWNERS @@ -1,3 +1,12 @@ -alecflett@chromium.org -kinuko@chromium.org michaeln@chromium.org +falken@chromium.org + +# may not be available +kinuko@chromium.org + +# per-file owners +per-file embedded_worker*=horo@chromium.org +per-file service_worker_process_manager*=horo@chromium.org +per-file service_worker_internals_ui*=horo@chromium.org +per-file service_worker_database*=nhiroki@chromium.org +per-file service_worker_storage*=nhiroki@chromium.org diff --git a/chromium/content/browser/service_worker/embedded_worker_instance.cc b/chromium/content/browser/service_worker/embedded_worker_instance.cc index 07998845986..fd2d201a660 100644 --- a/chromium/content/browser/service_worker/embedded_worker_instance.cc +++ b/chromium/content/browser/service_worker/embedded_worker_instance.cc @@ -4,40 +4,144 @@ #include "content/browser/service_worker/embedded_worker_instance.h" +#include "base/bind_helpers.h" +#include "content/browser/devtools/embedded_worker_devtools_manager.h" #include "content/browser/service_worker/embedded_worker_registry.h" +#include "content/browser/service_worker/service_worker_context_core.h" +#include "content/common/service_worker/embedded_worker_messages.h" +#include "content/public/browser/browser_thread.h" +#include "content/public/browser/render_process_host.h" +#include "ipc/ipc_message.h" #include "url/gurl.h" namespace content { -EmbeddedWorkerInstance::~EmbeddedWorkerInstance() { - registry_->RemoveWorker(embedded_worker_id_); +namespace { + +// Functor to sort by the .second element of a struct. +struct SecondGreater { + template <typename Value> + bool operator()(const Value& lhs, const Value& rhs) { + return lhs.second > rhs.second; + } +}; + +void NotifyWorkerContextStarted(int worker_process_id, int worker_route_id) { + if (!BrowserThread::CurrentlyOn(BrowserThread::UI)) { + BrowserThread::PostTask( + BrowserThread::UI, + FROM_HERE, + base::Bind( + NotifyWorkerContextStarted, worker_process_id, worker_route_id)); + return; + } + EmbeddedWorkerDevToolsManager::GetInstance()->WorkerContextStarted( + worker_process_id, worker_route_id); } -bool EmbeddedWorkerInstance::Start( +void NotifyWorkerDestroyed(int worker_process_id, int worker_route_id) { + if (!BrowserThread::CurrentlyOn(BrowserThread::UI)) { + BrowserThread::PostTask( + BrowserThread::UI, + FROM_HERE, + base::Bind(NotifyWorkerDestroyed, worker_process_id, worker_route_id)); + return; + } + EmbeddedWorkerDevToolsManager::GetInstance()->WorkerDestroyed( + worker_process_id, worker_route_id); +} + +void RegisterToWorkerDevToolsManager( + int process_id, + const ServiceWorkerContextCore* const service_worker_context, int64 service_worker_version_id, - const GURL& script_url) { + const base::Callback<void(int worker_devtools_agent_route_id, + bool pause_on_start)>& callback) { + if (!BrowserThread::CurrentlyOn(BrowserThread::UI)) { + BrowserThread::PostTask(BrowserThread::UI, + FROM_HERE, + base::Bind(RegisterToWorkerDevToolsManager, + process_id, + service_worker_context, + service_worker_version_id, + callback)); + return; + } + int worker_devtools_agent_route_id = MSG_ROUTING_NONE; + bool pause_on_start = false; + if (RenderProcessHost* rph = RenderProcessHost::FromID(process_id)) { + // |rph| may be NULL in unit tests. + worker_devtools_agent_route_id = rph->GetNextRoutingID(); + pause_on_start = + EmbeddedWorkerDevToolsManager::GetInstance()->ServiceWorkerCreated( + process_id, + worker_devtools_agent_route_id, + EmbeddedWorkerDevToolsManager::ServiceWorkerIdentifier( + service_worker_context, service_worker_version_id)); + } + BrowserThread::PostTask( + BrowserThread::IO, + FROM_HERE, + base::Bind(callback, worker_devtools_agent_route_id, pause_on_start)); +} + +} // namespace + +EmbeddedWorkerInstance::~EmbeddedWorkerInstance() { + if (status_ == STARTING || status_ == RUNNING) + Stop(); + if (worker_devtools_agent_route_id_ != MSG_ROUTING_NONE) + NotifyWorkerDestroyed(process_id_, worker_devtools_agent_route_id_); + if (context_ && process_id_ != -1) + context_->process_manager()->ReleaseWorkerProcess(embedded_worker_id_); + registry_->RemoveWorker(process_id_, embedded_worker_id_); +} + +void EmbeddedWorkerInstance::Start(int64 service_worker_version_id, + const GURL& scope, + const GURL& script_url, + const std::vector<int>& possible_process_ids, + const StatusCallback& callback) { + if (!context_) { + callback.Run(SERVICE_WORKER_ERROR_ABORT); + return; + } DCHECK(status_ == STOPPED); - if (!ChooseProcess()) - return false; status_ = STARTING; - bool success = registry_->StartWorker( - process_id_, + scoped_ptr<EmbeddedWorkerMsg_StartWorker_Params> params( + new EmbeddedWorkerMsg_StartWorker_Params()); + params->embedded_worker_id = embedded_worker_id_; + params->service_worker_version_id = service_worker_version_id; + params->scope = scope; + params->script_url = script_url; + params->worker_devtools_agent_route_id = MSG_ROUTING_NONE; + params->pause_on_start = false; + context_->process_manager()->AllocateWorkerProcess( embedded_worker_id_, - service_worker_version_id, - script_url); - if (!success) { - status_ = STOPPED; - process_id_ = -1; - } - return success; + SortProcesses(possible_process_ids), + script_url, + base::Bind(&EmbeddedWorkerInstance::RunProcessAllocated, + weak_factory_.GetWeakPtr(), + context_, + base::Passed(¶ms), + callback)); } -bool EmbeddedWorkerInstance::Stop() { +ServiceWorkerStatusCode EmbeddedWorkerInstance::Stop() { DCHECK(status_ == STARTING || status_ == RUNNING); - const bool success = registry_->StopWorker(process_id_, embedded_worker_id_); - if (success) + ServiceWorkerStatusCode status = + registry_->StopWorker(process_id_, embedded_worker_id_); + if (status == SERVICE_WORKER_OK) status_ = STOPPING; - return success; + return status; +} + +ServiceWorkerStatusCode EmbeddedWorkerInstance::SendMessage( + const IPC::Message& message) { + DCHECK(status_ == RUNNING); + return registry_->Send(process_id_, + new EmbeddedWorkerContextMsg_MessageToWorker( + thread_id_, embedded_worker_id_, message)); } void EmbeddedWorkerInstance::AddProcessReference(int process_id) { @@ -58,42 +162,167 @@ void EmbeddedWorkerInstance::ReleaseProcessReference(int process_id) { } EmbeddedWorkerInstance::EmbeddedWorkerInstance( - EmbeddedWorkerRegistry* registry, + base::WeakPtr<ServiceWorkerContextCore> context, int embedded_worker_id) - : registry_(registry), + : context_(context), + registry_(context->embedded_worker_registry()), embedded_worker_id_(embedded_worker_id), status_(STOPPED), process_id_(-1), - thread_id_(-1) { + thread_id_(-1), + worker_devtools_agent_route_id_(MSG_ROUTING_NONE), + weak_factory_(this) { +} + +// static +void EmbeddedWorkerInstance::RunProcessAllocated( + base::WeakPtr<EmbeddedWorkerInstance> instance, + base::WeakPtr<ServiceWorkerContextCore> context, + scoped_ptr<EmbeddedWorkerMsg_StartWorker_Params> params, + const EmbeddedWorkerInstance::StatusCallback& callback, + ServiceWorkerStatusCode status, + int process_id) { + if (!context) { + callback.Run(SERVICE_WORKER_ERROR_ABORT); + return; + } + if (!instance) { + if (status == SERVICE_WORKER_OK) { + // We only have a process allocated if the status is OK. + context->process_manager()->ReleaseWorkerProcess( + params->embedded_worker_id); + } + callback.Run(SERVICE_WORKER_ERROR_ABORT); + return; + } + instance->ProcessAllocated(params.Pass(), callback, process_id, status); +} + +void EmbeddedWorkerInstance::ProcessAllocated( + scoped_ptr<EmbeddedWorkerMsg_StartWorker_Params> params, + const StatusCallback& callback, + int process_id, + ServiceWorkerStatusCode status) { + DCHECK_EQ(process_id_, -1); + if (status != SERVICE_WORKER_OK) { + status_ = STOPPED; + callback.Run(status); + return; + } + const int64 service_worker_version_id = params->service_worker_version_id; + process_id_ = process_id; + RegisterToWorkerDevToolsManager( + process_id, + context_.get(), + service_worker_version_id, + base::Bind(&EmbeddedWorkerInstance::SendStartWorker, + weak_factory_.GetWeakPtr(), + base::Passed(¶ms), + callback)); +} + +void EmbeddedWorkerInstance::SendStartWorker( + scoped_ptr<EmbeddedWorkerMsg_StartWorker_Params> params, + const StatusCallback& callback, + int worker_devtools_agent_route_id, + bool pause_on_start) { + worker_devtools_agent_route_id_ = worker_devtools_agent_route_id; + params->worker_devtools_agent_route_id = worker_devtools_agent_route_id; + params->pause_on_start = pause_on_start; + registry_->SendStartWorker(params.Pass(), callback, process_id_); +} + +void EmbeddedWorkerInstance::OnScriptLoaded() { + if (worker_devtools_agent_route_id_ != MSG_ROUTING_NONE) + NotifyWorkerContextStarted(process_id_, worker_devtools_agent_route_id_); +} + +void EmbeddedWorkerInstance::OnScriptLoadFailed() { } void EmbeddedWorkerInstance::OnStarted(int thread_id) { + // Stop is requested before OnStarted is sent back from the worker. + if (status_ == STOPPING) + return; DCHECK(status_ == STARTING); status_ = RUNNING; thread_id_ = thread_id; + FOR_EACH_OBSERVER(Listener, listener_list_, OnStarted()); } void EmbeddedWorkerInstance::OnStopped() { + if (worker_devtools_agent_route_id_ != MSG_ROUTING_NONE) + NotifyWorkerDestroyed(process_id_, worker_devtools_agent_route_id_); + if (context_) + context_->process_manager()->ReleaseWorkerProcess(embedded_worker_id_); status_ = STOPPED; process_id_ = -1; thread_id_ = -1; + worker_devtools_agent_route_id_ = MSG_ROUTING_NONE; + FOR_EACH_OBSERVER(Listener, listener_list_, OnStopped()); +} + +bool EmbeddedWorkerInstance::OnMessageReceived(const IPC::Message& message) { + ListenerList::Iterator it(listener_list_); + while (Listener* listener = it.GetNext()) { + if (listener->OnMessageReceived(message)) + return true; + } + return false; +} + +void EmbeddedWorkerInstance::OnReportException( + const base::string16& error_message, + int line_number, + int column_number, + const GURL& source_url) { + FOR_EACH_OBSERVER( + Listener, + listener_list_, + OnReportException(error_message, line_number, column_number, source_url)); +} + +void EmbeddedWorkerInstance::OnReportConsoleMessage( + int source_identifier, + int message_level, + const base::string16& message, + int line_number, + const GURL& source_url) { + FOR_EACH_OBSERVER( + Listener, + listener_list_, + OnReportConsoleMessage( + source_identifier, message_level, message, line_number, source_url)); +} + +void EmbeddedWorkerInstance::AddListener(Listener* listener) { + listener_list_.AddObserver(listener); } -bool EmbeddedWorkerInstance::ChooseProcess() { - DCHECK_EQ(-1, process_id_); - // Naive implementation; chooses a process which has the biggest number of - // associated providers (so that hopefully likely live longer). - ProcessRefMap::iterator max_ref_iter = process_refs_.end(); - for (ProcessRefMap::iterator iter = process_refs_.begin(); - iter != process_refs_.end(); ++iter) { - if (max_ref_iter == process_refs_.end() || - max_ref_iter->second < iter->second) - max_ref_iter = iter; +void EmbeddedWorkerInstance::RemoveListener(Listener* listener) { + listener_list_.RemoveObserver(listener); +} + +std::vector<int> EmbeddedWorkerInstance::SortProcesses( + const std::vector<int>& possible_process_ids) const { + // Add the |possible_process_ids| to the existing process_refs_ since each one + // is likely to take a reference once the SW starts up. + ProcessRefMap refs_with_new_ids = process_refs_; + for (std::vector<int>::const_iterator it = possible_process_ids.begin(); + it != possible_process_ids.end(); + ++it) { + refs_with_new_ids[*it]++; } - if (max_ref_iter == process_refs_.end()) - return false; - process_id_ = max_ref_iter->first; - return true; + + std::vector<std::pair<int, int> > counted(refs_with_new_ids.begin(), + refs_with_new_ids.end()); + // Sort descending by the reference count. + std::sort(counted.begin(), counted.end(), SecondGreater()); + + std::vector<int> result(counted.size()); + for (size_t i = 0; i < counted.size(); ++i) + result[i] = counted[i].first; + return result; } } // namespace content diff --git a/chromium/content/browser/service_worker/embedded_worker_instance.h b/chromium/content/browser/service_worker/embedded_worker_instance.h index fb0fe19bb6c..9e404ff4640 100644 --- a/chromium/content/browser/service_worker/embedded_worker_instance.h +++ b/chromium/content/browser/service_worker/embedded_worker_instance.h @@ -6,25 +6,38 @@ #define CONTENT_BROWSER_SERVICE_WORKER_EMBEDDED_WORKER_INSTANCE_H_ #include <map> +#include <vector> #include "base/basictypes.h" #include "base/callback_forward.h" #include "base/gtest_prod_util.h" #include "base/logging.h" #include "base/memory/ref_counted.h" +#include "base/memory/weak_ptr.h" +#include "base/observer_list.h" +#include "base/strings/string16.h" #include "content/common/content_export.h" +#include "content/common/service_worker/service_worker_status_code.h" +#include "url/gurl.h" -class GURL; +struct EmbeddedWorkerMsg_StartWorker_Params; + +namespace IPC { +class Message; +} namespace content { class EmbeddedWorkerRegistry; +class ServiceWorkerContextCore; +struct ServiceWorkerFetchRequest; // This gives an interface to control one EmbeddedWorker instance, which // may be 'in-waiting' or running in one of the child processes added by // AddProcessReference(). class CONTENT_EXPORT EmbeddedWorkerInstance { public: + typedef base::Callback<void(ServiceWorkerStatusCode)> StatusCallback; enum Status { STOPPED, STARTING, @@ -32,44 +45,109 @@ class CONTENT_EXPORT EmbeddedWorkerInstance { STOPPING, }; + class Listener { + public: + virtual ~Listener() {} + virtual void OnStarted() = 0; + virtual void OnStopped() = 0; + virtual void OnReportException(const base::string16& error_message, + int line_number, + int column_number, + const GURL& source_url) {} + virtual void OnReportConsoleMessage(int source_identifier, + int message_level, + const base::string16& message, + int line_number, + const GURL& source_url) {} + // These should return false if the message is not handled by this + // listener. (TODO(kinuko): consider using IPC::Listener interface) + // TODO(kinuko): Deprecate OnReplyReceived. + virtual bool OnMessageReceived(const IPC::Message& message) = 0; + }; + ~EmbeddedWorkerInstance(); - // Starts the worker. It is invalid to call this when the worker is - // not in STOPPED status. - // This returns false if starting a worker fails immediately, e.g. when - // IPC couldn't be sent to the worker or no process was available. - bool Start(int64 service_worker_version_id, - const GURL& script_url); + // Starts the worker. It is invalid to call this when the worker is not in + // STOPPED status. |callback| is invoked when the worker's process is created + // if necessary and the IPC to evaluate the worker's script is sent. + // Observer::OnStarted() is run when the worker is actually started. + void Start(int64 service_worker_version_id, + const GURL& scope, + const GURL& script_url, + const std::vector<int>& possible_process_ids, + const StatusCallback& callback); // Stops the worker. It is invalid to call this when the worker is // not in STARTING or RUNNING status. // This returns false if stopping a worker fails immediately, e.g. when // IPC couldn't be sent to the worker. - bool Stop(); + ServiceWorkerStatusCode Stop(); + + // Sends |message| to the embedded worker running in the child process. + // It is invalid to call this while the worker is not in RUNNING status. + ServiceWorkerStatusCode SendMessage(const IPC::Message& message); // Add or remove |process_id| to the internal process set where this // worker can be started. void AddProcessReference(int process_id); void ReleaseProcessReference(int process_id); + bool HasProcessToRun() const { return !process_refs_.empty(); } int embedded_worker_id() const { return embedded_worker_id_; } Status status() const { return status_; } int process_id() const { return process_id_; } int thread_id() const { return thread_id_; } + int worker_devtools_agent_route_id() const { + return worker_devtools_agent_route_id_; + } + + void AddListener(Listener* listener); + void RemoveListener(Listener* listener); private: + typedef ObserverList<Listener> ListenerList; + friend class EmbeddedWorkerRegistry; FRIEND_TEST_ALL_PREFIXES(EmbeddedWorkerInstanceTest, StartAndStop); + FRIEND_TEST_ALL_PREFIXES(EmbeddedWorkerInstanceTest, SortProcesses); typedef std::map<int, int> ProcessRefMap; // Constructor is called via EmbeddedWorkerRegistry::CreateWorker(). // This instance holds a ref of |registry|. - EmbeddedWorkerInstance(EmbeddedWorkerRegistry* registry, + EmbeddedWorkerInstance(base::WeakPtr<ServiceWorkerContextCore> context, int embedded_worker_id); + // Called back from ServiceWorkerProcessManager after Start() passes control + // to the UI thread to acquire a reference to the process. + static void RunProcessAllocated( + base::WeakPtr<EmbeddedWorkerInstance> instance, + base::WeakPtr<ServiceWorkerContextCore> context, + scoped_ptr<EmbeddedWorkerMsg_StartWorker_Params> params, + const EmbeddedWorkerInstance::StatusCallback& callback, + ServiceWorkerStatusCode status, + int process_id); + void ProcessAllocated(scoped_ptr<EmbeddedWorkerMsg_StartWorker_Params> params, + const StatusCallback& callback, + int process_id, + ServiceWorkerStatusCode status); + // Called back after ProcessAllocated() passes control to the UI thread to + // register to WorkerDevToolsManager. + void SendStartWorker(scoped_ptr<EmbeddedWorkerMsg_StartWorker_Params> params, + const StatusCallback& callback, + int worker_devtools_agent_route_id, + bool pause_on_start); + // Called back from Registry when the worker instance has ack'ed that - // its WorkerGlobalScope is actually started on |thread_id| in the + // it finished loading the script. + void OnScriptLoaded(); + + // Called back from Registry when the worker instance has ack'ed that + // it failed to load the script. + void OnScriptLoadFailed(); + + // Called back from Registry when the worker instance has ack'ed that + // its WorkerGlobalScope is actually started and parsed on |thread_id| in the // child process. // This will change the internal status from STARTING to RUNNING. void OnStarted(int thread_id); @@ -80,10 +158,30 @@ class CONTENT_EXPORT EmbeddedWorkerInstance { // STOPPED. void OnStopped(); - // Chooses a process to start this worker and populate process_id_. - // Returns false when no process is available. - bool ChooseProcess(); - + // Called back from Registry when the worker instance sends message + // to the browser (i.e. EmbeddedWorker observers). + // Returns false if the message is not handled. + bool OnMessageReceived(const IPC::Message& message); + + // Called back from Registry when the worker instance reports the exception. + void OnReportException(const base::string16& error_message, + int line_number, + int column_number, + const GURL& source_url); + + // Called back from Registry when the worker instance reports to the console. + void OnReportConsoleMessage(int source_identifier, + int message_level, + const base::string16& message, + int line_number, + const GURL& source_url); + + // Chooses a list of processes to try to start this worker in, ordered by how + // many clients are currently in those processes. + std::vector<int> SortProcesses( + const std::vector<int>& possible_process_ids) const; + + base::WeakPtr<ServiceWorkerContextCore> context_; scoped_refptr<EmbeddedWorkerRegistry> registry_; const int embedded_worker_id_; Status status_; @@ -91,8 +189,12 @@ class CONTENT_EXPORT EmbeddedWorkerInstance { // Current running information. -1 indicates the worker is not running. int process_id_; int thread_id_; + int worker_devtools_agent_route_id_; ProcessRefMap process_refs_; + ListenerList listener_list_; + + base::WeakPtrFactory<EmbeddedWorkerInstance> weak_factory_; DISALLOW_COPY_AND_ASSIGN(EmbeddedWorkerInstance); }; diff --git a/chromium/content/browser/service_worker/embedded_worker_instance_unittest.cc b/chromium/content/browser/service_worker/embedded_worker_instance_unittest.cc index 45b2f29dc7f..d14ccd22f25 100644 --- a/chromium/content/browser/service_worker/embedded_worker_instance_unittest.cc +++ b/chromium/content/browser/service_worker/embedded_worker_instance_unittest.cc @@ -3,43 +3,21 @@ // found in the LICENSE file. #include "base/basictypes.h" +#include "base/run_loop.h" #include "base/stl_util.h" #include "content/browser/service_worker/embedded_worker_instance.h" #include "content/browser/service_worker/embedded_worker_registry.h" +#include "content/browser/service_worker/embedded_worker_test_helper.h" #include "content/browser/service_worker/service_worker_context_core.h" -#include "content/common/service_worker_messages.h" +#include "content/browser/service_worker/service_worker_context_wrapper.h" +#include "content/common/service_worker/embedded_worker_messages.h" #include "content/public/test/test_browser_thread_bundle.h" -#include "ipc/ipc_message.h" -#include "ipc/ipc_sender.h" +#include "testing/gmock/include/gmock/gmock.h" #include "testing/gtest/include/gtest/gtest.h" namespace content { -namespace { - -typedef std::vector<IPC::Message*> MessageList; - -class FakeSender : public IPC::Sender { - public: - FakeSender() {} - virtual ~FakeSender() { - STLDeleteContainerPointers(sent_messages_.begin(), sent_messages_.end()); - } - - // IPC::Sender implementation. - virtual bool Send(IPC::Message* message) OVERRIDE { - sent_messages_.push_back(message); - return true; - } - - const MessageList& sent_messages() { return sent_messages_; } - - private: - MessageList sent_messages_; - DISALLOW_COPY_AND_ASSIGN(FakeSender); -}; - -} // namespace +static const int kRenderProcessId = 11; class EmbeddedWorkerInstanceTest : public testing::Test { protected: @@ -47,91 +25,143 @@ class EmbeddedWorkerInstanceTest : public testing::Test { : thread_bundle_(TestBrowserThreadBundle::IO_MAINLOOP) {} virtual void SetUp() OVERRIDE { - context_.reset(new ServiceWorkerContextCore(base::FilePath(), NULL)); + helper_.reset(new EmbeddedWorkerTestHelper(kRenderProcessId)); } virtual void TearDown() OVERRIDE { - context_.reset(); + helper_.reset(); } + ServiceWorkerContextCore* context() { return helper_->context(); } + EmbeddedWorkerRegistry* embedded_worker_registry() { - DCHECK(context_); - return context_->embedded_worker_registry(); + DCHECK(context()); + return context()->embedded_worker_registry(); } + IPC::TestSink* ipc_sink() { return helper_->ipc_sink(); } + TestBrowserThreadBundle thread_bundle_; - scoped_ptr<ServiceWorkerContextCore> context_; + scoped_ptr<EmbeddedWorkerTestHelper> helper_; DISALLOW_COPY_AND_ASSIGN(EmbeddedWorkerInstanceTest); }; +static void SaveStatusAndCall(ServiceWorkerStatusCode* out, + const base::Closure& callback, + ServiceWorkerStatusCode status) { + *out = status; + callback.Run(); +} + TEST_F(EmbeddedWorkerInstanceTest, StartAndStop) { scoped_ptr<EmbeddedWorkerInstance> worker = embedded_worker_registry()->CreateWorker(); EXPECT_EQ(EmbeddedWorkerInstance::STOPPED, worker->status()); - FakeSender fake_sender; - const int process_id = 11; - const int thread_id = 33; + const int embedded_worker_id = worker->embedded_worker_id(); const int64 service_worker_version_id = 55L; + const GURL scope("http://example.com/*"); const GURL url("http://example.com/worker.js"); - // This fails as we have no available process yet. - EXPECT_FALSE(worker->Start(service_worker_version_id, url)); - EXPECT_EQ(EmbeddedWorkerInstance::STOPPED, worker->status()); - // Simulate adding one process to the worker. - worker->AddProcessReference(process_id); - embedded_worker_registry()->AddChildProcessSender(process_id, &fake_sender); + helper_->SimulateAddProcessToWorker(embedded_worker_id, kRenderProcessId); // Start should succeed. - EXPECT_TRUE(worker->Start(service_worker_version_id, url)); + ServiceWorkerStatusCode status; + base::RunLoop run_loop; + worker->Start( + service_worker_version_id, + scope, + url, + std::vector<int>(), + base::Bind(&SaveStatusAndCall, &status, run_loop.QuitClosure())); + run_loop.Run(); + EXPECT_EQ(SERVICE_WORKER_OK, status); EXPECT_EQ(EmbeddedWorkerInstance::STARTING, worker->status()); + base::RunLoop().RunUntilIdle(); - // Simulate an upcall from embedded worker to notify that it's started. - worker->OnStarted(thread_id); + // Worker started message should be notified (by EmbeddedWorkerTestHelper). EXPECT_EQ(EmbeddedWorkerInstance::RUNNING, worker->status()); - EXPECT_EQ(process_id, worker->process_id()); - EXPECT_EQ(thread_id, worker->thread_id()); + EXPECT_EQ(kRenderProcessId, worker->process_id()); // Stop the worker. - EXPECT_TRUE(worker->Stop()); + EXPECT_EQ(SERVICE_WORKER_OK, worker->Stop()); EXPECT_EQ(EmbeddedWorkerInstance::STOPPING, worker->status()); + base::RunLoop().RunUntilIdle(); - // Simulate an upcall from embedded worker to notify that it's stopped. - worker->OnStopped(); + // Worker stopped message should be notified (by EmbeddedWorkerTestHelper). EXPECT_EQ(EmbeddedWorkerInstance::STOPPED, worker->status()); // Verify that we've sent two messages to start and terminate the worker. - const MessageList& messages = fake_sender.sent_messages(); - ASSERT_EQ(2U, messages.size()); - ASSERT_EQ(ServiceWorkerMsg_StartWorker::ID, messages[0]->type()); - ASSERT_EQ(ServiceWorkerMsg_TerminateWorker::ID, messages[1]->type()); + ASSERT_TRUE(ipc_sink()->GetUniqueMessageMatching( + EmbeddedWorkerMsg_StartWorker::ID)); + ASSERT_TRUE(ipc_sink()->GetUniqueMessageMatching( + EmbeddedWorkerMsg_StopWorker::ID)); } -TEST_F(EmbeddedWorkerInstanceTest, ChooseProcess) { +TEST_F(EmbeddedWorkerInstanceTest, InstanceDestroyedBeforeStartFinishes) { scoped_ptr<EmbeddedWorkerInstance> worker = embedded_worker_registry()->CreateWorker(); EXPECT_EQ(EmbeddedWorkerInstance::STOPPED, worker->status()); - FakeSender fake_sender; + const int64 service_worker_version_id = 55L; + const GURL scope("http://example.com/*"); + const GURL url("http://example.com/worker.js"); + + ServiceWorkerStatusCode status; + base::RunLoop run_loop; + // Begin starting the worker. + std::vector<int> available_process; + available_process.push_back(kRenderProcessId); + worker->Start( + service_worker_version_id, + scope, + url, + available_process, + base::Bind(&SaveStatusAndCall, &status, run_loop.QuitClosure())); + // But destroy it before it gets a chance to complete. + worker.reset(); + run_loop.Run(); + EXPECT_EQ(SERVICE_WORKER_ERROR_ABORT, status); + + // Verify that we didn't send the message to start the worker. + ASSERT_FALSE( + ipc_sink()->GetUniqueMessageMatching(EmbeddedWorkerMsg_StartWorker::ID)); +} + +TEST_F(EmbeddedWorkerInstanceTest, SortProcesses) { + scoped_ptr<EmbeddedWorkerInstance> worker = + embedded_worker_registry()->CreateWorker(); + EXPECT_EQ(EmbeddedWorkerInstance::STOPPED, worker->status()); // Simulate adding processes to the worker. // Process 1 has 1 ref, 2 has 2 refs and 3 has 3 refs. - worker->AddProcessReference(1); - worker->AddProcessReference(2); - worker->AddProcessReference(2); - worker->AddProcessReference(3); - worker->AddProcessReference(3); - worker->AddProcessReference(3); - embedded_worker_registry()->AddChildProcessSender(1, &fake_sender); - embedded_worker_registry()->AddChildProcessSender(2, &fake_sender); - embedded_worker_registry()->AddChildProcessSender(3, &fake_sender); + const int embedded_worker_id = worker->embedded_worker_id(); + helper_->SimulateAddProcessToWorker(embedded_worker_id, 1); + helper_->SimulateAddProcessToWorker(embedded_worker_id, 2); + helper_->SimulateAddProcessToWorker(embedded_worker_id, 2); + helper_->SimulateAddProcessToWorker(embedded_worker_id, 3); + helper_->SimulateAddProcessToWorker(embedded_worker_id, 3); + helper_->SimulateAddProcessToWorker(embedded_worker_id, 3); // Process 3 has the biggest # of references and it should be chosen. - EXPECT_TRUE(worker->Start(1L, GURL("http://example.com/worker.js"))); - EXPECT_EQ(EmbeddedWorkerInstance::STARTING, worker->status()); - EXPECT_EQ(3, worker->process_id()); + EXPECT_THAT(worker->SortProcesses(std::vector<int>()), + testing::ElementsAre(3, 2, 1)); + EXPECT_EQ(-1, worker->process_id()); + + // Argument processes are added to the existing set, but only for a single + // call. + std::vector<int> registering_processes; + registering_processes.push_back(1); + registering_processes.push_back(1); + registering_processes.push_back(1); + registering_processes.push_back(4); + EXPECT_THAT(worker->SortProcesses(registering_processes), + testing::ElementsAre(1, 3, 2, 4)); + + EXPECT_THAT(worker->SortProcesses(std::vector<int>()), + testing::ElementsAre(3, 2, 1)); } } // namespace content diff --git a/chromium/content/browser/service_worker/embedded_worker_registry.cc b/chromium/content/browser/service_worker/embedded_worker_registry.cc index f8f0fbe7d5f..988b0563f42 100644 --- a/chromium/content/browser/service_worker/embedded_worker_registry.cc +++ b/chromium/content/browser/service_worker/embedded_worker_registry.cc @@ -4,10 +4,14 @@ #include "content/browser/service_worker/embedded_worker_registry.h" +#include "base/bind_helpers.h" #include "base/stl_util.h" +#include "content/browser/renderer_host/render_widget_helper.h" #include "content/browser/service_worker/embedded_worker_instance.h" #include "content/browser/service_worker/service_worker_context_core.h" -#include "content/common/service_worker_messages.h" +#include "content/browser/service_worker/service_worker_context_wrapper.h" +#include "content/common/service_worker/embedded_worker_messages.h" +#include "content/public/browser/browser_thread.h" #include "ipc/ipc_message.h" #include "ipc/ipc_sender.h" @@ -15,56 +19,197 @@ namespace content { EmbeddedWorkerRegistry::EmbeddedWorkerRegistry( base::WeakPtr<ServiceWorkerContextCore> context) - : context_(context), - next_embedded_worker_id_(0) {} + : context_(context), next_embedded_worker_id_(0) { +} scoped_ptr<EmbeddedWorkerInstance> EmbeddedWorkerRegistry::CreateWorker() { scoped_ptr<EmbeddedWorkerInstance> worker( - new EmbeddedWorkerInstance(this, next_embedded_worker_id_)); + new EmbeddedWorkerInstance(context_, next_embedded_worker_id_)); worker_map_[next_embedded_worker_id_++] = worker.get(); return worker.Pass(); } -void EmbeddedWorkerRegistry::RemoveWorker(int embedded_worker_id) { - DCHECK(ContainsKey(worker_map_, embedded_worker_id)); - worker_map_.erase(embedded_worker_id); +ServiceWorkerStatusCode EmbeddedWorkerRegistry::StopWorker( + int process_id, int embedded_worker_id) { + return Send(process_id, + new EmbeddedWorkerMsg_StopWorker(embedded_worker_id)); +} + +bool EmbeddedWorkerRegistry::OnMessageReceived(const IPC::Message& message) { + // TODO(kinuko): Move all EmbeddedWorker message handling from + // ServiceWorkerDispatcherHost. + + WorkerInstanceMap::iterator found = worker_map_.find(message.routing_id()); + if (found == worker_map_.end()) { + LOG(ERROR) << "Worker " << message.routing_id() << " not registered"; + return false; + } + return found->second->OnMessageReceived(message); +} + +void EmbeddedWorkerRegistry::Shutdown() { + for (WorkerInstanceMap::iterator it = worker_map_.begin(); + it != worker_map_.end(); + ++it) { + it->second->Stop(); + } +} + +void EmbeddedWorkerRegistry::OnWorkerScriptLoaded(int process_id, + int embedded_worker_id) { + WorkerInstanceMap::iterator found = worker_map_.find(embedded_worker_id); + if (found == worker_map_.end()) { + LOG(ERROR) << "Worker " << embedded_worker_id << " not registered"; + return; + } + if (found->second->process_id() != process_id) { + LOG(ERROR) << "Incorrect embedded_worker_id"; + return; + } + found->second->OnScriptLoaded(); +} + +void EmbeddedWorkerRegistry::OnWorkerScriptLoadFailed(int process_id, + int embedded_worker_id) { + WorkerInstanceMap::iterator found = worker_map_.find(embedded_worker_id); + if (found == worker_map_.end()) { + LOG(ERROR) << "Worker " << embedded_worker_id << " not registered"; + return; + } + if (found->second->process_id() != process_id) { + LOG(ERROR) << "Incorrect embedded_worker_id"; + return; + } + found->second->OnScriptLoadFailed(); +} + +void EmbeddedWorkerRegistry::OnWorkerStarted( + int process_id, int thread_id, int embedded_worker_id) { + DCHECK(!ContainsKey(worker_process_map_, process_id) || + worker_process_map_[process_id].count(embedded_worker_id) == 0); + WorkerInstanceMap::iterator found = worker_map_.find(embedded_worker_id); + if (found == worker_map_.end()) { + LOG(ERROR) << "Worker " << embedded_worker_id << " not registered"; + return; + } + if (found->second->process_id() != process_id) { + LOG(ERROR) << "Incorrect embedded_worker_id"; + return; + } + worker_process_map_[process_id].insert(embedded_worker_id); + found->second->OnStarted(thread_id); } -bool EmbeddedWorkerRegistry::StartWorker( - int process_id, +void EmbeddedWorkerRegistry::OnWorkerStopped( + int process_id, int embedded_worker_id) { + WorkerInstanceMap::iterator found = worker_map_.find(embedded_worker_id); + if (found == worker_map_.end()) { + LOG(ERROR) << "Worker " << embedded_worker_id << " not registered"; + return; + } + if (found->second->process_id() != process_id) { + LOG(ERROR) << "Incorrect embedded_worker_id"; + return; + } + worker_process_map_[process_id].erase(embedded_worker_id); + found->second->OnStopped(); +} + +void EmbeddedWorkerRegistry::OnReportException( int embedded_worker_id, - int64 service_worker_version_id, - const GURL& script_url) { - return Send(process_id, - new ServiceWorkerMsg_StartWorker(embedded_worker_id, - service_worker_version_id, - script_url)); + const base::string16& error_message, + int line_number, + int column_number, + const GURL& source_url) { + WorkerInstanceMap::iterator found = worker_map_.find(embedded_worker_id); + if (found == worker_map_.end()) { + LOG(ERROR) << "Worker " << embedded_worker_id << " not registered"; + return; + } + found->second->OnReportException( + error_message, line_number, column_number, source_url); } -bool EmbeddedWorkerRegistry::StopWorker(int process_id, - int embedded_worker_id) { - return Send(process_id, - new ServiceWorkerMsg_TerminateWorker(embedded_worker_id)); +void EmbeddedWorkerRegistry::OnReportConsoleMessage( + int embedded_worker_id, + int source_identifier, + int message_level, + const base::string16& message, + int line_number, + const GURL& source_url) { + WorkerInstanceMap::iterator found = worker_map_.find(embedded_worker_id); + if (found == worker_map_.end()) { + LOG(ERROR) << "Worker " << embedded_worker_id << " not registered"; + return; + } + found->second->OnReportConsoleMessage( + source_identifier, message_level, message, line_number, source_url); } void EmbeddedWorkerRegistry::AddChildProcessSender( int process_id, IPC::Sender* sender) { process_sender_map_[process_id] = sender; + DCHECK(!ContainsKey(worker_process_map_, process_id)); } void EmbeddedWorkerRegistry::RemoveChildProcessSender(int process_id) { process_sender_map_.erase(process_id); + std::map<int, std::set<int> >::iterator found = + worker_process_map_.find(process_id); + if (found != worker_process_map_.end()) { + const std::set<int>& worker_set = worker_process_map_[process_id]; + for (std::set<int>::const_iterator it = worker_set.begin(); + it != worker_set.end(); + ++it) { + int embedded_worker_id = *it; + DCHECK(ContainsKey(worker_map_, embedded_worker_id)); + worker_map_[embedded_worker_id]->OnStopped(); + } + worker_process_map_.erase(found); + } +} + +EmbeddedWorkerInstance* EmbeddedWorkerRegistry::GetWorker( + int embedded_worker_id) { + WorkerInstanceMap::iterator found = worker_map_.find(embedded_worker_id); + if (found == worker_map_.end()) + return NULL; + return found->second; +} + +EmbeddedWorkerRegistry::~EmbeddedWorkerRegistry() { + Shutdown(); } -EmbeddedWorkerRegistry::~EmbeddedWorkerRegistry() {} +void EmbeddedWorkerRegistry::SendStartWorker( + scoped_ptr<EmbeddedWorkerMsg_StartWorker_Params> params, + const StatusCallback& callback, + int process_id) { + // The ServiceWorkerDispatcherHost is supposed to be created when the process + // is created, and keep an entry in process_sender_map_ for its whole + // lifetime. + DCHECK(ContainsKey(process_sender_map_, process_id)); + callback.Run(Send(process_id, new EmbeddedWorkerMsg_StartWorker(*params))); +} -bool EmbeddedWorkerRegistry::Send(int process_id, IPC::Message* message) { +ServiceWorkerStatusCode EmbeddedWorkerRegistry::Send( + int process_id, IPC::Message* message_ptr) { + scoped_ptr<IPC::Message> message(message_ptr); if (!context_) - return false; + return SERVICE_WORKER_ERROR_ABORT; ProcessToSenderMap::iterator found = process_sender_map_.find(process_id); if (found == process_sender_map_.end()) - return false; - return found->second->Send(message); + return SERVICE_WORKER_ERROR_PROCESS_NOT_FOUND; + if (!found->second->Send(message.release())) + return SERVICE_WORKER_ERROR_IPC_FAILED; + return SERVICE_WORKER_OK; +} + +void EmbeddedWorkerRegistry::RemoveWorker(int process_id, + int embedded_worker_id) { + DCHECK(ContainsKey(worker_map_, embedded_worker_id)); + worker_map_.erase(embedded_worker_id); + worker_process_map_.erase(process_id); } } // namespace content diff --git a/chromium/content/browser/service_worker/embedded_worker_registry.h b/chromium/content/browser/service_worker/embedded_worker_registry.h index 8860e374b5c..3df2e238071 100644 --- a/chromium/content/browser/service_worker/embedded_worker_registry.h +++ b/chromium/content/browser/service_worker/embedded_worker_registry.h @@ -6,13 +6,18 @@ #define CONTENT_BROWSER_SERVICE_WORKER_EMBEDDED_WORKER_REGISTRY_H_ #include <map> +#include <set> +#include <vector> #include "base/basictypes.h" #include "base/memory/ref_counted.h" #include "base/memory/scoped_ptr.h" #include "base/memory/weak_ptr.h" +#include "base/strings/string16.h" #include "content/common/content_export.h" +#include "content/common/service_worker/service_worker_status_code.h" +struct EmbeddedWorkerMsg_StartWorker_Params; class GURL; namespace IPC { @@ -33,40 +38,76 @@ class ServiceWorkerContextCore; class CONTENT_EXPORT EmbeddedWorkerRegistry : public NON_EXPORTED_BASE(base::RefCounted<EmbeddedWorkerRegistry>) { public: + typedef base::Callback<void(ServiceWorkerStatusCode)> StatusCallback; + explicit EmbeddedWorkerRegistry( base::WeakPtr<ServiceWorkerContextCore> context); + bool OnMessageReceived(const IPC::Message& message); + // Creates and removes a new worker instance entry for bookkeeping. // This doesn't actually start or stop the worker. scoped_ptr<EmbeddedWorkerInstance> CreateWorker(); - void RemoveWorker(int embedded_worker_id); // Called from EmbeddedWorkerInstance, relayed to the child process. - bool StartWorker(int process_id, - int embedded_worker_id, - int64 service_worker_version_id, - const GURL& script_url); - bool StopWorker(int process_id, - int embedded_worker_id); + void SendStartWorker(scoped_ptr<EmbeddedWorkerMsg_StartWorker_Params> params, + const StatusCallback& callback, + int process_id); + ServiceWorkerStatusCode StopWorker(int process_id, + int embedded_worker_id); + + // Stop all active workers, even if they're handling events. + void Shutdown(); + + // Called back from EmbeddedWorker in the child process, relayed via + // ServiceWorkerDispatcherHost. + void OnWorkerScriptLoaded(int process_id, int embedded_worker_id); + void OnWorkerScriptLoadFailed(int process_id, int embedded_worker_id); + void OnWorkerStarted(int process_id, int thread_id, int embedded_worker_id); + void OnWorkerStopped(int process_id, int embedded_worker_id); + void OnReportException(int embedded_worker_id, + const base::string16& error_message, + int line_number, + int column_number, + const GURL& source_url); + void OnReportConsoleMessage(int embedded_worker_id, + int source_identifier, + int message_level, + const base::string16& message, + int line_number, + const GURL& source_url); // Keeps a map from process_id to sender information. void AddChildProcessSender(int process_id, IPC::Sender* sender); void RemoveChildProcessSender(int process_id); + // Returns an embedded worker instance for given |embedded_worker_id|. + EmbeddedWorkerInstance* GetWorker(int embedded_worker_id); + private: friend class base::RefCounted<EmbeddedWorkerRegistry>; - - ~EmbeddedWorkerRegistry(); - bool Send(int process_id, IPC::Message* message); + friend class EmbeddedWorkerInstance; typedef std::map<int, EmbeddedWorkerInstance*> WorkerInstanceMap; typedef std::map<int, IPC::Sender*> ProcessToSenderMap; + ~EmbeddedWorkerRegistry(); + + ServiceWorkerStatusCode Send(int process_id, IPC::Message* message); + + // RemoveWorker is called when EmbeddedWorkerInstance is destructed. + // |process_id| could be invalid (i.e. -1) if it's not running. + void RemoveWorker(int process_id, int embedded_worker_id); + base::WeakPtr<ServiceWorkerContextCore> context_; WorkerInstanceMap worker_map_; ProcessToSenderMap process_sender_map_; + // Map from process_id to embedded_worker_id. + // This map only contains running workers. + std::map<int, std::set<int> > worker_process_map_; + int next_embedded_worker_id_; DISALLOW_COPY_AND_ASSIGN(EmbeddedWorkerRegistry); diff --git a/chromium/content/browser/service_worker/embedded_worker_test_helper.cc b/chromium/content/browser/service_worker/embedded_worker_test_helper.cc new file mode 100644 index 00000000000..82d206142a1 --- /dev/null +++ b/chromium/content/browser/service_worker/embedded_worker_test_helper.cc @@ -0,0 +1,241 @@ +// Copyright 2014 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 "content/browser/service_worker/embedded_worker_test_helper.h" + +#include "base/bind.h" +#include "content/browser/service_worker/embedded_worker_instance.h" +#include "content/browser/service_worker/embedded_worker_registry.h" +#include "content/browser/service_worker/service_worker_context_core.h" +#include "content/browser/service_worker/service_worker_context_wrapper.h" +#include "content/common/service_worker/embedded_worker_messages.h" +#include "content/common/service_worker/service_worker_messages.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace content { + +EmbeddedWorkerTestHelper::EmbeddedWorkerTestHelper(int mock_render_process_id) + : wrapper_(new ServiceWorkerContextWrapper(NULL)), + next_thread_id_(0), + weak_factory_(this) { + wrapper_->InitInternal(base::FilePath(), + base::MessageLoopProxy::current(), + base::MessageLoopProxy::current(), + NULL); + wrapper_->process_manager()->SetProcessIdForTest(mock_render_process_id); + registry()->AddChildProcessSender(mock_render_process_id, this); +} + +EmbeddedWorkerTestHelper::~EmbeddedWorkerTestHelper() { + if (wrapper_) + wrapper_->Shutdown(); +} + +void EmbeddedWorkerTestHelper::SimulateAddProcessToWorker( + int embedded_worker_id, + int process_id) { + EmbeddedWorkerInstance* worker = registry()->GetWorker(embedded_worker_id); + ASSERT_TRUE(worker); + registry()->AddChildProcessSender(process_id, this); + worker->AddProcessReference(process_id); +} + +bool EmbeddedWorkerTestHelper::Send(IPC::Message* message) { + OnMessageReceived(*message); + delete message; + return true; +} + +bool EmbeddedWorkerTestHelper::OnMessageReceived(const IPC::Message& message) { + bool handled = true; + IPC_BEGIN_MESSAGE_MAP(EmbeddedWorkerTestHelper, message) + IPC_MESSAGE_HANDLER(EmbeddedWorkerMsg_StartWorker, OnStartWorkerStub) + IPC_MESSAGE_HANDLER(EmbeddedWorkerMsg_StopWorker, OnStopWorkerStub) + IPC_MESSAGE_HANDLER(EmbeddedWorkerContextMsg_MessageToWorker, + OnMessageToWorkerStub) + IPC_MESSAGE_UNHANDLED(handled = false) + IPC_END_MESSAGE_MAP() + + // IPC::TestSink only records messages that are not handled by filters, + // so we just forward all messages to the separate sink. + sink_.OnMessageReceived(message); + + return handled; +} + +ServiceWorkerContextCore* EmbeddedWorkerTestHelper::context() { + return wrapper_->context(); +} + +void EmbeddedWorkerTestHelper::ShutdownContext() { + wrapper_->Shutdown(); + wrapper_ = NULL; +} + +void EmbeddedWorkerTestHelper::OnStartWorker( + int embedded_worker_id, + int64 service_worker_version_id, + const GURL& scope, + const GURL& script_url) { + // By default just notify the sender that the worker is started. + SimulateWorkerStarted(next_thread_id_++, embedded_worker_id); +} + +void EmbeddedWorkerTestHelper::OnStopWorker(int embedded_worker_id) { + // By default just notify the sender that the worker is stopped. + SimulateWorkerStopped(embedded_worker_id); +} + +bool EmbeddedWorkerTestHelper::OnMessageToWorker( + int thread_id, + int embedded_worker_id, + const IPC::Message& message) { + bool handled = true; + current_embedded_worker_id_ = embedded_worker_id; + IPC_BEGIN_MESSAGE_MAP(EmbeddedWorkerTestHelper, message) + IPC_MESSAGE_HANDLER(ServiceWorkerMsg_ActivateEvent, OnActivateEventStub) + IPC_MESSAGE_HANDLER(ServiceWorkerMsg_InstallEvent, OnInstallEventStub) + IPC_MESSAGE_HANDLER(ServiceWorkerMsg_FetchEvent, OnFetchEventStub) + IPC_MESSAGE_UNHANDLED(handled = false) + IPC_END_MESSAGE_MAP() + // Record all messages directed to inner script context. + inner_sink_.OnMessageReceived(message); + return handled; +} + +void EmbeddedWorkerTestHelper::OnActivateEvent(int embedded_worker_id, + int request_id) { + SimulateSend( + new ServiceWorkerHostMsg_ActivateEventFinished( + embedded_worker_id, request_id, + blink::WebServiceWorkerEventResultCompleted)); +} + +void EmbeddedWorkerTestHelper::OnInstallEvent(int embedded_worker_id, + int request_id, + int active_version_id) { + SimulateSend( + new ServiceWorkerHostMsg_InstallEventFinished( + embedded_worker_id, request_id, + blink::WebServiceWorkerEventResultCompleted)); +} + +void EmbeddedWorkerTestHelper::OnFetchEvent( + int embedded_worker_id, + int request_id, + const ServiceWorkerFetchRequest& request) { + SimulateSend( + new ServiceWorkerHostMsg_FetchEventFinished( + embedded_worker_id, + request_id, + SERVICE_WORKER_FETCH_EVENT_RESULT_RESPONSE, + ServiceWorkerResponse(200, "OK", + std::map<std::string, std::string>(), + std::string()))); +} + +void EmbeddedWorkerTestHelper::SimulateWorkerStarted( + int thread_id, int embedded_worker_id) { + EmbeddedWorkerInstance* worker = registry()->GetWorker(embedded_worker_id); + ASSERT_TRUE(worker != NULL); + registry()->OnWorkerStarted( + worker->process_id(), + thread_id, + embedded_worker_id); +} + +void EmbeddedWorkerTestHelper::SimulateWorkerStopped( + int embedded_worker_id) { + EmbeddedWorkerInstance* worker = registry()->GetWorker(embedded_worker_id); + if (worker != NULL) + registry()->OnWorkerStopped(worker->process_id(), embedded_worker_id); +} + +void EmbeddedWorkerTestHelper::SimulateSend( + IPC::Message* message) { + registry()->OnMessageReceived(*message); + delete message; +} + +void EmbeddedWorkerTestHelper::OnStartWorkerStub( + const EmbeddedWorkerMsg_StartWorker_Params& params) { + EmbeddedWorkerInstance* worker = + registry()->GetWorker(params.embedded_worker_id); + ASSERT_TRUE(worker != NULL); + EXPECT_EQ(EmbeddedWorkerInstance::STARTING, worker->status()); + base::MessageLoopProxy::current()->PostTask( + FROM_HERE, + base::Bind(&EmbeddedWorkerTestHelper::OnStartWorker, + weak_factory_.GetWeakPtr(), + params.embedded_worker_id, + params.service_worker_version_id, + params.scope, + params.script_url)); +} + +void EmbeddedWorkerTestHelper::OnStopWorkerStub(int embedded_worker_id) { + EmbeddedWorkerInstance* worker = registry()->GetWorker(embedded_worker_id); + ASSERT_TRUE(worker != NULL); + base::MessageLoopProxy::current()->PostTask( + FROM_HERE, + base::Bind(&EmbeddedWorkerTestHelper::OnStopWorker, + weak_factory_.GetWeakPtr(), + embedded_worker_id)); +} + +void EmbeddedWorkerTestHelper::OnMessageToWorkerStub( + int thread_id, + int embedded_worker_id, + const IPC::Message& message) { + EmbeddedWorkerInstance* worker = registry()->GetWorker(embedded_worker_id); + ASSERT_TRUE(worker != NULL); + EXPECT_EQ(worker->thread_id(), thread_id); + base::MessageLoopProxy::current()->PostTask( + FROM_HERE, + base::Bind( + base::IgnoreResult(&EmbeddedWorkerTestHelper::OnMessageToWorker), + weak_factory_.GetWeakPtr(), + thread_id, + embedded_worker_id, + message)); +} + +void EmbeddedWorkerTestHelper::OnActivateEventStub(int request_id) { + base::MessageLoopProxy::current()->PostTask( + FROM_HERE, + base::Bind(&EmbeddedWorkerTestHelper::OnActivateEvent, + weak_factory_.GetWeakPtr(), + current_embedded_worker_id_, + request_id)); +} + +void EmbeddedWorkerTestHelper::OnInstallEventStub(int request_id, + int active_version_id) { + base::MessageLoopProxy::current()->PostTask( + FROM_HERE, + base::Bind(&EmbeddedWorkerTestHelper::OnInstallEvent, + weak_factory_.GetWeakPtr(), + current_embedded_worker_id_, + request_id, + active_version_id)); +} + +void EmbeddedWorkerTestHelper::OnFetchEventStub( + int request_id, + const ServiceWorkerFetchRequest& request) { + base::MessageLoopProxy::current()->PostTask( + FROM_HERE, + base::Bind(&EmbeddedWorkerTestHelper::OnFetchEvent, + weak_factory_.GetWeakPtr(), + current_embedded_worker_id_, + request_id, + request)); +} + +EmbeddedWorkerRegistry* EmbeddedWorkerTestHelper::registry() { + DCHECK(context()); + return context()->embedded_worker_registry(); +} + +} // namespace content diff --git a/chromium/content/browser/service_worker/embedded_worker_test_helper.h b/chromium/content/browser/service_worker/embedded_worker_test_helper.h new file mode 100644 index 00000000000..7cd817df6b9 --- /dev/null +++ b/chromium/content/browser/service_worker/embedded_worker_test_helper.h @@ -0,0 +1,135 @@ +// Copyright 2014 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 CONTENT_BROWSER_SERVICE_WORKER_EMBEDDED_WORKER_TEST_HELPER_H_ +#define CONTENT_BROWSER_SERVICE_WORKER_EMBEDDED_WORKER_TEST_HELPER_H_ + +#include <vector> + +#include "base/callback.h" +#include "base/memory/weak_ptr.h" +#include "ipc/ipc_listener.h" +#include "ipc/ipc_test_sink.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "url/gurl.h" + +struct EmbeddedWorkerMsg_StartWorker_Params; +class GURL; + +namespace content { + +class EmbeddedWorkerRegistry; +class EmbeddedWorkerTestHelper; +class ServiceWorkerContextCore; +class ServiceWorkerContextWrapper; +struct ServiceWorkerFetchRequest; + +// In-Process EmbeddedWorker test helper. +// +// Usage: create an instance of this class to test browser-side embedded worker +// code without creating a child process. This class will create a +// ServiceWorkerContextWrapper and ServiceWorkerContextCore for you. +// +// By default this class just notifies back WorkerStarted and WorkerStopped +// for StartWorker and StopWorker requests. The default implementation +// also returns success for event messages (e.g. InstallEvent, FetchEvent). +// +// Alternatively consumers can subclass this helper and override On*() +// methods to add their own logic/verification code. +// +// See embedded_worker_instance_unittest.cc for example usages. +// +class EmbeddedWorkerTestHelper : public IPC::Sender, + public IPC::Listener { + public: + // Initialize this helper for |context|, and enable this as an IPC + // sender for |mock_render_process_id|. + EmbeddedWorkerTestHelper(int mock_render_process_id); + virtual ~EmbeddedWorkerTestHelper(); + + // Call this to simulate add/associate a process to a worker. + // This also registers this sender for the process. + void SimulateAddProcessToWorker(int embedded_worker_id, int process_id); + + // IPC::Sender implementation. + virtual bool Send(IPC::Message* message) OVERRIDE; + + // IPC::Listener implementation. + virtual bool OnMessageReceived(const IPC::Message& msg) OVERRIDE; + + // IPC sink for EmbeddedWorker messages. + IPC::TestSink* ipc_sink() { return &sink_; } + // Inner IPC sink for script context messages sent via EmbeddedWorker. + IPC::TestSink* inner_ipc_sink() { return &inner_sink_; } + + ServiceWorkerContextCore* context(); + ServiceWorkerContextWrapper* context_wrapper() { return wrapper_.get(); } + void ShutdownContext(); + + protected: + // Called when StartWorker, StopWorker and SendMessageToWorker message + // is sent to the embedded worker. Override if necessary. By default + // they verify given parameters and: + // - OnStartWorker calls SimulateWorkerStarted + // - OnStopWorker calls SimulateWorkerStoped + // - OnSendMessageToWorker calls the message's respective On*Event handler + virtual void OnStartWorker(int embedded_worker_id, + int64 service_worker_version_id, + const GURL& scope, + const GURL& script_url); + virtual void OnStopWorker(int embedded_worker_id); + virtual bool OnMessageToWorker(int thread_id, + int embedded_worker_id, + const IPC::Message& message); + + // On*Event handlers. Called by the default implementation of + // OnMessageToWorker when events are sent to the embedded + // worker. By default they just return success via + // SimulateSendReplyToBrowser. + virtual void OnActivateEvent(int embedded_worker_id, int request_id); + virtual void OnInstallEvent(int embedded_worker_id, + int request_id, + int active_version_id); + virtual void OnFetchEvent(int embedded_worker_id, + int request_id, + const ServiceWorkerFetchRequest& request); + + // These functions simulate sending an EmbeddedHostMsg message to the + // browser. + void SimulateWorkerStarted(int thread_id, int embedded_worker_id); + void SimulateWorkerStopped(int embedded_worker_id); + void SimulateSend(IPC::Message* message); + + protected: + EmbeddedWorkerRegistry* registry(); + + private: + void OnStartWorkerStub(const EmbeddedWorkerMsg_StartWorker_Params& params); + void OnStopWorkerStub(int embedded_worker_id); + void OnMessageToWorkerStub(int thread_id, + int embedded_worker_id, + const IPC::Message& message); + void OnActivateEventStub(int request_id); + void OnInstallEventStub(int request_id, int active_version_id); + void OnFetchEventStub(int request_id, + const ServiceWorkerFetchRequest& request); + + scoped_refptr<ServiceWorkerContextWrapper> wrapper_; + + IPC::TestSink sink_; + IPC::TestSink inner_sink_; + + int next_thread_id_; + + // Updated each time MessageToWorker message is received. + int current_embedded_worker_id_; + + base::WeakPtrFactory<EmbeddedWorkerTestHelper> weak_factory_; + + DISALLOW_COPY_AND_ASSIGN(EmbeddedWorkerTestHelper); +}; + +} // namespace content + +#endif // CONTENT_BROWSER_SERVICE_WORKER_EMBEDDED_WORKER_TEST_HELPER_H_ diff --git a/chromium/content/browser/service_worker/service_worker_browsertest.cc b/chromium/content/browser/service_worker/service_worker_browsertest.cc new file mode 100644 index 00000000000..8e5b8871f9e --- /dev/null +++ b/chromium/content/browser/service_worker/service_worker_browsertest.cc @@ -0,0 +1,710 @@ +// Copyright 2014 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 "base/bind.h" +#include "base/callback.h" +#include "base/command_line.h" +#include "base/run_loop.h" +#include "content/browser/fileapi/chrome_blob_storage_context.h" +#include "content/browser/service_worker/embedded_worker_instance.h" +#include "content/browser/service_worker/embedded_worker_registry.h" +#include "content/browser/service_worker/service_worker_context_core.h" +#include "content/browser/service_worker/service_worker_context_wrapper.h" +#include "content/browser/service_worker/service_worker_registration.h" +#include "content/browser/service_worker/service_worker_test_utils.h" +#include "content/browser/service_worker/service_worker_version.h" +#include "content/common/service_worker/service_worker_messages.h" +#include "content/common/service_worker/service_worker_status_code.h" +#include "content/common/service_worker/service_worker_types.h" +#include "content/public/browser/browser_context.h" +#include "content/public/browser/browser_thread.h" +#include "content/public/browser/render_process_host.h" +#include "content/public/browser/storage_partition.h" +#include "content/public/browser/web_contents.h" +#include "content/public/common/content_switches.h" +#include "content/public/test/content_browser_test.h" +#include "content/public/test/content_browser_test_utils.h" +#include "content/shell/browser/shell.h" +#include "net/test/embedded_test_server/embedded_test_server.h" +#include "webkit/browser/blob/blob_data_handle.h" +#include "webkit/browser/blob/blob_storage_context.h" +#include "webkit/common/blob/blob_data.h" + +namespace content { + +namespace { + +struct FetchResult { + ServiceWorkerStatusCode status; + ServiceWorkerFetchEventResult result; + ServiceWorkerResponse response; + scoped_ptr<webkit_blob::BlobDataHandle> blob_data_handle; +}; + +void RunAndQuit(const base::Closure& closure, + const base::Closure& quit, + base::MessageLoopProxy* original_message_loop) { + closure.Run(); + original_message_loop->PostTask(FROM_HERE, quit); +} + +void RunOnIOThread(const base::Closure& closure) { + base::RunLoop run_loop; + BrowserThread::PostTask( + BrowserThread::IO, FROM_HERE, + base::Bind(&RunAndQuit, closure, run_loop.QuitClosure(), + base::MessageLoopProxy::current())); + run_loop.Run(); +} + +void RunOnIOThread( + const base::Callback<void(const base::Closure& continuation)>& closure) { + base::RunLoop run_loop; + base::Closure quit_on_original_thread = + base::Bind(base::IgnoreResult(&base::MessageLoopProxy::PostTask), + base::MessageLoopProxy::current().get(), + FROM_HERE, + run_loop.QuitClosure()); + BrowserThread::PostTask(BrowserThread::IO, + FROM_HERE, + base::Bind(closure, quit_on_original_thread)); + run_loop.Run(); +} + +// Contrary to the style guide, the output parameter of this function comes +// before input parameters so Bind can be used on it to create a FetchCallback +// to pass to DispatchFetchEvent. +void ReceiveFetchResult(BrowserThread::ID run_quit_thread, + const base::Closure& quit, + ChromeBlobStorageContext* blob_context, + FetchResult* out_result, + ServiceWorkerStatusCode actual_status, + ServiceWorkerFetchEventResult actual_result, + const ServiceWorkerResponse& actual_response) { + out_result->status = actual_status; + out_result->result = actual_result; + out_result->response = actual_response; + if (!actual_response.blob_uuid.empty()) { + out_result->blob_data_handle = + blob_context->context()->GetBlobDataFromUUID( + actual_response.blob_uuid); + } + if (!quit.is_null()) + BrowserThread::PostTask(BrowserThread::UI, FROM_HERE, quit); +} + +ServiceWorkerVersion::FetchCallback CreateResponseReceiver( + BrowserThread::ID run_quit_thread, + const base::Closure& quit, + ChromeBlobStorageContext* blob_context, + FetchResult* result) { + return base::Bind(&ReceiveFetchResult, run_quit_thread, quit, + make_scoped_refptr<ChromeBlobStorageContext>(blob_context), + result); +} + +void ReadResponseBody(std::string* body, + webkit_blob::BlobDataHandle* blob_data_handle) { + ASSERT_TRUE(blob_data_handle); + ASSERT_EQ(1U, blob_data_handle->data()->items().size()); + *body = std::string(blob_data_handle->data()->items()[0].bytes(), + blob_data_handle->data()->items()[0].length()); +} + +} // namespace + +class ServiceWorkerBrowserTest : public ContentBrowserTest { + protected: + typedef ServiceWorkerBrowserTest self; + + virtual void SetUpCommandLine(CommandLine* command_line) OVERRIDE { + command_line->AppendSwitch(switches::kEnableServiceWorker); + } + + virtual void SetUpOnMainThread() OVERRIDE { + ASSERT_TRUE(embedded_test_server()->InitializeAndWaitUntilReady()); + StoragePartition* partition = BrowserContext::GetDefaultStoragePartition( + shell()->web_contents()->GetBrowserContext()); + wrapper_ = static_cast<ServiceWorkerContextWrapper*>( + partition->GetServiceWorkerContext()); + + // Navigate to the page to set up a renderer page (where we can embed + // a worker). + NavigateToURLBlockUntilNavigationsComplete( + shell(), + embedded_test_server()->GetURL("/service_worker/empty.html"), 1); + + RunOnIOThread(base::Bind(&self::SetUpOnIOThread, this)); + } + + virtual void TearDownOnMainThread() OVERRIDE { + RunOnIOThread(base::Bind(&self::TearDownOnIOThread, this)); + wrapper_ = NULL; + } + + virtual void SetUpOnIOThread() {} + virtual void TearDownOnIOThread() {} + + ServiceWorkerContextWrapper* wrapper() { return wrapper_.get(); } + ServiceWorkerContext* public_context() { return wrapper(); } + + void AssociateRendererProcessToWorker(EmbeddedWorkerInstance* worker) { + worker->AddProcessReference( + shell()->web_contents()->GetRenderProcessHost()->GetID()); + } + + private: + scoped_refptr<ServiceWorkerContextWrapper> wrapper_; +}; + +class EmbeddedWorkerBrowserTest : public ServiceWorkerBrowserTest, + public EmbeddedWorkerInstance::Listener { + public: + typedef EmbeddedWorkerBrowserTest self; + + EmbeddedWorkerBrowserTest() + : last_worker_status_(EmbeddedWorkerInstance::STOPPED) {} + virtual ~EmbeddedWorkerBrowserTest() {} + + virtual void TearDownOnIOThread() OVERRIDE { + if (worker_) { + worker_->RemoveListener(this); + worker_.reset(); + } + } + + void StartOnIOThread() { + ASSERT_TRUE(BrowserThread::CurrentlyOn(BrowserThread::IO)); + worker_ = wrapper()->context()->embedded_worker_registry()->CreateWorker(); + EXPECT_EQ(EmbeddedWorkerInstance::STOPPED, worker_->status()); + worker_->AddListener(this); + + AssociateRendererProcessToWorker(worker_.get()); + + const int64 service_worker_version_id = 33L; + const GURL scope = embedded_test_server()->GetURL("/*"); + const GURL script_url = embedded_test_server()->GetURL( + "/service_worker/worker.js"); + std::vector<int> processes; + processes.push_back( + shell()->web_contents()->GetRenderProcessHost()->GetID()); + worker_->Start( + service_worker_version_id, + scope, + script_url, + processes, + base::Bind(&EmbeddedWorkerBrowserTest::StartOnIOThread2, this)); + } + void StartOnIOThread2(ServiceWorkerStatusCode status) { + last_worker_status_ = worker_->status(); + EXPECT_EQ(SERVICE_WORKER_OK, status); + EXPECT_EQ(EmbeddedWorkerInstance::STARTING, last_worker_status_); + + if (status != SERVICE_WORKER_OK && !done_closure_.is_null()) + done_closure_.Run(); + } + + void StopOnIOThread() { + ASSERT_TRUE(BrowserThread::CurrentlyOn(BrowserThread::IO)); + EXPECT_EQ(EmbeddedWorkerInstance::RUNNING, worker_->status()); + + ServiceWorkerStatusCode status = worker_->Stop(); + + last_worker_status_ = worker_->status(); + EXPECT_EQ(SERVICE_WORKER_OK, status); + EXPECT_EQ(EmbeddedWorkerInstance::STOPPING, last_worker_status_); + + if (status != SERVICE_WORKER_OK && !done_closure_.is_null()) + done_closure_.Run(); + } + + protected: + // EmbeddedWorkerInstance::Observer overrides: + virtual void OnStarted() OVERRIDE { + ASSERT_TRUE(worker_ != NULL); + ASSERT_FALSE(done_closure_.is_null()); + last_worker_status_ = worker_->status(); + BrowserThread::PostTask(BrowserThread::UI, FROM_HERE, done_closure_); + } + virtual void OnStopped() OVERRIDE { + ASSERT_TRUE(worker_ != NULL); + ASSERT_FALSE(done_closure_.is_null()); + last_worker_status_ = worker_->status(); + BrowserThread::PostTask(BrowserThread::UI, FROM_HERE, done_closure_); + } + virtual void OnReportException(const base::string16& error_message, + int line_number, + int column_number, + const GURL& source_url) OVERRIDE {} + virtual void OnReportConsoleMessage(int source_identifier, + int message_level, + const base::string16& message, + int line_number, + const GURL& source_url) OVERRIDE {} + virtual bool OnMessageReceived(const IPC::Message& message) OVERRIDE { + return false; + } + + scoped_ptr<EmbeddedWorkerInstance> worker_; + EmbeddedWorkerInstance::Status last_worker_status_; + + // Called by EmbeddedWorkerInstance::Observer overrides so that + // test code can wait for the worker status notifications. + base::Closure done_closure_; +}; + +class ServiceWorkerVersionBrowserTest : public ServiceWorkerBrowserTest { + public: + typedef ServiceWorkerVersionBrowserTest self; + + virtual ~ServiceWorkerVersionBrowserTest() {} + + virtual void TearDownOnIOThread() OVERRIDE { + registration_ = NULL; + version_ = NULL; + } + + void InstallTestHelper(const std::string& worker_url, + ServiceWorkerStatusCode expected_status) { + RunOnIOThread(base::Bind(&self::SetUpRegistrationOnIOThread, this, + worker_url)); + + // Dispatch install on a worker. + ServiceWorkerStatusCode status = SERVICE_WORKER_ERROR_FAILED; + base::RunLoop install_run_loop; + BrowserThread::PostTask(BrowserThread::IO, FROM_HERE, + base::Bind(&self::InstallOnIOThread, this, + install_run_loop.QuitClosure(), + &status)); + install_run_loop.Run(); + ASSERT_EQ(expected_status, status); + + // Stop the worker. + status = SERVICE_WORKER_ERROR_FAILED; + base::RunLoop stop_run_loop; + BrowserThread::PostTask(BrowserThread::IO, FROM_HERE, + base::Bind(&self::StopOnIOThread, this, + stop_run_loop.QuitClosure(), + &status)); + stop_run_loop.Run(); + ASSERT_EQ(SERVICE_WORKER_OK, status); + } + + void ActivateTestHelper( + const std::string& worker_url, + ServiceWorkerStatusCode expected_status) { + RunOnIOThread( + base::Bind(&self::SetUpRegistrationOnIOThread, this, worker_url)); + version_->SetStatus(ServiceWorkerVersion::INSTALLED); + ServiceWorkerStatusCode status = SERVICE_WORKER_ERROR_FAILED; + base::RunLoop run_loop; + BrowserThread::PostTask( + BrowserThread::IO, + FROM_HERE, + base::Bind( + &self::ActivateOnIOThread, this, run_loop.QuitClosure(), &status)); + run_loop.Run(); + ASSERT_EQ(expected_status, status); + } + + void FetchOnRegisteredWorker( + ServiceWorkerFetchEventResult* result, + ServiceWorkerResponse* response, + scoped_ptr<webkit_blob::BlobDataHandle>* blob_data_handle) { + blob_context_ = ChromeBlobStorageContext::GetFor( + shell()->web_contents()->GetBrowserContext()); + FetchResult fetch_result; + fetch_result.status = SERVICE_WORKER_ERROR_FAILED; + base::RunLoop fetch_run_loop; + BrowserThread::PostTask(BrowserThread::IO, + FROM_HERE, + base::Bind(&self::FetchOnIOThread, + this, + fetch_run_loop.QuitClosure(), + &fetch_result)); + fetch_run_loop.Run(); + *result = fetch_result.result; + *response = fetch_result.response; + *blob_data_handle = fetch_result.blob_data_handle.Pass(); + ASSERT_EQ(SERVICE_WORKER_OK, fetch_result.status); + } + + void FetchTestHelper( + const std::string& worker_url, + ServiceWorkerFetchEventResult* result, + ServiceWorkerResponse* response, + scoped_ptr<webkit_blob::BlobDataHandle>* blob_data_handle) { + RunOnIOThread( + base::Bind(&self::SetUpRegistrationOnIOThread, this, worker_url)); + FetchOnRegisteredWorker(result, response, blob_data_handle); + } + + void SetUpRegistrationOnIOThread(const std::string& worker_url) { + registration_ = new ServiceWorkerRegistration( + embedded_test_server()->GetURL("/*"), + embedded_test_server()->GetURL(worker_url), + wrapper()->context()->storage()->NewRegistrationId(), + wrapper()->context()->AsWeakPtr()); + version_ = new ServiceWorkerVersion( + registration_, + wrapper()->context()->storage()->NewVersionId(), + wrapper()->context()->AsWeakPtr()); + AssociateRendererProcessToWorker(version_->embedded_worker()); + } + + void StartOnIOThread(const base::Closure& done, + ServiceWorkerStatusCode* result) { + ASSERT_TRUE(BrowserThread::CurrentlyOn(BrowserThread::IO)); + version_->StartWorker(CreateReceiver(BrowserThread::UI, done, result)); + } + + void InstallOnIOThread(const base::Closure& done, + ServiceWorkerStatusCode* result) { + ASSERT_TRUE(BrowserThread::CurrentlyOn(BrowserThread::IO)); + version_->DispatchInstallEvent( + -1, CreateReceiver(BrowserThread::UI, done, result)); + } + + void ActivateOnIOThread(const base::Closure& done, + ServiceWorkerStatusCode* result) { + ASSERT_TRUE(BrowserThread::CurrentlyOn(BrowserThread::IO)); + version_->SetStatus(ServiceWorkerVersion::INSTALLED); + version_->DispatchActivateEvent( + CreateReceiver(BrowserThread::UI, done, result)); + } + + void FetchOnIOThread(const base::Closure& done, FetchResult* result) { + ASSERT_TRUE(BrowserThread::CurrentlyOn(BrowserThread::IO)); + ServiceWorkerFetchRequest request( + embedded_test_server()->GetURL("/service_worker/empty.html"), + "GET", + std::map<std::string, std::string>()); + version_->SetStatus(ServiceWorkerVersion::ACTIVE); + version_->DispatchFetchEvent( + request, CreateResponseReceiver(BrowserThread::UI, done, + blob_context_, result)); + } + + void StopOnIOThread(const base::Closure& done, + ServiceWorkerStatusCode* result) { + ASSERT_TRUE(version_); + version_->StopWorker(CreateReceiver(BrowserThread::UI, done, result)); + } + + void SyncEventOnIOThread(const base::Closure& done, + ServiceWorkerStatusCode* result) { + ASSERT_TRUE(BrowserThread::CurrentlyOn(BrowserThread::IO)); + version_->SetStatus(ServiceWorkerVersion::ACTIVE); + version_->DispatchSyncEvent( + CreateReceiver(BrowserThread::UI, done, result)); + } + + protected: + scoped_refptr<ServiceWorkerRegistration> registration_; + scoped_refptr<ServiceWorkerVersion> version_; + scoped_refptr<ChromeBlobStorageContext> blob_context_; +}; + +IN_PROC_BROWSER_TEST_F(EmbeddedWorkerBrowserTest, StartAndStop) { + // Start a worker and wait until OnStarted() is called. + base::RunLoop start_run_loop; + done_closure_ = start_run_loop.QuitClosure(); + BrowserThread::PostTask(BrowserThread::IO, FROM_HERE, + base::Bind(&self::StartOnIOThread, this)); + start_run_loop.Run(); + + ASSERT_EQ(EmbeddedWorkerInstance::RUNNING, last_worker_status_); + + // Stop a worker and wait until OnStopped() is called. + base::RunLoop stop_run_loop; + done_closure_ = stop_run_loop.QuitClosure(); + BrowserThread::PostTask(BrowserThread::IO, FROM_HERE, + base::Bind(&self::StopOnIOThread, this)); + stop_run_loop.Run(); + + ASSERT_EQ(EmbeddedWorkerInstance::STOPPED, last_worker_status_); +} + +IN_PROC_BROWSER_TEST_F(ServiceWorkerVersionBrowserTest, StartAndStop) { + RunOnIOThread(base::Bind(&self::SetUpRegistrationOnIOThread, this, + "/service_worker/worker.js")); + + // Start a worker. + ServiceWorkerStatusCode status = SERVICE_WORKER_ERROR_FAILED; + base::RunLoop start_run_loop; + BrowserThread::PostTask(BrowserThread::IO, FROM_HERE, + base::Bind(&self::StartOnIOThread, this, + start_run_loop.QuitClosure(), + &status)); + start_run_loop.Run(); + ASSERT_EQ(SERVICE_WORKER_OK, status); + + // Stop the worker. + status = SERVICE_WORKER_ERROR_FAILED; + base::RunLoop stop_run_loop; + BrowserThread::PostTask(BrowserThread::IO, FROM_HERE, + base::Bind(&self::StopOnIOThread, this, + stop_run_loop.QuitClosure(), + &status)); + stop_run_loop.Run(); + ASSERT_EQ(SERVICE_WORKER_OK, status); +} + +IN_PROC_BROWSER_TEST_F(ServiceWorkerVersionBrowserTest, StartNotFound) { + RunOnIOThread(base::Bind(&self::SetUpRegistrationOnIOThread, this, + "/service_worker/nonexistent.js")); + + // Start a worker for nonexistent URL. + ServiceWorkerStatusCode status = SERVICE_WORKER_ERROR_FAILED; + base::RunLoop start_run_loop; + BrowserThread::PostTask(BrowserThread::IO, FROM_HERE, + base::Bind(&self::StartOnIOThread, this, + start_run_loop.QuitClosure(), + &status)); + start_run_loop.Run(); + ASSERT_EQ(SERVICE_WORKER_ERROR_START_WORKER_FAILED, status); +} + +IN_PROC_BROWSER_TEST_F(ServiceWorkerVersionBrowserTest, Install) { + InstallTestHelper("/service_worker/worker.js", SERVICE_WORKER_OK); +} + +IN_PROC_BROWSER_TEST_F(ServiceWorkerVersionBrowserTest, + InstallWithWaitUntil_Fulfilled) { + InstallTestHelper("/service_worker/worker_install_fulfilled.js", + SERVICE_WORKER_OK); +} + +IN_PROC_BROWSER_TEST_F(ServiceWorkerVersionBrowserTest, + Activate_NoEventListener) { + ActivateTestHelper("/service_worker/worker.js", SERVICE_WORKER_OK); + ASSERT_EQ(ServiceWorkerVersion::ACTIVE, version_->status()); +} + +IN_PROC_BROWSER_TEST_F(ServiceWorkerVersionBrowserTest, Activate_Rejected) { + ActivateTestHelper("/service_worker/worker_activate_rejected.js", + SERVICE_WORKER_ERROR_ACTIVATE_WORKER_FAILED); +} + +IN_PROC_BROWSER_TEST_F(ServiceWorkerVersionBrowserTest, + InstallWithWaitUntil_Rejected) { + InstallTestHelper("/service_worker/worker_install_rejected.js", + SERVICE_WORKER_ERROR_INSTALL_WORKER_FAILED); +} + +IN_PROC_BROWSER_TEST_F(ServiceWorkerVersionBrowserTest, FetchEvent_Response) { + ServiceWorkerFetchEventResult result; + ServiceWorkerResponse response; + scoped_ptr<webkit_blob::BlobDataHandle> blob_data_handle; + FetchTestHelper("/service_worker/fetch_event.js", + &result, &response, &blob_data_handle); + ASSERT_EQ(SERVICE_WORKER_FETCH_EVENT_RESULT_RESPONSE, result); + EXPECT_EQ(301, response.status_code); + EXPECT_EQ("Moved Permanently", response.status_text); + std::map<std::string, std::string> expected_headers; + expected_headers["Content-Language"] = "fi"; + expected_headers["Content-Type"] = "text/html; charset=UTF-8"; + EXPECT_EQ(expected_headers, response.headers); + + std::string body; + RunOnIOThread( + base::Bind(&ReadResponseBody, + &body, base::Owned(blob_data_handle.release()))); + EXPECT_EQ("This resource is gone. Gone, gone, gone.", body); +} + +IN_PROC_BROWSER_TEST_F(ServiceWorkerVersionBrowserTest, + SyncAbortedWithoutFlag) { + RunOnIOThread(base::Bind( + &self::SetUpRegistrationOnIOThread, this, "/service_worker/sync.js")); + + // Run the sync event. + ServiceWorkerStatusCode status = SERVICE_WORKER_ERROR_FAILED; + base::RunLoop sync_run_loop; + BrowserThread::PostTask(BrowserThread::IO, + FROM_HERE, + base::Bind(&self::SyncEventOnIOThread, + this, + sync_run_loop.QuitClosure(), + &status)); + sync_run_loop.Run(); + ASSERT_EQ(SERVICE_WORKER_ERROR_ABORT, status); +} + +IN_PROC_BROWSER_TEST_F(ServiceWorkerVersionBrowserTest, SyncEventHandled) { + CommandLine* command_line = CommandLine::ForCurrentProcess(); + command_line->AppendSwitch(switches::kEnableServiceWorkerSync); + + RunOnIOThread(base::Bind( + &self::SetUpRegistrationOnIOThread, this, "/service_worker/sync.js")); + ServiceWorkerFetchEventResult result; + ServiceWorkerResponse response; + scoped_ptr<webkit_blob::BlobDataHandle> blob_data_handle; + // Should 404 before sync event. + FetchOnRegisteredWorker(&result, &response, &blob_data_handle); + EXPECT_EQ(404, response.status_code); + + // Run the sync event. + ServiceWorkerStatusCode status = SERVICE_WORKER_ERROR_FAILED; + base::RunLoop sync_run_loop; + BrowserThread::PostTask(BrowserThread::IO, + FROM_HERE, + base::Bind(&self::SyncEventOnIOThread, + this, + sync_run_loop.QuitClosure(), + &status)); + sync_run_loop.Run(); + ASSERT_EQ(SERVICE_WORKER_OK, status); + + // Should 200 after sync event. + FetchOnRegisteredWorker(&result, &response, &blob_data_handle); + EXPECT_EQ(200, response.status_code); +} + +class ServiceWorkerBlackBoxBrowserTest : public ServiceWorkerBrowserTest { + public: + typedef ServiceWorkerBlackBoxBrowserTest self; + + static void ExpectResultAndRun(bool expected, + const base::Closure& continuation, + bool actual) { + EXPECT_EQ(expected, actual); + continuation.Run(); + } + + void FindRegistrationOnIO(const GURL& document_url, + ServiceWorkerStatusCode* status, + GURL* script_url, + const base::Closure& continuation) { + wrapper()->context()->storage()->FindRegistrationForDocument( + document_url, + base::Bind(&ServiceWorkerBlackBoxBrowserTest::FindRegistrationOnIO2, + this, + status, + script_url, + continuation)); + } + + void FindRegistrationOnIO2( + ServiceWorkerStatusCode* out_status, + GURL* script_url, + const base::Closure& continuation, + ServiceWorkerStatusCode status, + const scoped_refptr<ServiceWorkerRegistration>& registration) { + *out_status = status; + if (registration) { + *script_url = registration->script_url(); + } else { + EXPECT_NE(SERVICE_WORKER_OK, status); + } + continuation.Run(); + } +}; + +static int CountRenderProcessHosts() { + int result = 0; + for (RenderProcessHost::iterator iter(RenderProcessHost::AllHostsIterator()); + !iter.IsAtEnd(); + iter.Advance()) { + result++; + } + return result; +} + +// Crashes on Android: http://crbug.com/387045 +#if defined(OS_ANDROID) +#define MAYBE_Registration DISABLED_Registration +#else +#define MAYBE_Registration Registration +#endif +IN_PROC_BROWSER_TEST_F(ServiceWorkerBlackBoxBrowserTest, MAYBE_Registration) { + // Close the only window to be sure we're not re-using its RenderProcessHost. + shell()->Close(); + EXPECT_EQ(0, CountRenderProcessHosts()); + + const std::string kWorkerUrl = "/service_worker/fetch_event.js"; + + // Unregistering nothing should return true. + { + base::RunLoop run_loop; + public_context()->UnregisterServiceWorker( + embedded_test_server()->GetURL("/*"), + base::Bind(&ServiceWorkerBlackBoxBrowserTest::ExpectResultAndRun, + true, + run_loop.QuitClosure())); + run_loop.Run(); + } + + // If we use a worker URL that doesn't exist, registration fails. + { + base::RunLoop run_loop; + public_context()->RegisterServiceWorker( + embedded_test_server()->GetURL("/*"), + embedded_test_server()->GetURL("/does/not/exist"), + base::Bind(&ServiceWorkerBlackBoxBrowserTest::ExpectResultAndRun, + false, + run_loop.QuitClosure())); + run_loop.Run(); + } + EXPECT_EQ(0, CountRenderProcessHosts()); + + // Register returns when the promise would be resolved. + { + base::RunLoop run_loop; + public_context()->RegisterServiceWorker( + embedded_test_server()->GetURL("/*"), + embedded_test_server()->GetURL(kWorkerUrl), + base::Bind(&ServiceWorkerBlackBoxBrowserTest::ExpectResultAndRun, + true, + run_loop.QuitClosure())); + run_loop.Run(); + } + EXPECT_EQ(1, CountRenderProcessHosts()); + + // Registering again should succeed, although the algo still + // might not be complete. + { + base::RunLoop run_loop; + public_context()->RegisterServiceWorker( + embedded_test_server()->GetURL("/*"), + embedded_test_server()->GetURL(kWorkerUrl), + base::Bind(&ServiceWorkerBlackBoxBrowserTest::ExpectResultAndRun, + true, + run_loop.QuitClosure())); + run_loop.Run(); + } + + // The registration algo might not be far enough along to have + // stored the registration data, so it may not be findable + // at this point. + + // Unregistering something should return true. + { + base::RunLoop run_loop; + public_context()->UnregisterServiceWorker( + embedded_test_server()->GetURL("/*"), + base::Bind(&ServiceWorkerBlackBoxBrowserTest::ExpectResultAndRun, + true, + run_loop.QuitClosure())); + run_loop.Run(); + } + EXPECT_GE(1, CountRenderProcessHosts()) << "Unregistering doesn't stop the " + "workers eagerly, so their RPHs " + "can still be running."; + + // Should not be able to find it. + { + ServiceWorkerStatusCode status = SERVICE_WORKER_ERROR_FAILED; + GURL script_url; + RunOnIOThread( + base::Bind(&ServiceWorkerBlackBoxBrowserTest::FindRegistrationOnIO, + this, + embedded_test_server()->GetURL("/service_worker/empty.html"), + &status, + &script_url)); + EXPECT_EQ(SERVICE_WORKER_ERROR_NOT_FOUND, status); + } +} + +} // namespace content diff --git a/chromium/content/browser/service_worker/service_worker_context.h b/chromium/content/browser/service_worker/service_worker_context.h deleted file mode 100644 index d9f51f3165c..00000000000 --- a/chromium/content/browser/service_worker/service_worker_context.h +++ /dev/null @@ -1,28 +0,0 @@ -// Copyright 2013 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 CONTENT_BROWSER_SERVICE_WORKER_SERVICE_WORKER_CONTEXT_H_ -#define CONTENT_BROWSER_SERVICE_WORKER_SERVICE_WORKER_CONTEXT_H_ - -#include "base/basictypes.h" - -namespace content { - -// Represents the per-BrowserContext ServiceWorker data. -class ServiceWorkerContext { - public: - // TODO(michaeln): This class is a place holder for content/public api - // which will come later. Promote this class when we get there. - - protected: - ServiceWorkerContext() {} - virtual ~ServiceWorkerContext() {} - - private: - DISALLOW_COPY_AND_ASSIGN(ServiceWorkerContext); -}; - -} // namespace content - -#endif // CONTENT_BROWSER_SERVICE_WORKER_SERVICE_WORKER_CONTEXT_H_ diff --git a/chromium/content/browser/service_worker/service_worker_context_core.cc b/chromium/content/browser/service_worker/service_worker_context_core.cc index 048d33bd836..cdc67460bcc 100644 --- a/chromium/content/browser/service_worker/service_worker_context_core.cc +++ b/chromium/content/browser/service_worker/service_worker_context_core.cc @@ -4,27 +4,106 @@ #include "content/browser/service_worker/service_worker_context_core.h" -#include "base/command_line.h" #include "base/files/file_path.h" +#include "base/message_loop/message_loop_proxy.h" #include "base/strings/string_util.h" #include "content/browser/service_worker/embedded_worker_registry.h" +#include "content/browser/service_worker/service_worker_context_observer.h" +#include "content/browser/service_worker/service_worker_context_wrapper.h" +#include "content/browser/service_worker/service_worker_info.h" +#include "content/browser/service_worker/service_worker_job_coordinator.h" +#include "content/browser/service_worker/service_worker_process_manager.h" #include "content/browser/service_worker/service_worker_provider_host.h" #include "content/browser/service_worker/service_worker_register_job.h" #include "content/browser/service_worker/service_worker_registration.h" #include "content/browser/service_worker/service_worker_storage.h" #include "content/public/browser/browser_thread.h" -#include "content/public/common/content_switches.h" #include "url/gurl.h" namespace content { +ServiceWorkerContextCore::ProviderHostIterator::~ProviderHostIterator() {} + +ServiceWorkerProviderHost* +ServiceWorkerContextCore::ProviderHostIterator::GetProviderHost() { + DCHECK(!IsAtEnd()); + return provider_host_iterator_->GetCurrentValue(); +} + +void ServiceWorkerContextCore::ProviderHostIterator::Advance() { + DCHECK(!IsAtEnd()); + DCHECK(!provider_host_iterator_->IsAtEnd()); + DCHECK(!provider_iterator_->IsAtEnd()); + + // Advance the inner iterator. If an element is reached, we're done. + provider_host_iterator_->Advance(); + if (!provider_host_iterator_->IsAtEnd()) + return; + + // Advance the outer iterator until an element is reached, or end is hit. + while (true) { + provider_iterator_->Advance(); + if (provider_iterator_->IsAtEnd()) + return; + ProviderMap* provider_map = provider_iterator_->GetCurrentValue(); + provider_host_iterator_.reset(new ProviderMap::iterator(provider_map)); + if (!provider_host_iterator_->IsAtEnd()) + return; + } +} + +bool ServiceWorkerContextCore::ProviderHostIterator::IsAtEnd() { + return provider_iterator_->IsAtEnd() && + (!provider_host_iterator_ || provider_host_iterator_->IsAtEnd()); +} + +ServiceWorkerContextCore::ProviderHostIterator::ProviderHostIterator( + ProcessToProviderMap* map) + : map_(map) { + DCHECK(map); + Initialize(); +} + +void ServiceWorkerContextCore::ProviderHostIterator::Initialize() { + provider_iterator_.reset(new ProcessToProviderMap::iterator(map_)); + // Advance to the first element. + while (!provider_iterator_->IsAtEnd()) { + ProviderMap* provider_map = provider_iterator_->GetCurrentValue(); + provider_host_iterator_.reset(new ProviderMap::iterator(provider_map)); + if (!provider_host_iterator_->IsAtEnd()) + return; + provider_iterator_->Advance(); + } +} + ServiceWorkerContextCore::ServiceWorkerContextCore( const base::FilePath& path, - quota::QuotaManagerProxy* quota_manager_proxy) - : storage_(new ServiceWorkerStorage(path, quota_manager_proxy)), - embedded_worker_registry_(new EmbeddedWorkerRegistry(AsWeakPtr())) {} + base::SequencedTaskRunner* database_task_runner, + base::MessageLoopProxy* disk_cache_thread, + quota::QuotaManagerProxy* quota_manager_proxy, + ObserverListThreadSafe<ServiceWorkerContextObserver>* observer_list, + ServiceWorkerContextWrapper* wrapper) + : weak_factory_(this), + wrapper_(wrapper), + storage_(new ServiceWorkerStorage(path, + AsWeakPtr(), + database_task_runner, + disk_cache_thread, + quota_manager_proxy)), + embedded_worker_registry_(new EmbeddedWorkerRegistry(AsWeakPtr())), + job_coordinator_(new ServiceWorkerJobCoordinator(AsWeakPtr())), + next_handle_id_(0), + observer_list_(observer_list) { +} -ServiceWorkerContextCore::~ServiceWorkerContextCore() {} +ServiceWorkerContextCore::~ServiceWorkerContextCore() { + for (VersionMap::iterator it = live_versions_.begin(); + it != live_versions_.end(); + ++it) { + it->second->RemoveListener(this); + } + weak_factory_.InvalidateWeakPtrs(); +} ServiceWorkerProviderHost* ServiceWorkerContextCore::GetProviderHost( int process_id, int provider_id) { @@ -58,42 +137,204 @@ void ServiceWorkerContextCore::RemoveAllProviderHostsForProcess( providers_.Remove(process_id); } -bool ServiceWorkerContextCore::IsEnabled() { - return CommandLine::ForCurrentProcess()->HasSwitch( - switches::kEnableServiceWorker); +scoped_ptr<ServiceWorkerContextCore::ProviderHostIterator> +ServiceWorkerContextCore::GetProviderHostIterator() { + return make_scoped_ptr(new ProviderHostIterator(&providers_)); } void ServiceWorkerContextCore::RegisterServiceWorker( const GURL& pattern, const GURL& script_url, + int source_process_id, + ServiceWorkerProviderHost* provider_host, const RegistrationCallback& callback) { - DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); + DCHECK_CURRENTLY_ON(BrowserThread::IO); - storage_->Register(pattern, - script_url, - base::Bind(&ServiceWorkerContextCore::RegistrationComplete, - AsWeakPtr(), - callback)); + // TODO(kinuko): Wire the provider_host so that we can tell which document + // is calling .register. + + job_coordinator_->Register( + pattern, + script_url, + source_process_id, + base::Bind(&ServiceWorkerContextCore::RegistrationComplete, + AsWeakPtr(), + pattern, + callback)); } void ServiceWorkerContextCore::UnregisterServiceWorker( const GURL& pattern, const UnregistrationCallback& callback) { - DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); + DCHECK_CURRENTLY_ON(BrowserThread::IO); - storage_->Unregister(pattern, callback); + job_coordinator_->Unregister( + pattern, + base::Bind(&ServiceWorkerContextCore::UnregistrationComplete, + AsWeakPtr(), + pattern, + callback)); } void ServiceWorkerContextCore::RegistrationComplete( + const GURL& pattern, const ServiceWorkerContextCore::RegistrationCallback& callback, - ServiceWorkerRegistrationStatus status, - const scoped_refptr<ServiceWorkerRegistration>& registration) { - if (status != REGISTRATION_OK) { - DCHECK(!registration); - callback.Run(status, -1L); + ServiceWorkerStatusCode status, + ServiceWorkerRegistration* registration, + ServiceWorkerVersion* version) { + if (status != SERVICE_WORKER_OK) { + DCHECK(!version); + callback.Run(status, + kInvalidServiceWorkerRegistrationId, + kInvalidServiceWorkerVersionId); + return; + } + + DCHECK(version); + DCHECK_EQ(version->registration_id(), registration->id()); + callback.Run(status, + registration->id(), + version->version_id()); + if (observer_list_) { + observer_list_->Notify(&ServiceWorkerContextObserver::OnRegistrationStored, + pattern); + } +} + +void ServiceWorkerContextCore::UnregistrationComplete( + const GURL& pattern, + const ServiceWorkerContextCore::UnregistrationCallback& callback, + ServiceWorkerStatusCode status) { + callback.Run(status); + if (observer_list_) { + observer_list_->Notify(&ServiceWorkerContextObserver::OnRegistrationDeleted, + pattern); + } +} + +ServiceWorkerRegistration* ServiceWorkerContextCore::GetLiveRegistration( + int64 id) { + RegistrationsMap::iterator it = live_registrations_.find(id); + return (it != live_registrations_.end()) ? it->second : NULL; +} + +void ServiceWorkerContextCore::AddLiveRegistration( + ServiceWorkerRegistration* registration) { + DCHECK(!GetLiveRegistration(registration->id())); + live_registrations_[registration->id()] = registration; +} + +void ServiceWorkerContextCore::RemoveLiveRegistration(int64 id) { + live_registrations_.erase(id); +} + +ServiceWorkerVersion* ServiceWorkerContextCore::GetLiveVersion( + int64 id) { + VersionMap::iterator it = live_versions_.find(id); + return (it != live_versions_.end()) ? it->second : NULL; +} + +void ServiceWorkerContextCore::AddLiveVersion(ServiceWorkerVersion* version) { + DCHECK(!GetLiveVersion(version->version_id())); + live_versions_[version->version_id()] = version; + version->AddListener(this); +} + +void ServiceWorkerContextCore::RemoveLiveVersion(int64 id) { + live_versions_.erase(id); +} + +std::vector<ServiceWorkerRegistrationInfo> +ServiceWorkerContextCore::GetAllLiveRegistrationInfo() { + std::vector<ServiceWorkerRegistrationInfo> infos; + for (std::map<int64, ServiceWorkerRegistration*>::const_iterator iter = + live_registrations_.begin(); + iter != live_registrations_.end(); + ++iter) { + infos.push_back(iter->second->GetInfo()); } + return infos; +} + +std::vector<ServiceWorkerVersionInfo> +ServiceWorkerContextCore::GetAllLiveVersionInfo() { + std::vector<ServiceWorkerVersionInfo> infos; + for (std::map<int64, ServiceWorkerVersion*>::const_iterator iter = + live_versions_.begin(); + iter != live_versions_.end(); + ++iter) { + infos.push_back(iter->second->GetInfo()); + } + return infos; +} + +int ServiceWorkerContextCore::GetNewServiceWorkerHandleId() { + return next_handle_id_++; +} + +void ServiceWorkerContextCore::OnWorkerStarted(ServiceWorkerVersion* version) { + if (!observer_list_) + return; + observer_list_->Notify(&ServiceWorkerContextObserver::OnWorkerStarted, + version->version_id(), + version->embedded_worker()->process_id(), + version->embedded_worker()->thread_id()); +} + +void ServiceWorkerContextCore::OnWorkerStopped(ServiceWorkerVersion* version) { + if (!observer_list_) + return; + observer_list_->Notify(&ServiceWorkerContextObserver::OnWorkerStopped, + version->version_id(), + version->embedded_worker()->process_id(), + version->embedded_worker()->thread_id()); +} + +void ServiceWorkerContextCore::OnVersionStateChanged( + ServiceWorkerVersion* version) { + if (!observer_list_) + return; + observer_list_->Notify(&ServiceWorkerContextObserver::OnVersionStateChanged, + version->version_id()); +} + +void ServiceWorkerContextCore::OnErrorReported( + ServiceWorkerVersion* version, + const base::string16& error_message, + int line_number, + int column_number, + const GURL& source_url) { + if (!observer_list_) + return; + observer_list_->Notify( + &ServiceWorkerContextObserver::OnErrorReported, + version->version_id(), + version->embedded_worker()->process_id(), + version->embedded_worker()->thread_id(), + ServiceWorkerContextObserver::ErrorInfo( + error_message, line_number, column_number, source_url)); +} + +void ServiceWorkerContextCore::OnReportConsoleMessage( + ServiceWorkerVersion* version, + int source_identifier, + int message_level, + const base::string16& message, + int line_number, + const GURL& source_url) { + if (!observer_list_) + return; + observer_list_->Notify( + &ServiceWorkerContextObserver::OnReportConsoleMessage, + version->version_id(), + version->embedded_worker()->process_id(), + version->embedded_worker()->thread_id(), + ServiceWorkerContextObserver::ConsoleMessage( + source_identifier, message_level, message, line_number, source_url)); +} - callback.Run(status, registration->id()); +ServiceWorkerProcessManager* ServiceWorkerContextCore::process_manager() { + return wrapper_->process_manager(); } } // namespace content diff --git a/chromium/content/browser/service_worker/service_worker_context_core.h b/chromium/content/browser/service_worker/service_worker_context_core.h index b7b26537bec..b3f97ae28f3 100644 --- a/chromium/content/browser/service_worker/service_worker_context_core.h +++ b/chromium/content/browser/service_worker/service_worker_context_core.h @@ -6,12 +6,16 @@ #define CONTENT_BROWSER_SERVICE_WORKER_SERVICE_WORKER_CONTEXT_CORE_H_ #include <map> +#include <vector> #include "base/callback.h" #include "base/files/file_path.h" #include "base/id_map.h" #include "base/memory/scoped_ptr.h" #include "base/memory/weak_ptr.h" +#include "base/observer_list_threadsafe.h" +#include "content/browser/service_worker/service_worker_info.h" +#include "content/browser/service_worker/service_worker_process_manager.h" #include "content/browser/service_worker/service_worker_provider_host.h" #include "content/browser/service_worker/service_worker_registration_status.h" #include "content/browser/service_worker/service_worker_storage.h" @@ -21,6 +25,8 @@ class GURL; namespace base { class FilePath; +class MessageLoopProxy; +class SequencedTaskRunner; } namespace quota { @@ -30,6 +36,10 @@ class QuotaManagerProxy; namespace content { class EmbeddedWorkerRegistry; +class ServiceWorkerContextObserver; +class ServiceWorkerContextWrapper; +class ServiceWorkerHandle; +class ServiceWorkerJobCoordinator; class ServiceWorkerProviderHost; class ServiceWorkerRegistration; class ServiceWorkerStorage; @@ -40,61 +50,149 @@ class ServiceWorkerStorage; // is the root of the containment hierarchy for service worker data // associated with a particular partition. class CONTENT_EXPORT ServiceWorkerContextCore - : NON_EXPORTED_BASE( - public base::SupportsWeakPtr<ServiceWorkerContextCore>) { + : public ServiceWorkerVersion::Listener { public: - typedef base::Callback<void(ServiceWorkerRegistrationStatus status, - int64 registration_id)> RegistrationCallback; + typedef base::Callback<void(ServiceWorkerStatusCode status, + int64 registration_id, + int64 version_id)> RegistrationCallback; typedef base::Callback< - void(ServiceWorkerRegistrationStatus status)> UnregistrationCallback; + void(ServiceWorkerStatusCode status)> UnregistrationCallback; + typedef IDMap<ServiceWorkerProviderHost, IDMapOwnPointer> ProviderMap; + typedef IDMap<ProviderMap, IDMapOwnPointer> ProcessToProviderMap; + + // Iterates over ServiceWorkerProviderHost objects in a ProcessToProviderMap. + class ProviderHostIterator { + public: + ~ProviderHostIterator(); + ServiceWorkerProviderHost* GetProviderHost(); + void Advance(); + bool IsAtEnd(); + + private: + friend class ServiceWorkerContextCore; + explicit ProviderHostIterator(ProcessToProviderMap* map); + void Initialize(); + + ProcessToProviderMap* map_; + scoped_ptr<ProcessToProviderMap::iterator> provider_iterator_; + scoped_ptr<ProviderMap::iterator> provider_host_iterator_; + + DISALLOW_COPY_AND_ASSIGN(ProviderHostIterator); + }; // This is owned by the StoragePartition, which will supply it with // the local path on disk. Given an empty |user_data_directory|, - // nothing will be stored on disk. - ServiceWorkerContextCore(const base::FilePath& user_data_directory, - quota::QuotaManagerProxy* quota_manager_proxy); - ~ServiceWorkerContextCore(); + // nothing will be stored on disk. |observer_list| is created in + // ServiceWorkerContextWrapper. When Notify() of |observer_list| is called in + // ServiceWorkerContextCore, the methods of ServiceWorkerContextObserver will + // be called on the thread which called AddObserver() of |observer_list|. + ServiceWorkerContextCore( + const base::FilePath& user_data_directory, + base::SequencedTaskRunner* database_task_runner, + base::MessageLoopProxy* disk_cache_thread, + quota::QuotaManagerProxy* quota_manager_proxy, + ObserverListThreadSafe<ServiceWorkerContextObserver>* observer_list, + ServiceWorkerContextWrapper* wrapper); + virtual ~ServiceWorkerContextCore(); + + // ServiceWorkerVersion::Listener overrides. + virtual void OnWorkerStarted(ServiceWorkerVersion* version) OVERRIDE; + virtual void OnWorkerStopped(ServiceWorkerVersion* version) OVERRIDE; + virtual void OnVersionStateChanged(ServiceWorkerVersion* version) OVERRIDE; + virtual void OnErrorReported(ServiceWorkerVersion* version, + const base::string16& error_message, + int line_number, + int column_number, + const GURL& source_url) OVERRIDE; + virtual void OnReportConsoleMessage(ServiceWorkerVersion* version, + int source_identifier, + int message_level, + const base::string16& message, + int line_number, + const GURL& source_url) OVERRIDE; ServiceWorkerStorage* storage() { return storage_.get(); } + ServiceWorkerProcessManager* process_manager(); + EmbeddedWorkerRegistry* embedded_worker_registry() { + return embedded_worker_registry_.get(); + } + ServiceWorkerJobCoordinator* job_coordinator() { + return job_coordinator_.get(); + } // The context class owns the set of ProviderHosts. ServiceWorkerProviderHost* GetProviderHost(int process_id, int provider_id); void AddProviderHost(scoped_ptr<ServiceWorkerProviderHost> provider_host); void RemoveProviderHost(int process_id, int provider_id); void RemoveAllProviderHostsForProcess(int process_id); - - // Checks the cmdline flag. - bool IsEnabled(); + scoped_ptr<ProviderHostIterator> GetProviderHostIterator(); // The callback will be called on the IO thread. + // A child process of |source_process_id| may be used to run the created + // worker for initial installation. + // Non-null |provider_host| must be given if this is called from a document, + // whose process_id() must match with |source_process_id|. void RegisterServiceWorker(const GURL& pattern, const GURL& script_url, + int source_process_id, + ServiceWorkerProviderHost* provider_host, const RegistrationCallback& callback); // The callback will be called on the IO thread. void UnregisterServiceWorker(const GURL& pattern, const UnregistrationCallback& callback); - EmbeddedWorkerRegistry* embedded_worker_registry() { - return embedded_worker_registry_.get(); + // This class maintains collections of live instances, this class + // does not own these object or influence their lifetime. + ServiceWorkerRegistration* GetLiveRegistration(int64 registration_id); + void AddLiveRegistration(ServiceWorkerRegistration* registration); + void RemoveLiveRegistration(int64 registration_id); + ServiceWorkerVersion* GetLiveVersion(int64 version_id); + void AddLiveVersion(ServiceWorkerVersion* version); + void RemoveLiveVersion(int64 registration_id); + + std::vector<ServiceWorkerRegistrationInfo> GetAllLiveRegistrationInfo(); + std::vector<ServiceWorkerVersionInfo> GetAllLiveVersionInfo(); + + // Returns new context-local unique ID for ServiceWorkerHandle. + int GetNewServiceWorkerHandleId(); + + base::WeakPtr<ServiceWorkerContextCore> AsWeakPtr() { + return weak_factory_.GetWeakPtr(); } private: - typedef IDMap<ServiceWorkerProviderHost, IDMapOwnPointer> ProviderMap; - typedef IDMap<ProviderMap, IDMapOwnPointer> ProcessToProviderMap; + typedef std::map<int64, ServiceWorkerRegistration*> RegistrationsMap; + typedef std::map<int64, ServiceWorkerVersion*> VersionMap; ProviderMap* GetProviderMapForProcess(int process_id) { return providers_.Lookup(process_id); } - void RegistrationComplete( - const RegistrationCallback& callback, - ServiceWorkerRegistrationStatus status, - const scoped_refptr<ServiceWorkerRegistration>& registration); - + void RegistrationComplete(const GURL& pattern, + const RegistrationCallback& callback, + ServiceWorkerStatusCode status, + ServiceWorkerRegistration* registration, + ServiceWorkerVersion* version); + + void UnregistrationComplete(const GURL& pattern, + const UnregistrationCallback& callback, + ServiceWorkerStatusCode status); + + base::WeakPtrFactory<ServiceWorkerContextCore> weak_factory_; + // It's safe to store a raw pointer instead of a scoped_refptr to |wrapper_| + // because the Wrapper::Shutdown call that hops threads to destroy |this| uses + // Bind() to hold a reference to |wrapper_| until |this| is fully destroyed. + ServiceWorkerContextWrapper* wrapper_; ProcessToProviderMap providers_; scoped_ptr<ServiceWorkerStorage> storage_; scoped_refptr<EmbeddedWorkerRegistry> embedded_worker_registry_; + scoped_ptr<ServiceWorkerJobCoordinator> job_coordinator_; + std::map<int64, ServiceWorkerRegistration*> live_registrations_; + std::map<int64, ServiceWorkerVersion*> live_versions_; + int next_handle_id_; + scoped_refptr<ObserverListThreadSafe<ServiceWorkerContextObserver> > + observer_list_; DISALLOW_COPY_AND_ASSIGN(ServiceWorkerContextCore); }; diff --git a/chromium/content/browser/service_worker/service_worker_context_observer.h b/chromium/content/browser/service_worker/service_worker_context_observer.h new file mode 100644 index 00000000000..8bdaeea394f --- /dev/null +++ b/chromium/content/browser/service_worker/service_worker_context_observer.h @@ -0,0 +1,70 @@ +// Copyright 2014 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 CONTENT_BROWSER_SERVICE_WORKER_SERVICE_WORKER_CONTEXT_OBSERVER_H_ +#define CONTENT_BROWSER_SERVICE_WORKER_SERVICE_WORKER_CONTEXT_OBSERVER_H_ + +#include "base/strings/string16.h" +#include "url/gurl.h" + +namespace content { + +class ServiceWorkerContextObserver { + public: + struct ErrorInfo { + ErrorInfo(const base::string16& message, + int line, + int column, + const GURL& url) + : error_message(message), + line_number(line), + column_number(column), + source_url(url) {} + const base::string16 error_message; + const int line_number; + const int column_number; + const GURL source_url; + }; + struct ConsoleMessage { + ConsoleMessage(int source_identifier, + int message_level, + const base::string16& message, + int line_number, + const GURL& source_url) + : source_identifier(source_identifier), + message_level(message_level), + message(message), + line_number(line_number), + source_url(source_url) {} + const int source_identifier; + const int message_level; + const base::string16 message; + const int line_number; + const GURL source_url; + }; + virtual void OnWorkerStarted(int64 version_id, + int process_id, + int thread_id) {} + virtual void OnWorkerStopped(int64 version_id, + int process_id, + int thread_id) {} + virtual void OnVersionStateChanged(int64 version_id) {} + virtual void OnErrorReported(int64 version_id, + int process_id, + int thread_id, + const ErrorInfo& info) {} + virtual void OnReportConsoleMessage(int64 version_id, + int process_id, + int thread_id, + const ConsoleMessage& message) {} + virtual void OnRegistrationStored(const GURL& pattern) {} + virtual void OnRegistrationDeleted(const GURL& pattern) {} + + protected: + virtual ~ServiceWorkerContextObserver() {} +}; + +} // namespace content + +#endif // CONTENT_BROWSER_SERVICE_WORKER_SERVICE_WORKER_CONTEXT_OBSERVER_H_ diff --git a/chromium/content/browser/service_worker/service_worker_context_request_handler.cc b/chromium/content/browser/service_worker/service_worker_context_request_handler.cc new file mode 100644 index 00000000000..248587fceb7 --- /dev/null +++ b/chromium/content/browser/service_worker/service_worker_context_request_handler.cc @@ -0,0 +1,90 @@ +// Copyright 2014 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 "content/browser/service_worker/service_worker_context_request_handler.h" + +#include "content/browser/service_worker/service_worker_context_core.h" +#include "content/browser/service_worker/service_worker_provider_host.h" +#include "content/browser/service_worker/service_worker_read_from_cache_job.h" +#include "content/browser/service_worker/service_worker_storage.h" +#include "content/browser/service_worker/service_worker_version.h" +#include "content/browser/service_worker/service_worker_write_to_cache_job.h" +#include "net/url_request/url_request.h" + +namespace content { + +ServiceWorkerContextRequestHandler::ServiceWorkerContextRequestHandler( + base::WeakPtr<ServiceWorkerContextCore> context, + base::WeakPtr<ServiceWorkerProviderHost> provider_host, + base::WeakPtr<webkit_blob::BlobStorageContext> blob_storage_context, + ResourceType::Type resource_type) + : ServiceWorkerRequestHandler(context, + provider_host, + blob_storage_context, + resource_type), + version_(provider_host_->running_hosted_version()) { + DCHECK(provider_host_->IsHostToRunningServiceWorker()); +} + +ServiceWorkerContextRequestHandler::~ServiceWorkerContextRequestHandler() { +} + +net::URLRequestJob* ServiceWorkerContextRequestHandler::MaybeCreateJob( + net::URLRequest* request, + net::NetworkDelegate* network_delegate) { + if (!provider_host_ || !version_ || !context_) + return NULL; + + // We currently have no use case for hijacking a redirected request. + if (request->url_chain().size() > 1) + return NULL; + + // We only use the script cache for main script loading and + // importScripts(), even if a cached script is xhr'd, we don't + // retrieve it from the script cache. + // TODO(michaeln): Get the desired behavior clarified in the spec, + // and make tweak the behavior here to match. + if (resource_type_ != ResourceType::SERVICE_WORKER && + resource_type_ != ResourceType::SCRIPT) { + return NULL; + } + + if (ShouldAddToScriptCache(request->url())) { + int64 response_id = context_->storage()->NewResourceId(); + if (response_id == kInvalidServiceWorkerResponseId) + return NULL; + return new ServiceWorkerWriteToCacheJob( + request, network_delegate, context_, version_, response_id); + } + + int64 response_id = kInvalidServiceWorkerResponseId; + if (ShouldReadFromScriptCache(request->url(), &response_id)) { + return new ServiceWorkerReadFromCacheJob( + request, network_delegate, context_, response_id); + } + + // NULL means use the network. + return NULL; +} + +bool ServiceWorkerContextRequestHandler::ShouldAddToScriptCache( + const GURL& url) { + // We only write imports that occur during the initial eval. + if (version_->status() != ServiceWorkerVersion::NEW) + return false; + return version_->script_cache_map()->Lookup(url) == + kInvalidServiceWorkerResponseId; +} + +bool ServiceWorkerContextRequestHandler::ShouldReadFromScriptCache( + const GURL& url, int64* response_id_out) { + // We don't read from the script cache until the version is INSTALLED. + if (version_->status() == ServiceWorkerVersion::NEW || + version_->status() == ServiceWorkerVersion::INSTALLING) + return false; + *response_id_out = version_->script_cache_map()->Lookup(url); + return *response_id_out != kInvalidServiceWorkerResponseId; +} + +} // namespace content diff --git a/chromium/content/browser/service_worker/service_worker_context_request_handler.h b/chromium/content/browser/service_worker/service_worker_context_request_handler.h new file mode 100644 index 00000000000..ca894ce237e --- /dev/null +++ b/chromium/content/browser/service_worker/service_worker_context_request_handler.h @@ -0,0 +1,42 @@ +// Copyright 2014 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 CONTENT_BROWSER_SERVICE_WORKER_SERVICE_WORKER_CONTEXT_REQUEST_HANDLER_H_ +#define CONTENT_BROWSER_SERVICE_WORKER_SERVICE_WORKER_CONTEXT_REQUEST_HANDLER_H_ + +#include "content/browser/service_worker/service_worker_request_handler.h" + +namespace content { + +class ServiceWorkerVersion; + +// A request handler derivative used to handle requests from +// service workers. +class CONTENT_EXPORT ServiceWorkerContextRequestHandler + : public ServiceWorkerRequestHandler { + public: + ServiceWorkerContextRequestHandler( + base::WeakPtr<ServiceWorkerContextCore> context, + base::WeakPtr<ServiceWorkerProviderHost> provider_host, + base::WeakPtr<webkit_blob::BlobStorageContext> blob_storage_context, + ResourceType::Type resource_type); + virtual ~ServiceWorkerContextRequestHandler(); + + // Called via custom URLRequestJobFactory. + virtual net::URLRequestJob* MaybeCreateJob( + net::URLRequest* request, + net::NetworkDelegate* network_delegate) OVERRIDE; + + private: + bool ShouldAddToScriptCache(const GURL& url); + bool ShouldReadFromScriptCache(const GURL& url, int64* response_id_out); + + scoped_refptr<ServiceWorkerVersion> version_; + + DISALLOW_COPY_AND_ASSIGN(ServiceWorkerContextRequestHandler); +}; + +} // namespace content + +#endif // CONTENT_BROWSER_SERVICE_WORKER_SERVICE_WORKER_CONTEXT_REQUEST_HANDLER_H_ diff --git a/chromium/content/browser/service_worker/service_worker_context_unittest.cc b/chromium/content/browser/service_worker/service_worker_context_unittest.cc index e7c230f93a0..14f54f7cb50 100644 --- a/chromium/content/browser/service_worker/service_worker_context_unittest.cc +++ b/chromium/content/browser/service_worker/service_worker_context_unittest.cc @@ -2,14 +2,19 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -#include "content/browser/service_worker/service_worker_context.h" +#include "content/public/browser/service_worker_context.h" #include "base/files/scoped_temp_dir.h" #include "base/logging.h" #include "base/message_loop/message_loop.h" #include "content/browser/browser_thread_impl.h" +#include "content/browser/service_worker/embedded_worker_registry.h" +#include "content/browser/service_worker/embedded_worker_test_helper.h" #include "content/browser/service_worker/service_worker_context_core.h" #include "content/browser/service_worker/service_worker_registration.h" +#include "content/browser/service_worker/service_worker_storage.h" +#include "content/common/service_worker/embedded_worker_messages.h" +#include "content/common/service_worker/service_worker_messages.h" #include "content/public/test/test_browser_thread_bundle.h" #include "content/public/test/test_utils.h" #include "testing/gtest/include/gtest/gtest.h" @@ -19,20 +24,27 @@ namespace content { namespace { void SaveResponseCallback(bool* called, - int64* store_result, - ServiceWorkerRegistrationStatus status, - int64 result) { + int64* store_registration_id, + int64* store_version_id, + ServiceWorkerStatusCode status, + int64 registration_id, + int64 version_id) { + EXPECT_EQ(SERVICE_WORKER_OK, status) << ServiceWorkerStatusToString(status); *called = true; - *store_result = result; + *store_registration_id = registration_id; + *store_version_id = version_id; } ServiceWorkerContextCore::RegistrationCallback MakeRegisteredCallback( bool* called, - int64* store_result) { - return base::Bind(&SaveResponseCallback, called, store_result); + int64* store_registration_id, + int64* store_version_id) { + return base::Bind(&SaveResponseCallback, called, + store_registration_id, + store_version_id); } -void CallCompletedCallback(bool* called, ServiceWorkerRegistrationStatus) { +void CallCompletedCallback(bool* called, ServiceWorkerStatusCode) { *called = true; } @@ -41,44 +53,212 @@ ServiceWorkerContextCore::UnregistrationCallback MakeUnregisteredCallback( return base::Bind(&CallCompletedCallback, called); } +void ExpectRegisteredWorkers( + ServiceWorkerStatusCode expect_status, + int64 expect_version_id, + bool expect_waiting, + bool expect_active, + ServiceWorkerStatusCode status, + const scoped_refptr<ServiceWorkerRegistration>& registration) { + ASSERT_EQ(expect_status, status); + if (status != SERVICE_WORKER_OK) { + EXPECT_FALSE(registration); + return; + } + + if (expect_waiting) { + EXPECT_TRUE(registration->waiting_version()); + EXPECT_EQ(expect_version_id, + registration->waiting_version()->version_id()); + } else { + EXPECT_FALSE(registration->waiting_version()); + } + + if (expect_active) { + EXPECT_TRUE(registration->active_version()); + EXPECT_EQ(expect_version_id, + registration->active_version()->version_id()); + } else { + EXPECT_FALSE(registration->active_version()); + } +} + +class RejectInstallTestHelper : public EmbeddedWorkerTestHelper { + public: + RejectInstallTestHelper(int mock_render_process_id) + : EmbeddedWorkerTestHelper(mock_render_process_id) {} + + virtual void OnInstallEvent(int embedded_worker_id, + int request_id, + int active_version_id) OVERRIDE { + SimulateSend( + new ServiceWorkerHostMsg_InstallEventFinished( + embedded_worker_id, request_id, + blink::WebServiceWorkerEventResultRejected)); + } +}; + +class RejectActivateTestHelper : public EmbeddedWorkerTestHelper { + public: + RejectActivateTestHelper(int mock_render_process_id) + : EmbeddedWorkerTestHelper(mock_render_process_id) {} + + virtual void OnActivateEvent(int embedded_worker_id, + int request_id) OVERRIDE { + SimulateSend( + new ServiceWorkerHostMsg_ActivateEventFinished( + embedded_worker_id, request_id, + blink::WebServiceWorkerEventResultRejected)); + } +}; + } // namespace class ServiceWorkerContextTest : public testing::Test { public: ServiceWorkerContextTest() - : browser_thread_bundle_(TestBrowserThreadBundle::IO_MAINLOOP) {} + : browser_thread_bundle_(TestBrowserThreadBundle::IO_MAINLOOP), + render_process_id_(99) {} virtual void SetUp() OVERRIDE { - context_.reset(new ServiceWorkerContextCore(base::FilePath(), NULL)); + helper_.reset(new EmbeddedWorkerTestHelper(render_process_id_)); + } + + virtual void TearDown() OVERRIDE { + helper_.reset(); } - virtual void TearDown() OVERRIDE { context_.reset(); } + ServiceWorkerContextCore* context() { return helper_->context(); } protected: TestBrowserThreadBundle browser_thread_bundle_; - scoped_ptr<ServiceWorkerContextCore> context_; + scoped_ptr<EmbeddedWorkerTestHelper> helper_; + const int render_process_id_; }; -void RegistrationCallback( - scoped_refptr<ServiceWorkerRegistration>* registration, - const scoped_refptr<ServiceWorkerRegistration>& result) { - *registration = result; -} - // Make sure basic registration is working. TEST_F(ServiceWorkerContextTest, Register) { - int64 registration_id = -1L; + int64 registration_id = kInvalidServiceWorkerRegistrationId; + int64 version_id = kInvalidServiceWorkerVersionId; bool called = false; - context_->RegisterServiceWorker( + context()->RegisterServiceWorker( GURL("http://www.example.com/*"), GURL("http://www.example.com/service_worker.js"), - MakeRegisteredCallback(&called, ®istration_id)); + render_process_id_, + NULL, + MakeRegisteredCallback(&called, ®istration_id, &version_id)); ASSERT_FALSE(called); base::RunLoop().RunUntilIdle(); - ASSERT_TRUE(called); + EXPECT_TRUE(called); + + EXPECT_EQ(4UL, helper_->ipc_sink()->message_count()); + EXPECT_TRUE(helper_->ipc_sink()->GetUniqueMessageMatching( + EmbeddedWorkerMsg_StartWorker::ID)); + EXPECT_TRUE(helper_->inner_ipc_sink()->GetUniqueMessageMatching( + ServiceWorkerMsg_InstallEvent::ID)); + EXPECT_TRUE(helper_->inner_ipc_sink()->GetUniqueMessageMatching( + ServiceWorkerMsg_ActivateEvent::ID)); + EXPECT_TRUE(helper_->ipc_sink()->GetUniqueMessageMatching( + EmbeddedWorkerMsg_StopWorker::ID)); + EXPECT_NE(kInvalidServiceWorkerRegistrationId, registration_id); + EXPECT_NE(kInvalidServiceWorkerVersionId, version_id); + + context()->storage()->FindRegistrationForId( + registration_id, + GURL("http://www.example.com"), + base::Bind(&ExpectRegisteredWorkers, + SERVICE_WORKER_OK, + version_id, + false /* expect_waiting */, + true /* expect_active */)); + base::RunLoop().RunUntilIdle(); +} + +// Test registration when the service worker rejects the install event. The +// registration callback should indicate success, but there should be no waiting +// or active worker in the registration. +TEST_F(ServiceWorkerContextTest, Register_RejectInstall) { + helper_.reset(); // Make sure the process lookups stay overridden. + helper_.reset(new RejectInstallTestHelper(render_process_id_)); + int64 registration_id = kInvalidServiceWorkerRegistrationId; + int64 version_id = kInvalidServiceWorkerVersionId; + bool called = false; + context()->RegisterServiceWorker( + GURL("http://www.example.com/*"), + GURL("http://www.example.com/service_worker.js"), + render_process_id_, + NULL, + MakeRegisteredCallback(&called, ®istration_id, &version_id)); + + ASSERT_FALSE(called); + base::RunLoop().RunUntilIdle(); + EXPECT_TRUE(called); + + EXPECT_EQ(3UL, helper_->ipc_sink()->message_count()); + EXPECT_TRUE(helper_->ipc_sink()->GetUniqueMessageMatching( + EmbeddedWorkerMsg_StartWorker::ID)); + EXPECT_TRUE(helper_->inner_ipc_sink()->GetUniqueMessageMatching( + ServiceWorkerMsg_InstallEvent::ID)); + EXPECT_FALSE(helper_->inner_ipc_sink()->GetUniqueMessageMatching( + ServiceWorkerMsg_ActivateEvent::ID)); + EXPECT_TRUE(helper_->ipc_sink()->GetUniqueMessageMatching( + EmbeddedWorkerMsg_StopWorker::ID)); + EXPECT_NE(kInvalidServiceWorkerRegistrationId, registration_id); + EXPECT_NE(kInvalidServiceWorkerVersionId, version_id); + + context()->storage()->FindRegistrationForId( + registration_id, + GURL("http://www.example.com"), + base::Bind(&ExpectRegisteredWorkers, + SERVICE_WORKER_ERROR_NOT_FOUND, + kInvalidServiceWorkerVersionId, + false /* expect_waiting */, + false /* expect_active */)); + base::RunLoop().RunUntilIdle(); +} - ASSERT_NE(-1L, registration_id); +// Test registration when the service worker rejects the activate event. The +// registration callback should indicate success, but there should be no waiting +// or active worker in the registration. +TEST_F(ServiceWorkerContextTest, Register_RejectActivate) { + helper_.reset(); // Make sure the process lookups stay overridden. + helper_.reset(new RejectActivateTestHelper(render_process_id_)); + int64 registration_id = kInvalidServiceWorkerRegistrationId; + int64 version_id = kInvalidServiceWorkerVersionId; + bool called = false; + context()->RegisterServiceWorker( + GURL("http://www.example.com/*"), + GURL("http://www.example.com/service_worker.js"), + render_process_id_, + NULL, + MakeRegisteredCallback(&called, ®istration_id, &version_id)); + + ASSERT_FALSE(called); + base::RunLoop().RunUntilIdle(); + EXPECT_TRUE(called); + + EXPECT_EQ(4UL, helper_->ipc_sink()->message_count()); + EXPECT_TRUE(helper_->ipc_sink()->GetUniqueMessageMatching( + EmbeddedWorkerMsg_StartWorker::ID)); + EXPECT_TRUE(helper_->inner_ipc_sink()->GetUniqueMessageMatching( + ServiceWorkerMsg_InstallEvent::ID)); + EXPECT_TRUE(helper_->inner_ipc_sink()->GetUniqueMessageMatching( + ServiceWorkerMsg_ActivateEvent::ID)); + EXPECT_TRUE(helper_->ipc_sink()->GetUniqueMessageMatching( + EmbeddedWorkerMsg_StopWorker::ID)); + EXPECT_NE(kInvalidServiceWorkerRegistrationId, registration_id); + EXPECT_NE(kInvalidServiceWorkerVersionId, version_id); + + context()->storage()->FindRegistrationForId( + registration_id, + GURL("http://www.example.com"), + base::Bind(&ExpectRegisteredWorkers, + SERVICE_WORKER_ERROR_NOT_FOUND, + kInvalidServiceWorkerVersionId, + false /* expect_waiting */, + false /* expect_active */)); + base::RunLoop().RunUntilIdle(); } // Make sure registrations are cleaned up when they are unregistered. @@ -86,22 +266,38 @@ TEST_F(ServiceWorkerContextTest, Unregister) { GURL pattern("http://www.example.com/*"); bool called = false; - int64 registration_id = -1L; - context_->RegisterServiceWorker( + int64 registration_id = kInvalidServiceWorkerRegistrationId; + int64 version_id = kInvalidServiceWorkerVersionId; + context()->RegisterServiceWorker( pattern, GURL("http://www.example.com/service_worker.js"), - MakeRegisteredCallback(&called, ®istration_id)); + render_process_id_, + NULL, + MakeRegisteredCallback(&called, ®istration_id, &version_id)); ASSERT_FALSE(called); base::RunLoop().RunUntilIdle(); ASSERT_TRUE(called); + EXPECT_NE(kInvalidServiceWorkerRegistrationId, registration_id); + EXPECT_NE(kInvalidServiceWorkerVersionId, version_id); called = false; - context_->UnregisterServiceWorker(pattern, MakeUnregisteredCallback(&called)); + context()->UnregisterServiceWorker(pattern, + MakeUnregisteredCallback(&called)); ASSERT_FALSE(called); base::RunLoop().RunUntilIdle(); ASSERT_TRUE(called); + + context()->storage()->FindRegistrationForId( + registration_id, + pattern.GetOrigin(), + base::Bind(&ExpectRegisteredWorkers, + SERVICE_WORKER_ERROR_NOT_FOUND, + kInvalidServiceWorkerVersionId, + false /* expect_waiting */, + false /* expect_active */)); + base::RunLoop().RunUntilIdle(); } // Make sure that when a new registration replaces an existing @@ -110,28 +306,41 @@ TEST_F(ServiceWorkerContextTest, RegisterNewScript) { GURL pattern("http://www.example.com/*"); bool called = false; - int64 old_registration_id = -1L; - context_->RegisterServiceWorker( + int64 old_registration_id = kInvalidServiceWorkerRegistrationId; + int64 old_version_id = kInvalidServiceWorkerVersionId; + context()->RegisterServiceWorker( pattern, GURL("http://www.example.com/service_worker.js"), - MakeRegisteredCallback(&called, &old_registration_id)); + render_process_id_, + NULL, + MakeRegisteredCallback(&called, &old_registration_id, &old_version_id)); ASSERT_FALSE(called); base::RunLoop().RunUntilIdle(); ASSERT_TRUE(called); + EXPECT_NE(kInvalidServiceWorkerRegistrationId, old_registration_id); + EXPECT_NE(kInvalidServiceWorkerVersionId, old_version_id); called = false; - int64 new_registration_id = -1L; - context_->RegisterServiceWorker( + int64 new_registration_id = kInvalidServiceWorkerRegistrationId; + int64 new_version_id = kInvalidServiceWorkerVersionId; + context()->RegisterServiceWorker( pattern, GURL("http://www.example.com/service_worker_new.js"), - MakeRegisteredCallback(&called, &new_registration_id)); + render_process_id_, + NULL, + MakeRegisteredCallback(&called, &new_registration_id, &new_version_id)); ASSERT_FALSE(called); base::RunLoop().RunUntilIdle(); ASSERT_TRUE(called); - ASSERT_NE(old_registration_id, new_registration_id); + // Returned IDs should be valid, and should differ from the values + // returned for the previous registration. + EXPECT_NE(kInvalidServiceWorkerRegistrationId, new_registration_id); + EXPECT_NE(kInvalidServiceWorkerVersionId, new_version_id); + EXPECT_NE(old_registration_id, new_registration_id); + EXPECT_NE(old_version_id, new_version_id); } // Make sure that when registering a duplicate pattern+script_url @@ -141,28 +350,36 @@ TEST_F(ServiceWorkerContextTest, RegisterDuplicateScript) { GURL script_url("http://www.example.com/service_worker.js"); bool called = false; - int64 old_registration_id = -1L; - context_->RegisterServiceWorker( + int64 old_registration_id = kInvalidServiceWorkerRegistrationId; + int64 old_version_id = kInvalidServiceWorkerVersionId; + context()->RegisterServiceWorker( pattern, script_url, - MakeRegisteredCallback(&called, &old_registration_id)); + render_process_id_, + NULL, + MakeRegisteredCallback(&called, &old_registration_id, &old_version_id)); ASSERT_FALSE(called); base::RunLoop().RunUntilIdle(); ASSERT_TRUE(called); + EXPECT_NE(kInvalidServiceWorkerRegistrationId, old_registration_id); + EXPECT_NE(kInvalidServiceWorkerVersionId, old_version_id); called = false; - int64 new_registration_id = -1L; - context_->RegisterServiceWorker( + int64 new_registration_id = kInvalidServiceWorkerRegistrationId; + int64 new_version_id = kInvalidServiceWorkerVersionId; + context()->RegisterServiceWorker( pattern, script_url, - MakeRegisteredCallback(&called, &new_registration_id)); + render_process_id_, + NULL, + MakeRegisteredCallback(&called, &new_registration_id, &new_version_id)); ASSERT_FALSE(called); base::RunLoop().RunUntilIdle(); ASSERT_TRUE(called); - - ASSERT_EQ(old_registration_id, new_registration_id); + EXPECT_EQ(old_registration_id, new_registration_id); + EXPECT_EQ(old_version_id, new_version_id); } } // namespace content diff --git a/chromium/content/browser/service_worker/service_worker_context_wrapper.cc b/chromium/content/browser/service_worker/service_worker_context_wrapper.cc index 49a6842968b..460f6df36e4 100644 --- a/chromium/content/browser/service_worker/service_worker_context_wrapper.cc +++ b/chromium/content/browser/service_worker/service_worker_context_wrapper.cc @@ -5,13 +5,20 @@ #include "content/browser/service_worker/service_worker_context_wrapper.h" #include "base/files/file_path.h" +#include "base/threading/sequenced_worker_pool.h" #include "content/browser/service_worker/service_worker_context_core.h" +#include "content/browser/service_worker/service_worker_context_observer.h" +#include "content/browser/service_worker/service_worker_process_manager.h" #include "content/public/browser/browser_thread.h" -#include "webkit/browser/quota/quota_manager.h" +#include "webkit/browser/quota/quota_manager_proxy.h" namespace content { -ServiceWorkerContextWrapper::ServiceWorkerContextWrapper() { +ServiceWorkerContextWrapper::ServiceWorkerContextWrapper( + BrowserContext* browser_context) + : observer_list_( + new ObserverListThreadSafe<ServiceWorkerContextObserver>()), + process_manager_(new ServiceWorkerProcessManager(browser_context)) { } ServiceWorkerContextWrapper::~ServiceWorkerContextWrapper() { @@ -20,33 +27,140 @@ ServiceWorkerContextWrapper::~ServiceWorkerContextWrapper() { void ServiceWorkerContextWrapper::Init( const base::FilePath& user_data_directory, quota::QuotaManagerProxy* quota_manager_proxy) { + scoped_refptr<base::SequencedTaskRunner> database_task_runner = + BrowserThread::GetBlockingPool()-> + GetSequencedTaskRunnerWithShutdownBehavior( + BrowserThread::GetBlockingPool()->GetSequenceToken(), + base::SequencedWorkerPool::SKIP_ON_SHUTDOWN); + scoped_refptr<base::MessageLoopProxy> disk_cache_thread = + BrowserThread::GetMessageLoopProxyForThread(BrowserThread::CACHE); + InitInternal(user_data_directory, database_task_runner, + disk_cache_thread, quota_manager_proxy); +} + +void ServiceWorkerContextWrapper::Shutdown() { + DCHECK_CURRENTLY_ON(BrowserThread::UI); + process_manager_->Shutdown(); + BrowserThread::PostTask( + BrowserThread::IO, + FROM_HERE, + base::Bind(&ServiceWorkerContextWrapper::ShutdownOnIO, this)); +} + +ServiceWorkerContextCore* ServiceWorkerContextWrapper::context() { + DCHECK_CURRENTLY_ON(BrowserThread::IO); + return context_core_.get(); +} + +static void FinishRegistrationOnIO( + const ServiceWorkerContext::ResultCallback& continuation, + ServiceWorkerStatusCode status, + int64 registration_id, + int64 version_id) { + DCHECK_CURRENTLY_ON(BrowserThread::IO); + BrowserThread::PostTask( + BrowserThread::UI, + FROM_HERE, + base::Bind(continuation, status == SERVICE_WORKER_OK)); +} + +void ServiceWorkerContextWrapper::RegisterServiceWorker( + const GURL& pattern, + const GURL& script_url, + const ResultCallback& continuation) { if (!BrowserThread::CurrentlyOn(BrowserThread::IO)) { BrowserThread::PostTask( - BrowserThread::IO, FROM_HERE, - base::Bind(&ServiceWorkerContextWrapper::Init, this, - user_data_directory, - make_scoped_refptr(quota_manager_proxy))); + BrowserThread::IO, + FROM_HERE, + base::Bind(&ServiceWorkerContextWrapper::RegisterServiceWorker, + this, + pattern, + script_url, + continuation)); return; } - DCHECK(!context_core_); - context_core_.reset( - new ServiceWorkerContextCore( - user_data_directory, quota_manager_proxy)); + + context()->RegisterServiceWorker( + pattern, + script_url, + -1, + NULL /* provider_host */, + base::Bind(&FinishRegistrationOnIO, continuation)); } -void ServiceWorkerContextWrapper::Shutdown() { +static void FinishUnregistrationOnIO( + const ServiceWorkerContext::ResultCallback& continuation, + ServiceWorkerStatusCode status) { + DCHECK_CURRENTLY_ON(BrowserThread::IO); + BrowserThread::PostTask( + BrowserThread::UI, + FROM_HERE, + base::Bind(continuation, status == SERVICE_WORKER_OK)); +} + +void ServiceWorkerContextWrapper::UnregisterServiceWorker( + const GURL& pattern, + const ResultCallback& continuation) { if (!BrowserThread::CurrentlyOn(BrowserThread::IO)) { BrowserThread::PostTask( - BrowserThread::IO, FROM_HERE, - base::Bind(&ServiceWorkerContextWrapper::Shutdown, this)); + BrowserThread::IO, + FROM_HERE, + base::Bind(&ServiceWorkerContextWrapper::UnregisterServiceWorker, + this, + pattern, + continuation)); return; } - context_core_.reset(); + + context()->UnregisterServiceWorker( + pattern, + base::Bind(&FinishUnregistrationOnIO, continuation)); } -ServiceWorkerContextCore* ServiceWorkerContextWrapper::context() { - DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); - return context_core_.get(); +void ServiceWorkerContextWrapper::Terminate() { + DCHECK_CURRENTLY_ON(BrowserThread::UI); + process_manager_->Shutdown(); +} + +void ServiceWorkerContextWrapper::AddObserver( + ServiceWorkerContextObserver* observer) { + observer_list_->AddObserver(observer); +} + +void ServiceWorkerContextWrapper::RemoveObserver( + ServiceWorkerContextObserver* observer) { + observer_list_->RemoveObserver(observer); +} + +void ServiceWorkerContextWrapper::InitInternal( + const base::FilePath& user_data_directory, + base::SequencedTaskRunner* database_task_runner, + base::MessageLoopProxy* disk_cache_thread, + quota::QuotaManagerProxy* quota_manager_proxy) { + if (!BrowserThread::CurrentlyOn(BrowserThread::IO)) { + BrowserThread::PostTask( + BrowserThread::IO, + FROM_HERE, + base::Bind(&ServiceWorkerContextWrapper::InitInternal, + this, + user_data_directory, + make_scoped_refptr(database_task_runner), + make_scoped_refptr(disk_cache_thread), + make_scoped_refptr(quota_manager_proxy))); + return; + } + DCHECK(!context_core_); + context_core_.reset(new ServiceWorkerContextCore(user_data_directory, + database_task_runner, + disk_cache_thread, + quota_manager_proxy, + observer_list_, + this)); +} + +void ServiceWorkerContextWrapper::ShutdownOnIO() { + DCHECK_CURRENTLY_ON(BrowserThread::IO); + context_core_.reset(); } } // namespace content diff --git a/chromium/content/browser/service_worker/service_worker_context_wrapper.h b/chromium/content/browser/service_worker/service_worker_context_wrapper.h index 1b9b6523907..029af6f54ca 100644 --- a/chromium/content/browser/service_worker/service_worker_context_wrapper.h +++ b/chromium/content/browser/service_worker/service_worker_context_wrapper.h @@ -5,14 +5,19 @@ #ifndef CONTENT_BROWSER_SERVICE_WORKER_SERVICE_WORKER_CONTEXT_WRAPPER_H_ #define CONTENT_BROWSER_SERVICE_WORKER_SERVICE_WORKER_CONTEXT_WRAPPER_H_ +#include <vector> + #include "base/files/file_path.h" #include "base/memory/ref_counted.h" #include "base/memory/scoped_ptr.h" -#include "content/browser/service_worker/service_worker_context.h" +#include "content/browser/service_worker/service_worker_context_core.h" #include "content/common/content_export.h" +#include "content/public/browser/service_worker_context.h" namespace base { class FilePath; +class MessageLoopProxy; +class SequencedTaskRunner; } namespace quota { @@ -21,7 +26,9 @@ class QuotaManagerProxy; namespace content { +class BrowserContext; class ServiceWorkerContextCore; +class ServiceWorkerContextObserver; // A refcounted wrapper class for our core object. Higher level content lib // classes keep references to this class on mutliple threads. The inner core @@ -31,7 +38,7 @@ class CONTENT_EXPORT ServiceWorkerContextWrapper : NON_EXPORTED_BASE(public ServiceWorkerContext), public base::RefCountedThreadSafe<ServiceWorkerContextWrapper> { public: - ServiceWorkerContextWrapper(); + ServiceWorkerContextWrapper(BrowserContext* browser_context); // Init and Shutdown are for use on the UI thread when the profile, // storagepartition is being setup and torn down. @@ -42,10 +49,40 @@ class CONTENT_EXPORT ServiceWorkerContextWrapper // The core context is only for use on the IO thread. ServiceWorkerContextCore* context(); + // The process manager can be used on either UI or IO. + ServiceWorkerProcessManager* process_manager() { + return process_manager_.get(); + } + + // ServiceWorkerContext implementation: + virtual void RegisterServiceWorker( + const GURL& pattern, + const GURL& script_url, + const ResultCallback& continuation) OVERRIDE; + virtual void UnregisterServiceWorker(const GURL& pattern, + const ResultCallback& continuation) + OVERRIDE; + virtual void Terminate() OVERRIDE; + + void AddObserver(ServiceWorkerContextObserver* observer); + void RemoveObserver(ServiceWorkerContextObserver* observer); + private: friend class base::RefCountedThreadSafe<ServiceWorkerContextWrapper>; + friend class EmbeddedWorkerTestHelper; + friend class ServiceWorkerProcessManager; virtual ~ServiceWorkerContextWrapper(); + void InitInternal(const base::FilePath& user_data_directory, + base::SequencedTaskRunner* database_task_runner, + base::MessageLoopProxy* disk_cache_thread, + quota::QuotaManagerProxy* quota_manager_proxy); + void ShutdownOnIO(); + + const scoped_refptr<ObserverListThreadSafe<ServiceWorkerContextObserver> > + observer_list_; + const scoped_ptr<ServiceWorkerProcessManager> process_manager_; + // Cleared in Shutdown(): scoped_ptr<ServiceWorkerContextCore> context_core_; }; diff --git a/chromium/content/browser/service_worker/service_worker_controllee_request_handler.cc b/chromium/content/browser/service_worker/service_worker_controllee_request_handler.cc new file mode 100644 index 00000000000..14a707b62e8 --- /dev/null +++ b/chromium/content/browser/service_worker/service_worker_controllee_request_handler.cc @@ -0,0 +1,121 @@ +// Copyright 2014 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 "content/browser/service_worker/service_worker_controllee_request_handler.h" + +#include "content/browser/service_worker/service_worker_context_core.h" +#include "content/browser/service_worker/service_worker_provider_host.h" +#include "content/browser/service_worker/service_worker_registration.h" +#include "content/browser/service_worker/service_worker_url_request_job.h" +#include "content/browser/service_worker/service_worker_utils.h" +#include "content/common/service_worker/service_worker_types.h" +#include "net/base/net_util.h" +#include "net/url_request/url_request.h" + +namespace content { + +ServiceWorkerControlleeRequestHandler::ServiceWorkerControlleeRequestHandler( + base::WeakPtr<ServiceWorkerContextCore> context, + base::WeakPtr<ServiceWorkerProviderHost> provider_host, + base::WeakPtr<webkit_blob::BlobStorageContext> blob_storage_context, + ResourceType::Type resource_type) + : ServiceWorkerRequestHandler(context, + provider_host, + blob_storage_context, + resource_type), + weak_factory_(this) { +} + +ServiceWorkerControlleeRequestHandler:: + ~ServiceWorkerControlleeRequestHandler() { +} + +net::URLRequestJob* ServiceWorkerControlleeRequestHandler::MaybeCreateJob( + net::URLRequest* request, + net::NetworkDelegate* network_delegate) { + if (!context_ || !provider_host_) { + // We can't do anything other than to fall back to network. + job_ = NULL; + return NULL; + } + + // This may get called multiple times for original and redirect requests: + // A. original request case: job_ is null, no previous location info. + // B. redirect or restarted request case: + // a) job_ is non-null if the previous location was forwarded to SW. + // b) job_ is null if the previous location was fallback. + // c) job_ is non-null if additional restart was required to fall back. + + // We've come here by restart, we already have original request and it + // tells we should fallback to network. (Case B-c) + if (job_.get() && job_->ShouldFallbackToNetwork()) { + job_ = NULL; + return NULL; + } + + // It's for original request (A) or redirect case (B-a or B-b). + DCHECK(!job_.get() || job_->ShouldForwardToServiceWorker()); + + job_ = new ServiceWorkerURLRequestJob( + request, network_delegate, provider_host_, blob_storage_context_); + if (ServiceWorkerUtils::IsMainResourceType(resource_type_)) + PrepareForMainResource(request->url()); + else + PrepareForSubResource(); + + if (job_->ShouldFallbackToNetwork()) { + // If we know we can fallback to network at this point (in case + // the storage lookup returned immediately), just return NULL here to + // fallback to network. + job_ = NULL; + return NULL; + } + + return job_.get(); +} + +void ServiceWorkerControlleeRequestHandler::PrepareForMainResource( + const GURL& url) { + DCHECK(job_.get()); + DCHECK(context_); + // The corresponding provider_host may already have associate version in + // redirect case, unassociate it now. + provider_host_->SetActiveVersion(NULL); + provider_host_->SetWaitingVersion(NULL); + + GURL stripped_url = net::SimplifyUrlForRequest(url); + provider_host_->SetDocumentUrl(stripped_url); + context_->storage()->FindRegistrationForDocument( + stripped_url, + base::Bind(&self::DidLookupRegistrationForMainResource, + weak_factory_.GetWeakPtr())); +} + +void +ServiceWorkerControlleeRequestHandler::DidLookupRegistrationForMainResource( + ServiceWorkerStatusCode status, + const scoped_refptr<ServiceWorkerRegistration>& registration) { + DCHECK(job_.get()); + if (status != SERVICE_WORKER_OK || !registration->active_version()) { + // No registration, or no active version for the registration is available. + job_->FallbackToNetwork(); + return; + } + // TODO(michaeln): should SetWaitingVersion() even if no active version so + // so the versions in the pipeline (.installing, .waiting) show up in the + // attribute values. + DCHECK(registration); + provider_host_->SetActiveVersion(registration->active_version()); + provider_host_->SetWaitingVersion(registration->waiting_version()); + job_->ForwardToServiceWorker(); +} + +void ServiceWorkerControlleeRequestHandler::PrepareForSubResource() { + DCHECK(job_.get()); + DCHECK(context_); + DCHECK(provider_host_->active_version()); + job_->ForwardToServiceWorker(); +} + +} // namespace content diff --git a/chromium/content/browser/service_worker/service_worker_controllee_request_handler.h b/chromium/content/browser/service_worker/service_worker_controllee_request_handler.h new file mode 100644 index 00000000000..87227cb32dd --- /dev/null +++ b/chromium/content/browser/service_worker/service_worker_controllee_request_handler.h @@ -0,0 +1,57 @@ +// Copyright 2014 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 CONTENT_BROWSER_SERVICE_WORKER_SERVICE_WORKER_CONTROLLEE_REQUEST_HANDLER_H_ +#define CONTENT_BROWSER_SERVICE_WORKER_SERVICE_WORKER_CONTROLLEE_REQUEST_HANDLER_H_ + +#include "content/browser/service_worker/service_worker_request_handler.h" + +namespace net { +class NetworkDelegate; +class URLRequest; +} + +namespace content { + +class ServiceWorkerRegistration; +class ServiceWorkerURLRequestJob; + +// A request handler derivative used to handle requests from +// controlled documents. +class CONTENT_EXPORT ServiceWorkerControlleeRequestHandler + : public ServiceWorkerRequestHandler { + public: + ServiceWorkerControlleeRequestHandler( + base::WeakPtr<ServiceWorkerContextCore> context, + base::WeakPtr<ServiceWorkerProviderHost> provider_host, + base::WeakPtr<webkit_blob::BlobStorageContext> blob_storage_context, + ResourceType::Type resource_type); + virtual ~ServiceWorkerControlleeRequestHandler(); + + // Called via custom URLRequestJobFactory. + virtual net::URLRequestJob* MaybeCreateJob( + net::URLRequest* request, + net::NetworkDelegate* network_delegate) OVERRIDE; + + private: + typedef ServiceWorkerControlleeRequestHandler self; + + // For main resource case. + void PrepareForMainResource(const GURL& url); + void DidLookupRegistrationForMainResource( + ServiceWorkerStatusCode status, + const scoped_refptr<ServiceWorkerRegistration>& registration); + + // For sub resource case. + void PrepareForSubResource(); + + scoped_refptr<ServiceWorkerURLRequestJob> job_; + base::WeakPtrFactory<ServiceWorkerControlleeRequestHandler> weak_factory_; + + DISALLOW_COPY_AND_ASSIGN(ServiceWorkerControlleeRequestHandler); +}; + +} // namespace content + +#endif // CONTENT_BROWSER_SERVICE_WORKER_SERVICE_WORKER_CONTROLLEE_REQUEST_HANDLER_H_ diff --git a/chromium/content/browser/service_worker/service_worker_database.cc b/chromium/content/browser/service_worker/service_worker_database.cc new file mode 100644 index 00000000000..1058acc6165 --- /dev/null +++ b/chromium/content/browser/service_worker/service_worker_database.cc @@ -0,0 +1,1113 @@ +// Copyright 2014 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 "content/browser/service_worker/service_worker_database.h" + +#include <string> + +#include "base/file_util.h" +#include "base/location.h" +#include "base/logging.h" +#include "base/metrics/histogram.h" +#include "base/stl_util.h" +#include "base/strings/string_number_conversions.h" +#include "base/strings/string_split.h" +#include "base/strings/string_util.h" +#include "base/strings/stringprintf.h" +#include "content/browser/service_worker/service_worker_database.pb.h" +#include "content/common/service_worker/service_worker_types.h" +#include "third_party/leveldatabase/src/helpers/memenv/memenv.h" +#include "third_party/leveldatabase/src/include/leveldb/db.h" +#include "third_party/leveldatabase/src/include/leveldb/env.h" +#include "third_party/leveldatabase/src/include/leveldb/write_batch.h" + +// LevelDB database schema +// ======================= +// +// NOTE +// - int64 value is serialized as a string by base::Int64ToString(). +// - GURL value is serialized as a string by GURL::spec(). +// +// Version 1 (in sorted order) +// key: "INITDATA_DB_VERSION" +// value: "1" +// +// key: "INITDATA_NEXT_REGISTRATION_ID" +// value: <int64 'next_available_registration_id'> +// +// key: "INITDATA_NEXT_RESOURCE_ID" +// value: <int64 'next_available_resource_id'> +// +// key: "INITDATA_NEXT_VERSION_ID" +// value: <int64 'next_available_version_id'> +// +// key: "INITDATA_UNIQUE_ORIGIN:" + <GURL 'origin'> +// value: <empty> +// +// key: "PRES:" + <int64 'purgeable_resource_id'> +// value: <empty> +// +// key: "REG:" + <GURL 'origin'> + '\x00' + <int64 'registration_id'> +// (ex. "REG:http://example.com\x00123456") +// value: <ServiceWorkerRegistrationData serialized as a string> +// +// key: "RES:" + <int64 'version_id'> + '\x00' + <int64 'resource_id'> +// (ex. "RES:123456\x00654321") +// value: <ServiceWorkerResourceRecord serialized as a string> +// +// key: "URES:" + <int64 'uncommitted_resource_id'> +// value: <empty> + +namespace content { + +namespace { + +const char kDatabaseVersionKey[] = "INITDATA_DB_VERSION"; +const char kNextRegIdKey[] = "INITDATA_NEXT_REGISTRATION_ID"; +const char kNextResIdKey[] = "INITDATA_NEXT_RESOURCE_ID"; +const char kNextVerIdKey[] = "INITDATA_NEXT_VERSION_ID"; +const char kUniqueOriginKey[] = "INITDATA_UNIQUE_ORIGIN:"; + +const char kRegKeyPrefix[] = "REG:"; +const char kResKeyPrefix[] = "RES:"; +const char kKeySeparator = '\x00'; + +const char kUncommittedResIdKeyPrefix[] = "URES:"; +const char kPurgeableResIdKeyPrefix[] = "PRES:"; + +const int64 kCurrentSchemaVersion = 1; + +// For histogram. +const char kOpenResultHistogramLabel[] = + "ServiceWorker.Database.OpenResult"; +const char kReadResultHistogramLabel[] = + "ServiceWorker.Database.ReadResult"; +const char kWriteResultHistogramLabel[] = + "ServiceWorker.Database.WriteResult"; + +bool RemovePrefix(const std::string& str, + const std::string& prefix, + std::string* out) { + if (!StartsWithASCII(str, prefix, true)) + return false; + if (out) + *out = str.substr(prefix.size()); + return true; +} + +std::string CreateRegistrationKey(int64 registration_id, + const GURL& origin) { + return base::StringPrintf("%s%s%c%s", + kRegKeyPrefix, + origin.spec().c_str(), + kKeySeparator, + base::Int64ToString(registration_id).c_str()); +} + +std::string CreateResourceRecordKeyPrefix(int64 version_id) { + return base::StringPrintf("%s%s%c", + kResKeyPrefix, + base::Int64ToString(version_id).c_str(), + kKeySeparator); +} + +std::string CreateResourceRecordKey(int64 version_id, + int64 resource_id) { + return CreateResourceRecordKeyPrefix(version_id).append( + base::Int64ToString(resource_id)); +} + +std::string CreateUniqueOriginKey(const GURL& origin) { + return base::StringPrintf("%s%s", kUniqueOriginKey, origin.spec().c_str()); +} + +std::string CreateResourceIdKey(const char* key_prefix, int64 resource_id) { + return base::StringPrintf( + "%s%s", key_prefix, base::Int64ToString(resource_id).c_str()); +} + +void PutRegistrationDataToBatch( + const ServiceWorkerDatabase::RegistrationData& input, + leveldb::WriteBatch* batch) { + DCHECK(batch); + + // Convert RegistrationData to ServiceWorkerRegistrationData. + ServiceWorkerRegistrationData data; + data.set_registration_id(input.registration_id); + data.set_scope_url(input.scope.spec()); + data.set_script_url(input.script.spec()); + data.set_version_id(input.version_id); + data.set_is_active(input.is_active); + data.set_has_fetch_handler(input.has_fetch_handler); + data.set_last_update_check_time(input.last_update_check.ToInternalValue()); + + std::string value; + bool success = data.SerializeToString(&value); + DCHECK(success); + GURL origin = input.scope.GetOrigin(); + batch->Put(CreateRegistrationKey(data.registration_id(), origin), value); +} + +void PutResourceRecordToBatch( + const ServiceWorkerDatabase::ResourceRecord& input, + int64 version_id, + leveldb::WriteBatch* batch) { + DCHECK(batch); + + // Convert ResourceRecord to ServiceWorkerResourceRecord. + ServiceWorkerResourceRecord record; + record.set_resource_id(input.resource_id); + record.set_url(input.url.spec()); + + std::string value; + bool success = record.SerializeToString(&value); + DCHECK(success); + batch->Put(CreateResourceRecordKey(version_id, input.resource_id), value); +} + +void PutUniqueOriginToBatch(const GURL& origin, + leveldb::WriteBatch* batch) { + // Value should be empty. + batch->Put(CreateUniqueOriginKey(origin), ""); +} + +void PutPurgeableResourceIdToBatch(int64 resource_id, + leveldb::WriteBatch* batch) { + // Value should be empty. + batch->Put(CreateResourceIdKey(kPurgeableResIdKeyPrefix, resource_id), ""); +} + +ServiceWorkerDatabase::Status ParseId( + const std::string& serialized, + int64* out) { + DCHECK(out); + int64 id; + if (!base::StringToInt64(serialized, &id) || id < 0) + return ServiceWorkerDatabase::STATUS_ERROR_CORRUPTED; + *out = id; + return ServiceWorkerDatabase::STATUS_OK; +} + +ServiceWorkerDatabase::Status ParseDatabaseVersion( + const std::string& serialized, + int64* out) { + DCHECK(out); + const int kFirstValidVersion = 1; + int64 version; + if (!base::StringToInt64(serialized, &version) || + version < kFirstValidVersion) { + return ServiceWorkerDatabase::STATUS_ERROR_CORRUPTED; + } + if (kCurrentSchemaVersion < version) { + DLOG(ERROR) << "ServiceWorkerDatabase has newer schema version" + << " than the current latest version: " + << version << " vs " << kCurrentSchemaVersion; + return ServiceWorkerDatabase::STATUS_ERROR_CORRUPTED; + } + *out = version; + return ServiceWorkerDatabase::STATUS_OK; +} + +ServiceWorkerDatabase::Status ParseRegistrationData( + const std::string& serialized, + ServiceWorkerDatabase::RegistrationData* out) { + DCHECK(out); + ServiceWorkerRegistrationData data; + if (!data.ParseFromString(serialized)) + return ServiceWorkerDatabase::STATUS_ERROR_CORRUPTED; + + GURL scope_url(data.scope_url()); + GURL script_url(data.script_url()); + if (!scope_url.is_valid() || + !script_url.is_valid() || + scope_url.GetOrigin() != script_url.GetOrigin()) { + return ServiceWorkerDatabase::STATUS_ERROR_CORRUPTED; + } + + // Convert ServiceWorkerRegistrationData to RegistrationData. + out->registration_id = data.registration_id(); + out->scope = scope_url; + out->script = script_url; + out->version_id = data.version_id(); + out->is_active = data.is_active(); + out->has_fetch_handler = data.has_fetch_handler(); + out->last_update_check = + base::Time::FromInternalValue(data.last_update_check_time()); + return ServiceWorkerDatabase::STATUS_OK; +} + +ServiceWorkerDatabase::Status ParseResourceRecord( + const std::string& serialized, + ServiceWorkerDatabase::ResourceRecord* out) { + DCHECK(out); + ServiceWorkerResourceRecord record; + if (!record.ParseFromString(serialized)) + return ServiceWorkerDatabase::STATUS_ERROR_CORRUPTED; + + GURL url(record.url()); + if (!url.is_valid()) + return ServiceWorkerDatabase::STATUS_ERROR_CORRUPTED; + + // Convert ServiceWorkerResourceRecord to ResourceRecord. + out->resource_id = record.resource_id(); + out->url = url; + return ServiceWorkerDatabase::STATUS_OK; +} + +ServiceWorkerDatabase::Status LevelDBStatusToStatus( + const leveldb::Status& status) { + if (status.ok()) + return ServiceWorkerDatabase::STATUS_OK; + else if (status.IsNotFound()) + return ServiceWorkerDatabase::STATUS_ERROR_NOT_FOUND; + else if (status.IsIOError()) + return ServiceWorkerDatabase::STATUS_ERROR_IO_ERROR; + else if (status.IsCorruption()) + return ServiceWorkerDatabase::STATUS_ERROR_CORRUPTED; + else + return ServiceWorkerDatabase::STATUS_ERROR_FAILED; +} + +const char* StatusToString(ServiceWorkerDatabase::Status status) { + switch (status) { + case ServiceWorkerDatabase::STATUS_OK: + return "Database OK"; + case ServiceWorkerDatabase::STATUS_ERROR_NOT_FOUND: + return "Database not found"; + case ServiceWorkerDatabase::STATUS_ERROR_IO_ERROR: + return "Database IO error"; + case ServiceWorkerDatabase::STATUS_ERROR_CORRUPTED: + return "Database corrupted"; + case ServiceWorkerDatabase::STATUS_ERROR_FAILED: + return "Database operation failed"; + case ServiceWorkerDatabase::STATUS_ERROR_MAX: + NOTREACHED(); + return "Database unknown error"; + } + NOTREACHED(); + return "Database unknown error"; +} + +} // namespace + +ServiceWorkerDatabase::RegistrationData::RegistrationData() + : registration_id(kInvalidServiceWorkerRegistrationId), + version_id(kInvalidServiceWorkerVersionId), + is_active(false), + has_fetch_handler(false) { +} + +ServiceWorkerDatabase::RegistrationData::~RegistrationData() { +} + +ServiceWorkerDatabase::ServiceWorkerDatabase(const base::FilePath& path) + : path_(path), + next_avail_registration_id_(0), + next_avail_resource_id_(0), + next_avail_version_id_(0), + state_(UNINITIALIZED) { + sequence_checker_.DetachFromSequence(); +} + +ServiceWorkerDatabase::~ServiceWorkerDatabase() { + DCHECK(sequence_checker_.CalledOnValidSequencedThread()); + db_.reset(); +} + +ServiceWorkerDatabase::Status ServiceWorkerDatabase::GetNextAvailableIds( + int64* next_avail_registration_id, + int64* next_avail_version_id, + int64* next_avail_resource_id) { + DCHECK(sequence_checker_.CalledOnValidSequencedThread()); + DCHECK(next_avail_registration_id); + DCHECK(next_avail_version_id); + DCHECK(next_avail_resource_id); + + Status status = LazyOpen(false); + if (IsNewOrNonexistentDatabase(status)) { + *next_avail_registration_id = 0; + *next_avail_version_id = 0; + *next_avail_resource_id = 0; + return STATUS_OK; + } + if (status != STATUS_OK) + return status; + + status = ReadNextAvailableId(kNextRegIdKey, &next_avail_registration_id_); + if (status != STATUS_OK) + return status; + status = ReadNextAvailableId(kNextVerIdKey, &next_avail_version_id_); + if (status != STATUS_OK) + return status; + status = ReadNextAvailableId(kNextResIdKey, &next_avail_resource_id_); + if (status != STATUS_OK) + return status; + + *next_avail_registration_id = next_avail_registration_id_; + *next_avail_version_id = next_avail_version_id_; + *next_avail_resource_id = next_avail_resource_id_; + return STATUS_OK; +} + +ServiceWorkerDatabase::Status +ServiceWorkerDatabase::GetOriginsWithRegistrations(std::set<GURL>* origins) { + DCHECK(sequence_checker_.CalledOnValidSequencedThread()); + DCHECK(origins->empty()); + + Status status = LazyOpen(false); + if (IsNewOrNonexistentDatabase(status)) + return STATUS_OK; + if (status != STATUS_OK) + return status; + + scoped_ptr<leveldb::Iterator> itr(db_->NewIterator(leveldb::ReadOptions())); + for (itr->Seek(kUniqueOriginKey); itr->Valid(); itr->Next()) { + status = LevelDBStatusToStatus(itr->status()); + if (status != STATUS_OK) { + HandleReadResult(FROM_HERE, status); + origins->clear(); + return status; + } + + std::string origin; + if (!RemovePrefix(itr->key().ToString(), kUniqueOriginKey, &origin)) + break; + origins->insert(GURL(origin)); + } + + HandleReadResult(FROM_HERE, status); + return status; +} + +ServiceWorkerDatabase::Status ServiceWorkerDatabase::GetRegistrationsForOrigin( + const GURL& origin, + std::vector<RegistrationData>* registrations) { + DCHECK(sequence_checker_.CalledOnValidSequencedThread()); + DCHECK(registrations->empty()); + + Status status = LazyOpen(false); + if (IsNewOrNonexistentDatabase(status)) + return STATUS_OK; + if (status != STATUS_OK) + return status; + + // Create a key prefix for registrations. + std::string prefix = base::StringPrintf( + "%s%s%c", kRegKeyPrefix, origin.spec().c_str(), kKeySeparator); + + scoped_ptr<leveldb::Iterator> itr(db_->NewIterator(leveldb::ReadOptions())); + for (itr->Seek(prefix); itr->Valid(); itr->Next()) { + status = LevelDBStatusToStatus(itr->status()); + if (status != STATUS_OK) { + HandleReadResult(FROM_HERE, status); + registrations->clear(); + return status; + } + + if (!RemovePrefix(itr->key().ToString(), prefix, NULL)) + break; + + RegistrationData registration; + status = ParseRegistrationData(itr->value().ToString(), ®istration); + if (status != STATUS_OK) { + HandleReadResult(FROM_HERE, status); + registrations->clear(); + return status; + } + registrations->push_back(registration); + } + + HandleReadResult(FROM_HERE, status); + return status; +} + +ServiceWorkerDatabase::Status ServiceWorkerDatabase::GetAllRegistrations( + std::vector<RegistrationData>* registrations) { + DCHECK(sequence_checker_.CalledOnValidSequencedThread()); + DCHECK(registrations->empty()); + + Status status = LazyOpen(false); + if (IsNewOrNonexistentDatabase(status)) + return STATUS_OK; + if (status != STATUS_OK) + return status; + + scoped_ptr<leveldb::Iterator> itr(db_->NewIterator(leveldb::ReadOptions())); + for (itr->Seek(kRegKeyPrefix); itr->Valid(); itr->Next()) { + status = LevelDBStatusToStatus(itr->status()); + if (status != STATUS_OK) { + HandleReadResult(FROM_HERE, status); + registrations->clear(); + return status; + } + + if (!RemovePrefix(itr->key().ToString(), kRegKeyPrefix, NULL)) + break; + + RegistrationData registration; + status = ParseRegistrationData(itr->value().ToString(), ®istration); + if (status != STATUS_OK) { + HandleReadResult(FROM_HERE, status); + registrations->clear(); + return status; + } + registrations->push_back(registration); + } + + HandleReadResult(FROM_HERE, status); + return status; +} + +ServiceWorkerDatabase::Status ServiceWorkerDatabase::ReadRegistration( + int64 registration_id, + const GURL& origin, + RegistrationData* registration, + std::vector<ResourceRecord>* resources) { + DCHECK(sequence_checker_.CalledOnValidSequencedThread()); + DCHECK(registration); + DCHECK(resources); + + Status status = LazyOpen(false); + if (IsNewOrNonexistentDatabase(status) || status != STATUS_OK) + return status; + + RegistrationData value; + status = ReadRegistrationData(registration_id, origin, &value); + if (status != STATUS_OK) + return status; + + status = ReadResourceRecords(value.version_id, resources); + if (status != STATUS_OK) + return status; + + *registration = value; + return STATUS_OK; +} + +ServiceWorkerDatabase::Status ServiceWorkerDatabase::WriteRegistration( + const RegistrationData& registration, + const std::vector<ResourceRecord>& resources, + std::vector<int64>* newly_purgeable_resources) { + DCHECK(sequence_checker_.CalledOnValidSequencedThread()); + Status status = LazyOpen(true); + if (status != STATUS_OK) + return status; + + leveldb::WriteBatch batch; + BumpNextRegistrationIdIfNeeded(registration.registration_id, &batch); + BumpNextVersionIdIfNeeded(registration.version_id, &batch); + + PutUniqueOriginToBatch(registration.scope.GetOrigin(), &batch); + PutRegistrationDataToBatch(registration, &batch); + + // Used for avoiding multiple writes for the same resource id or url. + std::set<int64> pushed_resources; + std::set<GURL> pushed_urls; + for (std::vector<ResourceRecord>::const_iterator itr = resources.begin(); + itr != resources.end(); ++itr) { + if (!itr->url.is_valid()) + return STATUS_ERROR_FAILED; + + // Duplicated resource id or url should not exist. + DCHECK(pushed_resources.insert(itr->resource_id).second); + DCHECK(pushed_urls.insert(itr->url).second); + + PutResourceRecordToBatch(*itr, registration.version_id, &batch); + + // Delete a resource from the uncommitted list. + batch.Delete(CreateResourceIdKey( + kUncommittedResIdKeyPrefix, itr->resource_id)); + } + + // Retrieve a previous version to sweep purgeable resources. + RegistrationData old_registration; + status = ReadRegistrationData(registration.registration_id, + registration.scope.GetOrigin(), + &old_registration); + if (status != STATUS_OK && status != STATUS_ERROR_NOT_FOUND) + return status; + if (status == STATUS_OK) { + DCHECK_LT(old_registration.version_id, registration.version_id); + status = DeleteResourceRecords( + old_registration.version_id, newly_purgeable_resources, &batch); + if (status != STATUS_OK) + return status; + + // Currently resource sharing across versions and registrations is not + // supported, so resource ids should not be overlapped between + // |registration| and |old_registration|. + std::set<int64> deleted_resources(newly_purgeable_resources->begin(), + newly_purgeable_resources->end()); + DCHECK(base::STLSetIntersection<std::set<int64> >( + pushed_resources, deleted_resources).empty()); + } + + return WriteBatch(&batch); +} + +ServiceWorkerDatabase::Status ServiceWorkerDatabase::UpdateVersionToActive( + int64 registration_id, + const GURL& origin) { + DCHECK(sequence_checker_.CalledOnValidSequencedThread()); + ServiceWorkerDatabase::Status status = LazyOpen(false); + if (IsNewOrNonexistentDatabase(status)) + return STATUS_ERROR_NOT_FOUND; + if (status != STATUS_OK) + return status; + if (!origin.is_valid()) + return STATUS_ERROR_FAILED; + + RegistrationData registration; + status = ReadRegistrationData(registration_id, origin, ®istration); + if (status != STATUS_OK) + return status; + + registration.is_active = true; + + leveldb::WriteBatch batch; + PutRegistrationDataToBatch(registration, &batch); + return WriteBatch(&batch); +} + +ServiceWorkerDatabase::Status ServiceWorkerDatabase::UpdateLastCheckTime( + int64 registration_id, + const GURL& origin, + const base::Time& time) { + DCHECK(sequence_checker_.CalledOnValidSequencedThread()); + ServiceWorkerDatabase::Status status = LazyOpen(false); + if (IsNewOrNonexistentDatabase(status)) + return STATUS_ERROR_NOT_FOUND; + if (status != STATUS_OK) + return status; + if (!origin.is_valid()) + return STATUS_ERROR_FAILED; + + RegistrationData registration; + status = ReadRegistrationData(registration_id, origin, ®istration); + if (status != STATUS_OK) + return status; + + registration.last_update_check = time; + + leveldb::WriteBatch batch; + PutRegistrationDataToBatch(registration, &batch); + return WriteBatch(&batch); +} + +ServiceWorkerDatabase::Status ServiceWorkerDatabase::DeleteRegistration( + int64 registration_id, + const GURL& origin, + std::vector<int64>* newly_purgeable_resources) { + DCHECK(sequence_checker_.CalledOnValidSequencedThread()); + Status status = LazyOpen(false); + if (IsNewOrNonexistentDatabase(status)) + return STATUS_OK; + if (status != STATUS_OK) + return status; + if (!origin.is_valid()) + return STATUS_ERROR_FAILED; + + leveldb::WriteBatch batch; + + // Remove |origin| from unique origins if a registration specified by + // |registration_id| is the only one for |origin|. + // TODO(nhiroki): Check the uniqueness by more efficient way. + std::vector<RegistrationData> registrations; + status = GetRegistrationsForOrigin(origin, ®istrations); + if (status != STATUS_OK) + return status; + + if (registrations.size() == 1 && + registrations[0].registration_id == registration_id) { + batch.Delete(CreateUniqueOriginKey(origin)); + } + + // Delete a registration specified by |registration_id|. + batch.Delete(CreateRegistrationKey(registration_id, origin)); + + // Delete resource records associated with the registration. + for (std::vector<RegistrationData>::const_iterator itr = + registrations.begin(); itr != registrations.end(); ++itr) { + if (itr->registration_id == registration_id) { + status = DeleteResourceRecords( + itr->version_id, newly_purgeable_resources, &batch); + if (status != STATUS_OK) + return status; + break; + } + } + + return WriteBatch(&batch); +} + +ServiceWorkerDatabase::Status +ServiceWorkerDatabase::GetUncommittedResourceIds(std::set<int64>* ids) { + return ReadResourceIds(kUncommittedResIdKeyPrefix, ids); +} + +ServiceWorkerDatabase::Status +ServiceWorkerDatabase::WriteUncommittedResourceIds(const std::set<int64>& ids) { + return WriteResourceIds(kUncommittedResIdKeyPrefix, ids); +} + +ServiceWorkerDatabase::Status +ServiceWorkerDatabase::ClearUncommittedResourceIds(const std::set<int64>& ids) { + return DeleteResourceIds(kUncommittedResIdKeyPrefix, ids); +} + +ServiceWorkerDatabase::Status +ServiceWorkerDatabase::GetPurgeableResourceIds(std::set<int64>* ids) { + return ReadResourceIds(kPurgeableResIdKeyPrefix, ids); +} + +ServiceWorkerDatabase::Status +ServiceWorkerDatabase::WritePurgeableResourceIds(const std::set<int64>& ids) { + return WriteResourceIds(kPurgeableResIdKeyPrefix, ids); +} + +ServiceWorkerDatabase::Status +ServiceWorkerDatabase::ClearPurgeableResourceIds(const std::set<int64>& ids) { + return DeleteResourceIds(kPurgeableResIdKeyPrefix, ids); +} + +ServiceWorkerDatabase::Status +ServiceWorkerDatabase::PurgeUncommittedResourceIds( + const std::set<int64>& ids) { + leveldb::WriteBatch batch; + Status status = DeleteResourceIdsInBatch( + kUncommittedResIdKeyPrefix, ids, &batch); + if (status != STATUS_OK) + return status; + status = WriteResourceIdsInBatch(kPurgeableResIdKeyPrefix, ids, &batch); + if (status != STATUS_OK) + return status; + return WriteBatch(&batch); +} + +ServiceWorkerDatabase::Status ServiceWorkerDatabase::DeleteAllDataForOrigin( + const GURL& origin, + std::vector<int64>* newly_purgeable_resources) { + DCHECK(sequence_checker_.CalledOnValidSequencedThread()); + Status status = LazyOpen(false); + if (IsNewOrNonexistentDatabase(status)) + return STATUS_OK; + if (status != STATUS_OK) + return status; + if (!origin.is_valid()) + return STATUS_ERROR_FAILED; + + leveldb::WriteBatch batch; + + // Delete from the unique origin list. + batch.Delete(CreateUniqueOriginKey(origin)); + + std::vector<RegistrationData> registrations; + status = GetRegistrationsForOrigin(origin, ®istrations); + if (status != STATUS_OK) + return status; + + // Delete registrations and resource records. + for (std::vector<RegistrationData>::const_iterator itr = + registrations.begin(); itr != registrations.end(); ++itr) { + batch.Delete(CreateRegistrationKey(itr->registration_id, origin)); + status = DeleteResourceRecords( + itr->version_id, newly_purgeable_resources, &batch); + if (status != STATUS_OK) + return status; + } + + return WriteBatch(&batch); +} + +ServiceWorkerDatabase::Status ServiceWorkerDatabase::DestroyDatabase() { + DCHECK(sequence_checker_.CalledOnValidSequencedThread()); + Disable(FROM_HERE, STATUS_OK); + return LevelDBStatusToStatus( + leveldb::DestroyDB(path_.AsUTF8Unsafe(), leveldb::Options())); +} + +ServiceWorkerDatabase::Status ServiceWorkerDatabase::LazyOpen( + bool create_if_missing) { + DCHECK(sequence_checker_.CalledOnValidSequencedThread()); + + // Do not try to open a database if we tried and failed once. + if (state_ == DISABLED) + return STATUS_ERROR_FAILED; + if (IsOpen()) + return STATUS_OK; + + // When |path_| is empty, open a database in-memory. + bool use_in_memory_db = path_.empty(); + + if (!create_if_missing) { + // Avoid opening a database if it does not exist at the |path_|. + if (use_in_memory_db || + !base::PathExists(path_) || + base::IsDirectoryEmpty(path_)) { + return STATUS_ERROR_NOT_FOUND; + } + } + + leveldb::Options options; + options.create_if_missing = create_if_missing; + if (use_in_memory_db) { + env_.reset(leveldb::NewMemEnv(leveldb::Env::Default())); + options.env = env_.get(); + } + + leveldb::DB* db = NULL; + Status status = LevelDBStatusToStatus( + leveldb::DB::Open(options, path_.AsUTF8Unsafe(), &db)); + HandleOpenResult(FROM_HERE, status); + if (status != STATUS_OK) { + DCHECK(!db); + // TODO(nhiroki): Should we retry to open the database? + return status; + } + db_.reset(db); + + int64 db_version; + status = ReadDatabaseVersion(&db_version); + if (status != STATUS_OK) + return status; + DCHECK_LE(0, db_version); + if (db_version > 0) + state_ = INITIALIZED; + return STATUS_OK; +} + +bool ServiceWorkerDatabase::IsNewOrNonexistentDatabase( + ServiceWorkerDatabase::Status status) { + if (status == STATUS_ERROR_NOT_FOUND) + return true; + if (status == STATUS_OK && state_ == UNINITIALIZED) + return true; + return false; +} + +ServiceWorkerDatabase::Status ServiceWorkerDatabase::ReadNextAvailableId( + const char* id_key, + int64* next_avail_id) { + DCHECK(id_key); + DCHECK(next_avail_id); + + std::string value; + Status status = LevelDBStatusToStatus( + db_->Get(leveldb::ReadOptions(), id_key, &value)); + if (status == STATUS_ERROR_NOT_FOUND) { + // Nobody has gotten the next resource id for |id_key|. + *next_avail_id = 0; + HandleReadResult(FROM_HERE, STATUS_OK); + return STATUS_OK; + } else if (status != STATUS_OK) { + HandleReadResult(FROM_HERE, status); + return status; + } + + status = ParseId(value, next_avail_id); + HandleReadResult(FROM_HERE, status); + return status; +} + +ServiceWorkerDatabase::Status ServiceWorkerDatabase::ReadRegistrationData( + int64 registration_id, + const GURL& origin, + RegistrationData* registration) { + DCHECK(registration); + + const std::string key = CreateRegistrationKey(registration_id, origin); + std::string value; + Status status = LevelDBStatusToStatus( + db_->Get(leveldb::ReadOptions(), key, &value)); + if (status != STATUS_OK) { + HandleReadResult( + FROM_HERE, + status == STATUS_ERROR_NOT_FOUND ? STATUS_OK : status); + return status; + } + + status = ParseRegistrationData(value, registration); + HandleReadResult(FROM_HERE, status); + return status; +} + +ServiceWorkerDatabase::Status ServiceWorkerDatabase::ReadResourceRecords( + int64 version_id, + std::vector<ResourceRecord>* resources) { + DCHECK(resources->empty()); + + Status status = STATUS_OK; + const std::string prefix = CreateResourceRecordKeyPrefix(version_id); + + scoped_ptr<leveldb::Iterator> itr(db_->NewIterator(leveldb::ReadOptions())); + for (itr->Seek(prefix); itr->Valid(); itr->Next()) { + Status status = LevelDBStatusToStatus(itr->status()); + if (status != STATUS_OK) { + HandleReadResult(FROM_HERE, status); + resources->clear(); + return status; + } + + if (!RemovePrefix(itr->key().ToString(), prefix, NULL)) + break; + + ResourceRecord resource; + status = ParseResourceRecord(itr->value().ToString(), &resource); + if (status != STATUS_OK) { + HandleReadResult(FROM_HERE, status); + resources->clear(); + return status; + } + resources->push_back(resource); + } + + HandleReadResult(FROM_HERE, status); + return status; +} + +ServiceWorkerDatabase::Status ServiceWorkerDatabase::DeleteResourceRecords( + int64 version_id, + std::vector<int64>* newly_purgeable_resources, + leveldb::WriteBatch* batch) { + DCHECK(batch); + + Status status = STATUS_OK; + const std::string prefix = CreateResourceRecordKeyPrefix(version_id); + + scoped_ptr<leveldb::Iterator> itr(db_->NewIterator(leveldb::ReadOptions())); + for (itr->Seek(prefix); itr->Valid(); itr->Next()) { + status = LevelDBStatusToStatus(itr->status()); + if (status != STATUS_OK) { + HandleReadResult(FROM_HERE, status); + return status; + } + + const std::string key = itr->key().ToString(); + std::string unprefixed; + if (!RemovePrefix(key, prefix, &unprefixed)) + break; + + int64 resource_id; + status = ParseId(unprefixed, &resource_id); + if (status != STATUS_OK) { + HandleReadResult(FROM_HERE, status); + return status; + } + + // Remove a resource record. + batch->Delete(key); + + // Currently resource sharing across versions and registrations is not + // supported, so we can purge this without caring about it. + PutPurgeableResourceIdToBatch(resource_id, batch); + newly_purgeable_resources->push_back(resource_id); + } + + HandleReadResult(FROM_HERE, status); + return status; +} + +ServiceWorkerDatabase::Status ServiceWorkerDatabase::ReadResourceIds( + const char* id_key_prefix, + std::set<int64>* ids) { + DCHECK(sequence_checker_.CalledOnValidSequencedThread()); + DCHECK(id_key_prefix); + DCHECK(ids->empty()); + + Status status = LazyOpen(false); + if (IsNewOrNonexistentDatabase(status)) + return STATUS_OK; + if (status != STATUS_OK) + return status; + + scoped_ptr<leveldb::Iterator> itr(db_->NewIterator(leveldb::ReadOptions())); + for (itr->Seek(id_key_prefix); itr->Valid(); itr->Next()) { + status = LevelDBStatusToStatus(itr->status()); + if (status != STATUS_OK) { + HandleReadResult(FROM_HERE, status); + ids->clear(); + return status; + } + + std::string unprefixed; + if (!RemovePrefix(itr->key().ToString(), id_key_prefix, &unprefixed)) + break; + + int64 resource_id; + status = ParseId(unprefixed, &resource_id); + if (status != STATUS_OK) { + HandleReadResult(FROM_HERE, status); + ids->clear(); + return status; + } + ids->insert(resource_id); + } + + HandleReadResult(FROM_HERE, status); + return status; +} + +ServiceWorkerDatabase::Status ServiceWorkerDatabase::WriteResourceIds( + const char* id_key_prefix, + const std::set<int64>& ids) { + leveldb::WriteBatch batch; + Status status = WriteResourceIdsInBatch(id_key_prefix, ids, &batch); + if (status != STATUS_OK) + return status; + return WriteBatch(&batch); +} + +ServiceWorkerDatabase::Status ServiceWorkerDatabase::WriteResourceIdsInBatch( + const char* id_key_prefix, + const std::set<int64>& ids, + leveldb::WriteBatch* batch) { + DCHECK(sequence_checker_.CalledOnValidSequencedThread()); + DCHECK(id_key_prefix); + + Status status = LazyOpen(true); + if (status != STATUS_OK) + return status; + + for (std::set<int64>::const_iterator itr = ids.begin(); + itr != ids.end(); ++itr) { + // Value should be empty. + batch->Put(CreateResourceIdKey(id_key_prefix, *itr), ""); + } + return STATUS_OK; +} + +ServiceWorkerDatabase::Status ServiceWorkerDatabase::DeleteResourceIds( + const char* id_key_prefix, + const std::set<int64>& ids) { + leveldb::WriteBatch batch; + Status status = DeleteResourceIdsInBatch(id_key_prefix, ids, &batch); + if (status != STATUS_OK) + return status; + return WriteBatch(&batch); +} + +ServiceWorkerDatabase::Status ServiceWorkerDatabase::DeleteResourceIdsInBatch( + const char* id_key_prefix, + const std::set<int64>& ids, + leveldb::WriteBatch* batch) { + DCHECK(sequence_checker_.CalledOnValidSequencedThread()); + DCHECK(id_key_prefix); + + Status status = LazyOpen(false); + if (IsNewOrNonexistentDatabase(status)) + return STATUS_OK; + if (status != STATUS_OK) + return status; + + for (std::set<int64>::const_iterator itr = ids.begin(); + itr != ids.end(); ++itr) { + batch->Delete(CreateResourceIdKey(id_key_prefix, *itr)); + } + return STATUS_OK; +} + +ServiceWorkerDatabase::Status ServiceWorkerDatabase::ReadDatabaseVersion( + int64* db_version) { + std::string value; + Status status = LevelDBStatusToStatus( + db_->Get(leveldb::ReadOptions(), kDatabaseVersionKey, &value)); + if (status == STATUS_ERROR_NOT_FOUND) { + // The database hasn't been initialized yet. + *db_version = 0; + HandleReadResult(FROM_HERE, STATUS_OK); + return STATUS_OK; + } + + if (status != STATUS_OK) { + HandleReadResult(FROM_HERE, status); + return status; + } + + status = ParseDatabaseVersion(value, db_version); + HandleReadResult(FROM_HERE, status); + return status; +} + +ServiceWorkerDatabase::Status ServiceWorkerDatabase::WriteBatch( + leveldb::WriteBatch* batch) { + DCHECK(batch); + DCHECK_NE(DISABLED, state_); + + if (state_ == UNINITIALIZED) { + // Write the database schema version. + batch->Put(kDatabaseVersionKey, base::Int64ToString(kCurrentSchemaVersion)); + state_ = INITIALIZED; + } + + Status status = LevelDBStatusToStatus( + db_->Write(leveldb::WriteOptions(), batch)); + HandleWriteResult(FROM_HERE, status); + return status; +} + +void ServiceWorkerDatabase::BumpNextRegistrationIdIfNeeded( + int64 used_id, leveldb::WriteBatch* batch) { + DCHECK(batch); + if (next_avail_registration_id_ <= used_id) { + next_avail_registration_id_ = used_id + 1; + batch->Put(kNextRegIdKey, base::Int64ToString(next_avail_registration_id_)); + } +} + +void ServiceWorkerDatabase::BumpNextVersionIdIfNeeded( + int64 used_id, leveldb::WriteBatch* batch) { + DCHECK(batch); + if (next_avail_version_id_ <= used_id) { + next_avail_version_id_ = used_id + 1; + batch->Put(kNextVerIdKey, base::Int64ToString(next_avail_version_id_)); + } +} + +bool ServiceWorkerDatabase::IsOpen() { + return db_ != NULL; +} + +void ServiceWorkerDatabase::Disable( + const tracked_objects::Location& from_here, + Status status) { + if (status != STATUS_OK) { + DLOG(ERROR) << "Failed at: " << from_here.ToString() + << " with error: " << StatusToString(status); + DLOG(ERROR) << "ServiceWorkerDatabase is disabled."; + } + state_ = DISABLED; + db_.reset(); +} + +void ServiceWorkerDatabase::HandleOpenResult( + const tracked_objects::Location& from_here, + Status status) { + if (status != ServiceWorkerDatabase::STATUS_OK) + Disable(from_here, status); + UMA_HISTOGRAM_ENUMERATION(kOpenResultHistogramLabel, + status, + ServiceWorkerDatabase::STATUS_ERROR_MAX); +} + +void ServiceWorkerDatabase::HandleReadResult( + const tracked_objects::Location& from_here, + Status status) { + if (status != ServiceWorkerDatabase::STATUS_OK) + Disable(from_here, status); + UMA_HISTOGRAM_ENUMERATION(kReadResultHistogramLabel, + status, + ServiceWorkerDatabase::STATUS_ERROR_MAX); +} + +void ServiceWorkerDatabase::HandleWriteResult( + const tracked_objects::Location& from_here, + Status status) { + if (status != ServiceWorkerDatabase::STATUS_OK) + Disable(from_here, status); + UMA_HISTOGRAM_ENUMERATION(kWriteResultHistogramLabel, + status, + ServiceWorkerDatabase::STATUS_ERROR_MAX); +} + +} // namespace content diff --git a/chromium/content/browser/service_worker/service_worker_database.h b/chromium/content/browser/service_worker/service_worker_database.h new file mode 100644 index 00000000000..6475de04a99 --- /dev/null +++ b/chromium/content/browser/service_worker/service_worker_database.h @@ -0,0 +1,324 @@ +// Copyright 2014 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 CONTENT_BROWSER_SERVICE_WORKER_SERVICE_WORKER_DATABASE_H_ +#define CONTENT_BROWSER_SERVICE_WORKER_SERVICE_WORKER_DATABASE_H_ + +#include <map> +#include <set> +#include <vector> + +#include "base/files/file_path.h" +#include "base/gtest_prod_util.h" +#include "base/macros.h" +#include "base/memory/scoped_ptr.h" +#include "base/sequence_checker.h" +#include "base/time/time.h" +#include "content/common/content_export.h" +#include "content/common/service_worker/service_worker_status_code.h" +#include "url/gurl.h" + +namespace leveldb { +class DB; +class Env; +class Status; +class WriteBatch; +} + +namespace content { + +// Class to persist serviceworker registration data in a database. +// Should NOT be used on the IO thread since this does blocking +// file io. The ServiceWorkerStorage class owns this class and +// is responsible for only calling it serially on background +// non-IO threads (ala SequencedWorkerPool). +class CONTENT_EXPORT ServiceWorkerDatabase { + public: + // We do leveldb stuff in |path| or in memory if |path| is empty. + explicit ServiceWorkerDatabase(const base::FilePath& path); + ~ServiceWorkerDatabase(); + + // Used in UMA. A new value must be appended only. + enum Status { + STATUS_OK, + STATUS_ERROR_NOT_FOUND, + STATUS_ERROR_IO_ERROR, + STATUS_ERROR_CORRUPTED, + STATUS_ERROR_FAILED, + STATUS_ERROR_MAX, + }; + + struct CONTENT_EXPORT RegistrationData { + // These values are immutable for the life of a registration. + int64 registration_id; + GURL scope; + GURL script; + + // Versions are first stored once they successfully install and become + // the waiting version. Then transition to the active version. The stored + // version may be in the ACTIVE state or in the INSTALLED state. + int64 version_id; + bool is_active; + bool has_fetch_handler; + base::Time last_update_check; + + RegistrationData(); + ~RegistrationData(); + }; + + struct ResourceRecord { + int64 resource_id; + GURL url; + + ResourceRecord() {} + ResourceRecord(int64 id, GURL url) : resource_id(id), url(url) {} + }; + + // Reads next available ids from the database. Returns OK if they are + // successfully read. Fills the arguments with an initial value and returns + // OK if they are not found in the database. Otherwise, returns an error. + Status GetNextAvailableIds( + int64* next_avail_registration_id, + int64* next_avail_version_id, + int64* next_avail_resource_id); + + // Reads origins that have one or more than one registration from the + // database. Returns OK if they are successfully read or not found. + // Otherwise, returns an error. + Status GetOriginsWithRegistrations(std::set<GURL>* origins); + + // Reads registrations for |origin| from the database. Returns OK if they are + // successfully read or not found. Otherwise, returns an error. + Status GetRegistrationsForOrigin( + const GURL& origin, + std::vector<RegistrationData>* registrations); + + // Reads all registrations from the database. Returns OK if successfully read + // or not found. Otherwise, returns an error. + Status GetAllRegistrations(std::vector<RegistrationData>* registrations); + + // Saving, retrieving, and updating registration data. + // (will bump next_avail_xxxx_ids as needed) + // (resource ids will be added/removed from the uncommitted/purgeable + // lists as needed) + + // Reads a registration for |registration_id| and resource records associated + // with it from the database. Returns OK if they are successfully read. + // Otherwise, returns an error. + Status ReadRegistration( + int64 registration_id, + const GURL& origin, + RegistrationData* registration, + std::vector<ResourceRecord>* resources); + + // Writes |registration| and |resources| into the database and does following + // things: + // - Deletes an old version of the registration if exists. + // - Bumps the next registration id and the next version id if needed. + // - Removes |resources| from the uncommitted list if exist. + // Returns OK they are successfully written. Otherwise, returns an error. + Status WriteRegistration( + const RegistrationData& registration, + const std::vector<ResourceRecord>& resources, + std::vector<int64>* newly_purgeable_resources); + + // Updates a registration for |registration_id| to an active state. Returns OK + // if it's successfully updated. Otherwise, returns an error. + Status UpdateVersionToActive( + int64 registration_id, + const GURL& origin); + + // Updates last check time of a registration for |registration_id| by |time|. + // Returns OK if it's successfully updated. Otherwise, returns an error. + Status UpdateLastCheckTime( + int64 registration_id, + const GURL& origin, + const base::Time& time); + + // Deletes a registration for |registration_id| and moves resource records + // associated with it into the purgeable list. Returns OK if it's successfully + // deleted or not found in the database. Otherwise, returns an error. + Status DeleteRegistration( + int64 registration_id, + const GURL& origin, + std::vector<int64>* newly_purgeable_resources); + + // As new resources are put into the diskcache, they go into an uncommitted + // list. When a registration is saved that refers to those ids, they're + // removed from that list. When a resource no longer has any registrations or + // caches referring to it, it's added to the purgeable list. Periodically, + // the purgeable list can be purged from the diskcache. At system startup, all + // uncommitted ids are moved to the purgeable list. + + // Reads uncommitted resource ids from the database. Returns OK on success. + // Otherwise clears |ids| and returns an error. + Status GetUncommittedResourceIds(std::set<int64>* ids); + + // Writes |ids| into the database as uncommitted resources. Returns OK on + // success. Otherwise writes nothing and returns an error. + Status WriteUncommittedResourceIds(const std::set<int64>& ids); + + // Deletes uncommitted resource ids specified by |ids| from the database. + // Returns OK on success. Otherwise deletes nothing and returns an error. + Status ClearUncommittedResourceIds(const std::set<int64>& ids); + + // Reads purgeable resource ids from the database. Returns OK on success. + // Otherwise clears |ids| and returns an error. + Status GetPurgeableResourceIds(std::set<int64>* ids); + + // Writes |ids| into the database as purgeable resources. Returns OK on + // success. Otherwise writes nothing and returns an error. + Status WritePurgeableResourceIds(const std::set<int64>& ids); + + // Deletes purgeable resource ids specified by |ids| from the database. + // Returns OK on success. Otherwise deletes nothing and returns an error. + Status ClearPurgeableResourceIds(const std::set<int64>& ids); + + // Moves |ids| from the uncommitted list to the purgeable list. + // Returns OK on success. Otherwise deletes nothing and returns an error. + Status PurgeUncommittedResourceIds(const std::set<int64>& ids); + + // Deletes all data for |origin|, namely, unique origin, registrations and + // resource records. Resources are moved to the purgeable list. Returns OK if + // they are successfully deleted or not found in the database. Otherwise, + // returns an error. + Status DeleteAllDataForOrigin( + const GURL& origin, + std::vector<int64>* newly_purgeable_resources); + + // Completely deletes the contents of the database. + // Be careful using this function. + Status DestroyDatabase(); + + private: + // Opens the database at the |path_|. This is lazily called when the first + // database API is called. Returns OK if the database is successfully opened. + // Returns NOT_FOUND if the database does not exist and |create_if_missing| is + // false. Otherwise, returns an error. + Status LazyOpen(bool create_if_missing); + + // Helper for LazyOpen(). |status| must be the return value from LazyOpen() + // and this must be called just after LazyOpen() is called. Returns true if + // the database is new or nonexistent, that is, it has never been used. + bool IsNewOrNonexistentDatabase(Status status); + + // Reads the next available id for |id_key|. Returns OK if it's successfully + // read. Fills |next_avail_id| with an initial value and returns OK if it's + // not found in the database. Otherwise, returns an error. + Status ReadNextAvailableId( + const char* id_key, + int64* next_avail_id); + + // Reads registration data for |registration_id| from the database. Returns OK + // if successfully reads. Otherwise, returns an error. + Status ReadRegistrationData( + int64 registration_id, + const GURL& origin, + RegistrationData* registration); + + // Reads resource records for |version_id| from the database. Returns OK if + // it's successfully read or not found in the database. Otherwise, returns an + // error. + Status ReadResourceRecords( + int64 version_id, + std::vector<ResourceRecord>* resources); + + // Deletes resource records for |version_id| from the database. Returns OK if + // they are successfully deleted or not found in the database. Otherwise, + // returns an error. + Status DeleteResourceRecords( + int64 version_id, + std::vector<int64>* newly_purgeable_resources, + leveldb::WriteBatch* batch); + + // Reads resource ids for |id_key_prefix| from the database. Returns OK if + // it's successfully read or not found in the database. Otherwise, returns an + // error. + Status ReadResourceIds( + const char* id_key_prefix, + std::set<int64>* ids); + + // Write resource ids for |id_key_prefix| into the database. Returns OK on + // success. Otherwise, returns writes nothing and returns an error. + Status WriteResourceIds( + const char* id_key_prefix, + const std::set<int64>& ids); + Status WriteResourceIdsInBatch( + const char* id_key_prefix, + const std::set<int64>& ids, + leveldb::WriteBatch* batch); + + // Deletes resource ids for |id_key_prefix| from the database. Returns OK if + // it's successfully deleted or not found in the database. Otherwise, returns + // an error. + Status DeleteResourceIds( + const char* id_key_prefix, + const std::set<int64>& ids); + Status DeleteResourceIdsInBatch( + const char* id_key_prefix, + const std::set<int64>& ids, + leveldb::WriteBatch* batch); + + // Reads the current schema version from the database. If the database hasn't + // been written anything yet, sets |db_version| to 0 and returns OK. + Status ReadDatabaseVersion(int64* db_version); + + // Writes a batch into the database. + // NOTE: You must call this when you want to put something into the database + // because this initializes the database if needed. + Status WriteBatch(leveldb::WriteBatch* batch); + + // Bumps the next available id if |used_id| is greater than or equal to the + // cached one. + void BumpNextRegistrationIdIfNeeded( + int64 used_id, + leveldb::WriteBatch* batch); + void BumpNextVersionIdIfNeeded( + int64 used_id, + leveldb::WriteBatch* batch); + + bool IsOpen(); + + void Disable( + const tracked_objects::Location& from_here, + Status status); + void HandleOpenResult( + const tracked_objects::Location& from_here, + Status status); + void HandleReadResult( + const tracked_objects::Location& from_here, + Status status); + void HandleWriteResult( + const tracked_objects::Location& from_here, + Status status); + + base::FilePath path_; + scoped_ptr<leveldb::Env> env_; + scoped_ptr<leveldb::DB> db_; + + int64 next_avail_registration_id_; + int64 next_avail_resource_id_; + int64 next_avail_version_id_; + + enum State { + UNINITIALIZED, + INITIALIZED, + DISABLED, + }; + State state_; + + base::SequenceChecker sequence_checker_; + + FRIEND_TEST_ALL_PREFIXES(ServiceWorkerDatabaseTest, OpenDatabase); + FRIEND_TEST_ALL_PREFIXES(ServiceWorkerDatabaseTest, OpenDatabase_InMemory); + FRIEND_TEST_ALL_PREFIXES(ServiceWorkerDatabaseTest, DatabaseVersion); + FRIEND_TEST_ALL_PREFIXES(ServiceWorkerDatabaseTest, GetNextAvailableIds); + FRIEND_TEST_ALL_PREFIXES(ServiceWorkerDatabaseTest, DestroyDatabase); + + DISALLOW_COPY_AND_ASSIGN(ServiceWorkerDatabase); +}; + +} // namespace content + +#endif // CONTENT_BROWSER_SERVICE_WORKER_SERVICE_WORKER_DATABASE_H_ diff --git a/chromium/content/browser/service_worker/service_worker_database.proto b/chromium/content/browser/service_worker/service_worker_database.proto new file mode 100644 index 00000000000..c2223ecdd76 --- /dev/null +++ b/chromium/content/browser/service_worker/service_worker_database.proto @@ -0,0 +1,31 @@ +// Copyright 2014 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. + +syntax = "proto2"; + +option optimize_for = LITE_RUNTIME; + +package content; + +message ServiceWorkerRegistrationData { + required int64 registration_id = 1; + required string scope_url = 2; + required string script_url = 3; + + // Versions are first stored once they successfully install and become the + // waiting version. Then they are updated when they transition to the active + // version. + required int64 version_id = 4; + + required bool is_active = 5; + required bool has_fetch_handler = 6; + + // Serialized by Time::ToInternalValue(). + required int64 last_update_check_time = 7; +} + +message ServiceWorkerResourceRecord { + required int64 resource_id = 1; + required string url = 2; +} diff --git a/chromium/content/browser/service_worker/service_worker_database_unittest.cc b/chromium/content/browser/service_worker/service_worker_database_unittest.cc new file mode 100644 index 00000000000..baa2542cbc1 --- /dev/null +++ b/chromium/content/browser/service_worker/service_worker_database_unittest.cc @@ -0,0 +1,904 @@ +// Copyright 2014 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 "content/browser/service_worker/service_worker_database.h" + +#include <string> + +#include "base/file_util.h" +#include "base/files/scoped_temp_dir.h" +#include "base/stl_util.h" +#include "content/browser/service_worker/service_worker_database.pb.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace content { + +namespace { + +typedef ServiceWorkerDatabase::RegistrationData RegistrationData; +typedef ServiceWorkerDatabase::ResourceRecord Resource; + +struct AvailableIds { + int64 reg_id; + int64 res_id; + int64 ver_id; + + AvailableIds() : reg_id(-1), res_id(-1), ver_id(-1) {} + ~AvailableIds() {} +}; + +GURL URL(const GURL& origin, const std::string& path) { + EXPECT_TRUE(origin.is_valid()); + EXPECT_EQ(origin, origin.GetOrigin()); + GURL out(origin.spec() + path); + EXPECT_TRUE(out.is_valid()); + return out; +} + +Resource CreateResource(int64 resource_id, const GURL& url) { + EXPECT_TRUE(url.is_valid()); + Resource resource; + resource.resource_id = resource_id; + resource.url = url; + return resource; +} + +ServiceWorkerDatabase* CreateDatabase(const base::FilePath& path) { + return new ServiceWorkerDatabase(path); +} + +ServiceWorkerDatabase* CreateDatabaseInMemory() { + return new ServiceWorkerDatabase(base::FilePath()); +} + +void VerifyRegistrationData(const RegistrationData& expected, + const RegistrationData& actual) { + EXPECT_EQ(expected.registration_id, actual.registration_id); + EXPECT_EQ(expected.scope, actual.scope); + EXPECT_EQ(expected.script, actual.script); + EXPECT_EQ(expected.version_id, actual.version_id); + EXPECT_EQ(expected.is_active, actual.is_active); + EXPECT_EQ(expected.has_fetch_handler, actual.has_fetch_handler); + EXPECT_EQ(expected.last_update_check, actual.last_update_check); +} + +void VerifyResourceRecords(const std::vector<Resource>& expected, + const std::vector<Resource>& actual) { + ASSERT_EQ(expected.size(), actual.size()); + for (size_t i = 0; i < expected.size(); ++i) { + EXPECT_EQ(expected[i].resource_id, actual[i].resource_id); + EXPECT_EQ(expected[i].url, actual[i].url); + } +} + +} // namespace + +TEST(ServiceWorkerDatabaseTest, OpenDatabase) { + base::ScopedTempDir database_dir; + ASSERT_TRUE(database_dir.CreateUniqueTempDir()); + scoped_ptr<ServiceWorkerDatabase> database( + CreateDatabase(database_dir.path())); + + // Should be false because the database does not exist at the path. + EXPECT_EQ(ServiceWorkerDatabase::STATUS_ERROR_NOT_FOUND, + database->LazyOpen(false)); + + EXPECT_EQ(ServiceWorkerDatabase::STATUS_OK, database->LazyOpen(true)); + + database.reset(CreateDatabase(database_dir.path())); + EXPECT_EQ(ServiceWorkerDatabase::STATUS_OK, database->LazyOpen(false)); +} + +TEST(ServiceWorkerDatabaseTest, OpenDatabase_InMemory) { + scoped_ptr<ServiceWorkerDatabase> database(CreateDatabaseInMemory()); + + // Should be false because the database does not exist in memory. + EXPECT_EQ(ServiceWorkerDatabase::STATUS_ERROR_NOT_FOUND, + database->LazyOpen(false)); + + EXPECT_EQ(ServiceWorkerDatabase::STATUS_OK, database->LazyOpen(true)); + database.reset(CreateDatabaseInMemory()); + + // Should be false because the database is not persistent. + EXPECT_EQ(ServiceWorkerDatabase::STATUS_ERROR_NOT_FOUND, + database->LazyOpen(false)); +} + +TEST(ServiceWorkerDatabaseTest, DatabaseVersion) { + scoped_ptr<ServiceWorkerDatabase> database(CreateDatabaseInMemory()); + EXPECT_EQ(ServiceWorkerDatabase::STATUS_OK, database->LazyOpen(true)); + + // Opening a new database does not write anything, so its schema version + // should be 0. + int64 db_version = -1; + EXPECT_EQ(ServiceWorkerDatabase::STATUS_OK, + database->ReadDatabaseVersion(&db_version)); + EXPECT_EQ(0u, db_version); + + // First writing triggers database initialization and bumps the schema + // version. + std::vector<ServiceWorkerDatabase::ResourceRecord> resources; + std::vector<int64> newly_purgeable_resources; + ServiceWorkerDatabase::RegistrationData data; + ASSERT_EQ(ServiceWorkerDatabase::STATUS_OK, + database->WriteRegistration(data, resources, + &newly_purgeable_resources)); + + EXPECT_EQ(ServiceWorkerDatabase::STATUS_OK, + database->ReadDatabaseVersion(&db_version)); + EXPECT_LT(0, db_version); +} + +TEST(ServiceWorkerDatabaseTest, GetNextAvailableIds) { + base::ScopedTempDir database_dir; + ASSERT_TRUE(database_dir.CreateUniqueTempDir()); + scoped_ptr<ServiceWorkerDatabase> database( + CreateDatabase(database_dir.path())); + + GURL origin("http://example.com"); + + // The database has never been used, so returns initial values. + AvailableIds ids; + EXPECT_EQ(ServiceWorkerDatabase::STATUS_OK, database->GetNextAvailableIds( + &ids.reg_id, &ids.ver_id, &ids.res_id)); + EXPECT_EQ(0, ids.reg_id); + EXPECT_EQ(0, ids.ver_id); + EXPECT_EQ(0, ids.res_id); + + ASSERT_EQ(ServiceWorkerDatabase::STATUS_OK, database->LazyOpen(true)); + EXPECT_EQ(ServiceWorkerDatabase::STATUS_OK, database->GetNextAvailableIds( + &ids.reg_id, &ids.ver_id, &ids.res_id)); + EXPECT_EQ(0, ids.reg_id); + EXPECT_EQ(0, ids.ver_id); + EXPECT_EQ(0, ids.res_id); + + // Writing a registration bumps the next available ids. + std::vector<Resource> resources; + RegistrationData data1; + std::vector<int64> newly_purgeable_resources; + data1.registration_id = 100; + data1.scope = URL(origin, "/foo"); + data1.script = URL(origin, "/script1.js"); + data1.version_id = 200; + ASSERT_EQ(ServiceWorkerDatabase::STATUS_OK, + database->WriteRegistration(data1, resources, + &newly_purgeable_resources)); + + EXPECT_EQ(ServiceWorkerDatabase::STATUS_OK, database->GetNextAvailableIds( + &ids.reg_id, &ids.ver_id, &ids.res_id)); + EXPECT_EQ(101, ids.reg_id); + EXPECT_EQ(201, ids.ver_id); + EXPECT_EQ(0, ids.res_id); + + // Writing a registration whose ids are lower than the stored ones should not + // bump the next available ids. + RegistrationData data2; + data2.registration_id = 10; + data2.scope = URL(origin, "/bar"); + data2.script = URL(origin, "/script2.js"); + data2.version_id = 20; + ASSERT_EQ(ServiceWorkerDatabase::STATUS_OK, + database->WriteRegistration(data2, resources, + &newly_purgeable_resources)); + + // Close and reopen the database to verify the stored values. + database.reset(CreateDatabase(database_dir.path())); + + EXPECT_EQ(ServiceWorkerDatabase::STATUS_OK, database->GetNextAvailableIds( + &ids.reg_id, &ids.ver_id, &ids.res_id)); + EXPECT_EQ(101, ids.reg_id); + EXPECT_EQ(201, ids.ver_id); + EXPECT_EQ(0, ids.res_id); +} + +TEST(ServiceWorkerDatabaseTest, GetOriginsWithRegistrations) { + scoped_ptr<ServiceWorkerDatabase> database(CreateDatabaseInMemory()); + + std::set<GURL> origins; + EXPECT_EQ(ServiceWorkerDatabase::STATUS_OK, + database->GetOriginsWithRegistrations(&origins)); + EXPECT_TRUE(origins.empty()); + + std::vector<Resource> resources; + std::vector<int64> newly_purgeable_resources; + + GURL origin1("http://example.com"); + RegistrationData data1; + data1.registration_id = 123; + data1.scope = URL(origin1, "/foo"); + data1.script = URL(origin1, "/script1.js"); + data1.version_id = 456; + ASSERT_EQ(ServiceWorkerDatabase::STATUS_OK, + database->WriteRegistration(data1, resources, + &newly_purgeable_resources)); + + GURL origin2("https://www.example.com"); + RegistrationData data2; + data2.registration_id = 234; + data2.scope = URL(origin2, "/bar"); + data2.script = URL(origin2, "/script2.js"); + data2.version_id = 567; + ASSERT_EQ(ServiceWorkerDatabase::STATUS_OK, + database->WriteRegistration(data2, resources, + &newly_purgeable_resources)); + + GURL origin3("https://example.org"); + RegistrationData data3; + data3.registration_id = 345; + data3.scope = URL(origin3, "/hoge"); + data3.script = URL(origin3, "/script3.js"); + data3.version_id = 678; + ASSERT_EQ(ServiceWorkerDatabase::STATUS_OK, + database->WriteRegistration(data3, resources, + &newly_purgeable_resources)); + + // |origin3| has two registrations. + RegistrationData data4; + data4.registration_id = 456; + data4.scope = URL(origin3, "/fuga"); + data4.script = URL(origin3, "/script4.js"); + data4.version_id = 789; + ASSERT_EQ(ServiceWorkerDatabase::STATUS_OK, + database->WriteRegistration(data4, resources, + &newly_purgeable_resources)); + + origins.clear(); + EXPECT_EQ(ServiceWorkerDatabase::STATUS_OK, + database->GetOriginsWithRegistrations(&origins)); + EXPECT_EQ(3U, origins.size()); + EXPECT_TRUE(ContainsKey(origins, origin1)); + EXPECT_TRUE(ContainsKey(origins, origin2)); + EXPECT_TRUE(ContainsKey(origins, origin3)); + + // |origin3| has another registration, so should not remove it from the + // unique origin list. + ASSERT_EQ(ServiceWorkerDatabase::STATUS_OK, + database->DeleteRegistration(data4.registration_id, origin3, + &newly_purgeable_resources)); + + origins.clear(); + EXPECT_EQ(ServiceWorkerDatabase::STATUS_OK, + database->GetOriginsWithRegistrations(&origins)); + EXPECT_EQ(3U, origins.size()); + EXPECT_TRUE(ContainsKey(origins, origin1)); + EXPECT_TRUE(ContainsKey(origins, origin2)); + EXPECT_TRUE(ContainsKey(origins, origin3)); + + // |origin3| should be removed from the unique origin list. + ASSERT_EQ(ServiceWorkerDatabase::STATUS_OK, + database->DeleteRegistration(data3.registration_id, origin3, + &newly_purgeable_resources)); + + origins.clear(); + EXPECT_EQ(ServiceWorkerDatabase::STATUS_OK, + database->GetOriginsWithRegistrations(&origins)); + EXPECT_EQ(2U, origins.size()); + EXPECT_TRUE(ContainsKey(origins, origin1)); + EXPECT_TRUE(ContainsKey(origins, origin2)); +} + +TEST(ServiceWorkerDatabaseTest, GetRegistrationsForOrigin) { + scoped_ptr<ServiceWorkerDatabase> database(CreateDatabaseInMemory()); + + GURL origin1("http://example.com"); + GURL origin2("https://www.example.com"); + GURL origin3("https://example.org"); + + std::vector<RegistrationData> registrations; + EXPECT_EQ(ServiceWorkerDatabase::STATUS_OK, + database->GetRegistrationsForOrigin(origin1, ®istrations)); + EXPECT_TRUE(registrations.empty()); + + std::vector<Resource> resources; + std::vector<int64> newly_purgeable_resources; + + RegistrationData data1; + data1.registration_id = 100; + data1.scope = URL(origin1, "/foo"); + data1.script = URL(origin1, "/script1.js"); + data1.version_id = 1000; + ASSERT_EQ(ServiceWorkerDatabase::STATUS_OK, + database->WriteRegistration(data1, resources, + &newly_purgeable_resources)); + + RegistrationData data2; + data2.registration_id = 200; + data2.scope = URL(origin2, "/bar"); + data2.script = URL(origin2, "/script2.js"); + data2.version_id = 2000; + ASSERT_EQ(ServiceWorkerDatabase::STATUS_OK, + database->WriteRegistration(data2, resources, + &newly_purgeable_resources)); + + RegistrationData data3; + data3.registration_id = 300; + data3.scope = URL(origin3, "/hoge"); + data3.script = URL(origin3, "/script3.js"); + data3.version_id = 3000; + ASSERT_EQ(ServiceWorkerDatabase::STATUS_OK, + database->WriteRegistration(data3, resources, + &newly_purgeable_resources)); + + // |origin3| has two registrations. + RegistrationData data4; + data4.registration_id = 400; + data4.scope = URL(origin3, "/fuga"); + data4.script = URL(origin3, "/script4.js"); + data4.version_id = 4000; + ASSERT_EQ(ServiceWorkerDatabase::STATUS_OK, + database->WriteRegistration(data4, resources, + &newly_purgeable_resources)); + + registrations.clear(); + EXPECT_EQ(ServiceWorkerDatabase::STATUS_OK, + database->GetRegistrationsForOrigin(origin3, ®istrations)); + EXPECT_EQ(2U, registrations.size()); + VerifyRegistrationData(data3, registrations[0]); + VerifyRegistrationData(data4, registrations[1]); +} + +TEST(ServiceWorkerDatabaseTest, GetAllRegistrations) { + scoped_ptr<ServiceWorkerDatabase> database(CreateDatabaseInMemory()); + + std::vector<RegistrationData> registrations; + EXPECT_EQ(ServiceWorkerDatabase::STATUS_OK, + database->GetAllRegistrations(®istrations)); + EXPECT_TRUE(registrations.empty()); + + std::vector<Resource> resources; + std::vector<int64> newly_purgeable_resources; + + GURL origin1("http://www1.example.com"); + RegistrationData data1; + data1.registration_id = 100; + data1.scope = URL(origin1, "/foo"); + data1.script = URL(origin1, "/script1.js"); + data1.version_id = 1000; + ASSERT_EQ(ServiceWorkerDatabase::STATUS_OK, + database->WriteRegistration(data1, resources, + &newly_purgeable_resources)); + + GURL origin2("http://www2.example.com"); + RegistrationData data2; + data2.registration_id = 200; + data2.scope = URL(origin2, "/bar"); + data2.script = URL(origin2, "/script2.js"); + data2.version_id = 2000; + ASSERT_EQ(ServiceWorkerDatabase::STATUS_OK, + database->WriteRegistration(data2, resources, + &newly_purgeable_resources)); + + GURL origin3("http://www3.example.com"); + RegistrationData data3; + data3.registration_id = 300; + data3.scope = URL(origin3, "/hoge"); + data3.script = URL(origin3, "/script3.js"); + data3.version_id = 3000; + ASSERT_EQ(ServiceWorkerDatabase::STATUS_OK, + database->WriteRegistration(data3, resources, + &newly_purgeable_resources)); + + // |origin3| has two registrations. + RegistrationData data4; + data4.registration_id = 400; + data4.scope = URL(origin3, "/fuga"); + data4.script = URL(origin3, "/script4.js"); + data4.version_id = 4000; + ASSERT_EQ(ServiceWorkerDatabase::STATUS_OK, + database->WriteRegistration(data4, resources, + &newly_purgeable_resources)); + + registrations.clear(); + EXPECT_EQ(ServiceWorkerDatabase::STATUS_OK, + database->GetAllRegistrations(®istrations)); + EXPECT_EQ(4U, registrations.size()); + VerifyRegistrationData(data1, registrations[0]); + VerifyRegistrationData(data2, registrations[1]); + VerifyRegistrationData(data3, registrations[2]); + VerifyRegistrationData(data4, registrations[3]); +} + +TEST(ServiceWorkerDatabaseTest, Registration_Basic) { + scoped_ptr<ServiceWorkerDatabase> database(CreateDatabaseInMemory()); + + GURL origin("http://example.com"); + RegistrationData data; + data.registration_id = 100; + data.scope = URL(origin, "/foo"); + data.script = URL(origin, "/script.js"); + data.version_id = 200; + + std::vector<Resource> resources; + resources.push_back(CreateResource(1, URL(origin, "/resource1"))); + resources.push_back(CreateResource(2, URL(origin, "/resource2"))); + + // Write a resource to the uncommitted list to make sure that writing + // registration removes resource ids associated with the registration from + // the uncommitted list. + std::set<int64> uncommitted_ids; + uncommitted_ids.insert(resources[0].resource_id); + EXPECT_EQ(ServiceWorkerDatabase::STATUS_OK, + database->WriteUncommittedResourceIds(uncommitted_ids)); + std::set<int64> uncommitted_ids_out; + EXPECT_EQ(ServiceWorkerDatabase::STATUS_OK, + database->GetUncommittedResourceIds(&uncommitted_ids_out)); + EXPECT_EQ(uncommitted_ids, uncommitted_ids_out); + + std::vector<int64> newly_purgeable_resources; + EXPECT_EQ(ServiceWorkerDatabase::STATUS_OK, + database->WriteRegistration(data, resources, + &newly_purgeable_resources)); + + // Make sure that the registration and resource records are stored. + RegistrationData data_out; + std::vector<Resource> resources_out; + EXPECT_EQ(ServiceWorkerDatabase::STATUS_OK, + database->ReadRegistration( + data.registration_id, origin, &data_out, &resources_out)); + VerifyRegistrationData(data, data_out); + VerifyResourceRecords(resources, resources_out); + + // Make sure that the resource is removed from the uncommitted list. + uncommitted_ids_out.clear(); + EXPECT_EQ(ServiceWorkerDatabase::STATUS_OK, + database->GetUncommittedResourceIds(&uncommitted_ids_out)); + EXPECT_TRUE(uncommitted_ids_out.empty()); + + EXPECT_EQ(ServiceWorkerDatabase::STATUS_OK, + database->DeleteRegistration(data.registration_id, origin, + &newly_purgeable_resources)); + ASSERT_EQ(resources.size(), newly_purgeable_resources.size()); + for (size_t i = 0; i < resources.size(); ++i) + EXPECT_EQ(newly_purgeable_resources[i], resources[i].resource_id); + + // Make sure that the registration and resource records are gone. + resources_out.clear(); + EXPECT_EQ(ServiceWorkerDatabase::STATUS_ERROR_NOT_FOUND, + database->ReadRegistration( + data.registration_id, origin, &data_out, &resources_out)); + EXPECT_TRUE(resources_out.empty()); + + // Resources should be purgeable because these are no longer referred. + std::set<int64> purgeable_ids_out; + EXPECT_EQ(ServiceWorkerDatabase::STATUS_OK, + database->GetPurgeableResourceIds(&purgeable_ids_out)); + EXPECT_EQ(2u, purgeable_ids_out.size()); + EXPECT_TRUE(ContainsKey(purgeable_ids_out, resources[0].resource_id)); + EXPECT_TRUE(ContainsKey(purgeable_ids_out, resources[1].resource_id)); +} + +TEST(ServiceWorkerDatabaseTest, Registration_Overwrite) { + scoped_ptr<ServiceWorkerDatabase> database(CreateDatabaseInMemory()); + + GURL origin("http://example.com"); + RegistrationData data; + data.registration_id = 100; + data.scope = URL(origin, "/foo"); + data.script = URL(origin, "/script.js"); + data.version_id = 200; + + std::vector<Resource> resources1; + resources1.push_back(CreateResource(1, URL(origin, "/resource1"))); + resources1.push_back(CreateResource(2, URL(origin, "/resource2"))); + std::vector<int64> newly_purgeable_resources; + + EXPECT_EQ(ServiceWorkerDatabase::STATUS_OK, + database->WriteRegistration(data, resources1, + &newly_purgeable_resources)); + EXPECT_TRUE(newly_purgeable_resources.empty()); + + // Make sure that the registration and resource records are stored. + RegistrationData data_out; + std::vector<Resource> resources_out; + EXPECT_EQ(ServiceWorkerDatabase::STATUS_OK, database->ReadRegistration( + data.registration_id, origin, &data_out, &resources_out)); + VerifyRegistrationData(data, data_out); + VerifyResourceRecords(resources1, resources_out); + + // Update the registration. + RegistrationData updated_data = data; + updated_data.version_id = data.version_id + 1; + std::vector<Resource> resources2; + resources2.push_back(CreateResource(3, URL(origin, "/resource3"))); + resources2.push_back(CreateResource(4, URL(origin, "/resource4"))); + + EXPECT_EQ(ServiceWorkerDatabase::STATUS_OK, + database->WriteRegistration(updated_data, resources2, + &newly_purgeable_resources)); + ASSERT_EQ(resources1.size(), newly_purgeable_resources.size()); + for(size_t i = 0; i < resources1.size(); ++i) + EXPECT_EQ(newly_purgeable_resources[i], resources1[i].resource_id); + + // Make sure that |updated_data| is stored and resources referred from |data| + // is moved to the purgeable list. + resources_out.clear(); + EXPECT_EQ(ServiceWorkerDatabase::STATUS_OK, database->ReadRegistration( + updated_data.registration_id, origin, &data_out, &resources_out)); + VerifyRegistrationData(updated_data, data_out); + VerifyResourceRecords(resources2, resources_out); + + std::set<int64> purgeable_ids_out; + EXPECT_EQ(ServiceWorkerDatabase::STATUS_OK, + database->GetPurgeableResourceIds(&purgeable_ids_out)); + EXPECT_EQ(2u, purgeable_ids_out.size()); + EXPECT_TRUE(ContainsKey(purgeable_ids_out, resources1[0].resource_id)); + EXPECT_TRUE(ContainsKey(purgeable_ids_out, resources1[1].resource_id)); +} + +TEST(ServiceWorkerDatabaseTest, Registration_Multiple) { + scoped_ptr<ServiceWorkerDatabase> database(CreateDatabaseInMemory()); + GURL origin("http://example.com"); + + std::vector<int64> newly_purgeable_resources; + + // Add registration1. + RegistrationData data1; + data1.registration_id = 100; + data1.scope = URL(origin, "/foo"); + data1.script = URL(origin, "/script1.js"); + data1.version_id = 200; + + std::vector<Resource> resources1; + resources1.push_back(CreateResource(1, URL(origin, "/resource1"))); + resources1.push_back(CreateResource(2, URL(origin, "/resource2"))); + EXPECT_EQ(ServiceWorkerDatabase::STATUS_OK, + database->WriteRegistration(data1, resources1, + &newly_purgeable_resources)); + + // Add registration2. + RegistrationData data2; + data2.registration_id = 101; + data2.scope = URL(origin, "/bar"); + data2.script = URL(origin, "/script2.js"); + data2.version_id = 201; + + std::vector<Resource> resources2; + resources2.push_back(CreateResource(3, URL(origin, "/resource3"))); + resources2.push_back(CreateResource(4, URL(origin, "/resource4"))); + EXPECT_EQ(ServiceWorkerDatabase::STATUS_OK, + database->WriteRegistration(data2, resources2, + &newly_purgeable_resources)); + + // Make sure that registration1 is stored. + RegistrationData data_out; + std::vector<Resource> resources_out; + EXPECT_EQ(ServiceWorkerDatabase::STATUS_OK, database->ReadRegistration( + data1.registration_id, origin, &data_out, &resources_out)); + VerifyRegistrationData(data1, data_out); + VerifyResourceRecords(resources1, resources_out); + + // Make sure that registration2 is also stored. + resources_out.clear(); + EXPECT_EQ(ServiceWorkerDatabase::STATUS_OK, database->ReadRegistration( + data2.registration_id, origin, &data_out, &resources_out)); + VerifyRegistrationData(data2, data_out); + VerifyResourceRecords(resources2, resources_out); + + std::set<int64> purgeable_ids_out; + EXPECT_EQ(ServiceWorkerDatabase::STATUS_OK, + database->GetPurgeableResourceIds(&purgeable_ids_out)); + EXPECT_TRUE(purgeable_ids_out.empty()); + + // Delete registration1. + EXPECT_EQ(ServiceWorkerDatabase::STATUS_OK, + database->DeleteRegistration(data1.registration_id, origin, + &newly_purgeable_resources)); + + // Make sure that registration1 is gone. + resources_out.clear(); + EXPECT_EQ(ServiceWorkerDatabase::STATUS_ERROR_NOT_FOUND, + database->ReadRegistration( + data1.registration_id, origin, &data_out, &resources_out)); + EXPECT_TRUE(resources_out.empty()); + + purgeable_ids_out.clear(); + EXPECT_EQ(ServiceWorkerDatabase::STATUS_OK, + database->GetPurgeableResourceIds(&purgeable_ids_out)); + EXPECT_EQ(2u, purgeable_ids_out.size()); + EXPECT_TRUE(ContainsKey(purgeable_ids_out, resources1[0].resource_id)); + EXPECT_TRUE(ContainsKey(purgeable_ids_out, resources1[1].resource_id)); + + // Make sure that registration2 is still alive. + resources_out.clear(); + EXPECT_EQ(ServiceWorkerDatabase::STATUS_OK, database->ReadRegistration( + data2.registration_id, origin, &data_out, &resources_out)); + VerifyRegistrationData(data2, data_out); + VerifyResourceRecords(resources2, resources_out); +} + +TEST(ServiceWorkerDatabaseTest, UpdateVersionToActive) { + scoped_ptr<ServiceWorkerDatabase> database(CreateDatabaseInMemory()); + GURL origin("http://example.com"); + + std::vector<int64> newly_purgeable_resources; + + // Should be false because a registration does not exist. + EXPECT_EQ(ServiceWorkerDatabase::STATUS_ERROR_NOT_FOUND, + database->UpdateVersionToActive(0, origin)); + + // Add a registration. + RegistrationData data; + data.registration_id = 100; + data.scope = URL(origin, "/foo"); + data.script = URL(origin, "/script.js"); + data.version_id = 200; + data.is_active = false; + EXPECT_EQ(ServiceWorkerDatabase::STATUS_OK, + database->WriteRegistration(data, std::vector<Resource>(), + &newly_purgeable_resources)); + + // Make sure that the registration is stored. + RegistrationData data_out; + std::vector<Resource> resources_out; + EXPECT_EQ(ServiceWorkerDatabase::STATUS_OK, + database->ReadRegistration( + data.registration_id, origin, &data_out, &resources_out)); + VerifyRegistrationData(data, data_out); + EXPECT_TRUE(resources_out.empty()); + + // Activate the registration. + EXPECT_EQ(ServiceWorkerDatabase::STATUS_OK, + database->UpdateVersionToActive(data.registration_id, origin)); + + // Make sure that the registration is activated. + resources_out.clear(); + EXPECT_EQ(ServiceWorkerDatabase::STATUS_OK, + database->ReadRegistration( + data.registration_id, origin, &data_out, &resources_out)); + RegistrationData expected_data = data; + expected_data.is_active = true; + VerifyRegistrationData(expected_data, data_out); + EXPECT_TRUE(resources_out.empty()); + + // Delete the registration. + EXPECT_EQ(ServiceWorkerDatabase::STATUS_OK, + database->DeleteRegistration(data.registration_id, origin, + &newly_purgeable_resources)); + + // Should be false because the registration is gone. + EXPECT_EQ(ServiceWorkerDatabase::STATUS_ERROR_NOT_FOUND, + database->UpdateVersionToActive(data.registration_id, origin)); +} + +TEST(ServiceWorkerDatabaseTest, UpdateLastCheckTime) { + scoped_ptr<ServiceWorkerDatabase> database(CreateDatabaseInMemory()); + GURL origin("http://example.com"); + std::vector<int64> newly_purgeable_resources; + + // Should be false because a registration does not exist. + EXPECT_EQ(ServiceWorkerDatabase::STATUS_ERROR_NOT_FOUND, + database->UpdateLastCheckTime(0, origin, base::Time::Now())); + + // Add a registration. + RegistrationData data; + data.registration_id = 100; + data.scope = URL(origin, "/foo"); + data.script = URL(origin, "/script.js"); + data.version_id = 200; + data.last_update_check = base::Time::Now(); + EXPECT_EQ(ServiceWorkerDatabase::STATUS_OK, + database->WriteRegistration(data, std::vector<Resource>(), + &newly_purgeable_resources)); + + // Make sure that the registration is stored. + RegistrationData data_out; + std::vector<Resource> resources_out; + EXPECT_EQ(ServiceWorkerDatabase::STATUS_OK, + database->ReadRegistration( + data.registration_id, origin, &data_out, &resources_out)); + VerifyRegistrationData(data, data_out); + EXPECT_TRUE(resources_out.empty()); + + // Update the last check time. + base::Time updated_time = base::Time::Now(); + EXPECT_EQ(ServiceWorkerDatabase::STATUS_OK, + database->UpdateLastCheckTime( + data.registration_id, origin, updated_time)); + + // Make sure that the registration is updated. + resources_out.clear(); + EXPECT_EQ(ServiceWorkerDatabase::STATUS_OK, + database->ReadRegistration( + data.registration_id, origin, &data_out, &resources_out)); + RegistrationData expected_data = data; + expected_data.last_update_check = updated_time; + VerifyRegistrationData(expected_data, data_out); + EXPECT_TRUE(resources_out.empty()); + + // Delete the registration. + EXPECT_EQ(ServiceWorkerDatabase::STATUS_OK, + database->DeleteRegistration(data.registration_id, origin, + &newly_purgeable_resources)); + + // Should be false because the registration is gone. + EXPECT_EQ(ServiceWorkerDatabase::STATUS_ERROR_NOT_FOUND, + database->UpdateLastCheckTime( + data.registration_id, origin, base::Time::Now())); +} + +TEST(ServiceWorkerDatabaseTest, UncommittedResourceIds) { + scoped_ptr<ServiceWorkerDatabase> database(CreateDatabaseInMemory()); + + // Write {1, 2, 3}. + std::set<int64> ids1; + ids1.insert(1); + ids1.insert(2); + ids1.insert(3); + EXPECT_EQ(ServiceWorkerDatabase::STATUS_OK, + database->WriteUncommittedResourceIds(ids1)); + + std::set<int64> ids_out; + EXPECT_EQ(ServiceWorkerDatabase::STATUS_OK, + database->GetUncommittedResourceIds(&ids_out)); + EXPECT_EQ(ids1, ids_out); + + // Write {2, 4}. + std::set<int64> ids2; + ids2.insert(2); + ids2.insert(4); + EXPECT_EQ(ServiceWorkerDatabase::STATUS_OK, + database->WriteUncommittedResourceIds(ids2)); + + ids_out.clear(); + EXPECT_EQ(ServiceWorkerDatabase::STATUS_OK, + database->GetUncommittedResourceIds(&ids_out)); + std::set<int64> expected = base::STLSetUnion<std::set<int64> >(ids1, ids2); + EXPECT_EQ(expected, ids_out); + + // Delete {2, 3}. + std::set<int64> ids3; + ids3.insert(2); + ids3.insert(3); + EXPECT_EQ(ServiceWorkerDatabase::STATUS_OK, + database->ClearUncommittedResourceIds(ids3)); + + ids_out.clear(); + EXPECT_EQ(ServiceWorkerDatabase::STATUS_OK, + database->GetUncommittedResourceIds(&ids_out)); + expected = base::STLSetDifference<std::set<int64> >(expected, ids3); + EXPECT_EQ(expected, ids_out); +} + +TEST(ServiceWorkerDatabaseTest, PurgeableResourceIds) { + scoped_ptr<ServiceWorkerDatabase> database(CreateDatabaseInMemory()); + + // Write {1, 2, 3}. + std::set<int64> ids1; + ids1.insert(1); + ids1.insert(2); + ids1.insert(3); + EXPECT_EQ(ServiceWorkerDatabase::STATUS_OK, + database->WritePurgeableResourceIds(ids1)); + + std::set<int64> ids_out; + EXPECT_EQ(ServiceWorkerDatabase::STATUS_OK, + database->GetPurgeableResourceIds(&ids_out)); + EXPECT_EQ(ids1, ids_out); + + // Write {2, 4}. + std::set<int64> ids2; + ids2.insert(2); + ids2.insert(4); + EXPECT_EQ(ServiceWorkerDatabase::STATUS_OK, + database->WritePurgeableResourceIds(ids2)); + + ids_out.clear(); + EXPECT_EQ(ServiceWorkerDatabase::STATUS_OK, + database->GetPurgeableResourceIds(&ids_out)); + std::set<int64> expected = base::STLSetUnion<std::set<int64> >(ids1, ids2); + EXPECT_EQ(expected, ids_out); + + // Delete {2, 3}. + std::set<int64> ids3; + ids3.insert(2); + ids3.insert(3); + EXPECT_EQ(ServiceWorkerDatabase::STATUS_OK, + database->ClearPurgeableResourceIds(ids3)); + + ids_out.clear(); + EXPECT_EQ(ServiceWorkerDatabase::STATUS_OK, + database->GetPurgeableResourceIds(&ids_out)); + expected = base::STLSetDifference<std::set<int64> >(expected, ids3); + EXPECT_EQ(expected, ids_out); +} + +TEST(ServiceWorkerDatabaseTest, DeleteAllDataForOrigin) { + scoped_ptr<ServiceWorkerDatabase> database(CreateDatabaseInMemory()); + std::vector<int64> newly_purgeable_resources; + + // Data associated with |origin1| will be removed. + GURL origin1("http://example.com"); + GURL origin2("http://example.org"); + + // |origin1| has two registrations. + RegistrationData data1; + data1.registration_id = 10; + data1.scope = URL(origin1, "/foo"); + data1.script = URL(origin1, "/script1.js"); + data1.version_id = 100; + + std::vector<Resource> resources1; + resources1.push_back(CreateResource(1, URL(origin1, "/resource1"))); + resources1.push_back(CreateResource(2, URL(origin1, "/resource2"))); + ASSERT_EQ(ServiceWorkerDatabase::STATUS_OK, + database->WriteRegistration(data1, resources1, + &newly_purgeable_resources)); + + RegistrationData data2; + data2.registration_id = 11; + data2.scope = URL(origin1, "/bar"); + data2.script = URL(origin1, "/script2.js"); + data2.version_id = 101; + + std::vector<Resource> resources2; + resources2.push_back(CreateResource(3, URL(origin1, "/resource3"))); + resources2.push_back(CreateResource(4, URL(origin1, "/resource4"))); + ASSERT_EQ(ServiceWorkerDatabase::STATUS_OK, + database->WriteRegistration(data2, resources2, + &newly_purgeable_resources)); + + // |origin2| has one registration. + RegistrationData data3; + data3.registration_id = 12; + data3.scope = URL(origin2, "/hoge"); + data3.script = URL(origin2, "/script3.js"); + data3.version_id = 102; + + std::vector<Resource> resources3; + resources3.push_back(CreateResource(5, URL(origin2, "/resource5"))); + resources3.push_back(CreateResource(6, URL(origin2, "/resource6"))); + ASSERT_EQ(ServiceWorkerDatabase::STATUS_OK, + database->WriteRegistration(data3, resources3, + &newly_purgeable_resources)); + + EXPECT_EQ(ServiceWorkerDatabase::STATUS_OK, + database->DeleteAllDataForOrigin(origin1, + &newly_purgeable_resources)); + + // |origin1| should be removed from the unique origin list. + std::set<GURL> unique_origins; + EXPECT_EQ(ServiceWorkerDatabase::STATUS_OK, + database->GetOriginsWithRegistrations(&unique_origins)); + EXPECT_EQ(1u, unique_origins.size()); + EXPECT_TRUE(ContainsKey(unique_origins, origin2)); + + // The registrations for |origin1| should be removed. + std::vector<RegistrationData> registrations; + EXPECT_EQ(ServiceWorkerDatabase::STATUS_OK, + database->GetRegistrationsForOrigin(origin1, ®istrations)); + EXPECT_TRUE(registrations.empty()); + + // The registration for |origin2| should not be removed. + RegistrationData data_out; + std::vector<Resource> resources_out; + EXPECT_EQ(ServiceWorkerDatabase::STATUS_OK, database->ReadRegistration( + data3.registration_id, origin2, &data_out, &resources_out)); + VerifyRegistrationData(data3, data_out); + VerifyResourceRecords(resources3, resources_out); + + // The resources associated with |origin1| should be purgeable. + std::set<int64> purgeable_ids_out; + EXPECT_EQ(ServiceWorkerDatabase::STATUS_OK, + database->GetPurgeableResourceIds(&purgeable_ids_out)); + EXPECT_EQ(4u, purgeable_ids_out.size()); + EXPECT_TRUE(ContainsKey(purgeable_ids_out, 1)); + EXPECT_TRUE(ContainsKey(purgeable_ids_out, 2)); + EXPECT_TRUE(ContainsKey(purgeable_ids_out, 3)); + EXPECT_TRUE(ContainsKey(purgeable_ids_out, 4)); +} + +TEST(ServiceWorkerDatabaseTest, DestroyDatabase) { + base::ScopedTempDir database_dir; + ASSERT_TRUE(database_dir.CreateUniqueTempDir()); + scoped_ptr<ServiceWorkerDatabase> database( + CreateDatabase(database_dir.path())); + + EXPECT_EQ(ServiceWorkerDatabase::STATUS_OK, database->LazyOpen(true)); + ASSERT_TRUE(base::DirectoryExists(database_dir.path())); + + EXPECT_EQ(ServiceWorkerDatabase::STATUS_OK, database->DestroyDatabase()); + ASSERT_FALSE(base::DirectoryExists(database_dir.path())); +} + +} // namespace content diff --git a/chromium/content/browser/service_worker/service_worker_disk_cache.cc b/chromium/content/browser/service_worker/service_worker_disk_cache.cc new file mode 100644 index 00000000000..a8ca52acae7 --- /dev/null +++ b/chromium/content/browser/service_worker/service_worker_disk_cache.cc @@ -0,0 +1,22 @@ +// Copyright 2014 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 "content/browser/service_worker/service_worker_disk_cache.h" + +namespace content { + +ServiceWorkerResponseReader::ServiceWorkerResponseReader( + int64 response_id, ServiceWorkerDiskCache* disk_cache) + : appcache::AppCacheResponseReader(response_id, 0, disk_cache) { +} + +ServiceWorkerResponseWriter::ServiceWorkerResponseWriter( + int64 response_id, ServiceWorkerDiskCache* disk_cache) + : appcache::AppCacheResponseWriter(response_id, 0, disk_cache) { +} + +HttpResponseInfoIOBuffer::~HttpResponseInfoIOBuffer() { +} + +} // namespace content diff --git a/chromium/content/browser/service_worker/service_worker_disk_cache.h b/chromium/content/browser/service_worker/service_worker_disk_cache.h new file mode 100644 index 00000000000..83850129aad --- /dev/null +++ b/chromium/content/browser/service_worker/service_worker_disk_cache.h @@ -0,0 +1,55 @@ +// Copyright 2014 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 CONTENT_BROWSER_SERVICE_WORKER_SERVICE_WORKER_DISK_CACHE_H_ +#define CONTENT_BROWSER_SERVICE_WORKER_SERVICE_WORKER_DISK_CACHE_H_ + +#include "content/common/content_export.h" +#include "webkit/browser/appcache/appcache_disk_cache.h" + +namespace content { + +// Wholesale reusage of the appcache code for response reading, +// writing, and storage. See the corresponding class in that +// library for doc comments and other details. +// TODO(michaeln): If this reuse sticks, refactor/move the +// resused classes to a more common location. + +class CONTENT_EXPORT ServiceWorkerDiskCache + : public appcache::AppCacheDiskCache { +}; + +class CONTENT_EXPORT ServiceWorkerResponseReader + : public appcache::AppCacheResponseReader { + protected: + // Should only be constructed by the storage class. + friend class ServiceWorkerStorage; + ServiceWorkerResponseReader( + int64 response_id, + ServiceWorkerDiskCache* disk_cache); +}; + +class CONTENT_EXPORT ServiceWorkerResponseWriter + : public appcache::AppCacheResponseWriter { + protected: + // Should only be constructed by the storage class. + friend class ServiceWorkerStorage; + ServiceWorkerResponseWriter( + int64 response_id, + ServiceWorkerDiskCache* disk_cache); +}; + +struct CONTENT_EXPORT HttpResponseInfoIOBuffer + : public appcache::HttpResponseInfoIOBuffer { + public: + HttpResponseInfoIOBuffer() : appcache::HttpResponseInfoIOBuffer() {} + explicit HttpResponseInfoIOBuffer(net::HttpResponseInfo* info) + : appcache::HttpResponseInfoIOBuffer(info) {} + protected: + virtual ~HttpResponseInfoIOBuffer(); +}; + +} // namespace content + +#endif // CONTENT_BROWSER_SERVICE_WORKER_SERVICE_WORKER_DISK_CACHE_H_ diff --git a/chromium/content/browser/service_worker/service_worker_dispatcher_host.cc b/chromium/content/browser/service_worker/service_worker_dispatcher_host.cc index 76e7ff746ae..964a393f8eb 100644 --- a/chromium/content/browser/service_worker/service_worker_dispatcher_host.cc +++ b/chromium/content/browser/service_worker/service_worker_dispatcher_host.cc @@ -4,18 +4,26 @@ #include "content/browser/service_worker/service_worker_dispatcher_host.h" +#include "base/logging.h" #include "base/strings/utf_string_conversions.h" +#include "content/browser/message_port_message_filter.h" +#include "content/browser/message_port_service.h" #include "content/browser/service_worker/embedded_worker_registry.h" #include "content/browser/service_worker/service_worker_context_core.h" #include "content/browser/service_worker/service_worker_context_wrapper.h" -#include "content/browser/service_worker/service_worker_provider_host.h" -#include "content/common/service_worker_messages.h" +#include "content/browser/service_worker/service_worker_handle.h" +#include "content/browser/service_worker/service_worker_registration.h" +#include "content/browser/service_worker/service_worker_utils.h" +#include "content/common/service_worker/embedded_worker_messages.h" +#include "content/common/service_worker/service_worker_messages.h" #include "ipc/ipc_message_macros.h" #include "third_party/WebKit/public/platform/WebServiceWorkerError.h" #include "url/gurl.h" using blink::WebServiceWorkerError; +namespace content { + namespace { const char kDisabledErrorMessage[] = @@ -23,19 +31,43 @@ const char kDisabledErrorMessage[] = const char kDomainMismatchErrorMessage[] = "Scope and scripts do not have the same origin"; -} // namespace +const uint32 kFilteredMessageClasses[] = { + ServiceWorkerMsgStart, + EmbeddedWorkerMsgStart, +}; -namespace content { +bool CanRegisterServiceWorker(const GURL& document_url, + const GURL& pattern, + const GURL& script_url) { + // TODO: Respect Chrome's content settings, if we add a setting for + // controlling whether Service Worker is allowed. + return document_url.GetOrigin() == pattern.GetOrigin() && + document_url.GetOrigin() == script_url.GetOrigin(); +} + +bool CanUnregisterServiceWorker(const GURL& document_url, + const GURL& pattern) { + // TODO: Respect Chrome's content settings, if we add a setting for + // controlling whether Service Worker is allowed. + return document_url.GetOrigin() == pattern.GetOrigin(); +} + +} // namespace ServiceWorkerDispatcherHost::ServiceWorkerDispatcherHost( - int render_process_id) - : render_process_id_(render_process_id) { + int render_process_id, + MessagePortMessageFilter* message_port_message_filter) + : BrowserMessageFilter(kFilteredMessageClasses, + arraysize(kFilteredMessageClasses)), + render_process_id_(render_process_id), + message_port_message_filter_(message_port_message_filter), + channel_ready_(false) { } ServiceWorkerDispatcherHost::~ServiceWorkerDispatcherHost() { - if (context_) { - context_->RemoveAllProviderHostsForProcess(render_process_id_); - context_->embedded_worker_registry()->RemoveChildProcessSender( + if (GetContext()) { + GetContext()->RemoveAllProviderHostsForProcess(render_process_id_); + GetContext()->embedded_worker_registry()->RemoveChildProcessSender( render_process_id_); } } @@ -46,27 +78,32 @@ void ServiceWorkerDispatcherHost::Init( BrowserThread::PostTask( BrowserThread::IO, FROM_HERE, base::Bind(&ServiceWorkerDispatcherHost::Init, - this, make_scoped_refptr(context_wrapper))); - return; + this, make_scoped_refptr(context_wrapper))); + return; } - context_ = context_wrapper->context()->AsWeakPtr(); - context_->embedded_worker_registry()->AddChildProcessSender( + context_wrapper_ = context_wrapper; + GetContext()->embedded_worker_registry()->AddChildProcessSender( render_process_id_, this); } +void ServiceWorkerDispatcherHost::OnFilterAdded(IPC::Sender* sender) { + BrowserMessageFilter::OnFilterAdded(sender); + channel_ready_ = true; + std::vector<IPC::Message*> messages; + pending_messages_.release(&messages); + for (size_t i = 0; i < messages.size(); ++i) { + BrowserMessageFilter::Send(messages[i]); + } +} + void ServiceWorkerDispatcherHost::OnDestruct() const { BrowserThread::DeleteOnIOThread::Destruct(this); } bool ServiceWorkerDispatcherHost::OnMessageReceived( - const IPC::Message& message, - bool* message_was_ok) { - if (IPC_MESSAGE_CLASS(message) != ServiceWorkerMsgStart) - return false; - + const IPC::Message& message) { bool handled = true; - IPC_BEGIN_MESSAGE_MAP_EX( - ServiceWorkerDispatcherHost, message, *message_was_ok) + IPC_BEGIN_MESSAGE_MAP(ServiceWorkerDispatcherHost, message) IPC_MESSAGE_HANDLER(ServiceWorkerHostMsg_RegisterServiceWorker, OnRegisterServiceWorker) IPC_MESSAGE_HANDLER(ServiceWorkerHostMsg_UnregisterServiceWorker, @@ -75,41 +112,100 @@ bool ServiceWorkerDispatcherHost::OnMessageReceived( OnProviderCreated) IPC_MESSAGE_HANDLER(ServiceWorkerHostMsg_ProviderDestroyed, OnProviderDestroyed) + IPC_MESSAGE_HANDLER(ServiceWorkerHostMsg_SetVersionId, + OnSetHostedVersionId) + IPC_MESSAGE_HANDLER(ServiceWorkerHostMsg_PostMessageToWorker, + OnPostMessageToWorker) + IPC_MESSAGE_HANDLER(EmbeddedWorkerHostMsg_WorkerScriptLoaded, + OnWorkerScriptLoaded) + IPC_MESSAGE_HANDLER(EmbeddedWorkerHostMsg_WorkerScriptLoadFailed, + OnWorkerScriptLoadFailed) + IPC_MESSAGE_HANDLER(EmbeddedWorkerHostMsg_WorkerStarted, + OnWorkerStarted) + IPC_MESSAGE_HANDLER(EmbeddedWorkerHostMsg_WorkerStopped, + OnWorkerStopped) + IPC_MESSAGE_HANDLER(EmbeddedWorkerHostMsg_ReportException, + OnReportException) + IPC_MESSAGE_HANDLER(EmbeddedWorkerHostMsg_ReportConsoleMessage, + OnReportConsoleMessage) + IPC_MESSAGE_HANDLER(ServiceWorkerHostMsg_IncrementServiceWorkerRefCount, + OnIncrementServiceWorkerRefCount) + IPC_MESSAGE_HANDLER(ServiceWorkerHostMsg_DecrementServiceWorkerRefCount, + OnDecrementServiceWorkerRefCount) IPC_MESSAGE_UNHANDLED(handled = false) IPC_END_MESSAGE_MAP() + if (!handled && GetContext()) { + handled = + GetContext()->embedded_worker_registry()->OnMessageReceived(message); + if (!handled) + BadMessageReceived(); + } + return handled; } +bool ServiceWorkerDispatcherHost::Send(IPC::Message* message) { + if (channel_ready_) { + BrowserMessageFilter::Send(message); + // Don't bother passing through Send()'s result: it's not reliable. + return true; + } + + pending_messages_.push_back(message); + return true; +} + +void ServiceWorkerDispatcherHost::RegisterServiceWorkerHandle( + scoped_ptr<ServiceWorkerHandle> handle) { + int handle_id = handle->handle_id(); + handles_.AddWithID(handle.release(), handle_id); +} + void ServiceWorkerDispatcherHost::OnRegisterServiceWorker( - int32 thread_id, - int32 request_id, + int thread_id, + int request_id, + int provider_id, const GURL& pattern, const GURL& script_url) { - if (!context_ || !context_->IsEnabled()) { + if (!GetContext() || !ServiceWorkerUtils::IsFeatureEnabled()) { Send(new ServiceWorkerMsg_ServiceWorkerRegistrationError( thread_id, request_id, - WebServiceWorkerError::DisabledError, - ASCIIToUTF16(kDisabledErrorMessage))); + WebServiceWorkerError::ErrorTypeDisabled, + base::ASCIIToUTF16(kDisabledErrorMessage))); return; } - // TODO(alecflett): This check is insufficient for release. Add a - // ServiceWorker-specific policy query in - // ChildProcessSecurityImpl. See http://crbug.com/311631. - if (pattern.GetOrigin() != script_url.GetOrigin()) { + ServiceWorkerProviderHost* provider_host = GetContext()->GetProviderHost( + render_process_id_, provider_id); + if (!provider_host) { + BadMessageReceived(); + return; + } + if (!provider_host->IsContextAlive()) { Send(new ServiceWorkerMsg_ServiceWorkerRegistrationError( thread_id, request_id, - WebServiceWorkerError::SecurityError, - ASCIIToUTF16(kDomainMismatchErrorMessage))); + WebServiceWorkerError::ErrorTypeDisabled, + base::ASCIIToUTF16(kDisabledErrorMessage))); return; } - context_->RegisterServiceWorker( + if (!CanRegisterServiceWorker( + provider_host->document_url(), pattern, script_url)) { + Send(new ServiceWorkerMsg_ServiceWorkerRegistrationError( + thread_id, + request_id, + WebServiceWorkerError::ErrorTypeSecurity, + base::ASCIIToUTF16(kDomainMismatchErrorMessage))); + return; + } + GetContext()->RegisterServiceWorker( pattern, script_url, + render_process_id_, + provider_host, base::Bind(&ServiceWorkerDispatcherHost::RegistrationComplete, this, thread_id, @@ -117,22 +213,44 @@ void ServiceWorkerDispatcherHost::OnRegisterServiceWorker( } void ServiceWorkerDispatcherHost::OnUnregisterServiceWorker( - int32 thread_id, - int32 request_id, + int thread_id, + int request_id, + int provider_id, const GURL& pattern) { - // TODO(alecflett): This check is insufficient for release. Add a - // ServiceWorker-specific policy query in - // ChildProcessSecurityImpl. See http://crbug.com/311631. - if (!context_ || !context_->IsEnabled()) { + if (!GetContext() || !ServiceWorkerUtils::IsFeatureEnabled()) { Send(new ServiceWorkerMsg_ServiceWorkerRegistrationError( thread_id, request_id, - blink::WebServiceWorkerError::DisabledError, - ASCIIToUTF16(kDisabledErrorMessage))); + blink::WebServiceWorkerError::ErrorTypeDisabled, + base::ASCIIToUTF16(kDisabledErrorMessage))); return; } - context_->UnregisterServiceWorker( + ServiceWorkerProviderHost* provider_host = GetContext()->GetProviderHost( + render_process_id_, provider_id); + if (!provider_host) { + BadMessageReceived(); + return; + } + if (!provider_host->IsContextAlive()) { + Send(new ServiceWorkerMsg_ServiceWorkerRegistrationError( + thread_id, + request_id, + blink::WebServiceWorkerError::ErrorTypeDisabled, + base::ASCIIToUTF16(kDisabledErrorMessage))); + return; + } + + if (!CanUnregisterServiceWorker(provider_host->document_url(), pattern)) { + Send(new ServiceWorkerMsg_ServiceWorkerRegistrationError( + thread_id, + request_id, + WebServiceWorkerError::ErrorTypeSecurity, + base::ASCIIToUTF16(kDomainMismatchErrorMessage))); + return; + } + + GetContext()->UnregisterServiceWorker( pattern, base::Bind(&ServiceWorkerDispatcherHost::UnregistrationComplete, this, @@ -140,47 +258,183 @@ void ServiceWorkerDispatcherHost::OnUnregisterServiceWorker( request_id)); } +void ServiceWorkerDispatcherHost::OnPostMessageToWorker( + int handle_id, + const base::string16& message, + const std::vector<int>& sent_message_port_ids) { + if (!GetContext() || !ServiceWorkerUtils::IsFeatureEnabled()) + return; + + ServiceWorkerHandle* handle = handles_.Lookup(handle_id); + if (!handle) { + BadMessageReceived(); + return; + } + + std::vector<int> new_routing_ids; + message_port_message_filter_->UpdateMessagePortsWithNewRoutes( + sent_message_port_ids, &new_routing_ids); + handle->version()->SendMessage( + ServiceWorkerMsg_MessageToWorker(message, + sent_message_port_ids, + new_routing_ids), + base::Bind(&ServiceWorkerUtils::NoOpStatusCallback)); +} + void ServiceWorkerDispatcherHost::OnProviderCreated(int provider_id) { - if (!context_) + if (!GetContext()) return; - if (context_->GetProviderHost(render_process_id_, provider_id)) { + if (GetContext()->GetProviderHost(render_process_id_, provider_id)) { BadMessageReceived(); return; } scoped_ptr<ServiceWorkerProviderHost> provider_host( - new ServiceWorkerProviderHost(render_process_id_, provider_id)); - context_->AddProviderHost(provider_host.Pass()); + new ServiceWorkerProviderHost( + render_process_id_, provider_id, GetContext()->AsWeakPtr(), this)); + GetContext()->AddProviderHost(provider_host.Pass()); } void ServiceWorkerDispatcherHost::OnProviderDestroyed(int provider_id) { - if (!context_) + if (!GetContext()) return; - if (!context_->GetProviderHost(render_process_id_, provider_id)) { + if (!GetContext()->GetProviderHost(render_process_id_, provider_id)) { BadMessageReceived(); return; } - context_->RemoveProviderHost(render_process_id_, provider_id); + GetContext()->RemoveProviderHost(render_process_id_, provider_id); +} + +void ServiceWorkerDispatcherHost::OnSetHostedVersionId( + int provider_id, int64 version_id) { + if (!GetContext()) + return; + ServiceWorkerProviderHost* provider_host = + GetContext()->GetProviderHost(render_process_id_, provider_id); + if (!provider_host) { + BadMessageReceived(); + return; + } + if (!provider_host->IsContextAlive()) + return; + if (!provider_host->SetHostedVersionId(version_id)) + BadMessageReceived(); } void ServiceWorkerDispatcherHost::RegistrationComplete( - int32 thread_id, - int32 request_id, - ServiceWorkerRegistrationStatus status, - int64 registration_id) { - if (status != REGISTRATION_OK) { + int thread_id, + int request_id, + ServiceWorkerStatusCode status, + int64 registration_id, + int64 version_id) { + if (!GetContext()) + return; + + if (status != SERVICE_WORKER_OK) { SendRegistrationError(thread_id, request_id, status); return; } + ServiceWorkerVersion* version = GetContext()->GetLiveVersion(version_id); + DCHECK(version); + DCHECK_EQ(registration_id, version->registration_id()); + scoped_ptr<ServiceWorkerHandle> handle = + ServiceWorkerHandle::Create(GetContext()->AsWeakPtr(), + this, thread_id, version); Send(new ServiceWorkerMsg_ServiceWorkerRegistered( - thread_id, request_id, registration_id)); + thread_id, request_id, handle->GetObjectInfo())); + RegisterServiceWorkerHandle(handle.Pass()); +} + +// TODO(nhiroki): These message handlers that take |embedded_worker_id| as an +// input should check if the worker refers to the live context. If the context +// was deleted, handle the messege gracefully (http://crbug.com/371675). +void ServiceWorkerDispatcherHost::OnWorkerScriptLoaded(int embedded_worker_id) { + if (!GetContext()) + return; + GetContext()->embedded_worker_registry()->OnWorkerScriptLoaded( + render_process_id_, embedded_worker_id); +} + +void ServiceWorkerDispatcherHost::OnWorkerScriptLoadFailed( + int embedded_worker_id) { + if (!GetContext()) + return; + GetContext()->embedded_worker_registry()->OnWorkerScriptLoadFailed( + render_process_id_, embedded_worker_id); +} + +void ServiceWorkerDispatcherHost::OnWorkerStarted( + int thread_id, int embedded_worker_id) { + if (!GetContext()) + return; + GetContext()->embedded_worker_registry()->OnWorkerStarted( + render_process_id_, thread_id, embedded_worker_id); +} + +void ServiceWorkerDispatcherHost::OnWorkerStopped(int embedded_worker_id) { + if (!GetContext()) + return; + GetContext()->embedded_worker_registry()->OnWorkerStopped( + render_process_id_, embedded_worker_id); +} + +void ServiceWorkerDispatcherHost::OnReportException( + int embedded_worker_id, + const base::string16& error_message, + int line_number, + int column_number, + const GURL& source_url) { + if (!GetContext()) + return; + GetContext()->embedded_worker_registry()->OnReportException( + embedded_worker_id, + error_message, + line_number, + column_number, + source_url); +} + +void ServiceWorkerDispatcherHost::OnReportConsoleMessage( + int embedded_worker_id, + const EmbeddedWorkerHostMsg_ReportConsoleMessage_Params& params) { + if (!GetContext()) + return; + GetContext()->embedded_worker_registry()->OnReportConsoleMessage( + embedded_worker_id, + params.source_identifier, + params.message_level, + params.message, + params.line_number, + params.source_url); +} + +void ServiceWorkerDispatcherHost::OnIncrementServiceWorkerRefCount( + int handle_id) { + ServiceWorkerHandle* handle = handles_.Lookup(handle_id); + if (!handle) { + BadMessageReceived(); + return; + } + handle->IncrementRefCount(); +} + +void ServiceWorkerDispatcherHost::OnDecrementServiceWorkerRefCount( + int handle_id) { + ServiceWorkerHandle* handle = handles_.Lookup(handle_id); + if (!handle) { + BadMessageReceived(); + return; + } + handle->DecrementRefCount(); + if (handle->HasNoRefCount()) + handles_.Remove(handle_id); } void ServiceWorkerDispatcherHost::UnregistrationComplete( - int32 thread_id, - int32 request_id, - ServiceWorkerRegistrationStatus status) { - if (status != REGISTRATION_OK) { + int thread_id, + int request_id, + ServiceWorkerStatusCode status) { + if (status != SERVICE_WORKER_OK) { SendRegistrationError(thread_id, request_id, status); return; } @@ -189,9 +443,9 @@ void ServiceWorkerDispatcherHost::UnregistrationComplete( } void ServiceWorkerDispatcherHost::SendRegistrationError( - int32 thread_id, - int32 request_id, - ServiceWorkerRegistrationStatus status) { + int thread_id, + int request_id, + ServiceWorkerStatusCode status) { base::string16 error_message; blink::WebServiceWorkerError::ErrorType error_type; GetServiceWorkerRegistrationStatusResponse( @@ -200,4 +454,8 @@ void ServiceWorkerDispatcherHost::SendRegistrationError( thread_id, request_id, error_type, error_message)); } +ServiceWorkerContextCore* ServiceWorkerDispatcherHost::GetContext() { + return context_wrapper_->context(); +} + } // namespace content diff --git a/chromium/content/browser/service_worker/service_worker_dispatcher_host.h b/chromium/content/browser/service_worker/service_worker_dispatcher_host.h index be008018ec5..b269311497b 100644 --- a/chromium/content/browser/service_worker/service_worker_dispatcher_host.h +++ b/chromium/content/browser/service_worker/service_worker_dispatcher_host.h @@ -5,28 +5,50 @@ #ifndef CONTENT_BROWSER_SERVICE_WORKER_SERVICE_WORKER_DISPATCHER_HOST_H_ #define CONTENT_BROWSER_SERVICE_WORKER_SERVICE_WORKER_DISPATCHER_HOST_H_ +#include "base/id_map.h" #include "base/memory/weak_ptr.h" +#include "base/strings/string16.h" #include "content/browser/service_worker/service_worker_registration_status.h" #include "content/public/browser/browser_message_filter.h" class GURL; +struct EmbeddedWorkerHostMsg_ReportConsoleMessage_Params; namespace content { +class MessagePortMessageFilter; class ServiceWorkerContextCore; class ServiceWorkerContextWrapper; +class ServiceWorkerHandle; class ServiceWorkerProviderHost; +class ServiceWorkerRegistration; class CONTENT_EXPORT ServiceWorkerDispatcherHost : public BrowserMessageFilter { public: - explicit ServiceWorkerDispatcherHost(int render_process_id); + ServiceWorkerDispatcherHost( + int render_process_id, + MessagePortMessageFilter* message_port_message_filter); void Init(ServiceWorkerContextWrapper* context_wrapper); - // BrowserIOMessageFilter implementation + // BrowserMessageFilter implementation + virtual void OnFilterAdded(IPC::Sender* sender) OVERRIDE; virtual void OnDestruct() const OVERRIDE; - virtual bool OnMessageReceived(const IPC::Message& message, - bool* message_was_ok) OVERRIDE; + virtual bool OnMessageReceived(const IPC::Message& message) OVERRIDE; + + // IPC::Sender implementation + + // Send() queues the message until the underlying sender is ready. This + // class assumes that Send() can only fail after that when the renderer + // process has terminated, at which point the whole instance will eventually + // be destroyed. + virtual bool Send(IPC::Message* message) OVERRIDE; + + void RegisterServiceWorkerHandle(scoped_ptr<ServiceWorkerHandle> handle); + + MessagePortMessageFilter* message_port_message_filter() { + return message_port_message_filter_; + } protected: virtual ~ServiceWorkerDispatcherHost(); @@ -37,31 +59,68 @@ class CONTENT_EXPORT ServiceWorkerDispatcherHost : public BrowserMessageFilter { friend class TestingServiceWorkerDispatcherHost; // IPC Message handlers - void OnRegisterServiceWorker(int32 thread_id, - int32 request_id, + void OnRegisterServiceWorker(int thread_id, + int request_id, + int provider_id, const GURL& pattern, const GURL& script_url); - void OnUnregisterServiceWorker(int32 thread_id, - int32 request_id, + void OnUnregisterServiceWorker(int thread_id, + int request_id, + int provider_id, const GURL& pattern); void OnProviderCreated(int provider_id); void OnProviderDestroyed(int provider_id); + void OnSetHostedVersionId(int provider_id, int64 version_id); + void OnWorkerScriptLoaded(int embedded_worker_id); + void OnWorkerScriptLoadFailed(int embedded_worker_id); + void OnWorkerStarted(int thread_id, + int embedded_worker_id); + void OnWorkerStopped(int embedded_worker_id); + void OnReportException(int embedded_worker_id, + const base::string16& error_message, + int line_number, + int column_number, + const GURL& source_url); + void OnReportConsoleMessage( + int embedded_worker_id, + const EmbeddedWorkerHostMsg_ReportConsoleMessage_Params& params); + void OnPostMessage(int handle_id, + const base::string16& message, + const std::vector<int>& sent_message_port_ids); + void OnIncrementServiceWorkerRefCount(int handle_id); + void OnDecrementServiceWorkerRefCount(int handle_id); + void OnPostMessageToWorker(int handle_id, + const base::string16& message, + const std::vector<int>& sent_message_port_ids); + void OnServiceWorkerObjectDestroyed(int handle_id); // Callbacks from ServiceWorkerContextCore - void RegistrationComplete(int32 thread_id, - int32 request_id, - ServiceWorkerRegistrationStatus status, - int64 registration_id); - - void UnregistrationComplete(int32 thread_id, - int32 request_id, - ServiceWorkerRegistrationStatus status); - - void SendRegistrationError(int32 thread_id, - int32 request_id, - ServiceWorkerRegistrationStatus status); + void RegistrationComplete(int thread_id, + int request_id, + ServiceWorkerStatusCode status, + int64 registration_id, + int64 version_id); + + void UnregistrationComplete(int thread_id, + int request_id, + ServiceWorkerStatusCode status); + + void SendRegistrationError(int thread_id, + int request_id, + ServiceWorkerStatusCode status); + + ServiceWorkerContextCore* GetContext(); + int render_process_id_; - base::WeakPtr<ServiceWorkerContextCore> context_; + MessagePortMessageFilter* const message_port_message_filter_; + scoped_refptr<ServiceWorkerContextWrapper> context_wrapper_; + + IDMap<ServiceWorkerHandle, IDMapOwnPointer> handles_; + + bool channel_ready_; // True after BrowserMessageFilter::sender_ != NULL. + ScopedVector<IPC::Message> pending_messages_; + + DISALLOW_COPY_AND_ASSIGN(ServiceWorkerDispatcherHost); }; } // namespace content diff --git a/chromium/content/browser/service_worker/service_worker_dispatcher_host_unittest.cc b/chromium/content/browser/service_worker/service_worker_dispatcher_host_unittest.cc index da9e83c8ed8..079c8de65fb 100644 --- a/chromium/content/browser/service_worker/service_worker_dispatcher_host_unittest.cc +++ b/chromium/content/browser/service_worker/service_worker_dispatcher_host_unittest.cc @@ -8,108 +8,178 @@ #include "base/files/file_path.h" #include "base/run_loop.h" #include "content/browser/browser_thread_impl.h" +#include "content/browser/service_worker/embedded_worker_instance.h" +#include "content/browser/service_worker/embedded_worker_registry.h" +#include "content/browser/service_worker/embedded_worker_test_helper.h" #include "content/browser/service_worker/service_worker_context_core.h" #include "content/browser/service_worker/service_worker_context_wrapper.h" -#include "content/common/service_worker_messages.h" +#include "content/common/service_worker/embedded_worker_messages.h" +#include "content/common/service_worker/service_worker_messages.h" #include "content/public/common/content_switches.h" #include "content/public/test/test_browser_thread_bundle.h" #include "testing/gtest/include/gtest/gtest.h" namespace content { -class ServiceWorkerDispatcherHostTest : public testing::Test { - protected: - ServiceWorkerDispatcherHostTest() - : browser_thread_bundle_(TestBrowserThreadBundle::IO_MAINLOOP) {} - - virtual void SetUp() { - context_wrapper_ = new ServiceWorkerContextWrapper; - context_wrapper_->Init(base::FilePath(), NULL); - } - - virtual void TearDown() { - if (context_wrapper_) { - context_wrapper_->Shutdown(); - context_wrapper_ = NULL; - } - } - - ServiceWorkerContextCore* context() { return context_wrapper_->context(); } - - TestBrowserThreadBundle browser_thread_bundle_; - scoped_refptr<ServiceWorkerContextWrapper> context_wrapper_; -}; - static const int kRenderProcessId = 1; class TestingServiceWorkerDispatcherHost : public ServiceWorkerDispatcherHost { public: TestingServiceWorkerDispatcherHost( int process_id, - ServiceWorkerContextWrapper* context_wrapper) - : ServiceWorkerDispatcherHost(process_id), - bad_messages_received_count_(0) { + ServiceWorkerContextWrapper* context_wrapper, + EmbeddedWorkerTestHelper* helper) + : ServiceWorkerDispatcherHost(process_id, NULL), + bad_messages_received_count_(0), + helper_(helper) { Init(context_wrapper); } virtual bool Send(IPC::Message* message) OVERRIDE { - sent_messages_.push_back(message); - return true; + return helper_->Send(message); } + IPC::TestSink* ipc_sink() { return helper_->ipc_sink(); } + virtual void BadMessageReceived() OVERRIDE { ++bad_messages_received_count_; } - ScopedVector<IPC::Message> sent_messages_; int bad_messages_received_count_; protected: + EmbeddedWorkerTestHelper* helper_; virtual ~TestingServiceWorkerDispatcherHost() {} }; +class ServiceWorkerDispatcherHostTest : public testing::Test { + protected: + ServiceWorkerDispatcherHostTest() + : browser_thread_bundle_(TestBrowserThreadBundle::IO_MAINLOOP) {} + + virtual void SetUp() { + helper_.reset(new EmbeddedWorkerTestHelper(kRenderProcessId)); + dispatcher_host_ = new TestingServiceWorkerDispatcherHost( + kRenderProcessId, context_wrapper(), helper_.get()); + } + + virtual void TearDown() { + helper_.reset(); + } + + ServiceWorkerContextCore* context() { return helper_->context(); } + ServiceWorkerContextWrapper* context_wrapper() { + return helper_->context_wrapper(); + } + + void Register(int64 provider_id, + GURL pattern, + GURL worker_url, + uint32 expected_message) { + dispatcher_host_->OnMessageReceived( + ServiceWorkerHostMsg_RegisterServiceWorker( + -1, -1, provider_id, pattern, worker_url)); + base::RunLoop().RunUntilIdle(); + EXPECT_TRUE(dispatcher_host_->ipc_sink()->GetUniqueMessageMatching( + expected_message)); + dispatcher_host_->ipc_sink()->ClearMessages(); + } + + void Unregister(int64 provider_id, GURL pattern, uint32 expected_message) { + dispatcher_host_->OnMessageReceived( + ServiceWorkerHostMsg_UnregisterServiceWorker( + -1, -1, provider_id, pattern)); + base::RunLoop().RunUntilIdle(); + EXPECT_TRUE(dispatcher_host_->ipc_sink()->GetUniqueMessageMatching( + expected_message)); + dispatcher_host_->ipc_sink()->ClearMessages(); + } + + TestBrowserThreadBundle browser_thread_bundle_; + scoped_ptr<EmbeddedWorkerTestHelper> helper_; + scoped_refptr<TestingServiceWorkerDispatcherHost> dispatcher_host_; +}; + TEST_F(ServiceWorkerDispatcherHostTest, DisabledCausesError) { DCHECK(!CommandLine::ForCurrentProcess()->HasSwitch( switches::kEnableServiceWorker)); - scoped_refptr<TestingServiceWorkerDispatcherHost> dispatcher_host = - new TestingServiceWorkerDispatcherHost(kRenderProcessId, - context_wrapper_.get()); - - bool handled; - dispatcher_host->OnMessageReceived( - ServiceWorkerHostMsg_RegisterServiceWorker(-1, -1, GURL(), GURL()), - &handled); - EXPECT_TRUE(handled); + dispatcher_host_->OnMessageReceived( + ServiceWorkerHostMsg_RegisterServiceWorker(-1, -1, -1, GURL(), GURL())); // TODO(alecflett): Pump the message loop when this becomes async. - ASSERT_EQ(1UL, dispatcher_host->sent_messages_.size()); - EXPECT_EQ( - static_cast<uint32>(ServiceWorkerMsg_ServiceWorkerRegistrationError::ID), - dispatcher_host->sent_messages_[0]->type()); + ASSERT_EQ(1UL, dispatcher_host_->ipc_sink()->message_count()); + EXPECT_TRUE(dispatcher_host_->ipc_sink()->GetUniqueMessageMatching( + ServiceWorkerMsg_ServiceWorkerRegistrationError::ID)); } -TEST_F(ServiceWorkerDispatcherHostTest, Enabled) { +// TODO(falken): Enable this test when we remove the +// --enable-service-worker-flag (see crbug.com/352581) +TEST_F(ServiceWorkerDispatcherHostTest, DISABLED_RegisterSameOrigin) { + const int64 kProviderId = 99; // Dummy value + scoped_ptr<ServiceWorkerProviderHost> host(new ServiceWorkerProviderHost( + kRenderProcessId, kProviderId, context()->AsWeakPtr(), NULL)); + host->SetDocumentUrl(GURL("http://www.example.com/foo")); + base::WeakPtr<ServiceWorkerProviderHost> provider_host = host->AsWeakPtr(); + context()->AddProviderHost(host.Pass()); + + Register(kProviderId, + GURL("http://www.example.com/*"), + GURL("http://foo.example.com/bar"), + ServiceWorkerMsg_ServiceWorkerRegistrationError::ID); + Register(kProviderId, + GURL("http://foo.example.com/*"), + GURL("http://www.example.com/bar"), + ServiceWorkerMsg_ServiceWorkerRegistrationError::ID); + Register(kProviderId, + GURL("http://foo.example.com/*"), + GURL("http://foo.example.com/bar"), + ServiceWorkerMsg_ServiceWorkerRegistrationError::ID); + Register(kProviderId, + GURL("http://www.example.com/*"), + GURL("http://www.example.com/bar"), + ServiceWorkerMsg_ServiceWorkerRegistered::ID); +} + +// TODO(falken): Enable this test when we remove the +// --enable-service-worker-flag (see crbug.com/352581) +TEST_F(ServiceWorkerDispatcherHostTest, DISABLED_UnregisterSameOrigin) { + const int64 kProviderId = 99; // Dummy value + scoped_ptr<ServiceWorkerProviderHost> host(new ServiceWorkerProviderHost( + kRenderProcessId, kProviderId, context()->AsWeakPtr(), NULL)); + host->SetDocumentUrl(GURL("http://www.example.com/foo")); + base::WeakPtr<ServiceWorkerProviderHost> provider_host = host->AsWeakPtr(); + context()->AddProviderHost(host.Pass()); + + Unregister(kProviderId, + GURL("http://foo.example.com/*"), + ServiceWorkerMsg_ServiceWorkerRegistrationError::ID); + Unregister(kProviderId, + GURL("http://www.example.com/*"), + ServiceWorkerMsg_ServiceWorkerUnregistered::ID); +} + +// Disable this since now we cache command-line switch in +// ServiceWorkerUtils::IsFeatureEnabled() and this could be flaky depending +// on testing order. (crbug.com/352581) +// TODO(kinuko): Just remove DisabledCausesError test above and enable +// this test when we remove the --enable-service-worker flag. +TEST_F(ServiceWorkerDispatcherHostTest, DISABLED_Enabled) { DCHECK(!CommandLine::ForCurrentProcess()->HasSwitch( switches::kEnableServiceWorker)); CommandLine::ForCurrentProcess()->AppendSwitch( switches::kEnableServiceWorker); - scoped_refptr<TestingServiceWorkerDispatcherHost> dispatcher_host = - new TestingServiceWorkerDispatcherHost(kRenderProcessId, - context_wrapper_.get()); - - bool handled; - dispatcher_host->OnMessageReceived( - ServiceWorkerHostMsg_RegisterServiceWorker(-1, -1, GURL(), GURL()), - &handled); - EXPECT_TRUE(handled); + dispatcher_host_->OnMessageReceived( + ServiceWorkerHostMsg_RegisterServiceWorker(-1, -1, -1, GURL(), GURL())); base::RunLoop().RunUntilIdle(); // TODO(alecflett): Pump the message loop when this becomes async. - ASSERT_EQ(1UL, dispatcher_host->sent_messages_.size()); - EXPECT_EQ(static_cast<uint32>(ServiceWorkerMsg_ServiceWorkerRegistered::ID), - dispatcher_host->sent_messages_[0]->type()); + ASSERT_EQ(2UL, dispatcher_host_->ipc_sink()->message_count()); + EXPECT_TRUE(dispatcher_host_->ipc_sink()->GetUniqueMessageMatching( + EmbeddedWorkerMsg_StartWorker::ID)); + EXPECT_TRUE(dispatcher_host_->ipc_sink()->GetUniqueMessageMatching( + ServiceWorkerMsg_ServiceWorkerRegistered::ID)); } TEST_F(ServiceWorkerDispatcherHostTest, EarlyContextDeletion) { @@ -118,72 +188,48 @@ TEST_F(ServiceWorkerDispatcherHostTest, EarlyContextDeletion) { CommandLine::ForCurrentProcess()->AppendSwitch( switches::kEnableServiceWorker); - scoped_refptr<TestingServiceWorkerDispatcherHost> dispatcher_host = - new TestingServiceWorkerDispatcherHost(kRenderProcessId, - context_wrapper_.get()); + helper_->ShutdownContext(); - context_wrapper_->Shutdown(); - context_wrapper_ = NULL; + // Let the shutdown reach the simulated IO thread. + base::RunLoop().RunUntilIdle(); - bool handled; - dispatcher_host->OnMessageReceived( - ServiceWorkerHostMsg_RegisterServiceWorker(-1, -1, GURL(), GURL()), - &handled); - EXPECT_TRUE(handled); + dispatcher_host_->OnMessageReceived( + ServiceWorkerHostMsg_RegisterServiceWorker(-1, -1, -1, GURL(), GURL())); // TODO(alecflett): Pump the message loop when this becomes async. - ASSERT_EQ(1UL, dispatcher_host->sent_messages_.size()); - EXPECT_EQ( - static_cast<uint32>(ServiceWorkerMsg_ServiceWorkerRegistrationError::ID), - dispatcher_host->sent_messages_[0]->type()); + ASSERT_EQ(1UL, dispatcher_host_->ipc_sink()->message_count()); + EXPECT_TRUE(dispatcher_host_->ipc_sink()->GetUniqueMessageMatching( + ServiceWorkerMsg_ServiceWorkerRegistrationError::ID)); } TEST_F(ServiceWorkerDispatcherHostTest, ProviderCreatedAndDestroyed) { - scoped_refptr<TestingServiceWorkerDispatcherHost> dispatcher_host = - new TestingServiceWorkerDispatcherHost(kRenderProcessId, - context_wrapper_.get()); - const int kProviderId = 1001; // Test with a value != kRenderProcessId. - bool handled = false; - dispatcher_host->OnMessageReceived( - ServiceWorkerHostMsg_ProviderCreated(kProviderId), - &handled); - EXPECT_TRUE(handled); + dispatcher_host_->OnMessageReceived( + ServiceWorkerHostMsg_ProviderCreated(kProviderId)); EXPECT_TRUE(context()->GetProviderHost(kRenderProcessId, kProviderId)); // Two with the same ID should be seen as a bad message. - handled = false; - dispatcher_host->OnMessageReceived( - ServiceWorkerHostMsg_ProviderCreated(kProviderId), - &handled); - EXPECT_TRUE(handled); - EXPECT_EQ(1, dispatcher_host->bad_messages_received_count_); - - handled = false; - dispatcher_host->OnMessageReceived( - ServiceWorkerHostMsg_ProviderDestroyed(kProviderId), - &handled); - EXPECT_TRUE(handled); + dispatcher_host_->OnMessageReceived( + ServiceWorkerHostMsg_ProviderCreated(kProviderId)); + EXPECT_EQ(1, dispatcher_host_->bad_messages_received_count_); + + dispatcher_host_->OnMessageReceived( + ServiceWorkerHostMsg_ProviderDestroyed(kProviderId)); EXPECT_FALSE(context()->GetProviderHost(kRenderProcessId, kProviderId)); // Destroying an ID that does not exist warrants a bad message. - handled = false; - dispatcher_host->OnMessageReceived( - ServiceWorkerHostMsg_ProviderDestroyed(kProviderId), - &handled); - EXPECT_TRUE(handled); - EXPECT_EQ(2, dispatcher_host->bad_messages_received_count_); + dispatcher_host_->OnMessageReceived( + ServiceWorkerHostMsg_ProviderDestroyed(kProviderId)); + EXPECT_EQ(2, dispatcher_host_->bad_messages_received_count_); // Deletion of the dispatcher_host should cause providers for that // process to get deleted as well. - dispatcher_host->OnMessageReceived( - ServiceWorkerHostMsg_ProviderCreated(kProviderId), - &handled); - EXPECT_TRUE(handled); + dispatcher_host_->OnMessageReceived( + ServiceWorkerHostMsg_ProviderCreated(kProviderId)); EXPECT_TRUE(context()->GetProviderHost(kRenderProcessId, kProviderId)); - EXPECT_TRUE(dispatcher_host->HasOneRef()); - dispatcher_host = NULL; + EXPECT_TRUE(dispatcher_host_->HasOneRef()); + dispatcher_host_ = NULL; EXPECT_FALSE(context()->GetProviderHost(kRenderProcessId, kProviderId)); } diff --git a/chromium/content/browser/service_worker/service_worker_fetch_dispatcher.cc b/chromium/content/browser/service_worker/service_worker_fetch_dispatcher.cc new file mode 100644 index 00000000000..81be9aff2a7 --- /dev/null +++ b/chromium/content/browser/service_worker/service_worker_fetch_dispatcher.cc @@ -0,0 +1,78 @@ +// Copyright 2014 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 "content/browser/service_worker/service_worker_fetch_dispatcher.h" + +#include "base/bind.h" +#include "content/browser/service_worker/service_worker_version.h" +#include "net/url_request/url_request.h" + +namespace content { + +ServiceWorkerFetchDispatcher::ServiceWorkerFetchDispatcher( + net::URLRequest* request, + ServiceWorkerVersion* version, + const FetchCallback& callback) + : version_(version), + callback_(callback), + weak_factory_(this) { + request_.url = request->url(); + request_.method = request->method(); + const net::HttpRequestHeaders& headers = request->extra_request_headers(); + for (net::HttpRequestHeaders::Iterator it(headers); it.GetNext();) + request_.headers[it.name()] = it.value(); +} + +ServiceWorkerFetchDispatcher::~ServiceWorkerFetchDispatcher() {} + +void ServiceWorkerFetchDispatcher::Run() { + DCHECK(version_->status() == ServiceWorkerVersion::ACTIVATING || + version_->status() == ServiceWorkerVersion::ACTIVE) + << version_->status(); + + if (version_->status() == ServiceWorkerVersion::ACTIVATING) { + version_->RegisterStatusChangeCallback( + base::Bind(&ServiceWorkerFetchDispatcher::DidWaitActivation, + weak_factory_.GetWeakPtr())); + return; + } + DispatchFetchEvent(); +} + +void ServiceWorkerFetchDispatcher::DidWaitActivation() { + if (version_->status() != ServiceWorkerVersion::ACTIVE) { + DCHECK_EQ(ServiceWorkerVersion::INSTALLED, version_->status()); + DidFailActivation(); + return; + } + DispatchFetchEvent(); +} + +void ServiceWorkerFetchDispatcher::DidFailActivation() { + // The previous activation seems to have failed, abort the step + // with activate error. (The error should be separately reported + // to the associated documents and association must be dropped + // at this point) + DidFinish(SERVICE_WORKER_ERROR_ACTIVATE_WORKER_FAILED, + SERVICE_WORKER_FETCH_EVENT_RESULT_FALLBACK, + ServiceWorkerResponse()); +} + +void ServiceWorkerFetchDispatcher::DispatchFetchEvent() { + version_->DispatchFetchEvent( + request_, + base::Bind(&ServiceWorkerFetchDispatcher::DidFinish, + weak_factory_.GetWeakPtr())); +} + +void ServiceWorkerFetchDispatcher::DidFinish( + ServiceWorkerStatusCode status, + ServiceWorkerFetchEventResult fetch_result, + const ServiceWorkerResponse& response) { + DCHECK(!callback_.is_null()); + FetchCallback callback = callback_; + callback.Run(status, fetch_result, response); +} + +} // namespace content diff --git a/chromium/content/browser/service_worker/service_worker_fetch_dispatcher.h b/chromium/content/browser/service_worker/service_worker_fetch_dispatcher.h new file mode 100644 index 00000000000..9e595ab0870 --- /dev/null +++ b/chromium/content/browser/service_worker/service_worker_fetch_dispatcher.h @@ -0,0 +1,57 @@ +// Copyright 2014 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 CONTENT_BROWSER_SERVICE_WORKER_SERVICE_WORKER_FETCH_DISPATCHER_H_ +#define CONTENT_BROWSER_SERVICE_WORKER_SERVICE_WORKER_FETCH_DISPATCHER_H_ + +#include "base/basictypes.h" +#include "base/callback.h" +#include "base/memory/weak_ptr.h" +#include "content/common/service_worker/service_worker_status_code.h" +#include "content/common/service_worker/service_worker_types.h" + +namespace net { +class URLRequest; +} + +namespace content { + +class ServiceWorkerVersion; + +// A helper class to dispatch fetch event to a service worker. +class ServiceWorkerFetchDispatcher { + public: + typedef base::Callback<void(ServiceWorkerStatusCode, + ServiceWorkerFetchEventResult, + const ServiceWorkerResponse&)> FetchCallback; + + ServiceWorkerFetchDispatcher( + net::URLRequest* request, + ServiceWorkerVersion* version, + const FetchCallback& callback); + ~ServiceWorkerFetchDispatcher(); + + // Dispatches a fetch event to the |version| given in ctor, and fires + // |callback| (also given in ctor) when finishes. + void Run(); + + private: + void DidWaitActivation(); + void DidFailActivation(); + void DispatchFetchEvent(); + void DidFinish(ServiceWorkerStatusCode status, + ServiceWorkerFetchEventResult fetch_result, + const ServiceWorkerResponse& response); + + scoped_refptr<ServiceWorkerVersion> version_; + FetchCallback callback_; + ServiceWorkerFetchRequest request_; + base::WeakPtrFactory<ServiceWorkerFetchDispatcher> weak_factory_; + + DISALLOW_COPY_AND_ASSIGN(ServiceWorkerFetchDispatcher); +}; + +} // namespace content + +#endif // CONTENT_BROWSER_SERVICE_WORKER_SERVICE_WORKER_FETCH_DISPATCHER_H_ diff --git a/chromium/content/browser/service_worker/service_worker_handle.cc b/chromium/content/browser/service_worker/service_worker_handle.cc new file mode 100644 index 00000000000..a07ab9e9d91 --- /dev/null +++ b/chromium/content/browser/service_worker/service_worker_handle.cc @@ -0,0 +1,126 @@ +// Copyright 2014 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 "content/browser/service_worker/service_worker_handle.h" + +#include "content/browser/service_worker/service_worker_context_core.h" +#include "content/browser/service_worker/service_worker_registration.h" +#include "content/common/service_worker/service_worker_messages.h" +#include "ipc/ipc_sender.h" + +namespace content { + +namespace { + +blink::WebServiceWorkerState +GetWebServiceWorkerState(ServiceWorkerVersion* version) { + DCHECK(version); + switch (version->status()) { + case ServiceWorkerVersion::NEW: + if (version->running_status() == ServiceWorkerVersion::RUNNING) + return blink::WebServiceWorkerStateParsed; + else + return blink::WebServiceWorkerStateUnknown; + case ServiceWorkerVersion::INSTALLING: + return blink::WebServiceWorkerStateInstalling; + case ServiceWorkerVersion::INSTALLED: + return blink::WebServiceWorkerStateInstalled; + case ServiceWorkerVersion::ACTIVATING: + return blink::WebServiceWorkerStateActivating; + case ServiceWorkerVersion::ACTIVE: + return blink::WebServiceWorkerStateActive; + case ServiceWorkerVersion::DEACTIVATED: + return blink::WebServiceWorkerStateDeactivated; + } + NOTREACHED() << version->status(); + return blink::WebServiceWorkerStateUnknown; +} + +} // namespace + +scoped_ptr<ServiceWorkerHandle> ServiceWorkerHandle::Create( + base::WeakPtr<ServiceWorkerContextCore> context, + IPC::Sender* sender, + int thread_id, + ServiceWorkerVersion* version) { + if (!context || !version) + return scoped_ptr<ServiceWorkerHandle>(); + ServiceWorkerRegistration* registration = + context->GetLiveRegistration(version->registration_id()); + return make_scoped_ptr( + new ServiceWorkerHandle(context, sender, thread_id, + registration, version)); +} + +ServiceWorkerHandle::ServiceWorkerHandle( + base::WeakPtr<ServiceWorkerContextCore> context, + IPC::Sender* sender, + int thread_id, + ServiceWorkerRegistration* registration, + ServiceWorkerVersion* version) + : context_(context), + sender_(sender), + thread_id_(thread_id), + handle_id_(context.get() ? context->GetNewServiceWorkerHandleId() : -1), + ref_count_(1), + registration_(registration), + version_(version) { + version_->AddListener(this); +} + +ServiceWorkerHandle::~ServiceWorkerHandle() { + version_->RemoveListener(this); + // TODO(kinuko): At this point we can discard the registration if + // all documents/handles that have a reference to the registration is + // closed or freed up, but could also keep it alive in cache + // (e.g. in context_) for a while with some timer so that we don't + // need to re-load the same registration from disk over and over. +} + +void ServiceWorkerHandle::OnWorkerStarted(ServiceWorkerVersion* version) { +} + +void ServiceWorkerHandle::OnWorkerStopped(ServiceWorkerVersion* version) { +} + +void ServiceWorkerHandle::OnErrorReported(ServiceWorkerVersion* version, + const base::string16& error_message, + int line_number, + int column_number, + const GURL& source_url) { +} + +void ServiceWorkerHandle::OnReportConsoleMessage(ServiceWorkerVersion* version, + int source_identifier, + int message_level, + const base::string16& message, + int line_number, + const GURL& source_url) { +} + +void ServiceWorkerHandle::OnVersionStateChanged(ServiceWorkerVersion* version) { + sender_->Send(new ServiceWorkerMsg_ServiceWorkerStateChanged( + thread_id_, handle_id_, GetWebServiceWorkerState(version))); +} + +ServiceWorkerObjectInfo ServiceWorkerHandle::GetObjectInfo() { + ServiceWorkerObjectInfo info; + info.handle_id = handle_id_; + info.scope = registration_->pattern(); + info.url = registration_->script_url(); + info.state = GetWebServiceWorkerState(version_); + return info; +} + +void ServiceWorkerHandle::IncrementRefCount() { + DCHECK_GT(ref_count_, 0); + ++ref_count_; +} + +void ServiceWorkerHandle::DecrementRefCount() { + DCHECK_GE(ref_count_, 0); + --ref_count_; +} + +} // namespace content diff --git a/chromium/content/browser/service_worker/service_worker_handle.h b/chromium/content/browser/service_worker/service_worker_handle.h new file mode 100644 index 00000000000..81d5b3574a2 --- /dev/null +++ b/chromium/content/browser/service_worker/service_worker_handle.h @@ -0,0 +1,92 @@ +// Copyright 2014 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 CONTENT_BROWSER_SERVICE_WORKER_SERVICE_WORKER_HANDLE_H_ +#define CONTENT_BROWSER_SERVICE_WORKER_SERVICE_WORKER_HANDLE_H_ + +#include "base/basictypes.h" +#include "base/memory/ref_counted.h" +#include "base/memory/scoped_ptr.h" +#include "base/memory/weak_ptr.h" +#include "content/browser/service_worker/service_worker_version.h" +#include "content/common/content_export.h" +#include "content/common/service_worker/service_worker_types.h" + +namespace IPC { +class Sender; +} + +namespace content { + +class ServiceWorkerContextCore; +class ServiceWorkerRegistration; + +// Roughly corresponds to one ServiceWorker object in the renderer process +// (WebServiceWorkerImpl). +// Has references to the corresponding ServiceWorkerVersion and +// ServiceWorkerRegistration (therefore they're guaranteed to be alive while +// this handle is around). +class CONTENT_EXPORT ServiceWorkerHandle + : public ServiceWorkerVersion::Listener { + public: + // Creates a handle for a live version. The version's corresponding + // registration must be also alive. + // This may return NULL if |context|.get() or |version| is NULL. + // |sender| and |thread_id| will be used to send messages to the + // corresponding WebServiceWorkerImpl (which should live on |thread_id| + // in the child process). + static scoped_ptr<ServiceWorkerHandle> Create( + base::WeakPtr<ServiceWorkerContextCore> context, + IPC::Sender* sender, + int thread_id, + ServiceWorkerVersion* version); + + ServiceWorkerHandle(base::WeakPtr<ServiceWorkerContextCore> context, + IPC::Sender* sender, + int thread_id, + ServiceWorkerRegistration* registration, + ServiceWorkerVersion* version); + virtual ~ServiceWorkerHandle(); + + // ServiceWorkerVersion::Listener overrides. + virtual void OnWorkerStarted(ServiceWorkerVersion* version) OVERRIDE; + virtual void OnWorkerStopped(ServiceWorkerVersion* version) OVERRIDE; + virtual void OnErrorReported(ServiceWorkerVersion* version, + const base::string16& error_message, + int line_number, + int column_number, + const GURL& source_url) OVERRIDE; + virtual void OnReportConsoleMessage(ServiceWorkerVersion* version, + int source_identifier, + int message_level, + const base::string16& message, + int line_number, + const GURL& source_url) OVERRIDE; + virtual void OnVersionStateChanged(ServiceWorkerVersion* version) OVERRIDE; + + ServiceWorkerObjectInfo GetObjectInfo(); + + ServiceWorkerRegistration* registration() { return registration_.get(); } + ServiceWorkerVersion* version() { return version_.get(); } + int handle_id() const { return handle_id_; } + + bool HasNoRefCount() const { return ref_count_ <= 0; } + void IncrementRefCount(); + void DecrementRefCount(); + + private: + base::WeakPtr<ServiceWorkerContextCore> context_; + IPC::Sender* sender_; // Not owned, it should always outlive this. + const int thread_id_; + const int handle_id_; + int ref_count_; // Created with 1. + scoped_refptr<ServiceWorkerRegistration> registration_; + scoped_refptr<ServiceWorkerVersion> version_; + + DISALLOW_COPY_AND_ASSIGN(ServiceWorkerHandle); +}; + +} // namespace content + +#endif // CONTENT_BROWSER_SERVICE_WORKER_SERVICE_WORKER_HANDLE_H_ diff --git a/chromium/content/browser/service_worker/service_worker_handle_unittest.cc b/chromium/content/browser/service_worker/service_worker_handle_unittest.cc new file mode 100644 index 00000000000..70c445613c9 --- /dev/null +++ b/chromium/content/browser/service_worker/service_worker_handle_unittest.cc @@ -0,0 +1,114 @@ +// Copyright 2014 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 "base/basictypes.h" +#include "base/run_loop.h" +#include "content/browser/service_worker/embedded_worker_registry.h" +#include "content/browser/service_worker/embedded_worker_test_helper.h" +#include "content/browser/service_worker/service_worker_context_core.h" +#include "content/browser/service_worker/service_worker_handle.h" +#include "content/browser/service_worker/service_worker_registration.h" +#include "content/browser/service_worker/service_worker_test_utils.h" +#include "content/browser/service_worker/service_worker_version.h" +#include "content/common/service_worker/embedded_worker_messages.h" +#include "content/common/service_worker/service_worker_messages.h" +#include "content/public/test/test_browser_thread_bundle.h" +#include "ipc/ipc_message.h" +#include "ipc/ipc_test_sink.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "third_party/WebKit/public/platform/WebServiceWorkerState.h" + +namespace content { + +namespace { + +const int kRenderProcessId = 88; // A dummy ID for testing. + +void VerifyStateChangedMessage(int expected_handle_id, + blink::WebServiceWorkerState expected_state, + const IPC::Message* message) { + ASSERT_TRUE(message != NULL); + ServiceWorkerMsg_ServiceWorkerStateChanged::Param param; + ASSERT_TRUE(ServiceWorkerMsg_ServiceWorkerStateChanged::Read( + message, ¶m)); + EXPECT_EQ(expected_handle_id, param.b); + EXPECT_EQ(expected_state, param.c); +} + +} // namespace + +class ServiceWorkerHandleTest : public testing::Test { + public: + ServiceWorkerHandleTest() + : browser_thread_bundle_(TestBrowserThreadBundle::IO_MAINLOOP) {} + + virtual void SetUp() OVERRIDE { + helper_.reset(new EmbeddedWorkerTestHelper(kRenderProcessId)); + + registration_ = new ServiceWorkerRegistration( + GURL("http://www.example.com/*"), + GURL("http://www.example.com/service_worker.js"), + 1L, + helper_->context()->AsWeakPtr()); + version_ = new ServiceWorkerVersion( + registration_, 1L, helper_->context()->AsWeakPtr()); + + // Simulate adding one process to the worker. + int embedded_worker_id = version_->embedded_worker()->embedded_worker_id(); + helper_->SimulateAddProcessToWorker(embedded_worker_id, kRenderProcessId); + } + + virtual void TearDown() OVERRIDE { + registration_ = NULL; + version_ = NULL; + helper_.reset(); + } + + IPC::TestSink* ipc_sink() { return helper_->ipc_sink(); } + + TestBrowserThreadBundle browser_thread_bundle_; + scoped_ptr<EmbeddedWorkerTestHelper> helper_; + scoped_refptr<ServiceWorkerRegistration> registration_; + scoped_refptr<ServiceWorkerVersion> version_; + DISALLOW_COPY_AND_ASSIGN(ServiceWorkerHandleTest); +}; + +TEST_F(ServiceWorkerHandleTest, OnVersionStateChanged) { + scoped_ptr<ServiceWorkerHandle> handle = + ServiceWorkerHandle::Create(helper_->context()->AsWeakPtr(), + helper_.get(), + 1 /* thread_id */, + version_); + + // Start the worker, and then... + ServiceWorkerStatusCode status = SERVICE_WORKER_ERROR_FAILED; + version_->StartWorker(CreateReceiverOnCurrentThread(&status)); + base::RunLoop().RunUntilIdle(); + EXPECT_EQ(SERVICE_WORKER_OK, status); + + // ...dispatch install event. + status = SERVICE_WORKER_ERROR_FAILED; + version_->DispatchInstallEvent(-1, CreateReceiverOnCurrentThread(&status)); + base::RunLoop().RunUntilIdle(); + EXPECT_EQ(SERVICE_WORKER_OK, status); + + ASSERT_EQ(4UL, ipc_sink()->message_count()); + + // We should be sending 1. StartWorker, + EXPECT_EQ(EmbeddedWorkerMsg_StartWorker::ID, + ipc_sink()->GetMessageAt(0)->type()); + // 2. StateChanged (state == Installing), + VerifyStateChangedMessage(handle->handle_id(), + blink::WebServiceWorkerStateInstalling, + ipc_sink()->GetMessageAt(1)); + // 3. SendMessageToWorker (to send InstallEvent), and + EXPECT_EQ(EmbeddedWorkerContextMsg_MessageToWorker::ID, + ipc_sink()->GetMessageAt(2)->type()); + // 4. StateChanged (state == Installed). + VerifyStateChangedMessage(handle->handle_id(), + blink::WebServiceWorkerStateInstalled, + ipc_sink()->GetMessageAt(3)); +} + +} // namespace content diff --git a/chromium/content/browser/service_worker/service_worker_histograms.cc b/chromium/content/browser/service_worker/service_worker_histograms.cc new file mode 100644 index 00000000000..8d1df4bef4b --- /dev/null +++ b/chromium/content/browser/service_worker/service_worker_histograms.cc @@ -0,0 +1,30 @@ +// Copyright 2014 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 "content/browser/service_worker/service_worker_histograms.h" + +#include "base/metrics/histogram.h" + +namespace content { + +// static +void ServiceWorkerHistograms::CountInitDiskCacheResult(bool result) { + UMA_HISTOGRAM_BOOLEAN("ServiceWorker.DiskCache.InitResult", result); +} + +// static +void ServiceWorkerHistograms::CountReadResponseResult( + ServiceWorkerHistograms::ReadResponseResult result) { + UMA_HISTOGRAM_ENUMERATION("ServiceWorker.DiskCache.ReadResponseResult", + result, NUM_READ_RESPONSE_RESULT_TYPES); +} + +// static +void ServiceWorkerHistograms::CountWriteResponseResult( + ServiceWorkerHistograms::WriteResponseResult result) { + UMA_HISTOGRAM_ENUMERATION("ServiceWorker.DiskCache.WriteResponseResult", + result, NUM_WRITE_RESPONSE_RESULT_TYPES); +} + +} // namespace content diff --git a/chromium/content/browser/service_worker/service_worker_histograms.h b/chromium/content/browser/service_worker/service_worker_histograms.h new file mode 100644 index 00000000000..a9ad8c6ef06 --- /dev/null +++ b/chromium/content/browser/service_worker/service_worker_histograms.h @@ -0,0 +1,38 @@ +// Copyright 2014 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 CONTENT_BROWSER_SERVICE_WORKER_SERVICE_WORKER_HISTOGRAMS_H_ +#define CONTENT_BROWSER_SERVICE_WORKER_SERVICE_WORKER_HISTOGRAMS_H_ + +#include "base/macros.h" + +namespace content { + +class ServiceWorkerHistograms { + public: + enum ReadResponseResult { + READ_OK, + READ_HEADERS_ERROR, + READ_DATA_ERROR, + NUM_READ_RESPONSE_RESULT_TYPES, + }; + + enum WriteResponseResult { + WRITE_OK, + WRITE_HEADERS_ERROR, + WRITE_DATA_ERROR, + NUM_WRITE_RESPONSE_RESULT_TYPES, + }; + + static void CountInitDiskCacheResult(bool result); + static void CountReadResponseResult(ReadResponseResult result); + static void CountWriteResponseResult(WriteResponseResult result); + + private: + DISALLOW_IMPLICIT_CONSTRUCTORS(ServiceWorkerHistograms); +}; + +} // namespace content + +#endif // CONTENT_BROWSER_SERVICE_WORKER_SERVICE_WORKER_HISTOGRAMS_H_ diff --git a/chromium/content/browser/service_worker/service_worker_info.cc b/chromium/content/browser/service_worker/service_worker_info.cc new file mode 100644 index 00000000000..8933340c2ff --- /dev/null +++ b/chromium/content/browser/service_worker/service_worker_info.cc @@ -0,0 +1,57 @@ +// Copyright 2014 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 "content/browser/service_worker/service_worker_info.h" + +#include "content/common/service_worker/service_worker_types.h" +#include "ipc/ipc_message.h" + +namespace content { + +ServiceWorkerVersionInfo::ServiceWorkerVersionInfo() + : is_null(true), + running_status(ServiceWorkerVersion::STOPPED), + status(ServiceWorkerVersion::NEW), + version_id(kInvalidServiceWorkerVersionId), + process_id(-1), + thread_id(-1), + devtools_agent_route_id(MSG_ROUTING_NONE) { +} + +ServiceWorkerVersionInfo::ServiceWorkerVersionInfo( + ServiceWorkerVersion::RunningStatus running_status, + ServiceWorkerVersion::Status status, + int64 version_id, + int process_id, + int thread_id, + int devtools_agent_route_id) + : is_null(false), + running_status(running_status), + status(status), + version_id(version_id), + process_id(process_id), + thread_id(thread_id), + devtools_agent_route_id(devtools_agent_route_id) { +} + +ServiceWorkerVersionInfo::~ServiceWorkerVersionInfo() {} + +ServiceWorkerRegistrationInfo::ServiceWorkerRegistrationInfo() {} + +ServiceWorkerRegistrationInfo::ServiceWorkerRegistrationInfo( + const GURL& script_url, + const GURL& pattern, + int64 registration_id, + const ServiceWorkerVersionInfo& active_version, + const ServiceWorkerVersionInfo& waiting_version) + : script_url(script_url), + pattern(pattern), + registration_id(registration_id), + active_version(active_version), + waiting_version(waiting_version) { +} + +ServiceWorkerRegistrationInfo::~ServiceWorkerRegistrationInfo() {} + +} // namespace content diff --git a/chromium/content/browser/service_worker/service_worker_info.h b/chromium/content/browser/service_worker/service_worker_info.h new file mode 100644 index 00000000000..99e374696b0 --- /dev/null +++ b/chromium/content/browser/service_worker/service_worker_info.h @@ -0,0 +1,56 @@ +// Copyright 2014 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 CONTENT_BROWSER_SERVICE_WORKER_SERVICE_WORKER_INFO_H_ +#define CONTENT_BROWSER_SERVICE_WORKER_SERVICE_WORKER_INFO_H_ + +#include <vector> + +#include "content/browser/service_worker/service_worker_version.h" +#include "content/common/content_export.h" +#include "url/gurl.h" + +namespace content { + +class CONTENT_EXPORT ServiceWorkerVersionInfo { + public: + ServiceWorkerVersionInfo(); + ServiceWorkerVersionInfo(ServiceWorkerVersion::RunningStatus running_status, + ServiceWorkerVersion::Status status, + int64 version_id, + int process_id, + int thread_id, + int devtools_agent_route_id); + ~ServiceWorkerVersionInfo(); + + bool is_null; + ServiceWorkerVersion::RunningStatus running_status; + ServiceWorkerVersion::Status status; + int64 version_id; + int process_id; + int thread_id; + int devtools_agent_route_id; +}; + +class CONTENT_EXPORT ServiceWorkerRegistrationInfo { + public: + ServiceWorkerRegistrationInfo(); + ServiceWorkerRegistrationInfo( + const GURL& script_url, + const GURL& pattern, + int64 registration_id, + const ServiceWorkerVersionInfo& active_version, + const ServiceWorkerVersionInfo& waiting_version); + ~ServiceWorkerRegistrationInfo(); + + GURL script_url; + GURL pattern; + int64 registration_id; + ServiceWorkerVersionInfo active_version; + ServiceWorkerVersionInfo waiting_version; +}; + +} // namespace content + +#endif // CONTENT_BROWSER_SERVICE_WORKER_SERVICE_WORKER_INFO_H_ diff --git a/chromium/content/browser/service_worker/service_worker_internals_ui.cc b/chromium/content/browser/service_worker/service_worker_internals_ui.cc new file mode 100644 index 00000000000..e976ad82884 --- /dev/null +++ b/chromium/content/browser/service_worker/service_worker_internals_ui.cc @@ -0,0 +1,675 @@ +// Copyright 2014 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 "content/browser/service_worker/service_worker_internals_ui.h" + +#include <string> +#include <vector> + +#include "base/bind.h" +#include "base/memory/scoped_vector.h" +#include "base/strings/string_number_conversions.h" +#include "base/values.h" +#include "content/browser/devtools/devtools_manager_impl.h" +#include "content/browser/devtools/embedded_worker_devtools_manager.h" +#include "content/browser/service_worker/service_worker_context_observer.h" +#include "content/browser/service_worker/service_worker_context_wrapper.h" +#include "content/browser/service_worker/service_worker_registration.h" +#include "content/browser/service_worker/service_worker_version.h" +#include "content/public/browser/browser_context.h" +#include "content/public/browser/browser_thread.h" +#include "content/public/browser/storage_partition.h" +#include "content/public/browser/web_contents.h" +#include "content/public/browser/web_ui.h" +#include "content/public/browser/web_ui_data_source.h" +#include "content/public/common/url_constants.h" +#include "grit/content_resources.h" + +using base::DictionaryValue; +using base::FundamentalValue; +using base::ListValue; +using base::StringValue; +using base::Value; +using base::WeakPtr; + +namespace content { + +namespace { + +void OperationCompleteCallback(WeakPtr<ServiceWorkerInternalsUI> internals, + int callback_id, + ServiceWorkerStatusCode status) { + if (!BrowserThread::CurrentlyOn(BrowserThread::UI)) { + BrowserThread::PostTask( + BrowserThread::UI, + FROM_HERE, + base::Bind(OperationCompleteCallback, internals, callback_id, status)); + return; + } + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); + if (internals) { + internals->web_ui()->CallJavascriptFunction( + "serviceworker.onOperationComplete", + FundamentalValue(static_cast<int>(status)), + FundamentalValue(callback_id)); + } +} + +void CallServiceWorkerVersionMethodWithVersionID( + ServiceWorkerInternalsUI::ServiceWorkerVersionMethod method, + scoped_refptr<ServiceWorkerContextWrapper> context, + int64 version_id, + const ServiceWorkerInternalsUI::StatusCallback& callback) { + if (!BrowserThread::CurrentlyOn(BrowserThread::IO)) { + BrowserThread::PostTask( + BrowserThread::IO, + FROM_HERE, + base::Bind(CallServiceWorkerVersionMethodWithVersionID, + method, + context, + version_id, + callback)); + return; + } + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); + scoped_refptr<ServiceWorkerVersion> version = + context->context()->GetLiveVersion(version_id); + if (!version) { + callback.Run(SERVICE_WORKER_ERROR_NOT_FOUND); + return; + } + (*version.*method)(callback); +} + +void DispatchPushEventWithVersionID( + scoped_refptr<ServiceWorkerContextWrapper> context, + int64 version_id, + const ServiceWorkerInternalsUI::StatusCallback& callback) { + if (!BrowserThread::CurrentlyOn(BrowserThread::IO)) { + BrowserThread::PostTask( + BrowserThread::IO, + FROM_HERE, + base::Bind(DispatchPushEventWithVersionID, + context, + version_id, + callback)); + return; + } + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); + scoped_refptr<ServiceWorkerVersion> version = + context->context()->GetLiveVersion(version_id); + if (!version) { + callback.Run(SERVICE_WORKER_ERROR_NOT_FOUND); + return; + } + std::string data = "Test push message from ServiceWorkerInternals."; + version->DispatchPushEvent(callback, data); +} + +void UnregisterWithScope( + scoped_refptr<ServiceWorkerContextWrapper> context, + const GURL& scope, + const ServiceWorkerInternalsUI::StatusCallback& callback) { + if (!BrowserThread::CurrentlyOn(BrowserThread::IO)) { + BrowserThread::PostTask( + BrowserThread::IO, + FROM_HERE, + base::Bind(UnregisterWithScope, context, scope, callback)); + return; + } + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); + context->context()->UnregisterServiceWorker(scope, callback); +} + +void WorkerStarted(const scoped_refptr<ServiceWorkerRegistration>& registration, + const ServiceWorkerInternalsUI::StatusCallback& callback, + ServiceWorkerStatusCode status) { + callback.Run(status); +} + +void StartActiveWorker( + const ServiceWorkerInternalsUI::StatusCallback& callback, + ServiceWorkerStatusCode status, + const scoped_refptr<ServiceWorkerRegistration>& registration) { + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); + if (status == SERVICE_WORKER_OK) { + // Pass the reference of |registration| to WorkerStarted callback to prevent + // it from being deleted while starting the worker. If the refcount of + // |registration| is 1, it will be deleted after WorkerStarted is called. + registration->active_version()->StartWorker( + base::Bind(WorkerStarted, registration, callback)); + return; + } + callback.Run(SERVICE_WORKER_ERROR_NOT_FOUND); +} + +void FindRegistrationForPattern( + scoped_refptr<ServiceWorkerContextWrapper> context, + const GURL& scope, + const ServiceWorkerStorage::FindRegistrationCallback callback) { + if (!BrowserThread::CurrentlyOn(BrowserThread::IO)) { + BrowserThread::PostTask( + BrowserThread::IO, + FROM_HERE, + base::Bind(FindRegistrationForPattern, context, scope, callback)); + return; + } + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); + context->context()->storage()->FindRegistrationForPattern(scope, callback); +} + +void UpdateVersionInfo(const ServiceWorkerVersionInfo& version, + DictionaryValue* info) { + switch (version.running_status) { + case ServiceWorkerVersion::STOPPED: + info->SetString("running_status", "STOPPED"); + break; + case ServiceWorkerVersion::STARTING: + info->SetString("running_status", "STARTING"); + break; + case ServiceWorkerVersion::RUNNING: + info->SetString("running_status", "RUNNING"); + break; + case ServiceWorkerVersion::STOPPING: + info->SetString("running_status", "STOPPING"); + break; + } + + switch (version.status) { + case ServiceWorkerVersion::NEW: + info->SetString("status", "NEW"); + break; + case ServiceWorkerVersion::INSTALLING: + info->SetString("status", "INSTALLING"); + break; + case ServiceWorkerVersion::INSTALLED: + info->SetString("status", "INSTALLED"); + break; + case ServiceWorkerVersion::ACTIVATING: + info->SetString("status", "ACTIVATING"); + break; + case ServiceWorkerVersion::ACTIVE: + info->SetString("status", "ACTIVE"); + break; + case ServiceWorkerVersion::DEACTIVATED: + info->SetString("status", "DEACTIVATED"); + break; + } + info->SetString("version_id", base::Int64ToString(version.version_id)); + info->SetInteger("process_id", version.process_id); + info->SetInteger("thread_id", version.thread_id); + info->SetInteger("devtools_agent_route_id", version.devtools_agent_route_id); +} + +ListValue* GetRegistrationListValue( + const std::vector<ServiceWorkerRegistrationInfo>& registrations) { + ListValue* result = new ListValue(); + for (std::vector<ServiceWorkerRegistrationInfo>::const_iterator it = + registrations.begin(); + it != registrations.end(); + ++it) { + const ServiceWorkerRegistrationInfo& registration = *it; + DictionaryValue* registration_info = new DictionaryValue(); + registration_info->SetString("scope", registration.pattern.spec()); + registration_info->SetString("script_url", registration.script_url.spec()); + registration_info->SetString( + "registration_id", base::Int64ToString(registration.registration_id)); + + if (!registration.active_version.is_null) { + DictionaryValue* active_info = new DictionaryValue(); + UpdateVersionInfo(registration.active_version, active_info); + registration_info->Set("active", active_info); + } + + if (!registration.waiting_version.is_null) { + DictionaryValue* waiting_info = new DictionaryValue(); + UpdateVersionInfo(registration.waiting_version, waiting_info); + registration_info->Set("waiting", waiting_info); + } + + result->Append(registration_info); + } + return result; +} + +ListValue* GetVersionListValue( + const std::vector<ServiceWorkerVersionInfo>& versions) { + ListValue* result = new ListValue(); + for (std::vector<ServiceWorkerVersionInfo>::const_iterator it = + versions.begin(); + it != versions.end(); + ++it) { + DictionaryValue* info = new DictionaryValue(); + UpdateVersionInfo(*it, info); + result->Append(info); + } + return result; +} + +void GetRegistrationsOnIOThread( + scoped_refptr<ServiceWorkerContextWrapper> context, + base::Callback<void(const std::vector<ServiceWorkerRegistrationInfo>&)> + callback) { + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); + context->context()->storage()->GetAllRegistrations(callback); +} + +void OnStoredRegistrations( + scoped_refptr<ServiceWorkerContextWrapper> context, + base::Callback<void(const std::vector<ServiceWorkerRegistrationInfo>&, + const std::vector<ServiceWorkerVersionInfo>&, + const std::vector<ServiceWorkerRegistrationInfo>&)> + callback, + const std::vector<ServiceWorkerRegistrationInfo>& stored_registrations) { + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); + BrowserThread::PostTask( + BrowserThread::UI, + FROM_HERE, + base::Bind(callback, + context->context()->GetAllLiveRegistrationInfo(), + context->context()->GetAllLiveVersionInfo(), + stored_registrations)); +} + +void OnAllRegistrations( + WeakPtr<ServiceWorkerInternalsUI> internals, + int partition_id, + const base::FilePath& context_path, + const std::vector<ServiceWorkerRegistrationInfo>& live_registrations, + const std::vector<ServiceWorkerVersionInfo>& live_versions, + const std::vector<ServiceWorkerRegistrationInfo>& stored_registrations) { + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); + if (!internals) + return; + + ScopedVector<const Value> args; + args.push_back(GetRegistrationListValue(live_registrations)); + args.push_back(GetVersionListValue(live_versions)); + args.push_back(GetRegistrationListValue(stored_registrations)); + args.push_back(new FundamentalValue(partition_id)); + args.push_back(new StringValue(context_path.value())); + internals->web_ui()->CallJavascriptFunction("serviceworker.onPartitionData", + args.get()); +} + +} // namespace + +class ServiceWorkerInternalsUI::PartitionObserver + : public ServiceWorkerContextObserver { + public: + PartitionObserver(int partition_id, WebUI* web_ui) + : partition_id_(partition_id), web_ui_(web_ui) {} + virtual ~PartitionObserver() {} + // ServiceWorkerContextObserver overrides: + virtual void OnWorkerStarted(int64 version_id, + int process_id, + int thread_id) OVERRIDE { + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); + web_ui_->CallJavascriptFunction( + "serviceworker.onWorkerStarted", + FundamentalValue(partition_id_), + StringValue(base::Int64ToString(version_id)), + FundamentalValue(process_id), + FundamentalValue(thread_id)); + } + virtual void OnWorkerStopped(int64 version_id, + int process_id, + int thread_id) OVERRIDE { + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); + web_ui_->CallJavascriptFunction( + "serviceworker.onWorkerStopped", + FundamentalValue(partition_id_), + StringValue(base::Int64ToString(version_id)), + FundamentalValue(process_id), + FundamentalValue(thread_id)); + } + virtual void OnVersionStateChanged(int64 version_id) OVERRIDE { + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); + web_ui_->CallJavascriptFunction( + "serviceworker.onVersionStateChanged", + FundamentalValue(partition_id_), + StringValue(base::Int64ToString(version_id))); + } + virtual void OnErrorReported(int64 version_id, + int process_id, + int thread_id, + const ErrorInfo& info) OVERRIDE { + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); + ScopedVector<const Value> args; + args.push_back(new FundamentalValue(partition_id_)); + args.push_back(new StringValue(base::Int64ToString(version_id))); + args.push_back(new FundamentalValue(process_id)); + args.push_back(new FundamentalValue(thread_id)); + scoped_ptr<DictionaryValue> value(new DictionaryValue()); + value->SetString("message", info.error_message); + value->SetInteger("lineNumber", info.line_number); + value->SetInteger("columnNumber", info.column_number); + value->SetString("sourceURL", info.source_url.spec()); + args.push_back(value.release()); + web_ui_->CallJavascriptFunction("serviceworker.onErrorReported", + args.get()); + } + virtual void OnReportConsoleMessage(int64 version_id, + int process_id, + int thread_id, + const ConsoleMessage& message) OVERRIDE { + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); + ScopedVector<const Value> args; + args.push_back(new FundamentalValue(partition_id_)); + args.push_back(new StringValue(base::Int64ToString(version_id))); + args.push_back(new FundamentalValue(process_id)); + args.push_back(new FundamentalValue(thread_id)); + scoped_ptr<DictionaryValue> value(new DictionaryValue()); + value->SetInteger("sourceIdentifier", message.source_identifier); + value->SetInteger("message_level", message.message_level); + value->SetString("message", message.message); + value->SetInteger("lineNumber", message.line_number); + value->SetString("sourceURL", message.source_url.spec()); + args.push_back(value.release()); + web_ui_->CallJavascriptFunction("serviceworker.onConsoleMessageReported", + args.get()); + } + virtual void OnRegistrationStored(const GURL& pattern) OVERRIDE { + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); + web_ui_->CallJavascriptFunction("serviceworker.onRegistrationStored", + StringValue(pattern.spec())); + } + virtual void OnRegistrationDeleted(const GURL& pattern) OVERRIDE { + web_ui_->CallJavascriptFunction("serviceworker.onRegistrationDeleted", + StringValue(pattern.spec())); + } + int partition_id() const { return partition_id_; } + + private: + const int partition_id_; + WebUI* const web_ui_; +}; + +ServiceWorkerInternalsUI::ServiceWorkerInternalsUI(WebUI* web_ui) + : WebUIController(web_ui), next_partition_id_(0) { + WebUIDataSource* source = + WebUIDataSource::Create(kChromeUIServiceWorkerInternalsHost); + source->SetUseJsonJSFormatV2(); + source->SetJsonPath("strings.js"); + source->AddResourcePath("serviceworker_internals.js", + IDR_SERVICE_WORKER_INTERNALS_JS); + source->AddResourcePath("serviceworker_internals.css", + IDR_SERVICE_WORKER_INTERNALS_CSS); + source->SetDefaultResource(IDR_SERVICE_WORKER_INTERNALS_HTML); + source->DisableDenyXFrameOptions(); + + BrowserContext* browser_context = + web_ui->GetWebContents()->GetBrowserContext(); + WebUIDataSource::Add(browser_context, source); + + web_ui->RegisterMessageCallback( + "GetOptions", + base::Bind(&ServiceWorkerInternalsUI::GetOptions, + base::Unretained(this))); + web_ui->RegisterMessageCallback( + "SetOption", + base::Bind(&ServiceWorkerInternalsUI::SetOption, base::Unretained(this))); + web_ui->RegisterMessageCallback( + "getAllRegistrations", + base::Bind(&ServiceWorkerInternalsUI::GetAllRegistrations, + base::Unretained(this))); + web_ui->RegisterMessageCallback( + "stop", + base::Bind(&ServiceWorkerInternalsUI::CallServiceWorkerVersionMethod, + base::Unretained(this), + &ServiceWorkerVersion::StopWorker)); + web_ui->RegisterMessageCallback( + "sync", + base::Bind(&ServiceWorkerInternalsUI::CallServiceWorkerVersionMethod, + base::Unretained(this), + &ServiceWorkerVersion::DispatchSyncEvent)); + web_ui->RegisterMessageCallback( + "push", + base::Bind(&ServiceWorkerInternalsUI::DispatchPushEvent, + base::Unretained(this))); + web_ui->RegisterMessageCallback( + "inspect", + base::Bind(&ServiceWorkerInternalsUI::InspectWorker, + base::Unretained(this))); + web_ui->RegisterMessageCallback( + "unregister", + base::Bind(&ServiceWorkerInternalsUI::Unregister, + base::Unretained(this))); + web_ui->RegisterMessageCallback( + "start", + base::Bind(&ServiceWorkerInternalsUI::StartWorker, + base::Unretained(this))); +} + +ServiceWorkerInternalsUI::~ServiceWorkerInternalsUI() { + BrowserContext* browser_context = + web_ui()->GetWebContents()->GetBrowserContext(); + // Safe to use base::Unretained(this) because + // ForEachStoragePartition is synchronous. + BrowserContext::StoragePartitionCallback remove_observer_cb = + base::Bind(&ServiceWorkerInternalsUI::RemoveObserverFromStoragePartition, + base::Unretained(this)); + BrowserContext::ForEachStoragePartition(browser_context, remove_observer_cb); +} + +void ServiceWorkerInternalsUI::GetOptions(const ListValue* args) { + DictionaryValue options; + options.SetBoolean("debug_on_start", + EmbeddedWorkerDevToolsManager::GetInstance() + ->debug_service_worker_on_start()); + web_ui()->CallJavascriptFunction("serviceworker.onOptions", options); +} + +void ServiceWorkerInternalsUI::SetOption(const ListValue* args) { + std::string option_name; + bool option_boolean; + if (!args->GetString(0, &option_name) || option_name != "debug_on_start" || + !args->GetBoolean(1, &option_boolean)) { + return; + } + EmbeddedWorkerDevToolsManager::GetInstance() + ->set_debug_service_worker_on_start(option_boolean); +} + +void ServiceWorkerInternalsUI::GetAllRegistrations(const ListValue* args) { + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); + BrowserContext* browser_context = + web_ui()->GetWebContents()->GetBrowserContext(); + // Safe to use base::Unretained(this) because + // ForEachStoragePartition is synchronous. + BrowserContext::StoragePartitionCallback add_context_cb = + base::Bind(&ServiceWorkerInternalsUI::AddContextFromStoragePartition, + base::Unretained(this)); + BrowserContext::ForEachStoragePartition(browser_context, add_context_cb); +} + +void ServiceWorkerInternalsUI::AddContextFromStoragePartition( + StoragePartition* partition) { + int partition_id = 0; + scoped_refptr<ServiceWorkerContextWrapper> context = + static_cast<ServiceWorkerContextWrapper*>( + partition->GetServiceWorkerContext()); + if (PartitionObserver* observer = + observers_.get(reinterpret_cast<uintptr_t>(partition))) { + partition_id = observer->partition_id(); + } else { + partition_id = next_partition_id_++; + scoped_ptr<PartitionObserver> new_observer( + new PartitionObserver(partition_id, web_ui())); + context->AddObserver(new_observer.get()); + observers_.set(reinterpret_cast<uintptr_t>(partition), new_observer.Pass()); + } + BrowserThread::PostTask( + BrowserThread::IO, + FROM_HERE, + base::Bind(GetRegistrationsOnIOThread, + context, + base::Bind(OnStoredRegistrations, + context, + base::Bind(OnAllRegistrations, + AsWeakPtr(), + partition_id, + partition->GetPath())))); +} + +void ServiceWorkerInternalsUI::RemoveObserverFromStoragePartition( + StoragePartition* partition) { + scoped_ptr<PartitionObserver> observer( + observers_.take_and_erase(reinterpret_cast<uintptr_t>(partition))); + if (!observer.get()) + return; + scoped_refptr<ServiceWorkerContextWrapper> context = + static_cast<ServiceWorkerContextWrapper*>( + partition->GetServiceWorkerContext()); + context->RemoveObserver(observer.get()); +} + +void ServiceWorkerInternalsUI::FindContext( + int partition_id, + StoragePartition** result_partition, + StoragePartition* storage_partition) const { + PartitionObserver* observer = + observers_.get(reinterpret_cast<uintptr_t>(storage_partition)); + if (observer && partition_id == observer->partition_id()) { + *result_partition = storage_partition; + } +} + +bool ServiceWorkerInternalsUI::GetServiceWorkerContext( + int partition_id, + scoped_refptr<ServiceWorkerContextWrapper>* context) const { + BrowserContext* browser_context = + web_ui()->GetWebContents()->GetBrowserContext(); + StoragePartition* result_partition(NULL); + BrowserContext::StoragePartitionCallback find_context_cb = + base::Bind(&ServiceWorkerInternalsUI::FindContext, + base::Unretained(this), + partition_id, + &result_partition); + BrowserContext::ForEachStoragePartition(browser_context, find_context_cb); + if (!result_partition) + return false; + *context = static_cast<ServiceWorkerContextWrapper*>( + result_partition->GetServiceWorkerContext()); + return true; +} + +void ServiceWorkerInternalsUI::CallServiceWorkerVersionMethod( + ServiceWorkerVersionMethod method, + const ListValue* args) { + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); + int callback_id; + int partition_id; + int64 version_id; + std::string version_id_string; + const DictionaryValue* cmd_args = NULL; + scoped_refptr<ServiceWorkerContextWrapper> context; + if (!args->GetInteger(0, &callback_id) || + !args->GetDictionary(1, &cmd_args) || + !cmd_args->GetInteger("partition_id", &partition_id) || + !GetServiceWorkerContext(partition_id, &context) || + !cmd_args->GetString("version_id", &version_id_string) || + !base::StringToInt64(version_id_string, &version_id)) { + return; + } + + base::Callback<void(ServiceWorkerStatusCode)> callback = + base::Bind(OperationCompleteCallback, AsWeakPtr(), callback_id); + CallServiceWorkerVersionMethodWithVersionID( + method, context, version_id, callback); +} + +void ServiceWorkerInternalsUI::DispatchPushEvent( + const ListValue* args) { + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); + int callback_id; + int partition_id; + int64 version_id; + std::string version_id_string; + const DictionaryValue* cmd_args = NULL; + scoped_refptr<ServiceWorkerContextWrapper> context; + if (!args->GetInteger(0, &callback_id) || + !args->GetDictionary(1, &cmd_args) || + !cmd_args->GetInteger("partition_id", &partition_id) || + !GetServiceWorkerContext(partition_id, &context) || + !cmd_args->GetString("version_id", &version_id_string) || + !base::StringToInt64(version_id_string, &version_id)) { + return; + } + + base::Callback<void(ServiceWorkerStatusCode)> callback = + base::Bind(OperationCompleteCallback, AsWeakPtr(), callback_id); + DispatchPushEventWithVersionID(context, version_id, callback); +} + +void ServiceWorkerInternalsUI::InspectWorker(const ListValue* args) { + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); + int callback_id; + int process_id; + int devtools_agent_route_id; + const DictionaryValue* cmd_args = NULL; + scoped_refptr<ServiceWorkerContextWrapper> context; + if (!args->GetInteger(0, &callback_id) || + !args->GetDictionary(1, &cmd_args) || + !cmd_args->GetInteger("process_id", &process_id) || + !cmd_args->GetInteger("devtools_agent_route_id", + &devtools_agent_route_id)) { + return; + } + base::Callback<void(ServiceWorkerStatusCode)> callback = + base::Bind(OperationCompleteCallback, AsWeakPtr(), callback_id); + scoped_refptr<DevToolsAgentHost> agent_host( + EmbeddedWorkerDevToolsManager::GetInstance() + ->GetDevToolsAgentHostForWorker(process_id, devtools_agent_route_id)); + if (!agent_host) { + callback.Run(SERVICE_WORKER_ERROR_NOT_FOUND); + return; + } + DevToolsManagerImpl::GetInstance()->Inspect( + web_ui()->GetWebContents()->GetBrowserContext(), agent_host.get()); + callback.Run(SERVICE_WORKER_OK); +} + +void ServiceWorkerInternalsUI::Unregister(const ListValue* args) { + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); + int callback_id; + int partition_id; + std::string scope_string; + const DictionaryValue* cmd_args = NULL; + scoped_refptr<ServiceWorkerContextWrapper> context; + if (!args->GetInteger(0, &callback_id) || + !args->GetDictionary(1, &cmd_args) || + !cmd_args->GetInteger("partition_id", &partition_id) || + !GetServiceWorkerContext(partition_id, &context) || + !cmd_args->GetString("scope", &scope_string)) { + return; + } + + base::Callback<void(ServiceWorkerStatusCode)> callback = + base::Bind(OperationCompleteCallback, AsWeakPtr(), callback_id); + UnregisterWithScope(context, GURL(scope_string), callback); +} + +void ServiceWorkerInternalsUI::StartWorker(const ListValue* args) { + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); + int callback_id; + int partition_id; + std::string scope_string; + const DictionaryValue* cmd_args = NULL; + scoped_refptr<ServiceWorkerContextWrapper> context; + if (!args->GetInteger(0, &callback_id) || + !args->GetDictionary(1, &cmd_args) || + !cmd_args->GetInteger("partition_id", &partition_id) || + !GetServiceWorkerContext(partition_id, &context) || + !cmd_args->GetString("scope", &scope_string)) { + return; + } + + base::Callback<void(ServiceWorkerStatusCode)> callback = + base::Bind(OperationCompleteCallback, AsWeakPtr(), callback_id); + FindRegistrationForPattern( + context, GURL(scope_string), base::Bind(StartActiveWorker, callback)); +} + +} // namespace content diff --git a/chromium/content/browser/service_worker/service_worker_internals_ui.h b/chromium/content/browser/service_worker/service_worker_internals_ui.h new file mode 100644 index 00000000000..84c1e19204e --- /dev/null +++ b/chromium/content/browser/service_worker/service_worker_internals_ui.h @@ -0,0 +1,74 @@ +// Copyright 2014 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 CONTENT_BROWSER_SERVICE_WORKER_SERVICE_WORKER_INTERNALS_UI_H_ +#define CONTENT_BROWSER_SERVICE_WORKER_SERVICE_WORKER_INTERNALS_UI_H_ + +#include <set> + +#include "base/containers/scoped_ptr_hash_map.h" +#include "base/files/file_path.h" +#include "base/memory/ref_counted.h" +#include "base/memory/scoped_ptr.h" +#include "base/memory/weak_ptr.h" +#include "content/browser/service_worker/service_worker_context_observer.h" +#include "content/common/service_worker/service_worker_status_code.h" +#include "content/public/browser/web_ui_controller.h" + +namespace base { +class ListValue; +class DictionaryValue; +} + +namespace content { + +class StoragePartition; +class ServiceWorkerContextWrapper; +class ServiceWorkerRegistration; +class ServiceWorkerVersion; + +class ServiceWorkerInternalsUI + : public WebUIController, + public base::SupportsWeakPtr<ServiceWorkerInternalsUI> { + public: + typedef base::Callback<void(ServiceWorkerStatusCode)> StatusCallback; + typedef void (ServiceWorkerVersion::*ServiceWorkerVersionMethod)( + const StatusCallback& callback); + + explicit ServiceWorkerInternalsUI(WebUI* web_ui); + + private: + class OperationProxy; + class PartitionObserver; + + virtual ~ServiceWorkerInternalsUI(); + void AddContextFromStoragePartition(StoragePartition* partition); + + void RemoveObserverFromStoragePartition(StoragePartition* partition); + + // Called from Javascript. + void GetOptions(const base::ListValue* args); + void SetOption(const base::ListValue* args); + void GetAllRegistrations(const base::ListValue* args); + void CallServiceWorkerVersionMethod(ServiceWorkerVersionMethod method, + const base::ListValue* args); + void DispatchPushEvent(const base::ListValue* args); + void InspectWorker(const base::ListValue* args); + void Unregister(const base::ListValue* args); + void StartWorker(const base::ListValue* args); + + bool GetServiceWorkerContext( + int partition_id, + scoped_refptr<ServiceWorkerContextWrapper>* context) const; + void FindContext(int partition_id, + StoragePartition** result_partition, + StoragePartition* storage_partition) const; + + base::ScopedPtrHashMap<uintptr_t, PartitionObserver> observers_; + int next_partition_id_; +}; + +} // namespace content + +#endif // CONTENT_BROWSER_SERVICE_WORKER_SERVICE_WORKER_INTERNALS_UI_H_ diff --git a/chromium/content/browser/service_worker/service_worker_job_coordinator.cc b/chromium/content/browser/service_worker/service_worker_job_coordinator.cc new file mode 100644 index 00000000000..8152b490272 --- /dev/null +++ b/chromium/content/browser/service_worker/service_worker_job_coordinator.cc @@ -0,0 +1,112 @@ +// Copyright 2014 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 "content/browser/service_worker/service_worker_job_coordinator.h" + +#include "base/memory/scoped_ptr.h" +#include "base/stl_util.h" +#include "content/browser/service_worker/service_worker_register_job_base.h" + +namespace content { + +ServiceWorkerJobCoordinator::JobQueue::JobQueue() {} + +ServiceWorkerJobCoordinator::JobQueue::~JobQueue() { + DCHECK(jobs_.empty()) << "Destroying JobQueue with " << jobs_.size() + << " unfinished jobs"; + STLDeleteElements(&jobs_); +} + +ServiceWorkerRegisterJobBase* ServiceWorkerJobCoordinator::JobQueue::Push( + scoped_ptr<ServiceWorkerRegisterJobBase> job) { + if (jobs_.empty()) { + job->Start(); + jobs_.push_back(job.release()); + } else if (!job->Equals(jobs_.back())) { + jobs_.push_back(job.release()); + } + // Note we are releasing 'job' here. + + DCHECK(!jobs_.empty()); + return jobs_.back(); +} + +void ServiceWorkerJobCoordinator::JobQueue::Pop( + ServiceWorkerRegisterJobBase* job) { + DCHECK(job == jobs_.front()); + jobs_.pop_front(); + delete job; + if (!jobs_.empty()) + jobs_.front()->Start(); +} + +void ServiceWorkerJobCoordinator::JobQueue::AbortAll() { + for (size_t i = 0; i < jobs_.size(); ++i) + jobs_[i]->Abort(); + STLDeleteElements(&jobs_); +} + +void ServiceWorkerJobCoordinator::JobQueue::ClearForShutdown() { + STLDeleteElements(&jobs_); +} + +ServiceWorkerJobCoordinator::ServiceWorkerJobCoordinator( + base::WeakPtr<ServiceWorkerContextCore> context) + : context_(context) { +} + +ServiceWorkerJobCoordinator::~ServiceWorkerJobCoordinator() { + if (!context_) { + for (RegistrationJobMap::iterator it = job_queues_.begin(); + it != job_queues_.end(); ++it) { + it->second.ClearForShutdown(); + } + job_queues_.clear(); + } + DCHECK(job_queues_.empty()) << "Destroying ServiceWorkerJobCoordinator with " + << job_queues_.size() << " job queues"; +} + +void ServiceWorkerJobCoordinator::Register( + const GURL& pattern, + const GURL& script_url, + int source_process_id, + const ServiceWorkerRegisterJob::RegistrationCallback& callback) { + scoped_ptr<ServiceWorkerRegisterJobBase> job( + new ServiceWorkerRegisterJob(context_, pattern, script_url)); + ServiceWorkerRegisterJob* queued_job = + static_cast<ServiceWorkerRegisterJob*>( + job_queues_[pattern].Push(job.Pass())); + queued_job->AddCallback(callback, source_process_id); +} + +void ServiceWorkerJobCoordinator::Unregister( + const GURL& pattern, + const ServiceWorkerUnregisterJob::UnregistrationCallback& callback) { + scoped_ptr<ServiceWorkerRegisterJobBase> job( + new ServiceWorkerUnregisterJob(context_, pattern)); + ServiceWorkerUnregisterJob* queued_job = + static_cast<ServiceWorkerUnregisterJob*>( + job_queues_[pattern].Push(job.Pass())); + queued_job->AddCallback(callback); +} + +void ServiceWorkerJobCoordinator::AbortAll() { + for (RegistrationJobMap::iterator it = job_queues_.begin(); + it != job_queues_.end(); ++it) { + it->second.AbortAll(); + } + job_queues_.clear(); +} + +void ServiceWorkerJobCoordinator::FinishJob(const GURL& pattern, + ServiceWorkerRegisterJobBase* job) { + RegistrationJobMap::iterator pending_jobs = job_queues_.find(pattern); + DCHECK(pending_jobs != job_queues_.end()) << "Deleting non-existent job."; + pending_jobs->second.Pop(job); + if (pending_jobs->second.empty()) + job_queues_.erase(pending_jobs); +} + +} // namespace content diff --git a/chromium/content/browser/service_worker/service_worker_job_coordinator.h b/chromium/content/browser/service_worker/service_worker_job_coordinator.h new file mode 100644 index 00000000000..06a1057d76e --- /dev/null +++ b/chromium/content/browser/service_worker/service_worker_job_coordinator.h @@ -0,0 +1,85 @@ +// Copyright 2014 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 CONTENT_BROWSER_SERVICE_WORKER_SERVICE_WORKER_JOB_COORDINATOR_H_ +#define CONTENT_BROWSER_SERVICE_WORKER_SERVICE_WORKER_JOB_COORDINATOR_H_ + +#include <deque> +#include <map> + +#include "content/browser/service_worker/service_worker_register_job.h" +#include "content/browser/service_worker/service_worker_unregister_job.h" +#include "content/common/content_export.h" +#include "url/gurl.h" + +namespace content { + +class EmbeddedWorkerRegistry; +class ServiceWorkerRegistration; +class ServiceWorkerStorage; + +// This class manages all in-flight registration or unregistration jobs. +class CONTENT_EXPORT ServiceWorkerJobCoordinator { + public: + explicit ServiceWorkerJobCoordinator( + base::WeakPtr<ServiceWorkerContextCore> context); + ~ServiceWorkerJobCoordinator(); + + void Register(const GURL& pattern, + const GURL& script_url, + int source_process_id, + const ServiceWorkerRegisterJob::RegistrationCallback& callback); + + void Unregister( + const GURL& pattern, + const ServiceWorkerUnregisterJob::UnregistrationCallback& callback); + + // Calls ServiceWorkerRegisterJobBase::Abort() on all jobs and removes them. + void AbortAll(); + + // Removes the job. A job that was not aborted must call FinishJob when it is + // done. + void FinishJob(const GURL& pattern, ServiceWorkerRegisterJobBase* job); + + private: + class JobQueue { + public: + JobQueue(); + ~JobQueue(); + + // Adds a job to the queue. If an identical job is already in the queue, no + // new job is added. Returns the job in the queue, regardless of whether it + // was newly added. + ServiceWorkerRegisterJobBase* Push( + scoped_ptr<ServiceWorkerRegisterJobBase> job); + + // Removes a job from the queue. + void Pop(ServiceWorkerRegisterJobBase* job); + + bool empty() { return jobs_.empty(); } + + // Aborts all jobs in the queue and removes them. + void AbortAll(); + + // Marks that the browser is shutting down, so jobs may be destroyed before + // finishing. + void ClearForShutdown(); + + private: + std::deque<ServiceWorkerRegisterJobBase*> jobs_; + }; + + typedef std::map<GURL, JobQueue> RegistrationJobMap; + + // The ServiceWorkerContextCore object should always outlive the + // job coordinator, the core owns the coordinator. + base::WeakPtr<ServiceWorkerContextCore> context_; + RegistrationJobMap job_queues_; + + DISALLOW_COPY_AND_ASSIGN(ServiceWorkerJobCoordinator); +}; + +} // namespace content + +#endif // CONTENT_BROWSER_SERVICE_WORKER_SERVICE_WORKER_JOB_COORDINATOR_H_ diff --git a/chromium/content/browser/service_worker/service_worker_job_unittest.cc b/chromium/content/browser/service_worker/service_worker_job_unittest.cc new file mode 100644 index 00000000000..efa8bd1aa1e --- /dev/null +++ b/chromium/content/browser/service_worker/service_worker_job_unittest.cc @@ -0,0 +1,702 @@ +// Copyright 2014 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 "base/files/scoped_temp_dir.h" +#include "base/logging.h" +#include "base/run_loop.h" +#include "content/browser/browser_thread_impl.h" +#include "content/browser/service_worker/embedded_worker_registry.h" +#include "content/browser/service_worker/embedded_worker_test_helper.h" +#include "content/browser/service_worker/service_worker_context_core.h" +#include "content/browser/service_worker/service_worker_job_coordinator.h" +#include "content/browser/service_worker/service_worker_registration.h" +#include "content/browser/service_worker/service_worker_registration_status.h" +#include "content/public/test/test_browser_thread_bundle.h" +#include "ipc/ipc_test_sink.h" +#include "testing/gtest/include/gtest/gtest.h" + +// Unit tests for testing all job registration tasks. +namespace content { + +namespace { + +void SaveRegistrationCallback( + ServiceWorkerStatusCode expected_status, + bool* called, + scoped_refptr<ServiceWorkerRegistration>* registration_out, + ServiceWorkerStatusCode status, + ServiceWorkerRegistration* registration, + ServiceWorkerVersion* version) { + ASSERT_TRUE(!version || version->registration_id() == registration->id()) + << version << " " << registration; + EXPECT_EQ(expected_status, status); + *called = true; + *registration_out = registration; +} + +void SaveFoundRegistrationCallback( + ServiceWorkerStatusCode expected_status, + bool* called, + scoped_refptr<ServiceWorkerRegistration>* registration, + ServiceWorkerStatusCode status, + const scoped_refptr<ServiceWorkerRegistration>& result) { + EXPECT_EQ(expected_status, status); + *called = true; + *registration = result; +} + +// Creates a callback which both keeps track of if it's been called, +// as well as the resulting registration. Whent the callback is fired, +// it ensures that the resulting status matches the expectation. +// 'called' is useful for making sure a sychronous callback is or +// isn't called. +ServiceWorkerRegisterJob::RegistrationCallback SaveRegistration( + ServiceWorkerStatusCode expected_status, + bool* called, + scoped_refptr<ServiceWorkerRegistration>* registration) { + *called = false; + return base::Bind( + &SaveRegistrationCallback, expected_status, called, registration); +} + +ServiceWorkerStorage::FindRegistrationCallback SaveFoundRegistration( + ServiceWorkerStatusCode expected_status, + bool* called, + scoped_refptr<ServiceWorkerRegistration>* registration) { + *called = false; + return base::Bind(&SaveFoundRegistrationCallback, + expected_status, + called, + registration); +} + +void SaveUnregistrationCallback(ServiceWorkerStatusCode expected_status, + bool* called, + ServiceWorkerStatusCode status) { + EXPECT_EQ(expected_status, status); + *called = true; +} + +ServiceWorkerUnregisterJob::UnregistrationCallback SaveUnregistration( + ServiceWorkerStatusCode expected_status, + bool* called) { + *called = false; + return base::Bind(&SaveUnregistrationCallback, expected_status, called); +} + +} // namespace + +class ServiceWorkerJobTest : public testing::Test { + public: + ServiceWorkerJobTest() + : browser_thread_bundle_(TestBrowserThreadBundle::IO_MAINLOOP), + render_process_id_(88) {} + + virtual void SetUp() OVERRIDE { + helper_.reset(new EmbeddedWorkerTestHelper(render_process_id_)); + } + + virtual void TearDown() OVERRIDE { + helper_.reset(); + } + + ServiceWorkerContextCore* context() const { return helper_->context(); } + + ServiceWorkerJobCoordinator* job_coordinator() const { + return context()->job_coordinator(); + } + ServiceWorkerStorage* storage() const { return context()->storage(); } + + protected: + TestBrowserThreadBundle browser_thread_bundle_; + scoped_ptr<EmbeddedWorkerTestHelper> helper_; + + int render_process_id_; +}; + +TEST_F(ServiceWorkerJobTest, SameDocumentSameRegistration) { + scoped_refptr<ServiceWorkerRegistration> original_registration; + bool called; + job_coordinator()->Register( + GURL("http://www.example.com/*"), + GURL("http://www.example.com/service_worker.js"), + render_process_id_, + SaveRegistration(SERVICE_WORKER_OK, &called, &original_registration)); + EXPECT_FALSE(called); + base::RunLoop().RunUntilIdle(); + EXPECT_TRUE(called); + + scoped_refptr<ServiceWorkerRegistration> registration1; + storage()->FindRegistrationForDocument( + GURL("http://www.example.com/"), + SaveFoundRegistration(SERVICE_WORKER_OK, &called, ®istration1)); + scoped_refptr<ServiceWorkerRegistration> registration2; + storage()->FindRegistrationForDocument( + GURL("http://www.example.com/"), + SaveFoundRegistration(SERVICE_WORKER_OK, &called, ®istration2)); + base::RunLoop().RunUntilIdle(); + EXPECT_TRUE(called); + ASSERT_TRUE(registration1); + ASSERT_EQ(registration1, original_registration); + ASSERT_EQ(registration1, registration2); +} + +TEST_F(ServiceWorkerJobTest, SameMatchSameRegistration) { + bool called; + scoped_refptr<ServiceWorkerRegistration> original_registration; + job_coordinator()->Register( + GURL("http://www.example.com/*"), + GURL("http://www.example.com/service_worker.js"), + render_process_id_, + SaveRegistration(SERVICE_WORKER_OK, &called, &original_registration)); + EXPECT_FALSE(called); + base::RunLoop().RunUntilIdle(); + EXPECT_TRUE(called); + ASSERT_NE(static_cast<ServiceWorkerRegistration*>(NULL), + original_registration.get()); + + scoped_refptr<ServiceWorkerRegistration> registration1; + storage()->FindRegistrationForDocument( + GURL("http://www.example.com/one"), + SaveFoundRegistration(SERVICE_WORKER_OK, &called, ®istration1)); + base::RunLoop().RunUntilIdle(); + EXPECT_TRUE(called); + + scoped_refptr<ServiceWorkerRegistration> registration2; + storage()->FindRegistrationForDocument( + GURL("http://www.example.com/two"), + SaveFoundRegistration(SERVICE_WORKER_OK, &called, ®istration2)); + base::RunLoop().RunUntilIdle(); + EXPECT_TRUE(called); + ASSERT_EQ(registration1, original_registration); + ASSERT_EQ(registration1, registration2); +} + +TEST_F(ServiceWorkerJobTest, DifferentMatchDifferentRegistration) { + bool called1; + scoped_refptr<ServiceWorkerRegistration> original_registration1; + job_coordinator()->Register( + GURL("http://www.example.com/one/*"), + GURL("http://www.example.com/service_worker.js"), + render_process_id_, + SaveRegistration(SERVICE_WORKER_OK, &called1, &original_registration1)); + + bool called2; + scoped_refptr<ServiceWorkerRegistration> original_registration2; + job_coordinator()->Register( + GURL("http://www.example.com/two/*"), + GURL("http://www.example.com/service_worker.js"), + render_process_id_, + SaveRegistration(SERVICE_WORKER_OK, &called2, &original_registration2)); + + EXPECT_FALSE(called1); + EXPECT_FALSE(called2); + base::RunLoop().RunUntilIdle(); + EXPECT_TRUE(called2); + EXPECT_TRUE(called1); + + scoped_refptr<ServiceWorkerRegistration> registration1; + storage()->FindRegistrationForDocument( + GURL("http://www.example.com/one/"), + SaveFoundRegistration(SERVICE_WORKER_OK, &called1, ®istration1)); + scoped_refptr<ServiceWorkerRegistration> registration2; + storage()->FindRegistrationForDocument( + GURL("http://www.example.com/two/"), + SaveFoundRegistration(SERVICE_WORKER_OK, &called2, ®istration2)); + + base::RunLoop().RunUntilIdle(); + EXPECT_TRUE(called2); + EXPECT_TRUE(called1); + ASSERT_NE(registration1, registration2); +} + +// Make sure basic registration is working. +TEST_F(ServiceWorkerJobTest, Register) { + bool called = false; + scoped_refptr<ServiceWorkerRegistration> registration; + job_coordinator()->Register( + GURL("http://www.example.com/*"), + GURL("http://www.example.com/service_worker.js"), + render_process_id_, + SaveRegistration(SERVICE_WORKER_OK, &called, ®istration)); + + ASSERT_FALSE(called); + base::RunLoop().RunUntilIdle(); + ASSERT_TRUE(called); + + ASSERT_NE(scoped_refptr<ServiceWorkerRegistration>(NULL), registration); +} + +// Make sure registrations are cleaned up when they are unregistered. +TEST_F(ServiceWorkerJobTest, Unregister) { + GURL pattern("http://www.example.com/*"); + + bool called; + scoped_refptr<ServiceWorkerRegistration> registration; + job_coordinator()->Register( + pattern, + GURL("http://www.example.com/service_worker.js"), + render_process_id_, + SaveRegistration(SERVICE_WORKER_OK, &called, ®istration)); + + ASSERT_FALSE(called); + base::RunLoop().RunUntilIdle(); + ASSERT_TRUE(called); + + job_coordinator()->Unregister(pattern, + SaveUnregistration(SERVICE_WORKER_OK, &called)); + + ASSERT_FALSE(called); + base::RunLoop().RunUntilIdle(); + ASSERT_TRUE(called); + + ASSERT_TRUE(registration->HasOneRef()); + + storage()->FindRegistrationForPattern( + pattern, + SaveFoundRegistration(SERVICE_WORKER_ERROR_NOT_FOUND, + &called, ®istration)); + + ASSERT_FALSE(called); + base::RunLoop().RunUntilIdle(); + ASSERT_TRUE(called); + + ASSERT_EQ(scoped_refptr<ServiceWorkerRegistration>(NULL), registration); +} + +TEST_F(ServiceWorkerJobTest, Unregister_NothingRegistered) { + GURL pattern("http://www.example.com/*"); + + bool called; + job_coordinator()->Unregister(pattern, + SaveUnregistration(SERVICE_WORKER_OK, &called)); + + ASSERT_FALSE(called); + base::RunLoop().RunUntilIdle(); + ASSERT_TRUE(called); +} + +// Make sure that when a new registration replaces an existing +// registration, that the old one is cleaned up. +TEST_F(ServiceWorkerJobTest, RegisterNewScript) { + GURL pattern("http://www.example.com/*"); + + bool called; + scoped_refptr<ServiceWorkerRegistration> old_registration; + job_coordinator()->Register( + pattern, + GURL("http://www.example.com/service_worker.js"), + render_process_id_, + SaveRegistration(SERVICE_WORKER_OK, &called, &old_registration)); + + ASSERT_FALSE(called); + base::RunLoop().RunUntilIdle(); + ASSERT_TRUE(called); + + scoped_refptr<ServiceWorkerRegistration> old_registration_by_pattern; + storage()->FindRegistrationForPattern( + pattern, + SaveFoundRegistration( + SERVICE_WORKER_OK, &called, &old_registration_by_pattern)); + + ASSERT_FALSE(called); + base::RunLoop().RunUntilIdle(); + ASSERT_TRUE(called); + + ASSERT_EQ(old_registration, old_registration_by_pattern); + old_registration_by_pattern = NULL; + + scoped_refptr<ServiceWorkerRegistration> new_registration; + job_coordinator()->Register( + pattern, + GURL("http://www.example.com/service_worker_new.js"), + render_process_id_, + SaveRegistration(SERVICE_WORKER_OK, &called, &new_registration)); + + ASSERT_FALSE(called); + base::RunLoop().RunUntilIdle(); + ASSERT_TRUE(called); + + ASSERT_TRUE(old_registration->HasOneRef()); + + ASSERT_NE(old_registration, new_registration); + + scoped_refptr<ServiceWorkerRegistration> new_registration_by_pattern; + storage()->FindRegistrationForPattern( + pattern, + SaveFoundRegistration( + SERVICE_WORKER_OK, &called, &new_registration)); + + ASSERT_FALSE(called); + base::RunLoop().RunUntilIdle(); + ASSERT_TRUE(called); + + ASSERT_NE(new_registration_by_pattern, old_registration); +} + +// Make sure that when registering a duplicate pattern+script_url +// combination, that the same registration is used. +TEST_F(ServiceWorkerJobTest, RegisterDuplicateScript) { + GURL pattern("http://www.example.com/*"); + GURL script_url("http://www.example.com/service_worker.js"); + + bool called; + scoped_refptr<ServiceWorkerRegistration> old_registration; + job_coordinator()->Register( + pattern, + script_url, + render_process_id_, + SaveRegistration(SERVICE_WORKER_OK, &called, &old_registration)); + + ASSERT_FALSE(called); + base::RunLoop().RunUntilIdle(); + ASSERT_TRUE(called); + + scoped_refptr<ServiceWorkerRegistration> old_registration_by_pattern; + storage()->FindRegistrationForPattern( + pattern, + SaveFoundRegistration( + SERVICE_WORKER_OK, &called, &old_registration_by_pattern)); + ASSERT_FALSE(called); + base::RunLoop().RunUntilIdle(); + ASSERT_TRUE(called); + + ASSERT_TRUE(old_registration_by_pattern); + + scoped_refptr<ServiceWorkerRegistration> new_registration; + job_coordinator()->Register( + pattern, + script_url, + render_process_id_, + SaveRegistration(SERVICE_WORKER_OK, &called, &new_registration)); + + ASSERT_FALSE(called); + base::RunLoop().RunUntilIdle(); + ASSERT_TRUE(called); + + ASSERT_EQ(old_registration, new_registration); + + ASSERT_FALSE(old_registration->HasOneRef()); + + scoped_refptr<ServiceWorkerRegistration> new_registration_by_pattern; + storage()->FindRegistrationForPattern( + pattern, + SaveFoundRegistration( + SERVICE_WORKER_OK, &called, &new_registration_by_pattern)); + + ASSERT_FALSE(called); + base::RunLoop().RunUntilIdle(); + ASSERT_TRUE(called); + + ASSERT_EQ(new_registration, old_registration); +} + +class FailToStartWorkerTestHelper : public EmbeddedWorkerTestHelper { + public: + FailToStartWorkerTestHelper(int mock_render_process_id) + : EmbeddedWorkerTestHelper(mock_render_process_id) {} + + virtual void OnStartWorker(int embedded_worker_id, + int64 service_worker_version_id, + const GURL& scope, + const GURL& script_url) OVERRIDE { + // Simulate failure by sending worker stopped instead of started. + EmbeddedWorkerInstance* worker = registry()->GetWorker(embedded_worker_id); + registry()->OnWorkerStopped(worker->process_id(), embedded_worker_id); + } +}; + +TEST_F(ServiceWorkerJobTest, Register_FailToStartWorker) { + helper_.reset(new FailToStartWorkerTestHelper(render_process_id_)); + + bool called = false; + scoped_refptr<ServiceWorkerRegistration> registration; + job_coordinator()->Register( + GURL("http://www.example.com/*"), + GURL("http://www.example.com/service_worker.js"), + render_process_id_, + SaveRegistration( + SERVICE_WORKER_ERROR_START_WORKER_FAILED, &called, ®istration)); + + ASSERT_FALSE(called); + base::RunLoop().RunUntilIdle(); + + ASSERT_TRUE(called); + ASSERT_EQ(scoped_refptr<ServiceWorkerRegistration>(NULL), registration); +} + +// Register and then unregister the pattern, in parallel. Job coordinator should +// process jobs until the last job. +TEST_F(ServiceWorkerJobTest, ParallelRegUnreg) { + GURL pattern("http://www.example.com/*"); + GURL script_url("http://www.example.com/service_worker.js"); + + bool registration_called = false; + scoped_refptr<ServiceWorkerRegistration> registration; + job_coordinator()->Register( + pattern, + script_url, + render_process_id_, + SaveRegistration(SERVICE_WORKER_OK, ®istration_called, ®istration)); + + bool unregistration_called = false; + job_coordinator()->Unregister( + pattern, + SaveUnregistration(SERVICE_WORKER_OK, &unregistration_called)); + + ASSERT_FALSE(registration_called); + ASSERT_FALSE(unregistration_called); + base::RunLoop().RunUntilIdle(); + ASSERT_TRUE(registration_called); + ASSERT_TRUE(unregistration_called); + + bool find_called = false; + storage()->FindRegistrationForPattern( + pattern, + SaveFoundRegistration( + SERVICE_WORKER_ERROR_NOT_FOUND, &find_called, ®istration)); + + base::RunLoop().RunUntilIdle(); + + ASSERT_EQ(scoped_refptr<ServiceWorkerRegistration>(), registration); +} + +// Register conflicting scripts for the same pattern. The most recent +// registration should win, and the old registration should have been +// shutdown. +TEST_F(ServiceWorkerJobTest, ParallelRegNewScript) { + GURL pattern("http://www.example.com/*"); + + GURL script_url1("http://www.example.com/service_worker1.js"); + bool registration1_called = false; + scoped_refptr<ServiceWorkerRegistration> registration1; + job_coordinator()->Register( + pattern, + script_url1, + render_process_id_, + SaveRegistration( + SERVICE_WORKER_OK, ®istration1_called, ®istration1)); + + GURL script_url2("http://www.example.com/service_worker2.js"); + bool registration2_called = false; + scoped_refptr<ServiceWorkerRegistration> registration2; + job_coordinator()->Register( + pattern, + script_url2, + render_process_id_, + SaveRegistration( + SERVICE_WORKER_OK, ®istration2_called, ®istration2)); + + ASSERT_FALSE(registration1_called); + ASSERT_FALSE(registration2_called); + base::RunLoop().RunUntilIdle(); + ASSERT_TRUE(registration1_called); + ASSERT_TRUE(registration2_called); + + scoped_refptr<ServiceWorkerRegistration> registration; + bool find_called = false; + storage()->FindRegistrationForPattern( + pattern, + SaveFoundRegistration( + SERVICE_WORKER_OK, &find_called, ®istration)); + + base::RunLoop().RunUntilIdle(); + + ASSERT_EQ(registration2, registration); +} + +// Register the exact same pattern + script. Requests should be +// coalesced such that both callers get the exact same registration +// object. +TEST_F(ServiceWorkerJobTest, ParallelRegSameScript) { + GURL pattern("http://www.example.com/*"); + + GURL script_url("http://www.example.com/service_worker1.js"); + bool registration1_called = false; + scoped_refptr<ServiceWorkerRegistration> registration1; + job_coordinator()->Register( + pattern, + script_url, + render_process_id_, + SaveRegistration( + SERVICE_WORKER_OK, ®istration1_called, ®istration1)); + + bool registration2_called = false; + scoped_refptr<ServiceWorkerRegistration> registration2; + job_coordinator()->Register( + pattern, + script_url, + render_process_id_, + SaveRegistration( + SERVICE_WORKER_OK, ®istration2_called, ®istration2)); + + ASSERT_FALSE(registration1_called); + ASSERT_FALSE(registration2_called); + base::RunLoop().RunUntilIdle(); + ASSERT_TRUE(registration1_called); + ASSERT_TRUE(registration2_called); + + ASSERT_EQ(registration1, registration2); + + scoped_refptr<ServiceWorkerRegistration> registration; + bool find_called = false; + storage()->FindRegistrationForPattern( + pattern, + SaveFoundRegistration( + SERVICE_WORKER_OK, &find_called, ®istration)); + + base::RunLoop().RunUntilIdle(); + ASSERT_EQ(registration, registration1); +} + +// Call simulataneous unregister calls. +TEST_F(ServiceWorkerJobTest, ParallelUnreg) { + GURL pattern("http://www.example.com/*"); + + GURL script_url("http://www.example.com/service_worker.js"); + bool unregistration1_called = false; + job_coordinator()->Unregister( + pattern, + SaveUnregistration(SERVICE_WORKER_OK, &unregistration1_called)); + + bool unregistration2_called = false; + job_coordinator()->Unregister( + pattern, SaveUnregistration(SERVICE_WORKER_OK, &unregistration2_called)); + + ASSERT_FALSE(unregistration1_called); + ASSERT_FALSE(unregistration2_called); + base::RunLoop().RunUntilIdle(); + ASSERT_TRUE(unregistration1_called); + ASSERT_TRUE(unregistration2_called); + + // There isn't really a way to test that they are being coalesced, + // but we can make sure they can exist simultaneously without + // crashing. + scoped_refptr<ServiceWorkerRegistration> registration; + bool find_called = false; + storage()->FindRegistrationForPattern( + pattern, + SaveFoundRegistration( + SERVICE_WORKER_ERROR_NOT_FOUND, &find_called, ®istration)); + + base::RunLoop().RunUntilIdle(); + ASSERT_EQ(scoped_refptr<ServiceWorkerRegistration>(), registration); +} + +TEST_F(ServiceWorkerJobTest, AbortAll_Register) { + GURL pattern1("http://www1.example.com/*"); + GURL pattern2("http://www2.example.com/*"); + GURL script_url1("http://www1.example.com/service_worker.js"); + GURL script_url2("http://www2.example.com/service_worker.js"); + + bool registration_called1 = false; + scoped_refptr<ServiceWorkerRegistration> registration1; + job_coordinator()->Register( + pattern1, + script_url1, + render_process_id_, + SaveRegistration(SERVICE_WORKER_ERROR_ABORT, + ®istration_called1, ®istration1)); + + bool registration_called2 = false; + scoped_refptr<ServiceWorkerRegistration> registration2; + job_coordinator()->Register( + pattern2, + script_url2, + render_process_id_, + SaveRegistration(SERVICE_WORKER_ERROR_ABORT, + ®istration_called2, ®istration2)); + + ASSERT_FALSE(registration_called1); + ASSERT_FALSE(registration_called2); + job_coordinator()->AbortAll(); + + base::RunLoop().RunUntilIdle(); + ASSERT_TRUE(registration_called1); + ASSERT_TRUE(registration_called2); + + bool find_called1 = false; + storage()->FindRegistrationForPattern( + pattern1, + SaveFoundRegistration( + SERVICE_WORKER_ERROR_NOT_FOUND, &find_called1, ®istration1)); + + bool find_called2 = false; + storage()->FindRegistrationForPattern( + pattern2, + SaveFoundRegistration( + SERVICE_WORKER_ERROR_NOT_FOUND, &find_called2, ®istration2)); + + base::RunLoop().RunUntilIdle(); + ASSERT_TRUE(find_called1); + ASSERT_TRUE(find_called2); + EXPECT_EQ(scoped_refptr<ServiceWorkerRegistration>(), registration1); + EXPECT_EQ(scoped_refptr<ServiceWorkerRegistration>(), registration2); +} + +TEST_F(ServiceWorkerJobTest, AbortAll_Unregister) { + GURL pattern1("http://www1.example.com/*"); + GURL pattern2("http://www2.example.com/*"); + + bool unregistration_called1 = false; + scoped_refptr<ServiceWorkerRegistration> registration1; + job_coordinator()->Unregister( + pattern1, + SaveUnregistration(SERVICE_WORKER_ERROR_ABORT, + &unregistration_called1)); + + bool unregistration_called2 = false; + job_coordinator()->Unregister( + pattern2, + SaveUnregistration(SERVICE_WORKER_ERROR_ABORT, + &unregistration_called2)); + + ASSERT_FALSE(unregistration_called1); + ASSERT_FALSE(unregistration_called2); + job_coordinator()->AbortAll(); + + base::RunLoop().RunUntilIdle(); + ASSERT_TRUE(unregistration_called1); + ASSERT_TRUE(unregistration_called2); +} + +TEST_F(ServiceWorkerJobTest, AbortAll_RegUnreg) { + GURL pattern("http://www.example.com/*"); + GURL script_url("http://www.example.com/service_worker.js"); + + bool registration_called = false; + scoped_refptr<ServiceWorkerRegistration> registration; + job_coordinator()->Register( + pattern, + script_url, + render_process_id_, + SaveRegistration(SERVICE_WORKER_ERROR_ABORT, + ®istration_called, ®istration)); + + bool unregistration_called = false; + job_coordinator()->Unregister( + pattern, + SaveUnregistration(SERVICE_WORKER_ERROR_ABORT, + &unregistration_called)); + + ASSERT_FALSE(registration_called); + ASSERT_FALSE(unregistration_called); + job_coordinator()->AbortAll(); + + base::RunLoop().RunUntilIdle(); + ASSERT_TRUE(registration_called); + ASSERT_TRUE(unregistration_called); + + bool find_called = false; + storage()->FindRegistrationForPattern( + pattern, + SaveFoundRegistration( + SERVICE_WORKER_ERROR_NOT_FOUND, &find_called, ®istration)); + + base::RunLoop().RunUntilIdle(); + ASSERT_TRUE(find_called); + EXPECT_EQ(scoped_refptr<ServiceWorkerRegistration>(), registration); +} + +} // namespace content diff --git a/chromium/content/browser/service_worker/service_worker_process_manager.cc b/chromium/content/browser/service_worker/service_worker_process_manager.cc new file mode 100644 index 00000000000..7395e7b7b77 --- /dev/null +++ b/chromium/content/browser/service_worker/service_worker_process_manager.cc @@ -0,0 +1,193 @@ +// Copyright 2014 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 "content/browser/service_worker/service_worker_process_manager.h" + +#include "content/browser/renderer_host/render_process_host_impl.h" +#include "content/browser/service_worker/service_worker_context_wrapper.h" +#include "content/public/browser/browser_thread.h" +#include "content/public/browser/site_instance.h" +#include "url/gurl.h" + +namespace content { + +static bool IncrementWorkerRefCountByPid(int process_id) { + RenderProcessHost* rph = RenderProcessHost::FromID(process_id); + if (!rph || rph->FastShutdownStarted()) + return false; + + static_cast<RenderProcessHostImpl*>(rph)->IncrementWorkerRefCount(); + return true; +} + +ServiceWorkerProcessManager::ProcessInfo::ProcessInfo( + const scoped_refptr<SiteInstance>& site_instance) + : site_instance(site_instance), + process_id(site_instance->GetProcess()->GetID()) { +} + +ServiceWorkerProcessManager::ProcessInfo::ProcessInfo(int process_id) + : process_id(process_id) { +} + +ServiceWorkerProcessManager::ProcessInfo::~ProcessInfo() { +} + +ServiceWorkerProcessManager::ServiceWorkerProcessManager( + BrowserContext* browser_context) + : browser_context_(browser_context), + process_id_for_test_(-1), + weak_this_factory_(this), + weak_this_(weak_this_factory_.GetWeakPtr()) { +} + +ServiceWorkerProcessManager::~ServiceWorkerProcessManager() { + DCHECK_CURRENTLY_ON(BrowserThread::UI); + DCHECK(browser_context_ == NULL) + << "Call Shutdown() before destroying |this|, so that racing method " + << "invocations don't use a destroyed BrowserContext."; +} + +void ServiceWorkerProcessManager::Shutdown() { + browser_context_ = NULL; + for (std::map<int, ProcessInfo>::const_iterator it = instance_info_.begin(); + it != instance_info_.end(); + ++it) { + RenderProcessHost* rph = RenderProcessHost::FromID(it->second.process_id); + DCHECK(rph); + static_cast<RenderProcessHostImpl*>(rph)->DecrementWorkerRefCount(); + } + instance_info_.clear(); +} + +void ServiceWorkerProcessManager::AllocateWorkerProcess( + int embedded_worker_id, + const std::vector<int>& process_ids, + const GURL& script_url, + const base::Callback<void(ServiceWorkerStatusCode, int process_id)>& + callback) { + if (!BrowserThread::CurrentlyOn(BrowserThread::UI)) { + BrowserThread::PostTask( + BrowserThread::UI, + FROM_HERE, + base::Bind(&ServiceWorkerProcessManager::AllocateWorkerProcess, + weak_this_, + embedded_worker_id, + process_ids, + script_url, + callback)); + return; + } + + if (process_id_for_test_ != -1) { + // Let tests specify the returned process ID. Note: We may need to be able + // to specify the error code too. + BrowserThread::PostTask( + BrowserThread::IO, + FROM_HERE, + base::Bind(callback, SERVICE_WORKER_OK, process_id_for_test_)); + return; + } + + DCHECK(!ContainsKey(instance_info_, embedded_worker_id)) + << embedded_worker_id << " already has a process allocated"; + + for (std::vector<int>::const_iterator it = process_ids.begin(); + it != process_ids.end(); + ++it) { + if (IncrementWorkerRefCountByPid(*it)) { + instance_info_.insert( + std::make_pair(embedded_worker_id, ProcessInfo(*it))); + BrowserThread::PostTask(BrowserThread::IO, + FROM_HERE, + base::Bind(callback, SERVICE_WORKER_OK, *it)); + return; + } + } + + if (!browser_context_) { + // Shutdown has started. + BrowserThread::PostTask( + BrowserThread::IO, + FROM_HERE, + base::Bind(callback, SERVICE_WORKER_ERROR_START_WORKER_FAILED, -1)); + return; + } + // No existing processes available; start a new one. + scoped_refptr<SiteInstance> site_instance = + SiteInstance::CreateForURL(browser_context_, script_url); + RenderProcessHost* rph = site_instance->GetProcess(); + // This Init() call posts a task to the IO thread that adds the RPH's + // ServiceWorkerDispatcherHost to the + // EmbeddedWorkerRegistry::process_sender_map_. + if (!rph->Init()) { + LOG(ERROR) << "Couldn't start a new process!"; + BrowserThread::PostTask( + BrowserThread::IO, + FROM_HERE, + base::Bind(callback, SERVICE_WORKER_ERROR_START_WORKER_FAILED, -1)); + return; + } + + instance_info_.insert( + std::make_pair(embedded_worker_id, ProcessInfo(site_instance))); + + static_cast<RenderProcessHostImpl*>(rph)->IncrementWorkerRefCount(); + BrowserThread::PostTask( + BrowserThread::IO, + FROM_HERE, + base::Bind(callback, SERVICE_WORKER_OK, rph->GetID())); +} + +void ServiceWorkerProcessManager::ReleaseWorkerProcess(int embedded_worker_id) { + if (!BrowserThread::CurrentlyOn(BrowserThread::UI)) { + BrowserThread::PostTask( + BrowserThread::UI, + FROM_HERE, + base::Bind(&ServiceWorkerProcessManager::ReleaseWorkerProcess, + weak_this_, + embedded_worker_id)); + return; + } + if (process_id_for_test_ != -1) { + // Unittests don't increment or decrement the worker refcount of a + // RenderProcessHost. + return; + } + if (browser_context_ == NULL) { + // Shutdown already released all instances. + DCHECK(instance_info_.empty()); + return; + } + std::map<int, ProcessInfo>::iterator info = + instance_info_.find(embedded_worker_id); + DCHECK(info != instance_info_.end()); + RenderProcessHost* rph = NULL; + if (info->second.site_instance) { + rph = info->second.site_instance->GetProcess(); + DCHECK_EQ(info->second.process_id, rph->GetID()) + << "A SiteInstance's process shouldn't get destroyed while we're " + "holding a reference to it. Was the reference actually held?"; + } else { + rph = RenderProcessHost::FromID(info->second.process_id); + DCHECK(rph) + << "Process " << info->second.process_id + << " was destroyed unexpectedly. Did we actually hold a reference?"; + } + static_cast<RenderProcessHostImpl*>(rph)->DecrementWorkerRefCount(); + instance_info_.erase(info); +} + +} // namespace content + +namespace base { +// Destroying ServiceWorkerProcessManagers only on the UI thread allows the +// member WeakPtr to safely guard the object's lifetime when used on that +// thread. +void DefaultDeleter<content::ServiceWorkerProcessManager>::operator()( + content::ServiceWorkerProcessManager* ptr) const { + content::BrowserThread::DeleteSoon( + content::BrowserThread::UI, FROM_HERE, ptr); +} +} // namespace base diff --git a/chromium/content/browser/service_worker/service_worker_process_manager.h b/chromium/content/browser/service_worker/service_worker_process_manager.h new file mode 100644 index 00000000000..85fbcc1c050 --- /dev/null +++ b/chromium/content/browser/service_worker/service_worker_process_manager.h @@ -0,0 +1,118 @@ +// Copyright 2014 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 CONTENT_BROWSER_SERVICE_WORKER_SERVICE_WORKER_PROCESS_MANAGER_H_ +#define CONTENT_BROWSER_SERVICE_WORKER_SERVICE_WORKER_PROCESS_MANAGER_H_ + +#include <map> +#include <vector> + +#include "base/callback.h" +#include "base/memory/scoped_ptr.h" +#include "base/memory/weak_ptr.h" +#include "content/common/service_worker/service_worker_status_code.h" + +class GURL; + +namespace content { + +class BrowserContext; +class SiteInstance; + +// Interacts with the UI thread to keep RenderProcessHosts alive while the +// ServiceWorker system is using them. Each instance of +// ServiceWorkerProcessManager is destroyed on the UI thread shortly after its +// ServiceWorkerContextWrapper is destroyed. +class CONTENT_EXPORT ServiceWorkerProcessManager { + public: + // |*this| must be owned by a ServiceWorkerContextWrapper in a + // StoragePartition within |browser_context|. + explicit ServiceWorkerProcessManager(BrowserContext* browser_context); + + // Shutdown must be called before the ProcessManager is destroyed. + ~ServiceWorkerProcessManager(); + + // Synchronously prevents new processes from being allocated. + // TODO(jyasskin): Drop references to RenderProcessHosts too. + void Shutdown(); + + // Returns a reference to a running process suitable for starting the Service + // Worker at |script_url|. Processes in |process_ids| will be checked in order + // for existence, and if none exist, then a new process will be created. Posts + // |callback| to the IO thread to indicate whether creation succeeded and the + // process ID that has a new reference. + // + // Allocation can fail with SERVICE_WORKER_ERROR_START_WORKER_FAILED if + // RenderProcessHost::Init fails. + void AllocateWorkerProcess( + int embedded_worker_id, + const std::vector<int>& process_ids, + const GURL& script_url, + const base::Callback<void(ServiceWorkerStatusCode, int process_id)>& + callback); + + // Drops a reference to a process that was running a Service Worker, and its + // SiteInstance. This must match a call to AllocateWorkerProcess. + void ReleaseWorkerProcess(int embedded_worker_id); + + // Sets a single process ID that will be used for all embedded workers. This + // bypasses the work of creating a process and managing its worker refcount so + // that unittests can run without a BrowserContext. The test is in charge of + // making sure this is only called on the same thread as runs the UI message + // loop. + void SetProcessIdForTest(int process_id) { + process_id_for_test_ = process_id; + } + + private: + // Information about the process for an EmbeddedWorkerInstance. + struct ProcessInfo { + explicit ProcessInfo(const scoped_refptr<SiteInstance>& site_instance); + explicit ProcessInfo(int process_id); + ~ProcessInfo(); + + // Stores the SiteInstance the Worker lives inside. This needs to outlive + // the instance's use of its RPH to uphold assumptions in the + // ContentBrowserClient interface. + scoped_refptr<SiteInstance> site_instance; + + // In case the process was allocated without using a SiteInstance, we need + // to store a process ID to decrement a worker reference on shutdown. + // TODO(jyasskin): Implement http://crbug.com/372045 or thread a frame_id in + // so all processes can be allocated with a SiteInstance. + int process_id; + }; + + // These fields are only accessed on the UI thread. + BrowserContext* browser_context_; + + // Maps the ID of a running EmbeddedWorkerInstance to information about the + // process it's running inside. Since the Instances themselves live on the IO + // thread, this can be slightly out of date: + // * instance_info_ is populated while an Instance is STARTING and before + // it's RUNNING. + // * instance_info_ is depopulated in a message sent as the Instance becomes + // STOPPED. + std::map<int, ProcessInfo> instance_info_; + + // In unit tests, this will be returned as the process for all + // EmbeddedWorkerInstances. + int process_id_for_test_; + + // Used to double-check that we don't access *this after it's destroyed. + base::WeakPtrFactory<ServiceWorkerProcessManager> weak_this_factory_; + const base::WeakPtr<ServiceWorkerProcessManager> weak_this_; +}; + +} // namespace content + +namespace base { +// Specialized to post the deletion to the UI thread. +template <> +struct CONTENT_EXPORT DefaultDeleter<content::ServiceWorkerProcessManager> { + void operator()(content::ServiceWorkerProcessManager* ptr) const; +}; +} // namespace base + +#endif // CONTENT_BROWSER_SERVICE_WORKER_SERVICE_WORKER_PROCESS_MANAGER_H_ diff --git a/chromium/content/browser/service_worker/service_worker_proto.gyp b/chromium/content/browser/service_worker/service_worker_proto.gyp new file mode 100644 index 00000000000..b2588fff6a5 --- /dev/null +++ b/chromium/content/browser/service_worker/service_worker_proto.gyp @@ -0,0 +1,17 @@ +{ + 'targets': [ + { + # GN version: //content/browser/service_worker:database_proto + 'target_name': 'database_proto', + 'type': 'static_library', + 'sources': [ + 'service_worker_database.proto', + ], + 'variables': { + 'proto_in_dir': '.', + 'proto_out_dir': 'content/browser/service_worker', + }, + 'includes': [ '../../../build/protoc.gypi' ] + }, + ], +} diff --git a/chromium/content/browser/service_worker/service_worker_provider_host.cc b/chromium/content/browser/service_worker/service_worker_provider_host.cc index 871343bac69..eeba3736d03 100644 --- a/chromium/content/browser/service_worker/service_worker_provider_host.cc +++ b/chromium/content/browser/service_worker/service_worker_provider_host.cc @@ -4,16 +4,177 @@ #include "content/browser/service_worker/service_worker_provider_host.h" +#include "base/stl_util.h" +#include "content/browser/message_port_message_filter.h" +#include "content/browser/service_worker/service_worker_context_core.h" +#include "content/browser/service_worker/service_worker_context_request_handler.h" +#include "content/browser/service_worker/service_worker_controllee_request_handler.h" +#include "content/browser/service_worker/service_worker_dispatcher_host.h" +#include "content/browser/service_worker/service_worker_handle.h" +#include "content/browser/service_worker/service_worker_utils.h" #include "content/browser/service_worker/service_worker_version.h" +#include "content/common/service_worker/service_worker_messages.h" namespace content { +static const int kDocumentMainThreadId = 0; + ServiceWorkerProviderHost::ServiceWorkerProviderHost( - int process_id, int provider_id) - : process_id_(process_id), provider_id_(provider_id) { + int process_id, int provider_id, + base::WeakPtr<ServiceWorkerContextCore> context, + ServiceWorkerDispatcherHost* dispatcher_host) + : process_id_(process_id), + provider_id_(provider_id), + context_(context), + dispatcher_host_(dispatcher_host) { } ServiceWorkerProviderHost::~ServiceWorkerProviderHost() { + if (active_version_) + active_version_->RemoveControllee(this); + if (waiting_version_) + waiting_version_->RemoveWaitingControllee(this); +} + +void ServiceWorkerProviderHost::SetDocumentUrl(const GURL& url) { + DCHECK(!url.has_ref()); + document_url_ = url; +} + +void ServiceWorkerProviderHost::SetActiveVersion( + ServiceWorkerVersion* version) { + if (version == active_version_) + return; + scoped_refptr<ServiceWorkerVersion> previous_version = active_version_; + active_version_ = version; + if (version) + version->AddControllee(this); + if (previous_version) + previous_version->RemoveControllee(this); + + if (!dispatcher_host_) + return; // Could be NULL in some tests. + + dispatcher_host_->Send(new ServiceWorkerMsg_SetCurrentServiceWorker( + kDocumentMainThreadId, provider_id(), CreateHandleAndPass(version))); +} + +void ServiceWorkerProviderHost::SetWaitingVersion( + ServiceWorkerVersion* version) { + DCHECK(ValidateVersionForAssociation(version)); + if (version == waiting_version_) + return; + scoped_refptr<ServiceWorkerVersion> previous_version = waiting_version_; + waiting_version_ = version; + if (version) + version->AddWaitingControllee(this); + if (previous_version) + previous_version->RemoveWaitingControllee(this); + + if (!dispatcher_host_) + return; // Could be NULL in some tests. + + dispatcher_host_->Send(new ServiceWorkerMsg_SetWaitingServiceWorker( + kDocumentMainThreadId, provider_id(), CreateHandleAndPass(version))); +} + +bool ServiceWorkerProviderHost::SetHostedVersionId(int64 version_id) { + if (!context_) + return true; // System is shutting down. + if (active_version_) + return false; // Unexpected bad message. + + ServiceWorkerVersion* live_version = context_->GetLiveVersion(version_id); + if (!live_version) + return true; // Was deleted before it got started. + + ServiceWorkerVersionInfo info = live_version->GetInfo(); + if (info.running_status != ServiceWorkerVersion::STARTING || + info.process_id != process_id_) { + // If we aren't trying to start this version in our process + // something is amiss. + return false; + } + + running_hosted_version_ = live_version; + return true; +} + +scoped_ptr<ServiceWorkerRequestHandler> +ServiceWorkerProviderHost::CreateRequestHandler( + ResourceType::Type resource_type, + base::WeakPtr<webkit_blob::BlobStorageContext> blob_storage_context) { + if (IsHostToRunningServiceWorker()) { + return scoped_ptr<ServiceWorkerRequestHandler>( + new ServiceWorkerContextRequestHandler( + context_, AsWeakPtr(), blob_storage_context, resource_type)); + } + if (ServiceWorkerUtils::IsMainResourceType(resource_type) || + active_version()) { + return scoped_ptr<ServiceWorkerRequestHandler>( + new ServiceWorkerControlleeRequestHandler( + context_, AsWeakPtr(), blob_storage_context, resource_type)); + } + return scoped_ptr<ServiceWorkerRequestHandler>(); +} + +bool ServiceWorkerProviderHost::ValidateVersionForAssociation( + ServiceWorkerVersion* version) { + if (running_hosted_version_) + return false; + if (!version) + return true; + + // A version to be associated with this provider should have the same + // registration (scope) as current active/waiting versions. + if (active_version_) { + if (active_version_->registration_id() != version->registration_id()) + return false; + DCHECK_EQ(active_version_->scope(), version->scope()); + } + if (waiting_version_) { + if (waiting_version_->registration_id() != version->registration_id()) + return false; + DCHECK_EQ(waiting_version_->scope(), version->scope()); + } + return true; +} + +void ServiceWorkerProviderHost::PostMessage( + const base::string16& message, + const std::vector<int>& sent_message_port_ids) { + if (!dispatcher_host_) + return; // Could be NULL in some tests. + + std::vector<int> new_routing_ids; + dispatcher_host_->message_port_message_filter()-> + UpdateMessagePortsWithNewRoutes(sent_message_port_ids, + &new_routing_ids); + + dispatcher_host_->Send( + new ServiceWorkerMsg_MessageToDocument( + kDocumentMainThreadId, provider_id(), + message, + sent_message_port_ids, + new_routing_ids)); +} + +ServiceWorkerObjectInfo ServiceWorkerProviderHost::CreateHandleAndPass( + ServiceWorkerVersion* version) { + DCHECK(ValidateVersionForAssociation(version)); + ServiceWorkerObjectInfo info; + if (context_ && version) { + scoped_ptr<ServiceWorkerHandle> handle = + ServiceWorkerHandle::Create(context_, dispatcher_host_, + kDocumentMainThreadId, version); + info = handle->GetObjectInfo(); + dispatcher_host_->RegisterServiceWorkerHandle(handle.Pass()); + } + return info; +} + +bool ServiceWorkerProviderHost::IsContextAlive() { + return context_ != NULL; } } // namespace content diff --git a/chromium/content/browser/service_worker/service_worker_provider_host.h b/chromium/content/browser/service_worker/service_worker_provider_host.h index 09990b61126..31f4a372b1b 100644 --- a/chromium/content/browser/service_worker/service_worker_provider_host.h +++ b/chromium/content/browser/service_worker/service_worker_provider_host.h @@ -5,36 +5,119 @@ #ifndef CONTENT_BROWSER_SERVICE_WORKER_SERVICE_WORKER_PROVIDER_HOST_H_ #define CONTENT_BROWSER_SERVICE_WORKER_SERVICE_WORKER_PROVIDER_HOST_H_ +#include <set> +#include <vector> + #include "base/memory/ref_counted.h" +#include "base/memory/weak_ptr.h" +#include "content/common/content_export.h" +#include "content/common/service_worker/service_worker_types.h" +#include "webkit/common/resource_type.h" + +namespace IPC { +class Sender; +} + +namespace webkit_blob { +class BlobStorageContext; +} namespace content { +class ServiceWorkerContextCore; +class ServiceWorkerDispatcherHost; +class ServiceWorkerRequestHandler; class ServiceWorkerVersion; -// This class is the browser-process representation of a serice worker +// This class is the browser-process representation of a service worker // provider. There is a provider per document and the lifetime of this // object is tied to the lifetime of its document in the renderer process. -// This class holds service worker state this is scoped to an individual +// This class holds service worker state that is scoped to an individual // document. -class ServiceWorkerProviderHost { +// +// Note this class can also host a running service worker, in which +// case it will observe resource loads made directly by the service worker. +class CONTENT_EXPORT ServiceWorkerProviderHost + : public base::SupportsWeakPtr<ServiceWorkerProviderHost> { public: ServiceWorkerProviderHost(int process_id, - int provider_id); + int provider_id, + base::WeakPtr<ServiceWorkerContextCore> context, + ServiceWorkerDispatcherHost* dispatcher_host); ~ServiceWorkerProviderHost(); int process_id() const { return process_id_; } int provider_id() const { return provider_id_; } - // The service worker version that corresponds with navigator.serviceWorker - // for our document. - ServiceWorkerVersion* associated_version() const { - return associated_version_.get(); + bool IsHostToRunningServiceWorker() { + return running_hosted_version_ != NULL; + } + + // The service worker version that corresponds with + // navigator.serviceWorker.active for our document. + ServiceWorkerVersion* active_version() const { + return active_version_.get(); + } + + // The service worker version that corresponds with + // navigate.serviceWorker.waiting for our document. + ServiceWorkerVersion* waiting_version() const { + return waiting_version_.get(); + } + + // The running version, if any, that this provider is providing resource + // loads for. + ServiceWorkerVersion* running_hosted_version() const { + return running_hosted_version_.get(); } + void SetDocumentUrl(const GURL& url); + const GURL& document_url() const { return document_url_; } + + // Associates |version| to this provider as its '.active' or '.waiting' + // version. + // Giving NULL to this method will unset the corresponding field. + void SetActiveVersion(ServiceWorkerVersion* version); + void SetWaitingVersion(ServiceWorkerVersion* version); + + // Returns false if the version is not in the expected STARTING in our + // process state. That would be indicative of a bad IPC message. + bool SetHostedVersionId(int64 versions_id); + + // Returns a handler for a request, the handler may return NULL if + // the request doesn't require special handling. + scoped_ptr<ServiceWorkerRequestHandler> CreateRequestHandler( + ResourceType::Type resource_type, + base::WeakPtr<webkit_blob::BlobStorageContext> blob_storage_context); + + // Returns true if |version| has the same registration as active and waiting + // versions. + bool ValidateVersionForAssociation(ServiceWorkerVersion* version); + + // Returns true if the context referred to by this host (i.e. |context_|) is + // still alive. + bool IsContextAlive(); + + // Dispatches message event to the document. + void PostMessage(const base::string16& message, + const std::vector<int>& sent_message_port_ids); + private: + // Creates a ServiceWorkerHandle to retain |version| and returns a + // ServiceWorkerInfo with the handle ID to pass to the provider. The + // provider is responsible for releasing the handle. + ServiceWorkerObjectInfo CreateHandleAndPass(ServiceWorkerVersion* version); + const int process_id_; const int provider_id_; - scoped_refptr<ServiceWorkerVersion> associated_version_; + GURL document_url_; + scoped_refptr<ServiceWorkerVersion> active_version_; + scoped_refptr<ServiceWorkerVersion> waiting_version_; + scoped_refptr<ServiceWorkerVersion> running_hosted_version_; + base::WeakPtr<ServiceWorkerContextCore> context_; + ServiceWorkerDispatcherHost* dispatcher_host_; + + DISALLOW_COPY_AND_ASSIGN(ServiceWorkerProviderHost); }; } // namespace content diff --git a/chromium/content/browser/service_worker/service_worker_provider_host_unittest.cc b/chromium/content/browser/service_worker/service_worker_provider_host_unittest.cc new file mode 100644 index 00000000000..1f52514a603 --- /dev/null +++ b/chromium/content/browser/service_worker/service_worker_provider_host_unittest.cc @@ -0,0 +1,283 @@ +// Copyright 2014 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 "base/basictypes.h" +#include "base/memory/weak_ptr.h" +#include "content/browser/service_worker/service_worker_context_core.h" +#include "content/browser/service_worker/service_worker_provider_host.h" +#include "content/browser/service_worker/service_worker_register_job.h" +#include "content/browser/service_worker/service_worker_registration.h" +#include "content/browser/service_worker/service_worker_version.h" +#include "content/public/test/test_browser_thread_bundle.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace content { + +static const int kRenderProcessId = 33; // Dummy process ID for testing. + +class ServiceWorkerProviderHostTest : public testing::Test { + protected: + ServiceWorkerProviderHostTest() + : thread_bundle_(TestBrowserThreadBundle::IO_MAINLOOP) {} + virtual ~ServiceWorkerProviderHostTest() {} + + virtual void SetUp() OVERRIDE { + context_.reset( + new ServiceWorkerContextCore(base::FilePath(), + base::MessageLoopProxy::current(), + base::MessageLoopProxy::current(), + NULL, + NULL, + NULL)); + + scope_ = GURL("http://www.example.com/*"); + script_url_ = GURL("http://www.example.com/service_worker.js"); + registration_ = new ServiceWorkerRegistration( + scope_, script_url_, 1L, context_->AsWeakPtr()); + version_ = new ServiceWorkerVersion( + registration_, + 1L, context_->AsWeakPtr()); + + // Prepare provider hosts (for the same process). + scoped_ptr<ServiceWorkerProviderHost> host1(new ServiceWorkerProviderHost( + kRenderProcessId, 1 /* provider_id */, + context_->AsWeakPtr(), NULL)); + scoped_ptr<ServiceWorkerProviderHost> host2(new ServiceWorkerProviderHost( + kRenderProcessId, 2 /* provider_id */, + context_->AsWeakPtr(), NULL)); + scoped_ptr<ServiceWorkerProviderHost> host3(new ServiceWorkerProviderHost( + kRenderProcessId, 3 /* provider_id */, + context_->AsWeakPtr(), NULL)); + provider_host1_ = host1->AsWeakPtr(); + provider_host2_ = host2->AsWeakPtr(); + provider_host3_ = host3->AsWeakPtr(); + context_->AddProviderHost(make_scoped_ptr(host1.release())); + context_->AddProviderHost(make_scoped_ptr(host2.release())); + context_->AddProviderHost(make_scoped_ptr(host3.release())); + } + + virtual void TearDown() OVERRIDE { + version_ = 0; + registration_ = 0; + context_.reset(); + } + + content::TestBrowserThreadBundle thread_bundle_; + scoped_ptr<ServiceWorkerContextCore> context_; + scoped_refptr<ServiceWorkerRegistration> registration_; + scoped_refptr<ServiceWorkerVersion> version_; + base::WeakPtr<ServiceWorkerProviderHost> provider_host1_; + base::WeakPtr<ServiceWorkerProviderHost> provider_host2_; + base::WeakPtr<ServiceWorkerProviderHost> provider_host3_; + GURL scope_; + GURL script_url_; + + private: + DISALLOW_COPY_AND_ASSIGN(ServiceWorkerProviderHostTest); +}; + +TEST_F(ServiceWorkerProviderHostTest, SetActiveVersion_ProcessStatus) { + ASSERT_FALSE(version_->HasProcessToRun()); + + // Associating version_ to a provider_host's active version will internally + // add the provider_host's process ref to the version. + provider_host1_->SetActiveVersion(version_); + ASSERT_TRUE(version_->HasProcessToRun()); + + // Re-associating the same version and provider_host should just work too. + provider_host1_->SetActiveVersion(version_); + ASSERT_TRUE(version_->HasProcessToRun()); + + // Resetting the provider_host's active version should remove process refs + // from the version. + provider_host1_->SetActiveVersion(NULL); + ASSERT_FALSE(version_->HasProcessToRun()); +} + +TEST_F(ServiceWorkerProviderHostTest, + SetActiveVersion_MultipleHostsForSameProcess) { + ASSERT_FALSE(version_->HasProcessToRun()); + + // Associating version_ to two providers as active version. + provider_host1_->SetActiveVersion(version_); + provider_host2_->SetActiveVersion(version_); + ASSERT_TRUE(version_->HasProcessToRun()); + + // Disassociating one provider_host shouldn't remove all process refs + // from the version yet. + provider_host1_->SetActiveVersion(NULL); + ASSERT_TRUE(version_->HasProcessToRun()); + + // Disassociating the other provider_host will remove all process refs. + provider_host2_->SetActiveVersion(NULL); + ASSERT_FALSE(version_->HasProcessToRun()); +} + +TEST_F(ServiceWorkerProviderHostTest, SetWaitingVersion_ProcessStatus) { + ASSERT_FALSE(version_->HasProcessToRun()); + + // Associating version_ to a provider_host's waiting version will internally + // add the provider_host's process ref to the version. + provider_host1_->SetWaitingVersion(version_); + ASSERT_TRUE(version_->HasProcessToRun()); + + // Re-associating the same version and provider_host should just work too. + provider_host1_->SetWaitingVersion(version_); + ASSERT_TRUE(version_->HasProcessToRun()); + + // Resetting the provider_host's waiting version should remove process refs + // from the version. + provider_host1_->SetWaitingVersion(NULL); + ASSERT_FALSE(version_->HasProcessToRun()); +} + +TEST_F(ServiceWorkerProviderHostTest, + SetWaitingVersion_MultipleHostsForSameProcess) { + ASSERT_FALSE(version_->HasProcessToRun()); + + // Associating version_ to two providers as active version. + provider_host1_->SetWaitingVersion(version_); + provider_host2_->SetWaitingVersion(version_); + ASSERT_TRUE(version_->HasProcessToRun()); + + // Disassociating one provider_host shouldn't remove all process refs + // from the version yet. + provider_host1_->SetWaitingVersion(NULL); + ASSERT_TRUE(version_->HasProcessToRun()); + + // Disassociating the other provider_host will remove all process refs. + provider_host2_->SetWaitingVersion(NULL); + ASSERT_FALSE(version_->HasProcessToRun()); +} + +class ServiceWorkerProviderHostWaitingVersionTest : public testing::Test { + protected: + ServiceWorkerProviderHostWaitingVersionTest() + : thread_bundle_(TestBrowserThreadBundle::IO_MAINLOOP), + next_provider_id_(1L) {} + virtual ~ServiceWorkerProviderHostWaitingVersionTest() {} + + virtual void SetUp() OVERRIDE { + context_.reset( + new ServiceWorkerContextCore(base::FilePath(), + base::MessageLoopProxy::current(), + base::MessageLoopProxy::current(), + NULL, + NULL, + NULL)); + + // Prepare provider hosts (for the same process). + provider_host1_ = CreateProviderHost(GURL("http://www.example.com/foo")); + provider_host2_ = CreateProviderHost(GURL("http://www.example.com/bar")); + provider_host3_ = CreateProviderHost(GURL("http://www.example.ca/foo")); + } + + base::WeakPtr<ServiceWorkerProviderHost> CreateProviderHost( + const GURL& document_url) { + scoped_ptr<ServiceWorkerProviderHost> host(new ServiceWorkerProviderHost( + kRenderProcessId, next_provider_id_++, context_->AsWeakPtr(), NULL)); + host->SetDocumentUrl(document_url); + base::WeakPtr<ServiceWorkerProviderHost> provider_host = host->AsWeakPtr(); + context_->AddProviderHost(host.Pass()); + return provider_host; + } + + virtual void TearDown() OVERRIDE { + context_.reset(); + } + + content::TestBrowserThreadBundle thread_bundle_; + scoped_ptr<ServiceWorkerContextCore> context_; + base::WeakPtr<ServiceWorkerProviderHost> provider_host1_; + base::WeakPtr<ServiceWorkerProviderHost> provider_host2_; + base::WeakPtr<ServiceWorkerProviderHost> provider_host3_; + + private: + int64 next_provider_id_; + + DISALLOW_COPY_AND_ASSIGN(ServiceWorkerProviderHostWaitingVersionTest); +}; + +TEST_F(ServiceWorkerProviderHostWaitingVersionTest, + AssociateWaitingVersionToDocuments) { + const GURL scope1("http://www.example.com/*"); + const GURL script_url1("http://www.example.com/service_worker1.js"); + scoped_refptr<ServiceWorkerRegistration> registration1( + new ServiceWorkerRegistration( + scope1, script_url1, 1L, context_->AsWeakPtr())); + scoped_refptr<ServiceWorkerVersion> version1( + new ServiceWorkerVersion(registration1, 1L, context_->AsWeakPtr())); + + ServiceWorkerRegisterJob::AssociateWaitingVersionToDocuments( + context_->AsWeakPtr(), version1.get()); + EXPECT_EQ(version1.get(), provider_host1_->waiting_version()); + EXPECT_EQ(version1.get(), provider_host2_->waiting_version()); + EXPECT_EQ(NULL, provider_host3_->waiting_version()); + + // Version2 is associated with the same registration as version1, so the + // waiting version of host1 and host2 should be replaced. + scoped_refptr<ServiceWorkerVersion> version2( + new ServiceWorkerVersion(registration1, 2L, context_->AsWeakPtr())); + ServiceWorkerRegisterJob::AssociateWaitingVersionToDocuments( + context_->AsWeakPtr(), version2.get()); + EXPECT_EQ(version2.get(), provider_host1_->waiting_version()); + EXPECT_EQ(version2.get(), provider_host2_->waiting_version()); + EXPECT_EQ(NULL, provider_host3_->waiting_version()); + + const GURL scope3(provider_host1_->document_url()); + const GURL script_url3("http://www.example.com/service_worker3.js"); + scoped_refptr<ServiceWorkerRegistration> registration3( + new ServiceWorkerRegistration( + scope3, script_url3, 3L, context_->AsWeakPtr())); + scoped_refptr<ServiceWorkerVersion> version3( + new ServiceWorkerVersion(registration3, 3L, context_->AsWeakPtr())); + + // Although version3 can match longer than version2 for host1, it should be + // ignored because version3 is associated with a different registration from + // version2. + ServiceWorkerRegisterJob::AssociateWaitingVersionToDocuments( + context_->AsWeakPtr(), version3.get()); + EXPECT_EQ(version2.get(), provider_host1_->waiting_version()); + EXPECT_EQ(version2.get(), provider_host2_->waiting_version()); + EXPECT_EQ(NULL, provider_host3_->waiting_version()); +} + +TEST_F(ServiceWorkerProviderHostWaitingVersionTest, + DisassociateWaitingVersionFromDocuments) { + const GURL scope1("http://www.example.com/*"); + const GURL script_url1("http://www.example.com/service_worker.js"); + scoped_refptr<ServiceWorkerRegistration> registration1( + new ServiceWorkerRegistration( + scope1, script_url1, 1L, context_->AsWeakPtr())); + scoped_refptr<ServiceWorkerVersion> version1( + new ServiceWorkerVersion(registration1, 1L, context_->AsWeakPtr())); + + const GURL scope2("http://www.example.ca/*"); + const GURL script_url2("http://www.example.ca/service_worker.js"); + scoped_refptr<ServiceWorkerRegistration> registration2( + new ServiceWorkerRegistration( + scope2, script_url2, 2L, context_->AsWeakPtr())); + scoped_refptr<ServiceWorkerVersion> version2( + new ServiceWorkerVersion(registration2, 2L, context_->AsWeakPtr())); + + ServiceWorkerRegisterJob::AssociateWaitingVersionToDocuments( + context_->AsWeakPtr(), version1.get()); + ServiceWorkerRegisterJob::AssociateWaitingVersionToDocuments( + context_->AsWeakPtr(), version2.get()); + + // Host1 and host2 are associated with version1 as a waiting version, whereas + // host3 is associated with version2. + EXPECT_EQ(version1.get(), provider_host1_->waiting_version()); + EXPECT_EQ(version1.get(), provider_host2_->waiting_version()); + EXPECT_EQ(version2.get(), provider_host3_->waiting_version()); + + // Disassociate version1 from host1 and host2. + ServiceWorkerRegisterJob::DisassociateWaitingVersionFromDocuments( + context_->AsWeakPtr(), version1->version_id()); + EXPECT_EQ(NULL, provider_host1_->waiting_version()); + EXPECT_EQ(NULL, provider_host2_->waiting_version()); + EXPECT_EQ(version2.get(), provider_host3_->waiting_version()); +} + +} // namespace content diff --git a/chromium/content/browser/service_worker/service_worker_read_from_cache_job.cc b/chromium/content/browser/service_worker/service_worker_read_from_cache_job.cc new file mode 100644 index 00000000000..5189a86c2bd --- /dev/null +++ b/chromium/content/browser/service_worker/service_worker_read_from_cache_job.cc @@ -0,0 +1,191 @@ +// Copyright 2014 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 "content/browser/service_worker/service_worker_read_from_cache_job.h" + +#include "content/browser/service_worker/service_worker_context_core.h" +#include "content/browser/service_worker/service_worker_disk_cache.h" +#include "content/browser/service_worker/service_worker_histograms.h" +#include "net/base/io_buffer.h" +#include "net/base/net_errors.h" +#include "net/http/http_request_headers.h" +#include "net/http/http_response_headers.h" +#include "net/http/http_util.h" +#include "net/url_request/url_request.h" +#include "net/url_request/url_request_status.h" + +namespace content { + +ServiceWorkerReadFromCacheJob::ServiceWorkerReadFromCacheJob( + net::URLRequest* request, + net::NetworkDelegate* network_delegate, + base::WeakPtr<ServiceWorkerContextCore> context, + int64 response_id) + : net::URLRequestJob(request, network_delegate), + context_(context), + response_id_(response_id), + has_been_killed_(false), + weak_factory_(this) { +} + +ServiceWorkerReadFromCacheJob::~ServiceWorkerReadFromCacheJob() { +} + +void ServiceWorkerReadFromCacheJob::Start() { + if (!context_) { + NotifyStartError(net::URLRequestStatus( + net::URLRequestStatus::FAILED, net::ERR_FAILED)); + return; + } + + // Create a response reader and start reading the headers, + // we'll continue when thats done. + reader_ = context_->storage()->CreateResponseReader(response_id_); + http_info_io_buffer_ = new HttpResponseInfoIOBuffer; + reader_->ReadInfo( + http_info_io_buffer_, + base::Bind(&ServiceWorkerReadFromCacheJob::OnReadInfoComplete, + weak_factory_.GetWeakPtr())); + SetStatus(net::URLRequestStatus(net::URLRequestStatus::IO_PENDING, 0)); +} + +void ServiceWorkerReadFromCacheJob::Kill() { + if (has_been_killed_) + return; + weak_factory_.InvalidateWeakPtrs(); + has_been_killed_ = true; + reader_.reset(); + context_.reset(); + http_info_io_buffer_ = NULL; + http_info_.reset(); + range_response_info_.reset(); + net::URLRequestJob::Kill(); +} + +net::LoadState ServiceWorkerReadFromCacheJob::GetLoadState() const { + if (reader_.get() && reader_->IsReadPending()) + return net::LOAD_STATE_READING_RESPONSE; + return net::LOAD_STATE_IDLE; +} + +bool ServiceWorkerReadFromCacheJob::GetCharset(std::string* charset) { + if (!http_info()) + return false; + return http_info()->headers->GetCharset(charset); +} + +bool ServiceWorkerReadFromCacheJob::GetMimeType(std::string* mime_type) const { + if (!http_info()) + return false; + return http_info()->headers->GetMimeType(mime_type); +} + +void ServiceWorkerReadFromCacheJob::GetResponseInfo( + net::HttpResponseInfo* info) { + if (!http_info()) + return; + *info = *http_info(); +} + +int ServiceWorkerReadFromCacheJob::GetResponseCode() const { + if (!http_info()) + return -1; + return http_info()->headers->response_code(); +} + +void ServiceWorkerReadFromCacheJob::SetExtraRequestHeaders( + const net::HttpRequestHeaders& headers) { + std::string value; + std::vector<net::HttpByteRange> ranges; + if (!headers.GetHeader(net::HttpRequestHeaders::kRange, &value) || + !net::HttpUtil::ParseRangeHeader(value, &ranges)) { + return; + } + + // If multiple ranges are requested, we play dumb and + // return the entire response with 200 OK. + if (ranges.size() == 1U) + range_requested_ = ranges[0]; +} + +bool ServiceWorkerReadFromCacheJob::ReadRawData( + net::IOBuffer* buf, + int buf_size, + int *bytes_read) { + DCHECK_NE(buf_size, 0); + DCHECK(bytes_read); + DCHECK(!reader_->IsReadPending()); + reader_->ReadData( + buf, buf_size, base::Bind(&ServiceWorkerReadFromCacheJob::OnReadComplete, + weak_factory_.GetWeakPtr())); + SetStatus(net::URLRequestStatus(net::URLRequestStatus::IO_PENDING, 0)); + return false; +} + +const net::HttpResponseInfo* ServiceWorkerReadFromCacheJob::http_info() const { + if (!http_info_) + return NULL; + if (range_response_info_) + return range_response_info_.get(); + return http_info_.get(); +} + +void ServiceWorkerReadFromCacheJob::OnReadInfoComplete(int result) { + scoped_refptr<ServiceWorkerReadFromCacheJob> protect(this); + if (!http_info_io_buffer_->http_info) { + DCHECK(result < 0); + ServiceWorkerHistograms::CountReadResponseResult( + ServiceWorkerHistograms::READ_HEADERS_ERROR); + NotifyDone(net::URLRequestStatus(net::URLRequestStatus::FAILED, result)); + return; + } + DCHECK(result >= 0); + SetStatus(net::URLRequestStatus()); // Clear the IO_PENDING status + http_info_.reset(http_info_io_buffer_->http_info.release()); + if (is_range_request()) + SetupRangeResponse(http_info_io_buffer_->response_data_size); + http_info_io_buffer_ = NULL; + NotifyHeadersComplete(); +} + +void ServiceWorkerReadFromCacheJob::SetupRangeResponse(int resource_size) { + DCHECK(is_range_request() && http_info_.get() && reader_.get()); + if (resource_size < 0 || !range_requested_.ComputeBounds(resource_size)) { + range_requested_ = net::HttpByteRange(); + return; + } + + DCHECK(range_requested_.IsValid()); + int offset = static_cast<int>(range_requested_.first_byte_position()); + int length = static_cast<int>(range_requested_.last_byte_position() - + range_requested_.first_byte_position() + 1); + + // Tell the reader about the range to read. + reader_->SetReadRange(offset, length); + + // Make a copy of the full response headers and fix them up + // for the range we'll be returning. + range_response_info_.reset(new net::HttpResponseInfo(*http_info_)); + net::HttpResponseHeaders* headers = range_response_info_->headers.get(); + headers->UpdateWithNewRange( + range_requested_, resource_size, true /* replace status line */); +} + +void ServiceWorkerReadFromCacheJob::OnReadComplete(int result) { + ServiceWorkerHistograms::ReadResponseResult check_result; + if (result == 0) { + check_result = ServiceWorkerHistograms::READ_OK; + NotifyDone(net::URLRequestStatus()); + } else if (result < 0) { + check_result = ServiceWorkerHistograms::READ_DATA_ERROR; + NotifyDone(net::URLRequestStatus(net::URLRequestStatus::FAILED, result)); + } else { + check_result = ServiceWorkerHistograms::READ_OK; + SetStatus(net::URLRequestStatus()); // Clear the IO_PENDING status + } + ServiceWorkerHistograms::CountReadResponseResult(check_result); + NotifyReadComplete(result); +} + +} // namespace content diff --git a/chromium/content/browser/service_worker/service_worker_read_from_cache_job.h b/chromium/content/browser/service_worker/service_worker_read_from_cache_job.h new file mode 100644 index 00000000000..5fb74015127 --- /dev/null +++ b/chromium/content/browser/service_worker/service_worker_read_from_cache_job.h @@ -0,0 +1,73 @@ +// Copyright 2014 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 CONTENT_BROWSER_SERVICE_WORKER_SERVICE_WORKER_READ_FROM_CACHE_JOB_H_ +#define CONTENT_BROWSER_SERVICE_WORKER_SERVICE_WORKER_READ_FROM_CACHE_JOB_H_ + +#include "base/memory/ref_counted.h" +#include "base/memory/weak_ptr.h" +#include "content/browser/service_worker/service_worker_disk_cache.h" +#include "content/common/content_export.h" +#include "net/http/http_byte_range.h" +#include "net/url_request/url_request_job.h" + +namespace content { + +class ServiceWorkerContextCore; +class ServiceWorkerResponseReader; + +// A URLRequestJob derivative used to retrieve script resources +// from the service workers script cache. It uses a response reader +// and pipes the response to the consumer of this url request job. +class CONTENT_EXPORT ServiceWorkerReadFromCacheJob + : public net::URLRequestJob { + public: + ServiceWorkerReadFromCacheJob( + net::URLRequest* request, + net::NetworkDelegate* network_delegate, + base::WeakPtr<ServiceWorkerContextCore> context, + int64 response_id); + + private: + virtual ~ServiceWorkerReadFromCacheJob(); + + // net::URLRequestJob overrides + virtual void Start() OVERRIDE; + virtual void Kill() OVERRIDE; + virtual net::LoadState GetLoadState() const OVERRIDE; + virtual bool GetCharset(std::string* charset) OVERRIDE; + virtual bool GetMimeType(std::string* mime_type) const OVERRIDE; + virtual void GetResponseInfo(net::HttpResponseInfo* info) OVERRIDE; + virtual int GetResponseCode() const OVERRIDE; + virtual void SetExtraRequestHeaders( + const net::HttpRequestHeaders& headers) OVERRIDE; + virtual bool ReadRawData(net::IOBuffer* buf, + int buf_size, + int *bytes_read) OVERRIDE; + + // Reader completion callbacks. + void OnReadInfoComplete(int result); + void OnReadComplete(int result); + + // Helpers + const net::HttpResponseInfo* http_info() const; + bool is_range_request() const { return range_requested_.IsValid(); } + void SetupRangeResponse(int response_data_size); + + base::WeakPtr<ServiceWorkerContextCore> context_; + int64 response_id_; + scoped_ptr<ServiceWorkerResponseReader> reader_; + scoped_refptr<HttpResponseInfoIOBuffer> http_info_io_buffer_; + scoped_ptr<net::HttpResponseInfo> http_info_; + net::HttpByteRange range_requested_; + scoped_ptr<net::HttpResponseInfo> range_response_info_; + bool has_been_killed_; + base::WeakPtrFactory<ServiceWorkerReadFromCacheJob> weak_factory_; + + DISALLOW_COPY_AND_ASSIGN(ServiceWorkerReadFromCacheJob); +}; + +} // namespace content + +#endif // CONTENT_BROWSER_SERVICE_WORKER_SERVICE_WORKER_READ_FROM_CACHE_JOB_H_ diff --git a/chromium/content/browser/service_worker/service_worker_register_job.cc b/chromium/content/browser/service_worker/service_worker_register_job.cc index 90333595891..098b6b31df8 100644 --- a/chromium/content/browser/service_worker/service_worker_register_job.cc +++ b/chromium/content/browser/service_worker/service_worker_register_job.cc @@ -4,114 +4,466 @@ #include "content/browser/service_worker/service_worker_register_job.h" +#include <vector> + +#include "base/message_loop/message_loop.h" +#include "content/browser/service_worker/service_worker_context_core.h" +#include "content/browser/service_worker/service_worker_job_coordinator.h" #include "content/browser/service_worker/service_worker_registration.h" -#include "content/public/browser/browser_thread.h" -#include "url/gurl.h" +#include "content/browser/service_worker/service_worker_storage.h" +#include "content/browser/service_worker/service_worker_utils.h" namespace content { +namespace { + +void RunSoon(const base::Closure& closure) { + base::MessageLoop::current()->PostTask(FROM_HERE, closure); +} + +} + +typedef ServiceWorkerRegisterJobBase::RegistrationJobType RegistrationJobType; + ServiceWorkerRegisterJob::ServiceWorkerRegisterJob( - const base::WeakPtr<ServiceWorkerStorage>& storage, - const RegistrationCompleteCallback& callback) - : storage_(storage), callback_(callback), weak_factory_(this) {} - -ServiceWorkerRegisterJob::~ServiceWorkerRegisterJob() {} - -void ServiceWorkerRegisterJob::StartRegister(const GURL& pattern, - const GURL& script_url) { - // Set up a chain of callbacks, in reverse order. Each of these - // callbacks may be called asynchronously by the previous callback. - ServiceWorkerStorage::RegistrationCallback finish_registration(base::Bind( - &ServiceWorkerRegisterJob::RegisterComplete, weak_factory_.GetWeakPtr())); - - ServiceWorkerStorage::UnregistrationCallback register_new( - base::Bind(&ServiceWorkerRegisterJob::RegisterPatternAndContinue, - weak_factory_.GetWeakPtr(), - pattern, - script_url, - finish_registration)); - - ServiceWorkerStorage::FindRegistrationCallback unregister_old( - base::Bind(&ServiceWorkerRegisterJob::UnregisterPatternAndContinue, - weak_factory_.GetWeakPtr(), - pattern, - script_url, - register_new)); - - storage_->FindRegistrationForPattern(pattern, unregister_old); -} - -void ServiceWorkerRegisterJob::StartUnregister(const GURL& pattern) { - // Set up a chain of callbacks, in reverse order. Each of these - // callbacks may be called asynchronously by the previous callback. - ServiceWorkerStorage::UnregistrationCallback finish_unregistration( - base::Bind(&ServiceWorkerRegisterJob::UnregisterComplete, + base::WeakPtr<ServiceWorkerContextCore> context, + const GURL& pattern, + const GURL& script_url) + : context_(context), + pattern_(pattern), + script_url_(script_url), + phase_(INITIAL), + is_promise_resolved_(false), + promise_resolved_status_(SERVICE_WORKER_OK), + weak_factory_(this) {} + +ServiceWorkerRegisterJob::~ServiceWorkerRegisterJob() { + DCHECK(!context_ || + phase_ == INITIAL || phase_ == COMPLETE || phase_ == ABORT) + << "Jobs should only be interrupted during shutdown."; +} + +void ServiceWorkerRegisterJob::AddCallback(const RegistrationCallback& callback, + int process_id) { + if (!is_promise_resolved_) { + callbacks_.push_back(callback); + if (process_id != -1 && (phase_ < UPDATE || !pending_version())) + pending_process_ids_.push_back(process_id); + return; + } + RunSoon(base::Bind( + callback, promise_resolved_status_, + promise_resolved_registration_, promise_resolved_version_)); +} + +void ServiceWorkerRegisterJob::Start() { + SetPhase(START); + context_->storage()->FindRegistrationForPattern( + pattern_, + base::Bind( + &ServiceWorkerRegisterJob::HandleExistingRegistrationAndContinue, + weak_factory_.GetWeakPtr())); +} + +void ServiceWorkerRegisterJob::Abort() { + SetPhase(ABORT); + CompleteInternal(SERVICE_WORKER_ERROR_ABORT); + // Don't have to call FinishJob() because the caller takes care of removing + // the jobs from the queue. +} + +bool ServiceWorkerRegisterJob::Equals(ServiceWorkerRegisterJobBase* job) { + if (job->GetType() != GetType()) + return false; + ServiceWorkerRegisterJob* register_job = + static_cast<ServiceWorkerRegisterJob*>(job); + return register_job->pattern_ == pattern_ && + register_job->script_url_ == script_url_; +} + +RegistrationJobType ServiceWorkerRegisterJob::GetType() { + return REGISTRATION; +} + +ServiceWorkerRegisterJob::Internal::Internal() {} + +ServiceWorkerRegisterJob::Internal::~Internal() {} + +void ServiceWorkerRegisterJob::set_registration( + ServiceWorkerRegistration* registration) { + DCHECK(phase_ == START || phase_ == REGISTER) << phase_; + DCHECK(!internal_.registration); + internal_.registration = registration; +} + +ServiceWorkerRegistration* ServiceWorkerRegisterJob::registration() { + DCHECK(phase_ >= REGISTER) << phase_; + return internal_.registration; +} + +void ServiceWorkerRegisterJob::set_pending_version( + ServiceWorkerVersion* version) { + DCHECK(phase_ == UPDATE) << phase_; + DCHECK(!internal_.pending_version); + internal_.pending_version = version; +} + +ServiceWorkerVersion* ServiceWorkerRegisterJob::pending_version() { + DCHECK(phase_ >= UPDATE) << phase_; + return internal_.pending_version; +} + +void ServiceWorkerRegisterJob::SetPhase(Phase phase) { + switch (phase) { + case INITIAL: + NOTREACHED(); + break; + case START: + DCHECK(phase_ == INITIAL) << phase_; + break; + case REGISTER: + DCHECK(phase_ == START) << phase_; + break; + case UPDATE: + DCHECK(phase_ == START || phase_ == REGISTER) << phase_; + break; + case INSTALL: + DCHECK(phase_ == UPDATE) << phase_; + break; + case STORE: + DCHECK(phase_ == INSTALL) << phase_; + break; + case ACTIVATE: + DCHECK(phase_ == STORE) << phase_; + break; + case COMPLETE: + DCHECK(phase_ != INITIAL && phase_ != COMPLETE) << phase_; + break; + case ABORT: + break; + } + phase_ = phase; +} + +// This function corresponds to the steps in Register following +// "Let serviceWorkerRegistration be _GetRegistration(scope)" +// |existing_registration| corresponds to serviceWorkerRegistration. +// Throughout this file, comments in quotes are excerpts from the spec. +void ServiceWorkerRegisterJob::HandleExistingRegistrationAndContinue( + ServiceWorkerStatusCode status, + const scoped_refptr<ServiceWorkerRegistration>& existing_registration) { + // On unexpected error, abort this registration job. + if (status != SERVICE_WORKER_ERROR_NOT_FOUND && status != SERVICE_WORKER_OK) { + Complete(status); + return; + } + + // "If serviceWorkerRegistration is not null and script is equal to + // serviceWorkerRegistration.scriptUrl..." resolve with the existing + // registration and abort. + if (existing_registration.get() && + existing_registration->script_url() == script_url_) { + set_registration(existing_registration); + // If there's no active version, go ahead to Update (this isn't in the spec + // but seems reasonable, and without SoftUpdate implemented we can never + // Update otherwise). + if (!existing_registration->active_version()) { + UpdateAndContinue(status); + return; + } + ResolvePromise( + status, existing_registration, existing_registration->active_version()); + Complete(SERVICE_WORKER_OK); + return; + } + + // "If serviceWorkerRegistration is null..." create a new registration. + if (!existing_registration.get()) { + RegisterAndContinue(SERVICE_WORKER_OK); + return; + } + + // On script URL mismatch, "set serviceWorkerRegistration.scriptUrl to + // script." We accomplish this by deleting the existing registration and + // registering a new one. + // TODO(falken): Match the spec. We now throw away the active_version_ and + // waiting_version_ of the existing registration, which isn't in the spec. + // TODO(michaeln): Deactivate the live existing_registration object and + // eventually call storage->DeleteVersionResources() + // when it no longer has any controllees. + context_->storage()->DeleteRegistration( + existing_registration->id(), + existing_registration->script_url().GetOrigin(), + base::Bind(&ServiceWorkerRegisterJob::RegisterAndContinue, weak_factory_.GetWeakPtr())); +} - ServiceWorkerStorage::FindRegistrationCallback unregister( - base::Bind(&ServiceWorkerRegisterJob::UnregisterPatternAndContinue, - weak_factory_.GetWeakPtr(), - pattern, - GURL(), - finish_unregistration)); +// Creates a new ServiceWorkerRegistration. +void ServiceWorkerRegisterJob::RegisterAndContinue( + ServiceWorkerStatusCode status) { + SetPhase(REGISTER); + if (status != SERVICE_WORKER_OK) { + // Abort this registration job. + Complete(status); + return; + } - storage_->FindRegistrationForPattern(pattern, unregister); + set_registration(new ServiceWorkerRegistration( + pattern_, script_url_, context_->storage()->NewRegistrationId(), + context_)); + context_->storage()->NotifyInstallingRegistration(registration()); + UpdateAndContinue(SERVICE_WORKER_OK); } -void ServiceWorkerRegisterJob::RegisterPatternAndContinue( - const GURL& pattern, - const GURL& script_url, - const ServiceWorkerStorage::RegistrationCallback& callback, - ServiceWorkerRegistrationStatus previous_status) { - if (previous_status != REGISTRATION_OK) { - BrowserThread::PostTask( - BrowserThread::IO, - FROM_HERE, - base::Bind(callback, - previous_status, - scoped_refptr<ServiceWorkerRegistration>())); +// This function corresponds to the spec's _Update algorithm. +void ServiceWorkerRegisterJob::UpdateAndContinue( + ServiceWorkerStatusCode status) { + SetPhase(UPDATE); + if (status != SERVICE_WORKER_OK) { + // Abort this registration job. + Complete(status); return; } - // TODO: Eventually RegisterInternal will be replaced by an asynchronous - // operation. Pass its resulting status through 'callback'. - scoped_refptr<ServiceWorkerRegistration> registration = - storage_->RegisterInternal(pattern, script_url); - BrowserThread::PostTask(BrowserThread::IO, - FROM_HERE, - base::Bind(callback, REGISTRATION_OK, registration)); + // TODO(falken): "If serviceWorkerRegistration.installingWorker is not null.." + // then terminate the installing worker. It doesn't make sense to implement + // yet since we always activate the worker if install completed, so there can + // be no installing worker at this point. + // TODO(nhiroki): Check 'installing_version()' instead when it's supported. + DCHECK(!registration()->waiting_version()); + + // "Let serviceWorker be a newly-created ServiceWorker object..." and start + // the worker. + set_pending_version(new ServiceWorkerVersion( + registration(), context_->storage()->NewVersionId(), context_)); + + // TODO(michaeln): Start the worker into a paused state where the + // script resource is downloaded but not yet evaluated. + pending_version()->StartWorkerWithCandidateProcesses( + pending_process_ids_, + base::Bind(&ServiceWorkerRegisterJob::OnStartWorkerFinished, + weak_factory_.GetWeakPtr())); } -void ServiceWorkerRegisterJob::UnregisterPatternAndContinue( - const GURL& pattern, - const GURL& new_script_url, - const ServiceWorkerStorage::UnregistrationCallback& callback, - bool found, - ServiceWorkerRegistrationStatus previous_status, - const scoped_refptr<ServiceWorkerRegistration>& previous_registration) { - - // The previous registration may not exist, which is ok. - if (previous_status == REGISTRATION_OK && found && - (new_script_url.is_empty() || - previous_registration->script_url() != new_script_url)) { - // TODO: Eventually UnregisterInternal will be replaced by an - // asynchronous operation. Pass its resulting status though - // 'callback'. - storage_->UnregisterInternal(pattern); - } - BrowserThread::PostTask( - BrowserThread::IO, FROM_HERE, base::Bind(callback, previous_status)); -} - -void ServiceWorkerRegisterJob::UnregisterComplete( - ServiceWorkerRegistrationStatus status) { - callback_.Run(this, status, NULL); -} - -void ServiceWorkerRegisterJob::RegisterComplete( - ServiceWorkerRegistrationStatus status, - const scoped_refptr<ServiceWorkerRegistration>& registration) { - callback_.Run(this, status, registration); +void ServiceWorkerRegisterJob::OnStartWorkerFinished( + ServiceWorkerStatusCode status) { + // "If serviceWorker fails to start up..." then reject the promise with an + // error and abort. + if (status != SERVICE_WORKER_OK) { + Complete(status); + return; + } + + // TODO(michaeln): Compare the old and new script. + // If different unpause the worker and continue with + // the job. If the same ResolvePromise with the current + // version and complete the job, throwing away the new version + // since there's nothing new. + + // "Resolve promise with serviceWorker." + // Although the spec doesn't set waitingWorker until after resolving the + // promise, our system's resolving works by passing ServiceWorkerRegistration + // to the callbacks, so waitingWorker must be set first. + DCHECK(!registration()->waiting_version()); + registration()->set_waiting_version(pending_version()); + ResolvePromise(status, registration(), pending_version()); + + AssociateWaitingVersionToDocuments(context_, pending_version()); + + InstallAndContinue(); +} + +// This function corresponds to the spec's _Install algorithm. +void ServiceWorkerRegisterJob::InstallAndContinue() { + SetPhase(INSTALL); + // "Set serviceWorkerRegistration.installingWorker._state to installing." + // "Fire install event on the associated ServiceWorkerGlobalScope object." + pending_version()->DispatchInstallEvent( + -1, + base::Bind(&ServiceWorkerRegisterJob::OnInstallFinished, + weak_factory_.GetWeakPtr())); +} + +void ServiceWorkerRegisterJob::OnInstallFinished( + ServiceWorkerStatusCode status) { + // "If any handler called waitUntil()..." and the resulting promise + // is rejected, abort. + // TODO(kinuko,falken): For some error cases (e.g. ServiceWorker is + // unexpectedly terminated) we may want to retry sending the event again. + if (status != SERVICE_WORKER_OK) { + Complete(status); + return; + } + + SetPhase(STORE); + context_->storage()->StoreRegistration( + registration(), + pending_version(), + base::Bind(&ServiceWorkerRegisterJob::OnStoreRegistrationComplete, + weak_factory_.GetWeakPtr())); +} + +void ServiceWorkerRegisterJob::OnStoreRegistrationComplete( + ServiceWorkerStatusCode status) { + if (status != SERVICE_WORKER_OK) { + Complete(status); + return; + } + + ActivateAndContinue(); +} + +// This function corresponds to the spec's _Activate algorithm. +void ServiceWorkerRegisterJob::ActivateAndContinue() { + SetPhase(ACTIVATE); + + // "If existingWorker is not null, then: wait for exitingWorker to finish + // handling any in-progress requests." + // See if we already have an active_version for the scope and it has + // controllee documents (if so activating the new version should wait + // until we have no documents controlled by the version). + if (registration()->active_version() && + registration()->active_version()->HasControllee()) { + // TODO(kinuko,falken): Currently we immediately return if the existing + // registration already has an active version, so we shouldn't come + // this way. + NOTREACHED(); + // TODO(falken): Register an continuation task to wait for NoControllees + // notification so that we can resume activation later (see comments + // in ServiceWorkerVersion::RemoveControllee). + Complete(SERVICE_WORKER_OK); + return; + } + + // "Set serviceWorkerRegistration.waitingWorker to null." + // "Set serviceWorkerRegistration.activeWorker to activatingWorker." + DisassociateWaitingVersionFromDocuments( + context_, pending_version()->version_id()); + registration()->set_waiting_version(NULL); + DCHECK(!registration()->active_version()); + registration()->set_active_version(pending_version()); + + // "Set serviceWorkerRegistration.activeWorker._state to activating." + // "Fire activate event on the associated ServiceWorkerGlobalScope object." + // "Set serviceWorkerRegistration.activeWorker._state to active." + pending_version()->DispatchActivateEvent( + base::Bind(&ServiceWorkerRegisterJob::OnActivateFinished, + weak_factory_.GetWeakPtr())); +} + +void ServiceWorkerRegisterJob::OnActivateFinished( + ServiceWorkerStatusCode status) { + // "If any handler called waitUntil()..." and the resulting promise + // is rejected, abort. + // TODO(kinuko,falken): For some error cases (e.g. ServiceWorker is + // unexpectedly terminated) we may want to retry sending the event again. + if (status != SERVICE_WORKER_OK) { + registration()->set_active_version(NULL); + Complete(status); + return; + } + context_->storage()->UpdateToActiveState( + registration(), + base::Bind(&ServiceWorkerUtils::NoOpStatusCallback)); + Complete(SERVICE_WORKER_OK); +} + +void ServiceWorkerRegisterJob::Complete(ServiceWorkerStatusCode status) { + CompleteInternal(status); + context_->job_coordinator()->FinishJob(pattern_, this); +} + +void ServiceWorkerRegisterJob::CompleteInternal( + ServiceWorkerStatusCode status) { + SetPhase(COMPLETE); + if (status != SERVICE_WORKER_OK) { + if (registration() && registration()->waiting_version()) { + DisassociateWaitingVersionFromDocuments( + context_, registration()->waiting_version()->version_id()); + registration()->set_waiting_version(NULL); + } + if (registration() && !registration()->active_version()) { + context_->storage()->DeleteRegistration( + registration()->id(), + registration()->script_url().GetOrigin(), + base::Bind(&ServiceWorkerUtils::NoOpStatusCallback)); + } + if (!is_promise_resolved_) + ResolvePromise(status, NULL, NULL); + } + DCHECK(callbacks_.empty()); + if (registration()) { + context_->storage()->NotifyDoneInstallingRegistration( + registration(), pending_version(), status); + } +} + +void ServiceWorkerRegisterJob::ResolvePromise( + ServiceWorkerStatusCode status, + ServiceWorkerRegistration* registration, + ServiceWorkerVersion* version) { + DCHECK(!is_promise_resolved_); + is_promise_resolved_ = true; + promise_resolved_status_ = status; + promise_resolved_registration_ = registration; + promise_resolved_version_ = version; + for (std::vector<RegistrationCallback>::iterator it = callbacks_.begin(); + it != callbacks_.end(); + ++it) { + it->Run(status, registration, version); + } + callbacks_.clear(); +} + +// static +void ServiceWorkerRegisterJob::AssociateWaitingVersionToDocuments( + base::WeakPtr<ServiceWorkerContextCore> context, + ServiceWorkerVersion* version) { + DCHECK(context); + DCHECK(version); + + for (scoped_ptr<ServiceWorkerContextCore::ProviderHostIterator> it = + context->GetProviderHostIterator(); + !it->IsAtEnd(); + it->Advance()) { + ServiceWorkerProviderHost* host = it->GetProviderHost(); + if (!host->IsContextAlive()) + continue; + if (ServiceWorkerUtils::ScopeMatches(version->scope(), + host->document_url())) { + // The spec's _Update algorithm says, "upgrades active version to a new + // version for the same URL scope.", so skip if the scope (registration) + // of |version| is different from that of the current active/waiting + // version. + if (!host->ValidateVersionForAssociation(version)) + continue; + + // TODO(nhiroki): Keep |host->waiting_version()| to be replaced and set + // status of them to 'redandunt' after breaking the loop. + + host->SetWaitingVersion(version); + // TODO(nhiroki): Set |host|'s installing version to null. + } + } +} + +// static +void ServiceWorkerRegisterJob::DisassociateWaitingVersionFromDocuments( + base::WeakPtr<ServiceWorkerContextCore> context, + int64 version_id) { + DCHECK(context); + for (scoped_ptr<ServiceWorkerContextCore::ProviderHostIterator> it = + context->GetProviderHostIterator(); + !it->IsAtEnd(); + it->Advance()) { + ServiceWorkerProviderHost* host = it->GetProviderHost(); + if (!host->IsContextAlive()) + continue; + if (host->waiting_version() && + host->waiting_version()->version_id() == version_id) { + host->SetWaitingVersion(NULL); + } + } } } // namespace content diff --git a/chromium/content/browser/service_worker/service_worker_register_job.h b/chromium/content/browser/service_worker/service_worker_register_job.h index 9e12eb51854..db10fa61118 100644 --- a/chromium/content/browser/service_worker/service_worker_register_job.h +++ b/chromium/content/browser/service_worker/service_worker_register_job.h @@ -5,76 +5,141 @@ #ifndef CONTENT_BROWSER_SERVICE_WORKER_SERVICE_WORKER_REGISTER_JOB_H_ #define CONTENT_BROWSER_SERVICE_WORKER_SERVICE_WORKER_REGISTER_JOB_H_ +#include <vector> + #include "base/memory/weak_ptr.h" -#include "content/browser/service_worker/service_worker_registration_status.h" -#include "content/browser/service_worker/service_worker_storage.h" +#include "content/browser/service_worker/service_worker_register_job_base.h" +#include "content/browser/service_worker/service_worker_registration.h" +#include "content/common/service_worker/service_worker_status_code.h" +#include "url/gurl.h" namespace content { -// A ServiceWorkerRegisterJob lives only for the lifetime of a single -// registration or unregistration. -class ServiceWorkerRegisterJob { +class ServiceWorkerJobCoordinator; +class ServiceWorkerStorage; + +// Handles the registration of a Service Worker. +// +// The registration flow includes most or all of the following, +// depending on what is already registered: +// - creating a ServiceWorkerRegistration instance if there isn't +// already something registered +// - creating a ServiceWorkerVersion for the new registration instance. +// - starting a worker for the ServiceWorkerVersion +// - telling the Version to evaluate the script +// - firing the 'install' event at the ServiceWorkerVersion +// - firing the 'activate' event at the ServiceWorkerVersion +// - waiting for older ServiceWorkerVersions to deactivate +// - designating the new version to be the 'active' version +class ServiceWorkerRegisterJob : public ServiceWorkerRegisterJobBase { public: - typedef base::Callback<void( - ServiceWorkerRegisterJob* job, - ServiceWorkerRegistrationStatus status, - ServiceWorkerRegistration* registration)> RegistrationCompleteCallback; - - // All type of jobs (Register and Unregister) complete through a - // single call to this callback on the IO thread. - ServiceWorkerRegisterJob(const base::WeakPtr<ServiceWorkerStorage>& storage, - const RegistrationCompleteCallback& callback); - ~ServiceWorkerRegisterJob(); - - // The Registration flow includes most or all of the following, - // depending on what is already registered: - // - creating a ServiceWorkerRegistration instance if there isn't - // already something registered - // - creating a ServiceWorkerVersion for the new registration instance. - // - starting a worker for the ServiceWorkerVersion - // - telling the Version to evaluate the script - // - firing the 'install' event at the ServiceWorkerVersion - // - firing the 'activate' event at the ServiceWorkerVersion - // - Waiting for older ServiceWorkerVersions to deactivate - // - designating the new version to be the 'active' version - // This method should be called once and only once per job. - void StartRegister(const GURL& pattern, const GURL& script_url); - - // The Unregistration process is primarily cleanup, removing - // everything that was created during the Registration process, - // including the ServiceWorkerRegistration itself. - // This method should be called once and only once per job. - void StartUnregister(const GURL& pattern); + typedef base::Callback<void(ServiceWorkerStatusCode status, + ServiceWorkerRegistration* registration, + ServiceWorkerVersion* version)> + RegistrationCallback; - private: - // These are all steps in the registration and unregistration pipeline. - void RegisterPatternAndContinue( + CONTENT_EXPORT ServiceWorkerRegisterJob( + base::WeakPtr<ServiceWorkerContextCore> context, const GURL& pattern, - const GURL& script_url, - const ServiceWorkerStorage::RegistrationCallback& callback, - ServiceWorkerRegistrationStatus previous_status); + const GURL& script_url); + virtual ~ServiceWorkerRegisterJob(); - void UnregisterPatternAndContinue( - const GURL& pattern, - const GURL& script_url, - const ServiceWorkerStorage::UnregistrationCallback& callback, - bool found, - ServiceWorkerRegistrationStatus previous_status, - const scoped_refptr<ServiceWorkerRegistration>& previous_registration); - - // These methods are the last internal callback in the callback - // chain, and ultimately call callback_. - void UnregisterComplete(ServiceWorkerRegistrationStatus status); - void RegisterComplete( - ServiceWorkerRegistrationStatus status, + // Registers a callback to be called when the promise would resolve (whether + // successfully or not). Multiple callbacks may be registered. If |process_id| + // is not -1, it's added to the existing clients when deciding in which + // process to create the Service Worker instance. If there are no existing + // clients, a new RenderProcessHost will be created. + void AddCallback(const RegistrationCallback& callback, int process_id); + + // ServiceWorkerRegisterJobBase implementation: + virtual void Start() OVERRIDE; + virtual void Abort() OVERRIDE; + virtual bool Equals(ServiceWorkerRegisterJobBase* job) OVERRIDE; + virtual RegistrationJobType GetType() OVERRIDE; + + private: + FRIEND_TEST_ALL_PREFIXES(ServiceWorkerProviderHostWaitingVersionTest, + AssociateWaitingVersionToDocuments); + FRIEND_TEST_ALL_PREFIXES(ServiceWorkerProviderHostWaitingVersionTest, + DisassociateWaitingVersionFromDocuments); + + enum Phase { + INITIAL, + START, + REGISTER, + UPDATE, + INSTALL, + STORE, + ACTIVATE, + COMPLETE, + ABORT, + }; + + // Holds internal state of ServiceWorkerRegistrationJob, to compel use of the + // getter/setter functions. + struct Internal { + Internal(); + ~Internal(); + scoped_refptr<ServiceWorkerRegistration> registration; + + // Holds 'installing' or 'waiting' version depending on the phase. + scoped_refptr<ServiceWorkerVersion> pending_version; + }; + + void set_registration(ServiceWorkerRegistration* registration); + ServiceWorkerRegistration* registration(); + void set_pending_version(ServiceWorkerVersion* version); + ServiceWorkerVersion* pending_version(); + + void SetPhase(Phase phase); + + void HandleExistingRegistrationAndContinue( + ServiceWorkerStatusCode status, const scoped_refptr<ServiceWorkerRegistration>& registration); + void RegisterAndContinue(ServiceWorkerStatusCode status); + void UpdateAndContinue(ServiceWorkerStatusCode status); + void OnStartWorkerFinished(ServiceWorkerStatusCode status); + void OnStoreRegistrationComplete(ServiceWorkerStatusCode status); + void InstallAndContinue(); + void OnInstallFinished(ServiceWorkerStatusCode status); + void ActivateAndContinue(); + void OnActivateFinished(ServiceWorkerStatusCode status); + void Complete(ServiceWorkerStatusCode status); + void CompleteInternal(ServiceWorkerStatusCode status); + + void ResolvePromise(ServiceWorkerStatusCode status, + ServiceWorkerRegistration* registration, + ServiceWorkerVersion* version); - const base::WeakPtr<ServiceWorkerStorage> storage_; - const RegistrationCompleteCallback callback_; + // Associates a waiting version to documents matched with a scope of the + // version. + CONTENT_EXPORT static void AssociateWaitingVersionToDocuments( + base::WeakPtr<ServiceWorkerContextCore> context, + ServiceWorkerVersion* version); + + // Disassociates a waiting version specified by |version_id| from documents. + CONTENT_EXPORT static void DisassociateWaitingVersionFromDocuments( + base::WeakPtr<ServiceWorkerContextCore> context, + int64 version_id); + + // The ServiceWorkerContextCore object should always outlive this. + base::WeakPtr<ServiceWorkerContextCore> context_; + + const GURL pattern_; + const GURL script_url_; + std::vector<RegistrationCallback> callbacks_; + std::vector<int> pending_process_ids_; + Phase phase_; + Internal internal_; + bool is_promise_resolved_; + ServiceWorkerStatusCode promise_resolved_status_; + scoped_refptr<ServiceWorkerRegistration> promise_resolved_registration_; + scoped_refptr<ServiceWorkerVersion> promise_resolved_version_; base::WeakPtrFactory<ServiceWorkerRegisterJob> weak_factory_; DISALLOW_COPY_AND_ASSIGN(ServiceWorkerRegisterJob); }; + } // namespace content #endif // CONTENT_BROWSER_SERVICE_WORKER_SERVICE_WORKER_REGISTER_JOB_H_ diff --git a/chromium/content/browser/service_worker/service_worker_register_job_base.h b/chromium/content/browser/service_worker/service_worker_register_job_base.h new file mode 100644 index 00000000000..7995adda7c6 --- /dev/null +++ b/chromium/content/browser/service_worker/service_worker_register_job_base.h @@ -0,0 +1,37 @@ +// Copyright 2014 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 CONTENT_BROWSER_SERVICE_WORKER_SERVICE_WORKER_REGISTER_JOB_BASE_H_ +#define CONTENT_BROWSER_SERVICE_WORKER_SERVICE_WORKER_REGISTER_JOB_BASE_H_ + +namespace content { + +// A base class for ServiceWorkerRegisterJob and ServiceWorkerUnregisterJob. A +// job lives only for the lifetime of a single registration or unregistration. +class ServiceWorkerRegisterJobBase { + public: + enum RegistrationJobType { REGISTRATION, UNREGISTRATION, }; + + virtual ~ServiceWorkerRegisterJobBase() {} + + // Starts the job. This method should be called once and only once per job. + virtual void Start() = 0; + + // Aborts the job. This method should be called once and only once per job. + // It can be called regardless of whether Start() was called. + virtual void Abort() = 0; + + // Returns true if this job is identical to |job| for the purpose of + // collapsing them together in a ServiceWorkerJobCoordinator queue. + // Registration jobs are equal if they are for the same pattern and script + // URL; unregistration jobs are equal if they are for the same pattern. + virtual bool Equals(ServiceWorkerRegisterJobBase* job) = 0; + + // Returns the type of this job. + virtual RegistrationJobType GetType() = 0; +}; + +} // namespace content + +#endif // CONTENT_BROWSER_SERVICE_WORKER_SERVICE_WORKER_REGISTER_JOB_BASE_H_ diff --git a/chromium/content/browser/service_worker/service_worker_registration.cc b/chromium/content/browser/service_worker/service_worker_registration.cc index 060de04608f..9e166b6f7b1 100644 --- a/chromium/content/browser/service_worker/service_worker_registration.cc +++ b/chromium/content/browser/service_worker/service_worker_registration.cc @@ -4,40 +4,56 @@ #include "content/browser/service_worker/service_worker_registration.h" +#include "content/browser/service_worker/service_worker_context_core.h" +#include "content/browser/service_worker/service_worker_info.h" #include "content/public/browser/browser_thread.h" namespace content { -ServiceWorkerRegistration::ServiceWorkerRegistration(const GURL& pattern, - const GURL& script_url, - int64 registration_id) +ServiceWorkerRegistration::ServiceWorkerRegistration( + const GURL& pattern, + const GURL& script_url, + int64 registration_id, + base::WeakPtr<ServiceWorkerContextCore> context) : pattern_(pattern), script_url_(script_url), registration_id_(registration_id), - is_shutdown_(false) { + is_shutdown_(false), + context_(context) { DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); + DCHECK(context_); + context_->AddLiveRegistration(this); } ServiceWorkerRegistration::~ServiceWorkerRegistration() { DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); - DCHECK(is_shutdown_); + if (context_) + context_->RemoveLiveRegistration(registration_id_); } -void ServiceWorkerRegistration::Shutdown() { - DCHECK(!is_shutdown_); - if (active_version_) - active_version_->Shutdown(); - active_version_ = NULL; - if (pending_version_) - pending_version_->Shutdown(); - pending_version_ = NULL; - is_shutdown_ = true; +ServiceWorkerRegistrationInfo ServiceWorkerRegistration::GetInfo() { + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); + return ServiceWorkerRegistrationInfo( + script_url(), + pattern(), + registration_id_, + active_version_ ? active_version_->GetInfo() : ServiceWorkerVersionInfo(), + waiting_version_ ? waiting_version_->GetInfo() + : ServiceWorkerVersionInfo()); +} + +ServiceWorkerVersion* ServiceWorkerRegistration::GetNewestVersion() { + if (active_version()) + return active_version(); + return waiting_version(); } -void ServiceWorkerRegistration::ActivatePendingVersion() { - active_version_->Shutdown(); - active_version_ = pending_version_; - pending_version_ = NULL; +void ServiceWorkerRegistration::ActivateWaitingVersion() { + active_version_->SetStatus(ServiceWorkerVersion::DEACTIVATED); + active_version_ = waiting_version_; + // TODO(kinuko): This should be set to ACTIVATING until activation finishes. + active_version_->SetStatus(ServiceWorkerVersion::ACTIVE); + waiting_version_ = NULL; } } // namespace content diff --git a/chromium/content/browser/service_worker/service_worker_registration.h b/chromium/content/browser/service_worker/service_worker_registration.h index 680fcfacbcc..df23dc3d61a 100644 --- a/chromium/content/browser/service_worker/service_worker_registration.h +++ b/chromium/content/browser/service_worker/service_worker_registration.h @@ -16,6 +16,7 @@ namespace content { +class ServiceWorkerRegistrationInfo; class ServiceWorkerVersion; // This class manages all persistence of service workers: @@ -30,16 +31,14 @@ class ServiceWorkerVersion; // // This class also manages the state of the upgrade process, which // includes managing which ServiceWorkerVersion is "active" vs "in -// waiting" (or "pending") +// waiting". class CONTENT_EXPORT ServiceWorkerRegistration : NON_EXPORTED_BASE(public base::RefCounted<ServiceWorkerRegistration>) { public: ServiceWorkerRegistration(const GURL& pattern, const GURL& script_url, - int64 registration_id); - - void Shutdown(); - bool is_shutdown() const { return is_shutdown_; } + int64 registration_id, + base::WeakPtr<ServiceWorkerContextCore> context); int64 id() const { return registration_id_; } const GURL& script_url() const { return script_url_; } @@ -50,9 +49,9 @@ class CONTENT_EXPORT ServiceWorkerRegistration return active_version_.get(); } - ServiceWorkerVersion* pending_version() const { + ServiceWorkerVersion* waiting_version() const { DCHECK(!is_shutdown_); - return pending_version_.get(); + return waiting_version_.get(); } void set_active_version(ServiceWorkerVersion* version) { @@ -60,17 +59,23 @@ class CONTENT_EXPORT ServiceWorkerRegistration active_version_ = version; } - void set_pending_version(ServiceWorkerVersion* version) { + void set_waiting_version(ServiceWorkerVersion* version) { DCHECK(!is_shutdown_); - pending_version_ = version; + waiting_version_ = version; } + ServiceWorkerRegistrationInfo GetInfo(); + + // Returns the active version, if it is not null; otherwise, returns the + // waiting version. + ServiceWorkerVersion* GetNewestVersion(); + // The final synchronous switchover after all events have been // fired, and the old "active version" is being shut down. - void ActivatePendingVersion(); + void ActivateWaitingVersion(); private: - virtual ~ServiceWorkerRegistration(); + ~ServiceWorkerRegistration(); friend class base::RefCounted<ServiceWorkerRegistration>; const GURL pattern_; @@ -78,9 +83,10 @@ class CONTENT_EXPORT ServiceWorkerRegistration const int64 registration_id_; scoped_refptr<ServiceWorkerVersion> active_version_; - scoped_refptr<ServiceWorkerVersion> pending_version_; + scoped_refptr<ServiceWorkerVersion> waiting_version_; bool is_shutdown_; + base::WeakPtr<ServiceWorkerContextCore> context_; DISALLOW_COPY_AND_ASSIGN(ServiceWorkerRegistration); }; diff --git a/chromium/content/browser/service_worker/service_worker_registration_status.cc b/chromium/content/browser/service_worker/service_worker_registration_status.cc index 4556b5f537f..ee5ea906676 100644 --- a/chromium/content/browser/service_worker/service_worker_registration_status.cc +++ b/chromium/content/browser/service_worker/service_worker_registration_status.cc @@ -7,35 +7,45 @@ #include "base/logging.h" #include "base/strings/utf_string_conversions.h" -namespace { -const char kInstallFailedErrorMessage[] = "ServiceWorker failed to install"; -const char kActivateFailedErrorMessage[] = "ServiceWorker failed to activate"; -} - namespace content { using blink::WebServiceWorkerError; void GetServiceWorkerRegistrationStatusResponse( - ServiceWorkerRegistrationStatus status, + ServiceWorkerStatusCode status, blink::WebServiceWorkerError::ErrorType* error_type, base::string16* message) { + *error_type = WebServiceWorkerError::ErrorTypeUnknown; + *message = base::ASCIIToUTF16(ServiceWorkerStatusToString(status)); switch (status) { - case REGISTRATION_OK: - NOTREACHED() << "Consumers should check registration status before " - "calling this function."; + case SERVICE_WORKER_OK: + NOTREACHED() << "Calling this when status == OK is not allowed"; + return; + + case SERVICE_WORKER_ERROR_START_WORKER_FAILED: + case SERVICE_WORKER_ERROR_INSTALL_WORKER_FAILED: + *error_type = WebServiceWorkerError::ErrorTypeInstall; return; - case REGISTRATION_INSTALL_FAILED: - *error_type = WebServiceWorkerError::InstallError; - *message = ASCIIToUTF16(kInstallFailedErrorMessage); + case SERVICE_WORKER_ERROR_ACTIVATE_WORKER_FAILED: + *error_type = WebServiceWorkerError::ErrorTypeActivate; return; - case REGISTRATION_ACTIVATE_FAILED: - *error_type = WebServiceWorkerError::ActivateError; - *message = ASCIIToUTF16(kActivateFailedErrorMessage); + case SERVICE_WORKER_ERROR_NOT_FOUND: + *error_type = WebServiceWorkerError::ErrorTypeNotFound; return; + + case SERVICE_WORKER_ERROR_ABORT: + case SERVICE_WORKER_ERROR_IPC_FAILED: + case SERVICE_WORKER_ERROR_FAILED: + case SERVICE_WORKER_ERROR_PROCESS_NOT_FOUND: + case SERVICE_WORKER_ERROR_EXISTS: + // Unexpected, or should have bailed out before calling this, or we don't + // have a corresponding blink error code yet. + break; // Fall through to NOTREACHED(). } - NOTREACHED(); + NOTREACHED() << "Got unexpected error code: " + << status << " " << ServiceWorkerStatusToString(status); } + } // namespace content diff --git a/chromium/content/browser/service_worker/service_worker_registration_status.h b/chromium/content/browser/service_worker/service_worker_registration_status.h index 1676b495cde..8cffd5628e9 100644 --- a/chromium/content/browser/service_worker/service_worker_registration_status.h +++ b/chromium/content/browser/service_worker/service_worker_registration_status.h @@ -6,21 +6,14 @@ #define CONTENT_BROWSER_SERVICE_WORKER_SERVICE_WORKER_REGISTRATION_STATUS_H_ #include "base/strings/string16.h" +#include "content/common/service_worker/service_worker_status_code.h" #include "third_party/WebKit/public/platform/WebServiceWorkerError.h" namespace content { -// This enum describes the reason a registration or unregistration succeeds or -// fails. -enum ServiceWorkerRegistrationStatus { - REGISTRATION_OK, - REGISTRATION_INSTALL_FAILED, - REGISTRATION_ACTIVATE_FAILED, -}; - -// This should only be called for errors, where status != REGISTRATION_OK. +// This should only be called for errors, where status != OK. void GetServiceWorkerRegistrationStatusResponse( - ServiceWorkerRegistrationStatus status, + ServiceWorkerStatusCode status, blink::WebServiceWorkerError::ErrorType* error_type, base::string16* message); diff --git a/chromium/content/browser/service_worker/service_worker_registration_unittest.cc b/chromium/content/browser/service_worker/service_worker_registration_unittest.cc index 3fb6611a2f3..ccce6417526 100644 --- a/chromium/content/browser/service_worker/service_worker_registration_unittest.cc +++ b/chromium/content/browser/service_worker/service_worker_registration_unittest.cc @@ -4,10 +4,13 @@ #include "content/browser/service_worker/service_worker_registration.h" + #include "base/files/scoped_temp_dir.h" #include "base/logging.h" #include "base/message_loop/message_loop.h" +#include "base/run_loop.h" #include "content/browser/browser_thread_impl.h" +#include "content/browser/service_worker/service_worker_context_core.h" #include "testing/gtest/include/gtest/gtest.h" #include "url/gurl.h" @@ -18,34 +21,29 @@ class ServiceWorkerRegistrationTest : public testing::Test { ServiceWorkerRegistrationTest() : io_thread_(BrowserThread::IO, &message_loop_) {} - virtual void SetUp() OVERRIDE {} + virtual void SetUp() OVERRIDE { + context_.reset( + new ServiceWorkerContextCore(base::FilePath(), + base::MessageLoopProxy::current(), + base::MessageLoopProxy::current(), + NULL, + NULL, + NULL)); + context_ptr_ = context_->AsWeakPtr(); + } + + virtual void TearDown() OVERRIDE { + context_.reset(); + base::RunLoop().RunUntilIdle(); + } protected: + scoped_ptr<ServiceWorkerContextCore> context_; + base::WeakPtr<ServiceWorkerContextCore> context_ptr_; base::MessageLoopForIO message_loop_; BrowserThreadImpl io_thread_; }; -TEST_F(ServiceWorkerRegistrationTest, Shutdown) { - const int64 registration_id = -1L; - const int64 version_id = -1L; - scoped_refptr<ServiceWorkerRegistration> registration = - new ServiceWorkerRegistration( - GURL("http://www.example.com/*"), - GURL("http://www.example.com/service_worker.js"), - registration_id); - - scoped_refptr<ServiceWorkerVersion> active_version = - new ServiceWorkerVersion(registration, NULL, version_id); - registration->set_active_version(active_version); - - registration->Shutdown(); - - DCHECK(registration->is_shutdown()); - DCHECK(active_version->is_shutdown()); - DCHECK(registration->HasOneRef()); - DCHECK(active_version->HasOneRef()); -} - // Make sure that activation does not leak TEST_F(ServiceWorkerRegistrationTest, ActivatePending) { int64 registration_id = -1L; @@ -53,33 +51,26 @@ TEST_F(ServiceWorkerRegistrationTest, ActivatePending) { new ServiceWorkerRegistration( GURL("http://www.example.com/*"), GURL("http://www.example.com/service_worker.js"), - registration_id); + registration_id, + context_ptr_); const int64 version_1_id = 1L; const int64 version_2_id = 2L; scoped_refptr<ServiceWorkerVersion> version_1 = - new ServiceWorkerVersion(registration, NULL, version_1_id); + new ServiceWorkerVersion(registration, version_1_id, context_ptr_); + version_1->SetStatus(ServiceWorkerVersion::ACTIVE); registration->set_active_version(version_1); scoped_refptr<ServiceWorkerVersion> version_2 = - new ServiceWorkerVersion(registration, NULL, version_2_id); - registration->set_pending_version(version_2); + new ServiceWorkerVersion(registration, version_2_id, context_ptr_); + registration->set_waiting_version(version_2); - registration->ActivatePendingVersion(); + registration->ActivateWaitingVersion(); DCHECK_EQ(version_2, registration->active_version()); - DCHECK(version_1->is_shutdown()); DCHECK(version_1->HasOneRef()); version_1 = NULL; - DCHECK(!version_2->is_shutdown()); DCHECK(!version_2->HasOneRef()); - - registration->Shutdown(); - - DCHECK(registration->is_shutdown()); - DCHECK(version_2->is_shutdown()); - DCHECK(registration->HasOneRef()); - DCHECK(version_2->HasOneRef()); } } // namespace content diff --git a/chromium/content/browser/service_worker/service_worker_request_handler.cc b/chromium/content/browser/service_worker/service_worker_request_handler.cc new file mode 100644 index 00000000000..a04e0116452 --- /dev/null +++ b/chromium/content/browser/service_worker/service_worker_request_handler.cc @@ -0,0 +1,111 @@ +// Copyright 2014 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 "content/browser/service_worker/service_worker_request_handler.h" + +#include "content/browser/service_worker/service_worker_context_core.h" +#include "content/browser/service_worker/service_worker_context_wrapper.h" +#include "content/browser/service_worker/service_worker_provider_host.h" +#include "content/browser/service_worker/service_worker_registration.h" +#include "content/browser/service_worker/service_worker_url_request_job.h" +#include "content/browser/service_worker/service_worker_utils.h" +#include "content/common/service_worker/service_worker_types.h" +#include "net/url_request/url_request.h" +#include "net/url_request/url_request_interceptor.h" +#include "webkit/browser/blob/blob_storage_context.h" + +namespace content { + +namespace { + +int kUserDataKey; // Key value is not important. + +class ServiceWorkerRequestInterceptor + : public net::URLRequestInterceptor { + public: + ServiceWorkerRequestInterceptor() {} + virtual ~ServiceWorkerRequestInterceptor() {} + virtual net::URLRequestJob* MaybeInterceptRequest( + net::URLRequest* request, + net::NetworkDelegate* network_delegate) const OVERRIDE { + ServiceWorkerRequestHandler* handler = + ServiceWorkerRequestHandler::GetHandler(request); + if (!handler) + return NULL; + return handler->MaybeCreateJob(request, network_delegate); + } + + private: + DISALLOW_COPY_AND_ASSIGN(ServiceWorkerRequestInterceptor); +}; + +bool IsMethodSupported(const std::string& method) { + return (method == "GET") || (method == "HEAD"); +} + +bool IsSchemeAndMethodSupported(const net::URLRequest* request) { + return request->url().SchemeIsHTTPOrHTTPS() && + IsMethodSupported(request->method()); +} + +} // namespace + +void ServiceWorkerRequestHandler::InitializeHandler( + net::URLRequest* request, + ServiceWorkerContextWrapper* context_wrapper, + webkit_blob::BlobStorageContext* blob_storage_context, + int process_id, + int provider_id, + ResourceType::Type resource_type) { + if (!ServiceWorkerUtils::IsFeatureEnabled() || + !IsSchemeAndMethodSupported(request)) { + return; + } + + if (!context_wrapper || !context_wrapper->context() || + provider_id == kInvalidServiceWorkerProviderId) { + return; + } + + ServiceWorkerProviderHost* provider_host = + context_wrapper->context()->GetProviderHost(process_id, provider_id); + if (!provider_host || !provider_host->IsContextAlive()) + return; + + scoped_ptr<ServiceWorkerRequestHandler> handler( + provider_host->CreateRequestHandler(resource_type, + blob_storage_context->AsWeakPtr())); + if (!handler) + return; + + request->SetUserData(&kUserDataKey, handler.release()); +} + +ServiceWorkerRequestHandler* ServiceWorkerRequestHandler::GetHandler( + net::URLRequest* request) { + return reinterpret_cast<ServiceWorkerRequestHandler*>( + request->GetUserData(&kUserDataKey)); +} + +scoped_ptr<net::URLRequestInterceptor> +ServiceWorkerRequestHandler::CreateInterceptor() { + return scoped_ptr<net::URLRequestInterceptor>( + new ServiceWorkerRequestInterceptor); +} + +ServiceWorkerRequestHandler::~ServiceWorkerRequestHandler() { +} + +ServiceWorkerRequestHandler::ServiceWorkerRequestHandler( + base::WeakPtr<ServiceWorkerContextCore> context, + base::WeakPtr<ServiceWorkerProviderHost> provider_host, + base::WeakPtr<webkit_blob::BlobStorageContext> blob_storage_context, + ResourceType::Type resource_type) + : context_(context), + provider_host_(provider_host), + blob_storage_context_(blob_storage_context), + resource_type_(resource_type) { +} + +} // namespace content diff --git a/chromium/content/browser/service_worker/service_worker_request_handler.h b/chromium/content/browser/service_worker/service_worker_request_handler.h new file mode 100644 index 00000000000..0c666b548df --- /dev/null +++ b/chromium/content/browser/service_worker/service_worker_request_handler.h @@ -0,0 +1,83 @@ +// Copyright 2014 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 CONTENT_BROWSER_SERVICE_WORKER_SERVICE_WORKER_REQUEST_HANDLER_H_ +#define CONTENT_BROWSER_SERVICE_WORKER_SERVICE_WORKER_REQUEST_HANDLER_H_ + +#include "base/basictypes.h" +#include "base/memory/weak_ptr.h" +#include "base/supports_user_data.h" +#include "content/common/content_export.h" +#include "content/common/service_worker/service_worker_status_code.h" +#include "net/url_request/url_request_job_factory.h" +#include "webkit/common/resource_type.h" + +namespace net { +class NetworkDelegate; +class URLRequest; +class URLRequestInterceptor; +} + +namespace webkit_blob { +class BlobStorageContext; +} + +namespace content { + +class ServiceWorkerContextCore; +class ServiceWorkerContextWrapper; +class ServiceWorkerProviderHost; + +// Abstract base class for routing network requests to ServiceWorkers. +// Created one per URLRequest and attached to each request. +class CONTENT_EXPORT ServiceWorkerRequestHandler + : public base::SupportsUserData::Data { + public: + // Attaches a newly created handler if the given |request| needs to + // be handled by ServiceWorker. + // TODO(kinuko): While utilizing UserData to attach data to URLRequest + // has some precedence, it might be better to attach this handler in a more + // explicit way within content layer, e.g. have ResourceRequestInfoImpl + // own it. + static void InitializeHandler( + net::URLRequest* request, + ServiceWorkerContextWrapper* context_wrapper, + webkit_blob::BlobStorageContext* blob_storage_context, + int process_id, + int provider_id, + ResourceType::Type resource_type); + + // Returns the handler attached to |request|. This may return NULL + // if no handler is attached. + static ServiceWorkerRequestHandler* GetHandler( + net::URLRequest* request); + + // Creates a protocol interceptor for ServiceWorker. + static scoped_ptr<net::URLRequestInterceptor> CreateInterceptor(); + + virtual ~ServiceWorkerRequestHandler(); + + // Called via custom URLRequestJobFactory. + virtual net::URLRequestJob* MaybeCreateJob( + net::URLRequest* request, + net::NetworkDelegate* network_delegate) = 0; + + protected: + ServiceWorkerRequestHandler( + base::WeakPtr<ServiceWorkerContextCore> context, + base::WeakPtr<ServiceWorkerProviderHost> provider_host, + base::WeakPtr<webkit_blob::BlobStorageContext> blob_storage_context, + ResourceType::Type resource_type); + + base::WeakPtr<ServiceWorkerContextCore> context_; + base::WeakPtr<ServiceWorkerProviderHost> provider_host_; + base::WeakPtr<webkit_blob::BlobStorageContext> blob_storage_context_; + ResourceType::Type resource_type_; + + DISALLOW_COPY_AND_ASSIGN(ServiceWorkerRequestHandler); +}; + +} // namespace content + +#endif // CONTENT_BROWSER_SERVICE_WORKER_SERVICE_WORKER_REQUEST_HANDLER_H_ diff --git a/chromium/content/browser/service_worker/service_worker_script_cache_map.cc b/chromium/content/browser/service_worker/service_worker_script_cache_map.cc new file mode 100644 index 00000000000..60876b8d716 --- /dev/null +++ b/chromium/content/browser/service_worker/service_worker_script_cache_map.cc @@ -0,0 +1,72 @@ +// Copyright 2014 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 "content/browser/service_worker/service_worker_script_cache_map.h" + +#include "base/logging.h" +#include "content/browser/service_worker/service_worker_context_core.h" +#include "content/browser/service_worker/service_worker_storage.h" +#include "content/browser/service_worker/service_worker_version.h" +#include "content/common/service_worker/service_worker_types.h" + +namespace content { + +ServiceWorkerScriptCacheMap::ServiceWorkerScriptCacheMap( + ServiceWorkerVersion* owner, + base::WeakPtr<ServiceWorkerContextCore> context) + : owner_(owner), + context_(context), + has_error_(false) { +} + +ServiceWorkerScriptCacheMap::~ServiceWorkerScriptCacheMap() { +} + +int64 ServiceWorkerScriptCacheMap::Lookup(const GURL& url) { + ResourceIDMap::const_iterator found = resource_ids_.find(url); + if (found == resource_ids_.end()) + return kInvalidServiceWorkerResponseId; + return found->second; +} + +void ServiceWorkerScriptCacheMap::NotifyStartedCaching( + const GURL& url, int64 resource_id) { + DCHECK_EQ(kInvalidServiceWorkerResponseId, Lookup(url)); + DCHECK(owner_->status() == ServiceWorkerVersion::NEW); + resource_ids_[url] = resource_id; + context_->storage()->StoreUncommittedReponseId(resource_id); +} + +void ServiceWorkerScriptCacheMap::NotifyFinishedCaching( + const GURL& url, bool success) { + DCHECK_NE(kInvalidServiceWorkerResponseId, Lookup(url)); + DCHECK(owner_->status() == ServiceWorkerVersion::NEW); + if (!success) { + context_->storage()->DoomUncommittedResponse(Lookup(url)); + has_error_ = true; + resource_ids_.erase(url); + } +} + +void ServiceWorkerScriptCacheMap::GetResources( + std::vector<ServiceWorkerDatabase::ResourceRecord>* resources) { + DCHECK(resources->empty()); + for (ResourceIDMap::const_iterator it = resource_ids_.begin(); + it != resource_ids_.end(); ++it) { + resources->push_back( + ServiceWorkerDatabase::ResourceRecord(it->second, it->first)); + } +} + +void ServiceWorkerScriptCacheMap::SetResources( + const std::vector<ServiceWorkerDatabase::ResourceRecord>& resources) { + DCHECK(resource_ids_.empty()); + typedef std::vector<ServiceWorkerDatabase::ResourceRecord> RecordVector; + for (RecordVector::const_iterator it = resources.begin(); + it != resources.end(); ++it) { + resource_ids_[it->url] = it->resource_id; + } +} + +} // namespace content diff --git a/chromium/content/browser/service_worker/service_worker_script_cache_map.h b/chromium/content/browser/service_worker/service_worker_script_cache_map.h new file mode 100644 index 00000000000..4513f3d9ea7 --- /dev/null +++ b/chromium/content/browser/service_worker/service_worker_script_cache_map.h @@ -0,0 +1,62 @@ +// Copyright 2014 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 CONTENT_BROWSER_SERVICE_WORKER_SERVICE_WORKER_SCRIPT_CACHE_MAP_H_ +#define CONTENT_BROWSER_SERVICE_WORKER_SERVICE_WORKER_SCRIPT_CACHE_MAP_H_ + +#include <map> +#include <vector> + +#include "base/basictypes.h" +#include "base/memory/weak_ptr.h" +#include "content/browser/service_worker/service_worker_database.h" + +class GURL; + +namespace content { + +class ServiceWorkerContextCore; +class ServiceWorkerVersion; + +// Class that maintains the mapping between urls and a resource id +// for a particular versions implicit script resources. +class ServiceWorkerScriptCacheMap { + public: + int64 Lookup(const GURL& url); + + // Used during the initial run of a new version to build the map + // of resources ids. + void NotifyStartedCaching(const GURL& url, int64 resource_id); + void NotifyFinishedCaching(const GURL& url, bool success); + + // Used to retrieve the results of the initial run of a new version. + bool HasError() const { return has_error_; } + void GetResources( + std::vector<ServiceWorkerDatabase::ResourceRecord>* resources); + + // Used when loading an existing version. + void SetResources( + const std::vector<ServiceWorkerDatabase::ResourceRecord>& resources); + + private: + typedef std::map<GURL, int64> ResourceIDMap; + + // The version objects owns its script cache and provides a rawptr to it. + friend class ServiceWorkerVersion; + ServiceWorkerScriptCacheMap( + ServiceWorkerVersion* owner, + base::WeakPtr<ServiceWorkerContextCore> context); + ~ServiceWorkerScriptCacheMap(); + + ServiceWorkerVersion* owner_; + base::WeakPtr<ServiceWorkerContextCore> context_; + ResourceIDMap resource_ids_; + bool has_error_; + + DISALLOW_COPY_AND_ASSIGN(ServiceWorkerScriptCacheMap); +}; + +} // namespace content + +#endif // CONTENT_BROWSER_SERVICE_WORKER_SERVICE_WORKER_SCRIPT_CACHE_MAP_H_ diff --git a/chromium/content/browser/service_worker/service_worker_storage.cc b/chromium/content/browser/service_worker/service_worker_storage.cc index 16a671e20b8..8a924e0c723 100644 --- a/chromium/content/browser/service_worker/service_worker_storage.cc +++ b/chromium/content/browser/service_worker/service_worker_storage.cc @@ -6,189 +6,980 @@ #include <string> -#include "base/strings/string_util.h" -#include "content/browser/service_worker/service_worker_register_job.h" +#include "base/bind_helpers.h" +#include "base/message_loop/message_loop.h" +#include "base/sequenced_task_runner.h" +#include "base/task_runner_util.h" +#include "content/browser/service_worker/service_worker_context_core.h" +#include "content/browser/service_worker/service_worker_disk_cache.h" +#include "content/browser/service_worker/service_worker_histograms.h" +#include "content/browser/service_worker/service_worker_info.h" #include "content/browser/service_worker/service_worker_registration.h" +#include "content/browser/service_worker/service_worker_utils.h" +#include "content/browser/service_worker/service_worker_version.h" +#include "content/common/service_worker/service_worker_types.h" #include "content/public/browser/browser_thread.h" -#include "webkit/browser/quota/quota_manager.h" +#include "net/base/net_errors.h" +#include "webkit/browser/quota/quota_manager_proxy.h" + +namespace content { namespace { -// This is temporary until we figure out how registration ids will be -// calculated. -int64 NextRegistrationId() { - static int64 worker_id = 0; - return worker_id++; + +void RunSoon(const tracked_objects::Location& from_here, + const base::Closure& closure) { + base::MessageLoop::current()->PostTask(from_here, closure); } -} // namespace -namespace content { +void CompleteFindNow( + const scoped_refptr<ServiceWorkerRegistration>& registration, + ServiceWorkerStatusCode status, + const ServiceWorkerStorage::FindRegistrationCallback& callback) { + callback.Run(status, registration); +} + +void CompleteFindSoon( + const tracked_objects::Location& from_here, + const scoped_refptr<ServiceWorkerRegistration>& registration, + ServiceWorkerStatusCode status, + const ServiceWorkerStorage::FindRegistrationCallback& callback) { + RunSoon(from_here, base::Bind(callback, status, registration)); +} const base::FilePath::CharType kServiceWorkerDirectory[] = - FILE_PATH_LITERAL("ServiceWorker"); + FILE_PATH_LITERAL("Service Worker"); +const base::FilePath::CharType kDatabaseName[] = + FILE_PATH_LITERAL("Database"); +const base::FilePath::CharType kDiskCacheName[] = + FILE_PATH_LITERAL("Cache"); + +const int kMaxMemDiskCacheSize = 10 * 1024 * 1024; +const int kMaxDiskCacheSize = 250 * 1024 * 1024; + +void EmptyCompletionCallback(int) {} + +ServiceWorkerStatusCode DatabaseStatusToStatusCode( + ServiceWorkerDatabase::Status status) { + switch (status) { + case ServiceWorkerDatabase::STATUS_OK: + return SERVICE_WORKER_OK; + case ServiceWorkerDatabase::STATUS_ERROR_NOT_FOUND: + return SERVICE_WORKER_ERROR_NOT_FOUND; + case ServiceWorkerDatabase::STATUS_ERROR_MAX: + NOTREACHED(); + default: + return SERVICE_WORKER_ERROR_FAILED; + } +} + +} // namespace + +ServiceWorkerStorage::InitialData::InitialData() + : next_registration_id(kInvalidServiceWorkerRegistrationId), + next_version_id(kInvalidServiceWorkerVersionId), + next_resource_id(kInvalidServiceWorkerResourceId) { +} + +ServiceWorkerStorage::InitialData::~InitialData() { +} ServiceWorkerStorage::ServiceWorkerStorage( const base::FilePath& path, + base::WeakPtr<ServiceWorkerContextCore> context, + base::SequencedTaskRunner* database_task_runner, + base::MessageLoopProxy* disk_cache_thread, quota::QuotaManagerProxy* quota_manager_proxy) - : quota_manager_proxy_(quota_manager_proxy), weak_factory_(this) { + : next_registration_id_(kInvalidServiceWorkerRegistrationId), + next_version_id_(kInvalidServiceWorkerVersionId), + next_resource_id_(kInvalidServiceWorkerResourceId), + state_(UNINITIALIZED), + context_(context), + database_task_runner_(database_task_runner), + disk_cache_thread_(disk_cache_thread), + quota_manager_proxy_(quota_manager_proxy), + is_purge_pending_(false), + weak_factory_(this) { if (!path.empty()) path_ = path.Append(kServiceWorkerDirectory); + database_.reset(new ServiceWorkerDatabase(GetDatabasePath())); } ServiceWorkerStorage::~ServiceWorkerStorage() { - for (PatternToRegistrationMap::const_iterator iter = - registration_by_pattern_.begin(); - iter != registration_by_pattern_.end(); - ++iter) { - iter->second->Shutdown(); + weak_factory_.InvalidateWeakPtrs(); + database_task_runner_->DeleteSoon(FROM_HERE, database_.release()); +} + +void ServiceWorkerStorage::FindRegistrationForDocument( + const GURL& document_url, + const FindRegistrationCallback& callback) { + DCHECK(!document_url.has_ref()); + if (!LazyInitialize(base::Bind( + &ServiceWorkerStorage::FindRegistrationForDocument, + weak_factory_.GetWeakPtr(), document_url, callback))) { + if (state_ != INITIALIZING || !context_) { + CompleteFindNow(scoped_refptr<ServiceWorkerRegistration>(), + SERVICE_WORKER_ERROR_FAILED, callback); + } + return; + } + DCHECK_EQ(INITIALIZED, state_); + + // See if there are any stored registrations for the origin. + if (!ContainsKey(registered_origins_, document_url.GetOrigin())) { + // Look for something currently being installed. + scoped_refptr<ServiceWorkerRegistration> installing_registration = + FindInstallingRegistrationForDocument(document_url); + CompleteFindNow( + installing_registration, + installing_registration ? + SERVICE_WORKER_OK : SERVICE_WORKER_ERROR_NOT_FOUND, + callback); + return; } - registration_by_pattern_.clear(); + + database_task_runner_->PostTask( + FROM_HERE, + base::Bind( + &FindForDocumentInDB, + database_.get(), + base::MessageLoopProxy::current(), + document_url, + base::Bind(&ServiceWorkerStorage::DidFindRegistrationForDocument, + weak_factory_.GetWeakPtr(), document_url, callback))); } void ServiceWorkerStorage::FindRegistrationForPattern( - const GURL& pattern, + const GURL& scope, const FindRegistrationCallback& callback) { - PatternToRegistrationMap::const_iterator match = - registration_by_pattern_.find(pattern); - if (match == registration_by_pattern_.end()) { - BrowserThread::PostTask( - BrowserThread::IO, - FROM_HERE, - base::Bind(callback, - false /* found */, - REGISTRATION_OK, - scoped_refptr<ServiceWorkerRegistration>())); + if (!LazyInitialize(base::Bind( + &ServiceWorkerStorage::FindRegistrationForPattern, + weak_factory_.GetWeakPtr(), scope, callback))) { + if (state_ != INITIALIZING || !context_) { + CompleteFindSoon(FROM_HERE, scoped_refptr<ServiceWorkerRegistration>(), + SERVICE_WORKER_ERROR_FAILED, callback); + } + return; + } + DCHECK_EQ(INITIALIZED, state_); + + // See if there are any stored registrations for the origin. + if (!ContainsKey(registered_origins_, scope.GetOrigin())) { + // Look for something currently being installed. + scoped_refptr<ServiceWorkerRegistration> installing_registration = + FindInstallingRegistrationForPattern(scope); + CompleteFindSoon( + FROM_HERE, installing_registration, + installing_registration ? + SERVICE_WORKER_OK : SERVICE_WORKER_ERROR_NOT_FOUND, + callback); return; } - BrowserThread::PostTask( - BrowserThread::IO, + + database_task_runner_->PostTask( FROM_HERE, - base::Bind(callback, true /* found */, REGISTRATION_OK, match->second)); + base::Bind( + &FindForPatternInDB, + database_.get(), + base::MessageLoopProxy::current(), + scope, + base::Bind(&ServiceWorkerStorage::DidFindRegistrationForPattern, + weak_factory_.GetWeakPtr(), scope, callback))); } -void ServiceWorkerStorage::FindRegistrationForDocument( - const GURL& document_url, +void ServiceWorkerStorage::FindRegistrationForId( + int64 registration_id, + const GURL& origin, const FindRegistrationCallback& callback) { - // TODO(alecflett): This needs to be synchronous in the fast path, - // but asynchronous in the slow path (when the patterns have to be - // loaded from disk). For now it is always pessimistically async. - for (PatternToRegistrationMap::const_iterator it = - registration_by_pattern_.begin(); - it != registration_by_pattern_.end(); - ++it) { - if (PatternMatches(it->first, document_url)) { - BrowserThread::PostTask( - BrowserThread::IO, - FROM_HERE, - base::Bind(callback, - true /* found */, - REGISTRATION_OK, - scoped_refptr<ServiceWorkerRegistration>(it->second))); - return; + if (!LazyInitialize(base::Bind( + &ServiceWorkerStorage::FindRegistrationForId, + weak_factory_.GetWeakPtr(), registration_id, origin, callback))) { + if (state_ != INITIALIZING || !context_) { + CompleteFindNow(scoped_refptr<ServiceWorkerRegistration>(), + SERVICE_WORKER_ERROR_FAILED, callback); } + return; + } + DCHECK_EQ(INITIALIZED, state_); + + // See if there are any stored registrations for the origin. + if (!ContainsKey(registered_origins_, origin)) { + // Look for something currently being installed. + scoped_refptr<ServiceWorkerRegistration> installing_registration = + FindInstallingRegistrationForId(registration_id); + CompleteFindNow( + installing_registration, + installing_registration ? + SERVICE_WORKER_OK : SERVICE_WORKER_ERROR_NOT_FOUND, + callback); + return; } - BrowserThread::PostTask( - BrowserThread::IO, + + scoped_refptr<ServiceWorkerRegistration> registration = + context_->GetLiveRegistration(registration_id); + if (registration) { + CompleteFindNow(registration, SERVICE_WORKER_OK, callback); + return; + } + + database_task_runner_->PostTask( + FROM_HERE, + base::Bind(&FindForIdInDB, + database_.get(), + base::MessageLoopProxy::current(), + registration_id, origin, + base::Bind(&ServiceWorkerStorage::DidFindRegistrationForId, + weak_factory_.GetWeakPtr(), callback))); +} + +void ServiceWorkerStorage::GetAllRegistrations( + const GetAllRegistrationInfosCallback& callback) { + if (!LazyInitialize(base::Bind( + &ServiceWorkerStorage::GetAllRegistrations, + weak_factory_.GetWeakPtr(), callback))) { + if (state_ != INITIALIZING || !context_) { + RunSoon(FROM_HERE, base::Bind( + callback, std::vector<ServiceWorkerRegistrationInfo>())); + } + return; + } + DCHECK_EQ(INITIALIZED, state_); + + RegistrationList* registrations = new RegistrationList; + PostTaskAndReplyWithResult( + database_task_runner_, FROM_HERE, - base::Bind(callback, - false /* found */, - REGISTRATION_OK, - scoped_refptr<ServiceWorkerRegistration>())); -} - -void ServiceWorkerStorage::Register(const GURL& pattern, - const GURL& script_url, - const RegistrationCallback& callback) { - scoped_ptr<ServiceWorkerRegisterJob> job(new ServiceWorkerRegisterJob( - weak_factory_.GetWeakPtr(), - base::Bind(&ServiceWorkerStorage::RegisterComplete, + base::Bind(&ServiceWorkerDatabase::GetAllRegistrations, + base::Unretained(database_.get()), + base::Unretained(registrations)), + base::Bind(&ServiceWorkerStorage::DidGetAllRegistrations, weak_factory_.GetWeakPtr(), - callback))); - job->StartRegister(pattern, script_url); - registration_jobs_.push_back(job.release()); + callback, + base::Owned(registrations))); +} + +void ServiceWorkerStorage::StoreRegistration( + ServiceWorkerRegistration* registration, + ServiceWorkerVersion* version, + const StatusCallback& callback) { + DCHECK(registration); + DCHECK(version); + + DCHECK(state_ == INITIALIZED || state_ == DISABLED); + if (state_ != INITIALIZED || !context_) { + RunSoon(FROM_HERE, base::Bind(callback, SERVICE_WORKER_ERROR_FAILED)); + return; + } + + ServiceWorkerDatabase::RegistrationData data; + data.registration_id = registration->id(); + data.scope = registration->pattern(); + data.script = registration->script_url(); + data.has_fetch_handler = true; + data.version_id = version->version_id(); + data.last_update_check = base::Time::Now(); + data.is_active = false; // initially stored in the waiting state + + ResourceList resources; + version->script_cache_map()->GetResources(&resources); + + database_task_runner_->PostTask( + FROM_HERE, + base::Bind(&WriteRegistrationInDB, + database_.get(), + base::MessageLoopProxy::current(), + data, resources, + base::Bind(&ServiceWorkerStorage::DidStoreRegistration, + weak_factory_.GetWeakPtr(), + callback))); } -void ServiceWorkerStorage::Unregister(const GURL& pattern, - const UnregistrationCallback& callback) { - scoped_ptr<ServiceWorkerRegisterJob> job(new ServiceWorkerRegisterJob( - weak_factory_.GetWeakPtr(), - base::Bind(&ServiceWorkerStorage::UnregisterComplete, +void ServiceWorkerStorage::UpdateToActiveState( + ServiceWorkerRegistration* registration, + const StatusCallback& callback) { + DCHECK(registration); + + DCHECK(state_ == INITIALIZED || state_ == DISABLED); + if (state_ != INITIALIZED || !context_) { + RunSoon(FROM_HERE, base::Bind(callback, SERVICE_WORKER_ERROR_FAILED)); + return; + } + + PostTaskAndReplyWithResult( + database_task_runner_, + FROM_HERE, + base::Bind(&ServiceWorkerDatabase::UpdateVersionToActive, + base::Unretained(database_.get()), + registration->id(), + registration->script_url().GetOrigin()), + base::Bind(&ServiceWorkerStorage::DidUpdateToActiveState, weak_factory_.GetWeakPtr(), - callback))); - job->StartUnregister(pattern); - registration_jobs_.push_back(job.release()); -} - -scoped_refptr<ServiceWorkerRegistration> ServiceWorkerStorage::RegisterInternal( - const GURL& pattern, - const GURL& script_url) { - - PatternToRegistrationMap::const_iterator current( - registration_by_pattern_.find(pattern)); - DCHECK(current == registration_by_pattern_.end() || - current->second->script_url() == script_url); - - if (current == registration_by_pattern_.end()) { - scoped_refptr<ServiceWorkerRegistration> registration( - new ServiceWorkerRegistration( - pattern, script_url, NextRegistrationId())); - // TODO(alecflett): version upgrade path. - registration_by_pattern_[pattern] = registration; - return registration; + callback)); +} + +void ServiceWorkerStorage::DeleteRegistration( + int64 registration_id, + const GURL& origin, + const StatusCallback& callback) { + DCHECK(state_ == INITIALIZED || state_ == DISABLED); + if (state_ != INITIALIZED || !context_) { + RunSoon(FROM_HERE, base::Bind(callback, SERVICE_WORKER_ERROR_FAILED)); + return; } - return current->second; + database_task_runner_->PostTask( + FROM_HERE, + base::Bind(&DeleteRegistrationFromDB, + database_.get(), + base::MessageLoopProxy::current(), + registration_id, origin, + base::Bind(&ServiceWorkerStorage::DidDeleteRegistration, + weak_factory_.GetWeakPtr(), origin, callback))); + + // TODO(michaeln): Either its instance should also be + // removed from liveregistrations map or the live object + // should marked as deleted in some way and not 'findable' + // thereafter. +} + +scoped_ptr<ServiceWorkerResponseReader> +ServiceWorkerStorage::CreateResponseReader(int64 response_id) { + return make_scoped_ptr( + new ServiceWorkerResponseReader(response_id, disk_cache())); } -void ServiceWorkerStorage::UnregisterInternal(const GURL& pattern) { - PatternToRegistrationMap::iterator match = - registration_by_pattern_.find(pattern); - if (match != registration_by_pattern_.end()) { - match->second->Shutdown(); - registration_by_pattern_.erase(match); +scoped_ptr<ServiceWorkerResponseWriter> +ServiceWorkerStorage::CreateResponseWriter(int64 response_id) { + return make_scoped_ptr( + new ServiceWorkerResponseWriter(response_id, disk_cache())); +} + +void ServiceWorkerStorage::StoreUncommittedReponseId(int64 id) { + DCHECK_NE(kInvalidServiceWorkerResponseId, id); + database_task_runner_->PostTask( + FROM_HERE, + base::Bind(base::IgnoreResult( + &ServiceWorkerDatabase::WriteUncommittedResourceIds), + base::Unretained(database_.get()), + std::set<int64>(&id, &id + 1))); +} + +void ServiceWorkerStorage::DoomUncommittedResponse(int64 id) { + DCHECK_NE(kInvalidServiceWorkerResponseId, id); + database_task_runner_->PostTask( + FROM_HERE, + base::Bind(base::IgnoreResult( + &ServiceWorkerDatabase::PurgeUncommittedResourceIds), + base::Unretained(database_.get()), + std::set<int64>(&id, &id + 1))); + StartPurgingResources(std::vector<int64>(1, id)); +} + +int64 ServiceWorkerStorage::NewRegistrationId() { + if (state_ == DISABLED) + return kInvalidServiceWorkerRegistrationId; + DCHECK_EQ(INITIALIZED, state_); + return next_registration_id_++; +} + +int64 ServiceWorkerStorage::NewVersionId() { + if (state_ == DISABLED) + return kInvalidServiceWorkerVersionId; + DCHECK_EQ(INITIALIZED, state_); + return next_version_id_++; +} + +int64 ServiceWorkerStorage::NewResourceId() { + if (state_ == DISABLED) + return kInvalidServiceWorkerResourceId; + DCHECK_EQ(INITIALIZED, state_); + return next_resource_id_++; +} + +void ServiceWorkerStorage::NotifyInstallingRegistration( + ServiceWorkerRegistration* registration) { + installing_registrations_[registration->id()] = registration; +} + +void ServiceWorkerStorage::NotifyDoneInstallingRegistration( + ServiceWorkerRegistration* registration, + ServiceWorkerVersion* version, + ServiceWorkerStatusCode status) { + installing_registrations_.erase(registration->id()); + if (status != SERVICE_WORKER_OK && version) { + ResourceList resources; + version->script_cache_map()->GetResources(&resources); + + std::set<int64> ids; + for (size_t i = 0; i < resources.size(); ++i) + ids.insert(resources[i].resource_id); + + database_task_runner_->PostTask( + FROM_HERE, + base::Bind(base::IgnoreResult( + &ServiceWorkerDatabase::PurgeUncommittedResourceIds), + base::Unretained(database_.get()), + ids)); + + StartPurgingResources(resources); } } -bool ServiceWorkerStorage::PatternMatches(const GURL& pattern, - const GURL& url) { - // This is a really basic, naive - // TODO(alecflett): Formalize what pattern matches mean. - // Temporarily borrowed directly from appcache::Namespace::IsMatch(). - // We have to escape '?' characters since MatchPattern also treats those - // as wildcards which we don't want here, we only do '*'s. - std::string pattern_spec(pattern.spec()); - if (pattern.has_query()) - ReplaceSubstringsAfterOffset(&pattern_spec, 0, "?", "\\?"); - return MatchPattern(url.spec(), pattern_spec); +base::FilePath ServiceWorkerStorage::GetDatabasePath() { + if (path_.empty()) + return base::FilePath(); + return path_.Append(kDatabaseName); +} + +base::FilePath ServiceWorkerStorage::GetDiskCachePath() { + if (path_.empty()) + return base::FilePath(); + return path_.Append(kDiskCacheName); } -void ServiceWorkerStorage::EraseJob(ServiceWorkerRegisterJob* job) { - ScopedVector<ServiceWorkerRegisterJob>::iterator job_position = - registration_jobs_.begin(); - for (; job_position != registration_jobs_.end(); ++job_position) { - if (*job_position == job) { - registration_jobs_.erase(job_position); - return; +bool ServiceWorkerStorage::LazyInitialize(const base::Closure& callback) { + if (!context_) + return false; + + switch (state_) { + case INITIALIZED: + return true; + case DISABLED: + return false; + case INITIALIZING: + pending_tasks_.push_back(callback); + return false; + case UNINITIALIZED: + pending_tasks_.push_back(callback); + // Fall-through. + } + + state_ = INITIALIZING; + database_task_runner_->PostTask( + FROM_HERE, + base::Bind(&ReadInitialDataFromDB, + database_.get(), + base::MessageLoopProxy::current(), + base::Bind(&ServiceWorkerStorage::DidReadInitialData, + weak_factory_.GetWeakPtr()))); + return false; +} + +void ServiceWorkerStorage::DidReadInitialData( + InitialData* data, + ServiceWorkerDatabase::Status status) { + DCHECK(data); + DCHECK_EQ(INITIALIZING, state_); + + if (status == ServiceWorkerDatabase::STATUS_OK) { + next_registration_id_ = data->next_registration_id; + next_version_id_ = data->next_version_id; + next_resource_id_ = data->next_resource_id; + registered_origins_.swap(data->origins); + state_ = INITIALIZED; + } else { + // TODO(nhiroki): If status==STATUS_ERROR_CORRUPTED, do corruption recovery + // (http://crbug.com/371675). + DLOG(WARNING) << "Failed to initialize: " << status; + state_ = DISABLED; + } + + for (std::vector<base::Closure>::const_iterator it = pending_tasks_.begin(); + it != pending_tasks_.end(); ++it) { + RunSoon(FROM_HERE, *it); + } + pending_tasks_.clear(); +} + +void ServiceWorkerStorage::DidFindRegistrationForDocument( + const GURL& document_url, + const FindRegistrationCallback& callback, + const ServiceWorkerDatabase::RegistrationData& data, + const ResourceList& resources, + ServiceWorkerDatabase::Status status) { + if (status == ServiceWorkerDatabase::STATUS_OK) { + callback.Run(SERVICE_WORKER_OK, GetOrCreateRegistration(data, resources)); + return; + } + + if (status == ServiceWorkerDatabase::STATUS_ERROR_NOT_FOUND) { + // Look for something currently being installed. + scoped_refptr<ServiceWorkerRegistration> installing_registration = + FindInstallingRegistrationForDocument(document_url); + callback.Run(installing_registration ? + SERVICE_WORKER_OK : SERVICE_WORKER_ERROR_NOT_FOUND, + installing_registration); + return; + } + + // TODO(nhiroki): Handle database error (http://crbug.com/371675). + callback.Run(DatabaseStatusToStatusCode(status), + scoped_refptr<ServiceWorkerRegistration>()); +} + +void ServiceWorkerStorage::DidFindRegistrationForPattern( + const GURL& scope, + const FindRegistrationCallback& callback, + const ServiceWorkerDatabase::RegistrationData& data, + const ResourceList& resources, + ServiceWorkerDatabase::Status status) { + if (status == ServiceWorkerDatabase::STATUS_OK) { + callback.Run(SERVICE_WORKER_OK, GetOrCreateRegistration(data, resources)); + return; + } + + if (status == ServiceWorkerDatabase::STATUS_ERROR_NOT_FOUND) { + scoped_refptr<ServiceWorkerRegistration> installing_registration = + FindInstallingRegistrationForPattern(scope); + callback.Run(installing_registration ? + SERVICE_WORKER_OK : SERVICE_WORKER_ERROR_NOT_FOUND, + installing_registration); + return; + } + + // TODO(nhiroki): Handle database error (http://crbug.com/371675). + callback.Run(DatabaseStatusToStatusCode(status), + scoped_refptr<ServiceWorkerRegistration>()); +} + +void ServiceWorkerStorage::DidFindRegistrationForId( + const FindRegistrationCallback& callback, + const ServiceWorkerDatabase::RegistrationData& data, + const ResourceList& resources, + ServiceWorkerDatabase::Status status) { + if (status == ServiceWorkerDatabase::STATUS_OK) { + callback.Run(SERVICE_WORKER_OK, + GetOrCreateRegistration(data, resources)); + return; + } + // TODO(nhiroki): Handle database error (http://crbug.com/371675). + callback.Run(DatabaseStatusToStatusCode(status), + scoped_refptr<ServiceWorkerRegistration>()); +} + +void ServiceWorkerStorage::DidGetAllRegistrations( + const GetAllRegistrationInfosCallback& callback, + RegistrationList* registrations, + ServiceWorkerDatabase::Status status) { + DCHECK(registrations); + if (status != ServiceWorkerDatabase::STATUS_OK) { + // TODO(nhiroki): Handle database error (http://crbug.com/371675). + callback.Run(std::vector<ServiceWorkerRegistrationInfo>()); + return; + } + + // Add all stored registrations. + std::set<int64> pushed_registrations; + std::vector<ServiceWorkerRegistrationInfo> infos; + for (RegistrationList::const_iterator it = registrations->begin(); + it != registrations->end(); ++it) { + const bool inserted = + pushed_registrations.insert(it->registration_id).second; + DCHECK(inserted); + + ServiceWorkerRegistration* registration = + context_->GetLiveRegistration(it->registration_id); + if (registration) { + infos.push_back(registration->GetInfo()); + continue; + } + + ServiceWorkerRegistrationInfo info; + info.pattern = it->scope; + info.script_url = it->script; + info.registration_id = it->registration_id; + if (ServiceWorkerVersion* version = + context_->GetLiveVersion(it->version_id)) { + if (it->is_active) + info.active_version = version->GetInfo(); + else + info.waiting_version = version->GetInfo(); + infos.push_back(info); + continue; + } + + if (it->is_active) { + info.active_version.is_null = false; + info.active_version.status = ServiceWorkerVersion::ACTIVE; + info.active_version.version_id = it->version_id; + } else { + info.waiting_version.is_null = false; + info.waiting_version.status = ServiceWorkerVersion::INSTALLED; + info.waiting_version.version_id = it->version_id; } + infos.push_back(info); } - NOTREACHED() << "Deleting non-existent job. "; + + // Add unstored registrations that are being installed. + for (RegistrationRefsById::const_iterator it = + installing_registrations_.begin(); + it != installing_registrations_.end(); ++it) { + if (pushed_registrations.insert(it->first).second) + infos.push_back(it->second->GetInfo()); + } + + callback.Run(infos); } -void ServiceWorkerStorage::UnregisterComplete( - const UnregistrationCallback& callback, - ServiceWorkerRegisterJob* job, - ServiceWorkerRegistrationStatus status, - ServiceWorkerRegistration* previous_registration) { - callback.Run(status); - EraseJob(job); +void ServiceWorkerStorage::DidStoreRegistration( + const StatusCallback& callback, + const GURL& origin, + const std::vector<int64>& newly_purgeable_resources, + ServiceWorkerDatabase::Status status) { + if (status != ServiceWorkerDatabase::STATUS_OK) { + // TODO(nhiroki): Handle database error (http://crbug.com/371675). + callback.Run(DatabaseStatusToStatusCode(status)); + return; + } + registered_origins_.insert(origin); + callback.Run(SERVICE_WORKER_OK); + StartPurgingResources(newly_purgeable_resources); } -void ServiceWorkerStorage::RegisterComplete( - const RegistrationCallback& callback, - ServiceWorkerRegisterJob* job, - ServiceWorkerRegistrationStatus status, - ServiceWorkerRegistration* registration) { - callback.Run(status, registration); - EraseJob(job); +void ServiceWorkerStorage::DidUpdateToActiveState( + const StatusCallback& callback, + ServiceWorkerDatabase::Status status) { + // TODO(nhiroki): Handle database error (http://crbug.com/371675). + callback.Run(DatabaseStatusToStatusCode(status)); +} + +void ServiceWorkerStorage::DidDeleteRegistration( + const GURL& origin, + const StatusCallback& callback, + bool origin_is_deletable, + const std::vector<int64>& newly_purgeable_resources, + ServiceWorkerDatabase::Status status) { + if (status != ServiceWorkerDatabase::STATUS_OK) { + // TODO(nhiroki): Handle database error (http://crbug.com/371675). + callback.Run(DatabaseStatusToStatusCode(status)); + return; + } + if (origin_is_deletable) + registered_origins_.erase(origin); + callback.Run(SERVICE_WORKER_OK); + StartPurgingResources(newly_purgeable_resources); +} + +scoped_refptr<ServiceWorkerRegistration> +ServiceWorkerStorage::GetOrCreateRegistration( + const ServiceWorkerDatabase::RegistrationData& data, + const ResourceList& resources) { + scoped_refptr<ServiceWorkerRegistration> registration = + context_->GetLiveRegistration(data.registration_id); + if (registration) + return registration; + + registration = new ServiceWorkerRegistration( + data.scope, data.script, data.registration_id, context_); + scoped_refptr<ServiceWorkerVersion> version = + context_->GetLiveVersion(data.version_id); + if (!version) { + version = new ServiceWorkerVersion(registration, data.version_id, context_); + version->SetStatus(data.is_active ? + ServiceWorkerVersion::ACTIVE : ServiceWorkerVersion::INSTALLED); + version->script_cache_map()->SetResources(resources); + } + + if (version->status() == ServiceWorkerVersion::ACTIVE) + registration->set_active_version(version); + else if (version->status() == ServiceWorkerVersion::INSTALLED) + registration->set_waiting_version(version); + else + NOTREACHED(); + // TODO(michaeln): Hmmm, what if DeleteReg was invoked after + // the Find result we're returning here? NOTREACHED condition? + return registration; +} + +ServiceWorkerRegistration* +ServiceWorkerStorage::FindInstallingRegistrationForDocument( + const GURL& document_url) { + DCHECK(!document_url.has_ref()); + + LongestScopeMatcher matcher(document_url); + ServiceWorkerRegistration* match = NULL; + + // TODO(nhiroki): This searches over installing registrations linearly and it + // couldn't be scalable. Maybe the regs should be partitioned by origin. + for (RegistrationRefsById::const_iterator it = + installing_registrations_.begin(); + it != installing_registrations_.end(); ++it) { + if (matcher.MatchLongest(it->second->pattern())) + match = it->second; + } + return match; +} + +ServiceWorkerRegistration* +ServiceWorkerStorage::FindInstallingRegistrationForPattern( + const GURL& scope) { + for (RegistrationRefsById::const_iterator it = + installing_registrations_.begin(); + it != installing_registrations_.end(); ++it) { + if (it->second->pattern() == scope) + return it->second; + } + return NULL; +} + +ServiceWorkerRegistration* +ServiceWorkerStorage::FindInstallingRegistrationForId( + int64 registration_id) { + RegistrationRefsById::const_iterator found = + installing_registrations_.find(registration_id); + if (found == installing_registrations_.end()) + return NULL; + return found->second; +} + +ServiceWorkerDiskCache* ServiceWorkerStorage::disk_cache() { + if (disk_cache_) + return disk_cache_.get(); + + disk_cache_.reset(new ServiceWorkerDiskCache); + + base::FilePath path = GetDiskCachePath(); + if (path.empty()) { + int rv = disk_cache_->InitWithMemBackend( + kMaxMemDiskCacheSize, + base::Bind(&EmptyCompletionCallback)); + DCHECK_EQ(net::OK, rv); + return disk_cache_.get(); + } + + int rv = disk_cache_->InitWithDiskBackend( + path, kMaxDiskCacheSize, false, + disk_cache_thread_.get(), + base::Bind(&ServiceWorkerStorage::OnDiskCacheInitialized, + weak_factory_.GetWeakPtr())); + if (rv != net::ERR_IO_PENDING) + OnDiskCacheInitialized(rv); + + return disk_cache_.get(); +} + +void ServiceWorkerStorage::OnDiskCacheInitialized(int rv) { + if (rv != net::OK) { + LOG(ERROR) << "Failed to open the serviceworker diskcache: " + << net::ErrorToString(rv); + // TODO(michaeln): DeleteAndStartOver() + disk_cache_->Disable(); + state_ = DISABLED; + } + ServiceWorkerHistograms::CountInitDiskCacheResult(rv == net::OK); +} + +void ServiceWorkerStorage::StartPurgingResources( + const std::vector<int64>& ids) { + for (size_t i = 0; i < ids.size(); ++i) + purgeable_reource_ids_.push_back(ids[i]); + ContinuePurgingResources(); +} + +void ServiceWorkerStorage::StartPurgingResources( + const ResourceList& resources) { + for (size_t i = 0; i < resources.size(); ++i) + purgeable_reource_ids_.push_back(resources[i].resource_id); + ContinuePurgingResources(); +} + +void ServiceWorkerStorage::ContinuePurgingResources() { + if (purgeable_reource_ids_.empty() || is_purge_pending_) + return; + + // Do one at a time until we're done, use RunSoon to avoid recursion when + // DoomEntry returns immediately. + is_purge_pending_ = true; + int64 id = purgeable_reource_ids_.front(); + purgeable_reource_ids_.pop_front(); + RunSoon(FROM_HERE, + base::Bind(&ServiceWorkerStorage::PurgeResource, + weak_factory_.GetWeakPtr(), id)); +} + +void ServiceWorkerStorage::PurgeResource(int64 id) { + DCHECK(is_purge_pending_); + int rv = disk_cache()->DoomEntry( + id, base::Bind(&ServiceWorkerStorage::OnResourcePurged, + weak_factory_.GetWeakPtr(), id)); + if (rv != net::ERR_IO_PENDING) + OnResourcePurged(id, rv); +} + +void ServiceWorkerStorage::OnResourcePurged(int64 id, int rv) { + DCHECK(is_purge_pending_); + is_purge_pending_ = false; + + database_task_runner_->PostTask( + FROM_HERE, + base::Bind(base::IgnoreResult( + &ServiceWorkerDatabase::ClearPurgeableResourceIds), + base::Unretained(database_.get()), + std::set<int64>(&id, &id + 1))); + + ContinuePurgingResources(); +} + +void ServiceWorkerStorage::ReadInitialDataFromDB( + ServiceWorkerDatabase* database, + scoped_refptr<base::SequencedTaskRunner> original_task_runner, + const InitializeCallback& callback) { + DCHECK(database); + scoped_ptr<ServiceWorkerStorage::InitialData> data( + new ServiceWorkerStorage::InitialData()); + + ServiceWorkerDatabase::Status status = + database->GetNextAvailableIds(&data->next_registration_id, + &data->next_version_id, + &data->next_resource_id); + if (status != ServiceWorkerDatabase::STATUS_OK) { + original_task_runner->PostTask( + FROM_HERE, base::Bind(callback, base::Owned(data.release()), status)); + return; + } + + status = database->GetOriginsWithRegistrations(&data->origins); + original_task_runner->PostTask( + FROM_HERE, base::Bind(callback, base::Owned(data.release()), status)); +} + +void ServiceWorkerStorage::DeleteRegistrationFromDB( + ServiceWorkerDatabase* database, + scoped_refptr<base::SequencedTaskRunner> original_task_runner, + int64 registration_id, + const GURL& origin, + const DeleteRegistrationCallback& callback) { + DCHECK(database); + + std::vector<int64> newly_purgeable_resources; + ServiceWorkerDatabase::Status status = + database->DeleteRegistration(registration_id, origin, + &newly_purgeable_resources); + if (status != ServiceWorkerDatabase::STATUS_OK) { + original_task_runner->PostTask( + FROM_HERE, base::Bind(callback, false, std::vector<int64>(), status)); + return; + } + + // TODO(nhiroki): Add convenient method to ServiceWorkerDatabase to check the + // unique origin list. + std::vector<ServiceWorkerDatabase::RegistrationData> registrations; + status = database->GetRegistrationsForOrigin(origin, ®istrations); + if (status != ServiceWorkerDatabase::STATUS_OK) { + original_task_runner->PostTask( + FROM_HERE, base::Bind(callback, false, std::vector<int64>(), status)); + return; + } + + bool deletable = registrations.empty(); + original_task_runner->PostTask( + FROM_HERE, base::Bind(callback, deletable, + newly_purgeable_resources, status)); +} + +void ServiceWorkerStorage::WriteRegistrationInDB( + ServiceWorkerDatabase* database, + scoped_refptr<base::SequencedTaskRunner> original_task_runner, + const ServiceWorkerDatabase::RegistrationData& data, + const ResourceList& resources, + const WriteRegistrationCallback& callback) { + DCHECK(database); + std::vector<int64> newly_purgeable_resources; + ServiceWorkerDatabase::Status status = + database->WriteRegistration(data, resources, &newly_purgeable_resources); + original_task_runner->PostTask( + FROM_HERE, + base::Bind(callback, data.script.GetOrigin(), + newly_purgeable_resources, status)); +} + +void ServiceWorkerStorage::FindForDocumentInDB( + ServiceWorkerDatabase* database, + scoped_refptr<base::SequencedTaskRunner> original_task_runner, + const GURL& document_url, + const FindInDBCallback& callback) { + GURL origin = document_url.GetOrigin(); + RegistrationList registrations; + ServiceWorkerDatabase::Status status = + database->GetRegistrationsForOrigin(origin, ®istrations); + if (status != ServiceWorkerDatabase::STATUS_OK) { + original_task_runner->PostTask( + FROM_HERE, + base::Bind(callback, + ServiceWorkerDatabase::RegistrationData(), + ResourceList(), + status)); + return; + } + + ServiceWorkerDatabase::RegistrationData data; + ResourceList resources; + status = ServiceWorkerDatabase::STATUS_ERROR_NOT_FOUND; + + // Find one with a pattern match. + LongestScopeMatcher matcher(document_url); + int64 match = kInvalidServiceWorkerRegistrationId; + for (size_t i = 0; i < registrations.size(); ++i) { + if (matcher.MatchLongest(registrations[i].scope)) + match = registrations[i].registration_id; + } + + if (match != kInvalidServiceWorkerRegistrationId) + status = database->ReadRegistration(match, origin, &data, &resources); + + original_task_runner->PostTask( + FROM_HERE, + base::Bind(callback, data, resources, status)); +} + +void ServiceWorkerStorage::FindForPatternInDB( + ServiceWorkerDatabase* database, + scoped_refptr<base::SequencedTaskRunner> original_task_runner, + const GURL& scope, + const FindInDBCallback& callback) { + GURL origin = scope.GetOrigin(); + std::vector<ServiceWorkerDatabase::RegistrationData> registrations; + ServiceWorkerDatabase::Status status = + database->GetRegistrationsForOrigin(origin, ®istrations); + if (status != ServiceWorkerDatabase::STATUS_OK) { + original_task_runner->PostTask( + FROM_HERE, + base::Bind(callback, + ServiceWorkerDatabase::RegistrationData(), + ResourceList(), + status)); + return; + } + + // Find one with an exact matching scope. + ServiceWorkerDatabase::RegistrationData data; + ResourceList resources; + status = ServiceWorkerDatabase::STATUS_ERROR_NOT_FOUND; + for (RegistrationList::const_iterator it = registrations.begin(); + it != registrations.end(); ++it) { + if (scope != it->scope) + continue; + status = database->ReadRegistration(it->registration_id, origin, + &data, &resources); + break; // We're done looping. + } + + original_task_runner->PostTask( + FROM_HERE, + base::Bind(callback, data, resources, status)); +} + +void ServiceWorkerStorage::FindForIdInDB( + ServiceWorkerDatabase* database, + scoped_refptr<base::SequencedTaskRunner> original_task_runner, + int64 registration_id, + const GURL& origin, + const FindInDBCallback& callback) { + ServiceWorkerDatabase::RegistrationData data; + ResourceList resources; + ServiceWorkerDatabase::Status status = + database->ReadRegistration(registration_id, origin, &data, &resources); + original_task_runner->PostTask( + FROM_HERE, base::Bind(callback, data, resources, status)); } } // namespace content diff --git a/chromium/content/browser/service_worker/service_worker_storage.h b/chromium/content/browser/service_worker/service_worker_storage.h index b3f1dbfaf69..5d89864dc21 100644 --- a/chromium/content/browser/service_worker/service_worker_storage.h +++ b/chromium/content/browser/service_worker/service_worker_storage.h @@ -5,99 +5,297 @@ #ifndef CONTENT_BROWSER_SERVICE_WORKER_SERVICE_WORKER_STORAGE_H_ #define CONTENT_BROWSER_SERVICE_WORKER_SERVICE_WORKER_STORAGE_H_ +#include <deque> #include <map> +#include <set> +#include <vector> #include "base/bind.h" #include "base/files/file_path.h" #include "base/gtest_prod_util.h" #include "base/memory/scoped_vector.h" -#include "content/browser/service_worker/service_worker_registration_status.h" +#include "base/memory/weak_ptr.h" +#include "content/browser/service_worker/service_worker_database.h" #include "content/common/content_export.h" +#include "content/common/service_worker/service_worker_status_code.h" #include "url/gurl.h" +namespace base { +class MessageLoopProxy; +class SequencedTaskRunner; +} + namespace quota { class QuotaManagerProxy; } namespace content { +class ServiceWorkerContextCore; +class ServiceWorkerDiskCache; class ServiceWorkerRegistration; -class ServiceWorkerRegisterJob; +class ServiceWorkerRegistrationInfo; +class ServiceWorkerResponseReader; +class ServiceWorkerResponseWriter; +class ServiceWorkerVersion; -// This class provides an interface to load registration data and -// instantiate ServiceWorkerRegistration objects. Any asynchronous -// operations are run through instances of ServiceWorkerRegisterJob. +// This class provides an interface to store and retrieve ServiceWorker +// registration data. class CONTENT_EXPORT ServiceWorkerStorage { public: - ServiceWorkerStorage(const base::FilePath& path, - quota::QuotaManagerProxy* quota_manager_proxy); - ~ServiceWorkerStorage(); - - typedef base::Callback<void(ServiceWorkerRegistrationStatus status, + typedef std::vector<ServiceWorkerDatabase::ResourceRecord> ResourceList; + typedef base::Callback<void(ServiceWorkerStatusCode status)> StatusCallback; + typedef base::Callback<void(ServiceWorkerStatusCode status, const scoped_refptr<ServiceWorkerRegistration>& - registration)> RegistrationCallback; + registration)> FindRegistrationCallback; + typedef base::Callback< + void(const std::vector<ServiceWorkerRegistrationInfo>& registrations)> + GetAllRegistrationInfosCallback; typedef base::Callback< - void(ServiceWorkerRegistrationStatus status)> UnregistrationCallback; + void(ServiceWorkerStatusCode status, int result)> + CompareCallback; - // `found` is only valid if status == REGISTRATION_OK. - typedef base::Callback<void(bool found, - ServiceWorkerRegistrationStatus status, - const scoped_refptr<ServiceWorkerRegistration>& - registration)> FindRegistrationCallback; + ServiceWorkerStorage(const base::FilePath& path, + base::WeakPtr<ServiceWorkerContextCore> context, + base::SequencedTaskRunner* database_task_runner, + base::MessageLoopProxy* disk_cache_thread, + quota::QuotaManagerProxy* quota_manager_proxy); + ~ServiceWorkerStorage(); + // Finds registration for |document_url| or |pattern| or |registration_id|. + // The Find methods will find stored and initially installing registrations. + // Returns SERVICE_WORKER_OK with non-null registration if registration + // is found, or returns SERVICE_WORKER_ERROR_NOT_FOUND if no matching + // registration is found. The FindRegistrationForPattern method is + // guaranteed to return asynchronously. However, the methods to find + // for |document_url| or |registration_id| may complete immediately + // (the callback may be called prior to the method returning) or + // asynchronously. void FindRegistrationForDocument(const GURL& document_url, const FindRegistrationCallback& callback); - void FindRegistrationForPattern(const GURL& pattern, + void FindRegistrationForPattern(const GURL& scope, const FindRegistrationCallback& callback); + void FindRegistrationForId(int64 registration_id, + const GURL& origin, + const FindRegistrationCallback& callback); + + // Returns info about all stored and initially installing registrations. + void GetAllRegistrations(const GetAllRegistrationInfosCallback& callback); + + // Commits |registration| with the installed but not activated |version| + // to storage, overwritting any pre-existing registration data for the scope. + // A pre-existing version's script resources will remain available until + // either a browser restart or DeleteVersionResources is called. + void StoreRegistration( + ServiceWorkerRegistration* registration, + ServiceWorkerVersion* version, + const StatusCallback& callback); + + // Updates the state of the registration's stored version to active. + void UpdateToActiveState( + ServiceWorkerRegistration* registration, + const StatusCallback& callback); + + // Deletes the registration data for |registration_id|, the + // script resources for the registration's stored version + // will remain available until either a browser restart or + // DeleteVersionResources is called. + void DeleteRegistration(int64 registration_id, + const GURL& origin, + const StatusCallback& callback); + + scoped_ptr<ServiceWorkerResponseReader> CreateResponseReader( + int64 response_id); + scoped_ptr<ServiceWorkerResponseWriter> CreateResponseWriter( + int64 response_id); + + // Adds |id| to the set of resources ids that are in the disk + // cache but not yet stored with a registration. + void StoreUncommittedReponseId(int64 id); + + // Removes |id| from uncommitted list, adds it to the + // purgeable list and purges it. + void DoomUncommittedResponse(int64 id); - void Register(const GURL& pattern, - const GURL& script_url, - const RegistrationCallback& callback); + // Returns new IDs which are guaranteed to be unique in the storage. + int64 NewRegistrationId(); + int64 NewVersionId(); + int64 NewResourceId(); - void Unregister(const GURL& pattern, const UnregistrationCallback& callback); + // Intended for use only by ServiceWorkerRegisterJob. + void NotifyInstallingRegistration( + ServiceWorkerRegistration* registration); + void NotifyDoneInstallingRegistration( + ServiceWorkerRegistration* registration, + ServiceWorkerVersion* version, + ServiceWorkerStatusCode status); private: - friend class ServiceWorkerRegisterJob; - FRIEND_TEST_ALL_PREFIXES(ServiceWorkerStorageTest, PatternMatches); - - typedef std::map<GURL, scoped_refptr<ServiceWorkerRegistration> > - PatternToRegistrationMap; - typedef ScopedVector<ServiceWorkerRegisterJob> RegistrationJobList; - - // TODO(alecflett): These are temporary internal methods providing - // synchronous in-memory registration. Eventually these will be - // replaced by asynchronous methods that persist registration to disk. - scoped_refptr<ServiceWorkerRegistration> RegisterInternal( - const GURL& pattern, - const GURL& script_url); - void UnregisterInternal(const GURL& pattern); - static bool PatternMatches(const GURL& pattern, const GURL& script_url); - - // Jobs are removed whenever they are finished or canceled. - void EraseJob(ServiceWorkerRegisterJob* job); - - // Called at ServiceWorkerRegisterJob completion. - void RegisterComplete(const RegistrationCallback& callback, - ServiceWorkerRegisterJob* job, - ServiceWorkerRegistrationStatus status, - ServiceWorkerRegistration* registration); - - // Called at ServiceWorkerRegisterJob completion. - void UnregisterComplete(const UnregistrationCallback& callback, - ServiceWorkerRegisterJob* job, - ServiceWorkerRegistrationStatus status, - ServiceWorkerRegistration* registration); - - // This is the in-memory registration. Eventually the registration will be - // persisted to disk. - // A list of currently running jobs. This is a temporary structure until we - // start managing overlapping registrations explicitly. - RegistrationJobList registration_jobs_; - - // in-memory map, to eventually be replaced with persistence - PatternToRegistrationMap registration_by_pattern_; - scoped_refptr<quota::QuotaManagerProxy> quota_manager_proxy_; + friend class ServiceWorkerStorageTest; + FRIEND_TEST_ALL_PREFIXES(ServiceWorkerStorageTest, + ResourceIdsAreStoredAndPurged); + + struct InitialData { + int64 next_registration_id; + int64 next_version_id; + int64 next_resource_id; + std::set<GURL> origins; + + InitialData(); + ~InitialData(); + }; + + typedef std::vector<ServiceWorkerDatabase::RegistrationData> RegistrationList; + typedef std::map<int64, scoped_refptr<ServiceWorkerRegistration> > + RegistrationRefsById; + typedef base::Callback<void( + InitialData* data, + ServiceWorkerDatabase::Status status)> InitializeCallback; + typedef base::Callback<void( + const GURL& origin, + const std::vector<int64>& newly_purgeable_resources, + ServiceWorkerDatabase::Status status)> WriteRegistrationCallback; + typedef base::Callback<void( + bool origin_is_deletable, + const std::vector<int64>& newly_purgeable_resources, + ServiceWorkerDatabase::Status status)> DeleteRegistrationCallback; + typedef base::Callback<void( + const ServiceWorkerDatabase::RegistrationData& data, + const ResourceList& resources, + ServiceWorkerDatabase::Status status)> FindInDBCallback; + + base::FilePath GetDatabasePath(); + base::FilePath GetDiskCachePath(); + + bool LazyInitialize( + const base::Closure& callback); + void DidReadInitialData( + InitialData* data, + ServiceWorkerDatabase::Status status); + void DidFindRegistrationForDocument( + const GURL& document_url, + const FindRegistrationCallback& callback, + const ServiceWorkerDatabase::RegistrationData& data, + const ResourceList& resources, + ServiceWorkerDatabase::Status status); + void DidFindRegistrationForPattern( + const GURL& scope, + const FindRegistrationCallback& callback, + const ServiceWorkerDatabase::RegistrationData& data, + const ResourceList& resources, + ServiceWorkerDatabase::Status status); + void DidFindRegistrationForId( + const FindRegistrationCallback& callback, + const ServiceWorkerDatabase::RegistrationData& data, + const ResourceList& resources, + ServiceWorkerDatabase::Status status); + void DidGetAllRegistrations( + const GetAllRegistrationInfosCallback& callback, + RegistrationList* registrations, + ServiceWorkerDatabase::Status status); + void DidStoreRegistration( + const StatusCallback& callback, + const GURL& origin, + const std::vector<int64>& newly_purgeable_resources, + ServiceWorkerDatabase::Status status); + void DidUpdateToActiveState( + const StatusCallback& callback, + ServiceWorkerDatabase::Status status); + void DidDeleteRegistration( + const GURL& origin, + const StatusCallback& callback, + bool origin_is_deletable, + const std::vector<int64>& newly_purgeable_resources, + ServiceWorkerDatabase::Status status); + + scoped_refptr<ServiceWorkerRegistration> GetOrCreateRegistration( + const ServiceWorkerDatabase::RegistrationData& data, + const ResourceList& resources); + ServiceWorkerRegistration* FindInstallingRegistrationForDocument( + const GURL& document_url); + ServiceWorkerRegistration* FindInstallingRegistrationForPattern( + const GURL& scope); + ServiceWorkerRegistration* FindInstallingRegistrationForId( + int64 registration_id); + + // Lazy disk_cache getter. + ServiceWorkerDiskCache* disk_cache(); + void OnDiskCacheInitialized(int rv); + + void StartPurgingResources(const std::vector<int64>& ids); + void StartPurgingResources(const ResourceList& resources); + void ContinuePurgingResources(); + void PurgeResource(int64 id); + void OnResourcePurged(int64 id, int rv); + + // Static cross-thread helpers. + static void ReadInitialDataFromDB( + ServiceWorkerDatabase* database, + scoped_refptr<base::SequencedTaskRunner> original_task_runner, + const InitializeCallback& callback); + static void DeleteRegistrationFromDB( + ServiceWorkerDatabase* database, + scoped_refptr<base::SequencedTaskRunner> original_task_runner, + int64 registration_id, + const GURL& origin, + const DeleteRegistrationCallback& callback); + static void WriteRegistrationInDB( + ServiceWorkerDatabase* database, + scoped_refptr<base::SequencedTaskRunner> original_task_runner, + const ServiceWorkerDatabase::RegistrationData& registration, + const ResourceList& resources, + const WriteRegistrationCallback& callback); + static void FindForDocumentInDB( + ServiceWorkerDatabase* database, + scoped_refptr<base::SequencedTaskRunner> original_task_runner, + const GURL& document_url, + const FindInDBCallback& callback); + static void FindForPatternInDB( + ServiceWorkerDatabase* database, + scoped_refptr<base::SequencedTaskRunner> original_task_runner, + const GURL& scope, + const FindInDBCallback& callback); + static void FindForIdInDB( + ServiceWorkerDatabase* database, + scoped_refptr<base::SequencedTaskRunner> original_task_runner, + int64 registration_id, + const GURL& origin, + const FindInDBCallback& callback); + + // For finding registrations being installed. + RegistrationRefsById installing_registrations_; + + // Origins having registations. + std::set<GURL> registered_origins_; + + // Pending database tasks waiting for initialization. + std::vector<base::Closure> pending_tasks_; + + int64 next_registration_id_; + int64 next_version_id_; + int64 next_resource_id_; + + enum State { + UNINITIALIZED, + INITIALIZING, + INITIALIZED, + DISABLED, + }; + State state_; + base::FilePath path_; + base::WeakPtr<ServiceWorkerContextCore> context_; + + // Only accessed on |database_task_runner_|. + scoped_ptr<ServiceWorkerDatabase> database_; + + scoped_refptr<base::SequencedTaskRunner> database_task_runner_; + scoped_refptr<base::MessageLoopProxy> disk_cache_thread_; + scoped_refptr<quota::QuotaManagerProxy> quota_manager_proxy_; + scoped_ptr<ServiceWorkerDiskCache> disk_cache_; + std::deque<int64> purgeable_reource_ids_; + bool is_purge_pending_; + base::WeakPtrFactory<ServiceWorkerStorage> weak_factory_; DISALLOW_COPY_AND_ASSIGN(ServiceWorkerStorage); diff --git a/chromium/content/browser/service_worker/service_worker_storage_unittest.cc b/chromium/content/browser/service_worker/service_worker_storage_unittest.cc index bd6d629eb80..417a0b7f237 100644 --- a/chromium/content/browser/service_worker/service_worker_storage_unittest.cc +++ b/chromium/content/browser/service_worker/service_worker_storage_unittest.cc @@ -2,383 +2,638 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -#include "content/browser/service_worker/service_worker_storage.h" +#include <string> -#include "base/files/scoped_temp_dir.h" #include "base/logging.h" +#include "base/message_loop/message_loop.h" #include "base/run_loop.h" #include "content/browser/browser_thread_impl.h" +#include "content/browser/service_worker/service_worker_context_core.h" +#include "content/browser/service_worker/service_worker_disk_cache.h" #include "content/browser/service_worker/service_worker_registration.h" +#include "content/browser/service_worker/service_worker_storage.h" +#include "content/browser/service_worker/service_worker_version.h" +#include "content/common/service_worker/service_worker_status_code.h" #include "content/public/test/test_browser_thread_bundle.h" +#include "net/base/io_buffer.h" +#include "net/base/net_errors.h" +#include "net/http/http_response_headers.h" #include "testing/gtest/include/gtest/gtest.h" +using net::IOBuffer; +using net::WrappedIOBuffer; + namespace content { namespace { -void SaveRegistrationCallback( - ServiceWorkerRegistrationStatus expected_status, - bool* called, - scoped_refptr<ServiceWorkerRegistration>* registration, - ServiceWorkerRegistrationStatus status, - const scoped_refptr<ServiceWorkerRegistration>& result) { - EXPECT_EQ(expected_status, status); - *called = true; - *registration = result; -} +typedef ServiceWorkerDatabase::RegistrationData RegistrationData; +typedef ServiceWorkerDatabase::ResourceRecord ResourceRecord; -void SaveFoundRegistrationCallback( - bool expected_found, - ServiceWorkerRegistrationStatus expected_status, - bool* called, - scoped_refptr<ServiceWorkerRegistration>* registration, - bool found, - ServiceWorkerRegistrationStatus status, - const scoped_refptr<ServiceWorkerRegistration>& result) { - EXPECT_EQ(expected_found, found); - EXPECT_EQ(expected_status, status); - *called = true; - *registration = result; +void StatusCallback(bool* was_called, + ServiceWorkerStatusCode* result, + ServiceWorkerStatusCode status) { + *was_called = true; + *result = status; } -// Creates a callback which both keeps track of if it's been called, -// as well as the resulting registration. Whent the callback is fired, -// it ensures that the resulting status matches the expectation. -// 'called' is useful for making sure a sychronous callback is or -// isn't called. -ServiceWorkerStorage::RegistrationCallback SaveRegistration( - ServiceWorkerRegistrationStatus expected_status, - bool* called, - scoped_refptr<ServiceWorkerRegistration>* registration) { - *called = false; - return base::Bind( - &SaveRegistrationCallback, expected_status, called, registration); +ServiceWorkerStorage::StatusCallback MakeStatusCallback( + bool* was_called, + ServiceWorkerStatusCode* result) { + return base::Bind(&StatusCallback, was_called, result); } -ServiceWorkerStorage::FindRegistrationCallback SaveFoundRegistration( - bool expected_found, - ServiceWorkerRegistrationStatus expected_status, - bool* called, - scoped_refptr<ServiceWorkerRegistration>* registration) { - *called = false; - return base::Bind(&SaveFoundRegistrationCallback, - expected_found, - expected_status, - called, - registration); +void FindCallback( + bool* was_called, + ServiceWorkerStatusCode* result, + scoped_refptr<ServiceWorkerRegistration>* found, + ServiceWorkerStatusCode status, + const scoped_refptr<ServiceWorkerRegistration>& registration) { + *was_called = true; + *result = status; + *found = registration; } -void SaveUnregistrationCallback(ServiceWorkerRegistrationStatus expected_status, - bool* called, - ServiceWorkerRegistrationStatus status) { - EXPECT_EQ(expected_status, status); - *called = true; +ServiceWorkerStorage::FindRegistrationCallback MakeFindCallback( + bool* was_called, + ServiceWorkerStatusCode* result, + scoped_refptr<ServiceWorkerRegistration>* found) { + return base::Bind(&FindCallback, was_called, result, found); } -ServiceWorkerStorage::UnregistrationCallback SaveUnregistration( - ServiceWorkerRegistrationStatus expected_status, - bool* called) { - *called = false; - return base::Bind(&SaveUnregistrationCallback, expected_status, called); +void GetAllCallback( + bool* was_called, + std::vector<ServiceWorkerRegistrationInfo>* all_out, + const std::vector<ServiceWorkerRegistrationInfo>& all) { + *was_called = true; + *all_out = all; } -} // namespace - -class ServiceWorkerStorageTest : public testing::Test { - public: - ServiceWorkerStorageTest() - : browser_thread_bundle_(TestBrowserThreadBundle::IO_MAINLOOP) {} - - virtual void SetUp() OVERRIDE { - storage_.reset(new ServiceWorkerStorage(base::FilePath(), NULL)); - } - - virtual void TearDown() OVERRIDE { storage_.reset(); } - - protected: - TestBrowserThreadBundle browser_thread_bundle_; - scoped_ptr<ServiceWorkerStorage> storage_; -}; - -TEST_F(ServiceWorkerStorageTest, PatternMatches) { - ASSERT_TRUE(ServiceWorkerStorage::PatternMatches( - GURL("http://www.example.com/*"), GURL("http://www.example.com/"))); - ASSERT_TRUE(ServiceWorkerStorage::PatternMatches( - GURL("http://www.example.com/*"), - GURL("http://www.example.com/page.html"))); - - ASSERT_FALSE(ServiceWorkerStorage::PatternMatches( - GURL("http://www.example.com/*"), GURL("https://www.example.com/"))); - ASSERT_FALSE(ServiceWorkerStorage::PatternMatches( - GURL("http://www.example.com/*"), - GURL("https://www.example.com/page.html"))); - - ASSERT_FALSE(ServiceWorkerStorage::PatternMatches( - GURL("http://www.example.com/*"), GURL("http://www.foo.com/"))); - ASSERT_FALSE(ServiceWorkerStorage::PatternMatches( - GURL("http://www.example.com/*"), GURL("https://www.foo.com/page.html"))); +ServiceWorkerStorage::GetAllRegistrationInfosCallback MakeGetAllCallback( + bool* was_called, + std::vector<ServiceWorkerRegistrationInfo>* all) { + return base::Bind(&GetAllCallback, was_called, all); } -TEST_F(ServiceWorkerStorageTest, SameDocumentSameRegistration) { - scoped_refptr<ServiceWorkerRegistration> original_registration; - bool called; - storage_->Register( - GURL("http://www.example.com/*"), - GURL("http://www.example.com/service_worker.js"), - SaveRegistration(REGISTRATION_OK, &called, &original_registration)); - EXPECT_FALSE(called); - base::RunLoop().RunUntilIdle(); - EXPECT_TRUE(called); - - scoped_refptr<ServiceWorkerRegistration> registration1; - storage_->FindRegistrationForDocument( - GURL("http://www.example.com/"), - SaveFoundRegistration(true, REGISTRATION_OK, &called, ®istration1)); - scoped_refptr<ServiceWorkerRegistration> registration2; - storage_->FindRegistrationForDocument( - GURL("http://www.example.com/"), - SaveFoundRegistration(true, REGISTRATION_OK, &called, ®istration2)); - - ServiceWorkerRegistration* null_registration(NULL); - ASSERT_EQ(null_registration, registration1); - ASSERT_EQ(null_registration, registration2); - EXPECT_FALSE(called); - base::RunLoop().RunUntilIdle(); - EXPECT_TRUE(called); - ASSERT_NE(null_registration, registration1); - ASSERT_NE(null_registration, registration2); - - ASSERT_EQ(registration1, registration2); +void OnIOComplete(int* rv_out, int rv) { + *rv_out = rv; } -TEST_F(ServiceWorkerStorageTest, SameMatchSameRegistration) { - bool called; - scoped_refptr<ServiceWorkerRegistration> original_registration; - storage_->Register( - GURL("http://www.example.com/*"), - GURL("http://www.example.com/service_worker.js"), - SaveRegistration(REGISTRATION_OK, &called, &original_registration)); - EXPECT_FALSE(called); +void WriteBasicResponse(ServiceWorkerStorage* storage, int64 id) { + scoped_ptr<ServiceWorkerResponseWriter> writer = + storage->CreateResponseWriter(id); + + const char kHttpHeaders[] = + "HTTP/1.0 200 HONKYDORY\0Content-Length: 6\0\0"; + const char kHttpBody[] = "Hello\0"; + scoped_refptr<IOBuffer> body(new WrappedIOBuffer(kHttpBody)); + std::string raw_headers(kHttpHeaders, arraysize(kHttpHeaders)); + scoped_ptr<net::HttpResponseInfo> info(new net::HttpResponseInfo); + info->request_time = base::Time::Now(); + info->response_time = base::Time::Now(); + info->was_cached = false; + info->headers = new net::HttpResponseHeaders(raw_headers); + scoped_refptr<HttpResponseInfoIOBuffer> info_buffer = + new HttpResponseInfoIOBuffer(info.release()); + + int rv = -1234; + writer->WriteInfo(info_buffer, base::Bind(&OnIOComplete, &rv)); base::RunLoop().RunUntilIdle(); - EXPECT_TRUE(called); - ASSERT_NE(static_cast<ServiceWorkerRegistration*>(NULL), - original_registration.get()); - - scoped_refptr<ServiceWorkerRegistration> registration1; - storage_->FindRegistrationForDocument( - GURL("http://www.example.com/one"), - SaveFoundRegistration(true, REGISTRATION_OK, &called, ®istration1)); + EXPECT_LT(0, rv); - EXPECT_FALSE(called); + rv = -1234; + writer->WriteData(body, arraysize(kHttpBody), + base::Bind(&OnIOComplete, &rv)); base::RunLoop().RunUntilIdle(); - EXPECT_TRUE(called); - - scoped_refptr<ServiceWorkerRegistration> registration2; - storage_->FindRegistrationForDocument( - GURL("http://www.example.com/two"), - SaveFoundRegistration(true, REGISTRATION_OK, &called, ®istration2)); - EXPECT_FALSE(called); - base::RunLoop().RunUntilIdle(); - EXPECT_TRUE(called); - - ASSERT_EQ(registration1, registration2); + EXPECT_EQ(static_cast<int>(arraysize(kHttpBody)), rv); } -TEST_F(ServiceWorkerStorageTest, DifferentMatchDifferentRegistration) { - bool called1; - scoped_refptr<ServiceWorkerRegistration> original_registration1; - storage_->Register( - GURL("http://www.example.com/one/*"), - GURL("http://www.example.com/service_worker.js"), - SaveRegistration(REGISTRATION_OK, &called1, &original_registration1)); - - bool called2; - scoped_refptr<ServiceWorkerRegistration> original_registration2; - storage_->Register( - GURL("http://www.example.com/two/*"), - GURL("http://www.example.com/service_worker.js"), - SaveRegistration(REGISTRATION_OK, &called2, &original_registration2)); - - EXPECT_FALSE(called1); - EXPECT_FALSE(called2); +bool VerifyBasicResponse(ServiceWorkerStorage* storage, int64 id, + bool expected_positive_result) { + const char kExpectedHttpBody[] = "Hello\0"; + scoped_ptr<ServiceWorkerResponseReader> reader = + storage->CreateResponseReader(id); + scoped_refptr<HttpResponseInfoIOBuffer> info_buffer = + new HttpResponseInfoIOBuffer(); + int rv = -1234; + reader->ReadInfo(info_buffer, base::Bind(&OnIOComplete, &rv)); base::RunLoop().RunUntilIdle(); - EXPECT_TRUE(called2); - EXPECT_TRUE(called1); - - scoped_refptr<ServiceWorkerRegistration> registration1; - storage_->FindRegistrationForDocument( - GURL("http://www.example.com/one/"), - SaveFoundRegistration(true, REGISTRATION_OK, &called1, ®istration1)); - scoped_refptr<ServiceWorkerRegistration> registration2; - storage_->FindRegistrationForDocument( - GURL("http://www.example.com/two/"), - SaveFoundRegistration(true, REGISTRATION_OK, &called2, ®istration2)); - - EXPECT_FALSE(called1); - EXPECT_FALSE(called2); + if (expected_positive_result) + EXPECT_LT(0, rv); + if (rv <= 0) + return false; + + const int kBigEnough = 512; + scoped_refptr<net::IOBuffer> buffer = new IOBuffer(kBigEnough); + rv = -1234; + reader->ReadData(buffer, kBigEnough, base::Bind(&OnIOComplete, &rv)); base::RunLoop().RunUntilIdle(); - EXPECT_TRUE(called2); - EXPECT_TRUE(called1); - - ASSERT_NE(registration1, registration2); + EXPECT_EQ(static_cast<int>(arraysize(kExpectedHttpBody)), rv); + if (rv <= 0) + return false; + + bool status_match = + std::string("HONKYDORY") == + info_buffer->http_info->headers->GetStatusText(); + bool data_match = + std::string(kExpectedHttpBody) == std::string(buffer->data()); + + EXPECT_TRUE(status_match); + EXPECT_TRUE(data_match); + return status_match && data_match; } -// Make sure basic registration is working. -TEST_F(ServiceWorkerStorageTest, Register) { - bool called = false; - scoped_refptr<ServiceWorkerRegistration> registration; - storage_->Register(GURL("http://www.example.com/*"), - GURL("http://www.example.com/service_worker.js"), - SaveRegistration(REGISTRATION_OK, &called, ®istration)); - - ASSERT_FALSE(called); - base::RunLoop().RunUntilIdle(); - ASSERT_TRUE(called); - - ASSERT_NE(scoped_refptr<ServiceWorkerRegistration>(NULL), registration); -} - -// Make sure registrations are cleaned up when they are unregistered. -TEST_F(ServiceWorkerStorageTest, Unregister) { - GURL pattern("http://www.example.com/*"); - - bool called; - scoped_refptr<ServiceWorkerRegistration> registration; - storage_->Register(pattern, - GURL("http://www.example.com/service_worker.js"), - SaveRegistration(REGISTRATION_OK, &called, ®istration)); - - ASSERT_FALSE(called); - base::RunLoop().RunUntilIdle(); - ASSERT_TRUE(called); - - storage_->Unregister(pattern, SaveUnregistration(REGISTRATION_OK, &called)); - - ASSERT_FALSE(called); - base::RunLoop().RunUntilIdle(); - ASSERT_TRUE(called); - - ASSERT_TRUE(registration->HasOneRef()); - - storage_->FindRegistrationForPattern( - pattern, - SaveFoundRegistration(false, REGISTRATION_OK, &called, ®istration)); - - ASSERT_FALSE(called); - base::RunLoop().RunUntilIdle(); - ASSERT_TRUE(called); - - ASSERT_EQ(scoped_refptr<ServiceWorkerRegistration>(NULL), registration); -} +} // namespace -// Make sure that when a new registration replaces an existing -// registration, that the old one is cleaned up. -TEST_F(ServiceWorkerStorageTest, RegisterNewScript) { - GURL pattern("http://www.example.com/*"); +class ServiceWorkerStorageTest : public testing::Test { + public: + ServiceWorkerStorageTest() + : browser_thread_bundle_(TestBrowserThreadBundle::IO_MAINLOOP) { + } - bool called; - scoped_refptr<ServiceWorkerRegistration> old_registration; - storage_->Register( - pattern, - GURL("http://www.example.com/service_worker.js"), - SaveRegistration(REGISTRATION_OK, &called, &old_registration)); + virtual void SetUp() OVERRIDE { + context_.reset( + new ServiceWorkerContextCore(base::FilePath(), + base::MessageLoopProxy::current(), + base::MessageLoopProxy::current(), + NULL, + NULL, + NULL)); + context_ptr_ = context_->AsWeakPtr(); + } - ASSERT_FALSE(called); - base::RunLoop().RunUntilIdle(); - ASSERT_TRUE(called); + virtual void TearDown() OVERRIDE { + context_.reset(); + } - scoped_refptr<ServiceWorkerRegistration> old_registration_by_pattern; - storage_->FindRegistrationForPattern( - pattern, - SaveFoundRegistration( - true, REGISTRATION_OK, &called, &old_registration_by_pattern)); + ServiceWorkerStorage* storage() { return context_->storage(); } + + // A static class method for friendliness. + static void VerifyPurgeableListStatusCallback( + ServiceWorkerDatabase* database, + std::set<int64> *purgeable_ids, + bool* was_called, + ServiceWorkerStatusCode* result, + ServiceWorkerStatusCode status) { + *was_called = true; + *result = status; + EXPECT_EQ(ServiceWorkerDatabase::STATUS_OK, + database->GetPurgeableResourceIds(purgeable_ids)); + } - ASSERT_FALSE(called); - base::RunLoop().RunUntilIdle(); - ASSERT_TRUE(called); + protected: + ServiceWorkerStatusCode StoreRegistration( + scoped_refptr<ServiceWorkerRegistration> registration, + scoped_refptr<ServiceWorkerVersion> version) { + bool was_called = false; + ServiceWorkerStatusCode result = SERVICE_WORKER_ERROR_FAILED; + storage()->StoreRegistration( + registration, version, MakeStatusCallback(&was_called, &result)); + EXPECT_FALSE(was_called); // always async + base::RunLoop().RunUntilIdle(); + EXPECT_TRUE(was_called); + return result; + } - ASSERT_EQ(old_registration, old_registration_by_pattern); - old_registration_by_pattern = NULL; + ServiceWorkerStatusCode DeleteRegistration( + int64 registration_id, + const GURL& origin) { + bool was_called = false; + ServiceWorkerStatusCode result = SERVICE_WORKER_ERROR_FAILED; + storage()->DeleteRegistration( + registration_id, origin, MakeStatusCallback(&was_called, &result)); + EXPECT_FALSE(was_called); // always async + base::RunLoop().RunUntilIdle(); + EXPECT_TRUE(was_called); + return result; + } - scoped_refptr<ServiceWorkerRegistration> new_registration; - storage_->Register( - pattern, - GURL("http://www.example.com/service_worker_new.js"), - SaveRegistration(REGISTRATION_OK, &called, &new_registration)); + void GetAllRegistrations( + std::vector<ServiceWorkerRegistrationInfo>* registrations) { + bool was_called = false; + storage()->GetAllRegistrations( + MakeGetAllCallback(&was_called, registrations)); + EXPECT_FALSE(was_called); // always async + base::RunLoop().RunUntilIdle(); + EXPECT_TRUE(was_called); + } - ASSERT_FALSE(called); - base::RunLoop().RunUntilIdle(); - ASSERT_TRUE(called); + ServiceWorkerStatusCode UpdateToActiveState( + scoped_refptr<ServiceWorkerRegistration> registration) { + bool was_called = false; + ServiceWorkerStatusCode result = SERVICE_WORKER_ERROR_FAILED; + storage()->UpdateToActiveState( + registration, MakeStatusCallback(&was_called, &result)); + EXPECT_FALSE(was_called); // always async + base::RunLoop().RunUntilIdle(); + EXPECT_TRUE(was_called); + return result; + } - ASSERT_TRUE(old_registration->HasOneRef()); + ServiceWorkerStatusCode FindRegistrationForDocument( + const GURL& document_url, + scoped_refptr<ServiceWorkerRegistration>* registration) { + bool was_called = false; + ServiceWorkerStatusCode result = SERVICE_WORKER_ERROR_FAILED; + storage()->FindRegistrationForDocument( + document_url, MakeFindCallback(&was_called, &result, registration)); + base::RunLoop().RunUntilIdle(); + EXPECT_TRUE(was_called); + return result; + } - ASSERT_NE(old_registration, new_registration); + ServiceWorkerStatusCode FindRegistrationForPattern( + const GURL& scope, + scoped_refptr<ServiceWorkerRegistration>* registration) { + bool was_called = false; + ServiceWorkerStatusCode result = SERVICE_WORKER_ERROR_FAILED; + storage()->FindRegistrationForPattern( + scope, MakeFindCallback(&was_called, &result, registration)); + EXPECT_FALSE(was_called); // always async + base::RunLoop().RunUntilIdle(); + EXPECT_TRUE(was_called); + return result; + } - scoped_refptr<ServiceWorkerRegistration> new_registration_by_pattern; - storage_->FindRegistrationForPattern( - pattern, - SaveFoundRegistration(true, REGISTRATION_OK, &called, &new_registration)); + ServiceWorkerStatusCode FindRegistrationForId( + int64 registration_id, + const GURL& origin, + scoped_refptr<ServiceWorkerRegistration>* registration) { + bool was_called = false; + ServiceWorkerStatusCode result = SERVICE_WORKER_ERROR_FAILED; + storage()->FindRegistrationForId( + registration_id, origin, + MakeFindCallback(&was_called, &result, registration)); + base::RunLoop().RunUntilIdle(); + EXPECT_TRUE(was_called); + return result; + } - ASSERT_FALSE(called); - base::RunLoop().RunUntilIdle(); - ASSERT_TRUE(called); + scoped_ptr<ServiceWorkerContextCore> context_; + base::WeakPtr<ServiceWorkerContextCore> context_ptr_; + TestBrowserThreadBundle browser_thread_bundle_; +}; - ASSERT_NE(new_registration_by_pattern, old_registration); +TEST_F(ServiceWorkerStorageTest, StoreFindUpdateDeleteRegistration) { + const GURL kScope("http://www.test.not/scope/*"); + const GURL kScript("http://www.test.not/script.js"); + const GURL kDocumentUrl("http://www.test.not/scope/document.html"); + const int64 kRegistrationId = 0; + const int64 kVersionId = 0; + + scoped_refptr<ServiceWorkerRegistration> found_registration; + + // We shouldn't find anything without having stored anything. + EXPECT_EQ(SERVICE_WORKER_ERROR_NOT_FOUND, + FindRegistrationForDocument(kDocumentUrl, &found_registration)); + EXPECT_FALSE(found_registration); + + EXPECT_EQ(SERVICE_WORKER_ERROR_NOT_FOUND, + FindRegistrationForPattern(kScope, &found_registration)); + EXPECT_FALSE(found_registration); + + EXPECT_EQ(SERVICE_WORKER_ERROR_NOT_FOUND, + FindRegistrationForId( + kRegistrationId, kScope.GetOrigin(), &found_registration)); + EXPECT_FALSE(found_registration); + + // Store something. + scoped_refptr<ServiceWorkerRegistration> live_registration = + new ServiceWorkerRegistration( + kScope, kScript, kRegistrationId, context_ptr_); + scoped_refptr<ServiceWorkerVersion> live_version = + new ServiceWorkerVersion( + live_registration, kVersionId, context_ptr_); + live_version->SetStatus(ServiceWorkerVersion::INSTALLED); + live_registration->set_waiting_version(live_version); + EXPECT_EQ(SERVICE_WORKER_OK, + StoreRegistration(live_registration, live_version)); + + // Now we should find it and get the live ptr back immediately. + EXPECT_EQ(SERVICE_WORKER_OK, + FindRegistrationForDocument(kDocumentUrl, &found_registration)); + EXPECT_EQ(live_registration, found_registration); + found_registration = NULL; + + // But FindRegistrationForPattern is always async. + EXPECT_EQ(SERVICE_WORKER_OK, + FindRegistrationForPattern(kScope, &found_registration)); + EXPECT_EQ(live_registration, found_registration); + found_registration = NULL; + + // Can be found by id too. + EXPECT_EQ(SERVICE_WORKER_OK, + FindRegistrationForId( + kRegistrationId, kScope.GetOrigin(), &found_registration)); + ASSERT_TRUE(found_registration); + EXPECT_EQ(kRegistrationId, found_registration->id()); + EXPECT_EQ(live_registration, found_registration); + found_registration = NULL; + + // Drop the live registration, but keep the version live. + live_registration = NULL; + + // Now FindRegistrationForDocument should be async. + EXPECT_EQ(SERVICE_WORKER_OK, + FindRegistrationForDocument(kDocumentUrl, &found_registration)); + ASSERT_TRUE(found_registration); + EXPECT_EQ(kRegistrationId, found_registration->id()); + EXPECT_TRUE(found_registration->HasOneRef()); + EXPECT_EQ(live_version, found_registration->waiting_version()); + found_registration = NULL; + + // Drop the live version too. + live_version = NULL; + + // And FindRegistrationForPattern is always async. + EXPECT_EQ(SERVICE_WORKER_OK, + FindRegistrationForPattern(kScope, &found_registration)); + ASSERT_TRUE(found_registration); + EXPECT_EQ(kRegistrationId, found_registration->id()); + EXPECT_TRUE(found_registration->HasOneRef()); + EXPECT_FALSE(found_registration->active_version()); + ASSERT_TRUE(found_registration->waiting_version()); + EXPECT_EQ(ServiceWorkerVersion::INSTALLED, + found_registration->waiting_version()->status()); + + // Update to active. + scoped_refptr<ServiceWorkerVersion> temp_version = + found_registration->waiting_version(); + found_registration->set_waiting_version(NULL); + temp_version->SetStatus(ServiceWorkerVersion::ACTIVE); + found_registration->set_active_version(temp_version); + temp_version = NULL; + EXPECT_EQ(SERVICE_WORKER_OK, UpdateToActiveState(found_registration)); + found_registration = NULL; + + // Trying to update a unstored registration to active should fail. + scoped_refptr<ServiceWorkerRegistration> unstored_registration = + new ServiceWorkerRegistration( + kScope, kScript, kRegistrationId + 1, context_ptr_); + EXPECT_EQ(SERVICE_WORKER_ERROR_NOT_FOUND, + UpdateToActiveState(unstored_registration)); + unstored_registration = NULL; + + // The Find methods should return a registration with an active version. + EXPECT_EQ(SERVICE_WORKER_OK, + FindRegistrationForDocument(kDocumentUrl, &found_registration)); + ASSERT_TRUE(found_registration); + EXPECT_EQ(kRegistrationId, found_registration->id()); + EXPECT_TRUE(found_registration->HasOneRef()); + EXPECT_FALSE(found_registration->waiting_version()); + ASSERT_TRUE(found_registration->active_version()); + EXPECT_EQ(ServiceWorkerVersion::ACTIVE, + found_registration->active_version()->status()); + + // Delete from storage but with a instance still live. + EXPECT_TRUE(context_->GetLiveVersion(kRegistrationId)); + EXPECT_EQ(SERVICE_WORKER_OK, + DeleteRegistration(kRegistrationId, kScope.GetOrigin())); + EXPECT_TRUE(context_->GetLiveVersion(kRegistrationId)); + + // Should no longer be found. + EXPECT_EQ(SERVICE_WORKER_ERROR_NOT_FOUND, + FindRegistrationForId( + kRegistrationId, kScope.GetOrigin(), &found_registration)); + EXPECT_FALSE(found_registration); + + // Deleting an unstored registration should succeed. + EXPECT_EQ(SERVICE_WORKER_OK, + DeleteRegistration(kRegistrationId + 1, kScope.GetOrigin())); } -// Make sure that when registering a duplicate pattern+script_url -// combination, that the same registration is used. -TEST_F(ServiceWorkerStorageTest, RegisterDuplicateScript) { - GURL pattern("http://www.example.com/*"); - GURL script_url("http://www.example.com/service_worker.js"); - - bool called; - scoped_refptr<ServiceWorkerRegistration> old_registration; - storage_->Register( - pattern, - script_url, - SaveRegistration(REGISTRATION_OK, &called, &old_registration)); - - ASSERT_FALSE(called); - base::RunLoop().RunUntilIdle(); - ASSERT_TRUE(called); - - scoped_refptr<ServiceWorkerRegistration> old_registration_by_pattern; - storage_->FindRegistrationForPattern( - pattern, - SaveFoundRegistration( - true, REGISTRATION_OK, &called, &old_registration_by_pattern)); - ASSERT_FALSE(called); - base::RunLoop().RunUntilIdle(); - ASSERT_TRUE(called); - - ASSERT_TRUE(old_registration_by_pattern); - - scoped_refptr<ServiceWorkerRegistration> new_registration; - storage_->Register( - pattern, - script_url, - SaveRegistration(REGISTRATION_OK, &called, &new_registration)); +TEST_F(ServiceWorkerStorageTest, InstallingRegistrationsAreFindable) { + const GURL kScope("http://www.test.not/scope/*"); + const GURL kScript("http://www.test.not/script.js"); + const GURL kDocumentUrl("http://www.test.not/scope/document.html"); + const int64 kRegistrationId = 0; + const int64 kVersionId = 0; + + scoped_refptr<ServiceWorkerRegistration> found_registration; + + // Create an unstored registration. + scoped_refptr<ServiceWorkerRegistration> live_registration = + new ServiceWorkerRegistration( + kScope, kScript, kRegistrationId, context_ptr_); + scoped_refptr<ServiceWorkerVersion> live_version = + new ServiceWorkerVersion( + live_registration, kVersionId, context_ptr_); + live_version->SetStatus(ServiceWorkerVersion::INSTALLING); + live_registration->set_waiting_version(live_version); + + // Should not be findable, including by GetAllRegistrations. + EXPECT_EQ(SERVICE_WORKER_ERROR_NOT_FOUND, + FindRegistrationForId( + kRegistrationId, kScope.GetOrigin(), &found_registration)); + EXPECT_FALSE(found_registration); + + EXPECT_EQ(SERVICE_WORKER_ERROR_NOT_FOUND, + FindRegistrationForDocument(kDocumentUrl, &found_registration)); + EXPECT_FALSE(found_registration); + + EXPECT_EQ(SERVICE_WORKER_ERROR_NOT_FOUND, + FindRegistrationForPattern(kScope, &found_registration)); + EXPECT_FALSE(found_registration); + + std::vector<ServiceWorkerRegistrationInfo> all_registrations; + GetAllRegistrations(&all_registrations); + EXPECT_TRUE(all_registrations.empty()); + + // Notify storage of it being installed. + storage()->NotifyInstallingRegistration(live_registration); + + // Now should be findable. + EXPECT_EQ(SERVICE_WORKER_OK, + FindRegistrationForId( + kRegistrationId, kScope.GetOrigin(), &found_registration)); + EXPECT_EQ(live_registration, found_registration); + found_registration = NULL; + + EXPECT_EQ(SERVICE_WORKER_OK, + FindRegistrationForDocument(kDocumentUrl, &found_registration)); + EXPECT_EQ(live_registration, found_registration); + found_registration = NULL; + + EXPECT_EQ(SERVICE_WORKER_OK, + FindRegistrationForPattern(kScope, &found_registration)); + EXPECT_EQ(live_registration, found_registration); + found_registration = NULL; + + GetAllRegistrations(&all_registrations); + EXPECT_EQ(1u, all_registrations.size()); + all_registrations.clear(); + + // Notify storage of installation no longer happening. + storage()->NotifyDoneInstallingRegistration( + live_registration, NULL, SERVICE_WORKER_OK); + + // Once again, should not be findable. + EXPECT_EQ(SERVICE_WORKER_ERROR_NOT_FOUND, + FindRegistrationForId( + kRegistrationId, kScope.GetOrigin(), &found_registration)); + EXPECT_FALSE(found_registration); + + EXPECT_EQ(SERVICE_WORKER_ERROR_NOT_FOUND, + FindRegistrationForDocument(kDocumentUrl, &found_registration)); + EXPECT_FALSE(found_registration); + + EXPECT_EQ(SERVICE_WORKER_ERROR_NOT_FOUND, + FindRegistrationForPattern(kScope, &found_registration)); + EXPECT_FALSE(found_registration); + + GetAllRegistrations(&all_registrations); + EXPECT_TRUE(all_registrations.empty()); +} - ASSERT_FALSE(called); +TEST_F(ServiceWorkerStorageTest, ResourceIdsAreStoredAndPurged) { + storage()->LazyInitialize(base::Bind(&base::DoNothing)); base::RunLoop().RunUntilIdle(); - ASSERT_TRUE(called); - - ASSERT_EQ(old_registration, new_registration); - - ASSERT_FALSE(old_registration->HasOneRef()); - - scoped_refptr<ServiceWorkerRegistration> new_registration_by_pattern; - storage_->FindRegistrationForPattern( - pattern, - SaveFoundRegistration( - true, REGISTRATION_OK, &called, &new_registration_by_pattern)); - - ASSERT_FALSE(called); + const GURL kScope("http://www.test.not/scope/*"); + const GURL kScript("http://www.test.not/script.js"); + const GURL kImport("http://www.test.not/import.js"); + const GURL kDocumentUrl("http://www.test.not/scope/document.html"); + const int64 kRegistrationId = storage()->NewRegistrationId(); + const int64 kVersionId = storage()->NewVersionId(); + const int64 kResourceId1 = storage()->NewResourceId(); + const int64 kResourceId2 = storage()->NewResourceId(); + + // Cons up a new registration+version with two script resources. + RegistrationData data; + data.registration_id = kRegistrationId; + data.scope = kScope; + data.script = kScript; + data.version_id = kVersionId; + data.is_active = false; + std::vector<ResourceRecord> resources; + resources.push_back(ResourceRecord(kResourceId1, kScript)); + resources.push_back(ResourceRecord(kResourceId2, kImport)); + scoped_refptr<ServiceWorkerRegistration> registration = + storage()->GetOrCreateRegistration(data, resources); + registration->waiting_version()->SetStatus(ServiceWorkerVersion::NEW); + + // Add the resources ids to the uncommitted list. + std::set<int64> resource_ids; + resource_ids.insert(kResourceId1); + resource_ids.insert(kResourceId2); + EXPECT_EQ(ServiceWorkerDatabase::STATUS_OK, + storage()->database_->WriteUncommittedResourceIds(resource_ids)); + + // And dump something in the disk cache for them. + WriteBasicResponse(storage(), kResourceId1); + WriteBasicResponse(storage(), kResourceId2); + EXPECT_TRUE(VerifyBasicResponse(storage(), kResourceId1, true)); + EXPECT_TRUE(VerifyBasicResponse(storage(), kResourceId2, true)); + + // Storing the registration/version should take the resources ids out + // of the uncommitted list. + EXPECT_EQ(SERVICE_WORKER_OK, + StoreRegistration(registration, registration->waiting_version())); + std::set<int64> verify_ids; + EXPECT_EQ(ServiceWorkerDatabase::STATUS_OK, + storage()->database_->GetUncommittedResourceIds(&verify_ids)); + EXPECT_TRUE(verify_ids.empty()); + + // Deleting it should result in the resources being added to the + // purgeable list and then doomed in the disk cache and removed from + // that list. + bool was_called = false; + ServiceWorkerStatusCode result = SERVICE_WORKER_ERROR_FAILED; + verify_ids.clear(); + storage()->DeleteRegistration( + registration->id(), kScope.GetOrigin(), + base::Bind(&VerifyPurgeableListStatusCallback, + base::Unretained(storage()->database_.get()), + &verify_ids, &was_called, &result)); base::RunLoop().RunUntilIdle(); - ASSERT_TRUE(called); + ASSERT_TRUE(was_called); + EXPECT_EQ(SERVICE_WORKER_OK, result); + EXPECT_EQ(2u, verify_ids.size()); + verify_ids.clear(); + EXPECT_EQ(ServiceWorkerDatabase::STATUS_OK, + storage()->database_->GetPurgeableResourceIds(&verify_ids)); + EXPECT_TRUE(verify_ids.empty()); + + EXPECT_FALSE(VerifyBasicResponse(storage(), kResourceId1, false)); + EXPECT_FALSE(VerifyBasicResponse(storage(), kResourceId2, false)); +} - ASSERT_EQ(new_registration, old_registration); +TEST_F(ServiceWorkerStorageTest, FindRegistration_LongestScopeMatch) { + const GURL kDocumentUrl("http://www.example.com/scope/foo"); + scoped_refptr<ServiceWorkerRegistration> found_registration; + + // Registration for "/scope/*". + const GURL kScope1("http://www.example.com/scope/*"); + const GURL kScript1("http://www.example.com/script1.js"); + const int64 kRegistrationId1 = 1; + const int64 kVersionId1 = 1; + scoped_refptr<ServiceWorkerRegistration> live_registration1 = + new ServiceWorkerRegistration( + kScope1, kScript1, kRegistrationId1, context_ptr_); + scoped_refptr<ServiceWorkerVersion> live_version1 = + new ServiceWorkerVersion( + live_registration1, kVersionId1, context_ptr_); + live_version1->SetStatus(ServiceWorkerVersion::INSTALLED); + live_registration1->set_waiting_version(live_version1); + + // Registration for "/scope/foo*". + const GURL kScope2("http://www.example.com/scope/foo*"); + const GURL kScript2("http://www.example.com/script2.js"); + const int64 kRegistrationId2 = 2; + const int64 kVersionId2 = 2; + scoped_refptr<ServiceWorkerRegistration> live_registration2 = + new ServiceWorkerRegistration( + kScope2, kScript2, kRegistrationId2, context_ptr_); + scoped_refptr<ServiceWorkerVersion> live_version2 = + new ServiceWorkerVersion( + live_registration2, kVersionId2, context_ptr_); + live_version2->SetStatus(ServiceWorkerVersion::INSTALLED); + live_registration2->set_waiting_version(live_version2); + + // Registration for "/scope/foo". + const GURL kScope3("http://www.example.com/scope/foo"); + const GURL kScript3("http://www.example.com/script3.js"); + const int64 kRegistrationId3 = 3; + const int64 kVersionId3 = 3; + scoped_refptr<ServiceWorkerRegistration> live_registration3 = + new ServiceWorkerRegistration( + kScope3, kScript3, kRegistrationId3, context_ptr_); + scoped_refptr<ServiceWorkerVersion> live_version3 = + new ServiceWorkerVersion( + live_registration3, kVersionId3, context_ptr_); + live_version3->SetStatus(ServiceWorkerVersion::INSTALLED); + live_registration3->set_waiting_version(live_version3); + + // Notify storage of they being installed. + storage()->NotifyInstallingRegistration(live_registration1); + storage()->NotifyInstallingRegistration(live_registration2); + storage()->NotifyInstallingRegistration(live_registration3); + + // Find a registration among installing ones. + EXPECT_EQ(SERVICE_WORKER_OK, + FindRegistrationForDocument(kDocumentUrl, &found_registration)); + EXPECT_EQ(live_registration2, found_registration); + found_registration = NULL; + + // Store registrations. + EXPECT_EQ(SERVICE_WORKER_OK, + StoreRegistration(live_registration1, live_version1)); + EXPECT_EQ(SERVICE_WORKER_OK, + StoreRegistration(live_registration2, live_version2)); + EXPECT_EQ(SERVICE_WORKER_OK, + StoreRegistration(live_registration3, live_version3)); + + // Notify storage of installations no longer happening. + storage()->NotifyDoneInstallingRegistration( + live_registration1, NULL, SERVICE_WORKER_OK); + storage()->NotifyDoneInstallingRegistration( + live_registration2, NULL, SERVICE_WORKER_OK); + storage()->NotifyDoneInstallingRegistration( + live_registration3, NULL, SERVICE_WORKER_OK); + + // Find a registration among installed ones. + EXPECT_EQ(SERVICE_WORKER_OK, + FindRegistrationForDocument(kDocumentUrl, &found_registration)); + EXPECT_EQ(live_registration2, found_registration); } } // namespace content diff --git a/chromium/content/browser/service_worker/service_worker_test_utils.h b/chromium/content/browser/service_worker/service_worker_test_utils.h new file mode 100644 index 00000000000..5bb72c9e105 --- /dev/null +++ b/chromium/content/browser/service_worker/service_worker_test_utils.h @@ -0,0 +1,39 @@ +// Copyright 2014 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 CONTENT_BROWSER_SERVICE_WORKER_SERVICE_WORKER_TEST_UTILS_H_ +#define CONTENT_BROWSER_SERVICE_WORKER_SERVICE_WORKER_TEST_UTILS_H_ + +#include "base/bind.h" +#include "base/callback.h" +#include "content/public/browser/browser_thread.h" + +namespace content { + +template <typename Arg> +void ReceiveResult(BrowserThread::ID run_quit_thread, + const base::Closure& quit, + Arg* out, Arg actual) { + *out = actual; + if (!quit.is_null()) + BrowserThread::PostTask(run_quit_thread, FROM_HERE, quit); +} + +template <typename Arg> base::Callback<void(Arg)> +CreateReceiver(BrowserThread::ID run_quit_thread, + const base::Closure& quit, Arg* out) { + return base::Bind(&ReceiveResult<Arg>, run_quit_thread, quit, out); +} + +template <typename Arg> base::Callback<void(Arg)> +CreateReceiverOnCurrentThread(Arg* out) { + BrowserThread::ID id; + bool ret = BrowserThread::GetCurrentThreadIdentifier(&id); + DCHECK(ret); + return base::Bind(&ReceiveResult<Arg>, id, base::Closure(), out); +} + +} // namespace content + +#endif // CONTENT_BROWSER_SERVICE_WORKER_SERVICE_WORKER_TEST_UTILS_H_ diff --git a/chromium/content/browser/service_worker/service_worker_unregister_job.cc b/chromium/content/browser/service_worker/service_worker_unregister_job.cc new file mode 100644 index 00000000000..dabcd22f108 --- /dev/null +++ b/chromium/content/browser/service_worker/service_worker_unregister_job.cc @@ -0,0 +1,90 @@ +// Copyright 2014 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 "content/browser/service_worker/service_worker_unregister_job.h" + +#include "content/browser/service_worker/service_worker_context_core.h" +#include "content/browser/service_worker/service_worker_job_coordinator.h" +#include "content/browser/service_worker/service_worker_registration.h" +#include "content/browser/service_worker/service_worker_storage.h" + +namespace content { + +typedef ServiceWorkerRegisterJobBase::RegistrationJobType RegistrationJobType; + +ServiceWorkerUnregisterJob::ServiceWorkerUnregisterJob( + base::WeakPtr<ServiceWorkerContextCore> context, + const GURL& pattern) + : context_(context), + pattern_(pattern), + weak_factory_(this) {} + +ServiceWorkerUnregisterJob::~ServiceWorkerUnregisterJob() {} + +void ServiceWorkerUnregisterJob::AddCallback( + const UnregistrationCallback& callback) { + callbacks_.push_back(callback); +} + +void ServiceWorkerUnregisterJob::Start() { + context_->storage()->FindRegistrationForPattern( + pattern_, + base::Bind(&ServiceWorkerUnregisterJob::DeleteExistingRegistration, + weak_factory_.GetWeakPtr())); +} + +void ServiceWorkerUnregisterJob::Abort() { + CompleteInternal(SERVICE_WORKER_ERROR_ABORT); +} + +bool ServiceWorkerUnregisterJob::Equals(ServiceWorkerRegisterJobBase* job) { + if (job->GetType() != GetType()) + return false; + return static_cast<ServiceWorkerUnregisterJob*>(job)->pattern_ == pattern_; +} + +RegistrationJobType ServiceWorkerUnregisterJob::GetType() { + return ServiceWorkerRegisterJobBase::UNREGISTRATION; +} + +void ServiceWorkerUnregisterJob::DeleteExistingRegistration( + ServiceWorkerStatusCode status, + const scoped_refptr<ServiceWorkerRegistration>& registration) { + if (status == SERVICE_WORKER_OK) { + DCHECK(registration); + // TODO(michaeln): Deactivate the live registration object and + // eventually call storage->DeleteVersionResources() + // when the version no longer has any controllees. + context_->storage()->DeleteRegistration( + registration->id(), + registration->script_url().GetOrigin(), + base::Bind(&ServiceWorkerUnregisterJob::Complete, + weak_factory_.GetWeakPtr())); + return; + } + + if (status == SERVICE_WORKER_ERROR_NOT_FOUND) { + DCHECK(!registration); + Complete(SERVICE_WORKER_OK); + return; + } + + Complete(status); +} + +void ServiceWorkerUnregisterJob::Complete(ServiceWorkerStatusCode status) { + CompleteInternal(status); + context_->job_coordinator()->FinishJob(pattern_, this); +} + +void ServiceWorkerUnregisterJob::CompleteInternal( + ServiceWorkerStatusCode status) { + for (std::vector<UnregistrationCallback>::iterator it = callbacks_.begin(); + it != callbacks_.end(); + ++it) { + it->Run(status); + } +} + +} // namespace content diff --git a/chromium/content/browser/service_worker/service_worker_unregister_job.h b/chromium/content/browser/service_worker/service_worker_unregister_job.h new file mode 100644 index 00000000000..484318136f3 --- /dev/null +++ b/chromium/content/browser/service_worker/service_worker_unregister_job.h @@ -0,0 +1,64 @@ +// Copyright 2014 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 CONTENT_BROWSER_SERVICE_WORKER_SERVICE_WORKER_UNREGISTER_JOB_H_ +#define CONTENT_BROWSER_SERVICE_WORKER_SERVICE_WORKER_UNREGISTER_JOB_H_ + +#include <vector> + +#include "base/memory/weak_ptr.h" +#include "content/browser/service_worker/service_worker_register_job_base.h" +#include "content/common/service_worker/service_worker_status_code.h" +#include "url/gurl.h" + +namespace content { + +class EmbeddedWorkerRegistry; +class ServiceWorkerContextCore; +class ServiceWorkerJobCoordinator; +class ServiceWorkerRegistration; +class ServiceWorkerStorage; + +// Handles the unregistration of a Service Worker. +// +// The unregistration process is primarily cleanup, removing everything that was +// created during the registration process, including the +// ServiceWorkerRegistration itself. +class ServiceWorkerUnregisterJob : public ServiceWorkerRegisterJobBase { + public: + typedef base::Callback<void(ServiceWorkerStatusCode status)> + UnregistrationCallback; + + ServiceWorkerUnregisterJob(base::WeakPtr<ServiceWorkerContextCore> context, + const GURL& pattern); + virtual ~ServiceWorkerUnregisterJob(); + + // Registers a callback to be called when the job completes (whether + // successfully or not). Multiple callbacks may be registered. + void AddCallback(const UnregistrationCallback& callback); + + // ServiceWorkerRegisterJobBase implementation: + virtual void Start() OVERRIDE; + virtual void Abort() OVERRIDE; + virtual bool Equals(ServiceWorkerRegisterJobBase* job) OVERRIDE; + virtual RegistrationJobType GetType() OVERRIDE; + + private: + void DeleteExistingRegistration( + ServiceWorkerStatusCode status, + const scoped_refptr<ServiceWorkerRegistration>& registration); + void Complete(ServiceWorkerStatusCode status); + void CompleteInternal(ServiceWorkerStatusCode status); + + // The ServiceWorkerStorage object should always outlive this. + base::WeakPtr<ServiceWorkerContextCore> context_; + const GURL pattern_; + std::vector<UnregistrationCallback> callbacks_; + base::WeakPtrFactory<ServiceWorkerUnregisterJob> weak_factory_; + + DISALLOW_COPY_AND_ASSIGN(ServiceWorkerUnregisterJob); +}; +} // namespace content + +#endif // CONTENT_BROWSER_SERVICE_WORKER_SERVICE_WORKER_UNREGISTER_JOB_H_ diff --git a/chromium/content/browser/service_worker/service_worker_url_request_job.cc b/chromium/content/browser/service_worker/service_worker_url_request_job.cc new file mode 100644 index 00000000000..9d75b93ec25 --- /dev/null +++ b/chromium/content/browser/service_worker/service_worker_url_request_job.cc @@ -0,0 +1,304 @@ +// Copyright 2014 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 "content/browser/service_worker/service_worker_url_request_job.h" + +#include "base/bind.h" +#include "base/strings/stringprintf.h" +#include "content/browser/service_worker/service_worker_fetch_dispatcher.h" +#include "content/browser/service_worker/service_worker_provider_host.h" +#include "net/http/http_request_headers.h" +#include "net/http/http_response_headers.h" +#include "net/http/http_response_info.h" +#include "net/http/http_util.h" +#include "webkit/browser/blob/blob_data_handle.h" +#include "webkit/browser/blob/blob_storage_context.h" +#include "webkit/browser/blob/blob_url_request_job_factory.h" + +namespace content { + +ServiceWorkerURLRequestJob::ServiceWorkerURLRequestJob( + net::URLRequest* request, + net::NetworkDelegate* network_delegate, + base::WeakPtr<ServiceWorkerProviderHost> provider_host, + base::WeakPtr<webkit_blob::BlobStorageContext> blob_storage_context) + : net::URLRequestJob(request, network_delegate), + provider_host_(provider_host), + response_type_(NOT_DETERMINED), + is_started_(false), + blob_storage_context_(blob_storage_context), + weak_factory_(this) { +} + +void ServiceWorkerURLRequestJob::FallbackToNetwork() { + DCHECK_EQ(NOT_DETERMINED, response_type_); + response_type_ = FALLBACK_TO_NETWORK; + MaybeStartRequest(); +} + +void ServiceWorkerURLRequestJob::ForwardToServiceWorker() { + DCHECK_EQ(NOT_DETERMINED, response_type_); + response_type_ = FORWARD_TO_SERVICE_WORKER; + MaybeStartRequest(); +} + +void ServiceWorkerURLRequestJob::Start() { + is_started_ = true; + MaybeStartRequest(); +} + +void ServiceWorkerURLRequestJob::Kill() { + net::URLRequestJob::Kill(); + fetch_dispatcher_.reset(); + blob_request_.reset(); + weak_factory_.InvalidateWeakPtrs(); +} + +net::LoadState ServiceWorkerURLRequestJob::GetLoadState() const { + // TODO(kinuko): refine this for better debug. + return net::URLRequestJob::GetLoadState(); +} + +bool ServiceWorkerURLRequestJob::GetCharset(std::string* charset) { + if (!http_info()) + return false; + return http_info()->headers->GetCharset(charset); +} + +bool ServiceWorkerURLRequestJob::GetMimeType(std::string* mime_type) const { + if (!http_info()) + return false; + return http_info()->headers->GetMimeType(mime_type); +} + +void ServiceWorkerURLRequestJob::GetResponseInfo(net::HttpResponseInfo* info) { + if (!http_info()) + return; + *info = *http_info(); +} + +int ServiceWorkerURLRequestJob::GetResponseCode() const { + if (!http_info()) + return -1; + return http_info()->headers->response_code(); +} + +void ServiceWorkerURLRequestJob::SetExtraRequestHeaders( + const net::HttpRequestHeaders& headers) { + std::string range_header; + std::vector<net::HttpByteRange> ranges; + if (!headers.GetHeader(net::HttpRequestHeaders::kRange, &range_header) || + !net::HttpUtil::ParseRangeHeader(range_header, &ranges)) { + return; + } + + // We don't support multiple range requests in one single URL request. + if (ranges.size() == 1U) + byte_range_ = ranges[0]; +} + +bool ServiceWorkerURLRequestJob::ReadRawData( + net::IOBuffer* buf, int buf_size, int *bytes_read) { + if (!blob_request_) { + *bytes_read = 0; + return true; + } + + blob_request_->Read(buf, buf_size, bytes_read); + net::URLRequestStatus status = blob_request_->status(); + SetStatus(status); + if (status.is_io_pending()) + return false; + return status.is_success(); +} + +void ServiceWorkerURLRequestJob::OnReceivedRedirect(net::URLRequest* request, + const GURL& new_url, + bool* defer_redirect) { + NOTREACHED(); +} + +void ServiceWorkerURLRequestJob::OnAuthRequired( + net::URLRequest* request, + net::AuthChallengeInfo* auth_info) { + NOTREACHED(); +} + +void ServiceWorkerURLRequestJob::OnCertificateRequested( + net::URLRequest* request, + net::SSLCertRequestInfo* cert_request_info) { + NOTREACHED(); +} + +void ServiceWorkerURLRequestJob::OnSSLCertificateError( + net::URLRequest* request, + const net::SSLInfo& ssl_info, + bool fatal) { + NOTREACHED(); +} + +void ServiceWorkerURLRequestJob::OnBeforeNetworkStart(net::URLRequest* request, + bool* defer) { + NOTREACHED(); +} + +void ServiceWorkerURLRequestJob::OnResponseStarted(net::URLRequest* request) { + // TODO(falken): Add Content-Length, Content-Type if they were not provided in + // the ServiceWorkerResponse. + CommitResponseHeader(); +} + +void ServiceWorkerURLRequestJob::OnReadCompleted(net::URLRequest* request, + int bytes_read) { + SetStatus(request->status()); + if (!request->status().is_success()) { + NotifyDone(request->status()); + return; + } + NotifyReadComplete(bytes_read); + if (bytes_read == 0) + NotifyDone(request->status()); +} + +const net::HttpResponseInfo* ServiceWorkerURLRequestJob::http_info() const { + if (!http_response_info_) + return NULL; + if (range_response_info_) + return range_response_info_.get(); + return http_response_info_.get(); +} + +ServiceWorkerURLRequestJob::~ServiceWorkerURLRequestJob() { +} + +void ServiceWorkerURLRequestJob::MaybeStartRequest() { + if (is_started_ && response_type_ != NOT_DETERMINED) { + // Start asynchronously. + base::MessageLoop::current()->PostTask( + FROM_HERE, + base::Bind(&ServiceWorkerURLRequestJob::StartRequest, + weak_factory_.GetWeakPtr())); + } +} + +void ServiceWorkerURLRequestJob::StartRequest() { + switch (response_type_) { + case NOT_DETERMINED: + NOTREACHED(); + return; + + case FALLBACK_TO_NETWORK: + // Restart the request to create a new job. Our request handler will + // return NULL, and the default job (which will hit network) should be + // created. + NotifyRestartRequired(); + return; + + case FORWARD_TO_SERVICE_WORKER: + DCHECK(provider_host_ && provider_host_->active_version()); + DCHECK(!fetch_dispatcher_); + + // Send a fetch event to the ServiceWorker associated to the + // provider_host. + fetch_dispatcher_.reset(new ServiceWorkerFetchDispatcher( + request(), provider_host_->active_version(), + base::Bind(&ServiceWorkerURLRequestJob::DidDispatchFetchEvent, + weak_factory_.GetWeakPtr()))); + fetch_dispatcher_->Run(); + return; + } + + NOTREACHED(); +} + +void ServiceWorkerURLRequestJob::DidDispatchFetchEvent( + ServiceWorkerStatusCode status, + ServiceWorkerFetchEventResult fetch_result, + const ServiceWorkerResponse& response) { + fetch_dispatcher_.reset(); + + // Check if we're not orphaned. + if (!request()) + return; + + if (status != SERVICE_WORKER_OK) { + // Dispatching event has been failed, falling back to the network. + // (Tentative behavior described on github) + // TODO(kinuko): consider returning error if we've come here because + // unexpected worker termination etc (so that we could fix bugs). + // TODO(kinuko): Would be nice to log the error case. + response_type_ = FALLBACK_TO_NETWORK; + NotifyRestartRequired(); + return; + } + + if (fetch_result == SERVICE_WORKER_FETCH_EVENT_RESULT_FALLBACK) { + // Change the response type and restart the request to fallback to + // the network. + response_type_ = FALLBACK_TO_NETWORK; + NotifyRestartRequired(); + return; + } + + // We should have a response now. + DCHECK_EQ(SERVICE_WORKER_FETCH_EVENT_RESULT_RESPONSE, fetch_result); + + // Set up a request for reading the blob. + if (!response.blob_uuid.empty() && blob_storage_context_) { + scoped_ptr<webkit_blob::BlobDataHandle> blob_data_handle = + blob_storage_context_->GetBlobDataFromUUID(response.blob_uuid); + if (!blob_data_handle) { + // The renderer gave us a bad blob UUID. + DeliverErrorResponse(); + return; + } + blob_request_ = webkit_blob::BlobProtocolHandler::CreateBlobRequest( + blob_data_handle.Pass(), request()->context(), this); + blob_request_->Start(); + } + + CreateResponseHeader( + response.status_code, response.status_text, response.headers); + if (!blob_request_) + CommitResponseHeader(); +} + +void ServiceWorkerURLRequestJob::CreateResponseHeader( + int status_code, + const std::string& status_text, + const std::map<std::string, std::string>& headers) { + // TODO(kinuko): If the response has an identifier to on-disk cache entry, + // pull response header from the disk. + std::string status_line( + base::StringPrintf("HTTP/1.1 %d %s", status_code, status_text.c_str())); + status_line.push_back('\0'); + http_response_headers_ = new net::HttpResponseHeaders(status_line); + for (std::map<std::string, std::string>::const_iterator it = headers.begin(); + it != headers.end(); + ++it) { + std::string header; + header.reserve(it->first.size() + 2 + it->second.size()); + header.append(it->first); + header.append(": "); + header.append(it->second); + http_response_headers_->AddHeader(header); + } +} + +void ServiceWorkerURLRequestJob::CommitResponseHeader() { + http_response_info_.reset(new net::HttpResponseInfo()); + http_response_info_->headers.swap(http_response_headers_); + NotifyHeadersComplete(); +} + +void ServiceWorkerURLRequestJob::DeliverErrorResponse() { + // TODO(falken): Print an error to the console of the ServiceWorker and of + // the requesting page. + CreateResponseHeader(500, + "Service Worker Response Error", + std::map<std::string, std::string>()); + CommitResponseHeader(); +} + +} // namespace content diff --git a/chromium/content/browser/service_worker/service_worker_url_request_job.h b/chromium/content/browser/service_worker/service_worker_url_request_job.h new file mode 100644 index 00000000000..498058cffb4 --- /dev/null +++ b/chromium/content/browser/service_worker/service_worker_url_request_job.h @@ -0,0 +1,137 @@ +// Copyright 2014 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 CONTENT_BROWSER_SERVICE_WORKER_SERVICE_WORKER_URL_REQUEST_JOB_H_ +#define CONTENT_BROWSER_SERVICE_WORKER_SERVICE_WORKER_URL_REQUEST_JOB_H_ + +#include "base/memory/weak_ptr.h" +#include "content/common/content_export.h" +#include "content/common/service_worker/service_worker_status_code.h" +#include "content/common/service_worker/service_worker_types.h" +#include "net/http/http_byte_range.h" +#include "net/url_request/url_request.h" +#include "net/url_request/url_request_job.h" + +namespace webkit_blob { +class BlobStorageContext; +} + +namespace content { + +class ServiceWorkerContextCore; +class ServiceWorkerFetchDispatcher; +class ServiceWorkerProviderHost; + +class CONTENT_EXPORT ServiceWorkerURLRequestJob + : public net::URLRequestJob, + public net::URLRequest::Delegate { + public: + ServiceWorkerURLRequestJob( + net::URLRequest* request, + net::NetworkDelegate* network_delegate, + base::WeakPtr<ServiceWorkerProviderHost> provider_host, + base::WeakPtr<webkit_blob::BlobStorageContext> blob_storage_context); + + // Sets the response type. + void FallbackToNetwork(); + void ForwardToServiceWorker(); + + bool ShouldFallbackToNetwork() const { + return response_type_ == FALLBACK_TO_NETWORK; + } + bool ShouldForwardToServiceWorker() const { + return response_type_ == FORWARD_TO_SERVICE_WORKER; + } + + // net::URLRequestJob overrides: + virtual void Start() OVERRIDE; + virtual void Kill() OVERRIDE; + virtual net::LoadState GetLoadState() const OVERRIDE; + virtual bool GetCharset(std::string* charset) OVERRIDE; + virtual bool GetMimeType(std::string* mime_type) const OVERRIDE; + virtual void GetResponseInfo(net::HttpResponseInfo* info) OVERRIDE; + virtual int GetResponseCode() const OVERRIDE; + virtual void SetExtraRequestHeaders( + const net::HttpRequestHeaders& headers) OVERRIDE; + virtual bool ReadRawData(net::IOBuffer* buf, + int buf_size, + int *bytes_read) OVERRIDE; + + // net::URLRequest::Delegate overrides that read the blob from the + // ServiceWorkerFetchResponse. + virtual void OnReceivedRedirect(net::URLRequest* request, + const GURL& new_url, + bool* defer_redirect) OVERRIDE; + virtual void OnAuthRequired(net::URLRequest* request, + net::AuthChallengeInfo* auth_info) OVERRIDE; + virtual void OnCertificateRequested( + net::URLRequest* request, + net::SSLCertRequestInfo* cert_request_info) OVERRIDE; + virtual void OnSSLCertificateError(net::URLRequest* request, + const net::SSLInfo& ssl_info, + bool fatal) OVERRIDE; + virtual void OnBeforeNetworkStart(net::URLRequest* request, + bool* defer) OVERRIDE; + virtual void OnResponseStarted(net::URLRequest* request) OVERRIDE; + virtual void OnReadCompleted(net::URLRequest* request, + int bytes_read) OVERRIDE; + + const net::HttpResponseInfo* http_info() const; + + protected: + virtual ~ServiceWorkerURLRequestJob(); + + private: + enum ResponseType { + NOT_DETERMINED, + FALLBACK_TO_NETWORK, + FORWARD_TO_SERVICE_WORKER, + }; + + // We start processing the request if Start() is called AND response_type_ + // is determined. + void MaybeStartRequest(); + void StartRequest(); + + // For FORWARD_TO_SERVICE_WORKER case. + void DidDispatchFetchEvent(ServiceWorkerStatusCode status, + ServiceWorkerFetchEventResult fetch_result, + const ServiceWorkerResponse& response); + + // Populates |http_response_headers_|. + void CreateResponseHeader(int status_code, + const std::string& status_text, + const std::map<std::string, std::string>& headers); + + // Creates |http_response_info_| using |http_response_headers_| and calls + // NotifyHeadersComplete. + void CommitResponseHeader(); + + // Creates and commits a response header indicating error. + void DeliverErrorResponse(); + + base::WeakPtr<ServiceWorkerProviderHost> provider_host_; + + ResponseType response_type_; + bool is_started_; + + net::HttpByteRange byte_range_; + scoped_ptr<net::HttpResponseInfo> range_response_info_; + scoped_ptr<net::HttpResponseInfo> http_response_info_; + // Headers that have not yet been committed to |http_response_info_|. + scoped_refptr<net::HttpResponseHeaders> http_response_headers_; + + // Used when response type is FORWARD_TO_SERVICE_WORKER. + scoped_ptr<ServiceWorkerFetchDispatcher> fetch_dispatcher_; + base::WeakPtr<webkit_blob::BlobStorageContext> blob_storage_context_; + scoped_ptr<net::URLRequest> blob_request_; + + base::WeakPtrFactory<ServiceWorkerURLRequestJob> weak_factory_; + + DISALLOW_COPY_AND_ASSIGN(ServiceWorkerURLRequestJob); +}; + +} // namespace content + +#endif // CONTENT_BROWSER_SERVICE_WORKER_SERVICE_WORKER_URL_REQUEST_JOB_H_ diff --git a/chromium/content/browser/service_worker/service_worker_url_request_job_unittest.cc b/chromium/content/browser/service_worker/service_worker_url_request_job_unittest.cc new file mode 100644 index 00000000000..54a2cc966fc --- /dev/null +++ b/chromium/content/browser/service_worker/service_worker_url_request_job_unittest.cc @@ -0,0 +1,238 @@ +// Copyright 2014 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 "base/basictypes.h" +#include "base/callback.h" +#include "base/memory/scoped_ptr.h" +#include "base/message_loop/message_loop.h" +#include "base/run_loop.h" +#include "content/browser/fileapi/chrome_blob_storage_context.h" +#include "content/browser/fileapi/mock_url_request_delegate.h" +#include "content/browser/service_worker/embedded_worker_registry.h" +#include "content/browser/service_worker/embedded_worker_test_helper.h" +#include "content/browser/service_worker/service_worker_context_core.h" +#include "content/browser/service_worker/service_worker_provider_host.h" +#include "content/browser/service_worker/service_worker_registration.h" +#include "content/browser/service_worker/service_worker_test_utils.h" +#include "content/browser/service_worker/service_worker_url_request_job.h" +#include "content/browser/service_worker/service_worker_version.h" +#include "content/common/service_worker/service_worker_messages.h" +#include "content/public/browser/blob_handle.h" +#include "content/public/test/test_browser_context.h" +#include "content/public/test/test_browser_thread_bundle.h" +#include "net/base/io_buffer.h" +#include "net/http/http_request_headers.h" +#include "net/http/http_response_headers.h" +#include "net/url_request/url_request.h" +#include "net/url_request/url_request_context.h" +#include "net/url_request/url_request_job_factory_impl.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "webkit/browser/blob/blob_storage_context.h" +#include "webkit/browser/blob/blob_url_request_job.h" +#include "webkit/browser/blob/blob_url_request_job_factory.h" +#include "webkit/common/blob/blob_data.h" + +namespace content { + +class ServiceWorkerURLRequestJobTest; + +namespace { + +const int kProcessID = 1; +const int kProviderID = 100; +const char kTestData[] = "Here is sample text for the blob."; + +class MockHttpProtocolHandler + : public net::URLRequestJobFactory::ProtocolHandler { + public: + MockHttpProtocolHandler( + base::WeakPtr<ServiceWorkerProviderHost> provider_host, + base::WeakPtr<webkit_blob::BlobStorageContext> blob_storage_context) + : provider_host_(provider_host), + blob_storage_context_(blob_storage_context) {} + virtual ~MockHttpProtocolHandler() {} + + virtual net::URLRequestJob* MaybeCreateJob( + net::URLRequest* request, + net::NetworkDelegate* network_delegate) const OVERRIDE { + ServiceWorkerURLRequestJob* job = new ServiceWorkerURLRequestJob( + request, network_delegate, provider_host_, blob_storage_context_); + job->ForwardToServiceWorker(); + return job; + } + + private: + base::WeakPtr<ServiceWorkerProviderHost> provider_host_; + base::WeakPtr<webkit_blob::BlobStorageContext> blob_storage_context_; +}; + +// Returns a BlobProtocolHandler that uses |blob_storage_context|. Caller owns +// the memory. +webkit_blob::BlobProtocolHandler* CreateMockBlobProtocolHandler( + webkit_blob::BlobStorageContext* blob_storage_context) { + // The FileSystemContext and MessageLoopProxy are not actually used but a + // MessageLoopProxy is needed to avoid a DCHECK in BlobURLRequestJob ctor. + return new webkit_blob::BlobProtocolHandler( + blob_storage_context, NULL, base::MessageLoopProxy::current().get()); +} + +} // namespace + +class ServiceWorkerURLRequestJobTest : public testing::Test { + protected: + ServiceWorkerURLRequestJobTest() + : thread_bundle_(TestBrowserThreadBundle::IO_MAINLOOP), + blob_data_(new webkit_blob::BlobData("blob-id:myblob")) {} + virtual ~ServiceWorkerURLRequestJobTest() {} + + virtual void SetUp() OVERRIDE { + browser_context_.reset(new TestBrowserContext); + SetUpWithHelper(new EmbeddedWorkerTestHelper(kProcessID)); + } + + void SetUpWithHelper(EmbeddedWorkerTestHelper* helper) { + helper_.reset(helper); + + registration_ = new ServiceWorkerRegistration( + GURL("http://example.com/*"), + GURL("http://example.com/service_worker.js"), + 1L, + helper_->context()->AsWeakPtr()); + version_ = new ServiceWorkerVersion( + registration_, 1L, helper_->context()->AsWeakPtr()); + + scoped_ptr<ServiceWorkerProviderHost> provider_host( + new ServiceWorkerProviderHost( + kProcessID, kProviderID, helper_->context()->AsWeakPtr(), NULL)); + provider_host->SetActiveVersion(version_.get()); + + ChromeBlobStorageContext* chrome_blob_storage_context = + ChromeBlobStorageContext::GetFor(browser_context_.get()); + // Wait for chrome_blob_storage_context to finish initializing. + base::RunLoop().RunUntilIdle(); + webkit_blob::BlobStorageContext* blob_storage_context = + chrome_blob_storage_context->context(); + + url_request_job_factory_.reset(new net::URLRequestJobFactoryImpl); + url_request_job_factory_->SetProtocolHandler( + "http", + new MockHttpProtocolHandler(provider_host->AsWeakPtr(), + blob_storage_context->AsWeakPtr())); + url_request_job_factory_->SetProtocolHandler( + "blob", CreateMockBlobProtocolHandler(blob_storage_context)); + url_request_context_.set_job_factory(url_request_job_factory_.get()); + + helper_->context()->AddProviderHost(provider_host.Pass()); + } + + virtual void TearDown() OVERRIDE { + version_ = NULL; + registration_ = NULL; + helper_.reset(); + } + + void TestRequest(int expected_status_code, + const std::string& expected_status_text, + const std::string& expected_response) { + request_ = url_request_context_.CreateRequest( + GURL("http://example.com/foo.html"), + net::DEFAULT_PRIORITY, + &url_request_delegate_, + NULL); + + request_->set_method("GET"); + request_->Start(); + base::RunLoop().RunUntilIdle(); + EXPECT_TRUE(request_->status().is_success()); + EXPECT_EQ(expected_status_code, + request_->response_headers()->response_code()); + EXPECT_EQ(expected_status_text, + request_->response_headers()->GetStatusText()); + EXPECT_EQ(expected_response, url_request_delegate_.response_data()); + } + + TestBrowserThreadBundle thread_bundle_; + + scoped_ptr<TestBrowserContext> browser_context_; + scoped_ptr<EmbeddedWorkerTestHelper> helper_; + scoped_refptr<ServiceWorkerRegistration> registration_; + scoped_refptr<ServiceWorkerVersion> version_; + + scoped_ptr<net::URLRequestJobFactoryImpl> url_request_job_factory_; + net::URLRequestContext url_request_context_; + MockURLRequestDelegate url_request_delegate_; + scoped_ptr<net::URLRequest> request_; + + scoped_refptr<webkit_blob::BlobData> blob_data_; + + DISALLOW_COPY_AND_ASSIGN(ServiceWorkerURLRequestJobTest); +}; + +TEST_F(ServiceWorkerURLRequestJobTest, Simple) { + version_->SetStatus(ServiceWorkerVersion::ACTIVE); + TestRequest(200, "OK", std::string()); +} + +TEST_F(ServiceWorkerURLRequestJobTest, WaitForActivation) { + ServiceWorkerStatusCode status = SERVICE_WORKER_ERROR_FAILED; + version_->SetStatus(ServiceWorkerVersion::INSTALLED); + version_->DispatchActivateEvent(CreateReceiverOnCurrentThread(&status)); + + TestRequest(200, "OK", std::string()); + + EXPECT_EQ(SERVICE_WORKER_OK, status); +} + +// Responds to fetch events with a blob. +class BlobResponder : public EmbeddedWorkerTestHelper { + public: + BlobResponder(int mock_render_process_id, const std::string& blob_uuid) + : EmbeddedWorkerTestHelper(mock_render_process_id), + blob_uuid_(blob_uuid) {} + virtual ~BlobResponder() {} + + protected: + virtual void OnFetchEvent(int embedded_worker_id, + int request_id, + const ServiceWorkerFetchRequest& request) OVERRIDE { + SimulateSend(new ServiceWorkerHostMsg_FetchEventFinished( + embedded_worker_id, + request_id, + SERVICE_WORKER_FETCH_EVENT_RESULT_RESPONSE, + ServiceWorkerResponse(200, + "OK", + std::map<std::string, std::string>(), + blob_uuid_))); + } + + std::string blob_uuid_; + DISALLOW_COPY_AND_ASSIGN(BlobResponder); +}; + +TEST_F(ServiceWorkerURLRequestJobTest, BlobResponse) { + ChromeBlobStorageContext* blob_storage_context = + ChromeBlobStorageContext::GetFor(browser_context_.get()); + std::string expected_response; + for (int i = 0; i < 1024; ++i) { + blob_data_->AppendData(kTestData); + expected_response += kTestData; + } + scoped_ptr<webkit_blob::BlobDataHandle> blob_handle = + blob_storage_context->context()->AddFinishedBlob(blob_data_); + SetUpWithHelper(new BlobResponder(kProcessID, blob_handle->uuid())); + + version_->SetStatus(ServiceWorkerVersion::ACTIVE); + TestRequest(200, "OK", expected_response); +} + +TEST_F(ServiceWorkerURLRequestJobTest, NonExistentBlobUUIDResponse) { + SetUpWithHelper(new BlobResponder(kProcessID, "blob-id:nothing-is-here")); + version_->SetStatus(ServiceWorkerVersion::ACTIVE); + TestRequest(500, "Service Worker Response Error", std::string()); +} + +// TODO(kinuko): Add more tests with different response data and also for +// FallbackToNetwork case. + +} // namespace content diff --git a/chromium/content/browser/service_worker/service_worker_utils.cc b/chromium/content/browser/service_worker/service_worker_utils.cc new file mode 100644 index 00000000000..2c3d05b773f --- /dev/null +++ b/chromium/content/browser/service_worker/service_worker_utils.cc @@ -0,0 +1,65 @@ +// Copyright 2014 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 "content/browser/service_worker/service_worker_utils.h" + +#include <string> + +#include "base/command_line.h" +#include "base/logging.h" +#include "content/public/common/content_switches.h" +#include "url/gurl.h" + +namespace content { + +// static +bool ServiceWorkerUtils::IsFeatureEnabled() { + static bool enabled = CommandLine::ForCurrentProcess()->HasSwitch( + switches::kEnableServiceWorker); + return enabled; +} + +// static +bool ServiceWorkerUtils::ScopeMatches(const GURL& scope, const GURL& url) { + DCHECK(!scope.has_ref()); + DCHECK(!url.has_ref()); + const std::string& scope_spec = scope.spec(); + const std::string& url_spec = url.spec(); + + size_t len = scope_spec.size(); + if (len > 0 && scope_spec[len - 1] == '*') + return scope_spec.compare(0, len - 1, url_spec, 0, len - 1) == 0; + return scope_spec == url_spec; +} + +bool LongestScopeMatcher::MatchLongest(const GURL& scope) { + if (!ServiceWorkerUtils::ScopeMatches(scope, url_)) + return false; + if (match_.is_empty()) { + match_ = scope; + return true; + } + + const std::string match_spec = match_.spec(); + const std::string scope_spec = scope.spec(); + if (match_spec.size() < scope_spec.size()) { + match_ = scope; + return true; + } + + // If |scope| has the same length with |match_|, they are compared as strings. + // For example: + // 1) for a document "/foo", "/foo" is prioritized over "/fo*". + // 2) for a document "/f(1)", "/f(1*" is prioritized over "/f(1)". + // TODO(nhiroki): This isn't in the spec. + // (https://github.com/slightlyoff/ServiceWorker/issues/287) + if (match_spec.size() == scope_spec.size() && match_spec < scope_spec) { + match_ = scope; + return true; + } + + return false; +} + +} // namespace content diff --git a/chromium/content/browser/service_worker/service_worker_utils.h b/chromium/content/browser/service_worker/service_worker_utils.h new file mode 100644 index 00000000000..25700dee2bd --- /dev/null +++ b/chromium/content/browser/service_worker/service_worker_utils.h @@ -0,0 +1,55 @@ +// Copyright 2014 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 CONTENT_BROWSER_SERVICE_WORKER_SERVICE_WORKER_UTILS_H_ +#define CONTENT_BROWSER_SERVICE_WORKER_SERVICE_WORKER_UTILS_H_ + +#include "content/common/content_export.h" +#include "content/common/service_worker/service_worker_status_code.h" +#include "webkit/common/resource_type.h" + +class GURL; + +namespace content { + +class ServiceWorkerUtils { + public: + static bool IsMainResourceType(ResourceType::Type type) { + return ResourceType::IsFrame(type) || + ResourceType::IsSharedWorker(type); + } + + static bool IsServiceWorkerResourceType(ResourceType::Type type) { + return ResourceType::IsServiceWorker(type); + } + + // Returns true if the feature is enabled (or not disabled) by command-line + // flag. + static bool IsFeatureEnabled(); + + // A helper for creating a do-nothing status callback. + static void NoOpStatusCallback(ServiceWorkerStatusCode status) {} + + // Returns true if |scope| matches |url|. + CONTENT_EXPORT static bool ScopeMatches(const GURL& scope, const GURL& url); +}; + +class CONTENT_EXPORT LongestScopeMatcher { + public: + explicit LongestScopeMatcher(const GURL& url) : url_(url) {} + virtual ~LongestScopeMatcher() {} + + // Returns true if |scope| matches |url_| longer than |match_|. + bool MatchLongest(const GURL& scope); + + private: + const GURL url_; + GURL match_; + + DISALLOW_COPY_AND_ASSIGN(LongestScopeMatcher); +}; + +} // namespace content + +#endif // CONTENT_BROWSER_SERVICE_WORKER_SERVICE_WORKER_UTILS_H_ diff --git a/chromium/content/browser/service_worker/service_worker_utils_unittest.cc b/chromium/content/browser/service_worker/service_worker_utils_unittest.cc new file mode 100644 index 00000000000..67a74801d6c --- /dev/null +++ b/chromium/content/browser/service_worker/service_worker_utils_unittest.cc @@ -0,0 +1,103 @@ +// Copyright 2014 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 "content/browser/service_worker/service_worker_utils.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace content { + +TEST(ServiceWorkerUtilsTest, ScopeMatches) { + ASSERT_TRUE(ServiceWorkerUtils::ScopeMatches( + GURL("http://www.example.com/*"), GURL("http://www.example.com/"))); + ASSERT_TRUE(ServiceWorkerUtils::ScopeMatches( + GURL("http://www.example.com/*"), + GURL("http://www.example.com/page.html"))); + + ASSERT_FALSE(ServiceWorkerUtils::ScopeMatches( + GURL("http://www.example.com/*"), GURL("https://www.example.com/"))); + ASSERT_FALSE(ServiceWorkerUtils::ScopeMatches( + GURL("http://www.example.com/*"), + GURL("https://www.example.com/page.html"))); + + ASSERT_FALSE(ServiceWorkerUtils::ScopeMatches( + GURL("http://www.example.com/*"), GURL("http://www.foo.com/"))); + ASSERT_FALSE(ServiceWorkerUtils::ScopeMatches( + GURL("http://www.example.com/*"), GURL("https://www.foo.com/page.html"))); + + ASSERT_TRUE(ServiceWorkerUtils::ScopeMatches( + GURL("http://www.example.com/"), GURL("http://www.example.com/"))); + ASSERT_FALSE(ServiceWorkerUtils::ScopeMatches( + GURL("http://www.example.com/"), GURL("http://www.example.com/x"))); + + // '?' is not a wildcard. + ASSERT_FALSE(ServiceWorkerUtils::ScopeMatches( + GURL("http://www.example.com/?"), GURL("http://www.example.com/x"))); + ASSERT_FALSE(ServiceWorkerUtils::ScopeMatches( + GURL("http://www.example.com/?"), GURL("http://www.example.com/"))); + ASSERT_FALSE(ServiceWorkerUtils::ScopeMatches( + GURL("http://www.example.com/?"), GURL("http://www.example.com/xx"))); + ASSERT_TRUE(ServiceWorkerUtils::ScopeMatches( + GURL("http://www.example.com/?"), GURL("http://www.example.com/?"))); + + // Query string is part of the resource. + ASSERT_TRUE( + ServiceWorkerUtils::ScopeMatches(GURL("http://www.example.com/?a=b"), + GURL("http://www.example.com/?a=b"))); + ASSERT_TRUE( + ServiceWorkerUtils::ScopeMatches(GURL("http://www.example.com/?a=*"), + GURL("http://www.example.com/?a=b"))); + ASSERT_TRUE(ServiceWorkerUtils::ScopeMatches( + GURL("http://www.example.com/*"), GURL("http://www.example.com/?a=b"))); + ASSERT_FALSE(ServiceWorkerUtils::ScopeMatches( + GURL("http://www.example.com/"), GURL("http://www.example.com/?a=b"))); + + // '*' only has special meaning in terminal position. + ASSERT_TRUE(ServiceWorkerUtils::ScopeMatches( + GURL("http://www.example.com/*/x"), GURL("http://www.example.com/*/x"))); + ASSERT_FALSE(ServiceWorkerUtils::ScopeMatches( + GURL("http://www.example.com/*/x"), GURL("http://www.example.com/a/x"))); + ASSERT_FALSE( + ServiceWorkerUtils::ScopeMatches(GURL("http://www.example.com/*/x/*"), + GURL("http://www.example.com/a/x/b"))); + ASSERT_TRUE( + ServiceWorkerUtils::ScopeMatches(GURL("http://www.example.com/*/x/*"), + GURL("http://www.example.com/*/x/b"))); + + // URLs canonicalize \ to / so this is equivalent to "...//*" and "...//x" + ASSERT_TRUE(ServiceWorkerUtils::ScopeMatches( + GURL("http://www.example.com/\\*"), GURL("http://www.example.com/\\x"))); +} + +TEST(ServiceWorkerUtilsTest, FindLongestScopeMatch_Basic) { + LongestScopeMatcher matcher(GURL("http://www.example.com/xxx")); + + // "/xx*" should be matched longest. + ASSERT_TRUE(matcher.MatchLongest(GURL("http://www.example.com/x*"))); + ASSERT_FALSE(matcher.MatchLongest(GURL("http://www.example.com/*"))); + ASSERT_TRUE(matcher.MatchLongest(GURL("http://www.example.com/xx*"))); + + // "xxx*" should be matched longer than "/xx*". + ASSERT_TRUE(matcher.MatchLongest(GURL("http://www.example.com/xxx*"))); + + ASSERT_FALSE(matcher.MatchLongest(GURL("http://www.example.com/xxxx*"))); +} + +TEST(ServiceWorkerUtilsTest, FindLongestScopeMatch_SameLength) { + LongestScopeMatcher matcher1(GURL("http://www.example.com/xxx")); + + // "/xxx" has the same length with "/xx*", so they are compared as strings + // and "/xxx" should win. + // TODO(nhiroki): This isn't in the spec (see: service_worker_utils.cc) + ASSERT_TRUE(matcher1.MatchLongest(GURL("http://www.example.com/xxx"))); + ASSERT_FALSE(matcher1.MatchLongest(GURL("http://www.example.com/xx*"))); + + LongestScopeMatcher matcher2(GURL("http://www.example.com/x(1)")); + + // "/xx*" should be prioritized over "/x(1)". + // TODO(nhiroki): This isn't in the spec (see: service_worker_utils.cc) + ASSERT_TRUE(matcher2.MatchLongest(GURL("http://www.example.com/x(1)"))); + ASSERT_TRUE(matcher2.MatchLongest(GURL("http://www.example.com/x(1*"))); +} + +} // namespace content diff --git a/chromium/content/browser/service_worker/service_worker_version.cc b/chromium/content/browser/service_worker/service_worker_version.cc index a91ac6e9aa9..50f24df48b6 100644 --- a/chromium/content/browser/service_worker/service_worker_version.cc +++ b/chromium/content/browser/service_worker/service_worker_version.cc @@ -4,53 +4,634 @@ #include "content/browser/service_worker/service_worker_version.h" +#include "base/command_line.h" #include "base/stl_util.h" +#include "base/strings/string16.h" #include "content/browser/service_worker/embedded_worker_instance.h" #include "content/browser/service_worker/embedded_worker_registry.h" #include "content/browser/service_worker/service_worker_context_core.h" #include "content/browser/service_worker/service_worker_registration.h" +#include "content/browser/service_worker/service_worker_utils.h" +#include "content/common/service_worker/service_worker_messages.h" +#include "content/public/browser/browser_thread.h" +#include "content/public/common/content_switches.h" namespace content { +typedef ServiceWorkerVersion::StatusCallback StatusCallback; +typedef ServiceWorkerVersion::MessageCallback MessageCallback; + +namespace { + +// Default delay to stop the worker context after all documents that +// are associated to the worker are closed. +// (Note that if all references to the version is dropped the worker +// is also stopped without delay) +const int64 kStopWorkerDelay = 30; // 30 secs. + +void RunSoon(const base::Closure& callback) { + if (!callback.is_null()) + base::MessageLoop::current()->PostTask(FROM_HERE, callback); +} + +template <typename CallbackArray, typename Arg> +void RunCallbacks(ServiceWorkerVersion* version, + CallbackArray* callbacks_ptr, + const Arg& arg) { + CallbackArray callbacks; + callbacks.swap(*callbacks_ptr); + scoped_refptr<ServiceWorkerVersion> protect(version); + for (typename CallbackArray::const_iterator i = callbacks.begin(); + i != callbacks.end(); ++i) + (*i).Run(arg); +} + +template <typename IDMAP, typename Method, typename Params> +void RunIDMapCallbacks(IDMAP* callbacks, Method method, const Params& params) { + typename IDMAP::iterator iter(callbacks); + while (!iter.IsAtEnd()) { + DispatchToMethod(iter.GetCurrentValue(), method, params); + iter.Advance(); + } + callbacks->Clear(); +} + +// A callback adapter to start a |task| after StartWorker. +void RunTaskAfterStartWorker( + base::WeakPtr<ServiceWorkerVersion> version, + const StatusCallback& error_callback, + const base::Closure& task, + ServiceWorkerStatusCode status) { + if (status != SERVICE_WORKER_OK) { + if (!error_callback.is_null()) + error_callback.Run(status); + return; + } + if (version->running_status() != ServiceWorkerVersion::RUNNING) { + // We've tried to start the worker (and it has succeeded), but + // it looks it's not running yet. + NOTREACHED() << "The worker's not running after successful StartWorker"; + if (!error_callback.is_null()) + error_callback.Run(SERVICE_WORKER_ERROR_START_WORKER_FAILED); + return; + } + task.Run(); +} + +void RunErrorFetchCallback(const ServiceWorkerVersion::FetchCallback& callback, + ServiceWorkerStatusCode status) { + callback.Run(status, + SERVICE_WORKER_FETCH_EVENT_RESULT_FALLBACK, + ServiceWorkerResponse()); +} + +} // namespace + ServiceWorkerVersion::ServiceWorkerVersion( ServiceWorkerRegistration* registration, - EmbeddedWorkerRegistry* worker_registry, - int64 version_id) + int64 version_id, + base::WeakPtr<ServiceWorkerContextCore> context) : version_id_(version_id), - is_shutdown_(false), - registration_(registration) { - if (worker_registry) - embedded_worker_ = worker_registry->CreateWorker(); + registration_id_(kInvalidServiceWorkerVersionId), + status_(NEW), + context_(context), + script_cache_map_(this, context), + weak_factory_(this) { + DCHECK(context_); + DCHECK(registration); + if (registration) { + registration_id_ = registration->id(); + script_url_ = registration->script_url(); + scope_ = registration->pattern(); + } + context_->AddLiveVersion(this); + embedded_worker_ = context_->embedded_worker_registry()->CreateWorker(); + embedded_worker_->AddListener(this); +} + +ServiceWorkerVersion::~ServiceWorkerVersion() { + embedded_worker_->RemoveListener(this); + if (context_) + context_->RemoveLiveVersion(version_id_); + // EmbeddedWorker's dtor sends StopWorker if it's still running. +} + +void ServiceWorkerVersion::SetStatus(Status status) { + if (status_ == status) + return; + + status_ = status; + + std::vector<base::Closure> callbacks; + callbacks.swap(status_change_callbacks_); + for (std::vector<base::Closure>::const_iterator i = callbacks.begin(); + i != callbacks.end(); ++i) { + (*i).Run(); + } + + FOR_EACH_OBSERVER(Listener, listeners_, OnVersionStateChanged(this)); +} + +void ServiceWorkerVersion::RegisterStatusChangeCallback( + const base::Closure& callback) { + status_change_callbacks_.push_back(callback); +} + +ServiceWorkerVersionInfo ServiceWorkerVersion::GetInfo() { + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); + return ServiceWorkerVersionInfo( + running_status(), + status(), + version_id(), + embedded_worker()->process_id(), + embedded_worker()->thread_id(), + embedded_worker()->worker_devtools_agent_route_id()); +} + +void ServiceWorkerVersion::StartWorker(const StatusCallback& callback) { + StartWorkerWithCandidateProcesses(std::vector<int>(), callback); +} + +void ServiceWorkerVersion::StartWorkerWithCandidateProcesses( + const std::vector<int>& possible_process_ids, + const StatusCallback& callback) { + switch (running_status()) { + case RUNNING: + RunSoon(base::Bind(callback, SERVICE_WORKER_OK)); + return; + case STOPPING: + RunSoon(base::Bind(callback, SERVICE_WORKER_ERROR_START_WORKER_FAILED)); + return; + case STOPPED: + case STARTING: + start_callbacks_.push_back(callback); + if (running_status() == STOPPED) { + embedded_worker_->Start( + version_id_, + scope_, + script_url_, + possible_process_ids, + base::Bind(&ServiceWorkerVersion::RunStartWorkerCallbacksOnError, + weak_factory_.GetWeakPtr())); + } + return; + } +} + +void ServiceWorkerVersion::StopWorker(const StatusCallback& callback) { + if (running_status() == STOPPED) { + RunSoon(base::Bind(callback, SERVICE_WORKER_OK)); + return; + } + if (stop_callbacks_.empty()) { + ServiceWorkerStatusCode status = embedded_worker_->Stop(); + if (status != SERVICE_WORKER_OK) { + RunSoon(base::Bind(callback, status)); + return; + } + } + stop_callbacks_.push_back(callback); +} + +void ServiceWorkerVersion::SendMessage( + const IPC::Message& message, const StatusCallback& callback) { + if (running_status() != RUNNING) { + // Schedule calling this method after starting the worker. + StartWorker(base::Bind(&RunTaskAfterStartWorker, + weak_factory_.GetWeakPtr(), callback, + base::Bind(&self::SendMessage, + weak_factory_.GetWeakPtr(), + message, callback))); + return; + } + + ServiceWorkerStatusCode status = embedded_worker_->SendMessage(message); + RunSoon(base::Bind(callback, status)); +} + +void ServiceWorkerVersion::DispatchInstallEvent( + int active_version_id, + const StatusCallback& callback) { + DCHECK_EQ(NEW, status()) << status(); + SetStatus(INSTALLING); + + if (running_status() != RUNNING) { + // Schedule calling this method after starting the worker. + StartWorker( + base::Bind(&RunTaskAfterStartWorker, + weak_factory_.GetWeakPtr(), + callback, + base::Bind(&self::DispatchInstallEventAfterStartWorker, + weak_factory_.GetWeakPtr(), + active_version_id, + callback))); + } else { + DispatchInstallEventAfterStartWorker(active_version_id, callback); + } +} + +void ServiceWorkerVersion::DispatchActivateEvent( + const StatusCallback& callback) { + DCHECK_EQ(INSTALLED, status()) << status(); + SetStatus(ACTIVATING); + + if (running_status() != RUNNING) { + // Schedule calling this method after starting the worker. + StartWorker( + base::Bind(&RunTaskAfterStartWorker, + weak_factory_.GetWeakPtr(), + callback, + base::Bind(&self::DispatchActivateEventAfterStartWorker, + weak_factory_.GetWeakPtr(), + callback))); + } else { + DispatchActivateEventAfterStartWorker(callback); + } +} + +void ServiceWorkerVersion::DispatchFetchEvent( + const ServiceWorkerFetchRequest& request, + const FetchCallback& callback) { + DCHECK_EQ(ACTIVE, status()) << status(); + + if (running_status() != RUNNING) { + // Schedule calling this method after starting the worker. + StartWorker(base::Bind(&RunTaskAfterStartWorker, + weak_factory_.GetWeakPtr(), + base::Bind(&RunErrorFetchCallback, callback), + base::Bind(&self::DispatchFetchEvent, + weak_factory_.GetWeakPtr(), + request, callback))); + return; + } + + int request_id = fetch_callbacks_.Add(new FetchCallback(callback)); + ServiceWorkerStatusCode status = embedded_worker_->SendMessage( + ServiceWorkerMsg_FetchEvent(request_id, request)); + if (status != SERVICE_WORKER_OK) { + fetch_callbacks_.Remove(request_id); + RunSoon(base::Bind(&RunErrorFetchCallback, + callback, + SERVICE_WORKER_ERROR_FAILED)); + } +} + +void ServiceWorkerVersion::DispatchSyncEvent(const StatusCallback& callback) { + DCHECK_EQ(ACTIVE, status()) << status(); + + if (!CommandLine::ForCurrentProcess()->HasSwitch( + switches::kEnableServiceWorkerSync)) { + callback.Run(SERVICE_WORKER_ERROR_ABORT); + return; + } + + if (running_status() != RUNNING) { + // Schedule calling this method after starting the worker. + StartWorker(base::Bind(&RunTaskAfterStartWorker, + weak_factory_.GetWeakPtr(), callback, + base::Bind(&self::DispatchSyncEvent, + weak_factory_.GetWeakPtr(), + callback))); + return; + } + + int request_id = sync_callbacks_.Add(new StatusCallback(callback)); + ServiceWorkerStatusCode status = embedded_worker_->SendMessage( + ServiceWorkerMsg_SyncEvent(request_id)); + if (status != SERVICE_WORKER_OK) { + sync_callbacks_.Remove(request_id); + RunSoon(base::Bind(callback, status)); + } +} + +void ServiceWorkerVersion::DispatchPushEvent(const StatusCallback& callback, + const std::string& data) { + DCHECK_EQ(ACTIVE, status()) << status(); + + if (!CommandLine::ForCurrentProcess()->HasSwitch( + switches::kEnableExperimentalWebPlatformFeatures)) { + callback.Run(SERVICE_WORKER_ERROR_ABORT); + return; + } + + if (running_status() != RUNNING) { + // Schedule calling this method after starting the worker. + StartWorker(base::Bind(&RunTaskAfterStartWorker, + weak_factory_.GetWeakPtr(), callback, + base::Bind(&self::DispatchPushEvent, + weak_factory_.GetWeakPtr(), + callback, data))); + return; + } + + int request_id = push_callbacks_.Add(new StatusCallback(callback)); + ServiceWorkerStatusCode status = embedded_worker_->SendMessage( + ServiceWorkerMsg_PushEvent(request_id, data)); + if (status != SERVICE_WORKER_OK) { + push_callbacks_.Remove(request_id); + RunSoon(base::Bind(callback, status)); + } } -ServiceWorkerVersion::~ServiceWorkerVersion() { DCHECK(is_shutdown_); } +void ServiceWorkerVersion::AddProcessToWorker(int process_id) { + embedded_worker_->AddProcessReference(process_id); +} -void ServiceWorkerVersion::Shutdown() { - is_shutdown_ = true; - registration_ = NULL; - embedded_worker_.reset(); +void ServiceWorkerVersion::RemoveProcessFromWorker(int process_id) { + embedded_worker_->ReleaseProcessReference(process_id); } -void ServiceWorkerVersion::StartWorker() { - DCHECK(!is_shutdown_); - DCHECK(registration_); - embedded_worker_->Start(version_id_, registration_->script_url()); +bool ServiceWorkerVersion::HasProcessToRun() const { + return embedded_worker_->HasProcessToRun(); } -void ServiceWorkerVersion::StopWorker() { - DCHECK(!is_shutdown_); - embedded_worker_->Stop(); +void ServiceWorkerVersion::AddControllee( + ServiceWorkerProviderHost* provider_host) { + DCHECK(!ContainsKey(controllee_map_, provider_host)); + int controllee_id = controllee_by_id_.Add(provider_host); + controllee_map_[provider_host] = controllee_id; + AddProcessToWorker(provider_host->process_id()); + if (stop_worker_timer_.IsRunning()) + stop_worker_timer_.Stop(); +} + +void ServiceWorkerVersion::RemoveControllee( + ServiceWorkerProviderHost* provider_host) { + ControlleeMap::iterator found = controllee_map_.find(provider_host); + DCHECK(found != controllee_map_.end()); + controllee_by_id_.Remove(found->second); + controllee_map_.erase(found); + RemoveProcessFromWorker(provider_host->process_id()); + if (!HasControllee()) + ScheduleStopWorker(); + // TODO(kinuko): Fire NoControllees notification when the # of controllees + // reaches 0, so that a new pending version can be activated (which will + // deactivate this version). + // TODO(michaeln): On no controllees call storage DeleteVersionResources + // if this version has been deactivated. Probably storage can listen for + // NoControllees for versions that have been deleted. } -void ServiceWorkerVersion::OnAssociateProvider( +void ServiceWorkerVersion::AddWaitingControllee( ServiceWorkerProviderHost* provider_host) { - DCHECK(!is_shutdown_); - embedded_worker_->AddProcessReference(provider_host->process_id()); + AddProcessToWorker(provider_host->process_id()); } -void ServiceWorkerVersion::OnUnassociateProvider( +void ServiceWorkerVersion::RemoveWaitingControllee( ServiceWorkerProviderHost* provider_host) { - embedded_worker_->ReleaseProcessReference(provider_host->process_id()); + RemoveProcessFromWorker(provider_host->process_id()); +} + +void ServiceWorkerVersion::AddListener(Listener* listener) { + listeners_.AddObserver(listener); +} + +void ServiceWorkerVersion::RemoveListener(Listener* listener) { + listeners_.RemoveObserver(listener); +} + +void ServiceWorkerVersion::OnStarted() { + DCHECK_EQ(RUNNING, running_status()); + // Fire all start callbacks. + RunCallbacks(this, &start_callbacks_, SERVICE_WORKER_OK); + FOR_EACH_OBSERVER(Listener, listeners_, OnWorkerStarted(this)); +} + +void ServiceWorkerVersion::OnStopped() { + DCHECK_EQ(STOPPED, running_status()); + scoped_refptr<ServiceWorkerVersion> protect(this); + + // Fire all stop callbacks. + RunCallbacks(this, &stop_callbacks_, SERVICE_WORKER_OK); + + // Let all start callbacks fail. + RunCallbacks( + this, &start_callbacks_, SERVICE_WORKER_ERROR_START_WORKER_FAILED); + + // Let all message callbacks fail (this will also fire and clear all + // callbacks for events). + // TODO(kinuko): Consider if we want to add queue+resend mechanism here. + RunIDMapCallbacks(&activate_callbacks_, + &StatusCallback::Run, + MakeTuple(SERVICE_WORKER_ERROR_ACTIVATE_WORKER_FAILED)); + RunIDMapCallbacks(&install_callbacks_, + &StatusCallback::Run, + MakeTuple(SERVICE_WORKER_ERROR_INSTALL_WORKER_FAILED)); + RunIDMapCallbacks(&fetch_callbacks_, + &FetchCallback::Run, + MakeTuple(SERVICE_WORKER_ERROR_FAILED, + SERVICE_WORKER_FETCH_EVENT_RESULT_FALLBACK, + ServiceWorkerResponse())); + RunIDMapCallbacks(&sync_callbacks_, + &StatusCallback::Run, + MakeTuple(SERVICE_WORKER_ERROR_FAILED)); + RunIDMapCallbacks(&push_callbacks_, + &StatusCallback::Run, + MakeTuple(SERVICE_WORKER_ERROR_FAILED)); + + FOR_EACH_OBSERVER(Listener, listeners_, OnWorkerStopped(this)); +} + +void ServiceWorkerVersion::OnReportException( + const base::string16& error_message, + int line_number, + int column_number, + const GURL& source_url) { + FOR_EACH_OBSERVER( + Listener, + listeners_, + OnErrorReported( + this, error_message, line_number, column_number, source_url)); +} + +void ServiceWorkerVersion::OnReportConsoleMessage(int source_identifier, + int message_level, + const base::string16& message, + int line_number, + const GURL& source_url) { + FOR_EACH_OBSERVER(Listener, + listeners_, + OnReportConsoleMessage(this, + source_identifier, + message_level, + message, + line_number, + source_url)); +} + +bool ServiceWorkerVersion::OnMessageReceived(const IPC::Message& message) { + bool handled = true; + IPC_BEGIN_MESSAGE_MAP(ServiceWorkerVersion, message) + IPC_MESSAGE_HANDLER(ServiceWorkerHostMsg_GetClientDocuments, + OnGetClientDocuments) + IPC_MESSAGE_HANDLER(ServiceWorkerHostMsg_ActivateEventFinished, + OnActivateEventFinished) + IPC_MESSAGE_HANDLER(ServiceWorkerHostMsg_InstallEventFinished, + OnInstallEventFinished) + IPC_MESSAGE_HANDLER(ServiceWorkerHostMsg_FetchEventFinished, + OnFetchEventFinished) + IPC_MESSAGE_HANDLER(ServiceWorkerHostMsg_SyncEventFinished, + OnSyncEventFinished) + IPC_MESSAGE_HANDLER(ServiceWorkerHostMsg_PushEventFinished, + OnPushEventFinished) + IPC_MESSAGE_HANDLER(ServiceWorkerHostMsg_PostMessageToDocument, + OnPostMessageToDocument) + IPC_MESSAGE_UNHANDLED(handled = false) + IPC_END_MESSAGE_MAP() + return handled; +} + +void ServiceWorkerVersion::RunStartWorkerCallbacksOnError( + ServiceWorkerStatusCode status) { + if (status != SERVICE_WORKER_OK) + RunCallbacks(this, &start_callbacks_, status); +} + +void ServiceWorkerVersion::DispatchInstallEventAfterStartWorker( + int active_version_id, + const StatusCallback& callback) { + DCHECK_EQ(RUNNING, running_status()) + << "Worker stopped too soon after it was started."; + int request_id = install_callbacks_.Add(new StatusCallback(callback)); + ServiceWorkerStatusCode status = embedded_worker_->SendMessage( + ServiceWorkerMsg_InstallEvent(request_id, active_version_id)); + if (status != SERVICE_WORKER_OK) { + install_callbacks_.Remove(request_id); + RunSoon(base::Bind(callback, status)); + } +} + +void ServiceWorkerVersion::DispatchActivateEventAfterStartWorker( + const StatusCallback& callback) { + DCHECK_EQ(RUNNING, running_status()) + << "Worker stopped too soon after it was started."; + int request_id = activate_callbacks_.Add(new StatusCallback(callback)); + ServiceWorkerStatusCode status = + embedded_worker_->SendMessage(ServiceWorkerMsg_ActivateEvent(request_id)); + if (status != SERVICE_WORKER_OK) { + activate_callbacks_.Remove(request_id); + RunSoon(base::Bind(callback, status)); + } +} + +void ServiceWorkerVersion::OnGetClientDocuments(int request_id) { + std::vector<int> client_ids; + ControlleeByIDMap::iterator it(&controllee_by_id_); + while (!it.IsAtEnd()) { + client_ids.push_back(it.GetCurrentKey()); + it.Advance(); + } + // Don't bother if it's no longer running. + if (running_status() == RUNNING) { + embedded_worker_->SendMessage( + ServiceWorkerMsg_DidGetClientDocuments(request_id, client_ids)); + } +} + +void ServiceWorkerVersion::OnActivateEventFinished( + int request_id, + blink::WebServiceWorkerEventResult result) { + StatusCallback* callback = activate_callbacks_.Lookup(request_id); + if (!callback) { + NOTREACHED() << "Got unexpected message: " << request_id; + return; + } + ServiceWorkerStatusCode status = SERVICE_WORKER_OK; + if (result == blink::WebServiceWorkerEventResultRejected) + status = SERVICE_WORKER_ERROR_ACTIVATE_WORKER_FAILED; + else + SetStatus(ACTIVE); + + scoped_refptr<ServiceWorkerVersion> protect(this); + callback->Run(status); + activate_callbacks_.Remove(request_id); +} + +void ServiceWorkerVersion::OnInstallEventFinished( + int request_id, + blink::WebServiceWorkerEventResult result) { + StatusCallback* callback = install_callbacks_.Lookup(request_id); + if (!callback) { + NOTREACHED() << "Got unexpected message: " << request_id; + return; + } + ServiceWorkerStatusCode status = SERVICE_WORKER_OK; + if (result == blink::WebServiceWorkerEventResultRejected) + status = SERVICE_WORKER_ERROR_INSTALL_WORKER_FAILED; + else + SetStatus(INSTALLED); + + scoped_refptr<ServiceWorkerVersion> protect(this); + callback->Run(status); + install_callbacks_.Remove(request_id); +} + +void ServiceWorkerVersion::OnFetchEventFinished( + int request_id, + ServiceWorkerFetchEventResult result, + const ServiceWorkerResponse& response) { + FetchCallback* callback = fetch_callbacks_.Lookup(request_id); + if (!callback) { + NOTREACHED() << "Got unexpected message: " << request_id; + return; + } + + scoped_refptr<ServiceWorkerVersion> protect(this); + callback->Run(SERVICE_WORKER_OK, result, response); + fetch_callbacks_.Remove(request_id); +} + +void ServiceWorkerVersion::OnSyncEventFinished( + int request_id) { + StatusCallback* callback = sync_callbacks_.Lookup(request_id); + if (!callback) { + NOTREACHED() << "Got unexpected message: " << request_id; + return; + } + + scoped_refptr<ServiceWorkerVersion> protect(this); + callback->Run(SERVICE_WORKER_OK); + sync_callbacks_.Remove(request_id); +} + +void ServiceWorkerVersion::OnPushEventFinished( + int request_id) { + StatusCallback* callback = push_callbacks_.Lookup(request_id); + if (!callback) { + NOTREACHED() << "Got unexpected message: " << request_id; + return; + } + + scoped_refptr<ServiceWorkerVersion> protect(this); + callback->Run(SERVICE_WORKER_OK); + push_callbacks_.Remove(request_id); +} + +void ServiceWorkerVersion::OnPostMessageToDocument( + int client_id, + const base::string16& message, + const std::vector<int>& sent_message_port_ids) { + ServiceWorkerProviderHost* provider_host = + controllee_by_id_.Lookup(client_id); + if (!provider_host) { + // The client may already have been closed, just ignore. + return; + } + provider_host->PostMessage(message, sent_message_port_ids); +} + +void ServiceWorkerVersion::ScheduleStopWorker() { + if (running_status() != RUNNING) + return; + if (stop_worker_timer_.IsRunning()) { + stop_worker_timer_.Reset(); + return; + } + stop_worker_timer_.Start( + FROM_HERE, base::TimeDelta::FromSeconds(kStopWorkerDelay), + base::Bind(&ServiceWorkerVersion::StopWorker, + weak_factory_.GetWeakPtr(), + base::Bind(&ServiceWorkerUtils::NoOpStatusCallback))); } } // namespace content diff --git a/chromium/content/browser/service_worker/service_worker_version.h b/chromium/content/browser/service_worker/service_worker_version.h index 46b75bd6866..0c7bda50775 100644 --- a/chromium/content/browser/service_worker/service_worker_version.h +++ b/chromium/content/browser/service_worker/service_worker_version.h @@ -5,86 +5,280 @@ #ifndef CONTENT_BROWSER_SERVICE_WORKER_SERVICE_WORKER_VERSION_H_ #define CONTENT_BROWSER_SERVICE_WORKER_SERVICE_WORKER_VERSION_H_ +#include <string> +#include <vector> + #include "base/basictypes.h" +#include "base/callback.h" #include "base/gtest_prod_util.h" +#include "base/id_map.h" #include "base/memory/ref_counted.h" #include "base/memory/scoped_ptr.h" +#include "base/observer_list.h" +#include "base/timer/timer.h" +#include "content/browser/service_worker/embedded_worker_instance.h" +#include "content/browser/service_worker/service_worker_script_cache_map.h" #include "content/common/content_export.h" +#include "content/common/service_worker/service_worker_status_code.h" +#include "content/common/service_worker/service_worker_types.h" +#include "third_party/WebKit/public/platform/WebServiceWorkerEventResult.h" class GURL; namespace content { -class EmbeddedWorkerInstance; class EmbeddedWorkerRegistry; +class ServiceWorkerContextCore; class ServiceWorkerProviderHost; class ServiceWorkerRegistration; +class ServiceWorkerVersionInfo; // This class corresponds to a specific version of a ServiceWorker // script for a given pattern. When a script is upgraded, there may be // more than one ServiceWorkerVersion "running" at a time, but only // one of them is active. This class connects the actual script with a // running worker. -// Instances of this class are in one of two install states: -// - Pending: The script is in the process of being installed. There -// may be another active script running. -// - Active: The script is the only worker handling requests for the -// registration's pattern. -// -// In addition, a version has a running state (this is a rough -// sketch). Since a service worker can be stopped and started at any -// time, it will transition among these states multiple times during -// its lifetime. -// - Stopped: The script is not running -// - Starting: A request to fire an event against the version has been -// queued, but the worker is not yet -// loaded/initialized/etc. -// - Started: The worker is ready to receive events -// - Stopping: The worker is returning to the stopped state. // -// The worker can "run" in both the Pending and the Active -// install states above. During the Pending state, the worker is only -// started in order to fire the 'install' and 'activate' -// events. During the Active state, it can receive other events such -// as 'fetch'. -// -// And finally, is_shutdown_ is detects the live-ness of the object -// itself. If the object is shut down, then it is in the process of -// being deleted from memory. This happens when a version is replaced -// as well as at browser shutdown. +// is_shutdown_ detects the live-ness of the object itself. If the object is +// shut down, then it is in the process of being deleted from memory. +// This happens when a version is replaced as well as at browser shutdown. class CONTENT_EXPORT ServiceWorkerVersion - : NON_EXPORTED_BASE(public base::RefCounted<ServiceWorkerVersion>) { + : NON_EXPORTED_BASE(public base::RefCounted<ServiceWorkerVersion>), + public EmbeddedWorkerInstance::Listener { public: + typedef base::Callback<void(ServiceWorkerStatusCode)> StatusCallback; + typedef base::Callback<void(ServiceWorkerStatusCode, + const IPC::Message& message)> MessageCallback; + typedef base::Callback<void(ServiceWorkerStatusCode, + ServiceWorkerFetchEventResult, + const ServiceWorkerResponse&)> FetchCallback; + + enum RunningStatus { + STOPPED = EmbeddedWorkerInstance::STOPPED, + STARTING = EmbeddedWorkerInstance::STARTING, + RUNNING = EmbeddedWorkerInstance::RUNNING, + STOPPING = EmbeddedWorkerInstance::STOPPING, + }; + + // Current version status; some of the status (e.g. INSTALLED and ACTIVE) + // should be persisted unlike running status. + enum Status { + NEW, // The version is just created. + INSTALLING, // Install event is dispatched and being handled. + INSTALLED, // Install event is finished and is ready to be activated. + ACTIVATING, // Activate event is dispatched and being handled. + ACTIVE, // Activation is finished and can run as active. + DEACTIVATED, // The version is no longer running as active, due to + // unregistration or replace. (TODO(kinuko): we may need + // different states for different termination sequences) + }; + + class Listener { + public: + virtual void OnWorkerStarted(ServiceWorkerVersion* version) = 0; + virtual void OnWorkerStopped(ServiceWorkerVersion* version) = 0; + virtual void OnVersionStateChanged(ServiceWorkerVersion* version) = 0; + virtual void OnErrorReported(ServiceWorkerVersion* version, + const base::string16& error_message, + int line_number, + int column_number, + const GURL& source_url) = 0; + virtual void OnReportConsoleMessage(ServiceWorkerVersion* version, + int source_identifier, + int message_level, + const base::string16& message, + int line_number, + const GURL& source_url) = 0; + }; + ServiceWorkerVersion( ServiceWorkerRegistration* registration, - EmbeddedWorkerRegistry* worker_registry, - int64 version_id); + int64 version_id, + base::WeakPtr<ServiceWorkerContextCore> context); int64 version_id() const { return version_id_; } + int64 registration_id() const { return registration_id_; } + const GURL& script_url() const { return script_url_; } + const GURL& scope() const { return scope_; } + RunningStatus running_status() const { + return static_cast<RunningStatus>(embedded_worker_->status()); + } + ServiceWorkerVersionInfo GetInfo(); + Status status() const { return status_; } + + // This sets the new status and also run status change callbacks + // if there're any (see RegisterStatusChangeCallback). + void SetStatus(Status status); + + // Registers status change callback. (This is for one-off observation, + // the consumer needs to re-register if it wants to continue observing + // status changes) + void RegisterStatusChangeCallback(const base::Closure& callback); + + // Starts an embedded worker for this version. + // This returns OK (success) if the worker is already running. + void StartWorker(const StatusCallback& callback); + + // Starts an embedded worker for this version. + // |potential_process_ids| is a list of processes in which to start the + // worker. + // This returns OK (success) if the worker is already running. + void StartWorkerWithCandidateProcesses( + const std::vector<int>& potential_process_ids, + const StatusCallback& callback); + + // Starts an embedded worker for this version. + // This returns OK (success) if the worker is already stopped. + void StopWorker(const StatusCallback& callback); + + // Sends an IPC message to the worker. + // If the worker is not running this first tries to start it by + // calling StartWorker internally. + // |callback| can be null if the sender does not need to know if the + // message is successfully sent or not. + void SendMessage(const IPC::Message& message, const StatusCallback& callback); + + // Sends install event to the associated embedded worker and asynchronously + // calls |callback| when it errors out or it gets response from the worker + // to notify install completion. + // |active_version_id| must be a valid positive ID + // if there's an active (previous) version running. + // + // This must be called when the status() is NEW. Calling this changes + // the version's status to INSTALLING. + // Upon completion, the version's status will be changed to INSTALLED + // on success, or back to NEW on failure. + void DispatchInstallEvent(int active_version_id, + const StatusCallback& callback); + + // Sends activate event to the associated embedded worker and asynchronously + // calls |callback| when it errors out or it gets response from the worker + // to notify activation completion. + // + // This must be called when the status() is INSTALLED. Calling this changes + // the version's status to ACTIVATING. + // Upon completion, the version's status will be changed to ACTIVE + // on success, or back to INSTALLED on failure. + void DispatchActivateEvent(const StatusCallback& callback); - void Shutdown(); - bool is_shutdown() const { return is_shutdown_; } + // Sends fetch event to the associated embedded worker and calls + // |callback| with the response from the worker. + // + // This must be called when the status() is ACTIVE. Calling this in other + // statuses will result in an error SERVICE_WORKER_ERROR_FAILED. + void DispatchFetchEvent(const ServiceWorkerFetchRequest& request, + const FetchCallback& callback); - // Starts and stops an embedded worker for this version. - void StartWorker(); - void StopWorker(); + // Sends sync event to the associated embedded worker and asynchronously calls + // |callback| when it errors out or it gets response from the worker to notify + // completion. + // + // This must be called when the status() is ACTIVE. + void DispatchSyncEvent(const StatusCallback& callback); - // Called when this version is associated to a provider host. - // Non-null |provider_host| must be given. - void OnAssociateProvider(ServiceWorkerProviderHost* provider_host); - void OnUnassociateProvider(ServiceWorkerProviderHost* provider_host); + // Sends push event to the associated embedded worker and asynchronously calls + // |callback| when it errors out or it gets response from the worker to notify + // completion. + // + // This must be called when the status() is ACTIVE. + void DispatchPushEvent(const StatusCallback& callback, + const std::string& data); + + // These are expected to be called when a renderer process host for the + // same-origin as for this ServiceWorkerVersion is created. The added + // processes are used to run an in-renderer embedded worker. + void AddProcessToWorker(int process_id); + void RemoveProcessFromWorker(int process_id); + + // Returns true if this has at least one process to run. + bool HasProcessToRun() const; + + // Adds and removes |provider_host| as a controllee of this ServiceWorker. + void AddControllee(ServiceWorkerProviderHost* provider_host); + void RemoveControllee(ServiceWorkerProviderHost* provider_host); + void AddWaitingControllee(ServiceWorkerProviderHost* provider_host); + void RemoveWaitingControllee(ServiceWorkerProviderHost* provider_host); + + // Returns if it has controllee. + bool HasControllee() const { return !controllee_map_.empty(); } + + // Adds and removes Listeners. + void AddListener(Listener* listener); + void RemoveListener(Listener* listener); + + ServiceWorkerScriptCacheMap* script_cache_map() { return &script_cache_map_; } + EmbeddedWorkerInstance* embedded_worker() { return embedded_worker_.get(); } private: + typedef ServiceWorkerVersion self; + typedef std::map<ServiceWorkerProviderHost*, int> ControlleeMap; + typedef IDMap<ServiceWorkerProviderHost> ControlleeByIDMap; friend class base::RefCounted<ServiceWorkerVersion>; - ~ServiceWorkerVersion(); + virtual ~ServiceWorkerVersion(); - const int64 version_id_; + // EmbeddedWorkerInstance::Listener overrides: + virtual void OnStarted() OVERRIDE; + virtual void OnStopped() OVERRIDE; + virtual void OnReportException(const base::string16& error_message, + int line_number, + int column_number, + const GURL& source_url) OVERRIDE; + virtual void OnReportConsoleMessage(int source_identifier, + int message_level, + const base::string16& message, + int line_number, + const GURL& source_url) OVERRIDE; + virtual bool OnMessageReceived(const IPC::Message& message) OVERRIDE; - bool is_shutdown_; - scoped_refptr<ServiceWorkerRegistration> registration_; + void RunStartWorkerCallbacksOnError(ServiceWorkerStatusCode status); + void DispatchInstallEventAfterStartWorker(int active_version_id, + const StatusCallback& callback); + void DispatchActivateEventAfterStartWorker(const StatusCallback& callback); + + // Message handlers. + void OnGetClientDocuments(int request_id); + void OnActivateEventFinished(int request_id, + blink::WebServiceWorkerEventResult result); + void OnInstallEventFinished(int request_id, + blink::WebServiceWorkerEventResult result); + void OnFetchEventFinished(int request_id, + ServiceWorkerFetchEventResult result, + const ServiceWorkerResponse& response); + void OnSyncEventFinished(int request_id); + void OnPushEventFinished(int request_id); + void OnPostMessageToDocument(int client_id, + const base::string16& message, + const std::vector<int>& sent_message_port_ids); + + void ScheduleStopWorker(); + + const int64 version_id_; + int64 registration_id_; + GURL script_url_; + GURL scope_; + Status status_; scoped_ptr<EmbeddedWorkerInstance> embedded_worker_; + std::vector<StatusCallback> start_callbacks_; + std::vector<StatusCallback> stop_callbacks_; + std::vector<base::Closure> status_change_callbacks_; + + // Message callbacks. + IDMap<StatusCallback, IDMapOwnPointer> activate_callbacks_; + IDMap<StatusCallback, IDMapOwnPointer> install_callbacks_; + IDMap<FetchCallback, IDMapOwnPointer> fetch_callbacks_; + IDMap<StatusCallback, IDMapOwnPointer> sync_callbacks_; + IDMap<StatusCallback, IDMapOwnPointer> push_callbacks_; + + ControlleeMap controllee_map_; + ControlleeByIDMap controllee_by_id_; + base::WeakPtr<ServiceWorkerContextCore> context_; + ObserverList<Listener> listeners_; + ServiceWorkerScriptCacheMap script_cache_map_; + base::OneShotTimer<ServiceWorkerVersion> stop_worker_timer_; + + base::WeakPtrFactory<ServiceWorkerVersion> weak_factory_; DISALLOW_COPY_AND_ASSIGN(ServiceWorkerVersion); }; diff --git a/chromium/content/browser/service_worker/service_worker_version_unittest.cc b/chromium/content/browser/service_worker/service_worker_version_unittest.cc new file mode 100644 index 00000000000..6622e5aba83 --- /dev/null +++ b/chromium/content/browser/service_worker/service_worker_version_unittest.cc @@ -0,0 +1,355 @@ +// Copyright 2014 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 "base/basictypes.h" +#include "base/run_loop.h" +#include "content/browser/service_worker/embedded_worker_registry.h" +#include "content/browser/service_worker/embedded_worker_test_helper.h" +#include "content/browser/service_worker/service_worker_context_core.h" +#include "content/browser/service_worker/service_worker_registration.h" +#include "content/browser/service_worker/service_worker_test_utils.h" +#include "content/browser/service_worker/service_worker_version.h" +#include "content/public/test/test_browser_thread_bundle.h" +#include "testing/gtest/include/gtest/gtest.h" + +// IPC messages for testing --------------------------------------------------- + +#define IPC_MESSAGE_IMPL +#include "ipc/ipc_message_macros.h" + +#define IPC_MESSAGE_START TestMsgStart + +IPC_MESSAGE_CONTROL0(TestMsg_Message); +IPC_MESSAGE_ROUTED1(TestMsg_MessageFromWorker, int); + +// --------------------------------------------------------------------------- + +namespace content { + +namespace { + +static const int kRenderProcessId = 1; + +class MessageReceiver : public EmbeddedWorkerTestHelper { + public: + MessageReceiver() + : EmbeddedWorkerTestHelper(kRenderProcessId), + current_embedded_worker_id_(0) {} + virtual ~MessageReceiver() {} + + virtual bool OnMessageToWorker(int thread_id, + int embedded_worker_id, + const IPC::Message& message) OVERRIDE { + if (EmbeddedWorkerTestHelper::OnMessageToWorker( + thread_id, embedded_worker_id, message)) { + return true; + } + current_embedded_worker_id_ = embedded_worker_id; + bool handled = true; + IPC_BEGIN_MESSAGE_MAP(MessageReceiver, message) + IPC_MESSAGE_HANDLER(TestMsg_Message, OnMessage) + IPC_MESSAGE_UNHANDLED(handled = false) + IPC_END_MESSAGE_MAP() + return handled; + } + + void SimulateSendValueToBrowser(int embedded_worker_id, int value) { + SimulateSend(new TestMsg_MessageFromWorker(embedded_worker_id, value)); + } + + private: + void OnMessage() { + // Do nothing. + } + + int current_embedded_worker_id_; + DISALLOW_COPY_AND_ASSIGN(MessageReceiver); +}; + +void VerifyCalled(bool* called) { + *called = true; +} + +void ObserveStatusChanges(ServiceWorkerVersion* version, + std::vector<ServiceWorkerVersion::Status>* statuses) { + statuses->push_back(version->status()); + version->RegisterStatusChangeCallback( + base::Bind(&ObserveStatusChanges, base::Unretained(version), statuses)); +} + +// A specialized listener class to receive test messages from a worker. +class MessageReceiverFromWorker : public EmbeddedWorkerInstance::Listener { + public: + explicit MessageReceiverFromWorker(EmbeddedWorkerInstance* instance) + : instance_(instance) { + instance_->AddListener(this); + } + virtual ~MessageReceiverFromWorker() { + instance_->RemoveListener(this); + } + + virtual void OnStarted() OVERRIDE { NOTREACHED(); } + virtual void OnStopped() OVERRIDE { NOTREACHED(); } + virtual bool OnMessageReceived(const IPC::Message& message) OVERRIDE { + bool handled = true; + IPC_BEGIN_MESSAGE_MAP(MessageReceiverFromWorker, message) + IPC_MESSAGE_HANDLER(TestMsg_MessageFromWorker, OnMessageFromWorker) + IPC_MESSAGE_UNHANDLED(handled = false) + IPC_END_MESSAGE_MAP() + return handled; + } + + void OnMessageFromWorker(int value) { received_values_.push_back(value); } + const std::vector<int>& received_values() const { return received_values_; } + + private: + EmbeddedWorkerInstance* instance_; + std::vector<int> received_values_; + DISALLOW_COPY_AND_ASSIGN(MessageReceiverFromWorker); +}; + +} // namespace + +class ServiceWorkerVersionTest : public testing::Test { + protected: + ServiceWorkerVersionTest() + : thread_bundle_(TestBrowserThreadBundle::IO_MAINLOOP) {} + + virtual void SetUp() OVERRIDE { + helper_.reset(new MessageReceiver()); + + registration_ = new ServiceWorkerRegistration( + GURL("http://www.example.com/*"), + GURL("http://www.example.com/service_worker.js"), + 1L, + helper_->context()->AsWeakPtr()); + version_ = new ServiceWorkerVersion( + registration_, 1L, helper_->context()->AsWeakPtr()); + + // Simulate adding one process to the worker. + int embedded_worker_id = version_->embedded_worker()->embedded_worker_id(); + helper_->SimulateAddProcessToWorker(embedded_worker_id, kRenderProcessId); + ASSERT_TRUE(version_->HasProcessToRun()); + } + + virtual void TearDown() OVERRIDE { + version_ = 0; + registration_ = 0; + helper_.reset(); + } + + TestBrowserThreadBundle thread_bundle_; + scoped_ptr<MessageReceiver> helper_; + scoped_refptr<ServiceWorkerRegistration> registration_; + scoped_refptr<ServiceWorkerVersion> version_; + DISALLOW_COPY_AND_ASSIGN(ServiceWorkerVersionTest); +}; + +TEST_F(ServiceWorkerVersionTest, ConcurrentStartAndStop) { + // Call StartWorker() multiple times. + ServiceWorkerStatusCode status1 = SERVICE_WORKER_ERROR_FAILED; + ServiceWorkerStatusCode status2 = SERVICE_WORKER_ERROR_FAILED; + ServiceWorkerStatusCode status3 = SERVICE_WORKER_ERROR_FAILED; + version_->StartWorker(CreateReceiverOnCurrentThread(&status1)); + version_->StartWorker(CreateReceiverOnCurrentThread(&status2)); + + EXPECT_EQ(ServiceWorkerVersion::STARTING, version_->running_status()); + base::RunLoop().RunUntilIdle(); + EXPECT_EQ(ServiceWorkerVersion::RUNNING, version_->running_status()); + + // Call StartWorker() after it's started. + version_->StartWorker(CreateReceiverOnCurrentThread(&status3)); + base::RunLoop().RunUntilIdle(); + + // All should just succeed. + EXPECT_EQ(SERVICE_WORKER_OK, status1); + EXPECT_EQ(SERVICE_WORKER_OK, status2); + EXPECT_EQ(SERVICE_WORKER_OK, status3); + + // Call StopWorker() multiple times. + status1 = SERVICE_WORKER_ERROR_FAILED; + status2 = SERVICE_WORKER_ERROR_FAILED; + status3 = SERVICE_WORKER_ERROR_FAILED; + version_->StopWorker(CreateReceiverOnCurrentThread(&status1)); + version_->StopWorker(CreateReceiverOnCurrentThread(&status2)); + + // Also try calling StartWorker while StopWorker is in queue. + version_->StartWorker(CreateReceiverOnCurrentThread(&status3)); + + EXPECT_EQ(ServiceWorkerVersion::STOPPING, version_->running_status()); + base::RunLoop().RunUntilIdle(); + EXPECT_EQ(ServiceWorkerVersion::STOPPED, version_->running_status()); + + // All StopWorker should just succeed, while StartWorker fails. + EXPECT_EQ(SERVICE_WORKER_OK, status1); + EXPECT_EQ(SERVICE_WORKER_OK, status2); + EXPECT_EQ(SERVICE_WORKER_ERROR_START_WORKER_FAILED, status3); +} + +TEST_F(ServiceWorkerVersionTest, SendMessage) { + EXPECT_EQ(ServiceWorkerVersion::STOPPED, version_->running_status()); + + // Send a message without starting the worker. + ServiceWorkerStatusCode status = SERVICE_WORKER_ERROR_FAILED; + version_->SendMessage(TestMsg_Message(), + CreateReceiverOnCurrentThread(&status)); + base::RunLoop().RunUntilIdle(); + EXPECT_EQ(SERVICE_WORKER_OK, status); + + // The worker should be now started. + EXPECT_EQ(ServiceWorkerVersion::RUNNING, version_->running_status()); + + // Stop the worker, and then send the message immediately. + ServiceWorkerStatusCode msg_status = SERVICE_WORKER_ERROR_FAILED; + ServiceWorkerStatusCode stop_status = SERVICE_WORKER_ERROR_FAILED; + version_->StopWorker(CreateReceiverOnCurrentThread(&stop_status)); + version_->SendMessage(TestMsg_Message(), + CreateReceiverOnCurrentThread(&msg_status)); + base::RunLoop().RunUntilIdle(); + EXPECT_EQ(SERVICE_WORKER_OK, stop_status); + + // SendMessage should return START_WORKER_FAILED error since it tried to + // start a worker while it was stopping. + EXPECT_EQ(SERVICE_WORKER_ERROR_START_WORKER_FAILED, msg_status); +} + +TEST_F(ServiceWorkerVersionTest, ReSendMessageAfterStop) { + EXPECT_EQ(ServiceWorkerVersion::STOPPED, version_->running_status()); + + // Start the worker. + ServiceWorkerStatusCode start_status = SERVICE_WORKER_ERROR_FAILED; + version_->StartWorker(CreateReceiverOnCurrentThread(&start_status)); + base::RunLoop().RunUntilIdle(); + EXPECT_EQ(SERVICE_WORKER_OK, start_status); + EXPECT_EQ(ServiceWorkerVersion::RUNNING, version_->running_status()); + + // Stop the worker, and then send the message immediately. + ServiceWorkerStatusCode msg_status = SERVICE_WORKER_ERROR_FAILED; + ServiceWorkerStatusCode stop_status = SERVICE_WORKER_ERROR_FAILED; + version_->StopWorker(CreateReceiverOnCurrentThread(&stop_status)); + version_->SendMessage(TestMsg_Message(), + CreateReceiverOnCurrentThread(&msg_status)); + base::RunLoop().RunUntilIdle(); + EXPECT_EQ(SERVICE_WORKER_OK, stop_status); + + // SendMessage should return START_WORKER_FAILED error since it tried to + // start a worker while it was stopping. + EXPECT_EQ(SERVICE_WORKER_ERROR_START_WORKER_FAILED, msg_status); + + // Resend the message, which should succeed and restart the worker. + version_->SendMessage(TestMsg_Message(), + CreateReceiverOnCurrentThread(&msg_status)); + base::RunLoop().RunUntilIdle(); + EXPECT_EQ(SERVICE_WORKER_OK, msg_status); + EXPECT_EQ(ServiceWorkerVersion::RUNNING, version_->running_status()); +} + +TEST_F(ServiceWorkerVersionTest, ReceiveMessageFromWorker) { + MessageReceiverFromWorker receiver(version_->embedded_worker()); + + // Simulate sending some dummy values from the worker. + helper_->SimulateSendValueToBrowser( + version_->embedded_worker()->embedded_worker_id(), 555); + helper_->SimulateSendValueToBrowser( + version_->embedded_worker()->embedded_worker_id(), 777); + + // Verify the receiver received the values. + ASSERT_EQ(2U, receiver.received_values().size()); + EXPECT_EQ(555, receiver.received_values()[0]); + EXPECT_EQ(777, receiver.received_values()[1]); +} + +TEST_F(ServiceWorkerVersionTest, InstallAndWaitCompletion) { + EXPECT_EQ(ServiceWorkerVersion::NEW, version_->status()); + + // Dispatch an install event. + ServiceWorkerStatusCode status = SERVICE_WORKER_ERROR_FAILED; + version_->DispatchInstallEvent(-1, CreateReceiverOnCurrentThread(&status)); + + // Wait for the completion. + bool status_change_called = false; + version_->RegisterStatusChangeCallback( + base::Bind(&VerifyCalled, &status_change_called)); + + base::RunLoop().RunUntilIdle(); + + // After successful completion, version's status must be changed to + // INSTALLED, and status change callback must have been fired. + EXPECT_EQ(SERVICE_WORKER_OK, status); + EXPECT_TRUE(status_change_called); + EXPECT_EQ(ServiceWorkerVersion::INSTALLED, version_->status()); +} + +TEST_F(ServiceWorkerVersionTest, ActivateAndWaitCompletion) { + version_->SetStatus(ServiceWorkerVersion::INSTALLED); + EXPECT_EQ(ServiceWorkerVersion::INSTALLED, version_->status()); + + // Dispatch an activate event. + ServiceWorkerStatusCode status = SERVICE_WORKER_ERROR_FAILED; + version_->DispatchActivateEvent(CreateReceiverOnCurrentThread(&status)); + + // Wait for the completion. + bool status_change_called = false; + version_->RegisterStatusChangeCallback( + base::Bind(&VerifyCalled, &status_change_called)); + + base::RunLoop().RunUntilIdle(); + + // After successful completion, version's status must be changed to + // ACTIVE, and status change callback must have been fired. + EXPECT_EQ(SERVICE_WORKER_OK, status); + EXPECT_TRUE(status_change_called); + EXPECT_EQ(ServiceWorkerVersion::ACTIVE, version_->status()); +} + +TEST_F(ServiceWorkerVersionTest, RepeatedlyObserveStatusChanges) { + EXPECT_EQ(ServiceWorkerVersion::NEW, version_->status()); + + // Repeatedly observe status changes (the callback re-registers itself). + std::vector<ServiceWorkerVersion::Status> statuses; + version_->RegisterStatusChangeCallback( + base::Bind(&ObserveStatusChanges, version_, &statuses)); + + // Dispatch some events. + ServiceWorkerStatusCode status = SERVICE_WORKER_ERROR_FAILED; + version_->DispatchInstallEvent(-1, CreateReceiverOnCurrentThread(&status)); + base::RunLoop().RunUntilIdle(); + EXPECT_EQ(SERVICE_WORKER_OK, status); + + status = SERVICE_WORKER_ERROR_FAILED; + version_->DispatchActivateEvent(CreateReceiverOnCurrentThread(&status)); + base::RunLoop().RunUntilIdle(); + EXPECT_EQ(SERVICE_WORKER_OK, status); + + // Verify that we could successfully observe repeated status changes. + ASSERT_EQ(4U, statuses.size()); + ASSERT_EQ(ServiceWorkerVersion::INSTALLING, statuses[0]); + ASSERT_EQ(ServiceWorkerVersion::INSTALLED, statuses[1]); + ASSERT_EQ(ServiceWorkerVersion::ACTIVATING, statuses[2]); + ASSERT_EQ(ServiceWorkerVersion::ACTIVE, statuses[3]); +} + +TEST_F(ServiceWorkerVersionTest, AddAndRemoveProcesses) { + // Preparation (to reset the process count to 0). + ASSERT_TRUE(version_->HasProcessToRun()); + version_->RemoveProcessFromWorker(kRenderProcessId); + ASSERT_FALSE(version_->HasProcessToRun()); + + // Add another process to the worker twice, and then remove process once. + const int another_process_id = kRenderProcessId + 1; + version_->AddProcessToWorker(another_process_id); + version_->AddProcessToWorker(another_process_id); + version_->RemoveProcessFromWorker(another_process_id); + + // We're ref-counting the process internally, so adding the same process + // multiple times should be handled correctly. + ASSERT_TRUE(version_->HasProcessToRun()); + + // Removing the process again (so that # of AddProcess == # of RemoveProcess + // for the process) should remove all process references. + version_->RemoveProcessFromWorker(another_process_id); + ASSERT_FALSE(version_->HasProcessToRun()); +} + +} // namespace content diff --git a/chromium/content/browser/service_worker/service_worker_write_to_cache_job.cc b/chromium/content/browser/service_worker/service_worker_write_to_cache_job.cc new file mode 100644 index 00000000000..bd16a132004 --- /dev/null +++ b/chromium/content/browser/service_worker/service_worker_write_to_cache_job.cc @@ -0,0 +1,327 @@ +// Copyright 2014 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 "content/browser/service_worker/service_worker_write_to_cache_job.h" + +#include "content/browser/service_worker/service_worker_context_core.h" +#include "content/browser/service_worker/service_worker_disk_cache.h" +#include "content/browser/service_worker/service_worker_histograms.h" +#include "net/base/io_buffer.h" +#include "net/base/net_errors.h" +#include "net/http/http_request_headers.h" +#include "net/http/http_response_headers.h" +#include "net/http/http_util.h" +#include "net/url_request/url_request.h" +#include "net/url_request/url_request_context.h" +#include "net/url_request/url_request_status.h" + +namespace content { + +ServiceWorkerWriteToCacheJob::ServiceWorkerWriteToCacheJob( + net::URLRequest* request, + net::NetworkDelegate* network_delegate, + base::WeakPtr<ServiceWorkerContextCore> context, + ServiceWorkerVersion* version, + int64 response_id) + : net::URLRequestJob(request, network_delegate), + context_(context), + url_(request->url()), + response_id_(response_id), + version_(version), + has_been_killed_(false), + did_notify_started_(false), + did_notify_finished_(false), + weak_factory_(this) { + InitNetRequest(); +} + +ServiceWorkerWriteToCacheJob::~ServiceWorkerWriteToCacheJob() { + DCHECK_EQ(did_notify_started_, did_notify_finished_); +} + +void ServiceWorkerWriteToCacheJob::Start() { + if (!context_) { + NotifyStartError(net::URLRequestStatus( + net::URLRequestStatus::FAILED, net::ERR_FAILED)); + return; + } + version_->script_cache_map()->NotifyStartedCaching( + url_, response_id_); + did_notify_started_ = true; + StartNetRequest(); +} + +void ServiceWorkerWriteToCacheJob::Kill() { + if (has_been_killed_) + return; + weak_factory_.InvalidateWeakPtrs(); + has_been_killed_ = true; + net_request_.reset(); + if (did_notify_started_ && !did_notify_finished_) { + version_->script_cache_map()->NotifyFinishedCaching( + url_, false); + did_notify_finished_ = true; + } + writer_.reset(); + context_.reset(); + net::URLRequestJob::Kill(); +} + +net::LoadState ServiceWorkerWriteToCacheJob::GetLoadState() const { + if (writer_ && writer_->IsWritePending()) + return net::LOAD_STATE_WAITING_FOR_APPCACHE; + if (net_request_) + return net_request_->GetLoadState().state; + return net::LOAD_STATE_IDLE; +} + +bool ServiceWorkerWriteToCacheJob::GetCharset(std::string* charset) { + if (!http_info()) + return false; + return http_info()->headers->GetCharset(charset); +} + +bool ServiceWorkerWriteToCacheJob::GetMimeType(std::string* mime_type) const { + if (!http_info()) + return false; + return http_info()->headers->GetMimeType(mime_type); +} + +void ServiceWorkerWriteToCacheJob::GetResponseInfo( + net::HttpResponseInfo* info) { + if (!http_info()) + return; + *info = *http_info(); +} + +int ServiceWorkerWriteToCacheJob::GetResponseCode() const { + if (!http_info()) + return -1; + return http_info()->headers->response_code(); +} + +void ServiceWorkerWriteToCacheJob::SetExtraRequestHeaders( + const net::HttpRequestHeaders& headers) { + std::string value; + DCHECK(!headers.GetHeader(net::HttpRequestHeaders::kRange, &value)); + net_request_->SetExtraRequestHeaders(headers); +} + +bool ServiceWorkerWriteToCacheJob::ReadRawData( + net::IOBuffer* buf, + int buf_size, + int *bytes_read) { + net::URLRequestStatus status = ReadNetData(buf, buf_size, bytes_read); + SetStatus(status); + if (status.is_io_pending()) + return false; + + // No more data to process, the job is complete. + io_buffer_ = NULL; + version_->script_cache_map()->NotifyFinishedCaching( + url_, status.is_success()); + did_notify_finished_ = true; + return status.is_success(); +} + +const net::HttpResponseInfo* ServiceWorkerWriteToCacheJob::http_info() const { + return http_info_.get(); +} + +void ServiceWorkerWriteToCacheJob::InitNetRequest() { + DCHECK(request()); + net_request_ = request()->context()->CreateRequest( + request()->url(), + request()->priority(), + this, + this->GetCookieStore()); + net_request_->set_first_party_for_cookies( + request()->first_party_for_cookies()); + net_request_->SetReferrer(request()->referrer()); + net_request_->SetExtraRequestHeaders(request()->extra_request_headers()); +} + +void ServiceWorkerWriteToCacheJob::StartNetRequest() { + net_request_->Start(); // We'll continue in OnResponseStarted. +} + +net::URLRequestStatus ServiceWorkerWriteToCacheJob::ReadNetData( + net::IOBuffer* buf, + int buf_size, + int *bytes_read) { + DCHECK_GT(buf_size, 0); + DCHECK(bytes_read); + + *bytes_read = 0; + io_buffer_ = buf; + int net_bytes_read = 0; + if (!net_request_->Read(buf, buf_size, &net_bytes_read)) { + if (net_request_->status().is_io_pending()) + return net_request_->status(); + DCHECK(!net_request_->status().is_success()); + return net_request_->status(); + } + + if (net_bytes_read != 0) { + WriteDataToCache(net_bytes_read); + DCHECK(GetStatus().is_io_pending()); + return GetStatus(); + } + + DCHECK(net_request_->status().is_success()); + return net_request_->status(); +} + +void ServiceWorkerWriteToCacheJob::WriteHeadersToCache() { + if (!context_) { + AsyncNotifyDoneHelper(net::URLRequestStatus( + net::URLRequestStatus::FAILED, net::ERR_FAILED)); + return; + } + writer_ = context_->storage()->CreateResponseWriter(response_id_); + info_buffer_ = new HttpResponseInfoIOBuffer( + new net::HttpResponseInfo(net_request_->response_info())); + writer_->WriteInfo( + info_buffer_, + base::Bind(&ServiceWorkerWriteToCacheJob::OnWriteHeadersComplete, + weak_factory_.GetWeakPtr())); + SetStatus(net::URLRequestStatus(net::URLRequestStatus::IO_PENDING, 0)); +} + +void ServiceWorkerWriteToCacheJob::OnWriteHeadersComplete(int result) { + SetStatus(net::URLRequestStatus()); // Clear the IO_PENDING status + if (result < 0) { + ServiceWorkerHistograms::CountWriteResponseResult( + ServiceWorkerHistograms::WRITE_HEADERS_ERROR); + AsyncNotifyDoneHelper(net::URLRequestStatus( + net::URLRequestStatus::FAILED, result)); + return; + } + http_info_.reset(info_buffer_->http_info.release()); + info_buffer_ = NULL; + NotifyHeadersComplete(); +} + +void ServiceWorkerWriteToCacheJob::WriteDataToCache(int amount_to_write) { + DCHECK_NE(0, amount_to_write); + SetStatus(net::URLRequestStatus(net::URLRequestStatus::IO_PENDING, 0)); + writer_->WriteData( + io_buffer_, amount_to_write, + base::Bind(&ServiceWorkerWriteToCacheJob::OnWriteDataComplete, + weak_factory_.GetWeakPtr())); +} + +void ServiceWorkerWriteToCacheJob::OnWriteDataComplete(int result) { + DCHECK_NE(0, result); + io_buffer_ = NULL; + if (!context_) { + AsyncNotifyDoneHelper(net::URLRequestStatus( + net::URLRequestStatus::FAILED, net::ERR_FAILED)); + return; + } + if (result < 0) { + ServiceWorkerHistograms::CountWriteResponseResult( + ServiceWorkerHistograms::WRITE_DATA_ERROR); + AsyncNotifyDoneHelper(net::URLRequestStatus( + net::URLRequestStatus::FAILED, result)); + return; + } + ServiceWorkerHistograms::CountWriteResponseResult( + ServiceWorkerHistograms::WRITE_OK); + SetStatus(net::URLRequestStatus()); // Clear the IO_PENDING status + NotifyReadComplete(result); +} + +void ServiceWorkerWriteToCacheJob::OnReceivedRedirect( + net::URLRequest* request, + const GURL& new_url, + bool* defer_redirect) { + DCHECK_EQ(net_request_, request); + // Script resources can't redirect. + AsyncNotifyDoneHelper(net::URLRequestStatus( + net::URLRequestStatus::FAILED, net::ERR_FAILED)); +} + +void ServiceWorkerWriteToCacheJob::OnAuthRequired( + net::URLRequest* request, + net::AuthChallengeInfo* auth_info) { + DCHECK_EQ(net_request_, request); + // TODO(michaeln): Pass this thru to our jobs client. + AsyncNotifyDoneHelper(net::URLRequestStatus( + net::URLRequestStatus::FAILED, net::ERR_FAILED)); +} + +void ServiceWorkerWriteToCacheJob::OnCertificateRequested( + net::URLRequest* request, + net::SSLCertRequestInfo* cert_request_info) { + DCHECK_EQ(net_request_, request); + // TODO(michaeln): Pass this thru to our jobs client. + // see NotifyCertificateRequested. + AsyncNotifyDoneHelper(net::URLRequestStatus( + net::URLRequestStatus::FAILED, net::ERR_FAILED)); +} + +void ServiceWorkerWriteToCacheJob:: OnSSLCertificateError( + net::URLRequest* request, + const net::SSLInfo& ssl_info, + bool fatal) { + DCHECK_EQ(net_request_, request); + // TODO(michaeln): Pass this thru to our jobs client, + // see NotifySSLCertificateError. + AsyncNotifyDoneHelper(net::URLRequestStatus( + net::URLRequestStatus::FAILED, net::ERR_FAILED)); +} + +void ServiceWorkerWriteToCacheJob::OnBeforeNetworkStart( + net::URLRequest* request, + bool* defer) { + DCHECK_EQ(net_request_, request); + NotifyBeforeNetworkStart(defer); +} + +void ServiceWorkerWriteToCacheJob::OnResponseStarted( + net::URLRequest* request) { + DCHECK_EQ(net_request_, request); + if (!request->status().is_success()) { + AsyncNotifyDoneHelper(request->status()); + return; + } + if (request->GetResponseCode() / 100 != 2) { + AsyncNotifyDoneHelper(net::URLRequestStatus( + net::URLRequestStatus::FAILED, net::ERR_FAILED)); + // TODO(michaeln): Instead of error'ing immediately, send the net + // response to our consumer, just don't cache it? + return; + } + WriteHeadersToCache(); +} + +void ServiceWorkerWriteToCacheJob::OnReadCompleted( + net::URLRequest* request, + int bytes_read) { + DCHECK_EQ(net_request_, request); + if (!request->status().is_success()) { + AsyncNotifyDoneHelper(request->status()); + return; + } + if (bytes_read > 0) { + WriteDataToCache(bytes_read); + return; + } + // We're done with all. + AsyncNotifyDoneHelper(request->status()); + return; +} + +void ServiceWorkerWriteToCacheJob::AsyncNotifyDoneHelper( + const net::URLRequestStatus& status) { + DCHECK(!status.is_io_pending()); + version_->script_cache_map()->NotifyFinishedCaching( + url_, status.is_success()); + did_notify_finished_ = true; + SetStatus(status); + NotifyDone(status); +} + +} // namespace content diff --git a/chromium/content/browser/service_worker/service_worker_write_to_cache_job.h b/chromium/content/browser/service_worker/service_worker_write_to_cache_job.h new file mode 100644 index 00000000000..040df41b4c6 --- /dev/null +++ b/chromium/content/browser/service_worker/service_worker_write_to_cache_job.h @@ -0,0 +1,117 @@ +// Copyright 2014 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 CONTENT_BROWSER_SERVICE_WORKER_SERVICE_WORKER_WRITE_TO_CACHE_JOB_H_ +#define CONTENT_BROWSER_SERVICE_WORKER_SERVICE_WORKER_WRITE_TO_CACHE_JOB_H_ + +#include "base/memory/weak_ptr.h" +#include "content/browser/service_worker/service_worker_disk_cache.h" +#include "content/browser/service_worker/service_worker_version.h" +#include "content/common/content_export.h" +#include "content/common/service_worker/service_worker_status_code.h" +#include "content/common/service_worker/service_worker_types.h" +#include "net/url_request/url_request.h" +#include "net/url_request/url_request_job.h" + +namespace content { + +class ServiceWorkerContextCore; +class ServiceWorkerResponseWriter; +class ServiceWorkerVersions; + +// A URLRequestJob derivative used to cache the main script +// and its imports during the initial install of a new version. +// Another separate URLRequest is started which will perform +// a network fetch. The response produced for that separate +// request is written to the service worker script cache and piped +// to the consumer of the ServiceWorkerWriteToCacheJob for delivery +// to the renderer process housing the worker. +class CONTENT_EXPORT ServiceWorkerWriteToCacheJob + : public net::URLRequestJob, + public net::URLRequest::Delegate { + public: + ServiceWorkerWriteToCacheJob( + net::URLRequest* request, + net::NetworkDelegate* network_delegate, + base::WeakPtr<ServiceWorkerContextCore> context, + ServiceWorkerVersion* version, + int64 response_id); + + private: + virtual ~ServiceWorkerWriteToCacheJob(); + + // net::URLRequestJob overrides + virtual void Start() OVERRIDE; + virtual void Kill() OVERRIDE; + virtual net::LoadState GetLoadState() const OVERRIDE; + virtual bool GetCharset(std::string* charset) OVERRIDE; + virtual bool GetMimeType(std::string* mime_type) const OVERRIDE; + virtual void GetResponseInfo(net::HttpResponseInfo* info) OVERRIDE; + virtual int GetResponseCode() const OVERRIDE; + virtual void SetExtraRequestHeaders( + const net::HttpRequestHeaders& headers) OVERRIDE; + virtual bool ReadRawData(net::IOBuffer* buf, + int buf_size, + int *bytes_read) OVERRIDE; + + const net::HttpResponseInfo* http_info() const; + + // Methods to drive the net request forward and + // write data to the disk cache. + void InitNetRequest(); + void StartNetRequest(); + net::URLRequestStatus ReadNetData( + net::IOBuffer* buf, + int buf_size, + int *bytes_read); + void WriteHeadersToCache(); + void OnWriteHeadersComplete(int result); + void WriteDataToCache(int bytes_to_write); + void OnWriteDataComplete(int result); + + // net::URLRequest::Delegate overrides that observe the net request. + virtual void OnReceivedRedirect( + net::URLRequest* request, + const GURL& new_url, + bool* defer_redirect) OVERRIDE; + virtual void OnAuthRequired( + net::URLRequest* request, + net::AuthChallengeInfo* auth_info) OVERRIDE; + virtual void OnCertificateRequested( + net::URLRequest* request, + net::SSLCertRequestInfo* cert_request_info) OVERRIDE; + virtual void OnSSLCertificateError( + net::URLRequest* request, + const net::SSLInfo& ssl_info, + bool fatal) OVERRIDE; + virtual void OnBeforeNetworkStart( + net::URLRequest* request, + bool* defer) OVERRIDE; + virtual void OnResponseStarted(net::URLRequest* request) OVERRIDE; + virtual void OnReadCompleted( + net::URLRequest* request, + int bytes_read) OVERRIDE; + + void AsyncNotifyDoneHelper(const net::URLRequestStatus& status); + + scoped_refptr<net::IOBuffer> io_buffer_; + scoped_refptr<HttpResponseInfoIOBuffer> info_buffer_; + base::WeakPtr<ServiceWorkerContextCore> context_; + GURL url_; + int64 response_id_; + scoped_ptr<net::URLRequest> net_request_; + scoped_ptr<net::HttpResponseInfo> http_info_; + scoped_ptr<ServiceWorkerResponseWriter> writer_; + scoped_refptr<ServiceWorkerVersion> version_; + bool has_been_killed_; + bool did_notify_started_; + bool did_notify_finished_; + base::WeakPtrFactory<ServiceWorkerWriteToCacheJob> weak_factory_; + + DISALLOW_COPY_AND_ASSIGN(ServiceWorkerWriteToCacheJob); +}; + +} // namespace content + +#endif // CONTENT_BROWSER_SERVICE_WORKER_SERVICE_WORKER_WRITE_TO_CACHE_JOB_H_ |