WebWorkerNote
Jump to navigation
Jump to search
Dedicated Worker
already_AddRefed<WorkerPrivate>
WorkerPrivate::Constructor(JSContext* aCx,
const nsAString& aScriptURL,
bool aIsChromeWorker, WorkerType aWorkerType,
const nsACString& aSharedWorkerName,
LoadInfo* aLoadInfo, ErrorResult& aRv)
{
WorkerPrivate* parent = NS_IsMainThread() ?
nullptr :
GetCurrentThreadWorkerPrivate();
if (parent) {
parent->AssertIsOnWorkerThread();
} else {
AssertIsOnMainThread();
}
MOZ_ASSERT_IF(aWorkerType != WorkerTypeDedicated,
!aSharedWorkerName.IsVoid());
MOZ_ASSERT_IF(aWorkerType == WorkerTypeDedicated,
aSharedWorkerName.IsEmpty());
Maybe<LoadInfo> stackLoadInfo;
if (!aLoadInfo) {
stackLoadInfo.emplace();
nsresult rv = GetLoadInfo(aCx, nullptr, parent, aScriptURL,
aIsChromeWorker, stackLoadInfo.ptr());
if (NS_FAILED(rv)) {
scriptloader::ReportLoadError(aCx, aScriptURL, rv, !parent);
aRv.Throw(rv);
return nullptr;
}
aLoadInfo = stackLoadInfo.ptr();
}
// NB: This has to be done before creating the WorkerPrivate, because it will
// attempt to use static variables that are initialized in the RuntimeService
// constructor.
RuntimeService* runtimeService;
if (!parent) {
runtimeService = RuntimeService::GetOrCreateService();
if (!runtimeService) {
JS_ReportError(aCx, "Failed to create runtime service!");
aRv.Throw(NS_ERROR_FAILURE);
return nullptr;
}
}
else {
runtimeService = RuntimeService::GetService();
}
MOZ_ASSERT(runtimeService);
nsRefPtr<WorkerPrivate> worker =
new WorkerPrivate(aCx, parent, aScriptURL, aIsChromeWorker,
aWorkerType, aSharedWorkerName, *aLoadInfo);
if (!runtimeService->RegisterWorker(aCx, worker)) {
aRv.Throw(NS_ERROR_UNEXPECTED);
return nullptr;
}
worker->EnableDebugger();
nsRefPtr<CompileScriptRunnable> compiler = new CompileScriptRunnable(worker);
if (!compiler->Dispatch(aCx)) {
aRv.Throw(NS_ERROR_UNEXPECTED);
return nullptr;
}
worker->mSelfRef = worker;
return worker.forget();
}
NS_IMETHODIMP
WorkerRunnable::Run()
{
bool targetIsWorkerThread = mBehavior == WorkerThreadModifyBusyCount ||
mBehavior == WorkerThreadUnchangedBusyCount;
#ifdef DEBUG
MOZ_ASSERT_IF(mCallingCancelWithinRun, targetIsWorkerThread);
if (targetIsWorkerThread) {
mWorkerPrivate->AssertIsOnWorkerThread();
}
else {
MOZ_ASSERT(mBehavior == ParentThreadUnchangedBusyCount);
mWorkerPrivate->AssertIsOnParentThread();
}
#endif
if (IsCanceled() && !mCallingCancelWithinRun) {
return NS_OK;
}
if (targetIsWorkerThread &&
mWorkerPrivate->AllPendingRunnablesShouldBeCanceled() &&
!IsCanceled() && !mCallingCancelWithinRun) {
// Prevent recursion.
mCallingCancelWithinRun = true;
Cancel();
MOZ_ASSERT(mCallingCancelWithinRun);
mCallingCancelWithinRun = false;
MOZ_ASSERT(IsCanceled(), "Subclass Cancel() didn't set IsCanceled()!");
return NS_OK;
}
// Track down the appropriate global to use for the AutoJSAPI/AutoEntryScript.
nsCOMPtr<nsIGlobalObject> globalObject;
bool isMainThread = !targetIsWorkerThread && !mWorkerPrivate->GetParent();
MOZ_ASSERT(isMainThread == NS_IsMainThread());
nsRefPtr<WorkerPrivate> kungFuDeathGrip;
if (targetIsWorkerThread) {
globalObject = mWorkerPrivate->GlobalScope();
}
else {
kungFuDeathGrip = mWorkerPrivate;
if (isMainThread) {
globalObject = static_cast<nsGlobalWindow*>(mWorkerPrivate->GetWindow());
} else {
globalObject = mWorkerPrivate->GetParent()->GlobalScope();
}
}
// We might run script as part of WorkerRun, so we need an AutoEntryScript.
// This is part of the HTML spec for workers at:
// http://www.whatwg.org/specs/web-apps/current-work/#run-a-worker
// If we don't have a globalObject we have to use an AutoJSAPI instead, but
// this is OK as we won't be running script in these circumstances.
// It's important that aes is declared after jsapi, because if WorkerRun
// creates a global then we construct aes before PostRun and we need them to
// be destroyed in the correct order.
mozilla::dom::AutoJSAPI jsapi;
Maybe<mozilla::dom::AutoEntryScript> aes;
JSContext* cx;
if (globalObject) {
aes.emplace(globalObject, isMainThread, isMainThread ? nullptr :
GetCurrentThreadJSContext());
cx = aes->cx();
} else {
jsapi.Init();
cx = jsapi.cx();
}
// If we're not on the worker thread we'll either be in our parent's
// compartment or the null compartment, so we need to enter our own.
Maybe<JSAutoCompartment> ac;
if (!targetIsWorkerThread && mWorkerPrivate->GetWrapper()) {
ac.emplace(cx, mWorkerPrivate->GetWrapper());
}
bool result = WorkerRun(cx, mWorkerPrivate);
// In the case of CompileScriptRunnnable, WorkerRun above can cause us to
// lazily create a global, so we construct aes here before calling PostRun.
if (targetIsWorkerThread && !aes && mWorkerPrivate->GlobalScope()) {
aes.emplace(mWorkerPrivate->GlobalScope(), false, GetCurrentThreadJSContext());
cx = aes->cx();
}
PostRun(cx, mWorkerPrivate, result);
return result ? NS_OK : NS_ERROR_FAILURE;
}
class CompileScriptRunnable MOZ_FINAL : public WorkerRunnable
{
public:
explicit CompileScriptRunnable(WorkerPrivate* aWorkerPrivate)
: WorkerRunnable(aWorkerPrivate, WorkerThreadModifyBusyCount)
{ }
private:
virtual bool
WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate) MOZ_OVERRIDE
{
JS::Rooted<JSObject*> global(aCx,
aWorkerPrivate->CreateGlobalScope(aCx));
if (!global) {
NS_WARNING("Failed to make global!");
return false;
}
JSAutoCompartment ac(aCx, global);
bool result = scriptloader::LoadWorkerScript(aCx);
if (result) {
aWorkerPrivate->SetWorkerScriptExecutedSuccessfully();
}
return result;
}
};
JSObject*
WorkerPrivate::CreateGlobalScope(JSContext* aCx)
{
AssertIsOnWorkerThread();
nsRefPtr<WorkerGlobalScope> globalScope;
if (IsSharedWorker()) {
globalScope = new SharedWorkerGlobalScope(this, SharedWorkerName());
} else if (IsServiceWorker()) {
globalScope = new ServiceWorkerGlobalScope(this, SharedWorkerName());
} else {
globalScope = new DedicatedWorkerGlobalScope(this);
}
JS::Rooted<JSObject*> global(aCx, globalScope->WrapGlobalObject(aCx));
NS_ENSURE_TRUE(global, nullptr);
JSAutoCompartment ac(aCx, global);
if (!RegisterBindings(aCx, global)) {
return nullptr;
}
mScope = globalScope.forget();
JS_FireOnNewGlobalObject(aCx, global);
return global;
}
InstallEvent in ServiceWorker
void
ServiceWorkerManager::Install(ServiceWorkerRegistrationInfo* aRegistration,
ServiceWorkerInfo* aServiceWorkerInfo)
{
AssertIsOnMainThread();
aRegistration->mInstallingWorker = aServiceWorkerInfo;
MOZ_ASSERT(aRegistration->mInstallingWorker);
InvalidateServiceWorkerRegistrationWorker(aRegistration,
WhichServiceWorker::INSTALLING_WORKER);
nsMainThreadPtrHandle<ServiceWorkerRegistrationInfo> handle(
new nsMainThreadPtrHolder<ServiceWorkerRegistrationInfo>(aRegistration));
nsRefPtr<ServiceWorker> serviceWorker;
nsresult rv =
CreateServiceWorker(aServiceWorkerInfo->GetScriptSpec(),
aRegistration->mScope,
getter_AddRefs(serviceWorker));
if (NS_WARN_IF(NS_FAILED(rv))) {
aRegistration->mInstallingWorker = nullptr;
// We don't need to invalidate here since the upper one will have done it.
return;
}
nsRefPtr<InstallEventRunnable> r =
new InstallEventRunnable(serviceWorker->GetWorkerPrivate(), handle);
AutoSafeJSContext cx;
r->Dispatch(cx);
// When this function exits, although we've lost references to the ServiceWorker,
// which means the underlying WorkerPrivate has no references, the worker
// will stay alive due to the modified busy count until the install event has
// been dispatched.
// NOTE: The worker spec does not require Promises to keep a worker alive, so
// the waitUntil() construct by itself will not keep a worker alive beyond
// the event dispatch. On the other hand, networking, IDB and so on do keep
// the worker alive, so the waitUntil() is only relevant if the Promise is
// gated on those actions. I (nsm) am not sure if it is worth requiring
// a special spec mention saying the install event should keep the worker
// alive indefinitely purely on the basis of calling waitUntil(), since
// a wait is likely to be required only when performing networking or storage
// transactions in the first place.
FireEventOnServiceWorkerRegistrations(aRegistration,
NS_LITERAL_STRING("updatefound"));
}
/*
* Fires 'install' event on the ServiceWorkerGlobalScope. Modifies busy count
* since it fires the event. This is ok since there can't be nested
* ServiceWorkers, so the parent thread -> worker thread requirement for
* runnables is satisfied.
*/
class InstallEventRunnable MOZ_FINAL : public WorkerRunnable
{
nsMainThreadPtrHandle<ServiceWorkerRegistrationInfo> mRegistration;
nsCString mScope;
public:
InstallEventRunnable(
WorkerPrivate* aWorkerPrivate,
const nsMainThreadPtrHandle<ServiceWorkerRegistrationInfo>& aRegistration)
: WorkerRunnable(aWorkerPrivate, WorkerThreadModifyBusyCount),
mRegistration(aRegistration),
mScope(aRegistration.get()->mScope) // copied for access on worker thread.
{
AssertIsOnMainThread();
MOZ_ASSERT(aWorkerPrivate);
}
bool
WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate)
{
MOZ_ASSERT(aWorkerPrivate);
return DispatchInstallEvent(aCx, aWorkerPrivate);
}
private:
bool
DispatchInstallEvent(JSContext* aCx, WorkerPrivate* aWorkerPrivate)
{
aWorkerPrivate->AssertIsOnWorkerThread();
MOZ_ASSERT(aWorkerPrivate->IsServiceWorker());
InstallEventInit init;
init.mBubbles = false;
init.mCancelable = true;
// FIXME(nsm): Bug 982787 pass previous active worker.
nsRefPtr<EventTarget> target = aWorkerPrivate->GlobalScope();
nsRefPtr<InstallEvent> event =
InstallEvent::Constructor(target, NS_LITERAL_STRING("install"), init);
event->SetTrusted(true);
nsRefPtr<Promise> waitUntilPromise;
nsresult rv = target->DispatchDOMEvent(nullptr, event, nullptr, nullptr);
nsCOMPtr<nsIGlobalObject> sgo = aWorkerPrivate->GlobalScope();
if (NS_SUCCEEDED(rv)) {
waitUntilPromise = event->GetPromise();
if (!waitUntilPromise) {
ErrorResult rv;
waitUntilPromise =
Promise::Resolve(sgo,
aCx, JS::UndefinedHandleValue, rv);
}
} else {
ErrorResult rv;
// Continue with a canceled install.
waitUntilPromise = Promise::Reject(sgo, aCx,
JS::UndefinedHandleValue, rv);
}
nsRefPtr<FinishInstallHandler> handler =
new FinishInstallHandler(mRegistration);
waitUntilPromise->AppendNativeHandler(handler);
return true;
}
};
class InstallEvent MOZ_FINAL : public InstallPhaseEvent
{
// FIXME(nsm): Bug 982787 will allow actually populating this.
nsRefPtr<ServiceWorker> mActiveWorker;
protected:
explicit InstallEvent(mozilla::dom::EventTarget* aOwner);
~InstallEvent() {}
public:
NS_DECL_ISUPPORTS_INHERITED
NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(InstallEvent, InstallPhaseEvent)
NS_FORWARD_TO_EVENT
virtual JSObject* WrapObjectInternal(JSContext* aCx) MOZ_OVERRIDE
{
return mozilla::dom::InstallEventBinding_workers::Wrap(aCx, this);
}
static already_AddRefed<InstallEvent>
Constructor(mozilla::dom::EventTarget* aOwner,
const nsAString& aType,
const InstallEventInit& aOptions)
{
nsRefPtr<InstallEvent> e = new InstallEvent(aOwner);
bool trusted = e->Init(aOwner);
e->InitEvent(aType, aOptions.mBubbles, aOptions.mCancelable);
e->SetTrusted(trusted);
e->mActiveWorker = aOptions.mActiveWorker;
return e.forget();
}
static already_AddRefed<InstallEvent>
Constructor(const GlobalObject& aGlobal,
const nsAString& aType,
const InstallEventInit& aOptions,
ErrorResult& aRv)
{
nsCOMPtr<EventTarget> owner = do_QueryInterface(aGlobal.GetAsSupports());
return Constructor(owner, aType, aOptions);
}
already_AddRefed<ServiceWorker>
GetActiveWorker() const
{
nsRefPtr<ServiceWorker> sw = mActiveWorker;
return sw.forget();
}
void
Replace()
{
// FIXME(nsm): Unspecced. Bug 982711
NS_WARNING("Not Implemented");
};
};
class InstallPhaseEvent : public Event
{
nsRefPtr<Promise> mPromise;
protected:
explicit InstallPhaseEvent(mozilla::dom::EventTarget* aOwner);
~InstallPhaseEvent() {}
public:
NS_DECL_ISUPPORTS_INHERITED
NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(InstallPhaseEvent, Event)
NS_FORWARD_TO_EVENT
virtual JSObject* WrapObjectInternal(JSContext* aCx) MOZ_OVERRIDE
{
return mozilla::dom::InstallPhaseEventBinding_workers::Wrap(aCx, this);
}
static already_AddRefed<InstallPhaseEvent>
Constructor(mozilla::dom::EventTarget* aOwner,
const nsAString& aType,
const EventInit& aOptions)
{
nsRefPtr<InstallPhaseEvent> e = new InstallPhaseEvent(aOwner);
bool trusted = e->Init(aOwner);
e->InitEvent(aType, aOptions.mBubbles, aOptions.mCancelable);
e->SetTrusted(trusted);
return e.forget();
}
static already_AddRefed<InstallPhaseEvent>
Constructor(const GlobalObject& aGlobal,
const nsAString& aType,
const EventInit& aOptions,
ErrorResult& aRv)
{
nsCOMPtr<EventTarget> target = do_QueryInterface(aGlobal.GetAsSupports());
return Constructor(target, aType, aOptions);
}
void
WaitUntil(Promise& aPromise);
already_AddRefed<Promise>
GetPromise() const
{
nsRefPtr<Promise> p = mPromise;
return p.forget();
}
};