Merge inbound to m-c
authorWes Kocher <wkocher@mozilla.com>
Mon, 16 Dec 2013 21:33:31 -0800
changeset 160765 b1e5ade62913bf350c0869a938b98ae93c44aaca
parent 160764 07c359a887c2e42781b8dd344a1bac433277ca9c (current diff)
parent 160739 1c6081f57d57ab677daed49eebc069ebada6e93c (diff)
child 160766 d9da95c84b246e266e4d3526ae75a154e428d643
child 160793 07f6abdf6cd71cc2e9b1b59330d6c94b84618784
child 160803 e27d451baafd1016010e688ddcd472468887e421
child 161311 ad12a958aa02ec35e2834d616c8626d6b93af72a
push id37697
push userkwierso@gmail.com
push dateTue, 17 Dec 2013 05:34:45 +0000
treeherdermozilla-inbound@d9da95c84b24 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
milestone29.0a1
first release with
nightly linux32
b1e5ade62913 / 29.0a1 / 20131217030203 / files
nightly linux64
b1e5ade62913 / 29.0a1 / 20131217030203 / files
nightly mac
b1e5ade62913 / 29.0a1 / 20131217030203 / files
nightly win32
b1e5ade62913 / 29.0a1 / 20131217030203 / files
nightly win64
b1e5ade62913 / 29.0a1 / 20131217030203 / files
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
releases
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Merge inbound to m-c
--- a/accessible/src/windows/ia2/moz.build
+++ b/accessible/src/windows/ia2/moz.build
@@ -10,29 +10,34 @@ EXPORTS += [
     'ia2AccessibleComponent.h',
     'ia2AccessibleEditableText.h',
     'ia2AccessibleHyperlink.h',
     'ia2AccessibleHypertext.h',
     'ia2AccessibleText.h',
     'ia2AccessibleValue.h',
 ]
 
-SOURCES += [
+UNIFIED_SOURCES += [
     'ia2Accessible.cpp',
     'ia2AccessibleAction.cpp',
     'ia2AccessibleComponent.cpp',
     'ia2AccessibleEditableText.cpp',
     'ia2AccessibleHyperlink.cpp',
     'ia2AccessibleHypertext.cpp',
     'ia2AccessibleImage.cpp',
     'ia2AccessibleRelation.cpp',
+    'ia2AccessibleText.cpp',
+    'ia2AccessibleValue.cpp',
+]
+
+# These files cannot be built in unified mode because they both include
+# AccessibleTable2_i.c.
+SOURCES += [
     'ia2AccessibleTable.cpp',
     'ia2AccessibleTableCell.cpp',
-    'ia2AccessibleText.cpp',
-    'ia2AccessibleValue.cpp',
 ]
 
 LOCAL_INCLUDES += [
     '../../base',
     '../../generic',
     '../../html',
     '../../xpcom',
     '../../xul',
--- a/accessible/src/windows/msaa/moz.build
+++ b/accessible/src/windows/msaa/moz.build
@@ -9,37 +9,41 @@ EXPORTS += [
 ]
 
 EXPORTS.mozilla.a11y += [
     'AccessibleWrap.h',
     'Compatibility.h',
     'HyperTextAccessibleWrap.h',
 ]
 
-SOURCES += [
+UNIFIED_SOURCES += [
     'AccessibleWrap.cpp',
     'ApplicationAccessibleWrap.cpp',
     'ARIAGridAccessibleWrap.cpp',
     'Compatibility.cpp',
     'DocAccessibleWrap.cpp',
     'EnumVariant.cpp',
     'HTMLTableAccessibleWrap.cpp',
     'HTMLWin32ObjectAccessible.cpp',
     'HyperTextAccessibleWrap.cpp',
     'ImageAccessibleWrap.cpp',
     'IUnknownImpl.cpp',
     'nsWinUtils.cpp',
     'Platform.cpp',
     'RootAccessibleWrap.cpp',
-    'ServiceProvider.cpp',
     'TextLeafAccessibleWrap.cpp',
 ]
 
+# This file cannot be built in unified mode because it includes ISimpleDOMNode_i.c.
+SOURCES += [
+    'ServiceProvider.cpp',
+]
+
 if CONFIG['MOZ_XUL']:
-    SOURCES += [
+    UNIFIED_SOURCES += [
         'XULListboxAccessibleWrap.cpp',
         'XULMenuAccessibleWrap.cpp',
         'XULTreeGridAccessibleWrap.cpp',
     ]
 
 LOCAL_INCLUDES += [
     '../../../../content/base/src',
     '../../../../content/events/src',
--- a/accessible/src/windows/sdn/moz.build
+++ b/accessible/src/windows/sdn/moz.build
@@ -1,15 +1,15 @@
 # -*- Mode: python; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 40 -*-
 # vim: set filetype=python:
 # This Source Code Form is subject to the terms of the Mozilla Public
 # 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/.
 
-SOURCES += [
+UNIFIED_SOURCES += [
     'sdnAccessible.cpp',
     'sdnDocAccessible.cpp',
     'sdnTextAccessible.cpp',
 ]
 
 LOCAL_INCLUDES += [
     '../../base',
     '../../generic',
--- a/config/baseconfig.mk
+++ b/config/baseconfig.mk
@@ -24,12 +24,12 @@ ifneq (4.0-,$(firstword $(sort 4.0- $(MA
 endif
 endif
 ifeq (a,$(firstword a$(subst /, ,$(srcdir))))
 $(error MSYS-style srcdir are not supported for Windows builds.)
 endif
 endif # WINNT
 
 ifdef .PYMAKE
-include_deps = $(eval -includedeps $(1))
+include_deps = $(eval $(if $(2),,-)includedeps $(1))
 else
-include_deps = $(eval -include $(1))
+include_deps = $(eval $(if $(2),,-)include $(1))
 endif
--- a/content/canvas/test/webgl/non-conf-tests/test_no_arr_points.html
+++ b/content/canvas/test/webgl/non-conf-tests/test_no_arr_points.html
@@ -111,21 +111,29 @@ void main(void) {
 
     gl.clear(gl.COLOR_BUFFER_BIT);
     gl.drawArrays(gl.POINTS, hugeFirst, 1);
     ok(!isScreenBlack(), '[' + info + '] drawArrays[huge first] should color pixels.');
 
     checkGLError(ok, info);
 
     var elemTestFunc = todo; // We fail on most implementations.
+    var checkGLTestFunc = todo;
     if (DriverInfo.getDriver() == DriverInfo.DRIVER.ANGLE ||
         DriverInfo.getOS() == DriverInfo.OS.ANDROID)
     {
       // ANGLE and Android slaves seem to work fine.
       elemTestFunc = ok;
+      checkGLTestFunc = ok;
+    }
+    if (DriverInfo.getDriver() == DriverInfo.DRIVER.ANDROID_X86_EMULATOR)
+    {
+      // ...but the Android 4.2 x86 emulator environment is different
+      elemTestFunc = todo;
+      checkGLTestFunc = ok;
     }
 
     // Now for drawElements:
     gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, gl.createBuffer());
     gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, indexArr, gl.STATIC_DRAW);
 
     gl.clear(gl.COLOR_BUFFER_BIT);
     gl.drawElements(gl.POINTS, 1, indexType, 0);
@@ -134,17 +142,17 @@ void main(void) {
     gl.clear(gl.COLOR_BUFFER_BIT);
     gl.drawElements(gl.POINTS, 1, indexType, 1*indexStride);
     elemTestFunc(!isScreenBlack(), '[' + info + '] drawElements[1] should color pixels.');
 
     gl.clear(gl.COLOR_BUFFER_BIT);
     gl.drawElements(gl.POINTS, 1, indexType, 2*indexStride);
     elemTestFunc(!isScreenBlack(), '[' + info + '] drawElements[huge offset] should color pixels.');
 
-    checkGLError(elemTestFunc, info);
+    checkGLError(checkGLTestFunc, info);
   }
 
   // Begin drawing
   gl.clearColor(0.0, 0.0, 0.0, 1.0);
   gl.disable(gl.DEPTH_TEST);
 
   // No-attrib prog:
   gl.useProgram(noAttribProg);
--- a/content/media/mediasource/MediaSourceDecoder.cpp
+++ b/content/media/mediasource/MediaSourceDecoder.cpp
@@ -80,17 +80,21 @@ public:
                 int64_t aCurrentTime) MOZ_OVERRIDE
   {
     return NS_ERROR_NOT_IMPLEMENTED;
   }
 
   nsresult GetBuffered(dom::TimeRanges* aBuffered, int64_t aStartTime) MOZ_OVERRIDE
   {
     // XXX: Merge result with audio reader.
-    return GetVideoReader()->GetBuffered(aBuffered, aStartTime);
+    MediaDecoderReader* reader = GetVideoReader() ? GetVideoReader() : GetAudioReader();
+    if (reader) {
+      return reader->GetBuffered(aBuffered, aStartTime);
+    }
+    return NS_OK;
   }
 
   MediaQueue<AudioData>& AudioQueue() MOZ_OVERRIDE
   {
     // TODO: Share AudioQueue with SubReaders.
     if (GetAudioReader()) {
       return GetAudioReader()->AudioQueue();
     }
--- a/content/media/plugins/MPAPI.h
+++ b/content/media/plugins/MPAPI.h
@@ -6,17 +6,17 @@
 #if !defined(MPAPI_h_)
 #define MPAPI_h_
 
 #include <stdint.h>
 
 namespace MPAPI {
 
 enum ColorFormat {
-  YCbCr,
+  I420,
   RGB565
 };
 
 /*
  * A callback for the plugin to use to request a buffer owned by gecko. This can
  * save us a copy or two down the line.
  */
 class BufferCallback {
--- a/content/media/plugins/MediaPluginReader.cpp
+++ b/content/media/plugins/MediaPluginReader.cpp
@@ -13,16 +13,17 @@
 #include "MediaPluginHost.h"
 #include "MediaDecoderStateMachine.h"
 #include "ImageContainer.h"
 #include "AbstractMediaDecoder.h"
 
 namespace mozilla {
 
 typedef mozilla::layers::Image Image;
+typedef mozilla::layers::PlanarYCbCrImage PlanarYCbCrImage;
 
 MediaPluginReader::MediaPluginReader(AbstractMediaDecoder *aDecoder,
                                      const nsACString& aContentType) :
   MediaDecoderReader(aDecoder),
   mType(aContentType),
   mPlugin(nullptr),
   mHasAudio(false),
   mHasVideo(false),
@@ -165,17 +166,17 @@ bool MediaPluginReader::DecodeVideoFrame
     }
 
     if (frame.mSize == 0)
       return true;
 
     currentImage = bufferCallback.GetImage();
     int64_t pos = mDecoder->GetResource()->Tell();
     nsIntRect picture = mPicture;
- 
+
     nsAutoPtr<VideoData> v;
     if (currentImage) {
       gfx::IntSize frameSize = currentImage->GetSize();
       if (frameSize.width != mInitialFrame.width ||
           frameSize.height != mInitialFrame.height) {
         // Frame size is different from what the container reports. This is legal,
         // and we will preserve the ratio of the crop rectangle as it
         // was reported relative to the picture size reported by the container.
@@ -333,42 +334,88 @@ nsresult MediaPluginReader::Seek(int64_t
 
 MediaPluginReader::ImageBufferCallback::ImageBufferCallback(mozilla::layers::ImageContainer *aImageContainer) :
   mImageContainer(aImageContainer)
 {
 }
 
 void *
 MediaPluginReader::ImageBufferCallback::operator()(size_t aWidth, size_t aHeight,
-                                                     MPAPI::ColorFormat aColorFormat)
+                                                   MPAPI::ColorFormat aColorFormat)
 {
   if (!mImageContainer) {
     NS_WARNING("No image container to construct an image");
     return nullptr;
   }
 
-  nsRefPtr<Image> rgbImage;
+  nsRefPtr<Image> image;
   switch(aColorFormat) {
     case MPAPI::RGB565:
-      rgbImage = mozilla::layers::CreateSharedRGBImage(mImageContainer,
-                                                       nsIntSize(aWidth, aHeight),
-                                                       gfxImageFormatRGB16_565);
-      if (!rgbImage) {
+      image = mozilla::layers::CreateSharedRGBImage(mImageContainer,
+                                                    nsIntSize(aWidth, aHeight),
+                                                    gfxImageFormatRGB16_565);
+      if (!image) {
         NS_WARNING("Could not create rgb image");
         return nullptr;
       }
 
-      mImage = rgbImage;
-      return rgbImage->AsSharedImage()->GetBuffer();
-    case MPAPI::YCbCr:
+      mImage = image;
+      return image->AsSharedImage()->GetBuffer();
+    case MPAPI::I420:
+      return CreateI420Image(aWidth, aHeight);
     default:
       NS_NOTREACHED("Color format not supported");
       return nullptr;
   }
 }
 
+uint8_t *
+MediaPluginReader::ImageBufferCallback::CreateI420Image(size_t aWidth,
+                                                        size_t aHeight)
+{
+  ImageFormat format = PLANAR_YCBCR;
+
+  mImage = mImageContainer->CreateImage(&format, 1 /* numFormats */);
+  PlanarYCbCrImage *yuvImage = static_cast<PlanarYCbCrImage *>(mImage.get());
+
+  if (!yuvImage) {
+    NS_WARNING("Could not create I420 image");
+    return nullptr;
+  }
+
+  size_t frameSize = aWidth * aHeight;
+
+  // Allocate enough for one full resolution Y plane
+  // and two quarter resolution Cb/Cr planes.
+  uint8_t *buffer = yuvImage->AllocateAndGetNewBuffer(frameSize * 3 / 2);
+
+  mozilla::layers::PlanarYCbCrData frameDesc;
+
+  frameDesc.mYChannel = buffer;
+  frameDesc.mCbChannel = buffer + frameSize;
+  frameDesc.mCrChannel = buffer + frameSize * 5 / 4;
+
+  frameDesc.mYSize = gfxIntSize(aWidth, aHeight);
+  frameDesc.mCbCrSize = gfxIntSize(aWidth / 2, aHeight / 2);
+
+  frameDesc.mYStride = aWidth;
+  frameDesc.mCbCrStride = aWidth / 2;
+
+  frameDesc.mYSkip = 0;
+  frameDesc.mCbSkip = 0;
+  frameDesc.mCrSkip = 0;
+
+  frameDesc.mPicX = 0;
+  frameDesc.mPicY = 0;
+  frameDesc.mPicSize = gfxIntSize(aWidth, aHeight);
+
+  yuvImage->SetDataNoCopy(frameDesc);
+
+  return buffer;
+}
+
 already_AddRefed<Image>
 MediaPluginReader::ImageBufferCallback::GetImage()
 {
   return mImage.forget();
 }
 
 } // namespace mozilla
--- a/content/media/plugins/MediaPluginReader.h
+++ b/content/media/plugins/MediaPluginReader.h
@@ -60,24 +60,30 @@ public:
   virtual bool HasVideo()
   {
     return mHasVideo;
   }
 
   virtual nsresult ReadMetadata(MediaInfo* aInfo,
                                 MetadataTags** aTags);
   virtual nsresult Seek(int64_t aTime, int64_t aStartTime, int64_t aEndTime, int64_t aCurrentTime);
+
   class ImageBufferCallback : public MPAPI::BufferCallback {
     typedef mozilla::layers::Image Image;
+
   public:
     ImageBufferCallback(mozilla::layers::ImageContainer *aImageContainer);
     void *operator()(size_t aWidth, size_t aHeight,
                      MPAPI::ColorFormat aColorFormat) MOZ_OVERRIDE;
     already_AddRefed<Image> GetImage();
+
   private:
+    uint8_t *CreateI420Image(size_t aWidth, size_t aHeight);
+
     mozilla::layers::ImageContainer *mImageContainer;
     nsRefPtr<Image> mImage;
   };
+
 };
 
 } // namespace mozilla
 
 #endif
--- a/dom/bindings/Makefile.in
+++ b/dom/bindings/Makefile.in
@@ -53,21 +53,20 @@ CSS2Properties.webidl: $(css2properties_
 # The generated .pp file contains all the important dependencies such as
 # changes to .webidl or .py files should result in code generation being
 # performed.
 codegen_dependencies := \
   $(nonstatic_webidl_files) \
   $(GLOBAL_DEPS) \
   $(NULL)
 
-$(call include_deps,codegen.pp)
+# The 1 is to make codegen.pp not optional.
+$(call include_deps,codegen.pp,1)
 
-codegen.pp: codegen.done
-
-codegen.done: $(codegen_dependencies)
+codegen.pp: $(codegen_dependencies)
 	$(call py_action,webidl,$(srcdir))
 	@$(TOUCH) $@
 
 .PHONY: compiletests
 compiletests:
 	$(call SUBMAKE,libs,test)
 
 GARBAGE += \
--- a/dom/bindings/mozwebidlcodegen/__init__.py
+++ b/dom/bindings/mozwebidlcodegen/__init__.py
@@ -541,17 +541,17 @@ def create_build_system_manager(topsrcdi
         os.path.join(src_dir, 'Bindings.conf'),
         inputs,
         os.path.join(dist_dir, 'include', 'mozilla', 'dom'),
         obj_dir,
         os.path.join(obj_dir, 'codegen.json'),
         cache_dir=cache_dir,
         # The make rules include a codegen.pp file containing dependencies.
         make_deps_path=os.path.join(obj_dir, 'codegen.pp'),
-        make_deps_target='codegen.done',
+        make_deps_target='codegen.pp',
     )
 
 
 class BuildSystemWebIDL(MozbuildObject):
     @property
     def manager(self):
         if not hasattr(self, '_webidl_manager'):
             self._webidl_manager = create_build_system_manager(
--- a/dom/promise/Promise.cpp
+++ b/dom/promise/Promise.cpp
@@ -10,16 +10,17 @@
 #include "mozilla/dom/OwningNonNull.h"
 #include "mozilla/dom/PromiseBinding.h"
 #include "mozilla/Preferences.h"
 #include "PromiseCallback.h"
 #include "PromiseNativeHandler.h"
 #include "nsContentUtils.h"
 #include "nsPIDOMWindow.h"
 #include "WorkerPrivate.h"
+#include "WorkerRunnable.h"
 #include "nsJSPrincipals.h"
 #include "nsJSUtils.h"
 #include "nsPIDOMWindow.h"
 #include "nsJSEnvironment.h"
 
 namespace mozilla {
 namespace dom {
 
@@ -55,18 +56,17 @@ public:
 private:
   nsRefPtr<Promise> mPromise;
 };
 
 class WorkerPromiseTask MOZ_FINAL : public WorkerRunnable
 {
 public:
   WorkerPromiseTask(WorkerPrivate* aWorkerPrivate, Promise* aPromise)
-    : WorkerRunnable(aWorkerPrivate, WorkerThread,
-                     UnchangedBusyCount, SkipWhenClearing)
+    : WorkerRunnable(aWorkerPrivate, WorkerThreadUnchangedBusyCount)
     , mPromise(aPromise)
   {
     MOZ_ASSERT(aPromise);
     MOZ_COUNT_CTOR(WorkerPromiseTask);
   }
 
   ~WorkerPromiseTask()
   {
@@ -164,18 +164,17 @@ public:
 class WorkerPromiseResolverTask MOZ_FINAL : public WorkerRunnable,
                                             public PromiseResolverMixin
 {
 public:
   WorkerPromiseResolverTask(WorkerPrivate* aWorkerPrivate,
                             Promise* aPromise,
                             JS::Handle<JS::Value> aValue,
                             Promise::PromiseState aState)
-    : WorkerRunnable(aWorkerPrivate, WorkerThread,
-                     UnchangedBusyCount, SkipWhenClearing),
+    : WorkerRunnable(aWorkerPrivate, WorkerThreadUnchangedBusyCount),
       PromiseResolverMixin(aPromise, aValue, aState)
   {}
 
   ~WorkerPromiseResolverTask()
   {}
 
   bool
   WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate)
--- a/dom/workers/MessagePort.cpp
+++ b/dom/workers/MessagePort.cpp
@@ -6,16 +6,17 @@
 #include "MessagePort.h"
 
 #include "mozilla/dom/MessagePortBinding.h"
 #include "nsDOMEvent.h"
 #include "nsEventDispatcher.h"
 
 #include "SharedWorker.h"
 #include "WorkerPrivate.h"
+#include "WorkerRunnable.h"
 
 using mozilla::dom::EventHandlerNonNull;
 using mozilla::dom::MessagePortBase;
 using mozilla::dom::Optional;
 using mozilla::dom::Sequence;
 
 USING_WORKERS_NAMESPACE
 
@@ -23,23 +24,20 @@ namespace {
 
 class DelayedEventRunnable MOZ_FINAL : public WorkerRunnable
 {
   nsRefPtr<MessagePort> mMessagePort;
   nsTArray<nsCOMPtr<nsIDOMEvent>> mEvents;
 
 public:
   DelayedEventRunnable(WorkerPrivate* aWorkerPrivate,
-                       Target aTarget,
+                       TargetAndBusyBehavior aBehavior,
                        MessagePort* aMessagePort,
                        nsTArray<nsCOMPtr<nsIDOMEvent>>& aEvents)
-  : WorkerRunnable(aWorkerPrivate, aTarget,
-                   aTarget == WorkerThread ? ModifyBusyCount : UnchangedBusyCount,
-                   SkipWhenClearing),
-    mMessagePort(aMessagePort)
+  : WorkerRunnable(aWorkerPrivate, aBehavior), mMessagePort(aMessagePort)
   {
     AssertIsOnMainThread();
     MOZ_ASSERT(aMessagePort);
     MOZ_ASSERT(aEvents.Length());
 
     mEvents.SwapElements(aEvents);
   }
 
@@ -104,26 +102,32 @@ MessagePort::Start()
 
   if (mStarted) {
     return;
   }
 
   mStarted = true;
 
   if (!mQueuedEvents.IsEmpty()) {
-    WorkerRunnable::Target target = WorkerRunnable::WorkerThread;
-    WorkerPrivate* workerPrivate = mWorkerPrivate;
+    WorkerPrivate* workerPrivate;
+    WorkerRunnable::TargetAndBusyBehavior behavior;
 
-    if (!workerPrivate) {
-      target = WorkerRunnable::ParentThread;
+    if (mWorkerPrivate) {
+      workerPrivate = mWorkerPrivate;
+      behavior = WorkerRunnable::WorkerThreadModifyBusyCount;
+    }
+    else {
       workerPrivate = mSharedWorker->GetWorkerPrivate();
+      MOZ_ASSERT(workerPrivate);
+
+      behavior = WorkerRunnable::ParentThreadUnchangedBusyCount;
     }
 
     nsRefPtr<DelayedEventRunnable> runnable =
-      new DelayedEventRunnable(workerPrivate, target, this, mQueuedEvents);
+      new DelayedEventRunnable(workerPrivate, behavior, this, mQueuedEvents);
     runnable->Dispatch(nullptr);
   }
 }
 
 void
 MessagePort::Close()
 {
   AssertCorrectThread();
--- a/dom/workers/RuntimeService.cpp
+++ b/dom/workers/RuntimeService.cpp
@@ -38,30 +38,36 @@
 #include "mozilla/Preferences.h"
 #include "mozilla/dom/Navigator.h"
 #include "nsContentUtils.h"
 #include "nsCycleCollector.h"
 #include "nsDOMJSUtils.h"
 #include "nsLayoutStatics.h"
 #include "nsNetUtil.h"
 #include "nsServiceManagerUtils.h"
+#include "nsThread.h"
 #include "nsThreadUtils.h"
 #include "nsTraceRefcnt.h"
 #include "nsXPCOM.h"
 #include "nsXPCOMPrivate.h"
 #include "OSFileConstants.h"
 #include "xpcpublic.h"
 
-#include "SharedWorker.h"
-#include "WorkerPrivate.h"
-
 #ifdef MOZ_NUWA_PROCESS
 #include "ipc/Nuwa.h"
 #endif
 
+#ifdef DEBUG
+#include "nsThreadManager.h"
+#endif
+
+#include "SharedWorker.h"
+#include "WorkerPrivate.h"
+#include "WorkerRunnable.h"
+
 using namespace mozilla;
 using namespace mozilla::dom;
 
 USING_WORKERS_NAMESPACE
 
 using mozilla::MutexAutoLock;
 using mozilla::MutexAutoUnlock;
 using mozilla::Preferences;
@@ -588,139 +594,86 @@ LoadJITHardeningOption(const char* /* aP
     rts->UpdateAllWorkerJITHardening(value);
   }
 }
 
 void
 ErrorReporter(JSContext* aCx, const char* aMessage, JSErrorReport* aReport)
 {
   WorkerPrivate* worker = GetWorkerPrivateFromContext(aCx);
+  MOZ_ASSERT(worker);
+
   return worker->ReportError(aCx, aMessage, aReport);
 }
 
 bool
 OperationCallback(JSContext* aCx)
 {
   WorkerPrivate* worker = GetWorkerPrivateFromContext(aCx);
+  MOZ_ASSERT(worker);
 
   // Now is a good time to turn on profiling if it's pending.
   profiler_js_operation_callback();
 
   return worker->OperationCallback(aCx);
 }
 
-class LogViolationDetailsRunnable : public nsRunnable
+class LogViolationDetailsRunnable MOZ_FINAL : public nsRunnable
 {
   WorkerPrivate* mWorkerPrivate;
+  nsCOMPtr<nsIEventTarget> mSyncLoopTarget;
   nsString mFileName;
   uint32_t mLineNum;
-  uint32_t mSyncQueueKey;
-
-private:
-  class LogViolationDetailsResponseRunnable : public WorkerSyncRunnable
-  {
-    uint32_t mSyncQueueKey;
-
-  public:
-    LogViolationDetailsResponseRunnable(WorkerPrivate* aWorkerPrivate,
-                                        uint32_t aSyncQueueKey)
-    : WorkerSyncRunnable(aWorkerPrivate, aSyncQueueKey, false),
-      mSyncQueueKey(aSyncQueueKey)
-    {
-      NS_ASSERTION(aWorkerPrivate, "Don't hand me a null WorkerPrivate!");
-    }
-
-    bool
-    WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate)
-    {
-      aWorkerPrivate->StopSyncLoop(mSyncQueueKey, true);
-      return true;
-    }
-
-    bool
-    PreDispatch(JSContext* aCx, WorkerPrivate* aWorkerPrivate)
-    {
-      AssertIsOnMainThread();
-      return true;
-    }
-
-    void
-    PostDispatch(JSContext* aCx, WorkerPrivate* aWorkerPrivate,
-                 bool aDispatchResult)
-    {
-      AssertIsOnMainThread();
-    }
-  };
 
 public:
   LogViolationDetailsRunnable(WorkerPrivate* aWorker,
                               const nsString& aFileName,
                               uint32_t aLineNum)
-  : mWorkerPrivate(aWorker),
-    mFileName(aFileName),
-    mLineNum(aLineNum),
-    mSyncQueueKey(0)
+  : mWorkerPrivate(aWorker), mFileName(aFileName), mLineNum(aLineNum)
   {
-    NS_ASSERTION(aWorker, "WorkerPrivate cannot be null");
+    MOZ_ASSERT(aWorker);
   }
 
+  NS_DECL_ISUPPORTS_INHERITED
+
   bool
   Dispatch(JSContext* aCx)
   {
     AutoSyncLoopHolder syncLoop(mWorkerPrivate);
-    mSyncQueueKey = syncLoop.SyncQueueKey();
+
+    mSyncLoopTarget = syncLoop.EventTarget();
+    MOZ_ASSERT(mSyncLoopTarget);
 
     if (NS_FAILED(NS_DispatchToMainThread(this, NS_DISPATCH_NORMAL))) {
       JS_ReportError(aCx, "Failed to dispatch to main thread!");
       return false;
     }
 
-    return syncLoop.RunAndForget(aCx);
+    return syncLoop.Run();
   }
 
-  NS_IMETHOD
-  Run()
-  {
-    AssertIsOnMainThread();
-
-    nsIContentSecurityPolicy* csp = mWorkerPrivate->GetCSP();
-    if (csp) {
-      NS_NAMED_LITERAL_STRING(scriptSample,
-         "Call to eval() or related function blocked by CSP.");
-      if (mWorkerPrivate->GetReportCSPViolations()) {
-        csp->LogViolationDetails(nsIContentSecurityPolicy::VIOLATION_TYPE_EVAL,
-                                 mFileName, scriptSample, mLineNum, EmptyString());
-      }
-    }
-
-    nsRefPtr<LogViolationDetailsResponseRunnable> response =
-        new LogViolationDetailsResponseRunnable(mWorkerPrivate, mSyncQueueKey);
-    if (!response->Dispatch(nullptr)) {
-      NS_WARNING("Failed to dispatch response!");
-    }
-
-    return NS_OK;
-  }
+private:
+  NS_DECL_NSIRUNNABLE
 };
 
 bool
 ContentSecurityPolicyAllows(JSContext* aCx)
 {
   WorkerPrivate* worker = GetWorkerPrivateFromContext(aCx);
   worker->AssertIsOnWorkerThread();
 
   if (worker->GetReportCSPViolations()) {
     nsString fileName;
     uint32_t lineNum = 0;
 
     JS::Rooted<JSScript*> script(aCx);
     const char* file;
     if (JS_DescribeScriptedCaller(aCx, &script, &lineNum) &&
         (file = JS_GetScriptFilename(aCx, script))) {
-      fileName.AssignASCII(file);
+      fileName = NS_ConvertUTF8toUTF16(file);
     } else {
       JS_ReportPendingException(aCx);
     }
 
     nsRefPtr<LogViolationDetailsRunnable> runnable =
         new LogViolationDetailsRunnable(worker, fileName, lineNum);
 
     if (!runnable->Dispatch(aCx)) {
@@ -954,91 +907,168 @@ public:
       nsCycleCollector_collect(nullptr);
     }
   }
 
 private:
   WorkerPrivate* mWorkerPrivate;
 };
 
-class WorkerThreadRunnable : public nsRunnable
+class WorkerThreadPrimaryRunnable MOZ_FINAL : public nsRunnable
 {
   WorkerPrivate* mWorkerPrivate;
+  nsRefPtr<RuntimeService::WorkerThread> mThread;
+
+  class FinishedRunnable MOZ_FINAL : public nsRunnable
+  {
+    nsRefPtr<RuntimeService::WorkerThread> mThread;
+
+  public:
+    FinishedRunnable(already_AddRefed<RuntimeService::WorkerThread> aThread)
+    : mThread(aThread)
+    {
+      MOZ_ASSERT(mThread);
+    }
+
+    NS_DECL_ISUPPORTS_INHERITED
+
+  private:
+    ~FinishedRunnable()
+    { }
+
+    NS_DECL_NSIRUNNABLE
+  };
 
 public:
-  WorkerThreadRunnable(WorkerPrivate* aWorkerPrivate)
-  : mWorkerPrivate(aWorkerPrivate)
-  {
-    NS_ASSERTION(mWorkerPrivate, "This should never be null!");
-  }
-
-  NS_IMETHOD
-  Run()
+  WorkerThreadPrimaryRunnable(WorkerPrivate* aWorkerPrivate,
+                              RuntimeService::WorkerThread* aThread)
+  : mWorkerPrivate(aWorkerPrivate), mThread(aThread)
   {
-#ifdef MOZ_NUWA_PROCESS
-    if (IsNuwaProcess()) {
-      NS_ASSERTION(NuwaMarkCurrentThread != nullptr,
-                   "NuwaMarkCurrentThread is undefined!");
-      NuwaMarkCurrentThread(nullptr, nullptr);
-      NuwaFreezeCurrentThread();
-    }
-#endif
-    WorkerPrivate* workerPrivate = mWorkerPrivate;
-    mWorkerPrivate = nullptr;
-
-    workerPrivate->AssertIsOnWorkerThread();
-
-    {
-      nsCycleCollector_startup();
-
-      WorkerJSRuntime runtime(workerPrivate);
-      JSRuntime* rt = runtime.Runtime();
-      JSContext* cx = CreateJSContextForWorker(workerPrivate, rt);
-      if (!cx) {
-        // XXX need to fire an error at parent.
-        NS_ERROR("Failed to create runtime and context!");
-        return NS_ERROR_FAILURE;
-      }
-
-      char aLocal;
-      profiler_register_thread("WebWorker", &aLocal);
-  #ifdef MOZ_ENABLE_PROFILER_SPS
-      if (PseudoStack* stack = mozilla_get_pseudo_stack())
-        stack->sampleRuntime(rt);
-  #endif
-
-      {
-        JSAutoRequest ar(cx);
-        workerPrivate->DoRunLoop(cx);
-      }
-
-      // Destroy the main context.  This will unroot the main worker global and
-      // GC.  This is not the last JSContext (WorkerJSRuntime maintains an
-      // internal JSContext).
-      JS_DestroyContext(cx);
-
-      // Now WorkerJSRuntime goes out of scope and its destructor will shut
-      // down the cycle collector and destroy the final JSContext.  This
-      // breaks any remaining cycles and collects the C++ and JS objects
-      // participating.
-    }
-
-#ifdef MOZ_ENABLE_PROFILER_SPS
-    if (PseudoStack* stack = mozilla_get_pseudo_stack())
-      stack->sampleRuntime(nullptr);
-#endif
-
-    workerPrivate->ScheduleDeletion(false);
-    profiler_unregister_thread();
-    return NS_OK;
+    MOZ_ASSERT(aWorkerPrivate);
+    MOZ_ASSERT(aThread);
+  }
+
+  NS_DECL_ISUPPORTS_INHERITED
+
+private:
+  ~WorkerThreadPrimaryRunnable()
+  { }
+
+  NS_DECL_NSIRUNNABLE
+};
+
+class WorkerTaskRunnable MOZ_FINAL : public WorkerRunnable
+{
+  nsRefPtr<WorkerTask> mTask;
+
+public:
+  WorkerTaskRunnable(WorkerPrivate* aWorkerPrivate, WorkerTask* aTask)
+  : WorkerRunnable(aWorkerPrivate, WorkerThreadUnchangedBusyCount), mTask(aTask)
+  {
+    MOZ_ASSERT(aTask);
+  }
+
+private:
+  virtual bool
+  PreDispatch(JSContext* aCx, WorkerPrivate* aWorkerPrivate) MOZ_OVERRIDE
+  {
+    // May be called on any thread!
+    return true;
+  }
+
+  virtual void
+  PostDispatch(JSContext* aCx, WorkerPrivate* aWorkerPrivate,
+               bool aDispatchResult) MOZ_OVERRIDE
+  {
+    // May be called on any thread!
+  }
+
+  virtual bool
+  WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate) MOZ_OVERRIDE
+  {
+    return mTask->RunTask(aCx);
   }
 };
 
 } /* anonymous namespace */
 
+class RuntimeService::WorkerThread MOZ_FINAL : public nsThread
+{
+  class Observer MOZ_FINAL : public nsIThreadObserver
+  {
+    WorkerPrivate* mWorkerPrivate;
+
+  public:
+    Observer(WorkerPrivate* aWorkerPrivate)
+    : mWorkerPrivate(aWorkerPrivate)
+    {
+      MOZ_ASSERT(aWorkerPrivate);
+      aWorkerPrivate->AssertIsOnWorkerThread();
+    }
+
+    NS_DECL_THREADSAFE_ISUPPORTS
+
+  private:
+    ~Observer()
+    {
+      mWorkerPrivate->AssertIsOnWorkerThread();
+    }
+
+    NS_DECL_NSITHREADOBSERVER
+  };
+
+  WorkerPrivate* mWorkerPrivate;
+  nsRefPtr<Observer> mObserver;
+
+#ifdef DEBUG
+  // Protected by nsThread::mLock.
+  bool mAcceptingNonWorkerRunnables;
+#endif
+
+public:
+  static already_AddRefed<WorkerThread>
+  Create();
+
+  void
+  SetWorker(WorkerPrivate* aWorkerPrivate);
+
+  NS_DECL_ISUPPORTS_INHERITED
+
+  NS_IMETHOD
+  Dispatch(nsIRunnable* aRunnable, uint32_t aFlags) MOZ_OVERRIDE;
+
+#ifdef DEBUG
+  bool
+  IsAcceptingNonWorkerRunnables()
+  {
+    MutexAutoLock lock(mLock);
+    return mAcceptingNonWorkerRunnables;
+  }
+
+  void
+  SetAcceptingNonWorkerRunnables(bool aAcceptingNonWorkerRunnables)
+  {
+    MutexAutoLock lock(mLock);
+    mAcceptingNonWorkerRunnables = aAcceptingNonWorkerRunnables;
+  }
+#endif
+
+private:
+  WorkerThread()
+  : nsThread(nsThread::NOT_MAIN_THREAD, WORKER_STACK_SIZE),
+    mWorkerPrivate(nullptr)
+#ifdef DEBUG
+    , mAcceptingNonWorkerRunnables(true)
+#endif
+  { }
+
+  ~WorkerThread()
+  { }
+};
+
 BEGIN_WORKERS_NAMESPACE
 
 // Entry point for main thread non-window globals.
 bool
 ResolveWorkerClasses(JSContext* aCx, JS::Handle<JSObject*> aObj, JS::Handle<jsid> aId,
                      unsigned aFlags, JS::MutableHandle<JSObject*> aObjp)
 {
   AssertIsOnMainThread();
@@ -1108,81 +1138,74 @@ ResumeWorkersForWindow(nsPIDOMWindow* aW
 {
   AssertIsOnMainThread();
   RuntimeService* runtime = RuntimeService::GetService();
   if (runtime) {
     runtime->ResumeWorkersForWindow(aWindow);
   }
 }
 
-namespace {
-
-class WorkerTaskRunnable : public WorkerRunnable
+WorkerCrossThreadDispatcher::WorkerCrossThreadDispatcher(
+                                                  WorkerPrivate* aWorkerPrivate)
+: mMutex("WorkerCrossThreadDispatcher::mMutex"),
+  mWorkerPrivate(aWorkerPrivate)
 {
-public:
-  WorkerTaskRunnable(WorkerPrivate* aPrivate, WorkerTask* aTask)
-    : WorkerRunnable(aPrivate, WorkerThread, UnchangedBusyCount,
-                     SkipWhenClearing),
-      mTask(aTask)
-  { }
-
-  virtual bool PreDispatch(JSContext* aCx, WorkerPrivate* aWorkerPrivate) {
-    return true;
-  }
-
-  virtual void PostDispatch(JSContext* aCx, WorkerPrivate* aWorkerPrivate,
-                            bool aDispatchResult)
-  { }
-
-  virtual bool WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate);
-
-private:
-  nsRefPtr<WorkerTask> mTask;
-};
-
-bool
-WorkerTaskRunnable::WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate)
-{
-  return mTask->RunTask(aCx);
-}
-
+  MOZ_ASSERT(aWorkerPrivate);
 }
 
 bool
 WorkerCrossThreadDispatcher::PostTask(WorkerTask* aTask)
 {
-  mozilla::MutexAutoLock lock(mMutex);
-  if (!mPrivate) {
+  MOZ_ASSERT(aTask);
+
+  MutexAutoLock lock(mMutex);
+
+  if (!mWorkerPrivate) {
+    NS_WARNING("Posted a task to a WorkerCrossThreadDispatcher that is no "
+               "longer accepting tasks!");
     return false;
   }
 
-  nsRefPtr<WorkerTaskRunnable> runnable = new WorkerTaskRunnable(mPrivate, aTask);
-  runnable->Dispatch(nullptr);
-  return true;
+  nsRefPtr<WorkerTaskRunnable> runnable =
+    new WorkerTaskRunnable(mWorkerPrivate, aTask);
+  return runnable->Dispatch(nullptr);
 }
 
 WorkerPrivate*
 GetWorkerPrivateFromContext(JSContext* aCx)
 {
-  NS_ASSERTION(!NS_IsMainThread(), "Wrong thread!");
-  return static_cast<WorkerThreadRuntimePrivate*>(JS_GetRuntimePrivate(JS_GetRuntime(aCx)))->mWorkerPrivate;
+  MOZ_ASSERT(!NS_IsMainThread());
+  MOZ_ASSERT(aCx);
+
+  JSRuntime* rt = JS_GetRuntime(aCx);
+  MOZ_ASSERT(rt);
+
+  void* rtPrivate = JS_GetRuntimePrivate(rt);
+  MOZ_ASSERT(rtPrivate);
+
+  return static_cast<WorkerThreadRuntimePrivate*>(rtPrivate)->mWorkerPrivate;
 }
 
 WorkerPrivate*
 GetCurrentThreadWorkerPrivate()
 {
-  MOZ_ASSERT(!NS_IsMainThread(), "Wrong thread!");
+  MOZ_ASSERT(!NS_IsMainThread());
+
   CycleCollectedJSRuntime* ccrt = CycleCollectedJSRuntime::Get();
   if (!ccrt) {
     return nullptr;
   }
 
   JSRuntime* rt = ccrt->Runtime();
-  return static_cast<WorkerThreadRuntimePrivate*>(JS_GetRuntimePrivate(rt))->
-    mWorkerPrivate;
+  MOZ_ASSERT(rt);
+
+  void* rtPrivate = JS_GetRuntimePrivate(rt);
+  MOZ_ASSERT(rtPrivate);
+
+  return static_cast<WorkerThreadRuntimePrivate*>(rtPrivate)->mWorkerPrivate;
 }
 
 bool
 IsCurrentThreadRunningChromeWorker()
 {
   return GetCurrentThreadWorkerPrivate()->UsesSystemPrincipal();
 }
 
@@ -1422,17 +1445,17 @@ RuntimeService::UnregisterWorker(JSConte
         domainInfo->mChildWorkerCount++;
       }
       else {
         domainInfo->mActiveWorkers.AppendElement(queuedWorker);
       }
     }
 
     if (!domainInfo->ActiveWorkerCount()) {
-      NS_ASSERTION(domainInfo->mQueuedWorkers.IsEmpty(), "Huh?!");
+      MOZ_ASSERT(domainInfo->mQueuedWorkers.IsEmpty());
       mDomainMap.Remove(domain);
     }
   }
 
   if (aWorkerPrivate->IsSharedWorker()) {
     AssertIsOnMainThread();
 
     nsAutoTArray<nsRefPtr<SharedWorker>, 5> sharedWorkersToNotify;
@@ -1450,26 +1473,21 @@ RuntimeService::UnregisterWorker(JSConte
   else if (aWorkerPrivate->IsSharedWorker()) {
     mWindowMap.Enumerate(RemoveSharedWorkerFromWindowMap, aWorkerPrivate);
   }
   else {
     // May be null.
     nsPIDOMWindow* window = aWorkerPrivate->GetWindow();
 
     nsTArray<WorkerPrivate*>* windowArray;
-    if (!mWindowMap.Get(window, &windowArray)) {
-      MOZ_ASSERT(false, "Don't have an entry for this window!");
-    }
-
-    if (!windowArray->RemoveElement(aWorkerPrivate)) {
-      MOZ_ASSERT(false, "Worker wasn't in the correct window array!");
-    }
+    MOZ_ALWAYS_TRUE(mWindowMap.Get(window, &windowArray));
+
+    MOZ_ALWAYS_TRUE(windowArray->RemoveElement(aWorkerPrivate));
 
     if (windowArray->IsEmpty()) {
-      MOZ_ASSERT(!queuedWorker, "queuedWorker should be in this array!");
       mWindowMap.Remove(window);
     }
   }
 
   if (queuedWorker && !ScheduleWorker(aCx, queuedWorker)) {
     UnregisterWorker(aCx, queuedWorker);
   }
 }
@@ -1477,56 +1495,57 @@ RuntimeService::UnregisterWorker(JSConte
 bool
 RuntimeService::ScheduleWorker(JSContext* aCx, WorkerPrivate* aWorkerPrivate)
 {
   if (!aWorkerPrivate->Start()) {
     // This is ok, means that we didn't need to make a thread for this worker.
     return true;
   }
 
-  nsCOMPtr<nsIThread> thread;
+  nsRefPtr<WorkerThread> thread;
   {
     MutexAutoLock lock(mMutex);
     if (!mIdleThreadArray.IsEmpty()) {
       uint32_t index = mIdleThreadArray.Length() - 1;
       mIdleThreadArray[index].mThread.swap(thread);
       mIdleThreadArray.RemoveElementAt(index);
     }
   }
 
   if (!thread) {
-    if (NS_FAILED(NS_NewNamedThread("DOM Worker",
-                                    getter_AddRefs(thread), nullptr,
-                                    WORKER_STACK_SIZE))) {
+    thread = WorkerThread::Create();
+    if (!thread) {
       UnregisterWorker(aCx, aWorkerPrivate);
       JS_ReportError(aCx, "Could not create new thread!");
       return false;
     }
   }
 
+  MOZ_ASSERT(thread->IsAcceptingNonWorkerRunnables());
+
   int32_t priority = aWorkerPrivate->IsChromeWorker() ?
                      nsISupportsPriority::PRIORITY_NORMAL :
                      nsISupportsPriority::PRIORITY_LOW;
 
-  nsCOMPtr<nsISupportsPriority> threadPriority = do_QueryInterface(thread);
-  if (!threadPriority || NS_FAILED(threadPriority->SetPriority(priority))) {
+  if (NS_FAILED(thread->SetPriority(priority))) {
     NS_WARNING("Could not set the thread's priority!");
   }
 
-#ifdef DEBUG
-  aWorkerPrivate->SetThread(thread);
-#endif
-
-  nsCOMPtr<nsIRunnable> runnable = new WorkerThreadRunnable(aWorkerPrivate);
+  nsCOMPtr<nsIRunnable> runnable =
+    new WorkerThreadPrimaryRunnable(aWorkerPrivate, thread);
   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();
@@ -1536,29 +1555,29 @@ RuntimeService::ShutdownIdleThreads(nsIT
 
   NS_ASSERTION(aTimer == runtime->mIdleThreadTimer, "Wrong timer!");
 
   // Cheat a little and grab all threads that expire within one second of now.
   TimeStamp now = TimeStamp::Now() + TimeDuration::FromSeconds(1);
 
   TimeStamp nextExpiration;
 
-  nsAutoTArray<nsCOMPtr<nsIThread>, 20> expiredThreads;
+  nsAutoTArray<nsRefPtr<WorkerThread>, 20> expiredThreads;
   {
     MutexAutoLock lock(runtime->mMutex);
 
     for (uint32_t index = 0; index < runtime->mIdleThreadArray.Length();
          index++) {
       IdleThreadInfo& info = runtime->mIdleThreadArray[index];
       if (info.mExpirationTime > now) {
         nextExpiration = info.mExpirationTime;
         break;
       }
 
-      nsCOMPtr<nsIThread>* thread = expiredThreads.AppendElement();
+      nsRefPtr<WorkerThread>* thread = expiredThreads.AppendElement();
       thread->swap(info.mThread);
     }
 
     if (!expiredThreads.IsEmpty()) {
       runtime->mIdleThreadArray.RemoveElementsAt(0, expiredThreads.Length());
     }
   }
 
@@ -1751,17 +1770,16 @@ RuntimeService::Shutdown()
 
   {
     MutexAutoLock lock(mMutex);
 
     nsAutoTArray<WorkerPrivate*, 100> workers;
     mDomainMap.EnumerateRead(AddAllTopLevelWorkersToArray, &workers);
 
     if (!workers.IsEmpty()) {
-
       // Cancel all top-level workers.
       {
         MutexAutoUnlock unlock(mMutex);
 
         AutoSafeJSContext cx;
         JSAutoRequest ar(cx);
 
         for (uint32_t index = 0; index < workers.Length(); index++) {
@@ -1798,17 +1816,17 @@ RuntimeService::Cleanup()
     mDomainMap.EnumerateRead(AddAllTopLevelWorkersToArray, &workers);
 
     if (!workers.IsEmpty()) {
       nsIThread* currentThread = NS_GetCurrentThread();
       NS_ASSERTION(currentThread, "This should never be null!");
 
       // Shut down any idle threads.
       if (!mIdleThreadArray.IsEmpty()) {
-        nsAutoTArray<nsCOMPtr<nsIThread>, 20> idleThreads;
+        nsAutoTArray<nsRefPtr<WorkerThread>, 20> idleThreads;
 
         uint32_t idleThreadCount = mIdleThreadArray.Length();
         idleThreads.SetLength(idleThreadCount);
 
         for (uint32_t index = 0; index < idleThreadCount; index++) {
           NS_ASSERTION(mIdleThreadArray[index].mThread, "Null thread!");
           idleThreads[index].swap(mIdleThreadArray[index].mThread);
         }
@@ -2181,20 +2199,24 @@ RuntimeService::ForgetSharedWorker(Worke
     if (match.mSharedWorkerInfo) {
       domainInfo->mSharedWorkerInfos.Remove(
         match.mSharedWorkerInfo->mScriptSpec);
     }
   }
 }
 
 void
-RuntimeService::NoteIdleThread(nsIThread* aThread)
+RuntimeService::NoteIdleThread(WorkerThread* aThread)
 {
   AssertIsOnMainThread();
-  NS_ASSERTION(aThread, "Null pointer!");
+  MOZ_ASSERT(aThread);
+
+#ifdef DEBUG
+  aThread->SetAcceptingNonWorkerRunnables(true);
+#endif
 
   static TimeDuration timeout =
     TimeDuration::FromSeconds(IDLE_THREAD_TIMEOUT_SEC);
 
   TimeStamp expirationTime = TimeStamp::Now() + timeout;
 
   bool shutdown;
   if (mShuttingDown) {
@@ -2211,29 +2233,25 @@ RuntimeService::NoteIdleThread(nsIThread
     }
     else {
       shutdown = true;
     }
   }
 
   // Too many idle threads, just shut this one down.
   if (shutdown) {
-    if (NS_FAILED(aThread->Shutdown())) {
-      NS_WARNING("Failed to shutdown thread!");
-    }
+    MOZ_ALWAYS_TRUE(NS_SUCCEEDED(aThread->Shutdown()));
     return;
   }
 
   // Schedule timer.
-  if (NS_FAILED(mIdleThreadTimer->
-                  InitWithFuncCallback(ShutdownIdleThreads, nullptr,
-                                       IDLE_THREAD_TIMEOUT_SEC * 1000,
-                                       nsITimer::TYPE_ONE_SHOT))) {
-    NS_ERROR("Can't schedule timer!");
-  }
+  MOZ_ALWAYS_TRUE(NS_SUCCEEDED(mIdleThreadTimer->InitWithFuncCallback(
+                                                 ShutdownIdleThreads, nullptr,
+                                                 IDLE_THREAD_TIMEOUT_SEC * 1000,
+                                                 nsITimer::TYPE_ONE_SHOT)));
 }
 
 void
 RuntimeService::UpdateAllWorkerJSContextOptions()
 {
   BROADCAST_ALL_WORKERS(UpdateJSContextOptions,
                         sDefaultJSSettings.content.contextOptions,
                         sDefaultJSSettings.chrome.contextOptions);
@@ -2349,8 +2367,277 @@ void
 RuntimeService::JSVersionChanged(const char* /* aPrefName */, void* /* aClosure */)
 {
   AssertIsOnMainThread();
 
   bool useLatest = Preferences::GetBool(PREF_WORKERS_LATEST_JS_VERSION, false);
   JS::CompartmentOptions& options = sDefaultJSSettings.content.compartmentOptions;
   options.setVersion(useLatest ? JSVERSION_LATEST : JSVERSION_DEFAULT);
 }
+
+// static
+already_AddRefed<RuntimeService::WorkerThread>
+RuntimeService::WorkerThread::Create()
+{
+  MOZ_ASSERT(nsThreadManager::get());
+
+  nsRefPtr<WorkerThread> thread = new WorkerThread();
+  if (NS_FAILED(thread->Init())) {
+    NS_WARNING("Failed to create new thread!");
+    return nullptr;
+  }
+
+  NS_SetThreadName(thread, "DOM Worker");
+
+  return thread.forget();
+}
+
+void
+RuntimeService::WorkerThread::SetWorker(WorkerPrivate* aWorkerPrivate)
+{
+  MOZ_ASSERT(PR_GetCurrentThread() == mThread);
+  MOZ_ASSERT_IF(aWorkerPrivate, !mWorkerPrivate);
+  MOZ_ASSERT_IF(!aWorkerPrivate, mWorkerPrivate);
+
+  // No need to lock here because mWorkerPrivate is only modified on mThread.
+
+  if (mWorkerPrivate) {
+    MOZ_ASSERT(mObserver);
+
+    MOZ_ALWAYS_TRUE(NS_SUCCEEDED(RemoveObserver(mObserver)));
+
+    mObserver = nullptr;
+    mWorkerPrivate->SetThread(nullptr);
+  }
+
+  mWorkerPrivate = aWorkerPrivate;
+
+  if (mWorkerPrivate) {
+    mWorkerPrivate->SetThread(this);
+
+    nsRefPtr<Observer> observer = new Observer(mWorkerPrivate);
+
+    MOZ_ALWAYS_TRUE(NS_SUCCEEDED(AddObserver(observer)));
+
+    mObserver.swap(observer);
+  }
+}
+
+NS_IMPL_ISUPPORTS_INHERITED0(RuntimeService::WorkerThread, nsThread)
+
+NS_IMETHODIMP
+RuntimeService::WorkerThread::Dispatch(nsIRunnable* aRunnable, uint32_t aFlags)
+{
+  // May be called on any thread!
+
+#ifdef DEBUG
+  if (PR_GetCurrentThread() == mThread) {
+    MOZ_ASSERT(mWorkerPrivate);
+    mWorkerPrivate->AssertIsOnWorkerThread();
+  }
+  else if (aRunnable && !IsAcceptingNonWorkerRunnables()) {
+    // Only enforce cancelable runnables after we've started the worker loop.
+    nsCOMPtr<nsICancelableRunnable> cancelable = do_QueryInterface(aRunnable);
+    MOZ_ASSERT(cancelable,
+               "Should have been wrapped by the worker's event target!");
+  }
+#endif
+
+  // Workers only support asynchronous dispatch for now.
+  if (NS_WARN_IF(aFlags != NS_DISPATCH_NORMAL)) {
+    return NS_ERROR_UNEXPECTED;
+  }
+
+  nsIRunnable* runnableToDispatch;
+  nsRefPtr<WorkerRunnable> workerRunnable;
+
+  if (aRunnable && PR_GetCurrentThread() == mThread) {
+    // No need to lock here because mWorkerPrivate is only modified on mThread.
+    workerRunnable = mWorkerPrivate->MaybeWrapAsWorkerRunnable(aRunnable);
+    runnableToDispatch = workerRunnable;
+  }
+  else {
+    runnableToDispatch = aRunnable;
+  }
+
+  nsresult rv = nsThread::Dispatch(runnableToDispatch, NS_DISPATCH_NORMAL);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
+  return NS_OK;
+}
+
+NS_IMPL_ISUPPORTS1(RuntimeService::WorkerThread::Observer, nsIThreadObserver)
+
+NS_IMETHODIMP
+RuntimeService::WorkerThread::Observer::OnDispatchedEvent(
+                                                nsIThreadInternal* /*aThread */)
+{
+  MOZ_ASSUME_UNREACHABLE("This should never be called!");
+}
+
+NS_IMETHODIMP
+RuntimeService::WorkerThread::Observer::OnProcessNextEvent(
+                                               nsIThreadInternal* /* aThread */,
+                                               bool aMayWait,
+                                               uint32_t aRecursionDepth)
+{
+  mWorkerPrivate->AssertIsOnWorkerThread();
+  MOZ_ASSERT(!aMayWait);
+
+  mWorkerPrivate->OnProcessNextEvent(aRecursionDepth);
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+RuntimeService::WorkerThread::Observer::AfterProcessNextEvent(
+                                               nsIThreadInternal* /* aThread */,
+                                               uint32_t aRecursionDepth,
+                                               bool /* aEventWasProcessed */)
+{
+  mWorkerPrivate->AssertIsOnWorkerThread();
+
+  mWorkerPrivate->AfterProcessNextEvent(aRecursionDepth);
+  return NS_OK;
+}
+
+NS_IMPL_ISUPPORTS_INHERITED0(LogViolationDetailsRunnable, nsRunnable)
+
+NS_IMETHODIMP
+LogViolationDetailsRunnable::Run()
+{
+  AssertIsOnMainThread();
+
+  nsIContentSecurityPolicy* csp = mWorkerPrivate->GetCSP();
+  if (csp) {
+    NS_NAMED_LITERAL_STRING(scriptSample,
+        "Call to eval() or related function blocked by CSP.");
+    if (mWorkerPrivate->GetReportCSPViolations()) {
+      csp->LogViolationDetails(nsIContentSecurityPolicy::VIOLATION_TYPE_EVAL,
+                               mFileName, scriptSample, mLineNum,
+                               EmptyString());
+    }
+  }
+
+  nsRefPtr<MainThreadStopSyncLoopRunnable> response =
+    new MainThreadStopSyncLoopRunnable(mWorkerPrivate, mSyncLoopTarget.forget(),
+                                       true);
+  MOZ_ALWAYS_TRUE(response->Dispatch(nullptr));
+
+  return NS_OK;
+}
+
+NS_IMPL_ISUPPORTS_INHERITED0(WorkerThreadPrimaryRunnable, nsRunnable)
+
+NS_IMETHODIMP
+WorkerThreadPrimaryRunnable::Run()
+{
+#ifdef MOZ_NUWA_PROCESS
+  if (IsNuwaProcess()) {
+    NS_ASSERTION(NuwaMarkCurrentThread != nullptr,
+                  "NuwaMarkCurrentThread is undefined!");
+    NuwaMarkCurrentThread(nullptr, nullptr);
+    NuwaFreezeCurrentThread();
+  }
+#endif
+
+  char stackBaseGuess;
+
+  nsAutoCString threadName;
+  threadName.AssignLiteral("WebWorker '");
+  threadName.Append(NS_LossyConvertUTF16toASCII(mWorkerPrivate->ScriptURL()));
+  threadName.Append('\'');
+
+  profiler_register_thread(threadName.get(), &stackBaseGuess);
+
+  mThread->SetWorker(mWorkerPrivate);
+
+  mWorkerPrivate->AssertIsOnWorkerThread();
+
+  {
+    nsCycleCollector_startup();
+
+    WorkerJSRuntime runtime(mWorkerPrivate);
+    JSRuntime* rt = runtime.Runtime();
+
+    JSContext* cx = CreateJSContextForWorker(mWorkerPrivate, rt);
+    if (!cx) {
+      // XXX need to fire an error at parent.
+      NS_ERROR("Failed to create runtime and context!");
+      return NS_ERROR_FAILURE;
+    }
+
+    {
+#ifdef MOZ_ENABLE_PROFILER_SPS
+      PseudoStack* stack = mozilla_get_pseudo_stack();
+      if (stack) {
+        stack->sampleRuntime(rt);
+      }
+#endif
+
+      {
+        JSAutoRequest ar(cx);
+
+        mWorkerPrivate->DoRunLoop(cx);
+
+        JS_ReportPendingException(cx);
+      }
+
+#ifdef MOZ_ENABLE_PROFILER_SPS
+      if (stack) {
+        stack->sampleRuntime(nullptr);
+      }
+#endif
+    }
+
+    // Destroy the main context.  This will unroot the main worker global and
+    // GC.  This is not the last JSContext (WorkerJSRuntime maintains an
+    // internal JSContext).
+    JS_DestroyContext(cx);
+
+    // Now WorkerJSRuntime goes out of scope and its destructor will shut
+    // down the cycle collector and destroy the final JSContext.  This
+    // breaks any remaining cycles and collects the C++ and JS objects
+    // participating.
+  }
+
+  mThread->SetWorker(nullptr);
+
+  mWorkerPrivate->ScheduleDeletion();
+
+  // It is no longer safe to touch mWorkerPrivate.
+  mWorkerPrivate = nullptr;
+
+  // Now recycle this thread.
+  nsCOMPtr<nsIThread> mainThread = do_GetMainThread();
+  MOZ_ASSERT(mainThread);
+
+  nsRefPtr<FinishedRunnable> finishedRunnable =
+    new FinishedRunnable(mThread.forget());
+  MOZ_ALWAYS_TRUE(NS_SUCCEEDED(mainThread->Dispatch(finishedRunnable,
+                                                    NS_DISPATCH_NORMAL)));
+
+  profiler_unregister_thread();
+  return NS_OK;
+}
+
+NS_IMPL_ISUPPORTS_INHERITED0(WorkerThreadPrimaryRunnable::FinishedRunnable,
+                             nsRunnable)
+
+NS_IMETHODIMP
+WorkerThreadPrimaryRunnable::FinishedRunnable::Run()
+{
+  AssertIsOnMainThread();
+
+  nsRefPtr<RuntimeService::WorkerThread> thread;
+  mThread.swap(thread);
+
+  RuntimeService* rts = RuntimeService::GetService();
+  if (rts) {
+    rts->NoteIdleThread(thread);
+  }
+  else if (thread->ShutdownRequired()) {
+    MOZ_ALWAYS_TRUE(NS_SUCCEEDED(thread->Shutdown()));
+  }
+
+  return NS_OK;
+}
--- a/dom/workers/RuntimeService.h
+++ b/dom/workers/RuntimeService.h
@@ -6,39 +6,38 @@
 
 #ifndef mozilla_dom_workers_runtimeservice_h__
 #define mozilla_dom_workers_runtimeservice_h__
 
 #include "Workers.h"
 
 #include "nsIObserver.h"
 
-#include "mozilla/Attributes.h"
-#include "mozilla/Mutex.h"
 #include "mozilla/TimeStamp.h"
 #include "mozilla/dom/BindingDeclarations.h"
-#include "nsAutoPtr.h"
 #include "nsClassHashtable.h"
-#include "nsCOMPtr.h"
-#include "nsCycleCollectionParticipant.h"
 #include "nsHashKeys.h"
-#include "nsString.h"
 #include "nsTArray.h"
 
+class nsIRunnable;
 class nsIThread;
 class nsITimer;
 class nsPIDOMWindow;
 
 BEGIN_WORKERS_NAMESPACE
 
 class SharedWorker;
 class WorkerPrivate;
 
 class RuntimeService MOZ_FINAL : public nsIObserver
 {
+public:
+  class WorkerThread;
+
+private:
   struct SharedWorkerInfo
   {
     WorkerPrivate* mWorkerPrivate;
     nsCString mScriptSpec;
     nsString mName;
 
     SharedWorkerInfo(WorkerPrivate* aWorkerPrivate,
                      const nsACString& aScriptSpec,
@@ -63,17 +62,17 @@ class RuntimeService MOZ_FINAL : public 
     ActiveWorkerCount() const
     {
       return mActiveWorkers.Length() + mChildWorkerCount;
     }
   };
 
   struct IdleThreadInfo
   {
-    nsCOMPtr<nsIThread> mThread;
+    nsRefPtr<WorkerThread> mThread;
     mozilla::TimeStamp mExpirationTime;
   };
 
   struct MatchSharedWorkerInfo
   {
     WorkerPrivate* mWorkerPrivate;
     SharedWorkerInfo* mSharedWorkerInfo;
 
@@ -168,17 +167,17 @@ public:
 
   const NavigatorStrings&
   GetNavigatorStrings() const
   {
     return mNavigatorStrings;
   }
 
   void
-  NoteIdleThread(nsIThread* aThread);
+  NoteIdleThread(WorkerThread* aThread);
 
   static void
   GetDefaultJSSettings(JSSettings& aSettings)
   {
     AssertIsOnMainThread();
     aSettings = sDefaultJSSettings;
   }
 
--- a/dom/workers/ScriptLoader.cpp
+++ b/dom/workers/ScriptLoader.cpp
@@ -30,16 +30,17 @@
 #include "nsThreadUtils.h"
 #include "nsXPCOM.h"
 #include "xpcpublic.h"
 
 #include "mozilla/dom/Exceptions.h"
 #include "Principal.h"
 #include "WorkerFeature.h"
 #include "WorkerPrivate.h"
+#include "WorkerRunnable.h"
 
 #define MAX_CONCURRENT_SCRIPTS 1000
 
 USING_WORKERS_NAMESPACE
 
 using mozilla::dom::workers::exceptions::ThrowDOMExceptionForNSResult;
 
 namespace {
@@ -126,18 +127,16 @@ ChannelFromScriptURL(nsIPrincipal* princ
   rv = NS_NewChannel(getter_AddRefs(channel), uri, ios, loadGroup, nullptr,
                      flags, channelPolicy);
   NS_ENSURE_SUCCESS(rv, rv);
 
   channel.forget(aChannel);
   return rv;
 }
 
-class ScriptLoaderRunnable;
-
 struct ScriptLoadInfo
 {
   ScriptLoadInfo()
   : mLoadResult(NS_ERROR_NOT_INITIALIZED), mExecutionScheduled(false),
     mExecutionResult(false)
   { }
 
   bool
@@ -150,94 +149,92 @@ struct ScriptLoadInfo
   nsCOMPtr<nsIChannel> mChannel;
   nsString mScriptText;
 
   nsresult mLoadResult;
   bool mExecutionScheduled;
   bool mExecutionResult;
 };
 
-class ScriptExecutorRunnable : public WorkerSyncRunnable
+class ScriptLoaderRunnable;
+
+class ScriptExecutorRunnable MOZ_FINAL : public MainThreadWorkerSyncRunnable
 {
   ScriptLoaderRunnable& mScriptLoader;
   uint32_t mFirstIndex;
   uint32_t mLastIndex;
 
 public:
   ScriptExecutorRunnable(ScriptLoaderRunnable& aScriptLoader,
-                         uint32_t aSyncQueueKey, uint32_t aFirstIndex,
+                         nsIEventTarget* aSyncLoopTarget, uint32_t aFirstIndex,
                          uint32_t aLastIndex);
 
-  bool
-  PreDispatch(JSContext* aCx, WorkerPrivate* aWorkerPrivate)
-  {
-    AssertIsOnMainThread();
-    return true;
-  }
+private:
+  ~ScriptExecutorRunnable()
+  { }
 
-  void
-  PostDispatch(JSContext* aCx, WorkerPrivate* aWorkerPrivate,
-               bool aDispatchResult)
-  {
-    AssertIsOnMainThread();
-  }
+  virtual bool
+  WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate) MOZ_OVERRIDE;
 
-  bool
-  WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate);
-
-  void
-  PostRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate, bool aRunResult);
+  virtual void
+  PostRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate, bool aRunResult)
+          MOZ_OVERRIDE;
 };
 
-class ScriptLoaderRunnable : public WorkerFeature,
-                             public nsIRunnable,
-                             public nsIStreamLoaderObserver
+class ScriptLoaderRunnable MOZ_FINAL : public WorkerFeature,
+                                       public nsIRunnable,
+                                       public nsIStreamLoaderObserver
 {
   friend class ScriptExecutorRunnable;
 
   WorkerPrivate* mWorkerPrivate;
-  uint32_t mSyncQueueKey;
+  nsCOMPtr<nsIEventTarget> mSyncLoopTarget;
   nsTArray<ScriptLoadInfo> mLoadInfos;
   bool mIsWorkerScript;
   bool mCanceled;
   bool mCanceledMainThread;
 
 public:
   NS_DECL_THREADSAFE_ISUPPORTS
 
   ScriptLoaderRunnable(WorkerPrivate* aWorkerPrivate,
-                       uint32_t aSyncQueueKey,
+                       nsIEventTarget* aSyncLoopTarget,
                        nsTArray<ScriptLoadInfo>& aLoadInfos,
                        bool aIsWorkerScript)
-  : mWorkerPrivate(aWorkerPrivate), mSyncQueueKey(aSyncQueueKey),
+  : mWorkerPrivate(aWorkerPrivate), mSyncLoopTarget(aSyncLoopTarget),
     mIsWorkerScript(aIsWorkerScript), mCanceled(false),
     mCanceledMainThread(false)
   {
     aWorkerPrivate->AssertIsOnWorkerThread();
-    NS_ASSERTION(!aIsWorkerScript || aLoadInfos.Length() == 1, "Bad args!");
+    MOZ_ASSERT(aSyncLoopTarget);
+    MOZ_ASSERT_IF(aIsWorkerScript, aLoadInfos.Length() == 1);
 
     mLoadInfos.SwapElements(aLoadInfos);
   }
 
+private:
+  ~ScriptLoaderRunnable()
+  { }
+
   NS_IMETHOD
-  Run()
+  Run() MOZ_OVERRIDE
   {
     AssertIsOnMainThread();
 
     if (NS_FAILED(RunInternal())) {
       CancelMainThread();
     }
 
     return NS_OK;
   }
 
   NS_IMETHOD
   OnStreamComplete(nsIStreamLoader* aLoader, nsISupports* aContext,
                    nsresult aStatus, uint32_t aStringLen,
-                   const uint8_t* aString)
+                   const uint8_t* aString) MOZ_OVERRIDE
   {
     AssertIsOnMainThread();
 
     nsCOMPtr<nsISupportsPRUint32> indexSupports(do_QueryInterface(aContext));
     NS_ASSERTION(indexSupports, "This should never fail!");
 
     uint32_t index = UINT32_MAX;
     if (NS_FAILED(indexSupports->GetData(&index)) ||
@@ -251,18 +248,18 @@ public:
                                                     aStringLen, aString,
                                                     loadInfo);
 
     ExecuteFinishedScripts();
 
     return NS_OK;
   }
 
-  bool
-  Notify(JSContext* aCx, Status aStatus)
+  virtual bool
+  Notify(JSContext* aCx, Status aStatus) MOZ_OVERRIDE
   {
     mWorkerPrivate->AssertIsOnWorkerThread();
 
     if (aStatus >= Terminating && !mCanceled) {
       mCanceled = true;
 
       nsCOMPtr<nsIRunnable> runnable =
         NS_NewRunnableMethod(this, &ScriptLoaderRunnable::CancelMainThread);
@@ -582,65 +579,47 @@ public:
         loadInfo.mExecutionScheduled = true;
 
         lastIndex = index;
       }
     }
 
     if (firstIndex != UINT32_MAX && lastIndex != UINT32_MAX) {
       nsRefPtr<ScriptExecutorRunnable> runnable =
-        new ScriptExecutorRunnable(*this, mSyncQueueKey, firstIndex, lastIndex);
+        new ScriptExecutorRunnable(*this, mSyncLoopTarget, firstIndex,
+                                   lastIndex);
       if (!runnable->Dispatch(nullptr)) {
-        NS_ERROR("This should never fail!");
+        MOZ_ASSERT(false, "This should never fail!");
       }
     }
   }
 };
 
 NS_IMPL_ISUPPORTS2(ScriptLoaderRunnable, nsIRunnable, nsIStreamLoaderObserver)
 
-class StopSyncLoopRunnable MOZ_FINAL : public MainThreadSyncRunnable
-{
-public:
-  StopSyncLoopRunnable(WorkerPrivate* aWorkerPrivate,
-                       uint32_t aSyncQueueKey)
-  : MainThreadSyncRunnable(aWorkerPrivate, SkipWhenClearing, aSyncQueueKey,
-                           false)
-  { }
-
-  bool
-  WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate) MOZ_OVERRIDE
-  {
-    aWorkerPrivate->AssertIsOnWorkerThread();
-    aWorkerPrivate->StopSyncLoop(mSyncQueueKey, true);
-    return true;
-  }
-};
-
 class ChannelGetterRunnable MOZ_FINAL : public nsRunnable
 {
   WorkerPrivate* mParentWorker;
-  uint32_t mSyncQueueKey;
+  nsCOMPtr<nsIEventTarget> mSyncLoopTarget;
   const nsAString& mScriptURL;
   nsIChannel** mChannel;
   nsresult mResult;
 
 public:
   ChannelGetterRunnable(WorkerPrivate* aParentWorker,
-                        uint32_t aSyncQueueKey,
+                        nsIEventTarget* aSyncLoopTarget,
                         const nsAString& aScriptURL,
                         nsIChannel** aChannel)
-  : mParentWorker(aParentWorker), mSyncQueueKey(aSyncQueueKey),
+  : mParentWorker(aParentWorker), mSyncLoopTarget(aSyncLoopTarget),
     mScriptURL(aScriptURL), mChannel(aChannel), mResult(NS_ERROR_FAILURE)
   {
     aParentWorker->AssertIsOnWorkerThread();
+    MOZ_ASSERT(aSyncLoopTarget);
   }
 
-  virtual ~ChannelGetterRunnable() { }
-
   NS_IMETHOD
   Run() MOZ_OVERRIDE
   {
     AssertIsOnMainThread();
 
     nsIPrincipal* principal = mParentWorker->GetPrincipal();
     NS_ASSERTION(principal, "This should never be null here!");
 
@@ -655,44 +634,47 @@ public:
     mResult =
       scriptloader::ChannelFromScriptURLMainThread(principal, baseURI,
                                                    parentDoc, mScriptURL,
                                                    getter_AddRefs(channel));
     if (NS_SUCCEEDED(mResult)) {
       channel.forget(mChannel);
     }
 
-    nsRefPtr<StopSyncLoopRunnable> runnable =
-      new StopSyncLoopRunnable(mParentWorker, mSyncQueueKey);
+    nsRefPtr<MainThreadStopSyncLoopRunnable> runnable =
+      new MainThreadStopSyncLoopRunnable(mParentWorker,
+                                         mSyncLoopTarget.forget(), true);
     if (!runnable->Dispatch(nullptr)) {
       NS_ERROR("This should never fail!");
     }
 
     return NS_OK;
   }
 
   nsresult
   GetResult() const
   {
     return mResult;
   }
 
+private:
+  virtual ~ChannelGetterRunnable()
+  { }
 };
 
 ScriptExecutorRunnable::ScriptExecutorRunnable(
                                             ScriptLoaderRunnable& aScriptLoader,
-                                            uint32_t aSyncQueueKey,
+                                            nsIEventTarget* aSyncLoopTarget,
                                             uint32_t aFirstIndex,
                                             uint32_t aLastIndex)
-: WorkerSyncRunnable(aScriptLoader.mWorkerPrivate, aSyncQueueKey),
+: MainThreadWorkerSyncRunnable(aScriptLoader.mWorkerPrivate, aSyncLoopTarget),
   mScriptLoader(aScriptLoader), mFirstIndex(aFirstIndex), mLastIndex(aLastIndex)
 {
-  NS_ASSERTION(aFirstIndex <= aLastIndex, "Bad first index!");
-  NS_ASSERTION(aLastIndex < aScriptLoader.mLoadInfos.Length(),
-               "Bad last index!");
+  MOZ_ASSERT(aFirstIndex <= aLastIndex);
+  MOZ_ASSERT(aLastIndex < aScriptLoader.mLoadInfos.Length());
 }
 
 bool
 ScriptExecutorRunnable::WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate)
 {
   nsTArray<ScriptLoadInfo>& loadInfos = mScriptLoader.mLoadInfos;
 
   // Don't run if something else has already failed.
@@ -754,47 +736,47 @@ ScriptExecutorRunnable::PostRun(JSContex
     for (uint32_t index = 0; index < loadInfos.Length(); index++) {
       if (!loadInfos[index].mExecutionResult) {
         result = false;
         break;
       }
     }
 
     aWorkerPrivate->RemoveFeature(aCx, &mScriptLoader);
-    aWorkerPrivate->StopSyncLoop(mSyncQueueKey, result);
+    aWorkerPrivate->StopSyncLoop(mSyncLoopTarget, result);
   }
 }
 
 bool
 LoadAllScripts(JSContext* aCx, WorkerPrivate* aWorkerPrivate,
                nsTArray<ScriptLoadInfo>& aLoadInfos, bool aIsWorkerScript)
 {
   aWorkerPrivate->AssertIsOnWorkerThread();
   NS_ASSERTION(!aLoadInfos.IsEmpty(), "Bad arguments!");
 
   AutoSyncLoopHolder syncLoop(aWorkerPrivate);
 
   nsRefPtr<ScriptLoaderRunnable> loader =
-    new ScriptLoaderRunnable(aWorkerPrivate, syncLoop.SyncQueueKey(),
+    new ScriptLoaderRunnable(aWorkerPrivate, syncLoop.EventTarget(),
                              aLoadInfos, aIsWorkerScript);
 
   NS_ASSERTION(aLoadInfos.IsEmpty(), "Should have swapped!");
 
   if (!aWorkerPrivate->AddFeature(aCx, loader)) {
     return false;
   }
 
   if (NS_FAILED(NS_DispatchToMainThread(loader, NS_DISPATCH_NORMAL))) {
     NS_ERROR("Failed to dispatch!");
 
     aWorkerPrivate->RemoveFeature(aCx, loader);
     return false;
   }
 
-  return syncLoop.RunAndForget(aCx);
+  return syncLoop.Run();
 }
 
 } /* anonymous namespace */
 
 BEGIN_WORKERS_NAMESPACE
 
 namespace scriptloader {
 
@@ -827,25 +809,25 @@ ChannelFromScriptURLWorkerThread(JSConte
                                  const nsAString& aScriptURL,
                                  nsIChannel** aChannel)
 {
   aParent->AssertIsOnWorkerThread();
 
   AutoSyncLoopHolder syncLoop(aParent);
 
   nsRefPtr<ChannelGetterRunnable> getter =
-    new ChannelGetterRunnable(aParent, syncLoop.SyncQueueKey(),
-                              aScriptURL, aChannel);
+    new ChannelGetterRunnable(aParent, syncLoop.EventTarget(), aScriptURL,
+                              aChannel);
 
   if (NS_FAILED(NS_DispatchToMainThread(getter, NS_DISPATCH_NORMAL))) {
     NS_ERROR("Failed to dispatch!");
     return NS_ERROR_FAILURE;
   }
 
-  if (!syncLoop.RunAndForget(aCx)) {
+  if (!syncLoop.Run()) {
     return NS_ERROR_FAILURE;
   }
 
   return getter->GetResult();
 }
 
 void ReportLoadError(JSContext* aCx, const nsAString& aURL,
                      nsresult aLoadResult, bool aIsMainThread)
--- a/dom/workers/URL.cpp
+++ b/dom/workers/URL.cpp
@@ -1,32 +1,32 @@
 /* -*- Mode: c++; c-basic-offset: 2; indent-tabs-mode: nil; tab-width: 40 -*- */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * 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 "URL.h"
-#include "File.h"
-
-#include "WorkerPrivate.h"
-#include "nsThreadUtils.h"
-
-#include "nsPIDOMWindow.h"
-#include "nsGlobalWindow.h"
-#include "nsHostObjectProtocolHandler.h"
-#include "nsServiceManagerUtils.h"
 
 #include "nsIDocument.h"
 #include "nsIDOMFile.h"
+#include "nsIIOService.h"
+#include "nsPIDOMWindow.h"
 
 #include "mozilla/dom/URL.h"
 #include "mozilla/dom/URLBinding.h"
 #include "mozilla/dom/URLSearchParams.h"
-#include "nsIIOService.h"
+#include "nsGlobalWindow.h"
+#include "nsHostObjectProtocolHandler.h"
 #include "nsNetCID.h"
+#include "nsServiceManagerUtils.h"
+#include "nsThreadUtils.h"
+
+#include "File.h"
+#include "WorkerPrivate.h"
+#include "WorkerRunnable.h"
 
 BEGIN_WORKERS_NAMESPACE
 using mozilla::dom::GlobalObject;
 
 class URLProxy MOZ_FINAL
 {
 public:
   NS_INLINE_DECL_THREADSAFE_REFCOUNTING(URLProxy)
@@ -62,86 +62,54 @@ private:
   nsRefPtr<mozilla::dom::URL> mURL;
 };
 
 // Base class for the URL runnable objects.
 class URLRunnable : public nsRunnable
 {
 protected:
   WorkerPrivate* mWorkerPrivate;
-  uint32_t mSyncQueueKey;
-
-private:
-  class ResponseRunnable : public WorkerSyncRunnable
-  {
-    uint32_t mSyncQueueKey;
-
-  public:
-    ResponseRunnable(WorkerPrivate* aWorkerPrivate,
-                     uint32_t aSyncQueueKey)
-    : WorkerSyncRunnable(aWorkerPrivate, aSyncQueueKey, false),
-      mSyncQueueKey(aSyncQueueKey)
-    {
-      NS_ASSERTION(aWorkerPrivate, "Don't hand me a null WorkerPrivate!");
-    }
-
-    bool
-    WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate)
-    {
-      aWorkerPrivate->StopSyncLoop(mSyncQueueKey, true);
-      return true;
-    }
-
-    bool
-    PreDispatch(JSContext* aCx, WorkerPrivate* aWorkerPrivate)
-    {
-      AssertIsOnMainThread();
-      return true;
-    }
-
-    void
-    PostDispatch(JSContext* aCx, WorkerPrivate* aWorkerPrivate,
-                 bool aDispatchResult)
-    {
-      AssertIsOnMainThread();
-    }
-  };
+  nsCOMPtr<nsIEventTarget> mSyncLoopTarget;
 
 protected:
   URLRunnable(WorkerPrivate* aWorkerPrivate)
   : mWorkerPrivate(aWorkerPrivate)
   {
     mWorkerPrivate->AssertIsOnWorkerThread();
   }
 
 public:
   bool
   Dispatch(JSContext* aCx)
   {
     mWorkerPrivate->AssertIsOnWorkerThread();
+
     AutoSyncLoopHolder syncLoop(mWorkerPrivate);
-    mSyncQueueKey = syncLoop.SyncQueueKey();
+
+    mSyncLoopTarget = syncLoop.EventTarget();
 
     if (NS_FAILED(NS_DispatchToMainThread(this, NS_DISPATCH_NORMAL))) {
       JS_ReportError(aCx, "Failed to dispatch to main thread!");
       return false;
     }
 
-    return syncLoop.RunAndForget(aCx);
+    return syncLoop.Run();
   }
 
 private:
   NS_IMETHOD Run()
   {
     AssertIsOnMainThread();
 
     MainThreadRun();
 
-    nsRefPtr<ResponseRunnable> response =
-      new ResponseRunnable(mWorkerPrivate, mSyncQueueKey);
+    nsRefPtr<MainThreadStopSyncLoopRunnable> response =
+      new MainThreadStopSyncLoopRunnable(mWorkerPrivate,
+                                         mSyncLoopTarget.forget(),
+                                         true);
     if (!response->Dispatch(nullptr)) {
       NS_WARNING("Failed to dispatch response!");
     }
 
     return NS_OK;
   }
 
 protected:
--- a/dom/workers/WorkerPrivate.cpp
+++ b/dom/workers/WorkerPrivate.cpp
@@ -18,30 +18,33 @@
 #include "nsIDocShell.h"
 #include "nsIMemoryReporter.h"
 #include "nsIPermissionManager.h"
 #include "nsIScriptError.h"
 #include "nsIScriptGlobalObject.h"
 #include "nsIScriptSecurityManager.h"
 #include "nsPIDOMWindow.h"
 #include "nsITextToSubURI.h"
+#include "nsIThreadInternal.h"
 #include "nsITimer.h"
 #include "nsIURI.h"
 #include "nsIURL.h"
 #include "nsIXPConnect.h"
 
 #include <algorithm>
 #include "jsfriendapi.h"
 #include "js/OldDebugAPI.h"
 #include "js/MemoryMetrics.h"
+#include "mozilla/Assertions.h"
 #include "mozilla/ContentEvents.h"
 #include "mozilla/Likely.h"
 #include "mozilla/dom/BindingUtils.h"
 #include "mozilla/dom/ErrorEvent.h"
 #include "mozilla/dom/ErrorEventBinding.h"
+#include "mozilla/dom/Exceptions.h"
 #include "mozilla/dom/FunctionBinding.h"
 #include "mozilla/dom/ImageData.h"
 #include "mozilla/dom/ImageDataBinding.h"
 #include "mozilla/dom/MessageEventBinding.h"
 #include "mozilla/dom/MessagePortList.h"
 #include "mozilla/dom/WorkerBinding.h"
 #include "mozilla/Preferences.h"
 #include "nsAlgorithm.h"
@@ -59,50 +62,74 @@
 #include "nsProxyRelease.h"
 #include "nsSandboxFlags.h"
 #include "xpcpublic.h"
 
 #ifdef ANDROID
 #include <android/log.h>
 #endif
 
-#include "mozilla/dom/Exceptions.h"
+#ifdef DEBUG
+#include "nsThreadManager.h"
+#endif
+
 #include "File.h"
 #include "MessagePort.h"
 #include "Principal.h"
 #include "RuntimeService.h"
 #include "ScriptLoader.h"
 #include "SharedWorker.h"
 #include "WorkerFeature.h"
+#include "WorkerRunnable.h"
 #include "WorkerScope.h"
 
-// GC will run once every thirty seconds during normal execution.
-#define NORMAL_GC_TIMER_DELAY_MS 30000
-
-// GC will run five seconds after the last event is processed.
-#define IDLE_GC_TIMER_DELAY_MS 5000
+// JS_MaybeGC will run once every second during normal execution.
+#define PERIODIC_GC_TIMER_DELAY_SEC 1
+
+// A shrinking GC will run five seconds after the last event is processed.
+#define IDLE_GC_TIMER_DELAY_SEC 5
 
 #define PREF_WORKERS_ENABLED "dom.workers.enabled"
 
-using mozilla::InternalScriptErrorEvent;
-using mozilla::MutexAutoLock;
-using mozilla::TimeDuration;
-using mozilla::TimeStamp;
-using mozilla::dom::Throw;
-using mozilla::AutoCxPusher;
-using mozilla::AutoPushJSContext;
-using mozilla::AutoSafeJSContext;
-
+#ifdef WORKER_LOGGING
+#define LOG(_args) do { printf _args ; fflush(stdout); } while (0)
+#else
+#define LOG(_args) do { } while (0)
+#endif
+
+using namespace mozilla;
+using namespace mozilla::dom;
 USING_WORKERS_NAMESPACE
-using namespace mozilla::dom;
 
 MOZ_DEFINE_MALLOC_SIZE_OF(JsWorkerMallocSizeOf)
 
+#ifdef DEBUG
+
+BEGIN_WORKERS_NAMESPACE
+
+void
+AssertIsOnMainThread()
+{
+  MOZ_ASSERT(NS_IsMainThread(), "Wrong thread!");
+}
+
+END_WORKERS_NAMESPACE
+
+#endif
+
 namespace {
 
+#ifdef DEBUG
+
+const nsIID kDEBUGWorkerEventTargetIID = {
+  0xccaba3fa, 0x5be2, 0x4de2, { 0xba, 0x87, 0x3b, 0x3b, 0x5b, 0x1d, 0x5, 0xfb }
+};
+
+#endif
+
 template <class T>
 class AutoPtrComparator
 {
   typedef nsAutoPtr<T> A;
   typedef T* B;
 
 public:
   bool Equals(const A& a, const B& b) const {
@@ -137,16 +164,61 @@ SwapToISupportsArray(SmartPtr<T>& aSrc,
   T* raw = nullptr;
   aSrc.swap(raw);
 
   nsISupports* rawSupports =
     static_cast<typename ISupportsBaseInfo<T>::ISupportsBase*>(raw);
   dest->swap(rawSupports);
 }
 
+// This class is used to wrap any runnables that the worker receives via the
+// nsIEventTarget::Dispatch() method (either from NS_DispatchToCurrentThread or
+// from the worker's EventTarget).
+class ExternalRunnableWrapper MOZ_FINAL : public WorkerRunnable
+{
+  nsCOMPtr<nsICancelableRunnable> mWrappedRunnable;
+
+public:
+  ExternalRunnableWrapper(WorkerPrivate* aWorkerPrivate,
+                          nsICancelableRunnable* aWrappedRunnable)
+  : WorkerRunnable(aWorkerPrivate, WorkerThreadUnchangedBusyCount),
+    mWrappedRunnable(aWrappedRunnable)
+  {
+    MOZ_ASSERT(aWorkerPrivate);
+    MOZ_ASSERT(aWrappedRunnable);
+  }
+
+  NS_DECL_ISUPPORTS_INHERITED
+
+private:
+  ~ExternalRunnableWrapper()
+  { }
+
+  virtual bool
+  WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate) MOZ_OVERRIDE
+  {
+    nsresult rv = mWrappedRunnable->Run();
+    if (NS_FAILED(rv)) {
+      if (!JS_IsExceptionPending(aCx)) {
+        Throw(aCx, rv);
+      }
+      return false;
+    }
+    return true;
+  }
+
+  NS_IMETHOD
+  Cancel() MOZ_OVERRIDE
+  {
+    nsresult rv = mWrappedRunnable->Cancel();
+    nsresult rv2 = WorkerRunnable::Cancel();
+    return NS_FAILED(rv) ? rv : rv2;
+  }
+};
+
 struct WindowAction
 {
   nsPIDOMWindow* mWindow;
   JSContext* mJSContext;
   bool mDefaultAction;
 
   WindowAction(nsPIDOMWindow* aWindow, JSContext* aJSContext)
   : mWindow(aWindow), mJSContext(aJSContext), mDefaultAction(true)
@@ -618,128 +690,117 @@ struct MainThreadChromeWorkerStructuredC
 };
 
 JSStructuredCloneCallbacks gMainThreadChromeWorkerStructuredCloneCallbacks = {
   MainThreadChromeWorkerStructuredCloneCallbacks::Read,
   MainThreadChromeWorkerStructuredCloneCallbacks::Write,
   MainThreadChromeWorkerStructuredCloneCallbacks::Error
 };
 
-class MainThreadReleaseRunnable : public nsRunnable
+class MainThreadReleaseRunnable MOZ_FINAL : public nsRunnable
 {
-  nsCOMPtr<nsIThread> mThread;
-  nsTArray<nsCOMPtr<nsISupports> > mDoomed;
+  nsTArray<nsCOMPtr<nsISupports>> mDoomed;
   nsTArray<nsCString> mHostObjectURIs;
 
 public:
-  MainThreadReleaseRunnable(nsCOMPtr<nsIThread>& aThread,
-                            nsTArray<nsCOMPtr<nsISupports> >& aDoomed,
-                            nsTArray<nsCString>& aHostObjectURIs)
-  {
-    mThread.swap(aThread);
-    mDoomed.SwapElements(aDoomed);
-    mHostObjectURIs.SwapElements(aHostObjectURIs);
-  }
-
-  MainThreadReleaseRunnable(nsTArray<nsCOMPtr<nsISupports> >& aDoomed,
+  MainThreadReleaseRunnable(nsTArray<nsCOMPtr<nsISupports>>& aDoomed,
                             nsTArray<nsCString>& aHostObjectURIs)
   {
     mDoomed.SwapElements(aDoomed);
     mHostObjectURIs.SwapElements(aHostObjectURIs);
   }
 
+  NS_DECL_ISUPPORTS_INHERITED
+
   NS_IMETHOD
-  Run()
+  Run() MOZ_OVERRIDE
   {
     mDoomed.Clear();
 
-    if (mThread) {
-      RuntimeService* runtime = RuntimeService::GetService();
-      NS_ASSERTION(runtime, "This should never be null!");
-
-      runtime->NoteIdleThread(mThread);
-    }
-
-    for (uint32_t i = 0, len = mHostObjectURIs.Length(); i < len; ++i) {
-      nsHostObjectProtocolHandler::RemoveDataEntry(mHostObjectURIs[i]);
+    for (uint32_t index = 0; index < mHostObjectURIs.Length(); index++) {
+      nsHostObjectProtocolHandler::RemoveDataEntry(mHostObjectURIs[index]);
     }
 
     return NS_OK;
   }
+
+private:
+  ~MainThreadReleaseRunnable()
+  { }
 };
 
-class WorkerFinishedRunnable : public WorkerControlRunnable
+class WorkerFinishedRunnable MOZ_FINAL : public WorkerControlRunnable
 {
   WorkerPrivate* mFinishedWorker;
-  nsCOMPtr<nsIThread> mThread;
 
 public:
   WorkerFinishedRunnable(WorkerPrivate* aWorkerPrivate,
-                         WorkerPrivate* aFinishedWorker,
-                         nsIThread* aFinishedThread)
-  : WorkerControlRunnable(aWorkerPrivate, WorkerThread, UnchangedBusyCount),
-    mFinishedWorker(aFinishedWorker), mThread(aFinishedThread)
+                         WorkerPrivate* aFinishedWorker)
+  : WorkerControlRunnable(aWorkerPrivate, WorkerThreadUnchangedBusyCount),
+    mFinishedWorker(aFinishedWorker)
   { }
 
-  bool
-  PreDispatch(JSContext* aCx, WorkerPrivate* aWorkerPrivate)
+private:
+  virtual bool
+  PreDispatch(JSContext* aCx, WorkerPrivate* aWorkerPrivate) MOZ_OVERRIDE
   {
     // Silence bad assertions.
     return true;
   }
 
-  void
+  virtual void
   PostDispatch(JSContext* aCx, WorkerPrivate* aWorkerPrivate,
-               bool aDispatchResult)
+               bool aDispatchResult) MOZ_OVERRIDE
   {
     // Silence bad assertions.
   }
 
-  bool
-  WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate)
+  virtual bool
+  WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate) MOZ_OVERRIDE
   {
-    nsTArray<nsCOMPtr<nsISupports> > doomed;
+    nsTArray<nsCOMPtr<nsISupports>> doomed;
     mFinishedWorker->ForgetMainThreadObjects(doomed);
 
     nsTArray<nsCString> hostObjectURIs;
     mFinishedWorker->StealHostObjectURIs(hostObjectURIs);
 
     nsRefPtr<MainThreadReleaseRunnable> runnable =
-      new MainThreadReleaseRunnable(mThread, doomed, hostObjectURIs);
+      new MainThreadReleaseRunnable(doomed, hostObjectURIs);
     if (NS_FAILED(NS_DispatchToMainThread(runnable, NS_DISPATCH_NORMAL))) {
       NS_WARNING("Failed to dispatch, going to leak!");
     }
 
     mFinishedWorker->Finish(aCx);
 
     RuntimeService* runtime = RuntimeService::GetService();
     NS_ASSERTION(runtime, "This should never be null!");
 
     runtime->UnregisterWorker(aCx, mFinishedWorker);
 
     mFinishedWorker->Release();
     return true;
   }
 };
 
-class TopLevelWorkerFinishedRunnable : public nsRunnable
+class TopLevelWorkerFinishedRunnable MOZ_FINAL : public nsRunnable
 {
   WorkerPrivate* mFinishedWorker;
-  nsCOMPtr<nsIThread> mThread;
 
 public:
-  TopLevelWorkerFinishedRunnable(WorkerPrivate* aFinishedWorker,
-                                 nsIThread* aFinishedThread)
-  : mFinishedWorker(aFinishedWorker), mThread(aFinishedThread)
+  TopLevelWorkerFinishedRunnable(WorkerPrivate* aFinishedWorker)
+  : mFinishedWorker(aFinishedWorker)
   {
     aFinishedWorker->AssertIsOnWorkerThread();
   }
 
+  NS_DECL_ISUPPORTS_INHERITED
+
+private:
   NS_IMETHOD
-  Run()
+  Run() MOZ_OVERRIDE
   {
     AssertIsOnMainThread();
 
     AutoSafeJSContext cx;
     JSAutoRequest ar(cx);
 
     mFinishedWorker->Finish(cx);
 
@@ -755,141 +816,157 @@ public:
     mFinishedWorker->StealHostObjectURIs(hostObjectURIs);
 
     nsRefPtr<MainThreadReleaseRunnable> runnable =
       new MainThreadReleaseRunnable(doomed, hostObjectURIs);
     if (NS_FAILED(NS_DispatchToCurrentThread(runnable))) {
       NS_WARNING("Failed to dispatch, going to leak!");
     }
 
-    if (mThread) {
-      runtime->NoteIdleThread(mThread);
-    }
-
     mFinishedWorker->Release();
 
     return NS_OK;
   }
 };
 
-class ModifyBusyCountRunnable : public WorkerControlRunnable
+class ModifyBusyCountRunnable MOZ_FINAL : public WorkerControlRunnable
 {
   bool mIncrease;
 
 public:
   ModifyBusyCountRunnable(WorkerPrivate* aWorkerPrivate, bool aIncrease)
-  : WorkerControlRunnable(aWorkerPrivate, ParentThread, UnchangedBusyCount),
+  : WorkerControlRunnable(aWorkerPrivate, ParentThreadUnchangedBusyCount),
     mIncrease(aIncrease)
   { }
 
-  bool
-  WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate)
+private:
+  virtual bool
+  WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate) MOZ_OVERRIDE
   {
     return aWorkerPrivate->ModifyBusyCount(aCx, mIncrease);
   }
 
-  void
+  virtual void
   PostRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate, bool aRunResult)
+          MOZ_OVERRIDE
   {
     if (mIncrease) {
       WorkerControlRunnable::PostRun(aCx, aWorkerPrivate, aRunResult);
       return;
     }
     // Don't do anything here as it's possible that aWorkerPrivate has been
     // deleted.
   }
 };
 
-class CompileScriptRunnable : public WorkerRunnable
+class CompileScriptRunnable MOZ_FINAL : public WorkerRunnable
 {
 public:
   CompileScriptRunnable(WorkerPrivate* aWorkerPrivate)
-  : WorkerRunnable(aWorkerPrivate, WorkerThread, ModifyBusyCount,
-                   SkipWhenClearing)
+  : WorkerRunnable(aWorkerPrivate, WorkerThreadModifyBusyCount)
   { }
 
-  bool
-  WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate)
+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);
     return scriptloader::LoadWorkerScript(aCx);
   }
 };
 
-class CloseEventRunnable : public WorkerRunnable
+class CloseEventRunnable MOZ_FINAL : public WorkerRunnable
 {
 public:
   CloseEventRunnable(WorkerPrivate* aWorkerPrivate)
-  : WorkerRunnable(aWorkerPrivate, WorkerThread, UnchangedBusyCount,
-                   SkipWhenClearing)
+  : WorkerRunnable(aWorkerPrivate, WorkerThreadUnchangedBusyCount)
   { }
 
-  bool
-  WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate)
+private:
+  virtual bool
+  PreDispatch(JSContext* aCx, WorkerPrivate* aWorkerPrivate) MOZ_OVERRIDE
+  {
+    MOZ_ASSUME_UNREACHABLE("Don't call Dispatch() on CloseEventRunnable!");
+  }
+
+  virtual void
+  PostDispatch(JSContext* aCx, WorkerPrivate* aWorkerPrivate,
+               bool aDispatchResult) MOZ_OVERRIDE
+  {
+    MOZ_ASSUME_UNREACHABLE("Don't call Dispatch() on CloseEventRunnable!");
+  }
+
+  virtual bool
+  WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate) MOZ_OVERRIDE
   {
     JS::Rooted<JSObject*> target(aCx, JS::CurrentGlobalOrNull(aCx));
     NS_ASSERTION(target, "This must never be null!");
 
     aWorkerPrivate->CloseHandlerStarted();
 
     WorkerGlobalScope* globalScope = aWorkerPrivate->GlobalScope();
 
     nsCOMPtr<nsIDOMEvent> event;
     nsresult rv =
       NS_NewDOMEvent(getter_AddRefs(event), globalScope, nullptr, nullptr);
-    NS_ENSURE_SUCCESS(rv, false);
+    if (NS_FAILED(rv)) {
+      Throw(aCx, rv);
+      return false;
+    }
 
     rv = event->InitEvent(NS_LITERAL_STRING("close"), false, false);
-    NS_ENSURE_SUCCESS(rv, false);
+    if (NS_FAILED(rv)) {
+      Throw(aCx, rv);
+      return false;
+    }
 
     event->SetTrusted(true);
 
     globalScope->DispatchDOMEvent(nullptr, event, nullptr, nullptr);
 
     return true;
   }
 
-  void
+  virtual void
   PostRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate, bool aRunResult)
+          MOZ_OVERRIDE
   {
     // Report errors.
     WorkerRunnable::PostRun(aCx, aWorkerPrivate, aRunResult);
 
     // Match the busy count increase from NotifyRunnable.
     if (!aWorkerPrivate->ModifyBusyCountFromWorker(aCx, false)) {
       JS_ReportPendingException(aCx);
     }
 
     aWorkerPrivate->CloseHandlerFinished();
   }
 };
 
-class MessageEventRunnable : public WorkerRunnable
+class MessageEventRunnable MOZ_FINAL : public WorkerRunnable
 {
   JSAutoStructuredCloneBuffer mBuffer;
   nsTArray<nsCOMPtr<nsISupports> > mClonedObjects;
   uint64_t mMessagePortSerial;
   bool mToMessagePort;
 
 public:
-  MessageEventRunnable(WorkerPrivate* aWorkerPrivate, Target aTarget,
+  MessageEventRunnable(WorkerPrivate* aWorkerPrivate,
+                       TargetAndBusyBehavior aBehavior,
                        JSAutoStructuredCloneBuffer& aData,
                        nsTArray<nsCOMPtr<nsISupports> >& aClonedObjects,
                        bool aToMessagePort, uint64_t aMessagePortSerial)
-  : WorkerRunnable(aWorkerPrivate, aTarget, aTarget == WorkerThread ?
-                                                       ModifyBusyCount :
-                                                       UnchangedBusyCount,
-                   SkipWhenClearing),
+  : WorkerRunnable(aWorkerPrivate, aBehavior),
     mMessagePortSerial(aMessagePortSerial), mToMessagePort(aToMessagePort)
   {
     mBuffer.swap(aData);
     mClonedObjects.SwapElements(aClonedObjects);
   }
 
   bool
   DispatchDOMEvent(JSContext* aCx, WorkerPrivate* aWorkerPrivate,
@@ -926,22 +1003,23 @@ public:
 
     nsCOMPtr<nsIDOMEvent> domEvent = do_QueryObject(event);
 
     nsEventStatus dummy = nsEventStatus_eIgnore;
     aTarget->DispatchDOMEvent(nullptr, domEvent, nullptr, &dummy);
     return true;
   }
 
-  bool
-  WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate)
+private:
+  virtual bool
+  WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate) MOZ_OVERRIDE
   {
     MOZ_ASSERT_IF(mToMessagePort, aWorkerPrivate->IsSharedWorker());
 
-    if (mTarget == ParentThread) {
+    if (mBehavior == ParentThreadUnchangedBusyCount) {
       // Don't fire this event if the JS object has been disconnected from the
       // private object.
       if (!aWorkerPrivate->IsAcceptingEvents()) {
         return true;
       }
 
       if (mToMessagePort) {
         return
@@ -958,201 +1036,130 @@ public:
 
       aWorkerPrivate->AssertInnerWindowIsCorrect();
 
       return DispatchDOMEvent(aCx, aWorkerPrivate, aWorkerPrivate,
                               !aWorkerPrivate->GetParent());
     }
 
     MOZ_ASSERT(aWorkerPrivate == GetWorkerPrivateFromContext(aCx));
+
     if (mToMessagePort) {
       nsRefPtr<workers::MessagePort> port =
         aWorkerPrivate->GetMessagePort(mMessagePortSerial);
       if (!port) {
         // Must have been closed already.
         return true;
       }
       return DispatchDOMEvent(aCx, aWorkerPrivate, port, false);
     }
 
     return DispatchDOMEvent(aCx, aWorkerPrivate, aWorkerPrivate->GlobalScope(),
                             false);
   }
-
-  void PostRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate, bool aRunResult)
-  {
-    WorkerRunnable::PostRun(aCx, aWorkerPrivate, aRunResult);
-  }
 };
 
-class NotifyRunnable : public WorkerControlRunnable
+class NotifyRunnable MOZ_FINAL : public WorkerControlRunnable
 {
   Status mStatus;
 
 public:
   NotifyRunnable(WorkerPrivate* aWorkerPrivate, Status aStatus)
-  : WorkerControlRunnable(aWorkerPrivate, WorkerThread, UnchangedBusyCount),
+  : WorkerControlRunnable(aWorkerPrivate, WorkerThreadUnchangedBusyCount),
     mStatus(aStatus)
   {
-    NS_ASSERTION(aStatus == Terminating || aStatus == Canceling ||
-                 aStatus == Killing, "Bad status!");
-  }
-
-  bool
-  PreDispatch(JSContext* aCx, WorkerPrivate* aWorkerPrivate)
+    MOZ_ASSERT(aStatus == Terminating || aStatus == Canceling ||
+               aStatus == Killing);
+  }
+
+private:
+  virtual bool
+  PreDispatch(JSContext* aCx, WorkerPrivate* aWorkerPrivate) MOZ_OVERRIDE
   {
     // Modify here, but not in PostRun! This busy count addition will be matched
     // by the CloseEventRunnable.
     return aWorkerPrivate->ModifyBusyCount(aCx, true);
   }
 
-  bool
-  WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate)
-  {
-    return aWorkerPrivate->NotifyInternal(aCx, mStatus);
-  }
-
-  void
+  virtual void
   PostDispatch(JSContext* aCx, WorkerPrivate* aWorkerPrivate,
-               bool aDispatchResult)
+               bool aDispatchResult) MOZ_OVERRIDE
   {
     if (!aDispatchResult) {
       // We couldn't dispatch to the worker, which means it's already dead.
       // Undo the busy count modification.
       aWorkerPrivate->ModifyBusyCount(aCx, false);
     }
   }
+
+  virtual bool
+  WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate) MOZ_OVERRIDE
+  {
+    return aWorkerPrivate->NotifyInternal(aCx, mStatus);
+  }
 };
 
 class CloseRunnable MOZ_FINAL : public WorkerControlRunnable
 {
 public:
   CloseRunnable(WorkerPrivate* aWorkerPrivate)
-  : WorkerControlRunnable(aWorkerPrivate, ParentThread, UnchangedBusyCount)
+  : WorkerControlRunnable(aWorkerPrivate, ParentThreadUnchangedBusyCount)
   { }
 
-  bool
-  WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate)
+private:
+  virtual bool
+  WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate) MOZ_OVERRIDE
   {
     // This busy count will be matched by the CloseEventRunnable.
     return aWorkerPrivate->ModifyBusyCount(aCx, true) &&
            aWorkerPrivate->Close(aCx);
   }
 };
 
-class SuspendRunnable : public WorkerControlRunnable
+class SuspendRunnable MOZ_FINAL : public WorkerControlRunnable
 {
 public:
   SuspendRunnable(WorkerPrivate* aWorkerPrivate)
-  : WorkerControlRunnable(aWorkerPrivate, WorkerThread, UnchangedBusyCount)
+  : WorkerControlRunnable(aWorkerPrivate, WorkerThreadUnchangedBusyCount)
   { }
 
-  bool
-  WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate)
+private:
+  virtual bool
+  WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate) MOZ_OVERRIDE
   {
     return aWorkerPrivate->SuspendInternal(aCx);
   }
 };
 
-class ResumeRunnable : public WorkerControlRunnable
+class ResumeRunnable MOZ_FINAL : public WorkerControlRunnable
 {
 public:
   ResumeRunnable(WorkerPrivate* aWorkerPrivate)
-  : WorkerControlRunnable(aWorkerPrivate, WorkerThread, UnchangedBusyCount)
+  : WorkerControlRunnable(aWorkerPrivate, WorkerThreadUnchangedBusyCount)
   { }
 
-  bool
-  WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate)
+private:
+  virtual bool
+  WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate) MOZ_OVERRIDE
   {
     return aWorkerPrivate->ResumeInternal(aCx);
   }
 };
 
-class ReportErrorRunnable : public WorkerRunnable
+class ReportErrorRunnable MOZ_FINAL : public WorkerRunnable
 {
   nsString mMessage;
   nsString mFilename;
   nsString mLine;
   uint32_t mLineNumber;
   uint32_t mColumnNumber;
   uint32_t mFlags;
   uint32_t mErrorNumber;
 
 public:
-  ReportErrorRunnable(WorkerPrivate* aWorkerPrivate, const nsString& aMessage,
-                      const nsString& aFilename, const nsString& aLine,
-                      uint32_t aLineNumber, uint32_t aColumnNumber,
-                      uint32_t aFlags, uint32_t aErrorNumber)
-  : WorkerRunnable(aWorkerPrivate, ParentThread, UnchangedBusyCount,
-                   SkipWhenClearing),
-    mMessage(aMessage), mFilename(aFilename), mLine(aLine),
-    mLineNumber(aLineNumber), mColumnNumber(aColumnNumber), mFlags(aFlags),
-    mErrorNumber(aErrorNumber)
-  { }
-
-  void
-  PostDispatch(JSContext* aCx, WorkerPrivate* aWorkerPrivate,
-               bool aDispatchResult)
-  {
-    aWorkerPrivate->AssertIsOnWorkerThread();
-
-    // Dispatch may fail if the worker was canceled, no need to report that as
-    // an error, so don't call base class PostDispatch.
-  }
-
-  bool
-  WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate)
-  {
-    // Don't fire this event if the JS object has been disconnected from the
-    // private object.
-    if (!aWorkerPrivate->IsAcceptingEvents()) {
-      return true;
-    }
-
-    JS::Rooted<JSObject*> target(aCx, aWorkerPrivate->GetWrapper());
-
-    uint64_t innerWindowId;
-    bool fireAtScope = true;
-
-    WorkerPrivate* parent = aWorkerPrivate->GetParent();
-    if (parent) {
-      innerWindowId = 0;
-    }
-    else {
-      AssertIsOnMainThread();
-
-      if (aWorkerPrivate->IsSuspended()) {
-        aWorkerPrivate->QueueRunnable(this);
-        return true;
-      }
-
-      if (aWorkerPrivate->IsSharedWorker()) {
-        aWorkerPrivate->BroadcastErrorToSharedWorkers(aCx, mMessage, mFilename,
-                                                      mLine, mLineNumber,
-                                                      mColumnNumber, mFlags);
-        return true;
-      }
-
-      aWorkerPrivate->AssertInnerWindowIsCorrect();
-
-      innerWindowId = aWorkerPrivate->GetInnerWindowId();
-    }
-
-    return ReportErrorRunnable::ReportError(aCx, parent, fireAtScope,
-                                            aWorkerPrivate, mMessage,
-                                            mFilename, mLine, mLineNumber,
-                                            mColumnNumber, mFlags,
-                                            mErrorNumber, innerWindowId);
-  }
-
-  void PostRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate, bool aRunResult)
-  {
-    WorkerRunnable::PostRun(aCx, aWorkerPrivate, aRunResult);
-  }
-
   // aWorkerPrivate is the worker thread we're on (or the main thread, if null)
   // aTarget is the worker object that we are going to fire an error at
   // (if any).
   static bool
   ReportError(JSContext* aCx, WorkerPrivate* aWorkerPrivate,
               bool aFireAtScope, WorkerPrivate* aTarget,
               const nsString& aMessage, const nsString& aFilename,
               const nsString& aLine, uint32_t aLineNumber,
@@ -1258,361 +1265,430 @@ public:
       return runnable->Dispatch(aCx);
     }
 
     // Otherwise log an error to the error console.
     LogErrorToConsole(aMessage, aFilename, aLine, aLineNumber, aColumnNumber,
                       aFlags, aInnerWindowId);
     return true;
   }
+
+private:
+  ReportErrorRunnable(WorkerPrivate* aWorkerPrivate, const nsString& aMessage,
+                      const nsString& aFilename, const nsString& aLine,
+                      uint32_t aLineNumber, uint32_t aColumnNumber,
+                      uint32_t aFlags, uint32_t aErrorNumber)
+  : WorkerRunnable(aWorkerPrivate, ParentThreadUnchangedBusyCount),
+    mMessage(aMessage), mFilename(aFilename), mLine(aLine),
+    mLineNumber(aLineNumber), mColumnNumber(aColumnNumber), mFlags(aFlags),
+    mErrorNumber(aErrorNumber)
+  { }
+
+  virtual void
+  PostDispatch(JSContext* aCx, WorkerPrivate* aWorkerPrivate,
+               bool aDispatchResult) MOZ_OVERRIDE
+  {
+    aWorkerPrivate->AssertIsOnWorkerThread();
+
+    // Dispatch may fail if the worker was canceled, no need to report that as
+    // an error, so don't call base class PostDispatch.
+  }
+
+  virtual bool
+  WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate) MOZ_OVERRIDE
+  {
+    // Don't fire this event if the JS object has been disconnected from the
+    // private object.
+    if (!aWorkerPrivate->IsAcceptingEvents()) {
+      return true;
+    }
+
+    JS::Rooted<JSObject*> target(aCx, aWorkerPrivate->GetWrapper());
+
+    uint64_t innerWindowId;
+    bool fireAtScope = true;
+
+    WorkerPrivate* parent = aWorkerPrivate->GetParent();
+    if (parent) {
+      innerWindowId = 0;
+    }
+    else {
+      AssertIsOnMainThread();
+
+      if (aWorkerPrivate->IsSuspended()) {
+        aWorkerPrivate->QueueRunnable(this);
+        return true;
+      }
+
+      if (aWorkerPrivate->IsSharedWorker()) {
+        aWorkerPrivate->BroadcastErrorToSharedWorkers(aCx, mMessage, mFilename,
+                                                      mLine, mLineNumber,
+                                                      mColumnNumber, mFlags);
+        return true;
+      }
+
+      aWorkerPrivate->AssertInnerWindowIsCorrect();
+
+      innerWindowId = aWorkerPrivate->GetInnerWindowId();
+    }
+
+    return ReportError(aCx, parent, fireAtScope, aWorkerPrivate, mMessage,
+                       mFilename, mLine, mLineNumber, mColumnNumber, mFlags,
+                       mErrorNumber, innerWindowId);
+  }
 };
 
-class TimerRunnable : public WorkerRunnable
+class TimerRunnable MOZ_FINAL : public WorkerRunnable
 {
 public:
   TimerRunnable(WorkerPrivate* aWorkerPrivate)
-  : WorkerRunnable(aWorkerPrivate, WorkerThread, UnchangedBusyCount,
-                   SkipWhenClearing)
+  : WorkerRunnable(aWorkerPrivate, WorkerThreadUnchangedBusyCount)
   { }
 
-  bool
-  PreDispatch(JSContext* aCx, WorkerPrivate* aWorkerPrivate)
+private:
+  virtual bool
+  PreDispatch(JSContext* aCx, WorkerPrivate* aWorkerPrivate) MOZ_OVERRIDE
   {
     // Silence bad assertions.
     return true;
   }
 
-  void
+  virtual void
   PostDispatch(JSContext* aCx, WorkerPrivate* aWorkerPrivate,
-               bool aDispatchResult)
+               bool aDispatchResult) MOZ_OVERRIDE
   {
     // Silence bad assertions.
   }
 
-  bool
-  WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate)
+  virtual bool
+  WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate) MOZ_OVERRIDE
   {
     return aWorkerPrivate->RunExpiredTimeouts(aCx);
   }
 };
 
 void
 DummyCallback(nsITimer* aTimer, void* aClosure)
 {
   // Nothing!
 }
 
-class WorkerRunnableEventTarget MOZ_FINAL : public nsIEventTarget
+class TimerThreadEventTarget MOZ_FINAL : public nsIEventTarget
 {
-protected:
+  WorkerPrivate* mWorkerPrivate;
   nsRefPtr<WorkerRunnable> mWorkerRunnable;
 
 public:
-  WorkerRunnableEventTarget(WorkerRunnable* aWorkerRunnable)
-  : mWorkerRunnable(aWorkerRunnable)
-  { }
+  TimerThreadEventTarget(WorkerPrivate* aWorkerPrivate,
+                         WorkerRunnable* aWorkerRunnable)
+  : mWorkerPrivate(aWorkerPrivate), mWorkerRunnable(aWorkerRunnable)
+  {
+    MOZ_ASSERT(aWorkerPrivate);
+    MOZ_ASSERT(aWorkerRunnable);
+  }
 
   NS_DECL_THREADSAFE_ISUPPORTS
 
+protected:
   NS_IMETHOD
-  Dispatch(nsIRunnable* aRunnable, uint32_t aFlags)
+  Dispatch(nsIRunnable* aRunnable, uint32_t aFlags) MOZ_OVERRIDE
   {
-    NS_ASSERTION(aFlags == nsIEventTarget::DISPATCH_NORMAL, "Don't call me!");
-
-    nsRefPtr<WorkerRunnableEventTarget> kungFuDeathGrip = this;
+    // This should only happen on the timer thread.
+    MOZ_ASSERT(!NS_IsMainThread());
+    MOZ_ASSERT(aFlags == nsIEventTarget::DISPATCH_NORMAL);
+
+    nsRefPtr<TimerThreadEventTarget> kungFuDeathGrip = this;
 
     // Run the runnable we're given now (should just call DummyCallback()),
     // otherwise the timer thread will leak it...  If we run this after
     // dispatch running the event can race against resetting the timer.
     aRunnable->Run();
 
     // This can fail if we're racing to terminate or cancel, should be handled
     // by the terminate or cancel code.
     mWorkerRunnable->Dispatch(nullptr);
 
     return NS_OK;
   }
 
   NS_IMETHOD
-  IsOnCurrentThread(bool* aIsOnCurrentThread)
+  IsOnCurrentThread(bool* aIsOnCurrentThread) MOZ_OVERRIDE
   {
-    *aIsOnCurrentThread = false;
+    MOZ_ASSERT(aIsOnCurrentThread);
+
+    nsresult rv = mWorkerPrivate->IsOnCurrentThread(aIsOnCurrentThread);
+    if (NS_WARN_IF(NS_FAILED(rv))) {
+      return rv;
+    }
+
     return NS_OK;
   }
 };
 
-NS_IMPL_ISUPPORTS1(WorkerRunnableEventTarget, nsIEventTarget)
-
-class KillCloseEventRunnable : public WorkerRunnable
+class KillCloseEventRunnable MOZ_FINAL : public WorkerRunnable
 {
   nsCOMPtr<nsITimer> mTimer;
 
-  class KillScriptRunnable : public WorkerControlRunnable
+  class KillScriptRunnable MOZ_FINAL : public WorkerControlRunnable
   {
   public:
     KillScriptRunnable(WorkerPrivate* aWorkerPrivate)
-    : WorkerControlRunnable(aWorkerPrivate, WorkerThread, UnchangedBusyCount)
+    : WorkerControlRunnable(aWorkerPrivate, WorkerThreadUnchangedBusyCount)
     { }
 
-    bool
-    PreDispatch(JSContext* aCx, WorkerPrivate* aWorkerPrivate)
+  private:
+    virtual bool
+    PreDispatch(JSContext* aCx, WorkerPrivate* aWorkerPrivate) MOZ_OVERRIDE
     {
-      // Silence bad assertions.
+      // Silence bad assertions, this is dispatched from the timer thread.
       return true;
     }
 
-    void
+    virtual void
     PostDispatch(JSContext* aCx, WorkerPrivate* aWorkerPrivate,
-                 bool aDispatchResult)
+                 bool aDispatchResult) MOZ_OVERRIDE
     {
-      // Silence bad assertions.
-    }
-
-    bool
-    WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate)
+      // Silence bad assertions, this is dispatched from the timer thread.
+    }
+
+    virtual bool
+    WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate) MOZ_OVERRIDE
     {
       // Kill running script.
       return false;
     }
   };
 
 public:
   KillCloseEventRunnable(WorkerPrivate* aWorkerPrivate)
-  : WorkerRunnable(aWorkerPrivate, WorkerThread, UnchangedBusyCount,
-                   SkipWhenClearing)
+  : WorkerRunnable(aWorkerPrivate, WorkerThreadUnchangedBusyCount)
   { }
 
-  ~KillCloseEventRunnable()
-  {
-    if (mTimer) {
-      mTimer->Cancel();
-    }
-  }
-
-  bool
-  PreDispatch(JSContext* aCx, WorkerPrivate* aWorkerPrivate)
-  {
-    NS_NOTREACHED("Not meant to be dispatched!");
-    return false;
-  }
-
   bool
   SetTimeout(JSContext* aCx, uint32_t aDelayMS)
   {
     nsCOMPtr<nsITimer> timer = do_CreateInstance(NS_TIMER_CONTRACTID);
     if (!timer) {
       JS_ReportError(aCx, "Failed to create timer!");
       return false;
     }
 
     nsRefPtr<KillScriptRunnable> runnable =
       new KillScriptRunnable(mWorkerPrivate);
 
-    nsRefPtr<WorkerRunnableEventTarget> target =
-      new WorkerRunnableEventTarget(runnable);
+    nsRefPtr<TimerThreadEventTarget> target =
+      new TimerThreadEventTarget(mWorkerPrivate, runnable);
 
     if (NS_FAILED(timer->SetTarget(target))) {
       JS_ReportError(aCx, "Failed to set timer's target!");
       return false;
     }
 
     if (NS_FAILED(timer->InitWithFuncCallback(DummyCallback, nullptr, aDelayMS,
                                               nsITimer::TYPE_ONE_SHOT))) {
       JS_ReportError(aCx, "Failed to start timer!");
       return false;
     }
 
     mTimer.swap(timer);
     return true;
   }
 
-  bool
-  WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate)
+private:
+  ~KillCloseEventRunnable()
+  {
+    if (mTimer) {
+      mTimer->Cancel();
+    }
+  }
+
+  virtual bool
+  PreDispatch(JSContext* aCx, WorkerPrivate* aWorkerPrivate) MOZ_OVERRIDE
+  {
+    MOZ_ASSUME_UNREACHABLE("Don't call Dispatch() on KillCloseEventRunnable!");
+  }
+
+  virtual void
+  PostDispatch(JSContext* aCx, WorkerPrivate* aWorkerPrivate,
+               bool aDispatchResult) MOZ_OVERRIDE
+  {
+    MOZ_ASSUME_UNREACHABLE("Don't call Dispatch() on KillCloseEventRunnable!");
+  }
+
+  virtual bool
+  WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate) MOZ_OVERRIDE
   {
     if (mTimer) {
       mTimer->Cancel();
       mTimer = nullptr;
     }
 
     return true;
   }
 };
 
-class UpdateJSContextOptionsRunnable : public WorkerControlRunnable
+class UpdateJSContextOptionsRunnable MOZ_FINAL : public WorkerControlRunnable
 {
   JS::ContextOptions mContentOptions;
   JS::ContextOptions mChromeOptions;
 
 public:
   UpdateJSContextOptionsRunnable(WorkerPrivate* aWorkerPrivate,
                                  const JS::ContextOptions& aContentOptions,
                                  const JS::ContextOptions& aChromeOptions)
-  : WorkerControlRunnable(aWorkerPrivate, WorkerThread, UnchangedBusyCount),
+  : WorkerControlRunnable(aWorkerPrivate, WorkerThreadUnchangedBusyCount),
     mContentOptions(aContentOptions), mChromeOptions(aChromeOptions)
   { }
 
-  bool
-  WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate)
+private:
+  virtual bool
+  WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate) MOZ_OVERRIDE
   {
     aWorkerPrivate->UpdateJSContextOptionsInternal(aCx, mContentOptions,
                                                    mChromeOptions);
     return true;
   }
 };
 
-class UpdatePreferenceRunnable : public WorkerControlRunnable
+class UpdatePreferenceRunnable MOZ_FINAL : public WorkerControlRunnable
 {
   WorkerPreference mPref;
   bool mValue;
 
 public:
   UpdatePreferenceRunnable(WorkerPrivate* aWorkerPrivate,
                            WorkerPreference aPref,
                            bool aValue)
-    : WorkerControlRunnable(aWorkerPrivate, WorkerThread, UnchangedBusyCount),
+    : WorkerControlRunnable(aWorkerPrivate, WorkerThreadUnchangedBusyCount),
       mPref(aPref),
       mValue(aValue)
-  {
-  }
-
-  bool
-  WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate)
+  { }
+
+  virtual bool
+  WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate) MOZ_OVERRIDE
   {
     aWorkerPrivate->UpdatePreferenceInternal(aCx, mPref, mValue);
     return true;
   }
 };
 
-class UpdateJSWorkerMemoryParameterRunnable : public WorkerControlRunnable
+class UpdateJSWorkerMemoryParameterRunnable MOZ_FINAL :
+  public WorkerControlRunnable
 {
   uint32_t mValue;
   JSGCParamKey mKey;
 
 public:
   UpdateJSWorkerMemoryParameterRunnable(WorkerPrivate* aWorkerPrivate,
                                         JSGCParamKey aKey,
                                         uint32_t aValue)
-  : WorkerControlRunnable(aWorkerPrivate, WorkerThread, UnchangedBusyCount),
+  : WorkerControlRunnable(aWorkerPrivate, WorkerThreadUnchangedBusyCount),
     mValue(aValue), mKey(aKey)
   { }
 
-  bool
-  WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate)
+private:
+  virtual bool
+  WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate) MOZ_OVERRIDE
   {
     aWorkerPrivate->UpdateJSWorkerMemoryParameterInternal(aCx, mKey, mValue);
     return true;
   }
 };
 
 #ifdef JS_GC_ZEAL
-class UpdateGCZealRunnable : public WorkerControlRunnable
+class UpdateGCZealRunnable MOZ_FINAL : public WorkerControlRunnable
 {
   uint8_t mGCZeal;
   uint32_t mFrequency;
 
 public:
   UpdateGCZealRunnable(WorkerPrivate* aWorkerPrivate,
                        uint8_t aGCZeal,
                        uint32_t aFrequency)
-  : WorkerControlRunnable(aWorkerPrivate, WorkerThread, UnchangedBusyCount),
+  : WorkerControlRunnable(aWorkerPrivate, WorkerThreadUnchangedBusyCount),
     mGCZeal(aGCZeal), mFrequency(aFrequency)
   { }
 
-  bool
-  WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate)
+private:
+  virtual bool
+  WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate) MOZ_OVERRIDE
   {
     aWorkerPrivate->UpdateGCZealInternal(aCx, mGCZeal, mFrequency);
     return true;
   }
 };
 #endif
 
-class UpdateJITHardeningRunnable : public WorkerControlRunnable
+class UpdateJITHardeningRunnable MOZ_FINAL : public WorkerControlRunnable
 {
   bool mJITHardening;
 
 public:
   UpdateJITHardeningRunnable(WorkerPrivate* aWorkerPrivate, bool aJITHardening)
-  : WorkerControlRunnable(aWorkerPrivate, WorkerThread, UnchangedBusyCount),
+  : WorkerControlRunnable(aWorkerPrivate, WorkerThreadUnchangedBusyCount),
     mJITHardening(aJITHardening)
   { }
 
-  bool
-  WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate)
+private:
+  virtual bool
+  WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate) MOZ_OVERRIDE
   {
     aWorkerPrivate->UpdateJITHardeningInternal(aCx, mJITHardening);
     return true;
   }
 };
 
-class GarbageCollectRunnable : public WorkerControlRunnable
+class GarbageCollectRunnable MOZ_FINAL : public WorkerControlRunnable
 {
-protected:
   bool mShrinking;
   bool mCollectChildren;
 
 public:
   GarbageCollectRunnable(WorkerPrivate* aWorkerPrivate, bool aShrinking,
                          bool aCollectChildren)
-  : WorkerControlRunnable(aWorkerPrivate, WorkerThread, UnchangedBusyCount),
+  : WorkerControlRunnable(aWorkerPrivate, WorkerThreadUnchangedBusyCount),
     mShrinking(aShrinking), mCollectChildren(aCollectChildren)
   { }
 
-  bool
-  PreDispatch(JSContext* aCx, WorkerPrivate* aWorkerPrivate)
+private:
+  virtual bool
+  PreDispatch(JSContext* aCx, WorkerPrivate* aWorkerPrivate) MOZ_OVERRIDE
   {
     // Silence bad assertions, this can be dispatched from either the main
     // thread or the timer thread..
     return true;
   }
 
-  void
+  virtual void
   PostDispatch(JSContext* aCx, WorkerPrivate* aWorkerPrivate,
-               bool aDispatchResult)
+                bool aDispatchResult) MOZ_OVERRIDE
   {
     // Silence bad assertions, this can be dispatched from either the main
     // thread or the timer thread..
   }
 
-  bool
-  WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate)
+  virtual bool
+  WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate) MOZ_OVERRIDE
   {
     aWorkerPrivate->GarbageCollectInternal(aCx, mShrinking, mCollectChildren);
     return true;
   }
 };
 
 class CycleCollectRunnable : public WorkerControlRunnable
 {
-protected:
   bool mCollectChildren;
 
 public:
   CycleCollectRunnable(WorkerPrivate* aWorkerPrivate, bool aCollectChildren)
-  : WorkerControlRunnable(aWorkerPrivate, WorkerThread, UnchangedBusyCount),
+  : WorkerControlRunnable(aWorkerPrivate, WorkerThreadUnchangedBusyCount),
     mCollectChildren(aCollectChildren)
   { }
 
   bool
-  PreDispatch(JSContext* aCx, WorkerPrivate* aWorkerPrivate)
-  {
-    // Silence bad assertions, this can be dispatched from either the main
-    // thread or the timer thread..
-    return true;
-  }
-
-  void
-  PostDispatch(JSContext* aCx, WorkerPrivate* aWorkerPrivate,
-               bool aDispatchResult)
-  {
-    // Silence bad assertions, this can be dispatched from either the main
-    // thread or the timer thread..
-  }
-
-  bool
   WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate)
   {
     aWorkerPrivate->CycleCollectInternal(aCx, mCollectChildren);
     return true;
   }
 };
 
 class WorkerJSRuntimeStats : public JS::RuntimeStats
@@ -1672,275 +1748,100 @@ public:
 
     // This should never be used when reporting with workers (hence the "?!").
     extras->domPathPrefix.AssignLiteral("explicit/workers/?!/");
 
     aCompartmentStats->extra = extras;
   }
 };
 
-class MessagePortRunnable : public WorkerRunnable
+class MessagePortRunnable MOZ_FINAL : public WorkerRunnable
 {
   uint64_t mMessagePortSerial;
   bool mConnect;
 
 public:
   MessagePortRunnable(WorkerPrivate* aWorkerPrivate,
                       uint64_t aMessagePortSerial,
                       bool aConnect)
-  : WorkerRunnable(aWorkerPrivate, WorkerThread,
-                   aConnect ? ModifyBusyCount : UnchangedBusyCount,
-                   SkipWhenClearing),
+  : WorkerRunnable(aWorkerPrivate, aConnect ?
+                                   WorkerThreadModifyBusyCount :
+                                   WorkerThreadUnchangedBusyCount),
     mMessagePortSerial(aMessagePortSerial), mConnect(aConnect)
   { }
 
-  bool
-  WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate)
+private:
+  ~MessagePortRunnable()
+  { }
+
+  virtual bool
+  WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate) MOZ_OVERRIDE
   {
     if (mConnect) {
       return aWorkerPrivate->ConnectMessagePort(aCx, mMessagePortSerial);
     }
 
     aWorkerPrivate->DisconnectMessagePort(mMessagePortSerial);
     return true;
   }
 };
 
-} /* anonymous namespace */
-
 #ifdef DEBUG
-void
-mozilla::dom::workers::AssertIsOnMainThread()
-{
-  MOZ_ASSERT(NS_IsMainThread(), "Wrong thread!");
-}
-
-WorkerRunnable::WorkerRunnable(WorkerPrivate* aWorkerPrivate, Target aTarget,
-                               BusyBehavior aBusyBehavior,
-                               ClearingBehavior aClearingBehavior)
-: mWorkerPrivate(aWorkerPrivate), mTarget(aTarget),
-  mBusyBehavior(aBusyBehavior), mClearingBehavior(aClearingBehavior)
-{
-  NS_ASSERTION(aWorkerPrivate, "Null worker private!");
-}
-#endif
-
-NS_IMPL_ISUPPORTS1(WorkerRunnable, nsIRunnable)
-
-bool
-WorkerRunnable::PreDispatch(JSContext* aCx, WorkerPrivate* aWorkerPrivate)
+
+PRThread*
+PRThreadFromThread(nsIThread* aThread)
 {
-#ifdef DEBUG
-  if (mBusyBehavior == ModifyBusyCount) {
-    NS_ASSERTION(mTarget == WorkerThread,
-                 "Don't set this option unless targeting the worker thread!");
-  }
-  if (mTarget == ParentThread) {
-    aWorkerPrivate->AssertIsOnWorkerThread();
-  }
-  else {
-    aWorkerPrivate->AssertIsOnParentThread();
-  }
-#endif
-
-  if (mBusyBehavior == ModifyBusyCount && aCx) {
-    return aWorkerPrivate->ModifyBusyCount(aCx, true);
-  }
-
-  return true;
-}
-
-bool
-WorkerRunnable::Dispatch(JSContext* aCx)
-{
-  bool ok;
-
-  if (!aCx) {
-    ok = PreDispatch(nullptr, mWorkerPrivate);
-    if (ok) {
-      ok = DispatchInternal();
-    }
-    PostDispatch(nullptr, mWorkerPrivate, ok);
-    return ok;
-  }
-
-  JSAutoRequest ar(aCx);
-
-  JS::Rooted<JSObject*> global(aCx, JS::CurrentGlobalOrNull(aCx));
-
-  Maybe<JSAutoCompartment> ac;
-  if (global) {
-    ac.construct(aCx, global);
-  }
-
-  ok = PreDispatch(aCx, mWorkerPrivate);
-
-  if (ok && !DispatchInternal()) {
-    ok = false;
-  }
-
-  PostDispatch(aCx, mWorkerPrivate, ok);
-
-  return ok;
-}
-
-// static
-bool
-WorkerRunnable::DispatchToMainThread(nsIRunnable* aRunnable)
-{
-  nsCOMPtr<nsIThread> mainThread = do_GetMainThread();
-  NS_ASSERTION(mainThread, "This should never fail!");
-
-  return NS_SUCCEEDED(mainThread->Dispatch(aRunnable, NS_DISPATCH_NORMAL));
+  MOZ_ASSERT(aThread);
+
+  PRThread* result;
+  MOZ_ASSERT(NS_SUCCEEDED(aThread->GetPRThread(&result)));
+  MOZ_ASSERT(result);
+
+  return result;
 }
 
-// These DispatchInternal functions look identical but carry important type
-// informaton so they can't be consolidated...
-
-#define IMPL_DISPATCH_INTERNAL(_class)                                         \
-  bool                                                                         \
-  _class ::DispatchInternal()                                                  \
-  {                                                                            \
-    if (mTarget == WorkerThread) {                                             \
-      return mWorkerPrivate->Dispatch(this);                                   \
-    }                                                                          \
-                                                                               \
-    if (mWorkerPrivate->GetParent()) {                                         \
-      return mWorkerPrivate->GetParent()->Dispatch(this);                      \
-    }                                                                          \
-                                                                               \
-    return DispatchToMainThread(this);                                         \
-  }
-
-IMPL_DISPATCH_INTERNAL(WorkerRunnable)
-IMPL_DISPATCH_INTERNAL(WorkerSyncRunnable)
-IMPL_DISPATCH_INTERNAL(WorkerControlRunnable)
-
-#undef IMPL_DISPATCH_INTERNAL
-
-void
-WorkerRunnable::PostDispatch(JSContext* aCx, WorkerPrivate* aWorkerPrivate,
-                             bool aDispatchResult)
-{
-#ifdef DEBUG
-  if (mTarget == ParentThread) {
-    aWorkerPrivate->AssertIsOnWorkerThread();
-  }
-  else {
-    aWorkerPrivate->AssertIsOnParentThread();
-  }
-#endif
-
-  if (!aDispatchResult && aCx) {
-    if (mBusyBehavior == ModifyBusyCount) {
-      aWorkerPrivate->ModifyBusyCount(aCx, false);
-    }
-    JS_ReportPendingException(aCx);
-  }
-}
-
-NS_IMETHODIMP
-WorkerRunnable::Run()
-{
-  JSContext* cx;
-  nsRefPtr<WorkerPrivate> kungFuDeathGrip;
-  nsCxPusher pusher;
-
-  if (mTarget == WorkerThread) {
-    mWorkerPrivate->AssertIsOnWorkerThread();
-    cx = mWorkerPrivate->GetJSContext();
-  } else {
-    kungFuDeathGrip = mWorkerPrivate;
-    mWorkerPrivate->AssertIsOnParentThread();
-    cx = mWorkerPrivate->ParentJSContext();
-
-    if (!mWorkerPrivate->GetParent()) {
-      AssertIsOnMainThread();
-      pusher.Push(cx);
-    }
-  }
-
-  JS::Rooted<JSObject*> targetCompartmentObject(cx);
-
-  if (mTarget == WorkerThread) {
-    targetCompartmentObject = JS::CurrentGlobalOrNull(cx);
-  } else {
-    targetCompartmentObject = mWorkerPrivate->GetWrapper();
-  }
-
-  NS_ASSERTION(cx, "Must have a context!");
-
-  JSAutoRequest ar(cx);
-
-  Maybe<JSAutoCompartment> ac;
-  if (targetCompartmentObject) {
-    ac.construct(cx, targetCompartmentObject);
-  }
-
-  bool result = WorkerRun(cx, mWorkerPrivate);
-  // In the case of CompileScriptRunnnable, WorkerRun above can cause us to
-  // lazily create a global, in which case we need to be in its compartment
-  // when calling PostRun() below. Maybe<> this time...
-  if (mTarget == WorkerThread && ac.empty() &&
-      js::DefaultObjectForContextOrNull(cx)) {
-    ac.construct(cx, js::DefaultObjectForContextOrNull(cx));
-  }
-  PostRun(cx, mWorkerPrivate, result);
-  return result ? NS_OK : NS_ERROR_FAILURE;
-}
-
-void
-WorkerRunnable::PostRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate,
-                        bool aRunResult)
-{
-#ifdef DEBUG
-  if (mTarget == ParentThread) {
-    mWorkerPrivate->AssertIsOnParentThread();
-  }
-  else {
-    mWorkerPrivate->AssertIsOnWorkerThread();
-  }
-#endif
-
-  if (mBusyBehavior == ModifyBusyCount) {
-    if (!aWorkerPrivate->ModifyBusyCountFromWorker(aCx, false)) {
-      aRunResult = false;
-    }
-  }
-
-  if (!aRunResult) {
-    JS_ReportPendingException(aCx);
-  }
-}
+#endif // DEBUG
+
+} /* anonymous namespace */
+
+NS_IMPL_ISUPPORTS_INHERITED0(MainThreadReleaseRunnable, nsRunnable)
+
+NS_IMPL_ISUPPORTS_INHERITED0(TopLevelWorkerFinishedRunnable, nsRunnable)
+
+NS_IMPL_ISUPPORTS1(TimerThreadEventTarget, nsIEventTarget)
 
 template <class Derived>
-class WorkerPrivateParent<Derived>::SynchronizeAndResumeRunnable
+class WorkerPrivateParent<Derived>::SynchronizeAndResumeRunnable MOZ_FINAL
   : public nsRunnable
 {
-  friend class WorkerPrivateParent<Derived>;
   friend class nsRevocableEventPtr<SynchronizeAndResumeRunnable>;
 
   WorkerPrivate* mWorkerPrivate;
   nsCOMPtr<nsPIDOMWindow> mWindow;
   nsCOMPtr<nsIScriptContext> mScriptContext;
 
+public:
   SynchronizeAndResumeRunnable(WorkerPrivate* aWorkerPrivate,
                                nsPIDOMWindow* aWindow,
                                nsIScriptContext* aScriptContext)
   : mWorkerPrivate(aWorkerPrivate), mWindow(aWindow),
     mScriptContext(aScriptContext)
   {
     AssertIsOnMainThread();
     MOZ_ASSERT(aWorkerPrivate);
     MOZ_ASSERT(aWindow);
     MOZ_ASSERT(!aWorkerPrivate->GetParent());
   }
 
+private:
+  ~SynchronizeAndResumeRunnable()
+  { }
+
   NS_IMETHOD
-  Run()
+  Run() MOZ_OVERRIDE
   {
     AssertIsOnMainThread();
 
     if (mWorkerPrivate) {
       AutoPushJSContext cx(mScriptContext ?
                            mScriptContext->GetNativeContext() :
                            nsContentUtils::GetSafeJSContext());
 
@@ -1960,16 +1861,72 @@ class WorkerPrivateParent<Derived>::Sync
     MOZ_ASSERT(mWindow);
 
     mWorkerPrivate = nullptr;
     mWindow = nullptr;
     mScriptContext = nullptr;
   }
 };
 
+template <class Derived>
+class WorkerPrivateParent<Derived>::EventTarget MOZ_FINAL
+  : public nsIEventTarget
+{
+  // This mutex protects mWorkerPrivate and must be acquired *before* the
+  // WorkerPrivate's mutex whenever they must both be held.
+  mozilla::Mutex mMutex;
+  WorkerPrivate* mWorkerPrivate;
+  nsIEventTarget* mWeakNestedEventTarget;
+  nsCOMPtr<nsIEventTarget> mNestedEventTarget;
+
+public:
+  EventTarget(WorkerPrivate* aWorkerPrivate)
+  : mMutex("WorkerPrivateParent::EventTarget::mMutex"),
+    mWorkerPrivate(aWorkerPrivate), mWeakNestedEventTarget(nullptr)
+  {
+    MOZ_ASSERT(aWorkerPrivate);
+  }
+
+  EventTarget(WorkerPrivate* aWorkerPrivate, nsIEventTarget* aNestedEventTarget)
+  : mMutex("WorkerPrivateParent::EventTarget::mMutex"),
+    mWorkerPrivate(aWorkerPrivate), mWeakNestedEventTarget(aNestedEventTarget),
+    mNestedEventTarget(aNestedEventTarget)
+  {
+    MOZ_ASSERT(aWorkerPrivate);
+    MOZ_ASSERT(aNestedEventTarget);
+  }
+
+  void
+  Disable()
+  {
+    nsCOMPtr<nsIEventTarget> nestedEventTarget;
+    {
+      MutexAutoLock lock(mMutex);
+
+      MOZ_ASSERT(mWorkerPrivate);
+      mWorkerPrivate = nullptr;
+      mNestedEventTarget.swap(nestedEventTarget);
+    }
+  }
+
+  nsIEventTarget*
+  GetWeakNestedEventTarget() const
+  {
+    MOZ_ASSERT(mWeakNestedEventTarget);
+    return mWeakNestedEventTarget;
+  }
+
+  NS_DECL_THREADSAFE_ISUPPORTS
+  NS_DECL_NSIEVENTTARGET
+
+private:
+  ~EventTarget()
+  { }
+};
+
 struct WorkerPrivate::TimeoutInfo
 {
   TimeoutInfo()
   : mTimeoutCallable(JS::UndefinedValue()), mLineNumber(0), mId(0),
     mIsInterval(false), mCanceled(false)
   {
     MOZ_COUNT_CTOR(mozilla::dom::workers::WorkerPrivate::TimeoutInfo);
   }
@@ -2113,16 +2070,31 @@ private:
     addonId.Insert(NS_LITERAL_CSTRING("add-ons/"), 0);
     addonId += "/";
     mRtPath.Insert(addonId, explicitLength);
   }
 };
 
 NS_IMPL_ISUPPORTS1(WorkerPrivate::MemoryReporter, nsIMemoryReporter)
 
+WorkerPrivate::SyncLoopInfo::SyncLoopInfo(EventTarget* aEventTarget)
+: mEventTarget(aEventTarget), mCompleted(false), mResult(false)
+#ifdef DEBUG
+  , mHasRun(false)
+#endif
+{
+}
+
+// Can't use NS_IMPL_CYCLE_COLLECTION_CLASS(WorkerPrivateParent) because of the
+// templates.
+template <class Derived>
+typename WorkerPrivateParent<Derived>::cycleCollection
+  WorkerPrivateParent<Derived>::_cycleCollectorGlobal =
+    WorkerPrivateParent<Derived>::cycleCollection();
+
 template <class Derived>
 WorkerPrivateParent<Derived>::WorkerPrivateParent(
                                              JSContext* aCx,
                                              WorkerPrivate* aParent,
                                              const nsAString& aScriptURL,
                                              bool aIsChromeWorker,
                                              WorkerType aWorkerType,
                                              const nsAString& aSharedWorkerName,
@@ -2167,68 +2139,177 @@ template <class Derived>
 WorkerPrivateParent<Derived>::~WorkerPrivateParent()
 {
   MOZ_ASSERT(!mRooted);
 
   DropJSObjects(this);
 }
 
 template <class Derived>
-NS_IMPL_ADDREF_INHERITED(WorkerPrivateParent<Derived>, nsDOMEventTargetHelper)
-
-template <class Derived>
-NS_IMPL_RELEASE_INHERITED(WorkerPrivateParent<Derived>, nsDOMEventTargetHelper)
-
-template <class Derived>
-NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(WorkerPrivateParent<Derived>)
-  // No new interfaces, just cycle collection.
-NS_INTERFACE_MAP_END_INHERITING(nsDOMEventTargetHelper)
-
-template <class Derived>
-NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(WorkerPrivateParent<Derived>,
-                                                  nsDOMEventTargetHelper)
-  // Nothing else to traverse
-  // The various strong references in LoadInfo are managed manually and cannot
-  // be cycle collected.
-  tmp->AssertIsOnParentThread();
-NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
-
-template <class Derived>
-NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(WorkerPrivateParent<Derived>,
-                                                nsDOMEventTargetHelper)
-  tmp->AssertIsOnParentThread();
-NS_IMPL_CYCLE_COLLECTION_UNLINK_END
-
-template <class Derived>
-NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN_INHERITED(WorkerPrivateParent<Derived>,
-                                               nsDOMEventTargetHelper)
-  tmp->AssertIsOnParentThread();
-NS_IMPL_CYCLE_COLLECTION_TRACE_END
-
-template <class Derived>
 JSObject*
 WorkerPrivateParent<Derived>::WrapObject(JSContext* aCx,
                                          JS::Handle<JSObject*> aScope)
 {
   MOZ_ASSERT(!IsSharedWorker(),
              "We should never wrap a WorkerPrivate for a SharedWorker");
 
   AssertIsOnParentThread();
 
-  JS::Rooted<JSObject*> obj(aCx, WorkerBinding::Wrap(aCx, aScope,
-                                                     ParentAsWorkerPrivate()));
+  JSObject* obj = WorkerBinding::Wrap(aCx, aScope, ParentAsWorkerPrivate());
 
   if (mRooted) {
     PreserveWrapper(this);
   }
 
   return obj;
 }
 
 template <class Derived>
+nsresult
+WorkerPrivateParent<Derived>::DispatchPrivate(WorkerRunnable* aRunnable,
+                                              nsIEventTarget* aSyncLoopTarget)
+{
+  // May be called on any thread!
+
+  WorkerPrivate* self = ParentAsWorkerPrivate();
+
+  {
+    MutexAutoLock lock(mMutex);
+
+    MOZ_ASSERT_IF(aSyncLoopTarget, self->mThread);
+
+    if (!self->mThread) {
+      if (ParentStatus() == Pending || self->mStatus == Pending) {
+        mPreStartRunnables.AppendElement(aRunnable);
+        return NS_OK;
+      }
+
+      NS_WARNING("Using a worker event target after the thread has already"
+                 "been released!");
+      return NS_ERROR_UNEXPECTED;
+    }
+
+    if (self->mStatus == Dead ||
+        (!aSyncLoopTarget && ParentStatus() > Running)) {
+      NS_WARNING("A runnable was posted to a worker that is already shutting "
+                 "down!");
+      return NS_ERROR_UNEXPECTED;
+    }
+
+    nsCOMPtr<nsIEventTarget> target;
+    if (aSyncLoopTarget) {
+      target = aSyncLoopTarget;
+    }
+    else {
+      target = self->mThread;
+    }
+
+    nsresult rv = target->Dispatch(aRunnable, NS_DISPATCH_NORMAL);
+    if (NS_WARN_IF(NS_FAILED(rv))) {
+      return rv;
+    }
+
+    mCondVar.Notify();
+  }
+
+  return NS_OK;
+}
+
+template <class Derived>
+nsresult
+WorkerPrivateParent<Derived>::DispatchControlRunnable(
+                                  WorkerControlRunnable* aWorkerControlRunnable)
+{
+  // May be called on any thread!
+
+  MOZ_ASSERT(aWorkerControlRunnable);
+
+  nsRefPtr<WorkerControlRunnable> runnable = aWorkerControlRunnable;
+
+  WorkerPrivate* self = ParentAsWorkerPrivate();
+
+  {
+    MutexAutoLock lock(mMutex);
+
+    if (self->mStatus == Dead) {
+      NS_WARNING("A control runnable was posted to a worker that is already "
+                 "shutting down!");
+      return NS_ERROR_UNEXPECTED;
+    }
+
+    // Transfer ownership to the control queue.
+    self->mControlQueue.Push(runnable.forget().get());
+
+    if (JSContext* cx = self->mJSContext) {
+      MOZ_ASSERT(self->mThread);
+
+      JSRuntime* rt = JS_GetRuntime(cx);
+      MOZ_ASSERT(rt);
+
+      JS_TriggerOperationCallback(rt);
+    }
+
+    mCondVar.Notify();
+  }
+
+  return NS_OK;
+}
+
+template <class Derived>
+already_AddRefed<WorkerRunnable>
+WorkerPrivateParent<Derived>::MaybeWrapAsWorkerRunnable(nsIRunnable* aRunnable)
+{
+  // May be called on any thread!
+
+  MOZ_ASSERT(aRunnable);
+
+  nsRefPtr<WorkerRunnable> workerRunnable =
+    WorkerRunnable::FromRunnable(aRunnable);
+  if (workerRunnable) {
+    return workerRunnable.forget();
+  }
+
+  nsCOMPtr<nsICancelableRunnable> cancelable = do_QueryInterface(aRunnable);
+  if (!cancelable) {
+    MOZ_CRASH("All runnables destined for a worker thread must be cancelable!");
+  }
+
+  workerRunnable =
+    new ExternalRunnableWrapper(ParentAsWorkerPrivate(), cancelable);
+  return workerRunnable.forget();
+}
+
+template <class Derived>
+already_AddRefed<nsIEventTarget>
+WorkerPrivateParent<Derived>::GetEventTarget()
+{
+  WorkerPrivate* self = ParentAsWorkerPrivate();
+
+  nsCOMPtr<nsIEventTarget> target;
+
+  {
+    MutexAutoLock lock(mMutex);
+
+    if (!mEventTarget &&
+        ParentStatus() <= Running &&
+        self->mStatus <= Running) {
+      mEventTarget = new EventTarget(self);
+    }
+
+    target = mEventTarget;
+  }
+
+  NS_WARN_IF_FALSE(target,
+                   "Requested event target for a worker that is already "
+                   "shutting down!");
+
+  return target.forget();
+}
+
+template <class Derived>
 bool
 WorkerPrivateParent<Derived>::Start()
 {
   // May be called on any thread!
   {
     MutexAutoLock lock(mMutex);
 
     NS_ASSERTION(mParentStatus != Running, "How can this be?!");
@@ -2265,27 +2346,32 @@ WorkerPrivateParent<Derived>::NotifyPriv
     RuntimeService* runtime = RuntimeService::GetService();
     MOZ_ASSERT(runtime);
 
     runtime->ForgetSharedWorker(ParentAsWorkerPrivate());
   }
 
   if (pending) {
     WorkerPrivate* self = ParentAsWorkerPrivate();
+
 #ifdef DEBUG
     {
-      // Silence useless assertions in debug builds.
+      // Fake a thread here just so that our assertions don't go off for no
+      // reason.
       nsIThread* currentThread = NS_GetCurrentThread();
-      NS_ASSERTION(currentThread, "This should never be null!");
-
-      self->SetThread(currentThread);
+      MOZ_ASSERT(currentThread);
+
+      MOZ_ASSERT(!self->mPRThread);
+      self->mPRThread = PRThreadFromThread(currentThread);
+      MOZ_ASSERT(self->mPRThread);
     }
 #endif
+
     // Worker never got a chance to run, go ahead and delete it.
-    self->ScheduleDeletion(true);
+    self->ScheduleDeletion();
     return true;
   }
 
   // Only top-level workers should have a synchronize runnable.
   MOZ_ASSERT_IF(mSynchronizeRunnable.get(), !GetParent());
   mSynchronizeRunnable.Revoke();
 
   NS_ASSERTION(aStatus != Terminating || mQueuedRunnables.IsEmpty(),
@@ -2464,22 +2550,21 @@ WorkerPrivateParent<Derived>::Resume(JSC
   mSynchronizeRunnable.Revoke();
 
   // Execute queued runnables before waking up the worker, otherwise the worker
   // could post new messages before we run those that have been queued.
   if (!mQueuedRunnables.IsEmpty()) {
     AssertIsOnMainThread();
     MOZ_ASSERT(IsDedicatedWorker());
 
-    nsTArray<nsRefPtr<WorkerRunnable> > runnables;
+    nsTArray<nsCOMPtr<nsIRunnable>> runnables;
     mQueuedRunnables.SwapElements(runnables);
 
     for (uint32_t index = 0; index < runnables.Length(); index++) {
-      nsRefPtr<WorkerRunnable>& runnable = runnables[index];
-      runnable->Run();
+      runnables[index]->Run();
     }
   }
 
   nsRefPtr<ResumeRunnable> runnable =
     new ResumeRunnable(ParentAsWorkerPrivate());
   if (!runnable->Dispatch(aCx)) {
     return false;
   }
@@ -2693,18 +2778,19 @@ WorkerPrivateParent<Derived>::PostMessag
   JSAutoStructuredCloneBuffer buffer;
   if (!buffer.write(aCx, aMessage, transferable, callbacks, &clonedObjects)) {
     aRv.Throw(NS_ERROR_DOM_DATA_CLONE_ERR);
     return;
   }
 
   nsRefPtr<MessageEventRunnable> runnable =
     new MessageEventRunnable(ParentAsWorkerPrivate(),
-                             WorkerRunnable::WorkerThread, buffer,
-                             clonedObjects, aToMessagePort, aMessagePortSerial);
+                             WorkerRunnable::WorkerThreadModifyBusyCount,
+                             buffer, clonedObjects, aToMessagePort,
+                             aMessagePortSerial);
   if (!runnable->Dispatch(aCx)) {
     aRv.Throw(NS_ERROR_FAILURE);
   }
 }
 
 template <class Derived>
 void
 WorkerPrivateParent<Derived>::PostMessageToMessagePort(
@@ -3339,39 +3425,130 @@ WorkerPrivateParent<Derived>::SetPrincip
 template <class Derived>
 JSContext*
 WorkerPrivateParent<Derived>::ParentJSContext() const
 {
   AssertIsOnParentThread();
 
   if (mParent) {
     return mParent->GetJSContext();
-    }
+  }
 
   AssertIsOnMainThread();
 
   return mLoadInfo.mScriptContext ?
          mLoadInfo.mScriptContext->GetNativeContext() :
          nsContentUtils::GetSafeJSContext();
 }
 
+template <class Derived>
+void
+WorkerPrivateParent<Derived>::RegisterHostObjectURI(const nsACString& aURI)
+{
+  AssertIsOnMainThread();
+  mHostObjectURIs.AppendElement(aURI);
+}
+
+template <class Derived>
+void
+WorkerPrivateParent<Derived>::UnregisterHostObjectURI(const nsACString& aURI)
+{
+  AssertIsOnMainThread();
+  mHostObjectURIs.RemoveElement(aURI);
+}
+
+template <class Derived>
+void
+WorkerPrivateParent<Derived>::StealHostObjectURIs(nsTArray<nsCString>& aArray)
+{
+  aArray.SwapElements(mHostObjectURIs);
+}
+
+template <class Derived>
+NS_IMPL_ADDREF_INHERITED(WorkerPrivateParent<Derived>, nsDOMEventTargetHelper)
+
+template <class Derived>
+NS_IMPL_RELEASE_INHERITED(WorkerPrivateParent<Derived>, nsDOMEventTargetHelper)
+
+template <class Derived>
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(WorkerPrivateParent<Derived>)
+NS_INTERFACE_MAP_END_INHERITING(nsDOMEventTargetHelper)
+
+template <class Derived>
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(WorkerPrivateParent<Derived>,
+                                                  nsDOMEventTargetHelper)
+  // Nothing else to traverse
+  // The various strong references in LoadInfo are managed manually and cannot
+  // be cycle collected.
+  tmp->AssertIsOnParentThread();
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
+
+template <class Derived>
+NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(WorkerPrivateParent<Derived>,
+                                                nsDOMEventTargetHelper)
+  tmp->AssertIsOnParentThread();
+NS_IMPL_CYCLE_COLLECTION_UNLINK_END
+
+template <class Derived>
+NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN_INHERITED(WorkerPrivateParent<Derived>,
+                                               nsDOMEventTargetHelper)
+  tmp->AssertIsOnParentThread();
+NS_IMPL_CYCLE_COLLECTION_TRACE_END
+
+#ifdef DEBUG
+
+template <class Derived>
+void
+WorkerPrivateParent<Derived>::AssertIsOnParentThread() const
+{
+  if (GetParent()) {
+    GetParent()->AssertIsOnWorkerThread();
+  }
+  else {
+    AssertIsOnMainThread();
+  }
+}
+
+template <class Derived>
+void
+WorkerPrivateParent<Derived>::AssertInnerWindowIsCorrect() const
+{
+  AssertIsOnParentThread();
+
+  // Only care about top level workers from windows.
+  if (mParent || !mLoadInfo.mWindow) {
+    return;
+  }
+
+  AssertIsOnMainThread();
+
+  nsPIDOMWindow* outer = mLoadInfo.mWindow->GetOuterWindow();
+  NS_ASSERTION(outer && outer->GetCurrentInnerWindow() == mLoadInfo.mWindow,
+               "Inner window no longer correct!");
+}
+
+#endif
 WorkerPrivate::WorkerPrivate(JSContext* aCx,
                              WorkerPrivate* aParent,
                              const nsAString& aScriptURL,
                              bool aIsChromeWorker, WorkerType aWorkerType,
                              const nsAString& aSharedWorkerName,
                              LoadInfo& aLoadInfo)
 : WorkerPrivateParent<WorkerPrivate>(aCx, aParent, aScriptURL,
                                      aIsChromeWorker, aWorkerType,
                                      aSharedWorkerName, aLoadInfo),
   mJSContext(nullptr), mErrorHandlerRecursionCount(0), mNextTimeoutId(1),
   mStatus(Pending), mSuspended(false), mTimerRunning(false),
   mRunningExpiredTimeouts(false), mCloseHandlerStarted(false),
   mCloseHandlerFinished(false), mMemoryReporterRunning(false),
-  mBlockedForMemoryReporter(false)
+  mBlockedForMemoryReporter(false), mCancelAllPendingRunnables(false),
+  mPeriodicGCTimerRunning(false)
+#ifdef DEBUG
+  , mPRThread(nullptr)
+#endif
 {
   MOZ_ASSERT_IF(IsSharedWorker(), !aSharedWorkerName.IsVoid());
   MOZ_ASSERT_IF(!IsSharedWorker(), aSharedWorkerName.IsEmpty());
 
   if (aParent) {
     aParent->AssertIsOnWorkerThread();
     aParent->GetAllPreferences(mPreferences);
   }
@@ -3451,17 +3628,17 @@ WorkerPrivate::Constructor(const GlobalO
 
   JSContext* cx = aGlobal.GetContext();
 
   MOZ_ASSERT_IF(aWorkerType == WorkerTypeShared,
                 !aSharedWorkerName.IsVoid());
   MOZ_ASSERT_IF(aWorkerType != WorkerTypeShared,
                 aSharedWorkerName.IsEmpty());
 
-  mozilla::Maybe<LoadInfo> stackLoadInfo;
+  Maybe<LoadInfo> stackLoadInfo;
   if (!aLoadInfo) {
     stackLoadInfo.construct();
 
     nsresult rv = GetLoadInfo(cx, nullptr, parent, aScriptURL,
                               aIsChromeWorker, stackLoadInfo.addr());
     if (NS_FAILED(rv)) {
       scriptloader::ReportLoadError(cx, aScriptURL, rv, !parent);
       aRv.Throw(rv);
@@ -3489,23 +3666,23 @@ WorkerPrivate::Constructor(const GlobalO
   }
 
   MOZ_ASSERT(runtimeService);
 
   nsRefPtr<WorkerPrivate> worker =
     new WorkerPrivate(cx, parent, aScriptURL, aIsChromeWorker,
                       aWorkerType, aSharedWorkerName, *aLoadInfo);
 
-  nsRefPtr<CompileScriptRunnable> compiler = new CompileScriptRunnable(worker);
-  if (!compiler->Dispatch(cx)) {
+  if (!runtimeService->RegisterWorker(cx, worker)) {
     aRv.Throw(NS_ERROR_UNEXPECTED);
     return nullptr;
   }
 
-  if (!runtimeService->RegisterWorker(cx, worker)) {
+  nsRefPtr<CompileScriptRunnable> compiler = new CompileScriptRunnable(worker);
+  if (!compiler->Dispatch(cx)) {
     aRv.Throw(NS_ERROR_UNEXPECTED);
     return nullptr;
   }
 
   // The worker will be owned by its JSObject (via the reference we return from
   // this function), but it also needs to be owned by its thread, so AddRef it
   // again.
   NS_ADDREF(worker.get());
@@ -3549,17 +3726,17 @@ WorkerPrivate::GetLoadInfo(JSContext* aC
                                           getter_AddRefs(loadInfo.mChannel));
     NS_ENSURE_SUCCESS(rv, rv);
 
     // Now that we've spun the loop there's no guarantee that our parent is
     // still alive.  We may have received control messages initiating shutdown.
     {
       MutexAutoLock lock(aParent->mMutex);
       parentStatus = aParent->mStatus;
-  }
+    }
 
     if (parentStatus > Running) {
       nsCOMPtr<nsIThread> mainThread;
       if (NS_FAILED(NS_GetMainThread(getter_AddRefs(mainThread))) ||
           NS_FAILED(NS_ProxyRelease(mainThread, loadInfo.mChannel))) {
         NS_WARNING("Failed to proxy release of channel, leaking instead!");
       }
       return NS_ERROR_FAILURE;
@@ -3586,19 +3763,19 @@ WorkerPrivate::GetLoadInfo(JSContext* aC
     if (isChrome) {
       rv = ssm->GetSystemPrincipal(getter_AddRefs(loadInfo.mPrincipal));
       NS_ENSURE_SUCCESS(rv, rv);
     }
 
     // See if we're being called from a window.
     nsCOMPtr<nsPIDOMWindow> globalWindow = aWindow;
     if (!globalWindow) {
-    nsCOMPtr<nsIScriptGlobalObject> scriptGlobal =
-      nsJSUtils::GetStaticScriptGlobal(JS::CurrentGlobalOrNull(aCx));
-    if (scriptGlobal) {
+      nsCOMPtr<nsIScriptGlobalObject> scriptGlobal =
+        nsJSUtils::GetStaticScriptGlobal(JS::CurrentGlobalOrNull(aCx));
+      if (scriptGlobal) {
         globalWindow = do_QueryInterface(scriptGlobal);
         MOZ_ASSERT(globalWindow);
       }
     }
 
     nsCOMPtr<nsIDocument> document;
 
     if (globalWindow) {
@@ -3649,23 +3826,23 @@ WorkerPrivate::GetLoadInfo(JSContext* aC
             // There was an unsandboxed ancestor, yay!
             nsCOMPtr<nsIPrincipal> tmpPrincipal = tmpDoc->NodePrincipal();
             rv = tmpPrincipal->GetBaseDomain(loadInfo.mDomain);
             NS_ENSURE_SUCCESS(rv, rv);
           } else {
             // No unsandboxed ancestor, use our GUID.
             rv = loadInfo.mPrincipal->GetBaseDomain(loadInfo.mDomain);
             NS_ENSURE_SUCCESS(rv, rv);
-            }
+          }
         } else {
           // Document creating the worker is not sandboxed.
           rv = loadInfo.mPrincipal->GetBaseDomain(loadInfo.mDomain);
           NS_ENSURE_SUCCESS(rv, rv);
-          }
         }
+      }
 
       nsCOMPtr<nsIPermissionManager> permMgr =
         do_GetService(NS_PERMISSIONMANAGER_CONTRACTID, &rv);
       NS_ENSURE_SUCCESS(rv, rv);
 
       uint32_t perm;
       rv = permMgr->TestPermissionFromPrincipal(loadInfo.mPrincipal, "systemXHR",
                                                 &perm);
@@ -3739,228 +3916,296 @@ WorkerPrivate::GetLoadInfo(JSContext* aC
     rv = ChannelFromScriptURLMainThread(loadInfo.mPrincipal, loadInfo.mBaseURI,
                                         document, aScriptURL,
                                         getter_AddRefs(loadInfo.mChannel));
     NS_ENSURE_SUCCESS(rv, rv);
 
     rv = NS_GetFinalChannelURI(loadInfo.mChannel,
                                getter_AddRefs(loadInfo.mResolvedScriptURI));
     NS_ENSURE_SUCCESS(rv, rv);
-    }
+  }
 
   aLoadInfo->StealFrom(loadInfo);
   return NS_OK;
 }
 
 void
 WorkerPrivate::DoRunLoop(JSContext* aCx)
 {
   AssertIsOnWorkerThread();
+  MOZ_ASSERT(mThread);
 
   {
     MutexAutoLock lock(mMutex);
     mJSContext = aCx;
 
-    NS_ASSERTION(mStatus == Pending, "Huh?!");
+    MOZ_ASSERT(mStatus == Pending);
     mStatus = Running;
   }
 
-  // We need a timer for GC. The basic plan is to run a normal (non-shrinking)
-  // GC periodically (NORMAL_GC_TIMER_DELAY_MS) while the worker is running.
-  // Once the worker goes idle we set a short (IDLE_GC_TIMER_DELAY_MS) timer to
-  // run a shrinking GC. If the worker receives more messages then the short
-  // timer is canceled and the periodic timer resumes.
-  nsCOMPtr<nsITimer> gcTimer = do_CreateInstance(NS_TIMER_CONTRACTID);
-  if (!gcTimer) {
-    JS_ReportError(aCx, "Failed to create GC timer!");
-    return;
-  }
-
-  bool normalGCTimerRunning = false;
-
-  // We need to swap event targets below to get different types of GC behavior.
-  nsCOMPtr<nsIEventTarget> normalGCEventTarget;
-  nsCOMPtr<nsIEventTarget> idleGCEventTarget;
-
-  // We also need to track the idle GC event so that we don't confuse it with a
-  // generic event that should re-trigger the idle GC timer.
-  nsCOMPtr<nsIRunnable> idleGCEvent;
-  {
-    nsRefPtr<GarbageCollectRunnable> runnable =
-      new GarbageCollectRunnable(this, false, false);
-    normalGCEventTarget = new WorkerRunnableEventTarget(runnable);
-
-    runnable = new GarbageCollectRunnable(this, true, false);
-    idleGCEventTarget = new WorkerRunnableEventTarget(runnable);
-
-    idleGCEvent = runnable;
-  }
-
   EnableMemoryReporter();
 
-  Maybe<JSAutoCompartment> maybeAC;
+  InitializeGCTimers();
+
+  Maybe<JSAutoCompartment> workerCompartment;
+
   for (;;) {
+    // Workers lazily create a global object in CompileScriptRunnable. We need
+    // to enter the global's compartment as soon as it has been created.
+    if (workerCompartment.empty()) {
+      if (JSObject* global = js::DefaultObjectForContextOrNull(aCx)) {
+        workerCompartment.construct(aCx, global);
+      }
+    }
+
     Status currentStatus;
-    bool scheduleIdleGC;
-
-    WorkerRunnable* event;
+    bool normalRunnablesPending = false;
+
     {
       MutexAutoLock lock(mMutex);
 
-      while (!mControlQueue.Pop(event) && !mQueue.Pop(event)) {
+      while (mControlQueue.IsEmpty() &&
+             !(normalRunnablesPending = NS_HasPendingEvents(mThread))) {
         WaitForWorkerEvents();
       }
 
-      bool eventIsNotIdleGCEvent;
-      currentStatus = mStatus;
-
-      {
-        MutexAutoUnlock unlock(mMutex);
-
-        // Workers lazily create a global object in CompileScriptRunnable. In
-        // the old world, creating the global would implicitly set it as the
-        // default compartment object on mJSContext, meaning that subsequent
-        // runnables would be able to operate in that compartment without
-        // explicitly entering it. That no longer works, so we mimic the
-        // "operate in the compartment of the worker global once it exists"
-        // behavior here. This could probably be improved with some refactoring.
-        if (maybeAC.empty() && js::DefaultObjectForContextOrNull(aCx)) {
-          maybeAC.construct(aCx, js::DefaultObjectForContextOrNull(aCx));
-        }
-
-        if (!normalGCTimerRunning &&
-            event != idleGCEvent &&
-            currentStatus <= Terminating) {
-          // Must always cancel before changing the timer's target.
-          if (NS_FAILED(gcTimer->Cancel())) {
-            NS_WARNING("Failed to cancel GC timer!");
-          }
-
-          if (NS_SUCCEEDED(gcTimer->SetTarget(normalGCEventTarget)) &&
-              NS_SUCCEEDED(gcTimer->InitWithFuncCallback(
-                                             DummyCallback, nullptr,
-                                             NORMAL_GC_TIMER_DELAY_MS,
-                                             nsITimer::TYPE_REPEATING_SLACK))) {
-            normalGCTimerRunning = true;
-          }
-          else {
-            JS_ReportError(aCx, "Failed to start normal GC timer!");
-          }
-        }
-
-        // Keep track of whether or not this is the idle GC event.
-        eventIsNotIdleGCEvent = event != idleGCEvent;
-
-        static_cast<nsIRunnable*>(event)->Run();
-        NS_RELEASE(event);
-      }
+      ProcessAllControlRunnablesLocked();
 
       currentStatus = mStatus;
-      scheduleIdleGC = mControlQueue.IsEmpty() &&
-                       mQueue.IsEmpty() &&
-                       eventIsNotIdleGCEvent &&
-                       JS::CurrentGlobalOrNull(aCx);
-    }
-
-    // Take care of the GC timer. If we're starting the close sequence then we
-    // kill the timer once and for all. Otherwise we schedule the idle timeout
-    // if there are no more events.
-    if (currentStatus > Terminating || scheduleIdleGC) {
-      if (NS_SUCCEEDED(gcTimer->Cancel())) {
-        normalGCTimerRunning = false;
-      }
-      else {
-        NS_WARNING("Failed to cancel GC timer!");
-      }
-    }
-
-    if (scheduleIdleGC) {
-      NS_ASSERTION(JS::CurrentGlobalOrNull(aCx), "Should have global here!");
-
-      // Now *might* be a good time to GC. Let the JS engine make the decision.
-      JSAutoCompartment ac(aCx, JS::CurrentGlobalOrNull(aCx));
-      JS_MaybeGC(aCx);
-
-      if (NS_SUCCEEDED(gcTimer->SetTarget(idleGCEventTarget)) &&
-          NS_SUCCEEDED(gcTimer->InitWithFuncCallback(
-                                                    DummyCallback, nullptr,
-                                                    IDLE_GC_TIMER_DELAY_MS,
-                                                    nsITimer::TYPE_ONE_SHOT))) {
-      }
-      else {
-        JS_ReportError(aCx, "Failed to start idle GC timer!");
-      }
-    }
-
+    }
+
+    // If the close handler has finished and all features are done then we can
+    // kill this thread.
     if (currentStatus != Running && !HasActiveFeatures()) {
-      // If the close handler has finished and all features are done then we can
-      // kill this thread.
       if (mCloseHandlerFinished && currentStatus != Killing) {
         if (!NotifyInternal(aCx, Killing)) {
           JS_ReportPendingException(aCx);
         }
 #ifdef DEBUG
         {
           MutexAutoLock lock(mMutex);
           currentStatus = mStatus;
         }
-        NS_ASSERTION(currentStatus == Killing, "Should have changed status!");
+        MOZ_ASSERT(currentStatus == Killing);
 #else
         currentStatus = Killing;
 #endif
       }
 
       // If we're supposed to die then we should exit the loop.
       if (currentStatus == Killing) {
-        // Always make sure the timer is canceled.
-        if (NS_FAILED(gcTimer->Cancel())) {
-          NS_WARNING("Failed to cancel the GC timer!");
-        }
+        ShutdownGCTimers();
 
         DisableMemoryReporter();
 
-        StopAcceptingEvents();
+        {
+          MutexAutoLock lock(mMutex);
+
+          mStatus = Dead;
+          mJSContext = nullptr;
+        }
+
+        // After mStatus is set to Dead there can be no more
+        // WorkerControlRunnables so no need to lock here.
+        if (!mControlQueue.IsEmpty()) {
+          WorkerControlRunnable* runnable;
+          while (mControlQueue.Pop(runnable)) {
+            runnable->Cancel();
+            runnable->Release();
+          }
+        }
 
         // Clear away our MessagePorts.
         mWorkerPorts.Clear();
 
         // Unroot the global
         mScope = nullptr;
 
         return;
       }
     }
-  }
-
-  NS_NOTREACHED("Shouldn't get here!");
+
+    // Nothing to do here if we don't have any runnables in the main queue.
+    if (!normalRunnablesPending) {
+      SetGCTimerMode(IdleTimer);
+      continue;
+    }
+
+    MOZ_ASSERT(NS_HasPendingEvents(mThread));
+
+    // Start the periodic GC timer if it is not already running.
+    SetGCTimerMode(PeriodicTimer);
+
+    // Process a single runnable from the main queue.
+    MOZ_ALWAYS_TRUE(NS_ProcessNextEvent(mThread, false));
+
+    if (NS_HasPendingEvents(mThread)) {
+      // Now *might* be a good time to GC. Let the JS engine make the decision.
+      if (!workerCompartment.empty()) {
+        JS_MaybeGC(aCx);
+      }
+    }
+    else {
+      // The normal event queue has been exhausted, cancel the periodic GC timer
+      // and schedule the idle GC timer.
+      SetGCTimerMode(IdleTimer);
+    }
+  }
+
+  MOZ_ASSUME_UNREACHABLE("Shouldn't get here!");
+}
+
+void
+WorkerPrivate::OnProcessNextEvent(uint32_t aRecursionDepth)
+{
+  AssertIsOnWorkerThread();
+  MOZ_ASSERT(aRecursionDepth);
+
+  // Normally we process control runnables in DoRunLoop or RunCurrentSyncLoop.
+  // However, it's possible that non-worker C++ could spin its own nested event
+  // loop, and in that case we must ensure that we continue to process control
+  // runnables here.
+  if (aRecursionDepth > 1 &&
+      mSyncLoopStack.Length() < aRecursionDepth - 1) {
+    ProcessAllControlRunnables();
+  }
+}
+
+void
+WorkerPrivate::AfterProcessNextEvent(uint32_t aRecursionDepth)
+{
+  AssertIsOnWorkerThread();
+  MOZ_ASSERT(aRecursionDepth);
+}
+
+void
+WorkerPrivate::InitializeGCTimers()
+{
+  AssertIsOnWorkerThread();
+
+  // We need a timer for GC. The basic plan is to run a non-shrinking GC
+  // periodically (PERIODIC_GC_TIMER_DELAY_SEC) while the worker is running.
+  // Once the worker goes idle we set a short (IDLE_GC_TIMER_DELAY_SEC) timer to
+  // run a shrinking GC. If the worker receives more messages then the short
+  // timer is canceled and the periodic timer resumes.
+  mGCTimer = do_CreateInstance(NS_TIMER_CONTRACTID);
+  MOZ_ASSERT(mGCTimer);
+
+  nsRefPtr<GarbageCollectRunnable> runnable =
+    new GarbageCollectRunnable(this, false, false);
+  mPeriodicGCTimerTarget = new TimerThreadEventTarget(this, runnable);
+
+  runnable = new GarbageCollectRunnable(this, true, false);
+  mIdleGCTimerTarget = new TimerThreadEventTarget(this, runnable);
+
+  mPeriodicGCTimerRunning = false;
+}
+
+void
+WorkerPrivate::SetGCTimerMode(GCTimerMode aMode)
+{
+  AssertIsOnWorkerThread();
+  MOZ_ASSERT(mGCTimer);
+  MOZ_ASSERT(mPeriodicGCTimerTarget);
+  MOZ_ASSERT(mIdleGCTimerTarget);
+
+  if (aMode == PeriodicTimer && mPeriodicGCTimerRunning) {
+    return;
+  }
+
+  MOZ_ALWAYS_TRUE(NS_SUCCEEDED(mGCTimer->Cancel()));
+
+  mPeriodicGCTimerRunning = false;
+
+  LOG(("Worker %p canceled GC timer because %s\n", this,
+       aMode == PeriodicTimer ?
+       "periodic" :
+       aMode == IdleTimer ? "idle" : "none"));
+
+  if (aMode == NoTimer) {
+    return;
+  }
+
+  MOZ_ASSERT(aMode == PeriodicTimer || aMode == IdleTimer);
+
+  nsIEventTarget* target;
+  uint32_t delay;
+  int16_t type;
+
+  if (aMode == PeriodicTimer) {
+    target = mPeriodicGCTimerTarget;
+    delay = PERIODIC_GC_TIMER_DELAY_SEC * 1000;
+    type = nsITimer::TYPE_REPEATING_SLACK;
+  }
+  else {
+    target = mIdleGCTimerTarget;
+    delay = IDLE_GC_TIMER_DELAY_SEC * 1000;
+    type = nsITimer::TYPE_ONE_SHOT;
+  }
+
+  MOZ_ALWAYS_TRUE(NS_SUCCEEDED(mGCTimer->SetTarget(target)));
+  MOZ_ALWAYS_TRUE(NS_SUCCEEDED(mGCTimer->InitWithFuncCallback(DummyCallback,
+                                                              nullptr, delay,
+                                                              type)));
+
+  if (aMode == PeriodicTimer) {
+    LOG(("Worker %p scheduled periodic GC timer\n", this));
+    mPeriodicGCTimerRunning = true;
+  }
+  else {
+    LOG(("Worker %p scheduled idle GC timer\n", this));
+  }
+}
+
+void
+WorkerPrivate::ShutdownGCTimers()
+{
+  AssertIsOnWorkerThread();
+
+  MOZ_ASSERT(mGCTimer);
+
+  // Always make sure the timer is canceled.
+  MOZ_ALWAYS_TRUE(NS_SUCCEEDED(mGCTimer->Cancel()));
+
+  LOG(("Worker %p killed the GC timer\n", this));
+
+  mGCTimer = nullptr;
+  mPeriodicGCTimerTarget = nullptr;
+  mIdleGCTimerTarget = nullptr;
+  mPeriodicGCTimerRunning = false;
 }
 
 bool
 WorkerPrivate::OperationCallback(JSContext* aCx)
 {
   AssertIsOnWorkerThread();
 
   bool mayContinue = true;
+  bool scheduledIdleGC = false;
 
   for (;;) {
     // Run all control events now.
     mayContinue = ProcessAllControlRunnables();
 
     bool maySuspend = mSuspended;
     if (maySuspend) {
       MutexAutoLock lock(mMutex);
       maySuspend = mStatus <= Running;
     }
 
     if (!mayContinue || !maySuspend) {
       break;
     }
 
-    // Clean up before suspending.
-    JS_GC(JS_GetRuntime(aCx));
+    // Cancel the periodic GC timer here before suspending. The idle GC timer
+    // will clean everything up once it runs.
+    if (!scheduledIdleGC) {
+      SetGCTimerMode(IdleTimer);
+      scheduledIdleGC = true;
+    }
 
     while ((mayContinue = MayContinueRunning())) {
       MutexAutoLock lock(mMutex);
       if (!mControlQueue.IsEmpty()) {
         break;
       }
 
       WaitForWorkerEvents(PR_MillisecondsToInterval(RemainingRunTimeMS()));
@@ -3969,49 +4214,68 @@ WorkerPrivate::OperationCallback(JSConte
 
   if (!mayContinue) {
     // We want only uncatchable exceptions here.
     NS_ASSERTION(!JS_IsExceptionPending(aCx),
                  "Should not have an exception set here!");
     return false;
   }
 
+  // Make sure the periodic timer gets turned back on here.
+  SetGCTimerMode(PeriodicTimer);
+
   return true;
 }
 
+nsresult
+WorkerPrivate::IsOnCurrentThread(bool* aIsOnCurrentThread)
+{
+  // May be called on any thread!
+
+  MOZ_ASSERT(aIsOnCurrentThread);
+
+  nsCOMPtr<nsIThread> thread;
+  {
+    MutexAutoLock lock(mMutex);
+    thread = mThread;
+  }
+
+  if (!thread) {
+    NS_WARNING("Trying to test thread correctness after the worker has "
+               "released its thread!");
+    return NS_ERROR_FAILURE;
+  }
+
+  nsresult rv = thread->IsOnCurrentThread(aIsOnCurrentThread);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
+  return NS_OK;
+}
+
 void
-WorkerPrivate::ScheduleDeletion(bool aWasPending)
+WorkerPrivate::ScheduleDeletion()
 {
   AssertIsOnWorkerThread();
-  NS_ASSERTION(mChildWorkers.IsEmpty(), "Live child workers!");
-  NS_ASSERTION(mSyncQueues.IsEmpty(), "Should have no sync queues here!");
-
-  StopAcceptingEvents();
-
-  nsIThread* currentThread;
-  if (aWasPending) {
-    // Don't want to close down this thread since we never got to run!
-    currentThread = nullptr;
-  }
-  else {
-    currentThread = NS_GetCurrentThread();
-    NS_ASSERTION(currentThread, "This should never be null!");
-  }
-
-  WorkerPrivate* parent = GetParent();
-  if (parent) {
+  MOZ_ASSERT(mChildWorkers.IsEmpty());
+  MOZ_ASSERT(mSyncLoopStack.IsEmpty());
+
+  ClearMainEventQueue();
+
+  if (WorkerPrivate* parent = GetParent()) {
     nsRefPtr<WorkerFinishedRunnable> runnable =
-      new WorkerFinishedRunnable(parent, this, currentThread);
+      new WorkerFinishedRunnable(parent, this);
     if (!runnable->Dispatch(nullptr)) {
       NS_WARNING("Failed to dispatch runnable!");
     }
   }
   else {
     nsRefPtr<TopLevelWorkerFinishedRunnable> runnable =
-      new TopLevelWorkerFinishedRunnable(this, currentThread);
+      new TopLevelWorkerFinishedRunnable(this);
     if (NS_FAILED(NS_DispatchToMainThread(runnable, NS_DISPATCH_NORMAL))) {
       NS_WARNING("Failed to dispatch runnable!");
     }
   }
 }
 
 bool
 WorkerPrivate::BlockAndCollectRuntimeStats(JS::RuntimeStats* aRtStats)
@@ -4061,28 +4325,27 @@ WorkerPrivate::BlockAndCollectRuntimeSta
 
   return succeeded;
 }
 
 void
 WorkerPrivate::EnableMemoryReporter()
 {
   AssertIsOnWorkerThread();
+  MOZ_ASSERT(!mMemoryReporter);
 
   // No need to lock here since the main thread can't race until we've
   // successfully registered the reporter.
   mMemoryReporter = new MemoryReporter(this);
 
   if (NS_FAILED(RegisterWeakMemoryReporter(mMemoryReporter))) {
     NS_WARNING("Failed to register memory reporter!");
     // No need to lock here since a failed registration means our memory
     // reporter can't start running. Just clean up.
     mMemoryReporter = nullptr;
-
-    return;
   }
 }
 
 void
 WorkerPrivate::DisableMemoryReporter()
 {
   AssertIsOnWorkerThread();
 
@@ -4158,146 +4421,81 @@ WorkerPrivate::WaitForWorkerEvents(PRInt
 
   NS_ASSERTION(mBlockedForMemoryReporter, "Somehow we got unblocked!");
 
   // No need to notify here as the main thread isn't watching for this state.
   mBlockedForMemoryReporter = false;
 }
 
 bool
-WorkerPrivate::ProcessAllControlRunnables()
+WorkerPrivate::ProcessAllControlRunnablesLocked()
 {
   AssertIsOnWorkerThread();
+  mMutex.AssertCurrentThreadOwns();
 
   bool result = true;
 
   for (;;) {
-    WorkerRunnable* event;
-    {
-      MutexAutoLock lock(mMutex);
-
-      // Block here if the memory reporter is trying to run.
-      if (mMemoryReporterRunning) {
-        NS_ASSERTION(!mBlockedForMemoryReporter,
-                     "Can't be blocked in more than one place at the same "
-                     "time!");
-
-        // Let the main thread know that we've received the block request and
-        // that memory reporting may proceed.
-        mBlockedForMemoryReporter = true;
-
-        // The main thread is almost certainly waiting so we must notify here.
-        mMemoryReportCondVar.Notify();
-
-        // Wait for the memory report to finish.
-        while (mMemoryReporterRunning) {
-          mMemoryReportCondVar.Wait();
-        }
-
-        NS_ASSERTION(mBlockedForMemoryReporter, "Somehow we got unblocked!");
-
-        // No need to notify here as the main thread isn't watching for this
-        // state.
-        mBlockedForMemoryReporter = false;
+    // Block here if the memory reporter is trying to run.
+    if (mMemoryReporterRunning) {
+      MOZ_ASSERT(!mBlockedForMemoryReporter);
+
+      // Let the main thread know that we've received the block request and
+      // that memory reporting may proceed.
+      mBlockedForMemoryReporter = true;
+
+      // The main thread is almost certainly waiting so we must notify here.
+      mMemoryReportCondVar.Notify();
+
+      // Wait for the memory report to finish.
+      while (mMemoryReporterRunning) {
+        mMemoryReportCondVar.Wait();
       }
 
-      if (!mControlQueue.Pop(event)) {
-        break;
-      }
-    }
-
+      MOZ_ASSERT(mBlockedForMemoryReporter);
+
+      // No need to notify here as the main thread isn't watching for this
+      // state.
+      mBlockedForMemoryReporter = false;
+    }
+
+    WorkerControlRunnable* event;
+    if (!mControlQueue.Pop(event)) {
+      break;
+    }
+
+    MutexAutoUnlock unlock(mMutex);
+
+    MOZ_ASSERT(event);
     if (NS_FAILED(static_cast<nsIRunnable*>(event)->Run())) {
       result = false;
     }
 
-    NS_RELEASE(event);
+    event->Release();
   }
 
   return result;
 }
 
-bool
-WorkerPrivate::Dispatch(WorkerRunnable* aEvent, EventQueue* aQueue)
-{
-  nsRefPtr<WorkerRunnable> event(aEvent);
-
-  {
-    MutexAutoLock lock(mMutex);
-
-    if (mStatus == Dead) {
-      // Nothing may be added after we've set Dead.
-      return false;
-    }
-
-    if (aQueue == &mQueue) {
-      // Check parent status.
-      Status parentStatus = ParentStatus();
-      if (parentStatus >= Terminating) {
-        // Throw.
-        return false;
-      }
-
-      // Check inner status too.
-      if (parentStatus >= Closing || mStatus >= Closing) {
-        // Silently eat this one.
-        return true;
-      }
-    }
-
-    if (!aQueue->Push(event)) {
-      return false;
-    }
-
-    if (aQueue == &mControlQueue && mJSContext) {
-      JS_TriggerOperationCallback(JS_GetRuntime(mJSContext));
-    }
-
-    mCondVar.Notify();
-  }
-
-  event.forget();
-  return true;
-}
-
-bool
-WorkerPrivate::DispatchToSyncQueue(WorkerSyncRunnable* aEvent)
-{
-  nsRefPtr<WorkerRunnable> event(aEvent);
-
-  {
-    MutexAutoLock lock(mMutex);
-
-    NS_ASSERTION(mSyncQueues.Length() > aEvent->mSyncQueueKey, "Bad event!");
-
-    if (!mSyncQueues[aEvent->mSyncQueueKey]->mQueue.Push(event)) {
-      return false;
-    }
-
-    mCondVar.Notify();
-  }
-
-  event.forget();
-  return true;
-}
-
 void
-WorkerPrivate::ClearQueue(EventQueue* aQueue)
+WorkerPrivate::ClearMainEventQueue()
 {
   AssertIsOnWorkerThread();
-  mMutex.AssertCurrentThreadOwns();
-
-  WorkerRunnable* event;
-  while (aQueue->Pop(event)) {
-    if (event->WantsToRunDuringClear()) {
-      MutexAutoUnlock unlock(mMutex);
-
-      static_cast<nsIRunnable*>(event)->Run();
-    }
-    event->Release();
-  }
+
+  nsIThread* currentThread = NS_GetCurrentThread();
+  MOZ_ASSERT(currentThread);
+
+  MOZ_ASSERT(!mCancelAllPendingRunnables);
+  mCancelAllPendingRunnables = true;
+
+  NS_ProcessPendingEvents(currentThread);
+  MOZ_ASSERT(!NS_HasPendingEvents(currentThread));
+
+  MOZ_ASSERT(mCancelAllPendingRunnables);
+  mCancelAllPendingRunnables = false;
 }
 
 uint32_t
 WorkerPrivate::RemainingRunTimeMS() const
 {
   if (mKillTime.IsNull()) {
     return UINT32_MAX;
   }
@@ -4372,18 +4570,18 @@ bool
 WorkerPrivate::AddChildWorker(JSContext* aCx, ParentType* aChildWorker)
 {
   AssertIsOnWorkerThread();
 
 #ifdef DEBUG
   {
     Status currentStatus;
     {
-    MutexAutoLock lock(mMutex);
-    currentStatus = mStatus;
+      MutexAutoLock lock(mMutex);
+      currentStatus = mStatus;
     }
 
     MOZ_ASSERT(currentStatus == Running);
   }
 #endif
 
   NS_ASSERTION(!mChildWorkers.Contains(aChildWorker),
                "Already know about this one!");
@@ -4499,102 +4697,200 @@ WorkerPrivate::CancelAllTimeouts(JSConte
   else if (!mRunningExpiredTimeouts) {
     NS_ASSERTION(mTimeouts.IsEmpty(), "Huh?!");
   }
 #endif
 
   mTimer = nullptr;
 }
 
-uint32_t
+already_AddRefed<nsIEventTarget>
 WorkerPrivate::CreateNewSyncLoop()
 {
   AssertIsOnWorkerThread();
 
-  NS_ASSERTION(mSyncQueues.Length() < UINT32_MAX,
-               "Should have bailed by now!");
-
-  mSyncQueues.AppendElement(new SyncQueue());
-  return mSyncQueues.Length() - 1;
+  nsCOMPtr<nsIThreadInternal> thread = do_QueryInterface(NS_GetCurrentThread());
+  MOZ_ASSERT(thread);
+
+  nsCOMPtr<nsIEventTarget> realEventTarget;
+  MOZ_ALWAYS_TRUE(NS_SUCCEEDED(thread->PushEventQueue(
+                                             getter_AddRefs(realEventTarget))));
+
+  nsRefPtr<EventTarget> workerEventTarget =
+    new EventTarget(this, realEventTarget);
+
+  {
+    // Modifications must be protected by mMutex in DEBUG builds, see comment
+    // about mSyncLoopStack in WorkerPrivate.h.
+#ifdef DEBUG
+    MutexAutoLock lock(mMutex);
+#endif
+
+    mSyncLoopStack.AppendElement(new SyncLoopInfo(workerEventTarget));
+  }
+
+  return workerEventTarget.forget();
 }
 
 bool
-WorkerPrivate::RunSyncLoop(JSContext* aCx, uint32_t aSyncLoopKey)
+WorkerPrivate::RunCurrentSyncLoop()
 {
   AssertIsOnWorkerThread();
 
-  NS_ASSERTION(!mSyncQueues.IsEmpty() ||
-               (aSyncLoopKey != mSyncQueues.Length() - 1),
-               "Forgot to call CreateNewSyncLoop!");
-  if (aSyncLoopKey != mSyncQueues.Length() - 1) {
-    return false;
-  }
-
-  SyncQueue* syncQueue = mSyncQueues[aSyncLoopKey].get();
-
-  for (;;) {
-    WorkerRunnable* event;
+  JSContext* cx = GetJSContext();
+  MOZ_ASSERT(cx);
+
+  // This should not change between now and the time we finish running this sync
+  // loop.
+  uint32_t currentLoopIndex = mSyncLoopStack.Length() - 1;
+
+  SyncLoopInfo* loopInfo = mSyncLoopStack[currentLoopIndex];
+
+  MOZ_ASSERT(loopInfo);
+  MOZ_ASSERT(!loopInfo->mHasRun);
+  MOZ_ASSERT(!loopInfo->mCompleted);
+
+#ifdef DEBUG
+  loopInfo->mHasRun = true;
+#endif
+
+  nsCOMPtr<nsIThreadInternal> thread = do_QueryInterface(mThread);
+  MOZ_ASSERT(thread);
+
+  while (!loopInfo->mCompleted) {
+    bool normalRunnablesPending = false;
+
+    // Don't block with the periodic GC timer running.
+    if (!NS_HasPendingEvents(thread)) {
+      SetGCTimerMode(IdleTimer);
+    }
+
+    // Wait for something to do.
     {
       MutexAutoLock lock(mMutex);
 
-      while (!mControlQueue.Pop(event) && !syncQueue->mQueue.Pop(event)) {
-        WaitForWorkerEvents();
+      for (;;) {
+        while (mControlQueue.IsEmpty() &&
+               !normalRunnablesPending &&
+               !(normalRunnablesPending = NS_HasPendingEvents(thread))) {
+          WaitForWorkerEvents();
+        }
+
+        ProcessAllControlRunnablesLocked();
+
+        if (normalRunnablesPending) {
+          break;
+        }
       }
     }
 
-    static_cast<nsIRunnable*>(event)->Run();
-    NS_RELEASE(event);
-
-    if (syncQueue->mComplete) {
-      NS_ASSERTION(mSyncQueues.Length() - 1 == aSyncLoopKey,
-                   "Mismatched calls!");
-      NS_ASSERTION(syncQueue->mQueue.IsEmpty(), "Unprocessed sync events!");
-
-      bool result = syncQueue->mResult;
-      DestroySyncLoop(aSyncLoopKey);
-
+    // Make sure the periodic timer is running before we continue.
+    SetGCTimerMode(PeriodicTimer);
+
+    MOZ_ALWAYS_TRUE(NS_ProcessNextEvent(thread, false));
+
+    // Now *might* be a good time to GC. Let the JS engine make the decision.
+    JS_MaybeGC(cx);
+  }
+
+  // Make sure that the stack didn't change underneath us.
+  MOZ_ASSERT(!mSyncLoopStack.IsEmpty());
+  MOZ_ASSERT(mSyncLoopStack.Length() - 1 == currentLoopIndex);
+  MOZ_ASSERT(mSyncLoopStack[currentLoopIndex] == loopInfo);
+
+  // We're about to delete |loop|, stash its event target and result.
+  nsIEventTarget* nestedEventTarget =
+    loopInfo->mEventTarget->GetWeakNestedEventTarget();
+  MOZ_ASSERT(nestedEventTarget);
+
+  bool result = loopInfo->mResult;
+
+  {
+    // Modifications must be protected by mMutex in DEBUG builds, see comment
+    // about mSyncLoopStack in WorkerPrivate.h.
 #ifdef DEBUG
-      syncQueue = nullptr;
+    MutexAutoLock lock(mMutex);
 #endif
 
-      return result;
-    }
-  }
-
-  NS_NOTREACHED("Shouldn't get here!");
-  return false;
+    // This will delete |loop|!
+    mSyncLoopStack.RemoveElementAt(currentLoopIndex);
+  }
+
+  MOZ_ALWAYS_TRUE(NS_SUCCEEDED(thread->PopEventQueue(nestedEventTarget)));
+
+  return result;
 }
 
 void
-WorkerPrivate::StopSyncLoop(uint32_t aSyncLoopKey, bool aSyncResult)
+WorkerPrivate::StopSyncLoop(nsIEventTarget* aSyncLoopTarget, bool aResult)
 {
   AssertIsOnWorkerThread();
-
-  NS_ASSERTION(mSyncQueues.IsEmpty() ||
-               (aSyncLoopKey == mSyncQueues.Length() - 1),
-               "Forgot to call CreateNewSyncLoop!");
-  if (aSyncLoopKey != mSyncQueues.Length() - 1) {
-    return;
-  }
-
-  SyncQueue* syncQueue = mSyncQueues[aSyncLoopKey].get();
-
-  NS_ASSERTION(!syncQueue->mComplete, "Already called StopSyncLoop?!");
-
-  syncQueue->mResult = aSyncResult;
-  syncQueue->mComplete = true;
+  AssertValidSyncLoop(aSyncLoopTarget);
+
+  MOZ_ASSERT(!mSyncLoopStack.IsEmpty());
+
+  for (uint32_t index = mSyncLoopStack.Length(); index > 0; index--) {
+    nsAutoPtr<SyncLoopInfo>& loopInfo = mSyncLoopStack[index - 1];
+    MOZ_ASSERT(loopInfo);
+    MOZ_ASSERT(loopInfo->mEventTarget);
+
+    if (loopInfo->mEventTarget == aSyncLoopTarget) {
+      // Can't assert |loop->mHasRun| here because dispatch failures can cause
+      // us to bail out early.
+      MOZ_ASSERT(!loopInfo->mCompleted);
+
+      loopInfo->mResult = aResult;
+      loopInfo->mCompleted = true;
+
+      loopInfo->mEventTarget->Disable();
+
+      return;
+    }
+
+    MOZ_ASSERT(!SameCOMIdentity(loopInfo->mEventTarget, aSyncLoopTarget));
+  }
+
+  MOZ_CRASH("Unknown sync loop!");
 }
 
+#ifdef DEBUG
 void
-WorkerPrivate::DestroySyncLoop(uint32_t aSyncLoopKey)
+WorkerPrivate::AssertValidSyncLoop(nsIEventTarget* aSyncLoopTarget)
 {
-  AssertIsOnWorkerThread();
-
-  mSyncQueues.RemoveElementAt(aSyncLoopKey);
+  MOZ_ASSERT(aSyncLoopTarget);
+
+  EventTarget* workerTarget;
+  nsresult rv =
+    aSyncLoopTarget->QueryInterface(kDEBUGWorkerEventTargetIID,
+                                    reinterpret_cast<void**>(&workerTarget));
+  MOZ_ASSERT(NS_SUCCEEDED(rv));
+  MOZ_ASSERT(workerTarget);
+
+  bool valid = false;
+
+  {
+    MutexAutoLock lock(mMutex);
+
+    for (uint32_t index = 0; index < mSyncLoopStack.Length(); index++) {
+      nsAutoPtr<SyncLoopInfo>& loopInfo = mSyncLoopStack[index];
+      MOZ_ASSERT(loopInfo);
+      MOZ_ASSERT(loopInfo->mEventTarget);
+
+      if (loopInfo->mEventTarget == aSyncLoopTarget) {
+        valid = true;
+        break;
+      }
+
+      MOZ_ASSERT(!SameCOMIdentity(loopInfo->mEventTarget, aSyncLoopTarget));
+    }
+  }
+
+  MOZ_ASSERT(valid);
 }
+#endif
 
 void
 WorkerPrivate::PostMessageToParentInternal(
                             JSContext* aCx,
                             JS::Handle<JS::Value> aMessage,
                             const Optional<Sequence<JS::Value>>& aTransferable,
                             bool aToMessagePort,
                             uint64_t aMessagePortSerial,
@@ -4624,18 +4920,20 @@ WorkerPrivate::PostMessageToParentIntern
 
   JSAutoStructuredCloneBuffer buffer;
   if (!buffer.write(aCx, aMessage, transferable, callbacks, &clonedObjects)) {
     aRv = NS_ERROR_DOM_DATA_CLONE_ERR;
     return;
   }
 
   nsRefPtr<MessageEventRunnable> runnable =
-    new MessageEventRunnable(this, WorkerRunnable::ParentThread, buffer,
-                             clonedObjects, aToMessagePort, aMessagePortSerial);
+    new MessageEventRunnable(this,
+                             WorkerRunnable::ParentThreadUnchangedBusyCount,
+                             buffer, clonedObjects, aToMessagePort,
+                             aMessagePortSerial);
   if (!runnable->Dispatch(aCx)) {
     aRv = NS_ERROR_FAILURE;
   }
 }
 
 void
 WorkerPrivate::PostMessageToParentMessagePort(
                              JSContext* aCx,
@@ -4658,52 +4956,65 @@ WorkerPrivate::PostMessageToParentMessag
 
 bool
 WorkerPrivate::NotifyInternal(JSContext* aCx, Status aStatus)
 {
   AssertIsOnWorkerThread();
 
   NS_ASSERTION(aStatus > Running && aStatus < Dead, "Bad status!");
 
+  nsRefPtr<EventTarget> eventTarget;
+
   // Save the old status and set the new status.
   Status previousStatus;
   {
     MutexAutoLock lock(mMutex);
 
     if (mStatus >= aStatus) {
+      MOZ_ASSERT(!mEventTarget);
       return true;
     }
 
     previousStatus = mStatus;
     mStatus = aStatus;
-  }
-
-  // Now that status > Running, no-one can create a new mCrossThreadDispatcher
-  // if we don't already have one.
+
+    mEventTarget.swap(eventTarget);
+  }
+
+  // Now that mStatus > Running, no-one can create a new WorkerEventTarget or
+  // WorkerCrossThreadDispatcher if we don't already have one.
+  if (eventTarget) {
+    // Since we'll no longer process events, make sure we no longer allow anyone
+    // to post them. We have to do this without mMutex held, since our mutex
+    // must be acquired *after* the WorkerEventTarget's mutex when they're both
+    // held.
+    eventTarget->Disable();
+    eventTarget = nullptr;
+  }
+
   if (mCrossThreadDispatcher) {
     // Since we'll no longer process events, make sure we no longer allow
-    // anyone to post them.
-    // We have to do this without mMutex held, since our mutex must be
-    // acquired *after* mCrossThreadDispatcher's mutex when they're both held.
+    // anyone to post them. We have to do this without mMutex held, since our
+    // mutex must be acquired *after* mCrossThreadDispatcher's mutex when
+    // they're both held.
     mCrossThreadDispatcher->Forget();
-  }
-
-  NS_ASSERTION(previousStatus != Pending, "How is this possible?!");
-
-  NS_ASSERTION(previousStatus >= Canceling || mKillTime.IsNull(),
-               "Bad kill time set!");
+    mCrossThreadDispatcher = nullptr;
+  }
+
+  MOZ_ASSERT(previousStatus != Pending);
+
+  MOZ_ASSERT(previousStatus >= Canceling || mKillTime.IsNull());
 
   // Let all our features know the new status.
   NotifyFeatures(aCx, aStatus);
 
   // If this is the first time our status has changed then we need to clear the
   // main event queue.
   if (previousStatus == Running) {
-    MutexAutoLock lock(mMutex);
-    ClearQueue(&mQueue);
+    ClearMainEventQueue();
   }
 
   // If we've run the close handler, we don't need to do anything else.
   if (mCloseHandlerFinished) {
     return true;
   }
 
   // If the worker script never ran, or failed to compile, we don't need to do
@@ -4712,29 +5023,20 @@ WorkerPrivate::NotifyInternal(JSContext*
     mCloseHandlerStarted = true;
     mCloseHandlerFinished = true;
     return true;
   }
 
   // If this is the first time our status has changed we also need to schedule
   // the close handler unless we're being shut down.
   if (previousStatus == Running && aStatus != Killing) {
-    NS_ASSERTION(!mCloseHandlerStarted && !mCloseHandlerFinished,
-                 "This is impossible!");
+    MOZ_ASSERT(!mCloseHandlerStarted && !mCloseHandlerFinished);
 
     nsRefPtr<CloseEventRunnable> closeRunnable = new CloseEventRunnable(this);
-
-    MutexAutoLock lock(mMutex);
-
-    if (!mQueue.Push(closeRunnable)) {
-      NS_WARNING("Failed to push closeRunnable!");
-      return false;
-    }
-
-    closeRunnable.forget();
+    MOZ_ALWAYS_TRUE(NS_SUCCEEDED(NS_DispatchToCurrentThread(closeRunnable)));
   }
 
   if (aStatus == Closing) {
     // Notify parent to stop sending us messages and balance our busy count.
     nsRefPtr<CloseRunnable> runnable = new CloseRunnable(this);
     if (!runnable->Dispatch(aCx)) {
       return false;
     }
@@ -4745,19 +5047,17 @@ WorkerPrivate::NotifyInternal(JSContext*
 
   if (aStatus == Terminating) {
     // Only abort the script if we're not yet running the close handler.
     return mCloseHandlerStarted;
   }
 
   if (aStatus == Canceling) {
     // We need to enforce a timeout on the close handler.
-    NS_ASSERTION(previousStatus == Running || previousStatus == Closing ||
-                 previousStatus == Terminating,
-                 "Bad previous status!");
+    MOZ_ASSERT(previousStatus >= Running && previousStatus <= Terminating);
 
     uint32_t killSeconds = IsChromeWorker() ?
       RuntimeService::GetChromeCloseHandlerTimeoutSeconds() :
       RuntimeService::GetContentCloseHandlerTimeoutSeconds();
 
     if (killSeconds) {
       mKillTime = TimeStamp::Now() + TimeDuration::FromSeconds(killSeconds);
 
@@ -4765,51 +5065,43 @@ WorkerPrivate::NotifyInternal(JSContext*
         return false;
       }
     }
 
     // Only abort the script if we're not yet running the close handler.
     return mCloseHandlerStarted;
   }
 
-  if (aStatus == Killing) {
-    mKillTime = TimeStamp::Now();
-
-    if (!mCloseHandlerFinished && !ScheduleKillCloseEventRunnable(aCx)) {
-      return false;
-    }
-
-    // Always abort the script.
-    return false;
-  }
-
-  NS_NOTREACHED("Should never get here!");
+  MOZ_ASSERT(aStatus == Killing);
+
+  mKillTime = TimeStamp::Now();
+
+  if (mCloseHandlerStarted && !mCloseHandlerFinished) {
+    ScheduleKillCloseEventRunnable(aCx);
+  }
+
+  // Always abort the script.
   return false;
 }
 
 bool
 WorkerPrivate::ScheduleKillCloseEventRunnable(JSContext* aCx)
 {
   AssertIsOnWorkerThread();
-  NS_ASSERTION(!mKillTime.IsNull(), "Must have a kill time!");
+  MOZ_ASSERT(!mKillTime.IsNull());
 
   nsRefPtr<KillCloseEventRunnable> killCloseEventRunnable =
     new KillCloseEventRunnable(this);
   if (!killCloseEventRunnable->SetTimeout(aCx, RemainingRunTimeMS())) {
     return false;
   }
 
-  MutexAutoLock lock(mMutex);
-
-  if (!mQueue.Push(killCloseEventRunnable)) {
-    NS_WARNING("Failed to push killCloseEventRunnable!");
-    return false;
-  }
-
-  killCloseEventRunnable.forget();
+  MOZ_ALWAYS_TRUE(NS_SUCCEEDED(NS_DispatchToCurrentThread(
+                                                      killCloseEventRunnable)));
+
   return true;
 }
 
 void
 WorkerPrivate::ReportError(JSContext* aCx, const char* aMessage,
                            JSErrorReport* aReport)
 {
   AssertIsOnWorkerThread();
@@ -4949,31 +5241,34 @@ WorkerPrivate::SetTimeout(JSContext* aCx
     mTimeouts.InsertElementSorted(newInfo.forget(), GetAutoPtrComparator(mTimeouts));
 
   // If the timeout we just made is set to fire next then we need to update the
   // timer.
   if (insertedInfo == mTimeouts.Elements()) {
     nsresult rv;
 
     if (!mTimer) {
-      mTimer = do_CreateInstance(NS_TIMER_CONTRACTID, &rv);
+      nsCOMPtr<nsITimer> timer = do_CreateInstance(NS_TIMER_CONTRACTID, &rv);
       if (NS_FAILED(rv)) {
         aRv.Throw(rv);
         return 0;
       }
 
-      nsRefPtr<TimerRunnable> timerRunnable = new TimerRunnable(this);
-
-      nsCOMPtr<nsIEventTarget> target =
-        new WorkerRunnableEventTarget(timerRunnable);
-      rv = mTimer->SetTarget(target);
+      nsRefPtr<TimerRunnable> runnable = new TimerRunnable(this);
+
+      nsRefPtr<TimerThreadEventTarget> target =
+        new TimerThreadEventTarget(this, runnable);
+
+      rv = timer->SetTarget(target);
       if (NS_FAILED(rv)) {
         aRv.Throw(rv);
         return 0;
       }
+
+      timer.swap(mTimer);
     }
 
     if (!mTimerRunning) {
       if (!ModifyBusyCountFromWorker(aCx, true)) {
         aRv.Throw(NS_ERROR_FAILURE);
         return 0;
       }
       mTimerRunning = true;
@@ -5239,29 +5534,40 @@ WorkerPrivate::UpdateJITHardeningInterna
 }
 
 void
 WorkerPrivate::GarbageCollectInternal(JSContext* aCx, bool aShrinking,
                                       bool aCollectChildren)
 {
   AssertIsOnWorkerThread();
 
+  if (!JS::CurrentGlobalOrNull(aCx)) {
+    // We haven't compiled anything yet. Just bail out.
+    return;
+  }
+
   if (aShrinking || aCollectChildren) {
     JSRuntime* rt = JS_GetRuntime(aCx);
     JS::PrepareForFullGC(rt);
 
     if (aShrinking) {
       JS::ShrinkingGC(rt, JS::gcreason::DOM_WORKER);
+
+      if (!aCollectChildren) {
+        LOG(("Worker %p collected idle garbage\n", this));
+      }
     }
     else {
       JS::GCForReason(rt, JS::gcreason::DOM_WORKER);
+      LOG(("Worker %p collected garbage\n", this));
     }
   }
   else {
     JS_MaybeGC(aCx);
+    LOG(("Worker %p collected periodic garbage\n", this));
   }
 
   if (aCollectChildren) {
     for (uint32_t index = 0; index < mChildWorkers.Length(); index++) {
       mChildWorkers[index]->GarbageCollect(aCx, aShrinking);
     }
   }
 }
@@ -5275,38 +5581,59 @@ WorkerPrivate::CycleCollectInternal(JSCo
 
   if (aCollectChildren) {
     for (uint32_t index = 0; index < mChildWorkers.Length(); index++) {
       mChildWorkers[index]->CycleCollect(aCx, /* dummy = */ false);
     }
   }
 }
 
-
-template <class Derived>
 void
-WorkerPrivateParent<Derived>::RegisterHostObjectURI(const nsACString& aURI)
+WorkerPrivate::SetThread(nsIThread* aThread)
 {
-  AssertIsOnMainThread();
-  mHostObjectURIs.AppendElement(aURI);
-}
-
-template <class Derived>
-void
-WorkerPrivateParent<Derived>::UnregisterHostObjectURI(const nsACString& aURI)
-{
-  AssertIsOnMainThread();
-  mHostObjectURIs.RemoveElement(aURI);
-}
-
-template <class Derived>
-void
-WorkerPrivateParent<Derived>::StealHostObjectURIs(nsTArray<nsCString>& aArray)
-{
-  aArray.SwapElements(mHostObjectURIs);
+#ifdef DEBUG
+  if (aThread) {
+    bool isOnCurrentThread;
+    MOZ_ASSERT(NS_SUCCEEDED(aThread->IsOnCurrentThread(&isOnCurrentThread)));
+    MOZ_ASSERT(isOnCurrentThread);
+
+    MOZ_ASSERT(!mPRThread);
+    mPRThread = PRThreadFromThread(aThread);
+    MOZ_ASSERT(mPRThread);
+  }
+  else {
+    MOZ_ASSERT(mPRThread);
+  }
+#endif
+
+  nsCOMPtr<nsIThread> doomedThread;
+
+  { // Scope so that |doomedThread| is released without holding the lock.
+    MutexAutoLock lock(mMutex);
+
+    if (aThread) {
+      MOZ_ASSERT(!mThread);
+      MOZ_ASSERT(mStatus == Pending);
+
+      mThread = aThread;
+
+      if (!mPreStartRunnables.IsEmpty()) {
+        for (uint32_t index = 0; index < mPreStartRunnables.Length(); index++) {
+          MOZ_ALWAYS_TRUE(NS_SUCCEEDED(mThread->Dispatch(
+                                                      mPreStartRunnables[index],
+                                                      NS_DISPATCH_NORMAL)));
+        }
+        mPreStartRunnables.Clear();
+      }
+    }
+    else {
+      MOZ_ASSERT(mThread);
+      mThread.swap(doomedThread);
+    }
+  }
 }
 
 WorkerCrossThreadDispatcher*
 WorkerPrivate::GetCrossThreadDispatcher()
 {
   MutexAutoLock lock(mMutex);
 
   if (!mCrossThreadDispatcher && mStatus <= Running) {
@@ -5316,16 +5643,19 @@ WorkerPrivate::GetCrossThreadDispatcher(
   return mCrossThreadDispatcher;
 }
 
 void
 WorkerPrivate::BeginCTypesCall()
 {
   AssertIsOnWorkerThread();
 
+  // Don't try to GC while we're blocked in a ctypes call.
+  SetGCTimerMode(NoTimer);
+
   MutexAutoLock lock(mMutex);
 
   NS_ASSERTION(!mBlockedForMemoryReporter,
                "Can't be blocked in more than one place at the same time!");
 
   // Let the main thread know that the worker is effectively blocked while in
   // this ctypes call. It isn't technically true (obviously the call could do
   // non-blocking things), but we're assuming that ctypes can't call back into
@@ -5337,28 +5667,33 @@ WorkerPrivate::BeginCTypesCall()
   mMemoryReportCondVar.Notify();
 }
 
 void
 WorkerPrivate::EndCTypesCall()
 {
   AssertIsOnWorkerThread();
 
-  MutexAutoLock lock(mMutex);
-
-  NS_ASSERTION(mBlockedForMemoryReporter, "Somehow we got unblocked!");
-
-  // Don't continue until the memory reporter has finished.
-  while (mMemoryReporterRunning) {
-    mMemoryReportCondVar.Wait();
-  }
-
-  // No need to notify the main thread here as it shouldn't be waiting to see
-  // this state.
-  mBlockedForMemoryReporter = false;
+  {
+    MutexAutoLock lock(mMutex);
+
+    NS_ASSERTION(mBlockedForMemoryReporter, "Somehow we got unblocked!");
+
+    // Don't continue until the memory reporter has finished.
+    while (mMemoryReporterRunning) {
+      mMemoryReportCondVar.Wait();
+    }
+
+    // No need to notify the main thread here as it shouldn't be waiting to see
+    // this state.
+    mBlockedForMemoryReporter = false;
+  }
+
+  // Make sure the periodic timer is running before we start running JS again.
+  SetGCTimerMode(PeriodicTimer);
 }
 
 bool
 WorkerPrivate::ConnectMessagePort(JSContext* aCx, uint64_t aMessagePortSerial)
 {
   AssertIsOnWorkerThread();
 
   NS_ASSERTION(!mWorkerPorts.GetWeak(aMessagePortSerial),
@@ -5457,91 +5792,152 @@ WorkerPrivate::CreateGlobalScope(JSConte
   mScope = globalScope.forget();
 
   JS_FireOnNewGlobalObject(aCx, global);
 
   return global;
 }
 
 #ifdef DEBUG
-template <class Derived>
-void
-WorkerPrivateParent<Derived>::AssertIsOnParentThread() const
-{
-  if (GetParent()) {
-    GetParent()->AssertIsOnWorkerThread();
-  }
-  else {
-    AssertIsOnMainThread();
-  }
-}
-
-template <class Derived>
-void
-WorkerPrivateParent<Derived>::AssertInnerWindowIsCorrect() const
-{
-  AssertIsOnParentThread();
-
-  // Only care about top level workers from windows.
-  if (mParent || !mLoadInfo.mWindow) {
-    return;
-  }
-
-  AssertIsOnMainThread();
-
-  nsPIDOMWindow* outer = mLoadInfo.mWindow->GetOuterWindow();
-  NS_ASSERTION(outer && outer->GetCurrentInnerWindow() == mLoadInfo.mWindow,
-               "Inner window no longer correct!");
-}
 
 void
 WorkerPrivate::AssertIsOnWorkerThread() const
 {
-  MOZ_ASSERT(mThread,
-             "Trying to assert thread identity after thread has been "
-             "shutdown!");
+  // This is much more complicated than it needs to be but we can't use mThread
+  // because it must be protected by mMutex and sometimes this method is called
+  // when mMutex is already locked. This method should always work.
+  MOZ_ASSERT(mPRThread,
+             "AssertIsOnWorkerThread() called before a thread was assigned!");
+
+  MOZ_ASSERT(nsThreadManager::get());
+
+  nsCOMPtr<nsIThread> thread;
+  nsresult rv =
+    nsThreadManager::get()->GetThreadFromPRThread(mPRThread,
+                                                  getter_AddRefs(thread));
+  MOZ_ASSERT(NS_SUCCEEDED(rv));
+  MOZ_ASSERT(thread);
 
   bool current;
-  MOZ_ASSERT(NS_SUCCEEDED(mThread->IsOnCurrentThread(&current)));
+  rv = thread->IsOnCurrentThread(&current);
+  MOZ_ASSERT(NS_SUCCEEDED(rv));
   MOZ_ASSERT(current, "Wrong thread!");
 }
+
 #endif // DEBUG
 
+NS_IMPL_ISUPPORTS_INHERITED0(ExternalRunnableWrapper, WorkerRunnable)
+
+template <class Derived>
+NS_IMPL_ADDREF(WorkerPrivateParent<Derived>::EventTarget)
+
+template <class Derived>
+NS_IMPL_RELEASE(WorkerPrivateParent<Derived>::EventTarget)
+
+template <class Derived>
+NS_INTERFACE_MAP_BEGIN(WorkerPrivateParent<Derived>::EventTarget)
+  NS_INTERFACE_MAP_ENTRY(nsIEventTarget)
+  NS_INTERFACE_MAP_ENTRY(nsISupports)
+#ifdef DEBUG
+  // kDEBUGWorkerEventTargetIID is special in that it does not AddRef its
+  // result.
+  if (aIID.Equals(kDEBUGWorkerEventTargetIID)) {
+    *aInstancePtr = this;
+    return NS_OK;
+  }
+  else
+#endif
+NS_INTERFACE_MAP_END
+
+template <class Derived>
+NS_IMETHODIMP
+WorkerPrivateParent<Derived>::
+EventTarget::Dispatch(nsIRunnable* aRunnable, uint32_t aFlags)
+{
+  // May be called on any thread!
+
+  // Workers only support asynchronous dispatch for now.
+  if (NS_WARN_IF(aFlags != NS_DISPATCH_NORMAL)) {
+    return NS_ERROR_UNEXPECTED;
+  }
+
+  nsRefPtr<WorkerRunnable> workerRunnable;
+
+  MutexAutoLock lock(mMutex);
+
+  if (!mWorkerPrivate) {
+    NS_WARNING("A runnable was posted to a worker that is already shutting "
+               "down!");
+    return NS_ERROR_UNEXPECTED;
+  }
+
+  if (aRunnable) {
+    workerRunnable = mWorkerPrivate->MaybeWrapAsWorkerRunnable(aRunnable);
+  }
+
+  nsresult rv =
+    mWorkerPrivate->DispatchPrivate(workerRunnable, mNestedEventTarget);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
+  return NS_OK;
+}
+
+template <class Derived>
+NS_IMETHODIMP
+WorkerPrivateParent<Derived>::
+EventTarget::IsOnCurrentThread(bool* aIsOnCurrentThread)
+{
+  // May be called on any thread!
+
+  MOZ_ASSERT(aIsOnCurrentThread);
+
+  MutexAutoLock lock(mMutex);
+
+  if (!mWorkerPrivate) {
+    NS_WARNING("A worker's event target was used after the worker has !");
+    return NS_ERROR_UNEXPECTED;
+  }
+
+  nsresult rv = mWorkerPrivate->IsOnCurrentThread(aIsOnCurrentThread);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
+  return NS_OK;
+}
+
 BEGIN_WORKERS_NAMESPACE
 
 WorkerCrossThreadDispatcher*
 GetWorkerCrossThreadDispatcher(JSContext* aCx, JS::Value aWorker)
 {
   if (!aWorker.isObject()) {
     return nullptr;
   }
 
   WorkerPrivate* w = nullptr;
   UNWRAP_OBJECT(Worker, &aWorker.toObject(), w);
   MOZ_ASSERT(w);
   return w->GetCrossThreadDispatcher();
 }
 
-// Can't use NS_IMPL_CYCLE_COLLECTION_CLASS(WorkerPrivateParent) because of the
-// templates.
-template <>
-WorkerPrivateParent<WorkerPrivate>::cycleCollection WorkerPrivateParent<WorkerPrivate>::_cycleCollectorGlobal = WorkerPrivateParent<WorkerPrivate>::cycleCollection();
-
-// Force instantiation.
-template class WorkerPrivateParent<WorkerPrivate>;
-
 JSStructuredCloneCallbacks*
 WorkerStructuredCloneCallbacks(bool aMainRuntime)
 {
   return aMainRuntime ?
          &gMainThreadWorkerStructuredCloneCallbacks :
          &gWorkerStructuredCloneCallbacks;
 }
 
 JSStructuredCloneCallbacks*
 ChromeWorkerStructuredCloneCallbacks(bool aMainRuntime)
 {
   return aMainRuntime ?
          &gMainThreadChromeWorkerStructuredCloneCallbacks :
          &gChromeWorkerStructuredCloneCallbacks;
 }
 
+// Force instantiation.
+template class WorkerPrivateParent<WorkerPrivate>;
+
 END_WORKERS_NAMESPACE
--- a/dom/workers/WorkerPrivate.h
+++ b/dom/workers/WorkerPrivate.h
@@ -4,203 +4,76 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #ifndef mozilla_dom_workers_workerprivate_h__
 #define mozilla_dom_workers_workerprivate_h__
 
 #include "Workers.h"
 
 #include "nsIContentSecurityPolicy.h"
-#include "nsIRunnable.h"
-#include "nsIThread.h"
-#include "nsIThreadInternal.h"
 #include "nsPIDOMWindow.h"
 
-#include "mozilla/Assertions.h"
 #include "mozilla/CondVar.h"
 #include "mozilla/TimeStamp.h"
 #include "mozilla/dom/BindingDeclarations.h"
 #include "nsCycleCollectionParticipant.h"
 #include "nsDataHashtable.h"
 #include "nsDOMEventTargetHelper.h"
-#include "nsEventQueue.h"
 #include "nsHashKeys.h"
 #include "nsRefPtrHashtable.h"
 #include "nsString.h"
 #include "nsTArray.h"
 #include "nsThreadUtils.h"
-#include "nsTPriorityQueue.h"
 #include "StructuredCloneTags.h"
 
 #include "Queue.h"
 #include "WorkerFeature.h"
 
 class JSAutoStructuredCloneBuffer;
 class nsIChannel;
 class nsIDocument;
+class nsIEventTarget;
 class nsIPrincipal;
 class nsIScriptContext;
+class nsIThread;
 class nsITimer;
 class nsIURI;
 
 namespace JS {
 class RuntimeStats;
 }
 
 namespace mozilla {
 namespace dom {
 class Function;
 }
 }
 
-BEGIN_WORKERS_NAMESPACE
-
-class MessagePort;
-class SharedWorker;
-class WorkerGlobalScope;
-class WorkerPrivate;
-
-class WorkerRunnable : public nsIRunnable
-{
-public:
-  enum Target { ParentThread, WorkerThread };
-  enum BusyBehavior { ModifyBusyCount, UnchangedBusyCount };
-  enum ClearingBehavior { SkipWhenClearing, RunWhenClearing };
-
-protected:
-  WorkerPrivate* mWorkerPrivate;
-  Target mTarget;
-  BusyBehavior mBusyBehavior;
-  ClearingBehavior mClearingBehavior;
-
-public:
-  NS_DECL_THREADSAFE_ISUPPORTS
-
-  bool
-  Dispatch(JSContext* aCx);
-
-  static bool
-  DispatchToMainThread(nsIRunnable*);
-
-  bool
-  WantsToRunDuringClear()
-  {
-    return mClearingBehavior == RunWhenClearing;
-  }
-
-protected:
-  WorkerRunnable(WorkerPrivate* aWorkerPrivate, Target aTarget,
-                 BusyBehavior aBusyBehavior,
-                 ClearingBehavior aClearingBehavior)
 #ifdef DEBUG
-  ;
-#else
-  : mWorkerPrivate(aWorkerPrivate), mTarget(aTarget),
-    mBusyBehavior(aBusyBehavior), mClearingBehavior(aClearingBehavior)
-  { }
+struct PRThread;
 #endif
 
-  virtual ~WorkerRunnable()
-  { }
-
-  virtual bool
-  PreDispatch(JSContext* aCx, WorkerPrivate* aWorkerPrivate);
-
-  virtual void
-  PostDispatch(JSContext* aCx, WorkerPrivate* aWorkerPrivate,
-               bool aDispatchResult);
-
-  virtual bool
-  DispatchInternal();
-
-  virtual bool
-  WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate) = 0;
-
-  virtual void
-  PostRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate, bool aRunResult);
-
-public:
-  NS_DECL_NSIRUNNABLE
-};
-
-class WorkerSyncRunnable : public WorkerRunnable
-{
-protected:
-  uint32_t mSyncQueueKey;
-  bool mBypassSyncQueue;
-
-protected:
-  friend class WorkerPrivate;
-
-  WorkerSyncRunnable(WorkerPrivate* aWorkerPrivate, uint32_t aSyncQueueKey,
-                     bool aBypassSyncQueue = false,
-                     ClearingBehavior aClearingBehavior = SkipWhenClearing)
-  : WorkerRunnable(aWorkerPrivate, WorkerThread, UnchangedBusyCount,
-                   aClearingBehavior),
-    mSyncQueueKey(aSyncQueueKey), mBypassSyncQueue(aBypassSyncQueue)
-  { }
-
-  virtual ~WorkerSyncRunnable()
-  { }
+BEGIN_WORKERS_NAMESPACE
 
-  virtual bool
-  DispatchInternal() MOZ_OVERRIDE;
-};
-
-class MainThreadSyncRunnable : public WorkerSyncRunnable
-{
-public:
-  MainThreadSyncRunnable(WorkerPrivate* aWorkerPrivate,
-                         ClearingBehavior aClearingBehavior,
-                         uint32_t aSyncQueueKey,
-                         bool aBypassSyncEventQueue)
-  : WorkerSyncRunnable(aWorkerPrivate, aSyncQueueKey, aBypassSyncEventQueue,
-                       aClearingBehavior)
-  {
-    AssertIsOnMainThread();
-  }
-
-  bool
-  PreDispatch(JSContext* aCx, WorkerPrivate* aWorkerPrivate) MOZ_OVERRIDE
-  {
-    AssertIsOnMainThread();
-    return true;
-  }
-
-  void
-  PostDispatch(JSContext* aCx, WorkerPrivate* aWorkerPrivate,
-               bool aDispatchResult) MOZ_OVERRIDE
-  {
-    AssertIsOnMainThread();
-  }
-};
-
-class WorkerControlRunnable : public WorkerRunnable
-{
-protected:
-  WorkerControlRunnable(WorkerPrivate* aWorkerPrivate, Target aTarget,
-                        BusyBehavior aBusyBehavior)
-  : WorkerRunnable(aWorkerPrivate, aTarget, aBusyBehavior, SkipWhenClearing)
-  { }
-
-  virtual ~WorkerControlRunnable()
-  { }
-
-  virtual bool
-  DispatchInternal() MOZ_OVERRIDE;
-};
+class AutoSyncLoopHolder;
+class MessagePort;
+class SharedWorker;
+class WorkerControlRunnable;
+class WorkerGlobalScope;
+class WorkerPrivate;
+class WorkerRunnable;
 
 // SharedMutex is a small wrapper around an (internal) reference-counted Mutex
 // object. It exists to avoid changing a lot of code to use Mutex* instead of
 // Mutex&.
 class SharedMutex
 {
   typedef mozilla::Mutex Mutex;
 
-  class RefCountedMutex : public Mutex
+  class RefCountedMutex MOZ_FINAL : public Mutex
   {
   public:
     RefCountedMutex(const char* aName)
     : Mutex(aName)
     { }
 
     NS_INLINE_DECL_THREADSAFE_REFCOUNTING(RefCountedMutex)
 
@@ -217,39 +90,40 @@ public:
   { }
 
   SharedMutex(SharedMutex& aOther)
   : mMutex(aOther.mMutex)
   { }
 
   operator Mutex&()
   {
-    MOZ_ASSERT(mMutex);
     return *mMutex;
   }
 
   operator const Mutex&() const
   {
-    MOZ_ASSERT(mMutex);
     return *mMutex;
   }
 
   void
   AssertCurrentThreadOwns() const
   {
-    MOZ_ASSERT(mMutex);
     mMutex->AssertCurrentThreadOwns();
   }
 };
 
 template <class Derived>
 class WorkerPrivateParent : public nsDOMEventTargetHelper
 {
   class SynchronizeAndResumeRunnable;
 
+protected:
+  class EventTarget;
+  friend class EventTarget;
+
 public:
   struct LocationInfo
   {
     nsCString mHref;
     nsCString mProtocol;
     nsCString mHost;
     nsCString mHostname;
     nsCString mPort;
@@ -307,27 +181,31 @@ public:
 
 protected:
   typedef mozilla::ErrorResult ErrorResult;
 
   SharedMutex mMutex;
   mozilla::CondVar mCondVar;
   mozilla::CondVar mMemoryReportCondVar;
 
+  // Protected by mMutex.
+  nsRefPtr<EventTarget> mEventTarget;
+  nsTArray<nsRefPtr<WorkerRunnable>> mPreStartRunnables;
+
 private:
   WorkerPrivate* mParent;
   nsString mScriptURL;
   nsString mSharedWorkerName;
   LocationInfo mLocationInfo;
   // The lifetime of these objects within LoadInfo is managed explicitly;
   // they do not need to be cycle collected.
   LoadInfo mLoadInfo;
 
   // Only used for top level workers.
-  nsTArray<nsRefPtr<WorkerRunnable> > mQueuedRunnables;
+  nsTArray<nsCOMPtr<nsIRunnable>> mQueuedRunnables;
   nsRevocableEventPtr<SynchronizeAndResumeRunnable> mSynchronizeRunnable;
 
   // Only for ChromeWorkers without window and only touched on the main thread.
   nsTArray<nsCString> mHostObjectURIs;
 
   // Protected by mMutex.
   JSSettings mJSSettings;
 
@@ -371,24 +249,42 @@ private:
   }
 
   void
   PostMessageInternal(JSContext* aCx, JS::Handle<JS::Value> aMessage,
                       const Optional<Sequence<JS::Value> >& aTransferable,
                       bool aToMessagePort, uint64_t aMessagePortSerial,
                       ErrorResult& aRv);
 
+  nsresult
+  DispatchPrivate(WorkerRunnable* aRunnable, nsIEventTarget* aSyncLoopTarget);
+
 public:
   virtual JSObject*
   WrapObject(JSContext* aCx, JS::Handle<JSObject*> aScope) MOZ_OVERRIDE;
 
   NS_DECL_ISUPPORTS_INHERITED
   NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS_INHERITED(WorkerPrivateParent,
                                                          nsDOMEventTargetHelper)
 
+  nsresult
+  Dispatch(WorkerRunnable* aRunnable)
+  {
+    return DispatchPrivate(aRunnable, nullptr);
+  }
+
+  nsresult
+  DispatchControlRunnable(WorkerControlRunnable* aWorkerControlRunnable);
+
+  already_AddRefed<WorkerRunnable>
+  MaybeWrapAsWorkerRunnable(nsIRunnable* aRunnable);
+
+  already_AddRefed<nsIEventTarget>
+  GetEventTarget();
+
   // May be called on any thread...
   bool
   Start();
 
   // Called on the parent thread.
   bool
   Notify(JSContext* aCx, Status aStatus)
   {
@@ -510,17 +406,17 @@ public:
                                 uint32_t aLineNumber,
                                 uint32_t aColumnNumber,
                                 uint32_t aFlags);
 
   void
   WorkerScriptLoaded();
 
   void
-  QueueRunnable(WorkerRunnable* aRunnable)
+  QueueRunnable(nsIRunnable* aRunnable)
   {
     AssertIsOnMainThread();
     mQueuedRunnables.AppendElement(aRunnable);
   }
 
   WorkerPrivate*
   GetParent() const
   {
@@ -533,23 +429,20 @@ public:
     AssertIsOnParentThread();
     return mParentSuspended;
   }
 
   bool
   IsAcceptingEvents()
   {
     AssertIsOnParentThread();
-    bool acceptingEvents;
-    {
-      mozilla::MutexAutoLock lock(mMutex);
-      acceptingEvents = mParentStatus < Terminating;
+
+    MutexAutoLock lock(mMutex);
+    return mParentStatus < Terminating;
     }
-    return acceptingEvents;
-  }
 
   Status
   ParentStatus() const
   {
     mMutex.AssertCurrentThreadOwns();
     return mParentStatus;
   }
 
@@ -770,76 +663,87 @@ public:
   { }
 #endif
 };
 
 class WorkerPrivate : public WorkerPrivateParent<WorkerPrivate>
 {
   friend class WorkerPrivateParent<WorkerPrivate>;
   typedef WorkerPrivateParent<WorkerPrivate> ParentType;
+  friend class AutoSyncLoopHolder;
 
   struct TimeoutInfo;
 
-  typedef Queue<WorkerRunnable*, 50> EventQueue;
-  EventQueue mQueue;
-  EventQueue mControlQueue;
-
-  struct SyncQueue
-  {
-    Queue<WorkerRunnable*, 10> mQueue;
-    bool mComplete;
-    bool mResult;
-
-    SyncQueue()
-    : mComplete(false), mResult(false)
-    { }
-
-    ~SyncQueue()
-    {
-      WorkerRunnable* event;
-      while (mQueue.Pop(event)) {
-        event->Release();
-      }
-    }
-  };
-
   class MemoryReporter;
   friend class MemoryReporter;
 
-  nsTArray<nsAutoPtr<SyncQueue> > mSyncQueues;
+  enum GCTimerMode
+  {
+    PeriodicTimer = 0,
+    IdleTimer,
+    NoTimer
+  };
+
+  Queue<WorkerControlRunnable*, 4> mControlQueue;
 
   // Touched on multiple threads, protected with mMutex.
   JSContext* mJSContext;
   nsRefPtr<WorkerCrossThreadDispatcher> mCrossThreadDispatcher;
+  nsTArray<nsCOMPtr<nsIRunnable>> mUndispatchedRunnablesForSyncLoop;
+  nsCOMPtr<nsIThread> mThread;
 
   // Things touched on worker thread only.
   nsRefPtr<WorkerGlobalScope> mScope;
   nsTArray<ParentType*> mChildWorkers;
   nsTArray<WorkerFeature*> mFeatures;
-  nsTArray<nsAutoPtr<TimeoutInfo> > mTimeouts;
+  nsTArray<nsAutoPtr<TimeoutInfo>> mTimeouts;
+
+  struct SyncLoopInfo
+  {
+    SyncLoopInfo(EventTarget* aEventTarget);
+
+    nsRefPtr<EventTarget> mEventTarget;
+    bool mCompleted;
+    bool mResult;
+#ifdef DEBUG
+    bool mHasRun;
+#endif
+  };
+
+  // This is only modified on the worker thread, but in DEBUG builds
+  // AssertValidSyncLoop function iterates it on other threads. Therefore
+  // modifications are done with mMutex held *only* in DEBUG builds.
+  nsTArray<nsAutoPtr<SyncLoopInfo>> mSyncLoopStack;
 
   nsCOMPtr<nsITimer> mTimer;
+
+  nsCOMPtr<nsITimer> mGCTimer;
+  nsCOMPtr<nsIEventTarget> mPeriodicGCTimerTarget;
+  nsCOMPtr<nsIEventTarget> mIdleGCTimerTarget;
+
   nsRefPtr<MemoryReporter> mMemoryReporter;
 
   nsRefPtrHashtable<nsUint64HashKey, MessagePort> mWorkerPorts;
 
-  mozilla::TimeStamp mKillTime;
+  TimeStamp mKillTime;
   uint32_t mErrorHandlerRecursionCount;
   uint32_t mNextTimeoutId;
   Status mStatus;
   bool mSuspended;
   bool mTimerRunning;
   bool mRunningExpiredTimeouts;
   bool mCloseHandlerStarted;
   bool mCloseHandlerFinished;
   bool mMemoryReporterRunning;
   bool mBlockedForMemoryReporter;
+  bool mCancelAllPendingRunnables;
+  bool mPeriodicGCTimerRunning;
 
 #ifdef DEBUG
-  nsCOMPtr<nsIThread> mThread;
+  PRThread* mPRThread;
 #endif
 
   bool mPreferences[WORKERPREF_COUNT];
 
 protected:
   ~WorkerPrivate();
 
 public:
@@ -862,37 +766,18 @@ public:
               LoadInfo* aLoadInfo);
 
   void
   DoRunLoop(JSContext* aCx);
 
   bool
   OperationCallback(JSContext* aCx);
 
-  bool
-  Dispatch(WorkerRunnable* aEvent)
-  {
-    return Dispatch(aEvent, &mQueue);
-  }
-
-  bool
-  Dispatch(WorkerSyncRunnable* aEvent)
-  {
-    if (aEvent->mBypassSyncQueue) {
-      return Dispatch(aEvent, &mQueue);
-    }
-
-    return DispatchToSyncQueue(aEvent);
-  }
-
-  bool
-  Dispatch(WorkerControlRunnable* aEvent)
-  {
-    return Dispatch(aEvent, &mControlQueue);
-  }
+  nsresult
+  IsOnCurrentThread(bool* aIsOnCurrentThread);
 
   bool
   CloseInternal(JSContext* aCx)
   {
     AssertIsOnWorkerThread();
     return NotifyInternal(aCx, Closing);
   }
 
@@ -925,28 +810,16 @@ public:
 
   bool
   HasActiveFeatures()
   {
     return !(mChildWorkers.IsEmpty() && mTimeouts.IsEmpty() &&
              mFeatures.IsEmpty());
   }
 
-  uint32_t
-  CreateNewSyncLoop();
-
-  bool
-  RunSyncLoop(JSContext* aCx, uint32_t aSyncLoopKey);
-
-  void
-  StopSyncLoop(uint32_t aSyncLoopKey, bool aSyncResult);
-
-  void
-  DestroySyncLoop(uint32_t aSyncLoopKey);
-
   void
   PostMessageToParent(JSContext* aCx,
                       JS::Handle<JS::Value> aMessage,
                       const Optional<Sequence<JS::Value>>& aTransferable,
                       ErrorResult& aRv)
   {
     PostMessageToParentInternal(aCx, aMessage, aTransferable, false, 0, aRv);
   }
@@ -1003,17 +876,17 @@ public:
 
   void
   UpdatePreferenceInternal(JSContext* aCx, WorkerPreference aPref, bool aValue);
 
   void
   UpdateJSWorkerMemoryParameterInternal(JSContext* aCx, JSGCParamKey key, uint32_t aValue);
 
   void
-  ScheduleDeletion(bool aWasPending);
+  ScheduleDeletion();
 
   bool
   BlockAndCollectRuntimeStats(JS::RuntimeStats* aRtStats);
 
 #ifdef JS_GC_ZEAL
   void
   UpdateGCZealInternal(JSContext* aCx, uint8_t aGCZeal, uint32_t aFrequency);
 #endif
@@ -1037,28 +910,24 @@ public:
 
   WorkerGlobalScope*
   GlobalScope() const
   {
     AssertIsOnWorkerThread();
     return mScope;
   }
 
-#ifdef DEBUG
   void
-  AssertIsOnWorkerThread() const;
+  SetThread(nsIThread* aThread);
 
   void
-  SetThread(nsIThread* aThread)
-  {
-    mThread = aThread;
-  }
+  AssertIsOnWorkerThread() const
+#ifdef DEBUG
+  ;
 #else
-  void
-  AssertIsOnWorkerThread() const
   { }
 #endif
 
   WorkerCrossThreadDispatcher*
   GetCrossThreadDispatcher();
 
   // This may block!
   void
@@ -1108,39 +977,56 @@ public:
 
   bool
   PromiseEnabled() const
   {
     AssertIsOnWorkerThread();
     return mPreferences[WORKERPREF_PROMISE];
   }
 
+  void
+  StopSyncLoop(nsIEventTarget* aSyncLoopTarget, bool aResult);
+
+  bool
+  AllPendingRunnablesShouldBeCanceled() const
+  {
+    return mCancelAllPendingRunnables;
+  }
+
+  void
+  OnProcessNextEvent(uint32_t aRecursionDepth);
+
+  void
+  AfterProcessNextEvent(uint32_t aRecursionDepth);
+
+  void
+  AssertValidSyncLoop(nsIEventTarget* aSyncLoopTarget)
+#ifdef DEBUG
+  ;
+#else
+  { }
+#endif
+
 private:
   WorkerPrivate(JSContext* aCx, WorkerPrivate* aParent,
                 const nsAString& aScriptURL, bool aIsChromeWorker,
                 WorkerType aWorkerType, const nsAString& aSharedWorkerName,
                 LoadInfo& aLoadInfo);
 
-  bool
-  Dispatch(WorkerRunnable* aEvent, EventQueue* aQueue);
-
-  bool
-  DispatchToSyncQueue(WorkerSyncRunnable* aEvent);
-
   void
-  ClearQueue(EventQueue* aQueue);
+  ClearMainEventQueue();
 
   bool
   MayContinueRunning()
   {
     AssertIsOnWorkerThread();
 
     Status status;
     {
-      mozilla::MutexAutoLock lock(mMutex);
+      MutexAutoLock lock(mMutex);
       status = mStatus;
     }
 
     if (status >= Killing) {
       return false;
     }
     if (status >= Running) {
       return mKillTime.IsNull() || RemainingRunTimeMS() > 0;
@@ -1152,32 +1038,25 @@ private:
   RemainingRunTimeMS() const;
 
   void
   CancelAllTimeouts(JSContext* aCx);
 
   bool
   ScheduleKillCloseEventRunnable(JSContext* aCx);
 
-  void
-  StopAcceptingEvents()
+  bool
+  ProcessAllControlRunnables()
   {
-    AssertIsOnWorkerThread();
-
-    mozilla::MutexAutoLock lock(mMutex);
-
-    mStatus = Dead;
-    mJSContext = nullptr;
-
-    ClearQueue(&mControlQueue);
-    ClearQueue(&mQueue);
+    MutexAutoLock lock(mMutex);
+    return ProcessAllControlRunnablesLocked();
   }
 
   bool
-  ProcessAllControlRunnables();
+  ProcessAllControlRunnablesLocked();
 
   void
   EnableMemoryReporter();
 
   void
   DisableMemoryReporter();
 
   void
@@ -1192,16 +1071,31 @@ private:
                               ErrorResult& aRv);
 
   void
   GetAllPreferences(bool aPreferences[WORKERPREF_COUNT]) const
   {
     AssertIsOnWorkerThread();
     memcpy(aPreferences, mPreferences, WORKERPREF_COUNT * sizeof(bool));
   }
+
+  already_AddRefed<nsIEventTarget>
+  CreateNewSyncLoop();
+
+  bool
+  RunCurrentSyncLoop();
+
+  void
+  InitializeGCTimers();
+
+  void
+  SetGCTimerMode(GCTimerMode aMode);
+
+  void
+  ShutdownGCTimers();
 };
 
 // This class is only used to trick the DOM bindings.  We never create
 // instances of it, and static_casting to it is fine since it doesn't add
 // anything to WorkerPrivate.
 class ChromeWorkerPrivate : public WorkerPrivate
 {
 public:
@@ -1241,45 +1135,47 @@ enum WorkerStructuredDataType
 JSStructuredCloneCallbacks*
 WorkerStructuredCloneCallbacks(bool aMainRuntime);
 
 JSStructuredCloneCallbacks*
 ChromeWorkerStructuredCloneCallbacks(bool aMainRuntime);
 
 class AutoSyncLoopHolder
 {
+  WorkerPrivate* mWorkerPrivate;
+  nsCOMPtr<nsIEventTarget> mTarget;
+
 public:
   AutoSyncLoopHolder(WorkerPrivate* aWorkerPrivate)
-  : mWorkerPrivate(aWorkerPrivate), mSyncLoopKey(UINT32_MAX)
+  : mWorkerPrivate(aWorkerPrivate), mTarget(aWorkerPrivate->CreateNewSyncLoop())
   {
-    mSyncLoopKey = mWorkerPrivate->CreateNewSyncLoop();
+    aWorkerPrivate->AssertIsOnWorkerThread();
   }
 
   ~AutoSyncLoopHolder()
   {
     if (mWorkerPrivate) {
-      mWorkerPrivate->StopSyncLoop(mSyncLoopKey, false);
-      mWorkerPrivate->DestroySyncLoop(mSyncLoopKey);
+      mWorkerPrivate->AssertIsOnWorkerThread();
+      mWorkerPrivate->StopSyncLoop(mTarget, false);
     }
   }
 
   bool
-  RunAndForget(JSContext* aCx)
+  Run()
   {
     WorkerPrivate* workerPrivate = mWorkerPrivate;
     mWorkerPrivate = nullptr;
-    return workerPrivate->RunSyncLoop(aCx, mSyncLoopKey);
+
+    workerPrivate->AssertIsOnWorkerThread();
+
+    return workerPrivate->RunCurrentSyncLoop();
   }
 
-  uint32_t
-  SyncQueueKey() const
+  nsIEventTarget*
+  EventTarget() const
   {
-    return mSyncLoopKey;
+    return mTarget;
   }
-
-private:
-  WorkerPrivate* mWorkerPrivate;
-  uint32_t mSyncLoopKey;
 };
 
 END_WORKERS_NAMESPACE
 
 #endif /* mozilla_dom_workers_workerprivate_h__ */
new file mode 100644
--- /dev/null
+++ b/dom/workers/WorkerRunnable.cpp
@@ -0,0 +1,480 @@
+/* -*- Mode: c++; c-basic-offset: 2; indent-tabs-mode: nil; tab-width: 40 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * 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 "WorkerRunnable.h"
+
+#include "nsIEventTarget.h"
+#include "nsIRunnable.h"
+
+#include "js/RootingAPI.h"
+#include "js/Value.h"
+#include "nsThreadUtils.h"
+
+#include "WorkerPrivate.h"
+
+USING_WORKERS_NAMESPACE
+
+namespace {
+
+const nsIID kWorkerRunnableIID = {
+  0x320cc0b5, 0xef12, 0x4084, { 0x88, 0x6e, 0xca, 0x6a, 0x81, 0xe4, 0x1d, 0x68 }
+};
+
+void
+MaybeReportMainThreadException(JSContext* aCx, bool aResult)
+{
+  AssertIsOnMainThread();
+
+  if (aCx && !aResult) {
+    JS_ReportPendingException(aCx);
+  }
+}
+
+} // anonymous namespace
+
+#ifdef DEBUG
+WorkerRunnable::WorkerRunnable(WorkerPrivate* aWorkerPrivate,
+                               TargetAndBusyBehavior aBehavior)
+: mWorkerPrivate(aWorkerPrivate), mBehavior(aBehavior), mCanceled(0),
+  mCallingCancelWithinRun(false)
+{
+  MOZ_ASSERT(aWorkerPrivate);
+}
+#endif
+
+bool
+WorkerRunnable::PreDispatch(JSContext* aCx, WorkerPrivate* aWorkerPrivate)
+{
+#ifdef DEBUG
+  MOZ_ASSERT(aWorkerPrivate);
+
+  switch (mBehavior) {
+    case ParentThreadUnchangedBusyCount:
+      aWorkerPrivate->AssertIsOnWorkerThread();
+      break;
+
+    case WorkerThreadModifyBusyCount:
+      aWorkerPrivate->AssertIsOnParentThread();
+      MOZ_ASSERT(aCx);
+      break;
+
+    case WorkerThreadUnchangedBusyCount:
+      aWorkerPrivate->AssertIsOnParentThread();
+      break;
+
+    default:
+      MOZ_ASSUME_UNREACHABLE("Unknown behavior!");
+  }
+#endif
+
+  if (mBehavior == WorkerThreadModifyBusyCount) {
+    return aWorkerPrivate->ModifyBusyCount(aCx, true);
+  }
+
+  return true;
+}
+
+bool
+WorkerRunnable::Dispatch(JSContext* aCx)
+{
+  bool ok;
+
+  if (!aCx) {
+    ok = PreDispatch(nullptr, mWorkerPrivate);
+    if (ok) {
+      ok = DispatchInternal();
+    }
+    PostDispatch(nullptr, mWorkerPrivate, ok);
+    return ok;
+  }
+
+  JSAutoRequest ar(aCx);
+
+  JS::Rooted<JSObject*> global(aCx, JS::CurrentGlobalOrNull(aCx));
+
+  Maybe<JSAutoCompartment> ac;
+  if (global) {
+    ac.construct(aCx, global);
+  }
+
+  ok = PreDispatch(aCx, mWorkerPrivate);
+
+  if (ok && !DispatchInternal()) {
+    ok = false;
+  }
+
+  PostDispatch(aCx, mWorkerPrivate, ok);
+
+  return ok;
+}
+
+bool
+WorkerRunnable::DispatchInternal()
+{
+  if (mBehavior == WorkerThreadModifyBusyCount ||
+      mBehavior == WorkerThreadUnchangedBusyCount) {
+    return NS_SUCCEEDED(mWorkerPrivate->Dispatch(this));
+  }
+
+  MOZ_ASSERT(mBehavior == ParentThreadUnchangedBusyCount);
+
+  if (WorkerPrivate* parent = mWorkerPrivate->GetParent()) {
+    return NS_SUCCEEDED(parent->Dispatch(this));
+  }
+
+  nsCOMPtr<nsIThread> mainThread = do_GetMainThread();
+  MOZ_ASSERT(mainThread);
+
+  return NS_SUCCEEDED(mainThread->Dispatch(this, NS_DISPATCH_NORMAL));
+}
+
+void
+WorkerRunnable::PostDispatch(JSContext* aCx, WorkerPrivate* aWorkerPrivate,
+                             bool aDispatchResult)
+{
+  MOZ_ASSERT(aWorkerPrivate);
+
+#ifdef DEBUG
+  switch (mBehavior) {
+    case ParentThreadUnchangedBusyCount:
+      aWorkerPrivate->AssertIsOnWorkerThread();
+      break;
+
+    case WorkerThreadModifyBusyCount:
+      aWorkerPrivate->AssertIsOnParentThread();
+      MOZ_ASSERT(aCx);
+      break;
+
+    case WorkerThreadUnchangedBusyCount:
+      aWorkerPrivate->AssertIsOnParentThread();
+      break;
+
+    default:
+      MOZ_ASSUME_UNREACHABLE("Unknown behavior!");
+  }
+#endif
+
+  if (!aDispatchResult) {
+    if (mBehavior == WorkerThreadModifyBusyCount) {
+      aWorkerPrivate->ModifyBusyCount(aCx, false);
+    }
+    if (aCx) {
+      JS_ReportPendingException(aCx);
+    }
+  }
+}
+
+void
+WorkerRunnable::PostRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate,
+                        bool aRunResult)
+{
+  MOZ_ASSERT(aCx);
+  MOZ_ASSERT(aWorkerPrivate);
+
+#ifdef DEBUG
+  switch (mBehavior) {
+    case ParentThreadUnchangedBusyCount:
+      aWorkerPrivate->AssertIsOnParentThread();
+      break;
+
+    case WorkerThreadModifyBusyCount:
+      aWorkerPrivate->AssertIsOnWorkerThread();
+      break;
+
+    case WorkerThreadUnchangedBusyCount:
+      aWorkerPrivate->AssertIsOnWorkerThread();
+      break;
+
+    default:
+      MOZ_ASSUME_UNREACHABLE("Unknown behavior!");
+  }
+#endif
+
+  if (mBehavior == WorkerThreadModifyBusyCount) {
+    if (!aWorkerPrivate->ModifyBusyCountFromWorker(aCx, false)) {
+      aRunResult = false;
+    }
+  }
+
+  if (!aRunResult) {
+    JS_ReportPendingException(aCx);
+  }
+}
+
+// static
+WorkerRunnable*
+WorkerRunnable::FromRunnable(nsIRunnable* aRunnable)
+{
+  MOZ_ASSERT(aRunnable);
+
+  WorkerRunnable* runnable;
+  nsresult rv = aRunnable->QueryInterface(kWorkerRunnableIID,
+                                          reinterpret_cast<void**>(&runnable));
+  if (NS_FAILED(rv)) {
+    return nullptr;
+  }
+
+  MOZ_ASSERT(runnable);
+  return runnable;
+}
+
+NS_IMPL_ADDREF(WorkerRunnable)
+NS_IMPL_RELEASE(WorkerRunnable)
+
+NS_INTERFACE_MAP_BEGIN(WorkerRunnable)
+  NS_INTERFACE_MAP_ENTRY(nsIRunnable)
+  NS_INTERFACE_MAP_ENTRY(nsICancelableRunnable)
+  NS_INTERFACE_MAP_ENTRY(nsISupports)
+  // kWorkerRunnableIID is special in that it does not AddRef its result.
+  if (aIID.Equals(kWorkerRunnableIID)) {
+    *aInstancePtr = this;
+    return NS_OK;
+  }
+  else
+NS_INTERFACE_MAP_END
+
+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;
+  }
+
+  JSContext* cx;
+  nsRefPtr<WorkerPrivate> kungFuDeathGrip;
+  nsCxPusher pusher;
+
+  if (targetIsWorkerThread) {
+    if (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;
+    }
+
+    cx = mWorkerPrivate->GetJSContext();
+    MOZ_ASSERT(cx);
+  }
+  else {
+    cx = mWorkerPrivate->ParentJSContext();
+    MOZ_ASSERT(cx);
+
+    kungFuDeathGrip = mWorkerPrivate;
+
+    if (!mWorkerPrivate->GetParent()) {
+      AssertIsOnMainThread();
+      pusher.Push(cx);
+    }
+  }
+
+  JSAutoRequest ar(cx);
+
+  JS::Rooted<JSObject*> targetCompartmentObject(cx);
+  if (targetIsWorkerThread) {
+    targetCompartmentObject = JS::CurrentGlobalOrNull(cx);
+  } else {
+    targetCompartmentObject = mWorkerPrivate->GetWrapper();
+  }
+
+  Maybe<JSAutoCompartment> ac;
+  if (targetCompartmentObject) {
+    ac.construct(cx, targetCompartmentObject);
+  }
+
+  bool result = WorkerRun(cx, mWorkerPrivate);
+
+  // In the case of CompileScriptRunnnable, WorkerRun above can cause us to
+  // lazily create a global, in which case we need to be in its compartment
+  // when calling PostRun() below. Maybe<> this time...
+  if (targetIsWorkerThread &&
+      ac.empty() &&
+      js::DefaultObjectForContextOrNull(cx)) {
+    ac.construct(cx, js::DefaultObjectForContextOrNull(cx));
+  }
+
+  PostRun(cx, mWorkerPrivate, result);
+
+  return result ? NS_OK : NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP
+WorkerRunnable::Cancel()
+{
+  uint32_t canceledCount = ++mCanceled;
+
+  MOZ_ASSERT(canceledCount, "Cancel() overflow!");
+
+  // The docs say that Cancel() should not be called more than once and that we
+  // should throw NS_ERROR_UNEXPECTED if it is.
+  return (canceledCount == 1) ? NS_OK : NS_ERROR_UNEXPECTED;
+}
+
+WorkerSyncRunnable::WorkerSyncRunnable(WorkerPrivate* aWorkerPrivate,
+                                       nsIEventTarget* aSyncLoopTarget)
+: WorkerRunnable(aWorkerPrivate, WorkerThreadUnchangedBusyCount),
+  mSyncLoopTarget(aSyncLoopTarget)
+{
+#ifdef DEBUG
+  if (mSyncLoopTarget) {
+    mWorkerPrivate->AssertValidSyncLoop(mSyncLoopTarget);
+  }
+#endif
+}
+
+WorkerSyncRunnable::WorkerSyncRunnable(
+                               WorkerPrivate* aWorkerPrivate,
+                               already_AddRefed<nsIEventTarget> aSyncLoopTarget)
+: WorkerRunnable(aWorkerPrivate, WorkerThreadUnchangedBusyCount),
+  mSyncLoopTarget(aSyncLoopTarget)
+{
+#ifdef DEBUG
+  if (mSyncLoopTarget) {
+    mWorkerPrivate->AssertValidSyncLoop(mSyncLoopTarget);
+  }
+#endif
+}
+
+WorkerSyncRunnable::~WorkerSyncRunnable()
+{
+}
+
+bool
+WorkerSyncRunnable::DispatchInternal()
+{
+  if (mSyncLoopTarget) {
+    return NS_SUCCEEDED(mSyncLoopTarget->Dispatch(this, NS_DISPATCH_NORMAL));
+  }
+
+  return WorkerRunnable::DispatchInternal();
+}
+
+void
+MainThreadWorkerSyncRunnable::PostDispatch(JSContext* aCx,
+                                           WorkerPrivate* aWorkerPrivate,
+                                           bool aDispatchResult)
+{
+  MaybeReportMainThreadException(aCx, aDispatchResult);
+}
+
+StopSyncLoopRunnable::StopSyncLoopRunnable(
+                               WorkerPrivate* aWorkerPrivate,
+                               already_AddRefed<nsIEventTarget> aSyncLoopTarget,
+                               bool aResult)
+: WorkerSyncRunnable(aWorkerPrivate, aSyncLoopTarget), mResult(aResult)
+{
+#ifdef DEBUG
+  mWorkerPrivate->AssertValidSyncLoop(mSyncLoopTarget);
+#endif
+}
+
+NS_IMETHODIMP
+StopSyncLoopRunnable::Cancel()
+{
+  nsresult rv = Run();
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  return NS_OK;
+}
+
+bool
+StopSyncLoopRunnable::WorkerRun(JSContext* aCx,
+                                WorkerPrivate* aWorkerPrivate)
+{
+  aWorkerPrivate->AssertIsOnWorkerThread();
+  MOZ_ASSERT(mSyncLoopTarget);
+
+  nsCOMPtr<nsIEventTarget> syncLoopTarget;
+  mSyncLoopTarget.swap(syncLoopTarget);
+
+  if (!mResult) {
+    MaybeSetException(aCx);
+  }
+
+  aWorkerPrivate->StopSyncLoop(syncLoopTarget, mResult);
+  return true;
+}
+
+bool
+StopSyncLoopRunnable::DispatchInternal()
+{
+  MOZ_ASSERT(mSyncLoopTarget);
+
+  return NS_SUCCEEDED(mSyncLoopTarget->Dispatch(this, NS_DISPATCH_NORMAL));
+}
+
+void
+MainThreadStopSyncLoopRunnable::PostDispatch(JSContext* aCx,
+                                             WorkerPrivate* aWorkerPrivate,
+                                             bool aDispatchResult)
+{
+  MaybeReportMainThreadException(aCx, aDispatchResult);
+}
+
+#ifdef DEBUG
+WorkerControlRunnable::WorkerControlRunnable(WorkerPrivate* aWorkerPrivate,
+                                             TargetAndBusyBehavior aBehavior)
+: WorkerRunnable(aWorkerPrivate, aBehavior)
+{
+  MOZ_ASSERT(aWorkerPrivate);
+  MOZ_ASSERT(aBehavior == ParentThreadUnchangedBusyCount ||
+             aBehavior == WorkerThreadUnchangedBusyCount,
+             "WorkerControlRunnables should not modify the busy count");
+}
+#endif
+
+bool
+WorkerControlRunnable::DispatchInternal()
+{
+  if (mBehavior == WorkerThreadUnchangedBusyCount) {
+    return NS_SUCCEEDED(mWorkerPrivate->DispatchControlRunnable(this));
+  }
+
+  if (WorkerPrivate* parent = mWorkerPrivate->GetParent()) {
+    return NS_SUCCEEDED(parent->DispatchControlRunnable(this));
+  }
+
+  nsCOMPtr<nsIThread> mainThread = do_GetMainThread();
+  MOZ_ASSERT(mainThread);
+
+  return NS_SUCCEEDED(mainThread->Dispatch(this, NS_DISPATCH_NORMAL));
+}
+
+void
+MainThreadWorkerControlRunnable::PostDispatch(JSContext* aCx,
+                                              WorkerPrivate* aWorkerPrivate,
+                                              bool aDispatchResult)
+{
+  AssertIsOnMainThread();
+
+  if (aCx && !aDispatchResult) {
+    JS_ReportPendingException(aCx);
+  }
+}
+
+NS_IMPL_ISUPPORTS_INHERITED0(WorkerControlRunnable, WorkerRunnable)
new file mode 100644
--- /dev/null
+++ b/dom/workers/WorkerRunnable.h
@@ -0,0 +1,319 @@
+/* -*- Mode: c++; c-basic-offset: 2; indent-tabs-mode: nil; tab-width: 40 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * 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/. */
+
+#ifndef mozilla_dom_workers_workerrunnable_h__
+#define mozilla_dom_workers_workerrunnable_h__
+
+#include "Workers.h"
+
+#include "nsICancelableRunnable.h"
+
+#include "mozilla/Atomics.h"
+#include "nsISupportsImpl.h"
+
+class JSContext;
+class nsIEventTarget;
+
+BEGIN_WORKERS_NAMESPACE
+
+class WorkerPrivate;
+
+// Use this runnable to communicate from the worker to its parent or vice-versa.
+// The busy count must be taken into consideration and declared at construction
+// time.
+class WorkerRunnable : public nsICancelableRunnable
+{
+public:
+  enum TargetAndBusyBehavior {
+    // Target the main thread for top-level workers, otherwise target the
+    // WorkerThread of the worker's parent. No change to the busy count.
+    ParentThreadUnchangedBusyCount,
+
+    // Target the thread where the worker event loop runs. The busy count will
+    // be incremented before dispatching and decremented (asynchronously) after
+    // running.
+    WorkerThreadModifyBusyCount,
+
+    // Target the thread where the worker event loop runs. The busy count will
+    // not be modified in any way. Besides worker-internal runnables this is
+    // almost always the wrong choice.
+    WorkerThreadUnchangedBusyCount
+  };
+
+protected:
+  // The WorkerPrivate that this runnable is associated with.
+  WorkerPrivate* mWorkerPrivate;
+
+  // See above.
+  TargetAndBusyBehavior mBehavior;
+
+  // It's unclear whether or not Cancel() is supposed to work when called on any
+  // thread. To be safe we're using an atomic but it's likely overkill.
+  Atomic<uint32_t> mCanceled;
+
+private:
+  // Whether or not Cancel() is currently being called from inside the Run()
+  // method. Avoids infinite recursion when a subclass calls Run() from inside
+  // Cancel(). Only checked and modified on the target thread.
+  bool mCallingCancelWithinRun;
+
+public:
+  NS_DECL_THREADSAFE_ISUPPORTS
+
+  // If you override Cancel() then you'll need to either call the base class
+  // Cancel() method or override IsCanceled() so that the Run() method bails out
+  // appropriately.
+  NS_DECL_NSICANCELABLERUNNABLE
+
+  // Passing a JSContext here is required for the WorkerThreadModifyBusyCount
+  // behavior. It also guarantees that any failure (false return) will throw an
+  // exception on the given context. If a context is not passed then failures
+  // must be dealt with by the caller.
+  bool
+  Dispatch(JSContext* aCx);
+
+  // See above note about Cancel().
+  virtual bool
+  IsCanceled() const
+  {
+    return mCanceled != 0;
+  }
+
+  static WorkerRunnable*
+  FromRunnable(nsIRunnable* aRunnable);
+
+protected:
+  WorkerRunnable(WorkerPrivate* aWorkerPrivate, TargetAndBusyBehavior aBehavior)
+#ifdef DEBUG
+  ;
+#else
+  : mWorkerPrivate(aWorkerPrivate), mBehavior(aBehavior), mCanceled(0),
+    mCallingCancelWithinRun(false)
+  { }
+#endif
+
+  // This class is reference counted.
+  virtual ~WorkerRunnable()
+  { }
+
+  // By default asserts that Dispatch() is being called on the right thread
+  // (ParentThread if |mTarget| is WorkerThread, or WorkerThread otherwise).
+  // Also increments the busy count of |mWorkerPrivate| if targeting the
+  // WorkerThread.
+  virtual bool
+  PreDispatch(JSContext* aCx, WorkerPrivate* aWorkerPrivate);
+
+  // By default asserts that Dispatch() is being called on the right thread
+  // (ParentThread if |mTarget| is WorkerThread, or WorkerThread otherwise).
+  // Also reports any Dispatch() failures as an exception on |aCx|, and
+  // busy count if targeting the WorkerThread and Dispatch() failed.
+  virtual void
+  PostDispatch(JSContext* aCx, WorkerPrivate* aWorkerPrivate,
+               bool aDispatchResult);
+
+  // Must be implemented by subclasses. Called on the target thread.
+  virtual bool
+  WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate) = 0;
+
+  // By default asserts that Run() (and WorkerRun()) were called on the correct
+  // thread. Any failures (false return from WorkerRun) are reported on |aCx|.
+  // Also sends an asynchronous message to the ParentThread if the busy
+  // count was previously modified in PreDispatch().
+  virtual void
+  PostRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate, bool aRunResult);
+
+  virtual bool
+  DispatchInternal();
+
+  // Calling Run() directly is not supported. Just call Dispatch() and
+  // WorkerRun() will be called on the correct thread automatically.
+  NS_DECL_NSIRUNNABLE
+};
+
+// This runnable is used to send a message directly to a worker's sync loop.
+class WorkerSyncRunnable : public WorkerRunnable
+{
+protected:
+  nsCOMPtr<nsIEventTarget> mSyncLoopTarget;
+
+  // Passing null for aSyncLoopTarget is allowed and will result in the behavior
+  // of a normal WorkerRunnable.
+  WorkerSyncRunnable(WorkerPrivate* aWorkerPrivate,
+                     nsIEventTarget* aSyncLoopTarget);
+
+  WorkerSyncRunnable(WorkerPrivate* aWorkerPrivate,
+                     already_AddRefed<nsIEventTarget> aSyncLoopTarget);
+
+  virtual ~WorkerSyncRunnable();
+
+private:
+  virtual bool
+  DispatchInternal() MOZ_OVERRIDE;
+};
+
+// This runnable is identical to WorkerSyncRunnable except it is meant to be
+// used on the main thread only.
+class MainThreadWorkerSyncRunnable : public WorkerSyncRunnable
+{
+protected:
+  // Passing null for aSyncLoopTarget is allowed and will result in the behavior
+  // of a normal WorkerRunnable.
+  MainThreadWorkerSyncRunnable(WorkerPrivate* aWorkerPrivate,
+                               nsIEventTarget* aSyncLoopTarget)
+  : WorkerSyncRunnable(aWorkerPrivate, aSyncLoopTarget)
+  {
+    AssertIsOnMainThread();
+  }
+
+  MainThreadWorkerSyncRunnable(WorkerPrivate* aWorkerPrivate,
+                               already_AddRefed<nsIEventTarget> aSyncLoopTarget)
+  : WorkerSyncRunnable(aWorkerPrivate, aSyncLoopTarget)
+  {
+    AssertIsOnMainThread();
+  }
+
+  virtual ~MainThreadWorkerSyncRunnable()
+  { }
+
+private:
+  virtual bool
+  PreDispatch(JSContext* aCx, WorkerPrivate* aWorkerPrivate) MOZ_OVERRIDE
+  {
+    AssertIsOnMainThread();
+    return true;
+  }
+
+  virtual void
+  PostDispatch(JSContext* aCx, WorkerPrivate* aWorkerPrivate,
+               bool aDispatchResult) MOZ_OVERRIDE;
+};
+
+// This runnable is used to stop a sync loop . As sync loops keep the busy count
+// incremented as long as they run this runnable does not modify the busy count
+// in any way.
+class StopSyncLoopRunnable : public WorkerSyncRunnable
+{
+  bool mResult;
+
+public:
+  // Passing null for aSyncLoopTarget is not allowed.
+  StopSyncLoopRunnable(WorkerPrivate* aWorkerPrivate,
+                       already_AddRefed<nsIEventTarget> aSyncLoopTarget,
+                       bool aResult);
+
+  // By default StopSyncLoopRunnables cannot be canceled since they could leave
+  // a sync loop spinning forever.
+  NS_DECL_NSICANCELABLERUNNABLE
+
+protected:
+  virtual ~StopSyncLoopRunnable()
+  { }
+
+  // Called on the worker thread to set an exception on the context if mResult
+  // is false. Override if you need an exception.
+  virtual void
+  MaybeSetException(JSContext* aCx)
+  { }
+
+private:
+  virtual bool
+  WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate) MOZ_OVERRIDE;
+
+  virtual bool
+  DispatchInternal() MOZ_OVERRIDE;
+};
+
+// This runnable is identical to StopSyncLoopRunnable except it is meant to be
+// used on the main thread only.
+class MainThreadStopSyncLoopRunnable : public StopSyncLoopRunnable
+{
+public:
+  // Passing null for aSyncLoopTarget is not allowed.
+  MainThreadStopSyncLoopRunnable(
+                               WorkerPrivate* aWorkerPrivate,
+                               already_AddRefed<nsIEventTarget> aSyncLoopTarget,
+                               bool aResult)
+  : StopSyncLoopRunnable(aWorkerPrivate, aSyncLoopTarget, aResult)
+  {
+    AssertIsOnMainThread();
+  }
+
+protected:
+  virtual ~MainThreadStopSyncLoopRunnable()
+  { }
+
+private:
+  virtual bool
+  PreDispatch(JSContext* aCx, WorkerPrivate* aWorkerPrivate) MOZ_OVERRIDE
+  {
+    AssertIsOnMainThread();
+    return true;
+  }
+
+  virtual void
+  PostDispatch(JSContext* aCx, WorkerPrivate* aWorkerPrivate,
+               bool aDispatchResult) MOZ_OVERRIDE;
+};
+
+// This runnable is processed as soon as it is received by the worker,
+// potentially running before previously queued runnables and perhaps even with
+// other JS code executing on the stack. These runnables must not alter the
+// state of the JS runtime and should only twiddle state values. The busy count
+// is never modified.
+class WorkerControlRunnable : public WorkerRunnable
+{
+  friend class WorkerPrivate;
+
+protected:
+  WorkerControlRunnable(WorkerPrivate* aWorkerPrivate,
+                        TargetAndBusyBehavior aBehavior)
+#ifdef DEBUG
+  ;
+#else
+  : WorkerRunnable(aWorkerPrivate, aBehavior)
+  { }
+#endif
+
+  virtual ~WorkerControlRunnable()
+  { }
+
+public:
+  NS_DECL_ISUPPORTS_INHERITED
+
+private:
+  virtual bool
+  DispatchInternal() MOZ_OVERRIDE;
+
+  // Should only be called by WorkerPrivate::DoRunLoop.
+  using WorkerRunnable::Cancel;
+};
+
+// A convenience class for WorkerControlRunnables that originate on the main
+// thread.
+class MainThreadWorkerControlRunnable : public WorkerControlRunnable
+{
+protected:
+  MainThreadWorkerControlRunnable(WorkerPrivate* aWorkerPrivate)
+  : WorkerControlRunnable(aWorkerPrivate, WorkerThreadUnchangedBusyCount)
+  { }
+
+  virtual ~MainThreadWorkerControlRunnable()
+  { }
+
+  virtual bool
+  PreDispatch(JSContext* aCx, WorkerPrivate* aWorkerPrivate) MOZ_OVERRIDE
+  {
+    AssertIsOnMainThread();
+    return true;
+  }
+
+  virtual void
+  PostDispatch(JSContext* aCx, WorkerPrivate* aWorkerPrivate,
+               bool aDispatchResult) MOZ_OVERRIDE;
+};
+
+END_WORKERS_NAMESPACE
+
+#endif // mozilla_dom_workers_workerrunnable_h__
--- a/dom/workers/Workers.h
+++ b/dom/workers/Workers.h
@@ -179,49 +179,60 @@ void
 CancelWorkersForWindow(nsPIDOMWindow* aWindow);
 
 void
 SuspendWorkersForWindow(nsPIDOMWindow* aWindow);
 
 void
 ResumeWorkersForWindow(nsPIDOMWindow* aWindow);
 
-class WorkerTask {
-public:
-    NS_INLINE_DECL_THREADSAFE_REFCOUNTING(WorkerTask)
+class WorkerTask
+{
+protected:
+  WorkerTask()
+  { }
 
-    virtual ~WorkerTask() { }
+  virtual ~WorkerTask()
+  { }
 
-    virtual bool RunTask(JSContext* aCx) = 0;
+public:
+  NS_INLINE_DECL_THREADSAFE_REFCOUNTING(WorkerTask)
+
+  virtual bool
+  RunTask(JSContext* aCx) = 0;
 };
 
-class WorkerCrossThreadDispatcher {
+class WorkerCrossThreadDispatcher
+{
+   friend class WorkerPrivate;
+
+  // Must be acquired *before* the WorkerPrivate's mutex, when they're both
+  // held.
+  Mutex mMutex;
+  WorkerPrivate* mWorkerPrivate;
+
+private:
+  // Only created by WorkerPrivate.
+  WorkerCrossThreadDispatcher(WorkerPrivate* aWorkerPrivate);
+
+  // Only called by WorkerPrivate.
+  void
+  Forget()
+  {
+    MutexAutoLock lock(mMutex);
+    mWorkerPrivate = nullptr;
+  }
+
 public:
   NS_INLINE_DECL_THREADSAFE_REFCOUNTING(WorkerCrossThreadDispatcher)
 
-  WorkerCrossThreadDispatcher(WorkerPrivate* aPrivate) :
-    mMutex("WorkerCrossThreadDispatcher"), mPrivate(aPrivate) {}
-  void Forget()
-  {
-    mozilla::MutexAutoLock lock(mMutex);
-    mPrivate = nullptr;
-  }
-
-  /**
-   * Generically useful function for running a bit of C++ code on the worker
-   * thread.
-   */
-  bool PostTask(WorkerTask* aTask);
-
-protected:
-  friend class WorkerPrivate;
-
-  // Must be acquired *before* the WorkerPrivate's mutex, when they're both held.
-  mozilla::Mutex mMutex;
-  WorkerPrivate* mPrivate;
+  // Generically useful function for running a bit of C++ code on the worker
+  // thread.
+  bool
+  PostTask(WorkerTask* aTask);
 };
 
 WorkerCrossThreadDispatcher*
 GetWorkerCrossThreadDispatcher(JSContext* aCx, jsval aWorker);
 
 // Random unique constant to facilitate JSPrincipal debugging
 const uint32_t kJSPrincipalsDebugToken = 0x7e2df9d2;
 
--- a/dom/workers/XMLHttpRequest.cpp
+++ b/dom/workers/XMLHttpRequest.cpp
@@ -9,31 +9,31 @@
 #include "nsIDOMEventListener.h"
 #include "nsIDOMProgressEvent.h"
 #include "nsIRunnable.h"
 #include "nsIVariant.h"
 #include "nsIXMLHttpRequest.h"
 #include "nsIXPConnect.h"
 
 #include "jsfriendapi.h"
+#include "mozilla/ArrayUtils.h"
+#include "mozilla/dom/Exceptions.h"
+#include "nsComponentManagerUtils.h"
 #include "nsContentUtils.h"
 #include "nsCxPusher.h"
 #include "nsEventDispatcher.h"
 #include "nsJSUtils.h"
 #include "nsThreadUtils.h"
 
-#include "mozilla/dom/Exceptions.h"
 #include "File.h"
 #include "RuntimeService.h"
 #include "WorkerPrivate.h"
+#include "WorkerRunnable.h"
 #include "XMLHttpRequestUpload.h"
 
-#include "mozilla/Attributes.h"
-#include "nsComponentManagerUtils.h"
-
 using namespace mozilla;
 
 using namespace mozilla::dom;
 USING_WORKERS_NAMESPACE
 
 // XXX Need to figure this out...
 #define UNCATCHABLE_EXCEPTION NS_ERROR_OUT_OF_MEMORY
 
@@ -91,16 +91,18 @@ public:
 
   // XHR Params:
   bool mMozAnon;
   bool mMozSystem;
 
   // Only touched on the main thread.
   nsRefPtr<nsXMLHttpRequest> mXHR;
   nsCOMPtr<nsIXMLHttpRequestUpload> mXHRUpload;
+  nsCOMPtr<nsIEventTarget> mSyncLoopTarget;
+  nsCOMPtr<nsIEventTarget> mSyncEventResponseTarget;
   uint32_t mInnerEventStreamId;
   uint32_t mInnerChannelId;
   uint32_t mOutstandingSendCount;
 
   // Only touched on the worker thread.
   uint32_t mOuterEventStreamId;
   uint32_t mOuterChannelId;
   uint64_t mLastLoaded;
@@ -109,95 +111,38 @@ public:
   uint64_t mLastUploadTotal;
   bool mIsSyncXHR;
   bool mLastLengthComputable;
   bool mLastUploadLengthComputable;
   bool mSeenLoadStart;
   bool mSeenUploadLoadStart;
 
   // Only touched on the main thread.
-  uint32_t mSyncQueueKey;
-  uint32_t mSyncEventResponseSyncQueueKey;
   bool mUploadEventListenersAttached;
   bool mMainThreadSeenLoadStart;
   bool mInOpen;
 
 public:
-  NS_DECL_THREADSAFE_ISUPPORTS
-  NS_DECL_NSIDOMEVENTLISTENER
-
   Proxy(XMLHttpRequest* aXHRPrivate, bool aMozAnon, bool aMozSystem)
   : mWorkerPrivate(nullptr), mXMLHttpRequestPrivate(aXHRPrivate),
     mMozAnon(aMozAnon), mMozSystem(aMozSystem),
     mInnerEventStreamId(0), mInnerChannelId(0), mOutstandingSendCount(0),
     mOuterEventStreamId(0), mOuterChannelId(0), mLastLoaded(0), mLastTotal(0),
     mLastUploadLoaded(0), mLastUploadTotal(0), mIsSyncXHR(false),
     mLastLengthComputable(false), mLastUploadLengthComputable(false),
     mSeenLoadStart(false), mSeenUploadLoadStart(false),
-    mSyncQueueKey(UINT32_MAX),
-    mSyncEventResponseSyncQueueKey(UINT32_MAX),
     mUploadEventListenersAttached(false), mMainThreadSeenLoadStart(false),
     mInOpen(false)
   { }
 
-  ~Proxy()
-  {
-    NS_ASSERTION(!mXHR, "Still have an XHR object attached!");
-    NS_ASSERTION(!mXHRUpload, "Still have an XHR upload object attached!");
-    NS_ASSERTION(!mOutstandingSendCount, "We're dying too early!");
-  }
+  NS_DECL_THREADSAFE_ISUPPORTS
+  NS_DECL_NSIDOMEVENTLISTENER
 
   bool
-  Init()
-  {
-    AssertIsOnMainThread();
-    NS_ASSERTION(mWorkerPrivate, "Must have a worker here!");
-
-    if (!mXHR) {
-      nsPIDOMWindow* ownerWindow = mWorkerPrivate->GetWindow();
-      if (ownerWindow) {
-        ownerWindow = ownerWindow->GetOuterWindow();
-        if (!ownerWindow) {
-          NS_ERROR("No outer window?!");
-          return false;
-        }
-
-        nsPIDOMWindow* innerWindow = ownerWindow->GetCurrentInnerWindow();
-        if (mWorkerPrivate->GetWindow() != innerWindow) {
-          NS_WARNING("Window has navigated, cannot create XHR here.");
-          return false;
-        }
-      }
-
-      mXHR = new nsXMLHttpRequest();
-
-      nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(ownerWindow);
-      if (NS_FAILED(mXHR->Init(mWorkerPrivate->GetPrincipal(),
-                               mWorkerPrivate->GetScriptContext(),
-                               global, mWorkerPrivate->GetBaseURI()))) {
-        mXHR = nullptr;
-        return false;
-      }
-
-      mXHR->SetParameters(mMozAnon, mMozSystem);
-
-      if (NS_FAILED(mXHR->GetUpload(getter_AddRefs(mXHRUpload)))) {
-        mXHR = nullptr;
-        return false;
-      }
-
-      if (!AddRemoveEventListeners(false, true)) {
-        mXHRUpload = nullptr;
-        mXHR = nullptr;
-        return false;
-      }
-    }
-
-    return true;
-  }
+  Init();
 
   void
   Teardown();
 
   bool
   AddRemoveEventListeners(bool aUpload, bool aAdd);
 
   void
@@ -205,38 +150,38 @@ public:
   {
     AssertIsOnMainThread();
 
     if (mUploadEventListenersAttached) {
       AddRemoveEventListeners(true, false);
     }
   }
 
-  uint32_t
-  GetSyncQueueKey()
-  {
-    AssertIsOnMainThread();
-    return mSyncEventResponseSyncQueueKey == UINT32_MAX ?
-           mSyncQueueKey :
-           mSyncEventResponseSyncQueueKey;
-  }
-
-  bool
-  EventsBypassSyncQueue()
+  already_AddRefed<nsIEventTarget>
+  GetEventTarget()
   {
     AssertIsOnMainThread();
 
-    return mSyncQueueKey == UINT32_MAX &&
-           mSyncEventResponseSyncQueueKey == UINT32_MAX;
+    nsCOMPtr<nsIEventTarget> target = mSyncEventResponseTarget ?
+                                      mSyncEventResponseTarget :
+                                      mSyncLoopTarget;
+    return target.forget();
+  }
+
+private:
+  ~Proxy()
+  {
+    MOZ_ASSERT(!mXHR);
+    MOZ_ASSERT(!mXHRUpload);
+    MOZ_ASSERT(!mOutstandingSendCount);
   }
 };
 
 END_WORKERS_NAMESPACE
 
-
 namespace {
 
 inline void
 ConvertResponseTypeToString(XMLHttpRequestResponseType aType,
                             nsString& aString)
 {
   using namespace
     mozilla::dom::XMLHttpRequestResponseTypeValues;
@@ -254,17 +199,17 @@ ConvertStringToResponseType(const nsAStr
     mozilla::dom::XMLHttpRequestResponseTypeValues;
 
   for (size_t index = 0; index < ArrayLength(strings) - 1; index++) {
     if (aString.EqualsASCII(strings[index].value, strings[index].length)) {
       return static_cast<XMLHttpRequestResponseType>(index);
     }
   }
 
-  MOZ_CRASH("Don't know anything about this response type!");
+  MOZ_ASSUME_UNREACHABLE("Don't know anything about this response type!");
 }
 
 enum
 {
   STRING_abort = 0,
   STRING_error,
   STRING_load,
   STRING_loadstart,
@@ -274,248 +219,196 @@ enum
   STRING_loadend,
 
   STRING_COUNT,
 
   STRING_LAST_XHR = STRING_loadend,
   STRING_LAST_EVENTTARGET = STRING_timeout
 };
 
-JS_STATIC_ASSERT(STRING_LAST_XHR >= STRING_LAST_EVENTTARGET);
-JS_STATIC_ASSERT(STRING_LAST_XHR == STRING_COUNT - 1);
+static_assert(STRING_LAST_XHR >= STRING_LAST_EVENTTARGET, "Bad string setup!");
+static_assert(STRING_LAST_XHR == STRING_COUNT - 1, "Bad string setup!");
 
 const char* const sEventStrings[] = {
   // nsIXMLHttpRequestEventTarget event types, supported by both XHR and Upload.
   "abort",
   "error",
   "load",
   "loadstart",
   "progress",
   "timeout",
 
   // nsIXMLHttpRequest event types, supported only by XHR.
   "readystatechange",
   "loadend",
 };
 
-JS_STATIC_ASSERT(JS_ARRAY_LENGTH(sEventStrings) == STRING_COUNT);
+static_assert(MOZ_ARRAY_LENGTH(sEventStrings) == STRING_COUNT,
+              "Bad string count!");
 
-class MainThreadProxyRunnable : public MainThreadSyncRunnable
+class MainThreadProxyRunnable : public MainThreadWorkerSyncRunnable
 {
 protected:
   nsRefPtr<Proxy> mProxy;
 
-public:
-  MainThreadProxyRunnable(WorkerPrivate* aWorkerPrivate,
-                          ClearingBehavior aClearingBehavior, Proxy* aProxy)
-  : MainThreadSyncRunnable(aWorkerPrivate, aClearingBehavior,
-                           aProxy->GetSyncQueueKey(),
-                           aProxy->EventsBypassSyncQueue()),
+  MainThreadProxyRunnable(WorkerPrivate* aWorkerPrivate, Proxy* aProxy)
+  : MainThreadWorkerSyncRunnable(aWorkerPrivate, aProxy->GetEventTarget()),
     mProxy(aProxy)
+  {
+    MOZ_ASSERT(aProxy);
+  }
+
+  virtual ~MainThreadProxyRunnable()
   { }
 };
 
-class XHRUnpinRunnable : public WorkerControlRunnable
+class XHRUnpinRunnable MOZ_FINAL : public MainThreadWorkerControlRunnable
 {
   XMLHttpRequest* mXMLHttpRequestPrivate;
 
 public:
   XHRUnpinRunnable(WorkerPrivate* aWorkerPrivate,
                    XMLHttpRequest* aXHRPrivate)
-  : WorkerControlRunnable(aWorkerPrivate, WorkerThread, UnchangedBusyCount),
+  : MainThreadWorkerControlRunnable(aWorkerPrivate),
     mXMLHttpRequestPrivate(aXHRPrivate)
-  { }
-
-  bool
-  PreDispatch(JSContext* aCx, WorkerPrivate* aWorkerPrivate)
   {
-    AssertIsOnMainThread();
-    return true;
+    MOZ_ASSERT(aXHRPrivate);
   }
 
-  void
-  PostDispatch(JSContext* aCx, WorkerPrivate* aWorkerPrivate,
-               bool aDispatchResult)
-  {
-    AssertIsOnMainThread();
-  }
+private:
+  ~XHRUnpinRunnable()
+  { }
 
   bool
   WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate)
   {
     mXMLHttpRequestPrivate->Unpin();
 
     return true;
   }
 };
 
-class AsyncTeardownRunnable : public nsRunnable
+class AsyncTeardownRunnable MOZ_FINAL : public nsRunnable
 {
   nsRefPtr<Proxy> mProxy;
 
 public:
   AsyncTeardownRunnable(Proxy* aProxy)
+  : mProxy(aProxy)
   {
-    mProxy = aProxy;
-    NS_ASSERTION(mProxy, "Null proxy!");
+    MOZ_ASSERT(aProxy);
   }
 
-  NS_IMETHOD Run()
+  NS_DECL_ISUPPORTS_INHERITED
+
+private:
+  ~AsyncTeardownRunnable()
+  { }
+
+  NS_IMETHOD
+  Run() MOZ_OVERRIDE
   {
     AssertIsOnMainThread();
 
     mProxy->Teardown();
     mProxy = nullptr;
 
     return NS_OK;
   }
 };
 
-class LoadStartDetectionRunnable MOZ_FINAL : public nsIRunnable,
+class LoadStartDetectionRunnable MOZ_FINAL : public nsRunnable,
                                              public nsIDOMEventListener
 {
   WorkerPrivate* mWorkerPrivate;
   nsRefPtr<Proxy> mProxy;
   nsRefPtr<nsXMLHttpRequest> mXHR;
   XMLHttpRequest* mXMLHttpRequestPrivate;
   nsString mEventType;
+  uint32_t mChannelId;
   bool mReceivedLoadStart;
-  uint32_t mChannelId;
 
-  class ProxyCompleteRunnable : public MainThreadProxyRunnable
+  class ProxyCompleteRunnable MOZ_FINAL : public MainThreadProxyRunnable
   {
     XMLHttpRequest* mXMLHttpRequestPrivate;
     uint32_t mChannelId;
 
   public:
     ProxyCompleteRunnable(WorkerPrivate* aWorkerPrivate, Proxy* aProxy,
                           XMLHttpRequest* aXHRPrivate, uint32_t aChannelId)
-    : MainThreadProxyRunnable(aWorkerPrivate, RunWhenClearing, aProxy),
+    : MainThreadProxyRunnable(aWorkerPrivate, aProxy),
       mXMLHttpRequestPrivate(aXHRPrivate), mChannelId(aChannelId)
     { }
 
-    bool
-    PreDispatch(JSContext* aCx, WorkerPrivate* aWorkerPrivate)
-    {
-      AssertIsOnMainThread();
-      return true;
-    }
+  private:
+    ~ProxyCompleteRunnable()
+    { }
 
-    void
-    PostDispatch(JSContext* aCx, WorkerPrivate* aWorkerPrivate,
-                 bool aDispatchResult)
-    {
-      AssertIsOnMainThread();
-    }
-
-    bool
-    WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate)
+    virtual bool
+    WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate) MOZ_OVERRIDE
     {
       if (mChannelId != mProxy->mOuterChannelId) {
         // Threads raced, this event is now obsolete.
         return true;
       }
 
-      if (mSyncQueueKey != UINT32_MAX) {
-        aWorkerPrivate->StopSyncLoop(mSyncQueueKey, true);
+      if (mSyncLoopTarget) {
+        aWorkerPrivate->StopSyncLoop(mSyncLoopTarget, true);
       }
 
       mXMLHttpRequestPrivate->Unpin();
 
       return true;
     }
+
+    NS_IMETHOD
+    Cancel() MOZ_OVERRIDE
+    {
+      // This must run!
+      nsresult rv = MainThreadProxyRunnable::Cancel();
+      nsresult rv2 = Run();
+      return NS_FAILED(rv) ? rv : rv2;
+    }
   };
 
 public:
-  NS_DECL_ISUPPORTS
-
   LoadStartDetectionRunnable(Proxy* aProxy, XMLHttpRequest* aXHRPrivate)
   : mWorkerPrivate(aProxy->mWorkerPrivate), mProxy(aProxy), mXHR(aProxy->mXHR),
-    mXMLHttpRequestPrivate(aXHRPrivate), mReceivedLoadStart(false),
-    mChannelId(mProxy->mInnerChannelId)
+    mXMLHttpRequestPrivate(aXHRPrivate), mChannelId(mProxy->mInnerChannelId),
+    mReceivedLoadStart(false)
   {
     AssertIsOnMainThread();
     mEventType.AssignWithConversion(sEventStrings[STRING_loadstart]);
   }
 
-  ~LoadStartDetectionRunnable()
-  {
-    AssertIsOnMainThread();
-  }
+  NS_DECL_ISUPPORTS_INHERITED
+  NS_DECL_NSIRUNNABLE
+  NS_DECL_NSIDOMEVENTLISTENER
 
   bool
   RegisterAndDispatch()
   {
     AssertIsOnMainThread();
 
     if (NS_FAILED(mXHR->AddEventListener(mEventType, this, false, false, 2))) {
       NS_WARNING("Failed to add event listener!");
       return false;
     }
 
     return NS_SUCCEEDED(NS_DispatchToCurrentThread(this));
   }
 
-  NS_IMETHOD
-  Run()
+private:
+  ~LoadStartDetectionRunnable()
   {
     AssertIsOnMainThread();
-
-    if (NS_FAILED(mXHR->RemoveEventListener(mEventType, this, false))) {
-      NS_WARNING("Failed to remove event listener!");
     }
-
-    if (!mReceivedLoadStart) {
-      if (mProxy->mOutstandingSendCount > 1) {
-        mProxy->mOutstandingSendCount--;
-      } else if (mProxy->mOutstandingSendCount == 1) {
-        mProxy->Reset();
-
-        nsRefPtr<ProxyCompleteRunnable> runnable =
-          new ProxyCompleteRunnable(mWorkerPrivate, mProxy,
-                                    mXMLHttpRequestPrivate,
-                                    mChannelId);
-        if (runnable->Dispatch(nullptr)) {
-          mProxy->mWorkerPrivate = nullptr;
-          mProxy->mOutstandingSendCount--;
-        }
-      }
-    }
-
-    mProxy = nullptr;
-    mXHR = nullptr;
-    mXMLHttpRequestPrivate = nullptr;
-    return NS_OK;
-  }
-
-  NS_IMETHOD
-  HandleEvent(nsIDOMEvent* aEvent)
-  {
-    AssertIsOnMainThread();
-
-#ifdef DEBUG
-    {
-      nsString type;
-      if (NS_SUCCEEDED(aEvent->GetType(type))) {
-        NS_ASSERTION(type == mEventType, "Unexpected event type!");
-      }
-      else {
-        NS_WARNING("Failed to get event type!");
-      }
-    }
-#endif
-
-    mReceivedLoadStart = true;
-    return NS_OK;
-  }
 };
 
-NS_IMPL_ISUPPORTS2(LoadStartDetectionRunnable, nsIRunnable, nsIDOMEventListener)
-
-class EventRunnable : public MainThreadProxyRunnable
+class EventRunnable MOZ_FINAL : public MainThreadProxyRunnable
 {
   nsString mType;
   nsString mResponseType;
   JSAutoStructuredCloneBuffer mResponseBuffer;
   nsTArray<nsCOMPtr<nsISupports> > mClonedObjects;
   JS::Heap<JS::Value> mResponse;
   nsString mResponseText;
   nsCString mStatusText;
@@ -527,524 +420,321 @@ class EventRunnable : public MainThreadP
   bool mUploadEvent;
   bool mProgressEvent;
   bool mLengthComputable;
   nsresult mResponseTextResult;
   nsresult mStatusResult;
   nsresult mResponseResult;
 
 public:
-  EventRunnable(Proxy* aProxy, bool aUploadEvent, const nsString& aType,
-                bool aLengthComputable, uint64_t aLoaded, uint64_t aTotal)
-  : MainThreadProxyRunnable(aProxy->mWorkerPrivate, SkipWhenClearing, aProxy),
-    mType(aType), mResponse(JSVAL_VOID), mLoaded(aLoaded), mTotal(aTotal),
-    mEventStreamId(aProxy->mInnerEventStreamId), mStatus(0), mReadyState(0),
-    mUploadEvent(aUploadEvent), mProgressEvent(true),
-    mLengthComputable(aLengthComputable), mResponseTextResult(NS_OK),
-    mStatusResult(NS_OK), mResponseResult(NS_OK)
-  { }
-
-  EventRunnable(Proxy* aProxy, bool aUploadEvent, const nsString& aType)
-  : MainThreadProxyRunnable(aProxy->mWorkerPrivate, SkipWhenClearing, aProxy),
-    mType(aType), mResponse(JSVAL_VOID), mLoaded(0), mTotal(0),
-    mEventStreamId(aProxy->mInnerEventStreamId), mStatus(0), mReadyState(0),
-    mUploadEvent(aUploadEvent), mProgressEvent(false), mLengthComputable(0),
-    mResponseTextResult(NS_OK), mStatusResult(NS_OK), mResponseResult(NS_OK)
-  { }
-
-  bool
-  PreDispatch(JSContext* aCx, WorkerPrivate* aWorkerPrivate)
-  {
-    nsRefPtr<nsXMLHttpRequest>& xhr = mProxy->mXHR;
-    NS_ASSERTION(xhr, "Must have an XHR here!");
-
-    if (NS_FAILED(xhr->GetResponseType(mResponseType))) {
-      NS_ERROR("This should never fail!");
-    }
-
-    mResponseTextResult = xhr->GetResponseText(mResponseText);
-    if (NS_SUCCEEDED(mResponseTextResult)) {
-      mResponseResult = mResponseTextResult;
-      if (mResponseText.IsVoid()) {
-        mResponse = JSVAL_NULL;
-      }
-    }
-    else {
-      JS::Rooted<JS::Value> response(aCx);
-      mResponseResult = xhr->GetResponse(aCx, response.address());
-      if (NS_SUCCEEDED(mResponseResult)) {
-        if (JSVAL_IS_UNIVERSAL(response)) {
-          mResponse = response;
-        }
-        else {
-          // Anything subject to GC must be cloned.
-          JSStructuredCloneCallbacks* callbacks =
-            aWorkerPrivate->IsChromeWorker() ?
-            ChromeWorkerStructuredCloneCallbacks(true) :
-            WorkerStructuredCloneCallbacks(true);
-
-          nsTArray<nsCOMPtr<nsISupports> > clonedObjects;
-
-          if (mResponseBuffer.write(aCx, response, callbacks, &clonedObjects)) {
-            mClonedObjects.SwapElements(clonedObjects);
-          }
-          else {
-            NS_WARNING("Failed to clone response!");
-            mResponseResult = NS_ERROR_DOM_DATA_CLONE_ERR;
-          }
-        }
-      }
-    }
-
-    mStatusResult = xhr->GetStatus(&mStatus);
-
-    xhr->GetStatusText(mStatusText);
-
-    mReadyState = xhr->ReadyState();
-
-    return true;
-  }
-
   class StateDataAutoRooter : private JS::CustomAutoRooter
   {
+    XMLHttpRequest::StateData* mStateData;
+    js::SkipRoot mSkip;
+    MOZ_DECL_USE_GUARD_OBJECT_NOTIFIER
+
   public:
     explicit StateDataAutoRooter(JSContext* aCx, XMLHttpRequest::StateData* aData
                                  MOZ_GUARD_OBJECT_NOTIFIER_PARAM)
     : CustomAutoRooter(aCx), mStateData(aData), mSkip(aCx, mStateData)
     {
       MOZ_GUARD_OBJECT_NOTIFIER_INIT;
     }
 
   private:
     virtual void trace(JSTracer* aTrc)
     {
       JS_CallHeapValueTracer(aTrc, &mStateData->mResponse,
                              "XMLHttpRequest::StateData::mResponse");
     }
-
-    XMLHttpRequest::StateData* mStateData;
-    js::SkipRoot mSkip;
-    MOZ_DECL_USE_GUARD_OBJECT_NOTIFIER
   };
 
-  bool
-  WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate)
-  {
-    if (mEventStreamId != mProxy->mOuterEventStreamId) {
-      // Threads raced, this event is now obsolete.
-      return true;
-    }
-
-    if (!mProxy->mXMLHttpRequestPrivate) {
-      // Object was finalized, bail.
-      return true;
-    }
-
-    if (mType.EqualsASCII(sEventStrings[STRING_loadstart])) {
-      if (mUploadEvent) {
-        mProxy->mSeenUploadLoadStart = true;
-      }
-      else {
-        mProxy->mSeenLoadStart = true;
-      }
-    }
-    else if (mType.EqualsASCII(sEventStrings[STRING_loadend])) {
-      if (mUploadEvent) {
-        mProxy->mSeenUploadLoadStart = false;
-      }
-      else {
-        mProxy->mSeenLoadStart = false;
-      }
-    }
-    else if (mType.EqualsASCII(sEventStrings[STRING_abort])) {
-      if ((mUploadEvent && !mProxy->mSeenUploadLoadStart) ||
-          (!mUploadEvent && !mProxy->mSeenLoadStart)) {
-        // We've already dispatched premature abort events.
-        return true;
-      }
-    }
-    else if (mType.EqualsASCII(sEventStrings[STRING_readystatechange])) {
-      if (mReadyState == 4 && !mUploadEvent && !mProxy->mSeenLoadStart) {
-        // We've already dispatched premature abort events.
-        return true;
-      }
-    }
-
-    if (mProgressEvent) {
-      // Cache these for premature abort events.
-      if (mUploadEvent) {
-        mProxy->mLastUploadLengthComputable = mLengthComputable;
-        mProxy->mLastUploadLoaded = mLoaded;
-        mProxy->mLastUploadTotal = mTotal;
-      }
-      else {
-        mProxy->mLastLengthComputable = mLengthComputable;
-        mProxy->mLastLoaded = mLoaded;
-        mProxy->mLastTotal = mTotal;
-      }
-    }
-
-    nsAutoPtr<XMLHttpRequest::StateData> state(new XMLHttpRequest::StateData());
-    StateDataAutoRooter rooter(aCx, state);
-
-    state->mResponseTextResult = mResponseTextResult;
-    state->mResponseText = mResponseText;
-
-    if (NS_SUCCEEDED(mResponseTextResult)) {
-      MOZ_ASSERT(JSVAL_IS_VOID(mResponse) || JSVAL_IS_NULL(mResponse));
-      state->mResponseResult = mResponseTextResult;
-      state->mResponse = mResponse;
-    }
-    else {
-      state->mResponseResult = mResponseResult;
-
-      if (NS_SUCCEEDED(mResponseResult)) {
-        if (mResponseBuffer.data()) {
-          MOZ_ASSERT(JSVAL_IS_VOID(mResponse));
-
-          JSAutoStructuredCloneBuffer responseBuffer;
-          mResponseBuffer.swap(responseBuffer);
+  EventRunnable(Proxy* aProxy, bool aUploadEvent, const nsString& aType,
+                bool aLengthComputable, uint64_t aLoaded, uint64_t aTotal)
+  : MainThreadProxyRunnable(aProxy->mWorkerPrivate, aProxy), mType(aType),
+    mResponse(JSVAL_VOID), mLoaded(aLoaded), mTotal(aTotal),
+    mEventStreamId(aProxy->mInnerEventStreamId), mStatus(0), mReadyState(0),
+    mUploadEvent(aUploadEvent), mProgressEvent(true),
+    mLengthComputable(aLengthComputable), mResponseTextResult(NS_OK),
+    mStatusResult(NS_OK), mResponseResult(NS_OK)
+  { }
 
-          JSStructuredCloneCallbacks* callbacks =
-            aWorkerPrivate->IsChromeWorker() ?
-            ChromeWorkerStructuredCloneCallbacks(false) :
-            WorkerStructuredCloneCallbacks(false);
-
-          nsTArray<nsCOMPtr<nsISupports> > clonedObjects;
-          clonedObjects.SwapElements(mClonedObjects);
-
-          JS::Rooted<JS::Value> response(aCx);
-          if (!responseBuffer.read(aCx, &response, callbacks, &clonedObjects)) {
-            return false;
-          }
-
-          state->mResponse = response;
-        }
-        else {
-          state->mResponse = mResponse;
-        }
-      }
-    }
-
-    state->mStatusResult = mStatusResult;
-    state->mStatus = mStatus;
-
-    state->mStatusText = mStatusText;
-
-    state->mReadyState = mReadyState;
-
-    XMLHttpRequest* xhr = mProxy->mXMLHttpRequestPrivate;
-    xhr->UpdateState(*state);
-
-    if (mUploadEvent && !xhr->GetUploadObjectNoCreate()) {
-      return true;
-    }
-
-    JS::Rooted<JSString*> type(aCx, JS_NewUCStringCopyN(aCx, mType.get(), mType.Length()));
-    if (!type) {
-      return false;
-    }
+  EventRunnable(Proxy* aProxy, bool aUploadEvent, const nsString& aType)
+  : MainThreadProxyRunnable(aProxy->mWorkerPrivate, aProxy), mType(aType),
+    mResponse(JSVAL_VOID), mLoaded(0), mTotal(0),
+    mEventStreamId(aProxy->mInnerEventStreamId), mStatus(0), mReadyState(0),
+    mUploadEvent(aUploadEvent), mProgressEvent(false), mLengthComputable(0),
+    mResponseTextResult(NS_OK), mStatusResult(NS_OK), mResponseResult(NS_OK)
+  { }
 
-    nsXHREventTarget* target;
-    if (mUploadEvent) {
-      target = xhr->GetUploadObjectNoCreate();
-    }
-    else {
-      target = xhr;
-    }
-
-    MOZ_ASSERT(target);
-
-    nsCOMPtr<nsIDOMEvent> event;
-    if (mProgressEvent) {
-      NS_NewDOMProgressEvent(getter_AddRefs(event), target, nullptr, nullptr);
-      nsCOMPtr<nsIDOMProgressEvent> progress = do_QueryInterface(event);
+private:
+  ~EventRunnable()
+  { }
 
-      if (progress) {
-        progress->InitProgressEvent(mType, false, false, mLengthComputable,
-                                    mLoaded, mTotal);
-      }
-    }
-    else {
-      NS_NewDOMEvent(getter_AddRefs(event), target, nullptr, nullptr);
-
-      if (event) {
-        event->InitEvent(mType, false, false);
-      }
-    }
+  virtual bool
+  PreDispatch(JSContext* aCx, WorkerPrivate* aWorkerPrivate) MOZ_OVERRIDE;
 
-    if (!event) {
-      return false;
-    }
-
-    event->SetTrusted(true);
-
-    target->DispatchDOMEvent(nullptr, event, nullptr, nullptr);
-
-    // After firing the event set mResponse to JSVAL_NULL for chunked response
-    // types.
-    if (StringBeginsWith(mResponseType, NS_LITERAL_STRING("moz-chunked-"))) {
-      xhr->NullResponseText();
-    }
-
-    return true;
-  }
+  virtual bool
+  WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate) MOZ_OVERRIDE;
 };
 
 class WorkerThreadProxySyncRunnable : public nsRunnable
 {
 protected:
   WorkerPrivate* mWorkerPrivate;
   nsRefPtr<Proxy> mProxy;
-  uint32_t mSyncQueueKey;
+  nsCOMPtr<nsIEventTarget> mSyncLoopTarget;
 
 private:
-  class ResponseRunnable : public MainThreadProxyRunnable
+  class ResponseRunnable MOZ_FINAL: public MainThreadStopSyncLoopRunnable
   {
-    uint32_t mSyncQueueKey;
+    nsRefPtr<Proxy> mProxy;
     nsresult mErrorCode;
 
   public:
     ResponseRunnable(WorkerPrivate* aWorkerPrivate, Proxy* aProxy,
-                     uint32_t aSyncQueueKey, nsresult aErrorCode)
-    : MainThreadProxyRunnable(aWorkerPrivate, SkipWhenClearing, aProxy),
-      mSyncQueueKey(aSyncQueueKey), mErrorCode(aErrorCode)
+                     nsresult aErrorCode)
+    : MainThreadStopSyncLoopRunnable(aWorkerPrivate, aProxy->GetEventTarget(),
+                                     NS_SUCCEEDED(aErrorCode)),
+      mProxy(aProxy), mErrorCode(aErrorCode)
     {
-      NS_ASSERTION(aProxy, "Don't hand me a null proxy!");
+      MOZ_ASSERT(aProxy);
     }
 
-    bool
-    WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate)
+  private:
+    ~ResponseRunnable()
+    { }
+
+    virtual void
+    MaybeSetException(JSContext* aCx) MOZ_OVERRIDE
     {
-      if (NS_FAILED(mErrorCode)) {
+      MOZ_ASSERT(NS_FAILED(mErrorCode));
+
         Throw(aCx, mErrorCode);
-        aWorkerPrivate->StopSyncLoop(mSyncQueueKey, false);
       }
-      else {
-        aWorkerPrivate->StopSyncLoop(mSyncQueueKey, true);
-      }
-
-      return true;
-    }
   };
 
 public:
   WorkerThreadProxySyncRunnable(WorkerPrivate* aWorkerPrivate, Proxy* aProxy)
-  : mWorkerPrivate(aWorkerPrivate), mProxy(aProxy), mSyncQueueKey(0)
+  : mWorkerPrivate(aWorkerPrivate), mProxy(aProxy)
   {
-    mWorkerPrivate->AssertIsOnWorkerThread();
-    NS_ASSERTION(aProxy, "Don't hand me a null proxy!");
+    MOZ_ASSERT(aWorkerPrivate);
+    MOZ_ASSERT(aProxy);
+    aWorkerPrivate->AssertIsOnWorkerThread();
   }
 
+  NS_DECL_ISUPPORTS_INHERITED
+
   bool
   Dispatch(JSContext* aCx)
   {
     mWorkerPrivate->AssertIsOnWorkerThread();
 
     AutoSyncLoopHolder syncLoop(mWorkerPrivate);
-    mSyncQueueKey = syncLoop.SyncQueueKey();
+    mSyncLoopTarget = syncLoop.EventTarget();
 
     if (NS_FAILED(NS_DispatchToMainThread(this, NS_DISPATCH_NORMAL))) {
       JS_ReportError(aCx, "Failed to dispatch to main thread!");
       return false;
     }
 
-    return syncLoop.RunAndForget(aCx);
+    return syncLoop.Run();
   }
 
+protected:
+  virtual ~WorkerThreadProxySyncRunnable()
+  { }
+
   virtual nsresult
   MainThreadRun() = 0;
 
-  NS_IMETHOD
-  Run()
-  {
-    AssertIsOnMainThread();
-
-    uint32_t oldSyncQueueKey = mProxy->mSyncEventResponseSyncQueueKey;
-    mProxy->mSyncEventResponseSyncQueueKey = mSyncQueueKey;
+private:
+  NS_DECL_NSIRUNNABLE
+};
 
-    nsresult rv = MainThreadRun();
+class SyncTeardownRunnable MOZ_FINAL : public WorkerThreadProxySyncRunnable
+{
+public:
+  SyncTeardownRunnable(WorkerPrivate* aWorkerPrivate, Proxy* aProxy)
+  : WorkerThreadProxySyncRunnable(aWorkerPrivate, aProxy)
+  { }
 
-    nsRefPtr<ResponseRunnable> response =
-      new ResponseRunnable(mWorkerPrivate, mProxy, mSyncQueueKey, rv);
-    if (!response->Dispatch(nullptr)) {
-      NS_WARNING("Failed to dispatch response!");
-    }
+private:
+  ~SyncTeardownRunnable()
+  { }
 
-    mProxy->mSyncEventResponseSyncQueueKey = oldSyncQueueKey;
-
+  virtual nsresult
+  MainThreadRun() MOZ_OVERRIDE
+  {
+    mProxy->Teardown();
     return NS_OK;
   }
 };
 
-class SyncTeardownRunnable : public WorkerThreadProxySyncRunnable
-{
-public:
-  SyncTeardownRunnable(WorkerPrivate* aWorkerPrivate, Proxy* aProxy)
-  : WorkerThreadProxySyncRunnable(aWorkerPrivate, aProxy)
-  {
-    MOZ_ASSERT(aWorkerPrivate);
-    MOZ_ASSERT(aProxy);
-  }
-
-  virtual nsresult
-  MainThreadRun()
-  {
-    AssertIsOnMainThread();
-
-    mProxy->Teardown();
-
-    return NS_OK;
-  }
-};
-
-class SetBackgroundRequestRunnable : public WorkerThreadProxySyncRunnable
+class SetBackgroundRequestRunnable MOZ_FINAL :
+  public WorkerThreadProxySyncRunnable
 {
   bool mValue;
 
 public:
   SetBackgroundRequestRunnable(WorkerPrivate* aWorkerPrivate, Proxy* aProxy,
                                bool aValue)
   : WorkerThreadProxySyncRunnable(aWorkerPrivate, aProxy), mValue(aValue)
   { }
 
-  nsresult
-  MainThreadRun()
+private:
+  ~SetBackgroundRequestRunnable()
+  { }
+
+  virtual nsresult
+  MainThreadRun() MOZ_OVERRIDE
   {
     return mProxy->mXHR->SetMozBackgroundRequest(mValue);
   }
 };
 
-class SetWithCredentialsRunnable : public WorkerThreadProxySyncRunnable
+class SetWithCredentialsRunnable MOZ_FINAL :
+  public WorkerThreadProxySyncRunnable
 {
   bool mValue;
 
 public:
   SetWithCredentialsRunnable(WorkerPrivate* aWorkerPrivate, Proxy* aProxy,
                              bool aValue)
   : WorkerThreadProxySyncRunnable(aWorkerPrivate, aProxy), mValue(aValue)
   { }
 
-  nsresult
-  MainThreadRun()
+private:
+  ~SetWithCredentialsRunnable()
+  { }
+
+  virtual nsresult
+  MainThreadRun() MOZ_OVERRIDE
   {
     return mProxy->mXHR->SetWithCredentials(mValue);
   }
 };
 
-class SetResponseTypeRunnable : public WorkerThreadProxySyncRunnable
+class SetResponseTypeRunnable MOZ_FINAL : public WorkerThreadProxySyncRunnable
 {
   nsString mResponseType;
 
 public:
   SetResponseTypeRunnable(WorkerPrivate* aWorkerPrivate, Proxy* aProxy,
                           const nsAString& aResponseType)
   : WorkerThreadProxySyncRunnable(aWorkerPrivate, aProxy),
     mResponseType(aResponseType)
   { }
 
-  nsresult
-  MainThreadRun()
+  void
+  GetResponseType(nsAString& aResponseType)
+  {
+    aResponseType.Assign(mResponseType);
+  }
+
+private:
+  ~SetResponseTypeRunnable()
+  { }
+
+  virtual nsresult
+  MainThreadRun() MOZ_OVERRIDE
   {
     nsresult rv = mProxy->mXHR->SetResponseType(mResponseType);
     mResponseType.Truncate();
     if (NS_SUCCEEDED(rv)) {
       rv = mProxy->mXHR->GetResponseType(mResponseType);
     }
     return rv;
   }
-
-  void
-  GetResponseType(nsAString& aResponseType) {
-    aResponseType.Assign(mResponseType);
-  }
 };
 
-class SetTimeoutRunnable : public WorkerThreadProxySyncRunnable
+class SetTimeoutRunnable MOZ_FINAL : public WorkerThreadProxySyncRunnable
 {
   uint32_t mTimeout;
 
 public:
   SetTimeoutRunnable(WorkerPrivate* aWorkerPrivate, Proxy* aProxy,
                      uint32_t aTimeout)
-  : WorkerThreadProxySyncRunnable(aWorkerPrivate, aProxy),
-    mTimeout(aTimeout)
+  : WorkerThreadProxySyncRunnable(aWorkerPrivate, aProxy), mTimeout(aTimeout)
   { }
 
-  nsresult
-  MainThreadRun()
+private:
+  ~SetTimeoutRunnable()
+  { }
+
+  virtual nsresult
+  MainThreadRun() MOZ_OVERRIDE
   {
     return mProxy->mXHR->SetTimeout(mTimeout);
   }
 };
 
-class AbortRunnable : public WorkerThreadProxySyncRunnable
+class AbortRunnable MOZ_FINAL : public WorkerThreadProxySyncRunnable
 {
 public:
   AbortRunnable(WorkerPrivate* aWorkerPrivate, Proxy* aProxy)
   : WorkerThreadProxySyncRunnable(aWorkerPrivate, aProxy)
   { }
 
-  nsresult
-  MainThreadRun()
-  {
-    mProxy->mInnerEventStreamId++;
-
-    WorkerPrivate* oldWorker = mProxy->mWorkerPrivate;
-    mProxy->mWorkerPrivate = mWorkerPrivate;
+private:
+  ~AbortRunnable()
+  { }
 
-    mProxy->mXHR->Abort();
-
-    mProxy->mWorkerPrivate = oldWorker;
-
-    mProxy->Reset();
-
-    return NS_OK;
-  }
+  virtual nsresult
+  MainThreadRun() MOZ_OVERRIDE;
 };
 
-class GetAllResponseHeadersRunnable : public WorkerThreadProxySyncRunnable
+class GetAllResponseHeadersRunnable MOZ_FINAL :
+  public WorkerThreadProxySyncRunnable
 {
   nsCString& mResponseHeaders;
 
 public:
   GetAllResponseHeadersRunnable(WorkerPrivate* aWorkerPrivate, Proxy* aProxy,
                                 nsCString& aResponseHeaders)
   : WorkerThreadProxySyncRunnable(aWorkerPrivate, aProxy),
     mResponseHeaders(aResponseHeaders)
   { }
 
-  nsresult
-  MainThreadRun()
+private:
+  ~GetAllResponseHeadersRunnable()
+  { }
+
+  virtual nsresult
+  MainThreadRun() MOZ_OVERRIDE
   {
     mProxy->mXHR->GetAllResponseHeaders(mResponseHeaders);
     return NS_OK;
   }
 };
 
-class GetResponseHeaderRunnable : public WorkerThreadProxySyncRunnable
+class GetResponseHeaderRunnable MOZ_FINAL : public WorkerThreadProxySyncRunnable
 {
   const nsCString mHeader;
   nsCString& mValue;
 
 public:
   GetResponseHeaderRunnable(WorkerPrivate* aWorkerPrivate, Proxy* aProxy,
                             const nsACString& aHeader, nsCString& aValue)
   : WorkerThreadProxySyncRunnable(aWorkerPrivate, aProxy), mHeader(aHeader),
     mValue(aValue)
   { }
 
-  nsresult
-  MainThreadRun()
+private:
+  ~GetResponseHeaderRunnable()
+  { }
+
+  virtual nsresult
+  MainThreadRun() MOZ_OVERRIDE
   {
     return mProxy->mXHR->GetResponseHeader(mHeader, mValue);
   }
 };
 
-class OpenRunnable : public WorkerThreadProxySyncRunnable
+class OpenRunnable MOZ_FINAL : public WorkerThreadProxySyncRunnable
 {
   nsCString mMethod;
   nsString mURL;
   Optional<nsAString> mUser;
   nsString mUserStr;
   Optional<nsAString> mPassword;
   nsString mPasswordStr;
   bool mBackgroundRequest;
@@ -1054,223 +744,127 @@ class OpenRunnable : public WorkerThread
 public:
   OpenRunnable(WorkerPrivate* aWorkerPrivate, Proxy* aProxy,
                const nsACString& aMethod, const nsAString& aURL,
                const Optional<nsAString>& aUser,
                const Optional<nsAString>& aPassword,
                bool aBackgroundRequest, bool aWithCredentials,
                uint32_t aTimeout)
   : WorkerThreadProxySyncRunnable(aWorkerPrivate, aProxy), mMethod(aMethod),
-    mURL(aURL),
-    mBackgroundRequest(aBackgroundRequest), mWithCredentials(aWithCredentials),
-    mTimeout(aTimeout)
+    mURL(aURL), mBackgroundRequest(aBackgroundRequest),
+    mWithCredentials(aWithCredentials), mTimeout(aTimeout)
   {
     if (aUser.WasPassed()) {
       mUserStr = aUser.Value();
       mUser = &mUserStr;
     }
     if (aPassword.WasPassed()) {
       mPasswordStr = aPassword.Value();
       mPassword = &mPasswordStr;
     }
   }
 
-  nsresult
-  MainThreadRun()
+private:
+  ~OpenRunnable()
+  { }
+
+  virtual nsresult
+  MainThreadRun() MOZ_OVERRIDE
   {
     WorkerPrivate* oldWorker = mProxy->mWorkerPrivate;
     mProxy->mWorkerPrivate = mWorkerPrivate;
 
     nsresult rv = MainThreadRunInternal();
 
     mProxy->mWorkerPrivate = oldWorker;
     return rv;
   }
 
   nsresult
-  MainThreadRunInternal()
-  {
-    if (!mProxy->Init()) {
-      return NS_ERROR_DOM_INVALID_STATE_ERR;
-    }
-
-    nsresult rv;
-
-    if (mBackgroundRequest) {
-      rv = mProxy->mXHR->SetMozBackgroundRequest(mBackgroundRequest);
-      NS_ENSURE_SUCCESS(rv, rv);
-    }
-
-    if (mWithCredentials) {
-      rv = mProxy->mXHR->SetWithCredentials(mWithCredentials);
-      NS_ENSURE_SUCCESS(rv, rv);
-    }
-
-    if (mTimeout) {
-      rv = mProxy->mXHR->SetTimeout(mTimeout);
-      NS_ENSURE_SUCCESS(rv, rv);
-    }
-
-    NS_ASSERTION(!mProxy->mInOpen, "Reentrancy is bad!");
-    mProxy->mInOpen = true;
-
-    ErrorResult rv2;
-    mProxy->mXHR->Open(mMethod, mURL, true, mUser, mPassword, rv2);
-
-    NS_ASSERTION(mProxy->mInOpen, "Reentrancy is bad!");
-    mProxy->mInOpen = false;
-
-    if (rv2.Failed()) {
-      return rv2.ErrorCode();
-    }
-
-    rv = mProxy->mXHR->SetResponseType(NS_LITERAL_STRING("text"));
-
-    return rv;
-  }
+  MainThreadRunInternal();
 };
 
-class SendRunnable : public WorkerThreadProxySyncRunnable
+class SendRunnable MOZ_FINAL : public WorkerThreadProxySyncRunnable
 {
   nsString mStringBody;
   JSAutoStructuredCloneBuffer mBody;
   nsTArray<nsCOMPtr<nsISupports> > mClonedObjects;
-  uint32_t mSyncQueueKey;
+  nsCOMPtr<nsIEventTarget> mSyncLoopTarget;
   bool mHasUploadListeners;
 
 public:
   SendRunnable(WorkerPrivate* aWorkerPrivate, Proxy* aProxy,
                const nsAString& aStringBody, JSAutoStructuredCloneBuffer& aBody,
                nsTArray<nsCOMPtr<nsISupports> >& aClonedObjects,
-               uint32_t aSyncQueueKey, bool aHasUploadListeners)
+               nsIEventTarget* aSyncLoopTarget, bool aHasUploadListeners)
   : WorkerThreadProxySyncRunnable(aWorkerPrivate, aProxy),
-    mStringBody(aStringBody), mSyncQueueKey(aSyncQueueKey),
+    mStringBody(aStringBody), mSyncLoopTarget(aSyncLoopTarget),
     mHasUploadListeners(aHasUploadListeners)
   {
     mBody.swap(aBody);
     mClonedObjects.SwapElements(aClonedObjects);
   }
 
-  nsresult
-  MainThreadRun()
-  {
-    nsCOMPtr<nsIVariant> variant;
-
-    if (mBody.data()) {
-      AutoSafeJSContext cx;
-      JSAutoRequest ar(cx);
-      nsIXPConnect* xpc = nsContentUtils::XPConnect();
-      NS_ASSERTION(xpc, "This should never be null!");
-
-      nsresult rv = NS_OK;
-
-      JSStructuredCloneCallbacks* callbacks =
-        mWorkerPrivate->IsChromeWorker() ?
-        ChromeWorkerStructuredCloneCallbacks(true) :
-        WorkerStructuredCloneCallbacks(true);
-
-      JS::Rooted<JS::Value> body(cx);
-      if (mBody.read(cx, &body, callbacks, &mClonedObjects)) {
-        if (NS_FAILED(xpc->JSValToVariant(cx, body.address(),
-                                          getter_AddRefs(variant)))) {
-          rv = NS_ERROR_DOM_INVALID_STATE_ERR;
-        }
-      }
-      else {
-        rv = NS_ERROR_DOM_DATA_CLONE_ERR;
-      }
-
-      mBody.clear();
-      mClonedObjects.Clear();
+private:
+  ~SendRunnable()
+  { }
 
-      NS_ENSURE_SUCCESS(rv, rv);
-    }
-    else {
-      nsCOMPtr<nsIWritableVariant> wvariant =
-        do_CreateInstance(NS_VARIANT_CONTRACTID);
-      NS_ENSURE_TRUE(wvariant, NS_ERROR_UNEXPECTED);
-
-      if (NS_FAILED(wvariant->SetAsAString(mStringBody))) {
-        NS_ERROR("This should never fail!");
-      }
-
-      variant = wvariant;
-    }
-
-    NS_ASSERTION(!mProxy->mWorkerPrivate, "Should be null!");
-    mProxy->mWorkerPrivate = mWorkerPrivate;
-
-    NS_ASSERTION(mProxy->mSyncQueueKey == UINT32_MAX, "Should be unset!");
-    mProxy->mSyncQueueKey = mSyncQueueKey;
-
-    if (mHasUploadListeners) {
-      NS_ASSERTION(!mProxy->mUploadEventListenersAttached, "Huh?!");
-      if (!mProxy->AddRemoveEventListeners(true, true)) {
-        NS_ERROR("This should never fail!");
-      }
-    }
-
-    mProxy->mInnerChannelId++;
-
-    nsresult rv = mProxy->mXHR->Send(variant);
-
-    if (NS_SUCCEEDED(rv)) {
-      mProxy->mOutstandingSendCount++;
-
-      if (!mHasUploadListeners) {
-        NS_ASSERTION(!mProxy->mUploadEventListenersAttached, "Huh?!");
-        if (!mProxy->AddRemoveEventListeners(true, true)) {
-          NS_ERROR("This should never fail!");
-        }
-      }
-    }
-
-    return rv;
-  }
+  virtual nsresult
+  MainThreadRun() MOZ_OVERRIDE;
 };
 
-class SetRequestHeaderRunnable : public WorkerThreadProxySyncRunnable
+class SetRequestHeaderRunnable MOZ_FINAL : public WorkerThreadProxySyncRunnable
 {
   nsCString mHeader;
   nsCString mValue;
 
 public:
   SetRequestHeaderRunnable(WorkerPrivate* aWorkerPrivate, Proxy* aProxy,
                            const nsACString& aHeader, const nsACString& aValue)
   : WorkerThreadProxySyncRunnable(aWorkerPrivate, aProxy), mHeader(aHeader),
     mValue(aValue)
   { }
 
-  nsresult
-  MainThreadRun()
+private:
+  ~SetRequestHeaderRunnable()
+  { }
+
+  virtual nsresult
+  MainThreadRun() MOZ_OVERRIDE
   {
     return mProxy->mXHR->SetRequestHeader(mHeader, mValue);
   }
 };
 
-class OverrideMimeTypeRunnable : public WorkerThreadProxySyncRunnable
+class OverrideMimeTypeRunnable MOZ_FINAL : public WorkerThreadProxySyncRunnable
 {
   nsString mMimeType;
 
 public:
   OverrideMimeTypeRunnable(WorkerPrivate* aWorkerPrivate, Proxy* aProxy,
                            const nsAString& aMimeType)
   : WorkerThreadProxySyncRunnable(aWorkerPrivate, aProxy), mMimeType(aMimeType)
   { }
 
-  nsresult
-  MainThreadRun()
+private:
+  ~OverrideMimeTypeRunnable()
+  { }
+
+  virtual nsresult
+  MainThreadRun() MOZ_OVERRIDE
   {
     mProxy->mXHR->OverrideMimeType(mMimeType);
     return NS_OK;
   }
 };
 
 class AutoUnpinXHR
 {
+  XMLHttpRequest* mXMLHttpRequestPrivate;
+
 public:
   AutoUnpinXHR(XMLHttpRequest* aXMLHttpRequestPrivate)
   : mXMLHttpRequestPrivate(aXMLHttpRequestPrivate)
   {
     MOZ_ASSERT(aXMLHttpRequestPrivate);
   }
 
   ~AutoUnpinXHR()
@@ -1279,23 +873,71 @@ public:
       mXMLHttpRequestPrivate->Unpin();
     }
   }
 
   void Clear()
   {
     mXMLHttpRequestPrivate = nullptr;
   }
-
-private:
-  XMLHttpRequest* mXMLHttpRequestPrivate;
 };
 
 } // anonymous namespace
 
+bool
+Proxy::Init()
+{
+  AssertIsOnMainThread();
+  MOZ_ASSERT(mWorkerPrivate);
+
+  if (mXHR) {
+    return true;
+  }
+
+  nsPIDOMWindow* ownerWindow = mWorkerPrivate->GetWindow();
+  if (ownerWindow) {
+    ownerWindow = ownerWindow->GetOuterWindow();
+    if (!ownerWindow) {
+      NS_ERROR("No outer window?!");
+      return false;
+    }
+
+    nsPIDOMWindow* innerWindow = ownerWindow->GetCurrentInnerWindow();
+    if (mWorkerPrivate->GetWindow() != innerWindow) {
+      NS_WARNING("Window has navigated, cannot create XHR here.");
+      return false;
+    }
+  }
+
+  mXHR = new nsXMLHttpRequest();
+
+  nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(ownerWindow);
+  if (NS_FAILED(mXHR->Init(mWorkerPrivate->GetPrincipal(),
+                           mWorkerPrivate->GetScriptContext(),
+                           global, mWorkerPrivate->GetBaseURI()))) {
+    mXHR = nullptr;
+    return false;
+  }
+
+  mXHR->SetParameters(mMozAnon, mMozSystem);
+
+  if (NS_FAILED(mXHR->GetUpload(getter_AddRefs(mXHRUpload)))) {
+    mXHR = nullptr;
+    return false;
+  }
+
+  if (!AddRemoveEventListeners(false, true)) {
+    mXHRUpload = nullptr;
+    mXHR = nullptr;
+    return false;
+  }
+
+  return true;
+}
+
 void
 Proxy::Teardown()
 {
   AssertIsOnMainThread();
 
   if (mXHR) {
     Reset();
 
@@ -1432,16 +1074,453 @@ Proxy::HandleEvent(nsIDOMEvent* aEvent)
         NS_WARNING("Failed to dispatch LoadStartDetectionRunnable!");
       }
     }
   }
 
   return NS_OK;
 }
 
+NS_IMPL_ISUPPORTS_INHERITED0(WorkerThreadProxySyncRunnable, nsRunnable)
+
+NS_IMPL_ISUPPORTS_INHERITED0(AsyncTeardownRunnable, nsRunnable)
+
+NS_IMPL_ISUPPORTS_INHERITED1(LoadStartDetectionRunnable, nsRunnable,
+                                                         nsIDOMEventListener)
+
+NS_IMETHODIMP
+LoadStartDetectionRunnable::Run()
+{
+  AssertIsOnMainThread();
+
+  if (NS_FAILED(mXHR->RemoveEventListener(mEventType, this, false))) {
+    NS_WARNING("Failed to remove event listener!");
+  }
+
+  if (!mReceivedLoadStart) {
+    if (mProxy->mOutstandingSendCount > 1) {
+      mProxy->mOutstandingSendCount--;
+    } else if (mProxy->mOutstandingSendCount == 1) {
+      mProxy->Reset();
+
+      nsRefPtr<ProxyCompleteRunnable> runnable =
+        new ProxyCompleteRunnable(mWorkerPrivate, mProxy,
+                                  mXMLHttpRequestPrivate, mChannelId);
+      if (runnable->Dispatch(nullptr)) {
+        mProxy->mWorkerPrivate = nullptr;
+        mProxy->mOutstandingSendCount--;
+      }
+    }
+  }
+
+  mProxy = nullptr;
+  mXHR = nullptr;
+  mXMLHttpRequestPrivate = nullptr;
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+LoadStartDetectionRunnable::HandleEvent(nsIDOMEvent* aEvent)
+{
+  AssertIsOnMainThread();
+
+#ifdef DEBUG
+  {
+    nsString type;
+    if (NS_SUCCEEDED(aEvent->GetType(type))) {
+      MOZ_ASSERT(type == mEventType);
+    }
+    else {
+      NS_WARNING("Failed to get event type!");
+    }
+  }
+#endif
+
+  mReceivedLoadStart = true;
+  return NS_OK;
+}
+
+bool
+EventRunnable::PreDispatch(JSContext* aCx, WorkerPrivate* aWorkerPrivate)
+{
+  AssertIsOnMainThread();
+
+  nsRefPtr<nsXMLHttpRequest>& xhr = mProxy->mXHR;
+  MOZ_ASSERT(xhr);
+
+  if (NS_FAILED(xhr->GetResponseType(mResponseType))) {
+    MOZ_ASSERT(false, "This should never fail!");
+  }
+
+  mResponseTextResult = xhr->GetResponseText(mResponseText);
+  if (NS_SUCCEEDED(mResponseTextResult)) {
+    mResponseResult = mResponseTextResult;
+    if (mResponseText.IsVoid()) {
+      mResponse = JSVAL_NULL;
+    }
+  }
+  else {
+    JS::Rooted<JS::Value> response(aCx);
+    mResponseResult = xhr->GetResponse(aCx, response.address());
+    if (NS_SUCCEEDED(mResponseResult)) {
+      if (JSVAL_IS_UNIVERSAL(response)) {
+        mResponse = response;
+      }
+      else {
+        // Anything subject to GC must be cloned.
+        JSStructuredCloneCallbacks* callbacks =
+          aWorkerPrivate->IsChromeWorker() ?
+          ChromeWorkerStructuredCloneCallbacks(true) :
+          WorkerStructuredCloneCallbacks(true);
+
+        nsTArray<nsCOMPtr<nsISupports> > clonedObjects;
+
+        if (mResponseBuffer.write(aCx, response, callbacks, &clonedObjects)) {
+          mClonedObjects.SwapElements(clonedObjects);
+        }
+        else {
+          NS_WARNING("Failed to clone response!");
+          mResponseResult = NS_ERROR_DOM_DATA_CLONE_ERR;
+        }
+      }
+    }
+  }
+
+  mStatusResult = xhr->GetStatus(&mStatus);
+
+  xhr->GetStatusText(mStatusText);
+
+  mReadyState = xhr->ReadyState();
+
+  return true;
+}
+
+bool
+EventRunnable::WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate)
+{
+  if (mEventStreamId != mProxy->mOuterEventStreamId) {
+    // Threads raced, this event is now obsolete.
+    return true;
+  }
+
+  if (!mProxy->mXMLHttpRequestPrivate) {
+    // Object was finalized, bail.
+    return true;
+  }
+
+  if (mType.EqualsASCII(sEventStrings[STRING_loadstart])) {
+    if (mUploadEvent) {
+      mProxy->mSeenUploadLoadStart = true;
+    }
+    else {
+      mProxy->mSeenLoadStart = true;
+    }
+  }
+  else if (mType.EqualsASCII(sEventStrings[STRING_loadend])) {
+    if (mUploadEvent) {
+      mProxy->mSeenUploadLoadStart = false;
+    }
+    else {
+      mProxy->mSeenLoadStart = false;
+    }
+  }
+  else if (mType.EqualsASCII(sEventStrings[STRING_abort])) {
+    if ((mUploadEvent && !mProxy->mSeenUploadLoadStart) ||
+        (!mUploadEvent && !mProxy->mSeenLoadStart)) {
+      // We've already dispatched premature abort events.
+      return true;
+    }
+  }
+  else if (mType.EqualsASCII(sEventStrings[STRING_readystatechange])) {
+    if (mReadyState == 4 && !mUploadEvent && !mProxy->mSeenLoadStart) {
+      // We've already dispatched premature abort events.
+      return true;
+    }
+  }
+
+  if (mProgressEvent) {
+    // Cache these for premature abort events.
+    if (mUploadEvent) {
+      mProxy->mLastUploadLengthComputable = mLengthComputable;
+      mProxy->mLastUploadLoaded = mLoaded;
+      mProxy->mLastUploadTotal = mTotal;
+    }
+    else {
+      mProxy->mLastLengthComputable = mLengthComputable;
+      mProxy->mLastLoaded = mLoaded;
+      mProxy->mLastTotal = mTotal;
+    }
+  }
+
+  nsAutoPtr<XMLHttpRequest::StateData> state(new XMLHttpRequest::StateData());
+  StateDataAutoRooter rooter(aCx, state);
+
+  state->mResponseTextResult = mResponseTextResult;
+  state->mResponseText = mResponseText;
+
+  if (NS_SUCCEEDED(mResponseTextResult)) {
+    MOZ_ASSERT(JSVAL_IS_VOID(mResponse) || JSVAL_IS_NULL(mResponse));
+    state->mResponseResult = mResponseTextResult;
+    state->mResponse = mResponse;
+  }
+  else {
+    state->mResponseResult = mResponseResult;
+
+    if (NS_SUCCEEDED(mResponseResult)) {
+      if (mResponseBuffer.data()) {
+        MOZ_ASSERT(JSVAL_IS_VOID(mResponse));
+
+        JSAutoStructuredCloneBuffer responseBuffer;
+        mResponseBuffer.swap(responseBuffer);
+
+        JSStructuredCloneCallbacks* callbacks =
+          aWorkerPrivate->IsChromeWorker() ?
+          ChromeWorkerStructuredCloneCallbacks(false) :
+          WorkerStructuredCloneCallbacks(false);
+
+        nsTArray<nsCOMPtr<nsISupports> > clonedObjects;
+        clonedObjects.SwapElements(mClonedObjects);
+
+        JS::Rooted<JS::Value> response(aCx);
+        if (!responseBuffer.read(aCx, &response, callbacks, &clonedObjects)) {
+          return false;
+        }
+
+        state->mResponse = response;
+      }
+      else {
+        state->mResponse = mResponse;
+      }
+    }
+  }
+
+  state->mStatusResult = mStatusResult;
+  state->mStatus = mStatus;
+
+  state->mStatusText = mStatusText;
+
+  state->mReadyState = mReadyState;
+
+  XMLHttpRequest* xhr = mProxy->mXMLHttpRequestPrivate;
+  xhr->UpdateState(*state);
+
+  if (mUploadEvent && !xhr->GetUploadObjectNoCreate()) {
+    return true;
+  }
+
+  JS::Rooted<JSString*> type(aCx,
+    JS_NewUCStringCopyN(aCx, mType.get(), mType.Length()));
+  if (!type) {
+    return false;
+  }
+
+  nsXHREventTarget* target;
+  if (mUploadEvent) {
+    target = xhr->GetUploadObjectNoCreate();
+  }
+  else {
+    target = xhr;
+  }
+
+  MOZ_ASSERT(target);
+
+  nsCOMPtr<nsIDOMEvent> event;
+  if (mProgressEvent) {
+    NS_NewDOMProgressEvent(getter_AddRefs(event), target, nullptr, nullptr);
+    nsCOMPtr<nsIDOMProgressEvent> progress = do_QueryInterface(event);
+
+    if (progress) {
+      progress->InitProgressEvent(mType, false, false, mLengthComputable,
+                                  mLoaded, mTotal);
+    }
+  }
+  else {
+    NS_NewDOMEvent(getter_AddRefs(event), target, nullptr, nullptr);
+
+    if (event) {
+      event->InitEvent(mType, false, false);
+    }
+  }
+
+  if (!event) {
+    return false;
+  }
+
+  event->SetTrusted(true);
+
+  target->DispatchDOMEvent(nullptr, event, nullptr, nullptr);
+
+  // After firing the event set mResponse to JSVAL_NULL for chunked response
+  // types.
+  if (StringBeginsWith(mResponseType, NS_LITERAL_STRING("moz-chunked-"))) {
+    xhr->NullResponseText();
+  }
+
+  return true;
+}
+
+NS_IMETHODIMP
+WorkerThreadProxySyncRunnable::Run()
+{
+  AssertIsOnMainThread();
+
+  nsCOMPtr<nsIEventTarget> tempTarget;
+  mSyncLoopTarget.swap(tempTarget);
+
+  mProxy->mSyncEventResponseTarget.swap(tempTarget);
+
+  nsresult rv = MainThreadRun();
+
+  nsRefPtr<ResponseRunnable> response =
+    new ResponseRunnable(mWorkerPrivate, mProxy, rv);
+  if (!response->Dispatch(nullptr)) {
+    MOZ_ASSERT(false, "Failed to dispatch response!");
+  }
+
+  mProxy->mSyncEventResponseTarget.swap(tempTarget);
+
+  return NS_OK;
+}
+
+nsresult
+AbortRunnable::MainThreadRun()
+{
+  mProxy->mInnerEventStreamId++;
+
+  WorkerPrivate* oldWorker = mProxy->mWorkerPrivate;
+  mProxy->mWorkerPrivate = mWorkerPrivate;
+
+  mProxy->mXHR->Abort();
+
+  mProxy->mWorkerPrivate = oldWorker;
+
+  mProxy->Reset();
+
+  return NS_OK;
+}
+
+nsresult
+OpenRunnable::MainThreadRunInternal()
+{
+  if (!mProxy->Init()) {
+    return NS_ERROR_DOM_INVALID_STATE_ERR;
+  }
+
+  nsresult rv;
+
+  if (mBackgroundRequest) {
+    rv = mProxy->mXHR->SetMozBackgroundRequest(mBackgroundRequest);
+    NS_ENSURE_SUCCESS(rv, rv);
+  }
+
+  if (mWithCredentials) {
+    rv = mProxy->mXHR->SetWithCredentials(mWithCredentials);
+    NS_ENSURE_SUCCESS(rv, rv);
+  }
+
+  if (mTimeout) {
+    rv = mProxy->mXHR->SetTimeout(mTimeout);
+    NS_ENSURE_SUCCESS(rv, rv);
+  }
+
+  MOZ_ASSERT(!mProxy->mInOpen);
+  mProxy->mInOpen = true;
+
+  ErrorResult rv2;
+  mProxy->mXHR->Open(mMethod, mURL, true, mUser, mPassword, rv2);
+
+  MOZ_ASSERT(mProxy->mInOpen);
+  mProxy->mInOpen = false;
+
+  if (rv2.Failed()) {
+    return rv2.ErrorCode();
+  }
+
+  return mProxy->mXHR->SetResponseType(NS_LITERAL_STRING("text"));
+}
+
+
+nsresult
+SendRunnable::MainThreadRun()
+{
+  nsCOMPtr<nsIVariant> variant;
+
+  if (mBody.data()) {
+    AutoSafeJSContext cx;
+    JSAutoRequest ar(cx);
+
+    nsIXPConnect* xpc = nsContentUtils::XPConnect();
+    MOZ_ASSERT(xpc);
+
+    nsresult rv = NS_OK;
+
+    JSStructuredCloneCallbacks* callbacks =
+      mWorkerPrivate->IsChromeWorker() ?
+      ChromeWorkerStructuredCloneCallbacks(true) :
+      WorkerStructuredCloneCallbacks(true);
+
+    JS::Rooted<JS::Value> body(cx);
+    if (mBody.read(cx, &body, callbacks, &mClonedObjects)) {
+      if (NS_FAILED(xpc->JSValToVariant(cx, body.address(),
+                                        getter_AddRefs(variant)))) {
+        rv = NS_ERROR_DOM_INVALID_STATE_ERR;
+      }
+    }
+    else {
+      rv = NS_ERROR_DOM_DATA_CLONE_ERR;
+    }
+
+    mBody.clear();
+    mClonedObjects.Clear();
+
+    NS_ENSURE_SUCCESS(rv, rv);
+  }
+  else {
+    nsCOMPtr<nsIWritableVariant> wvariant =
+      do_CreateInstance(NS_VARIANT_CONTRACTID);
+    NS_ENSURE_TRUE(wvariant, NS_ERROR_UNEXPECTED);
+
+    if (NS_FAILED(wvariant->SetAsAString(mStringBody))) {
+      MOZ_ASSERT(false, "This should never fail!");
+    }
+
+    variant = wvariant;
+  }
+
+  MOZ_ASSERT(!mProxy->mWorkerPrivate);
+  mProxy->mWorkerPrivate = mWorkerPrivate;
+
+  MOZ_ASSERT(!mProxy->mSyncLoopTarget);
+  mProxy->mSyncLoopTarget.swap(mSyncLoopTarget);
+
+  if (mHasUploadListeners) {
+    NS_ASSERTION(!mProxy->mUploadEventListenersAttached, "Huh?!");
+    if (!mProxy->AddRemoveEventListeners(true, true)) {
+      MOZ_ASSERT(false, "This should never fail!");
+    }
+  }
+
+  mProxy->mInnerChannelId++;
+
+  nsresult rv = mProxy->mXHR->Send(variant);
+
+  if (NS_SUCCEEDED(rv)) {
+    mProxy->mOutstandingSendCount++;
+
+    if (!mHasUploadListeners) {
+      NS_ASSERTION(!mProxy->mUploadEventListenersAttached, "Huh?!");
+      if (!mProxy->AddRemoveEventListeners(true, true)) {
+        MOZ_ASSERT(false, "This should never fail!");
+      }
+    }
+  }
+
+  return rv;
+}
+
 XMLHttpRequest::XMLHttpRequest(WorkerPrivate* aWorkerPrivate)
 : mWorkerPrivate(aWorkerPrivate),
   mResponseType(XMLHttpRequestResponseType::Text), mTimeout(0),
   mRooted(false), mBackgroundRequest(false), mWithCredentials(false),
   mCanceled(false), mMozAnon(false), mMozSystem(false)
 {
   mWorkerPrivate->AssertIsOnWorkerThread();
 
@@ -1698,30 +1777,30 @@ XMLHttpRequest::SendInternal(const nsASt
   MaybePin(aRv);
   if (aRv.Failed()) {
     return;
   }
 
   AutoUnpinXHR autoUnpin(this);
   Maybe<AutoSyncLoopHolder> autoSyncLoop;
 
-  uint32_t syncQueueKey = UINT32_MAX;
+  nsCOMPtr<nsIEventTarget> syncLoopTarget;
   bool isSyncXHR = mProxy->mIsSyncXHR;
   if (isSyncXHR) {
     autoSyncLoop.construct(mWorkerPrivate);
-    syncQueueKey = autoSyncLoop.ref().SyncQueueKey();
+    syncLoopTarget = autoSyncLoop.ref().EventTarget();
   }
 
   mProxy->mOuterChannelId++;
 
   JSContext* cx = mWorkerPrivate->GetJSContext();
 
   nsRefPtr<SendRunnable> runnable =
     new SendRunnable(mWorkerPrivate, mProxy, aStringBody, aBody,
-                     aClonedObjects, syncQueueKey, hasUploadListeners);
+                     aClonedObjects, syncLoopTarget, hasUploadListeners);
   if (!runnable->Dispatch(cx)) {
     aRv.Throw(NS_ERROR_FAILURE);
     return;
   }
 
   if (!isSyncXHR)  {
     autoUnpin.Clear();
     MOZ_ASSERT(autoSyncLoop.empty());
@@ -1733,17 +1812,17 @@ XMLHttpRequest::SendInternal(const nsASt
   // be a ProxyCompleteRunnable in the sync loop, but rather than run the loop
   // to get it we just let our RAII helpers clean up.
   if (mCanceled) {
     return;
   }
 
   autoUnpin.Clear();
 
-  if (!autoSyncLoop.ref().RunAndForget(cx)) {
+  if (!autoSyncLoop.ref().Run()) {
     aRv.Throw(NS_ERROR_FAILURE);
   }
 }
 
 bool
 XMLHttpRequest::Notify(JSContext* aCx, Status aStatus)
 {
   mWorkerPrivate->AssertIsOnWorkerThread();
--- a/dom/workers/XMLHttpRequest.h
+++ b/dom/workers/XMLHttpRequest.h
@@ -17,18 +17,18 @@
 #include "nsXMLHttpRequest.h"
 
 BEGIN_WORKERS_NAMESPACE
 
 class Proxy;
 class XMLHttpRequestUpload;
 class WorkerPrivate;
 
-class XMLHttpRequest : public nsXHREventTarget,
-                       public WorkerFeature
+class XMLHttpRequest MOZ_FINAL: public nsXHREventTarget,
+                                public WorkerFeature
 {
 public:
   struct StateData
   {
     nsString mResponseText;
     uint32_t mStatus;
     nsCString mStatusText;
     uint16_t mReadyState;
@@ -56,20 +56,16 @@ private:
   bool mRooted;
   bool mBackgroundRequest;
   bool mWithCredentials;
   bool mCanceled;
 
   bool mMozAnon;
   bool mMozSystem;
 
-protected:
-  XMLHttpRequest(WorkerPrivate* aWorkerPrivate);
-  virtual ~XMLHttpRequest();
-
 public:
   virtual JSObject*
   WrapObject(JSContext* aCx, JS::Handle<JSObject*> aScope) MOZ_OVERRIDE;
 
   NS_DECL_ISUPPORTS_INHERITED
   NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS_INHERITED(XMLHttpRequest,
                                                          nsXHREventTarget)
 
@@ -267,16 +263,19 @@ public:
   }
 
   bool MozSystem() const
   {
     return mMozSystem;
   }
 
 private:
+  XMLHttpRequest(WorkerPrivate* aWorkerPrivate);
+  ~XMLHttpRequest();
+
   enum ReleaseType { Default, XHRIsGoingAway, WorkerIsGoingAway };
 
   void
   ReleaseProxy(ReleaseType aType = Default);
 
   void
   MaybePin(ErrorResult& aRv);
 
--- a/dom/workers/moz.build
+++ b/dom/workers/moz.build
@@ -4,16 +4,17 @@
 # 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/.
 
 TEST_DIRS += ['test']
 
 # Public stuff.
 EXPORTS.mozilla.dom += [
     'WorkerPrivate.h',
+    'WorkerRunnable.h',
     'WorkerScope.h',
 ]
 
 EXPORTS.mozilla.dom.workers += [
     'Workers.h',
 ]
 
 # Stuff needed for the bindings, not really public though.
@@ -38,28 +39,30 @@ SOURCES += [
     'Navigator.cpp',
     'Principal.cpp',
     'RegisterBindings.cpp',
     'RuntimeService.cpp',
     'ScriptLoader.cpp',
     'SharedWorker.cpp',
     'URL.cpp',
     'WorkerPrivate.cpp',
+    'WorkerRunnable.cpp',
     'WorkerScope.cpp',
     'XMLHttpRequest.cpp',
     'XMLHttpRequestUpload.cpp',
 ]
 
 FAIL_ON_WARNINGS = True
 
 MSVC_ENABLE_PGO = True
 
 LOCAL_INCLUDES += [
     '../base',
     '../system',
     '/content/base/src',
     '/content/events/src',
     '/xpcom/build',
+    '/xpcom/threads',
 ]
 
 include('/ipc/chromium/chromium-config.mozbuild')
 
 FINAL_LIBRARY = 'gklayout'
new file mode 100644
--- /dev/null
+++ b/gfx/2d/StackArray.h
@@ -0,0 +1,30 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * 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/. */
+
+/* A handy class that will allocate data for size*T objects on the stack and
+ * otherwise allocate them on the heap. It is similar in purpose to nsAutoTArray */
+
+template <class T, size_t size>
+class StackArray
+{
+public:
+  StackArray(size_t count) {
+    if (count > size) {
+      mData = new T[count];
+    } else {
+      mData = mStackData;
+    }
+  }
+  ~StackArray() {
+    if (mData != mStackData) {
+      delete[] mData;
+    }
+  }
+  T& operator[](size_t n) { return mData[n]; }
+  const T& operator[](size_t n) const { return mData[n]; }
+  T* data() { return mData; };
+private:
+  T mStackData[size];
+  T* mData;
+};
--- a/image/decoders/EXIF.cpp
+++ b/image/decoders/EXIF.cpp
@@ -73,26 +73,23 @@ EXIFParser::ParseEXIFHeader()
 
 /////////////////////////////////////////////////////////
 // Parse the TIFF header. (Section 4.5.2, Table 1)
 /////////////////////////////////////////////////////////
 bool
 EXIFParser::ParseTIFFHeader(uint32_t& aIFD0OffsetOut)
 {
   // Determine byte order.
-  if (MatchString("MM", 2))
+  if (MatchString("MM\0*", 4))
     mByteOrder = ByteOrder::BigEndian;
-  else if (MatchString("II", 2))
+  else if (MatchString("II*\0", 4))
     mByteOrder = ByteOrder::LittleEndian;
   else
     return false;
 
-  if (!MatchString("\0*", 2))
-    return false;
-
   // Determine offset of the 0th IFD. (It shouldn't be greater than 64k, which
   // is the maximum size of the entry APP1 segment.)
   uint32_t ifd0Offset;
   if (!ReadUInt32(ifd0Offset) || ifd0Offset > 64 * 1024)
     return false;
 
   // The IFD offset is relative to the beginning of the TIFF header, which
   // begins after the EXIF header, so we need to increase the offset
--- a/js/src/builtin/Intl.cpp
+++ b/js/src/builtin/Intl.cpp
@@ -392,17 +392,17 @@ udat_close(UDateFormat *format)
 
 /******************** Common to Intl constructors ********************/
 
 static bool
 IntlInitialize(JSContext *cx, HandleObject obj, Handle<PropertyName*> initializer,
                HandleValue locales, HandleValue options)
 {
     RootedValue initializerValue(cx);
-    if (!cx->global()->getIntrinsicValue(cx, initializer, &initializerValue))
+    if (!GlobalObject::getIntrinsicValue(cx, cx->global(), initializer, &initializerValue))
         return false;
     JS_ASSERT(initializerValue.isObject());
     JS_ASSERT(initializerValue.toObject().is<JSFunction>());
 
     InvokeArgs args(cx);
     if (!args.init(3))
         return false;
 
@@ -458,17 +458,17 @@ intl_availableLocales(JSContext *cx, Cou
 
 /**
  * Returns the object holding the internal properties for obj.
  */
 static bool
 GetInternals(JSContext *cx, HandleObject obj, MutableHandleObject internals)
 {
     RootedValue getInternalsValue(cx);
-    if (!cx->global()->getIntrinsicValue(cx, cx->names().getInternals, &getInternalsValue))
+    if (!GlobalObject::getIntrinsicValue(cx, cx->global(), cx->names().getInternals, &getInternalsValue))
         return false;
     JS_ASSERT(getInternalsValue.isObject());
     JS_ASSERT(getInternalsValue.toObject().is<JSFunction>());
 
     InvokeArgs args(cx);
     if (!args.init(1))
         return false;
 
@@ -685,17 +685,17 @@ InitCollatorClass(JSContext *cx, HandleO
         return nullptr;
 
     /*
      * Install the getter for Collator.prototype.compare, which returns a bound
      * comparison function for the specified Collator object (suitable for
      * passing to methods like Array.prototype.sort).
      */
     RootedValue getter(cx);
-    if (!cx->global()->getIntrinsicValue(cx, cx->names().CollatorCompareGet, &getter))
+    if (!GlobalObject::getIntrinsicValue(cx, cx->global(), cx->names().CollatorCompareGet, &getter))
         return nullptr;
     RootedValue undefinedValue(cx, UndefinedValue());
     if (!JSObject::defineProperty(cx, proto, cx->names().compare, undefinedValue,
                                   JS_DATA_TO_FUNC_PTR(JSPropertyOp, &getter.toObject()),
                                   nullptr, JSPROP_GETTER))
     {
         return nullptr;
     }
@@ -1173,17 +1173,17 @@ InitNumberFormatClass(JSContext *cx, Han
         return nullptr;
 
     /*
      * Install the getter for NumberFormat.prototype.format, which returns a
      * bound formatting function for the specified NumberFormat object (suitable
      * for passing to methods like Array.prototype.map).
      */
     RootedValue getter(cx);
-    if (!cx->global()->getIntrinsicValue(cx, cx->names().NumberFormatFormatGet, &getter))
+    if (!GlobalObject::getIntrinsicValue(cx, cx->global(), cx->names().NumberFormatFormatGet, &getter))
         return nullptr;
     RootedValue undefinedValue(cx, UndefinedValue());
     if (!JSObject::defineProperty(cx, proto, cx->names().format, undefinedValue,
                                   JS_DATA_TO_FUNC_PTR(JSPropertyOp, &getter.toObject()),
                                   nullptr, JSPROP_GETTER))
     {
         return nullptr;
     }
@@ -1630,17 +1630,17 @@ InitDateTimeFormatClass(JSContext *cx, H
         return nullptr;
 
     /*
      * Install the getter for DateTimeFormat.prototype.format, which returns a
      * bound formatting function for the specified DateTimeFormat object
      * (suitable for passing to methods like Array.prototype.map).
      */
     RootedValue getter(cx);
-    if (!cx->global()->getIntrinsicValue(cx, cx->names().DateTimeFormatFormatGet, &getter))
+    if (!GlobalObject::getIntrinsicValue(cx, cx->global(), cx->names().DateTimeFormatFormatGet, &getter))
         return nullptr;
     RootedValue undefinedValue(cx, UndefinedValue());
     if (!JSObject::defineProperty(cx, proto, cx->names().format, undefinedValue,
                                   JS_DATA_TO_FUNC_PTR(JSPropertyOp, &getter.toObject()),
                                   nullptr, JSPROP_GETTER))
     {
         return nullptr;
     }
--- a/js/src/frontend/BytecodeEmitter.cpp
+++ b/js/src/frontend/BytecodeEmitter.cpp
@@ -704,28 +704,28 @@ AllLocalsAliased(StaticBlockObject &obj)
     for (unsigned i = 0; i < obj.slotCount(); i++)
         if (!obj.isAliased(i))
             return false;
     return true;
 }
 #endif
 
 static bool
-ComputeAliasedSlots(ExclusiveContext *cx, BytecodeEmitter *bce, StaticBlockObject &blockObj)
-{
-    uint32_t depthPlusFixed = blockObj.stackDepth();
+ComputeAliasedSlots(ExclusiveContext *cx, BytecodeEmitter *bce, Handle<StaticBlockObject *> blockObj)
+{
+    uint32_t depthPlusFixed = blockObj->stackDepth();
     if (!AdjustBlockSlot(cx, bce, &depthPlusFixed))
         return false;
 
-    for (unsigned i = 0; i < blockObj.slotCount(); i++) {
-        Definition *dn = blockObj.maybeDefinitionParseNode(i);
+    for (unsigned i = 0; i < blockObj->slotCount(); i++) {
+        Definition *dn = blockObj->maybeDefinitionParseNode(i);
 
         /* Beware the empty destructuring dummy. */
         if (!dn) {
-            blockObj.setAliased(i, bce->sc->allLocalsAliased());
+            blockObj->setAliased(i, bce->sc->allLocalsAliased());
             continue;
         }
 
         JS_ASSERT(dn->isDefn());
         JS_ASSERT(dn->frameSlot() + depthPlusFixed < JS_BIT(16));
         if (!dn->pn_cookie.set(bce->parser->tokenStream, dn->pn_cookie.level(),
                                uint16_t(dn->frameSlot() + depthPlusFixed)))
             return false;
@@ -733,20 +733,20 @@ ComputeAliasedSlots(ExclusiveContext *cx
 #ifdef DEBUG
         for (ParseNode *pnu = dn->dn_uses; pnu; pnu = pnu->pn_link) {
             JS_ASSERT(pnu->pn_lexdef == dn);
             JS_ASSERT(!(pnu->pn_dflags & PND_BOUND));
             JS_ASSERT(pnu->pn_cookie.isFree());
         }
 #endif
 
-        blockObj.setAliased(i, bce->isAliasedName(dn));
-    }
-
-    JS_ASSERT_IF(bce->sc->allLocalsAliased(), AllLocalsAliased(blockObj));
+        blockObj->setAliased(i, bce->isAliasedName(dn));
+    }
+
+    JS_ASSERT_IF(bce->sc->allLocalsAliased(), AllLocalsAliased(*blockObj));
 
     return true;
 }
 
 static bool
 EmitInternedObjectOp(ExclusiveContext *cx, uint32_t index, JSOp op, BytecodeEmitter *bce);
 
 // ~ Block Scopes ~
@@ -804,39 +804,39 @@ EnterBlockScope(ExclusiveContext *cx, By
 {
     uint32_t parent = BlockScopeNote::NoBlockScopeIndex;
     if (bce->blockChain) {
         StmtInfoBCE *stmt = bce->topScopeStmt;
         for (; stmt->blockObj != bce->blockChain; stmt = stmt->down) {}
         parent = stmt->blockScopeIndex;
     }
 
-    StaticBlockObject &blockObj = objbox->object->as<StaticBlockObject>();
+    Rooted<StaticBlockObject *> blockObj(cx, &objbox->object->as<StaticBlockObject>());
 
     uint32_t scopeObjectIndex = bce->objectList.add(objbox);
 
-    int depth = bce->stackDepth - (blockObj.slotCount() + extraSlots);
+    int depth = bce->stackDepth - (blockObj->slotCount() + extraSlots);
     JS_ASSERT(depth >= 0);
-    blockObj.setStackDepth(depth);
+    blockObj->setStackDepth(depth);
 
     if (!ComputeAliasedSlots(cx, bce, blockObj))
         return false;
 
-    if (blockObj.needsClone()) {
+    if (blockObj->needsClone()) {
         if (!EmitInternedObjectOp(cx, scopeObjectIndex, JSOP_PUSHBLOCKSCOPE, bce))
             return false;
     }
 
     stmt->blockScopeIndex = bce->blockScopeList.length();
     if (!bce->blockScopeList.append(scopeObjectIndex, bce->offset(), parent))
         return false;
 
     PushStatementBCE(bce, stmt, STMT_BLOCK, bce->offset());
-    blockObj.initEnclosingStaticScope(EnclosingStaticScope(bce));
-    FinishPushBlockScope(bce, stmt, blockObj);
+    blockObj->initEnclosingStaticScope(EnclosingStaticScope(bce));
+    FinishPushBlockScope(bce, stmt, *blockObj);
 
     JS_ASSERT(stmt->isBlockScope);
 
     return true;
 }
 
 // Patches |breaks| and |continues| unless the top statement info record
 // represents a try-catch-finally suite. May fail if a jump offset overflows.
@@ -2379,17 +2379,17 @@ EmitSwitch(ExclusiveContext *cx, Bytecod
 
     pn2 = pn->pn_right;
     JS_ASSERT(pn2->isKind(PNK_LEXICALSCOPE) || pn2->isKind(PNK_STATEMENTLIST));
 
     /*
      * If there are hoisted let declarations, their stack slots go under the
      * discriminant's value so push their slots now and enter the block later.
      */
-    StaticBlockObject *blockObj = nullptr;
+    Rooted<StaticBlockObject *> blockObj(cx, nullptr);
     if (pn2->isKind(PNK_LEXICALSCOPE)) {
         blockObj = &pn2->pn_objbox->object->as<StaticBlockObject>();
         for (uint32_t i = 0; i < blockObj->slotCount(); ++i) {
             if (Emit1(cx, bce, JSOP_UNDEFINED) < 0)
                 return false;
         }
     }
 
@@ -2799,17 +2799,23 @@ frontend::EmitFunctionScript(ExclusiveCo
     if (runOnce) {
         bce->script->setTreatAsRunOnce();
         JS_ASSERT(!bce->script->hasRunOnce());
     }
 
     /* Initialize fun->script() so that the debugger has a valid fun->script(). */
     RootedFunction fun(cx, bce->script->function());
     JS_ASSERT(fun->isInterpreted());
-    fun->setScript(bce->script);
+
+    if (fun->isInterpretedLazy()) {
+        AutoLockForCompilation lock(cx);
+        fun->setUnlazifiedScript(bce->script);
+    } else {
+        fun->setScript(bce->script);
+    }
 
     bce->tellDebuggerAboutCompiledScript(cx);
 
     return true;
 }
 
 static bool
 MaybeEmitVarDecl(ExclusiveContext *cx, BytecodeEmitter *bce, JSOp prologOp, ParseNode *pn,
--- a/js/src/frontend/Parser.cpp
+++ b/js/src/frontend/Parser.cpp
@@ -2848,27 +2848,28 @@ Parser<ParseHandler>::bindVarOrConst(Bin
 
     StmtInfoPC *stmt = LexicalLookup(pc, name, nullptr, (StmtInfoPC *)nullptr);
 
     if (stmt && stmt->type == STMT_WITH) {
         parser->handler.setFlag(pn, PND_DEOPTIMIZED);
         if (pc->sc->isFunctionBox()) {
             FunctionBox *funbox = pc->sc->asFunctionBox();
             funbox->setMightAliasLocals();
-
-            /*
-             * This definition isn't being added to the parse context's
-             * declarations, so make sure to indicate the need to deoptimize
-             * the script's arguments object. Mark the function as if it
-             * contained a debugger statement, which will deoptimize arguments
-             * as much as possible.
-             */
-            if (name == cx->names().arguments)
-                funbox->setHasDebuggerStatement();
         }
+
+        /*
+         * This definition isn't being added to the parse context's
+         * declarations, so make sure to indicate the need to deoptimize
+         * the script's arguments object. Mark the function as if it
+         * contained a debugger statement, which will deoptimize arguments
+         * as much as possible.
+         */
+        if (name == cx->names().arguments)
+            pc->sc->setHasDebuggerStatement();
+
         return true;
     }
 
     DefinitionList::Range defs = pc->decls().lookupMulti(name);
     JS_ASSERT_IF(stmt, !defs.empty());
 
     if (defs.empty()) {
         return pc->define(parser->tokenStream, name, pn,
--- a/js/src/gc/Marking.cpp
+++ b/js/src/gc/Marking.cpp
@@ -1124,66 +1124,54 @@ ScanTypeObject(GCMarker *gcmarker, types
 {
     unsigned count = type->getPropertyCount();
     for (unsigned i = 0; i < count; i++) {
         types::Property *prop = type->getProperty(i);
         if (prop && JSID_IS_STRING(prop->id))
             PushMarkStack(gcmarker, JSID_TO_STRING(prop->id));
     }
 
-    if (TaggedProto(type->proto).isObject())
-        PushMarkStack(gcmarker, type->proto);
+    if (type->proto().isObject())
+        PushMarkStack(gcmarker, type->proto().toObject());
 
     if (type->singleton && !type->lazy())
         PushMarkStack(gcmarker, type->singleton);
 
-    if (type->addendum) {
-        switch (type->addendum->kind) {
-          case types::TypeObjectAddendum::NewScript:
-            PushMarkStack(gcmarker, type->newScript()->fun);
-            PushMarkStack(gcmarker, type->newScript()->templateObject);
-            break;
-
-          case types::TypeObjectAddendum::TypedObject:
-            PushMarkStack(gcmarker, type->typedObject()->typeRepr->ownerObject());
-            break;
-        }
+    if (type->hasNewScript()) {
+        PushMarkStack(gcmarker, type->newScript()->fun);
+        PushMarkStack(gcmarker, type->newScript()->templateObject);
+    } else if (type->hasTypedObject()) {
+        PushMarkStack(gcmarker, type->typedObject()->typeRepr->ownerObject());
     }
 
     if (type->interpretedFunction)
         PushMarkStack(gcmarker, type->interpretedFunction);
 }
 
 static void
 gc::MarkChildren(JSTracer *trc, types::TypeObject *type)
 {
     unsigned count = type->getPropertyCount();
     for (unsigned i = 0; i < count; i++) {
         types::Property *prop = type->getProperty(i);
         if (prop)
             MarkId(trc, &prop->id, "type_prop");
     }
 
-    if (TaggedProto(type->proto).isObject())
-        MarkObject(trc, &type->proto, "type_proto");
+    if (type->proto().isObject())
+        MarkObject(trc, &type->protoRaw(), "type_proto");
 
     if (type->singleton && !type->lazy())
         MarkObject(trc, &type->singleton, "type_singleton");
 
-    if (type->addendum) {
-        switch (type->addendum->kind) {
-          case types::TypeObjectAddendum::NewScript:
-            MarkObject(trc, &type->newScript()->fun, "type_new_function");
-            MarkObject(trc, &type->newScript()->templateObject, "type_new_template");
-            break;
-
-          case types::TypeObjectAddendum::TypedObject:
-            type->typedObject()->typeRepr->mark(trc);
-            break;
-        }
+    if (type->hasNewScript()) {
+        MarkObject(trc, &type->newScript()->fun, "type_new_function");
+        MarkObject(trc, &type->newScript()->templateObject, "type_new_template");
+    } else if (type->hasTypedObject()) {
+        type->typedObject()->typeRepr->mark(trc);
     }
 
     if (type->interpretedFunction)
         MarkObject(trc, &type->interpretedFunction, "type_function");
 }
 
 static void
 gc::MarkChildren(JSTracer *trc, jit::IonCode *code)
@@ -1436,17 +1424,17 @@ GCMarker::processMarkStackTop(SliceBudge
 
         types::TypeObject *type = obj->typeFromGC();
         PushMarkStack(this, type);
 
         Shape *shape = obj->lastProperty();
         PushMarkStack(this, shape);
 
         /* Call the trace hook if necessary. */
-        const Class *clasp = type->clasp;
+        const Class *clasp = type->clasp();
         if (clasp->trace) {
             JS_ASSERT_IF(runtime->gcMode() == JSGC_MODE_INCREMENTAL &&
                          runtime->gcIncrementalEnabled,
                          clasp->flags & JSCLASS_IMPLEMENTS_BARRIERS);
             clasp->trace(this, obj);
         }
 
         if (!shape->isNative())
--- a/js/src/gc/RootMarking.cpp
+++ b/js/src/gc/RootMarking.cpp
@@ -42,17 +42,17 @@ typedef RootedValueMap::Enum RootEnum;
 #ifdef JSGC_USE_EXACT_ROOTING
 static inline void
 MarkExactStackRoot(JSTracer *trc, Rooted<void*> *rooter, ThingRootKind kind)
 {
     void **addr = (void **)rooter->address();
     if (IsNullTaggedPointer(*addr))
         return;
 
-    if (kind == THING_ROOT_OBJECT && *addr == Proxy::LazyProto)
+    if (kind == THING_ROOT_OBJECT && *addr == TaggedProto::LazyProto)
         return;
 
     switch (kind) {
       case THING_ROOT_OBJECT:      MarkObjectRoot(trc, (JSObject **)addr, "exact-object"); break;
       case THING_ROOT_STRING:      MarkStringRoot(trc, (JSString **)addr, "exact-string"); break;
       case THING_ROOT_SCRIPT:      MarkScriptRoot(trc, (JSScript **)addr, "exact-script"); break;
       case THING_ROOT_SHAPE:       MarkShapeRoot(trc, (Shape **)addr, "exact-shape"); break;
       case THING_ROOT_BASE_SHAPE:  MarkBaseShapeRoot(trc, (BaseShape **)addr, "exact-baseshape"); break;
--- a/js/src/jit-test/tests/asm.js/testBasic.js
+++ b/js/src/jit-test/tests/asm.js/testBasic.js
@@ -102,48 +102,16 @@ function assertTypeFailInEval(str)
     options("werror");
 }
 assertTypeFailInEval('function f({}) { "use asm"; function g() {} return g }');
 assertTypeFailInEval('function f({global}) { "use asm"; function g() {} return g }');
 assertTypeFailInEval('function f(global, {imports}) { "use asm"; function g() {} return g }');
 assertTypeFailInEval('function f(g = 2) { "use asm"; function g() {} return g }');
 assertTypeFailInEval('function *f() { "use asm"; function g() {} return g }');
 
-function assertLinkFailInEval(str)
-{
-    if (!isAsmJSCompilationAvailable())
-        return;
-
-    var caught = false;
-    var oldOpts = options("werror");
-    assertEq(oldOpts.indexOf("werror"), -1);
-    try {
-        eval(str);
-    } catch (e) {
-        assertEq((''+e).indexOf(ASM_OK_STRING) == -1, false);
-        caught = true;
-    }
-    assertEq(caught, true);
-    options("werror");
-
-    var code = eval(str);
-
-    var caught = false;
-    var oldOpts = options("werror");
-    assertEq(oldOpts.indexOf("werror"), -1);
-    try {
-        code.apply(null, Array.slice(arguments, 1));
-    } catch (e) {
-        caught = true;
-    }
-    assertEq(caught, true);
-    options("werror");
-}
-assertLinkFailInEval('(function(global) { "use asm"; var im=global.Math.imul; function g() {} return g })');
-
 assertThrowsInstanceOf(function() { new Function(USE_ASM + 'var)') }, SyntaxError);
 assertThrowsInstanceOf(function() { new Function(USE_ASM + 'return)') }, SyntaxError);
 assertThrowsInstanceOf(function() { new Function(USE_ASM + 'var z=-2w') }, SyntaxError);
 assertThrowsInstanceOf(function() { new Function(USE_ASM + 'var z=-2w;') }, SyntaxError);
 assertThrowsInstanceOf(function() { new Function(USE_ASM + 'function') }, SyntaxError);
 assertThrowsInstanceOf(function() { new Function(USE_ASM + 'function f') }, SyntaxError);
 assertThrowsInstanceOf(function() { new Function(USE_ASM + 'function f(') }, SyntaxError);
 assertThrowsInstanceOf(function() { new Function(USE_ASM + 'function f()') }, SyntaxError);
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/basic/bug937089.js
@@ -0,0 +1,10 @@
+
+function test1() {
+  eval("with (arguments) var arguments = 0;");
+}
+test1();
+
+function test2() {
+  eval("eval('with (arguments) var arguments = 0;')");
+}
+test2();
--- a/js/src/jit/BaselineCompiler.cpp
+++ b/js/src/jit/BaselineCompiler.cpp
@@ -181,20 +181,18 @@ BaselineCompiler::compile()
                                                          pcEntries.length(),
                                                          bytecodeTypeMapEntries);
     if (!baselineScript)
         return Method_Error;
 
     baselineScript->setMethod(code);
     baselineScript->setTemplateScope(templateScope);
 
-    script->setBaselineScript(baselineScript);
-
     IonSpew(IonSpew_BaselineScripts, "Created BaselineScript %p (raw %p) for %s:%d",
-            (void *) script->baselineScript(), (void *) code->raw(),
+            (void *) baselineScript, (void *) code->raw(),
             script->filename(), script->lineno());
 
 #ifdef JS_ION_PERF
     writePerfSpewerBaselineProfile(script, code);
 #endif
 
     JS_ASSERT(pcMappingIndexEntries.length() > 0);
     baselineScript->copyPCMappingIndexEntries(&pcMappingIndexEntries[0]);
@@ -249,16 +247,18 @@ BaselineCompiler::compile()
         // The last entry in the last index found, and is used to avoid binary
         // searches for the sought entry when queries are in linear order.
         bytecodeMap[script->nTypeSets()] = 0;
     }
 
     if (script->compartment()->debugMode())
         baselineScript->setDebugMode();
 
+    script->setBaselineScript(cx, baselineScript);
+
     return Method_Compiled;
 }
 
 bool
 BaselineCompiler::emitPrologue()
 {
     masm.push(BaselineFrameReg);
     masm.mov(BaselineStackReg, BaselineFrameReg);
--- a/js/src/jit/BaselineIC.cpp
+++ b/js/src/jit/BaselineIC.cpp
@@ -125,22 +125,26 @@ ICStubIterator::operator++()
     if (!unlinked_)
         previousStub_ = currentStub_;
     currentStub_ = currentStub_->next();
     unlinked_ = false;
     return *this;
 }
 
 void
-ICStubIterator::unlink(Zone *zone)
+ICStubIterator::unlink(JSContext *cx)
 {
     JS_ASSERT(currentStub_->next() != nullptr);
     JS_ASSERT(currentStub_ != fallbackStub_);
     JS_ASSERT(!unlinked_);
-    fallbackStub_->unlinkStub(zone, previousStub_, currentStub_);
+
+    {
+        AutoLockForCompilation lock(cx);
+        fallbackStub_->unlinkStub(cx->zone(), previousStub_, currentStub_);
+    }
 
     // Mark the current iterator position as unlinked, so operator++ works properly.
     unlinked_ = true;
 }
 
 
 void
 ICStub::markCode(JSTracer *trc, const char *name)
@@ -167,24 +171,24 @@ ICStub::trace(JSTracer *trc)
     // If the stub is a monitored fallback stub, then mark the monitor ICs hanging
     // off of that stub.  We don't need to worry about the regular monitored stubs,
     // because the regular monitored stubs will always have a monitored fallback stub
     // that references the same stub chain.
     if (isMonitoredFallback()) {
         ICTypeMonitor_Fallback *lastMonStub = toMonitoredFallbackStub()->fallbackMonitorStub();
         for (ICStubConstIterator iter = lastMonStub->firstMonitorStub(); !iter.atEnd(); iter++) {
             JS_ASSERT_IF(iter->next() == nullptr, *iter == lastMonStub);
-            iter->markCode(trc, "baseline-monitor-stub-ioncode");
+            iter->trace(trc);
         }
     }
 
     if (isUpdated()) {
         for (ICStubConstIterator iter = toUpdatedStub()->firstUpdateStub(); !iter.atEnd(); iter++) {
             JS_ASSERT_IF(iter->next() == nullptr, iter->isTypeUpdate_Fallback());
-            iter->markCode(trc, "baseline-update-stub-ioncode");
+            iter->trace(trc);
         }
     }
 
     switch (kind()) {
       case ICStub::Call_Scripted: {
         ICCall_Scripted *callStub = toCall_Scripted();
         MarkScript(trc, &callStub->calleeScript(), "baseline-callscripted-callee");
         if (callStub->templateObject())
@@ -480,17 +484,17 @@ ICFallbackStub::unlinkStub(Zone *zone, I
 #endif
 }
 
 void
 ICFallbackStub::unlinkStubsWithKind(JSContext *cx, ICStub::Kind kind)
 {
     for (ICStubIterator iter = beginChain(); !iter.atEnd(); iter++) {
         if (iter->kind() == kind)
-            iter.unlink(cx->zone());
+            iter.unlink(cx);
     }
 }
 
 void
 ICTypeMonitor_Fallback::resetMonitorStubChain(Zone *zone)
 {
     if (zone->needsBarrier()) {
         // We are removing edges from monitored stubs to gcthings (IonCode).
@@ -1056,17 +1060,17 @@ DoProfilerFallback(JSContext *cx, Baseli
     IonSpew(IonSpew_BaselineIC, "  Generating Profiler_PushFunction stub for %s:%d",
             script->filename(), script->lineno());
 
     // Create a new optimized stub.
     ICProfiler_PushFunction::Compiler compiler(cx, string, script);
     ICStub *optStub = compiler.getStub(compiler.getStubSpace(script));
     if (!optStub)
         return false;
-    stub->addNewStub(optStub);
+    stub->addNewStub(cx, optStub);
 
     return true;
 }
 
 typedef bool (*DoProfilerFallbackFn)(JSContext *, BaselineFrame *frame, ICProfiler_Fallback *);
 static const VMFunction DoProfilerFallbackInfo =
     FunctionInfo<DoProfilerFallbackFn>(DoProfilerFallback);
 
@@ -1141,18 +1145,20 @@ ICTypeMonitor_Fallback::addMonitorStubFo
                 if (existingStub->containsType(type))
                     return true;
             }
         }
 
         ICTypeMonitor_PrimitiveSet::Compiler compiler(cx, existingStub, type);
         ICStub *stub = existingStub ? compiler.updateStub()
                                     : compiler.getStub(compiler.getStubSpace(script));
-        if (!stub)
-            return false;
+        if (!stub) {
+            js_ReportOutOfMemory(cx);
+            return false;
+        }
 
         IonSpew(IonSpew_BaselineIC, "  %s TypeMonitor stub %p for primitive type %d",
                 existingStub ? "Modified existing" : "Created new", stub, type);
 
         if (!existingStub) {
             JS_ASSERT(!hasStub(TypeMonitor_PrimitiveSet));
             addOptimizedMonitorStub(stub);
         }
@@ -1166,18 +1172,20 @@ ICTypeMonitor_Fallback::addMonitorStubFo
                 iter->toTypeMonitor_SingleObject()->object() == obj)
             {
                 return true;
             }
         }
 
         ICTypeMonitor_SingleObject::Compiler compiler(cx, obj);
         ICStub *stub = compiler.getStub(compiler.getStubSpace(script));
-        if (!stub)
-            return false;
+        if (!stub) {
+            js_ReportOutOfMemory(cx);
+            return false;
+        }
 
         IonSpew(IonSpew_BaselineIC, "  Added TypeMonitor stub %p for singleton %p",
                 stub, obj.get());
 
         addOptimizedMonitorStub(stub);
 
     } else {
         RootedTypeObject type(cx, val.toObject().type());
@@ -1188,18 +1196,20 @@ ICTypeMonitor_Fallback::addMonitorStubFo
                 iter->toTypeMonitor_TypeObject()->type() == type)
             {
                 return true;
             }
         }
 
         ICTypeMonitor_TypeObject::Compiler compiler(cx, type);
         ICStub *stub = compiler.getStub(compiler.getStubSpace(script));
-        if (!stub)
-            return false;
+        if (!stub) {
+            js_ReportOutOfMemory(cx);
+            return false;
+        }
 
         IonSpew(IonSpew_BaselineIC, "  Added TypeMonitor stub %p for TypeObject %p",
                 stub, type.get());
 
         addOptimizedMonitorStub(stub);
     }
 
     bool firstMonitorStubAdded = wasDetachedMonitorChain && (numOptimizedMonitorStubs_ > 0);
@@ -1780,17 +1790,17 @@ DoCompareFallback(JSContext *cx, Baselin
     // Try to generate new stubs.
     if (lhs.isInt32() && rhs.isInt32()) {
         IonSpew(IonSpew_BaselineIC, "  Generating %s(Int32, Int32) stub", js_CodeName[op]);
         ICCompare_Int32::Compiler compiler(cx, op);
         ICStub *int32Stub = compiler.getStub(compiler.getStubSpace(script));
         if (!int32Stub)
             return false;
 
-        stub->addNewStub(int32Stub);
+        stub->addNewStub(cx, int32Stub);
         return true;
     }
 
     if (!cx->runtime()->jitSupportsFloatingPoint && (lhs.isNumber() || rhs.isNumber()))
         return true;
 
     if (lhs.isNumber() && rhs.isNumber()) {
         IonSpew(IonSpew_BaselineIC, "  Generating %s(Number, Number) stub", js_CodeName[op]);
@@ -1798,80 +1808,80 @@ DoCompareFallback(JSContext *cx, Baselin
         // Unlink int32 stubs, it's faster to always use the double stub.
         stub->unlinkStubsWithKind(cx, ICStub::Compare_Int32);
 
         ICCompare_Double::Compiler compiler(cx, op);
         ICStub *doubleStub = compiler.getStub(compiler.getStubSpace(script));
         if (!doubleStub)
             return false;
 
-        stub->addNewStub(doubleStub);
+        stub->addNewStub(cx, doubleStub);
         return true;
     }
 
     if ((lhs.isNumber() && rhs.isUndefined()) ||
         (lhs.isUndefined() && rhs.isNumber()))
     {
         IonSpew(IonSpew_BaselineIC, "  Generating %s(%s, %s) stub", js_CodeName[op],
                     rhs.isUndefined() ? "Number" : "Undefined",
                     rhs.isUndefined() ? "Undefined" : "Number");
         ICCompare_NumberWithUndefined::Compiler compiler(cx, op, lhs.isUndefined());
         ICStub *doubleStub = compiler.getStub(compiler.getStubSpace(script));
         if (!doubleStub)
             return false;
 
-        stub->addNewStub(doubleStub);
+        stub->addNewStub(cx, doubleStub);
         return true;
     }
 
     if (lhs.isBoolean() && rhs.isBoolean()) {
         IonSpew(IonSpew_BaselineIC, "  Generating %s(Boolean, Boolean) stub", js_CodeName[op]);
         ICCompare_Boolean::Compiler compiler(cx, op);
         ICStub *booleanStub = compiler.getStub(compiler.getStubSpace(script));
         if (!booleanStub)
             return false;
 
-        stub->addNewStub(booleanStub);
+        stub->addNewStub(cx, booleanStub);
         return true;
     }
 
     if ((lhs.isBoolean() && rhs.isInt32()) || (lhs.isInt32() && rhs.isBoolean())) {
         IonSpew(IonSpew_BaselineIC, "  Generating %s(%s, %s) stub", js_CodeName[op],
                     rhs.isInt32() ? "Boolean" : "Int32",
                     rhs.isInt32() ? "Int32" : "Boolean");
         ICCompare_Int32WithBoolean::Compiler compiler(cx, op, lhs.isInt32());
         ICStub *optStub = compiler.getStub(compiler.getStubSpace(script));
         if (!optStub)
             return false;
 
-        stub->addNewStub(optStub);
+        stub->addNewStub(cx, optStub);
         return true;
     }
 
     if (IsEqualityOp(op)) {
         if (lhs.isString() && rhs.isString() && !stub->hasStub(ICStub::Compare_String)) {
             IonSpew(IonSpew_BaselineIC, "  Generating %s(String, String) stub", js_CodeName[op]);
             ICCompare_String::Compiler compiler(cx, op);
             ICStub *stringStub = compiler.getStub(compiler.getStubSpace(script));
             if (!stringStub)
                 return false;
 
-            stub->addNewStub(stringStub);
+            stub->addNewStub(cx, stringStub);
             return true;
         }
 
         if (lhs.isObject() && rhs.isObject()) {
             JS_ASSERT(!stub->hasStub(ICStub::Compare_Object));
             IonSpew(IonSpew_BaselineIC, "  Generating %s(Object, Object) stub", js_CodeName[op]);
             ICCompare_Object::Compiler compiler(cx, op);
             ICStub *objectStub = compiler.getStub(compiler.getStubSpace(script));
             if (!objectStub)
                 return false;
 
-            stub->addNewStub(objectStub);
+            stub->addNewStub(cx, objectStub);
             return true;
         }
 
         if ((lhs.isObject() || lhs.isNull() || lhs.isUndefined()) &&
             (rhs.isObject() || rhs.isNull() || rhs.isUndefined()) &&
             !stub->hasStub(ICStub::Compare_ObjectWithUndefined))
         {
             IonSpew(IonSpew_BaselineIC, "  Generating %s(Obj/Null/Undef, Obj/Null/Undef) stub",
@@ -1879,17 +1889,17 @@ DoCompareFallback(JSContext *cx, Baselin
             bool lhsIsUndefined = lhs.isNull() || lhs.isUndefined();
             bool compareWithNull = lhs.isNull() || rhs.isNull();
             ICCompare_ObjectWithUndefined::Compiler compiler(cx, op,
                                                              lhsIsUndefined, compareWithNull);
             ICStub *objectStub = compiler.getStub(compiler.getStubSpace(script));
             if (!objectStub)
                 return false;
 
-            stub->addNewStub(objectStub);
+            stub->addNewStub(cx, objectStub);
             return true;
         }
     }
 
     return true;
 }
 
 typedef bool (*DoCompareFallbackFn)(JSContext *, BaselineFrame *, ICCompare_Fallback *,
@@ -2085,17 +2095,17 @@ ICCompare_ObjectWithUndefined::Compiler:
         // obj !== undefined for all objects.
         masm.moveValue(BooleanValue(op == JSOP_STRICTNE), R0);
         EmitReturnFromIC(masm);
     } else {
         // obj != undefined only where !obj->getClass()->emulatesUndefined()
         Label emulatesUndefined;
         Register obj = masm.extractObject(objectOperand, ExtractTemp0);
         masm.loadPtr(Address(obj, JSObject::offsetOfType()), obj);
-        masm.loadPtr(Address(obj, offsetof(types::TypeObject, clasp)), obj);
+        masm.loadPtr(Address(obj, types::TypeObject::offsetOfClasp()), obj);
         masm.branchTest32(Assembler::NonZero,
                           Address(obj, Class::offsetOfFlags()),
                           Imm32(JSCLASS_EMULATES_UNDEFINED),
                           &emulatesUndefined);
         masm.moveValue(BooleanValue(op == JSOP_NE), R0);
         EmitReturnFromIC(masm);
         masm.bind(&emulatesUndefined);
         masm.moveValue(BooleanValue(op == JSOP_EQ), R0);
@@ -2191,60 +2201,60 @@ DoToBoolFallback(JSContext *cx, Baseline
     // Try to generate new stubs.
     if (arg.isInt32()) {
         IonSpew(IonSpew_BaselineIC, "  Generating ToBool(Int32) stub.");
         ICToBool_Int32::Compiler compiler(cx);
         ICStub *int32Stub = compiler.getStub(compiler.getStubSpace(script));
         if (!int32Stub)
             return false;
 
-        stub->addNewStub(int32Stub);
+        stub->addNewStub(cx, int32Stub);
         return true;
     }
 
     if (arg.isDouble() && cx->runtime()->jitSupportsFloatingPoint) {
         IonSpew(IonSpew_BaselineIC, "  Generating ToBool(Double) stub.");
         ICToBool_Double::Compiler compiler(cx);
         ICStub *doubleStub = compiler.getStub(compiler.getStubSpace(script));
         if (!doubleStub)
             return false;
 
-        stub->addNewStub(doubleStub);
+        stub->addNewStub(cx, doubleStub);
         return true;
     }
 
     if (arg.isString()) {
         IonSpew(IonSpew_BaselineIC, "  Generating ToBool(String) stub");
         ICToBool_String::Compiler compiler(cx);
         ICStub *stringStub = compiler.getStub(compiler.getStubSpace(script));
         if (!stringStub)
             return false;
 
-        stub->addNewStub(stringStub);
+        stub->addNewStub(cx, stringStub);
         return true;
     }
 
     if (arg.isNull() || arg.isUndefined()) {
         ICToBool_NullUndefined::Compiler compiler(cx);
         ICStub *nilStub = compiler.getStub(compiler.getStubSpace(script));
         if (!nilStub)
             return false;
 
-        stub->addNewStub(nilStub);
+        stub->addNewStub(cx, nilStub);
         return true;
     }
 
     if (arg.isObject()) {
         IonSpew(IonSpew_BaselineIC, "  Generating ToBool(Object) stub.");
         ICToBool_Object::Compiler compiler(cx);
         ICStub *objStub = compiler.getStub(compiler.getStubSpace(script));
         if (!objStub)
             return false;
 
-        stub->addNewStub(objStub);
+        stub->addNewStub(cx, objStub);
         return true;
     }
 
     return true;
 }
 
 typedef bool (*pf)(JSContext *, BaselineFrame *, ICToBool_Fallback *, HandleValue,
                    MutableHandleValue);
@@ -2526,69 +2536,69 @@ DoBinaryArithFallback(JSContext *cx, Bas
             return false;
         break;
       }
       default:
         MOZ_ASSUME_UNREACHABLE("Unhandled baseline arith op");
     }
 
     if (ret.isDouble())
-        stub->setSawDoubleResult();
+        stub->setSawDoubleResult(cx);
 
     // Check to see if a new stub should be generated.
     if (stub->numOptimizedStubs() >= ICBinaryArith_Fallback::MAX_OPTIMIZED_STUBS) {
-        stub->noteUnoptimizableOperands();
+        stub->noteUnoptimizableOperands(cx);
         return true;
     }
 
     // Handle string concat.
     if (op == JSOP_ADD) {
         if (lhs.isString() && rhs.isString()) {
             IonSpew(IonSpew_BaselineIC, "  Generating %s(String, String) stub", js_CodeName[op]);
             JS_ASSERT(ret.isString());
             ICBinaryArith_StringConcat::Compiler compiler(cx);
             ICStub *strcatStub = compiler.getStub(compiler.getStubSpace(script));
             if (!strcatStub)
                 return false;
-            stub->addNewStub(strcatStub);
+            stub->addNewStub(cx, strcatStub);
             return true;
         }
 
         if ((lhs.isString() && rhs.isObject()) || (lhs.isObject() && rhs.isString())) {
             IonSpew(IonSpew_BaselineIC, "  Generating %s(%s, %s) stub", js_CodeName[op],
                     lhs.isString() ? "String" : "Object",
                     lhs.isString() ? "Object" : "String");
             JS_ASSERT(ret.isString());
             ICBinaryArith_StringObjectConcat::Compiler compiler(cx, lhs.isString());
             ICStub *strcatStub = compiler.getStub(compiler.getStubSpace(script));
             if (!strcatStub)
                 return false;
-            stub->addNewStub(strcatStub);
+            stub->addNewStub(cx, strcatStub);
             return true;
         }
     }
 
     if (((lhs.isBoolean() && (rhs.isBoolean() || rhs.isInt32())) ||
          (rhs.isBoolean() && (lhs.isBoolean() || lhs.isInt32()))) &&
         (op == JSOP_ADD || op == JSOP_SUB || op == JSOP_BITOR || op == JSOP_BITAND ||
          op == JSOP_BITXOR))
     {
         IonSpew(IonSpew_BaselineIC, "  Generating %s(%s, %s) stub", js_CodeName[op],
                 lhs.isBoolean() ? "Boolean" : "Int32", rhs.isBoolean() ? "Boolean" : "Int32");
         ICBinaryArith_BooleanWithInt32::Compiler compiler(cx, op, lhs.isBoolean(), rhs.isBoolean());
         ICStub *arithStub = compiler.getStub(compiler.getStubSpace(script));
         if (!arithStub)
             return false;
-        stub->addNewStub(arithStub);
+        stub->addNewStub(cx, arithStub);
         return true;
     }
 
     // Handle only int32 or double.
     if (!lhs.isNumber() || !rhs.isNumber()) {
-        stub->noteUnoptimizableOperands();
+        stub->noteUnoptimizableOperands(cx);
         return true;
     }
 
     JS_ASSERT(ret.isNumber());
 
     if (lhs.isDouble() || rhs.isDouble() || ret.isDouble()) {
         if (!cx->runtime()->jitSupportsFloatingPoint)
             return true;
@@ -2602,17 +2612,17 @@ DoBinaryArithFallback(JSContext *cx, Bas
             // Unlink int32 stubs, it's faster to always use the double stub.
             stub->unlinkStubsWithKind(cx, ICStub::BinaryArith_Int32);
             IonSpew(IonSpew_BaselineIC, "  Generating %s(Double, Double) stub", js_CodeName[op]);
 
             ICBinaryArith_Double::Compiler compiler(cx, op);
             ICStub *doubleStub = compiler.getStub(compiler.getStubSpace(script));
             if (!doubleStub)
                 return false;
-            stub->addNewStub(doubleStub);
+            stub->addNewStub(cx, doubleStub);
             return true;
           }
           default:
             break;
         }
     }
 
     if (lhs.isInt32() && rhs.isInt32()) {
@@ -2620,17 +2630,17 @@ DoBinaryArithFallback(JSContext *cx, Bas
         if (allowDouble)
             stub->unlinkStubsWithKind(cx, ICStub::BinaryArith_Int32);
         IonSpew(IonSpew_BaselineIC, "  Generating %s(Int32, Int32%s) stub", js_CodeName[op],
                 allowDouble ? " => Double" : "");
         ICBinaryArith_Int32::Compiler compilerInt32(cx, op, allowDouble);
         ICStub *int32Stub = compilerInt32.getStub(compilerInt32.getStubSpace(script));
         if (!int32Stub)
             return false;
-        stub->addNewStub(int32Stub);
+        stub->addNewStub(cx, int32Stub);
         return true;
     }
 
     // Handle Double <BITOP> Int32 or Int32 <BITOP> Double case.
     if (((lhs.isDouble() && rhs.isInt32()) || (lhs.isInt32() && rhs.isDouble())) &&
         ret.isInt32())
     {
         switch(op) {
@@ -2639,25 +2649,25 @@ DoBinaryArithFallback(JSContext *cx, Bas
           case JSOP_BITAND: {
             IonSpew(IonSpew_BaselineIC, "  Generating %s(%s, %s) stub", js_CodeName[op],
                         lhs.isDouble() ? "Double" : "Int32",
                         lhs.isDouble() ? "Int32" : "Double");
             ICBinaryArith_DoubleWithInt32::Compiler compiler(cx, op, lhs.isDouble());
             ICStub *optStub = compiler.getStub(compiler.getStubSpace(script));
             if (!optStub)
                 return false;
-            stub->addNewStub(optStub);
+            stub->addNewStub(cx, optStub);
             return true;
           }
           default:
             break;
         }
     }
 
-    stub->noteUnoptimizableOperands();
+    stub->noteUnoptimizableOperands(cx);
     return true;
 }
 #if defined(_MSC_VER)
 # pragma optimize("", on)
 #endif
 
 typedef bool (*DoBinaryArithFallbackFn)(JSContext *, BaselineFrame *, ICBinaryArith_Fallback *,
                                         HandleValue, HandleValue, MutableHandleValue);
@@ -3042,31 +3052,31 @@ DoUnaryArithFallback(JSContext *cx, Base
     }
 
     if (val.isInt32() && res.isInt32()) {
         IonSpew(IonSpew_BaselineIC, "  Generating %s(Int32 => Int32) stub", js_CodeName[op]);
         ICUnaryArith_Int32::Compiler compiler(cx, op);
         ICStub *int32Stub = compiler.getStub(compiler.getStubSpace(script));
         if (!int32Stub)
             return false;
-        stub->addNewStub(int32Stub);
+        stub->addNewStub(cx, int32Stub);
         return true;
     }
 
     if (val.isNumber() && res.isNumber() && cx->runtime()->jitSupportsFloatingPoint) {
         IonSpew(IonSpew_BaselineIC, "  Generating %s(Number => Number) stub", js_CodeName[op]);
 
         // Unlink int32 stubs, the double stub handles both cases and TI specializes for both.
         stub->unlinkStubsWithKind(cx, ICStub::UnaryArith_Int32);
 
         ICUnaryArith_Double::Compiler compiler(cx, op);
         ICStub *doubleStub = compiler.getStub(compiler.getStubSpace(script));
         if (!doubleStub)
             return false;
-        stub->addNewStub(doubleStub);
+        stub->addNewStub(cx, doubleStub);
         return true;
     }
 
     return true;
 }
 #if define