Merge autoland to mozilla-central a=merge
authorRazvan Maries <rmaries@mozilla.com>
Sat, 23 Feb 2019 06:12:29 +0200
changeset 518584 fdd04819e350ff9429bd2a2731498741c2a757a1
parent 518443 ca0ea512f874390477cb4595d9c36c8106296820 (current diff)
parent 518583 7a18c5eda62a8767aa51f8314d7be00be4bc3906 (diff)
child 518606 6924dd16f7b1e2e6ce71a8eb4cbe330d3e4745dc
push id10862
push userffxbld-merge
push dateMon, 11 Mar 2019 13:01:11 +0000
treeherdermozilla-beta@a2e7f5c935da [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmerge
milestone67.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 autoland to mozilla-central a=merge
browser/actors/FormSubmitChild.jsm
browser/components/search/searchplugins/meta-ua.xml
browser/locales/en-US/chrome/browser/pageInfo.dtd
browser/locales/en-US/chrome/browser/pageInfo.properties
devtools/client/debugger/new/src/actions/ast/setPausePoints.js
devtools/client/debugger/new/src/selectors/visiblePausePoints.js
devtools/client/debugger/new/src/utils/pause/pausePoints.js
devtools/client/debugger/new/src/utils/pause/stepping.js
devtools/client/debugger/new/src/workers/parser/pausePoints.js
devtools/client/debugger/new/src/workers/parser/tests/pausePoints.spec.js
gfx/wr/wrench/reftests/blend/multiply-2-ref.yaml
testing/web-platform/meta/css/css-fonts/font-display/__dir__.ini
toolkit/components/satchel/formSubmitListener.js
toolkit/components/satchel/jar.mn
toolkit/modules/Battery.jsm
toolkit/modules/tests/browser/browser_Battery.js
--- a/.eslintignore
+++ b/.eslintignore
@@ -324,16 +324,18 @@ testing/talos/talos/scripts/jszip.min.js
 testing/talos/talos/startup_test/sessionrestore/profile/sessionstore.js
 testing/talos/talos/startup_test/sessionrestore/profile-manywindows/sessionstore.js
 testing/talos/talos/tests/devtools/addon/content/pages/**
 testing/talos/talos/tests/dromaeo/**
 testing/talos/talos/tests/v8_7/**
 testing/talos/talos/tests/kraken/**
 # Runing Talos may extract data here, see bug 1435677.
 testing/talos/talos/tests/tp5n/**
+# Raptor third party
+testing/raptor/raptor/playback/scripts/catapult/**
 
 testing/web-platform/**
 testing/xpcshell/moz-http2/**
 testing/xpcshell/node-http2/**
 
 # Third party services
 services/common/kinto-http-client.js
 services/common/kinto-offline-client.js
--- a/CLOBBER
+++ b/CLOBBER
@@ -17,9 +17,9 @@
 #
 # Modifying this file will now automatically clobber the buildbot machines \o/
 #
 
 # Are you updating CLOBBER because you think it's needed for your WebIDL
 # changes to stick? As of bug 928195, this shouldn't be necessary! Please
 # don't change CLOBBER for WebIDL changes any more.
 
-Bug 1524688: Deleting preprocessed manifests requires clobber.
+Bug 1521879: Adding PMediaTransport.ipdl requires a clobber.
rename from browser/actors/FormSubmitChild.jsm
rename to browser/actors/FormValidationChild.jsm
--- a/browser/actors/FormSubmitChild.jsm
+++ b/browser/actors/FormValidationChild.jsm
@@ -1,25 +1,25 @@
 /* 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";
 
-/*
+/**
  * Handles the validation callback from nsIFormFillController and
  * the display of the help panel on invalid elements.
  */
 
-var EXPORTED_SYMBOLS = ["FormSubmitChild"];
+var EXPORTED_SYMBOLS = ["FormValidationChild"];
 
 const {ActorChild} = ChromeUtils.import("resource://gre/modules/ActorChild.jsm");
 const {BrowserUtils} = ChromeUtils.import("resource://gre/modules/BrowserUtils.jsm");
 const {Services} = ChromeUtils.import("resource://gre/modules/Services.jsm");
 
-class FormSubmitChild extends ActorChild {
+class FormValidationChild extends ActorChild {
   constructor(dispatcher) {
     super(dispatcher);
 
     this._validationMessage = "";
     this._element = null;
 
     this.mm.addEventListener("pageshow", this);
   }
--- a/browser/actors/moz.build
+++ b/browser/actors/moz.build
@@ -28,17 +28,17 @@ with Files("WebRTCChild.jsm"):
 FINAL_TARGET_FILES.actors += [
     'AboutReaderChild.jsm',
     'BlockedSiteChild.jsm',
     'BrowserTabChild.jsm',
     'ClickHandlerChild.jsm',
     'ContentSearchChild.jsm',
     'ContextMenuChild.jsm',
     'DOMFullscreenChild.jsm',
-    'FormSubmitChild.jsm',
+    'FormValidationChild.jsm',
     'LightweightThemeChild.jsm',
     'LightWeightThemeInstallChild.jsm',
     'LinkHandlerChild.jsm',
     'NetErrorChild.jsm',
     'OfflineAppsChild.jsm',
     'PageInfoChild.jsm',
     'PageStyleChild.jsm',
     'PluginChild.jsm',
--- a/browser/app/permissions
+++ b/browser/app/permissions
@@ -5,18 +5,16 @@
 # * type is a string that identifies the type of permission (e.g. "cookie")
 # * permission is an integer between 1 and 15
 # See nsPermissionManager.cpp for more...
 
 # UITour
 origin	uitour	1	https://www.mozilla.org
 origin	uitour	1	https://screenshots.firefox.com
 origin	uitour	1	https://support.mozilla.org
-origin	uitour	1	https://addons.mozilla.org
-origin	uitour	1	https://discovery.addons.mozilla.org
 origin	uitour	1	about:home
 origin	uitour	1	about:newtab
 
 # XPInstall
 origin	install	1	https://addons.mozilla.org
 
 # Remote troubleshooting
 origin	remote-troubleshooting	1	https://input.mozilla.org
--- a/browser/base/content/browser-pageActions.js
+++ b/browser/base/content/browser-pageActions.js
@@ -973,16 +973,46 @@ BrowserPageActions.bookmark = {
   },
 
   onCommand(event, buttonNode) {
     PanelMultiView.hidePopup(BrowserPageActions.panelNode);
     BookmarkingUI.onStarCommand(event);
   },
 };
 
+// pin tab
+BrowserPageActions.pinTab = {
+  updateState() {
+    let action = PageActions.actionForID("pinTab");
+    let { pinned } = gBrowser.selectedTab;
+    if (pinned) {
+      action.setTitle(BrowserPageActions.panelNode.getAttribute("unpinTab-title"));
+    } else {
+      action.setTitle(BrowserPageActions.panelNode.getAttribute("pinTab-title"));
+    }
+
+    let panelButton = BrowserPageActions.panelButtonNodeForActionID(action.id);
+    if (panelButton) {
+      panelButton.toggleAttribute("pinned", pinned);
+    }
+    let urlbarButton = BrowserPageActions.urlbarButtonNodeForActionID(action.id);
+    if (urlbarButton) {
+      urlbarButton.toggleAttribute("pinned", pinned);
+    }
+  },
+
+  onCommand(event, buttonNode) {
+    if (gBrowser.selectedTab.pinned) {
+      gBrowser.unpinTab(gBrowser.selectedTab);
+    } else {
+      gBrowser.pinTab(gBrowser.selectedTab);
+    }
+  },
+};
+
 // copy URL
 BrowserPageActions.copyURL = {
   onBeforePlacedInWindow(browserWindow) {
     let action = PageActions.actionForID("copyURL");
     BrowserPageActions.takeActionTitleFromPanel(action);
   },
 
   onCommand(event, buttonNode) {
--- a/browser/base/content/browser.js
+++ b/browser/base/content/browser.js
@@ -4942,16 +4942,17 @@ var XULBrowserWindow = {
     this._event = aEvent;
     this._lastLocationForEvent = spec;
 
     if (typeof(aIsSimulated) != "boolean" && typeof(aIsSimulated) != "undefined") {
       throw "onContentBlockingEvent: aIsSimulated receieved an unexpected type";
     }
 
     ContentBlocking.onContentBlockingEvent(this._event, aWebProgress, aIsSimulated);
+    gBrowser.selectedBrowser.updateSecurityUIForContentBlockingEvent(aEvent);
   },
 
   // This is called in multiple ways:
   //  1. Due to the nsIWebProgressListener.onSecurityChange notification.
   //  2. Called by tabbrowser.xml when updating the current browser.
   //  3. Called directly during this object's initializations.
   // aRequest will be null always in case 2 and 3, and sometimes in case 1.
   onSecurityChange(aWebProgress, aRequest, aState, aIsSimulated) {
--- a/browser/base/content/browser.xul
+++ b/browser/base/content/browser.xul
@@ -568,16 +568,18 @@ xmlns="http://www.w3.org/1999/xhtml"
            role="group"
            type="arrow"
            hidden="true"
            flip="slide"
            photon="true"
            position="bottomcenter topright"
            tabspecific="true"
            noautofocus="true"
+           pinTab-title="&pinTab.label;"
+           unpinTab-title="&unpinTab.label;"
            pocket-title="&saveToPocketCmd.label;"
            copyURL-title="&pageAction.copyLink.label;"
            emailLink-title="&emailPageCmd.label;"
            sendToDevice-notReadyTitle="&sendToDevice.syncNotReady.label;"
            shareURL-title="&pageAction.shareUrl.label;"
            shareMore-label="&pageAction.shareMore.label;">
       <panelmultiview id="pageActionPanelMultiView"
                       mainViewId="pageActionPanelMainView"
--- a/browser/base/content/content.js
+++ b/browser/base/content/content.js
@@ -36,21 +36,27 @@ addMessageListener("RemoteLogins:fillFor
 });
 
 function shouldIgnoreLoginManagerEvent(event) {
   // If we have a null principal then prevent any more password manager code from running and
   // incorrectly using the document `location`.
   return event.target.nodePrincipal.isNullPrincipal;
 }
 
+addEventListener("DOMFormBeforeSubmit", function(event) {
+  if (shouldIgnoreLoginManagerEvent(event)) {
+    return;
+  }
+  LoginManagerContent.onDOMFormBeforeSubmit(event);
+});
 addEventListener("DOMFormHasPassword", function(event) {
   if (shouldIgnoreLoginManagerEvent(event)) {
     return;
   }
-  LoginManagerContent.onDOMFormHasPassword(event, content);
+  LoginManagerContent.onDOMFormHasPassword(event);
   let formLike = LoginFormFactory.createFromForm(event.originalTarget);
   InsecurePasswordUtils.reportInsecurePasswords(formLike);
 });
 addEventListener("DOMInputPasswordAdded", function(event) {
   if (shouldIgnoreLoginManagerEvent(event)) {
     return;
   }
   LoginManagerContent.onDOMInputPasswordAdded(event, content);
--- a/browser/base/content/contentSearchUI.js
+++ b/browser/base/content/contentSearchUI.js
@@ -76,18 +76,18 @@ ContentSearchUIController.prototype = {
     return this._defaultEngine;
   },
 
   set defaultEngine(engine) {
     if (this._defaultEngine && this._defaultEngine.icon) {
       URL.revokeObjectURL(this._defaultEngine.icon);
     }
     let icon;
-    if (engine.iconBuffer) {
-      icon = this._getFaviconURIFromBuffer(engine.iconBuffer);
+    if (engine.iconData) {
+      icon = this._getFaviconURIFromIconData(engine.iconData);
     } else {
       icon = "chrome://mozapps/skin/places/defaultFavicon.svg";
     }
     this._defaultEngine = {
       name: engine.name,
       icon,
     };
     this._updateDefaultEngineHeader();
@@ -693,19 +693,25 @@ ContentSearchUIController.prototype = {
         entry.appendChild(document.createTextNode(" "));
       }
     }
 
     row.appendChild(entry);
     return row;
   },
 
-  // Converts favicon array buffer into a data URI.
-  _getFaviconURIFromBuffer(buffer) {
-    let blob = new Blob([buffer]);
+  // If the favicon is an array buffer, convert it into a Blob URI.
+  // Otherwise just return the plain URI.
+  _getFaviconURIFromIconData(data) {
+    if (typeof(data) == "string") {
+      return data;
+    }
+
+    // If typeof(data) != "string", we assume it's an ArrayBuffer
+    let blob = new Blob([data]);
     return URL.createObjectURL(blob);
   },
 
   // Adds "@2x" to the name of the given PNG url for "retina" screens.
   _getImageURIForCurrentResolution(uri) {
     if (window.devicePixelRatio > 1) {
       return uri.replace(/\.png$/, "@2x.png");
     }
@@ -870,18 +876,18 @@ ContentSearchUIController.prototype = {
         cell = document.createElementNS(HTML_NS, "td");
         row.setAttribute("class", "contentSearchSuggestionsContainer");
         cell.setAttribute("class", "contentSearchSuggestionsContainer");
       }
       let button = document.createElementNS(HTML_NS, "button");
       button.setAttribute("class", "contentSearchOneOffItem");
       let img = document.createElementNS(HTML_NS, "img");
       let uri;
-      if (engine.iconBuffer) {
-        uri = this._getFaviconURIFromBuffer(engine.iconBuffer);
+      if (engine.iconData) {
+        uri = this._getFaviconURIFromIconData(engine.iconData);
       } else {
         uri = this._getImageURIForCurrentResolution(
           "chrome://browser/skin/search-engine-placeholder.png");
       }
       img.setAttribute("src", uri);
       img.addEventListener("load", function() {
         URL.revokeObjectURL(uri);
       }, {once: true});
--- a/browser/base/content/nsContextMenu.js
+++ b/browser/base/content/nsContextMenu.js
@@ -713,18 +713,19 @@ nsContextMenu.prototype = {
 
   initPasswordManagerItems() {
     let loginFillInfo = gContextMenuContentData && gContextMenuContentData.loginFillInfo;
 
     // If we could not find a password field we
     // don't want to show the form fill option.
     let showFill = loginFillInfo && loginFillInfo.passwordField.found;
 
-    // Disable the fill option if the user has set a master password
+    // Disable the fill option if the user hasn't unlocked with their master password
     // or if the password field or target field are disabled.
+    // XXX: Bug 1529025 to respect signon.rememberSignons
     let disableFill = !loginFillInfo ||
                       !Services.logins ||
                       !Services.logins.isLoggedIn ||
                       loginFillInfo.passwordField.disabled ||
                       (!this.onPassword && loginFillInfo.usernameField.disabled);
 
     this.showItem("fill-login-separator", showFill);
     this.showItem("fill-login", showFill);
--- a/browser/base/content/pageinfo/pageInfo.js
+++ b/browser/base/content/pageinfo/pageInfo.js
@@ -167,28 +167,16 @@ gImageView.getCellProperties = function(
     props += "broken";
 
   if (col.element.id == "image-address")
     props += " ltr";
 
   return props;
 };
 
-gImageView.getCellText = function(row, column) {
-  var value = this.data[row][column.index];
-  if (column.index == COL_IMAGE_SIZE) {
-    if (value == -1) {
-      return gStrings.unknown;
-    }
-    var kbSize = Number(Math.round(value / 1024 * 100) / 100);
-    return gBundle.getFormattedString("mediaFileSize", [kbSize]);
-  }
-  return value || "";
-};
-
 gImageView.onPageMediaSort = function(columnname) {
   var tree = document.getElementById(this.treeid);
   var treecol = tree.columns.getNamedColumn(columnname);
 
   var comparator;
   var index = treecol.index;
   if (index == COL_IMAGE_SIZE || index == COL_IMAGE_COUNT) {
     comparator = function numComparator(a, b) { return a - b; };
@@ -292,31 +280,30 @@ var onFinished = [ ];
 var onUnloadRegistry = [ ];
 
 /* Called when PageInfo window is loaded.  Arguments are:
  *  window.arguments[0] - (optional) an object consisting of
  *                         - doc: (optional) document to use for source. if not provided,
  *                                the calling window's document will be used
  *                         - initialTab: (optional) id of the inital tab to display
  */
-function onLoadPageInfo() {
-  gBundle = document.getElementById("pageinfobundle");
-  gStrings.unknown = gBundle.getString("unknown");
-  gStrings.notSet = gBundle.getString("notset");
-  gStrings.mediaImg = gBundle.getString("mediaImg");
-  gStrings.mediaBGImg = gBundle.getString("mediaBGImg");
-  gStrings.mediaBorderImg = gBundle.getString("mediaBorderImg");
-  gStrings.mediaListImg = gBundle.getString("mediaListImg");
-  gStrings.mediaCursor = gBundle.getString("mediaCursor");
-  gStrings.mediaObject = gBundle.getString("mediaObject");
-  gStrings.mediaEmbed = gBundle.getString("mediaEmbed");
-  gStrings.mediaLink = gBundle.getString("mediaLink");
-  gStrings.mediaInput = gBundle.getString("mediaInput");
-  gStrings.mediaVideo = gBundle.getString("mediaVideo");
-  gStrings.mediaAudio = gBundle.getString("mediaAudio");
+async function onLoadPageInfo() {
+  gStrings.unknown = await document.l10n.formatValue("image-size-unknown");
+  gStrings.notSet = await document.l10n.formatValue("not-set-alternative-text");
+  gStrings.mediaImg = await document.l10n.formatValue("media-img");
+  gStrings.mediaBGImg = await document.l10n.formatValue("media-bg-img");
+  gStrings.mediaBorderImg = await document.l10n.formatValue("media-border-img");
+  gStrings.mediaListImg = await document.l10n.formatValue("media-list-img");
+  gStrings.mediaCursor = await document.l10n.formatValue("media-cursor");
+  gStrings.mediaObject = await document.l10n.formatValue("media-object");
+  gStrings.mediaEmbed = await document.l10n.formatValue("media-embed");
+  gStrings.mediaLink = await document.l10n.formatValue("media-link");
+  gStrings.mediaInput = await document.l10n.formatValue("media-input");
+  gStrings.mediaVideo = await document.l10n.formatValue("media-video");
+  gStrings.mediaAudio = await document.l10n.formatValue("media-audio");
 
   var args = "arguments" in window &&
              window.arguments.length >= 1 &&
              window.arguments[0];
 
   // init media view
   var imageTree = document.getElementById("imagetree");
   imageTree.view = gImageView;
@@ -333,34 +320,33 @@ function loadPageInfo(frameOuterWindowID
   let imageInfo = imageElement;
 
   // Look for pageInfoListener in content.js. Sends message to listener with arguments.
   mm.sendAsyncMessage("PageInfo:getData", {strings: gStrings, frameOuterWindowID});
 
   let pageInfoData;
 
   // Get initial pageInfoData needed to display the general, permission and security tabs.
-  mm.addMessageListener("PageInfo:data", function onmessage(message) {
+  mm.addMessageListener("PageInfo:data", async function onmessage(message) {
     mm.removeMessageListener("PageInfo:data", onmessage);
     pageInfoData = message.data;
     let docInfo = pageInfoData.docInfo;
     let windowInfo = pageInfoData.windowInfo;
     let uri = Services.io.newURI(docInfo.documentURIObject.spec);
     let principal = docInfo.principal;
     gDocInfo = docInfo;
 
     gImageElement = imageInfo;
-
-    var titleFormat = windowInfo.isTopWindow ? "pageInfo.page.title"
-                                             : "pageInfo.frame.title";
-    document.title = gBundle.getFormattedString(titleFormat, [docInfo.location]);
+    var titleFormat = windowInfo.isTopWindow ? "page-info-page"
+                                             : "page-info-frame";
+    document.l10n.setAttributes(document.documentElement, titleFormat, {"website": docInfo.location});
 
     document.getElementById("main-window").setAttribute("relatedUrl", docInfo.location);
 
-    makeGeneralTab(pageInfoData.metaViewRows, docInfo);
+    await makeGeneralTab(pageInfoData.metaViewRows, docInfo);
     onLoadPermission(uri, principal);
     securityOnLoad(uri, windowInfo);
   });
 
   // Get the media elements from content script to setup the media tab.
   mm.addMessageListener("PageInfo:mediaData", function onmessage(message) {
     // Page info window was closed.
     if (window.closed) {
@@ -460,96 +446,106 @@ function openCacheEntry(key, cb) {
     },
     onCacheEntryAvailable(entry, isNew, appCache, status) {
       cb(entry);
     },
   };
   diskStorage.asyncOpenURI(Services.io.newURI(key), "", nsICacheStorage.OPEN_READONLY, checkCacheListener);
 }
 
-function makeGeneralTab(metaViewRows, docInfo) {
-  var title = (docInfo.title) ? docInfo.title : gBundle.getString("noPageTitle");
-  document.getElementById("titletext").value = title;
+async function makeGeneralTab(metaViewRows, docInfo) {
+  // Sets Title in the General Tab, set to "Untitled Page" if no title found
+  if (docInfo.title) {
+    document.getElementById("titletext").value = docInfo.title;
+  } else {
+    document.l10n.setAttributes(document.getElementById("titletext"), "no-page-title");
+  }
 
   var url = docInfo.location;
   setItemValue("urltext", url);
 
   var referrer = ("referrer" in docInfo && docInfo.referrer);
   setItemValue("refertext", referrer);
 
-  var mode = ("compatMode" in docInfo && docInfo.compatMode == "BackCompat") ? "generalQuirksMode" : "generalStrictMode";
-  document.getElementById("modetext").value = gBundle.getString(mode);
+  var mode = ("compatMode" in docInfo && docInfo.compatMode == "BackCompat") ? "general-quirks-mode" : "general-strict-mode";
+  document.l10n.setAttributes(document.getElementById("modetext"), mode);
 
   // find out the mime type
   var mimeType = docInfo.contentType;
   setItemValue("typetext", mimeType);
 
   // get the document characterset
   var encoding = docInfo.characterSet;
   document.getElementById("encodingtext").value = encoding;
 
   let length = metaViewRows.length;
 
   var metaGroup = document.getElementById("metaTags");
   if (!length)
     metaGroup.style.visibility = "hidden";
   else {
-    var metaTagsCaption = document.getElementById("metaTagsCaption");
-    if (length == 1)
-      metaTagsCaption.value = gBundle.getString("generalMetaTag");
-    else
-      metaTagsCaption.value = gBundle.getFormattedString("generalMetaTags", [length]);
+    document.l10n.setAttributes(document.getElementById("metaTagsCaption"),
+                                "general-meta-tags", {"tags": length});
+
     var metaTree = document.getElementById("metatree");
     metaTree.view = gMetaView;
 
     // Add the metaViewRows onto the general tab's meta info tree.
     gMetaView.addRows(metaViewRows);
 
     metaGroup.style.removeProperty("visibility");
   }
 
-  // get the date of last modification
-  var modifiedText = formatDate(docInfo.lastModified, gStrings.notSet);
+  var modifiedText = formatDate(docInfo.lastModified, await document.l10n.formatValue("not-set-date"));
   document.getElementById("modifiedtext").value = modifiedText;
 
   // get cache info
   var cacheKey = url.replace(/#.*$/, "");
   openCacheEntry(cacheKey, function(cacheEntry) {
     var sizeText;
     if (cacheEntry) {
       var pageSize = cacheEntry.dataSize;
       var kbSize = formatNumber(Math.round(pageSize / 1024 * 100) / 100);
-      sizeText = gBundle.getFormattedString("generalSize", [kbSize, formatNumber(pageSize)]);
+      document.l10n.setAttributes(document.getElementById("sizetext"),
+                                  "properties-general-size",
+                                  {"kb": kbSize, "bytes": formatNumber(pageSize)});
+    } else {
+      setItemValue("sizetext", sizeText);
     }
-    setItemValue("sizetext", sizeText);
   });
 }
 
-function addImage(imageViewRow) {
+async function addImage(imageViewRow) {
   let [url, type, alt, elem, isBg] = imageViewRow;
-
   if (!url)
     return;
 
   if (!gImageHash.hasOwnProperty(url))
     gImageHash[url] = { };
   if (!gImageHash[url].hasOwnProperty(type))
     gImageHash[url][type] = { };
   if (!gImageHash[url][type].hasOwnProperty(alt)) {
     gImageHash[url][type][alt] = gImageView.data.length;
-    var row = [url, type, -1, alt, 1, elem, isBg];
+    var row = [url, type, gStrings.unknown, alt, 1, elem, isBg];
     gImageView.addRow(row);
 
     // Fill in cache data asynchronously
     openCacheEntry(url, function(cacheEntry) {
       // The data at row[2] corresponds to the data size.
       if (cacheEntry) {
-        row[2] = cacheEntry.dataSize;
-        // Invalidate the row to trigger a repaint.
-        gImageView.tree.invalidateRow(gImageView.data.indexOf(row));
+        let value = cacheEntry.dataSize;
+        // If value is not -1 then replace with actual value, else keep as "unknown"
+        if (value != -1) {
+          let kbSize = Number(Math.round(value / 1024 * 100) / 100);
+          document.l10n.formatValue("media-file-size", {"size": kbSize}).then(function(response) {
+            row[2] = response;
+            // Invalidate the row to trigger a repaint.
+            gImageView.tree.invalidateRow(gImageView.data.indexOf(row));
+          });
+        }
       }
     });
 
     // Add the observer, only once.
     if (gImageView.data.length == 1) {
       document.getElementById("mediaTab").hidden = false;
       Services.obs.addObserver(imagePermissionObserver, "perm-changed");
     }
@@ -616,20 +612,20 @@ function getSelectedRows(tree) {
   return rowArray;
 }
 
 function getSelectedRow(tree) {
   var rows = getSelectedRows(tree);
   return (rows.length == 1) ? rows[0] : -1;
 }
 
-function selectSaveFolder(aCallback) {
+async function selectSaveFolder(aCallback) {
   const nsIFile = Ci.nsIFile;
   const nsIFilePicker = Ci.nsIFilePicker;
-  let titleText = gBundle.getString("mediaSelectFolder");
+  let titleText = await document.l10n.formatValue("media-select-folder");
   let fp = Cc["@mozilla.org/filepicker;1"].
            createInstance(nsIFilePicker);
   let fpCallback = function fpCallback_done(aResult) {
     if (aResult == nsIFilePicker.returnOK) {
       aCallback(fp.file.QueryInterface(nsIFile));
     } else {
       aCallback(null);
     }
@@ -754,49 +750,52 @@ function makePreview(row) {
   setItemValue("imageurltext", url);
   setItemValue("imagetext", item.imageText);
   setItemValue("imagelongdesctext", item.longDesc);
 
   // get cache info
   var cacheKey = url.replace(/#.*$/, "");
   openCacheEntry(cacheKey, function(cacheEntry) {
     // find out the file size
-    var sizeText;
     if (cacheEntry) {
       let imageSize = cacheEntry.dataSize;
       var kbSize = Math.round(imageSize / 1024 * 100) / 100;
-      sizeText = gBundle.getFormattedString("generalSize",
-                                            [formatNumber(kbSize), formatNumber(imageSize)]);
-    } else
-      sizeText = gBundle.getString("mediaUnknownNotCached");
-    setItemValue("imagesizetext", sizeText);
+      document.l10n.setAttributes(document.getElementById("imagesizetext"),
+                                  "properties-general-size",
+                                  {"kb": formatNumber(kbSize), "bytes": formatNumber(imageSize)});
+    } else {
+      document.l10n.setAttributes(document.getElementById("imagesizetext"),
+                                  "media-unknown-not-cached");
+    }
 
     var mimeType = item.mimeType || this.getContentTypeFromHeaders(cacheEntry);
     var numFrames = item.numFrames;
 
     var imageType;
     if (mimeType) {
       // We found the type, try to display it nicely
       let imageMimeType = /^image\/(.*)/i.exec(mimeType);
       if (imageMimeType) {
         imageType = imageMimeType[1].toUpperCase();
         if (numFrames > 1)
-          imageType = gBundle.getFormattedString("mediaAnimatedImageType",
-                                                 [imageType, numFrames]);
+          document.l10n.setAttributes(document.getElementById("imagetypetext"),
+                                      "media-animated-image-type",
+                                      {"type": imageType, "frames": numFrames});
         else
-          imageType = gBundle.getFormattedString("mediaImageType", [imageType]);
+          document.l10n.setAttributes(document.getElementById("imagetypetext"),
+                                      "media-image-type",
+                                      {"type": imageType});
       } else {
         // the MIME type doesn't begin with image/, display the raw type
-        imageType = mimeType;
+        setItemValue("imagetypetext", mimeType);
       }
     } else {
       // We couldn't find the type, fall back to the value in the treeview
-      imageType = gImageView.data[row][COL_IMAGE_TYPE];
+      setItemValue("imagetypetext", gImageView.data[row][COL_IMAGE_TYPE]);
     }
-    setItemValue("imagetypetext", imageType);
 
     var imageContainer = document.getElementById("theimagecontainer");
     var oldImage = document.getElementById("thepreviewimage");
 
     var isProtocolAllowed = checkProtocol(gImageView.data[row]);
 
     var newImage = new Image;
     newImage.id = "thepreviewimage";
@@ -842,31 +841,31 @@ function makePreview(row) {
         }
 
         width = newImage.width;
         height = newImage.height;
 
         document.getElementById("theimagecontainer").collapsed = false;
         document.getElementById("brokenimagecontainer").collapsed = true;
 
-        let imageSize = "";
         if (url) {
           if (width != physWidth || height != physHeight) {
-            imageSize = gBundle.getFormattedString("mediaDimensionsScaled",
-                                                   [formatNumber(physWidth),
-                                                    formatNumber(physHeight),
-                                                    formatNumber(width),
-                                                    formatNumber(height)]);
+            document.l10n.setAttributes(document.getElementById("imagedimensiontext"),
+                                        "media-dimensions-scaled",
+                                        {"dimx": formatNumber(physWidth),
+                                         "dimy": formatNumber(physHeight),
+                                         "scaledx": formatNumber(width),
+                                         "scaledy": formatNumber(height)});
           } else {
-            imageSize = gBundle.getFormattedString("mediaDimensions",
-                                                   [formatNumber(width),
-                                                    formatNumber(height)]);
+            document.l10n.setAttributes(document.getElementById("imagedimensiontext"),
+                                        "media-dimensions",
+                                        {"dimx": formatNumber(width),
+                                         "dimy": formatNumber(height)});
           }
         }
-        setItemValue("imagedimensiontext", imageSize);
       }, {once: true});
 
       newImage.setAttribute("triggeringprincipal", triggeringPrinStr);
       newImage.setAttribute("src", url);
     } else {
       // Handle the case where newImage is not used for width & height
       if (item.HTMLVideoElement && isProtocolAllowed) {
         newImage = document.createElementNS("http://www.w3.org/1999/xhtml", "video");
@@ -891,23 +890,22 @@ function makePreview(row) {
         document.getElementById("brokenimagecontainer").collapsed = true;
       } else {
         // fallback image for protocols not allowed (e.g., javascript:)
         // or elements not [yet] handled (e.g., object, embed).
         document.getElementById("brokenimagecontainer").collapsed = false;
         document.getElementById("theimagecontainer").collapsed = true;
       }
 
-      let imageSize = "";
       if (url && !isAudio) {
-        imageSize = gBundle.getFormattedString("mediaDimensions",
-                                               [formatNumber(width),
-                                                formatNumber(height)]);
+        document.l10n.setAttributes(document.getElementById("imagedimensiontext"),
+                                    "media-dimensions",
+                                    {"dimx": formatNumber(width),
+                                     "dimy": formatNumber(height)});
       }
-      setItemValue("imagedimensiontext", imageSize);
     }
 
     makeBlockImage(url);
 
     imageContainer.removeChild(oldImage);
     imageContainer.appendChild(newImage);
   });
 }
@@ -921,17 +919,17 @@ function makeBlockImage(url) {
   if (!(/^https?:/.test(url)) || imagePref == 2)
     // We can't block the images from this host because either is is not
     // for http(s) or we don't load images at all
     checkbox.hidden = true;
   else {
     var uri = Services.io.newURI(url);
     if (uri.host) {
       checkbox.hidden = false;
-      checkbox.label = gBundle.getFormattedString("mediaBlockImage", [uri.host]);
+      document.l10n.setAttributes(checkbox, "media-block-image", {"website": uri.host});
       var perm = permissionManager.testPermission(uri, "image");
       checkbox.checked = perm == nsIPermissionManager.DENY_ACTION;
     } else
       checkbox.hidden = true;
   }
 }
 
 var imagePermissionObserver = {
--- a/browser/base/content/pageinfo/pageInfo.xul
+++ b/browser/base/content/pageinfo/pageInfo.xul
@@ -2,385 +2,384 @@
 # 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/.
 
 <?xml-stylesheet href="chrome://browser/content/pageinfo/pageInfo.css" type="text/css"?>
 <?xml-stylesheet href="chrome://browser/skin/pageInfo.css" type="text/css"?>
 
 <!DOCTYPE window [
-  <!ENTITY % pageInfoDTD SYSTEM "chrome://browser/locale/pageInfo.dtd">
-  %pageInfoDTD;
 #ifdef XP_MACOSX
 #include ../browser-doctype.inc
 #endif
 ]>
 
 <window id="main-window"
   xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+  xmlns:html="http://www.w3.org/1999/xhtml"
+  data-l10n-id="page-info-window"
+  data-l10n-attrs="style"
   windowtype="Browser:page-info"
   onload="onLoadPageInfo()"
   onunload="onUnloadPageInfo()"
   align="stretch"
   screenX="10" screenY="10"
-  width="&pageInfoWindow.width;" height="&pageInfoWindow.height;"
   persist="screenX screenY width height sizemode">
 
 #ifdef XP_MACOSX
 #include ../macWindow.inc.xul
 #endif
 
+  <linkset>
+    <html:link rel="localization" href="browser/pageInfo.ftl"/>
+  </linkset>
   <script type="application/javascript" src="chrome://global/content/globalOverlay.js"/>
   <script type="application/javascript" src="chrome://global/content/contentAreaUtils.js"/>
   <script type="application/javascript" src="chrome://global/content/treeUtils.js"/>
   <script type="application/javascript" src="chrome://browser/content/pageinfo/pageInfo.js"/>
   <script type="application/javascript" src="chrome://browser/content/pageinfo/permissions.js"/>
   <script type="application/javascript" src="chrome://browser/content/pageinfo/security.js"/>
   <script type="application/javascript" src="chrome://browser/content/utilityOverlay.js"/>
 
   <stringbundleset id="pageinfobundleset">
-    <stringbundle id="pageinfobundle" src="chrome://browser/locale/pageInfo.properties"/>
     <stringbundle id="pkiBundle" src="chrome://pippki/locale/pippki.properties"/>
     <stringbundle id="browserBundle" src="chrome://browser/locale/browser.properties"/>
   </stringbundleset>
 
   <commandset id="pageInfoCommandSet">
     <command id="cmd_close"     oncommand="window.close();"/>
     <command id="cmd_help"      oncommand="doHelpButton();"/>
     <command id="cmd_copy"      oncommand="doCopy();"/>
     <command id="cmd_selectall" oncommand="doSelectAll();"/>
   </commandset>
 
   <keyset id="pageInfoKeySet">
-    <key key="&closeWindow.key;" modifiers="accel" command="cmd_close"/>
+    <key data-l10n-id="close-window" modifiers="accel" command="cmd_close"/>
     <key keycode="VK_ESCAPE"                       command="cmd_close"/>
 #ifdef XP_MACOSX
     <key key="."                 modifiers="meta"  command="cmd_close"/>
 #else
     <key keycode="VK_F1"                           command="cmd_help"/>
 #endif
-    <key key="&copy.key;"        modifiers="accel" command="cmd_copy"/>
-    <key key="&selectall.key;"   modifiers="accel" command="cmd_selectall"/>
-    <key key="&selectall.key;"   modifiers="alt"   command="cmd_selectall"/>
+    <key data-l10n-id="copy"        modifiers="accel" command="cmd_copy"/>
+    <key data-l10n-id="select-all"  modifiers="accel" command="cmd_selectall"/>
+    <key data-l10n-id="select-all"  modifiers="alt"   command="cmd_selectall"/>
   </keyset>
 
   <menupopup id="picontext">
-    <menuitem id="menu_selectall" label="&selectall.label;" command="cmd_selectall" accesskey="&selectall.accesskey;"/>
-    <menuitem id="menu_copy"      label="&copy.label;"      command="cmd_copy"      accesskey="&copy.accesskey;"/>
+    <menuitem id="menu_selectall" data-l10n-id="menu-select-all" command="cmd_selectall"/>
+    <menuitem id="menu_copy"      data-l10n-id="menu-copy"      command="cmd_copy"/>
   </menupopup>
 
   <vbox id="topBar">
     <radiogroup id="viewGroup" class="chromeclass-toolbar" orient="horizontal">
-      <radio id="generalTab"  label="&generalTab;"  accesskey="&generalTab.accesskey;"
+      <radio id="generalTab"  data-l10n-id="general-tab"
            oncommand="showTab('general');"/>
-      <radio id="mediaTab"    label="&mediaTab;"    accesskey="&mediaTab.accesskey;"
+      <radio id="mediaTab"    data-l10n-id="media-tab"
            oncommand="showTab('media');" hidden="true"/>
-      <radio id="permTab"     label="&permTab;"     accesskey="&permTab.accesskey;"
+      <radio id="permTab"     data-l10n-id="perm-tab"
            oncommand="showTab('perm');"/>
-      <radio id="securityTab" label="&securityTab;" accesskey="&securityTab.accesskey;"
+      <radio id="securityTab" data-l10n-id="security-tab"
            oncommand="showTab('security');"/>
     </radiogroup>
   </vbox>
 
   <deck id="mainDeck" flex="1">
     <!-- General page information -->
     <vbox id="generalPanel">
       <grid id="generalGrid">
         <columns>
           <column/>
           <column class="gridSeparator"/>
           <column flex="1"/>
         </columns>
         <rows id="generalRows">
           <row id="generalTitle">
-            <label control="titletext" value="&generalTitle;"/>
+            <label control="titletext" data-l10n-id="general-title"/>
             <separator/>
             <textbox readonly="true" id="titletext"/>
           </row>
           <row id="generalURLRow">
-            <label control="urltext" value="&generalURL;"/>
+            <label control="urltext" data-l10n-id="general-url"/>
             <separator/>
             <textbox readonly="true" id="urltext"/>
           </row>
           <row id="generalSeparatorRow1">
             <separator class="thin"/>
           </row>
           <row id="generalTypeRow">
-            <label control="typetext" value="&generalType;"/>
+            <label control="typetext" data-l10n-id="general-type"/>
             <separator/>
             <textbox readonly="true" id="typetext"/>
           </row>
           <row id="generalModeRow">
-            <label control="modetext" value="&generalMode;"/>
+            <label control="modetext" data-l10n-id="general-mode"/>
             <separator/>
             <textbox readonly="true" crop="end" id="modetext"/>
           </row>
           <row id="generalEncodingRow">
-            <label control="encodingtext" value="&generalEncoding2;"/>
+            <label control="encodingtext" data-l10n-id="general-encoding"/>
             <separator/>
             <textbox readonly="true" id="encodingtext"/>
           </row>
           <row id="generalSizeRow">
-            <label control="sizetext" value="&generalSize;"/>
+            <label control="sizetext" data-l10n-id="general-size"/>
             <separator/>
             <textbox readonly="true" id="sizetext"/>
           </row>
           <row id="generalReferrerRow">
-            <label control="refertext" value="&generalReferrer;"/>
+            <label control="refertext" data-l10n-id="general-referrer"/>
             <separator/>
             <textbox readonly="true" id="refertext"/>
           </row>
           <row id="generalSeparatorRow2">
             <separator class="thin"/>
           </row>
           <row id="generalModifiedRow">
-            <label control="modifiedtext" value="&generalModified;"/>
+            <label control="modifiedtext" data-l10n-id="general-modified"/>
             <separator/>
             <textbox readonly="true" id="modifiedtext"/>
           </row>
         </rows>
       </grid>
       <separator class="thin"/>
       <vbox id="metaTags" flex="1">
         <label control="metatree" id="metaTagsCaption" class="header"/>
         <tree id="metatree" flex="1" hidecolumnpicker="true" contextmenu="picontext">
           <treecols>
-            <treecol id="meta-name"    label="&generalMetaName;"
+            <treecol id="meta-name"    data-l10n-id="general-meta-name"
                      persist="width" flex="1"
                      onclick="gMetaView.onPageMediaSort('meta-name');"/>
             <splitter class="tree-splitter"/>
-            <treecol id="meta-content" label="&generalMetaContent;"
+            <treecol id="meta-content" data-l10n-id="general-meta-content"
                      persist="width" flex="4"
                      onclick="gMetaView.onPageMediaSort('meta-content');"/>
           </treecols>
           <treechildren id="metatreechildren" flex="1"/>
-        </tree>        
+        </tree>
       </vbox>
       <hbox pack="end">
-        <button command="cmd_help" label="&helpButton.label;" dlgtype="help"/>
+        <button command="cmd_help" data-l10n-id="help-button" dlgtype="help"/>
       </hbox>
     </vbox>
 
     <!-- Media information -->
     <vbox id="mediaPanel">
       <tree id="imagetree" onselect="onImageSelect();" contextmenu="picontext"
             ondragstart="onBeginLinkDrag(event,'image-address','image-alt')">
         <treecols>
           <treecol primary="true" persist="width" flex="10"
-                        width="10" id="image-address" label="&mediaAddress;"
+                        width="10" id="image-address" data-l10n-id="media-address"
                         onclick="gImageView.onPageMediaSort('image-address');"/>
           <splitter class="tree-splitter"/>
           <treecol persist="hidden width" flex="2"
-                        width="2"  id="image-type"    label="&mediaType;"
+                        width="2"  id="image-type"    data-l10n-id="media-type"
                         onclick="gImageView.onPageMediaSort('image-type');"/>
           <splitter class="tree-splitter"/>
           <treecol hidden="true" persist="hidden width" flex="2"
-                        width="2"  id="image-size"  label="&mediaSize;" value="size"
+                        width="2"  id="image-size"  data-l10n-id="media-size" value="size"
                         onclick="gImageView.onPageMediaSort('image-size');"/>
           <splitter class="tree-splitter"/>
           <treecol hidden="true" persist="hidden width" flex="4"
-                        width="4"  id="image-alt"    label="&mediaAltHeader;"
+                        width="4"  id="image-alt"    data-l10n-id="media-alt-header"
                         onclick="gImageView.onPageMediaSort('image-alt');"/>
           <splitter class="tree-splitter"/>
           <treecol hidden="true" persist="hidden width" flex="1"
-                        width="1"  id="image-count"    label="&mediaCount;"
+                        width="1"  id="image-count"    data-l10n-id="media-count"
                         onclick="gImageView.onPageMediaSort('image-count');"/>
         </treecols>
         <treechildren id="imagetreechildren" flex="1"/>
       </tree>
       <splitter orient="vertical" id="mediaSplitter"/>
       <vbox flex="1" id="mediaPreviewBox" collapsed="true">
         <grid id="mediaGrid">
           <columns>
             <column id="mediaLabelColumn"/>
             <column class="gridSeparator"/>
             <column flex="1"/>
           </columns>
           <rows id="mediaRows">
             <row id="mediaLocationRow">
-              <label control="imageurltext" value="&mediaLocation;"/>
+              <label control="imageurltext" data-l10n-id="media-location"/>
               <separator/>
               <textbox readonly="true" id="imageurltext"/>
             </row>
             <row id="mediaTypeRow">
-              <label control="imagetypetext" value="&generalType;"/>
+              <label control="imagetypetext" data-l10n-id="general-type"/>
               <separator/>
-              <textbox readonly="true" id="imagetypetext"/>
+              <textbox id="imagetypetext"/>
             </row>
             <row id="mediaSizeRow">
-              <label control="imagesizetext" value="&generalSize;"/>
+              <label control="imagesizetext" data-l10n-id="general-size"/>
               <separator/>
               <textbox readonly="true" id="imagesizetext"/>
             </row>
             <row id="mediaDimensionRow">
-              <label control="imagedimensiontext" value="&mediaDimension;"/>
+              <label control="imagedimensiontext" data-l10n-id="media-dimension"/>
               <separator/>
               <textbox readonly="true" id="imagedimensiontext"/>
             </row>
             <row id="mediaTextRow">
-              <label control="imagetext" value="&mediaText;"/>
+              <label control="imagetext" data-l10n-id="media-text"/>
               <separator/>
               <textbox readonly="true" id="imagetext"/>
             </row>
             <row id="mediaLongdescRow">
-              <label control="imagelongdesctext" value="&mediaLongdesc;"/>
+              <label control="imagelongdesctext" data-l10n-id="media-long-desc"/>
               <separator/>
               <textbox readonly="true" id="imagelongdesctext"/>
             </row>
           </rows>
         </grid>
         <hbox id="imageSaveBox" align="end">
           <vbox id="blockImageBox">
             <checkbox id="blockImage" hidden="true" oncommand="onBlockImage()"
-                      accesskey="&mediaBlockImage.accesskey;"/>
-            <label control="thepreviewimage" value="&mediaPreview;" class="header"/>
+                      data-l10n-id="media-block-image"/>
+            <label control="thepreviewimage" data-l10n-id="media-preview" class="header"/>
           </vbox>
           <spacer id="imageSaveBoxSpacer" flex="1"/>
-          <button label="&selectall.label;" accesskey="&selectall.accesskey;"
+          <button data-l10n-id="menu-select-all"
                   id="selectallbutton"
                   oncommand="doSelectAllMedia();"/>
-          <button label="&mediaSaveAs;" accesskey="&mediaSaveAs.accesskey;"
+          <button data-l10n-id="media-save-as"
                   icon="save" id="imagesaveasbutton"
                   oncommand="saveMedia();"/>
         </hbox>
         <vbox id="imagecontainerbox" flex="1" pack="center">
           <hbox id="theimagecontainer" pack="center">
             <image id="thepreviewimage"/>
           </hbox>
           <hbox id="brokenimagecontainer" pack="center" collapsed="true">
             <image id="brokenimage" src="resource://gre-resources/broken-image.png"/>
           </hbox>
         </vbox>
       </vbox>
       <hbox id="mediaSaveBox" collapsed="true">
         <spacer id="mediaSaveBoxSpacer" flex="1"/>
-        <button label="&mediaSaveAs;" accesskey="&mediaSaveAs2.accesskey;"
+        <button data-l10n-id="media-save-image-as"
                 icon="save" id="mediasaveasbutton"
                 oncommand="saveMedia();"/>
       </hbox>
       <hbox pack="end">
-        <button command="cmd_help" label="&helpButton.label;" dlgtype="help"/>
+        <button command="cmd_help" data-l10n-id="help-button" dlgtype="help"/>
       </hbox>
     </vbox>
 
     <!-- Permissions -->
     <vbox id="permPanel">
       <hbox id="permHostBox">
-        <label value="&permissionsFor;" control="hostText" />
+        <label data-l10n-id="permissions-for" control="hostText" />
         <textbox id="hostText" class="header" readonly="true"
                  crop="end" flex="1"/>
       </hbox>
 
       <vbox id="permList" flex="1"/>
       <hbox pack="end">
-        <button command="cmd_help" label="&helpButton.label;" dlgtype="help"/>
+        <button command="cmd_help" data-l10n-id="help-button" dlgtype="help"/>
       </hbox>
     </vbox>
 
     <!-- Security & Privacy -->
     <vbox id="securityPanel">
       <!-- Identity Section -->
       <groupbox>
-        <label class="header" value="&securityView.identity.header;"/>
+        <label class="header" data-l10n-id="security-view-identity"/>
         <grid>
           <columns>
             <column/>
             <column flex="1"/>
           </columns>
           <rows>
             <!-- Domain -->
             <row>
-              <label value="&securityView.identity.domain;"
+              <label data-l10n-id="security-view-identity-domain"
                      control="security-identity-domain-value"/>
               <textbox id="security-identity-domain-value" readonly="true"/>
             </row>
             <!-- Owner -->
             <row>
               <label id="security-identity-owner-label"
                      class="fieldLabel"
-                     value="&securityView.identity.owner;"
+                     data-l10n-id="security-view-identity-owner"
                      control="security-identity-owner-value"/>
               <textbox id="security-identity-owner-value" readonly="true"/>
             </row>
             <!-- Verifier -->
             <row>
-              <label value="&securityView.identity.verifier;"
+              <label data-l10n-id="security-view-identity-verifier"
                      control="security-identity-verifier-value"/>
               <hbox align="center">
                 <textbox id="security-identity-verifier-value" readonly="true"
                          flex="1"/>
-                <button id="security-view-cert" label="&securityView.certView;"
-                        accesskey="&securityView.accesskey;"
+                <button id="security-view-cert" data-l10n-id="security-view"
                         oncommand="security.viewCert();"/>
               </hbox>
             </row>
             <!-- Certificate Validity -->
             <row id="security-identity-validity-row">
-              <label value="&securityView.identity.validity;"
+              <label data-l10n-id="security-view-identity-validity"
                      control="security-identity-validity-value"/>
               <textbox id="security-identity-validity-value" readonly="true"/>
             </row>
           </rows>
         </grid>
       </groupbox>
 
       <!-- Privacy & History section -->
       <groupbox>
-        <label class="header" value="&securityView.privacy.header;"/>
+        <label class="header" data-l10n-id="security-view-privacy"/>
         <grid>
           <columns>
             <column flex="1"/>
             <column flex="1"/>
           </columns>
           <rows>
             <!-- History -->
             <row>
-              <label control="security-privacy-history-value">&securityView.privacy.history;</label>
+              <label control="security-privacy-history-value" data-l10n-id="security-view-privacy-history-value"/>
               <label id="security-privacy-history-value"
-                     value="&securityView.unknown;"/>
+                     data-l10n-id="security-view-unknown"/>
             </row>
             <!-- Site Data & Cookies -->
-            <row>
-              <label control="security-privacy-sitedata-value">&securityView.privacy.siteData;</label>
+            <row id="security-privacy-sitedata-row">
+              <label control="security-privacy-sitedata-value" data-l10n-id="security-view-privacy-sitedata-value"/>
               <hbox id="security-privacy-sitedata-box" align="center">
-                <label id="security-privacy-sitedata-value"
-                       flex="1">&securityView.unknown;</label>
+                <label id="security-privacy-sitedata-value" data-l10n-id="security-view-unknown"
+                       flex="1"/>
                 <button id="security-clear-sitedata"
                         disabled="true"
-                        label="&securityView.privacy.clearSiteData;"
-                        accesskey="&securityView.privacy.clearSiteData.accessKey;"
+                        data-l10n-id="security-view-privacy-clearsitedata"
                         oncommand="security.clearSiteData();"/>
               </hbox>
             </row>
             <!-- Passwords -->
             <row>
-              <label control="security-privacy-passwords-value">&securityView.privacy.passwords;</label>
+              <label control="security-privacy-passwords-value" data-l10n-id="security-view-privacy-passwords-value"/>
               <hbox id="security-privacy-passwords-box" align="center">
                 <label id="security-privacy-passwords-value"
-                       value="&securityView.unknown;"
+                       data-l10n-id="security-view-unknown"
                        flex="1"/>
                 <button id="security-view-password"
-                        label="&securityView.privacy.viewPasswords;"
-                        accesskey="&securityView.privacy.viewPasswords.accessKey;"
+                        data-l10n-id="security-view-privacy-viewpasswords"
                         oncommand="security.viewPasswords();"/>
               </hbox>
             </row>
           </rows>
         </grid>
       </groupbox>
 
       <!-- Technical Details section -->
       <groupbox>
-        <label class="header" value="&securityView.technical.header;"/>
+        <label class="header" data-l10n-id="security-view-technical"/>
         <label id="security-technical-shortform"/>
         <description id="security-technical-longform1"/>
         <description id="security-technical-longform2"/>
         <description id="security-technical-certificate-transparency"/>
       </groupbox>
 
       <hbox pack="end">
-        <button command="cmd_help" label="&helpButton.label;" dlgtype="help"/>
+        <button command="cmd_help" data-l10n-id="help-button" dlgtype="help"/>
       </hbox>
     </vbox>
     <!-- Others added by overlay -->
   </deck>
 
 </window>
--- a/browser/base/content/pageinfo/permissions.js
+++ b/browser/base/content/pageinfo/permissions.js
@@ -133,17 +133,17 @@ function createRow(aPartId) {
 
   let controls = document.createXULElement("hbox");
   controls.setAttribute("role", "group");
   controls.setAttribute("aria-labelledby", labelId);
 
   let checkbox = document.createXULElement("checkbox");
   checkbox.setAttribute("id", aPartId + "Def");
   checkbox.setAttribute("oncommand", "onCheckboxClick('" + aPartId + "');");
-  checkbox.setAttribute("label", gBundle.getString("permissions.useDefault"));
+  document.l10n.setAttributes(checkbox, "permissions-use-default");
   controls.appendChild(checkbox);
 
   let spacer = document.createXULElement("spacer");
   spacer.setAttribute("flex", "1");
   controls.appendChild(spacer);
 
   let radiogroup = document.createXULElement("radiogroup");
   radiogroup.setAttribute("id", radiogroupId);
--- a/browser/base/content/pageinfo/security.js
+++ b/browser/base/content/pageinfo/security.js
@@ -133,44 +133,40 @@ var security = {
     return null;
   },
 
   async _updateSiteDataInfo() {
     // Save site data info for deleting.
     this.siteData = await SiteDataManager.getSites(
       SiteDataManager.getBaseDomainFromHost(this.uri.host));
 
-    let pageInfoBundle = document.getElementById("pageinfobundle");
     let clearSiteDataButton = document.getElementById("security-clear-sitedata");
     let siteDataLabel = document.getElementById("security-privacy-sitedata-value");
 
     if (!this.siteData.length) {
-      let noStr = pageInfoBundle.getString("securitySiteDataNo");
-      siteDataLabel.textContent = noStr;
+      document.l10n.setAttributes(siteDataLabel, "security-site-data-no");
       clearSiteDataButton.setAttribute("disabled", "true");
       return;
     }
 
-    let usageText;
     let usage = this.siteData.reduce((acc, site) => acc + site.usage, 0);
     if (usage > 0) {
       let size = DownloadUtils.convertByteUnits(usage);
       let hasCookies = this.siteData.some(site => site.cookies.length > 0);
       if (hasCookies) {
-        usageText = pageInfoBundle.getFormattedString("securitySiteDataCookies", size);
+        document.l10n.setAttributes(siteDataLabel, "security-site-data-cookies", {"value": size[0], "unit": size[1]});
       } else {
-        usageText = pageInfoBundle.getFormattedString("securitySiteDataOnly", size);
+         document.l10n.setAttributes(siteDataLabel, "security-site-data-only", {"value": size[0], "unit": size[1]});
       }
     } else {
       // We're storing cookies, else the list would have been empty.
-      usageText = pageInfoBundle.getString("securitySiteDataCookiesOnly");
+       document.l10n.setAttributes(siteDataLabel, "security-site-data-cookies-only");
     }
 
     clearSiteDataButton.removeAttribute("disabled");
-    siteDataLabel.textContent = usageText;
   },
 
   /**
    * Clear Site Data and Cookies
    */
   clearSiteData() {
     if (this.siteData && this.siteData.length) {
       let hosts = this.siteData.map(site => site.host);
@@ -195,83 +191,80 @@ function securityOnLoad(uri, windowInfo)
 
   var info = security._getSecurityInfo();
   if (!info || uri.scheme === "about") {
     document.getElementById("securityTab").hidden = true;
     return;
   }
   document.getElementById("securityTab").hidden = false;
 
-  const pageInfoBundle = document.getElementById("pageinfobundle");
-
   /* Set Identity section text */
   setText("security-identity-domain-value", info.hostName);
 
-  var owner, verifier, validity;
+  var validity;
   if (info.cert && !info.isBroken) {
     validity = info.cert.validity.notAfterLocalDay;
 
     // Try to pull out meaningful values.  Technically these fields are optional
     // so we'll employ fallbacks where appropriate.  The EV spec states that Org
     // fields must be specified for subject and issuer so that case is simpler.
     if (info.isEV) {
-      owner = info.cert.organization;
-      verifier = info.cAName;
+      setText("security-identity-owner-value", info.cert.organization);
+      setText("security-identity-verifier-value", info.cAName);
     } else {
       // Technically, a non-EV cert might specify an owner in the O field or not,
       // depending on the CA's issuing policies.  However we don't have any programmatic
       // way to tell those apart, and no policy way to establish which organization
       // vetting standards are good enough (that's what EV is for) so we default to
       // treating these certs as domain-validated only.
-      owner = pageInfoBundle.getString("securityNoOwner");
-      verifier = info.cAName || info.cert.issuerCommonName || info.cert.issuerName;
+      document.l10n.setAttributes(document.getElementById("security-identity-owner-value"),
+        "security-no-owner");
+      setText("security-identity-verifier-value", info.cAName || info.cert.issuerCommonName || info.cert.issuerName);
     }
   } else {
     // We don't have valid identity credentials.
-    owner = pageInfoBundle.getString("securityNoOwner");
-    verifier = pageInfoBundle.getString("notset");
+    document.l10n.setAttributes(document.getElementById("security-identity-owner-value"),
+                                "security-no-owner");
+    document.l10n.setAttributes(document.getElementById("security-identity-verifier-value"),
+                                "not-set-verified-by");
   }
 
-  setText("security-identity-owner-value", owner);
-  setText("security-identity-verifier-value", verifier);
   if (validity) {
     setText("security-identity-validity-value", validity);
   } else {
     document.getElementById("security-identity-validity-row").hidden = true;
   }
 
   /* Manage the View Cert button*/
   var viewCert = document.getElementById("security-view-cert");
   if (info.cert) {
     security._cert = info.cert;
     viewCert.collapsed = false;
   } else
     viewCert.collapsed = true;
 
   /* Set Privacy & History section text */
-  var yesStr = pageInfoBundle.getString("yes");
-  var noStr = pageInfoBundle.getString("no");
 
   // Only show quota usage data for websites, not internal sites.
   if (uri.scheme == "http" || uri.scheme == "https") {
     SiteDataManager.updateSites().then(() => security._updateSiteDataInfo());
   } else {
     document.getElementById("security-privacy-sitedata-row").hidden = true;
   }
 
-  setText("security-privacy-passwords-value",
-          realmHasPasswords(uri) ? yesStr : noStr);
-
-  var visitCount = previousVisitCount(info.hostName);
+  if (realmHasPasswords(uri)) {
+    document.l10n.setAttributes(document.getElementById("security-privacy-passwords-value"),
+                                "saved-passwords-yes");
+  } else {
+    document.l10n.setAttributes(document.getElementById("security-privacy-passwords-value"),
+                                "saved-passwords-no");
+  }
 
-  let visitCountStr = visitCount > 0
-    ? PluralForm.get(visitCount, pageInfoBundle.getString("securityVisitsNumber"))
-        .replace("#1", visitCount.toLocaleString())
-    : pageInfoBundle.getString("securityNoVisits");
-  setText("security-privacy-history-value", visitCountStr);
+  document.l10n.setAttributes(document.getElementById("security-privacy-history-value"),
+                              "security-visits-number", {"visits": previousVisitCount(info.hostName)});
 
   /* Set the Technical Detail section messages */
   const pkiBundle = document.getElementById("pkiBundle");
   var hdr;
   var msg1;
   var msg2;
 
   if (info.isBroken) {
--- a/browser/base/content/test/pageActions/browser_page_action_menu.js
+++ b/browser/base/content/test/pageActions/browser_page_action_menu.js
@@ -86,16 +86,68 @@ add_task(async function bookmark() {
 
     // Done.
     hiddenPromise = promisePageActionPanelHidden();
     BrowserPageActions.panelNode.hidePopup();
     await hiddenPromise;
   });
 });
 
+add_task(async function pinTabFromPanel() {
+  // Open an actionable page so that the main page action button appears.  (It
+  // does not appear on about:blank for example.)
+  let url = "http://example.com/";
+  await BrowserTestUtils.withNewTab(url, async () => {
+    // Open the panel and click Pin Tab.
+    await promisePageActionPanelOpen();
+
+    let pinTabButton = document.getElementById("pageAction-panel-pinTab");
+    Assert.equal(pinTabButton.label, "Pin Tab");
+    let hiddenPromise = promisePageActionPanelHidden();
+    EventUtils.synthesizeMouseAtCenter(pinTabButton, {});
+    await hiddenPromise;
+
+    Assert.ok(gBrowser.selectedTab.pinned, "Tab was pinned");
+
+    // Open the panel and click Unpin Tab.
+    Assert.equal(pinTabButton.label, "Unpin Tab");
+    await promisePageActionPanelOpen();
+
+    hiddenPromise = promisePageActionPanelHidden();
+    EventUtils.synthesizeMouseAtCenter(pinTabButton, {});
+    await hiddenPromise;
+
+    Assert.ok(!gBrowser.selectedTab.pinned, "Tab was unpinned");
+  });
+});
+
+
+add_task(async function pinTabFromURLBar() {
+  // Open an actionable page so that the main page action button appears.  (It
+  // does not appear on about:blank for example.)
+  let url = "http://example.com/";
+  await BrowserTestUtils.withNewTab(url, async () => {
+    // Add action to URL bar.
+    let action = PageActions._builtInActions.find(a => a.id == "pinTab");
+    action.pinnedToUrlbar = true;
+    registerCleanupFunction(() => action.pinnedToUrlbar = false);
+
+    // Click the Pin Tab button.
+    let pinTabButton = document.getElementById("pageAction-urlbar-pinTab");
+    EventUtils.synthesizeMouseAtCenter(pinTabButton, {});
+    await BrowserTestUtils.waitForCondition(() => gBrowser.selectedTab.pinned,
+      "Tab was pinned");
+
+    // Click the Unpin Tab button
+    EventUtils.synthesizeMouseAtCenter(pinTabButton, {});
+    await BrowserTestUtils.waitForCondition(() => !gBrowser.selectedTab.pinned,
+      "Tab was unpinned");
+  });
+});
+
 add_task(async function emailLink() {
   // Open an actionable page so that the main page action button appears.  (It
   // does not appear on about:blank for example.)
   let url = "http://example.com/";
   await BrowserTestUtils.withNewTab(url, async () => {
     // Replace the email-link entry point to check whether it's called.
     let originalFn = MailIntegration.sendLinkForBrowser;
     let fnCalled = false;
--- a/browser/base/content/test/performance/browser_startup_content.js
+++ b/browser/base/content/test/performance/browser_startup_content.js
@@ -36,20 +36,16 @@ const whitelist = {
 
     // Logging related
     "resource://gre/modules/Log.jsm",
 
     // Session store
     "resource:///modules/sessionstore/ContentSessionStore.jsm",
     "resource://gre/modules/sessionstore/SessionHistory.jsm",
 
-    // Forms and passwords
-    "resource://formautofill/FormAutofill.jsm",
-    "resource://formautofill/FormAutofillContent.jsm",
-
     // Browser front-end
     "resource:///actors/AboutReaderChild.jsm",
     "resource:///actors/BrowserTabChild.jsm",
     "resource:///modules/ContentMetaHandler.jsm",
     "resource:///actors/LinkHandlerChild.jsm",
     "resource:///actors/PageStyleChild.jsm",
     "resource:///actors/SearchTelemetryChild.jsm",
     "resource://gre/modules/ActorChild.jsm",
@@ -83,20 +79,18 @@ const whitelist = {
 
     // Extensions
     "resource://gre/modules/addons/Content.js",
   ]),
   processScripts: new Set([
     "chrome://global/content/process-content.js",
     "resource:///modules/ContentObservers.js",
     "data:,ChromeUtils.import('resource://gre/modules/ExtensionProcessScript.jsm')",
-    "chrome://satchel/content/formSubmitListener.js",
     "resource://devtools/client/jsonview/converter-observer.js",
     "resource://gre/modules/WebRequestContent.js",
-    "data:,new function() {\n      ChromeUtils.import(\"resource://formautofill/FormAutofillContent.jsm\");\n    }",
   ]),
 };
 
 // Items on this list are allowed to be loaded but not
 // required, as opposed to items in the main whitelist,
 // which are all required.
 const intermittently_loaded_whitelist = {
   modules: new Set([
--- a/browser/base/content/test/static/browser_all_files_referenced.js
+++ b/browser/base/content/test/static/browser_all_files_referenced.js
@@ -139,18 +139,16 @@ var whitelist = [
   // Bug 1348533
   {file: "chrome://mozapps/skin/downloads/buttons.png", platforms: ["macosx"]},
   {file: "chrome://mozapps/skin/downloads/downloadButtons.png", platforms: ["linux", "win"]},
   // Bug 1348558
   {file: "chrome://mozapps/skin/update/downloadButtons.png",
    platforms: ["linux"]},
   // Bug 1348559
   {file: "chrome://pippki/content/resetpassword.xul"},
-  // Bug 1351078
-  {file: "resource://gre/modules/Battery.jsm"},
   // Bug 1337345
   {file: "resource://gre/modules/Manifest.jsm"},
   // Bug 1351097
   {file: "resource://gre/modules/accessibility/AccessFu.jsm"},
   // Bug 1356043
   {file: "resource://gre/modules/PerfMeasurement.jsm"},
   // Bug 1356045
   {file: "chrome://global/content/test-ipc.xul"},
--- a/browser/base/content/test/trackingUI/browser.ini
+++ b/browser/base/content/test/trackingUI/browser.ini
@@ -25,10 +25,11 @@ support-files =
   file_trackingUI_fetch.js^headers^
 [browser_trackingUI_fingerprinters.js]
 [browser_trackingUI_open_preferences.js]
 [browser_trackingUI_pbmode_exceptions.js]
 [browser_trackingUI_report_breakage.js]
 [browser_trackingUI_state.js]
 skip-if = serviceworker_e10s # see https://bugzilla.mozilla.org/show_bug.cgi?id=1511303#c1
 [browser_trackingUI_state_all_disabled.js]
+[browser_trackingUI_state_reset.js]
 [browser_trackingUI_telemetry.js]
 [browser_trackingUI_trackers_subview.js]
new file mode 100644
--- /dev/null
+++ b/browser/base/content/test/trackingUI/browser_trackingUI_state_reset.js
@@ -0,0 +1,47 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+const TP_PREF = "privacy.trackingprotection.enabled";
+const TRACKING_PAGE = "http://tracking.example.org/browser/browser/base/content/test/trackingUI/trackingPage.html";
+const BENIGN_PAGE = "http://tracking.example.org/browser/browser/base/content/test/trackingUI/benignPage.html";
+
+/* This asserts that the content blocking event state is correctly reset
+ * when navigating to a new location, and that the user is correctly
+ * reset when switching between tabs. */
+
+add_task(async function testResetOnLocationChange() {
+  Services.prefs.setBoolPref(TP_PREF, true);
+
+  let tab = await BrowserTestUtils.openNewForegroundTab(gBrowser, BENIGN_PAGE);
+  let browser = tab.linkedBrowser;
+
+  is(browser.securityUI.contentBlockingEvent, 0, "Benign page has no content blocking event");
+  ok(!ContentBlocking.iconBox.hasAttribute("active"), "shield is not active");
+
+  await Promise.all([promiseTabLoadEvent(tab, TRACKING_PAGE),
+                     waitForContentBlockingEvent(2)]);
+
+  is(browser.securityUI.contentBlockingEvent, Ci.nsIWebProgressListener.STATE_BLOCKED_TRACKING_CONTENT, "Tracking page has a content blocking event");
+  ok(ContentBlocking.iconBox.hasAttribute("active"), "shield is active");
+
+  await promiseTabLoadEvent(tab, BENIGN_PAGE);
+
+  is(browser.securityUI.contentBlockingEvent, 0, "Benign page has no content blocking event");
+  ok(!ContentBlocking.iconBox.hasAttribute("active"), "shield is not active");
+
+  let contentBlockingEvent = waitForContentBlockingEvent(3);
+  let trackingTab = await BrowserTestUtils.openNewForegroundTab(gBrowser, TRACKING_PAGE);
+  await contentBlockingEvent;
+
+  is(trackingTab.linkedBrowser.securityUI.contentBlockingEvent, Ci.nsIWebProgressListener.STATE_BLOCKED_TRACKING_CONTENT, "Tracking page has a content blocking event");
+  ok(ContentBlocking.iconBox.hasAttribute("active"), "shield is active");
+
+  gBrowser.selectedTab = tab;
+  is(browser.securityUI.contentBlockingEvent, 0, "Benign page has no content blocking event");
+  ok(!ContentBlocking.iconBox.hasAttribute("active"), "shield is not active");
+
+  gBrowser.removeTab(trackingTab);
+  gBrowser.removeTab(tab);
+
+  Services.prefs.clearUserPref(TP_PREF);
+});
--- a/browser/components/BrowserGlue.jsm
+++ b/browser/components/BrowserGlue.jsm
@@ -118,19 +118,19 @@ let ACTORS = {
       },
       messages: [
         "DOMFullscreen:Entered",
         "DOMFullscreen:CleanUp",
       ],
     },
   },
 
-  FormSubmit: {
+  FormValidation: {
     child: {
-      module: "resource:///actors/FormSubmitChild.jsm",
+      module: "resource:///actors/FormValidationChild.jsm",
       events: {
         "MozInvalidForm": {},
       },
     },
   },
 
   LightWeightThemeInstall: {
     child: {
--- a/browser/components/enterprisepolicies/Policies.jsm
+++ b/browser/components/enterprisepolicies/Policies.jsm
@@ -480,17 +480,17 @@ var Policies = {
           setDefaultPref("network.trr.uri", param.ProviderURL.href);
         }
       }
     },
   },
 
   "DontCheckDefaultBrowser": {
     onBeforeUIStartup(manager, param) {
-      setAndLockPref("browser.shell.checkDefaultBrowser", false);
+      setAndLockPref("browser.shell.checkDefaultBrowser", !param);
     },
   },
 
   "EnableTrackingProtection": {
     onBeforeUIStartup(manager, param) {
       if (param.Value) {
         if (param.Locked) {
           setAndLockPref("privacy.trackingprotection.enabled", true);
--- a/browser/components/enterprisepolicies/tests/browser/browser_policy_default_browser_check.js
+++ b/browser/components/enterprisepolicies/tests/browser/browser_policy_default_browser_check.js
@@ -20,8 +20,23 @@ add_task(async function test_default_bro
 
   is(ShellService.shouldCheckDefaultBrowser, false, "Policy changed it to not check");
 
   // Try to change it to true and check that it doesn't take effect
   ShellService.shouldCheckDefaultBrowser = true;
 
   is(ShellService.shouldCheckDefaultBrowser, false, "Policy is enforced");
 });
+
+add_task(async function test_default_browser_check() {
+  await setupPolicyEngineWithJson({
+    "policies": {
+      "DontCheckDefaultBrowser": false,
+    },
+  });
+
+  is(ShellService.shouldCheckDefaultBrowser, true, "Policy changed it to check");
+
+  // Try to change it to false and check that it doesn't take effect
+  ShellService.shouldCheckDefaultBrowser = false;
+
+  is(ShellService.shouldCheckDefaultBrowser, true, "Policy is enforced");
+});
--- a/browser/components/pocket/content/SaveToPocket.jsm
+++ b/browser/components/pocket/content/SaveToPocket.jsm
@@ -28,17 +28,17 @@ var PocketPageAction = {
     if (!this.pageAction) {
       this.pageAction = PageActions.addAction(new PageActions.Action({
         id,
         title: "pocket-title",
         pinnedToUrlbar: true,
         wantsIframe: true,
         urlbarIDOverride: "pocket-button-box",
         anchorIDOverride: "pocket-button",
-        _insertBeforeActionID: PageActions.ACTION_ID_BOOKMARK_SEPARATOR,
+        _insertBeforeActionID: PageActions.ACTION_ID_PIN_TAB,
         _urlbarNodeInMarkup: true,
         onBeforePlacedInWindow(window) {
           let action = PageActions.actionForID("pocket");
           window.BrowserPageActions.takeActionTitleFromPanel(action);
         },
         onIframeShowing(iframe, panel) {
           Pocket.onShownInPhotonPageActionPanel(panel, iframe);
 
--- a/browser/components/search/searchplugins/list.json
+++ b/browser/components/search/searchplugins/list.json
@@ -816,17 +816,17 @@
         "visibleDefaultEngines": [
           "google-b-d", "bing", "amazondotcom", "ddg", "wikipedia-es"
         ]
       }
     },
     "uk": {
       "default": {
         "visibleDefaultEngines": [
-          "google-b-d", "bing", "meta-ua", "ddg", "wikipedia-uk", "hotline-ua"
+          "google-b-d", "bing", "ddg", "wikipedia-uk", "hotline-ua"
         ]
       }
     },
     "ur": {
       "default": {
         "visibleDefaultEngines": [
           "google-b-d", "bing", "amazon-in", "ddg", "twitter", "wikipedia-ur"
         ]
deleted file mode 100644
--- a/browser/components/search/searchplugins/meta-ua.xml
+++ /dev/null
@@ -1,20 +0,0 @@
-<!-- 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/. -->
-
-<SearchPlugin xmlns="http://www.mozilla.org/2006/browser/search/">
-<ShortName>&lt;META&gt;</ShortName>
-<Description>Українська пошукова система.</Description>
-<InputEncoding>windows-1251</InputEncoding>
-<Image width="16" height="16">data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAMAAAAoLQ9TAAAABGdBTUEAAK/INwWK6QAAABl0RVh0U29mdHdhcmUAQWRvYmUgSW1hZ2VSZWFkeXHJZTwAAABgUExURUJicNDy9Oz19AJ2thGF1YvN9Cqa87bm9Eax9C2Uzv/eH2u36wBsqwJ/xHjG812686WGCjSi9ESi26je9MadABqN4JyyxBiAunV2VA5ZmP/1hCeDwCNmj3Ogwhd/vP///4N3QeoAAAAgdFJOU/////////////////////////////////////////8AXFwb7QAAAKZJREFUeNpEj+sWgyAMgykKFE3FG5ub23j/t1zRedZfzXcgaUypA8wo55hD72R7/AF6a4mlCPADROQ4i6WIE3yWkNhHCi7D6MOhb5g5D2FgFiP6n8LdbK9F9QQTLYUlGfscuepiEFTz1nXr7nNNQRq4jbdx3aybW9GUxstsyb3VK3FNKXgsuqqXUxccd9S8JiW+QFNX71H1eelUAbQzrrYQuep/BRgAuVcP4GB+AzgAAAAASUVORK5CYII=</Image>
-<SearchForm>http://search.meta.ua/</SearchForm>
-<Url type="text/html" method="GET" template="http://meta.ua/search.asp" resultdomain="meta.ua">
-    <Param name="q" value="{searchTerms}"/>
-    <Param name="sourceid" value="Mozilla-search"/>
-</Url>
-<Url type="application/x-suggestions+json" method="GET" template="http://meta.ua/suggestions/">
-    <Param name="output" value="fxjson"/>
-    <Param name="q" value="{searchTerms}"/>
-    <Param name="sourceid" value="Mozilla-suggestqueries"/>
-</Url>
-</SearchPlugin>
--- a/browser/components/urlbar/UrlbarInput.jsm
+++ b/browser/components/urlbar/UrlbarInput.jsm
@@ -109,27 +109,31 @@ class UrlbarInput {
     // our handleEvent at the right time.
     this.eventBufferer = new UrlbarEventBufferer(this);
     this.inputField.addEventListener("blur", this.eventBufferer);
     this.inputField.addEventListener("keydown", this.eventBufferer);
 
     const inputFieldEvents = [
       "focus", "input", "keyup", "mouseover", "paste", "scrollend", "select",
       "overflow", "underflow", "dragstart", "dragover", "drop",
+      "compositionstart", "compositionend",
     ];
     for (let name of inputFieldEvents) {
       this.inputField.addEventListener(name, this);
     }
 
     this.addEventListener("mousedown", this);
     this.view.panel.addEventListener("popupshowing", this);
     this.view.panel.addEventListener("popuphidden", this);
 
     this.inputField.controllers.insertControllerAt(0, new CopyCutController(this));
     this._initPasteAndGo();
+
+    // Tracks IME composition.
+    this._compositionState == UrlbarUtils.COMPOSITION.NONE;
   }
 
   /**
    * Shortens the given value, usually by removing http:// and trailing slashes,
    * such that calling nsIURIFixup::createFixupURI with the result will produce
    * the same URI.
    *
    * @param {string} val
@@ -984,16 +988,36 @@ class UrlbarInput {
     }
     this.removeAttribute("actiontype");
 
     if (!value && this.view.isOpen) {
       this.view.close();
       return;
     }
 
+    // During composition with an IME, the following events happen in order:
+    // 1. a compositionstart event
+    // 2. some input events
+    // 3. a compositionend event
+    // 4. an input event
+
+    // We should do nothing during composition.
+    if (this._compositionState == UrlbarUtils.COMPOSITION.COMPOSING) {
+      return;
+    }
+
+    if (this._compositionState == UrlbarUtils.COMPOSITION.COMMIT) {
+      this._compositionState = UrlbarUtils.COMPOSITION.NONE;
+    }
+
+    // Note: if in the future we should re-implement the legacy optimization
+    // where we didn't search again when the string is the same, skip it if we
+    // are committing a composition; since the search was canceled on
+    // composition start, we should restart it.
+
     // XXX Fill in lastKey, and add anything else we need.
     this.startQuery({
       lastKey: null,
     });
   }
 
   _on_select(event) {
     if (!Services.clipboard.supportsSelectionClipboard()) {
@@ -1082,16 +1106,36 @@ class UrlbarInput {
     this.controller.handleKeyNavigation(event);
     this._toggleActionOverride(event);
   }
 
   _on_keyup(event) {
     this._toggleActionOverride(event);
   }
 
+  _on_compositionstart(event) {
+    if (this._compositionState == UrlbarUtils.COMPOSITION.COMPOSING) {
+      throw new Error("Trying to start a nested composition?");
+    }
+    this._compositionState = UrlbarUtils.COMPOSITION.COMPOSING;
+
+    // Close the view. This will also stop searching.
+    this.closePopup();
+  }
+
+  _on_compositionend(event) {
+    if (this._compositionState != UrlbarUtils.COMPOSITION.COMPOSING) {
+      throw new Error("Trying to stop a non existing composition?");
+    }
+
+    // We can't yet retrieve the committed value from the editor, since it isn't
+    // completely committed yet. We'll handle it at the next input event.
+    this._compositionState = UrlbarUtils.COMPOSITION.COMMIT;
+  }
+
   _on_popupshowing() {
     this.setAttribute("open", "true");
   }
 
   _on_popuphidden() {
     this.removeAttribute("open");
   }
 
--- a/browser/components/urlbar/UrlbarUtils.jsm
+++ b/browser/components/urlbar/UrlbarUtils.jsm
@@ -102,16 +102,23 @@ var UrlbarUtils = {
   },
 
   // This defines icon locations that are common used in the UI.
   ICON: {
     DEFAULT: Ci.nsIFaviconService.FAVICON_DEFAULT_URL,
     SEARCH_GLASS: "chrome://browser/skin/search-glass.svg",
   },
 
+  // IME composition states.
+  COMPOSITION: {
+    NONE: 1,
+    COMPOSING: 2,
+    COMMIT: 3,
+  },
+
   /**
    * Adds a url to history as long as it isn't in a private browsing window,
    * and it is valid.
    *
    * @param {string} url The url to add to history.
    * @param {nsIDomWindow} window The window from where the url is being added.
    */
   addToUrlbarHistory(url, window) {
--- a/browser/extensions/formautofill/FormAutofillContent.jsm
+++ b/browser/extensions/formautofill/FormAutofillContent.jsm
@@ -238,16 +238,18 @@ let ProfileAutocomplete = {
 
     FormAutofill.defineLazyLogGetter(this, "ProfileAutocomplete");
     this.debug("ensureRegistered");
     this._factory = new AutocompleteFactory();
     this._factory.register(AutofillProfileAutoCompleteSearch);
     this._registered = true;
 
     Services.obs.addObserver(this, "autocomplete-will-enter-text");
+
+    this.debug("ensureRegistered. Finished with _registered:", this._registered);
   },
 
   ensureUnregistered() {
     if (!this._registered) {
       return;
     }
 
     this.debug("ensureUnregistered");
@@ -331,123 +333,116 @@ let ProfileAutocomplete = {
 };
 
 /**
  * Handles content's interactions for the process.
  *
  * NOTE: Declares it by "var" to make it accessible in unit tests.
  */
 var FormAutofillContent = {
-  QueryInterface: ChromeUtils.generateQI([Ci.nsIFormSubmitObserver]),
   /**
    * @type {WeakMap} mapping FormLike root HTML elements to FormAutofillHandler objects.
    */
   _formsDetails: new WeakMap(),
 
   /**
    * @type {Set} Set of the fields with usable values in any saved profile.
    */
-  savedFieldNames: null,
+  get savedFieldNames() {
+    return Services.cpmm.sharedData.get("FormAutofill:savedFieldNames");
+  },
 
   /**
    * @type {Object} The object where to store the active items, e.g. element,
    * handler, section, and field detail.
    */
   _activeItems: {},
 
   init() {
     FormAutofill.defineLazyLogGetter(this, "FormAutofillContent");
+    this.debug("init");
 
-    Services.cpmm.addMessageListener("FormAutofill:enabledStatus", this);
-    Services.cpmm.addMessageListener("FormAutofill:savedFieldNames", this);
-    Services.obs.addObserver(this, "earlyformsubmit");
+    // eslint-disable-next-line mozilla/balanced-listeners
+    Services.cpmm.sharedData.addEventListener("change", this);
 
-    let autofillEnabled = Services.cpmm.initialProcessData.autofillEnabled;
+    let autofillEnabled = Services.cpmm.sharedData.get("FormAutofill:enabled");
     // If storage hasn't be initialized yet autofillEnabled is undefined but we need to ensure
     // autocomplete is registered before the focusin so register it in this case as long as the
     // pref is true.
     let shouldEnableAutofill = autofillEnabled === undefined &&
                                (FormAutofill.isAutofillAddressesEnabled ||
                                FormAutofill.isAutofillCreditCardsEnabled);
     if (autofillEnabled || shouldEnableAutofill) {
       ProfileAutocomplete.ensureRegistered();
     }
-
-    this.savedFieldNames =
-      Services.cpmm.initialProcessData.autofillSavedFieldNames;
   },
 
   /**
    * Send the profile to parent for doorhanger and storage saving/updating.
    *
    * @param {Object} profile Submitted form's address/creditcard guid and record.
    * @param {Object} domWin Current content window.
    * @param {int} timeStartedFillingMS Time of form filling started.
    */
   _onFormSubmit(profile, domWin, timeStartedFillingMS) {
     let mm = this._messageManagerFromWindow(domWin);
     mm.sendAsyncMessage("FormAutofill:OnFormSubmit",
                         {profile, timeStartedFillingMS});
   },
 
   /**
-   * Handle earlyformsubmit event and early return when:
+   * Handle a form submission and early return when:
    * 1. In private browsing mode.
    * 2. Could not map any autofill handler by form element.
    * 3. Number of filled fields is less than autofill threshold
    *
-   * @param {HTMLElement} formElement Root element which receives earlyformsubmit event.
-   * @param {Object} domWin Content window
-   * @returns {boolean} Should always return true so form submission isn't canceled.
+   * @param {HTMLElement} formElement Root element which receives submit event.
+   * @param {Window} domWin Content window only passed for unit tests
    */
-  notify(formElement, domWin) {
-    try {
-      this.debug("Notifying form early submission");
+  formSubmitted(formElement, domWin = formElement.ownerGlobal) {
+    this.debug("Handling form submission");
 
-      if (!FormAutofill.isAutofillEnabled) {
-        this.debug("Form Autofill is disabled");
-        return true;
-      }
+    if (!FormAutofill.isAutofillEnabled) {
+      this.debug("Form Autofill is disabled");
+      return;
+    }
 
-      if (domWin && PrivateBrowsingUtils.isContentWindowPrivate(domWin)) {
-        this.debug("Ignoring submission in a private window");
-        return true;
-      }
-
-      let handler = this._formsDetails.get(formElement);
-      if (!handler) {
-        this.debug("Form element could not map to an existing handler");
-        return true;
-      }
+    // The `domWin` truthiness test is used by unit tests to bypass this check.
+    if (domWin && PrivateBrowsingUtils.isContentWindowPrivate(domWin)) {
+      this.debug("Ignoring submission in a private window");
+      return;
+    }
 
-      let records = handler.createRecords();
-      if (!Object.values(records).some(typeRecords => typeRecords.length)) {
-        return true;
-      }
+    let handler = this._formsDetails.get(formElement);
+    if (!handler) {
+      this.debug("Form element could not map to an existing handler");
+      return;
+    }
 
-      this._onFormSubmit(records, domWin, handler.timeStartedFillingMS);
-    } catch (ex) {
-      Cu.reportError(ex);
+    let records = handler.createRecords();
+    if (!Object.values(records).some(typeRecords => typeRecords.length)) {
+      return;
     }
-    return true;
+
+    this._onFormSubmit(records, domWin, handler.timeStartedFillingMS);
   },
 
-  receiveMessage({name, data}) {
-    switch (name) {
-      case "FormAutofill:enabledStatus": {
-        if (data) {
+  handleEvent(evt) {
+    switch (evt.type) {
+      case "change": {
+        if (!evt.changedKeys.includes("FormAutofill:enabled")) {
+          return;
+        }
+        if (Services.cpmm.sharedData.get("FormAutofill:enabled")) {
           ProfileAutocomplete.ensureRegistered();
         } else {
           ProfileAutocomplete.ensureUnregistered();
         }
         break;
       }
-      case "FormAutofill:savedFieldNames": {
-        this.savedFieldNames = data;
-      }
     }
   },
 
   /**
    * Get the form's handler from cache which is created after page identified.
    *
    * @param {HTMLInputElement} element Focused input which triggered profile searching
    * @returns {Array<Object>|null}
--- a/browser/extensions/formautofill/FormAutofillParent.jsm
+++ b/browser/extensions/formautofill/FormAutofillParent.jsm
@@ -182,30 +182,30 @@ FormAutofillParent.prototype = {
     }
   },
 
   /**
    * Broadcast the status to frames when the form autofill status changes.
    */
   _onStatusChanged() {
     log.debug("_onStatusChanged: Status changed to", this._active);
-    Services.ppmm.broadcastAsyncMessage("FormAutofill:enabledStatus", this._active);
-    // Sync process data autofillEnabled to make sure the value up to date
+    Services.ppmm.sharedData.set("FormAutofill:enabled", this._active);
+    // Sync autofill enabled to make sure the value is up-to-date
     // no matter when the new content process is initialized.
-    Services.ppmm.initialProcessData.autofillEnabled = this._active;
+    Services.ppmm.sharedData.flush();
   },
 
   /**
    * Query preference and storage status to determine the overall status of the
    * form autofill feature.
    *
    * @returns {boolean} whether form autofill is active (enabled and has data)
    */
   _computeStatus() {
-    const savedFieldNames = Services.ppmm.initialProcessData.autofillSavedFieldNames;
+    const savedFieldNames = Services.ppmm.sharedData.get("FormAutofill:savedFieldNames");
 
     return (Services.prefs.getBoolPref(ENABLED_AUTOFILL_ADDRESSES_PREF) ||
            Services.prefs.getBoolPref(ENABLED_AUTOFILL_CREDITCARDS_PREF)) &&
            savedFieldNames &&
            savedFieldNames.size > 0;
   },
 
   /**
@@ -370,28 +370,30 @@ FormAutofillParent.prototype = {
     }
 
     target.sendAsyncMessage("FormAutofill:Records", records);
   },
 
   _updateSavedFieldNames() {
     log.debug("_updateSavedFieldNames");
 
+    let savedFieldNames;
     // Don't access the credit cards store unless it is enabled.
     if (FormAutofill.isAutofillCreditCardsAvailable) {
-      Services.ppmm.initialProcessData.autofillSavedFieldNames =
-        new Set([...this.formAutofillStorage.addresses.getSavedFieldNames(),
-          ...this.formAutofillStorage.creditCards.getSavedFieldNames()]);
+      savedFieldNames = new Set([
+        ...this.formAutofillStorage.addresses.getSavedFieldNames(),
+        ...this.formAutofillStorage.creditCards.getSavedFieldNames(),
+      ]);
     } else {
-      Services.ppmm.initialProcessData.autofillSavedFieldNames =
-        this.formAutofillStorage.addresses.getSavedFieldNames();
+      savedFieldNames = this.formAutofillStorage.addresses.getSavedFieldNames();
     }
 
-    Services.ppmm.broadcastAsyncMessage("FormAutofill:savedFieldNames",
-                                        Services.ppmm.initialProcessData.autofillSavedFieldNames);
+    Services.ppmm.sharedData.set("FormAutofill:savedFieldNames", savedFieldNames);
+    Services.ppmm.sharedData.flush();
+
     this._updateStatus();
   },
 
   async _onAddressSubmit(address, target, timeStartedFillingMS) {
     let showDoorhanger = null;
     if (address.guid) {
       // Avoid updating the fields that users don't modify.
       let originalAddress = await this.formAutofillStorage.addresses.get(address.guid);
--- a/browser/extensions/formautofill/api.js
+++ b/browser/extensions/formautofill/api.js
@@ -122,21 +122,16 @@ this.formautofill = class extends Extens
     } else {
       Services.prefs.clearUserPref("services.sync.engine.creditcards.available");
     }
 
     // Listen for the autocomplete popup message to lazily append our stylesheet related to the popup.
     Services.mm.addMessageListener("FormAutoComplete:MaybeOpenPopup", onMaybeOpenPopup);
 
     formAutofillParent.init().catch(Cu.reportError);
-    /* eslint-disable no-unused-vars */
-    Services.ppmm.loadProcessScript("data:,new " + function() {
-      ChromeUtils.import("resource://formautofill/FormAutofillContent.jsm");
-    }, true);
-    /* eslint-enable no-unused-vars */
     Services.mm.loadFrameScript("chrome://formautofill/content/FormAutofillFrameScript.js", true, true);
   }
 
   onShutdown() {
     resProto.setSubstitution(RESOURCE_HOST, null);
 
     this.chromeHandle.destruct();
     this.chromeHandle = null;
--- a/browser/extensions/formautofill/content/FormAutofillFrameScript.js
+++ b/browser/extensions/formautofill/content/FormAutofillFrameScript.js
@@ -1,33 +1,32 @@
 /* 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/. */
 
-/*
+/**
  * Form Autofill frame script.
  */
 
 "use strict";
 
 /* eslint-env mozilla/frame-script */
 
 var {Services} = ChromeUtils.import("resource://gre/modules/Services.jsm");
-var {FormAutofillContent} = ChromeUtils.import("resource://formautofill/FormAutofillContent.jsm");
 ChromeUtils.defineModuleGetter(this, "setTimeout",
                                "resource://gre/modules/Timer.jsm");
 ChromeUtils.defineModuleGetter(this, "FormAutofill",
                                "resource://formautofill/FormAutofill.jsm");
+ChromeUtils.defineModuleGetter(this, "FormAutofillContent",
+                               "resource://formautofill/FormAutofillContent.jsm");
 ChromeUtils.defineModuleGetter(this, "FormAutofillUtils",
                                "resource://formautofill/FormAutofillUtils.jsm");
 
 /**
  * Handles content's interactions for the frame.
- *
- * NOTE: Declares it by "var" to make it accessible in unit tests.
  */
 var FormAutofillFrameScript = {
   _nextHandleElement: null,
   _alreadyDOMContentLoaded: false,
   _hasDOMContentLoadedHandler: false,
   _hasPendingTask: false,
 
   _doIdentifyAutofillFields() {
@@ -44,26 +43,44 @@ var FormAutofillFrameScript = {
       // form has been identified, and ready to open popup.
       sendAsyncMessage("FormAutofill:FieldsIdentified");
       FormAutofillContent.updateActiveInput();
     });
   },
 
   init() {
     addEventListener("focusin", this);
+    addEventListener("DOMFormBeforeSubmit", this);
     addMessageListener("FormAutofill:PreviewProfile", this);
     addMessageListener("FormAutofill:ClearForm", this);
     addMessageListener("FormAutoComplete:PopupClosed", this);
     addMessageListener("FormAutoComplete:PopupOpened", this);
   },
 
   handleEvent(evt) {
     if (!evt.isTrusted || !FormAutofill.isAutofillEnabled) {
       return;
     }
+
+    switch (evt.type) {
+      case "focusin": {
+        this.onFocusIn(evt);
+        break;
+      }
+      case "DOMFormBeforeSubmit": {
+        this.onDOMFormBeforeSubmit(evt);
+        break;
+      }
+      default: {
+        throw new Error("Unexpected event type");
+      }
+    }
+  },
+
+  onFocusIn(evt) {
     FormAutofillContent.updateActiveInput();
 
     let element = evt.target;
     if (!FormAutofillUtils.isFieldEligibleForAutofill(element)) {
       return;
     }
     this._nextHandleElement = element;
 
@@ -77,16 +94,30 @@ var FormAutofillFrameScript = {
         return;
       }
       this._alreadyDOMContentLoaded = true;
     }
 
     this._doIdentifyAutofillFields();
   },
 
+  /**
+   * Handle the DOMFormBeforeSubmit event.
+   * @param {Event} evt
+   */
+  onDOMFormBeforeSubmit(evt) {
+    let formElement = evt.target;
+
+    if (!FormAutofill.isAutofillEnabled) {
+      return;
+    }
+
+    FormAutofillContent.formSubmitted(formElement);
+  },
+
   receiveMessage(message) {
     if (!FormAutofill.isAutofillEnabled) {
       return;
     }
 
     const doc = content.document;
     const {chromeEventHandler} = doc.ownerGlobal.docShell;
 
--- a/browser/extensions/formautofill/test/unit/test_activeStatus.js
+++ b/browser/extensions/formautofill/test/unit/test_activeStatus.js
@@ -11,29 +11,29 @@ add_task(async function setup() {
 });
 
 add_task(async function test_activeStatus_init() {
   let formAutofillParent = new FormAutofillParent();
   sinon.spy(formAutofillParent, "_updateStatus");
 
   // Default status is null before initialization
   Assert.equal(formAutofillParent._active, null);
-  Assert.equal(Services.ppmm.initialProcessData.autofillEnabled, undefined);
+  Assert.equal(Services.ppmm.sharedData.get("FormAutofill:enabled"), undefined);
 
   await formAutofillParent.init();
   // init shouldn't call updateStatus since that requires storage which will
   // lead to startup time regressions.
   Assert.equal(formAutofillParent._updateStatus.called, false);
-  Assert.equal(Services.ppmm.initialProcessData.autofillEnabled, undefined);
+  Assert.equal(Services.ppmm.sharedData.get("FormAutofill:enabled"), undefined);
 
   // Initialize profile storage
   await formAutofillParent.formAutofillStorage.initialize();
   // Upon first initializing profile storage, status should be computed.
   Assert.equal(formAutofillParent._updateStatus.called, true);
-  Assert.equal(Services.ppmm.initialProcessData.autofillEnabled, false);
+  Assert.equal(Services.ppmm.sharedData.get("FormAutofill:enabled"), false);
 
   formAutofillParent._uninit();
 });
 
 add_task(async function test_activeStatus_observe() {
   let formAutofillParent = new FormAutofillParent();
   sinon.stub(formAutofillParent, "_computeStatus");
   sinon.spy(formAutofillParent, "_onStatusChanged");
--- a/browser/extensions/formautofill/test/unit/test_onFormSubmitted.js
+++ b/browser/extensions/formautofill/test/unit/test_onFormSubmitted.js
@@ -486,22 +486,22 @@ const TESTCASES = [
           untouchedFields: [],
         }],
         creditCard: [],
       },
     },
   },
 ];
 
-add_task(async function handle_earlyformsubmit_event() {
+add_task(async function handle_invalid_form() {
   info("Starting testcase: Test an invalid form element");
   let fakeForm = MOCK_DOC.createElement("form");
   sinon.spy(FormAutofillContent, "_onFormSubmit");
 
-  Assert.equal(FormAutofillContent.notify(fakeForm), true);
+  FormAutofillContent.formSubmitted(fakeForm, null);
   Assert.equal(FormAutofillContent._onFormSubmit.called, false);
   FormAutofillContent._onFormSubmit.restore();
 });
 
 add_task(async function autofill_disabled() {
   let form = MOCK_DOC.getElementById("form1");
   form.reset();
 
@@ -520,41 +520,41 @@ add_task(async function autofill_disable
   FormAutofillContent.identifyAutofillFields(element);
 
   sinon.stub(FormAutofillContent, "_onFormSubmit");
 
   // "_onFormSubmit" shouldn't be called if both "addresses" and "creditCards"
   // are disabled.
   Services.prefs.setBoolPref("extensions.formautofill.addresses.enabled", false);
   Services.prefs.setBoolPref("extensions.formautofill.creditCards.enabled", false);
-  FormAutofillContent.notify(form);
+  FormAutofillContent.formSubmitted(form, null);
   Assert.equal(FormAutofillContent._onFormSubmit.called, false);
   FormAutofillContent._onFormSubmit.reset();
 
   // "_onFormSubmit" should be called as usual.
   Services.prefs.clearUserPref("extensions.formautofill.addresses.enabled");
   Services.prefs.clearUserPref("extensions.formautofill.creditCards.enabled");
-  FormAutofillContent.notify(form);
+  FormAutofillContent.formSubmitted(form, null);
   Assert.equal(FormAutofillContent._onFormSubmit.called, true);
   Assert.notDeepEqual(FormAutofillContent._onFormSubmit.args[0][0].address, []);
   Assert.notDeepEqual(FormAutofillContent._onFormSubmit.args[0][0].creditCard, []);
   FormAutofillContent._onFormSubmit.reset();
 
   // "address" should be empty if "addresses" pref is disabled.
   Services.prefs.setBoolPref("extensions.formautofill.addresses.enabled", false);
-  FormAutofillContent.notify(form);
+  FormAutofillContent.formSubmitted(form, null);
   Assert.equal(FormAutofillContent._onFormSubmit.called, true);
   Assert.deepEqual(FormAutofillContent._onFormSubmit.args[0][0].address, []);
   Assert.notDeepEqual(FormAutofillContent._onFormSubmit.args[0][0].creditCard, []);
   FormAutofillContent._onFormSubmit.reset();
   Services.prefs.clearUserPref("extensions.formautofill.addresses.enabled");
 
   // "creditCard" should be empty if "creditCards" pref is disabled.
   Services.prefs.setBoolPref("extensions.formautofill.creditCards.enabled", false);
-  FormAutofillContent.notify(form);
+  FormAutofillContent.formSubmitted(form, null);
   Assert.deepEqual(FormAutofillContent._onFormSubmit.called, true);
   Assert.notDeepEqual(FormAutofillContent._onFormSubmit.args[0][0].address, []);
   Assert.deepEqual(FormAutofillContent._onFormSubmit.args[0][0].creditCard, []);
   FormAutofillContent._onFormSubmit.reset();
   Services.prefs.clearUserPref("extensions.formautofill.creditCards.enabled");
 
   FormAutofillContent._onFormSubmit.restore();
 });
@@ -577,17 +577,17 @@ TESTCASES.forEach(testcase => {
       } else {
         input.value = testcase.formValue[key];
       }
     }
     sinon.stub(FormAutofillContent, "_onFormSubmit");
 
     let element = MOCK_DOC.getElementById(TARGET_ELEMENT_ID);
     FormAutofillContent.identifyAutofillFields(element);
-    FormAutofillContent.notify(form);
+    FormAutofillContent.formSubmitted(form, null);
 
     Assert.equal(FormAutofillContent._onFormSubmit.called,
                  testcase.expectedResult.formSubmission,
                  "Check expected onFormSubmit.called");
     if (FormAutofillContent._onFormSubmit.called) {
       Assert.deepEqual(FormAutofillContent._onFormSubmit.args[0][0],
                        testcase.expectedResult.records);
     }
--- a/browser/extensions/formautofill/test/unit/test_savedFieldNames.js
+++ b/browser/extensions/formautofill/test/unit/test_savedFieldNames.js
@@ -1,10 +1,10 @@
 /*
- * Test for keeping the valid fields information in initialProcessData.
+ * Test for keeping the valid fields information in sharedData.
  */
 
 "use strict";
 
 let FormAutofillParent;
 
 add_task(async function setup() {
   ({FormAutofillParent} = ChromeUtils.import("resource://formautofill/FormAutofillParent.jsm", null));
@@ -49,17 +49,17 @@ add_task(async function test_profileSave
   Object.defineProperty(
     formAutofillParent.formAutofillStorage.addresses,
     "_data", {writable: true});
 
   formAutofillParent.formAutofillStorage.addresses._data = [];
 
   // The set is empty if there's no profile in the store.
   formAutofillParent._updateSavedFieldNames();
-  Assert.equal(Services.ppmm.initialProcessData.autofillSavedFieldNames.size, 0);
+  Assert.equal(Services.ppmm.sharedData.get("FormAutofill:savedFieldNames").size, 0);
 
   // 2 profiles with 4 valid fields.
   formAutofillParent.formAutofillStorage.addresses._data = [{
     guid: "test-guid-1",
     organization: "Sesame Street",
     "street-address": "123 Sesame Street.",
     tel: "1-345-345-3456",
     email: "",
@@ -76,17 +76,17 @@ add_task(async function test_profileSave
     timeCreated: 0,
     timeLastUsed: 0,
     timeLastModified: 0,
     timesUsed: 0,
   }];
 
   formAutofillParent._updateSavedFieldNames();
 
-  let autofillSavedFieldNames = Services.ppmm.initialProcessData.autofillSavedFieldNames;
+  let autofillSavedFieldNames = Services.ppmm.sharedData.get("FormAutofill:savedFieldNames");
   Assert.equal(autofillSavedFieldNames.size, 4);
   Assert.equal(autofillSavedFieldNames.has("organization"), true);
   Assert.equal(autofillSavedFieldNames.has("street-address"), true);
   Assert.equal(autofillSavedFieldNames.has("tel"), true);
   Assert.equal(autofillSavedFieldNames.has("email"), false);
   Assert.equal(autofillSavedFieldNames.has("guid"), false);
   Assert.equal(autofillSavedFieldNames.has("timeCreated"), false);
   Assert.equal(autofillSavedFieldNames.has("timeLastUsed"), false);
new file mode 100644
--- /dev/null
+++ b/browser/locales/en-US/browser/pageInfo.ftl
@@ -0,0 +1,256 @@
+# 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/. --
+
+page-info-window =
+    .style = width: 600px; min-height: 550px;
+
+copy =
+    .key = C
+menu-copy =
+    .label = Copy
+    .accesskey = C
+
+select-all =
+    .key = A
+menu-select-all =
+    .label = Select All
+    .accesskey = A
+
+close-window =
+    .key = A
+
+general-tab =
+    .label = General
+    .accesskey = G
+general-title =
+    .value = Title:
+general-url =
+    .value = Address:
+general-type =
+    .value = Type:
+general-mode =
+    .value = Render Mode:
+general-size =
+    .value = Size:
+general-referrer =
+    .value = Referring URL:
+general-modified =
+    .value = Modified:
+general-encoding =
+    .value = Text Encoding:
+general-meta-name =
+    .label = Name
+general-meta-content =
+    .label = Content
+
+media-tab =
+    .label = Media
+    .accesskey = M
+media-location =
+    .value = Location:
+media-text =
+    .value = Associated Text:
+media-alt-header =
+    .label = Alternate Text
+media-address =
+    .label = Address
+media-type =
+    .label = Type
+media-size =
+    .label = Size
+media-count =
+    .label = Count
+media-dimension =
+    .value = Dimensions:
+media-long-desc =
+    .value = Long Description:
+media-save-as =
+    .label = Save As…
+    .accesskey = A
+media-save-image-as =
+    .label = Save As…
+    .accesskey = e
+media-preview =
+    .value = Media Preview:
+
+perm-tab =
+    .label = Permissions
+    .accesskey = P
+permissions-for =
+    .value = Permissions for:
+
+security-tab =
+    .label = Security
+    .accesskey = S
+security-view =
+    .label = View Certificate
+    .accesskey = V
+security-view-unknown = Unknown
+    .value = Unknown
+security-view-identity =
+    .value = Website Identity
+security-view-identity-owner =
+    .value = Owner:
+security-view-identity-domain =
+    .value = Website:
+security-view-identity-verifier =
+    .value = Verified by:
+security-view-identity-validity =
+    .value = Expires on:
+security-view-privacy =
+    .value = Privacy & History
+
+security-view-privacy-history-value = Have I visited this website prior to today?
+security-view-privacy-sitedata-value = Is this website storing information on my computer?
+
+security-view-privacy-clearsitedata =
+    .label = Clear Cookies and Site Data
+    .accesskey = C
+
+security-view-privacy-passwords-value = Have I saved any passwords for this website?
+
+security-view-privacy-viewpasswords =
+    .label = View Saved Passwords
+    .accesskey = w
+security-view-technical =
+    .value = Technical Details
+
+help-button =
+    .label = Help
+
+security-site-data-cookies-only = Yes, cookies
+security-site-data-no = No
+
+## These strings are used to tell the user if the website is storing cookies
+## and data on the users computer in the security tab of pageInfo
+## Variables:
+##   $value (number) - Amount of data being stored
+##   $unit (string) - The unit of data being stored (Usually KB)
+security-site-data-cookies = Yes, cookies and { $value } { $unit } of site data
+security-site-data-only = Yes, { $value } { $unit } of site data
+
+security-site-data-cookies-only = Yes, cookies
+security-site-data-no = No
+
+image-size-unknown = Unknown
+not-set-verified-by = Not specified
+not-set-alternative-text = Not specified
+not-set-date = Not specified
+media-img = Image
+media-bg-img = Background
+media-border-img = Border
+media-list-img = Bullet
+media-cursor = Cursor
+media-object = Object
+media-embed = Embed
+media-link = Icon
+media-input = Input
+media-video = Video
+media-audio = Audio
+saved-passwords-yes = Yes
+saved-passwords-no = No
+
+no-page-title =
+    .value = Untitled Page:
+general-quirks-mode =
+    .value = Quirks mode
+general-strict-mode =
+    .value = Standards compliance mode
+security-no-owner = This website does not supply ownership information.
+media-select-folder = Select a Folder to Save the Images
+media-unknown-not-cached =
+    .value = Unknown (not cached)
+permissions-use-default =
+    .label = Use Default
+security-no-visits = No
+
+# This string is used to display the number of meta tags
+# in the General Tab
+# Variables:
+#   $tags (number) - The number of meta tags
+general-meta-tags =
+    .value =
+        { $tags ->
+             [one] Meta (1 tag)
+            *[other] Meta ({ $tags } tags)
+        }
+
+# This string is used to display the number of times
+# the user has visited the website prior
+# Variables:
+#   $visits (number) - The number of previous visits
+security-visits-number =
+    { $visits ->
+         [0] No
+         [one] Yes, once
+        *[other] Yes, { $visits } times
+    }
+
+# This string is used to display the size of a media file
+# Variables:
+#   $kb (number) - The size of an image in Kilobytes
+#   $bytes (number) - The size of an image in Bytes
+properties-general-size =
+    .value = { $bytes ->
+         [one] { $kb } KB ({ $bytes } byte)
+        *[other] { $kb } KB ({ $bytes } bytes)
+    }
+
+# This string is used to display the type and number
+# of frames of a animated image
+# Variables:
+#   $type (string) - The type of a animated image
+#   $frames (number) - The number of frames in an animated image
+media-animated-image-type =
+    .value = { $frames ->
+         [one] { $type } Image (animated, { $frames } frame)
+        *[other] { $type } Image (animated, { $frames } frames)
+    }
+
+# This string is used to display the type of
+# an image
+# Variables:
+#   $type (string) - The type of an image
+media-image-type =
+    .value = { $type } Image
+
+# This string is used to display the size of a scaled image
+# in both scaled and unscaled pixels
+# Variables:
+#   $dimx (number) - The horizontal size of an image
+#   $dimy (number) - The vertical size of an image
+#   $scaledx (number) - The scaled horizontal size of an image
+#   $scaledy (number) - The scaled vertical size of an image
+media-dimensions-scaled =
+    .value = { $dimx }px × { $dimy }px (scaled to { $scaledx }px × { $scaledy }px)
+
+# This string is used to display the size of an image in pixels
+# Variables:
+#   $dimx (number) - The horizontal size of an image
+#   $dimy (number) - The vertical size of an image
+media-dimensions =
+    .value = { $dimx }px × { $dimy }px
+
+# This string is used to display the size of a media
+# file in kilobytes
+# Variables:
+#   $size (number) - The size of the media file in kilobytes
+media-file-size = { $size } KB
+
+# This string is used to display the website name next to the
+# "Block Images" checkbox in the media tab
+# Variables:
+#   $website (string) - The website name
+media-block-image =
+    .label = Block Images from { $website }
+    .accesskey = B
+
+# This string is used to display the URL of the website on top of the
+# pageInfo dialog box
+# Variables:
+#   $website (string) - The url of the website pageInfo is getting info for
+page-info-page =
+    .title = Page Info - { $website }
+page-info-frame =
+    .title = Frame Info - { $website }
deleted file mode 100644
--- a/browser/locales/en-US/chrome/browser/pageInfo.dtd
+++ /dev/null
@@ -1,75 +0,0 @@
-<!-- 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/. -->
-
-<!ENTITY  pageInfoWindow.width  "600">
-<!ENTITY  pageInfoWindow.height "550">
-
-<!ENTITY  copy.key              "C">
-<!ENTITY  copy.label            "Copy">
-<!ENTITY  copy.accesskey        "C">
-<!ENTITY  selectall.key         "A">
-<!ENTITY  selectall.label       "Select All">
-<!ENTITY  selectall.accesskey   "A">
-<!ENTITY  closeWindow.key       "w">
-
-<!ENTITY  generalTab            "General">
-<!ENTITY  generalTab.accesskey  "G">
-<!ENTITY  generalTitle          "Title:">
-<!ENTITY  generalURL            "Address:">
-<!ENTITY  generalType           "Type:">
-<!ENTITY  generalMode           "Render Mode:">
-<!ENTITY  generalSize           "Size:">
-<!ENTITY  generalReferrer       "Referring URL:">
-<!ENTITY  generalSource         "Cache Source:">
-<!ENTITY  generalModified       "Modified:">
-<!ENTITY  generalEncoding2      "Text Encoding:">
-<!ENTITY  generalMetaName       "Name">
-<!ENTITY  generalMetaContent    "Content">
-
-<!ENTITY  mediaTab              "Media">
-<!ENTITY  mediaTab.accesskey    "M">
-<!ENTITY  mediaLocation         "Location:">
-<!ENTITY  mediaText             "Associated Text:">
-<!ENTITY  mediaAltHeader        "Alternate Text">
-<!ENTITY  mediaAddress          "Address">
-<!ENTITY  mediaType             "Type">
-<!ENTITY  mediaSize             "Size">
-<!ENTITY  mediaCount            "Count">
-<!ENTITY  mediaDimension        "Dimensions:">
-<!ENTITY  mediaLongdesc         "Long Description:">
-<!ENTITY  mediaBlockImage.accesskey "B">
-<!ENTITY  mediaSaveAs           "Save As…">
-<!ENTITY  mediaSaveAs.accesskey "A">
-<!ENTITY  mediaSaveAs2.accesskey "e">
-<!ENTITY  mediaPreview          "Media Preview:">
-
-<!ENTITY  permTab               "Permissions">
-<!ENTITY  permTab.accesskey     "P">
-<!ENTITY  permissionsFor        "Permissions for:">
-
-<!ENTITY  securityTab           "Security">
-<!ENTITY  securityTab.accesskey "S">
-<!ENTITY  securityView.certView "View Certificate">
-<!ENTITY  securityView.accesskey "V">
-<!ENTITY  securityView.unknown   "Unknown">
-
-
-<!ENTITY  securityView.identity.header   "Website Identity">
-<!ENTITY  securityView.identity.owner    "Owner:">
-<!ENTITY  securityView.identity.domain   "Website:">
-<!ENTITY  securityView.identity.verifier "Verified by:">
-<!ENTITY  securityView.identity.validity "Expires on:">
-
-<!ENTITY  securityView.privacy.header                   "Privacy &amp; History">
-<!ENTITY  securityView.privacy.history                  "Have I visited this website prior to today?">
-<!ENTITY  securityView.privacy.siteData                 "Is this website storing information on my computer?">
-<!ENTITY  securityView.privacy.clearSiteData            "Clear Cookies and Site Data">
-<!ENTITY  securityView.privacy.clearSiteData.accessKey  "C">
-<!ENTITY  securityView.privacy.passwords                "Have I saved any passwords for this website?">
-<!ENTITY  securityView.privacy.viewPasswords            "View Saved Passwords">
-<!ENTITY  securityView.privacy.viewPasswords.accessKey  "w">
-
-<!ENTITY  securityView.technical.header                 "Technical Details">
-
-<!ENTITY  helpButton.label                              "Help">
deleted file mode 100644
--- a/browser/locales/en-US/chrome/browser/pageInfo.properties
+++ /dev/null
@@ -1,62 +0,0 @@
-# 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/.
-
-pageInfo.page.title=Page Info - %S
-pageInfo.frame.title=Frame Info - %S
-
-noPageTitle=Untitled Page:
-unknown=Unknown
-notset=Not specified
-yes=Yes
-no=No
-
-mediaImg=Image
-mediaVideo=Video
-mediaAudio=Audio
-mediaBGImg=Background
-mediaBorderImg=Border
-mediaListImg=Bullet
-mediaCursor=Cursor
-mediaObject=Object
-mediaEmbed=Embed
-mediaLink=Icon
-mediaInput=Input
-mediaFileSize=%S KB
-mediaSize=%Spx \u00D7 %Spx
-mediaSelectFolder=Select a Folder to Save the Images
-mediaBlockImage=Block Images from %S
-mediaUnknownNotCached=Unknown (not cached)
-mediaImageType=%S Image
-mediaAnimatedImageType=%S Image (animated, %S frames)
-mediaDimensions=%Spx \u00D7 %Spx
-mediaDimensionsScaled=%Spx \u00D7 %Spx (scaled to %Spx \u00D7 %Spx)
-
-generalQuirksMode=Quirks mode
-generalStrictMode=Standards compliance mode
-generalSize=%S KB (%S bytes)
-generalMetaTag=Meta (1 tag)
-generalMetaTags=Meta (%S tags)
-
-securityNoOwner=This website does not supply ownership information.
-# LOCALIZATION NOTE (securityVisitsNumber):
-# Semi-colon list of plural forms.
-# See: https://developer.mozilla.org/en/docs/Localization_and_Plurals
-# #1 is the number of visits and can be used in all plural forms as needed, e.g.
-# for '1': 'Yes, #1 time'
-securityVisitsNumber=Yes, once;Yes, #1 times
-securityNoVisits=No
-
-# LOCALIZATION NOTE(securitySiteDataCookies,securitySiteDataOnly): This is for site data disk usage.
-#   It confirms that a website is indeed using this much space.
-#   e.g. Is this website storing site data? "Yes, 50.23 MB"
-#   %1$S = size (in bytes or megabytes, ...)
-#   %2$S = unit of measure (bytes, KB, MB, ...)
-securitySiteDataCookies=Yes, cookies and %1$S %2$S of site data
-securitySiteDataOnly=Yes, %1$S %2$S of site data
-# LOCALIZATION NOTE(securitySiteDataCookiesOnly,securitySiteDataNo):
-# This is for site data and cookies usage. It answers the question "Is this website storing cookies and/or site data?"
-securitySiteDataCookiesOnly=Yes, cookies
-securitySiteDataNo=No
-
-permissions.useDefault=Use Default
--- a/browser/locales/jar.mn
+++ b/browser/locales/jar.mn
@@ -17,18 +17,16 @@
     locale/browser/aboutPrivateBrowsing.dtd        (%chrome/browser/aboutPrivateBrowsing.dtd)
     locale/browser/accounts.properties             (%chrome/browser/accounts.properties)
     locale/browser/browser.dtd                     (%chrome/browser/browser.dtd)
     locale/browser/baseMenuOverlay.dtd             (%chrome/browser/baseMenuOverlay.dtd)
     locale/browser/browser.properties              (%chrome/browser/browser.properties)
     locale/browser/customizableui/customizableWidgets.properties (%chrome/browser/customizableui/customizableWidgets.properties)
     locale/browser/lightweightThemes.properties    (%chrome/browser/lightweightThemes.properties)
     locale/browser/uiDensity.properties            (%chrome/browser/uiDensity.properties)
-    locale/browser/pageInfo.dtd                    (%chrome/browser/pageInfo.dtd)
-    locale/browser/pageInfo.properties             (%chrome/browser/pageInfo.properties)
     locale/browser/pocket.properties               (%chrome/browser/pocket.properties)
     locale/browser/search.properties               (%chrome/browser/search.properties)
     locale/browser/siteData.properties             (%chrome/browser/siteData.properties)
     locale/browser/sitePermissions.properties      (%chrome/browser/sitePermissions.properties)
     locale/browser/setDesktopBackground.dtd        (%chrome/browser/setDesktopBackground.dtd)
     locale/browser/shellservice.properties         (%chrome/browser/shellservice.properties)
     locale/browser/tabbrowser.properties           (%chrome/browser/tabbrowser.properties)
     locale/browser/taskbar.properties              (%chrome/browser/taskbar.properties)
--- a/browser/modules/ContentSearch.jsm
+++ b/browser/modules/ContentSearch.jsm
@@ -19,16 +19,18 @@ ChromeUtils.defineModuleGetter(this, "Pr
 ChromeUtils.defineModuleGetter(this, "SearchSuggestionController",
   "resource://gre/modules/SearchSuggestionController.jsm");
 
 const INBOUND_MESSAGE = "ContentSearch";
 const OUTBOUND_MESSAGE = INBOUND_MESSAGE;
 const MAX_LOCAL_SUGGESTIONS = 3;
 const MAX_SUGGESTIONS = 6;
 
+const HANDOFF_TO_AWESOMEBAR_PREF = "browser.newtabpage.activity-stream.improvesearch.handoffToAwesomebar";
+
 /**
  * ContentSearch receives messages named INBOUND_MESSAGE and sends messages
  * named OUTBOUND_MESSAGE.  The data of each message is expected to look like
  * { type, data }.  type is the message's type (or subtype if you consider the
  * type of the message itself to be INBOUND_MESSAGE), and data is data that is
  * specific to the type.
  *
  * Inbound messages have the following types:
@@ -323,35 +325,31 @@ var ContentSearch = {
       handleCompletion: () => {},
       handleError: err => {
         Cu.reportError("Error adding form history entry: " + err);
       },
     });
     return true;
   },
 
-  async currentStateObj(uriFlag = false) {
+  async currentStateObj() {
     let state = {
       engines: [],
       currentEngine: await this._currentEngineObj(),
     };
-    if (uriFlag) {
-      state.currentEngine.iconBuffer = Services.search.defaultEngine.getIconURLBySize(16, 16);
-    }
+
     let pref = Services.prefs.getCharPref("browser.search.hiddenOneOffs");
     let hiddenList = pref ? pref.split(",") : [];
     for (let engine of await Services.search.getVisibleEngines()) {
       let uri = engine.getIconURLBySize(16, 16);
-      let iconBuffer = uri;
-      if (!uriFlag) {
-        iconBuffer = await this._arrayBufferFromDataURI(uri);
-      }
+      let iconData = await this._maybeConvertURIToArrayBuffer(uri);
+
       state.engines.push({
         name: engine.name,
-        iconBuffer,
+        iconData,
         hidden: hiddenList.includes(engine.name),
         identifier: engine.identifier,
       });
     }
     return state;
   },
 
   _processEventQueue() {
@@ -512,25 +510,43 @@ var ContentSearch = {
   async _currentEngineObj() {
     let engine = Services.search.defaultEngine;
     let favicon = engine.getIconURLBySize(16, 16);
     let placeholder = this._stringBundle.formatStringFromName(
       "searchWithEngine", [engine.name], 1);
     let obj = {
       name: engine.name,
       placeholder,
-      iconBuffer: await this._arrayBufferFromDataURI(favicon),
+      iconData: await this._maybeConvertURIToArrayBuffer(favicon),
     };
     return obj;
   },
 
-  _arrayBufferFromDataURI(uri) {
+  _maybeConvertURIToArrayBuffer(uri) {
     if (!uri) {
       return Promise.resolve(null);
     }
+
+    // The uri received here can be of two types
+    // 1 - resource://search-plugins/images/foo.ico
+    // 2 - data:image/x-icon;base64,VERY-LONG-STRING
+    //
+    // If the URI is not a data: URI, there's no point in converting
+    // it to an arraybuffer (which is used to optimize passing the data
+    // accross processes): we can just pass the original URI, which is cheaper.
+    //
+    // There's a catch, though: the previous UI (search one-off buttons)
+    // doesn't work with .ico files. So, if the pref for the new UI
+    // is not toggled, we skip this optimization and let all icons be
+    // sent as array buffers.
+    if (!uri.startsWith("data:") &&
+        Services.prefs.getBoolPref(HANDOFF_TO_AWESOMEBAR_PREF)) {
+      return Promise.resolve(uri);
+    }
+
     return new Promise(resolve => {
       let xhr = new XMLHttpRequest();
       xhr.open("GET", uri, true);
       xhr.responseType = "arraybuffer";
       xhr.onload = () => {
         resolve(xhr.response);
       };
       xhr.onerror = xhr.onabort = xhr.ontimeout = () => {
--- a/browser/modules/PageActions.jsm
+++ b/browser/modules/PageActions.jsm
@@ -3,16 +3,17 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 "use strict";
 
 var EXPORTED_SYMBOLS = [
   "PageActions",
   // PageActions.Action
   // PageActions.ACTION_ID_BOOKMARK
+  // PageActions.ACTION_ID_PIN_TAB
   // PageActions.ACTION_ID_BOOKMARK_SEPARATOR
   // PageActions.ACTION_ID_BUILT_IN_SEPARATOR
   // PageActions.ACTION_ID_TRANSIENT_SEPARATOR
 ];
 
 const {Services} = ChromeUtils.import("resource://gre/modules/Services.jsm");
 
 ChromeUtils.defineModuleGetter(this, "AppConstants",
@@ -20,16 +21,17 @@ ChromeUtils.defineModuleGetter(this, "Ap
 ChromeUtils.defineModuleGetter(this, "AsyncShutdown",
   "resource://gre/modules/AsyncShutdown.jsm");
 ChromeUtils.defineModuleGetter(this, "BinarySearch",
   "resource://gre/modules/BinarySearch.jsm");
 ChromeUtils.defineModuleGetter(this, "PrivateBrowsingUtils",
   "resource://gre/modules/PrivateBrowsingUtils.jsm");
 
 const ACTION_ID_BOOKMARK = "bookmark";
+const ACTION_ID_PIN_TAB = "pinTab";
 const ACTION_ID_BOOKMARK_SEPARATOR = "bookmarkSeparator";
 const ACTION_ID_BUILT_IN_SEPARATOR = "builtInSeparator";
 const ACTION_ID_TRANSIENT_SEPARATOR = "transientSeparator";
 
 const PREF_PERSISTED_ACTIONS = "browser.pageActions.persistedActions";
 const PERSISTED_ACTIONS_CURRENT_VERSION = 1;
 
 // Escapes the given raw URL string, and returns an equivalent CSS url()
@@ -1076,16 +1078,17 @@ Action.prototype = {
 
 this.PageActions.Action = Action;
 
 this.PageActions.ACTION_ID_BUILT_IN_SEPARATOR = ACTION_ID_BUILT_IN_SEPARATOR;
 this.PageActions.ACTION_ID_TRANSIENT_SEPARATOR = ACTION_ID_TRANSIENT_SEPARATOR;
 
 // These are only necessary so that Pocket and the test can use them.
 this.PageActions.ACTION_ID_BOOKMARK = ACTION_ID_BOOKMARK;
+this.PageActions.ACTION_ID_PIN_TAB = ACTION_ID_PIN_TAB;
 this.PageActions.ACTION_ID_BOOKMARK_SEPARATOR = ACTION_ID_BOOKMARK_SEPARATOR;
 this.PageActions.PREF_PERSISTED_ACTIONS = PREF_PERSISTED_ACTIONS;
 
 
 // Sorted in the order in which they should appear in the page action panel.
 // Does not include the page actions of extensions bundled with the browser.
 // They're added by the relevant extension code.
 // NOTE: If you add items to this list (or system add-on actions that we
@@ -1105,16 +1108,50 @@ var gBuiltInActions = [
     onShowingInPanel(buttonNode) {
       browserPageActions(buttonNode).bookmark.onShowingInPanel(buttonNode);
     },
     onCommand(event, buttonNode) {
       browserPageActions(buttonNode).bookmark.onCommand(event, buttonNode);
     },
   },
 
+  // pin tab
+  {
+    id: ACTION_ID_PIN_TAB,
+    // The title is set in browser-pageActions.js.
+    title: "",
+    onBeforePlacedInWindow(browserWindow) {
+      function handlePinEvent() {
+        browserPageActions(browserWindow).pinTab.updateState();
+      }
+      function handleWindowUnload() {
+        for (let event of ["TabPinned", "TabUnpinned"]) {
+          browserWindow.removeEventListener(event, handlePinEvent);
+        }
+      }
+
+      for (let event of ["TabPinned", "TabUnpinned"]) {
+        browserWindow.addEventListener(event, handlePinEvent);
+      }
+      browserWindow.addEventListener("unload", handleWindowUnload, {once: true});
+    },
+    onPlacedInPanel(buttonNode) {
+      browserPageActions(buttonNode).pinTab.updateState();
+    },
+    onPlacedInUrlbar(buttonNode) {
+      browserPageActions(buttonNode).pinTab.updateState();
+    },
+    onLocationChange(browserWindow) {
+      browserPageActions(browserWindow).pinTab.updateState();
+    },
+    onCommand(event, buttonNode) {
+      browserPageActions(buttonNode).pinTab.onCommand(event, buttonNode);
+    },
+  },
+
   // separator
   {
     id: ACTION_ID_BOOKMARK_SEPARATOR,
     _isSeparator: true,
   },
 
   // copy URL
   {
--- a/browser/modules/test/browser/browser.ini
+++ b/browser/modules/test/browser/browser.ini
@@ -8,16 +8,17 @@ prefs =
 [browser_ContentSearch.js]
 support-files =
   contentSearch.js
   contentSearchBadImage.xml
   contentSearchSuggestions.sjs
   contentSearchSuggestions.xml
   !/browser/components/search/test/browser/head.js
   !/browser/components/search/test/browser/testEngine.xml
+  testEngine_chromeicon.xml
 [browser_LiveBookmarkMigrator.js]
 [browser_PageActions.js]
 [browser_PermissionUI.js]
 [browser_PermissionUI_prompts.js]
 [browser_ProcessHangNotifications.js]
 skip-if = !e10s
 [browser_SitePermissions.js]
 [browser_SitePermissions_combinations.js]
--- a/browser/modules/test/browser/browser_ContentSearch.js
+++ b/browser/modules/test/browser/browser_ContentSearch.js
@@ -1,50 +1,66 @@
 /* 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/. */
 
 const TEST_MSG = "ContentSearchTest";
 const CONTENT_SEARCH_MSG = "ContentSearch";
 const TEST_CONTENT_SCRIPT_BASENAME = "contentSearch.js";
+const HANDOFF_TO_AWESOMEBAR_PREF = "browser.newtabpage.activity-stream.improvesearch.handoffToAwesomebar";
 
 /* import-globals-from ../../../components/search/test/browser/head.js */
 Services.scriptloader.loadSubScript(
   "chrome://mochitests/content/browser/browser/components/search/test/browser/head.js",
   this);
 
 var originalEngine;
 
+var arrayBufferIconTested = false;
+var plainURIIconTested = false;
+
 add_task(async function setup() {
   originalEngine = await Services.search.getDefault();
 
   await SpecialPowers.pushPrefEnv({
-    set: [["browser.newtab.preload", false]],
+    set: [
+      ["browser.newtab.preload", false],
+      // Hack: set this pref to make sure the expected
+      // optimization for non-data URIs kicks in.
+      [HANDOFF_TO_AWESOMEBAR_PREF, true],
+    ],
   });
 
   await promiseNewEngine("testEngine.xml", {
     setAsCurrent: true,
     testPath: "chrome://mochitests/content/browser/browser/components/search/test/browser/",
   });
 
+  await promiseNewEngine("testEngine_chromeicon.xml", {
+    setAsCurrent: false,
+  });
+
   registerCleanupFunction(async () => {
     await Services.search.setDefault(originalEngine);
   });
 });
 
 add_task(async function GetState() {
   let { mm } = await addTab();
   mm.sendAsyncMessage(TEST_MSG, {
     type: "GetState",
   });
   let msg = await waitForTestMsg(mm, "State");
   checkMsg(msg, {
     type: "State",
     data: await currentStateObj(),
   });
+
+  ok(arrayBufferIconTested, "ArrayBuffer path for the iconData was tested");
+  ok(plainURIIconTested, "Plain URI path for the iconData was tested");
 });
 
 add_task(async function SetDefaultEngine() {
   let { mm } = await addTab();
   let newDefaultEngine = null;
   let oldDefaultEngine = await Services.search.getDefault();
   let engines = await Services.search.getVisibleEngines();
   for (let engine of engines) {
@@ -152,19 +168,19 @@ add_task(async function badImage() {
   // engine.  That's what we're testing, and obviously it shouldn't happen.
   let vals = await waitForNewEngine(mm, "contentSearchBadImage.xml", 1);
   let engine = vals[0];
   let finalCurrentStateMsg = vals[vals.length - 1];
   let expectedCurrentState = await currentStateObj();
   let expectedEngine =
     expectedCurrentState.engines.find(e => e.name == engine.name);
   ok(!!expectedEngine, "Sanity check: engine should be in expected state");
-  ok(expectedEngine.iconBuffer === null,
+  ok(expectedEngine.iconData === null,
      "Sanity check: icon array buffer of engine in expected state " +
-     "should be null: " + expectedEngine.iconBuffer);
+     "should be null: " + expectedEngine.iconData);
   checkMsg(finalCurrentStateMsg, {
     type: "CurrentState",
     data: expectedCurrentState,
   });
   // Removing the engine triggers a final CurrentState message.  Wait for it so
   // it doesn't trip up subsequent tests.
   await Services.search.removeEngine(engine);
   await waitForTestMsg(mm, "CurrentState");
@@ -353,47 +369,55 @@ var currentStateObj = async function() {
   let state = {
     engines: [],
     currentEngine: await defaultEngineObj(),
   };
   for (let engine of await Services.search.getVisibleEngines()) {
     let uri = engine.getIconURLBySize(16, 16);
     state.engines.push({
       name: engine.name,
-      iconBuffer: await arrayBufferFromDataURI(uri),
+      iconData: await iconDataFromURI(uri),
       hidden: false,
       identifier: engine.identifier,
     });
   }
   return state;
 };
 
 var defaultEngineObj = async function() {
   let engine = await Services.search.getDefault();
   let uriFavicon = engine.getIconURLBySize(16, 16);
   let bundle = Services.strings.createBundle("chrome://global/locale/autocomplete.properties");
   return {
     name: engine.name,
     placeholder: bundle.formatStringFromName("searchWithEngine", [engine.name], 1),
-    iconBuffer: await arrayBufferFromDataURI(uriFavicon),
+    iconData: await iconDataFromURI(uriFavicon),
   };
 };
 
-function arrayBufferFromDataURI(uri) {
+function iconDataFromURI(uri) {
   if (!uri) {
     return Promise.resolve(null);
   }
+
+  if (!uri.startsWith("data:") &&
+      Services.prefs.getBoolPref(HANDOFF_TO_AWESOMEBAR_PREF)) {
+    plainURIIconTested = true;
+    return Promise.resolve(uri);
+  }
+
   return new Promise(resolve => {
     let xhr = new XMLHttpRequest();
     xhr.open("GET", uri, true);
     xhr.responseType = "arraybuffer";
     xhr.onerror = () => {
       resolve(null);
     };
     xhr.onload = () => {
+      arrayBufferIconTested = true;
       resolve(xhr.response);
     };
     try {
       xhr.send();
     } catch (err) {
       resolve(null);
     }
   });
copy from browser/components/search/test/browser/testEngine.xml
copy to browser/modules/test/browser/testEngine_chromeicon.xml
--- a/browser/components/search/test/browser/testEngine.xml
+++ b/browser/modules/test/browser/testEngine_chromeicon.xml
@@ -1,12 +1,12 @@
 <OpenSearchDescription xmlns="http://a9.com/-/spec/opensearch/1.1/"
                        xmlns:moz="http://www.mozilla.org/2006/browser/search/">
-  <ShortName>Foo</ShortName>
-  <Description>Foo Search</Description>
+  <ShortName>FooChromeIcon</ShortName>
+  <Description>Foo Chrome Icon Search</Description>
   <InputEncoding>utf-8</InputEncoding>
-  <Image width="16" height="16">data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAIAAACQkWg2AAABGklEQVQoz2NgGB6AnZ1dUlJSXl4eSDIyMhLW4Ovr%2B%2Fr168uXL69Zs4YoG%2BLi4i5dusTExMTGxsbNzd3f37937976%2BnpmZmagbHR09J49e5YvX66kpATVEBYW9ubNm2nTphkbG7e2tp44cQLIuHfvXm5urpaWFlDKysqqu7v73LlzECMYIiIiHj58mJCQoKKicvXq1bS0NKBgW1vbjh074uPjgeqAXE1NzSdPnvDz84M0AEUvXLgAsW379u1z5swBen3jxo2zZ892cHB4%2BvQp0KlAfwI1cHJyghQFBwfv2rULokFXV%2FfixYu7d%2B8GGqGgoMDKyrpu3br9%2B%2FcDuXl5eVA%2FAEWBfoWHAdAYoNuAYQ0XAeoUERFhGDYAAPoUaT2dfWJuAAAAAElFTkSuQmCC</Image>
+  <Image width="16" height="16">chrome://browser/skin/info.svg</Image>
   <Url type="text/html" method="GET" template="http://mochi.test:8888/browser/browser/components/search/test/browser/?search">
     <Param name="test" value="{searchTerms}"/>
   </Url>
   <moz:SearchForm>http://mochi.test:8888/browser/browser/components/search/test/browser/</moz:SearchForm>
-  <moz:Alias>fooalias</moz:Alias>
+  <moz:Alias>foochromeiconalias</moz:Alias>
 </OpenSearchDescription>
--- a/browser/themes/shared/urlbar-searchbar.inc.css
+++ b/browser/themes/shared/urlbar-searchbar.inc.css
@@ -155,16 +155,26 @@
 }
 
 #pocket-button-box[open="true"] > #pocket-button,
 #pocket-button-box[pocketed="true"] > #pocket-button {
   fill: #ef4056;
   fill-opacity: 1;
 }
 
+#pageAction-panel-pinTab,
+#pageAction-urlbar-pinTab {
+  list-style-image: url("resource://activity-stream/data/content/assets/glyph-pin-16.svg");
+}
+
+#pageAction-panel-pinTab[pinned],
+#pageAction-urlbar-pinTab[pinned] {
+  list-style-image: url("resource://activity-stream/data/content/assets/glyph-unpin-16.svg");
+}
+
 #pageAction-panel-copyURL,
 #pageAction-urlbar-copyURL {
   list-style-image: url("chrome://browser/skin/link.svg");
 }
 
 #pageAction-panel-emailLink,
 #pageAction-urlbar-emailLink {
   list-style-image: url("chrome://browser/skin/mail.svg");
--- a/build/mozconfig.nasm
+++ b/build/mozconfig.nasm
@@ -2,10 +2,11 @@
 # 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/.
 
 case "$(uname -s)" in
 MINGW*)
     export NASM=$topsrcdir/nasm/nasm.exe
     ;;
 *)
+    export NASM=$topsrcdir/nasm/nasm
     ;;
 esac
deleted file mode 100644
--- a/devtools/client/debugger/new/src/actions/ast/setPausePoints.js
+++ /dev/null
@@ -1,63 +0,0 @@
-/* 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/>. */
-
-// @flow
-
-import { getSourceFromId } from "../../selectors";
-import * as parser from "../../workers/parser";
-import { convertToList } from "../../utils/pause/pausePoints";
-import { features } from "../../utils/prefs";
-import { getGeneratedLocation } from "../../utils/source-maps";
-
-import type { SourceId } from "../../types";
-import type { ThunkArgs, Action } from "../types";
-
-async function mapLocations(pausePoints, state, source, sourceMaps) {
-  const pausePointList = convertToList(pausePoints);
-  const sourceId = source.id;
-
-  return Promise.all(
-    pausePointList.map(async ({ types, location }) => {
-      const generatedLocation = await getGeneratedLocation(
-        state,
-        source,
-        {
-          ...location,
-          sourceId
-        },
-        sourceMaps
-      );
-
-      return { types, location, generatedLocation };
-    })
-  );
-}
-export function setPausePoints(sourceId: SourceId) {
-  return async ({ dispatch, getState, client, sourceMaps }: ThunkArgs) => {
-    const source = getSourceFromId(getState(), sourceId);
-    if (!features.pausePoints || !source || !source.text) {
-      return;
-    }
-
-    if (source.isWasm) {
-      return;
-    }
-
-    const pausePoints = await mapLocations(
-      await parser.getPausePoints(sourceId),
-      getState(),
-      source,
-      sourceMaps
-    );
-
-    dispatch(
-      ({
-        type: "SET_PAUSE_POINTS",
-        sourceText: source.text || "",
-        sourceId,
-        pausePoints
-      }: Action)
-    );
-  };
-}
--- a/devtools/client/debugger/new/src/actions/pause/commands.js
+++ b/devtools/client/debugger/new/src/actions/pause/commands.js
@@ -17,22 +17,17 @@ import { addHiddenBreakpoint } from "../
 import { features } from "../../utils/prefs";
 import { recordEvent } from "../../utils/telemetry";
 
 import type { Source } from "../../types";
 import type { ThunkArgs } from "../types";
 import type { Command } from "../../reducers/types";
 
 export function selectThread(thread: string) {
-  return async ({ dispatch, client }: ThunkArgs) => {
-    return dispatch({
-      type: "SELECT_THREAD",
-      thread
-    });
-  };
+  return { type: "SELECT_THREAD", thread }
 }
 
 /**
  * Debugger commands like stepOver, stepIn, stepUp
  *
  * @param string $0.type
  * @memberof actions/pause
  * @static
--- a/devtools/client/debugger/new/src/actions/sources/newSources.js
+++ b/devtools/client/debugger/new/src/actions/sources/newSources.js
@@ -63,16 +63,24 @@ function loadSourceMaps(sources: Source[
       sources.map(async ({ id }) => {
         const originalSources = await dispatch(loadSourceMap(id));
         sourceQueue.queueSources(originalSources);
         return originalSources;
       })
     );
 
     await sourceQueue.flush();
+
+    // We would like to sync breakpoints after we are done
+    // loading source maps as sometimes generated and original
+    // files share the same paths.
+    for (const source of sources) {
+      dispatch(checkPendingBreakpoints(source.id));
+    }
+
     return flatten(sourceList);
   };
 }
 
 /**
  * @memberof actions/sources
  * @static
  */
@@ -204,38 +212,25 @@ function restoreBlackBoxedSources(source
  * @static
  */
 export function newSource(source: Source) {
   return async ({ dispatch }: ThunkArgs) => {
     await dispatch(newSources([source]));
   };
 }
 
-export function newSources(foundSources: Source[]) {
+export function newSources(sources: Source[]) {
   return async ({ dispatch, getState }: ThunkArgs) => {
-    const sources = foundSources.filter(
+
+    const newSources = sources.filter(
       source => !getSource(getState(), source.id)
     );
 
-    if (sources.length == 0) {
-      return;
-    }
-
     dispatch({ type: "ADD_SOURCES", sources });
 
-    for (const source of sources) {
+    for (const source of newSources) {
       dispatch(checkSelectedSource(source.id));
     }
 
-    // We would like to restore the blackboxed state
-    // after loading all states to make sure the correctness.
-    dispatch(restoreBlackBoxedSources(sources));
-
-    dispatch(loadSourceMaps(sources)).then(() => {
-      // We would like to sync breakpoints after we are done
-      // loading source maps as sometimes generated and original
-      // files share the same paths.
-      for (const source of sources) {
-        dispatch(checkPendingBreakpoints(source.id));
-      }
-    });
+    dispatch(restoreBlackBoxedSources(newSources));
+    dispatch(loadSourceMaps(newSources));
   };
 }
--- a/devtools/client/debugger/new/src/client/firefox/types.js
+++ b/devtools/client/debugger/new/src/client/firefox/types.js
@@ -221,21 +221,19 @@ export type TabTarget = {
       input: string,
       cursor: number,
       func: Function,
       frameId: ?string
     ) => void
   },
   form: { consoleActor: any },
   root: any,
-  activeTab: {
-    navigateTo: ({ url: string }) => Promise<*>,
-    listWorkers: () => Promise<*>,
-    reload: () => Promise<*>
-  },
+  navigateTo: ({ url: string }) => Promise<*>,
+  listWorkers: () => Promise<*>,
+  reload: () => Promise<*>,
   destroy: () => void,
   isBrowsingContext: boolean,
   isContentProcess: boolean
 };
 
 /**
  * Clients for accessing the Firefox debug server and browser
  * @memberof firefox/clients
--- a/devtools/client/debugger/new/src/reducers/sources.js
+++ b/devtools/client/debugger/new/src/reducers/sources.js
@@ -100,26 +100,26 @@ export function createSource(state: Sour
 function update(
   state: SourcesState = initialSourcesState(),
   action: Action
 ): SourcesState {
   let location = null;
 
   switch (action.type) {
     case "UPDATE_SOURCE":
-      return updateSources(state, [action.source]);
+      return updateSource(state, action.source);
 
     case "ADD_SOURCE":
-      return updateSources(state, [action.source]);
+      return addSources(state, [action.source]);
 
     case "ADD_SOURCES":
-      return updateSources(state, action.sources);
+      return addSources(state, action.sources);
 
     case "SET_WORKERS":
-      return updateWorkers(state, action.workers, action.mainThread);
+      return updateWorkers(state, action);
 
     case "SET_SELECTED_LOCATION":
       location = {
         ...action.location,
         url: action.source.url
       };
 
       if (action.source.url) {
@@ -150,179 +150,181 @@ function update(
         url: action.url,
         line: action.line
       };
 
       prefs.pendingSelectedLocation = location;
       return { ...state, pendingSelectedLocation: location };
 
     case "LOAD_SOURCE_TEXT":
-      return setSourceTextProps(state, action);
+      return updateLoadedState(state, action);
 
     case "BLACKBOX":
       if (action.status === "done") {
         const { id, url } = action.source;
         const { isBlackBoxed } = ((action: any): DonePromiseAction).value;
         updateBlackBoxList(url, isBlackBoxed);
-        return updateSources(state, [{ id, isBlackBoxed }]);
+        return updateSource(state, { id, isBlackBoxed });
       }
       break;
 
     case "SET_PROJECT_DIRECTORY_ROOT":
       return updateProjectDirectoryRoot(state, action.url);
 
     case "NAVIGATE":
       return initialSourcesState();
 
     case "SET_FOCUSED_SOURCE_ITEM":
       return { ...state, focusedItem: action.item };
   }
 
   return state;
 }
 
-function getTextPropsFromAction(action) {
-  const { sourceId } = action;
-
-  if (action.status === "start") {
-    return { id: sourceId, loadedState: "loading" };
-  } else if (action.status === "error") {
-    return { id: sourceId, error: action.error, loadedState: "loaded" };
-  }
-
-  if (!action.value) {
-    return null;
-  }
-
+/*
+ * Update a source when its state changes
+ * e.g. the text was loaded, it was blackboxed
+ */
+function updateSource(state: SourcesState, source: Object) {
+  const existingSource = state.sources[source.id];
   return {
-    id: sourceId,
-    text: action.value.text,
-    contentType: action.value.contentType,
-    loadedState: "loaded"
+    ...state, 
+    sources: {
+      ...state.sources, 
+      [source.id]: { ...existingSource, ...source }
+    }
   };
 }
 
-// TODO: Action is coerced to `any` unfortunately because how we type
-// asynchronous actions is wrong. The `value` may be null for the
-// "start" and "error" states but we don't type it like that. We need
-// to rethink how we type async actions.
-function setSourceTextProps(state, action: LoadSourceAction): SourcesState {
-  const source = getTextPropsFromAction(action);
-  if (!source) {
-    return state;
-  }
-  return updateSources(state, [source]);
+/*
+ * Update all of the sources when an event occurs.
+ * e.g. workers are updated, project directory root changes
+ */
+function updateAllSources(
+  state: SourcesState,
+  callback: any
+) {
+  const updatedSources = Object.values(state.sources).map(source => ({
+    ...source,
+    ...callback(source)
+  }))
+
+  return addSources({ ...state, ...emptySources }, updatedSources);
 }
 
-function updateSources(state, sources: Object[]) {
-  const displayed = { ...state.displayed };
-  for (const thread in displayed) {
-    displayed[thread] = { ...displayed[thread] };
-  }
-
+/*
+ * Add sources to the sources store
+ * - Add the source to the sources store
+ * - Add the source URL to the urls map
+ * - Add the source ID to the thread displayed map
+ */
+function addSources(state: SourcesState, sources: Source[]) {
   state = {
     ...state,
     sources: { ...state.sources },
     urls: { ...state.urls },
-    displayed
+    displayed: { ...state.displayed }
   };
 
-  sources.forEach(source => updateSource(state, source));
+  for (const source of sources) {
+    const existingSource = state.sources[source.id];
+    let updatedSource = existingSource || source;
+
+    // Merge the source actor list
+    if (existingSource && source.actors) {
+      const actors = uniqBy(
+        [...existingSource.actors, ...source.actors],
+        ({ actor }) => actor
+      );
+      updatedSource = { ...updatedSource, actors }
+    }
+
+    // 1. Add the source to the sources map
+    state.sources[source.id] = updatedSource;
+
+    // 2. Update the source url map
+    const existing = state.urls[source.url] || [];
+    if (!existing.includes(source.id)) {
+      state.urls[source.url] = [...existing, source.id];
+    }
+
+    // 3. Update the displayed actor map
+    if (
+      underRoot(source, state.projectDirectoryRoot) &&
+      (!source.isExtension || getChromeAndExtenstionsEnabled({ sources: state }))
+    ) {
+      for (const actor of getSourceActors(state, source)) {
+        if (!state.displayed[actor.thread]) {
+          state.displayed[actor.thread] = {}
+        }
+        state.displayed[actor.thread][source.id] = true;
+      }
+    }
+  }
+
   return state;
 }
 
-function updateSourceUrl(state: SourcesState, source: Source) {
-  const existing = state.urls[source.url] || [];
-  if (!existing.includes(source.id)) {
-    state.urls[source.url] = [...existing, source.id];
-  }
-}
-
-function updateSource(state: SourcesState, source: Object) {
-  if (!source.id) {
-    return;
-  }
+/*
+ * Update sources when the worker list changes.
+ * - filter source actor lists so that missing threads no longer appear
+ * - NOTE: we do not remove sources for destroyed threads
+ */
+function updateWorkers(state: SourcesState, action: Object) {
+  const threads = [
+    action.mainThread,
+    ...action.workers.map(({ actor }) => actor)
+  ];
 
-  const existingSource = state.sources[source.id];
-  const updatedSource = existingSource
-    ? { ...existingSource, ...source }
-    : createSource(state, source);
-
-  // Any actors in the source are added to the existing ones.
-  if (existingSource && source.actors) {
-    updatedSource.actors = uniqBy(
-      [...existingSource.actors, ...updatedSource.actors],
-      ({ actor }) => actor
-    );
-  }
-
-  state.sources[source.id] = updatedSource;
-
-  updateSourceUrl(state, updatedSource);
-  updateDisplayedSource(state, updatedSource);
+  return updateAllSources(state, source => ({
+    actors: source.actors.filter(({ thread }) => threads.includes(thread))
+  }));
 }
 
-function updateDisplayedSource(state: SourcesState, source: Source) {
-  const root = state.projectDirectoryRoot;
-
-  if (
-    !underRoot(source, root) ||
-    (!getChromeAndExtenstionsEnabled({ sources: state }) && source.isExtension)
-  ) {
-    return;
-  }
-
-  let actors = source.actors;
-
-  // Original sources do not have actors, so use the generated source.
-  if (isOriginalSource(source)) {
-    const generatedSource = state.sources[originalToGeneratedId(source.id)];
-    actors = generatedSource ? generatedSource.actors : [];
-  }
-
-  actors.forEach(({ thread }) => {
-    if (!state.displayed[thread]) {
-      state.displayed[thread] = {};
-    }
-    state.displayed[thread][source.id] = true;
-  });
-}
-
-function updateWorkers(
-  state: SourcesState,
-  workers: WorkerList,
-  mainThread: ThreadId
-) {
-  // Filter out source actors for all removed threads.
-  const threads = [mainThread, ...workers.map(({ actor }) => actor)];
-  const updateActors = source =>
-    source.actors.filter(({ thread }) => threads.includes(thread));
-
-  const sources: Source[] = Object.values(state.sources)
-    .map((source: any) => ({ ...source, actors: updateActors(source) }))
-
-  // Regenerate derived information from the updated sources.
-  return updateSources({ ...state, ...emptySources }, sources);
-}
-
+/*
+ * Update sources when the project directory root changes
+ */
 function updateProjectDirectoryRoot(state: SourcesState, root: string) {
   prefs.projectDirectoryRoot = root;
 
-  const sources: Source[] = Object.values(state.sources).map((source: any) => {
-    return {
-      ...source,
-      relativeUrl: getRelativeUrl(source, root)
+  return updateAllSources(
+    { ...state, projectDirectoryRoot: root },
+    source => ({ relativeUrl: getRelativeUrl(source, root) })
+  );
+}
+
+/*
+ * Update a source's loaded state fields
+ * i.e. loadedState, text, error
+ */
+function updateLoadedState(state, action: LoadSourceAction): SourcesState {
+  const { sourceId } = action;
+  let source;
+
+  if (action.status === "start") {
+    source = { id: sourceId, loadedState: "loading" };
+
+  } else if (action.status === "error") {
+    source = { id: sourceId, error: action.error, loadedState: "loaded" };
+
+  } else {
+
+    if (!action.value) {
+      return state;
+    }
+
+    source = {
+      id: sourceId,
+      text: action.value.text,
+      contentType: action.value.contentType,
+      loadedState: "loaded"
     };
-  });
+  }
 
-  // Regenerate derived information from the updated sources.
-  return updateSources(
-    { ...state, projectDirectoryRoot: root, ...emptySources },
-    sources
-  );
+  return updateSource(state, source);
 }
 
 function updateBlackBoxList(url, isBlackBoxed) {
   const tabs = getBlackBoxList();
   const i = tabs.indexOf(url);
   if (i >= 0) {
     if (!isBlackBoxed) {
       tabs.splice(i, 1);
@@ -345,16 +347,26 @@ export function getBlackBoxList() {
 // module for the UI, and all of those selectors should take the
 // top-level app state, so we'd have to "wrap" them to automatically
 // pick off the piece of state we're interested in. It's impossible
 // (right now) to type those wrapped functions.
 type OuterState = { sources: SourcesState };
 
 const getSourcesState = (state: OuterState) => state.sources;
 
+function getSourceActors(state, source) {
+  if (isGenerated(source)) {
+    return source.actors;
+  }
+
+  // Original sources do not have actors, so use the generated source.
+  const generatedSource = state.sources[originalToGeneratedId(source.id)];
+  return generatedSource ? generatedSource.actors : [];
+}
+
 export function getSourceInSources(sources: SourcesMap, id: string): ?Source {
   return sources[id];
 }
 
 export function getSource(state: OuterState, id: SourceId): ?Source {
   return getSourceInSources(getSources(state), id);
 }
 
deleted file mode 100644
--- a/devtools/client/debugger/new/src/selectors/visiblePausePoints.js
+++ /dev/null
@@ -1,19 +0,0 @@
-/* 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/>. */
-
-// @flow
-
-import { getSelectedSource } from "../reducers/sources";
-import { getPausePoints } from "../reducers/ast";
-
-import type { State } from "../reducers/types";
-
-export function getVisiblePausePoints(state: State) {
-  const source = getSelectedSource(state);
-  if (!source) {
-    return null;
-  }
-
-  return getPausePoints(state, source.id);
-}
deleted file mode 100644
--- a/devtools/client/debugger/new/src/utils/pause/pausePoints.js
+++ /dev/null
@@ -1,37 +0,0 @@
-/* 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/>. */
-
-// @flow
-import { reverse } from "lodash";
-
-import type { PausePoints, PausePointsMap } from "../../reducers/types";
-
-function insertStrtAt(string, index, newString) {
-  const start = string.slice(0, index);
-  const end = string.slice(index);
-  return `${start}${newString}${end}`;
-}
-
-export function convertToList(pausePoints: PausePointsMap): PausePoints {
-  const list = [];
-  for (const line in pausePoints) {
-    for (const column in pausePoints[line]) {
-      list.push(pausePoints[line][column]);
-    }
-  }
-  return list;
-}
-
-export function formatPausePoints(text: string, pausePoints: PausePoints) {
-  const nodes = reverse(pausePoints);
-  const lines = text.split("\n");
-  nodes.forEach((node, index) => {
-    const { line, column } = node.location;
-    const { break: breakPoint, step } = node.types;
-    const types = `${breakPoint ? "b" : ""}${step ? "s" : ""}`;
-    lines[line - 1] = insertStrtAt(lines[line - 1], column, `/*${types}*/`);
-  });
-
-  return lines.join("\n");
-}
deleted file mode 100644
--- a/devtools/client/debugger/new/src/utils/pause/stepping.js
+++ /dev/null
@@ -1,48 +0,0 @@
-/* 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/>. */
-
-// @flow
-
-import { isEqual } from "lodash";
-import { isGeneratedId } from "devtools-source-map";
-import type { Frame, MappedLocation } from "../../types";
-import type { State } from "../../reducers/types";
-import { isOriginal } from "../../utils/source";
-
-import {
-  getSelectedSource,
-  getPreviousPauseFrameLocation,
-  getPausePoint
-} from "../../selectors";
-
-function getFrameLocation(source, frame: ?MappedLocation) {
-  if (!frame) {
-    return null;
-  }
-
-  return isOriginal(source) ? frame.location : frame.generatedLocation;
-}
-
-export function shouldStep(rootFrame: ?Frame, state: State, sourceMaps: any) {
-  const selectedSource = getSelectedSource(state);
-  const previousFrameInfo = getPreviousPauseFrameLocation(state);
-
-  if (!rootFrame || !selectedSource) {
-    return false;
-  }
-
-  const previousFrameLoc = getFrameLocation(selectedSource, previousFrameInfo);
-  const frameLoc = getFrameLocation(selectedSource, rootFrame);
-
-  const sameLocation = previousFrameLoc && isEqual(previousFrameLoc, frameLoc);
-  const pausePoint = getPausePoint(state, frameLoc);
-  const invalidPauseLocation = pausePoint && !pausePoint.types.step;
-
-  // We always want to pause in generated locations
-  if (!frameLoc || isGeneratedId(frameLoc.sourceId)) {
-    return false;
-  }
-
-  return sameLocation || invalidPauseLocation;
-}
--- a/devtools/client/debugger/new/src/utils/prefs.js
+++ b/devtools/client/debugger/new/src/utils/prefs.js
@@ -28,22 +28,20 @@ if (isDevelopment()) {
   pref("devtools.debugger.expressions-visible", true);
   pref("devtools.debugger.xhr-breakpoints-visible", true);
   pref("devtools.debugger.breakpoints-visible", true);
   pref("devtools.debugger.event-listeners-visible", true);
   pref("devtools.debugger.start-panel-collapsed", false);
   pref("devtools.debugger.end-panel-collapsed", false);
   pref("devtools.debugger.start-panel-size", 300);
   pref("devtools.debugger.end-panel-size", 300);
-  pref("devtools.debugger.tabs", "[]");
   pref("devtools.debugger.tabsBlackBoxed", "[]");
   pref("devtools.debugger.ui.editor-wrapping", false);
   pref("devtools.debugger.ui.framework-grouping-on", true);
   pref("devtools.debugger.pending-selected-location", "{}");
-  pref("devtools.debugger.pending-breakpoints", "{}");
   pref("devtools.debugger.expressions", "[]");
   pref("devtools.debugger.file-search-case-sensitive", false);
   pref("devtools.debugger.file-search-whole-word", false);
   pref("devtools.debugger.file-search-regex-match", false);
   pref("devtools.debugger.project-directory-root", "");
   pref("devtools.debugger.prefs-schema-version", "1.0.1");
   pref("devtools.debugger.skip-pausing", false);
   pref("devtools.debugger.features.workers", true);
@@ -86,20 +84,18 @@ export const prefs = new PrefsHelper("de
   expressionsVisible: ["Bool", "debugger.expressions-visible"],
   xhrBreakpointsVisible: ["Bool", "debugger.xhr-breakpoints-visible"],
   eventListenersVisible: ["Bool", "debugger.event-listeners-visible"],
   startPanelCollapsed: ["Bool", "debugger.start-panel-collapsed"],
   endPanelCollapsed: ["Bool", "debugger.end-panel-collapsed"],
   startPanelSize: ["Int", "debugger.start-panel-size"],
   endPanelSize: ["Int", "debugger.end-panel-size"],
   frameworkGroupingOn: ["Bool", "debugger.ui.framework-grouping-on"],
-  tabs: ["Json", "debugger.tabs", []],
   tabsBlackBoxed: ["Json", "debugger.tabsBlackBoxed", []],
   pendingSelectedLocation: ["Json", "debugger.pending-selected-location", {}],
-  pendingBreakpoints: ["Json", "debugger.pending-breakpoints", {}],
   expressions: ["Json", "debugger.expressions", []],
   fileSearchCaseSensitive: ["Bool", "debugger.file-search-case-sensitive"],
   fileSearchWholeWord: ["Bool", "debugger.file-search-whole-word"],
   fileSearchRegexMatch: ["Bool", "debugger.file-search-regex-match"],
   debuggerPrefsSchemaVersion: ["Char", "debugger.prefs-schema-version"],
   projectDirectoryRoot: ["Char", "debugger.project-directory-root", ""],
   skipPausing: ["Bool", "debugger.skip-pausing"]
 });
--- a/devtools/client/debugger/new/src/utils/source-queue.js
+++ b/devtools/client/debugger/new/src/utils/source-queue.js
@@ -24,15 +24,17 @@ export default {
     newSources = actions.newSources;
     queuedSources = [];
   },
   queue: (source: Source) => {
     queuedSources.push(source);
     queue();
   },
   queueSources: (sources: Source[]) => {
-    queuedSources = queuedSources.concat(sources);
-    queue();
+    if (sources.length > 0) {
+      queuedSources = queuedSources.concat(sources);
+      queue();
+    }
   },
 
   flush: () => Promise.all([queue.flush(), currentWork]),
   clear: () => queue.cancel()
 };
deleted file mode 100644
--- a/devtools/client/debugger/new/src/workers/parser/pausePoints.js
+++ /dev/null
@@ -1,199 +0,0 @@
-/* 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/>. */
-
-// @flow
-
-import { traverseAst } from "./utils/ast";
-import * as t from "@babel/types";
-import isEqual from "lodash/isEqual";
-
-import type { BabelNode } from "@babel/types";
-import type { SimplePath } from "./utils/simple-path";
-import type { PausePointsMap } from "./types";
-
-const isForStatement = node =>
-  t.isForStatement(node) || t.isForOfStatement(node);
-
-const isControlFlow = node =>
-  isForStatement(node) ||
-  t.isWhileStatement(node) ||
-  t.isIfStatement(node) ||
-  t.isSwitchCase(node) ||
-  t.isSwitchStatement(node) ||
-  t.isTryStatement(node) ||
-  t.isWithStatement(node);
-
-const isAssignment = node =>
-  t.isVariableDeclarator(node) ||
-  t.isAssignmentExpression(node) ||
-  t.isAssignmentPattern(node);
-
-const isImport = node => t.isImport(node) || t.isImportDeclaration(node);
-const isCall = node => t.isCallExpression(node) || t.isJSXElement(node);
-
-const inStepExpression = parent =>
-  t.isArrayExpression(parent) ||
-  t.isObjectProperty(parent) ||
-  t.isCallExpression(parent) ||
-  t.isJSXElement(parent) ||
-  t.isSequenceExpression(parent);
-
-const inExpression = (parent, grandParent) =>
-  inStepExpression(parent) ||
-  t.isJSXAttribute(grandParent) ||
-  t.isTemplateLiteral(parent);
-
-const isExport = node =>
-  t.isExportNamedDeclaration(node) || t.isExportDefaultDeclaration(node);
-
-// Finds the first call item in a step expression so that we can step
-// to the beginning of the list and either step in or over. e.g. [], x(), { }
-function isFirstCall(node, parentNode, grandParentNode) {
-  let children = [];
-  if (t.isArrayExpression(parentNode)) {
-    children = parentNode.elements;
-  }
-
-  if (t.isObjectProperty(parentNode)) {
-    children = grandParentNode.properties.map(({ value }) => value);
-  }
-
-  if (t.isSequenceExpression(parentNode)) {
-    children = parentNode.expressions;
-  }
-
-  if (t.isCallExpression(parentNode)) {
-    children = parentNode.arguments;
-  }
-
-  return children.find(child => isCall(child)) === node;
-}
-
-export function getPausePoints(sourceId: string): PausePointsMap {
-  const state = {};
-  traverseAst(sourceId, { enter: onEnter }, state);
-  return state;
-}
-
-/* eslint-disable complexity */
-function onEnter(node: BabelNode, ancestors: SimplePath[], state) {
-  const parent = ancestors[ancestors.length - 1];
-  const parentNode = parent && parent.node;
-  const grandParent = ancestors[ancestors.length - 2];
-  const grandParentNode = grandParent && grandParent.node;
-  const startLocation = node.loc.start;
-
-  if (
-    isImport(node) ||
-    t.isClassDeclaration(node) ||
-    isExport(node) ||
-    t.isDebuggerStatement(node) ||
-    t.isThrowStatement(node) ||
-    t.isBreakStatement(node) ||
-    t.isContinueStatement(node) ||
-    t.isReturnStatement(node)
-  ) {
-    return addStopPoint(state, startLocation);
-  }
-
-  if (isControlFlow(node)) {
-    addStopPoint(state, startLocation);
-
-    // We want to pause at tests so that we can pause at each iteration
-    // e.g `while (i++ < 3) { }`
-    const test = node.test || node.discriminant;
-    if (test) {
-      addStopPoint(state, test.loc.start);
-    }
-    return;
-  }
-
-  if (t.isBlockStatement(node) || t.isArrayExpression(node)) {
-    return addEmptyPoint(state, startLocation);
-  }
-
-  if (isAssignment(node)) {
-    // step at assignments unless the right side is a default assignment
-    // e.g. `( b = 2 ) => {}`
-    const defaultAssignment =
-      t.isFunction(parentNode) && parent.key === "params";
-
-    return addPoint(state, startLocation, !defaultAssignment);
-  }
-
-  if (isCall(node)) {
-    let location = startLocation;
-
-    // When functions are chained, we want to use the property location
-    // e.g `foo().bar()`
-    if (t.isMemberExpression(node.callee)) {
-      location = node.callee.property.loc.start;
-    }
-
-    // NOTE: We want to skip all nested calls in expressions except for the
-    // first call in arrays and objects expression e.g. [], {}, call
-    const step =
-      isFirstCall(node, parentNode, grandParentNode) ||
-      !inExpression(parentNode, grandParentNode);
-
-    // NOTE: we add a point at the beginning of the expression
-    // and each of the calls because the engine does not support
-    // column-based member expression calls.
-    addPoint(state, startLocation, { break: true, step });
-
-    if (location && !isEqual(location, startLocation)) {
-      addPoint(state, location, { break: true, step });
-    }
-
-    return;
-  }
-
-  if (t.isClassProperty(node)) {
-    return addBreakPoint(state, startLocation);
-  }
-
-  if (t.isFunction(node)) {
-    const { line, column } = node.loc.end;
-    addBreakPoint(state, startLocation);
-    return addEmptyPoint(state, { line, column: column - 1 });
-  }
-
-  if (!hasPoint(state, startLocation) && inStepExpression(parentNode)) {
-    return addEmptyPoint(state, startLocation);
-  }
-}
-
-function hasPoint(state, { line, column }) {
-  return state[line] && state[line][column];
-}
-
-function addPoint(
-  state,
-  location,
-  types: boolean | { break?: boolean, step?: boolean }
-) {
-  if (typeof types === "boolean") {
-    types = { step: types, break: types };
-  }
-
-  const { line, column } = location;
-
-  if (!state[line]) {
-    state[line] = {};
-  }
-  state[line][column] = { types, location };
-  return state;
-}
-
-function addStopPoint(state, location) {
-  return addPoint(state, location, { break: true, step: true });
-}
-
-function addEmptyPoint(state, location) {
-  return addPoint(state, location, {});
-}
-
-function addBreakPoint(state, location) {
-  return addPoint(state, location, { break: true });
-}
deleted file mode 100644
--- a/devtools/client/debugger/new/src/workers/parser/tests/pausePoints.spec.js
+++ /dev/null
@@ -1,43 +0,0 @@
-/* eslint max-nested-callbacks: ["error", 4]*/
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at <http://mozilla.org/MPL/2.0/>. */
-
-// @flow
-
-import cases from "jest-in-case";
-import {
-  formatPausePoints,
-  convertToList
-} from "../../../utils/pause/pausePoints";
-
-import { getPausePoints } from "../pausePoints";
-import { getSource, getOriginalSource } from "./helpers";
-import { setSource } from "../sources";
-
-cases(
-  "Parser.pausePoints",
-  ({ name, file, original, type }) => {
-    const source = original
-      ? getOriginalSource(file, type)
-      : getSource(file, type);
-
-    setSource(source);
-
-    // The coercion here is needed because getPausePoints and convertToList
-    // operate on two incompatible definitions of PausePointsMap
-    const nodes = convertToList((getPausePoints(source.id): any));
-    expect(formatPausePoints(source.text || "", nodes)).toMatchSnapshot();
-  },
-  [
-    { name: "control-flow", file: "control-flow" },
-    { name: "flow", file: "flow", original: true },
-    { name: "calls", file: "calls" },
-    { name: "statements", file: "statements" },
-    { name: "modules", file: "modules", original: true },
-    { name: "jsx", file: "jsx", original: true },
-    { name: "func", file: "func", original: true },
-    { name: "decorators", file: "decorators", original: true },
-    { name: "html", file: "parseScriptTags", type: "html" }
-  ]
-);
--- a/devtools/client/debugger/new/test/mochitest/browser_dbg-windowless-workers.js
+++ b/devtools/client/debugger/new/test/mochitest/browser_dbg-windowless-workers.js
@@ -20,82 +20,62 @@ function threadIsSelected(dbg, index) {
 // separately controlled from the same debugger.
 add_task(async function() {
   await pushPref("devtools.debugger.features.windowless-workers", true);
 
   const dbg = await initDebugger("doc-windowless-workers.html");
   const mainThread = dbg.toolbox.threadClient.actor;
 
   const workers = await getWorkers(dbg);
-  ok(workers.length == 1, "Got one worker");
-  const workerThread = workers[0].actor;
+  ok(workers.length == 2, "Got two workers");
+  const worker1Thread = workers[0].actor;
+  const worker2Thread = workers[1].actor;
 
   const mainThreadSource = findSource(dbg, "doc-windowless-workers.html");
   const workerSource = findSource(dbg, "simple-worker.js");
 
+  info("Test pausing in the main thread");
   assertNotPaused(dbg);
-
   await dbg.actions.breakOnNext();
   await waitForPaused(dbg, "doc-windowless-workers.html");
-
-  // We should be paused at the timer in doc-windowless-workers.html
-  assertPausedAtSourceAndLine(dbg, mainThreadSource.id, 9);
+  assertPausedAtSourceAndLine(dbg, mainThreadSource.id, 10);
 
-  await dbg.actions.selectThread(workerThread);
+  info("Test pausing in a worker");
+  await dbg.actions.selectThread(worker1Thread);
   assertNotPaused(dbg);
-
   await dbg.actions.breakOnNext();
   await waitForPaused(dbg, "simple-worker.js");
-
-  // We should be paused at the timer in simple-worker.js
   assertPausedAtSourceAndLine(dbg, workerSource.id, 3);
 
+  info("Test stepping in a worker");
   await stepOver(dbg);
   assertPausedAtSourceAndLine(dbg, workerSource.id, 4);
 
-  await dbg.actions.selectThread(mainThread);
-
-  await stepOver(dbg);
-  assertPausedAtSourceAndLine(dbg, mainThreadSource.id, 10);
+  info("Test resuming in a worker");
+  await resume(dbg);
+  assertNotPaused(dbg);
 
-  is(
-    findElement(dbg, "threadSourceTreeHeader", 2).innerText,
-    "simple-worker.js",
-    "simple-worker.js - worker is shown in the source tree"
-  );
-
-  clickElement(dbg, "threadSourceTreeSourceNode", 2, 2);
+  info("Test stepping in the main thread");
+  dbg.actions.selectThread(mainThread);
+  await stepOver(dbg);
+  assertPausedAtSourceAndLine(dbg, mainThreadSource.id, 11);
 
-  await waitForElement(dbg, "threadSourceTreeSourceNode", 2, 3);
-
-  is(
-    findElement(dbg, "threadSourceTreeSourceNode", 2, 3).innerText,
-    "simple-worker.js",
-    "simple-worker.js - source is shown in the source tree"
-  );
+  info("Test resuming in the mainThread");
+  await resume(dbg);
+  assertNotPaused(dbg);
 
-  is(
-    findElement(dbg, "threadSourceTreeSourceNode", 2, 3).innerText,
-    "simple-worker.js",
-    "simple-worker.js - source is shown in the source tree"
-  );
-  // Threads Pane
-  is(
-    findElement(dbg, "threadsPaneItem", 1).innerText,
-    "Main Thread",
-    "Main Thread is shown in the threads pane"
-  );
+  info("Test pausing in both workers");
+  await addBreakpoint(dbg, "simple-worker", 10);
+  invokeInTab("sayHello");
+  dbg.actions.selectThread(worker1Thread);
+  await waitForPaused(dbg);
+  assertPausedAtSourceAndLine(dbg, workerSource.id, 10);
 
-  is(
-    findElement(dbg, "threadsPaneItem", 2).innerText,
-    "simple-worker.js",
-    "simple-worker.js is shown in the threads pane"
-  );
+  dbg.actions.selectThread(worker2Thread);
+  await waitForPaused(dbg);
+  assertPausedAtSourceAndLine(dbg, workerSource.id, 10);
 
-  ok(threadIsSelected(dbg, 1), "main thread is selected");
-  ok(threadIsPaused(dbg, 1), "main thread is paused");
-  ok(threadIsPaused(dbg, 2), "simple-worker.js is paused");
-  is(findAllElements(dbg, "frames").length, 1, "Main Thread has one frame");
-
-  clickElement(dbg, "threadsPaneItem", 2);
-  await waitFor(() => threadIsSelected(dbg, 2));
-  is(findAllElements(dbg, "frames").length, 1, "simple worker has one frame");
+  info("Test stepping in second worker and not the first");
+  await stepOver(dbg);
+  assertPausedAtSourceAndLine(dbg, workerSource.id, 11);
+  dbg.actions.selectThread(worker1Thread);
+  assertPausedAtSourceAndLine(dbg, workerSource.id, 10);
 });
--- a/devtools/client/debugger/new/test/mochitest/examples/doc-windowless-workers.html
+++ b/devtools/client/debugger/new/test/mochitest/examples/doc-windowless-workers.html
@@ -1,19 +1,25 @@
 <!DOCTYPE HTML>
 <html>
 
 <script>
-var worker = new Worker("simple-worker.js");
+var worker1 = new Worker("simple-worker.js");
+var worker2 = new Worker("simple-worker.js");
 
 var count = 0;
 function timer() {
   var n = ++count;
   console.log("MAIN THREAD SAYS HELLO! " + n);
 }
 
 setInterval(timer, 1000);
+
+window.sayHello = () => {
+  worker1.postMessage({yo: 'yo'})
+  worker2.postMessage({yo: 'yo'})
+}
 </script>
 
 <body>
 Hello World!
 </body>
 </html>
--- a/devtools/client/debugger/new/test/mochitest/examples/simple-worker.js
+++ b/devtools/client/debugger/new/test/mochitest/examples/simple-worker.js
@@ -1,7 +1,11 @@
 var count = 0;
 function timer() {
   var n = ++count;
   console.log("WORKER SAYS HELLO! " + n);
 }
 
 setInterval(timer, 1000);
+
+self.onmessage = () => {
+  console.log('hi')
+}
\ No newline at end of file
--- a/devtools/client/debugger/new/test/mochitest/helpers.js
+++ b/devtools/client/debugger/new/test/mochitest/helpers.js
@@ -500,19 +500,17 @@ function isSelectedFrameSelected(dbg, st
  * Clear all the debugger related preferences.
  */
 function clearDebuggerPreferences() {
   asyncStorage.clear();
   Services.prefs.clearUserPref("devtools.recordreplay.enabled");
   Services.prefs.clearUserPref("devtools.debugger.pause-on-exceptions");
   Services.prefs.clearUserPref("devtools.debugger.pause-on-caught-exceptions");
   Services.prefs.clearUserPref("devtools.debugger.ignore-caught-exceptions");
-  Services.prefs.clearUserPref("devtools.debugger.tabs");
   Services.prefs.clearUserPref("devtools.debugger.pending-selected-location");
-  Services.prefs.clearUserPref("devtools.debugger.pending-breakpoints");
   Services.prefs.clearUserPref("devtools.debugger.expressions");
   Services.prefs.clearUserPref("devtools.debugger.call-stack-visible");
   Services.prefs.clearUserPref("devtools.debugger.scopes-visible");
   Services.prefs.clearUserPref("devtools.debugger.skip-pausing");
 }
 
 /**
  * Intilializes the debugger.
--- a/devtools/client/inspector/markup/test/browser.ini
+++ b/devtools/client/inspector/markup/test/browser.ini
@@ -1,9 +1,14 @@
 [DEFAULT]
+prefs =
+  # Bug 1520383 - Force devtools.chrome.enabled to false regardless of whether
+  # we're in an official build so we don't show event bubbles from chrome event
+  # listeners in the inspector on unprivileged test pages.
+  devtools.chrome.enabled=false
 tags = devtools
 subsuite = devtools
 support-files =
   doc_markup_anonymous.html
   doc_markup_anonymous_xul.xul
   doc_markup_dragdrop.html
   doc_markup_dragdrop_autoscroll_01.html
   doc_markup_dragdrop_autoscroll_02.html
--- a/devtools/client/inspector/rules/rules.js
+++ b/devtools/client/inspector/rules/rules.js
@@ -26,26 +26,29 @@ const {
   VIEW_NODE_VARIABLE_TYPE,
   VIEW_NODE_FONT_TYPE,
 } = require("devtools/client/inspector/shared/node-types");
 const TooltipsOverlay = require("devtools/client/inspector/shared/tooltips-overlay");
 const {createChild, promiseWarn} = require("devtools/client/inspector/shared/utils");
 const {debounce} = require("devtools/shared/debounce");
 const EventEmitter = require("devtools/shared/event-emitter");
 
+loader.lazyRequireGetter(this, "flashElementOn", "devtools/client/inspector/markup/utils", true);
+loader.lazyRequireGetter(this, "flashElementOff", "devtools/client/inspector/markup/utils", true);
 loader.lazyRequireGetter(this, "ClassListPreviewer", "devtools/client/inspector/rules/views/class-list-previewer");
 loader.lazyRequireGetter(this, "StyleInspectorMenu", "devtools/client/inspector/shared/style-inspector-menu");
 loader.lazyRequireGetter(this, "AutocompletePopup", "devtools/client/shared/autocomplete-popup");
 loader.lazyRequireGetter(this, "KeyShortcuts", "devtools/client/shared/key-shortcuts");
 loader.lazyRequireGetter(this, "clipboardHelper", "devtools/shared/platform/clipboard");
 
 const HTML_NS = "http://www.w3.org/1999/xhtml";
 const PREF_UA_STYLES = "devtools.inspector.showUserAgentStyles";
 const PREF_DEFAULT_COLOR_UNIT = "devtools.defaultColorUnit";
 const FILTER_CHANGED_TIMEOUT = 150;
+const REMOVE_FLASH_ELEMENT_DURATION = 500;
 
 // This is used to parse user input when filtering.
 const FILTER_PROP_RE = /\s*([^:\s]*)\s*:\s*(.*?)\s*;?$/;
 // This is used to parse the filter search value to see if the filter
 // should be strict or not
 const FILTER_STRICT_RE = /\s*`(.*?)`\s*$/;
 const INSET_POINT_TYPES = ["top", "right", "bottom", "left"];
 
@@ -118,16 +121,18 @@ function CssRuleView(inspector, document
   this._onAddRule = this._onAddRule.bind(this);
   this._onContextMenu = this._onContextMenu.bind(this);
   this._onCopy = this._onCopy.bind(this);
   this._onFilterStyles = this._onFilterStyles.bind(this);
   this._onClearSearch = this._onClearSearch.bind(this);
   this._onTogglePseudoClassPanel = this._onTogglePseudoClassPanel.bind(this);
   this._onTogglePseudoClass = this._onTogglePseudoClass.bind(this);
   this._onToggleClassPanel = this._onToggleClassPanel.bind(this);
+  this.highlightElementRule = this.highlightElementRule.bind(this);
+  this.highlightProperty = this.highlightProperty.bind(this);
 
   const doc = this.styleDocument;
   this.element = doc.getElementById("ruleview-container-focusable");
   this.addRuleButton = doc.getElementById("ruleview-add-rule-button");
   this.searchField = doc.getElementById("ruleview-searchbox");
   this.searchClearButton = doc.getElementById("ruleview-searchinput-clear");
   this.pseudoClassPanel = doc.getElementById("pseudo-class-panel");
   this.pseudoClassToggle = doc.getElementById("pseudo-class-panel-toggle");
@@ -1046,16 +1051,18 @@ CssRuleView.prototype = {
     this.element.appendChild(container);
 
     header.addEventListener("click", () => {
       this._toggleContainerVisibility(twisty, container, isPseudo,
         !this.showPseudoElements);
     });
 
     if (isPseudo) {
+      container.id = "pseudo-elements-container";
+      twisty.id = "pseudo-elements-header-twisty";
       this._toggleContainerVisibility(twisty, container, isPseudo,
         this.showPseudoElements);
     }
 
     return container;
   },
 
   /**
@@ -1603,16 +1610,180 @@ CssRuleView.prototype = {
                event.target === this.searchField &&
                this._onClearSearch()) {
       // Handle the search box's keypress event. If the escape key is pressed,
       // clear the search box field.
       event.preventDefault();
       event.stopPropagation();
     }
   },
+
+  /**
+   * Temporarily flash the given element.
+   *
+   * @param  {Element} element
+   *         The element.
+   */
+  _flashElement(element) {
+    flashElementOn(element);
+    flashElementOff(element);
+
+    if (this._removeFlashOutTimer) {
+      clearTimeout(this._removeFlashOutTimer);
+      this._removeFlashOutTimer = null;
+    }
+
+    // Remove the flash-out class to prevent the element from re-flashing when the view
+    // is resized.
+    this._removeFlashOutTimer = setTimeout(() => {
+      element.classList.remove("flash-out");
+      // Emit "scrolled-to-property" for use by tests.
+      this.emit("scrolled-to-element");
+    }, REMOVE_FLASH_ELEMENT_DURATION);
+  },
+
+  /**
+   * Scrolls to the top of either the rule or declaration. The view will try to scroll to
+   * the rule if both can fit in the viewport. If not, then scroll to the declaration.
+   *
+   * @param  {Element} rule
+   *         The rule to scroll to.
+   * @param  {Element|null} declaration
+   *         Optional. The declaration to scroll to.
+   * @param  {String} scrollBehavior
+   *         Optional. The transition animation when scrolling.
+   */
+  _scrollToElement(rule, declaration, scrollBehavior = "smooth") {
+    let elementToScrollTo = rule;
+
+    if (declaration) {
+      const { offsetTop, offsetHeight } = declaration;
+      // Get the distance between both the rule and declaration. If the distance is
+      // greater than the height of the rule view, then only scroll to the declaration.
+      const distance = (offsetTop + offsetHeight) - rule.offsetTop;
+
+      if (this.element.parentNode.offsetHeight <= distance) {
+        elementToScrollTo = declaration;
+      }
+    }
+
+    elementToScrollTo.scrollIntoView({ behavior: scrollBehavior });
+  },
+
+  /**
+   * Toggles the visibility of the pseudo element rule's container.
+   */
+  _togglePseudoElementRuleContainer() {
+    const container = this.styleDocument.getElementById("pseudo-elements-container");
+    const twisty = this.styleDocument.getElementById("pseudo-elements-header-twisty");
+    this._toggleContainerVisibility(twisty, container, true, true);
+  },
+
+  /**
+   * Finds the rule with the matching actorID and highlights it.
+   *
+   * @param  {String} ruleId
+   *         The actorID of the rule.
+   */
+  highlightElementRule: function(ruleId) {
+    let scrollBehavior = "smooth";
+
+    const rule = this.rules.find(r => r.domRule.actorID === ruleId);
+
+    if (!rule) {
+      return;
+    }
+
+    if (rule.domRule.actorID === ruleId) {
+      // If using 2-Pane mode, then switch to the Rules tab first.
+      if (!this.inspector.is3PaneModeEnabled) {
+        this.inspector.sidebar.select("ruleview");
+      }
+
+      if (rule.pseudoElement.length && !this.showPseudoElements) {
+        scrollBehavior = "auto";
+        this._togglePseudoElementRuleContainer();
+      }
+
+      const { editor: { element } } = rule;
+
+      // Scroll to the top of the rule and highlight it.
+      this._scrollToElement(element, null, scrollBehavior);
+      this._flashElement(element);
+    }
+  },
+
+  /**
+   * Finds the specified TextProperty name in the rule view. If found, scroll to and
+   * flash the TextProperty.
+   *
+   * @param  {String} name
+   *         The property name to scroll to and highlight.
+   * @return {Boolean} true if the TextProperty name is found, and false otherwise.
+   */
+  highlightProperty: function(name) {
+    for (const rule of this.rules) {
+      for (const textProp of rule.textProps) {
+        if (textProp.overridden || textProp.invisible || !textProp.enabled) {
+          continue;
+        }
+
+        const { editor: { selectorText } } = rule;
+        let scrollBehavior = "smooth";
+
+        // First, search for a matching authored property.
+        if (textProp.name === name) {
+          // If using 2-Pane mode, then switch to the Rules tab first.
+          if (!this.inspector.is3PaneModeEnabled) {
+            this.inspector.sidebar.select("ruleview");
+          }
+
+          // If the property is being applied by a pseudo element rule, expand the pseudo
+          // element list container.
+          if (rule.pseudoElement.length && !this.showPseudoElements) {
+            // Set the scroll behavior to "auto" to avoid timing issues between toggling
+            // the pseudo element container and scrolling smoothly to the rule.
+            scrollBehavior = "auto";
+            this._togglePseudoElementRuleContainer();
+          }
+
+          // Scroll to the top of the property's rule so that both the property and its
+          // rule are visible.
+          this._scrollToElement(selectorText, textProp.editor.element, scrollBehavior);
+          this._flashElement(textProp.editor.element);
+
+          return true;
+        }
+
+        // If there is no matching property, then look in computed properties.
+        for (const computed of textProp.computed) {
+          if (computed.name === name) {
+            if (!this.inspector.is3PaneModeEnabled) {
+              this.inspector.sidebar.select("ruleview");
+            }
+
+            if (textProp.rule.pseudoElement.length && !this.showPseudoElements) {
+              scrollBehavior = "auto";
+              this._togglePseudoElementRuleContainer();
+            }
+
+            // Expand the computed list.
+            textProp.editor.expandForFilter();
+
+            this._scrollToElement(selectorText, computed.element, scrollBehavior);
+            this._flashElement(computed.element);
+
+            return true;
+          }
+        }
+      }
+    }
+
+    return false;
+  },
 };
 
 /**
  * Helper functions
  */
 
 /**
  * Walk up the DOM from a given node until a parent property holder is found.
--- a/devtools/client/inspector/rules/test/browser.ini
+++ b/devtools/client/inspector/rules/test/browser.ini
@@ -182,16 +182,18 @@ skip-if = (os == "win" && debug) # bug 9
 [browser_rules_grid-toggle_01.js]
 [browser_rules_grid-toggle_01b.js]
 [browser_rules_grid-toggle_02.js]
 [browser_rules_grid-toggle_03.js]
 [browser_rules_grid-toggle_04.js]
 [browser_rules_grid-toggle_05.js]
 [browser_rules_gridline-names-autocomplete.js]
 [browser_rules_guessIndentation.js]
+[browser_rules_highlight-element-rule.js]
+[browser_rules_highlight-property.js]
 [browser_rules_highlight-used-fonts.js]
 [browser_rules_inherited-properties_01.js]
 [browser_rules_inherited-properties_02.js]
 [browser_rules_inherited-properties_03.js]
 [browser_rules_inherited-properties_04.js]
 [browser_rules_inline-source-map.js]
 [browser_rules_inline-style-order.js]
 [browser_rules_invalid.js]
new file mode 100644
--- /dev/null
+++ b/devtools/client/inspector/rules/test/browser_rules_highlight-element-rule.js
@@ -0,0 +1,42 @@
+/* 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";
+
+// Tests that the rule view's highlightElementRule scrolls to the specified rule.
+
+const TEST_URI = `
+  <style type="text/css">
+    .test::after {
+      content: "!";
+      color: red;
+    }
+  </style>
+  <div class="test">Hello</div>
+`;
+
+add_task(async function() {
+  await addTab("data:text/html;charset=utf-8," + encodeURIComponent(TEST_URI));
+  const { inspector, view } = await openRuleView();
+
+  await selectNode(".test", inspector);
+  const { rules, styleWindow } = view;
+
+  info("Highlight .test::after rule.");
+  const ruleId = rules[0].domRule.actorID;
+
+  info("Wait for the view to scroll to the property.");
+  const onHighlightProperty = view.once("scrolled-to-element");
+
+  view.highlightElementRule(ruleId);
+
+  await onHighlightProperty;
+
+  ok(isInViewport(rules[0].editor.element, styleWindow), ".test::after is in view.");
+});
+
+function isInViewport(element, win) {
+  const { top, left, bottom, right } = element.getBoundingClientRect();
+  return top >= 0 && bottom <= win.innerHeight && left >= 0 && right <= win.innerWidth;
+}
new file mode 100644
--- /dev/null
+++ b/devtools/client/inspector/rules/test/browser_rules_highlight-property.js
@@ -0,0 +1,69 @@
+/* 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";
+
+// Tests that the rule view's highlightProperty scrolls to the specified declaration.
+
+const TEST_URI = `
+  <style type="text/css">
+    .test {
+      font-size: 12px;
+      border: 1px solid blue;
+    }
+
+    .test::after {
+      content: "!";
+      color: red;
+    }
+  </style>
+  <div class="test">Hello this is a test</div>
+`;
+
+add_task(async function() {
+  await addTab("data:text/html;charset=utf-8," + encodeURIComponent(TEST_URI));
+  const { inspector, view } = await openRuleView();
+
+  await selectNode(".test", inspector);
+  const { rules, styleWindow } = view;
+
+  info("Highlight the computed border-left-width declaration in the rule view.");
+  const borderLeftWidthStyle = rules[2].textProps[1].computed
+    .find(({ name }) => name === "border-left-width");
+
+  let onHighlightProperty = view.once("scrolled-to-element");
+  let isHighlighted = view.highlightProperty("border-left-width");
+  await onHighlightProperty;
+
+  ok(isHighlighted, "border-left-property is highlighted.");
+  ok(isInViewport(borderLeftWidthStyle.element, styleWindow),
+    "border-left-width is in view.");
+
+  info("Highlight the computed font-size declaration in the rule view.");
+  const fontSize = rules[2].textProps[0].editor;
+
+  info("Wait for the view to scroll to the property.");
+  onHighlightProperty = view.once("scrolled-to-element");
+  isHighlighted = view.highlightProperty("font-size");
+  await onHighlightProperty;
+
+  ok(isHighlighted, "font-size property is highlighted.");
+  ok(isInViewport(fontSize.element, styleWindow), "font-size is in view.");
+
+  info("Highlight the pseudo-element's color declaration in the rule view.");
+  const color = rules[0].textProps[1].editor;
+
+  info("Wait for the view to scroll to the property.");
+  onHighlightProperty = view.once("scrolled-to-element");
+  isHighlighted = view.highlightProperty("color");
+  await onHighlightProperty;
+
+  ok(isHighlighted, "color property is highlighted.");
+  ok(isInViewport(color.element, styleWindow), "color property is in view.");
+});
+
+function isInViewport(element, win) {
+  const { top, left, bottom, right } = element.getBoundingClientRect();
+  return top >= 0 && bottom <= win.innerHeight && left >= 0 && right <= win.innerWidth;
+}
--- a/devtools/client/performance-new/browser.js
+++ b/devtools/client/performance-new/browser.js
@@ -3,37 +3,37 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 "use strict";
 const Services = require("Services");
 
 const TRANSFER_EVENT = "devtools:perf-html-transfer-profile";
 const SYMBOL_TABLE_REQUEST_EVENT = "devtools:perf-html-request-symbol-table";
 const SYMBOL_TABLE_RESPONSE_EVENT = "devtools:perf-html-reply-symbol-table";
 const UI_BASE_URL_PREF = "devtools.performance.recording.ui-base-url";
-const UI_BASE_URL_DEFAULT = "https://perf-html.io";
+const UI_BASE_URL_DEFAULT = "https://profiler.firefox.com";
 const OBJDIRS_PREF = "devtools.performance.recording.objdirs";
 
 /**
  * This file contains all of the privileged browser-specific functionality. This helps
  * keep a clear separation between the privileged and non-privileged client code. It
  * is also helpful in being able to mock out browser behavior for tests, without
  * worrying about polluting the browser environment.
  */
 
 /**
- * Once a profile is received from the actor, it needs to be opened up in perf.html
- * to be analyzed. This function opens up perf.html into a new browser tab, and injects
- * the profile via a frame script.
+ * Once a profile is received from the actor, it needs to be opened up in
+ * profiler.firefox.com to be analyzed. This function opens up profiler.firefox.com
+ * into a new browser tab, and injects the profile via a frame script.
  *
  * @param {object} profile - The Gecko profile.
  * @param {function} getSymbolTableCallback - A callback function with the signature
  *   (debugName, breakpadId) => Promise<SymbolTableAsTuple>, which will be invoked
- *   when perf-html.io sends SYMBOL_TABLE_REQUEST_EVENT messages to us. This function
- *   should obtain a symbol table for the requested binary and resolve the returned
- *   promise with it.
+ *   when profiler.firefox.com sends SYMBOL_TABLE_REQUEST_EVENT messages to us. This
+ *   function should obtain a symbol table for the requested binary and resolve the
+ *   returned promise with it.
  */
 function receiveProfile(profile, getSymbolTableCallback) {
   // Find the most recently used window, as the DevTools client could be in a variety
   // of hosts.
   const win = Services.wm.getMostRecentWindow("navigator:browser");
   if (!win) {
     throw new Error("No browser window");
   }
--- a/devtools/client/performance-new/components/Description.js
+++ b/devtools/client/performance-new/components/Description.js
@@ -43,26 +43,26 @@ class Description extends PureComponent 
   render() {
     return div(
       { className: "perf-description" },
       p(null,
         "This new recording panel is a bit different from the existing " +
           "performance panel. It records the entire browser, and then opens up " +
           "and shares the profile with ",
         this.renderLink(
-          "https://perf-html.io",
-          "perf-html.io"
+          "https://profiler.firefox.com",
+          "profiler.firefox.com"
         ),
         ", a Mozilla performance analysis tool."
       ),
       p(null,
         "This is still a prototype. Join along or file bugs at: ",
         this.renderLink(
-          "https://github.com/firefox-devtools/perf.html",
-          "github.com/firefox-devtools/perf.html"
+          "https://github.com/firefox-devtools/profiler",
+          "github.com/firefox-devtools/profiler"
         ),
         "."
       )
     );
   }
 }
 
 module.exports = Description;
--- a/devtools/client/performance-new/frame-script.js
+++ b/devtools/client/performance-new/frame-script.js
@@ -1,16 +1,16 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 "use strict";
 /* global addMessageListener, addEventListener, content, sendAsyncMessage */
 
 /**
- * This frame script injects itself into perf-html.io and injects the profile
+ * This frame script injects itself into profiler.firefox.com and injects the profile
  * into the page. It is mostly taken from the Gecko Profiler Addon implementation.
  */
 
 const TRANSFER_EVENT = "devtools:perf-html-transfer-profile";
 const SYMBOL_TABLE_REQUEST_EVENT = "devtools:perf-html-request-symbol-table";
 const SYMBOL_TABLE_RESPONSE_EVENT = "devtools:perf-html-reply-symbol-table";
 
 let gProfile = null;
--- a/devtools/client/preferences/debugger.js
+++ b/devtools/client/preferences/debugger.js
@@ -34,20 +34,18 @@ pref("devtools.debugger.workers-visible"
 pref("devtools.debugger.breakpoints-visible", true);
 pref("devtools.debugger.expressions-visible", true);
 pref("devtools.debugger.xhr-breakpoints-visible", true);
 pref("devtools.debugger.event-listeners-visible", false);
 pref("devtools.debugger.start-panel-collapsed", false);
 pref("devtools.debugger.end-panel-collapsed", false);
 pref("devtools.debugger.start-panel-size", 300);
 pref("devtools.debugger.end-panel-size", 300);
-pref("devtools.debugger.tabs", "[]");
 pref("devtools.debugger.tabsBlackBoxed", "[]");
 pref("devtools.debugger.pending-selected-location", "{}");
-pref("devtools.debugger.pending-breakpoints", "{}");
 pref("devtools.debugger.expressions", "[]");
 pref("devtools.debugger.event-listener-breakpoints", "[]");
 pref("devtools.debugger.file-search-case-sensitive", false);
 pref("devtools.debugger.file-search-whole-word", false);
 pref("devtools.debugger.file-search-regex-match", false);
 pref("devtools.debugger.project-directory-root", "");
 pref("devtools.debugger.skip-pausing", false);
 pref("devtools.debugger.logging", false);
--- a/devtools/client/preferences/devtools-client.js
+++ b/devtools/client/preferences/devtools-client.js
@@ -130,21 +130,21 @@ pref("devtools.performance.ui.enable-mem
 // Enable experimental options in the UI only in Nightly
 #if defined(NIGHTLY_BUILD)
 pref("devtools.performance.ui.experimental", true);
 #else
 pref("devtools.performance.ui.experimental", false);
 #endif
 
 // Preferences for the new performance panel
-// This pref configures the base URL for the perf.html instance to use. This is
-// useful so that a developer can change it while working on perf.html, or in
+// This pref configures the base URL for the profiler.firefox.com instance to use. This is
+// useful so that a developer can change it while working on profiler.firefox.com, or in
 // tests.
 // This isn't exposed directly to the user.
-pref("devtools.performance.recording.ui-base-url", "https://perf-html.io");
+pref("devtools.performance.recording.ui-base-url", "https://profiler.firefox.com");
 
 // A JSON array of strings, where each string is a file path to an objdir on
 // the host machine. This is used in order to look up symbol information from
 // build artifacts of local builds.
 pref("devtools.performance.recording.objdirs", "[]");
 
 // The default cache UI setting
 pref("devtools.cache.disabled", false);
--- a/devtools/client/themes/rules.css
+++ b/devtools/client/themes/rules.css
@@ -635,8 +635,18 @@
 
 #pseudo-class-panel-toggle::before {
   background-image: url("chrome://devtools/skin/images/pseudo-class.svg");
 }
 
 #class-panel-toggle::before {
   content: ".cls";
 }
+
+.flash-out {
+  animation: flash-out 1s ease-out;
+}
+
+@keyframes flash-out {
+  from {
+    background: var(--theme-contrast-background);
+  }
+}
--- a/devtools/client/webconsole/test/chrome/test_render_perf.html
+++ b/devtools/client/webconsole/test/chrome/test_render_perf.html
@@ -12,17 +12,18 @@
 <p>Test for render perf</p>
 <div id="output"></div>
 
 <script type="text/javascript">
 "use strict";
 
 // To analyze the profile results:
 // > ./mach mochitest test_render_perf.html
-// Then open https://perf-html.io and drag the json file printed at the end of this test
+// Then open https://profiler.firefox.com and drag the json file printed at the end of
+// this test
 
 const NUM_MESSAGES = 4000;
 const NUM_STREAMING = 100;
 const {FileUtils} = ChromeUtils.import("resource://gre/modules/FileUtils.jsm");
 const Services = browserRequire("Services");
 Services.prefs.setIntPref("devtools.hud.loglimit", NUM_MESSAGES);
 const WebConsoleWrapper = browserRequire("devtools/client/webconsole/webconsole-wrapper");
 const actions =
@@ -173,17 +174,17 @@ window.onload = async function() {
     document.getElementById("output"),
     {hud: EventEmitter.decorate({proxy: {}}), focus: () => {}},
     {},
     null,
     document,
   );
   wrapper.init();
 
-  // From https://github.com/firefox-devtools/perf.html/blob/b73eb73df04c7df51464fa50eeadef3dc7f5d4e2/docs/gecko-profile-format.md#L21
+  // From https://github.com/firefox-devtools/profiler/blob/b73eb73df04c7df51464fa50eeadef3dc7f5d4e2/docs/gecko-profile-format.md#L21
   const settings = {
     entries: 100000000,
     interval: 1,
     features: ["js"],
     threads: ["GeckoMain"],
   };
   Services.profiler.StartProfiler(
     settings.entries,
@@ -220,17 +221,17 @@ window.onload = async function() {
 
   info(`
 
 SAVING PROFILE: ${file.path}
 
 To upload the profile, run the following commands:
 
   gzip ${file.path}
-  curl 'https://profile-store.appspot.com/compressed-store' --compressed --data-binary @${file.path}.gz | awk '{print "Hosted at: https://perf-html.io/public/"$1}'
+  curl 'https://profile-store.appspot.com/compressed-store' --compressed --data-binary @${file.path}.gz | awk '{print "Hosted at: https://profiler.firefox.com/public/"$1}'
 
 
 `);
 
   SimpleTest.finish();
 };
 </script>
 </body>
--- a/devtools/docs/backend/client-api.md
+++ b/devtools/docs/backend/client-api.md
@@ -119,18 +119,16 @@ client.attachThread(response.threadActor
   }
 
   // Attach listeners for thread events.
   threadClient.addListener("paused", onPause);
   threadClient.addListener("resumed", fooListener);
   threadClient.addListener("detached", fooListener);
   threadClient.addListener("framesadded", onFrames);
   threadClient.addListener("framescleared", fooListener);
-  threadClient.addListener("scriptsadded", onScripts);
-  threadClient.addListener("scriptscleared", fooListener);
 
   // Resume the thread.
   threadClient.resume();
 
   // Debugger is now ready and debuggee is running.
 });
 ```
 
@@ -186,18 +184,16 @@ function debugTab() {
       // Attach to the thread (context).
       targetFront.attachThread().then(([response, threadClient]) => {
         // Attach listeners for thread events.
         threadClient.addListener("paused", onPause);
         threadClient.addListener("resumed", fooListener);
         threadClient.addListener("detached", fooListener);
         threadClient.addListener("framesadded", onFrames);
         threadClient.addListener("framescleared", fooListener);
-        threadClient.addListener("scriptsadded", onScripts);
-        threadClient.addListener("scriptscleared", fooListener);
 
         // Resume the thread.
         threadClient.resume();
         // Debugger is now ready and debuggee is running.
       });
     });
   });
 }
@@ -234,34 +230,16 @@ function onFrames() {
   for (let frame of client.activeThread.cachedFrames) {
     // frame is a Debugger.Frame grip.
     dump("frame: " + frame.toSource() + "\n");
     inspectFrame(frame);
   }
 }
 
 /**
- * Handler for scriptsadded events.
- */
-function onScripts() {
-  // Get the list of scripts in the server.
-  for (let script of client.activeThread.cachedScripts) {
-    // script is a Debugger.Script grip.
-    dump("script: " + script.toSource() + "\n");
-  }
-
-  // Resume execution, since this is the last thing going on in the paused
-  // state and there is no UI in this program. Wait a bit so that object
-  // inspection has a chance to finish.
-  setTimeout(() => {
-    threadClient.resume();
-  }, 1000);
-}
-
-/**
  * Helper function to inspect the provided frame.
  */
 function inspectFrame(frame) {
   // Get the "this" object.
   if (frame["this"]) {
     getObjectProperties(frame["this"]);
   }
 
--- a/devtools/docs/contributing/performance.md
+++ b/devtools/docs/contributing/performance.md
@@ -87,20 +87,20 @@ The Performance API also allows recordin
 window.performance.mark("my-function-start");
 
 // Run the code you want to measure
 
 // Once it is done, do:
 window.performance.measure("my-function", "my-function-start");
 ```
 
-This marker will appear in the `Marker Chart` section in perf-html, in the `UserTiming` lines:
+This marker will appear in the `Marker Chart` section in [profiler.firefox.com](https://profiler.firefox.com), in the `UserTiming` lines:
   ![custom markers](performance/profiler-custom-markers.png)
 
-You can double click on it to make perf-html display the record during this precise moment in time,
+You can double click on it to make [profiler.firefox.com](https://profiler.firefox.com) display the record during this precise moment in time,
 and the call tree will only display what was executed during this measurement.
 
 ### Prototype quickly
 
 Sometimes the best way to find what is slow is to comment blocks of code out
 and uncomment them one by one until you identify the culprit. And then focus on it.
 
 There are few things worse than spending a long time refactoring the piece of code that was not slow to begin with!
--- a/devtools/docs/tests/writing-perf-tests.md
+++ b/devtools/docs/tests/writing-perf-tests.md
@@ -89,17 +89,18 @@ There is a `runTest` helper method that 
 // Calling `runTest` will immediately start recording your action duration.
 // You can execute any necessary setup action you don't want to record before calling it.
 let test = this.runTest("my.test.name"); // `runTest` expects the test name as argument
 
 // <== Do an action you want to record here
 
 // Once your action is completed, call `runTest` returned object's `done` method.
 // It will automatically record the action duration and appear in PerfHerder as a new subtest.
-// It also creates markers in the profiler so that you can better inspect this action in perf-html.
+// It also creates markers in the profiler so that you can better inspect this action in
+// profiler.firefox.com.
 test.done();
 ```
 
 So for our click example it would be:
 ```
 async inspector() {
   await this.testSetup(url);
   let toolbox = await this.openToolboxAndLog(label + ".inspector", "inspector");
@@ -140,17 +141,17 @@ Also, you can record a profile while run
 ```
 `--geckoProfiler` enables the profiler
 `--geckoProfileEntries` defines the profiler buffer size, which needs to be large while recording performance tests
 
 Once it is done executing, the profile lives in a zip file you have to uncompress like this:
 ```
 unzip testing/mozharness/build/blobber_upload_dir/profile_damp.zip
 ```
-Then you have to open [https://perf-html.io/](https://perf-html.io/) and manually load the profile file that lives here: `profile_damp/page_0_pagecycle_1/cycle_0.profile`
+Then you have to open [https://profiler.firefox.com/](https://profiler.firefox.com/) and manually load the profile file that lives here: `profile_damp/page_0_pagecycle_1/cycle_0.profile`
 
 ## How to write a good performance test?
 
 ### Verify that you wait for all asynchronous code
 
 If your test involves asynchronous code, which is very likely given the DevTools codebase, please review carefully your test script.
 You should ensure that _any_ code ran directly or indirectly by your test is completed.
 You should not only wait for the functions related to the very precise feature you are trying to measure.
--- a/devtools/server/actors/addon/webextension-inspected-window.js
+++ b/devtools/server/actors/addon/webextension-inspected-window.js
@@ -133,17 +133,17 @@ CustomizedReload.prototype = {
         if (this.ignoreCache) {
           reloadFlags |= Ci.nsIWebNavigation.LOAD_FLAGS_BYPASS_CACHE;
         }
 
         try {
           if (this.injectedScript) {
             // Listen to the newly created document elements only if there is an
             // injectedScript to evaluate.
-            Services.obs.addObserver(this, "document-element-inserted");
+            Services.obs.addObserver(this, "initial-document-element-inserted");
           }
 
           // Watch the loading progress and clear the current CustomizedReload once the
           // page has been reloaded (or if its reloading has been interrupted).
           this.docShell.addProgressListener(this,
                                             Ci.nsIWebProgress.NOTIFY_STATE_DOCUMENT);
 
           this.webNavigation.reload(reloadFlags);
@@ -154,17 +154,17 @@ CustomizedReload.prototype = {
         }
       });
     }
 
     return this.waitForReloadCompleted;
   },
 
   observe(subject, topic, data) {
-    if (topic !== "document-element-inserted") {
+    if (topic !== "initial-document-element-inserted") {
       return;
     }
 
     const document = subject;
     const window = document && document.defaultView;
 
     // Filter out non interesting documents.
     if (!document || !document.location || !window) {
@@ -227,17 +227,17 @@ CustomizedReload.prototype = {
   stop(error) {
     if (this.stopped) {
       return;
     }
 
     this.docShell.removeProgressListener(this);
 
     if (this.injectedScript) {
-      Services.obs.removeObserver(this, "document-element-inserted");
+      Services.obs.removeObserver(this, "initial-document-element-inserted");
     }
 
     // Reset the customized user agent.
     if (this.userAgent && this.docShell.customUserAgent == this.userAgent) {
       this.docShell.customUserAgent = null;
     }
 
     if (error) {
--- a/devtools/server/tests/unit/test_framebindings-07.js
+++ b/devtools/server/tests/unit/test_framebindings-07.js
@@ -2,16 +2,17 @@
    http://creativecommons.org/publicdomain/zero/1.0/ */
 /* eslint-disable no-shadow, max-nested-callbacks */
 
 "use strict";
 
 var gDebuggee;
 var gClient;
 var gThreadClient;
+const EnvironmentClient = require("devtools/shared/client/environment-client");
 
 Services.prefs.setBoolPref("security.allow_eval_with_system_principal", true);
 
 registerCleanupFunction(() => {
   Services.prefs.clearUserPref("security.allow_eval_with_system_principal");
 });
 
 // Test that the EnvironmentClient's getBindings() method works as expected.
@@ -36,25 +37,25 @@ function test_banana_environment() {
     Assert.equal(environment.type, "function");
 
     const parent = environment.parent;
     Assert.equal(parent.type, "block");
 
     const grandpa = parent.parent;
     Assert.equal(grandpa.type, "function");
 
-    const envClient = gThreadClient.environment(environment);
+    const envClient = new EnvironmentClient(gThreadClient, environment);
     envClient.getBindings(response => {
       Assert.equal(response.bindings.arguments[0].z.value, "z");
 
-      const parentClient = gThreadClient.environment(parent);
+      const parentClient = new EnvironmentClient(gThreadClient, parent);
       parentClient.getBindings(response => {
         Assert.equal(response.bindings.variables.banana3.value.class, "Function");
 
-        const grandpaClient = gThreadClient.environment(grandpa);
+        const grandpaClient = new EnvironmentClient(gThreadClient, grandpa);
         grandpaClient.getBindings(response => {
           Assert.equal(response.bindings.arguments[0].y.value, "y");
           gThreadClient.resume(() => finishClient(gClient));
         });
       });
     });
   });
 
--- a/devtools/shared/client/thread-client.js
+++ b/devtools/shared/client/thread-client.js
@@ -8,17 +8,16 @@
 const promise = require("devtools/shared/deprecated-sync-thenables");
 
 const DevToolsUtils = require("devtools/shared/DevToolsUtils");
 const {arg, DebuggerClient} = require("devtools/shared/client/debugger-client");
 const eventSource = require("devtools/shared/client/event-source");
 const {ThreadStateTypes} = require("devtools/shared/client/constants");
 
 loader.lazyRequireGetter(this, "ArrayBufferClient", "devtools/shared/client/array-buffer-client");
-loader.lazyRequireGetter(this, "EnvironmentClient", "devtools/shared/client/environment-client");
 loader.lazyRequireGetter(this, "LongStringClient", "devtools/shared/client/long-string-client");
 loader.lazyRequireGetter(this, "ObjectClient", "devtools/shared/client/object-client");
 loader.lazyRequireGetter(this, "SourceClient", "devtools/shared/client/source-client");
 
 const noop = () => {};
 
 /**
  * Creates a thread client for the remote debugging protocol server. This client
@@ -690,23 +689,16 @@ ThreadClient.prototype = {
    */
   removeXHRBreakpoint: DebuggerClient.requester({
     type: "removeXHRBreakpoint",
     path: arg(0),
     method: arg(1),
   }),
 
   /**
-   * Return an EnvironmentClient instance for the given environment actor form.
-   */
-  environment: function(form) {
-    return new EnvironmentClient(this.client, form);
-  },
-
-  /**
    * Return an instance of SourceClient for the given source actor form.
    */
   source: function(form) {
     if (form.actor in this._threadGrips) {
       return this._threadGrips[form.actor];
     }
 
     this._threadGrips[form.actor] = new SourceClient(this, form);
--- a/dom/base/Document.cpp
+++ b/dom/base/Document.cpp
@@ -9362,16 +9362,17 @@ class UnblockParsingPromiseHandler final
       : mPromise(aPromise) {
     nsCOMPtr<nsIParser> parser = aDocument->CreatorParserOrNull();
     if (parser &&
         (aOptions.mBlockScriptCreated || !parser->IsScriptCreated())) {
       parser->BlockParser();
       mParser = do_GetWeakReference(parser);
       mDocument = aDocument;
       mDocument->BlockOnload();
+      mDocument->BlockDOMContentLoaded();
     }
   }
 
   void ResolvedCallback(JSContext* aCx, JS::Handle<JS::Value> aValue) override {
     MaybeUnblockParser();
 
     mPromise->MaybeResolve(aCx, aValue);
   }
@@ -9395,18 +9396,26 @@ class UnblockParsingPromiseHandler final
   void MaybeUnblockParser() {
     nsCOMPtr<nsIParser> parser = do_QueryReferent(mParser);
     if (parser) {
       MOZ_DIAGNOSTIC_ASSERT(mDocument);
       nsCOMPtr<nsIParser> docParser = mDocument->CreatorParserOrNull();
       if (parser == docParser) {
         parser->UnblockParser();
         parser->ContinueInterruptedParsingAsync();
-        mDocument->UnblockOnload(false);
-      }
+      }
+    }
+    if (mDocument) {
+      // We blocked DOMContentLoaded and load events on this document.  Unblock
+      // them.  Note that we want to do that no matter what's going on with the
+      // parser state for this document.  Maybe someone caused it to stop being
+      // parsed, so CreatorParserOrNull() is returning null, but we still want
+      // to unblock these.
+      mDocument->UnblockDOMContentLoaded();
+      mDocument->UnblockOnload(false);
     }
     mParser = nullptr;
     mDocument = nullptr;
   }
 
   nsWeakPtr mParser;
   RefPtr<Promise> mPromise;
   RefPtr<Document> mDocument;
--- a/dom/base/nsContentSink.cpp
+++ b/dom/base/nsContentSink.cpp
@@ -24,16 +24,17 @@
 #include "nsIProtocolHandler.h"
 #include "nsIHttpChannel.h"
 #include "nsIContent.h"
 #include "nsIPresShell.h"
 #include "nsPresContext.h"
 #include "nsViewManager.h"
 #include "nsAtom.h"
 #include "nsGkAtoms.h"
+#include "nsGlobalWindowInner.h"
 #include "nsNetCID.h"
 #include "nsIOfflineCacheUpdate.h"
 #include "nsIApplicationCache.h"
 #include "nsIApplicationCacheContainer.h"
 #include "nsIApplicationCacheChannel.h"
 #include "nsIScriptSecurityManager.h"
 #include "nsICookieService.h"
 #include "nsContentUtils.h"
@@ -1548,20 +1549,30 @@ void nsContentSink::WillBuildModelImpl()
 }
 
 /* static */
 void nsContentSink::NotifyDocElementCreated(Document* aDoc) {
   MOZ_ASSERT(nsContentUtils::IsSafeToRunScript());
 
   nsCOMPtr<nsIObserverService> observerService =
       mozilla::services::GetObserverService();
-  if (observerService) {
-    observerService->NotifyObservers(
-        ToSupports(aDoc), "document-element-inserted", EmptyString().get());
+  MOZ_ASSERT(observerService);
+
+  auto* win = nsGlobalWindowInner::Cast(aDoc->GetInnerWindow());
+  bool fireInitialInsertion = !win || !win->DidFireDocElemInserted();
+  if (win) {
+    win->SetDidFireDocElemInserted();
   }
+  if (fireInitialInsertion) {
+    observerService->NotifyObservers(ToSupports(aDoc),
+                                     "initial-document-element-inserted",
+                                     EmptyString().get());
+  }
+  observerService->NotifyObservers(
+      ToSupports(aDoc), "document-element-inserted", EmptyString().get());
 
   nsContentUtils::DispatchChromeEvent(
       aDoc, ToSupports(aDoc), NS_LITERAL_STRING("DOMDocElementInserted"),
       CanBubble::eYes, Cancelable::eNo);
 }
 
 NS_IMETHODIMP
 nsContentSink::GetName(nsACString& aName) {
--- a/dom/base/nsFrameLoader.cpp
+++ b/dom/base/nsFrameLoader.cpp
@@ -158,23 +158,22 @@ NS_IMPL_CYCLE_COLLECTING_RELEASE(nsFrame
 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(nsFrameLoader)
   NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
   NS_INTERFACE_MAP_ENTRY_CONCRETE(nsFrameLoader)
   NS_INTERFACE_MAP_ENTRY(nsIMutationObserver)
   NS_INTERFACE_MAP_ENTRY(nsISupports)
 NS_INTERFACE_MAP_END
 
 nsFrameLoader::nsFrameLoader(Element* aOwner, nsPIDOMWindowOuter* aOpener,
-                             bool aNetworkCreated, int32_t aJSPluginID)
+                             bool aNetworkCreated)
     : mOwnerContent(aOwner),
       mDetachedSubdocFrame(nullptr),
       mOpener(aOpener),
       mRemoteBrowser(nullptr),
       mChildID(0),
-      mJSPluginID(aJSPluginID),
       mDepthTooGreat(false),
       mIsTopLevelContent(false),
       mDestroyCalled(false),
       mNeedsAsyncDestroy(false),
       mInSwap(false),
       mInShow(false),
       mHideCalled(false),
       mNetworkCreated(aNetworkCreated),
@@ -192,18 +191,17 @@ nsFrameLoader::~nsFrameLoader() {
   if (mMessageManager) {
     mMessageManager->Disconnect();
   }
   MOZ_RELEASE_ASSERT(mDestroyCalled);
 }
 
 nsFrameLoader* nsFrameLoader::Create(Element* aOwner,
                                      nsPIDOMWindowOuter* aOpener,
-                                     bool aNetworkCreated,
-                                     int32_t aJSPluginId) {
+                                     bool aNetworkCreated) {
   NS_ENSURE_TRUE(aOwner, nullptr);
   Document* doc = aOwner->OwnerDoc();
 
   // We never create nsFrameLoaders for elements in resource documents.
   //
   // We never create nsFrameLoaders for elements in data documents, unless the
   // document is a static document.
   // Static documents are an exception because any sub-documents need an
@@ -222,17 +220,17 @@ nsFrameLoader* nsFrameLoader::Create(Ele
   // since for a static document we know aOwner will end up in a document and
   // the nsFrameLoader will be used for its docShell.)
   //
   NS_ENSURE_TRUE(!doc->IsResourceDoc() &&
                      ((!doc->IsLoadedAsData() && aOwner->IsInComposedDoc()) ||
                       doc->IsStaticDocument()),
                  nullptr);
 
-  return new nsFrameLoader(aOwner, aOpener, aNetworkCreated, aJSPluginId);
+  return new nsFrameLoader(aOwner, aOpener, aNetworkCreated);
 }
 
 void nsFrameLoader::LoadFrame(bool aOriginalSrc) {
   if (NS_WARN_IF(!mOwnerContent)) {
     return;
   }
 
   nsAutoString src;
@@ -313,24 +311,18 @@ nsresult nsFrameLoader::LoadURI(nsIURI* 
       aTriggeringPrincipal,
       "Must have an explicit triggeringPrincipal to nsFrameLoader::LoadURI.");
 
   mLoadingOriginalSrc = aOriginalSrc;
 
   nsCOMPtr<Document> doc = mOwnerContent->OwnerDoc();
 
   nsresult rv;
-  // If IsForJSPlugin() returns true then we want to allow the load. We're just
-  // loading the source for the implementation of the JS plugin from a URI
-  // that's under our control. We will already have done the security checks for
-  // loading the plugin content itself in the object/embed loading code.
-  if (!IsForJSPlugin()) {
-    rv = CheckURILoad(aURI, aTriggeringPrincipal);
-    NS_ENSURE_SUCCESS(rv, rv);
-  }
+  rv = CheckURILoad(aURI, aTriggeringPrincipal);
+  NS_ENSURE_SUCCESS(rv, rv);
 
   mURIToLoad = aURI;
   mTriggeringPrincipal = aTriggeringPrincipal;
   rv = doc->InitializeFrameLoader(this);
   if (NS_FAILED(rv)) {
     mURIToLoad = nullptr;
     mTriggeringPrincipal = nullptr;
   }
@@ -1779,20 +1771,16 @@ bool nsFrameLoader::OwnerIsIsolatedMozBr
   if (isolated) {
     return true;
   }
 
   return false;
 }
 
 bool nsFrameLoader::ShouldUseRemoteProcess() {
-  if (IsForJSPlugin()) {
-    return true;
-  }
-
   if (PR_GetEnv("MOZ_DISABLE_OOP_TABS") ||
       Preferences::GetBool("dom.ipc.tabs.disabled", false)) {
     return false;
   }
 
   // Don't try to launch nested children if we don't have OMTC.
   // They won't render!
   if (XRE_IsContentProcess() &&
@@ -2452,18 +2440,17 @@ bool nsFrameLoader::TryRemoteBrowser() {
     openerContentParent = openingTab->Manager()->AsContentParent();
   }
 
   // <iframe mozbrowser> gets to skip these checks.
   // iframes for JS plugins also get to skip these checks. We control the URL
   // that gets loaded, but the load is triggered from the document containing
   // the plugin.
   // out of process iframes also get to skip this check.
-  if (!OwnerIsMozBrowserFrame() && !IsForJSPlugin() &&
-      !XRE_IsContentProcess()) {
+  if (!OwnerIsMozBrowserFrame() && !XRE_IsContentProcess()) {
     if (parentDocShell->ItemType() != nsIDocShellTreeItem::typeChrome) {
       // Allow about:addon an exception to this rule so it can load remote
       // extension options pages.
       //
       // Note that the new frame's message manager will not be a child of the
       // chrome window message manager, and, the values of window.top and
       // window.parent will be different than they would be for a non-remote
       // frame.
@@ -3168,22 +3155,16 @@ void nsFrameLoader::MaybeUpdatePrimaryTa
           kNameSpaceID_None, nsGkAtoms::primary, nsGkAtoms::_true, eIgnoreCase);
       parentTreeOwner->TabParentAdded(mRemoteBrowser, isPrimary);
     }
   }
 }
 
 nsresult nsFrameLoader::GetNewTabContext(MutableTabContext* aTabContext,
                                          nsIURI* aURI) {
-  if (IsForJSPlugin()) {
-    return aTabContext->SetTabContextForJSPluginFrame(mJSPluginID)
-               ? NS_OK
-               : NS_ERROR_FAILURE;
-  }
-
   OriginAttributes attrs;
   attrs.mInIsolatedMozBrowser = OwnerIsIsolatedMozBrowserFrame();
   nsresult rv;
 
   attrs.mAppId = nsIScriptSecurityManager::NO_APP_ID;
 
   // set the userContextId on the attrs before we pass them into
   // the tab context
--- a/dom/base/nsFrameLoader.h
+++ b/dom/base/nsFrameLoader.h
@@ -92,18 +92,17 @@ class nsFrameLoader final : public nsStu
   typedef mozilla::dom::PBrowserParent PBrowserParent;
   typedef mozilla::dom::Document Document;
   typedef mozilla::dom::TabParent TabParent;
   typedef mozilla::layout::RenderFrame RenderFrame;
 
  public:
   static nsFrameLoader* Create(
       mozilla::dom::Element* aOwner, nsPIDOMWindowOuter* aOpener,
-      bool aNetworkCreated,
-      int32_t aJSPluginID = nsFakePluginTag::NOT_JSPLUGIN);
+      bool aNetworkCreated);
 
   NS_DECLARE_STATIC_IID_ACCESSOR(NS_FRAMELOADER_IID)
 
   NS_DECL_CYCLE_COLLECTING_ISUPPORTS
   NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(nsFrameLoader)
 
   NS_DECL_NSIMUTATIONOBSERVER_ATTRIBUTECHANGED
   nsresult CheckForRecursiveLoad(nsIURI* aURI);
@@ -351,25 +350,23 @@ class nsFrameLoader final : public nsStu
   RefPtr<mozilla::dom::ChromeMessageSender> mMessageManager;
   RefPtr<mozilla::dom::InProcessTabChildMessageManager> mChildMessageManager;
 
   virtual JSObject* WrapObject(JSContext* cx,
                                JS::Handle<JSObject*> aGivenProto) override;
 
  private:
   nsFrameLoader(mozilla::dom::Element* aOwner, nsPIDOMWindowOuter* aOpener,
-                bool aNetworkCreated, int32_t aJSPluginID);
+                bool aNetworkCreated);
   ~nsFrameLoader();
 
   void SetOwnerContent(mozilla::dom::Element* aContent);
 
   bool ShouldUseRemoteProcess();
 
-  bool IsForJSPlugin() { return mJSPluginID != nsFakePluginTag::NOT_JSPLUGIN; }
-
   /**
    * Is this a frame loader for an isolated <iframe mozbrowser>?
    *
    * By default, mozbrowser frames are isolated.  Isolation can be disabled by
    * setting the frame's noisolation attribute.  Disabling isolation is
    * only allowed if the containing document is chrome.
    */
   bool OwnerIsIsolatedMozBrowserFrame();
@@ -454,18 +451,16 @@ class nsFrameLoader final : public nsStu
   nsCOMPtr<nsPIDOMWindowOuter> mOpener;
 
   TabParent* mRemoteBrowser;
   uint64_t mChildID;
 
   // This is used when this refers to a remote sub frame
   RefPtr<mozilla::dom::RemoteFrameChild> mRemoteFrameChild;
 
-  int32_t mJSPluginID;
-
   // Holds the last known size of the frame.
   mozilla::ScreenIntSize mLazySize;
 
   RefPtr<mozilla::dom::ParentSHistory> mParentSHistory;
 
   bool mDepthTooGreat : 1;
   bool mIsTopLevelContent : 1;
   bool mDestroyCalled : 1;
--- a/dom/base/nsGlobalWindowInner.cpp
+++ b/dom/base/nsGlobalWindowInner.cpp
@@ -851,16 +851,17 @@ nsGlobalWindowInner::nsGlobalWindowInner
       mNotifyIdleObserversIdleOnThaw(false),
       mNotifyIdleObserversActiveOnThaw(false),
       mIsChrome(false),
       mCleanMessageManager(false),
       mNeedsFocus(true),
       mHasFocus(false),
       mShowFocusRingForContent(false),
       mFocusByKeyOccurred(false),
+      mDidFireDocElemInserted(false),
       mHasGamepad(false),
       mHasVREvents(false),
       mHasVRDisplayActivateEvents(false),
       mHasSeenGamepadInput(false),
       mSuspendDepth(0),
       mFreezeDepth(0),
 #ifdef DEBUG
       mSerial(0),
--- a/dom/base/nsGlobalWindowInner.h
+++ b/dom/base/nsGlobalWindowInner.h
@@ -858,16 +858,19 @@ class nsGlobalWindowInner final : public
   void SetFullScreen(bool aFullscreen, mozilla::ErrorResult& aError);
   bool Find(const nsAString& aString, bool aCaseSensitive, bool aBackwards,
             bool aWrapAround, bool aWholeWord, bool aSearchInFrames,
             bool aShowDialog, mozilla::ErrorResult& aError);
   uint64_t GetMozPaintCount(mozilla::ErrorResult& aError);
 
   bool ShouldResistFingerprinting();
 
+  bool DidFireDocElemInserted() const { return mDidFireDocElemInserted; }
+  void SetDidFireDocElemInserted() { mDidFireDocElemInserted = true; }
+
   mozilla::dom::Nullable<mozilla::dom::WindowProxyHolder> OpenDialog(
       JSContext* aCx, const nsAString& aUrl, const nsAString& aName,
       const nsAString& aOptions,
       const mozilla::dom::Sequence<JS::Value>& aExtraArgument,
       mozilla::ErrorResult& aError);
   void UpdateCommands(const nsAString& anAction, mozilla::dom::Selection* aSel,
                       int16_t aReason);
 
@@ -1312,16 +1315,20 @@ class nsGlobalWindowInner final : public
   // when true, show focus rings for the current focused content only.
   // This will be reset when another element is focused
   bool mShowFocusRingForContent : 1;
 
   // true if tab navigation has occurred for this window. Focus rings
   // should be displayed.
   bool mFocusByKeyOccurred : 1;
 
+  // True if we have notified document-element-inserted observers for this
+  // document.
+  bool mDidFireDocElemInserted : 1;
+
   // Indicates whether this window wants gamepad input events
   bool mHasGamepad : 1;
 
   // Indicates whether this window wants VR events
   bool mHasVREvents : 1;
 
   // Indicates whether this window wants VRDisplayActivate events
   bool mHasVRDisplayActivateEvents : 1;
--- a/dom/base/nsGlobalWindowOuter.cpp
+++ b/dom/base/nsGlobalWindowOuter.cpp
@@ -1781,18 +1781,55 @@ static bool InitializeLegacyNetscapeObje
 
   /* Define PrivilegeManager object with the necessary "static" methods. */
   obj = JS_DefineObject(aCx, obj, "PrivilegeManager", nullptr);
   NS_ENSURE_TRUE(obj, false);
 
   return JS_DefineFunctions(aCx, obj, EnablePrivilegeSpec);
 }
 
+struct MOZ_STACK_CLASS CompartmentFinderState {
+  explicit CompartmentFinderState(nsIPrincipal* aPrincipal)
+      : principal(aPrincipal), compartment(nullptr) {}
+
+  // Input: we look for a compartment which is same-origin with the
+  // given principal.
+  nsIPrincipal* principal;
+
+  // Output: We set this member if we find a compartment.
+  JS::Compartment* compartment;
+};
+
+static JS::CompartmentIterResult FindSameOriginCompartment(
+    JSContext* aCx, void* aData, JS::Compartment* aCompartment) {
+  auto* data = static_cast<CompartmentFinderState*>(aData);
+  MOZ_ASSERT(!data->compartment, "Why are we getting called?");
+
+  // If this compartment is not safe to share across globals, don't do
+  // anything with it; in particular we should not be getting a
+  // CompartmentPrivate from such a compartment, because it may be in
+  // the middle of being collected and its CompartmentPrivate may no
+  // longer be valid.
+  if (!js::IsSharableCompartment(aCompartment)) {
+    return JS::CompartmentIterResult::KeepGoing;
+  }
+
+  auto* compartmentPrivate = xpc::CompartmentPrivate::Get(aCompartment);
+  if (!compartmentPrivate->CanShareCompartmentWith(data->principal)) {
+    // Can't reuse this one, keep going.
+    return JS::CompartmentIterResult::KeepGoing;
+  }
+
+  // We have a winner!
+  data->compartment = aCompartment;
+  return JS::CompartmentIterResult::Stop;
+}
+
 static JS::RealmCreationOptions& SelectZone(
-    nsIPrincipal* aPrincipal, nsGlobalWindowInner* aNewInner,
+    JSContext* aCx, nsIPrincipal* aPrincipal, nsGlobalWindowInner* aNewInner,
     JS::RealmCreationOptions& aOptions) {
   // Use the shared system compartment for chrome windows.
   if (nsContentUtils::IsSystemPrincipal(aPrincipal)) {
     return aOptions.setExistingCompartment(xpc::PrivilegedJunkScope());
   }
 
   if (aNewInner->GetOuterWindow()) {
     nsGlobalWindowOuter* top = aNewInner->GetTopInternal();
@@ -1800,16 +1837,24 @@ static JS::RealmCreationOptions& SelectZ
       // We're a toplevel load.  Use a new zone.  This way, when we do
       // zone-based compartment sharing we won't share compartments
       // across navigations.
       return aOptions.setNewCompartmentAndZone();
     }
 
     // If we have a top-level window, use its zone.
     if (top && top->GetGlobalJSObject()) {
+      JS::Zone* zone = JS::GetObjectZone(top->GetGlobalJSObject());
+      // Now try to find an existing compartment that's same-origin
+      // with our principal.
+      CompartmentFinderState data(aPrincipal);
+      JS_IterateCompartmentsInZone(aCx, zone, &data, FindSameOriginCompartment);
+      if (data.compartment) {
+        return aOptions.setExistingCompartment(data.compartment);
+      }
       return aOptions.setNewCompartmentInExistingZone(top->GetGlobalJSObject());
     }
   }
 
   return aOptions.setNewCompartmentAndZone();
 }
 
 /**
@@ -1829,17 +1874,17 @@ static nsresult CreateNativeGlobalForInn
 
   // DOMWindow with nsEP is not supported, we have to make sure
   // no one creates one accidentally.
   nsCOMPtr<nsIExpandedPrincipal> nsEP = do_QueryInterface(aPrincipal);
   MOZ_RELEASE_ASSERT(!nsEP, "DOMWindow with nsEP is not supported");
 
   JS::RealmOptions options;
 
-  SelectZone(aPrincipal, aNewInner, options.creationOptions());
+  SelectZone(aCx, aPrincipal, aNewInner, options.creationOptions());
 
   options.creationOptions().setSecureContext(aIsSecureContext);
 
   xpc::InitGlobalObjectOptions(options, aPrincipal);
 
   // Determine if we need the Components object.
   bool needComponents = nsContentUtils::IsSystemPrincipal(aPrincipal) ||
                         TreatAsRemoteXUL(aPrincipal);
--- a/dom/base/nsObjectLoadingContent.cpp
+++ b/dom/base/nsObjectLoadingContent.cpp
@@ -520,18 +520,17 @@ bool nsObjectLoadingContent::MakePluginL
 
 // Helper to spawn the frameloader.
 void nsObjectLoadingContent::SetupFrameLoader(int32_t aJSPluginId) {
   nsCOMPtr<nsIContent> thisContent =
       do_QueryInterface(static_cast<nsIImageLoadingContent*>(this));
   NS_ASSERTION(thisContent, "must be a content");
 
   mFrameLoader = nsFrameLoader::Create(thisContent->AsElement(),
-                                       /* aOpener = */ nullptr, mNetworkCreated,
-                                       aJSPluginId);
+                                       /* aOpener = */ nullptr, mNetworkCreated);
   MOZ_ASSERT(mFrameLoader, "nsFrameLoader::Create failed");
 }
 
 // Helper to spawn the frameloader and return a pointer to its docshell.
 already_AddRefed<nsIDocShell> nsObjectLoadingContent::SetupDocShell(
     nsIURI* aRecursionCheckURI) {
   SetupFrameLoader(nsFakePluginTag::NOT_JSPLUGIN);
   if (!mFrameLoader) {
@@ -2103,110 +2102,16 @@ nsresult nsObjectLoadingContent::LoadObj
         }
 
         // We'll handle this below
         doSpawnPlugin = true;
       } else {
         rv = AsyncStartPluginInstance();
       }
     } break;
-    case eType_FakePlugin: {
-      if (mChannel) {
-        /// XXX(johns): Ideally we'd have some way to pass the channel to the
-        ///             fake plugin handler, but for now handlers will need to
-        ///             request element.srcURI themselves if they want it
-        LOG(("OBJLC [%p]: Closing unused channel for fake plugin type", this));
-        CloseChannel();
-      }
-
-      /// XXX(johns) Bug FIXME - We need to cleanup the various plugintag
-      ///            classes to be more sane and avoid this dance
-      nsCOMPtr<nsIPluginTag> basetag =
-          nsContentUtils::PluginTagForType(mContentType, false);
-      nsCOMPtr<nsIFakePluginTag> tag = do_QueryInterface(basetag);
-
-      uint32_t id;
-      if (NS_FAILED(tag->GetId(&id))) {
-        rv = NS_ERROR_FAILURE;
-        break;
-      }
-
-      MOZ_ASSERT(id <= PR_INT32_MAX,
-                 "Something went wrong, nsPluginHost::RegisterFakePlugin "
-                 "shouldn't have "
-                 "given out this id.");
-
-      SetupFrameLoader(int32_t(id));
-      if (!mFrameLoader) {
-        rv = NS_ERROR_FAILURE;
-        break;
-      }
-
-      nsString sandboxScript;
-      tag->GetSandboxScript(sandboxScript);
-      if (!sandboxScript.IsEmpty()) {
-        // Create a sandbox.
-        AutoJSAPI jsapi;
-        jsapi.Init();
-        JS::Rooted<JSObject*> sandbox(jsapi.cx());
-        rv = nsContentUtils::XPConnect()->CreateSandbox(
-            jsapi.cx(), nsContentUtils::GetSystemPrincipal(),
-            sandbox.address());
-        if (NS_FAILED(rv)) {
-          break;
-        }
-
-        AutoEntryScript aes(sandbox, "JS plugin sandbox code");
-
-        JS::Rooted<JS::Value> element(aes.cx());
-        if (!ToJSValue(aes.cx(), thisContent, &element)) {
-          rv = NS_ERROR_FAILURE;
-          break;
-        }
-
-        if (!JS_DefineProperty(aes.cx(), sandbox, "pluginElement", element,
-                               JSPROP_ENUMERATE)) {
-          rv = NS_ERROR_FAILURE;
-          break;
-        }
-
-        JS::Rooted<JS::Value> rval(aes.cx());
-        // If the eval'ed code throws we won't load and do fallback instead.
-        rv = nsContentUtils::XPConnect()->EvalInSandboxObject(
-            sandboxScript, nullptr, aes.cx(), sandbox, &rval);
-        if (NS_FAILED(rv)) {
-          break;
-        }
-      }
-
-      nsCOMPtr<nsIURI> handlerURI;
-      if (tag) {
-        tag->GetHandlerURI(getter_AddRefs(handlerURI));
-      }
-
-      if (!handlerURI) {
-        MOZ_ASSERT_UNREACHABLE(
-            "Selected type is not a proper fake plugin "
-            "handler");
-        rv = NS_ERROR_FAILURE;
-        break;
-      }
-
-      nsCString spec;
-      handlerURI->GetSpec(spec);
-      LOG(("OBJLC [%p]: Loading fake plugin handler (%s)", this, spec.get()));
-
-      rv = mFrameLoader->LoadURI(
-          handlerURI, thisContent->AsElement()->NodePrincipal(), false);
-      if (NS_FAILED(rv)) {
-        LOG(("OBJLC [%p]: LoadURI() failed for fake handler", this));
-        mFrameLoader->Destroy();
-        mFrameLoader = nullptr;
-      }
-    } break;
     case eType_Document: {
       if (!mChannel) {
         // We could mFrameLoader->LoadURI(mURI), but UpdateObjectParameters
         // requires documents have a channel, so this is not a valid state.
         MOZ_ASSERT_UNREACHABLE(
             "Attempting to load a document without a "
             "channel");
         rv = NS_ERROR_FAILURE;
@@ -2247,16 +2152,19 @@ nsresult nsObjectLoadingContent::LoadObj
       if (NS_FAILED(rv)) {
         LOG(("OBJLC [%p]: OpenChannel returned failure (%" PRIu32 ")", this,
              static_cast<uint32_t>(rv)));
       }
       break;
     case eType_Null:
       // Handled below, silence compiler warnings
       break;
+    case eType_FakePlugin:
+      // We're now in the process of removing FakePlugin. See bug 1529133.
+      MOZ_CRASH("Shouldn't reach here! This means there's a fakeplugin trying to be loaded.");
   }
 
   //
   // Loaded, handle notifications and fallback
   //
   if (NS_FAILED(rv)) {
     // If we failed in the loading hunk above, switch to fallback
     LOG(("OBJLC [%p]: Loading failed, switching to fallback", this));
--- a/dom/base/nsWindowRoot.cpp
+++ b/dom/base/nsWindowRoot.cpp
@@ -20,16 +20,17 @@
 #include "nsIContent.h"
 #include "nsIControllers.h"
 #include "nsIController.h"
 #include "xpcpublic.h"
 #include "nsCycleCollectionParticipant.h"
 #include "mozilla/dom/TabParent.h"
 #include "mozilla/dom/HTMLTextAreaElement.h"
 #include "mozilla/dom/HTMLInputElement.h"
+#include "mozilla/dom/JSWindowActorService.h"
 
 #ifdef MOZ_XUL
 #  include "nsXULElement.h"
 #endif
 
 using namespace mozilla;
 using namespace mozilla::dom;
 
@@ -45,16 +46,18 @@ nsWindowRoot::nsWindowRoot(nsPIDOMWindow
   mShowFocusRings = true;
 #endif
 }
 
 nsWindowRoot::~nsWindowRoot() {
   if (mListenerManager) {
     mListenerManager->Disconnect();
   }
+
+  JSWindowActorService::UnregisterWindowRoot(this);
 }
 
 NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(nsWindowRoot, mWindow, mListenerManager,
                                       mParent)
 
 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(nsWindowRoot)
   NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
   NS_INTERFACE_MAP_ENTRY(nsISupports)
@@ -324,10 +327,16 @@ void nsWindowRoot::EnumerateBrowsers(Bro
     aEnumFunc(tabParents[i], aArg);
   }
 }
 
 ///////////////////////////////////////////////////////////////////////////////////
 
 already_AddRefed<EventTarget> NS_NewWindowRoot(nsPIDOMWindowOuter* aWindow) {
   nsCOMPtr<EventTarget> result = new nsWindowRoot(aWindow);
+
+  RefPtr<JSWindowActorService> wasvc = JSWindowActorService::GetSingleton();
+  if (wasvc) {
+    wasvc->RegisterWindowRoot(result);
+  }
+
   return result.forget();
 }
--- a/dom/bindings/Bindings.conf
+++ b/dom/bindings/Bindings.conf
@@ -1114,16 +1114,21 @@ DOMInterfaces = {
     'headerFile': 'WebGLExtensions.h'
 },
 
 'EXT_texture_compression_rgtc': {
     'nativeType': 'mozilla::WebGLExtensionCompressedTextureRGTC',
     'headerFile': 'WebGLExtensions.h'
 },
 
+'OES_fbo_render_mipmap': {
+    'nativeType': 'mozilla::WebGLExtensionFBORenderMipmap',
+    'headerFile': 'WebGLExtensions.h'
+},
+
 'WEBGL_compressed_texture_astc': {
     'nativeType': 'mozilla::WebGLExtensionCompressedTextureASTC',
     'headerFile': 'WebGLExtensions.h'
 },
 
 'WEBGL_compressed_texture_etc': {
     'nativeType': 'mozilla::WebGLExtensionCompressedTextureES3',
     'headerFile': 'WebGLExtensions.h'
--- a/dom/bindings/Codegen.py
+++ b/dom/bindings/Codegen.py
@@ -13694,22 +13694,24 @@ class CGDictionary(CGThing):
         except CycleCollectionUnsupported:
             # We have some member that we don't know how to CC.  Don't output
             # our cycle collection overloads, so attempts to CC us will fail to
             # compile instead of misbehaving.
             pass
 
         if CGDictionary.isDictionaryCopyConstructible(d):
             disallowCopyConstruction = False
-            # Note: no base constructors because our operator= will
-            # deal with that.
+            # Note: gcc's -Wextra has a warning against not initializng our
+            # base explicitly. If we have one. Use our non-initializing base
+            # constructor to get around that.
             ctors.append(ClassConstructor([Argument("const %s&" % selfName,
                                                     "aOther")],
                                           bodyInHeader=True,
                                           visibility="public",
+                                          baseConstructors=baseConstructors,
                                           explicit=True,
                                           body="*this = aOther;\n"))
             methods.append(self.assignmentOperator())
         else:
             disallowCopyConstruction = True
 
         if self.canHaveEqualsOperator():
             methods.append(self.equalsOperator())
--- a/dom/cache/CacheTypes.ipdlh
+++ b/dom/cache/CacheTypes.ipdlh
@@ -33,17 +33,17 @@ struct CacheQueryParams
   bool cacheNameSet;
   nsString cacheName;
 };
 
 struct CacheReadStream
 {
   nsID id;
   nullable PCacheStreamControl control;
-  OptionalIPCStream stream;
+  IPCStream? stream;
 };
 
 union CacheReadStreamOrVoid
 {
   void_t;
   CacheReadStream;
 };
 
--- a/dom/cache/ReadStream.cpp
+++ b/dom/cache/ReadStream.cpp
@@ -18,17 +18,16 @@
 
 namespace mozilla {
 namespace dom {
 namespace cache {
 
 using mozilla::Unused;
 using mozilla::ipc::AutoIPCStream;
 using mozilla::ipc::IPCStream;
-using mozilla::ipc::OptionalIPCStream;
 
 // ----------------------------------------------------------------------------
 
 // The inner stream class.  This is where all of the real work is done.  As
 // an invariant Inner::Close() must be called before ~Inner().  This is
 // guaranteed by our outer ReadStream class.
 class ReadStream::Inner final : public ReadStream::Controllable {
  public:
@@ -219,20 +218,20 @@ void ReadStream::Inner::Serialize(
   mControl->SerializeControl(aReadStreamOut);
 
   {
     MutexAutoLock lock(mMutex);
     mControl->SerializeStream(aReadStreamOut, mStream, aStreamCleanupList);
   }
 
   MOZ_DIAGNOSTIC_ASSERT(
-      aReadStreamOut->stream().type() == OptionalIPCStream::Tvoid_t ||
-      (aReadStreamOut->stream().get_IPCStream().stream().type() !=
+      aReadStreamOut->stream().isNothing() ||
+      (aReadStreamOut->stream().ref().stream().type() !=
            mozilla::ipc::InputStreamParams::TIPCRemoteStreamParams &&
-       aReadStreamOut->stream().get_IPCStream().stream().type() !=
+       aReadStreamOut->stream().ref().stream().type() !=
            mozilla::ipc::InputStreamParams::T__None));
 
   // We're passing ownership across the IPC barrier with the control, so
   // do not signal that the stream is closed here.
   Forget();
 }
 
 void ReadStream::Inner::CloseStream() {
@@ -517,20 +516,20 @@ already_AddRefed<ReadStream> ReadStream:
   // The parameter may or may not be for a Cache created stream.  The way we
   // tell is by looking at the stream control actor.  If the actor exists,
   // then we know the Cache created it.
   if (!aReadStream.controlChild() && !aReadStream.controlParent()) {
     return nullptr;
   }
 
   MOZ_DIAGNOSTIC_ASSERT(
-      aReadStream.stream().type() == OptionalIPCStream::Tvoid_t ||
-      (aReadStream.stream().get_IPCStream().stream().type() !=
+      aReadStream.stream().isNothing() ||
+      (aReadStream.stream().ref().stream().type() !=
            mozilla::ipc::InputStreamParams::TIPCRemoteStreamParams &&
-       aReadStream.stream().get_IPCStream().stream().type() !=
+       aReadStream.stream().ref().stream().type() !=
            mozilla::ipc::InputStreamParams::T__None));
 
   // Control is guaranteed to survive this method as ActorDestroy() cannot
   // run on this thread until we complete.
   StreamControl* control;
   if (aReadStream.controlChild()) {
     auto actor =
         static_cast<CacheStreamControlChild*>(aReadStream.controlChild());
--- a/dom/canvas/WebGLContextExtensions.cpp
+++ b/dom/canvas/WebGLContextExtensions.cpp
@@ -39,16 +39,17 @@ namespace mozilla {
     WEBGL_EXTENSION_IDENTIFIER(EXT_frag_depth)
     WEBGL_EXTENSION_IDENTIFIER(EXT_shader_texture_lod)
     WEBGL_EXTENSION_IDENTIFIER(EXT_sRGB)
     WEBGL_EXTENSION_IDENTIFIER(EXT_texture_compression_bptc)
     WEBGL_EXTENSION_IDENTIFIER(EXT_texture_compression_rgtc)
     WEBGL_EXTENSION_IDENTIFIER(EXT_texture_filter_anisotropic)
     WEBGL_EXTENSION_IDENTIFIER(MOZ_debug)
     WEBGL_EXTENSION_IDENTIFIER(OES_element_index_uint)
+    WEBGL_EXTENSION_IDENTIFIER(OES_fbo_render_mipmap)
     WEBGL_EXTENSION_IDENTIFIER(OES_standard_derivatives)
     WEBGL_EXTENSION_IDENTIFIER(OES_texture_float)
     WEBGL_EXTENSION_IDENTIFIER(OES_texture_float_linear)
     WEBGL_EXTENSION_IDENTIFIER(OES_texture_half_float)
     WEBGL_EXTENSION_IDENTIFIER(OES_texture_half_float_linear)
     WEBGL_EXTENSION_IDENTIFIER(OES_vertex_array_object)
     WEBGL_EXTENSION_IDENTIFIER(WEBGL_color_buffer_float)
     WEBGL_EXTENSION_IDENTIFIER(WEBGL_compressed_texture_astc)
@@ -148,16 +149,19 @@ bool WebGLContext::IsExtensionSupported(
       return gl->IsExtensionSupported(
           gl::GLContext::EXT_texture_filter_anisotropic);
 
     // OES_
     case WebGLExtensionID::OES_element_index_uint:
       if (IsWebGL2()) return false;
       return gl->IsSupported(gl::GLFeature::element_index_uint);
 
+    case WebGLExtensionID::OES_fbo_render_mipmap:
+      return WebGLExtensionFBORenderMipmap::IsSupported(this);
+
     case WebGLExtensionID::OES_standard_derivatives:
       if (IsWebGL2()) return false;
       return gl->IsSupported(gl::GLFeature::standard_derivatives);
 
     case WebGLExtensionID::OES_texture_float:
       return WebGLExtensionTextureFloat::IsSupported(this);
 
     case WebGLExtensionID::OES_texture_float_linear:
@@ -340,16 +344,19 @@ void WebGLContext::EnableExtension(WebGL
     case WebGLExtensionID::MOZ_debug:
       obj = new WebGLExtensionMOZDebug(this);
       break;
 
     // OES_
     case WebGLExtensionID::OES_element_index_uint:
       obj = new WebGLExtensionElementIndexUint(this);
       break;
+    case WebGLExtensionID::OES_fbo_render_mipmap:
+      obj = new WebGLExtensionFBORenderMipmap(this);
+      break;
     case WebGLExtensionID::OES_standard_derivatives:
       obj = new WebGLExtensionStandardDerivatives(this);
       break;
     case WebGLExtensionID::OES_texture_float:
       obj = new WebGLExtensionTextureFloat(this);
       break;
     case WebGLExtensionID::OES_texture_float_linear:
       obj = new WebGLExtensionTextureFloatLinear(this);
--- a/dom/canvas/WebGLExtensions.cpp
+++ b/dom/canvas/WebGLExtensions.cpp
@@ -45,9 +45,30 @@ bool WebGLExtensionFloatBlend::IsSupport
 
   const auto& gl = webgl->gl;
   return !gl->IsGLES() || gl->IsANGLE() ||
          gl->IsExtensionSupported(gl::GLContext::EXT_float_blend);
 }
 
 IMPL_WEBGL_EXTENSION_GOOP(WebGLExtensionFloatBlend, EXT_float_blend)
 
+// -
+
+WebGLExtensionFBORenderMipmap::WebGLExtensionFBORenderMipmap(WebGLContext* const webgl)
+    : WebGLExtensionBase(webgl) {
+  MOZ_ASSERT(IsSupported(webgl), "Don't construct extension if unsupported.");
+}
+
+WebGLExtensionFBORenderMipmap::~WebGLExtensionFBORenderMipmap() = default;
+
+bool WebGLExtensionFBORenderMipmap::IsSupported(const WebGLContext* const webgl) {
+  if (webgl->IsWebGL2()) return false;
+  if (!gfxPrefs::WebGLDraftExtensionsEnabled()) return false;
+
+  const auto& gl = webgl->gl;
+  if (!gl->IsGLES()) return true;
+  if (gl->Version() >= 300) return true;
+  return gl->IsExtensionSupported(gl::GLContext::OES_fbo_render_mipmap);
+}
+
+IMPL_WEBGL_EXTENSION_GOOP(WebGLExtensionFBORenderMipmap, OES_fbo_render_mipmap)
+
 }  // namespace mozilla
--- a/dom/canvas/WebGLExtensions.h
+++ b/dom/canvas/WebGLExtensions.h
@@ -180,16 +180,26 @@ class WebGLExtensionEXTColorBufferFloat 
   explicit WebGLExtensionEXTColorBufferFloat(WebGLContext*);
   virtual ~WebGLExtensionEXTColorBufferFloat() {}
 
   static bool IsSupported(const WebGLContext*);
 
   DECL_WEBGL_EXTENSION_GOOP
 };
 
+class WebGLExtensionFBORenderMipmap : public WebGLExtensionBase {
+ public:
+  explicit WebGLExtensionFBORenderMipmap(WebGLContext* webgl);
+  virtual ~WebGLExtensionFBORenderMipmap();
+
+  static bool IsSupported(const WebGLContext*);
+
+  DECL_WEBGL_EXTENSION_GOOP
+};
+
 class WebGLExtensionFloatBlend : public WebGLExtensionBase {
  public:
   explicit WebGLExtensionFloatBlend(WebGLContext* webgl);
   virtual ~WebGLExtensionFloatBlend();
 
   static bool IsSupported(const WebGLContext*);
 
   DECL_WEBGL_EXTENSION_GOOP
--- a/dom/canvas/WebGLFramebuffer.cpp
+++ b/dom/canvas/WebGLFramebuffer.cpp
@@ -1185,17 +1185,18 @@ void WebGLFramebuffer::FramebufferTextur
       return;
     }
   }
 
   // `level`
   if (level < 0)
     return mContext->ErrorInvalidValue("`level` must not be negative.");
 
-  if (mContext->IsWebGL2()) {
+  if (mContext->IsWebGL2() ||
+      mContext->IsExtensionEnabled(WebGLExtensionID::OES_fbo_render_mipmap)) {
     /* GLES 3.0.4 p208:
      *   If textarget is one of TEXTURE_CUBE_MAP_POSITIVE_X,
      *   TEXTURE_CUBE_MAP_POSITIVE_Y, TEXTURE_CUBE_MAP_POSITIVE_Z,
      *   TEXTURE_CUBE_MAP_NEGATIVE_X, TEXTURE_CUBE_MAP_NEGATIVE_Y,
      *   or TEXTURE_CUBE_MAP_NEGATIVE_Z, then level must be greater
      *   than or equal to zero and less than or equal to log2 of the
      *   value of MAX_CUBE_MAP_TEXTURE_SIZE. If textarget is TEXTURE_2D,
      *   level must be greater than or equal to zero and no larger than
--- a/dom/canvas/WebGLTypes.h
+++ b/dom/canvas/WebGLTypes.h
@@ -106,16 +106,17 @@ enum class WebGLExtensionID : uint8_t {
   EXT_frag_depth,
   EXT_shader_texture_lod,
   EXT_sRGB,
   EXT_texture_compression_bptc,
   EXT_texture_compression_rgtc,
   EXT_texture_filter_anisotropic,
   MOZ_debug,
   OES_element_index_uint,
+  OES_fbo_render_mipmap,
   OES_standard_derivatives,
   OES_texture_float,
   OES_texture_float_linear,
   OES_texture_half_float,
   OES_texture_half_float_linear,
   OES_vertex_array_object,
   WEBGL_color_buffer_float,
   WEBGL_compressed_texture_astc,
new file mode 100644
--- /dev/null
+++ b/dom/canvas/test/webgl-mochitest/ensure-exts/test_OES_fbo_render_mipmap.html
@@ -0,0 +1,20 @@
+<!DOCTYPE HTML>
+<html>
+  <head>
+    <meta charset='utf-8'/>
+    <script src='/tests/SimpleTest/SimpleTest.js'></script>
+    <link rel='stylesheet' href='/tests/SimpleTest/test.css'>
+    <script src='ensure-ext.js'></script>
+  </head>
+  <body>
+    <script>
+
+'use strict';
+
+Lastly_WithDraftExtsEnabled(function() {
+  EnsureExtFor('webgl', 'OES_fbo_render_mipmap');
+});
+
+    </script>
+  </body>
+</html>
--- a/dom/canvas/test/webgl-mochitest/ensure-exts/test_common.html
+++ b/dom/canvas/test/webgl-mochitest/ensure-exts/test_common.html
@@ -48,17 +48,18 @@ var defaultExts = [
     ['WEBGL_compressed_texture_atc'      , [MACHINE_SPECIFIC, MACHINE_SPECIFIC]],
     ['WEBGL_compressed_texture_etc'      , [MACHINE_SPECIFIC, MACHINE_SPECIFIC]],
     ['WEBGL_compressed_texture_etc1'     , [MACHINE_SPECIFIC, MACHINE_SPECIFIC]],
     ['WEBGL_compressed_texture_pvrtc'    , [MACHINE_SPECIFIC, MACHINE_SPECIFIC]],
     ['WEBGL_compressed_texture_s3tc_srgb', [MACHINE_SPECIFIC, MACHINE_SPECIFIC]],
 ];
 
 var draftExts = [
-    ['EXT_float_blend', [MACHINE_SPECIFIC, MACHINE_SPECIFIC]],
+    ['EXT_float_blend'      , [MACHINE_SPECIFIC, MACHINE_SPECIFIC]],
+    ['OES_fbo_render_mipmap', [MACHINE_SPECIFIC, FORBID          ]],
 ];
 
 ////////////////////
 // Ensure that we never see any extensions that we haven't listed above!
 
 (function() {
     const expectedExts = (defaultExts.concat(draftExts)).map(x => x[0]);
 
--- a/dom/canvas/test/webgl-mochitest/mochitest.ini
+++ b/dom/canvas/test/webgl-mochitest/mochitest.ini
@@ -29,16 +29,18 @@ fail-if = (os == 'android')
 [ensure-exts/test_EXT_shader_texture_lod.html]
 fail-if = (os == 'android')
 [ensure-exts/test_EXT_texture_compression_bptc.html]
 fail-if = (os == 'android') || (os == 'linux') || (os == 'mac') || (os == 'win')
 [ensure-exts/test_EXT_texture_compression_rgtc.html]
 fail-if = (os == 'android') || (os == 'win')
 [ensure-exts/test_EXT_texture_filter_anisotropic.html]
 fail-if = (os == 'android') || (os == 'linux')
+[ensure-exts/test_OES_fbo_render_mipmap.html]
+fail-if = (os == 'android') || (os == 'win')
 [ensure-exts/test_OES_standard_derivatives.html]
 fail-if = (os == 'android')
 [ensure-exts/test_WEBGL_color_buffer_float.html]
 fail-if = (os == 'android')
 [ensure-exts/test_WEBGL_compressed_texture_astc.html]
 fail-if = (os == 'android') || (os == 'linux') || (os == 'mac') || (os == 'win')
 [ensure-exts/test_WEBGL_compressed_texture_etc.html]
 fail-if = (os == 'android') || (os == 'mac') || (os == 'win')
--- a/dom/chrome-webidl/ChromeUtils.webidl
+++ b/dom/chrome-webidl/ChromeUtils.webidl
@@ -532,24 +532,32 @@ dictionary HeapSnapshotBoundaries {
 dictionary Base64URLEncodeOptions {
   /** Specifies whether the output should be padded with "=" characters. */
   required boolean pad;
 };
 
 dictionary WindowActorOptions {
   /** This fields are used for configuring individual sides of the actor. */
   required WindowActorSidedOptions parent;
-  required WindowActorSidedOptions child;
+  required WindowActorChildOptions child;
 };
 
 dictionary WindowActorSidedOptions {
   /** The module path which should be loaded for the actor on this side. */
   required ByteString moduleURI;
 };
 
+dictionary WindowActorChildOptions : WindowActorSidedOptions {
+  /**
+   * Events which this actor wants to be listening to. When these events fire,
+   * it will trigger actor creation, and then forward the event to the actor.
+   */
+  record<DOMString, AddEventListenerOptions> events;
+};
+
 enum Base64URLDecodePadding {
   /**
    * Fails decoding if the input is unpadded. RFC 4648, section 3.2 requires
    * padding, unless the referring specification prohibits it.
    */
   "require",
 
   /** Tolerates padded and unpadded input. */
--- a/dom/file/ipc/IPCBlobInputStreamChild.cpp
+++ b/dom/file/ipc/IPCBlobInputStreamChild.cpp
@@ -279,17 +279,17 @@ void IPCBlobInputStreamChild::StreamNeed
     return;
   }
 
   RefPtr<StreamNeededRunnable> runnable = new StreamNeededRunnable(this);
   mOwningEventTarget->Dispatch(runnable.forget(), NS_DISPATCH_NORMAL);
 }
 
 mozilla::ipc::IPCResult IPCBlobInputStreamChild::RecvStreamReady(
-    const OptionalIPCStream& aStream) {
+    const Maybe<IPCStream>& aStream) {
   nsCOMPtr<nsIInputStream> stream = mozilla::ipc::DeserializeIPCStream(aStream);
 
   RefPtr<IPCBlobInputStream> pendingStream;
   nsCOMPtr<nsIEventTarget> eventTarget;
 
   {
     MutexAutoLock lock(mMutex);
 
--- a/dom/file/ipc/IPCBlobInputStreamChild.h
+++ b/dom/file/ipc/IPCBlobInputStreamChild.h
@@ -51,17 +51,17 @@ class IPCBlobInputStreamChild final
   void ForgetStream(IPCBlobInputStream* aStream);
 
   const nsID& ID() const { return mID; }
 
   uint64_t Size() const { return mSize; }
 
   void StreamNeeded(IPCBlobInputStream* aStream, nsIEventTarget* aEventTarget);
 
-  mozilla::ipc::IPCResult RecvStreamReady(const OptionalIPCStream& aStream);
+  mozilla::ipc::IPCResult RecvStreamReady(const Maybe<IPCStream>& aStream);
 
   void LengthNeeded(IPCBlobInputStream* aStream, nsIEventTarget* aEventTarget);
 
   mozilla::ipc::IPCResult RecvLengthReady(const int64_t& aLength);
 
   void Shutdown();
 
   void Migrated();
--- a/dom/file/ipc/IPCBlobInputStreamParent.cpp
+++ b/dom/file/ipc/IPCBlobInputStreamParent.cpp
@@ -103,17 +103,17 @@ void IPCBlobInputStreamParent::SetCallba
 
 mozilla::ipc::IPCResult IPCBlobInputStreamParent::RecvStreamNeeded() {
   MOZ_ASSERT(mContentManager || mPBackgroundManager);
 
   nsCOMPtr<nsIInputStream> stream;
   IPCBlobInputStreamStorage::Get()->GetStream(mID, 0, mSize,
                                               getter_AddRefs(stream));
   if (!stream) {
-    if (!SendStreamReady(void_t())) {
+    if (!SendStreamReady(Nothing())) {
       return IPC_FAIL(this, "SendStreamReady failed");
     }
 
     return IPC_OK();
   }
 
   mozilla::ipc::AutoIPCStream ipcStream;
   bool ok = false;
@@ -125,17 +125,17 @@ mozilla::ipc::IPCResult IPCBlobInputStre
     MOZ_ASSERT(mPBackgroundManager);
     ok = ipcStream.Serialize(stream, mPBackgroundManager);
   }
 
   if (NS_WARN_IF(!ok)) {
     return IPC_FAIL(this, "SendStreamReady failed");
   }
 
-  if (!SendStreamReady(ipcStream.TakeValue())) {
+  if (!SendStreamReady(Some(ipcStream.TakeValue()))) {
     return IPC_FAIL(this, "SendStreamReady failed");
   }
 
   return IPC_OK();
 }
 
 mozilla::ipc::IPCResult IPCBlobInputStreamParent::RecvLengthNeeded() {
   MOZ_ASSERT(mContentManager || mPBackgroundManager);
--- a/dom/file/ipc/PIPCBlobInputStream.ipdl
+++ b/dom/file/ipc/PIPCBlobInputStream.ipdl
@@ -23,17 +23,17 @@ parent:
 
   async LengthNeeded();
 
   // When this is called, the parent releases the inputStream and sends a
   // __delete__.
   async Close();
 
 child:
-  async StreamReady(OptionalIPCStream aStream);
+  async StreamReady(IPCStream? aStream);
 
   async LengthReady(int64_t aLength);
 
 both:
   // __delete__ can be called by parent and by child for 2 reasons:
   // - parent->child: This happens after a Close(). The child wants to inform
   //                  the parent that no other messages will be dispatched and
   //                  that the channel can be interrupted.
--- a/dom/html/HTMLFormElement.cpp
+++ b/dom/html/HTMLFormElement.cpp
@@ -87,19 +87,16 @@ static const uint8_t NS_FORM_AUTOCOMPLET
 static const nsAttrValue::EnumTable kFormAutocompleteTable[] = {
     {"on", NS_FORM_AUTOCOMPLETE_ON},
     {"off", NS_FORM_AUTOCOMPLETE_OFF},
     {nullptr, 0}};
 // Default autocomplete value is 'on'.
 static const nsAttrValue::EnumTable* kFormDefaultAutocomplete =
     &kFormAutocompleteTable[0];
 
-bool HTMLFormElement::gFirstFormSubmitted = false;
-bool HTMLFormElement::gPasswordManagerInitialized = false;
-
 HTMLFormElement::HTMLFormElement(
     already_AddRefed<mozilla::dom::NodeInfo>&& aNodeInfo)
     : nsGenericHTMLElement(std::move(aNodeInfo)),
       mControls(new HTMLFormControlsCollection(this)),
       mSelectedRadioButtons(2),
       mRequiredRadioButtonCounts(2),
       mValueMissingRadioGroups(2),
       mPendingSubmission(nullptr),
@@ -848,72 +845,36 @@ nsresult HTMLFormElement::DoSecureToInse
                                    telemetryBucket + 1);
   }
   return NS_OK;
 }
 
 nsresult HTMLFormElement::NotifySubmitObservers(nsIURI* aActionURL,
                                                 bool* aCancelSubmit,
                                                 bool aEarlyNotify) {
-  // If this is the first form, bring alive the first form submit
-  // category observers
-  if (!gFirstFormSubmitted) {
-    gFirstFormSubmitted = true;
-    NS_CreateServicesFromCategory(NS_FIRST_FORMSUBMIT_CATEGORY, nullptr,
-                                  NS_FIRST_FORMSUBMIT_CATEGORY);
-  }
-
   if (!aEarlyNotify) {
     nsresult rv = DoSecureToInsecureSubmitCheck(aActionURL, aCancelSubmit);
     if (NS_FAILED(rv)) {
       return rv;
     }
     if (*aCancelSubmit) {
       return NS_OK;
     }
   }
 
-  // Notify observers that the form is being submitted.
-  nsCOMPtr<nsIObserverService> service =
-      mozilla::services::GetObserverService();
-  if (!service) return NS_ERROR_FAILURE;
-
-  nsCOMPtr<nsISimpleEnumerator> theEnum;
-  nsresult rv = service->EnumerateObservers(
-      aEarlyNotify ? NS_EARLYFORMSUBMIT_SUBJECT : NS_FORMSUBMIT_SUBJECT,
-      getter_AddRefs(theEnum));
-  NS_ENSURE_SUCCESS(rv, rv);
-
-  if (theEnum) {
-    nsCOMPtr<nsISupports> inst;
-    *aCancelSubmit = false;
-
-    // XXXbz what do the submit observers actually want?  The window
-    // of the document this is shown in?  Or something else?
-    // sXBL/XBL2 issue
-    nsCOMPtr<nsPIDOMWindowOuter> window = OwnerDoc()->GetWindow();
-
-    bool loop = true;
-    while (NS_SUCCEEDED(theEnum->HasMoreElements(&loop)) && loop) {
-      theEnum->GetNext(getter_AddRefs(inst));
-
-      nsCOMPtr<nsIFormSubmitObserver> formSubmitObserver(
-          do_QueryInterface(inst));
-      if (formSubmitObserver) {
-        rv = formSubmitObserver->Notify(
-            this, window ? window->GetCurrentInnerWindow() : nullptr,
-            aActionURL, aCancelSubmit);
-        NS_ENSURE_SUCCESS(rv, rv);
-      }
-      if (*aCancelSubmit) {
-        return NS_OK;
-      }
-    }
+  bool defaultAction = true;
+  nsresult rv = nsContentUtils::DispatchEventOnlyToChrome(
+      OwnerDoc(), static_cast<nsINode*>(this),
+      aEarlyNotify ? NS_LITERAL_STRING("DOMFormBeforeSubmit")
+                   : NS_LITERAL_STRING("DOMFormSubmit"),
+      CanBubble::eYes, Cancelable::eYes, &defaultAction);
+  *aCancelSubmit = !defaultAction;
+  if (*aCancelSubmit) {
+    return NS_OK;
   }
-
   return rv;
 }
 
 nsresult HTMLFormElement::WalkFormElements(
     HTMLFormSubmission* aFormSubmission) {
   // This shouldn't be called recursively, so use a rather large value
   // for the preallocated buffer.
   AutoTArray<RefPtr<nsGenericHTMLFormElement>, 100> sortedControls;
@@ -1119,26 +1080,18 @@ nsresult HTMLFormElement::AddElement(nsG
   bool lastElement = AddElementToList(controlList, aChild, this);
 
 #ifdef DEBUG
   AssertDocumentOrder(controlList, this);
 #endif
 
   int32_t type = aChild->ControlType();
 
-  //
-  // If it is a password control, and the password manager has not yet been
-  // initialized, initialize the password manager
-  //
+  // If it is a password control, inform the password manager.
   if (type == NS_FORM_INPUT_PASSWORD) {
-    if (!gPasswordManagerInitialized) {
-      gPasswordManagerInitialized = true;
-      NS_CreateServicesFromCategory(NS_PASSWORDMANAGER_CATEGORY, nullptr,
-                                    NS_PASSWORDMANAGER_CATEGORY);
-    }
     PostPasswordEvent();
   }
 
   // Default submit element handling
   if (aChild->IsSubmitControl()) {
     // Update mDefaultSubmitElement, mFirstSubmitInElements,
     // mFirstSubmitNotInElements.
 
--- a/dom/html/HTMLFormElement.h
+++ b/dom/html/HTMLFormElement.h
@@ -597,22 +597,16 @@ class HTMLFormElement final : public nsG
   /** Keep track of whether a submission was user-initiated or not */
   bool mSubmitInitiatedFromUserInput;
   /**
    * Whether the submission of this form has been ever prevented because of
    * being invalid.
    */
   bool mEverTriedInvalidSubmit;
 
- protected:
-  /** Detection of first form to notify observers */
-  static bool gFirstFormSubmitted;
-  /** Detection of first password input to initialize the password manager */
-  static bool gPasswordManagerInitialized;
-
  private:
   ~HTMLFormElement();
 };
 
 }  // namespace dom
 
 }  // namespace mozilla
 
--- a/dom/html/nsHTMLContentSink.cpp
+++ b/dom/html/nsHTMLContentSink.cpp
@@ -36,16 +36,17 @@
 
 #include "nsGenericHTMLElement.h"
 
 #include "nsIScriptElement.h"
 
 #include "nsIComponentManager.h"
 #include "nsIServiceManager.h"
 
+#include "nsDocElementCreatedNotificationRunner.h"
 #include "nsGkAtoms.h"
 #include "nsContentUtils.h"
 #include "nsIChannel.h"
 #include "nsIHttpChannel.h"
 #include "nsIDocShell.h"
 #include "mozilla/dom/Document.h"
 #include "nsStubDocumentObserver.h"
 #include "nsIHTMLDocument.h"
@@ -900,16 +901,19 @@ void HTMLContentSink::NotifyRootInsertio
   // tag.
   mNotifiedRootInsertion = true;
   NotifyInsert(nullptr, mRoot);
 
   // Now update the notification information in all our
   // contexts, since we just inserted the root and notified on
   // our whole tree
   UpdateChildCounts();
+
+  nsContentUtils::AddScriptRunner(
+      new nsDocElementCreatedNotificationRunner(mDocument));
 }
 
 void HTMLContentSink::UpdateChildCounts() {
   uint32_t numContexts = mContextStack.Length();
   for (uint32_t i = 0; i < numContexts; i++) {
     SinkContext* sc = mContextStack.ElementAt(i);
 
     sc->UpdateChildCounts();
--- a/dom/html/nsIFormSubmitObserver.idl
+++ b/dom/html/nsIFormSubmitObserver.idl
@@ -11,21 +11,15 @@ interface nsIURI;
 interface nsIArray;
 
 webidl HTMLFormElement;
 webidl Element;
 
 [scriptable, uuid(867cb7e7-835d-408b-9788-d2834d284e03)]
 interface nsIFormSubmitObserver: nsISupports
 {
-  void notify(in HTMLFormElement formNode, in mozIDOMWindow window, in nsIURI actionURL, out boolean cancelSubmit);
-
   void notifyInvalidSubmit(in HTMLFormElement formNode,
                            in Array<Element> invalidElements);
 };
 
 %{C++
-#define NS_FORMSUBMIT_SUBJECT "formsubmit"
-#define NS_EARLYFORMSUBMIT_SUBJECT "earlyformsubmit"
-#define NS_FIRST_FORMSUBMIT_CATEGORY "firstformsubmit"
-#define NS_PASSWORDMANAGER_CATEGORY "passwordmanager"
 #define NS_INVALIDFORMSUBMIT_SUBJECT "invalidformsubmit"
 %}
--- a/dom/interfaces/base/nsIBrowser.idl
+++ b/dom/interfaces/base/nsIBrowser.idl
@@ -89,24 +89,16 @@ interface nsIBrowser : nsISupports
                                        in unsigned long enabledLength,
                                        [array, size_is(enabledLength)] in string enabledCommands,
                                        in unsigned long disabledLength,
                                        [array, size_is(disabledLength)] in string disabledCommands);
 
   readonly attribute nsIPrincipal contentPrincipal;
 
   /**
-   * Called by Gecko when a content blocking event needs to update the event
-   * state stored in the security UI object stored in the parent process.
-   * @param aEvent one of the content blocking event codes defined in
-   *               nsIWebProgressListener.idl.
-   */
-  void updateSecurityUIForContentBlockingEvent(in unsigned long aEvent);
-
-  /**
    * Called by Gecko when we need to call the web progress listeners on our
    * browser element in order to notify them about a content blocking event
    * which happened in the content process.
    * @param isWebProgressPassed whether we're passed a webProgress argument
    * @param isTopLevel whether we're in the top-level document
    * @param isLoadingDocument whether we're in the process of loading a document
    * @param loadType the type of load in progress
    * @param DOMWindowID the ID of the window receiving the notification
--- a/dom/ipc/ContentChild.cpp
+++ b/dom/ipc/ContentChild.cpp
@@ -2577,16 +2577,23 @@ mozilla::ipc::IPCResult ContentChild::Re
 
 mozilla::ipc::IPCResult ContentChild::RecvInitJSWindowActorInfos(
     nsTArray<JSWindowActorInfo>&& aInfos) {
   RefPtr<JSWindowActorService> actSvc = JSWindowActorService::GetSingleton();
   actSvc->LoadJSWindowActorInfos(aInfos);
   return IPC_OK();
 }
 
+mozilla::ipc::IPCResult ContentChild::RecvUnregisterJSWindowActor(
+    const nsString& aName) {
+  RefPtr<JSWindowActorService> actSvc = JSWindowActorService::GetSingleton();
+  actSvc->UnregisterWindowActor(aName);
+  return IPC_OK();
+}
+
 mozilla::ipc::IPCResult ContentChild::RecvLastPrivateDocShellDestroyed() {
   nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
   obs->NotifyObservers(nullptr, "last-pb-context-exited", nullptr);
   return IPC_OK();
 }
 
 mozilla::ipc::IPCResult ContentChild::RecvNotifyProcessPriorityChanged(
     const hal::ProcessPriority& aPriority) {
--- a/dom/ipc/ContentChild.h
+++ b/dom/ipc/ContentChild.h
@@ -423,16 +423,18 @@ class ContentChild final : public PConte
       const ServiceWorkerConfiguration& aConfig);
 
   mozilla::ipc::IPCResult RecvInitBlobURLs(
       nsTArray<BlobURLRegistrationData>&& aRegistations);
 
   mozilla::ipc::IPCResult RecvInitJSWindowActorInfos(
       nsTArray<JSWindowActorInfo>&& aInfos);
 
+  mozilla::ipc::IPCResult RecvUnregisterJSWindowActor(const nsString& aName);
+
   mozilla::ipc::IPCResult RecvLastPrivateDocShellDestroyed();
 
   mozilla::ipc::IPCResult RecvNotifyProcessPriorityChanged(
       const hal::ProcessPriority& aPriority);
 
   mozilla::ipc::IPCResult RecvMinimizeMemoryUsage();
 
   mozilla::ipc::IPCResult RecvLoadAndRegisterSheet(const URIParams& aURI,
--- a/dom/ipc/JSWindowActorService.cpp
+++ b/dom/ipc/JSWindowActorService.cpp
@@ -2,26 +2,284 @@
 
 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "mozilla/dom/JSWindowActorService.h"
 #include "mozilla/dom/ChromeUtilsBinding.h"
+#include "mozilla/dom/EventTargetBinding.h"
+#include "mozilla/dom/EventTarget.h"
 #include "mozilla/dom/PContent.h"
 #include "mozilla/StaticPtr.h"
 #include "mozJSComponentLoader.h"
 
 namespace mozilla {
 namespace dom {
 namespace {
 StaticRefPtr<JSWindowActorService> gJSWindowActorService;
 }
 
+/**
+ * Helper for calling a named method on a JS Window Actor object with a single
+ * parameter.
+ *
+ * It will do the following:
+ *  1. Enter the actor object's compartment.
+ *  2. Convert the given parameter into a JS parameter with ToJSValue.
+ *  3. Call the named method, passing the single parameter.
+ *  4. Place the return value in aRetVal.
+ *
+ * If an error occurs during this process, this method clears any pending
+ * exceptions, and returns a nsresult.
+ */
+template <typename T>
+nsresult CallJSActorMethod(nsWrapperCache* aActor, const char* aName,
+                           T& aNativeArg, JS::MutableHandleValue aRetVal) {
+  // FIXME(nika): We should avoid atomizing and interning the |aName| strings
+  // every time we do this call. Given the limited set of possible IDs, it would
+  // be better to cache the `jsid` values.
+
+  aRetVal.setUndefined();
+
+  // Get the wrapper for our actor. If we don't have a wrapper, the target
+  // method won't be defined on it. so there's no reason to continue.
+  JS::Rooted<JSObject*> actor(RootingCx(), aActor->GetWrapper());
+  if (NS_WARN_IF(!actor)) {
+    return NS_ERROR_NOT_IMPLEMENTED;
+  }
+
+  // Enter the realm of our actor object to begin running script.
+  AutoEntryScript aes(actor, "CallJSActorMethod");
+  JSContext* cx = aes.cx();
+  JSAutoRealm ar(cx, actor);
+
+  // Get the method we want to call, and produce NS_ERROR_NOT_IMPLEMENTED if
+  // it is not present.
+  JS::Rooted<JS::Value> func(cx);
+  if (NS_WARN_IF(!JS_GetProperty(cx, actor, aName, &func) ||
+                 func.isPrimitive())) {
+    JS_ClearPendingException(cx);
+    return NS_ERROR_NOT_IMPLEMENTED;
+  }
+
+  // Convert the native argument to a JS value.
+  JS::Rooted<JS::Value> argv(cx);
+  if (NS_WARN_IF(!ToJSValue(cx, aNativeArg, &argv))) {
+    JS_ClearPendingException(cx);
+    return NS_ERROR_FAILURE;
+  }
+
+  // Call our method.
+  if (NS_WARN_IF(!JS_CallFunctionValue(cx, actor, func,
+                                       JS::HandleValueArray(argv), aRetVal))) {
+    JS_ClearPendingException(cx);
+    return NS_ERROR_FAILURE;
+  }
+
+  return NS_OK;
+}
+
+/**
+ * Object corresponding to a single actor protocol. This object acts as an
+ * Event listener for the actor which is called for events which would
+ * trigger actor creation.
+ *
+ * This object also can act as a carrier for methods and other state related to
+ * a single protocol managed by the JSWindowActorService.
+ */
+class JSWindowActorProtocol final : public nsIDOMEventListener {
+ public:
+  NS_DECL_ISUPPORTS
+  NS_DECL_NSIDOMEVENTLISTENER
+
+  static already_AddRefed<JSWindowActorProtocol> FromIPC(
+      const JSWindowActorInfo& aInfo);
+  JSWindowActorInfo ToIPC();
+
+  static already_AddRefed<JSWindowActorProtocol> FromWebIDLOptions(
+      const nsAString& aName, const WindowActorOptions& aOptions,
+      ErrorResult& aRv);
+
+  struct Sided {
+    nsCString mModuleURI;
+  };
+
+  struct ParentSide : public Sided {};
+
+  struct EventDecl {
+    nsString mName;
+    EventListenerFlags mFlags;
+    Optional<bool> mPassive;
+  };
+
+  struct ChildSide : public Sided {
+    nsTArray<EventDecl> mEvents;
+  };
+
+  const nsAString& Name() const { return mName; }
+  const ParentSide& Parent() const { return mParent; }
+  const ChildSide& Child() const { return mChild; }
+
+  void RegisterListenersFor(EventTarget* aRoot);
+  void UnregisterListenersFor(EventTarget* aRoot);
+
+ private:
+  explicit JSWindowActorProtocol(const nsAString& aName) : mName(aName) {}
+
+  ~JSWindowActorProtocol() = default;
+
+  nsString mName;
+  ParentSide mParent;
+  ChildSide mChild;
+};
+
+NS_IMPL_ISUPPORTS(JSWindowActorProtocol, nsIDOMEventListener);
+
+/* static */ already_AddRefed<JSWindowActorProtocol>
+JSWindowActorProtocol::FromIPC(const JSWindowActorInfo& aInfo) {
+  MOZ_DIAGNOSTIC_ASSERT(XRE_IsContentProcess());
+
+  RefPtr<JSWindowActorProtocol> proto = new JSWindowActorProtocol(aInfo.name());
+  proto->mChild.mModuleURI.Assign(aInfo.url());
+
+  proto->mChild.mEvents.SetCapacity(aInfo.events().Length());
+  for (auto& ipc : aInfo.events()) {
+    auto* event = proto->mChild.mEvents.AppendElement();
+    event->mName.Assign(ipc.name());
+    event->mFlags.mCapture = ipc.capture();
+    event->mFlags.mInSystemGroup = ipc.systemGroup();
+    event->mFlags.mAllowUntrustedEvents = ipc.allowUntrusted();
+    if (ipc.hasPassive()) {
+      event->mPassive.Construct(ipc.passive());
+    }
+  }
+
+  return proto.forget();
+}
+
+JSWindowActorInfo JSWindowActorProtocol::ToIPC() {
+  MOZ_DIAGNOSTIC_ASSERT(XRE_IsParentProcess());
+
+  JSWindowActorInfo info;
+  info.name() = mName;
+  info.url() = mChild.mModuleURI;
+
+  info.events().SetCapacity(mChild.mEvents.Length());
+  for (auto& event : mChild.mEvents) {
+    auto* ipc = info.events().AppendElement();
+    ipc->name().Assign(event.mName);
+    ipc->capture() = event.mFlags.mCapture;
+    ipc->systemGroup() = event.mFlags.mInSystemGroup;
+    ipc->allowUntrusted() = event.mFlags.mAllowUntrustedEvents;
+    ipc->hasPassive() = event.mPassive.WasPassed();
+    if (event.mPassive.WasPassed()) {
+      ipc->passive() = event.mPassive.Value();
+    }
+  }
+
+  return info;
+}
+
+already_AddRefed<JSWindowActorProtocol>
+JSWindowActorProtocol::FromWebIDLOptions(const nsAString& aName,
+                                         const WindowActorOptions& aOptions,
+                                         ErrorResult& aRv) {
+  MOZ_DIAGNOSTIC_ASSERT(XRE_IsParentProcess());
+
+  RefPtr<JSWindowActorProtocol> proto = new JSWindowActorProtocol(aName);
+  proto->mParent.mModuleURI = aOptions.mParent.mModuleURI;
+  proto->mChild.mModuleURI = aOptions.mChild.mModuleURI;
+
+  // For each event declared in the source dictionary, initialize the
+  // corresponding envent declaration entry in the JSWindowActorProtocol.
+  if (aOptions.mChild.mEvents.WasPassed()) {
+    auto& entries = aOptions.mChild.mEvents.Value().Entries();
+    proto->mChild.mEvents.SetCapacity(entries.Length());
+
+    for (auto& entry : entries) {
+      // We don't support the mOnce field, as it doesn't work well in this
+      // environment. For now, throw an error in that case.
+      if (entry.mValue.mOnce) {
+        aRv.Throw(NS_ERROR_DOM_NOT_SUPPORTED_ERR);
+        return nullptr;
+      }
+
+      // Add the EventDecl to our list of events.
+      EventDecl* evt = proto->mChild.mEvents.AppendElement();
+      evt->mName = entry.mKey;
+      evt->mFlags.mCapture = entry.mValue.mCapture;
+      evt->mFlags.mInSystemGroup = entry.mValue.mMozSystemGroup;
+      evt->mFlags.mAllowUntrustedEvents =
+          entry.mValue.mWantUntrusted.WasPassed()
+              ? entry.mValue.mWantUntrusted.Value()
+              : false;
+      if (entry.mValue.mPassive.WasPassed()) {
+        evt->mPassive.Construct(entry.mValue.mPassive.Value());
+      }
+    }
+  }
+
+  return proto.forget();
+}
+
+/**
+ * This listener only listens for events for the child side of the protocol.
+ * This will work in both content and parent processes.
+ */
+NS_IMETHODIMP JSWindowActorProtocol::HandleEvent(Event* aEvent) {
+  // Determine which inner window we're associated with, and get its
+  // WindowGlobalChild actor.
+  EventTarget* target = aEvent->GetOriginalTarget();
+  if (NS_WARN_IF(!target)) {
+    return NS_ERROR_FAILURE;
+  }
+
+  nsCOMPtr<nsPIDOMWindowInner> inner =
+      do_QueryInterface(target->GetOwnerGlobal());
+  if (NS_WARN_IF(!inner)) {
+    return NS_ERROR_FAILURE;
+  }
+
+  RefPtr<WindowGlobalChild> wgc = inner->GetWindowGlobalChild();
+  if (NS_WARN_IF(!wgc)) {
+    return NS_ERROR_FAILURE;
+  }
+
+  // Ensure our actor is present.
+  ErrorResult error;
+  RefPtr<JSWindowActorChild> actor = wgc->GetActor(mName, error);
+  if (NS_WARN_IF(error.Failed())) {
+    return error.StealNSResult();
+  }
+
+  // Call the "handleEvent" method on our actor.
+  JS::Rooted<JS::Value> dummy(RootingCx());
+  return CallJSActorMethod(actor, "handleEvent", aEvent, &dummy);
+}
+
+void JSWindowActorProtocol::RegisterListenersFor(EventTarget* aRoot) {
+  EventListenerManager* elm = aRoot->GetOrCreateListenerManager();
+
+  for (auto& event : mChild.mEvents) {
+    elm->AddEventListenerByType(EventListenerHolder(this), event.mName,
+                                event.mFlags, event.mPassive);
+  }
+}
+
+void JSWindowActorProtocol::UnregisterListenersFor(EventTarget* aRoot) {
+  EventListenerManager* elm = aRoot->GetOrCreateListenerManager();
+
+  for (auto& event : mChild.mEvents) {
+    elm->RemoveEventListenerByType(EventListenerHolder(this), event.mName,
+                                   event.mFlags);
+  }
+}
+
 JSWindowActorService::JSWindowActorService() { MOZ_ASSERT(NS_IsMainThread()); }
 
 JSWindowActorService::~JSWindowActorService() { MOZ_ASSERT(NS_IsMainThread()); }
 
 /* static */ already_AddRefed<JSWindowActorService>
 JSWindowActorService::GetSingleton() {
   MOZ_ASSERT(NS_IsMainThread());
   if (!gJSWindowActorService) {
@@ -40,88 +298,118 @@ void JSWindowActorService::RegisterWindo
   MOZ_ASSERT(XRE_IsParentProcess());
 
   auto entry = mDescriptors.LookupForAdd(aName);
   if (entry) {
     aRv.Throw(NS_ERROR_DOM_NOT_SUPPORTED_ERR);
     return;
   }
 
-  entry.OrInsert([&] { return new WindowActorOptions(aOptions); });
+  // Insert a new entry for the protocol.
+  RefPtr<JSWindowActorProtocol> proto =
+      JSWindowActorProtocol::FromWebIDLOptions(aName, aOptions, aRv);
+  if (NS_WARN_IF(aRv.Failed())) {
+    return;
+  }
 
-  // Send child's WindowActorOptions to any existing content processes,
-  // because parent's WindowActorOptions can never be accessed in content.
+  entry.OrInsert([&] { return proto; });
+
+  // Send information about the newly added entry to every existing content
+  // process.
+  AutoTArray<JSWindowActorInfo, 1> ipcInfos{proto->ToIPC()};
   for (auto* cp : ContentParent::AllProcesses(ContentParent::eLive)) {
-    nsTArray<JSWindowActorInfo> infos;
-    infos.AppendElement(
-        JSWindowActorInfo(nsString(aName), aOptions.mChild.mModuleURI));
-    Unused << cp->SendInitJSWindowActorInfos(infos);
+    Unused << cp->SendInitJSWindowActorInfos(ipcInfos);
+  }
+
+  // Register event listeners for any existing window roots.
+  for (EventTarget* root : mRoots) {
+    proto->RegisterListenersFor(root);
   }
 }
 
 void JSWindowActorService::UnregisterWindowActor(const nsAString& aName) {
-  mDescriptors.Remove(aName);
+  nsAutoString name(aName);
+
+  RefPtr<JSWindowActorProtocol> proto;
+  if (mDescriptors.Remove(aName, getter_AddRefs(proto))) {
+    // If we're in the parent process, also unregister the window actor in all
+    // live content processes.
+    if (XRE_IsParentProcess()) {
+      for (auto* cp : ContentParent::AllProcesses(ContentParent::eLive)) {
+        Unused << cp->SendUnregisterJSWindowActor(name);
+      }
+    }
+
+    // Remove listeners for this actor from each of our window roots.
+    for (EventTarget* root : mRoots) {
+      proto->UnregisterListenersFor(root);
+    }
+  }
 }
 
 void JSWindowActorService::LoadJSWindowActorInfos(
     nsTArray<JSWindowActorInfo>& aInfos) {
   MOZ_ASSERT(NS_IsMainThread());
   MOZ_ASSERT(XRE_IsContentProcess());
 
   for (uint32_t i = 0, len = aInfos.Length(); i < len; i++) {
-    auto entry = mDescriptors.LookupForAdd(aInfos[i].name());
+    // Create our JSWindowActorProtocol, register it in mDescriptors.
+    RefPtr<JSWindowActorProtocol> proto =
+        JSWindowActorProtocol::FromIPC(aInfos[i]);
+    mDescriptors.Put(aInfos[i].name(), proto);
 
-    entry.OrInsert([&] {
-      WindowActorOptions* option = new WindowActorOptions();
-      option->mChild.mModuleURI.Assign(aInfos[i].url());
-      return option;
-    });
+    // Register listeners for each window root.
+    for (EventTarget* root : mRoots) {
+      proto->RegisterListenersFor(root);
+    }
   }
 }
 
 void JSWindowActorService::GetJSWindowActorInfos(
     nsTArray<JSWindowActorInfo>& aInfos) {
   MOZ_ASSERT(NS_IsMainThread());
   MOZ_ASSERT(XRE_IsParentProcess());
 
   for (auto iter = mDescriptors.ConstIter(); !iter.Done(); iter.Next()) {
-    aInfos.AppendElement(JSWindowActorInfo(nsString(iter.Key()),
-                                           iter.Data()->mChild.mModuleURI));
+    aInfos.AppendElement(iter.Data()->ToIPC());
   }
 }
 
 void JSWindowActorService::ConstructActor(const nsAString& aName,
                                           bool aParentSide,
                                           JS::MutableHandleObject aActor,
                                           ErrorResult& aRv) {
   MOZ_ASSERT_IF(aParentSide, XRE_IsParentProcess());
 
   // Constructing an actor requires a running script, so push an AutoEntryScript
   // onto the stack.
   AutoEntryScript aes(xpc::PrivilegedJunkScope(), "JSWindowActor construction");
   JSContext* cx = aes.cx();
 
   // Load our descriptor
-  const WindowActorOptions* descriptor = mDescriptors.Get(aName);
-  if (!descriptor) {
-    MOZ_ASSERT(false, "WindowActorOptions must be found in mDescriptors");
+  RefPtr<JSWindowActorProtocol> proto = mDescriptors.Get(aName);
+  if (!proto) {
     aRv.Throw(NS_ERROR_DOM_NOT_SUPPORTED_ERR);
     return;
   }
 
-  const WindowActorSidedOptions& side =
-      aParentSide ? descriptor->mParent : descriptor->mChild;
+  const JSWindowActorProtocol::Sided* side;
+  if (aParentSide) {
+    side = &proto->Parent();
+  } else {
+    side = &proto->Child();
+  }
 
   // Load the module using mozJSComponentLoader.
   RefPtr<mozJSComponentLoader> loader = mozJSComponentLoader::Get();
   MOZ_ASSERT(loader);
 
   JS::RootedObject global(cx);
   JS::RootedObject exports(cx);
-  aRv = loader->Import(cx, side.mModuleURI, &global, &exports);
+  aRv = loader->Import(cx, side->mModuleURI, &global, &exports);
   if (aRv.Failed()) {
     return;
   }
   MOZ_ASSERT(exports, "null exports!");
 
   // Load the specific property from our module.
   JS::RootedValue ctor(cx);
   nsAutoString ctorName(aName);
@@ -175,10 +463,28 @@ void JSWindowActorService::ReceiveMessag
   JS::RootedValue dummy(cx);
   if (NS_WARN_IF(!JS_CallFunctionName(cx, aObj, "recvAsyncMessage",
                                       JS::HandleValueArray(argv), &dummy))) {
     JS_ClearPendingException(cx);
     return;
   }
 }
 
+void JSWindowActorService::RegisterWindowRoot(EventTarget* aRoot) {
+  MOZ_ASSERT(!mRoots.Contains(aRoot));
+  mRoots.AppendElement(aRoot);
+
+  // Register event listeners on the newly added Window Root.
+  for (auto iter = mDescriptors.Iter(); !iter.Done(); iter.Next()) {
+    iter.Data()->RegisterListenersFor(aRoot);
+  }
+}
+
+/* static */ void JSWindowActorService::UnregisterWindowRoot(
+    EventTarget* aRoot) {
+  if (gJSWindowActorService) {
+    // NOTE: No need to unregister listeners here, as the root is going away.
+    gJSWindowActorService->mRoots.RemoveElement(aRoot);
+  }
+}
+
 }  // namespace dom
 }  // namespace mozilla
\ No newline at end of file
--- a/dom/ipc/JSWindowActorService.h
+++ b/dom/ipc/JSWindowActorService.h
@@ -2,23 +2,25 @@
 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #ifndef mozilla_dom_JSWindowActorService_h
 #define mozilla_dom_JSWindowActorService_h
 
-#include "nsClassHashtable.h"
+#include "nsRefPtrHashtable.h"
 #include "nsString.h"
 
 namespace mozilla {
 namespace dom {
 struct WindowActorOptions;
 class JSWindowActorInfo;
+class JSWindowActorProtocol;
+class EventTarget;
 
 class JSWindowActorService final {
  public:
   NS_INLINE_DECL_REFCOUNTING(JSWindowActorService)
 
   static already_AddRefed<JSWindowActorService> GetSingleton();
 
   void RegisterWindowActor(const nsAString& aName,
@@ -38,19 +40,26 @@ class JSWindowActorService final {
   // This method will not initialize the actor or set its manager,
   // which is handled by callers.
   void ConstructActor(const nsAString& aName, bool aParentSide,
                       JS::MutableHandleObject aActor, ErrorResult& aRv);
 
   void ReceiveMessage(JS::RootedObject& aObj, const nsString& aMessageName,
                       ipc::StructuredCloneData& aData);
 
+  // Register or unregister a WindowRoot object from this JSWindowActorService.
+  void RegisterWindowRoot(EventTarget* aRoot);
+
+  // NOTE: This method is static, as it may be called during shutdown.
+  static void UnregisterWindowRoot(EventTarget* aRoot);
+
  private:
   JSWindowActorService();
   ~JSWindowActorService();
 
-  nsClassHashtable<nsStringHashKey, WindowActorOptions> mDescriptors;
+  nsTArray<EventTarget*> mRoots;
+  nsRefPtrHashtable<nsStringHashKey, JSWindowActorProtocol> mDescriptors;
 };
 
 }  // namespace dom
 }  // namespace mozilla
 
 #endif  // mozilla_dom_JSWindowActorService_h
\ No newline at end of file
--- a/dom/ipc/PContent.ipdl
+++ b/dom/ipc/PContent.ipdl
@@ -225,20 +225,35 @@ union FileCreationResult
 struct BlobURLRegistrationData
 {
   nsCString url;
   IPCBlob blob;
   Principal principal;
   bool revoked;
 };
 
+struct JSWindowActorEventDecl
+{
+  nsString name;
+  bool capture;
+  bool systemGroup;
+  bool allowUntrusted;
+
+  // FIXME(gross): Optional<bool> is a bit gross to encode here, so instead it's
+  // encoded as two booleans.
+  bool hasPassive;
+  bool passive;
+};
+
 struct JSWindowActorInfo
 {
   nsString name;
   nsCString url;
+
+  JSWindowActorEventDecl[] events;
 };
 
 struct GMPAPITags
 {
     nsCString api;
     nsCString[] tags;
 };
 
@@ -538,16 +553,21 @@ child:
      */
     async InitBlobURLs(BlobURLRegistrationData[] registrations);
 
     /**
      * Send JSWindowActorInfos to child process.
      */
     async InitJSWindowActorInfos(JSWindowActorInfo[] aInfos);
 
+    /**
+     * Unregister a previously registered JSWindowActor in the child process.
+     */
+    async UnregisterJSWindowActor(nsString name);
+
     async SetXPCOMProcessAttributes(XPCOMInitData xpcomInit,
                                     StructuredCloneData initialData,
                                     LookAndFeelInt[] lookAndFeelIntCache,
                                     /* used on MacOSX and Linux only: */
                                     SystemFontListEntry[] systemFontList);
 
     // Notify child that last-pb-context-exited notification was observed
     async LastPrivateDocShellDestroyed();
--- a/dom/ipc/TabParent.cpp
+++ b/dom/ipc/TabParent.cpp
@@ -2138,19 +2138,16 @@ mozilla::ipc::IPCResult TabParent::RecvO
     MOZ_ASSERT(aWebProgressData.type() != OptionalWebProgressData::T__None);
 
     if (aWebProgressData.type() == OptionalWebProgressData::Tvoid_t) {
       Unused << browser->CallWebProgressContentBlockingEventListeners(
           false, false, false, 0, 0, aRequestData.requestURI(),
           aRequestData.originalRequestURI(), aRequestData.matchedList(),
           aEvent);
     } else {
-      if (aWebProgressData.get_WebProgressData().isTopLevel()) {
-        Unused << browser->UpdateSecurityUIForContentBlockingEvent(aEvent);
-      }
       Unused << browser->CallWebProgressContentBlockingEventListeners(
           true, aWebProgressData.get_WebProgressData().isTopLevel(),
           aWebProgressData.get_WebProgressData().isLoadingDocument(),
           aWebProgressData.get_WebProgressData().loadType(),
           aWebProgressData.get_WebProgressData().DOMWindowID(),
           aRequestData.requestURI(), aRequestData.originalRequestURI(),
           aRequestData.matchedList(), aEvent);
     }
--- a/dom/ipc/tests/browser_JSWindowActor.js
+++ b/dom/ipc/tests/browser_JSWindowActor.js
@@ -4,16 +4,20 @@
 
 const URL = "about:blank";
 let windowActorOptions = {
   parent: {
     moduleURI: "resource://testing-common/TestParent.jsm",
   },
   child: {
     moduleURI: "resource://testing-common/TestChild.jsm",
+
+    events: {
+      "mozshowdropdown": {},
+    },
   },
 };
 
 add_task(function test_registerWindowActor() {
   ok(ChromeUtils, "Should be able to get the ChromeUtils interface");
   ChromeUtils.registerWindowActor("Test", windowActorOptions);
   SimpleTest.doesThrow(() =>
     ChromeUtils.registerWindowActor("Test", windowActorOptions),
@@ -94,8 +98,46 @@ add_task(async function test_asyncMessag
             ok(data.toChild, "ToChild should be true.");
           });
 
           await promise;
         });
         ChromeUtils.unregisterWindowActor("Test");
     });
 });
+
+add_task(async function test_events() {
+  ChromeUtils.registerWindowActor("Test", windowActorOptions);
+  await BrowserTestUtils.withNewTab({gBrowser, url: URL}, async browser => {
+    // Add a select element to the DOM of the loaded document.
+    await ContentTask.spawn(browser, {}, async function() {
+      content.document.body.innerHTML += `
+        <select id="testSelect">
+          <option>A</option>
+          <option>B</option>
+        </select>`;
+    });
+
+    // Wait for the observer notification.
+    let observePromise = new Promise(resolve => {
+      const TOPIC = "test-js-window-actor-parent-event";
+      Services.obs.addObserver(function obs(subject, topic, data) {
+        is(topic, TOPIC, "topic matches");
+
+        Services.obs.removeObserver(obs, TOPIC);
+        resolve({subject, data});
+      }, TOPIC);
+    });
+
+    // Click on the select to show the dropdown.
+    await BrowserTestUtils.synthesizeMouseAtCenter("#testSelect", {}, browser);
+
+    // Wait for the observer notification to fire, and inspect the results.
+    let {subject, data} = await observePromise;
+    is(data, "mozshowdropdown");
+
+    let parent = browser.browsingContext.currentWindowGlobal;
+    let actorParent = parent.getActor("Test");
+    ok(actorParent, "JSWindowActorParent should have value.");
+    is(subject.wrappedJSObject, actorParent, "Should have been recieved on the right actor");
+  });
+  ChromeUtils.unregisterWindowActor("Test");
+});
--- a/dom/media/moz.build
+++ b/dom/media/moz.build
@@ -170,25 +170,26 @@ EXPORTS += [
 ]
 
 EXPORTS.mozilla += [
     'MediaManager.h',
 ]
 
 EXPORTS.mozilla.media.webrtc += [
     'webrtc/WebrtcGlobal.h',
+    'webrtc/WebrtcIPCTraits.h',
 ]
 
 if not CONFIG['MOZ_WEBRTC']:
   EXPORTS.mtransport += [
       '../../media/mtransport/runnable_utils.h',
   ]
 
 IPDL_SOURCES += [
-    'webrtc/PWebrtcGlobal.ipdl'
+    'webrtc/PWebrtcGlobal.ipdl',
 ]
 
 EXPORTS.mozilla.dom += [
     'AudioDeviceInfo.h',
     'AudioStreamTrack.h',
     'AudioTrack.h',
     'AudioTrackList.h',
     'CanvasCaptureMediaStream.h',
@@ -201,16 +202,18 @@ EXPORTS.mozilla.dom += [
     'TextTrackCue.h',
     'TextTrackCueList.h',
     'TextTrackList.h',
     'TextTrackRegion.h',
     'VideoPlaybackQuality.h',
     'VideoStreamTrack.h',
     'VideoTrack.h',
     'VideoTrackList.h',
+    'webrtc/MediaTransportChild.h',
+    'webrtc/MediaTransportParent.h',
 ]
 
 UNIFIED_SOURCES += [
     'ADTSDecoder.cpp',
     'ADTSDemuxer.cpp',
     'AudioCaptureStream.cpp',
     'AudioChannelFormat.cpp',
     'AudioCompactor.cpp',
--- a/dom/media/mp4/moz.build
+++ b/dom/media/mp4/moz.build
@@ -23,54 +23,20 @@ EXPORTS += [
 
 UNIFIED_SOURCES += [
     'Box.cpp',
     'BufferStream.cpp',
     'DecoderData.cpp',
     'Index.cpp',
     'MoofParser.cpp',
     'MP4Decoder.cpp',
+    'MP4Demuxer.cpp',
     'MP4Metadata.cpp',
     'ResourceStream.cpp',
     'SinfParser.cpp',
 ]
 
-
-SOURCES += [
-    'MP4Demuxer.cpp',
-]
-
 FINAL_LIBRARY = 'xul'
 
-# Supress warnnings for now.
-if CONFIG['CC_TYPE'] == 'clang-cl':
-    CFLAGS += [
-        '-wd4013', # 'function' undefined; assuming extern returning int
-        '-wd4101', # unreferenced local variable
-    ]
-    CXXFLAGS += [
-        '-wd4018', # '<' : signed/unsigned mismatch
-        '-wd4275', # non dll-interface class used as base for dll-interface class
-        '-wd4305', # '=' : truncation from 'double' to 'float'
-        '-wd4309', # '=' : truncation of constant value
-        '-wd4355', # 'this' : used in base member initializer list
-        '-wd4804', # '>' : unsafe use of type 'bool' in operation
-        '-wd4099', # mismatched class/struct tags
-    ]
-elif CONFIG['CC_TYPE'] in ('clang', 'gcc'):
-    CFLAGS += [
-        '-Wno-comment',
-        '-Wno-sign-compare',
-        '-Wno-unused-function',
-    ]
-    CXXFLAGS += [
-        '-Werror=format',
-        '-Wno-multichar',
-        '-Wno-sign-compare',
-        '-Wno-unused',
-    ]
-    if CONFIG['CC_TYPE'] == 'clang':
-        CXXFLAGS += [
-            '-Wno-mismatched-tags',
-            '-Wno-tautological-constant-out-of-range-compare',
-            '-Wno-unreachable-code-return',
-            '-Wno-implicit-fallthrough',
-        ]
+# Suppress warnings for now.
+CXXFLAGS += [
+    '-Wno-sign-compare',
+]
--- a/dom/media/platforms/agnostic/bytestreams/moz.build
+++ b/dom/media/platforms/agnostic/bytestreams/moz.build
@@ -20,42 +20,12 @@ UNIFIED_SOURCES += [
 ]
 
 LOCAL_INCLUDES += [
     '../../../mp4/',
 ]
 
 FINAL_LIBRARY = 'xul'
 
-# Suppress warnings in third-party code.
-if CONFIG['CC_TYPE'] == 'clang-cl':
-    CFLAGS += [
-        '-wd4013', # 'function' undefined; assuming extern returning int
-        '-wd4101', # unreferenced local variable
-    ]
-    CXXFLAGS += [
-        '-wd4018', # '<' : signed/unsigned mismatch
-        '-wd4275', # non dll-interface class used as base for dll-interface class
-        '-wd4305', # '=' : truncation from 'double' to 'float'
-        '-wd4309', # '=' : truncation of constant value
-        '-wd4355', # 'this' : used in base member initializer list
-        '-wd4804', # '>' : unsafe use of type 'bool' in operation
-        '-wd4099', # mismatched class/struct tags
-    ]
-elif CONFIG['CC_TYPE'] in ('clang', 'gcc'):
-    CFLAGS += [
-        '-Wno-comment',
-        '-Wno-sign-compare',
-        '-Wno-unused-function',
-    ]
-    CXXFLAGS += [
-        '-Werror=format',
-        '-Wno-multichar',
-        '-Wno-sign-compare',
-        '-Wno-unused',
-    ]
-    if CONFIG['CC_TYPE'] == 'clang':
-        CXXFLAGS += [
-            '-Wno-mismatched-tags',
-            '-Wno-tautological-constant-out-of-range-compare',
-            '-Wno-unreachable-code-return',
-            '-Wno-implicit-fallthrough',
-        ]
+# Suppress warnings for now.
+CXXFLAGS += [
+    '-Wno-sign-compare',
+]
--- a/dom/media/tests/mochitest/mochitest.ini
+++ b/dom/media/tests/mochitest/mochitest.ini
@@ -121,23 +121,23 @@ skip-if = (android_version == '18') # an
 skip-if = (android_version == '18') # android(Bug 1189784, timeouts on 4.3 emulator)
 [test_peerConnection_audioSynchronizationSourcesUnidirectional.html]
 skip-if = (android_version == '18') # android(Bug 1189784, timeouts on 4.3 emulator)
 [test_peerConnection_audioContributingSources.html]
 skip-if = (android_version == '18') # android(Bug 1189784, timeouts on 4.3 emulator)
 [test_peerConnection_checkPacketDumpHook.html]
 skip-if = (android_version == '18') # android(Bug 1189784, timeouts on 4.3 emulator)
 [test_peerConnection_basicAudioNATSrflx.html]
-skip-if = toolkit == 'android' || (verify && (os == 'linux')) # websockets don't work on android (bug 1266217)
+skip-if = toolkit == 'android' || (verify && (os == 'linux')) || socketprocess_e10s # websockets don't work on android (bug 1266217, IPV6 is busted in try which causes timeouts in socket process case (bug 1521117))
 [test_peerConnection_basicAudioNATRelay.html]
-skip-if = toolkit == 'android' || (verify && debug && (os == 'linux')) # websockets don't work on android (bug 1266217)
+skip-if = toolkit == 'android' || (verify && debug && (os == 'linux')) || socketprocess_e10s # websockets don't work on android (bug 1266217, IPV6 is busted in try which causes timeouts in socket process case (bug 1521117))
 [test_peerConnection_basicAudioNATRelayTCP.html]
-skip-if = toolkit == 'android' # websockets don't work on android (bug 1266217)
+skip-if = toolkit == 'android' || socketprocess_e10s # websockets don't work on android (bug 1266217, IPV6 is busted in try which causes timeouts in socket process case (bug 1521117))
 [test_peerConnection_basicAudioNATRelayTLS.html]
-skip-if = true # need pyopenssl on builders, see bug 1323439 # toolkit == 'android' # websockets don't work on android (bug 1266217)
+skip-if = true # need pyopenssl on builders, see bug 1323439 # toolkit == 'android' || socketprocess_e10s # websockets don't work on android (bug 1266217), IPV6 is busted in try which causes timeouts in socket process case (bug 1521117)
 [test_peerConnection_basicAudioRequireEOC.html]
 skip-if = (android_version == '18') # android(Bug 1189784, timeouts on 4.3 emulator)
 [test_peerConnection_basicAudioPcmaPcmuOnly.html]
 skip-if = android_version == '18'
 [test_peerConnection_basicAudioDynamicPtMissingRtpmap.html]
 skip-if = (android_version == '18') # android(Bug 1189784, timeouts on 4.3 emulator)
 [test_peerConnection_basicAudioVerifyRtpHeaderExtensions.html]
 skip-if = (android_version == '18') # android(Bug 1189784, timeouts on 4.3 emulator)
@@ -342,17 +342,17 @@ skip-if = (android_version == '18') # an
 [test_peerConnection_threeUnbundledConnections.html]
 skip-if = (android_version == '18') # android(Bug 1189784, timeouts on 4.3 emulator)
 [test_selftest.html]
 # Bug 1227781: Crash with bogus TURN server.
 [test_peerConnection_bug1227781.html]
 [test_peerConnection_stats.html]
 skip-if = toolkit == 'android' # android(Bug 1189784, timeouts on 4.3 emulator, Bug 1373858)
 [test_peerConnection_stats_relayProtocol.html]
-skip-if = toolkit == 'android' # android(Bug 1189784, timeouts on 4.3 emulator, Bug 1373858)
+skip-if = toolkit == 'android' || socketprocess_e10s # android(Bug 1189784, timeouts on 4.3 emulator, Bug 1373858, Bug 1521117)
 [test_peerConnection_sender_and_receiver_stats.html]
 skip-if = (android_version == '18') # android(Bug 1189784, timeouts on 4.3 emulator)
 [test_peerConnection_trackless_sender_stats.html]
 skip-if = (android_version == '18') # android(Bug 1189784, timeouts on 4.3 emulator)
 [test_peerConnection_verifyDescriptions.html]
 skip-if = (android_version == '18')
 [test_fingerprinting_resistance.html]
 [test_getUserMedia_nonDefaultRate.html]
new file mode 100644
--- /dev/null
+++ b/dom/media/webrtc/MediaTransportChild.h
@@ -0,0 +1,38 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef _MTRANSPORTCHILD_H__
+#define _MTRANSPORTCHILD_H__
+
+#include "mozilla/dom/PMediaTransportChild.h"
+
+namespace mozilla {
+class MediaTransportHandlerIPC;
+
+class MediaTransportChild : public dom::PMediaTransportChild {
+ public:
+#ifdef MOZ_WEBRTC
+  explicit MediaTransportChild(MediaTransportHandlerIPC* aUser);
+  virtual ~MediaTransportChild();
+  mozilla::ipc::IPCResult RecvOnCandidate(const string& transportId,
+                                          const CandidateInfo& candidateInfo);
+  mozilla::ipc::IPCResult RecvOnAlpnNegotiated(const string& alpn);
+  mozilla::ipc::IPCResult RecvOnGatheringStateChange(const int& state);
+  mozilla::ipc::IPCResult RecvOnConnectionStateChange(const int& state);
+  mozilla::ipc::IPCResult RecvOnPacketReceived(const string& transportId,
+                                               const MediaPacket& packet);
+  mozilla::ipc::IPCResult RecvOnEncryptedSending(const string& transportId,
+                                                 const MediaPacket& packet);
+  mozilla::ipc::IPCResult RecvOnStateChange(const string& transportId,
+                                            const int& state);
+  mozilla::ipc::IPCResult RecvOnRtcpStateChange(const string& transportId,
+                                                const int& state);
+
+ private:
+  RefPtr<MediaTransportHandlerIPC> mUser;
+#endif //MOZ_WEBRTC
+};
+
+}  // namespace mozilla
+#endif
new file mode 100644
--- /dev/null
+++ b/dom/media/webrtc/MediaTransportParent.h
@@ -0,0 +1,64 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef _MTRANSPORTHANDLER_PARENT_H__
+#define _MTRANSPORTHANDLER_PARENT_H__
+
+#include "mozilla/dom/PMediaTransportParent.h"
+#include <memory>
+
+namespace mozilla {
+
+class MediaTransportParent : public dom::PMediaTransportParent {
+ public:
+#ifdef MOZ_WEBRTC
+  MediaTransportParent();
+  virtual ~MediaTransportParent();
+
+  mozilla::ipc::IPCResult RecvGetIceLog(const nsCString& pattern,
+                                        GetIceLogResolver&& aResolve);
+  mozilla::ipc::IPCResult RecvClearIceLog();
+  mozilla::ipc::IPCResult RecvEnterPrivateMode();
+  mozilla::ipc::IPCResult RecvExitPrivateMode();
+  mozilla::ipc::IPCResult RecvCreateIceCtx(
+      const string& name, nsTArray<RTCIceServer>&& iceServers,
+      const RTCIceTransportPolicy& icePolicy);
+  mozilla::ipc::IPCResult RecvSetProxyServer(const PBrowserOrId& browserOrId,
+                                             const nsCString& alpn);
+  mozilla::ipc::IPCResult RecvEnsureProvisionalTransport(
+      const string& transportId, const string& localUfrag,
+      const string& localPwd, const int& componentCount);
+  mozilla::ipc::IPCResult RecvStartIceGathering(
+      const bool& defaultRouteOnly, const net::NrIceStunAddrArray& stunAddrs);
+  mozilla::ipc::IPCResult RecvActivateTransport(
+      const string& transportId, const string& localUfrag,
+      const string& localPwd, const int& componentCount,
+      const string& remoteUfrag, const string& remotePwd,
+      nsTArray<uint8_t>&& keyDer, nsTArray<uint8_t>&& certDer,
+      const int& authType, const bool& dtlsClient,
+      const DtlsDigestList& digests, const bool& privacyRequested);
+  mozilla::ipc::IPCResult RecvRemoveTransportsExcept(
+      const StringVector& transportIds);
+  mozilla::ipc::IPCResult RecvStartIceChecks(const bool& isControlling,
+                                             const bool& isOfferer,
+                                             const StringVector& iceOptions);
+  mozilla::ipc::IPCResult RecvSendPacket(const string& transportId,
+                                         const MediaPacket& packet);
+  mozilla::ipc::IPCResult RecvAddIceCandidate(const string& transportId,
+                                              const string& candidate);
+  mozilla::ipc::IPCResult RecvUpdateNetworkState(const bool& online);
+  mozilla::ipc::IPCResult RecvGetIceStats(
+      const string& transportId, const double& now,
+      const RTCStatsReportInternal& reportIn, GetIceStatsResolver&& aResolve);
+
+  void ActorDestroy(ActorDestroyReason aWhy);
+
+ private:
+  // Hide the sigslot/MediaTransportHandler stuff from IPC.
+  class Impl;
+  std::unique_ptr<Impl> mImpl;
+#endif //MOZ_WEBRTC
+};
+}  // namespace mozilla
+#endif  //_MTRANSPORTHANDLER_PARENT_H__
new file mode 100644
--- /dev/null
+++ b/dom/media/webrtc/PMediaTransport.ipdl
@@ -0,0 +1,100 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+include protocol PSocketProcessBridge;
+
+#ifdef MOZ_WEBRTC
+include PBrowserOrId;
+
+// ParamTraits stuff for generated code and other classes we don't want to change
+include "mozilla/media/webrtc/WebrtcIPCTraits.h";
+using StringVector from "mozilla/media/webrtc/WebrtcIPCTraits.h";
+using CandidateInfo from "mozilla/media/webrtc/WebrtcIPCTraits.h";
+using DtlsDigestList from "mozilla/media/webrtc/WebrtcIPCTraits.h";
+using std::string from "ipc/IPCMessageUtils.h";
+using struct mozilla::dom::RTCStatsReportInternal from "mozilla/dom/RTCStatsReportBinding.h";
+using struct mozilla::dom::MovableRTCStatsReportInternal from "mozilla/media/webrtc/WebrtcGlobal.h";
+using WebrtcGlobalLog from "mozilla/media/webrtc/WebrtcGlobal.h";
+using mozilla::dom::RTCIceServer from "mozilla/dom/RTCConfigurationBinding.h";
+using mozilla::dom::RTCIceTransportPolicy from "mozilla/dom/RTCConfigurationBinding.h";
+
+// ParamTraits stuff for our own classes
+using MediaPacket from "mtransport/mediapacket.h";
+using net::NrIceStunAddrArray from "mozilla/net/PStunAddrsParams.h";
+#endif // MOZ_WEBRTC
+
+namespace mozilla {
+namespace dom {
+
+async protocol PMediaTransport {
+  manager PSocketProcessBridge;
+
+parent:
+  async __delete__();
+
+#ifdef MOZ_WEBRTC
+  async GetIceLog(nsCString pattern) returns (WebrtcGlobalLog loglines);
+  async ClearIceLog();
+  async EnterPrivateMode();
+  async ExitPrivateMode();
+
+  async CreateIceCtx(string name,
+                     RTCIceServer[] iceServers,
+                     RTCIceTransportPolicy icePolicy);
+
+  async SetProxyServer(PBrowserOrId browserOrId,
+                       nsCString alpn);
+
+  async EnsureProvisionalTransport(string transportId,
+                                   string localUfrag,
+                                   string localPwd,
+                                   int componentCount);
+
+  async StartIceGathering(bool defaultRouteOnly,
+                          NrIceStunAddrArray stunAddrs);
+
+  async ActivateTransport(string transportId,
+                          string localUfrag,
+                          string localPwd,
+                          int componentCount,
+                          string remoteUfrag,
+                          string remotePwd,
+                          uint8_t[] keyDer,
+                          uint8_t[] certDer,
+                          int authType,
+                          bool dtlsClient,
+                          DtlsDigestList digests,
+                          bool privacyRequested);
+
+  async RemoveTransportsExcept(StringVector transportIds);
+
+  async StartIceChecks(bool isControlling,
+                       bool isOfferer,
+                       StringVector iceOptions);
+
+  async SendPacket(string transportId, MediaPacket packet);
+
+  async AddIceCandidate(string transportId,
+                        string candidate);
+
+  async UpdateNetworkState(bool online);
+
+  async GetIceStats(string transportId,
+                    double now,
+                    RTCStatsReportInternal reportIn) returns (MovableRTCStatsReportInternal reportOut);
+
+child:
+  async OnCandidate(string transportId, CandidateInfo candidateInfo);
+  async OnAlpnNegotiated(string alpn);
+  async OnGatheringStateChange(int state);
+  async OnConnectionStateChange(int state);
+  async OnPacketReceived(string transportId, MediaPacket packet);
+  async OnEncryptedSending(string transportId, MediaPacket packet);
+  async OnStateChange(string transportId, int state);
+  async OnRtcpStateChange(string transportId, int state);
+
+#endif // MOZ_WEBRTC
+};
+} // namespace dom
+} // namespace mozilla
--- a/dom/media/webrtc/RTCCertificate.cpp
+++ b/dom/media/webrtc/RTCCertificate.cpp
@@ -10,16 +10,17 @@
 #include "cert.h"
 #include "jsapi.h"
 #include "mozilla/dom/CryptoKey.h"
 #include "mozilla/dom/RTCCertificateBinding.h"
 #include "mozilla/dom/WebCryptoCommon.h"
 #include "mozilla/dom/WebCryptoTask.h"
 #include "mozilla/Move.h"
 #include "mozilla/Sprintf.h"
+#include "mtransport/dtlsidentity.h"
 
 #include <cstdio>
 
 namespace mozilla {
 namespace dom {
 
 #define RTCCERTIFICATE_SC_VERSION 0x00000001
 
--- a/dom/media/webrtc/RTCCertificate.h
+++ b/dom/media/webrtc/RTCCertificate.h
@@ -14,21 +14,21 @@
 #include "sslt.h"
 #include "ScopedNSSTypes.h"
 
 #include "mozilla/ErrorResult.h"
 #include "mozilla/UniquePtr.h"
 #include "mozilla/RefPtr.h"
 #include "mozilla/dom/CryptoKey.h"
 #include "mozilla/dom/RTCCertificateBinding.h"
-#include "mtransport/dtlsidentity.h"
 #include "js/StructuredClone.h"
 #include "js/TypeDecls.h"
 
 namespace mozilla {
+class DtlsIdentity;
 namespace dom {
 
 class ObjectOrString;
 
 class RTCCertificate final : public nsISupports, public nsWrapperCache {
  public:
   NS_DECL_CYCLE_COLLECTING_ISUPPORTS
   NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(RTCCertificate)
--- a/dom/media/webrtc/WebrtcGlobal.h
+++ b/dom/media/webrtc/WebrtcGlobal.h
@@ -9,16 +9,34 @@
 #include "mozilla/dom/BindingDeclarations.h"
 #include "mozilla/dom/RTCStatsReportBinding.h"
 #include "nsAutoPtr.h"
 
 typedef mozilla::dom::RTCStatsReportInternal StatsReport;
 typedef nsTArray<nsAutoPtr<StatsReport>> RTCReports;
 typedef mozilla::dom::Sequence<nsString> WebrtcGlobalLog;
 
+namespace mozilla {
+namespace dom {
+// webidl dictionaries don't have move semantics, which is something that ipdl
+// needs for async returns. So, we create a "moveable" subclass that just
+// copies. _Really_ lame, but it gets the job done.
+struct MovableRTCStatsReportInternal : public RTCStatsReportInternal {
+  MovableRTCStatsReportInternal() = default;
+  explicit MovableRTCStatsReportInternal(RTCStatsReportInternal&& aReport) {
+    RTCStatsReportInternal::operator=(aReport);
+  }
+  explicit MovableRTCStatsReportInternal(
+      const RTCStatsReportInternal& aReport) {
+    RTCStatsReportInternal::operator=(aReport);
+  }
+};
+}  // namespace dom
+}  // namespace mozilla
+
 namespace IPC {
 
 template <typename T>
 struct ParamTraits<mozilla::dom::Sequence<T>> {
   typedef mozilla::dom::Sequence<T> paramType;
 
   static void Write(Message* aMsg, const paramType& aParam) {
     WriteParam(aMsg, static_cast<const FallibleTArray<T>&>(aParam));
@@ -46,16 +64,31 @@ struct ParamTraits<mozilla::dom::RTCStat
 template <>
 struct ParamTraits<mozilla::dom::RTCIceCandidateType>
     : public ContiguousEnumSerializer<
           mozilla::dom::RTCIceCandidateType,
           mozilla::dom::RTCIceCandidateType::Host,
           mozilla::dom::RTCIceCandidateType::EndGuard_> {};
 
 template <>
+struct ParamTraits<mozilla::dom::MovableRTCStatsReportInternal> {
+  typedef mozilla::dom::MovableRTCStatsReportInternal paramType;
+  static void Write(Message* aMsg, const paramType& aParam) {
+    WriteParam(
+        aMsg, static_cast<const mozilla::dom::RTCStatsReportInternal&>(aParam));
+  }
+  static bool Read(const Message* aMsg, PickleIterator* aIter,
+                   paramType* aResult) {
+    return ReadParam(
+        aMsg, aIter,
+        static_cast<mozilla::dom::RTCStatsReportInternal*>(aResult));
+  }
+};
+
+template <>
 struct ParamTraits<mozilla::dom::RTCStatsReportInternal> {
   typedef mozilla::dom::RTCStatsReportInternal paramType;
 
   static void Write(Message* aMsg, const paramType& aParam) {
     WriteParam(aMsg, aParam.mClosed);
     WriteParam(aMsg, aParam.mCodecStats);
     WriteParam(aMsg, aParam.mIceCandidatePairStats);
     WriteParam(aMsg, aParam.mIceCandidateStats);
@@ -263,39 +296,47 @@ struct ParamTraits<mozilla::dom::RTCIceC
   }
 };
 
 static void WriteRTCRtpStreamStats(
     Message* aMsg, const mozilla::dom::RTCRtpStreamStats& aParam) {
   WriteParam(aMsg, aParam.mBitrateMean);
   WriteParam(aMsg, aParam.mBitrateStdDev);
   WriteParam(aMsg, aParam.mCodecId);
+  WriteParam(aMsg, aParam.mFirCount);
   WriteParam(aMsg, aParam.mFramerateMean);
   WriteParam(aMsg, aParam.mFramerateStdDev);
+  WriteParam(aMsg, aParam.mKind);
+  WriteParam(aMsg, aParam.mLocalId);
   WriteParam(aMsg, aParam.mMediaTrackId);
   WriteParam(aMsg, aParam.mMediaType);
-  WriteParam(aMsg, aParam.mKind);
+  WriteParam(aMsg, aParam.mNackCount);
+  WriteParam(aMsg, aParam.mPliCount);
+  WriteParam(aMsg, aParam.mQpSum);
   WriteParam(aMsg, aParam.mRemoteId);
-  WriteParam(aMsg, aParam.mLocalId);
   WriteParam(aMsg, aParam.mSsrc);
   WriteParam(aMsg, aParam.mTransportId);
 }
 
 static bool ReadRTCRtpStreamStats(const Message* aMsg, PickleIterator* aIter,
                                   mozilla::dom::RTCRtpStreamStats* aResult) {
   if (!ReadParam(aMsg, aIter, &(aResult->mBitrateMean)) ||
       !ReadParam(aMsg, aIter, &(aResult->mBitrateStdDev)) ||
       !ReadParam(aMsg, aIter, &(aResult->mCodecId)) ||
+      !ReadParam(aMsg, aIter, &(aResult->mFirCount)) ||
       !ReadParam(aMsg, aIter, &(aResult->mFramerateMean)) ||
       !ReadParam(aMsg, aIter, &(aResult->mFramerateStdDev)) ||
+      !ReadParam(aMsg, aIter, &(aResult->mKind)) ||
+      !ReadParam(aMsg, aIter, &(aResult->mLocalId)) ||
       !ReadParam(aMsg, aIter, &(aResult->mMediaTrackId)) ||
       !ReadParam(aMsg, aIter, &(aResult->mMediaType)) ||
-      !ReadParam(aMsg, aIter, &(aResult->mKind)) ||
+      !ReadParam(aMsg, aIter, &(aResult->mNackCount)) ||
+      !ReadParam(aMsg, aIter, &(aResult->mPliCount)) ||
+      !ReadParam(aMsg, aIter, &(aResult->mQpSum)) ||
       !ReadParam(aMsg, aIter, &(aResult->mRemoteId)) ||
-      !ReadParam(aMsg, aIter, &(aResult->mLocalId)) ||
       !ReadParam(aMsg, aIter, &(aResult->mSsrc)) ||
       !ReadParam(aMsg, aIter, &(aResult->mTransportId))) {
     return false;
   }
 
   return true;
 }
 
@@ -303,68 +344,62 @@ template <>
 struct ParamTraits<mozilla::dom::RTCInboundRTPStreamStats> {
   typedef mozilla::dom::RTCInboundRTPStreamStats paramType;
 
   static void Write(Message* aMsg, const paramType& aParam) {
     WriteParam(aMsg, aParam.mBytesReceived);
     WriteParam(aMsg, aParam.mDiscardedPackets);
     WriteParam(aMsg, aParam.mFramesDecoded);
     WriteParam(aMsg, aParam.mJitter);
-    WriteParam(aMsg, aParam.mRoundTripTime);
     WriteParam(aMsg, aParam.mPacketsLost);
     WriteParam(aMsg, aParam.mPacketsReceived);
+    WriteParam(aMsg, aParam.mRoundTripTime);
     WriteRTCRtpStreamStats(aMsg, aParam);
     WriteRTCStats(aMsg, aParam);
   }
 
   static bool Read(const Message* aMsg, PickleIterator* aIter,
                    paramType* aResult) {
     if (!ReadParam(aMsg, aIter, &(aResult->mBytesReceived)) ||
         !ReadParam(aMsg, aIter, &(aResult->mDiscardedPackets)) ||
         !ReadParam(aMsg, aIter, &(aResult->mFramesDecoded)) ||
         !ReadParam(aMsg, aIter, &(aResult->mJitter)) ||
-        !ReadParam(aMsg, aIter, &(aResult->mRoundTripTime)) ||
         !ReadParam(aMsg, aIter, &(aResult->mPacketsLost)) ||
         !ReadParam(aMsg, aIter, &(aResult->mPacketsReceived)) ||
+        !ReadParam(aMsg, aIter, &(aResult->mRoundTripTime)) ||
         !ReadRTCRtpStreamStats(aMsg, aIter, aResult) ||
         !ReadRTCStats(aMsg, aIter, aResult)) {
       return false;
     }
 
     return true;
   }
 };
 
 template <>
 struct ParamTraits<mozilla::dom::RTCOutboundRTPStreamStats> {
   typedef mozilla::dom::RTCOutboundRTPStreamStats paramType;
 
   static void Write(Message* aMsg, const paramType& aParam) {
     WriteParam(aMsg, aParam.mBytesSent);
     WriteParam(aMsg, aParam.mDroppedFrames);
+    WriteParam(aMsg, aParam.mFramesEncoded);
     WriteParam(aMsg, aParam.mPacketsSent);
     WriteParam(aMsg, aParam.mTargetBitrate);
-    WriteParam(aMsg, aParam.mFramesEncoded);
-    WriteParam(aMsg, aParam.mFirCount);
-    WriteParam(aMsg, aParam.mNackCount);
-    WriteParam(aMsg, aParam.mPliCount);
     WriteRTCRtpStreamStats(aMsg, aParam);
     WriteRTCStats(aMsg, aParam);
   }
 
   static bool Read(const Message* aMsg, PickleIterator* aIter,
                    paramType* aResult) {
     if (!ReadParam(aMsg, aIter, &(aResult->mBytesSent)) ||
         !ReadParam(aMsg, aIter, &(aResult->mDroppedFrames)) ||
+        !ReadParam(aMsg, aIter, &(aResult->mFramesEncoded)) ||
         !ReadParam(aMsg, aIter, &(aResult->mPacketsSent)) ||
         !ReadParam(aMsg, aIter, &(aResult->mTargetBitrate)) ||
-        !ReadParam(aMsg, aIter, &(aResult->mFramesEncoded)) ||
-        !ReadParam(aMsg, aIter, &(aResult->mFirCount)) ||
-        !ReadParam(aMsg, aIter, &(aResult->mNackCount)) ||
-        !ReadParam(aMsg, aIter, &(aResult->mPliCount)) ||
         !ReadRTCRtpStreamStats(aMsg, aIter, aResult) ||
         !ReadRTCStats(aMsg, aIter, aResult)) {
       return false;
     }
 
     return true;
   }
 };
new file mode 100644
--- /dev/null
+++ b/dom/media/webrtc/WebrtcIPCTraits.h
@@ -0,0 +1,167 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef _WEBRTC_IPC_TRAITS_H_
+#define _WEBRTC_IPC_TRAITS_H_
+
+#include "ipc/IPCMessageUtils.h"
+#include "mozilla/dom/BindingDeclarations.h"
+#include "mozilla/dom/RTCConfigurationBinding.h"
+#include "mozilla/media/webrtc/WebrtcGlobal.h"
+#include "mozilla/dom/CandidateInfo.h"
+#include "mozilla/MacroForEach.h"
+#include "mtransport/transportlayerdtls.h"
+#include <vector>
+
+namespace mozilla {
+typedef std::vector<std::string> StringVector;
+}
+
+namespace IPC {
+
+template <typename T>
+struct ParamTraits<std::vector<T>> {
+  typedef std::vector<T> paramType;
+
+  static void Write(Message* aMsg, const paramType& aParam) {
+    aMsg->WriteUInt32(aParam.size());
+    for (const T& elem : aParam) {
+      WriteParam(aMsg, elem);
+    }
+  }
+
+  static bool Read(const Message* aMsg, PickleIterator* aIter,
+                   paramType* aResult) {
+    uint32_t size;
+    if (!aMsg->ReadUInt32(aIter, &size)) {
+      return false;
+    }
+    while (size--) {
+      // Only works when T is movable. Meh.
+      T elem;
+      if (!ReadParam(aMsg, aIter, &elem)) {
+        return false;
+      }
+      aResult->emplace_back(std::move(elem));
+    }
+
+    return true;
+  }
+};
+
+template <>
+struct ParamTraits<mozilla::dom::OwningStringOrStringSequence> {
+  typedef mozilla::dom::OwningStringOrStringSequence paramType;
+
+  // Ugh. OwningStringOrStringSequence already has this enum, but it is
+  // private generated code. So we have to re-create it.
+  enum Type { kUninitialized, kString, kStringSequence };
+
+  static void Write(Message* aMsg, const paramType& aParam) {
+    if (aParam.IsString()) {
+      aMsg->WriteInt16(kString);
+      WriteParam(aMsg, aParam.GetAsString());
+    } else if (aParam.IsStringSequence()) {
+      aMsg->WriteInt16(kStringSequence);
+      WriteParam(aMsg, aParam.GetAsStringSequence());
+    } else {
+      aMsg->WriteInt16(kUninitialized);
+    }
+  }
+
+  static bool Read(const Message* aMsg, PickleIterator* aIter,
+                   paramType* aResult) {
+    int16_t type;
+    if (!aMsg->ReadInt16(aIter, &type)) {
+      return false;
+    }
+
+    switch (type) {
+      case kUninitialized:
+        aResult->Uninit();
+        return true;
+      case kString:
+        return ReadParam(aMsg, aIter, &aResult->SetAsString());
+      case kStringSequence:
+        return ReadParam(aMsg, aIter, &aResult->SetAsStringSequence());
+    }
+
+    return false;
+  }
+};
+
+template <typename T>
+struct WebidlEnumSerializer
+    : public ContiguousEnumSerializer<T, T(0), T::EndGuard_> {};
+
+template <>
+struct ParamTraits<mozilla::dom::RTCIceCredentialType>
+    : public WebidlEnumSerializer<mozilla::dom::RTCIceCredentialType> {};
+
+template <>
+struct ParamTraits<mozilla::dom::RTCIceTransportPolicy>
+    : public WebidlEnumSerializer<mozilla::dom::RTCIceTransportPolicy> {};
+
+// A couple of recursive helper functions, allows syntax like:
+// WriteParams(aMsg, aParam.foo, aParam.bar, aParam.baz)
+// ReadParams(aMsg, aIter, aParam.foo, aParam.bar, aParam.baz)
+
+// Base case
+static void WriteParams(Message* aMsg) {}
+
+template <typename T0, typename... Tn>
+static void WriteParams(Message* aMsg, const T0& aArg,
+                        const Tn&... aRemainingArgs) {
+  WriteParam(aMsg, aArg);                // Write first arg
+  WriteParams(aMsg, aRemainingArgs...);  // Recurse for the rest
+}
+
+// Base case
+static bool ReadParams(const Message* aMsg, PickleIterator* aIter) {
+  return true;
+}
+
+template <typename T0, typename... Tn>
+static bool ReadParams(const Message* aMsg, PickleIterator* aIter, T0& aArg,
+                       Tn&... aRemainingArgs) {
+  return ReadParam(aMsg, aIter, &aArg) &&             // Read first arg
+         ReadParams(aMsg, aIter, aRemainingArgs...);  // Recurse for the rest
+}
+
+// Macros that allow syntax like:
+// DEFINE_IPC_SERIALIZER_WITH_FIELDS(SomeType, member1, member2, member3)
+// Makes sure that serialize/deserialize code do the same members in the same
+// order.
+#define ACCESS_PARAM_FIELD(Field) aParam.Field
+
+#define DEFINE_IPC_SERIALIZER_WITH_FIELDS(Type, ...)                         \
+  template <>                                                                \
+  struct ParamTraits<Type> {                                                 \
+    typedef Type paramType;                                                  \
+    static void Write(Message* aMsg, const paramType& aParam) {              \
+      WriteParams(aMsg, MOZ_FOR_EACH_SEPARATED(ACCESS_PARAM_FIELD, (, ), (), \
+                                               (__VA_ARGS__)));              \
+    }                                                                        \
+                                                                             \
+    static bool Read(const Message* aMsg, PickleIterator* aIter,             \
+                     paramType* aResult) {                                   \
+      paramType& aParam = *aResult;                                          \
+      return ReadParams(aMsg, aIter,                                         \
+                        MOZ_FOR_EACH_SEPARATED(ACCESS_PARAM_FIELD, (, ), (), \
+                                               (__VA_ARGS__)));              \
+    }                                                                        \
+  };
+
+DEFINE_IPC_SERIALIZER_WITH_FIELDS(mozilla::dom::RTCIceServer, mCredential,
+                                  mCredentialType, mUrl, mUrls, mUsername)
+
+DEFINE_IPC_SERIALIZER_WITH_FIELDS(mozilla::CandidateInfo, mCandidate,
+                                  mDefaultHostRtp, mDefaultPortRtp,
+                                  mDefaultHostRtcp, mDefaultPortRtcp)
+
+DEFINE_IPC_SERIALIZER_WITH_FIELDS(mozilla::DtlsDigest, algorithm_, value_)
+
+}  // namespace IPC
+
+#endif  // _WEBRTC_IPC_TRAITS_H_
--- a/dom/media/webrtc/moz.build
+++ b/dom/media/webrtc/moz.build
@@ -48,16 +48,20 @@ if CONFIG['MOZ_WEBRTC']:
         '/dom/media',
         '/media/libyuv/libyuv/include',
         '/media/webrtc/signaling/src/common',
         '/media/webrtc/signaling/src/common/browser_logging',
         '/media/webrtc/trunk',
         '/media/webrtc/trunk/webrtc'
     ]
 
+PREPROCESSED_IPDL_SOURCES += [
+    'PMediaTransport.ipdl',
+]
+
 XPIDL_SOURCES += [
     'nsITabSource.idl'
 ]
 
 UNIFIED_SOURCES += [
     'MediaEngineDefault.cpp',
     'MediaEngineSource.cpp',
     'MediaTrackConstraints.cpp',
--- a/dom/media/webvtt/vtt.jsm
+++ b/dom/media/webvtt/vtt.jsm
@@ -25,16 +25,19 @@ var EXPORTED_SYMBOLS = ["WebVTT"];
  * 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.
  */
 
 const {Services} = ChromeUtils.import('resource://gre/modules/Services.jsm');
 const {XPCOMUtils} = ChromeUtils.import("resource://gre/modules/XPCOMUtils.jsm");
 
+XPCOMUtils.defineLazyPreferenceGetter(this, "supportPseudo",
+                                      "media.webvtt.pseudo.enabled", false);
+
 (function(global) {
 
   var _objCreate = Object.create || (function() {
     function F() {}
     return function(o) {
       if (arguments.length !== 1) {
         throw new Error('Object.create shim only accepts one parameter.');
       }
@@ -488,132 +491,203 @@ const {XPCOMUtils} = ChromeUtils.import(
 
   StyleBox.prototype.formatStyle = function(val, unit) {
     return val === 0 ? 0 : val + unit;
   };
 
   XPCOMUtils.defineLazyPreferenceGetter(StyleBox.prototype, "supportPseudo",
                                         "media.webvtt.pseudo.enabled", false);
 
-  // Constructs the computed display state of the cue (a div). Places the div
-  // into the overlay which should be a block level element (usually a div).
-  function CueStyleBox(window, cue, styleOptions) {
-    var isIE8 = (typeof navigator !== "undefined") &&
-      (/MSIE\s8\.0/).test(navigator.userAgent);
-
-    var isFirefoxSupportPseudo = (/firefox/i.test(window.navigator.userAgent))
-          && this.supportPseudo;
-    var color = "rgba(255, 255, 255, 1)";
-    var backgroundColor = "rgba(0, 0, 0, 0.8)";
-
-    if (isIE8) {
-      color = "rgb(255, 255, 255)";
-      backgroundColor = "rgb(0, 0, 0)";
-    }
-
-    StyleBox.call(this);
-    this.cue = cue;
-
-    // Parse our cue's text into a DOM tree rooted at 'cueDiv'. This div will
-    // have inline positioning and will function as the cue background box.
-    if (isFirefoxSupportPseudo) {
-      this.cueDiv = parseContent(window, cue.text, PARSE_CONTENT_MODE.PSUEDO_CUE);
-    } else {
-      this.cueDiv = parseContent(window, cue.text, PARSE_CONTENT_MODE.NORMAL_CUE);
-    }
-    var styles = {
-      color: color,
-      backgroundColor: backgroundColor,
-      display: "inline",
-      font: styleOptions.font,
-      whiteSpace: "pre-line",
-    };
-    if (isFirefoxSupportPseudo) {
-      delete styles.color;
-      delete styles.backgroundColor;
-      delete styles.font;
-      delete styles.whiteSpace;
+  // TODO(alwu): remove StyleBox and change other style box to class-based.
+  class StyleBoxBase {
+    applyStyles(styles, div) {
+      div = div || this.div;
+      Object.assign(div.style, styles);
     }
 
-    if (!isIE8) {
-      styles.writingMode = cue.vertical === "" ? "horizontal-tb"
-                                               : cue.vertical === "lr" ? "vertical-lr"
-                                                                       : "vertical-rl";
-      styles.unicodeBidi = "plaintext";
+    formatStyle(val, unit) {
+      return val === 0 ? 0 : val + unit;
     }
-    this.applyStyles(styles, this.cueDiv);
-
-    // Create an absolutely positioned div that will be used to position the cue
-    // div.
-    styles = {
-      position: "absolute",
-      textAlign: cue.align,
-      font: styleOptions.font,
-    };
-
-    this.div = window.document.createElement("div");
-    this.applyStyles(styles);
-
-    this.div.appendChild(this.cueDiv);
+  }
 
-    // Calculate the distance from the reference edge of the viewport to the text
-    // position of the cue box. The reference edge will be resolved later when
-    // the box orientation styles are applied.
-    function convertCuePostionToPercentage(cuePosition) {
-      if (cuePosition === "auto") {
-        return 50;
+  // Constructs the computed display state of the cue (a div). Places the div
+  // into the overlay which should be a block level element (usually a div).
+  class CueStyleBox extends StyleBoxBase {
+    constructor(window, cue, containerBox) {
+      super();
+      this.cue = cue;
+      this.div = window.document.createElement("div");
+      this.cueDiv = parseContent(window, cue.text, supportPseudo ?
+        PARSE_CONTENT_MODE.PSUEDO_CUE : PARSE_CONTENT_MODE.NORMAL_CUE);
+      this.div.appendChild(this.cueDiv);
+
+      this.fontSize = this._getFontSize(containerBox.height);
+      // As pseudo element won't inherit the parent div's style, so we have to
+      // set the font size explicitly.
+      if (supportPseudo) {
+        this.cueDiv.style.setProperty("--cue-font-size", this.fontSize);
+      } else {
+        this._applyNonPseudoCueStyles();
       }
-      return cuePosition;
-    }
-    var textPos = 0;
-    let postionPercentage = convertCuePostionToPercentage(cue.position);
-    switch (cue.computedPositionAlign) {
-      // TODO : modify these fomula to follow the spec, see bug 1277437.
-      case "line-left":
-        textPos = postionPercentage;
-        break;
-      case "center":
-        textPos = postionPercentage - (cue.size / 2);
-        break;
-      case "line-right":
-        textPos = postionPercentage - cue.size;
-        break;
+      this.applyStyles(this._getNodeDefaultStyles(cue));
     }
 
-    // Horizontal box orientation; textPos is the distance from the left edge of the
-    // area to the left edge of the box and cue.size is the distance extending to
-    // the right from there.
-    if (cue.vertical === "") {
-      this.applyStyles({
-        left:  this.formatStyle(textPos, "%"),
-        width: this.formatStyle(cue.size, "%")
-      });
-    // Vertical box orientation; textPos is the distance from the top edge of the
-    // area to the top edge of the box and cue.size is the height extending
-    // downwards from there.
-    } else {
-      this.applyStyles({
-        top: this.formatStyle(textPos, "%"),
-        height: this.formatStyle(cue.size, "%")
-      });
-    }
-
-    this.move = function(box) {
+    move(box) {
       this.applyStyles({
         top: this.formatStyle(box.top, "px"),
         bottom: this.formatStyle(box.bottom, "px"),
         left: this.formatStyle(box.left, "px"),
         right: this.formatStyle(box.right, "px"),
         height: this.formatStyle(box.height, "px"),
         width: this.formatStyle(box.width, "px")
       });
-    };
+    }
+
+    /**
+     * Following methods are private functions, should not use them outside this
+     * class.
+     */
+    _getFontSize(renderingAreaHeight) {
+      // In https://www.w3.org/TR/webvtt1/#applying-css-properties, the spec
+      // said the font size is '5vh', which means 5% of the viewport height.
+      // However, if we use 'vh' as a basic unit, it would eventually become
+      // 5% of screen height, instead of video's viewport height. Therefore, we
+      // have to use 'px' here to make sure we have the correct font size.
+      return renderingAreaHeight * 0.05 + "px";
+    }
+
+    _applyNonPseudoCueStyles() {
+      // If cue div is not a pseudo element, we should set the default css style
+      // for it, the reason we need to set these attributes to cueDiv is because
+      // if we set background on the root node directly, if would cause filling
+      // too large area for the background color as the size of root node won't
+      // be adjusted by cue size.
+      this.applyStyles({
+        "color": "rgba(255, 255, 255, 1)",
+        "white-space": "pre-line",
+        "font": this.fontSize + " sans-serif",
+        "background-color": "rgba(0, 0, 0, 0.8)",
+        "display": "inline",
+      }, this.cueDiv);
+    }
+
+    // spec https://www.w3.org/TR/webvtt1/#applying-css-properties
+    _getNodeDefaultStyles(cue) {
+      let styles = {
+        "position": "absolute",
+        "unicode-bidi": "plaintext",
+        "overflow-wrap": "break-word",
+        // "text-wrap": "balance", (we haven't supported this CSS attribute yet)
+        "font": this.fontSize + " sans-serif",
+        "white-space": "pre-line",
+        "text-align": cue.align,
+      }
+
+      this._processCueSetting(cue, styles);
+      return styles;
+    }
+
+    // spec https://www.w3.org/TR/webvtt1/#processing-cue-settings
+    _processCueSetting(cue, styles) {
+      // spec 7.2.1, calculate 'writing-mode'.
+      styles["writing-mode"] = this._getCueWritingMode(cue);
+
+      // spec 7.2.2 ~ 7.2.4, calculate 'width' and 'height'.
+      const {width, height} = this._getCueWidthAndHeight(cue);
+      styles["width"] = width;
+      styles["height"] = height;
+
+      // spec 7.2.5 ~ 7.2.7, calculate 'left' and 'top'.
+      const {left, top} = this._getCueLeftAndTop(cue);
+      styles["left"] = left;
+      styles["top"] = top;
+    }
+
+    _getCueWritingMode(cue) {
+      if (cue.vertical == "") {
+        return "horizontal-tb";
+      }
+      return cue.vertical == "lr" ? "vertical-lr" : "vertical-rl";
+    }
+
+    _getCueWidthAndHeight(cue) {
+      // spec 7.2.2, determine the value of maximum size for cue as per the
+      // appropriate rules from the following list.
+      let maximumSize;
+      let computedPosition = cue.computedPosition;
+      switch (cue.computedPositionAlign) {
+        case "line-left":
+          maximumSize = 100 - computedPosition;
+          break;
+        case "line-right":
+          maximumSize = computedPosition;
+          break;
+        case "center":
+          maximumSize = computedPosition <= 50 ?
+            computedPosition * 2 : (100 - computedPosition) * 2;
+          break;
+      }
+      const size = Math.min(cue.size, maximumSize);
+      return cue.vertical == "" ? {
+        width: size + "%",
+        height: "auto",
+      } : {
+        width: "auto",
+        height: size + "%",
+      };
+    }
+
+    _getCueLeftAndTop(cue) {
+      // spec 7.2.5, determine the value of x-position or y-position for cue as
+      // per the appropriate rules from the following list.
+      let xPosition = 0.0, yPosition = 0.0;
+      const isWritingDirectionHorizontal = cue.vertical == "";
+      switch (cue.computedPositionAlign) {
+        case "line-left":
+          if (isWritingDirectionHorizontal) {
+            xPosition = cue.computedPosition;
+          } else {
+            yPosition = cue.computedPosition;
+          }
+          break;
+        case "center":
+          if (isWritingDirectionHorizontal) {
+            xPosition = cue.computedPosition - (cue.size / 2);
+          } else {
+            yPosition = cue.computedPosition - (cue.size / 2);
+          }
+          break;
+        case "line-right":
+          if (isWritingDirectionHorizontal) {
+            xPosition = cue.computedPosition - cue.size;
+          } else {
+            yPosition = cue.computedPosition - cue.size;
+          }
+          break;
+      }
+
+      // spec 7.2.6, determine the value of whichever of x-position or
+      // y-position is not yet calculated for cue as per the appropriate rules
+      // from the following list.
+      if (!cue.snapToLines) {
+        if (isWritingDirectionHorizontal) {
+          yPosition = cue.computedPosition;
+        } else {
+          xPosition = cue.computedPosition;
+        }
+      } else {
+        if (isWritingDirectionHorizontal) {
+          yPosition = 0;
+        } else {
+          xPosition = 0;
+        }
+      }
+      return { left: xPosition + "%", top: yPosition + "%"};
+    }
   }
-  CueStyleBox.prototype = _objCreate(StyleBox.prototype);
-  CueStyleBox.prototype.constructor = CueStyleBox;
 
   function RegionNodeBox(window, region, container) {
     StyleBox.call(this);
 
     var boxLineHeight = container.height * 0.0533 // 0.0533vh ? 5.33vh
     var boxHeight = boxLineHeight * region.lines;
     var boxWidth = container.width * region.width / 100; // convert percentage to px
 
@@ -682,19 +756,16 @@ const {XPCOMUtils} = ChromeUtils.import(
   }
   RegionCueStyleBox.prototype = _objCreate(StyleBox.prototype);
   RegionCueStyleBox.prototype.constructor = RegionCueStyleBox;
 
   // Represents the co-ordinates of an Element in a way that we can easily
   // compute things with such as if it overlaps or intersects with another Element.
   // Can initialize it with either a StyleBox or another BoxPosition.
   function BoxPosition(obj) {
-    var isIE8 = (typeof navigator !== "undefined") &&
-      (/MSIE\s8\.0/).test(navigator.userAgent);
-
     // Either a BoxPosition was passed in and we need to copy it, or a StyleBox
     // was passed in and we need to copy the results of 'getBoundingClientRect'
     // as the object returned is readonly. All co-ordinate values are in reference
     // to the viewport origin (top left).
     var lh, height, width, top;
     if (obj.div) {
       height = obj.div.offsetHeight;
       width = obj.div.offsetWidth;
@@ -713,20 +784,16 @@ const {XPCOMUtils} = ChromeUtils.import(
     }
     this.left = obj.left;
     this.right = obj.right;
     this.top = obj.top || top;
     this.height = obj.height || height;
     this.bottom = obj.bottom || (top + (obj.height || height));
     this.width = obj.width || width;
     this.lineHeight = lh !== undefined ? lh : obj.lineHeight;
-
-    if (isIE8 && !this.lineHeight) {
-      this.lineHeight = 13;
-    }
   }
 
   // Move the box along a particular axis. Optionally pass in an amount to move
   // the box. If no amount is passed then the default is the line height of the
   // box.
   BoxPosition.prototype.move = function(axis, toMove) {
     toMove = toMove !== undefined ? toMove : this.lineHeight;
     switch (axis) {
@@ -991,19 +1058,16 @@ const {XPCOMUtils} = ChromeUtils.import(
 
   WebVTT.convertCueToDOMTree = function(window, cuetext) {
     if (!window) {
       return null;
     }
     return parseContent(window, cuetext, PARSE_CONTENT_MODE.DOCUMENT_FRAGMENT);
   };
 
-  var FONT_SIZE_PERCENT = 0.05;
-  var FONT_STYLE = "sans-serif";
-
   // Runs the processing model over the cues and regions passed to it.
   // @param overlay A block level element (usually a div) that the computed cues
   //                and regions will be placed into.
   // @param controls  A Control bar element. Cues' position will be
   //                 affected and repositioned according to it.
   WebVTT.processCues = function(window, cues, overlay, controls) {
     if (!window || !cues || !overlay) {
       return null;
@@ -1051,21 +1115,17 @@ const {XPCOMUtils} = ChromeUtils.import(
     rootOfCues.style.position = "absolute";
     rootOfCues.style.left = "0";
     rootOfCues.style.right = "0";
     rootOfCues.style.top = "0";
     rootOfCues.style.bottom = "0";
     overlay.appendChild(rootOfCues);
 
     var boxPositions = [],
-        containerBox = BoxPosition.getSimpleBoxPosition(rootOfCues),
-        fontSize = Math.round(containerBox.height * FONT_SIZE_PERCENT * 100) / 100;
-    var styleOptions = {
-      font: fontSize + "px " + FONT_STYLE
-    };
+        containerBox = BoxPosition.getSimpleBoxPosition(rootOfCues);
 
     (function() {
       var styleBox, cue, controlBarBox;
 
       if (controlBarShown) {
         controlBarBox = BoxPosition.getSimpleBoxPosition(controlBar);
         // Add an empty output box that cover the same region as video control bar.
         boxPositions.push(controlBarBox);
@@ -1105,18 +1165,17 @@ const {XPCOMUtils} = ChromeUtils.import(
           }
 
           currentRegionNodeDiv.appendChild(styleBox.div);
           rootOfCues.appendChild(currentRegionNodeDiv);
           cue.displayState = styleBox.div;
           boxPositions.push(BoxPosition.getSimpleBoxPosition(currentRegionBox));
         } else {
           // Compute the intial position and styles of the cue div.
-          styleBox = new CueStyleBox(window, cue, styleOptions);
-          styleBox.cueDiv.style.setProperty("--cue-font-size", fontSize + "px");
+          styleBox = new CueStyleBox(window, cue, containerBox);
           rootOfCues.appendChild(styleBox.div);
 
           // Move the cue div to it's correct line position.
           moveBoxToLinePosition(window, styleBox, containerBox, boxPositions);
 
           // Remember the computed div so that we don't have to recompute it later
           // if we don't have too.
           cue.displayState = styleBox.div;
--- a/dom/network/UDPSocketChild.cpp
+++ b/dom/network/UDPSocketChild.cpp
@@ -176,17 +176,17 @@ UDPSocketChild::SendBinaryStream(const n
   NS_ENSURE_ARG(aStream);
 
   mozilla::ipc::AutoIPCStream autoStream;
   autoStream.Serialize(aStream, static_cast<mozilla::dom::ContentChild*>(
                                     gNeckoChild->Manager()));
 
   UDPSOCKET_LOG(
       ("%s: %s:%u", __FUNCTION__, PromiseFlatCString(aHost).get(), aPort));
-  SendOutgoingData(UDPData(autoStream.TakeOptionalValue()),
+  SendOutgoingData(UDPData(autoStream.TakeValue()),
                    UDPSocketAddr(UDPAddressInfo(nsCString(aHost), aPort)));
 
   return NS_OK;
 }
 
 NS_IMETHODIMP
 UDPSocketChild::JoinMulticast(const nsACString& aMulticastAddress,
                               const nsACString& aInterface) {
--- a/dom/webbrowserpersist/PWebBrowserPersistDocument.ipdl
+++ b/dom/webbrowserpersist/PWebBrowserPersistDocument.ipdl
@@ -60,17 +60,17 @@ protocol PWebBrowserPersistDocument {
   manages PWebBrowserPersistSerialize;
 
 parent:
   // The actor isn't exposed to XPCOM until after it gets one of these
   // two messages; see also the state transition rules.  The message
   // is either a response to the constructor (if it was parent->child)
   // or sent after it (if it was child->parent).
   async Attributes(WebBrowserPersistDocumentAttrs aAttrs,
-                   OptionalIPCStream stream);
+                   IPCStream? stream);
   async InitFailure(nsresult aStatus);
 
 child:
   async SetPersistFlags(uint32_t aNewFlags);
   async PWebBrowserPersistResources();
   async PWebBrowserPersistSerialize(WebBrowserPersistURIMap aMap,
                                     nsCString aRequestedContentType,
                                     uint32_t aEncoderFlags,
--- a/dom/webbrowserpersist/WebBrowserPersistDocumentParent.cpp
+++ b/dom/webbrowserpersist/WebBrowserPersistDocumentParent.cpp
@@ -48,17 +48,17 @@ void WebBrowserPersistDocumentParent::Ac
 }
 
 WebBrowserPersistDocumentParent::~WebBrowserPersistDocumentParent() {
   MOZ_RELEASE_ASSERT(!mReflection);
   MOZ_ASSERT(!mOnReady);
 }
 
 mozilla::ipc::IPCResult WebBrowserPersistDocumentParent::RecvAttributes(
-    const Attrs& aAttrs, const OptionalIPCStream& aPostStream) {
+    const Attrs& aAttrs, const Maybe<IPCStream>& aPostStream) {
   // Deserialize the postData unconditionally so that fds aren't leaked.
   nsCOMPtr<nsIInputStream> postData =
       mozilla::ipc::DeserializeIPCStream(aPostStream);
   if (!mOnReady || mReflection) {
     return IPC_FAIL_NO_REASON(this);
   }
   mReflection = new WebBrowserPersistRemoteDocument(this, aAttrs, postData);
   RefPtr<WebBrowserPersistRemoteDocument> reflection = mReflection;
--- a/dom/webbrowserpersist/WebBrowserPersistDocumentParent.h
+++ b/dom/webbrowserpersist/WebBrowserPersistDocumentParent.h
@@ -40,17 +40,17 @@ class WebBrowserPersistDocumentParent fi
   // state.  This method must be called exactly once while the actor
   // is still in the START state (or is unconstructed).
   void SetOnReady(nsIWebBrowserPersistDocumentReceiver* aOnReady);
 
   using Attrs = WebBrowserPersistDocumentAttrs;
 
   // IPDL methods:
   mozilla::ipc::IPCResult RecvAttributes(const Attrs& aAttrs,
-                                         const OptionalIPCStream& aPostStream);
+                                         const Maybe<IPCStream>& aPostStream);
   mozilla::ipc::IPCResult RecvInitFailure(const nsresult& aFailure);
 
   PWebBrowserPersistResourcesParent* AllocPWebBrowserPersistResourcesParent();
   bool DeallocPWebBrowserPersistResourcesParent(
       PWebBrowserPersistResourcesParent* aActor);
 
   PWebBrowserPersistSerializeParent* AllocPWebBrowserPersistSerializeParent(
       const WebBrowserPersistURIMap& aMap,
--- a/dom/webidl/WebGLRenderingContext.webidl
+++ b/dom/webidl/WebGLRenderingContext.webidl
@@ -1115,9 +1115,13 @@ interface MOZ_debug {
     const GLenum DOES_INDEX_VALIDATION   = 0x10002;
 
     [Throws]
     any getParameter(GLenum pname);
 };
 
 [NoInterfaceObject]
 interface EXT_float_blend {
-}; // interface EXT_float_blend
+};
+
+[NoInterfaceObject]
+interface OES_fbo_render_mipmap {
+};
--- a/gfx/gl/GLContext.cpp
+++ b/gfx/gl/GLContext.cpp
@@ -59,21 +59,21 @@ namespace gl {
 
 using namespace mozilla::gfx;
 using namespace mozilla::layers;
 
 MOZ_THREAD_LOCAL(uintptr_t) GLContext::sCurrentContext;
 
 // If adding defines, don't forget to undefine symbols. See #undef block below.
 // clang-format off
-#define CORE_SYMBOL(x) { (PRFuncPtr*) &mSymbols.f##x, { #x, nullptr } }
-#define CORE_EXT_SYMBOL2(x,y,z) { (PRFuncPtr*) &mSymbols.f##x, { #x, #x #y, #x #z, nullptr } }
-#define EXT_SYMBOL2(x,y,z) { (PRFuncPtr*) &mSymbols.f##x, { #x #y, #x #z, nullptr } }
-#define EXT_SYMBOL3(x,y,z,w) { (PRFuncPtr*) &mSymbols.f##x, { #x #y, #x #z, #x #w, nullptr } }
-#define END_SYMBOLS { nullptr, { nullptr } }
+#define CORE_SYMBOL(x) { (PRFuncPtr*) &mSymbols.f##x, {{ "gl" #x }} }
+#define CORE_EXT_SYMBOL2(x,y,z) { (PRFuncPtr*) &mSymbols.f##x, {{ "gl" #x, "gl" #x #y, "gl" #x #z }} }
+#define EXT_SYMBOL2(x,y,z) { (PRFuncPtr*) &mSymbols.f##x, {{ "gl" #x #y, "gl" #x #z }} }
+#define EXT_SYMBOL3(x,y,z,w) { (PRFuncPtr*) &mSymbols.f##x, {{ "gl" #x #y, "gl" #x #z, "gl" #x #w }} }
+#define END_SYMBOLS { nullptr, {} }
 // clang-format on
 
 // should match the order of GLExtensions, and be null-terminated.
 static const char* const sExtensionNames[] = {
     "NO_EXTENSION",
     "GL_AMD_compressed_ATC_texture",
     "GL_ANGLE_depth_texture",
     "GL_ANGLE_framebuffer_blit",
@@ -185,16 +185,17 @@ static const char* const sExtensionNames
     "GL_OES_EGL_image",
     "GL_OES_EGL_image_external",
     "GL_OES_EGL_sync",
     "GL_OES_compressed_ETC1_RGB8_texture",
     "GL_OES_depth24",
     "GL_OES_depth32",
     "GL_OES_depth_texture",
     "GL_OES_element_index_uint",
+    "GL_OES_fbo_render_mipmap",
     "GL_OES_framebuffer_object",
     "GL_OES_packed_depth_stencil",
     "GL_OES_rgb8_rgba8",
     "GL_OES_standard_derivatives",
     "GL_OES_stencil8",
     "GL_OES_texture_3D",
     "GL_OES_texture_float",
     "GL_OES_texture_float_linear",
@@ -297,220 +298,220 @@ GLContext::~GLContext() {
                                                GLuint id, GLenum severity,
                                                GLsizei length,
                                                const GLchar* message,
                                                const GLvoid* userParam) {
   GLContext* gl = (GLContext*)userParam;
   gl->DebugCallback(source, type, id, severity, length, message);
 }
 
-static void ClearSymbols(const GLLibraryLoader::SymLoadStruct* symbols) {
-  while (symbols->symPointer) {
-    *symbols->symPointer = nullptr;
-    symbols++;
-  }
-}
-
-bool GLContext::InitWithPrefix(const char* prefix, bool trygl) {
+bool GLContext::Init() {
   MOZ_RELEASE_ASSERT(!mSymbols.fBindFramebuffer,
-                     "GFX: InitWithPrefix should only be called once.");
+                     "GFX: GLContext::Init should only be called once.");
 
   ScopedGfxFeatureReporter reporter("GL Context");
 
-  if (!InitWithPrefixImpl(prefix, trygl)) {
+  if (!InitImpl()) {
     // If initialization fails, zero the symbols to avoid hard-to-understand
     // bugs.
     mSymbols = {};
     NS_WARNING("GLContext::InitWithPrefix failed!");
     return false;
   }
 
   reporter.SetSuccessful();
   return true;
 }
 
-static bool LoadGLSymbols(GLContext* gl, const char* prefix, bool trygl,
-                          const GLLibraryLoader::SymLoadStruct* list,
-                          const char* desc) {
+static bool LoadSymbolsWithDesc(const SymbolLoader& loader,
+                                const SymLoadStruct* list, const char* desc) {
   const auto warnOnFailure = bool(desc);
-  if (gl->LoadSymbols(list, trygl, prefix, warnOnFailure)) return true;
+  if (loader.LoadSymbols(list, warnOnFailure)) return true;
 
   ClearSymbols(list);
 
   if (desc) {
     const nsPrintfCString err("Failed to load symbols for %s.", desc);
     NS_ERROR(err.BeginReading());
   }
   return false;
 }
 
-bool GLContext::LoadExtSymbols(const char* prefix, bool trygl,
+bool GLContext::LoadExtSymbols(const SymbolLoader& loader,
                                const SymLoadStruct* list, GLExtensions ext) {
   const char* extName = sExtensionNames[size_t(ext)];
-  if (!LoadGLSymbols(this, prefix, trygl, list, extName)) {
+  if (!LoadSymbolsWithDesc(loader, list, extName)) {
     MarkExtensionUnsupported(ext);
     return false;
   }
   return true;
 };
 
-bool GLContext::LoadFeatureSymbols(const char* prefix, bool trygl,
+bool GLContext::LoadFeatureSymbols(const SymbolLoader& loader,
                                    const SymLoadStruct* list,
                                    GLFeature feature) {
   const char* featureName = GetFeatureName(feature);
-  if (!LoadGLSymbols(this, prefix, trygl, list, featureName)) {
+  if (!LoadSymbolsWithDesc(loader, list, featureName)) {
     MarkUnsupported(feature);
     return false;
   }
   return true;
 };
 
-bool GLContext::InitWithPrefixImpl(const char* prefix, bool trygl) {
+bool GLContext::InitImpl() {
   if (!MakeCurrent(true)) return false;
 
+  const auto loader = GetSymbolLoader();
+  if (!loader) return false;
+
+  const auto fnLoadSymbols = [&](const SymLoadStruct* const list,
+                                 const char* const desc) {
+    return LoadSymbolsWithDesc(*loader, list, desc);
+  };
+
   // clang-format off
     const SymLoadStruct coreSymbols[] = {
-        { (PRFuncPtr*) &mSymbols.fActiveTexture, { "ActiveTexture", "ActiveTextureARB", nullptr } },
-        { (PRFuncPtr*) &mSymbols.fAttachShader, { "AttachShader", "AttachShaderARB", nullptr } },
-        { (PRFuncPtr*) &mSymbols.fBindAttribLocation, { "BindAttribLocation", "BindAttribLocationARB", nullptr } },
-        { (PRFuncPtr*) &mSymbols.fBindBuffer, { "BindBuffer", "BindBufferARB", nullptr } },
-        { (PRFuncPtr*) &mSymbols.fBindTexture, { "BindTexture", "BindTextureARB", nullptr } },
-        { (PRFuncPtr*) &mSymbols.fBlendColor, { "BlendColor", nullptr } },
-        { (PRFuncPtr*) &mSymbols.fBlendEquation, { "BlendEquation", nullptr } },
-        { (PRFuncPtr*) &mSymbols.fBlendEquationSeparate, { "BlendEquationSeparate", "BlendEquationSeparateEXT", nullptr } },
-        { (PRFuncPtr*) &mSymbols.fBlendFunc, { "BlendFunc", nullptr } },
-        { (PRFuncPtr*) &mSymbols.fBlendFuncSeparate, { "BlendFuncSeparate", "BlendFuncSeparateEXT", nullptr } },
-        { (PRFuncPtr*) &mSymbols.fBufferData, { "BufferData", nullptr } },
-        { (PRFuncPtr*) &mSymbols.fBufferSubData, { "BufferSubData", nullptr } },
-        { (PRFuncPtr*) &mSymbols.fClear, { "Clear", nullptr } },
-        { (PRFuncPtr*) &mSymbols.fClearColor, { "ClearColor", nullptr } },
-        { (PRFuncPtr*) &mSymbols.fClearStencil, { "ClearStencil", nullptr } },
-        { (PRFuncPtr*) &mSymbols.fColorMask, { "ColorMask", nullptr } },
-        { (PRFuncPtr*) &mSymbols.fCompressedTexImage2D, {"CompressedTexImage2D", nullptr} },
-        { (PRFuncPtr*) &mSymbols.fCompressedTexSubImage2D, {"CompressedTexSubImage2D", nullptr} },
-        { (PRFuncPtr*) &mSymbols.fCullFace, { "CullFace", nullptr } },
-        { (PRFuncPtr*) &mSymbols.fDetachShader, { "DetachShader", "DetachShaderARB", nullptr } },
-        { (PRFuncPtr*) &mSymbols.fDepthFunc, { "DepthFunc", nullptr } },
-        { (PRFuncPtr*) &mSymbols.fDepthMask, { "DepthMask", nullptr } },
-        { (PRFuncPtr*) &mSymbols.fDisable, { "Disable", nullptr } },
-        { (PRFuncPtr*) &mSymbols.fDisableVertexAttribArray, { "DisableVertexAttribArray", "DisableVertexAttribArrayARB", nullptr } },
-        { (PRFuncPtr*) &mSymbols.fDrawArrays, { "DrawArrays", nullptr } },
-        { (PRFuncPtr*) &mSymbols.fDrawElements, { "DrawElements", nullptr } },
-        { (PRFuncPtr*) &mSymbols.fEnable, { "Enable", nullptr } },
-        { (PRFuncPtr*) &mSymbols.fEnableVertexAttribArray, { "EnableVertexAttribArray", "EnableVertexAttribArrayARB", nullptr } },
-        { (PRFuncPtr*) &mSymbols.fFinish, { "Finish", nullptr } },
-        { (PRFuncPtr*) &mSymbols.fFlush, { "Flush", nullptr } },
-        { (PRFuncPtr*) &mSymbols.fFrontFace, { "FrontFace", nullptr } },
-        { (PRFuncPtr*) &mSymbols.fGetActiveAttrib, { "GetActiveAttrib", "GetActiveAttribARB", nullptr } },
-        { (PRFuncPtr*) &mSymbols.fGetActiveUniform, { "GetActiveUniform", "GetActiveUniformARB", nullptr } },
-        { (PRFuncPtr*) &mSymbols.fGetAttachedShaders, { "GetAttachedShaders", "GetAttachedShadersARB", nullptr } },
-        { (PRFuncPtr*) &mSymbols.fGetAttribLocation, { "GetAttribLocation", "GetAttribLocationARB", nullptr } },
-        { (PRFuncPtr*) &mSymbols.fGetIntegerv, { "GetIntegerv", nullptr } },
-        { (PRFuncPtr*) &mSymbols.fGetFloatv, { "GetFloatv", nullptr } },
-        { (PRFuncPtr*) &mSymbols.fGetBooleanv, { "GetBooleanv", nullptr } },
-        { (PRFuncPtr*) &mSymbols.fGetBufferParameteriv, { "GetBufferParameteriv", "GetBufferParameterivARB", nullptr } },
-        { (PRFuncPtr*) &mSymbols.fGetError, { "GetError", nullptr } },
-        { (PRFuncPtr*) &mSymbols.fGetProgramiv, { "GetProgramiv", "GetProgramivARB", nullptr } },
-        { (PRFuncPtr*) &mSymbols.fGetProgramInfoLog, { "GetProgramInfoLog", "GetProgramInfoLogARB", nullptr } },
-        { (PRFuncPtr*) &mSymbols.fTexParameteri, { "TexParameteri", nullptr } },
-        { (PRFuncPtr*) &mSymbols.fTexParameteriv, { "TexParameteriv", nullptr } },
-        { (PRFuncPtr*) &mSymbols.fTexParameterf, { "TexParameterf", nullptr } },
-        { (PRFuncPtr*) &mSymbols.fGetString, { "GetString", nullptr } },
-        { (PRFuncPtr*) &mSymbols.fGetTexParameterfv, { "GetTexParameterfv", nullptr } },
-        { (PRFuncPtr*) &mSymbols.fGetTexParameteriv, { "GetTexParameteriv", nullptr } },
-        { (PRFuncPtr*) &mSymbols.fGetUniformfv, { "GetUniformfv", "GetUniformfvARB", nullptr } },
-        { (PRFuncPtr*) &mSymbols.fGetUniformiv, { "GetUniformiv", "GetUniformivARB", nullptr } },
-        { (PRFuncPtr*) &mSymbols.fGetUniformLocation, { "GetUniformLocation", "GetUniformLocationARB", nullptr } },
-        { (PRFuncPtr*) &mSymbols.fGetVertexAttribfv, { "GetVertexAttribfv", "GetVertexAttribfvARB", nullptr } },
-        { (PRFuncPtr*) &mSymbols.fGetVertexAttribiv, { "GetVertexAttribiv", "GetVertexAttribivARB", nullptr } },
-        { (PRFuncPtr*) &mSymbols.fGetVertexAttribPointerv, { "GetVertexAttribPointerv", nullptr } },
-        { (PRFuncPtr*) &mSymbols.fHint, { "Hint", nullptr } },
-        { (PRFuncPtr*) &mSymbols.fIsBuffer, { "IsBuffer", "IsBufferARB", nullptr } },
-        { (PRFuncPtr*) &mSymbols.fIsEnabled, { "IsEnabled", nullptr } },
-        { (PRFuncPtr*) &mSymbols.fIsProgram, { "IsProgram", "IsProgramARB", nullptr } },
-        { (PRFuncPtr*) &mSymbols.fIsShader, { "IsShader", "IsShaderARB", nullptr } },
-        { (PRFuncPtr*) &mSymbols.fIsTexture, { "IsTexture", "IsTextureARB", nullptr } },
-        { (PRFuncPtr*) &mSymbols.fLineWidth, { "LineWidth", nullptr } },
-        { (PRFuncPtr*) &mSymbols.fLinkProgram, { "LinkProgram", "LinkProgramARB", nullptr } },
-        { (PRFuncPtr*) &mSymbols.fPixelStorei, { "PixelStorei", nullptr } },
-        { (PRFuncPtr*) &mSymbols.fPolygonOffset, { "PolygonOffset", nullptr } },
-        { (PRFuncPtr*) &mSymbols.fReadPixels, { "ReadPixels", nullptr } },
-        { (PRFuncPtr*) &mSymbols.fSampleCoverage, { "SampleCoverage", nullptr } },
-        { (PRFuncPtr*) &mSymbols.fScissor, { "Scissor", nullptr } },
-        { (PRFuncPtr*) &mSymbols.fStencilFunc, { "StencilFunc", nullptr } },
-        { (PRFuncPtr*) &mSymbols.fStencilFuncSeparate, { "StencilFuncSeparate", "StencilFuncSeparateEXT", nullptr } },
-        { (PRFuncPtr*) &mSymbols.fStencilMask, { "StencilMask", nullptr } },
-        { (PRFuncPtr*) &mSymbols.fStencilMaskSeparate, { "StencilMaskSeparate", "StencilMaskSeparateEXT", nullptr } },
-        { (PRFuncPtr*) &mSymbols.fStencilOp, { "StencilOp", nullptr } },
-        { (PRFuncPtr*) &mSymbols.fStencilOpSeparate, { "StencilOpSeparate", "StencilOpSeparateEXT", nullptr } },
-        { (PRFuncPtr*) &mSymbols.fTexImage2D, { "TexImage2D", nullptr } },
-        { (PRFuncPtr*) &mSymbols.fTexSubImage2D, { "TexSubImage2D", nullptr } },
-        { (PRFuncPtr*) &mSymbols.fUniform1f, { "Uniform1f", nullptr } },
-        { (PRFuncPtr*) &mSymbols.fUniform1fv, { "Uniform1fv", nullptr } },
-        { (PRFuncPtr*) &mSymbols.fUniform1i, { "Uniform1i", nullptr } },
-        { (PRFuncPtr*) &mSymbols.fUniform1iv, { "Uniform1iv", nullptr } },
-        { (PRFuncPtr*) &mSymbols.fUniform2f, { "Uniform2f", nullptr } },
-        { (PRFuncPtr*) &mSymbols.fUniform2fv, { "Uniform2fv", nullptr } },
-        { (PRFuncPtr*) &mSymbols.fUniform2i, { "Uniform2i", nullptr } },
-        { (PRFuncPtr*) &mSymbols.fUniform2iv, { "Uniform2iv", nullptr } },
-        { (PRFuncPtr*) &mSymbols.fUniform3f, { "Uniform3f", nullptr } },
-        { (PRFuncPtr*) &mSymbols.fUniform3fv, { "Uniform3fv", nullptr } },
-        { (PRFuncPtr*) &mSymbols.fUniform3i, { "Uniform3i", nullptr } },
-        { (PRFuncPtr*) &mSymbols.fUniform3iv, { "Uniform3iv", nullptr } },
-        { (PRFuncPtr*) &mSymbols.fUniform4f, { "Uniform4f", nullptr } },
-        { (PRFuncPtr*) &mSymbols.fUniform4fv, { "Uniform4fv", nullptr } },
-        { (PRFuncPtr*) &mSymbols.fUniform4i, { "Uniform4i", nullptr } },
-        { (PRFuncPtr*) &mSymbols.fUniform4iv, { "Uniform4iv", nullptr } },
-        { (PRFuncPtr*) &mSymbols.fUniformMatrix2fv, { "UniformMatrix2fv", nullptr } },
-        { (PRFuncPtr*) &mSymbols.fUniformMatrix3fv, { "UniformMatrix3fv", nullptr } },
-        { (PRFuncPtr*) &mSymbols.fUniformMatrix4fv, { "UniformMatrix4fv", nullptr } },
-        { (PRFuncPtr*) &mSymbols.fUseProgram, { "UseProgram", nullptr } },
-        { (PRFuncPtr*) &mSymbols.fValidateProgram, { "ValidateProgram", nullptr } },
-        { (PRFuncPtr*) &mSymbols.fVertexAttribPointer, { "VertexAttribPointer", nullptr } },
-        { (PRFuncPtr*) &mSymbols.fVertexAttrib1f, { "VertexAttrib1f", nullptr } },
-        { (PRFuncPtr*) &mSymbols.fVertexAttrib2f, { "VertexAttrib2f", nullptr } },
-        { (PRFuncPtr*) &mSymbols.fVertexAttrib3f, { "VertexAttrib3f", nullptr } },
-        { (PRFuncPtr*) &mSymbols.fVertexAttrib4f, { "VertexAttrib4f", nullptr } },
-        { (PRFuncPtr*) &mSymbols.fVertexAttrib1fv, { "VertexAttrib1fv", nullptr } },
-        { (PRFuncPtr*) &mSymbols.fVertexAttrib2fv, { "VertexAttrib2fv", nullptr } },
-        { (PRFuncPtr*) &mSymbols.fVertexAttrib3fv, { "VertexAttrib3fv", nullptr } },
-        { (PRFuncPtr*) &mSymbols.fVertexAttrib4fv, { "VertexAttrib4fv", nullptr } },
-        { (PRFuncPtr*) &mSymbols.fViewport, { "Viewport", nullptr } },
-        { (PRFuncPtr*) &mSymbols.fCompileShader, { "CompileShader", nullptr } },
-        { (PRFuncPtr*) &mSymbols.fCopyTexImage2D, { "CopyTexImage2D", nullptr } },
-        { (PRFuncPtr*) &mSymbols.fCopyTexSubImage2D, { "CopyTexSubImage2D", nullptr } },
-        { (PRFuncPtr*) &mSymbols.fGetShaderiv, { "GetShaderiv", nullptr } },
-        { (PRFuncPtr*) &mSymbols.fGetShaderInfoLog, { "GetShaderInfoLog", nullptr } },
-        { (PRFuncPtr*) &mSymbols.fGetShaderSource, { "GetShaderSource", nullptr } },
-        { (PRFuncPtr*) &mSymbols.fShaderSource, { "ShaderSource", nullptr } },
-        { (PRFuncPtr*) &mSymbols.fVertexAttribPointer, { "VertexAttribPointer", nullptr } },
-
-        { (PRFuncPtr*) &mSymbols.fGenBuffers, { "GenBuffers", "GenBuffersARB", nullptr } },
-        { (PRFuncPtr*) &mSymbols.fGenTextures, { "GenTextures", nullptr } },
-        { (PRFuncPtr*) &mSymbols.fCreateProgram, { "CreateProgram", "CreateProgramARB", nullptr } },
-        { (PRFuncPtr*) &mSymbols.fCreateShader, { "CreateShader", "CreateShaderARB", nullptr } },
-
-        { (PRFuncPtr*) &mSymbols.fDeleteBuffers, { "DeleteBuffers", "DeleteBuffersARB", nullptr } },
-        { (PRFuncPtr*) &mSymbols.fDeleteTextures, { "DeleteTextures", "DeleteTexturesARB", nullptr } },
-        { (PRFuncPtr*) &mSymbols.fDeleteProgram, { "DeleteProgram", "DeleteProgramARB", nullptr } },
-        { (PRFuncPtr*) &mSymbols.fDeleteShader, { "DeleteShader", "DeleteShaderARB", nullptr } },
+        { (PRFuncPtr*) &mSymbols.fActiveTexture, {{ "glActiveTexture", "glActiveTextureARB" }} },
+        { (PRFuncPtr*) &mSymbols.fAttachShader, {{ "glAttachShader", "glAttachShaderARB" }} },
+        { (PRFuncPtr*) &mSymbols.fBindAttribLocation, {{ "glBindAttribLocation", "glBindAttribLocationARB" }} },
+        { (PRFuncPtr*) &mSymbols.fBindBuffer, {{ "glBindBuffer", "glBindBufferARB" }} },
+        { (PRFuncPtr*) &mSymbols.fBindTexture, {{ "glBindTexture", "glBindTextureARB" }} },
+        { (PRFuncPtr*) &mSymbols.fBlendColor, {{ "glBlendColor" }} },
+        { (PRFuncPtr*) &mSymbols.fBlendEquation, {{ "glBlendEquation" }} },
+        { (PRFuncPtr*) &mSymbols.fBlendEquationSeparate, {{ "glBlendEquationSeparate", "glBlendEquationSeparateEXT" }} },
+        { (PRFuncPtr*) &mSymbols.fBlendFunc, {{ "glBlendFunc" }} },
+        { (PRFuncPtr*) &mSymbols.fBlendFuncSeparate, {{ "glBlendFuncSeparate", "glBlendFuncSeparateEXT" }} },
+        { (PRFuncPtr*) &mSymbols.fBufferData, {{ "glBufferData" }} },
+        { (PRFuncPtr*) &mSymbols.fBufferSubData, {{ "glBufferSubData" }} },
+        { (PRFuncPtr*) &mSymbols.fClear, {{ "glClear" }} },
+        { (PRFuncPtr*) &mSymbols.fClearColor, {{ "glClearColor" }} },
+        { (PRFuncPtr*) &mSymbols.fClearStencil, {{ "glClearStencil" }} },
+        { (PRFuncPtr*) &mSymbols.fColorMask, {{ "glColorMask" }} },
+        { (PRFuncPtr*) &mSymbols.fCompressedTexImage2D, {{ "glCompressedTexImage2D" }} },
+        { (PRFuncPtr*) &mSymbols.fCompressedTexSubImage2D, {{ "glCompressedTexSubImage2D" }} },
+        { (PRFuncPtr*) &mSymbols.fCullFace, {{ "glCullFace" }} },
+        { (PRFuncPtr*) &mSymbols.fDetachShader, {{ "glDetachShader", "glDetachShaderARB" }} },
+        { (PRFuncPtr*) &mSymbols.fDepthFunc, {{ "glDepthFunc" }} },
+        { (PRFuncPtr*) &mSymbols.fDepthMask, {{ "glDepthMask" }} },
+        { (PRFuncPtr*) &mSymbols.fDisable, {{ "glDisable" }} },
+        { (PRFuncPtr*) &mSymbols.fDisableVertexAttribArray, {{ "glDisableVertexAttribArray", "glDisableVertexAttribArrayARB" }} },
+        { (PRFuncPtr*) &mSymbols.fDrawArrays, {{ "glDrawArrays" }} },
+        { (PRFuncPtr*) &mSymbols.fDrawElements, {{ "glDrawElements" }} },
+        { (PRFuncPtr*) &mSymbols.fEnable, {{ "glEnable" }} },
+        { (PRFuncPtr*) &mSymbols.fEnableVertexAttribArray, {{ "glEnableVertexAttribArray", "glEnableVertexAttribArrayARB" }} },
+        { (PRFuncPtr*) &mSymbols.fFinish, {{ "glFinish" }} },
+        { (PRFuncPtr*) &mSymbols.fFlush, {{ "glFlush" }} },
+        { (PRFuncPtr*) &mSymbols.fFrontFace, {{ "glFrontFace" }} },
+        { (PRFuncPtr*) &mSymbols.fGetActiveAttrib, {{ "glGetActiveAttrib", "glGetActiveAttribARB" }} },
+        { (PRFuncPtr*) &mSymbols.fGetActiveUniform, {{ "glGetActiveUniform", "glGetActiveUniformARB" }} },
+        { (PRFuncPtr*) &mSymbols.fGetAttachedShaders, {{ "glGetAttachedShaders", "glGetAttachedShadersARB" }} },
+        { (PRFuncPtr*) &mSymbols.fGetAttribLocation, {{ "glGetAttribLocation", "glGetAttribLocationARB" }} },
+        { (PRFuncPtr*) &mSymbols.fGetIntegerv, {{ "glGetIntegerv" }} },
+        { (PRFuncPtr*) &mSymbols.fGetFloatv, {{ "glGetFloatv" }} },
+        { (PRFuncPtr*) &mSymbols.fGetBooleanv, {{ "glGetBooleanv" }} },
+        { (PRFuncPtr*) &mSymbols.fGetBufferParameteriv, {{ "glGetBufferParameteriv", "glGetBufferParameterivARB" }} },
+        { (PRFuncPtr*) &mSymbols.fGetError, {{ "glGetError" }} },
+        { (PRFuncPtr*) &mSymbols.fGetProgramiv, {{ "glGetProgramiv", "glGetProgramivARB" }} },
+        { (PRFuncPtr*) &mSymbols.fGetProgramInfoLog, {{ "glGetProgramInfoLog", "glGetProgramInfoLogARB" }} },
+        { (PRFuncPtr*) &mSymbols.fTexParameteri, {{ "glTexParameteri" }} },
+        { (PRFuncPtr*) &mSymbols.fTexParameteriv, {{ "glTexParameteriv" }} },
+        { (PRFuncPtr*) &mSymbols.fTexParameterf, {{ "glTexParameterf" }} },
+        { (PRFuncPtr*) &mSymbols.fGetString, {{ "glGetString" }} },
+        { (PRFuncPtr*) &mSymbols.fGetTexParameterfv, {{ "glGetTexParameterfv" }} },
+        { (PRFuncPtr*) &mSymbols.fGetTexParameteriv, {{ "glGetTexParameteriv" }} },
+        { (PRFuncPtr*) &mSymbols.fGetUniformfv, {{ "glGetUniformfv", "glGetUniformfvARB" }} },
+        { (PRFuncPtr*) &mSymbols.fGetUniformiv, {{ "glGetUniformiv", "glGetUniformivARB" }} },
+        { (PRFuncPtr*) &mSymbols.fGetUniformLocation, {{ "glGetUniformLocation", "glGetUniformLocationARB" }} },
+        { (PRFuncPtr*) &mSymbols.fGetVertexAttribfv, {{ "glGetVertexAttribfv", "glGetVertexAttribfvARB" }} },
+        { (PRFuncPtr*) &mSymbols.fGetVertexAttribiv, {{ "glGetVertexAttribiv", "glGetVertexAttribivARB" }} },
+        { (PRFuncPtr*) &mSymbols.fGetVertexAttribPointerv, {{ "glGetVertexAttribPointerv" }} },
+        { (PRFuncPtr*) &mSymbols.fHint, {{ "glHint" }} },
+        { (PRFuncPtr*) &mSymbols.fIsBuffer, {{ "glIsBuffer", "glIsBufferARB" }} },
+        { (PRFuncPtr*) &mSymbols.fIsEnabled, {{ "glIsEnabled" }} },
+        { (PRFuncPtr*) &mSymbols.fIsProgram, {{ "glIsProgram", "glIsProgramARB" }} },
+        { (PRFuncPtr*) &mSymbols.fIsShader, {{ "glIsShader", "glIsShaderARB" }} },
+        { (PRFuncPtr*) &mSymbols.fIsTexture, {{ "glIsTexture", "glIsTextureARB" }} },
+        { (PRFuncPtr*) &mSymbols.fLineWidth, {{ "glLineWidth" }} },
+        { (PRFuncPtr*) &mSymbols.fLinkProgram, {{ "glLinkProgram", "glLinkProgramARB" }} },
+        { (PRFuncPtr*) &mSymbols.fPixelStorei, {{ "glPixelStorei" }} },
+        { (PRFuncPtr*) &mSymbols.fPolygonOffset, {{ "glPolygonOffset" }} },
+        { (PRFuncPtr*) &mSymbols.fReadPixels, {{ "glReadPixels" }} },
+        { (PRFuncPtr*) &mSymbols.fSampleCoverage, {{ "glSampleCoverage" }} },
+        { (PRFuncPtr*) &mSymbols.fScissor, {{ "glScissor" }} },
+        { (PRFuncPtr*) &mSymbols.fStencilFunc, {{ "glStencilFunc" }} },
+        { (PRFuncPtr*) &mSymbols.fStencilFuncSeparate, {{ "glStencilFuncSeparate", "glStencilFuncSeparateEXT" }} },
+        { (PRFuncPtr*) &mSymbols.fStencilMask, {{ "glStencilMask" }} },
+        { (PRFuncPtr*) &mSymbols.fStencilMaskSeparate, {{ "glStencilMaskSeparate", "glStencilMaskSeparateEXT" }} },
+        { (PRFuncPtr*) &mSymbols.fStencilOp, {{ "glStencilOp" }} },
+        { (PRFuncPtr*) &mSymbols.fStencilOpSeparate, {{ "glStencilOpSeparate", "glStencilOpSeparateEXT" }} },
+        { (PRFuncPtr*) &mSymbols.fTexImage2D, {{ "glTexImage2D" }} },
+        { (PRFuncPtr*) &mSymbols.fTexSubImage2D, {{ "glTexSubImage2D" }} },
+        { (PRFuncPtr*) &mSymbols.fUniform1f, {{ "glUniform1f" }} },
+        { (PRFuncPtr*) &mSymbols.fUniform1fv, {{ "glUniform1fv" }} },
+        { (PRFuncPtr*) &mSymbols.fUniform1i, {{ "glUniform1i" }} },
+        { (PRFuncPtr*) &mSymbols.fUniform1iv, {{ "glUniform1iv" }} },
+        { (PRFuncPtr*) &mSymbols.fUniform2f, {{ "glUniform2f" }} },
+        { (PRFuncPtr*) &mSymbols.fUniform2fv, {{ "glUniform2fv" }} },
+        { (PRFuncPtr*) &mSymbols.fUniform2i, {{ "glUniform2i" }} },
+        { (PRFuncPtr*) &mSymbols.fUniform2iv, {{ "glUniform2iv" }} },
+        { (PRFuncPtr*) &mSymbols.fUniform3f, {{ "glUniform3f" }} },
+        { (PRFuncPtr*) &mSymbols.fUniform3fv, {{ "glUniform3fv" }} },
+        { (PRFuncPtr*) &mSymbols.fUniform3i, {{ "glUniform3i" }} },
+        { (PRFuncPtr*) &mSymbols.fUniform3iv, {{ "glUniform3iv" }} },
+        { (PRFuncPtr*) &mSymbols.fUniform4f, {{ "glUniform4f" }} },
+        { (PRFuncPtr*) &mSymbols.fUniform4fv, {{ "glUniform4fv" }} },
+        { (PRFuncPtr*) &mSymbols.fUniform4i, {{ "glUniform4i" }} },
+        { (PRFuncPtr*) &mSymbols.fUniform4iv, {{ "glUniform4iv" }} },
+        { (PRFuncPtr*) &mSymbols.fUniformMatrix2fv, {{ "glUniformMatrix2fv" }} },
+        { (PRFuncPtr*) &mSymbols.fUniformMatrix3fv, {{ "glUniformMatrix3fv" }} },
+        { (PRFuncPtr*) &mSymbols.fUniformMatrix4fv, {{ "glUniformMatrix4fv" }} },
+        { (PRFuncPtr*) &mSymbols.fUseProgram, {{ "glUseProgram" }} },
+        { (PRFuncPtr*) &mSymbols.fValidateProgram, {{ "glValidateProgram" }} },
+        { (PRFuncPtr*) &mSymbols.fVertexAttribPointer, {{ "glVertexAttribPointer" }} },
+        { (PRFuncPtr*) &mSymbols.fVertexAttrib1f, {{ "glVertexAttrib1f" }} },
+        { (PRFuncPtr*) &mSymbols.fVertexAttrib2f, {{ "glVertexAttrib2f" }} },
+        { (PRFuncPtr*) &mSymbols.fVertexAttrib3f, {{ "glVertexAttrib3f" }} },
+        { (PRFuncPtr*) &mSymbols.fVertexAttrib4f, {{ "glVertexAttrib4f" }} },
+        { (PRFuncPtr*) &mSymbols.fVertexAttrib1fv, {{ "glVertexAttrib1fv" }} },
+        { (PRFuncPtr*) &mSymbols.fVertexAttrib2fv, {{ "glVertexAttrib2fv" }} },
+        { (PRFuncPtr*) &mSymbols.fVertexAttrib3fv, {{ "glVertexAttrib3fv" }} },
+        { (PRFuncPtr*) &mSymbols.fVertexAttrib4fv, {{ "glVertexAttrib4fv" }} },
+        { (PRFuncPtr*) &mSymbols.fViewport, {{ "glViewport" }} },
+        { (PRFuncPtr*) &mSymbols.fCompileShader, {{ "glCompileShader" }} },
+        { (PRFuncPtr*) &mSymbols.fCopyTexImage2D, {{ "glCopyTexImage2D" }} },
+        { (PRFuncPtr*) &mSymbols.fCopyTexSubImage2D, {{ "glCopyTexSubImage2D" }} },
+        { (PRFuncPtr*) &mSymbols.fGetShaderiv, {{ "glGetShaderiv" }} },
+        { (PRFuncPtr*) &mSymbols.fGetShaderInfoLog, {{ "glGetShaderInfoLog" }} },
+        { (PRFuncPtr*) &mSymbols.fGetShaderSource, {{ "glGetShaderSource" }} },
+        { (PRFuncPtr*) &mSymbols.fShaderSource, {{ "glShaderSource" }} },
+        { (PRFuncPtr*) &mSymbols.fVertexAttribPointer, {{ "glVertexAttribPointer" }} },
+
+        { (PRFuncPtr*) &mSymbols.fGenBuffers, {{ "glGenBuffers", "glGenBuffersARB" }} },
+        { (PRFuncPtr*) &mSymbols.fGenTextures, {{ "glGenTextures" }} },
+        { (PRFuncPtr*) &mSymbols.fCreateProgram, {{ "glCreateProgram", "glCreateProgramARB" }} },
+        { (PRFuncPtr*) &mSymbols.fCreateShader, {{ "glCreateShader", "glCreateShaderARB" }} },
+
+        { (PRFuncPtr*) &mSymbols.fDeleteBuffers, {{ "glDeleteBuffers", "glDeleteBuffersARB" }} },
+        { (PRFuncPtr*) &mSymbols.fDeleteTextures, {{ "glDeleteTextures", "glDeleteTexturesARB" }} },
+        { (PRFuncPtr*) &mSymbols.fDeleteProgram, {{ "glDeleteProgram", "glDeleteProgramARB" }} },
+        { (PRFuncPtr*) &mSymbols.fDeleteShader, {{ "glDeleteShader", "glDeleteShaderARB" }} },
 
         END_SYMBOLS
     };
   // clang-format on
 
-  if (!LoadGLSymbols(this, prefix, trygl, coreSymbols, "GL")) return false;
+  if (!fnLoadSymbols(coreSymbols, "GL")) return false;
 
   {
     const SymLoadStruct symbols[] = {
         {(PRFuncPtr*)&mSymbols.fGetGraphicsResetStatus,
-         {"GetGraphicsResetStatus", "GetGraphicsResetStatusARB",
-          "GetGraphicsResetStatusKHR", "GetGraphicsResetStatusEXT", nullptr}},
+         {{"glGetGraphicsResetStatus", "glGetGraphicsResetStatusARB",
+           "glGetGraphicsResetStatusKHR", "glGetGraphicsResetStatusEXT"}}},
         END_SYMBOLS};
-    (void)LoadGLSymbols(this, prefix, trygl, symbols, nullptr);
+    (void)fnLoadSymbols(symbols, nullptr);
 
     auto err = fGetError();
     if (err == LOCAL_GL_CONTEXT_LOST) {
       MOZ_ASSERT(mSymbols.fGetGraphicsResetStatus);
       const auto status = fGetGraphicsResetStatus();
       if (status) {
         printf_stderr("Unflushed glGetGraphicsResetStatus: 0x%04x\n", status);
       }
@@ -566,34 +567,33 @@ bool GLContext::InitWithPrefixImpl(const
   ////////////////
 
   // Load OpenGL ES 2.0 symbols, or desktop if we aren't using ES 2.
   if (mProfile == ContextProfile::OpenGLES) {
     const SymLoadStruct symbols[] = {CORE_SYMBOL(GetShaderPrecisionFormat),
                                      CORE_SYMBOL(ClearDepthf),
                                      CORE_SYMBOL(DepthRangef), END_SYMBOLS};
 
-    if (!LoadGLSymbols(this, prefix, trygl, symbols, "OpenGL ES")) return false;
+    if (!fnLoadSymbols(symbols, "OpenGL ES")) return false;
   } else {
     const SymLoadStruct symbols[] = {
         CORE_SYMBOL(ClearDepth), CORE_SYMBOL(DepthRange),
         CORE_SYMBOL(ReadBuffer), CORE_SYMBOL(MapBuffer),
         CORE_SYMBOL(UnmapBuffer), CORE_SYMBOL(PointParameterf),
         CORE_SYMBOL(DrawBuffer),
         // The following functions are only used by Skia/GL in desktop mode.
         // Other parts of Gecko should avoid using these
         CORE_SYMBOL(DrawBuffers), CORE_SYMBOL(ClientActiveTexture),
         CORE_SYMBOL(DisableClientState), CORE_SYMBOL(EnableClientState),
         CORE_SYMBOL(LoadIdentity), CORE_SYMBOL(LoadMatrixf),
         CORE_SYMBOL(MatrixMode), CORE_SYMBOL(PolygonMode), CORE_SYMBOL(TexGeni),
         CORE_SYMBOL(TexGenf), CORE_SYMBOL(TexGenfv), CORE_SYMBOL(VertexPointer),
         END_SYMBOLS};
 
-    if (!LoadGLSymbols(this, prefix, trygl, symbols, "Desktop OpenGL"))
-      return false;
+    if (!fnLoadSymbols(symbols, "Desktop OpenGL")) return false;
   }
 
   ////////////////
 
   const char* glVendorString = (const char*)fGetString(LOCAL_GL_VENDOR);
   const char* glRendererString = (const char*)fGetString(LOCAL_GL_RENDERER);
   if (!glVendorString || !glRendererString) return false;
 
@@ -648,20 +648,19 @@ bool GLContext::InitWithPrefixImpl(const
     printf_stderr("GL_RENDERER: %s\n", glRendererString);
     printf_stderr("mRenderer: %s\n", rendererMatchStrings[size_t(mRenderer)]);
   }
 
   ////////////////
 
   if (mVersion >= 300) {  // Both GL3 and ES3.
     const SymLoadStruct symbols[] = {
-        {(PRFuncPtr*)&mSymbols.fGetStringi, {"GetStringi", nullptr}},
-        END_SYMBOLS};
-
-    if (!LoadGLSymbols(this, prefix, trygl, symbols, "GetStringi")) {
+        {(PRFuncPtr*)&mSymbols.fGetStringi, {{"glGetStringi"}}}, END_SYMBOLS};
+
+    if (!fnLoadSymbols(symbols, "GetStringi")) {
       MOZ_RELEASE_ASSERT(false, "GFX: GetStringi is required!");
       return false;
     }
   }
 
   InitExtensions();
   if (mProfile != ContextProfile::OpenGLES) {
     if (mVersion >= 310 && !IsExtensionSupported(ARB_compatibility)) {
@@ -726,19 +725,19 @@ bool GLContext::InitWithPrefixImpl(const
     MOZ_ASSERT(
         (mSymbols.fMapBuffer && mSymbols.fUnmapBuffer),
         "ARB_pixel_buffer_object supported without glMapBuffer/UnmapBuffer"
         " being available!");
   }
 
   ////////////////////////////////////////////////////////////////////////////
 
-  const auto fnLoadForFeature = [this, prefix, trygl](const SymLoadStruct* list,
-                                                      GLFeature feature) {
-    return this->LoadFeatureSymbols(prefix, trygl, list, feature);
+  const auto fnLoadForFeature = [&](const SymLoadStruct* list,
+                                    GLFeature feature) {
+    return this->LoadFeatureSymbols(*loader, list, feature);
   };
 
   // Check for ARB_framebuffer_objects
   if (IsSupported(GLFeature::framebuffer_object)) {
     // https://www.opengl.org/registry/specs/ARB/framebuffer_object.txt
     const SymLoadStruct symbols[] = {
         CORE_SYMBOL(IsRenderbuffer),
         CORE_SYMBOL(BindRenderbuffer),
@@ -797,17 +796,17 @@ bool GLContext::InitWithPrefixImpl(const
           END_SYMBOLS};
       fnLoadForFeature(symbols, GLFeature::framebuffer_multisample);
     }
 
     if (IsExtensionSupported(GLContext::ARB_geometry_shader4) ||
         IsExtensionSupported(GLContext::NV_geometry_program4)) {
       const SymLoadStruct symbols[] = {
           EXT_SYMBOL2(FramebufferTextureLayer, ARB, EXT), END_SYMBOLS};
-      if (!LoadGLSymbols(this, prefix, trygl, symbols,
+      if (!fnLoadSymbols(symbols,
                          "ARB_geometry_shader4/NV_geometry_program4")) {
         MarkExtensionUnsupported(GLContext::ARB_geometry_shader4);
         MarkExtensionUnsupported(GLContext::NV_geometry_program4);
       }
     }
   }
 
   if (!IsSupported(GLFeature::framebuffer_object) &&
@@ -819,17 +818,17 @@ bool GLContext::InitWithPrefixImpl(const
                      "GFX: mSymbols.fBindFramebuffer zero or not set.");
 
   ////////////////
 
   const auto err = fGetError();
   MOZ_RELEASE_ASSERT(!IsBadCallError(err));
   if (err) return false;
 
-  LoadMoreSymbols(prefix, trygl);
+  LoadMoreSymbols(*loader);
 
   ////////////////////////////////////////////////////////////////////////////
 
   raw_fGetIntegerv(LOCAL_GL_VIEWPORT, mViewportRect);
   raw_fGetIntegerv(LOCAL_GL_SCISSOR_BOX, mScissorRect);
   raw_fGetIntegerv(LOCAL_GL_MAX_TEXTURE_SIZE, &mMaxTextureSize);
   raw_fGetIntegerv(LOCAL_GL_MAX_CUBE_MAP_TEXTURE_SIZE, &mMaxCubeMapTextureSize);
   raw_fGetIntegerv(LOCAL_GL_MAX_RENDERBUFFER_SIZE, &mMaxRenderbufferSize);
@@ -930,29 +929,27 @@ bool GLContext::InitWithPrefixImpl(const
     fDebugMessageCallback(&StaticDebugCallback, (void*)this);
     fDebugMessageControl(LOCAL_GL_DONT_CARE, LOCAL_GL_DONT_CARE,
                          LOCAL_GL_DONT_CARE, 0, nullptr, true);
   }
 
   return true;
 }
 
-void GLContext::LoadMoreSymbols(const char* prefix, bool trygl) {
-  const auto fnLoadForExt = [this, prefix, trygl](const SymLoadStruct* list,
-                                                  GLExtensions ext) {
-    return this->LoadExtSymbols(prefix, trygl, list, ext);
+void GLContext::LoadMoreSymbols(const SymbolLoader& loader) {
+  const auto fnLoadForExt = [&](const SymLoadStruct* list, GLExtensions ext) {
+    return this->LoadExtSymbols(loader, list, ext);
   };
 
-  const auto fnLoadForFeature = [this, prefix, trygl](const SymLoadStruct* list,
-                                                      GLFeature feature) {
-    return this->LoadFeatureSymbols(prefix, trygl, list, feature);
+  const auto fnLoadForFeature = [&](const SymLoadStruct* list,
+                                    GLFeature feature) {
+    return this->LoadFeatureSymbols(loader, list, feature);
   };
 
-  const auto fnLoadFeatureByCore = [this, fnLoadForFeature](
-                                       const SymLoadStruct* coreList,
+  const auto fnLoadFeatureByCore = [&](const SymLoadStruct* coreList,
                                        const SymLoadStruct* extList,
                                        GLFeature feature) {
     const bool useCore = this->IsFeatureProvidedByCoreSymbols(feature);
     const auto list = useCore ? coreList : extList;
     return fnLoadForFeature(list, feature);
   };
 
   if (IsSupported(GLFeature::robustness)) {
@@ -979,19 +976,19 @@ void GLContext::LoadMoreSymbols(const ch
         CORE_SYMBOL(WaitSync),   CORE_SYMBOL(GetInteger64v),
         CORE_SYMBOL(GetSynciv),  END_SYMBOLS};
     fnLoadForFeature(symbols, GLFeature::sync);
   }
 
   if (IsExtensionSupported(OES_EGL_image)) {
     const SymLoadStruct symbols[] = {
         {(PRFuncPtr*)&mSymbols.fEGLImageTargetTexture2D,
-         {"EGLImageTargetTexture2DOES", nullptr}},
+         {{"glEGLImageTargetTexture2DOES"}}},
         {(PRFuncPtr*)&mSymbols.fEGLImageTargetRenderbufferStorage,
-         {"EGLImageTargetRenderbufferStorageOES", nullptr}},
+         {{"glEGLImageTargetRenderbufferStorageOES"}}},
         END_SYMBOLS};
     fnLoadForExt(symbols, OES_EGL_image);
   }
 
   if (IsExtensionSupported(APPLE_texture_range)) {
     const SymLoadStruct symbols[] = {CORE_SYMBOL(TextureRangeAPPLE),
                                      END_SYMBOLS};
     fnLoadForExt(symbols, APPLE_texture_range);
@@ -1002,441 +999,441 @@ void GLContext::LoadMoreSymbols(const ch
                                      CORE_SYMBOL(TestObjectAPPLE), END_SYMBOLS};
     fnLoadForExt(symbols, APPLE_fence);
   }
 
   // clang-format off
 
     if (IsSupported(GLFeature::vertex_array_object)) {
         const SymLoadStruct coreSymbols[] = {
-            { (PRFuncPtr*) &mSymbols.fIsVertexArray, { "IsVertexArray", nullptr } },
-            { (PRFuncPtr*) &mSymbols.fGenVertexArrays, { "GenVertexArrays", nullptr } },
-            { (PRFuncPtr*) &mSymbols.fBindVertexArray, { "BindVertexArray", nullptr } },
-            { (PRFuncPtr*) &mSymbols.fDeleteVertexArrays, { "DeleteVertexArrays", nullptr } },
+            { (PRFuncPtr*) &mSymbols.fIsVertexArray, {{ "glIsVertexArray" }} },
+            { (PRFuncPtr*) &mSymbols.fGenVertexArrays, {{ "glGenVertexArrays" }} },
+            { (PRFuncPtr*) &mSymbols.fBindVertexArray, {{ "glBindVertexArray" }} },
+            { (PRFuncPtr*) &mSymbols.fDeleteVertexArrays, {{ "glDeleteVertexArrays" }} },
             END_SYMBOLS
         };
         const SymLoadStruct extSymbols[] = {
-            { (PRFuncPtr*) &mSymbols.fIsVertexArray, { "IsVertexArrayARB", "IsVertexArrayOES", "IsVertexArrayAPPLE", nullptr } },
-            { (PRFuncPtr*) &mSymbols.fGenVertexArrays, { "GenVertexArraysARB", "GenVertexArraysOES", "GenVertexArraysAPPLE", nullptr } },
-            { (PRFuncPtr*) &mSymbols.fBindVertexArray, { "BindVertexArrayARB", "BindVertexArrayOES", "BindVertexArrayAPPLE", nullptr } },
-            { (PRFuncPtr*) &mSymbols.fDeleteVertexArrays, { "DeleteVertexArraysARB", "DeleteVertexArraysOES", "DeleteVertexArraysAPPLE", nullptr } },
+            { (PRFuncPtr*) &mSymbols.fIsVertexArray, {{ "glIsVertexArrayARB", "glIsVertexArrayOES", "glIsVertexArrayAPPLE" }} },
+            { (PRFuncPtr*) &mSymbols.fGenVertexArrays, {{ "glGenVertexArraysARB", "glGenVertexArraysOES", "glGenVertexArraysAPPLE" }} },
+            { (PRFuncPtr*) &mSymbols.fBindVertexArray, {{ "glBindVertexArrayARB", "glBindVertexArrayOES", "glBindVertexArrayAPPLE" }} },
+            { (PRFuncPtr*) &mSymbols.fDeleteVertexArrays, {{ "glDeleteVertexArraysARB", "glDeleteVertexArraysOES", "glDeleteVertexArraysAPPLE" }} },
             END_SYMBOLS
         };
         fnLoadFeatureByCore(coreSymbols, extSymbols, GLFeature::vertex_array_object);
     }
 
     if (IsSupported(GLFeature::draw_instanced)) {
         const SymLoadStruct coreSymbols[] = {
-            { (PRFuncPtr*) &mSymbols.fDrawArraysInstanced, { "DrawArraysInstanced", nullptr } },
-            { (PRFuncPtr*) &mSymbols.fDrawElementsInstanced, { "DrawElementsInstanced", nullptr } },
+            { (PRFuncPtr*) &mSymbols.fDrawArraysInstanced, {{ "glDrawArraysInstanced" }} },
+            { (PRFuncPtr*) &mSymbols.fDrawElementsInstanced, {{ "glDrawElementsInstanced" }} },
             END_SYMBOLS
         };
         const SymLoadStruct extSymbols[] = {
-            { (PRFuncPtr*) &mSymbols.fDrawArraysInstanced, { "DrawArraysInstancedARB", "DrawArraysInstancedEXT", "DrawArraysInstancedNV", "DrawArraysInstancedANGLE", nullptr } },
-            { (PRFuncPtr*) &mSymbols.fDrawElementsInstanced, { "DrawElementsInstancedARB", "DrawElementsInstancedEXT", "DrawElementsInstancedNV", "DrawElementsInstancedANGLE", nullptr }
+            { (PRFuncPtr*) &mSymbols.fDrawArraysInstanced, {{ "glDrawArraysInstancedARB", "glDrawArraysInstancedEXT", "glDrawArraysInstancedNV", "glDrawArraysInstancedANGLE" }} },
+            { (PRFuncPtr*) &mSymbols.fDrawElementsInstanced, {{ "glDrawElementsInstancedARB", "glDrawElementsInstancedEXT", "glDrawElementsInstancedNV", "glDrawElementsInstancedANGLE" }}
             },
             END_SYMBOLS
         };
         fnLoadFeatureByCore(coreSymbols, extSymbols, GLFeature::draw_instanced);
     }
 
     if (IsSupported(GLFeature::instanced_arrays)) {
         const SymLoadStruct coreSymbols[] = {
-            { (PRFuncPtr*) &mSymbols.fVertexAttribDivisor, { "VertexAttribDivisor", nullptr } },
+            { (PRFuncPtr*) &mSymbols.fVertexAttribDivisor, {{ "glVertexAttribDivisor" }} },
             END_SYMBOLS
         };
         const SymLoadStruct extSymbols[] = {
-            { (PRFuncPtr*) &mSymbols.fVertexAttribDivisor, { "VertexAttribDivisorARB", "VertexAttribDivisorNV", "VertexAttribDivisorANGLE", nullptr } },
+            { (PRFuncPtr*) &mSymbols.fVertexAttribDivisor, {{ "glVertexAttribDivisorARB", "glVertexAttribDivisorNV", "glVertexAttribDivisorANGLE" }} },
             END_SYMBOLS
         };
         fnLoadFeatureByCore(coreSymbols, extSymbols, GLFeature::instanced_arrays);
     }
 
     if (IsSupported(GLFeature::texture_storage)) {
         const SymLoadStruct coreSymbols[] = {
-            { (PRFuncPtr*) &mSymbols.fTexStorage2D, { "TexStorage2D", nullptr } },
-            { (PRFuncPtr*) &mSymbols.fTexStorage3D, { "TexStorage3D", nullptr } },
+            { (PRFuncPtr*) &mSymbols.fTexStorage2D, {{ "glTexStorage2D" }} },
+            { (PRFuncPtr*) &mSymbols.fTexStorage3D, {{ "glTexStorage3D" }} },
             END_SYMBOLS
         };
         const SymLoadStruct extSymbols[] = {
-            { (PRFuncPtr*) &mSymbols.fTexStorage2D, { "TexStorage2DEXT", nullptr } },
-            { (PRFuncPtr*) &mSymbols.fTexStorage3D, { "TexStorage3DEXT", nullptr } },
+            { (PRFuncPtr*) &mSymbols.fTexStorage2D, {{ "glTexStorage2DEXT" }} },
+            { (PRFuncPtr*) &mSymbols.fTexStorage3D, {{ "glTexStorage3DEXT" }} },
             END_SYMBOLS
         };
         fnLoadFeatureByCore(coreSymbols, extSymbols, GLFeature::texture_storage);
     }
 
     if (IsSupported(GLFeature::sampler_objects)) {
         const SymLoadStruct symbols[] = {
-            { (PRFuncPtr*) &mSymbols.fGenSamplers, { "GenSamplers", nullptr } },
-            { (PRFuncPtr*) &mSymbols.fDeleteSamplers, { "DeleteSamplers", nullptr } },
-            { (PRFuncPtr*) &mSymbols.fIsSampler, { "IsSampler", nullptr } },
-            { (PRFuncPtr*) &mSymbols.fBindSampler, { "BindSampler", nullptr } },
-            { (PRFuncPtr*) &mSymbols.fSamplerParameteri, { "SamplerParameteri", nullptr } },
-            { (PRFuncPtr*) &mSymbols.fSamplerParameteriv, { "SamplerParameteriv", nullptr } },
-            { (PRFuncPtr*) &mSymbols.fSamplerParameterf, { "SamplerParameterf", nullptr } },
-            { (PRFuncPtr*) &mSymbols.fSamplerParameterfv, { "SamplerParameterfv", nullptr } },
-            { (PRFuncPtr*) &mSymbols.fGetSamplerParameteriv, { "GetSamplerParameteriv", nullptr } },
-            { (PRFuncPtr*) &mSymbols.fGetSamplerParameterfv, { "GetSamplerParameterfv", nullptr } },
+            { (PRFuncPtr*) &mSymbols.fGenSamplers, {{ "glGenSamplers" }} },
+            { (PRFuncPtr*) &mSymbols.fDeleteSamplers, {{ "glDeleteSamplers" }} },
+            { (PRFuncPtr*) &mSymbols.fIsSampler, {{ "glIsSampler" }} },
+            { (PRFuncPtr*) &mSymbols.fBindSampler, {{ "glBindSampler" }} },
+            { (PRFuncPtr*) &mSymbols.fSamplerParameteri, {{ "glSamplerParameteri" }} },
+            { (PRFuncPtr*) &mSymbols.fSamplerParameteriv, {{ "glSamplerParameteriv" }} },
+            { (PRFuncPtr*) &mSymbols.fSamplerParameterf, {{ "glSamplerParameterf" }} },
+            { (PRFuncPtr*) &mSymbols.fSamplerParameterfv, {{ "glSamplerParameterfv" }} },
+            { (PRFuncPtr*) &mSymbols.fGetSamplerParameteriv, {{ "glGetSamplerParameteriv" }} },
+            { (PRFuncPtr*) &mSymbols.fGetSamplerParameterfv, {{ "glGetSamplerParameterfv" }} },
             END_SYMBOLS
         };
         fnLoadForFeature(symbols, GLFeature::sampler_objects);
     }
 
     // ARB_transform_feedback2/NV_transform_feedback2 is a
     // superset of EXT_transform_feedback/NV_transform_feedback
     // and adds glPauseTransformFeedback &
     // glResumeTransformFeedback, which are required for WebGL2.
     if (IsSupported(GLFeature::transform_feedback2)) {
         const SymLoadStruct coreSymbols[] = {
-            { (PRFuncPtr*) &mSymbols.fBindBufferBase, { "BindBufferBase", nullptr } },
-            { (PRFuncPtr*) &mSymbols.fBindBufferRange, { "BindBufferRange", nullptr } },
-            { (PRFuncPtr*) &mSymbols.fGenTransformFeedbacks, { "GenTransformFeedbacks", nullptr } },
-            { (PRFuncPtr*) &mSymbols.fBindTransformFeedback, { "BindTransformFeedback", nullptr } },
-            { (PRFuncPtr*) &mSymbols.fDeleteTransformFeedbacks, { "DeleteTransformFeedbacks", nullptr } },
-            { (PRFuncPtr*) &mSymbols.fIsTransformFeedback, { "IsTransformFeedback", nullptr } },
-            { (PRFuncPtr*) &mSymbols.fBeginTransformFeedback, { "BeginTransformFeedback", nullptr } },
-            { (PRFuncPtr*) &mSymbols.fEndTransformFeedback, { "EndTransformFeedback", nullptr } },
-            { (PRFuncPtr*) &mSymbols.fTransformFeedbackVaryings, { "TransformFeedbackVaryings", nullptr } },
-            { (PRFuncPtr*) &mSymbols.fGetTransformFeedbackVarying, { "GetTransformFeedbackVarying", nullptr } },
-            { (PRFuncPtr*) &mSymbols.fPauseTransformFeedback, { "PauseTransformFeedback", nullptr } },
-            { (PRFuncPtr*) &mSymbols.fResumeTransformFeedback, { "ResumeTransformFeedback", nullptr } },
+            { (PRFuncPtr*) &mSymbols.fBindBufferBase, {{ "glBindBufferBase" }} },
+            { (PRFuncPtr*) &mSymbols.fBindBufferRange, {{ "glBindBufferRange" }} },
+            { (PRFuncPtr*) &mSymbols.fGenTransformFeedbacks, {{ "glGenTransformFeedbacks" }} },
+            { (PRFuncPtr*) &mSymbols.fBindTransformFeedback, {{ "glBindTransformFeedback" }} },
+            { (PRFuncPtr*) &mSymbols.fDeleteTransformFeedbacks, {{ "glDeleteTransformFeedbacks" }} },
+            { (PRFuncPtr*) &mSymbols.fIsTransformFeedback, {{ "glIsTransformFeedback" }} },
+            { (PRFuncPtr*) &mSymbols.fBeginTransformFeedback, {{ "glBeginTransformFeedback" }} },
+            { (PRFuncPtr*) &mSymbols.fEndTransformFeedback, {{ "glEndTransformFeedback" }} },
+            { (PRFuncPtr*) &mSymbols.fTransformFeedbackVaryings, {{ "glTransformFeedbackVaryings" }} },
+            { (PRFuncPtr*) &mSymbols.fGetTransformFeedbackVarying, {{ "glGetTransformFeedbackVarying" }} },
+            { (PRFuncPtr*) &mSymbols.fPauseTransformFeedback, {{ "glPauseTransformFeedback" }} },
+            { (PRFuncPtr*) &mSymbols.fResumeTransformFeedback, {{ "glResumeTransformFeedback" }} },
             END_SYMBOLS
         };
         const SymLoadStruct extSymbols[] = {
-            { (PRFuncPtr*) &mSymbols.fBindBufferBase, { "BindBufferBaseEXT", "BindBufferBaseNV", nullptr } },
-            { (PRFuncPtr*) &mSymbols.fBindBufferRange, { "BindBufferRangeEXT", "BindBufferRangeNV", nullptr } },
-            { (PRFuncPtr*) &mSymbols.fGenTransformFeedbacks, { "GenTransformFeedbacksNV", nullptr } },
-            { (PRFuncPtr*) &mSymbols.fBindTransformFeedback, { "BindTransformFeedbackNV", nullptr } },
-            { (PRFuncPtr*) &mSymbols.fDeleteTransformFeedbacks, { "DeleteTransformFeedbacksNV", nullptr } },
-            { (PRFuncPtr*) &mSymbols.fIsTransformFeedback, { "IsTransformFeedbackNV", nullptr } },
-            { (PRFuncPtr*) &mSymbols.fBeginTransformFeedback, { "BeginTransformFeedbackEXT", "BeginTransformFeedbackNV", nullptr } },
-            { (PRFuncPtr*) &mSymbols.fEndTransformFeedback, { "EndTransformFeedbackEXT", "EndTransformFeedbackNV", nullptr } },
-            { (PRFuncPtr*) &mSymbols.fTransformFeedbackVaryings, { "TransformFeedbackVaryingsEXT", "TransformFeedbackVaryingsNV", nullptr } },
-            { (PRFuncPtr*) &mSymbols.fGetTransformFeedbackVarying, { "GetTransformFeedbackVaryingEXT", "GetTransformFeedbackVaryingNV", nullptr } },
-            { (PRFuncPtr*) &mSymbols.fPauseTransformFeedback, { "PauseTransformFeedbackNV", nullptr } },
-            { (PRFuncPtr*) &mSymbols.fResumeTransformFeedback, { "ResumeTransformFeedbackNV", nullptr } },
+            { (PRFuncPtr*) &mSymbols.fBindBufferBase, {{ "glBindBufferBaseEXT", "glBindBufferBaseNV" }} },
+            { (PRFuncPtr*) &mSymbols.fBindBufferRange, {{ "glBindBufferRangeEXT", "glBindBufferRangeNV" }} },
+            { (PRFuncPtr*) &mSymbols.fGenTransformFeedbacks, {{ "glGenTransformFeedbacksNV" }} },
+            { (PRFuncPtr*) &mSymbols.fBindTransformFeedback, {{ "glBindTransformFeedbackNV" }} },
+            { (PRFuncPtr*) &mSymbols.fDeleteTransformFeedbacks, {{ "glDeleteTransformFeedbacksNV" }} },
+            { (PRFuncPtr*) &mSymbols.fIsTransformFeedback, {{ "glIsTransformFeedbackNV" }} },
+            { (PRFuncPtr*) &mSymbols.fBeginTransformFeedback, {{ "glBeginTransformFeedbackEXT", "glBeginTransformFeedbackNV" }} },
+            { (PRFuncPtr*) &mSymbols.fEndTransformFeedback, {{ "glEndTransformFeedbackEXT", "glEndTransformFeedbackNV" }} },
+            { (PRFuncPtr*) &mSymbols.fTransformFeedbackVaryings, {{ "glTransformFeedbackVaryingsEXT", "glTransformFeedbackVaryingsNV" }} },
+            { (PRFuncPtr*) &mSymbols.fGetTransformFeedbackVarying, {{ "glGetTransformFeedbackVaryingEXT", "glGetTransformFeedbackVaryingNV" }} },
+            { (PRFuncPtr*) &mSymbols.fPauseTransformFeedback, {{ "glPauseTransformFeedbackNV" }} },
+            { (PRFuncPtr*) &mSymbols.fResumeTransformFeedback, {{ "glResumeTransformFeedbackNV" }} },
             END_SYMBOLS
         };
         if (!fnLoadFeatureByCore(coreSymbols, extSymbols, GLFeature::texture_storage)) {
             // Also mark bind_buffer_offset as unsupported.
             MarkUnsupported(GLFeature::bind_buffer_offset);
         }
     }
 
     if (IsSupported(GLFeature::bind_buffer_offset)) {
         const SymLoadStruct coreSymbols[] = {
-            { (PRFuncPtr*) &mSymbols.fBindBufferOffset, { "BindBufferOffset", nullptr } },
+            { (PRFuncPtr*) &mSymbols.fBindBufferOffset, {{ "glBindBufferOffset" }} },
             END_SYMBOLS
         };
         const SymLoadStruct extSymbols[] = {
             { (PRFuncPtr*) &mSymbols.fBindBufferOffset,
-              { "BindBufferOffsetEXT", "BindBufferOffsetNV", nullptr }
+              {{ "glBindBufferOffsetEXT", "glBindBufferOffsetNV" }}
             },
             END_SYMBOLS
         };
         fnLoadFeatureByCore(coreSymbols, extSymbols, GLFeature::bind_buffer_offset);
     }
 
     if (IsSupported(GLFeature::query_counter)) {
         const SymLoadStruct coreSymbols[] = {
-            { (PRFuncPtr*) &mSymbols.fQueryCounter, { "QueryCounter", nullptr } },
+            { (PRFuncPtr*) &mSymbols.fQueryCounter, {{ "glQueryCounter" }} },
             END_SYMBOLS
         };
         const SymLoadStruct extSymbols[] = {
-            { (PRFuncPtr*) &mSymbols.fQueryCounter, { "QueryCounterEXT", "QueryCounterANGLE", nullptr } },
+            { (PRFuncPtr*) &mSymbols.fQueryCounter, {{ "glQueryCounterEXT", "glQueryCounterANGLE" }} },
             END_SYMBOLS
         };
         fnLoadFeatureByCore(coreSymbols, extSymbols, GLFeature::query_counter);
     }
 
     if (IsSupported(GLFeature::query_objects)) {
         const SymLoadStruct coreSymbols[] = {
-            { (PRFuncPtr*) &mSymbols.fBeginQuery, { "BeginQuery", nullptr } },
-            { (PRFuncPtr*) &mSymbols.fGenQueries, { "GenQueries", nullptr } },
-            { (PRFuncPtr*) &mSymbols.fDeleteQueries, { "DeleteQueries", nullptr } },
-            { (PRFuncPtr*) &mSymbols.fEndQuery, { "EndQuery", nullptr } },
-            { (PRFuncPtr*) &mSymbols.fGetQueryiv, { "GetQueryiv", nullptr } },
-            { (PRFuncPtr*) &mSymbols.fGetQueryObjectuiv, { "GetQueryObjectuiv", nullptr } },
-            { (PRFuncPtr*) &mSymbols.fIsQuery, { "IsQuery", nullptr } },
+            { (PRFuncPtr*) &mSymbols.fBeginQuery, {{ "glBeginQuery" }} },
+            { (PRFuncPtr*) &mSymbols.fGenQueries, {{ "glGenQueries" }} },
+            { (PRFuncPtr*) &mSymbols.fDeleteQueries, {{ "glDeleteQueries" }} },
+            { (PRFuncPtr*) &mSymbols.fEndQuery, {{ "glEndQuery" }} },
+            { (PRFuncPtr*) &mSymbols.fGetQueryiv, {{ "glGetQueryiv" }} },
+            { (PRFuncPtr*) &mSymbols.fGetQueryObjectuiv, {{ "glGetQueryObjectuiv" }} },
+            { (PRFuncPtr*) &mSymbols.fIsQuery, {{ "glIsQuery" }} },
             END_SYMBOLS
         };
         const SymLoadStruct extSymbols[] = {
-            { (PRFuncPtr*) &mSymbols.fBeginQuery, { "BeginQueryEXT", "BeginQueryANGLE", nullptr } },
-            { (PRFuncPtr*) &mSymbols.fGenQueries, { "GenQueriesEXT", "GenQueriesANGLE", nullptr } },
-            { (PRFuncPtr*) &mSymbols.fDeleteQueries, { "DeleteQueriesEXT", "DeleteQueriesANGLE", nullptr } },
-            { (PRFuncPtr*) &mSymbols.fEndQuery, { "EndQueryEXT", "EndQueryANGLE", nullptr } },
-            { (PRFuncPtr*) &mSymbols.fGetQueryiv, { "GetQueryivEXT", "GetQueryivANGLE", nullptr } },
-            { (PRFuncPtr*) &mSymbols.fGetQueryObjectuiv, { "GetQueryObjectuivEXT", "GetQueryObjectuivANGLE", nullptr } },
-            { (PRFuncPtr*) &mSymbols.fIsQuery, { "IsQueryEXT", "IsQueryANGLE", nullptr } },
+            { (PRFuncPtr*) &mSymbols.fBeginQuery, {{ "glBeginQueryEXT", "glBeginQueryANGLE" }} },
+            { (PRFuncPtr*) &mSymbols.fGenQueries, {{ "glGenQueriesEXT", "glGenQueriesANGLE" }} },
+            { (PRFuncPtr*) &mSymbols.fDeleteQueries, {{ "glDeleteQueriesEXT", "glDeleteQueriesANGLE" }} },
+            { (PRFuncPtr*) &mSymbols.fEndQuery, {{ "glEndQueryEXT", "glEndQueryANGLE" }} },
+            { (PRFuncPtr*) &mSymbols.fGetQueryiv, {{ "glGetQueryivEXT", "glGetQueryivANGLE" }} },
+            { (PRFuncPtr*) &mSymbols.fGetQueryObjectuiv, {{ "glGetQueryObjectuivEXT", "glGetQueryObjectuivANGLE" }} },
+            { (PRFuncPtr*) &mSymbols.fIsQuery, {{ "glIsQueryEXT", "glIsQueryANGLE" }} },
             END_SYMBOLS
         };
         if (!fnLoadFeatureByCore(coreSymbols, extSymbols, GLFeature::query_objects)) {
             MarkUnsupported(GLFeature::get_query_object_i64v);
             MarkUnsupported(GLFeature::get_query_object_iv);
             MarkUnsupported(GLFeature::occlusion_query);
             MarkUnsupported(GLFeature::occlusion_query_boolean);
             MarkUnsupported(GLFeature::occlusion_query2);
         }
     }
 
     if (IsSupported(GLFeature::get_query_object_i64v)) {
         const SymLoadStruct coreSymbols[] = {
-            { (PRFuncPtr*) &mSymbols.fGetQueryObjecti64v, { "GetQueryObjecti64v", nullptr } },
-            { (PRFuncPtr*) &mSymbols.fGetQueryObjectui64v, { "GetQueryObjectui64v", nullptr } },
+            { (PRFuncPtr*) &mSymbols.fGetQueryObjecti64v, {{ "glGetQueryObjecti64v" }} },
+            { (PRFuncPtr*) &mSymbols.fGetQueryObjectui64v, {{ "glGetQueryObjectui64v" }} },
             END_SYMBOLS
         };
         const SymLoadStruct extSymbols[] = {
-            { (PRFuncPtr*) &mSymbols.fGetQueryObjecti64v, { "GetQueryObjecti64vEXT", "GetQueryObjecti64vANGLE", nullptr } },
-            { (PRFuncPtr*) &mSymbols.fGetQueryObjectui64v, { "GetQueryObjectui64vEXT", "GetQueryObjectui64vANGLE", nullptr } },
+            { (PRFuncPtr*) &mSymbols.fGetQueryObjecti64v, {{ "glGetQueryObjecti64vEXT", "glGetQueryObjecti64vANGLE" }} },
+            { (PRFuncPtr*) &mSymbols.fGetQueryObjectui64v, {{ "glGetQueryObjectui64vEXT", "glGetQueryObjectui64vANGLE" }} },
             END_SYMBOLS
         };
         if (!fnLoadFeatureByCore(coreSymbols, extSymbols, GLFeature::get_query_object_i64v)) {
             MarkUnsupported(GLFeature::query_counter);
         }
     }
 
     if (IsSupported(GLFeature::get_query_object_iv)) {
         const SymLoadStruct coreSymbols[] = {
-            { (PRFuncPtr*) &mSymbols.fGetQueryObjectiv, { "GetQueryObjectiv", nullptr } },
+            { (PRFuncPtr*) &mSymbols.fGetQueryObjectiv, {{ "glGetQueryObjectiv" }} },
             END_SYMBOLS
         };
         const SymLoadStruct extSymbols[] = {
-            { (PRFuncPtr*) &mSymbols.fGetQueryObjectiv, { "GetQueryObjectivEXT", "GetQueryObjectivANGLE", nullptr } },
+            { (PRFuncPtr*) &mSymbols.fGetQueryObjectiv, {{ "glGetQueryObjectivEXT", "glGetQueryObjectivANGLE" }} },
             END_SYMBOLS
         };
         fnLoadFeatureByCore(coreSymbols, extSymbols, GLFeature::get_query_object_iv);
     }
 
     if (IsSupported(GLFeature::clear_buffers)) {
         const SymLoadStruct symbols[] = {
-            { (PRFuncPtr*) &mSymbols.fClearBufferfi,  { "ClearBufferfi",  nullptr } },
-            { (PRFuncPtr*) &mSymbols.fClearBufferfv,  { "ClearBufferfv",  nullptr } },
-            { (PRFuncPtr*) &mSymbols.fClearBufferiv,  { "ClearBufferiv",  nullptr } },
-            { (PRFuncPtr*) &mSymbols.fClearBufferuiv, { "ClearBufferuiv", nullptr } },
+            { (PRFuncPtr*) &mSymbols.fClearBufferfi,  {{ "glClearBufferfi",  }} },
+            { (PRFuncPtr*) &mSymbols.fClearBufferfv,  {{ "glClearBufferfv",  }} },
+            { (PRFuncPtr*) &mSymbols.fClearBufferiv,  {{ "glClearBufferiv",  }} },
+            { (PRFuncPtr*) &mSymbols.fClearBufferuiv, {{ "glClearBufferuiv" }} },
             END_SYMBOLS
         };
         fnLoadForFeature(symbols, GLFeature::clear_buffers);
     }
 
     if (IsSupported(GLFeature::copy_buffer)) {
         const SymLoadStruct symbols[] = {
-            { (PRFuncPtr*) &mSymbols.fCopyBufferSubData, { "CopyBufferSubData", nullptr } },
+            { (PRFuncPtr*) &mSymbols.fCopyBufferSubData, {{ "glCopyBufferSubData" }} },
             END_SYMBOLS
         };
         fnLoadForFeature(symbols, GLFeature::copy_buffer);
     }
 
     if (IsSupported(GLFeature::draw_buffers)) {
         const SymLoadStruct coreSymbols[] = {
-            { (PRFuncPtr*) &mSymbols.fDrawBuffers, { "DrawBuffers", nullptr } },
+            { (PRFuncPtr*) &mSymbols.fDrawBuffers, {{ "glDrawBuffers" }} },
             END_SYMBOLS
         };
         const SymLoadStruct extSymbols[] = {
-            { (PRFuncPtr*) &mSymbols.fDrawBuffers, { "DrawBuffersARB", "DrawBuffersEXT", nullptr } },
+            { (PRFuncPtr*) &mSymbols.fDrawBuffers, {{ "glDrawBuffersARB", "glDrawBuffersEXT" }} },
             END_SYMBOLS
         };
         fnLoadFeatureByCore(coreSymbols, extSymbols, GLFeature::draw_buffers);
     }
 
     if (IsSupported(GLFeature::draw_range_elements)) {
         const SymLoadStruct coreSymbols[] = {
-            { (PRFuncPtr*) &mSymbols.fDrawRangeElements, { "DrawRangeElements", nullptr } },
+            { (PRFuncPtr*) &mSymbols.fDrawRangeElements, {{ "glDrawRangeElements" }} },
             END_SYMBOLS
         };
         const SymLoadStruct extSymbols[] = {
-            { (PRFuncPtr*) &mSymbols.fDrawRangeElements, { "DrawRangeElementsEXT", nullptr } },
+            { (PRFuncPtr*) &mSymbols.fDrawRangeElements, {{ "glDrawRangeElementsEXT" }} },
             END_SYMBOLS
         };
         fnLoadFeatureByCore(coreSymbols, extSymbols, GLFeature::draw_range_elements);
     }
 
     if (IsSupported(GLFeature::get_integer_indexed)) {
         const SymLoadStruct coreSymbols[] = {
-            { (PRFuncPtr*) &mSymbols.fGetIntegeri_v, { "GetIntegeri_v", nullptr } },
+            { (PRFuncPtr*) &mSymbols.fGetIntegeri_v, {{ "glGetIntegeri_v" }} },
             END_SYMBOLS
         };
         const SymLoadStruct extSymbols[] ={
-            { (PRFuncPtr*) &mSymbols.fGetIntegeri_v, { "GetIntegerIndexedvEXT", nullptr } },
+            { (PRFuncPtr*) &mSymbols.fGetIntegeri_v, {{ "glGetIntegerIndexedvEXT" }} },