summaryrefslogtreecommitdiffstats
path: root/content/browser/service_worker
diff options
context:
space:
mode:
authoralecflett@chromium.org <alecflett@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2013-12-08 06:58:01 +0000
committeralecflett@chromium.org <alecflett@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2013-12-08 06:58:01 +0000
commit8f2eae4b19a459dcbe374d11fcc8a7f79ba2725f (patch)
tree8bceef3d3dbebd54201a383293de376eaf8e7f31 /content/browser/service_worker
parent2229d4250c2bf133ab1b59a1dfc5f66cddf9c72c (diff)
downloadchromium_src-8f2eae4b19a459dcbe374d11fcc8a7f79ba2725f.zip
chromium_src-8f2eae4b19a459dcbe374d11fcc8a7f79ba2725f.tar.gz
chromium_src-8f2eae4b19a459dcbe374d11fcc8a7f79ba2725f.tar.bz2
Implement memory-persistent registration
This simply provides an in-memory registration in ServiceWorkerContext. It provides basic registration/unregistration, adding stub ServiceWorkerRegistry and ServiceWorkerVersion classes. BUG=285976 TBR=jam@chromium.org Review URL: https://codereview.chromium.org/62203007 git-svn-id: svn://svn.chromium.org/chrome/trunk/src@239380 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'content/browser/service_worker')
-rw-r--r--content/browser/service_worker/service_worker_context.h6
-rw-r--r--content/browser/service_worker/service_worker_context_core.cc71
-rw-r--r--content/browser/service_worker/service_worker_context_core.h41
-rw-r--r--content/browser/service_worker/service_worker_context_unittest.cc168
-rw-r--r--content/browser/service_worker/service_worker_dispatcher_host.cc11
-rw-r--r--content/browser/service_worker/service_worker_dispatcher_host.h4
-rw-r--r--content/browser/service_worker/service_worker_dispatcher_host_unittest.cc2
-rw-r--r--content/browser/service_worker/service_worker_register_job.cc118
-rw-r--r--content/browser/service_worker/service_worker_register_job.h79
-rw-r--r--content/browser/service_worker/service_worker_registration_status.h21
-rw-r--r--content/browser/service_worker/service_worker_storage.cc190
-rw-r--r--content/browser/service_worker/service_worker_storage.h102
-rw-r--r--content/browser/service_worker/service_worker_storage_unittest.cc354
13 files changed, 1140 insertions, 27 deletions
diff --git a/content/browser/service_worker/service_worker_context.h b/content/browser/service_worker/service_worker_context.h
index ce8bad9..d9f51f3 100644
--- a/content/browser/service_worker/service_worker_context.h
+++ b/content/browser/service_worker/service_worker_context.h
@@ -5,18 +5,22 @@
#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
diff --git a/content/browser/service_worker/service_worker_context_core.cc b/content/browser/service_worker/service_worker_context_core.cc
index 22ae500..19b6c7f 100644
--- a/content/browser/service_worker/service_worker_context_core.cc
+++ b/content/browser/service_worker/service_worker_context_core.cc
@@ -6,29 +6,23 @@
#include "base/command_line.h"
#include "base/files/file_path.h"
+#include "base/strings/string_util.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 "webkit/browser/quota/quota_manager.h"
+#include "url/gurl.h"
namespace content {
-namespace {
-
-const base::FilePath::CharType kServiceWorkerDirectory[] =
- FILE_PATH_LITERAL("ServiceWorker");
-
-} // namespace
-
ServiceWorkerContextCore::ServiceWorkerContextCore(
- const base::FilePath& user_data_directory,
+ const base::FilePath& path,
quota::QuotaManagerProxy* quota_manager_proxy)
- : quota_manager_proxy_(quota_manager_proxy) {
- if (!user_data_directory.empty())
- path_ = user_data_directory.Append(kServiceWorkerDirectory);
-}
+ : storage_(new ServiceWorkerStorage(path, quota_manager_proxy)) {}
-ServiceWorkerContextCore::~ServiceWorkerContextCore() {
-}
+ServiceWorkerContextCore::~ServiceWorkerContextCore() {}
ServiceWorkerProviderHost* ServiceWorkerContextCore::GetProviderHost(
int process_id, int provider_id) {
@@ -67,4 +61,51 @@ bool ServiceWorkerContextCore::IsEnabled() {
switches::kEnableServiceWorker);
}
+void ServiceWorkerContextCore::RegisterServiceWorker(
+ const GURL& pattern,
+ const GURL& script_url,
+ const RegistrationCallback& callback) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
+
+ storage_->Register(pattern,
+ script_url,
+ base::Bind(&ServiceWorkerContextCore::RegistrationComplete,
+ AsWeakPtr(),
+ callback));
+}
+
+void ServiceWorkerContextCore::UnregisterServiceWorker(
+ const GURL& pattern,
+ const UnregistrationCallback& callback) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
+
+ storage_->Unregister(
+ pattern,
+ base::Bind(&ServiceWorkerContextCore::UnregistrationComplete,
+ AsWeakPtr(),
+ callback));
+}
+
+void ServiceWorkerContextCore::RegistrationComplete(
+ const ServiceWorkerContextCore::RegistrationCallback& callback,
+ ServiceWorkerRegistrationStatus status,
+ const scoped_refptr<ServiceWorkerRegistration>& registration) {
+ if (status != REGISTRATION_OK) {
+ DCHECK(!registration);
+ callback.Run(status, -1L);
+ }
+
+ callback.Run(status, registration->id());
+}
+
+void ServiceWorkerContextCore::UnregistrationComplete(
+ const UnregistrationCallback& callback,
+ ServiceWorkerRegistrationStatus status) {
+ // Unregistering a non-existent registration is a no-op.
+ if (status == REGISTRATION_OK || status == REGISTRATION_NOT_FOUND)
+ callback.Run(REGISTRATION_OK);
+ else
+ callback.Run(status);
+}
+
} // namespace content
diff --git a/content/browser/service_worker/service_worker_context_core.h b/content/browser/service_worker/service_worker_context_core.h
index 8d6f442..854e234 100644
--- a/content/browser/service_worker/service_worker_context_core.h
+++ b/content/browser/service_worker/service_worker_context_core.h
@@ -5,13 +5,20 @@
#ifndef CONTENT_BROWSER_SERVICE_WORKER_SERVICE_WORKER_CONTEXT_CORE_H_
#define CONTENT_BROWSER_SERVICE_WORKER_SERVICE_WORKER_CONTEXT_CORE_H_
+#include <map>
+
+#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 "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"
#include "content/common/content_export.h"
+class GURL;
+
namespace base {
class FilePath;
}
@@ -22,6 +29,8 @@ class QuotaManagerProxy;
namespace content {
+class ServiceWorkerStorage;
+class ServiceWorkerRegistration;
class ServiceWorkerProviderHost;
// This class manages data associated with service workers.
@@ -33,11 +42,20 @@ class CONTENT_EXPORT ServiceWorkerContextCore
: NON_EXPORTED_BASE(
public base::SupportsWeakPtr<ServiceWorkerContextCore>) {
public:
- // Given an empty |user_data_directory|, nothing will be stored on disk.
+ typedef base::Callback<void(ServiceWorkerRegistrationStatus status,
+ int64 registration_id)> RegistrationCallback;
+ typedef base::Callback<
+ void(ServiceWorkerRegistrationStatus status)> UnregistrationCallback;
+
+ // 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();
+ ServiceWorkerStorage* storage() { return storage_.get(); }
+
// The context class owns the set of ProviderHosts.
ServiceWorkerProviderHost* GetProviderHost(int process_id, int provider_id);
void AddProviderHost(scoped_ptr<ServiceWorkerProviderHost> provider_host);
@@ -47,6 +65,15 @@ class CONTENT_EXPORT ServiceWorkerContextCore
// Checks the cmdline flag.
bool IsEnabled();
+ // The callback will be called on the IO thread.
+ void RegisterServiceWorker(const GURL& pattern,
+ const GURL& script_url,
+ const RegistrationCallback& callback);
+
+ // The callback will be called on the IO thread.
+ void UnregisterServiceWorker(const GURL& pattern,
+ const UnregistrationCallback& callback);
+
private:
typedef IDMap<ServiceWorkerProviderHost, IDMapOwnPointer> ProviderMap;
typedef IDMap<ProviderMap, IDMapOwnPointer> ProcessToProviderMap;
@@ -55,9 +82,17 @@ class CONTENT_EXPORT ServiceWorkerContextCore
return providers_.Lookup(process_id);
}
+ void RegistrationComplete(
+ const RegistrationCallback& callback,
+ ServiceWorkerRegistrationStatus status,
+ const scoped_refptr<ServiceWorkerRegistration>& registration);
+ void UnregistrationComplete(const UnregistrationCallback& callback,
+ ServiceWorkerRegistrationStatus status);
+
ProcessToProviderMap providers_;
- scoped_refptr<quota::QuotaManagerProxy> quota_manager_proxy_;
- base::FilePath path_;
+ scoped_ptr<ServiceWorkerStorage> storage_;
+
+ DISALLOW_COPY_AND_ASSIGN(ServiceWorkerContextCore);
};
} // namespace content
diff --git a/content/browser/service_worker/service_worker_context_unittest.cc b/content/browser/service_worker/service_worker_context_unittest.cc
new file mode 100644
index 0000000..e7c230f
--- /dev/null
+++ b/content/browser/service_worker/service_worker_context_unittest.cc
@@ -0,0 +1,168 @@
+// 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.
+
+#include "content/browser/service_worker/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/service_worker_context_core.h"
+#include "content/browser/service_worker/service_worker_registration.h"
+#include "content/public/test/test_browser_thread_bundle.h"
+#include "content/public/test/test_utils.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace content {
+
+namespace {
+
+void SaveResponseCallback(bool* called,
+ int64* store_result,
+ ServiceWorkerRegistrationStatus status,
+ int64 result) {
+ *called = true;
+ *store_result = result;
+}
+
+ServiceWorkerContextCore::RegistrationCallback MakeRegisteredCallback(
+ bool* called,
+ int64* store_result) {
+ return base::Bind(&SaveResponseCallback, called, store_result);
+}
+
+void CallCompletedCallback(bool* called, ServiceWorkerRegistrationStatus) {
+ *called = true;
+}
+
+ServiceWorkerContextCore::UnregistrationCallback MakeUnregisteredCallback(
+ bool* called) {
+ return base::Bind(&CallCompletedCallback, called);
+}
+
+} // namespace
+
+class ServiceWorkerContextTest : public testing::Test {
+ public:
+ ServiceWorkerContextTest()
+ : browser_thread_bundle_(TestBrowserThreadBundle::IO_MAINLOOP) {}
+
+ virtual void SetUp() OVERRIDE {
+ context_.reset(new ServiceWorkerContextCore(base::FilePath(), NULL));
+ }
+
+ virtual void TearDown() OVERRIDE { context_.reset(); }
+
+ protected:
+ TestBrowserThreadBundle browser_thread_bundle_;
+ scoped_ptr<ServiceWorkerContextCore> context_;
+};
+
+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;
+ bool called = false;
+ context_->RegisterServiceWorker(
+ GURL("http://www.example.com/*"),
+ GURL("http://www.example.com/service_worker.js"),
+ MakeRegisteredCallback(&called, &registration_id));
+
+ ASSERT_FALSE(called);
+ base::RunLoop().RunUntilIdle();
+ ASSERT_TRUE(called);
+
+ ASSERT_NE(-1L, registration_id);
+}
+
+// Make sure registrations are cleaned up when they are unregistered.
+TEST_F(ServiceWorkerContextTest, Unregister) {
+ GURL pattern("http://www.example.com/*");
+
+ bool called = false;
+ int64 registration_id = -1L;
+ context_->RegisterServiceWorker(
+ pattern,
+ GURL("http://www.example.com/service_worker.js"),
+ MakeRegisteredCallback(&called, &registration_id));
+
+ ASSERT_FALSE(called);
+ base::RunLoop().RunUntilIdle();
+ ASSERT_TRUE(called);
+
+ called = false;
+ context_->UnregisterServiceWorker(pattern, MakeUnregisteredCallback(&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(ServiceWorkerContextTest, RegisterNewScript) {
+ GURL pattern("http://www.example.com/*");
+
+ bool called = false;
+ int64 old_registration_id = -1L;
+ context_->RegisterServiceWorker(
+ pattern,
+ GURL("http://www.example.com/service_worker.js"),
+ MakeRegisteredCallback(&called, &old_registration_id));
+
+ ASSERT_FALSE(called);
+ base::RunLoop().RunUntilIdle();
+ ASSERT_TRUE(called);
+
+ called = false;
+ int64 new_registration_id = -1L;
+ context_->RegisterServiceWorker(
+ pattern,
+ GURL("http://www.example.com/service_worker_new.js"),
+ MakeRegisteredCallback(&called, &new_registration_id));
+
+ ASSERT_FALSE(called);
+ base::RunLoop().RunUntilIdle();
+ ASSERT_TRUE(called);
+
+ ASSERT_NE(old_registration_id, new_registration_id);
+}
+
+// Make sure that when registering a duplicate pattern+script_url
+// combination, that the same registration is used.
+TEST_F(ServiceWorkerContextTest, RegisterDuplicateScript) {
+ GURL pattern("http://www.example.com/*");
+ GURL script_url("http://www.example.com/service_worker.js");
+
+ bool called = false;
+ int64 old_registration_id = -1L;
+ context_->RegisterServiceWorker(
+ pattern,
+ script_url,
+ MakeRegisteredCallback(&called, &old_registration_id));
+
+ ASSERT_FALSE(called);
+ base::RunLoop().RunUntilIdle();
+ ASSERT_TRUE(called);
+
+ called = false;
+ int64 new_registration_id = -1L;
+ context_->RegisterServiceWorker(
+ pattern,
+ script_url,
+ MakeRegisteredCallback(&called, &new_registration_id));
+
+ ASSERT_FALSE(called);
+ base::RunLoop().RunUntilIdle();
+ ASSERT_TRUE(called);
+
+ ASSERT_EQ(old_registration_id, new_registration_id);
+}
+
+} // namespace content
diff --git a/content/browser/service_worker/service_worker_dispatcher_host.cc b/content/browser/service_worker/service_worker_dispatcher_host.cc
index 696c5c1..b6614fa 100644
--- a/content/browser/service_worker/service_worker_dispatcher_host.cc
+++ b/content/browser/service_worker/service_worker_dispatcher_host.cc
@@ -83,7 +83,7 @@ bool ServiceWorkerDispatcherHost::OnMessageReceived(
void ServiceWorkerDispatcherHost::OnRegisterServiceWorker(
int32 thread_id,
int32 request_id,
- const GURL& scope,
+ const GURL& pattern,
const GURL& script_url) {
if (!context_ || !context_->IsEnabled()) {
Send(new ServiceWorkerMsg_ServiceWorkerRegistrationError(
@@ -97,7 +97,7 @@ void ServiceWorkerDispatcherHost::OnRegisterServiceWorker(
// TODO(alecflett): This check is insufficient for release. Add a
// ServiceWorker-specific policy query in
// ChildProcessSecurityImpl. See http://crbug.com/311631.
- if (scope.GetOrigin() != script_url.GetOrigin()) {
+ if (pattern.GetOrigin() != script_url.GetOrigin()) {
Send(new ServiceWorkerMsg_ServiceWorkerRegistrationError(
thread_id,
request_id,
@@ -110,9 +110,10 @@ void ServiceWorkerDispatcherHost::OnRegisterServiceWorker(
thread_id, request_id, NextWorkerId()));
}
-void ServiceWorkerDispatcherHost::OnUnregisterServiceWorker(int32 thread_id,
- int32 request_id,
- const GURL& scope) {
+void ServiceWorkerDispatcherHost::OnUnregisterServiceWorker(
+ int32 thread_id,
+ int32 request_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.
diff --git a/content/browser/service_worker/service_worker_dispatcher_host.h b/content/browser/service_worker/service_worker_dispatcher_host.h
index bb0abf8..563e733 100644
--- a/content/browser/service_worker/service_worker_dispatcher_host.h
+++ b/content/browser/service_worker/service_worker_dispatcher_host.h
@@ -38,11 +38,11 @@ class CONTENT_EXPORT ServiceWorkerDispatcherHost : public BrowserMessageFilter {
// IPC Message handlers
void OnRegisterServiceWorker(int32 thread_id,
int32 request_id,
- const GURL& scope,
+ const GURL& pattern,
const GURL& script_url);
void OnUnregisterServiceWorker(int32 thread_id,
int32 request_id,
- const GURL& scope);
+ const GURL& pattern);
void OnProviderCreated(int provider_id);
void OnProviderDestroyed(int provider_id);
diff --git a/content/browser/service_worker/service_worker_dispatcher_host_unittest.cc b/content/browser/service_worker/service_worker_dispatcher_host_unittest.cc
index 2276a5a..dd50c91 100644
--- a/content/browser/service_worker/service_worker_dispatcher_host_unittest.cc
+++ b/content/browser/service_worker/service_worker_dispatcher_host_unittest.cc
@@ -1,4 +1,4 @@
-// Copyright (c) 2013 The Chromium Authors. All rights reserved.
+// 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.
diff --git a/content/browser/service_worker/service_worker_register_job.cc b/content/browser/service_worker/service_worker_register_job.cc
new file mode 100644
index 0000000..51ae40ff
--- /dev/null
+++ b/content/browser/service_worker/service_worker_register_job.cc
@@ -0,0 +1,118 @@
+// 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.
+
+#include "content/browser/service_worker/service_worker_register_job.h"
+
+#include "content/browser/service_worker/service_worker_registration.h"
+#include "content/public/browser/browser_thread.h"
+#include "url/gurl.h"
+
+namespace content {
+
+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::RegistrationCallback 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,
+ weak_factory_.GetWeakPtr()));
+
+ ServiceWorkerStorage::RegistrationCallback unregister(
+ base::Bind(&ServiceWorkerRegisterJob::UnregisterPatternAndContinue,
+ weak_factory_.GetWeakPtr(),
+ pattern,
+ GURL(),
+ finish_unregistration));
+
+ storage_->FindRegistrationForPattern(pattern, unregister);
+}
+
+void ServiceWorkerRegisterJob::RegisterPatternAndContinue(
+ const GURL& pattern,
+ const GURL& script_url,
+ const ServiceWorkerStorage::RegistrationCallback& callback,
+ ServiceWorkerRegistrationStatus previous_status) {
+ if (previous_status != REGISTRATION_OK &&
+ previous_status != REGISTRATION_NOT_FOUND) {
+ BrowserThread::PostTask(
+ BrowserThread::IO,
+ FROM_HERE,
+ base::Bind(callback,
+ previous_status,
+ scoped_refptr<ServiceWorkerRegistration>()));
+ }
+
+ // 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));
+}
+
+void ServiceWorkerRegisterJob::UnregisterPatternAndContinue(
+ const GURL& pattern,
+ const GURL& new_script_url,
+ const ServiceWorkerStorage::UnregistrationCallback& callback,
+ ServiceWorkerRegistrationStatus previous_status,
+ const scoped_refptr<ServiceWorkerRegistration>& previous_registration) {
+ DCHECK(previous_status == REGISTRATION_OK ||
+ previous_status == REGISTRATION_NOT_FOUND);
+
+ // The previous registration may not exist, which is ok.
+ if (previous_status == REGISTRATION_OK &&
+ (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);
+}
+
+} // namespace content
diff --git a/content/browser/service_worker/service_worker_register_job.h b/content/browser/service_worker/service_worker_register_job.h
new file mode 100644
index 0000000..f1e35d0
--- /dev/null
+++ b/content/browser/service_worker/service_worker_register_job.h
@@ -0,0 +1,79 @@
+// 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_REGISTER_JOB_H_
+#define CONTENT_BROWSER_SERVICE_WORKER_SERVICE_WORKER_REGISTER_JOB_H_
+
+#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"
+
+namespace content {
+
+// A ServiceWorkerRegisterJob lives only for the lifetime of a single
+// registration or unregistration.
+class ServiceWorkerRegisterJob {
+ 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);
+
+ private:
+ // These are all steps in the registration and unregistration pipeline.
+ void RegisterPatternAndContinue(
+ const GURL& pattern,
+ const GURL& script_url,
+ const ServiceWorkerStorage::RegistrationCallback& callback,
+ ServiceWorkerRegistrationStatus previous_status);
+
+ void UnregisterPatternAndContinue(
+ const GURL& pattern,
+ const GURL& script_url,
+ const ServiceWorkerStorage::UnregistrationCallback& callback,
+ 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,
+ const scoped_refptr<ServiceWorkerRegistration>& registration);
+
+ const base::WeakPtr<ServiceWorkerStorage> storage_;
+ const RegistrationCompleteCallback callback_;
+ 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/content/browser/service_worker/service_worker_registration_status.h b/content/browser/service_worker/service_worker_registration_status.h
new file mode 100644
index 0000000..82ab553
--- /dev/null
+++ b/content/browser/service_worker/service_worker_registration_status.h
@@ -0,0 +1,21 @@
+// 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_REGISTRATION_STATUS_H_
+#define CONTENT_BROWSER_SERVICE_WORKER_SERVICE_WORKER_REGISTRATION_STATUS_H_
+
+namespace content {
+
+// This enum is used to describe the final state of a ServiceWorkerRegistration.
+enum ServiceWorkerRegistrationStatus {
+ REGISTRATION_OK,
+ REGISTRATION_NOT_FOUND,
+ REGISTRATION_INSTALL_FAILED,
+ REGISTRATION_ACTIVATE_FAILED,
+ REGISTRATION_FAILED,
+};
+
+} // namespace content
+
+#endif // CONTENT_BROWSER_SERVICE_WORKER_SERVICE_WORKER_REGISTRATION_STATUS_H_
diff --git a/content/browser/service_worker/service_worker_storage.cc b/content/browser/service_worker/service_worker_storage.cc
new file mode 100644
index 0000000..dc8c7b1
--- /dev/null
+++ b/content/browser/service_worker/service_worker_storage.cc
@@ -0,0 +1,190 @@
+// 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.
+
+#include "content/browser/service_worker/service_worker_storage.h"
+
+#include <string>
+
+#include "base/strings/string_util.h"
+#include "content/browser/service_worker/service_worker_register_job.h"
+#include "content/browser/service_worker/service_worker_registration.h"
+#include "content/public/browser/browser_thread.h"
+#include "webkit/browser/quota/quota_manager.h"
+
+namespace {
+// This is temporary until we figure out how registration ids will be
+// calculated.
+int64 NextRegistrationId() {
+ static int64 worker_id = 0;
+ return worker_id++;
+}
+} // namespace
+
+namespace content {
+
+const base::FilePath::CharType kServiceWorkerDirectory[] =
+ FILE_PATH_LITERAL("ServiceWorker");
+
+ServiceWorkerStorage::ServiceWorkerStorage(
+ const base::FilePath& path,
+ quota::QuotaManagerProxy* quota_manager_proxy)
+ : quota_manager_proxy_(quota_manager_proxy), weak_factory_(this) {
+ if (!path.empty())
+ path_ = path.Append(kServiceWorkerDirectory);
+}
+
+ServiceWorkerStorage::~ServiceWorkerStorage() {
+ for (PatternToRegistrationMap::const_iterator iter =
+ registration_by_pattern_.begin();
+ iter != registration_by_pattern_.end();
+ ++iter) {
+ iter->second->Shutdown();
+ }
+ registration_by_pattern_.clear();
+}
+
+void ServiceWorkerStorage::FindRegistrationForPattern(
+ const GURL& pattern,
+ const RegistrationCallback& 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,
+ REGISTRATION_NOT_FOUND,
+ scoped_refptr<ServiceWorkerRegistration>()));
+ return;
+ }
+ BrowserThread::PostTask(BrowserThread::IO,
+ FROM_HERE,
+ base::Bind(callback, REGISTRATION_OK, match->second));
+}
+
+void ServiceWorkerStorage::FindRegistrationForDocument(
+ const GURL& document_url,
+ const RegistrationCallback& 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,
+ REGISTRATION_OK,
+ scoped_refptr<ServiceWorkerRegistration>(it->second)));
+ return;
+ }
+ }
+ BrowserThread::PostTask(
+ BrowserThread::IO,
+ FROM_HERE,
+ base::Bind(callback,
+ REGISTRATION_NOT_FOUND,
+ 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,
+ weak_factory_.GetWeakPtr(),
+ callback)));
+ job->StartRegister(pattern, script_url);
+ registration_jobs_.push_back(job.release());
+}
+
+void ServiceWorkerStorage::Unregister(const GURL& pattern,
+ const UnregistrationCallback& callback) {
+ scoped_ptr<ServiceWorkerRegisterJob> job(new ServiceWorkerRegisterJob(
+ weak_factory_.GetWeakPtr(),
+ base::Bind(&ServiceWorkerStorage::UnregisterComplete,
+ 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;
+ }
+
+ return current->second;
+}
+
+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);
+ }
+}
+
+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);
+}
+
+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;
+ }
+ }
+ NOTREACHED() << "Deleting non-existent job. ";
+}
+
+void ServiceWorkerStorage::UnregisterComplete(
+ const UnregistrationCallback& callback,
+ ServiceWorkerRegisterJob* job,
+ ServiceWorkerRegistrationStatus status,
+ ServiceWorkerRegistration* previous_registration) {
+ callback.Run(status);
+ EraseJob(job);
+}
+
+void ServiceWorkerStorage::RegisterComplete(
+ const RegistrationCallback& callback,
+ ServiceWorkerRegisterJob* job,
+ ServiceWorkerRegistrationStatus status,
+ ServiceWorkerRegistration* registration) {
+ callback.Run(status, registration);
+ EraseJob(job);
+}
+
+} // namespace content
diff --git a/content/browser/service_worker/service_worker_storage.h b/content/browser/service_worker/service_worker_storage.h
new file mode 100644
index 0000000..d700ee2
--- /dev/null
+++ b/content/browser/service_worker/service_worker_storage.h
@@ -0,0 +1,102 @@
+// 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_STORAGE_H_
+#define CONTENT_BROWSER_SERVICE_WORKER_SERVICE_WORKER_STORAGE_H_
+
+#include <map>
+
+#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 "content/common/content_export.h"
+#include "url/gurl.h"
+
+namespace quota {
+class QuotaManagerProxy;
+}
+
+namespace content {
+
+class ServiceWorkerRegistration;
+class ServiceWorkerRegisterJob;
+
+// This class provides an interface to load registration data and
+// instantiate ServiceWorkerRegistration objects. Any asynchronous
+// operations are run through instances of ServiceWorkerRegisterJob.
+class CONTENT_EXPORT ServiceWorkerStorage {
+ public:
+ ServiceWorkerStorage(const base::FilePath& path,
+ quota::QuotaManagerProxy* quota_manager_proxy);
+ ~ServiceWorkerStorage();
+
+ typedef base::Callback<void(ServiceWorkerRegistrationStatus status,
+ const scoped_refptr<ServiceWorkerRegistration>&
+ registration)> RegistrationCallback;
+ typedef base::Callback<
+ void(ServiceWorkerRegistrationStatus status)> UnregistrationCallback;
+
+ void FindRegistrationForDocument(const GURL& document_url,
+ const RegistrationCallback& callback);
+ void FindRegistrationForPattern(const GURL& pattern,
+ const RegistrationCallback& callback);
+
+ void Register(const GURL& pattern,
+ const GURL& script_url,
+ const RegistrationCallback& callback);
+
+ void Unregister(const GURL& pattern, const UnregistrationCallback& callback);
+
+ 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_;
+ base::FilePath path_;
+ base::WeakPtrFactory<ServiceWorkerStorage> weak_factory_;
+
+ DISALLOW_COPY_AND_ASSIGN(ServiceWorkerStorage);
+};
+
+} // namespace content
+
+#endif // CONTENT_BROWSER_SERVICE_WORKER_SERVICE_WORKER_STORAGE_H_
diff --git a/content/browser/service_worker/service_worker_storage_unittest.cc b/content/browser/service_worker/service_worker_storage_unittest.cc
new file mode 100644
index 0000000..e82ff3c
--- /dev/null
+++ b/content/browser/service_worker/service_worker_storage_unittest.cc
@@ -0,0 +1,354 @@
+// 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.
+
+#include "content/browser/service_worker/service_worker_storage.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/service_worker_registration.h"
+#include "content/public/test/test_browser_thread_bundle.h"
+#include "content/public/test/test_utils.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+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;
+}
+
+// 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);
+}
+
+void SaveUnregistrationCallback(ServiceWorkerRegistrationStatus expected_status,
+ bool* called,
+ ServiceWorkerRegistrationStatus status) {
+ EXPECT_EQ(expected_status, status);
+ *called = true;
+}
+
+ServiceWorkerStorage::UnregistrationCallback SaveUnregistration(
+ ServiceWorkerRegistrationStatus expected_status,
+ bool* called) {
+ *called = false;
+ return base::Bind(&SaveUnregistrationCallback, expected_status, called);
+}
+
+} // 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")));
+}
+
+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/"),
+ SaveRegistration(REGISTRATION_OK, &called, &registration1));
+ scoped_refptr<ServiceWorkerRegistration> registration2;
+ storage_->FindRegistrationForDocument(
+ GURL("http://www.example.com/"),
+ SaveRegistration(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);
+}
+
+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);
+ 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"),
+ SaveRegistration(REGISTRATION_OK, &called, &registration1));
+
+ EXPECT_FALSE(called);
+ base::RunLoop().RunUntilIdle();
+ EXPECT_TRUE(called);
+
+ scoped_refptr<ServiceWorkerRegistration> registration2;
+ storage_->FindRegistrationForDocument(
+ GURL("http://www.example.com/two"),
+ SaveRegistration(REGISTRATION_OK, &called, &registration2));
+ EXPECT_FALSE(called);
+ base::RunLoop().RunUntilIdle();
+ EXPECT_TRUE(called);
+
+ ASSERT_EQ(registration1, registration2);
+}
+
+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);
+ base::RunLoop().RunUntilIdle();
+ EXPECT_TRUE(called2);
+ EXPECT_TRUE(called1);
+
+ scoped_refptr<ServiceWorkerRegistration> registration1;
+ storage_->FindRegistrationForDocument(
+ GURL("http://www.example.com/one/"),
+ SaveRegistration(REGISTRATION_OK, &called1, &registration1));
+ scoped_refptr<ServiceWorkerRegistration> registration2;
+ storage_->FindRegistrationForDocument(
+ GURL("http://www.example.com/two/"),
+ SaveRegistration(REGISTRATION_OK, &called2, &registration2));
+
+ EXPECT_FALSE(called1);
+ EXPECT_FALSE(called2);
+ base::RunLoop().RunUntilIdle();
+ EXPECT_TRUE(called2);
+ EXPECT_TRUE(called1);
+
+ ASSERT_NE(registration1, registration2);
+}
+
+// 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,
+ SaveRegistration(REGISTRATION_NOT_FOUND, &called, &registration));
+
+ ASSERT_FALSE(called);
+ base::RunLoop().RunUntilIdle();
+ ASSERT_TRUE(called);
+
+ ASSERT_EQ(scoped_refptr<ServiceWorkerRegistration>(NULL), registration);
+}
+
+// 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/*");
+
+ bool called;
+ scoped_refptr<ServiceWorkerRegistration> old_registration;
+ storage_->Register(
+ pattern,
+ GURL("http://www.example.com/service_worker.js"),
+ 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,
+ SaveRegistration(REGISTRATION_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;
+ storage_->Register(
+ pattern,
+ GURL("http://www.example.com/service_worker_new.js"),
+ SaveRegistration(REGISTRATION_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, SaveRegistration(REGISTRATION_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(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,
+ SaveRegistration(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));
+
+ 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,
+ SaveRegistration(REGISTRATION_OK, &called, &new_registration_by_pattern));
+
+ ASSERT_FALSE(called);
+ base::RunLoop().RunUntilIdle();
+ ASSERT_TRUE(called);
+
+ ASSERT_EQ(new_registration, old_registration);
+}
+
+} // namespace content