summaryrefslogtreecommitdiffstats
path: root/dom/workers/ServiceWorkerWindowClient.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'dom/workers/ServiceWorkerWindowClient.cpp')
-rw-r--r--dom/workers/ServiceWorkerWindowClient.cpp558
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();
+}