diff options
Diffstat (limited to 'dom/serviceworkers/ServiceWorkerRegistration.cpp')
-rw-r--r-- | dom/serviceworkers/ServiceWorkerRegistration.cpp | 548 |
1 files changed, 548 insertions, 0 deletions
diff --git a/dom/serviceworkers/ServiceWorkerRegistration.cpp b/dom/serviceworkers/ServiceWorkerRegistration.cpp new file mode 100644 index 0000000000..59a7615b75 --- /dev/null +++ b/dom/serviceworkers/ServiceWorkerRegistration.cpp @@ -0,0 +1,548 @@ +/* -*- 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/ServiceWorkerUtils.h" +#include "mozilla/dom/WorkerPrivate.h" +#include "mozilla/ScopeExit.h" +#include "nsCycleCollectionParticipant.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) { + MOZ_DIAGNOSTIC_ASSERT(mInner); + + KeepAliveIfHasListenersFor(u"updatefound"_ns); + + 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); + // This is not called from within the constructor, as it may call content code + // which can cause the deletion of the registration, so we need to keep a + // strong reference while calling it. + registration->UpdateState(aDescriptor); + + 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); + // This is not called from within the constructor, as it may call content code + // which can cause the deletion of the registration, so we need to keep a + // strong reference while calling it. + registration->UpdateState(aDescriptor); + + return registration.forget(); +} + +void ServiceWorkerRegistration::DisconnectFromOwner() { + DOMEventTargetHelper::DisconnectFromOwner(); +} + +void ServiceWorkerRegistration::RegistrationCleared() { + // 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(u"updatefound"_ns); +} + +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 = + std::move(mVersionCallbackList); + 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; + } + + // `ServiceWorker` objects are not exposed on worker threads yet, so calling + // `ServiceWorkerRegistration::Get{Installing,Waiting,Active}` won't work. + const Maybe<ServiceWorkerDescriptor> newestWorkerDescriptor = + mDescriptor.Newest(); + + // "If newestWorker is null, return a promise rejected with an + // "InvalidStateError" DOMException and abort these steps." + if (newestWorkerDescriptor.isNothing()) { + outer->MaybeReject(NS_ERROR_DOM_INVALID_STATE_ERR); + return outer.forget(); + } + + // "If the context object’s relevant settings object’s global object + // globalObject is a ServiceWorkerGlobalScope object, and globalObject’s + // associated service worker's state is "installing", return a promise + // rejected with an "InvalidStateError" DOMException and abort these steps." + if (!NS_IsMainThread()) { + WorkerPrivate* workerPrivate = GetCurrentThreadWorkerPrivate(); + MOZ_ASSERT(workerPrivate); + + if (workerPrivate->IsServiceWorker() && + (workerPrivate->GetServiceWorkerDescriptor().State() == + ServiceWorkerState::Installing)) { + outer->MaybeReject(NS_ERROR_DOM_INVALID_STATE_ERR); + return outer.forget(); + } + } + + RefPtr<ServiceWorkerRegistration> self = this; + + mInner->Update( + newestWorkerDescriptor.ref().ScriptURL(), + [outer, self](const ServiceWorkerRegistrationDescriptor& aDesc) { + nsIGlobalObject* global = self->GetParentObject(); + // It's possible this binding was detached from the global. In cases + // where we use IPC with Promise callbacks, we use + // DOMMozPromiseRequestHolder in order to auto-disconnect the promise + // that would hold these callbacks. However in bug 1466681 we changed + // this call to use (synchronous) callbacks because the use of + // MozPromise introduced an additional runnable scheduling which made + // it very difficult to maintain ordering required by the standard. + // + // If we were to delete this actor at the time of DETH detaching, we + // would not need to do this check because the IPC callback of the + // RemoteServiceWorkerRegistrationImpl lambdas would never occur. + // However, its actors currently depend on asking the parent to delete + // the actor for us. Given relaxations in the IPC lifecyle, we could + // potentially issue a direct termination, but that requires additional + // evaluation. + if (!global) { + outer->MaybeReject(NS_ERROR_DOM_INVALID_STATE_ERR); + return; + } + RefPtr<ServiceWorkerRegistration> ref = + global->GetOrCreateServiceWorkerRegistration(aDesc); + if (!ref) { + outer->MaybeReject(NS_ERROR_DOM_INVALID_STATE_ERR); + return; + } + outer->MaybeResolve(ref); + }, + [outer, self](ErrorResult&& aRv) { outer->MaybeReject(std::move(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. + aRv.SuppressException(); + 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; + } + + // 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>(mDescriptor.Scope()); + return nullptr; + } + + NS_ConvertUTF8toUTF16 scope(mDescriptor.Scope()); + + RefPtr<Promise> p = Notification::ShowPersistentNotification( + aCx, global, scope, aTitle, aOptions, mDescriptor, 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) { + // This function sets mScheduledUpdateFoundId to note when we were told about + // a new installing worker. We rely on a call to + // MaybeDispatchUpdateFoundRunnable (called indirectly from UpdateJobCallback) + // to actually fire the event. + 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; +} + +void ServiceWorkerRegistration::MaybeDispatchUpdateFoundRunnable() { + if (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::MaybeDispatchUpdateFound() { + uint64_t scheduledId = mScheduledUpdateFoundId; + mScheduledUpdateFoundId = kInvalidUpdateFoundId; + + if (scheduledId == kInvalidUpdateFoundId || + scheduledId == mDispatchedUpdateFoundId) { + return; + } + + mDispatchedUpdateFoundId = scheduledId; + DispatchTrustedEvent(u"updatefound"_ns); +} + +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({ + std::move(mInstallingWorker), + std::move(mWaitingWorker), + std::move(mActiveWorker), + }); + + // 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; + } +} + +} // namespace dom +} // namespace mozilla |