merge mozilla-inbound to mozilla-central a=merge
authorCarsten "Tomcat" Book <cbook@mozilla.com>
Wed, 15 Jun 2016 06:24:33 +0100
changeset 341849 14c5bf11d37b9e92d27f7089d9392de2ac339bb3
parent 341758 53f5b5c289fba6ad82c675578cf1c548ae37f0c1 (current diff)
parent 341848 e05b52a2043562838eac7c98713948474534c0d9 (diff)
child 341855 da53037ac8087280b90ee94a55f116a24e5e9730
child 341871 683c7cabedaa562f2772c8c13e58220b5f4b01a5
push id6389
push userraliiev@mozilla.com
push dateMon, 19 Sep 2016 13:38:22 +0000
treeherdermozilla-beta@01d67bfe6c81 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmerge
milestone50.0a1
first release with
nightly linux32
14c5bf11d37b / 50.0a1 / 20160615030209 / files
nightly linux64
14c5bf11d37b / 50.0a1 / 20160615030209 / files
nightly mac
14c5bf11d37b / 50.0a1 / 20160615030209 / files
nightly win32
14c5bf11d37b / 50.0a1 / 20160615030209 / files
nightly win64
14c5bf11d37b / 50.0a1 / 20160615030209 / 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 mozilla-inbound to mozilla-central a=merge
browser/components/contextualidentity/test/browser/browser_imageCache.js
dom/media/tests/mochitest/test_getUserMedia_addtrack_removetrack_events.html
dom/media/tests/mochitest/test_getUserMedia_trackEnded.html
dom/media/tests/mochitest/test_peerConnection_addtrack_removetrack_events.html
dom/plugins/ipc/TaskFactory.h
gfx/thebes/PrintTargetThebes.cpp
gfx/thebes/PrintTargetThebes.h
mobile/android/base/java/org/mozilla/gecko/tabqueue/TabQueueDispatcher.java
testing/web-platform/meta/webvtt/webvtt-api-for-browsers/vttcue-interface/align.html.ini
testing/web-platform/meta/webvtt/webvtt-api-for-browsers/vttcue-interface/vertical.html.ini
--- a/CLOBBER
+++ b/CLOBBER
@@ -17,9 +17,9 @@
 #
 # Modifying this file will now automatically clobber the buildbot machines \o/
 #
 
 # Are you updating CLOBBER because you think it's needed for your WebIDL
 # changes to stick? As of bug 928195, this shouldn't be necessary! Please
 # don't change CLOBBER for WebIDL changes any more.
 
-Merge day clobber
\ No newline at end of file
+Bug 1276696 - New Android dependencies
--- a/accessible/generic/DocAccessible-inl.h
+++ b/accessible/generic/DocAccessible-inl.h
@@ -86,29 +86,16 @@ DocAccessible::UpdateText(nsIContent* aT
   NS_ASSERTION(mNotificationController, "The document was shut down!");
 
   // Ignore the notification if initial tree construction hasn't been done yet.
   if (mNotificationController && HasLoadState(eTreeConstructed))
     mNotificationController->ScheduleTextUpdate(aTextNode);
 }
 
 inline void
-DocAccessible::UpdateRootElIfNeeded()
-{
-  dom::Element* rootEl = mDocumentNode->GetBodyElement();
-  if (!rootEl) {
-    rootEl = mDocumentNode->GetRootElement();
-  }
-  if (rootEl != mContent) {
-    mContent = rootEl;
-    SetRoleMapEntry(aria::GetRoleMap(rootEl));
-  }
-}
-
-inline void
 DocAccessible::AddScrollListener()
 {
   // Delay scroll initializing until the document has a root frame.
   if (!mPresShell->GetRootFrame())
     return;
 
   mDocFlags |= eScrollInitialized;
   nsIScrollableFrame* sf = mPresShell->GetRootScrollFrameAsScrollable();
--- a/accessible/generic/DocAccessible.cpp
+++ b/accessible/generic/DocAccessible.cpp
@@ -1594,16 +1594,20 @@ bool
 DocAccessible::UpdateAccessibleOnAttrChange(dom::Element* aElement,
                                             nsIAtom* aAttribute)
 {
   if (aAttribute == nsGkAtoms::role) {
     // It is common for js libraries to set the role on the body element after
     // the document has loaded. In this case we just update the role map entry.
     if (mContent == aElement) {
       SetRoleMapEntry(aria::GetRoleMap(aElement));
+      if (mIPCDoc) {
+        mIPCDoc->SendRoleChangedEvent(Role());
+      }
+
       return true;
     }
 
     // Recreate the accessible when role is changed because we might require a
     // different accessible class for the new role or the accessible may expose
     // a different sets of interfaces (COM restriction).
     RecreateAccessible(aElement);
 
@@ -1629,16 +1633,32 @@ DocAccessible::UpdateAccessibleOnAttrCha
     RecreateAccessible(aElement);
 
     return true;
   }
 
   return false;
 }
 
+void
+DocAccessible::UpdateRootElIfNeeded()
+{
+  dom::Element* rootEl = mDocumentNode->GetBodyElement();
+  if (!rootEl) {
+    rootEl = mDocumentNode->GetRootElement();
+  }
+  if (rootEl != mContent) {
+    mContent = rootEl;
+    SetRoleMapEntry(aria::GetRoleMap(rootEl));
+    if (mIPCDoc) {
+      mIPCDoc->SendRoleChangedEvent(Role());
+    }
+  }
+}
+
 /**
  * Content insertion helper.
  */
 class InsertIterator final
 {
 public:
   InsertIterator(Accessible* aContext,
                  const nsTArray<nsCOMPtr<nsIContent> >* aNodes) :
--- a/accessible/ipc/DocAccessibleParent.cpp
+++ b/accessible/ipc/DocAccessibleParent.cpp
@@ -322,16 +322,28 @@ DocAccessibleParent::RecvSelectionEvent(
   RefPtr<xpcAccEvent> event = new xpcAccEvent(aType, xpcTarget, xpcDoc,
                                               nullptr, false);
   nsCoreUtils::DispatchAccEvent(Move(event));
 
   return true;
 }
 
 bool
+DocAccessibleParent::RecvRoleChangedEvent(const uint32_t& aRole)
+{
+ if (aRole >= roles::LAST_ROLE) {
+   NS_ERROR("child sent bad role in RoleChangedEvent");
+   return false;
+ }
+
+ mRole = static_cast<a11y::role>(aRole);
+ return true;
+}
+
+bool
 DocAccessibleParent::RecvBindChildDoc(PDocAccessibleParent* aChildDoc, const uint64_t& aID)
 {
   // One document should never directly be the child of another.
   // We should always have at least an outer doc accessible in between.
   MOZ_ASSERT(aID);
   if (!aID)
     return false;
 
--- a/accessible/ipc/DocAccessibleParent.h
+++ b/accessible/ipc/DocAccessibleParent.h
@@ -65,16 +65,18 @@ public:
                                    const int32_t& aStart, const uint32_t& aLen,
                                    const bool& aIsInsert,
                                    const bool& aFromUser) override;
 
   virtual bool RecvSelectionEvent(const uint64_t& aID,
                                   const uint64_t& aWidgetID,
                                   const uint32_t& aType) override;
 
+  virtual bool RecvRoleChangedEvent(const uint32_t& aRole) override final;
+
   virtual bool RecvBindChildDoc(PDocAccessibleParent* aChildDoc, const uint64_t& aID) override;
   void Unbind()
   {
     mParent = nullptr;
     if (DocAccessibleParent* parent = ParentDoc()) {
       parent->mChildDocs.RemoveElement(this);
     }
 
--- a/accessible/ipc/PDocAccessible.ipdl
+++ b/accessible/ipc/PDocAccessible.ipdl
@@ -57,16 +57,17 @@ parent:
   async Event(uint64_t aID, uint32_t type);
   async ShowEvent(ShowEventData data, bool aFromuser);
   async HideEvent(uint64_t aRootID, bool aFromUser);
   async StateChangeEvent(uint64_t aID, uint64_t aState, bool aEnabled);
   async CaretMoveEvent(uint64_t aID, int32_t aOffset);
   async TextChangeEvent(uint64_t aID, nsString aStr, int32_t aStart, uint32_t aLen,
                         bool aIsInsert, bool aFromUser);
   async SelectionEvent(uint64_t aID, uint64_t aWidgetID, uint32_t aType);
+  async RoleChangedEvent(uint32_t aRole);
 
   /*
    * Tell the parent document to bind the existing document as a new child
    * document.
    */
   async BindChildDoc(PDocAccessible aChildDoc, uint64_t aID);
 
 child:
--- a/accessible/ipc/ProxyAccessible.h
+++ b/accessible/ipc/ProxyAccessible.h
@@ -426,17 +426,21 @@ protected:
 protected:
   ProxyAccessible* mParent;
 
 private:
   nsTArray<ProxyAccessible*> mChildren;
   DocAccessibleParent* mDoc;
   uintptr_t mWrapper;
   uint64_t mID;
+protected:
+  // XXX DocAccessibleParent gets to change this to change the role of
+  // documents.
   role mRole : 29;
+private:
   bool mOuterDoc : 1;
 
 public:
   const bool mIsDoc: 1;
   const bool mHasValue: 1;
 };
 
 }
--- a/browser/app/profile/firefox.js
+++ b/browser/app/profile/firefox.js
@@ -1338,17 +1338,21 @@ pref("media.gmp-provider.enabled", true)
 pref("privacy.trackingprotection.ui.enabled", true);
 #else
 pref("privacy.trackingprotection.ui.enabled", false);
 #endif
 pref("privacy.trackingprotection.introCount", 0);
 pref("privacy.trackingprotection.introURL", "https://www.mozilla.org/%LOCALE%/firefox/%VERSION%/tracking-protection/start/");
 
 // Enable Contextual Identity Containers
+#ifdef NIGHTLY_BUILD
+pref("privacy.userContext.enabled", true);
+#else
 pref("privacy.userContext.enabled", false);
+#endif
 
 #ifndef RELEASE_BUILD
 // At the moment, autostart.2 is used, while autostart.1 is unused.
 // We leave it here set to false to reset users' defaults and allow
 // us to change everybody to true in the future, when desired.
 pref("browser.tabs.remote.autostart.1", false);
 pref("browser.tabs.remote.autostart.2", true);
 #endif
--- a/browser/base/content/test/general/browser_contextmenu.js
+++ b/browser/base/content/test/general/browser_contextmenu.js
@@ -7,16 +7,17 @@ let LOGIN_FILL_ITEMS = [
     [
       "fill-login-no-logins", false,
       "---", null,
       "fill-login-saved-passwords", true
     ], null,
 ];
 
 let hasPocket = Services.prefs.getBoolPref("extensions.pocket.enabled");
+let hasContainers = Services.prefs.getBoolPref("privacy.userContext.enabled");
 
 add_task(function* test_setup() {
   const example_base = "http://example.com/browser/browser/base/content/test/general/";
   const url = example_base + "subtst_contextmenu.html";
   yield BrowserTestUtils.openNewForegroundTab(gBrowser, url);
 
   const chrome_base = "chrome://mochitests/content/browser/browser/base/content/test/general/";
   const contextmenu_common = chrome_base + "contextmenu_common.js";
@@ -57,16 +58,20 @@ add_task(function* test_plaintext() {
                     "context-viewinfo",     true
                    ];
   yield test_contextmenu("#test-text", plainTextItems);
 });
 
 add_task(function* test_link() {
   yield test_contextmenu("#test-link",
     ["context-openlinkintab", true,
+     ...(hasContainers ? ["context-openlinkinusercontext-menu", true] : []),
+     // We need a blank entry here because the containers submenu is
+     // dynamically generated with no ids.
+     ...(hasContainers ? ["", null] : []),
      "context-openlink",      true,
      "context-openlinkprivate", true,
      "---",                   null,
      "context-bookmarklink",  true,
      "context-savelink",      true,
      ...(hasPocket ? ["context-savelinktopocket", true] : []),
      "context-copylink",      true,
      "context-searchselect",  true
@@ -617,16 +622,20 @@ add_task(function* test_select_text() {
     }
   );
 });
 
 add_task(function* test_select_text_link() {
   yield test_contextmenu("#test-select-text-link",
     ["context-openlinkincurrent",           true,
      "context-openlinkintab",               true,
+     ...(hasContainers ? ["context-openlinkinusercontext-menu", true] : []),
+     // We need a blank entry here because the containers submenu is
+     // dynamically generated with no ids.
+     ...(hasContainers ? ["", null] : []),
      "context-openlink",                    true,
      "context-openlinkprivate",             true,
      "---",                                 null,
      "context-bookmarklink",                true,
      "context-savelink",                    true,
      "context-copy",                        true,
      "context-selectall",                   true,
      "---",                                 null,
@@ -647,16 +656,20 @@ add_task(function* test_select_text_link
       }
     }
   );
 });
 
 add_task(function* test_imagelink() {
   yield test_contextmenu("#test-image-link",
     ["context-openlinkintab", true,
+     ...(hasContainers ? ["context-openlinkinusercontext-menu", true] : []),
+     // We need a blank entry here because the containers submenu is
+     // dynamically generated with no ids.
+     ...(hasContainers ? ["", null] : []),
      "context-openlink",      true,
      "context-openlinkprivate", true,
      "---",                   null,
      "context-bookmarklink",  true,
      "context-savelink",      true,
      ...(hasPocket ? ["context-savelinktopocket", true] : []),
      "context-copylink",      true,
      "---",                   null,
--- a/browser/components/contextualidentity/test/browser/browser.ini
+++ b/browser/components/contextualidentity/test/browser/browser.ini
@@ -15,9 +15,8 @@ skip-if = os == "mac" || os == "win" # I
 [browser_windowName.js]
 tags = openwindow
 [browser_windowOpen.js]
 tags = openwindow
 [browser_serviceworkers.js]
 [browser_broadcastchannel.js]
 [browser_blobUrl.js]
 [browser_middleClick.js]
-[browser_imageCache.js]
deleted file mode 100644
--- a/browser/components/contextualidentity/test/browser/browser_imageCache.js
+++ /dev/null
@@ -1,59 +0,0 @@
-let Cu = Components.utils;
-let {HttpServer} = Cu.import("resource://testing-common/httpd.js", {});
-
-const NUM_USER_CONTEXTS = 3;
-
-let gHits = 0;
-
-let server = new HttpServer();
-server.registerPathHandler('/image.png', imageHandler);
-server.registerPathHandler('/file.html', fileHandler);
-server.start(-1);
-
-let BASE_URI = 'http://localhost:' + server.identity.primaryPort;
-let IMAGE_URI = BASE_URI + '/image.png';
-let FILE_URI = BASE_URI + '/file.html';
-
-function imageHandler(metadata, response) {
-  gHits++;
-  response.setHeader("Cache-Control", "max-age=10000", false);
-  response.setStatusLine(metadata.httpVersion, 200, "OK");
-  response.setHeader("Content-Type", "image/png", false);
-  var body = "iVBORw0KGgoAAAANSUhEUgAAAAMAAAADCAIAAADZSiLoAAAAEUlEQVQImWP4z8AAQTAamQkAhpcI+DeMzFcAAAAASUVORK5CYII=";
-  response.bodyOutputStream.write(body, body.length);
-}
-
-function fileHandler(metadata, response) {
-  response.setStatusLine(metadata.httpVersion, 200, "OK");
-  response.setHeader("Content-Type", "text/html", false);
-  let body = `<html><body><image src=${IMAGE_URI}></body></html>`;
-  response.bodyOutputStream.write(body, body.length);
-}
-
-add_task(function* setup() {
-  // make sure userContext is enabled.
-  yield SpecialPowers.pushPrefEnv({"set": [["privacy.userContext.enabled", true]]});
-});
-
-// opens `uri' in a new tab with the provided userContextId and focuses it.
-// returns the newly opened tab
-function* openTabInUserContext(uri, userContextId) {
-  // open the tab in the correct userContextId
-  let tab = gBrowser.addTab(uri, {userContextId});
-
-  // select tab and make sure its browser is focused
-  gBrowser.selectedTab = tab;
-  tab.ownerDocument.defaultView.focus();
-
-  let browser = gBrowser.getBrowserForTab(tab);
-  yield BrowserTestUtils.browserLoaded(browser);
-  return tab;
-}
-
-add_task(function* test() {
-  for (let userContextId = 0; userContextId < NUM_USER_CONTEXTS; userContextId++) {
-    let tab = yield* openTabInUserContext(FILE_URI, userContextId);
-    gBrowser.removeTab(tab);
-  }
-  is(gHits, NUM_USER_CONTEXTS, "should get an image request for each user contexts");
-});
--- a/browser/locales/en-US/chrome/browser/browser.dtd
+++ b/browser/locales/en-US/chrome/browser/browser.dtd
@@ -452,17 +452,17 @@ These should match what Safari and other
 <!ENTITY openLinkInPrivateWindowCmd.accesskey "P">
 <!ENTITY openLinkCmdInCurrent.label     "Open Link">
 <!ENTITY openLinkCmdInCurrent.accesskey "O">
 <!ENTITY openFrameCmdInTab.label      "Open Frame in New Tab">
 <!ENTITY openFrameCmdInTab.accesskey  "T">
 <!ENTITY openFrameCmd.label           "Open Frame in New Window">
 <!ENTITY openFrameCmd.accesskey       "W">
 <!ENTITY openLinkCmdInContainerTab.label "Open Link in New Container Tab">
-<!ENTITY openLinkCmdInContainerTab.accesskey "C">
+<!ENTITY openLinkCmdInContainerTab.accesskey "z">
 <!ENTITY showOnlyThisFrameCmd.label     "Show Only This Frame">
 <!ENTITY showOnlyThisFrameCmd.accesskey "S">
 <!ENTITY reloadCmd.commandkey         "r">
 <!ENTITY reloadFrameCmd.label         "Reload Frame">
 <!ENTITY reloadFrameCmd.accesskey     "R">
 <!ENTITY viewPartialSourceForSelectionCmd.label "View Selection Source">
 <!ENTITY viewPartialSourceForMathMLCmd.label    "View MathML Source">
 <!-- LOCALIZATION NOTE (viewPartialSourceCmd.accesskey): This accesskey is used for both 
--- a/build/autoconf/android.m4
+++ b/build/autoconf/android.m4
@@ -336,16 +336,17 @@ case "$target" in
     ANDROID_BUILD_TOOLS_VERSION="$2"
     AC_DEFINE_UNQUOTED(ANDROID_TARGET_SDK,$ANDROID_TARGET_SDK)
     AC_SUBST(ANDROID_TARGET_SDK)
     AC_SUBST(ANDROID_SDK_ROOT)
     AC_SUBST(ANDROID_SDK)
     AC_SUBST(ANDROID_TOOLS)
     AC_SUBST(ANDROID_BUILD_TOOLS_VERSION)
 
+    MOZ_ANDROID_AAR(customtabs, $ANDROID_SUPPORT_LIBRARY_VERSION, android, com/android/support)
     MOZ_ANDROID_AAR(appcompat-v7, $ANDROID_SUPPORT_LIBRARY_VERSION, android, com/android/support)
     MOZ_ANDROID_AAR(cardview-v7, $ANDROID_SUPPORT_LIBRARY_VERSION, android, com/android/support)
     MOZ_ANDROID_AAR(design, $ANDROID_SUPPORT_LIBRARY_VERSION, android, com/android/support)
     MOZ_ANDROID_AAR(recyclerview-v7, $ANDROID_SUPPORT_LIBRARY_VERSION, android, com/android/support)
     MOZ_ANDROID_AAR(support-v4, $ANDROID_SUPPORT_LIBRARY_VERSION, android, com/android/support, REQUIRED_INTERNAL_IMPL)
 
     ANDROID_SUPPORT_ANNOTATIONS_JAR="$ANDROID_SDK_ROOT/extras/android/m2repository/com/android/support/support-annotations/$ANDROID_SUPPORT_LIBRARY_VERSION/support-annotations-$ANDROID_SUPPORT_LIBRARY_VERSION.jar"
     AC_MSG_CHECKING([for support-annotations JAR])
--- a/dom/base/StructuredCloneHolder.cpp
+++ b/dom/base/StructuredCloneHolder.cpp
@@ -898,16 +898,17 @@ ReadFormData(JSContext* aCx,
 
         RefPtr<Blob> blob =
           Blob::Create(aHolder->ParentDuringRead(), blobImpl);
         MOZ_ASSERT(blob);
 
         ErrorResult rv;
         formData->Append(name, *blob, thirdArg, rv);
         if (NS_WARN_IF(rv.Failed())) {
+          rv.SuppressException();
           return nullptr;
         }
 
       } else {
         MOZ_ASSERT(tag == 0);
 
         nsAutoString value;
         value.SetLength(indexOrLengthOfString);
@@ -915,16 +916,17 @@ ReadFormData(JSContext* aCx,
         if (!JS_ReadBytes(aReader, (void*) value.BeginWriting(),
                           indexOrLengthOfString * charSize)) {
           return nullptr;
         }
 
         ErrorResult rv;
         formData->Append(name, value, rv);
         if (NS_WARN_IF(rv.Failed())) {
+          rv.SuppressException();
           return nullptr;
         }
       }
     }
 
     if (!ToJSValue(aCx, formData, &val)) {
       return nullptr;
     }
@@ -1061,16 +1063,21 @@ StructuredCloneHolder::CustomWriteHandle
       return WriteBlob(aWriter, blob, this);
     }
   }
 
   // See if this is a Directory object.
   {
     Directory* directory = nullptr;
     if (NS_SUCCEEDED(UNWRAP_OBJECT(Directory, aObj, directory))) {
+      if (mSupportedContext != SameProcessSameThread &&
+          !directory->ClonableToDifferentThreadOrProcess()) {
+        return false;
+      }
+
       return WriteDirectory(aWriter, directory);
     }
   }
 
   // See if this is a FileList object.
   {
     FileList* fileList = nullptr;
     if (NS_SUCCEEDED(UNWRAP_OBJECT(FileList, aObj, fileList))) {
@@ -1115,16 +1122,17 @@ StructuredCloneHolder::CustomReadTransfe
     const MessagePortIdentifier& portIdentifier = mPortIdentifiers[aExtraData];
 
     nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(mParent);
 
     ErrorResult rv;
     RefPtr<MessagePort> port =
       MessagePort::Create(global, portIdentifier, rv);
     if (NS_WARN_IF(rv.Failed())) {
+      rv.SuppressException();
       return false;
     }
 
     mTransferredPorts.AppendElement(port);
 
     JS::Rooted<JS::Value> value(aCx);
     if (!GetOrCreateDOMReflector(aCx, port, &value)) {
       JS_ClearPendingException(aCx);
--- a/dom/base/nsContentUtils.cpp
+++ b/dom/base/nsContentUtils.cpp
@@ -1064,19 +1064,19 @@ nsContentUtils::ParseHTMLInteger(const n
   }
 
   if (iter == end) {
     result |= eParseHTMLInteger_Error | eParseHTMLInteger_ErrorNoValue;
     *aResult = (ParseHTMLIntegerResultFlags)result;
     return 0;
   }
 
-  bool negate = false;
+  int sign = 1;
   if (*iter == char16_t('-')) {
-    negate = true;
+    sign = -1;
     ++iter;
   } else if (*iter == char16_t('+')) {
     result |= eParseHTMLInteger_NonStandard;
     ++iter;
   }
 
   bool foundValue = false;
   CheckedInt32 value = 0;
@@ -1090,17 +1090,17 @@ nsContentUtils::ParseHTMLInteger(const n
 
     ++leadingZeros;
     foundValue = true;
     ++iter;
   }
 
   while (iter != end) {
     if (*iter >= char16_t('0') && *iter <= char16_t('9')) {
-      value = (value * 10) + (*iter - char16_t('0'));
+      value = (value * 10) + (*iter - char16_t('0')) * sign;
       ++iter;
       if (!value.isValid()) {
         result |= eParseHTMLInteger_Error | eParseHTMLInteger_ErrorOverflow;
         break;
       } else {
         foundValue = true;
       }
     } else if (*iter == char16_t('%')) {
@@ -1111,26 +1111,19 @@ nsContentUtils::ParseHTMLInteger(const n
       break;
     }
   }
 
   if (!foundValue) {
     result |= eParseHTMLInteger_Error | eParseHTMLInteger_ErrorNoValue;
   }
 
-  if (value.isValid() && negate) {
-    value = -value;
-    // Checking the special case of -0.
-    if (value == 0) {
-      result |= eParseHTMLInteger_NonStandard;
-    }
-  }
-
   if (value.isValid() &&
-      (leadingZeros > 1 || (leadingZeros == 1 && !(value == 0)))) {
+       ((leadingZeros > 1 || (leadingZeros == 1 && !(value == 0))) ||
+       (sign == -1 && value == 0))) {
     result |= eParseHTMLInteger_NonStandard;
   }
 
   if (iter != end) {
     result |= eParseHTMLInteger_DidNotConsumeAllInput;
   }
 
   *aResult = (ParseHTMLIntegerResultFlags)result;
--- a/dom/events/test/test_all_synthetic_events.html
+++ b/dom/events/test/test_all_synthetic_events.html
@@ -298,18 +298,19 @@ const kEventConstructors = {
   MediaKeyMessageEvent:                          { create: function (aName, aProps) {
                                                          return new MediaKeyMessageEvent(aName, aProps);
                                                        },
                                              },
   MediaStreamEvent:                          { create: function (aName, aProps) {
                                                          return new MediaStreamEvent(aName, aProps);
                                                        },
                                              },
-  MediaStreamTrackEvent:                     {
-                                               // Difficult to test required arguments.
+  MediaStreamTrackEvent:                     { create: function (aName, aProps) {
+                                                         return new MediaStreamTrackEvent(aName, aProps);
+                                                       },
                                              },
   MessageEvent:                              { create: function (aName, aProps) {
                                                          var e = new MessageEvent("messageevent", { bubbles: aProps.bubbles,
                                                              cancelable: aProps.cancelable, data: aProps.data, origin: aProps.origin,
                                                              lastEventId: aProps.lastEventId, source: aProps.source });
                                                          return e;
                                                        },
                                              },
--- a/dom/html/HTMLMediaElement.cpp
+++ b/dom/html/HTMLMediaElement.cpp
@@ -1461,29 +1461,29 @@ NS_IMETHODIMP HTMLMediaElement::GetCurre
   return NS_OK;
 }
 
 void
 HTMLMediaElement::FastSeek(double aTime, ErrorResult& aRv)
 {
   LOG(LogLevel::Debug, ("Reporting telemetry VIDEO_FASTSEEK_USED"));
   Telemetry::Accumulate(Telemetry::VIDEO_FASTSEEK_USED, 1);
-  Seek(aTime, SeekTarget::PrevSyncPoint, aRv);
-}
-
-void
+  RefPtr<Promise> tobeDropped = Seek(aTime, SeekTarget::PrevSyncPoint, aRv);
+}
+
+already_AddRefed<Promise>
 HTMLMediaElement::SeekToNextFrame(ErrorResult& aRv)
 {
-  Seek(CurrentTime(), SeekTarget::NextFrame, aRv);
+  return Seek(CurrentTime(), SeekTarget::NextFrame, aRv);
 }
 
 void
 HTMLMediaElement::SetCurrentTime(double aCurrentTime, ErrorResult& aRv)
 {
-  Seek(aCurrentTime, SeekTarget::Accurate, aRv);
+  RefPtr<Promise> tobeDropped = Seek(aCurrentTime, SeekTarget::Accurate, aRv);
 }
 
 /**
  * Check if aValue is inside a range of aRanges, and if so sets aIsInRanges
  * to true and put the range index in aIntervalIndex. If aValue is not
  * inside a range, aIsInRanges is set to false, and aIntervalIndex
  * is set to the index of the range which ends immediately before aValue
  * (and can be -1 if aValue is before aRanges.Start(0)). Returns NS_OK
@@ -1514,113 +1514,130 @@ IsInRanges(dom::TimeRanges& aRanges,
       aIsInRanges = true;
       return NS_OK;
     }
   }
   aIntervalIndex = length - 1;
   return NS_OK;
 }
 
-void
+already_AddRefed<Promise>
 HTMLMediaElement::Seek(double aTime,
                        SeekTarget::Type aSeekType,
                        ErrorResult& aRv)
 {
   // aTime should be non-NaN.
   MOZ_ASSERT(!mozilla::IsNaN(aTime));
 
+  nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(OwnerDoc()->GetInnerWindow());
+
+  if (!global) {
+    aRv.Throw(NS_ERROR_UNEXPECTED);
+    return nullptr;
+  }
+
+  RefPtr<Promise> promise = Promise::Create(global, aRv);
+
+  if (NS_WARN_IF(aRv.Failed())) {
+    return nullptr;
+  }
+
   // Detect if user has interacted with element by seeking so that
   // play will not be blocked when initiated by a script.
   if (EventStateManager::IsHandlingUserInput() || nsContentUtils::LegacyIsCallerChromeOrNativeCode()) {
     mHasUserInteraction = true;
   }
 
   StopSuspendingAfterFirstFrame();
 
   if (mSrcStream) {
     // do nothing since media streams have an empty Seekable range.
-    return;
+    promise->MaybeRejectWithUndefined();
+    return promise.forget();
   }
 
   if (mPlayed && mCurrentPlayRangeStart != -1.0) {
     double rangeEndTime = CurrentTime();
     LOG(LogLevel::Debug, ("%p Adding \'played\' a range : [%f, %f]", this, mCurrentPlayRangeStart, rangeEndTime));
     // Multiple seek without playing, or seek while playing.
     if (mCurrentPlayRangeStart != rangeEndTime) {
       mPlayed->Add(mCurrentPlayRangeStart, rangeEndTime);
     }
     // Reset the current played range start time. We'll re-set it once
     // the seek completes.
     mCurrentPlayRangeStart = -1.0;
   }
 
   if (mReadyState == nsIDOMHTMLMediaElement::HAVE_NOTHING) {
     mDefaultPlaybackStartPosition = aTime;
-    return;
+    promise->MaybeRejectWithUndefined();
+    return promise.forget();
   }
 
   if (!mDecoder) {
     // mDecoder must always be set in order to reach this point.
     NS_ASSERTION(mDecoder, "SetCurrentTime failed: no decoder");
-    return;
+    promise->MaybeRejectWithUndefined();
+    return promise.forget();
   }
 
   // Clamp the seek target to inside the seekable ranges.
   RefPtr<dom::TimeRanges> seekable = new dom::TimeRanges(ToSupports(OwnerDoc()));
   media::TimeIntervals seekableIntervals = mDecoder->GetSeekable();
   if (seekableIntervals.IsInvalid()) {
-    aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
-    return;
+    aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR); // This will reject the promise.
+    return promise.forget();
   }
   seekableIntervals.ToTimeRanges(seekable);
   uint32_t length = 0;
   seekable->GetLength(&length);
   if (!length) {
-    return;
+    promise->MaybeRejectWithUndefined();
+    return promise.forget();
   }
 
   // If the position we want to seek to is not in a seekable range, we seek
   // to the closest position in the seekable ranges instead. If two positions
   // are equally close, we seek to the closest position from the currentTime.
   // See seeking spec, point 7 :
   // http://www.whatwg.org/specs/web-apps/current-work/multipage/the-video-element.html#seeking
   int32_t range = 0;
   bool isInRange = false;
   if (NS_FAILED(IsInRanges(*seekable, aTime, isInRange, range))) {
-    aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
-    return;
+    aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR); // This will reject the promise.
+    return promise.forget();
   }
   if (!isInRange) {
     if (range != -1) {
       // |range + 1| can't be negative, because the only possible negative value
       // for |range| is -1.
       if (uint32_t(range + 1) < length) {
         double leftBound, rightBound;
         if (NS_FAILED(seekable->End(range, &leftBound))) {
           aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
-          return;
+          return promise.forget();
         }
         if (NS_FAILED(seekable->Start(range + 1, &rightBound))) {
           aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
-          return;
+          return promise.forget();
         }
         double distanceLeft = Abs(leftBound - aTime);
         double distanceRight = Abs(rightBound - aTime);
         if (distanceLeft == distanceRight) {
           double currentTime = CurrentTime();
           distanceLeft = Abs(leftBound - currentTime);
           distanceRight = Abs(rightBound - currentTime);
         }
         aTime = (distanceLeft < distanceRight) ? leftBound : rightBound;
       } else {
         // Seek target is after the end last range in seekable data.
         // Clamp the seek target to the end of the last seekable range.
         if (NS_FAILED(seekable->End(length - 1, &aTime))) {
           aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
-          return;
+          return promise.forget();
         }
       }
     } else {
       // aTime is before the first range in |seekable|, the closest point we can
       // seek to is the start of the first range.
       seekable->Start(0, &aTime);
     }
   }
@@ -1636,23 +1653,25 @@ HTMLMediaElement::Seek(double aTime,
   // Set the Variable if the Seekstarted while active playing
   if (mPlayingThroughTheAudioChannel) {
     mPlayingThroughTheAudioChannelBeforeSeek = true;
   }
 
   // The media backend is responsible for dispatching the timeupdate
   // event if it changes the playback position as a result of the seek.
   LOG(LogLevel::Debug, ("%p SetCurrentTime(%f) starting seek", this, aTime));
-  nsresult rv = mDecoder->Seek(aTime, aSeekType);
+  nsresult rv = mDecoder->Seek(aTime, aSeekType, promise);
   if (NS_FAILED(rv)) {
     aRv.Throw(rv);
   }
 
   // We changed whether we're seeking so we need to AddRemoveSelfReference.
   AddRemoveSelfReference();
+
+  return promise.forget();
 }
 
 NS_IMETHODIMP HTMLMediaElement::SetCurrentTime(double aCurrentTime)
 {
   // Detect for a NaN and invalid values.
   if (mozilla::IsNaN(aCurrentTime)) {
     LOG(LogLevel::Debug, ("%p SetCurrentTime(%f) failed: bad time", this, aCurrentTime));
     return NS_ERROR_FAILURE;
--- a/dom/html/HTMLMediaElement.h
+++ b/dom/html/HTMLMediaElement.h
@@ -477,17 +477,17 @@ public:
   bool Seeking() const;
 
   double CurrentTime() const;
 
   void SetCurrentTime(double aCurrentTime, ErrorResult& aRv);
 
   void FastSeek(double aTime, ErrorResult& aRv);
 
-  void SeekToNextFrame(ErrorResult& aRv);
+  already_AddRefed<Promise> SeekToNextFrame(ErrorResult& aRv);
 
   double Duration() const;
 
   bool HasAudio() const
   {
     return mMediaInfo.HasAudio();
   }
 
@@ -1120,17 +1120,17 @@ protected:
 
   // Check the permissions for audiochannel.
   bool CheckAudioChannelPermissions(const nsAString& aType);
 
   // Seeks to aTime seconds. aSeekType can be Exact to seek to exactly the
   // seek target, or PrevSyncPoint if a quicker but less precise seek is
   // desired, and we'll seek to the sync point (keyframe and/or start of the
   // next block of audio samples) preceeding seek target.
-  void Seek(double aTime, SeekTarget::Type aSeekType, ErrorResult& aRv);
+  already_AddRefed<Promise> Seek(double aTime, SeekTarget::Type aSeekType, ErrorResult& aRv);
 
   // A method to check if we are playing through the AudioChannel.
   bool IsPlayingThroughTheAudioChannel() const;
 
   // A method to check whether we are currently playing.
   bool IsCurrentlyPlaying() const;
 
   // Update the audio channel playing state
--- a/dom/html/test/reflect.js
+++ b/dom/html/test/reflect.js
@@ -561,49 +561,37 @@ function reflectInt(aParameters)
   valuesToTest.forEach(function(v) {
     var intValue = stringToInteger(v, nonNegative, defaultValue);
 
     element.setAttribute(attr, v);
 
     is(element.getAttribute(attr), expectedGetAttributeResult(v), element.localName + ".setAttribute(" +
       attr + ", " + v + "), " + element.localName + ".getAttribute(" + attr + ") ");
 
-    if (intValue == -2147483648 && element[attr] == defaultValue) {
-      //TBD: Bug 586761: .setAttribute(attr, -2147483648) --> element[attr] == defaultValue instead of -2147483648
-      todo_is(element[attr], intValue, "Bug 586761: " + element.localName +
-        ".setAttribute(value, " + v + "), " + element.localName + "[" + attr + "] ");
-    } else {
-      is(element[attr], intValue, element.localName +
-        ".setAttribute(" + attr + ", " + v + "), " + element.localName + "[" + attr + "] ");
-    }
+    is(element[attr], intValue, element.localName +
+       ".setAttribute(" + attr + ", " + v + "), " + element.localName + "[" + attr + "] ");
     element.removeAttribute(attr);
 
     if (nonNegative && expectedIdlAttributeResult(v) < 0) {
       try {
         element[attr] = v;
         ok(false, element.localName + "[" + attr + "] = " + v + " should throw IndexSizeError");
       } catch(e) {
         is(e.name, "IndexSizeError", element.localName + "[" + attr + "] = " + v +
           " should throw IndexSizeError");
         is(e.code, DOMException.INDEX_SIZE_ERR, element.localName + "[" + attr + "] = " + v +
           " should throw INDEX_SIZE_ERR");
       }
     } else {
       element[attr] = v;
-      if (expectedIdlAttributeResult(v) == -2147483648 && element[attr] == defaultValue) {
-        //TBD: Bug 586761: .setAttribute(attr, -2147483648) --> element[attr] == defaultValue instead of -2147483648
-        todo_is(element[attr], expectedIdlAttributeResult(v), "Bug 586761: " + element.localName + "[" +
-          attr + "] = " + v + ", " + element.localName + "[" + attr + "] ");
-      } else {
-        is(element[attr], expectedIdlAttributeResult(v), element.localName + "[" + attr + "] = " + v +
-          ", " + element.localName + "[" + attr + "] ");
-        is(element.getAttribute(attr), String(expectedIdlAttributeResult(v)),
-           element.localName + "[" + attr + "] = " + v + ", " +
-           element.localName + ".getAttribute(" + attr + ") ");
-      }
+      is(element[attr], expectedIdlAttributeResult(v), element.localName + "[" + attr + "] = " + v +
+         ", " + element.localName + "[" + attr + "] ");
+      is(element.getAttribute(attr), String(expectedIdlAttributeResult(v)),
+         element.localName + "[" + attr + "] = " + v + ", " +
+         element.localName + ".getAttribute(" + attr + ") ");
     }
     element.removeAttribute(attr);
   });
 
   // Tests after removeAttribute() is called. Should be equivalent with not set.
   is(element.getAttribute(attr), null,
      "When not set, the content attribute should be null.");
   is(element[attr], defaultValue,
--- a/dom/manifest/test/browser.ini
+++ b/dom/manifest/test/browser.ini
@@ -1,8 +1,9 @@
 [DEFAULT]
 support-files =
-  manifestLoader.html
   file_reg_install_event.html
   file_testserver.sjs
+  manifestLoader.html
+  resource.sjs
 [browser_ManifestFinder_browserHasManifestLink.js]
 [browser_ManifestObtainer_obtain.js]
 [browser_fire_install_event.js]
\ No newline at end of file
--- a/dom/manifest/test/browser_ManifestFinder_browserHasManifestLink.js
+++ b/dom/manifest/test/browser_ManifestFinder_browserHasManifestLink.js
@@ -1,114 +1,84 @@
 //Used by JSHint:
-/*global Cu, BrowserTestUtils, is, ok, add_task, gBrowser, ManifestFinder */
+/*global Cu, BrowserTestUtils, ok, add_task, gBrowser */
 "use strict";
-Cu.import("resource://gre/modules/ManifestFinder.jsm", this);  // jshint ignore:line
+const { ManifestFinder } = Cu.import("resource://gre/modules/ManifestFinder.jsm", {});
+const defaultURL = new URL("http://example.org/browser/dom/manifest/test/resource.sjs");
+defaultURL.searchParams.set("Content-Type", "text/html; charset=utf-8");
 
-const defaultURL =
-  "http://example.org/tests/dom/manifest/test/resource.sjs";
 const tests = [{
-  expected: "Document has a web manifest.",
-  get tabURL() {
-    let query = [
-      `body=<h1>${this.expected}</h1>`,
-      "Content-Type=text/html; charset=utf-8",
-    ];
-    const URL = `${defaultURL}?${query.join("&")}`;
-    return URL;
-  },
+  body: `
+    <link rel="manifesto" href='${defaultURL}?body={"name":"fail"}'>
+    <link rel="foo bar manifest bar test" href='${defaultURL}?body={"name":"value"}'>
+    <link rel="manifest" href='${defaultURL}?body={"name":"fail"}'>
+  `,
   run(result) {
-    is(result, true, this.expected);
-  },
-  testData: `
-      <link rel="manifesto" href='${defaultURL}?body={"name":"fail"}'>
-      <link rel="foo bar manifest bar test" href='${defaultURL}?body={"name":"value"}'>
-      <link rel="manifest" href='${defaultURL}?body={"name":"fail"}'>`
-}, {
-  expected: "Document does not have a web manifest.",
-  get tabURL() {
-    let query = [
-      `body=<h1>${this.expected}</h1>`,
-      "Content-Type=text/html; charset=utf-8",
-    ];
-    const URL = `${defaultURL}?${query.join("&")}`;
-    return URL;
-  },
-  run(result) {
-    is(result, false, this.expected);
+    ok(result, "Document has a web manifest.");
   },
-  testData: `
-      <link rel="amanifista" href='${defaultURL}?body={"name":"fail"}'>
-      <link rel="foo bar manifesto bar test" href='${defaultURL}?body={"name":"pass-1"}'>
-      <link rel="manifesto" href='${defaultURL}?body={"name":"fail"}'>`
 }, {
-  expected: "Manifest link is has empty href.",
-  get tabURL() {
-    let query = [
-      `body=<h1>${this.expected}</h1>`,
-      "Content-Type=text/html; charset=utf-8",
-    ];
-    const URL = `${defaultURL}?${query.join("&")}`;
-    return URL;
+  body: `
+    <link rel="amanifista" href='${defaultURL}?body={"name":"fail"}'>
+    <link rel="foo bar manifesto bar test" href='${defaultURL}?body={"name":"pass-1"}'>
+    <link rel="manifesto" href='${defaultURL}?body={"name":"fail"}'>`,
+  run(result) {
+    ok(!result, "Document does not have a web manifest.");
   },
+}, {
+  body: `
+    <link rel="manifest" href="">
+    <link rel="manifest" href='${defaultURL}?body={"name":"fail"}'>`,
   run(result) {
-    is(result, false, this.expected);
+    ok(!result, "Manifest link is has empty href.");
   },
-  testData: `
-  <link rel="manifest" href="">
-  <link rel="manifest" href='${defaultURL}?body={"name":"fail"}'>`
 }, {
-  expected: "Manifest link is missing.",
-  get tabURL() {
-    let query = [
-      `body=<h1>${this.expected}</h1>`,
-      "Content-Type=text/html; charset=utf-8",
-    ];
-    const URL = `${defaultURL}?${query.join("&")}`;
-    return URL;
+  body: `
+    <link rel="manifest">
+    <link rel="manifest" href='${defaultURL}?body={"name":"fail"}'>`,
+  run(result) {
+    ok(!result, "Manifest link is missing.");
   },
-  run(result) {
-    is(result, false, this.expected);
-  },
-  testData: `
-    <link rel="manifest">
-    <link rel="manifest" href='${defaultURL}?body={"name":"fail"}'>`
 }];
 
+function makeTestURL({ body }) {
+  const url = new URL(defaultURL);
+  url.searchParams.set("body", encodeURIComponent(body));
+  return url.href;
+}
+
 /**
  * Test basic API error conditions
  */
-add_task(function* () {
-  let expected = "Invalid types should throw a TypeError.";
+add_task(function*() {
+  const expected = "Invalid types should throw a TypeError.";
   for (let invalidValue of [undefined, null, 1, {}, "test"]) {
     try {
       yield ManifestFinder.contentManifestLink(invalidValue);
       ok(false, expected);
     } catch (e) {
       is(e.name, "TypeError", expected);
     }
     try {
       yield ManifestFinder.browserManifestLink(invalidValue);
       ok(false, expected);
     } catch (e) {
       is(e.name, "TypeError", expected);
     }
   }
 });
 
-add_task(function* () {
-  for (let test of tests) {
-    let tabOptions = {
-      gBrowser: gBrowser,
-      url: test.tabURL,
-    };
-    yield BrowserTestUtils.withNewTab(
-      tabOptions,
-      browser => testHasManifest(browser, test)
+add_task(function*() {
+  const runningTests = tests
+    .map(
+      test => ({
+        gBrowser,
+        test,
+        url: makeTestURL(test),
+      })
+    )
+    .map(
+      tabOptions => BrowserTestUtils.withNewTab(tabOptions, function*(browser) {
+        const result = yield ManifestFinder.browserHasManifestLink(browser);
+        tabOptions.test.run(result);
+      })
     );
-  }
-
-  function* testHasManifest(aBrowser, aTest) {
-    aBrowser.contentWindowAsCPOW.document.head.innerHTML = aTest.testData;
-    const result = yield ManifestFinder.browserHasManifestLink(aBrowser);
-    aTest.run(result);
-  }
+  yield Promise.all(runningTests);
 });
--- a/dom/manifest/test/browser_ManifestObtainer_obtain.js
+++ b/dom/manifest/test/browser_ManifestObtainer_obtain.js
@@ -1,249 +1,176 @@
 //Used by JSHint:
-/*global requestLongerTimeout, Cu, BrowserTestUtils, add_task, SpecialPowers, gBrowser, Assert*/ 'use strict';
-const {
-  ManifestObtainer
-} = Cu.import('resource://gre/modules/ManifestObtainer.jsm', {});
+/*global ok, is, Cu, BrowserTestUtils, add_task, gBrowser, makeTestURL, requestLongerTimeout*/
+'use strict';
+const { ManifestObtainer } = Cu.import('resource://gre/modules/ManifestObtainer.jsm', {});
+const remoteURL = 'http://mochi.test:8888/browser/dom/manifest/test/resource.sjs';
+const defaultURL = new URL('http://example.org/browser/dom/manifest/test/resource.sjs');
+defaultURL.searchParams.set('Content-Type', 'text/html; charset=utf-8');
+requestLongerTimeout(4);
 
-requestLongerTimeout(4); // e10s tests take time.
-const defaultURL =
-  'http://example.org/tests/dom/manifest/test/resource.sjs';
-const remoteURL =
-  'http://mochi.test:8888/tests/dom/manifest/test/resource.sjs';
 const tests = [
   // Fetch tests.
   {
-    expected: 'Manifest is first `link` where @rel contains token manifest.',
-    get tabURL() {
-      let query = [
-        `body=<h1>${this.expected}</h1>`,
-        'Content-Type=text/html; charset=utf-8',
-      ];
-      const URL = `${defaultURL}?${query.join('&')}`;
-      return URL;
-    },
+    body: `
+      <link rel="manifesto" href='resource.sjs?body={"name":"fail"}'>
+      <link rel="foo bar manifest bar test" href='resource.sjs?body={"name":"pass-1"}'>
+      <link rel="manifest" href='resource.sjs?body={"name":"fail"}'>`,
     run(manifest) {
-      Assert.strictEqual(manifest.name, 'pass-1', this.expected);
-    },
-    testData: `
-      <link rel="manifesto" href='${defaultURL}?body={"name":"fail"}'>
-      <link rel="foo bar manifest bar test" href='${defaultURL}?body={"name":"pass-1"}'>
-      <link rel="manifest" href='${defaultURL}?body={"name":"fail"}'>`
+      is(manifest.name, 'pass-1', 'Manifest is first `link` where @rel contains token manifest.');
+    }
   }, {
-    expected: 'Manifest is first `link` where @rel contains token manifest.',
-    get tabURL() {
-      let query = [
-        `body=<h1>${this.expected}</h1>`,
-        'Content-Type=text/html; charset=utf-8',
-      ];
-      const URL = `${defaultURL}?${query.join('&')}`;
-      return URL;
-    },
-    run(manifest) {
-      Assert.strictEqual(manifest.name, 'pass-2', this.expected);
-    },
-    testData: `
+    body: `
       <link rel="foo bar manifest bar test" href='resource.sjs?body={"name":"pass-2"}'>
       <link rel="manifest" href='resource.sjs?body={"name":"fail"}'>
-      <link rel="manifest foo bar test" href='resource.sjs?body={"name":"fail"}'>`
+      <link rel="manifest foo bar test" href='resource.sjs?body={"name":"fail"}'>`,
+    run(manifest) {
+      is(manifest.name, 'pass-2', 'Manifest is first `link` where @rel contains token manifest.');
+    },
   }, {
-    expected: 'By default, manifest cannot load cross-origin.',
-    get tabURL() {
-      let query = [
-        `body=<h1>${this.expected}</h1>`,
-        'Content-Type=text/html; charset=utf-8',
-      ];
-      const URL = `${defaultURL}?${query.join('&')}`;
-      return URL;
+    body: `<link rel="manifest" href='${remoteURL}?body={"name":"pass-3"}'>`,
+    run(err) {
+      is(err.name, 'TypeError', 'By default, manifest cannot load cross-origin.');
     },
-    run(err) {
-      Assert.strictEqual(err.name, 'TypeError', this.expected);
-    },
-    testData: `<link rel="manifest" href='${remoteURL}?body={"name":"pass-3"}'>`
   },
   // CORS Tests.
   {
-    expected: 'CORS enabled, manifest must be fetched.',
-    get tabURL() {
-      let query = [
-        `body=<h1>${this.expected}</h1>`,
-        'Content-Type=text/html; charset=utf-8',
-      ];
-      const URL = `${defaultURL}?${query.join('&')}`;
-      return URL;
-    },
-    run(manifest) {
-      Assert.strictEqual(manifest.name, 'pass-4', this.expected);
-    },
-    get testData() {
+    get body() {
       const body = 'body={"name": "pass-4"}';
       const CORS =
-        `Access-Control-Allow-Origin=${new URL(this.tabURL).origin}`;
+        `Access-Control-Allow-Origin=${defaultURL.origin}`;
       const link =
         `<link
         crossorigin=anonymous
         rel="manifest"
         href='${remoteURL}?${body}&${CORS}'>`;
       return link;
-    }
+    },
+    run(manifest) {
+      is(manifest.name, 'pass-4', 'CORS enabled, manifest must be fetched.');
+    },
   }, {
-    expected: 'Fetch blocked by CORS - origin does not match.',
-    get tabURL() {
-      let query = [
-        `body=<h1>${this.expected}</h1>`,
-        'Content-Type=text/html; charset=utf-8',
-      ];
-      const URL = `${defaultURL}?${query.join('&')}`;
-      return URL;
-    },
-    run(err) {
-      Assert.strictEqual(err.name, 'TypeError', this.expected);
-    },
-    get testData() {
+    get body() {
       const body = 'body={"name": "fail"}';
       const CORS = 'Access-Control-Allow-Origin=http://not-here';
       const link =
         `<link
         crossorigin
         rel="manifest"
         href='${remoteURL}?${body}&${CORS}'>`;
       return link;
-    }
-  },{
-    expected: 'Trying to load from about:whatever is a TypeError.',
-    get tabURL() {
-      let query = [
-        `body=<h1>${this.expected}</h1>`,
-        'Content-Type=text/html; charset=utf-8',
-      ];
-      const URL = `${defaultURL}?${query.join('&')}`;
-      return URL;
     },
     run(err) {
-      Assert.strictEqual(err.name, 'TypeError', this.expected);
+      is(err.name, 'TypeError', 'Fetch blocked by CORS - origin does not match.');
     },
-    testData: `<link rel="manifest" href='about:whatever'>`
-  },
-  {
-    expected: 'Trying to load from file://whatever is a TypeError.',
-    get tabURL() {
-      let query = [
-        `body=<h1>${this.expected}</h1>`,
-        'Content-Type=text/html; charset=utf-8',
-      ];
-      const URL = `${defaultURL}?${query.join('&')}`;
-      return URL;
+  }, {
+    body: `<link rel="manifest" href='about:whatever'>`,
+    run(err) {
+      is(err.name, 'TypeError', 'Trying to load from about:whatever is TypeError.');
     },
+  }, {
+    body: `<link rel="manifest" href='file://manifest'>`,
     run(err) {
-      Assert.strictEqual(err.name, 'TypeError', this.expected);
+      is(err.name, 'TypeError', 'Trying to load from file://whatever is a TypeError.');
     },
-    testData: `<link rel="manifest" href='file://manifest'>`
   },
   //URL parsing tests
   {
-    expected: 'Trying to load invalid URL is a TypeError.',
-    get tabURL() {
-      let query = [
-        `body=<h1>${this.expected}</h1>`,
-        'Content-Type=text/html; charset=utf-8',
-      ];
-      const URL = `${defaultURL}?${query.join('&')}`;
-      return URL;
+    body: `<link rel="manifest" href='http://[12.1212.21.21.12.21.12]'>`,
+    run(err) {
+      is(err.name, 'TypeError', 'Trying to load invalid URL is a TypeError.');
     },
-    run(err) {
-      Assert.strictEqual(err.name, 'TypeError', this.expected);
-    },
-    testData: `<link rel="manifest" href='http://[12.1212.21.21.12.21.12]'>`
   },
 ];
 
+function makeTestURL({ body }) {
+  const url = new URL(defaultURL);
+  url.searchParams.set('body', encodeURIComponent(body));
+  return url.href;
+}
+
 add_task(function*() {
-  yield new Promise(resolve => {
-    SpecialPowers.pushPrefEnv({
-      'set': [
-        ['dom.fetch.enabled', true]
-      ]
-    }, resolve);
-  });
-  for (let test of tests) {
-    let tabOptions = {
-      gBrowser: gBrowser,
-      url: test.tabURL,
+  const promises = tests
+    .map(test => ({
+      gBrowser,
+      testRunner: testObtainingManifest(test),
+      url: makeTestURL(test)
+    }))
+    .reduce((collector, tabOpts) => {
+      const promise = BrowserTestUtils.withNewTab(tabOpts, tabOpts.testRunner);
+      collector.push(promise);
+      return collector;
+    }, []);
+
+  const results = yield Promise.all(promises);
+
+  function testObtainingManifest(aTest) {
+    return function*(aBrowser) {
+      try {
+        const manifest = yield ManifestObtainer.browserObtainManifest(aBrowser);
+        aTest.run(manifest);
+      } catch (e) {
+        aTest.run(e);
+      }
     };
-    yield BrowserTestUtils.withNewTab(
-      tabOptions,
-      browser => testObtainingManifest(browser, test)
-    );
-  }
-
-  function* testObtainingManifest(aBrowser, aTest) {
-    aBrowser.contentWindowAsCPOW.document.head.innerHTML = aTest.testData;
-    try {
-      const manifest = yield ManifestObtainer.browserObtainManifest(aBrowser);
-      aTest.run(manifest);
-    } catch (e) {
-      aTest.run(e);
-    }
   }
 });
 
 /*
  * e10s race condition tests
  * Open a bunch of tabs and load manifests
  * in each tab. They should all return pass.
  */
 add_task(function*() {
-  const defaultPath = '/tests/dom/manifest/test/manifestLoader.html';
+  const defaultPath = '/browser/dom/manifest/test/manifestLoader.html';
   const tabURLs = [
-    `http://test:80${defaultPath}`,
+    `http://example.com:80${defaultPath}`,
+    `http://example.org:80${defaultPath}`,
+    `http://example.org:8000${defaultPath}`,
     `http://mochi.test:8888${defaultPath}`,
-    `http://test1.mochi.test:8888${defaultPath}`,
+    `http://sub1.test1.example.com:80${defaultPath}`,
+    `http://sub1.test1.example.org:80${defaultPath}`,
+    `http://sub1.test1.example.org:8000${defaultPath}`,
     `http://sub1.test1.mochi.test:8888${defaultPath}`,
-    `http://sub2.xn--lt-uia.mochi.test:8888${defaultPath}`,
-    `http://test2.mochi.test:8888${defaultPath}`,
-    `http://example.org:80${defaultPath}`,
-    `http://test1.example.org:80${defaultPath}`,
-    `http://test2.example.org:80${defaultPath}`,
-    `http://sub1.test1.example.org:80${defaultPath}`,
+    `http://sub1.test2.example.com:80${defaultPath}`,
     `http://sub1.test2.example.org:80${defaultPath}`,
+    `http://sub1.test2.example.org:8000${defaultPath}`,
+    `http://sub2.test1.example.com:80${defaultPath}`,
     `http://sub2.test1.example.org:80${defaultPath}`,
+    `http://sub2.test1.example.org:8000${defaultPath}`,
+    `http://sub2.test2.example.com:80${defaultPath}`,
     `http://sub2.test2.example.org:80${defaultPath}`,
-    `http://example.org:8000${defaultPath}`,
+    `http://sub2.test2.example.org:8000${defaultPath}`,
+    `http://sub2.xn--lt-uia.mochi.test:8888${defaultPath}`,
+    `http://test1.example.com:80${defaultPath}`,
+    `http://test1.example.org:80${defaultPath}`,
     `http://test1.example.org:8000${defaultPath}`,
+    `http://test1.mochi.test:8888${defaultPath}`,
+    `http://test2.example.com:80${defaultPath}`,
+    `http://test2.example.org:80${defaultPath}`,
     `http://test2.example.org:8000${defaultPath}`,
-    `http://sub1.test1.example.org:8000${defaultPath}`,
-    `http://sub1.test2.example.org:8000${defaultPath}`,
-    `http://sub2.test1.example.org:8000${defaultPath}`,
-    `http://sub2.test2.example.org:8000${defaultPath}`,
-    `http://example.com:80${defaultPath}`,
+    `http://test2.mochi.test:8888${defaultPath}`,
+    `http://test:80${defaultPath}`,
     `http://www.example.com:80${defaultPath}`,
-    `http://test1.example.com:80${defaultPath}`,
-    `http://test2.example.com:80${defaultPath}`,
-    `http://sub1.test1.example.com:80${defaultPath}`,
-    `http://sub1.test2.example.com:80${defaultPath}`,
-    `http://sub2.test1.example.com:80${defaultPath}`,
-    `http://sub2.test2.example.com:80${defaultPath}`,
   ];
   // Open tabs an collect corresponding browsers
   let browsers = [
     for (url of tabURLs) gBrowser.addTab(url).linkedBrowser
   ];
   // Once all the pages have loaded, run a bunch of tests in "parallel".
   yield Promise.all((
     for (browser of browsers) BrowserTestUtils.browserLoaded(browser)
   ));
   // Flood random browsers with requests. Once promises settle, check that
   // responses all pass.
   const results = yield Promise.all((
-    for (browser of randBrowsers(browsers, 100)) ManifestObtainer.browserObtainManifest(browser)
+    for (browser of randBrowsers(browsers, 50)) ManifestObtainer.browserObtainManifest(browser)
   ));
-  const expected = 'Expect every manifest to have name equal to `pass`.';
   const pass = results.every(manifest => manifest.name === 'pass');
-  Assert.ok(pass, expected);
+  ok(pass, 'Expect every manifest to have name equal to `pass`.');
   //cleanup
   browsers
     .map(browser => gBrowser.getTabForBrowser(browser))
     .forEach(tab => gBrowser.removeTab(tab));
 
   //Helper generator, spits out random browsers
   function* randBrowsers(aBrowsers, aMax) {
     for (let i = 0; i < aMax; i++) {
--- a/dom/manifest/test/resource.sjs
+++ b/dom/manifest/test/resource.sjs
@@ -15,16 +15,17 @@
  * Outputs:
  * HTTP/1.1 200 OK
  * Content-Type: text/html
  * Hello: hi
  * <h1>hello</h1>
  */
 //global handleRequest
 'use strict';
+Components.utils.importGlobalProperties(["URLSearchParams"]);
 const HTTPStatus = new Map([
   [100, 'Continue'],
   [101, 'Switching Protocol'],
   [200, 'OK'],
   [201, 'Created'],
   [202, 'Accepted'],
   [203, 'Non-Authoritative Information'],
   [204, 'No Content'],
@@ -61,33 +62,24 @@ const HTTPStatus = new Map([
   [501, 'Not Implemented'],
   [502, 'Bad Gateway'],
   [503, 'Service Unavailable'],
   [504, 'Gateway Timeout'],
   [505, 'HTTP Version Not Supported']
 ]);
 
 function handleRequest(request, response) {
-  const queryMap = createQueryMap(request);
+  const queryMap = new URLSearchParams(request.queryString);
   if (queryMap.has('statusCode')) {
     let statusCode = parseInt(queryMap.get('statusCode'));
     let statusText = HTTPStatus.get(statusCode);
     queryMap.delete('statusCode');
     response.setStatusLine('1.1', statusCode, statusText);
   }
   if (queryMap.has('body')) {
     let body = queryMap.get('body') || '';
     queryMap.delete('body');
-    response.write(body);
+    response.write(decodeURIComponent(body));
   }
-  for (let [key, value] of queryMap) {
+  for (let [key, value] of queryMap.entries()) {
     response.setHeader(key, value);
   }
-
-  function createQueryMap(request) {
-    const queryMap = new Map();
-    request.queryString.split('&')
-      //split on first "="
-      .map((component) => component.split(/=(.+)?/))
-      .forEach(pair => queryMap.set(pair[0], decodeURIComponent(pair[1])));
-    return queryMap;
-  }
 }
--- a/dom/media/DOMMediaStream.cpp
+++ b/dom/media/DOMMediaStream.cpp
@@ -5,17 +5,16 @@
 
 #include "DOMMediaStream.h"
 #include "nsContentUtils.h"
 #include "nsServiceManagerUtils.h"
 #include "nsIScriptError.h"
 #include "nsIUUIDGenerator.h"
 #include "nsPIDOMWindow.h"
 #include "mozilla/dom/MediaStreamBinding.h"
-#include "mozilla/dom/MediaStreamTrackEvent.h"
 #include "mozilla/dom/LocalMediaStreamBinding.h"
 #include "mozilla/dom/AudioNode.h"
 #include "AudioChannelAgent.h"
 #include "mozilla/dom/AudioTrack.h"
 #include "mozilla/dom/AudioTrackList.h"
 #include "mozilla/dom/VideoTrack.h"
 #include "mozilla/dom/VideoTrackList.h"
 #include "mozilla/dom/HTMLCanvasElement.h"
@@ -94,20 +93,20 @@ DOMMediaStream::TrackPort::GetSource() c
 
 TrackID
 DOMMediaStream::TrackPort::GetSourceTrackId() const
 {
   return mInputPort ? mInputPort->GetSourceTrackId() : TRACK_INVALID;
 }
 
 already_AddRefed<Pledge<bool>>
-DOMMediaStream::TrackPort::BlockSourceTrackId(TrackID aTrackId, BlockingMode aBlockingMode)
+DOMMediaStream::TrackPort::BlockSourceTrackId(TrackID aTrackId)
 {
   if (mInputPort) {
-    return mInputPort->BlockSourceTrackId(aTrackId, aBlockingMode);
+    return mInputPort->BlockSourceTrackId(aTrackId);
   }
   RefPtr<Pledge<bool>> rejected = new Pledge<bool>();
   rejected->Reject(NS_ERROR_FAILURE);
   return rejected.forget();
 }
 
 NS_IMPL_CYCLE_COLLECTION(DOMMediaStream::TrackPort, mTrack)
 NS_IMPL_CYCLE_COLLECTION_ROOT_NATIVE(DOMMediaStream::TrackPort, AddRef)
@@ -138,17 +137,17 @@ public:
   {
     MOZ_ASSERT(NS_IsMainThread());
 
     if (!mStream) {
       return;
     }
 
     MediaStreamTrack* track =
-      mStream->FindOwnedDOMTrack(aInputStream, aInputTrackID, aTrackID);
+      mStream->FindOwnedDOMTrack(aInputStream, aInputTrackID);
     if (!track) {
       // Track had not been created on main thread before, create it now.
       NS_WARN_IF_FALSE(!mStream->mTracks.IsEmpty(),
                        "A new track was detected on the input stream; creating "
                        "a corresponding MediaStreamTrack. Initial tracks "
                        "should be added manually to immediately and "
                        "synchronously be available to JS.");
       RefPtr<MediaStreamTrackSource> source;
@@ -161,27 +160,26 @@ public:
         nsIDocument* doc = window ? window->GetExtantDoc() : nullptr;
         nsIPrincipal* principal = doc ? doc->NodePrincipal() : nullptr;
         source = new BasicUnstoppableTrackSource(principal);
       }
       track = mStream->CreateDOMTrack(aTrackID, aType, source);
     }
   }
 
-  void DoNotifyTrackEnded(MediaStream* aInputStream, TrackID aInputTrackID,
-                          TrackID aTrackID)
+  void DoNotifyTrackEnded(MediaStream* aInputStream, TrackID aInputTrackID)
   {
     MOZ_ASSERT(NS_IsMainThread());
 
     if (!mStream) {
       return;
     }
 
     RefPtr<MediaStreamTrack> track =
-      mStream->FindOwnedDOMTrack(aInputStream, aInputTrackID, aTrackID);
+      mStream->FindOwnedDOMTrack(aInputStream, aInputTrackID);
     NS_ASSERTION(track, "Owned MediaStreamTracks must be known by the DOMMediaStream");
     if (track) {
       LOG(LogLevel::Debug, ("DOMMediaStream %p MediaStreamTrack %p ended at the source. Marking it ended.",
                             mStream, track.get()));
       track->NotifyEnded();
     }
   }
 
@@ -194,19 +192,19 @@ public:
     if (aTrackEvents & TRACK_EVENT_CREATED) {
       nsCOMPtr<nsIRunnable> runnable =
         NewRunnableMethod<TrackID, MediaSegment::Type, MediaStream*, TrackID>(
           this, &OwnedStreamListener::DoNotifyTrackCreated,
           aID, aQueuedMedia.GetType(), aInputStream, aInputTrackID);
       aGraph->DispatchToMainThreadAfterStreamStateUpdate(runnable.forget());
     } else if (aTrackEvents & TRACK_EVENT_ENDED) {
       nsCOMPtr<nsIRunnable> runnable =
-        NewRunnableMethod<MediaStream*, TrackID, TrackID>(
+        NewRunnableMethod<MediaStream*, TrackID>(
           this, &OwnedStreamListener::DoNotifyTrackEnded,
-          aInputStream, aInputTrackID, aID);
+          aInputStream, aInputTrackID);
       aGraph->DispatchToMainThreadAfterStreamStateUpdate(runnable.forget());
     }
   }
 
 private:
   // These fields may only be accessed on the main thread
   DOMMediaStream* mStream;
 };
@@ -595,21 +593,17 @@ DOMMediaStream::RemoveTrack(MediaStreamT
     LOG(LogLevel::Debug, ("DOMMediaStream %p does not contain track %p", this, &aTrack));
     return;
   }
 
   // If the track comes from a TRACK_ANY input port (i.e., mOwnedPort), we need
   // to block it in the port. Doing this for a locked track is still OK as it
   // will first block the track, then destroy the port. Both cause the track to
   // end.
-  // If the track has already ended, it's input port might be gone, so in those
-  // cases blocking the underlying track should be avoided.
-  if (!aTrack.Ended()) {
-    BlockPlaybackTrack(toRemove);
-  }
+  BlockPlaybackTrack(toRemove);
 
   DebugOnly<bool> removed = mTracks.RemoveElement(toRemove);
   MOZ_ASSERT(removed);
 
   NotifyTrackRemoved(&aTrack);
 
   LOG(LogLevel::Debug, ("DOMMediaStream %p Removed track %p", this, &aTrack));
 }
@@ -1002,19 +996,16 @@ DOMMediaStream::CreateDOMTrack(TrackID a
 
   mOwnedTracks.AppendElement(
     new TrackPort(mOwnedPort, track, TrackPort::InputPortOwnership::EXTERNAL));
 
   mTracks.AppendElement(
     new TrackPort(mPlaybackPort, track, TrackPort::InputPortOwnership::EXTERNAL));
 
   NotifyTrackAdded(track);
-
-  DispatchTrackEvent(NS_LITERAL_STRING("addtrack"), track);
-
   return track;
 }
 
 already_AddRefed<MediaStreamTrack>
 DOMMediaStream::CloneDOMTrack(MediaStreamTrack& aTrack,
                               TrackID aCloneTrackID)
 {
   MOZ_RELEASE_ASSERT(mOwnedStream);
@@ -1039,27 +1030,16 @@ DOMMediaStream::CloneDOMTrack(MediaStrea
     new TrackPort(inputPort, newTrack, TrackPort::InputPortOwnership::OWNED));
 
   mTracks.AppendElement(
     new TrackPort(mPlaybackPort, newTrack, TrackPort::InputPortOwnership::EXTERNAL));
 
   NotifyTrackAdded(newTrack);
 
   newTrack->SetEnabled(aTrack.Enabled());
-  newTrack->SetReadyState(aTrack.ReadyState());
-
-  if (aTrack.Ended()) {
-    // For extra suspenders, make sure that we don't forward data by mistake
-    // to the clone when the original has already ended.
-    // We only block END_EXISTING to allow any pending clones to end.
-    RefPtr<Pledge<bool, nsresult>> blockingPledge =
-      inputPort->BlockSourceTrackId(inputTrackID,
-                                    BlockingMode::END_EXISTING);
-    Unused << blockingPledge;
-  }
 
   return newTrack.forget();
 }
 
 static DOMMediaStream::TrackPort*
 FindTrackPortAmongTracks(const MediaStreamTrack& aTrack,
                          const nsTArray<RefPtr<DOMMediaStream::TrackPort>>& aTracks)
 {
@@ -1068,26 +1048,24 @@ FindTrackPortAmongTracks(const MediaStre
       return info;
     }
   }
   return nullptr;
 }
 
 MediaStreamTrack*
 DOMMediaStream::FindOwnedDOMTrack(MediaStream* aInputStream,
-                                  TrackID aInputTrackID,
-                                  TrackID aTrackID) const
+                                  TrackID aInputTrackID) const
 {
   MOZ_RELEASE_ASSERT(mOwnedStream);
 
   for (const RefPtr<TrackPort>& info : mOwnedTracks) {
     if (info->GetInputPort() &&
         info->GetInputPort()->GetSource() == aInputStream &&
-        info->GetTrack()->mInputTrackID == aInputTrackID &&
-        (aTrackID == TRACK_ANY || info->GetTrack()->mTrackID == aTrackID)) {
+        info->GetTrack()->mInputTrackID == aInputTrackID) {
       // This track is owned externally but in our playback stream.
       return info->GetTrack();
     }
   }
   return nullptr;
 }
 
 DOMMediaStream::TrackPort*
@@ -1244,48 +1222,30 @@ DOMMediaStream::NotifyTrackRemoved(const
     mTrackListeners[i]->NotifyTrackRemoved(aTrack);
   }
 
   // Don't call RecomputePrincipal here as the track may still exist in the
   // playback stream in the MediaStreamGraph. It will instead be called when the
   // track has been confirmed removed by the graph. See BlockPlaybackTrack().
 }
 
-nsresult
-DOMMediaStream::DispatchTrackEvent(const nsAString& aName,
-                                   const RefPtr<MediaStreamTrack>& aTrack)
-{
-  MOZ_ASSERT(aName == NS_LITERAL_STRING("addtrack"),
-             "Only 'addtrack' is supported at this time");
-
-  MediaStreamTrackEventInit init;
-  init.mTrack = aTrack;
-
-  RefPtr<MediaStreamTrackEvent> event =
-    MediaStreamTrackEvent::Constructor(this, aName, init);
-
-  return DispatchTrustedEvent(event);
-}
-
 void
 DOMMediaStream::CreateAndAddPlaybackStreamListener(MediaStream* aStream)
 {
   MOZ_ASSERT(GetCameraStream(), "I'm a hack. Only DOMCameraControl may use me.");
   mPlaybackListener = new PlaybackStreamListener(this);
   aStream->AddListener(mPlaybackListener);
 }
 
 void
 DOMMediaStream::BlockPlaybackTrack(TrackPort* aTrack)
 {
   MOZ_ASSERT(aTrack);
   ++mTracksPendingRemoval;
-  RefPtr<Pledge<bool>> p =
-    aTrack->BlockSourceTrackId(aTrack->GetTrack()->mTrackID,
-                               BlockingMode::CREATION);
+  RefPtr<Pledge<bool>> p = aTrack->BlockSourceTrackId(aTrack->GetTrack()->mTrackID);
   RefPtr<DOMMediaStream> self = this;
   p->Then([self] (const bool& aIgnore) { self->NotifyPlaybackTrackBlocked(); },
           [] (const nsresult& aIgnore) { NS_ERROR("Could not remove track from MSG"); }
   );
 }
 
 void
 DOMMediaStream::NotifyPlaybackTrackBlocked()
--- a/dom/media/DOMMediaStream.h
+++ b/dom/media/DOMMediaStream.h
@@ -29,18 +29,16 @@ class DOMHwMediaStream;
 class DOMLocalMediaStream;
 class DOMMediaStream;
 class MediaStream;
 class MediaInputPort;
 class MediaStreamDirectListener;
 class MediaStreamGraph;
 class ProcessedMediaStream;
 
-enum class BlockingMode;
-
 namespace dom {
 class AudioNode;
 class HTMLCanvasElement;
 class MediaStreamTrack;
 class MediaStreamTrackSource;
 class AudioStreamTrack;
 class VideoStreamTrack;
 class AudioTrack;
@@ -301,18 +299,17 @@ public:
     MediaInputPort* GetInputPort() const { return mInputPort; }
     MediaStreamTrack* GetTrack() const { return mTrack; }
 
     /**
      * Blocks aTrackId from going into mInputPort unless the port has been
      * destroyed. Returns a pledge that gets resolved when the MediaStreamGraph
      * has applied the block in the playback stream.
      */
-    already_AddRefed<media::Pledge<bool, nsresult>>
-    BlockSourceTrackId(TrackID aTrackId, BlockingMode aBlockingMode);
+    already_AddRefed<media::Pledge<bool, nsresult>> BlockSourceTrackId(TrackID aTrackId);
 
   private:
     RefPtr<MediaInputPort> mInputPort;
     RefPtr<MediaStreamTrack> mTrack;
 
     // Defines if we've been given ownership of the input port or if it's owned
     // externally. The owner is responsible for destroying the port.
     const InputPortOwnership mOwnership;
@@ -358,18 +355,16 @@ public:
   void GetTracks(nsTArray<RefPtr<MediaStreamTrack> >& aTracks) const;
   MediaStreamTrack* GetTrackById(const nsAString& aId) const;
   void AddTrack(MediaStreamTrack& aTrack);
   void RemoveTrack(MediaStreamTrack& aTrack);
 
   /** Identical to CloneInternal(TrackForwardingOption::EXPLICIT) */
   already_AddRefed<DOMMediaStream> Clone();
 
-  IMPL_EVENT_HANDLER(addtrack)
-
   // NON-WebIDL
 
   /**
    * Option to provide to CloneInternal() of which tracks should be forwarded
    * from the source stream (`this`) to the returned stream clone.
    *
    * CURRENT forwards the tracks currently in the source stream's track set.
    * ALL     forwards like EXPLICIT plus any and all future tracks originating
@@ -390,27 +385,20 @@ public:
 
   /**
    * Returns true if this DOMMediaStream owns aTrack.
    */
   bool OwnsTrack(const MediaStreamTrack& aTrack) const;
 
   /**
    * Returns the corresponding MediaStreamTrack if it's in our mOwnedStream.
-   * aInputTrackID should match the track's TrackID in its input stream,
-   * and aTrackID the TrackID in mOwnedStream.
-   *
-   * When aTrackID is not supplied or set to TRACK_ANY, we return the first
-   * MediaStreamTrack that matches the given input track. Note that there may
-   * be multiple MediaStreamTracks matching the same input track, but that they
-   * in that case all share the same MediaStreamTrackSource.
+   * aInputTrackID should match the track's TrackID in its input stream.
    */
   MediaStreamTrack* FindOwnedDOMTrack(MediaStream* aInputStream,
-                                      TrackID aInputTrackID,
-                                      TrackID aTrackID = TRACK_ANY) const;
+                                      TrackID aInputTrackID) const;
 
   /**
    * Returns the TrackPort connecting aTrack's input stream to mOwnedStream,
    * or nullptr if aTrack is not owned by this DOMMediaStream.
    */
   TrackPort* FindOwnedTrackPort(const MediaStreamTrack& aTrack) const;
 
   /**
@@ -520,22 +508,17 @@ public:
   {
     mLogicalStreamStartTime = aTime;
   }
 
   /**
    * Called for each track in our owned stream to indicate to JS that we
    * are carrying that track.
    *
-   * Creates a MediaStreamTrack, adds it to mTracks, raises "addtrack" and
-   * returns it.
-   *
-   * Note that "addtrack" is raised synchronously and only has an effect if
-   * this MediaStream is already exposed to script. For spec compliance this is
-   * to be called from an async task.
+   * Creates a MediaStreamTrack, adds it to mTracks and returns it.
    */
   MediaStreamTrack* CreateDOMTrack(TrackID aTrackID, MediaSegment::Type aType,
                                    MediaStreamTrackSource* aSource);
 
   /**
    * Creates a MediaStreamTrack cloned from aTrack, adds it to mTracks and
    * returns it.
    * aCloneTrackID is the TrackID the new track will get in mOwnedStream.
@@ -606,20 +589,16 @@ protected:
   void NotifyTracksCreated();
 
   // Dispatches NotifyTrackAdded() to all registered track listeners.
   void NotifyTrackAdded(const RefPtr<MediaStreamTrack>& aTrack);
 
   // Dispatches NotifyTrackRemoved() to all registered track listeners.
   void NotifyTrackRemoved(const RefPtr<MediaStreamTrack>& aTrack);
 
-  // Dispatches "addtrack" or "removetrack".
-  nsresult DispatchTrackEvent(const nsAString& aName,
-                              const RefPtr<MediaStreamTrack>& aTrack);
-
   class OwnedStreamListener;
   friend class OwnedStreamListener;
 
   class PlaybackStreamListener;
   friend class PlaybackStreamListener;
 
   // XXX Bug 1124630. Remove with CameraPreviewMediaStream.
   void CreateAndAddPlaybackStreamListener(MediaStream*);
--- a/dom/media/MediaDecoder.cpp
+++ b/dom/media/MediaDecoder.cpp
@@ -20,16 +20,17 @@
 #include "nsIMemoryReporter.h"
 #include "nsComponentManagerUtils.h"
 #include <algorithm>
 #include "MediaShutdownManager.h"
 #include "AudioChannelService.h"
 #include "mozilla/dom/AudioTrack.h"
 #include "mozilla/dom/AudioTrackList.h"
 #include "mozilla/dom/HTMLMediaElement.h"
+#include "mozilla/dom/Promise.h"
 #include "mozilla/dom/VideoTrack.h"
 #include "mozilla/dom/VideoTrackList.h"
 #include "nsPrintfCString.h"
 #include "mozilla/Telemetry.h"
 
 #ifdef MOZ_ANDROID_OMX
 #include "AndroidBridge.h"
 #endif
@@ -809,47 +810,85 @@ MediaDecoder::Play()
     return NS_OK;
   }
 
   ChangeState(PLAY_STATE_PLAYING);
   return NS_OK;
 }
 
 nsresult
-MediaDecoder::Seek(double aTime, SeekTarget::Type aSeekType)
+MediaDecoder::Seek(double aTime, SeekTarget::Type aSeekType, dom::Promise* aPromise /*=nullptr*/)
 {
   MOZ_ASSERT(NS_IsMainThread());
   NS_ENSURE_TRUE(!mShuttingDown, NS_ERROR_FAILURE);
 
   UpdateDormantState(false /* aDormantTimeout */, true /* aActivity */);
 
   MOZ_ASSERT(!mIsDormant, "should be out of dormant by now");
   MOZ_ASSERT(aTime >= 0.0, "Cannot seek to a negative value.");
 
   int64_t timeUsecs = TimeUnit::FromSeconds(aTime).ToMicroseconds();
 
   mLogicalPosition = aTime;
   mWasEndedWhenEnteredDormant = false;
 
   mLogicallySeeking = true;
   SeekTarget target = SeekTarget(timeUsecs, aSeekType);
-  CallSeek(target);
+  CallSeek(target, aPromise);
 
   if (mPlayState == PLAY_STATE_ENDED) {
     PinForSeek();
     ChangeState(mOwner->GetPaused() ? PLAY_STATE_PAUSED : PLAY_STATE_PLAYING);
   }
   return NS_OK;
 }
 
 void
-MediaDecoder::CallSeek(const SeekTarget& aTarget)
+MediaDecoder::AsyncResolveSeekDOMPromiseIfExists()
+{
+  MOZ_ASSERT(NS_IsMainThread());
+  if (mSeekDOMPromise) {
+    RefPtr<dom::Promise> promise = mSeekDOMPromise;
+    nsCOMPtr<nsIRunnable> r = NS_NewRunnableFunction([=] () {
+      promise->MaybeResolve(JS::UndefinedHandleValue);
+    });
+    AbstractThread::MainThread()->Dispatch(r.forget());
+    mSeekDOMPromise = nullptr;
+  }
+}
+
+void
+MediaDecoder::AsyncRejectSeekDOMPromiseIfExists()
+{
+  MOZ_ASSERT(NS_IsMainThread());
+  if (mSeekDOMPromise) {
+    RefPtr<dom::Promise> promise = mSeekDOMPromise;
+    nsCOMPtr<nsIRunnable> r = NS_NewRunnableFunction([=] () {
+      promise->MaybeRejectWithUndefined();
+    });
+    AbstractThread::MainThread()->Dispatch(r.forget());
+    mSeekDOMPromise = nullptr;
+  }
+}
+
+void
+MediaDecoder::DiscardOngoingSeekIfExists()
 {
   MOZ_ASSERT(NS_IsMainThread());
   mSeekRequest.DisconnectIfExists();
+  AsyncRejectSeekDOMPromiseIfExists();
+}
+
+void
+MediaDecoder::CallSeek(const SeekTarget& aTarget, dom::Promise* aPromise)
+{
+  MOZ_ASSERT(NS_IsMainThread());
+  DiscardOngoingSeekIfExists();
+
+  mSeekDOMPromise = aPromise;
   mSeekRequest.Begin(
     mDecoderStateMachine->InvokeSeek(aTarget)
     ->Then(AbstractThread::MainThread(), __func__, this,
            &MediaDecoder::OnSeekResolved, &MediaDecoder::OnSeekRejected));
 }
 
 double
 MediaDecoder::GetCurrentTime()
@@ -1251,23 +1290,33 @@ MediaDecoder::OnSeekResolved(SeekResolve
     mLogicallySeeking = false;
   }
 
   // Ensure logical position is updated after seek.
   UpdateLogicalPositionInternal(aVal.mEventVisibility);
 
   if (aVal.mEventVisibility != MediaDecoderEventVisibility::Suppressed) {
     mOwner->SeekCompleted();
+    AsyncResolveSeekDOMPromiseIfExists();
     if (fireEnded) {
       mOwner->PlaybackEnded();
     }
   }
 }
 
 void
+MediaDecoder::OnSeekRejected()
+{
+  MOZ_ASSERT(NS_IsMainThread());
+  mSeekRequest.Complete();
+  mLogicallySeeking = false;
+  AsyncRejectSeekDOMPromiseIfExists();
+}
+
+void
 MediaDecoder::SeekingStarted(MediaDecoderEventVisibility aEventVisibility)
 {
   MOZ_ASSERT(NS_IsMainThread());
   if (mShuttingDown)
     return;
 
   if (aEventVisibility != MediaDecoderEventVisibility::Suppressed) {
     mOwner->SeekStarted();
--- a/dom/media/MediaDecoder.h
+++ b/dom/media/MediaDecoder.h
@@ -38,16 +38,20 @@
 #include "TimeUnits.h"
 #include "SeekTarget.h"
 
 class nsIStreamListener;
 class nsIPrincipal;
 
 namespace mozilla {
 
+namespace dom {
+class Promise;
+}
+
 class VideoFrameContainer;
 class MediaDecoderStateMachine;
 
 enum class MediaEventType : int8_t;
 
 // GetCurrentTime is defined in winbase.h as zero argument macro forwarding to
 // GetTickCount() and conflicts with MediaDecoder::GetCurrentTime implementation.
 #ifdef GetCurrentTime
@@ -161,17 +165,18 @@ public:
 
   // Return the time position in the video stream being
   // played measured in seconds.
   virtual double GetCurrentTime();
 
   // Seek to the time position in (seconds) from the start of the video.
   // If aDoFastSeek is true, we'll seek to the sync point/keyframe preceeding
   // the seek target.
-  virtual nsresult Seek(double aTime, SeekTarget::Type aSeekType);
+  virtual nsresult Seek(double aTime, SeekTarget::Type aSeekType,
+                        dom::Promise* aPromise = nullptr);
 
   // Initialize state machine and schedule it.
   nsresult InitializeStateMachine();
 
   // Start playback of a video. 'Load' must have previously been
   // called.
   virtual nsresult Play();
 
@@ -386,22 +391,17 @@ private:
   // Removes all audio tracks and video tracks that are previously added into
   // the track list. Call on the main thread only.
   void RemoveMediaTracks();
 
   // Called when the video has completed playing.
   // Call on the main thread only.
   void PlaybackEnded();
 
-  void OnSeekRejected()
-  {
-    MOZ_ASSERT(NS_IsMainThread());
-    mSeekRequest.Complete();
-    mLogicallySeeking = false;
-  }
+  void OnSeekRejected();
   void OnSeekResolved(SeekResolveValue aVal);
 
   void SeekingChanged()
   {
     // Stop updating the bytes downloaded for progress notifications when
     // seeking to prevent wild changes to the progress notification.
     MOZ_ASSERT(NS_IsMainThread());
     mIgnoreProgressData = mLogicallySeeking;
@@ -615,22 +615,34 @@ private:
   RefPtr<ResourceCallback> mResourceCallback;
 
 #ifdef MOZ_EME
   MozPromiseHolder<CDMProxyPromise> mCDMProxyPromiseHolder;
   RefPtr<CDMProxyPromise> mCDMProxyPromise;
 #endif
 
 protected:
-  virtual void CallSeek(const SeekTarget& aTarget);
+  // The promise resolving/rejection is queued as a "micro-task" which will be
+  // handled immediately after the current JS task and before any pending JS
+  // tasks.
+  // At the time we are going to resolve/reject a promise, the "seeking" event
+  // task should already be queued but might yet be processed, so we queue one
+  // more task to file the promise resolving/rejection micro-tasks
+  // asynchronously to make sure that the micro-tasks are processed after the
+  // "seeking" event task.
+  void AsyncResolveSeekDOMPromiseIfExists();
+  void AsyncRejectSeekDOMPromiseIfExists();
+  void DiscardOngoingSeekIfExists();
+  virtual void CallSeek(const SeekTarget& aTarget, dom::Promise* aPromise);
 
   // Returns true if heuristic dormant is supported.
   bool IsHeuristicDormantSupported() const;
 
   MozPromiseRequestHolder<SeekPromise> mSeekRequest;
+  RefPtr<dom::Promise> mSeekDOMPromise;
 
   // True when seeking or otherwise moving the play position around in
   // such a manner that progress event data is inaccurate. This is set
   // during seek and duration operations to prevent the progress indicator
   // from jumping around. Read/Write on the main thread only.
   bool mIgnoreProgressData;
 
   // True if the stream is infinite (e.g. a webradio).
--- a/dom/media/MediaStreamGraph.cpp
+++ b/dom/media/MediaStreamGraph.cpp
@@ -2375,41 +2375,16 @@ MediaStream::Resume()
 }
 
 void
 MediaStream::AddListenerImpl(already_AddRefed<MediaStreamListener> aListener)
 {
   MediaStreamListener* listener = *mListeners.AppendElement() = aListener;
   listener->NotifyBlockingChanged(GraphImpl(),
     mNotifiedBlocked ? MediaStreamListener::BLOCKED : MediaStreamListener::UNBLOCKED);
-
-  for (StreamTracks::TrackIter it(mTracks); !it.IsEnded(); it.Next()) {
-    MediaStream* inputStream = nullptr;
-    TrackID inputTrackID = TRACK_INVALID;
-    if (ProcessedMediaStream* ps = AsProcessedStream()) {
-      // The only ProcessedMediaStream where we should have listeners is
-      // TrackUnionStream - it's what's used as owned stream in DOMMediaStream,
-      // the only main-thread exposed stream type.
-      // TrackUnionStream guarantees that each of its tracks has an input track.
-      // Other types do not implement GetInputStreamFor() and will return null.
-      inputStream = ps->GetInputStreamFor(it->GetID());
-      MOZ_ASSERT(inputStream);
-      inputTrackID = ps->GetInputTrackIDFor(it->GetID());
-      MOZ_ASSERT(IsTrackIDExplicit(inputTrackID));
-    }
-
-    uint32_t flags = MediaStreamListener::TRACK_EVENT_CREATED;
-    if (it->IsEnded()) {
-      flags |= MediaStreamListener::TRACK_EVENT_ENDED;
-    }
-    nsAutoPtr<MediaSegment> segment(it->GetSegment()->CreateEmptyClone());
-    listener->NotifyQueuedTrackChanges(Graph(), it->GetID(), it->GetEnd(),
-                                       flags, *segment,
-                                       inputStream, inputTrackID);
-  }
   if (mNotifiedFinished) {
     listener->NotifyEvent(GraphImpl(), MediaStreamListener::EVENT_FINISHED);
   }
   if (mNotifiedHasCurrentData) {
     listener->NotifyHasCurrentData(GraphImpl());
   }
 }
 
@@ -3156,60 +3131,57 @@ MediaInputPort::Graph()
 void
 MediaInputPort::SetGraphImpl(MediaStreamGraphImpl* aGraph)
 {
   MOZ_ASSERT(!mGraph || !aGraph, "Should only be set once");
   mGraph = aGraph;
 }
 
 void
-MediaInputPort::BlockSourceTrackIdImpl(TrackID aTrackId, BlockingMode aBlockingMode)
+MediaInputPort::BlockSourceTrackIdImpl(TrackID aTrackId)
 {
-  mBlockedTracks.AppendElement(Pair<TrackID, BlockingMode>(aTrackId, aBlockingMode));
+  mBlockedTracks.AppendElement(aTrackId);
 }
 
 already_AddRefed<Pledge<bool>>
-MediaInputPort::BlockSourceTrackId(TrackID aTrackId, BlockingMode aBlockingMode)
+MediaInputPort::BlockSourceTrackId(TrackID aTrackId)
 {
   class Message : public ControlMessage {
   public:
     explicit Message(MediaInputPort* aPort,
                      TrackID aTrackId,
-                     BlockingMode aBlockingMode,
                      already_AddRefed<nsIRunnable> aRunnable)
       : ControlMessage(aPort->GetDestination()),
-        mPort(aPort), mTrackId(aTrackId), mBlockingMode(aBlockingMode),
-        mRunnable(aRunnable) {}
+        mPort(aPort), mTrackId(aTrackId), mRunnable(aRunnable) {}
     void Run() override
     {
-      mPort->BlockSourceTrackIdImpl(mTrackId, mBlockingMode);
+      mPort->BlockSourceTrackIdImpl(mTrackId);
       if (mRunnable) {
         mStream->Graph()->DispatchToMainThreadAfterStreamStateUpdate(mRunnable.forget());
       }
     }
     void RunDuringShutdown() override
     {
       Run();
     }
     RefPtr<MediaInputPort> mPort;
     TrackID mTrackId;
-    BlockingMode mBlockingMode;
     nsCOMPtr<nsIRunnable> mRunnable;
   };
 
   MOZ_ASSERT(IsTrackIDExplicit(aTrackId),
              "Only explicit TrackID is allowed");
 
   RefPtr<Pledge<bool>> pledge = new Pledge<bool>();
   nsCOMPtr<nsIRunnable> runnable = NewRunnableFrom([pledge]() {
     MOZ_ASSERT(NS_IsMainThread());
     pledge->Resolve(true);
     return NS_OK;
   });
-  GraphImpl()->AppendMessage(MakeUnique<Message>(this, aTrackId, aBlockingMode, runnable.forget()));
+  GraphImpl()->AppendMessage(MakeUnique<Message>(this, aTrackId, runnable.forget()));
   return pledge.forget();
 }
 
 already_AddRefed<MediaInputPort>
 ProcessedMediaStream::AllocateInputPort(MediaStream* aStream, TrackID aTrackID,
                                         TrackID aDestTrackID,
                                         uint16_t aInputNumber, uint16_t aOutputNumber,
                                         nsTArray<TrackID>* aBlockedTracks)
@@ -3242,17 +3214,17 @@ ProcessedMediaStream::AllocateInputPort(
              "Only TRACK_ANY and explicit ID are allowed for destination track");
   MOZ_ASSERT(aTrackID != TRACK_ANY || aDestTrackID == TRACK_ANY,
              "Generic MediaInputPort cannot produce a single destination track");
   RefPtr<MediaInputPort> port =
     new MediaInputPort(aStream, aTrackID, this, aDestTrackID,
                        aInputNumber, aOutputNumber);
   if (aBlockedTracks) {
     for (TrackID trackID : *aBlockedTracks) {
-      port->BlockSourceTrackIdImpl(trackID, BlockingMode::CREATION);
+      port->BlockSourceTrackIdImpl(trackID);
     }
   }
   port->SetGraphImpl(GraphImpl());
   GraphImpl()->AppendMessage(MakeUnique<Message>(port));
   return port.forget();
 }
 
 void
--- a/dom/media/MediaStreamGraph.h
+++ b/dom/media/MediaStreamGraph.h
@@ -1173,33 +1173,16 @@ protected:
   nsTArray<RefPtr<MediaStreamDirectListener> > mDirectListeners;
   nsTArray<TrackBound<MediaStreamTrackDirectListener>> mDirectTrackListeners;
   bool mPullEnabled;
   bool mUpdateFinished;
   bool mNeedsMixing;
 };
 
 /**
- * The blocking mode decides how a track should be blocked in a MediaInputPort.
- */
-enum class BlockingMode
-{
-  /**
-   * BlockingMode CREATION blocks the source track from being created
-   * in the destination. It'll end if it already exists.
-   */
-  CREATION,
-  /**
-   * BlockingMode END_EXISTING allows a track to be created in the destination
-   * but will end it before any data has been passed through.
-   */
-  END_EXISTING,
-};
-
-/**
  * Represents a connection between a ProcessedMediaStream and one of its
  * input streams.
  * We make these refcounted so that stream-related messages with MediaInputPort*
  * pointers can be sent to the main thread safely.
  *
  * A port can be locked to a specific track in the source stream, in which case
  * only this track will be forwarded to the destination stream. TRACK_ANY
  * can used to signal that all tracks shall be forwarded.
@@ -1270,49 +1253,26 @@ public:
   TrackID GetDestinationTrackId() { return mDestTrack; }
 
   /**
    * Block aTrackId in the source stream from being passed through the port.
    * Consumers will interpret this track as ended.
    * Returns a pledge that resolves on the main thread after the track block has
    * been applied by the MSG.
    */
-  already_AddRefed<media::Pledge<bool, nsresult>> BlockSourceTrackId(TrackID aTrackId,
-                                                                     BlockingMode aBlockingMode);
+  already_AddRefed<media::Pledge<bool, nsresult>> BlockSourceTrackId(TrackID aTrackId);
 private:
-  void BlockSourceTrackIdImpl(TrackID aTrackId, BlockingMode aBlockingMode);
+  void BlockSourceTrackIdImpl(TrackID aTrackId);
 
 public:
-  // Returns true if aTrackId has not been blocked for any reason and this port
-  // has not been locked to another track.
+  // Returns true if aTrackId has not been blocked and this port has not
+  // been locked to another track.
   bool PassTrackThrough(TrackID aTrackId) {
-    bool blocked = false;
-    for (auto pair : mBlockedTracks) {
-      if (pair.first() == aTrackId &&
-          (pair.second() == BlockingMode::CREATION ||
-           pair.second() == BlockingMode::END_EXISTING)) {
-        blocked = true;
-        break;
-      }
-    }
-    return !blocked && (mSourceTrack == TRACK_ANY || mSourceTrack == aTrackId);
-  }
-
-  // Returns true if aTrackId has not been blocked for track creation and this
-  // port has not been locked to another track.
-  bool AllowCreationOf(TrackID aTrackId) {
-    bool blocked = false;
-    for (auto pair : mBlockedTracks) {
-      if (pair.first() == aTrackId &&
-          pair.second() == BlockingMode::CREATION) {
-        blocked = true;
-        break;
-      }
-    }
-    return !blocked && (mSourceTrack == TRACK_ANY || mSourceTrack == aTrackId);
+    return !mBlockedTracks.Contains(aTrackId) &&
+           (mSourceTrack == TRACK_ANY || mSourceTrack == aTrackId);
   }
 
   uint16_t InputNumber() const { return mInputNumber; }
   uint16_t OutputNumber() const { return mOutputNumber; }
 
   // Call on graph manager thread
   struct InputInterval {
     GraphTime mStart;
@@ -1357,19 +1317,17 @@ private:
   MediaStream* mSource;
   TrackID mSourceTrack;
   ProcessedMediaStream* mDest;
   TrackID mDestTrack;
   // The input and output numbers are optional, and are currently only used by
   // Web Audio.
   const uint16_t mInputNumber;
   const uint16_t mOutputNumber;
-
-  typedef Pair<TrackID, BlockingMode> BlockedTrack;
-  nsTArray<BlockedTrack> mBlockedTracks;
+  nsTArray<TrackID> mBlockedTracks;
 
   // Our media stream graph
   MediaStreamGraphImpl* mGraph;
 };
 
 /**
  * This stream processes zero or more input streams in parallel to produce
  * its output. The details of how the output is produced are handled by
@@ -1435,18 +1393,16 @@ public:
   bool HasInputPort(MediaInputPort* aPort)
   {
     return mInputs.Contains(aPort);
   }
   uint32_t InputPortCount()
   {
     return mInputs.Length();
   }
-  virtual MediaStream* GetInputStreamFor(TrackID aTrackID) { return nullptr; }
-  virtual TrackID GetInputTrackIDFor(TrackID aTrackID) { return TRACK_NONE; }
   void DestroyImpl() override;
   /**
    * This gets called after we've computed the blocking states for all
    * streams (mBlocked is up to date up to mStateComputedTime).
    * Also, we've produced output for all streams up to this one. If this stream
    * is not in a cycle, then all its source streams have produced data.
    * Generate output from aFrom to aTo.
    * This will be called on streams that have finished. Most stream types should
--- a/dom/media/MediaStreamTrack.cpp
+++ b/dom/media/MediaStreamTrack.cpp
@@ -110,18 +110,17 @@ protected:
 };
 
 MediaStreamTrack::MediaStreamTrack(DOMMediaStream* aStream, TrackID aTrackID,
                                    TrackID aInputTrackID,
                                    MediaStreamTrackSource* aSource)
   : mOwningStream(aStream), mTrackID(aTrackID),
     mInputTrackID(aInputTrackID), mSource(aSource),
     mPrincipal(aSource->GetPrincipal()),
-    mReadyState(MediaStreamTrackState::Live),
-    mEnabled(true), mRemote(aSource->IsRemote())
+    mEnded(false), mEnabled(true), mRemote(aSource->IsRemote()), mStopped(false)
 {
 
   if (!gMediaStreamTrackLog) {
     gMediaStreamTrackLog = PR_NewLogModule("MediaStreamTrack");
   }
 
   GetSource().RegisterSink(this);
 
@@ -212,18 +211,18 @@ MediaStreamTrack::SetEnabled(bool aEnabl
   GetOwnedStream()->SetTrackEnabled(mTrackID, aEnabled);
 }
 
 void
 MediaStreamTrack::Stop()
 {
   LOG(LogLevel::Info, ("MediaStreamTrack %p Stop()", this));
 
-  if (Ended()) {
-    LOG(LogLevel::Warning, ("MediaStreamTrack %p Already ended", this));
+  if (mStopped) {
+    LOG(LogLevel::Warning, ("MediaStreamTrack %p Already stopped", this));
     return;
   }
 
   if (mRemote) {
     LOG(LogLevel::Warning, ("MediaStreamTrack %p is remote. Can't be stopped.", this));
     return;
   }
 
@@ -232,20 +231,20 @@ MediaStreamTrack::Stop()
     return;
   }
 
   mSource->UnregisterSink(this);
 
   MOZ_ASSERT(mOwningStream, "Every MediaStreamTrack needs an owning DOMMediaStream");
   DOMMediaStream::TrackPort* port = mOwningStream->FindOwnedTrackPort(*this);
   MOZ_ASSERT(port, "A MediaStreamTrack must exist in its owning DOMMediaStream");
-  RefPtr<Pledge<bool>> p = port->BlockSourceTrackId(mInputTrackID, BlockingMode::CREATION);
+  RefPtr<Pledge<bool>> p = port->BlockSourceTrackId(mInputTrackID);
   Unused << p;
 
-  mReadyState = MediaStreamTrackState::Ended;
+  mStopped = true;
 }
 
 already_AddRefed<Promise>
 MediaStreamTrack::ApplyConstraints(const MediaTrackConstraints& aConstraints,
                                    ErrorResult &aRv)
 {
   if (MOZ_LOG_TEST(gMediaStreamTrackLog, LogLevel::Info)) {
     nsString str;
@@ -346,32 +345,16 @@ MediaStreamTrack::Clone()
 
   MediaStreamGraph* graph = Graph();
   newStream->InitOwnedStreamCommon(graph);
   newStream->InitPlaybackStreamCommon(graph);
 
   return newStream->CloneDOMTrack(*this, mTrackID);
 }
 
-void
-MediaStreamTrack::NotifyEnded()
-{
-  MOZ_ASSERT(NS_IsMainThread());
-
-  if (Ended()) {
-    return;
-  }
-
-  LOG(LogLevel::Info, ("MediaStreamTrack %p ended", this));
-
-  mReadyState = MediaStreamTrackState::Ended;
-
-  DispatchTrustedEvent(NS_LITERAL_STRING("ended"));
-}
-
 DOMMediaStream*
 MediaStreamTrack::GetInputDOMStream()
 {
   MediaStreamTrack* originalTrack =
     mOriginalTrack ? mOriginalTrack.get() : this;
   MOZ_RELEASE_ASSERT(originalTrack->mOwningStream);
   return originalTrack->mOwningStream;
 }
--- a/dom/media/MediaStreamTrack.h
+++ b/dom/media/MediaStreamTrack.h
@@ -261,39 +261,20 @@ public:
   void GetId(nsAString& aID) const;
   void GetLabel(nsAString& aLabel) { GetSource().GetLabel(aLabel); }
   bool Enabled() { return mEnabled; }
   void SetEnabled(bool aEnabled);
   void Stop();
   already_AddRefed<Promise>
   ApplyConstraints(const dom::MediaTrackConstraints& aConstraints, ErrorResult &aRv);
   already_AddRefed<MediaStreamTrack> Clone();
-  MediaStreamTrackState ReadyState() { return mReadyState; }
 
-  IMPL_EVENT_HANDLER(ended)
-
-  /**
-   * Convenience (and legacy) method for when ready state is "ended".
-   */
-  bool Ended() const { return mReadyState == MediaStreamTrackState::Ended; }
-
-  /**
-   * Forces the ready state to a particular value, for instance when we're
-   * cloning an already ended track.
-   */
-  void SetReadyState(MediaStreamTrackState aState) { mReadyState = aState; }
-
-  /**
-   * Notified by the MediaStreamGraph, through our owning MediaStream on the
-   * main thread.
-   *
-   * Note that this sets the track to ended and raises the "ended" event
-   * synchronously.
-   */
-  void NotifyEnded();
+  bool Ended() const { return mEnded; }
+  // Notifications from the MediaStreamGraph
+  void NotifyEnded() { mEnded = true; }
 
   /**
    * Get this track's principal.
    */
   nsIPrincipal* GetPrincipal() const { return mPrincipal; }
 
   /**
    * Called by the PrincipalHandleListener when this track's PrincipalHandle changes on
@@ -412,17 +393,18 @@ protected:
   TrackID mTrackID;
   TrackID mInputTrackID;
   RefPtr<MediaStreamTrackSource> mSource;
   RefPtr<MediaStreamTrack> mOriginalTrack;
   nsCOMPtr<nsIPrincipal> mPrincipal;
   nsCOMPtr<nsIPrincipal> mPendingPrincipal;
   RefPtr<PrincipalHandleListener> mPrincipalHandleListener;
   nsString mID;
-  MediaStreamTrackState mReadyState;
+  bool mEnded;
   bool mEnabled;
   const bool mRemote;
+  bool mStopped;
 };
 
 } // namespace dom
 } // namespace mozilla
 
 #endif /* MEDIASTREAMTRACK_H_ */
--- a/dom/media/StreamTracks.cpp
+++ b/dom/media/StreamTracks.cpp
@@ -98,14 +98,20 @@ StreamTracks::ForgetUpTo(StreamTime aTim
   const StreamTime minChunkSize = 2400;
   if (aTime < mForgottenTime + minChunkSize) {
     return;
   }
   mForgottenTime = aTime;
 
   for (uint32_t i = 0; i < mTracks.Length(); ++i) {
     Track* track = mTracks[i];
+    if (track->IsEnded() && track->GetEnd() <= aTime) {
+      mTracks.RemoveElementAt(i);
+      mTracksDirty = true;
+      --i;
+      continue;
+    }
     StreamTime forgetTo = std::min(track->GetEnd() - 1, aTime);
     track->ForgetUpTo(forgetTo);
   }
 }
 
 } // namespace mozilla
--- a/dom/media/TextTrackCue.cpp
+++ b/dom/media/TextTrackCue.cpp
@@ -1,15 +1,16 @@
 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 /* 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 "mozilla/dom/HTMLTrackElement.h"
 #include "mozilla/dom/TextTrackCue.h"
+#include "mozilla/dom/TextTrackList.h"
 #include "mozilla/dom/TextTrackRegion.h"
 #include "nsComponentManagerUtils.h"
 #include "mozilla/ClearOnShutdown.h"
 
 namespace mozilla {
 namespace dom {
 
 NS_IMPL_CYCLE_COLLECTION_INHERITED(TextTrackCue,
@@ -29,17 +30,17 @@ StaticRefPtr<nsIWebVTTParserWrapper> Tex
 
 // Set cue setting defaults based on step 19 & seq.
 // in http://dev.w3.org/html5/webvtt/#parsing
 void
 TextTrackCue::SetDefaultCueSettings()
 {
   mPosition = 50;
   mPositionAlign = PositionAlignSetting::Center;
-  mSize = 100;
+  mSize = 100.0;
   mPauseOnExit = false;
   mSnapToLines = true;
   mLineIsAutoKeyword = true;
   mAlign = AlignSetting::Middle;
   mLineAlign = LineAlignSetting::Start;
   mVertical = DirectionSetting::_empty;
   mActive = false;
 }
@@ -164,16 +165,50 @@ TextTrackCue::SetRegion(TextTrackRegion*
 {
   if (mRegion == aRegion) {
     return;
   }
   mRegion = aRegion;
   mReset = true;
 }
 
+double
+TextTrackCue::ComputedLine()
+{
+  // See spec https://w3c.github.io/webvtt/#cue-computed-line
+  if (!mLineIsAutoKeyword && !mSnapToLines &&
+      (mLine < 0.0 || mLine > 100.0)) {
+    return 100.0;
+  } else if (!mLineIsAutoKeyword) {
+    return mLine;
+  } else if (mLineIsAutoKeyword && !mSnapToLines) {
+    return 100.0;
+  } else if (!mTrack ||
+             !mTrack->GetTextTrackList() ||
+             !mTrack->GetTextTrackList()->GetMediaElement()) {
+    return -1.0;
+  }
+
+  RefPtr<TextTrackList> trackList = mTrack->GetTextTrackList();
+  bool dummy;
+  uint32_t showingTracksNum = 0;
+  for (uint32_t idx = 0; idx < trackList->Length(); idx++) {
+    RefPtr<TextTrack> track = trackList->IndexedGetter(idx, dummy);
+    if (track->Mode() == TextTrackMode::Showing) {
+      showingTracksNum++;
+    }
+
+    if (mTrack == track) {
+      break;
+    }
+  }
+
+  return (-1.0) * showingTracksNum;
+}
+
 PositionAlignSetting
 TextTrackCue::ComputedPositionAlign()
 {
   // See spec https://w3c.github.io/webvtt/#cue-computed-position-alignment
   if (mPositionAlign != PositionAlignSetting::Auto) {
     return mPositionAlign;
   } else if (mAlign == AlignSetting::Left) {
     return PositionAlignSetting::Line_left;
--- a/dom/media/TextTrackCue.h
+++ b/dom/media/TextTrackCue.h
@@ -146,31 +146,31 @@ public:
     if (mSnapToLines == aSnapToLines) {
       return;
     }
 
     mReset = true;
     mSnapToLines = aSnapToLines;
   }
 
-  void GetLine(OwningLongOrAutoKeyword& aLine) const
+  void GetLine(OwningDoubleOrAutoKeyword& aLine) const
   {
     if (mLineIsAutoKeyword) {
       aLine.SetAsAutoKeyword() = AutoKeyword::Auto;
       return;
     }
-    aLine.SetAsLong() = mLineLong;
+    aLine.SetAsDouble() = mLine;
   }
 
-  void SetLine(const LongOrAutoKeyword& aLine)
+  void SetLine(const DoubleOrAutoKeyword& aLine)
   {
-    if (aLine.IsLong() &&
-        (mLineIsAutoKeyword || (aLine.GetAsLong() != mLineLong))) {
+    if (aLine.IsDouble() &&
+        (mLineIsAutoKeyword || (aLine.GetAsDouble() != mLine))) {
       mLineIsAutoKeyword = false;
-      mLineLong = aLine.GetAsLong();
+      mLine = aLine.GetAsDouble();
       mReset = true;
       return;
     }
     if (aLine.IsAutoKeyword() && !mLineIsAutoKeyword) {
       mLineIsAutoKeyword = true;
       mReset = true;
     }
   }
@@ -220,28 +220,28 @@ public:
     if (mPositionAlign == aPositionAlign) {
       return;
     }
 
     mReset = true;
     mPositionAlign = aPositionAlign;
   }
 
-  int32_t Size() const
+  double Size() const
   {
     return mSize;
   }
 
-  void SetSize(int32_t aSize, ErrorResult& aRv)
+  void SetSize(double aSize, ErrorResult& aRv)
   {
     if (mSize == aSize) {
       return;
     }
 
-    if (aSize < 0 || aSize > 100) {
+    if (aSize < 0.0 || aSize > 100.0) {
       aRv.Throw(NS_ERROR_DOM_INDEX_SIZE_ERR);
       return;
     }
 
     mReset = true;
     mSize = aSize;
   }
 
@@ -294,16 +294,17 @@ public:
     mReset = true;
   }
 
   bool HasBeenReset()
   {
     return mReset;
   }
 
+  double ComputedLine();
   PositionAlignSetting ComputedPositionAlign();
 
   // Helper functions for implementation.
   bool
   operator==(const TextTrackCue& rhs) const
   {
     return mId.Equals(rhs.mId);
   }
@@ -356,23 +357,23 @@ private:
   double mStartTime;
   double mEndTime;
 
   RefPtr<TextTrack> mTrack;
   RefPtr<HTMLTrackElement> mTrackElement;
   nsString mId;
   int32_t mPosition;
   PositionAlignSetting mPositionAlign;
-  int32_t mSize;
+  double mSize;
   bool mPauseOnExit;
   bool mSnapToLines;
   RefPtr<TextTrackRegion> mRegion;
   DirectionSetting mVertical;
   bool mLineIsAutoKeyword;
-  long mLineLong;
+  double mLine;
   AlignSetting mAlign;
   LineAlignSetting mLineAlign;
 
   // Holds the computed DOM elements that represent the parsed cue text.
   // http://www.whatwg.org/specs/web-apps/current-work/#text-track-cue-display-state
   RefPtr<nsGenericHTMLElement> mDisplayState;
   // Tells whether or not we need to recompute mDisplayState. This is set
   // anytime a property that relates to the display of the TextTrackCue is
--- a/dom/media/TrackUnionStream.cpp
+++ b/dom/media/TrackUnionStream.cpp
@@ -102,17 +102,17 @@ TrackUnionStream::TrackUnionStream(DOMMe
             } else {
               CopyTrackData(tracks.get(), j, aFrom, aTo, &trackFinished);
             }
             mappedTracksFinished[j] = trackFinished;
             mappedTracksWithMatchingInputTracks[j] = true;
             break;
           }
         }
-        if (!found && mInputs[i]->AllowCreationOf(tracks->GetID())) {
+        if (!found && mInputs[i]->PassTrackThrough(tracks->GetID())) {
           bool trackFinished = false;
           trackAdded = true;
           uint32_t mapIndex = AddTrack(mInputs[i], tracks.get(), aFrom);
           CopyTrackData(tracks.get(), mapIndex, aFrom, aTo, &trackFinished);
           mappedTracksFinished.AppendElement(trackFinished);
           mappedTracksWithMatchingInputTracks.AppendElement(true);
         }
       }
@@ -369,40 +369,16 @@ TrackUnionStream::SetTrackEnabledImpl(Tr
           listener->IncreaseDisabled();
         }
       }
     }
   }
   MediaStream::SetTrackEnabledImpl(aTrackID, aEnabled);
 }
 
-MediaStream*
-TrackUnionStream::GetInputStreamFor(TrackID aTrackID)
-{
-  for (TrackMapEntry& entry : mTrackMap) {
-    if (entry.mOutputTrackID == aTrackID && entry.mInputPort) {
-      return entry.mInputPort->GetSource();
-    }
-  }
-
-  return nullptr;
-}
-
-TrackID
-TrackUnionStream::GetInputTrackIDFor(TrackID aTrackID)
-{
-  for (TrackMapEntry& entry : mTrackMap) {
-    if (entry.mOutputTrackID == aTrackID) {
-      return entry.mInputTrackID;
-    }
-  }
-
-  return TRACK_NONE;
-}
-
 void
 TrackUnionStream::AddDirectTrackListenerImpl(already_AddRefed<MediaStreamTrackDirectListener> aListener,
                                              TrackID aTrackID)
 {
   RefPtr<MediaStreamTrackDirectListener> listener = aListener;
 
   for (TrackMapEntry& entry : mTrackMap) {
     if (entry.mOutputTrackID == aTrackID) {
--- a/dom/media/TrackUnionStream.h
+++ b/dom/media/TrackUnionStream.h
@@ -19,19 +19,16 @@ class TrackUnionStream : public Processe
 public:
   explicit TrackUnionStream(DOMMediaStream* aWrapper);
 
   void RemoveInput(MediaInputPort* aPort) override;
   void ProcessInput(GraphTime aFrom, GraphTime aTo, uint32_t aFlags) override;
 
   void SetTrackEnabledImpl(TrackID aTrackID, bool aEnabled) override;
 
-  MediaStream* GetInputStreamFor(TrackID aTrackID) override;
-  TrackID GetInputTrackIDFor(TrackID aTrackID) override;
-
 protected:
   // Only non-ended tracks are allowed to persist in this map.
   struct TrackMapEntry {
     // mEndOfConsumedInputTicks is the end of the input ticks that we've consumed.
     // 0 if we haven't consumed any yet.
     StreamTime mEndOfConsumedInputTicks;
     // mEndOfLastInputIntervalInInputStream is the timestamp for the end of the
     // previous interval which was unblocked for both the input and output
--- a/dom/media/omx/OMXCodecWrapper.cpp
+++ b/dom/media/omx/OMXCodecWrapper.cpp
@@ -410,17 +410,17 @@ ConvertGrallocImageToNV12(GrallocImage* 
 
   graphicBuffer->unlock();
 }
 
 static nsresult
 ConvertSourceSurfaceToNV12(const RefPtr<SourceSurface>& aSurface, uint8_t* aDestination)
 {
   if (!aSurface) {
-    CODEC_ERROR("Getting surface %s from image failed");
+    CODEC_ERROR("Getting surface from image failed");
     return NS_ERROR_FAILURE;
   }
 
   uint32_t width = aSurface->GetSize().width;
   uint32_t height = aSurface->GetSize().height;
 
   uint8_t* y = aDestination;
   int yStride = width;
--- a/dom/media/test/external/requirements.txt
+++ b/dom/media/test/external/requirements.txt
@@ -12,9 +12,9 @@ moznetwork==0.27
 mozprocess==0.22
 mozprofile==0.28
 mozrunner==6.11
 moztest==0.7
 mozversion==1.4
 wptserve==1.3.0
 marionette-client==2.2.0
 marionette-driver==1.3.0
-firefox-puppeteer==4.0.0
+firefox-puppeteer~=49.0.0
--- a/dom/media/test/test_seekToNextFrame.html
+++ b/dom/media/test/test_seekToNextFrame.html
@@ -24,48 +24,54 @@ function startTest(test, token) {
       Log(token, "timed out: ended=" + v.seenEnded);
     }
   };
   manager.started(token, handler);
 
   v.src = test.name;
   v.name = test.name;
 
+  function callSeekToNextFrame() {
+    v.seekToNextFrame().then(
+      () => {
+        ok(v.seenSeeking, "Should have already received seeking event.")
+        v.seenSeeking = false;
+        if (!v.seenEnded)
+          callSeekToNextFrame();
+      },
+      () => {
+        ok(false, "seekToNextFrame() failed.");
+      }
+    );
+  }
+
   var onLoadedmetadata = function(test, v) { return function() {
-    v.seekToNextFrame();
+    callSeekToNextFrame();
   }}(test, v);
 
   var finish = function() {
     v.finished = true;
     v.removeEventListener("loadedmetadata", onLoadedmetadata, false);
     v.removeEventListener("seeking", onSeeking, false);
-    v.removeEventListener("seeked", onSeeked, false);
     removeNodeAndSource(v);
     manager.finished(v.token);
   }
 
   var onEnded = function(test, v) { return function() {
     v.seenEnded = true;
     finish();
   }}(test, v);
 
   var onSeeking = function(test, v) { return function() {
     ok(!v.seenSeeking, "Should yet receive seeking event.")
     v.seenSeeking = true;
   }}(test, v);
 
-  var onSeeked = function(test, v) { return function() {
-    ok(v.seenSeeking, "Should have already received seeking event.")
-    v.seenSeeking = false;
-    v.seekToNextFrame();
-  }}(test, v);
-
   v.addEventListener("loadedmetadata", onLoadedmetadata, false);
   v.addEventListener("seeking", onSeeking, false);
-  v.addEventListener("seeked", onSeeked, false);
   v.addEventListener("ended", onEnded, false);
 
   document.body.appendChild(v);
 }
 
 SimpleTest.waitForExplicitFinish();
 SpecialPowers.pushPrefEnv(
   {
--- a/dom/media/test/test_texttrackcue.html
+++ b/dom/media/test/test_texttrackcue.html
@@ -84,34 +84,37 @@ SpecialPowers.pushPrefEnv({"set": [["med
       cue.pauseOnExit = true;
       is(cue.pauseOnExit, true, "Cue's pause on exit flag should be true.");
       video.addEventListener("pause", function pauseOnExit() {
         video.removeEventListener("pause", pauseOnExit, false);
         video.play();
       });
 
       var exceptionHappened;
-      function checkPercentageValue(prop) {
+      function checkPercentageValue(prop, initialVal) {
         ok(prop in cue, prop + " should be a property on VTTCue.");
-        cue[prop] = 20;
-        is(cue[prop], 20, "Cue's " + prop + " should now be 20.");
+        cue[prop] = initialVal;
+        is(cue[prop], initialVal, "Cue's " + prop + " should now be " + initialVal);
         [ 101, -1 ].forEach(function(val) {
           exceptionHappened = false;
           try {
             cue[prop] = val;
           } catch(e) {
             exceptionHappened = true;
             is(e.name, "IndexSizeError", "Should have thrown IndexSizeError.");
           }
           ok(exceptionHappened, "Exception should have happened.");
         });
       }
 
-      checkPercentageValue("size");
-      checkPercentageValue("position");
+      checkPercentageValue("size", 100.0);
+      cue.size = 50.5;
+      is(cue.size, 50.5, "Cue's size should be 50.5.")
+
+      checkPercentageValue("position", 50.0);
 
       ok(cue.snapToLines, "Cue's snapToLines should be set by set.");
       cue.snapToLines = false;
       ok(!cue.snapToLines, "Cue's snapToLines should not be set.");
 
       function checkEnumValue(prop, initialVal, acceptedValues) {
         ok(prop in cue, prop + " should be a property on VTTCue.");
         is(cue[prop], initialVal, "Cue's " + prop + " should be " + initialVal);
@@ -145,18 +148,18 @@ SpecialPowers.pushPrefEnv({"set": [["med
       is(cue.positionAlign, "line-left", "Cue's position align should be line-left.");
       cue.positionAlign = "auto";
       is(cue.positionAlign, "auto", "Cue's position align should be auto.");
       cue.positionAlign = "line-right";
       is(cue.positionAlign, "line-right", "Cue's position align should be line-right.");
 
       // Check cue.line
       is(cue.line, "auto", "Cue's line value should initially be auto.");
-      cue.line = 12410
-      is(cue.line, 12410, "Cue's line value should now be 12410.");
+      cue.line = 0.5;
+      is(cue.line, 0.5, "Cue's line value should now be 0.5.");
       cue.line = "auto";
       is(cue.line, "auto", "Cue's line value should now be auto.");
 
       // Check that we can create and add new VTTCues
       var vttCue = new VTTCue(3.999, 4, "foo");
       is(vttCue.track, null, "Cue's track should be null.");
       trackElement.track.addCue(vttCue);
       is(cue.track, trackElement.track, "Cue's track should be defined.");
--- a/dom/media/tests/mochitest/head.js
+++ b/dom/media/tests/mochitest/head.js
@@ -436,18 +436,18 @@ function checkMediaStreamTrackCloneAgain
      "Track clone's kind should be same as the original's");
   is(clone.enabled, original.enabled,
      "Track clone's kind should be same as the original's");
 }
 
 /*** Utility methods */
 
 /** The dreadful setTimeout, use sparingly */
-function wait(time, message) {
-  return new Promise(r => setTimeout(() => r(message), time));
+function wait(time) {
+  return new Promise(r => setTimeout(r, time));
 }
 
 /** The even more dreadful setInterval, use even more sparingly */
 function waitUntil(func, time) {
   return new Promise(resolve => {
     var interval = setInterval(() => {
       if (func())  {
         clearInterval(interval);
@@ -477,18 +477,18 @@ var addFinallyToPromise = promise => {
     );
   }
   return promise;
 }
 
 /** Use event listener to call passed-in function on fire until it returns true */
 var listenUntil = (target, eventName, onFire) => {
   return new Promise(resolve => target.addEventListener(eventName,
-                                                        function callback(event) {
-    var result = onFire(event);
+                                                        function callback() {
+    var result = onFire();
     if (result) {
       target.removeEventListener(eventName, callback, false);
       resolve(result);
     }
   }, false));
 };
 
 /* Test that a function throws the right error */
@@ -585,37 +585,16 @@ function createOneShotEventWrapper(wrapp
   obj[onx] = e => {
     info(wrapper + ': "on' + event + '" event fired');
     e.wrapper = wrapper;
     wrapper[onx](e);
     wrapper[onx] = unexpected;
   };
 }
 
-/**
- * Returns a promise that resolves when `target` has raised an event with the
- * given name. Cancel the returned promise by passing in a `cancelPromise` and
- * resolve it.
- *
- * @param {object} target
- *        The target on which the event should occur.
- * @param {string} name
- *        The name of the event that should occur.
- * @param {promise} cancelPromise
- *        A promise that on resolving rejects the returned promise,
- *        so we can avoid logging results after a test has finished.
- */
-function haveEvent(target, name, cancelPromise) {
-  var listener;
-  var p = Promise.race([
-    (cancelPromise || new Promise()).then(e => Promise.reject(e)),
-    new Promise(resolve => target.addEventListener(name, listener = resolve))
-  ]);
-  return p.then(event => (target.removeEventListener(name, listener), event));
-};
 
 /**
  * This class executes a series of functions in a continuous sequence.
  * Promise-bearing functions are executed after the previous promise completes.
  *
  * @constructor
  * @param {object} framework
  *        A back reference to the framework which makes use of the class. It is
--- a/dom/media/tests/mochitest/mediaStreamPlayback.js
+++ b/dom/media/tests/mochitest/mediaStreamPlayback.js
@@ -50,37 +50,27 @@ MediaStreamPlayback.prototype = {
     var elem = this.mediaElement;
     var waitForEnded = () => new Promise(resolve => {
       elem.addEventListener('ended', function ended() {
         elem.removeEventListener('ended', ended);
         resolve();
       });
     });
 
-    var noTrackEnded = Promise.all(this.mediaStream.getTracks().map(t => {
-      let onNextLoop = wait(0);
-      let p = Promise.race([
-        onNextLoop,
-        haveEvent(t, "ended", onNextLoop)
-          .then(() => Promise.reject("Unexpected ended event for track " + t.id),
-                () => Promise.resolve())
-      ]);
-      t.stop();
-      return p;
-    }));
+    // TODO (bug 910249) Also check that all the tracks are local.
+    this.mediaStream.getTracks().forEach(t => t.stop());
 
     // XXX (bug 1208316) When we implement MediaStream.active, do not stop
     // the stream. We just do it now so the media element will raise 'ended'.
     if (!this.mediaStream.stop) {
       return;
     }
     this.mediaStream.stop();
     return timeout(waitForEnded(), ENDED_TIMEOUT_LENGTH, "ended event never fired")
-             .then(() => ok(true, "ended event successfully fired"))
-             .then(() => noTrackEnded);
+             .then(() => ok(true, "ended event successfully fired"));
   },
 
   /**
    * Starts media with a media stream, runs it until a canplaythrough and
    * timeupdate event fires, and stops the media.
    *
    * @param {Boolean} isResume specifies if this media element is being resumed
    *                           from a previous run
--- a/dom/media/tests/mochitest/mochitest.ini
+++ b/dom/media/tests/mochitest/mochitest.ini
@@ -39,17 +39,16 @@ skip-if = toolkit == 'gonk' || buildapp 
 skip-if = toolkit == 'gonk' || buildapp == 'mulet' # b2g emulator seems to be too slow (Bug 1016498 and 1008080)
 [test_dataChannel_noOffer.html]
 [test_enumerateDevices.html]
 skip-if = buildapp == 'mulet'
 [test_getUserMedia_audioCapture.html]
 skip-if = toolkit == 'gonk' || buildapp == 'mulet' || android_version == '18' # b2g emulator seems to be too slow (Bug 1016498 and 1008080), android(Bug 1189784, timeouts on 4.3 emulator)
 [test_getUserMedia_addTrackRemoveTrack.html]
 skip-if = (toolkit == 'gonk' || buildapp == 'mulet' && debug) # debug-only failure
-[test_getUserMedia_addtrack_removetrack_events.html]
 [test_getUserMedia_basicAudio.html]
 skip-if = (toolkit == 'gonk' || buildapp == 'mulet' && debug) # debug-only failure
 [test_getUserMedia_basicVideo.html]
 skip-if = (toolkit == 'gonk' || buildapp == 'mulet' && debug) # debug-only failure
 [test_getUserMedia_basicVideo_playAfterLoadedmetadata.html]
 skip-if = (toolkit == 'gonk' || buildapp == 'mulet' && debug) # debug-only failure
 [test_getUserMedia_basicScreenshare.html]
 skip-if = buildapp == 'b2g' || toolkit == 'android' # no screenshare on b2g/android # Bug 1141029 Mulet parity with B2G Desktop for TC
@@ -75,21 +74,19 @@ skip-if = toolkit == 'gonk' || buildapp 
 [test_getUserMedia_spinEventLoop.html]
 skip-if = (toolkit == 'gonk' || buildapp == 'mulet' && debug) # copied from basicAudio
 [test_getUserMedia_stopAudioStream.html]
 [test_getUserMedia_stopAudioStreamWithFollowupAudio.html]
 [test_getUserMedia_stopVideoAudioStream.html]
 [test_getUserMedia_stopVideoAudioStreamWithFollowupVideoAudio.html]
 [test_getUserMedia_stopVideoStream.html]
 [test_getUserMedia_stopVideoStreamWithFollowupVideo.html]
-[test_getUserMedia_trackEnded.html]
 [test_getUserMedia_peerIdentity.html]
 skip-if = toolkit == 'gonk' || buildapp == 'mulet' # b2g(Bug 1021776, too --ing slow on b2g)
 [test_peerConnection_addIceCandidate.html]
-[test_peerConnection_addtrack_removetrack_events.html]
 [test_peerConnection_basicAudio.html]
 skip-if = toolkit == 'gonk' # B2G emulator is too slow to handle a two-way audio call reliably
 [test_peerConnection_basicAudioNATSrflx.html]
 skip-if = toolkit == 'gonk' || toolkit == 'android' # B2G emulator is too slow to handle a two-way audio call reliably, websockets don't work on android (bug 1266217)
 [test_peerConnection_basicAudioNATRelay.html]
 skip-if = toolkit == 'gonk' || toolkit == 'android' # B2G emulator is too slow to handle a two-way audio call reliably, websockets don't work on android (bug 1266217)
 [test_peerConnection_basicAudioNATRelayTCP.html]
 skip-if = toolkit == 'gonk' || toolkit == 'android' # B2G emulator is too slow to handle a two-way audio call reliably, websockets don't work on android (bug 1266217)
--- a/dom/media/tests/mochitest/pc.js
+++ b/dom/media/tests/mochitest/pc.js
@@ -129,39 +129,23 @@ function timerGuard(p, time, message) {
 PeerConnectionTest.prototype.closePC = function() {
   info("Closing peer connections");
 
   var closeIt = pc => {
     if (!pc || pc.signalingState === "closed") {
       return Promise.resolve();
     }
 
-    var promise = Promise.all([
-      new Promise(resolve => {
-        pc.onsignalingstatechange = e => {
-          is(e.target.signalingState, "closed", "signalingState is closed");
-          resolve();
-        };
-      }),
-      Promise.all(pc._pc.getReceivers()
-        .filter(receiver => receiver.track.readyState == "live")
-        .map(receiver => {
-          info("Waiting for track " + receiver.track.id + " (" +
-               receiver.track.kind + ") to end.");
-          return haveEvent(receiver.track, "ended", wait(50000))
-            .then(event => {
-              is(event.target, receiver.track, "Event target should be the correct track");
-              info("ended fired for track " + receiver.track.id);
-            }, e => e ? Promise.reject(e)
-                      : ok(false, "ended never fired for track " +
-                                    receiver.track.id));
-        }))
-    ]);
-    pc.close();
-    return promise;
+    return new Promise(resolve => {
+      pc.onsignalingstatechange = e => {
+        is(e.target.signalingState, "closed", "signalingState is closed");
+        resolve();
+      };
+      pc.close();
+    });
   };
 
   return timerGuard(Promise.all([
     closeIt(this.pcLocal),
     closeIt(this.pcRemote)
   ]), 60000, "failed to close peer connection");
 };
 
@@ -461,32 +445,29 @@ PeerConnectionTest.prototype.updateChain
 
 /**
  * Start running the tests as assigned to the command chain.
  */
 PeerConnectionTest.prototype.run = function() {
   /* We have to modify the chain here to allow tests which modify the default
    * test chain instantiating a PeerConnectionTest() */
   this.updateChainSteps();
-  var finished = () => {
-    if (window.SimpleTest) {
-      networkTestFinished();
-    } else {
-      finish();
-    }
-  };
   return this.chain.execute()
     .then(() => this.close())
+    .then(() => {
+      if (window.SimpleTest) {
+        networkTestFinished();
+      } else {
+        finish();
+      }
+    })
     .catch(e =>
-      ok(false, 'Error in test execution: ' + e +
-         ((typeof e.stack === 'string') ?
-          (' ' + e.stack.split('\n').join(' ... ')) : '')))
-    .then(() => finished())
-    .catch(e =>
-      ok(false, "Error in finished()"));
+           ok(false, 'Error in test execution: ' + e +
+              ((typeof e.stack === 'string') ?
+               (' ' + e.stack.split('\n').join(' ... ')) : '')));
 };
 
 /**
  * Routes ice candidates from one PCW to the other PCW
  */
 PeerConnectionTest.prototype.iceCandidateHandler = function(caller, candidate) {
   info("Received: " + JSON.stringify(candidate) + " from " + caller);
 
deleted file mode 100644
--- a/dom/media/tests/mochitest/test_getUserMedia_addtrack_removetrack_events.html
+++ /dev/null
@@ -1,109 +0,0 @@
-<!DOCTYPE HTML>
-<html>
-<head>
-  <script type="application/javascript" src="mediaStreamPlayback.js"></script>
-</head>
-<body>
-<pre id="test">
-<script type="application/javascript">
-"use strict";
-
-createHTML({
-  title: "MediaStream's 'addtrack' and 'removetrack' events shouldn't fire on manual operations",
-  bug: "1208328"
-});
-
-var spinEventLoop = () => new Promise(r => setTimeout(r, 0));
-
-var stream;
-var clone;
-var newStream;
-
-var addTrack = track => {
-  info("Adding track " + track.id);
-  stream.addTrack(track);
-};
-var removeTrack = track => {
-  info("Removing track " + track.id);
-  stream.removeTrack(track);
-};
-var stopTrack = track => {
-  if (track.readyState == "live") {
-    info("Stopping track " + track.id);
-  }
-  track.stop();
-};
-
-runTest(() => getUserMedia({audio: true, video: true})
-  .then(s => {
-    stream = s;
-    clone = s.clone();
-    stream.addEventListener("addtrack", function onAddtrack(event) {
-      ok(false, "addtrack fired unexpectedly for track " + event.track.id);
-    });
-    stream.addEventListener("removetrack", function onRemovetrack(event) {
-      ok(false, "removetrack fired unexpectedly for track " + event.track.id);
-    });
-
-    return getUserMedia({audio: true, video: true});
-  })
-  .then(s => {
-    newStream = s;
-
-    info("Stopping an original track");
-    stopTrack(stream.getTracks()[0]);
-
-    return spinEventLoop();
-  })
-  .then(() => {
-    info("Removing original tracks");
-    stream.getTracks().forEach(t => stream.removeTrack(t));
-
-    return spinEventLoop();
-  })
-  .then(() => {
-    info("Adding other gUM tracks");
-    newStream.getTracks().forEach(t => addTrack(t))
-
-    return spinEventLoop();
-  })
-  .then(() => {
-    info("Adding cloned tracks");
-    let clone = stream.clone();
-    clone.getTracks().forEach(t => addTrack(t));
-
-    return spinEventLoop();
-  })
-  .then(() => {
-    info("Removing a clone");
-    removeTrack(clone.getTracks()[0]);
-
-    return spinEventLoop();
-  })
-  .then(() => {
-    info("Stopping clones");
-    clone.getTracks().forEach(t => stopTrack(t));
-
-    return spinEventLoop();
-  })
-  .then(() => {
-    info("Stopping originals");
-    stream.getTracks().forEach(t => stopTrack(t));
-
-    return spinEventLoop();
-  })
-  .then(() => {
-    info("Removing remaining tracks");
-    stream.getTracks().forEach(t => removeTrack(t));
-
-    return spinEventLoop();
-  })
-  .then(() => {
-      // Test MediaStreamTrackEvent required args here.
-      mustThrowWith("MediaStreamTrackEvent without required args",
-                    "TypeError", () => new MediaStreamTrackEvent("addtrack", {}));
-  }));
-</script>
-</pre>
-</body>
-</html>
--- a/dom/media/tests/mochitest/test_getUserMedia_mediaStreamTrackClone.html
+++ b/dom/media/tests/mochitest/test_getUserMedia_mediaStreamTrackClone.html
@@ -47,20 +47,17 @@
       var cloneStream = new MediaStream();
       cloneStream.addTrack(inceptionClone);
 
       // cloneStream is now essentially the same as stream.clone();
       checkMediaStreamCloneAgainstOriginal(cloneStream, stream);
 
       var test = createMediaElement('video', 'testClonePlayback');
       var playback = new MediaStreamPlayback(test, cloneStream);
-      return playback.playMediaWithMediaStreamTracksStop(false)
-        .then(() => info("Testing that clones of ended tracks are ended"))
-        .then(() => cloneStream.clone().getTracks().forEach(t =>
-          is(t.readyState, "ended", "Track " + t.id + " should be ended")));
+      return playback.playMediaWithMediaStreamTracksStop(false);
     })
     .then(() => getUserMedia({audio: true, video: true})).then(stream => {
       info("Test adding many track clones to the original stream");
 
       const LOOPS = 3;
       for (var i = 0; i < LOOPS; i++) {
         stream.getTracks().forEach(t => stream.addTrack(t.clone()));
       }
deleted file mode 100644
--- a/dom/media/tests/mochitest/test_getUserMedia_trackEnded.html
+++ /dev/null
@@ -1,60 +0,0 @@
-<!DOCTYPE HTML>
-<html>
-<head>
-  <script type="application/javascript" src="mediaStreamPlayback.js"></script>
-</head>
-<body>
-<pre id="test">
-<iframe id="iframe" srcdoc="
-  <script type='application/javascript'>
-  document.gUM = (constraints, success, failure) =>
-    navigator.mediaDevices.getUserMedia(constraints).then(success, failure);
-  </script>">
-</iframe>
-<script type="application/javascript">
-  "use strict";
-
-  createHTML({
-    title: "getUserMedia MediaStreamTrack 'ended' event on navigating",
-    bug: "1208373",
-  });
-
-  runTest(() => {
-    let iframe = document.getElementById("iframe");
-    let stream;
-    // We're passing callbacks into a method in the iframe here, because
-    // a Promise created in the iframe is unusable after the iframe has
-    // navigated away (see bug 1269400 for details).
-    return new Promise((resolve, reject) =>
-        iframe.contentDocument.gUM({audio: true, video: true}, resolve, reject))
-      .then(s => {
-        // We're cloning a stream containing identical tracks (an original
-        // and its clone) to test that ended works both for originals
-        // clones when they're both owned by the same MediaStream.
-        // (Bug 1274221)
-        stream = new MediaStream([].concat(s.getTracks(), s.getTracks())
-                                   .map(t => t.clone())).clone();
-        var allTracksEnded = Promise.all(stream.getTracks().map(t => {
-          info("Set up ended handler for track " + t.id);
-          return haveEvent(t, "ended", wait(5000))
-            .then(event => {
-              info("ended handler invoked for track " + t.id);
-              is(event.target, t, "Target should be correct");
-            }, e => e ? Promise.reject(e)
-                      : ok(false, "ended event never raised for track " + t.id));
-        }));
-        stream.getTracks().forEach(t =>
-          is(t.readyState, "live",
-             "Non-ended track should have readyState 'live'"));
-        iframe.srcdoc = "";
-        info("iframe has been reset. Waiting for tracks to end.");
-        return allTracksEnded;
-      })
-      .then(() => stream.getTracks().forEach(t =>
-        is(t.readyState, "ended",
-           "Ended track should have readyState 'ended'")));
-  });
-</script>
-</pre>
-</body>
-</html>
deleted file mode 100644
--- a/dom/media/tests/mochitest/test_peerConnection_addtrack_removetrack_events.html
+++ /dev/null
@@ -1,70 +0,0 @@
-<!DOCTYPE HTML>
-<html>
-<head>
-  <script type="application/javascript" src="pc.js"></script>
-</head>
-<body>
-<pre id="test">
-<script type="application/javascript">
-"use strict";
-
-createHTML({
-  title: "MediaStream's 'addtrack' and 'removetrack' events with gUM",
-  bug: "1208328"
-});
-
-runNetworkTest(function (options) {
-  let test = new PeerConnectionTest(options);
-  let eventsPromise;
-  addRenegotiation(test.chain,
-    [
-      function PC_LOCAL_SWAP_VIDEO_TRACKS(test) {
-        return getUserMedia({video: true}).then(stream => {
-          let localStream = test.pcLocal._pc.getLocalStreams()[0];
-          let remoteStream = test.pcRemote._pc.getRemoteStreams()[0];
-
-          let newTrack = stream.getTracks()[0];
-
-          let videoSenderIndex =
-            test.pcLocal._pc.getSenders().findIndex(s => s.track.kind == "video");
-          isnot(videoSenderIndex, -1, "Should have video sender");
-
-          test.pcLocal.removeSender(videoSenderIndex);
-          test.pcLocal.attachLocalTrack(stream.getTracks()[0], localStream);
-
-          let onNextLoop = wait(0);
-          eventsPromise = haveEvent(remoteStream, "addtrack", wait(50000, "No addtrack event"))
-            .then(trackEvent => {
-              ok(trackEvent instanceof MediaStreamTrackEvent,
-                 "Expected event to be instance of MediaStreamTrackEvent");
-              is(trackEvent.type, "addtrack",
-                 "Expected addtrack event type");
-              is(trackEvent.track.id, newTrack.id, "Expected track in event");
-              is(trackEvent.track.readyState, "live",
-                 "added track should be live");
-            })
-            .then(() => haveEvent(remoteStream, "addtrack", onNextLoop)
-              .then(() => Promise.reject("Unexpected addtrack event for remote stream " + remoteStream.id),
-                    () => Promise.resolve())
-            );
-          remoteStream.addEventListener("removetrack",
-                                        function onRemovetrack(trackEvent) {
-            ok(false, "UA shouldn't raise 'removetrack' when receiving peer connection");
-          })
-        });
-      },
-    ],
-    [
-      function PC_REMOTE_CHECK_EVENTS(test) {
-        return eventsPromise;
-      },
-    ]
-  );
-
-  test.setMediaConstraints([{audio: true, video: true}], []);
-  test.run();
-});
-</script>
-</pre>
-</body>
-</html>
--- a/dom/media/tests/mochitest/test_peerConnection_capturedVideo.html
+++ b/dom/media/tests/mochitest/test_peerConnection_capturedVideo.html
@@ -68,18 +68,18 @@ function startTest(media, token) {
       return test.chain.execute();
     });
   })
   // Handle both MediaErrors (with the `code` attribute) and other errors.
   .catch(e => ok(false, "Error (" + e + ")" +
                         (e.code ? " (code=" + e.code + ")" : "") +
                         " in test for " + media.name +
                         (e.stack ? ":\n" + e.stack : "")))
-  .then(() => test && test.close())
   .then(() => {
+    if (test) { test.close(); }
     removeNodeAndSource(video);
     manager.finished(token);
   })
   .catch(e => ok(false, "Error (" + e + ") during shutdown."));
 };
 
 </script>
 </pre>
--- a/dom/media/webvtt/vtt.jsm
+++ b/dom/media/webvtt/vtt.jsm
@@ -674,36 +674,16 @@ this.EXPORTED_SYMBOLS = ["WebVTT"];
             return "rtl";
           }
         }
       }
     }
     return "ltr";
   }
 
-  function computeLinePos(cue) {
-    if (typeof cue.line === "number" &&
-        (cue.snapToLines || (cue.line >= 0 && cue.line <= 100))) {
-      return cue.line;
-    }
-    if (!cue.track || !cue.track.textTrackList ||
-        !cue.track.textTrackList.mediaElement) {
-      return -1;
-    }
-    var track = cue.track,
-        trackList = track.textTrackList,
-        count = 0;
-    for (var i = 0; i < trackList.length && trackList[i] !== track; i++) {
-      if (trackList[i].mode === "showing") {
-        count++;
-      }
-    }
-    return ++count * -1;
-  }
-
   function StyleBox() {
   }
 
   // Apply styles to a div. If there is no div passed then it defaults to the
   // div on 'this'.
   StyleBox.prototype.applyStyles = function(styles, div) {
     div = div || this.div;
     for (var prop in styles) {
@@ -1015,17 +995,17 @@ this.EXPORTED_SYMBOLS = ["WebVTT"];
         // Reset the box position to the specified position.
         b = new BoxPosition(specifiedPosition);
       }
       return bestPosition || specifiedPosition;
     }
 
     var boxPosition = new BoxPosition(styleBox),
         cue = styleBox.cue,
-        linePos = computeLinePos(cue),
+        linePos = cue.computedLine,
         axis = [];
 
     // If we have a line number to align the cue to.
     if (cue.snapToLines) {
       var size;
       switch (cue.vertical) {
       case "":
         axis = [ "+y", "-y" ];
--- a/dom/plugins/ipc/PluginModuleParent.h
+++ b/dom/plugins/ipc/PluginModuleParent.h
@@ -10,17 +10,17 @@
 #include "base/process.h"
 #include "mozilla/FileUtils.h"
 #include "mozilla/HangAnnotations.h"
 #include "mozilla/PluginLibrary.h"
 #include "mozilla/plugins/PluginProcessParent.h"
 #include "mozilla/plugins/PPluginModuleParent.h"
 #include "mozilla/plugins/PluginMessageUtils.h"
 #include "mozilla/plugins/PluginTypes.h"
-#include "mozilla/plugins/TaskFactory.h"
+#include "mozilla/ipc/TaskFactory.h"
 #include "mozilla/TimeStamp.h"
 #include "npapi.h"
 #include "npfunctions.h"
 #include "nsDataHashtable.h"
 #include "nsHashKeys.h"
 #include "nsIObserver.h"
 #ifdef XP_WIN
 #include "nsWindowsHelpers.h"
@@ -324,17 +324,17 @@ protected:
     bool mIsChrome;
     bool mShutdown;
     bool mHadLocalInstance;
     bool mClearSiteDataSupported;
     bool mGetSitesWithDataSupported;
     NPNetscapeFuncs* mNPNIface;
     NPPluginFuncs* mNPPIface;
     nsNPAPIPlugin* mPlugin;
-    TaskFactory<PluginModuleParent> mTaskFactory;
+    ipc::TaskFactory<PluginModuleParent> mTaskFactory;
     nsString mPluginDumpID;
     nsString mBrowserDumpID;
     nsString mHangID;
     RefPtr<nsIObserver> mProfilerObserver;
     TimeDuration mTimeBlocked;
     nsCString mPluginName;
     nsCString mPluginVersion;
     int32_t mSandboxLevel;
@@ -542,17 +542,17 @@ private:
 
     virtual bool RecvNotifyContentModuleDestroyed() override;
 
     static void CachedSettingChanged(const char* aPref, void* aModule);
 
     PluginProcessParent* mSubprocess;
     uint32_t mPluginId;
 
-    TaskFactory<PluginModuleChromeParent> mChromeTaskFactory;
+    ipc::TaskFactory<PluginModuleChromeParent> mChromeTaskFactory;
 
     enum HangAnnotationFlags
     {
         kInPluginCall = (1u << 0),
         kHangUIShown = (1u << 1),
         kHangUIContinued = (1u << 2),
         kHangUIDontShow = (1u << 3)
     };
--- a/dom/plugins/ipc/PluginProcessParent.h
+++ b/dom/plugins/ipc/PluginProcessParent.h
@@ -12,17 +12,17 @@
 
 #include "base/file_path.h"
 #include "base/task.h"
 #include "base/thread.h"
 #include "base/waitable_event.h"
 #include "chrome/common/child_process_host.h"
 
 #include "mozilla/ipc/GeckoChildProcessHost.h"
-#include "mozilla/plugins/TaskFactory.h"
+#include "mozilla/ipc/TaskFactory.h"
 #include "mozilla/UniquePtr.h"
 #include "nsCOMPtr.h"
 #include "nsIRunnable.h"
 
 namespace mozilla {
 namespace plugins {
 
 class LaunchCompleteTask : public Runnable
@@ -76,17 +76,17 @@ public:
     virtual void OnChannelError() override;
 
     bool IsConnected();
 
 private:
     void RunLaunchCompleteTask();
 
     std::string mPluginFilePath;
-    TaskFactory<PluginProcessParent> mTaskFactory;
+    ipc::TaskFactory<PluginProcessParent> mTaskFactory;
     UniquePtr<LaunchCompleteTask> mLaunchCompleteTask;
     MessageLoop* mMainMsgLoop;
     bool mRunCompleteTaskImmediately;
 
     DISALLOW_EVIL_CONSTRUCTORS(PluginProcessParent);
 };
 
 
--- a/dom/plugins/ipc/moz.build
+++ b/dom/plugins/ipc/moz.build
@@ -38,17 +38,16 @@ EXPORTS.mozilla.plugins += [
     'PluginScriptableObjectUtils.h',
     'PluginStreamChild.h',
     'PluginStreamParent.h',
     'PluginUtilsOSX.h',
     'PluginWidgetChild.h',
     'PluginWidgetParent.h',
     'StreamNotifyChild.h',
     'StreamNotifyParent.h',
-    'TaskFactory.h',
 ]
 
 if CONFIG['OS_ARCH'] == 'WINNT':
     EXPORTS.mozilla.plugins += [
         'PluginSurfaceParent.h',
     ]
     UNIFIED_SOURCES += [
         'PluginHangUIParent.cpp',
--- a/dom/promise/Promise.cpp
+++ b/dom/promise/Promise.cpp
@@ -935,16 +935,24 @@ Promise::MaybeReject(const RefPtr<MediaS
 void
 Promise::MaybeRejectWithNull()
 {
   NS_ASSERT_OWNINGTHREAD(Promise);
 
   MaybeSomething(JS::NullHandleValue, &Promise::MaybeReject);
 }
 
+void
+Promise::MaybeRejectWithUndefined()
+{
+  NS_ASSERT_OWNINGTHREAD(Promise);
+
+  MaybeSomething(JS::UndefinedHandleValue, &Promise::MaybeReject);
+}
+
 
 #ifdef SPIDERMONKEY_PROMISE
 void
 Promise::ReportRejectedPromise(JSContext* aCx, JS::HandleObject aPromise)
 {
   MOZ_ASSERT(!js::IsWrapper(aPromise));
 
   MOZ_ASSERT(JS::GetPromiseState(aPromise) == JS::PromiseState::Rejected);
--- a/dom/promise/Promise.h
+++ b/dom/promise/Promise.h
@@ -148,16 +148,18 @@ public:
     MOZ_ASSERT(aArg.Failed());
     MaybeSomething(aArg, &Promise::MaybeReject);
   }
 
   void MaybeReject(const RefPtr<MediaStreamError>& aArg);
 
   void MaybeRejectWithNull();
 
+  void MaybeRejectWithUndefined();
+
   // DO NOT USE MaybeRejectBrokenly with in new code.  Promises should be
   // rejected with Error instances.
   // Note: MaybeRejectBrokenly is a template so we can use it with DOMError
   // without instantiating the DOMError specialization of MaybeSomething in
   // every translation unit that includes this header, because that would
   // require use to include DOMError.h either here or in all those translation
   // units.
   template<typename T>
--- a/dom/webidl/HTMLMediaElement.webidl
+++ b/dom/webidl/HTMLMediaElement.webidl
@@ -207,12 +207,10 @@ partial interface HTMLMediaElement {
  *     "seeked" event.
  * (2) Otherwise, if the currentTime is larger or equal to the n-th frame's
  *     beginning time, then the SeekToNextFrame() operation sets the media's
  *     currentTime to the duration of the media source and dispatches a "seeked"
  *     event and an "ended" event.
  */
 partial interface HTMLMediaElement {
   [Throws, Pref="media.seekToNextFrame.enabled"]
-  void seekToNextFrame(); // This API should be an asynchronous one which
-                          // returns a Promise<void>. Bug 1276272 follows this
-                          // issue.
+  Promise<void> seekToNextFrame();
 };
--- a/dom/webidl/MediaStream.webidl
+++ b/dom/webidl/MediaStream.webidl
@@ -38,12 +38,12 @@ interface MediaStream : EventTarget {
     sequence<MediaStreamTrack> getTracks ();
     MediaStreamTrack?          getTrackById (DOMString trackId);
     void                       addTrack (MediaStreamTrack track);
     void                       removeTrack (MediaStreamTrack track);
     MediaStream                clone ();
     // readonly    attribute boolean      active;
     //             attribute EventHandler onactive;
     //             attribute EventHandler oninactive;
-                attribute EventHandler onaddtrack;
+    //             attribute EventHandler onaddtrack;
     //             attribute EventHandler onremovetrack;
     readonly attribute double currentTime;
 };
--- a/dom/webidl/MediaStreamTrack.webidl
+++ b/dom/webidl/MediaStreamTrack.webidl
@@ -58,34 +58,29 @@ dictionary MediaTrackConstraintSet {
     ConstrainBoolean mozNoiseSuppression;
     ConstrainBoolean mozAutoGainControl;
 };
 
 dictionary MediaTrackConstraints : MediaTrackConstraintSet {
     sequence<MediaTrackConstraintSet> advanced;
 };
 
-enum MediaStreamTrackState {
-    "live",
-    "ended"
-};
-
 [Exposed=Window]
 interface MediaStreamTrack : EventTarget {
     readonly    attribute DOMString             kind;
     readonly    attribute DOMString             id;
     readonly    attribute DOMString             label;
                 attribute boolean               enabled;
 //  readonly    attribute boolean               muted;
 //              attribute EventHandler          onmute;
 //              attribute EventHandler          onunmute;
 //  readonly    attribute boolean               _readonly;
 //  readonly    attribute boolean               remote;
-    readonly    attribute MediaStreamTrackState readyState;
-                attribute EventHandler          onended;
+//  readonly    attribute MediaStreamTrackState readyState;
+//                attribute EventHandler          onended;
     MediaStreamTrack       clone ();
     void                   stop ();
 //  MediaTrackCapabilities getCapabilities ();
 //  MediaTrackConstraints  getConstraints ();
 //  MediaTrackSettings     getSettings ();
 
     [Throws]
     Promise<void>          applyConstraints (optional MediaTrackConstraints constraints);
--- a/dom/webidl/MediaStreamTrackEvent.webidl
+++ b/dom/webidl/MediaStreamTrackEvent.webidl
@@ -3,17 +3,20 @@
  * 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/.
  *
  * The origin of this IDL file is
  * http://dev.w3.org/2011/webrtc/editor/webrtc.html#mediastreamevent
  */
 
 dictionary MediaStreamTrackEventInit : EventInit {
-    required MediaStreamTrack track;
+  MediaStreamTrack? track = null;
+  RTCRtpReceiver? receiver = null;
+  MediaStream? stream = null;
 };
 
-[Exposed=Window,
- Constructor (DOMString type, MediaStreamTrackEventInit eventInitDict)]
+[Pref="media.peerconnection.enabled",
+ Constructor(DOMString type, optional MediaStreamTrackEventInit eventInitDict)]
 interface MediaStreamTrackEvent : Event {
-    [SameObject]
-    readonly        attribute MediaStreamTrack track;
+  readonly attribute RTCRtpReceiver? receiver;
+  readonly attribute MediaStreamTrack? track;
+  readonly attribute MediaStream? stream;
 };
--- a/dom/webidl/VTTCue.webidl
+++ b/dom/webidl/VTTCue.webidl
@@ -38,31 +38,33 @@ enum DirectionSetting {
 
 [Constructor(double startTime, double endTime, DOMString text),
  Pref="media.webvtt.enabled"]
 interface VTTCue : TextTrackCue {
   [Pref="media.webvtt.regions.enabled"]
   attribute VTTRegion? region;
   attribute DirectionSetting vertical;
   attribute boolean snapToLines;
-  attribute (long or AutoKeyword) line;
+  attribute (double or AutoKeyword) line;
   [SetterThrows]
   attribute LineAlignSetting lineAlign;
   [SetterThrows]
   attribute long position;
   [SetterThrows]
   attribute PositionAlignSetting positionAlign;
   [SetterThrows]
-  attribute long size;
+  attribute double size;
   attribute AlignSetting align;
   attribute DOMString text;
   DocumentFragment getCueAsHTML();
 };
 
 // Mozilla extensions.
 partial interface VTTCue {
   [ChromeOnly]
   attribute HTMLDivElement? displayState;
   [ChromeOnly]
   readonly attribute boolean hasBeenReset;
   [ChromeOnly]
+  readonly attribute double computedLine;
+  [ChromeOnly]
   readonly attribute PositionAlignSetting computedPositionAlign;
 };
--- a/embedding/browser/nsContextMenuInfo.cpp
+++ b/embedding/browser/nsContextMenuInfo.cpp
@@ -265,18 +265,17 @@ nsContextMenuInfo::GetBackgroundImageReq
   auto* piWindow = nsPIDOMWindowOuter::From(window);
   nsPIDOMWindowInner* innerWindow = piWindow->GetCurrentInnerWindow();
   MOZ_ASSERT(innerWindow);
 
   nsCOMPtr<nsIDOMCSSPrimitiveValue> primitiveValue;
   nsAutoString bgStringValue;
 
   nsCOMPtr<nsIDocument> doc(do_QueryInterface(document));
-  NS_ENSURE_TRUE(doc, NS_ERROR_FAILURE);
-  nsCOMPtr<nsIPrincipal> principal = doc->NodePrincipal();
+  nsCOMPtr<nsIPrincipal> principal = doc ? doc->NodePrincipal() : nullptr;
 
   while (true) {
     nsCOMPtr<Element> domElement(do_QueryInterface(domNode));
     // bail for the parent node of the root element or null argument
     if (!domElement) {
       break;
     }
 
--- a/gfx/config/gfxFeature.h
+++ b/gfx/config/gfxFeature.h
@@ -17,16 +17,17 @@ namespace gfx {
 
 #define GFX_FEATURE_MAP(_)                                                        \
   /* Name,                        Type,         Description */                    \
   _(HW_COMPOSITING,               Feature,      "Compositing")                    \
   _(D3D11_COMPOSITING,            Feature,      "Direct3D11 Compositing")         \
   _(D3D9_COMPOSITING,             Feature,      "Direct3D9 Compositing")          \
   _(DIRECT2D,                     Feature,      "Direct2D")                       \
   _(D3D11_HW_ANGLE,               Feature,      "Direct3D11 hardware ANGLE")               \
+  _(GPU_PROCESS,                  Feature,      "GPU Process")                    \
   /* Add new entries above this comment */
 
 enum class Feature : uint32_t {
 #define MAKE_ENUM(name, type, desc) name,
   GFX_FEATURE_MAP(MAKE_ENUM)
 #undef MAKE_ENUM
   NumValues
 };
new file mode 100644
--- /dev/null
+++ b/gfx/ipc/GPUChild.cpp
@@ -0,0 +1,52 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=99: */
+/* 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 "GPUChild.h"
+#include "GPUProcessHost.h"
+
+namespace mozilla {
+namespace gfx {
+
+GPUChild::GPUChild(GPUProcessHost* aHost)
+ : mHost(aHost)
+{
+  MOZ_COUNT_CTOR(GPUChild);
+}
+
+GPUChild::~GPUChild()
+{
+  MOZ_COUNT_DTOR(GPUChild);
+}
+
+void
+GPUChild::ActorDestroy(ActorDestroyReason aWhy)
+{
+  mHost->OnChannelClosed();
+}
+
+class DeferredDeleteGPUChild : public Runnable
+{
+public:
+  explicit DeferredDeleteGPUChild(UniquePtr<GPUChild>&& aChild)
+    : mChild(Move(aChild))
+  {
+  }
+
+  NS_IMETHODIMP Run() override {
+    return NS_OK;
+  }
+
+private:
+  UniquePtr<GPUChild> mChild;
+};
+
+/* static */ void
+GPUChild::Destroy(UniquePtr<GPUChild>&& aChild)
+{
+  NS_DispatchToMainThread(new DeferredDeleteGPUChild(Move(aChild)));
+}
+
+} // namespace gfx
+} // namespace mozilla
new file mode 100644
--- /dev/null
+++ b/gfx/ipc/GPUChild.h
@@ -0,0 +1,34 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=99: */
+/* 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 _include_mozilla_gfx_ipc_GPUChild_h_
+#define _include_mozilla_gfx_ipc_GPUChild_h_
+
+#include "mozilla/gfx/PGPUChild.h"
+#include "mozilla/UniquePtr.h"
+
+namespace mozilla {
+namespace gfx {
+
+class GPUProcessHost;
+
+class GPUChild final : public PGPUChild
+{
+public:
+  explicit GPUChild(GPUProcessHost* aHost);
+  ~GPUChild();
+
+  static void Destroy(UniquePtr<GPUChild>&& aChild);
+
+  void ActorDestroy(ActorDestroyReason aWhy) override;
+
+private:
+  GPUProcessHost* mHost;
+};
+
+} // namespace gfx
+} // namespace mozilla
+
+#endif // _include_mozilla_gfx_ipc_GPUChild_h_
new file mode 100644
--- /dev/null
+++ b/gfx/ipc/GPUParent.cpp
@@ -0,0 +1,62 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=99: */
+/* 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 "GPUParent.h"
+#include "gfxConfig.h"
+#include "GPUProcessHost.h"
+#include "mozilla/Assertions.h"
+#include "mozilla/ipc/ProcessChild.h"
+
+namespace mozilla {
+namespace gfx {
+
+using namespace ipc;
+
+GPUParent::GPUParent()
+{
+}
+
+GPUParent::~GPUParent()
+{
+}
+
+bool
+GPUParent::Init(base::ProcessId aParentPid,
+                MessageLoop* aIOLoop,
+                IPC::Channel* aChannel)
+{
+  if (NS_WARN_IF(!Open(aChannel, aParentPid, aIOLoop))) {
+    return false;
+  }
+
+  return true;
+}
+
+bool
+GPUParent::RecvNothing()
+{
+  return true;
+}
+
+void
+GPUParent::ActorDestroy(ActorDestroyReason aWhy)
+{
+  if (AbnormalShutdown == aWhy) {
+    NS_WARNING("Shutting down GPU process early due to a crash!");
+    ProcessChild::QuickExit();
+  }
+
+#ifndef NS_FREE_PERMANENT_DATA
+  // No point in going through XPCOM shutdown because we don't keep persistent
+  // state. Currently we quick-exit in RecvBeginShutdown so this should be
+  // unreachable.
+  ProcessChild::QuickExit();
+#else
+  XRE_ShutdownChildProcess();
+#endif
+}
+
+} // namespace gfx
+} // namespace mozilla
new file mode 100644
--- /dev/null
+++ b/gfx/ipc/GPUParent.h
@@ -0,0 +1,32 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=99: */
+/* 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 _include_gfx_ipc_GPUParent_h__
+#define _include_gfx_ipc_GPUParent_h__
+
+#include "mozilla/gfx/PGPUParent.h"
+
+namespace mozilla {
+namespace gfx {
+
+class GPUParent final : public PGPUParent
+{
+public:
+  GPUParent();
+  ~GPUParent();
+
+  bool Init(base::ProcessId aParentPid,
+            MessageLoop* aIOLoop,
+            IPC::Channel* aChannel);
+
+  bool RecvNothing() override;
+
+  void ActorDestroy(ActorDestroyReason aWhy) override;
+};
+
+} // namespace gfx
+} // namespace mozilla
+
+#endif // _include_gfx_ipc_GPUParent_h__
new file mode 100644
--- /dev/null
+++ b/gfx/ipc/GPUProcessHost.cpp
@@ -0,0 +1,209 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ * vim: sts=8 sw=2 ts=2 tw=99 et :
+ * 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 "GPUProcessHost.h"
+#include "chrome/common/process_watcher.h"
+#include "gfxPrefs.h"
+#include "mozilla/gfx/Logging.h"
+#include "nsITimer.h"
+
+namespace mozilla {
+namespace gfx {
+
+GPUProcessHost::GPUProcessHost(Listener* aListener)
+ : GeckoChildProcessHost(GeckoProcessType_GPU),
+   mListener(aListener),
+   mTaskFactory(this),
+   mLaunchPhase(LaunchPhase::Unlaunched),
+   mShutdownRequested(false)
+{
+  MOZ_COUNT_CTOR(GPUProcessHost);
+}
+
+GPUProcessHost::~GPUProcessHost()
+{
+  MOZ_COUNT_DTOR(GPUProcessHost);
+}
+
+bool
+GPUProcessHost::Launch()
+{
+  MOZ_ASSERT(mLaunchPhase == LaunchPhase::Unlaunched);
+  MOZ_ASSERT(!mGPUChild);
+
+  mLaunchPhase = LaunchPhase::Waiting;
+  if (!GeckoChildProcessHost::AsyncLaunch()) {
+    mLaunchPhase = LaunchPhase::Complete;
+    return false;
+  }
+  return true;
+}
+
+bool
+GPUProcessHost::WaitForLaunch()
+{
+  if (mLaunchPhase == LaunchPhase::Complete) {
+    return !!mGPUChild;
+  }
+
+  int32_t timeoutMs = gfxPrefs::GPUProcessDevTimeoutMs();
+
+  // Our caller expects the connection to be finished after we return, so we
+  // immediately set up the IPDL actor and fire callbacks. The IO thread will
+  // still dispatch a notification to the main thread - we'll just ignore it.
+  bool result = GeckoChildProcessHost::WaitUntilConnected(timeoutMs);
+  InitAfterConnect(result);
+  return result;
+}
+
+void
+GPUProcessHost::OnChannelConnected(int32_t peer_pid)
+{
+  MOZ_ASSERT(!NS_IsMainThread());
+
+  GeckoChildProcessHost::OnChannelConnected(peer_pid);
+
+  // Post a task to the main thread. Take the lock because mTaskFactory is not
+  // thread-safe.
+  RefPtr<Runnable> runnable;
+  {
+    MonitorAutoLock lock(mMonitor);
+    runnable = mTaskFactory.NewRunnableMethod(&GPUProcessHost::OnChannelConnectedTask);
+  }
+  NS_DispatchToMainThread(runnable);
+}
+
+void
+GPUProcessHost::OnChannelError()
+{
+  MOZ_ASSERT(!NS_IsMainThread());
+
+  GeckoChildProcessHost::OnChannelError();
+
+  // Post a task to the main thread. Take the lock because mTaskFactory is not
+  // thread-safe.
+  RefPtr<Runnable> runnable;
+  {
+    MonitorAutoLock lock(mMonitor);
+    runnable = mTaskFactory.NewRunnableMethod(&GPUProcessHost::OnChannelErrorTask);
+  }
+  NS_DispatchToMainThread(runnable);
+}
+
+void
+GPUProcessHost::OnChannelConnectedTask()
+{
+  if (mLaunchPhase == LaunchPhase::Waiting) {
+    InitAfterConnect(true);
+  }
+}
+
+void
+GPUProcessHost::OnChannelErrorTask()
+{
+  if (mLaunchPhase == LaunchPhase::Waiting) {
+    InitAfterConnect(false);
+  }
+}
+
+void
+GPUProcessHost::InitAfterConnect(bool aSucceeded)
+{
+  MOZ_ASSERT(mLaunchPhase == LaunchPhase::Waiting);
+  MOZ_ASSERT(!mGPUChild);
+
+  mLaunchPhase = LaunchPhase::Complete;
+
+  if (aSucceeded) {
+    mGPUChild = MakeUnique<GPUChild>(this);
+    DebugOnly<bool> rv =
+      mGPUChild->Open(GetChannel(), base::GetProcId(GetChildProcessHandle()));
+    MOZ_ASSERT(rv);
+  }
+
+  if (mListener) {
+    mListener->OnProcessLaunchComplete(this);
+  }
+}
+
+void
+GPUProcessHost::Shutdown()
+{
+  MOZ_ASSERT(!mShutdownRequested);
+
+  mListener = nullptr;
+
+  if (mGPUChild) {
+    // OnChannelClosed uses this to check if the shutdown was expected or
+    // unexpected.
+    mShutdownRequested = true;
+
+#ifdef NS_FREE_PERMANENT_DATA
+    mGPUChild->Close();
+#else
+    // No need to communicate shutdown, the GPU process doesn't need to
+    // communicate anything back.
+    KillHard("NormalShutdown");
+#endif
+
+    // Wait for ActorDestroy.
+    return;
+  }
+
+  DestroyProcess();
+}
+
+void
+GPUProcessHost::OnChannelClosed()
+{
+  if (!mShutdownRequested) {
+    // This is an unclean shutdown. Notify our listener that we're going away.
+    if (mListener) {
+      mListener->OnProcessUnexpectedShutdown(this);
+    }
+  }
+
+  // Release the actor.
+  GPUChild::Destroy(Move(mGPUChild));
+  MOZ_ASSERT(!mGPUChild);
+
+  // If the owner of GPUProcessHost already requested shutdown, we can now
+  // schedule destruction. Otherwise we must wait for someone to call
+  // Shutdown. Note that GPUProcessManager calls Shutdown within
+  // OnProcessUnexpectedShutdown.
+  if (mShutdownRequested) {
+    DestroyProcess();
+  }
+}
+
+void
+GPUProcessHost::KillHard(const char* aReason)
+{
+  ProcessHandle handle = GetChildProcessHandle();
+  if (!base::KillProcess(handle, base::PROCESS_END_KILLED_BY_USER, false)) {
+    NS_WARNING("failed to kill subprocess!");
+  }
+
+  SetAlreadyDead();
+  XRE_GetIOMessageLoop()->PostTask(
+    NewRunnableFunction(&ProcessWatcher::EnsureProcessTerminated, handle, /*force=*/true));
+}
+
+void
+GPUProcessHost::DestroyProcess()
+{
+  // Cancel all tasks. We don't want anything triggering after our caller
+  // expects this to go away.
+  {
+    MonitorAutoLock lock(mMonitor);
+    mTaskFactory.RevokeAll();
+  }
+
+  DissociateActor();
+}
+
+} // namespace gfx
+} // namespace mozilla
new file mode 100644
--- /dev/null
+++ b/gfx/ipc/GPUProcessHost.h
@@ -0,0 +1,125 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ * vim: sts=8 sw=2 ts=2 tw=99 et :
+ * 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 _include_mozilla_gfx_ipc_GPUProcessHost_h_
+#define _include_mozilla_gfx_ipc_GPUProcessHost_h_
+
+#include "mozilla/Function.h"
+#include "mozilla/Maybe.h"
+#include "mozilla/UniquePtr.h"
+#include "mozilla/ipc/GeckoChildProcessHost.h"
+#include "mozilla/ipc/ProtocolUtils.h"
+#include "mozilla/ipc/TaskFactory.h"
+
+class nsITimer;
+
+namespace mozilla {
+namespace gfx {
+
+class GPUChild;
+
+// GPUProcessHost is the "parent process" container for a subprocess handle and
+// IPC connection. It owns the parent process IPDL actor, which in this case,
+// is a GPUChild.
+//
+// GPUProcessHosts are allocated and managed by GPUProcessManager. For all
+// intents and purposes it is a singleton, though more than one may be allocated
+// at a time due to its shutdown being asynchronous.
+class GPUProcessHost final : public ipc::GeckoChildProcessHost
+{
+  friend class GPUChild;
+
+public:
+  class Listener {
+  public:
+    virtual void OnProcessLaunchComplete(GPUProcessHost* aHost)
+    {}
+
+    // The GPUProcessHost has unexpectedly shutdown or had its connection
+    // severed. This is not called if an error occurs after calling
+    // Shutdown().
+    virtual void OnProcessUnexpectedShutdown(GPUProcessHost* aHost)
+    {}
+  };
+
+public:
+  explicit GPUProcessHost(Listener* listener);
+  ~GPUProcessHost();
+
+  // Launch the subprocess asynchronously. On failure, false is returned.
+  // Otherwise, true is returned, and the OnLaunchComplete listener callback
+  // will be invoked either when a connection has been established, or if a
+  // connection could not be established due to an asynchronous error.
+  bool Launch();
+
+  // If the process is being launched, block until it has launched and
+  // connected. If a launch task is pending, it will fire immediately.
+  //
+  // Returns true if the process is successfully connected; false otherwise.
+  bool WaitForLaunch();
+
+  // Inform the process that it should clean up its resources and shut down.
+  // This initiates an asynchronous shutdown sequence. After this method returns,
+  // it is safe for the caller to forget its pointer to the GPUProcessHost.
+  //
+  // After this returns, the attached Listener is no longer used.
+  void Shutdown();
+
+  // Return the actor for the top-level actor of the process. If the process
+  // has not connected yet, this returns null.
+  GPUChild* GetActor() const {
+    return mGPUChild.get();
+  }
+
+  bool IsConnected() const {
+    return !!mGPUChild;
+  }
+
+  // Called on the IO thread.
+  void OnChannelConnected(int32_t peer_pid) override;
+  void OnChannelError() override;
+
+  void SetListener(Listener* aListener);
+
+private:
+  // Called on the main thread.
+  void OnChannelConnectedTask();
+  void OnChannelErrorTask();
+
+  // Called on the main thread after a connection has been established.
+  void InitAfterConnect(bool aSucceeded);
+
+  // Called on the main thread when the mGPUChild actor is shutting down.
+  void OnChannelClosed();
+
+  // Kill the remote process, triggering IPC shutdown.
+  void KillHard(const char* aReason);
+
+  void DestroyProcess();
+
+private:
+  DISALLOW_COPY_AND_ASSIGN(GPUProcessHost);
+
+  Listener* mListener;
+  ipc::TaskFactory<GPUProcessHost> mTaskFactory;
+
+  enum class LaunchPhase {
+    Unlaunched,
+    Waiting,
+    Complete
+  };
+  LaunchPhase mLaunchPhase;
+
+  UniquePtr<GPUChild> mGPUChild;
+  Listener* listener_;
+
+  bool mShutdownRequested;
+};
+
+} // namespace gfx
+} // namespace mozilla
+
+#endif // _include_mozilla_gfx_ipc_GPUProcessHost_h_
new file mode 100644
--- /dev/null
+++ b/gfx/ipc/GPUProcessImpl.cpp
@@ -0,0 +1,37 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=99: */
+/* 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 "GPUProcessImpl.h"
+#include "mozilla/ipc/IOThreadChild.h"
+
+namespace mozilla {
+namespace gfx {
+
+using namespace ipc;
+
+GPUProcessImpl::GPUProcessImpl(ProcessId aParentPid)
+ : ProcessChild(aParentPid)
+{
+}
+
+GPUProcessImpl::~GPUProcessImpl()
+{
+}
+
+bool
+GPUProcessImpl::Init()
+{
+  return mGPU.Init(ParentPid(),
+                   IOThreadChild::message_loop(),
+                   IOThreadChild::channel());
+}
+
+void
+GPUProcessImpl::CleanUp()
+{
+}
+
+} // namespace gfx
+} // namespace mozilla
new file mode 100644
--- /dev/null
+++ b/gfx/ipc/GPUProcessImpl.h
@@ -0,0 +1,34 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=99: */
+/* 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 _include_gfx_ipc_GPUProcessImpl_h__
+#define _include_gfx_ipc_GPUProcessImpl_h__
+
+#include "mozilla/ipc/ProcessChild.h"
+#include "GPUParent.h"
+
+namespace mozilla {
+namespace gfx {
+
+// This class owns the subprocess instance of a PGPU - which in this case,
+// is a GPUParent. It is instantiated as a singleton in XRE_InitChildProcess.
+class GPUProcessImpl final : public ipc::ProcessChild
+{
+public:
+  explicit GPUProcessImpl(ProcessId aParentPid);
+  ~GPUProcessImpl();
+
+  bool Init() override;
+  void CleanUp() override;
+
+private:
+  DISALLOW_COPY_AND_ASSIGN(GPUProcessImpl);
+  GPUParent mGPU;
+};
+
+} // namespace gfx
+} // namespace mozilla
+
+#endif // _include_gfx_ipc_GPUProcessImpl_h__
--- a/gfx/ipc/GPUProcessManager.cpp
+++ b/gfx/ipc/GPUProcessManager.cpp
@@ -1,16 +1,18 @@
 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
 /* vim: set ts=8 sts=2 et sw=2 tw=99: */
 /* 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 "GPUProcessManager.h"
+#include "GPUProcessHost.h"
 #include "mozilla/layers/CompositorSession.h"
 #include "mozilla/StaticPtr.h"
+#include "nsContentUtils.h"
 
 namespace mozilla {
 namespace gfx {
 
 using namespace mozilla::layers;
 
 static StaticAutoPtr<GPUProcessManager> sSingleton;
 
@@ -29,21 +31,127 @@ GPUProcessManager::Initialize()
 
 void
 GPUProcessManager::Shutdown()
 {
   sSingleton = nullptr;
 }
 
 GPUProcessManager::GPUProcessManager()
+ : mProcess(nullptr),
+   mGPUChild(nullptr)
 {
+  mObserver = new Observer(this);
+  nsContentUtils::RegisterShutdownObserver(mObserver);
 }
 
 GPUProcessManager::~GPUProcessManager()
 {
+  // The GPU process should have already been shut down.
+  MOZ_ASSERT(!mProcess && !mGPUChild);
+
+  // We should have already removed observers.
+  MOZ_ASSERT(!mObserver);
+}
+
+NS_IMPL_ISUPPORTS(GPUProcessManager::Observer, nsIObserver);
+
+GPUProcessManager::Observer::Observer(GPUProcessManager* aManager)
+ : mManager(aManager)
+{
+}
+
+NS_IMETHODIMP
+GPUProcessManager::Observer::Observe(nsISupports* aSubject, const char* aTopic, const char16_t* aData)
+{
+  if (!strcmp(aTopic, NS_XPCOM_SHUTDOWN_OBSERVER_ID)) {
+    mManager->OnXPCOMShutdown();
+  }
+  return NS_OK;
+}
+
+void
+GPUProcessManager::OnXPCOMShutdown()
+{
+  if (mObserver) {
+    nsContentUtils::UnregisterShutdownObserver(mObserver);
+    mObserver = nullptr;
+  }
+
+  DestroyProcess();
+}
+
+void
+GPUProcessManager::EnableGPUProcess()
+{
+  if (mProcess) {
+    return;
+  }
+
+  // The subprocess is launched asynchronously, so we wait for a callback to
+  // acquire the IPDL actor.
+  mProcess = new GPUProcessHost(this);
+  if (!mProcess->Launch()) {
+    DisableGPUProcess("Failed to launch GPU process");
+  }
+}
+
+void
+GPUProcessManager::DisableGPUProcess(const char* aMessage)
+{
+  gfxConfig::SetFailed(Feature::GPU_PROCESS, FeatureStatus::Failed, aMessage);
+  gfxCriticalNote << aMessage;
+
+  DestroyProcess();
+}
+
+void
+GPUProcessManager::EnsureGPUReady()
+{
+  if (mProcess && mProcess->IsConnected()) {
+    if (!mProcess->WaitForLaunch()) {
+      // If this fails, we should have fired OnProcessLaunchComplete and
+      // removed the process.
+      MOZ_ASSERT(!mProcess && !mGPUChild);
+      return;
+    }
+  }
+}
+
+void
+GPUProcessManager::OnProcessLaunchComplete(GPUProcessHost* aHost)
+{
+  MOZ_ASSERT(mProcess && mProcess == aHost);
+
+  if (!mProcess->IsConnected()) {
+    DisableGPUProcess("Failed to launch GPU process");
+    return;
+  }
+
+  mGPUChild = mProcess->GetActor();
+}
+
+void
+GPUProcessManager::OnProcessUnexpectedShutdown(GPUProcessHost* aHost)
+{
+  MOZ_ASSERT(mProcess && mProcess == aHost);
+
+  DestroyProcess();
+}
+
+void
+GPUProcessManager::DestroyProcess()
+{
+  if (!mProcess) {
+    return;
+  }
+
+  mProcess->Shutdown();
+  mProcess = nullptr;
+  mGPUChild = nullptr;
 }
 
 already_AddRefed<CompositorSession>
 GPUProcessManager::CreateTopLevelCompositor(widget::CompositorWidgetProxy* aProxy,
                                             ClientLayerManager* aLayerManager,
                                             CSSToLayoutDeviceScale aScale,
                                             bool aUseAPZ,
                                             bool aUseExternalSurfaceSize,
--- a/gfx/ipc/GPUProcessManager.h
+++ b/gfx/ipc/GPUProcessManager.h
@@ -5,17 +5,19 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 #ifndef _include_mozilla_gfx_ipc_GPUProcessManager_h_
 #define _include_mozilla_gfx_ipc_GPUProcessManager_h_
 
 #include "base/basictypes.h"
 #include "base/process.h"
 #include "Units.h"
 #include "mozilla/dom/ipc/IdType.h"
+#include "mozilla/gfx/GPUProcessHost.h"
 #include "mozilla/ipc/Transport.h"
+#include "nsIObserverService.h"
 
 namespace mozilla {
 namespace layers {
 class APZCTreeManager;
 class CompositorSession;
 class ClientLayerManager;
 class CompositorUpdateObserver;
 class PCompositorBridgeParent;
@@ -27,31 +29,41 @@ namespace dom {
 class ContentParent;
 class TabParent;
 } // namespace dom
 namespace ipc {
 class GeckoChildProcessHost;
 } // namespace ipc
 namespace gfx {
 
+class GPUChild;
+
 // The GPUProcessManager is a singleton responsible for creating GPU-bound
 // objects that may live in another process. Currently, it provides access
 // to the compositor via CompositorBridgeParent.
-class GPUProcessManager final
+class GPUProcessManager final : public GPUProcessHost::Listener
 {
   typedef layers::APZCTreeManager APZCTreeManager;
   typedef layers::CompositorUpdateObserver CompositorUpdateObserver;
 
 public:
   static void Initialize();
   static void Shutdown();
   static GPUProcessManager* Get();
 
   ~GPUProcessManager();
 
+  // If not using a GPU process, launch a new GPU process asynchronously.
+  void EnableGPUProcess();
+
+  // Ensure that GPU-bound methods can be used. If no GPU process is being
+  // used, or one is launched and ready, this function returns immediately.
+  // Otherwise it blocks until the GPU process has finished launching.
+  void EnsureGPUReady();
+
   already_AddRefed<layers::CompositorSession> CreateTopLevelCompositor(
     widget::CompositorWidgetProxy* aProxy,
     layers::ClientLayerManager* aLayerManager,
     CSSToLayoutDeviceScale aScale,
     bool aUseAPZ,
     bool aUseExternalSurfaceSize,
     int aSurfaceWidth,
     int aSurfaceHeight);
@@ -87,18 +99,49 @@ public:
   // aContentParent The ContentParent for the process that the TabChild for
   //                aTabId lives in.
   // aBrowserParent The toplevel TabParent for aTabId.
   bool UpdateRemoteContentController(uint64_t aLayersId,
                                      dom::ContentParent* aContentParent,
                                      const dom::TabId& aTabId,
                                      dom::TabParent* aBrowserParent);
 
+  void OnProcessLaunchComplete(GPUProcessHost* aHost) override;
+  void OnProcessUnexpectedShutdown(GPUProcessHost* aHost) override;
+
+private:
+  // Called from our xpcom-shutdown observer.
+  void OnXPCOMShutdown();
+
 private:
   GPUProcessManager();
 
+  // Permanently disable the GPU process and record a message why.
+  void DisableGPUProcess(const char* aMessage);
+
+  // Shutdown the GPU process.
+  void DestroyProcess();
+
   DISALLOW_COPY_AND_ASSIGN(GPUProcessManager);
+
+  class Observer final : public nsIObserver {
+  public:
+    NS_DECL_ISUPPORTS
+    NS_DECL_NSIOBSERVER
+    explicit Observer(GPUProcessManager* aManager);
+
+  protected:
+    ~Observer() {}
+
+    GPUProcessManager* mManager;
+  };
+  friend class Observer;
+
+private:
+  RefPtr<Observer> mObserver;
+  GPUProcessHost* mProcess;
+  GPUChild* mGPUChild;
 };
 
 } // namespace gfx
 } // namespace mozilla
 
 #endif // _include_mozilla_gfx_ipc_GPUProcessManager_h_
new file mode 100644
--- /dev/null
+++ b/gfx/ipc/PGPU.ipdl
@@ -0,0 +1,17 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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/. */
+
+namespace mozilla {
+namespace gfx {
+
+sync protocol PGPU
+{
+parent:
+  // Sent by the UI process to initiate shutdown.
+  async Nothing();
+};
+
+} // namespace gfx
+} // namespace mozilla
--- a/gfx/ipc/moz.build
+++ b/gfx/ipc/moz.build
@@ -5,16 +5,20 @@
 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
 
 EXPORTS.mozilla += [
     'D3DMessageUtils.h',
     'GfxMessageUtils.h'
 ]
 
 EXPORTS.mozilla.gfx += [
+    'GPUChild.h',
+    'GPUParent.h',
+    'GPUProcessHost.h',
+    'GPUProcessImpl.h',
     'GPUProcessManager.h',
     'SharedDIB.h',
 ]
 
 EXPORTS.mozilla.layers += [
     'CompositorSession.h',
 ]
 
@@ -26,22 +30,27 @@ if CONFIG['MOZ_WIDGET_TOOLKIT'] == 'wind
     UNIFIED_SOURCES += [
         'SharedDIBSurface.cpp',
         'SharedDIBWin.cpp',
     ]
 
 UNIFIED_SOURCES += [
     'CompositorSession.cpp',
     'D3DMessageUtils.cpp',
+    'GPUChild.cpp',
+    'GPUParent.cpp',
+    'GPUProcessHost.cpp',
+    'GPUProcessImpl.cpp',
     'GPUProcessManager.cpp',
     'SharedDIB.cpp',
 ]
 
 IPDL_SOURCES = [
     'GraphicsMessages.ipdlh',
+    'PGPU.ipdl',
 ]
 
 include('/ipc/chromium/chromium-config.mozbuild')
 
 FINAL_LIBRARY = 'xul'
 
 CXXFLAGS += CONFIG['MOZ_CAIRO_CFLAGS']
 CXXFLAGS += CONFIG['TK_CFLAGS']
deleted file mode 100644
--- a/gfx/thebes/PrintTargetThebes.cpp
+++ /dev/null
@@ -1,90 +0,0 @@
-/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 4 -*-
- * 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 "PrintTargetThebes.h"
-
-#include "gfxASurface.h"
-#include "gfxPlatform.h"
-#include "mozilla/gfx/Logging.h"
-
-namespace mozilla {
-namespace gfx {
-
-/* static */ already_AddRefed<PrintTargetThebes>
-PrintTargetThebes::CreateOrNull(gfxASurface* aSurface)
-{
-  MOZ_ASSERT(aSurface);
-
-  if (!aSurface || aSurface->CairoStatus()) {
-    return nullptr;
-  }
-
-  RefPtr<PrintTargetThebes> target = new PrintTargetThebes(aSurface);
-
-  return target.forget();
-}
-
-PrintTargetThebes::PrintTargetThebes(gfxASurface* aSurface)
-  : PrintTarget(nullptr, aSurface->GetSize())
-  , mGfxSurface(aSurface)
-{
-}
-
-already_AddRefed<DrawTarget>
-PrintTargetThebes::MakeDrawTarget(const IntSize& aSize,
-                                  DrawEventRecorder* aRecorder)
-{
-  MOZ_ASSERT(!aRecorder,
-             "aRecorder should only be passed to an instance of "
-             "PrintTargetRecording");
-
-  RefPtr<gfx::DrawTarget> dt =
-    gfxPlatform::GetPlatform()->CreateDrawTargetForSurface(mGfxSurface, aSize);
-  if (!dt || !dt->IsValid()) {
-    return nullptr;
-  }
-
-  return dt.forget();
-}
-
-nsresult
-PrintTargetThebes::BeginPrinting(const nsAString& aTitle,
-                                 const nsAString& aPrintToFileName)
-{
-  return mGfxSurface->BeginPrinting(aTitle, aPrintToFileName);
-}
-
-nsresult
-PrintTargetThebes::EndPrinting()
-{
-  return mGfxSurface->EndPrinting();
-}
-
-nsresult
-PrintTargetThebes::AbortPrinting()
-{
-  return mGfxSurface->AbortPrinting();
-}
-
-nsresult
-PrintTargetThebes::BeginPage()
-{
-  return mGfxSurface->BeginPage();
-}
-
-nsresult
-PrintTargetThebes::EndPage()
-{
-  return mGfxSurface->EndPage();
-}
-
-void
-PrintTargetThebes::Finish()
-{
-  return mGfxSurface->Finish();
-}
-
-} // namespace gfx
-} // namespace mozilla
deleted file mode 100644
--- a/gfx/thebes/PrintTargetThebes.h
+++ /dev/null
@@ -1,52 +0,0 @@
-/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 4 -*-
- * 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_GFX_PRINTTARGETTHEBES_H
-#define MOZILLA_GFX_PRINTTARGETTHEBES_H
-
-#include "mozilla/gfx/PrintTarget.h"
-
-class gfxASurface;
-
-namespace mozilla {
-namespace gfx {
-
-/**
- * XXX Remove this class.
- *
- * This class should go away once all the logic from the gfxASurface subclasses
- * has been moved to new PrintTarget subclasses and we no longer need to
- * wrap a gfxASurface.
- */
-class PrintTargetThebes final : public PrintTarget {
-public:
-
-  static already_AddRefed<PrintTargetThebes>
-  CreateOrNull(gfxASurface* aSurface);
-
-  virtual nsresult BeginPrinting(const nsAString& aTitle,
-                                 const nsAString& aPrintToFileName) override;
-  virtual nsresult EndPrinting() override;
-  virtual nsresult AbortPrinting() override;
-  virtual nsresult BeginPage() override;
-  virtual nsresult EndPage() override;
-  virtual void Finish() override;
-
-  virtual already_AddRefed<DrawTarget>
-  MakeDrawTarget(const IntSize& aSize,
-                 DrawEventRecorder* aRecorder = nullptr) override;
-
-private:
-
-  // Only created via CreateOrNull
-  explicit PrintTargetThebes(gfxASurface* aSurface);
-
-  RefPtr<gfxASurface> mGfxSurface;
-};
-
-} // namespace gfx
-} // namespace mozilla
-
-#endif /* MOZILLA_GFX_PRINTTARGETTHEBES_H */
new file mode 100644
--- /dev/null
+++ b/gfx/thebes/PrintTargetWindows.cpp
@@ -0,0 +1,112 @@
+/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ * 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 "PrintTargetWindows.h"
+
+#include "cairo-win32.h"
+#include "mozilla/gfx/HelpersCairo.h"
+#include "nsCoord.h"
+#include "nsString.h"
+
+namespace mozilla {
+namespace gfx {
+
+PrintTargetWindows::PrintTargetWindows(cairo_surface_t* aCairoSurface,
+                                       const IntSize& aSize,
+                                       HDC aDC)
+  : PrintTarget(aCairoSurface, aSize)
+  , mDC(aDC)
+{
+  // TODO: At least add basic memory reporting.
+  // 4 * mSize.width * mSize.height + sizeof(PrintTargetWindows) ?
+}
+
+/* static */ already_AddRefed<PrintTargetWindows>
+PrintTargetWindows::CreateOrNull(HDC aDC)
+{
+  // Figure out the cairo surface size - Windows we need to use the printable
+  // area of the page.  Note: we only scale the printing using the LOGPIXELSY,
+  // so we use that when calculating the surface width as well as the height.
+  int32_t heightDPI = ::GetDeviceCaps(aDC, LOGPIXELSY);
+  float width =
+    (::GetDeviceCaps(aDC, HORZRES) * POINTS_PER_INCH_FLOAT) / heightDPI;
+  float height =
+    (::GetDeviceCaps(aDC, VERTRES) * POINTS_PER_INCH_FLOAT) / heightDPI;
+  IntSize size(width, height);
+
+  if (!Factory::CheckSurfaceSize(size)) {
+    return nullptr;
+  }
+
+  cairo_surface_t* surface = cairo_win32_printing_surface_create(aDC);
+
+  if (cairo_surface_status(surface)) {
+    return nullptr;
+  }
+
+  // The new object takes ownership of our surface reference.
+  RefPtr<PrintTargetWindows> target =
+    new PrintTargetWindows(surface, size, aDC);
+
+  return target.forget();
+}
+
+nsresult
+PrintTargetWindows::BeginPrinting(const nsAString& aTitle,
+                                  const nsAString& aPrintToFileName)
+{
+  const uint32_t DOC_TITLE_LENGTH = MAX_PATH - 1;
+
+  DOCINFOW docinfo;
+
+  nsString titleStr(aTitle);
+  if (titleStr.Length() > DOC_TITLE_LENGTH) {
+    titleStr.SetLength(DOC_TITLE_LENGTH - 3);
+    titleStr.AppendLiteral("...");
+  }
+
+  nsString docName(aPrintToFileName);
+  docinfo.cbSize = sizeof(docinfo);
+  docinfo.lpszDocName = titleStr.Length() > 0 ? titleStr.get() : L"Mozilla Document";
+  docinfo.lpszOutput = docName.Length() > 0 ? docName.get() : nullptr;
+  docinfo.lpszDatatype = nullptr;
+  docinfo.fwType = 0;
+
+  ::StartDocW(mDC, &docinfo);
+
+  return NS_OK;
+}
+
+nsresult
+PrintTargetWindows::EndPrinting()
+{
+  int result = ::EndDoc(mDC);
+  return (result <= 0) ? NS_ERROR_FAILURE : NS_OK;
+}
+
+nsresult
+PrintTargetWindows::AbortPrinting()
+{
+  int result = ::AbortDoc(mDC);
+  return (result <= 0) ? NS_ERROR_FAILURE : NS_OK;
+}
+
+nsresult
+PrintTargetWindows::BeginPage()
+{
+  int result = ::StartPage(mDC);
+  return (result <= 0) ? NS_ERROR_FAILURE : NS_OK;
+}
+
+nsresult
+PrintTargetWindows::EndPage()
+{
+  cairo_surface_show_page(mCairoSurface);
+  int result = ::EndPage(mDC);
+  return (result <= 0) ? NS_ERROR_FAILURE : NS_OK;
+}
+
+} // namespace gfx
+} // namespace mozilla
new file mode 100644
--- /dev/null
+++ b/gfx/thebes/PrintTargetWindows.h
@@ -0,0 +1,43 @@
+/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ * 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_GFX_PRINTTARGETWINDOWS_H
+#define MOZILLA_GFX_PRINTTARGETWINDOWS_H
+
+#include "PrintTarget.h"
+
+/* include windows.h for the HDC definitions that we need. */
+#include <windows.h>
+
+namespace mozilla {
+namespace gfx {
+
+/**
+ * Windows printing target.
+ */
+class PrintTargetWindows final : public PrintTarget
+{
+public:
+  static already_AddRefed<PrintTargetWindows>
+  CreateOrNull(HDC aDC);
+
+  virtual nsresult BeginPrinting(const nsAString& aTitle,
+                                 const nsAString& aPrintToFileName) override;
+  virtual nsresult EndPrinting() override;
+  virtual nsresult AbortPrinting() override;
+  virtual nsresult BeginPage() override;
+  virtual nsresult EndPage() override;
+
+private:
+  PrintTargetWindows(cairo_surface_t* aCairoSurface,
+                     const IntSize& aSize,
+                     HDC aDC);
+  HDC mDC;
+};
+
+} // namespace gfx
+} // namespace mozilla
+
+#endif /* MOZILLA_GFX_PRINTTARGETWINDOWS_H */
--- a/gfx/thebes/gfxASurface.cpp
+++ b/gfx/thebes/gfxASurface.cpp
@@ -348,46 +348,16 @@ gfxASurface::CairoStatus()
 /* static */
 int32_t
 gfxASurface::FormatStrideForWidth(gfxImageFormat format, int32_t width)
 {
     cairo_format_t cformat = GfxFormatToCairoFormat(format);
     return cairo_format_stride_for_width(cformat, (int)width);
 }
 
-nsresult
-gfxASurface::BeginPrinting(const nsAString& aTitle, const nsAString& aPrintToFileName)
-{
-    return NS_OK;
-}
-
-nsresult
-gfxASurface::EndPrinting()
-{
-    return NS_OK;
-}
-
-nsresult
-gfxASurface::AbortPrinting()
-{
-    return NS_OK;
-}
-
-nsresult
-gfxASurface::BeginPage()
-{
-    return NS_OK;
-}
-
-nsresult
-gfxASurface::EndPage()
-{
-    return NS_OK;
-}
-
 gfxContentType
 gfxASurface::ContentFromFormat(gfxImageFormat format)
 {
     switch (format) {
         case SurfaceFormat::A8R8G8B8_UINT32:
             return gfxContentType::COLOR_ALPHA;
         case SurfaceFormat::X8R8G8B8_UINT32:
         case SurfaceFormat::R5G6B5_UINT16:
--- a/gfx/thebes/gfxASurface.h
+++ b/gfx/thebes/gfxASurface.h
@@ -59,23 +59,16 @@ public:
 
     void SetDeviceOffset(const gfxPoint& offset);
     gfxPoint GetDeviceOffset() const;
 
     void Flush() const;
     void MarkDirty();
     void MarkDirty(const gfxRect& r);
 
-    /* Printing backend functions */
-    virtual nsresult BeginPrinting(const nsAString& aTitle, const nsAString& aPrintToFileName);
-    virtual nsresult EndPrinting();
-    virtual nsresult AbortPrinting();
-    virtual nsresult BeginPage();
-    virtual nsresult EndPage();
-
     void SetData(const cairo_user_data_key_t *key,
                  void *user_data,
                  thebes_destroy_func_t destroy);
     void *GetData(const cairo_user_data_key_t *key);
 
     virtual void Finish();
 
     /**
--- a/gfx/thebes/gfxPlatform.cpp
+++ b/gfx/thebes/gfxPlatform.cpp
@@ -137,16 +137,17 @@ class mozilla::gl::SkiaGLGlue : public G
 #include "nsIGfxInfo.h"
 #include "nsIXULRuntime.h"
 #include "VsyncSource.h"
 #include "SoftwareVsyncSource.h"
 #include "nscore.h" // for NS_FREE_PERMANENT_DATA
 #include "mozilla/dom/ContentChild.h"
 #include "gfxVR.h"
 #include "VRManagerChild.h"
+#include "mozilla/gfx/GPUParent.h"
 
 namespace mozilla {
 namespace layers {
 #ifdef MOZ_WIDGET_GONK
 void InitGralloc();
 #endif
 void ShutdownTileCache();
 } // namespace layers
@@ -589,17 +590,19 @@ gfxPlatform::Init()
     gEverInitialized = true;
 
     // Initialize the preferences by creating the singleton.
     gfxPrefs::GetSingleton();
     MediaPrefs::GetSingleton();
 
     gfxConfig::Init();
 
-    GPUProcessManager::Initialize();
+    if (XRE_IsParentProcess()) {
+      GPUProcessManager::Initialize();
+    }
 
     auto fwd = new CrashStatsLogForwarder("GraphicsCriticalError");
     fwd->SetCircularBufferSize(gfxPrefs::GfxLoggingCrashLength());
 
     // Drop a note in the crash report if we end up forcing an option that could
     // destabilize things.  New items should be appended at the end (of an existing
     // or in a new section), so that we don't have to know the version to interpret
     // these cryptic strings.
@@ -843,17 +846,19 @@ gfxPlatform::Shutdown()
     // most platforms.  Windows is a "special snowflake", though, and has three
     // context providers available, so we have to shut all of them down.
     // We should only support the default GL provider on Windows; then, this
     // could go away. Unfortunately, we currently support WGL (the default) for
     // WebGL on Optimus.
     GLContextProviderEGL::Shutdown();
 #endif
 
-    GPUProcessManager::Shutdown();
+    if (XRE_IsParentProcess()) {
+      GPUProcessManager::Shutdown();
+    }
 
     // This is a bit iffy - we're assuming that we were the ones that set the
     // log forwarder in the Factory, so that it's our responsibility to
     // delete it.
     delete mozilla::gfx::Factory::GetLogForwarder();
     mozilla::gfx::Factory::SetLogForwarder(nullptr);
 
     gfx::Factory::ShutDown();
@@ -2110,16 +2115,33 @@ gfxPlatform::InitAcceleration()
          sLayersSupportsHardwareVideoDecoding = true;
     }
   }
 
   Preferences::AddBoolVarCache(&sLayersHardwareVideoDecodingFailed,
                                "media.hardware-video-decoding.failed",
                                false);
 
+  if (XRE_IsParentProcess()) {
+    if (gfxPrefs::GPUProcessDevEnabled()) {
+      // We want to hide this from about:support, so only set a default if the
+      // pref is known to be true.
+      gfxConfig::SetDefaultFromPref(
+        Feature::GPU_PROCESS,
+        gfxPrefs::GetGPUProcessDevEnabledPrefName(),
+        true,
+        gfxPrefs::GetGPUProcessDevEnabledPrefDefault());
+    }
+
+    if (gfxConfig::IsEnabled(Feature::GPU_PROCESS)) {
+      GPUProcessManager* gpu = GPUProcessManager::Get();
+      gpu->EnableGPUProcess();
+    }
+  }
+
   sLayersAccelerationPrefsInitialized = true;
 }
 
 void
 gfxPlatform::InitCompositorAccelerationPrefs()
 {
   const char *acceleratedEnv = PR_GetEnv("MOZ_ACCELERATED");
 
--- a/gfx/thebes/gfxPrefs.h
+++ b/gfx/thebes/gfxPrefs.h
@@ -66,17 +66,17 @@ static const char* Get##Name##PrefName()
 static Type Get##Name##PrefDefault() { return Default; }                      \
 private:                                                                      \
 PrefTemplate<UpdatePolicy::Update, Type, Get##Name##PrefDefault, Get##Name##PrefName> mPref##Name
 
 class PreferenceAccessImpl;
 class gfxPrefs;
 class gfxPrefs final
 {
-  private:
+private:
   /// See Logging.h.  This lets Moz2D access preference values it owns.
   PreferenceAccessImpl* mMoz2DPrefAccess;
 
 private:
   // Enums for the update policy.
   enum class UpdatePolicy {
     Skip, // Set the value to default, skip any Preferences calls
     Once, // Evaluate the preference once, unchanged during the session
@@ -360,16 +360,18 @@ private:
   // decrease it until we hit mid gray at -1 contrast, after that it gets weird.
   DECL_GFX_PREF(Live, "layers.effect.contrast",                LayersEffectContrast, float, 0.0f);
   DECL_GFX_PREF(Live, "layers.effect.grayscale",               LayersEffectGrayscale, bool, false);
   DECL_GFX_PREF(Live, "layers.effect.invert",                  LayersEffectInvert, bool, false);
   DECL_GFX_PREF(Once, "layers.enable-tiles",                   LayersTilesEnabled, bool, false);
   DECL_GFX_PREF(Live, "layers.flash-borders",                  FlashLayerBorders, bool, false);
   DECL_GFX_PREF(Once, "layers.force-shmem-tiles",              ForceShmemTiles, bool, false);
   DECL_GFX_PREF(Live, "layers.frame-counter",                  DrawFrameCounter, bool, false);
+  DECL_GFX_PREF(Once, "layers.gpu-process.dev.enabled",        GPUProcessDevEnabled, bool, false);
+  DECL_GFX_PREF(Once, "layers.gpu-process.dev.timeout_ms",     GPUProcessDevTimeoutMs, int32_t, 5000);
   DECL_GFX_PREF(Once, "layers.gralloc.disable",                DisableGralloc, bool, false);
   DECL_GFX_PREF(Live, "layers.low-precision-buffer",           UseLowPrecisionBuffer, bool, false);
   DECL_GFX_PREF(Live, "layers.low-precision-opacity",          LowPrecisionOpacity, float, 1.0f);
   DECL_GFX_PREF(Live, "layers.low-precision-resolution",       LowPrecisionResolution, float, 0.25f);
   DECL_GFX_PREF(Live, "layers.max-active",                     MaxActiveLayers, int32_t, -1);
   DECL_GFX_PREF(Once, "layers.offmainthreadcomposition.force-disabled", LayersOffMainThreadCompositionForceDisabled, bool, false);
   DECL_GFX_PREF(Live, "layers.offmainthreadcomposition.frame-rate", LayersCompositionFrameRate, int32_t,-1);
   DECL_GFX_PREF(Live, "layers.orientation.sync.timeout",       OrientationSyncMillis, uint32_t, (uint32_t)0);
--- a/gfx/thebes/gfxWindowsSurface.cpp
+++ b/gfx/thebes/gfxWindowsSurface.cpp
@@ -11,49 +11,37 @@
 #include "mozilla/gfx/Logging.h"
 
 #include "cairo.h"
 #include "cairo-win32.h"
 
 #include "nsString.h"
 
 gfxWindowsSurface::gfxWindowsSurface(HDC dc, uint32_t flags) :
-    mOwnsDC(false), mForPrinting(false), mDC(dc), mWnd(nullptr)
+    mOwnsDC(false), mDC(dc), mWnd(nullptr)
 {
-    if (flags & FLAG_TAKE_DC)
-        mOwnsDC = true;
-
-#ifdef NS_PRINTING
-    if (flags & FLAG_FOR_PRINTING) {
-        Init(cairo_win32_printing_surface_create(mDC));
-        mForPrinting = true;
-        if (!mSurfaceValid) {
-            gfxCriticalNote << "Invalid printing surface";
-        }
-    } else
-#endif
     InitWithDC(flags);
 }
 
 gfxWindowsSurface::gfxWindowsSurface(IDirect3DSurface9 *surface, uint32_t flags) :
-    mOwnsDC(false), mForPrinting(false), mDC(0), mWnd(nullptr)
+    mOwnsDC(false), mDC(0), mWnd(nullptr)
 {
     cairo_surface_t *surf = cairo_win32_surface_create_with_d3dsurface9(surface);
     Init(surf);
 }
 
 
 void
 gfxWindowsSurface::MakeInvalid(mozilla::gfx::IntSize& size)
 {
     size = mozilla::gfx::IntSize(-1, -1);
 }
 
 gfxWindowsSurface::gfxWindowsSurface(const mozilla::gfx::IntSize& realSize, gfxImageFormat imageFormat) :
-    mOwnsDC(false), mForPrinting(false), mWnd(nullptr)
+    mOwnsDC(false), mWnd(nullptr)
 {
     mozilla::gfx::IntSize size(realSize);
     if (!mozilla::gfx::Factory::CheckSurfaceSize(size))
         MakeInvalid(size);
 
     cairo_format_t cformat = GfxFormatToCairoFormat(imageFormat);
     cairo_surface_t *surf =
         cairo_win32_surface_create_with_dib(cformat, size.width, size.height);
@@ -64,25 +52,24 @@ gfxWindowsSurface::gfxWindowsSurface(con
         mDC = cairo_win32_surface_get_dc(CairoSurface());
         RecordMemoryUsed(size.width * size.height * 4 + sizeof(gfxWindowsSurface));
     } else {
         mDC = nullptr;
     }
 }
 
 gfxWindowsSurface::gfxWindowsSurface(cairo_surface_t *csurf) :
-    mOwnsDC(false), mForPrinting(false), mWnd(nullptr)
+    mOwnsDC(false), mWnd(nullptr)
 {
     if (cairo_surface_status(csurf) == 0)
         mDC = cairo_win32_surface_get_dc(csurf);
     else
         mDC = nullptr;
 
-    if (cairo_surface_get_type(csurf) == CAIRO_SURFACE_TYPE_WIN32_PRINTING)
-        mForPrinting = true;
+    MOZ_ASSERT(cairo_surface_get_type(csurf) != CAIRO_SURFACE_TYPE_WIN32_PRINTING);
 
     Init(csurf, true);
 }
 
 void
 gfxWindowsSurface::InitWithDC(uint32_t flags)
 {
     if (flags & FLAG_IS_TRANSPARENT) {
@@ -96,17 +83,17 @@ already_AddRefed<gfxASurface>
 gfxWindowsSurface::CreateSimilarSurface(gfxContentType aContent,
                                         const mozilla::gfx::IntSize& aSize)
 {
     if (!mSurface || !mSurfaceValid) {
         return nullptr;
     }
 
     cairo_surface_t *surface;
-    if (!mForPrinting && GetContentType() == gfxContentType::COLOR_ALPHA) {
+    if (GetContentType() == gfxContentType::COLOR_ALPHA) {
         // When creating a similar surface to a transparent surface, ensure
         // the new surface uses a DIB. cairo_surface_create_similar won't
         // use  a DIB for a gfxContentType::COLOR surface if this surface doesn't
         // have a DIB (e.g. if we're a transparent window surface). But
         // we need a DIB to perform well if the new surface is composited into
         // a surface that's the result of create_similar(gfxContentType::COLOR_ALPHA)
         // (e.g. a backbuffer for the window) --- that new surface *would*
         // have a DIB.
@@ -142,158 +129,39 @@ gfxWindowsSurface::~gfxWindowsSurface()
 }
 
 HDC
 gfxWindowsSurface::GetDC()
 {
     return cairo_win32_surface_get_dc (CairoSurface());
 }
 
-
 already_AddRefed<gfxImageSurface>
 gfxWindowsSurface::GetAsImageSurface()
 {
     if (!mSurfaceValid) {
         NS_WARNING ("GetImageSurface on an invalid (null) surface; who's calling this without checking for surface errors?");
         return nullptr;
     }
 
     NS_ASSERTION(CairoSurface() != nullptr, "CairoSurface() shouldn't be nullptr when mSurfaceValid is TRUE!");
 
-    if (mForPrinting)
-        return nullptr;
-
     cairo_surface_t *isurf = cairo_win32_surface_get_image(CairoSurface());
     if (!isurf)
         return nullptr;
 
     RefPtr<gfxImageSurface> result = gfxASurface::Wrap(isurf).downcast<gfxImageSurface>();
     result->SetOpaqueRect(GetOpaqueRect());
 
     return result.forget();
 }
 
-nsresult
-gfxWindowsSurface::BeginPrinting(const nsAString& aTitle,
-                                 const nsAString& aPrintToFileName)
-{
-#ifdef NS_PRINTING
-#define DOC_TITLE_LENGTH (MAX_PATH-1)
-    if (!mForPrinting) {
-        return NS_OK;
-    }
-
-    DOCINFOW docinfo;
-
-    nsString titleStr(aTitle);
-    if (titleStr.Length() > DOC_TITLE_LENGTH) {
-        titleStr.SetLength(DOC_TITLE_LENGTH-3);
-        titleStr.AppendLiteral("...");
-    }
-
-    nsString docName(aPrintToFileName);
-    docinfo.cbSize = sizeof(docinfo);
-    docinfo.lpszDocName = titleStr.Length() > 0 ? titleStr.get() : L"Mozilla Document";
-    docinfo.lpszOutput = docName.Length() > 0 ? docName.get() : nullptr;
-    docinfo.lpszDatatype = nullptr;
-    docinfo.fwType = 0;
-
-    ::StartDocW(mDC, &docinfo);
-
-    return NS_OK;
-#else
-    return NS_ERROR_FAILURE;
-#endif
-}
-
-nsresult
-gfxWindowsSurface::EndPrinting()
-{
-#ifdef NS_PRINTING
-    if (!mForPrinting) {
-        return NS_OK;
-    }
-
-    int result = ::EndDoc(mDC);
-    if (result <= 0)
-        return NS_ERROR_FAILURE;
-
-    return NS_OK;
-#else
-    return NS_ERROR_FAILURE;
-#endif
-}
-
-nsresult
-gfxWindowsSurface::AbortPrinting()
-{
-#ifdef NS_PRINTING
-    if (!mForPrinting) {
-        return NS_OK;
-    }
-
-    int result = ::AbortDoc(mDC);
-    if (result <= 0)
-        return NS_ERROR_FAILURE;
-    return NS_OK;
-#else
-    return NS_ERROR_FAILURE;
-#endif
-}
-
-nsresult
-gfxWindowsSurface::BeginPage()
-{
-#ifdef NS_PRINTING
-    if (!mForPrinting) {
-        return NS_OK;
-    }
-
-    int result = ::StartPage(mDC);
-    if (result <= 0)
-        return NS_ERROR_FAILURE;
-    return NS_OK;
-#else
-    return NS_ERROR_FAILURE;
-#endif
-}
-
-nsresult
-gfxWindowsSurface::EndPage()
-{
-#ifdef NS_PRINTING
-    if (!mForPrinting) {
-        return NS_OK;
-    }
-
-    cairo_surface_show_page(CairoSurface());
-    int result = ::EndPage(mDC);
-    if (result <= 0)
-        return NS_ERROR_FAILURE;
-    return NS_OK;
-#else
-    return NS_ERROR_FAILURE;
-#endif
-}
-
 const mozilla::gfx::IntSize
 gfxWindowsSurface::GetSize() const
 {
-    if (mForPrinting) {
-        // On Windows we need to use the printable area of the page.
-        // Note: we only scale the printing using the LOGPIXELSY, so we use that
-        // when calculating the surface width as well as the height.
-        int32_t heightDPI = ::GetDeviceCaps(mDC, LOGPIXELSY);
-        float width = (::GetDeviceCaps(mDC, HORZRES) * POINTS_PER_INCH_FLOAT)
-                      / heightDPI;
-        float height = (::GetDeviceCaps(mDC, VERTRES) * POINTS_PER_INCH_FLOAT)
-                       / heightDPI;
-        return mozilla::gfx::IntSize(width, height);
-    }
-
     if (!mSurfaceValid) {
         NS_WARNING ("GetImageSurface on an invalid (null) surface; who's calling this without checking for surface errors?");
         return mozilla::gfx::IntSize(-1, -1);
     }
 
     NS_ASSERTION(mSurface != nullptr, "CairoSurface() shouldn't be nullptr when mSurfaceValid is TRUE!");
 
     return mozilla::gfx::IntSize(cairo_win32_surface_get_width(mSurface),
--- a/gfx/thebes/gfxWindowsSurface.h
+++ b/gfx/thebes/gfxWindowsSurface.h
@@ -17,18 +17,16 @@ struct IDirect3DSurface9;
 /* undefine LoadImage because our code uses that name */
 #undef LoadImage
 
 class gfxContext;
 
 class gfxWindowsSurface : public gfxASurface {
 public:
     enum {
-        FLAG_TAKE_DC = (1 << 0),
-        FLAG_FOR_PRINTING = (1 << 1),
         FLAG_IS_TRANSPARENT = (1 << 2)
     };
 
     gfxWindowsSurface(HDC dc, uint32_t flags = 0);
 
     // Create from a shared d3d9surface
     gfxWindowsSurface(IDirect3DSurface9 *surface, uint32_t flags = 0);
 
@@ -44,27 +42,20 @@ public:
     void InitWithDC(uint32_t flags);
 
     virtual ~gfxWindowsSurface();
 
     HDC GetDC();
 
     already_AddRefed<gfxImageSurface> GetAsImageSurface();
 
-    nsresult BeginPrinting(const nsAString& aTitle, const nsAString& aPrintToFileName);
-    nsresult EndPrinting();
-    nsresult AbortPrinting();
-    nsresult BeginPage();
-    nsresult EndPage();
-
     const mozilla::gfx::IntSize GetSize() const;
 
 private:
     void MakeInvalid(mozilla::gfx::IntSize& size);
 
     bool mOwnsDC;
-    bool mForPrinting;
 
     HDC mDC;
     HWND mWnd;
 };
 
 #endif /* GFX_WINDOWSSURFACE_H */
--- a/gfx/thebes/moz.build
+++ b/gfx/thebes/moz.build
@@ -48,17 +48,16 @@ EXPORTS += [
     'RoundedRect.h',
     'SoftwareVsyncSource.h',
     'VsyncSource.h',
 ]
 
 EXPORTS.mozilla.gfx += [
     'PrintTarget.h',
     'PrintTargetRecording.h',
-    'PrintTargetThebes.h',
 ]
 
 if CONFIG['MOZ_WIDGET_TOOLKIT'] == 'android':
     EXPORTS += [
         'gfxAndroidPlatform.h',
         'gfxFT2FontBase.h',
         'gfxFT2Fonts.h',
     ]
@@ -175,24 +174,26 @@ elif CONFIG['MOZ_WIDGET_TOOLKIT'] == 'wi
         'gfxGDIFontList.h',
         'gfxPlatformFontList.h',
         'gfxWindowsNativeDrawing.h',
         'gfxWindowsPlatform.h',
         'gfxWindowsSurface.h',
     ]
     EXPORTS.mozilla.gfx += [
         'PrintTargetPDF.h',
+        'PrintTargetWindows.h',
     ]
     SOURCES += [
         'gfxGDIFont.cpp',
         'gfxGDIFontList.cpp',
         'gfxWindowsNativeDrawing.cpp',
         'gfxWindowsPlatform.cpp',
         'gfxWindowsSurface.cpp',
         'PrintTargetPDF.cpp',
+        'PrintTargetWindows.cpp',
     ]
     if CONFIG['MOZ_ENABLE_DWRITE_FONT']:
         UNIFIED_SOURCES += [
             'gfxDWriteFontList.cpp',
         ]
         SOURCES += [
             'gfxDWriteCommon.cpp',
             'gfxDWriteFonts.cpp',
@@ -214,17 +215,16 @@ SOURCES += [
     # we could consider removing soon (affects Ubuntus older than 10.04 LTS)
     # which currently prevent it from joining UNIFIED_SOURCES.
     'gfxDrawable.cpp',
     # gfxPlatform.cpp includes mac system header conflicting with point/size
     'gfxPlatform.cpp',
     'gfxPrefs.cpp',
     'PrintTarget.cpp',
     'PrintTargetRecording.cpp',
-    'PrintTargetThebes.cpp',
 ]
 
 UNIFIED_SOURCES += [
     'CJKCompatSVS.cpp',
     'gfxAlphaRecovery.cpp',
     'gfxBaseSharedMemorySurface.cpp',
     'gfxBlur.cpp',
     'gfxContext.cpp',
--- a/image/ImageCacheKey.cpp
+++ b/image/ImageCacheKey.cpp
@@ -40,79 +40,67 @@ BlobSerial(ImageURL* aURI)
   if (NS_SUCCEEDED(NS_GetBlobForBlobURISpec(spec, getter_AddRefs(blob))) &&
       blob) {
     return Some(blob->GetSerialNumber());
   }
 
   return Nothing();
 }
 
-ImageCacheKey::ImageCacheKey(nsIURI* aURI,
-                             const PrincipalOriginAttributes& aAttrs,
-                             nsIDocument* aDocument)
+ImageCacheKey::ImageCacheKey(nsIURI* aURI, nsIDocument* aDocument)
   : mURI(new ImageURL(aURI))
-  , mOriginAttributes(aAttrs)
   , mControlledDocument(GetControlledDocumentToken(aDocument))
   , mIsChrome(URISchemeIs(mURI, "chrome"))
 {
   MOZ_ASSERT(NS_IsMainThread());
 
   if (URISchemeIs(mURI, "blob")) {
     mBlobSerial = BlobSerial(mURI);
   }
 
-  mHash = ComputeHash(mURI, mBlobSerial, mOriginAttributes, mControlledDocument);
+  mHash = ComputeHash(mURI, mBlobSerial, mControlledDocument);
 }
 
-ImageCacheKey::ImageCacheKey(ImageURL* aURI,
-                             const PrincipalOriginAttributes& aAttrs,
-                             nsIDocument* aDocument)
+ImageCacheKey::ImageCacheKey(ImageURL* aURI, nsIDocument* aDocument)
   : mURI(aURI)
-  , mOriginAttributes(aAttrs)
   , mControlledDocument(GetControlledDocumentToken(aDocument))
   , mIsChrome(URISchemeIs(mURI, "chrome"))
 {
   MOZ_ASSERT(aURI);
 
   if (URISchemeIs(mURI, "blob")) {
     mBlobSerial = BlobSerial(mURI);
   }
 
-  mHash = ComputeHash(mURI, mBlobSerial, mOriginAttributes, mControlledDocument);
+  mHash = ComputeHash(mURI, mBlobSerial, mControlledDocument);
 }
 
 ImageCacheKey::ImageCacheKey(const ImageCacheKey& aOther)
   : mURI(aOther.mURI)
   , mBlobSerial(aOther.mBlobSerial)
-  , mOriginAttributes(aOther.mOriginAttributes)
   , mControlledDocument(aOther.mControlledDocument)
   , mHash(aOther.mHash)
   , mIsChrome(aOther.mIsChrome)
 { }
 
 ImageCacheKey::ImageCacheKey(ImageCacheKey&& aOther)
   : mURI(Move(aOther.mURI))
   , mBlobSerial(Move(aOther.mBlobSerial))
-  , mOriginAttributes(aOther.mOriginAttributes)
   , mControlledDocument(aOther.mControlledDocument)
   , mHash(aOther.mHash)
   , mIsChrome(aOther.mIsChrome)
 { }
 
 bool
 ImageCacheKey::operator==(const ImageCacheKey& aOther) const
 {
   // Don't share the image cache between a controlled document and anything else.
   if (mControlledDocument != aOther.mControlledDocument) {
     return false;
   }
-  // The origin attributes always have to match.
-  if (mOriginAttributes != aOther.mOriginAttributes) {
-    return false;
-  }
   if (mBlobSerial || aOther.mBlobSerial) {
     // If at least one of us has a blob serial, just compare the blob serial and
     // the ref portion of the URIs.
     return mBlobSerial == aOther.mBlobSerial &&
            mURI->HasSameRef(*aOther.mURI);
   }
 
   // For non-blob URIs, compare the URIs.
@@ -123,41 +111,37 @@ const char*
 ImageCacheKey::Spec() const
 {
   return mURI->Spec();
 }
 
 /* static */ uint32_t
 ImageCacheKey::ComputeHash(ImageURL* aURI,
                            const Maybe<uint64_t>& aBlobSerial,
-                           const PrincipalOriginAttributes& aAttrs,
                            void* aControlledDocument)
 {
   // Since we frequently call Hash() several times in a row on the same
   // ImageCacheKey, as an optimization we compute our hash once and store it.
 
   nsPrintfCString ptr("%p", aControlledDocument);
-  nsAutoCString suffix;
-  aAttrs.CreateSuffix(suffix);
-
   if (aBlobSerial) {
     // For blob URIs, we hash the serial number of the underlying blob, so that
     // different blob URIs which point to the same blob share a cache entry. We
     // also include the ref portion of the URI to support -moz-samplesize, which
     // requires us to create different Image objects even if the source data is
     // the same.
     nsAutoCString ref;
     aURI->GetRef(ref);
-    return HashGeneric(*aBlobSerial, HashString(ref + suffix + ptr));
+    return HashGeneric(*aBlobSerial, HashString(ref + ptr));
   }
 
   // For non-blob URIs, we hash the URI spec.
   nsAutoCString spec;
   aURI->GetSpec(spec);
-  return HashString(spec + suffix + ptr);
+  return HashString(spec + ptr);
 }
 
 /* static */ void*
 ImageCacheKey::GetControlledDocumentToken(nsIDocument* aDocument)
 {
   // For non-controlled documents, we just return null.  For controlled
   // documents, we cast the pointer into a void* to avoid dereferencing
   // it (since we only use it for comparisons), and return it.
--- a/image/ImageCacheKey.h
+++ b/image/ImageCacheKey.h
@@ -5,17 +5,16 @@
 
 /**
  * ImageCacheKey is the key type for the image cache (see imgLoader.h).
  */
 
 #ifndef mozilla_image_src_ImageCacheKey_h
 #define mozilla_image_src_ImageCacheKey_h
 
-#include "mozilla/BasePrincipal.h"
 #include "mozilla/Maybe.h"
 
 class nsIDocument;
 class nsIURI;
 
 namespace mozilla {
 namespace image {
 
@@ -27,20 +26,18 @@ class ImageURL;
  * We key the cache on the initial URI (before any redirects), with some
  * canonicalization applied. See ComputeHash() for the details.
  * Controlled documents do not share their cache entries with
  * non-controlled documents, or other controlled documents.
  */
 class ImageCacheKey final
 {
 public:
-  ImageCacheKey(nsIURI* aURI, const PrincipalOriginAttributes& aAttrs,
-                nsIDocument* aDocument);
-  ImageCacheKey(ImageURL* aURI, const PrincipalOriginAttributes& aAttrs,
-                nsIDocument* aDocument);
+  ImageCacheKey(nsIURI* aURI, nsIDocument* aDocument);
+  ImageCacheKey(ImageURL* aURI, nsIDocument* aDocument);
 
   ImageCacheKey(const ImageCacheKey& aOther);
   ImageCacheKey(ImageCacheKey&& aOther);
 
   bool operator==(const ImageCacheKey& aOther) const;
   uint32_t Hash() const { return mHash; }
 
   /// A weak pointer to the URI spec for this cache entry. For logging only.
@@ -51,23 +48,21 @@ public:
 
   /// A token indicating which service worker controlled document this entry
   /// belongs to, if any.
   void* ControlledDocument() const { return mControlledDocument; }
 
 private:
   static uint32_t ComputeHash(ImageURL* aURI,
                               const Maybe<uint64_t>& aBlobSerial,
-                              const PrincipalOriginAttributes& aAttrs,
                               void* aControlledDocument);
   static void* GetControlledDocumentToken(nsIDocument* aDocument);
 
   RefPtr<ImageURL> mURI;
   Maybe<uint64_t> mBlobSerial;
-  PrincipalOriginAttributes mOriginAttributes;
   void* mControlledDocument;
   uint32_t mHash;
   bool mIsChrome;
 };
 
 } // namespace image
 } // namespace mozilla
 
--- a/image/imgLoader.cpp
+++ b/image/imgLoader.cpp
@@ -1363,30 +1363,17 @@ imgLoader::ClearCache(bool chrome)
 NS_IMETHODIMP
 imgLoader::FindEntryProperties(nsIURI* uri,
                                nsIDOMDocument* aDOMDoc,
                                nsIProperties** _retval)
 {
   *_retval = nullptr;
 
   nsCOMPtr<nsIDocument> doc = do_QueryInterface(aDOMDoc);
-  nsCOMPtr<nsIPrincipal> principal;
-  if (doc) {
-    principal = doc->NodePrincipal();
-  } else {
-    nsCOMPtr<nsIScriptSecurityManager> ssm = nsContentUtils::GetSecurityManager();
-    NS_ENSURE_TRUE(ssm, NS_ERROR_FAILURE);
-    ssm->GetSystemPrincipal(getter_AddRefs(principal));
-  }
-  NS_ENSURE_TRUE(principal, NS_ERROR_FAILURE);
-
-  const PrincipalOriginAttributes& attrs =
-    BasePrincipal::Cast(principal)->OriginAttributesRef();
-
-  ImageCacheKey key(uri, attrs, doc);
+  ImageCacheKey key(uri, doc);
   imgCacheTable& cache = GetCache(key);
 
   RefPtr<imgCacheEntry> entry;
   if (cache.Get(key, getter_AddRefs(entry)) && entry) {
     if (mCacheTracker && entry->HasNoProxies()) {
       mCacheTracker->MarkUsed(entry);
     }
 
@@ -2133,21 +2120,17 @@ imgLoader::LoadImage(nsIURI* aURI,
   }
 
   RefPtr<imgCacheEntry> entry;
 
   // Look in the cache for our URI, and then validate it.
   // XXX For now ignore aCacheKey. We will need it in the future
   // for correctly dealing with image load requests that are a result
   // of post data.
-  NS_ENSURE_TRUE(aLoadingPrincipal, NS_ERROR_FAILURE);
-  const PrincipalOriginAttributes& attrs =
-    BasePrincipal::Cast(aLoadingPrincipal)->OriginAttributesRef();
-
-  ImageCacheKey key(aURI, attrs, aLoadingDocument);
+  ImageCacheKey key(aURI, aLoadingDocument);
   imgCacheTable& cache = GetCache(key);
 
   if (cache.Get(key, getter_AddRefs(entry)) && entry) {
     if (ValidateEntry(entry, aURI, aInitialDocumentURI, aReferrerURI,
                       aReferrerPolicy, aLoadGroup, aObserver, aLoadingDocument,
                       requestFlags, aContentPolicyType, true, _retval,
                       aLoadingPrincipal, corsmode)) {
       request = entry->GetRequest();
@@ -2341,25 +2324,17 @@ imgLoader::LoadImageWithChannel(nsIChann
 
   MOZ_ASSERT(NS_UsePrivateBrowsing(channel) == mRespectPrivacy);
 
   RefPtr<imgRequest> request;
 
   nsCOMPtr<nsIURI> uri;
   channel->GetURI(getter_AddRefs(uri));
   nsCOMPtr<nsIDocument> doc = do_QueryInterface(aCX);
-
-  NS_ENSURE_TRUE(channel, NS_ERROR_FAILURE);
-  nsCOMPtr<nsILoadInfo> loadInfo = channel->GetLoadInfo();
-  NS_ENSURE_TRUE(loadInfo, NS_ERROR_FAILURE);
-
-  PrincipalOriginAttributes attrs;
-  attrs.InheritFromNecko(loadInfo->GetOriginAttributes());
-
-  ImageCacheKey key(uri, attrs, doc);
+  ImageCacheKey key(uri, doc);
 
   nsLoadFlags requestFlags = nsIRequest::LOAD_NORMAL;
   channel->GetLoadFlags(&requestFlags);
 
   RefPtr<imgCacheEntry> entry;
 
   if (requestFlags & nsIRequest::LOAD_BYPASS_CACHE) {
     RemoveFromCache(key);
@@ -2451,17 +2426,17 @@ imgLoader::LoadImageWithChannel(nsIChann
     // We use originalURI here to fulfil the imgIRequest contract on GetURI.
     nsCOMPtr<nsIURI> originalURI;
     channel->GetOriginalURI(getter_AddRefs(originalURI));
 
     // XXX(seth): We should be able to just use |key| here, except that |key| is
     // constructed above with the *current URI* and not the *original URI*. I'm
     // pretty sure this is a bug, and it's preventing us from ever getting a
     // cache hit in LoadImageWithChannel when redirects are involved.
-    ImageCacheKey originalURIKey(originalURI, attrs, doc);
+    ImageCacheKey originalURIKey(originalURI, doc);
 
     // Default to doing a principal check because we don't know who
     // started that load and whether their principal ended up being
     // inherited on the channel.
     NewRequestAndEntry(/* aForcePrincipalCheckForCacheEntry = */ true,
                        this, originalURIKey,
                        getter_AddRefs(request),
                        getter_AddRefs(entry));
--- a/image/test/unit/async_load_tests.js
+++ b/image/test/unit/async_load_tests.js
@@ -8,19 +8,16 @@
 
 var Cc = Components.classes;
 var Ci = Components.interfaces;
 var Cu = Components.utils;
 var Cr = Components.results;
 
 Cu.import("resource://testing-common/httpd.js");
 Cu.import("resource://gre/modules/NetUtil.jsm");
-Cu.import("resource://gre/modules/Services.jsm");
-
-var ssm = Services.scriptSecurityManager;
 
 var server = new HttpServer();
 server.registerDirectory("/", do_get_file(''));
 server.registerContentType("sjs", "sjs");
 server.start(-1);
 
 
 load('image_load_helpers.js');
@@ -95,19 +92,17 @@ function secondLoadDone(oldlistener, aRe
 // therefore would be at most risk of being served synchronously.
 function checkSecondLoad()
 {
   do_test_pending();
 
   var listener = new ImageListener(checkClone, secondLoadDone);
   var outer = Cc["@mozilla.org/image/tools;1"].getService(Ci.imgITools)
                 .createScriptedObserver(listener);
-  var systemPrincipal = ssm.getSystemPrincipal();
-  requests.push(gCurrentLoader.loadImageXPCOM(uri, null, null, "default",
-                                              systemPrincipal, null, outer, null, 0, null));
+  requests.push(gCurrentLoader.loadImageXPCOM(uri, null, null, "default", null, null, outer, null, 0, null));
   listener.synchronous = false;
 }
 
 function firstLoadDone(oldlistener, aRequest)
 {
   checkSecondLoad(uri);
 
   do_test_finished();
@@ -177,19 +172,17 @@ function startImageCallback(otherCb)
 {
   return function(listener, request)
   {
     // Make sure we can load the same image immediately out of the cache.
     do_test_pending();
     var listener2 = new ImageListener(null, function(foo, bar) { do_test_finished(); });
     var outer = Cc["@mozilla.org/image/tools;1"].getService(Ci.imgITools)
                   .createScriptedObserver(listener2);
-    var systemPrincipal = ssm.getSystemPrincipal();
-    requests.push(gCurrentLoader.loadImageXPCOM(uri, null, null, "default",
-                                                systemPrincipal, null, outer, null, 0, null));
+    requests.push(gCurrentLoader.loadImageXPCOM(uri, null, null, "default", null, null, outer, null, 0, null));
     listener2.synchronous = false;
 
     // Now that we've started another load, chain to the callback.
     otherCb(listener, request);
   }
 }
 
 var gCurrentLoader;
@@ -206,18 +199,16 @@ function run_test()
   do_register_cleanup(cleanup);
 
   gCurrentLoader = Cc["@mozilla.org/image/loader;1"].createInstance(Ci.imgILoader);
 
   do_test_pending();
   var listener = new ImageListener(startImageCallback(checkClone), firstLoadDone);
   var outer = Cc["@mozilla.org/image/tools;1"].getService(Ci.imgITools)
                 .createScriptedObserver(listener);
-  var systemPrincipal = ssm.getSystemPrincipal();
-  var req = gCurrentLoader.loadImageXPCOM(uri, null, null, "default",
-                                          systemPrincipal, null, outer, null, 0, null);
+  var req = gCurrentLoader.loadImageXPCOM(uri, null, null, "default", null, null, outer, null, 0, null);
   requests.push(req);
 
   // Ensure that we don't cause any mayhem when we lock an image.
   req.lockImage();
 
   listener.synchronous = false;
 }
--- a/image/test/unit/test_private_channel.js
+++ b/image/test/unit/test_private_channel.js
@@ -2,18 +2,16 @@ var Cc = Components.classes;
 var Ci = Components.interfaces;
 var Cr = Components.results;
 var Cu = Components.utils;
 
 Cu.import("resource://gre/modules/Services.jsm");
 Cu.import("resource://gre/modules/NetUtil.jsm");
 Cu.import("resource://testing-common/httpd.js");
 
-var ssm = Services.scriptSecurityManager;
-
 var server = new HttpServer();
 server.registerPathHandler('/image.png', imageHandler);
 server.start(-1);
 
 load('image_load_helpers.js');
 
 var gHits = 0;
 
@@ -81,19 +79,17 @@ function setup_chan(path, isPrivate, cal
 function loadImage(isPrivate, callback) {
   var listener = new ImageListener(null, callback);
   var outer = Cc["@mozilla.org/image/tools;1"].getService(Ci.imgITools)
                 .createScriptedObserver(listener);
   var uri = gIoService.newURI(gImgPath, null, null);
   var loadGroup = Cc["@mozilla.org/network/load-group;1"].createInstance(Ci.nsILoadGroup);
   loadGroup.notificationCallbacks = new NotificationCallbacks(isPrivate);
   var loader = isPrivate ? gPrivateLoader : gPublicLoader;
-  var systemPrincipal = ssm.getSystemPrincipal();
-  requests.push(loader.loadImageXPCOM(uri, null, null, "default",
-                                      systemPrincipal, loadGroup, outer, null, 0, null));
+  requests.push(loader.loadImageXPCOM(uri, null, null, "default", null, loadGroup, outer, null, 0, null));
   listener.synchronous = false;
 }
 
 function run_loadImage_tests() {
   function observer() {
     Services.obs.removeObserver(observer, "cacheservice:empty-cache");
     gHits = 0;
     loadImage(false, function() {
@@ -103,19 +99,16 @@ function run_loadImage_tests() {
             do_check_eq(gHits, 2);
             server.stop(do_test_finished);
           });
         });
       });
     });
   }
 
-  gPrivateLoader.QueryInterface(Ci.imgICache).clearCache(false);
-  gPublicLoader.QueryInterface(Ci.imgICache).clearCache(false);
-
   Services.obs.addObserver(observer, "cacheservice:empty-cache", false);
   let cs = Cc["@mozilla.org/netwerk/cache-storage-service;1"]
              .getService(Ci.nsICacheStorageService);
   cs.clear();
 }
 
 function cleanup()
 {
--- a/ipc/glue/GeckoChildProcessHost.cpp
+++ b/ipc/glue/GeckoChildProcessHost.cpp
@@ -1067,16 +1067,18 @@ GeckoChildProcessHost::PerformAsyncLaunc
         auto level = isWidevine ? SandboxBroker::Restricted : SandboxBroker::LockDown;
         bool ok = mSandboxBroker.SetSecurityLevelForGMPlugin(level);
         if (!ok) {
           return false;
         }
         shouldSandboxCurrentProcess = true;
       }
       break;
+    case GeckoProcessType_GPU:
+      break;
     case GeckoProcessType_Default:
     default:
       MOZ_CRASH("Bad process type in GeckoChildProcessHost");
       break;
   };
 
   if (shouldSandboxCurrentProcess) {
     MaybeAddNsprLogFileAccess(mAllowedFilesReadWrite);
rename from dom/plugins/ipc/TaskFactory.h
rename to ipc/glue/TaskFactory.h
--- a/dom/plugins/ipc/TaskFactory.h
+++ b/ipc/glue/TaskFactory.h
@@ -14,17 +14,17 @@
  * Chromium's factories assert if tasks are created and run on different threads,
  * which is something we need to do in PluginModuleParent (hang UI vs. main thread).
  * TaskFactory just provides cancellable tasks that don't assert this.
  * This version also allows both ScopedMethod and regular Tasks to be generated
  * by the same Factory object.
  */
 
 namespace mozilla {
-namespace plugins {
+namespace ipc {
 
 template<class T>
 class TaskFactory : public RevocableStore
 {
 private:
   template<class TaskType>
   class TaskWrapper : public TaskType
   {
@@ -98,12 +98,12 @@ protected:
     Method meth_;
     Params params_;
   };
 
 private:
   T* object_;
 };
 
-} // namespace plugins
+} // namespace ipc
 } // namespace mozilla
 
 #endif // mozilla_plugins_TaskFactory_h
--- a/ipc/glue/moz.build
+++ b/ipc/glue/moz.build
@@ -30,16 +30,17 @@ EXPORTS.mozilla.ipc += [
     'ProcessChild.h',
     'ProtocolUtils.h',
     'ScopedXREEmbed.h',
     'SendStream.h',
     'SendStreamAlloc.h',
     'SharedMemory.h',
     'SharedMemoryBasic.h',
     'Shmem.h',
+    'TaskFactory.h',
     'Transport.h',
     'URIUtils.h',
     'WindowsMessageLoop.h',
 ]
 
 if CONFIG['MOZ_FAULTY'] == '1':
     EXPORTS.mozilla.ipc += ['Faulty.h']
     SOURCES += ['Faulty.cpp']
--- a/js/src/asmjs/AsmJS.cpp
+++ b/js/src/asmjs/AsmJS.cpp
@@ -2367,17 +2367,17 @@ IsSimdTuple(ModuleValidator& m, ParseNod
     if (CallArgListLength(pn) != GetSimdLanes(global->simdCtorType()))
         return false;
 
     *type = global->simdCtorType();
     return true;
 }
 
 static bool
-IsNumericLiteral(ModuleValidator& m, ParseNode* pn);
+IsNumericLiteral(ModuleValidator& m, ParseNode* pn, bool* isSimd = nullptr);
 
 static NumLit
 ExtractNumericLiteral(ModuleValidator& m, ParseNode* pn);
 
 static inline bool
 IsLiteralInt(ModuleValidator& m, ParseNode* pn, uint32_t* u32);
 
 static bool
@@ -2418,21 +2418,26 @@ IsSimdLiteral(ModuleValidator& m, ParseN
         arg = NextNode(arg);
     }
 
     MOZ_ASSERT(arg == nullptr);
     return true;
 }
 
 static bool
-IsNumericLiteral(ModuleValidator& m, ParseNode* pn)
-{
-    return IsNumericNonFloatLiteral(pn) ||
-           IsFloatLiteral(m, pn) ||
-           IsSimdLiteral(m, pn);
+IsNumericLiteral(ModuleValidator& m, ParseNode* pn, bool* isSimd)
+{
+    if (IsNumericNonFloatLiteral(pn) || IsFloatLiteral(m, pn))
+        return true;
+    if (IsSimdLiteral(m, pn)) {
+        if (isSimd)
+            *isSimd = true;
+        return true;
+    }
+    return false;
 }
 
 // The JS grammar treats -42 as -(42) (i.e., with separate grammar
 // productions) for the unary - and literal 42). However, the asm.js spec
 // recognizes -42 (modulo parens, so -(42) and -((42))) as a single literal
 // so fold the two potential parse nodes into a single double value.
 static double
 ExtractNumericNonFloatValue(ParseNode* pn, ParseNode** out = nullptr)
@@ -2751,19 +2756,23 @@ SimdToExpr(SimdType type, SimdOperation 
         break;
 
       default: break;
     }
     MOZ_CRASH("unexpected SIMD (type, operator) combination");
 }
 
 #undef CASE
-#undef I32CASE
-#undef F32CASE
-#undef B32CASE
+#undef I8x16CASE
+#undef I16x8CASE
+#undef I32x4CASE
+#undef F32x4CASE
+#undef B8x16CASE
+#undef B16x8CASE
+#undef B32x4CASE
 #undef ENUMERATE
 
 typedef Vector<PropertyName*, 4, SystemAllocPolicy> NameVector;
 
 // Encapsulates the building of an asm bytecode function from an asm.js function
 // source code, packing the asm.js code into the asm bytecode form that can
 // be decoded and compiled with a FunctionCompiler.
 class MOZ_STACK_CLASS FunctionValidator
@@ -2827,16 +2836,23 @@ class MOZ_STACK_CLASS FunctionValidator
     }
 
     bool finish(uint32_t funcIndex) {
         MOZ_ASSERT(!blockDepth_);
         MOZ_ASSERT(breakableStack_.empty());
         MOZ_ASSERT(continuableStack_.empty());
         MOZ_ASSERT(breakLabels_.empty());
         MOZ_ASSERT(continueLabels_.empty());
+        for (auto iter = locals_.all(); !iter.empty(); iter.popFront()) {
+            if (iter.front().value().type.isSimd()) {
+                setUsesSimd();
+                break;
+            }
+        }
+
         return m_.mg().finishFuncDef(funcIndex, &fg_);
     }
 
     bool fail(ParseNode* pn, const char* str) {
         return m_.fail(pn, str);
     }
 
     bool failf(ParseNode* pn, const char* fmt, ...) {
@@ -2846,16 +2862,26 @@ class MOZ_STACK_CLASS FunctionValidator
         va_end(ap);
         return false;
     }
 
     bool failName(ParseNode* pn, const char* fmt, PropertyName* name) {
         return m_.failName(pn, fmt, name);
     }
 
+    /***************************************************** Attributes */
+
+    void setUsesSimd() {
+        fg_.setUsesSimd();
+    }
+
+    void setUsesAtomics() {
+        fg_.setUsesAtomics();
+    }
+
     /***************************************************** Local scope setup */
 
     bool addLocal(ParseNode* pn, PropertyName* name, Type type) {
         LocalMap::AddPtr p = locals_.lookupForAdd(name);
         if (p)
             return failName(pn, "duplicate local name '%s' not allowed", name);
         return locals_.add(p, name, Local(type, locals_.count()));
     }
@@ -3721,18 +3747,22 @@ IsLiteralOrConst(FunctionValidator& f, P
         const ModuleValidator::Global* global = f.lookupGlobal(pn->name());
         if (!global || global->which() != ModuleValidator::Global::ConstantLiteral)
             return false;
 
         *lit = global->constLiteralValue();
         return true;
     }
 
-    if (!IsNumericLiteral(f.m(), pn))
-        return false;
+    bool isSimd = false;
+    if (!IsNumericLiteral(f.m(), pn, &isSimd))
+        return false;
+
+    if (isSimd)
+        f.setUsesSimd();
 
     *lit = ExtractNumericLiteral(f.m(), pn);
     return true;
 }
 
 static bool
 CheckFinalReturn(FunctionValidator& f, ParseNode* lastNonEmptyStmt)
 {
@@ -4523,16 +4553,18 @@ CheckAtomicsExchange(FunctionValidator& 
     *type = Type::Int;
     return true;
 }
 
 static bool
 CheckAtomicsBuiltinCall(FunctionValidator& f, ParseNode* callNode, AsmJSAtomicsBuiltinFunction func,
                         Type* type)
 {
+    f.setUsesAtomics();
+
     switch (func) {
       case AsmJSAtomicsBuiltin_compareExchange:
         return CheckAtomicsCompareExchange(f, callNode, type);
       case AsmJSAtomicsBuiltin_exchange:
         return CheckAtomicsExchange(f, callNode, type);
       case AsmJSAtomicsBuiltin_load:
         return CheckAtomicsLoad(f, callNode, type);
       case AsmJSAtomicsBuiltin_store:
@@ -5445,16 +5477,18 @@ CheckSimdSplat(FunctionValidator& f, Par
     *type = opType;
     return true;
 }
 
 static bool
 CheckSimdOperationCall(FunctionValidator& f, ParseNode* call, const ModuleValidator::Global* global,
                        Type* type)
 {
+    f.setUsesSimd();
+
     MOZ_ASSERT(global->isSimdOperation());
 
     SimdType opType = global->simdOperationType();
 
     switch (SimdOperation op = global->simdOperation()) {
       case SimdOperation::Fn_check:
         return CheckSimdCheck(f, call, opType, type);
 
@@ -5537,16 +5571,18 @@ CheckSimdOperationCall(FunctionValidator
     }
     MOZ_CRASH("unexpected simd operation in CheckSimdOperationCall");
 }
 
 static bool
 CheckSimdCtorCall(FunctionValidator& f, ParseNode* call, const ModuleValidator::Global* global,
                   Type* type)
 {
+    f.setUsesSimd();
+
     MOZ_ASSERT(call->isKind(PNK_CALL));
 
     SimdType simdType = global->simdCtorType();
     unsigned length = GetSimdLanes(simdType);
     if (!CheckSimdCallArgs(f, call, length, CheckSimdScalarArgs(simdType)))
         return false;
 
     if (!f.writeSimdOp(simdType, SimdOperation::Constructor))
@@ -5669,17 +5705,20 @@ CheckCoercedAtomicsBuiltinCall(FunctionV
 
 static bool
 CheckCoercedCall(FunctionValidator& f, ParseNode* call, Type ret, Type* type)
 {
     MOZ_ASSERT(ret.isCanonical());
 
     JS_CHECK_RECURSION_DONT_REPORT(f.cx(), return f.m().failOverRecursed());
 
-    if (IsNumericLiteral(f.m(), call)) {
+    bool isSimd = false;
+    if (IsNumericLiteral(f.m(), call, &isSimd)) {
+        if (isSimd)
+            f.setUsesSimd();
         NumLit lit = ExtractNumericLiteral(f.m(), call);
         if (!f.writeConstExpr(lit))
             return false;
         return CoerceResult(f, call, ret, Type::lit(lit), type);
     }
 
     ParseNode* callee = CallCallee(call);
 
@@ -6208,18 +6247,22 @@ CheckBitwise(FunctionValidator& f, Parse
     return true;
 }
 
 static bool
 CheckExpr(FunctionValidator& f, ParseNode* expr, Type* type)
 {
     JS_CHECK_RECURSION_DONT_REPORT(f.cx(), return f.m().failOverRecursed());
 
-    if (IsNumericLiteral(f.m(), expr))
+    bool isSimd = false;
+    if (IsNumericLiteral(f.m(), expr, &isSimd)) {
+        if (isSimd)
+            f.setUsesSimd();
         return CheckNumericLiteral(f, expr, type);
+    }
 
     switch (expr->getKind()) {
       case PNK_NAME:        return CheckVarRef(f, expr, type);
       case PNK_ELEM:        return CheckLoadArray(f, expr, type);
       case PNK_ASSIGN:      return CheckAssign(f, expr, type);
       case PNK_POS:         return CheckPos(f, expr, type);
       case PNK_NOT:         return CheckNot(f, expr, type);
       case PNK_NEG:         return CheckNeg(f, expr, type);
--- a/js/src/asmjs/Wasm.cpp
+++ b/js/src/asmjs/Wasm.cpp
@@ -121,19 +121,18 @@ static bool
 CheckValType(JSContext* cx, Decoder& d, ValType type)
 {
     switch (type) {
       case ValType::I32:
       case ValType::F32:
       case ValType::F64:
         return true;
       case ValType::I64:
-#ifndef JS_CPU_X64
-        return Fail(cx, d, "i64 NYI on this platform");
-#endif
+        if (!IsI64Implemented())
+            return Fail(cx, d, "i64 NYI on this platform");
         return true;
       default:
         // Note: it's important not to remove this default since readValType()
         // can return ValType values for which there is no enumerator.
         break;
     }
 
     return Fail(cx, d, "bad type");
new file mode 100644
--- /dev/null
+++ b/js/src/asmjs/WasmBaselineCompile.cpp
@@ -0,0 +1,6488 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ * vim: set ts=8 sts=4 et sw=4 tw=99:
+ *
+ * Copyright 2016 Mozilla Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/* General status notes:
+ *
+ * "FIXME" indicates a known or suspected bug.
+ * "TODO" indicates an opportunity for a general improvement, with an
+ * additional tag to indicate the area of improvement.
+ *
+ * Unimplemented functionality:
+ *
+ *  - This is not actually a baseline compiler, as it performs no
+ *    profiling and does not trigger ion compilation and function
+ *    replacement (duh)
+ *  - int64 load and store
+ *  - SIMD
+ *  - Atomics
+ *  - current_memory, grow_memory
+ *  - non-signaling interrupts
+ *  - non-signaling bounds checks
+ *  - profiler support (devtools)
+ *  - Platform support:
+ *      x86
+ *      ARM-32
+ *      ARM-64
+ *
+ * There are lots of machine dependencies here but they are pretty
+ * well isolated to a segment of the compiler.  Many dependencies
+ * will eventually be factored into the MacroAssembler layer and shared
+ * with other code generators.
+ *
+ *
+ * High-value compiler performance improvements:
+ *
+ * - The specific-register allocator (the needI32(r), needI64(r) etc
+ *   methods) can avoid syncing the value stack if the specific
+ *   register is in use but there is a free register to shuffle the
+ *   specific register into.  (This will also improve the generated
+ *   code.)  The sync happens often enough here to show up in
+ *   profiles, because it is triggered by integer multiply and divide.
+ *
+ *
+ * High-value code generation improvements:
+ *
+ * - Many opportunities for cheaply folding in a constant rhs, we do
+ *   this already for I32 add and shift operators, this reduces
+ *   register pressure and instruction count.
+ *
+ * - Boolean evaluation for control can be optimized by pushing a
+ *   bool-generating operation onto the value stack in the same way
+ *   that we now push latent constants and local lookups, or (easier)
+ *   by remembering the operation in a side location if the next Expr
+ *   will consume it.
+ *
+ * - Conditional branches (br_if and br_table) pessimize by branching
+ *   over code that performs stack cleanup and a branch.  But if no
+ *   cleanup is needed we could just branch conditionally to the
+ *   target.
+ *
+ * - Register management around calls: At the moment we sync the value
+ *   stack unconditionally (this is simple) but there are probably
+ *   many common cases where we could instead save/restore live
+ *   caller-saves registers and perform parallel assignment into
+ *   argument registers.  This may be important if we keep some locals
+ *   in registers.
+ *
+ * - Allocate some locals to registers on machines where there are
+ *   enough registers.  This is probably hard to do well in a one-pass
+ *   compiler but it might be that just keeping register arguments and
+ *   the first few locals in registers is a viable strategy; another
+ *   (more general) strategy is caching locals in registers in
+ *   straight-line code.  Such caching could also track constant
+ *   values in registers, if that is deemed valuable.  A combination
+ *   of techniques may be desirable: parameters and the first few
+ *   locals could be cached on entry to the function but not
+ *   statically assigned to registers throughout.
+ *
+ *   (On a large corpus of code it should be possible to compute, for
+ *   every signature comprising the types of parameters and locals,
+ *   and using a static weight for loops, a list in priority order of
+ *   which parameters and locals that should be assigned to registers.
+ *   Or something like that.  Wasm makes this simple.  Static
+ *   assignments are desirable because they are not flushed to memory
+ *   by the pre-block sync() call.)
+ */
+
+#include "asmjs/WasmBaselineCompile.h"
+#include "asmjs/WasmBinaryIterator.h"
+#include "asmjs/WasmGenerator.h"
+#include "jit/AtomicOp.h"
+#include "jit/IonTypes.h"
+#include "jit/JitAllocPolicy.h"
+#include "jit/Label.h"
+#include "jit/MacroAssembler.h"
+#include "jit/MIR.h"
+#include "jit/Registers.h"
+#include "jit/RegisterSets.h"
+#if defined(JS_CODEGEN_X64) || defined(JS_CODEGEN_X86)
+# include "jit/x86-shared/Architecture-x86-shared.h"
+# include "jit/x86-shared/Assembler-x86-shared.h"
+#endif
+
+#include "jit/MacroAssembler-inl.h"
+
+using mozilla::DebugOnly;
+using mozilla::FloatingPoint;
+using mozilla::SpecificNaN;
+
+namespace js {
+namespace wasm {
+
+using namespace js::jit;
+
+struct BaseCompilePolicy : ExprIterPolicy
+{
+    static const bool Output = true;
+
+    // The baseline compiler tracks values on a stack of its own -- it
+    // needs to scan that stack for spilling -- and thus has no need
+    // for the values maintained by the iterator.
+    //
+    // The baseline compiler tracks control items on a stack of its
+    // own as well.
+    //
+    // TODO / REDUNDANT: It would be nice if we could make use of the
+    // iterator's ControlItems and not require our own stack for that.
+};
+
+typedef ExprIter<BaseCompilePolicy> BaseExprIter;
+
+typedef bool IsUnsigned;
+typedef bool IsDouble;
+typedef bool IsMax;
+typedef bool IsLoad;
+typedef bool ZeroOnOverflow;
+
+#ifdef JS_CODEGEN_ARM64
+// FIXME: This is not correct, indeed for ARM64 there is no reliable
+// StackPointer and we'll need to change the abstractions that use
+// SP-relative addressing.  There's a guard in emitFunction() below to
+// prevent this workaround from having any consequence.  This hack
+// exists only as a stopgap; there is no ARM64 JIT support yet.
+static const Register StackPointer = RealStackPointer;
+
+// FIXME: This should somehow use vixl::UseScratchRegisterScope, or we
+// should define our own scratch register independent of the masm.
+class ScratchRegisterScope
+{
+  public:
+    ScratchRegisterScope(MacroAssembler& masm) {}
+    operator Register() const { return ScratchReg; }
+};
+#endif
+
+#ifdef JS_CODEGEN_X86
+// The selection of EBX here steps gingerly around: the need for EDX
+// to be allocatable for multiply/divide; ECX to be allocatable for
+// shift/rotate; EAX (= ReturnReg) to be allocatable as the joinreg;
+// EBX not being one of the WasmTableCall registers; and needing a
+// temp register for load/store that has a single-byte persona.
+static const Register ScratchRegX86 = ebx;
+
+// FIXME: We want this to have teeth.  One way to ensure that is to
+// pass BaseCompiler to ScratchRegisterScope instead of masm, and then
+// have a property on BaseCompiler that tracks availability.  On other
+// platforms than x86 we'd delegate from our private
+// ScratchRegisterScope to the standard one by inheritance, passing
+// BaseCompiler->masm to the base constructor.
+class ScratchRegisterScope
+{
+  public:
+    ScratchRegisterScope(MacroAssembler& masm) {}
+    operator Register() const { return ScratchRegX86; }
+};
+#endif
+
+class BaseCompiler
+{
+    // A Label in the code, allocated out of a temp pool in the
+    // TempAllocator attached to the compilation.
+
+    struct PooledLabel : public Label, public TempObject, public InlineListNode<PooledLabel>
+    {
+        PooledLabel() : f(nullptr) {}
+        explicit PooledLabel(BaseCompiler* f) : f(f) {}
+        BaseCompiler* f;
+    };
+
+    typedef Vector<PooledLabel*, 8, SystemAllocPolicy> LabelVector;
+
+    struct UniquePooledLabelFreePolicy
+    {
+        void operator()(PooledLabel* p) {
+            p->f->freeLabel(p);
+        }
+    };
+
+    typedef UniquePtr<PooledLabel, UniquePooledLabelFreePolicy> UniquePooledLabel;
+
+    // The strongly typed register wrappers have saved my bacon a few
+    // times; though they are largely redundant they stay, for now.
+
+    // TODO / INVESTIGATE: Things would probably be simpler if these
+    // inherited from Register, Register64, and FloatRegister.
+
+    struct RegI32
+    {
+        RegI32() {}
+        explicit RegI32(Register reg) : reg(reg) {}
+        Register reg;
+        bool operator==(const RegI32& that) { return reg == that.reg; }
+        bool operator!=(const RegI32& that) { return reg != that.reg; }
+    };
+
+    struct RegI64
+    {
+        RegI64() : reg(Register::Invalid()) {}
+        explicit RegI64(Register64 reg) : reg(reg) {}
+        Register64 reg;
+        bool operator==(const RegI64& that) { return reg == that.reg; }
+        bool operator!=(const RegI64& that) { return reg != that.reg; }
+    };
+
+    struct RegF32
+    {
+        RegF32() {}
+        explicit RegF32(FloatRegister reg) : reg(reg) {}
+        FloatRegister reg;
+        bool operator==(const RegF32& that) { return reg == that.reg; }
+        bool operator!=(const RegF32& that) { return reg != that.reg; }
+    };
+
+    struct RegF64
+    {
+        RegF64() {}
+        explicit RegF64(FloatRegister reg) : reg(reg) {}
+        FloatRegister reg;
+        bool operator==(const RegF64& that) { return reg == that.reg; }
+        bool operator!=(const RegF64& that) { return reg != that.reg; }
+    };
+
+    struct AnyReg
+    {
+        AnyReg() { tag = NONE; }
+        explicit AnyReg(RegI32 r) { tag = I32; i32_ = r; }
+        explicit AnyReg(RegI64 r) { tag = I64; i64_ = r; }
+        explicit AnyReg(RegF32 r) { tag = F32; f32_ = r; }
+        explicit AnyReg(RegF64 r) { tag = F64; f64_ = r; }
+
+        RegI32 i32() { MOZ_ASSERT(tag == I32); return i32_; }
+        RegI64 i64() { MOZ_ASSERT(tag == I64); return i64_; }
+        RegF32 f32() { MOZ_ASSERT(tag == F32); return f32_; }
+        RegF64 f64() { MOZ_ASSERT(tag == F64); return f64_; }
+
+        union {
+            RegI32 i32_;
+            RegI64 i64_;
+            RegF32 f32_;
+            RegF64 f64_;
+        };
+        enum { NONE, I32, I64, F32, F64 } tag;
+    };
+
+    struct Local
+    {
+        Local() : type_(MIRType::None), offs_(UINT32_MAX) {}
+        Local(MIRType type, uint32_t offs) : type_(type), offs_(offs) {}
+
+        void init(MIRType type_, uint32_t offs_) {
+            this->type_ = type_;
+            this->offs_ = offs_;
+        }
+
+        MIRType  type_;              // Type of the value, or MIRType::None
+        uint32_t offs_;              // Zero-based frame offset of value, or UINT32_MAX
+
+        MIRType type() const { MOZ_ASSERT(type_ != MIRType::None); return type_; }
+        uint32_t offs() const { MOZ_ASSERT(offs_ != UINT32_MAX); return offs_; }
+    };
+
+    // Control node, representing labels and stack heights at join points.
+
+    struct Control
+    {
+        Control(uint32_t framePushed, uint32_t stackSize)
+            : label(nullptr),
+              otherLabel(nullptr),
+              framePushed(framePushed),
+              stackSize(stackSize)
+        {}
+
+        PooledLabel* label;
+        PooledLabel* otherLabel;        // Used for the "else" branch of if-then-else
+        uint32_t framePushed;           // From masm
+        uint32_t stackSize;             // Value stack height
+    };
+
+    // Volatile registers except ReturnReg.
+
+    static LiveRegisterSet VolatileReturnGPR;
+
+    // The baseline compiler will use OOL code more sparingly than
+    // Baldr since our code is not high performance and frills like
+    // code density and branch prediction friendliness will be less
+    // important.
+
+    class OutOfLineCode : public TempObject
+    {
+      private:
+        Label entry_;
+        Label rejoin_;
+
+      public:
+        Label* entry() { return &entry_; }
+        Label* rejoin() { return &rejoin_; }
+
+        void bind(MacroAssembler& masm) {
+            masm.bind(&entry_);
+        }
+
+        // Save volatile registers but not ReturnReg.
+
+        void saveVolatileReturnGPR(MacroAssembler& masm) {
+            masm.PushRegsInMask(BaseCompiler::VolatileReturnGPR);
+        }
+
+        // Restore volatile registers but not ReturnReg.
+
+        void restoreVolatileReturnGPR(MacroAssembler& masm) {
+            masm.PopRegsInMask(BaseCompiler::VolatileReturnGPR);
+        }
+
+        // The generate() method must be careful about register use
+        // because it will be invoked when there is a register
+        // assignment in the BaseCompiler that does not correspond
+        // to the available registers when the generated OOL code is
+        // executed.  The register allocator *must not* be called.
+        //
+        // The best strategy is for the creator of the OOL object to
+        // allocate all temps that the OOL code will need.
+        //
+        // Input, output, and temp registers are embedded in the OOL
+        // object and are known to the code generator.
+        //
+        // Scratch registers are available to use in OOL code.
+        //
+        // All other registers must be explicitly saved and restored
+        // by the OOL code before being used.
+
+        virtual void generate(MacroAssembler& masm) = 0;
+    };
+
+    const ModuleGeneratorData&  mg_;
+    BaseExprIter                iter_;
+    const FuncBytes&            func_;
+    size_t                      lastReadCallSite_;
+    TempAllocator&              alloc_;
+    const ValTypeVector&        locals_;         // Types of parameters and locals
+    int32_t                     localSize_;      // Size of local area in bytes (stable after beginFunction)
+    int32_t                     varLow_;         // Low byte offset of local area for true locals (not parameters)
+    int32_t                     varHigh_;        // High byte offset + 1 of local area for true locals
+    int32_t                     maxFramePushed_; // Max value of masm.framePushed() observed
+    ValTypeVector               SigDD_;
+    ValTypeVector               SigD_;
+    ValTypeVector               SigF_;
+    Label                       returnLabel_;
+    Label                       outOfLinePrologue_;
+    Label                       bodyLabel_;
+
+    FuncCompileResults&         compileResults_;
+    MacroAssembler&             masm;            // No '_' suffix - too tedious...
+
+    AllocatableGeneralRegisterSet availGPR_;
+    AllocatableFloatRegisterSet availFPU_;
+
+    TempObjectPool<PooledLabel> labelPool_;
+
+    Vector<Local, 8, SystemAllocPolicy> localInfo_;
+    Vector<OutOfLineCode*, 8, SystemAllocPolicy> outOfLine_;
+
+    // On specific platforms we sometimes need to use specific registers.
+
+#ifdef JS_CODEGEN_X64
+    RegI64 specific_rax;
+    RegI64 specific_rcx;
+    RegI64 specific_rdx;
+#endif
+
+#if defined(JS_CODEGEN_X64) || defined(JS_CODEGEN_X86)
+    RegI32 specific_eax;
+    RegI32 specific_ecx;
+    RegI32 specific_edx;
+#endif
+
+    // The join registers are used to carry values out of blocks.
+
+    RegI32 joinRegI32;
+    RegI64 joinRegI64;
+    RegF32 joinRegF32;
+    RegF64 joinRegF64;
+
+    // More members: see the stk_ and ctl_ vectors, defined below.
+
+  public:
+    BaseCompiler(const ModuleGeneratorData& mg,
+                 Decoder& decoder,
+                 const FuncBytes& func,
+                 const ValTypeVector& locals,
+                 FuncCompileResults& compileResults);
+
+    MOZ_MUST_USE
+    bool init();
+
+    void finish();
+
+    MOZ_MUST_USE
+    bool emitFunction();
+
+  private:
+
+    ////////////////////////////////////////////////////////////
+    //
+    // Out of line code management.
+
+    MOZ_MUST_USE
+    OutOfLineCode* addOutOfLineCode(OutOfLineCode* ool) {
+        if (ool && !outOfLine_.append(ool))
+            return nullptr;
+        return ool;
+    }
+
+    MOZ_MUST_USE
+    bool generateOutOfLineCode() {
+        for (uint32_t i = 0; i < outOfLine_.length(); i++) {
+            OutOfLineCode* ool = outOfLine_[i];
+            ool->bind(masm);
+            ool->generate(masm);
+        }
+
+        return !masm.oom();
+    }
+
+    ////////////////////////////////////////////////////////////
+    //
+    // The stack frame.
+
+    // SP-relative load and store.
+
+    int32_t localOffsetToSPOffset(int32_t offset) {
+        return masm.framePushed() - offset;
+    }
+
+    void storeToFrameI32(Register r, int32_t offset) {
+        masm.store32(r, Address(StackPointer, localOffsetToSPOffset(offset)));
+    }
+
+    void storeToFrameI64(Register64 r, int32_t offset) {
+#ifdef JS_CODEGEN_X64
+        masm.movq(r.reg, Operand(StackPointer, localOffsetToSPOffset(offset)));
+#else
+        MOZ_CRASH("BaseCompiler platform hook: storeToFrameI64");
+#endif
+    }
+
+    void storeToFrameF64(FloatRegister r, int32_t offset) {
+        masm.storeDouble(r, Address(StackPointer, localOffsetToSPOffset(offset)));
+    }
+
+    void storeToFrameF32(FloatRegister r, int32_t offset) {
+        masm.storeFloat32(r, Address(StackPointer, localOffsetToSPOffset(offset)));
+    }
+
+    void loadFromFrameI32(Register r, int32_t offset) {
+        masm.load32(Address(StackPointer, localOffsetToSPOffset(offset)), r);
+    }
+
+    void loadFromFrameI64(Register64 r, int32_t offset) {
+#ifdef JS_CODEGEN_X64
+        masm.movq(Operand(StackPointer, localOffsetToSPOffset(offset)), r.reg);
+#else
+        MOZ_CRASH("BaseCompiler platform hook: loadFromFrameI64");
+#endif
+    }
+
+    void loadFromFrameF64(FloatRegister r, int32_t offset) {
+        masm.loadDouble(Address(StackPointer, localOffsetToSPOffset(offset)), r);
+    }
+
+    void loadFromFrameF32(FloatRegister r, int32_t offset) {
+        masm.loadFloat32(Address(StackPointer, localOffsetToSPOffset(offset)), r);
+    }
+
+    // Stack-allocated local slots.
+
+    int32_t pushLocal(size_t nbytes) {
+        if (nbytes == 8)
+            localSize_ = AlignBytes(localSize_, 8);
+        else if (nbytes == 16)
+            localSize_ = AlignBytes(localSize_, 16);
+        localSize_ += nbytes;
+        return localSize_;          // Locals grow down so capture base address
+    }
+
+    int32_t frameOffsetFromSlot(uint32_t slot, MIRType type) {
+        MOZ_ASSERT(localInfo_[slot].type() == type);
+        return localInfo_[slot].offs();
+    }
+
+    ////////////////////////////////////////////////////////////
+    //
+    // Low-level register allocation.
+
+    bool isAvailable(Register r) {
+        return availGPR_.has(r);
+    }
+
+    bool hasGPR() {
+        return !availGPR_.empty();
+    }
+
+    void allocGPR(Register r) {
+        MOZ_ASSERT(isAvailable(r));
+        availGPR_.take(r);
+    }
+
+    Register allocGPR() {
+        MOZ_ASSERT(hasGPR());
+        return availGPR_.takeAny();
+    }
+
+    void freeGPR(Register r) {
+        availGPR_.add(r);
+    }
+
+    // Notes on float register allocation.
+    //
+    // The general rule in SpiderMonkey is that float registers can
+    // alias double registers, but there are predicates to handle
+    // exceptions to that rule: hasUnaliasedDouble() and
+    // hasMultiAlias().  The way aliasing actually works is platform
+    // dependent and exposed through the aliased(n, &r) predicate,
+    // etc.
+    //
+    //  - hasUnaliasedDouble(): on ARM VFPv3-D32 there are double
+    //    registers that cannot be treated as float.
+    //  - hasMultiAlias(): on ARM and MIPS a double register aliases
+    //    two float registers.
+    //  - notes in Architecture-arm.h indicate that when we use a
+    //    float register that aliases a double register we only use
+    //    the low float register, never the high float register.  I
+    //    think those notes lie, or at least are confusing.
+    //  - notes in Architecture-mips32.h suggest that the MIPS port
+    //    will use both low and high float registers except on the
+    //    Longsoon, which may be the only MIPS that's being tested, so
+    //    who knows what's working.
+    //  - SIMD is not yet implemented on ARM or MIPS so constraints
+    //    may change there.
+    //
+    // On some platforms (x86, x64, ARM64) but not all (ARM)
+    // ScratchFloat32Register is the same as ScratchDoubleRegister.
+    //
+    // It's a basic invariant of the AllocatableRegisterSet that it
+    // deals properly with aliasing of registers: if s0 or s1 are
+    // allocated then d0 is not allocatable; if s0 and s1 are freed
+    // individually then d0 becomes allocatable.
+
+    template<MIRType t>
+    FloatRegisters::SetType maskFromTypeFPU() {
+        static_assert(t == MIRType::Float32 || t == MIRType::Double, "Float mask type");
+        if (t == MIRType::Float32)
+            return FloatRegisters::AllSingleMask;
+        return FloatRegisters::AllDoubleMask;
+    }
+
+    template<MIRType t>
+    bool hasFPU() {
+        return !!(availFPU_.bits() & maskFromTypeFPU<t>());
+    }
+
+    bool isAvailable(FloatRegister r) {
+        return availFPU_.has(r);
+    }
+
+    void allocFPU(FloatRegister r) {
+        MOZ_ASSERT(isAvailable(r));
+        availFPU_.take(r);
+    }
+
+    template<MIRType t>
+    FloatRegister allocFPU() {
+        MOZ_ASSERT(hasFPU<t>());
+        FloatRegister r =
+            FloatRegisterSet::Intersect(FloatRegisterSet(availFPU_.bits()),
+                                        FloatRegisterSet(maskFromTypeFPU<t>())).getAny();
+        availFPU_.take(r);
+        return r;
+    }
+
+    void freeFPU(FloatRegister r) {
+        availFPU_.add(r);
+    }
+
+    ////////////////////////////////////////////////////////////
+    //
+    // Value stack and high-level register allocation.
+    //
+    // The value stack facilitates some on-the-fly register allocation
+    // and immediate-constant use.  It tracks constants, latent
+    // references to locals, register contents, and values on the CPU
+    // stack.
+    //
+    // The stack can be flushed to memory using sync().  This is handy
+    // to avoid problems with control flow and messy register usage
+    // patterns.
+
+    struct Stk
+    {
+        enum Kind
+        {
+            // The Mem opcodes are all clustered at the beginning to
+            // allow for a quick test within sync().
+            MemI32,               // 32-bit integer stack value ("offs")
+            MemI64,               // 64-bit integer stack value ("offs")
+            MemF32,               // 32-bit floating stack value ("offs")
+            MemF64,               // 64-bit floating stack value ("offs")
+
+            // The Local opcodes follow the Mem opcodes for a similar
+            // quick test within hasLocal().
+            LocalI32,             // Local int32 var ("slot")
+            LocalI64,             // Local int64 var ("slot")
+            LocalF32,             // Local float32 var ("slot")
+            LocalF64,             // Local double var ("slot")
+
+            RegisterI32,          // 32-bit integer register ("i32reg")
+            RegisterI64,          // 64-bit integer register ("i64reg")
+            RegisterF32,          // 32-bit floating register ("f32reg")
+            RegisterF64,          // 64-bit floating register ("f64reg")
+
+            ConstI32,             // 32-bit integer constant ("i32val")
+            ConstI64,             // 64-bit integer constant ("i64val")
+            ConstF32,             // 32-bit floating constant ("f32val")
+            ConstF64,             // 64-bit floating constant ("f64val")
+
+            None                  // Uninitialized or void
+        };
+
+        Kind kind_;
+
+        static const Kind MemLast = MemF64;
+        static const Kind LocalLast = LocalF64;
+
+        union {
+            RegI32   i32reg_;
+            RegI64   i64reg_;
+            RegF32   f32reg_;
+            RegF64   f64reg_;
+            int32_t  i32val_;
+            int64_t  i64val_;
+            float    f32val_;
+            double   f64val_;
+            uint32_t slot_;
+            uint32_t offs_;
+        };
+
+        Stk() { kind_ = None; }
+
+        Kind kind() const { return kind_; }
+
+        RegI32   i32reg() const { MOZ_ASSERT(kind_ == RegisterI32); return i32reg_; }
+        RegI64   i64reg() const { MOZ_ASSERT(kind_ == RegisterI64); return i64reg_; }
+        RegF32   f32reg() const { MOZ_ASSERT(kind_ == RegisterF32); return f32reg_; }
+        RegF64   f64reg() const { MOZ_ASSERT(kind_ == RegisterF64); return f64reg_; }
+        int32_t  i32val() const { MOZ_ASSERT(kind_ == ConstI32); return i32val_; }
+        int64_t  i64val() const { MOZ_ASSERT(kind_ == ConstI64); return i64val_; }
+        float    f32val() const { MOZ_ASSERT(kind_ == ConstF32); return f32val_; }
+        double   f64val() const { MOZ_ASSERT(kind_ == ConstF64); return f64val_; }
+        uint32_t slot() const { MOZ_ASSERT(kind_ > MemLast && kind_ <= LocalLast); return slot_; }
+        uint32_t offs() const { MOZ_ASSERT(kind_ <= MemLast); return offs_; }
+
+        void setI32Reg(RegI32 r) { kind_ = RegisterI32; i32reg_ = r; }
+        void setI64Reg(RegI64 r) { kind_ = RegisterI64; i64reg_ = r; }
+        void setF32Reg(RegF32 r) { kind_ = RegisterF32; f32reg_ = r; }
+        void setF64Reg(RegF64 r) { kind_ = RegisterF64; f64reg_ = r; }
+        void setI32Val(int32_t v) { kind_ = ConstI32; i32val_ = v; }
+        void setI64Val(int64_t v) { kind_ = ConstI64; i64val_ = v; }
+        void setF32Val(float v) { kind_ = ConstF32; f32val_ = v; }
+        void setF64Val(double v) { kind_ = ConstF64; f64val_ = v; }
+        void setSlot(Kind k, uint32_t v) { MOZ_ASSERT(k > MemLast && k <= LocalLast); kind_ = k; slot_ = v; }
+        void setOffs(Kind k, uint32_t v) { MOZ_ASSERT(k <= MemLast); kind_ = k; offs_ = v; }
+    };
+
+    Vector<Stk, 8, SystemAllocPolicy> stk_;
+
+    Stk& push() {
+        stk_.infallibleEmplaceBack(Stk());
+        return stk_.back();
+    }
+
+    Register64 invalidRegister64() {
+#ifdef JS_PUNBOX64
+        return Register64(Register::Invalid());
+#else
+        MOZ_CRASH("BaseCompiler platform hook: invalidRegister64");
+#endif
+    }
+
+    RegI32 invalidI32() {
+        return RegI32(Register::Invalid());
+    }
+
+    RegI64 invalidI64() {
+        return RegI64(invalidRegister64());
+    }
+
+    RegF64 invalidF64() {
+        return RegF64(InvalidFloatReg);
+    }
+
+    RegI32 fromI64(RegI64 r) {
+#ifdef JS_PUNBOX64
+        return RegI32(r.reg.reg);
+#else
+        MOZ_CRASH("BaseCompiler platform hook: fromI64");
+#endif
+    }
+
+    RegI64 fromI32(RegI32 r) {
+#ifdef JS_PUNBOX64
+        return RegI64(Register64(r.reg));
+#else
+        MOZ_CRASH("BaseCompiler platform hook: fromI32");
+#endif
+    }
+
+    void freeI32(RegI32 r) {
+        freeGPR(r.reg);
+    }
+
+    void freeI64(RegI64 r) {
+#ifdef JS_PUNBOX64
+        freeGPR(r.reg.reg);
+#else
+        MOZ_CRASH("BaseCompiler platform hook: freeI64");
+#endif
+    }
+
+    void freeF64(RegF64 r) {
+        freeFPU(r.reg);
+    }
+
+    void freeF32(RegF32 r) {
+        freeFPU(r.reg);
+    }
+
+    MOZ_MUST_USE
+    RegI32 needI32() {
+        if (!hasGPR())
+            sync();            // TODO / OPTIMIZE: improve this
+        return RegI32(allocGPR());
+    }
+
+    void needI32(RegI32 specific) {
+        if (!isAvailable(specific.reg))
+            sync();            // TODO / OPTIMIZE: improve this
+        allocGPR(specific.reg);
+    }
+
+    // TODO / OPTIMIZE: need2xI32() can be optimized along with needI32()
+    // to avoid sync().
+
+    void need2xI32(RegI32 r0, RegI32 r1) {
+        needI32(r0);
+        needI32(r1);
+    }
+
+    MOZ_MUST_USE
+    RegI64 needI64() {
+        if (!hasGPR())
+            sync();            // TODO / OPTIMIZE: improve this
+        return RegI64(Register64(allocGPR()));
+    }
+
+    void needI64(RegI64 specific) {
+#ifdef JS_PUNBOX64
+        if (!isAvailable(specific.reg.reg))
+            sync();            // TODO / OPTIMIZE: improve this
+        allocGPR(specific.reg.reg);
+#else
+        MOZ_CRASH("BaseCompiler platform hook: needI64");
+#endif
+    }
+
+    void need2xI64(RegI64 r0, RegI64 r1) {
+        needI64(r0);
+        needI64(r1);
+    }
+
+    MOZ_MUST_USE
+    RegF32 needF32() {
+        if (!hasFPU<MIRType::Float32>())
+            sync();            // TODO / OPTIMIZE: improve this
+        return RegF32(allocFPU<MIRType::Float32>());
+    }
+
+    void needF32(RegF32 specific) {
+        if (!isAvailable(specific.reg))
+            sync();            // TODO / OPTIMIZE: improve this
+        allocFPU(specific.reg);
+    }
+
+    MOZ_MUST_USE
+    RegF64 needF64() {
+        if (!hasFPU<MIRType::Double>())
+            sync();            // TODO / OPTIMIZE: improve this
+        return RegF64(allocFPU<MIRType::Double>());
+    }
+
+    void needF64(RegF64 specific) {
+        if (!isAvailable(specific.reg))
+            sync();            // TODO / OPTIMIZE: improve this
+        allocFPU(specific.reg);
+    }
+
+    void moveI32(RegI32 src, RegI32 dest) {
+        if (src != dest)
+            masm.move32(src.reg, dest.reg);
+    }
+
+    void moveI64(RegI64 src, RegI64 dest) {
+        if (src != dest)
+            masm.move64(src.reg, dest.reg);
+    }
+
+    void moveF64(RegF64 src, RegF64 dest) {
+        if (src != dest)
+            masm.moveDouble(src.reg, dest.reg);
+    }
+
+    void moveF32(RegF32 src, RegF32 dest) {
+        if (src != dest)
+            masm.moveFloat32(src.reg, dest.reg);
+    }
+
+    void setI64(int64_t v, RegI64 r) {
+#ifdef JS_PUNBOX64
+        masm.move64(Imm64(v), r.reg);
+#else
+        MOZ_CRASH("BaseCompiler platform hook: setI64");
+#endif
+    }
+
+    void loadConstI32(Register r, Stk& src) {
+        masm.mov(ImmWord((uint32_t)src.i32val() & 0xFFFFFFFFU), r);
+    }
+
+    void loadMemI32(Register r, Stk& src) {
+        loadFromFrameI32(r, src.offs());
+    }
+
+    void loadLocalI32(Register r, Stk& src) {
+        loadFromFrameI32(r, frameOffsetFromSlot(src.slot(), MIRType::Int32));
+    }
+
+    void loadRegisterI32(Register r, Stk& src) {
+        if (src.i32reg().reg != r)
+            masm.move32(src.i32reg().reg, r);
+    }
+
+    void loadI32(Register r, Stk& src) {
+        switch (src.kind()) {
+          case Stk::ConstI32:
+            loadConstI32(r, src);
+            break;
+          case Stk::MemI32:
+            loadMemI32(r, src);
+            break;
+          case Stk::LocalI32:
+            loadLocalI32(r, src);
+            break;
+          case Stk::RegisterI32:
+            loadRegisterI32(r, src);
+            break;
+          case Stk::None:
+            break;
+          default:
+            MOZ_CRASH("Compiler bug: Expected int on stack");
+        }
+    }
+
+    // TODO / OPTIMIZE: Refactor loadI64, loadF64, and loadF32 in the
+    // same way as loadI32 to avoid redundant dispatch in callers of
+    // these load() functions.
+
+    void loadI64(Register64 r, Stk& src) {
+        switch (src.kind()) {
+          case Stk::ConstI64:
+            masm.move64(Imm64(src.i64val()), r);
+            break;
+          case Stk::MemI64:
+            loadFromFrameI64(r, src.offs());
+            break;
+          case Stk::LocalI64:
+            loadFromFrameI64(r, frameOffsetFromSlot(src.slot(), MIRType::Int64));
+            break;
+          case Stk::RegisterI64:
+            if (src.i64reg().reg != r)
+                masm.move64(src.i64reg().reg, r);
+            break;
+          case Stk::None:
+            break;
+          default:
+            MOZ_CRASH("Compiler bug: Expected int on stack");
+        }
+    }
+
+    void loadF64(FloatRegister r, Stk& src) {
+        switch (src.kind()) {
+          case Stk::ConstF64:
+            masm.loadConstantFloatingPoint(src.f64val(), 0.0f, r, MIRType::Double);
+            break;
+          case Stk::MemF64:
+            loadFromFrameF64(r, src.offs());
+            break;
+          case Stk::LocalF64:
+            loadFromFrameF64(r, frameOffsetFromSlot(src.slot(), MIRType::Double));
+            break;
+          case Stk::RegisterF64:
+            if (src.f64reg().reg != r)
+                masm.moveDouble(src.f64reg().reg, r);
+            break;
+          case Stk::None:
+            break;
+          default:
+            MOZ_CRASH("Compiler bug: expected double on stack");
+        }
+    }
+
+    void loadF32(FloatRegister r, Stk& src) {
+        switch (src.kind()) {
+          case Stk::ConstF32:
+            masm.loadConstantFloatingPoint(0.0, src.f32val(), r, MIRType::Float32);
+            break;
+          case Stk::MemF32:
+            loadFromFrameF32(r, src.offs());
+            break;
+          case Stk::LocalF32:
+            loadFromFrameF32(r, frameOffsetFromSlot(src.slot(), MIRType::Float32));
+            break;
+          case Stk::RegisterF32:
+            if (src.f32reg().reg != r)
+                masm.moveFloat32(src.f32reg().reg, r);
+            break;
+          case Stk::None:
+            break;
+          default:
+            MOZ_CRASH("Compiler bug: expected float on stack");
+        }
+    }
+
+    // Flush all local and register value stack elements to memory.
+    //
+    // TODO / OPTIMIZE: As this is fairly expensive and causes worse
+    // code to be emitted subsequently, it is useful to avoid calling
+    // it.
+    //
+    // Some optimization has been done already.  Remaining
+    // opportunities:
+    //
+    //  - It would be interesting to see if we can specialize it
+    //    before calls with particularly simple signatures, or where
+    //    we can do parallel assignment of register arguments, or
+    //    similar.  See notes in emitCall().
+    //
+    //  - Operations that need specific registers: multiply, quotient,
+    //    remainder, will tend to sync because the registers we need
+    //    will tend to be allocated.  We may be able to avoid that by
+    //    prioritizing registers differently (takeLast instead of
+    //    takeFirst) but we may also be able to allocate an unused
+    //    register on demand to free up one we need, thus avoiding the
+    //    sync.  That type of fix would go into needI32().
+
+    void sync() {
+        size_t start = 0;
+        size_t lim = stk_.length();
+
+        for (size_t i = lim; i > 0; i--) {
+            // Memory opcodes are first in the enum, single check against MemLast is fine.
+            if (stk_[i-1].kind() <= Stk::MemLast) {
+                start = i;
+                break;
+            }
+        }
+
+        for (size_t i = start; i < lim; i++) {
+            Stk& v = stk_[i];
+            switch (v.kind()) {
+              case Stk::LocalI32: {
+#if defined(JS_CODEGEN_X64) || defined(JS_CODEGEN_X86) || defined(JS_CODEGEN_ARM)
+                ScratchRegisterScope scratch(masm);
+                loadLocalI32(scratch, v);
+                masm.Push(scratch);
+#else
+                MOZ_CRASH("BaseCompiler platform hook: sync LocalI32");
+#endif
+                v.setOffs(Stk::MemI32, masm.framePushed());
+                break;
+              }
+              case Stk::RegisterI32: {
+                masm.Push(v.i32reg().reg);
+                freeI32(v.i32reg());
+                v.setOffs(Stk::MemI32, masm.framePushed());
+                break;
+              }
+              case Stk::LocalI64: {
+#ifdef JS_PUNBOX64
+                ScratchRegisterScope scratch(masm);
+                loadI64(Register64(scratch), v);
+                masm.Push(scratch);
+#else
+                MOZ_CRASH("BaseCompiler platform hook: sync LocalI64");
+#endif
+                v.setOffs(Stk::MemI64, masm.framePushed());
+                break;
+              }
+              case Stk::RegisterI64: {
+#ifdef JS_PUNBOX64
+                masm.Push(v.i64reg().reg.reg);
+                freeI64(v.i64reg());
+#else
+                MOZ_CRASH("BaseCompiler platform hook: sync RegI64");
+#endif
+                v.setOffs(Stk::MemI64, masm.framePushed());
+                break;
+              }
+              case Stk::LocalF64: {
+#if defined(JS_CODEGEN_X86) || defined(JS_CODEGEN_X64) || defined(JS_CODEGEN_ARM)
+                ScratchDoubleScope scratch(masm);
+                loadF64(scratch, v);
+                masm.Push(scratch);
+#else
+                MOZ_CRASH("BaseCompiler platform hook: sync LocalF64");
+#endif
+                v.setOffs(Stk::MemF64, masm.framePushed());
+                break;
+              }
+              case Stk::RegisterF64: {
+                masm.Push(v.f64reg().reg);
+                freeF64(v.f64reg());
+                v.setOffs(Stk::MemF64, masm.framePushed());
+                break;
+              }
+              case Stk::LocalF32: {
+#if defined(JS_CODEGEN_X86) || defined(JS_CODEGEN_X64) || defined(JS_CODEGEN_ARM)
+                ScratchFloat32Scope scratch(masm);
+                loadF32(scratch, v);
+                masm.Push(scratch);
+#else
+                MOZ_CRASH("BaseCompiler platform hook: sync LocalF32");
+#endif
+                v.setOffs(Stk::MemF32, masm.framePushed());
+                break;
+              }
+              case Stk::RegisterF32: {
+                masm.Push(v.f32reg().reg);
+                freeF32(v.f32reg());
+                v.setOffs(Stk::MemF32, masm.framePushed());
+                break;
+              }
+              default: {
+                break;
+              }
+            }
+        }
+
+        maxFramePushed_ = Max(maxFramePushed_, int32_t(masm.framePushed()));
+    }
+
+    // This is an optimization used to avoid calling sync() for
+    // setLocal(): if the local does not exist unresolved on the stack
+    // then we can skip the sync.
+
+    bool hasLocal(uint32_t slot) {
+        for (size_t i = stk_.length(); i > 0; i--) {
+            // Memory opcodes are first in the enum, single check against MemLast is fine.
+            Stk::Kind kind = stk_[i-1].kind();
+            if (kind <= Stk::MemLast)
+                return false;
+
+            // Local opcodes follow memory opcodes in the enum, single check against
+            // LocalLast is sufficient.
+            if (kind <= Stk::LocalLast && stk_[i-1].slot() == slot)
+                return true;
+        }
+        return false;
+    }
+
+    void syncLocal(uint32_t slot) {
+        if (hasLocal(slot))
+            sync();            // TODO / OPTIMIZE: Improve this?
+    }
+
+    // Push the register r onto the stack.
+
+    void pushI32(RegI32 r) {
+        MOZ_ASSERT(!isAvailable(r.reg));
+        Stk& x = push();
+        x.setI32Reg(r);
+    }
+
+    void pushI64(RegI64 r) {
+#ifdef JS_PUNBOX64
+        MOZ_ASSERT(!isAvailable(r.reg.reg));
+        Stk& x = push();
+        x.setI64Reg(r);
+#else
+        MOZ_CRASH("BaseCompiler platform hook: pushI64");
+#endif
+    }
+
+    void pushF64(RegF64 r) {
+        MOZ_ASSERT(!isAvailable(r.reg));
+        Stk& x = push();
+        x.setF64Reg(r);
+    }
+
+    void pushF32(RegF32 r) {
+        MOZ_ASSERT(!isAvailable(r.reg));
+        Stk& x = push();
+        x.setF32Reg(r);
+    }
+
+    // Push the value onto the stack.
+
+    void pushI32(int32_t v) {
+        Stk& x = push();
+        x.setI32Val(v);
+    }
+
+    void pushI64(int64_t v) {
+        Stk& x = push();
+        x.setI64Val(v);
+    }
+
+    void pushF64(double v) {
+        Stk& x = push();
+        x.setF64Val(v);
+    }
+
+    void pushF32(float v) {
+        Stk& x = push();
+        x.setF32Val(v);
+    }
+
+    // Push the local slot onto the stack.  The slot will not be read
+    // here; it will be read when it is consumed, or when a side
+    // effect to the slot forces its value to be saved.
+
+    void pushLocalI32(uint32_t slot) {
+        Stk& x = push();
+        x.setSlot(Stk::LocalI32, slot);
+    }
+
+    void pushLocalI64(uint32_t slot) {
+        Stk& x = push();
+        x.setSlot(Stk::LocalI64, slot);
+    }
+
+    void pushLocalF64(uint32_t slot) {
+        Stk& x = push();
+        x.setSlot(Stk::LocalF64, slot);
+    }
+
+    void pushLocalF32(uint32_t slot) {
+        Stk& x = push();
+        x.setSlot(Stk::LocalF32, slot);
+    }
+
+    // Push a void value.  Like constants this is never flushed to memory,
+    // it just helps maintain the invariants of the type system.
+
+    void pushVoid() {
+        push();
+    }
+
+    // PRIVATE.  Call only from other popI32() variants.
+    // v must be the stack top.
+
+    void popI32(Stk& v, RegI32 r) {
+        switch (v.kind()) {
+          case Stk::ConstI32:
+            loadConstI32(r.reg, v);
+            break;
+          case Stk::LocalI32:
+            loadLocalI32(r.reg, v);
+            break;
+          case Stk::MemI32:
+            masm.Pop(r.reg);
+            break;
+          case Stk::RegisterI32:
+            moveI32(v.i32reg(), r);
+            break;
+          case Stk::None:
+            // This case crops up in situations where there's unreachable code that
+            // the type system interprets as "generating" a value of the correct type:
+            //
+            //   (if (return) E1 E2)                    type is type(E1) meet type(E2)
+            //   (if E (unreachable) (i32.const 1))     type is int
+            //   (if E (i32.const 1) (unreachable))     type is int
+            //
+            // It becomes silly to handle this throughout the code, so just handle it
+            // here even if that means weaker run-time checking.
+            break;
+          default:
+            MOZ_CRASH("Compiler bug: expected int on stack");
+        }
+    }
+
+    MOZ_MUST_USE
+    RegI32 popI32() {
+        Stk& v = stk_.back();
+        RegI32 r;
+        if (v.kind() == Stk::RegisterI32)
+            r = v.i32reg();
+        else
+            popI32(v, (r = needI32()));
+        stk_.popBack();
+        return r;
+    }
+
+    RegI32 popI32(RegI32 specific) {
+        Stk& v = stk_.back();
+
+        if (!(v.kind() == Stk::RegisterI32 && v.i32reg() == specific)) {
+            needI32(specific);
+            popI32(v, specific);
+            if (v.kind() == Stk::RegisterI32)
+                freeI32(v.i32reg());
+        }
+
+        stk_.popBack();
+        return specific;
+    }
+
+    // PRIVATE.  Call only from other popI64() variants.
+    // v must be the stack top.
+
+    void popI64(Stk& v, RegI64 r) {
+        // TODO / OPTIMIZE: avoid loadI64() here
+        switch (v.kind()) {
+          case Stk::ConstI64:
+          case Stk::LocalI64:
+            loadI64(r.reg, v);
+            break;
+          case Stk::MemI64:
+#ifdef JS_PUNBOX64
+            masm.Pop(r.reg.reg);
+#else
+            MOZ_CRASH("BaseCompiler platform hook: popI64");
+#endif
+            break;
+          case Stk::RegisterI64:
+            moveI64(v.i64reg(), r);
+            break;
+          case Stk::None:
+            // See popI32()
+            break;
+          default:
+            MOZ_CRASH("Compiler bug: expected long on stack");
+        }
+    }
+
+    MOZ_MUST_USE
+    RegI64 popI64() {
+        Stk& v = stk_.back();
+        RegI64 r;
+        if (v.kind() == Stk::RegisterI64)
+            r = v.i64reg();
+        else
+            popI64(v, (r = needI64()));
+        stk_.popBack();
+        return r;
+    }
+
+    RegI64 popI64(RegI64 specific) {
+        Stk& v = stk_.back();
+
+        if (!(v.kind() == Stk::RegisterI64 && v.i64reg() == specific)) {
+            needI64(specific);
+            popI64(v, specific);
+            if (v.kind() == Stk::RegisterI64)
+                freeI64(v.i64reg());
+        }
+
+        stk_.popBack();
+        return specific;
+    }
+
+    // PRIVATE.  Call only from other popF64() variants.
+    // v must be the stack top.
+
+    void popF64(Stk& v, RegF64 r) {
+        // TODO / OPTIMIZE: avoid loadF64 here
+        switch (v.kind()) {
+          case Stk::ConstF64:
+          case Stk::LocalF64:
+            loadF64(r.reg, v);
+            break;
+          case Stk::MemF64:
+#if defined(JS_CODEGEN_X86) || defined(JS_CODEGEN_X64)
+            masm.Pop(r.reg);
+#else
+            // Strangely, ARM has no Pop(FloatRegister)
+            MOZ_CRASH("BaseCompiler platform hook: popD");
+#endif
+            break;
+          case Stk::RegisterF64:
+            moveF64(v.f64reg(), r);
+            break;
+          case Stk::None:
+            // See popI32()
+            break;
+          default:
+            MOZ_CRASH("Compiler bug: expected double on stack");
+        }
+    }
+
+    MOZ_MUST_USE
+    RegF64 popF64() {
+        Stk& v = stk_.back();
+        RegF64 r;
+        if (v.kind() == Stk::RegisterF64)
+            r = v.f64reg();
+        else
+            popF64(v, (r = needF64()));
+        stk_.popBack();
+        return r;
+    }
+
+    RegF64 popF64(RegF64 specific) {
+        Stk& v = stk_.back();
+
+        if (!(v.kind() == Stk::RegisterF64 && v.f64reg() == specific)) {
+            needF64(specific);
+            popF64(v, specific);
+            if (v.kind() == Stk::RegisterF64)
+                freeF64(v.f64reg());
+        }
+
+        stk_.popBack();
+        return specific;
+    }
+
+    // PRIVATE.  Call only from other popF32() variants.
+    // v must be the stack top.
+
+    void popF32(Stk& v, RegF32 r) {
+        // TODO / OPTIMIZE: avoid loadF32 here
+        switch (v.kind()) {
+          case Stk::ConstF32:
+          case Stk::LocalF32:
+            loadF32(r.reg, v);
+            break;
+          case Stk::MemF32:
+#if defined(JS_CODEGEN_X86) || defined(JS_CODEGEN_X64)
+            masm.Pop(r.reg);
+#else
+            // Strangely, ARM has no Pop(FloatRegister)
+            MOZ_CRASH("BaseCompiler platform hook: popF");
+#endif
+            break;
+          case Stk::RegisterF32:
+            moveF32(v.f32reg(), r);
+            break;
+          case Stk::None:
+            // See popI32()
+            break;
+          default:
+            MOZ_CRASH("Compiler bug: expected float on stack");
+        }
+    }
+
+    MOZ_MUST_USE
+    RegF32 popF32() {
+        Stk& v = stk_.back();
+        RegF32 r;
+        if (v.kind() == Stk::RegisterF32)
+            r = v.f32reg();
+        else
+            popF32(v, (r = needF32()));
+        stk_.popBack();
+        return r;
+    }
+
+    RegF32 popF32(RegF32 specific) {
+        Stk& v = stk_.back();
+
+        if (!(v.kind() == Stk::RegisterF32 && v.f32reg() == specific)) {
+            needF32(specific);
+            popF32(v, specific);
+            if (v.kind() == Stk::RegisterF32)
+                freeF32(v.f32reg());
+        }
+
+        stk_.popBack();
+        return specific;
+    }
+
+    MOZ_MUST_USE
+    bool popConstI32(int32_t& c) {
+        Stk& v = stk_.back();
+        if (v.kind() != Stk::ConstI32)
+            return false;
+        c = v.i32val();
+        stk_.popBack();
+        return true;
+    }
+
+    // TODO / OPTIMIZE: At the moment we use ReturnReg for JoinReg.
+    // It is possible other choices would lead to better register
+    // allocation, as ReturnReg is often first in the register set and
+    // will be heavily wanted by the register allocator that uses
+    // takeFirst().
+    //
+    // Obvious options:
+    //  - pick a register at the back of the register set
+    //  - pick a random register per block (different blocks have
+    //    different join regs)
+    //
+    // On the other hand, we sync() before every block and only the
+    // JoinReg is live out of the block.  But on the way out, we
+    // currently pop the JoinReg before freeing regs to be discarded,
+    // so there is a real risk of some pointless shuffling there.  If
+    // we instead integrate the popping of the join reg into the
+    // popping of the stack we can just use the JoinReg as it will
+    // become available in that process.
+
+    MOZ_MUST_USE
+    AnyReg popJoinReg() {
+        switch (stk_.back().kind()) {
+          case Stk::RegisterI32:
+          case Stk::ConstI32:
+          case Stk::MemI32:
+          case Stk::LocalI32:
+            return AnyReg(popI32(joinRegI32));
+          case Stk::RegisterI64:
+          case Stk::ConstI64:
+          case Stk::MemI64:
+          case Stk::LocalI64:
+            return AnyReg(popI64(joinRegI64));
+          case Stk::RegisterF64:
+          case Stk::ConstF64:
+          case Stk::MemF64:
+          case Stk::LocalF64:
+            return AnyReg(popF64(joinRegF64));
+          case Stk::RegisterF32:
+          case Stk::ConstF32:
+          case Stk::MemF32:
+          case Stk::LocalF32:
+            return AnyReg(popF32(joinRegF32));
+          case Stk::None:
+            stk_.popBack();
+            return AnyReg();
+          default:
+            MOZ_CRASH("Compiler bug: unexpected value on stack");
+        }
+    }
+
+    void pushJoinReg(AnyReg r) {
+        switch (r.tag) {
+          case AnyReg::NONE:
+            break;
+          case AnyReg::I32:
+            pushI32(r.i32());
+            break;
+          case AnyReg::I64:
+            pushI64(r.i64());
+            break;
+          case AnyReg::F64:
+            pushF64(r.f64());
+            break;
+          case AnyReg::F32:
+            pushF32(r.f32());
+            break;
+        }
+    }
+
+    void freeJoinReg(AnyReg r) {
+        switch (r.tag) {
+          case AnyReg::NONE:
+            break;
+          case AnyReg::I32:
+            freeI32(r.i32());
+            break;
+          case AnyReg::I64:
+            freeI64(r.i64());
+            break;
+          case AnyReg::F64:
+            freeF64(r.f64());
+            break;
+          case AnyReg::F32:
+            freeF32(r.f32());
+            break;
+        }
+    }
+
+    // Return the amount of execution stack consumed by the top numval
+    // values on the value stack.
+
+    size_t stackConsumed(size_t numval) {
+        size_t size = 0;
+        MOZ_ASSERT(numval <= stk_.length());
+        for (uint32_t i = stk_.length()-1; numval > 0; numval--, i--) {
+#if defined(JS_CODEGEN_X64) || defined(JS_CODEGEN_X86)
+            Stk& v = stk_[i];
+            switch (v.kind()) {
+              // The size computations come from the implementations of Push()
+              // in MacroAssembler-x86-shared.cpp.
+              case Stk::MemI32: size += sizeof(intptr_t); break;
+              case Stk::MemI64: size += sizeof(int64_t); break;
+              case Stk::MemF64: size += sizeof(double); break;
+              case Stk::MemF32: size += sizeof(double); break;
+              default: break;
+            }
+#else
+            MOZ_CRASH("BaseCompiler platform hook: stackConsumed");
+#endif
+        }
+        return size;
+    }
+
+    void popValueStackTo(uint32_t stackSize) {
+        for (uint32_t i = stk_.length(); i > stackSize; i--) {
+            Stk& v = stk_[i-1];
+            switch (v.kind()) {
+              case Stk::RegisterI32:
+                freeI32(v.i32reg());
+                break;
+              case Stk::RegisterI64:
+                freeI64(v.i64reg());
+                break;
+              case Stk::RegisterF64:
+                freeF64(v.f64reg());
+                break;
+              case Stk::RegisterF32:
+                freeF32(v.f32reg());
+                break;
+              default:
+                break;
+            }
+        }
+        stk_.shrinkTo(stackSize);
+    }
+
+    void popValueStackBy(uint32_t items) {
+        popValueStackTo(stk_.length() - items);
+    }
+
+    // Before branching to an outer control label, pop the execution
+    // stack to the level expected by that region, but do not free the
+    // stack as that will happen as compilation leaves the block.
+
+    void popStackBeforeBranch(uint32_t framePushed) {
+        uint32_t frameHere = masm.framePushed();
+        if (frameHere > framePushed)
+            masm.addPtr(ImmWord(frameHere - framePushed), StackPointer);
+    }
+
+    // Before exiting a nested control region, pop the execution stack
+    // to the level expected by the nesting region, and free the
+    // stack.
+
+    void popStackOnBlockExit(uint32_t framePushed) {
+        uint32_t frameHere = masm.framePushed();
+        if (frameHere > framePushed)
+            masm.freeStack(frameHere - framePushed);
+    }
+
+    // Peek at the stack, for calls.
+
+    Stk& peek(uint32_t relativeDepth) {
+        return stk_[stk_.length()-1-relativeDepth];
+    }
+
+    ////////////////////////////////////////////////////////////
+    //
+    // Control stack
+
+    Vector<Control, 8, SystemAllocPolicy> ctl_;
+
+    MOZ_MUST_USE
+    bool pushControl(UniquePooledLabel* label, UniquePooledLabel* otherLabel = nullptr) {
+        uint32_t framePushed = masm.framePushed();
+        uint32_t stackSize = stk_.length();
+
+        // Always a void value at the beginning of a block, ensures
+        // stack is never empty even if the block has no expressions.
+        pushVoid();
+
+        if (!ctl_.emplaceBack(Control(framePushed, stackSize)))
+            return false;
+        if (label)
+            ctl_.back().label = label->release();
+        if (otherLabel)
+            ctl_.back().otherLabel = otherLabel->release();
+        return true;
+    }
+
+    void popControl() {
+        Control last = ctl_.popCopy();
+        if (last.label)
+            freeLabel(last.label);
+        if (last.otherLabel)
+            freeLabel(last.otherLabel);
+    }
+
+    Control& controlItem(uint32_t relativeDepth) {
+        return ctl_[ctl_.length() - 1 - relativeDepth];
+    }
+
+    MOZ_MUST_USE
+    PooledLabel* newLabel() {
+        PooledLabel* candidate = labelPool_.allocate();
+        if (!candidate)
+            return nullptr;
+        return new (candidate) PooledLabel(this);
+    }
+
+    void freeLabel(PooledLabel* label) {
+        label->~PooledLabel();
+        labelPool_.free(label);
+    }
+
+    //////////////////////////////////////////////////////////////////////
+    //
+    // Function prologue and epilogue.
+
+    void beginFunction() {
+        JitSpew(JitSpew_Codegen, "# Emitting wasm baseline code");
+
+        wasm::GenerateFunctionPrologue(masm, localSize_, mg_.funcSigIndex(func_.index()),
+                                       &compileResults_.offsets());
+
+        MOZ_ASSERT(masm.framePushed() == uint32_t(localSize_));
+
+        maxFramePushed_ = localSize_;
+
+        // We won't know until after we've generated code how big the
+        // frame will be (we may need arbitrary spill slots and
+        // outgoing param slots) so branch to code emitted after the
+        // function body that will perform the check.
+        //
+        // Code there will also assume that the fixed-size stack frame
+        // has been allocated.
+
+        masm.jump(&outOfLinePrologue_);
+        masm.bind(&bodyLabel_);
+
+        // Copy arguments from registers to stack.
+
+        const ValTypeVector& args = func_.sig().args();
+
+        for (ABIArgIter<const ValTypeVector> i(args); !i.done(); i++) {
+            Local& l = localInfo_[i.index()];
+            switch (i.mirType()) {
+              case MIRType::Int32:
+                if (i->argInRegister())
+                    storeToFrameI32(i->gpr(), l.offs());
+                break;
+              case MIRType::Int64:
+                if (i->argInRegister())
+                    storeToFrameI64(Register64(i->gpr()), l.offs());
+                break;
+              case MIRType::Double:
+                if (i->argInRegister())
+                    storeToFrameF64(i->fpu(), l.offs());
+                break;
+              case MIRType::Float32:
+                if (i->argInRegister())
+                    storeToFrameF32(i->fpu(), l.offs());
+                break;
+              default:
+                MOZ_CRASH("Function argument type");
+            }
+        }
+
+        // Initialize the stack locals to zero.
+        //
+        // TODO / OPTIMIZE: on x64, at least, scratch will be a 64-bit
+        // register and we can move 64 bits at a time.
+        //
+        // TODO / OPTIMIZE: On SSE2 or better SIMD systems we may be
+        // able to store 128 bits at a time.  (I suppose on some
+        // systems we have 512-bit SIMD for that matter.)
+        //
+        // TODO / OPTIMIZE: if we have only one initializing store
+        // then it's better to store a zero literal, probably.
+
+        if (varLow_ < varHigh_) {
+#if defined(JS_CODEGEN_X64) || defined(JS_CODEGEN_X86) || defined(JS_CODEGEN_ARM)
+            ScratchRegisterScope scratch(masm);
+            masm.mov(ImmWord(0), scratch);
+            for (int32_t i = varLow_ ; i < varHigh_ ; i+=4)
+                storeToFrameI32(scratch, i+4);
+#else
+            MOZ_CRASH("BaseCompiler platform hook: init frame");
+#endif
+        }
+    }
+
+    bool endFunction() {
+        // A frame greater than 256KB is implausible, probably an attack,
+        // so bail out.
+
+        if (maxFramePushed_ > 256 * 1024)
+            return false;
+
+        // Out-of-line prologue.  Assumes that the in-line prologue has
+        // been executed and that a frame of size = localSize_ + sizeof(AsmJSFrame)
+        // has been allocated.
+
+        masm.bind(&outOfLinePrologue_);
+
+        MOZ_ASSERT(maxFramePushed_ >= localSize_);
+
+        // ABINonArgReg0 != ScratchReg, which can be used by branchPtr().
+
+        masm.movePtr(masm.getStackPointer(), ABINonArgReg0);
+        masm.subPtr(Imm32(maxFramePushed_ - localSize_), ABINonArgReg0);
+        masm.branchPtr(Assembler::Below,
+                       SymbolicAddress::StackLimit,
+                       ABINonArgReg0,
+                       &bodyLabel_);
+
+
+        // The stack overflow stub assumes that only sizeof(AsmJSFrame) bytes
+        // have been pushed. The overflow check occurs after incrementing by
+        // localSize_, so pop that before jumping to the overflow exit.
+
+        masm.addToStackPtr(Imm32(localSize_));
+        masm.jump(wasm::JumpTarget::StackOverflow);
+
+        masm.bind(&returnLabel_);
+
+        wasm::GenerateFunctionEpilogue(masm, localSize_, &compileResults_.offsets());
+
+#if defined(JS_ION_PERF)
+        // FIXME - profiling code missing
+
+        // Note the end of the inline code and start of the OOL code.
+        //gen->perfSpewer().noteEndInlineCode(masm);
+#endif
+
+        if (!generateOutOfLineCode())
+            return false;
+
+        compileResults_.offsets().end = masm.currentOffset();
+
+        return true;
+    }
+
+    //////////////////////////////////////////////////////////////////////
+    //
+    // Calls.
+
+    struct FunctionCall
+    {
+        explicit FunctionCall(uint32_t lineOrBytecode)
+          : lineOrBytecode_(lineOrBytecode),
+            callSavesMachineState_(false),
+            machineStateAreaSize_(0),
+            frameAlignAdjustment_(0),
+            stackArgAreaSize_(0),
+            calleePopsArgs_(false)
+        {}
+
+        uint32_t lineOrBytecode_;
+        ABIArgGenerator abi_;
+        bool callSavesMachineState_;
+        size_t machineStateAreaSize_;
+        size_t frameAlignAdjustment_;
+        size_t stackArgAreaSize_;
+
+        // TODO / INVESTIGATE: calleePopsArgs_ is unused on x86, x64,
+        // always false at present, certainly not tested.
+
+        bool calleePopsArgs_;
+    };
+
+    void beginCall(FunctionCall& call, bool escapesSandbox)
+    {
+        call.callSavesMachineState_ = escapesSandbox;
+        if (call.callSavesMachineState_) {
+#if defined(JS_CODEGEN_X64)
+            call.machineStateAreaSize_ = 16; // Save HeapReg
+#elif defined(JS_CODEGEN_X86)
+            // Nothing
+#else
+            MOZ_CRASH("BaseCompiler platform hook: beginCall");
+#endif
+        }
+
+        call.frameAlignAdjustment_ = ComputeByteAlignment(masm.framePushed() + sizeof(AsmJSFrame),
+                                                          ABIStackAlignment);
+    }
+
+    void endCall(FunctionCall& call)
+    {
+        if (call.machineStateAreaSize_ || call.frameAlignAdjustment_) {
+            int size = call.calleePopsArgs_ ? 0 : call.stackArgAreaSize_;
+            if (call.callSavesMachineState_) {
+#if defined(JS_CODEGEN_X64)
+                masm.loadPtr(Address(StackPointer, size + 8), HeapReg);
+#elif defined(JS_CODEGEN_X86)
+                // Nothing
+#else
+                MOZ_CRASH("BaseCompiler platform hook: endCall");
+#endif
+            }
+            masm.freeStack(size + call.machineStateAreaSize_ + call.frameAlignAdjustment_);
+        } else if (!call.calleePopsArgs_) {
+            masm.freeStack(call.stackArgAreaSize_);
+        }
+    }
+
+    // TODO / OPTIMIZE: This is expensive; let's roll the iterator
+    // walking into the walking done for passArg.  See comments in
+    // passArg.
+
+    size_t stackArgAreaSize(const ValTypeVector& args) {
+        ABIArgIter<const ValTypeVector> i(args);
+        while (!i.done())
+            i++;
+        return AlignBytes(i.stackBytesConsumedSoFar(), 16);
+    }
+
+    void startCallArgs(FunctionCall& call, size_t stackArgAreaSize)
+    {
+        call.stackArgAreaSize_ = stackArgAreaSize;
+        if (call.machineStateAreaSize_ || call.frameAlignAdjustment_) {
+            masm.reserveStack(stackArgAreaSize + call.machineStateAreaSize_ + call.frameAlignAdjustment_);
+            if (call.callSavesMachineState_) {
+#if defined(JS_CODEGEN_X64)
+                masm.storePtr(HeapReg, Address(StackPointer, stackArgAreaSize + 8));
+#elif defined(JS_CODEGEN_X86)
+                // Nothing
+#else
+                MOZ_CRASH("BaseCompiler platform hook: startCallArgs");
+#endif
+            }
+        } else if (stackArgAreaSize > 0) {
+            masm.reserveStack(stackArgAreaSize);
+        }
+    }
+
+    // TODO / OPTIMIZE: Note passArg is used only in one place.  I'm
+    // not saying we should manually inline it, but we could hoist the
+    // dispatch into the caller and have type-specific implementations
+    // of passArg: passArgI32(), etc.  Then those might be inlined, at
+    // least in PGO builds.
+    //
+    // The bulk of the work here (60%) is in the next() call, though.
+    //
+    // Notably, since next() is so expensive, stackArgAreaSize() becomes
+    // expensive too.
+    //
+    // Somehow there could be a trick here where the sequence of
+    // argument types (read from the input stream) leads to a cached
+    // entry for stackArgAreaSize() and for how to pass arguments...
+    //
+    // But at least we could reduce the cost of stackArgAreaSize() by
+    // first reading the argument types into a (reusable) vector, then
+    // we have the outgoing size at low cost, and then we can pass
+    // args based on the info we read.
+
+    void passArg(FunctionCall& call, ValType type, Stk& arg) {
+        switch (type) {
+          case ValType::I32: {
+            ABIArg argLoc = call.abi_.next(MIRType::Int32);
+            if (argLoc.kind() == ABIArg::Stack) {
+#if defined(JS_CODEGEN_X64) || defined(JS_CODEGEN_X86) || defined(JS_CODEGEN_ARM)
+                ScratchRegisterScope scratch(masm);
+                loadI32(scratch, arg);
+                masm.store32(scratch, Address(StackPointer, argLoc.offsetFromArgBase()));
+#else
+                MOZ_CRASH("BaseCompiler platform hook: passArg");
+#endif
+            } else {
+                loadI32(argLoc.reg().gpr(), arg);
+            }
+            break;
+          }
+          case ValType::I64: {
+#ifdef JS_CODEGEN_X64
+            ABIArg argLoc = call.abi_.next(MIRType::Int64);
+            if (argLoc.kind() == ABIArg::Stack) {
+                ScratchRegisterScope scratch(masm);
+                loadI64(Register64(scratch), arg);
+                masm.movq(scratch, Operand(StackPointer, argLoc.offsetFromArgBase()));
+            } else {
+                loadI64(Register64(argLoc.reg().gpr()), arg);
+            }
+#else
+            MOZ_CRASH("BaseCompiler platform hook: passArg I64");
+#endif
+            break;
+          }
+          case ValType::F64: {
+            ABIArg argLoc = call.abi_.next(MIRType::Double);
+            if (argLoc.kind() == ABIArg::Stack) {
+#if defined(JS_CODEGEN_X86) || defined(JS_CODEGEN_X64) || defined(JS_CODEGEN_ARM)
+                ScratchDoubleScope scratch(masm);
+                loadF64(scratch, arg);
+                masm.storeDouble(scratch, Address(StackPointer, argLoc.offsetFromArgBase()));
+#else
+                MOZ_CRASH("BaseCompiler platform hook: passArg F64");
+#endif
+            } else {
+                loadF64(argLoc.reg().fpu(), arg);
+            }
+            break;
+          }
+          case ValType::F32: {
+            ABIArg argLoc = call.abi_.next(MIRType::Float32);
+            if (argLoc.kind() == ABIArg::Stack) {
+#if defined(JS_CODEGEN_X86) || defined(JS_CODEGEN_X64) || defined(JS_CODEGEN_ARM)
+                ScratchFloat32Scope scratch(masm);
+                loadF32(scratch, arg);
+                masm.storeFloat32(scratch, Address(StackPointer, argLoc.offsetFromArgBase()));
+#else
+                MOZ_CRASH("BaseCompiler platform hook: passArg F32");
+#endif
+            } else {
+                loadF32(argLoc.reg().fpu(), arg);
+            }
+            break;
+          }
+          default:
+            MOZ_CRASH("Function argument type");
+        }
+    }
+
+    void callDirect(uint32_t calleeIndex, const FunctionCall& call)
+    {
+        CallSiteDesc desc(call.lineOrBytecode_, CallSiteDesc::Relative);
+        masm.call(desc, calleeIndex);
+    }
+
+    void callDynamic(Register callee, const FunctionCall& call) {
+        CallSiteDesc desc(call.lineOrBytecode_, CallSiteDesc::Register);
+        masm.call(desc, callee);
+    }
+
+    void callSymbolic(wasm::SymbolicAddress callee, const FunctionCall& call) {
+        CallSiteDesc desc(call.lineOrBytecode_, CallSiteDesc::Register);
+        masm.call(callee);
+    }
+
+    // Precondition: sync()
+
+    void funcPtrCall(const Sig& sig, uint32_t sigIndex, uint32_t length, uint32_t globalDataOffset,
+                     Stk& indexVal, const FunctionCall& call)
+    {
+        Register ptrReg = WasmTableCallPtrReg;
+
+        loadI32(ptrReg, indexVal);
+
+        if (isCompilingAsmJS()) {
+            MOZ_ASSERT(IsPowerOfTwo(length));
+            masm.andPtr(Imm32((length - 1)), ptrReg);
+        } else {
+            masm.branch32(Assembler::Condition::AboveOrEqual, ptrReg, Imm32(length),
+                          wasm::JumpTarget::OutOfBounds);
+            masm.move32(Imm32(sigIndex), WasmTableCallSigReg);
+        }
+
+#if defined(JS_CODEGEN_X64)
+        // CodeGeneratorX64::visitAsmJSLoadFuncPtr()
+        {
+            ScratchRegisterScope scratch(masm);
+            CodeOffset label = masm.leaRipRelative(scratch);
+            masm.loadPtr(Operand(scratch, ptrReg, TimesEight, 0), ptrReg);
+            masm.append(AsmJSGlobalAccess(label, globalDataOffset));
+        }
+#elif defined(JS_CODEGEN_X86)
+        // CodeGeneratorX86::visitAsmJSLoadFuncPtr()
+        CodeOffset label = masm.movlWithPatch(PatchedAbsoluteAddress(), ptrReg, TimesFour, ptrReg);
+        masm.append(AsmJSGlobalAccess(label, globalDataOffset));
+#else
+        MOZ_CRASH("BaseCompiler platform hook: funcPtrCall");
+#endif
+
+        callDynamic(ptrReg, call);
+    }
+
+    // Precondition: sync()
+
+    void ffiCall(unsigned globalDataOffset, const FunctionCall& call)
+    {
+        Register ptrReg = WasmTableCallPtrReg;
+
+#if defined(JS_CODEGEN_X64)
+        // CodeGeneratorX64::visitAsmJSLoadFFIFunc()
+        CodeOffset label = masm.loadRipRelativeInt64(ptrReg);
+        masm.append(AsmJSGlobalAccess(label, globalDataOffset));
+#elif defined(JS_CODEGEN_X86)
+        // CodeGeneratorX86::visitAsmJSLoadFFIFunc()
+        CodeOffset label = masm.movlWithPatch(PatchedAbsoluteAddress(), ptrReg);
+        masm.append(AsmJSGlobalAccess(label, globalDataOffset));
+#else
+        MOZ_CRASH("BaseCompiler platform hook: ffiCall");
+#endif
+        callDynamic(ptrReg, call);
+    }
+
+    void builtinCall(SymbolicAddress builtin, const FunctionCall& call)
+    {
+        callSymbolic(builtin, call);
+    }
+
+    //////////////////////////////////////////////////////////////////////
+    //
+    // Sundry low-level code generators.
+
+    void addInterruptCheck()
+    {
+        if (mg_.args.useSignalHandlersForInterrupt)
+            return;
+
+        // FIXME - implement this.
+        MOZ_CRASH("Only interrupting signal handlers supported");
+    }
+
+    void jumpTable(LabelVector& labels) {
+#if defined(JS_CODEGEN_X64) || defined(JS_CODEGEN_X86)
+        for (uint32_t i = 0; i < labels.length(); i++) {
+            CodeLabel cl;
+            masm.writeCodePointer(cl.patchAt());
+            cl.target()->bind(labels[i]->offset());
+            masm.addCodeLabel(cl);
+        }
+#else
+        MOZ_CRASH("BaseCompiler platform hook: jumpTable");
+#endif
+    }
+
+    void tableSwitch(Label* theTable, RegI32 switchValue) {
+#if defined(JS_CODEGEN_X64) || defined(JS_CODEGEN_X86)
+        ScratchRegisterScope scratch(masm);
+        CodeLabel tableCl;
+
+        masm.mov(tableCl.patchAt(), scratch);
+
+        tableCl.target()->bind(theTable->offset());
+        masm.addCodeLabel(tableCl);
+
+        masm.jmp(Operand(scratch, switchValue.reg, ScalePointer));
+#else
+        MOZ_CRASH("BaseCompiler platform hook: tableSwitch");
+#endif
+    }
+
+    void captureReturnedI32(RegI32 dest) {
+        moveI32(RegI32(ReturnReg), dest);
+    }
+
+    void captureReturnedI64(RegI64 dest) {
+#ifdef JS_PUNBOX64
+        moveI64(RegI64(ReturnReg64), dest);
+#else
+        MOZ_CRASH("BaseCompiler platform hook: pushReturned");
+#endif
+    }
+
+    void captureReturnedF32(RegF32 dest) {
+        moveF32(RegF32(ReturnFloat32Reg), dest);
+    }
+
+    void captureBuiltinReturnedF32(RegF32 dest) {
+#ifdef JS_CODEGEN_X86
+        masm.reserveStack(sizeof(float));
+        Operand op(esp, 0);
+        masm.fstp32(op);
+        masm.loadFloat32(op, dest.reg);
+        masm.freeStack(sizeof(float));
+#else
+        captureReturnedF32(dest);
+#endif
+    }
+
+    void captureReturnedF64(RegF64 dest) {
+        moveF64(RegF64(ReturnDoubleReg), dest);
+    }
+
+    void captureBuiltinReturnedF64(RegF64 dest) {
+#ifdef JS_CODEGEN_X86
+        masm.reserveStack(sizeof(double));
+        Operand op(esp, 0);
+        masm.fstp(op);
+        masm.loadDouble(op, dest.reg);
+        masm.freeStack(sizeof(double));
+#else
+        captureReturnedF64(dest);
+#endif
+    }
+
+    void returnVoid() {
+        popStackBeforeBranch(ctl_[0].framePushed);
+        masm.jump(&returnLabel_);
+    }
+
+    void returnI32(RegI32 r) {
+        moveI32(r, RegI32(ReturnReg));
+        popStackBeforeBranch(ctl_[0].framePushed);
+        masm.jump(&returnLabel_);
+    }
+
+    void returnI64(RegI64 r) {
+#ifdef JS_PUNBOX64
+        moveI64(r, RegI64(Register64(ReturnReg)));
+#else
+        MOZ_CRASH("BaseCompiler platform hook: returnI64");
+#endif
+        popStackBeforeBranch(ctl_[0].framePushed);
+        masm.jump(&returnLabel_);
+    }
+
+    void returnF64(RegF64 r) {
+        moveF64(r, RegF64(ReturnDoubleReg));
+        popStackBeforeBranch(ctl_[0].framePushed);
+        masm.jump(&returnLabel_);
+    }
+
+    void returnF32(RegF32 r) {
+        moveF32(r, RegF32(ReturnFloat32Reg));
+        popStackBeforeBranch(ctl_[0].framePushed);
+        masm.jump(&returnLabel_);
+    }
+
+    void addF32(RegF32 rhs, RegF32 srcDest) {
+#if defined(JS_CODEGEN_X86) || defined(JS_CODEGEN_X64)
+        masm.addFloat32(rhs.reg, srcDest.reg);
+#else
+        MOZ_CRASH("BaseCompiler platform hook: addF");
+#endif
+     }
+
+    void subI64(RegI64 rhs, RegI64 srcDest) {
+#if defined(JS_CODEGEN_X64)
+        masm.subq(rhs.reg.reg, srcDest.reg.reg);
+#else
+        MOZ_CRASH("BaseCompiler platform hook: subI64");
+#endif
+    }
+
+    void subF32(RegF32 rhs, RegF32 srcDest) {
+#if defined(JS_CODEGEN_X86) || defined(JS_CODEGEN_X64)
+        // CodeGeneratorX86Shared::visitMathF()
+        masm.vsubss(rhs.reg, srcDest.reg, srcDest.reg);
+#else
+        MOZ_CRASH("BaseCompiler platform hook: subF32");
+#endif
+    }
+
+    void mulI32(RegI32 rhs, RegI32 srcDest) {
+#if defined(JS_CODEGEN_X86) || defined(JS_CODEGEN_X64)
+        MOZ_ASSERT(srcDest.reg == eax);
+        MOZ_ASSERT(isAvailable(edx));
+        masm.imull(rhs.reg, srcDest.reg);
+#else
+        MOZ_CRASH("BaseCompiler platform hook: mulI32");
+#endif
+    }
+
+    void mulI64(RegI64 rhs, RegI64 srcDest) {
+#if defined(JS_CODEGEN_X64)
+        MOZ_ASSERT(srcDest.reg.reg == rax);
+        MOZ_ASSERT(isAvailable(rdx));
+        masm.imulq(rhs.reg.reg, srcDest.reg.reg);
+#else
+        MOZ_CRASH("BaseCompiler platform hook: mulI64");
+#endif
+    }
+
+    void mulF32(RegF32 rhs, RegF32 srcDest) {
+#if defined(JS_CODEGEN_X86) || defined(JS_CODEGEN_X64)
+        masm.vmulss(rhs.reg, srcDest.reg, srcDest.reg);
+#else
+        MOZ_CRASH("BaseCompiler platform hook: mulF32");
+#endif
+    }
+
+    void divideF64(RegF64 rhs, RegF64 srcDest) {
+#if defined(JS_CODEGEN_X86) || defined(JS_CODEGEN_X64)
+        masm.vdivsd(rhs.reg, srcDest.reg, srcDest.reg);
+#else
+        MOZ_CRASH("BaseCompiler platform hook: divideF64");
+#endif
+    }
+
+    void divideF32(RegF32 rhs, RegF32 srcDest) {
+#if defined(JS_CODEGEN_X86) || defined(JS_CODEGEN_X64)
+        masm.vdivss(rhs.reg, srcDest.reg, srcDest.reg);
+#else
+        MOZ_CRASH("BaseCompiler platform hook: divideF32");
+#endif
+    }
+
+    void checkDivideByZeroI32(RegI32 rhs, RegI32 srcDest, Label* done) {
+        if (isCompilingAsmJS()) {
+            // Truncated division by zero is zero (Infinity|0 == 0)
+            Label notDivByZero;
+            masm.branchTest32(Assembler::NonZero, rhs.reg, rhs.reg, &notDivByZero);
+            masm.move32(Imm32(0), srcDest.reg);
+            masm.jump(done);
+            masm.bind(&notDivByZero);
+        } else {
+            masm.branchTest32(Assembler::Zero, rhs.reg, rhs.reg, wasm::JumpTarget::IntegerDivideByZero);
+        }
+    }
+
+    void checkDivideByZeroI64(RegI64 rhs, RegI64 srcDest, Label* done) {
+        MOZ_ASSERT(!isCompilingAsmJS());
+#ifdef JS_CODEGEN_X64
+        masm.testq(rhs.reg.reg, rhs.reg.reg);
+        masm.j(Assembler::Zero, wasm::JumpTarget::IntegerDivideByZero);
+#else
+        MOZ_CRASH("BaseCompiler platform hook: checkDivideByZeroI64");
+#endif
+    }
+
+    void checkDivideSignedOverflowI32(RegI32 rhs, RegI32 srcDest, Label* done, bool zeroOnOverflow) {
+        Label notMin;
+        masm.branch32(Assembler::NotEqual, srcDest.reg, Imm32(INT32_MIN), &notMin);
+        if (zeroOnOverflow) {
+            masm.branch32(Assembler::NotEqual, rhs.reg, Imm32(-1), &notMin);
+            masm.move32(Imm32(0), srcDest.reg);
+            masm.jump(done);
+        } else if (isCompilingAsmJS()) {
+            // (-INT32_MIN)|0 == INT32_MIN and INT32_MIN is already in srcDest.
+            masm.branch32(Assembler::Equal, rhs.reg, Imm32(-1), done);
+        } else {
+            masm.branch32(Assembler::Equal, rhs.reg, Imm32(-1), wasm::JumpTarget::IntegerOverflow);
+        }
+        masm.bind(&notMin);
+    }
+
+    void checkDivideSignedOverflowI64(RegI64 rhs, RegI64 srcDest, Label* done, bool zeroOnOverflow) {
+        MOZ_ASSERT(!isCompilingAsmJS());
+#ifdef JS_CODEGEN_X64
+        Label notMin;
+        {
+            ScratchRegisterScope scratch(masm);
+            masm.move64(Imm64(INT64_MIN), Register64(scratch));
+            masm.cmpq(scratch, srcDest.reg.reg);
+        }
+        masm.j(Assembler::NotEqual, &notMin);
+        masm.cmpq(Imm32(-1), rhs.reg.reg);
+        if (zeroOnOverflow) {
+            masm.j(Assembler::NotEqual, &notMin);
+            masm.xorq(srcDest.reg.reg, srcDest.reg.reg);
+            masm.jump(done);
+        } else {
+            masm.j(Assembler::Equal, wasm::JumpTarget::IntegerOverflow);
+        }
+        masm.bind(&notMin);
+#else
+        MOZ_CRASH("BaseCompiler platform hook: checkDivideSignedOverflowI64");
+#endif
+    }
+
+    void quotientI32(RegI32 rhs, RegI32 srcDest, IsUnsigned isUnsigned) {
+        // CodeGeneratorX86Shared::visitDivI(),
+        // CodeGeneratorX86Shared::visitUDivOrMod()
+
+        Label done;
+
+        checkDivideByZeroI32(rhs, srcDest, &done);
+
+        if (!isUnsigned)
+            checkDivideSignedOverflowI32(rhs, srcDest, &done, ZeroOnOverflow(false));
+
+#if defined(JS_CODEGEN_X86) || defined(JS_CODEGEN_X64)
+        // The caller must set up the following situation.
+        MOZ_ASSERT(srcDest.reg == eax);
+        MOZ_ASSERT(isAvailable(edx));
+        if (isUnsigned) {
+            masm.mov(ImmWord(0), edx);
+            masm.udiv(rhs.reg);
+        } else {
+            masm.cdq();
+            masm.idiv(rhs.reg);
+        }
+#else
+        MOZ_CRASH("BaseCompiler platform hook: quotientI32");
+#endif
+        masm.bind(&done);
+    }
+
+    void quotientI64(RegI64 rhs, RegI64 srcDest, IsUnsigned isUnsigned) {
+        // This follows quotientI32, above.
+        Label done;
+
+        checkDivideByZeroI64(rhs, srcDest, &done);
+
+        if (!isUnsigned)
+            checkDivideSignedOverflowI64(rhs, srcDest, &done, ZeroOnOverflow(false));
+
+#if defined(JS_CODEGEN_X64)
+        // The caller must set up the following situation.
+        MOZ_ASSERT(srcDest.reg.reg == rax);
+        MOZ_ASSERT(isAvailable(rdx));
+        if (isUnsigned) {
+            masm.xorq(rdx, rdx);
+            masm.udivq(rhs.reg.reg);
+        } else {
+            masm.cqo();
+            masm.idivq(rhs.reg.reg);
+        }
+#else
+        MOZ_CRASH("BaseCompiler platform hook: quotientI64");
+#endif
+        masm.bind(&done);
+    }
+
+    void remainderI32(RegI32 rhs, RegI32 srcDest, IsUnsigned isUnsigned) {
+        Label done;
+
+        checkDivideByZeroI32(rhs, srcDest, &done);
+
+        if (!isUnsigned)
+            checkDivideSignedOverflowI32(rhs, srcDest, &done, ZeroOnOverflow(true));
+
+#if defined(JS_CODEGEN_X86) || defined(JS_CODEGEN_X64)
+        // The caller must set up the following situation.
+        MOZ_ASSERT(srcDest.reg == eax);
+        MOZ_ASSERT(isAvailable(edx));
+        if (isUnsigned) {
+            masm.mov(ImmWord(0), edx);
+            masm.udiv(rhs.reg);
+        } else {
+            masm.cdq();
+            masm.idiv(rhs.reg);
+        }
+        masm.mov(edx, eax);
+#else
+        MOZ_CRASH("BaseCompiler platform hook: remainderI32");
+#endif
+        masm.bind(&done);
+    }
+
+    void remainderI64(RegI64 rhs, RegI64 srcDest, IsUnsigned isUnsigned) {
+        Label done;
+
+        checkDivideByZeroI64(rhs, srcDest, &done);
+
+        if (!isUnsigned)
+            checkDivideSignedOverflowI64(rhs, srcDest, &done, ZeroOnOverflow(true));
+
+#if defined(JS_CODEGEN_X64)
+        // The caller must set up the following situation.
+        MOZ_ASSERT(srcDest.reg.reg == rax);
+        MOZ_ASSERT(isAvailable(rdx));
+
+        if (isUnsigned) {
+            masm.xorq(rdx, rdx);
+            masm.udivq(rhs.reg.reg);
+        } else {
+            masm.cqo();
+            masm.idivq(rhs.reg.reg);
+        }
+        masm.movq(rdx, rax);
+#else
+        MOZ_CRASH("BaseCompiler platform hook: remainderI64");
+#endif
+        masm.bind(&done);
+    }
+
+    void orI64(RegI64 rhs, RegI64 srcDest) {
+#if defined(JS_CODEGEN_X64)
+        masm.orq(rhs.reg.reg, srcDest.reg.reg);
+#else
+        MOZ_CRASH("BaseCompiler platform hook: orI64");
+#endif
+    }
+
+    void andI64(RegI64 rhs, RegI64 srcDest) {
+#if defined(JS_CODEGEN_X64)
+        masm.andq(rhs.reg.reg, srcDest.reg.reg);
+#else
+        MOZ_CRASH("BaseCompiler platform hook: andI64");
+#endif
+    }
+
+    void xorI32(RegI32 rhs, RegI32 srcDest) {
+#if defined(JS_CODEGEN_X86) || defined(JS_CODEGEN_X64)
+        masm.xor32(rhs.reg, srcDest.reg);
+#else
+        MOZ_CRASH("BaseCompiler platform hook: xorI32");
+#endif
+    }
+
+    void xorI64(RegI64 rhs, RegI64 srcDest) {
+#if defined(JS_CODEGEN_X64)
+        masm.xorq(rhs.reg.reg, srcDest.reg.reg);
+#else
+        MOZ_CRASH("BaseCompiler platform hook: xorI64");
+#endif
+    }
+
+    void lshiftI32(RegI32 rhs, RegI32 srcDest) {
+#if defined(JS_CODEGEN_X86) || defined(JS_CODEGEN_X64)
+        MOZ_ASSERT(rhs.reg == ecx);
+        masm.lshift32(rhs.reg, srcDest.reg);
+#else
+        MOZ_CRASH("BaseCompiler platform hook: lshiftI32");
+#endif
+    }
+
+    void lshiftI32(int32_t count, RegI32 srcDest) {
+#if defined(JS_CODEGEN_X86) || defined(JS_CODEGEN_X64)
+        masm.shll(Imm32(count), srcDest.reg);
+#else
+        MOZ_CRASH("BaseCompiler platform hook: lshiftI32");
+#endif
+    }
+
+    void lshiftI64(RegI64 rhs, RegI64 srcDest) {
+#if defined(JS_CODEGEN_X64)
+        MOZ_ASSERT(rhs.reg.reg == rcx);
+        masm.shlq_cl(srcDest.reg.reg);
+#else
+        MOZ_CRASH("BaseCompiler platform hook: lshiftI64");
+#endif
+    }
+
+    void rshiftI32(RegI32 rhs, RegI32 srcDest) {
+#if defined(JS_CODEGEN_X86) || defined(JS_CODEGEN_X64)
+        MOZ_ASSERT(rhs.reg == ecx);
+        masm.rshift32Arithmetic(rhs.reg, srcDest.reg);
+#else
+        MOZ_CRASH("BaseCompiler platform hook: rshiftI32");
+#endif
+    }
+
+    void rshiftI32(int32_t count, RegI32 srcDest) {
+#if defined(JS_CODEGEN_X86) || defined(JS_CODEGEN_X64)
+        masm.sarl(Imm32(count), srcDest.reg);
+#else
+        MOZ_CRASH("BaseCompiler platform hook: rshiftI32");
+#endif
+    }
+
+    void rshiftI64(RegI64 rhs, RegI64 srcDest) {
+#if defined(JS_CODEGEN_X64)
+        MOZ_ASSERT(rhs.reg.reg == rcx);
+        masm.sarq_cl(srcDest.reg.reg);
+#else
+        MOZ_CRASH("BaseCompiler platform hook: rshiftI64");
+#endif
+    }
+
+    void rshiftU32(RegI32 rhs, RegI32 srcDest) {
+#if defined(JS_CODEGEN_X86) || defined(JS_CODEGEN_X64)
+        MOZ_ASSERT(rhs.reg == ecx);
+        masm.rshift32(rhs.reg, srcDest.reg);
+#else
+        MOZ_CRASH("BaseCompiler platform hook: rshiftU32");
+#endif
+    }
+
+    void rshiftU32(int32_t count, RegI32 srcDest) {
+#if defined(JS_CODEGEN_X86) || defined(JS_CODEGEN_X64)
+        masm.shrl(Imm32(count), srcDest.reg);
+#else
+        MOZ_CRASH("BaseCompiler platform hook: rshiftU32");
+#endif
+    }
+
+    void rshiftU64(RegI64 rhs, RegI64 srcDest) {
+#if defined(JS_CODEGEN_X64)
+        MOZ_ASSERT(rhs.reg.reg == rcx);
+        masm.shrq_cl(srcDest.reg.reg);
+#else
+        MOZ_CRASH("BaseCompiler platform hook: rshiftU64");
+#endif
+    }
+
+    void rotateRightI32(RegI32 rhs, RegI32 srcDest) {
+#if defined(JS_CODEGEN_X86) || defined(JS_CODEGEN_X64)
+        MOZ_ASSERT(rhs.reg == ecx);
+#endif
+        masm.rotateRight(rhs.reg, srcDest.reg, srcDest.reg);
+    }
+
+    void rotateRightI64(RegI64 rhs, RegI64 srcDest) {
+#if defined(JS_CODEGEN_X64)
+        MOZ_ASSERT(rhs.reg.reg == rcx);
+        masm.rorq_cl(srcDest.reg.reg);
+#else
+        MOZ_CRASH("BaseCompiler platform hook: rotateRightI64");
+#endif
+    }
+
+    void rotateLeftI32(RegI32 rhs, RegI32 srcDest) {
+#if defined(JS_CODEGEN_X86) || defined(JS_CODEGEN_X64)
+        MOZ_ASSERT(rhs.reg == ecx);
+#endif
+        masm.rotateLeft(rhs.reg, srcDest.reg, srcDest.reg);
+    }
+
+    void rotateLeftI64(RegI64 rhs, RegI64 srcDest) {
+#if defined(JS_CODEGEN_X64)
+        MOZ_ASSERT(rhs.reg.reg == rcx);
+        masm.rolq_cl(srcDest.reg.reg);
+#else
+        MOZ_CRASH("BaseCompiler platform hook: rotateLeftI64");
+#endif
+    }
+
+    void clzI32(RegI32 srcDest) {
+#if defined(JS_CODEGEN_X86) || defined(JS_CODEGEN_X64)
+        masm.clz32(srcDest.reg, srcDest.reg, false);
+#else
+        MOZ_CRASH("BaseCompiler platform hook: clzI32");
+#endif
+    }
+
+    void clzI64(RegI64 srcDest) {
+#if defined(JS_CODEGEN_X64)
+        masm.clz64(srcDest.reg, srcDest.reg);
+#else
+        MOZ_CRASH("BaseCompiler platform hook: clzI64");
+#endif
+    }
+
+    void ctzI32(RegI32 srcDest) {
+#if defined(JS_CODEGEN_X86) || defined(JS_CODEGEN_X64)
+        masm.ctz32(srcDest.reg, srcDest.reg, false);
+#else
+        MOZ_CRASH("BaseCompiler platform hook: ctzI32");
+#endif
+    }
+
+    void ctzI64(RegI64 srcDest) {
+#if defined(JS_CODEGEN_X64)
+        masm.ctz64(srcDest.reg, srcDest.reg);
+#else
+        MOZ_CRASH("BaseCompiler platform hook: ctzI64");
+#endif
+    }
+
+    bool popcntNeedsTemp() {
+#if defined(JS_CODEGEN_X86) || defined(JS_CODEGEN_X64)
+        return !AssemblerX86Shared::HasPOPCNT();
+#else
+        return false;
+#endif
+    }
+
+    void popcntI32(RegI32 srcDest, RegI32 tmp) {
+#if defined(JS_CODEGEN_X86) || defined(JS_CODEGEN_X64)
+        masm.popcnt32(srcDest.reg, srcDest.reg, tmp.reg);
+#else
+        MOZ_CRASH("BaseCompiler platform hook: popcntI32");
+#endif
+    }
+
+    void popcntI64(RegI64 srcDest, RegI64 tmp) {
+#if defined(JS_CODEGEN_X64)
+        masm.popcnt64(srcDest.reg, srcDest.reg, tmp.reg);
+#else
+        MOZ_CRASH("BaseCompiler platform hook: popcntI64");
+#endif
+    }
+
+    void absI32(RegI32 srcDest) {
+        // TODO / OPTIMIZE: Use conditional move on some platforms
+        Label nonnegative;
+        masm.branch32(Assembler::GreaterThanOrEqual, srcDest.reg, Imm32(0), &nonnegative);
+        masm.neg32(srcDest.reg);
+        masm.bind(&nonnegative);
+    }
+
+    void absF32(RegF32 srcDest) {
+#if defined(JS_CODEGEN_X86) || defined(JS_CODEGEN_X64)
+        // CodeGeneratorX86Shared::visitAbsF()
+        ScratchFloat32Scope scratch(masm);
+        masm.loadConstantFloat32(SpecificNaN<float>(0, FloatingPoint<float>::kSignificandBits), scratch);
+        masm.vandps(scratch, srcDest.reg, srcDest.reg);
+#else
+        MOZ_CRASH("BaseCompiler platform hook: absF32");
+#endif
+    }
+
+    void absF64(RegF64 srcDest) {
+#if defined(JS_CODEGEN_X86) || defined(JS_CODEGEN_X64)
+        // CodeGeneratorX86Shared::visitAbsD()
+        ScratchDoubleScope scratch(masm);
+        masm.loadConstantDouble(SpecificNaN<double>(0, FloatingPoint<double>::kSignificandBits), scratch);
+        masm.vandpd(scratch, srcDest.reg, srcDest.reg);
+#else
+        MOZ_CRASH("BaseCompiler platform hook: absF64");
+#endif
+    }
+
+    void negateF32(RegF32 srcDest) {
+#if defined(JS_CODEGEN_X86) || defined(JS_CODEGEN_X64)
+        masm.negateFloat(srcDest.reg);
+#else
+        MOZ_CRASH("BaseCompiler platform hook: negateF32");
+#endif
+    }
+
+    void sqrtF64(RegF64 srcDest) {
+#if defined(JS_CODEGEN_X86) || defined(JS_CODEGEN_X64)
+        masm.vsqrtsd(srcDest.reg, srcDest.reg, srcDest.reg);
+#else
+        MOZ_CRASH("BaseCompiler platform hook: sqrtF64");
+#endif
+    }
+
+    void sqrtF32(RegF32 srcDest) {
+#if defined(JS_CODEGEN_X86) || defined(JS_CODEGEN_X64)
+        masm.vsqrtss(srcDest.reg, srcDest.reg, srcDest.reg);
+#else
+        MOZ_CRASH("BaseCompiler platform hook: sqrtF32");
+#endif
+    }
+
+    void minMaxF32(RegF32 rhs, RegF32 srcDest, IsMax isMax) {
+#if defined(JS_CODEGEN_X86) || defined(JS_CODEGEN_X64)
+        masm.minMaxFloat32(srcDest.reg, rhs.reg, true, isMax);
+#else
+        MOZ_CRASH("BaseCompiler platform hook: minMaxF32");
+#endif
+    }
+
+    void minMaxF64(RegF64 rhs, RegF64 srcDest, IsMax isMax) {
+#if defined(JS_CODEGEN_X86) || defined(JS_CODEGEN_X64)
+        masm.minMaxDouble(srcDest.reg, rhs.reg, true, isMax);
+#else
+        MOZ_CRASH("BaseCompiler platform hook: minMaxF64");
+#endif
+    }
+
+    void reinterpretI32AsF32(RegI32 src, RegF32 dest) {
+#if defined(JS_CODEGEN_X86) || defined(JS_CODEGEN_X64)
+        masm.vmovd(src.reg, dest.reg);
+#else
+        MOZ_CRASH("BaseCompiler platform hook: reinterpretI32AsF32");
+#endif
+    }
+
+    void reinterpretI64AsF64(RegI64 src, RegF64 dest) {
+#if defined(JS_CODEGEN_X64)
+        masm.vmovq(src.reg.reg, dest.reg);
+#else
+        MOZ_CRASH("BaseCompiler platform hook: reinterpretI64AsF64");
+#endif
+    }
+
+    void reinterpretF32AsI32(RegF32 src, RegI32 dest) {
+#if defined(JS_CODEGEN_X86) || defined(JS_CODEGEN_X64)
+        masm.vmovd(src.reg, dest.reg);
+#else
+        MOZ_CRASH("BaseCompiler platform hook: reinterpretF32AsI32");
+#endif
+    }
+
+    void reinterpretF64AsI64(RegF64 src, RegI64 dest) {
+#if defined(JS_CODEGEN_X64)
+        masm.vmovq(src.reg, dest.reg.reg);
+#else
+        MOZ_CRASH("BaseCompiler platform hook: reinterpretF64AsI64");
+#endif
+    }
+
+    void wrapI64ToI32(RegI64 src, RegI32 dest) {
+#if defined(JS_CODEGEN_X64)
+        // movl clears the high bits if the two registers are the same.
+        masm.movl(src.reg.reg, dest.reg);
+#else
+        MOZ_CRASH("BaseCompiler platform hook: wrapI64ToI32");
+#endif
+    }
+
+    void extendI32ToI64(RegI32 src, RegI64 dest) {
+#if defined(JS_CODEGEN_X64)
+        masm.movslq(src.reg, dest.reg.reg);
+#else
+        MOZ_CRASH("BaseCompiler platform hook: extendI32ToI64");
+#endif
+    }
+
+    void extendU32ToI64(RegI32 src, RegI64 dest) {
+#if defined(JS_CODEGEN_X64)
+        masm.movl(src.reg, dest.reg.reg);
+#else
+        MOZ_CRASH("BaseCompiler platform hook: extendU32ToI64");
+#endif
+    }
+
+    class OutOfLineTruncateF32OrF64ToI32 : public OutOfLineCode
+    {
+        AnyReg src;
+        RegI32 dest;
+      public:
+        OutOfLineTruncateF32OrF64ToI32(AnyReg src, RegI32 dest)
+          : src(src),
+            dest(dest)
+        {}
+
+        virtual void generate(MacroAssembler& masm) {
+            // isWasm must be true (for now), see bug 1279876 for related issues.
+            bool isWasm = true;
+            bool isFloat = src.tag == AnyReg::F32;
+            FloatRegister fsrc = isFloat ? src.f32().reg : src.f64().reg;
+            saveVolatileReturnGPR(masm);
+            masm.outOfLineTruncateSlow(fsrc, dest.reg, isFloat, isWasm);
+            restoreVolatileReturnGPR(masm);
+            masm.jump(rejoin());
+        }
+    };
+
+    MOZ_MUST_USE
+    bool truncateF32ToI32(RegF32 src, RegI32 dest) {
+        OutOfLineCode* ool =
+            addOutOfLineCode(new (alloc_) OutOfLineTruncateF32OrF64ToI32(AnyReg(src),
+                                                                         dest));
+        if (!ool)
+            return false;
+        masm.branchTruncateFloat32(src.reg, dest.reg, ool->entry());
+        masm.bind(ool->rejoin());
+        return true;
+    }
+
+    MOZ_MUST_USE
+    bool truncateF64ToI32(RegF64 src, RegI32 dest) {
+        OutOfLineCode* ool =
+            addOutOfLineCode(new (alloc_) OutOfLineTruncateF32OrF64ToI32(AnyReg(src),
+                                                                         dest));
+        if (!ool)
+            return false;
+        masm.branchTruncateDouble(src.reg, dest.reg, ool->entry());
+        masm.bind(ool->rejoin());
+        return true;
+    }
+
+#if defined(JS_CODEGEN_X86) || defined(JS_CODEGEN_X64)
+    class OutOfLineTruncateCheckF32OrF64ToI64 : public OutOfLineCode
+    {
+        AnyReg src;
+        bool isUnsigned;
+      public:
+        OutOfLineTruncateCheckF32OrF64ToI64(AnyReg src, bool isUnsigned)
+          : src(src),
+            isUnsigned(isUnsigned)
+        {}
+
+        virtual void generate(MacroAssembler& masm) {
+            bool isFloat = src.tag == AnyReg::F32;
+            FloatRegister fsrc = isFloat ? src.f32().reg : src.f64().reg;
+            masm.outOfLineWasmTruncateCheck(fsrc, isFloat ? MIRType::Float32 : MIRType::Double,
+                                            MIRType::Int64, isUnsigned, rejoin());
+        }
+    };
+#endif
+
+    MOZ_MUST_USE
+    bool truncateF32ToI64(RegF32 src, RegI64 dest, bool isUnsigned, RegF64 temp) {
+#ifdef JS_CODEGEN_X64
+        OutOfLineCode* ool =
+            addOutOfLineCode(new (alloc_) OutOfLineTruncateCheckF32OrF64ToI64(AnyReg(src),
+                                                                              isUnsigned));
+        if (!ool)
+            return false;
+        if (isUnsigned)
+            masm.wasmTruncateFloat32ToUInt64(src.reg, dest.reg.reg, ool->entry(),
+                                             ool->rejoin(), temp.reg);
+        else
+            masm.wasmTruncateFloat32ToInt64(src.reg, dest.reg.reg, ool->entry(),
+                                            ool->rejoin(), temp.reg);
+        masm.bind(ool->rejoin());
+#else
+        MOZ_CRASH("BaseCompiler platform hook: truncateF32ToI64");
+#endif
+        return true;
+    }
+
+    MOZ_MUST_USE
+    bool truncateF64ToI64(RegF64 src, RegI64 dest, bool isUnsigned, RegF64 temp) {
+#ifdef JS_CODEGEN_X64
+        OutOfLineCode* ool =
+            addOutOfLineCode(new (alloc_) OutOfLineTruncateCheckF32OrF64ToI64(AnyReg(src),
+                                                                              isUnsigned));
+        if (!ool)
+            return false;
+        if (isUnsigned)
+            masm.wasmTruncateDoubleToUInt64(src.reg, dest.reg.reg, ool->entry(),
+                                            ool->rejoin(), temp.reg);
+        else
+            masm.wasmTruncateDoubleToInt64(src.reg, dest.reg.reg, ool->entry(),
+                                            ool->rejoin(), temp.reg);
+        masm.bind(ool->rejoin());
+#else
+        MOZ_CRASH("BaseCompiler platform hook: truncateF64ToI64");
+#endif
+        return true;
+    }
+
+    void convertI64ToF32(RegI64 src, bool isUnsigned, RegF32 dest) {
+#ifdef JS_CODEGEN_X64
+        if (isUnsigned)
+            masm.convertUInt64ToFloat32(src.reg.reg, dest.reg);
+        else
+            masm.convertInt64ToFloat32(src.reg.reg, dest.reg);
+#else
+        MOZ_CRASH("BaseCompiler platform hook: convertI64ToF32");
+#endif
+    }
+
+    void convertI64ToF64(RegI64 src, bool isUnsigned, RegF64 dest) {
+#ifdef JS_CODEGEN_X64
+        if (isUnsigned)
+            masm.convertUInt64ToDouble(src.reg.reg, dest.reg);
+        else
+            masm.convertInt64ToDouble(src.reg.reg, dest.reg);
+#else
+        MOZ_CRASH("BaseCompiler platform hook: convertI32ToF64");
+#endif
+    }
+
+    void cmp64Set(Assembler::Condition cond, RegI64 lhs, RegI64 rhs, RegI32 dest) {
+#if defined(JS_CODEGEN_X64)
+        masm.cmpq(rhs.reg.reg, lhs.reg.reg);
+        masm.emitSet(cond, dest.reg);
+#else
+        MOZ_CRASH("BaseCompiler platform hook: cmp64Set");
+#endif
+    }
+
+    void unreachableTrap()
+    {
+        masm.jump(wasm::JumpTarget::Unreachable);
+#ifdef DEBUG
+        masm.breakpoint();
+#endif
+    }
+
+    //////////////////////////////////////////////////////////////////////
+    //
+    // Global variable access.
+
+    // CodeGeneratorX64::visitAsmJSLoadGlobalVar()
+
+    void loadGlobalVarI32(unsigned globalDataOffset, RegI32 r)
+    {
+#ifdef JS_CODEGEN_X64
+        CodeOffset label = masm.loadRipRelativeInt32(r.reg);
+        masm.append(AsmJSGlobalAccess(label, globalDataOffset));
+#else
+        MOZ_CRASH("BaseCompiler platform hook: loadGlobalVarI32");
+#endif
+    }
+
+    void loadGlobalVarI64(unsigned globalDataOffset, RegI64 r)
+    {
+#ifdef JS_CODEGEN_X64
+        CodeOffset label = masm.loadRipRelativeInt64(r.reg.reg);
+        masm.append(AsmJSGlobalAccess(label, globalDataOffset));
+#else
+        MOZ_CRASH("BaseCompiler platform hook: loadGlobalVarI64");
+#endif
+    }
+
+    void loadGlobalVarF32(unsigned globalDataOffset, RegF32 r)
+    {
+#ifdef JS_CODEGEN_X64
+        CodeOffset label = masm.loadRipRelativeFloat32(r.reg);
+        masm.append(AsmJSGlobalAccess(label, globalDataOffset));
+#else
+        MOZ_CRASH("BaseCompiler platform hook: loadGlobalVarF32");
+#endif
+    }
+
+    void loadGlobalVarF64(unsigned globalDataOffset, RegF64 r)
+    {
+#ifdef JS_CODEGEN_X64
+        CodeOffset label = masm.loadRipRelativeDouble(r.reg);
+        masm.append(AsmJSGlobalAccess(label, globalDataOffset));
+#else
+        MOZ_CRASH("BaseCompiler platform hook: loadGlobalVarF32");
+#endif
+    }
+
+    // CodeGeneratorX64::visitAsmJSStoreGlobalVar()
+
+    void storeGlobalVarI32(unsigned globalDataOffset, RegI32 r)
+    {
+#ifdef JS_CODEGEN_X64
+        CodeOffset label = masm.storeRipRelativeInt32(r.reg);
+        masm.append(AsmJSGlobalAccess(label, globalDataOffset));
+#else
+        MOZ_CRASH("BaseCompiler platform hook: storeGlobalVarI32");
+#endif
+    }
+
+    void storeGlobalVarI64(unsigned globalDataOffset, RegI64 r)
+    {
+#ifdef JS_CODEGEN_X64
+        CodeOffset label = masm.storeRipRelativeInt64(r.reg.reg);
+        masm.append(AsmJSGlobalAccess(label, globalDataOffset));
+#else
+        MOZ_CRASH("BaseCompiler platform hook: storeGlobalVarI64");
+#endif
+    }
+
+    void storeGlobalVarF32(unsigned globalDataOffset, RegF32 r)
+    {
+#ifdef JS_CODEGEN_X64
+        CodeOffset label = masm.storeRipRelativeFloat32(r.reg);
+        masm.append(AsmJSGlobalAccess(label, globalDataOffset));
+#else
+        MOZ_CRASH("BaseCompiler platform hook: storeGlobalVarF32");
+#endif
+    }
+
+    void storeGlobalVarF64(unsigned globalDataOffset, RegF64 r)
+    {
+#ifdef JS_CODEGEN_X64
+        CodeOffset label = masm.storeRipRelativeDouble(r.reg);
+        masm.append(AsmJSGlobalAccess(label, globalDataOffset));
+#else
+        MOZ_CRASH("BaseCompiler platform hook: storeGlobalVarF64");
+#endif
+    }
+
+    //////////////////////////////////////////////////////////////////////
+    //
+    // Heap access.
+
+#if defined(JS_CODEGEN_X64)
+    // Copied from CodeGenerator-x64.cpp
+    // TODO / CLEANUP - share with the code generator.
+
+    wasm::MemoryAccess
+    AsmJSMemoryAccess(uint32_t before, wasm::MemoryAccess::OutOfBoundsBehavior throwBehavior,
+                      uint32_t offsetWithinWholeSimdVector = 0)
+    {
+        return wasm::MemoryAccess(before, throwBehavior, wasm::MemoryAccess::WrapOffset,
+                                  offsetWithinWholeSimdVector);
+    }
+#endif
+
+    void memoryBarrier(MemoryBarrierBits barrier) {
+#if defined(JS_CODEGEN_X86) || defined(JS_CODEGEN_X64)
+        if (barrier & MembarStoreLoad)
+            masm.storeLoadFence();
+#else
+        MOZ_CRASH("BaseCompiler platform hook: memoryBarrier");
+#endif
+    }
+
+    // Cloned from MIRGraph.cpp, merge somehow?
+
+    bool needsBoundsCheckBranch(const MAsmJSHeapAccess& access) const {
+        // A heap access needs a bounds-check branch if we're not relying on signal
+        // handlers to catch errors, and if it's not proven to be within bounds.
+        // We use signal-handlers on x64, but on x86 there isn't enough address
+        // space for a guard region.  Also, on x64 the atomic loads and stores
+        // can't (yet) use the signal handlers.
+
+#if defined(ASMJS_MAY_USE_SIGNAL_HANDLERS_FOR_OOB)
+        if (mg_.args.useSignalHandlersForOOB && !access.isAtomicAccess())
+            return false;
+#endif
+
+        return access.needsBoundsCheck();
+    }
+
+#if defined(JS_CODEGEN_X64) || defined(JS_CODEGEN_X86)
+    void verifyHeapAccessDisassembly(uint32_t before, uint32_t after, bool isLoad,
+                                     Scalar::Type accessType, int nelems, Operand srcAddr,
+                                     AnyReg dest)
+    {
+#ifdef DEBUG
+        // TODO / MISSING: this needs to be adapted from what's in the
+        // platform's CodeGenerator; that code takes an LAllocation as
+        // the last arg now.
+#endif
+    }
+#endif
+
+    void loadHeap(const MAsmJSHeapAccess& access, RegI32 ptr, AnyReg dest) {
+#if defined(JS_CODEGEN_X64)
+        // CodeGeneratorX64::visitAsmJSLoadHeap()
+
+        if (needsBoundsCheckBranch(access))
+            MOZ_CRASH("BaseCompiler platform hook: bounds checking");
+
+        Operand srcAddr(HeapReg, ptr.reg, TimesOne, access.offset());
+
+        uint32_t before = masm.size();
+        switch (access.accessType()) {
+          case Scalar::Int8:      masm.movsbl(srcAddr, dest.i32().reg); break;
+          case Scalar::Uint8:     masm.movzbl(srcAddr, dest.i32().reg); break;
+          case Scalar::Int16:     masm.movswl(srcAddr, dest.i32().reg); break;
+          case Scalar::Uint16:    masm.movzwl(srcAddr, dest.i32().reg); break;
+          case Scalar::Int32:
+          case Scalar::Uint32:    masm.movl(srcAddr, dest.i32().reg); break;
+          case Scalar::Float32:   masm.loadFloat32(srcAddr, dest.f32().reg); break;
+          case Scalar::Float64:   masm.loadDouble(srcAddr, dest.f64().reg); break;
+          default:
+            MOZ_CRASH("Compiler bug: Unexpected array type");
+        }
+        uint32_t after = masm.size();
+
+        masm.append(AsmJSMemoryAccess(before, wasm::MemoryAccess::CarryOn));
+        verifyHeapAccessDisassembly(before, after, IsLoad(true), access.accessType(), 0, srcAddr, dest);
+#else
+        MOZ_CRASH("BaseCompiler platform hook: loadHeap");
+#endif
+    }
+
+    void storeHeap(const MAsmJSHeapAccess& access, RegI32 ptr, AnyReg src) {
+#if defined(JS_CODEGEN_X64)
+        // CodeGeneratorX64::visitAsmJSStoreHeap()
+
+        if (needsBoundsCheckBranch(access))
+            MOZ_CRASH("BaseCompiler platform hook: bounds checking");
+
+        Operand dstAddr(HeapReg, ptr.reg, TimesOne, access.offset());
+
+        uint32_t before = masm.size();
+        switch (access.accessType()) {
+          case Scalar::Int8:
+          case Scalar::Uint8:        masm.movb(src.i32().reg, dstAddr); break;
+          case Scalar::Int16:
+          case Scalar::Uint16:       masm.movw(src.i32().reg, dstAddr); break;
+          case Scalar::Int32:
+          case Scalar::Uint32:       masm.movl(src.i32().reg, dstAddr); break;
+          case Scalar::Float32:      masm.storeFloat32(src.f32().reg, dstAddr); break;
+          case Scalar::Float64:      masm.storeDouble(src.f64().reg, dstAddr); break;
+          default:
+              MOZ_CRASH("Compiler bug: Unexpected array type");
+        }
+        uint32_t after = masm.size();
+
+        masm.append(AsmJSMemoryAccess(before, wasm::MemoryAccess::CarryOn));
+        verifyHeapAccessDisassembly(before, after, IsLoad(false), access.accessType(), 0, dstAddr, src);
+#else
+        MOZ_CRASH("BaseCompiler platform hook: storeHeap");
+#endif
+    }
+
+    ////////////////////////////////////////////////////////////
+
+    // Generally speaking, ABOVE this point there should be no value
+    // stack manipulation (calls to popI32 etc).
+
+    // Generally speaking, BELOW this point there should be no
+    // platform dependencies.  We make an exception for x86 register
+    // targeting, which is not too hard to keep clean.
+
+    ////////////////////////////////////////////////////////////
+    //
+    // Sundry wrappers.
+
+    void pop2xI32(RegI32* r0, RegI32* r1) {
+        *r1 = popI32();
+        *r0 = popI32();
+    }
+
+    RegI32 popI32ToSpecific(RegI32 specific) {
+        freeI32(specific);
+        return popI32(specific);
+    }
+
+    void pop2xI64(RegI64* r0, RegI64* r1) {
+        *r1 = popI64();
+        *r0 = popI64();
+    }
+
+    RegI64 popI64ToSpecific(RegI64 specific) {
+        freeI64(specific);
+        return popI64(specific);
+    }
+
+    void pop2xF32(RegF32* r0, RegF32* r1) {
+        *r1 = popF32();
+        *r0 = popF32();
+    }
+
+    void pop2xF64(RegF64* r0, RegF64* r1) {
+        *r1 = popF64();
+        *r0 = popF64();
+    }
+
+    ////////////////////////////////////////////////////////////
+    //
+    // Sundry helpers.
+
+    uint32_t readCallSiteLineOrBytecode(uint32_t callOffset) {
+        if (!func_.callSiteLineNums().empty())
+            return func_.callSiteLineNums()[lastReadCallSite_++];
+        return callOffset;
+    }
+
+    bool done() const {
+        return iter_.done();
+    }
+
+    bool isCompilingAsmJS() const {
+        return mg_.kind == ModuleKind::AsmJS;
+    }
+
+    //////////////////////////////////////////////////////////////////////
+
+    MOZ_MUST_USE
+    bool emitBody();
+    MOZ_MUST_USE
+    bool emitBlock();
+    MOZ_MUST_USE
+    bool emitLoop();
+    MOZ_MUST_USE
+    bool emitIf();
+    MOZ_MUST_USE
+    bool emitElse();
+    MOZ_MUST_USE
+    bool emitEnd();
+    MOZ_MUST_USE
+    bool emitBr();
+    MOZ_MUST_USE
+    bool emitBrIf();
+    MOZ_MUST_USE
+    bool emitBrTable();
+    MOZ_MUST_USE
+    bool emitReturn();
+    MOZ_MUST_USE
+    bool emitCallArgs(const ValTypeVector& args, FunctionCall& baselineCall);
+    MOZ_MUST_USE
+    bool emitCall(uint32_t callOffset);
+    MOZ_MUST_USE
+    bool emitCallIndirect(uint32_t callOffset);
+    MOZ_MUST_USE
+    bool emitCallImport(uint32_t callOffset);
+    MOZ_MUST_USE
+    bool emitUnaryMathBuiltinCall(uint32_t callOffset, SymbolicAddress callee, ValType operandType);
+    MOZ_MUST_USE
+    bool emitBinaryMathBuiltinCall(uint32_t callOffset, SymbolicAddress callee, ValType operandType);
+    MOZ_MUST_USE
+    bool emitGetLocal();
+    MOZ_MUST_USE
+    bool emitSetLocal();
+    MOZ_MUST_USE
+    bool emitGetGlobal();
+    MOZ_MUST_USE
+    bool emitSetGlobal();
+    MOZ_MUST_USE
+    bool emitLoad(ValType type, Scalar::Type viewType);
+    MOZ_MUST_USE
+    bool emitStore(ValType resultType, Scalar::Type viewType);
+    MOZ_MUST_USE
+    bool emitStoreWithCoercion(ValType resultType, Scalar::Type viewType);
+    MOZ_MUST_USE
+    bool emitSelect();
+
+    void endBlock();
+    void endLoop();
+    void endIfThen();
+    void endIfThenElse();
+
+    void pushReturned(ExprType type);
+    void pushBuiltinReturned(ExprType type);
+
+    void emitCompareI32(JSOp compareOp, MCompare::CompareType compareType);
+    void emitCompareI64(JSOp compareOp, MCompare::CompareType compareType);
+    void emitCompareF32(JSOp compareOp, MCompare::CompareType compareType);
+    void emitCompareF64(JSOp compareOp, MCompare::CompareType compareType);
+
+    void emitAddI32();
+    void emitAddI64();
+    void emitAddF64();
+    void emitAddF32();
+    void emitSubtractI32();
+    void emitSubtractI64();
+    void emitSubtractF32();
+    void emitSubtractF64();
+    void emitMultiplyI32();
+    void emitMultiplyI64();
+    void emitMultiplyF32();
+    void emitMultiplyF64();
+    void emitQuotientI32();
+    void emitQuotientU32();
+    void emitQuotientI64();
+    void emitQuotientU64();
+    void emitRemainderI32();
+    void emitRemainderU32();
+    void emitRemainderI64();
+    void emitRemainderU64();
+    void emitDivideF32();
+    void emitDivideF64();
+    void emitMinI32();
+    void emitMaxI32();
+    void emitMinMaxI32(Assembler::Condition cond);
+    void emitMinF32();
+    void emitMaxF32();
+    void emitMinF64();
+    void emitMaxF64();
+    void emitCopysignF32();
+    void emitCopysignF64();
+    void emitOrI32();
+    void emitOrI64();
+    void emitAndI32();
+    void emitAndI64();
+    void emitXorI32();
+    void emitXorI64();
+    void emitShlI32();
+    void emitShlI64();
+    void emitShrI32();
+    void emitShrI64();
+    void emitShrU32();
+    void emitShrU64();
+    void emitRotrI32();
+    void emitRotrI64();
+    void emitRotlI32();
+    void emitRotlI64();
+    void emitEqzI32();
+    void emitEqzI64();
+    void emitClzI32();
+    void emitClzI64();
+    void emitCtzI32();
+    void emitCtzI64();
+    void emitPopcntI32();
+    void emitPopcntI64();
+    void emitBitNotI32();
+    void emitAbsI32();
+    void emitAbsF32();
+    void emitAbsF64();
+    void emitNegateI32();
+    void emitNegateF32();
+    void emitNegateF64();
+    void emitSqrtF32();
+    void emitSqrtF64();
+    template<bool isUnsigned> MOZ_MUST_USE bool emitTruncateF32ToI32();
+    template<bool isUnsigned> MOZ_MUST_USE bool emitTruncateF32ToI64();
+    template<bool isUnsigned> MOZ_MUST_USE bool emitTruncateF64ToI32();
+    template<bool isUnsigned> MOZ_MUST_USE bool emitTruncateF64ToI64();
+    void emitWrapI64ToI32();
+    void emitExtendI32ToI64();
+    void emitExtendU32ToI64();
+    void emitReinterpretF32AsI32();
+    void emitReinterpretF64AsI64();
+    void emitConvertF64ToF32();
+    void emitConvertI32ToF32();
+    void emitConvertU32ToF32();
+    void emitConvertI64ToF32();
+    void emitConvertU64ToF32();
+    void emitConvertF32ToF64();
+    void emitConvertI32ToF64();
+    void emitConvertU32ToF64();
+    void emitConvertI64ToF64();
+    void emitConvertU64ToF64();
+    void emitReinterpretI32AsF32();
+    void emitReinterpretI64AsF64();
+};
+
+void
+BaseCompiler::emitAddI32()
+{
+    int32_t c;
+    if (popConstI32(c)) {
+        RegI32 r = popI32();
+        masm.add32(Imm32(c), r.reg);
+        pushI32(r);
+    } else {
+        RegI32 r0, r1;
+        pop2xI32(&r0, &r1);
+        masm.add32(r1.reg, r0.reg);
+        freeI32(r1);
+        pushI32(r0);
+    }
+}
+
+void
+BaseCompiler::emitAddI64()
+{
+    // TODO / OPTIMIZE: Ditto check for constant here
+    RegI64 r0, r1;
+    pop2xI64(&r0, &r1);
+    masm.add64(r1.reg, r0.reg);
+    freeI64(r1);
+    pushI64(r0);
+}
+
+void
+BaseCompiler::emitAddF64()
+{
+    // TODO / OPTIMIZE: Ditto check for constant here
+    RegF64 r0, r1;
+    pop2xF64(&r0, &r1);
+    masm.addDouble(r1.reg, r0.reg);
+    freeF64(r1);
+    pushF64(r0);
+}
+
+void
+BaseCompiler::emitAddF32()
+{
+    // TODO / OPTIMIZE: Ditto check for constant here
+    RegF32 r0, r1;
+    pop2xF32(&r0, &r1);
+    addF32(r1, r0);
+    freeF32(r1);
+    pushF32(r0);
+}
+
+void
+BaseCompiler::emitSubtractI32()
+{
+    RegI32 r0, r1;
+    pop2xI32(&r0, &r1);
+    masm.sub32(r1.reg, r0.reg);
+    freeI32(r1);
+    pushI32(r0);
+}
+
+void
+BaseCompiler::emitSubtractI64()
+{
+    RegI64 r0, r1;
+    pop2xI64(&r0, &r1);
+    subI64(r1, r0);
+    freeI64(r1);
+    pushI64(r0);
+}
+
+void
+BaseCompiler::emitSubtractF32()
+{
+    RegF32 r0, r1;
+    pop2xF32(&r0, &r1);
+    subF32(r1, r0);
+    freeF32(r1);
+    pushF32(r0);
+}
+
+void
+BaseCompiler::emitSubtractF64()
+{
+    RegF64 r0, r1;
+    pop2xF64(&r0, &r1);
+    masm.subDouble(r1.reg, r0.reg);
+    freeF64(r1);
+    pushF64(r0);
+}
+
+void
+BaseCompiler::emitMultiplyI32()
+{
+    // TODO / OPTIMIZE: Multiplication by constant is common (bug 1275442)
+    RegI32 r0, r1;
+#if defined(JS_CODEGEN_X86) || defined(JS_CODEGEN_X64)
+    need2xI32(specific_eax, specific_edx);
+    r1 = popI32();
+    r0 = popI32ToSpecific(specific_eax);
+    freeI32(specific_edx);
+#else
+    pop2xI32(&r0, &r1);
+#endif
+    mulI32(r1, r0);
+    freeI32(r1);
+    pushI32(r0);
+}
+
+void
+BaseCompiler::emitMultiplyI64()
+{
+    // TODO / OPTIMIZE: Multiplication by constant is common (bug 1275442)
+    RegI64 r0, r1;
+#if defined(JS_CODEGEN_X64)
+    need2xI64(specific_rax, specific_rdx);
+    r1 = popI64();
+    r0 = popI64ToSpecific(specific_rax);
+    freeI64(specific_rdx);
+#elif defined(JS_CODEGEN_X86)
+    MOZ_CRASH("BaseCompiler platform hook: emitMultiplyI64");
+#else
+    pop2xI64(&r0, &r1);
+#endif
+    mulI64(r1, r0);
+    freeI64(r1);
+    pushI64(r0);
+}
+
+void
+BaseCompiler::emitMultiplyF32()
+{
+    RegF32 r0, r1;
+    pop2xF32(&r0, &r1);
+    mulF32(r1, r0);
+    freeF32(r1);
+    pushF32(r0);
+}
+
+void
+BaseCompiler::emitMultiplyF64()
+{
+    RegF64 r0, r1;
+    pop2xF64(&r0, &r1);
+    masm.mulDouble(r1.reg, r0.reg);
+    freeF64(r1);
+    pushF64(r0);
+}
+
+void
+BaseCompiler::emitQuotientI32()
+{
+    RegI32 r0, r1;
+#if defined(JS_CODEGEN_X86) || defined(JS_CODEGEN_X64)
+    need2xI32(specific_eax, specific_edx);
+    r1 = popI32();
+    r0 = popI32ToSpecific(specific_eax);
+    freeI32(specific_edx);
+#else
+    pop2xI32(&r0, &r1);
+#endif
+    quotientI32(r1, r0, IsUnsigned(false));
+    freeI32(r1);
+    pushI32(r0);
+}
+
+void
+BaseCompiler::emitQuotientU32()
+{
+    RegI32 r0, r1;
+#if defined(JS_CODEGEN_X86) || defined(JS_CODEGEN_X64)
+    need2xI32(specific_eax, specific_edx);
+    r1 = popI32();
+    r0 = popI32ToSpecific(specific_eax);
+    freeI32(specific_edx);
+#else
+    pop2xI32(&r0, &r1);
+#endif
+    quotientI32(r1, r0, IsUnsigned(true));
+    freeI32(r1);
+    pushI32(r0);
+}
+
+void
+BaseCompiler::emitQuotientI64()
+{
+    RegI64 r0, r1;
+#if defined(JS_CODEGEN_X64)
+    need2xI64(specific_rax, specific_rdx);
+    r1 = popI64();
+    r0 = popI64ToSpecific(specific_rax);
+    freeI64(specific_rdx);
+#elif defined(JS_CODEGEN_X86)
+    MOZ_CRASH("BaseCompiler platform hook: emitQuotientI64");
+#else
+    pop2xI64(&r0, &r1);
+#endif
+    quotientI64(r1, r0, IsUnsigned(false));
+    freeI64(r1);
+    pushI64(r0);
+}
+
+void
+BaseCompiler::emitQuotientU64()
+{
+    RegI64 r0, r1;
+#if defined(JS_CODEGEN_X64)
+    need2xI64(specific_rax, specific_rdx);
+    r1 = popI64();
+    r0 = popI64ToSpecific(specific_rax);
+    freeI64(specific_rdx);
+#elif defined(JS_CODEGEN_X86)
+    MOZ_CRASH("BaseCompiler platform hook: emitQuotientU64");
+#else
+    pop2xI64(&r0, &r1);
+#endif
+    quotientI64(r1, r0, IsUnsigned(true));
+    freeI64(r1);
+    pushI64(r0);
+}
+
+void
+BaseCompiler::emitRemainderI32()
+{
+    RegI32 r0, r1;
+#if defined(JS_CODEGEN_X86) || defined(JS_CODEGEN_X64)
+    need2xI32(specific_eax, specific_edx);
+    r1 = popI32();
+    r0 = popI32ToSpecific(specific_eax);
+    freeI32(specific_edx);
+#else
+    pop2xI32(&r0, &r1);
+#endif
+    // TODO / OPTIMIZE: Fast case if lhs >= 0 and rhs is power of two.
+    remainderI32(r1, r0, IsUnsigned(false));
+    freeI32(r1);
+    pushI32(r0);
+}
+
+void
+BaseCompiler::emitRemainderU32()
+{
+    RegI32 r0, r1;
+#if defined(JS_CODEGEN_X86) || defined(JS_CODEGEN_X64)
+    need2xI32(specific_eax, specific_edx);
+    r1 = popI32();
+    r0 = popI32ToSpecific(specific_eax);
+    freeI32(specific_edx);
+#else
+    pop2xI32(&r0, &r1);
+#endif
+    // TODO / OPTIMIZE: Fast case if lhs >= 0 and rhs is power of two.
+    remainderI32(r1, r0, IsUnsigned(true));
+    freeI32(r1);
+    pushI32(r0);
+}
+
+void
+BaseCompiler::emitRemainderI64()
+{
+    RegI64 r0, r1;
+#if defined(JS_CODEGEN_X64)
+    need2xI64(specific_rax, specific_rdx);
+    r1 = popI64();
+    r0 = popI64ToSpecific(specific_rax);
+    freeI64(specific_rdx);
+#elif defined(JS_CODEGEN_X86)
+    MOZ_CRASH("BaseCompiler platform hook: emitRemainderI64");
+#else
+    pop2xI64(&r0, &r1);
+#endif
+    remainderI64(r1, r0, IsUnsigned(false));
+    freeI64(r1);
+    pushI64(r0);
+}
+
+void
+BaseCompiler::emitRemainderU64()
+{
+    RegI64 r0, r1;
+#if defined(JS_CODEGEN_X64)
+    need2xI64(specific_rax, specific_rdx);
+    r1 = popI64();
+    r0 = popI64ToSpecific(specific_rax);
+    freeI64(specific_rdx);
+#elif defined(JS_CODEGEN_X86)
+    MOZ_CRASH("BaseCompiler platform hook: emitRemainderU64");
+#else
+    pop2xI64(&r0, &r1);
+#endif
+    remainderI64(r1, r0, IsUnsigned(true));
+    freeI64(r1);
+    pushI64(r0);
+}
+
+void
+BaseCompiler::emitDivideF32()
+{
+    RegF32 r0, r1;
+    pop2xF32(&r0, &r1);
+    divideF32(r1, r0);
+    freeF32(r1);
+    pushF32(r0);
+}
+
+void
+BaseCompiler::emitDivideF64()
+{
+    RegF64 r0, r1;
+    pop2xF64(&r0, &r1);
+    divideF64(r1, r0);
+    freeF64(r1);
+    pushF64(r0);
+}
+
+void
+BaseCompiler::emitMinI32()
+{
+    emitMinMaxI32(Assembler::LessThan);
+}
+
+void
+BaseCompiler::emitMaxI32()
+{
+    emitMinMaxI32(Assembler::GreaterThan);
+}
+
+void
+BaseCompiler::emitMinMaxI32(Assembler::Condition cond)
+{
+    Label done;
+    RegI32 r0, r1;
+    pop2xI32(&r0, &r1);
+    // TODO / OPTIMIZE: Conditional move
+    masm.branch32(cond, r0.reg, r1.reg, &done);
+    moveI32(r1, r0);
+    masm.bind(&done);
+    freeI32(r1);
+    pushI32(r0);
+}
+
+void
+BaseCompiler::emitMinF32()
+{
+    RegF32 r0, r1;
+    pop2xF32(&r0, &r1);
+    minMaxF32(r1, r0, IsMax(false));
+    freeF32(r1);
+    pushF32(r0);
+}
+
+void
+BaseCompiler::emitMaxF32()
+{
+    RegF32 r0, r1;
+    pop2xF32(&r0, &r1);
+    minMaxF32(r1, r0, IsMax(true));
+    freeF32(r1);
+    pushF32(r0);
+}
+
+void
+BaseCompiler::emitMinF64()
+{
+    RegF64 r0, r1;
+    pop2xF64(&r0, &r1);
+    minMaxF64(r1, r0, IsMax(false));
+    freeF64(r1);
+    pushF64(r0);
+}
+
+void
+BaseCompiler::emitMaxF64()
+{
+    RegF64 r0, r1;
+    pop2xF64(&r0, &r1);
+    minMaxF64(r1, r0, IsMax(true));
+    freeF64(r1);
+    pushF64(r0);
+}
+
+void
+BaseCompiler::emitCopysignF32()
+{
+    RegF32 r0, r1;
+    pop2xF32(&r0, &r1);
+    RegI32 i0 = needI32();
+    RegI32 i1 = needI32();
+    reinterpretF32AsI32(r0, i0);
+    reinterpretF32AsI32(r1, i1);
+    masm.and32(Imm32(INT32_MAX), i0.reg);
+    masm.and32(Imm32(INT32_MIN), i1.reg);
+    masm.or32(i1.reg, i0.reg);
+    reinterpretI32AsF32(i0, r0);
+    freeI32(i0);
+    freeI32(i1);
+    freeF32(r1);
+    pushF32(r0);
+}
+
+void
+BaseCompiler::emitCopysignF64()
+{
+    RegF64 r0, r1;
+    pop2xF64(&r0, &r1);
+    RegI64 x0 = needI64();
+    RegI64 x1 = needI64();
+    reinterpretF64AsI64(r0, x0);
+    reinterpretF64AsI64(r1, x1);
+    masm.and64(Imm64(INT64_MAX), x0.reg);
+    masm.and64(Imm64(INT64_MIN), x1.reg);
+    masm.or64(x1.reg, x0.reg);
+    reinterpretI64AsF64(x0, r0);
+    freeI64(x0);
+    freeI64(x1);
+    freeF64(r1);
+    pushF64(r0);
+}
+
+void
+BaseCompiler::emitOrI32()
+{
+    RegI32 r0, r1;
+    pop2xI32(&r0, &r1);
+    masm.or32(r1.reg, r0.reg);
+    freeI32(r1);
+    pushI32(r0);
+}
+
+void
+BaseCompiler::emitOrI64()
+{
+    RegI64 r0, r1;
+    pop2xI64(&r0, &r1);
+    orI64(r1, r0);
+    freeI64(r1);
+    pushI64(r0);
+}
+
+void
+BaseCompiler::emitAndI32()
+{
+    RegI32 r0, r1;
+    pop2xI32(&r0, &r1);
+    masm.and32(r1.reg, r0.reg);
+    freeI32(r1);
+    pushI32(r0);
+}
+
+void
+BaseCompiler::emitAndI64()
+{
+    RegI64 r0, r1;
+    pop2xI64(&r0, &r1);
+    andI64(r1, r0);
+    freeI64(r1);
+    pushI64(r0);
+}
+
+void
+BaseCompiler::emitXorI32()
+{
+    RegI32 r0, r1;
+    pop2xI32(&r0, &r1);
+    xorI32(r1, r0);
+    freeI32(r1);
+    pushI32(r0);
+}
+
+void
+BaseCompiler::emitXorI64()
+{
+    RegI64 r0, r1;
+    pop2xI64(&r0, &r1);
+    xorI64(r1, r0);
+    freeI64(r1);
+    pushI64(r0);
+}
+
+void
+BaseCompiler::emitShlI32()
+{
+    int32_t c;
+    if (popConstI32(c)) {
+        RegI32 r = popI32();
+        lshiftI32(c, r);
+        pushI32(r);
+    } else {
+        RegI32 r0, r1;
+#if defined(JS_CODEGEN_X86) || defined(JS_CODEGEN_X64)
+        r1 = popI32(specific_ecx);
+        r0 = popI32();
+#else
+        pop2xI32(&r0, &r1);
+#endif
+        lshiftI32(r1, r0);
+        freeI32(r1);
+        pushI32(r0);
+    }
+}
+
+void
+BaseCompiler::emitShlI64()
+{
+    // TODO / OPTIMIZE: Constant rhs
+    RegI64 r0, r1;
+#if defined(JS_CODEGEN_X64)
+    r1 = popI64(specific_rcx);
+    r0 = popI64();
+#elif defined(JS_CODEGEN_X86)
+    MOZ_CRASH("BaseCompiler platform hook: emitShlI64");
+#else
+    pop2xI64(&r0, &r1);
+#endif
+    lshiftI64(r1, r0);
+    freeI64(r1);
+    pushI64(r0);
+}
+
+void
+BaseCompiler::emitShrI32()
+{
+    int32_t c;
+    if (popConstI32(c)) {
+        RegI32 r = popI32();
+        rshiftI32(c, r);
+        pushI32(r);
+    } else {
+        RegI32 r0, r1;
+#if defined(JS_CODEGEN_X86) || defined(JS_CODEGEN_X64)
+        r1 = popI32(specific_ecx);
+        r0 = popI32();
+#else
+        pop2xI32(&r0, &r1);
+#endif
+        rshiftI32(r1, r0);
+        freeI32(r1);
+        pushI32(r0);
+    }
+}
+
+void
+BaseCompiler::emitShrI64()
+{
+    // TODO / OPTIMIZE: Constant rhs
+    RegI64 r0, r1;
+#if defined(JS_CODEGEN_X64)
+    r1 = popI64(specific_rcx);
+    r0 = popI64();
+#elif defined(JS_CODEGEN_X86)
+    MOZ_CRASH("BaseCompiler platform hook: emitShrI64");
+#else
+    pop2xI64(&r0, &r1);
+#endif
+    rshiftI64(r1, r0);
+    freeI64(r1);
+    pushI64(r0);
+}
+
+void
+BaseCompiler::emitShrU32()
+{
+    int32_t c;
+    if (popConstI32(c)) {
+        RegI32 r = popI32();
+        rshiftU32(c, r);
+        pushI32(r);
+    } else {
+        RegI32 r0, r1;
+#if defined(JS_CODEGEN_X86) || defined(JS_CODEGEN_X64)
+        r1 = popI32(specific_ecx);
+        r0 = popI32();
+#else
+        pop2xI32(&r0, &r1);
+#endif
+        rshiftU32(r1, r0);
+        freeI32(r1);
+        pushI32(r0);
+    }
+}
+
+void
+BaseCompiler::emitShrU64()
+{
+    // TODO / OPTIMIZE: Constant rhs
+    RegI64 r0, r1;
+#if defined(JS_CODEGEN_X64)
+    r1 = popI64(specific_rcx);
+    r0 = popI64();
+#elif defined(JS_CODEGEN_X86)
+    MOZ_CRASH("BaseCompiler platform hook: emitShrUI64");
+#else
+    pop2xI64(&r0, &r1);
+#endif
+    rshiftU64(r1, r0);
+    freeI64(r1);
+    pushI64(r0);
+}
+
+void
+BaseCompiler::emitRotrI32()
+{
+    // TODO / OPTIMIZE: Constant rhs
+    RegI32 r0, r1;
+#if defined(JS_CODEGEN_X86) || defined(JS_CODEGEN_X64)
+    r1 = popI32(specific_ecx);
+    r0 = popI32();
+#else
+    pop2xI32(&r0, &r1);
+#endif
+    rotateRightI32(r1, r0);
+    freeI32(r1);
+    pushI32(r0);
+}
+
+void
+BaseCompiler::emitRotrI64()
+{
+    // TODO / OPTIMIZE: Constant rhs
+    RegI64 r0, r1;
+#if defined(JS_CODEGEN_X64)
+    r1 = popI64(specific_rcx);
+    r0 = popI64();
+#elif defined(JS_CODEGEN_X86)
+    MOZ_CRASH("BaseCompiler platform hook: emitRotrI64");
+#else
+    pop2xI64(&r0, &r1);
+#endif
+    rotateRightI64(r1, r0);
+    freeI64(r1);
+    pushI64(r0);
+}
+
+void
+BaseCompiler::emitRotlI32()
+{
+    // TODO / OPTIMIZE: Constant rhs
+    RegI32 r0, r1;
+#if defined(JS_CODEGEN_X86) || defined(JS_CODEGEN_X64)
+    r1 = popI32(specific_ecx);
+    r0 = popI32();
+#else
+    pop2xI32(&r0, &r1);
+#endif
+    rotateLeftI32(r1, r0);
+    freeI32(r1);
+    pushI32(r0);
+}
+
+void
+BaseCompiler::emitRotlI64()
+{
+    // TODO / OPTIMIZE: Constant rhs
+    RegI64 r0, r1;
+#if defined(JS_CODEGEN_X64)
+    r1 = popI64(specific_rcx);
+    r0 = popI64();
+#elif defined(JS_CODEGEN_X86)
+    MOZ_CRASH("BaseCompiler platform hook: emitRotlI64");
+#else
+    pop2xI64(&r0, &r1);
+#endif
+    rotateLeftI64(r1, r0);
+    freeI64(r1);
+    pushI64(r0);
+}
+
+void
+BaseCompiler::emitEqzI32(