gecko-dev/dom/serviceworkers/ServiceWorkerRegistration.cpp

558 строки
17 KiB
C++

/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "ServiceWorkerRegistration.h"
#include "mozilla/dom/DOMMozPromiseRequestHolder.h"
#include "mozilla/dom/Notification.h"
#include "mozilla/dom/Promise.h"
#include "mozilla/dom/PushManager.h"
#include "mozilla/dom/ServiceWorker.h"
#include "mozilla/dom/ServiceWorkerRegistrationBinding.h"
#include "mozilla/dom/WorkerPrivate.h"
#include "nsCycleCollectionParticipant.h"
#include "nsISupportsPrimitives.h"
#include "nsPIDOMWindow.h"
#include "RemoteServiceWorkerRegistrationImpl.h"
#include "ServiceWorkerRegistrationImpl.h"
namespace mozilla {
namespace dom {
NS_IMPL_CYCLE_COLLECTION_INHERITED(ServiceWorkerRegistration,
DOMEventTargetHelper,
mInstallingWorker,
mWaitingWorker,
mActiveWorker,
mPushManager);
NS_IMPL_ADDREF_INHERITED(ServiceWorkerRegistration, DOMEventTargetHelper)
NS_IMPL_RELEASE_INHERITED(ServiceWorkerRegistration, DOMEventTargetHelper)
NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(ServiceWorkerRegistration)
NS_INTERFACE_MAP_ENTRY(ServiceWorkerRegistration)
NS_INTERFACE_MAP_END_INHERITING(DOMEventTargetHelper)
namespace {
const uint64_t kInvalidUpdateFoundId = 0;
} // anonymous namespace
ServiceWorkerRegistration::ServiceWorkerRegistration(nsIGlobalObject* aGlobal,
const ServiceWorkerRegistrationDescriptor& aDescriptor,
ServiceWorkerRegistration::Inner* aInner)
: DOMEventTargetHelper(aGlobal)
, mDescriptor(aDescriptor)
, mInner(aInner)
, mScheduledUpdateFoundId(kInvalidUpdateFoundId)
, mDispatchedUpdateFoundId(kInvalidUpdateFoundId)
, mPendingUpdatePromises(0)
{
MOZ_DIAGNOSTIC_ASSERT(mInner);
KeepAliveIfHasListenersFor(NS_LITERAL_STRING("updatefound"));
UpdateState(mDescriptor);
mInner->SetServiceWorkerRegistration(this);
}
ServiceWorkerRegistration::~ServiceWorkerRegistration()
{
mInner->ClearServiceWorkerRegistration(this);
}
JSObject*
ServiceWorkerRegistration::WrapObject(JSContext* aCx,
JS::Handle<JSObject*> aGivenProto)
{
return ServiceWorkerRegistration_Binding::Wrap(aCx, this, aGivenProto);
}
/* static */ already_AddRefed<ServiceWorkerRegistration>
ServiceWorkerRegistration::CreateForMainThread(nsPIDOMWindowInner* aWindow,
const ServiceWorkerRegistrationDescriptor& aDescriptor)
{
MOZ_ASSERT(aWindow);
MOZ_ASSERT(NS_IsMainThread());
RefPtr<Inner> inner;
if (ServiceWorkerParentInterceptEnabled()) {
inner = new RemoteServiceWorkerRegistrationImpl(aDescriptor);
} else {
inner = new ServiceWorkerRegistrationMainThread(aDescriptor);
}
NS_ENSURE_TRUE(inner, nullptr);
RefPtr<ServiceWorkerRegistration> registration =
new ServiceWorkerRegistration(aWindow->AsGlobal(), aDescriptor, inner);
return registration.forget();
}
/* static */ already_AddRefed<ServiceWorkerRegistration>
ServiceWorkerRegistration::CreateForWorker(WorkerPrivate* aWorkerPrivate,
nsIGlobalObject* aGlobal,
const ServiceWorkerRegistrationDescriptor& aDescriptor)
{
MOZ_DIAGNOSTIC_ASSERT(aWorkerPrivate);
MOZ_DIAGNOSTIC_ASSERT(aGlobal);
aWorkerPrivate->AssertIsOnWorkerThread();
RefPtr<Inner> inner;
if (ServiceWorkerParentInterceptEnabled()) {
inner = new RemoteServiceWorkerRegistrationImpl(aDescriptor);
} else {
inner = new ServiceWorkerRegistrationWorkerThread(aDescriptor);
}
NS_ENSURE_TRUE(inner, nullptr);
RefPtr<ServiceWorkerRegistration> registration =
new ServiceWorkerRegistration(aGlobal, aDescriptor, inner);
return registration.forget();
}
void
ServiceWorkerRegistration::DisconnectFromOwner()
{
DOMEventTargetHelper::DisconnectFromOwner();
}
void
ServiceWorkerRegistration::RegistrationRemoved()
{
// Its possible that the registration will fail to install and be
// immediately removed. In that case we may never receive the
// UpdateState() call if the actor was too slow to connect, etc.
// Ensure that we force all our known actors to redundant so that
// the appropriate statechange events are fired. If we got the
// UpdateState() already then this will be a no-op.
UpdateStateInternal(Maybe<ServiceWorkerDescriptor>(),
Maybe<ServiceWorkerDescriptor>(),
Maybe<ServiceWorkerDescriptor>());
// Our underlying registration was removed from SWM, so we
// will never get an updatefound event again. We can let
// the object GC if content is not holding it alive.
IgnoreKeepAliveIfHasListenersFor(NS_LITERAL_STRING("updatefound"));
}
already_AddRefed<ServiceWorker>
ServiceWorkerRegistration::GetInstalling() const
{
RefPtr<ServiceWorker> ref = mInstallingWorker;
return ref.forget();
}
already_AddRefed<ServiceWorker>
ServiceWorkerRegistration::GetWaiting() const
{
RefPtr<ServiceWorker> ref = mWaitingWorker;
return ref.forget();
}
already_AddRefed<ServiceWorker>
ServiceWorkerRegistration::GetActive() const
{
RefPtr<ServiceWorker> ref = mActiveWorker;
return ref.forget();
}
void
ServiceWorkerRegistration::UpdateState(const ServiceWorkerRegistrationDescriptor& aDescriptor)
{
MOZ_DIAGNOSTIC_ASSERT(MatchesDescriptor(aDescriptor));
mDescriptor = aDescriptor;
UpdateStateInternal(aDescriptor.GetInstalling(),
aDescriptor.GetWaiting(),
aDescriptor.GetActive());
nsTArray<UniquePtr<VersionCallback>> callbackList;
mVersionCallbackList.SwapElements(callbackList);
for (auto& cb : callbackList) {
if (cb->mVersion > mDescriptor.Version()) {
mVersionCallbackList.AppendElement(std::move(cb));
continue;
}
cb->mFunc(cb->mVersion == mDescriptor.Version());
}
}
bool
ServiceWorkerRegistration::MatchesDescriptor(const ServiceWorkerRegistrationDescriptor& aDescriptor) const
{
return aDescriptor.Id() == mDescriptor.Id() &&
aDescriptor.PrincipalInfo() == mDescriptor.PrincipalInfo() &&
aDescriptor.Scope() == mDescriptor.Scope();
}
void
ServiceWorkerRegistration::GetScope(nsAString& aScope) const
{
CopyUTF8toUTF16(mDescriptor.Scope(), aScope);
}
ServiceWorkerUpdateViaCache
ServiceWorkerRegistration::GetUpdateViaCache(ErrorResult& aRv) const
{
return mDescriptor.UpdateViaCache();
}
already_AddRefed<Promise>
ServiceWorkerRegistration::Update(ErrorResult& aRv)
{
if (!mInner) {
aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
return nullptr;
}
nsIGlobalObject* global = GetParentObject();
if (!global) {
aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
return nullptr;
}
RefPtr<Promise> outer = Promise::Create(global, aRv);
if (NS_WARN_IF(aRv.Failed())) {
return nullptr;
}
RefPtr<ServiceWorkerRegistration> self = this;
mPendingUpdatePromises += 1;
mInner->Update(
[outer, self](const ServiceWorkerRegistrationDescriptor& aDesc) {
auto scopeExit = MakeScopeExit([&] { self->UpdatePromiseSettled(); });
nsIGlobalObject* global = self->GetParentObject();
MOZ_DIAGNOSTIC_ASSERT(global);
RefPtr<ServiceWorkerRegistration> ref =
global->GetOrCreateServiceWorkerRegistration(aDesc);
if (!ref) {
outer->MaybeReject(NS_ERROR_DOM_INVALID_STATE_ERR);
return;
}
outer->MaybeResolve(ref);
}, [outer, self] (ErrorResult& aRv) {
auto scopeExit = MakeScopeExit([&] { self->UpdatePromiseSettled(); });
outer->MaybeReject(aRv);
});
return outer.forget();
}
already_AddRefed<Promise>
ServiceWorkerRegistration::Unregister(ErrorResult& aRv)
{
nsIGlobalObject* global = GetParentObject();
if (NS_WARN_IF(!global)) {
aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
return nullptr;
}
RefPtr<Promise> outer = Promise::Create(global, aRv);
if (NS_WARN_IF(aRv.Failed())) {
return nullptr;
}
if (!mInner) {
outer->MaybeResolve(false);
return outer.forget();
}
mInner->Unregister(
[outer] (bool aSuccess) {
outer->MaybeResolve(aSuccess);
}, [outer] (ErrorResult& aRv) {
// register() should be resilient and resolve false instead
// of rejecting in most cases.
outer->MaybeResolve(false);
});
return outer.forget();
}
already_AddRefed<PushManager>
ServiceWorkerRegistration::GetPushManager(JSContext* aCx, ErrorResult& aRv)
{
if (!mPushManager) {
nsCOMPtr<nsIGlobalObject> globalObject = GetParentObject();
if (!globalObject) {
aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
return nullptr;
}
GlobalObject global(aCx, globalObject->GetGlobalJSObject());
mPushManager =
PushManager::Constructor(global,
NS_ConvertUTF8toUTF16(mDescriptor.Scope()),
aRv);
if (aRv.Failed()) {
return nullptr;
}
}
RefPtr<PushManager> ret = mPushManager;
return ret.forget();
}
already_AddRefed<Promise>
ServiceWorkerRegistration::ShowNotification(JSContext* aCx,
const nsAString& aTitle,
const NotificationOptions& aOptions,
ErrorResult& aRv)
{
nsIGlobalObject* global = GetParentObject();
if (!global) {
aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
return nullptr;
}
NS_ConvertUTF8toUTF16 scope(mDescriptor.Scope());
// Until we ship ServiceWorker objects on worker threads the active
// worker will always be nullptr. So limit this check to main
// thread for now.
if (mDescriptor.GetActive().isNothing() && NS_IsMainThread()) {
aRv.ThrowTypeError<MSG_NO_ACTIVE_WORKER>(scope);
return nullptr;
}
RefPtr<Promise> p =
Notification::ShowPersistentNotification(aCx, global, scope,
aTitle, aOptions, aRv);
if (NS_WARN_IF(aRv.Failed())) {
return nullptr;
}
return p.forget();
}
already_AddRefed<Promise>
ServiceWorkerRegistration::GetNotifications(const GetNotificationOptions& aOptions,
ErrorResult& aRv)
{
nsIGlobalObject* global = GetParentObject();
if (!global) {
aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
return nullptr;
}
NS_ConvertUTF8toUTF16 scope(mDescriptor.Scope());
if (NS_IsMainThread()) {
nsCOMPtr<nsPIDOMWindowInner> window = do_QueryInterface(global);
if (NS_WARN_IF(!window)) {
aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
return nullptr;
}
return Notification::Get(window, aOptions, scope, aRv);
}
WorkerPrivate* worker = GetCurrentThreadWorkerPrivate();
worker->AssertIsOnWorkerThread();
return Notification::WorkerGet(worker, aOptions, scope, aRv);
}
const ServiceWorkerRegistrationDescriptor&
ServiceWorkerRegistration::Descriptor() const
{
return mDescriptor;
}
void
ServiceWorkerRegistration::WhenVersionReached(uint64_t aVersion,
ServiceWorkerBoolCallback&& aCallback)
{
if (aVersion <= mDescriptor.Version()) {
aCallback(aVersion == mDescriptor.Version());
return;
}
mVersionCallbackList.AppendElement(
MakeUnique<VersionCallback>(aVersion, std::move(aCallback)));
}
void
ServiceWorkerRegistration::MaybeScheduleUpdateFound(const Maybe<ServiceWorkerDescriptor>& aInstallingDescriptor)
{
uint64_t newId = aInstallingDescriptor.isSome()
? aInstallingDescriptor.ref().Id()
: kInvalidUpdateFoundId;
if (mScheduledUpdateFoundId != kInvalidUpdateFoundId) {
if (mScheduledUpdateFoundId == newId) {
return;
}
MaybeDispatchUpdateFound();
MOZ_DIAGNOSTIC_ASSERT(mScheduledUpdateFoundId == kInvalidUpdateFoundId);
}
bool updateFound = newId != kInvalidUpdateFoundId &&
mDispatchedUpdateFoundId != newId;
if (!updateFound) {
return;
}
mScheduledUpdateFoundId = newId;
if (mPendingUpdatePromises > 0) {
return;
}
nsIGlobalObject* global = GetParentObject();
NS_ENSURE_TRUE_VOID(global);
nsCOMPtr<nsIRunnable> r = NewCancelableRunnableMethod(
"ServiceWorkerRegistration::MaybeDispatchUpdateFound",
this,
&ServiceWorkerRegistration::MaybeDispatchUpdateFound);
Unused << global->EventTargetFor(TaskCategory::Other)->Dispatch(
r.forget(), NS_DISPATCH_NORMAL);
}
void
ServiceWorkerRegistration::MaybeDispatchUpdateFound()
{
uint64_t scheduledId = mScheduledUpdateFoundId;
mScheduledUpdateFoundId = kInvalidUpdateFoundId;
if (scheduledId == kInvalidUpdateFoundId ||
scheduledId == mDispatchedUpdateFoundId) {
return;
}
mDispatchedUpdateFoundId = scheduledId;
DispatchTrustedEvent(NS_LITERAL_STRING("updatefound"));
}
void
ServiceWorkerRegistration::UpdatePromiseSettled()
{
MOZ_DIAGNOSTIC_ASSERT(mPendingUpdatePromises > 0);
mPendingUpdatePromises -= 1;
if (mPendingUpdatePromises > 0 ||
mScheduledUpdateFoundId == kInvalidUpdateFoundId) {
return;
}
nsIGlobalObject* global = GetParentObject();
NS_ENSURE_TRUE_VOID(global);
nsCOMPtr<nsIRunnable> r = NewCancelableRunnableMethod(
"ServiceWorkerRegistration::MaybeDispatchUpdateFound",
this,
&ServiceWorkerRegistration::MaybeDispatchUpdateFound);
Unused << global->EventTargetFor(TaskCategory::Other)->Dispatch(
r.forget(), NS_DISPATCH_NORMAL);
}
void
ServiceWorkerRegistration::UpdateStateInternal(const Maybe<ServiceWorkerDescriptor>& aInstalling,
const Maybe<ServiceWorkerDescriptor>& aWaiting,
const Maybe<ServiceWorkerDescriptor>& aActive)
{
// Do this immediately as it may flush an already pending updatefound
// event. In that case we want to fire the pending event before
// modifying any of the registration properties.
MaybeScheduleUpdateFound(aInstalling);
// Move the currently exposed workers into a separate list
// of "old" workers. We will then potentially add them
// back to the registration properties below based on the
// given descriptor. Any that are not restored will need
// to be moved to the redundant state.
AutoTArray<RefPtr<ServiceWorker>, 3> oldWorkerList({
mInstallingWorker.forget(),
mWaitingWorker.forget(),
mActiveWorker.forget(),
});
// Its important that all state changes are actually applied before
// dispatching any statechange events. Each ServiceWorker object
// should be in the correct state and the ServiceWorkerRegistration
// properties need to be set correctly as well. To accomplish this
// we use a ScopeExit to dispatch any statechange events.
auto scopeExit = MakeScopeExit([&] {
// Check to see if any of the "old" workers was completely discarded.
// Set these workers to the redundant state.
for (auto& oldWorker : oldWorkerList) {
if (!oldWorker ||
oldWorker == mInstallingWorker ||
oldWorker == mWaitingWorker ||
oldWorker == mActiveWorker) {
continue;
}
oldWorker->SetState(ServiceWorkerState::Redundant);
}
// Check each worker to see if it needs a statechange event dispatched.
if (mInstallingWorker) {
mInstallingWorker->MaybeDispatchStateChangeEvent();
}
if (mWaitingWorker) {
mWaitingWorker->MaybeDispatchStateChangeEvent();
}
if (mActiveWorker) {
mActiveWorker->MaybeDispatchStateChangeEvent();
}
// We also check the "old" workers to see if they need a statechange
// event as well. Note, these may overlap with the known worker properties
// above, but MaybeDispatchStateChangeEvent() will ignore duplicated calls.
for (auto& oldWorker : oldWorkerList) {
if (!oldWorker) {
continue;
}
oldWorker->MaybeDispatchStateChangeEvent();
}
});
// Clear all workers if the registration has been detached from the global.
// Also, we cannot expose ServiceWorker objects on worker threads yet, so
// do the same on when off-main-thread. This main thread check should be
// removed as part of bug 1113522.
nsCOMPtr<nsIGlobalObject> global = GetParentObject();
if (!global || !NS_IsMainThread()) {
return;
}
if (aActive.isSome()) {
if ((mActiveWorker = global->GetOrCreateServiceWorker(aActive.ref()))) {
mActiveWorker->SetState(aActive.ref().State());
}
} else {
mActiveWorker = nullptr;
}
if (aWaiting.isSome()) {
if ((mWaitingWorker = global->GetOrCreateServiceWorker(aWaiting.ref()))) {
mWaitingWorker->SetState(aWaiting.ref().State());
}
} else {
mWaitingWorker = nullptr;
}
if (aInstalling.isSome()) {
if ((mInstallingWorker = global->GetOrCreateServiceWorker(aInstalling.ref()))) {
mInstallingWorker->SetState(aInstalling.ref().State());
}
} else {
mInstallingWorker = nullptr;
}
}
} // dom namespace
} // mozilla namespace