Files
tubestation/dom/workers/ServiceWorkerWindowClient.cpp
Wes Kocher e70a727506 Backed out 6 changesets (bug 1300658) for frequent Windows VM Xpcshell failures a=backout
Backed out changeset 6cf3a60640cf (bug 1300658)
Backed out changeset c74062a27462 (bug 1300658)
Backed out changeset 39fbc61739ef (bug 1300658)
Backed out changeset 0b9d70b040a2 (bug 1300658)
Backed out changeset 4e921d61f036 (bug 1300658)
Backed out changeset 56496fad6494 (bug 1300658)
2016-09-12 16:34:08 -07:00

526 lines
14 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 "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(NS_DispatchToMainThread(r));
} 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,
nsPIDOMWindowOuter* aWindow, nsIURI* aBaseURI)
: mPromiseProxy(aPromiseProxy)
, mWindow(aWindow)
, mBaseURI(aBaseURI)
{
MOZ_ASSERT(aPromiseProxy);
MOZ_ASSERT(aWindow);
MOZ_ASSERT(aWindow->IsOuterWindow());
MOZ_ASSERT(aBaseURI);
AssertIsOnMainThread();
mPromiseProxy->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;
}
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;
nsCOMPtr<nsPIDOMWindowOuter> mWindow;
nsCOMPtr<nsIURI> mBaseURI;
};
NS_IMPL_CYCLE_COLLECTING_ADDREF(WebProgressListener)
NS_IMPL_CYCLE_COLLECTING_RELEASE(WebProgressListener)
NS_IMPL_CYCLE_COLLECTION(WebProgressListener, mPromiseProxy,
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;
RefPtr<PromiseWorkerProxy> mPromiseProxy;
WorkerPrivate* mWorkerPrivate;
public:
ClientNavigateRunnable(uint64_t aWindowId, const nsAString& aUrl,
PromiseWorkerProxy* aPromiseProxy)
: mWindowId(aWindowId)
, mUrl(aUrl)
, mPromiseProxy(aPromiseProxy)
{
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();
}
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;
}
nsCOMPtr<nsIWebProgressListener> listener =
new WebProgressListener(mPromiseProxy, 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();
}
RefPtr<PromiseWorkerProxy> promiseProxy =
PromiseWorkerProxy::Create(workerPrivate, promise);
if (promiseProxy) {
RefPtr<ClientNavigateRunnable> r =
new ClientNavigateRunnable(mWindowId, aUrl, promiseProxy);
MOZ_ALWAYS_SUCCEEDS(NS_DispatchToMainThread(r));
} else {
promise->MaybeReject(NS_ERROR_DOM_ABORT_ERR);
}
return promise.forget();
}