Merge mozilla-central to mozilla-inbound
authorCarsten "Tomcat" Book <cbook@mozilla.com>
Tue, 10 Jan 2017 12:21:12 +0100
changeset 328760 48517fcee7701be4f684128d09d4a9d3dcd80a4f
parent 328759 e31b6b05ba5b713a6fcac67dbde2eccd591a85dd (current diff)
parent 328707 7011ed1427de2b6f075c46cc6f4618d3e9fcd2a4 (diff)
child 328761 2ac52f6d25659c6f67bb6ea0efa12ef218e971eb
push id31187
push userkwierso@gmail.com
push dateWed, 11 Jan 2017 01:56:54 +0000
treeherdermozilla-central@b079c9833e3e [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
milestone53.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Merge mozilla-central to mozilla-inbound
browser/config/tooltool-manifests/win32/clang.manifest
browser/config/tooltool-manifests/win32/releng.manifest
browser/config/tooltool-manifests/win64/clang.manifest
browser/config/tooltool-manifests/win64/releng.manifest
build/moz.configure/windows.configure
build/mozconfig.win-common
js/src/vm/Interpreter.cpp
js/src/vm/Scope.h
js/src/wasm/WasmCode.cpp
taskcluster/ci/build-signing/android-signing.yml
taskcluster/scripts/builder/desktop-setup.sh
taskcluster/scripts/builder/get-objdir.py
taskcluster/scripts/builder/install-packages.sh
toolkit/crashreporter/tools/symbolstore.py
--- a/browser/base/content/browser-media.js
+++ b/browser/base/content/browser-media.js
@@ -1,13 +1,15 @@
 /* -*- indent-tabs-mode: nil; js-indent-level: 2 -*-
  * This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
+"use strict";
+
 var gEMEHandler = {
   get uiEnabled() {
     let emeUIEnabled = Services.prefs.getBoolPref("browser.eme.ui.enabled");
     // Force-disable on WinXP:
     if (navigator.platform.toLowerCase().startsWith("win")) {
       emeUIEnabled = emeUIEnabled && parseFloat(Services.sysinfo.get("version")) >= 6;
     }
     return emeUIEnabled;
@@ -196,27 +198,20 @@ const TELEMETRY_DDSTAT_SHOWN_FIRST = 1;
 const TELEMETRY_DDSTAT_CLICKED = 2;
 const TELEMETRY_DDSTAT_CLICKED_FIRST = 3;
 const TELEMETRY_DDSTAT_SOLVED = 4;
 
 let gDecoderDoctorHandler = {
   getLabelForNotificationBox(type) {
     if (type == "adobe-cdm-not-found" &&
         AppConstants.platform == "win") {
-      if (AppConstants.isPlatformAndVersionAtMost("win", "5.9")) {
-        // We supply our own Learn More button so we don't need to populate the message here.
-        return gNavigatorBundle.getFormattedString("emeNotifications.drmContentDisabled.message", [""]);
-      }
       return gNavigatorBundle.getString("decoder.noCodecs.message");
     }
     if (type == "adobe-cdm-not-activated" &&
         AppConstants.platform == "win") {
-      if (AppConstants.isPlatformAndVersionAtMost("win", "5.9")) {
-        return gNavigatorBundle.getString("decoder.noCodecsXP.message");
-      }
       if (!AppConstants.isPlatformAndVersionAtLeast("win", "6.1")) {
         return gNavigatorBundle.getString("decoder.noCodecsVista.message");
       }
       return gNavigatorBundle.getString("decoder.noCodecs.message");
     }
     if (type == "platform-decoder-not-found") {
       if (AppConstants.isPlatformAndVersionAtLeast("win", "6.1")) {
         return gNavigatorBundle.getString("decoder.noHWAcceleration.message");
--- a/browser/base/content/browser.js
+++ b/browser/base/content/browser.js
@@ -6600,21 +6600,16 @@ var gIdentityHandler = {
    */
   _sslStatus: null,
 
   /**
    * Bitmask provided by nsIWebProgressListener.onSecurityChange.
    */
   _state: 0,
 
-  /**
-   * Whether a permission is just removed from permission list.
-   */
-  _permissionJustRemoved: false,
-
   get _isBroken() {
     return this._state & Ci.nsIWebProgressListener.STATE_IS_BROKEN;
   },
 
   get _isSecure() {
     // If a <browser> is included within a chrome document, then this._state
     // will refer to the security state for the <browser> and not the top level
     // document. In this case, don't upgrade the security state in the UI
@@ -7299,16 +7294,19 @@ var gIdentityHandler = {
     if (gURLBar.getAttribute("pageproxystate") != "valid") {
       return;
     }
 
     // Make sure that the display:none style we set in xul is removed now that
     // the popup is actually needed
     this._identityPopup.hidden = false;
 
+    // Remove the reload hint that we show after a user has cleared a permission.
+    this._permissionReloadHint.setAttribute("hidden", "true");
+
     // Update the popup strings
     this.refreshIdentityPopup();
 
     // Add the "open" attribute to the identity box for styling
     this._identityBox.setAttribute("open", "true");
 
     // Now open the popup, anchored off the primary chrome element
     this._identityPopup.openPopup(this._identityIcon, "bottomcenter topleft");
@@ -7359,31 +7357,20 @@ var gIdentityHandler = {
     dt.setData("text/x-moz-url", urlString);
     dt.setData("text/uri-list", value);
     dt.setData("text/plain", value);
     dt.setData("text/html", htmlString);
     dt.setDragImage(this._identityIcon, 16, 16);
   },
 
   onLocationChange() {
-    this._permissionJustRemoved = false;
-    this.updatePermissionHint();
-  },
-
-  updatePermissionHint() {
-    if (!this._permissionList.hasChildNodes() && !this._permissionJustRemoved) {
+    this._permissionReloadHint.setAttribute("hidden", "true");
+
+    if (!this._permissionList.hasChildNodes()) {
       this._permissionEmptyHint.removeAttribute("hidden");
-    } else {
-      this._permissionEmptyHint.setAttribute("hidden", "true");
-    }
-
-    if (this._permissionJustRemoved) {
-      this._permissionReloadHint.removeAttribute("hidden");
-    } else {
-      this._permissionReloadHint.setAttribute("hidden", "true");
     }
   },
 
   updateSitePermissions() {
     while (this._permissionList.hasChildNodes())
       this._permissionList.removeChild(this._permissionList.lastChild);
 
     let uri = gBrowser.currentURI;
@@ -7413,17 +7400,23 @@ var gIdentityHandler = {
         }
       }
     }
     for (let permission of permissions) {
       let item = this._createPermissionItem(permission);
       this._permissionList.appendChild(item);
     }
 
-    this.updatePermissionHint();
+    // Show a placeholder text if there's no permission and no reload hint.
+    if (!this._permissionList.hasChildNodes() &&
+        this._permissionReloadHint.hasAttribute("hidden")) {
+      this._permissionEmptyHint.removeAttribute("hidden");
+    } else {
+      this._permissionEmptyHint.setAttribute("hidden", "true");
+    }
   },
 
   _handleHeightChange(aFunction, aWillShowReloadHint) {
     let heightBefore = getComputedStyle(this._permissionList).height;
     aFunction();
     let heightAfter = getComputedStyle(this._permissionList).height;
     // Showing the reload hint increases the height, we need to account for it.
     if (aWillShowReloadHint) {
@@ -7459,18 +7452,19 @@ var gIdentityHandler = {
     stateLabel.textContent = SitePermissions.getStateLabel(
       aPermission.id, aPermission.state, aPermission.inUse || false);
 
     let button = document.createElement("button");
     button.setAttribute("class", "identity-popup-permission-remove-button");
     let tooltiptext = gNavigatorBundle.getString("permissions.remove.tooltip");
     button.setAttribute("tooltiptext", tooltiptext);
     button.addEventListener("command", () => {
-      this._handleHeightChange(() =>
-        this._permissionList.removeChild(container), !this._permissionJustRemoved);
+      // Only resize the window if the reload hint was previously hidden.
+      this._handleHeightChange(() => this._permissionList.removeChild(container),
+                               this._permissionReloadHint.hasAttribute("hidden"));
       if (aPermission.inUse &&
           ["camera", "microphone", "screen"].includes(aPermission.id)) {
         let windowId = this._sharingState.windowId;
         if (aPermission.id == "screen") {
           windowId = "screen:" + windowId;
         } else {
           // If we set persistent permissions or the sharing has
           // started due to existing persistent permissions, we need
@@ -7485,18 +7479,18 @@ var gIdentityHandler = {
                 SitePermissions.remove(uri, id);
             }
           }
         }
         let mm = gBrowser.selectedBrowser.messageManager;
         mm.sendAsyncMessage("webrtc:StopSharing", windowId);
       }
       SitePermissions.remove(gBrowser.currentURI, aPermission.id);
-      this._permissionJustRemoved = true;
-      this.updatePermissionHint();
+
+      this._permissionReloadHint.removeAttribute("hidden");
 
       // Set telemetry values for clearing a permission
       let histogram = Services.telemetry.getKeyedHistogramById("WEB_PERMISSION_CLEARED");
 
       let permissionType = 0;
       if (aPermission.state == SitePermissions.ALLOW) {
         // 1 : clear permanently allowed permission
         permissionType = 1;
--- a/browser/base/content/test/general/browser_decoderDoctor.js
+++ b/browser/base/content/test/general/browser_decoderDoctor.js
@@ -47,48 +47,31 @@ function* test_decoder_doctor_notificati
 }
 
 add_task(function* test_adobe_cdm_not_found() {
   // This is only sent on Windows.
   if (AppConstants.platform != "win") {
     return;
   }
 
-  let message;
-  if (AppConstants.isPlatformAndVersionAtMost("win", "5.9")) {
-    message = gNavigatorBundle.getFormattedString("emeNotifications.drmContentDisabled.message", [""]);
-  } else {
-    message = gNavigatorBundle.getString("decoder.noCodecs.message");
-  }
-
+  let message = gNavigatorBundle.getString("decoder.noCodecs.message");
   yield test_decoder_doctor_notification("adobe-cdm-not-found", message);
 });
 
 add_task(function* test_adobe_cdm_not_activated() {
   // This is only sent on Windows.
   if (AppConstants.platform != "win") {
     return;
   }
 
-  let message;
-  if (AppConstants.isPlatformAndVersionAtMost("win", "5.9")) {
-    message = gNavigatorBundle.getString("decoder.noCodecsXP.message");
-  } else {
-    message = gNavigatorBundle.getString("decoder.noCodecs.message");
-  }
-
+  let message = gNavigatorBundle.getString("decoder.noCodecs.message");
   yield test_decoder_doctor_notification("adobe-cdm-not-activated", message);
 });
 
 add_task(function* test_platform_decoder_not_found() {
-  // Not sent on Windows XP.
-  if (AppConstants.isPlatformAndVersionAtMost("win", "5.9")) {
-    return;
-  }
-
   let message;
   let isLinux = AppConstants.platform == "linux";
   if (isLinux) {
     message = gNavigatorBundle.getString("decoder.noCodecsLinux.message");
   } else {
     message = gNavigatorBundle.getString("decoder.noHWAcceleration.message");
   }
 
--- a/browser/components/downloads/DownloadsViewUI.jsm
+++ b/browser/components/downloads/DownloadsViewUI.jsm
@@ -192,26 +192,18 @@ this.DownloadsViewUI.DownloadElementShel
     let s = DownloadsCommon.strings;
 
     let text = "";
     let tip = "";
 
     if (!this.download.stopped) {
       let totalBytes = this.download.hasProgress ? this.download.totalBytes
                                                  : -1;
-      // By default, extended status information including the individual
-      // download rate is displayed in the tooltip. The history view overrides
-      // the getter and displays the datails in the main area instead.
-      [text] = DownloadUtils.getDownloadStatusNoRate(
-                                          this.download.currentBytes,
-                                          totalBytes,
-                                          this.download.speed,
-                                          this.lastEstimatedSecondsLeft);
       let newEstimatedSecondsLeft;
-      [tip, newEstimatedSecondsLeft] = DownloadUtils.getDownloadStatus(
+      [text, newEstimatedSecondsLeft] = DownloadUtils.getDownloadStatus(
                                           this.download.currentBytes,
                                           totalBytes,
                                           this.download.speed,
                                           this.lastEstimatedSecondsLeft);
       this.lastEstimatedSecondsLeft = newEstimatedSecondsLeft;
     } else if (this.download.canceled && this.download.hasPartialData) {
       let totalBytes = this.download.hasProgress ? this.download.totalBytes
                                                  : -1;
--- a/browser/extensions/shield-recipe-client/bootstrap.js
+++ b/browser/extensions/shield-recipe-client/bootstrap.js
@@ -14,33 +14,33 @@ const REASONS = {
   ADDON_DISABLE: 4,    // The add-on is being disabled. (Also sent during uninstallation)
   ADDON_INSTALL: 5,    // The add-on is being installed.
   ADDON_UNINSTALL: 6,  // The add-on is being uninstalled.
   ADDON_UPGRADE: 7,    // The add-on is being upgraded.
   ADDON_DOWNGRADE: 8,  //The add-on is being downgraded.
 };
 
 const PREF_BRANCH = "extensions.shield-recipe-client.";
-const PREFS = {
+const DEFAULT_PREFS = {
   api_url: "https://self-repair.mozilla.org/api/v1",
   dev_mode: false,
   enabled: true,
   startup_delay_seconds: 300,
 };
 const PREF_DEV_MODE = "extensions.shield-recipe-client.dev_mode";
 const PREF_SELF_SUPPORT_ENABLED = "browser.selfsupport.enabled";
 
 let shouldRun = true;
 
 this.install = function() {
   // Self Repair only checks its pref on start, so if we disable it, wait until
   // next startup to run, unless the dev_mode preference is set.
   if (Preferences.get(PREF_SELF_SUPPORT_ENABLED, true)) {
     Preferences.set(PREF_SELF_SUPPORT_ENABLED, false);
-    if (!Services.prefs.getBoolPref(PREF_DEV_MODE, false)) {
+    if (!Preferences.get(PREF_DEV_MODE, false)) {
       shouldRun = false;
     }
   }
 };
 
 this.startup = function() {
   setDefaultPrefs();
 
@@ -77,26 +77,16 @@ this.shutdown = function(data, reason) {
     Cu.unload(`resource://shield-recipe-client/${module}`);
   }
 };
 
 this.uninstall = function() {
 };
 
 function setDefaultPrefs() {
-  const branch = Services.prefs.getDefaultBranch(PREF_BRANCH);
-  for (const [key, val] of Object.entries(PREFS)) {
+  for (const [key, val] of Object.entries(DEFAULT_PREFS)) {
+    const fullKey = PREF_BRANCH + key;
     // If someone beat us to setting a default, don't overwrite it.
-    if (branch.getPrefType(key) !== branch.PREF_INVALID)
-      continue;
-    switch (typeof val) {
-      case "boolean":
-        branch.setBoolPref(key, val);
-        break;
-      case "number":
-        branch.setIntPref(key, val);
-        break;
-      case "string":
-        branch.setCharPref(key, val);
-        break;
+    if (!Preferences.isSet(fullKey)) {
+      Preferences.set(fullKey, val);
     }
   }
 }
--- a/browser/locales/en-US/chrome/browser/browser.properties
+++ b/browser/locales/en-US/chrome/browser/browser.properties
@@ -724,17 +724,16 @@ pendingCrashReports2.label = You have an
 pendingCrashReports.viewAll = View
 pendingCrashReports.send = Send
 pendingCrashReports.alwaysSend = Always Send
 
 decoder.noCodecs.button = Learn how
 decoder.noCodecs.accesskey = L
 decoder.noCodecs.message = To play video, you may need to install Microsoft’s Media Feature Pack.
 decoder.noCodecsVista.message = To play video, you may need to install Microsoft’s Platform Update Supplement for Windows Vista.
-decoder.noCodecsXP.message = To play video, you may need to enable Adobe’s Primetime Content Decryption Module.
 decoder.noCodecsLinux.message = To play video, you may need to install the required video codecs.
 decoder.noHWAcceleration.message = To improve video quality, you may need to install Microsoft’s Media Feature Pack.
 decoder.noHWAccelerationVista.message = To improve video quality, you may need to install Microsoft’s Platform Update Supplement for Windows Vista.
 decoder.noPulseAudio.message = To play audio, you may need to install the required PulseAudio software.
 decoder.unsupportedLibavcodec.message = libavcodec may be vulnerable or is not supported, and should be updated to play video.
 
 # LOCALIZATION NOTE (captivePortal.infoMessage2):
 # Shown in a notification bar when we detect a captive portal is blocking network access
--- a/browser/locales/en-US/chrome/browser/downloads/downloads.dtd
+++ b/browser/locales/en-US/chrome/browser/downloads/downloads.dtd
@@ -14,19 +14,21 @@
      downloads and those of blocked downloads.
 
      A good rule of thumb is to try to determine the longest string possible
      that an in-progress download could display, and use that value in ch
      units.
 
      For example, in English, a long string would be:
 
-     59 minutes, 59 seconds remaining - 1022 of 1023 KB
+     59m 59s left - 1022 of 1023 KB (120.5 KB/sec)
 
-     That's 50 characters, so we set the width at 50ch.
+     Since Downloads Panel is redesigned to show the detail string including
+     the hovering case for an item or an action button.
+     Bug 1328519 is for discussing the detail rule of `downloadDetails.width`.
      -->
 <!ENTITY downloadDetails.width            "50ch">
 
 <!-- LOCALIZATION NOTE (downloadsSummary.minWidth2):
      Minimum width for the main description of the downloads summary,
      which is displayed at the bottom of the Downloads Panel if the
      number of downloads exceeds the limit that the panel can display.
 
--- a/browser/themes/linux/preferences/preferences.css
+++ b/browser/themes/linux/preferences/preferences.css
@@ -49,17 +49,17 @@
 }
 
 #linksOpenInBox {
   margin-top: 5px;
 }
 
 #advancedPrefs {
   margin-left: 0;
-  margin-right: 0; 
+  margin-right: 0;
 }
 
 #cookiesChildren::-moz-tree-image(domainCol, container) {
   list-style-image: url("moz-icon://stock/gtk-directory?size=menu");
 }
 
 #cookieInfoBox {
   border: 1px solid ThreeDShadow;
@@ -92,15 +92,14 @@
 }
 
 #syncAddDeviceLabel {
   margin-top: 1em;
   margin-bottom: 1em;
 }
 
 #noFxaAccount {
-  margin: 5px;
   line-height: 1.2em;
 }
 
 #noFxaAccount > label:first-child {
   margin-bottom: 0.6em;
 }
--- a/browser/themes/osx/preferences/preferences.css
+++ b/browser/themes/osx/preferences/preferences.css
@@ -117,15 +117,14 @@ caption {
 }
 
 #syncAddDeviceLabel {
   margin-top: 1em;
   margin-bottom: 1em;
 }
 
 #noFxaAccount {
-  margin: 12px 4px;
   line-height: 1.2em;
 }
 
 #noFxaAccount > label:first-child {
   margin-bottom: 0.6em;
 }
--- a/browser/themes/shared/aboutNetError.css
+++ b/browser/themes/shared/aboutNetError.css
@@ -11,16 +11,20 @@ body {
 }
 
 body.certerror {
   background-image: linear-gradient(-45deg, #f0d000,     #f0d000 33%,
                                             #fedc00 33%, #fedc00 66%,
                                             #f0d000 66%, #f0d000);
 }
 
+body.captiveportal .title {
+  background-image: url("wifi.svg");
+}
+
 body.certerror .title {
   background-image: url("cert-error.svg");
 }
 
 #errorContainer {
   display: none;
 }
 
new file mode 100644
--- /dev/null
+++ b/browser/themes/shared/incontent-icons/wifi.svg
@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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/. -->
+<svg version="1.1"
+     xmlns="http://www.w3.org/2000/svg"
+     width="64"
+     height="64"
+     viewBox="0 0 64 64">
+
+  <style>
+    .gray {
+      fill: #797c80;
+    }
+  </style>
+
+  <defs>
+    <clipPath id="clip-path">
+      <polygon points="32 52.35 78.88 6.06 -14.88 6.06 32 52.35"/>
+    </clipPath>
+  </defs>
+
+  <circle class="gray" cx="32" cy="52" r="6"/>
+
+  <g clip-path="url('#clip-path')">
+    <path class="gray" d="M71.63,52A39.63,39.63,0,1,1,32,12.38,39.63,39.63,0,0,1,71.63,52ZM32,7.63A44.38,44.38,0,1,0,76.38,52,44.38,44.38,0,0,0,32,7.63Z"/>
+    <path class="gray" d="M47.75,52A15.75,15.75,0,1,1,32,36.25,15.75,15.75,0,0,1,47.75,52ZM32,31.65A20.35,20.35,0,1,0,52.35,52,20.35,20.35,0,0,0,32,31.65Z"/>
+    <path class="gray" d="M59.58,52A27.58,27.58,0,1,1,32,24.42,27.58,27.58,0,0,1,59.58,52ZM32,19.38A32.63,32.63,0,1,0,64.63,52,32.63,32.63,0,0,0,32,19.38Z"/>
+  </g>
+</svg>
--- a/browser/themes/shared/incontentprefs/preferences.inc.css
+++ b/browser/themes/shared/incontentprefs/preferences.inc.css
@@ -416,19 +416,16 @@ description > html|a {
   box-shadow: 0px 0px 0px 1px #0095DD;
 }
 
 #fxaProfileImage.actionable:hover:active {
   box-shadow: 0px 0px 0px 1px #ff9500;
 }
 
 #noFxaAccount {
-  /* Overriding the margins from the base preferences.css theme file.
-     These overrides can be simplified by fixing bug 1027174 */
-  margin: 0;
   padding-top: 15px;
 }
 
 #fxaContentWrapper {
   -moz-box-flex: 1;
 }
 
 #noFxaGroup {
--- a/browser/themes/shared/jar.inc.mn
+++ b/browser/themes/shared/jar.inc.mn
@@ -118,16 +118,17 @@
   skin/classic/browser/translation-16@2x.png                   (../shared/translation/translation-16@2x.png)
   skin/classic/browser/undoCloseTab.png                        (../shared/undoCloseTab.png)
   skin/classic/browser/undoCloseTab@2x.png                     (../shared/undoCloseTab@2x.png)
   skin/classic/browser/update-badge.svg                        (../shared/update-badge.svg)
   skin/classic/browser/update-badge-failed.svg                 (../shared/update-badge-failed.svg)
   skin/classic/browser/warning.svg                             (../shared/warning.svg)
   skin/classic/browser/warning-white.svg                       (../shared/warning-white.svg)
   skin/classic/browser/cert-error.svg                          (../shared/incontent-icons/cert-error.svg)
+  skin/classic/browser/wifi.svg                                (../shared/incontent-icons/wifi.svg)
   skin/classic/browser/session-restore.svg                     (../shared/incontent-icons/session-restore.svg)
   skin/classic/browser/tab-crashed.svg                         (../shared/incontent-icons/tab-crashed.svg)
   skin/classic/browser/favicon-search-16.svg                   (../shared/favicon-search-16.svg)
   skin/classic/browser/icon-search-64.svg                      (../shared/incontent-icons/icon-search-64.svg)
   skin/classic/browser/welcome-back.svg                        (../shared/incontent-icons/welcome-back.svg)
   skin/classic/browser/reader-tour.png                         (../shared/reader/reader-tour.png)
   skin/classic/browser/reader-tour@2x.png                      (../shared/reader/reader-tour@2x.png)
   skin/classic/browser/readerMode.svg                          (../shared/reader/readerMode.svg)
--- a/browser/themes/windows/preferences/preferences.css
+++ b/browser/themes/windows/preferences/preferences.css
@@ -83,15 +83,14 @@
 }
 
 #syncAddDeviceLabel {
   margin-top: 1em;
   margin-bottom: 1em;
 }
 
 #noFxaAccount {
-  margin: 6px;
   line-height: 1.2em;
 }
 
 #noFxaAccount > label:first-child {
   margin-bottom: 0.6em;
 }
--- a/browser/tools/mozscreenshots/mozscreenshots/extension/configurations/PermissionPrompts.jsm
+++ b/browser/tools/mozscreenshots/mozscreenshots/extension/configurations/PermissionPrompts.jsm
@@ -15,17 +15,16 @@ Cu.import("resource://testing-common/Con
 Cu.import("resource://testing-common/BrowserTestUtils.jsm");
 
 const URL = "https://test1.example.com/extensions/mozscreenshots/browser/chrome/mozscreenshots/lib/permissionPrompts.html";
 let lastTab = null;
 
 this.PermissionPrompts = {
   init(libDir) {
     Services.prefs.setBoolPref("media.navigator.permission.fake", true);
-    Services.prefs.setBoolPref("media.getusermedia.screensharing.allow_on_old_platforms", true);
     Services.prefs.setCharPref("media.getusermedia.screensharing.allowed_domains",
                                "test1.example.com");
     Services.prefs.setBoolPref("extensions.install.requireBuiltInCerts", false);
     Services.prefs.setBoolPref("signon.rememberSignons", true);
   },
 
   configurations: {
     shareDevices: {
--- a/devtools/client/framework/test/shared-head.js
+++ b/devtools/client/framework/test/shared-head.js
@@ -251,16 +251,51 @@ function waitForNEvents(target, eventNam
       break;
     }
   }
 
   return deferred.promise;
 }
 
 /**
+ * Wait for DOM change on target.
+ *
+ * @param {Object} target
+ *        The Node on which to observe DOM mutations.
+ * @param {String} selector
+ *        Given a selector to watch whether the expected element is changed
+ *        on target.
+ * @param {Number} expectedLength
+ *        Optional, default set to 1
+ *        There may be more than one element match an array match the selector,
+ *        give an expected length to wait for more elements.
+ * @return A promise that resolves when the event has been handled
+ */
+function waitForDOM(target, selector, expectedLength = 1) {
+  return new Promise((resolve) => {
+    let observer = new MutationObserver((mutations) => {
+      mutations.forEach((mutation) => {
+        let elements = mutation.target.querySelectorAll(selector);
+
+        if (elements.length === expectedLength) {
+          observer.disconnect();
+          resolve(elements);
+        }
+      });
+    });
+
+    observer.observe(target, {
+      attributes: true,
+      childList: true,
+      subtree: true,
+    });
+  });
+}
+
+/**
  * Wait for eventName on target.
  *
  * @param {Object} target
  *        An observable object that either supports on/off or
  *        addEventListener/removeEventListener
  * @param {String} eventName
  * @param {Boolean} useCapture
  *        Optional, for addEventListener/removeEventListener
@@ -459,21 +494,24 @@ function waitForContextMenu(popup, butto
     onHidden && onHidden();
 
     deferred.resolve(popup);
   }
 
   popup.addEventListener("popupshown", onPopupShown);
 
   info("wait for the context menu to open");
-  button.scrollIntoView();
+  synthesizeContextMenuEvent(button);
+  return deferred.promise;
+}
+
+function synthesizeContextMenuEvent(el) {
+  el.scrollIntoView();
   let eventDetails = {type: "contextmenu", button: 2};
-  EventUtils.synthesizeMouse(button, 5, 2, eventDetails,
-                             button.ownerDocument.defaultView);
-  return deferred.promise;
+  EventUtils.synthesizeMouse(el, 5, 2, eventDetails, el.ownerDocument.defaultView);
 }
 
 /**
  * Promise wrapper around SimpleTest.waitForClipboard
  */
 function waitForClipboardPromise(setup, expected) {
   return new Promise((resolve, reject) => {
     SimpleTest.waitForClipboard(expected, setup, resolve, reject);
--- a/devtools/client/locales/en-US/webconsole.properties
+++ b/devtools/client/locales/en-US/webconsole.properties
@@ -196,8 +196,46 @@ webconsole.find.key=CmdOrCtrl+F
 # LOCALIZATION NOTE (webconsole.close.key)
 # Key shortcut used to close the Browser console (doesn't work in regular web console)
 webconsole.close.key=CmdOrCtrl+W
 
 # LOCALIZATION NOTE (webconsole.clear.key*)
 # Key shortcut used to clear the console output
 webconsole.clear.key=Ctrl+Shift+L
 webconsole.clear.keyOSX=Ctrl+L
+
+
+# LOCALIZATION NOTE (webconsole.menu.copyURL.label)
+# Label used for a context-menu item displayed for network message logs. Clicking on it
+# copies the URL displayed in the message to the clipboard.
+webconsole.menu.copyURL.label=Copy Link Location
+webconsole.menu.copyURL.accesskey=a
+
+# LOCALIZATION NOTE (webconsole.menu.openURL.label)
+# Label used for a context-menu item displayed for network message logs. Clicking on it
+# opens the URL displayed in a new browser tab.
+webconsole.menu.openURL.label=Open URL in New Tab
+webconsole.menu.openURL.accesskey=T
+
+# LOCALIZATION NOTE (webconsole.menu.openInVarView.label)
+# Label used for a context-menu item displayed for object/variable logs. Clicking on it
+# opens the webconsole variable view for the logged variable.
+webconsole.menu.openInVarView.label=Open in Variables View
+webconsole.menu.openInVarView.accesskey=V
+
+# LOCALIZATION NOTE (webconsole.menu.storeAsGlobalVar.label)
+# Label used for a context-menu item displayed for object/variable logs. Clicking on it
+# creates a new global variable pointing to the logged variable.
+webconsole.menu.storeAsGlobalVar.label=Store as global variable
+webconsole.menu.storeAsGlobalVar.accesskey=S
+
+# LOCALIZATION NOTE (webconsole.menu.copy.label)
+# Label used for a context-menu item displayed for any log. Clicking on it will copy the
+# content of the log (or the user selection, if any).
+webconsole.menu.copy.label=Copy
+webconsole.menu.copy.accesskey=C
+
+# LOCALIZATION NOTE (webconsole.menu.selectAll.label)
+# Label used for a context-menu item that will select all the content of the webconsole
+# output.
+webconsole.menu.selectAll.label=Select all
+webconsole.menu.selectAll.accesskey=A
+
--- a/devtools/client/netmonitor/constants.js
+++ b/devtools/client/netmonitor/constants.js
@@ -1,17 +1,17 @@
 /* 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/. */
 
 "use strict";
 
 const general = {
   CONTENT_SIZE_DECIMALS: 2,
-  FREETEXT_FILTER_SEARCH_DELAY: 200,
+  FILTER_SEARCH_DELAY: 200,
   REQUEST_TIME_DECIMALS: 2,
 };
 
 const actionTypes = {
   ADD_REQUEST: "ADD_REQUEST",
   ADD_TIMING_MARKER: "ADD_TIMING_MARKER",
   BATCH_ACTIONS: "BATCH_ACTIONS",
   BATCH_ENABLE: "BATCH_ENABLE",
--- a/devtools/client/netmonitor/details-view.js
+++ b/devtools/client/netmonitor/details-view.js
@@ -16,24 +16,22 @@ const { ToolSidebar } = require("devtool
 const { VariablesView } = require("resource://devtools/client/shared/widgets/VariablesView.jsm");
 const { VariablesViewController } = require("resource://devtools/client/shared/widgets/VariablesViewController.jsm");
 const { EVENTS } = require("./events");
 const { L10N } = require("./l10n");
 const { Filters } = require("./filter-predicates");
 const {
   decodeUnicodeUrl,
   formDataURI,
-  getFormDataSections,
   getUrlBaseName,
-  getUrlQuery,
-  parseQueryString,
 } = require("./request-utils");
 const { createFactory } = require("devtools/client/shared/vendor/react");
 const ReactDOM = require("devtools/client/shared/vendor/react-dom");
 const Provider = createFactory(require("devtools/client/shared/vendor/react-redux").Provider);
+const ParamsPanel = createFactory(require("./shared/components/params-panel"));
 const PreviewPanel = createFactory(require("./shared/components/preview-panel"));
 const SecurityPanel = createFactory(require("./shared/components/security-panel"));
 const TimingsPanel = createFactory(require("./shared/components/timings-panel"));
 
 // 100 KB in bytes
 const SOURCE_SYNTAX_HIGHLIGHT_MAX_FILE_SIZE = 102400;
 const HEADERS_SIZE_DECIMALS = 3;
 const CONTENT_MIME_TYPE_MAPPINGS = {
@@ -89,16 +87,23 @@ DetailsView.prototype = {
   },
 
   /**
    * Initialization function, called when the network monitor is started.
    */
   initialize: function (store) {
     dumpn("Initializing the DetailsView");
 
+    this._paramsPanelNode = $("#react-params-tabpanel-hook");
+
+    ReactDOM.render(Provider(
+      { store },
+      ParamsPanel()
+    ), this._paramsPanelNode);
+
     this._previewPanelNode = $("#react-preview-tabpanel-hook");
 
     ReactDOM.render(Provider(
       { store },
       PreviewPanel()
     ), this._previewPanelNode);
 
     this._securityPanelNode = $("#react-security-tabpanel-hook");
@@ -126,45 +131,38 @@ DetailsView.prototype = {
         emptyText: L10N.getStr("headersEmptyText"),
         searchPlaceholder: L10N.getStr("headersFilterText")
       }));
     this._cookies = new VariablesView($("#all-cookies"),
       Heritage.extend(GENERIC_VARIABLES_VIEW_SETTINGS, {
         emptyText: L10N.getStr("cookiesEmptyText"),
         searchPlaceholder: L10N.getStr("cookiesFilterText")
       }));
-    this._params = new VariablesView($("#request-params"),
-      Heritage.extend(GENERIC_VARIABLES_VIEW_SETTINGS, {
-        emptyText: L10N.getStr("paramsEmptyText"),
-        searchPlaceholder: L10N.getStr("paramsFilterText")
-      }));
     this._json = new VariablesView($("#response-content-json"),
       Heritage.extend(GENERIC_VARIABLES_VIEW_SETTINGS, {
         onlyEnumVisible: true,
         searchPlaceholder: L10N.getStr("jsonFilterText")
       }));
     VariablesViewController.attach(this._json);
 
-    this._paramsQueryString = L10N.getStr("paramsQueryString");
-    this._paramsFormData = L10N.getStr("paramsFormData");
-    this._paramsPostPayload = L10N.getStr("paramsPostPayload");
     this._requestHeaders = L10N.getStr("requestHeaders");
     this._requestHeadersFromUpload = L10N.getStr("requestHeadersFromUpload");
     this._responseHeaders = L10N.getStr("responseHeaders");
     this._requestCookies = L10N.getStr("requestCookies");
     this._responseCookies = L10N.getStr("responseCookies");
 
     $("tabpanels", this.widget).addEventListener("select", this._onTabSelect);
   },
 
   /**
    * Destruction function, called when the network monitor is closed.
    */
   destroy: function () {
     dumpn("Destroying the DetailsView");
+    ReactDOM.unmountComponentAtNode(this._paramsPanelNode);
     ReactDOM.unmountComponentAtNode(this._previewPanelNode);
     ReactDOM.unmountComponentAtNode(this._securityPanelNode);
     ReactDOM.unmountComponentAtNode(this._timingsPanelNode);
     this.sidebar.destroy();
     $("tabpanels", this.widget).removeEventListener("select",
       this._onTabSelect);
   },
 
@@ -172,19 +170,16 @@ DetailsView.prototype = {
    * Populates this view with the specified data.
    *
    * @param object data
    *        The data source (this should be the attachment of a request item).
    * @return object
    *        Returns a promise that resolves upon population the view.
    */
   populate: function (data) {
-    $("#request-params-box").setAttribute("flex", "1");
-    $("#request-params-box").hidden = false;
-    $("#request-post-data-textarea-box").hidden = true;
     $("#response-content-info-header").hidden = true;
     $("#response-content-json-box").hidden = true;
     $("#response-content-textarea-box").hidden = true;
     $("#raw-headers").hidden = true;
     $("#response-content-image-box").hidden = true;
 
     let isHtml = Filters.html(data);
 
@@ -205,17 +200,16 @@ DetailsView.prototype = {
     if (!isHtml && this.widget.selectedPanel === $("#preview-tabpanel") ||
         !hasSecurityInfo && this.widget.selectedPanel ===
           $("#security-tabpanel")) {
       this.widget.selectedIndex = 0;
     }
 
     this._headers.empty();
     this._cookies.empty();
-    this._params.empty();
     this._json.empty();
 
     this._dataSrc = { src: data, populated: [] };
     this._onTabSelect();
     window.emit(EVENTS.NETWORKDETAILSVIEW_POPULATED);
 
     return promise.resolve();
   },
@@ -255,24 +249,16 @@ DetailsView.prototype = {
             src.requestHeaders,
             src.requestHeadersFromUploadStream);
           break;
         // "Cookies"
         case 1:
           yield view._setResponseCookies(src.responseCookies);
           yield view._setRequestCookies(src.requestCookies);
           break;
-        // "Params"
-        case 2:
-          yield view._setRequestGetParams(src.url);
-          yield view._setRequestPostParams(
-            src.requestHeaders,
-            src.requestHeadersFromUploadStream,
-            src.requestPostData);
-          break;
         // "Response"
         case 3:
           yield view._setResponseBody(src.url, src.responseContent);
           break;
       }
       viewState.updating[tab] = false;
     }).then(() => {
       if (tab == this.widget.selectedIndex) {
@@ -481,118 +467,16 @@ DetailsView.prototype = {
       }
       cookieVar.populate(rawObject);
       cookieVar.twisty = true;
       cookieVar.expanded = true;
     }
   }),
 
   /**
-   * Sets the network request get params shown in this view.
-   *
-   * @param string url
-   *        The request's url.
-   */
-  _setRequestGetParams: function (url) {
-    let query = getUrlQuery(url);
-    if (query) {
-      this._addParams(this._paramsQueryString, query);
-    }
-  },
-
-  /**
-   * Sets the network request post params shown in this view.
-   *
-   * @param object headers
-   *        The "requestHeaders" message received from the server.
-   * @param object uploadHeaders
-   *        The "requestHeadersFromUploadStream" inferred from the POST payload.
-   * @param object postData
-   *        The "requestPostData" message received from the server.
-   * @return object
-   *        A promise that is resolved when the request post params are set.
-   */
-  _setRequestPostParams: Task.async(function* (headers, uploadHeaders,
-    postData) {
-    if (!headers || !uploadHeaders || !postData) {
-      return;
-    }
-
-    let formDataSections = yield getFormDataSections(
-      headers,
-      uploadHeaders,
-      postData,
-      gNetwork.getString.bind(gNetwork));
-
-    this._params.onlyEnumVisible = false;
-
-    // Handle urlencoded form data sections (e.g. "?foo=bar&baz=42").
-    if (formDataSections.length > 0) {
-      formDataSections.forEach(section => {
-        this._addParams(this._paramsFormData, section);
-      });
-    } else {
-      // Handle JSON and actual forms ("multipart/form-data" content type).
-      let postDataLongString = postData.postData.text;
-      let text = yield gNetwork.getString(postDataLongString);
-      let jsonVal = null;
-      try {
-        jsonVal = JSON.parse(text);
-      } catch (ex) { // eslint-disable-line
-      }
-
-      if (jsonVal) {
-        this._params.onlyEnumVisible = true;
-        let jsonScopeName = L10N.getStr("jsonScopeName");
-        let jsonScope = this._params.addScope(jsonScopeName);
-        jsonScope.expanded = true;
-        let jsonItem = jsonScope.addItem(undefined, { enumerable: true });
-        jsonItem.populate(jsonVal, { sorted: true });
-      } else {
-        // This is really awkward, but hey, it works. Let's show an empty
-        // scope in the params view and place the source editor containing
-        // the raw post data directly underneath.
-        $("#request-params-box").removeAttribute("flex");
-        let paramsScope = this._params.addScope(this._paramsPostPayload);
-        paramsScope.expanded = true;
-        paramsScope.locked = true;
-
-        $("#request-post-data-textarea-box").hidden = false;
-        let editor = yield NetMonitorView.editor("#request-post-data-textarea");
-        editor.setMode(Editor.modes.text);
-        editor.setText(text);
-      }
-    }
-
-    window.emit(EVENTS.REQUEST_POST_PARAMS_DISPLAYED);
-  }),
-
-  /**
-   * Populates the params container in this view with the specified data.
-   *
-   * @param string name
-   *        The type of params to populate (get or post).
-   * @param string queryString
-   *        A query string of params (e.g. "?foo=bar&baz=42").
-   */
-  _addParams: function (name, queryString) {
-    let paramsArray = parseQueryString(queryString);
-    if (!paramsArray) {
-      return;
-    }
-    let paramsScope = this._params.addScope(name);
-    paramsScope.expanded = true;
-
-    for (let param of paramsArray) {
-      let paramVar = paramsScope.addItem(param.name, {}, {relaxed: true});
-      paramVar.setGrip(param.value);
-    }
-  },
-
-  /**
    * Sets the network response body shown in this view.
    *
    * @param string url
    *        The request's url.
    * @param object response
    *        The message received from the server.
    * @return object
    *         A promise that is resolved when the response body is set.
@@ -700,20 +584,16 @@ DetailsView.prototype = {
     }
 
     window.emit(EVENTS.RESPONSE_BODY_DISPLAYED);
   }),
 
   _dataSrc: null,
   _headers: null,
   _cookies: null,
-  _params: null,
   _json: null,
-  _paramsQueryString: "",
-  _paramsFormData: "",
-  _paramsPostPayload: "",
   _requestHeaders: "",
   _responseHeaders: "",
   _requestCookies: "",
   _responseCookies: ""
 };
 
 exports.DetailsView = DetailsView;
--- a/devtools/client/netmonitor/netmonitor.xul
+++ b/devtools/client/netmonitor/netmonitor.xul
@@ -207,24 +207,18 @@
               <tabpanel id="cookies-tabpanel"
                         class="tabpanel-content">
                 <vbox flex="1">
                   <vbox id="all-cookies" flex="1"/>
                 </vbox>
               </tabpanel>
               <tabpanel id="params-tabpanel"
                         class="tabpanel-content">
-                <vbox flex="1">
-                  <vbox id="request-params-box" flex="1" hidden="true">
-                    <vbox id="request-params" flex="1"/>
-                  </vbox>
-                  <vbox id="request-post-data-textarea-box" flex="1" hidden="true">
-                    <vbox id="request-post-data-textarea" flex="1"/>
-                  </vbox>
-                </vbox>
+                <html:div xmlns="http://www.w3.org/1999/xhtml"
+                          id="react-params-tabpanel-hook"/>
               </tabpanel>
               <tabpanel id="response-tabpanel"
                         class="tabpanel-content">
                 <vbox flex="1">
                   <label id="response-content-info-header"/>
                   <vbox id="response-content-json-box" flex="1" hidden="true">
                     <vbox id="response-content-json" flex="1" context="network-response-popup" />
                   </vbox>
--- a/devtools/client/netmonitor/reducers/requests.js
+++ b/devtools/client/netmonitor/reducers/requests.js
@@ -9,17 +9,17 @@ const { getUrlDetails } = require("../re
 const {
   ADD_REQUEST,
   UPDATE_REQUEST,
   CLEAR_REQUESTS,
   SELECT_REQUEST,
   PRESELECT_REQUEST,
   CLONE_SELECTED_REQUEST,
   REMOVE_SELECTED_CUSTOM_REQUEST,
-  OPEN_SIDEBAR
+  OPEN_SIDEBAR,
 } = require("../constants");
 
 const Request = I.Record({
   id: null,
   // Set to true in case of a request that's being edited as part of "edit and resend"
   isCustom: false,
   // Request properties - at the beginning, they are unknown and are gradually filled in
   startedMillis: undefined,
@@ -32,30 +32,31 @@ const Request = I.Record({
   cause: undefined,
   fromCache: undefined,
   fromServiceWorker: undefined,
   status: undefined,
   statusText: undefined,
   httpVersion: undefined,
   securityState: undefined,
   securityInfo: undefined,
-  mimeType: undefined,
+  mimeType: "text/plain",
   contentSize: undefined,
   transferredSize: undefined,
   totalTime: undefined,
   eventTimings: undefined,
   headersSize: undefined,
   requestHeaders: undefined,
   requestHeadersFromUploadStream: undefined,
   requestCookies: undefined,
   requestPostData: undefined,
   responseHeaders: undefined,
   responseCookies: undefined,
   responseContent: undefined,
   responseContentDataUri: undefined,
+  formDataSections: undefined,
 });
 
 const Requests = I.Record({
   // The request list
   requests: I.List(),
   // Selection state
   selectedId: null,
   preselectedId: null,
@@ -82,17 +83,18 @@ const UPDATE_PROPS = [
   "headersSize",
   "requestHeaders",
   "requestHeadersFromUploadStream",
   "requestCookies",
   "requestPostData",
   "responseHeaders",
   "responseCookies",
   "responseContent",
-  "responseContentDataUri"
+  "responseContentDataUri",
+  "formDataSections",
 ];
 
 function requestsReducer(state = new Requests(), action) {
   switch (action.type) {
     case ADD_REQUEST: {
       return state.withMutations(st => {
         let newRequest = new Request(Object.assign(
           { id: action.id },
@@ -134,23 +136,16 @@ function requestsReducer(state = new Req
 
           request[key] = value;
 
           switch (key) {
             case "url":
               // Compute the additional URL details
               request.urlDetails = getUrlDetails(value);
               break;
-            case "responseContent":
-              // If there's no mime type available when the response content
-              // is received, assume text/plain as a fallback.
-              if (!request.mimeType) {
-                request.mimeType = "text/plain";
-              }
-              break;
             case "totalTime":
               const endedMillis = request.startedMillis + value;
               lastEndedMillis = Math.max(lastEndedMillis, endedMillis);
               break;
             case "requestPostData":
               request.requestHeadersFromUploadStream = {
                 headers: [],
                 headersSize: 0,
--- a/devtools/client/netmonitor/requests-menu-view.js
+++ b/devtools/client/netmonitor/requests-menu-view.js
@@ -18,25 +18,26 @@ const { Provider } = require("devtools/c
 const RequestList = createFactory(require("./components/request-list"));
 const RequestListContextMenu = require("./request-list-context-menu");
 const Actions = require("./actions/index");
 const { Prefs } = require("./prefs");
 
 const {
   formDataURI,
   writeHeaderText,
-  loadCauseString
+  loadCauseString,
+  getFormDataSections,
 } = require("./request-utils");
 
 const {
   getActiveFilters,
   getSortedRequests,
   getDisplayedRequests,
   getRequestById,
-  getSelectedRequest
+  getSelectedRequest,
 } = require("./selectors/index");
 
 // ms
 const RESIZE_REFRESH_RATE = 50;
 
 // A smart store watcher to notify store changes as necessary
 function storeWatcher(initialValue, reduceValue, onChange) {
   let currentValue = initialValue;
@@ -83,16 +84,67 @@ RequestsMenuView.prototype = {
 
     // Watch the sidebar status and resize the waterfall column on change
     this.store.subscribe(storeWatcher(
       false,
       () => this.store.getState().ui.sidebarOpen,
       () => this.onResize()
     ));
 
+    // Watch the requestHeaders, requestHeadersFromUploadStream and requestPostData
+    // in order to update formDataSections for composing form data
+    this.store.subscribe(storeWatcher(
+      false,
+      (currentRequest) => {
+        const request = getSelectedRequest(this.store.getState());
+        if (!request) {
+          return {};
+        }
+
+        const isChanged = request.requestHeaders !== currentRequest.requestHeaders ||
+        request.requestHeadersFromUploadStream !==
+        currentRequest.requestHeadersFromUploadStream ||
+        request.requestPostData !== currentRequest.requestPostData;
+
+        if (isChanged) {
+          return {
+            id: request.id,
+            requestHeaders: request.requestHeaders,
+            requestHeadersFromUploadStream: request.requestHeadersFromUploadStream,
+            requestPostData: request.requestPostData,
+          };
+        }
+
+        return currentRequest;
+      },
+      (newRequest) => {
+        const {
+          id,
+          requestHeaders,
+          requestHeadersFromUploadStream,
+          requestPostData,
+        } = newRequest;
+
+        if (requestHeaders && requestHeadersFromUploadStream && requestPostData) {
+          getFormDataSections(
+            requestHeaders,
+            requestHeadersFromUploadStream,
+            requestPostData,
+            gNetwork.getString.bind(gNetwork),
+          ).then((formDataSections) => {
+            this.store.dispatch(Actions.updateRequest(
+              id,
+              { formDataSections },
+              true,
+            ));
+          });
+        }
+      },
+    ));
+
     this.sendCustomRequestEvent = this.sendCustomRequest.bind(this);
     this.closeCustomRequestEvent = this.closeCustomRequest.bind(this);
     this.cloneSelectedRequestEvent = this.cloneSelectedRequest.bind(this);
     this.toggleRawHeadersEvent = this.toggleRawHeaders.bind(this);
 
     $("#toggle-raw-headers")
       .addEventListener("click", this.toggleRawHeadersEvent, false);
 
--- a/devtools/client/netmonitor/shared/components/moz.build
+++ b/devtools/client/netmonitor/shared/components/moz.build
@@ -1,11 +1,12 @@
 # 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/.
 
 DevToolsModules(
     'editor.js',
+    'params-panel.js',
     'preview-panel.js',
     'properties-view.js',
     'security-panel.js',
     'timings-panel.js',
 )
new file mode 100644
--- /dev/null
+++ b/devtools/client/netmonitor/shared/components/params-panel.js
@@ -0,0 +1,128 @@
+/* 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/. */
+
+"use strict";
+
+const {
+  createFactory,
+  DOM,
+  PropTypes,
+} = require("devtools/client/shared/vendor/react");
+const { connect } = require("devtools/client/shared/vendor/react-redux");
+const { L10N } = require("../../l10n");
+const { getSelectedRequest } = require("../../selectors/index");
+const { getUrlQuery, parseQueryString } = require("../../request-utils");
+
+// Components
+const PropertiesView = createFactory(require("./properties-view"));
+
+const { div } = DOM;
+
+const JSON_SCOPE_NAME = L10N.getStr("jsonScopeName");
+const PARAMS_EMPTY_TEXT = L10N.getStr("paramsEmptyText");
+const PARAMS_FILTER_TEXT = L10N.getStr("paramsFilterText");
+const PARAMS_FORM_DATA = L10N.getStr("paramsFormData");
+const PARAMS_POST_PAYLOAD = L10N.getStr("paramsPostPayload");
+const PARAMS_QUERY_STRING = L10N.getStr("paramsQueryString");
+const SECTION_NAMES = [
+  JSON_SCOPE_NAME,
+  PARAMS_FORM_DATA,
+  PARAMS_POST_PAYLOAD,
+  PARAMS_QUERY_STRING,
+];
+
+/*
+ * Params panel component
+ * Displays the GET parameters and POST data of a request
+ */
+function ParamsPanel({
+  formDataSections,
+  mimeType,
+  postData,
+  query,
+}) {
+  if (!formDataSections && !postData && !query) {
+    return div({ className: "empty-notice" },
+      PARAMS_EMPTY_TEXT
+    );
+  }
+
+  let object = {};
+  let json;
+
+  // Query String section
+  if (query) {
+    object[PARAMS_QUERY_STRING] =
+      parseQueryString(query)
+        .reduce((acc, { name, value }) =>
+          name ? Object.assign(acc, { [name]: value }) : acc
+        , {});
+  }
+  // Form Data section
+  if (formDataSections && formDataSections.length > 0) {
+    let sections = formDataSections.filter((str) => /\S/.test(str)).join("&");
+    object[PARAMS_FORM_DATA] =
+      parseQueryString(sections)
+        .reduce((acc, { name, value }) =>
+          name ? Object.assign(acc, { [name]: value }) : acc
+        , {});
+  }
+
+  // Request payload section
+  if (formDataSections && formDataSections.length === 0 && postData) {
+    try {
+      json = JSON.parse(postData);
+    } catch (error) {
+      // Continue regardless of parsing error
+    }
+
+    if (json) {
+      object[JSON_SCOPE_NAME] = json;
+    } else {
+      object[PARAMS_POST_PAYLOAD] = {
+        EDITOR_CONFIG: {
+          text: postData,
+          mode: mimeType.replace(/;.+/, ""),
+        },
+      };
+    }
+  } else {
+    postData = "";
+  }
+
+  return (
+    PropertiesView({
+      object,
+      filterPlaceHolder: PARAMS_FILTER_TEXT,
+      sectionNames: SECTION_NAMES,
+    })
+  );
+}
+
+ParamsPanel.displayName = "ParamsPanel";
+
+ParamsPanel.propTypes = {
+  formDataSections: PropTypes.array,
+  postData: PropTypes.string,
+  query: PropTypes.string,
+};
+
+module.exports = connect(
+  (state) => {
+    const selectedRequest = getSelectedRequest(state);
+
+    if (selectedRequest) {
+      const { formDataSections, mimeType, requestPostData, url } = selectedRequest;
+
+      return {
+        formDataSections,
+        mimeType,
+        postData: requestPostData ? requestPostData.postData.text : null,
+        query: getUrlQuery(url),
+      };
+    }
+
+    return {};
+  }
+)(ParamsPanel);
--- a/devtools/client/netmonitor/shared/components/properties-view.js
+++ b/devtools/client/netmonitor/shared/components/properties-view.js
@@ -17,25 +17,27 @@ const { FILTER_SEARCH_DELAY } = require(
 // Components
 const Editor = createFactory(require("devtools/client/netmonitor/shared/components/editor"));
 const SearchBox = createFactory(require("devtools/client/shared/components/search-box"));
 const TreeView = createFactory(require("devtools/client/shared/components/tree/tree-view"));
 const TreeRow = createFactory(require("devtools/client/shared/components/tree/tree-row"));
 const { Rep } = createFactories(require("devtools/client/shared/components/reps/rep"));
 
 const { div, tr, td } = DOM;
+const AUTO_EXPAND_MAX_LEVEL = 7;
+const EDITOR_CONFIG_ID = "EDITOR_CONFIG";
 
 /*
  * Properties View component
  * A scrollable tree view component which provides some useful features for
  * representing object properties.
  *
  * Search filter - Set enableFilter to enable / disable SearchBox feature.
  * Tree view - Default enabled.
- * Source editor - Enable by specifying object level 1 property name to "editorText".
+ * Source editor - Enable by specifying object level 1 property name to EDITOR_CONFIG_ID.
  * Rep - Default enabled.
  */
 const PropertiesView = createClass({
   displayName: "PropertiesView",
 
   propTypes: {
     object: PropTypes.object,
     enableInput: PropTypes.bool,
@@ -71,28 +73,34 @@ const PropertiesView = createClass({
       return true;
     }
 
     let jsonString = JSON.stringify({ [name]: value }).toLowerCase();
     return jsonString.includes(filterText.toLowerCase());
   },
 
   renderRowWithEditor(props) {
-    const { level, name, value } = props.member;
-    // Display source editor when prop name specify to editorText
-    if (level === 1 && name === "editorText") {
+    const { level, name, value, path } = props.member;
+
+    // Display source editor when specifying to EDITOR_CONFIG_ID along with config
+    if (level === 1 && name === EDITOR_CONFIG_ID) {
       return (
         tr({},
           td({ colSpan: 2 },
-            Editor({ text: value })
+            Editor(value)
           )
         )
       );
     }
 
+    // Skip for editor config
+    if (level >= 1 && path.includes(EDITOR_CONFIG_ID)) {
+      return null;
+    }
+
     return TreeRow(props);
   },
 
   renderValueWithRep(props) {
     // Hide rep summary for sections
     if (props.member.level === 0) {
       return null;
     }
@@ -101,58 +109,85 @@ const PropertiesView = createClass({
       // FIXME: A workaround for the issue in StringRep
       // Force StringRep to crop the text everytime
       member: Object.assign({}, props.member, { open: false }),
       mode: MODE.TINY,
       cropLimit: 60,
     }));
   },
 
+  shouldRenderSearchBox(object) {
+    return this.props.enableFilter && object && Object.keys(object)
+      .filter((section) => !object[section][EDITOR_CONFIG_ID]).length > 0;
+  },
+
   updateFilterText(filterText) {
     this.setState({
       filterText,
     });
   },
 
+  getExpandedNodes: function (object, path = "", level = 0) {
+    if (typeof object != "object") {
+      return null;
+    }
+
+    if (level > AUTO_EXPAND_MAX_LEVEL) {
+      return null;
+    }
+
+    let expandedNodes = new Set();
+    for (let prop in object) {
+      let nodePath = path + "/" + prop;
+      expandedNodes.add(nodePath);
+
+      let nodes = this.getExpandedNodes(object[prop], nodePath, level + 1);
+      if (nodes) {
+        expandedNodes = new Set([...expandedNodes, ...nodes]);
+      }
+    }
+    return expandedNodes;
+  },
+
   render() {
     const {
       object,
       decorator,
       enableInput,
-      enableFilter,
       expandableStrings,
       filterPlaceHolder,
       renderRow,
       renderValue,
       sectionNames,
     } = this.props;
 
     return (
       div({ className: "properties-view" },
-        enableFilter && div({ className: "searchbox-section" },
-          SearchBox({
-            delay: FILTER_SEARCH_DELAY,
-            type: "filter",
-            onChange: this.updateFilterText,
-            placeholder: filterPlaceHolder,
-          }),
-        ),
+        this.shouldRenderSearchBox(object) &&
+          div({ className: "searchbox-section" },
+            SearchBox({
+              delay: FILTER_SEARCH_DELAY,
+              type: "filter",
+              onChange: this.updateFilterText,
+              placeholder: filterPlaceHolder,
+            }),
+          ),
         div({ className: "tree-container" },
           TreeView({
             object,
             columns: [{
               id: "value",
               width: "100%",
             }],
             decorator: decorator || {
               getRowClass: (rowObject) => this.getRowClass(rowObject, sectionNames),
             },
             enableInput,
             expandableStrings,
-            expandedNodes: new Set(sectionNames.map((sec) => "/" + sec)),
+            expandedNodes: this.getExpandedNodes(object),
             onFilter: (props) => this.onFilter(props, sectionNames),
             renderRow: renderRow || this.renderRowWithEditor,
             renderValue: renderValue || this.renderValueWithRep,
           }),
         ),
       )
     );
   }
--- a/devtools/client/netmonitor/test/browser_net_cached-status.js
+++ b/devtools/client/netmonitor/test/browser_net_cached-status.js
@@ -10,17 +10,16 @@
 add_task(function* () {
   let { tab, monitor } = yield initNetMonitor(STATUS_CODES_URL, null, true);
   info("Starting test... ");
 
   let { NetMonitorView } = monitor.panelWin;
   let { RequestsMenu, NetworkDetails } = NetMonitorView;
 
   RequestsMenu.lazyUpdate = false;
-  NetworkDetails._params.lazyEmpty = false;
 
   const REQUEST_DATA = [
     {
       method: "GET",
       uri: STATUS_CODES_SJS + "?sts=ok&cached",
       details: {
         status: 200,
         statusText: "OK",
--- a/devtools/client/netmonitor/test/browser_net_complex-params.js
+++ b/devtools/client/netmonitor/test/browser_net_complex-params.js
@@ -9,187 +9,174 @@
  */
 
 add_task(function* () {
   let { L10N } = require("devtools/client/netmonitor/l10n");
 
   let { tab, monitor } = yield initNetMonitor(PARAMS_URL);
   info("Starting test... ");
 
-  let { document, EVENTS, Editor, NetMonitorView } = monitor.panelWin;
-  let { RequestsMenu, NetworkDetails } = NetMonitorView;
+  let { document, NetMonitorView } = monitor.panelWin;
+  let { RequestsMenu } = NetMonitorView;
 
   RequestsMenu.lazyUpdate = false;
-  NetworkDetails._params.lazyEmpty = false;
 
   let wait = waitForNetworkEvents(monitor, 1, 6);
   yield ContentTask.spawn(tab.linkedBrowser, {}, function* () {
     content.wrappedJSObject.performRequests();
   });
   yield wait;
 
-  let onEvent = monitor.panelWin.once(EVENTS.REQUEST_POST_PARAMS_DISPLAYED);
+  wait = waitForDOM(document, "#params-tabpanel .tree-section", 2);
   EventUtils.sendMouseEvent({ type: "mousedown" },
     document.getElementById("details-pane-toggle"));
   EventUtils.sendMouseEvent({ type: "mousedown" },
     document.querySelectorAll("#details-pane tab")[2]);
-  yield onEvent;
-  yield testParamsTab1("a", '""', '{ "foo": "bar" }', '""');
+  yield wait;
+  testParamsTab1("a", '""', '{ "foo": "bar" }', '""');
 
-  onEvent = monitor.panelWin.once(EVENTS.REQUEST_POST_PARAMS_DISPLAYED);
+  wait = waitForDOM(document, "#params-tabpanel .tree-section", 2);
   RequestsMenu.selectedIndex = 1;
-  yield onEvent;
-  yield testParamsTab1("a", '"b"', '{ "foo": "bar" }', '""');
+  yield wait;
+  testParamsTab1("a", '"b"', '{ "foo": "bar" }', '""');
 
-  onEvent = monitor.panelWin.once(EVENTS.REQUEST_POST_PARAMS_DISPLAYED);
+  wait = waitForDOM(document, "#params-tabpanel .tree-section", 2);
   RequestsMenu.selectedIndex = 2;
-  yield onEvent;
-  yield testParamsTab1("a", '"b"', "foo", '"bar"');
+  yield wait;
+  testParamsTab1("a", '"b"', "foo", '"bar"');
 
-  onEvent = monitor.panelWin.once(EVENTS.REQUEST_POST_PARAMS_DISPLAYED);
+  wait = waitForDOM(document, "#params-tabpanel tr:not(.tree-section).treeRow", 2);
   RequestsMenu.selectedIndex = 3;
-  yield onEvent;
-  yield testParamsTab2("a", '""', '{ "foo": "bar" }', "js");
+  yield wait;
+  testParamsTab2("a", '""', '{ "foo": "bar" }', "js");
 
-  onEvent = monitor.panelWin.once(EVENTS.REQUEST_POST_PARAMS_DISPLAYED);
+  wait = waitForDOM(document, "#params-tabpanel tr:not(.tree-section).treeRow", 2);
   RequestsMenu.selectedIndex = 4;
-  yield onEvent;
-  yield testParamsTab2("a", '"b"', '{ "foo": "bar" }', "js");
+  yield wait;
+  testParamsTab2("a", '"b"', '{ "foo": "bar" }', "js");
 
-  onEvent = monitor.panelWin.once(EVENTS.REQUEST_POST_PARAMS_DISPLAYED);
+  // Wait for all tree sections and editor updated by react
+  let waitSections = waitForDOM(document, "#params-tabpanel .tree-section", 2);
+  let waitEditor = waitForDOM(document, "#params-tabpanel .editor-mount iframe");
   RequestsMenu.selectedIndex = 5;
-  yield onEvent;
-  yield testParamsTab2("a", '"b"', "?foo=bar", "text");
+  let [, editorFrames] = yield Promise.all([waitSections, waitEditor]);
+  yield once(editorFrames[0], "DOMContentLoaded");
+  yield waitForDOM(editorFrames[0].contentDocument, ".CodeMirror-code");
+  testParamsTab2("a", '"b"', "?foo=bar", "text");
 
-  onEvent = monitor.panelWin.once(EVENTS.SIDEBAR_POPULATED);
+  wait = waitForDOM(document, "#params-tabpanel .empty-notice");
   RequestsMenu.selectedIndex = 6;
-  yield onEvent;
-  yield testParamsTab3("a", '"b"');
+  yield wait;
+  testParamsTab3();
 
   yield teardown(monitor);
 
   function testParamsTab1(queryStringParamName, queryStringParamValue,
                           formDataParamName, formDataParamValue) {
     let tabpanel = document.querySelectorAll("#details-pane tabpanel")[2];
 
-    is(tabpanel.querySelectorAll(".variables-view-scope").length, 2,
-      "The number of param scopes displayed in this tabpanel is incorrect.");
-    is(tabpanel.querySelectorAll(".variable-or-property").length, 2,
-      "The number of param values displayed in this tabpanel is incorrect.");
-    is(tabpanel.querySelectorAll(".variables-view-empty-notice").length, 0,
+    is(tabpanel.querySelectorAll(".tree-section").length, 2,
+      "The number of param tree sections displayed in this tabpanel is incorrect.");
+    is(tabpanel.querySelectorAll("tr:not(.tree-section).treeRow").length, 2,
+      "The number of param rows displayed in this tabpanel is incorrect.");
+    is(tabpanel.querySelectorAll(".empty-notice").length, 0,
       "The empty notice should not be displayed in this tabpanel.");
 
-    is(tabpanel.querySelector("#request-params-box")
-      .hasAttribute("hidden"), false,
-      "The request params box should not be hidden.");
-    is(tabpanel.querySelector("#request-post-data-textarea-box")
-      .hasAttribute("hidden"), true,
-      "The request post data textarea box should be hidden.");
+    ok(tabpanel.querySelector(".treeTable"),
+      "The request params box should be displayed.");
+    ok(tabpanel.querySelector(".editor-mount") === null,
+      "The request post data editor should not be displayed.");
 
-    let paramsScope = tabpanel.querySelectorAll(".variables-view-scope")[0];
-    let formDataScope = tabpanel.querySelectorAll(".variables-view-scope")[1];
+    let treeSections = tabpanel.querySelectorAll(".tree-section");
+    let labels = tabpanel
+      .querySelectorAll("tr:not(.tree-section) .treeLabelCell .treeLabel");
+    let values = tabpanel
+      .querySelectorAll("tr:not(.tree-section) .treeValueCell .objectBox");
 
-    is(paramsScope.querySelector(".name").getAttribute("value"),
+    is(treeSections[0].querySelector(".treeLabel").textContent,
       L10N.getStr("paramsQueryString"),
-      "The params scope doesn't have the correct title.");
-    is(formDataScope.querySelector(".name").getAttribute("value"),
+      "The params section doesn't have the correct title.");
+    is(treeSections[1].querySelector(".treeLabel").textContent,
       L10N.getStr("paramsFormData"),
-      "The form data scope doesn't have the correct title.");
+      "The form data section doesn't have the correct title.");
 
-    is(paramsScope.querySelectorAll(".variables-view-variable .name")[0]
-      .getAttribute("value"),
-      queryStringParamName,
+    is(labels[0].textContent, queryStringParamName,
       "The first query string param name was incorrect.");
-    is(paramsScope.querySelectorAll(".variables-view-variable .value")[0]
-      .getAttribute("value"),
-      queryStringParamValue,
+    is(values[0].textContent, queryStringParamValue,
       "The first query string param value was incorrect.");
 
-    is(formDataScope.querySelectorAll(".variables-view-variable .name")[0]
-      .getAttribute("value"),
-      formDataParamName,
+    is(labels[1].textContent, formDataParamName,
       "The first form data param name was incorrect.");
-    is(formDataScope.querySelectorAll(".variables-view-variable .value")[0]
-      .getAttribute("value"),
-      formDataParamValue,
+    is(values[1].textContent, formDataParamValue,
       "The first form data param value was incorrect.");
   }
 
-  function* testParamsTab2(queryStringParamName, queryStringParamValue,
+  function testParamsTab2(queryStringParamName, queryStringParamValue,
                           requestPayload, editorMode) {
-    let isJSON = editorMode == "js";
+    let isJSON = editorMode === "js";
     let tabpanel = document.querySelectorAll("#details-pane tabpanel")[2];
 
-    is(tabpanel.querySelectorAll(".variables-view-scope").length, 2,
-      "The number of param scopes displayed in this tabpanel is incorrect.");
-    is(tabpanel.querySelectorAll(".variable-or-property").length, isJSON ? 4 : 1,
-      "The number of param values displayed in this tabpanel is incorrect.");
-    is(tabpanel.querySelectorAll(".variables-view-empty-notice").length, 0,
+    is(tabpanel.querySelectorAll(".tree-section").length, 2,
+      "The number of param tree sections displayed in this tabpanel is incorrect.");
+    is(tabpanel.querySelectorAll("tr:not(.tree-section).treeRow").length, isJSON ? 2 : 1,
+      "The number of param rows displayed in this tabpanel is incorrect.");
+    is(tabpanel.querySelectorAll(".empty-notice").length, 0,
       "The empty notice should not be displayed in this tabpanel.");
 
-    is(tabpanel.querySelector("#request-params-box")
-      .hasAttribute("hidden"), false,
-      "The request params box should not be hidden.");
-    is(tabpanel.querySelector("#request-post-data-textarea-box")
-      .hasAttribute("hidden"), isJSON,
-      "The request post data textarea box should be hidden.");
+    ok(tabpanel.querySelector(".treeTable"),
+      "The request params box should be displayed.");
+    is(tabpanel.querySelector(".editor-mount") === null,
+      isJSON,
+      "The request post data editor should be not displayed.");
 
-    let paramsScope = tabpanel.querySelectorAll(".variables-view-scope")[0];
-    let payloadScope = tabpanel.querySelectorAll(".variables-view-scope")[1];
+    let treeSections = tabpanel.querySelectorAll(".tree-section");
 
-    is(paramsScope.querySelector(".name").getAttribute("value"),
+    is(treeSections[0].querySelector(".treeLabel").textContent,
       L10N.getStr("paramsQueryString"),
-      "The params scope doesn't have the correct title.");
-    is(payloadScope.querySelector(".name").getAttribute("value"),
+      "The query section doesn't have the correct title.");
+    is(treeSections[1].querySelector(".treeLabel").textContent,
       isJSON ? L10N.getStr("jsonScopeName") : L10N.getStr("paramsPostPayload"),
-      "The request payload scope doesn't have the correct title.");
+      "The post section doesn't have the correct title.");
 
-    is(paramsScope.querySelectorAll(".variables-view-variable .name")[0]
-      .getAttribute("value"),
-      queryStringParamName,
+    let labels = tabpanel
+      .querySelectorAll("tr:not(.tree-section) .treeLabelCell .treeLabel");
+    let values = tabpanel
+      .querySelectorAll("tr:not(.treeS-section) .treeValueCell .objectBox");
+
+    is(labels[0].textContent, queryStringParamName,
       "The first query string param name was incorrect.");
-    is(paramsScope.querySelectorAll(".variables-view-variable .value")[0]
-      .getAttribute("value"),
-      queryStringParamValue,
+    is(values[0].textContent, queryStringParamValue,
       "The first query string param value was incorrect.");
 
     if (isJSON) {
       let requestPayloadObject = JSON.parse(requestPayload);
       let requestPairs = Object.keys(requestPayloadObject)
         .map(k => [k, requestPayloadObject[k]]);
-      let displayedNames = payloadScope.querySelectorAll(
-        ".variables-view-property.variable-or-property .name");
-      let displayedValues = payloadScope.querySelectorAll(
-        ".variables-view-property.variable-or-property .value");
-      for (let i = 0; i < requestPairs.length; i++) {
+      for (let i = 1; i < requestPairs.length; i++) {
         let [requestPayloadName, requestPayloadValue] = requestPairs[i];
-        is(requestPayloadName, displayedNames[i].getAttribute("value"),
+        is(requestPayloadName, labels[i].textContent,
           "JSON property name " + i + " should be displayed correctly");
-        is('"' + requestPayloadValue + '"', displayedValues[i].getAttribute("value"),
+        is('"' + requestPayloadValue + '"', values[i].textContent,
           "JSON property value " + i + " should be displayed correctly");
       }
     } else {
-      let editor = yield NetMonitorView.editor("#request-post-data-textarea");
-      is(editor.getText(), requestPayload,
+      let editor = editorFrames[0].contentDocument.querySelector(".CodeMirror-code");
+      ok(editor.textContent.includes(requestPayload),
         "The text shown in the source editor is incorrect.");
-      is(editor.getMode(), Editor.modes[editorMode],
-        "The mode active in the source editor is incorrect.");
     }
   }
 
-  function testParamsTab3(queryStringParamName, queryStringParamValue) {
+  function testParamsTab3() {
     let tabpanel = document.querySelectorAll("#details-pane tabpanel")[2];
 
-    is(tabpanel.querySelectorAll(".variables-view-scope").length, 0,
-      "The number of param scopes displayed in this tabpanel is incorrect.");
-    is(tabpanel.querySelectorAll(".variable-or-property").length, 0,
-      "The number of param values displayed in this tabpanel is incorrect.");
-    is(tabpanel.querySelectorAll(".variables-view-empty-notice").length, 1,
+    is(tabpanel.querySelectorAll(".tree-section").length, 0,
+      "The number of param tree sections displayed in this tabpanel is incorrect.");
+    is(tabpanel.querySelectorAll("tr:not(.tree-section).treeRow").length, 0,
+      "The number of param rows displayed in this tabpanel is incorrect.");
+    is(tabpanel.querySelectorAll(".empty-notice").length, 1,
       "The empty notice should be displayed in this tabpanel.");
 
-    is(tabpanel.querySelector("#request-params-box")
-      .hasAttribute("hidden"), false,
-      "The request params box should not be hidden.");
-    is(tabpanel.querySelector("#request-post-data-textarea-box")
-      .hasAttribute("hidden"), true,
-      "The request post data textarea box should be hidden.");
+    ok(!tabpanel.querySelector(".treeTable"),
+      "The request params box should be hidden.");
+    ok(!tabpanel.querySelector(".editor-mount iframe"),
+      "The request post data editor should be hidden.");
   }
 });
--- a/devtools/client/netmonitor/test/browser_net_post-data-01.js
+++ b/devtools/client/netmonitor/test/browser_net_post-data-01.js
@@ -8,21 +8,20 @@
  */
 
 add_task(function* () {
   let { L10N } = require("devtools/client/netmonitor/l10n");
 
   let { tab, monitor } = yield initNetMonitor(POST_DATA_URL);
   info("Starting test... ");
 
-  let { document, EVENTS, Editor, NetMonitorView } = monitor.panelWin;
-  let { RequestsMenu, NetworkDetails } = NetMonitorView;
+  let { document, NetMonitorView } = monitor.panelWin;
+  let { RequestsMenu } = NetMonitorView;
 
   RequestsMenu.lazyUpdate = false;
-  NetworkDetails._params.lazyEmpty = false;
 
   let wait = waitForNetworkEvents(monitor, 0, 2);
   yield ContentTask.spawn(tab.linkedBrowser, {}, function* () {
     content.wrappedJSObject.performRequests();
   });
   yield wait;
 
   verifyRequestItemTarget(RequestsMenu, RequestsMenu.getItemAtIndex(0),
@@ -39,128 +38,99 @@ add_task(function* () {
       status: 200,
       statusText: "Och Aye",
       type: "plain",
       fullMimeType: "text/plain; charset=utf-8",
       size: L10N.getFormatStrWithNumbers("networkMenu.sizeB", 12),
       time: true
     });
 
-  let onEvent = monitor.panelWin.once(EVENTS.TAB_UPDATED);
+  // Wait for all tree sections updated by react
+  wait = waitForDOM(document, "#params-tabpanel .tree-section", 2);
   EventUtils.sendMouseEvent({ type: "mousedown" },
     document.getElementById("details-pane-toggle"));
   EventUtils.sendMouseEvent({ type: "mousedown" },
     document.querySelectorAll("#details-pane tab")[2]);
-  yield onEvent;
+  yield wait;
   yield testParamsTab("urlencoded");
 
-  onEvent = monitor.panelWin.once(EVENTS.TAB_UPDATED);
+  // Wait for all tree sections and editor updated by react
+  let waitForSections = waitForDOM(document, "#params-tabpanel .tree-section", 2);
+  let waitForEditor = waitForDOM(document, "#params-tabpanel .editor-mount iframe");
   RequestsMenu.selectedIndex = 1;
-  yield onEvent;
+  let [, editorFrames] = yield Promise.all([waitForSections, waitForEditor]);
+  yield once(editorFrames[0], "DOMContentLoaded");
+  yield waitForDOM(editorFrames[0].contentDocument, ".CodeMirror-code");
   yield testParamsTab("multipart");
 
   return teardown(monitor);
 
   function* testParamsTab(type) {
     let tabEl = document.querySelectorAll("#details-pane tab")[2];
     let tabpanel = document.querySelectorAll("#details-pane tabpanel")[2];
 
     is(tabEl.getAttribute("selected"), "true",
       "The params tab in the network details pane should be selected.");
 
     function checkVisibility(box) {
-      is(tabpanel.querySelector("#request-params-box")
-        .hasAttribute("hidden"), !box.includes("params"),
-        "The request params box doesn't have the indended visibility.");
-      is(tabpanel.querySelector("#request-post-data-textarea-box")
-        .hasAttribute("hidden"), !box.includes("textarea"),
-        "The request post data textarea box doesn't have the indended visibility.");
+      is(!tabpanel.querySelector(".treeTable"), !box.includes("params"),
+        "The request params doesn't have the indended visibility.");
+      is(tabpanel.querySelector(".editor-mount") === null,
+        !box.includes("editor"),
+        "The request post data doesn't have the indended visibility.");
     }
 
-    is(tabpanel.querySelectorAll(".variables-view-scope").length, 2,
-      "There should be 2 param scopes displayed in this tabpanel.");
-    is(tabpanel.querySelectorAll(".variables-view-empty-notice").length, 0,
+    is(tabpanel.querySelectorAll(".tree-section").length, 2,
+      "There should be 2 tree sections displayed in this tabpanel.");
+    is(tabpanel.querySelectorAll(".empty-notice").length, 0,
       "The empty notice should not be displayed in this tabpanel.");
 
-    let queryScope = tabpanel.querySelectorAll(".variables-view-scope")[0];
-    let postScope = tabpanel.querySelectorAll(".variables-view-scope")[1];
+    let treeSections = tabpanel.querySelectorAll(".tree-section");
 
-    is(queryScope.querySelector(".name").getAttribute("value"),
+    is(treeSections[0].querySelector(".treeLabel").textContent,
       L10N.getStr("paramsQueryString"),
-      "The query scope doesn't have the correct title.");
+      "The query section doesn't have the correct title.");
 
-    is(postScope.querySelector(".name").getAttribute("value"),
+    is(treeSections[1].querySelector(".treeLabel").textContent,
       L10N.getStr(type == "urlencoded" ? "paramsFormData" : "paramsPostPayload"),
-      "The post scope doesn't have the correct title.");
+      "The post section doesn't have the correct title.");
 
-    is(queryScope.querySelectorAll(".variables-view-variable .name")[0]
-      .getAttribute("value"),
-      "foo", "The first query param name was incorrect.");
-    is(queryScope.querySelectorAll(".variables-view-variable .value")[0]
-      .getAttribute("value"),
-      "\"bar\"", "The first query param value was incorrect.");
-    is(queryScope.querySelectorAll(".variables-view-variable .name")[1]
-      .getAttribute("value"),
-      "baz", "The second query param name was incorrect.");
-    is(queryScope.querySelectorAll(".variables-view-variable .value")[1]
-      .getAttribute("value"),
-      "\"42\"", "The second query param value was incorrect.");
-    is(queryScope.querySelectorAll(".variables-view-variable .name")[2]
-      .getAttribute("value"),
-      "type", "The third query param name was incorrect.");
-    is(queryScope.querySelectorAll(".variables-view-variable .value")[2]
-      .getAttribute("value"),
-      "\"" + type + "\"", "The third query param value was incorrect.");
+    let labels = tabpanel.querySelectorAll("tr:not(.tree-section) .treeLabelCell .treeLabel");
+    let values = tabpanel.querySelectorAll("tr:not(.tree-section) .treeValueCell .objectBox");
+
+    is(labels[0].textContent, "foo", "The first query param name was incorrect.");
+    is(values[0].textContent, "\"bar\"", "The first query param value was incorrect.");
+    is(labels[1].textContent, "baz", "The second query param name was incorrect.");
+    is(values[1].textContent, "\"42\"", "The second query param value was incorrect.");
+    is(labels[2].textContent, "type", "The third query param name was incorrect.");
+    is(values[2].textContent, "\"" + type + "\"", "The third query param value was incorrect.");
 
     if (type == "urlencoded") {
       checkVisibility("params");
-
-      is(tabpanel.querySelectorAll(".variables-view-variable").length, 5,
-        "There should be 5 param values displayed in this tabpanel.");
-      is(queryScope.querySelectorAll(".variables-view-variable").length, 3,
-        "There should be 3 param values displayed in the query scope.");
-      is(postScope.querySelectorAll(".variables-view-variable").length, 2,
-        "There should be 2 param values displayed in the post scope.");
+      is(labels.length, 5, "There should be 5 param values displayed in this tabpanel.");
+      is(labels[3].textContent, "foo", "The first post param name was incorrect.");
+      is(values[3].textContent, "\"bar\"", "The first post param value was incorrect.");
+      is(labels[4].textContent, "baz", "The second post param name was incorrect.");
+      is(values[4].textContent, "\"123\"", "The second post param value was incorrect.");
+    } else {
+      checkVisibility("params editor");
 
-      is(postScope.querySelectorAll(".variables-view-variable .name")[0]
-        .getAttribute("value"),
-        "foo", "The first post param name was incorrect.");
-      is(postScope.querySelectorAll(".variables-view-variable .value")[0]
-        .getAttribute("value"),
-        "\"bar\"", "The first post param value was incorrect.");
-      is(postScope.querySelectorAll(".variables-view-variable .name")[1]
-        .getAttribute("value"),
-        "baz", "The second post param name was incorrect.");
-      is(postScope.querySelectorAll(".variables-view-variable .value")[1]
-        .getAttribute("value"),
-        "\"123\"", "The second post param value was incorrect.");
-    } else {
-      checkVisibility("params textarea");
+      is(labels.length, 3, "There should be 3 param values displayed in this tabpanel.");
 
-      is(tabpanel.querySelectorAll(".variables-view-variable").length, 3,
-        "There should be 3 param values displayed in this tabpanel.");
-      is(queryScope.querySelectorAll(".variables-view-variable").length, 3,
-        "There should be 3 param values displayed in the query scope.");
-      is(postScope.querySelectorAll(".variables-view-variable").length, 0,
-        "There should be 0 param values displayed in the post scope.");
-
-      let editor = yield NetMonitorView.editor("#request-post-data-textarea");
-      let text = editor.getText();
+      let text = editorFrames[0].contentDocument.querySelector(".CodeMirror-code").textContent;
 
       ok(text.includes("Content-Disposition: form-data; name=\"text\""),
         "The text shown in the source editor is incorrect (1.1).");
       ok(text.includes("Content-Disposition: form-data; name=\"email\""),
         "The text shown in the source editor is incorrect (2.1).");
       ok(text.includes("Content-Disposition: form-data; name=\"range\""),
         "The text shown in the source editor is incorrect (3.1).");
       ok(text.includes("Content-Disposition: form-data; name=\"Custom field\""),
         "The text shown in the source editor is incorrect (4.1).");
       ok(text.includes("Some text..."),
         "The text shown in the source editor is incorrect (2.2).");
       ok(text.includes("42"),
         "The text shown in the source editor is incorrect (3.2).");
       ok(text.includes("Extra data"),
         "The text shown in the source editor is incorrect (4.2).");
-      is(editor.getMode(), Editor.modes.text,
-        "The mode active in the source editor is incorrect.");
     }
   }
 });
--- a/devtools/client/netmonitor/test/browser_net_post-data-02.js
+++ b/devtools/client/netmonitor/test/browser_net_post-data-02.js
@@ -9,65 +9,55 @@
  */
 
 add_task(function* () {
   let { L10N } = require("devtools/client/netmonitor/l10n");
 
   let { tab, monitor } = yield initNetMonitor(POST_RAW_URL);
   info("Starting test... ");
 
-  let { document, EVENTS, NetMonitorView } = monitor.panelWin;
-  let { RequestsMenu, NetworkDetails } = NetMonitorView;
+  let { document, NetMonitorView } = monitor.panelWin;
+  let { RequestsMenu } = NetMonitorView;
 
   RequestsMenu.lazyUpdate = false;
-  NetworkDetails._params.lazyEmpty = false;
 
   let wait = waitForNetworkEvents(monitor, 0, 1);
   yield ContentTask.spawn(tab.linkedBrowser, {}, function* () {
     content.wrappedJSObject.performRequests();
   });
   yield wait;
 
-  let onEvent = monitor.panelWin.once(EVENTS.TAB_UPDATED);
-  NetMonitorView.toggleDetailsPane({ visible: true }, 2);
-  RequestsMenu.selectedIndex = 0;
-  yield onEvent;
-
-  let tabEl = document.querySelectorAll("#event-details-pane tab")[2];
-  let tabpanel = document.querySelectorAll("#event-details-pane tabpanel")[2];
-
-  is(tabEl.getAttribute("selected"), "true",
-    "The params tab in the network details pane should be selected.");
+  // Wait for all tree view updated by react
+  wait = waitForDOM(document, "#params-tabpanel .tree-section");
+  EventUtils.sendMouseEvent({ type: "mousedown" },
+    document.getElementById("details-pane-toggle"));
+  EventUtils.sendMouseEvent({ type: "mousedown" },
+    document.querySelectorAll("#details-pane tab")[2]);
+  yield wait;
 
-  is(tabpanel.querySelector("#request-params-box")
-    .hasAttribute("hidden"), false,
-    "The request params box doesn't have the indended visibility.");
-  is(tabpanel.querySelector("#request-post-data-textarea-box")
-    .hasAttribute("hidden"), true,
-    "The request post data textarea box doesn't have the indended visibility.");
+  let tabpanel = document.querySelectorAll("#details-pane tabpanel")[2];
 
-  is(tabpanel.querySelectorAll(".variables-view-scope").length, 1,
-    "There should be 1 param scopes displayed in this tabpanel.");
-  is(tabpanel.querySelectorAll(".variables-view-empty-notice").length, 0,
+  ok(tabpanel.querySelector(".treeTable"),
+    "The request params doesn't have the indended visibility.");
+  ok(tabpanel.querySelector(".editor-mount") === null,
+    "The request post data doesn't have the indended visibility.");
+
+  is(tabpanel.querySelectorAll(".tree-section").length, 1,
+    "There should be 1 tree sections displayed in this tabpanel.");
+  is(tabpanel.querySelectorAll(".empty-notice").length, 0,
     "The empty notice should not be displayed in this tabpanel.");
 
-  let postScope = tabpanel.querySelectorAll(".variables-view-scope")[0];
-  is(postScope.querySelector(".name").getAttribute("value"),
+  is(tabpanel.querySelector(".tree-section .treeLabel").textContent,
     L10N.getStr("paramsFormData"),
-    "The post scope doesn't have the correct title.");
+    "The post section doesn't have the correct title.");
 
-  is(postScope.querySelectorAll(".variables-view-variable").length, 2,
-    "There should be 2 param values displayed in the post scope.");
-  is(postScope.querySelectorAll(".variables-view-variable .name")[0]
-    .getAttribute("value"),
-    "foo", "The first query param name was incorrect.");
-  is(postScope.querySelectorAll(".variables-view-variable .value")[0]
-    .getAttribute("value"),
-    "\"bar\"", "The first query param value was incorrect.");
-  is(postScope.querySelectorAll(".variables-view-variable .name")[1]
-    .getAttribute("value"),
-    "baz", "The second query param name was incorrect.");
-  is(postScope.querySelectorAll(".variables-view-variable .value")[1]
-    .getAttribute("value"),
-    "\"123\"", "The second query param value was incorrect.");
+  let labels = tabpanel
+    .querySelectorAll("tr:not(.tree-section) .treeLabelCell .treeLabel");
+  let values = tabpanel
+    .querySelectorAll("tr:not(.tree-section) .treeValueCell .objectBox");
+
+  is(labels[0].textContent, "foo", "The first query param name was incorrect.");
+  is(values[0].textContent, "\"bar\"", "The first query param value was incorrect.");
+  is(labels[1].textContent, "baz", "The second query param name was incorrect.");
+  is(values[1].textContent, "\"123\"", "The second query param value was incorrect.");
 
   return teardown(monitor);
 });
--- a/devtools/client/netmonitor/test/browser_net_post-data-03.js
+++ b/devtools/client/netmonitor/test/browser_net_post-data-03.js
@@ -9,31 +9,34 @@
  */
 
 add_task(function* () {
   let { L10N } = require("devtools/client/netmonitor/l10n");
 
   let { tab, monitor } = yield initNetMonitor(POST_RAW_WITH_HEADERS_URL);
   info("Starting test... ");
 
-  let { document, EVENTS, NetMonitorView } = monitor.panelWin;
+  let { document, NetMonitorView } = monitor.panelWin;
   let { RequestsMenu } = NetMonitorView;
 
   RequestsMenu.lazyUpdate = false;
 
   let wait = waitForNetworkEvents(monitor, 0, 1);
   yield ContentTask.spawn(tab.linkedBrowser, {}, function* () {
     content.wrappedJSObject.performRequests();
   });
   yield wait;
 
-  let onEvent = monitor.panelWin.once(EVENTS.TAB_UPDATED);
-  NetMonitorView.toggleDetailsPane({ visible: true });
-  RequestsMenu.selectedIndex = 0;
-  yield onEvent;
+  // Wait for all tree view updated by react
+  wait = waitForDOM(document, "#headers-tabpanel .variables-view-scope", 3);
+  EventUtils.sendMouseEvent({ type: "mousedown" },
+    document.getElementById("details-pane-toggle"));
+  EventUtils.sendMouseEvent({ type: "mousedown" },
+    document.querySelectorAll("#details-pane tab")[0]);
+  yield wait;
 
   let tabEl = document.querySelectorAll("#details-pane tab")[0];
   let tabpanel = document.querySelectorAll("#details-pane tabpanel")[0];
   let requestFromUploadScope = tabpanel.querySelectorAll(".variables-view-scope")[2];
 
   is(tabEl.getAttribute("selected"), "true",
     "The headers tab in the network details pane should be selected.");
   is(tabpanel.querySelectorAll(".variables-view-scope").length, 3,
@@ -55,44 +58,37 @@ add_task(function* () {
     "The first request header value was incorrect.");
   is(requestFromUploadScope.querySelectorAll(".variables-view-variable .name")[1]
     .getAttribute("value"),
     "custom-header", "The second request header name was incorrect.");
   is(requestFromUploadScope.querySelectorAll(".variables-view-variable .value")[1]
     .getAttribute("value"),
     "\"hello world!\"", "The second request header value was incorrect.");
 
-  onEvent = monitor.panelWin.once(EVENTS.TAB_UPDATED);
+  // Wait for all tree sections updated by react
+  wait = waitForDOM(document, "#params-tabpanel .tree-section");
   EventUtils.sendMouseEvent({ type: "mousedown" },
     document.querySelectorAll("#details-pane tab")[2]);
-  yield onEvent;
+  yield wait;
 
-  tabEl = document.querySelectorAll("#details-pane tab")[2];
   tabpanel = document.querySelectorAll("#details-pane tabpanel")[2];
-  let formDataScope = tabpanel.querySelectorAll(".variables-view-scope")[0];
 
-  is(tab.getAttribute("selected"), "true",
-    "The response tab in the network details pane should be selected.");
-  is(tabpanel.querySelectorAll(".variables-view-scope").length, 1,
-    "There should be 1 header scope displayed in this tabpanel.");
-
-  is(formDataScope.querySelector(".name").getAttribute("value"),
-    L10N.getStr("paramsFormData"),
-    "The form data scope doesn't have the correct title.");
+  ok(tabpanel.querySelector(".treeTable"),
+    "The params tree view should be displayed.");
+  ok(tabpanel.querySelector(".editor-mount") === null,
+    "The post data shouldn't be displayed.");
 
-  is(formDataScope.querySelectorAll(".variables-view-variable").length, 2,
-    "There should be 2 payload values displayed in the form data scope.");
+  is(tabpanel.querySelector(".tree-section .treeLabel").textContent,
+    L10N.getStr("paramsFormData"),
+    "The form data section doesn't have the correct title.");
 
-  is(formDataScope.querySelectorAll(".variables-view-variable .name")[0]
-    .getAttribute("value"),
-    "foo", "The first payload param name was incorrect.");
-  is(formDataScope.querySelectorAll(".variables-view-variable .value")[0]
-    .getAttribute("value"),
-    "\"bar\"", "The first payload param value was incorrect.");
-  is(formDataScope.querySelectorAll(".variables-view-variable .name")[1]
-    .getAttribute("value"),
-    "baz", "The second payload param name was incorrect.");
-  is(formDataScope.querySelectorAll(".variables-view-variable .value")[1]
-    .getAttribute("value"),
-    "\"123\"", "The second payload param value was incorrect.");
+  let labels = tabpanel
+    .querySelectorAll("tr:not(.tree-section) .treeLabelCell .treeLabel");
+  let values = tabpanel
+    .querySelectorAll("tr:not(.tree-section) .treeValueCell .objectBox");
+
+  is(labels[0].textContent, "foo", "The first payload param name was incorrect.");
+  is(values[0].textContent, "\"bar\"", "The first payload param value was incorrect.");
+  is(labels[1].textContent, "baz", "The second payload param name was incorrect.");
+  is(values[1].textContent, "\"123\"", "The second payload param value was incorrect.");
 
   return teardown(monitor);
 });
--- a/devtools/client/netmonitor/test/browser_net_post-data-04.js
+++ b/devtools/client/netmonitor/test/browser_net_post-data-04.js
@@ -9,66 +9,53 @@
  */
 
 add_task(function* () {
   let { L10N } = require("devtools/client/netmonitor/l10n");
 
   let { tab, monitor } = yield initNetMonitor(POST_JSON_URL);
   info("Starting test... ");
 
-  let { document, EVENTS, NetMonitorView } = monitor.panelWin;
-  let { RequestsMenu, NetworkDetails } = NetMonitorView;
+  let { document, NetMonitorView } = monitor.panelWin;
+  let { RequestsMenu } = NetMonitorView;
 
   RequestsMenu.lazyUpdate = false;
-  NetworkDetails._params.lazyEmpty = false;
 
   let wait = waitForNetworkEvents(monitor, 0, 1);
   yield ContentTask.spawn(tab.linkedBrowser, {}, function* () {
     content.wrappedJSObject.performRequests();
   });
   yield wait;
 
-  let onEvent = monitor.panelWin.once(EVENTS.TAB_UPDATED);
-  NetMonitorView.toggleDetailsPane({ visible: true }, 2);
-  RequestsMenu.selectedIndex = 0;
-  yield onEvent;
+  // Wait for all tree view updated by react
+  wait = waitForDOM(document, "#params-tabpanel .tree-section");
+  EventUtils.sendMouseEvent({ type: "mousedown" },
+    document.getElementById("details-pane-toggle"));
+  EventUtils.sendMouseEvent({ type: "mousedown" },
+    document.querySelectorAll("#details-pane tab")[2]);
+  yield wait;
 
-  let tabEl = document.querySelectorAll("#event-details-pane tab")[2];
   let tabpanel = document.querySelectorAll("#event-details-pane tabpanel")[2];
 
-  is(tabEl.getAttribute("selected"), "true",
-    "The params tab in the network details pane should be selected.");
+  ok(tabpanel.querySelector(".treeTable"),
+    "The request params doesn't have the indended visibility.");
+  ok(tabpanel.querySelector(".editor-mount") === null,
+    "The request post data doesn't have the indended visibility.");
 
-  is(tabpanel.querySelector("#request-params-box")
-    .hasAttribute("hidden"), false,
-    "The request params box doesn't have the intended visibility.");
-  is(tabpanel.querySelector("#request-post-data-textarea-box")
-    .hasAttribute("hidden"), true,
-    "The request post data textarea box doesn't have the intended visibility.");
-
-  is(tabpanel.querySelectorAll(".variables-view-scope").length, 1,
-    "There should be 1 param scopes displayed in this tabpanel.");
-  is(tabpanel.querySelectorAll(".variables-view-empty-notice").length, 0,
+  is(tabpanel.querySelectorAll(".tree-section").length, 1,
+    "There should be 1 tree sections displayed in this tabpanel.");
+  is(tabpanel.querySelectorAll(".empty-notice").length, 0,
     "The empty notice should not be displayed in this tabpanel.");
 
-  let jsonScope = tabpanel.querySelectorAll(".variables-view-scope")[0];
-  is(jsonScope.querySelector(".name").getAttribute("value"),
+  is(tabpanel.querySelector(".tree-section .treeLabel").textContent,
     L10N.getStr("jsonScopeName"),
-    "The JSON scope doesn't have the correct title.");
-
-  let valueScope = tabpanel.querySelector(
-    ".variables-view-scope > .variables-view-element-details");
+    "The JSON section doesn't have the correct title.");
 
-  is(valueScope.querySelectorAll(".variables-view-variable").length, 1,
-    "There should be 1 value displayed in the JSON scope.");
-  is(valueScope.querySelector(".variables-view-property .name")
-    .getAttribute("value"),
-    "a", "The JSON var name was incorrect.");
-  is(valueScope.querySelector(".variables-view-property .value")
-    .getAttribute("value"),
-    "1", "The JSON var value was incorrect.");
+  let labels = tabpanel
+    .querySelectorAll("tr:not(.tree-section) .treeLabelCell .treeLabel");
+  let values = tabpanel
+    .querySelectorAll("tr:not(.tree-section) .treeValueCell .objectBox");
 
-  let detailsParent = valueScope.querySelector(".variables-view-property .name")
-    .closest(".variables-view-element-details");
-  is(detailsParent.hasAttribute("open"), true, "The JSON value must be visible");
+  is(labels[0].textContent, "a", "The JSON var name was incorrect.");
+  is(values[0].textContent, "1", "The JSON var value was incorrect.");
 
   return teardown(monitor);
 });
--- a/devtools/client/netmonitor/test/browser_net_status-codes.js
+++ b/devtools/client/netmonitor/test/browser_net_status-codes.js
@@ -14,17 +14,16 @@ add_task(function* () {
 
   info("Starting test... ");
 
   let { document, EVENTS, NetMonitorView } = monitor.panelWin;
   let { RequestsMenu, NetworkDetails } = NetMonitorView;
   let requestItems = [];
 
   RequestsMenu.lazyUpdate = false;
-  NetworkDetails._params.lazyEmpty = false;
 
   const REQUEST_DATA = [
     {
       // request #0
       method: "GET",
       uri: STATUS_CODES_SJS + "?sts=100",
       details: {
         status: 101,
@@ -168,43 +167,42 @@ add_task(function* () {
 
   /**
    * A function that tests "Params" tab contains correct information.
    */
   function* testParams(data) {
     let tabpanel = document.querySelectorAll("#details-pane tabpanel")[2];
     let statusParamValue = data.uri.split("=").pop();
     let statusParamShownValue = "\"" + statusParamValue + "\"";
+    let treeSections = tabpanel.querySelectorAll(".tree-section");
 
-    is(tabpanel.querySelectorAll(".variables-view-scope").length, 1,
-      "There should be 1 param scope displayed in this tabpanel.");
-    is(tabpanel.querySelectorAll(".variable-or-property").length, 1,
-      "There should be 1 param value displayed in this tabpanel.");
-    is(tabpanel.querySelectorAll(".variables-view-empty-notice").length, 0,
+    is(treeSections.length, 1,
+      "There should be 1 param section displayed in this tabpanel.");
+    is(tabpanel.querySelectorAll("tr:not(.tree-section).treeRow").length, 1,
+      "There should be 1 param row displayed in this tabpanel.");
+    is(tabpanel.querySelectorAll(".empty-notice").length, 0,
       "The empty notice should not be displayed in this tabpanel.");
 
-    let paramsScope = tabpanel.querySelectorAll(".variables-view-scope")[0];
+    let labels = tabpanel
+      .querySelectorAll("tr:not(.tree-section) .treeLabelCell .treeLabel");
+    let values = tabpanel
+      .querySelectorAll("tr:not(.tree-section) .treeValueCell .objectBox");
 
-    is(paramsScope.querySelector(".name").getAttribute("value"),
+    is(treeSections[0].querySelector(".treeLabel").textContent,
       L10N.getStr("paramsQueryString"),
       "The params scope doesn't have the correct title.");
 
-    is(paramsScope.querySelectorAll(".variables-view-variable .name")[0]
-      .getAttribute("value"),
-      "sts", "The param name was incorrect.");
-    is(paramsScope.querySelectorAll(".variables-view-variable .value")[0]
-      .getAttribute("value"),
-      statusParamShownValue, "The param value was incorrect.");
+    is(labels[0].textContent, "sts", "The param name was incorrect.");
+    is(values[0].textContent, statusParamShownValue, "The param value was incorrect.");
 
-    is(tabpanel.querySelector("#request-params-box")
-      .hasAttribute("hidden"), false,
-      "The request params box should not be hidden.");
-    is(tabpanel.querySelector("#request-post-data-textarea-box")
-      .hasAttribute("hidden"), true,
-      "The request post data textarea box should be hidden.");
+    ok(tabpanel.querySelector(".treeTable"),
+      "The request params tree view should be displayed.");
+    is(tabpanel.querySelector(".editor-mount") === null,
+      true,
+      "The request post data editor should be hidden.");
   }
 
   /**
    * A helper that clicks on a specified request and returns a promise resolved
    * when NetworkDetails has been populated with the data of the given request.
    */
   function chooseRequest(index) {
     let onTabUpdated = monitor.panelWin.once(EVENTS.TAB_UPDATED);
--- a/devtools/client/preferences/devtools.js
+++ b/devtools/client/preferences/devtools.js
@@ -353,18 +353,13 @@ pref("devtools.editor.detectindentation"
 pref("devtools.editor.enableCodeFolding", true);
 pref("devtools.editor.autocomplete", true);
 
 // Pref to store the browser version at the time of a telemetry ping for an
 // opened developer tool. This allows us to ping telemetry just once per browser
 // version for each user.
 pref("devtools.telemetry.tools.opened.version", "{}");
 
-// Enable the JSON View tool (an inspector for application/json documents) on
-// Nightly and Dev. Edition.
-#ifdef RELEASE_OR_BETA
-pref("devtools.jsonview.enabled", false);
-#else
+// Enable the JSON View tool (an inspector for application/json documents).
 pref("devtools.jsonview.enabled", true);
-#endif
 
 // Enable the HTML responsive design mode for all channels.
 pref("devtools.responsive.html.enabled", true);
--- a/devtools/client/shared/telemetry.js
+++ b/devtools/client/shared/telemetry.js
@@ -140,17 +140,16 @@ Telemetry.prototype = {
       timerHistogram: "DEVTOOLS_PAINTFLASHING_TIME_ACTIVE_SECONDS"
     },
     scratchpad: {
       histogram: "DEVTOOLS_SCRATCHPAD_OPENED_COUNT",
       timerHistogram: "DEVTOOLS_SCRATCHPAD_TIME_ACTIVE_SECONDS"
     },
     "scratchpad-window": {
       histogram: "DEVTOOLS_SCRATCHPAD_WINDOW_OPENED_COUNT",
-      timerHistogram: "DEVTOOLS_SCRATCHPAD_WINDOW_TIME_ACTIVE_SECONDS"
     },
     responsive: {
       histogram: "DEVTOOLS_RESPONSIVE_OPENED_COUNT",
       timerHistogram: "DEVTOOLS_RESPONSIVE_TIME_ACTIVE_SECONDS"
     },
     eyedropper: {
       histogram: "DEVTOOLS_EYEDROPPER_OPENED_COUNT",
     },
--- a/devtools/client/themes/netmonitor.css
+++ b/devtools/client/themes/netmonitor.css
@@ -1103,28 +1103,23 @@
 }
 
 .treeTable .textbox-input {
   text-overflow: ellipsis;
   border: none;
   background: none;
   color: inherit;
   width: 100%;
-  margin-inline-end: 2px;
 }
 
 .treeTable .textbox-input:focus {
   outline: 0;
   box-shadow: var(--theme-focus-box-shadow-textbox);
 }
 
-.treeTable .treeLabel {
-  font-weight: 600;
-}
-
 .properties-view {
   /* FIXME: Minus 24px * 2 for toolbox height + panel height
    * Give a fixed panel container height in order to force tree view scrollable */
   height: calc(100vh - 48px);
   display: flex;
   flex-direction: column;
 }
 
@@ -1157,16 +1152,21 @@
 }
 
 .properties-view .devtools-searchbox,
 .tree-container .treeTable .tree-section {
   width: 100%;
   background-color: var(--theme-toolbar-background);
 }
 
+.properties-view .devtools-searchbox,
+.tree-container .treeTable tr:not(:last-child) td:not([class=""]) {
+  border-bottom: 1px solid var(--theme-splitter-color);
+}
+
 .tree-container .treeTable .tree-section > * {
   vertical-align: middle;
 }
 
 .tree-container .treeTable .treeRow.tree-section > .treeLabelCell > .treeLabel,
 .tree-container .treeTable .treeRow.tree-section > .treeLabelCell > .treeLabel:hover {
   color: var(--theme-body-color-alt);
 }
@@ -1176,39 +1176,46 @@
   max-width: 0;
   padding-inline-end: 5px;
 }
 
 .tree-container .objectBox {
   white-space: nowrap;
 }
 
+.empty-notice {
+  color: var(--theme-body-color-alt);
+  padding: 3px 8px;
+}
+
 .editor-container,
 .editor-mount,
 .editor-mount iframe {
   border: none;
   width: 100%;
   height: 100%;
 }
 
 /*
  * FIXME: normal html block element cannot fill outer XUL element
  * This workaround should be removed after netmonitor is migrated to react
  */
+#react-params-tabpanel-hook,
 #react-preview-tabpanel-hook,
 #react-security-tabpanel-hook,
 #react-timings-tabpanel-hook,
 #network-statistics-charts,
 #primed-cache-chart,
 #empty-cache-chart {
   display: -moz-box;
   -moz-box-flex: 1;
 }
 
 /* For vbox */
+#react-params-tabpanel-hook,
 #react-preview-tabpanel-hook,
 #react-security-tabpanel-hook,
 #react-timings-tabpanel-hook,
 #primed-cache-chart,
 #empty-cache-chart {
   -moz-box-orient: vertical;
 }
 
--- a/devtools/client/webconsole/new-console-output/components/console-output.js
+++ b/devtools/client/webconsole/new-console-output/components/console-output.js
@@ -53,16 +53,22 @@ const ConsoleOutput = createClass({
   },
 
   componentDidUpdate() {
     if (this.shouldScrollBottom) {
       scrollToBottom(this.outputNode);
     }
   },
 
+  onContextMenu(e) {
+    this.props.serviceContainer.openContextMenu(e);
+    e.stopPropagation();
+    e.preventDefault();
+  },
+
   render() {
     let {
       dispatch,
       autoscroll,
       messages,
       messagesUi,
       messagesTableData,
       serviceContainer,
@@ -94,16 +100,17 @@ const ConsoleOutput = createClass({
 
     if (!timestampsVisible) {
       classList.push("hideTimestamps");
     }
 
     return (
       dom.div({
         className: classList.join(" "),
+        onContextMenu: this.onContextMenu,
         ref: node => {
           this.outputNode = node;
         },
       }, messageNodes
       )
     );
   }
 });
--- a/devtools/client/webconsole/new-console-output/components/message-types/evaluation-result.js
+++ b/devtools/client/webconsole/new-console-output/components/message-types/evaluation-result.js
@@ -30,23 +30,24 @@ function EvaluationResult(props) {
   const {
     source,
     type,
     level,
     id: messageId,
     exceptionDocURL,
     frame,
     timeStamp,
+    parameters,
   } = message;
 
   let messageBody;
   if (message.messageText) {
     messageBody = message.messageText;
   } else {
-    messageBody = GripMessageBody({grip: message.parameters, serviceContainer});
+    messageBody = GripMessageBody({grip: parameters, serviceContainer});
   }
 
   const topLevelClasses = ["cm-s-mozilla"];
 
   const childProps = {
     source,
     type,
     level,
@@ -54,13 +55,14 @@ function EvaluationResult(props) {
     topLevelClasses,
     messageBody,
     messageId,
     scrollToMessage: props.autoscroll,
     serviceContainer,
     exceptionDocURL,
     frame,
     timeStamp,
+    parameters,
   };
   return Message(childProps);
 }
 
 module.exports = EvaluationResult;
--- a/devtools/client/webconsole/new-console-output/components/message-types/network-event-message.js
+++ b/devtools/client/webconsole/new-console-output/components/message-types/network-event-message.js
@@ -52,13 +52,14 @@ function NetworkEventMessage(props) {
     source,
     type,
     level,
     indent,
     topLevelClasses,
     timeStamp,
     messageBody,
     serviceContainer,
+    request,
   };
   return Message(childProps);
 }
 
 module.exports = NetworkEventMessage;
--- a/devtools/client/webconsole/new-console-output/components/message.js
+++ b/devtools/client/webconsole/new-console-output/components/message.js
@@ -38,21 +38,24 @@ const Message = createClass({
     messageBody: PropTypes.any.isRequired,
     repeat: PropTypes.any,
     frame: PropTypes.any,
     attachment: PropTypes.any,
     stacktrace: PropTypes.any,
     messageId: PropTypes.string,
     scrollToMessage: PropTypes.bool,
     exceptionDocURL: PropTypes.string,
+    parameters: PropTypes.object,
+    request: PropTypes.object,
     serviceContainer: PropTypes.shape({
       emitNewMessage: PropTypes.func.isRequired,
       onViewSourceInDebugger: PropTypes.func.isRequired,
       onViewSourceInScratchpad: PropTypes.func.isRequired,
       onViewSourceInStyleEditor: PropTypes.func.isRequired,
+      openContextMenu: PropTypes.func.isRequired,
       sourceMapService: PropTypes.any,
     }),
   },
 
   getDefaultProps: function () {
     return {
       indent: 0
     };
@@ -72,16 +75,27 @@ const Message = createClass({
     }
   },
 
   onLearnMoreClick: function () {
     let {exceptionDocURL} = this.props;
     this.props.serviceContainer.openLink(exceptionDocURL);
   },
 
+  onContextMenu(e) {
+    let { serviceContainer, source, request } = this.props;
+    let messageInfo = {
+      source,
+      request,
+    };
+    serviceContainer.openContextMenu(e, messageInfo);
+    e.stopPropagation();
+    e.preventDefault();
+  },
+
   render() {
     const {
       messageId,
       open,
       collapsible,
       collapseTitle,
       source,
       type,
@@ -168,16 +182,17 @@ const Message = createClass({
         className: "learn-more-link webconsole-learn-more-link",
         title: exceptionDocURL.split("?")[0],
         onClick: this.onLearnMoreClick,
       }, `[${l10n.getStr("webConsoleMoreInfoLabel")}]`);
     }
 
     return dom.div({
       className: topLevelClasses.join(" "),
+      onContextMenu: this.onContextMenu,
       ref: node => {
         this.messageNode = node;
       }
     },
       timestampEl,
       MessageIndent({indent}),
       icon,
       collapse,
--- a/devtools/client/webconsole/new-console-output/components/variables-view-link.js
+++ b/devtools/client/webconsole/new-console-output/components/variables-view-link.js
@@ -20,15 +20,17 @@ VariablesViewLink.propTypes = {
 };
 
 function VariablesViewLink(props) {
   const { className, object, children } = props;
 
   return (
     dom.a({
       onClick: openVariablesView.bind(null, object),
+      // Context menu can use this actor id information to enable additional menu items.
+      "data-link-actor-id": object.actor,
       className: className || "cm-variable",
       draggable: false,
     }, children)
   );
 }
 
 module.exports = VariablesViewLink;
--- a/devtools/client/webconsole/new-console-output/new-console-output-wrapper.js
+++ b/devtools/client/webconsole/new-console-output/new-console-output-wrapper.js
@@ -4,26 +4,30 @@
 "use strict";
 
 // React & Redux
 const React = require("devtools/client/shared/vendor/react");
 const ReactDOM = require("devtools/client/shared/vendor/react-dom");
 const { Provider } = require("devtools/client/shared/vendor/react-redux");
 
 const actions = require("devtools/client/webconsole/new-console-output/actions/index");
+const { createContextMenu } = require("devtools/client/webconsole/new-console-output/utils/context-menu");
 const { configureStore } = require("devtools/client/webconsole/new-console-output/store");
 
+const EventEmitter = require("devtools/shared/event-emitter");
 const ConsoleOutput = React.createFactory(require("devtools/client/webconsole/new-console-output/components/console-output"));
 const FilterBar = React.createFactory(require("devtools/client/webconsole/new-console-output/components/filter-bar"));
 
 const store = configureStore();
 let queuedActions = [];
 let throttledDispatchTimeout = false;
 
 function NewConsoleOutputWrapper(parentNode, jsterm, toolbox, owner, document) {
+  EventEmitter.decorate(this);
+
   this.parentNode = parentNode;
   this.jsterm = jsterm;
   this.toolbox = toolbox;
   this.owner = owner;
   this.document = document;
 
   this.init = this.init.bind(this);
 }
@@ -58,16 +62,35 @@ NewConsoleOutputWrapper.prototype = {
           frame.url,
           frame.line
         ),
         onViewSourceInStyleEditor: frame => this.toolbox.viewSourceInStyleEditor.call(
           this.toolbox,
           frame.url,
           frame.line
         ),
+        openContextMenu: (e, message) => {
+          let { screenX, screenY, target } = e;
+
+          let messageEl = target.closest(".message");
+          let clipboardText = messageEl ? messageEl.textContent : null;
+
+          // Retrieve closes actor id from the DOM.
+          let actorEl = target.closest("[data-link-actor-id]");
+          let actor = actorEl ? actorEl.dataset.linkActorId : null;
+
+          let menu = createContextMenu(this.jsterm, this.parentNode,
+            { actor, clipboardText, message });
+
+          // Emit the "menu-open" event for testing.
+          menu.once("open", () => this.emit("menu-open"));
+          menu.popup(screenX, screenY, this.toolbox);
+
+          return menu;
+        },
         openNetworkPanel: (requestId) => {
           return this.toolbox.selectTool("netmonitor").then(panel => {
             return panel.panelWin.NetMonitorController.inspectRequest(requestId);
           });
         },
         sourceMapService: this.toolbox ? this.toolbox._sourceMapService : null,
         openLink: url => this.jsterm.hud.owner.openLink.call(this.jsterm.hud.owner, url),
         createElement: nodename => {
@@ -85,16 +108,17 @@ NewConsoleOutputWrapper.prototype = {
         },
       }
     });
     let filterBar = FilterBar({
       serviceContainer: {
         attachRefToHud
       }
     });
+
     let provider = React.createElement(
       Provider,
       { store },
       React.DOM.div(
         {className: "webconsole-output-wrapper"},
         filterBar,
         childComponent
     ));
--- a/devtools/client/webconsole/new-console-output/test/mochitest/browser.ini
+++ b/devtools/client/webconsole/new-console-output/test/mochitest/browser.ini
@@ -15,16 +15,23 @@ support-files =
   test-location-styleeditor-link-2.css
   test-location-styleeditor-link.html
   test-stacktrace-location-debugger-link.html
   !/devtools/client/framework/test/shared-head.js
 
 [browser_webconsole_batching.js]
 [browser_webconsole_console_group.js]
 [browser_webconsole_console_table.js]
+[browser_webconsole_context_menu_copy_entire_message.js]
+subsuite = clipboard
+[browser_webconsole_context_menu_copy_link_location.js]
+subsuite = clipboard
+[browser_webconsole_context_menu_open_in_var_view.js]
+[browser_webconsole_context_menu_open_url.js]
+[browser_webconsole_context_menu_store_as_global.js]
 [browser_webconsole_filters.js]
 [browser_webconsole_init.js]
 [browser_webconsole_input_focus.js]
 [browser_webconsole_keyboard_accessibility.js]
 [browser_webconsole_location_debugger_link.js]
 [browser_webconsole_location_scratchpad_link.js]
 [browser_webconsole_location_styleeditor_link.js]
 [browser_webconsole_nodes_highlight.js]
new file mode 100644
--- /dev/null
+++ b/devtools/client/webconsole/new-console-output/test/mochitest/browser_webconsole_context_menu_copy_entire_message.js
@@ -0,0 +1,65 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+const TEST_URI = `data:text/html;charset=utf-8,<script>
+  window.logStuff = function () {
+    console.log("simple text message");
+    console.trace();
+  };
+</script>`;
+
+// Test the Copy menu item of the webconsole copies the expected clipboard text for
+// different log messages.
+
+add_task(function* () {
+  let hud = yield openNewTabAndConsole(TEST_URI);
+  hud.jsterm.clearOutput();
+
+  yield ContentTask.spawn(gBrowser.selectedBrowser, null, () => {
+    content.wrappedJSObject.logStuff();
+  });
+
+  let messages = [];
+  for (let s of ["simple text message", "console.trace()"]) {
+    messages.push(yield waitFor(() => findMessage(hud, s)));
+  }
+
+  for (let message of messages) {
+    let menuPopup = yield openContextMenu(hud, message);
+    let copyMenuItem = menuPopup.querySelector("#console-menu-copy");
+    ok(copyMenuItem, "copy menu item is enabled");
+
+    yield waitForClipboardPromise(
+      () => copyMenuItem.click(),
+      data => data === message.textContent
+    );
+
+    ok(true, "Clipboard text was found and saved");
+
+    // TODO: The copy menu item & this test should be improved for the new console.
+    // This is tracked by https://bugzilla.mozilla.org/show_bug.cgi?id=1329606 .
+
+    // The rest of this test was copied from the old console frontend. The copy menu item
+    // logic is not on par with the one from the old console yet.
+
+    // let lines = clipboardText.split("\n");
+    // ok(lines.length > 0, "There is at least one newline in the message");
+    // is(lines.pop(), "", "There is a newline at the end");
+    // is(lines.length, result.lines, `There are ${result.lines} lines in the message`);
+
+    // // Test the first line for "timestamp message repeat file:line"
+    // let firstLine = lines.shift();
+    // ok(/^[\d:.]+ .+ \d+ .+:\d+$/.test(firstLine),
+    //   "The message's first line has the right format:\n" + firstLine);
+
+    // // Test the remaining lines (stack trace) for "TABfunctionName sourceURL:line:col"
+    // for (let line of lines) {
+    //   ok(/^\t.+ .+:\d+:\d+$/.test(line),
+    //     "The stack trace line has the right format:\n" + line);
+    // }
+  }
+});
new file mode 100644
--- /dev/null
+++ b/devtools/client/webconsole/new-console-output/test/mochitest/browser_webconsole_context_menu_copy_link_location.js
@@ -0,0 +1,59 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+// Test the Copy Link Location menu item of the webconsole is displayed for network
+// messages and copies the expected URL.
+
+"use strict";
+
+const TEST_URI = "http://example.com/browser/devtools/client/webconsole/" +
+  "new-console-output/test/mochitest/test-console.html?_date=" + Date.now();
+const CONTEXT_MENU_ID = "#console-menu-copy-url";
+
+add_task(function* () {
+  // Enable net messages in the console for this test.
+  yield pushPref("devtools.webconsole.filter.net", true);
+
+  let hud = yield openNewTabAndConsole(TEST_URI);
+  hud.jsterm.clearOutput();
+
+  info("Test Copy URL menu item for text log");
+
+  info("Logging a text message in the content window");
+  yield ContentTask.spawn(gBrowser.selectedBrowser, null, () => {
+    content.wrappedJSObject.console.log("simple text message");
+  });
+  let message = yield waitFor(() => findMessage(hud, "simple text message"));
+  ok(message, "Text log found in the console");
+
+  info("Open and check the context menu for the logged text message");
+  let menuPopup = yield openContextMenu(hud, message);
+  let copyURLItem = menuPopup.querySelector(CONTEXT_MENU_ID);
+  ok(!copyURLItem, "Copy URL menu item is hidden for a simple text message");
+
+  yield hideContextMenu(hud);
+  hud.jsterm.clearOutput();
+
+  info("Test Copy URL menu item for network log");
+
+  info("Reload the content window to produce a network log");
+  yield ContentTask.spawn(gBrowser.selectedBrowser, null, () => {
+    content.wrappedJSObject.location.reload();
+  });
+
+  message = yield waitFor(() => findMessage(hud, "test-console.html"));
+  ok(message, "Network log found in the console");
+
+  info("Open and check the context menu for the logged network message");
+  menuPopup = yield openContextMenu(hud, message);
+  copyURLItem = menuPopup.querySelector(CONTEXT_MENU_ID);
+  ok(copyURLItem, "Copy url menu item is available in context menu");
+
+  info("Click on Copy URL menu item and wait for clipboard to be updated");
+  yield waitForClipboardPromise(() => copyURLItem.click(), TEST_URI);
+  ok(true, "Expected text was copied to the clipboard.");
+
+  yield hideContextMenu(hud);
+});
new file mode 100644
--- /dev/null
+++ b/devtools/client/webconsole/new-console-output/test/mochitest/browser_webconsole_context_menu_open_in_var_view.js
@@ -0,0 +1,45 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+// Test the Open in Variables View menu item of the webconsole is enabled only when
+// clicking on messages that can be opened in the variables view.
+
+"use strict";
+
+const TEST_URI = `data:text/html;charset=utf-8,<script>
+  console.log("foo");
+  console.log("foo", window);
+</script>`;
+
+add_task(function* () {
+  let hud = yield openNewTabAndConsole(TEST_URI);
+
+  let [msgWithText, msgWithObj] = yield waitFor(() => findMessages(hud, "foo"));
+  ok(msgWithText && msgWithObj, "Two messages should have appeared");
+
+  let text = msgWithText.querySelector(".objectBox-string");
+  let objInMsgWithObj = msgWithObj.querySelector(".cm-variable");
+  let textInMsgWithObj = msgWithObj.querySelector(".objectBox-string");
+
+  info("Check open in variables view is disabled for text only messages");
+  let menuPopup = yield openContextMenu(hud, text);
+  let openMenuItem = menuPopup.querySelector("#console-menu-open");
+  ok(openMenuItem.disabled, "open in variables view is disabled for text message");
+  yield hideContextMenu(hud);
+
+  info("Check open in variables view is enabled for objects in complex messages");
+  menuPopup = yield openContextMenu(hud, objInMsgWithObj);
+  openMenuItem = menuPopup.querySelector("#console-menu-open");
+  ok(!openMenuItem.disabled,
+    "open in variables view is enabled for object in complex message");
+  yield hideContextMenu(hud);
+
+  info("Check open in variables view is disabled for text in complex messages");
+  menuPopup = yield openContextMenu(hud, textInMsgWithObj);
+  openMenuItem = menuPopup.querySelector("#console-menu-open");
+  ok(openMenuItem.disabled,
+    "open in variables view is disabled for text in complex message");
+  yield hideContextMenu(hud);
+});
new file mode 100644
--- /dev/null
+++ b/devtools/client/webconsole/new-console-output/test/mochitest/browser_webconsole_context_menu_open_url.js
@@ -0,0 +1,80 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+// Test that the Open URL in new Tab menu item is displayed for network logs and works as
+// expected.
+
+"use strict";
+
+const TEST_URI = "http://example.com/browser/devtools/client/webconsole/" +
+  "new-console-output/test/mochitest/test-console.html";
+
+add_task(function* () {
+  // Enable net messages in the console for this test.
+  yield pushPref("devtools.webconsole.filter.net", true);
+
+  let hud = yield openNewTabAndConsole(TEST_URI);
+  hud.jsterm.clearOutput();
+
+  info("Test Open URL menu item for text log");
+
+  info("Logging a text message in the content window");
+  yield ContentTask.spawn(gBrowser.selectedBrowser, null, () => {
+    content.wrappedJSObject.console.log("simple text message");
+  });
+  let message = yield waitFor(() => findMessage(hud, "simple text message"));
+  ok(message, "Text log found in the console");
+
+  info("Open and check the context menu for the logged text message");
+  let menuPopup = yield openContextMenu(hud, message);
+  let openUrlItem = menuPopup.querySelector("#console-menu-open-url");
+  ok(!openUrlItem, "Open URL menu item is not available");
+
+  yield hideContextMenu(hud);
+  hud.jsterm.clearOutput();
+
+  info("Test Open URL menu item for network log");
+
+  info("Reload the content window to produce a network log");
+  yield ContentTask.spawn(gBrowser.selectedBrowser, null, () => {
+    content.wrappedJSObject.location.reload();
+  });
+  message = yield waitFor(() => findMessage(hud, "test-console.html"));
+  ok(message, "Network log found in the console");
+
+  info("Open and check the context menu for the logged network message");
+  menuPopup = yield openContextMenu(hud, message);
+  openUrlItem = menuPopup.querySelector("#console-menu-open-url");
+  ok(openUrlItem, "Open URL menu item is available");
+
+  let currentTab = gBrowser.selectedTab;
+  let tabLoaded = listenToTabLoad();
+  info("Click on Open URL menu item and wait for new tab to open");
+  openUrlItem.click();
+  yield hideContextMenu(hud);
+  let newTab = yield tabLoaded;
+  let newTabHref = newTab.linkedBrowser._contentWindow.location.href;
+  is(newTabHref, TEST_URI, "Tab was opened with the expected URL");
+
+  info("Remove the new tab and select the previous tab back");
+  gBrowser.removeTab(newTab);
+  gBrowser.selectedTab = currentTab;
+});
+
+/**
+ * Simple helper to wrap a tab load listener in a promise.
+ */
+function listenToTabLoad() {
+  return new Promise((resolve) => {
+    gBrowser.tabContainer.addEventListener("TabOpen", function onTabOpen(evt) {
+      gBrowser.tabContainer.removeEventListener("TabOpen", onTabOpen, true);
+      let newTab = evt.target;
+      newTab.linkedBrowser.addEventListener("load", function onTabLoad() {
+        newTab.linkedBrowser.removeEventListener("load", onTabLoad, true);
+        resolve(newTab);
+      }, true);
+    }, true);
+  });
+}
new file mode 100644
--- /dev/null
+++ b/devtools/client/webconsole/new-console-output/test/mochitest/browser_webconsole_context_menu_store_as_global.js
@@ -0,0 +1,91 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+// Test the "Store as global variable" menu item of the webconsole is enabled only when
+// clicking on messages that are associated with an object actor.
+
+"use strict";
+
+const TEST_URI = `data:text/html;charset=utf-8,<script>
+  window.bar = { baz: 1 };
+  console.log("foo");
+  console.log("foo", window.bar);
+  console.log(["foo", window.bar, 2]);
+</script>`;
+
+add_task(function* () {
+  let hud = yield openNewTabAndConsole(TEST_URI);
+
+  let [msgWithText, msgWithObj, msgNested] =
+    yield waitFor(() => findMessages(hud, "foo"));
+  ok(msgWithText && msgWithObj && msgNested, "Three messages should have appeared");
+
+  let text = msgWithText.querySelector(".objectBox-string");
+  let objInMsgWithObj = msgWithObj.querySelector(".cm-variable");
+  let textInMsgWithObj = msgWithObj.querySelector(".objectBox-string");
+
+  // The third message has an object nested in an array, the array is therefore the top
+  // object, the object is the nested object.
+  let topObjInMsg = msgNested.querySelector(".objectBox-array > .cm-variable");
+  let nestedObjInMsg = msgNested.querySelector(".objectBox-object > .cm-variable");
+
+  info("Check store as global variable is disabled for text only messages");
+  let menuPopup = yield openContextMenu(hud, text);
+  let storeMenuItem = menuPopup.querySelector("#console-menu-store");
+  ok(storeMenuItem.disabled, "store as global variable is disabled for text message");
+  yield hideContextMenu(hud);
+
+  info("Check store as global variable is disabled for text in complex messages");
+  menuPopup = yield openContextMenu(hud, textInMsgWithObj);
+  storeMenuItem = menuPopup.querySelector("#console-menu-store");
+  ok(storeMenuItem.disabled,
+    "store as global variable is disabled for text in complex message");
+  yield hideContextMenu(hud);
+
+  info("Check store as global variable is enabled for objects in complex messages");
+  yield storeAsVariable(hud, objInMsgWithObj);
+
+  is(hud.jsterm.getInputValue(), "temp0", "Input was set");
+
+  let executedResult = yield hud.jsterm.execute();
+  ok(executedResult.textContent.includes("{ baz: 1 }"),
+     "Correct variable assigned into console");
+
+  info("Check store as global variable is enabled for top object in nested messages");
+  yield storeAsVariable(hud, topObjInMsg);
+
+  is(hud.jsterm.getInputValue(), "temp1", "Input was set");
+
+  executedResult = yield hud.jsterm.execute();
+  ok(executedResult.textContent.includes("[ \"foo\", Object, 2 ]"),
+     "Correct variable assigned into console " + executedResult.textContent);
+
+  info("Check store as global variable is enabled for nested object in nested messages");
+  yield storeAsVariable(hud, nestedObjInMsg);
+
+  is(hud.jsterm.getInputValue(), "temp2", "Input was set");
+
+  executedResult = yield hud.jsterm.execute();
+  ok(executedResult.textContent.includes("{ baz: 1 }"),
+     "Correct variable assigned into console " + executedResult.textContent);
+});
+
+function* storeAsVariable(hud, element) {
+  info("Check store as global variable is enabled");
+  let menuPopup = yield openContextMenu(hud, element);
+  let storeMenuItem = menuPopup.querySelector("#console-menu-store");
+  ok(!storeMenuItem.disabled,
+    "store as global variable is enabled for object in complex message");
+
+  info("Click on store as global variable");
+  let onceInputSet = hud.jsterm.once("set-input-value");
+  storeMenuItem.click();
+
+  info("Wait for console input to be updated with the temp variable");
+  yield onceInputSet;
+
+  info("Wait for context menu to be hidden");
+  yield hideContextMenu(hud);
+}
--- a/devtools/client/webconsole/new-console-output/test/mochitest/head.js
+++ b/devtools/client/webconsole/new-console-output/test/mochitest/head.js
@@ -1,14 +1,15 @@
 /* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
 /* vim: set ft=javascript ts=2 et sw=2 tw=80: */
 /* Any copyright is dedicated to the Public Domain.
  * http://creativecommons.org/publicdomain/zero/1.0/ */
 /* import-globals-from ../../../../framework/test/shared-head.js */
-/* exported WCUL10n, openNewTabAndConsole, waitForMessages, waitFor, findMessage */
+/* exported WCUL10n, openNewTabAndConsole, waitForMessages, waitFor, findMessage,
+   openContextMenu, hideContextMenu */
 
 "use strict";
 
 // shared-head.js handles imports, constants, and utility functions
 // Load the shared-head file first.
 Services.scriptloader.loadSubScript(
   "chrome://mochitests/content/browser/devtools/client/framework/test/shared-head.js",
   this);
@@ -35,17 +36,17 @@ registerCleanupFunction(function* () {
  *
  * @param string url
  *        The URL for the tab to be opened.
  * @return Promise
  *         Resolves when the tab has been added, loaded and the toolbox has been opened.
  *         Resolves to the toolbox.
  */
 var openNewTabAndConsole = Task.async(function* (url) {
-  let toolbox = yield openNewTabAndToolbox(TEST_URI, "webconsole");
+  let toolbox = yield openNewTabAndToolbox(url, "webconsole");
   let hud = toolbox.getCurrentPanel().hud;
   hud.jsterm._lazyVariablesView = false;
   return hud;
 });
 
 /**
  * Wait for messages in the web console output, resolving once they are receieved.
  *
@@ -135,8 +136,44 @@ function findMessage(hud, text, selector
 function findMessages(hud, text, selector = ".message") {
   const messages = hud.ui.experimentalOutputNode.querySelectorAll(selector);
   const elements = Array.prototype.filter.call(
     messages,
     (el) => el.textContent.includes(text)
   );
   return elements;
 }
+
+/**
+ * Simulate a context menu event on the provided element, and wait for the console context
+ * menu to open. Returns a promise that resolves the menu popup element.
+ *
+ * @param object hud
+ *        The web console.
+ * @param element element
+ *        The dom element on which the context menu event should be synthesized.
+ * @return promise
+ */
+function* openContextMenu(hud, element) {
+  let onConsoleMenuOpened = hud.ui.newConsoleOutput.once("menu-open");
+  synthesizeContextMenuEvent(element);
+  yield onConsoleMenuOpened;
+  return hud.ui.newConsoleOutput.toolbox.doc.getElementById("webconsole-menu");
+}
+
+/**
+ * Hide the webconsole context menu popup. Returns a promise that will resolve when the
+ * context menu popup is hidden or immediately if the popup can't be found.
+ *
+ * @param object hud
+ *        The web console.
+ * @return promise
+ */
+function hideContextMenu(hud) {
+  let popup = hud.ui.newConsoleOutput.toolbox.doc.getElementById("webconsole-menu");
+  if (!popup) {
+    return Promise.resolve();
+  }
+
+  let onPopupHidden = once(popup, "popuphidden");
+  popup.hidePopup();
+  return onPopupHidden;
+}
new file mode 100644
--- /dev/null
+++ b/devtools/client/webconsole/new-console-output/utils/context-menu.js
@@ -0,0 +1,143 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set ft=javascript 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/. */
+
+"use strict";
+
+const Services = require("Services");
+const {gDevTools} = require("devtools/client/framework/devtools");
+
+const Menu = require("devtools/client/framework/menu");
+const MenuItem = require("devtools/client/framework/menu-item");
+
+const { MESSAGE_SOURCE } = require("devtools/client/webconsole/new-console-output/constants");
+
+const {openVariablesView} = require("devtools/client/webconsole/new-console-output/utils/variables-view");
+const clipboardHelper = require("devtools/shared/platform/clipboard");
+const { l10n } = require("devtools/client/webconsole/new-console-output/utils/messages");
+
+/**
+ * Create a Menu instance for the webconsole.
+ *
+ * @param {Object} jsterm
+ *        The JSTerm instance used by the webconsole.
+ * @param {Element} parentNode
+ *        The container of the new console frontend output wrapper.
+ * @param {Object} options
+ *        - {String} actor (optional) actor id to use for context menu actions
+ *        - {String} clipboardText (optional) text to "Copy" if no selection is available
+ *        - {Object} message (optional) message object containing metadata such as:
+ *          - {String} source
+ *          - {String} request
+ */
+function createContextMenu(jsterm, parentNode, { actor, clipboardText, message }) {
+  let win = parentNode.ownerDocument.defaultView;
+  let selection = win.getSelection();
+
+  let { source, request } = message || {};
+
+  let menu = new Menu({
+    id: "webconsole-menu"
+  });
+
+  // Copy URL for a network request.
+  menu.append(new MenuItem({
+    id: "console-menu-copy-url",
+    label: l10n.getStr("webconsole.menu.copyURL.label"),
+    accesskey: l10n.getStr("webconsole.menu.copyURL.accesskey"),
+    visible: source === MESSAGE_SOURCE.NETWORK,
+    click: () => {
+      if (!request) {
+        return;
+      }
+      clipboardHelper.copyString(request.url);
+    },
+  }));
+
+  // Open URL in a new tab for a network request.
+  menu.append(new MenuItem({
+    id: "console-menu-open-url",
+    label: l10n.getStr("webconsole.menu.openURL.label"),
+    accesskey: l10n.getStr("webconsole.menu.openURL.accesskey"),
+    visible: source === MESSAGE_SOURCE.NETWORK,
+    click: () => {
+      if (!request) {
+        return;
+      }
+      let mainWindow = Services.wm.getMostRecentWindow(gDevTools.chromeWindowType);
+      mainWindow.openUILinkIn(request.url, "tab");
+    },
+  }));
+
+  // Open in variables view.
+  menu.append(new MenuItem({
+    id: "console-menu-open",
+    label: l10n.getStr("webconsole.menu.openInVarView.label"),
+    accesskey: l10n.getStr("webconsole.menu.openInVarView.accesskey"),
+    disabled: !actor,
+    click: () => {
+      openVariablesView(actor);
+    },
+  }));
+
+  // Store as global variable.
+  menu.append(new MenuItem({
+    id: "console-menu-store",
+    label: l10n.getStr("webconsole.menu.storeAsGlobalVar.label"),
+    accesskey: l10n.getStr("webconsole.menu.storeAsGlobalVar.accesskey"),
+    disabled: !actor,
+    click: () => {
+      let evalString = `{ let i = 0;
+        while (this.hasOwnProperty("temp" + i) && i < 1000) {
+          i++;
+        }
+        this["temp" + i] = _self;
+        "temp" + i;
+      }`;
+      let options = {
+        selectedObjectActor: actor,
+      };
+
+      jsterm.requestEvaluation(evalString, options).then((res) => {
+        jsterm.focus();
+        jsterm.setInputValue(res.result);
+      });
+    },
+  }));
+
+  // Copy message or grip.
+  menu.append(new MenuItem({
+    id: "console-menu-copy",
+    label: l10n.getStr("webconsole.menu.copy.label"),
+    accesskey: l10n.getStr("webconsole.menu.copy.accesskey"),
+    // Disabled if there is no selection and no message element available to copy.
+    disabled: selection.isCollapsed && !clipboardText,
+    click: () => {
+      if (selection.isCollapsed) {
+        // If the selection is empty/collapsed, copy the text content of the
+        // message for which the context menu was opened.
+        clipboardHelper.copyString(clipboardText);
+      } else {
+        clipboardHelper.copyString(selection.toString());
+      }
+    },
+  }));
+
+  // Select all.
+  menu.append(new MenuItem({
+    id: "console-menu-select",
+    label: l10n.getStr("webconsole.menu.selectAll.label"),
+    accesskey: l10n.getStr("webconsole.menu.selectAll.accesskey"),
+    disabled: false,
+    click: () => {
+      let webconsoleOutput = parentNode.querySelector(".webconsole-output");
+      selection.selectAllChildren(webconsoleOutput);
+    },
+  }));
+
+  return menu;
+}
+
+exports.createContextMenu = createContextMenu;
--- a/devtools/client/webconsole/new-console-output/utils/moz.build
+++ b/devtools/client/webconsole/new-console-output/utils/moz.build
@@ -1,10 +1,11 @@
 # 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/.
 
 DevToolsModules(
+    'context-menu.js',
     'id-generator.js',
     'messages.js',
     'variables-view.js',
 )
--- a/dom/base/NameSpaceConstants.h
+++ b/dom/base/NameSpaceConstants.h
@@ -19,11 +19,12 @@ static const int32_t kNameSpaceID_None =
 #define kNameSpaceID_XLink    4
 #define kNameSpaceID_XSLT     5
 #define kNameSpaceID_XBL      6
 #define kNameSpaceID_MathML   7
 #define kNameSpaceID_RDF      8
 #define kNameSpaceID_XUL      9
 #define kNameSpaceID_SVG      10
 #define kNameSpaceID_disabled_MathML      11
-#define kNameSpaceID_LastBuiltin          11 // last 'built-in' namespace
+#define kNameSpaceID_disabled_SVG         12
+#define kNameSpaceID_LastBuiltin          12 // last 'built-in' namespace
 
 #endif // mozilla_dom_NameSpaceConstants_h__
--- a/dom/base/TimeoutManager.cpp
+++ b/dom/base/TimeoutManager.cpp
@@ -600,17 +600,17 @@ TimeoutManager::MaybeApplyBackPressure()
   if (queue->Length() < kThrottledEventQueueBackPressure) {
     return;
   }
 
   // First attempt to dispatch a runnable to update our back pressure state.  We
   // do this first in order to verify we can dispatch successfully before
   // entering the back pressure state.
   nsCOMPtr<nsIRunnable> r =
-    NewNonOwningRunnableMethod<StorensRefPtrPassByPtr<nsGlobalWindow>>(this,
+    NewNonOwningRunnableMethod<StoreRefPtrPassByPtr<nsGlobalWindow>>(this,
       &TimeoutManager::CancelOrUpdateBackPressure, &mWindow);
   nsresult rv = queue->Dispatch(r.forget(), NS_DISPATCH_NORMAL);
   NS_ENSURE_SUCCESS_VOID(rv);
 
   // Since the callback was scheduled successfully we can now persist the
   // backpressure value.
   mBackPressureDelayMS = CalculateNewBackPressureDelayMS(queue->Length());
 }
@@ -663,17 +663,17 @@ TimeoutManager::CancelOrUpdateBackPressu
 
   // Otherwise, if there is a back pressure delay still in effect we need
   // queue a runnable to check if it can be reduced in the future.  Note
   // that this runnable is dispatched to the ThrottledEventQueue.  This
   // means we will not check for a new value until the current back log
   // has been processed.  The next update will only keep back pressure if
   // more runnables continue to be dispatched to the queue.
   nsCOMPtr<nsIRunnable> r =
-    NewNonOwningRunnableMethod<StorensRefPtrPassByPtr<nsGlobalWindow>>(this,
+    NewNonOwningRunnableMethod<StoreRefPtrPassByPtr<nsGlobalWindow>>(this,
       &TimeoutManager::CancelOrUpdateBackPressure, &mWindow);
   MOZ_ALWAYS_SUCCEEDS(queue->Dispatch(r.forget(), NS_DISPATCH_NORMAL));
 }
 
 bool
 TimeoutManager::RescheduleTimeout(Timeout* aTimeout, const TimeStamp& now,
                                   bool aRunningPendingTimeouts)
 {
--- a/dom/base/nsFrameLoader.cpp
+++ b/dom/base/nsFrameLoader.cpp
@@ -983,18 +983,25 @@ nsFrameLoader::AddTreeItemToTreeOwner(ns
                                       nsIDocShellTreeOwner* aOwner,
                                       int32_t aParentType,
                                       nsIDocShell* aParentNode)
 {
   NS_PRECONDITION(aItem, "Must have docshell treeitem");
   NS_PRECONDITION(mOwnerContent, "Must have owning content");
 
   nsAutoString value;
-  bool isContent = mOwnerContent->AttrValueIs(
-    kNameSpaceID_None, TypeAttrName(), nsGkAtoms::content, eIgnoreCase);
+  bool isContent = false;
+  mOwnerContent->GetAttr(kNameSpaceID_None, TypeAttrName(), value);
+
+  // we accept "content" and "content-xxx" values.
+  // We ignore anything that comes after 'content-'.
+  isContent = value.LowerCaseEqualsLiteral("content") ||
+    StringBeginsWith(value, NS_LITERAL_STRING("content-"),
+                     nsCaseInsensitiveStringComparator());
+
 
   // Force mozbrowser frames to always be typeContent, even if the
   // mozbrowser interfaces are disabled.
   nsCOMPtr<nsIDOMMozBrowserFrame> mozbrowser =
     do_QueryInterface(mOwnerContent);
   if (mozbrowser) {
     bool isMozbrowser = false;
     mozbrowser->GetMozbrowser(&isMozbrowser);
@@ -2888,18 +2895,22 @@ nsFrameLoader::TryRemoteBrowser()
         return false;
       }
     }
 
     if (!mOwnerContent->IsXULElement()) {
       return false;
     }
 
-    if (!mOwnerContent->AttrValueIs(kNameSpaceID_None, nsGkAtoms::type,
-                                    nsGkAtoms::content, eIgnoreCase)) {
+    nsAutoString value;
+    mOwnerContent->GetAttr(kNameSpaceID_None, nsGkAtoms::type, value);
+
+    if (!value.LowerCaseEqualsLiteral("content") &&
+        !StringBeginsWith(value, NS_LITERAL_STRING("content-"),
+                          nsCaseInsensitiveStringComparator())) {
       return false;
     }
 
     // Try to get the related content parent from our browser element.
     openerContentParent = GetContentParent(mOwnerContent);
   }
 
   uint32_t chromeFlags = 0;
@@ -3349,17 +3360,23 @@ nsFrameLoader::AttributeChanged(nsIDocum
   if (!is_primary) {
     nsXULPopupManager* pm = nsXULPopupManager::GetInstance();
     if (pm)
       pm->HidePopupsInDocShell(mDocShell);
   }
 #endif
 
   parentTreeOwner->ContentShellRemoved(mDocShell);
-  if (aElement->AttrValueIs(kNameSpaceID_None, TypeAttrName(), nsGkAtoms::content, eIgnoreCase)) {
+
+  nsAutoString value;
+  aElement->GetAttr(kNameSpaceID_None, TypeAttrName(), value);
+
+  if (value.LowerCaseEqualsLiteral("content") ||
+      StringBeginsWith(value, NS_LITERAL_STRING("content-"),
+                       nsCaseInsensitiveStringComparator())) {
     parentTreeOwner->ContentShellAdded(mDocShell, is_primary);
   }
 }
 
 /**
  * Send the RequestNotifyAfterRemotePaint message to the current Tab.
  */
 NS_IMETHODIMP
--- a/dom/base/nsNameSpaceManager.cpp
+++ b/dom/base/nsNameSpaceManager.cpp
@@ -23,19 +23,21 @@
 #include "mozilla/ClearOnShutdown.h"
 #include "mozilla/dom/XBLChildrenElement.h"
 #include "mozilla/dom/Element.h"
 #include "mozilla/Preferences.h"
 
 using namespace mozilla;
 using namespace mozilla::dom;
 
+static const char* kPrefSVGDisabled = "svg.disabled";
 static const char* kPrefMathMLDisabled = "mathml.disabled";
 static const char* kObservedPrefs[] = {
   kPrefMathMLDisabled,
+  kPrefSVGDisabled,
   nullptr
 };
 StaticRefPtr<nsNameSpaceManager> nsNameSpaceManager::sInstance;
 
 /* static */ nsNameSpaceManager*
 nsNameSpaceManager::GetInstance() {
   if (!sInstance) {
     sInstance = new nsNameSpaceManager();
@@ -58,32 +60,34 @@ bool nsNameSpaceManager::Init()
   NS_ENSURE_SUCCESS(rv, false)
 
 #define REGISTER_DISABLED_NAMESPACE(uri, id) \
   rv = AddDisabledNameSpace(dont_AddRef(uri), id); \
   NS_ENSURE_SUCCESS(rv, false)
 
   mozilla::Preferences::AddStrongObservers(this, kObservedPrefs);
   mMathMLDisabled = mozilla::Preferences::GetBool(kPrefMathMLDisabled);
+  mSVGDisabled = mozilla::Preferences::GetBool(kPrefSVGDisabled);
 
 
   // Need to be ordered according to ID.
   MOZ_ASSERT(mURIArray.IsEmpty());
   REGISTER_NAMESPACE(nsGkAtoms::empty, kNameSpaceID_None);
   REGISTER_NAMESPACE(nsGkAtoms::nsuri_xmlns, kNameSpaceID_XMLNS);
   REGISTER_NAMESPACE(nsGkAtoms::nsuri_xml, kNameSpaceID_XML);
   REGISTER_NAMESPACE(nsGkAtoms::nsuri_xhtml, kNameSpaceID_XHTML);
   REGISTER_NAMESPACE(nsGkAtoms::nsuri_xlink, kNameSpaceID_XLink);
   REGISTER_NAMESPACE(nsGkAtoms::nsuri_xslt, kNameSpaceID_XSLT);
   REGISTER_NAMESPACE(nsGkAtoms::nsuri_xbl, kNameSpaceID_XBL);
   REGISTER_NAMESPACE(nsGkAtoms::nsuri_mathml, kNameSpaceID_MathML);
   REGISTER_NAMESPACE(nsGkAtoms::nsuri_rdf, kNameSpaceID_RDF);
   REGISTER_NAMESPACE(nsGkAtoms::nsuri_xul, kNameSpaceID_XUL);
   REGISTER_NAMESPACE(nsGkAtoms::nsuri_svg, kNameSpaceID_SVG);
   REGISTER_DISABLED_NAMESPACE(nsGkAtoms::nsuri_mathml, kNameSpaceID_disabled_MathML);
+  REGISTER_DISABLED_NAMESPACE(nsGkAtoms::nsuri_svg, kNameSpaceID_disabled_SVG);
 
 #undef REGISTER_NAMESPACE
 #undef REGISTER_DISABLED_NAMESPACE
 
   return true;
 }
 
 nsresult
@@ -146,19 +150,21 @@ int32_t
 nsNameSpaceManager::GetNameSpaceID(nsIAtom* aURI,
                                    bool aInChromeDoc)
 {
   if (aURI == nsGkAtoms::_empty) {
     return kNameSpaceID_None; // xmlns="", see bug 75700 for details
   }
 
   int32_t nameSpaceID;
-  if (mMathMLDisabled &&
-      mDisabledURIToIDTable.Get(aURI, &nameSpaceID) &&
-      !aInChromeDoc) {
+  if (!aInChromeDoc
+      && (mMathMLDisabled || mSVGDisabled)
+      && mDisabledURIToIDTable.Get(aURI, &nameSpaceID)
+      && ((mMathMLDisabled && kNameSpaceID_disabled_MathML == nameSpaceID) ||
+      (mSVGDisabled && kNameSpaceID_disabled_SVG == nameSpaceID))) {
     NS_POSTCONDITION(nameSpaceID >= 0, "Bogus namespace ID");
     return nameSpaceID;
   }
   if (mURIToIDTable.Get(aURI, &nameSpaceID)) {
     NS_POSTCONDITION(nameSpaceID >= 0, "Bogus namespace ID");
     return nameSpaceID;
   }
 
@@ -181,28 +187,59 @@ NS_NewElement(Element** aResult,
     return NS_NewXULElement(aResult, ni.forget());
   }
 #endif
   if (ns == kNameSpaceID_MathML) {
     // If the mathml.disabled pref. is true, convert all MathML nodes into
     // disabled MathML nodes by swapping the namespace.
     nsNameSpaceManager* nsmgr = nsNameSpaceManager::GetInstance();
     if ((nsmgr && !nsmgr->mMathMLDisabled) ||
-        nsContentUtils::IsChromeDoc(ni->GetDocument())) {
+        nsContentUtils::IsSystemPrincipal(ni->GetDocument()->NodePrincipal())) {
       return NS_NewMathMLElement(aResult, ni.forget());
     }
 
     RefPtr<mozilla::dom::NodeInfo> genericXMLNI =
       ni->NodeInfoManager()->
       GetNodeInfo(ni->NameAtom(), ni->GetPrefixAtom(),
         kNameSpaceID_disabled_MathML, ni->NodeType(), ni->GetExtraName());
     return NS_NewXMLElement(aResult, genericXMLNI.forget());
   }
   if (ns == kNameSpaceID_SVG) {
-    return NS_NewSVGElement(aResult, ni.forget(), aFromParser);
+    // If the svg.disabled pref. is true, convert all SVG nodes into
+    // disabled SVG nodes by swapping the namespace.
+    nsNameSpaceManager* nsmgr = nsNameSpaceManager::GetInstance();
+    nsCOMPtr<nsILoadInfo> loadInfo;
+    bool SVGEnabled = false;
+
+    if (nsmgr && !nsmgr->mSVGDisabled) {
+      SVGEnabled = true;
+    } else {
+      nsCOMPtr<nsIChannel> channel = ni->GetDocument()->GetChannel();
+      // We don't have a channel for SVGs constructed inside a SVG script
+      if (channel) {
+        loadInfo  = channel->GetLoadInfo();
+      }
+    }
+    if (SVGEnabled ||
+        nsContentUtils::IsSystemPrincipal(ni->GetDocument()->NodePrincipal()) ||
+        (loadInfo &&
+         (loadInfo->GetExternalContentPolicyType() == nsIContentPolicy::TYPE_IMAGE ||
+         loadInfo->GetExternalContentPolicyType() == nsIContentPolicy::TYPE_OTHER) &&
+         (nsContentUtils::IsSystemPrincipal(loadInfo->LoadingPrincipal()) ||
+          nsContentUtils::IsSystemPrincipal(loadInfo->TriggeringPrincipal())
+         )
+        )
+       ) {
+      return NS_NewSVGElement(aResult, ni.forget(), aFromParser);
+    }
+    RefPtr<mozilla::dom::NodeInfo> genericXMLNI =
+      ni->NodeInfoManager()->
+      GetNodeInfo(ni->NameAtom(), ni->GetPrefixAtom(),
+        kNameSpaceID_disabled_SVG, ni->NodeType(), ni->GetExtraName());
+    return NS_NewXMLElement(aResult, genericXMLNI.forget());
   }
   if (ns == kNameSpaceID_XBL && ni->Equals(nsGkAtoms::children)) {
     NS_ADDREF(*aResult = new XBLChildrenElement(ni.forget()));
     return NS_OK;
   }
 
   return NS_NewXMLElement(aResult, ni.forget());
 }
@@ -257,10 +294,11 @@ NS_IMPL_ISUPPORTS(nsNameSpaceManager,
                   nsIObserver)
 
 // nsIObserver
 NS_IMETHODIMP
 nsNameSpaceManager::Observe(nsISupports* aObject, const char* aTopic,
                             const char16_t* aMessage)
 {
   mMathMLDisabled = mozilla::Preferences::GetBool(kPrefMathMLDisabled);
+  mSVGDisabled = mozilla::Preferences::GetBool(kPrefSVGDisabled);
   return NS_OK;
 }
--- a/dom/base/nsNameSpaceManager.h
+++ b/dom/base/nsNameSpaceManager.h
@@ -61,16 +61,17 @@ public:
                          bool aInChromeDoc);
   int32_t GetNameSpaceID(nsIAtom* aURI,
                          bool aInChromeDoc);
 
   bool HasElementCreator(int32_t aNameSpaceID);
 
   static nsNameSpaceManager* GetInstance();
   bool mMathMLDisabled;
+  bool mSVGDisabled;
 
 private:
   bool Init();
   nsresult AddNameSpace(already_AddRefed<nsIAtom> aURI, const int32_t aNameSpaceID);
   nsresult AddDisabledNameSpace(already_AddRefed<nsIAtom> aURI, const int32_t aNameSpaceID);
   ~nsNameSpaceManager() {};
 
   nsDataHashtable<nsISupportsHashKey, int32_t> mURIToIDTable;
--- a/dom/html/HTMLMediaElement.cpp
+++ b/dom/html/HTMLMediaElement.cpp
@@ -2018,16 +2018,22 @@ void HTMLMediaElement::NotifyMediaTrackD
       VideoFrameContainer* container = GetVideoFrameContainer();
       if (mSrcStreamIsPlaying && container) {
         mSelectedVideoStreamTrack->RemoveVideoOutput(container);
       }
       mSelectedVideoStreamTrack = nullptr;
     }
   }
 
+  if (mReadyState == HAVE_NOTHING) {
+    // No MediaStreamTracks are captured until we have metadata, and code
+    // below doesn't do anything for captured decoders.
+    return;
+  }
+
   for (OutputMediaStream& ms : mOutputStreams) {
     if (ms.mCapturingDecoder) {
       MOZ_ASSERT(!ms.mCapturingMediaStream);
       continue;
     }
     MOZ_ASSERT(ms.mCapturingMediaStream);
     for (int32_t i = ms.mTrackPorts.Length() - 1; i >= 0; --i) {
       if (ms.mTrackPorts[i].first() == aTrack->GetId()) {
@@ -2908,16 +2914,20 @@ public:
                            TrackID aDestinationTrackID)
     : MediaStreamTrackSource(aCapturedTrackSource->GetPrincipal(),
                              nsString())
     , mElement(aElement)
     , mCapturedTrackSource(aCapturedTrackSource)
     , mOwningStream(aOwningStream)
     , mDestinationTrackID(aDestinationTrackID)
   {
+    MOZ_ASSERT(mElement);
+    MOZ_ASSERT(mCapturedTrackSource);
+    MOZ_ASSERT(mOwningStream);
+    MOZ_ASSERT(IsTrackIDExplicit(mDestinationTrackID));
   }
 
   void Destroy() override
   {
     if (mCapturedTrackSource) {
       mCapturedTrackSource->UnregisterSink(this);
       mCapturedTrackSource = nullptr;
     }
@@ -2925,16 +2935,21 @@ public:
 
   MediaSourceEnum GetMediaSource() const override
   {
     return MediaSourceEnum::Other;
   }
 
   CORSMode GetCORSMode() const override
   {
+    if (!mCapturedTrackSource) {
+      // This could happen during shutdown.
+      return CORS_NONE;
+    }
+
     return mCapturedTrackSource->GetCORSMode();
   }
 
   void Stop() override
   {
     if (mElement && mElement->mSrcStream) {
       // Only notify if we're still playing the source stream. GC might have
       // cleared it before the track sources.
@@ -2943,16 +2958,21 @@ public:
     mElement = nullptr;
     mOwningStream = nullptr;
 
     Destroy();
   }
 
   void PrincipalChanged() override
   {
+    if (!mCapturedTrackSource) {
+      // This could happen during shutdown.
+      return;
+    }
+
     mPrincipal = mCapturedTrackSource->GetPrincipal();
     MediaStreamTrackSource::PrincipalChanged();
   }
 
 private:
   virtual ~StreamCaptureTrackSource() {}
 
   RefPtr<HTMLMediaElement> mElement;
@@ -2988,29 +3008,36 @@ public:
     , mElement(aElement)
   {
     MOZ_ASSERT(mElement);
     mElement->AddDecoderPrincipalChangeObserver(this);
   }
 
   void Destroy() override
   {
-    MOZ_ASSERT(mElement);
-    DebugOnly<bool> res = mElement->RemoveDecoderPrincipalChangeObserver(this);
-    NS_ASSERTION(res, "Removing decoder principal changed observer failed. "
-                      "Had it already been removed?");
+    if (mElement) {
+      DebugOnly<bool> res = mElement->RemoveDecoderPrincipalChangeObserver(this);
+      NS_ASSERTION(res, "Removing decoder principal changed observer failed. "
+                        "Had it already been removed?");
+      mElement = nullptr;
+    }
   }
 
   MediaSourceEnum GetMediaSource() const override
   {
     return MediaSourceEnum::Other;
   }
 
   CORSMode GetCORSMode() const override
   {
+    if (!mElement) {
+      MOZ_ASSERT(false, "Should always have an element if in use");
+      return CORS_NONE;
+    }
+
     return mElement->GetCORSMode();
   }
 
   void Stop() override
   {
     // We don't notify the source that a track was stopped since it will keep
     // producing tracks until the element ends. The decoder also needs the
     // tracks it created to be live at the source since the decoder's clock is
@@ -3118,16 +3145,22 @@ HTMLMediaElement::AddCaptureMediaTrackTo
                                                      bool aAsyncAddtrack)
 {
   if (aOutputStream.mCapturingDecoder) {
     MOZ_ASSERT(!aOutputStream.mCapturingMediaStream);
     return;
   }
   aOutputStream.mCapturingMediaStream = true;
 
+  if (aOutputStream.mStream == mSrcStream) {
+    // Cycle detected. This can happen since tracks are added async.
+    // We avoid forwarding it to the output here or we'd get into an infloop.
+    return;
+  }
+
   MediaStream* outputSource = aOutputStream.mStream->GetInputStream();
   if (!outputSource) {
     NS_ERROR("No output source stream");
     return;
   }
 
   ProcessedMediaStream* processedOutputSource =
     outputSource->AsProcessedStream();
@@ -3166,17 +3199,17 @@ HTMLMediaElement::AddCaptureMediaTrackTo
                           ? MediaSegment::AUDIO
                           : MediaSegment::VIDEO;
 
   RefPtr<MediaStreamTrack> track =
     aOutputStream.mStream->CreateDOMTrack(destinationTrackID, type, source);
 
   if (aAsyncAddtrack) {
     NS_DispatchToMainThread(
-      NewRunnableMethod<StorensRefPtrPassByPtr<MediaStreamTrack>>(
+      NewRunnableMethod<StoreRefPtrPassByPtr<MediaStreamTrack>>(
         aOutputStream.mStream, &DOMMediaStream::AddTrackInternal, track));
   } else {
     aOutputStream.mStream->AddTrackInternal(track);
   }
 
   // Track is muted initially, so we don't leak data if it's added while paused
   // and an MSG iteration passes before the mute comes into effect.
   processedOutputSource->SetTrackEnabled(destinationTrackID,
--- a/dom/media/MediaManager.cpp
+++ b/dom/media/MediaManager.cpp
@@ -1848,17 +1848,17 @@ MediaManager::GetNonE10sParent()
   }
   return mNonE10sParent;
 }
 
 /* static */ void
 MediaManager::StartupInit()
 {
 #ifdef WIN32
-  if (IsVistaOrLater() && !IsWin8OrLater()) {
+  if (!IsWin8OrLater()) {
     // Bug 1107702 - Older Windows fail in GetAdaptersInfo (and others) if the
     // first(?) call occurs after the process size is over 2GB (kb/2588507).
     // Attempt to 'prime' the pump by making a call at startup.
     unsigned long out_buf_len = sizeof(IP_ADAPTER_INFO);
     PIP_ADAPTER_INFO pAdapterInfo = (IP_ADAPTER_INFO *) moz_xmalloc(out_buf_len);
     if (GetAdaptersInfo(pAdapterInfo, &out_buf_len) == ERROR_BUFFER_OVERFLOW) {
       free(pAdapterInfo);
       pAdapterInfo = (IP_ADAPTER_INFO *) moz_xmalloc(out_buf_len);
@@ -2110,29 +2110,21 @@ if (privileged) {
           nsPIDOMWindowOuter* outer = aWindow->GetOuterWindow();
           vc.mBrowserWindow.Construct(outer->WindowID());
         }
         MOZ_FALLTHROUGH;
       case MediaSourceEnum::Screen:
       case MediaSourceEnum::Application:
       case MediaSourceEnum::Window:
         // Deny screensharing request if support is disabled, or
-        // the requesting document is not from a host on the whitelist, or
-        // we're on WinXP until proved that it works
+        // the requesting document is not from a host on the whitelist.
         if (!Preferences::GetBool(((videoType == MediaSourceEnum::Browser)?
                                    "media.getusermedia.browser.enabled" :
                                    "media.getusermedia.screensharing.enabled"),
                                   false) ||
-#if defined(XP_WIN)
-            (
-              // Allow tab sharing for all platforms including XP
-              (videoType != MediaSourceEnum::Browser) &&
-              !Preferences::GetBool("media.getusermedia.screensharing.allow_on_old_platforms",
-                                    false) && !IsVistaOrLater()) ||
-#endif
             (!privileged && !HostIsHttps(*docURI))) {
           RefPtr<MediaStreamError> error =
               new MediaStreamError(aWindow,
                                    NS_LITERAL_STRING("NotAllowedError"));
           onFailure->OnError(error);
           return NS_OK;
         }
         break;
--- a/dom/media/PeerConnection.js
+++ b/dom/media/PeerConnection.js
@@ -292,27 +292,39 @@ RTCStatsReport.prototype = {
   contractID: PC_STATS_CONTRACT,
   QueryInterface: XPCOMUtils.generateQI([Ci.nsISupports]),
 
   setInternal: function(aKey, aObj) {
     return this.__DOM_IMPL__.__set(aKey, aObj);
   },
 
   // TODO: Remove legacy API eventually
+  // see Bug 1328194
   //
   // Since maplike is recent, we still also make the stats available as legacy
   // enumerable read-only properties directly on our content-facing object.
   // Must be called after our webidl sandwich is made.
+  _specToLegacyFieldMapping: {
+        'inbound-rtp' : 'inboundrtp',
+        'outbound-rtp':'outboundrtp',
+        'candidate-pair':'candidatepair',
+        'local-candidate':'localcandidate',
+        'remote-candidate':'remotecandidate'
+  },
 
-  makeStatsPublic: function(warnNullable) {
+  makeStatsPublic: function(warnNullable, isLegacy) {
     let legacyProps = {};
     for (let key in this._report) {
+      let internal = Cu.cloneInto(this._report[key], this._win);
+      if (isLegacy) {
+        internal.type = this._specToLegacyFieldMapping[internal.type] || internal.type;
+      }
+      this.setInternal(key, internal);
       let value = Cu.cloneInto(this._report[key], this._win);
-      this.setInternal(key, value);
-
+      value.type = this._specToLegacyFieldMapping[value.type] || value.type;
       legacyProps[key] = {
         enumerable: true, configurable: false,
         get: Cu.exportFunction(function() {
           if (warnNullable.warn) {
             warnNullable.warn();
             warnNullable.warn = null;
           }
           return value;
@@ -351,16 +363,20 @@ function RTCPeerConnection() {
   // is set to true or false based on the presence of the "trickle" ice-option
   this._canTrickle = null;
 
   // States
   this._iceGatheringState = this._iceConnectionState = "new";
 
   this._hasStunServer = this._hasTurnServer = false;
   this._iceGatheredRelayCandidates = false;
+
+  // TODO: Remove legacy API eventually
+  // see Bug 1328194
+  this._onGetStatsIsLegacy = false;
 }
 RTCPeerConnection.prototype = {
   classDescription: "RTCPeerConnection",
   classID: PC_CID,
   contractID: PC_CONTRACT,
   QueryInterface: XPCOMUtils.generateQI([Ci.nsISupports,
                                          Ci.nsIDOMGlobalPropertyInitializer]),
   init: function(win) { this._win = win; },
@@ -1206,27 +1222,29 @@ RTCPeerConnection.prototype = {
     if (state != this._iceConnectionState) {
       this._iceConnectionState = state;
       _globalPCList.notifyLifecycleObservers(this, "iceconnectionstatechange");
       this.dispatchEvent(new this._win.Event("iceconnectionstatechange"));
     }
   },
 
   getStats: function(selector, onSucc, onErr) {
-    if (typeof onSucc == "function" &&
+    let isLegacy = (typeof onSucc) == "function";
+    if (isLegacy &&
         this._warnDeprecatedStatsCallbacksNullable.warn) {
       this._warnDeprecatedStatsCallbacksNullable.warn();
       this._warnDeprecatedStatsCallbacksNullable.warn = null;
     }
-    return this._auto(onSucc, onErr, () => this._getStats(selector));
+    return this._auto(onSucc, onErr, () => this._getStats(selector, isLegacy));
   },
 
-  _getStats: async function(selector) {
+  _getStats: async function(selector, isLegacy) {
     // getStats is allowed even in closed state.
     return await this._chain(() => new Promise((resolve, reject) => {
+      this._onGetStatsIsLegacy = isLegacy;
       this._onGetStatsSuccess = resolve;
       this._onGetStatsFailure = reject;
       this._impl.getStats(selector);
     }));
   },
 
   createDataChannel: function(label, {
                                 maxRetransmits, ordered, negotiated,
@@ -1479,17 +1497,18 @@ PeerConnectionObserver.prototype = {
     }
   },
 
   onGetStatsSuccess: function(dict) {
     let pc = this._dompc;
     let chromeobj = new RTCStatsReport(pc._win, dict);
     let webidlobj = pc._win.RTCStatsReport._create(pc._win, chromeobj);
     chromeobj.makeStatsPublic(pc._warnDeprecatedStatsCallbacksNullable &&
-                              pc._warnDeprecatedStatsAccessNullable);
+                              pc._warnDeprecatedStatsAccessNullable,
+                              pc._onGetStatsIsLegacy);
     pc._onGetStatsSuccess(webidlobj);
   },
 
   onGetStatsError: function(code, message) {
     this._dompc._onGetStatsFailure(this.newError(message, code));
   },
 
   onAddStream: function(stream) {
--- a/dom/media/eme/MediaKeySystemAccess.cpp
+++ b/dom/media/eme/MediaKeySystemAccess.cpp
@@ -8,17 +8,16 @@
 #include "mozilla/dom/MediaKeySystemAccessBinding.h"
 #include "mozilla/Preferences.h"
 #include "MediaPrefs.h"
 #include "nsContentTypeParser.h"
 #ifdef MOZ_FMP4
 #include "MP4Decoder.h"
 #endif
 #ifdef XP_WIN
-#include "mozilla/WindowsVersion.h"
 #include "WMFDecoderModule.h"
 #endif
 #include "nsContentCID.h"
 #include "nsServiceManagerUtils.h"
 #include "mozIGeckoMediaPluginService.h"
 #include "VideoUtils.h"
 #include "mozilla/Services.h"
 #include "nsIObserverService.h"
@@ -34,16 +33,17 @@
 #include "WebMDecoder.h"
 #include "mozilla/StaticPtr.h"
 #include "mozilla/ClearOnShutdown.h"
 #include "nsUnicharUtils.h"
 #include "mozilla/dom/MediaSource.h"
 #ifdef MOZ_WIDGET_ANDROID
 #include "FennecJNIWrappers.h"
 #endif
+
 namespace mozilla {
 namespace dom {
 
 NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(MediaKeySystemAccess,
                                       mParent)
 NS_IMPL_CYCLE_COLLECTING_ADDREF(MediaKeySystemAccess)
 NS_IMPL_CYCLE_COLLECTING_RELEASE(MediaKeySystemAccess)
 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(MediaKeySystemAccess)
@@ -135,36 +135,22 @@ MediaKeySystemAccess::GetKeySystemStatus
   }
 
   if (Preferences::GetBool("media.gmp-eme-adobe.visible", false)) {
     if (IsPrimetimeKeySystem(aKeySystem)) {
       if (!Preferences::GetBool("media.gmp-eme-adobe.enabled", false)) {
         aOutMessage = NS_LITERAL_CSTRING("Adobe EME disabled");
         return MediaKeySystemStatus::Cdm_disabled;
       }
-#ifdef XP_WIN
-      // Win Vista and later only.
-      if (!IsVistaOrLater()) {
-        aOutMessage = NS_LITERAL_CSTRING("Minimum Windows version (Vista) not met for Adobe EME");
-        return MediaKeySystemStatus::Cdm_not_supported;
-      }
-#endif
       return EnsureCDMInstalled(aKeySystem, aOutMessage);
     }
   }
 
   if (IsWidevineKeySystem(aKeySystem)) {
     if (Preferences::GetBool("media.gmp-widevinecdm.visible", false)) {
-#ifdef XP_WIN
-      // Win Vista and later only.
-      if (!IsVistaOrLater()) {
-        aOutMessage = NS_LITERAL_CSTRING("Minimum Windows version (Vista) not met for Widevine EME");
-        return MediaKeySystemStatus::Cdm_not_supported;
-      }
-#endif
       if (!Preferences::GetBool("media.gmp-widevinecdm.enabled", false)) {
         aOutMessage = NS_LITERAL_CSTRING("Widevine EME disabled");
         return MediaKeySystemStatus::Cdm_disabled;
       }
       return EnsureCDMInstalled(aKeySystem, aOutMessage);
 #ifdef MOZ_WIDGET_ANDROID
     } else if (Preferences::GetBool("media.mediadrm-widevinecdm.visible", false)) {
         nsCString keySystem = NS_ConvertUTF16toUTF8(aKeySystem);
--- a/dom/media/gmp/GMPChild.cpp
+++ b/dom/media/gmp/GMPChild.cpp
@@ -293,22 +293,20 @@ GMPChild::GetAPI(const char* aAPIName,
 
 mozilla::ipc::IPCResult
 GMPChild::RecvPreloadLibs(const nsCString& aLibs)
 {
 #ifdef XP_WIN
   // Pre-load DLLs that need to be used by the EME plugin but that can't be
   // loaded after the sandbox has started
   // Items in this must be lowercase!
-  static const char* whitelist[] = {
+  static const char *const whitelist[] = {
     "d3d9.dll", // Create an `IDirect3D9` to get adapter information
     "dxva2.dll", // Get monitor information
     "evr.dll", // MFGetStrideForBitmapInfoHeader
-    "mfh264dec.dll", // H.264 decoder (on Windows Vista)
-    "mfheaacdec.dll", // AAC decoder (on Windows Vista)
     "mfplat.dll", // MFCreateSample, MFCreateAlignedMemoryBuffer, MFCreateMediaType
     "msauddecmft.dll", // AAC decoder (on Windows 8)
     "msmpeg2adec.dll", // AAC decoder (on Windows 7)
     "msmpeg2vdec.dll", // H.264 decoder
   };
 
   nsTArray<nsCString> libs;
   SplitAt(", ", aLibs, libs);
--- a/dom/media/gtest/TestGMPCrossOrigin.cpp
+++ b/dom/media/gtest/TestGMPCrossOrigin.cpp
@@ -19,20 +19,16 @@
 #include "nsISimpleEnumerator.h"
 #include "mozilla/Atomics.h"
 #include "nsNSSComponent.h"
 #include "mozilla/DebugOnly.h"
 #include "GMPDeviceBinding.h"
 #include "mozilla/dom/MediaKeyStatusMapBinding.h" // For MediaKeyStatus
 #include "mozilla/dom/MediaKeyMessageEventBinding.h" // For MediaKeyMessageType
 
-#if defined(XP_WIN)
-#include "mozilla/WindowsVersion.h"
-#endif
-
 using namespace std;
 
 using namespace mozilla;
 using namespace mozilla::gmp;
 
 struct GMPTestRunner
 {
   NS_INLINE_DECL_THREADSAFE_REFCOUNTING(GMPTestRunner)
@@ -1530,21 +1526,16 @@ TEST(GeckoMediaPlugins, GMPStorageAsyncS
 
 TEST(GeckoMediaPlugins, GMPPluginVoucher) {
   RefPtr<GMPStorageTest> runner = new GMPStorageTest();
   runner->DoTest(&GMPStorageTest::TestPluginVoucher);
 }
 
 #if defined(XP_WIN)
 TEST(GeckoMediaPlugins, GMPOutputProtection) {
-  // Output Protection is not available pre-Vista.
-  if (!IsVistaOrLater()) {
-    return;
-  }
-
   RefPtr<GMPStorageTest> runner = new GMPStorageTest();
   runner->DoTest(&GMPStorageTest::TestOutputProtection);
 }
 #endif
 
 TEST(GeckoMediaPlugins, GMPStorageGetRecordNamesInMemoryStorage) {
   RefPtr<GMPStorageTest> runner = new GMPStorageTest();
   runner->DoTest(&GMPStorageTest::TestGetRecordNamesInMemoryStorage);
--- a/dom/media/mediasource/TrackBuffersManager.cpp
+++ b/dom/media/mediasource/TrackBuffersManager.cpp
@@ -955,17 +955,17 @@ TrackBuffersManager::OnDemuxerInitDone(n
 
   int64_t videoDuration = numVideos ? info.mVideo.mDuration : 0;
   int64_t audioDuration = numAudios ? info.mAudio.mDuration : 0;
 
   int64_t duration = std::max(videoDuration, audioDuration);
   // 1. Update the duration attribute if it currently equals NaN.
   // Those steps are performed by the MediaSourceDecoder::SetInitialDuration
   AbstractThread::MainThread()->Dispatch(NewRunnableMethod<int64_t>
-                                         (mParentDecoder,
+                                         (mParentDecoder.get(),
                                           &MediaSourceDecoder::SetInitialDuration,
                                           duration ? duration : -1));
 
   // 2. If the initialization segment has no audio, video, or text tracks, then
   // run the append error algorithm with the decode error parameter set to true
   // and abort these steps.
   if (!numVideos && !numAudios) {
     RejectAppend(NS_ERROR_FAILURE, __func__);
--- a/dom/media/platforms/wrappers/FuzzingWrapper.cpp
+++ b/dom/media/platforms/wrappers/FuzzingWrapper.cpp
@@ -120,17 +120,17 @@ DecoderCallbackFuzzingWrapper::SetDontDe
   mDontDelayInputExhausted = aDontDelayInputExhausted;
 }
 
 void
 DecoderCallbackFuzzingWrapper::Output(MediaData* aData)
 {
   if (!mTaskQueue->IsCurrentThreadIn()) {
     nsCOMPtr<nsIRunnable> task =
-      NewRunnableMethod<StorensRefPtrPassByPtr<MediaData>>(
+      NewRunnableMethod<StoreRefPtrPassByPtr<MediaData>>(
         this, &DecoderCallbackFuzzingWrapper::Output, aData);
     mTaskQueue->Dispatch(task.forget());
     return;
   }
   CFW_LOGV("aData.mTime=%lld", aData->mTime);
   MOZ_ASSERT(mCallback);
   if (mFrameOutputMinimumInterval) {
     if (!mPreviousOutput.IsNull()) {
--- a/dom/media/test/manifest.js
+++ b/dom/media/test/manifest.js
@@ -533,25 +533,19 @@ var gErrorTests = [
   { name:"bug504843.ogv", type:"video/ogg" },
   { name:"bug501279.ogg", type:"audio/ogg" },
   { name:"bug580982.webm", type:"video/webm" },
   { name:"bug603918.webm", type:"video/webm" },
   { name:"bug604067.webm", type:"video/webm" },
   { name:"bogus.duh", type:"bogus/duh" }
 ];
 
-function IsWindowsVistaOrLater() {
-  var re = /Windows NT (\d+.\d)/;
-  var winver = manifestNavigator().userAgent.match(re);
-  return winver && winver.length == 2 && parseFloat(winver[1]) >= 6.0;
-}
-
 // Windows' H.264 decoder cannot handle H.264 streams with resolution
 // less than 48x48 pixels. We refuse to play and error on such streams.
-if (IsWindowsVistaOrLater() &&
+if (manifestNavigator().userAgent.includes("Windows") &&
     manifestVideo().canPlayType('video/mp4; codecs="avc1.42E01E"')) {
   gErrorTests = gErrorTests.concat({name: "red-46x48.mp4", type:"video/mp4"},
                                    {name: "red-48x46.mp4", type:"video/mp4"});
 }
 
 // These are files that have nontrivial duration and are useful for seeking within.
 var gSeekTests = [
   { name:"r11025_s16_c1.wav", type:"audio/x-wav", duration:1.0 },
--- a/dom/media/test/test_aspectratio_mp4.html
+++ b/dom/media/test/test_aspectratio_mp4.html
@@ -10,22 +10,16 @@ https://bugzilla.mozilla.org/show_bug.cg
   <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
 </head>
 <body>
 <a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=975978">Mozilla Bug 975978</a>
 
 <pre id="test">
 <script class="testbody" type="text/javascript">
 
-function IsWindows7OrLater() {
-  var re = /Windows NT (\d.\d)/;
-  var winver = navigator.userAgent.match(re);
-  return winver && winver.length == 2 && parseFloat(winver[1]) >= 6.1;
-}
-
 SimpleTest.waitForExplicitFinish();
 
 // MP4 video with display size is difference to decode frame size.
 // The display size is recorded in TrackHeaderBox 'tkhd' of this mp4 video.
 var resource =
   { name:"pixel_aspect_ratio.mp4", type:"video/mp4", width:525, height:288 };
 
 var v = document.createElement("video");
--- a/dom/media/test/test_can_play_type_mpeg.html
+++ b/dom/media/test/test_can_play_type_mpeg.html
@@ -101,22 +101,16 @@ function check_mp3(v, enabled) {
 
   check("audio/mpeg; codecs=\"mp3\"", "probably");
   check("audio/mpeg; codecs=mp3", "probably");
 
   check("audio/mp3; codecs=\"mp3\"", "probably");
   check("audio/mp3; codecs=mp3", "probably");
 }
 
-function IsWindowsVistaOrLater() {
-  var re = /Windows NT (\d+\.\d)/;
-  var winver = navigator.userAgent.match(re);
-  return winver && winver.length == 2 && parseFloat(winver[1]) >= 6.0;
-}
-
 function IsMacOSSnowLeopardOrLater() {
   var re = /Mac OS X (\d+)\.(\d+)/;
   var ver = navigator.userAgent.match(re);
   if (!ver || ver.length != 3) {
     return false;
   }
   var major = ver[1] | 0;
   var minor = ver[2] | 0;
@@ -138,26 +132,26 @@ function getPref(name) {
 function IsSupportedAndroid() {
   return getAndroidVersion() >= 14;
 }
 
 function IsJellyBeanOrLater() {
   return getAndroidVersion() >= 16;
 }
 
-var haveMp4 = (getPref("media.wmf.enabled") && IsWindowsVistaOrLater()) ||
+var haveMp4 = getPref("media.wmf.enabled") ||
               IsMacOSSnowLeopardOrLater() ||
               (IsSupportedAndroid() &&
                (IsJellyBeanOrLater() || getPref("media.plugins.enabled"))) ||
               (IsLinux() && getPref("media.ffmpeg.enabled"));
 
 check_mp4(document.getElementById('v'), haveMp4);
 
 var haveMp3 = getPref("media.directshow.enabled") ||
-              (getPref("media.wmf.enabled") && IsWindowsVistaOrLater()) ||
+              getPref("media.wmf.enabled") ||
               (IsLinux() && getPref("media.ffmpeg.enabled")) ||
               (IsSupportedAndroid() &&
                ((IsJellyBeanOrLater() && getPref("media.android-media-codec.enabled")) ||
                 getPref("media.plugins.enabled"))) ||
               getPref("media.apple.mp3.enabled");
 check_mp3(document.getElementById('v'), haveMp3);
 
 mediaTestCleanup();
--- a/dom/media/tests/mochitest/pc.js
+++ b/dom/media/tests/mochitest/pc.js
@@ -1418,24 +1418,24 @@ PeerConnectionWrapper.prototype = {
    * @returns {Promise}
    *        A promise that resolves when media is flowing.
    */
   waitForRtpFlow(track) {
     var hasFlow = (stats, retries) => {
       info("Checking for stats in " + JSON.stringify(stats) + " for " + track.kind
         + " track " + track.id + ", retry number " + retries);
       var rtp = stats.get([...Object.keys(stats)].find(key =>
-        !stats.get(key).isRemote && stats.get(key).type.endsWith("boundrtp")));
+        !stats.get(key).isRemote && stats.get(key).type.endsWith("bound-rtp")));
       if (!rtp) {
 
         return false;
       }
       info("Should have RTP stats for track " + track.id);
       info("RTP stats: "+JSON.stringify(rtp));
-      var nrPackets = rtp[rtp.type == "outboundrtp" ? "packetsSent"
+      var nrPackets = rtp[rtp.type == "outbound-rtp" ? "packetsSent"
                                                     : "packetsReceived"];
       info("Track " + track.id + " has " + nrPackets + " " +
            rtp.type + " RTP packets.");
       return nrPackets > 0;
     };
 
     // Time between stats checks
     var retryInterval = 500;
@@ -1543,16 +1543,38 @@ PeerConnectionWrapper.prototype = {
         ok(true, "input and output audio data matches");
         inputElem.ontimeupdate = null;
         resolve();
       }
     });
   },
 
   /**
+   * Get stats from the "legacy" getStats callback interface
+   */
+  getStatsLegacy : function(selector, onSuccess, onFail) {
+    let wrapper = stats => {
+      info(this + ": Got legacy stats: " + JSON.stringify(stats));
+      onSuccess(stats);
+    };
+    return this._pc.getStats(selector, wrapper, onFail);
+  },
+
+  /**
+   * Check that the stats returned from the "legacy" getStats callback
+   * interface have unhyphenated names.
+   */
+  checkLegacyStatTypeNames: function(stats) {
+    let types = [];
+    stats.forEach(stat => types.push(stat.type));
+    ok(types.filter(type => type.includes("-")).length == 0,
+       "legacy getStats API is not returning stats with hyphenated types.");
+  },
+
+  /**
    * Check that stats are present by checking for known stats.
    */
   getStats : function(selector) {
     return this._pc.getStats(selector).then(stats => {
       info(this + ": Got stats: " + JSON.stringify(stats));
       this._last_stats = stats;
       return stats;
     });
@@ -1596,38 +1618,38 @@ PeerConnectionWrapper.prototype = {
         }
       }
       if (res.isRemote) {
         continue;
       }
       counters[res.type] = (counters[res.type] || 0) + 1;
 
       switch (res.type) {
-        case "inboundrtp":
-        case "outboundrtp": {
+        case "inbound-rtp":
+        case "outbound-rtp": {
           // ssrc is a 32 bit number returned as a string by spec
           ok(res.ssrc.length > 0, "Ssrc has length");
           ok(res.ssrc.length < 11, "Ssrc not lengthy");
           ok(!/[^0-9]/.test(res.ssrc), "Ssrc numeric");
           ok(parseInt(res.ssrc) < Math.pow(2,32), "Ssrc within limits");
 
-          if (res.type == "outboundrtp") {
+          if (res.type == "outbound-rtp") {
             ok(res.packetsSent !== undefined, "Rtp packetsSent");
             // We assume minimum payload to be 1 byte (guess from RFC 3550)
             ok(res.bytesSent >= res.packetsSent, "Rtp bytesSent");
           } else {
             ok(res.packetsReceived !== undefined, "Rtp packetsReceived");
             ok(res.bytesReceived >= res.packetsReceived, "Rtp bytesReceived");
           }
           if (res.remoteId) {
-            var rem = stats[res.remoteId];
+            var rem = stats.get(res.remoteId);
             ok(rem.isRemote, "Remote is rtcp");
             ok(rem.remoteId == res.id, "Remote backlink match");
-            if(res.type == "outboundrtp") {
-              ok(rem.type == "inboundrtp", "Rtcp is inbound");
+            if(res.type == "outbound-rtp") {
+              ok(rem.type == "inbound-rtp", "Rtcp is inbound");
               ok(rem.packetsReceived !== undefined, "Rtcp packetsReceived");
               ok(rem.packetsLost !== undefined, "Rtcp packetsLost");
               ok(rem.bytesReceived >= rem.packetsReceived, "Rtcp bytesReceived");
 	       if (false) { // Bug 1325430 if (!this.disableRtpCountChecking) {
 	       // no guarantee which one is newer!
 	       // Note: this must change when we add a timestamp field to remote RTCP reports
 	       // and make rem.timestamp be the reception time
 		if (res.timestamp >= rem.timestamp) {
@@ -1639,77 +1661,85 @@ PeerConnectionWrapper.prototype = {
 		// Else we may have received more than outdated Rtcp packetsSent
                 ok(rem.bytesReceived <= res.bytesSent, "No more than sent bytes");
               }
               ok(rem.jitter !== undefined, "Rtcp jitter");
               ok(rem.mozRtt !== undefined, "Rtcp rtt");
               ok(rem.mozRtt >= 0, "Rtcp rtt " + rem.mozRtt + " >= 0");
               ok(rem.mozRtt < 60000, "Rtcp rtt " + rem.mozRtt + " < 1 min");
             } else {
-              ok(rem.type == "outboundrtp", "Rtcp is outbound");
+              ok(rem.type == "outbound-rtp", "Rtcp is outbound");
               ok(rem.packetsSent !== undefined, "Rtcp packetsSent");
               // We may have received more than outdated Rtcp packetsSent
               ok(rem.bytesSent >= rem.packetsSent, "Rtcp bytesSent");
             }
             ok(rem.ssrc == res.ssrc, "Remote ssrc match");
           } else {
             info("No rtcp info received yet");
           }
         }
         break;
       }
     }
 
+    var legacyToSpecMapping = {
+      'inboundrtp':'inbound-rtp',
+      'outboundrtp':'outbound-rtp',
+      'candidatepair':'candidate-pair',
+      'localcandidate':'local-candidate',
+      'remotecandidate':'remote-candidate'
+    };
     // Use legacy way of enumerating stats
     var counters2 = {};
     for (let key in stats) {
       if (!stats.hasOwnProperty(key)) {
         continue;
       }
       var res = stats[key];
+      var type = legacyToSpecMapping[res.type] || res.type;
       if (!res.isRemote) {
-        counters2[res.type] = (counters2[res.type] || 0) + 1;
+        counters2[type] = (counters2[type] || 0) + 1;
       }
     }
     is(JSON.stringify(counters), JSON.stringify(counters2),
        "Spec and legacy variant of RTCStatsReport enumeration agree");
     var nin = Object.keys(this.expectedRemoteTrackInfoById).length;
     var nout = Object.keys(this.expectedLocalTrackInfoById).length;
     var ndata = this.dataChannels.length;
 
-    // TODO(Bug 957145): Restore stronger inboundrtp test once Bug 948249 is fixed
-    //is((counters["inboundrtp"] || 0), nin, "Have " + nin + " inboundrtp stat(s)");
-    ok((counters.inboundrtp || 0) >= nin, "Have at least " + nin + " inboundrtp stat(s) *");
+    // TODO(Bug 957145): Restore stronger inbound-rtp test once Bug 948249 is fixed
+    //is((counters["inbound-rtp"] || 0), nin, "Have " + nin + " inbound-rtp stat(s)");
+    ok((counters["inbound-rtp"] || 0) >= nin, "Have at least " + nin + " inbound-rtp stat(s) *");
 
-    is(counters.outboundrtp || 0, nout, "Have " + nout + " outboundrtp stat(s)");
+    is(counters["outbound-rtp"] || 0, nout, "Have " + nout + " outbound-rtp stat(s)");
 
-    var numLocalCandidates  = counters.localcandidate || 0;
-    var numRemoteCandidates = counters.remotecandidate || 0;
+    var numLocalCandidates  = counters["local-candidate"] || 0;
+    var numRemoteCandidates = counters["remote-candidate"] || 0;
     // If there are no tracks, there will be no stats either.
     if (nin + nout + ndata > 0) {
-      ok(numLocalCandidates, "Have localcandidate stat(s)");
-      ok(numRemoteCandidates, "Have remotecandidate stat(s)");
+      ok(numLocalCandidates, "Have local-candidate stat(s)");
+      ok(numRemoteCandidates, "Have remote-candidate stat(s)");
     } else {
-      is(numLocalCandidates, 0, "Have no localcandidate stats");
-      is(numRemoteCandidates, 0, "Have no remotecandidate stats");
+      is(numLocalCandidates, 0, "Have no local-candidate stats");
+      is(numRemoteCandidates, 0, "Have no remote-candidate stats");
     }
   },
 
   /**
    * Compares the Ice server configured for this PeerConnectionWrapper
    * with the ICE candidates received in the RTCP stats.
    *
    * @param {object} stats
    *        The stats to be verified for relayed vs. direct connection.
    */
   checkStatsIceConnectionType : function(stats, expectedLocalCandidateType) {
     let lId;
     let rId;
     for (let stat of stats.values()) {
-      if (stat.type == "candidatepair" && stat.selected) {
+      if (stat.type == "candidate-pair" && stat.selected) {
         lId = stat.localCandidateId;
         rId = stat.remoteCandidateId;
         break;
       }
     }
     isnot(lId, undefined, "Got local candidate ID " + lId + " for selected pair");
     isnot(rId, undefined, "Got remote candidate ID " + rId + " for selected pair");
     let lCand = stats.get(lId);
@@ -1750,18 +1780,18 @@ PeerConnectionWrapper.prototype = {
    * @param {object} counters
    *        The counters for media and data tracks based on constraints
    * @param {object} testOptions
    *        The test options object from the PeerConnectionTest
    */
   checkStatsIceConnections : function(stats,
       offerConstraintsList, offerOptions, testOptions) {
     var numIceConnections = 0;
-    Object.keys(stats).forEach(key => {
-      if ((stats[key].type === "candidatepair") && stats[key].selected) {
+    stats.forEach(stat => {
+      if ((stat.type === "candidate-pair") && stat.selected) {
         numIceConnections += 1;
       }
     });
     info("ICE connections according to stats: " + numIceConnections);
     isnot(numIceConnections, 0, "Number of ICE connections according to stats is not zero");
     if (testOptions.bundle) {
       if (testOptions.rtcpmux) {
         is(numIceConnections, 1, "stats reports exactly 1 ICE connection");
--- a/dom/media/tests/mochitest/templates.js
+++ b/dom/media/tests/mochitest/templates.js
@@ -85,22 +85,22 @@ function waitForAnIceCandidate(pc) {
 
 function checkTrackStats(pc, rtpSenderOrReceiver, outbound) {
   var track = rtpSenderOrReceiver.track;
   var audio = (track.kind == "audio");
   var msg = pc + " stats " + (outbound ? "outbound " : "inbound ") +
       (audio ? "audio" : "video") + " rtp track id " + track.id;
   return pc.getStats(track).then(stats => {
     ok(pc.hasStat(stats, {
-      type: outbound ? "outboundrtp" : "inboundrtp",
+      type: outbound ? "outbound-rtp" : "inbound-rtp",
       isRemote: false,
       mediaType: audio ? "audio" : "video"
     }), msg + " - found expected stats");
     ok(!pc.hasStat(stats, {
-      type: outbound ? "inboundrtp" : "outboundrtp",
+      type: outbound ? "inbound-rtp" : "outbound-rtp",
       isRemote: false
     }), msg + " - did not find extra stats with wrong direction");
     ok(!pc.hasStat(stats, {
       mediaType: audio ? "video" : "audio"
     }), msg + " - did not find extra stats with wrong media type");
   });
 }
 
@@ -403,16 +403,18 @@ var commandsPeerConnectionOfferAnswer = 
 
   function PC_REMOTE_WAIT_FOR_MEDIA_FLOW(test) {
     return test.pcRemote.waitForMediaFlow();
   },
 
   function PC_LOCAL_CHECK_STATS(test) {
     return test.pcLocal.getStats().then(stats => {
       test.pcLocal.checkStats(stats, test.testOptions.steeplechase);
+    }).then(() => {
+      test.pcLocal.getStatsLegacy(null, test.pcLocal.checkLegacyStatTypeNames, e => {});
     });
   },
 
   function PC_REMOTE_CHECK_STATS(test) {
     return test.pcRemote.getStats().then(stats => {
       test.pcRemote.checkStats(stats, test.testOptions.steeplechase);
     });
   },
--- a/dom/media/webaudio/AudioNode.cpp
+++ b/dom/media/webaudio/AudioNode.cpp
@@ -201,17 +201,17 @@ AudioNode::Connect(AudioNode& aDestinati
 {
   if (aOutput >= NumberOfOutputs() ||
       aInput >= aDestination.NumberOfInputs()) {
     aRv.Throw(NS_ERROR_DOM_INDEX_SIZE_ERR);
     return nullptr;
   }
 
   if (Context() != aDestination.Context()) {
-    aRv.Throw(NS_ERROR_DOM_SYNTAX_ERR);
+    aRv.Throw(NS_ERROR_DOM_INVALID_ACCESS_ERR);
     return nullptr;
   }
 
   if (FindIndexOfNodeWithPorts(aDestination.mInputNodes,
                                this, aInput, aOutput) !=
       nsTArray<AudioNode::InputNode>::NoIndex) {
     // connection already exists.
     return &aDestination;
@@ -252,17 +252,17 @@ AudioNode::Connect(AudioParam& aDestinat
                    ErrorResult& aRv)
 {
   if (aOutput >= NumberOfOutputs()) {
     aRv.Throw(NS_ERROR_DOM_INDEX_SIZE_ERR);
     return;
   }
 
   if (Context() != aDestination.GetParentObject()) {
-    aRv.Throw(NS_ERROR_DOM_SYNTAX_ERR);
+    aRv.Throw(NS_ERROR_DOM_INVALID_ACCESS_ERR);
     return;
   }
 
   if (FindIndexOfNodeWithPorts(aDestination.InputNodes(),
                                this, INVALID_PORT, aOutput) !=
       nsTArray<AudioNode::InputNode>::NoIndex) {
     // connection already exists.
     return;
--- a/dom/media/webaudio/test/test_badConnect.html
+++ b/dom/media/webaudio/test/test_badConnect.html
@@ -7,36 +7,40 @@
 </head>
 <body>
 <pre id="test">
 <script src="webaudio.js" type="text/javascript"></script>
 <script class="testbody" type="text/javascript">
 
 SimpleTest.waitForExplicitFinish();
 addLoadEvent(function() {
-  var context1 = new AudioContext();
-  var context2 = new AudioContext();
+  var context1 = new OfflineAudioContext(1, 128, 44100);
+  var context2 = new OfflineAudioContext(1, 128, 44100);
 
   var destination1 = context1.destination;
   var destination2 = context2.destination;
+  var gain1 = new GainNode(context2);
 
   isnot(destination1, destination2, "Destination nodes should not be the same");
   isnot(destination1.context, destination2.context, "Destination nodes should not have the same context");
 
   var source1 = context1.createBufferSource();
 
   expectException(function() {
     source1.connect(destination1, 1);
   }, DOMException.INDEX_SIZE_ERR);
   expectException(function() {
     source1.connect(destination1, 0, 1);
   }, DOMException.INDEX_SIZE_ERR);
   expectException(function() {
     source1.connect(destination2);
-  }, DOMException.SYNTAX_ERR);
+  }, DOMException.INVALID_ACCESS_ERR);
+  expectException(function() {
+    source1.connect(gain1.gain);
+  }, DOMException.INVALID_ACCESS_ERR);
 
   source1.connect(destination1);
 
   expectException(function() {
     source1.disconnect(1);
   }, DOMException.INDEX_SIZE_ERR);
 
   SimpleTest.finish();
--- a/dom/media/webrtc/MediaEngineWebRTC.cpp
+++ b/dom/media/webrtc/MediaEngineWebRTC.cpp
@@ -6,19 +6,16 @@
 
 #include "nsIPrefService.h"
 #include "nsIPrefBranch.h"
 
 #include "CSFLog.h"
 #include "prenv.h"
 
 #include "mozilla/Logging.h"
-#ifdef XP_WIN
-#include "mozilla/WindowsVersion.h"
-#endif
 
 static mozilla::LazyLogModule sGetUserMediaLog("GetUserMedia");
 
 #include "MediaEngineWebRTC.h"
 #include "ImageContainer.h"
 #include "nsIComponentRegistrar.h"
 #include "MediaEngineTabVideoSource.h"
 #include "MediaEngineRemoteVideoSource.h"
@@ -264,21 +261,17 @@ MediaEngineWebRTC::EnumerateVideoDevices
   if (mHasTabVideoSource || dom::MediaSourceEnum::Browser == aMediaSource) {
     aVSources->AppendElement(new MediaEngineTabVideoSource());
   }
 }
 
 bool
 MediaEngineWebRTC::SupportsDuplex()
 {
-#ifndef XP_WIN
   return mFullDuplex;
-#else
-  return IsVistaOrLater() && mFullDuplex;
-#endif
 }
 
 void
 MediaEngineWebRTC::EnumerateAudioDevices(dom::MediaSourceEnum aMediaSource,
                                          nsTArray<RefPtr<MediaEngineAudioSource> >* aASources)
 {
   ScopedCustomReleasePtr<webrtc::VoEBase> ptrVoEBase;
   // We spawn threads to handle gUM runnables, so we must protect the member vars
--- a/dom/media/webrtc/WebrtcGlobal.h
+++ b/dom/media/webrtc/WebrtcGlobal.h
@@ -67,17 +67,17 @@ struct ParamTraits<mozilla::dom::Sequenc
     return ReadParam(aMsg, aIter, dynamic_cast<FallibleTArray<T>*>(aResult));
   }
 };
 
 template<>
 struct ParamTraits<mozilla::dom::RTCStatsType> :
   public ContiguousEnumSerializer<
     mozilla::dom::RTCStatsType,
-    mozilla::dom::RTCStatsType::Inboundrtp,
+    mozilla::dom::RTCStatsType::Inbound_rtp,
     mozilla::dom::RTCStatsType::EndGuard_>
 {};
 
 template<>
 struct ParamTraits<mozilla::dom::RTCStatsIceCandidatePairState> :
   public ContiguousEnumSerializer<
     mozilla::dom::RTCStatsIceCandidatePairState,
     mozilla::dom::RTCStatsIceCandidatePairState::Frozen,
--- a/dom/network/PTCPSocket.ipdl
+++ b/dom/network/PTCPSocket.ipdl
@@ -40,17 +40,18 @@ parent:
   // is expanded to |useSSL| (from TCPOptions.useSecureTransport) and
   // |binaryType| (from TCPOption.binaryType).
   async Open(nsString host, uint16_t port, bool useSSL, bool useArrayBuffers);
 
   // Ask parent to open a socket and bind the newly-opened socket to a local
   // address specified in |localAddr| and |localPort|.
   async OpenBind(nsCString host, uint16_t port,
                  nsCString localAddr, uint16_t localPort,
-                 bool useSSL, bool aUseArrayBuffers, nsCString aFilter);
+                 bool useSSL, bool reuseAddrPort,
+                 bool aUseArrayBuffers, nsCString aFilter);
 
   // When child's send() is called, this message requrests parent to send
   // data and update it's trackingNumber.
   async Data(SendableData data, uint32_t trackingNumber);
 
   // Forward calling to child's upgradeToSecure() method to parent.
   async StartTLS();
 
--- a/dom/network/TCPSocketChild.cpp
+++ b/dom/network/TCPSocketChild.cpp
@@ -104,26 +104,27 @@ TCPSocketChild::SendOpen(nsITCPSocketCal
   MOZ_ASSERT(mFilterName.IsEmpty()); // Currently nobody should use this
   PTCPSocketChild::SendOpen(mHost, mPort, aUseSSL, aUseArrayBuffers);
 }
 
 void
 TCPSocketChild::SendWindowlessOpenBind(nsITCPSocketCallback* aSocket,
                                        const nsACString& aRemoteHost, uint16_t aRemotePort,
                                        const nsACString& aLocalHost, uint16_t aLocalPort,
-                                       bool aUseSSL)
+                                       bool aUseSSL, bool aReuseAddrPort)
 {
   mSocket = aSocket;
   AddIPDLReference();
   gNeckoChild->SendPTCPSocketConstructor(this,
                                          NS_ConvertUTF8toUTF16(aRemoteHost),
                                          aRemotePort);
   PTCPSocketChild::SendOpenBind(nsCString(aRemoteHost), aRemotePort,
                                 nsCString(aLocalHost), aLocalPort,
-                                aUseSSL, true, mFilterName);
+                                aUseSSL, aReuseAddrPort,
+                                true, mFilterName);
 }
 
 void
 TCPSocketChildBase::ReleaseIPDLReference()
 {
   MOZ_ASSERT(mIPCOpen);
   mIPCOpen = false;
   mSocket = nullptr;
--- a/dom/network/TCPSocketChild.h
+++ b/dom/network/TCPSocketChild.h
@@ -51,17 +51,17 @@ public:
 
   TCPSocketChild(const nsAString& aHost, const uint16_t& aPort);
   ~TCPSocketChild();
 
   void SendOpen(nsITCPSocketCallback* aSocket, bool aUseSSL, bool aUseArrayBuffers);
   void SendWindowlessOpenBind(nsITCPSocketCallback* aSocket,
                               const nsACString& aRemoteHost, uint16_t aRemotePort,
                               const nsACString& aLocalHost, uint16_t aLocalPort,
-                              bool aUseSSL);
+                              bool aUseSSL, bool aUseRealtimeOptions);
   NS_IMETHOD SendSendArray(nsTArray<uint8_t>& aArray,
                            uint32_t aTrackingNumber);
   void SendSend(const nsACString& aData, uint32_t aTrackingNumber);
   nsresult SendSend(const ArrayBuffer& aData,
                     uint32_t aByteOffset,
                     uint32_t aByteLength,
                     uint32_t aTrackingNumber);
   void SendSendArray(nsTArray<uint8_t>* arr,
--- a/dom/network/TCPSocketParent.cpp
+++ b/dom/network/TCPSocketParent.cpp
@@ -129,16 +129,17 @@ TCPSocketParent::RecvOpen(const nsString
 }
 
 mozilla::ipc::IPCResult
 TCPSocketParent::RecvOpenBind(const nsCString& aRemoteHost,
                               const uint16_t& aRemotePort,
                               const nsCString& aLocalAddr,
                               const uint16_t& aLocalPort,
                               const bool&     aUseSSL,
+                              const bool&     aReuseAddrPort,
                               const bool&     aUseArrayBuffers,
                               const nsCString& aFilter)
 {
   nsresult rv;
   nsCOMPtr<nsISocketTransportService> sts =
     do_GetService("@mozilla.org/network/socket-transport-service;1", &rv);
   if (NS_FAILED(rv)) {
     FireInteralError(this, __LINE__);
@@ -149,16 +150,20 @@ TCPSocketParent::RecvOpenBind(const nsCS
   rv = sts->CreateTransport(nullptr, 0,
                             aRemoteHost, aRemotePort,
                             nullptr, getter_AddRefs(socketTransport));
   if (NS_FAILED(rv)) {
     FireInteralError(this, __LINE__);
     return IPC_OK();
   }
 
+  // in most cases aReuseAddrPort is false, but ICE TCP needs
+  // sockets options set that allow addr/port reuse
+  socketTransport->SetReuseAddrPort(aReuseAddrPort);
+
   PRNetAddr prAddr;
   if (PR_SUCCESS != PR_InitializeNetAddr(PR_IpAddrAny, aLocalPort, &prAddr)) {
     FireInteralError(this, __LINE__);
     return IPC_OK();
   }
   if (PR_SUCCESS != PR_StringToNetAddr(aLocalAddr.BeginReading(), &prAddr)) {
     FireInteralError(this, __LINE__);
     return IPC_OK();
--- a/dom/network/TCPSocketParent.h
+++ b/dom/network/TCPSocketParent.h
@@ -50,16 +50,17 @@ public:
   virtual mozilla::ipc::IPCResult RecvOpen(const nsString& aHost, const uint16_t& aPort,
                                            const bool& useSSL, const bool& aUseArrayBuffers) override;
 
   virtual mozilla::ipc::IPCResult RecvOpenBind(const nsCString& aRemoteHost,
                                                const uint16_t& aRemotePort,
                                                const nsCString& aLocalAddr,
                                                const uint16_t& aLocalPort,
                                                const bool&     aUseSSL,
+                                               const bool&     aReuseAddrPort,
                                                const bool& aUseArrayBuffers,
                                                const nsCString& aFilter) override;
 
   virtual mozilla::ipc::IPCResult RecvStartTLS() override;
   virtual mozilla::ipc::IPCResult RecvSuspend() override;
   virtual mozilla::ipc::IPCResult RecvResume() override;
   virtual mozilla::ipc::IPCResult RecvClose() override;
   virtual mozilla::ipc::IPCResult RecvData(const SendableData& aData,
--- a/dom/tests/mochitest/general/test_interfaces.html
+++ b/dom/tests/mochitest/general/test_interfaces.html
@@ -640,29 +640,29 @@ var interfaceNamesInGlobalScope =
     "MediaDeviceInfo",
 // IMPORTANT: Do not change this list without review from a DOM peer!
     "MediaDevices",
 // IMPORTANT: Do not change this list without review from a DOM peer!
     "MediaElementAudioSourceNode",
 // IMPORTANT: Do not change this list without review from a DOM peer!
     "MediaError",
 // IMPORTANT: Do not change this list without review from a DOM peer!
-    {name: "MediaKeyError", android: false},
+    "MediaKeyError",
 // IMPORTANT: Do not change this list without review from a DOM peer!
-    {name: "MediaEncryptedEvent", android: false},
+    "MediaEncryptedEvent",
 // IMPORTANT: Do not change this list without review from a DOM peer!
-    {name: "MediaKeys", android: false},
+    "MediaKeys",
 // IMPORTANT: Do not change this list without review from a DOM peer!
-    {name: "MediaKeySession", android: false},
+    "MediaKeySession",
 // IMPORTANT: Do not change this list without review from a DOM peer!
-    {name: "MediaKeySystemAccess", android: false},
+    "MediaKeySystemAccess",
 // IMPORTANT: Do not change this list without review from a DOM peer!
-    {name: "MediaKeyMessageEvent", android: false},
+    "MediaKeyMessageEvent",
 // IMPORTANT: Do not change this list without review from a DOM peer!
-    {name: "MediaKeyStatusMap", android: false},
+    "MediaKeyStatusMap",
 // IMPORTANT: Do not change this list without review from a DOM peer!
     "MediaList",
 // IMPORTANT: Do not change this list without review from a DOM peer!
     "MediaQueryList",
 // IMPORTANT: Do not change this list without review from a DOM peer!
     "MediaRecorder",
 // IMPORTANT: Do not change this list without review from a DOM peer!
     "MediaSource",
--- a/dom/webidl/RTCStatsReport.webidl
+++ b/dom/webidl/RTCStatsReport.webidl
@@ -4,24 +4,24 @@
  * You can obtain one at http://mozilla.org/MPL/2.0/.
  *
  * The origin of this IDL file is
  * http://dev.w3.org/2011/webrtc/editor/webrtc.html#rtcstatsreport-object
  * http://www.w3.org/2011/04/webrtc/wiki/Stats
  */
 
 enum RTCStatsType {
-  "inboundrtp",
-  "outboundrtp",
+  "inbound-rtp",
+  "outbound-rtp",
   "session",
   "track",
   "transport",
-  "candidatepair",
-  "localcandidate",
-  "remotecandidate"
+  "candidate-pair",
+  "local-candidate",
+  "remote-candidate"
 };
 
 dictionary RTCStats {
   DOMHighResTimeStamp timestamp;
   RTCStatsType type;
   DOMString id;
 };
 
--- a/dom/xml/nsXMLContentSink.cpp
+++ b/dom/xml/nsXMLContentSink.cpp
@@ -1054,16 +1054,19 @@ nsXMLContentSink::HandleEndElement(const
   // elements do not get pushed on the stack, the template
   // element content is pushed instead.
   bool isTemplateElement = debugTagAtom == nsGkAtoms::_template &&
                            debugNameSpaceID == kNameSpaceID_XHTML;
   NS_ASSERTION(content->NodeInfo()->Equals(debugTagAtom, debugNameSpaceID) ||
                (debugNameSpaceID == kNameSpaceID_MathML &&
                 content->NodeInfo()->NamespaceID() == kNameSpaceID_disabled_MathML &&
                 content->NodeInfo()->Equals(debugTagAtom)) ||
+               (debugNameSpaceID == kNameSpaceID_SVG &&
+                content->NodeInfo()->NamespaceID() == kNameSpaceID_disabled_SVG &&
+                content->NodeInfo()->Equals(debugTagAtom)) ||
                isTemplateElement, "Wrong element being closed");
 #endif
 
   result = CloseElement(content);
 
   if (mCurrentHead == content) {
     mCurrentHead = nullptr;
   }
--- a/dom/xul/nsXULElement.cpp
+++ b/dom/xul/nsXULElement.cpp
@@ -376,18 +376,17 @@ nsXULElement::Clone(mozilla::dom::NodeIn
     for (uint32_t i = 0; i < count; ++i) {
         const nsAttrName* originalName = mAttrsAndChildren.AttrNameAt(i);
         const nsAttrValue* originalValue = mAttrsAndChildren.AttrAt(i);
         nsAttrValue attrValue;
 
         // Style rules need to be cloned.
         if (originalValue->Type() == nsAttrValue::eCSSDeclaration) {
             DeclarationBlock* decl = originalValue->GetCSSDeclarationValue();
-            RefPtr<css::Declaration>
-              declClone = new css::Declaration(*decl->AsGecko());
+            RefPtr<DeclarationBlock> declClone = decl->Clone();
 
             nsString stringValue;
             originalValue->ToString(stringValue);
 
             attrValue.SetTo(declClone.forget(), &stringValue);
         } else {
             attrValue.SetTo(*originalValue);
         }
@@ -1913,18 +1912,17 @@ nsXULElement::MakeHeavyweight(nsXULProto
     nsresult rv;
     for (i = 0; i < aPrototype->mNumAttributes; ++i) {
         nsXULPrototypeAttribute* protoattr = &aPrototype->mAttributes[i];
         nsAttrValue attrValue;
 
         // Style rules need to be cloned.
         if (protoattr->mValue.Type() == nsAttrValue::eCSSDeclaration) {
             DeclarationBlock* decl = protoattr->mValue.GetCSSDeclarationValue();
-            RefPtr<css::Declaration>
-              declClone = new css::Declaration(*decl->AsGecko());
+            RefPtr<DeclarationBlock> declClone = decl->Clone();
 
             nsString stringValue;
             protoattr->mValue.ToString(stringValue);
 
             attrValue.SetTo(declClone.forget(), &stringValue);
         } else {
             attrValue.SetTo(protoattr->mValue);
         }
--- a/gfx/2d/2D.h
+++ b/gfx/2d/2D.h
@@ -1554,16 +1554,18 @@ public:
 
   static already_AddRefed<ScaledFont>
     CreateScaledFontForDWriteFont(IDWriteFontFace* aFontFace,
                                   const gfxFontStyle* aStyle,
                                   Float aSize,
                                   bool aUseEmbeddedBitmap,
                                   bool aForceGDIMode);
 
+  static void UpdateSystemTextQuality();
+
 private:
   static ID2D1Device *mD2D1Device;
   static ID3D11Device *mD3D11Device;
   static IDWriteFactory *mDWriteFactory;
 #endif
 
   static DrawEventRecorder *mRecorder;
 };
--- a/gfx/2d/Factory.cpp
+++ b/gfx/2d/Factory.cpp
@@ -36,16 +36,17 @@
 #endif
 
 #ifdef WIN32
 #include "DrawTargetD2D1.h"
 #include "ScaledFontDWrite.h"
 #include "NativeFontResourceDWrite.h"
 #include <d3d10_1.h>
 #include "HelpersD2D.h"
+#include "HelpersWinFonts.h"
 #endif
 
 #include "DrawTargetDual.h"
 #include "DrawTargetTiled.h"
 #include "DrawTargetRecording.h"
 
 #include "SourceSurfaceRawData.h"
 
@@ -704,16 +705,25 @@ Factory::SupportsD2D1()
 }
 
 already_AddRefed<GlyphRenderingOptions>
 Factory::CreateDWriteGlyphRenderingOptions(IDWriteRenderingParams *aParams)
 {
   return MakeAndAddRef<GlyphRenderingOptionsDWrite>(aParams);
 }
 
+BYTE sSystemTextQuality = CLEARTYPE_QUALITY;
+void
+Factory::UpdateSystemTextQuality()
+{
+#ifdef WIN32
+  gfx::UpdateSystemTextQuality();
+#endif
+}
+
 uint64_t
 Factory::GetD2DVRAMUsageDrawTarget()
 {
   return DrawTargetD2D1::mVRAMUsageDT;
 }
 
 uint64_t
 Factory::GetD2DVRAMUsageSourceSurface()
--- a/gfx/2d/HelpersWinFonts.h
+++ b/gfx/2d/HelpersWinFonts.h
@@ -1,42 +1,55 @@
 /* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*-
  * This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 namespace mozilla {
 namespace gfx {
 
-// Cleartype can be dynamically enabled/disabled, so we have to check it
-// everytime we want to render some text.
+
+extern BYTE sSystemTextQuality;
+
 static BYTE
 GetSystemTextQuality()
 {
+  return sSystemTextQuality;
+}
+
+// Cleartype can be dynamically enabled/disabled, so we have to allow for dynamically
+// updating it.
+static void
+UpdateSystemTextQuality()
+{
   BOOL font_smoothing;
   UINT smoothing_type;
 
   if (!SystemParametersInfo(SPI_GETFONTSMOOTHING, 0, &font_smoothing, 0)) {
-    return DEFAULT_QUALITY;
+    sSystemTextQuality = DEFAULT_QUALITY;
+    return;
   }
 
   if (font_smoothing) {
       if (!SystemParametersInfo(SPI_GETFONTSMOOTHINGTYPE,
                                 0, &smoothing_type, 0)) {
-        return DEFAULT_QUALITY;
+        sSystemTextQuality = DEFAULT_QUALITY;
+        return;
       }
 
       if (smoothing_type == FE_FONTSMOOTHINGCLEARTYPE) {
-        return CLEARTYPE_QUALITY;
+        sSystemTextQuality = CLEARTYPE_QUALITY;
+        return;
       }
 
-      return ANTIALIASED_QUALITY;
+      sSystemTextQuality = ANTIALIASED_QUALITY;
+      return;
   }
 
-  return DEFAULT_QUALITY;
+  sSystemTextQuality = DEFAULT_QUALITY;
 }
 
 static AntialiasMode
 GetSystemDefaultAAMode()
 {
   AntialiasMode defaultMode = AntialiasMode::SUBPIXEL;
   if (gfxPrefs::DisableAllTextAA()) {
     return AntialiasMode::NONE;
--- a/gfx/layers/client/ClientLayerManager.cpp
+++ b/gfx/layers/client/ClientLayerManager.cpp
@@ -30,16 +30,17 @@
 #include "mozilla/dom/WindowBinding.h"  // for Overfill Callback
 #include "FrameLayerBuilder.h"          // for FrameLayerbuilder
 #ifdef MOZ_WIDGET_ANDROID
 #include "AndroidBridge.h"
 #include "LayerMetricsWrapper.h"
 #endif
 #ifdef XP_WIN
 #include "mozilla/gfx/DeviceManagerDx.h"
+#include "gfxDWriteFonts.h"
 #endif
 
 namespace mozilla {
 namespace layers {
 
 using namespace mozilla::gfx;
 
 void
@@ -284,16 +285,24 @@ ClientLayerManager::BeginTransaction()
 
 bool
 ClientLayerManager::EndTransactionInternal(DrawPaintedLayerCallback aCallback,
                                            void* aCallbackData,
                                            EndTransactionFlags)
 {
   PaintTelemetry::AutoRecord record(PaintTelemetry::Metric::Rasterization);
 
+#ifdef WIN32
+  if (aCallbackData) {
+    // Content processes don't get OnPaint called. So update here whenever we
+    // may do Thebes drawing.
+    gfxDWriteFont::UpdateClearTypeUsage();
+  }
+#endif
+
   PROFILER_LABEL("ClientLayerManager", "EndTransactionInternal",
     js::ProfileEntry::Category::GRAPHICS);
 
 #ifdef MOZ_LAYERS_HAVE_LOG
   MOZ_LAYERS_LOG(("  ----- (beginning paint)"));
   Log();
 #endif
   profiler_tracing("Paint", "Rasterize", TRACING_INTERVAL_START);
--- a/gfx/thebes/gfxDWriteFonts.cpp
+++ b/gfx/thebes/gfxDWriteFonts.cpp
@@ -118,16 +118,17 @@ gfxDWriteFont::~gfxDWriteFont()
         cairo_scaled_font_destroy(mScaledFont);
     }
     delete mMetrics;
 }
 
 void
 gfxDWriteFont::UpdateClearTypeUsage()
 {
+  Factory::UpdateSystemTextQuality();
   mUseClearType = UsingClearType();
 }
 
 UniquePtr<gfxFont>
 gfxDWriteFont::CopyWithAntialiasOption(AntialiasOption anAAOption)
 {
     auto entry = static_cast<gfxDWriteFontEntry*>(mFontEntry.get());
     return MakeUnique<gfxDWriteFont>(entry, &mStyle, mNeedsBold, anAAOption);
--- a/js/src/doc/Debugger/Debugger.Frame.md
+++ b/js/src/doc/Debugger/Debugger.Frame.md
@@ -106,31 +106,36 @@ its prototype:
 
     * `"eval"`: a frame running code passed to `eval`.
 
     * `"global"`: a frame running global code (JavaScript that is neither of
       the above).
 
     * `"module"`: a frame running code at the top level of a module.
 
+    * `"wasmcall"`: a frame running a WebAssembly function call.
+
     * `"debugger"`: a frame for a call to user code invoked by the debugger
       (see the `eval` method below).
 
 `implementation`
 :   A string describing which tier of the JavaScript engine this frame is
     executing in:
 
     * `"interpreter"`: a frame running in the interpreter.
 
     * `"baseline"`: a frame running in the unoptimizing, baseline JIT.
 
     * `"ion"`: a frame running in the optimizing JIT.
 
+    * `"wasm"`: a frame running in WebAssembly baseline JIT.
+
 `this`
-:   The value of `this` for this frame (a debuggee value).
+:   The value of `this` for this frame (a debuggee value). For a `wasmcall`
+    frame, this property throws a `TypeError`.
 
 `older`
 :   The next-older visible frame, in which control will resume when this
     frame completes. If there is no older frame, this is `null`.
 
 `depth`
 :   The depth of this frame, counting from oldest to youngest; the oldest
     frame has a depth of zero.
@@ -144,16 +149,17 @@ its prototype:
 :   The script being executed in this frame (a [`Debugger.Script`][script]
     instance), or `null` on frames that do not represent calls to debuggee
     code. On frames whose `callee` property is not null, this is equal to
     `callee.script`.
 
 `offset`
 :   The offset of the bytecode instruction currently being executed in
     `script`, or `undefined` if the frame's `script` property is `null`.
+    For a `wasmcall` frame, this property throws a `TypeError`.
 
 `environment`
 :   The lexical environment within which evaluation is taking place (a
     [`Debugger.Environment`][environment] instance), or `null` on frames
     that do not represent the evaluation of debuggee code, like calls
     non-debuggee functions, host functions or `"debugger"` frames.
 
 `callee`
@@ -263,19 +269,19 @@ the compartment to which the handler met
 
 The functions described below may only be called with a `this` value
 referring to a `Debugger.Frame` instance; they may not be used as
 methods of other kinds of objects.
 
 <code id="eval">eval(<i>code</i>, [<i>options</i>])</code>
 :   Evaluate <i>code</i> in the execution context of this frame, and return
     a [completion value][cv] describing how it completed. <i>Code</i> is a
-    string. If this frame's `environment` property is `null`, throw a
-    `TypeError`. All extant handler methods, breakpoints, and
-    so on remain active during the call. This function follows the
+    string. If this frame's `environment` property is `null` or `type` property
+    is `wasmcall`, throw a `TypeError`. All extant handler methods, breakpoints,
+    and so on remain active during the call. This function follows the
     [invocation function conventions][inv fr].
 
     <i>Code</i> is interpreted as strict mode code when it contains a Use
     Strict Directive, or the code executing in this frame is strict mode
     code.
 
     If <i>code</i> is not strict mode code, then variable declarations in
     <i>code</i> affect the environment of this frame. (In the terms used by
@@ -321,8 +327,10 @@ methods of other kinds of objects.
     of the execution context that this frame represents, and the
     <i>bindings</i> appear in a new declarative environment, which is the
     eval code's `LexicalEnvironment`.) If implementation restrictions
     prevent SpiderMonkey from extending this frame's environment as
     requested, this call throws an `Error` exception.
 
     The <i>options</i> argument is as for
     [`Debugger.Frame.prototype.eval`][fr eval], described above.
+    Also like `eval`, if this frame's `environment` property is `null` or
+    `type` property is `wasmcall`, throw a `TypeError`.
--- a/js/src/frontend/BytecodeEmitter.cpp
+++ b/js/src/frontend/BytecodeEmitter.cpp
@@ -731,16 +731,19 @@ BytecodeEmitter::EmitterScope::searchInE
             return NameLocation::Dynamic();
 
           case ScopeKind::Global:
             return NameLocation::Global(BindingKind::Var);
 
           case ScopeKind::With:
           case ScopeKind::NonSyntactic:
             return NameLocation::Dynamic();
+
+          case ScopeKind::WasmFunction:
+            MOZ_CRASH("No direct eval inside wasm functions");
         }
 
         if (hasEnv) {
             MOZ_ASSERT(hops < ENVCOORD_HOPS_LIMIT - 1);
             hops++;
         }
     }
 
@@ -1428,16 +1431,19 @@ BytecodeEmitter::EmitterScope::leave(Byt
       case ScopeKind::NamedLambda:
       case ScopeKind::StrictNamedLambda:
       case ScopeKind::Eval:
       case ScopeKind::StrictEval:
       case ScopeKind::Global:
       case ScopeKind::NonSyntactic:
       case ScopeKind::Module:
         break;
+
+      case ScopeKind::WasmFunction:
+        MOZ_CRASH("No wasm function scopes in JS");
     }
 
     // Finish up the scope if we are leaving it in LIFO fashion.
     if (!nonLocal) {
         // Popping scopes due to non-local jumps generate additional scope
         // notes. See NonLocalExitControl::prepareForNonLocalJump.
         if (ScopeKindIsInBody(kind)) {
             // The extra function var scope is never popped once it's pushed,
--- a/js/src/gc/Marking.cpp
+++ b/js/src/gc/Marking.cpp
@@ -1259,16 +1259,22 @@ EvalScope::Data::trace(JSTracer* trc)
 }
 void
 ModuleScope::Data::trace(JSTracer* trc)
 {
     TraceNullableEdge(trc, &module, "scope module");
     TraceBindingNames(trc, names, length);
 }
 void
+WasmFunctionScope::Data::trace(JSTracer* trc)
+{
+    TraceNullableEdge(trc, &instance, "wasm function");
+    TraceBindingNames(trc, names, length);
+}
+void
 Scope::traceChildren(JSTracer* trc)
 {
     TraceNullableEdge(trc, &enclosing_, "scope enclosing");
     TraceNullableEdge(trc, &environmentShape_, "scope env shape");
     switch (kind_) {
       case ScopeKind::Function:
         reinterpret_cast<FunctionScope::Data*>(data_)->trace(trc);
         break;
@@ -1291,16 +1297,19 @@ Scope::traceChildren(JSTracer* trc)
       case ScopeKind::StrictEval:
         reinterpret_cast<EvalScope::Data*>(data_)->trace(trc);
         break;
       case ScopeKind::Module:
         reinterpret_cast<ModuleScope::Data*>(data_)->trace(trc);
         break;
       case ScopeKind::With:
         break;
+      case ScopeKind::WasmFunction:
+        reinterpret_cast<WasmFunctionScope::Data*>(data_)->trace(trc);
+        break;
     }
 }
 inline void
 js::GCMarker::eagerlyMarkChildren(Scope* scope)
 {
     if (scope->enclosing_)
         traverseEdge(scope, static_cast<Scope*>(scope->enclosing_));
     if (scope->environmentShape_)
@@ -1356,16 +1365,24 @@ js::GCMarker::eagerlyMarkChildren(Scope*
         traverseEdge(scope, static_cast<JSObject*>(data->module));
         names = data->names;
         length = data->length;
         break;
       }
 
       case ScopeKind::With:
         break;
+
+      case ScopeKind::WasmFunction: {
+        WasmFunctionScope::Data* data = reinterpret_cast<WasmFunctionScope::Data*>(scope->data_);
+        traverseEdge(scope, static_cast<JSObject*>(data->instance));
+        names = data->names;
+        length = data->length;
+        break;
+      }
     }
     if (scope->kind_ == ScopeKind::Function) {
         for (uint32_t i = 0; i < length; i++) {
             if (JSAtom* name = names[i].name())
                 traverseEdge(scope, static_cast<JSString*>(name));
         }
     } else {
         for (uint32_t i = 0; i < length; i++)
--- a/js/src/gc/Policy.h
+++ b/js/src/gc/Policy.h
@@ -47,16 +47,17 @@ class RegExpObject;
 class SavedFrame;
 class Scope;
 class EnvironmentObject;
 class ScriptSourceObject;
 class Shape;
 class SharedArrayBufferObject;
 class StructTypeDescr;
 class UnownedBaseShape;
+class WasmFunctionScope;
 class WasmMemoryObject;
 namespace jit {
 class JitCode;
 } // namespace jit
 } // namespace js
 
 // Expand the given macro D for each valid GC reference type.
 #define FOR_EACH_INTERNAL_GC_POINTER_TYPE(D) \
@@ -87,16 +88,17 @@ class JitCode;
     D(js::RegExpObject*) \
     D(js::SavedFrame*) \
     D(js::Scope*) \
     D(js::ScriptSourceObject*) \
     D(js::Shape*) \
     D(js::SharedArrayBufferObject*) \
     D(js::StructTypeDescr*) \
     D(js::UnownedBaseShape*) \
+    D(js::WasmFunctionScope*) \
     D(js::WasmInstanceObject*) \
     D(js::WasmMemoryObject*) \
     D(js::WasmTableObject*) \
     D(js::jit::JitCode*)
 
 // Expand the given macro D for each internal tagged GC pointer type.
 #define FOR_EACH_INTERNAL_TAGGED_GC_POINTER_TYPE(D) \
     D(js::TaggedProto)
--- a/js/src/gdb/mozilla/Interpreter.py
+++ b/js/src/gdb/mozilla/Interpreter.py
@@ -11,16 +11,17 @@ from mozilla.prettyprinters import prett
 class InterpreterTypeCache(object):
     def __init__(self):
         self.tValue = gdb.lookup_type('JS::Value')
         self.tJSOp = gdb.lookup_type('JSOp')
         self.tScriptFrameIterData = gdb.lookup_type('js::ScriptFrameIter::Data')
         self.tInterpreterFrame = gdb.lookup_type('js::InterpreterFrame')
         self.tBaselineFrame = gdb.lookup_type('js::jit::BaselineFrame')
         self.tRematerializedFrame = gdb.lookup_type('js::jit::RematerializedFrame')
+        self.tDebugFrame = gdb.lookup_type('js::wasm::DebugFrame')
 
 @pretty_printer('js::InterpreterRegs')
 class InterpreterRegs(object):
     def __init__(self, value, cache):
         self.value = value
         self.cache = cache
         if not cache.mod_Interpreter:
             cache.mod_Interpreter = InterpreterTypeCache()
@@ -42,17 +43,18 @@ class InterpreterRegs(object):
         return '{{ {}, {}, {} }}'.format(fp_, sp, pc)
 
 @pretty_printer('js::AbstractFramePtr')
 class AbstractFramePtr(object):
     Tag_ScriptFrameIterData = 0x0
     Tag_InterpreterFrame = 0x1
     Tag_BaselineFrame = 0x2
     Tag_RematerializedFrame = 0x3
-    TagMask = 0x3
+    Tag_WasmDebugFrame = 0x4
+    TagMask = 0x7
 
     def __init__(self, value, cache):
         self.value = value
         self.cache = cache
         if not cache.mod_Interpreter:
             cache.mod_Interpreter = InterpreterTypeCache()
         self.itc = cache.mod_Interpreter
 
@@ -67,14 +69,17 @@ class AbstractFramePtr(object):
             label = 'js::InterpreterFrame'
             ptr = ptr.cast(self.itc.tInterpreterFrame.pointer())
         if tag == AbstractFramePtr.Tag_BaselineFrame:
             label = 'js::jit::BaselineFrame'
             ptr = ptr.cast(self.itc.tBaselineFrame.pointer())
         if tag == AbstractFramePtr.Tag_RematerializedFrame:
             label = 'js::jit::RematerializedFrame'
             ptr = ptr.cast(self.itc.tRematerializedFrame.pointer())
+        if tag == AbstractFramePtr.Tag_WasmDebugFrame:
+            label = 'js::wasm::DebugFrame'
+            ptr = ptr.cast(self.itc.tDebugFrame.pointer())
         return 'AbstractFramePtr (({} *) {})'.format(label, ptr)
 
     # Provide the ptr_ field as a child, so it prints after the pretty string
     # provided above.
     def children(self):
         yield ('ptr_', self.value['ptr_'])
--- a/js/src/gdb/tests/test-Interpreter.cpp
+++ b/js/src/gdb/tests/test-Interpreter.cpp
@@ -45,16 +45,23 @@ GDBTestInitAbstractFramePtr(AbstractFram
 
 void
 GDBTestInitAbstractFramePtr(AbstractFramePtr& frame, jit::RematerializedFrame* ptr)
 {
     MOZ_ASSERT((uintptr_t(ptr) & AbstractFramePtr::TagMask) == 0);
     frame.ptr_ = uintptr_t(ptr) | AbstractFramePtr::Tag_RematerializedFrame;
 }
 
+void
+GDBTestInitAbstractFramePtr(AbstractFramePtr& frame, wasm::DebugFrame* ptr)
+{
+    MOZ_ASSERT((uintptr_t(ptr) & AbstractFramePtr::TagMask) == 0);
+    frame.ptr_ = uintptr_t(ptr) | AbstractFramePtr::Tag_WasmDebugFrame;
+}
+
 } // namespace js
 
 FRAGMENT(Interpreter, Regs) {
   struct FakeFrame {
     js::InterpreterFrame frame;
     JS::Value slot0;
     JS::Value slot1;
     JS::Value slot2;
@@ -78,15 +85,18 @@ FRAGMENT(Interpreter, AbstractFramePtr) 
     GDBTestInitAbstractFramePtr(ifptr, (js::InterpreterFrame*) uintptr_t(0x8badf00));
 
     js::AbstractFramePtr bfptr;
     GDBTestInitAbstractFramePtr(bfptr, (js::jit::BaselineFrame*) uintptr_t(0xbadcafe0));
 
     js::AbstractFramePtr rfptr;
     GDBTestInitAbstractFramePtr(rfptr, (js::jit::RematerializedFrame*) uintptr_t(0xdabbad00));
 
+    js::AbstractFramePtr sfptr;
+    GDBTestInitAbstractFramePtr(sfptr, (js::wasm::DebugFrame*) uintptr_t(0xcb98ad00));
+
     breakpoint();
 
     (void) sfidptr;
     (void) ifptr;
     (void) bfptr;
     (void) rfptr;
 }
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/debug/wasm-06-onEnterFrame-null.js
@@ -0,0 +1,20 @@
+// |jit-test| test-also-wasm-baseline; exitstatus: 3
+// Checking resumption values for 'null' at onEnterFrame.
+
+load(libdir + "asserts.js");
+
+if (!wasmIsSupported())
+     quit(3);
+
+var g = newGlobal('');
+var dbg = new Debugger();
+dbg.addDebuggee(g);
+sandbox.eval(`
+var wasm = wasmTextToBinary('(module (func (nop)) (export "test" 0))');
+var m = new WebAssembly.Instance(new WebAssembly.Module(wasm));`);
+dbg.onEnterFrame = function (frame) {
+    if (frame.type !== "wasmcall") return;
+    return null;
+};
+g.eval("m.exports.test()");
+assertEq(false, true);
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/debug/wasm-06-onPop-null.js
@@ -0,0 +1,22 @@
+// |jit-test| test-also-wasm-baseline; exitstatus: 3
+// Checking resumption values for 'null' at frame's onPop.
+
+load(libdir + "asserts.js");
+
+if (!wasmIsSupported())
+     quit(3);
+
+var g = newGlobal('');
+var dbg = new Debugger();
+dbg.addDebuggee(g);
+sandbox.eval(`
+var wasm = wasmTextToBinary('(module (func (nop)) (export "test" 0))');
+var m = new WebAssembly.Instance(new WebAssembly.Module(wasm));`);
+dbg.onEnterFrame = function (frame) {
+    if (frame.type !== "wasmcall") return;
+    frame.onPop = function () {
+        return null;
+    };
+};
+g.eval("m.exports.test()");
+assertEq(false, true);
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/debug/wasm-06.js
@@ -0,0 +1,335 @@
+// |jit-test| test-also-wasm-baseline; error: TestComplete
+// Tests that wasm module scripts raises onEnterFrame and onLeaveFrame events.
+
+load(libdir + "asserts.js");
+
+if (!wasmIsSupported())
+     throw "TestComplete";
+
+function runWasmWithDebugger(wast, lib, init, done) {
+    let g = newGlobal('');
+    let dbg = new Debugger(g);
+
+    g.eval(`
+var wasm = wasmTextToBinary('${wast}');
+var lib = ${lib || 'undefined'};
+var m = new WebAssembly.Instance(new WebAssembly.Module(wasm), lib);`);
+
+    init(dbg, g);
+    let result = undefined, error = undefined;
+    try {
+        result = g.eval("m.exports.test()");
+    } catch (ex) {
+        error = ex;
+    }
+    done(dbg, result, error, g);
+}
+
+// Checking if onEnterFrame is fired for wasm frames and verifying the content
+// of the frame and environment properties.
+var onEnterFrameCalled, onLeaveFrameCalled, onExceptionUnwindCalled, testComplete;
+runWasmWithDebugger(
+    '(module (func (result i32) (i32.const 42)) (export "test" 0))', undefined,
+    function (dbg) {
+        var wasmScript = dbg.findScripts().filter(s => s.format == 'wasm')[0];
+        assertEq(!!wasmScript, true);
+        onEnterFrameCalled = 0;
+        onLeaveFrameCalled = 0;
+        testComplete = false;
+        var evalFrame;
+        dbg.onEnterFrame = function (frame) {
+            if (frame.type !== 'wasmcall') {
+                if (frame.type === 'eval')
+                    evalFrame = frame;
+                return;
+            }
+
+            onEnterFrameCalled++;
+
+            assertEq(frame.script, wasmScript);
+            assertEq(frame.older, evalFrame);
+            assertEq(frame.type, 'wasmcall');
+
+            let env = frame.environment;
+            assertEq(env instanceof Object, true);
+            assertEq(env.inspectable, true);
+            assertEq(env.parent !== null, true);
+            assertEq(env.type, 'declarative');
+            assertEq(env.callee, null);
+            assertEq(Array.isArray(env.names()), true);
+            assertEq(env.names().length, 0);
+
+            frame.onPop = function() {
+                onLeaveFrameCalled++;
+                testComplete = true;
+            };
+       };
+    },
+    function (dbg, result, error) {
+        assertEq(testComplete, true);
+        assertEq(onEnterFrameCalled, 1);
+        assertEq(onLeaveFrameCalled, 1);
+        assertEq(result, 42);
+        assertEq(error, undefined);
+    }
+);
+
+// Checking the dbg.getNewestFrame() and frame.older.
+runWasmWithDebugger(
+    '(module (import $fn1 "env" "ex") (func $fn2 (call $fn1)) (export "test" $fn2))',
+    '{env: { ex: () => { }}}',
+    function (dbg) {
+        onEnterFrameCalled = 0;
+        onLeaveFrameCalled = 0;
+        testComplete = false;
+        var evalFrame, wasmFrame;
+        dbg.onEnterFrame = function (frame) {
+            onEnterFrameCalled++;
+
+            assertEq(dbg.getNewestFrame(), frame);
+
+            switch (frame.type) {
+              case 'eval':
+                evalFrame = frame;
+                break;
+              case 'wasmcall':
+                wasmFrame = frame;
+                break;
+              case 'call':
+                assertEq(frame.older, wasmFrame);
+                assertEq(frame.older.older, evalFrame);
+                assertEq(frame.older.older.older, null);
+                testComplete = true;
+                break;
+            }
+
+            frame.onPop = function() {
+                onLeaveFrameCalled++;
+            };
+        };
+    },
+    function (dbg, result, error) {
+        assertEq(testComplete, true);
+        assertEq(onEnterFrameCalled, 3);
+        assertEq(onLeaveFrameCalled, 3);
+        assertEq(error, undefined);
+    }
+);
+
+// Checking if we can enumerate frames and find 'wasmcall' one.
+runWasmWithDebugger(
+    '(module (import $fn1 "env" "ex") (func $fn2 (call $fn1)) (export "test" $fn2))',
+    '{env: { ex: () => { debugger; }}}',
+    function (dbg) {
+        testComplete = false;
+        dbg.onDebuggerStatement = function (frame) {
+            assertEq(frame.type, 'call');
+            assertEq(frame.older.type, 'wasmcall');
+            assertEq(frame.older.older.type, 'eval');
+            assertEq(frame.older.older.older, null);
+            testComplete = true;
+        }
+    },
+    function (dbg, result, error) {
+        assertEq(testComplete, true);
+        assertEq(error, undefined);
+    }
+);
+
+// Checking if onPop works without onEnterFrame handler.
+runWasmWithDebugger(
+    '(module (import $fn1 "env" "ex") (func $fn2 (call $fn1)) (export "test" $fn2))',
+    '{env: { ex: () => { debugger; }}}',
+    function (dbg) {
+        onLeaveFrameCalled = 0;
+        dbg.onDebuggerStatement = function (frame) {
+            if (!frame.older || frame.older.type != 'wasmcall')
+                return;
+            frame.older.onPop = function () {
+                onLeaveFrameCalled++;
+            };
+        }
+    },
+    function (dbg, result, error) {
+        assertEq(onLeaveFrameCalled, 1);
+        assertEq(error, undefined);
+    }
+);
+
+// Checking if function return values are not changed.
+runWasmWithDebugger(
+    '(module (func (result f64) (f64.const 0.42)) (export "test" 0))', undefined,
+    function (dbg) {
+        dbg.onEnterFrame = function (frame) {
+            dbg.onPop = function () {};
+        };
+    },
+    function (dbg, result, error) {
+        assertEq(result, 0.42);
+        assertEq(error, undefined);
+    }
+);
+runWasmWithDebugger(
+    '(module (func (result f32) (f32.const 4.25)) (export "test" 0))', undefined,
+    function (dbg) {
+        dbg.onEnterFrame = function (frame) {
+            dbg.onPop = function () {};
+        };
+    },
+    function (dbg, result, error) {
+        assertEq(result, 4.25);
+        assertEq(error, undefined);
+    }
+);
+
+// Checking if onEnterFrame/onExceptionUnwind work during exceptions --
+// `unreachable` causes wasm to throw WebAssembly.RuntimeError exception.
+runWasmWithDebugger(
+    '(module (func (unreachable)) (export "test" 0))', undefined,
+    function (dbg) {
+       onEnterFrameCalled = 0;
+       onLeaveFrameCalled = 0;
+       onExceptionUnwindCalled = 0;
+       dbg.onEnterFrame = function (frame) {
+            if (frame.type !== "wasmcall") return;
+            onEnterFrameCalled++;
+            frame.onPop = function() {
+                onLeaveFrameCalled++;
+            };
+       };
+       dbg.onExceptionUnwind = function (frame) {
+         if (frame.type !== "wasmcall") return;
+         onExceptionUnwindCalled++;
+       };
+    },
+    function (dbg, result, error, g) {
+        assertEq(onEnterFrameCalled, 1);
+        assertEq(onLeaveFrameCalled, 1);
+        assertEq(onExceptionUnwindCalled, 1);
+        assertEq(error instanceof g.WebAssembly.RuntimeError, true);
+    }
+);
+
+// Checking if onEnterFrame/onExceptionUnwind work during exceptions
+// originated in the JavaScript import call.
+runWasmWithDebugger(
+    '(module (import $fn1 "env" "ex") (func $fn2 (call $fn1)) (export "test" $fn2))',
+    '{env: { ex: () => { throw new Error(); }}}',
+    function (dbg) {
+       onEnterFrameCalled = 0;
+       onLeaveFrameCalled = 0;
+       onExceptionUnwindCalled = 0;
+       dbg.onEnterFrame = function (frame) {
+            if (frame.type !== "wasmcall") return;
+            onEnterFrameCalled++;
+            frame.onPop = function() {
+                onLeaveFrameCalled++;
+            };
+       };
+       dbg.onExceptionUnwind = function (frame) {
+         if (frame.type !== "wasmcall") return;
+         onExceptionUnwindCalled++;
+       };
+    },
+    function (dbg, result, error, g) {
+        assertEq(onEnterFrameCalled, 1);
+        assertEq(onLeaveFrameCalled, 1);
+        assertEq(onExceptionUnwindCalled, 1);
+        assertEq(error instanceof g.Error, true);
+    }
+);
+
+// Checking throwing in the handler.
+runWasmWithDebugger(
+    '(module (func (unreachable)) (export "test" 0))', undefined,
+    function (dbg) {
+        dbg.uncaughtExceptionHook = function (value) {
+            assertEq(value instanceof Error, true);
+            return {throw: 'test'};
+        };
+        dbg.onEnterFrame = function (frame) {
+           if (frame.type !== "wasmcall") return;
+           throw new Error();
+        };
+    },
+    function (dbg, result, error) {
+        assertEq(error, 'test');
+    }
+);
+runWasmWithDebugger(
+    '(module (func (unreachable)) (export "test" 0))', undefined,
+    function (dbg) {
+        dbg.uncaughtExceptionHook = function (value) {
+            assertEq(value instanceof Error, true);
+            return {throw: 'test'};
+        };
+        dbg.onEnterFrame = function (frame) {
+            if (frame.type !== "wasmcall") return;
+            frame.onPop = function () {
+                throw new Error();
+            }
+        };
+    },
+    function (dbg, result, error) {
+        assertEq(error, 'test');
+    }
+);
+
+// Checking resumption values for JS_THROW.
+runWasmWithDebugger(
+    '(module (func (nop)) (export "test" 0))', undefined,
+    function (dbg, g) {
+        dbg.onEnterFrame = function (frame) {
+            if (frame.type !== "wasmcall") return;
+            return {throw: 'test'};
+        };
+    },
+    function (dbg, result, error, g) {
+        assertEq(error, 'test');
+    }
+);
+runWasmWithDebugger(
+    '(module (func (nop)) (export "test" 0))', undefined,
+    function (dbg, g) {
+        dbg.onEnterFrame = function (frame) {
+            if (frame.type !== "wasmcall") return;
+            frame.onPop = function () {
+                return {throw: 'test'};
+            }
+        };
+    },
+    function (dbg, result, error, g) {
+        assertEq(error, 'test');
+    }
+);
+
+// Checking resumption values for JS_RETURN (not implemented by wasm baseline).
+runWasmWithDebugger(
+    '(module (func (unreachable)) (export "test" 0))', undefined,
+    function (dbg) {
+        dbg.onEnterFrame = function (frame) {
+            if (frame.type !== "wasmcall") return;
+            return {return: 2};
+        };
+    },
+    function (dbg, result, error) {
+        assertEq(result, undefined, 'NYI: result == 2, if JS_RETURN is implemented');
+        assertEq(error != undefined, true, 'NYI: error == undefined, if JS_RETURN is implemented');
+    }
+);
+runWasmWithDebugger(
+    '(module (func (unreachable)) (export "test" 0))', undefined,
+    function (dbg) {
+        dbg.onEnterFrame = function (frame) {
+            if (frame.type !== "wasmcall") return;
+            frame.onPop = function () {
+                return {return: 2};
+            }
+        };
+    },
+    function (dbg, result, error) {
+        assertEq(result, undefined, 'NYI: result == 2, if JS_RETURN is implemented');
+        assertEq(error != undefined, true, 'NYI: error == undefined, if JS_RETURN is implemented');
+    }
+);
+throw "TestComplete";
--- a/js/src/jit-test/tests/wasm/profiling.js
+++ b/js/src/jit-test/tests/wasm/profiling.js
@@ -122,32 +122,32 @@ function testError(code, error, expect)
 }
 
 testError(
 `(module
     (func $foo (unreachable))
     (func (export "") (call $foo))
 )`,
 WebAssembly.RuntimeError,
-["", ">", "1,>", "0,1,>", "trap handling,0,1,>", "inline stub,0,1,>", ""]);
+["", ">", "1,>", "0,1,>", "trap handling,0,1,>", "inline stub,0,1,>", "trap handling,0,1,>", "inline stub,0,1,>", ""]);
 
 testError(
 `(module
     (type $good (func))
     (type $bad (func (param i32)))
     (func $foo (call_indirect $bad (i32.const 1) (i32.const 0)))
     (func $bar (type $good))
     (table anyfunc (elem $bar))
     (export "" $foo)
 )`,
 WebAssembly.RuntimeError,
 // Technically we have this one *one-instruction* interval where
 // the caller is lost (the stack with "1,>"). It's annoying to fix and shouldn't
 // mess up profiles in practice so we ignore it.
-["", ">", "0,>", "1,0,>", "1,>", "trap handling,0,>", "inline stub,0,>", ""]);
+["", ">", "0,>", "1,0,>", "1,>", "trap handling,0,>", "inline stub,0,>", "trap handling,0,>", "inline stub,0,>", ""]);
 
 (function() {
     var e = wasmEvalText(`
     (module
         (func $foo (result i32) (i32.const 42))
         (export "foo" $foo)
         (func $bar (result i32) (i32.const 13))
         (table 10 anyfunc)
--- a/js/src/jit/MacroAssembler.h
+++ b/js/src/jit/MacroAssembler.h
@@ -524,16 +524,22 @@ class MacroAssembler : public MacroAssem
     static void repatchFarJump(uint8_t* code, uint32_t farJumpOffset, uint32_t targetOffset) PER_SHARED_ARCH;
 
     // Emit a nop that can be patched to and from a nop and a jump with an int8
     // relative displacement.
     CodeOffset nopPatchableToNearJump() PER_SHARED_ARCH;
     static void patchNopToNearJump(uint8_t* jump, uint8_t* target) PER_SHARED_ARCH;
     static void patchNearJumpToNop(uint8_t* jump) PER_SHARED_ARCH;
 
+    // Emit a nop that can be patched to and from a nop and a call with int32
+    // relative displacement.
+    CodeOffset nopPatchableToCall(const wasm::CallSiteDesc& desc) PER_SHARED_ARCH;
+    static void patchNopToCall(uint8_t* callsite, uint8_t* target) PER_SHARED_ARCH;
+    static void patchCallToNop(uint8_t* callsite) PER_SHARED_ARCH;
+
   public:
     // ===============================================================
     // ABI function calls.
 
     // Setup a call to C/C++ code, given the assumption that the framePushed
     // accruately define the state of the stack, and that the top of the stack
     // was properly aligned. Note that this only supports cdecl.
     void setupAlignedABICall(); // CRASH_ON(arm64)
--- a/js/src/jit/arm/MacroAssembler-arm.cpp
+++ b/js/src/jit/arm/MacroAssembler-arm.cpp
@@ -5123,16 +5123,44 @@ MacroAssembler::patchNopToNearJump(uint8
 
 void
 MacroAssembler::patchNearJumpToNop(uint8_t* jump)
 {
     MOZ_ASSERT(reinterpret_cast<Instruction*>(jump)->is<InstBImm>());
     new (jump) InstNOP();
 }
 
+CodeOffset
+MacroAssembler::nopPatchableToCall(const wasm::CallSiteDesc& desc)
+{
+    CodeOffset offset(currentOffset());
+    ma_nop();
+    append(desc, CodeOffset(currentOffset()), framePushed());
+    return offset;
+}
+
+void
+MacroAssembler::patchNopToCall(uint8_t* call, uint8_t* target)
+{
+    uint8_t* inst = call - 4;
+    MOZ_ASSERT(reinterpret_cast<Instruction*>(inst)->is<InstBLImm>() ||
+               reinterpret_cast<Instruction*>(inst)->is<InstNOP>());
+
+    new (inst) InstBLImm(BOffImm(target - inst), Assembler::Always);
+}
+
+void
+MacroAssembler::patchCallToNop(uint8_t* call)
+{
+    uint8_t* inst = call - 4;
+    MOZ_ASSERT(reinterpret_cast<Instruction*>(inst)->is<InstBLImm>() ||
+               reinterpret_cast<Instruction*>(inst)->is<InstNOP>());
+    new (inst) InstNOP();
+}
+
 void
 MacroAssembler::pushReturnAddress()
 {
     push(lr);
 }
 
 void
 MacroAssembler::popReturnAddress()
--- a/js/src/jit/arm64/MacroAssembler-arm64.cpp
+++ b/js/src/jit/arm64/MacroAssembler-arm64.cpp
@@ -582,16 +582,35 @@ MacroAssembler::patchNopToNearJump(uint8
 }
 
 void
 MacroAssembler::patchNearJumpToNop(uint8_t* jump)
 {
     MOZ_CRASH("NYI");
 }
 
+CodeOffset
+MacroAssembler::nopPatchableToCall(const wasm::CallSiteDesc& desc)
+{
+    MOZ_CRASH("NYI");
+    return CodeOffset();
+}
+
+void
+MacroAssembler::patchNopToCall(uint8_t* call, uint8_t* target)
+{
+    MOZ_CRASH("NYI");
+}
+
+void
+MacroAssembler::patchCallToNop(uint8_t* call)
+{
+    MOZ_CRASH("NYI");
+}
+
 void
 MacroAssembler::pushReturnAddress()
 {
     push(lr);
 }
 
 void
 MacroAssembler::popReturnAddress()
--- a/js/src/jit/mips-shared/MacroAssembler-mips-shared.cpp
+++ b/js/src/jit/mips-shared/MacroAssembler-mips-shared.cpp
@@ -1673,16 +1673,35 @@ void
 MacroAssembler::call(JitCode* c)
 {
     BufferOffset bo = m_buffer.nextOffset();
     addPendingJump(bo, ImmPtr(c->raw()), Relocation::JITCODE);
     ma_liPatchable(ScratchRegister, ImmPtr(c->raw()));
     callJitNoProfiler(ScratchRegister);
 }
 
+CodeOffset
+MacroAssembler::nopPatchableToCall(const wasm::CallSiteDesc& desc)
+{
+    MOZ_CRASH("NYI");
+    return CodeOffset();
+}
+
+void
+MacroAssembler::patchNopToCall(uint8_t* call, uint8_t* target)
+{
+    MOZ_CRASH("NYI");
+}
+
+void
+MacroAssembler::patchCallToNop(uint8_t* call)
+{
+    MOZ_CRASH("NYI");
+}
+
 void
 MacroAssembler::pushReturnAddress()
 {
     push(ra);
 }
 
 void
 MacroAssembler::popReturnAddress()
--- a/js/src/jit/shared/Assembler-shared.h
+++ b/js/src/jit/shared/Assembler-shared.h
@@ -660,38 +660,16 @@ class CodeLocationLabel
         return raw_;
     }
 };
 
 } // namespace jit
 
 namespace wasm {
 
-// As an invariant across architectures, within wasm code:
-//   $sp % WasmStackAlignment = (sizeof(wasm::Frame) + masm.framePushed) % WasmStackAlignment
-// Thus, wasm::Frame represents the bytes pushed after the call (which occurred
-// with a WasmStackAlignment-aligned StackPointer) that are not included in
-// masm.framePushed.
-
-struct Frame
-{
-    // The caller's saved frame pointer. In non-profiling mode, internal
-    // wasm-to-wasm calls don't update fp and thus don't save the caller's
-    // frame pointer; the space is reserved, however, so that profiling mode can
-    // reuse the same function body without recompiling.
-    uint8_t* callerFP;
-
-    // The return address pushed by the call (in the case of ARM/MIPS the return
-    // address is pushed by the first instruction of the prologue).
-    void* returnAddress;
-};
-
-static_assert(sizeof(Frame) == 2 * sizeof(void*), "?!");
-static const uint32_t FrameBytesAfterReturnAddress = sizeof(void*);
-
 // Represents an instruction to be patched and the intended pointee. These
 // links are accumulated in the MacroAssembler, but patching is done outside
 // the MacroAssembler (in Module::staticallyLink).
 
 struct SymbolicAccess
 {
     SymbolicAccess(jit::CodeOffset patchAt, SymbolicAddress target)
       : patchAt(patchAt), target(target) {}
--- a/js/src/jit/x86-shared/Assembler-x86-shared.h
+++ b/js/src/jit/x86-shared/Assembler-x86-shared.h
@@ -1101,16 +1101,23 @@ class AssemblerX86Shared : public Assemb
     }
     static void patchTwoByteNopToJump(uint8_t* jump, uint8_t* target) {
         X86Encoding::BaseAssembler::patchTwoByteNopToJump(jump, target);
     }
     static void patchJumpToTwoByteNop(uint8_t* jump) {
         X86Encoding::BaseAssembler::patchJumpToTwoByteNop(jump);
     }
 
+    static void patchFiveByteNopToCall(uint8_t* callsite, uint8_t* target) {
+        X86Encoding::BaseAssembler::patchFiveByteNopToCall(callsite, target);
+    }
+    static void patchCallToFiveByteNop(uint8_t* callsite) {
+        X86Encoding::BaseAssembler::patchCallToFiveByteNop(callsite);
+    }
+
     void breakpoint() {
         masm.int3();
     }
 
     static bool HasSSE2() { return CPUInfo::IsSSE2Present(); }
     static bool HasSSE3() { return CPUInfo::IsSSE3Present(); }
     static bool HasSSSE3() { return CPUInfo::IsSSSE3Present(); }
     static bool HasSSE41() { return CPUInfo::IsSSE41Present(); }
--- a/js/src/jit/x86-shared/BaseAssembler-x86-shared.h
+++ b/js/src/jit/x86-shared/BaseAssembler-x86-shared.h
@@ -111,16 +111,50 @@ public:
     static void patchJumpToTwoByteNop(uint8_t* jump)
     {
         // See twoByteNop.
         MOZ_RELEASE_ASSERT(jump[0] == OP_JMP_rel8);
         jump[0] = PRE_OPERAND_SIZE;
         jump[1] = OP_NOP;
     }
 
+    static void patchFiveByteNopToCall(uint8_t* callsite, uint8_t* target)
+    {
+        // Note: the offset is relative to the address of the instruction after
+        // the call which is five bytes.
+        uint8_t* inst = callsite - sizeof(int32_t) - 1;
+        // The nop can be already patched as call, overriding the call.
+        // See also nop_five.
+        MOZ_ASSERT(inst[0] == OP_NOP_0F || inst[0] == OP_CALL_rel32);
+        MOZ_ASSERT_IF(inst[0] == OP_NOP_0F, inst[1] == OP_NOP_1F ||
+                                            inst[2] == OP_NOP_44 ||
+                                            inst[3] == OP_NOP_00 ||
+                                            inst[4] == OP_NOP_00);
+        inst[0] = OP_CALL_rel32;
+        SetRel32(callsite, target);
+    }
+
+    static void patchCallToFiveByteNop(uint8_t* callsite)
+    {
+        // See also patchFiveByteNopToCall and nop_five.
+        uint8_t* inst = callsite - sizeof(int32_t) - 1;
+        // The call can be already patched as nop.
+        if (inst[0] == OP_NOP_0F) {
+            MOZ_ASSERT(inst[1] == OP_NOP_1F || inst[2] == OP_NOP_44 ||
+                       inst[3] == OP_NOP_00 || inst[4] == OP_NOP_00);
+            return;
+        }
+        MOZ_ASSERT(inst[0] == OP_CALL_rel32);
+        inst[0] = OP_NOP_0F;
+        inst[1] = OP_NOP_1F;
+        inst[2] = OP_NOP_44;
+        inst[3] = OP_NOP_00;
+        inst[4] = OP_NOP_00;
+    }
+
     /*
      * The nop multibytes sequences are directly taken from the Intel's
      * architecture software developer manual.
      * They are defined for sequences of sizes from 1 to 9 included.
      */
     void nop_one()
     {
         m_formatter.oneByteOp(OP_NOP);
--- a/js/src/jit/x86-shared/MacroAssembler-x86-shared.cpp
+++ b/js/src/jit/x86-shared/MacroAssembler-x86-shared.cpp
@@ -736,16 +736,38 @@ MacroAssembler::patchNopToNearJump(uint8
 }
 
 void
 MacroAssembler::patchNearJumpToNop(uint8_t* jump)
 {
     Assembler::patchJumpToTwoByteNop(jump);
 }
 
+CodeOffset
+MacroAssembler::nopPatchableToCall(const wasm::CallSiteDesc& desc)
+{
+    CodeOffset offset(currentOffset());
+    masm.nop_five();
+    append(desc, CodeOffset(currentOffset()), framePushed());
+    MOZ_ASSERT_IF(!oom(), size() - offset.offset() == ToggledCallSize(nullptr));
+    return offset;
+}
+
+void
+MacroAssembler::patchNopToCall(uint8_t* callsite, uint8_t* target)
+{
+    Assembler::patchFiveByteNopToCall(callsite, target);
+}
+
+void
+MacroAssembler::patchCallToNop(uint8_t* callsite)
+{
+    Assembler::patchCallToFiveByteNop(callsite);
+}
+
 // ===============================================================
 // Jit Frames.
 
 uint32_t
 MacroAssembler::pushFakeReturnAddress(Register scratch)
 {
     CodeLabel cl;
 
--- a/js/src/jsscript.cpp
+++ b/js/src/jsscript.cpp
@@ -747,16 +747,19 @@ js::XDRScript(XDRState<mode>* xdr, Handl
               case ScopeKind::Global:
               case ScopeKind::NonSyntactic:
                 if (!GlobalScope::XDR(xdr, scopeKind, &scope))
                     return false;
                 break;
               case ScopeKind::Module:
                 MOZ_CRASH("NYI");
                 break;
+              case ScopeKind::WasmFunction:
+                MOZ_CRASH("wasm functions cannot be nested in JSScripts");
+                break;
             }
 
             if (mode == XDR_DECODE)
                 vector[i].init(scope);
         }
     }
 
     /*
--- a/js/src/moz.build
+++ b/js/src/moz.build
@@ -368,16 +368,17 @@ UNIFIED_SOURCES += [
     'wasm/WasmBaselineCompile.cpp',
     'wasm/WasmBinaryIterator.cpp',
     'wasm/WasmBinaryToAST.cpp',
     'wasm/WasmBinaryToExperimentalText.cpp',
     'wasm/WasmBinaryToText.cpp',
     'wasm/WasmCode.cpp',
     'wasm/WasmCompartment.cpp',
     'wasm/WasmCompile.cpp',
+    'wasm/WasmDebugFrame.cpp',
     'wasm/WasmFrameIterator.cpp',
     'wasm/WasmGenerator.cpp',
     'wasm/WasmInstance.cpp',
     'wasm/WasmIonCompile.cpp',
     'wasm/WasmJS.cpp',
     'wasm/WasmModule.cpp',
     'wasm/WasmSignalHandlers.cpp',
     'wasm/WasmStubs.cpp',
--- a/js/src/vm/CommonPropertyNames.h
+++ b/js/src/vm/CommonPropertyNames.h
@@ -339,16 +339,17 @@
     macro(useStrict, useStrict, "use strict") \
     macro(value, value, "value") \
     macro(valueOf, valueOf, "valueOf") \
     macro(values, values, "values") \
     macro(var, var, "var") \
     macro(variable, variable, "variable") \
     macro(void0, void0, "(void 0)") \
     macro(wasm, wasm, "wasm") \
+    macro(wasmcall, wasmcall, "wasmcall") \
     macro(watch, watch, "watch") \
     macro(WeakMapConstructorInit, WeakMapConstructorInit, "WeakMapConstructorInit") \
     macro(WeakSetConstructorInit, WeakSetConstructorInit, "WeakSetConstructorInit") \
     macro(WeakSet_add, WeakSet_add, "WeakSet_add") \
     macro(weekday, weekday, "weekday") \
     macro(weekendEnd, weekendEnd, "weekendEnd") \
     macro(weekendStart, weekendStart, "weekendStart") \
     macro(writable, writable, "writable") \
--- a/js/src/vm/Debugger-inl.h
+++ b/js/src/vm/Debugger-inl.h
@@ -10,17 +10,17 @@
 #include "vm/Debugger.h"
 
 #include "vm/Stack-inl.h"
 
 /* static */ inline bool
 js::Debugger::onLeaveFrame(JSContext* cx, AbstractFramePtr frame, jsbytecode* pc, bool ok)
 {
     MOZ_ASSERT_IF(frame.isInterpreterFrame(), frame.asInterpreterFrame() == cx->interpreterFrame());
-    MOZ_ASSERT_IF(frame.script()->isDebuggee(), frame.isDebuggee());
+    MOZ_ASSERT_IF(frame.hasScript() && frame.script()->isDebuggee(), frame.isDebuggee());
     /* Traps must be cleared from eval frames, see slowPathOnLeaveFrame. */
     mozilla::DebugOnly<bool> evalTraps = frame.isEvalFrame() &&
                                          frame.script()->hasAnyBreakpointsOrStepMode();
     MOZ_ASSERT_IF(evalTraps, frame.isDebuggee());
     if (frame.isDebuggee())
         ok = slowPathOnLeaveFrame(cx, frame, pc, ok);
     MOZ_ASSERT(!inFrameMaps(frame));
     return ok;
@@ -39,17 +39,17 @@ js::Debugger::checkNoExecute(JSContext* 
     if (!cx->compartment()->isDebuggee() || !cx->runtime()->noExecuteDebuggerTop)
         return true;
     return slowPathCheckNoExecute(cx, script);
 }
 
 /* static */ JSTrapStatus
 js::Debugger::onEnterFrame(JSContext* cx, AbstractFramePtr frame)
 {
-    MOZ_ASSERT_IF(frame.script()->isDebuggee(), frame.isDebuggee());
+    MOZ_ASSERT_IF(frame.hasScript() && frame.script()->isDebuggee(), frame.isDebuggee());
     if (!frame.isDebuggee())
         return JSTRAP_CONTINUE;
     return slowPathOnEnterFrame(cx, frame);
 }
 
 /* static */ JSTrapStatus
 js::Debugger::onDebuggerStatement(JSContext* cx, AbstractFramePtr frame)
 {
@@ -69,17 +69,17 @@ js::Debugger::onExceptionUnwind(JSContex
 /* static */ void
 js::Debugger::onNewWasmInstance(JSContext* cx, Handle<WasmInstanceObject*> wasmInstance)
 {
     if (cx->compartment()->isDebuggee())
         slowPathOnNewWasmInstance(cx, wasmInstance);
 }
 
 inline bool
-js::Debugger::getScriptFrame(JSContext* cx, const ScriptFrameIter& iter,
+js::Debugger::getScriptFrame(JSContext* cx, const FrameIter& iter,
                              MutableHandle<DebuggerFrame*> result)
 {
     return getScriptFrameWithIter(cx, iter.abstractFramePtr(), &iter, result);
 }
 
 inline js::Debugger*
 js::DebuggerEnvironment::owner() const
 {
--- a/js/src/vm/Debugger.cpp
+++ b/js/src/vm/Debugger.cpp
@@ -734,35 +734,35 @@ DebuggerMemory&
 Debugger::memory() const
 {
     MOZ_ASSERT(hasMemory());
     return object->getReservedSlot(JSSLOT_DEBUG_MEMORY_INSTANCE).toObject().as<DebuggerMemory>();
 }
 
 bool
 Debugger::getScriptFrameWithIter(JSContext* cx, AbstractFramePtr referent,
-                                 const ScriptFrameIter* maybeIter, MutableHandleValue vp)
+                                 const FrameIter* maybeIter, MutableHandleValue vp)
 {
     RootedDebuggerFrame result(cx);
     if (!Debugger::getScriptFrameWithIter(cx, referent, maybeIter, &result))
         return false;
 
     vp.setObject(*result);
     return true;
 }
 
 bool
 Debugger::getScriptFrameWithIter(JSContext* cx, AbstractFramePtr referent,
-                                 const ScriptFrameIter* maybeIter,
+                                 const FrameIter* maybeIter,
                                  MutableHandleDebuggerFrame result)
 {
     MOZ_ASSERT_IF(maybeIter, maybeIter->abstractFramePtr() == referent);
-    MOZ_ASSERT(!referent.script()->selfHosted());
-
-    if (!referent.script()->ensureHasAnalyzedArgsUsage(cx))
+    MOZ_ASSERT_IF(referent.hasScript(), !referent.script()->selfHosted());
+
+    if (referent.hasScript() && !referent.script()->ensureHasAnalyzedArgsUsage(cx))
         return false;
 
     FrameMap::AddPtr p = frames.lookupForAdd(referent);
     if (!p) {
         /* Create and populate the Debugger.Frame object. */
         RootedObject proto(cx, &object->getReservedSlot(JSSLOT_DEBUG_FRAME_PROTO).toObject());
         RootedNativeObject debugger(cx, object);
 
@@ -1010,17 +1010,17 @@ Debugger::slowPathOnDebuggerStatement(JS
 Debugger::slowPathOnExceptionUnwind(JSContext* cx, AbstractFramePtr frame)
 {
     // Invoking more JS on an over-recursed stack or after OOM is only going
     // to result in more of the same error.
     if (cx->isThrowingOverRecursed() || cx->isThrowingOutOfMemory())
         return JSTRAP_CONTINUE;
 
     // The Debugger API mustn't muck with frames from self-hosted scripts.
-    if (frame.script()->selfHosted())
+    if (frame.hasScript() && frame.script()->selfHosted())
         return JSTRAP_CONTINUE;
 
     RootedValue rval(cx);
     JSTrapStatus status = dispatchHook(
         cx,
         [](Debugger* dbg) -> bool { return dbg->getHook(OnExceptionUnwind); },
         [&](Debugger* dbg) -> JSTrapStatus {
             return dbg->fireExceptionUnwind(cx, &rval);
@@ -1766,17 +1766,17 @@ Debugger::fireExceptionUnwind(JSContext*
     cx->clearPendingException();
 
     Maybe<AutoCompartment> ac;
     ac.emplace(cx, object);
 
     RootedValue scriptFrame(cx);
     RootedValue wrappedExc(cx, exc);
 
-    ScriptFrameIter iter(cx);
+    FrameIter iter(cx);
     if (!getScriptFrame(cx, iter, &scriptFrame) || !wrapDebuggeeValue(cx, &wrappedExc))
         return reportUncaughtException(ac);
 
     RootedValue fval(cx, ObjectValue(*hook));
     RootedValue rv(cx);
     bool ok = js::Call(cx, fval, object, scriptFrame, wrappedExc, &rv);
     JSTrapStatus st = processHandlerResult(ac, ok, rv, iter.abstractFramePtr(), iter.pc(), vp);
     if (st == JSTRAP_CONTINUE)
@@ -1791,17 +1791,17 @@ Debugger::fireEnterFrame(JSContext* cx, 
     MOZ_ASSERT(hook);
     MOZ_ASSERT(hook->isCallable());
 
     Maybe<AutoCompartment> ac;
     ac.emplace(cx, object);
 
     RootedValue scriptFrame(cx);
 
-    ScriptFrameIter iter(cx);
+    FrameIter iter(cx);
     if (!getScriptFrame(cx, iter, &scriptFrame))
         return reportUncaughtException(ac);
 
     RootedValue fval(cx, ObjectValue(*hook));
     RootedValue rv(cx);
     bool ok = js::Call(cx, fval, object, scriptFrame, &rv);
 
     return processHandlerResult(ac, ok, rv, iter.abstractFramePtr(), iter.pc(), vp);
@@ -2044,16 +2044,18 @@ Debugger::onSingleStep(JSContext* cx, Mu
         JSScript* trappingScript = iter.script();
         GlobalObject* global = cx->global();
         if (GlobalObject::DebuggerVector* debuggers = global->getDebuggers()) {
             for (auto p = debuggers->begin(); p != debuggers->end(); p++) {
                 Debugger* dbg = *p;
                 for (FrameMap::Range r = dbg->frames.all(); !r.empty(); r.popFront()) {
                     AbstractFramePtr frame = r.front().key();
                     NativeObject* frameobj = r.front().value();
+                    if (frame.isWasmDebugFrame())
+                        continue;
                     if (frame.script() == trappingScript &&
                         !frameobj->getReservedSlot(JSSLOT_DEBUGFRAME_ONSTEP_HANDLER).isUndefined())
                     {
                         stepperCount++;
                     }
                 }
             }
         }
@@ -2341,19 +2343,20 @@ class MOZ_RAII ExecutionObservableCompar
 
     typedef HashSet<JSCompartment*>::Range CompartmentRange;
     const HashSet<JSCompartment*>* compartments() const { return &compartments_; }
 
     const HashSet<Zone*>* zones() const { return &zones_; }
     bool shouldRecompileOrInvalidate(JSScript* script) const {
         return script->hasBaselineScript() && compartments_.has(script->compartment());
     }
-    bool shouldMarkAsDebuggee(ScriptFrameIter& iter) const {
-        // AbstractFramePtr can't refer to non-remateralized Ion frames, so if
-        // iter refers to one such, we know we don't match.
+    bool shouldMarkAsDebuggee(FrameIter& iter) const {
+        // AbstractFramePtr can't refer to non-remateralized Ion frames or
+        // non-debuggee wasm frames, so if iter refers to one such, we know we
+        // don't match.
         return iter.hasUsableAbstractFramePtr() && compartments_.has(iter.compartment());
     }
 
     MOZ_DECL_USE_GUARD_OBJECT_NOTIFIER
 };
 
 // Given a particular AbstractFramePtr F that has become observable, this
 // represents the stack frames that need to be bailed out or marked as
@@ -2395,26 +2398,27 @@ class MOZ_RAII ExecutionObservableFrame 
         // Baseline script to recompile.
         //
         // Note that this does not, by design, invalidate *all* inliners of
         // frame_.script(), as only frame_ is made observable, not
         // frame_.script().
         if (!script->hasBaselineScript())
             return false;
 
-        if (script == frame_.script())
+        if (frame_.hasScript() && script == frame_.script())
             return true;
 
         return frame_.isRematerializedFrame() &&
                script == frame_.asRematerializedFrame()->outerScript();
     }
 
-    bool shouldMarkAsDebuggee(ScriptFrameIter& iter) const {
-        // AbstractFramePtr can't refer to non-remateralized Ion frames, so if
-        // iter refers to one such, we know we don't match.
+    bool shouldMarkAsDebuggee(FrameIter& iter) const {
+        // AbstractFramePtr can't refer to non-remateralized Ion frames or
+        // non-debuggee wasm frames, so if iter refers to one such, we know we
+        // don't match.
         //
         // We never use this 'has' overload for frame invalidation, only for
         // frame debuggee marking; so this overload doesn't need a parallel to
         // the just-so inlining logic above.
         return iter.hasUsableAbstractFramePtr() && iter.abstractFramePtr() == frame_;
     }
 
     MOZ_DECL_USE_GUARD_OBJECT_NOTIFIER
@@ -2432,26 +2436,29 @@ class MOZ_RAII ExecutionObservableScript
         MOZ_GUARD_OBJECT_NOTIFIER_INIT;
     }
 
     Zone* singleZone() const { return script_->compartment()->zone(); }
     JSScript* singleScriptForZoneInvalidation() const { return script_; }
     bool shouldRecompileOrInvalidate(JSScript* script) const {
         return script->hasBaselineScript() && script == script_;
     }
-    bool shouldMarkAsDebuggee(ScriptFrameIter& iter) const {
+    bool shouldMarkAsDebuggee(FrameIter& iter) const {
         // AbstractFramePtr can't refer to non-remateralized Ion frames, and
         // while a non-rematerialized Ion frame may indeed be running script_,
         // we cannot mark them as debuggees until they bail out.
         //
         // Upon bailing out, any newly constructed Baseline frames that came
         // from Ion frames with scripts that are isDebuggee() is marked as
         // debuggee. This is correct in that the only other way a frame may be
         // marked as debuggee is via Debugger.Frame reflection, which would
         // have rematerialized any Ion frames.
+        //
+        // Also AbstractFramePtr can't refer to non-debuggee wasm frames, so if
+        // iter refers to one such, we know we don't match.
         return iter.hasUsableAbstractFramePtr() && iter.abstractFramePtr().script() == script_;
     }
 
     MOZ_DECL_USE_GUARD_OBJECT_NOTIFIER
 };
 
 /* static */ bool
 Debugger::updateExecutionObservabilityOfFrames(JSContext* cx, const ExecutionObservableSet& obs,
@@ -2463,26 +2470,28 @@ Debugger::updateExecutionObservabilityOf
         jit::JitContext jctx(cx, nullptr);
         if (!jit::RecompileOnStackBaselineScriptsForDebugMode(cx, obs, observing)) {
             ReportOutOfMemory(cx);
             return false;
         }
     }
 
     AbstractFramePtr oldestEnabledFrame;
-    for (ScriptFrameIter iter(cx);
+    for (FrameIter iter(cx);
          !iter.done();
          ++iter)
     {
         if (obs.shouldMarkAsDebuggee(iter)) {
             if (observing) {
                 if (!iter.abstractFramePtr().isDebuggee()) {
                     oldestEnabledFrame = iter.abstractFramePtr();
                     oldestEnabledFrame.setIsDebuggee();
                 }
+                if (iter.abstractFramePtr().isWasmDebugFrame())
+                    iter.abstractFramePtr().asWasmDebugFrame()->observeFrame(cx);
             } else {
 #ifdef DEBUG
                 // Debugger.Frame lifetimes are managed by the debug epilogue,
                 // so in general it's unsafe to unmark a frame if it has a
                 // Debugger.Frame associated with it.
                 MOZ_ASSERT(!inFrameMaps(iter.abstractFramePtr()));
 #endif
                 iter.abstractFramePtr().unsetIsDebuggee();
@@ -2581,16 +2590,27 @@ UpdateExecutionObservabilityOfScriptsInZ
     // Iterate through the scripts again and finish discarding
     // BaselineScripts. This must be done as a separate phase as we can only
     // discard the BaselineScript on scripts that have no IonScript.
     for (size_t i = 0; i < scripts.length(); i++) {
         MOZ_ASSERT_IF(scripts[i]->isDebuggee(), observing);
         FinishDiscardBaselineScript(fop, scripts[i]);
     }
 
+    // Iterate through all wasm instances to find ones that need to be updated.
+    for (JSCompartment* c : zone->compartments) {
+        for (wasm::Instance* instance : c->wasm.instances()) {
+            if (!instance->debugEnabled())
+                continue;
+
+            bool enableTrap = observing == Debugger::IsObserving::Observing;
+            instance->ensureEnterFrameTrapsState(cx, enableTrap);
+        }
+    }
+
     return true;
 }
 
 /* static */ bool
 Debugger::updateExecutionObservabilityOfScripts(JSContext* cx, const ExecutionObservableSet& obs,
                                                 IsObserving observing)
 {
     if (Zone* zone = obs.singleZone())
@@ -2604,17 +2624,17 @@ Debugger::updateExecutionObservabilityOf
 
     return true;
 }
 
 template <typename FrameFn>
 /* static */ void
 Debugger::forEachDebuggerFrame(AbstractFramePtr frame, FrameFn fn)
 {
-    GlobalObject* global = &frame.script()->global();
+    GlobalObject* global = frame.global();
     if (GlobalObject::DebuggerVector* debuggers = global->getDebuggers()) {
         for (auto p = debuggers->begin(); p != debuggers->end(); p++) {
             Debugger* dbg = *p;
             if (FrameMap::Ptr entry = dbg->frames.lookup(frame))
                 fn(entry->value());
         }
     }
 }
@@ -2663,17 +2683,18 @@ Debugger::ensureExecutionObservabilityOf
     }
     ExecutionObservableFrame obs(frame);
     return updateExecutionObservabilityOfFrames(cx, obs, Observing);
 }
 
 /* static */ bool
 Debugger::ensureExecutionObservabilityOfFrame(JSContext* cx, AbstractFramePtr frame)
 {
-    MOZ_ASSERT_IF(frame.script()->isDebuggee(), frame.isDebuggee());
+    MOZ_ASSERT_IF(frame.hasScript() && frame.script()->isDebuggee(), frame.isDebuggee());
+    MOZ_ASSERT_IF(frame.isWasmDebugFrame(), frame.wasmInstance()->debugEnabled());
     if (frame.isDebuggee())
         return true;
     ExecutionObservableFrame obs(frame);
     return updateExecutionObservabilityOfFrames(cx, obs, Observing);
 }
 
 /* static */ bool
 Debugger::ensureExecutionObservabilityOfCompartment(JSContext* cx, JSCompartment* comp)
@@ -2769,17 +2790,17 @@ Debugger::updateObservesCoverageOnDebugg
         // dangling pointers to freed PCCounts.
         if (!obs.add(comp))
             return false;
     }
 
     // If any frame on the stack belongs to the debuggee, then we cannot update
     // the ScriptCounts, because this would imply to invalidate a Debugger.Frame
     // to recompile it with/without ScriptCount support.
-    for (ScriptFrameIter iter(cx);
+    for (FrameIter iter(cx);
          !iter.done();
          ++iter)
     {
         if (obs.shouldMarkAsDebuggee(iter)) {
             JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_DEBUG_NOT_IDLE);
             return false;
         }
     }
@@ -3738,25 +3759,25 @@ Debugger::getDebuggees(JSContext* cx, un
     return true;
 }
 
 /* static */ bool
 Debugger::getNewestFrame(JSContext* cx, unsigned argc, Value* vp)
 {
     THIS_DEBUGGER(cx, argc, vp, "getNewestFrame", args, dbg);
 
-    /* Since there may be multiple contexts, use AllScriptFramesIter. */
-    for (AllScriptFramesIter i(cx); !i.done(); ++i) {
+    /* Since there may be multiple contexts, use AllFramesIter. */
+    for (AllFramesIter i(cx); !i.done(); ++i) {
         if (dbg->observesFrame(i)) {
             // Ensure that Ion frames are rematerialized. Only rematerialized
             // Ion frames may be used as AbstractFramePtrs.
             if (i.isIon() && !i.ensureHasRematerializedFrame(cx))
                 return false;
             AbstractFramePtr frame = i.abstractFramePtr();
-            ScriptFrameIter iter(i.activation()->cx());
+            FrameIter iter(i.activation()->cx());
             while (!iter.hasUsableAbstractFramePtr() || iter.abstractFramePtr() != frame)
                 ++iter;
             return dbg->getScriptFrame(cx, iter, args.rval());
         }
     }
     args.rval().setNull();
     return true;
 }
@@ -4009,17 +4030,17 @@ Debugger::removeDebuggeeGlobal(FreeOp* f
      * have live Frame objects. So we take the easy way out and kill them here.
      * This is a bug, since it's observable and contrary to the spec. One
      * possible fix would be to put such objects into a compartment-wide bag
      * which slowPathOnLeaveFrame would have to examine.
      */
     for (FrameMap::Enum e(frames); !e.empty(); e.popFront()) {
         AbstractFramePtr frame = e.front().key();
         NativeObject* frameobj = e.front().value();
-        if (&frame.script()->global() == global) {
+        if (frame.global() == global) {
             DebuggerFrame_freeScriptFrameIterData(fop, frameobj);
             DebuggerFrame_maybeDecrementFrameScriptStepModeCount(fop, frame, frameobj);
             e.removeFront();
         }
     }
 
     auto *globalDebuggersVector = global->getDebuggers();
     auto *zoneDebuggersVector = global->zone()->getDebuggers();
@@ -6256,43 +6277,54 @@ DebuggerScript_getLineOffsets(JSContext*
 
     args.rval().setObject(*matcher.result());
     return true;
 }
 
 bool
 Debugger::observesFrame(AbstractFramePtr frame) const
 {
+    if (frame.isWasmDebugFrame())
+        return observesWasm(frame.wasmInstance());
+
     return observesScript(frame.script());
 }
 
 bool
 Debugger::observesFrame(const FrameIter& iter) const
 {
     // Skip frames not yet fully initialized during their prologue.
     if (iter.isInterp() && iter.isFunctionFrame()) {
         const Value& thisVal = iter.interpFrame()->thisArgument();
         if (thisVal.isMagic() && thisVal.whyMagic() == JS_IS_CONSTRUCTING)
             return false;
     }
     if (iter.isWasm())
-        return false;
+        return observesWasm(iter.wasmInstance());
     return observesScript(iter.script());
 }
 
 bool
 Debugger::observesScript(JSScript* script) const
 {
     if (!enabled)
         return false;
     // Don't ever observe self-hosted scripts: the Debugger API can break
     // self-hosted invariants.
     return observesGlobal(&script->global()) && !script->selfHosted();
 }
 
+bool
+Debugger::observesWasm(wasm::Instance* instance) const
+{
+    if (!enabled || !instance->debugEnabled())
+        return false;
+    return observesGlobal(&instance->object()->global());
+}
+
 /* static */ bool
 Debugger::replaceFrameGuts(JSContext* cx, AbstractFramePtr from, AbstractFramePtr to,
                            ScriptFrameIter& iter)
 {
     auto removeFromDebuggerFramesOnExit = MakeScopeExit([&] {
         // Remove any remaining old entries on exit, as the 'from' frame will
         // be gone. This is only done in the failure case. On failure, the
         // removeToDebuggerFramesOnExit lambda below will rollback any frames
@@ -7312,25 +7344,25 @@ DebuggerFrame::initClass(JSContext* cx, 
     RootedObject objProto(cx, global->getOrCreateObjectPrototype(cx));
 
     return InitClass(cx, dbgCtor, objProto, &class_, construct, 0, properties_,
                      methods_, nullptr, nullptr);
 }
 
 /* static */ DebuggerFrame*
 DebuggerFrame::create(JSContext* cx, HandleObject proto, AbstractFramePtr referent,
-                      const ScriptFrameIter* maybeIter, HandleNativeObject debugger)
+                      const FrameIter* maybeIter, HandleNativeObject debugger)
 {
   JSObject* obj = NewObjectWithGivenProto(cx, &DebuggerFrame::class_, proto);
   if (!obj)
       return nullptr;
 
   DebuggerFrame& frame = obj->as<DebuggerFrame>();
 
-  // Eagerly copy ScriptFrameIter data if we've already walked the stack.
+  // Eagerly copy FrameIter data if we've already walked the stack.
   if (maybeIter) {
       AbstractFramePtr data = maybeIter->copyDataAsAbstractFramePtr();
       if (!data)
           return nullptr;
       frame.setPrivate(data.raw());
   } else {
       frame.setPrivate(referent.raw());
   }
@@ -7358,28 +7390,33 @@ DebuggerFrame::getCallee(JSContext* cx, 
     return dbg->wrapDebuggeeObject(cx, callee, result);
 }
 
 /* static */ bool
 DebuggerFrame::getIsConstructing(JSContext* cx, HandleDebuggerFrame frame, bool& result)
 {
     MOZ_ASSERT(frame->isLive());
 
-    Maybe<ScriptFrameIter> maybeIter;
-    if (!DebuggerFrame::getScriptFrameIter(cx, frame, maybeIter))
-        return false;
-    ScriptFrameIter& iter = *maybeIter;
+    Maybe<FrameIter> maybeIter;
+    if (!DebuggerFrame::getFrameIter(cx, frame, maybeIter))
+        return false;
+    FrameIter& iter = *maybeIter;
 
     result = iter.isFunctionFrame() && iter.isConstructing();
     return true;
 }
 
 static void
 UpdateFrameIterPc(FrameIter& iter)
 {
+    if (iter.abstractFramePtr().isWasmDebugFrame()) {
+        // Wasm debug frames don't need their pc updated -- it's null.
+        return;
+    }
+
     if (iter.abstractFramePtr().isRematerializedFrame()) {
 #ifdef DEBUG
         // Rematerialized frames don't need their pc updated. The reason we
         // need to update pc is because we might get the same Debugger.Frame
         // object for multiple re-entries into debugger code from debuggee
         // code. This reentrancy is not possible with rematerialized frames,
         // because when returning to debuggee code, we would have bailed out
         // to baseline.
@@ -7412,68 +7449,71 @@ UpdateFrameIterPc(FrameIter& iter)
 /* static */ bool
 DebuggerFrame::getEnvironment(JSContext* cx, HandleDebuggerFrame frame,
                               MutableHandleDebuggerEnvironment result)
 {
     MOZ_ASSERT(frame->isLive());
 
     Debugger* dbg = frame->owner();
 
-    Maybe<ScriptFrameIter> maybeIter;
-    if (!DebuggerFrame::getScriptFrameIter(cx, frame, maybeIter))
-        return false;
-    ScriptFrameIter& iter = *maybeIter;
+    Maybe<FrameIter> maybeIter;
+    if (!DebuggerFrame::getFrameIter(cx, frame, maybeIter))
+        return false;
+    FrameIter& iter = *maybeIter;
 
     Rooted<Env*> env(cx);
     {
         AutoCompartment ac(cx, iter.abstractFramePtr().environmentChain());
         UpdateFrameIterPc(iter);
         env = GetDebugEnvironmentForFrame(cx, iter.abstractFramePtr(), iter.pc());
         if (!env)
             return false;
     }
 
     return dbg->wrapEnvironment(cx, env, result);
 }
 
 /* static */ bool
 DebuggerFrame::getIsGenerator(HandleDebuggerFrame frame)
 {
-    return DebuggerFrame::getReferent(frame).script()->isGenerator();
+    AbstractFramePtr referent = DebuggerFrame::getReferent(frame);
+    return referent.hasScript() && referent.script()->isGenerator();
 }
 
 /* static */ bool
 DebuggerFrame::getOffset(JSContext* cx, HandleDebuggerFrame frame, size_t& result)
 {
     MOZ_ASSERT(frame->isLive());
-
-    Maybe<ScriptFrameIter> maybeIter;
-    if (!DebuggerFrame::getScriptFrameIter(cx, frame, maybeIter))
-        return false;
-    ScriptFrameIter& iter = *maybeIter;
+    if (!requireScriptReferent(cx, frame))
+        return false;
+
+    Maybe<FrameIter> maybeIter;
+    if (!DebuggerFrame::getFrameIter(cx, frame, maybeIter))
+        return false;
+    FrameIter& iter = *maybeIter;
 
     JSScript* script = iter.script();
     UpdateFrameIterPc(iter);
     jsbytecode* pc = iter.pc();
     result = script->pcToOffset(pc);
     return true;
 }
 
 /* static */ bool
 DebuggerFrame::getOlder(JSContext* cx, HandleDebuggerFrame frame,
                         MutableHandleDebuggerFrame result)
 {
     MOZ_ASSERT(frame->isLive());
 
     Debugger* dbg = frame->owner();
 
-    Maybe<ScriptFrameIter> maybeIter;
-    if (!DebuggerFrame::getScriptFrameIter(cx, frame, maybeIter))
-        return false;
-    ScriptFrameIter& iter = *maybeIter;
+    Maybe<FrameIter> maybeIter;
+    if (!DebuggerFrame::getFrameIter(cx, frame, maybeIter))
+        return false;
+    FrameIter& iter = *maybeIter;
 
     for (++iter; !iter.done(); ++iter) {
         if (dbg->observesFrame(iter)) {
             if (iter.isIon() && !iter.ensureHasRematerializedFrame(cx))
                 return false;
             return dbg->getScriptFrame(cx, iter, result);
         }
     }
@@ -7481,23 +7521,25 @@ DebuggerFrame::getOlder(JSContext* cx, H
     result.set(nullptr);
     return true;
 }
 
 /* static */ bool
 DebuggerFrame::getThis(JSContext* cx, HandleDebuggerFrame frame, MutableHandleValue result)
 {
     MOZ_ASSERT(frame->isLive());
+    if (!requireScriptReferent(cx, frame))
+        return false;
 
     Debugger* dbg = frame->owner();
 
-    Maybe<ScriptFrameIter> maybeIter;
-    if (!DebuggerFrame::getScriptFrameIter(cx, frame, maybeIter))
-        return false;
-    ScriptFrameIter& iter = *maybeIter;
+    Maybe<FrameIter> maybeIter;
+    if (!DebuggerFrame::getFrameIter(cx, frame, maybeIter))
+        return false;
+    FrameIter& iter = *maybeIter;
 
     {
         AbstractFramePtr frame = iter.abstractFramePtr();
         AutoCompartment ac(cx, frame.environmentChain());
 
         UpdateFrameIterPc(iter);
 
         if (!GetThisValueForDebuggerMaybeOptimizedOut(cx, frame, iter.pc(), result))
@@ -7519,42 +7561,50 @@ DebuggerFrame::getType(HandleDebuggerFra
     if (referent.isEvalFrame())
         return DebuggerFrameType::Eval;
     else if (referent.isGlobalFrame())
         return DebuggerFrameType::Global;
     else if (referent.isFunctionFrame())
         return DebuggerFrameType::Call;
     else if (referent.isModuleFrame())
         return DebuggerFrameType::Module;
+    else if (referent.isWasmDebugFrame())
+        return DebuggerFrameType::WasmCall;
     MOZ_CRASH("Unknown frame type");
 }
 
 /* static */ DebuggerFrameImplementation
 DebuggerFrame::getImplementation(HandleDebuggerFrame frame)
 {
     AbstractFramePtr referent = DebuggerFrame::getReferent(frame);
 
     if (referent.isBaselineFrame())
         return DebuggerFrameImplementation::Baseline;
     else if (referent.isRematerializedFrame())
         return DebuggerFrameImplementation::Ion;
+    else if (referent.isWasmDebugFrame())
+        return DebuggerFrameImplementation::Wasm;
     return DebuggerFrameImplementation::Interpreter;
 }
 
 /*
  * If succesful, transfers the ownership of the given `handler` to this
  * Debugger.Frame. Note that on failure, the ownership of `handler` is not
  * transferred, and the caller is responsible for cleaning it up.
  */
 /* static */ bool
 DebuggerFrame::setOnStepHandler(JSContext* cx, HandleDebuggerFrame frame, OnStepHandler* handler)
 {
     MOZ_ASSERT(frame->isLive());
 
     AbstractFramePtr referent = DebuggerFrame::getReferent(frame);
+    if (referent.isWasmDebugFrame()) {
+        MOZ_CRASH();
+        return true;
+    }
 
     OnStepHandler* prior = frame->onStepHandler();
     if (prior && handler != prior) {
         prior->drop();
     }
 
     if (handler && !prior) {
         // Single stepping toggled off->on.
@@ -7665,17 +7715,17 @@ EvaluateInEnv(JSContext* cx, Handle<Env*
 
     return ExecuteKernel(cx, script, *env, NullValue(), frame, rval.address());
 }
 
 static bool
 DebuggerGenericEval(JSContext* cx, const mozilla::Range<const char16_t> chars,
                     HandleObject bindings, const EvalOptions& options,
                     JSTrapStatus& status, MutableHandleValue value,
-                    Debugger* dbg, HandleObject envArg, ScriptFrameIter* iter)
+                    Debugger* dbg, HandleObject envArg, FrameIter* iter)
 {
     /* Either we're specifying the frame, or a global. */
     MOZ_ASSERT_IF(iter, !envArg);
     MOZ_ASSERT_IF(!iter, envArg && IsGlobalLexicalEnvironment(envArg));
 
     /*
      * Gather keys and values of bindings, if any. This must be done in the
      * debugger compartment, since that is where any exceptions must be
@@ -7756,23 +7806,25 @@ DebuggerGenericEval(JSContext* cx, const
 }
 
 /* static */ bool
 DebuggerFrame::eval(JSContext* cx, HandleDebuggerFrame frame, mozilla::Range<const char16_t> chars,
                     HandleObject bindings, const EvalOptions& options, JSTrapStatus& status,
                     MutableHandleValue value)
 {
     MOZ_ASSERT(frame->isLive());
+    if (!requireScriptReferent(cx, frame))
+        return false;
 
     Debugger* dbg = frame->owner();
 
-    Maybe<ScriptFrameIter> maybeIter;
-    if (!DebuggerFrame::getScriptFrameIter(cx, frame, maybeIter))
-        return false;
-    ScriptFrameIter& iter = *maybeIter;
+    Maybe<FrameIter> maybeIter;
+    if (!DebuggerFrame::getFrameIter(cx, frame, maybeIter))
+        return false;
+    FrameIter& iter = *maybeIter;
 
     UpdateFrameIterPc(iter);
 
     return DebuggerGenericEval(cx, chars, bindings, options, status, value, dbg, nullptr, &iter);
 }
 
 /* statuc */ bool
 DebuggerFrame::isLive() const
@@ -7819,48 +7871,62 @@ DebuggerFrame_requireLive(JSContext* cx,
     return true;
 }
 
 /* static */ AbstractFramePtr
 DebuggerFrame::getReferent(HandleDebuggerFrame frame)
 {
     AbstractFramePtr referent = AbstractFramePtr::FromRaw(frame->getPrivate());
     if (referent.isScriptFrameIterData()) {
-        ScriptFrameIter iter(*(ScriptFrameIter::Data*)(referent.raw()));
+        FrameIter iter(*(FrameIter::Data*)(referent.raw()));
         referent = iter.abstractFramePtr();
     }
     return referent;
 }
 
 /* static */ bool
-DebuggerFrame::getScriptFrameIter(JSContext* cx, HandleDebuggerFrame frame,
-                                  Maybe<ScriptFrameIter>& result)
+DebuggerFrame::getFrameIter(JSContext* cx, HandleDebuggerFrame frame,
+                            Maybe<FrameIter>& result)
 {
     AbstractFramePtr referent = AbstractFramePtr::FromRaw(frame->getPrivate());
     if (referent.isScriptFrameIterData()) {
-        result.emplace(*reinterpret_cast<ScriptFrameIter::Data*>(referent.raw()));
+        result.emplace(*reinterpret_cast<FrameIter::Data*>(referent.raw()));
     } else {
-        result.emplace(cx, ScriptFrameIter::IGNORE_DEBUGGER_EVAL_PREV_LINK);
-        ScriptFrameIter& iter = *result;
+        result.emplace(cx, FrameIter::IGNORE_DEBUGGER_EVAL_PREV_LINK);
+        FrameIter& iter = *result;
         while (!iter.hasUsableAbstractFramePtr() || iter.abstractFramePtr() != referent)
             ++iter;
         AbstractFramePtr data = iter.copyDataAsAbstractFramePtr();
         if (!data)
             return false;
         frame->setPrivate(data.raw());
     }
     return true;
 }
 
+/* static */ bool
+DebuggerFrame::requireScriptReferent(JSContext* cx, HandleDebuggerFrame frame)
+{
+    AbstractFramePtr referent = DebuggerFrame::getReferent(frame);
+    if (!referent.hasScript()) {
+        RootedValue frameobj(cx, ObjectValue(*frame));
+        ReportValueErrorFlags(cx, JSREPORT_ERROR, JSMSG_DEBUG_BAD_REFERENT,
+                              JSDVG_SEARCH_STACK, frameobj, nullptr,
+                              "a script frame", nullptr);
+        return false;
+    }
+    return true;
+}
+
 static void
 DebuggerFrame_freeScriptFrameIterData(FreeOp* fop, JSObject* obj)
 {
     AbstractFramePtr frame = AbstractFramePtr::FromRaw(obj->as<NativeObject>().getPrivate());
     if (frame.isScriptFrameIterData())
-        fop->delete_((ScriptFrameIter::Data*) frame.raw());
+        fop->delete_((FrameIter::Data*) frame.raw());
     obj->as<NativeObject>().setPrivate(nullptr);
 }
 
 static void
 DebuggerFrame_maybeDecrementFrameScriptStepModeCount(FreeOp* fop, AbstractFramePtr frame,
                                                      NativeObject* frameobj)
 {
     /* If this frame has an onStep handler, decrement the script's count. */
@@ -7925,29 +7991,29 @@ DebuggerFrame_checkThis(JSContext* cx, c
             return nullptr;
     }
 
     return frame;
 }
 
 /*
  * To make frequently fired hooks like onEnterFrame more performant,
- * Debugger.Frame methods should not create a ScriptFrameIter unless it
+ * Debugger.Frame methods should not create a FrameIter unless it
  * absolutely needs to. That is, unless the method has to call a method on
- * ScriptFrameIter that's otherwise not available on AbstractFramePtr.
+ * FrameIter that's otherwise not available on AbstractFramePtr.
  *
  * When a Debugger.Frame is first created, its private slot is set to the
  * AbstractFramePtr itself. The first time the users asks for a
- * ScriptFrameIter, we construct one, have it settle on the frame pointed to
+ * FrameIter, we construct one, have it settle on the frame pointed to
  * by the AbstractFramePtr and cache its internal Data in the Debugger.Frame
  * object's private slot. Subsequent uses of the Debugger.Frame object will
- * always create a ScriptFrameIter from the cached Data.
+ * always create a FrameIter from the cached Data.
  *
  * Methods that only need the AbstractFramePtr should use THIS_FRAME.
- * Methods that need a ScriptFrameIterator should use THIS_FRAME_ITER.
+ * Methods that need a FrameIterator should use THIS_FRAME_ITER.
  */
 
 #define THIS_DEBUGGER_FRAME(cx, argc, vp, fnname, args, frame)                          \
     CallArgs args = CallArgsFromVp(argc, vp);                                           \
     RootedDebuggerFrame frame(cx, DebuggerFrame_checkThis(cx, args, fnname, true));     \
     if (!frame)                                                                         \
         return false;
 
@@ -7956,39 +8022,39 @@ DebuggerFrame_checkThis(JSContext* cx, c
     RootedNativeObject thisobj(cx, DebuggerFrame_checkThis(cx, args, fnname, true));  \
     if (!thisobj)                                                                     \
         return false
 
 #define THIS_FRAME(cx, argc, vp, fnname, args, thisobj, frame)                 \
     THIS_FRAME_THISOBJ(cx, argc, vp, fnname, args, thisobj);                   \
     AbstractFramePtr frame = AbstractFramePtr::FromRaw(thisobj->getPrivate()); \
     if (frame.isScriptFrameIterData()) {                                       \
-        ScriptFrameIter iter(*(ScriptFrameIter::Data*)(frame.raw()));          \
+        FrameIter iter(*(FrameIter::Data*)(frame.raw()));                      \
         frame = iter.abstractFramePtr();                                       \
     }
 
 #define THIS_FRAME_ITER(cx, argc, vp, fnname, args, thisobj, maybeIter, iter)  \
     THIS_FRAME_THISOBJ(cx, argc, vp, fnname, args, thisobj);                   \
-    Maybe<ScriptFrameIter> maybeIter;                                          \
+    Maybe<FrameIter> maybeIter;                                                \
     {                                                                          \
         AbstractFramePtr f = AbstractFramePtr::FromRaw(thisobj->getPrivate()); \
         if (f.isScriptFrameIterData()) {                                       \
-            maybeIter.emplace(*(ScriptFrameIter::Data*)(f.raw()));             \
+            maybeIter.emplace(*(FrameIter::Data*)(f.raw()));                   \
         } else {                                                               \
-            maybeIter.emplace(cx, ScriptFrameIter::IGNORE_DEBUGGER_EVAL_PREV_LINK); \
-            ScriptFrameIter& iter = *maybeIter;                                \
+            maybeIter.emplace(cx, FrameIter::IGNORE_DEBUGGER_EVAL_PREV_LINK);  \
+            FrameIter& iter = *maybeIter;                                      \
             while (!iter.hasUsableAbstractFramePtr() || iter.abstractFramePtr() != f) \
                 ++iter;                                                        \
             AbstractFramePtr data = iter.copyDataAsAbstractFramePtr();         \
             if (!data)                                                         \
                 return false;                                                  \
             thisobj->setPrivate(data.raw());                                   \
         }                                                                      \
     }                                                                          \
-    ScriptFrameIter& iter = *maybeIter
+    FrameIter& iter = *maybeIter
 
 #define THIS_FRAME_OWNER(cx, argc, vp, fnname, args, thisobj, frame, dbg)      \
     THIS_FRAME(cx, argc, vp, fnname, args, thisobj, frame);                    \
     Debugger* dbg = Debugger::fromChildJSObject(thisobj)
 
 #define THIS_FRAME_OWNER_ITER(cx, argc, vp, fnname, args, thisobj, maybeIter, iter, dbg) \
     THIS_FRAME_ITER(cx, argc, vp, fnname, args, thisobj, maybeIter, iter);               \
     Debugger* dbg = Debugger::fromChildJSObject(thisobj)
@@ -8009,16 +8075,19 @@ DebuggerFrame::typeGetter(JSContext* cx,
         str = cx->names().global;
         break;
       case DebuggerFrameType::Call:
         str = cx->names().call;
         break;
       case DebuggerFrameType::Module:
         str = cx->names().module;
         break;
+      case DebuggerFrameType::WasmCall:
+        str = cx->names().wasmcall;
+        break;
       default:
         MOZ_CRASH("bad DebuggerFrameType value");
     }
 
     args.rval().setString(str);
     return true;
 }
 
@@ -8035,16 +8104,19 @@ DebuggerFrame::implementationGetter(JSCo
         s = "baseline";
         break;
       case DebuggerFrameImplementation::Ion:
         s = "ion";
         break;
       case DebuggerFrameImplementation::Interpreter:
         s = "interpreter";
         break;
+      case DebuggerFrameImplementation::Wasm:
+        s = "wasm";
+        break;
       default:
         MOZ_CRASH("bad DebuggerFrameImplementation value");
     }
 
     JSAtom* str = Atomize(cx, s, strlen(s));
     if (!str)
         return false;
 
@@ -8250,16 +8322,21 @@ DebuggerFrame_getScript(JSContext* cx, u
     if (frame.isFunctionFrame()) {
         RootedFunction callee(cx, frame.callee());
         if (callee->isInterpreted()) {
             RootedScript script(cx, callee->nonLazyScript());
             scriptObject = debug->wrapScript(cx, script);
             if (!scriptObject)
                 return false;
         }
+    } else if (frame.isWasmDebugFrame()) {
+        RootedWasmInstanceObject instance(cx, frame.wasmInstance()->object());
+        scriptObject = debug->wrapWasmScript(cx, instance);
+        if (!scriptObject)
+            return false;
     } else {
         /*
          * We got eval, JS_Evaluate*, or JS_ExecuteScript non-function script
          * frames.
          */
         RootedScript script(cx, frame.script());
         scriptObject = debug->wrapScript(cx, script);
         if (!scriptObject)
--- a/js/src/vm/Debugger.h
+++ b/js/src/vm/Debugger.h
@@ -298,17 +298,17 @@ class Debugger : private mozilla::Linked
       public:
         typedef HashSet<Zone*>::Range ZoneRange;
 
         virtual Zone* singleZone() const { return nullptr; }
         virtual JSScript* singleScriptForZoneInvalidation() const { return nullptr; }
         virtual const HashSet<Zone*>* zones() const { return nullptr; }
 
         virtual bool shouldRecompileOrInvalidate(JSScript* script) const = 0;
-        virtual bool shouldMarkAsDebuggee(ScriptFrameIter& iter) const = 0;
+        virtual bool shouldMarkAsDebuggee(FrameIter& iter) const = 0;
     };
 
     // This enum is converted to and compare with bool values; NotObserving
     // must be 0 and Observing must be 1.
     enum IsObserving {
         NotObserving = 0,
         Observing = 1
     };
@@ -770,20 +770,20 @@ class Debugger : private mozilla::Linked
     void fireOnGarbageCollectionHook(JSContext* cx,
                                      const JS::dbg::GarbageCollectionEvent::Ptr& gcData);
 
     /*
      * Gets a Debugger.Frame object. If maybeIter is non-null, we eagerly copy
      * its data if we need to make a new Debugger.Frame.
      */
     MOZ_MUST_USE bool getScriptFrameWithIter(JSContext* cx, AbstractFramePtr frame,
-                                             const ScriptFrameIter* maybeIter,
+                                             const FrameIter* maybeIter,
                                              MutableHandleValue vp);
     MOZ_MUST_USE bool getScriptFrameWithIter(JSContext* cx, AbstractFramePtr frame,
-                                             const ScriptFrameIter* maybeIter,
+                                             const FrameIter* maybeIter,
                                              MutableHandleDebuggerFrame result);
 
     inline Breakpoint* firstBreakpoint() const;
 
     static inline Debugger* fromOnNewGlobalObjectWatchersLink(JSCList* link);
 
     static MOZ_MUST_USE bool replaceFrameGuts(JSContext* cx, AbstractFramePtr from,
                                               AbstractFramePtr to,
@@ -912,16 +912,17 @@ class Debugger : private mozilla::Linked
 
     inline bool observesEnterFrame() const;
     inline bool observesNewScript() const;
     inline bool observesNewGlobalObject() const;
     inline bool observesGlobal(GlobalObject* global) const;
     bool observesFrame(AbstractFramePtr frame) const;
     bool observesFrame(const FrameIter& iter) const;
     bool observesScript(JSScript* script) const;
+    bool observesWasm(wasm::Instance* instance) const;
 
     /*
      * If env is nullptr, call vp->setNull() and return true. Otherwise, find
      * or create a Debugger.Environment object for the given Env. On success,
      * store the Environment object in *vp and return true.
      */
     MOZ_MUST_USE bool wrapEnvironment(JSContext* cx, Handle<Env*> env, MutableHandleValue vp);
     MOZ_MUST_USE bool wrapEnvironment(JSContext* cx, Handle<Env*> env,
@@ -996,21 +997,21 @@ class Debugger : private mozilla::Linked
     /*
      * Store the Debugger.Frame object for iter in *vp/result. Eagerly copies a
      * ScriptFrameIter::Data.
      *
      * Use this if you had to make a ScriptFrameIter to get the required
      * frame, in which case the cost of walking the stack has already been
      * paid.
      */
-    MOZ_MUST_USE bool getScriptFrame(JSContext* cx, const ScriptFrameIter& iter,
+    MOZ_MUST_USE bool getScriptFrame(JSContext* cx, const FrameIter& iter,
                                      MutableHandleValue vp) {
         return getScriptFrameWithIter(cx, iter.abstractFramePtr(), &iter, vp);
     }
-    MOZ_MUST_USE bool getScriptFrame(JSContext* cx, const ScriptFrameIter& iter,
+    MOZ_MUST_USE bool getScriptFrame(JSContext* cx, const FrameIter& iter,
                                      MutableHandleDebuggerFrame result);
 
 
     /*
      * Set |*status| and |*value| to a (JSTrapStatus, Value) pair reflecting a
      * standard SpiderMonkey call state: a boolean success value |ok|, a return
      * value |rv|, and a context |cx| that may or may not have an exception set.
      * If an exception was pending on |cx|, it is cleared (and |ok| is asserted
@@ -1145,23 +1146,25 @@ class DebuggerEnvironment : public Nativ
     static MOZ_MUST_USE bool getVariableMethod(JSContext* cx, unsigned argc, Value* vp);
     static MOZ_MUST_USE bool setVariableMethod(JSContext* cx, unsigned argc, Value* vp);
 };
 
 enum class DebuggerFrameType {
     Eval,
     Global,
     Call,
-    Module
+    Module,
+    WasmCall
 };
 
 enum class DebuggerFrameImplementation {
     Interpreter,
     Baseline,
-    Ion
+    Ion,
+    Wasm
 };
 
 /*
  * A Handler represents a reference to a handler function. These handler
  * functions are called by the Debugger API to notify the user of certain
  * events. For each event type, we define a separate subclass of Handler. This
  * allows users to define a single reference to an object that implements
  * multiple handlers, by inheriting from the appropriate subclasses.
@@ -1279,17 +1282,17 @@ class DebuggerFrame : public NativeObjec
     };
 
     static const unsigned RESERVED_SLOTS = 1;
 
     static const Class class_;
 
     static NativeObject* initClass(JSContext* cx, HandleObject dbgCtor, HandleObject objProto);
     static DebuggerFrame* create(JSContext* cx, HandleObject proto, AbstractFramePtr referent,
-                                 const ScriptFrameIter* maybeIter, HandleNativeObject debugger);
+                                 const FrameIter* maybeIter, HandleNativeObject debugger);
 
     static MOZ_MUST_USE bool getArguments(JSContext* cx, HandleDebuggerFrame frame,
                                           MutableHandleDebuggerArguments result);
     static MOZ_MUST_USE bool getCallee(JSContext* cx, HandleDebuggerFrame frame,
                                        MutableHandleDebuggerObject result);
     static MOZ_MUST_USE bool getIsConstructing(JSContext* cx, HandleDebuggerFrame frame,
                                                bool& result);
     static MOZ_MUST_USE bool getEnvironment(JSContext* cx, HandleDebuggerFrame frame,
@@ -1317,18 +1320,19 @@ class DebuggerFrame : public NativeObjec
 
   private:
     static const ClassOps classOps_;
 
     static const JSPropertySpec properties_[];
     static const JSFunctionSpec methods_[];
 
     static AbstractFramePtr getReferent(HandleDebuggerFrame frame);
-    static MOZ_MUST_USE bool getScriptFrameIter(JSContext* cx, HandleDebuggerFrame frame,
-                                                mozilla::Maybe<ScriptFrameIter>& result);
+    static MOZ_MUST_USE bool getFrameIter(JSContext* cx, HandleDebuggerFrame frame,
+                                          mozilla::Maybe<FrameIter>& result);
+    static MOZ_MUST_USE bool requireScriptReferent(JSContext* cx, HandleDebuggerFrame frame);
 
     static MOZ_MUST_USE bool construct(JSContext* cx, unsigned argc, Value* vp);
 
     static MOZ_MUST_USE bool argumentsGetter(JSContext* cx, unsigned argc, Value* vp);
     static MOZ_MUST_USE bool calleeGetter(JSContext* cx, unsigned argc, Value* vp);
     static MOZ_MUST_USE bool constructingGetter(JSContext* cx, unsigned argc, Value* vp);
     static MOZ_MUST_USE bool environmentGetter(JSContext* cx, unsigned argc, Value* vp);
     static MOZ_MUST_USE bool generatorGetter(JSContext* cx, unsigned argc, Value* vp);
--- a/js/src/vm/EnvironmentObject.cpp
+++ b/js/src/vm/EnvironmentObject.cpp
@@ -17,16 +17,17 @@
 #include "frontend/ParseNode.h"
 #include "gc/Policy.h"
 #include "vm/ArgumentsObject.h"
 #include "vm/AsyncFunction.h"
 #include "vm/GlobalObject.h"
 #include "vm/ProxyObject.h"
 #include "vm/Shape.h"
 #include "vm/Xdr.h"
+#include "wasm/WasmInstance.h"
 
 #include "jsatominlines.h"
 #include "jsobjinlines.h"
 #include "jsscriptinlines.h"
 
 #include "vm/Stack-inl.h"
 
 using namespace js;
@@ -623,16 +624,47 @@ ModuleEnvironmentObject::enumerate(JSCon
         properties.infallibleAppend(r.front().propid());
 
     MOZ_ASSERT(properties.length() == count);
     return true;
 }
 
 /*****************************************************************************/
 
+const Class WasmFunctionCallObject::class_ = {
+    "WasmCall",
+    JSCLASS_IS_ANONYMOUS | JSCLASS_HAS_RESERVED_SLOTS(WasmFunctionCallObject::RESERVED_SLOTS)
+};
+
+/* static */ WasmFunctionCallObject*
+WasmFunctionCallObject::createHollowForDebug(JSContext* cx, WasmFunctionScope* scope)
+{
+    RootedObjectGroup group(cx, ObjectGroup::defaultNewGroup(cx, &class_, TaggedProto(nullptr)));
+    if (!group)
+        return nullptr;
+
+    RootedShape shape(cx, scope->getEmptyEnvironmentShape(cx));
+    if (!shape)
+        return nullptr;
+
+    gc::AllocKind kind = gc::GetGCObjectKind(shape->numFixedSlots());
+    MOZ_ASSERT(CanBeFinalizedInBackground(kind, &class_));
+    kind = gc::GetBackgroundAllocKind(kind);
+
+    JSObject* obj;
+    JS_TRY_VAR_OR_RETURN_NULL(cx, obj, JSObject::create(cx, kind, gc::DefaultHeap, shape, group));
+
+    Rooted<WasmFunctionCallObject*> callobj(cx, &obj->as<WasmFunctionCallObject>());
+    callobj->initEnclosingEnvironment(&cx->global()->lexicalEnvironment());
+
+    return callobj;
+}
+
+/*****************************************************************************/
+
 WithEnvironmentObject*
 WithEnvironmentObject::create(JSContext* cx, HandleObject object, HandleObject enclosing,
                               Handle<WithScope*> scope)
 {
     Rooted<WithEnvironmentObject*> obj(cx);
     obj = NewObjectWithNullTaggedProto<WithEnvironmentObject>(cx, GenericObject,
                                                               BaseShape::DELEGATE);
     if (!obj)
@@ -1197,16 +1229,27 @@ EnvironmentIter::EnvironmentIter(JSConte
     env_(cx, frame.environmentChain()),
     frame_(frame)
 {
     assertSameCompartment(cx, frame);
     settle();
     MOZ_GUARD_OBJECT_NOTIFIER_INIT;
 }
 
+EnvironmentIter::EnvironmentIter(JSContext* cx, JSObject* env, Scope* scope, AbstractFramePtr frame
+                                 MOZ_GUARD_OBJECT_NOTIFIER_PARAM_IN_IMPL)
+  : si_(cx, ScopeIter(scope)),
+    env_(cx, env),
+    frame_(frame)
+{
+    assertSameCompartment(cx, frame);
+    settle();
+    MOZ_GUARD_OBJECT_NOTIFIER_INIT;
+}
+
 void
 EnvironmentIter::incrementScopeIter()
 {
     if (si_.scope()->is<GlobalScope>()) {
         // GlobalScopes may be syntactic or non-syntactic. Non-syntactic
         // GlobalScopes correspond to zero or more non-syntactic
         // EnvironmentsObjects followed by the global lexical scope, then the
         // GlobalObject or another non-EnvironmentObject object.
@@ -1217,35 +1260,40 @@ EnvironmentIter::incrementScopeIter()
     }
 }
 
 void
 EnvironmentIter::settle()
 {
     // Check for trying to iterate a function or eval frame before the prologue has
     // created the CallObject, in which case we have to skip.
-    if (frame_ && frame_.script()->initialEnvironmentShape() && !frame_.hasInitialEnvironment()) {
+    if (frame_ && frame_.hasScript() &&
+        frame_.script()->initialEnvironmentShape() && !frame_.hasInitialEnvironment())
+    {
         // Skip until we're at the enclosing scope of the script.
         while (si_.scope() != frame_.script()->enclosingScope()) {
             if (env_->is<LexicalEnvironmentObject>() &&
                 !env_->as<LexicalEnvironmentObject>().isExtensible() &&
                 &env_->as<LexicalEnvironmentObject>().scope() == si_.scope())
             {
                 MOZ_ASSERT(si_.kind() == ScopeKind::NamedLambda ||
                            si_.kind() == ScopeKind::StrictNamedLambda);
                 env_ = &env_->as<EnvironmentObject>().enclosingEnvironment();
             }
             incrementScopeIter();
         }
     }
 
     // Check if we have left the extent of the initial frame after we've
     // settled on a static scope.
-    if (frame_ && (!si_ || si_.scope() == frame_.script()->enclosingScope()))
+    if (frame_ && (frame_.isWasmDebugFrame() ||
+                   (!si_ || si_.scope() == frame_.script()->enclosingScope())))
+    {
         frame_ = NullFramePtr();
+    }
 
 #ifdef DEBUG
     if (si_) {
         if (hasSyntacticEnvironment()) {
             Scope* scope = si_.scope();
             if (scope->is<LexicalScope>()) {
                 MOZ_ASSERT(scope == &env_->as<LexicalEnvironmentObject>().scope());
             } else if (scope->is<FunctionScope>()) {
@@ -2221,16 +2269,17 @@ DebugEnvironmentProxy::initSnapshot(Arra
 
 bool
 DebugEnvironmentProxy::isForDeclarative() const
 {
     EnvironmentObject& e = environment();
     return e.is<CallObject>() ||
            e.is<VarEnvironmentObject>() ||
            e.is<ModuleEnvironmentObject>() ||
+           e.is<WasmFunctionCallObject>() ||
            e.is<LexicalEnvironmentObject>();
 }
 
 bool
 DebugEnvironmentProxy::getMaybeSentinelValue(JSContext* cx, HandleId id, MutableHandleValue vp)
 {
     Rooted<DebugEnvironmentProxy*> self(cx, this);
     return DebugEnvironmentProxyHandler::singleton.getMaybeSentinelValue(cx, self, id, vp);
@@ -2725,17 +2774,22 @@ DebugEnvironments::updateLiveEnvironment
             continue;
 
         if (frame.isFunctionFrame() && frame.callee()->isGenerator())
             continue;
 
         if (!frame.isDebuggee())
             continue;
 
-        for (EnvironmentIter ei(cx, frame, i.pc()); ei.withinInitialFrame(); ei++) {
+        RootedObject env(cx);
+        RootedScope scope(cx);
+        if (!GetFrameEnvironmentAndScope(cx, frame, i.pc(), &env, &scope))
+            return false;
+
+        for (EnvironmentIter ei(cx, env, scope, frame); ei.withinInitialFrame(); ei++) {
             if (ei.hasSyntacticEnvironment() && !ei.scope().is<GlobalScope>()) {
                 MOZ_ASSERT(ei.environment().compartment() == cx->compartment());
                 DebugEnvironments* envs = ensureCompartmentData(cx);
                 if (!envs)
                     return false;
                 if (!envs->liveEnvs.put(&ei.environment(), LiveEnvironmentVal(ei)))
                     return false;
             }
@@ -2849,16 +2903,17 @@ GetDebugEnvironmentForEnvironmentObject(
 }
 
 static DebugEnvironmentProxy*
 GetDebugEnvironmentForMissing(JSContext* cx, const EnvironmentIter& ei)
 {
     MOZ_ASSERT(!ei.hasSyntacticEnvironment() &&
                (ei.scope().is<FunctionScope>() ||
                 ei.scope().is<LexicalScope>() ||
+                ei.scope().is<WasmFunctionScope>() ||
                 ei.scope().is<VarScope>()));
 
     if (DebugEnvironmentProxy* debugEnv = DebugEnvironments::hasDebugEnvironment(cx, ei))
         return debugEnv;
 
     EnvironmentIter copy(cx, ei);
     RootedObject enclosingDebug(cx, GetDebugEnvironment(cx, ++copy));
     if (!enclosingDebug)
@@ -2891,16 +2946,23 @@ GetDebugEnvironmentForMissing(JSContext*
     } else if (ei.scope().is<LexicalScope>()) {
         Rooted<LexicalScope*> lexicalScope(cx, &ei.scope().as<LexicalScope>());
         Rooted<LexicalEnvironmentObject*> env(cx,
             LexicalEnvironmentObject::createHollowForDebug(cx, lexicalScope));
         if (!env)
             return nullptr;
 
         debugEnv = DebugEnvironmentProxy::create(cx, *env, enclosingDebug);
+    } else if (ei.scope().is<WasmFunctionScope>()) {
+        Rooted<WasmFunctionScope*> wasmFunctionScope(cx, &ei.scope().as<WasmFunctionScope>());
+        Rooted<WasmFunctionCallObject*> callobj(cx, WasmFunctionCallObject::createHollowForDebug(cx, wasmFunctionScope));
+        if (!callobj)
+            return nullptr;
+
+        debugEnv = DebugEnvironmentProxy::create(cx, *callobj, enclosingDebug);
     } else {
         Rooted<VarScope*> varScope(cx, &ei.scope().as<VarScope>());
         Rooted<VarEnvironmentObject*> env(cx,
             VarEnvironmentObject::createHollowForDebug(cx, varScope));
         if (!env)
             return nullptr;
 
         debugEnv = DebugEnvironmentProxy::create(cx, *env, enclosingDebug);
@@ -2935,16 +2997,17 @@ GetDebugEnvironment(JSContext* cx, const
     if (ei.done())
         return GetDebugEnvironmentForNonEnvironmentObject(ei);
 
     if (ei.hasAnyEnvironmentObject())
         return GetDebugEnvironmentForEnvironmentObject(cx, ei);
 
     if (ei.scope().is<FunctionScope>() ||
         ei.scope().is<LexicalScope>() ||
+        ei.scope().is<WasmFunctionScope>() ||
         ei.scope().is<VarScope>())
     {
         return GetDebugEnvironmentForMissing(cx, ei);
     }
 
     EnvironmentIter copy(cx, ei);
     return GetDebugEnvironment(cx, ++copy);
 }
@@ -2965,17 +3028,22 @@ js::GetDebugEnvironmentForFunction(JSCon
 
 JSObject*
 js::GetDebugEnvironmentForFrame(JSContext* cx, AbstractFramePtr frame, jsbytecode* pc)
 {
     assertSameCompartment(cx, frame);
     if (CanUseDebugEnvironmentMaps(cx) && !DebugEnvironments::updateLiveEnvironments(cx))
         return nullptr;
 
-    EnvironmentIter ei(cx, frame, pc);
+    RootedObject env(cx);
+    RootedScope scope(cx);
+    if (!GetFrameEnvironmentAndScope(cx, frame, pc, &env, &scope))
+        return nullptr;
+
+    EnvironmentIter ei(cx, env, scope, frame);
     return GetDebugEnvironment(cx, ei);
 }
 
 JSObject*
 js::GetDebugEnvironmentForGlobalLexicalEnvironment(JSContext* cx)
 {
     EnvironmentIter ei(cx, &cx->global()->lexicalEnvironment(), &cx->global()->emptyGlobalScope());
     return GetDebugEnvironment(cx, ei);
@@ -3060,17 +3128,22 @@ js::GetModuleEnvironmentForScript(JSScri
     }
     return nullptr;
 }
 
 bool
 js::GetThisValueForDebuggerMaybeOptimizedOut(JSContext* cx, AbstractFramePtr frame, jsbytecode* pc,
                                              MutableHandleValue res)
 {
-    for (EnvironmentIter ei(cx, frame, pc); ei; ei++) {
+    RootedObject scopeChain(cx);
+    RootedScope scope(cx);
+    if (!GetFrameEnvironmentAndScope(cx, frame, pc, &scopeChain, &scope))
+        return false;
+
+    for (EnvironmentIter ei(cx, scopeChain, scope, frame); ei; ei++) {
         if (ei.scope().kind() == ScopeKind::Module) {
             res.setUndefined();
             return true;
         }
 
         if (!ei.scope().is<FunctionScope>() ||
             ei.scope().as<FunctionScope>().canonicalFunction()->hasLexicalThis())
         {
@@ -3134,17 +3207,16 @@ js::GetThisValueForDebuggerMaybeOptimize
                 res.setMagic(JS_OPTIMIZED_OUT);
 
             return true;
         }
 
         MOZ_CRASH("'this' binding must be found");
     }
 
-    RootedObject scopeChain(cx, frame.environmentChain());
     return GetNonSyntacticGlobalThis(cx, scopeChain, res);
 }
 
 bool
 js::CheckLexicalNameConflict(JSContext* cx, Handle<LexicalEnvironmentObject*> lexicalEnv,
                              HandleObject varObj, HandlePropertyName name)
 {
     const char* redeclKind = nullptr;
@@ -3403,16 +3475,35 @@ js::PushVarEnvironmentObject(JSContext* 
 {
     VarEnvironmentObject* env = VarEnvironmentObject::create(cx, scope, frame);
     if (!env)
         return false;
     frame.pushOnEnvironmentChain(*env);
     return true;
 }
 
+bool
+js::GetFrameEnvironmentAndScope(JSContext* cx, AbstractFramePtr frame, jsbytecode* pc,
+                                MutableHandleObject env, MutableHandleScope scope)
+{
+    env.set(frame.environmentChain());
+
+    if (frame.isWasmDebugFrame()) {
+        RootedWasmInstanceObject instance(cx, frame.wasmInstance()->object());
+        uint32_t funcIndex = frame.asWasmDebugFrame()->funcIndex();
+        scope.set(WasmInstanceObject::getFunctionScope(cx, instance, funcIndex));
+        if (!scope)
+            return false;
+    } else {
+        scope.set(frame.script()->innermostScope(pc));
+    }
+    return true;
+}
+
+
 #ifdef DEBUG
 
 typedef HashSet<PropertyName*> PropertyNameSet;
 
 static bool
 RemoveReferencedNames(JSContext* cx, HandleScript script, PropertyNameSet& remainingNames)
 {
     // Remove from remainingNames --- the closure variables in some outer
--- a/js/src/vm/EnvironmentObject.h
+++ b/js/src/vm/EnvironmentObject.h
@@ -421,16 +421,27 @@ class ModuleEnvironmentObject : public E
     static bool enumerate(JSContext* cx, HandleObject obj, AutoIdVector& properties,
                           bool enumerableOnly);
 };
 
 typedef Rooted<ModuleEnvironmentObject*> RootedModuleEnvironmentObject;
 typedef Handle<ModuleEnvironmentObject*> HandleModuleEnvironmentObject;
 typedef MutableHandle<ModuleEnvironmentObject*> MutableHandleModuleEnvironmentObject;
 
+class WasmFunctionCallObject : public EnvironmentObject
+{
+  public:
+    static const Class class_;
+
+    static const uint32_t RESERVED_SLOTS = 1;
+
+    static WasmFunctionCallObject* createHollowForDebug(JSContext* cx,
+                                                        WasmFunctionScope* scope);
+};
+
 class LexicalEnvironmentObject : public EnvironmentObject
 {
     // Global and non-syntactic lexical environments need to store a 'this'
     // value and all other lexical environments have a fixed shape and store a
     // backpointer to the LexicalScope.
     //
     // Since the two sets are disjoint, we only use one slot to save space.
     static const unsigned THIS_VALUE_OR_SCOPE_SLOT = 1;
@@ -656,16 +667,22 @@ class MOZ_RAII EnvironmentIter
     EnvironmentIter(JSContext* cx, JSObject* env, Scope* scope
                     MOZ_GUARD_OBJECT_NOTIFIER_PARAM);
 
     // Constructing from a frame. Places the EnvironmentIter on the innermost
     // environment at pc.
     EnvironmentIter(JSContext* cx, AbstractFramePtr frame, jsbytecode* pc
                     MOZ_GUARD_OBJECT_NOTIFIER_PARAM);
 
+    // Constructing from an environment, scope and frame. The frame is given
+    // to initialize to proper enclosing environment/scope.
+    EnvironmentIter(JSContext* cx, JSObject* env, Scope* scope, AbstractFramePtr frame
+                    MOZ_GUARD_OBJECT_NOTIFIER_PARAM);
+
+
     bool done() const {
         return si_.done();
     }
 
     explicit operator bool() const {
         return !done();
     }
 
@@ -979,16 +996,17 @@ class DebugEnvironments
 
 template <>
 inline bool
 JSObject::is<js::EnvironmentObject>() const
 {
     return is<js::CallObject>() ||
            is<js::VarEnvironmentObject>() ||
            is<js::ModuleEnvironmentObject>() ||
+           is<js::WasmFunctionCallObject>() ||
            is<js::LexicalEnvironmentObject>() ||
            is<js::WithEnvironmentObject>() ||
            is<js::NonSyntacticVariablesObject>() ||
            is<js::RuntimeLexicalErrorObject>();
 }
 
 template<>
 bool
@@ -1103,16 +1121,20 @@ CheckEvalDeclarationConflicts(JSContext*
                               HandleObject varObj);
 
 MOZ_MUST_USE bool
 InitFunctionEnvironmentObjects(JSContext* cx, AbstractFramePtr frame);
 
 MOZ_MUST_USE bool
 PushVarEnvironmentObject(JSContext* cx, HandleScope scope, AbstractFramePtr frame);
 
+MOZ_MUST_USE bool
+GetFrameEnvironmentAndScope(JSContext* cx, AbstractFramePtr frame, jsbytecode* pc,
+                            MutableHandleObject env, MutableHandleScope scope);
+
 #ifdef DEBUG
 bool
 AnalyzeEntrainedVariables(JSContext* cx, HandleScript script);
 #endif
 
 } // namespace js
 
 namespace JS {
--- a/js/src/vm/Interpreter.cpp
+++ b/js/src/vm/Interpreter.cpp
@@ -1015,16 +1015,19 @@ PopEnvironment(JSContext* cx, Environmen
         if (ei.scope().hasEnvironment())
             ei.initialFrame().popOffEnvironmentChain<VarEnvironmentObject>();
         break;
       case ScopeKind::Eval:
       case ScopeKind::Global:
       case ScopeKind::NonSyntactic:
       case ScopeKind::Module:
         break;
+      case ScopeKind::WasmFunction:
+        MOZ_CRASH("wasm is not interpreted");
+        break;
     }
 }
 
 // Unwind environment chain and iterator to match the env corresponding to
 // the given bytecode position.
 void
 js::UnwindEnvironment(JSContext* cx, EnvironmentIter& ei, jsbytecode* pc)
 {
--- a/js/src/vm/SavedStacks.cpp
+++ b/js/src/vm/SavedStacks.cpp
@@ -152,18 +152,18 @@ struct SavedFrame::Lookup {
         asyncCause(asyncCause),
         parent(parent),
         principals(principals),
         framePtr(framePtr),
         pc(pc),
         activation(activation)
     {
         MOZ_ASSERT(source);
-        MOZ_ASSERT_IF(framePtr.isSome(), pc);
         MOZ_ASSERT_IF(framePtr.isSome(), activation);
+        MOZ_ASSERT_IF(framePtr.isSome() && !activation->isWasm(), pc);
 
 #ifdef JS_MORE_DETERMINISTIC
         column = 0;
 #endif
     }
 
     explicit Lookup(SavedFrame& savedFrame)
       : source(savedFrame.getSource()),
@@ -1319,17 +1319,17 @@ SavedStacks::insertFrames(JSContext* cx,
         }
 
         // The bit set means that the next older parent (frame, pc) pair *must*
         // be in the cache.
         if (capture.is<JS::AllFrames>())
             parentIsInCache = iter.hasCachedSavedFrame();
 
         auto principals = iter.compartment()->principals();
-        auto displayAtom = iter.isFunctionFrame() ? iter.functionDisplayAtom() : nullptr;
+        auto displayAtom = (iter.isWasm() || iter.isFunctionFrame()) ? iter.functionDisplayAtom() : nullptr;
         if (!stackChain->emplaceBack(location.source(),
                                      location.line(),
                                      location.column(),
                                      displayAtom,
                                      nullptr,
                                      nullptr,
                                      principals,
                                      LiveSavedFrameCache::getFramePtr(iter),
--- a/js/src/vm/Scope.cpp
+++ b/js/src/vm/Scope.cpp
@@ -70,16 +70,18 @@ js::ScopeKindString(ScopeKind kind)
       case ScopeKind::StrictEval:
         return "strict eval";
       case ScopeKind::Global:
         return "global";
       case ScopeKind::NonSyntactic:
         return "non-syntactic";
       case ScopeKind::Module:
         return "module";
+      case ScopeKind::WasmFunction:
+        return "wasm function";
     }
     MOZ_CRASH("Bad ScopeKind");
 }
 
 static Shape*
 EmptyEnvironmentShape(ExclusiveContext* cx, const Class* cls, uint32_t numSlots,
                       uint32_t baseShapeFlags)
 {
@@ -374,19 +376,24 @@ Scope::clone(JSContext* cx, HandleScope 
         return create(cx, scope->kind_, enclosing, envShape, Move(dataClone));
       }
 
       case ScopeKind::Global:
       case ScopeKind::NonSyntactic:
         MOZ_CRASH("Use GlobalScope::clone.");
         break;
 
+      case ScopeKind::WasmFunction:
+        MOZ_CRASH("wasm functions are not nested in JSScript");
+        break;
+
       case ScopeKind::Module:
         MOZ_CRASH("NYI");
         break;
+
     }
 
     return nullptr;
 }
 
 void
 Scope::finalize(FreeOp* fop)
 {
@@ -458,16 +465,19 @@ LexicalScope::nextFrameSlot(Scope* scope
           case ScopeKind::Eval:
           case ScopeKind::StrictEval:
             return si.scope()->as<EvalScope>().nextFrameSlot();
           case ScopeKind::Global:
           case ScopeKind::NonSyntactic:
             return 0;
           case ScopeKind::Module:
             return si.scope()->as<ModuleScope>().nextFrameSlot();
+          case ScopeKind::WasmFunction:
+            // TODO return si.scope()->as<WasmFunctionScope>().nextFrameSlot();
+            return 0;
         }
     }
     MOZ_CRASH("Not an enclosing intra-frame Scope");
 }
 
 /* static */ LexicalScope*
 LexicalScope::create(ExclusiveContext* cx, ScopeKind kind, Handle<Data*> data,
                      uint32_t firstFrameSlot, HandleScope enclosing)
@@ -1137,16 +1147,58 @@ ModuleScope::getEmptyEnvironmentShape(Ex
 }
 
 JSScript*
 ModuleScope::script() const
 {
     return module()->script();
 }
 
+// TODO Check what Debugger behavior should be when it evaluates a
+// var declaration.
+static const uint32_t WasmFunctionEnvShapeFlags =
+    BaseShape::NOT_EXTENSIBLE | BaseShape::DELEGATE;
+
+/* static */ WasmFunctionScope*
+WasmFunctionScope::create(JSContext* cx, WasmInstanceObject* instance, uint32_t funcIndex)
+{
+    // WasmFunctionScope::Data has GCManagedDeletePolicy because it contains a
+    // GCPtr. Destruction of |data| below may trigger calls into the GC.
+    Rooted<WasmFunctionScope*> wasmFunctionScope(cx);
+
+    {
+        // TODO pull the local variable names from the wasm function definition.
+
+        Rooted<UniquePtr<Data>> data(cx, NewEmptyScopeData<WasmFunctionScope>(cx));
+        if (!data)
+            return nullptr;
+
+        Rooted<Scope*> enclosingScope(cx, &cx->global()->emptyGlobalScope());
+
+        data->instance.init(instance);
+        data->funcIndex = funcIndex;
+
+        Scope* scope = Scope::create(cx, ScopeKind::WasmFunction, enclosingScope, /* envShape = */ nullptr);
+        if (!scope)
+            return nullptr;
+
+        wasmFunctionScope = &scope->as<WasmFunctionScope>();
+        wasmFunctionScope->initData(Move(data.get()));
+    }
+
+    return wasmFunctionScope;
+}
+
+/* static */ Shape*
+WasmFunctionScope::getEmptyEnvironmentShape(ExclusiveContext* cx)
+{
+    const Class* cls = &WasmFunctionCallObject::class_;
+    return EmptyEnvironmentShape(cx, cls, JSSLOT_FREE(cls), WasmFunctionEnvShapeFlags);
+}
+
 ScopeIter::ScopeIter(JSScript* script)
   : scope_(script->bodyScope())
 { }
 
 bool
 ScopeIter::hasSyntacticEnvironment() const
 {
     return scope()->hasEnvironment() && scope()->kind() != ScopeKind::NonSyntactic;
@@ -1188,16 +1240,19 @@ BindingIter::BindingIter(Scope* scope)
         break;
       case ScopeKind::Global:
       case ScopeKind::NonSyntactic:
         init(scope->as<GlobalScope>().data());
         break;
       case ScopeKind::Module:
         init(scope->as<ModuleScope>().data());
         break;
+      case ScopeKind::WasmFunction:
+        init(scope->as<WasmFunctionScope>().data());
+        break;
     }
 }
 
 BindingIter::BindingIter(JSScript* script)
   : BindingIter(script->bodyScope())
 { }
 
 void
@@ -1318,16 +1373,32 @@ BindingIter::init(ModuleScope::Data& dat
     //               lets - [data.letStart, data.constStart)
     //             consts - [data.constStart, data.length)
     init(data.varStart, data.varStart, data.varStart, data.varStart, data.letStart, data.constStart,
          CanHaveFrameSlots | CanHaveEnvironmentSlots,
          0, JSSLOT_FREE(&ModuleEnvironmentObject::class_),
          data.names, data.length);
 }
 
+void
+BindingIter::init(WasmFunctionScope::Data& data)
+{
+    //            imports - [0, 0)
+    // positional formals - [0, 0)
+    //      other formals - [0, 0)
+    //    top-level funcs - [0, 0)
+    //               vars - [0, 0)
+    //               lets - [0, 0)
+    //             consts - [0, 0)
+    init(0, 0, 0, 0, 0, 0,
+         CanHaveFrameSlots | CanHaveEnvironmentSlots,
+         UINT32_MAX, UINT32_MAX,
+         data.names, data.length);
+}
+
 PositionalFormalParameterIter::PositionalFormalParameterIter(JSScript* script)
   : BindingIter(script)
 {
     // Reinit with flags = 0, i.e., iterate over all positional parameters.
     if (script->bodyScope()->is<FunctionScope>())
         init(script->bodyScope()->as<FunctionScope>().data(), /* flags = */ 0);
     settle();
 }
--- a/js/src/vm/Scope.h
+++ b/js/src/vm/Scope.h
@@ -66,17 +66,20 @@ enum class ScopeKind : uint8_t
     Eval,
     StrictEval,
 
     // GlobalScope
     Global,
     NonSyntactic,
 
     // ModuleScope
-    Module
+    Module,
+
+    // WasmFunctionScope
+    WasmFunction
 };
 
 static inline bool
 ScopeKindIsCatch(ScopeKind kind)
 {
     return kind == ScopeKind::SimpleCatch || kind == ScopeKind::Catch;
 }
 
@@ -888,16 +891,67 @@ class ModuleScope : public Scope
         return data().module;
     }
 
     JSScript* script() const;
 
     static Shape* getEmptyEnvironmentShape(ExclusiveContext* cx);
 };
 
+// Scope corresponding to the wasm function. A WasmFunctionScope is used by
+// Debugger only, and not for wasm execution.
+//
+class WasmFunctionScope : public Scope
+{
+    friend class BindingIter;
+    friend class Scope;
+    static const ScopeKind classScopeKind_ = ScopeKind::WasmFunction;
+
+  public:
+    struct Data
+    {
+        uint32_t length;
+        uint32_t nextFrameSlot;
+        uint32_t funcIndex;
+
+        // The wasm instance of the scope.
+        GCPtr<WasmInstanceObject*> instance;
+
+        BindingName names[1];
+
+        void trace(JSTracer* trc);
+    };
+
+    static WasmFunctionScope* create(JSContext* cx, WasmInstanceObject* instance, uint32_t funcIndex);
+
+    static size_t sizeOfData(uint32_t length) {
+        return sizeof(Data) + (length ? length - 1 : 0) * sizeof(BindingName);
+    }
+
+  private:
+    Data& data() {
+        return *reinterpret_cast<Data*>(data_);
+    }
+
+    const Data& data() const {
+        return *reinterpret_cast<Data*>(data_);
+    }
+
+  public:
+    WasmInstanceObject* instance() const {
+        return data().instance;
+    }
+
+    uint32_t funcIndex() const {
+        return data().funcIndex;
+    }
+
+    static Shape* getEmptyEnvironmentShape(ExclusiveContext* cx);
+};
+
 //
 // An iterator for a Scope's bindings. This is the source of truth for frame
 // and environment object layout.
 //
 // It may be placed in GC containers; for example:
 //
 //   for (Rooted<BindingIter> bi(cx, BindingIter(scope)); bi; bi++) {
 //     use(bi);
@@ -997,16 +1051,17 @@ class BindingIter
     }
 
     void init(LexicalScope::Data& data, uint32_t firstFrameSlot, uint8_t flags);
     void init(FunctionScope::Data& data, uint8_t flags);
     void init(VarScope::Data& data, uint32_t firstFrameSlot);
     void init(GlobalScope::Data& data);
     void init(EvalScope::Data& data, bool strict);
     void init(ModuleScope::Data& data);
+    void init(WasmFunctionScope::Data& data);
 
     bool hasFormalParameterExprs() const {
         return flags_ & HasFormalParameterExprs;
     }
 
     bool ignoreDestructuredFormalParameters() const {
         return flags_ & IgnoreDestructuredFormalParameters;
     }
@@ -1069,16 +1124,20 @@ class BindingIter
     explicit BindingIter(GlobalScope::Data& data) {
         init(data);
     }
 
     explicit BindingIter(ModuleScope::Data& data) {
         init(data);
     }
 
+    explicit BindingIter(WasmFunctionScope::Data& data) {
+        init(data);
+    }
+
     BindingIter(EvalScope::Data& data, bool strict) {
         init(data, strict);
     }
 
     explicit BindingIter(const BindingIter& bi) = default;
 
     bool done() const {
         return index_ == length_;
@@ -1373,16 +1432,17 @@ struct ScopeDataGCPolicy
     { }
 
 DEFINE_SCOPE_DATA_GCPOLICY(js::LexicalScope::Data);
 DEFINE_SCOPE_DATA_GCPOLICY(js::FunctionScope::Data);
 DEFINE_SCOPE_DATA_GCPOLICY(js::VarScope::Data);
 DEFINE_SCOPE_DATA_GCPOLICY(js::GlobalScope::Data);
 DEFINE_SCOPE_DATA_GCPOLICY(js::EvalScope::Data);
 DEFINE_SCOPE_DATA_GCPOLICY(js::ModuleScope::Data);
+DEFINE_SCOPE_DATA_GCPOLICY(js::WasmFunctionScope::Data);
 
 #undef DEFINE_SCOPE_DATA_GCPOLICY
 
 namespace ubi {
 
 template <>
 class Concrete<js::Scope> : TracerConcrete<js::Scope>
 {
--- a/js/src/vm/Stack-inl.h
+++ b/js/src/vm/Stack-inl.h
@@ -14,16 +14,18 @@
 #include "jscntxt.h"
 #include "jsscript.h"
 
 #include "jit/BaselineFrame.h"
 #include "jit/RematerializedFrame.h"
 #include "js/Debug.h"
 #include "vm/EnvironmentObject.h"
 #include "vm/GeneratorObject.h"
+#include "wasm/WasmDebugFrame.h"
+#include "wasm/WasmInstance.h"
 
 #include "jsobjinlines.h"
 #include "jsscriptinlines.h"
 
 #include "jit/BaselineFrame-inl.h"
 
 namespace js {
 
@@ -415,40 +417,50 @@ FrameIter::unaliasedForEachActual(JSCont
     MOZ_CRASH("Unexpected state");
 }
 
 inline HandleValue
 AbstractFramePtr::returnValue() const
 {
     if (isInterpreterFrame())
         return asInterpreterFrame()->returnValue();
+    if (isWasmDebugFrame())
+        return UndefinedHandleValue;
     return asBaselineFrame()->returnValue();
 }
 
 inline void
 AbstractFramePtr::setReturnValue(const Value& rval) const
 {
     if (isInterpreterFrame()) {
         asInterpreterFrame()->setReturnValue(rval);
         return;
     }
     if (isBaselineFrame()) {
         asBaselineFrame()->setReturnValue(rval);
         return;
     }
+    if (isWasmDebugFrame()) {
+        // TODO handle wasm function return value
+        // The function is called from Debugger::slowPathOnLeaveFrame --
+        // ignoring value for wasm.
+        return;
+    }
     asRematerializedFrame()->setReturnValue(rval);
 }
 
 inline JSObject*
 AbstractFramePtr::environmentChain() const
 {
     if (isInterpreterFrame())
         return asInterpreterFrame()->environmentChain();
     if (isBaselineFrame())
         return asBaselineFrame()->environmentChain();
+    if (isWasmDebugFrame())
+        return asWasmDebugFrame()->environmentChain();
     return asRematerializedFrame()->environmentChain();
 }
 
 template <typename SpecificEnvironment>
 inline void
 AbstractFramePtr::pushOnEnvironmentChain(SpecificEnvironment& env)
 {
     if (isInterpreterFrame()) {
@@ -575,36 +587,42 @@ AbstractFramePtr::createSingleton() cons
 
 inline bool
 AbstractFramePtr::isGlobalFrame() const
 {
     if (isInterpreterFrame())
         return asInterpreterFrame()->isGlobalFrame();
     if (isBaselineFrame())
         return asBaselineFrame()->isGlobalFrame();
+    if (isWasmDebugFrame())
+        return false;
     return asRematerializedFrame()->isGlobalFrame();
 }
 
 inline bool
 AbstractFramePtr::isModuleFrame() const
 {
     if (isInterpreterFrame())
         return asInterpreterFrame()->isModuleFrame();
     if (isBaselineFrame())
         return asBaselineFrame()->isModuleFrame();
+    if (isWasmDebugFrame())
+        return false;
     return asRematerializedFrame()->isModuleFrame();
 }
 
 inline bool
 AbstractFramePtr::isEvalFrame() const
 {
     if (isInterpreterFrame())
         return asInterpreterFrame()->isEvalFrame();
     if (isBaselineFrame())
         return asBaselineFrame()->isEvalFrame();
+    if (isWasmDebugFrame())
+        return false;
     MOZ_ASSERT(isRematerializedFrame());
     return false;
 }
 
 inline bool
 AbstractFramePtr::isDebuggerEvalFrame() const
 {
     if (isInterpreterFrame())
@@ -617,77 +635,108 @@ AbstractFramePtr::isDebuggerEvalFrame() 
 
 inline bool
 AbstractFramePtr::hasCachedSavedFrame() const
 {
     if (isInterpreterFrame())
         return asInterpreterFrame()->hasCachedSavedFrame();
     if (isBaselineFrame())
         return asBaselineFrame()->hasCachedSavedFrame();
+    if (isWasmDebugFrame())
+        return asWasmDebugFrame()->hasCachedSavedFrame();
     return asRematerializedFrame()->hasCachedSavedFrame();
 }
 
 inline void
 AbstractFramePtr::setHasCachedSavedFrame()
 {
     if (isInterpreterFrame())
         asInterpreterFrame()->setHasCachedSavedFrame();
     else if (isBaselineFrame())
         asBaselineFrame()->setHasCachedSavedFrame();
+    else if (isWasmDebugFrame())
+        asWasmDebugFrame()->setHasCachedSavedFrame();
     else
         asRematerializedFrame()->setHasCachedSavedFrame();
 }
 
 inline bool
 AbstractFramePtr::isDebuggee() const
 {
     if (isInterpreterFrame())
         return asInterpreterFrame()->isDebuggee();
     if (isBaselineFrame())
         return asBaselineFrame()->isDebuggee();
+    if (isWasmDebugFrame())
+        return asWasmDebugFrame()->isDebuggee();
     return asRematerializedFrame()->isDebuggee();
 }
 
 inline void
 AbstractFramePtr::setIsDebuggee()
 {
     if (isInterpreterFrame())
         asInterpreterFrame()->setIsDebuggee();
     else if (isBaselineFrame())
         asBaselineFrame()->setIsDebuggee();
+    else if (isWasmDebugFrame())
+        asWasmDebugFrame()->setIsDebuggee();
     else
         asRematerializedFrame()->setIsDebuggee();
 }
 
 inline void
 AbstractFramePtr::unsetIsDebuggee()
 {
     if (isInterpreterFrame())
         asInterpreterFrame()->unsetIsDebuggee();
     else if (isBaselineFrame())
         asBaselineFrame()->unsetIsDebuggee();
+    else if (isWasmDebugFrame())
+        asWasmDebugFrame()->unsetIsDebuggee();
     else
         asRematerializedFrame()->unsetIsDebuggee();
 }
 
 inline bool
-AbstractFramePtr::hasArgs() const {
+AbstractFramePtr::hasArgs() const
+{
     return isFunctionFrame();
 }
 
+inline bool
+AbstractFramePtr::hasScript() const
+{
+    return !isWasmDebugFrame();
+}
+
 inline JSScript*
 AbstractFramePtr::script() const
 {
     if (isInterpreterFrame())
         return asInterpreterFrame()->script();
     if (isBaselineFrame())
         return asBaselineFrame()->script();
     return asRematerializedFrame()->script();
 }
 
+inline wasm::Instance*
+AbstractFramePtr::wasmInstance() const
+{
+    return asWasmDebugFrame()->instance();
+}
+
+inline GlobalObject*
+AbstractFramePtr::global() const
+{
+    if (isWasmDebugFrame())
+        return &wasmInstance()->object()->global();
+    return &script()->global();
+}
+
 inline JSFunction*
 AbstractFramePtr::callee() const
 {
     if (isInterpreterFrame())
         return &asInterpreterFrame()->callee();
     if (isBaselineFrame())
         return asBaselineFrame()->callee();
     return asRematerializedFrame()->callee();
@@ -705,16 +754,18 @@ AbstractFramePtr::calleev() const
 
 inline bool
 AbstractFramePtr::isFunctionFrame() const
 {
     if (isInterpreterFrame())
         return asInterpreterFrame()->isFunctionFrame();
     if (isBaselineFrame())
         return asBaselineFrame()->isFunctionFrame();
+    if (isWasmDebugFrame())
+        return false;
     return asRematerializedFrame()->isFunctionFrame();
 }
 
 inline bool
 AbstractFramePtr::isNonStrictDirectEvalFrame() const
 {
     if (isInterpreterFrame())
         return asInterpreterFrame()->isNonStrictDirectEvalFrame();
@@ -777,44 +828,54 @@ AbstractFramePtr::initArgsObj(ArgumentsO
 
 inline bool
 AbstractFramePtr::prevUpToDate() const
 {
     if (isInterpreterFrame())
         return asInterpreterFrame()->prevUpToDate();
     if (isBaselineFrame())
         return asBaselineFrame()->prevUpToDate();
+    if (isWasmDebugFrame())
+        return asWasmDebugFrame()->prevUpToDate();
     return asRematerializedFrame()->prevUpToDate();
 }
 
 inline void
 AbstractFramePtr::setPrevUpToDate() const
 {
     if (isInterpreterFrame()) {
         asInterpreterFrame()->setPrevUpToDate();
         return;
     }
     if (isBaselineFrame()) {
         asBaselineFrame()->setPrevUpToDate();
         return;
     }
+    if (isWasmDebugFrame()) {
+        asWasmDebugFrame()->setPrevUpToDate();
+        return;
+    }
     asRematerializedFrame()->setPrevUpToDate();
 }
 
 inline void
 AbstractFramePtr::unsetPrevUpToDate() const
 {
     if (isInterpreterFrame()) {
         asInterpreterFrame()->unsetPrevUpToDate();
         return;
     }
     if (isBaselineFrame()) {
         asBaselineFrame()->unsetPrevUpToDate();
         return;
     }
+    if (isWasmDebugFrame()) {
+        asWasmDebugFrame()->unsetPrevUpToDate();
+        return;
+    }
     asRematerializedFrame()->unsetPrevUpToDate();
 }
 
 inline Value&
 AbstractFramePtr::thisArgument() const
 {
     if (isInterpreterFrame())
         return asInterpreterFrame()->thisArgument();
@@ -831,16 +892,18 @@ AbstractFramePtr::newTarget() const
     if (isBaselineFrame())
         return asBaselineFrame()->newTarget();
     return asRematerializedFrame()->newTarget();
 }
 
 inline bool
 AbstractFramePtr::debuggerNeedsCheckPrimitiveReturn() const
 {
+    if (isWasmDebugFrame())
+        return false;
     return script()->isDerivedClassConstructor();
 }
 
 ActivationEntryMonitor::~ActivationEntryMonitor()
 {
     if (entryMonitor_)
         entryMonitor_->Exit(cx_);
 
--- a/js/src/vm/Stack.cpp
+++ b/js/src/vm/Stack.cpp
@@ -12,16 +12,17 @@
 
 #include "gc/Marking.h"
 #include "jit/BaselineFrame.h"
 #include "jit/JitcodeMap.h"
 #include "jit/JitCompartment.h"
 #include "js/GCAPI.h"
 #include "vm/Debugger.h"
 #include "vm/Opcodes.h"
+#include "wasm/WasmDebugFrame.h"
 
 #include "jit/JitFrameIterator-inl.h"
 #include "vm/EnvironmentObject-inl.h"
 #include "vm/Interpreter-inl.h"
 #include "vm/Probes-inl.h"
 
 using namespace js;
 
@@ -151,16 +152,20 @@ AssertScopeMatchesEnvironment(Scope* sco
                 MOZ_CRASH("NonSyntactic should not have a syntactic environment");
                 break;
 
               case ScopeKind::Module:
                 MOZ_ASSERT(env->as<ModuleEnvironmentObject>().module().script() ==
                            si.scope()->as<ModuleScope>().script());
                 env = &env->as<ModuleEnvironmentObject>().enclosingEnvironment();
                 break;
+
+              case ScopeKind::WasmFunction:
+                env = &env->as<WasmFunctionCallObject>().enclosingEnvironment();
+                break;
             }
         }
     }
 
     // In the case of a non-syntactic env chain, the immediate parent of the
     // outermost non-syntactic env may be the global lexical env, or, if
     // called from Debugger, a DebugEnvironmentProxy.
     //
@@ -542,17 +547,17 @@ FrameIter::settleOnActivation()
         if (activation->isWasm()) {
             data_.wasmFrames_ = wasm::FrameIterator(*data_.activations_->asWasm());
 
             if (data_.wasmFrames_.done()) {
                 ++data_.activations_;
                 continue;
             }
 
-            data_.pc_ = (jsbytecode*)data_.wasmFrames_.pc();
+            data_.pc_ = nullptr;
             data_.state_ = WASM;
             return;
         }
 
         MOZ_ASSERT(activation->isInterpreter());
 
         InterpreterActivation* interpAct = activation->asInterpreter();
         data_.interpFrames_ = InterpreterFrameIterator(interpAct);
@@ -675,17 +680,17 @@ FrameIter::popJitFrame()
 }
 
 void
 FrameIter::popWasmFrame()
 {
     MOZ_ASSERT(data_.state_ == WASM);
 
     ++data_.wasmFrames_;
-    data_.pc_ = (jsbytecode*)data_.wasmFrames_.pc();
+    data_.pc_ = nullptr;
     if (data_.wasmFrames_.done())
         popActivation();
 }
 
 FrameIter&
 FrameIter::operator++()
 {
     switch (data_.state_) {
@@ -723,17 +728,16 @@ FrameIter::operator++()
 
 FrameIter::Data*
 FrameIter::copyData() const
 {
     Data* data = data_.cx_->new_<Data>(data_);
     if (!data)
         return nullptr;
 
-    MOZ_ASSERT(data_.state_ != WASM);
     if (data && data_.jitFrames_.isIonScripted())
         data->ionInlineFrameNo_ = ionInlineFrames_.frameNo();
     return data;
 }
 
 AbstractFramePtr
 FrameIter::copyDataAsAbstractFramePtr() const
 {
@@ -749,17 +753,17 @@ FrameIter::rawFramePtr() const
     switch (data_.state_) {
       case DONE:
         return nullptr;
       case JIT:
         return data_.jitFrames_.fp();
       case INTERP:
         return interpFrame();
       case WASM:
-        return data_.wasmFrames_.fp();
+        return nullptr;
     }
     MOZ_CRASH("Unexpected state");
 }
 
 JSCompartment*
 FrameIter::compartment() const
 {
     switch (data_.state_) {
@@ -801,33 +805,33 @@ FrameIter::isFunctionFrame() const
         break;
       case INTERP:
         return interpFrame()->isFunctionFrame();
       case JIT:
         if (data_.jitFrames_.isBaselineJS())
             return data_.jitFrames_.baselineFrame()->isFunctionFrame();
         return script()->functionNonDelazifying();
       case WASM:
-        return true;
+        return false;
     }
     MOZ_CRASH("Unexpected state");
 }
 
 JSAtom*
 FrameIter::functionDisplayAtom() const
 {
-    MOZ_ASSERT(isFunctionFrame());
-
     switch (data_.state_) {
       case DONE:
         break;
       case INTERP:
       case JIT:
+        MOZ_ASSERT(isFunctionFrame());
         return calleeTemplate()->displayAtom();
       case WASM:
+        MOZ_ASSERT(isWasm());
         return data_.wasmFrames_.functionDisplayAtom();
     }
 
     MOZ_CRASH("Unexpected state");
 }
 
 ScriptSource*
 FrameIter::scriptSource() const
@@ -936,60 +940,64 @@ FrameIter::ensureHasRematerializedFrame(
     return !!activation()->asJit()->getRematerializedFrame(cx, data_.jitFrames_);
 }
 
 bool
 FrameIter::hasUsableAbstractFramePtr() const
 {
     switch (data_.state_) {
       case DONE:
-      case WASM:
         return false;
       case JIT:
         if (data_.jitFrames_.isBaselineJS())
             return true;
 
         MOZ_ASSERT(data_.jitFrames_.isIonScripted());
         return !!activation()->asJit()->lookupRematerializedFrame(data_.jitFrames_.fp(),
                                                                   ionInlineFrames_.frameNo());
         break;
       case INTERP:
         return true;
+      case WASM:
+        return data_.wasmFrames_.debugEnabled();
     }
     MOZ_CRASH("Unexpected state");
 }
 
 AbstractFramePtr
 FrameIter::abstractFramePtr() const
 {
     MOZ_ASSERT(hasUsableAbstractFramePtr());
     switch (data_.state_) {
       case DONE:
-      case WASM:
         break;
       case JIT: {
         if (data_.jitFrames_.isBaselineJS())
             return data_.jitFrames_.baselineFrame();
 
         MOZ_ASSERT(data_.jitFrames_.isIonScripted());
         return activation()->asJit()->lookupRematerializedFrame(data_.jitFrames_.fp(),
                                                                 ionInlineFrames_.frameNo());
         break;
       }
       case INTERP:
         MOZ_ASSERT(interpFrame());
         return AbstractFramePtr(interpFrame());
+      case WASM:
+        MOZ_ASSERT(data_.wasmFrames_.debugEnabled());
+        return data_.wasmFrames_.debugFrame();
     }
     MOZ_CRASH("Unexpected state");
 }
 
 void
 FrameIter::updatePcQuadratic()
 {
     switch (data_.state_) {
+      case WASM:
       case DONE:
         break;
       case INTERP: {
         InterpreterFrame* frame = interpFrame();
         InterpreterActivation* activation = data_.activations_->asInterpreter();
 
         // Look for the current frame.
         data_.interpFrames_ = InterpreterFrameIterator(activation);
@@ -1018,20 +1026,16 @@ FrameIter::updatePcQuadratic()
                 ++data_.jitFrames_;
 
             // Update the pc.
             MOZ_ASSERT(data_.jitFrames_.baselineFrame() == frame);
             data_.jitFrames_.baselineScriptAndPc(nullptr, &data_.pc_);
             return;
         }
         break;
-      case WASM:
-        // Update the pc.
-        data_.pc_ = (jsbytecode*)data_.wasmFrames_.pc();
-        break;
     }
     MOZ_CRASH("Unexpected state");
 }
 
 JSFunction*
 FrameIter::calleeTemplate() const
 {
     switch (data_.state_) {
--- a/js/src/vm/Stack.h
+++ b/js/src/vm/Stack.h
@@ -48,16 +48,17 @@ class EnvironmentIter;
 class EnvironmentCoordinate;
 
 class SavedFrame;
 
 namespace jit {
 class CommonFrameLayout;
 }
 namespace wasm {
+class DebugFrame;
 class Instance;
 }
 
 // VM stack layout
 //
 // A JSRuntime's stack consists of a linked list of activations. Every activation
 // contains a number of scripted frames that are either running in the interpreter
 // (InterpreterActivation) or JIT code (JitActivation). The frames inside a single
@@ -121,17 +122,18 @@ class AbstractFramePtr
 
     uintptr_t ptr_;
 
     enum {
         Tag_ScriptFrameIterData = 0x0,
         Tag_InterpreterFrame = 0x1,
         Tag_BaselineFrame = 0x2,
         Tag_RematerializedFrame = 0x3,
-        TagMask = 0x3
+        Tag_WasmDebugFrame = 0x4,
+        TagMask = 0x7
     };
 
   public:
     AbstractFramePtr()
       : ptr_(0)
     {}
 
     MOZ_IMPLICIT AbstractFramePtr(InterpreterFrame* fp)
@@ -147,16 +149,22 @@ class AbstractFramePtr
     }
 
     MOZ_IMPLICIT AbstractFramePtr(jit::RematerializedFrame* fp)
       : ptr_(fp ? uintptr_t(fp) | Tag_RematerializedFrame : 0)
     {
         MOZ_ASSERT_IF(fp, asRematerializedFrame() == fp);
     }
 
+    MOZ_IMPLICIT AbstractFramePtr(wasm::DebugFrame* fp)
+      : ptr_(fp ? uintptr_t(fp) | Tag_WasmDebugFrame : 0)
+    {
+        MOZ_ASSERT_IF(fp, asWasmDebugFrame() == fp);
+    }
+
     static AbstractFramePtr FromRaw(void* raw) {
         AbstractFramePtr frame;
         frame.ptr_ = uintptr_t(raw);
         return frame;
     }
 
     bool isScriptFrameIterData() const {
         return !!ptr_ && (ptr_ & TagMask) == Tag_ScriptFrameIterData;
@@ -183,16 +191,25 @@ class AbstractFramePtr
         return (ptr_ & TagMask) == Tag_RematerializedFrame;
     }
     jit::RematerializedFrame* asRematerializedFrame() const {
         MOZ_ASSERT(isRematerializedFrame());
         jit::RematerializedFrame* res = (jit::RematerializedFrame*)(ptr_ & ~TagMask);
         MOZ_ASSERT(res);
         return res;
     }
+    bool isWasmDebugFrame() const {
+        return (ptr_ & TagMask) == Tag_WasmDebugFrame;
+    }
+    wasm::DebugFrame* asWasmDebugFrame() const {
+        MOZ_ASSERT(isWasmDebugFrame());
+        wasm::DebugFrame* res = (wasm::DebugFrame*)(ptr_ & ~TagMask);
+        MOZ_ASSERT(res);
+        return res;
+    }
 
     void* raw() const { return reinterpret_cast<void*>(ptr_); }
 
     bool operator ==(const AbstractFramePtr& other) const { return ptr_ == other.ptr_; }
     bool operator !=(const AbstractFramePtr& other) const { return ptr_ != other.ptr_; }
 
     explicit operator bool() const { return !!ptr_; }
 
@@ -210,17 +227,20 @@ class AbstractFramePtr
     inline bool hasInitialEnvironment() const;
     inline bool isGlobalFrame() const;
     inline bool isModuleFrame() const;
     inline bool isEvalFrame() const;
     inline bool isDebuggerEvalFrame() const;
     inline bool hasCachedSavedFrame() const;
     inline void setHasCachedSavedFrame();
 
+    inline bool hasScript() const;
     inline JSScript* script() const;
+    inline wasm::Instance* wasmInstance() const;
+    inline GlobalObject* global() const;
     inline JSFunction* callee() const;
     inline Value calleev() const;
     inline Value& thisArgument() const;
 
     inline Value newTarget() const;
 
     inline bool debuggerNeedsCheckPrimitiveReturn() const;
 
@@ -254,16 +274,17 @@ class AbstractFramePtr
 
     inline HandleValue returnValue() const;
     inline void setReturnValue(const Value& rval) const;
 
     friend void GDBTestInitAbstractFramePtr(AbstractFramePtr&, void*);
     friend void GDBTestInitAbstractFramePtr(AbstractFramePtr&, InterpreterFrame*);
     friend void GDBTestInitAbstractFramePtr(AbstractFramePtr&, jit::BaselineFrame*);
     friend void GDBTestInitAbstractFramePtr(AbstractFramePtr&, jit::RematerializedFrame*);
+    friend void GDBTestInitAbstractFramePtr(AbstractFramePtr& frame, wasm::DebugFrame* ptr);
 };
 
 class NullFramePtr : public AbstractFramePtr
 {
   public:
     NullFramePtr()
       : AbstractFramePtr()
     { }
@@ -1783,16 +1804,21 @@ class FrameIter
     const char16_t* displayURL() const;
     unsigned computeLine(uint32_t* column = nullptr) const;
     JSAtom* functionDisplayAtom() const;
     bool mutedErrors() const;
 
     bool hasScript() const { return !isWasm(); }
 
     // -----------------------------------------------------------
+    //  The following functions can only be called when isWasm()
+    // -----------------------------------------------------------
+    inline wasm::Instance* wasmInstance() const;
+
+    // -----------------------------------------------------------
     // The following functions can only be called when hasScript()
     // -----------------------------------------------------------
 
     inline JSScript* script() const;
 
     bool        isConstructing() const;
     jsbytecode* pc() const { MOZ_ASSERT(!done()); return data_.pc_; }
     void        updatePcQuadratic();
@@ -1843,17 +1869,17 @@ class FrameIter
     bool ensureHasRematerializedFrame(JSContext* cx);
 
     // True when isInterp() or isBaseline(). True when isIon() if it
     // has a rematerialized frame. False otherwise false otherwise.
     bool hasUsableAbstractFramePtr() const;
 
     // -----------------------------------------------------------
     // The following functions can only be called when isInterp(),
-    // isBaseline(), or isIon(). Further, abstractFramePtr() can
+    // isBaseline(), isWasm() or isIon(). Further, abstractFramePtr() can
     // only be called when hasUsableAbstractFramePtr().
     // -----------------------------------------------------------
 
     AbstractFramePtr abstractFramePtr() const;
     AbstractFramePtr copyDataAsAbstractFramePtr() const;
     Data* copyData() const;
 
     // This can only be called when isInterp():
@@ -2025,16 +2051,24 @@ FrameIter::script() const
     if (data_.state_ == INTERP)
         return interpFrame()->script();
     MOZ_ASSERT(data_.state_ == JIT);
     if (data_.jitFrames_.isIonJS())
         return ionInlineFrames_.script();
     return data_.jitFrames_.script();
 }
 
+inline wasm::Instance*
+FrameIter::wasmInstance() const
+{
+    MOZ_ASSERT(!done());
+    MOZ_ASSERT(data_.state_ == WASM);
+    return data_.wasmFrames_.instance();
+}
+
 inline bool
 FrameIter::isIon() const
 {
     return isJit() && data_.jitFrames_.isIonJS();
 }
 
 inline bool
 FrameIter::isBaseline() const
--- a/js/src/wasm/WasmBaselineCompile.cpp
+++ b/js/src/wasm/WasmBaselineCompile.cpp
@@ -99,16 +99,17 @@
 # include "jit/arm/Assembler-arm.h"
 #endif
 #if defined(JS_CODEGEN_X64) || defined(JS_CODEGEN_X86)
 # include "jit/x86-shared/Architecture-x86-shared.h"
 # include "jit/x86-shared/Assembler-x86-shared.h"
 #endif
 
 #include "wasm/WasmBinaryIterator.h"
+#include "wasm/WasmDebugFrame.h"
 #include "wasm/WasmGenerator.h"
 #include "wasm/WasmSignalHandlers.h"
 #include "wasm/WasmValidate.h"
 
 #include "jit/MacroAssembler-inl.h"
 
 using mozilla::DebugOnly;
 using mozilla::FloatingPoint;
@@ -492,16 +493,17 @@ class BaseCompiler
     size_t                      lastReadCallSite_;
     TempAllocator&              alloc_;
     const ValTypeVector&        locals_;         // Types of parameters and locals
     int32_t                     localSize_;      // Size of local area in bytes (stable after beginFunction)
     int32_t                     varLow_;         // Low byte offset of local area for true locals (not parameters)
     int32_t                     varHigh_;        // High byte offset + 1 of local area for true locals
     int32_t                     maxFramePushed_; // Max value of masm.framePushed() observed
     bool                        deadCode_;       // Flag indicating we should decode & discard the opcode
+    bool                        debugEnabled_;
     ValTypeVector               SigI64I64_;
     ValTypeVector               SigDD_;
     ValTypeVector               SigD_;
     ValTypeVector               SigF_;
     ValTypeVector               SigI_;
     ValTypeVector               Sig_;
     Label                       returnLabel_;
     Label                       stackOverflowLabel_;
@@ -563,16 +565,17 @@ class BaseCompiler
 
     // More members: see the stk_ and ctl_ vectors, defined below.
 
   public:
     BaseCompiler(const ModuleEnvironment& env,
                  Decoder& decoder,
                  const FuncBytes& func,
                  const ValTypeVector& locals,
+                 bool debugEnabled,
                  TempAllocator* alloc,
                  MacroAssembler* masm);
 
     MOZ_MUST_USE bool init();
 
     FuncOffsets finish();
 
     MOZ_MUST_USE bool emitFunction();
@@ -2076,16 +2079,21 @@ class BaseCompiler
         return new (candidate) PooledLabel(this);
     }
 
     void freeLabel(PooledLabel* label) {
         label->~PooledLabel();
         labelPool_.free(label);
     }
 
+    void insertBreakablePoint(CallSiteDesc::Kind kind) {
+        const uint32_t offset = iter_.currentOffset();
+        masm.nopPatchableToCall(CallSiteDesc(offset, kind));
+    }
+
     //////////////////////////////////////////////////////////////////////
     //
     // Function prologue and epilogue.
 
     void beginFunction() {
         JitSpew(JitSpew_Codegen, "# Emitting wasm baseline code");
 
         SigIdDesc sigId = env_.funcSigs[func_.index()]->id;
@@ -2134,16 +2142,24 @@ class BaseCompiler
               default:
                 MOZ_CRASH("Function argument type");
             }
         }
 
         // The TLS pointer is always passed as a hidden argument in WasmTlsReg.
         // Save it into its assigned local slot.
         storeToFramePtr(WasmTlsReg, localInfo_[tlsSlot_].offs());
+        if (debugEnabled_) {
+            // Initialize funcIndex and flag fields of DebugFrame.
+            size_t debugFrame = masm.framePushed() - DebugFrame::offsetOfFrame();
+            masm.store32(Imm32(func_.index()),
+                         Address(masm.getStackPointer(), debugFrame + DebugFrame::offsetOfFuncIndex()));
+            masm.storePtr(ImmWord(0),
+                          Address(masm.getStackPointer(), debugFrame + DebugFrame::offsetOfFlagsWord()));
+        }
 
         // Initialize the stack locals to zero.
         //
         // The following are all Bug 1316820:
         //
         // TODO / OPTIMIZE: on x64, at least, scratch will be a 64-bit
         // register and we can move 64 bits at a time.
         //
@@ -2155,16 +2171,68 @@ class BaseCompiler
         // then it's better to store a zero literal, probably.
 
         if (varLow_ < varHigh_) {
             ScratchI32 scratch(*this);
             masm.mov(ImmWord(0), scratch);
             for (int32_t i = varLow_ ; i < varHigh_ ; i += 4)
                 storeToFrameI32(scratch, i + 4);
         }
+
+        if (debugEnabled_)
+            insertBreakablePoint(CallSiteDesc::EnterFrame);
+    }
+
+    void saveResult() {
+        MOZ_ASSERT(debugEnabled_);
+        size_t debugFrameOffset = masm.framePushed() - DebugFrame::offsetOfFrame();
+        Address resultsAddress(StackPointer, debugFrameOffset + DebugFrame::offsetOfResults());
+        switch (func_.sig().ret()) {
+          case ExprType::Void:
+            break;
+          case ExprType::I32:
+            masm.store32(RegI32(ReturnReg), resultsAddress);
+            break;
+
+          case ExprType::I64:
+            masm.store64(RegI64(ReturnReg64), resultsAddress);
+            break;
+          case ExprType::F64:
+            masm.storeDouble(RegF64(ReturnDoubleReg), resultsAddress);
+            break;
+          case ExprType::F32:
+            masm.storeFloat32(RegF32(ReturnFloat32Reg), resultsAddress);
+            break;
+          default:
+            MOZ_CRASH("Function return type");
+        }
+    }
+
+    void restoreResult() {
+        MOZ_ASSERT(debugEnabled_);
+        size_t debugFrameOffset = masm.framePushed() - DebugFrame::offsetOfFrame();
+        Address resultsAddress(StackPointer, debugFrameOffset + DebugFrame::offsetOfResults());
+        switch (func_.sig().ret()) {
+          case ExprType::Void:
+            break;
+          case ExprType::I32:
+            masm.load32(resultsAddress, RegI32(ReturnReg));
+            break;
+          case ExprType::I64:
+            masm.load64(resultsAddress, RegI64(ReturnReg64));
+            break;
+          case ExprType::F64:
+            masm.loadDouble(resultsAddress, RegF64(ReturnDoubleReg));
+            break;
+          case ExprType::F32:
+            masm.loadFloat32(resultsAddress, RegF32(ReturnFloat32Reg));
+            break;
+          default:
+            MOZ_CRASH("Function return type");
+        }
     }
 
     bool endFunction() {
         // Always branch to stackOverflowLabel_ or returnLabel_.
         masm.breakpoint();
 
         // Patch the add in the prologue so that it checks against the correct
         // frame size.
@@ -2176,16 +2244,24 @@ class BaseCompiler
         // distance away from the end of the native stack.
         masm.bind(&stackOverflowLabel_);
         if (localSize_)
             masm.addToStackPtr(Imm32(localSize_));
         masm.jump(TrapDesc(prologueTrapOffset_, Trap::StackOverflow, /* framePushed = */ 0));
 
         masm.bind(&returnLabel_);
 
+        if (debugEnabled_) {
+            // Store and reload the return value from DebugFrame::return so that
+            // it can be clobbered, and/or modified by the debug trap.
+            saveResult();
+            insertBreakablePoint(CallSiteDesc::LeaveFrame);
+            restoreResult();
+        }
+
         // Restore the TLS register in case it was overwritten by the function.
         loadFromFramePtr(WasmTlsReg, frameOffsetFromSlot(tlsSlot_, MIRType::Pointer));
 
         GenerateFunctionEpilogue(masm, localSize_, &offsets_);
 
 #if defined(JS_ION_PERF)
         // FIXME - profiling code missing.  Bug 1286948.
 
@@ -7721,29 +7797,31 @@ BaseCompiler::emitFunction()
 
     return true;
 }
 
 BaseCompiler::BaseCompiler(const ModuleEnvironment& env,
                            Decoder& decoder,
                            const FuncBytes& func,
                            const ValTypeVector& locals,
+                           bool debugEnabled,
                            TempAllocator* alloc,
                            MacroAssembler* masm)
     : env_(env),
       iter_(decoder, func.lineOrBytecode()),
       func_(func),
       lastReadCallSite_(0),
       alloc_(*alloc),
       locals_(locals),
       localSize_(0),
       varLow_(0),
       varHigh_(0),
       maxFramePushed_(0),
       deadCode_(false),
+      debugEnabled_(debugEnabled),
       prologueTrapOffset_(trapOffset()),
       stackAddOffset_(0),
       latentOp_(LatentOp::None),
       latentType_(ValType::I32),
       latentIntCmp_(Assembler::Equal),
       latentDoubleCmp_(Assembler::DoubleEqual),
       masm(*masm),
       availGPR_(GeneralRegisterSet::All()),
@@ -7816,16 +7894,27 @@ BaseCompiler::init()
     // entries for special locals. Currently the only special local is the TLS
     // pointer.
     tlsSlot_ = locals_.length();
     if (!localInfo_.resize(locals_.length() + 1))
         return false;
 
     localSize_ = 0;
 
+    // Reserve a stack slot for the TLS pointer outside the varLow..varHigh
+    // range so it isn't zero-filled like the normal locals.
+    localInfo_[tlsSlot_].init(MIRType::Pointer, pushLocal(sizeof(void*)));
+    if (debugEnabled_) {
+        // If debug information is generated, constructing DebugFrame record:
+        // reserving some data before TLS pointer. The TLS pointer allocated
+        // above and regular wasm::Frame data starts after locals.
+        localSize_ += DebugFrame::offsetOfTlsData();
+        MOZ_ASSERT(DebugFrame::offsetOfFrame() == localSize_);
+    }
+
     for (ABIArgIter<const ValTypeVector> i(args); !i.done(); i++) {
         Local& l = localInfo_[i.index()];
         switch (i.mirType()) {
           case MIRType::Int32:
             if (i->argInRegister())
                 l.init(MIRType::Int32, pushLocal(4));
             else
                 l.init(MIRType::Int32, -(i->offsetFromArgBase() + sizeof(Frame)));
@@ -7848,20 +7937,16 @@ BaseCompiler::init()
             else
                 l.init(MIRType::Float32, -(i->offsetFromArgBase() + sizeof(Frame)));
             break;
           default:
             MOZ_CRASH("Argument type");
         }
     }
 
-    // Reserve a stack slot for the TLS pointer outside the varLow..varHigh
-    // range so it isn't zero-filled like the normal locals.
-    localInfo_[tlsSlot_].init(MIRType::Pointer, pushLocal(sizeof(void*)));
-
     varLow_ = localSize_;
 
     for (size_t i = args.length(); i < locals_.length(); i++) {
         Local& l = localInfo_[i];
         switch (locals_[i]) {
           case ValType::I32:
             l.init(MIRType::Int32, pushLocal(4));
             break;
@@ -7967,17 +8052,17 @@ js::wasm::BaselineCompileFunction(Compil
         return false;
 
     // The MacroAssembler will sometimes access the jitContext.
 
     JitContext jitContext(&task->alloc());
 
     // One-pass baseline compilation.
 
-    BaseCompiler f(task->env(), d, func, locals, &task->alloc(), &task->masm());
+    BaseCompiler f(task->env(), d, func, locals, task->debugEnabled(), &task->alloc(), &task->masm());
     if (!f.init())
         return false;
 
     if (!f.emitFunction())
         return false;
 
     unit->finish(f.finish());
     return true;
--- a/js/src/wasm/WasmCode.cpp
+++ b/js/src/wasm/WasmCode.cpp
@@ -341,17 +341,18 @@ CodeRange::CodeRange(Kind kind, Offsets 
     funcBeginToTableEntry_(0),
     funcBeginToTableProfilingJump_(0),
     funcBeginToNonProfilingEntry_(0),
     funcProfilingJumpToProfilingReturn_(0),
     funcProfilingEpilogueToProfilingReturn_(0),
     kind_(kind)
 {
     MOZ_ASSERT(begin_ <= end_);
-    MOZ_ASSERT(kind_ == Entry || kind_ == Inline || kind_ == FarJumpIsland);
+    MOZ_ASSERT(kind_ == Entry || kind_ == Inline ||
+               kind_ == FarJumpIsland || kind_ == DebugTrap);
 }
 
 CodeRange::CodeRange(Kind kind, ProfilingOffsets offsets)
   : begin_(offsets.begin),
     profilingReturn_(offsets.profilingReturn),
     end_(offsets.end),
     funcIndex_(0),
     funcLineOrBytecode_(0),
@@ -453,16 +454,17 @@ Metadata::serializedSize() const
            SerializedPodVectorSize(funcNames) +
            SerializedPodVectorSize(customSections) +
            filename.serializedSize();
 }
 
 uint8_t*
 Metadata::serialize(uint8_t* cursor) const
 {
+    MOZ_ASSERT(!debugEnabled && debugTrapFarJumpOffsets.empty());
     cursor = WriteBytes(cursor, &pod(), sizeof(pod()));
     cursor = SerializeVector(cursor, funcImports);
     cursor = SerializeVector(cursor, funcExports);
     cursor = SerializeVector(cursor, sigIds);
     cursor = SerializePodVector(cursor, globals);
     cursor = SerializePodVector(cursor, tables);
     cursor = SerializePodVector(cursor, memoryAccesses);
     cursor = SerializePodVector(cursor, memoryPatches);
@@ -489,16 +491,18 @@ Metadata::deserialize(const uint8_t* cur
     (cursor = DeserializePodVector(cursor, &memoryPatches)) &&
     (cursor = DeserializePodVector(cursor, &boundsChecks)) &&
     (cursor = DeserializePodVector(cursor, &codeRanges)) &&
     (cursor = DeserializePodVector(cursor, &callSites)) &&
     (cursor = DeserializePodVector(cursor, &callThunks)) &&
     (cursor = DeserializePodVector(cursor, &funcNames)) &&
     (cursor = DeserializePodVector(cursor, &customSections)) &&
     (cursor = filename.deserialize(cursor));
+    debugEnabled = false;
+    debugTrapFarJumpOffsets.clear();
     return cursor;
 }
 
 size_t
 Metadata::sizeOfExcludingThis(MallocSizeOf mallocSizeOf) const
 {
     return SizeOfVectorExcludingThis(funcImports, mallocSizeOf) +
            SizeOfVectorExcludingThis(funcExports, mallocSizeOf) +
@@ -566,18 +570,21 @@ Metadata::getFuncName(const Bytes* maybe
 }
 
 Code::Code(UniqueCodeSegment segment,
            const Metadata& metadata,
            const ShareableBytes* maybeBytecode)
   : segment_(Move(segment)),
     metadata_(&metadata),
     maybeBytecode_(maybeBytecode),
+    enterAndLeaveFrameTrapsCounter_(0),
     profilingEnabled_(false)
-{}
+{
+    MOZ_ASSERT_IF(metadata_->debugEnabled, maybeBytecode);
+}
 
 struct CallSiteRetAddrOffset
 {
     const CallSiteVector& callSites;
     explicit CallSiteRetAddrOffset(const CallSiteVector& callSites) : callSites(callSites) {}
     uint32_t operator[](size_t index) const {
         return callSites[index].returnAddressOffset();
     }
@@ -820,16 +827,62 @@ Code::ensureProfilingState(JSRuntime* rt
         for (const CodeRange& codeRange : metadata_->codeRanges)
             ToggleProfiling(*this, codeRange, newProfilingEnabled);
     }
 
     return true;
 }
 
 void
+Code::toggleDebugTrap(uint32_t offset, bool enabled)
+{
+    MOZ_ASSERT(offset);
+    uint8_t* trap = segment_->base() + offset;
+    const Uint32Vector& farJumpOffsets = metadata_->debugTrapFarJumpOffsets;
+    if (enabled) {
+        MOZ_ASSERT(farJumpOffsets.length() > 0);
+        size_t i = 0;
+        while (i < farJumpOffsets.length() && offset < farJumpOffsets[i])
+            i++;
+        if (i >= farJumpOffsets.length() ||
+            (i > 0 && offset - farJumpOffsets[i - 1] < farJumpOffsets[i] - offset))
+            i--;
+        uint8_t* farJump = segment_->base() + farJumpOffsets[i];
+        MacroAssembler::patchNopToCall(trap, farJump);
+    } else {
+        MacroAssembler::patchCallToNop(trap);
+    }
+}
+
+void
+Code::adjustEnterAndLeaveFrameTrapsState(JSContext* cx, bool enabled)
+{
+    MOZ_ASSERT(metadata_->debugEnabled);
+    MOZ_ASSERT_IF(!enabled, enterAndLeaveFrameTrapsCounter_ > 0);
+
+    bool wasEnabled = enterAndLeaveFrameTrapsCounter_ > 0;
+    if (enabled)
+        ++enterAndLeaveFrameTrapsCounter_;
+    else
+        --enterAndLeaveFrameTrapsCounter_;
+    bool stillEnabled = enterAndLeaveFrameTrapsCounter_ > 0;
+    if (wasEnabled == stillEnabled)
+        return;
+
+    AutoWritableJitCode awjc(cx->runtime(), segment_->base(), segment_->codeLength());
+    AutoFlushICache afc("Code::adjustEnterAndLeaveFrameTrapsState");
+    AutoFlushICache::setRange(uintptr_t(segment_->base()), segment_->codeLength());
+    for (const CallSite& callSite : metadata_->callSites) {
+        if (callSite.kind() != CallSite::EnterFrame && callSite.kind() != CallSite::LeaveFrame)
+            continue;
+        toggleDebugTrap(callSite.returnAddressOffset(), stillEnabled);
+    }
+}
+
+void
 Code::addSizeOfMisc(MallocSizeOf mallocSizeOf,
                     Metadata::SeenSet* seenMetadata,
                     ShareableBytes::SeenSet* seenBytes,
                     size_t* code,
                     size_t* data) const
 {
     *code += segment_->codeLength();
     *data += mallocSizeOf(this) +
--- a/js/src/wasm/WasmCode.h
+++ b/js/src/wasm/WasmCode.h
@@ -14,26 +14,29 @@
  * 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.
  */
 
 #ifndef wasm_code_h
 #define wasm_code_h
 
+#include "js/HashTable.h"
 #include "wasm/WasmTypes.h"
 
 namespace js {
 
 struct AsmJSMetadata;
+class WasmActivation;
 
 namespace wasm {
 
 struct LinkData;
 struct Metadata;
+class FrameIterator;
 
 // A wasm CodeSegment owns the allocated executable code for a wasm module.
 // This allocation also currently includes the global data segment, which allows
 // RIP-relative access to global data on some architectures, but this will
 // change in the future to give global data its own allocation.
 
 class CodeSegment;
 typedef UniquePtr<CodeSegment> UniqueCodeSegment;
@@ -235,16 +238,18 @@ class CodeRange
 {
   public:
     enum Kind {
         Function,          // function definition
         Entry,             // calls into wasm from C++
         ImportJitExit,     // fast-path calling from wasm into JIT code
         ImportInterpExit,  // slow-path calling from wasm into C++ interp
         TrapExit,          // calls C++ to report and jumps to throw stub
+        DebugTrap,         // calls C++ to handle debug event such as
+                           // enter/leave frame or breakpoint
         FarJumpIsland,     // inserted to connect otherwise out-of-range insns
         Inline             // stub that is jumped-to, not called, and thus
                            // replaces/loses preceding innermost frame
     };
 
   private:
     // All fields are treated as cacheable POD:
     uint32_t begin_;
@@ -462,16 +467,20 @@ struct Metadata : ShareableBase<Metadata
     BoundsCheckVector     boundsChecks;
     CodeRangeVector       codeRanges;
     CallSiteVector        callSites;
     CallThunkVector       callThunks;
     NameInBytecodeVector  funcNames;
     CustomSectionVector   customSections;
     CacheableChars        filename;
 
+    // Debug-enabled code is not serialized.
+    bool                  debugEnabled;
+    Uint32Vector          debugTrapFarJumpOffsets;
+
     bool usesMemory() const { return UsesMemory(memoryUsage); }
     bool hasSharedMemory() const { return memoryUsage == MemoryUsage::Shared; }
 
     const FuncExport& lookupFuncExport(uint32_t funcIndex) const;
 
     // AsmJSMetadata derives Metadata iff isAsmJS(). Mostly this distinction is
     // encapsulated within AsmJS.cpp, but the additional virtual functions allow
     // asm.js to override wasm behavior in the handful of cases that can't be
@@ -566,18 +575,21 @@ typedef UniquePtr<GeneratedSourceMap> Un
 
 class Code
 {
     const UniqueCodeSegment  segment_;
     const SharedMetadata     metadata_;
     const SharedBytes        maybeBytecode_;
     UniqueGeneratedSourceMap maybeSourceMap_;
     CacheableCharsVector     funcLabels_;
+    uint32_t                 enterAndLeaveFrameTrapsCounter_;
     bool                     profilingEnabled_;
 
+    void toggleDebugTrap(uint32_t offset, bool enabled);
+
   public:
     Code(UniqueCodeSegment segment,
          const Metadata& metadata,
          const ShareableBytes* maybeBytecode);
 
     CodeSegment& segment() { return *segment_; }
     const CodeSegment& segment() const { return *segment_; }
     const Metadata& metadata() const { return *metadata_; }
@@ -606,16 +618,22 @@ class Code
     // the stack. Once in profiling mode, ProfilingFrameIterator can be used to
     // asynchronously walk the stack. Otherwise, the ProfilingFrameIterator will
     // skip any activations of this code.
 
     MOZ_MUST_USE bool ensureProfilingState(JSRuntime* rt, bool enabled);
     bool profilingEnabled() const { return profilingEnabled_; }
     const char* profilingLabel(uint32_t funcIndex) const { return funcLabels_[funcIndex].get(); }
 
+    // The Code can track enter/leave frame events. Any such event triggers
+    // debug trap. The enter frame events enabled across all functions, but
+    // the leave frame events only for particular function.
+
+    void adjustEnterAndLeaveFrameTrapsState(JSContext* cx, bool enabled);
+
     // about:memory reporting:
 
     void addSizeOfMisc(MallocSizeOf mallocSizeOf,
                        Metadata::SeenSet* seenMetadata,
                        ShareableBytes::SeenSet* seenBytes,
                        size_t* code,
                        size_t* data) const;
 
--- a/js/src/wasm/WasmCompile.cpp
+++ b/js/src/wasm/WasmCompile.cpp
@@ -89,16 +89,23 @@ DecodeCodeSection(Decoder& d, ModuleGene
 
     return mg.finishFuncDefs();
 }
 
 bool
 CompileArgs::initFromContext(ExclusiveContext* cx, ScriptedCaller&& scriptedCaller)
 {
     alwaysBaseline = cx->options().wasmAlwaysBaseline();
+
+    // Debug information such as source view or debug traps will require
+    // additional memory and permanently stay in baseline code, so we try to
+    // only enable it when a developer actually cares: when the debugger tab
+    // is open.
+    debugEnabled = cx->compartment()->debuggerObservesAsmJS();
+
     this->scriptedCaller = Move(scriptedCaller);
     return assumptions.initBuildIdFromContext(cx);
 }
 
 SharedModule
 wasm::Compile(const ShareableBytes& bytecode, const CompileArgs& args, UniqueChars* error)
 {
     MOZ_RELEASE_ASSERT(wasm::HaveSignalHandlers());
--- a/js/src/wasm/WasmCompile.h
+++ b/js/src/wasm/WasmCompile.h
@@ -35,21 +35,23 @@ struct ScriptedCaller
 
 // Describes all the parameters that control wasm compilation.
 
 struct CompileArgs
 {
     Assumptions assumptions;
     ScriptedCaller scriptedCaller;
     bool alwaysBaseline;
+    bool debugEnabled;
 
     CompileArgs(Assumptions&& assumptions, ScriptedCaller&& scriptedCaller)
       : assumptions(Move(assumptions)),
         scriptedCaller(Move(scriptedCaller)),
-        alwaysBaseline(false)
+        alwaysBaseline(false),
+        debugEnabled(false)
     {}
 
     // If CompileArgs is constructed without arguments, initFromContext() must
     // be called to complete initialization.
     CompileArgs() = default;
     bool initFromContext(ExclusiveContext* cx, ScriptedCaller&& scriptedCaller);
 };
 
new file mode 100644
--- /dev/null
+++ b/js/src/wasm/WasmDebugFrame.cpp
@@ -0,0 +1,65 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ * vim: set ts=8 sts=4 et sw=4 tw=99:
+ *
+ * Copyright 2016 Mozilla Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "wasm/WasmDebugFrame.h"
+
+#include "vm/EnvironmentObject.h"
+#include "wasm/WasmInstance.h"
+
+#include "jsobjinlines.h"
+
+using namespace js;
+using namespace js::wasm;
+
+Instance*
+DebugFrame::instance() const
+{
+    return tlsData_->instance;
+}
+
+GlobalObject*
+DebugFrame::global() const
+{
+    return &instance()->object()->global();
+}
+
+JSObject*
+DebugFrame::environmentChain() const
+{
+    return &global()->lexicalEnvironment();
+}
+
+void
+DebugFrame::observeFrame(JSContext* cx)
+{
+   if (observing_)
+       return;
+
+   instance()->code().adjustEnterAndLeaveFrameTrapsState(cx, /* enabled = */ true);
+   observing_ = true;
+}
+
+void
+DebugFrame::leaveFrame(JSContext* cx)
+{
+   if (!observing_)
+       return;
+
+   instance()->code().adjustEnterAndLeaveFrameTrapsState(cx, /* enabled = */ false);
+   observing_ = false;
+}
new file mode 100644
--- /dev/null
+++ b/js/src/wasm/WasmDebugFrame.h
@@ -0,0 +1,110 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ * vim: set ts=8 sts=4 et sw=4 tw=99:
+ *
+ * Copyright 2016 Mozilla Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef wasmdebugframe_js_h
+#define wasmdebugframe_js_h
+
+#include "gc/Barrier.h"
+#include "js/RootingAPI.h"
+#include "js/TracingAPI.h"
+#include "wasm/WasmTypes.h"
+
+namespace js {
+
+class WasmFunctionCallObject;
+
+namespace wasm {
+
+class DebugFrame
+{
+    union
+    {
+        int32_t resultI32_;
+        int64_t resultI64_;
+        float   resultF32_;
+        double  resultF64_;
+    };
+
+    // The fields below are initialized by the baseline compiler.
+    uint32_t    funcIndex_;
+    uint32_t    reserved0_;
+
+    union
+    {
+        struct
+        {
+            bool    observing_ : 1;
+            bool    isDebuggee_ : 1;
+            bool    prevUpToDate_ : 1;
+            bool    hasCachedSavedFrame_ : 1;
+        };
+        void*   reserved1_;
+    };
+
+    TlsData*    tlsData_;
+    Frame       frame_;
+
+    explicit DebugFrame() {}
+
+  public:
+    inline uint32_t funcIndex() const { return funcIndex_; }
+    inline TlsData* tlsData() const { return tlsData_; }
+    inline Frame& frame() { return frame_; }
+
+    Instance* instance() const;
+    GlobalObject* global() const;
+
+    JSObject* environmentChain() const;
+
+    void observeFrame(JSContext* cx);
+    void leaveFrame(JSContext* cx);
+
+    void trace(JSTracer* trc);
+
+    // These are opaque boolean flags used by the debugger and
+    // saved-frame-chains code.
+    inline bool isDebuggee() const { return isDebuggee_; }
+    inline void setIsDebuggee() { isDebuggee_ = true; }
+    inline void unsetIsDebuggee() { isDebuggee_ = false; }
+
+    inline bool prevUpToDate() const { return prevUpToDate_; }
+    inline void setPrevUpToDate() { prevUpToDate_ = true; }
+    inline void unsetPrevUpToDate() { prevUpToDate_ = false; }
+
+    inline bool hasCachedSavedFrame() const { return hasCachedSavedFrame_; }
+    inline void setHasCachedSavedFrame() { hasCachedSavedFrame_ = true; }
+
+    inline void* resultsPtr() { return &resultI32_; }
+
+    static constexpr size_t offsetOfResults() { return offsetof(DebugFrame, resultI32_); }
+    static constexpr size_t offsetOfFlagsWord() { return offsetof(DebugFrame, reserved1_); }
+    static constexpr size_t offsetOfFuncIndex() { return offsetof(DebugFrame, funcIndex_); }
+    static constexpr size_t offsetOfTlsData() { return offsetof(DebugFrame, tlsData_); }
+    static constexpr size_t offsetOfFrame() { return offsetof(DebugFrame, frame_); }
+};
+
+static_assert(DebugFrame::offsetOfResults() == 0, "results shall be at offset 0");
+static_assert(DebugFrame::offsetOfTlsData() + sizeof(TlsData*) == DebugFrame::offsetOfFrame(),
+              "TLS pointer must be a field just before the wasm frame");
+static_assert(sizeof(DebugFrame) % 8 == 0 && DebugFrame::offsetOfFrame() % 8 == 0,
+              "DebugFrame and its portion is 8-bytes aligned for AbstractFramePtr");
+
+} // namespace wasm
+} // namespace js
+
+#endif // wasmdebugframe_js_h
--- a/js/src/wasm/WasmFrameIterator.cpp
+++ b/js/src/wasm/WasmFrameIterator.cpp
@@ -13,16 +13,17 @@
  * 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 "wasm/WasmFrameIterator.h"
 
+#include "wasm/WasmDebugFrame.h"
 #include "wasm/WasmInstance.h"
 
 #include "jit/MacroAssembler-inl.h"
 
 using namespace js;
 using namespace js::jit;
 using namespace js::wasm;
 
@@ -39,16 +40,23 @@ ReturnAddressFromFP(void* fp)
 }
 
 static uint8_t*
 CallerFPFromFP(void* fp)
 {
     return reinterpret_cast<Frame*>(fp)->callerFP;
 }
 
+static TlsData*
+TlsDataFromFP(void *fp)
+{
+    void* debugFrame = (uint8_t*)fp - DebugFrame::offsetOfFrame();
+    return reinterpret_cast<DebugFrame*>(debugFrame)->tlsData();
+}
+
 FrameIterator::FrameIterator()
   : activation_(nullptr),
     code_(nullptr),
     callsite_(nullptr),
     codeRange_(nullptr),
     fp_(nullptr),
     pc_(nullptr),
     missingFrameMessage_(false)
@@ -138,16 +146,17 @@ FrameIterator::settle()
         pc_ = nullptr;
         code_ = nullptr;
         codeRange_ = nullptr;
         MOZ_ASSERT(done());
         break;
       case CodeRange::ImportJitExit:
       case CodeRange::ImportInterpExit:
       case CodeRange::TrapExit:
+      case CodeRange::DebugTrap:
       case CodeRange::Inline:
       case CodeRange::FarJumpIsland:
         MOZ_CRASH("Should not encounter an exit during iteration");
     }
 }
 
 const char*
 FrameIterator::filename() const
@@ -203,16 +212,48 @@ FrameIterator::functionDisplayAtom() con
 unsigned
 FrameIterator::lineOrBytecode() const
 {
     MOZ_ASSERT(!done());
     return callsite_ ? callsite_->lineOrBytecode()
                      : (codeRange_ ? codeRange_->funcLineOrBytecode() : 0);
 }
 
+Instance*
+FrameIterator::instance() const
+{
+    MOZ_ASSERT(!done() && debugEnabled());
+    return TlsDataFromFP(fp_ + callsite_->stackDepth())->instance;
+}
+
+bool
+FrameIterator::debugEnabled() const
+{
+    MOZ_ASSERT(!done() && code_);
+    MOZ_ASSERT_IF(!missingFrameMessage_, codeRange_->kind() == CodeRange::Function);
+    return code_->metadata().debugEnabled;
+}
+
+DebugFrame*
+FrameIterator::debugFrame() const
+{
+    MOZ_ASSERT(!done() && debugEnabled());
+    // The fp() points to wasm::Frame.
+    void* buf = static_cast<uint8_t*>(fp_ + callsite_->stackDepth()) - DebugFrame::offsetOfFrame();
+    return static_cast<DebugFrame*>(buf);
+}
+
+const CallSite*
+FrameIterator::debugTrapCallsite() const
+{
+    MOZ_ASSERT(!done() && debugEnabled());
+    MOZ_ASSERT(callsite_->kind() == CallSite::EnterFrame || callsite_->kind() == CallSite::LeaveFrame);
+    return callsite_;
+}
+
 /*****************************************************************************/
 // Prologue/epilogue code generation
 
 // These constants reflect statically-determined offsets in the profiling
 // prologue/epilogue. The offsets are dynamically asserted during code
 // generation.
 #if defined(JS_CODEGEN_X64)
 # if defined(DEBUG)
@@ -556,16 +597,17 @@ ProfilingFrameIterator::initFromFP()
         fp = CallerFPFromFP(fp);
         callerPC_ = ReturnAddressFromFP(fp);
         callerFP_ = CallerFPFromFP(fp);
         AssertMatchesCallSite(*activation_, callerPC_, callerFP_, fp);
         break;
       case CodeRange::ImportJitExit:
       case CodeRange::ImportInterpExit:
       case CodeRange::TrapExit:
+      case CodeRange::DebugTrap:
       case CodeRange::Inline:
       case CodeRange::FarJumpIsland:
         MOZ_CRASH("Unexpected CodeRange kind");
     }
 
     // The iterator inserts a pretend innermost frame for non-None ExitReasons.
     // This allows the variety of exit reasons to show up in the callstack.
     exitReason_ = activation_->exitReason();
@@ -684,16 +726,17 @@ ProfilingFrameIterator::ProfilingFrameIt
         // The entry trampoline is the final frame in an WasmActivation. The entry
         // trampoline also doesn't GeneratePrologue/Epilogue so we can't use
         // the general unwinding logic above.
         MOZ_ASSERT(!fp);
         callerPC_ = nullptr;
         callerFP_ = nullptr;
         break;
       }
+      case CodeRange::DebugTrap:
       case CodeRange::Inline: {
         // The throw stub clears WasmActivation::fp on it's way out.
         if (!fp) {
             MOZ_ASSERT(done());
             return;
         }
 
         // Most inline code stubs execute after the prologue/epilogue have
@@ -740,16 +783,17 @@ ProfilingFrameIterator::operator++()
       case CodeRange::Entry:
         MOZ_ASSERT(callerFP_ == nullptr);
         callerPC_ = nullptr;
         break;
       case CodeRange::Function:
       case CodeRange::ImportJitExit:
       case CodeRange::ImportInterpExit:
       case CodeRange::TrapExit:
+      case CodeRange::DebugTrap:
       case CodeRange::Inline:
       case CodeRange::FarJumpIsland:
         stackAddress_ = callerFP_;
         callerPC_ = ReturnAddressFromFP(callerFP_);
         AssertMatchesCallSite(*activation_, callerPC_, CallerFPFromFP(callerFP_), callerFP_);
         callerFP_ = CallerFPFromFP(callerFP_);
         break;
     }
@@ -766,36 +810,40 @@ ProfilingFrameIterator::label() const
     // entries will be coalesced by the profiler.
     //
     // NB: these labels are parsed for location by
     //     devtools/client/performance/modules/logic/frame-utils.js
     const char* importJitDescription = "fast FFI trampoline (in asm.js)";
     const char* importInterpDescription = "slow FFI trampoline (in asm.js)";
     const char* nativeDescription = "native call (in asm.js)";
     const char* trapDescription = "trap handling (in asm.js)";
+    const char* debugTrapDescription = "debug trap handling (in asm.js)";
 
     switch (exitReason_) {
       case ExitReason::None:
         break;
       case ExitReason::ImportJit:
         return importJitDescription;
       case ExitReason::ImportInterp:
         return importInterpDescription;
       case ExitReason::Native:
         return nativeDescription;
       case ExitReason::Trap:
         return trapDescription;
+      case ExitReason::DebugTrap:
+        return debugTrapDescription;
     }
 
     switch (codeRange_->kind()) {
       case CodeRange::Function:         return code_->profilingLabel(codeRange_->funcIndex());
       case CodeRange::Entry:            return "entry trampoline (in asm.js)";
       case CodeRange::ImportJitExit:    return importJitDescription;
       case CodeRange::ImportInterpExit: return importInterpDescription;
       case CodeRange::TrapExit:         return trapDescription;
+      case CodeRange::DebugTrap:        return debugTrapDescription;
       case CodeRange::Inline:           return "inline stub (in asm.js)";
       case CodeRange::FarJumpIsland:    return "interstitial (in asm.js)";
     }
 
     MOZ_CRASH("bad code range kind");
 }
 
 /*****************************************************************************/
--- a/js/src/wasm/WasmFrameIterator.h
+++ b/js/src/wasm/WasmFrameIterator.h
@@ -28,16 +28,18 @@ namespace js {
 class WasmActivation;
 namespace jit { class MacroAssembler; }
 
 namespace wasm {
 
 class CallSite;
 class Code;
 class CodeRange;
+class DebugFrame;
+class Instance;
 class SigIdDesc;
 struct CallThunk;
 struct FuncOffsets;
 struct ProfilingOffsets;
 struct TrapOffset;
 
 // Iterates over the frames of a single WasmActivation, called synchronously
 // from C++ in the thread of the asm.js.
@@ -64,29 +66,33 @@ class FrameIterator
     explicit FrameIterator(const WasmActivation& activation);
     void operator++();
     bool done() const;
     const char* filename() const;
     const char16_t* displayURL() const;
     bool mutedErrors() const;
     JSAtom* functionDisplayAtom() const;
     unsigned lineOrBytecode() const;
-    inline void* fp() const { return fp_; }
-    inline uint8_t* pc() const { return pc_; }
+    const CodeRange* codeRange() const { return codeRange_; }
+    Instance* instance() const;
+    bool debugEnabled() const;
+    DebugFrame* debugFrame() const;
+    const CallSite* debugTrapCallsite() const;
 };
 
 // An ExitReason describes the possible reasons for leaving compiled wasm code
 // or the state of not having left compiled wasm code (ExitReason::None).
 enum class ExitReason : uint32_t
 {
     None,          // default state, the pc is in wasm code
     ImportJit,     // fast-path call directly into JIT code
     ImportInterp,  // slow-path call into C++ Invoke()
     Native,        // call to native C++ code (e.g., Math.sin, ToInt32(), interrupt)
-    Trap           // call to trap handler for the trap in WasmActivation::trap
+    Trap,          // call to trap handler for the trap in WasmActivation::trap
+    DebugTrap      // call to debug trap handler
 };
 
 // Iterates over the frames of a single WasmActivation, given an
 // asynchrously-interrupted thread's state. If the activation's
 // module is not in profiling mode, the activation is skipped.
 class ProfilingFrameIterator
 {
     const WasmActivation* activation_;
--- a/js/src/wasm/WasmGenerator.cpp
+++ b/js/src/wasm/WasmGenerator.cpp
@@ -41,16 +41,17 @@ using mozilla::MakeEnumeratedRange;
 // ModuleGenerator
 
 static const unsigned GENERATOR_LIFO_DEFAULT_CHUNK_SIZE = 4 * 1024;
 static const unsigned COMPILATION_LIFO_DEFAULT_CHUNK_SIZE = 64 * 1024;
 static const uint32_t BAD_CODE_RANGE = UINT32_MAX;
 
 ModuleGenerator::ModuleGenerator(UniqueChars* error)
   : alwaysBaseline_(false),
+    debugEnabled_(false),
     error_(error),
     numSigs_(0),
     numTables_(0),
     lifo_(GENERATOR_LIFO_DEFAULT_CHUNK_SIZE),
     masmAlloc_(&lifo_),
     masm_(MacroAssembler::WasmToken(), masmAlloc_),
     lastPatchedCallsite_(0),
     startOfUnpatchedCallsites_(0),
@@ -197,16 +198,17 @@ bool
 ModuleGenerator::init(UniqueModuleEnvironment env, const CompileArgs& args,
                       Metadata* maybeAsmJSMetadata)
 {
     env_ = Move(env);
 
     linkData_.globalDataLength = AlignBytes(InitialGlobalDataBytes, sizeof(void*));
 
     alwaysBaseline_ = args.alwaysBaseline;
+    debugEnabled_ = args.debugEnabled;
 
     if (!funcToCodeRange_.appendN(BAD_CODE_RANGE, env_->funcSigs.length()))
         return false;
 
     if (!assumptions_.clone(args.assumptions))
         return false;
 
     if (!exportedFuncs_.init())
@@ -371,35 +373,60 @@ ModuleGenerator::patchCallSites(TrapExit
                 if (!metadata_->codeRanges.emplaceBack(CodeRange::FarJumpIsland, offsets))
                     return false;
                 existingTrapFarJumps[cs.trap()] = Some(offsets.begin);
             }
 
             masm_.patchCall(callerOffset, *existingTrapFarJumps[cs.trap()]);
             break;
           }
+          case CallSiteDesc::EnterFrame:
+          case CallSiteDesc::LeaveFrame: {
+            Uint32Vector& jumps = metadata_->debugTrapFarJumpOffsets;
+            if (jumps.empty() ||
+                uint32_t(abs(int32_t(jumps.back()) - int32_t(callerOffset))) >= JumpRange())
+            {
+                Offsets offsets;
+                offsets.begin = masm_.currentOffset();
+                uint32_t jumpOffset = masm_.farJumpWithPatch().offset();
+                offsets.end = masm_.currentOffset();
+                if (masm_.oom())
+                    return false;
+
+                if (!metadata_->codeRanges.emplaceBack(CodeRange::FarJumpIsland, offsets))
+                    return false;
+                if (!debugTrapFarJumps_.emplaceBack(jumpOffset))
+                    return false;
+                if (!jumps.emplaceBack(offsets.begin))
+                    return false;
+            }
+            break;
+          }
         }
     }
 
     return true;
 }
 
 bool
-ModuleGenerator::patchFarJumps(const TrapExitOffsetArray& trapExits)
+ModuleGenerator::patchFarJumps(const TrapExitOffsetArray& trapExits, const Offsets& debugTrapStub)
 {
     for (CallThunk& callThunk : metadata_->callThunks) {
         uint32_t funcIndex = callThunk.u.funcIndex;
         callThunk.u.codeRangeIndex = funcToCodeRange_[funcIndex];
         CodeOffset farJump(callThunk.offset);
         masm_.patchFarJump(farJump, funcCodeRange(funcIndex).funcNonProfilingEntry());
     }
 
     for (const TrapFarJump& farJump : masm_.trapFarJumps())
         masm_.patchFarJump(farJump.jump, trapExits[farJump.trap].begin);
 
+    for (uint32_t debugTrapFarJump : debugTrapFarJumps_)
+        masm_.patchFarJump(CodeOffset(debugTrapFarJump), debugTrapStub.begin);
+
     return true;
 }
 
 bool
 ModuleGenerator::finishTask(CompileTask* task)
 {
     masm_.haltingAlign(CodeAlignment);
 
@@ -505,16 +532,17 @@ ModuleGenerator::finishCodegen()
     OffsetVector entries;
     ProfilingOffsetVector interpExits;
     ProfilingOffsetVector jitExits;
     TrapExitOffsetArray trapExits;
     Offsets outOfBoundsExit;
     Offsets unalignedAccessExit;
     Offsets interruptExit;
     Offsets throwStub;
+    Offsets debugTrapStub;
 
     {
         TempAllocator alloc(&lifo_);
         MacroAssembler masm(MacroAssembler::WasmToken(), alloc);
         Label throwLabel;
 
         if (!entries.resize(numFuncExports))
             return false;
@@ -532,16 +560,17 @@ ModuleGenerator::finishCodegen()
 
         for (Trap trap : MakeEnumeratedRange(Trap::Limit))
             trapExits[trap] = GenerateTrapExit(masm, trap, &throwLabel);
 
         outOfBoundsExit = GenerateOutOfBoundsExit(masm, &throwLabel);
         unalignedAccessExit = GenerateUnalignedExit(masm, &throwLabel);
         interruptExit = GenerateInterruptExit(masm, &throwLabel);
         throwStub = GenerateThrowStub(masm, &throwLabel);
+        debugTrapStub = GenerateDebugTrapStub(masm, &throwLabel);
 
         if (masm.oom() || !masm_.asmMergeWith(masm))
             return false;
     }
 
     // Adjust each of the resulting Offsets (to account for being merged into
     // masm_) and then create code ranges for all the stubs.
 
@@ -581,29 +610,33 @@ ModuleGenerator::finishCodegen()
     interruptExit.offsetBy(offsetInWhole);
     if (!metadata_->codeRanges.emplaceBack(CodeRange::Inline, interruptExit))
         return false;
 
     throwStub.offsetBy(offsetInWhole);
     if (!metadata_->codeRanges.emplaceBack(CodeRange::Inline, throwStub))
         return false;
 
+    debugTrapStub.offsetBy(offsetInWhole);
+    if (!metadata_->codeRanges.emplaceBack(CodeRange::DebugTrap, debugTrapStub))
+        return false;
+
     // Fill in LinkData with the offsets of these stubs.
 
     linkData_.outOfBoundsOffset = outOfBoundsExit.begin;
     linkData_.interruptOffset = interruptExit.begin;
 
     // Now that all other code has been emitted, patch all remaining callsites
     // then far jumps. Patching callsites can generate far jumps so there is an
     // ordering dependency.
 
     if (!patchCallSites(&trapExits))
         return false;
 
-    if (!patchFarJumps(trapExits))
+    if (!patchFarJumps(trapExits, debugTrapStub))
         return false;
 
     // Code-generation is complete!
 
     masm_.finish();
     return !masm_.oom();
 }
 
@@ -910,16 +943,18 @@ ModuleGenerator::startFuncDef(uint32_t l
     return true;
 }
 
 bool
 ModuleGenerator::launchBatchCompile()
 {
     MOZ_ASSERT(currentTask_);
 
+    currentTask_->setDebugEnabled(debugEnabled_);
+
     size_t numBatchedFuncs = currentTask_->units().length();
     MOZ_ASSERT(numBatchedFuncs);
 
     if (parallel_) {
         if (!StartOffThreadWasmCompile(currentTask_))
             return false;
         outstanding_++;
     } else {
@@ -940,19 +975,25 @@ bool
 ModuleGenerator::finishFuncDef(uint32_t funcIndex, FunctionGenerator* fg)
 {
     MOZ_ASSERT(activeFuncDef_ == fg);
 
     UniqueFuncBytes func = Move(fg->funcBytes_);
 
     func->setFunc(funcIndex, &funcSig(funcIndex));
 
-    auto mode = alwaysBaseline_ && BaselineCanCompile(fg)
-                ? CompileMode::Baseline
-                : CompileMode::Ion;
+    CompileMode mode;
+    if ((alwaysBaseline_ || debugEnabled_) && BaselineCanCompile(fg)) {
+      mode = CompileMode::Baseline;
+    } else {
+      mode = CompileMode::Ion;
+      // Ion does not support debugging -- reset debugEnabled_ flags to avoid
+      // turning debugging for wasm::Code.
+      debugEnabled_ = false;
+    }
 
     CheckedInt<uint32_t> newBatched = func->bytes().length();
     if (mode == CompileMode::Ion)
         newBatched *= JitOptions.wasmBatchIonScaleFactor;
     newBatched += batchedBytecode_;
 
     if (!currentTask_->units().emplaceBack(Move(func), mode))
         return false;
@@ -1136,31 +1177,43 @@ ModuleGenerator::finish(const ShareableB
     // These Vectors can get large and the excess capacity can be significant,
     // so realloc them down to size.
     metadata_->memoryAccesses.podResizeToFit();
     metadata_->memoryPatches.podResizeToFit();
     metadata_->boundsChecks.podResizeToFit();
     metadata_->codeRanges.podResizeToFit();
     metadata_->callSites.podResizeToFit();
     metadata_->callThunks.podResizeToFit();
+    metadata_->debugTrapFarJumpOffsets.podResizeToFit();
 
     // For asm.js, the tables vector is over-allocated (to avoid resize during
     // parallel copilation). Shrink it back down to fit.
     if (isAsmJS() && !metadata_->tables.resize(numTables_))
         return nullptr;
 
+    metadata_->debugEnabled = debugEnabled_;
+
     // Assert CodeRanges are sorted.
 #ifdef DEBUG
     uint32_t lastEnd = 0;
     for (const CodeRange& codeRange : metadata_->codeRanges) {
         MOZ_ASSERT(codeRange.begin() >= lastEnd);
         lastEnd = codeRange.end();
     }
 #endif
 
+    // Assert debugTrapFarJumpOffsets are sorted.
+#ifdef DEBUG
+    uint32_t lastOffset = 0;
+    for (uint32_t debugTrapFarJumpOffset : metadata_->debugTrapFarJumpOffsets) {
+        MOZ_ASSERT(debugTrapFarJumpOffset >= lastOffset);
+        lastOffset = debugTrapFarJumpOffset;
+    }
+#endif
+
     if (!finishLinkData(code))
         return nullptr;
 
     return SharedModule(js_new<Module>(Move(assumptions_),
                                        Move(code),
                                        Move(linkData_),
                                        Move(env_->imports),
                                        Move(env_->exports),
--- a/js/src/wasm/WasmGenerator.h
+++ b/js/src/wasm/WasmGenerator.h
@@ -139,23 +139,25 @@ typedef Vector<FuncCompileUnit, 8, Syste
 
 class CompileTask
 {
     const ModuleEnvironment&   env_;
     LifoAlloc                  lifo_;
     Maybe<jit::TempAllocator>  alloc_;
     Maybe<jit::MacroAssembler> masm_;
     FuncCompileUnitVector      units_;
+    bool                       debugEnabled_;
 
     CompileTask(const CompileTask&) = delete;
     CompileTask& operator=(const CompileTask&) = delete;
 
     void init() {
         alloc_.emplace(&lifo_);
         masm_.emplace(jit::MacroAssembler::WasmToken(), *alloc_);
+        debugEnabled_ = false;
     }
 
   public:
     CompileTask(const ModuleEnvironment& env, size_t defaultChunkSize)
       : env_(env),
         lifo_(defaultChunkSize)
     {
         init();
@@ -170,16 +172,22 @@ class CompileTask
         return env_;
     }
     jit::MacroAssembler& masm() {
         return *masm_;
     }
     FuncCompileUnitVector& units() {
         return units_;
     }
+    bool debugEnabled() const {
+        return debugEnabled_;
+    }
+    void setDebugEnabled(bool enabled) {
+        debugEnabled_ = enabled;
+    }
     bool reset(UniqueFuncBytesVector* freeFuncBytes) {
         for (FuncCompileUnit& unit : units_) {
             if (!freeFuncBytes->emplaceBack(Move(unit.recycle())))
                 return false;
         }
 
         units_.clear();
         masm_.reset();
@@ -201,16 +209,17 @@ class MOZ_STACK_CLASS ModuleGenerator
 {
     typedef HashSet<uint32_t, DefaultHasher<uint32_t>, SystemAllocPolicy> Uint32Set;
     typedef Vector<CompileTask, 0, SystemAllocPolicy> CompileTaskVector;
     typedef Vector<CompileTask*, 0, SystemAllocPolicy> CompileTaskPtrVector;
     typedef EnumeratedArray<Trap, Trap::Limit, ProfilingOffsets> TrapExitOffsetArray;
 
     // Constant parameters
     bool                            alwaysBaseline_;
+    bool                            debugEnabled_;
     UniqueChars*                    error_;
 
     // Data that is moved into the result of finish()
     Assumptions                     assumptions_;
     LinkData                        linkData_;
     MutableMetadata                 metadata_;
 
     // Data scoped to the ModuleGenerator's lifetime
@@ -220,16 +229,17 @@ class MOZ_STACK_CLASS ModuleGenerator
     LifoAlloc                       lifo_;
     jit::JitContext                 jcx_;
     jit::TempAllocator              masmAlloc_;
     jit::MacroAssembler             masm_;
     Uint32Vector                    funcToCodeRange_;
     Uint32Set                       exportedFuncs_;
     uint32_t                        lastPatchedCallsite_;