diff options
Diffstat (limited to 'dom/workers/ServiceWorkerWindowClient.cpp')
-rw-r--r-- | dom/workers/ServiceWorkerWindowClient.cpp | 558 |
1 files changed, 558 insertions, 0 deletions
diff --git a/dom/workers/ServiceWorkerWindowClient.cpp b/dom/workers/ServiceWorkerWindowClient.cpp new file mode 100644 index 000000000..2ce0603cf --- /dev/null +++ b/dom/workers/ServiceWorkerWindowClient.cpp @@ -0,0 +1,558 @@ +/* -*- 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 "ServiceWorkerWindowClient.h" + +#include "js/Value.h" +#include "mozilla/Mutex.h" +#include "mozilla/dom/ClientBinding.h" +#include "mozilla/dom/Promise.h" +#include "mozilla/dom/PromiseWorkerProxy.h" +#include "mozilla/UniquePtr.h" +#include "nsContentUtils.h" +#include "nsGlobalWindow.h" +#include "nsIDocShell.h" +#include "nsIDocShellLoadInfo.h" +#include "nsIDocument.h" +#include "nsIGlobalObject.h" +#include "nsIPrincipal.h" +#include "nsIScriptSecurityManager.h" +#include "nsIWebNavigation.h" +#include "nsIWebProgress.h" +#include "nsIWebProgressListener.h" +#include "nsString.h" +#include "nsWeakReference.h" +#include "ServiceWorker.h" +#include "ServiceWorkerInfo.h" +#include "ServiceWorkerManager.h" +#include "WorkerPrivate.h" +#include "WorkerScope.h" + +using namespace mozilla; +using namespace mozilla::dom; +using namespace mozilla::dom::workers; + +using mozilla::UniquePtr; + +JSObject* +ServiceWorkerWindowClient::WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) +{ + return WindowClientBinding::Wrap(aCx, this, aGivenProto); +} + +namespace { + +class ResolveOrRejectPromiseRunnable final : public WorkerRunnable +{ + RefPtr<PromiseWorkerProxy> mPromiseProxy; + UniquePtr<ServiceWorkerClientInfo> mClientInfo; + nsresult mRv; + +public: + // Passing a null clientInfo will resolve the promise with a null value. + ResolveOrRejectPromiseRunnable( + WorkerPrivate* aWorkerPrivate, PromiseWorkerProxy* aPromiseProxy, + UniquePtr<ServiceWorkerClientInfo>&& aClientInfo) + : WorkerRunnable(aWorkerPrivate) + , mPromiseProxy(aPromiseProxy) + , mClientInfo(Move(aClientInfo)) + , mRv(NS_OK) + { + AssertIsOnMainThread(); + } + + // Reject the promise with passed nsresult. + ResolveOrRejectPromiseRunnable(WorkerPrivate* aWorkerPrivate, + PromiseWorkerProxy* aPromiseProxy, + nsresult aRv) + : WorkerRunnable(aWorkerPrivate) + , mPromiseProxy(aPromiseProxy) + , mClientInfo(nullptr) + , mRv(aRv) + { + MOZ_ASSERT(NS_FAILED(aRv)); + AssertIsOnMainThread(); + } + + bool + WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate) override + { + MOZ_ASSERT(aWorkerPrivate); + aWorkerPrivate->AssertIsOnWorkerThread(); + + RefPtr<Promise> promise = mPromiseProxy->WorkerPromise(); + MOZ_ASSERT(promise); + + if (NS_WARN_IF(NS_FAILED(mRv))) { + promise->MaybeReject(mRv); + } else if (mClientInfo) { + RefPtr<ServiceWorkerWindowClient> client = + new ServiceWorkerWindowClient(promise->GetParentObject(), *mClientInfo); + promise->MaybeResolve(client); + } else { + promise->MaybeResolve(JS::NullHandleValue); + } + + // Release the reference on the worker thread. + mPromiseProxy->CleanUp(); + + return true; + } +}; + +class ClientFocusRunnable final : public Runnable +{ + uint64_t mWindowId; + RefPtr<PromiseWorkerProxy> mPromiseProxy; + +public: + ClientFocusRunnable(uint64_t aWindowId, PromiseWorkerProxy* aPromiseProxy) + : mWindowId(aWindowId) + , mPromiseProxy(aPromiseProxy) + { + MOZ_ASSERT(mPromiseProxy); + } + + NS_IMETHOD + Run() override + { + AssertIsOnMainThread(); + nsGlobalWindow* window = nsGlobalWindow::GetInnerWindowWithId(mWindowId); + UniquePtr<ServiceWorkerClientInfo> clientInfo; + + if (window) { + nsCOMPtr<nsIDocument> doc = window->GetDocument(); + if (doc) { + nsContentUtils::DispatchFocusChromeEvent(window->GetOuterWindow()); + clientInfo.reset(new ServiceWorkerClientInfo(doc)); + } + } + + DispatchResult(Move(clientInfo)); + return NS_OK; + } + +private: + void + DispatchResult(UniquePtr<ServiceWorkerClientInfo>&& aClientInfo) + { + AssertIsOnMainThread(); + MutexAutoLock lock(mPromiseProxy->Lock()); + if (mPromiseProxy->CleanedUp()) { + return; + } + + RefPtr<ResolveOrRejectPromiseRunnable> resolveRunnable; + if (aClientInfo) { + resolveRunnable = new ResolveOrRejectPromiseRunnable( + mPromiseProxy->GetWorkerPrivate(), mPromiseProxy, Move(aClientInfo)); + } else { + resolveRunnable = new ResolveOrRejectPromiseRunnable( + mPromiseProxy->GetWorkerPrivate(), mPromiseProxy, + NS_ERROR_DOM_INVALID_ACCESS_ERR); + } + + resolveRunnable->Dispatch(); + } +}; + +} // namespace + +already_AddRefed<Promise> +ServiceWorkerWindowClient::Focus(ErrorResult& aRv) const +{ + WorkerPrivate* workerPrivate = GetCurrentThreadWorkerPrivate(); + MOZ_ASSERT(workerPrivate); + workerPrivate->AssertIsOnWorkerThread(); + + nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(GetParentObject()); + MOZ_ASSERT(global); + + RefPtr<Promise> promise = Promise::Create(global, aRv); + if (NS_WARN_IF(aRv.Failed())) { + return nullptr; + } + + if (workerPrivate->GlobalScope()->WindowInteractionAllowed()) { + RefPtr<PromiseWorkerProxy> promiseProxy = + PromiseWorkerProxy::Create(workerPrivate, promise); + if (promiseProxy) { + RefPtr<ClientFocusRunnable> r = new ClientFocusRunnable(mWindowId, + promiseProxy); + MOZ_ALWAYS_SUCCEEDS(workerPrivate->DispatchToMainThread(r.forget())); + } else { + promise->MaybeReject(NS_ERROR_DOM_ABORT_ERR); + } + + } else { + promise->MaybeReject(NS_ERROR_DOM_INVALID_STATE_ERR); + } + + return promise.forget(); +} + +class WebProgressListener final : public nsIWebProgressListener, + public nsSupportsWeakReference +{ +public: + NS_DECL_CYCLE_COLLECTING_ISUPPORTS + NS_DECL_CYCLE_COLLECTION_CLASS_AMBIGUOUS(WebProgressListener, + nsIWebProgressListener) + + WebProgressListener(PromiseWorkerProxy* aPromiseProxy, + ServiceWorkerPrivate* aServiceWorkerPrivate, + nsPIDOMWindowOuter* aWindow, nsIURI* aBaseURI) + : mPromiseProxy(aPromiseProxy) + , mServiceWorkerPrivate(aServiceWorkerPrivate) + , mWindow(aWindow) + , mBaseURI(aBaseURI) + { + MOZ_ASSERT(aPromiseProxy); + MOZ_ASSERT(aServiceWorkerPrivate); + MOZ_ASSERT(aWindow); + MOZ_ASSERT(aWindow->IsOuterWindow()); + MOZ_ASSERT(aBaseURI); + AssertIsOnMainThread(); + + mServiceWorkerPrivate->StoreISupports(static_cast<nsIWebProgressListener*>(this)); + } + + NS_IMETHOD + OnStateChange(nsIWebProgress* aWebProgress, nsIRequest* aRequest, + uint32_t aStateFlags, nsresult aStatus) override + { + if (!(aStateFlags & STATE_IS_DOCUMENT) || + !(aStateFlags & (STATE_STOP | STATE_TRANSFERRING))) { + return NS_OK; + } + + // This is safe because our caller holds a strong ref. + mServiceWorkerPrivate->RemoveISupports(static_cast<nsIWebProgressListener*>(this)); + aWebProgress->RemoveProgressListener(this); + + WorkerPrivate* workerPrivate; + + { + MutexAutoLock lock(mPromiseProxy->Lock()); + if (mPromiseProxy->CleanedUp()) { + return NS_OK; + } + + workerPrivate = mPromiseProxy->GetWorkerPrivate(); + } + + nsCOMPtr<nsIDocument> doc = mWindow->GetExtantDoc(); + + RefPtr<ResolveOrRejectPromiseRunnable> resolveRunnable; + UniquePtr<ServiceWorkerClientInfo> clientInfo; + if (!doc) { + resolveRunnable = new ResolveOrRejectPromiseRunnable( + workerPrivate, mPromiseProxy, NS_ERROR_TYPE_ERR); + resolveRunnable->Dispatch(); + + return NS_OK; + } + + // Check same origin. + nsCOMPtr<nsIScriptSecurityManager> securityManager = + nsContentUtils::GetSecurityManager(); + nsresult rv = securityManager->CheckSameOriginURI(doc->GetOriginalURI(), + mBaseURI, false); + + if (NS_SUCCEEDED(rv)) { + nsContentUtils::DispatchFocusChromeEvent(mWindow->GetOuterWindow()); + clientInfo.reset(new ServiceWorkerClientInfo(doc)); + } + + resolveRunnable = new ResolveOrRejectPromiseRunnable( + workerPrivate, mPromiseProxy, Move(clientInfo)); + resolveRunnable->Dispatch(); + + return NS_OK; + } + + NS_IMETHOD + OnProgressChange(nsIWebProgress* aWebProgress, nsIRequest* aRequest, + int32_t aCurSelfProgress, int32_t aMaxSelfProgress, + int32_t aCurTotalProgress, + int32_t aMaxTotalProgress) override + { + MOZ_CRASH("Unexpected notification."); + return NS_OK; + } + + NS_IMETHOD + OnLocationChange(nsIWebProgress* aWebProgress, nsIRequest* aRequest, + nsIURI* aLocation, uint32_t aFlags) override + { + MOZ_CRASH("Unexpected notification."); + return NS_OK; + } + + NS_IMETHOD + OnStatusChange(nsIWebProgress* aWebProgress, nsIRequest* aRequest, + nsresult aStatus, const char16_t* aMessage) override + { + MOZ_CRASH("Unexpected notification."); + return NS_OK; + } + + NS_IMETHOD + OnSecurityChange(nsIWebProgress* aWebProgress, nsIRequest* aRequest, + uint32_t aState) override + { + MOZ_CRASH("Unexpected notification."); + return NS_OK; + } + +private: + ~WebProgressListener() {} + + RefPtr<PromiseWorkerProxy> mPromiseProxy; + RefPtr<ServiceWorkerPrivate> mServiceWorkerPrivate; + nsCOMPtr<nsPIDOMWindowOuter> mWindow; + nsCOMPtr<nsIURI> mBaseURI; +}; + +NS_IMPL_CYCLE_COLLECTING_ADDREF(WebProgressListener) +NS_IMPL_CYCLE_COLLECTING_RELEASE(WebProgressListener) +NS_IMPL_CYCLE_COLLECTION(WebProgressListener, mPromiseProxy, + mServiceWorkerPrivate, mWindow) + +NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(WebProgressListener) + NS_INTERFACE_MAP_ENTRY(nsIWebProgressListener) + NS_INTERFACE_MAP_ENTRY(nsISupportsWeakReference) +NS_INTERFACE_MAP_END + +class ClientNavigateRunnable final : public Runnable +{ + uint64_t mWindowId; + nsString mUrl; + nsCString mBaseUrl; + nsString mScope; + RefPtr<PromiseWorkerProxy> mPromiseProxy; + MOZ_INIT_OUTSIDE_CTOR WorkerPrivate* mWorkerPrivate; + +public: + ClientNavigateRunnable(uint64_t aWindowId, const nsAString& aUrl, + const nsAString& aScope, + PromiseWorkerProxy* aPromiseProxy) + : mWindowId(aWindowId) + , mUrl(aUrl) + , mScope(aScope) + , mPromiseProxy(aPromiseProxy) + , mWorkerPrivate(nullptr) + { + MOZ_ASSERT(aPromiseProxy); + MOZ_ASSERT(aPromiseProxy->GetWorkerPrivate()); + aPromiseProxy->GetWorkerPrivate()->AssertIsOnWorkerThread(); + } + + NS_IMETHOD + Run() override + { + AssertIsOnMainThread(); + + nsCOMPtr<nsIPrincipal> principal; + + { + MutexAutoLock lock(mPromiseProxy->Lock()); + if (mPromiseProxy->CleanedUp()) { + return NS_OK; + } + + mWorkerPrivate = mPromiseProxy->GetWorkerPrivate(); + WorkerPrivate::LocationInfo& info = mWorkerPrivate->GetLocationInfo(); + mBaseUrl = info.mHref; + principal = mWorkerPrivate->GetPrincipal(); + MOZ_DIAGNOSTIC_ASSERT(principal); + } + + nsCOMPtr<nsIURI> baseUrl; + nsCOMPtr<nsIURI> url; + nsresult rv = ParseUrl(getter_AddRefs(baseUrl), getter_AddRefs(url)); + + if (NS_WARN_IF(NS_FAILED(rv))) { + return RejectPromise(NS_ERROR_TYPE_ERR); + } + + rv = principal->CheckMayLoad(url, true, false); + if (NS_WARN_IF(NS_FAILED(rv))) { + return RejectPromise(rv); + } + + nsGlobalWindow* window; + rv = Navigate(url, principal, &window); + if (NS_WARN_IF(NS_FAILED(rv))) { + return RejectPromise(rv); + } + + nsCOMPtr<nsIDocShell> docShell = window->GetDocShell(); + nsCOMPtr<nsIWebProgress> webProgress = do_GetInterface(docShell); + if (NS_WARN_IF(!webProgress)) { + return NS_ERROR_FAILURE; + } + + RefPtr<ServiceWorkerManager> swm = ServiceWorkerManager::GetInstance(); + if (!swm) { + return NS_ERROR_FAILURE; + } + + RefPtr<ServiceWorkerRegistrationInfo> registration = + swm->GetRegistration(principal, NS_ConvertUTF16toUTF8(mScope)); + if (NS_WARN_IF(!registration)) { + return NS_ERROR_FAILURE; + } + RefPtr<ServiceWorkerInfo> serviceWorkerInfo = + registration->GetServiceWorkerInfoById(mWorkerPrivate->ServiceWorkerID()); + if (NS_WARN_IF(!serviceWorkerInfo)) { + return NS_ERROR_FAILURE; + } + + nsCOMPtr<nsIWebProgressListener> listener = + new WebProgressListener(mPromiseProxy, serviceWorkerInfo->WorkerPrivate(), + window->GetOuterWindow(), baseUrl); + + rv = webProgress->AddProgressListener( + listener, nsIWebProgress::NOTIFY_STATE_DOCUMENT); + + if (NS_WARN_IF(NS_FAILED(rv))) { + return RejectPromise(rv); + } + + return NS_OK; + } + +private: + nsresult + RejectPromise(nsresult aRv) + { + MOZ_ASSERT(mWorkerPrivate); + RefPtr<ResolveOrRejectPromiseRunnable> resolveRunnable = + new ResolveOrRejectPromiseRunnable(mWorkerPrivate, mPromiseProxy, aRv); + + resolveRunnable->Dispatch(); + return NS_OK; + } + + nsresult + ResolvePromise(UniquePtr<ServiceWorkerClientInfo>&& aClientInfo) + { + MOZ_ASSERT(mWorkerPrivate); + RefPtr<ResolveOrRejectPromiseRunnable> resolveRunnable = + new ResolveOrRejectPromiseRunnable(mWorkerPrivate, mPromiseProxy, + Move(aClientInfo)); + + resolveRunnable->Dispatch(); + return NS_OK; + } + + nsresult + ParseUrl(nsIURI** aBaseUrl, nsIURI** aUrl) + { + MOZ_ASSERT(aBaseUrl); + MOZ_ASSERT(aUrl); + AssertIsOnMainThread(); + + nsCOMPtr<nsIURI> baseUrl; + nsresult rv = NS_NewURI(getter_AddRefs(baseUrl), mBaseUrl); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<nsIURI> url; + rv = NS_NewURI(getter_AddRefs(url), mUrl, nullptr, baseUrl); + NS_ENSURE_SUCCESS(rv, rv); + + baseUrl.forget(aBaseUrl); + url.forget(aUrl); + + return NS_OK; + } + + nsresult + Navigate(nsIURI* aUrl, nsIPrincipal* aPrincipal, nsGlobalWindow** aWindow) + { + MOZ_ASSERT(aWindow); + + nsGlobalWindow* window = nsGlobalWindow::GetInnerWindowWithId(mWindowId); + if (NS_WARN_IF(!window)) { + return NS_ERROR_TYPE_ERR; + } + + nsCOMPtr<nsIDocument> doc = window->GetDocument(); + if (NS_WARN_IF(!doc)) { + return NS_ERROR_TYPE_ERR; + } + + if (NS_WARN_IF(!doc->IsActive())) { + return NS_ERROR_TYPE_ERR; + } + + nsCOMPtr<nsIDocShell> docShell = window->GetDocShell(); + if (NS_WARN_IF(!docShell)) { + return NS_ERROR_TYPE_ERR; + } + + nsCOMPtr<nsIDocShellLoadInfo> loadInfo; + nsresult rv = docShell->CreateLoadInfo(getter_AddRefs(loadInfo)); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + loadInfo->SetTriggeringPrincipal(aPrincipal); + loadInfo->SetReferrer(doc->GetOriginalURI()); + loadInfo->SetReferrerPolicy(doc->GetReferrerPolicy()); + loadInfo->SetLoadType(nsIDocShellLoadInfo::loadStopContentAndReplace); + loadInfo->SetSourceDocShell(docShell); + rv = + docShell->LoadURI(aUrl, loadInfo, nsIWebNavigation::LOAD_FLAGS_NONE, true); + + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + *aWindow = window; + return NS_OK; + } +}; + +already_AddRefed<Promise> +ServiceWorkerWindowClient::Navigate(const nsAString& aUrl, ErrorResult& aRv) +{ + WorkerPrivate* workerPrivate = GetCurrentThreadWorkerPrivate(); + MOZ_ASSERT(workerPrivate); + workerPrivate->AssertIsOnWorkerThread(); + + nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(GetParentObject()); + MOZ_ASSERT(global); + + RefPtr<Promise> promise = Promise::Create(global, aRv); + if (NS_WARN_IF(aRv.Failed())) { + return nullptr; + } + + if (aUrl.EqualsLiteral("about:blank")) { + promise->MaybeReject(NS_ERROR_TYPE_ERR); + return promise.forget(); + } + + ServiceWorkerGlobalScope* globalScope = + static_cast<ServiceWorkerGlobalScope*>(workerPrivate->GlobalScope()); + nsString scope; + globalScope->GetScope(scope); + + RefPtr<PromiseWorkerProxy> promiseProxy = + PromiseWorkerProxy::Create(workerPrivate, promise); + if (promiseProxy) { + RefPtr<ClientNavigateRunnable> r = + new ClientNavigateRunnable(mWindowId, aUrl, scope, promiseProxy); + MOZ_ALWAYS_SUCCEEDS(workerPrivate->DispatchToMainThread(r.forget())); + } else { + promise->MaybeReject(NS_ERROR_DOM_ABORT_ERR); + } + + return promise.forget(); +} |