Bug 1013571 Support PBackground on workers. r=bent
authorBen Kelly <ben@wanderview.com>
Fri, 27 Jun 2014 13:36:25 -0700
changeset 191251 303027a0da95c3ce6674637ee338cd066703462f
parent 191250 a588fb7df4a3b881d62d56ef97b5eaed870fb4c9
child 191252 2c765580762defdff51d639f6c3b1892c63a7f4a
push id45527
push userbkelly@mozilla.com
push dateFri, 27 Jun 2014 20:36:30 +0000
treeherdermozilla-inbound@303027a0da95 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersbent
bugs1013571
milestone33.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1013571 Support PBackground on workers. r=bent
dom/workers/RuntimeService.cpp
ipc/glue/BackgroundChild.h
ipc/glue/BackgroundImpl.cpp
ipc/glue/MessagePump.cpp
--- a/dom/workers/RuntimeService.cpp
+++ b/dom/workers/RuntimeService.cpp
@@ -16,16 +16,17 @@
 #include "nsIScriptContext.h"
 #include "nsIScriptSecurityManager.h"
 #include "nsISupportsPriority.h"
 #include "nsITimer.h"
 #include "nsIURI.h"
 #include "nsPIDOMWindow.h"
 
 #include <algorithm>
+#include "BackgroundChild.h"
 #include "GeckoProfiler.h"
 #include "js/OldDebugAPI.h"
 #include "jsfriendapi.h"
 #include "mozilla/ArrayUtils.h"
 #include "mozilla/CycleCollectedJSRuntime.h"
 #include "mozilla/dom/asmjscache/AsmJSCache.h"
 #include "mozilla/dom/AtomList.h"
 #include "mozilla/dom/BindingUtils.h"
@@ -34,16 +35,17 @@
 #include "mozilla/dom/MessageEventBinding.h"
 #include "mozilla/dom/WorkerBinding.h"
 #include "mozilla/DebugOnly.h"
 #include "mozilla/Preferences.h"
 #include "mozilla/dom/Navigator.h"
 #include "nsContentUtils.h"
 #include "nsCycleCollector.h"
 #include "nsDOMJSUtils.h"
+#include "nsIIPCBackgroundChildCreateCallback.h"
 #include "nsISupportsImpl.h"
 #include "nsLayoutStatics.h"
 #include "nsNetUtil.h"
 #include "nsServiceManagerUtils.h"
 #include "nsThread.h"
 #include "nsThreadUtils.h"
 #include "nsXPCOM.h"
 #include "nsXPCOMPrivate.h"
@@ -58,16 +60,22 @@
 #include "nsThreadManager.h"
 #endif
 
 #include "ServiceWorker.h"
 #include "SharedWorker.h"
 #include "WorkerPrivate.h"
 #include "WorkerRunnable.h"
 
+#ifdef ENABLE_TESTS
+#include "BackgroundChildImpl.h"
+#include "mozilla/ipc/PBackgroundChild.h"
+#include "prrng.h"
+#endif
+
 using namespace mozilla;
 using namespace mozilla::dom;
 
 USING_WORKERS_NAMESPACE
 
 using mozilla::MutexAutoLock;
 using mozilla::MutexAutoUnlock;
 using mozilla::Preferences;
@@ -155,16 +163,20 @@ const JS::ContextOptions kRequiredContex
 uint32_t gMaxWorkersPerDomain = MAX_WORKERS_PER_DOMAIN;
 
 // Does not hold an owning reference.
 RuntimeService* gRuntimeService = nullptr;
 
 // Only non-null during the call to Init.
 RuntimeService* gRuntimeServiceDuringInit = nullptr;
 
+#ifdef ENABLE_TESTS
+bool gTestPBackground = false;
+#endif // ENABLE_TESTS
+
 enum {
   ID_Worker = 0,
   ID_ChromeWorker,
   ID_Event,
   ID_MessageEvent,
   ID_ErrorEvent,
 
   ID_COUNT
@@ -899,16 +911,47 @@ public:
       nsCycleCollector_collect(nullptr);
     }
   }
 
 private:
   WorkerPrivate* mWorkerPrivate;
 };
 
+class WorkerBackgroundChildCallback MOZ_FINAL :
+  public nsIIPCBackgroundChildCreateCallback
+{
+  bool* mDone;
+
+public:
+  WorkerBackgroundChildCallback(bool* aDone)
+  : mDone(aDone)
+  {
+    MOZ_ASSERT(mDone);
+  }
+
+  NS_DECL_ISUPPORTS
+
+private:
+  ~WorkerBackgroundChildCallback()
+  { }
+
+  virtual void
+  ActorCreated(PBackgroundChild* aActor) MOZ_OVERRIDE
+  {
+    *mDone = true;
+  }
+
+  virtual void
+  ActorFailed() MOZ_OVERRIDE
+  {
+    *mDone = true;
+  }
+};
+
 class WorkerThreadPrimaryRunnable MOZ_FINAL : public nsRunnable
 {
   WorkerPrivate* mWorkerPrivate;
   nsRefPtr<RuntimeService::WorkerThread> mThread;
   JSRuntime* mParentRuntime;
 
   class FinishedRunnable MOZ_FINAL : public nsRunnable
   {
@@ -941,16 +984,19 @@ public:
   }
 
   NS_DECL_ISUPPORTS_INHERITED
 
 private:
   ~WorkerThreadPrimaryRunnable()
   { }
 
+  nsresult
+  SynchronouslyCreatePBackground();
+
   NS_DECL_NSIRUNNABLE
 };
 
 class WorkerTaskRunnable MOZ_FINAL : public WorkerRunnable
 {
   nsRefPtr<WorkerTask> mTask;
 
 public:
@@ -1040,16 +1086,38 @@ public:
   void
   SetAcceptingNonWorkerRunnables(bool aAcceptingNonWorkerRunnables)
   {
     MutexAutoLock lock(mLock);
     mAcceptingNonWorkerRunnables = aAcceptingNonWorkerRunnables;
   }
 #endif
 
+#ifdef ENABLE_TESTS
+  void
+  TestPBackground()
+  {
+    using namespace mozilla::ipc;
+    if (gTestPBackground) {
+      // Randomize value to validate workers are not cross-posting messages.
+      uint32_t testValue;
+      PRSize randomSize = PR_GetRandomNoise(&testValue, sizeof(testValue));
+      MOZ_RELEASE_ASSERT(randomSize == sizeof(testValue));
+      nsCString testStr;
+      testStr.AppendInt(testValue);
+      testStr.AppendInt(reinterpret_cast<int64_t>(PR_GetCurrentThread()));
+      PBackgroundChild* existingBackgroundChild =
+        BackgroundChild::GetForCurrentThread();
+      MOZ_RELEASE_ASSERT(existingBackgroundChild);
+      bool ok = existingBackgroundChild->SendPBackgroundTestConstructor(testStr);
+      MOZ_RELEASE_ASSERT(ok);
+    }
+  }
+#endif // #ENABLE_TESTS
+
 private:
   WorkerThread()
   : nsThread(nsThread::NOT_MAIN_THREAD, WORKER_STACK_SIZE),
     mWorkerPrivate(nullptr)
 #ifdef DEBUG
     , mAcceptingNonWorkerRunnables(true)
 #endif
   { }
@@ -1243,16 +1311,20 @@ RuntimeService::GetOrCreateService()
   if (!gRuntimeService) {
     nsRefPtr<RuntimeService> service = new RuntimeService();
     if (NS_FAILED(service->Init())) {
       NS_WARNING("Failed to initialize!");
       service->Cleanup();
       return nullptr;
     }
 
+#ifdef ENABLE_TESTS
+    gTestPBackground = mozilla::Preferences::GetBool("pbackground.testing", false);
+#endif // ENABLE_TESTS
+
     // The observer service now owns us until shutdown.
     gRuntimeService = service;
   }
 
   return gRuntimeService;
 }
 
 // static
@@ -1532,20 +1604,16 @@ RuntimeService::ScheduleWorker(JSContext
   nsCOMPtr<nsIRunnable> runnable =
     new WorkerThreadPrimaryRunnable(aWorkerPrivate, thread, JS_GetParentRuntime(aCx));
   if (NS_FAILED(thread->Dispatch(runnable, NS_DISPATCH_NORMAL))) {
     UnregisterWorker(aCx, aWorkerPrivate);
     JS_ReportError(aCx, "Could not dispatch to thread!");
     return false;
   }
 
-#ifdef DEBUG
-  thread->SetAcceptingNonWorkerRunnables(false);
-#endif
-
   return true;
 }
 
 // static
 void
 RuntimeService::ShutdownIdleThreads(nsITimer* aTimer, void* /* aClosure */)
 {
   AssertIsOnMainThread();
@@ -2487,18 +2555,30 @@ RuntimeService::WorkerThread::Observer::
 }
 
 NS_IMETHODIMP
 RuntimeService::WorkerThread::Observer::OnProcessNextEvent(
                                                nsIThreadInternal* /* aThread */,
                                                bool aMayWait,
                                                uint32_t aRecursionDepth)
 {
+  using mozilla::ipc::BackgroundChild;
+
   mWorkerPrivate->AssertIsOnWorkerThread();
-  MOZ_ASSERT(!aMayWait);
+
+  // If the PBackground child is not created yet, then we must permit
+  // blocking event processing to support SynchronouslyCreatePBackground().
+  // If this occurs then we are spinning on the event queue at the start of
+  // PrimaryWorkerRunnable::Run() and don't want to process the event in
+  // mWorkerPrivate yet.
+  if (aMayWait) {
+    MOZ_ASSERT(aRecursionDepth == 2);
+    MOZ_ASSERT(!BackgroundChild::GetForCurrentThread());
+    return NS_OK;
+  }
 
   mWorkerPrivate->OnProcessNextEvent(aRecursionDepth);
   return NS_OK;
 }
 
 NS_IMETHODIMP
 RuntimeService::WorkerThread::Observer::AfterProcessNextEvent(
                                                nsIThreadInternal* /* aThread */,
@@ -2532,21 +2612,25 @@ LogViolationDetailsRunnable::Run()
   nsRefPtr<MainThreadStopSyncLoopRunnable> response =
     new MainThreadStopSyncLoopRunnable(mWorkerPrivate, mSyncLoopTarget.forget(),
                                        true);
   MOZ_ALWAYS_TRUE(response->Dispatch(nullptr));
 
   return NS_OK;
 }
 
+NS_IMPL_ISUPPORTS(WorkerBackgroundChildCallback, nsIIPCBackgroundChildCreateCallback)
+
 NS_IMPL_ISUPPORTS_INHERITED0(WorkerThreadPrimaryRunnable, nsRunnable)
 
 NS_IMETHODIMP
 WorkerThreadPrimaryRunnable::Run()
 {
+  using mozilla::ipc::BackgroundChild;
+
 #ifdef MOZ_NUWA_PROCESS
   if (IsNuwaProcess()) {
     NS_ASSERTION(NuwaMarkCurrentThread != nullptr,
                   "NuwaMarkCurrentThread is undefined!");
     NuwaMarkCurrentThread(nullptr, nullptr);
     NuwaFreezeCurrentThread();
   }
 #endif
@@ -2555,16 +2639,29 @@ WorkerThreadPrimaryRunnable::Run()
 
   nsAutoCString threadName;
   threadName.AssignLiteral("WebWorker '");
   threadName.Append(NS_LossyConvertUTF16toASCII(mWorkerPrivate->ScriptURL()));
   threadName.Append('\'');
 
   profiler_register_thread(threadName.get(), &stackBaseGuess);
 
+  // Note: SynchronouslyCreatePBackground() must be called prior to
+  //       mThread->SetWorker() in order to avoid accidentally consuming
+  //       worker messages here.
+  nsresult rv = SynchronouslyCreatePBackground();
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    // XXX need to fire an error at parent.
+    return rv;
+  }
+
+#ifdef ENABLE_TESTS
+  mThread->TestPBackground();
+#endif
+
   mThread->SetWorker(mWorkerPrivate);
 
   mWorkerPrivate->AssertIsOnWorkerThread();
 
   {
     nsCycleCollector_startup();
 
     WorkerJSRuntime runtime(mParentRuntime, mWorkerPrivate);
@@ -2588,16 +2685,22 @@ WorkerThreadPrimaryRunnable::Run()
       {
         JSAutoRequest ar(cx);
 
         mWorkerPrivate->DoRunLoop(cx);
 
         JS_ReportPendingException(cx);
       }
 
+#ifdef ENABLE_TESTS
+      mThread->TestPBackground();
+#endif
+
+      BackgroundChild::CloseForCurrentThread();
+
 #ifdef MOZ_ENABLE_PROFILER_SPS
       if (stack) {
         stack->sampleRuntime(nullptr);
       }
 #endif
     }
 
     // Destroy the main context.  This will unroot the main worker global and
@@ -2626,16 +2729,48 @@ WorkerThreadPrimaryRunnable::Run()
     new FinishedRunnable(mThread.forget());
   MOZ_ALWAYS_TRUE(NS_SUCCEEDED(mainThread->Dispatch(finishedRunnable,
                                                     NS_DISPATCH_NORMAL)));
 
   profiler_unregister_thread();
   return NS_OK;
 }
 
+nsresult
+WorkerThreadPrimaryRunnable::SynchronouslyCreatePBackground()
+{
+  using mozilla::ipc::BackgroundChild;
+
+  MOZ_ASSERT(!BackgroundChild::GetForCurrentThread());
+
+  bool done = false;
+  nsCOMPtr<nsIIPCBackgroundChildCreateCallback> callback =
+    new WorkerBackgroundChildCallback(&done);
+
+  if (NS_WARN_IF(!BackgroundChild::GetOrCreateForCurrentThread(callback))) {
+    return NS_ERROR_FAILURE;
+  }
+
+  while (!done) {
+    if (NS_WARN_IF(!NS_ProcessNextEvent(mThread, true /* aMayWay */))) {
+      return NS_ERROR_FAILURE;
+    }
+  }
+
+  if (NS_WARN_IF(!BackgroundChild::GetForCurrentThread())) {
+    return NS_ERROR_FAILURE;
+  }
+
+#ifdef DEBUG
+  mThread->SetAcceptingNonWorkerRunnables(false);
+#endif
+
+  return NS_OK;
+}
+
 NS_IMPL_ISUPPORTS_INHERITED0(WorkerThreadPrimaryRunnable::FinishedRunnable,
                              nsRunnable)
 
 NS_IMETHODIMP
 WorkerThreadPrimaryRunnable::FinishedRunnable::Run()
 {
   AssertIsOnMainThread();
 
--- a/ipc/glue/BackgroundChild.h
+++ b/ipc/glue/BackgroundChild.h
@@ -32,16 +32,21 @@ class PBackgroundChild;
 //
 // Creation of PBackground is asynchronous. GetForCurrentThread() will return
 // null until the sequence is complete. GetOrCreateForCurrentThread() will start
 // the creation sequence and will call back via the
 // nsIIPCBackgroundChildCreateCallback interface when completed. Thereafter
 // (assuming success) GetForCurrentThread() will return the same actor every
 // time.
 //
+// CloseForCurrentThread() will close the current PBackground actor.  Subsequent
+// calls to GetForCurrentThread will return null.  CloseForCurrentThread() may
+// only be called exactly once per thread.  Currently it is illegal to call this
+// before the PBackground actor has been created.
+//
 // The PBackgroundChild actor and all its sub-protocol actors will be
 // automatically destroyed when its designated thread completes.
 class BackgroundChild MOZ_FINAL
 {
   friend class mozilla::dom::ContentChild;
   friend class mozilla::dom::ContentParent;
 
   typedef base::ProcessId ProcessId;
@@ -51,16 +56,20 @@ public:
   // See above.
   static PBackgroundChild*
   GetForCurrentThread();
 
   // See above.
   static bool
   GetOrCreateForCurrentThread(nsIIPCBackgroundChildCreateCallback* aCallback);
 
+  // See above.
+  static void
+  CloseForCurrentThread();
+
 private:
   // Only called by ContentChild or ContentParent.
   static void
   Startup();
 
   // Only called by ContentChild.
   static PBackgroundChild*
   Alloc(Transport* aTransport, ProcessId aOtherProcess);
--- a/ipc/glue/BackgroundImpl.cpp
+++ b/ipc/glue/BackgroundImpl.cpp
@@ -345,16 +345,18 @@ class ChildImpl MOZ_FINAL : public Backg
   static bool sShutdownHasStarted;
 
 #ifdef RELEASE_BUILD
   DebugOnly<nsIThread*> mBoundThread;
 #else
   nsIThread* mBoundThread;
 #endif
 
+  DebugOnly<bool> mActorDestroyed;
+
 public:
   static bool
   OpenProtocolOnMainThread(nsIEventTarget* aEventTarget);
 
   static void
   Shutdown();
 
   void
@@ -367,18 +369,25 @@ public:
 #else
     bool current;
 #endif
     THREADSAFETY_ASSERT(
       NS_SUCCEEDED(mBoundThread->IsOnCurrentThread(&current)));
     THREADSAFETY_ASSERT(current);
   }
 
+  void
+  AssertActorDestroyed()
+  {
+    MOZ_ASSERT(mActorDestroyed, "ChildImpl::ActorDestroy not called in time");
+  }
+
   ChildImpl()
   : mBoundThread(nullptr)
+  , mActorDestroyed(false)
   {
     AssertIsOnMainThread();
   }
 
   NS_INLINE_DECL_REFCOUNTING(ChildImpl)
 
 private:
   // Forwarded from BackgroundChild.
@@ -392,39 +401,56 @@ private:
   // Forwarded from BackgroundChild.
   static PBackgroundChild*
   GetForCurrentThread();
 
   // Forwarded from BackgroundChild.
   static bool
   GetOrCreateForCurrentThread(nsIIPCBackgroundChildCreateCallback* aCallback);
 
+  // Forwarded from BackgroundChild.
+  static void
+  CloseForCurrentThread();
+
   // Forwarded from BackgroundChildImpl.
   static BackgroundChildImpl::ThreadLocal*
   GetThreadLocalForCurrentThread();
 
   static void
   ThreadLocalDestructor(void* aThreadLocal)
   {
     auto threadLocalInfo = static_cast<ThreadLocalInfo*>(aThreadLocal);
 
     if (threadLocalInfo) {
       if (threadLocalInfo->mActor) {
         threadLocalInfo->mActor->Close();
+        threadLocalInfo->mActor->AssertActorDestroyed();
+        // Since the actor is created on the main thread it must only
+        // be released on the main thread as well.
+        if (!NS_IsMainThread()) {
+          ChildImpl* actor;
+          threadLocalInfo->mActor.forget(&actor);
+
+          nsCOMPtr<nsIRunnable> releaser =
+            NS_NewNonOwningRunnableMethod(actor, &ChildImpl::Release);
+          MOZ_ALWAYS_TRUE(NS_SUCCEEDED(NS_DispatchToMainThread(releaser)));
+        }
       }
       delete threadLocalInfo;
     }
   }
 
   static void
   DispatchFailureCallback(nsIEventTarget* aEventTarget);
 
   // This class is reference counted.
   ~ChildImpl()
-  { }
+  {
+    AssertActorDestroyed();
+  }
 
   void
   SetBoundThread()
   {
     THREADSAFETY_ASSERT(!mBoundThread);
 
 #if defined(DEBUG) || !defined(RELEASE_BUILD)
     mBoundThread = NS_GetCurrentThread();
@@ -830,16 +856,23 @@ BackgroundChild::GetForCurrentThread()
 // static
 bool
 BackgroundChild::GetOrCreateForCurrentThread(
                                  nsIIPCBackgroundChildCreateCallback* aCallback)
 {
   return ChildImpl::GetOrCreateForCurrentThread(aCallback);
 }
 
+// static
+void
+BackgroundChild::CloseForCurrentThread()
+{
+  ChildImpl::CloseForCurrentThread();
+}
+
 // -----------------------------------------------------------------------------
 // BackgroundChildImpl Public Methods
 // -----------------------------------------------------------------------------
 
 // static
 BackgroundChildImpl::ThreadLocal*
 BackgroundChildImpl::GetThreadLocalForCurrentThread()
 {
@@ -1573,17 +1606,21 @@ ChildImpl::Alloc(Transport* aTransport, 
 PBackgroundChild*
 ChildImpl::GetForCurrentThread()
 {
   MOZ_ASSERT(sThreadLocalIndex != kBadThreadLocalIndex);
 
   auto threadLocalInfo =
     static_cast<ThreadLocalInfo*>(PR_GetThreadPrivate(sThreadLocalIndex));
 
-  return threadLocalInfo ? threadLocalInfo->mActor : nullptr;
+  if (!threadLocalInfo) {
+    return nullptr;
+  }
+
+  return threadLocalInfo->mActor;
 }
 
 // static
 bool
 ChildImpl::GetOrCreateForCurrentThread(
                                  nsIIPCBackgroundChildCreateCallback* aCallback)
 {
   MOZ_ASSERT(aCallback);
@@ -1638,16 +1675,41 @@ ChildImpl::GetOrCreateForCurrentThread(
     CRASH_IN_CHILD_PROCESS("Failed to dispatch to main thread!");
     return false;
   }
 
   return true;
 }
 
 // static
+void
+ChildImpl::CloseForCurrentThread()
+{
+  MOZ_ASSERT(sThreadLocalIndex != kBadThreadLocalIndex,
+             "BackgroundChild::Startup() was never called!");
+  auto threadLocalInfo =
+    static_cast<ThreadLocalInfo*>(PR_GetThreadPrivate(sThreadLocalIndex));
+
+  // If we don't have a thread local we are in one of these conditions:
+  //   1) Startup has not completed and we are racing
+  //   2) We were called again after a previous close or shutdown
+  // For now, these should not happen, so crash.  We can add extra complexity
+  // in the future if it turns out we need to support these cases.
+  if (!threadLocalInfo) {
+    MOZ_CRASH("Attempting to close a non-existent PBackground actor!");
+  }
+
+  if (threadLocalInfo->mActor) {
+    threadLocalInfo->mActor->FlushPendingInterruptQueue();
+  }
+  DebugOnly<PRStatus> status = PR_SetThreadPrivate(sThreadLocalIndex, nullptr);
+  MOZ_ASSERT(status == PR_SUCCESS);
+}
+
+// static
 BackgroundChildImpl::ThreadLocal*
 ChildImpl::GetThreadLocalForCurrentThread()
 {
   MOZ_ASSERT(sThreadLocalIndex != kBadThreadLocalIndex,
              "BackgroundChild::Startup() was never called!");
 
   auto threadLocalInfo =
     static_cast<ThreadLocalInfo*>(PR_GetThreadPrivate(sThreadLocalIndex));
@@ -1941,16 +2003,17 @@ ChildImpl::DispatchFailureCallback(nsIEv
   }
 }
 
 void
 ChildImpl::ActorDestroy(ActorDestroyReason aWhy)
 {
   AssertIsOnBoundThread();
 
+  mActorDestroyed = true;
   BackgroundChildImpl::ActorDestroy(aWhy);
 }
 
 NS_IMPL_ISUPPORTS(ChildImpl::ShutdownObserver, nsIObserver)
 
 NS_IMETHODIMP
 ChildImpl::ShutdownObserver::Observe(nsISupports* aSubject,
                                      const char* aTopic,
--- a/ipc/glue/MessagePump.cpp
+++ b/ipc/glue/MessagePump.cpp
@@ -2,21 +2,23 @@
  * 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 "MessagePump.h"
 
 #include "nsIRunnable.h"
 #include "nsIThread.h"
 #include "nsITimer.h"
+#include "nsICancelableRunnable.h"
 
 #include "base/basictypes.h"
 #include "base/logging.h"
 #include "base/scoped_nsautorelease_pool.h"
 #include "mozilla/Assertions.h"
+#include "mozilla/AutoRestore.h"
 #include "mozilla/DebugOnly.h"
 #include "nsComponentManagerUtils.h"
 #include "nsDebug.h"
 #include "nsServiceManagerUtils.h"
 #include "nsString.h"
 #include "nsThreadUtils.h"
 #include "nsTimerImpl.h"
 #include "nsXULAppAPI.h"
@@ -35,35 +37,40 @@ using namespace mozilla::ipc;
 
 NS_DEFINE_NAMED_CID(NS_TIMER_CID);
 
 static mozilla::DebugOnly<MessagePump::Delegate*> gFirstDelegate;
 
 namespace mozilla {
 namespace ipc {
 
-class DoWorkRunnable MOZ_FINAL : public nsIRunnable,
+class DoWorkRunnable MOZ_FINAL : public nsICancelableRunnable,
                                  public nsITimerCallback
 {
 public:
   DoWorkRunnable(MessagePump* aPump)
   : mPump(aPump)
+  , mCanceled(false)
+  , mCallingRunWhileCanceled(false)
   {
     MOZ_ASSERT(aPump);
   }
 
   NS_DECL_THREADSAFE_ISUPPORTS
   NS_DECL_NSIRUNNABLE
   NS_DECL_NSITIMERCALLBACK
+  NS_DECL_NSICANCELABLERUNNABLE
 
 private:
   ~DoWorkRunnable()
   { }
 
   MessagePump* mPump;
+  bool mCanceled;
+  bool mCallingRunWhileCanceled;
 };
 
 } /* namespace ipc */
 } /* namespace mozilla */
 
 MessagePump::MessagePump()
 : mThread(nullptr)
 {
@@ -206,21 +213,27 @@ void
 MessagePump::DoDelayedWork(base::MessagePump::Delegate* aDelegate)
 {
   aDelegate->DoDelayedWork(&delayed_work_time_);
   if (!delayed_work_time_.is_null()) {
     ScheduleDelayedWork(delayed_work_time_);
   }
 }
 
-NS_IMPL_ISUPPORTS(DoWorkRunnable, nsIRunnable, nsITimerCallback)
+NS_IMPL_ISUPPORTS(DoWorkRunnable, nsIRunnable, nsITimerCallback,
+                                  nsICancelableRunnable)
 
 NS_IMETHODIMP
 DoWorkRunnable::Run()
 {
+  MOZ_ASSERT(!mCanceled || mCallingRunWhileCanceled);
+  if (mCanceled && !mCallingRunWhileCanceled) {
+    return NS_OK;
+  }
+
   MessageLoop* loop = MessageLoop::current();
   MOZ_ASSERT(loop);
 
   bool nestableTasksAllowed = loop->NestableTasksAllowed();
 
   // MessageLoop::RunTask() disallows nesting, but our Frankenventloop will
   // always dispatch DoWork() below from what looks to MessageLoop like a nested
   // context.  So we unconditionally allow nesting here.
@@ -237,16 +250,33 @@ DoWorkRunnable::Notify(nsITimer* aTimer)
   MessageLoop* loop = MessageLoop::current();
   MOZ_ASSERT(loop);
 
   mPump->DoDelayedWork(loop);
 
   return NS_OK;
 }
 
+NS_IMETHODIMP
+DoWorkRunnable::Cancel()
+{
+  MOZ_ASSERT(!mCanceled);
+  MOZ_ASSERT(!mCallingRunWhileCanceled);
+
+  // Workers require cancelable runnables, but we can't really cancel cleanly
+  // here.  If we don't process all of these then we will leave something
+  // unprocessed in the chromium queue.  Therefore, eagerly complete our work
+  // instead by immediately calling Run().
+  mCanceled = true;
+  mozilla::AutoRestore<bool> guard(mCallingRunWhileCanceled);
+  mCallingRunWhileCanceled = true;
+  Run();
+  return NS_OK;
+}
+
 void
 MessagePumpForChildProcess::Run(base::MessagePump::Delegate* aDelegate)
 {
   if (mFirstRun) {
     MOZ_ASSERT(aDelegate && !gFirstDelegate);
     gFirstDelegate = aDelegate;
 
     mFirstRun = false;