WebWorkerNote

From MozillaWiki
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"));

}