summaryrefslogtreecommitdiffstats
path: root/chromium/content/browser/service_worker
diff options
context:
space:
mode:
authorJocelyn Turcotte <jocelyn.turcotte@digia.com>2014-08-08 14:30:41 +0200
committerJocelyn Turcotte <jocelyn.turcotte@digia.com>2014-08-12 13:49:54 +0200
commitab0a50979b9eb4dfa3320eff7e187e41efedf7a9 (patch)
tree498dfb8a97ff3361a9f7486863a52bb4e26bb898 /chromium/content/browser/service_worker
parent4ce69f7403811819800e7c5ae1318b2647e778d1 (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')
-rw-r--r--chromium/content/browser/service_worker/BUILD.gn12
-rw-r--r--chromium/content/browser/service_worker/DEPS3
-rw-r--r--chromium/content/browser/service_worker/OWNERS13
-rw-r--r--chromium/content/browser/service_worker/embedded_worker_instance.cc301
-rw-r--r--chromium/content/browser/service_worker/embedded_worker_instance.h130
-rw-r--r--chromium/content/browser/service_worker/embedded_worker_instance_unittest.cc166
-rw-r--r--chromium/content/browser/service_worker/embedded_worker_registry.cc193
-rw-r--r--chromium/content/browser/service_worker/embedded_worker_registry.h61
-rw-r--r--chromium/content/browser/service_worker/embedded_worker_test_helper.cc241
-rw-r--r--chromium/content/browser/service_worker/embedded_worker_test_helper.h135
-rw-r--r--chromium/content/browser/service_worker/service_worker_browsertest.cc710
-rw-r--r--chromium/content/browser/service_worker/service_worker_context.h28
-rw-r--r--chromium/content/browser/service_worker/service_worker_context_core.cc287
-rw-r--r--chromium/content/browser/service_worker/service_worker_context_core.h140
-rw-r--r--chromium/content/browser/service_worker/service_worker_context_observer.h70
-rw-r--r--chromium/content/browser/service_worker/service_worker_context_request_handler.cc90
-rw-r--r--chromium/content/browser/service_worker/service_worker_context_request_handler.h42
-rw-r--r--chromium/content/browser/service_worker/service_worker_context_unittest.cc301
-rw-r--r--chromium/content/browser/service_worker/service_worker_context_wrapper.cc148
-rw-r--r--chromium/content/browser/service_worker/service_worker_context_wrapper.h41
-rw-r--r--chromium/content/browser/service_worker/service_worker_controllee_request_handler.cc121
-rw-r--r--chromium/content/browser/service_worker/service_worker_controllee_request_handler.h57
-rw-r--r--chromium/content/browser/service_worker/service_worker_database.cc1113
-rw-r--r--chromium/content/browser/service_worker/service_worker_database.h324
-rw-r--r--chromium/content/browser/service_worker/service_worker_database.proto31
-rw-r--r--chromium/content/browser/service_worker/service_worker_database_unittest.cc904
-rw-r--r--chromium/content/browser/service_worker/service_worker_disk_cache.cc22
-rw-r--r--chromium/content/browser/service_worker/service_worker_disk_cache.h55
-rw-r--r--chromium/content/browser/service_worker/service_worker_dispatcher_host.cc380
-rw-r--r--chromium/content/browser/service_worker/service_worker_dispatcher_host.h101
-rw-r--r--chromium/content/browser/service_worker/service_worker_dispatcher_host_unittest.cc252
-rw-r--r--chromium/content/browser/service_worker/service_worker_fetch_dispatcher.cc78
-rw-r--r--chromium/content/browser/service_worker/service_worker_fetch_dispatcher.h57
-rw-r--r--chromium/content/browser/service_worker/service_worker_handle.cc126
-rw-r--r--chromium/content/browser/service_worker/service_worker_handle.h92
-rw-r--r--chromium/content/browser/service_worker/service_worker_handle_unittest.cc114
-rw-r--r--chromium/content/browser/service_worker/service_worker_histograms.cc30
-rw-r--r--chromium/content/browser/service_worker/service_worker_histograms.h38
-rw-r--r--chromium/content/browser/service_worker/service_worker_info.cc57
-rw-r--r--chromium/content/browser/service_worker/service_worker_info.h56
-rw-r--r--chromium/content/browser/service_worker/service_worker_internals_ui.cc675
-rw-r--r--chromium/content/browser/service_worker/service_worker_internals_ui.h74
-rw-r--r--chromium/content/browser/service_worker/service_worker_job_coordinator.cc112
-rw-r--r--chromium/content/browser/service_worker/service_worker_job_coordinator.h85
-rw-r--r--chromium/content/browser/service_worker/service_worker_job_unittest.cc702
-rw-r--r--chromium/content/browser/service_worker/service_worker_process_manager.cc193
-rw-r--r--chromium/content/browser/service_worker/service_worker_process_manager.h118
-rw-r--r--chromium/content/browser/service_worker/service_worker_proto.gyp17
-rw-r--r--chromium/content/browser/service_worker/service_worker_provider_host.cc165
-rw-r--r--chromium/content/browser/service_worker/service_worker_provider_host.h101
-rw-r--r--chromium/content/browser/service_worker/service_worker_provider_host_unittest.cc283
-rw-r--r--chromium/content/browser/service_worker/service_worker_read_from_cache_job.cc191
-rw-r--r--chromium/content/browser/service_worker/service_worker_read_from_cache_job.h73
-rw-r--r--chromium/content/browser/service_worker/service_worker_register_job.cc538
-rw-r--r--chromium/content/browser/service_worker/service_worker_register_job.h177
-rw-r--r--chromium/content/browser/service_worker/service_worker_register_job_base.h37
-rw-r--r--chromium/content/browser/service_worker/service_worker_registration.cc52
-rw-r--r--chromium/content/browser/service_worker/service_worker_registration.h30
-rw-r--r--chromium/content/browser/service_worker/service_worker_registration_status.cc42
-rw-r--r--chromium/content/browser/service_worker/service_worker_registration_status.h13
-rw-r--r--chromium/content/browser/service_worker/service_worker_registration_unittest.cc63
-rw-r--r--chromium/content/browser/service_worker/service_worker_request_handler.cc111
-rw-r--r--chromium/content/browser/service_worker/service_worker_request_handler.h83
-rw-r--r--chromium/content/browser/service_worker/service_worker_script_cache_map.cc72
-rw-r--r--chromium/content/browser/service_worker/service_worker_script_cache_map.h62
-rw-r--r--chromium/content/browser/service_worker/service_worker_storage.cc1057
-rw-r--r--chromium/content/browser/service_worker/service_worker_storage.h322
-rw-r--r--chromium/content/browser/service_worker/service_worker_storage_unittest.cc899
-rw-r--r--chromium/content/browser/service_worker/service_worker_test_utils.h39
-rw-r--r--chromium/content/browser/service_worker/service_worker_unregister_job.cc90
-rw-r--r--chromium/content/browser/service_worker/service_worker_unregister_job.h64
-rw-r--r--chromium/content/browser/service_worker/service_worker_url_request_job.cc304
-rw-r--r--chromium/content/browser/service_worker/service_worker_url_request_job.h137
-rw-r--r--chromium/content/browser/service_worker/service_worker_url_request_job_unittest.cc238
-rw-r--r--chromium/content/browser/service_worker/service_worker_utils.cc65
-rw-r--r--chromium/content/browser/service_worker/service_worker_utils.h55
-rw-r--r--chromium/content/browser/service_worker/service_worker_utils_unittest.cc103
-rw-r--r--chromium/content/browser/service_worker/service_worker_version.cc627
-rw-r--r--chromium/content/browser/service_worker/service_worker_version.h280
-rw-r--r--chromium/content/browser/service_worker/service_worker_version_unittest.cc355
-rw-r--r--chromium/content/browser/service_worker/service_worker_write_to_cache_job.cc327
-rw-r--r--chromium/content/browser/service_worker/service_worker_write_to_cache_job.h117
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(&params),
+ 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(&params),
+ 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, &registration_id));
+ render_process_id_,
+ NULL,
+ MakeRegisteredCallback(&called, &registration_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, &registration_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, &registration_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, &registration_id));
+ render_process_id_,
+ NULL,
+ MakeRegisteredCallback(&called, &registration_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(), &registration);
+ 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(), &registration);
+ 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, &registration);
+ 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, &registration);
+ 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, &registrations);
+ 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, &registrations);
+ 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, &registrations));
+ 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, &registrations));
+ 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(&registrations));
+ 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(&registrations));
+ 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, &registrations));
+ 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, &param));
+ 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, &registration1));
+ scoped_refptr<ServiceWorkerRegistration> registration2;
+ storage()->FindRegistrationForDocument(
+ GURL("http://www.example.com/"),
+ SaveFoundRegistration(SERVICE_WORKER_OK, &called, &registration2));
+ 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, &registration1));
+ base::RunLoop().RunUntilIdle();
+ EXPECT_TRUE(called);
+
+ scoped_refptr<ServiceWorkerRegistration> registration2;
+ storage()->FindRegistrationForDocument(
+ GURL("http://www.example.com/two"),
+ SaveFoundRegistration(SERVICE_WORKER_OK, &called, &registration2));
+ 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, &registration1));
+ scoped_refptr<ServiceWorkerRegistration> registration2;
+ storage()->FindRegistrationForDocument(
+ GURL("http://www.example.com/two/"),
+ SaveFoundRegistration(SERVICE_WORKER_OK, &called2, &registration2));
+
+ 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, &registration));
+
+ 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, &registration));
+
+ 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, &registration));
+
+ 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, &registration));
+
+ 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, &registration_called, &registration));
+
+ 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, &registration));
+
+ 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, &registration1_called, &registration1));
+
+ 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, &registration2_called, &registration2));
+
+ 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, &registration));
+
+ 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, &registration1_called, &registration1));
+
+ bool registration2_called = false;
+ scoped_refptr<ServiceWorkerRegistration> registration2;
+ job_coordinator()->Register(
+ pattern,
+ script_url,
+ render_process_id_,
+ SaveRegistration(
+ SERVICE_WORKER_OK, &registration2_called, &registration2));
+
+ 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, &registration));
+
+ 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, &registration));
+
+ 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,
+ &registration_called1, &registration1));
+
+ bool registration_called2 = false;
+ scoped_refptr<ServiceWorkerRegistration> registration2;
+ job_coordinator()->Register(
+ pattern2,
+ script_url2,
+ render_process_id_,
+ SaveRegistration(SERVICE_WORKER_ERROR_ABORT,
+ &registration_called2, &registration2));
+
+ 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, &registration1));
+
+ bool find_called2 = false;
+ storage()->FindRegistrationForPattern(
+ pattern2,
+ SaveFoundRegistration(
+ SERVICE_WORKER_ERROR_NOT_FOUND, &find_called2, &registration2));
+
+ 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,
+ &registration_called, &registration));
+
+ 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, &registration));
+
+ 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, &registrations);
+ 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, &registrations);
+ 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, &registrations);
+ 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, &registration1));
- scoped_refptr<ServiceWorkerRegistration> registration2;
- storage_->FindRegistrationForDocument(
- GURL("http://www.example.com/"),
- SaveFoundRegistration(true, REGISTRATION_OK, &called, &registration2));
-
- 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, &registration1));
+ 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, &registration2));
- 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, &registration1));
- scoped_refptr<ServiceWorkerRegistration> registration2;
- storage_->FindRegistrationForDocument(
- GURL("http://www.example.com/two/"),
- SaveFoundRegistration(true, REGISTRATION_OK, &called2, &registration2));
-
- 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, &registration));
-
- 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, &registration));
-
- 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, &registration));
-
- 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_