WebWorkerNote

From MozillaWiki
Jump to: navigation, 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;
}

AudioProcessingEvent in WebAudio

class AudioProcessingEvent : public Event
{
public:
  AudioProcessingEvent(ScriptProcessorNode* aOwner,
                       nsPresContext* aPresContext,
                       WidgetEvent* aEvent);

  NS_DECL_ISUPPORTS_INHERITED
  NS_FORWARD_TO_EVENT
  NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(AudioProcessingEvent, Event)

  virtual JSObject* WrapObjectInternal(JSContext* aCx) MOZ_OVERRIDE;

  void InitEvent(AudioBuffer* aInputBuffer,
                 uint32_t aNumberOfInputChannels,
                 double aPlaybackTime)
  {
    InitEvent(NS_LITERAL_STRING("audioprocess"), false, false);
    mInputBuffer = aInputBuffer;
    mNumberOfInputChannels = aNumberOfInputChannels;
    mPlaybackTime = aPlaybackTime;
  }

  double PlaybackTime() const
  {
    return mPlaybackTime;
  }

  AudioBuffer* GetInputBuffer(ErrorResult& aRv)
  {
    if (!mInputBuffer) {
      mInputBuffer = LazilyCreateBuffer(mNumberOfInputChannels, aRv);
    }
    return mInputBuffer;
  }

  AudioBuffer* GetOutputBuffer(ErrorResult& aRv)
  {
    if (!mOutputBuffer) {
      mOutputBuffer = LazilyCreateBuffer(mNode->NumberOfOutputChannels(), aRv);
    }
    return mOutputBuffer;
  }

  bool HasOutputBuffer() const
  {
    return !!mOutputBuffer;
  }

protected:
  virtual ~AudioProcessingEvent();

private:
  already_AddRefed<AudioBuffer>
  LazilyCreateBuffer(uint32_t aNumberOfChannels, ErrorResult& rv);

private:
  double mPlaybackTime;
  nsRefPtr<AudioBuffer> mInputBuffer;
  nsRefPtr<AudioBuffer> mOutputBuffer;
  nsRefPtr<ScriptProcessorNode> mNode;
  uint32_t mNumberOfInputChannels;
};

// This class manages a queue of output buffers shared between
// the main thread and the Media Stream Graph thread.
class SharedBuffers
{
private:
  class OutputQueue
  {
  public:
    explicit OutputQueue(const char* aName)
      : mMutex(aName)
    {}

    size_t SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const
    {
      mMutex.AssertCurrentThreadOwns();

      size_t amount = 0;
      for (size_t i = 0; i < mBufferList.size(); i++) {
        amount += mBufferList[i].SizeOfExcludingThis(aMallocSizeOf, false);
      }

      return amount;
    }

    Mutex& Lock() const { return const_cast<OutputQueue*>(this)->mMutex; }

    size_t ReadyToConsume() const
    {
      mMutex.AssertCurrentThreadOwns();
      MOZ_ASSERT(!NS_IsMainThread());
      return mBufferList.size();
    }

    // Produce one buffer
    AudioChunk& Produce()
    {
      mMutex.AssertCurrentThreadOwns();
      MOZ_ASSERT(NS_IsMainThread());
      mBufferList.push_back(AudioChunk());
      return mBufferList.back();
    }

    // Consumes one buffer.
    AudioChunk Consume()
    {
      mMutex.AssertCurrentThreadOwns();
      MOZ_ASSERT(!NS_IsMainThread());
      MOZ_ASSERT(ReadyToConsume() > 0);
      AudioChunk front = mBufferList.front();
      mBufferList.pop_front();
      return front;
    }

    // Empties the buffer queue.
    void Clear()
    {
      mMutex.AssertCurrentThreadOwns();
      mBufferList.clear();
    }

  private:
    typedef std::deque<AudioChunk> BufferList;

    // Synchronizes access to mBufferList.  Note that it's the responsibility
    // of the callers to perform the required locking, and we assert that every
    // time we access mBufferList.
    Mutex mMutex;
    // The list representing the queue.
    BufferList mBufferList;
  };

public:
  explicit SharedBuffers(float aSampleRate)
    : mOutputQueue("SharedBuffers::outputQueue")
    , mDelaySoFar(STREAM_TIME_MAX)
    , mSampleRate(aSampleRate)
    , mLatency(0.0)
    , mDroppingBuffers(false)
  {
  }

  size_t SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const
  {
    size_t amount = aMallocSizeOf(this);

    {
      MutexAutoLock lock(mOutputQueue.Lock());
      amount += mOutputQueue.SizeOfExcludingThis(aMallocSizeOf);
    }

    return amount;
  }

  // main thread
  void FinishProducingOutputBuffer(ThreadSharedFloatArrayBufferList* aBuffer,
                                   uint32_t aBufferSize)
  {
    MOZ_ASSERT(NS_IsMainThread());

    TimeStamp now = TimeStamp::Now();

    if (mLastEventTime.IsNull()) {
      mLastEventTime = now;
    } else {
      // When the main thread is blocked, and all the event are processed in a
      // burst after the main thread unblocks, the |(now - mLastEventTime)|
      // interval will be very short. |latency - bufferDuration| will be
      // negative, effectively moving back mLatency to a smaller and smaller
      // value, until it crosses zero, at which point we stop dropping buffers
      // and resume normal operation. This does not work if at the same time,
      // the MSG thread was also slowed down, so if the latency on the MSG
      // thread is normal, and we are still dropping buffers, and mLatency is
      // still more than twice the duration of a buffer, we reset it and stop
      // dropping buffers.
      float latency = (now - mLastEventTime).ToSeconds();
      float bufferDuration = aBufferSize / mSampleRate;
      mLatency += latency - bufferDuration;
      mLastEventTime = now;
      if (mLatency > MAX_LATENCY_S ||
          (mDroppingBuffers && mLatency > 0.0 &&
           fabs(latency - bufferDuration) < bufferDuration)) {
        mDroppingBuffers = true;
        return;
      } else {
        if (mDroppingBuffers) {
          mLatency = 0;
        }
        mDroppingBuffers = false;
      }
    }

    MutexAutoLock lock(mOutputQueue.Lock());
    for (uint32_t offset = 0; offset < aBufferSize; offset += WEBAUDIO_BLOCK_SIZE) {
      AudioChunk& chunk = mOutputQueue.Produce();
      if (aBuffer) {
        chunk.mDuration = WEBAUDIO_BLOCK_SIZE;
        chunk.mBuffer = aBuffer;
        chunk.mChannelData.SetLength(aBuffer->GetChannels());
        for (uint32_t i = 0; i < aBuffer->GetChannels(); ++i) {
          chunk.mChannelData[i] = aBuffer->GetData(i) + offset;
        }
        chunk.mVolume = 1.0f;
        chunk.mBufferFormat = AUDIO_FORMAT_FLOAT32;
      } else {
        chunk.SetNull(WEBAUDIO_BLOCK_SIZE);
      }
    }
  }

  // graph thread
  AudioChunk GetOutputBuffer()
  {
    MOZ_ASSERT(!NS_IsMainThread());
    AudioChunk buffer;

    {
      MutexAutoLock lock(mOutputQueue.Lock());
      if (mOutputQueue.ReadyToConsume() > 0) {
        if (mDelaySoFar == STREAM_TIME_MAX) {
          mDelaySoFar = 0;
        }
        buffer = mOutputQueue.Consume();
      } else {
        // If we're out of buffers to consume, just output silence
        buffer.SetNull(WEBAUDIO_BLOCK_SIZE);
        if (mDelaySoFar != STREAM_TIME_MAX) {
          // Remember the delay that we just hit
          mDelaySoFar += WEBAUDIO_BLOCK_SIZE;
        }
      }
    }

    return buffer;
  }

  StreamTime DelaySoFar() const
  {
    MOZ_ASSERT(!NS_IsMainThread());
    return mDelaySoFar == STREAM_TIME_MAX ? 0 : mDelaySoFar;
  }

  void Reset()
  {
    MOZ_ASSERT(!NS_IsMainThread());
    mDelaySoFar = STREAM_TIME_MAX;
    mLatency = 0.0f;
    {
      MutexAutoLock lock(mOutputQueue.Lock());
      mOutputQueue.Clear();
    }
    mLastEventTime = TimeStamp();
  }

private:
  OutputQueue mOutputQueue;
  // How much delay we've seen so far.  This measures the amount of delay
  // caused by the main thread lagging behind in producing output buffers.
  // STREAM_TIME_MAX means that we have not received our first buffer yet.
  StreamTime mDelaySoFar;
  // The samplerate of the context.
  float mSampleRate;
  // This is the latency caused by the buffering. If this grows too high, we
  // will drop buffers until it is acceptable.
  float mLatency;
  // This is the time at which we last produced a buffer, to detect if the main
  // thread has been blocked.
  TimeStamp mLastEventTime;
  // True if we should be dropping buffers.
  bool mDroppingBuffers;
};


  void ScriptProcessorNodeEngine::SendBuffersToMainThread(AudioNodeStream* aStream)
  {
    MOZ_ASSERT(!NS_IsMainThread());

    // we now have a full input buffer ready to be sent to the main thread.
    StreamTime playbackTick = mSource->GetCurrentPosition();
    // Add the duration of the current sample
    playbackTick += WEBAUDIO_BLOCK_SIZE;
    // Add the delay caused by the main thread
    playbackTick += mSharedBuffers->DelaySoFar();
    // Compute the playback time in the coordinate system of the destination
    double playbackTime =
      mSource->DestinationTimeFromTicks(mDestination, playbackTick);

    class Command : public nsRunnable
    {
    public:
      Command(AudioNodeStream* aStream,
              InputChannels& aInputChannels,
              double aPlaybackTime,
              bool aNullInput)
        : mStream(aStream)
        , mPlaybackTime(aPlaybackTime)
        , mNullInput(aNullInput)
      {
        mInputChannels.SetLength(aInputChannels.Length());
        if (!aNullInput) {
          for (uint32_t i = 0; i < mInputChannels.Length(); ++i) {
            mInputChannels[i] = aInputChannels[i].forget();
          }
        }
      }

      NS_IMETHODIMP Run()
      {
        nsRefPtr<ScriptProcessorNode> node = static_cast<ScriptProcessorNode*>
          (mStream->Engine()->NodeMainThread());
        if (!node) {
          return NS_OK;
        }
        AudioContext* context = node->Context();
        if (!context) {
          return NS_OK;
        }

        AutoJSAPI jsapi;
        if (NS_WARN_IF(!jsapi.Init(node->GetOwner()))) {
          return NS_OK;
        }
        JSContext* cx = jsapi.cx();

        // Create the input buffer
        nsRefPtr<AudioBuffer> inputBuffer;
        if (!mNullInput) {
          ErrorResult rv;
          inputBuffer =
            AudioBuffer::Create(context, mInputChannels.Length(),
                                node->BufferSize(),
                                context->SampleRate(), cx, rv);
          if (rv.Failed()) {
            return NS_OK;
          }
          // Put the channel data inside it
          for (uint32_t i = 0; i < mInputChannels.Length(); ++i) {
            inputBuffer->SetRawChannelContents(i, mInputChannels[i]);
          }
        }

        // Ask content to produce data in the output buffer
        // Note that we always avoid creating the output buffer here, and we try to
        // avoid creating the input buffer as well.  The AudioProcessingEvent class
        // knows how to lazily create them if needed once the script tries to access
        // them.  Otherwise, we may be able to get away without creating them!
        nsRefPtr<AudioProcessingEvent> event = new AudioProcessingEvent(node, nullptr, nullptr);
        event->InitEvent(inputBuffer,
                         mInputChannels.Length(),
                         context->StreamTimeToDOMTime(mPlaybackTime));
        node->DispatchTrustedEvent(event);

        // Steal the output buffers if they have been set.
        // Don't create a buffer if it hasn't been used to return output;
        // FinishProducingOutputBuffer() will optimize output = null.
        // GetThreadSharedChannelsForRate() may also return null after OOM.
        nsRefPtr<ThreadSharedFloatArrayBufferList> output;
        if (event->HasOutputBuffer()) {
          ErrorResult rv;
          AudioBuffer* buffer = event->GetOutputBuffer(rv);
          // HasOutputBuffer() returning true means that GetOutputBuffer()
          // will not fail.
          MOZ_ASSERT(!rv.Failed());
          output = buffer->GetThreadSharedChannelsForRate(cx);
        }

        // Append it to our output buffer queue
        node->GetSharedBuffers()->FinishProducingOutputBuffer(output, node->BufferSize());

        return NS_OK;
      }
    private:
      nsRefPtr<AudioNodeStream> mStream;
      InputChannels mInputChannels;
      double mPlaybackTime;
      bool mNullInput;
    };

    NS_DispatchToMainThread(new Command(aStream, mInputChannels,
                                        playbackTime,
                                        !mSeenNonSilenceInput));
  }

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