Commit cf8cd3d1 authored by michaeln@chromium.org's avatar michaeln@chromium.org

Update installed ServiceWorkers.

The cl defines a new job type for UPDATE_JOB and modifies the ServiceWorkerRegisterJob class to be able to perform this type of job in addition to the REGISTRATION_JOB. There is much in common between the two.

An update job is scheduled to run shortly after a page is loaded thru a service worker. The job checks for a new script resource and if found, goes thru the process of installing the new service worker and possibly activating it. Generally, activation has to be deferred until the page that was loaded closes. The update job sets up a deferred activation helper for that case prior to completing w/o having performed the activation.

BUG=371671
TEST=unittests

Review URL: https://codereview.chromium.org/380093002

git-svn-id: svn://svn.chromium.org/chrome/trunk/src@284329 0039d316-1c4b-4281-b951-d872f2087c98
parent bf5fbac6
......@@ -21,6 +21,7 @@ namespace content {
EmbeddedWorkerTestHelper::EmbeddedWorkerTestHelper(int mock_render_process_id)
: wrapper_(new ServiceWorkerContextWrapper(NULL)),
next_thread_id_(0),
mock_render_process_id_(mock_render_process_id),
weak_factory_(this) {
wrapper_->InitInternal(base::FilePath(),
base::MessageLoopProxy::current(),
......@@ -57,6 +58,8 @@ bool EmbeddedWorkerTestHelper::OnMessageReceived(const IPC::Message& message) {
IPC_MESSAGE_HANDLER(EmbeddedWorkerMsg_StopWorker, OnStopWorkerStub)
IPC_MESSAGE_HANDLER(EmbeddedWorkerContextMsg_MessageToWorker,
OnMessageToWorkerStub)
IPC_MESSAGE_HANDLER(EmbeddedWorkerMsg_ResumeAfterDownload,
OnResumeAfterDownloadStub)
IPC_MESSAGE_UNHANDLED(handled = false)
IPC_END_MESSAGE_MAP()
......@@ -80,8 +83,18 @@ 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.
const GURL& script_url,
bool pause_after_download) {
if (pause_after_download) {
SimulatePausedAfterDownload(embedded_worker_id);
return;
}
SimulateWorkerScriptLoaded(embedded_worker_id);
SimulateWorkerStarted(next_thread_id_++, embedded_worker_id);
}
void EmbeddedWorkerTestHelper::OnResumeAfterDownload(int embedded_worker_id) {
SimulateWorkerScriptLoaded(embedded_worker_id);
SimulateWorkerStarted(next_thread_id_++, embedded_worker_id);
}
......@@ -138,6 +151,20 @@ void EmbeddedWorkerTestHelper::OnFetchEvent(
std::string())));
}
void EmbeddedWorkerTestHelper::SimulatePausedAfterDownload(
int embedded_worker_id) {
EmbeddedWorkerInstance* worker = registry()->GetWorker(embedded_worker_id);
ASSERT_TRUE(worker != NULL);
registry()->OnPausedAfterDownload(worker->process_id(), embedded_worker_id);
}
void EmbeddedWorkerTestHelper::SimulateWorkerScriptLoaded(
int embedded_worker_id) {
EmbeddedWorkerInstance* worker = registry()->GetWorker(embedded_worker_id);
ASSERT_TRUE(worker != NULL);
registry()->OnWorkerScriptLoaded(worker->process_id(), embedded_worker_id);
}
void EmbeddedWorkerTestHelper::SimulateWorkerStarted(
int thread_id, int embedded_worker_id) {
EmbeddedWorkerInstance* worker = registry()->GetWorker(embedded_worker_id);
......@@ -170,11 +197,23 @@ void EmbeddedWorkerTestHelper::OnStartWorkerStub(
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,
params.pause_after_download));
}
void EmbeddedWorkerTestHelper::OnResumeAfterDownloadStub(
int embedded_worker_id) {
EmbeddedWorkerInstance* worker = registry()->GetWorker(embedded_worker_id);
ASSERT_TRUE(worker != NULL);
base::MessageLoopProxy::current()->PostTask(
FROM_HERE,
base::Bind(&EmbeddedWorkerTestHelper::OnResumeAfterDownload,
weak_factory_.GetWeakPtr(),
params.embedded_worker_id,
params.service_worker_version_id,
params.scope,
params.script_url));
embedded_worker_id));
}
void EmbeddedWorkerTestHelper::OnStopWorkerStub(int embedded_worker_id) {
......
......@@ -67,6 +67,8 @@ class EmbeddedWorkerTestHelper : public IPC::Sender,
ServiceWorkerContextWrapper* context_wrapper() { return wrapper_.get(); }
void ShutdownContext();
int mock_render_process_id() const { return mock_render_process_id_;}
protected:
// Called when StartWorker, StopWorker and SendMessageToWorker message
// is sent to the embedded worker. Override if necessary. By default
......@@ -77,7 +79,9 @@ class EmbeddedWorkerTestHelper : public IPC::Sender,
virtual void OnStartWorker(int embedded_worker_id,
int64 service_worker_version_id,
const GURL& scope,
const GURL& script_url);
const GURL& script_url,
bool pause_after_download);
virtual void OnResumeAfterDownload(int embedded_worker_id);
virtual void OnStopWorker(int embedded_worker_id);
virtual bool OnMessageToWorker(int thread_id,
int embedded_worker_id,
......@@ -97,15 +101,17 @@ class EmbeddedWorkerTestHelper : public IPC::Sender,
// These functions simulate sending an EmbeddedHostMsg message to the
// browser.
void SimulatePausedAfterDownload(int embedded_worker_id);
void SimulateWorkerScriptLoaded(int embedded_worker_id);
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 OnResumeAfterDownloadStub(int embedded_worker_id);
void OnStopWorkerStub(int embedded_worker_id);
void OnMessageToWorkerStub(int thread_id,
int embedded_worker_id,
......@@ -121,6 +127,7 @@ class EmbeddedWorkerTestHelper : public IPC::Sender,
IPC::TestSink inner_sink_;
int next_thread_id_;
int mock_render_process_id_;
// Updated each time MessageToWorker message is received.
int current_embedded_worker_id_;
......
......@@ -205,6 +205,14 @@ void ServiceWorkerContextCore::UnregisterServiceWorker(
callback));
}
void ServiceWorkerContextCore::UpdateServiceWorker(
ServiceWorkerRegistration* registration) {
DCHECK_CURRENTLY_ON(BrowserThread::IO);
if (storage()->IsDisabled())
return;
job_coordinator_->Update(registration);
}
void ServiceWorkerContextCore::RegistrationComplete(
const GURL& pattern,
const ServiceWorkerContextCore::RegistrationCallback& callback,
......
......@@ -131,7 +131,6 @@ class CONTENT_EXPORT ServiceWorkerContextCore
void RemoveAllProviderHostsForProcess(int process_id);
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,
......@@ -141,10 +140,9 @@ class CONTENT_EXPORT ServiceWorkerContextCore
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);
void UpdateServiceWorker(ServiceWorkerRegistration* registration);
// This class maintains collections of live instances, this class
// does not own these object or influence their lifetime.
......
......@@ -120,18 +120,21 @@ ServiceWorkerControlleeRequestHandler::DidLookupRegistrationForMainResource(
if (status != SERVICE_WORKER_OK || !registration->active_version()) {
// No registration, or no active version for the registration is available.
job_->FallbackToNetwork();
// TODO(michaeln): If there's a waiting version, activate it instead of
// using the network.
return;
}
ServiceWorkerMetrics::CountControlledPageLoad();
// TODO(michaeln): should SetWaitingVersion() even if no active version so
// so the versions in the pipeline (.installing, .waiting) show up in the
// attribute values.
// TODO(michaeln): if 'activating' wait until it's activated before
// forwarding the request to the serviceworker.
DCHECK(registration);
provider_host_->SetControllerVersion(registration->active_version());
provider_host_->SetActiveVersion(registration->active_version());
provider_host_->SetWaitingVersion(registration->waiting_version());
provider_host_->SetInstallingVersion(registration->installing_version());
job_->ForwardToServiceWorker();
}
......
......@@ -92,6 +92,13 @@ void ServiceWorkerJobCoordinator::Unregister(
queued_job->AddCallback(callback);
}
void ServiceWorkerJobCoordinator::Update(
ServiceWorkerRegistration* registration) {
job_queues_[registration->pattern()].Push(
make_scoped_ptr<ServiceWorkerRegisterJobBase>(
new ServiceWorkerRegisterJob(context_, registration)));
}
void ServiceWorkerJobCoordinator::AbortAll() {
for (RegistrationJobMap::iterator it = job_queues_.begin();
it != job_queues_.end(); ++it) {
......
......@@ -35,6 +35,8 @@ class CONTENT_EXPORT ServiceWorkerJobCoordinator {
const GURL& pattern,
const ServiceWorkerUnregisterJob::UnregistrationCallback& callback);
void Update(ServiceWorkerRegistration* registration);
// Calls ServiceWorkerRegisterJobBase::Abort() on all jobs and removes them.
void AbortAll();
......
......@@ -30,6 +30,9 @@ ServiceWorkerProviderHost::ServiceWorkerProviderHost(
}
ServiceWorkerProviderHost::~ServiceWorkerProviderHost() {
// Clear docurl so the deferred activation of a waiting worker
// won't associate the new version with a provider being destroyed.
document_url_ = GURL();
if (controlling_version_)
controlling_version_->RemoveControllee(this);
if (active_version_)
......
......@@ -19,19 +19,20 @@ namespace content {
class ServiceWorkerJobCoordinator;
class ServiceWorkerStorage;
// Handles the registration of a Service Worker.
// Handles the initial registration of a Service Worker and the
// subsequent update of existing registrations.
//
// The registration flow includes most or all of the following,
// The control 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.
// - creating a ServiceWorkerVersion for the new version.
// - 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
// - updating storage
class ServiceWorkerRegisterJob
: public ServiceWorkerRegisterJobBase,
public EmbeddedWorkerInstance::Listener {
......@@ -41,10 +42,16 @@ class ServiceWorkerRegisterJob
ServiceWorkerVersion* version)>
RegistrationCallback;
// For registration jobs.
CONTENT_EXPORT ServiceWorkerRegisterJob(
base::WeakPtr<ServiceWorkerContextCore> context,
const GURL& pattern,
const GURL& script_url);
// For update jobs.
CONTENT_EXPORT ServiceWorkerRegisterJob(
base::WeakPtr<ServiceWorkerContextCore> context,
ServiceWorkerRegistration* registration);
virtual ~ServiceWorkerRegisterJob();
// Registers a callback to be called when the promise would resolve (whether
......@@ -60,6 +67,22 @@ class ServiceWorkerRegisterJob
virtual bool Equals(ServiceWorkerRegisterJobBase* job) OVERRIDE;
virtual RegistrationJobType GetType() OVERRIDE;
// TODO(michaeln): Use the registration listerer's OnVersionAttributesChanged
// method to replace these methods, have the host listen for changes
// to their registration.
CONTENT_EXPORT static void AssociateInstallingVersionToDocuments(
base::WeakPtr<ServiceWorkerContextCore> context,
ServiceWorkerVersion* version);
static void AssociateWaitingVersionToDocuments(
base::WeakPtr<ServiceWorkerContextCore> context,
ServiceWorkerVersion* version);
static void AssociateActiveVersionToDocuments(
base::WeakPtr<ServiceWorkerContextCore> context,
ServiceWorkerVersion* version);
CONTENT_EXPORT static void DisassociateVersionFromDocuments(
base::WeakPtr<ServiceWorkerContextCore> context,
ServiceWorkerVersion* version);
private:
FRIEND_TEST_ALL_PREFIXES(ServiceWorkerProviderHostWaitingVersionTest,
AssociateInstallingVersionToDocuments);
......@@ -97,11 +120,14 @@ class ServiceWorkerRegisterJob
void SetPhase(Phase phase);
void HandleExistingRegistrationAndContinue(
void ContinueWithRegistration(
ServiceWorkerStatusCode status,
const scoped_refptr<ServiceWorkerRegistration>& registration);
void ContinueWithUpdate(
ServiceWorkerStatusCode status,
const scoped_refptr<ServiceWorkerRegistration>& registration);
void RegisterAndContinue(ServiceWorkerStatusCode status);
void UpdateAndContinue(ServiceWorkerStatusCode status);
void UpdateAndContinue();
void OnStartWorkerFinished(ServiceWorkerStatusCode status);
void OnStoreRegistrationComplete(ServiceWorkerStatusCode status);
void InstallAndContinue();
......@@ -110,7 +136,6 @@ class ServiceWorkerRegisterJob
void OnActivateFinished(ServiceWorkerStatusCode status);
void Complete(ServiceWorkerStatusCode status);
void CompleteInternal(ServiceWorkerStatusCode status);
void ResolvePromise(ServiceWorkerStatusCode status,
ServiceWorkerRegistration* registration,
ServiceWorkerVersion* version);
......@@ -119,32 +144,15 @@ class ServiceWorkerRegisterJob
virtual void OnPausedAfterDownload() OVERRIDE;
virtual bool OnMessageReceived(const IPC::Message& message) OVERRIDE;
// Associates an installing version to documents matched with a scope of the
// version.
CONTENT_EXPORT static void AssociateInstallingVersionToDocuments(
base::WeakPtr<ServiceWorkerContextCore> context,
ServiceWorkerVersion* version);
// Associates a waiting version to documents matched with a scope of the
// version.
static void AssociateWaitingVersionToDocuments(
base::WeakPtr<ServiceWorkerContextCore> context,
ServiceWorkerVersion* version);
// Associates an active version to documents matched with a scope of the
// version.
static void AssociateActiveVersionToDocuments(
base::WeakPtr<ServiceWorkerContextCore> context,
ServiceWorkerVersion* version);
// Disassociates a version specified by |version_id| from documents.
CONTENT_EXPORT static void DisassociateVersionFromDocuments(
base::WeakPtr<ServiceWorkerContextCore> context,
ServiceWorkerVersion* version);
void OnCompareScriptResourcesComplete(
ServiceWorkerVersion* current_version,
ServiceWorkerStatusCode status,
bool are_equal);
// The ServiceWorkerContextCore object should always outlive this.
base::WeakPtr<ServiceWorkerContextCore> context_;
RegistrationJobType job_type_;
const GURL pattern_;
const GURL script_url_;
std::vector<RegistrationCallback> callbacks_;
......
......@@ -7,11 +7,13 @@
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, };
enum RegistrationJobType {
REGISTRATION_JOB,
UNREGISTRATION_JOB,
UPDATE_JOB
};
virtual ~ServiceWorkerRegisterJobBase() {}
......
......@@ -6,6 +6,8 @@
#include "content/browser/service_worker/service_worker_context_core.h"
#include "content/browser/service_worker/service_worker_info.h"
#include "content/browser/service_worker/service_worker_register_job.h"
#include "content/browser/service_worker/service_worker_utils.h"
#include "content/public/browser/browser_thread.h"
namespace content {
......@@ -62,19 +64,19 @@ ServiceWorkerRegistrationInfo ServiceWorkerRegistration::GetInfo() {
void ServiceWorkerRegistration::SetActiveVersion(
ServiceWorkerVersion* version) {
SetVersionInternal(version, &active_version_,
ChangedVersionAttributesMask::ACTIVE_VERSION);
ChangedVersionAttributesMask::ACTIVE_VERSION);
}
void ServiceWorkerRegistration::SetWaitingVersion(
ServiceWorkerVersion* version) {
SetVersionInternal(version, &waiting_version_,
ChangedVersionAttributesMask::WAITING_VERSION);
ChangedVersionAttributesMask::WAITING_VERSION);
}
void ServiceWorkerRegistration::SetInstallingVersion(
ServiceWorkerVersion* version) {
SetVersionInternal(version, &installing_version_,
ChangedVersionAttributesMask::INSTALLING_VERSION);
ChangedVersionAttributesMask::INSTALLING_VERSION);
}
void ServiceWorkerRegistration::UnsetVersion(ServiceWorkerVersion* version) {
......@@ -122,5 +124,78 @@ void ServiceWorkerRegistration::UnsetVersionInternal(
}
}
void ServiceWorkerRegistration::ActivateWaitingVersion(
const StatusCallback& completion_callback) {
DCHECK(context_);
DCHECK(waiting_version());
scoped_refptr<ServiceWorkerVersion> activating_version = waiting_version();
scoped_refptr<ServiceWorkerVersion> exiting_version = active_version();
// "4. If exitingWorker is not null,
if (exiting_version) {
DCHECK(!exiting_version->HasControllee());
// TODO(michaeln): should wait for events to be complete
// "1. Wait for exitingWorker to finish handling any in-progress requests."
// "2. Terminate exitingWorker."
exiting_version->StopWorker(
base::Bind(&ServiceWorkerUtils::NoOpStatusCallback));
// "3. Run the [[UpdateState]] algorithm passing exitingWorker and
// "redundant" as the arguments."
exiting_version->SetStatus(ServiceWorkerVersion::REDUNDANT);
}
// "5. Set serviceWorkerRegistration.activeWorker to activatingWorker."
// "6. Set serviceWorkerRegistration.waitingWorker to null."
ServiceWorkerRegisterJob::DisassociateVersionFromDocuments(
context_, activating_version);
SetActiveVersion(activating_version);
ServiceWorkerRegisterJob::AssociateActiveVersionToDocuments(
context_, activating_version);
// "7. Run the [[UpdateState]] algorithm passing registration.activeWorker and
// "activating" as arguments."
activating_version->SetStatus(ServiceWorkerVersion::ACTIVATING);
// TODO(nhiroki): "8. Fire a simple event named controllerchange..."
// "9. Queue a task to fire an event named activate..."
activating_version->DispatchActivateEvent(
base::Bind(&ServiceWorkerRegistration::OnActivateEventFinished,
this, activating_version, completion_callback));
}
void ServiceWorkerRegistration::OnActivateEventFinished(
ServiceWorkerVersion* activating_version,
const StatusCallback& completion_callback,
ServiceWorkerStatusCode status) {
if (activating_version != active_version())
return;
// 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) {
// "11. If activateFailed is true, then:..."
ServiceWorkerRegisterJob::DisassociateVersionFromDocuments(
context_, activating_version);
UnsetVersion(activating_version);
activating_version->Doom();
if (!active_version()) {
context_->storage()->DeleteRegistration(
id(), script_url().GetOrigin(),
base::Bind(&ServiceWorkerUtils::NoOpStatusCallback));
}
completion_callback.Run(status);
return;
}
// "12. Run the [[UpdateState]] algorithm passing registration.activeWorker
// and "activated" as the arguments."
activating_version->SetStatus(ServiceWorkerVersion::ACTIVATED);
if (context_) {
context_->storage()->UpdateToActiveState(
this,
base::Bind(&ServiceWorkerUtils::NoOpStatusCallback));
}
completion_callback.Run(SERVICE_WORKER_OK);
}
} // namespace content
......@@ -20,22 +20,16 @@ namespace content {
class ServiceWorkerRegistrationInfo;
class ServiceWorkerVersion;
// This class manages all persistence of service workers:
// - Registrations
// - Mapping of caches to registrations / versions
//
// This is the place where we manage simultaneous
// requests for the same registrations and caches, making sure that
// two pages that are registering the same pattern at the same time
// have their registrations coalesced rather than overwriting each
// other.
//
// This class also manages the state of the upgrade process, which
// includes managing which ServiceWorkerVersion is "active" vs "in
// waiting".
// This class represents a service worker registration. The
// scope and script url are constant for the life of the persistent
// registration. It's refcounted to facillitate multiple controllees
// being associated with the same registration. The class roughly
// corresponds to navigator.serviceWorker.registgration.
class CONTENT_EXPORT ServiceWorkerRegistration
: NON_EXPORTED_BASE(public base::RefCounted<ServiceWorkerRegistration>) {
public:
typedef base::Callback<void(ServiceWorkerStatusCode status)> StatusCallback;
class Listener {
public:
virtual void OnVersionAttributesChanged(
......@@ -70,8 +64,8 @@ class CONTENT_EXPORT ServiceWorkerRegistration
ServiceWorkerRegistrationInfo GetInfo();
// Sets the corresposding version attribute and resets the position (if any)
// left vacant (ie. by a waiting version being promoted).
// Sets the corresposding version attribute and resets the position
// (if any) left vacant (ie. by a waiting version being promoted).
// Also notifies listeners via OnVersionAttributesChanged.
void SetActiveVersion(ServiceWorkerVersion* version);
void SetWaitingVersion(ServiceWorkerVersion* version);
......@@ -82,6 +76,11 @@ class CONTENT_EXPORT ServiceWorkerRegistration
// listeners via OnVersionAttributesChanged.
void UnsetVersion(ServiceWorkerVersion* version);
// This method corresponds to the [[Activate]] algorithm described in
// the service worker specification. It's only valid to call this method
// when the registration's active version has no controllees.
void ActivateWaitingVersion(const StatusCallback& completion_callback);
private:
~ServiceWorkerRegistration();
friend class base::RefCounted<ServiceWorkerRegistration>;
......@@ -93,6 +92,10 @@ class CONTENT_EXPORT ServiceWorkerRegistration
void UnsetVersionInternal(
ServiceWorkerVersion* version,
ChangedVersionAttributesMask* mask);
void OnActivateEventFinished(
ServiceWorkerVersion* activating_version,
const StatusCallback& completion_callback,
ServiceWorkerStatusCode status);
const GURL pattern_;
const GURL script_url_;
......
......@@ -4,7 +4,6 @@
#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"
......@@ -37,6 +36,31 @@ class ServiceWorkerRegistrationTest : public testing::Test {
base::RunLoop().RunUntilIdle();
}
class RegistrationListener : public ServiceWorkerRegistration::Listener {
public:
RegistrationListener() {}
~RegistrationListener() {}
virtual void OnVersionAttributesChanged(
ServiceWorkerRegistration* registration,
ChangedVersionAttributesMask changed_mask,
const ServiceWorkerRegistrationInfo& info) OVERRIDE {
observed_registration_ = registration;
observed_changed_mask_ = changed_mask;
observed_info_ = info;
}
void Reset() {
observed_registration_ = NULL;
observed_changed_mask_ = ChangedVersionAttributesMask();
observed_info_ = ServiceWorkerRegistrationInfo();
}
scoped_refptr<ServiceWorkerRegistration> observed_registration_;
ChangedVersionAttributesMask observed_changed_mask_;
ServiceWorkerRegistrationInfo observed_info_;
};
protected:
scoped_ptr<ServiceWorkerContextCore> context_;
base::WeakPtr<ServiceWorkerContextCore> context_ptr_;
......@@ -44,4 +68,73 @@ class ServiceWorkerRegistrationTest : public testing::Test {
BrowserThreadImpl io_thread_;
};
TEST_F(ServiceWorkerRegistrationTest, SetAndUnsetVersions) {
const GURL kScope("http://www.example.not/*");
const GURL kScript("http://www.example.not/service_worker.js");
int64 kRegistrationId = 1L;
scoped_refptr<ServiceWorkerRegistration> registration =
new ServiceWorkerRegistration(
kScope,
kScript,
kRegistrationId,
context_ptr_);
const int64 version_1_id = 1L;
const int64 version_2_id = 2L;
scoped_refptr<ServiceWorkerVersion> version_1 =
new ServiceWorkerVersion(registration, version_1_id, context_ptr_);
scoped_refptr<ServiceWorkerVersion> version_2 =
new ServiceWorkerVersion(registration, version_2_id, context_ptr_);
RegistrationListener listener;
registration->AddListener(&listener);
registration->SetActiveVersion(version_1);
EXPECT_EQ(version_1, registration->active_version());
EXPECT_EQ(registration, listener.observed_registration_);
EXPECT_EQ(ChangedVersionAttributesMask::ACTIVE_VERSION,
listener.observed_changed_mask_.changed());
EXPECT_EQ(kScope, listener.observed_info_.pattern);
EXPECT_EQ(kScript, listener.observed_info_.script_url);
EXPECT_EQ(version_1_id, listener.observed_info_.active_version.version_id);
EXPECT_TRUE(listener.observed_info_.installing_version.is_null);
EXPECT_TRUE(listener.observed_info_.waiting_version.is_null);
EXPECT_TRUE(listener.observed_info_.controlling_version.is_null);
listener.Reset();
registration->SetInstallingVersion(version_2);
EXPECT_EQ(version_2, registration->installing_version());
EXPECT_EQ(ChangedVersionAttributesMask::INSTALLING_VERSION,
listener.observed_changed_mask_.changed());
EXPECT_EQ(version_1_id, listener.observed_info_.active_version.version_id);
EXPECT_EQ(version_2_id,
listener.observed_info_.installing_version.version_id);
EXPECT_TRUE(listener.observed_info_.waiting_version.is_null);
EXPECT_TRUE(listener.observed_info_.controlling_version.is_null);
listener.Reset();
registration->SetWaitingVersion(version_2);
EXPECT_EQ(version_2, registration->waiting_version());
EXPECT_FALSE(registration->installing_version());
EXPECT_TRUE(listener.observed_changed_mask_.waiting_changed());
EXPECT_TRUE(listener.observed_changed_mask_.installing_changed());
EXPECT_EQ(version_1_id, listener.observed_info_.active_version.version_id);
EXPECT_EQ(version_2_id, listener.observed_info_.waiting_version.version_id);
EXPECT_TRUE(listener.observed_info_.installing_version.is_null);
EXPECT_TRUE(listener.observed_info_.controlling_version.is_null);
listener.Reset();
registration->UnsetVersion(version_2);
EXPECT_FALSE(registration->waiting_version());
EXPECT_EQ(ChangedVersionAttributesMask::WAITING_VERSION,
listener.observed_changed_mask_.changed());
EXPECT_EQ(version_1_id, listener.observed_info_.active_version.version_id);
EXPECT_TRUE(listener.observed_info_.waiting_version.is_null);
EXPECT_TRUE(listener.observed_info_.installing_version.is_null);
EXPECT_TRUE(listener.observed_info_.controlling_version.is_null);
}
} // namespace content
......@@ -11,6 +11,7 @@
#include "base/basictypes.h"
#include "base/memory/weak_ptr.h"
#include "content/browser/service_worker/service_worker_database.h"
#include "content/common/content_export.h"
class GURL;
......@@ -21,7 +22,7 @@ class ServiceWorkerVersion;
// Class that maintains the mapping between urls and a resource id
// for a particular version's implicit script resources.
class ServiceWorkerScriptCacheMap {
class CONTENT_EXPORT ServiceWorkerScriptCacheMap {
public:
int64 Lookup(const GURL& url);
......
......@@ -21,6 +21,7 @@
#include "content/common/service_worker/service_worker_types.h"
#include "content/public/browser/browser_thread.h"
#include "net/base/completion_callback.h"
#include "net/base/io_buffer.h"
#include "net/base/net_errors.h"
#include "webkit/browser/quota/quota_manager_proxy.h"
......@@ -72,6 +73,133 @@ ServiceWorkerStatusCode DatabaseStatusToStatusCode(
}
}
class ResponseComparer : public base::RefCounted<ResponseComparer> {
public:
ResponseComparer(
base::WeakPtr<ServiceWorkerStorage> owner,
scoped_ptr<ServiceWorkerResponseReader> lhs,
scoped_ptr<ServiceWorkerResponseReader> rhs,
const ServiceWorkerStorage::CompareCallback& callback)
: owner_(owner),
completion_callback_(callback),
lhs_reader_(lhs.release()),
rhs_reader_(rhs.release()),
completion_count_(0),
previous_result_(0) {
}
void Start();
private:
friend class base::RefCounted<ResponseComparer>;
static const int kBufferSize = 16 * 1024;
~ResponseComparer() {}
void ReadInfos();
void OnReadInfoComplete(int result);
void ReadSomeData();
void OnReadDataComplete(int result);
base::WeakPtr<ServiceWorkerStorage> owner_;
ServiceWorkerStorage::CompareCallback completion_callback_;
scoped_ptr<ServiceWorkerResponseReader> lhs_reader_;
scoped_refptr<HttpResponseInfoIOBuffer> lhs_info_;
scoped_refptr<net::IOBuffer> lhs_buffer_;
scoped_ptr<ServiceWorkerResponseReader> rhs_reader_;
scoped_refptr<HttpResponseInfoIOBuffer> rhs_info_;
scoped_refptr<net::IOBuffer> rhs_buffer_;
int completion_count_;
int previous_result_;
DISALLOW_COPY_AND_ASSIGN(ResponseComparer);
};
void ResponseComparer::Start() {
lhs_buffer_ = new net::IOBuffer(kBufferSize);
lhs_info_ = new HttpResponseInfoIOBuffer();
rhs_buffer_ = new net::IOBuffer(kBufferSize);
rhs_info_ = new HttpResponseInfoIOBuffer();
ReadInfos();
}
void ResponseComparer::ReadInfos() {
lhs_reader_->ReadInfo(
lhs_info_,
base::Bind(&ResponseComparer::OnReadInfoComplete,
this));
rhs_reader_->ReadInfo(
rhs_info_,
base::Bind(&ResponseComparer::OnReadInfoComplete,
this));
}
void ResponseComparer::OnReadInfoComplete(int result) {
if (completion_callback_.is_null() || !owner_)
return;
if (result < 0) {
completion_callback_.Run(SERVICE_WORKER_ERROR_FAILED, false);
completion_callback_.Reset();
return;
}
if (++completion_count_ != 2)
return;
if (lhs_info_->response_data_size != rhs_info_->response_data_size) {
completion_callback_.Run(SERVICE_WORKER_OK, false);
return;
}
ReadSomeData();
}
void ResponseComparer::ReadSomeData() {
completion_count_ = 0;
lhs_reader_->ReadData(
lhs_buffer_,
kBufferSize,
base::Bind(&ResponseComparer::OnReadDataComplete, this));
rhs_reader_->ReadData(
rhs_buffer_,
kBufferSize,
base::Bind(&ResponseComparer::OnReadDataComplete, this));
}
void ResponseComparer::OnReadDataComplete(int result) {
if (completion_callback_.is_null() || !owner_)
return;
if (result < 0) {
completion_callback_.Run(SERVICE_WORKER_ERROR_FAILED, false);
completion_callback_.Reset();
return;
}
if (++completion_count_ != 2) {
previous_result_ = result;
return;
}
// TODO(michaeln): Probably shouldn't assume that the amounts read from
// each reader will always be the same. This would wrongly signal false
// in that case.
if (result != previous_result_) {
completion_callback_.Run(SERVICE_WORKER_OK, false);
return;
}
if (result == 0) {
completion_callback_.Run(SERVICE_WORKER_OK, true);
return;
}
int compare_result =
memcmp(lhs_buffer_->data(), rhs_buffer_->data(), result);
if (compare_result != 0) {
completion_callback_.Run(SERVICE_WORKER_OK, false);
return;
}
ReadSomeData();
}
} // namespace
ServiceWorkerStorage::InitialData::InitialData()
......@@ -391,6 +519,18 @@ void ServiceWorkerStorage::DoomUncommittedResponse(int64 id) {
StartPurgingResources(std::vector<int64>(1, id));
}
void ServiceWorkerStorage::CompareScriptResources(
int64 lhs_id, int64 rhs_id,
const CompareCallback& callback) {
DCHECK(!callback.is_null());
scoped_refptr<ResponseComparer> comparer =
new ResponseComparer(weak_factory_.GetWeakPtr(),
CreateResponseReader(lhs_id),
CreateResponseReader(rhs_id),
callback);
comparer->Start(); // It deletes itself when done.
}
void ServiceWorkerStorage::DeleteAndStartOver(const StatusCallback& callback) {
Disable();
......@@ -760,6 +900,10 @@ ServiceWorkerStorage::GetOrCreateRegistration(
version->SetStatus(data.is_active ?
ServiceWorkerVersion::ACTIVATED : ServiceWorkerVersion::INSTALLED);
version->script_cache_map()->SetResources(resources);
// TODO(michaeln): need to activate a waiting version that wasn't
// actrivated in an earlier session, maybe test for this condition
// (waitingversion and no activeversion) when navigating to a page?
}
if (version->status() == ServiceWorkerVersion::ACTIVATED)
......@@ -768,8 +912,7 @@ ServiceWorkerStorage::GetOrCreateRegistration(
registration->SetWaitingVersion(version);
else
NOTREACHED();
// TODO(michaeln): Hmmm, what if DeleteReg was invoked after
// the Find result we're returning here? NOTREACHED condition?
return registration;
}
......
......@@ -53,7 +53,7 @@ class CONTENT_EXPORT ServiceWorkerStorage
void(const std::vector<ServiceWorkerRegistrationInfo>& registrations)>
GetAllRegistrationInfosCallback;
typedef base::Callback<
void(ServiceWorkerStatusCode status, int result)>
void(ServiceWorkerStatusCode status, bool are_equal)>
CompareCallback;
virtual ~ServiceWorkerStorage();
......@@ -124,6 +124,10 @@ class CONTENT_EXPORT ServiceWorkerStorage
// purgeable list and purges it.
void DoomUncommittedResponse(int64 id);
// Compares only the response bodies.
void CompareScriptResources(int64 lhs_id, int64 rhs_id,
const CompareCallback& callback);
// Deletes the storage and starts over.
void DeleteAndStartOver(const StatusCallback& callback);
......
......@@ -83,20 +83,25 @@ void OnIOComplete(int* rv_out, int rv) {
*rv_out = rv;
}
void WriteBasicResponse(ServiceWorkerStorage* storage, int64 id) {
void OnCompareComplete(
ServiceWorkerStatusCode* status_out, bool* are_equal_out,
ServiceWorkerStatusCode status, bool are_equal) {
*status_out = status;
*are_equal_out = are_equal;
}
void WriteResponse(
ServiceWorkerStorage* storage, int64 id,
const std::string& headers,
IOBuffer* body, int length) {
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);
info->headers = new net::HttpResponseHeaders(headers);
scoped_refptr<HttpResponseInfoIOBuffer> info_buffer =
new HttpResponseInfoIOBuffer(info.release());
......@@ -106,15 +111,32 @@ void WriteBasicResponse(ServiceWorkerStorage* storage, int64 id) {
EXPECT_LT(0, rv);
rv = -1234;
writer->WriteData(body, arraysize(kHttpBody),
base::Bind(&OnIOComplete, &rv));
writer->WriteData(body, length, base::Bind(&OnIOComplete, &rv));
base::RunLoop().RunUntilIdle();
EXPECT_EQ(static_cast<int>(arraysize(kHttpBody)), rv);
EXPECT_EQ(length, rv);
}
void WriteStringResponse(
ServiceWorkerStorage* storage, int64 id,
const std::string& headers,
const std::string& body) {
scoped_refptr<IOBuffer> body_buffer(new WrappedIOBuffer(body.data()));
WriteResponse(storage, id, headers, body_buffer, body.length());
}
void WriteBasicResponse(ServiceWorkerStorage* storage, int64 id) {
scoped_ptr<ServiceWorkerResponseWriter> writer =
storage->CreateResponseWriter(id);
const char kHttpHeaders[] = "HTTP/1.0 200 HONKYDORY\0Content-Length: 5\0\0";
const char kHttpBody[] = "Hello";
std::string headers(kHttpHeaders, arraysize(kHttpHeaders));
WriteStringResponse(storage, id, headers, std::string(kHttpBody));
}
bool VerifyBasicResponse(ServiceWorkerStorage* storage, int64 id,
bool expected_positive_result) {
const char kExpectedHttpBody[] = "Hello\0";
const std::string kExpectedHttpBody("Hello");
scoped_ptr<ServiceWorkerResponseReader> reader =
storage->CreateResponseReader(id);
scoped_refptr<HttpResponseInfoIOBuffer> info_buffer =
......@@ -129,28 +151,38 @@ bool VerifyBasicResponse(ServiceWorkerStorage* storage, int64 id,
return false;
}
const int kBigEnough = 512;
scoped_refptr<net::IOBuffer> buffer = new IOBuffer(kBigEnough);
std::string received_body;
{
const int kBigEnough = 512;
scoped_refptr<net::IOBuffer> buffer = new IOBuffer(kBigEnough);
TestCompletionCallback cb;
reader->ReadData(buffer, kBigEnough, cb.callback());
int rv = cb.WaitForResult();
EXPECT_EQ(static_cast<int>(arraysize(kExpectedHttpBody)), rv);
EXPECT_EQ(static_cast<int>(kExpectedHttpBody.size()), rv);
if (rv <= 0)
return false;
received_body.assign(buffer->data(), rv);
}
bool status_match =
std::string("HONKYDORY") ==
info_buffer->http_info->headers->GetStatusText();
bool data_match =
std::string(kExpectedHttpBody) == std::string(buffer->data());
bool data_match = kExpectedHttpBody == received_body;
EXPECT_TRUE(status_match);
EXPECT_TRUE(data_match);
return status_match && data_match;
}
void WriteResponseOfSize(ServiceWorkerStorage* storage, int64 id,
char val, int size) {
const char kHttpHeaders[] = "HTTP/1.0 200 HONKYDORY\00";
std::string headers(kHttpHeaders, arraysize(kHttpHeaders));
scoped_refptr<net::IOBuffer> buffer = new net::IOBuffer(size);
memset(buffer->data(), val, size);
WriteResponse(storage, id, headers, buffer, size);
}
} // namespace
class ServiceWorkerStorageTest : public testing::Test {
......@@ -932,4 +964,66 @@ TEST_F(ServiceWorkerStorageTest, FindRegistration_LongestScopeMatch) {
EXPECT_EQ(live_registration2, found_registration);
}
TEST_F(ServiceWorkerStorageTest, CompareResources) {
// Compare two small responses containing the same data.
WriteBasicResponse(storage(), 1);
WriteBasicResponse(storage(), 2);
ServiceWorkerStatusCode status = static_cast<ServiceWorkerStatusCode>(-1);
bool are_equal = false;
storage()->CompareScriptResources(
1, 2,
base::Bind(&OnCompareComplete, &status, &are_equal));
base::RunLoop().RunUntilIdle();
EXPECT_EQ(SERVICE_WORKER_OK, status);
EXPECT_TRUE(are_equal);
// Compare two small responses with different data.
const char kHttpHeaders[] = "HTTP/1.0 200 HONKYDORY\0\0";
const char kHttpBody[] = "Goodbye";
std::string headers(kHttpHeaders, arraysize(kHttpHeaders));
WriteStringResponse(storage(), 3, headers, std::string(kHttpBody));
status = static_cast<ServiceWorkerStatusCode>(-1);
are_equal = true;
storage()->CompareScriptResources(
1, 3,
base::Bind(&OnCompareComplete, &status, &are_equal));
base::RunLoop().RunUntilIdle();
EXPECT_EQ(SERVICE_WORKER_OK, status);
EXPECT_FALSE(are_equal);
// Compare two large responses with the same data.
const int k32K = 32 * 1024;
WriteResponseOfSize(storage(), 4, 'a', k32K);
WriteResponseOfSize(storage(), 5, 'a', k32K);
status = static_cast<ServiceWorkerStatusCode>(-1);
are_equal = false;
storage()->CompareScriptResources(
4, 5,
base::Bind(&OnCompareComplete, &status, &are_equal));
base::RunLoop().RunUntilIdle();
EXPECT_EQ(SERVICE_WORKER_OK, status);
EXPECT_TRUE(are_equal);
// Compare a large and small response.
status = static_cast<ServiceWorkerStatusCode>(-1);
are_equal = true;
storage()->CompareScriptResources(
1, 5,
base::Bind(&OnCompareComplete, &status, &are_equal));
base::RunLoop().RunUntilIdle();
EXPECT_EQ(SERVICE_WORKER_OK, status);
EXPECT_FALSE(are_equal);
// Compare two large responses with different data.
WriteResponseOfSize(storage(), 6, 'b', k32K);
status = static_cast<ServiceWorkerStatusCode>(-1);
are_equal = true;
storage()->CompareScriptResources(
5, 6,
base::Bind(&OnCompareComplete, &status, &are_equal));
base::RunLoop().RunUntilIdle();
EXPECT_EQ(SERVICE_WORKER_OK, status);
EXPECT_FALSE(are_equal);
}
} // namespace content
......@@ -45,7 +45,7 @@ bool ServiceWorkerUnregisterJob::Equals(ServiceWorkerRegisterJobBase* job) {
}
RegistrationJobType ServiceWorkerUnregisterJob::GetType() {
return ServiceWorkerRegisterJobBase::UNREGISTRATION;
return UNREGISTRATION_JOB;
}
void ServiceWorkerUnregisterJob::OnRegistrationFound(
......
......@@ -217,7 +217,13 @@ void ServiceWorkerVersion::DeferScheduledUpdate() {
void ServiceWorkerVersion::StartUpdate() {
update_timer_.Stop();
// TODO(michaeln): write me
if (!context_)
return;
ServiceWorkerRegistration* registration =
context_->GetLiveRegistration(registration_id_);
if (!registration)
return;
context_->UpdateServiceWorker(registration);
}
void ServiceWorkerVersion::SendMessage(
......@@ -389,6 +395,7 @@ void ServiceWorkerVersion::RemoveControllee(
RemoveProcessFromWorker(provider_host->process_id());
if (HasControllee())
return;
FOR_EACH_OBSERVER(Listener, listeners_, OnNoControllees(this));
if (is_doomed_) {
DoomInternal();
return;
......
......@@ -64,7 +64,7 @@ class CONTENT_EXPORT ServiceWorkerVersion
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.
ACTIVATED, // Activation is finished and can run as activated.
ACTIVATED, // Activation is finished and can run as activated.
REDUNDANT, // The version is no longer running as activated, due to
// unregistration or replace.
};
......@@ -85,6 +85,11 @@ class CONTENT_EXPORT ServiceWorkerVersion
const base::string16& message,
int line_number,
const GURL& source_url) {}
// Fires when a version transitions from having a controllee to not.
virtual void OnNoControllees(ServiceWorkerVersion* version) {}
protected:
virtual ~Listener() {}
};
ServiceWorkerVersion(
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment