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"));
}