/* -*- 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 "ipc/ErrorIPCUtils.h" #include "mozilla/dom/Notification.h" #include "mozilla/dom/Promise.h" #include "mozilla/dom/PromiseWorkerProxy.h" #include "mozilla/dom/PushManagerBinding.h" #include "mozilla/dom/PushManager.h" #include "mozilla/dom/ServiceWorkerRegistrationBinding.h" #include "mozilla/Preferences.h" #include "mozilla/Services.h" #include "mozilla/Unused.h" #include "nsCycleCollectionParticipant.h" #include "nsNetUtil.h" #include "nsServiceManagerUtils.h" #include "ServiceWorker.h" #include "ServiceWorkerManager.h" #include "nsIDocument.h" #include "nsIServiceWorkerManager.h" #include "nsISupportsPrimitives.h" #include "nsPIDOMWindow.h" #include "nsContentUtils.h" #include "WorkerPrivate.h" #include "Workers.h" #include "WorkerScope.h" using namespace mozilla::dom::workers; namespace mozilla { namespace dom { /* static */ bool ServiceWorkerRegistration::Visible(JSContext* aCx, JSObject* aObj) { if (NS_IsMainThread()) { return Preferences::GetBool("dom.serviceWorkers.enabled", false); } // Otherwise check the pref via the work private helper WorkerPrivate* workerPrivate = GetWorkerPrivateFromContext(aCx); if (!workerPrivate) { return false; } return workerPrivate->ServiceWorkersEnabled(); } /* static */ bool ServiceWorkerRegistration::NotificationAPIVisible(JSContext* aCx, JSObject* aObj) { if (NS_IsMainThread()) { return Preferences::GetBool("dom.webnotifications.serviceworker.enabled", false); } // Otherwise check the pref via the work private helper WorkerPrivate* workerPrivate = GetWorkerPrivateFromContext(aCx); if (!workerPrivate) { return false; } return workerPrivate->DOMServiceWorkerNotificationEnabled(); } //////////////////////////////////////////////////// // Main Thread implementation class ServiceWorkerRegistrationMainThread final : public ServiceWorkerRegistration, public ServiceWorkerRegistrationListener { friend nsPIDOMWindowInner; public: NS_DECL_ISUPPORTS_INHERITED NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(ServiceWorkerRegistrationMainThread, ServiceWorkerRegistration) ServiceWorkerRegistrationMainThread(nsPIDOMWindowInner* aWindow, const nsAString& aScope); already_AddRefed Update(ErrorResult& aRv) override; already_AddRefed Unregister(ErrorResult& aRv) override; // Partial interface from Notification API. already_AddRefed ShowNotification(JSContext* aCx, const nsAString& aTitle, const NotificationOptions& aOptions, ErrorResult& aRv) override; already_AddRefed GetNotifications(const GetNotificationOptions& aOptions, ErrorResult& aRv) override; already_AddRefed GetInstalling() override; already_AddRefed GetWaiting() override; already_AddRefed GetActive() override; already_AddRefed GetPushManager(JSContext* aCx, ErrorResult& aRv) override; // DOMEventTargethelper void DisconnectFromOwner() override { StopListeningForEvents(); ServiceWorkerRegistration::DisconnectFromOwner(); } // ServiceWorkerRegistrationListener void UpdateFound() override; void InvalidateWorkers(WhichServiceWorker aWhichOnes) override; void TransitionWorker(WhichServiceWorker aWhichOne) override; void RegistrationRemoved() override; void GetScope(nsAString& aScope) const override { aScope = mScope; } private: ~ServiceWorkerRegistrationMainThread(); already_AddRefed GetWorkerReference(WhichServiceWorker aWhichOne); void StartListeningForEvents(); void StopListeningForEvents(); bool mListeningForEvents; // The following properties are cached here to ensure JS equality is satisfied // instead of acquiring a new worker instance from the ServiceWorkerManager // for every access. A null value is considered a cache miss. // These three may change to a new worker at any time. RefPtr mInstallingWorker; RefPtr mWaitingWorker; RefPtr mActiveWorker; RefPtr mPushManager; }; NS_IMPL_ADDREF_INHERITED(ServiceWorkerRegistrationMainThread, ServiceWorkerRegistration) NS_IMPL_RELEASE_INHERITED(ServiceWorkerRegistrationMainThread, ServiceWorkerRegistration) NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(ServiceWorkerRegistrationMainThread) NS_INTERFACE_MAP_END_INHERITING(ServiceWorkerRegistration) NS_IMPL_CYCLE_COLLECTION_INHERITED(ServiceWorkerRegistrationMainThread, ServiceWorkerRegistration, mPushManager, mInstallingWorker, mWaitingWorker, mActiveWorker); ServiceWorkerRegistrationMainThread::ServiceWorkerRegistrationMainThread(nsPIDOMWindowInner* aWindow, const nsAString& aScope) : ServiceWorkerRegistration(aWindow, aScope) , mListeningForEvents(false) { AssertIsOnMainThread(); MOZ_ASSERT(aWindow); MOZ_ASSERT(aWindow->IsInnerWindow()); StartListeningForEvents(); } ServiceWorkerRegistrationMainThread::~ServiceWorkerRegistrationMainThread() { StopListeningForEvents(); MOZ_ASSERT(!mListeningForEvents); } already_AddRefed ServiceWorkerRegistrationMainThread::GetWorkerReference(WhichServiceWorker aWhichOne) { nsCOMPtr window = GetOwner(); if (!window) { return nullptr; } nsresult rv; nsCOMPtr swm = do_GetService(SERVICEWORKERMANAGER_CONTRACTID, &rv); if (NS_WARN_IF(NS_FAILED(rv))) { return nullptr; } nsCOMPtr serviceWorker; switch(aWhichOne) { case WhichServiceWorker::INSTALLING_WORKER: rv = swm->GetInstalling(window, mScope, getter_AddRefs(serviceWorker)); break; case WhichServiceWorker::WAITING_WORKER: rv = swm->GetWaiting(window, mScope, getter_AddRefs(serviceWorker)); break; case WhichServiceWorker::ACTIVE_WORKER: rv = swm->GetActive(window, mScope, getter_AddRefs(serviceWorker)); break; default: MOZ_CRASH("Invalid enum value"); } NS_WARNING_ASSERTION( NS_SUCCEEDED(rv) || rv == NS_ERROR_DOM_NOT_FOUND_ERR, "Unexpected error getting service worker instance from " "ServiceWorkerManager"); if (NS_FAILED(rv)) { return nullptr; } RefPtr ref = static_cast(serviceWorker.get()); return ref.forget(); } // XXXnsm, maybe this can be optimized to only add when a event handler is // registered. void ServiceWorkerRegistrationMainThread::StartListeningForEvents() { AssertIsOnMainThread(); MOZ_ASSERT(!mListeningForEvents); RefPtr swm = ServiceWorkerManager::GetInstance(); if (swm) { swm->AddRegistrationEventListener(mScope, this); mListeningForEvents = true; } } void ServiceWorkerRegistrationMainThread::StopListeningForEvents() { AssertIsOnMainThread(); if (!mListeningForEvents) { return; } RefPtr swm = ServiceWorkerManager::GetInstance(); if (swm) { swm->RemoveRegistrationEventListener(mScope, this); } mListeningForEvents = false; } already_AddRefed ServiceWorkerRegistrationMainThread::GetInstalling() { AssertIsOnMainThread(); if (!mInstallingWorker) { mInstallingWorker = GetWorkerReference(WhichServiceWorker::INSTALLING_WORKER); } RefPtr ret = mInstallingWorker; return ret.forget(); } already_AddRefed ServiceWorkerRegistrationMainThread::GetWaiting() { AssertIsOnMainThread(); if (!mWaitingWorker) { mWaitingWorker = GetWorkerReference(WhichServiceWorker::WAITING_WORKER); } RefPtr ret = mWaitingWorker; return ret.forget(); } already_AddRefed ServiceWorkerRegistrationMainThread::GetActive() { AssertIsOnMainThread(); if (!mActiveWorker) { mActiveWorker = GetWorkerReference(WhichServiceWorker::ACTIVE_WORKER); } RefPtr ret = mActiveWorker; return ret.forget(); } void ServiceWorkerRegistrationMainThread::UpdateFound() { DispatchTrustedEvent(NS_LITERAL_STRING("updatefound")); } void ServiceWorkerRegistrationMainThread::TransitionWorker(WhichServiceWorker aWhichOne) { AssertIsOnMainThread(); // We assert the worker's previous state because the 'statechange' // event is dispatched in a queued runnable. if (aWhichOne == WhichServiceWorker::INSTALLING_WORKER) { MOZ_ASSERT_IF(mInstallingWorker, mInstallingWorker->State() == ServiceWorkerState::Installing); mWaitingWorker = mInstallingWorker.forget(); } else if (aWhichOne == WhichServiceWorker::WAITING_WORKER) { MOZ_ASSERT_IF(mWaitingWorker, mWaitingWorker->State() == ServiceWorkerState::Installed); mActiveWorker = mWaitingWorker.forget(); } else { MOZ_ASSERT_UNREACHABLE("Invalid transition!"); } } void ServiceWorkerRegistrationMainThread::InvalidateWorkers(WhichServiceWorker aWhichOnes) { AssertIsOnMainThread(); if (aWhichOnes & WhichServiceWorker::INSTALLING_WORKER) { mInstallingWorker = nullptr; } if (aWhichOnes & WhichServiceWorker::WAITING_WORKER) { mWaitingWorker = nullptr; } if (aWhichOnes & WhichServiceWorker::ACTIVE_WORKER) { mActiveWorker = nullptr; } } void ServiceWorkerRegistrationMainThread::RegistrationRemoved() { // If the registration is being removed completely, remove it from the // window registration hash table so that a new registration would get a new // wrapper JS object. if (nsCOMPtr window = GetOwner()) { window->InvalidateServiceWorkerRegistration(mScope); } } namespace { void UpdateInternal(nsIPrincipal* aPrincipal, const nsAString& aScope, ServiceWorkerUpdateFinishCallback* aCallback) { AssertIsOnMainThread(); MOZ_ASSERT(aPrincipal); MOZ_ASSERT(aCallback); RefPtr swm = ServiceWorkerManager::GetInstance(); if (!swm) { // browser shutdown return; } swm->Update(aPrincipal, NS_ConvertUTF16toUTF8(aScope), aCallback); } class MainThreadUpdateCallback final : public ServiceWorkerUpdateFinishCallback { RefPtr mPromise; ~MainThreadUpdateCallback() { } public: explicit MainThreadUpdateCallback(Promise* aPromise) : mPromise(aPromise) { AssertIsOnMainThread(); } void UpdateSucceeded(ServiceWorkerRegistrationInfo* aRegistration) override { mPromise->MaybeResolveWithUndefined(); } void UpdateFailed(ErrorResult& aStatus) override { mPromise->MaybeReject(aStatus); } }; class UpdateResultRunnable final : public WorkerRunnable { RefPtr mPromiseProxy; IPC::Message mSerializedErrorResult; ~UpdateResultRunnable() {} public: UpdateResultRunnable(PromiseWorkerProxy* aPromiseProxy, ErrorResult& aStatus) : WorkerRunnable(aPromiseProxy->GetWorkerPrivate()) , mPromiseProxy(aPromiseProxy) { // ErrorResult is not thread safe. Serialize it for transfer across // threads. IPC::WriteParam(&mSerializedErrorResult, aStatus); aStatus.SuppressException(); } bool WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate) override { // Deserialize the ErrorResult now that we are back in the worker // thread. ErrorResult status; PickleIterator iter = PickleIterator(mSerializedErrorResult); Unused << IPC::ReadParam(&mSerializedErrorResult, &iter, &status); Promise* promise = mPromiseProxy->WorkerPromise(); if (status.Failed()) { promise->MaybeReject(status); } else { promise->MaybeResolveWithUndefined(); } status.SuppressException(); mPromiseProxy->CleanUp(); return true; } }; class WorkerThreadUpdateCallback final : public ServiceWorkerUpdateFinishCallback { RefPtr mPromiseProxy; ~WorkerThreadUpdateCallback() { } public: explicit WorkerThreadUpdateCallback(PromiseWorkerProxy* aPromiseProxy) : mPromiseProxy(aPromiseProxy) { AssertIsOnMainThread(); } void UpdateSucceeded(ServiceWorkerRegistrationInfo* aRegistration) override { ErrorResult rv(NS_OK); Finish(rv); } void UpdateFailed(ErrorResult& aStatus) override { Finish(aStatus); } void Finish(ErrorResult& aStatus) { if (!mPromiseProxy) { return; } RefPtr proxy = mPromiseProxy.forget(); MutexAutoLock lock(proxy->Lock()); if (proxy->CleanedUp()) { return; } RefPtr r = new UpdateResultRunnable(proxy, aStatus); r->Dispatch(); } }; class UpdateRunnable final : public Runnable { public: UpdateRunnable(PromiseWorkerProxy* aPromiseProxy, const nsAString& aScope) : mPromiseProxy(aPromiseProxy) , mScope(aScope) {} NS_IMETHOD Run() override { AssertIsOnMainThread(); ErrorResult result; nsCOMPtr principal; // UpdateInternal may try to reject the promise synchronously leading // to a deadlock. { MutexAutoLock lock(mPromiseProxy->Lock()); if (mPromiseProxy->CleanedUp()) { return NS_OK; } principal = mPromiseProxy->GetWorkerPrivate()->GetPrincipal(); } MOZ_ASSERT(principal); RefPtr cb = new WorkerThreadUpdateCallback(mPromiseProxy); UpdateInternal(principal, mScope, cb); return NS_OK; } private: ~UpdateRunnable() {} RefPtr mPromiseProxy; const nsString mScope; }; class UnregisterCallback final : public nsIServiceWorkerUnregisterCallback { RefPtr mPromise; public: NS_DECL_ISUPPORTS explicit UnregisterCallback(Promise* aPromise) : mPromise(aPromise) { MOZ_ASSERT(mPromise); } NS_IMETHOD UnregisterSucceeded(bool aState) override { AssertIsOnMainThread(); mPromise->MaybeResolve(aState); return NS_OK; } NS_IMETHOD UnregisterFailed() override { AssertIsOnMainThread(); mPromise->MaybeReject(NS_ERROR_DOM_SECURITY_ERR); return NS_OK; } private: ~UnregisterCallback() { } }; NS_IMPL_ISUPPORTS(UnregisterCallback, nsIServiceWorkerUnregisterCallback) class FulfillUnregisterPromiseRunnable final : public WorkerRunnable { RefPtr mPromiseWorkerProxy; Maybe mState; public: FulfillUnregisterPromiseRunnable(PromiseWorkerProxy* aProxy, const Maybe& aState) : WorkerRunnable(aProxy->GetWorkerPrivate()) , mPromiseWorkerProxy(aProxy) , mState(aState) { AssertIsOnMainThread(); MOZ_ASSERT(mPromiseWorkerProxy); } bool WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate) override { RefPtr promise = mPromiseWorkerProxy->WorkerPromise(); if (mState.isSome()) { promise->MaybeResolve(mState.value()); } else { promise->MaybeReject(NS_ERROR_DOM_SECURITY_ERR); } mPromiseWorkerProxy->CleanUp(); return true; } }; class WorkerUnregisterCallback final : public nsIServiceWorkerUnregisterCallback { RefPtr mPromiseWorkerProxy; public: NS_DECL_ISUPPORTS explicit WorkerUnregisterCallback(PromiseWorkerProxy* aProxy) : mPromiseWorkerProxy(aProxy) { MOZ_ASSERT(aProxy); } NS_IMETHOD UnregisterSucceeded(bool aState) override { AssertIsOnMainThread(); Finish(Some(aState)); return NS_OK; } NS_IMETHOD UnregisterFailed() override { AssertIsOnMainThread(); Finish(Nothing()); return NS_OK; } private: ~WorkerUnregisterCallback() {} void Finish(const Maybe& aState) { AssertIsOnMainThread(); if (!mPromiseWorkerProxy) { return; } RefPtr proxy = mPromiseWorkerProxy.forget(); MutexAutoLock lock(proxy->Lock()); if (proxy->CleanedUp()) { return; } RefPtr r = new FulfillUnregisterPromiseRunnable(proxy, aState); r->Dispatch(); } }; NS_IMPL_ISUPPORTS(WorkerUnregisterCallback, nsIServiceWorkerUnregisterCallback); /* * If the worker goes away, we still continue to unregister, but we don't try to * resolve the worker Promise (which doesn't exist by that point). */ class StartUnregisterRunnable final : public Runnable { RefPtr mPromiseWorkerProxy; const nsString mScope; public: StartUnregisterRunnable(PromiseWorkerProxy* aProxy, const nsAString& aScope) : mPromiseWorkerProxy(aProxy) , mScope(aScope) { MOZ_ASSERT(aProxy); } NS_IMETHOD Run() override { AssertIsOnMainThread(); // XXXnsm: There is a rare chance of this failing if the worker gets // destroyed. In that case, unregister() called from a SW is no longer // guaranteed to run. We should fix this by having a main thread proxy // maintain a strongref to ServiceWorkerRegistrationInfo and use its // principal. Can that be trusted? nsCOMPtr principal; { MutexAutoLock lock(mPromiseWorkerProxy->Lock()); if (mPromiseWorkerProxy->CleanedUp()) { return NS_OK; } WorkerPrivate* worker = mPromiseWorkerProxy->GetWorkerPrivate(); MOZ_ASSERT(worker); principal = worker->GetPrincipal(); } MOZ_ASSERT(principal); RefPtr cb = new WorkerUnregisterCallback(mPromiseWorkerProxy); nsCOMPtr swm = mozilla::services::GetServiceWorkerManager(); nsresult rv = swm->Unregister(principal, cb, mScope); if (NS_WARN_IF(NS_FAILED(rv))) { cb->UnregisterFailed(); } return NS_OK; } }; } // namespace already_AddRefed ServiceWorkerRegistrationMainThread::Update(ErrorResult& aRv) { AssertIsOnMainThread(); nsCOMPtr go = do_QueryInterface(GetOwner()); if (!go) { aRv.Throw(NS_ERROR_FAILURE); return nullptr; } RefPtr promise = Promise::Create(go, aRv); if (NS_WARN_IF(aRv.Failed())) { return nullptr; } nsCOMPtr doc = GetOwner()->GetExtantDoc(); MOZ_ASSERT(doc); RefPtr cb = new MainThreadUpdateCallback(promise); UpdateInternal(doc->NodePrincipal(), mScope, cb); return promise.forget(); } already_AddRefed ServiceWorkerRegistrationMainThread::Unregister(ErrorResult& aRv) { AssertIsOnMainThread(); nsCOMPtr go = do_QueryInterface(GetOwner()); if (!go) { aRv.Throw(NS_ERROR_FAILURE); return nullptr; } // Although the spec says that the same-origin checks should also be done // asynchronously, we do them in sync because the Promise created by the // WebIDL infrastructure due to a returned error will be resolved // asynchronously. We aren't making any internal state changes in these // checks, so ordering of multiple calls is not affected. nsCOMPtr document = GetOwner()->GetExtantDoc(); if (!document) { aRv.Throw(NS_ERROR_FAILURE); return nullptr; } nsCOMPtr scopeURI; nsCOMPtr baseURI = document->GetBaseURI(); // "If the origin of scope is not client's origin..." nsresult rv = NS_NewURI(getter_AddRefs(scopeURI), mScope, nullptr, baseURI); if (NS_WARN_IF(NS_FAILED(rv))) { aRv.Throw(NS_ERROR_DOM_SECURITY_ERR); return nullptr; } nsCOMPtr documentPrincipal = document->NodePrincipal(); rv = documentPrincipal->CheckMayLoad(scopeURI, true /* report */, false /* allowIfInheritsPrinciple */); if (NS_FAILED(rv)) { aRv.Throw(NS_ERROR_DOM_SECURITY_ERR); return nullptr; } nsAutoCString uriSpec; aRv = scopeURI->GetSpecIgnoringRef(uriSpec); if (NS_WARN_IF(aRv.Failed())) { return nullptr; } nsCOMPtr swm = mozilla::services::GetServiceWorkerManager(); RefPtr promise = Promise::Create(go, aRv); if (NS_WARN_IF(aRv.Failed())) { return nullptr; } RefPtr cb = new UnregisterCallback(promise); NS_ConvertUTF8toUTF16 scope(uriSpec); aRv = swm->Unregister(documentPrincipal, cb, scope); if (aRv.Failed()) { return nullptr; } return promise.forget(); } // Notification API extension. already_AddRefed ServiceWorkerRegistrationMainThread::ShowNotification(JSContext* aCx, const nsAString& aTitle, const NotificationOptions& aOptions, ErrorResult& aRv) { AssertIsOnMainThread(); nsCOMPtr window = GetOwner(); if (NS_WARN_IF(!window)) { aRv.Throw(NS_ERROR_FAILURE); return nullptr; } nsCOMPtr doc = window->GetExtantDoc(); if (NS_WARN_IF(!doc)) { aRv.Throw(NS_ERROR_FAILURE); return nullptr; } RefPtr worker = GetActive(); if (!worker) { aRv.ThrowTypeError(mScope); return nullptr; } nsCOMPtr global = do_QueryInterface(window); RefPtr p = Notification::ShowPersistentNotification(aCx, global, mScope, aTitle, aOptions, aRv); if (NS_WARN_IF(aRv.Failed())) { return nullptr; } return p.forget(); } already_AddRefed ServiceWorkerRegistrationMainThread::GetNotifications(const GetNotificationOptions& aOptions, ErrorResult& aRv) { AssertIsOnMainThread(); nsCOMPtr window = GetOwner(); if (NS_WARN_IF(!window)) { aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR); return nullptr; } return Notification::Get(window, aOptions, mScope, aRv); } already_AddRefed ServiceWorkerRegistrationMainThread::GetPushManager(JSContext* aCx, ErrorResult& aRv) { AssertIsOnMainThread(); if (!mPushManager) { nsCOMPtr globalObject = do_QueryInterface(GetOwner()); if (!globalObject) { aRv.Throw(NS_ERROR_FAILURE); return nullptr; } GlobalObject global(aCx, globalObject->GetGlobalJSObject()); mPushManager = PushManager::Constructor(global, mScope, aRv); if (aRv.Failed()) { return nullptr; } } RefPtr ret = mPushManager; return ret.forget(); } //////////////////////////////////////////////////// // Worker Thread implementation class ServiceWorkerRegistrationWorkerThread final : public ServiceWorkerRegistration , public WorkerHolder { public: NS_DECL_ISUPPORTS_INHERITED NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(ServiceWorkerRegistrationWorkerThread, ServiceWorkerRegistration) ServiceWorkerRegistrationWorkerThread(WorkerPrivate* aWorkerPrivate, const nsAString& aScope); already_AddRefed Update(ErrorResult& aRv) override; already_AddRefed Unregister(ErrorResult& aRv) override; // Partial interface from Notification API. already_AddRefed ShowNotification(JSContext* aCx, const nsAString& aTitle, const NotificationOptions& aOptions, ErrorResult& aRv) override; already_AddRefed GetNotifications(const GetNotificationOptions& aOptions, ErrorResult& aRv) override; already_AddRefed GetInstalling() override; already_AddRefed GetWaiting() override; already_AddRefed GetActive() override; void GetScope(nsAString& aScope) const override { aScope = mScope; } bool Notify(Status aStatus) override; already_AddRefed GetPushManager(JSContext* aCx, ErrorResult& aRv) override; private: ~ServiceWorkerRegistrationWorkerThread(); void InitListener(); void ReleaseListener(); WorkerPrivate* mWorkerPrivate; RefPtr mListener; RefPtr mPushManager; }; class WorkerListener final : public ServiceWorkerRegistrationListener { // Accessed on the main thread. WorkerPrivate* mWorkerPrivate; nsString mScope; bool mListeningForEvents; // Accessed on the worker thread. ServiceWorkerRegistrationWorkerThread* mRegistration; public: NS_INLINE_DECL_THREADSAFE_REFCOUNTING(WorkerListener, override) WorkerListener(WorkerPrivate* aWorkerPrivate, ServiceWorkerRegistrationWorkerThread* aReg) : mWorkerPrivate(aWorkerPrivate) , mListeningForEvents(false) , mRegistration(aReg) { MOZ_ASSERT(mWorkerPrivate); mWorkerPrivate->AssertIsOnWorkerThread(); MOZ_ASSERT(mRegistration); // Copy scope so we can return it on the main thread. mRegistration->GetScope(mScope); } void StartListeningForEvents() { AssertIsOnMainThread(); MOZ_ASSERT(!mListeningForEvents); MOZ_ASSERT(mWorkerPrivate); RefPtr swm = ServiceWorkerManager::GetInstance(); if (swm) { // FIXME(nsm): Maybe the function shouldn't take an explicit scope. swm->AddRegistrationEventListener(mScope, this); mListeningForEvents = true; } } void StopListeningForEvents() { AssertIsOnMainThread(); MOZ_ASSERT(mListeningForEvents); RefPtr swm = ServiceWorkerManager::GetInstance(); // We aren't going to need this anymore and we shouldn't hold on since the // worker will go away soon. mWorkerPrivate = nullptr; if (swm) { // FIXME(nsm): Maybe the function shouldn't take an explicit scope. swm->RemoveRegistrationEventListener(mScope, this); mListeningForEvents = false; } } // ServiceWorkerRegistrationListener void UpdateFound() override; void TransitionWorker(WhichServiceWorker aWhichOne) override { AssertIsOnMainThread(); NS_WARNING("FIXME: Not implemented!"); } void InvalidateWorkers(WhichServiceWorker aWhichOnes) override { AssertIsOnMainThread(); // FIXME(nsm); } void RegistrationRemoved() override { AssertIsOnMainThread(); } void GetScope(nsAString& aScope) const override { aScope = mScope; } ServiceWorkerRegistrationWorkerThread* GetRegistration() const { if (mWorkerPrivate) { mWorkerPrivate->AssertIsOnWorkerThread(); } return mRegistration; } void ClearRegistration() { if (mWorkerPrivate) { mWorkerPrivate->AssertIsOnWorkerThread(); } mRegistration = nullptr; } private: ~WorkerListener() { MOZ_ASSERT(!mListeningForEvents); } }; NS_IMPL_ADDREF_INHERITED(ServiceWorkerRegistrationWorkerThread, ServiceWorkerRegistration) NS_IMPL_RELEASE_INHERITED(ServiceWorkerRegistrationWorkerThread, ServiceWorkerRegistration) NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(ServiceWorkerRegistrationWorkerThread) NS_INTERFACE_MAP_END_INHERITING(ServiceWorkerRegistration) // Expanded macros since we need special behaviour to release the proxy. NS_IMPL_CYCLE_COLLECTION_CLASS(ServiceWorkerRegistrationWorkerThread) NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(ServiceWorkerRegistrationWorkerThread, ServiceWorkerRegistration) NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mPushManager) NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(ServiceWorkerRegistrationWorkerThread, ServiceWorkerRegistration) NS_IMPL_CYCLE_COLLECTION_UNLINK(mPushManager) tmp->ReleaseListener(); NS_IMPL_CYCLE_COLLECTION_UNLINK_END ServiceWorkerRegistrationWorkerThread::ServiceWorkerRegistrationWorkerThread(WorkerPrivate* aWorkerPrivate, const nsAString& aScope) : ServiceWorkerRegistration(nullptr, aScope) , mWorkerPrivate(aWorkerPrivate) { InitListener(); } ServiceWorkerRegistrationWorkerThread::~ServiceWorkerRegistrationWorkerThread() { ReleaseListener(); MOZ_ASSERT(!mListener); } already_AddRefed ServiceWorkerRegistrationWorkerThread::GetInstalling() { // FIXME(nsm): Will be implemented after Bug 1113522. return nullptr; } already_AddRefed ServiceWorkerRegistrationWorkerThread::GetWaiting() { // FIXME(nsm): Will be implemented after Bug 1113522. return nullptr; } already_AddRefed ServiceWorkerRegistrationWorkerThread::GetActive() { // FIXME(nsm): Will be implemented after Bug 1113522. return nullptr; } already_AddRefed ServiceWorkerRegistrationWorkerThread::Update(ErrorResult& aRv) { WorkerPrivate* worker = GetCurrentThreadWorkerPrivate(); MOZ_ASSERT(worker); worker->AssertIsOnWorkerThread(); RefPtr promise = Promise::Create(worker->GlobalScope(), aRv); if (aRv.Failed()) { return nullptr; } // Avoid infinite update loops by ignoring update() calls during top // level script evaluation. See: // https://github.com/slightlyoff/ServiceWorker/issues/800 if (worker->LoadScriptAsPartOfLoadingServiceWorkerScript()) { promise->MaybeResolveWithUndefined(); return promise.forget(); } RefPtr proxy = PromiseWorkerProxy::Create(worker, promise); if (!proxy) { aRv.Throw(NS_ERROR_DOM_ABORT_ERR); return nullptr; } RefPtr r = new UpdateRunnable(proxy, mScope); MOZ_ALWAYS_SUCCEEDS(worker->DispatchToMainThread(r.forget())); return promise.forget(); } already_AddRefed ServiceWorkerRegistrationWorkerThread::Unregister(ErrorResult& aRv) { WorkerPrivate* worker = GetCurrentThreadWorkerPrivate(); MOZ_ASSERT(worker); worker->AssertIsOnWorkerThread(); if (!worker->IsServiceWorker()) { // For other workers, the registration probably originated from // getRegistration(), so we may have to validate origin etc. Let's do this // this later. aRv.Throw(NS_ERROR_DOM_SECURITY_ERR); return nullptr; } RefPtr promise = Promise::Create(worker->GlobalScope(), aRv); if (aRv.Failed()) { return nullptr; } RefPtr proxy = PromiseWorkerProxy::Create(worker, promise); if (!proxy) { aRv.Throw(NS_ERROR_DOM_ABORT_ERR); return nullptr; } RefPtr r = new StartUnregisterRunnable(proxy, mScope); MOZ_ALWAYS_SUCCEEDS(worker->DispatchToMainThread(r.forget())); return promise.forget(); } void ServiceWorkerRegistrationWorkerThread::InitListener() { MOZ_ASSERT(!mListener); WorkerPrivate* worker = GetCurrentThreadWorkerPrivate(); MOZ_ASSERT(worker); worker->AssertIsOnWorkerThread(); mListener = new WorkerListener(worker, this); if (!HoldWorker(worker, Closing)) { mListener = nullptr; NS_WARNING("Could not add feature"); return; } nsCOMPtr r = NewRunnableMethod(mListener, &WorkerListener::StartListeningForEvents); MOZ_ALWAYS_SUCCEEDS(worker->DispatchToMainThread(r.forget())); } void ServiceWorkerRegistrationWorkerThread::ReleaseListener() { if (!mListener) { return; } // We can assert worker here, because: // 1) We always HoldWorker, so if the worker has shutdown already, we'll // have received Notify and removed it. If HoldWorker had failed, // mListener will be null and we won't reach here. // 2) Otherwise, worker is still around even if we are going away. mWorkerPrivate->AssertIsOnWorkerThread(); ReleaseWorker(); mListener->ClearRegistration(); nsCOMPtr r = NewRunnableMethod(mListener, &WorkerListener::StopListeningForEvents); MOZ_ALWAYS_SUCCEEDS(mWorkerPrivate->DispatchToMainThread(r.forget())); mListener = nullptr; mWorkerPrivate = nullptr; } bool ServiceWorkerRegistrationWorkerThread::Notify(Status aStatus) { ReleaseListener(); return true; } class FireUpdateFoundRunnable final : public WorkerRunnable { RefPtr mListener; public: FireUpdateFoundRunnable(WorkerPrivate* aWorkerPrivate, WorkerListener* aListener) : WorkerRunnable(aWorkerPrivate) , mListener(aListener) { // Need this assertion for now since runnables which modify busy count can // only be dispatched from parent thread to worker thread and we don't deal // with nested workers. SW threads can't be nested. MOZ_ASSERT(aWorkerPrivate->IsServiceWorker()); } bool WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate) override { MOZ_ASSERT(aWorkerPrivate); aWorkerPrivate->AssertIsOnWorkerThread(); ServiceWorkerRegistrationWorkerThread* reg = mListener->GetRegistration(); if (reg) { reg->DispatchTrustedEvent(NS_LITERAL_STRING("updatefound")); } return true; } }; void WorkerListener::UpdateFound() { AssertIsOnMainThread(); if (mWorkerPrivate) { RefPtr r = new FireUpdateFoundRunnable(mWorkerPrivate, this); Unused << NS_WARN_IF(!r->Dispatch()); } } // Notification API extension. already_AddRefed ServiceWorkerRegistrationWorkerThread::ShowNotification(JSContext* aCx, const nsAString& aTitle, const NotificationOptions& aOptions, ErrorResult& aRv) { // Until Bug 1131324 exposes ServiceWorkerContainer on workers, // ShowPersistentNotification() checks for valid active worker while it is // also verifying scope so that we block the worker on the main thread only // once. RefPtr p = Notification::ShowPersistentNotification(aCx, mWorkerPrivate->GlobalScope(), mScope, aTitle, aOptions, aRv); if (NS_WARN_IF(aRv.Failed())) { return nullptr; } return p.forget(); } already_AddRefed ServiceWorkerRegistrationWorkerThread::GetNotifications(const GetNotificationOptions& aOptions, ErrorResult& aRv) { return Notification::WorkerGet(mWorkerPrivate, aOptions, mScope, aRv); } already_AddRefed ServiceWorkerRegistrationWorkerThread::GetPushManager(JSContext* aCx, ErrorResult& aRv) { if (!mPushManager) { mPushManager = new PushManager(mScope); } RefPtr ret = mPushManager; return ret.forget(); } //////////////////////////////////////////////////// // Base class implementation NS_IMPL_ADDREF_INHERITED(ServiceWorkerRegistration, DOMEventTargetHelper) NS_IMPL_RELEASE_INHERITED(ServiceWorkerRegistration, DOMEventTargetHelper) NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(ServiceWorkerRegistration) NS_INTERFACE_MAP_END_INHERITING(DOMEventTargetHelper) ServiceWorkerRegistration::ServiceWorkerRegistration(nsPIDOMWindowInner* aWindow, const nsAString& aScope) : DOMEventTargetHelper(aWindow) , mScope(aScope) {} JSObject* ServiceWorkerRegistration::WrapObject(JSContext* aCx, JS::Handle aGivenProto) { return ServiceWorkerRegistrationBinding::Wrap(aCx, this, aGivenProto); } /* static */ already_AddRefed ServiceWorkerRegistration::CreateForMainThread(nsPIDOMWindowInner* aWindow, const nsAString& aScope) { MOZ_ASSERT(aWindow); MOZ_ASSERT(NS_IsMainThread()); RefPtr registration = new ServiceWorkerRegistrationMainThread(aWindow, aScope); return registration.forget(); } /* static */ already_AddRefed ServiceWorkerRegistration::CreateForWorker(workers::WorkerPrivate* aWorkerPrivate, const nsAString& aScope) { MOZ_ASSERT(aWorkerPrivate); aWorkerPrivate->AssertIsOnWorkerThread(); RefPtr registration = new ServiceWorkerRegistrationWorkerThread(aWorkerPrivate, aScope); return registration.forget(); } } // dom namespace } // mozilla namespace