Merge inbound to central, a=merge
authorWes Kocher <wkocher@mozilla.com>
Fri, 04 Sep 2015 15:34:42 -0700
changeset 260972 e816a7a854a333bda1447ab6c5b633233f322669
parent 260867 d41fa6f2f1da5f2c2740414bb25e5d76982f8397 (current diff)
parent 260971 1cec2d7bf26bbfdaf9a46b8c388c851fa320f413 (diff)
child 260973 91107089dffab0af7c83fab0d79e2134cf0b45d1
child 260985 0474d05de9d5219aa36d10a40dfe86b384ef4530
child 261004 2ffa79d48ab5cfaf4d3afb2cd6a354afcf396e07
child 261052 187273e992bfbb9463ba08366a61df20c7c148ed
push id29329
push userkwierso@gmail.com
push dateFri, 04 Sep 2015 22:34:50 +0000
treeherdermozilla-central@e816a7a854a3 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmerge
milestone43.0a1
first release with
nightly linux32
e816a7a854a3 / 43.0a1 / 20150905030205 / files
nightly linux64
e816a7a854a3 / 43.0a1 / 20150905030205 / files
nightly mac
e816a7a854a3 / 43.0a1 / 20150905030205 / files
nightly win32
e816a7a854a3 / 43.0a1 / 20150905030205 / files
nightly win64
e816a7a854a3 / 43.0a1 / 20150905030205 / files
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
releases
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Merge inbound to central, a=merge
testing/web-platform/meta/content-security-policy/blink-contrib-2/scripthash-basic-blocked.sub.html.ini
testing/web-platform/meta/content-security-policy/blink-contrib-2/scripthash-ignore-unsafeinline.sub.html.ini
testing/web-platform/meta/content-security-policy/blink-contrib/combine-multiple-policies.sub.html.ini
testing/web-platform/meta/content-security-policy/blink-contrib/connect-src-eventsource-blocked.sub.html.ini
testing/web-platform/meta/content-security-policy/blink-contrib/connect-src-xmlhttprequest-redirect-to-blocked.sub.html.ini
testing/web-platform/meta/content-security-policy/blink-contrib/injected-inline-script-allowed.sub.html.ini
testing/web-platform/meta/content-security-policy/blink-contrib/worker-connect-src-allowed.sub.html.ini
testing/web-platform/meta/content-security-policy/blink-contrib/worker-connect-src-blocked.sub.html.ini
testing/web-platform/meta/content-security-policy/script-src/script-src-1_9.html.ini
testing/web-platform/meta/html/dom/documents/dom-tree-accessors/Document.currentScript.html.ini
testing/web-platform/meta/html/dom/documents/dom-tree-accessors/document.forms.html.ini
testing/web-platform/meta/quirks-mode/font-element-text-decoration-color/001-a.html.ini
testing/web-platform/meta/quirks-mode/font-element-text-decoration-color/001-q.html.ini
testing/web-platform/meta/quirks-mode/font-element-text-decoration-color/001-s.html.ini
testing/web-platform/meta/quirks-mode/font-element-text-decoration-color/001-x.xhtml.ini
testing/web-platform/mozilla/meta/service-workers/service-worker/registration-service-worker-attributes.https.html.ini
testing/web-platform/tests/content-security-policy/blink-contrib/combine-multiple-policies.sub.html
testing/web-platform/tests/content-security-policy/blink-contrib/combine-multiple-policies.sub.html.sub.headers
testing/web-platform/tests/content-security-policy/blink-contrib/frame-src-redirect-blocked.sub.html
testing/web-platform/tests/content-security-policy/blink-contrib/frame-src-redirect-blocked.sub.html.sub.headers
testing/web-platform/tests/content-security-policy/blink-contrib/report-uri-scheme-relative.sub.html
testing/web-platform/tests/content-security-policy/blink-contrib/report-uri-scheme-relative.sub.html.sub.headers
testing/web-platform/tests/content-security-policy/blink-contrib/resources/alert-fail.js
testing/web-platform/tests/content-security-policy/blink-contrib/resources/shared-worker-make-xhr-allowed.js
testing/web-platform/tests/content-security-policy/blink-contrib/resources/shared-worker-make-xhr-blocked.js
testing/web-platform/tests/content-security-policy/blink-contrib/resources/shared-worker-make-xhr-blocked.js.sub.headers
testing/web-platform/tests/content-security-policy/blink-contrib/resources/worker-make-xhr-blocked.js
testing/web-platform/tests/content-security-policy/blink-contrib/resources/worker-make-xhr-blocked.js.sub.headers
testing/web-platform/tests/content-security-policy/blink-contrib/resources/worker-make-xhr.js
testing/web-platform/tests/content-security-policy/script-src/script-src-1_9.html
testing/web-platform/tests/content-security-policy/script-src/script-src-1_9.html.sub.headers
testing/web-platform/tests/html/dom/documents/dom-tree-accessors/Document.currentScript.html
testing/web-platform/tests/quirks-mode/font-element-text-decoration-color/001-a.html
testing/web-platform/tests/quirks-mode/font-element-text-decoration-color/001-q.html
testing/web-platform/tests/quirks-mode/font-element-text-decoration-color/001-ref.html
testing/web-platform/tests/quirks-mode/font-element-text-decoration-color/001-s.html
testing/web-platform/tests/quirks-mode/font-element-text-decoration-color/001-x.xhtml
toolkit/components/extensions/test/mochitest/head.js
--- a/accessible/jsat/Traversal.jsm
+++ b/accessible/jsat/Traversal.jsm
@@ -219,16 +219,38 @@ this.TraversalRules = { // jshint ignore
 
   Landmark: new BaseTraversalRule(
     [],
     function Landmark_match(aAccessible) {
       return Utils.getLandmarkName(aAccessible) ? Filters.MATCH :
         Filters.IGNORE;
     }, null, true),
 
+  /* A rule for Android's section navigation, lands on landmarks, regions, and
+     on headings to aid navigation of traditionally structured documents */
+  Section: new BaseTraversalRule(
+    [],
+    function Section_match(aAccessible) {
+      if (aAccessible.role === Roles.HEADING) {
+        return Filters.MATCH;
+      }
+
+      let matchedRole = Utils.matchRoles(aAccessible, [
+        'banner',
+        'complementary',
+        'contentinfo',
+        'main',
+        'navigation',
+        'search',
+        'region'
+        ]);
+
+      return matchedRole ? Filters.MATCH : Filters.IGNORE;
+    }, null, true),
+
   Entry: new BaseTraversalRule(
     [Roles.ENTRY,
      Roles.PASSWORD_TEXT]),
 
   FormElement: new BaseTraversalRule(
     [Roles.PUSHBUTTON,
      Roles.SPINBUTTON,
      Roles.TOGGLE_BUTTON,
--- a/accessible/tests/mochitest/jsat/doc_traversal.html
+++ b/accessible/tests/mochitest/jsat/doc_traversal.html
@@ -77,26 +77,30 @@
         <li id="listitem-3-3">Clojure</li>
         <li id="listitem-3-4"><strong>Standard</strong> Lisp</li>
         <li id="listitem-3-5"><a id="link-0" href="#">Common</a> Lisp</li>
         <li id="listitem-3-6"><input id="checkbox-1-5" type="checkbox"> LeLisp</li>
       </ol>
     </li>
     <li id="listitem-2-3">JavaScript</li>
   </ul>
-  <h6 id="heading-5">The last (visible) one!</h6>
-  <img id="image-1" src="http://example.com" alt="">
-  <img id="image-2" src="../moz.png" alt="stuff">
-  <div id="image-3" tabindex="0" role="img">Not actually an image</div>
-  <h4 id="heading-6" aria-hidden="true">Hidden header</h4>
-  <a id="link-1" href="http://www.mozilla.org">Link</a>
-  <a id="anchor-1">Words</a>
-  <a id="link-2" href="http://www.mozilla.org">Link the second</a>
-  <a id="anchor-2">Sentences</a>
-  <a id="link-3" href="http://www.example.com">Link the third</a>
+  <section>
+    <h6 id="heading-5">The last (visible) one!</h6>
+    <img id="image-1" src="http://example.com" alt="">
+    <img id="image-2" src="../moz.png" alt="stuff">
+    <div id="image-3" tabindex="0" role="img">Not actually an image</div>
+  </section>
+  <section>
+    <h4 id="heading-6" aria-hidden="true">Hidden header</h4>
+    <a id="link-1" href="http://www.mozilla.org">Link</a>
+    <a id="anchor-1">Words</a>
+    <a id="link-2" href="http://www.mozilla.org">Link the second</a>
+    <a id="anchor-2">Sentences</a>
+    <a id="link-3" href="http://www.example.com">Link the third</a>
+  </section>
   <hr id="separator-1">
   <h6 id="heading-6"></h6>
   <table id="table-1">
   <tr>
     <td>3</td>
     <td>1</td>
   </tr>
   <tr>
--- a/accessible/tests/mochitest/jsat/test_traversal_helper.html
+++ b/accessible/tests/mochitest/jsat/test_traversal_helper.html
@@ -78,17 +78,19 @@
       vc = docAcc.virtualCursor;
 
       testTraversalHelper('Landmark',
         ['heading-1', 'heading-2', 'statusbar-1']);
 
       testTraversalHelper('List',
         ['Programming Language', 'listitem-2-1', 'listitem-3-1']);
 
-      vc.position = null;
+      testTraversalHelper('Section',
+        ['heading-1', 'heading-2', 'heading-3',
+         'heading-5', 'link-1', 'statusbar-1']);
 
       SimpleTest.finish();
     }
 
     SimpleTest.waitForExplicitFinish();
     addLoadEvent(function () {
       /* We open a new browser because we need to test with a top-level content
          document. */
--- a/b2g/app/b2g.js
+++ b/b2g/app/b2g.js
@@ -198,16 +198,19 @@ pref("privacy.item.geolocation", true);
 pref("privacy.item.siteSettings", true);
 pref("privacy.item.syncAccount", true);
 
 // base url for the wifi geolocation network provider
 pref("geo.provider.use_mls", false);
 pref("geo.cell.scan", true);
 pref("geo.wifi.uri", "https://location.services.mozilla.com/v1/geolocate?key=%MOZILLA_API_KEY%");
 
+// base url for the stumbler
+pref("geo.stumbler.url", "https://location.services.mozilla.com/v1/geosubmit?key=%MOZILLA_API_KEY%");
+
 // enable geo
 pref("geo.enabled", true);
 
 // content sink control -- controls responsiveness during page load
 // see https://bugzilla.mozilla.org/show_bug.cgi?id=481566#c9
 pref("content.sink.enable_perf_mode",  2); // 0 - switch, 1 - interactive, 2 - perf
 pref("content.sink.pending_event_mode", 0);
 pref("content.sink.perf_deflect_count", 1000000);
--- a/b2g/chrome/content/content.css
+++ b/b2g/chrome/content/content.css
@@ -12,22 +12,22 @@ xul|window xul|scrollbar {
 
 /* Bug 1041576 - Scrollable with scrollgrab should not have scrollbars */
 @-moz-document domain(system.gaiamobile.org) {
   .browser-container > xul|scrollbar {
     display: none;
   }
 }
 
-html xul|scrollbar[root="true"] {
+xul|scrollbar[root="true"] {
   position: relative;
   z-index: 2147483647;
 }
 
-html xul|scrollbar {
+xul|scrollbar {
   -moz-appearance: none !important;
   background-color: transparent !important;
   background-image: none !important;
   border: 0px solid transparent !important;
   pointer-events: none;
 }
 
 /* Scrollbar code will reset the margin to the correct side depending on
--- a/browser/base/content/test/general/browser_tabs_isActive.js
+++ b/browser/base/content/test/general/browser_tabs_isActive.js
@@ -1,29 +1,152 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
-function test() {
-  test_tab("about:blank");
-  test_tab("about:license");
+// Test for the docshell active state of local and remote browsers.
+
+const kTestPage = "http://example.org/browser/browser/base/content/test/general/dummy_page.html";
+
+function promiseNewTabSwitched() {
+  return new Promise(resolve => {
+    gBrowser.addEventListener("TabSwitchDone", function onSwitch() {
+      gBrowser.removeEventListener("TabSwitchDone", onSwitch);
+      executeSoon(resolve);
+    });
+  });
+}
+
+function getParentTabState(aTab) {
+  return aTab.linkedBrowser.docShellIsActive;
+}
+
+function getChildTabState(aTab) {
+  return ContentTask.spawn(aTab.linkedBrowser, {}, function* () {
+    return docShell.isActive;
+  });
+}
+
+function checkState(parentSide, childSide, value, message) {
+  is(parentSide, value, message + " (parent side)");
+  is(childSide, value, message + " (child side)");
+}
+
+function waitForMs(aMs) {
+  return new Promise((resolve) => {
+    setTimeout(done, aMs);
+    function done() {
+      resolve(true);
+    }
+  });
 }
 
-function test_tab(url) {
-  let originalTab = gBrowser.selectedTab;
+add_task(function *() {
+  let url = kTestPage;
+  let originalTab = gBrowser.selectedTab; // test tab
   let newTab = gBrowser.addTab(url, {skipAnimation: true});
-  is(tabIsActive(newTab), false, "newly added " + url + " tab is not active");
-  is(tabIsActive(originalTab), true, "original tab is active initially");
+  let parentSide, childSide;
 
+  // new tab added but not selected checks
+  parentSide = getParentTabState(newTab);
+  childSide = yield getChildTabState(newTab);
+  checkState(parentSide, childSide, false, "newly added " + url + " tab is not active");
+  parentSide = getParentTabState(originalTab);
+  childSide = yield getChildTabState(originalTab);
+  checkState(parentSide, childSide, true, "original tab is active initially");
+
+  // select the newly added tab and wait  for TabSwitchDone event
+  let tabSwitchedPromise = promiseNewTabSwitched();
   gBrowser.selectedTab = newTab;
-  is(tabIsActive(newTab), true, "newly added " + url + " tab is active after selection");
-  is(tabIsActive(originalTab), false, "original tab is not active while unselected");
+  yield tabSwitchedPromise;
+
+  if (Services.appinfo.browserTabsRemoteAutostart) {
+    ok(newTab.linkedBrowser.isRemoteBrowser, "for testing we need a remote tab");
+  }
 
+  // check active state of both tabs
+  parentSide = getParentTabState(newTab);
+  childSide = yield getChildTabState(newTab);
+  checkState(parentSide, childSide, true, "newly added " + url + " tab is active after selection");
+  parentSide = getParentTabState(originalTab);
+  childSide = yield getChildTabState(originalTab);
+  checkState(parentSide, childSide, false, "original tab is not active while unselected");
+
+  // switch back to the original test tab and wait for TabSwitchDone event
+  tabSwitchedPromise = promiseNewTabSwitched();
   gBrowser.selectedTab = originalTab;
-  is(tabIsActive(newTab), false, "newly added " + url + " tab is not active after switch back");
-  is(tabIsActive(originalTab), true, "original tab is active again after switch back");
-  
+  yield tabSwitchedPromise;
+
+  // check active state of both tabs
+  parentSide = getParentTabState(newTab);
+  childSide = yield getChildTabState(newTab);
+  checkState(parentSide, childSide, false, "newly added " + url + " tab is not active after switch back");
+  parentSide = getParentTabState(originalTab);
+  childSide = yield getChildTabState(originalTab);
+  checkState(parentSide, childSide, true, "original tab is active again after switch back");
+
+  // switch to the new tab and wait for TabSwitchDone event
+  tabSwitchedPromise = promiseNewTabSwitched();
+  gBrowser.selectedTab = newTab;
+  yield tabSwitchedPromise;
+
+  // check active state of both tabs
+  parentSide = getParentTabState(newTab);
+  childSide = yield getChildTabState(newTab);
+  checkState(parentSide, childSide, true, "newly added " + url + " tab is not active after switch back");
+  parentSide = getParentTabState(originalTab);
+  childSide = yield getChildTabState(originalTab);
+  checkState(parentSide, childSide, false, "original tab is active again after switch back");
+
   gBrowser.removeTab(newTab);
-}
+});
+
+add_task(function *() {
+  let url = "about:about";
+  let originalTab = gBrowser.selectedTab; // test tab
+  let newTab = gBrowser.addTab(url, {skipAnimation: true});
+  let parentSide, childSide;
+
+  parentSide = getParentTabState(newTab);
+  childSide = yield getChildTabState(newTab);
+  checkState(parentSide, childSide, false, "newly added " + url + " tab is not active");
+  parentSide = getParentTabState(originalTab);
+  childSide = yield getChildTabState(originalTab);
+  checkState(parentSide, childSide, true, "original tab is active initially");
+
+  let tabSwitchedPromise = promiseNewTabSwitched();
+  gBrowser.selectedTab = newTab;
+  yield tabSwitchedPromise;
+
+  if (Services.appinfo.browserTabsRemoteAutostart) {
+    ok(!newTab.linkedBrowser.isRemoteBrowser, "for testing we need a local tab");
+  }
 
-function tabIsActive(tab) {
-  let browser = tab.linkedBrowser;
-  return browser.docShell.isActive;
-}
+  parentSide = getParentTabState(newTab);
+  childSide = yield getChildTabState(newTab);
+  checkState(parentSide, childSide, true, "newly added " + url + " tab is active after selection");
+  parentSide = getParentTabState(originalTab);
+  childSide = yield getChildTabState(originalTab);
+  checkState(parentSide, childSide, false, "original tab is not active while unselected");
+
+  tabSwitchedPromise = promiseNewTabSwitched();
+  gBrowser.selectedTab = originalTab;
+  yield tabSwitchedPromise;
+
+  parentSide = getParentTabState(newTab);
+  childSide = yield getChildTabState(newTab);
+  checkState(parentSide, childSide, false, "newly added " + url + " tab is not active after switch back");
+  parentSide = getParentTabState(originalTab);
+  childSide = yield getChildTabState(originalTab);
+  checkState(parentSide, childSide, true, "original tab is active again after switch back");
+
+  tabSwitchedPromise = promiseNewTabSwitched();
+  gBrowser.selectedTab = newTab;
+  yield tabSwitchedPromise;
+
+  parentSide = getParentTabState(newTab);
+  childSide = yield getChildTabState(newTab);
+  checkState(parentSide, childSide, true, "newly added " + url + " tab is not active after switch back");
+  parentSide = getParentTabState(originalTab);
+  childSide = yield getChildTabState(originalTab);
+  checkState(parentSide, childSide, false, "original tab is active again after switch back");
+
+  gBrowser.removeTab(newTab);
+});
--- a/browser/components/extensions/ext-browserAction.js
+++ b/browser/components/extensions/ext-browserAction.js
@@ -135,16 +135,17 @@ BrowserAction.prototype = {
 
         let document = node.ownerDocument;
         let panel = document.createElement("panel");
         panel.setAttribute("class", "browser-action-panel");
         panel.setAttribute("type", "arrow");
         panel.setAttribute("flip", "slide");
         node.appendChild(panel);
 
+        const XUL_NS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
         let browser = document.createElementNS(XUL_NS, "browser");
         browser.setAttribute("type", "content");
         browser.setAttribute("disableglobalhistory", "true");
         browser.setAttribute("width", "500");
         browser.setAttribute("height", "500");
         panel.appendChild(browser);
 
         let loadListener = () => {
--- a/browser/components/extensions/moz.build
+++ b/browser/components/extensions/moz.build
@@ -1,7 +1,9 @@
 # -*- Mode: python; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 40 -*-
 # vim: set filetype=python:
 # This Source Code Form is subject to the terms of the Mozilla Public
 # License, v. 2.0. If a copy of the MPL was not distributed with this
 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
 
 JAR_MANIFESTS += ['jar.mn']
+
+BROWSER_CHROME_MANIFESTS += ['test/browser/browser.ini']
new file mode 100644
--- /dev/null
+++ b/browser/components/extensions/test/browser/browser.ini
@@ -0,0 +1,8 @@
+[DEFAULT]
+skip-if = os == 'android' || buildapp == 'b2g' || os == 'mac'
+
+[browser_extensions_simple.js]
+[browser_ext_browserAction_simple.js]
+[browser_ext_tabs_executeScript.js]
+[browser_ext_tabs_query.js]
+[browser_ext_tabs_update.js]
new file mode 100644
--- /dev/null
+++ b/browser/components/extensions/test/browser/browser_ext_browserAction_simple.js
@@ -0,0 +1,36 @@
+add_task(function* () {
+  let extension = ExtensionTestUtils.loadExtension({
+    manifest: {
+      "browser_action": {
+        "default_popup": "popup.html"
+      }
+    },
+
+    files: {
+      "popup.html": `
+      <!DOCTYPE html>
+      <html><body>
+      <script src="popup.js"></script>
+      </body></html>
+      `,
+
+      "popup.js": function() {
+        browser.runtime.sendMessage("from-popup");
+      }
+    },
+
+    background: function() {
+      browser.runtime.onMessage.addListener(msg => {
+        browser.test.assertEq(msg, "from-popup", "correct message received");
+        browser.test.notifyPass("browser_action.simple");
+      });
+    },
+  });
+
+  yield extension.startup();
+
+  // FIXME: Should really test opening the popup here.
+
+  yield extension.awaitFinish("browser_action.simple");
+  yield extension.unload();
+});
new file mode 100644
--- /dev/null
+++ b/browser/components/extensions/test/browser/browser_ext_tabs_executeScript.js
@@ -0,0 +1,32 @@
+add_task(function* () {
+  let tab = yield BrowserTestUtils.openNewForegroundTab(gBrowser, "http://mochi.test:8888/");
+
+  let extension = ExtensionTestUtils.loadExtension({
+    manifest: {
+      "permissions": ["tabs"]
+    },
+
+    background: function() {
+      browser.runtime.onMessage.addListener((msg, sender) => {
+        browser.test.assertEq(msg, "script ran", "script ran");
+        browser.test.notifyPass("executeScript");
+      });
+
+      browser.tabs.executeScript({
+        file: "script.js"
+      });
+    },
+
+    files: {
+      "script.js": function() {
+        browser.runtime.sendMessage("script ran");
+      }
+    }
+  });
+
+  yield extension.startup();
+  yield extension.awaitFinish("executeScript");
+  yield extension.unload();
+
+  yield BrowserTestUtils.removeTab(tab);
+});
new file mode 100644
--- /dev/null
+++ b/browser/components/extensions/test/browser/browser_ext_tabs_query.js
@@ -0,0 +1,48 @@
+add_task(function* () {
+  let tab1 = yield BrowserTestUtils.openNewForegroundTab(gBrowser, "about:robots");
+  let tab2 = yield BrowserTestUtils.openNewForegroundTab(gBrowser, "about:config");
+
+  gBrowser.selectedTab = tab1;
+
+  let extension = ExtensionTestUtils.loadExtension({
+    manifest: {
+      "permissions": ["tabs"]
+    },
+
+    background: function() {
+      browser.tabs.query({
+        lastFocusedWindow: true
+      }, function(tabs) {
+        browser.test.assertEq(tabs.length, 3, "should have three tabs");
+
+        tabs.sort(function (tab1, tab2) { return tab1.index - tab2.index; });
+
+        browser.test.assertEq(tabs[0].url, "about:blank", "first tab blank");
+        tabs.shift();
+
+        browser.test.assertTrue(tabs[0].active, "tab 0 active");
+        browser.test.assertFalse(tabs[1].active, "tab 1 inactive");
+
+        browser.test.assertFalse(tabs[0].pinned, "tab 0 unpinned");
+        browser.test.assertFalse(tabs[1].pinned, "tab 1 unpinned");
+
+        browser.test.assertEq(tabs[0].url, "about:robots", "tab 0 url correct");
+        browser.test.assertEq(tabs[1].url, "about:config", "tab 1 url correct");
+
+        browser.test.assertEq(tabs[0].status, "complete", "tab 0 status correct");
+        browser.test.assertEq(tabs[1].status, "complete", "tab 1 status correct");
+
+        browser.test.assertEq(tabs[0].title, "Gort! Klaatu barada nikto!", "tab 0 title correct");
+
+        browser.test.notifyPass("tabs.query");
+      });
+    },
+  });
+
+  yield extension.startup();
+  yield extension.awaitFinish("tabs.query");
+  yield extension.unload();
+
+  yield BrowserTestUtils.removeTab(tab1);
+  yield BrowserTestUtils.removeTab(tab2);
+});
new file mode 100644
--- /dev/null
+++ b/browser/components/extensions/test/browser/browser_ext_tabs_update.js
@@ -0,0 +1,42 @@
+add_task(function* () {
+  let tab1 = yield BrowserTestUtils.openNewForegroundTab(gBrowser, "about:robots");
+  let tab2 = yield BrowserTestUtils.openNewForegroundTab(gBrowser, "about:config");
+
+  gBrowser.selectedTab = tab1;
+
+  let extension = ExtensionTestUtils.loadExtension({
+    manifest: {
+      "permissions": ["tabs"]
+    },
+
+    background: function() {
+      browser.tabs.query({
+        lastFocusedWindow: true,
+      }, function(tabs) {
+        browser.test.assertEq(tabs.length, 3, "should have three tabs");
+
+        tabs.sort(function (tab1, tab2) { return tab1.index - tab2.index; });
+
+        browser.test.assertEq(tabs[0].url, "about:blank", "first tab blank");
+        tabs.shift();
+
+        browser.test.assertTrue(tabs[0].active, "tab 0 active");
+        browser.test.assertFalse(tabs[1].active, "tab 1 inactive");
+
+        browser.tabs.update(tabs[1].id, {active: true}, function() {
+          browser.test.sendMessage("check");
+        });
+      });
+    },
+  });
+
+  yield extension.startup();
+  yield extension.awaitMessage("check");
+
+  ok(gBrowser.selectedTab == tab2, "correct tab selected");
+
+  yield extension.unload();
+
+  yield BrowserTestUtils.removeTab(tab1);
+  yield BrowserTestUtils.removeTab(tab2);
+});
new file mode 100644
--- /dev/null
+++ b/browser/components/extensions/test/browser/browser_extensions_simple.js
@@ -0,0 +1,22 @@
+add_task(function* test_simple() {
+  let extension = ExtensionTestUtils.loadExtension("simple");
+  info("load complete");
+  yield extension.startup();
+  info("startup complete");
+  yield extension.unload();
+  info("extension unloaded successfully");
+});
+
+add_task(function* test_background() {
+  let extension = ExtensionTestUtils.loadExtension("background");
+  info("load complete");
+  yield extension.startup();
+  let x = yield extension.awaitMessage("running");
+  is(x, 1, "got correct value from extension");
+  info("startup complete");
+  extension.sendMessage(10, 20);
+  yield extension.awaitFinish();
+  info("test complete");
+  yield extension.unload();
+  info("extension unloaded successfully");
+});
--- a/browser/locales/en-US/chrome/browser/browser.properties
+++ b/browser/locales/en-US/chrome/browser/browser.properties
@@ -566,16 +566,20 @@ getUserMedia.selectScreen.accesskey=S
 getUserMedia.selectApplication.label=Application to share:
 getUserMedia.selectApplication.accesskey=A
 getUserMedia.noVideo.label = No Video
 getUserMedia.noApplication.label = No Application
 getUserMedia.noScreen.label = No Screen
 getUserMedia.noWindow.label = No Window
 getUserMedia.noAudio.label = No Audio
 getUserMedia.shareEntireScreen.label = Entire screen
+# LOCALIZATION NOTE (getUserMedia.shareMonitor.label):
+# %S is screen number (digits 1, 2, etc)
+# Example: Screen 1, Screen 2,..
+getUserMedia.shareMonitor.label = Screen %S
 # LOCALIZATION NOTE (getUserMedia.shareApplicationWindowCount.label):
 # Semicolon-separated list of plural forms.
 # See: http://developer.mozilla.org/en/docs/Localization_and_Plurals
 # Replacement for #1 is the name of the application.
 # Replacement for #2 is the number of windows currently displayed by the application.
 getUserMedia.shareApplicationWindowCount.label=#1 (#2 window);#1 (#2 windows)
 # LOCALIZATION NOTE (getUserMedia.shareSelectedDevices.label):
 # Semicolon-separated list of plural forms. See:
--- a/browser/modules/webrtcUI.jsm
+++ b/browser/modules/webrtcUI.jsm
@@ -427,22 +427,28 @@ function prompt(aBrowser, aRequest) {
         // "No <type>" is the default because we can't pick a
         // 'default' window to share.
         addDeviceToList(menupopup,
                         stringBundle.getString("getUserMedia.no" + typeName + ".label"),
                         "-1");
         menupopup.appendChild(chromeDoc.createElement("menuseparator"));
 
         // Build the list of 'devices'.
+        let monitorIndex = 1;
         for (let i = 0; i < devices.length; ++i) {
           let name;
-          // Screen has a special treatment because we currently only support
-          // sharing the primary screen and want to display a localized string.
+          // Building screen list from available screens.
           if (type == "screen") {
-            name = stringBundle.getString("getUserMedia.shareEntireScreen.label");
+            if (devices[i].name == "Primary Monitor") {
+              name = stringBundle.getString("getUserMedia.shareEntireScreen.label");
+            } else {
+              name = stringBundle.getFormattedString("getUserMedia.shareMonitor.label",
+                                                     [monitorIndex]);
+              ++monitorIndex;
+            }
           }
           else {
             name = devices[i].name;
             if (type == "application") {
               // The application names returned by the platform are of the form:
               // <window count>\x1e<application name>
               let sepIndex = name.indexOf("\x1e");
               let count = name.slice(0, sepIndex);
--- a/build/clang-plugin/clang-plugin.cpp
+++ b/build/clang-plugin/clang-plugin.cpp
@@ -703,43 +703,16 @@ AST_MATCHER(MemberExpr, isAddRefOrReleas
     return Name == "AddRef" || Name == "Release";
   }
   return false;
 }
 
 /// This matcher will select classes which are refcounted.
 AST_MATCHER(QualType, isRefCounted) { return isClassRefCounted(Node); }
 
-#if CLANG_VERSION_FULL < 304
-
-/// The 'equalsBoundeNode' matcher was added in clang 3.4.
-/// Since infra runs clang 3.3, we polyfill it here.
-AST_POLYMORPHIC_MATCHER_P(equalsBoundNode, std::string, ID) {
-  BoundNodesTree bindings = Builder->build();
-  bool haveMatchingResult = false;
-  struct Visitor : public BoundNodesTree::Visitor {
-    const NodeType &Node;
-    std::string ID;
-    bool &haveMatchingResult;
-    Visitor(const NodeType &Node, const std::string &ID,
-            bool &haveMatchingResult)
-        : Node(Node), ID(ID), haveMatchingResult(haveMatchingResult) {}
-    void visitMatch(const BoundNodes &BoundNodesView) override {
-      if (BoundNodesView.getNodeAs<NodeType>(ID) == &Node) {
-        haveMatchingResult = true;
-      }
-    }
-  };
-  Visitor visitor(Node, ID, haveMatchingResult);
-  bindings.visitMatches(&visitor);
-  return haveMatchingResult;
-}
-
-#endif
-
 AST_MATCHER(CXXRecordDecl, hasRefCntMember) {
   return isClassRefCounted(&Node) && getClassRefCntMember(&Node);
 }
 
 AST_MATCHER(QualType, hasVTable) { return typeHasVTable(Node); }
 
 AST_MATCHER(CXXRecordDecl, hasNeedsNoVTableTypeAttr) {
   return MozChecker::hasCustomAnnotation(&Node, "moz_needs_no_vtable_type");
--- a/build/gyp.mozbuild
+++ b/build/gyp.mozbuild
@@ -41,16 +41,19 @@ gyp_vars = {
      # turn off mandatory use of NEON and instead use NEON detection
     'arm_neon': 0,
     'arm_neon_optional': 1,
 
     'moz_widget_toolkit_gonk': 0,
     'moz_webrtc_omx': 0,
     'moz_webrtc_mediacodec': 0,
 
+    # Turn off multi monitor screen share
+    'multi_monitor_screenshare%' : 0,
+
     # (for vp8) chromium sets to 0 also
     'use_temporal_layers': 0,
 
     # Creates AEC internal sample dump files in current directory
     'aec_debug_dump': 1,
 
     # Enable and force use of hardware AEC
     'hardware_aec_ns': 1 if CONFIG['MOZ_WEBRTC_HARDWARE_AEC_NS'] else 0,
--- a/dom/animation/test/chrome/test_running_on_compositor.html
+++ b/dom/animation/test/chrome/test_running_on_compositor.html
@@ -1,65 +1,72 @@
 <!doctype html>
 <head>
 <meta charset=utf-8>
 <title>Bug 1045994 - Add a chrome-only property to inspect if an animation is
        running on the compositor or not</title>
 <script type="application/javascript"
   src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
 <script type="application/javascript"
+  src="chrome://mochikit/content/tests/SimpleTest/SpawnTask.js"></script>
+<script type="application/javascript"
   src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script>
 <link rel="stylesheet" type="text/css"
   href="chrome://mochikit/content/tests/SimpleTest/test.css"/>
 <style>
 @keyframes anim {
   to { transform: translate(100px) }
 }
-.target {
+div {
   /* Element needs geometry to be eligible for layerization */
   width: 100px;
   height: 100px;
   background-color: white;
 }
 </style>
 </head>
 <body>
 <a href="https://bugzilla.mozilla.org/show_bug.cgi?id=1045994"
   target="_blank">Mozilla Bug 1045994</a>
-<div class="target"></div>
 <script>
 'use strict';
 
 /** Test for bug 1045994 - Add a chrome-only property to inspect if an
     animation is running on the compositor or not **/
 
-SimpleTest.waitForExplicitFinish();
-
-var div = document.querySelector('div.target');
+function addDiv(attrs) {
+  var div = document.createElement('div');
+  if (attrs) {
+    for (var attrName in attrs) {
+      div.setAttribute(attrName, attrs[attrName]);
+    }
+  }
+  document.body.appendChild(div);
+  return div;
+}
 
 const OMTAPrefKey = 'layers.offmainthreadcomposition.async-animations';
 var omtaEnabled = SpecialPowers.DOMWindowUtils.layerManagerRemote &&
                   SpecialPowers.getBoolPref(OMTAPrefKey);
 
-// FIXME: When we implement Element.animate, use that here instead of CSS
-// so that we remove any dependency on the CSS mapping.
-div.style.animation = 'anim 100s';
-var animation = div.getAnimations()[0];
+add_task(function* play_and_pause_from_style() {
+  // FIXME: When we implement Element.animate, use that here instead of CSS
+  // so that we remove any dependency on the CSS mapping.
+  var div = addDiv({ style: 'animation: anim 100s' });
+  var animation = div.getAnimations()[0];
 
-animation.ready.then(function() {
+  yield animation.ready;
+
   is(animation.isRunningOnCompositor, omtaEnabled,
      'Animation reports that it is running on the compositor'
      + ' during playback');
 
   div.style.animationPlayState = 'paused';
-  window.getComputedStyle(div).animationPlayState;
+
+  yield animation.ready;
 
-  // FIXME: When we implement deferred pausing (bug 1109390), we should wait
-  // on animation.ready here.
-  window.requestAnimationFrame(function() {
-    is(animation.isRunningOnCompositor, false,
-       'Animation reports that it is NOT running on the compositor'
-       + ' when paused');
-    SimpleTest.finish();
-  });
+  is(animation.isRunningOnCompositor, false,
+     'Animation reports that it is NOT running on the compositor'
+     + ' when paused');
+  div.parentNode.removeChild(div);
 });
 </script>
 </body>
--- a/dom/bindings/Codegen.py
+++ b/dom/bindings/Codegen.py
@@ -8500,17 +8500,17 @@ class CGMemberJITInfo(CGThing):
         assert(not eliminatable or aliasSet != "AliasEverything")  # Can't eliminate write-aliasing things
         assert(not alwaysInSlot or eliminatable)  # Things always in slots had better be eliminatable
 
         def jitInfoInitializer(isTypedMethod):
             initializer = fill(
                 """
                 {
                   { ${opName} },
-                  prototypes::id::${name},
+                  { prototypes::id::${name} },
                   PrototypeTraits<prototypes::id::${name}>::Depth,
                   JSJitInfo::${opType},
                   JSJitInfo::${aliasSet}, /* aliasSet.  Not relevant for setters. */
                   ${returnType},  /* returnType.  Not relevant for setters. */
                   ${isInfallible},  /* isInfallible. False in setters. */
                   ${isMovable},  /* isMovable.  Not relevant for setters. */
                   ${isEliminatable}, /* isEliminatable.  Not relevant for setters. */
                   ${isAlwaysInSlot}, /* isAlwaysInSlot.  Only relevant for getters. */
@@ -8881,17 +8881,17 @@ class CGStaticMethodJitinfo(CGGeneric):
     A class for generating the JITInfo for a promise-returning static method.
     """
     def __init__(self, method):
         CGGeneric.__init__(
             self,
             "\n"
             "static const JSJitInfo %s_methodinfo = {\n"
             "  { (JSJitGetterOp)%s },\n"
-            "  prototypes::id::_ID_Count, 0, JSJitInfo::StaticMethod,\n"
+            "  { prototypes::id::_ID_Count }, 0, JSJitInfo::StaticMethod,\n"
             "  JSJitInfo::AliasEverything, JSVAL_TYPE_MISSING, false, false,\n"
             "  false, false, 0\n"
             "};\n" %
             (IDLToCIdentifier(method.identifier.name),
              CppKeywords.checkMethodName(
                  IDLToCIdentifier(method.identifier.name))))
 
 
--- a/dom/events/Event.cpp
+++ b/dom/events/Event.cpp
@@ -753,17 +753,17 @@ Event::GetEventPopupControlState(WidgetE
     // nsPresShell::HandleEventInternal() for details.
     if (EventStateManager::IsHandlingUserInput()) {
       switch(aEvent->mMessage) {
       case eFormChange:
         if (PopupAllowedForEvent("change")) {
           abuse = openControlled;
         }
         break;
-      case NS_XUL_COMMAND:
+      case eXULCommand:
         abuse = openControlled;
         break;
       default:
         break;
       }
     }
     break;
   case eKeyboardEventClass:
--- a/dom/events/EventNameList.h
+++ b/dom/events/EventNameList.h
@@ -683,45 +683,45 @@ NON_IDL_EVENT(compositionupdate,
               NS_COMPOSITION_UPDATE,
               EventNameType_XUL,
               eCompositionEventClass)
 NON_IDL_EVENT(compositionend,
               NS_COMPOSITION_END,
               EventNameType_XUL,
               eCompositionEventClass)
 NON_IDL_EVENT(command,
-              NS_XUL_COMMAND,
+              eXULCommand,
               EventNameType_XUL,
               eInputEventClass)
 NON_IDL_EVENT(close,
               eWindowClose,
               EventNameType_XUL,
               eBasicEventClass)
 NON_IDL_EVENT(popupshowing,
-              NS_XUL_POPUP_SHOWING,
+              eXULPopupShowing,
               EventNameType_XUL,
               eBasicEventClass)
 NON_IDL_EVENT(popupshown,
-              NS_XUL_POPUP_SHOWN,
+              eXULPopupShown,
               EventNameType_XUL,
               eBasicEventClass)
 NON_IDL_EVENT(popuphiding,
-              NS_XUL_POPUP_HIDING,
+              eXULPopupHiding,
               EventNameType_XUL,
               eBasicEventClass)
 NON_IDL_EVENT(popuphidden,
-              NS_XUL_POPUP_HIDDEN,
+              eXULPopupHidden,
               EventNameType_XUL,
               eBasicEventClass)
 NON_IDL_EVENT(broadcast,
-              NS_XUL_BROADCAST,
+              eXULBroadcast,
               EventNameType_XUL,
               eBasicEventClass)
 NON_IDL_EVENT(commandupdate,
-              NS_XUL_COMMAND_UPDATE,
+              eXULCommandUpdate,
               EventNameType_XUL,
               eBasicEventClass)
 NON_IDL_EVENT(dragexit,
               eDragExit,
               EventNameType_XUL,
               eDragEventClass)
 NON_IDL_EVENT(dragdrop,
               eLegacyDragDrop,
--- a/dom/fetch/Fetch.cpp
+++ b/dom/fetch/Fetch.cpp
@@ -22,16 +22,17 @@
 
 #include "mozilla/ErrorResult.h"
 #include "mozilla/dom/EncodingUtils.h"
 #include "mozilla/dom/Exceptions.h"
 #include "mozilla/dom/FetchDriver.h"
 #include "mozilla/dom/File.h"
 #include "mozilla/dom/Headers.h"
 #include "mozilla/dom/Promise.h"
+#include "mozilla/dom/PromiseWorkerProxy.h"
 #include "mozilla/dom/Request.h"
 #include "mozilla/dom/Response.h"
 #include "mozilla/dom/ScriptSettings.h"
 #include "mozilla/dom/URLSearchParams.h"
 #include "mozilla/Telemetry.h"
 
 #include "InternalRequest.h"
 #include "InternalResponse.h"
@@ -42,98 +43,55 @@
 #include "WorkerScope.h"
 #include "Workers.h"
 
 namespace mozilla {
 namespace dom {
 
 using namespace workers;
 
-class WorkerFetchResolver final : public FetchDriverObserver,
-                                  public WorkerFeature
+class WorkerFetchResolver final : public FetchDriverObserver
 {
   friend class MainThreadFetchRunnable;
   friend class WorkerFetchResponseEndRunnable;
   friend class WorkerFetchResponseRunnable;
 
-  workers::WorkerPrivate* mWorkerPrivate;
-
-  Mutex mCleanUpLock;
-  bool mCleanedUp;
-  // The following are initialized and used exclusively on the worker thread.
-  nsRefPtr<Promise> mFetchPromise;
-  nsRefPtr<Response> mResponse;
+  nsRefPtr<PromiseWorkerProxy> mPromiseProxy;
 public:
+  // Returns null if worker is shutting down.
+  static already_AddRefed<WorkerFetchResolver>
+  Create(workers::WorkerPrivate* aWorkerPrivate, Promise* aPromise)
+  {
+    MOZ_ASSERT(aWorkerPrivate);
+    aWorkerPrivate->AssertIsOnWorkerThread();
+    nsRefPtr<PromiseWorkerProxy> proxy = PromiseWorkerProxy::Create(aWorkerPrivate, aPromise);
+    if (!proxy) {
+      return nullptr;
+    }
 
-  WorkerFetchResolver(workers::WorkerPrivate* aWorkerPrivate, Promise* aPromise)
-    : mWorkerPrivate(aWorkerPrivate)
-    , mCleanUpLock("WorkerFetchResolver")
-    , mCleanedUp(false)
-    , mFetchPromise(aPromise)
-  {
+    nsRefPtr<WorkerFetchResolver> r = new WorkerFetchResolver(proxy);
+    return r.forget();
   }
 
   void
   OnResponseAvailable(InternalResponse* aResponse) override;
 
   void
   OnResponseEnd() override;
 
-  bool
-  Notify(JSContext* aCx, Status aStatus) override
-  {
-    if (aStatus > Running) {
-      CleanUp(aCx);
-    }
-    return true;
-  }
-
-  void
-  CleanUp(JSContext* aCx)
+private:
+  explicit WorkerFetchResolver(PromiseWorkerProxy* aProxy)
+    : mPromiseProxy(aProxy)
   {
-    MutexAutoLock lock(mCleanUpLock);
-
-    if (mCleanedUp) {
-      return;
-    }
-
-    MOZ_ASSERT(mWorkerPrivate);
-    mWorkerPrivate->AssertIsOnWorkerThread();
-    MOZ_ASSERT(mWorkerPrivate->GetJSContext() == aCx);
-
-    mWorkerPrivate->RemoveFeature(aCx, this);
-    CleanUpUnchecked();
+    MOZ_ASSERT(!NS_IsMainThread());
+    MOZ_ASSERT(mPromiseProxy);
   }
 
-  void
-  CleanUpUnchecked()
-  {
-    mResponse = nullptr;
-    if (mFetchPromise) {
-      mFetchPromise->MaybeReject(NS_ERROR_DOM_ABORT_ERR);
-      mFetchPromise = nullptr;
-    }
-    mCleanedUp = true;
-  }
-
-  workers::WorkerPrivate*
-  GetWorkerPrivate() const
-  {
-    // It's ok to race on |mCleanedUp|, because it will never cause us to fire
-    // the assertion when we should not.
-    MOZ_ASSERT(!mCleanedUp);
-    return mWorkerPrivate;
-  }
-
-private:
   ~WorkerFetchResolver()
-  {
-    MOZ_ASSERT(mCleanedUp);
-    MOZ_ASSERT(!mFetchPromise);
-  }
+  {}
 };
 
 class MainThreadFetchResolver final : public FetchDriverObserver
 {
   nsRefPtr<Promise> mPromise;
   nsRefPtr<Response> mResponse;
 
   NS_DECL_OWNINGTHREAD
@@ -148,44 +106,41 @@ private:
 };
 
 class MainThreadFetchRunnable : public nsRunnable
 {
   nsRefPtr<WorkerFetchResolver> mResolver;
   nsRefPtr<InternalRequest> mRequest;
 
 public:
-  MainThreadFetchRunnable(WorkerPrivate* aWorkerPrivate,
-                          Promise* aPromise,
+  MainThreadFetchRunnable(WorkerFetchResolver* aResolver,
                           InternalRequest* aRequest)
-    : mResolver(new WorkerFetchResolver(aWorkerPrivate, aPromise))
+    : mResolver(aResolver)
     , mRequest(aRequest)
   {
-    MOZ_ASSERT(aWorkerPrivate);
-    aWorkerPrivate->AssertIsOnWorkerThread();
-    if (!aWorkerPrivate->AddFeature(aWorkerPrivate->GetJSContext(), mResolver)) {
-      NS_WARNING("Could not add WorkerFetchResolver feature to worker");
-      mResolver->CleanUpUnchecked();
-      mResolver = nullptr;
-    }
+    MOZ_ASSERT(mResolver);
   }
 
   NS_IMETHODIMP
   Run()
   {
     AssertIsOnMainThread();
-    // AddFeature() call failed, don't bother running.
-    if (!mResolver) {
+    nsRefPtr<PromiseWorkerProxy> proxy = mResolver->mPromiseProxy;
+    MutexAutoLock lock(proxy->Lock());
+    if (proxy->CleanedUp()) {
+      NS_WARNING("Aborting Fetch because worker already shut down");
       return NS_OK;
     }
 
-    nsCOMPtr<nsIPrincipal> principal = mResolver->GetWorkerPrivate()->GetPrincipal();
-    nsCOMPtr<nsILoadGroup> loadGroup = mResolver->GetWorkerPrivate()->GetLoadGroup();
+    nsCOMPtr<nsIPrincipal> principal = proxy->GetWorkerPrivate()->GetPrincipal();
+    MOZ_ASSERT(principal);
+    nsCOMPtr<nsILoadGroup> loadGroup = proxy->GetWorkerPrivate()->GetLoadGroup();
+    MOZ_ASSERT(loadGroup);
     nsRefPtr<FetchDriver> fetch = new FetchDriver(mRequest, principal, loadGroup);
-    nsIDocument* doc = mResolver->GetWorkerPrivate()->GetDocument();
+    nsIDocument* doc = proxy->GetWorkerPrivate()->GetDocument();
     if (doc) {
       fetch->SetDocument(doc);
     }
 
     nsresult rv = fetch->Fetch(mResolver);
     // Right now we only support async fetch, which should never directly fail.
     if (NS_WARN_IF(NS_FAILED(rv))) {
       return rv;
@@ -257,20 +212,25 @@ FetchRequest(nsIGlobalObject* aGlobal, c
     MOZ_ASSERT(worker);
 
     Telemetry::Accumulate(Telemetry::FETCH_IS_MAINTHREAD, 0);
 
     if (worker->IsServiceWorker()) {
       r->SetSkipServiceWorker();
     }
 
-    nsRefPtr<MainThreadFetchRunnable> run = new MainThreadFetchRunnable(worker, p, r);
-    if (NS_FAILED(NS_DispatchToMainThread(run))) {
-      NS_WARNING("MainThreadFetchRunnable dispatch failed!");
+    nsRefPtr<WorkerFetchResolver> resolver = WorkerFetchResolver::Create(worker, p);
+    if (!resolver) {
+      NS_WARNING("Could not add WorkerFetchResolver feature to worker");
+      aRv.Throw(NS_ERROR_DOM_ABORT_ERR);
+      return nullptr;
     }
+
+    nsRefPtr<MainThreadFetchRunnable> run = new MainThreadFetchRunnable(resolver, r);
+    MOZ_ALWAYS_TRUE(NS_SUCCEEDED(NS_DispatchToMainThread(run)));
   }
 
   return p.forget();
 }
 
 MainThreadFetchResolver::MainThreadFetchResolver(Promise* aPromise)
   : mPromise(aPromise)
 {
@@ -299,102 +259,105 @@ MainThreadFetchResolver::~MainThreadFetc
 }
 
 class WorkerFetchResponseRunnable final : public WorkerRunnable
 {
   nsRefPtr<WorkerFetchResolver> mResolver;
   // Passed from main thread to worker thread after being initialized.
   nsRefPtr<InternalResponse> mInternalResponse;
 public:
-  WorkerFetchResponseRunnable(WorkerFetchResolver* aResolver, InternalResponse* aResponse)
-    : WorkerRunnable(aResolver->GetWorkerPrivate(), WorkerThreadModifyBusyCount)
+  WorkerFetchResponseRunnable(WorkerPrivate* aWorkerPrivate,
+                              WorkerFetchResolver* aResolver,
+                              InternalResponse* aResponse)
+    : WorkerRunnable(aWorkerPrivate, WorkerThreadModifyBusyCount)
     , mResolver(aResolver)
     , mInternalResponse(aResponse)
   {
   }
 
   bool
   WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate) override
   {
     MOZ_ASSERT(aWorkerPrivate);
     aWorkerPrivate->AssertIsOnWorkerThread();
-    MOZ_ASSERT(aWorkerPrivate == mResolver->GetWorkerPrivate());
 
-    nsRefPtr<Promise> promise = mResolver->mFetchPromise.forget();
+    nsRefPtr<Promise> promise = mResolver->mPromiseProxy->WorkerPromise();
 
     if (mInternalResponse->Type() != ResponseType::Error) {
       nsRefPtr<nsIGlobalObject> global = aWorkerPrivate->GlobalScope();
-      mResolver->mResponse = new Response(global, mInternalResponse);
-
-      promise->MaybeResolve(mResolver->mResponse);
+      nsRefPtr<Response> response = new Response(global, mInternalResponse);
+      promise->MaybeResolve(response);
     } else {
       ErrorResult result;
       result.ThrowTypeError(MSG_FETCH_FAILED);
       promise->MaybeReject(result);
     }
     return true;
   }
 };
 
 class WorkerFetchResponseEndRunnable final : public WorkerRunnable
 {
   nsRefPtr<WorkerFetchResolver> mResolver;
 public:
-  explicit WorkerFetchResponseEndRunnable(WorkerFetchResolver* aResolver)
-    : WorkerRunnable(aResolver->GetWorkerPrivate(), WorkerThreadModifyBusyCount)
+  WorkerFetchResponseEndRunnable(WorkerPrivate* aWorkerPrivate,
+                                 WorkerFetchResolver* aResolver)
+    : WorkerRunnable(aWorkerPrivate, WorkerThreadModifyBusyCount)
     , mResolver(aResolver)
   {
   }
 
   bool
   WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate) override
   {
     MOZ_ASSERT(aWorkerPrivate);
     aWorkerPrivate->AssertIsOnWorkerThread();
-    MOZ_ASSERT(aWorkerPrivate == mResolver->GetWorkerPrivate());
 
-    mResolver->CleanUp(aCx);
+    mResolver->mPromiseProxy->CleanUp(aCx);
     return true;
   }
 };
 
 void
 WorkerFetchResolver::OnResponseAvailable(InternalResponse* aResponse)
 {
   AssertIsOnMainThread();
 
-  MutexAutoLock lock(mCleanUpLock);
-  if (mCleanedUp) {
+  MutexAutoLock lock(mPromiseProxy->Lock());
+  if (mPromiseProxy->CleanedUp()) {
     return;
   }
 
   nsRefPtr<WorkerFetchResponseRunnable> r =
-    new WorkerFetchResponseRunnable(this, aResponse);
+    new WorkerFetchResponseRunnable(mPromiseProxy->GetWorkerPrivate(), this,
+                                    aResponse);
 
-  AutoSafeJSContext cx;
-  if (!r->Dispatch(cx)) {
-    NS_WARNING("Could not dispatch fetch resolve");
+  AutoJSAPI jsapi;
+  jsapi.Init();
+  if (!r->Dispatch(jsapi.cx())) {
+    NS_WARNING("Could not dispatch fetch response");
   }
 }
 
 void
 WorkerFetchResolver::OnResponseEnd()
 {
   AssertIsOnMainThread();
-  MutexAutoLock lock(mCleanUpLock);
-  if (mCleanedUp) {
+  MutexAutoLock lock(mPromiseProxy->Lock());
+  if (mPromiseProxy->CleanedUp()) {
     return;
   }
 
   nsRefPtr<WorkerFetchResponseEndRunnable> r =
-    new WorkerFetchResponseEndRunnable(this);
+    new WorkerFetchResponseEndRunnable(mPromiseProxy->GetWorkerPrivate(), this);
 
-  AutoSafeJSContext cx;
-  if (!r->Dispatch(cx)) {
-    NS_WARNING("Could not dispatch fetch resolve end");
+  AutoJSAPI jsapi;
+  jsapi.Init();
+  if (!r->Dispatch(jsapi.cx())) {
+    NS_WARNING("Could not dispatch fetch response end");
   }
 }
 
 namespace {
 nsresult
 ExtractFromArrayBuffer(const ArrayBuffer& aBuffer,
                        nsIInputStream** aStream)
 {
--- a/dom/interfaces/base/nsITabParent.idl
+++ b/dom/interfaces/base/nsITabParent.idl
@@ -1,16 +1,16 @@
 /* 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 "domstubs.idl"
 
-[scriptable, uuid(531b902b-b551-4faa-9814-1a73e8299ac4)]
+[scriptable, uuid(3dd203e4-66ec-40fd-acde-43f0b35c98e9)]
 interface nsITabParent : nsISupports
 {
   void injectTouchEvent(in AString aType,
                         [array, size_is(count)] in uint32_t aIdentifiers,
                         [array, size_is(count)] in int32_t aXs,
                         [array, size_is(count)] in int32_t aYs,
                         [array, size_is(count)] in uint32_t aRxs,
                         [array, size_is(count)] in uint32_t aRys,
@@ -18,17 +18,20 @@ interface nsITabParent : nsISupports
                         [array, size_is(count)] in float aForces,
                         in uint32_t count,
                         in long aModifiers);
 
   void getChildProcessOffset(out int32_t aCssX, out int32_t aCssY);
 
   readonly attribute boolean useAsyncPanZoom;
 
-  void setIsDocShellActive(in bool aIsActive);
+  /**
+    * Manages the docshell active state of the remote browser.
+    */
+  attribute boolean docShellIsActive;
 
   /**
    * During interactions where painting performance
    * is more important than scrolling, we may temporarily
    * suppress the displayport. Each enable called must be matched
    * with a disable call.
    */
   void suppressDisplayport(in bool aEnabled);
--- a/dom/ipc/PBrowser.ipdl
+++ b/dom/ipc/PBrowser.ipdl
@@ -717,19 +717,19 @@ child:
 
     /**
      * Tell the child side if it has to update it's touchable region
      * to the parent.
      */
     SetUpdateHitRegion(bool aEnabled);
 
     /**
-     * Tell the child to update its docShell's active state.
+     * Update the child side docShell active (resource use) state.
      */
-    SetIsDocShellActive(bool aIsActive);
+    SetDocShellIsActive(bool aIsActive);
 
     /**
      * Notify the child that it shouldn't paint the offscreen displayport.
      * This is useful to speed up interactive operations over async
      * scrolling performance like resize, tabswitch, pageload.
      *
      * Each enable call must be matched with a disable call. The child
      * will remain in the suppress mode as long as there's
--- a/dom/ipc/TabChild.cpp
+++ b/dom/ipc/TabChild.cpp
@@ -2567,17 +2567,17 @@ TabChild::RecvSetUpdateHitRegion(const b
     nsRefPtr<nsPresContext> presContext = presShell->GetPresContext();
     NS_ENSURE_TRUE(presContext, true);
     presContext->InvalidatePaintedLayers();
 
     return true;
 }
 
 bool
-TabChild::RecvSetIsDocShellActive(const bool& aIsActive)
+TabChild::RecvSetDocShellIsActive(const bool& aIsActive)
 {
     nsCOMPtr<nsIDocShell> docShell = do_GetInterface(WebNavigation());
     if (docShell) {
       docShell->SetIsActive(aIsActive);
     }
     return true;
 }
 
--- a/dom/ipc/TabChild.h
+++ b/dom/ipc/TabChild.h
@@ -507,17 +507,17 @@ public:
 
 protected:
     virtual ~TabChild();
 
     virtual PRenderFrameChild* AllocPRenderFrameChild() override;
     virtual bool DeallocPRenderFrameChild(PRenderFrameChild* aFrame) override;
     virtual bool RecvDestroy() override;
     virtual bool RecvSetUpdateHitRegion(const bool& aEnabled) override;
-    virtual bool RecvSetIsDocShellActive(const bool& aIsActive) override;
+    virtual bool RecvSetDocShellIsActive(const bool& aIsActive) override;
     virtual bool RecvNavigateByKey(const bool& aForward, const bool& aForDocumentNavigation) override;
 
     virtual bool RecvRequestNotifyAfterRemotePaint() override;
 
     virtual bool RecvSuppressDisplayport(const bool& aEnabled) override;
 
     virtual bool RecvParentActivated(const bool& aActivated) override;
 
--- a/dom/ipc/TabParent.cpp
+++ b/dom/ipc/TabParent.cpp
@@ -271,16 +271,17 @@ TabParent::TabParent(nsIContentParent* a
   , mFrameElement(nullptr)
   , mRect(0, 0, 0, 0)
   , mDimensions(0, 0)
   , mOrientation(0)
   , mDPI(0)
   , mDefaultScale(0)
   , mUpdatedDimensions(false)
   , mManager(aManager)
+  , mDocShellIsActive(false)
   , mMarkedDestroying(false)
   , mIsDestroyed(false)
   , mIsDetached(true)
   , mAppPackageFileDescriptorSent(false)
   , mSendOfflineStatus(true)
   , mChromeFlags(aChromeFlags)
   , mDragAreaX(0)
   , mDragAreaY(0)
@@ -3039,20 +3040,29 @@ TabParent::InjectTouchEvent(const nsAStr
 
 NS_IMETHODIMP
 TabParent::GetUseAsyncPanZoom(bool* useAsyncPanZoom)
 {
   *useAsyncPanZoom = AsyncPanZoomEnabled();
   return NS_OK;
 }
 
+// defined in nsITabParent
 NS_IMETHODIMP
-TabParent::SetIsDocShellActive(bool isActive)
+TabParent::SetDocShellIsActive(bool isActive)
 {
-  unused << SendSetIsDocShellActive(isActive);
+  mDocShellIsActive = isActive;
+  unused << SendSetDocShellIsActive(isActive);
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+TabParent::GetDocShellIsActive(bool* aIsActive)
+{
+  *aIsActive = mDocShellIsActive;
   return NS_OK;
 }
 
 NS_IMETHODIMP
 TabParent::SuppressDisplayport(bool aEnabled)
 {
   if (IsDestroyed()) {
     return NS_OK;
--- a/dom/ipc/TabParent.h
+++ b/dom/ipc/TabParent.h
@@ -499,16 +499,19 @@ private:
     void TryCacheDPIAndScale();
 
     nsresult UpdatePosition();
 
     CSSPoint AdjustTapToChildWidget(const CSSPoint& aPoint);
 
     bool AsyncPanZoomEnabled() const;
 
+    // Cached value indicating the docshell active state of the remote browser.
+    bool mDocShellIsActive;
+
     // Update state prior to routing an APZ-aware event to the child process.
     // |aOutTargetGuid| will contain the identifier
     // of the APZC instance that handled the event. aOutTargetGuid may be null.
     // |aOutInputBlockId| will contain the identifier of the input block
     // that this event was added to, if there was one. aOutInputBlockId may be null.
     // |aOutApzResponse| will contain the response that the APZ gave when processing
     // the input block; this is used for generating appropriate pointercancel events.
     void ApzAwareEventRoutingToChild(ScrollableLayerGuid* aOutTargetGuid,
--- a/dom/media/tests/mochitest/pc.js
+++ b/dom/media/tests/mochitest/pc.js
@@ -1248,18 +1248,18 @@ PeerConnectionWrapper.prototype = {
         todo(this._pc.iceGatheringState === 'completed',
            "ICE gathering state has reached completed");
         resolveEndOfTrickle(this.label);
         return;
       }
 
       info(this.label + ": iceCandidate = " + JSON.stringify(anEvent.candidate));
       ok(anEvent.candidate.candidate.length > 0, "ICE candidate contains candidate");
-      // we don't support SDP MID's yet
-      ok(anEvent.candidate.sdpMid.length === 0, "SDP MID has length zero");
+      ok(anEvent.candidate.sdpMid.length > 0, "SDP mid not empty");
+
       ok(typeof anEvent.candidate.sdpMLineIndex === 'number', "SDP MLine Index needs to exist");
       this._local_ice_candidates.push(anEvent.candidate);
       candidateHandler(this.label, anEvent.candidate);
     };
   },
 
   checkLocalMediaTracks : function() {
     var observed = {};
--- a/dom/notification/Notification.cpp
+++ b/dom/notification/Notification.cpp
@@ -1713,17 +1713,17 @@ public:
     , mPromiseProxy(aPromiseProxy)
     , mStrings(Move(aStrings))
   {
   }
 
   void
   WorkerRunInternal(JSContext* aCx, WorkerPrivate* aWorkerPrivate) override
   {
-    nsRefPtr<Promise> workerPromise = mPromiseProxy->GetWorkerPromise();
+    nsRefPtr<Promise> workerPromise = mPromiseProxy->WorkerPromise();
 
     ErrorResult result;
     nsAutoTArray<nsRefPtr<Notification>, 5> notifications;
     for (uint32_t i = 0; i < mStrings.Length(); ++i) {
       nsRefPtr<Notification> n =
         Notification::ConstructFromFields(aWorkerPrivate->GlobalScope(),
                                           mStrings[i].mID,
                                           mStrings[i].mTitle,
@@ -1762,94 +1762,73 @@ public:
     AssertIsOnMainThread();
     MOZ_ASSERT(aProxy);
   }
 
   NS_IMETHOD Done(JSContext* aCx) final
   {
     AssertIsOnMainThread();
     MOZ_ASSERT(mPromiseProxy, "Was Done() called twice?");
-    MutexAutoLock lock(mPromiseProxy->GetCleanUpLock());
-    if (mPromiseProxy->IsClean()) {
+
+    nsRefPtr<PromiseWorkerProxy> proxy = mPromiseProxy.forget();
+    MutexAutoLock lock(proxy->Lock());
+    if (proxy->CleanedUp()) {
       return NS_OK;
     }
 
-    MOZ_ASSERT(mPromiseProxy->GetWorkerPrivate());
     nsRefPtr<WorkerGetResultRunnable> r =
-      new WorkerGetResultRunnable(mPromiseProxy->GetWorkerPrivate(),
-                                  mPromiseProxy,
+      new WorkerGetResultRunnable(proxy->GetWorkerPrivate(),
+                                  proxy,
                                   Move(mStrings));
 
-    if (!r->Dispatch(aCx)) {
-      nsRefPtr<PromiseWorkerProxyControlRunnable> cr =
-        new PromiseWorkerProxyControlRunnable(mPromiseProxy->GetWorkerPrivate(),
-                                              mPromiseProxy);
-
-      DebugOnly<bool> ok = cr->Dispatch(aCx);
-      MOZ_ASSERT(ok);
-    }
-
-    mPromiseProxy = nullptr;
+    r->Dispatch(aCx);
     return NS_OK;
   }
 
 private:
   ~WorkerGetCallback()
   {}
 };
 
 NS_IMPL_ISUPPORTS(WorkerGetCallback, nsINotificationStorageCallback)
 
 class WorkerGetRunnable final : public nsRunnable
 {
   nsRefPtr<PromiseWorkerProxy> mPromiseProxy;
   const nsString mTag;
   const nsString mScope;
 public:
-  WorkerGetRunnable(WorkerPrivate* aWorkerPrivate,
-                    Promise* aWorkerPromise,
+  WorkerGetRunnable(PromiseWorkerProxy* aProxy,
                     const nsAString& aTag,
                     const nsAString& aScope)
-    : mTag(aTag), mScope(aScope)
+    : mPromiseProxy(aProxy), mTag(aTag), mScope(aScope)
   {
-    aWorkerPrivate->AssertIsOnWorkerThread();
-    mPromiseProxy =
-      PromiseWorkerProxy::Create(aWorkerPrivate,
-                                 aWorkerPromise);
-
-    if (!mPromiseProxy || !mPromiseProxy->GetWorkerPromise()) {
-      aWorkerPromise->MaybeReject(NS_ERROR_DOM_ABORT_ERR);
-      mPromiseProxy = nullptr;
-    }
+    MOZ_ASSERT(mPromiseProxy);
   }
 
   NS_IMETHOD
   Run() override
   {
     AssertIsOnMainThread();
-    if (!mPromiseProxy) {
-      return NS_OK;
-    }
-
     nsCOMPtr<nsINotificationStorageCallback> callback =
       new WorkerGetCallback(mPromiseProxy, mScope);
 
     AutoJSAPI jsapi;
     jsapi.Init();
 
     nsresult rv;
     nsCOMPtr<nsINotificationStorage> notificationStorage =
       do_GetService(NS_NOTIFICATION_STORAGE_CONTRACTID, &rv);
     if (NS_WARN_IF(NS_FAILED(rv))) {
       callback->Done(jsapi.cx());
       return rv;
     }
 
-    MutexAutoLock lock(mPromiseProxy->GetCleanUpLock());
-    if (mPromiseProxy->IsClean()) {
+    MutexAutoLock lock(mPromiseProxy->Lock());
+    if (mPromiseProxy->CleanedUp()) {
       return NS_OK;
     }
 
     nsString origin;
     rv =
       Notification::GetOrigin(mPromiseProxy->GetWorkerPrivate()->GetPrincipal(),
                               origin);
     if (NS_WARN_IF(NS_FAILED(rv))) {
@@ -1878,23 +1857,28 @@ Notification::WorkerGet(WorkerPrivate* a
 {
   MOZ_ASSERT(aWorkerPrivate);
   aWorkerPrivate->AssertIsOnWorkerThread();
   nsRefPtr<Promise> p = Promise::Create(aWorkerPrivate->GlobalScope(), aRv);
   if (NS_WARN_IF(aRv.Failed())) {
     return nullptr;
   }
 
-  nsRefPtr<WorkerGetRunnable> r =
-    new WorkerGetRunnable(aWorkerPrivate, p, aFilter.mTag, aScope);
-  if (NS_WARN_IF(NS_FAILED(NS_DispatchToMainThread(r)))) {
+  nsRefPtr<PromiseWorkerProxy> proxy =
+    PromiseWorkerProxy::Create(aWorkerPrivate, p);
+  if (!proxy) {
     aRv.Throw(NS_ERROR_DOM_ABORT_ERR);
     return nullptr;
   }
 
+  nsRefPtr<WorkerGetRunnable> r =
+    new WorkerGetRunnable(proxy, aFilter.mTag, aScope);
+  // Since this is called from script via
+  // ServiceWorkerRegistration::GetNotifications, we can assert dispatch.
+  MOZ_ALWAYS_TRUE(NS_SUCCEEDED(NS_DispatchToMainThread(r)));
   return p.forget();
 }
 
 JSObject*
 Notification::WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto)
 {
   return mozilla::dom::NotificationBinding::Wrap(aCx, this, aGivenProto);
 }
--- a/dom/promise/Promise.cpp
+++ b/dom/promise/Promise.cpp
@@ -1504,18 +1504,17 @@ public:
   virtual bool
   WorkerRun(JSContext* aCx, workers::WorkerPrivate* aWorkerPrivate)
   {
     MOZ_ASSERT(aWorkerPrivate);
     aWorkerPrivate->AssertIsOnWorkerThread();
     MOZ_ASSERT(aWorkerPrivate == mWorkerPrivate);
 
     MOZ_ASSERT(mPromiseWorkerProxy);
-    nsRefPtr<Promise> workerPromise = mPromiseWorkerProxy->GetWorkerPromise();
-    MOZ_ASSERT(workerPromise);
+    nsRefPtr<Promise> workerPromise = mPromiseWorkerProxy->WorkerPromise();
 
     // Here we convert the buffer to a JS::Value.
     JS::Rooted<JS::Value> value(aCx);
     if (!mBuffer.read(aCx, &value, mCallbacks, mPromiseWorkerProxy)) {
       JS_ClearPendingException(aCx);
       return false;
     }
 
@@ -1548,102 +1547,129 @@ PromiseWorkerProxy::Create(workers::Work
   aWorkerPrivate->AssertIsOnWorkerThread();
   MOZ_ASSERT(aWorkerPromise);
 
   nsRefPtr<PromiseWorkerProxy> proxy =
     new PromiseWorkerProxy(aWorkerPrivate, aWorkerPromise, aCb);
 
   // We do this to make sure the worker thread won't shut down before the
   // promise is resolved/rejected on the worker thread.
-  if (!aWorkerPrivate->AddFeature(aWorkerPrivate->GetJSContext(), proxy)) {
+  if (!proxy->AddRefObject()) {
     // Probably the worker is terminating. We cannot complete the operation
     // and we have to release all the resources.
-    proxy->mCleanedUp = true;
-    proxy->mWorkerPromise = nullptr;
+    proxy->CleanProperties();
     return nullptr;
   }
 
   return proxy.forget();
 }
 
 NS_IMPL_ISUPPORTS0(PromiseWorkerProxy)
 
 PromiseWorkerProxy::PromiseWorkerProxy(workers::WorkerPrivate* aWorkerPrivate,
                                        Promise* aWorkerPromise,
                                        const JSStructuredCloneCallbacks* aCallbacks)
   : mWorkerPrivate(aWorkerPrivate)
   , mWorkerPromise(aWorkerPromise)
   , mCleanedUp(false)
   , mCallbacks(aCallbacks)
   , mCleanUpLock("cleanUpLock")
+  , mFeatureAdded(false)
 {
 }
 
 PromiseWorkerProxy::~PromiseWorkerProxy()
 {
   MOZ_ASSERT(mCleanedUp);
+  MOZ_ASSERT(!mFeatureAdded);
   MOZ_ASSERT(!mWorkerPromise);
+  MOZ_ASSERT(!mWorkerPrivate);
+}
+
+void
+PromiseWorkerProxy::CleanProperties()
+{
+#ifdef DEBUG
+  workers::WorkerPrivate* worker = GetCurrentThreadWorkerPrivate();
+  MOZ_ASSERT(worker);
+  worker->AssertIsOnWorkerThread();
+#endif
+  // Ok to do this unprotected from Create().
+  // CleanUp() holds the lock before calling this.
+  mCleanedUp = true;
+  mWorkerPromise = nullptr;
+  mWorkerPrivate = nullptr;
+}
+
+bool
+PromiseWorkerProxy::AddRefObject()
+{
+  MOZ_ASSERT(mWorkerPrivate);
+  mWorkerPrivate->AssertIsOnWorkerThread();
+  MOZ_ASSERT(!mFeatureAdded);
+  if (!mWorkerPrivate->AddFeature(mWorkerPrivate->GetJSContext(),
+                                  this)) {
+    return false;
+  }
+
+  mFeatureAdded = true;
+  // Maintain a reference so that we have a valid object to clean up when
+  // removing the feature.
+  AddRef();
+  return true;
 }
 
 workers::WorkerPrivate*
 PromiseWorkerProxy::GetWorkerPrivate() const
 {
-  // It's ok to race on |mCleanedUp|, because it will never cause us to fire
-  // the assertion when we should not.
-  MOZ_ASSERT(!mCleanedUp);
-
 #ifdef DEBUG
   if (NS_IsMainThread()) {
     mCleanUpLock.AssertCurrentThreadOwns();
   }
 #endif
+  // Safe to check this without a lock since we assert lock ownership on the
+  // main thread above.
+  MOZ_ASSERT(!mCleanedUp);
+  MOZ_ASSERT(mFeatureAdded);
 
   return mWorkerPrivate;
 }
 
 Promise*
-PromiseWorkerProxy::GetWorkerPromise() const
+PromiseWorkerProxy::WorkerPromise() const
 {
-
 #ifdef DEBUG
   workers::WorkerPrivate* worker = GetCurrentThreadWorkerPrivate();
   MOZ_ASSERT(worker);
   worker->AssertIsOnWorkerThread();
 #endif
+  MOZ_ASSERT(mWorkerPromise);
   return mWorkerPromise;
 }
 
 void
 PromiseWorkerProxy::StoreISupports(nsISupports* aSupports)
 {
   MOZ_ASSERT(NS_IsMainThread());
 
   nsMainThreadPtrHandle<nsISupports> supports(
     new nsMainThreadPtrHolder<nsISupports>(aSupports));
   mSupportsArray.AppendElement(supports);
 }
 
-bool
-PromiseWorkerProxyControlRunnable::WorkerRun(JSContext* aCx,
-                                             workers::WorkerPrivate* aWorkerPrivate)
-{
-  mProxy->CleanUp(aCx);
-  return true;
-}
-
 void
 PromiseWorkerProxy::RunCallback(JSContext* aCx,
                                 JS::Handle<JS::Value> aValue,
                                 RunCallbackFunc aFunc)
 {
   MOZ_ASSERT(NS_IsMainThread());
 
-  MutexAutoLock lock(GetCleanUpLock());
+  MutexAutoLock lock(Lock());
   // If the worker thread's been cancelled we don't need to resolve the Promise.
-  if (IsClean()) {
+  if (CleanedUp()) {
     return;
   }
 
   // The |aValue| is written into the buffer. Note that we also pass |this|
   // into the structured-clone write in order to set its |mSupportsArray| to
   // keep objects alive until the structured-clone read/write is done.
   JSAutoStructuredCloneBuffer buffer;
   if (!buffer.write(aCx, aValue, mCallbacks, this)) {
@@ -1652,21 +1678,17 @@ PromiseWorkerProxy::RunCallback(JSContex
   }
 
   nsRefPtr<PromiseWorkerProxyRunnable> runnable =
     new PromiseWorkerProxyRunnable(this,
                                    mCallbacks,
                                    Move(buffer),
                                    aFunc);
 
-  if (!runnable->Dispatch(aCx)) {
-    nsRefPtr<WorkerControlRunnable> runnable =
-      new PromiseWorkerProxyControlRunnable(mWorkerPrivate, this);
-    mWorkerPrivate->DispatchControlRunnable(runnable.forget());
-  }
+  runnable->Dispatch(aCx);
 }
 
 void
 PromiseWorkerProxy::ResolvedCallback(JSContext* aCx,
                                      JS::Handle<JS::Value> aValue)
 {
   RunCallback(aCx, aValue, &Promise::ResolveInternal);
 }
@@ -1676,48 +1698,49 @@ PromiseWorkerProxy::RejectedCallback(JSC
                                      JS::Handle<JS::Value> aValue)
 {
   RunCallback(aCx, aValue, &Promise::RejectInternal);
 }
 
 bool
 PromiseWorkerProxy::Notify(JSContext* aCx, Status aStatus)
 {
-  MOZ_ASSERT(mWorkerPrivate);
-  mWorkerPrivate->AssertIsOnWorkerThread();
-  MOZ_ASSERT(mWorkerPrivate->GetJSContext() == aCx);
-
   if (aStatus >= Canceling) {
     CleanUp(aCx);
   }
 
   return true;
 }
 
 void
 PromiseWorkerProxy::CleanUp(JSContext* aCx)
 {
-  MutexAutoLock lock(mCleanUpLock);
+  // Can't release Mutex while it is still locked, so scope the lock.
+  {
+    MutexAutoLock lock(Lock());
 
-  // |mWorkerPrivate| might not be safe to use anymore if we have already
-  // cleaned up and RemoveFeature(), so we need to check |mCleanedUp| first.
-  if (mCleanedUp) {
-    return;
-  }
+    // |mWorkerPrivate| is not safe to use anymore if we have already
+    // cleaned up and RemoveFeature(), so we need to check |mCleanedUp| first.
+    if (CleanedUp()) {
+      return;
+    }
 
-  MOZ_ASSERT(mWorkerPrivate);
-  mWorkerPrivate->AssertIsOnWorkerThread();
-  MOZ_ASSERT(mWorkerPrivate->GetJSContext() == aCx);
+    MOZ_ASSERT(mWorkerPrivate);
+    mWorkerPrivate->AssertIsOnWorkerThread();
+    MOZ_ASSERT(mWorkerPrivate->GetJSContext() == aCx);
 
-  // Release the Promise and remove the PromiseWorkerProxy from the features of
-  // the worker thread since the Promise has been resolved/rejected or the
-  // worker thread has been cancelled.
-  mWorkerPromise = nullptr;
-  mWorkerPrivate->RemoveFeature(aCx, this);
-  mCleanedUp = true;
+    // Release the Promise and remove the PromiseWorkerProxy from the features of
+    // the worker thread since the Promise has been resolved/rejected or the
+    // worker thread has been cancelled.
+    MOZ_ASSERT(mFeatureAdded);
+    mWorkerPrivate->RemoveFeature(mWorkerPrivate->GetJSContext(), this);
+    mFeatureAdded = false;
+    CleanProperties();
+  }
+  Release();
 }
 
 // Specializations of MaybeRejectBrokenly we actually support.
 template<>
 void Promise::MaybeRejectBrokenly(const nsRefPtr<DOMError>& aArg) {
   MaybeSomething(aArg, &Promise::MaybeReject);
 }
 template<>
--- a/dom/promise/PromiseWorkerProxy.h
+++ b/dom/promise/PromiseWorkerProxy.h
@@ -19,83 +19,138 @@ namespace mozilla {
 namespace dom {
 
 class Promise;
 
 namespace workers {
 class WorkerPrivate;
 } // namespace workers
 
-// A proxy to catch the resolved/rejected Promise's result from the main thread
-// and resolve/reject that on the worker thread eventually.
+// A proxy to (eventually) mirror a resolved/rejected Promise's result from the
+// main thread to a Promise on the worker thread.
 //
 // How to use:
 //
 //   1. Create a Promise on the worker thread and return it to the content
 //      script:
 //
 //        nsRefPtr<Promise> promise = Promise::Create(workerPrivate->GlobalScope(), aRv);
 //        if (aRv.Failed()) {
 //          return nullptr;
 //        }
-//        // Pass |promise| around to the WorkerMainThreadRunnable
+//
+//   2. Create a PromiseWorkerProxy wrapping the Promise. If this fails, the
+//      worker is shutting down and you should fail the original call. This is
+//      only likely to happen in (Gecko-specific) worker onclose handlers.
+//
+//        nsRefPtr<PromiseWorkerProxy> proxy =
+//          PromiseWorkerProxy::Create(workerPrivate, promise);
+//        if (!proxy) {
+//          // You may also reject the Promise with an AbortError or similar.
+//          return nullptr;
+//        }
+//
+//   3. Dispatch a runnable to the main thread, with a reference to the proxy to
+//      perform the main thread operation. PromiseWorkerProxy is thread-safe
+//      refcounted.
+//
+//   4. Return the worker thread promise to the JS caller:
+//
 //        return promise.forget();
 //
-//   2. In your WorkerMainThreadRunnable's ctor, create a PromiseWorkerProxy
-//      which holds a nsRefPtr<Promise> to the Promise created at #1.
-//
-//   3. In your WorkerMainThreadRunnable::MainThreadRun(), obtain a Promise on
+//   5. In your main thread runnable Run(), obtain a Promise on
 //      the main thread and call its AppendNativeHandler(PromiseNativeHandler*)
 //      to bind the PromiseWorkerProxy created at #2.
 //
 //   4. Then the Promise results returned by ResolvedCallback/RejectedCallback
 //      will be dispatched as a WorkerRunnable to the worker thread to
 //      resolve/reject the Promise created at #1.
 //
 // PromiseWorkerProxy can also be used in situations where there is no main
 // thread Promise, or where special handling is required on the worker thread
-// for promise resolution. Create a PromiseWorkerProxy as in steps 1 and
-// 2 above. When the main thread is ready to resolve the worker thread promise,
-// dispatch a runnable to the worker. Use GetWorkerPrivate() to acquire the
-// worker. This might be null! In the WorkerRunnable's WorkerRun() use
-// GetWorkerPromise() to access the Promise and resolve/reject it. Then call
-// CleanUp() on the worker thread.
+// for promise resolution. Create a PromiseWorkerProxy as in steps 1 to 3
+// above. When the main thread is ready to resolve the worker thread promise:
+//
+//   1. Acquire the mutex before attempting to access the worker private.
+//
+//        AssertIsOnMainThread();
+//        MutexAutoLock lock(proxy->Lock());
+//        if (proxy->CleanedUp()) {
+//          // Worker has already shut down, can't access worker private.
+//          return;
+//        }
+//
+//   2. Dispatch a runnable to the worker. Use GetWorkerPrivate() to acquire the
+//      worker.
 //
-// IMPORTANT: Dispatching the runnable to the worker thread may fail causing
-// the promise to leak. To successfully release the promise on the
-// worker thread in this case, use |PromiseWorkerProxyControlRunnable| to
-// dispatch a control runnable that will deref the object on the correct thread.
+//        nsRefPtr<FinishTaskWorkerRunnable> runnable =
+//          new FinishTaskWorkerRunnable(proxy->GetWorkerPrivate(), proxy, result);
+//        AutoJSAPI jsapi;
+//        jsapi.Init();
+//        if (!r->Dispatch(jsapi.cx())) {
+//          // Worker is alive but not Running any more, so the Promise can't
+//          // be resolved, give up. The proxy will get Release()d at some
+//          // point.
+//
+//          // Usually do nothing, but you may want to log the fact.
+//        }
+//
+//   3. In the WorkerRunnable's WorkerRun() use WorkerPromise() to access the
+//      Promise and resolve/reject it. Then call CleanUp().
+//
+//        bool
+//        WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate) override
+//        {
+//          aWorkerPrivate->AssertIsOnWorkerThread();
+//          nsRefPtr<Promise> promise = mProxy->WorkerPromise();
+//          promise->MaybeResolve(mResult);
+//          mProxy->CleanUp(aCx);
+//        }
+//
+// Note: If a PromiseWorkerProxy is not cleaned up by a WorkerRunnable - this
+// can happen if the main thread Promise is never fulfilled - it will
+// stay alive till the worker reaches a Canceling state, even if all external
+// references to it are dropped.
 
 class PromiseWorkerProxy : public PromiseNativeHandler,
                            public workers::WorkerFeature
 {
   friend class PromiseWorkerProxyRunnable;
 
   NS_DECL_THREADSAFE_ISUPPORTS
 
 public:
   static already_AddRefed<PromiseWorkerProxy>
   Create(workers::WorkerPrivate* aWorkerPrivate,
          Promise* aWorkerPromise,
          const JSStructuredCloneCallbacks* aCallbacks = nullptr);
 
+  // Main thread callers must hold Lock() and check CleanUp() before calling this.
+  // Worker thread callers, this will assert that the proxy has not been cleaned
+  // up.
   workers::WorkerPrivate* GetWorkerPrivate() const;
 
-  Promise* GetWorkerPromise() const;
+  // This should only be used within WorkerRunnable::WorkerRun() running on the
+  // worker thread! Do not call this after calling CleanUp().
+  Promise* WorkerPromise() const;
 
   void StoreISupports(nsISupports* aSupports);
 
+  // Worker thread only. Calling this invalidates several assumptions, so be
+  // sure this is the last thing you do.
+  // 1. WorkerPrivate() will no longer return a valid worker.
+  // 2. WorkerPromise() will crash!
   void CleanUp(JSContext* aCx);
 
-  Mutex& GetCleanUpLock()
+  Mutex& Lock()
   {
     return mCleanUpLock;
   }
 
-  bool IsClean() const
+  bool CleanedUp() const
   {
     mCleanUpLock.AssertCurrentThreadOwns();
     return mCleanedUp;
   }
 
 protected:
   virtual void ResolvedCallback(JSContext* aCx,
                                 JS::Handle<JS::Value> aValue) override;
@@ -107,61 +162,48 @@ protected:
 
 private:
   PromiseWorkerProxy(workers::WorkerPrivate* aWorkerPrivate,
                      Promise* aWorkerPromise,
                      const JSStructuredCloneCallbacks* aCallbacks = nullptr);
 
   virtual ~PromiseWorkerProxy();
 
+  bool AddRefObject();
+
+  // If not called from Create(), be sure to hold Lock().
+  void CleanProperties();
+
   // Function pointer for calling Promise::{ResolveInternal,RejectInternal}.
   typedef void (Promise::*RunCallbackFunc)(JSContext*,
                                            JS::Handle<JS::Value>);
 
   void RunCallback(JSContext* aCx,
                    JS::Handle<JS::Value> aValue,
                    RunCallbackFunc aFunc);
 
+  // Any thread with appropriate checks.
   workers::WorkerPrivate* mWorkerPrivate;
 
-  // This lives on the worker thread.
+  // Worker thread only.
   nsRefPtr<Promise> mWorkerPromise;
 
+  // Modified on the worker thread.
+  // It is ok to *read* this without a lock on the worker.
+  // Main thread must always acquire a lock.
   bool mCleanedUp; // To specify if the cleanUp() has been done.
 
   const JSStructuredCloneCallbacks* mCallbacks;
 
   // Aimed to keep objects alive when doing the structured-clone read/write,
   // which can be added by calling StoreISupports() on the main thread.
   nsTArray<nsMainThreadPtrHandle<nsISupports>> mSupportsArray;
 
   // Ensure the worker and the main thread won't race to access |mCleanedUp|.
   Mutex mCleanUpLock;
+
+  // Maybe get rid of this entirely and rely on mCleanedUp
+  DebugOnly<bool> mFeatureAdded;
 };
-
-// Helper runnable used for releasing the proxied promise when the worker
-// is not accepting runnables and the promise object would leak.
-// See the instructions above.
-class PromiseWorkerProxyControlRunnable final : public workers::WorkerControlRunnable
-{
-  nsRefPtr<PromiseWorkerProxy> mProxy;
-
-public:
-  PromiseWorkerProxyControlRunnable(workers::WorkerPrivate* aWorkerPrivate,
-                                    PromiseWorkerProxy* aProxy)
-    : WorkerControlRunnable(aWorkerPrivate, WorkerThreadUnchangedBusyCount)
-    , mProxy(aProxy)
-  {
-    MOZ_ASSERT(aProxy);
-  }
-
-  virtual bool
-  WorkerRun(JSContext* aCx, workers::WorkerPrivate* aWorkerPrivate) override;
-
-private:
-  ~PromiseWorkerProxyControlRunnable()
-  {}
-};
-
 } // namespace dom
 } // namespace mozilla
 
 #endif // mozilla_dom_PromiseWorkerProxy_h
--- a/dom/push/PushManager.cpp
+++ b/dom/push/PushManager.cpp
@@ -211,40 +211,16 @@ WorkerPushSubscription::Constructor(Glob
   WorkerPrivate* worker = GetCurrentThreadWorkerPrivate();
   MOZ_ASSERT(worker);
   worker->AssertIsOnWorkerThread();
 
   nsRefPtr<WorkerPushSubscription> sub = new WorkerPushSubscription(aEndpoint, aScope);
   return sub.forget();
 }
 
-namespace {
-// The caller MUST take ownership of the proxy's lock before it calls this.
-void
-ReleasePromiseWorkerProxy(already_AddRefed<PromiseWorkerProxy> aProxy)
-{
-  AssertIsOnMainThread();
-  nsRefPtr<PromiseWorkerProxy> proxy = aProxy;
-  MOZ_ASSERT(proxy);
-  proxy->GetCleanUpLock().AssertCurrentThreadOwns();
-  if (proxy->IsClean()) {
-    return;
-  }
-
-  AutoJSAPI jsapi;
-  jsapi.Init();
-
-  nsRefPtr<PromiseWorkerProxyControlRunnable> cr =
-    new PromiseWorkerProxyControlRunnable(proxy->GetWorkerPrivate(),
-                                          proxy);
-
-  MOZ_ALWAYS_TRUE(cr->Dispatch(jsapi.cx()));
-}
-} // anonymous namespace
-
 class UnsubscribeResultRunnable final : public WorkerRunnable
 {
 public:
   UnsubscribeResultRunnable(PromiseWorkerProxy* aProxy,
                             nsresult aStatus,
                             bool aSuccess)
     : WorkerRunnable(aProxy->GetWorkerPrivate(), WorkerThreadModifyBusyCount)
     , mProxy(aProxy)
@@ -255,25 +231,24 @@ public:
   }
 
   bool
   WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate) override
   {
     MOZ_ASSERT(aWorkerPrivate);
     aWorkerPrivate->AssertIsOnWorkerThread();
 
-    nsRefPtr<PromiseWorkerProxy> proxy = mProxy.forget();
-    nsRefPtr<Promise> promise = proxy->GetWorkerPromise();
+    nsRefPtr<Promise> promise = mProxy->WorkerPromise();
     if (NS_SUCCEEDED(mStatus)) {
       promise->MaybeResolve(mSuccess);
     } else {
       promise->MaybeReject(NS_ERROR_DOM_NETWORK_ERR);
     }
 
-    proxy->CleanUp(aCx);
+    mProxy->CleanUp(aCx);
     return true;
   }
 private:
   ~UnsubscribeResultRunnable()
   {}
 
   nsRefPtr<PromiseWorkerProxy> mProxy;
   nsresult mStatus;
@@ -290,52 +265,37 @@ public:
   {
     AssertIsOnMainThread();
   }
 
   NS_IMETHOD
   OnUnsubscribe(nsresult aStatus, bool aSuccess) override
   {
     AssertIsOnMainThread();
-    if (!mProxy) {
-      return NS_OK;
-    }
+    MOZ_ASSERT(mProxy, "OnUnsubscribe() called twice?");
 
-    MutexAutoLock lock(mProxy->GetCleanUpLock());
-    if (mProxy->IsClean()) {
+    nsRefPtr<PromiseWorkerProxy> proxy = mProxy.forget();
+
+    MutexAutoLock lock(proxy->Lock());
+    if (proxy->CleanedUp()) {
       return NS_OK;
     }
 
     AutoJSAPI jsapi;
     jsapi.Init();
 
     nsRefPtr<UnsubscribeResultRunnable> r =
-      new UnsubscribeResultRunnable(mProxy, aStatus, aSuccess);
-    if (!r->Dispatch(jsapi.cx())) {
-      ReleasePromiseWorkerProxy(mProxy.forget());
-    }
-
-    mProxy = nullptr;
+      new UnsubscribeResultRunnable(proxy, aStatus, aSuccess);
+    r->Dispatch(jsapi.cx());
     return NS_OK;
   }
 
 private:
   ~WorkerUnsubscribeResultCallback()
   {
-    AssertIsOnMainThread();
-    if (mProxy) {
-      MutexAutoLock lock(mProxy->GetCleanUpLock());
-      if (!mProxy->IsClean()) {
-        AutoJSAPI jsapi;
-        jsapi.Init();
-        nsRefPtr<PromiseWorkerProxyControlRunnable> cr =
-          new PromiseWorkerProxyControlRunnable(mProxy->GetWorkerPrivate(), mProxy);
-        cr->Dispatch(jsapi.cx());
-      }
-    }
   }
 
   nsRefPtr<PromiseWorkerProxy> mProxy;
 };
 
 NS_IMPL_ISUPPORTS(WorkerUnsubscribeResultCallback, nsIUnsubscribeResultCallback)
 
 class UnsubscribeRunnable final : public nsRunnable
@@ -349,18 +309,18 @@ public:
     MOZ_ASSERT(aProxy);
     MOZ_ASSERT(!aScope.IsEmpty());
   }
 
   NS_IMETHOD
   Run() override
   {
     AssertIsOnMainThread();
-    MutexAutoLock lock(mProxy->GetCleanUpLock());
-    if (mProxy->IsClean()) {
+    MutexAutoLock lock(mProxy->Lock());
+    if (mProxy->CleanedUp()) {
       return NS_OK;
     }
 
     nsRefPtr<WorkerUnsubscribeResultCallback> callback =
       new WorkerUnsubscribeResultCallback(mProxy);
 
     nsCOMPtr<nsIPushClient> client =
       do_CreateInstance("@mozilla.org/push/PushClient;1");
@@ -443,31 +403,30 @@ public:
     , mStatus(aStatus)
     , mEndpoint(aEndpoint)
     , mScope(aScope)
   { }
 
   bool
   WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate) override
   {
-    nsRefPtr<PromiseWorkerProxy> proxy = mProxy.forget();
-    nsRefPtr<Promise> promise = proxy->GetWorkerPromise();
+    nsRefPtr<Promise> promise = mProxy->WorkerPromise();
     if (NS_SUCCEEDED(mStatus)) {
       if (mEndpoint.IsEmpty()) {
         promise->MaybeResolve(JS::NullHandleValue);
       } else {
         nsRefPtr<WorkerPushSubscription> sub =
           new WorkerPushSubscription(mEndpoint, mScope);
         promise->MaybeResolve(sub);
       }
     } else {
       promise->MaybeReject(NS_ERROR_DOM_ABORT_ERR);
     }
 
-    proxy->CleanUp(aCx);
+    mProxy->CleanUp(aCx);
     return true;
   }
 private:
   ~GetSubscriptionResultRunnable()
   {}
 
   nsRefPtr<PromiseWorkerProxy> mProxy;
   nsresult mStatus;
@@ -485,54 +444,37 @@ public:
     : mProxy(aProxy)
     , mScope(aScope)
   {}
 
   NS_IMETHOD
   OnPushEndpoint(nsresult aStatus, const nsAString& aEndpoint) override
   {
     AssertIsOnMainThread();
+    MOZ_ASSERT(mProxy, "OnPushEndpoint() called twice?");
 
-    if (!mProxy) {
-      return NS_OK;
-    }
+    nsRefPtr<PromiseWorkerProxy> proxy = mProxy.forget();
 
-    MutexAutoLock lock(mProxy->GetCleanUpLock());
-    if (mProxy->IsClean()) {
+    MutexAutoLock lock(proxy->Lock());
+    if (proxy->CleanedUp()) {
       return NS_OK;
     }
 
     AutoJSAPI jsapi;
     jsapi.Init();
 
     nsRefPtr<GetSubscriptionResultRunnable> r =
-      new GetSubscriptionResultRunnable(mProxy, aStatus, aEndpoint, mScope);
-    if (!r->Dispatch(jsapi.cx())) {
-      ReleasePromiseWorkerProxy(mProxy.forget());
-    }
-
-    mProxy = nullptr;
+      new GetSubscriptionResultRunnable(proxy, aStatus, aEndpoint, mScope);
+    r->Dispatch(jsapi.cx());
     return NS_OK;
   }
 
 protected:
   ~GetSubscriptionCallback()
-  {
-    AssertIsOnMainThread();
-    if (mProxy) {
-      MutexAutoLock lock(mProxy->GetCleanUpLock());
-      if (!mProxy->IsClean()) {
-        AutoJSAPI jsapi;
-        jsapi.Init();
-        nsRefPtr<PromiseWorkerProxyControlRunnable> cr =
-          new PromiseWorkerProxyControlRunnable(mProxy->GetWorkerPrivate(), mProxy);
-        cr->Dispatch(jsapi.cx());
-      }
-    }
-  }
+  {}
 
 private:
   nsRefPtr<PromiseWorkerProxy> mProxy;
   nsString mScope;
 };
 
 NS_IMPL_ISUPPORTS(GetSubscriptionCallback, nsIPushEndpointCallback)
 
@@ -545,51 +487,50 @@ public:
     : mProxy(aProxy)
     , mScope(aScope), mAction(aAction)
   {}
 
   NS_IMETHOD
   Run() override
   {
     AssertIsOnMainThread();
-    MutexAutoLock lock(mProxy->GetCleanUpLock());
-    if (mProxy->IsClean()) {
+    MutexAutoLock lock(mProxy->Lock());
+    if (mProxy->CleanedUp()) {
       return NS_OK;
     }
 
     nsRefPtr<GetSubscriptionCallback> callback = new GetSubscriptionCallback(mProxy, mScope);
 
     nsCOMPtr<nsIPermissionManager> permManager =
       mozilla::services::GetPermissionManager();
     if (!permManager) {
       callback->OnPushEndpoint(NS_ERROR_FAILURE, EmptyString());
       return NS_OK;
     }
 
+    nsCOMPtr<nsIPrincipal> principal = mProxy->GetWorkerPrivate()->GetPrincipal();
+
     uint32_t permission = nsIPermissionManager::DENY_ACTION;
     nsresult rv = permManager->TestExactPermissionFromPrincipal(
-                    mProxy->GetWorkerPrivate()->GetPrincipal(),
+                    principal,
                     "push",
                     &permission);
 
     if (NS_WARN_IF(NS_FAILED(rv)) || permission != nsIPermissionManager::ALLOW_ACTION) {
       callback->OnPushEndpoint(NS_ERROR_FAILURE, EmptyString());
       return NS_OK;
     }
 
     nsCOMPtr<nsIPushClient> client =
       do_CreateInstance("@mozilla.org/push/PushClient;1");
     if (!client) {
       callback->OnPushEndpoint(NS_ERROR_FAILURE, EmptyString());
       return NS_OK;
     }
 
-    nsCOMPtr<nsIPrincipal> principal = mProxy->GetWorkerPrivate()->GetPrincipal();
-    mProxy = nullptr;
-
     if (mAction == WorkerPushManager::SubscribeAction) {
       rv = client->Subscribe(mScope, principal, callback);
     } else {
       MOZ_ASSERT(mAction == WorkerPushManager::GetSubscriptionAction);
       rv = client->GetSubscription(mScope, principal, callback);
     }
 
     if (NS_WARN_IF(NS_FAILED(rv))) {
@@ -662,27 +603,27 @@ public:
   }
 
   bool
   WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate) override
   {
     MOZ_ASSERT(aWorkerPrivate);
     aWorkerPrivate->AssertIsOnWorkerThread();
 
-    nsRefPtr<PromiseWorkerProxy> proxy = mProxy.forget();
-    nsRefPtr<Promise> promise = proxy->GetWorkerPromise();
+    nsRefPtr<Promise> promise = mProxy->WorkerPromise();
     if (NS_SUCCEEDED(mStatus)) {
       MOZ_ASSERT(uint32_t(mState) < ArrayLength(PushPermissionStateValues::strings));
-      nsAutoCString stringState(PushPermissionStateValues::strings[uint32_t(mState)].value, PushPermissionStateValues::strings[uint32_t(mState)].length);
+      nsAutoCString stringState(PushPermissionStateValues::strings[uint32_t(mState)].value,
+                                PushPermissionStateValues::strings[uint32_t(mState)].length);
       promise->MaybeResolve(NS_ConvertUTF8toUTF16(stringState));
     } else {
       promise->MaybeReject(aCx, JS::UndefinedHandleValue);
     }
 
-    proxy->CleanUp(aCx);
+    mProxy->CleanUp(aCx);
     return true;
   }
 
 private:
   ~PermissionResultRunnable()
   {}
 
   nsRefPtr<PromiseWorkerProxy> mProxy;
@@ -696,18 +637,18 @@ public:
   explicit PermissionStateRunnable(PromiseWorkerProxy* aProxy)
     : mProxy(aProxy)
   {}
 
   NS_IMETHOD
   Run() override
   {
     AssertIsOnMainThread();
-    MutexAutoLock lock(mProxy->GetCleanUpLock());
-    if (mProxy->IsClean()) {
+    MutexAutoLock lock(mProxy->Lock());
+    if (mProxy->CleanedUp()) {
       return NS_OK;
     }
 
     nsCOMPtr<nsIPermissionManager> permManager =
       mozilla::services::GetPermissionManager();
 
     nsresult rv = NS_ERROR_FAILURE;
     PushPermissionState state = PushPermissionState::Denied;
@@ -735,19 +676,17 @@ public:
         }
       }
     }
 
     AutoJSAPI jsapi;
     jsapi.Init();
     nsRefPtr<PermissionResultRunnable> r =
       new PermissionResultRunnable(mProxy, rv, state);
-    if (!r->Dispatch(jsapi.cx())) {
-      ReleasePromiseWorkerProxy(mProxy.forget());
-    }
+    r->Dispatch(jsapi.cx());
     return NS_OK;
   }
 
 private:
   ~PermissionStateRunnable()
   {}
 
   nsRefPtr<PromiseWorkerProxy> mProxy;
--- a/dom/system/gonk/GonkGPSGeolocationProvider.cpp
+++ b/dom/system/gonk/GonkGPSGeolocationProvider.cpp
@@ -10,16 +10,17 @@
  * 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.
  */
 
 #include "GonkGPSGeolocationProvider.h"
+#include "mozstumbler/MozStumbler.h"
 
 #include <pthread.h>
 #include <hardware/gps.h>
 
 #include "mozilla/Constants.h"
 #include "mozilla/Preferences.h"
 #include "mozilla/Services.h"
 #include "nsContentUtils.h"
@@ -82,16 +83,82 @@ NS_IMPL_ISUPPORTS(GonkGPSGeolocationProv
 /* static */ GonkGPSGeolocationProvider* GonkGPSGeolocationProvider::sSingleton = nullptr;
 GpsCallbacks GonkGPSGeolocationProvider::mCallbacks;
 
 #ifdef MOZ_B2G_RIL
 AGpsCallbacks GonkGPSGeolocationProvider::mAGPSCallbacks;
 AGpsRilCallbacks GonkGPSGeolocationProvider::mAGPSRILCallbacks;
 #endif // MOZ_B2G_RIL
 
+double CalculateDeltaInMeter(double aLat, double aLon, double aLastLat, double aLastLon)
+{
+  // Use spherical law of cosines to calculate difference
+  // Not quite as correct as the Haversine but simpler and cheaper
+  const double radsInDeg = M_PI / 180.0;
+  const double rNewLat = aLat * radsInDeg;
+  const double rNewLon = aLon * radsInDeg;
+  const double rOldLat = aLastLat * radsInDeg;
+  const double rOldLon = aLastLon * radsInDeg;
+  // WGS84 equatorial radius of earth = 6378137m
+  double cosDelta = (sin(rNewLat) * sin(rOldLat)) +
+                    (cos(rNewLat) * cos(rOldLat) * cos(rOldLon - rNewLon));
+  if (cosDelta > 1.0) {
+    cosDelta = 1.0;
+  } else if (cosDelta < -1.0) {
+    cosDelta = -1.0;
+  }
+  return acos(cosDelta) * 6378137;
+}
+
+class RequestCellInfoEvent : public nsRunnable {
+  public:
+    RequestCellInfoEvent(StumblerInfo *callback)
+      : mRequestCallback(callback)
+      {}
+
+    NS_IMETHOD Run() {
+      MOZ_ASSERT(NS_IsMainThread());
+      // Get Cell Info
+      nsCOMPtr<nsIMobileConnectionService> service =
+        do_GetService(NS_MOBILE_CONNECTION_SERVICE_CONTRACTID);
+
+      if (!service) {
+        nsContentUtils::LogMessageToConsole("Stumbler-can not get nsIMobileConnectionService \n");
+        return NS_OK;
+      }
+      nsCOMPtr<nsIMobileConnection> connection;
+      uint32_t numberOfRilServices = 1, cellInfoNum = 0;
+
+      service->GetNumItems(&numberOfRilServices);
+      for (uint32_t rilNum = 0; rilNum < numberOfRilServices; rilNum++) {
+        service->GetItemByServiceId(rilNum /* Client Id */, getter_AddRefs(connection));
+        if (!connection) {
+          nsContentUtils::LogMessageToConsole("Stumbler-can not get nsIMobileConnection by ServiceId %d \n", rilNum);
+        } else {
+          cellInfoNum++;
+          connection->GetCellInfoList(mRequestCallback);
+        }
+      }
+      mRequestCallback->SetCellInfoResponsesExpected(cellInfoNum);
+
+      // Get Wifi AP Info
+      nsCOMPtr<nsIInterfaceRequestor> ir = do_GetService("@mozilla.org/telephony/system-worker-manager;1");
+      nsCOMPtr<nsIWifi> wifi = do_GetInterface(ir);
+      if (!wifi) {
+        mRequestCallback->SetWifiInfoResponseReceived();
+        nsContentUtils::LogMessageToConsole("Stumbler-can not get nsIWifi interface\n");
+        return NS_OK;
+      }
+      wifi->GetWifiScanResults(mRequestCallback);
+      return NS_OK;
+    }
+  private:
+    nsRefPtr<StumblerInfo> mRequestCallback;
+};
+
 void
 GonkGPSGeolocationProvider::LocationCallback(GpsLocation* location)
 {
   if (gDebug_isGPSLocationIgnored) {
     return;
   }
 
   class UpdateLocationEvent : public nsRunnable {
@@ -136,17 +203,48 @@ GonkGPSGeolocationProvider::LocationCall
 
   if (gDebug_isLoggingEnabled) {
     nsContentUtils::LogMessageToConsole("geo: GPS got a fix (%f, %f). accuracy: %f",
                                         location->latitude,
                                         location->longitude,
                                         location->accuracy);
   }
 
-  NS_DispatchToMainThread(new UpdateLocationEvent(somewhere));
+  nsRefPtr<UpdateLocationEvent> event = new UpdateLocationEvent(somewhere);
+  NS_DispatchToMainThread(event);
+
+  const double kMinChangeInMeters = 30;
+  static int64_t lastTime_ms = 0;
+  static double sLastLat = 0;
+  static double sLastLon = 0;
+  double delta = -1.0;
+  int64_t timediff = (PR_Now() / PR_USEC_PER_MSEC) - lastTime_ms;
+
+  if (0 != sLastLon || 0 != sLastLat) {
+    delta = CalculateDeltaInMeter(location->latitude, location->longitude, sLastLat, sLastLon);
+  }
+  if (gDebug_isLoggingEnabled) {
+    nsContentUtils::LogMessageToConsole("Stumbler-Location. [%f , %f] time_diff:%lld, delta : %f\n",
+      location->longitude, location->latitude, timediff, delta);
+  }
+
+  // Consecutive GPS locations must be 30 meters and 3 seconds apart
+  if (lastTime_ms == 0 || ((timediff >= STUMBLE_INTERVAL_MS) && (delta > kMinChangeInMeters))){
+    lastTime_ms = (PR_Now() / PR_USEC_PER_MSEC);
+    sLastLat = location->latitude;
+    sLastLon = location->longitude;
+    nsRefPtr<StumblerInfo> requestCallback = new StumblerInfo(somewhere);
+    nsRefPtr<RequestCellInfoEvent> runnable = new RequestCellInfoEvent(requestCallback);
+    NS_DispatchToMainThread(runnable);
+  } else {
+    if (gDebug_isLoggingEnabled) {
+      nsContentUtils::LogMessageToConsole(
+        "Stumbler-GPS locations less than 30 meters and 3 seconds. Ignore!\n");
+    }
+  }
 }
 
 void
 GonkGPSGeolocationProvider::StatusCallback(GpsStatus* status)
 {
   if (gDebug_isLoggingEnabled) {
     switch (status->status) {
       case GPS_STATUS_NONE:
@@ -881,33 +979,17 @@ GonkGPSGeolocationProvider::NetworkLocat
   coords->GetAccuracy(&acc);
 
   double delta = -1.0;
 
   static double sLastMLSPosLat = 0;
   static double sLastMLSPosLon = 0;
 
   if (0 != sLastMLSPosLon || 0 != sLastMLSPosLat) {
-    // Use spherical law of cosines to calculate difference
-    // Not quite as correct as the Haversine but simpler and cheaper
-    // Should the following be a utility function? Others might need this calc.
-    const double radsInDeg = M_PI / 180.0;
-    const double rNewLat = lat * radsInDeg;
-    const double rNewLon = lon * radsInDeg;
-    const double rOldLat = sLastMLSPosLat * radsInDeg;
-    const double rOldLon = sLastMLSPosLon * radsInDeg;
-    // WGS84 equatorial radius of earth = 6378137m
-    double cosDelta = (sin(rNewLat) * sin(rOldLat)) +
-                      (cos(rNewLat) * cos(rOldLat) * cos(rOldLon - rNewLon));
-    if (cosDelta > 1.0) {
-      cosDelta = 1.0;
-    } else if (cosDelta < -1.0) {
-      cosDelta = -1.0;
-    }
-    delta = acos(cosDelta) * 6378137;
+    delta = CalculateDeltaInMeter(lat, lon, sLastMLSPosLat, sLastMLSPosLon);
   }
 
   sLastMLSPosLat = lat;
   sLastMLSPosLon = lon;
 
   // if the MLS coord change is smaller than this arbitrarily small value
   // assume the MLS coord is unchanged, and stick with the GPS location
   const double kMinMLSCoordChangeInMeters = 10;
--- a/dom/system/gonk/moz.build
+++ b/dom/system/gonk/moz.build
@@ -29,28 +29,33 @@ XPIDL_SOURCES += [
     'nsIVolumeStat.idl',
     'nsIWorkerHolder.idl',
 ]
 
 XPIDL_MODULE = 'dom_system_gonk'
 
 EXPORTS += [
     'GonkGPSGeolocationProvider.h',
+    'mozstumbler/MozStumbler.h',
     'nsVolume.h',
     'nsVolumeService.h',
 ]
 UNIFIED_SOURCES += [
     'AudioChannelManager.cpp',
     'AudioManager.cpp',
     'AutoMounter.cpp',
     'AutoMounterSetting.cpp',
     'GonkGPSGeolocationProvider.cpp',
     'MozMtpDatabase.cpp',
     'MozMtpServer.cpp',
     'MozMtpStorage.cpp',
+    'mozstumbler/MozStumbler.cpp',
+    'mozstumbler/StumblerLogging.cpp',
+    'mozstumbler/UploadStumbleRunnable.cpp',
+    'mozstumbler/WriteStumbleOnThread.cpp',
     'NetIdManager.cpp',
     'NetworkUtils.cpp',
     'NetworkWorker.cpp',
     'nsVolume.cpp',
     'nsVolumeMountLock.cpp',
     'nsVolumeService.cpp',
     'nsVolumeStat.cpp',
     'OpenFileFinder.cpp',
new file mode 100644
--- /dev/null
+++ b/dom/system/gonk/mozstumbler/MozStumbler.cpp
@@ -0,0 +1,323 @@
+/* -*- Mode: c++; c-basic-offset: 2; indent-tabs-mode: nil; tab-width: 40 -*- */
+/* vim: set ts=2 et sw=2 tw=80: */
+/* 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 "MozStumbler.h"
+#include "nsGeoPosition.h"
+#include "nsPrintfCString.h"
+#include "StumblerLogging.h"
+#include "WriteStumbleOnThread.h"
+#include "nsNetCID.h"
+#include "nsDataHashtable.h"
+
+using namespace mozilla;
+using namespace mozilla::dom;
+
+
+NS_IMPL_ISUPPORTS(StumblerInfo, nsICellInfoListCallback, nsIWifiScanResultsReady)
+
+void
+StumblerInfo::SetWifiInfoResponseReceived()
+{
+  mIsWifiInfoResponseReceived = true;
+
+  if (mIsWifiInfoResponseReceived && mCellInfoResponsesReceived == mCellInfoResponsesExpected) {
+    STUMBLER_DBG("Call DumpStumblerInfo from SetWifiInfoResponseReceived\n");
+    DumpStumblerInfo();
+  }
+}
+
+void
+StumblerInfo::SetCellInfoResponsesExpected(uint8_t count)
+{
+  mCellInfoResponsesExpected = count;
+  STUMBLER_DBG("SetCellInfoNum (%d)\n", count);
+
+  if (mIsWifiInfoResponseReceived && mCellInfoResponsesReceived == mCellInfoResponsesExpected) {
+    STUMBLER_DBG("Call DumpStumblerInfo from SetCellInfoResponsesExpected\n");
+    DumpStumblerInfo();
+  }
+}
+
+
+#define TEXT_LAT NS_LITERAL_CSTRING("latitude")
+#define TEXT_LON NS_LITERAL_CSTRING("longitude")
+#define TEXT_ACC NS_LITERAL_CSTRING("accuracy")
+#define TEXT_ALT NS_LITERAL_CSTRING("altitude")
+#define TEXT_ALTACC NS_LITERAL_CSTRING("altitudeAccuracy")
+#define TEXT_HEAD NS_LITERAL_CSTRING("heading")
+#define TEXT_SPD NS_LITERAL_CSTRING("speed")
+
+nsresult
+StumblerInfo::LocationInfoToString(nsACString& aLocDesc)
+{
+  nsCOMPtr<nsIDOMGeoPositionCoords> coords;
+  mPosition->GetCoords(getter_AddRefs(coords));
+  if (!coords) {
+    return NS_ERROR_FAILURE;
+  }
+
+  nsDataHashtable<nsCStringHashKey, double> info;
+
+  double val;
+  coords->GetLatitude(&val);
+  info.Put(TEXT_LAT, val);
+  coords->GetLongitude(&val);
+  info.Put(TEXT_LON, val);
+  coords->GetAccuracy(&val);
+  info.Put(TEXT_ACC, val);
+  coords->GetAltitude(&val);
+  info.Put(TEXT_ALT, val);
+  coords->GetAltitudeAccuracy(&val);
+  info.Put(TEXT_ALTACC, val);
+  coords->GetHeading(&val);
+  info.Put(TEXT_HEAD, val);
+  coords->GetSpeed(&val);
+  info.Put(TEXT_SPD, val);
+
+  for (auto it = info.Iter(); !it.Done(); it.Next()) {
+    const nsACString& key = it.Key();
+    val = it.UserData();
+    if (!IsNaN(val)) {
+      aLocDesc += nsPrintfCString("\"%s\":%f,", key.BeginReading(), val);
+    }
+  }
+
+  aLocDesc += nsPrintfCString("\"timestamp\":%lld,", PR_Now() / PR_USEC_PER_MSEC).get();
+  return NS_OK;
+}
+
+#define TEXT_RADIOTYPE NS_LITERAL_CSTRING("radioType")
+#define TEXT_MCC NS_LITERAL_CSTRING("mobileCountryCode")
+#define TEXT_MNC NS_LITERAL_CSTRING("mobileNetworkCode")
+#define TEXT_LAC NS_LITERAL_CSTRING("locationAreaCode")
+#define TEXT_CID NS_LITERAL_CSTRING("cellId")
+#define TEXT_PSC NS_LITERAL_CSTRING("psc")
+#define TEXT_STRENGTH_ASU NS_LITERAL_CSTRING("asu")
+#define TEXT_STRENGTH_DBM NS_LITERAL_CSTRING("signalStrength")
+#define TEXT_REGISTERED NS_LITERAL_CSTRING("serving")
+#define TEXT_TIMEING_ADVANCE NS_LITERAL_CSTRING("timingAdvance")
+
+template <class T> void
+ExtractCommonNonCDMACellInfoItems(nsCOMPtr<T>& cell, nsDataHashtable<nsCStringHashKey, int32_t>& info)
+{
+  int32_t mcc, mnc, cid, sig;
+
+  cell->GetMcc(&mcc);
+  cell->GetMnc(&mnc);
+  cell->GetCid(&cid);
+  cell->GetSignalStrength(&sig);
+
+  info.Put(TEXT_MCC, mcc);
+  info.Put(TEXT_MNC, mnc);
+  info.Put(TEXT_CID, cid);
+  info.Put(TEXT_STRENGTH_ASU, sig);
+}
+
+void
+StumblerInfo::CellNetworkInfoToString(nsACString& aCellDesc)
+{
+  aCellDesc += "\"cellTowers\": [";
+
+  for (uint32_t idx = 0; idx < mCellInfo.Length() ; idx++) {
+    const char* radioType = 0;
+    int32_t type;
+    mCellInfo[idx]->GetType(&type);
+    bool registered;
+    mCellInfo[idx]->GetRegistered(&registered);
+    if (idx) {
+      aCellDesc += ",{";
+    } else {
+      aCellDesc += "{";
+    }
+
+    STUMBLER_DBG("type=%d\n", type);
+
+    nsDataHashtable<nsCStringHashKey, int32_t> info;
+    info.Put(TEXT_REGISTERED, registered);
+
+    if(type == nsICellInfo::CELL_INFO_TYPE_GSM) {
+      radioType = "gsm";
+      nsCOMPtr<nsIGsmCellInfo> gsmCellInfo = do_QueryInterface(mCellInfo[idx]);
+      ExtractCommonNonCDMACellInfoItems(gsmCellInfo, info);
+      int32_t lac;
+      gsmCellInfo->GetLac(&lac);
+      info.Put(TEXT_LAC, lac);
+    } else if (type == nsICellInfo::CELL_INFO_TYPE_WCDMA) {
+      radioType = "wcdma";
+      nsCOMPtr<nsIWcdmaCellInfo> wcdmaCellInfo = do_QueryInterface(mCellInfo[idx]);
+      ExtractCommonNonCDMACellInfoItems(wcdmaCellInfo, info);
+      int32_t lac, psc;
+      wcdmaCellInfo->GetLac(&lac);
+      wcdmaCellInfo->GetPsc(&psc);
+      info.Put(TEXT_LAC, lac);
+      info.Put(TEXT_PSC, psc);
+    } else if (type == nsICellInfo::CELL_INFO_TYPE_CDMA) {
+      radioType = "cdma";
+      nsCOMPtr<nsICdmaCellInfo> cdmaCellInfo = do_QueryInterface(mCellInfo[idx]);
+      int32_t mnc, lac, cid, sig;
+      cdmaCellInfo->GetSystemId(&mnc);
+      cdmaCellInfo->GetNetworkId(&lac);
+      cdmaCellInfo->GetBaseStationId(&cid);
+      info.Put(TEXT_MNC, mnc);
+      info.Put(TEXT_LAC, lac);
+      info.Put(TEXT_CID, cid);
+
+      cdmaCellInfo->GetEvdoDbm(&sig);
+      if (sig < 0 || sig == nsICellInfo::UNKNOWN_VALUE) {
+        cdmaCellInfo->GetCdmaDbm(&sig);
+      }
+      if (sig > -1 && sig != nsICellInfo::UNKNOWN_VALUE)  {
+        sig *= -1;
+        info.Put(TEXT_STRENGTH_DBM, sig);
+      }
+    } else if (type == nsICellInfo::CELL_INFO_TYPE_LTE) {
+      radioType = "lte";
+      nsCOMPtr<nsILteCellInfo> lteCellInfo = do_QueryInterface(mCellInfo[idx]);
+      ExtractCommonNonCDMACellInfoItems(lteCellInfo, info);
+      int32_t lac, timingAdvance, pcid, rsrp;
+      lteCellInfo->GetTac(&lac);
+      lteCellInfo->GetTimingAdvance(&timingAdvance);
+      lteCellInfo->GetPcid(&pcid);
+      lteCellInfo->GetRsrp(&rsrp);
+      info.Put(TEXT_LAC, lac);
+      info.Put(TEXT_TIMEING_ADVANCE, timingAdvance);
+      info.Put(TEXT_PSC, pcid);
+      if (rsrp != nsICellInfo::UNKNOWN_VALUE) {
+        info.Put(TEXT_STRENGTH_DBM, rsrp * -1);
+      }
+    }
+
+    aCellDesc += nsPrintfCString("\"%s\":\"%s\"", TEXT_RADIOTYPE.get(), radioType);
+    for (auto it = info.Iter(); !it.Done(); it.Next()) {
+      const nsACString& key = it.Key();
+      int32_t value = it.UserData();
+      if (value != nsICellInfo::UNKNOWN_VALUE) {
+        aCellDesc += nsPrintfCString(",\"%s\":%d", key.BeginReading(), value);
+      }
+    }
+
+    aCellDesc += "}";
+  }
+  aCellDesc += "]";
+}
+
+void
+StumblerInfo::DumpStumblerInfo()
+{
+  if (!mIsWifiInfoResponseReceived || mCellInfoResponsesReceived != mCellInfoResponsesExpected) {
+    STUMBLER_DBG("CellInfoReceived=%d (Expected=%d), WifiInfoResponseReceived=%d\n",
+                  mCellInfoResponsesReceived, mCellInfoResponsesExpected, mIsWifiInfoResponseReceived);
+    return;
+  }
+  mIsWifiInfoResponseReceived = false;
+  mCellInfoResponsesReceived = 0;
+
+  nsAutoCString desc;
+  nsresult rv = LocationInfoToString(desc);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    STUMBLER_ERR("LocationInfoToString failed, skip this dump");
+    return;
+  }
+
+  CellNetworkInfoToString(desc);
+  desc += mWifiDesc;
+
+  STUMBLER_DBG("dispatch write event to thread\n");
+  nsCOMPtr<nsIEventTarget> target = do_GetService(NS_STREAMTRANSPORTSERVICE_CONTRACTID);
+  MOZ_ASSERT(target);
+
+  nsCOMPtr<nsIRunnable> event = new WriteStumbleOnThread(desc);
+  target->Dispatch(event, NS_DISPATCH_NORMAL);
+}
+
+/* void notifyGetCellInfoList (in uint32_t count, [array, size_is (count)] in nsICellInfo result); */
+NS_IMETHODIMP
+StumblerInfo::NotifyGetCellInfoList(uint32_t count, nsICellInfo** aCellInfos)
+{
+  MOZ_ASSERT(NS_IsMainThread());
+  STUMBLER_DBG("There are %d cellinfo in the result\n", count);
+
+  for (uint32_t i = 0; i < count; i++) {
+    mCellInfo.AppendElement(aCellInfos[i]);
+  }
+  mCellInfoResponsesReceived++;
+  DumpStumblerInfo();
+  return NS_OK;
+}
+
+/* void notifyGetCellInfoListFailed (in DOMString error); */
+NS_IMETHODIMP StumblerInfo::NotifyGetCellInfoListFailed(const nsAString& error)
+{
+  MOZ_ASSERT(NS_IsMainThread());
+  mCellInfoResponsesReceived++;
+  STUMBLER_ERR("NotifyGetCellInfoListFailedm CellInfoReadyNum=%d, mCellInfoResponsesExpected=%d, mIsWifiInfoResponseReceived=%d",
+                mCellInfoResponsesReceived, mCellInfoResponsesExpected, mIsWifiInfoResponseReceived);
+  DumpStumblerInfo();
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+StumblerInfo::Onready(uint32_t count, nsIWifiScanResult** results)
+{
+  MOZ_ASSERT(NS_IsMainThread());
+  STUMBLER_DBG("There are %d wifiAPinfo in the result\n",count);
+
+  mWifiDesc += ",\"wifiAccessPoints\": [";
+  bool firstItem = true;
+  for (uint32_t i = 0 ; i < count ; i++) {
+    nsString ssid;
+    results[i]->GetSsid(ssid);
+    if (ssid.IsEmpty()) {
+      STUMBLER_DBG("no ssid, skip this AP\n");
+      continue;
+    }
+
+    if (ssid.Length() >= 6) {
+      if (StringEndsWith(ssid, NS_LITERAL_STRING("_nomap"))) {
+        STUMBLER_DBG("end with _nomap. skip this AP(ssid :%s)\n", ssid.get());
+        continue;
+      }
+    }
+
+    if (firstItem) {
+      mWifiDesc += "{";
+      firstItem = false;
+    } else {
+      mWifiDesc += ",{";
+    }
+
+    // mac address
+    nsString bssid;
+    results[i]->GetBssid(bssid);
+    //   00:00:00:00:00:00 --> 000000000000
+    bssid.StripChars(":");
+    mWifiDesc += "\"macAddress\":\"";
+    mWifiDesc += NS_ConvertUTF16toUTF8(bssid);
+
+    uint32_t signal;
+    results[i]->GetSignalStrength(&signal);
+    mWifiDesc += "\",\"signalStrength\":";
+    mWifiDesc.AppendInt(signal);
+
+    mWifiDesc += "}";
+  }
+  mWifiDesc += "]";
+
+  mIsWifiInfoResponseReceived = true;
+  DumpStumblerInfo();
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+StumblerInfo::Onfailure()
+{
+  MOZ_ASSERT(NS_IsMainThread());
+  STUMBLER_ERR("GetWifiScanResults Onfailure\n");
+  mIsWifiInfoResponseReceived = true;
+  DumpStumblerInfo();
+  return NS_OK;
+}
+
new file mode 100644
--- /dev/null
+++ b/dom/system/gonk/mozstumbler/MozStumbler.h
@@ -0,0 +1,45 @@
+/* -*- Mode: c++; c-basic-offset: 2; indent-tabs-mode: nil; tab-width: 40 -*- */
+/* vim: set ts=2 et sw=2 tw=80: */
+/* 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_system_mozstumbler_h__
+#define mozilla_system_mozstumbler_h__
+
+#include "nsIDOMEventTarget.h"
+#include "nsICellInfo.h"
+#include "nsIWifi.h"
+
+#define STUMBLE_INTERVAL_MS 3000
+
+class nsGeoPosition;
+
+class StumblerInfo final : public nsICellInfoListCallback,
+                           public nsIWifiScanResultsReady
+{
+public:
+  NS_DECL_THREADSAFE_ISUPPORTS
+  NS_DECL_NSICELLINFOLISTCALLBACK
+  NS_DECL_NSIWIFISCANRESULTSREADY
+
+  explicit StumblerInfo(nsGeoPosition* position)
+    : mPosition(position), mCellInfoResponsesExpected(0), mCellInfoResponsesReceived(0), mIsWifiInfoResponseReceived(0)
+  {}
+  void SetWifiInfoResponseReceived();
+  void SetCellInfoResponsesExpected(uint8_t count);
+
+private:
+  ~StumblerInfo() {}
+  void DumpStumblerInfo();
+  nsresult LocationInfoToString(nsACString& aLocDesc);
+  void CellNetworkInfoToString(nsACString& aCellDesc);
+  nsTArray<nsRefPtr<nsICellInfo>> mCellInfo;
+  nsCString mWifiDesc;
+  nsRefPtr<nsGeoPosition> mPosition;
+  int mCellInfoResponsesExpected;
+  int mCellInfoResponsesReceived;
+  bool mIsWifiInfoResponseReceived;
+};
+#endif // mozilla_system_mozstumbler_h__
+
new file mode 100644
--- /dev/null
+++ b/dom/system/gonk/mozstumbler/StumblerLogging.cpp
@@ -0,0 +1,13 @@
+/* -*- Mode: c++; c-basic-offset: 2; indent-tabs-mode: nil; tab-width: 40 -*- */
+/* vim: set ts=2 et sw=2 tw=80: */
+/* 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 "StumblerLogging.h"
+
+PRLogModuleInfo* GetLog()
+{
+  static PRLogModuleInfo* log = PR_NewLogModule("mozstumbler");
+  return log;
+}
new file mode 100644
--- /dev/null
+++ b/dom/system/gonk/mozstumbler/StumblerLogging.h
@@ -0,0 +1,18 @@
+/* -*- Mode: c++; c-basic-offset: 2; indent-tabs-mode: nil; tab-width: 40 -*- */
+/* vim: set ts=2 et sw=2 tw=80: */
+/* 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 STUMBLERLOGGING_H
+#define STUMBLERLOGGING_H
+
+#include "mozilla/Logging.h"
+
+PRLogModuleInfo* GetLog();
+
+#define STUMBLER_DBG(arg, ...)  MOZ_LOG(GetLog(), mozilla::LogLevel::Debug, ("STUMBLER - %s: " arg, __func__, ##__VA_ARGS__))
+#define STUMBLER_LOG(arg, ...)  MOZ_LOG(GetLog(), mozilla::LogLevel::Info, ("STUMBLER - %s: " arg, __func__, ##__VA_ARGS__))
+#define STUMBLER_ERR(arg, ...)  MOZ_LOG(GetLog(), mozilla::LogLevel::Error, ("STUMBLER -%s: " arg, __func__, ##__VA_ARGS__))
+
+#endif
new file mode 100644
--- /dev/null
+++ b/dom/system/gonk/mozstumbler/UploadStumbleRunnable.cpp
@@ -0,0 +1,151 @@
+/* -*- Mode: c++; c-basic-offset: 2; indent-tabs-mode: nil; tab-width: 40 -*- */
+/* vim: set ts=2 et sw=2 tw=80: */
+/* 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 "UploadStumbleRunnable.h"
+#include "StumblerLogging.h"
+#include "mozilla/dom/Event.h"
+#include "nsIScriptSecurityManager.h"
+#include "nsIURLFormatter.h"
+#include "nsIVariant.h"
+#include "nsIXMLHttpRequest.h"
+#include "nsNetUtil.h"
+
+UploadStumbleRunnable::UploadStumbleRunnable(const nsACString& aUploadData)
+: mUploadData(aUploadData)
+{
+}
+
+NS_IMETHODIMP
+UploadStumbleRunnable::Run()
+{
+  MOZ_ASSERT(NS_IsMainThread());
+  nsresult rv = Upload();
+  if (NS_FAILED(rv)) {
+    WriteStumbleOnThread::UploadEnded(false);
+  }
+  return NS_OK;
+}
+
+nsresult
+UploadStumbleRunnable::Upload()
+{
+  nsresult rv;
+  nsCOMPtr<nsIWritableVariant> variant = do_CreateInstance("@mozilla.org/variant;1", &rv);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  rv = variant->SetAsACString(mUploadData);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  nsCOMPtr<nsIXMLHttpRequest> xhr = do_CreateInstance(NS_XMLHTTPREQUEST_CONTRACTID, &rv);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  nsCOMPtr<nsIScriptSecurityManager> secman =
+    do_GetService(NS_SCRIPTSECURITYMANAGER_CONTRACTID, &rv);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  nsCOMPtr<nsIPrincipal> systemPrincipal;
+  rv = secman->GetSystemPrincipal(getter_AddRefs(systemPrincipal));
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  rv = xhr->Init(systemPrincipal, nullptr, nullptr, nullptr, nullptr);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  nsCOMPtr<nsIURLFormatter> formatter =
+    do_CreateInstance("@mozilla.org/toolkit/URLFormatterService;1", &rv);
+  NS_ENSURE_SUCCESS(rv, rv);
+  nsString url;
+  rv = formatter->FormatURLPref(NS_LITERAL_STRING("geo.stumbler.url"), url);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  rv = xhr->Open(NS_LITERAL_CSTRING("POST"), NS_ConvertUTF16toUTF8(url), false, EmptyString(), EmptyString());
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  xhr->SetRequestHeader(NS_LITERAL_CSTRING("Content-Type"), NS_LITERAL_CSTRING("application/json"));
+  xhr->SetMozBackgroundRequest(true);
+  // 60s timeout
+  xhr->SetTimeout(60 * 1000);
+
+  nsCOMPtr<EventTarget> target(do_QueryInterface(xhr));
+  nsRefPtr<nsIDOMEventListener> listener = new UploadEventListener(xhr, mUploadData.Length());
+
+  const char* const sEventStrings[] = {
+    // nsIXMLHttpRequestEventTarget event types
+    "abort",
+    "error",
+    "load",
+    "timeout"
+  };
+
+  for (uint32_t index = 0; index < MOZ_ARRAY_LENGTH(sEventStrings); index++) {
+    nsAutoString eventType = NS_ConvertASCIItoUTF16(sEventStrings[index]);
+    rv = target->AddEventListener(eventType, listener, false);
+    NS_ENSURE_SUCCESS(rv, rv);
+  }
+
+  rv = xhr->Send(variant);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  return NS_OK;
+}
+
+NS_IMPL_ISUPPORTS(UploadEventListener, nsIDOMEventListener)
+
+UploadEventListener::UploadEventListener(nsIXMLHttpRequest* aXHR, int64_t aFileSize)
+: mXHR(aXHR), mFileSize(aFileSize)
+{
+}
+
+NS_IMETHODIMP
+UploadEventListener::HandleEvent(nsIDOMEvent* aEvent)
+{
+  nsString type;
+  if (NS_FAILED(aEvent->GetType(type))) {
+    STUMBLER_ERR("Failed to get event type");
+    WriteStumbleOnThread::UploadEnded(false);
+    return NS_ERROR_FAILURE;
+  }
+
+  if (type.EqualsLiteral("load")) {
+    STUMBLER_DBG("Got load Event : size %lld", mFileSize);
+  } else if (type.EqualsLiteral("error") && mXHR) {
+    STUMBLER_ERR("Upload Error");
+  } else {
+    STUMBLER_DBG("Receive %s Event", NS_ConvertUTF16toUTF8(type).get());
+  }
+
+  uint32_t statusCode = 0;
+  bool doDelete = false;
+  if (!mXHR) {
+    return NS_OK;
+  }
+  nsresult rv = mXHR->GetStatus(&statusCode);
+  if (NS_SUCCEEDED(rv)) {
+    STUMBLER_DBG("statuscode %d \n", statusCode);
+  }
+
+  if (200 == statusCode || 400 == statusCode) {
+    doDelete = true;
+  }
+
+  WriteStumbleOnThread::UploadEnded(doDelete);
+  nsCOMPtr<EventTarget> target(do_QueryInterface(mXHR));
+
+  const char* const sEventStrings[] = {
+    // nsIXMLHttpRequestEventTarget event types
+    "abort",
+    "error",
+    "load",
+    "timeout"
+  };
+
+  for (uint32_t index = 0; index < MOZ_ARRAY_LENGTH(sEventStrings); index++) {
+    nsAutoString eventType = NS_ConvertASCIItoUTF16(sEventStrings[index]);
+    rv = target->RemoveEventListener(eventType, this, false);
+  }
+
+  mXHR = nullptr;
+  return NS_OK;
+}
new file mode 100644
--- /dev/null
+++ b/dom/system/gonk/mozstumbler/UploadStumbleRunnable.h
@@ -0,0 +1,46 @@
+/* -*- Mode: c++; c-basic-offset: 2; indent-tabs-mode: nil; tab-width: 40 -*- */
+/* vim: set ts=2 et sw=2 tw=80: */
+/* 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 UPLOADSTUMBLERUNNABLE_H
+#define UPLOADSTUMBLERUNNABLE_H
+
+#include "nsIDOMEventListener.h"
+
+class nsIXMLHttpRequest;
+
+/*
+ This runnable is managed by WriteStumbleOnThread only, see that class
+ for how this is scheduled.
+ */
+class UploadStumbleRunnable final : public nsRunnable
+{
+public:
+  explicit UploadStumbleRunnable(const nsACString& aUploadData);
+
+  NS_IMETHOD Run() override;
+private:
+  virtual ~UploadStumbleRunnable() {}
+  const nsCString mUploadData;
+  nsresult Upload();
+};
+
+
+class UploadEventListener : public nsIDOMEventListener
+{
+public:
+  UploadEventListener(nsIXMLHttpRequest* aXHR, int64_t aFileSize);
+
+  NS_DECL_ISUPPORTS
+  NS_DECL_NSIDOMEVENTLISTENER
+
+protected:
+  virtual ~UploadEventListener() {}
+  nsCOMPtr<nsIXMLHttpRequest> mXHR;
+  int64_t mFileSize;
+};
+
+#endif
new file mode 100644
--- /dev/null
+++ b/dom/system/gonk/mozstumbler/WriteStumbleOnThread.cpp
@@ -0,0 +1,317 @@
+/* -*- Mode: c++; c-basic-offset: 2; indent-tabs-mode: nil; tab-width: 40 -*- */
+/* vim: set ts=2 et sw=2 tw=80: */
+/* 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 "WriteStumbleOnThread.h"
+#include "StumblerLogging.h"
+#include "UploadStumbleRunnable.h"
+#include "nsDumpUtils.h"
+#include "nsGZFileWriter.h"
+#include "nsIFileStreams.h"
+#include "nsIInputStream.h"
+#include "nsPrintfCString.h"
+
+#define MAXFILESIZE_KB (15 * 1024)
+#define ONEDAY_IN_MSEC (24 * 60 * 60 * 1000)
+#define MAX_UPLOAD_ATTEMPTS 20
+
+mozilla::Atomic<bool> WriteStumbleOnThread::sIsUploading(false);
+mozilla::Atomic<bool> WriteStumbleOnThread::sIsAlreadyRunning(false);
+WriteStumbleOnThread::UploadFreqGuard WriteStumbleOnThread::sUploadFreqGuard = {0};
+
+#define FILENAME_INPROGRESS NS_LITERAL_CSTRING("stumbles.json")
+#define FILENAME_COMPLETED NS_LITERAL_CSTRING("stumbles.done.json")
+#define OUTPUT_DIR NS_LITERAL_CSTRING("mozstumbler")
+
+class DeleteRunnable : public nsRunnable
+{
+  public:
+    DeleteRunnable() {}
+
+    NS_IMETHODIMP
+    Run() override
+    {
+      nsCOMPtr<nsIFile> tmpFile;
+      nsresult rv = nsDumpUtils::OpenTempFile(FILENAME_COMPLETED,
+                                              getter_AddRefs(tmpFile),
+                                              OUTPUT_DIR,
+                                              nsDumpUtils::CREATE);
+      if (NS_SUCCEEDED(rv)) {
+        tmpFile->Remove(true);
+      }
+      // critically, this sets this flag to false so writing can happen again
+      WriteStumbleOnThread::sIsUploading = false;
+      return NS_OK;
+    }
+
+  private:
+    ~DeleteRunnable() {}
+};
+
+void
+WriteStumbleOnThread::UploadEnded(bool deleteUploadFile)
+{
+  if (!deleteUploadFile) {
+    sIsUploading = false;
+    return;
+  }
+
+  nsCOMPtr<nsIEventTarget> target = do_GetService(NS_STREAMTRANSPORTSERVICE_CONTRACTID);
+  MOZ_ASSERT(target);
+  nsCOMPtr<nsIRunnable> event = new DeleteRunnable();
+  target->Dispatch(event, NS_DISPATCH_NORMAL);
+}
+
+#define DUMP(o, s) \
+  do { \
+    const char* s2 = (s); \
+    uint32_t dummy; \
+    nsresult rv = (o)->Write((s2), strlen(s2), &dummy); \
+    if (NS_WARN_IF(NS_FAILED(rv))) \
+    STUMBLER_ERR("write err"); \
+  } while (0)
+
+void
+WriteStumbleOnThread::WriteJSON(Partition aPart)
+{
+  MOZ_ASSERT(!NS_IsMainThread());
+
+  nsCOMPtr<nsIFile> tmpFile;
+  nsresult rv;
+  rv = nsDumpUtils::OpenTempFile(FILENAME_INPROGRESS, getter_AddRefs(tmpFile),
+                                 OUTPUT_DIR, nsDumpUtils::CREATE);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    STUMBLER_ERR("Open a file for stumble failed");
+    return;
+  }
+
+  nsCOMPtr<nsIFileOutputStream> ostream = do_CreateInstance("@mozilla.org/network/file-output-stream;1");
+  rv = ostream->Init(tmpFile, PR_WRONLY | PR_APPEND, 0666, 0);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    STUMBLER_ERR("Open a file for stumble failed");
+    return;
+  }
+
+  /*
+   The json format is like below.
+   {items:[
+   {item},
+   {item},
+   {item}
+   ]}
+   */
+
+  // Need to add "]}" after the last item
+  if (aPart == Partition::End) {
+    DUMP(ostream, "]}");
+    rv = ostream->Close();
+    if (NS_WARN_IF(NS_FAILED(rv))) {
+      STUMBLER_ERR("ostream finish failed");
+    }
+
+    nsCOMPtr<nsIFile> targetFile;
+    nsresult rv = nsDumpUtils::OpenTempFile(FILENAME_COMPLETED, getter_AddRefs(targetFile),
+                                            OUTPUT_DIR, nsDumpUtils::CREATE);
+    nsAutoString targetFilename;
+    rv = targetFile->GetLeafName(targetFilename);
+    if (NS_WARN_IF(NS_FAILED(rv))) {
+      STUMBLER_ERR("Get Filename failed");
+      return;
+    }
+    rv = targetFile->Remove(true);
+    if (NS_WARN_IF(NS_FAILED(rv))) {
+      STUMBLER_ERR("Remove File failed");
+      return;
+    }
+    // Rename tmpfile
+    rv = tmpFile->MoveTo(/* directory */ nullptr, targetFilename);
+    if (NS_WARN_IF(NS_FAILED(rv))) {
+      STUMBLER_ERR("Rename File failed");
+      return;
+    }
+    return;
+  }
+
+  // Need to add "{items:[" before the first item
+  if (aPart == Partition::Begining) {
+    DUMP(ostream, "{\"items\":[{");
+  } else if (aPart == Partition::Middle) {
+    DUMP(ostream, ",{");
+  }
+  DUMP(ostream, mDesc.get());
+  //  one item is ended with '}' (e.g. {item})
+  DUMP(ostream, "}");
+  rv = ostream->Close();
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    STUMBLER_ERR("ostream finish failed");
+  }
+
+  // check if it is the end of this file
+  int64_t fileSize = 0;
+  rv = tmpFile->GetFileSize(&fileSize);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    STUMBLER_ERR("GetFileSize failed");
+    return;
+  }
+  if (fileSize >= MAXFILESIZE_KB) {
+    WriteJSON(Partition::End);
+    return;
+  }
+}
+
+WriteStumbleOnThread::Partition
+WriteStumbleOnThread::GetWritePosition()
+{
+  MOZ_ASSERT(!NS_IsMainThread());
+
+  nsCOMPtr<nsIFile> tmpFile;
+  nsresult rv = nsDumpUtils::OpenTempFile(FILENAME_INPROGRESS, getter_AddRefs(tmpFile),
+                                          OUTPUT_DIR, nsDumpUtils::CREATE);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    STUMBLER_ERR("Open a file for stumble failed");
+    return Partition::Unknown;
+  }
+
+  int64_t fileSize = 0;
+  rv = tmpFile->GetFileSize(&fileSize);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    STUMBLER_ERR("GetFileSize failed");
+    return Partition::Unknown;
+  }
+
+  if (fileSize == 0) {
+    return Partition::Begining;
+  } else if (fileSize >= MAXFILESIZE_KB) {
+    return Partition::End;
+  } else {
+    return Partition::Middle;
+  }
+}
+
+NS_IMETHODIMP
+WriteStumbleOnThread::Run()
+{
+  MOZ_ASSERT(!NS_IsMainThread());
+
+  bool b = sIsAlreadyRunning.exchange(true);
+  if (b) {
+    return NS_OK;
+  }
+
+  UploadFileStatus status = GetUploadFileStatus();
+
+  if (UploadFileStatus::NoFile != status) {
+    if (UploadFileStatus::ExistsAndReadyToUpload == status) {
+      Upload();
+    }
+  } else {
+    Partition partition = GetWritePosition();
+    if (partition == Partition::Unknown) {
+      STUMBLER_ERR("GetWritePosition failed, skip once");
+    } else {
+      WriteJSON(partition);
+    }
+  }
+
+  sIsAlreadyRunning = false;
+  return NS_OK;
+}
+
+
+/*
+ If the upload file exists, then check if it is one day old.
+ • if it is a day old -> ExistsAndReadyToUpload
+ • if it is less than the current day old -> Exists
+ • otherwise -> NoFile
+
+ The Exists case means that the upload and the stumbling is rate limited
+ per-day to the size of the one file.
+ */
+WriteStumbleOnThread::UploadFileStatus
+WriteStumbleOnThread::GetUploadFileStatus()
+{
+  nsCOMPtr<nsIFile> tmpFile;
+  nsresult rv = nsDumpUtils::OpenTempFile(FILENAME_COMPLETED, getter_AddRefs(tmpFile),
+                                          OUTPUT_DIR, nsDumpUtils::CREATE);
+  int64_t fileSize;
+  rv = tmpFile->GetFileSize(&fileSize);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    STUMBLER_ERR("GetFileSize failed");
+    return UploadFileStatus::NoFile;
+  }
+  if (fileSize <= 0) {
+    tmpFile->Remove(true);
+    return UploadFileStatus::NoFile;
+  }
+
+  PRTime lastModifiedTime;
+  tmpFile->GetLastModifiedTime(&lastModifiedTime);
+  if ((PR_Now() / PR_USEC_PER_MSEC) - lastModifiedTime >= ONEDAY_IN_MSEC) {
+    return UploadFileStatus::ExistsAndReadyToUpload;
+  }
+  return UploadFileStatus::Exists;
+}
+
+void
+WriteStumbleOnThread::Upload()
+{
+  MOZ_ASSERT(!NS_IsMainThread());
+
+  bool b = sIsUploading.exchange(true);
+  if (b) {
+    return;
+  }
+
+  time_t seconds = time(0);
+  int day = seconds / (60 * 60 * 24);
+
+  if (sUploadFreqGuard.daySinceEpoch < day) {
+    sUploadFreqGuard.daySinceEpoch = day;
+    sUploadFreqGuard.attempts = 0;
+  }
+
+  sUploadFreqGuard.attempts++;
+  if (sUploadFreqGuard.attempts > MAX_UPLOAD_ATTEMPTS) {
+    STUMBLER_ERR("Too many upload attempts today");
+    sIsUploading = false;
+    return;
+  }
+
+  nsCOMPtr<nsIFile> tmpFile;
+  nsresult rv = nsDumpUtils::OpenTempFile(FILENAME_COMPLETED, getter_AddRefs(tmpFile),
+                                          OUTPUT_DIR, nsDumpUtils::CREATE);
+  int64_t fileSize;
+  rv = tmpFile->GetFileSize(&fileSize);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    STUMBLER_ERR("GetFileSize failed");
+    sIsUploading = false;
+    return;
+  }
+
+  if (fileSize <= 0) {
+    sIsUploading = false;
+    return;
+  }
+
+  // prepare json into nsIInputStream
+  nsCOMPtr<nsIInputStream> inStream;
+  rv = NS_NewLocalFileInputStream(getter_AddRefs(inStream), tmpFile, -1, -1,
+                                  nsIFileInputStream::DEFER_OPEN);
+  if (NS_FAILED(rv)) {
+    sIsUploading = false;
+    return;
+  }
+
+  nsCString bufStr;
+  rv = NS_ReadInputStreamToString(inStream, bufStr, fileSize);
+
+  if (NS_FAILED(rv)) {
+    sIsUploading = false;
+    return;
+  }
+
+  nsRefPtr<nsIRunnable> uploader = new UploadStumbleRunnable(bufStr);
+  NS_DispatchToMainThread(uploader);
+}
new file mode 100644
--- /dev/null
+++ b/dom/system/gonk/mozstumbler/WriteStumbleOnThread.h
@@ -0,0 +1,84 @@
+/* -*- Mode: c++; c-basic-offset: 2; indent-tabs-mode: nil; tab-width: 40 -*- */
+/* vim: set ts=2 et sw=2 tw=80: */
+/* 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 WriteStumbleOnThread_H
+#define WriteStumbleOnThread_H
+
+#include "mozilla/Atomics.h"
+
+/*
+ This class is the entry point to stumbling, in that it
+ receives the location+cell+wifi string and writes it
+ to disk, or instead, it calls UploadStumbleRunnable
+ to upload the data.
+
+ Writes will happen until the file is a max size, then stop.
+ Uploads will happen only when the file is one day old.
+ The purpose of these decisions is to have very simple rate-limiting
+ on the writes, as well as the uploads.
+
+ There is only one file active; it is either being used for writing,
+ or for uploading. If the file is ready for uploading, no further
+ writes will take place until this file has been uploaded.
+ This can mean writing might not take place for days until the uploaded
+ file is processed. This is correct by-design.
+
+ A notable limitation is that the upload is triggered by a location event,
+ this is used as an arbitrary and simple trigger. In future, there are
+ better events that can be used, such as detecting network activity.
+
+ This thread is guarded so that only one instance is active (see the
+ mozilla::Atomics used for this).
+ */
+class WriteStumbleOnThread : public nsRunnable
+{
+public:
+  explicit WriteStumbleOnThread(const nsCString& aDesc)
+  : mDesc(aDesc)
+  {}
+
+  NS_IMETHODIMP Run() override;
+
+  static void UploadEnded(bool deleteUploadFile);
+  // Don't write while uploading is happening
+  static mozilla::Atomic<bool> sIsUploading;
+
+private:
+
+  enum class Partition {
+    Begining,
+    Middle,
+    End,
+    Unknown
+  };
+
+  enum class UploadFileStatus {
+    NoFile, Exists, ExistsAndReadyToUpload
+  };
+
+  ~WriteStumbleOnThread() {}
+
+  Partition GetWritePosition();
+  UploadFileStatus GetUploadFileStatus();
+  void WriteJSON(Partition aPart);
+  void Upload();
+
+  nsCString mDesc;
+
+  // Only run one instance of this
+  static mozilla::Atomic<bool> sIsAlreadyRunning;
+
+  // Limit the upload attempts per day. If the device is rebooted
+  // this resets the allowed attempts, which is acceptable.
+  struct UploadFreqGuard {
+    int attempts;
+    int daySinceEpoch;
+  };
+  static UploadFreqGuard sUploadFreqGuard;
+
+};
+
+#endif
--- a/dom/workers/ServiceWorkerClients.cpp
+++ b/dom/workers/ServiceWorkerClients.cpp
@@ -60,83 +60,66 @@ public:
   }
 
   bool
   WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate)
   {
     MOZ_ASSERT(aWorkerPrivate);
     aWorkerPrivate->AssertIsOnWorkerThread();
 
-    Promise* promise = mPromiseProxy->GetWorkerPromise();
+    Promise* promise = mPromiseProxy->WorkerPromise();
     MOZ_ASSERT(promise);
 
     nsTArray<nsRefPtr<ServiceWorkerClient>> ret;
     for (size_t i = 0; i < mValue.Length(); i++) {
       ret.AppendElement(nsRefPtr<ServiceWorkerClient>(
             new ServiceWorkerWindowClient(promise->GetParentObject(),
                                           mValue.ElementAt(i))));
     }
+
     promise->MaybeResolve(ret);
-
-    // release the reference on the worker thread.
     mPromiseProxy->CleanUp(aCx);
-
     return true;
   }
 };
 
 class MatchAllRunnable final : public nsRunnable
 {
-  WorkerPrivate* mWorkerPrivate;
   nsRefPtr<PromiseWorkerProxy> mPromiseProxy;
   nsCString mScope;
 public:
-  MatchAllRunnable(WorkerPrivate* aWorkerPrivate,
-                   PromiseWorkerProxy* aPromiseProxy,
+  MatchAllRunnable(PromiseWorkerProxy* aPromiseProxy,
                    const nsCString& aScope)
-    : mWorkerPrivate(aWorkerPrivate),
-      mPromiseProxy(aPromiseProxy),
+    : mPromiseProxy(aPromiseProxy),
       mScope(aScope)
   {
-    MOZ_ASSERT(aWorkerPrivate);
-    aWorkerPrivate->AssertIsOnWorkerThread();
+    MOZ_ASSERT(mPromiseProxy);
   }
 
   NS_IMETHOD
   Run() override
   {
     AssertIsOnMainThread();
 
-    MutexAutoLock lock(mPromiseProxy->GetCleanUpLock());
-    if (mPromiseProxy->IsClean()) {
-      // Don't resolve the promise if it was already released.
+    MutexAutoLock lock(mPromiseProxy->Lock());
+    if (mPromiseProxy->CleanedUp()) {
       return NS_OK;
     }
 
     nsRefPtr<ServiceWorkerManager> swm = ServiceWorkerManager::GetInstance();
     nsTArray<ServiceWorkerClientInfo> result;
 
-    swm->GetAllClients(mWorkerPrivate->GetPrincipal(), mScope, result);
+    swm->GetAllClients(mPromiseProxy->GetWorkerPrivate()->GetPrincipal(), mScope, result);
     nsRefPtr<ResolvePromiseWorkerRunnable> r =
-      new ResolvePromiseWorkerRunnable(mWorkerPrivate, mPromiseProxy, result);
-
-    AutoSafeJSContext cx;
-    if (r->Dispatch(cx)) {
-      return NS_OK;
-    }
+      new ResolvePromiseWorkerRunnable(mPromiseProxy->GetWorkerPrivate(),
+                                       mPromiseProxy, result);
 
-    // Dispatch to worker thread failed because the worker is shutting down.
-    // Use a control runnable to release the runnable on the worker thread.
-    nsRefPtr<PromiseWorkerProxyControlRunnable> releaseRunnable =
-      new PromiseWorkerProxyControlRunnable(mWorkerPrivate, mPromiseProxy);
-
-    if (!releaseRunnable->Dispatch(cx)) {
-      NS_RUNTIMEABORT("Failed to dispatch MatchAll promise control runnable.");
-    }
-
+    AutoJSAPI jsapi;
+    jsapi.Init();
+    r->Dispatch(jsapi.cx());
     return NS_OK;
   }
 };
 
 class ResolveClaimRunnable final : public WorkerRunnable
 {
   nsRefPtr<PromiseWorkerProxy> mPromiseProxy;
   nsresult mResult;
@@ -153,55 +136,52 @@ public:
   }
 
   bool
   WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate) override
   {
     MOZ_ASSERT(aWorkerPrivate);
     aWorkerPrivate->AssertIsOnWorkerThread();
 
-    Promise* promise = mPromiseProxy->GetWorkerPromise();
+    nsRefPtr<Promise> promise = mPromiseProxy->WorkerPromise();
     MOZ_ASSERT(promise);
 
     if (NS_SUCCEEDED(mResult)) {
       promise->MaybeResolve(JS::UndefinedHandleValue);
     } else {
       promise->MaybeReject(NS_ERROR_DOM_INVALID_STATE_ERR);
     }
 
-    // Release the reference on the worker thread.
     mPromiseProxy->CleanUp(aCx);
-
     return true;
   }
 };
 
 class ClaimRunnable final : public nsRunnable
 {
   nsRefPtr<PromiseWorkerProxy> mPromiseProxy;
   nsCString mScope;
-  // We grab the ID so we don't have to hold a lock the entire time the claim
-  // operation is happening on the main thread.
   uint64_t mServiceWorkerID;
 
 public:
   ClaimRunnable(PromiseWorkerProxy* aPromiseProxy, const nsCString& aScope)
     : mPromiseProxy(aPromiseProxy)
     , mScope(aScope)
+    // Safe to call GetWorkerPrivate() since we are being called on the worker
+    // thread via script (so no clean up has occured yet).
     , mServiceWorkerID(aPromiseProxy->GetWorkerPrivate()->ServiceWorkerID())
   {
     MOZ_ASSERT(aPromiseProxy);
   }
 
   NS_IMETHOD
   Run() override
   {
-    MutexAutoLock lock(mPromiseProxy->GetCleanUpLock());
-    if (mPromiseProxy->IsClean()) {
-      // Don't resolve the promise if it was already released.
+    MutexAutoLock lock(mPromiseProxy->Lock());
+    if (mPromiseProxy->CleanedUp()) {
       return NS_OK;
     }
 
     WorkerPrivate* workerPrivate = mPromiseProxy->GetWorkerPrivate();
     MOZ_ASSERT(workerPrivate);
 
     nsRefPtr<ServiceWorkerManager> swm = ServiceWorkerManager::GetInstance();
     MOZ_ASSERT(swm);
@@ -209,30 +189,17 @@ public:
     nsresult rv = swm->ClaimClients(workerPrivate->GetPrincipal(),
                                     mScope, mServiceWorkerID);
 
     nsRefPtr<ResolveClaimRunnable> r =
       new ResolveClaimRunnable(workerPrivate, mPromiseProxy, rv);
 
     AutoJSAPI jsapi;
     jsapi.Init();
-    JSContext* cx = jsapi.cx();
-    if (r->Dispatch(cx)) {
-      return NS_OK;
-    }
-
-    // Dispatch to worker thread failed because the worker is shutting down.
-    // Use a control runnable to release the runnable on the worker thread.
-    nsRefPtr<PromiseWorkerProxyControlRunnable> releaseRunnable =
-      new PromiseWorkerProxyControlRunnable(workerPrivate, mPromiseProxy);
-
-    if (!releaseRunnable->Dispatch(cx)) {
-      NS_RUNTIMEABORT("Failed to dispatch Claim control runnable.");
-    }
-
+    r->Dispatch(jsapi.cx());
     return NS_OK;
   }
 };
 
 } // namespace
 
 already_AddRefed<Promise>
 ServiceWorkerClients::MatchAll(const ClientQueryOptions& aOptions,
@@ -252,31 +219,25 @@ ServiceWorkerClients::MatchAll(const Cli
 
   nsRefPtr<Promise> promise = Promise::Create(mWorkerScope, aRv);
   if (NS_WARN_IF(aRv.Failed())) {
     return nullptr;
   }
 
   nsRefPtr<PromiseWorkerProxy> promiseProxy =
     PromiseWorkerProxy::Create(workerPrivate, promise);
-  if (!promiseProxy->GetWorkerPromise()) {
-    // Don't dispatch if adding the worker feature failed.
+  if (!promiseProxy) {
+    promise->MaybeReject(NS_ERROR_DOM_ABORT_ERR);
     return promise.forget();
   }
 
   nsRefPtr<MatchAllRunnable> r =
-    new MatchAllRunnable(workerPrivate,
-                         promiseProxy,
+    new MatchAllRunnable(promiseProxy,
                          NS_ConvertUTF16toUTF8(scope));
-  nsresult rv = NS_DispatchToMainThread(r);
-
-  if (NS_WARN_IF(NS_FAILED(rv))) {
-    promise->MaybeReject(NS_ERROR_NOT_AVAILABLE);
-  }
-
+  MOZ_ALWAYS_TRUE(NS_SUCCEEDED(NS_DispatchToMainThread(r)));
   return promise.forget();
 }
 
 already_AddRefed<Promise>
 ServiceWorkerClients::OpenWindow(const nsAString& aUrl)
 {
   ErrorResult result;
   nsRefPtr<Promise> promise = Promise::Create(mWorkerScope, result);
@@ -296,26 +257,22 @@ ServiceWorkerClients::Claim(ErrorResult&
 
   nsRefPtr<Promise> promise = Promise::Create(mWorkerScope, aRv);
   if (NS_WARN_IF(aRv.Failed())) {
     return nullptr;
   }
 
   nsRefPtr<PromiseWorkerProxy> promiseProxy =
     PromiseWorkerProxy::Create(workerPrivate, promise);
-  if (!promiseProxy->GetWorkerPromise()) {
-    // Don't dispatch if adding the worker feature failed.
+  if (!promiseProxy) {
+    promise->MaybeReject(NS_ERROR_DOM_ABORT_ERR);
     return promise.forget();
   }
 
   nsString scope;
   mWorkerScope->GetScope(scope);
 
   nsRefPtr<ClaimRunnable> runnable =
     new ClaimRunnable(promiseProxy, NS_ConvertUTF16toUTF8(scope));
 
-  aRv = NS_DispatchToMainThread(runnable);
-  if (NS_WARN_IF(aRv.Failed())) {
-    promise->MaybeReject(NS_ERROR_DOM_ABORT_ERR);
-  }
-
+  MOZ_ALWAYS_TRUE(NS_SUCCEEDED(NS_DispatchToMainThread(runnable)));
   return promise.forget();
 }
--- a/dom/workers/ServiceWorkerRegistration.cpp
+++ b/dom/workers/ServiceWorkerRegistration.cpp
@@ -296,34 +296,33 @@ public:
     : WorkerRunnable(aPromiseProxy->GetWorkerPrivate(), WorkerThreadModifyBusyCount)
     , mPromiseProxy(aPromiseProxy)
     , mStatus(aStatus)
   { }
 
   bool
   WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate) override
   {
-    Promise* promise = mPromiseProxy->GetWorkerPromise();
+    Promise* promise = mPromiseProxy->WorkerPromise();
     if (NS_SUCCEEDED(mStatus)) {
       promise->MaybeResolve(JS::UndefinedHandleValue);
     } else {
       promise->MaybeReject(mStatus);
     }
     mPromiseProxy->CleanUp(aCx);
     return true;
   }
 };
 
 class WorkerThreadUpdateCallback final : public ServiceWorkerUpdateFinishCallback
 {
   nsRefPtr<PromiseWorkerProxy> mPromiseProxy;
 
   ~WorkerThreadUpdateCallback()
   {
-    Finish(NS_ERROR_FAILURE);
   }
 
 public:
   explicit WorkerThreadUpdateCallback(PromiseWorkerProxy* aPromiseProxy)
     : mPromiseProxy(aPromiseProxy)
   {
     AssertIsOnMainThread();
   }
@@ -346,31 +345,27 @@ public:
   Finish(nsresult aStatus)
   {
     if (!mPromiseProxy) {
       return;
     }
 
     nsRefPtr<PromiseWorkerProxy> proxy = mPromiseProxy.forget();
 
-    MutexAutoLock lock(proxy->GetCleanUpLock());
-    if (proxy->IsClean()) {
+    MutexAutoLock lock(proxy->Lock());
+    if (proxy->CleanedUp()) {
       return;
     }
 
     AutoJSAPI jsapi;
     jsapi.Init();
 
     nsRefPtr<UpdateResultRunnable> r =
       new UpdateResultRunnable(proxy, aStatus);
-    if (!r->Dispatch(jsapi.cx())) {
-      nsRefPtr<PromiseWorkerProxyControlRunnable> r =
-        new PromiseWorkerProxyControlRunnable(proxy->GetWorkerPrivate(), proxy);
-      r->Dispatch(jsapi.cx());
-    }
+    r->Dispatch(jsapi.cx());
   }
 };
 
 class UpdateRunnable final : public nsRunnable
 {
 public:
   UpdateRunnable(PromiseWorkerProxy* aPromiseProxy,
                  const nsAString& aScope)
@@ -379,18 +374,18 @@ public:
   {}
 
   NS_IMETHOD
   Run() override
   {
     AssertIsOnMainThread();
     ErrorResult result;
 
-    MutexAutoLock lock(mPromiseProxy->GetCleanUpLock());
-    if (mPromiseProxy->IsClean()) {
+    MutexAutoLock lock(mPromiseProxy->Lock());
+    if (mPromiseProxy->CleanedUp()) {
       return NS_OK;
     }
 
     nsRefPtr<WorkerThreadUpdateCallback> cb =
       new WorkerThreadUpdateCallback(mPromiseProxy);
     UpdateInternal(mPromiseProxy->GetWorkerPrivate()->GetPrincipal(), mScope, cb);
     return NS_OK;
   }
@@ -440,32 +435,30 @@ private:
 
 NS_IMPL_ISUPPORTS(UnregisterCallback, nsIServiceWorkerUnregisterCallback)
 
 class FulfillUnregisterPromiseRunnable final : public WorkerRunnable
 {
   nsRefPtr<PromiseWorkerProxy> mPromiseWorkerProxy;
   Maybe<bool> mState;
 public:
-  FulfillUnregisterPromiseRunnable(WorkerPrivate* aWorkerPrivate,
-                                   PromiseWorkerProxy* aProxy,
+  FulfillUnregisterPromiseRunnable(PromiseWorkerProxy* aProxy,
                                    Maybe<bool> aState)
-    : WorkerRunnable(aWorkerPrivate, WorkerThreadModifyBusyCount)
+    : WorkerRunnable(aProxy->GetWorkerPrivate(), WorkerThreadModifyBusyCount)
     , mPromiseWorkerProxy(aProxy)
     , mState(aState)
   {
     AssertIsOnMainThread();
     MOZ_ASSERT(mPromiseWorkerProxy);
   }
 
   bool
   WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate) override
   {
-    Promise* promise = mPromiseWorkerProxy->GetWorkerPromise();
-    MOZ_ASSERT(promise);
+    nsRefPtr<Promise> promise = mPromiseWorkerProxy->WorkerPromise();
     if (mState.isSome()) {
       promise->MaybeResolve(mState.value());
     } else {
       promise->MaybeReject(NS_ERROR_DOM_SECURITY_ERR);
     }
 
     mPromiseWorkerProxy->CleanUp(aCx);
     return true;
@@ -476,16 +469,17 @@ class WorkerUnregisterCallback final : p
 {
   nsRefPtr<PromiseWorkerProxy> mPromiseWorkerProxy;
 public:
   NS_DECL_ISUPPORTS
 
   explicit WorkerUnregisterCallback(PromiseWorkerProxy* aProxy)
     : mPromiseWorkerProxy(aProxy)
   {
+    MOZ_ASSERT(aProxy);
   }
 
   NS_IMETHODIMP
   UnregisterSucceeded(bool aState) override
   {
     AssertIsOnMainThread();
     Finish(Some(aState));
     return NS_OK;
@@ -496,92 +490,86 @@ public:
   {
     AssertIsOnMainThread();
     Finish(Nothing());
     return NS_OK;
   }
 
 private:
   ~WorkerUnregisterCallback()
-  { }
+  {}
 
   void
   Finish(Maybe<bool> aState)
   {
     AssertIsOnMainThread();
     if (!mPromiseWorkerProxy) {
       return;
     }
 
-    MutexAutoLock lock(mPromiseWorkerProxy->GetCleanUpLock());
-    if (mPromiseWorkerProxy->IsClean()) {
+    nsRefPtr<PromiseWorkerProxy> proxy = mPromiseWorkerProxy.forget();
+    MutexAutoLock lock(proxy->Lock());
+    if (proxy->CleanedUp()) {
       return;
     }
 
     nsRefPtr<WorkerRunnable> r =
-      new FulfillUnregisterPromiseRunnable(mPromiseWorkerProxy->GetWorkerPrivate(),
-                                           mPromiseWorkerProxy, aState);
+      new FulfillUnregisterPromiseRunnable(proxy, aState);
 
     AutoJSAPI jsapi;
     jsapi.Init();
-    if (!r->Dispatch(jsapi.cx())) {
-      nsRefPtr<WorkerControlRunnable> cr =
-        new PromiseWorkerProxyControlRunnable(
-          mPromiseWorkerProxy->GetWorkerPrivate(),
-          mPromiseWorkerProxy);
-      cr->Dispatch(jsapi.cx());
-    }
+    r->Dispatch(jsapi.cx());
   }
 };
 
 NS_IMPL_ISUPPORTS(WorkerUnregisterCallback, nsIServiceWorkerUnregisterCallback);
 
 /*
  * If the worker goes away, we still continue to unregister, but we don't try to
  * resolve the worker Promise (which doesn't exist by that point).
  */
 class StartUnregisterRunnable final : public nsRunnable
 {
   nsRefPtr<PromiseWorkerProxy> mPromiseWorkerProxy;
   const nsString mScope;
 
 public:
-  StartUnregisterRunnable(WorkerPrivate* aWorker, Promise* aPromise,
+  StartUnregisterRunnable(PromiseWorkerProxy* aProxy,
                           const nsAString& aScope)
-    : mPromiseWorkerProxy(PromiseWorkerProxy::Create(aWorker, aPromise))
+    : mPromiseWorkerProxy(aProxy)
     , mScope(aScope)
   {
-    // mPromiseWorkerProxy may be null if AddFeature failed.
+    MOZ_ASSERT(aProxy);
   }
 
   NS_IMETHOD
   Run() override
   {
     AssertIsOnMainThread();
 
-    nsRefPtr<WorkerUnregisterCallback> cb = new WorkerUnregisterCallback(mPromiseWorkerProxy);
-
     // XXXnsm: There is a rare chance of this failing if the worker gets
     // destroyed. In that case, unregister() called from a SW is no longer
     // guaranteed to run. We should fix this by having a main thread proxy
     // maintain a strongref to ServiceWorkerRegistrationInfo and use its
     // principal. Can that be trusted?
     nsCOMPtr<nsIPrincipal> principal;
     {
-      MutexAutoLock lock(mPromiseWorkerProxy->GetCleanUpLock());
-      if (mPromiseWorkerProxy->IsClean()) {
+      MutexAutoLock lock(mPromiseWorkerProxy->Lock());
+      if (mPromiseWorkerProxy->CleanedUp()) {
         return NS_OK;
       }
 
       WorkerPrivate* worker = mPromiseWorkerProxy->GetWorkerPrivate();
       MOZ_ASSERT(worker);
       principal = worker->GetPrincipal();
     }
     MOZ_ASSERT(principal);
 
+    nsRefPtr<WorkerUnregisterCallback> cb =
+      new WorkerUnregisterCallback(mPromiseWorkerProxy);
     nsCOMPtr<nsIServiceWorkerManager> swm =
       mozilla::services::GetServiceWorkerManager();
     nsresult rv = swm->Unregister(principal, cb, mScope);
     if (NS_WARN_IF(NS_FAILED(rv))) {
       cb->UnregisterFailed();
     }
 
     return NS_OK;
@@ -957,18 +945,18 @@ ServiceWorkerRegistrationWorkerThread::U
 
   nsRefPtr<Promise> promise = Promise::Create(worker->GlobalScope(), aRv);
   if (aRv.Failed()) {
     return nullptr;
   }
 
   nsRefPtr<PromiseWorkerProxy> proxy = PromiseWorkerProxy::Create(worker, promise);
   if (!proxy) {
-    promise->MaybeResolve(NS_ERROR_DOM_ABORT_ERR);
-    return promise.forget();
+    aRv.Throw(NS_ERROR_DOM_ABORT_ERR);
+    return nullptr;
   }
 
   nsRefPtr<UpdateRunnable> r = new UpdateRunnable(proxy, mScope);
   MOZ_ALWAYS_TRUE(NS_SUCCEEDED(NS_DispatchToMainThread(r)));
 
   return promise.forget();
 }
 
@@ -987,17 +975,23 @@ ServiceWorkerRegistrationWorkerThread::U
     return nullptr;
   }
 
   nsRefPtr<Promise> promise = Promise::Create(worker->GlobalScope(), aRv);
   if (aRv.Failed()) {
     return nullptr;
   }
 
-  nsRefPtr<StartUnregisterRunnable> r = new StartUnregisterRunnable(worker, promise, mScope);
+  nsRefPtr<PromiseWorkerProxy> proxy = PromiseWorkerProxy::Create(worker, promise);
+  if (!proxy) {
+    aRv.Throw(NS_ERROR_DOM_ABORT_ERR);
+    return nullptr;
+  }
+
+  nsRefPtr<StartUnregisterRunnable> r = new StartUnregisterRunnable(proxy, mScope);
   MOZ_ALWAYS_TRUE(NS_SUCCEEDED(NS_DispatchToMainThread(r)));
 
   return promise.forget();
 }
 
 class StartListeningRunnable final : public nsRunnable
 {
   nsRefPtr<WorkerListener> mListener;
--- a/dom/workers/ServiceWorkerWindowClient.cpp
+++ b/dom/workers/ServiceWorkerWindowClient.cpp
@@ -46,17 +46,17 @@ public:
   }
 
   bool
   WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate) override
   {
     MOZ_ASSERT(aWorkerPrivate);
     aWorkerPrivate->AssertIsOnWorkerThread();
 
-    Promise* promise = mPromiseProxy->GetWorkerPromise();
+    nsRefPtr<Promise> promise = mPromiseProxy->WorkerPromise();
     MOZ_ASSERT(promise);
 
     if (mClientInfo) {
       nsRefPtr<ServiceWorkerWindowClient> client =
         new ServiceWorkerWindowClient(promise->GetParentObject(), *mClientInfo);
       promise->MaybeResolve(client);
     } else {
       promise->MaybeReject(NS_ERROR_DOM_INVALID_ACCESS_ERR);
@@ -75,17 +75,16 @@ class ClientFocusRunnable final : public
   nsRefPtr<PromiseWorkerProxy> mPromiseProxy;
 
 public:
   ClientFocusRunnable(uint64_t aWindowId, PromiseWorkerProxy* aPromiseProxy)
     : mWindowId(aWindowId)
     , mPromiseProxy(aPromiseProxy)
   {
     MOZ_ASSERT(mPromiseProxy);
-    MOZ_ASSERT(mPromiseProxy->GetWorkerPromise());
   }
 
   NS_IMETHOD
   Run() override
   {
     AssertIsOnMainThread();
     nsGlobalWindow* window = nsGlobalWindow::GetInnerWindowWithId(mWindowId);
     UniquePtr<ServiceWorkerClientInfo> clientInfo;
@@ -105,38 +104,28 @@ public:
     return NS_OK;
   }
 
 private:
   void
   DispatchResult(UniquePtr<ServiceWorkerClientInfo>&& aClientInfo)
   {
     AssertIsOnMainThread();
-    MutexAutoLock lock(mPromiseProxy->GetCleanUpLock());
-    if (mPromiseProxy->IsClean()) {
+    MutexAutoLock lock(mPromiseProxy->Lock());
+    if (mPromiseProxy->CleanedUp()) {
       return;
     }
 
-    WorkerPrivate* workerPrivate = mPromiseProxy->GetWorkerPrivate();
-    MOZ_ASSERT(workerPrivate);
-
     nsRefPtr<ResolveOrRejectPromiseRunnable> resolveRunnable =
-      new ResolveOrRejectPromiseRunnable(workerPrivate, mPromiseProxy,
-                                         Move(aClientInfo));
+      new ResolveOrRejectPromiseRunnable(mPromiseProxy->GetWorkerPrivate(),
+                                         mPromiseProxy, Move(aClientInfo));
 
     AutoJSAPI jsapi;
     jsapi.Init();
-    JSContext* cx = jsapi.cx();
-    if (!resolveRunnable->Dispatch(cx)) {
-      nsRefPtr<PromiseWorkerProxyControlRunnable> controlRunnable =
-        new PromiseWorkerProxyControlRunnable(workerPrivate, mPromiseProxy);
-      if (!controlRunnable->Dispatch(cx)) {
-        NS_RUNTIMEABORT("Failed to dispatch Focus promise control runnable.");
-      }
-    }
+    resolveRunnable->Dispatch(jsapi.cx());
   }
 };
 
 } // namespace
 
 already_AddRefed<Promise>
 ServiceWorkerWindowClient::Focus(ErrorResult& aRv) const
 {
@@ -150,25 +139,22 @@ ServiceWorkerWindowClient::Focus(ErrorRe
   nsRefPtr<Promise> promise = Promise::Create(global, aRv);
   if (NS_WARN_IF(aRv.Failed())) {
     return nullptr;
   }
 
   if (workerPrivate->GlobalScope()->WindowInteractionAllowed()) {
     nsRefPtr<PromiseWorkerProxy> promiseProxy =
       PromiseWorkerProxy::Create(workerPrivate, promise);
-    if (!promiseProxy->GetWorkerPromise()) {
-      // Don't dispatch if adding the worker feature failed.
-      return promise.forget();
+    if (promiseProxy) {
+      nsRefPtr<ClientFocusRunnable> r = new ClientFocusRunnable(mWindowId,
+                                                                promiseProxy);
+      MOZ_ALWAYS_TRUE(NS_SUCCEEDED(NS_DispatchToMainThread(r)));
+    } else {
+      promise->MaybeReject(NS_ERROR_DOM_ABORT_ERR);
     }
 
-    nsRefPtr<ClientFocusRunnable> r = new ClientFocusRunnable(mWindowId,
-                                                              promiseProxy);
-    aRv = NS_DispatchToMainThread(r);
-    if (NS_WARN_IF(aRv.Failed())) {
-      promise->MaybeReject(aRv);
-    }
   } else {
     promise->MaybeReject(NS_ERROR_DOM_INVALID_STATE_ERR);
   }
 
   return promise.forget();
 }
--- a/dom/workers/WorkerScope.cpp
+++ b/dom/workers/WorkerScope.cpp
@@ -541,19 +541,17 @@ public:
   }
 
   virtual bool
   WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate) override
   {
     MOZ_ASSERT(aWorkerPrivate);
     aWorkerPrivate->AssertIsOnWorkerThread();
 
-    Promise* promise = mPromiseProxy->GetWorkerPromise();
-    MOZ_ASSERT(promise);
-
+    nsRefPtr<Promise> promise = mPromiseProxy->WorkerPromise();
     promise->MaybeResolve(JS::UndefinedHandleValue);
 
     // Release the reference on the worker thread.
     mPromiseProxy->CleanUp(aCx);
 
     return true;
   }
 };
@@ -574,45 +572,31 @@ public:
 
   NS_IMETHODIMP
   Run() override
   {
     AssertIsOnMainThread();
     nsRefPtr<ServiceWorkerManager> swm = ServiceWorkerManager::GetInstance();
     MOZ_ASSERT(swm);
 
-    MutexAutoLock lock(mPromiseProxy->GetCleanUpLock());
-    if (mPromiseProxy->IsClean()) {
+    MutexAutoLock lock(mPromiseProxy->Lock());
+    if (mPromiseProxy->CleanedUp()) {
       return NS_OK;
     }
+
     WorkerPrivate* workerPrivate = mPromiseProxy->GetWorkerPrivate();
-    MOZ_ASSERT(workerPrivate);
-
     swm->SetSkipWaitingFlag(workerPrivate->GetPrincipal(), mScope,
                             workerPrivate->ServiceWorkerID());
 
     nsRefPtr<SkipWaitingResultRunnable> runnable =
       new SkipWaitingResultRunnable(workerPrivate, mPromiseProxy);
 
     AutoJSAPI jsapi;
     jsapi.Init();
-    JSContext* cx = jsapi.cx();
-    if (runnable->Dispatch(cx)) {
-      return NS_OK;
-    }
-
-    // Dispatch to worker thread failed because the worker is shutting down.
-    // Use a control runnable to release the runnable on the worker thread.
-    nsRefPtr<PromiseWorkerProxyControlRunnable> releaseRunnable =
-      new PromiseWorkerProxyControlRunnable(workerPrivate, mPromiseProxy);
-
-    if (!releaseRunnable->Dispatch(cx)) {
-      NS_RUNTIMEABORT("Failed to dispatch Claim control runnable.");
-    }
-
+    runnable->Dispatch(jsapi.cx());
     return NS_OK;
   }
 };
 
 } // namespace
 
 already_AddRefed<Promise>
 ServiceWorkerGlobalScope::SkipWaiting(ErrorResult& aRv)
@@ -622,31 +606,26 @@ ServiceWorkerGlobalScope::SkipWaiting(Er
 
   nsRefPtr<Promise> promise = Promise::Create(this, aRv);
   if (NS_WARN_IF(aRv.Failed())) {
     return nullptr;
   }
 
   nsRefPtr<PromiseWorkerProxy> promiseProxy =
     PromiseWorkerProxy::Create(mWorkerPrivate, promise);
-  if (!promiseProxy->GetWorkerPromise()) {
-    // Don't dispatch if adding the worker feature failed.
+  if (!promiseProxy) {
     promise->MaybeResolve(JS::UndefinedHandleValue);
     return promise.forget();
   }
 
   nsRefPtr<WorkerScopeSkipWaitingRunnable> runnable =
     new WorkerScopeSkipWaitingRunnable(promiseProxy,
                                        NS_ConvertUTF16toUTF8(mScope));
 
-  aRv = NS_DispatchToMainThread(runnable);
-  if (NS_WARN_IF(aRv.Failed())) {
-    promise->MaybeReject(NS_ERROR_DOM_ABORT_ERR);
-  }
-
+  MOZ_ALWAYS_TRUE(NS_SUCCEEDED(NS_DispatchToMainThread(runnable)));
   return promise.forget();
 }
 
 // static
 bool
 ServiceWorkerGlobalScope::InterceptionEnabled(JSContext* aCx, JSObject* aObj)
 {
   WorkerPrivate* worker = GetCurrentThreadWorkerPrivate();
--- a/dom/xul/XULDocument.cpp
+++ b/dom/xul/XULDocument.cpp
@@ -885,17 +885,17 @@ XULDocument::ExecuteOnBroadcastHandlerFo
 
         if (!aAttr->Equals(listeningToAttribute) &&
             !listeningToAttribute.EqualsLiteral("*")) {
             continue;
         }
 
         // This is the right <observes> element. Execute the
         // |onbroadcast| event handler
-        WidgetEvent event(true, NS_XUL_BROADCAST);
+        WidgetEvent event(true, eXULBroadcast);
 
         nsCOMPtr<nsIPresShell> shell = GetShell();
         if (shell) {
             nsRefPtr<nsPresContext> aPresContext = shell->GetPresContext();
 
             // Handle the DOM event
             nsEventStatus status = nsEventStatus_eIgnore;
             EventDispatcher::Dispatch(child, aPresContext, &event, nullptr,
--- a/dom/xul/nsXULCommandDispatcher.cpp
+++ b/dom/xul/nsXULCommandDispatcher.cpp
@@ -392,17 +392,17 @@ nsXULCommandDispatcher::UpdateCommands(c
       CopyUTF16toUTF8(aEventName, aeventnameC);
       MOZ_LOG(gCommandLog, LogLevel::Debug,
              ("xulcmd[%p] update %p event=%s",
               this, content,
               aeventnameC.get()));
     }
 #endif
 
-    WidgetEvent event(true, NS_XUL_COMMAND_UPDATE);
+    WidgetEvent event(true, eXULCommandUpdate);
     EventDispatcher::Dispatch(content, nullptr, &event);
   }
   return NS_OK;
 }
 
 bool
 nsXULCommandDispatcher::Matches(const nsString& aList, 
                                 const nsAString& aElement)
--- a/dom/xul/nsXULElement.cpp
+++ b/dom/xul/nsXULElement.cpp
@@ -1262,26 +1262,26 @@ nsXULElement::List(FILE* out, int32_t aI
 nsresult
 nsXULElement::PreHandleEvent(EventChainPreVisitor& aVisitor)
 {
     aVisitor.mForceContentDispatch = true; //FIXME! Bug 329119
     if (IsRootOfNativeAnonymousSubtree() &&
         (IsAnyOfXULElements(nsGkAtoms::scrollbar, nsGkAtoms::scrollcorner)) &&
         (aVisitor.mEvent->mMessage == eMouseClick ||
          aVisitor.mEvent->mMessage == eMouseDoubleClick ||
-         aVisitor.mEvent->mMessage == NS_XUL_COMMAND ||
+         aVisitor.mEvent->mMessage == eXULCommand ||
          aVisitor.mEvent->mMessage == eContextMenu ||
          aVisitor.mEvent->mMessage == eDragStart ||
          aVisitor.mEvent->mMessage == eLegacyDragGesture)) {
         // Don't propagate these events from native anonymous scrollbar.
         aVisitor.mCanHandle = true;
         aVisitor.mParentTarget = nullptr;
         return NS_OK;
     }
-    if (aVisitor.mEvent->mMessage == NS_XUL_COMMAND &&
+    if (aVisitor.mEvent->mMessage == eXULCommand &&
         aVisitor.mEvent->mClass == eInputEventClass &&
         aVisitor.mEvent->originalTarget == static_cast<nsIContent*>(this) &&
         !IsXULElement(nsGkAtoms::command)) {
         // Check that we really have an xul command event. That will be handled
         // in a special way.
         nsCOMPtr<nsIDOMXULCommandEvent> xulEvent =
             do_QueryInterface(aVisitor.mDOMEvent);
         // See if we have a command elt.  If so, we execute on the command
--- a/gfx/layers/apz/src/APZCTreeManager.cpp
+++ b/gfx/layers/apz/src/APZCTreeManager.cpp
@@ -1543,46 +1543,38 @@ APZCTreeManager::GetAPZCAtPoint(HitTesti
   return nullptr;
 }
 
 AsyncPanZoomController*
 APZCTreeManager::FindRootApzcForLayersId(uint64_t aLayersId) const
 {
   mTreeLock.AssertCurrentThreadOwns();
 
-  struct RootForLayersIdMatcher {
-    uint64_t mLayersId;
-    bool operator()(const HitTestingTreeNode* aNode) const {
-      AsyncPanZoomController* apzc = aNode->GetApzc();
-      return apzc
-          && apzc->GetLayersId() == mLayersId
-          && apzc->IsRootForLayersId();
-    }
-  };
   const HitTestingTreeNode* resultNode = BreadthFirstSearch(mRootNode.get(),
-      RootForLayersIdMatcher{aLayersId});
+      [aLayersId](const HitTestingTreeNode* aNode) {
+        AsyncPanZoomController* apzc = aNode->GetApzc();
+        return apzc
+            && apzc->GetLayersId() == aLayersId
+            && apzc->IsRootForLayersId();
+      });
   return resultNode ? resultNode->GetApzc() : nullptr;
 }
 
 AsyncPanZoomController*
 APZCTreeManager::FindRootContentApzcForLayersId(uint64_t aLayersId) const
 {
   mTreeLock.AssertCurrentThreadOwns();
 
-  struct RootContentForLayersIdMatcher {
-    uint64_t mLayersId;
-    bool operator()(const HitTestingTreeNode* aNode) const {
-      AsyncPanZoomController* apzc = aNode->GetApzc();
-      return apzc
-          && apzc->GetLayersId() == mLayersId
-          && apzc->IsRootContent();
-    }
-  };
   const HitTestingTreeNode* resultNode = BreadthFirstSearch(mRootNode.get(),
-      RootContentForLayersIdMatcher{aLayersId});
+      [aLayersId](const HitTestingTreeNode* aNode) {
+        AsyncPanZoomController* apzc = aNode->GetApzc();
+        return apzc
+            && apzc->GetLayersId() == aLayersId
+            && apzc->IsRootContent();
+      });
   return resultNode ? resultNode->GetApzc() : nullptr;
 }
 
 /* The methods GetScreenToApzcTransform() and GetApzcToGeckoTransform() return
    some useful transformations that input events may need applied. This is best
    illustrated with an example. Consider a chain of layers, L, M, N, O, P, Q, R. Layer L
    is the layer that corresponds to the argument |aApzc|, and layer R is the root
    of the layer tree. Layer M is the parent of L, N is the parent of M, and so on.
--- a/gfx/thebes/gfxHarfBuzzShaper.cpp
+++ b/gfx/thebes/gfxHarfBuzzShaper.cpp
@@ -1019,23 +1019,16 @@ HBGetScript(hb_unicode_funcs_t *ufuncs, 
 
 static hb_unicode_combining_class_t
 HBGetCombiningClass(hb_unicode_funcs_t *ufuncs, hb_codepoint_t aCh,
                     void *user_data)
 {
     return hb_unicode_combining_class_t(GetCombiningClass(aCh));
 }
 
-static unsigned int
-HBGetEastAsianWidth(hb_unicode_funcs_t *ufuncs, hb_codepoint_t aCh,
-                    void *user_data)
-{
-    return GetEastAsianWidth(aCh);
-}
-
 // Hebrew presentation forms with dagesh, for characters 0x05D0..0x05EA;
 // note that some letters do not have a dagesh presForm encoded
 static const char16_t sDageshForms[0x05EA - 0x05D0 + 1] = {
     0xFB30, // ALEF
     0xFB31, // BET
     0xFB32, // GIMEL
     0xFB33, // DALET
     0xFB34, // HE
@@ -1299,19 +1292,16 @@ gfxHarfBuzzShaper::Initialize()
         hb_unicode_funcs_set_script_func(sHBUnicodeFuncs, HBGetScript,
                                          nullptr, nullptr);
         hb_unicode_funcs_set_general_category_func(sHBUnicodeFuncs,
                                                    HBGetGeneralCategory,
                                                    nullptr, nullptr);
         hb_unicode_funcs_set_combining_class_func(sHBUnicodeFuncs,
                                                   HBGetCombiningClass,
                                                   nullptr, nullptr);
-        hb_unicode_funcs_set_eastasian_width_func(sHBUnicodeFuncs,
-                                                  HBGetEastAsianWidth,
-                                                  nullptr, nullptr);
         hb_unicode_funcs_set_compose_func(sHBUnicodeFuncs,
                                           HBUnicodeCompose,
                                           nullptr, nullptr);
         hb_unicode_funcs_set_decompose_func(sHBUnicodeFuncs,
                                             HBUnicodeDecompose,
                                             nullptr, nullptr);
 
 #if MOZ_HB_SHAPER_USE_ICU_NORMALIZATION
--- a/image/imgTools.cpp
+++ b/image/imgTools.cpp
@@ -20,20 +20,20 @@
 #include "nsStreamUtils.h"
 #include "nsContentUtils.h"
 #include "ImageFactory.h"
 #include "Image.h"
 #include "ScriptedNotificationObserver.h"
 #include "imgIScriptedNotificationObserver.h"
 #include "gfxPlatform.h"
 
-using namespace mozilla;
-using namespace mozilla::image;
 using namespace mozilla::gfx;
 
+namespace mozilla {
+namespace image {
 /* ========== imgITools implementation ========== */
 
 
 
 NS_IMPL_ISUPPORTS(imgTools, imgITools)
 
 imgTools::imgTools()
 {
@@ -344,8 +344,11 @@ imgTools::GetImgLoaderForDocument(nsIDOM
 NS_IMETHODIMP
 imgTools::GetImgCacheForDocument(nsIDOMDocument* aDoc, imgICache** aCache)
 {
   nsCOMPtr<imgILoader> loader;
   nsresult rv = GetImgLoaderForDocument(aDoc, getter_AddRefs(loader));
   NS_ENSURE_SUCCESS(rv, rv);
   return CallQueryInterface(loader, aCache);
 }
+
+} // namespace image
+} // namespace mozilla
--- a/image/imgTools.h
+++ b/image/imgTools.h
@@ -12,20 +12,27 @@
 #define NS_IMGTOOLS_CID \
 { /* 3d8fa16d-c9e1-4b50-bdef-2c7ae249967a */         \
      0x3d8fa16d,                                     \
      0xc9e1,                                         \
      0x4b50,                                         \
     {0xbd, 0xef, 0x2c, 0x7a, 0xe2, 0x49, 0x96, 0x7a} \
 }
 
+namespace mozilla {
+namespace image {
+
 class imgTools final : public imgITools
 {
 public:
   NS_DECL_ISUPPORTS
   NS_DECL_IMGITOOLS
 
   imgTools();
 
 private:
   virtual ~imgTools();
 };
+
+} // namespace image
+} // namespace mozilla
+
 #endif // mozilla_image_imgITools_h
--- a/intl/icu-patches/bug-1172609-timezone-recreateDefault.diff
+++ b/intl/icu-patches/bug-1172609-timezone-recreateDefault.diff
@@ -58,25 +58,26 @@ diff --git a/intl/icu/source/i18n/timezo
          ucln_i18n_registerCleanup(UCLN_I18N_TIMEZONE, timeZone_cleanup);
      }
  }
  // -------------------------------------
  
 diff --git a/intl/icu/source/i18n/unicode/timezone.h b/intl/icu/source/i18n/unicode/timezone.h
 --- a/intl/icu/source/i18n/unicode/timezone.h
 +++ b/intl/icu/source/i18n/unicode/timezone.h
-@@ -299,16 +299,18 @@ public:
+@@ -299,16 +299,19 @@ public:
       * and made the default.
       *
       * @return   A default TimeZone. Clients are responsible for deleting the time zone
       *           object returned.
       * @stable ICU 2.0
       */
      static TimeZone* U_EXPORT2 createDefault(void);
  
++#define ICU_TZ_HAS_RECREATE_DEFAULT
 +    static void U_EXPORT2 recreateDefault();
 +
      /**
       * Sets the default time zone (i.e., what's returned by createDefault()) to be the
       * specified time zone.  If NULL is specified for the time zone, the default time
       * zone is set to the default host time zone.  This call adopts the TimeZone object
       * passed in; the client is no longer responsible for deleting it.
       *
--- a/intl/icu/source/i18n/unicode/timezone.h
+++ b/intl/icu/source/i18n/unicode/timezone.h
@@ -299,16 +299,17 @@ public:
      * and made the default.
      *
      * @return   A default TimeZone. Clients are responsible for deleting the time zone
      *           object returned.
      * @stable ICU 2.0
      */
     static TimeZone* U_EXPORT2 createDefault(void);
 
+#define ICU_TZ_HAS_RECREATE_DEFAULT
     static void U_EXPORT2 recreateDefault();
 
     /**
      * Sets the default time zone (i.e., what's returned by createDefault()) to be the
      * specified time zone.  If NULL is specified for the time zone, the default time
      * zone is set to the default host time zone.  This call adopts the TimeZone object
      * passed in; the client is no longer responsible for deleting it.
      *
--- a/js/src/asmjs/AsmJSLink.cpp
+++ b/js/src/asmjs/AsmJSLink.cpp
@@ -949,59 +949,31 @@ SendFunctionsToPerf(JSContext* cx, AsmJS
             return false;
 
         writePerfSpewerAsmJSFunctionMap(start, size, filename, func.pod.lineno,
                                         func.pod.columnIndex, name);
     }
 
     return true;
 }
-
-static bool
-SendBlocksToPerf(JSContext* cx, AsmJSModule& module)
-{
-    if (!PerfBlockEnabled())
-        return true;
-
-    unsigned long funcBaseAddress = (unsigned long) module.codeBase();
-    const char* filename = module.scriptSource()->filename();
-
-    for (unsigned i = 0; i < module.numPerfBlocksFunctions(); i++) {
-        const AsmJSModule::ProfiledBlocksFunction& func = module.perfProfiledBlocksFunction(i);
-
-        size_t size = func.pod.endCodeOffset - func.pod.startCodeOffset;
-
-        JSAutoByteString bytes;
-        const char* name = AtomToPrintableString(cx, func.name, &bytes);
-        if (!name)
-            return false;
-
-        writePerfSpewerAsmJSBlocksMap(funcBaseAddress, func.pod.startCodeOffset,
-                                      func.endInlineCodeOffset, size, filename, name, func.blocks);
-    }
-
-    return true;
-}
 #endif
 
 static bool
 SendModuleToAttachedProfiler(JSContext* cx, AsmJSModule& module)
 {
 #if defined(MOZ_VTUNE)
     if (IsVTuneProfilingActive() && !SendFunctionsToVTune(cx, module))
         return false;
 #endif
 
 #if defined(JS_ION_PERF)
     if (module.numExportedFunctions() > 0) {
         size_t firstEntryCode = size_t(module.codeBase() + module.functionBytes());
         writePerfSpewerAsmJSEntriesAndExits(firstEntryCode, module.codeBytes() - module.functionBytes());
     }
-    if (!SendBlocksToPerf(cx, module))
-        return false;
     if (!SendFunctionsToPerf(cx, module))
         return false;
 #endif
 
     return true;
 }
 
 
--- a/js/src/asmjs/AsmJSModule.cpp
+++ b/js/src/asmjs/AsmJSModule.cpp
@@ -147,20 +147,16 @@ AsmJSModule::trace(JSTracer* trc)
     for (unsigned i = 0; i < exports_.length(); i++)
         exports_[i].trace(trc);
     for (unsigned i = 0; i < names_.length(); i++)
         TraceManuallyBarrieredEdge(trc, &names_[i].name(), "asm.js module function name");
 #if defined(MOZ_VTUNE) || defined(JS_ION_PERF)
     for (unsigned i = 0; i < profiledFunctions_.length(); i++)
         profiledFunctions_[i].trace(trc);
 #endif
-#if defined(JS_ION_PERF)
-    for (unsigned i = 0; i < perfProfiledBlocksFunctions_.length(); i++)
-        perfProfiledBlocksFunctions_[i].trace(trc);
-#endif
     if (globalArgumentName_)
         TraceManuallyBarrieredEdge(trc, &globalArgumentName_, "asm.js global argument name");
     if (importArgumentName_)
         TraceManuallyBarrieredEdge(trc, &importArgumentName_, "asm.js import argument name");
     if (bufferArgumentName_)
         TraceManuallyBarrieredEdge(trc, &bufferArgumentName_, "asm.js buffer argument name");
     if (maybeHeap_)
         TraceEdge(trc, &maybeHeap_, "asm.js heap");
@@ -180,19 +176,16 @@ AsmJSModule::addSizeOfMisc(mozilla::Mall
                         funcPtrTables_.sizeOfExcludingThis(mallocSizeOf) +
                         builtinThunkOffsets_.sizeOfExcludingThis(mallocSizeOf) +
                         names_.sizeOfExcludingThis(mallocSizeOf) +
                         heapAccesses_.sizeOfExcludingThis(mallocSizeOf) +
                         functionCounts_.sizeOfExcludingThis(mallocSizeOf) +
 #if defined(MOZ_VTUNE) || defined(JS_ION_PERF)
                         profiledFunctions_.sizeOfExcludingThis(mallocSizeOf) +
 #endif
-#if defined(JS_ION_PERF)
-                        perfProfiledBlocksFunctions_.sizeOfExcludingThis(mallocSizeOf) +
-#endif
                         staticLinkData_.sizeOfExcludingThis(mallocSizeOf);
 }
 
 struct CallSiteRetAddrOffset
 {
     const CallSiteVector& callSites;
     explicit CallSiteRetAddrOffset(const CallSiteVector& callSites) : callSites(callSites) {}
     uint32_t operator[](size_t index) const {
@@ -430,30 +423,16 @@ AsmJSModule::finish(ExclusiveContext* cx
 #if defined(MOZ_VTUNE) || defined(JS_ION_PERF)
     // Fix up the code offsets.
     for (size_t i = 0; i < profiledFunctions_.length(); i++) {
         ProfiledFunction& pf = profiledFunctions_[i];
         pf.pod.startCodeOffset = masm.actualOffset(pf.pod.startCodeOffset);
         pf.pod.endCodeOffset = masm.actualOffset(pf.pod.endCodeOffset);
     }
 #endif
-#ifdef JS_ION_PERF
-    for (size_t i = 0; i < perfProfiledBlocksFunctions_.length(); i++) {
-        ProfiledBlocksFunction& pbf = perfProfiledBlocksFunctions_[i];
-        pbf.pod.startCodeOffset = masm.actualOffset(pbf.pod.startCodeOffset);
-        pbf.endInlineCodeOffset = masm.actualOffset(pbf.endInlineCodeOffset);
-        pbf.pod.endCodeOffset = masm.actualOffset(pbf.pod.endCodeOffset);
-        BasicBlocksVector& basicBlocks = pbf.blocks;
-        for (uint32_t i = 0; i < basicBlocks.length(); i++) {
-            Record& r = basicBlocks[i];
-            r.startOffset = masm.actualOffset(r.startOffset);
-            r.endOffset = masm.actualOffset(r.endOffset);
-        }
-    }
-#endif
 
     return true;
 }
 
 void
 AsmJSModule::setAutoFlushICacheRange()
 {
     MOZ_ASSERT(isFinished());
--- a/js/src/asmjs/AsmJSModule.h
+++ b/js/src/asmjs/AsmJSModule.h
@@ -857,19 +857,16 @@ class AsmJSModule
     Vector<uint32_t,               0, SystemAllocPolicy> builtinThunkOffsets_;
     Vector<Name,                   0, SystemAllocPolicy> names_;
     Vector<ProfilingLabel,         0, SystemAllocPolicy> profilingLabels_;
     Vector<jit::AsmJSHeapAccess,   0, SystemAllocPolicy> heapAccesses_;
     Vector<jit::IonScriptCounts*,  0, SystemAllocPolicy> functionCounts_;
 #if defined(MOZ_VTUNE) || defined(JS_ION_PERF)
     Vector<ProfiledFunction,       0, SystemAllocPolicy> profiledFunctions_;
 #endif
-#if defined(JS_ION_PERF)
-    Vector<ProfiledBlocksFunction, 0, SystemAllocPolicy> perfProfiledBlocksFunctions_;
-#endif
 
     ScriptSource *                        scriptSource_;
     PropertyName *                        globalArgumentName_;
     PropertyName *                        importArgumentName_;
     PropertyName *                        bufferArgumentName_;
     uint8_t *                             code_;
     uint8_t *                             interruptExit_;
     uint8_t *                             outOfBoundsExit_;
@@ -1220,31 +1217,16 @@ class AsmJSModule
         MOZ_ASSERT(isFinishedWithModulePrologue());
         return profiledFunctions_.length();
     }
     ProfiledFunction& profiledFunction(unsigned i) {
         MOZ_ASSERT(isFinishedWithModulePrologue());
         return profiledFunctions_[i];
     }
 #endif
-#ifdef JS_ION_PERF
-    bool addProfiledBlocks(ProfiledBlocksFunction&& func)
-    {
-        MOZ_ASSERT(isFinishedWithModulePrologue() && !isFinishedWithFunctionBodies());
-        return perfProfiledBlocksFunctions_.append(mozilla::Move(func));
-    }
-    unsigned numPerfBlocksFunctions() const {
-        MOZ_ASSERT(isFinishedWithModulePrologue());
-        return perfProfiledBlocksFunctions_.length();
-    }
-    ProfiledBlocksFunction& perfProfiledBlocksFunction(unsigned i) {
-        MOZ_ASSERT(isFinishedWithModulePrologue());
-        return perfProfiledBlocksFunctions_[i];
-    }
-#endif
 
     /*************************************************************************/
 
     // This function is called after compiling the function bodies (before
     // compiling entries/exits) to record the extent of compiled function code.
     void finishFunctionBodies(size_t functionBytes) {
         MOZ_ASSERT(isFinishedWithModulePrologue() && !isFinishedWithFunctionBodies());
         pod.functionBytes_ = functionBytes;
--- a/js/src/asmjs/AsmJSValidate.cpp
+++ b/js/src/asmjs/AsmJSValidate.cpp
@@ -1089,57 +1089,65 @@ class AsmFunction
 
     VarTypeVector argTypes_;
     RetType returnedType_;
 
     PropertyName* name_;
 
     unsigned funcIndex_;
     unsigned srcBegin_;
+    unsigned lineno_;
+    unsigned column_;
     unsigned compileTime_;
 
-
   public:
-
     explicit AsmFunction(LifoAlloc& alloc)
       : varInitializers_(alloc),
         bytecode_(alloc),
         argTypes_(alloc),
         returnedType_(RetType::Which(-1)),
         name_(nullptr),
         funcIndex_(-1),
         srcBegin_(-1),
+        lineno_(-1),
+        column_(-1),
         compileTime_(-1)
     {}
 
     bool init(const VarTypeVector& args) {
         if (!argTypes_.initCapacity(args.length()))
             return false;
         for (size_t i = 0; i < args.length(); i++)
             argTypes_.append(args[i]);
         return true;
     }
 
     bool finish(const VarTypeVector& args, PropertyName* name, unsigned funcIndex,
-                unsigned srcBegin, unsigned compileTime)
+                unsigned srcBegin, unsigned lineno, unsigned column, unsigned compileTime)
     {
         if (!argTypes_.initCapacity(args.length()))
             return false;
         for (size_t i = 0; i < args.length(); i++)
             argTypes_.infallibleAppend(args[i]);
 
         MOZ_ASSERT(name_ == nullptr);
         name_ = name;
 
         MOZ_ASSERT(funcIndex_ == unsigned(-1));
         funcIndex_ = funcIndex;
 
         MOZ_ASSERT(srcBegin_ == unsigned(-1));
         srcBegin_ = srcBegin;
 
+        MOZ_ASSERT(lineno_ == unsigned(-1));
+        lineno_ = lineno;
+
+        MOZ_ASSERT(column_ == unsigned(-1));
+        column_ = column;
+
         MOZ_ASSERT(compileTime_ == unsigned(-1));
         compileTime_ = compileTime;
         return true;
     }
 
   private:
     AsmFunction(const AsmFunction&) = delete;
     AsmFunction(AsmFunction&& other) = delete;
@@ -1236,37 +1244,43 @@ class AsmFunction
         MOZ_ASSERT(returnedType_ == RetType::Which(-1));
         returnedType_ = retType;
     }
 
     // Read-only interface
     PropertyName* name() const { return name_; }
     unsigned funcIndex() const { return funcIndex_; }
     unsigned srcBegin() const { return srcBegin_; }
+    unsigned lineno() const { return lineno_; }
+    unsigned column() const { return column_; }
     unsigned compileTime() const { return compileTime_; }
 
     size_t size() const { return bytecode_.length(); }
 
     const VarTypeVector& argTypes() const { return argTypes_; }
 
     const VarInitializerVector& varInitializers() const { return varInitializers_; }
     size_t numLocals() const { return argTypes_.length() + varInitializers_.length(); }
     RetType returnedType() const {
         MOZ_ASSERT(returnedType_ != RetType::Which(-1));
         return returnedType_;
     }
 };
 
 struct ModuleCompileInputs
 {
-    bool usesSignalHandlersForInterrupt;
+    CompileCompartment* compartment;
+    CompileRuntime* runtime;
     bool usesSignalHandlersForOOB;
 
-    ModuleCompileInputs(bool usesSignalHandlersForInterrupt, bool usesSignalHandlersForOOB)
-      : usesSignalHandlersForInterrupt(usesSignalHandlersForInterrupt),
+    ModuleCompileInputs(CompileCompartment* compartment,
+                        CompileRuntime* runtime,
+                        bool usesSignalHandlersForOOB)
+      : compartment(compartment),
+        runtime(runtime),
         usesSignalHandlersForOOB(usesSignalHandlersForOOB)
     {}
 };
 
 class ModuleCompileResults
 {
   public:
     struct SlowFunction
@@ -1276,56 +1290,48 @@ class ModuleCompileResults
         {}
 
         PropertyName* name;
         unsigned ms;
         unsigned line;
         unsigned column;
     };
 
-    typedef Vector<SlowFunction> SlowFunctionVector;
-    typedef Vector<Label*> LabelVector;
+    typedef Vector<SlowFunction                  , 0, SystemAllocPolicy> SlowFunctionVector;
+    typedef Vector<Label*                        , 8, SystemAllocPolicy> LabelVector;
+    typedef Vector<AsmJSModule::FunctionCodeRange, 8, SystemAllocPolicy> FunctionCodeRangeVector;
+    typedef Vector<jit::IonScriptCounts*         , 0, SystemAllocPolicy> ScriptCountVector;
+#if defined(MOZ_VTUNE) || defined(JS_ION_PERF)
+    typedef Vector<AsmJSModule::ProfiledFunction , 0, SystemAllocPolicy> ProfiledFunctionVector;
+#endif // defined(MOZ_VTUNE) || defined(JS_ION_PERF)
 
   private:
     LifoAlloc           lifo_;
     MacroAssembler      masm_;
 
-    SlowFunctionVector slowFunctions_;
-    LabelVector functionEntries_;
-    Vector<AsmJSModule::FunctionCodeRange> codeRanges_;
-    Vector<jit::IonScriptCounts*> functionCounts_;
+    SlowFunctionVector      slowFunctions_;
+    LabelVector             functionEntries_;
+    FunctionCodeRangeVector codeRanges_;
+    ScriptCountVector       functionCounts_;
 #if defined(MOZ_VTUNE) || defined(JS_ION_PERF)
-    Vector<AsmJSModule::ProfiledFunction> profiledFunctions_;
+    ProfiledFunctionVector  profiledFunctions_;
 #endif // defined(MOZ_VTUNE) || defined(JS_ION_PERF)
-#if defined(JS_ION_PERF)
-    Vector<AsmJSModule::ProfiledBlocksFunction> perfProfiledBlocksFunctions_;
-#endif
 
     NonAssertingLabel   stackOverflowLabel_;
     NonAssertingLabel   asyncInterruptLabel_;
     NonAssertingLabel   syncInterruptLabel_;
     NonAssertingLabel   onDetachedLabel_;
     NonAssertingLabel   onConversionErrorLabel_;
     NonAssertingLabel   onOutOfBoundsLabel_;
     int64_t             usecBefore_;
 
   public:
-    explicit ModuleCompileResults(ExclusiveContext* cx)
+    ModuleCompileResults()
       : lifo_(LIFO_ALLOC_PRIMARY_CHUNK_SIZE),
         masm_(MacroAssembler::AsmJSToken()),
-        slowFunctions_(cx),
-        functionEntries_(cx),
-        codeRanges_(cx),
-        functionCounts_(cx),
-#if defined(MOZ_VTUNE) || defined(JS_ION_PERF)
-        profiledFunctions_(cx),
-#endif // defined(MOZ_VTUNE) || defined(JS_ION_PERF)
-#if defined(JS_ION_PERF)
-        perfProfiledBlocksFunctions_(cx),
-#endif // defined(JS_ION_PERF)
         usecBefore_(PRMJ_Now())
     {}
 
     MacroAssembler& masm()              { return masm_; }
     Label& stackOverflowLabel()         { return stackOverflowLabel_; }
     Label& asyncInterruptLabel()        { return asyncInterruptLabel_; }
     Label& syncInterruptLabel()         { return syncInterruptLabel_; }
     Label& onOutOfBoundsLabel()         { return onOutOfBoundsLabel_; }
@@ -1362,25 +1368,16 @@ class ModuleCompileResults
     size_t numProfiledFunctions() const { return profiledFunctions_.length(); }
     bool addProfiledFunction(AsmJSModule::ProfiledFunction func) {
         return profiledFunctions_.append(func);
     }
     AsmJSModule::ProfiledFunction& profiledFunction(unsigned i) {
         return profiledFunctions_[i];
     }
 #endif // defined(MOZ_VTUNE) || defined(JS_ION_PERF)
-#if defined(JS_ION_PERF)
-    size_t numProfiledBlocks() const { return perfProfiledBlocksFunctions_.length(); }
-    bool addProfiledBlocks(AsmJSModule::ProfiledBlocksFunction&& func) {
-        return perfProfiledBlocksFunctions_.append(Move(func));
-    }
-    AsmJSModule::ProfiledBlocksFunction& profiledBlocks(unsigned i) {
-        return perfProfiledBlocksFunctions_[i];
-    }
-#endif // defined(JS_ION_PERF)
 };
 
 // The ModuleValidator encapsulates the entire validation of an asm.js module.
 // Its lifetime goes from the validation of the top components of an asm.js
 // module (all the globals), the emission of bytecode for all the functions in
 // the module and the validation of function's pointer tables. It also finishes
 // the compilation of all the module's stubs.
 //
@@ -2309,22 +2306,16 @@ class MOZ_STACK_CLASS ModuleValidator
                 return false;
         }
 #if defined(MOZ_VTUNE) || defined(JS_ION_PERF)
         for (size_t i = 0; i < compileResults_->numProfiledFunctions(); ++i) {
             if (!module().addProfiledFunction(Move(compileResults_->profiledFunction(i))))
                 return false;
         }
 #endif // defined(MOZ_VTUNE) || defined(JS_ION_PERF)
-#if defined(JS_ION_PERF)
-        for (size_t i = 0; i < compileResults_->numProfiledBlocks(); ++i) {
-            if (!module().addProfiledBlocks(Move(compileResults_->profiledBlocks(i))))
-                return false;
-        }
-#endif // defined(JS_ION_PERF)
 
         // Hand in code ranges, script counts and perf profiling data to the AsmJSModule
         for (size_t i = 0; i < compileResults_->numCodeRanges(); ++i) {
             AsmJSModule::FunctionCodeRange& codeRange = compileResults_->codeRange(i);
             if (!module().addFunctionCodeRange(codeRange.name(), Move(codeRange)))
                 return false;
         }
 
@@ -2399,17 +2390,19 @@ class MOZ_STACK_CLASS ModuleValidator
             break;
         }
         out->reset(JS_smprintf("total compilation time %dms; %s%s",
                                msTotal, cacheString, slowFuns ? slowFuns.get() : ""));
 #endif
     }
 
     ModuleCompileInputs compileInputs() const {
-        return ModuleCompileInputs(module().usesSignalHandlersForInterrupt(),
+        CompileCompartment* compartment = CompileCompartment::get(cx()->compartment());
+        return ModuleCompileInputs(compartment,
+                                   compartment->runtime(),
                                    module().usesSignalHandlersForOOB());
     }
 };
 
 // ModuleCompiler encapsulates the compilation of an entire asm.js module. Over
 // the course of an ModuleCompiler object's lifetime, many FunctionCompiler
 // objects will be created and destroyed in sequence, one for each function in
 // the module.
@@ -2459,64 +2452,52 @@ class MOZ_STACK_CLASS ModuleValidator
 // to add a new exit or reuse an existing one. The key is an index into the
 // Vector<Exit> stored in the AsmJSModule and the value is the signature of
 // that exit's variant.
 //
 // The same rooting note at the top comment of ModuleValidator applies here as
 // well.
 class MOZ_STACK_CLASS ModuleCompiler
 {
-    ExclusiveContext*                       cx_;
-    TokenStream&                            tokenStream_;
-
     ModuleCompileInputs                     compileInputs_;
     ScopedJSDeletePtr<ModuleCompileResults> compileResults_;
 
   public:
-    ModuleCompiler(ExclusiveContext* cx, TokenStream& ts, const ModuleCompileInputs& inputs)
-      : cx_(cx),
-        tokenStream_(ts),
-        compileInputs_(inputs),
-        compileResults_(js_new<ModuleCompileResults>(cx))
+    explicit ModuleCompiler(const ModuleCompileInputs& inputs)
+      : compileInputs_(inputs),
+        compileResults_(js_new<ModuleCompileResults>())
     {}
 
     /*************************************************** Read-only interface */
 
-    ExclusiveContext* cx() const     { return cx_; }
-    TokenStream& tokenStream() const { return tokenStream_; }
-
     MacroAssembler& masm()          { return compileResults_->masm(); }
     Label& stackOverflowLabel()     { return compileResults_->stackOverflowLabel(); }
     Label& asyncInterruptLabel()    { return compileResults_->asyncInterruptLabel(); }
     Label& syncInterruptLabel()     { return compileResults_->syncInterruptLabel(); }
     Label& onOutOfBoundsLabel()     { return compileResults_->onOutOfBoundsLabel(); }
     Label& onConversionErrorLabel() { return compileResults_->onConversionErrorLabel(); }
     int64_t usecBefore()            { return compileResults_->usecBefore(); }
 
-    bool usesSignalHandlersForInterrupt() const {
-        return compileInputs_.usesSignalHandlersForInterrupt;
-    }
-    bool usesSignalHandlersForOOB() const {
-        return compileInputs_.usesSignalHandlersForOOB;
-    }
+    bool usesSignalHandlersForOOB() const   { return compileInputs_.usesSignalHandlersForOOB; }
+    CompileRuntime* runtime() const         { return compileInputs_.runtime; }
+    CompileCompartment* compartment() const { return compileInputs_.compartment; }
 
     /***************************************************** Mutable interface */
 
     bool getOrCreateFunctionEntry(uint32_t funcIndex, Label** label)
     {
         return compileResults_->getOrCreateFunctionEntry(funcIndex, label);
     }
 
     bool finishGeneratingFunction(AsmFunction& func, CodeGenerator& codegen,
                                   const AsmJSFunctionLabels& labels)
     {
         // Code range
-        uint32_t line, column;
-        tokenStream().srcCoords.lineNumAndColumnIndex(func.srcBegin(), &line, &column);
-
+        unsigned line = func.lineno();
+        unsigned column = func.column();
         PropertyName* funcName = func.name();
         if (!compileResults_->addCodeRange(AsmJSModule::FunctionCodeRange(funcName, line, labels)))
             return false;
 
         // Script counts
         jit::IonScriptCounts* counts = codegen.extractScriptCounts();
         if (counts && !compileResults_->addFunctionCounts(counts)) {
             js_delete(counts);
@@ -2532,29 +2513,16 @@ class MOZ_STACK_CLASS ModuleCompiler
 
 #if defined(MOZ_VTUNE) || defined(JS_ION_PERF)
         // Perf and profiling information
         unsigned begin = labels.begin.offset();
         unsigned end = labels.end.offset();
         AsmJSModule::ProfiledFunction profiledFunc(funcName, begin, end, line, column);
         if (!compileResults_->addProfiledFunction(profiledFunc))
             return false;
-# ifdef JS_ION_PERF
-        // Per-block profiling info uses significantly more memory so only store
-        // this information if it is actively requested.
-        if (PerfBlockEnabled()) {
-            AsmJSPerfSpewer& ps = codegen.mirGen().perfSpewer();
-            ps.noteBlocksOffsets();
-            AsmJSModule::ProfiledBlocksFunction profiledBlocksFunction(
-                funcName, begin, ps.endInlineCode.offset(), end, ps.basicBlocks()
-            );
-            if (!compileResults_->addProfiledBlocks(Move(profiledBlocksFunction)))
-                return false;
-        }
-# endif // JS_ION_PERF
 #endif // defined(MOZ_VTUNE) || defined(JS_ION_PERF)
         return true;
     }
 
     void finish(ScopedJSDeletePtr<ModuleCompileResults>* results) {
         *results = compileResults_.forget();
     }
 };
@@ -2881,16 +2849,18 @@ enum class Stmt : uint8_t {
     I32Expr,
     F32Expr,
     F64Expr,
     I32X4Expr,
     F32X4Expr,
 
     Id,
     Noop,
+    InterruptCheckHead,
+    InterruptCheckLoop,
 
     DebugCheckPoint,
 
     Bad
 };
 
 enum class I32 : uint8_t {
     // Common opcodes
@@ -3202,17 +3172,17 @@ bool AsmFunction::pcIsPatchable(size_t p
         patchable &= Stmt(bytecode_[pc]) == Stmt::Bad;
     return patchable;
 }
 #endif // DEBUG
 
 // 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 FunctionBuilder
+class FunctionValidator
 {
   public:
     struct Local
     {
         VarType type;
         unsigned slot;
         Local(VarType t, unsigned slot) : type(t), slot(slot) {}
     };
@@ -3229,17 +3199,17 @@ class FunctionBuilder
     LocalMap               locals_;
     LabelMap               labels_;
 
     unsigned               heapExpressionDepth_;
 
     bool                   hasAlreadyReturned_;
 
   public:
-    FunctionBuilder(ModuleValidator& m, AsmFunction& func, ParseNode* fn)
+    FunctionValidator(ModuleValidator& m, AsmFunction& func, ParseNode* fn)
       : m_(m),
         fn_(fn),
         func_(func),
         locals_(m.cx()),
         labels_(m.cx()),
         heapExpressionDepth_(0),
         hasAlreadyReturned_(false)
     {}
@@ -3354,16 +3324,20 @@ class FunctionBuilder
             return nullptr;
         return m_.lookupGlobal(name);
     }
 
     size_t numLocals() const { return locals_.count(); }
 
     /************************************************* Packing interface */
 
+    bool startedPacking() const {
+        return func_.size() != 0;
+    }
+
     template<class T>
     size_t writeOp(T op) {
         static_assert(sizeof(T) == sizeof(uint8_t), "opcodes must be uint8");
         return func_.writeU8(uint8_t(op));
     }
 
     void writeDebugCheckPoint() {
 #ifdef DEBUG
@@ -3454,80 +3428,72 @@ class FunctionBuilder
 };
 
 static bool
 NoExceptionPending(ExclusiveContext* cx)
 {
     return !cx->isJSContext() || !cx->asJSContext()->isExceptionPending();
 }
 
-typedef Vector<size_t,1> LabelVector;
-typedef Vector<MBasicBlock*,8> BlockVector;
+typedef Vector<size_t, 1, SystemAllocPolicy> LabelVector;
+typedef Vector<MBasicBlock*, 8, SystemAllocPolicy> BlockVector;
 
 // Encapsulates the compilation of a single function in an asm.js module. The
 // function compiler handles the creation and final backend compilation of the
 // MIR graph. Also see ModuleCompiler comment.
 class FunctionCompiler
 {
   private:
-    typedef HashMap<uint32_t, BlockVector> LabeledBlockMap;
-    typedef HashMap<size_t, BlockVector> UnlabeledBlockMap;
-    typedef Vector<size_t, 4> PositionStack;
-    typedef Vector<Type, 4> LocalVarTypes;
-
-    ModuleCompiler &       m_;
-    LifoAlloc &            lifo_;
-    RetType                retType_;
-
-    const AsmFunction &    func_;
-    size_t                 pc_;
-
-    TempAllocator *        alloc_;
-    MIRGraph *             graph_;
-    CompileInfo *          info_;
-    MIRGenerator *         mirGen_;
-    Maybe<JitContext>      jitContext_;
-
-    MBasicBlock *          curBlock_;
-
-    PositionStack          loopStack_;
-    PositionStack          breakableStack_;
-    UnlabeledBlockMap      unlabeledBreaks_;
-    UnlabeledBlockMap      unlabeledContinues_;
-    LabeledBlockMap        labeledBreaks_;
-    LabeledBlockMap        labeledContinues_;
-
-    LocalVarTypes          localVarTypes_;
+    typedef HashMap<uint32_t, BlockVector, DefaultHasher<uint32_t>, SystemAllocPolicy> LabeledBlockMap;
+    typedef HashMap<size_t, BlockVector, DefaultHasher<uint32_t>, SystemAllocPolicy> UnlabeledBlockMap;
+    typedef Vector<size_t, 4, SystemAllocPolicy> PositionStack;
+    typedef Vector<Type, 4, SystemAllocPolicy> LocalVarTypes;
+
+    ModuleCompiler &         m_;
+    LifoAlloc &              lifo_;
+    RetType                  retType_;
+
+    const AsmFunction &      func_;
+    size_t                   pc_;
+
+    TempAllocator *          alloc_;
+    MIRGraph *               graph_;
+    CompileInfo *            info_;
+    MIRGenerator *           mirGen_;
+    Maybe<JitContext>        jitContext_;
+
+    MBasicBlock *            curBlock_;
+
+    PositionStack            loopStack_;
+    PositionStack            breakableStack_;
+    UnlabeledBlockMap        unlabeledBreaks_;
+    UnlabeledBlockMap        unlabeledContinues_;
+    LabeledBlockMap          labeledBreaks_;
+    LabeledBlockMap          labeledContinues_;
+
+    LocalVarTypes            localVarTypes_;
 
   public:
     FunctionCompiler(ModuleCompiler& m, const AsmFunction& func, LifoAlloc& lifo)
       : m_(m),
         lifo_(lifo),
         retType_(func.returnedType()),
         func_(func),
         pc_(0),
         alloc_(nullptr),
         graph_(nullptr),
         info_(nullptr),
         mirGen_(nullptr),
-        curBlock_(nullptr),
-        loopStack_(m.cx()),
-        breakableStack_(m.cx()),
-        unlabeledBreaks_(m.cx()),
-        unlabeledContinues_(m.cx()),
-        labeledBreaks_(m.cx()),
-        labeledContinues_(m.cx()),
-        localVarTypes_(m.cx())
+        curBlock_(nullptr)
     {}
 
     ModuleCompiler &        m() const            { return m_; }
     TempAllocator &         alloc() const        { return *alloc_; }
     LifoAlloc &             lifo() const         { return lifo_; }
     RetType                 returnedType() const { return retType_; }
-    ExclusiveContext *      cx() const           { return m_.cx(); }
 
     bool init()
     {
         return unlabeledBreaks_.init() &&
                unlabeledContinues_.init() &&
                labeledBreaks_.init() &&
                labeledContinues_.init();
     }
@@ -3942,27 +3908,21 @@ class FunctionCompiler
 
     void storeGlobalVar(uint32_t globalDataOffset, MDefinition* v)
     {
         if (inDeadCode())
             return;
         curBlock_->add(MAsmJSStoreGlobalVar::New(alloc(), globalDataOffset, v));
     }
 
-    void maybeAddInterruptCheck(size_t pos)
+    void addInterruptCheck(unsigned lineno, unsigned column)
     {
         if (inDeadCode())
             return;
 
-        if (m().usesSignalHandlersForInterrupt())
-            return;
-
-        unsigned lineno = 0, column = 0;
-        unsigned offset = sourceOffsetFromBytecodePosition(pos);
-        m().tokenStream().srcCoords.lineNumAndColumnIndex(offset, &lineno, &column);
         CallSiteDesc callDesc(lineno, column, CallSiteDesc::Relative);
         curBlock_->add(MAsmJSInterruptCheck::New(alloc(), &m().syncInterruptLabel(), callDesc));
     }
 
     MDefinition* extractSimdElement(SimdLane lane, MDefinition* base, MIRType type)
     {
         if (inDeadCode())
             return nullptr;
@@ -4014,35 +3974,35 @@ class FunctionCompiler
     // between evaluating an argument and making the call, another argument
     // evaluation could perform a call that also needs to store to the stack.
     // When this occurs childClobbers_ = true and the parent expression's
     // arguments are stored above the maximum depth clobbered by a child
     // expression.
 
     class Call
     {
-        uint32_t nodePosition_;
+        uint32_t lineno_;
+        uint32_t column_;
         ABIArgGenerator abi_;
         uint32_t prevMaxStackBytes_;
         uint32_t maxChildStackBytes_;
         uint32_t spIncrement_;
         MAsmJSCall::Args regArgs_;
-        Vector<MAsmJSPassStackArg*> stackArgs_;
+        Vector<MAsmJSPassStackArg*, 0, SystemAllocPolicy> stackArgs_;
         bool childClobbers_;
 
         friend class FunctionCompiler;
 
       public:
-        Call(FunctionCompiler& f, uint32_t callNodePosition)
-          : nodePosition_(callNodePosition),
+        Call(FunctionCompiler& f, uint32_t lineno, uint32_t column)
+          : lineno_(lineno),
+            column_(column),
             prevMaxStackBytes_(0),
             maxChildStackBytes_(0),
             spIncrement_(0),
-            regArgs_(f.cx()),
-            stackArgs_(f.cx()),
             childClobbers_(false)
         { }
     };
 
     void startCallArgs(Call* call)
     {
         if (inDeadCode())
             return;
@@ -4096,28 +4056,25 @@ class FunctionCompiler
   private:
     bool callPrivate(MAsmJSCall::Callee callee, const Call& call, MIRType returnType, MDefinition** def)
     {
         if (inDeadCode()) {
             *def = nullptr;
             return true;
         }
 
-        uint32_t line, column;
-        m_.tokenStream().srcCoords.lineNumAndColumnIndex(call.nodePosition_, &line, &column);
-
         CallSiteDesc::Kind kind = CallSiteDesc::Kind(-1);  // initialize to silence GCC warning
         switch (callee.which()) {
           case MAsmJSCall::Callee::Internal: kind = CallSiteDesc::Relative; break;
           case MAsmJSCall::Callee::Dynamic:  kind = CallSiteDesc::Register; break;
           case MAsmJSCall::Callee::Builtin:  kind = CallSiteDesc::Register; break;
         }
 
-        MAsmJSCall* ins = MAsmJSCall::New(alloc(), CallSiteDesc(line, column, kind), callee,
-                                          call.regArgs_, returnType, call.spIncrement_);
+        MAsmJSCall* ins = MAsmJSCall::New(alloc(), CallSiteDesc(call.lineno_, call.column_, kind),
+                                          callee, call.regArgs_, returnType, call.spIncrement_);
         if (!ins)
             return false;
 
         curBlock_->add(ins);
         *def = ins;
         return true;
     }
 
@@ -4184,28 +4141,27 @@ class FunctionCompiler
     {
         if (inDeadCode())
             return;
         MAsmJSVoidReturn* ins = MAsmJSVoidReturn::New(alloc());
         curBlock_->end(ins);
         curBlock_ = nullptr;
     }
 
-    bool branchAndStartThen(MDefinition* cond, size_t thenPos, size_t elsePos,
-                            MBasicBlock** thenBlock, MBasicBlock** elseBlock)
+    bool branchAndStartThen(MDefinition* cond, MBasicBlock** thenBlock, MBasicBlock** elseBlock)
     {
         if (inDeadCode())
             return true;
 
         bool hasThenBlock = *thenBlock != nullptr;
         bool hasElseBlock = *elseBlock != nullptr;
 
-        if (!hasThenBlock && !newBlock(curBlock_, thenPos, thenBlock))
-            return false;
-        if (!hasElseBlock && !newBlock(curBlock_, elsePos, elseBlock))
+        if (!hasThenBlock && !newBlock(curBlock_, thenBlock))
+            return false;
+        if (!hasElseBlock && !newBlock(curBlock_, elseBlock))
             return false;
 
         curBlock_->end(MTest::New(alloc(), cond, *thenBlock, *elseBlock));
 
         // Only add as a predecessor if newBlock hasn't been called (as it does it for us)
         if (hasThenBlock && !(*thenBlock)->addPredecessor(alloc(), curBlock_))
             return false;
         if (hasElseBlock && !(*elseBlock)->addPredecessor(alloc(), curBlock_))
@@ -4247,23 +4203,23 @@ class FunctionCompiler
     void switchToElse(MBasicBlock* elseBlock)
     {
         if (!elseBlock)
             return;
         curBlock_ = elseBlock;
         mirGraph().moveBlockToEnd(curBlock_);
     }
 
-    bool joinIfElse(const BlockVector& thenBlocks, size_t joinPos)
+    bool joinIfElse(const BlockVector& thenBlocks)
     {
         if (inDeadCode() && thenBlocks.empty())
             return true;
         MBasicBlock* pred = curBlock_ ? curBlock_ : thenBlocks[0];
         MBasicBlock* join;
-        if (!newBlock(pred, joinPos, &join))
+        if (!newBlock(pred, &join))
             return false;
         if (curBlock_)
             curBlock_->end(MGoto::New(alloc(), join));
         for (size_t i = 0; i < thenBlocks.length(); i++) {
             thenBlocks[i]->end(MGoto::New(alloc(), join));
             if (pred == curBlock_ || i > 0) {
                 if (!join->addPredecessor(alloc(), thenBlocks[i]))
                     return false;
@@ -4298,40 +4254,37 @@ class FunctionCompiler
             return true;
         }
         MOZ_ASSERT(curBlock_->loopDepth() == loopStack_.length() - 1);
         *loopEntry = MBasicBlock::NewAsmJS(mirGraph(), info(), curBlock_,
                                            MBasicBlock::PENDING_LOOP_HEADER);
         if (!*loopEntry)
             return false;
         mirGraph().addBlock(*loopEntry);
-        noteBasicBlockPosition(*loopEntry, pos);
         (*loopEntry)->setLoopDepth(loopStack_.length());
         curBlock_->end(MGoto::New(alloc(), *loopEntry));
         curBlock_ = *loopEntry;
-        maybeAddInterruptCheck(pos);
-        return true;
-    }
-
-    bool branchAndStartLoopBody(MDefinition* cond, size_t bodyPos, size_t afterPos,
-                                MBasicBlock** afterLoop)
+        return true;
+    }
+
+    bool branchAndStartLoopBody(MDefinition* cond, MBasicBlock** afterLoop)
     {
         if (inDeadCode()) {
             *afterLoop = nullptr;
             return true;
         }
         MOZ_ASSERT(curBlock_->loopDepth() > 0);
         MBasicBlock* body;
-        if (!newBlock(curBlock_, bodyPos, &body))
+        if (!newBlock(curBlock_, &body))
             return false;
         if (cond->isConstant() && cond->toConstant()->valueToBoolean()) {
             *afterLoop = nullptr;
             curBlock_->end(MGoto::New(alloc(), body));
         } else {
-            if (!newBlockWithDepth(curBlock_, curBlock_->loopDepth() - 1, afterPos, afterLoop))
+            if (!newBlockWithDepth(curBlock_, curBlock_->loopDepth() - 1, afterLoop))
                 return false;
             curBlock_->end(MTest::New(alloc(), cond, body, *afterLoop));
         }
         curBlock_ = body;
         return true;
     }
 
   private:
@@ -4362,17 +4315,17 @@ class FunctionCompiler
                 return false;
         }
         curBlock_ = afterLoop;
         if (curBlock_)
             mirGraph().moveBlockToEnd(curBlock_);
         return bindUnlabeledBreaks(pos);
     }
 
-    bool branchAndCloseDoWhileLoop(MDefinition* cond, MBasicBlock* loopEntry, size_t afterPos)
+    bool branchAndCloseDoWhileLoop(MDefinition* cond, MBasicBlock* loopEntry)
     {
         size_t pos = popLoop();
         if (!loopEntry) {
             MOZ_ASSERT(inDeadCode());
             MOZ_ASSERT(!unlabeledBreaks_.has(pos));
             return true;
         }
         MOZ_ASSERT(loopEntry->loopDepth() == loopStack_.length() + 1);
@@ -4381,49 +4334,49 @@ class FunctionCompiler
             if (cond->isConstant()) {
                 if (cond->toConstant()->valueToBoolean()) {
                     curBlock_->end(MGoto::New(alloc(), loopEntry));
                     if (!loopEntry->setBackedgeAsmJS(curBlock_))
                         return false;
                     curBlock_ = nullptr;
                 } else {
                     MBasicBlock* afterLoop;
-                    if (!newBlock(curBlock_, afterPos, &afterLoop))
+                    if (!newBlock(curBlock_, &afterLoop))
                         return false;
                     curBlock_->end(MGoto::New(alloc(), afterLoop));
                     curBlock_ = afterLoop;
                 }
             } else {
                 MBasicBlock* afterLoop;
-                if (!newBlock(curBlock_, afterPos, &afterLoop))
+                if (!newBlock(curBlock_, &afterLoop))
                     return false;
                 curBlock_->end(MTest::New(alloc(), cond, loopEntry, afterLoop));
                 if (!loopEntry->setBackedgeAsmJS(curBlock_))
                     return false;
                 curBlock_ = afterLoop;
             }
         }
         return bindUnlabeledBreaks(pos);
     }
 
     bool bindContinues(size_t pos, const LabelVector* maybeLabels)
     {
         bool createdJoinBlock = false;
         if (UnlabeledBlockMap::Ptr p = unlabeledContinues_.lookup(pos)) {
-            if (!bindBreaksOrContinues(&p->value(), pos, &createdJoinBlock))
+            if (!bindBreaksOrContinues(&p->value(), &createdJoinBlock))
                 return false;
             unlabeledContinues_.remove(p);
         }
-        return bindLabeledBreaksOrContinues(maybeLabels, &labeledContinues_, &createdJoinBlock, pos);
-    }
-
-    bool bindLabeledBreaks(size_t pos, const LabelVector* maybeLabels)
+        return bindLabeledBreaksOrContinues(maybeLabels, &labeledContinues_, &createdJoinBlock);
+    }
+
+    bool bindLabeledBreaks(const LabelVector* maybeLabels)
     {
         bool createdJoinBlock = false;
-        return bindLabeledBreaksOrContinues(maybeLabels, &labeledBreaks_, &createdJoinBlock, pos);
+        return bindLabeledBreaksOrContinues(maybeLabels, &labeledBreaks_, &createdJoinBlock);
     }
 
     bool addBreak(uint32_t* maybeLabelId) {
         if (maybeLabelId)
             return addBreakOrContinue(*maybeLabelId, &labeledBreaks_);
         return addBreakOrContinue(breakableStack_.back(), &unlabeledBreaks_);
     }
 
@@ -4443,61 +4396,59 @@ class FunctionCompiler
             return true;
         }
         curBlock_->end(MTableSwitch::New(alloc(), expr, low, high));
         *switchBlock = curBlock_;
         curBlock_ = nullptr;
         return true;
     }
 
-    bool startSwitchCase(MBasicBlock* switchBlock, size_t pos, MBasicBlock** next)
+    bool startSwitchCase(MBasicBlock* switchBlock, MBasicBlock** next)
     {
         if (!switchBlock) {
             *next = nullptr;
             return true;
         }
-        if (!newBlock(switchBlock, pos, next))
+        if (!newBlock(switchBlock, next))
             return false;
         if (curBlock_) {
             curBlock_->end(MGoto::New(alloc(), *next));
             if (!(*next)->addPredecessor(alloc(), curBlock_))
                 return false;
         }
         curBlock_ = *next;
         return true;
     }
 
-    bool startSwitchDefault(MBasicBlock* switchBlock, BlockVector* cases, size_t defaultPos,
-                            MBasicBlock** defaultBlock)
-    {
-        if (!startSwitchCase(switchBlock, defaultPos, defaultBlock))
+    bool startSwitchDefault(MBasicBlock* switchBlock, BlockVector* cases, MBasicBlock** defaultBlock)
+    {
+        if (!startSwitchCase(switchBlock, defaultBlock))
             return false;
         if (!*defaultBlock)
             return true;
         mirGraph().moveBlockToEnd(*defaultBlock);
         return true;
     }
 
-    bool joinSwitch(MBasicBlock* switchBlock, const BlockVector& cases, MBasicBlock* defaultBlock,
-                    size_t nextPos)
+    bool joinSwitch(MBasicBlock* switchBlock, const BlockVector& cases, MBasicBlock* defaultBlock)
     {
         size_t pos = breakableStack_.popCopy();
         if (!switchBlock)
             return true;
         MTableSwitch* mir = switchBlock->lastIns()->toTableSwitch();
         size_t defaultIndex = mir->addDefault(defaultBlock);
         for (unsigned i = 0; i < cases.length(); i++) {
             if (!cases[i])
                 mir->addCase(defaultIndex);
             else
                 mir->addCase(mir->addSuccessor(cases[i]));
         }
         if (curBlock_) {
             MBasicBlock* next;
-            if (!newBlock(curBlock_, nextPos, &next))
+            if (!newBlock(curBlock_, &next))
                 return false;
             curBlock_->end(MGoto::New(alloc(), next));
             curBlock_ = next;
         }
         return bindUnlabeledBreaks(pos);
     }
 
     /************************************************************ DECODING ***/
@@ -4526,37 +4477,36 @@ class FunctionCompiler
     {
         const AsmFunction::VarInitializerVector& varInitializers = func_.varInitializers();
         size_t numLocals = func_.numLocals();
 
         // Prepare data structures
         alloc_  = lifo_.new_<TempAllocator>(&lifo_);
         if (!alloc_)
             return false;
-        jitContext_.emplace(m_.cx(), alloc_);
-
-        MOZ_ASSERT(numLocals == argTypes.length() + varInitializers.length());
+        jitContext_.emplace(m().runtime(), /* CompileCompartment = */ nullptr, alloc_);
         graph_  = lifo_.new_<MIRGraph>(alloc_);
         if (!graph_)
             return false;
+        MOZ_ASSERT(numLocals == argTypes.length() + varInitializers.length());
         info_   = lifo_.new_<CompileInfo>(numLocals);
         if (!info_)
             return false;
         const OptimizationInfo* optimizationInfo = js_IonOptimizations.get(Optimization_AsmJS);
         const JitCompileOptions options;
-        mirGen_ = lifo_.new_<MIRGenerator>(CompileCompartment::get(cx()->compartment()),
+        mirGen_ = lifo_.new_<MIRGenerator>(m().compartment(),
                                            options, alloc_,
                                            graph_, info_, optimizationInfo,
                                            &m().onOutOfBoundsLabel(),
                                            &m().onConversionErrorLabel(),
                                            m().usesSignalHandlersForOOB());
         if (!mirGen_)
             return false;
 
-        if (!newBlock(/* pred = */ nullptr, 0, &curBlock_))
+        if (!newBlock(/* pred = */ nullptr, &curBlock_))
             return false;
 
         // Emit parameters and local variables
         for (ABIArgTypeIter i(argTypes); !i.done(); i++) {
             MAsmJSParameter* ins = MAsmJSParameter::New(alloc(), *i, i.mirType());
             curBlock_->add(ins);
             curBlock_->initSlot(info().localSlot(i.index()), ins);
             if (!mirGen_->ensureBallast())
@@ -4578,76 +4528,57 @@ class FunctionCompiler
 
             curBlock_->add(ins);
             curBlock_->initSlot(info().localSlot(firstLocalSlot + i), ins);
             if (!mirGen_->ensureBallast())
                 return false;
             localVarTypes_.append(type);
         }
 
-        maybeAddInterruptCheck(/* pos = */ 0);
         return true;
     }
 
     /*************************************************************************/
 
     MIRGenerator* extractMIR()
     {
         MOZ_ASSERT(mirGen_ != nullptr);
         MIRGenerator* mirGen = mirGen_;
         mirGen_ = nullptr;
         return mirGen;
     }
 
     /*************************************************************************/
   private:
-    unsigned sourceOffsetFromBytecodePosition(size_t pos)
-    {
-        // TODO (bug 1178840) : implement me!
-        return 0;
-    }
-
-    void noteBasicBlockPosition(MBasicBlock* blk, size_t pos)
-    {
-#if defined(JS_ION_PERF) || defined(DEBUG)
-        unsigned offset = sourceOffsetFromBytecodePosition(pos);
-        unsigned line = 0U, column = 0U;
-        m().tokenStream().srcCoords.lineNumAndColumnIndex(offset, &line, &column);
-        blk->setLineno(line);
-        blk->setColumnIndex(column);
-#endif
-    }
-
-    bool newBlockWithDepth(MBasicBlock* pred, unsigned loopDepth, size_t pos, MBasicBlock** block)
+    bool newBlockWithDepth(MBasicBlock* pred, unsigned loopDepth, MBasicBlock** block)
     {
         *block = MBasicBlock::NewAsmJS(mirGraph(), info(), pred, MBasicBlock::NORMAL);
         if (!*block)
             return false;
-        noteBasicBlockPosition(*block, pos);
         mirGraph().addBlock(*block);
         (*block)->setLoopDepth(loopDepth);
         return true;
     }
 
-    bool newBlock(MBasicBlock* pred, size_t pos, MBasicBlock** block)
-    {
-        return newBlockWithDepth(pred, loopStack_.length(), pos, block);
-    }
-
-    bool bindBreaksOrContinues(BlockVector* preds, size_t joinPos, bool* createdJoinBlock)
+    bool newBlock(MBasicBlock* pred, MBasicBlock** block)
+    {
+        return newBlockWithDepth(pred, loopStack_.length(), block);
+    }
+
+    bool bindBreaksOrContinues(BlockVector* preds, bool* createdJoinBlock)
     {
         for (unsigned i = 0; i < preds->length(); i++) {
             MBasicBlock* pred = (*preds)[i];
             if (*createdJoinBlock) {
                 pred->end(MGoto::New(alloc(), curBlock_));
                 if (!curBlock_->addPredecessor(alloc(), pred))
                     return false;
             } else {
                 MBasicBlock* next;
-                if (!newBlock(pred, joinPos, &next))
+                if (!newBlock(pred, &next))
                     return false;
                 pred->end(MGoto::New(alloc(), next));
                 if (curBlock_) {
                     curBlock_->end(MGoto::New(alloc(), next));
                     if (!next->addPredecessor(alloc(), curBlock_))
                         return false;
                 }
                 curBlock_ = next;
@@ -4657,55 +4588,55 @@ class FunctionCompiler
             if (!mirGen_->ensureBallast())
                 return false;
         }
         preds->clear();
         return true;
     }
 
     bool bindLabeledBreaksOrContinues(const LabelVector* maybeLabels, LabeledBlockMap* map,
-                                      bool* createdJoinBlock, size_t pos)
+                                      bool* createdJoinBlock)
     {
         if (!maybeLabels)
             return true;
         const LabelVector& labels = *maybeLabels;
         for (unsigned i = 0; i < labels.length(); i++) {
             if (LabeledBlockMap::Ptr p = map->lookup(labels[i])) {
-                if (!bindBreaksOrContinues(&p->value(), pos, createdJoinBlock))
+                if (!bindBreaksOrContinues(&p->value(), createdJoinBlock))
                     return false;
                 map->remove(p);
             }
             if (!mirGen_->ensureBallast())
                 return false;
         }
         return true;
     }
 
     template <class Key, class Map>
     bool addBreakOrContinue(Key key, Map* map)
     {
         if (inDeadCode())
             return true;
         typename Map::AddPtr p = map->lookupForAdd(key);
         if (!p) {
-            BlockVector empty(m().cx());
+            BlockVector empty;
             if (!map->add(p, key, Move(empty)))
                 return false;
         }
         if (!p->value().append(curBlock_))
             return false;
         curBlock_ = nullptr;
         return true;
     }
 
     bool bindUnlabeledBreaks(size_t pos)
     {
         bool createdJoinBlock = false;
         if (UnlabeledBlockMap::Ptr p = unlabeledBreaks_.lookup(pos)) {
-            if (!bindBreaksOrContinues(&p->value(), pos, &createdJoinBlock))
+            if (!bindBreaksOrContinues(&p->value(), &createdJoinBlock))
                 return false;
             unlabeledBreaks_.remove(p);
         }
         return true;
     }
 };
 
 } /* anonymous namespace */
@@ -5241,24 +5172,24 @@ CheckModuleGlobals(ModuleValidator& m)
                 return false;
         }
     }
 
     return true;
 }
 
 static bool
-ArgFail(FunctionBuilder& f, PropertyName* argName, ParseNode* stmt)
+ArgFail(FunctionValidator& f, PropertyName* argName, ParseNode* stmt)
 {
     return f.failName(stmt, "expecting argument type declaration for '%s' of the "
                       "form 'arg = arg|0' or 'arg = +arg' or 'arg = fround(arg)'", argName);
 }
 
 static bool
-CheckArgumentType(FunctionBuilder& f, ParseNode* stmt, PropertyName* name, VarType* type)
+CheckArgumentType(FunctionValidator& f, ParseNode* stmt, PropertyName* name, VarType* type)
 {
     if (!stmt || !IsExpressionStatement(stmt))
         return ArgFail(f, name, stmt ? stmt : f.fn());
 
     ParseNode* initNode = ExpressionStatementExpr(stmt);
     if (!initNode || !initNode->isKind(PNK_ASSIGN))
         return ArgFail(f, name, stmt);
 
@@ -5288,17 +5219,17 @@ CheckProcessingDirectives(ModuleValidato
     while (stmt && IsIgnoredDirective(m.cx(), stmt))
         stmt = NextNode(stmt);
 
     *stmtIter = stmt;
     return true;
 }
 
 static bool
-CheckArguments(FunctionBuilder& f, ParseNode** stmtIter, VarTypeVector* argTypes)
+CheckArguments(FunctionValidator& f, ParseNode** stmtIter, VarTypeVector* argTypes)
 {
     ParseNode* stmt = *stmtIter;
 
     unsigned numFormals;
     ParseNode* argpn = FunctionArgsList(f.fn(), &numFormals);
 
     for (unsigned i = 0; i < numFormals; i++, argpn = NextNode(argpn), stmt = NextNode(stmt)) {
         PropertyName* name;
@@ -5316,17 +5247,17 @@ CheckArguments(FunctionBuilder& f, Parse
             return false;
     }
 
     *stmtIter = stmt;
     return true;
 }
 
 static bool
-IsLiteralOrConst(FunctionBuilder& f, ParseNode* pn, AsmJSNumLit* lit)
+IsLiteralOrConst(FunctionValidator& f, ParseNode* pn, AsmJSNumLit* lit)
 {
     if (pn->isKind(PNK_NAME)) {
         const ModuleValidator::Global* global = f.lookupGlobal(pn->name());
         if (!global || global->which() != ModuleValidator::Global::ConstantLiteral)
             return false;
 
         *lit = global->constLiteralValue();
         return true;
@@ -5335,17 +5266,17 @@ IsLiteralOrConst(FunctionBuilder& f, Par
     if (!IsNumericLiteral(f.m(), pn))
         return false;
 
     *lit = ExtractNumericLiteral(f.m(), pn);
     return true;
 }
 
 static bool
-CheckFinalReturn(FunctionBuilder& f, ParseNode* lastNonEmptyStmt)
+CheckFinalReturn(FunctionValidator& f, ParseNode* lastNonEmptyStmt)
 {
     if (!f.hasAlreadyReturned()) {
         f.setReturnedType(RetType::Void);
         f.writeOp(Stmt::Ret);
         return true;
     }
 
     if (!lastNonEmptyStmt->isKind(PNK_RETURN)) {
@@ -5355,17 +5286,17 @@ CheckFinalReturn(FunctionBuilder& f, Par
         f.writeOp(Stmt::Ret);
         return true;
     }
 
     return true;
 }
 
 static bool
-CheckVariable(FunctionBuilder& f, ParseNode* var)
+CheckVariable(FunctionValidator& f, ParseNode* var)
 {
     if (!IsDefinition(var))
         return f.fail(var, "local variable names must not restate argument names");
 
     PropertyName* name = var->name();
 
     if (!CheckIdentifier(f.m(), var, name))
         return false;
@@ -5380,36 +5311,36 @@ CheckVariable(FunctionBuilder& f, ParseN
 
     if (!lit.hasType())
         return f.failName(var, "var '%s' initializer out of range", name);
 
     return f.addVariable(var, name, lit);
 }
 
 static bool
-CheckVariables(FunctionBuilder& f, ParseNode** stmtIter)
+CheckVariables(FunctionValidator& f, ParseNode** stmtIter)
 {
     ParseNode* stmt = *stmtIter;
 
     for (; stmt && stmt->isKind(PNK_VAR); stmt = NextNonEmptyStatement(stmt)) {
         for (ParseNode* var = VarListHead(stmt); var; var = NextNode(var)) {
             if (!CheckVariable(f, var))
                 return false;
         }
     }
 
     *stmtIter = stmt;
     return true;
 }
 
 static bool
-CheckExpr(FunctionBuilder& f, ParseNode* expr, Type* type);
-
-static bool
-CheckNumericLiteral(FunctionBuilder& f, ParseNode* num, Type* type)
+CheckExpr(FunctionValidator& f, ParseNode* expr, Type* type);
+
+static bool
+CheckNumericLiteral(FunctionValidator& f, ParseNode* num, Type* type)
 {
     AsmJSNumLit literal = ExtractNumericLiteral(f.m(), num);
     if (!literal.hasType())
         return f.fail(num, "numeric literal out of representable integer range");
     f.writeLit(literal);
     *type = Type::Of(literal);
     return true;
 }
@@ -5443,21 +5374,21 @@ EmitLiteral(FunctionCompiler& f, AsmType
         *def = f.constant(lit, MIRType_Float32x4);
         return true;
       }
     }
     MOZ_CRASH("unexpected literal type");
 }
 
 static bool
-CheckVarRef(FunctionBuilder& f, ParseNode* varRef, Type* type)
+CheckVarRef(FunctionValidator& f, ParseNode* varRef, Type* type)
 {
     PropertyName* name = varRef->name();
 
-    if (const FunctionBuilder::Local* local = f.lookupLocal(name)) {
+    if (const FunctionValidator::Local* local = f.lookupLocal(name)) {
         switch (local->type.which()) {
           case VarType::Int:       f.writeOp(I32::GetLocal);   break;
           case VarType::Double:    f.writeOp(F64::GetLocal);   break;
           case VarType::Float:     f.writeOp(F32::GetLocal);   break;
           case VarType::Int32x4:   f.writeOp(I32X4::GetLocal); break;
           case VarType::Float32x4: f.writeOp(F32X4::GetLocal); break;
         }
         f.writeU32(local->slot);
@@ -5525,27 +5456,27 @@ EmitGetGlo(FunctionCompiler& f, MIRType 
 {
     uint32_t globalDataOffset = f.readU32();
     bool isConst = bool(f.readU8());
     *def = f.loadGlobalVar(globalDataOffset, isConst, type);
     return true;
 }
 
 static inline bool
-IsLiteralOrConstInt(FunctionBuilder& f, ParseNode* pn, uint32_t* u32)
+IsLiteralOrConstInt(FunctionValidator& f, ParseNode* pn, uint32_t* u32)
 {
     AsmJSNumLit lit;
     if (!IsLiteralOrConst(f, pn, &lit))
         return false;
 
     return IsLiteralInt(lit, u32);
 }
 
 static bool
-FoldMaskedArrayIndex(FunctionBuilder& f, ParseNode** indexExpr, int32_t* mask,
+FoldMaskedArrayIndex(FunctionValidator& f, ParseNode** indexExpr, int32_t* mask,
                      NeedsBoundsCheck* needsBoundsCheck)
 {
     MOZ_ASSERT((*indexExpr)->isKind(PNK_BITAND));
 
     ParseNode* indexNode = BitwiseLeft(*indexExpr);
     ParseNode* maskNode = BitwiseRight(*indexExpr);
 
     uint32_t mask2;
@@ -5563,17 +5494,17 @@ FoldMaskedArrayIndex(FunctionBuilder& f,
     }
 
     return false;
 }
 
 static const int32_t NoMask = -1;
 
 static bool
-CheckArrayAccess(FunctionBuilder& f, ParseNode* viewName, ParseNode* indexExpr,
+CheckArrayAccess(FunctionValidator& f, ParseNode* viewName, ParseNode* indexExpr,
                  Scalar::Type* viewType, NeedsBoundsCheck* needsBoundsCheck, int32_t* mask)
 {
     *needsBoundsCheck = NEEDS_BOUNDS_CHECK;
 
     if (!viewName->isKind(PNK_NAME))
         return f.fail(viewName, "base of array access must be a typed array view name");
 
     const ModuleValidator::Global* global = f.lookupGlobal(viewName->name());
@@ -5661,17 +5592,17 @@ CheckArrayAccess(FunctionBuilder& f, Par
                 return f.failf(pointerNode, "%s is not a subtype of int", pointerType.toChars());
         }
     }
 
     return true;
 }
 
 static bool
-CheckAndPrepareArrayAccess(FunctionBuilder& f, ParseNode* viewName, ParseNode* indexExpr,
+CheckAndPrepareArrayAccess(FunctionValidator& f, ParseNode* viewName, ParseNode* indexExpr,
                            Scalar::Type* viewType, NeedsBoundsCheck* needsBoundsCheck, int32_t* mask)
 {
     size_t prepareAt = f.tempOp();
 
     if (!CheckArrayAccess(f, viewName, indexExpr, viewType, needsBoundsCheck, mask))
         return false;
 
     // Don't generate the mask op if there is no need for it which could happen for
@@ -5682,17 +5613,17 @@ CheckAndPrepareArrayAccess(FunctionBuild
     } else {
         f.patchOp(prepareAt, I32::Id);
     }
 
     return true;
 }
 
 static bool
-CheckLoadArray(FunctionBuilder& f, ParseNode* elem, Type* type)
+CheckLoadArray(FunctionValidator& f, ParseNode* elem, Type* type)
 {
     Scalar::Type viewType;
     NeedsBoundsCheck needsBoundsCheck;
     int32_t mask;
 
     size_t opcodeAt = f.tempOp();
     size_t needsBoundsCheckAt = f.tempU8();
 
@@ -5731,17 +5662,17 @@ EmitLoadArray(FunctionCompiler& f, Scala
     MDefinition* ptr;
     if (!EmitI32Expr(f, &ptr))
         return false;
     *def = f.loadHeap(scalarType, ptr, needsBoundsCheck);
     return true;
 }
 
 static bool
-CheckDotAccess(FunctionBuilder& f, ParseNode* elem, Type* type)
+CheckDotAccess(FunctionValidator& f, ParseNode* elem, Type* type)
 {
     MOZ_ASSERT(elem->isKind(PNK_DOT));
 
     size_t opcodeAt = f.tempOp();
 
     ParseNode* base = DotBase(elem);
     Type baseType;
     if (!CheckExpr(f, base, &baseType))
@@ -5772,17 +5703,17 @@ EmitSignMask(FunctionCompiler& f, AsmTyp
     MDefinition* in;
     if (!EmitExpr(f, type, &in))
         return false;
     *def = f.extractSignMask(in);
     return true;
 }
 
 static bool
-CheckStoreArray(FunctionBuilder& f, ParseNode* lhs, ParseNode* rhs, Type* type)
+CheckStoreArray(FunctionValidator& f, ParseNode* lhs, ParseNode* rhs, Type* type)
 {
     size_t opcodeAt = f.tempOp();
     size_t needsBoundsCheckAt = f.tempU8();
 
     Scalar::Type viewType;
     NeedsBoundsCheck needsBoundsCheck;
     int32_t mask;
     if (!CheckAndPrepareArrayAccess(f, ElemBase(lhs), ElemIndex(lhs), &viewType, &needsBoundsCheck, &mask))
@@ -5909,28 +5840,28 @@ EmitStoreWithCoercion(FunctionCompiler& 
     }
 
     f.storeHeap(viewType, ptr, coerced, needsBoundsCheck);
     *def = rhs;
     return true;
 }
 
 static bool
-CheckAssignName(FunctionBuilder& f, ParseNode* lhs, ParseNode* rhs, Type* type)
+CheckAssignName(FunctionValidator& f, ParseNode* lhs, ParseNode* rhs, Type* type)
 {
     RootedPropertyName name(f.cx(), lhs->name());
 
     size_t opcodeAt = f.tempOp();
     size_t indexAt = f.temp32();
 
     Type rhsType;
     if (!CheckExpr(f, rhs, &rhsType))
         return false;
 
-    if (const FunctionBuilder::Local* lhsVar = f.lookupLocal(name)) {
+    if (const FunctionValidator::Local* lhsVar = f.lookupLocal(name)) {
         if (!(rhsType <= lhsVar->type)) {
             return f.failf(lhs, "%s is not a subtype of %s",
                            rhsType.toChars(), lhsVar->type.toType().toChars());
         }
 
         switch (lhsVar->type.which()) {
           case VarType::Int:       f.patchOp(opcodeAt, I32::SetLocal);   break;
           case VarType::Float:     f.patchOp(opcodeAt, F32::SetLocal);   break;
@@ -5995,34 +5926,34 @@ EmitSetGlo(FunctionCompiler& f, AsmType 
     if (!EmitExpr(f, type, &expr))
         return false;
     f.storeGlobalVar(globalDataOffset, expr);
     *def = expr;
     return true;
 }
 
 static bool
-CheckAssign(FunctionBuilder& f, ParseNode* assign, Type* type)
+CheckAssign(FunctionValidator& f, ParseNode* assign, Type* type)
 {
     MOZ_ASSERT(assign->isKind(PNK_ASSIGN));
 
     ParseNode* lhs = BinaryLeft(assign);
     ParseNode* rhs = BinaryRight(assign);
 
     if (lhs->getKind() == PNK_ELEM)
         return CheckStoreArray(f, lhs, rhs, type);
 
     if (lhs->getKind() == PNK_NAME)
         return CheckAssignName(f, lhs, rhs, type);
 
     return f.fail(assign, "left-hand side of assignment must be a variable or array access");
 }
 
 static bool
-CheckMathIMul(FunctionBuilder& f, ParseNode* call, Type* type)
+CheckMathIMul(FunctionValidator& f, ParseNode* call, Type* type)
 {
     if (CallArgListLength(call) != 2)
         return f.fail(call, "Math.imul must be passed 2 arguments");
 
     ParseNode* lhs = CallArgList(call);
     ParseNode* rhs = NextNode(lhs);
 
     f.writeOp(I32::Mul);
@@ -6040,17 +5971,17 @@ CheckMathIMul(FunctionBuilder& f, ParseN
     if (!rhsType.isIntish())
         return f.failf(rhs, "%s is not a subtype of intish", rhsType.toChars());
 
     *type = Type::Signed;
     return true;
 }
 
 static bool
-CheckMathClz32(FunctionBuilder& f, ParseNode* call, Type* type)
+CheckMathClz32(FunctionValidator& f, ParseNode* call, Type* type)
 {
     if (CallArgListLength(call) != 1)
         return f.fail(call, "Math.clz32 must be passed 1 argument");
 
     f.writeOp(I32::Clz);
 
     ParseNode* arg = CallArgList(call);
 
@@ -6061,17 +5992,17 @@ CheckMathClz32(FunctionBuilder& f, Parse
     if (!argType.isIntish())
         return f.failf(arg, "%s is not a subtype of intish", argType.toChars());
 
     *type = Type::Fixnum;
     return true;
 }
 
 static bool
-CheckMathAbs(FunctionBuilder& f, ParseNode* call, Type* type)
+CheckMathAbs(FunctionValidator& f, ParseNode* call, Type* type)
 {
     if (CallArgListLength(call) != 1)
         return f.fail(call, "Math.abs must be passed 1 argument");
 
     ParseNode* arg = CallArgList(call);
 
     size_t opcodeAt = f.tempOp();
 
@@ -6096,17 +6027,17 @@ CheckMathAbs(FunctionBuilder& f, ParseNo
         *type = Type::Floatish;
         return true;
     }
 
     return f.failf(call, "%s is not a subtype of signed, float? or double?", argType.toChars());
 }
 
 static bool
-CheckMathSqrt(FunctionBuilder& f, ParseNode* call, Type* type)
+CheckMathSqrt(FunctionValidator& f, ParseNode* call, Type* type)
 {
     if (CallArgListLength(call) != 1)
         return f.fail(call, "Math.sqrt must be passed 1 argument");
 
     ParseNode* arg = CallArgList(call);
 
     size_t opcodeAt = f.tempOp();
 
@@ -6125,17 +6056,17 @@ CheckMathSqrt(FunctionBuilder& f, ParseN
         *type = Type::Floatish;
         return true;
     }
 
     return f.failf(call, "%s is neither a subtype of double? nor float?", argType.toChars());
 }
 
 static bool
-CheckMathMinMax(FunctionBuilder& f, ParseNode* callNode, bool isMax, Type* type)
+CheckMathMinMax(FunctionValidator& f, ParseNode* callNode, bool isMax, Type* type)
 {
     if (CallArgListLength(callNode) < 2)
         return f.fail(callNode, "Math.min/max must be passed at least 2 arguments");
 
     size_t opcodeAt = f.tempOp();
     size_t numArgsAt = f.tempU8();
 
     ParseNode* firstArg = CallArgList(callNode);
@@ -6205,17 +6136,17 @@ EmitMathMinMax(FunctionCompiler& f, AsmT
             return false;
         lastDef = f.minMax(lastDef, next, mirType, isMax);
     }
     *def = lastDef;
     return true;
 }
 
 static bool
-CheckSharedArrayAtomicAccess(FunctionBuilder& f, ParseNode* viewName, ParseNode* indexExpr,
+CheckSharedArrayAtomicAccess(FunctionValidator& f, ParseNode* viewName, ParseNode* indexExpr,
                              Scalar::Type* viewType, NeedsBoundsCheck* needsBoundsCheck,
                              int32_t* mask)
 {
     if (!CheckAndPrepareArrayAccess(f, viewName, indexExpr, viewType, needsBoundsCheck, mask))
         return false;
 
     // Atomic accesses may be made on shared integer arrays only.
 
@@ -6235,28 +6166,28 @@ CheckSharedArrayAtomicAccess(FunctionBui
       default:
         return f.failf(viewName, "not an integer array");
     }
 
     return true;
 }
 
 static bool
-CheckAtomicsFence(FunctionBuilder& f, ParseNode* call, Type* type)
+CheckAtomicsFence(FunctionValidator& f, ParseNode* call, Type* type)
 {
     if (CallArgListLength(call) != 0)
         return f.fail(call, "Atomics.fence must be passed 0 arguments");
 
     f.writeOp(Stmt::AtomicsFence);
     *type = Type::Void;
     return true;
 }
 
 static bool
-CheckAtomicsLoad(FunctionBuilder& f, ParseNode* call, Type* type)
+CheckAtomicsLoad(FunctionValidator& f, ParseNode* call, Type* type)
 {
     if (CallArgListLength(call) != 2)
         return f.fail(call, "Atomics.load must be passed 2 arguments");
 
     ParseNode* arrayArg = CallArgList(call);
     ParseNode* indexArg = NextNode(arrayArg);
 
     f.writeOp(I32::AtomicsLoad);
@@ -6284,17 +6215,17 @@ EmitAtomicsLoad(FunctionCompiler& f, MDe
     MDefinition* index;
     if (!EmitI32Expr(f, &index))
         return false;
     *def = f.atomicLoadHeap(viewType, index, needsBoundsCheck);
     return true;
 }
 
 static bool
-CheckAtomicsStore(FunctionBuilder& f, ParseNode* call, Type* type)
+CheckAtomicsStore(FunctionValidator& f, ParseNode* call, Type* type)
 {
     if (CallArgListLength(call) != 3)
         return f.fail(call, "Atomics.store must be passed 3 arguments");
 
     ParseNode* arrayArg = CallArgList(call);
     ParseNode* indexArg = NextNode(arrayArg);
     ParseNode* valueArg = NextNode(indexArg);
 
@@ -6334,17 +6265,17 @@ EmitAtomicsStore(FunctionCompiler& f, MD
     if (!EmitI32Expr(f, &value))
         return false;
     f.atomicStoreHeap(viewType, index, value, needsBoundsCheck);
     *def = value;
     return true;
 }
 
 static bool
-CheckAtomicsBinop(FunctionBuilder& f, ParseNode* call, Type* type, js::jit::AtomicOp op)
+CheckAtomicsBinop(FunctionValidator& f, ParseNode* call, Type* type, js::jit::AtomicOp op)
 {
     if (CallArgListLength(call) != 3)
         return f.fail(call, "Atomics binary operator must be passed 3 arguments");
 
     ParseNode* arrayArg = CallArgList(call);
     ParseNode* indexArg = NextNode(arrayArg);
     ParseNode* valueArg = NextNode(indexArg);
 
@@ -6385,34 +6316,34 @@ EmitAtomicsBinOp(FunctionCompiler& f, MD
     MDefinition* value;
     if (!EmitI32Expr(f, &value))
         return false;
     *def = f.atomicBinopHeap(op, viewType, index, value, needsBoundsCheck);
     return true;
 }
 
 static bool
-CheckAtomicsIsLockFree(FunctionBuilder& f, ParseNode* call, Type* type)
+CheckAtomicsIsLockFree(FunctionValidator& f, ParseNode* call, Type* type)
 {
     if (CallArgListLength(call) != 1)
         return f.fail(call, "Atomics.isLockFree must be passed 1 argument");
 
     ParseNode* sizeArg = CallArgList(call);
 
     uint32_t size;
     if (!IsLiteralInt(f.m(), sizeArg, &size))
         return f.fail(sizeArg, "Atomics.isLockFree requires an integer literal argument");
 
     f.writeInt32Lit(AtomicOperations::isLockfree(size));
     *type = Type::Int;
     return true;
 }
 
 static bool
-CheckAtomicsCompareExchange(FunctionBuilder& f, ParseNode* call, Type* type)
+CheckAtomicsCompareExchange(FunctionValidator& f, ParseNode* call, Type* type)
 {
     if (CallArgListLength(call) != 4)
         return f.fail(call, "Atomics.compareExchange must be passed 4 arguments");
 
     ParseNode* arrayArg = CallArgList(call);
     ParseNode* indexArg = NextNode(arrayArg);
     ParseNode* oldValueArg = NextNode(indexArg);
     ParseNode* newValueArg = NextNode(oldValueArg);
@@ -6462,17 +6393,17 @@ EmitAtomicsCompareExchange(FunctionCompi
     MDefinition* newValue;
     if (!EmitI32Expr(f, &newValue))
         return false;
     *def = f.atomicCompareExchangeHeap(viewType, index, oldValue, newValue, needsBoundsCheck);
     return true;
 }
 
 static bool
-CheckAtomicsExchange(FunctionBuilder& f, ParseNode* call, Type* type)
+CheckAtomicsExchange(FunctionValidator& f, ParseNode* call, Type* type)
 {
     if (CallArgListLength(call) != 3)
         return f.fail(call, "Atomics.exchange must be passed 3 arguments");
 
     ParseNode* arrayArg = CallArgList(call);
     ParseNode* indexArg = NextNode(arrayArg);
     ParseNode* valueArg = NextNode(indexArg);
 
@@ -6511,17 +6442,17 @@ EmitAtomicsExchange(FunctionCompiler& f,
     MDefinition* value;
     if (!EmitI32Expr(f, &value))
         return false;
     *def = f.atomicExchangeHeap(viewType, index, value, needsBoundsCheck);
     return true;
 }
 
 static bool
-CheckAtomicsBuiltinCall(FunctionBuilder& f, ParseNode* callNode, AsmJSAtomicsBuiltinFunction func,
+CheckAtomicsBuiltinCall(FunctionValidator& f, ParseNode* callNode, AsmJSAtomicsBuiltinFunction func,
                         Type* resultType)
 {
     switch (func) {
       case AsmJSAtomicsBuiltin_compareExchange:
         return CheckAtomicsCompareExchange(f, callNode, resultType);
       case AsmJSAtomicsBuiltin_exchange:
         return CheckAtomicsExchange(f, callNode, resultType);
       case AsmJSAtomicsBuiltin_load:
@@ -6542,20 +6473,20 @@ CheckAtomicsBuiltinCall(FunctionBuilder&
         return CheckAtomicsBinop(f, callNode, resultType, AtomicFetchXorOp);
       case AsmJSAtomicsBuiltin_isLockFree:
         return CheckAtomicsIsLockFree(f, callNode, resultType);
       default:
         MOZ_CRASH("unexpected atomicsBuiltin function");
     }
 }
 
-typedef bool (*CheckArgType)(FunctionBuilder& f, ParseNode* argNode, Type type);
-
-static bool
-CheckCallArgs(FunctionBuilder& f, ParseNode* callNode, CheckArgType checkArg, Signature& signature)
+typedef bool (*CheckArgType)(FunctionValidator& f, ParseNode* argNode, Type type);
+
+static bool
+CheckCallArgs(FunctionValidator& f, ParseNode* callNode, CheckArgType checkArg, Signature& signature)
 {
     ParseNode* argNode = CallArgList(callNode);
     for (unsigned i = 0; i < CallArgListLength(callNode); i++, argNode = NextNode(argNode)) {
         Type type;
         if (!CheckExpr(f, argNode, &type))
             return false;
 
         if (!checkArg(f, argNode, type))
@@ -6627,25 +6558,41 @@ CheckFunctionSignature(ModuleValidator& 
     if (!CheckSignatureAgainstExisting(m, usepn, sig, existing->sig()))
         return false;
 
     *func = existing;
     return true;
 }
 
 static bool
-CheckIsVarType(FunctionBuilder& f, ParseNode* argNode, Type type)
+CheckIsVarType(FunctionValidator& f, ParseNode* argNode, Type type)
 {
     if (!type.isVarType())
         return f.failf(argNode, "%s is not a subtype of int, float or double", type.toChars());
     return true;
 }
 
-static bool
-CheckInternalCall(FunctionBuilder& f, ParseNode* callNode, PropertyName* calleeName,
+static void
+WriteCallLineCol(FunctionValidator& f, ParseNode* pn)
+{
+    uint32_t line, column;
+    f.m().tokenStream().srcCoords.lineNumAndColumnIndex(pn->pn_pos.begin, &line, &column);
+    f.writeU32(line);
+    f.writeU32(column);
+}
+
+static void
+ReadCallLineCol(FunctionCompiler& f, uint32_t* line, uint32_t* column)
+{
+    *line = f.readU32();
+    *column = f.readU32();
+}
+
+static bool
+CheckInternalCall(FunctionValidator& f, ParseNode* callNode, PropertyName* calleeName,
                   RetType retType, Type* type)
 {
     if (!f.canCall()) {
         return f.fail(callNode, "call expressions may not be nested inside heap expressions "
                                 "when the module contains a change-heap function");
     }
 
     switch (retType.which()) {
@@ -6657,17 +6604,17 @@ CheckInternalCall(FunctionBuilder& f, Pa
         case RetType::Float32x4: f.writeOp(F32X4::CallInternal); break;
     }
 
     // Function's index, to find out the function's entry
     size_t funcIndexAt = f.temp32();
     // Function's signature in lifo
     size_t signatureAt = f.tempPtr();
     // Call node position (asm.js specific)
-    f.writeU32(callNode->pn_pos.begin);
+    WriteCallLineCol(f, callNode);
 
     Signature signature(f.m().lifo(), retType);
     if (!CheckCallArgs(f, callNode, CheckIsVarType, signature))
         return false;
 
     ModuleValidator::Func* callee;
     if (!CheckFunctionSignature(f.m(), callNode, Move(signature), calleeName, &callee))
         return false;
@@ -6686,19 +6633,20 @@ EmitInternalCall(FunctionCompiler& f, Re
     Label* entry;
     if (!f.m().getOrCreateFunctionEntry(funcIndex, &entry))
         return false;
 
     const Signature& sig = *f.readSignature();
 
     MOZ_ASSERT_IF(sig.retType() != RetType::Void, sig.retType() == retType);
 
-    uint32_t callNodePosition = f.readU32();
-
-    FunctionCompiler::Call call(f, callNodePosition);
+    uint32_t lineno, column;
+    ReadCallLineCol(f, &lineno, &column);
+
+    FunctionCompiler::Call call(f, lineno, column);
     if (!EmitCallArgs(f, sig, &call))
         return false;
 
     return f.internalCall(sig, entry, call, def);
 }
 
 static bool
 CheckFuncPtrTableAgainstExisting(ModuleValidator& m, ParseNode* usepn,
@@ -6722,17 +6670,17 @@ CheckFuncPtrTableAgainstExisting(ModuleV
 
     if (!CheckModuleLevelName(m, usepn, name))
         return false;
 
     return m.addFuncPtrTable(name, usepn->pn_pos.begin, Move(sig), mask, tableOut);
 }
 
 static bool
-CheckFuncPtrCall(FunctionBuilder& f, ParseNode* callNode, RetType retType, Type* type)
+CheckFuncPtrCall(FunctionValidator& f, ParseNode* callNode, RetType retType, Type* type)
 {
     if (!f.canCall()) {
         return f.fail(callNode, "function-pointer call expressions may not be nested inside heap "
                                 "expressions when the module contains a change-heap function");
     }
 
     ParseNode* callee = CallCallee(callNode);
     ParseNode* tableNode = ElemBase(callee);
@@ -6769,17 +6717,17 @@ CheckFuncPtrCall(FunctionBuilder& f, Par
 
     // Table's mask
     f.writeU32(mask);
     // Global data offset
     size_t globalDataOffsetAt = f.temp32();
     // Signature
     size_t signatureAt = f.tempPtr();
     // Call node position (asm.js specific)
-    f.writeU32(callNode->pn_pos.begin);
+    WriteCallLineCol(f, callNode);
 
     Type indexType;
     if (!CheckExpr(f, indexNode, &indexType))
         return false;
 
     if (!indexType.isIntish())
         return f.failf(indexNode, "%s is not a subtype of intish", indexType.toChars());
 
@@ -6802,39 +6750,40 @@ static bool
 EmitFuncPtrCall(FunctionCompiler& f, RetType retType, MDefinition** def)
 {
     uint32_t mask = f.readU32();
     uint32_t globalDataOffset = f.readU32();
 
     const Signature& sig = *f.readSignature();
     MOZ_ASSERT_IF(sig.retType() != RetType::Void, sig.retType() == retType);
 
-    uint32_t callNodePosition = f.readU32();
+    uint32_t lineno, column;
+    ReadCallLineCol(f, &lineno, &column);
 
     MDefinition *index;
     if (!EmitI32Expr(f, &index))
         return false;
 
-    FunctionCompiler::Call call(f, callNodePosition);
+    FunctionCompiler::Call call(f, lineno, column);
     if (!EmitCallArgs(f, sig, &call))
         return false;
 
     return f.funcPtrCall(sig, mask, globalDataOffset, index, call, def);
 }
 
 static bool
-CheckIsExternType(FunctionBuilder& f, ParseNode* argNode, Type type)
+CheckIsExternType(FunctionValidator& f, ParseNode* argNode, Type type)
 {
     if (!type.isExtern())
         return f.failf(argNode, "%s is not a subtype of extern", type.toChars());
     return true;
 }
 
 static bool
-CheckFFICall(FunctionBuilder& f, ParseNode* callNode, unsigned ffiIndex, RetType retType,
+CheckFFICall(FunctionValidator& f, ParseNode* callNode, unsigned ffiIndex, RetType retType,
              Type* type)
 {
     if (!f.canCall()) {
         return f.fail(callNode, "FFI call expressions may not be nested inside heap "
                                 "expressions when the module contains a change-heap function");
     }
 
     PropertyName* calleeName = CallCallee(callNode)->name();
@@ -6853,17 +6802,17 @@ CheckFFICall(FunctionBuilder& f, ParseNo
         case RetType::Float32x4: f.writeOp(F32X4::CallImport); break;
     }
 
     // Global data offset
     size_t offsetAt = f.temp32();
     // Pointer to the exit's signature in the module's lifo
     size_t sigAt = f.tempPtr();
     // Call node position (asm.js specific)
-    f.writeU32(callNode->pn_pos.begin);
+    WriteCallLineCol(f, callNode);
 
     Signature signature(f.m().lifo(), retType);
     if (!CheckCallArgs(f, callNode, CheckIsExternType, signature))
         return false;
 
     unsigned exitIndex;
     const LifoSignature* lifoSig = nullptr;
     if (!f.m().addExit(ffiIndex, calleeName, Move(signature), &exitIndex, &lifoSig))
@@ -6881,26 +6830,28 @@ CheckFFICall(FunctionBuilder& f, ParseNo
 static bool
 EmitFFICall(FunctionCompiler& f, RetType retType, MDefinition** def)
 {
     unsigned globalDataOffset = f.readI32();
 
     const Signature& sig = *f.readSignature();
     MOZ_ASSERT_IF(sig.retType() != RetType::Void, sig.retType() == retType);
 
-    uint32_t callNodePosition = f.readU32();
-    FunctionCompiler::Call call(f, callNodePosition);
+    uint32_t lineno, column;
+    ReadCallLineCol(f, &lineno, &column);
+
+    FunctionCompiler::Call call(f, lineno, column);
     if (!EmitCallArgs(f, sig, &call))
         return false;
 
     return f.ffiCall(globalDataOffset, call, retType.toMIRType(), def);
 }
 
 static bool
-CheckFloatCoercionArg(FunctionBuilder& f, ParseNode* inputNode, Type inputType,
+CheckFloatCoercionArg(FunctionValidator& f, ParseNode* inputNode, Type inputType,
                       size_t opcodeAt)
 {
     if (inputType.isMaybeDouble()) {
         f.patchOp(opcodeAt, F32::FromF64);
         return true;
     }
     if (inputType.isSigned()) {
         f.patchOp(opcodeAt, F32::FromS32);
@@ -6915,20 +6866,20 @@ CheckFloatCoercionArg(FunctionBuilder& f
         return true;
     }
 
     return f.failf(inputNode, "%s is not a subtype of signed, unsigned, double? or floatish",
                    inputType.toChars());
 }
 
 static bool
-CheckCoercedCall(FunctionBuilder& f, ParseNode* call, RetType retType, Type* type);
-
-static bool
-CheckCoercionArg(FunctionBuilder& f, ParseNode* arg, AsmJSCoercion expected, Type* type)
+CheckCoercedCall(FunctionValidator& f, ParseNode* call, RetType retType, Type* type);
+
+static bool
+CheckCoercionArg(FunctionValidator& f, ParseNode* arg, AsmJSCoercion expected, Type* type)
 {
     RetType retType(expected);
     if (arg->isKind(PNK_CALL))
         return CheckCoercedCall(f, arg, retType, type);
 
     size_t opcodeAt = f.tempOp();
 
     Type argType;
@@ -6955,33 +6906,33 @@ CheckCoercionArg(FunctionBuilder& f, Par
         MOZ_CRASH("not call coercions");
     }
 
     *type = retType.toType();
     return true;
 }
 
 static bool
-CheckMathFRound(FunctionBuilder& f, ParseNode* callNode, Type* type)
+CheckMathFRound(FunctionValidator& f, ParseNode* callNode, Type* type)
 {
     if (CallArgListLength(callNode) != 1)
         return f.fail(callNode, "Math.fround must be passed 1 argument");
 
     ParseNode* argNode = CallArgList(callNode);
     Type argType;
     if (!CheckCoercionArg(f, argNode, AsmJS_FRound, &argType))
         return false;
 
     MOZ_ASSERT(argType == Type::Float);
     *type = Type::Float;
     return true;
 }
 
 static bool
-CheckMathBuiltinCall(FunctionBuilder& f, ParseNode* callNode, AsmJSMathBuiltinFunction func,
+CheckMathBuiltinCall(FunctionValidator& f, ParseNode* callNode, AsmJSMathBuiltinFunction func,
                      Type* type)
 {
     unsigned arity = 0;
     F32 f32;
     F64 f64;
     switch (func) {
       case AsmJSMathBuiltin_imul:   return CheckMathIMul(f, callNode, type);
       case AsmJSMathBuiltin_clz32:  return CheckMathClz32(f, callNode, type);
@@ -7006,17 +6957,17 @@ CheckMathBuiltinCall(FunctionBuilder& f,
     }
 
     unsigned actualArity = CallArgListLength(callNode);
     if (actualArity != arity)
         return f.failf(callNode, "call passed %u arguments, expected %u", actualArity, arity);
 
     size_t opcodeAt = f.tempOp();
     // Call node position (asm.js specific)
-    f.writeU32(callNode->pn_pos.begin);
+    WriteCallLineCol(f, callNode);
 
     Type firstType;
     ParseNode* argNode = CallArgList(callNode);
     if (!CheckExpr(f, argNode, &firstType))
         return false;
 
     if (!firstType.isMaybeFloat() && !firstType.isMaybeDouble())
         return f.fail(argNode, "arguments to math call should be a subtype of double? or float?");
@@ -7046,37 +6997,39 @@ CheckMathBuiltinCall(FunctionBuilder& f,
     return true;
 }
 
 static bool
 EmitMathBuiltinCall(FunctionCompiler& f, F32 f32, MDefinition** def)
 {
     MOZ_ASSERT(f32 == F32::Ceil || f32 == F32::Floor);
 
-    uint32_t callNodePos = f.readU32();
-
-    FunctionCompiler::Call call(f, callNodePos);
+    uint32_t lineno, column;
+    ReadCallLineCol(f, &lineno, &column);
+
+    FunctionCompiler::Call call(f, lineno, column);
     f.startCallArgs(&call);
 
     MDefinition* firstArg;
     if (!EmitF32Expr(f, &firstArg) || !f.passArg(firstArg, MIRType_Float32, &call))
         return false;
 
     f.finishCallArgs(&call);
 
     AsmJSImmKind callee = f32 == F32::Ceil ? AsmJSImm_CeilF : AsmJSImm_FloorF;
     return f.builtinCall(callee, call, MIRType_Float32, def);
 }
 
 static bool
 EmitMathBuiltinCall(FunctionCompiler& f, F64 f64, MDefinition** def)
 {
-    uint32_t callNodePos = f.readU32();
-
-    FunctionCompiler::Call call(f, callNodePos);
+    uint32_t lineno, column;
+    ReadCallLineCol(f, &lineno, &column);
+
+    FunctionCompiler::Call call(f, lineno, column);
     f.startCallArgs(&call);
 
     MDefinition* firstArg;
     if (!EmitF64Expr(f, &firstArg) || !f.passArg(firstArg, MIRType_Double, &call))
         return false;
 
     if (f64 == F64::Pow || f64 == F64::Atan2) {
         MDefinition* secondArg;
@@ -7106,17 +7059,17 @@ EmitMathBuiltinCall(FunctionCompiler& f,
     return f.builtinCall(callee, call, MIRType_Double, def);
 }
 
 namespace {
 // Include CheckSimdCallArgs in unnamed namespace to avoid MSVC name lookup bug.
 
 template<class CheckArgOp>
 static bool
-CheckSimdCallArgs(FunctionBuilder& f, ParseNode* call, unsigned expectedArity,
+CheckSimdCallArgs(FunctionValidator& f, ParseNode* call, unsigned expectedArity,
                   const CheckArgOp& checkArg)
 {
     unsigned numArgs = CallArgListLength(call);
     if (numArgs != expectedArity)
         return f.failf(call, "expected %u arguments to SIMD call, got %u", expectedArity, numArgs);
 
     ParseNode* arg = CallArgList(call);
     for (size_t i = 0; i < numArgs; i++, arg = NextNode(arg)) {
@@ -7128,17 +7081,17 @@ CheckSimdCallArgs(FunctionBuilder& f, Pa
             return false;
     }
 
     return true;
 }
 
 template<class CheckArgOp>
 static bool
-CheckSimdCallArgsPatchable(FunctionBuilder& f, ParseNode* call, unsigned expectedArity,
+CheckSimdCallArgsPatchable(FunctionValidator& f, ParseNode* call, unsigned expectedArity,
                            const CheckArgOp& checkArg)
 {
     unsigned numArgs = CallArgListLength(call);
     if (numArgs != expectedArity)
         return f.failf(call, "expected %u arguments to SIMD call, got %u", expectedArity, numArgs);
 
     ParseNode* arg = CallArgList(call);
     for (size_t i = 0; i < numArgs; i++, arg = NextNode(arg)) {
@@ -7157,17 +7110,17 @@ CheckSimdCallArgsPatchable(FunctionBuild
 
 class CheckArgIsSubtypeOf
 {
     Type formalType_;
 
   public:
     explicit CheckArgIsSubtypeOf(AsmJSSimdType t) : formalType_(t) {}
 
-    bool operator()(FunctionBuilder& f, ParseNode* arg, unsigned argIndex, Type actualType) const
+    bool operator()(FunctionValidator& f, ParseNode* arg, unsigned argIndex, Type actualType) const
     {
         if (!(actualType <= formalType_)) {
             return f.failf(arg, "%s is not a subtype of %s", actualType.toChars(),
                            formalType_.toChars());
         }
         return true;
     }
 };
@@ -7189,17 +7142,17 @@ class CheckSimdScalarArgs
     AsmJSSimdType simdType_;
     Type formalType_;
 
   public:
     explicit CheckSimdScalarArgs(AsmJSSimdType simdType)
       : simdType_(simdType), formalType_(SimdToCoercedScalarType(simdType))
     {}
 
-    bool operator()(FunctionBuilder& f, ParseNode* arg, unsigned argIndex, Type actualType,
+    bool operator()(FunctionValidator& f, ParseNode* arg, unsigned argIndex, Type actualType,
                     size_t patchAt) const
     {
         if (!(actualType <= formalType_)) {
             // As a special case, accept doublelit arguments to float32x4 ops by
             // re-emitting them as float32 constants.
             if (simdType_ != AsmJSSimdType_float32x4 || !actualType.isDoubleLit()) {
                 return f.failf(arg, "%s is not a subtype of %s%s",
                                actualType.toChars(), formalType_.toChars(),
@@ -7226,17 +7179,17 @@ class CheckSimdScalarArgs
 
 class CheckSimdSelectArgs
 {
     Type formalType_;
 
   public:
     explicit CheckSimdSelectArgs(AsmJSSimdType t) : formalType_(t) {}
 
-    bool operator()(FunctionBuilder& f, ParseNode* arg, unsigned argIndex, Type actualType) const
+    bool operator()(FunctionValidator& f, ParseNode* arg, unsigned argIndex, Type actualType) const
     {
         if (argIndex == 0) {
             // First argument of select is an int32x4 mask.
             if (!(actualType <= Type::Int32x4))
                 return f.failf(arg, "%s is not a subtype of Int32x4", actualType.toChars());
             return true;
         }
 
@@ -7250,17 +7203,17 @@ class CheckSimdSelectArgs
 
 class CheckSimdVectorScalarArgs
 {
     AsmJSSimdType formalSimdType_;
 
   public:
     explicit CheckSimdVectorScalarArgs(AsmJSSimdType t) : formalSimdType_(t) {}
 
-    bool operator()(FunctionBuilder& f, ParseNode* arg, unsigned argIndex, Type actualType,
+    bool operator()(FunctionValidator& f, ParseNode* arg, unsigned argIndex, Type actualType,
                     size_t patchAt = -1) const
     {
         MOZ_ASSERT(argIndex < 2);
         if (argIndex == 0) {
             // First argument is the vector
             if (!(actualType <= Type(formalSimdType_))) {
                 return f.failf(arg, "%s is not a subtype of %s", actualType.toChars(),
                                Type(formalSimdType_).toChars());
@@ -7284,17 +7237,17 @@ class CheckSimdVectorScalarArgs
 
 class CheckSimdExtractLaneArgs
 {
     AsmJSSimdType formalSimdType_;
 
   public:
     explicit CheckSimdExtractLaneArgs(AsmJSSimdType t) : formalSimdType_(t) {}
 
-    bool operator()(FunctionBuilder& f, ParseNode* arg, unsigned argIndex, Type actualType) const
+    bool operator()(FunctionValidator& f, ParseNode* arg, unsigned argIndex, Type actualType) const
     {
         MOZ_ASSERT(argIndex < 2);
         if (argIndex == 0) {
             // First argument is the vector
             if (!(actualType <= Type(formalSimdType_))) {
                 return f.failf(arg, "%s is not a subtype of %s", actualType.toChars(),
                                Type(formalSimdType_).toChars());
             }
@@ -7313,17 +7266,17 @@ class CheckSimdExtractLaneArgs
 
 class CheckSimdReplaceLaneArgs
 {
     AsmJSSimdType formalSimdType_;
 
   public:
     explicit CheckSimdReplaceLaneArgs(AsmJSSimdType t) : formalSimdType_(t) {}
 
-    bool operator()(FunctionBuilder& f, ParseNode* arg, unsigned argIndex, Type actualType,
+    bool operator()(FunctionValidator& f, ParseNode* arg, unsigned argIndex, Type actualType,
                     size_t patchAt) const
     {
         MOZ_ASSERT(argIndex < 3);
         uint32_t u32;
         switch (argIndex) {
           case 0:
             // First argument is the vector
             if (!(actualType <= Type(formalSimdType_))) {
@@ -7349,27 +7302,27 @@ class CheckSimdReplaceLaneArgs
         }
         return false;
     }
 };
 
 } // namespace
 
 static void
-SwitchPackOp(FunctionBuilder& f, AsmJSSimdType type, I32X4 i32x4, F32X4 f32x4)
+SwitchPackOp(FunctionValidator& f, AsmJSSimdType type, I32X4 i32x4, F32X4 f32x4)
 {
     switch (type) {
       case AsmJSSimdType_int32x4:   f.writeOp(i32x4); return;
       case AsmJSSimdType_float32x4: f.writeOp(f32x4); return;
     }
     MOZ_CRASH("unexpected simd type");
 }
 
 static bool
-CheckSimdUnary(FunctionBuilder& f, ParseNode* call, AsmJSSimdType opType,
+CheckSimdUnary(FunctionValidator& f, ParseNode* call, AsmJSSimdType opType,
                MSimdUnaryArith::Operation op, Type* type)
 {
     SwitchPackOp(f, opType, I32X4::Unary, F32X4::Unary);
     f.writeU8(uint8_t(op));
     if (!CheckSimdCallArgs(f, call, 1, CheckArgIsSubtypeOf(opType)))
         return false;
     *type = opType;
     return true;
@@ -7383,28 +7336,28 @@ EmitSimdUnary(FunctionCompiler& f, AsmTy
     if (!EmitExpr(f, type, &in))
         return false;
     *def = f.unarySimd(in, op, MIRTypeFromAsmType(type));
     return true;
 }
 
 template<class OpKind>
 inline bool
-CheckSimdBinaryGuts(FunctionBuilder& f, ParseNode* call, AsmJSSimdType opType, OpKind op,
+CheckSimdBinaryGuts(FunctionValidator& f, ParseNode* call, AsmJSSimdType opType, OpKind op,
                     Type* type)
 {
     f.writeU8(uint8_t(op));
     if (!CheckSimdCallArgs(f, call, 2, CheckArgIsSubtypeOf(opType)))
         return false;
     *type = opType;
     return true;
 }
 
 static bool
-CheckSimdBinary(FunctionBuilder& f, ParseNode* call, AsmJSSimdType opType,
+CheckSimdBinary(FunctionValidator& f, ParseNode* call, AsmJSSimdType opType,
                 MSimdBinaryArith::Operation op, Type* type)
 {
     SwitchPackOp(f, opType, I32X4::Binary, F32X4::Binary);
     return CheckSimdBinaryGuts(f, call, opType, op, type);
 }
 
 template<class OpKind>
 inline bool
@@ -7423,32 +7376,32 @@ EmitBinarySimdGuts(FunctionCompiler& f, 
 static bool
 EmitSimdBinaryArith(FunctionCompiler& f, AsmType type, MDefinition** def)
 {
     MSimdBinaryArith::Operation op = MSimdBinaryArith::Operation(f.readU8());
     return EmitBinarySimdGuts(f, type, op, def);
 }
 
 static bool
-CheckSimdBinary(FunctionBuilder& f, ParseNode* call, AsmJSSimdType opType,
+CheckSimdBinary(FunctionValidator& f, ParseNode* call, AsmJSSimdType opType,
                 MSimdBinaryBitwise::Operation op, Type* type)
 {
     SwitchPackOp(f, opType, I32X4::BinaryBitwise, F32X4::BinaryBitwise);
     return CheckSimdBinaryGuts(f, call, opType, op, type);
 }
 
 static bool
 EmitSimdBinaryBitwise(FunctionCompiler& f, AsmType type, MDefinition** def)
 {
     MSimdBinaryBitwise::Operation op = MSimdBinaryBitwise::Operation(f.readU8());
     return EmitBinarySimdGuts(f, type, op, def);
 }
 
 static bool
-CheckSimdBinary(FunctionBuilder& f, ParseNode* call, AsmJSSimdType opType,
+CheckSimdBinary(FunctionValidator& f, ParseNode* call, AsmJSSimdType opType,
                 MSimdBinaryComp::Operation op, Type* type)
 {
     switch (opType) {
       case AsmJSSimdType_int32x4:   f.writeOp(I32X4::BinaryCompI32X4); break;
       case AsmJSSimdType_float32x4: f.writeOp(I32X4::BinaryCompF32X4); break;
     }
     f.writeU8(uint8_t(op));
     if (!CheckSimdCallArgs(f, call, 2, CheckArgIsSubtypeOf(opType)))
@@ -7467,17 +7420,17 @@ EmitSimdBinaryComp(FunctionCompiler& f, 
     MDefinition* rhs;
     if (!EmitExpr(f, type, &rhs))
         return false;
     *def = f.binarySimd<MSimdBinaryComp>(lhs, rhs, op);
     return true;
 }
 
 static bool
-CheckSimdBinary(FunctionBuilder& f, ParseNode* call, AsmJSSimdType opType,
+CheckSimdBinary(FunctionValidator& f, ParseNode* call, AsmJSSimdType opType,
                 MSimdShift::Operation op, Type* type)
 {
     f.writeOp(I32X4::BinaryShift);
     f.writeU8(uint8_t(op));
     if (!CheckSimdCallArgs(f, call, 2, CheckSimdVectorScalarArgs(opType)))
         return false;
     *type = Type::Int32x4;
     return true;
@@ -7493,17 +7446,17 @@ EmitSimdBinaryShift(FunctionCompiler& f,
     MDefinition* rhs;
     if (!EmitI32Expr(f, &rhs))
         return false;
     *def = f.binarySimd<MSimdShift>(lhs, rhs, op);
     return true;
 }
 
 static bool
-CheckSimdExtractLane(FunctionBuilder& f, ParseNode* call, AsmJSSimdType opType, Type* type)
+CheckSimdExtractLane(FunctionValidator& f, ParseNode* call, AsmJSSimdType opType, Type* type)
 {
     switch (opType) {
       case AsmJSSimdType_int32x4:
         f.writeOp(I32::I32X4ExtractLane);
         *type = Type::Signed;
         break;
       case AsmJSSimdType_float32x4:
         f.writeOp(F32::F32X4ExtractLane);
@@ -7547,17 +7500,17 @@ EmitExtractLane(FunctionCompiler& f, Asm
     MOZ_ASSERT(laneLit < 4);
     SimdLane lane = SimdLane(laneLit);
 
     *def = f.extractSimdElement(lane, vec, ScalarMIRTypeFromSimdAsmType(type));
     return true;
 }
 
 static bool
-CheckSimdReplaceLane(FunctionBuilder& f, ParseNode* call, AsmJSSimdType opType, Type* type)
+CheckSimdReplaceLane(FunctionValidator& f, ParseNode* call, AsmJSSimdType opType, Type* type)
 {
     SwitchPackOp(f, opType, I32X4::ReplaceLane, F32X4::ReplaceLane);
     if (!CheckSimdCallArgsPatchable(f, call, 3, CheckSimdReplaceLaneArgs(opType)))
         return false;
     *type = opType;
     return true;
 }
 
@@ -7603,17 +7556,17 @@ EmitSimdReplaceLane(FunctionCompiler& f,
 }
 
 typedef bool IsBitCast;
 
 namespace {
 // Include CheckSimdCast in unnamed namespace to avoid MSVC name lookup bug (due to the use of Type).
 
 static bool
-CheckSimdCast(FunctionBuilder& f, ParseNode* call, AsmJSSimdType fromType, AsmJSSimdType toType,
+CheckSimdCast(FunctionValidator& f, ParseNode* call, AsmJSSimdType fromType, AsmJSSimdType toType,
               bool bitcast, Type* type)
 {
     SwitchPackOp(f, toType,
                  bitcast ? I32X4::FromF32X4Bits : I32X4::FromF32X4,
                  bitcast ? F32X4::FromI32X4Bits : F32X4::FromI32X4);
     if (!CheckSimdCallArgs(f, call, 1, CheckArgIsSubtypeOf(fromType)))
         return false;
     *type = toType;
@@ -7629,31 +7582,31 @@ EmitSimdCast(FunctionCompiler& f, AsmTyp
     MDefinition* in;
     if (!EmitExpr(f, fromType, &in))
         return false;
     *def = f.convertSimd<T>(in, MIRTypeFromAsmType(fromType), MIRTypeFromAsmType(toType));
     return true;
 }
 
 static bool
-CheckSimdShuffleSelectors(FunctionBuilder& f, ParseNode* lane, int32_t lanes[4], uint32_t maxLane)
+CheckSimdShuffleSelectors(FunctionValidator& f, ParseNode* lane, int32_t lanes[4], uint32_t maxLane)
 {
     for (unsigned i = 0; i < 4; i++, lane = NextNode(lane)) {
         uint32_t u32;
         if (!IsLiteralInt(f.m(), lane, &u32))
             return f.failf(lane, "lane selector should be a constant integer literal");
         if (u32 >= maxLane)
             return f.failf(lane, "lane selector should be less than %u", maxLane);
         lanes[i] = int32_t(u32);
     }
     return true;
 }
 
 static bool
-CheckSimdSwizzle(FunctionBuilder& f, ParseNode* call, AsmJSSimdType opType, Type* type)
+CheckSimdSwizzle(FunctionValidator& f, ParseNode* call, AsmJSSimdType opType, Type* type)
 {
     unsigned numArgs = CallArgListLength(call);
     if (numArgs != 5)
         return f.failf(call, "expected 5 arguments to SIMD swizzle, got %u", numArgs);
 
     SwitchPackOp(f, opType, I32X4::Swizzle, F32X4::Swizzle);
 
     Type retType = opType;
@@ -7686,17 +7639,17 @@ EmitSimdSwizzle(FunctionCompiler& f, Asm
     for (unsigned i = 0; i < 4; i++)
         lanes[i] = f.readU8();
 
     *def = f.swizzleSimd(in, lanes[0], lanes[1], lanes[2], lanes[3], MIRTypeFromAsmType(type));
     return true;
 }
 
 static bool
-CheckSimdShuffle(FunctionBuilder& f, ParseNode* call, AsmJSSimdType opType, Type* type)
+CheckSimdShuffle(FunctionValidator& f, ParseNode* call, AsmJSSimdType opType, Type* type)
 {
     unsigned numArgs = CallArgListLength(call);
     if (numArgs != 6)
         return f.failf(call, "expected 6 arguments to SIMD shuffle, got %u", numArgs);
 
     SwitchPackOp(f, opType, I32X4::Shuffle, F32X4::Shuffle);
 
     Type retType = opType;
@@ -7736,17 +7689,17 @@ EmitSimdShuffle(FunctionCompiler& f, Asm
         lanes[i] = f.readU8();
 
     *def = f.shuffleSimd(lhs, rhs, lanes[0], lanes[1], lanes[2], lanes[3],
                          MIRTypeFromAsmType(type));
     return true;
 }
 
 static bool
-CheckSimdLoadStoreArgs(FunctionBuilder& f, ParseNode* call, AsmJSSimdType opType,
+CheckSimdLoadStoreArgs(FunctionValidator& f, ParseNode* call, AsmJSSimdType opType,
                        Scalar::Type* viewType, NeedsBoundsCheck* needsBoundsCheck)
 {
     ParseNode* view = CallArgList(call);
     if (!view->isKind(PNK_NAME))
         return f.fail(view, "expected Uint8Array view as SIMD.*.load/store first argument");
 
     const ModuleValidator::Global* global = f.lookupGlobal(view->name());
     if (!global ||
@@ -7789,17 +7742,17 @@ CheckSimdLoadStoreArgs(FunctionBuilder& 
         return f.failf(indexExpr, "%s is not a subtype of intish", indexType.toChars());
 
     f.leaveHeapExpression();
 
     return true;
 }
 
 static bool
-CheckSimdLoad(FunctionBuilder& f, ParseNode* call, AsmJSSimdType opType,
+CheckSimdLoad(FunctionValidator& f, ParseNode* call, AsmJSSimdType opType,
               unsigned numElems, Type* type)
 {
     unsigned numArgs = CallArgListLength(call);
     if (numArgs != 2)
         return f.failf(call, "expected 2 arguments to SIMD load, got %u", numArgs);
 
     SwitchPackOp(f, opType, I32X4::Load, F32X4::Load);
     size_t viewTypeAt = f.tempU8();
@@ -7829,17 +7782,17 @@ EmitSimdLoad(FunctionCompiler& f, AsmTyp
     if (!EmitI32Expr(f, &index))
         return false;
 
     *def = f.loadSimdHeap(viewType, index, needsBoundsCheck, numElems);
     return true;
 }
 
 static bool
-CheckSimdStore(FunctionBuilder& f, ParseNode* call, AsmJSSimdType opType,
+CheckSimdStore(FunctionValidator& f, ParseNode* call, AsmJSSimdType opType,
                unsigned numElems, Type* type)
 {
     unsigned numArgs = CallArgListLength(call);
     if (numArgs != 3)
         return f.failf(call, "expected 3 arguments to SIMD store, got %u", numArgs);
 
     SwitchPackOp(f, opType, I32X4::Store, F32X4::Store);
     size_t viewTypeAt = f.tempU8();
@@ -7882,17 +7835,17 @@ EmitSimdStore(FunctionCompiler& f, AsmTy
         return false;
 
     f.storeSimdHeap(viewType, index, vec, needsBoundsCheck, numElems);
     *def = vec;
     return true;
 }
 
 static bool
-CheckSimdSelect(FunctionBuilder& f, ParseNode* call, AsmJSSimdType opType, bool isElementWise,
+CheckSimdSelect(FunctionValidator& f, ParseNode* call, AsmJSSimdType opType, bool isElementWise,
                 Type* type)
 {
     SwitchPackOp(f, opType,
                  isElementWise ? I32X4::Select : I32X4::BitSelect,
                  isElementWise ? F32X4::Select : F32X4::BitSelect);
     if (!CheckSimdCallArgs(f, call, 3, CheckSimdSelectArgs(opType)))
         return false;
     *type = opType;
@@ -7907,27 +7860,27 @@ EmitSimdSelect(FunctionCompiler& f, AsmT
     MDefinition* defs[3];
     if (!EmitI32X4Expr(f, &defs[0]) || !EmitExpr(f, type, &defs[1]) || !EmitExpr(f, type, &defs[2]))
         return false;
     *def = f.selectSimd(defs[0], defs[1], defs[2], MIRTypeFromAsmType(type), isElementWise);
     return true;
 }
 
 static bool
-CheckSimdCheck(FunctionBuilder& f, ParseNode* call, AsmJSSimdType opType, Type* type)
+CheckSimdCheck(FunctionValidator& f, ParseNode* call, AsmJSSimdType opType, Type* type)
 {
     AsmJSCoercion coercion;
     ParseNode* argNode;
     if (!IsCoercionCall(f.m(), call, &coercion, &argNode))
         return f.failf(call, "expected 1 argument in call to check");
     return CheckCoercionArg(f, argNode, coercion, type);
 }
 
 static bool
-CheckSimdSplat(FunctionBuilder& f, ParseNode* call, AsmJSSimdType opType, Type* type)
+CheckSimdSplat(FunctionValidator& f, ParseNode* call, AsmJSSimdType opType, Type* type)
 {
     SwitchPackOp(f, opType, I32X4::Splat, F32X4::Splat);
     if (!CheckSimdCallArgsPatchable(f, call, 1, CheckSimdScalarArgs(opType)))
         return false;
     *type = opType;
     return true;
 }
 
@@ -7937,17 +7890,17 @@ EmitSimdSplat(FunctionCompiler& f, AsmTy
     MDefinition* in;
     if (!EmitExpr(f, AsmSimdTypeToScalarType(type), &in))
         return false;
     *def = f.splatSimd(in, MIRTypeFromAsmType(type));
     return true;
 }
 
 static bool
-CheckSimdOperationCall(FunctionBuilder& f, ParseNode* call, const ModuleValidator::Global* global,
+CheckSimdOperationCall(FunctionValidator& f, ParseNode* call, const ModuleValidator::Global* global,
                        Type* type)
 {
     MOZ_ASSERT(global->isSimdOperation());
 
     AsmJSSimdType opType = global->simdOperationType();
 
     switch (global->simdOperation()) {
       case AsmJSSimdOperation_check:
@@ -8043,17 +7996,17 @@ CheckSimdOperationCall(FunctionBuilder& 
 
       case AsmJSSimdOperation_splat:
         return CheckSimdSplat(f, call, opType, type);
     }
     MOZ_CRASH("unexpected simd operation in CheckSimdOperationCall");
 }
 
 static bool
-CheckSimdCtorCall(FunctionBuilder& f, ParseNode* call, const ModuleValidator::Global* global,
+CheckSimdCtorCall(FunctionValidator& f, ParseNode* call, const ModuleValidator::Global* global,
                   Type* type)
 {
     MOZ_ASSERT(call->isKind(PNK_CALL));
 
     AsmJSSimdType simdType = global->simdCtorType();
     SwitchPackOp(f, simdType, I32X4::Ctor, F32X4::Ctor);
 
     unsigned length = SimdTypeToLength(simdType);
@@ -8090,17 +8043,17 @@ EmitSimdCtor(FunctionCompiler& f, AsmTyp
       case AsmType::Float32:
       case AsmType::Float64:
         break;
     }
     MOZ_CRASH("unexpected SIMD type");
 }
 
 static bool
-CheckUncoercedCall(FunctionBuilder& f, ParseNode* expr, Type* type)
+CheckUncoercedCall(FunctionValidator& f, ParseNode* expr, Type* type)
 {
     MOZ_ASSERT(expr->isKind(PNK_CALL));
 
     const ModuleValidator::Global* global;
     if (IsCallToGlobal(f.m(), expr, &global)) {
         if (global->isMathFunction())
             return CheckMathBuiltinCall(f, expr, global->mathBuiltinFunction(), type);
         if (global->isAtomicsFunction())
@@ -8113,17 +8066,17 @@ CheckUncoercedCall(FunctionBuilder& f, P
 
     return f.fail(expr, "all function calls must either be calls to standard lib math functions, "
                         "standard atomic functions, standard SIMD constructors or operations, "
                         "ignored (via f(); or comma-expression), coerced to signed (via f()|0), "
                         "coerced to float (via fround(f())) or coerced to double (via +f())");
 }
 
 static bool
-CoerceResult(FunctionBuilder& f, ParseNode* expr, RetType expected, Type resultType,
+CoerceResult(FunctionValidator& f, ParseNode* expr, RetType expected, Type resultType,
              size_t patchAt, Type* type)
 {
     // At this point, the bytecode resembles this:
     //      | patchAt | the thing we wanted to coerce | current position |>
     switch (expected.which()) {
       case RetType::Void: {
         if (resultType.isIntish())
             f.patchOp(patchAt, Stmt::I32Expr);
@@ -8196,28 +8149,28 @@ CoerceResult(FunctionBuilder& f, ParseNo
         return true;
       }
     }
 
     return true;
 }
 
 static bool
-CheckCoercedMathBuiltinCall(FunctionBuilder& f, ParseNode* callNode, AsmJSMathBuiltinFunction func,
+CheckCoercedMathBuiltinCall(FunctionValidator& f, ParseNode* callNode, AsmJSMathBuiltinFunction func,
                             RetType retType, Type* type)
 {
     size_t opcodeAt = f.tempOp();
     Type resultType;
     if (!CheckMathBuiltinCall(f, callNode, func, &resultType))
         return false;
     return CoerceResult(f, callNode, retType, resultType, opcodeAt, type);
 }
 
 static bool
-CheckCoercedSimdCall(FunctionBuilder& f, ParseNode* call, const ModuleValidator::Global* global,
+CheckCoercedSimdCall(FunctionValidator& f, ParseNode* call, const ModuleValidator::Global* global,
                      RetType retType, Type* type)
 {
     size_t opcodeAt = f.tempOp();
 
     if (global->isSimdCtor()) {
         if (!CheckSimdCtorCall(f, call, global, type))
             return false;
         MOZ_ASSERT(type->isSimd());
@@ -8227,29 +8180,29 @@ CheckCoercedSimdCall(FunctionBuilder& f,
             return false;
         MOZ_ASSERT_IF(global->simdOperation() != AsmJSSimdOperation_extractLane, type->isSimd());
     }
 
     return CoerceResult(f, call, retType, *type, opcodeAt, type);
 }
 
 static bool
-CheckCoercedAtomicsBuiltinCall(FunctionBuilder& f, ParseNode* callNode,
+CheckCoercedAtomicsBuiltinCall(FunctionValidator& f, ParseNode* callNode,
                                AsmJSAtomicsBuiltinFunction func, RetType retType,
                                Type* resultType)
 {
     size_t opcodeAt = f.tempOp();
     Type actualRetType;
     if (!CheckAtomicsBuiltinCall(f, callNode, func, &actualRetType))
         return false;
     return CoerceResult(f, callNode, retType, actualRetType, opcodeAt, resultType);
 }
 
 static bool
-CheckCoercedCall(FunctionBuilder& f, ParseNode* call, RetType retType, Type* type)
+CheckCoercedCall(FunctionValidator& f, ParseNode* call, RetType retType, Type* type)
 {
     JS_CHECK_RECURSION_DONT_REPORT(f.cx(), return f.m().failOverRecursed());
 
     if (IsNumericLiteral(f.m(), call)) {
         size_t coerceOp = f.tempOp();
         AsmJSNumLit literal = ExtractNumericLiteral(f.m(), call);
         f.writeLit(literal);
         return CoerceResult(f, call, retType, Type::Of(literal), coerceOp, type);
@@ -8289,34 +8242,34 @@ CheckCoercedCall(FunctionBuilder& f, Par
             break;
         }
     }
 
     return CheckInternalCall(f, call, calleeName, retType, type);
 }
 
 static bool
-CheckPos(FunctionBuilder& f, ParseNode* pos, Type* type)
+CheckPos(FunctionValidator& f, ParseNode* pos, Type* type)
 {
     MOZ_ASSERT(pos->isKind(PNK_POS));
     ParseNode* operand = UnaryKid(pos);
 
     if (operand->isKind(PNK_CALL))
         return CheckCoercedCall(f, operand, RetType::Double, type);
 
     size_t opcodeAt = f.tempOp();
     Type operandType;
     if (!CheckExpr(f, operand, &operandType))
         return false;
 
     return CoerceResult(f, operand, RetType::Double, operandType, opcodeAt, type);
 }
 
 static bool
-CheckNot(FunctionBuilder& f, ParseNode* expr, Type* type)
+CheckNot(FunctionValidator& f, ParseNode* expr, Type* type)
 {
     MOZ_ASSERT(expr->isKind(PNK_NOT));
     ParseNode* operand = UnaryKid(expr);
 
     f.writeOp(I32::Not);
 
     Type operandType;
     if (!CheckExpr(f, operand, &operandType))
@@ -8325,17 +8278,17 @@ CheckNot(FunctionBuilder& f, ParseNode* 
     if (!operandType.isInt())
         return f.failf(operand, "%s is not a subtype of int", operandType.toChars());
 
     *type = Type::Int;
     return true;
 }
 
 static bool
-CheckNeg(FunctionBuilder& f, ParseNode* expr, Type* type)
+CheckNeg(FunctionValidator& f, ParseNode* expr, Type* type)
 {
     MOZ_ASSERT(expr->isKind(PNK_NEG));
     ParseNode* operand = UnaryKid(expr);
 
     size_t opcodeAt = f.tempOp();
 
     Type operandType;
     if (!CheckExpr(f, operand, &operandType))
@@ -8380,17 +8333,17 @@ EmitUnaryMir(FunctionCompiler& f, AsmTyp
     MDefinition* in;
     if (!EmitExpr(f, type, &in))
         return false;
     *def = f.unary<T>(in, MIRTypeFromAsmType(type));
     return true;
 }
 
 static bool
-CheckCoerceToInt(FunctionBuilder& f, ParseNode* expr, Type* type)
+CheckCoerceToInt(FunctionValidator& f, ParseNode* expr, Type* type)
 {
     MOZ_ASSERT(expr->isKind(PNK_BITNOT));
     ParseNode* operand = UnaryKid(expr);
 
     size_t opcodeAt = f.tempOp();
 
     Type operandType;
     if (!CheckExpr(f, operand, &operandType))
@@ -8406,17 +8359,17 @@ CheckCoerceToInt(FunctionBuilder& f, Par
         return f.failf(operand, "%s is not a subtype of double?, float? or intish", operandType.toChars());
 
     f.patchOp(opcodeAt, I32::Id);
     *type = Type::Signed;
     return true;
 }
 
 static bool
-CheckBitNot(FunctionBuilder& f, ParseNode* neg, Type* type)
+CheckBitNot(FunctionValidator& f, ParseNode* neg, Type* type)
 {
     MOZ_ASSERT(neg->isKind(PNK_BITNOT));
     ParseNode* operand = UnaryKid(neg);
 
     if (operand->isKind(PNK_BITNOT))
         return CheckCoerceToInt(f, operand, type);
 
     f.writeOp(I32::BitNot);
@@ -8428,20 +8381,20 @@ CheckBitNot(FunctionBuilder& f, ParseNod
     if (!operandType.isIntish())
         return f.failf(operand, "%s is not a subtype of intish", operandType.toChars());
 
     *type = Type::Signed;
     return true;
 }
 
 static bool
-CheckAsExprStatement(FunctionBuilder& f, ParseNode* exprStmt);
-
-static bool
-CheckComma(FunctionBuilder& f, ParseNode* comma, Type* type)
+CheckAsExprStatement(FunctionValidator& f, ParseNode* exprStmt);
+
+static bool
+CheckComma(FunctionValidator& f, ParseNode* comma, Type* type)
 {
     MOZ_ASSERT(comma->isKind(PNK_COMMA));
     ParseNode* operands = ListHead(comma);
 
     size_t commaAt = f.tempOp();
     f.writeU32(ListLength(comma));
 
     ParseNode* pn = operands;
@@ -8478,17 +8431,17 @@ EmitComma(FunctionCompiler& f, AsmType t
     for (uint32_t i = 1; i < numExpr; i++) {
         if (!EmitStatement(f))
             return false;
     }
     return EmitExpr(f, type, def);
 }
 
 static bool
-CheckConditional(FunctionBuilder& f, ParseNode* ternary, Type* type)
+CheckConditional(FunctionValidator& f, ParseNode* ternary, Type* type)
 {
     MOZ_ASSERT(ternary->isKind(PNK_CONDITIONAL));
 
     size_t opcodeAt = f.tempOp();
 
     ParseNode* cond = TernaryKid1(ternary);
     ParseNode* thenExpr = TernaryKid2(ternary);
     ParseNode* elseExpr = TernaryKid3(ternary);
@@ -8536,41 +8489,38 @@ static bool
 EmitConditional(FunctionCompiler& f, AsmType type, MDefinition** def)
 {
     MDefinition* cond;
     if (!EmitI32Expr(f, &cond))
         return false;
 
     MBasicBlock* thenBlock = nullptr;
     MBasicBlock* elseBlock = nullptr;
-
-    // TODO (bug 1178840) : find thenPos and elsePos
-    uint32_t thenPos = 0, elsePos = 0;
-    if (!f.branchAndStartThen(cond, thenPos, elsePos, &thenBlock, &elseBlock))
+    if (!f.branchAndStartThen(cond, &thenBlock, &elseBlock))
         return false;
 
     MDefinition* ifTrue;
     if (!EmitExpr(f, type, &ifTrue))
         return false;
 
-    BlockVector thenBlocks(f.cx());
+    BlockVector thenBlocks;
     if (!f.appendThenBlock(&thenBlocks))
         return false;
 
     f.pushPhiInput(ifTrue);
 
     f.switchToElse(elseBlock);
 
     MDefinition* ifFalse;
     if (!EmitExpr(f, type, &ifFalse))
         return false;
 
     f.pushPhiInput(ifFalse);
 
-    if (!f.joinIfElse(thenBlocks, f.pc()))
+    if (!f.joinIfElse(thenBlocks))
         return false;
 
     *def = f.popPhiOutput();
     return true;
 }
 
 static bool
 IsValidIntMultiplyConstant(ModuleValidator& m, ParseNode* expr)
@@ -8593,17 +8543,17 @@ IsValidIntMultiplyConstant(ModuleValidat
       case AsmJSNumLit::Float32x4:
         return false;
     }
 
     MOZ_MAKE_COMPILER_ASSUME_IS_UNREACHABLE("Bad literal");
 }
 
 static bool
-CheckMultiply(FunctionBuilder& f, ParseNode* star, Type* type)
+CheckMultiply(FunctionValidator& f, ParseNode* star, Type* type)
 {
     MOZ_ASSERT(star->isKind(PNK_STAR));
     ParseNode* lhs = MultiplyLeft(star);
     ParseNode* rhs = MultiplyRight(star);
 
     size_t opcodeAt = f.tempOp();
 
     Type lhsType;
@@ -8647,17 +8597,17 @@ EmitMultiply(FunctionCompiler& f, AsmTyp
     if (!EmitExpr(f, type, &rhs))
         return false;
     MIRType mirType = MIRTypeFromAsmType(type);
     *def = f.mul(lhs, rhs, mirType, type == AsmType::Int32 ? MMul::Integer : MMul::Normal);
     return true;
 }
 
 static bool
-CheckAddOrSub(FunctionBuilder& f, ParseNode* expr, Type* type, unsigned* numAddOrSubOut = nullptr)
+CheckAddOrSub(FunctionValidator& f, ParseNode* expr, Type* type, unsigned* numAddOrSubOut = nullptr)
 {
     JS_CHECK_RECURSION_DONT_REPORT(f.cx(), return f.m().failOverRecursed());
 
     MOZ_ASSERT(expr->isKind(PNK_ADD) || expr->isKind(PNK_SUB));
     ParseNode* lhs = AddSubLeft(expr);
     ParseNode* rhs = AddSubRight(expr);
 
     Type lhsType, rhsType;
@@ -8722,17 +8672,17 @@ EmitAddOrSub(FunctionCompiler& f, AsmTyp
     if (!EmitExpr(f, type, &rhs))
         return false;
     MIRType mirType = MIRTypeFromAsmType(type);
     *def = isAdd ? f.binary<MAdd>(lhs, rhs, mirType) : f.binary<MSub>(lhs, rhs, mirType);
     return true;
 }
 
 static bool
-CheckDivOrMod(FunctionBuilder& f, ParseNode* expr, Type* type)
+CheckDivOrMod(FunctionValidator& f, ParseNode* expr, Type* type)
 {
     MOZ_ASSERT(expr->isKind(PNK_DIV) || expr->isKind(PNK_MOD));
 
     size_t opcodeAt = f.tempOp();
 
     ParseNode* lhs = DivOrModLeft(expr);
     ParseNode* rhs = DivOrModRight(expr);
 
@@ -8794,17 +8744,17 @@ EmitDivOrMod(FunctionCompiler& f, AsmTyp
 static bool
 EmitDivOrMod(FunctionCompiler& f, AsmType type, bool isDiv, MDefinition** def)
 {
     MOZ_ASSERT(type != AsmType::Int32, "int div or mod must precise signedness");
     return EmitDivOrMod(f, type, isDiv, false, def);
 }
 
 static bool
-CheckComparison(FunctionBuilder& f, ParseNode* comp, Type* type)
+CheckComparison(FunctionValidator& f, ParseNode* comp, Type* type)
 {
     MOZ_ASSERT(comp->isKind(PNK_LT) || comp->isKind(PNK_LE) || comp->isKind(PNK_GT) ||
                comp->isKind(PNK_GE) || comp->isKind(PNK_EQ) || comp->isKind(PNK_NE));
 
     size_t opcodeAt = f.tempOp();
 
     ParseNode* lhs = ComparisonLeft(comp);
     ParseNode* rhs = ComparisonRight(comp);
@@ -8960,17 +8910,17 @@ EmitComparison(FunctionCompiler& f, I32 
       default: MOZ_CRASH("unexpected comparison opcode");
     }
 
     *def = f.compare(lhs, rhs, compareOp, compareType);
     return true;
 }
 
 static bool
-CheckBitwise(FunctionBuilder& f, ParseNode* bitwise, Type* type)
+CheckBitwise(FunctionValidator& f, ParseNode* bitwise, Type* type)
 {
     ParseNode* lhs = BitwiseLeft(bitwise);
     ParseNode* rhs = BitwiseRight(bitwise);
 
     int32_t identityElement;
     bool onlyOnRight;
     switch (bitwise->getKind()) {
       case PNK_BITOR:  identityElement = 0;  onlyOnRight = false; *type = Type::Signed;   break;
@@ -9051,17 +9001,17 @@ EmitBitwise<MBitNot>(FunctionCompiler& f
     MDefinition* in;
     if (!EmitI32Expr(f, &in))
         return false;
     *def = f.bitwise<MBitNot>(in);
     return true;
 }
 
 static bool
-CheckExpr(FunctionBuilder& f, ParseNode* expr, Type* type)
+CheckExpr(FunctionValidator& f, ParseNode* expr, Type* type)
 {
     JS_CHECK_RECURSION_DONT_REPORT(f.cx(), return f.m().failOverRecursed());
 
     if (IsNumericLiteral(f.m(), expr))
         return CheckNumericLiteral(f, expr, type);
 
     switch (expr->getKind()) {
       case PNK_NAME:        return CheckVarRef(f, expr, type);
@@ -9112,20 +9062,20 @@ EmitExpr(FunctionCompiler& f, AsmType ty
       case AsmType::Float64:   return EmitF64Expr(f, def);
       case AsmType::Int32x4:   return EmitI32X4Expr(f, def);
       case AsmType::Float32x4: return EmitF32X4Expr(f, def);
     }
     MOZ_CRASH("unexpected asm type");
 }
 
 static bool
-CheckStatement(FunctionBuilder& f, ParseNode* stmt);
-
-static bool
-CheckAsExprStatement(FunctionBuilder& f, ParseNode* expr)
+CheckStatement(FunctionValidator& f, ParseNode* stmt);
+
+static bool
+CheckAsExprStatement(FunctionValidator& f, ParseNode* expr)
 {
     Type type;
     if (expr->isKind(PNK_CALL))
         return CheckCoercedCall(f, expr, RetType::Void, &type);
 
     size_t opcodeAt = f.tempOp();
 
     if (!CheckExpr(f, expr, &type))
@@ -9143,44 +9093,85 @@ CheckAsExprStatement(FunctionBuilder& f,
         f.patchOp(opcodeAt, Stmt::F32X4Expr);
     else
         MOZ_CRASH("unexpected or unimplemented expression statement");
 
     return true;
 }
 
 static bool
-CheckExprStatement(FunctionBuilder& f, ParseNode* exprStmt)
+CheckExprStatement(FunctionValidator& f, ParseNode* exprStmt)
 {
     MOZ_ASSERT(exprStmt->isKind(PNK_SEMI));
     ParseNode* expr = UnaryKid(exprStmt);
 
     if (!expr) {
         f.writeOp(Stmt::Noop);
         return true;
     }
 
     return CheckAsExprStatement(f, expr);
 }
 
-static bool
-CheckWhile(FunctionBuilder& f, ParseNode* whileStmt)
+enum class InterruptCheckPosition {
+    Head,
+    Loop
+};
+
+static void
+MaybeAddInterruptCheck(FunctionValidator& f, InterruptCheckPosition pos, ParseNode* pn)
+{
+    if (f.m().module().usesSignalHandlersForInterrupt())
+        return;
+
+    switch (pos) {
+      case InterruptCheckPosition::Head: f.writeOp(Stmt::InterruptCheckHead); break;
+      case InterruptCheckPosition::Loop: f.writeOp(Stmt::InterruptCheckLoop); break;
+    }
+
+    unsigned lineno = 0, column = 0;
+    f.m().tokenStream().srcCoords.lineNumAndColumnIndex(pn->pn_pos.begin, &lineno, &column);
+    f.writeU32(lineno);
+    f.writeU32(column);
+}
+
+static bool
+EmitInterruptCheck(FunctionCompiler& f)
+{
+    unsigned lineno = f.readU32();
+    unsigned column = f.readU32();
+    f.addInterruptCheck(lineno, column);
+    return true;
+}
+
+static bool
+EmitInterruptCheckLoop(FunctionCompiler& f)
+{
+    if (!EmitInterruptCheck(f))
+        return false;
+    return EmitStatement(f);
+}
+
+static bool
+CheckWhile(FunctionValidator& f, ParseNode* whileStmt)
 {
     MOZ_ASSERT(whileStmt->isKind(PNK_WHILE));
     ParseNode* cond = BinaryLeft(whileStmt);
     ParseNode* body = BinaryRight(whileStmt);
 
     f.writeOp(Stmt::While);
 
     Type condType;
     if (!CheckExpr(f, cond, &condType))
         return false;
     if (!condType.isInt())
         return f.failf(cond, "%s is not a subtype of int", condType.toChars());
 
+    MaybeAddInterruptCheck(f, InterruptCheckPosition::Loop, whileStmt);
+
     return CheckStatement(f, body);
 }
 
 static bool
 EmitWhile(FunctionCompiler& f, const LabelVector* maybeLabels)
 {
     size_t headPc = f.pc();
 
@@ -9188,60 +9179,60 @@ EmitWhile(FunctionCompiler& f, const Lab
     if (!f.startPendingLoop(headPc, &loopEntry))
         return false;
 
     MDefinition* condDef;
     if (!EmitI32Expr(f, &condDef))
         return false;
 
     MBasicBlock* afterLoop;
-    // TODO (bug 1178840) : find afterPos' value
-    size_t bodyPos = f.pc(), afterPos = 0;
-    if (!f.branchAndStartLoopBody(condDef, bodyPos, afterPos, &afterLoop))
+    if (!f.branchAndStartLoopBody(condDef, &afterLoop))
         return false;
 
     if (!EmitStatement(f))
         return false;
 
     if (!f.bindContinues(headPc, maybeLabels))
         return false;
 
     return f.closeLoop(loopEntry, afterLoop);
 }
 
 static bool
-CheckFor(FunctionBuilder& f, ParseNode* forStmt)
+CheckFor(FunctionValidator& f, ParseNode* forStmt)
 {
     MOZ_ASSERT(forStmt->isKind(PNK_FOR));
     ParseNode* forHead = BinaryLeft(forStmt);
     ParseNode* body = BinaryRight(forStmt);
 
     if (!forHead->isKind(PNK_FORHEAD))
         return f.fail(forHead, "unsupported for-loop statement");
 
     ParseNode* maybeInit = TernaryKid1(forHead);
     ParseNode* maybeCond = TernaryKid2(forHead);
     ParseNode* maybeInc = TernaryKid3(forHead);
 
     f.writeOp(maybeInit ? (maybeInc ? Stmt::ForInitInc   : Stmt::ForInitNoInc)
-                       : (maybeInc ? Stmt::ForNoInitInc : Stmt::ForNoInitNoInc));
+                        : (maybeInc ? Stmt::ForNoInitInc : Stmt::ForNoInitNoInc));
 
     if (maybeInit && !CheckAsExprStatement(f, maybeInit))
         return false;
 
     if (maybeCond) {
         Type condType;
         if (!CheckExpr(f, maybeCond, &condType))
             return false;
         if (!condType.isInt())
             return f.failf(maybeCond, "%s is not a subtype of int", condType.toChars());
     } else {
         f.writeInt32Lit(1);
     }
 
+    MaybeAddInterruptCheck(f, InterruptCheckPosition::Loop, forStmt);
+
     if (!CheckStatement(f, body))
         return false;
 
     if (maybeInc && !CheckAsExprStatement(f, maybeInc))
         return false;
 
     f.writeDebugCheckPoint();
     return true;
@@ -9263,19 +9254,17 @@ EmitFor(FunctionCompiler& f, Stmt stmt, 
     if (!f.startPendingLoop(headPc, &loopEntry))
         return false;
 
     MDefinition* condDef;
     if (!EmitI32Expr(f, &condDef))
         return false;
 
     MBasicBlock* afterLoop;
-    // TODO (bug 1178840) : find afterPos' value
-    size_t bodyPos = f.pc(), afterPos = 0;
-    if (!f.branchAndStartLoopBody(condDef, bodyPos, afterPos, &afterLoop))
+    if (!f.branchAndStartLoopBody(condDef, &afterLoop))
         return false;
 
     if (!EmitStatement(f))
         return false;
 
     if (!f.bindContinues(headPc, maybeLabels))
         return false;
 
@@ -9285,24 +9274,26 @@ EmitFor(FunctionCompiler& f, Stmt stmt, 
     }
 
     f.assertDebugCheckPoint();
 
     return f.closeLoop(loopEntry, afterLoop);
 }
 
 static bool
-CheckDoWhile(FunctionBuilder& f, ParseNode* whileStmt)
+CheckDoWhile(FunctionValidator& f, ParseNode* whileStmt)
 {
     MOZ_ASSERT(whileStmt->isKind(PNK_DOWHILE));
     ParseNode* body = BinaryLeft(whileStmt);
     ParseNode* cond = BinaryRight(whileStmt);
 
     f.writeOp(Stmt::DoWhile);
 
+    MaybeAddInterruptCheck(f, InterruptCheckPosition::Loop, cond);
+
     if (!CheckStatement(f, body))
         return false;
 
     Type condType;
     if (!CheckExpr(f, cond, &condType))
         return false;
     if (!condType.isInt())
         return f.failf(cond, "%s is not a subtype of int", condType.toChars());
@@ -9324,21 +9315,21 @@ EmitDoWhile(FunctionCompiler& f, const L
 
     if (!f.bindContinues(headPc, maybeLabels))
         return false;
 
     MDefinition* condDef;
     if (!EmitI32Expr(f, &condDef))
         return false;
 
-    return f.branchAndCloseDoWhileLoop(condDef, loopEntry, f.pc());
-}
-
-static bool
-CheckLabel(FunctionBuilder& f, ParseNode* labeledStmt)
+    return f.branchAndCloseDoWhileLoop(condDef, loopEntry);
+}
+
+static bool
+CheckLabel(FunctionValidator& f, ParseNode* labeledStmt)
 {
     MOZ_ASSERT(labeledStmt->isKind(PNK_LABEL));
     PropertyName* label = LabeledStatementLabel(labeledStmt);
     ParseNode* stmt = LabeledStatementStatement(labeledStmt);
 
     f.writeOp(Stmt::Label);
 
     uint32_t labelId;
@@ -9352,39 +9343,38 @@ CheckLabel(FunctionBuilder& f, ParseNode
 
     f.removeLabel(label);
     return true;
 }
 
 static bool
 EmitLabel(FunctionCompiler& f, LabelVector* maybeLabels)
 {
-    size_t labelPc = f.pc();
     uint32_t labelId = f.readU32();
 
     if (maybeLabels) {
         if (!maybeLabels->append(labelId))
             return false;
         return EmitStatement(f, maybeLabels);
     }
 
-    LabelVector labels(f.cx());
+    LabelVector labels;
     if (!labels.append(labelId))
         return false;
 
     if (!EmitStatement(f, &labels))
         return false;
 
-    return f.bindLabeledBreaks(labelPc, &labels);
+    return f.bindLabeledBreaks(&labels);
 }
 
 static bool EmitStatement(FunctionCompiler& f, Stmt stmt, LabelVector* maybeLabels = nullptr);
 
 static bool
-CheckIf(FunctionBuilder& f, ParseNode* ifStmt)
+CheckIf(FunctionValidator& f, ParseNode* ifStmt)
 {
   recurse:
     size_t opcodeAt = f.tempOp();
 
     MOZ_ASSERT(ifStmt->isKind(PNK_IF));
     ParseNode* cond = TernaryKid1(ifStmt);
     ParseNode* thenStmt = TernaryKid2(ifStmt);
     ParseNode* elseStmt = TernaryKid3(ifStmt);
@@ -9419,29 +9409,26 @@ typedef bool HasElseBlock;
 
 static bool
 EmitIfElse(FunctionCompiler& f, bool hasElse)
 {
     // Handle if/else-if chains using iteration instead of recursion. This
     // avoids blowing the C stack quota for long if/else-if chains and also
     // creates fewer MBasicBlocks at join points (by creating one join block
     // for the entire if/else-if chain).
-    BlockVector thenBlocks(f.cx());
+    BlockVector thenBlocks;
 
   recurse:
     MDefinition* condition;
     if (!EmitI32Expr(f, &condition))
         return false;
 
     MBasicBlock* thenBlock = nullptr;
     MBasicBlock* elseOrJoinBlock = nullptr;
-
-    // TODO (bug 1178840) : find thenPos and elsePos
-    uint32_t thenPos = 0, elsePos = 0;
-    if (!f.branchAndStartThen(condition, thenPos, elsePos, &thenBlock, &elseOrJoinBlock))
+    if (!f.branchAndStartThen(condition, &thenBlock, &elseOrJoinBlock))
         return false;
 
     if (!EmitStatement(f))
         return false;
 
     if (!f.appendThenBlock(&thenBlocks))
         return false;
 
@@ -9456,24 +9443,24 @@ EmitIfElse(FunctionCompiler& f, bool has
         if (nextStmt == Stmt::IfElse) {
             hasElse = true;
             goto recurse;
         }
 
         if (!EmitStatement(f, nextStmt))
             return false;
 
-        return f.joinIfElse(thenBlocks, f.pc());
+        return f.joinIfElse(thenBlocks);
     } else {
         return f.joinIf(thenBlocks, elseOrJoinBlock);
     }
 }
 
 static bool
-CheckCaseExpr(FunctionBuilder& f, ParseNode* caseExpr, int32_t* value)
+CheckCaseExpr(FunctionValidator& f, ParseNode* caseExpr, int32_t* value)
 {
     if (!IsNumericLiteral(f.m(), caseExpr))
         return f.fail(caseExpr, "switch case expression must be an integer literal");
 
     AsmJSNumLit literal = ExtractNumericLiteral(f.m(), caseExpr);
     switch (literal.which()) {
       case AsmJSNumLit::Fixnum:
       case AsmJSNumLit::NegativeInt:
@@ -9488,29 +9475,29 @@ CheckCaseExpr(FunctionBuilder& f, ParseN
       case AsmJSNumLit::Float32x4:
         return f.fail(caseExpr, "switch case expression must be an integer literal");
     }
 
     return true;
 }
 
 static bool
-CheckDefaultAtEnd(FunctionBuilder& f, ParseNode* stmt)
+CheckDefaultAtEnd(FunctionValidator& f, ParseNode* stmt)
 {
     for (; stmt; stmt = NextNode(stmt)) {
         MOZ_ASSERT(stmt->isKind(PNK_CASE) || stmt->isKind(PNK_DEFAULT));
         if (stmt->isKind(PNK_DEFAULT) && NextNode(stmt) != nullptr)
             return f.fail(stmt, "default label must be at the end");
     }
 
     return true;
 }
 
 static bool
-CheckSwitchRange(FunctionBuilder& f, ParseNode* stmt, int32_t* low, int32_t* high,
+CheckSwitchRange(FunctionValidator& f, ParseNode* stmt, int32_t* low, int32_t* high,
                  int32_t* tableLength)
 {
     if (stmt->isKind(PNK_DEFAULT)) {
         *low = 0;
         *high = -1;
         *tableLength = 0;
         return true;
     }
@@ -9535,30 +9522,30 @@ CheckSwitchRange(FunctionBuilder& f, Par
     if (i64 > 4*1024*1024)
         return f.fail(initialStmt, "all switch statements generate tables; this table would be too big");
 
     *tableLength = int32_t(i64);
     return true;
 }
 
 void
-PatchSwitch(FunctionBuilder& f,
+PatchSwitch(FunctionValidator& f,
             size_t hasDefaultAt, bool hasDefault,
             size_t lowAt, int32_t low,
             size_t highAt, int32_t high,
             size_t numCasesAt, uint32_t numCases)
 {
     f.patchU8(hasDefaultAt, uint8_t(hasDefault));
     f.patch32(lowAt, low);
     f.patch32(highAt, high);
     f.patch32(numCasesAt, numCases);
 }
 
 static bool
-CheckSwitch(FunctionBuilder& f, ParseNode* switchStmt)
+CheckSwitch(FunctionValidator& f, ParseNode* switchStmt)
 {
     MOZ_ASSERT(switchStmt->isKind(PNK_SWITCH));
 
     f.writeOp(Stmt::Switch);
     // Has default
     size_t hasDefaultAt = f.tempU8();
     // Low / High / Num cases
     size_t lowAt = f.temp32();
@@ -9635,62 +9622,62 @@ EmitSwitch(FunctionCompiler& f)
     MDefinition* exprDef;
     if (!EmitI32Expr(f, &exprDef))
         return false;
 
     // Switch with no cases
     if (!hasDefault && numCases == 0)
         return true;
 
-    BlockVector cases(f.cx());
+    BlockVector cases;
     if (!cases.resize(high - low + 1))
         return false;
 
     MBasicBlock* switchBlock;
     if (!f.startSwitch(f.pc(), exprDef, low, high, &switchBlock))
         return false;
 
     while (numCases--) {
         int32_t caseValue = f.readI32();
         MOZ_ASSERT(caseValue >= low && caseValue <= high);
         unsigned caseIndex = caseValue - low;
-        if (!f.startSwitchCase(switchBlock, f.pc(), &cases[caseIndex]))
+        if (!f.startSwitchCase(switchBlock, &cases[caseIndex]))
             return false;
         if (!EmitStatement(f))
             return false;
     }
 
     MBasicBlock* defaultBlock;
-    if (!f.startSwitchDefault(switchBlock, &cases, f.pc(), &defaultBlock))
+    if (!f.startSwitchDefault(switchBlock, &cases, &defaultBlock))
         return false;
 
     if (hasDefault && !EmitStatement(f))
         return false;
 
-    return f.joinSwitch(switchBlock, cases, defaultBlock, f.pc());
-}
-
-static bool
-CheckReturnType(FunctionBuilder& f, ParseNode* usepn, RetType retType)
+    return f.joinSwitch(switchBlock, cases, defaultBlock);
+}
+
+static bool
+CheckReturnType(FunctionValidator& f, ParseNode* usepn, RetType retType)
 {
     if (!f.hasAlreadyReturned()) {
         f.setReturnedType(retType);
         return true;
     }
 
     if (f.returnedType() != retType) {
         return f.failf(usepn, "%s incompatible with previous return of type %s",
                        retType.toType().toChars(), f.returnedType().toType().toChars());
     }
 
     return true;
 }
 
 static bool
-CheckReturn(FunctionBuilder& f, ParseNode* returnStmt)
+CheckReturn(FunctionValidator& f, ParseNode* returnStmt)
 {
     ParseNode* expr = ReturnExpr(returnStmt);
 
     f.writeOp(Stmt::Ret);
 
     if (!expr)
         return CheckReturnType(f, returnStmt, RetType::Void);
 
@@ -9745,17 +9732,17 @@ EmitRet(FunctionCompiler& f)
     MDefinition *def = nullptr;
     if (!EmitExpr(f, type, &def))
         return false;
     f.returnExpr(def);
     return true;
 }
 
 static bool
-CheckStatementList(FunctionBuilder& f, ParseNode* stmtList)
+CheckStatementList(FunctionValidator& f, ParseNode* stmtList)
 {
     MOZ_ASSERT(stmtList->isKind(PNK_STATEMENTLIST));
 
     f.writeOp(Stmt::Block);
     f.writeU32(ListLength(stmtList));
 
     for (ParseNode* stmt = ListHead(stmtList); stmt; stmt = NextNode(stmt)) {
         if (!CheckStatement(f, stmt))
@@ -9774,17 +9761,17 @@ EmitBlock(FunctionCompiler& f)
         if (!EmitStatement(f))
             return false;
     }
     f.assertDebugCheckPoint();
     return true;
 }
 
 static bool
-CheckBreakOrContinue(FunctionBuilder& f, PropertyName* maybeLabel,
+CheckBreakOrContinue(FunctionValidator& f, PropertyName* maybeLabel,
                      Stmt withoutLabel, Stmt withLabel)
 {
     if (!maybeLabel) {
         f.writeOp(withoutLabel);
         return true;
     }
 
     f.writeOp(withLabel);
@@ -9812,17 +9799,17 @@ EmitBreak(FunctionCompiler& f, bool hasL
 {
     if (!hasLabel)
         return f.addBreak(nullptr);
     uint32_t labelId = f.readU32();
     return f.addBreak(&labelId);
 }
 
 static bool
-CheckStatement(FunctionBuilder& f, ParseNode* stmt)
+CheckStatement(FunctionValidator& f, ParseNode* stmt)
 {
     JS_CHECK_RECURSION_DONT_REPORT(f.cx(), return f.m().failOverRecursed());
 
     switch (stmt->getKind()) {
       case PNK_SEMI:          return CheckExprStatement(f, stmt);
       case PNK_WHILE:         return CheckWhile(f, stmt);
       case PNK_FOR:           return CheckFor(f, stmt);
       case PNK_DOWHILE:       return CheckDoWhile(f, stmt);
@@ -9844,43 +9831,45 @@ CheckStatement(FunctionBuilder& f, Parse
 static bool
 EmitStatement(FunctionCompiler& f, Stmt stmt, LabelVector* maybeLabels /*= nullptr */)
 {
     if (!f.mirGen().ensureBallast())
         return false;
 
     MDefinition* _;
     switch (stmt) {
-      case Stmt::Block:           return EmitBlock(f);
-      case Stmt::IfThen:          return EmitIfElse(f, HasElseBlock(false));
-      case Stmt::IfElse:          return EmitIfElse(f, HasElseBlock(true));
-      case Stmt::Switch:          return EmitSwitch(f);
-      case Stmt::While:           return EmitWhile(f, maybeLabels);
-      case Stmt::DoWhile:         return EmitDoWhile(f, maybeLabels);
+      case Stmt::Block:              return EmitBlock(f);
+      case Stmt::IfThen:             return EmitIfElse(f, HasElseBlock(false));
+      case Stmt::IfElse:             return EmitIfElse(f, HasElseBlock(true));
+      case Stmt::Switch:             return EmitSwitch(f);
+      case Stmt::While:              return EmitWhile(f, maybeLabels);
+      case Stmt::DoWhile:            return EmitDoWhile(f, maybeLabels);
       case Stmt::ForInitInc:
       case Stmt::ForInitNoInc:
       case Stmt::ForNoInitNoInc:
-      case Stmt::ForNoInitInc:    return EmitFor(f, stmt, maybeLabels);
-      case Stmt::Label:           return EmitLabel(f, maybeLabels);
-      case Stmt::Continue:        return EmitContinue(f, HasLabel(false));
-      case Stmt::ContinueLabel:   return EmitContinue(f, HasLabel(true));
-      case Stmt::Break:           return EmitBreak(f, HasLabel(false));
-      case Stmt::BreakLabel:      return EmitBreak(f, HasLabel(true));
-      case Stmt::Ret:             return EmitRet(f);
-      case Stmt::I32Expr:         return EmitI32Expr(f, &_);
-      case Stmt::F32Expr:         return EmitF32Expr(f, &_);
-      case Stmt::F64Expr:         return EmitF64Expr(f, &_);
-      case Stmt::I32X4Expr:       return EmitI32X4Expr(f, &_);
-      case Stmt::F32X4Expr:       return EmitF32X4Expr(f, &_);
-      case Stmt::CallInternal:    return EmitInternalCall(f, RetType::Void, &_);
-      case Stmt::CallIndirect:    return EmitFuncPtrCall(f, RetType::Void, &_);
-      case Stmt::CallImport:      return EmitFFICall(f, RetType::Void, &_);
-      case Stmt::AtomicsFence:    f.memoryBarrier(MembarFull); return true;
-      case Stmt::Noop:            return true;
-      case Stmt::Id:              return EmitStatement(f);
+      case Stmt::ForNoInitInc:       return EmitFor(f, stmt, maybeLabels);
+      case Stmt::Label:              return EmitLabel(f, maybeLabels);
+      case Stmt::Continue:           return EmitContinue(f, HasLabel(false));
+      case Stmt::ContinueLabel:      return EmitContinue(f, HasLabel(true));
+      case Stmt::Break:              return EmitBreak(f, HasLabel(false));
+      case Stmt::BreakLabel:         return EmitBreak(f, HasLabel(true));
+      case Stmt::Ret:                return EmitRet(f);
+      case Stmt::I32Expr:            return EmitI32Expr(f, &_);
+      case Stmt::F32Expr:            return EmitF32Expr(f, &_);
+      case Stmt::F64Expr:            return EmitF64Expr(f, &_);
+      case Stmt::I32X4Expr:          return EmitI32X4Expr(f, &_);
+      case Stmt::F32X4Expr:          return EmitF32X4Expr(f, &_);
+      case Stmt::CallInternal:       return EmitInternalCall(f, RetType::Void, &_);
+      case Stmt::CallIndirect:       return EmitFuncPtrCall(f, RetType::Void, &_);
+      case Stmt::CallImport:         return EmitFFICall(f, RetType::Void, &_);
+      case Stmt::AtomicsFence:       f.memoryBarrier(MembarFull); return true;
+      case Stmt::Noop:               return true;
+      case Stmt::Id:                 return EmitStatement(f);
+      case Stmt::InterruptCheckHead: return EmitInterruptCheck(f);
+      case Stmt::InterruptCheckLoop: return EmitInterruptCheckLoop(f);
       case Stmt::DebugCheckPoint:
       case Stmt::Bad:             break;
     }
     MOZ_CRASH("unexpected statement");
 }
 
 static bool
 EmitStatement(FunctionCompiler& f, LabelVector* maybeLabels /* = nullptr */)
@@ -10624,32 +10613,35 @@ CheckFunction(ModuleValidator& m, LifoAl
             return false;
         if (validated) {
             *funcOut = nullptr;
             return true;
         }
     }
 
     AsmFunction* asmFunc = lifo.new_<AsmFunction>(lifo);
-    FunctionBuilder f(m, *asmFunc, fn);
+    FunctionValidator f(m, *asmFunc, fn);
     if (!f.init())
         return false;
 
     ParseNode* stmtIter = ListHead(FunctionStatementList(fn));
 
     if (!CheckProcessingDirectives(m, &stmtIter))
         return false;
 
     VarTypeVector argTypes(m.lifo());
     if (!CheckArguments(f, &stmtIter, &argTypes))
         return false;
 
     if (!CheckVariables(f, &stmtIter))
         return false;
 
+    MOZ_ASSERT(!f.startedPacking(), "No bytecode should be written at this point.");
+    MaybeAddInterruptCheck(f, InterruptCheckPosition::Head, fn);
+
     ParseNode* lastNonEmptyStmt = nullptr;
     for (; stmtIter; stmtIter = NextNode(stmtIter)) {
         if (!CheckStatement(f, stmtIter))
             return false;
         if (!IsEmptyStatement(stmtIter))
             lastNonEmptyStmt = stmtIter;
     }
 
@@ -10663,18 +10655,20 @@ CheckFunction(ModuleValidator& m, LifoAl
 
     if (func->defined())
         return m.failName(fn, "function '%s' already defined", FunctionName(fn));
 
     func->define(fn);
 
     func->accumulateCompileTime((PRMJ_Now() - before) / PRMJ_USEC_PER_MSEC);
 
-    if (!asmFunc->finish(func->sig().args(), func->name(), func->funcIndex(), func->srcBegin(),
-                         func->compileTime()))
+    unsigned lineno, column;
+    m.tokenStream().srcCoords.lineNumAndColumnIndex(func->srcBegin(), &lineno, &column);
+    if (!asmFunc->finish(func->sig().args(), func->name(), func->funcIndex(),
+                         func->srcBegin(), lineno, column, func->compileTime()))
     {
         return false;
     }
 
     m.parser().release(mark);
 
     *funcOut = asmFunc;
     return true;
@@ -10733,17 +10727,17 @@ CheckAllFunctionsDefined(ModuleValidator
 static bool
 CheckFunctionsSequential(ModuleValidator& m, ScopedJSDeletePtr<ModuleCompileResults>* compileResults)
 {
     // Use a single LifoAlloc to allocate all the temporary compiler IR.
     // All allocated LifoAlloc'd memory is released after compiling each
     // function by the LifoAllocScope inside the loop.
     LifoAlloc lifo(LIFO_ALLOC_PRIMARY_CHUNK_SIZE);
 
-    ModuleCompiler mc(m.cx(), m.parser().tokenStream, m.compileInputs());
+    ModuleCompiler mc(m.compileInputs());
 
     while (true) {
         TokenKind tk;
         if (!PeekToken(m.parser(), &tk))
             return false;
         if (tk != TOK_FUNCTION)
             break;
 
@@ -10863,17 +10857,17 @@ GetUsedTask(ModuleCompiler& m, ParallelG
     if (!task)
         return false;
 
     auto& func = *reinterpret_cast<AsmFunction*>(task->func);
     func.accumulateCompileTime(task->compileTime);
 
     {
         // Perform code generation on the main thread.
-        JitContext jitContext(m.cx(), &task->mir->alloc());
+        JitContext jitContext(m.runtime(), /* CompileCompartment = */ nullptr, &task->mir->alloc());
         if (!GenerateCode(m, func, *task->mir, *task->lir))
             return false;
     }
 
     group.compiledJobs++;
 
     // Clear the LifoAlloc for use by another helper.
     TempAllocator& tempAlloc = task->mir->alloc();
@@ -10904,17 +10898,17 @@ CheckFunctionsParallel(ModuleValidator& 
     {
         AutoLockHelperThreadState lock;
         MOZ_ASSERT(HelperThreadState().asmJSWorklist().empty());
         MOZ_ASSERT(HelperThreadState().asmJSFinishedList().empty());
     }
 #endif
     HelperThreadState().resetAsmJSFailureState();
 
-    ModuleCompiler mc(m.cx(), m.parser().tokenStream, m.compileInputs());
+    ModuleCompiler mc(m.compileInputs());
 
     AsmJSParallelTask* task = nullptr;
     for (unsigned i = 0;; i++) {
         TokenKind tk;
         if (!PeekToken(m.parser(), &tk))
             return false;
         if (tk != TOK_FUNCTION)
             break;
--- a/js/src/builtin/AtomicsObject.cpp
+++ b/js/src/builtin/AtomicsObject.cpp
@@ -50,16 +50,17 @@
 #include "mozilla/Atomics.h"
 #include "mozilla/FloatingPoint.h"
 
 #include "jsapi.h"
 #include "jsfriendapi.h"
 
 #include "asmjs/AsmJSModule.h"
 #include "jit/AtomicOperations.h"
+#include "jit/InlinableNatives.h"
 #include "js/Class.h"
 #include "vm/GlobalObject.h"
 #include "vm/SharedTypedArrayObject.h"
 #include "vm/Time.h"
 #include "vm/TypedArrayObject.h"
 
 #include "jsobjinlines.h"
 #include "jit/AtomicOperations-inl.h"
@@ -1209,30 +1210,30 @@ js::FutexRuntime::wake(WakeReason reason
         break;
       default:
         MOZ_CRASH();
     }
     PR_NotifyCondVar(cond_);
 }
 
 const JSFunctionSpec AtomicsMethods[] = {
-    JS_FN("compareExchange",    atomics_compareExchange,    4,0),
-    JS_FN("load",               atomics_load,               2,0),
-    JS_FN("store",              atomics_store,              3,0),
-    JS_FN("exchange",           atomics_exchange,           3,0),
-    JS_FN("fence",              atomics_fence,              0,0),
-    JS_FN("add",                atomics_add,                3,0),
-    JS_FN("sub",                atomics_sub,                3,0),
-    JS_FN("and",                atomics_and,                3,0),
-    JS_FN("or",                 atomics_or,                 3,0),
-    JS_FN("xor",                atomics_xor,                3,0),
-    JS_FN("isLockFree",         atomics_isLockFree,         1,0),
-    JS_FN("futexWait",          atomics_futexWait,          4,0),
-    JS_FN("futexWake",          atomics_futexWake,          3,0),
-    JS_FN("futexWakeOrRequeue", atomics_futexWakeOrRequeue, 5,0),
+    JS_INLINABLE_FN("compareExchange",    atomics_compareExchange,    4,0, AtomicsCompareExchange),
+    JS_INLINABLE_FN("load",               atomics_load,               2,0, AtomicsLoad),
+    JS_INLINABLE_FN("store",              atomics_store,              3,0, AtomicsStore),
+    JS_INLINABLE_FN("exchange",           atomics_exchange,           3,0, AtomicsExchange),
+    JS_INLINABLE_FN("fence",              atomics_fence,              0,0, AtomicsFence),
+    JS_INLINABLE_FN("add",                atomics_add,                3,0, AtomicsAdd),
+    JS_INLINABLE_FN("sub",                atomics_sub,                3,0, AtomicsSub),
+    JS_INLINABLE_FN("and",                atomics_and,                3,0, AtomicsAnd),
+    JS_INLINABLE_FN("or",                 atomics_or,                 3,0, AtomicsOr),
+    JS_INLINABLE_FN("xor",                atomics_xor,                3,0, AtomicsXor),
+    JS_INLINABLE_FN("isLockFree",         atomics_isLockFree,         1,0, AtomicsIsLockFree),
+    JS_FN("futexWait",                    atomics_futexWait,          4,0),
+    JS_FN("futexWake",                    atomics_futexWake,          3,0),
+    JS_FN("futexWakeOrRequeue",           atomics_futexWakeOrRequeue, 5,0),
     JS_FS_END
 };
 
 static const JSConstDoubleSpec AtomicsConstants[] = {
     {"OK",       AtomicsObject::FutexOK},
     {"TIMEDOUT", AtomicsObject::FutexTimedout},
     {"NOTEQUAL", AtomicsObject::FutexNotequal},
     {0,          0}
--- a/js/src/builtin/ModuleObject.cpp
+++ b/js/src/builtin/ModuleObject.cpp
@@ -255,16 +255,17 @@ ModuleObject::isInstance(HandleValue val
 ModuleObject::create(ExclusiveContext* cx)
 {
     return NewBuiltinClassInstance<ModuleObject>(cx, TenuredObject);
 }
 
 void
 ModuleObject::init(HandleScript script)
 {
+    MOZ_ASSERT(!script->enclosingStaticScope());
     initReservedSlot(ScriptSlot, PrivateValue(script));
 }
 
 void
 ModuleObject::setInitialEnvironment(HandleModuleEnvironmentObject initialEnvironment)
 {
     initReservedSlot(InitialEnvironmentSlot, ObjectValue(*initialEnvironment));
 }
@@ -278,35 +279,54 @@ ModuleObject::initImportExportData(Handl
 {
     initReservedSlot(RequestedModulesSlot, ObjectValue(*requestedModules));
     initReservedSlot(ImportEntriesSlot, ObjectValue(*importEntries));
     initReservedSlot(LocalExportEntriesSlot, ObjectValue(*localExportEntries));
     initReservedSlot(IndirectExportEntriesSlot, ObjectValue(*indirectExportEntries));
     initReservedSlot(StarExportEntriesSlot, ObjectValue(*starExportEntries));
 }
 
+bool
+ModuleObject::hasScript() const
+{
+    // When modules are parsed via the Reflect.parse() API, the module object
+    // doesn't have a script.
+    return !getReservedSlot(ScriptSlot).isUndefined();
+}
+
 JSScript*
 ModuleObject::script() const
 {
     return static_cast<JSScript*>(getReservedSlot(ScriptSlot).toPrivate());
 }
 
 ModuleEnvironmentObject&
 ModuleObject::initialEnvironment() const
 {
     return getReservedSlot(InitialEnvironmentSlot).toObject().as<ModuleEnvironmentObject>();
 }
 
+JSObject*
+ModuleObject::enclosingStaticScope() const
+{
+    // A ModuleObject is always the last thing on the scope chain before the global.
+    // TODO: This may no longer be true when we get top-level lexical scopes.
+    MOZ_ASSERT_IF(hasScript(), !script()->enclosingStaticScope());
+    return nullptr;
+}
+
 /* static */ void
 ModuleObject::trace(JSTracer* trc, JSObject* obj)
 {
     ModuleObject& module = obj->as<ModuleObject>();
-    JSScript* script = module.script();
-    TraceManuallyBarrieredEdge(trc, &script, "Module script");
-    module.setReservedSlot(ScriptSlot, PrivateValue(script));
+    if (module.hasScript()) {
+        JSScript* script = module.script();
+        TraceManuallyBarrieredEdge(trc, &script, "Module script");
+        module.setReservedSlot(ScriptSlot, PrivateValue(script));
+    }
 }
 
 DEFINE_GETTER_FUNCTIONS(ModuleObject, initialEnvironment, InitialEnvironmentSlot)
 DEFINE_GETTER_FUNCTIONS(ModuleObject, requestedModules, RequestedModulesSlot)
 DEFINE_GETTER_FUNCTIONS(ModuleObject, importEntries, ImportEntriesSlot)
 DEFINE_GETTER_FUNCTIONS(ModuleObject, localExportEntries, LocalExportEntriesSlot)
 DEFINE_GETTER_FUNCTIONS(ModuleObject, indirectExportEntries, IndirectExportEntriesSlot)
 DEFINE_GETTER_FUNCTIONS(ModuleObject, starExportEntries, StarExportEntriesSlot)
@@ -480,20 +500,20 @@ ModuleBuilder::processImport(frontend::P
     }
 
     return true;
 }
 
 bool
 ModuleBuilder::processExport(frontend::ParseNode* pn)
 {
-    MOZ_ASSERT(pn->isArity(PN_UNARY));
+    MOZ_ASSERT(pn->getArity() == pn->isKind(PNK_EXPORT) ? PN_UNARY : PN_BINARY);
 
-    ParseNode* kid = pn->pn_kid;
     bool isDefault = pn->getKind() == PNK_EXPORT_DEFAULT;
+    ParseNode* kid = isDefault ? pn->pn_left : pn->pn_kid;
 
     switch (kid->getKind()) {
       case PNK_EXPORT_SPEC_LIST:
         MOZ_ASSERT(!isDefault);
         for (ParseNode* spec = kid->pn_head; spec; spec = spec->pn_next) {
             MOZ_ASSERT(spec->isKind(PNK_EXPORT_SPEC));
             RootedAtom localName(cx_, spec->pn_left->pn_atom);
             RootedAtom exportName(cx_, spec->pn_right->pn_atom);
--- a/js/src/builtin/ModuleObject.h
+++ b/js/src/builtin/ModuleObject.h
@@ -100,19 +100,22 @@ class ModuleObject : public NativeObject
 
     JSScript* script() const;
     ModuleEnvironmentObject& initialEnvironment() const;
     ArrayObject& requestedModules() const;
     ArrayObject& importEntries() const;
     ArrayObject& localExportEntries() const;
     ArrayObject& indirectExportEntries() const;
     ArrayObject& starExportEntries() const;
+    JSObject* enclosingStaticScope() const;
 
   private:
     static void trace(JSTracer* trc, JSObject* obj);
+
+    bool hasScript() const;
 };
 
 typedef Rooted<ModuleObject*> RootedModuleObject;
 typedef Handle<ModuleObject*> HandleModuleObject;
 
 // Process a module's parse tree to collate the import and export data used when
 // creating a ModuleObject.
 class MOZ_STACK_CLASS ModuleBuilder
--- a/js/src/builtin/ReflectParse.cpp
+++ b/js/src/builtin/ReflectParse.cpp
@@ -28,16 +28,22 @@
 
 using namespace js;
 using namespace js::frontend;
 
 using JS::AutoValueArray;
 using mozilla::ArrayLength;
 using mozilla::DebugOnly;
 
+enum class ParseTarget
+{
+    Script,
+    Module
+};
+
 enum ASTType {
     AST_ERROR = -1,
 #define ASTDEF(ast, str, method) ast,
 #include "jsast.tbl"
 #undef ASTDEF
     AST_LIMIT
 };
 
@@ -2297,22 +2303,23 @@ ASTSerializer::importSpecifier(ParseNode
 }
 
 bool
 ASTSerializer::exportDeclaration(ParseNode* pn, MutableHandleValue dst)
 {
     MOZ_ASSERT(pn->isKind(PNK_EXPORT) ||
                pn->isKind(PNK_EXPORT_FROM) ||
                pn->isKind(PNK_EXPORT_DEFAULT));
+    MOZ_ASSERT(pn->getArity() == pn->isKind(PNK_EXPORT) ? PN_UNARY : PN_BINARY);
     MOZ_ASSERT_IF(pn->isKind(PNK_EXPORT_FROM), pn->pn_right->isKind(PNK_STRING));
 
     RootedValue decl(cx, NullValue());
     NodeVector elts(cx);
 
-    ParseNode* kid = pn->isKind(PNK_EXPORT_FROM) ? pn->pn_left: pn->pn_kid;
+    ParseNode* kid = pn->isKind(PNK_EXPORT) ? pn->pn_kid : pn->pn_left;
     switch (ParseNodeKind kind = kid->getKind()) {
       case PNK_EXPORT_SPEC_LIST:
         if (!elts.reserve(pn->pn_left->pn_count))
             return false;
 
         for (ParseNode* next = pn->pn_left->pn_head; next; next = next->pn_next) {
             RootedValue elt(cx);
             if (next->isKind(PNK_EXPORT_SPEC)) {
@@ -2335,17 +2342,17 @@ ASTSerializer::exportDeclaration(ParseNo
         if (!classDefinition(kid, false, &decl))
             return false;
         break;
 
       case PNK_VAR:
       case PNK_CONST:
       case PNK_GLOBALCONST:
       case PNK_LET:
-        if (!variableDeclaration(kid, kind == PNK_LET, &decl))
+        if (!variableDeclaration(kid, (kind == PNK_LET || kind == PNK_CONST), &decl))
             return false;
         break;
 
       default:
           if (!expression(kid, &decl))
               return false;
           break;
     }
@@ -3711,18 +3718,18 @@ reflect_parse(JSContext* cx, uint32_t ar
 
     RootedString src(cx, ToString<CanGC>(cx, args[0]));
     if (!src)
         return false;
 
     ScopedJSFreePtr<char> filename;
     uint32_t lineno = 1;
     bool loc = true;
-
     RootedObject builder(cx);
+    ParseTarget target = ParseTarget::Script;
 
     RootedValue arg(cx, args.get(1));
 
     if (!arg.isNullOrUndefined()) {
         if (!arg.isObject()) {
             ReportValueErrorFlags(cx, JSREPORT_ERROR, JSMSG_UNEXPECTED_TYPE,
                                   JSDVG_SEARCH_STACK, arg, nullptr,
                                   "not an object", nullptr);
@@ -3777,16 +3784,46 @@ reflect_parse(JSContext* cx, uint32_t ar
             if (!prop.isObject()) {
                 ReportValueErrorFlags(cx, JSREPORT_ERROR, JSMSG_UNEXPECTED_TYPE,
                                       JSDVG_SEARCH_STACK, prop, nullptr,
                                       "not an object", nullptr);
                 return false;
             }
             builder = &prop.toObject();
         }
+
+        /* config.target */
+        RootedId targetId(cx, NameToId(cx->names().target));
+        RootedValue scriptVal(cx, StringValue(cx->names().script));
+        if (!GetPropertyDefault(cx, config, targetId, scriptVal, &prop))
+            return false;
+
+        if (!prop.isString()) {
+            ReportValueErrorFlags(cx, JSREPORT_ERROR, JSMSG_UNEXPECTED_TYPE, JSDVG_SEARCH_STACK,
+                                  prop, nullptr, "not 'script' or 'module'", nullptr);
+            return false;
+        }
+
+        RootedString stringProp(cx, prop.toString());
+        bool isScript = false;
+        bool isModule = false;
+        if (!EqualStrings(cx, stringProp, cx->names().script, &isScript))
+            return false;
+
+        if (!EqualStrings(cx, stringProp, cx->names().module, &isModule))
+            return false;
+
+        if (isScript) {
+            target = ParseTarget::Script;
+        } else if (isModule) {
+            target = ParseTarget::Module;
+        } else {
+            JS_ReportError(cx, "Bad target value, expected 'script' or 'module'");
+            return false;
+        }
     }
 
     /* Extract the builder methods first to report errors before parsing. */
     ASTSerializer serialize(cx, loc, filename, lineno);
     if (!serialize.init(builder))
         return false;
 
     JSLinearString* linear = src->ensureLinear(cx);
@@ -3803,19 +3840,33 @@ reflect_parse(JSContext* cx, uint32_t ar
     mozilla::Range<const char16_t> chars = linearChars.twoByteRange();
     Parser<FullParseHandler> parser(cx, &cx->tempLifoAlloc(), options, chars.start().get(),
                                     chars.length(), /* foldConstants = */ false, nullptr, nullptr);
     if (!parser.checkOptions())
         return false;
 
     serialize.setParser(&parser);
 
-    ParseNode* pn = parser.parse();
-    if (!pn)
-        return false;
+    ParseNode* pn;
+    if (target == ParseTarget::Script) {
+        pn = parser.parse();
+        if (!pn)
+            return false;
+    } else {
+        Rooted<ModuleObject*> module(cx, ModuleObject::create(cx));
+        if (!module)
+            return false;
+
+        pn = parser.standaloneModule(module);
+        if (!pn)
+            return false;
+
+        MOZ_ASSERT(pn->getKind() == PNK_MODULE);
+        pn = pn->pn_body;
+    }
 
     RootedValue val(cx);
     if (!serialize.program(pn, &val)) {
         args.rval().setNull();
         return false;
     }
 
     args.rval().set(val);
--- a/js/src/builtin/RegExp.cpp
+++ b/js/src/builtin/RegExp.cpp
@@ -6,16 +6,17 @@
 
 #include "builtin/RegExp.h"
 
 #include "mozilla/TypeTraits.h"
 
 #include "jscntxt.h"
 
 #include "irregexp/RegExpParser.h"
+#include "jit/InlinableNatives.h"
 #include "vm/RegExpStatics.h"
 #include "vm/StringBuffer.h"
 
 #include "jsobjinlines.h"
 
 #include "vm/NativeObject-inl.h"
 
 using namespace js;
@@ -555,18 +556,18 @@ const JSPropertySpec js::regexp_properti
 };
 
 const JSFunctionSpec js::regexp_methods[] = {
 #if JS_HAS_TOSOURCE
     JS_SELF_HOSTED_FN(js_toSource_str, "RegExpToString", 0, 0),
 #endif
     JS_SELF_HOSTED_FN(js_toString_str, "RegExpToString", 0, 0),
     JS_FN("compile",        regexp_compile,     2,0),
-    JS_FN("exec",           regexp_exec,        1,0),
-    JS_FN("test",           regexp_test,        1,0),
+    JS_INLINABLE_FN("exec", regexp_exec,        1,0, RegExpExec),
+    JS_INLINABLE_FN("test", regexp_test,        1,0, RegExpTest),
     JS_FS_END
 };
 
 #define STATIC_PAREN_GETTER_CODE(parenNum)                                      \
     if (!res->createParen(cx, parenNum, args.rval()))                           \
         return false;                                                           \
     if (args.rval().isUndefined())                                              \
         args.rval().setString(cx->runtime()->emptyString);                      \
--- a/js/src/devtools/rootAnalysis/annotations.js
+++ b/js/src/devtools/rootAnalysis/annotations.js
@@ -33,17 +33,17 @@ function indirectCallCannotGC(fullCaller
         return true;
 
     if (name == "params" && caller == "PR_ExplodeTime")
         return true;
 
     if (name == "op" && /GetWeakmapKeyDelegate/.test(caller))
         return true;
 
-    var CheckCallArgs = "AsmJSValidate.cpp:uint8 CheckCallArgs(FunctionBuilder*, js::frontend::ParseNode*, (uint8)(FunctionBuilder*,js::frontend::ParseNode*,Type)*, Signature*)";
+    var CheckCallArgs = "AsmJSValidate.cpp:uint8 CheckCallArgs(FunctionValidator*, js::frontend::ParseNode*, (uint8)(FunctionValidator*,js::frontend::ParseNode*,Type)*, Signature*)";
     if (name == "checkArg" && caller == CheckCallArgs)
         return true;
 
     // hook called during script finalization which cannot GC.
     if (/CallDestroyScriptHook/.test(caller))
         return true;
 
     // template method called during marking and hence cannot GC
--- a/js/src/doc/JITOptimizations/Outcomes.md
+++ b/js/src/doc/JITOptimizations/Outcomes.md
@@ -16,85 +16,145 @@ General outcomes shared between various 
 The optimization attempt failed, and the reason was not recorded.
 
 ### GenericSuccess
 
 Optimization succeeded.
 
 ### Disabled
 
+The optimization has been explicitly disallowed.
+
 ### NoTypeInfo
 
 Optimization failed because there was no type information associated with
 object containing the property. This failure mode is unlikely, and occurs
 if the target object is obtained in some roundabout way.
 
 ### NoAnalysisInfo
+
+TODO
+
 ### NoShapeInfo
+
+The baseline compiler recorded no usable shape information for this operation.
+
 ### UnknownObject
-### UnknownProperties
+
+The type of the object is not known.  This can happen if the operation sees many different types of objects, and so the type of the input to the operation cannot be resolved to a single type.
 
 ### UnknownProperties
 
 Optimization failed because the object containing the property was marked
 as having unknown properties. This can happen if too many properties are
 defined on the object, or if `delete` is used to remove one of the object's
 properties.
 
 ### Singleton
-### NotSingleton
+
+One of the types present in the typeset was a singleton type, preventing the optimization from being enabled.
 
 ### NotSingleton
 
 Optimization failed because the object containing the property did not
 have a 'singleton' type. Singleton types are assigned to objects that are
 "one of a kind", such as global objects, literal objects declared in the
 global scope, and top-level function objects.
 
 ### NotFixedSlot
+
+The property being accessed is not stored at a known location in the object.  This can occur if one of the expected types of objects to be used in this operation has unknown properties, or if different instances of the object store the property at different locations (for example, some instances have the property assigned in a different order than others).
+
 ### InconsistentFixedSlot
 
+The property being accessed is not stored at a known location in the object.  This can occur if the operation is polymorphic on different object types and one or more of the object types contain the property at a different slot than the others.
+
 ### NotObject
 
 Optimization failed because the stored in the property could potentially
 be a non-object value.  Since only objects can be uniquely typed, the
 optimization strategy fails in this case.
 
 ### NotStruct
 
 The object holding the property is not a typed struct object.
 
 ### NotUnboxed
+
+The object whose property is being accessed is not formatted as an
+unboxed object.
+
 ### UnboxedConvertedToNative
 
+The object whose property is being accessed was previously unboxed,
+but was deoptimized and converted to a native object.
+
 ### StructNoField
 
-The property being accessed does not correspond to a field on typed
+The unboxed property being accessed does not correspond to a field on typed
 object.
 
 ### InconsistentFieldType
+
+The type of an unboxed field is not consistent across all the different types of objects it could be accessed from.
+
 ### InconsistentFieldOffset
 
+The offset of an unboxed field is not consistent across all the different types of objects it could be accessed from.
+
 ### NeedsTypeBarrier
 
 Optimization failed because somehow the property was accessed in a way
 that returned a different type than the expected constant. This is an
 unlikely failure mode, and should not occur.
 
 ### InDictionaryMode
+
+The object whose property is being accessed is in dictionary mode.  Objects which are used in ways that suggest they are hashtables, are turned into dictionary objects and their types marked as such.
+
 ### NoProtoFound
+
+A prototype object was not found for all the object used by this operation.
+
 ### MultiProtoPaths
+
+Objects used in this operation had differing prototypes.
+
 ### NonWritableProperty
+
+The property being assigned to is not writable for some types of objects which are used in this operation.
+
 ### ProtoIndexedProps
+
+The object being accessed has indexed properties that are exotic (for example, defined as a property on a prototype object and left as a hole in the underlying object).
+
 ### ArrayBadFlags
+
+The array being accessed may have flags that the optimization strategy cannot handle.  For example, if the array has sparse indexes, or has indexes that overflow the array's length, the optimization strategy may fail.
+
 ### ArrayDoubleConversion
+
+The type-system indicates that some arrays at this site should be converted to packed arrays of doubles, while others should not.  The optimization strategy fails for this condition.
+
 ### ArrayRange
+
+Could not accurately calculate the range attributes of an inline array creation.
+
 ### ArraySeenNegativeIndex
+
+Arrays at this element access location have seen negative indexes.
+
 ### TypedObjectNeutered
+
+The typed object being accessed at this location may have been neutered (a neutered typed object is one where the underlying byte buffer has been removed or transferred to a worker).
+
 ### TypedObjectArrayRange
+
+Failed to do range check of element access on a typed object.
+
 ### AccessNotDense
 
 ### AccessNotSimdObject
 
 The observed type of the target of the property access doesn't guarantee
 that it is a SIMD object.
 
 ### AccessNotTypedObject
--- a/js/src/doc/JITOptimizations/Strategies.md
+++ b/js/src/doc/JITOptimizations/Strategies.md
@@ -2,34 +2,34 @@
 
 SpiderMonkey's optimizing JIT, IonMonkey, uses a number of different
 optimization strategies to speed up various operations. The most commonplace
 operations that are relevant for fast program execution are property accesses
 and function calls.
 
 Optimization information is currently collected for the following operations:
 
-- [GetProperty][getprop] (`obj.prop`)
-- [SetProperty][setprop] (`obj.prop = val`)
-- [GetElement][getelem] (`obj[elemName]`)
-- [SetElement][setelem] (`obj[elemName] = val`)
-- [Call][call] (`func(...)`)
+- [GetProperty](#getprop) (`obj.prop`)
+- [SetProperty](#setprop) (`obj.prop = val`)
+- [GetElement](#getelem) (`obj[elemName]`)
+- [SetElement](#setelem) (`obj[elemName] = val`)
+- [Call](#call) (`func(...)`)
 
 At each operation site, IonMonkey tries a battery of <i>strategies</i>, from
 the most optimized but most restrictive to the least optimized but least
 restrictive. For each strategy attempted, its <i>outcome</i> is tracked. An
 outcome is either success or a reason why the strategy failed to apply.
 
 This page documents the various optimization strategies and their outcomes. It
 provides information on what they attempt to do, what general level of
 speed-up they provide, what kind of program characteristics can prevent them
 from being used, and common ways to enable the engine to utilize that
 optimization.
 
-## GetProperty
+## <a name="getprop"></a>GetProperty
 
 ### GetProperty_ArgumentsLength
 
 Attempts to optimize an `arguments.length` property access. This optimization
 only works if the arguments object is used in well-understood ways within the
 function. The function containing the arguments.length is allowed to use the
 arguments object in the following ways without disabling this optimization:
 
@@ -73,37 +73,33 @@ applies to property accesses on objects 
 only one instance of them per program. This includes global objects, object
 literals defined at the top-level of a script, and top-level function objects.
 
 This optimization makes the assumption that a property that has not changed
 after it was first assigned, is likely a constant property.  It then directly
 inlines the value of the property into hot code that accesses it. For
 example, in the following code:
 
-```language-js
-var Constants = {};
-Constants.N = 100;
+    var Constants = {};
+    Constants.N = 100;
 
-function testArray(array) {
-  for (var i = 0; i < array.length; i++) {
-    if (array[i] > Constants.N)
-      return true;
-  }
-  return false;
-}
-```
+    function testArray(array) {
+      for (var i = 0; i < array.length; i++) {
+        if (array[i] > Constants.N)
+          return true;
+      }
+      return false;
+    }
 
 Will have the loop compiled into the following when `testArray` gets hot.
 
-```language-js
-for (var i = 0; i < array.length; i++) {
-  if (array[i] > 100)
-    return true;
-}
-```
+    for (var i = 0; i < array.length; i++) {
+      if (array[i] > 100)
+        return true;
+    }
 
 When this optimization is successful, property access is eliminated entirely
 and replaced with an inline constant.
 
 ### GetProp_Constant
 
 Attempts to optimize reading a property that contains a uniquely-typed (or
 "singleton") object.  With uniquely-typed objects, it is guaranteed that
@@ -140,24 +136,22 @@ optimization to succeed, the property ne
 For objects constructed by constructor functions, this means that the property
 needs to be defined in the constructor, before any complex logic occurs within
 the constructor.
 
 This is the best case for a regular "field" type property that is not
 turned into a constant.  It compiles down to a single CPU-level load
 instruction.
 
-```language-js
-function SomeConstructor() {
-    this.x = 10;    // x is a definite slot property
-    this.y = 10;    // y is a definite slot property
-    someComplicatedFunctionCall();
-    this.z = 20;    // z is not a definite slot property.
-}
-```
+    function SomeConstructor() {
+        this.x = 10;    // x is a definite slot property
+        this.y = 10;    // y is a definite slot property
+        someComplicatedFunctionCall();
+        this.z = 20;    // z is not a definite slot property.
+    }
 
 In the above example, the properties `x` and `y` can be determined to always
 exist on any instance of `SomeConstructor` at definite locations, allowing
 the engine to deterministically infer the position of `x` without a shape
 guard.
 
 This optimization can fail for a number of reasons.  If the types observed
 at the property access are polymorphic (more than one type), this optimization
@@ -168,22 +162,20 @@ slot as described above.
 ### GetProp_Unboxed
 
 Similar to `GetProp_DefiniteSlot`.  Unboxed property reads are possible on
 properties which satisfy all the characteristics of a definite slot, and
 additionally have been observed to only store values of one kind of value.
 
 Consider the following constructor:
 
-```language-js
-function Point(x, y) {
-    this.x = x;
-    this.y = y;
-}
-```
+    function Point(x, y) {
+        this.x = x;
+        this.y = y;
+    }
 
 If only integers are ever stored in the `x` and `y` properties,
 then the instances of `Point` will be represented in an "unboxed" mode -
 with the property values stored as raw 4-byte values within the object.
 
 Objects which have the unboxed optimization are more compact.
 
 ### GetProp_CommonGetter
@@ -192,28 +184,26 @@ Optimizes access to properties which are
 where the getter is shared between multiple types.
 
 This optimization applies most often when the property access site is
 polymorphic, but all the object types are derived variants of a single
 base class, where the property access refers to a getter on the base
 class.
 
 Consider the following example:
-```language-js
-function Base() {}
-Base.prototype = {
-    get x() { return 3; }
-};
+    function Base() {}
+    Base.prototype = {
+        get x() { return 3; }
+    };
 
-function Derived1() {}
-Derived1.prototype = Object.create(Base.prototype);
+    function Derived1() {}
+    Derived1.prototype = Object.create(Base.prototype);
 
-function Derived2() {}
-Derived1.prototype = Object.create(Base.prototype);
-```
+    function Derived2() {}
+    Derived1.prototype = Object.create(Base.prototype);
 
 If a property access for `d.x` sees only instances of both `Derived1` and
 `Derived2` for `d`, it can optimize the access to `x` to a call to the
 getter function defined on `Base`.
 
 This optimization applies to shared getters on both pure JS objects as well
 as DOM objects.
 
@@ -229,26 +219,24 @@ GetProp_DefiniteProperty), then this opt
 
 Alternatively, if the property access is polymorphic, but only has a few
 different shapes observed at the access site, this optimization may be used.
 
 This optimization compiles down to one-or more shape-guarded direct loads
 from the object.  The following pseudocode describes the kind of machine
 code generated by this optimization:
 
-```
-if obj.shape == Shape1 then
-  obj.slots[0]
-elif obj.shape == Shape2 then
-  obj.slots[5]
-elif obj.shape == Shape3 then
-  obj.slots[2]
-...
-end
-```
+    if obj.shape == Shape1 then
+      obj.slots[0]
+    elif obj.shape == Shape2 then
+      obj.slots[5]
+    elif obj.shape == Shape3 then
+      obj.slots[2]
+    ...
+    end
 
 ### GetProp_Innerize
 
 Attempts to optimize a situation where a property access of the form
 `window.PROP` can be directly translated into a property access on
 the inner global object.
 
 This optimization will always fail on property accesses which are
@@ -270,41 +258,39 @@ can be optimized in that particular case
 "stub" (or freestanding piece of jitcode) and changes the inline cache
 to jump to the stub.  The stub attempts to optimize further occurrences
 of that same kind of operation.
 
 Inline caches are an order of magnitude slower than the other optimization
 strategies, and are an indication that the type inference engine has
 failed to collect enough information to guide the optimization process.
 
-## SetProperty
+## <a name="setprop"></a>SetProperty
 
 ### SetProp_CommonSetter
 
 Optimizes access to properties which are implemented by a setter function,
 where the setter is shared between multiple types.
 
 This optimization applies most often when the property access site is
 polymorphic, but all the object types are derived variants of a single
 base class, where the property access refers to a setter on the base
 class.
 
 Consider the following example:
-```language-js
-function Base() {}
-Base.prototype = {
-    set x(val) { ... }
-};
+    function Base() {}
+    Base.prototype = {
+        set x(val) { ... }
+    };
 
-function Derived1() {}
-Derived1.prototype = Object.create(Base.prototype);
+    function Derived1() {}
+    Derived1.prototype = Object.create(Base.prototype);
 
-function Derived2() {}
-Derived1.prototype = Object.create(Base.prototype);
-```
+    function Derived2() {}
+    Derived1.prototype = Object.create(Base.prototype);
 
 If a property write for `d.x = val` sees only instances of both `Derived1` and
 `Derived2` for `d`, it can optimize the write to `x` to a call to the
 setter function defined on `Base`.
 
 This optimization applies to shared setters on both pure JS objects as well
 as DOM objects.
 
@@ -319,24 +305,22 @@ optimization to succeed, the property ne
 For objects constructed by constructor functions, this means that the property
 needs to be defined in the constructor, before any complex logic occurs within
 the constructor.
 
 This is the best case for a regular "field" type property that is not
 turned into a constant.  It compiles down to a single CPU-level load
 instruction.
 
-```language-js
-function SomeConstructor() {
-    this.x = 10;    // x is a definite slot property
-    this.y = 10;    // y is a definite slot property
-    someComplicatedFunctionCall();
-    this.z = 20;    // z is not a definite slot property.
-}
-```
+    function SomeConstructor() {
+        this.x = 10;    // x is a definite slot property
+        this.y = 10;    // y is a definite slot property
+        someComplicatedFunctionCall();
+        this.z = 20;    // z is not a definite slot property.
+    }
 
 In the above example, the properties `x` and `y` can be determined to always
 exist on any instance of `SomeConstructor` at definite locations, allowing
 the engine to deterministically infer the position of `x` without a shape
 guard.
 
 This optimization can fail for a number of reasons.  If the types observed
 at the property access are polymorphic (more than one type), this optimization
@@ -347,22 +331,20 @@ slot as described above.
 ### SetProp_Unboxed
 
 Similar to `SetProp_DefiniteSlot`.  Unboxed property writes are possible on
 properties which satisfy all the characteristics of a definite slot, and
 additionally have been observed to only store values of one kind of value.
 
 Consider the following constructor:
 
-```language-js
-function Point(x, y) {
-    this.x = x;
-    this.y = y;
-}
-```
+    function Point(x, y) {
+        this.x = x;
+        this.y = y;
+    }
 
 If only integers are ever stored in the `x` and `y` properties,
 then the instances of `Point` will be represented in an "unboxed" mode -
 with the property values stored as raw 4-byte values within the object.
 
 Objects which have the unboxed optimization are more compact.
 
 ### SetProp_InlineAccess
@@ -377,26 +359,24 @@ GetProp_DefiniteProperty), then this opt
 
 Alternatively, if the property write is polymorphic, but only has a few
 different shapes observed at the access site, this optimization may be used.
 
 This optimization compiles down to one-or more shape-guarded direct stores
 to the object.  The following pseudocode describes the kind of machine
 code generated by this optimization:
 
-```
-if obj.shape == Shape1 then
-  obj.slots[0] = val
-elif obj.shape == Shape2 then
-  obj.slots[5] = val
-elif obj.shape == Shape3 then
-  obj.slots[2] = val
-...
-end
-```
+    if obj.shape == Shape1 then
+      obj.slots[0] = val
+    elif obj.shape == Shape2 then
+      obj.slots[5] = val
+    elif obj.shape == Shape3 then
+      obj.slots[2] = val
+    ...
+    end
 
 ### SetProp_InlineCache
 
 This is the worst-case scenario for a property access optimization.  This
 strategy is used when all the others fail.  The engine simply inserts
 an inline cache at the property write site.
 
 Inline caches start off as a jump to a separate piece of code called
@@ -406,17 +386,17 @@ can be optimized in that particular case
 "stub" (or freestanding piece of jitcode) and changes the inline cache
 to jump to the stub.  The stub attempts to optimize further occurrences
 of that same kind of operation.
 
 Inline caches are an order of magnitude slower than the other optimization
 strategies, and are an indication that the type inference engine has
 failed to collect enough information to guide the optimization process.
 
-## GetElement
+## <a name="getelem"></a>GetElement
 
 ### GetElem_TypedObject
 
 Attempts to optimized element accesses on array Typed Objects.
 
 ### GetElem_Dense
 
 Attempts to optimize element accesses on densely packed array objects.  Dense
@@ -462,35 +442,31 @@ calls (except for the `apply` case above
 ### GetElem_ArgumentsInlined
 
 Similar to GetEelem_Arguments, but optimizes cases where the access on
 `arguments` is happening within an inlined function.  In these cases, an
 access of the form `arguments[i]` can be directly translated into a
 direct reference to the corresponding argument value in the inlined call.
 
 Consider the following:
-```language-js
-function foo(arg) {
-  return bar(arg, 3);
-}
-function bar() {
-  return arguments[0] + arguments[1];
-}
-```
+    function foo(arg) {
+      return bar(arg, 3);
+    }
+    function bar() {
+      return arguments[0] + arguments[1];
+    }
 
 In the above case, if `foo` is compiled with Ion, and the call to `bar`
 is inlined, then this optimization can transform the entire procedure to
 the following pseudo-code:
 
-```
-compiled foo(arg):
-    // inlined call to bar(arg, 3) {
-    return arg + 3;
-    // }
-```
+    compiled foo(arg):
+        // inlined call to bar(arg, 3) {
+        return arg + 3;
+        // }
 
 ### GetElem_InlineCache
 
 This is the worst-case scenario for a element access optimization.  This
 strategy is used when all the others fail.  The engine simply inserts
 an inline cache at the property write site.
 
 Inline caches start off as a jump to a separate piece of code called
@@ -500,17 +476,17 @@ can be optimized in that particular case
 "stub" (or freestanding piece of jitcode) and changes the inline cache
 to jump to the stub.  The stub attempts to optimize further occurrences
 of that same kind of operation.
 
 Inline caches are an order of magnitude slower than the other optimization
 strategies, and are an indication that the type inference engine has
 failed to collect enough information to guide the optimization process.
 
-## SetElement
+## <a name="setelem"></a>SetElement
 
 ### SetElem_TypedObject
 
 Attempts to optimized element writes on array Typed Objects.
 
 ### SetElem_TypedStatic
 
 Attempts to optimize element writes on a typed array that can be determined
@@ -562,17 +538,17 @@ can be optimized in that particular case
 "stub" (or freestanding piece of jitcode) and changes the inline cache
 to jump to the stub.  The stub attempts to optimize further occurrences
 of that same kind of operation.
 
 Inline caches are an order of magnitude slower than the other optimization
 strategies, and are an indication that the type inference engine has
 failed to collect enough information to guide the optimization process.
 
-## Call
+## <a name="call"></a>Call
 
 ### Call_Inline
 
 A function call `f(x)` usually pushes a frame onto the call stack. Inlining a
 call site conceptually copies the body of the callee function and pastes it
 in place of the call site and avoids pushing a new execution frame. Usually,
 hot functions do well to be inlined. This is one of the most important
 optimizations the JIT performs.
--- a/js/src/frontend/BytecodeEmitter.cpp
+++ b/js/src/frontend/BytecodeEmitter.cpp
@@ -139,16 +139,17 @@ BytecodeEmitter::BytecodeEmitter(Bytecod
     yieldOffsetList(cx),
     typesetCount(0),
     hasSingletons(false),
     hasTryFinally(false),
     emittingForInit(false),
     emittingRunOnceLambda(false),
     insideEval(insideEval),
     insideNonGlobalEval(insideNonGlobalEval),
+    insideModule(false),
     emitterMode(emitterMode)
 {
     MOZ_ASSERT_IF(evalCaller, insideEval);
     MOZ_ASSERT_IF(emitterMode == LazyFunction, lazyScript);
 }
 
 bool
 BytecodeEmitter::init()
@@ -354,17 +355,16 @@ BytecodeEmitter::emitDupAt(unsigned slot
     jsbytecode* pc = code(off);
     SET_UINT24(pc, slotFromTop);
     return true;
 }
 
 /* XXX too many "... statement" L10N gaffes below -- fix via js.msg! */
 const char js_with_statement_str[] = "with statement";
 const char js_finally_block_str[]  = "finally block";
-const char js_script_str[]         = "script";
 
 static const char * const statementName[] = {
     "label statement",       /* LABEL */
     "if statement",          /* IF */
     "else statement",        /* ELSE */
     "destructuring body",    /* BODY */
     "switch statement",      /* SWITCH */
     "block",                 /* BLOCK */
@@ -1424,16 +1424,17 @@ BytecodeEmitter::isAliasedName(BytecodeE
         return script->formalIsAliased(pn->pn_scopecoord.slot());
       case Definition::VAR:
       case Definition::GLOBALCONST:
         MOZ_ASSERT_IF(sc->allLocalsAliased(), script->localIsAliased(pn->pn_scopecoord.slot()));
         return script->localIsAliased(pn->pn_scopecoord.slot());
       case Definition::PLACEHOLDER:
       case Definition::NAMED_LAMBDA:
       case Definition::MISSING:
+      case Definition::IMPORT:
         MOZ_CRASH("unexpected dn->kind");
     }
     return false;
 }
 
 bool
 BytecodeEmitter::computeDefinitionIsAliased(BytecodeEmitter* bceOfDef, Definition* dn, JSOp* op)
 {
@@ -1586,16 +1587,21 @@ BytecodeEmitter::tryConvertFreeName(Pars
         }
     }
 
     // Unbound names aren't recognizable global-property references if the
     // script is inside a non-global eval call.
     if (insideNonGlobalEval)
         return false;
 
+    // If we are inside a module then unbound names in a function may refer to
+    // imports, so we can't use GNAME ops here.
+    if (insideModule)
+        return false;
+
     // Skip trying to use GNAME ops if we know our script has a non-syntactic
     // scope, since they'll just get treated as NAME ops anyway.
     if (script->hasNonSyntacticScope())
         return false;
 
     // Deoptimized names also aren't necessarily globals.
     if (pn->isDeoptimized())
         return false;
@@ -1837,20 +1843,21 @@ BytecodeEmitter::bindNameToSlotHelper(Pa
         }
 
         pn->setOp(op);
         pn->pn_dflags |= PND_BOUND;
         return true;
       }
 
       case Definition::PLACEHOLDER:
+      case Definition::IMPORT:
         return true;
 
       case Definition::MISSING:
-        MOZ_CRASH("missing");
+        MOZ_CRASH("unexpected definition kind");
     }
 
     // The hop count is the number of dynamic scopes during execution that must
     // be skipped to access the binding.
     BytecodeEmitter* bceOfDef;
     uint32_t slot = dn->pn_scopecoord.slot();
     uint32_t hops = computeHops(pn, &bceOfDef);
 
@@ -1858,17 +1865,17 @@ BytecodeEmitter::bindNameToSlotHelper(Pa
      * Explicitly disallow accessing var/let bindings in global scope from
      * nested functions. The reason for this limitation is that, since the
      * global script is not included in the static scope chain (1. because it
      * has no object to stand in the static scope chain, 2. to minimize memory
      * bloat where a single live function keeps its whole global script
      * alive.), ScopeCoordinateToTypeSet is not able to find the var/let's
      * associated TypeSet.
      */
-    if (bceOfDef != this && !bceOfDef->sc->isFunctionBox())
+    if (bceOfDef != this && bceOfDef->sc->isGlobalContext())
         return true;
 
     if (!pn->pn_scopecoord.set(parser->tokenStream, hops, slot))
         return false;
 
     if (!computeDefinitionIsAliased(bceOfDef, dn, &op))
         return false;
 
@@ -3351,16 +3358,26 @@ BytecodeEmitter::emitYieldOp(JSOp op)
     SET_UINT24(code(off), yieldIndex);
 
     if (!yieldOffsetList.append(offset()))
         return false;
 
     return emit1(JSOP_DEBUGAFTERYIELD);
 }
 
+static bool
+IsModuleOnScopeChain(JSObject* obj)
+{
+    for (StaticScopeIter<NoGC> ssi(obj); !ssi.done(); ssi++) {
+        if (ssi.type() == StaticScopeIter<NoGC>::Module)
+            return true;
+    }
+    return false;
+}
+
 bool
 BytecodeEmitter::emitFunctionScript(ParseNode* body)
 {
     if (!updateLocalsToFrameSlots())
         return false;
 
     /*
      * IonBuilder has assumptions about what may occur immediately after
@@ -3370,16 +3387,19 @@ BytecodeEmitter::emitFunctionScript(Pars
      */
 
     FunctionBox* funbox = sc->asFunctionBox();
 
     // Link the function and the script to each other, so that StaticScopeIter
     // may walk the scope chain of currently compiling scripts.
     JSScript::linkToFunctionFromEmitter(cx, script, funbox);
 
+    // Determine whether the function is defined inside a module.
+    insideModule = IsModuleOnScopeChain(sc->staticScope());
+
     if (funbox->argumentsHasLocalBinding()) {
         MOZ_ASSERT(offset() == 0);  /* See JSScript::argumentsBytecode. */
         switchToPrologue();
         if (!emit1(JSOP_ARGUMENTS))
             return false;
         BindingIter bi = Bindings::argumentsBinding(cx, script);
         if (script->bindingIsAliased(bi)) {
             ScopeCoordinate sc;
@@ -3480,16 +3500,18 @@ BytecodeEmitter::emitFunctionScript(Pars
     tellDebuggerAboutCompiledScript(cx);
 
     return true;
 }
 
 bool
 BytecodeEmitter::emitModuleScript(ParseNode* body)
 {
+    insideModule = true;
+
     if (!updateLocalsToFrameSlots())
         return false;
 
     /*
      * IonBuilder has assumptions about what may occur immediately after
      * script->main (e.g., in the case of destructuring params). Thus, put the
      * following ops into the range [script->code, script->main). Note:
      * execution starts from script->code, so this has no semantic effect.
@@ -5728,28 +5750,28 @@ BytecodeEmitter::emitFor(ParseNode* pn, 
 MOZ_NEVER_INLINE bool
 BytecodeEmitter::emitFunction(ParseNode* pn, bool needsProto)
 {
     FunctionBox* funbox = pn->pn_funbox;
     RootedFunction fun(cx, funbox->function());
     MOZ_ASSERT_IF(fun->isInterpretedLazy(), fun->lazyScript());
 
     /*
-     * Set the EMITTEDFUNCTION flag in function definitions once they have
-     * been emitted. Function definitions that need hoisting to the top of the
+     * Set the |wasEmitted| flag in the funbox once the function has been
+     * emitted. Function definitions that need hoisting to the top of the
      * function will be seen by emitFunction in two places.
      */
-    if (pn->pn_dflags & PND_EMITTEDFUNCTION) {
+    if (funbox->wasEmitted) {
         MOZ_ASSERT_IF(fun->hasScript(), fun->nonLazyScript());
         MOZ_ASSERT(pn->functionIsHoisted());
         MOZ_ASSERT(sc->isFunctionBox());
         return true;
     }
 
-    pn->pn_dflags |= PND_EMITTEDFUNCTION;
+    funbox->wasEmitted = true;
 
     /*
      * Mark as singletons any function which will only be executed once, or
      * which is inner to a lambda we only expect to run once. In the latter
      * case, if the lambda runs multiple times then CloneFunctionObject will
      * make a deep clone of its contents.
      */
     if (fun->isInterpreted()) {
@@ -7858,22 +7880,19 @@ BytecodeEmitter::emitTree(ParseNode* pn)
             ok = emitTree(pn->pn_kid);
         else
             ok = true;
         break;
 
       case PNK_EXPORT_DEFAULT:
         if (!checkIsModule())
             return false;
-        if (pn->pn_kid->isDefn()) {
-            ok = emitTree(pn->pn_kid);
-        } else {
-            // TODO: Emit a definition of *default* from child expression.
-            ok = true;
-        }
+        ok = emitTree(pn->pn_kid);
+        if (ok && pn->pn_right)
+            ok = emitLexicalInitialization(pn->pn_right, JSOP_DEFCONST) && emit1(JSOP_POP);
         break;
 
       case PNK_EXPORT_FROM:
         if (!checkIsModule())
             return false;
         ok = true;
         break;
 
--- a/js/src/frontend/BytecodeEmitter.h
+++ b/js/src/frontend/BytecodeEmitter.h
@@ -195,16 +195,18 @@ struct BytecodeEmitter
     bool isRunOnceLambda();
 
     bool            insideEval:1;       /* True if compiling an eval-expression or a function
                                            nested inside an eval. */
 
     const bool      insideNonGlobalEval:1;  /* True if this is a direct eval
                                                call in some non-global scope. */
 
+    bool            insideModule:1;     /* True if compiling inside a module. */
+
     enum EmitterMode {
         Normal,
 
         /*
          * Emit JSOP_GETINTRINSIC instead of JSOP_GETNAME and assert that
          * JSOP_GETNAME and JSOP_*GNAME don't ever get emitted. See the comment
          * for the field |selfHostingMode| in Parser.h for details.
          */
--- a/js/src/frontend/FoldConstants.cpp
+++ b/js/src/frontend/FoldConstants.cpp
@@ -1770,21 +1770,24 @@ Fold(ExclusiveContext* cx, ParseNode** p
         return FoldIncrementDecrement(cx, pn, parser, inGenexpLambda);
 
       case PNK_THROW:
       case PNK_ARRAYPUSH:
       case PNK_MUTATEPROTO:
       case PNK_COMPUTED_NAME:
       case PNK_SPREAD:
       case PNK_EXPORT:
-      case PNK_EXPORT_DEFAULT:
       case PNK_VOID:
         MOZ_ASSERT(pn->isArity(PN_UNARY));
         return Fold(cx, &pn->pn_kid, parser, inGenexpLambda);
 
+      case PNK_EXPORT_DEFAULT:
+        MOZ_ASSERT(pn->isArity(PN_BINARY));
+        return Fold(cx, &pn->pn_left, parser, inGenexpLambda);
+
       case PNK_SEMI:
         MOZ_ASSERT(pn->isArity(PN_UNARY));
         if (ParseNode*& expr = pn->pn_kid)
             return Fold(cx, &expr, parser, inGenexpLambda);
         return true;
 
       case PNK_AND:
       case PNK_OR:
--- a/js/src/frontend/FullParseHandler.h
+++ b/js/src/frontend/FullParseHandler.h
@@ -515,18 +515,19 @@ class FullParseHandler
     {
         ParseNode* pn = new_<BinaryNode>(PNK_EXPORT_FROM, JSOP_NOP, exportSpecSet, moduleSpec);
         if (!pn)
             return null();
         pn->pn_pos.begin = begin;
         return pn;
     }
 
-    ParseNode* newExportDefaultDeclaration(ParseNode* kid, const TokenPos& pos) {
-        return new_<UnaryNode>(PNK_EXPORT_DEFAULT, JSOP_NOP, pos, kid);
+    ParseNode* newExportDefaultDeclaration(ParseNode* kid, ParseNode* maybeBinding,
+                                           const TokenPos& pos) {
+        return new_<BinaryNode>(PNK_EXPORT_DEFAULT, JSOP_NOP, pos, kid, maybeBinding);
     }
 
     ParseNode* newExprStatement(ParseNode* expr, uint32_t end) {
         MOZ_ASSERT(expr->pn_pos.end <= end);
         return new_<UnaryNode>(PNK_SEMI, JSOP_NOP, TokenPos(expr->pn_pos.begin, end), expr);
     }
 
     ParseNode* newIfStatement(uint32_t begin, ParseNode* cond, ParseNode* thenBranch,
--- a/js/src/frontend/NameFunctions.cpp
+++ b/js/src/frontend/NameFunctions.cpp
@@ -407,17 +407,16 @@ class NameResolver
           case PNK_POSTINCREMENT:
           case PNK_PREDECREMENT:
           case PNK_POSTDECREMENT:
           case PNK_COMPUTED_NAME:
           case PNK_ARRAYPUSH:
           case PNK_SPREAD:
           case PNK_MUTATEPROTO:
           case PNK_EXPORT:
-          case PNK_EXPORT_DEFAULT:
             MOZ_ASSERT(cur->isArity(PN_UNARY));
             if (!resolve(cur->pn_kid, prefix))
                 return false;
             break;
 
           // Nodes with a single nullable child.
           case PNK_SEMI:
             MOZ_ASSERT(cur->isArity(PN_UNARY));
@@ -512,23 +511,25 @@ class NameResolver
                 MOZ_ASSERT(internalAssignForGenerators->pn_atom == cx->names().dotGenRVal);
                 MOZ_ASSERT(internalAssignForGenerators->isAssigned());
             }
 #endif
             break;
 
           case PNK_IMPORT:
           case PNK_EXPORT_FROM:
+          case PNK_EXPORT_DEFAULT:
             MOZ_ASSERT(cur->isArity(PN_BINARY));
             // The left halves of these nodes don't contain any unconstrained
             // expressions, but it's very hard to assert this to safely rely on
             // it.  So recur anyway.
             if (!resolve(cur->pn_left, prefix))
                 return false;
-            MOZ_ASSERT(cur->pn_right->isKind(PNK_STRING));
+            MOZ_ASSERT_IF(!cur->isKind(PNK_EXPORT_DEFAULT),
+                          cur->pn_right->isKind(PNK_STRING));
             break;
 
           // Ternary nodes with three expression children.
           case PNK_CONDITIONAL:
             MOZ_ASSERT(cur->isArity(PN_TERNARY));
             if (!resolve(cur->pn_kid1, prefix))
                 return false;
             if (!resolve(cur->pn_kid2, prefix))
--- a/js/src/frontend/ParseNode.cpp
+++ b/js/src/frontend/ParseNode.cpp
@@ -237,17 +237,16 @@ PushNodeChildren(ParseNode* pn, NodeStac
       case PNK_POSTINCREMENT:
       case PNK_PREDECREMENT:
       case PNK_POSTDECREMENT:
       case PNK_COMPUTED_NAME:
       case PNK_ARRAYPUSH:
       case PNK_SPREAD:
       case PNK_MUTATEPROTO:
       case PNK_EXPORT:
-      case PNK_EXPORT_DEFAULT:
         return PushUnaryNodeChild(pn, stack);
 
       // Nodes with a single nullable child.
       case PNK_SEMI: {
         MOZ_ASSERT(pn->isArity(PN_UNARY));
         if (pn->pn_kid)
             stack->push(pn->pn_kid);
         return PushResult::Recyclable;
@@ -362,16 +361,25 @@ PushNodeChildren(ParseNode* pn, NodeStac
         MOZ_ASSERT_IF(pn->isKind(PNK_EXPORT_FROM), pn->pn_left->isKind(PNK_EXPORT_SPEC_LIST));
         MOZ_ASSERT(pn->pn_left->isArity(PN_LIST));
         MOZ_ASSERT(pn->pn_right->isKind(PNK_STRING));
         stack->pushList(pn->pn_left);
         stack->push(pn->pn_right);
         return PushResult::Recyclable;
       }
 
+      case PNK_EXPORT_DEFAULT: {
+        MOZ_ASSERT(pn->isArity(PN_BINARY));
+        MOZ_ASSERT_IF(pn->pn_right, pn->pn_right->isKind(PNK_NAME));
+        stack->push(pn->pn_left);
+        if (pn->pn_right)
+            stack->push(pn->pn_right);
+        return PushResult::Recyclable;
+      }
+
       // Ternary nodes with all children non-null.
       case PNK_CONDITIONAL: {
         MOZ_ASSERT(pn->isArity(PN_TERNARY));
         stack->push(pn->pn_kid1);
         stack->push(pn->pn_kid2);
         stack->push(pn->pn_kid3);
         return PushResult::Recyclable;
       }
@@ -638,21 +646,29 @@ ParseNode::appendOrCreateList(ParseNodeK
 
     list->append(right);
     return list;
 }
 
 const char*
 Definition::kindString(Kind kind)
 {
-    static const char * const table[] = {
-        "", js_var_str, js_const_str, js_const_str, js_let_str, "argument", js_function_str, "unknown"
+    static const char* const table[] = {
+        "",
+        js_var_str,
+        js_const_str,
+        js_const_str,
+        js_let_str,
+        "argument",
+        js_function_str,
+        "unknown",
+        js_import_str
     };
 
-    MOZ_ASSERT(unsigned(kind) <= unsigned(ARG));
+    MOZ_ASSERT(kind < ArrayLength(table));
     return table[kind];
 }
 
 namespace js {
 namespace frontend {
 
 /*
  * This function assumes the cloned tree is for use in the same statement and
@@ -1167,12 +1183,13 @@ ObjectBox::trace(JSTracer* trc)
         if (box->isFunctionBox()) {
             FunctionBox* funbox = box->asFunctionBox();
             funbox->bindings.trace(trc);
             if (funbox->enclosingStaticScope_)
                 TraceRoot(trc, &funbox->enclosingStaticScope_, "funbox-enclosingStaticScope");
         } else if (box->isModuleBox()) {
             ModuleBox* modulebox = box->asModuleBox();
             modulebox->bindings.trace(trc);
+            modulebox->exportNames.trace(trc);
         }
         box = box->traceLink;
     }
 }
--- a/js/src/frontend/ParseNode.h
+++ b/js/src/frontend/ParseNode.h
@@ -511,17 +511,17 @@ class BreakStatement;
 class ContinueStatement;
 class ConditionalExpression;
 class PropertyAccess;
 
 class ParseNode
 {
     uint32_t            pn_type   : 16, /* PNK_* type */
                         pn_op     : 8,  /* see JSOp enum and jsopcode.tbl */
-                        pn_arity  : 5,  /* see ParseNodeArity enum */
+                        pn_arity  : 4,  /* see ParseNodeArity enum */
                         pn_parens : 1,  /* this expr was enclosed in parens */
                         pn_used   : 1,  /* name node is on a use-chain */
                         pn_defn   : 1;  /* this node is a Definition */
 
     ParseNode(const ParseNode& other) = delete;
     void operator=(const ParseNode& other) = delete;
 
   public:
@@ -745,19 +745,19 @@ class ParseNode
                                            optimizable via an upvar opcode */
 #define PND_CLOSED              0x40    /* variable is closed over */
 #define PND_KNOWNALIASED        0x80    /* definition known to be aliased and
                                            already has a translated pnk_scopecoord */
 #define PND_IMPLICITARGUMENTS  0x100    /* the definition is a placeholder for
                                            'arguments' that has been converted
                                            into a definition after the function
                                            body has been parsed. */
-#define PND_EMITTEDFUNCTION    0x200    /* hoisted function that was emitted */
+#define PND_IMPORT             0x200    /* the definition is a module import. */
 
-    static_assert(PND_EMITTEDFUNCTION < (1 << NumDefinitionFlagBits), "Not enough bits");
+    static_assert(PND_IMPORT < (1 << NumDefinitionFlagBits), "Not enough bits");
 
 /* Flags to propagate from uses to definition. */
 #define PND_USE2DEF_FLAGS (PND_ASSIGNED | PND_CLOSED)
 
 /* PN_LIST pn_xflags bits. */
 #define PNX_POPVAR      0x01            /* PNK_VAR or PNK_CONST last result
                                            needs popping */
 #define PNX_FUNCDEFS    0x02            /* contains top-level function statements */
@@ -816,16 +816,17 @@ class ParseNode
     bool isPlaceholder() const  { return test(PND_PLACEHOLDER); }
     bool isDeoptimized() const  { return test(PND_DEOPTIMIZED); }
     bool isAssigned() const     { return test(PND_ASSIGNED); }
     bool isClosed() const       { return test(PND_CLOSED); }
     bool isBound() const        { return test(PND_BOUND); }
     bool isImplicitArguments() const { return test(PND_IMPLICITARGUMENTS); }
     bool isHoistedLexicalUse() const { return test(PND_LEXICAL) && isUsed(); }
     bool isKnownAliased() const { return test(PND_KNOWNALIASED); }
+    bool isImport() const       { return test(PND_IMPORT); }
 
     /* True if pn is a parsenode representing a literal constant. */
     bool isLiteral() const {
         return isKind(PNK_NUMBER) ||
                isKind(PNK_STRING) ||
                isKind(PNK_TRUE) ||
                isKind(PNK_FALSE) ||
                isKind(PNK_NULL);
@@ -1562,17 +1563,27 @@ void DumpParseTree(ParseNode* pn, int in
  */
 struct Definition : public ParseNode
 {
     bool isFreeVar() const {
         MOZ_ASSERT(isDefn());
         return pn_scopecoord.isFree();
     }
 
-    enum Kind { MISSING = 0, VAR, GLOBALCONST, CONST, LET, ARG, NAMED_LAMBDA, PLACEHOLDER };
+    enum Kind {
+        MISSING = 0,
+        VAR,
+        GLOBALCONST,
+        CONST,
+        LET,
+        ARG,
+        NAMED_LAMBDA,
+        PLACEHOLDER,
+        IMPORT
+    };
 
     bool canHaveInitializer() { return int(kind()) <= int(ARG); }
 
     static const char* kindString(Kind kind);
 
     Kind kind() {
         if (getKind() == PNK_FUNCTION) {
             if (isOp(JSOP_GETARG))
@@ -1581,16 +1592,18 @@ struct Definition : public ParseNode
         }
         MOZ_ASSERT(getKind() == PNK_NAME);
         if (isOp(JSOP_CALLEE))
             return NAMED_LAMBDA;
         if (isPlaceholder())
             return PLACEHOLDER;
         if (isOp(JSOP_GETARG))
             return ARG;
+        if (isImport())
+            return IMPORT;
         if (isLexical())
             return isConst() ? CONST : LET;
         if (isConst())
             return GLOBALCONST;
         return VAR;
     }
 };
 
--- a/js/src/frontend/Parser.cpp
+++ b/js/src/frontend/Parser.cpp
@@ -276,16 +276,23 @@ ParseContext<FullParseHandler>::define(T
         // the definition has existing uses, they need to be marked so that we
         // emit dead zone checks.
         MarkUsesAsHoistedLexical(pn);
 
         if (!decls_.addShadow(name, dn))
             return false;
         break;
 
+      case Definition::IMPORT:
+        dn->pn_dflags |= PND_LEXICAL | PND_CLOSED;
+        MOZ_ASSERT(atBodyLevel());
+        if (!decls_.addShadow(name, dn))
+            return false;
+        break;
+
       default:
         MOZ_CRASH("unexpected kind");
     }
 
     return true;
 }
 
 template <>
@@ -331,17 +338,17 @@ void
 ParseContext<ParseHandler>::updateDecl(JSAtom* atom, Node pn)
 {
     Definition* oldDecl = decls_.lookupFirst(atom);
 
     pn->setDefn(true);
     Definition* newDecl = (Definition*)pn;
     decls_.updateFirst(atom, newDecl);
 
-    if (!sc->isFunctionBox()) {
+    if (sc->isGlobalContext()) {
         MOZ_ASSERT(newDecl->isFreeVar());
         return;
     }
 
     MOZ_ASSERT(oldDecl->isBound());
     MOZ_ASSERT(!oldDecl->pn_scopecoord.isFree());
     newDecl->pn_scopecoord = oldDecl->pn_scopecoord;
     newDecl->pn_dflags |= PND_BOUND;
@@ -387,16 +394,19 @@ AppendPackedBindings(const ParseContext<
             break;
           case Definition::CONST:
           case Definition::GLOBALCONST:
             kind = Binding::CONSTANT;
             break;
           case Definition::ARG:
             kind = Binding::ARGUMENT;
             break;
+          case Definition::IMPORT:
+            // Skip module imports.
+            continue;
           default:
             MOZ_CRASH("unexpected dn->kind");
         }
 
         /*
          * Bindings::init does not check for duplicates so we must ensure that
          * only one binding with a given name is marked aliased. pc->decls
          * maintains the canonical definition for each name, so use that.
@@ -642,16 +652,17 @@ FunctionBox::FunctionBox(ExclusiveContex
     bufStart(0),
     bufEnd(0),
     length(0),
     generatorKindBits_(GeneratorKindAsBits(generatorKind)),
     inGenexpLambda(false),
     hasDestructuringArgs(false),
     useAsm(false),
     insideUseAsm(outerpc && outerpc->useAsmOrInsideUseAsm()),
+    wasEmitted(false),
     usesArguments(false),
     usesApply(false),
     usesThis(false),
     funCxFlags()
 {
     // Functions created at parse time may be set singleton after parsing and
     // baked into JIT code, so they must be allocated tenured. They are held by
     // the JSScript so cannot be collected during a minor GC anyway.
@@ -692,17 +703,18 @@ Parser<ParseHandler>::newFunctionBox(Nod
     return funbox;
 }
 
 template <typename ParseHandler>
 ModuleBox::ModuleBox(ExclusiveContext* cx, ObjectBox* traceListHead, ModuleObject* module,
                      ParseContext<ParseHandler>* outerpc)
   : ObjectBox(module, traceListHead),
     SharedContext(cx, Directives(true), false),
-    bindings()
+    bindings(),
+    exportNames(cx)
 {}
 
 template <typename ParseHandler>
 ModuleBox*
 Parser<ParseHandler>::newModuleBox(Node pn, HandleModuleObject module)
 {
     MOZ_ASSERT(module);
 
@@ -3238,18 +3250,25 @@ Parser<FullParseHandler>::bindLexical(Bi
     // script->nfixed and body-level lets.
     //
     // For body-level lets, the index is bogus at this point and is adjusted
     // when creating Bindings. See ParseContext::generateBindings and
     // AppendPackedBindings.
     if (!pn->pn_scopecoord.setSlot(parser->tokenStream, index))
         return false;
 
+    Definition::Kind bindingKind;
+    if (pn->isImport())
+        bindingKind = Definition::IMPORT;
+    else if (data->isConst())
+        bindingKind = Definition::CONST;
+    else
+        bindingKind = Definition::LET;
+
     Definition* dn = pc->decls().lookupFirst(name);
-    Definition::Kind bindingKind = data->isConst() ? Definition::CONST : Definition::LET;
 
     /*
      * For bindings that are hoisted to the beginning of the block/function,
      * define() right now. Otherwise, delay define until pushLetScope.
      */
     if (data->letData().varContext == HoistVars) {
         if (dn && dn->pn_blockid == pc->blockid())
             return parser->reportRedeclaration(pn, dn->kind(), name);
@@ -3496,16 +3515,17 @@ Parser<ParseHandler>::bindVarOrGlobalCon
             parser->report(ParseError, false, pn, JSMSG_REDECLARED_PARAM, bytes.ptr());
             return false;
         }
         if (!parser->report(ParseExtraWarning, false, pn, JSMSG_VAR_HIDES_ARG, bytes.ptr()))
             return false;
     } else {
         bool inCatchBody = (stmt && stmt->type == StmtType::CATCH);
         bool error = (isConstDecl ||
+                      dn_kind == Definition::IMPORT ||
                       dn_kind == Definition::CONST ||
                       dn_kind == Definition::GLOBALCONST ||
                       (dn_kind == Definition::LET &&
                        (!inCatchBody || OuterLet(pc, stmt, name))));
 
         if (parser->options().extraWarningsOption
             ? data->op() != JSOP_DEFVAR || dn_kind != Definition::VAR
             : error)
@@ -3595,25 +3615,34 @@ Parser<ParseHandler>::noteNameUse(Handle
         }
     }
 
     return true;
 }
 
 template <>
 bool
-Parser<FullParseHandler>::bindInitialized(BindData<FullParseHandler>* data, ParseNode* pn)
+Parser<FullParseHandler>::bindUninitialized(BindData<FullParseHandler>* data, ParseNode* pn)
 {
     MOZ_ASSERT(pn->isKind(PNK_NAME));
 
     RootedPropertyName name(context, pn->pn_atom->asPropertyName());
 
     data->setNameNode(pn);
     if (!data->bind(name, this))
         return false;
+    return true;
+}
+
+template <>
+bool
+Parser<FullParseHandler>::bindInitialized(BindData<FullParseHandler>* data, ParseNode* pn)
+{
+    if (!bindUninitialized(data, pn))
+        return false;
 
     /*
      * Select the appropriate name-setting opcode, respecting eager selection
      * done by the data->bind function.
      */
     if (data->op() == JSOP_INITLEXICAL)
         pn->setOp(JSOP_INITLEXICAL);
     else if (pn->pn_dflags & PND_BOUND)
@@ -4441,16 +4470,42 @@ template <>
 SyntaxParseHandler::Node
 Parser<SyntaxParseHandler>::letDeclarationOrBlock(YieldHandling yieldHandling)
 {
     JS_ALWAYS_FALSE(abortIfSyntaxParser());
     return SyntaxParseHandler::NodeFailure;
 }
 
 template<>
+ParseNode*
+Parser<FullParseHandler>::newBoundImportForCurrentName()
+{
+    Node importNode = newName(tokenStream.currentName());
+    if (!importNode)
+        return null();
+
+    importNode->pn_dflags |= PND_CONST | PND_IMPORT;
+    BindData<FullParseHandler> data(context);
+    data.initLexical(HoistVars, nullptr, JSMSG_TOO_MANY_LOCALS);
+    handler.setPosition(importNode, pos());
+    if (!bindUninitialized(&data, importNode))
+        return null();
+
+    return importNode;
+}
+
+template <>
+SyntaxParseHandler::Node
+Parser<SyntaxParseHandler>::newBoundImportForCurrentName()
+{
+    JS_ALWAYS_FALSE(abortIfSyntaxParser());
+    return SyntaxParseHandler::NodeFailure;
+}
+
+template<>
 bool
 Parser<FullParseHandler>::namedImportsOrNamespaceImport(TokenKind tt, Node importSpecSet)
 {
     if (tt == TOK_LC) {
         TokenStream::Modifier modifier = TokenStream::KeywordIsName;
         while (true) {
             // Handle the forms |import {} from 'a'| and
             // |import { ..., } from 'a'| (where ... is non empty), by
@@ -4483,17 +4538,18 @@ Parser<FullParseHandler>::namedImportsOr
                     JSAutoByteString bytes;
                     if (!AtomToPrintableString(context, importName->name(), &bytes))
                         return false;
                     report(ParseError, false, null(), JSMSG_AS_AFTER_RESERVED_WORD, bytes.ptr());
                     return false;
                 }
                 tokenStream.ungetToken();
             }
-            Node bindingName = newName(tokenStream.currentName());
+
+            Node bindingName = newBoundImportForCurrentName();
             if (!bindingName)
                 return false;
 
             Node importSpec = handler.newBinary(PNK_IMPORT_SPEC, importName, bindingName);
             if (!importSpec)
                 return false;
 
             handler.addList(importSpecSet, importSpec);
@@ -4520,17 +4576,17 @@ Parser<FullParseHandler>::namedImportsOr
         }
 
         MUST_MATCH_TOKEN(TOK_NAME, JSMSG_NO_BINDING_NAME);
 
         Node importName = newName(context->names().star);
         if (!importName)
             return null();
 
-        Node bindingName = newName(tokenStream.currentName());
+        Node bindingName = newBoundImportForCurrentName();
         if (!bindingName)
             return false;
 
         Node importSpec = handler.newBinary(PNK_IMPORT_SPEC, importName, bindingName);
         if (!importSpec)
             return false;
 
         handler.addList(importSpecSet, importSpec);
@@ -4548,17 +4604,17 @@ Parser<SyntaxParseHandler>::namedImports
 }
 
 template<typename ParseHandler>
 typename ParseHandler::Node
 Parser<ParseHandler>::importDeclaration()
 {
     MOZ_ASSERT(tokenStream.currentToken().type == TOK_IMPORT);
 
-    if (pc->sc->isFunctionBox() || !pc->atBodyLevel()) {
+    if (!pc->atModuleLevel()) {
         report(ParseError, false, null(), JSMSG_IMPORT_DECL_AT_TOP_LEVEL);
         return null();
     }
 
     uint32_t begin = pos().begin;
     TokenKind tt;
     if (!tokenStream.getToken(&tt))
         return null();
@@ -4572,17 +4628,17 @@ Parser<ParseHandler>::importDeclaration(
             // Handle the form |import a from 'b'|, by adding a single import
             // specifier to the list, with 'default' as the import name and
             // 'a' as the binding name. This is equivalent to
             // |import { default as a } from 'b'|.
             Node importName = newName(context->names().default_);
             if (!importName)
                 return null();
 
-            Node bindingName = newName(tokenStream.currentName());
+            Node bindingName = newBoundImportForCurrentName();
             if (!bindingName)
                 return null();
 
             Node importSpec = handler.newBinary(PNK_IMPORT_SPEC, importName, bindingName);
             if (!importSpec)
                 return null();
 
             handler.addList(importSpecSet, importSpec);
@@ -4644,23 +4700,49 @@ Parser<SyntaxParseHandler>::importDeclar
 }
 
 template <>
 ParseNode*
 Parser<FullParseHandler>::classDefinition(YieldHandling yieldHandling,
                                           ClassContext classContext,
                                           DefaultHandling defaultHandling);
 
+
+template<>
+bool
+Parser<FullParseHandler>::addExportName(JSAtom* exportName)
+{
+    TraceableVector<JSAtom*>& exportNames = pc->sc->asModuleBox()->exportNames;
+    for (JSAtom* name : exportNames) {
+        if (name == exportName) {
+            JSAutoByteString str;
+            if (AtomToPrintableString(context, exportName, &str))
+                report(ParseError, false, null(), JSMSG_DUPLICATE_EXPORT_NAME, str.ptr());
+            return false;
+        }
+    }
+
+    return exportNames.append(exportName);
+}
+
+template<>
+bool
+Parser<SyntaxParseHandler>::addExportName(JSAtom* exportName)
+{
+    JS_ALWAYS_FALSE(abortIfSyntaxParser());
+    return SyntaxParseHandler::NodeFailure;