gecko-dev/dom/workers/ServiceWorkerWindowClient.cpp
Nikhil Marathe 25e263af21 Bug 1144660 - client.focus() adds window interaction checks and directly uses DOMServiceWorkerFocusClient to focus window. r=ehsan,dao
client.focus() now directly uses the DOMServiceWorkerFocusClient event. The
platform popup checking is not available on service workers. Instead each
worker maintains a counter of if it is allowed to interact with windows. This
counter is currently only incremented by the notificationclick event and
dropped after the event has been dispatched.

Since acquiring a client is an async operation most service workers will
perform in notificationclick, an additional extension is granted after the event
during which the service worker may focus a client. This extension is only granted
if the script invokes NotificationEvent.waitUntil() at which point the timer begins.
The extension is terminated when the Promise passed to waitUntil() is fulfilled, or
the timer expires, whichever comes first.

--HG--
extra : commitid : 5wavKTRZWcy
extra : rebase_source : fc8ab4ef6c9bf384b5525b0bc979b3cedc4e1d6c
2015-07-23 08:30:27 -07:00

162 lines
4.9 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 "mozilla/dom/ClientBinding.h"
#include "mozilla/dom/Promise.h"
#include "mozilla/dom/PromiseWorkerProxy.h"
#include "mozilla/UniquePtr.h"
#include "nsGlobalWindow.h"
#include "WorkerPrivate.h"
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 {
// Passing a null clientInfo will reject the promise with InvalidAccessError.
class ResolveOrRejectPromiseRunnable final : public WorkerRunnable
{
nsRefPtr<PromiseWorkerProxy> mPromiseProxy;
UniquePtr<ServiceWorkerClientInfo> mClientInfo;
public:
ResolveOrRejectPromiseRunnable(WorkerPrivate* aWorkerPrivate,
PromiseWorkerProxy* aPromiseProxy,
UniquePtr<ServiceWorkerClientInfo>&& aClientInfo)
: WorkerRunnable(aWorkerPrivate, WorkerThreadModifyBusyCount)
, mPromiseProxy(aPromiseProxy)
, mClientInfo(Move(aClientInfo))
{
AssertIsOnMainThread();
}
bool
WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate) override
{
MOZ_ASSERT(aWorkerPrivate);
aWorkerPrivate->AssertIsOnWorkerThread();
Promise* promise = mPromiseProxy->GetWorkerPromise();
MOZ_ASSERT(promise);
if (mClientInfo) {
nsRefPtr<ServiceWorkerWindowClient> client =
new ServiceWorkerWindowClient(promise->GetParentObject(), *mClientInfo);
promise->MaybeResolve(client);
} else {
promise->MaybeReject(NS_ERROR_DOM_INVALID_ACCESS_ERR);
}
// Release the reference on the worker thread.
mPromiseProxy->CleanUp(aCx);
return true;
}
};
class ClientFocusRunnable final : public nsRunnable
{
uint64_t mWindowId;
nsRefPtr<PromiseWorkerProxy> mPromiseProxy;
public:
ClientFocusRunnable(uint64_t aWindowId, PromiseWorkerProxy* aPromiseProxy)
: mWindowId(aWindowId)
, mPromiseProxy(aPromiseProxy)
{
MOZ_ASSERT(mPromiseProxy);
MOZ_ASSERT(mPromiseProxy->GetWorkerPromise());
}
NS_IMETHOD
Run() override
{
AssertIsOnMainThread();
nsGlobalWindow* window = nsGlobalWindow::GetInnerWindowWithId(mWindowId);
UniquePtr<ServiceWorkerClientInfo> clientInfo;
if (window) {
nsContentUtils::DispatchChromeEvent(window->GetExtantDoc(), window->GetOuterWindow(), NS_LITERAL_STRING("DOMServiceWorkerFocusClient"), true, true);
clientInfo.reset(new ServiceWorkerClientInfo(window->GetDocument(),
window->GetOuterWindow()));
}
DispatchResult(Move(clientInfo));
return NS_OK;
}
private:
void
DispatchResult(UniquePtr<ServiceWorkerClientInfo>&& aClientInfo)
{
WorkerPrivate* workerPrivate = mPromiseProxy->GetWorkerPrivate();
MOZ_ASSERT(workerPrivate);
nsRefPtr<ResolveOrRejectPromiseRunnable> resolveRunnable =
new ResolveOrRejectPromiseRunnable(workerPrivate, mPromiseProxy,
Move(aClientInfo));
AutoJSAPI jsapi;
jsapi.Init();
JSContext* cx = jsapi.cx();
if (!resolveRunnable->Dispatch(cx)) {
nsRefPtr<PromiseWorkerProxyControlRunnable> controlRunnable =
new PromiseWorkerProxyControlRunnable(workerPrivate, mPromiseProxy);
if (!controlRunnable->Dispatch(cx)) {
NS_RUNTIMEABORT("Failed to dispatch Focus promise control runnable.");
}
}
}
};
} // 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);
nsRefPtr<Promise> promise = Promise::Create(global, aRv);
if (NS_WARN_IF(aRv.Failed())) {
return nullptr;
}
if (workerPrivate->GlobalScope()->WindowInteractionAllowed()) {
nsRefPtr<PromiseWorkerProxy> promiseProxy =
PromiseWorkerProxy::Create(workerPrivate, promise);
if (!promiseProxy->GetWorkerPromise()) {
// Don't dispatch if adding the worker feature failed.
return promise.forget();
}
nsRefPtr<ClientFocusRunnable> r = new ClientFocusRunnable(mWindowId,
promiseProxy);
aRv = NS_DispatchToMainThread(r);
if (NS_WARN_IF(aRv.Failed())) {
promise->MaybeReject(aRv);
}
} else {
promise->MaybeReject(NS_ERROR_DOM_INVALID_STATE_ERR);
}
return promise.forget();
}