merge mozilla-central to mozilla-beta. a=merge l10n=pre-version-bump-merge CLOSED TREE
authorSebastian Hengst <archaeopteryx@coole-files.de>
Sat, 25 Aug 2018 19:11:37 +0300
changeset 491146 4c64041c3a305c8ebe2a0d9939faa866dba0df33
parent 491010 7e1be6d4f6d23e952b6e2e3e61e13b9869b3f59f (current diff)
parent 491145 aa9cb0d8ffbff15764cb86202bc4c9929dabf140 (diff)
child 491147 0c64d9000fabbf143f40ba8bc691e36ee929886c
push id1815
push userffxbld-merge
push dateMon, 15 Oct 2018 10:40:45 +0000
treeherdermozilla-release@18d4c09e9378 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmerge
milestone63.0
merge mozilla-central to mozilla-beta. a=merge l10n=pre-version-bump-merge CLOSED TREE
browser/locales/l10n-changesets.json
build/build-clang/clang-tidy-cxx14.patch
build/build-clang/llvm-debug-frame-for-5.patch
devtools/client/aboutdebugging-new/src/components/DebugTargetItem.css
devtools/client/aboutdebugging-new/src/components/DebugTargetItem.js
devtools/client/aboutdebugging-new/src/components/DebugTargetList.css
devtools/client/aboutdebugging-new/src/components/DebugTargetList.js
devtools/client/aboutdebugging-new/src/components/DebugTargetPane.js
editor/nsEditorCID.h
mobile/android/app/src/main/res/drawable-xxhdpi/exit_fullscreen.png
mobile/android/app/src/main/res/drawable-xxhdpi/fullscreen.png
mobile/android/base/java/org/mozilla/gecko/media/VideoPlayer.java
mobile/android/components/Snippets.js
testing/web-platform/meta/webdriver/tests/maximize_window/user_prompts.py.ini
testing/web-platform/meta/webdriver/tests/new_session/create_alwaysMatch.py.ini
testing/web-platform/meta/webdriver/tests/new_session/create_firstMatch.py.ini
testing/web-platform/tests/service-workers/service-worker/resources/update-top-level-worker.py
testing/web-platform/tests/service-workers/service-worker/update-top-level.https.html
tools/clang-tidy/test/misc-argument-comment.cpp
tools/clang-tidy/test/misc-argument-comment.json
tools/clang-tidy/test/misc-assert-side-effect.cpp
tools/clang-tidy/test/misc-assert-side-effect.json
tools/clang-tidy/test/misc-bool-pointer-implicit-conversion.cpp
tools/clang-tidy/test/misc-bool-pointer-implicit-conversion.json
tools/clang-tidy/test/misc-forward-declaration-namespace.cpp
tools/clang-tidy/test/misc-forward-declaration-namespace.json
tools/clang-tidy/test/misc-macro-repeated-side-effects.cpp
tools/clang-tidy/test/misc-macro-repeated-side-effects.json
tools/clang-tidy/test/misc-string-constructor.cpp
tools/clang-tidy/test/misc-string-constructor.json
tools/clang-tidy/test/misc-string-integer-assignment.cpp
tools/clang-tidy/test/misc-string-integer-assignment.json
tools/clang-tidy/test/misc-suspicious-missing-comma.cpp
tools/clang-tidy/test/misc-suspicious-missing-comma.json
tools/clang-tidy/test/misc-suspicious-semicolon.cpp
tools/clang-tidy/test/misc-suspicious-semicolon.json
tools/clang-tidy/test/misc-swapped-arguments.cpp
tools/clang-tidy/test/misc-swapped-arguments.json
tools/clang-tidy/test/misc-unused-raii.cpp
tools/clang-tidy/test/misc-unused-raii.json
--- a/.eslintignore
+++ b/.eslintignore
@@ -308,17 +308,16 @@ mobile/android/app/geckoview-prefs.js
 mobile/android/chrome/content/about.js
 
 # Not much JS to lint and non-standard at that
 mobile/android/installer/
 mobile/android/locales/
 
 # Non-standard `(catch ex if ...)`
 mobile/android/chrome/content/browser.js
-mobile/android/components/Snippets.js
 
 # Only contains non-standard test files.
 python/**
 
 # security/ exclusions (pref files).
 security/manager/ssl/security-prefs.js
 
 # NSS / taskcluster only.
--- a/accessible/html/HTMLFormControlAccessible.cpp
+++ b/accessible/html/HTMLFormControlAccessible.cpp
@@ -302,17 +302,21 @@ already_AddRefed<nsIPersistentProperties
 HTMLTextFieldAccessible::NativeAttributes()
 {
   nsCOMPtr<nsIPersistentProperties> attributes =
     HyperTextAccessibleWrap::NativeAttributes();
 
   // Expose type for text input elements as it gives some useful context,
   // especially for mobile.
   nsAutoString type;
-  if (mContent->AsElement()->GetAttr(kNameSpaceID_None, nsGkAtoms::type, type)) {
+  // In the case of input[type=number], mContent is anonymous and is an
+  // input[type=text]. Getting the root not-anonymous content will give
+  // us the right type. In case of other input types, this returns the same node.
+  nsIContent* content = mContent->FindFirstNonChromeOnlyAccessContent();
+  if (content->AsElement()->GetAttr(kNameSpaceID_None, nsGkAtoms::type, type)) {
     nsAccUtils::SetAccAttr(attributes, nsGkAtoms::textInputType, type);
     if (!ARIARoleMap() && type.EqualsLiteral("search")) {
       nsAccUtils::SetAccAttr(attributes, nsGkAtoms::xmlroles,
                              NS_LITERAL_STRING("searchbox"));
     }
   }
 
   return attributes.forget();
--- a/accessible/tests/mochitest/attributes/test_obj.html
+++ b/accessible/tests/mochitest/attributes/test_obj.html
@@ -109,16 +109,17 @@ https://bugzilla.mozilla.org/show_bug.cg
       // text input type
       testAbsentAttrs("button", { "text-input-type": "button"});
       testAbsentAttrs("checkbox", { "text-input-type": "checkbox"});
       testAbsentAttrs("radio", { "text-input-type": "radio"});
       testAttrs("email", {"text-input-type": "email"}, true);
       testAttrs("search", {"text-input-type": "search"}, true);
       testAttrs("tel", {"text-input-type": "tel"}, true);
       testAttrs("url", {"text-input-type": "url"}, true);
+      testAttrs(getAccessible("number").firstChild, {"text-input-type": "number"}, true);
 
       // ARIA
       testAttrs("searchbox", {"text-input-type": "search"}, true);
 
       // html
       testAttrs("radio", {"checkable": "true"}, true);
       testAttrs("checkbox", {"checkable": "true"}, true);
       testAttrs("draggable", {"draggable": "true"}, true);
@@ -215,16 +216,17 @@ https://bugzilla.mozilla.org/show_bug.cg
   </div>
 
   <!-- text input type -->
   <input id="button" type="button"/>
   <input id="email" type="email"/>
   <input id="search" type="search"/>
   <input id="tel" type="tel"/>
   <input id="url" type="url"/>
+  <input id="number" type="number"/>
   <div id="searchbox" role="searchbox"></div>
 
   <!-- html -->
   <input id="radio" type="radio"/>
   <input id="checkbox" type="checkbox"/>
   <div id="draggable" draggable="true">Draggable div</div>
   <table>
     <tr>
--- a/browser/base/content/browser-contentblocking.js
+++ b/browser/base/content/browser-contentblocking.js
@@ -236,21 +236,20 @@ var ContentBlocking = {
     this.icon = $("#tracking-protection-icon");
     this.iconBox = $("#tracking-protection-icon-box");
     this.animatedIcon = $("#tracking-protection-icon-animatable-image");
     this.animatedIcon.addEventListener("animationend", () => this.iconBox.removeAttribute("animate"));
 
     this.identityPopupMultiView = $("#identity-popup-multiView");
     this.reportBreakageButton = $("#identity-popup-content-blocking-report-breakage");
     this.reportBreakageURL = $("#identity-popup-breakageReportView-collection-url");
-    this.reportBreakageUA = $("#identity-popup-breakageReportView-collection-userAgent");
     this.reportBreakageLearnMore = $("#identity-popup-breakageReportView-learn-more");
 
     let baseURL = Services.urlFormatter.formatURLPref("app.support.baseURL");
-    this.reportBreakageLearnMore.href = baseURL + "tracking-protection-pbm";
+    this.reportBreakageLearnMore.href = baseURL + "blocking-breakage";
 
     this.updateReportBreakageUI = () => {
       this.reportBreakageButton.hidden = !Services.prefs.getBoolPref(this.PREF_REPORT_BREAKAGE_ENABLED);
     };
 
     this.updateReportBreakageUI();
 
     Services.prefs.addObserver(this.PREF_REPORT_BREAKAGE_ENABLED, this.updateReportBreakageUI);
@@ -351,17 +350,17 @@ var ContentBlocking = {
     }
 
     let formData = new FormData();
     formData.set("title", this.reportURI.host);
 
     // Leave the ? at the end of the URL to signify that this URL had its query stripped.
     let urlWithoutQuery = this.reportURI.asciiSpec.replace(this.reportURI.query, "");
     let body = `Full URL: ${urlWithoutQuery}\n`;
-    body += `userAgent: ${this.reportBreakageUA.textContent}\n`;
+    body += `userAgent: ${navigator.userAgent}\n`;
 
     body += "\n**Preferences**\n";
     body += `${TrackingProtection.PREF_ENABLED_GLOBALLY}: ${Services.prefs.getBoolPref(TrackingProtection.PREF_ENABLED_GLOBALLY)}\n`;
     body += `${TrackingProtection.PREF_ENABLED_IN_PRIVATE_WINDOWS}: ${Services.prefs.getBoolPref(TrackingProtection.PREF_ENABLED_IN_PRIVATE_WINDOWS)}\n`;
     body += `${TrackingProtection.PREF_UI_ENABLED}: ${Services.prefs.getBoolPref(TrackingProtection.PREF_UI_ENABLED)}\n`;
     body += `urlclassifier.trackingTable: ${Services.prefs.getStringPref("urlclassifier.trackingTable")}\n`;
     body += `network.http.referer.defaultPolicy: ${Services.prefs.getIntPref("network.http.referer.defaultPolicy")}\n`;
     body += `network.http.referer.defaultPolicy.pbmode: ${Services.prefs.getIntPref("network.http.referer.defaultPolicy.pbmode")}\n`;
@@ -390,17 +389,16 @@ var ContentBlocking = {
   },
 
   showReportBreakageSubview() {
     // Save this URI to make sure that the user really only submits the location
     // they see in the report breakage dialog.
     this.reportURI = gBrowser.currentURI;
     let urlWithoutQuery = this.reportURI.asciiSpec.replace("?" + this.reportURI.query, "");
     this.reportBreakageURL.textContent = urlWithoutQuery;
-    this.reportBreakageUA.textContent = navigator.userAgent;
     this.identityPopupMultiView.showSubView("identity-popup-breakageReportView");
   },
 
   eventsHistogramAdd(value) {
     if (PrivateBrowsingUtils.isWindowPrivate(window)) {
       return;
     }
     Services.telemetry.getHistogramById("TRACKING_PROTECTION_EVENTS").add(value);
@@ -442,39 +440,41 @@ var ContentBlocking = {
     // This state will be overriden later if there's an exception set for this site.
     let active = this.enabled && detected;
 
     for (let blocker of this.blockers) {
       blocker.categoryItem.classList.toggle("blocked", this.enabled && blocker.enabled);
       blocker.categoryItem.hidden = !blocker.visible;
     }
 
+    let isBrowserPrivate = PrivateBrowsingUtils.isBrowserPrivate(gBrowser.selectedBrowser);
+
     // Check whether the user has added an exception for this site.
-    let type = PrivateBrowsingUtils.isBrowserPrivate(gBrowser.selectedBrowser) ?
-                 "trackingprotection-pb" :
-                 "trackingprotection";
+    let type =  isBrowserPrivate ? "trackingprotection-pb" : "trackingprotection";
     let hasException = Services.perms.testExactPermission(baseURI, type) ==
       Services.perms.ALLOW_ACTION;
 
     this.content.toggleAttribute("detected", detected);
     this.content.toggleAttribute("hasException", hasException);
 
     this.iconBox.toggleAttribute("active", active);
     this.iconBox.toggleAttribute("hasException", this.enabled && hasException);
 
     if (isSimulated) {
       this.iconBox.removeAttribute("animate");
     } else if (active && webProgress.isTopLevel) {
       this.iconBox.setAttribute("animate", "true");
 
-      let introCount = Services.prefs.getIntPref(this.prefIntroCount);
-      if (introCount < this.MAX_INTROS) {
-        Services.prefs.setIntPref(this.prefIntroCount, ++introCount);
-        Services.prefs.savePrefFile(null);
-        this.showIntroPanel();
+      if (!isBrowserPrivate) {
+        let introCount = Services.prefs.getIntPref(this.prefIntroCount);
+        if (introCount < this.MAX_INTROS) {
+          Services.prefs.setIntPref(this.prefIntroCount, ++introCount);
+          Services.prefs.savePrefFile(null);
+          this.showIntroPanel();
+        }
       }
     }
 
     if (hasException) {
       this.iconBox.setAttribute("tooltiptext", this.disabledTooltipText);
       this.shieldHistogramAdd(1);
     } else if (active) {
       this.iconBox.setAttribute("tooltiptext", this.activeTooltipText);
--- a/browser/base/content/browser-pageActions.js
+++ b/browser/base/content/browser-pageActions.js
@@ -1231,17 +1231,17 @@ BrowserPageActions.shareURL = {
       item.setAttribute("label", share.menuItemTitle);
       item.setAttribute("share-name", share.name);
       item.setAttribute("image", share.image);
       item.classList.add("subviewbutton", "subviewbutton-iconic");
       item.addEventListener("command", onCommand);
       fragment.appendChild(item);
     });
 
-    let item = document.createElement("toolbarbutton");
+    let item = document.createXULElement("toolbarbutton");
     item.setAttribute("label", BrowserPageActions.panelNode.getAttribute("shareMore-label"));
     item.classList.add("subviewbutton", "subviewbutton-iconic", "share-more-button");
     item.addEventListener("command", onCommand);
     fragment.appendChild(item);
 
     while (bodyNode.firstChild) {
       bodyNode.firstChild.remove();
     }
--- a/browser/base/content/browser.css
+++ b/browser/base/content/browser.css
@@ -592,25 +592,31 @@ html|input.urlbar-scheme {
 }
 
 /* Visible if the urlbar is not focused and it overflows at the start.
    Uses the required-valid trick to check if it contains a value */
 html|input.urlbar-scheme[textoverflow="start"]:not([focused]):valid {
   visibility: visible;
 }
 
-/* Fade out URL on overflow */
+/* Fade out URL on overflow
+   This mask may be overriden when a Contextual Feature Recommendation is shown,
+   see browser/themes/shared/urlbar-searchbar.inc.css for details */
 html|input.urlbar-input[textoverflow="end"]:not([focused]) {
   mask-image: linear-gradient(to left, transparent, black 3ch);
 }
 
 html|input.urlbar-input[textoverflow="start"]:not([focused]) {
   mask-image: linear-gradient(to right, transparent var(--urlbar-scheme-size), black calc(var(--urlbar-scheme-size) + 3ch));
 }
 
+html|input.urlbar-input:not([focused]) {
+  mask-repeat: no-repeat;
+}
+
 /* Apply crisp rendering for favicons at exactly 2dppx resolution */
 @media (resolution: 2dppx) {
   .searchbar-engine-image {
     image-rendering: -moz-crisp-edges;
   }
 }
 
 /* Always show URLs LTR. */
--- a/browser/base/content/browser.js
+++ b/browser/base/content/browser.js
@@ -1136,29 +1136,16 @@ function RedirectLoad({ target: browser,
         LoadInOtherProcess(browser, data.loadOptions, data.historyIndex);
       }
     };
     Services.obs.addObserver(delayedStartupFinished,
                              "browser-delayed-startup-finished");
   }
 }
 
-if (document.documentElement.getAttribute("windowtype") == "navigator:browser") {
-  window.addEventListener("MozBeforeInitialXULLayout", () => {
-    gBrowserInit.onBeforeInitialXULLayout();
-  }, { once: true });
-  // The listener of DOMContentLoaded must be set on window, rather than
-  // document, because the window can go away before the event is fired.
-  // In that case, we don't want to initialize anything, otherwise we
-  // may be leaking things because they will never be destroyed after.
-  window.addEventListener("DOMContentLoaded", () => {
-    gBrowserInit.onDOMContentLoaded();
-  }, { once: true });
-}
-
 let _resolveDelayedStartup;
 var delayedStartupPromise = new Promise(resolve => {
   _resolveDelayedStartup = resolve;
 });
 
 var gBrowserInit = {
   delayedStartupFinished: false,
   idleTasksFinished: false,
@@ -5450,17 +5437,23 @@ nsBrowserAccess.prototype = {
         // referrer like the other loads do?
         var url = aURI ? aURI.spec : "about:blank";
         let features = "all,dialog=no";
         if (isPrivate) {
           features += ",private";
         }
         // Pass all params to openDialog to ensure that "url" isn't passed through
         // loadOneOrMoreURIs, which splits based on "|"
-        newWindow = openDialog(AppConstants.BROWSER_CHROME_URL, "_blank", features, url, null, null, null);
+        try {
+          newWindow = openDialog(AppConstants.BROWSER_CHROME_URL, "_blank", features,
+                      // window.arguments
+                      url, null, null, null, null, null, null, null, aTriggeringPrincipal);
+        } catch (ex) {
+          Cu.reportError(ex);
+        }
         break;
       case Ci.nsIBrowserDOMWindow.OPEN_NEWTAB :
         // If we have an opener, that means that the caller is expecting access
         // to the nsIDOMWindow of the opened tab right away. For e10s windows,
         // this means forcing the newly opened browser to be non-remote so that
         // we can hand back the nsIDOMWindow. The XULBrowserWindow.shouldLoadURI
         // will do the job of shuttling off the newly opened browser to run in
         // the right process once it starts loading a URI.
--- a/browser/base/content/browser.xul
+++ b/browser/base/content/browser.xul
@@ -32,17 +32,16 @@
 ]>
 
 <window id="main-window"
         xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
         xmlns:svg="http://www.w3.org/2000/svg"
         xmlns:html="http://www.w3.org/1999/xhtml"
         xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
         xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
-        onload="gBrowserInit.onLoad()" onunload="gBrowserInit.onUnload()" onclose="return WindowIsClosing();"
         title="&mainWindow.title;"
         title_normal="&mainWindow.title;"
 #ifdef XP_MACOSX
         title_privatebrowsing="&mainWindow.title;&mainWindow.titlemodifiermenuseparator;&mainWindow.titlePrivateBrowsingSuffix;"
         titledefault="&mainWindow.title;"
         titlemodifier=""
         titlemodifier_normal=""
         titlemodifier_privatebrowsing="&mainWindow.titlePrivateBrowsingSuffix;"
@@ -74,16 +73,33 @@
 
 <script type="application/javascript"
 #ifdef BROWSER_XHTML
 xmlns="http://www.w3.org/1999/xhtml"
 #endif
 >
   Services.scriptloader.loadSubScript("chrome://global/content/contentAreaUtils.js", this);
   Services.scriptloader.loadSubScript("chrome://browser/content/tabbrowser.js", this);
+
+  window.onload = gBrowserInit.onLoad.bind(gBrowserInit);
+  window.onunload = gBrowserInit.onUnload.bind(gBrowserInit);
+  window.onclose = WindowIsClosing;
+#ifdef BROWSER_XHTML
+  window.addEventListener("DOMContentLoaded",
+    gBrowserInit.onBeforeInitialXULLayout.bind(gBrowserInit), { once: true });
+#else
+  window.addEventListener("MozBeforeInitialXULLayout",
+    gBrowserInit.onBeforeInitialXULLayout.bind(gBrowserInit), { once: true });
+#endif
+  // The listener of DOMContentLoaded must be set on window, rather than
+  // document, because the window can go away before the event is fired.
+  // In that case, we don't want to initialize anything, otherwise we
+  // may be leaking things because they will never be destroyed after.
+  window.addEventListener("DOMContentLoaded",
+    gBrowserInit.onDOMContentLoaded.bind(gBrowserInit), { once: true });
 </script>
 
 # All sets except for popupsets (commands, keys, and stringbundles)
 # *must* go into the browser-sets.inc file so that they can be shared with other
 # top level windows in macWindow.inc.xul.
 #include browser-sets.inc
 
   <popupset id="mainPopupSet">
@@ -901,16 +917,24 @@ xmlns="http://www.w3.org/1999/xhtml"
                   <label id="identity-icon-country-label" class="plain"/>
                 </hbox>
               </box>
               <box id="urlbar-display-box" align="center">
                 <label id="switchtab" class="urlbar-display urlbar-display-switchtab" value="&urlbar.switchToTab.label;"/>
                 <label id="extension" class="urlbar-display urlbar-display-extension" value="&urlbar.extension.label;"/>
               </box>
               <hbox id="page-action-buttons" context="pageActionContextMenu">
+                <hbox id="contextual-feature-recommendation" role="button" hidden="true">
+                  <hbox id="cfr-label-container">
+                    <label id="cfr-label"/>
+                  </hbox>
+                  <image id="cfr-button"
+                         class="urlbar-icon urlbar-page-action"
+                         role="presentation"/>
+                </hbox>
                 <hbox id="userContext-icons" hidden="true">
                   <label id="userContext-label"/>
                   <image id="userContext-indicator"/>
                 </hbox>
                 <image id="reader-mode-button"
                        class="urlbar-icon urlbar-page-action"
                        tooltip="dynamic-shortcut-tooltip"
                        role="button"
--- a/browser/base/content/pageinfo/feeds.js
+++ b/browser/base/content/pageinfo/feeds.js
@@ -12,48 +12,48 @@ function initFeedTab(feeds) {
     addRow(name, type, url);
   }
 
   const feedListbox = document.getElementById("feedListbox");
   document.getElementById("feedTab").hidden = feedListbox.getRowCount() == 0;
 }
 
 function addRow(name, type, url) {
-  const item = document.createElement("richlistitem");
+  const item = document.createXULElement("richlistitem");
 
-  const top = document.createElement("hbox");
+  const top = document.createXULElement("hbox");
   top.setAttribute("flex", "1");
   item.appendChild(top);
 
-  const bottom = document.createElement("hbox");
+  const bottom = document.createXULElement("hbox");
   bottom.setAttribute("flex", "1");
   item.appendChild(bottom);
 
-  const nameLabel = document.createElement("label");
+  const nameLabel = document.createXULElement("label");
   nameLabel.className = "feedTitle";
   nameLabel.textContent = name;
   nameLabel.setAttribute("flex", "1");
   top.appendChild(nameLabel);
 
-  const typeLabel = document.createElement("label");
+  const typeLabel = document.createXULElement("label");
   typeLabel.textContent = type;
   top.appendChild(typeLabel);
 
-  const urlContainer = document.createElement("hbox");
+  const urlContainer = document.createXULElement("hbox");
   urlContainer.setAttribute("flex", "1");
   bottom.appendChild(urlContainer);
 
-  const urlLabel = document.createElement("label");
+  const urlLabel = document.createXULElement("label");
   urlLabel.className = "text-link";
   urlLabel.textContent = url;
   urlLabel.setAttribute("tooltiptext", url);
   urlLabel.addEventListener("click", ev => openUILink(this.value, ev, {triggeringPrincipal: Services.scriptSecurityManager.createNullPrincipal({})}));
   urlContainer.appendChild(urlLabel);
 
-  const subscribeButton = document.createElement("button");
+  const subscribeButton = document.createXULElement("button");
   subscribeButton.className = "feed-subscribe";
   subscribeButton.addEventListener("click",
     () => openWebLinkIn(url, "current", { ignoreAlt: true }));
   subscribeButton.setAttribute("label", gBundle.getString("feedSubscribe"));
   subscribeButton.setAttribute("accesskey", gBundle.getString("feedSubscribe.accesskey"));
   bottom.appendChild(subscribeButton);
 
   document.getElementById("feedListbox").appendChild(item);
--- a/browser/base/content/pageinfo/permissions.js
+++ b/browser/base/content/pageinfo/permissions.js
@@ -109,51 +109,51 @@ function createRow(aPartId) {
   let rowId = "perm-" + aPartId + "-row";
   if (document.getElementById(rowId))
     return;
 
   let commandId = "cmd_" + aPartId + "Toggle";
   let labelId = "perm-" + aPartId + "-label";
   let radiogroupId = aPartId + "RadioGroup";
 
-  let command = document.createElement("command");
+  let command = document.createXULElement("command");
   command.setAttribute("id", commandId);
   command.setAttribute("oncommand", "onRadioClick('" + aPartId + "');");
   document.getElementById("pageInfoCommandSet").appendChild(command);
 
-  let row = document.createElement("vbox");
+  let row = document.createXULElement("vbox");
   row.setAttribute("id", rowId);
   row.setAttribute("class", "permission");
 
-  let label = document.createElement("label");
+  let label = document.createXULElement("label");
   label.setAttribute("id", labelId);
   label.setAttribute("control", radiogroupId);
   label.setAttribute("value", SitePermissions.getPermissionLabel(aPartId));
   label.setAttribute("class", "permissionLabel");
   row.appendChild(label);
 
-  let controls = document.createElement("hbox");
+  let controls = document.createXULElement("hbox");
   controls.setAttribute("role", "group");
   controls.setAttribute("aria-labelledby", labelId);
 
-  let checkbox = document.createElement("checkbox");
+  let checkbox = document.createXULElement("checkbox");
   checkbox.setAttribute("id", aPartId + "Def");
   checkbox.setAttribute("oncommand", "onCheckboxClick('" + aPartId + "');");
   checkbox.setAttribute("label", gBundle.getString("permissions.useDefault"));
   controls.appendChild(checkbox);
 
-  let spacer = document.createElement("spacer");
+  let spacer = document.createXULElement("spacer");
   spacer.setAttribute("flex", "1");
   controls.appendChild(spacer);
 
-  let radiogroup = document.createElement("radiogroup");
+  let radiogroup = document.createXULElement("radiogroup");
   radiogroup.setAttribute("id", radiogroupId);
   radiogroup.setAttribute("orient", "horizontal");
   for (let state of SitePermissions.getAvailableStates(aPartId)) {
-    let radio = document.createElement("radio");
+    let radio = document.createXULElement("radio");
     radio.setAttribute("id", aPartId + "#" + state);
     radio.setAttribute("label", SitePermissions.getMultichoiceStateLabel(state));
     radio.setAttribute("command", commandId);
     radiogroup.appendChild(radio);
   }
   controls.appendChild(radiogroup);
 
   row.appendChild(controls);
--- a/browser/base/content/tabbrowser.xml
+++ b/browser/base/content/tabbrowser.xml
@@ -548,17 +548,18 @@
                 scrollButtonWidth: arrowScrollbox._scrollButtonDown.getBoundingClientRect().width
               };
             }
 
             let width = 0;
             for (let i = numPinned - 1; i >= 0; i--) {
               let tab = this.children[i];
               width += layoutData.pinnedTabWidth;
-              tab.style.marginInlineStart = -(width + layoutData.scrollButtonWidth) + "px";
+              tab.style.setProperty("margin-inline-start",
+                -(width + layoutData.scrollButtonWidth) + "px", "important");
               tab._pinnedUnscrollable = true;
             }
             this.style.paddingInlineStart = width + "px";
           } else {
             this.removeAttribute("positionpinnedtabs");
 
             for (let i = 0; i < numPinned; i++) {
               let tab = this.children[i];
@@ -1270,17 +1271,17 @@
               dt.updateDragImage(canvas, dragImageOffset, dragImageOffset);
             };
           } else {
             // Create a panel to use it in setDragImage
             // which will tell xul to render a panel that follows
             // the pointer while a dnd session is on.
             if (!this._dndPanel) {
               this._dndCanvas = canvas;
-              this._dndPanel = document.createElement("panel");
+              this._dndPanel = document.createXULElement("panel");
               this._dndPanel.className = "dragfeedback-tab";
               this._dndPanel.setAttribute("type", "drag");
               let wrapper = document.createElementNS("http://www.w3.org/1999/xhtml", "div");
               wrapper.style.width = "160px";
               wrapper.style.height = "90px";
               wrapper.appendChild(canvas);
               this._dndPanel.appendChild(wrapper);
               document.documentElement.appendChild(this._dndPanel);
--- a/browser/base/content/test/contextMenu/browser_utilityOverlay.js
+++ b/browser/base/content/test/contextMenu/browser_utilityOverlay.js
@@ -31,42 +31,42 @@ function test_eventMatchesKey() {
   let checkEvent = function(e) {
     e.stopPropagation();
     e.preventDefault();
     eventMatchResult = eventMatchesKey(e, key);
   };
   document.addEventListener("keypress", checkEvent);
 
   try {
-    key = document.createElement("key");
+    key = document.createXULElement("key");
     let keyset = document.getElementById("mainKeyset");
     key.setAttribute("key", "t");
     key.setAttribute("modifiers", "accel");
     keyset.appendChild(key);
     EventUtils.synthesizeKey("t", {accelKey: true});
     is(eventMatchResult, true, "eventMatchesKey: one modifier");
     keyset.removeChild(key);
 
-    key = document.createElement("key");
+    key = document.createXULElement("key");
     key.setAttribute("key", "g");
     key.setAttribute("modifiers", "accel,shift");
     keyset.appendChild(key);
     EventUtils.synthesizeKey("g", {accelKey: true, shiftKey: true});
     is(eventMatchResult, true, "eventMatchesKey: combination modifiers");
     keyset.removeChild(key);
 
-    key = document.createElement("key");
+    key = document.createXULElement("key");
     key.setAttribute("key", "w");
     key.setAttribute("modifiers", "accel");
     keyset.appendChild(key);
     EventUtils.synthesizeKey("f", {accelKey: true});
     is(eventMatchResult, false, "eventMatchesKey: mismatch keys");
     keyset.removeChild(key);
 
-    key = document.createElement("key");
+    key = document.createXULElement("key");
     key.setAttribute("keycode", "VK_DELETE");
     keyset.appendChild(key);
     EventUtils.synthesizeKey("VK_DELETE", {accelKey: true});
     is(eventMatchResult, false, "eventMatchesKey: mismatch modifiers");
     keyset.removeChild(key);
   } finally {
     // Make sure to remove the event listener so future tests don't
     // fail when they simulate key presses.
--- a/browser/base/content/test/general/browser_accesskeys.js
+++ b/browser/base/content/test/general/browser_accesskeys.js
@@ -16,17 +16,17 @@ add_task(async function() {
   let focusedId = await performAccessKey("y");
   is(focusedId, "button", "button accesskey");
 
   // Press an accesskey in the child document while the content document is focused.
   focusedId = await performAccessKey("z");
   is(focusedId, "checkbox", "checkbox accesskey");
 
   // Add an element with an accesskey to the chrome and press its accesskey while the chrome is focused.
-  let newButton = document.createElement("button");
+  let newButton = document.createXULElement("button");
   newButton.id = "chromebutton";
   newButton.setAttribute("accesskey", "z");
   document.documentElement.appendChild(newButton);
 
   Services.focus.clearFocus(window);
 
   focusedId = await performAccessKeyForChrome("z");
   is(focusedId, "chromebutton", "chromebutton accesskey");
--- a/browser/base/content/test/general/browser_tab_dragdrop2_frame1.xul
+++ b/browser/base/content/test/general/browser_tab_dragdrop2_frame1.xul
@@ -76,22 +76,22 @@ function popupShown(event)
   ++i;
 
   currentTest.result(currentTest.testname + " ", panel);
   panel.hidePopup();
 }
 
 function createPanel(attrs)
 {
-  var panel = document.createElement("panel");
+  var panel = document.createXULElement("panel");
   for (var a in attrs) {
     panel.setAttribute(a, attrs[a]);
   }
 
-  var button = document.createElement("button");
+  var button = document.createXULElement("button");
   panel.appendChild(button);
   button.label = "OK";
   button.width = 120;
   button.height = 40;
   button.setAttribute("style", "-moz-appearance: none; border: 0; margin: 0;");
   panel.setAttribute("style", "-moz-appearance: none; border: 0; margin: 0;");
   return document.documentElement.appendChild(panel);
 }
--- a/browser/base/content/test/general/browser_tabbar_big_widgets.js
+++ b/browser/base/content/test/general/browser_tabbar_big_widgets.js
@@ -3,17 +3,17 @@
  */
 
 const kButtonId = "test-tabbar-size-with-large-buttons";
 
 function test() {
   registerCleanupFunction(cleanup);
   let titlebar = document.getElementById("titlebar");
   let originalHeight = titlebar.getBoundingClientRect().height;
-  let button = document.createElement("toolbarbutton");
+  let button = document.createXULElement("toolbarbutton");
   button.id = kButtonId;
   button.setAttribute("style", "min-height: 100px");
   gNavToolbox.palette.appendChild(button);
   CustomizableUI.addWidgetToArea(kButtonId, CustomizableUI.AREA_TABSTRIP);
   let currentHeight = titlebar.getBoundingClientRect().height;
   ok(currentHeight > originalHeight, "Titlebar should have grown");
   CustomizableUI.removeWidgetFromArea(kButtonId);
   currentHeight = titlebar.getBoundingClientRect().height;
--- a/browser/base/content/test/permissions/browser_reservedkey.js
+++ b/browser/base/content/test/permissions/browser_reservedkey.js
@@ -1,52 +1,52 @@
 add_task(async function test_reserved_shortcuts() {
-  let keyset = document.createElement("keyset");
-  let key1 = document.createElement("key");
+  let keyset = document.createXULElement("keyset");
+  let key1 = document.createXULElement("key");
   key1.setAttribute("id", "kt_reserved");
   key1.setAttribute("modifiers", "shift");
   key1.setAttribute("key", "O");
   key1.setAttribute("reserved", "true");
   key1.setAttribute("count", "0");
   // We need to have the attribute "oncommand" for the "command" listener to fire
   key1.setAttribute("oncommand", "//");
   key1.addEventListener("command", () => {
     let attribute = key1.getAttribute("count");
     key1.setAttribute("count", Number(attribute) + 1);
   });
 
-  let key2 = document.createElement("key");
+  let key2 = document.createXULElement("key");
   key2.setAttribute("id", "kt_notreserved");
   key2.setAttribute("modifiers", "shift");
   key2.setAttribute("key", "P");
   key2.setAttribute("reserved", "false");
   key2.setAttribute("count", "0");
   // We need to have the attribute "oncommand" for the "command" listener to fire
   key2.setAttribute("oncommand", "//");
   key2.addEventListener("command", () => {
     let attribute = key2.getAttribute("count");
     key2.setAttribute("count", Number(attribute) + 1);
   });
 
-  let key3 = document.createElement("key");
+  let key3 = document.createXULElement("key");
   key3.setAttribute("id", "kt_reserveddefault");
   key3.setAttribute("modifiers", "shift");
   key3.setAttribute("key", "Q");
   key3.setAttribute("count", "0");
   // We need to have the attribute "oncommand" for the "command" listener to fire
   key3.setAttribute("oncommand", "//");
   key3.addEventListener("command", () => {
     let attribute = key3.getAttribute("count");
     key3.setAttribute("count", Number(attribute) + 1);
   });
 
   keyset.appendChild(key1);
   keyset.appendChild(key2);
   keyset.appendChild(key3);
-  let container = document.createElement("box");
+  let container = document.createXULElement("box");
   container.appendChild(keyset);
   document.documentElement.appendChild(container);
 
   const pageUrl = "data:text/html,<body onload='document.body.firstElementChild.focus();'><div onkeydown='event.preventDefault();' tabindex=0>Test</div></body>";
   let tab = await BrowserTestUtils.openNewForegroundTab(gBrowser, pageUrl);
 
   EventUtils.sendString("OPQ");
 
--- a/browser/base/content/test/popupNotifications/browser_popupNotification_2.js
+++ b/browser/base/content/test/popupNotifications/browser_popupNotification_2.js
@@ -135,20 +135,20 @@ var tests = [
       gBrowser.selectedTab = this.oldSelectedTab;
     }
   },
 
   // Test that nested icon nodes correctly activate popups
   { id: "Test#6",
     run() {
       // Add a temporary box as the anchor with a button
-      this.box = document.createElement("box");
+      this.box = document.createXULElement("box");
       PopupNotifications.iconBox.appendChild(this.box);
 
-      let button = document.createElement("button");
+      let button = document.createXULElement("button");
       button.setAttribute("label", "Please click me!");
       this.box.appendChild(button);
 
       // The notification should open up on the box
       this.notifyObj = new BasicNotification(this.id);
       this.notifyObj.anchorID = this.box.id = "nested-box";
       this.notifyObj.addOptions({dismissed: true});
       this.notification = showNotification(this.notifyObj);
--- a/browser/base/content/test/popupNotifications/browser_popupNotification_3.js
+++ b/browser/base/content/test/popupNotifications/browser_popupNotification_3.js
@@ -192,17 +192,17 @@ var tests = [
     }
   },
   // Popup notification anchor shouldn't disappear when a notification with the same ID is re-added in a background tab
   { id: "Test#8",
     async run() {
       await promiseTabLoadEvent(gBrowser.selectedTab, "http://example.com/");
       let originalTab = gBrowser.selectedTab;
       let bgTab = await BrowserTestUtils.openNewForegroundTab(gBrowser, "http://example.com/");
-      let anchor = document.createElement("box");
+      let anchor = document.createXULElement("box");
       anchor.id = "test26-anchor";
       anchor.className = "notification-anchor-icon";
       PopupNotifications.iconBox.appendChild(anchor);
 
       gBrowser.selectedTab = originalTab;
 
       let fgNotifyObj = new BasicNotification(this.id);
       fgNotifyObj.anchorID = anchor.id;
--- a/browser/base/content/test/siteIdentity/browser.ini
+++ b/browser/base/content/test/siteIdentity/browser.ini
@@ -51,17 +51,17 @@ support-files = ../permissions/permissio
 [browser_identityPopup_focus.js]
 [browser_insecureLoginForms.js]
 support-files =
   insecure_opener.html
   !/toolkit/components/passwordmgr/test/browser/form_basic.html
   !/toolkit/components/passwordmgr/test/browser/insecure_test.html
   !/toolkit/components/passwordmgr/test/browser/insecure_test_subframe.html
 [browser_mcb_redirect.js]
-skip-if = verify && !debug && os == 'mac'
+skip-if = (verify && !debug && os == 'mac') || (os == 'linux') || (os == 'mac') # Bug 1376771
 tags = mcb
 support-files =
   test_mcb_redirect.html
   test_mcb_redirect_image.html
   test_mcb_double_redirect_image.html
   test_mcb_redirect.js
   test_mcb_redirect.sjs
 [browser_mixed_content_cert_override.js]
--- a/browser/base/content/test/tabPrompts/browser_closeTabSpecificPanels.js
+++ b/browser/base/content/test/tabPrompts/browser_closeTabSpecificPanels.js
@@ -5,19 +5,19 @@
  * and one that isn't. When a tab loses focus, panel specific to that tab should close.
  * The non-specific panel should remain open.
  *
  */
 
 add_task(async function() {
   let tab1 = BrowserTestUtils.addTab(gBrowser, "http://mochi.test:8888/#0");
   let tab2 = BrowserTestUtils.addTab(gBrowser, "http://mochi.test:8888/#1");
-  let specificPanel = document.createElement("panel");
+  let specificPanel = document.createXULElement("panel");
   specificPanel.setAttribute("tabspecific", "true");
-  let generalPanel = document.createElement("panel");
+  let generalPanel = document.createXULElement("panel");
   let anchor = document.getElementById(CustomizableUI.AREA_NAVBAR);
 
   anchor.appendChild(specificPanel);
   anchor.appendChild(generalPanel);
   is(specificPanel.state, "closed", "specificPanel starts as closed");
   is(generalPanel.state, "closed", "generalPanel starts as closed");
 
   let specificPanelPromise = BrowserTestUtils.waitForEvent(specificPanel, "popupshown");
--- a/browser/base/content/test/tabs/browser.ini
+++ b/browser/base/content/test/tabs/browser.ini
@@ -47,16 +47,17 @@ skip-if = (debug && os == 'linux' && bit
 support-files = file_new_tab_page.html
 [browser_new_tab_in_privileged_process_pref.js]
 skip-if = !e10s # Pref and test only relevant for e10s.
 [browser_new_web_tab_in_file_process_pref.js]
 skip-if = !e10s # Pref and test only relevant for e10s.
 [browser_newwindow_tabstrip_overflow.js]
 [browser_open_newtab_start_observer_notification.js]
 [browser_opened_file_tab_navigated_to_web.js]
+skip-if = (os == 'mac' && debug) || (os == 'linux' && debug) # Bug 1356347
 [browser_overflowScroll.js]
 [browser_pinnedTabs_clickOpen.js]
 [browser_pinnedTabs_closeByKeyboard.js]
 [browser_pinnedTabs.js]
 [browser_positional_attributes.js]
 skip-if = (verify && (os == 'win' || os == 'mac'))
 [browser_preloadedBrowser_zoom.js]
 [browser_reload_deleted_file.js]
--- a/browser/base/content/test/trackingUI/browser_trackingUI_report_breakage.js
+++ b/browser/base/content/test/trackingUI/browser_trackingUI_report_breakage.js
@@ -157,20 +157,18 @@ add_task(async function testReportBreaka
     ok(BrowserTestUtils.is_visible(reportBreakageButton), "report breakage button is visible");
     let reportBreakageView = document.getElementById("identity-popup-breakageReportView");
     let viewShown = BrowserTestUtils.waitForEvent(reportBreakageView, "ViewShown");
     reportBreakageButton.click();
     await viewShown;
 
     let submitButton = document.getElementById("identity-popup-breakageReportView-submit");
     let reportURL = document.getElementById("identity-popup-breakageReportView-collection-url").textContent;
-    let reportUA = document.getElementById("identity-popup-breakageReportView-collection-userAgent").textContent;
 
     is(reportURL, TRACKING_PAGE, "Shows the correct URL in the report UI.");
-    is(reportUA, navigator.userAgent, "Shows the correct user agent in the report UI.");
 
     // Make sure that sending the report closes the identity popup.
     let popuphidden = BrowserTestUtils.waitForEvent(gIdentityHandler._identityPopup, "popuphidden");
 
     // Check that we're receiving a good report.
     await new Promise(resolve => {
       server.registerPathHandler("/", async (request, response) => {
         is(request.method, "POST", "request was a post");
@@ -202,17 +200,17 @@ add_task(async function testReportBreaka
           prefsBody += `${pref}: ${Preferences.get(pref)}\r\n`;
         }
 
         Assert.deepEqual(sections, [
           "",
           "Content-Disposition: form-data; name=\"title\"\r\n\r\ntracking.example.org\r\n",
           "Content-Disposition: form-data; name=\"body\"\r\n\r\n" +
           `Full URL: ${reportURL + "?"}\r\n` +
-          `userAgent: ${reportUA}\r\n\r\n` +
+          `userAgent: ${navigator.userAgent}\r\n\r\n` +
           "**Preferences**\r\n" +
           `${prefsBody}\r\n` +
           "**Comments**\r\n" +
           "This is a comment\r\n",
           ""
         ], "Should send the correct form data");
 
         resolve();
--- a/browser/base/content/test/urlbar/browser_urlbarAboutHomeLoading.js
+++ b/browser/base/content/test/urlbar/browser_urlbarAboutHomeLoading.js
@@ -11,16 +11,19 @@ add_task(async function clearURLBarAfter
   let tab = await new Promise(resolve => {
     gBrowser.selectedTab = BrowserTestUtils.addTab(gBrowser, "about:preferences");
     let newTabBrowser = gBrowser.getBrowserForTab(gBrowser.selectedTab);
     newTabBrowser.addEventListener("Initialized", async function() {
       resolve(gBrowser.selectedTab);
     }, {capture: true, once: true});
   });
   document.getElementById("home-button").click();
+  if (!tab.linkedBrowser.isRemoteBrowser) {
+    await BrowserTestUtils.waitForEvent(tab.linkedBrowser, "XULFrameLoaderCreated");
+  }
   await BrowserTestUtils.browserLoaded(tab.linkedBrowser);
   is(gURLBar.value, "", "URL bar should be empty");
   is(tab.linkedBrowser.userTypedValue, null, "The browser should have no recorded userTypedValue");
   BrowserTestUtils.removeTab(tab);
 });
 
 /**
  * Same as above, but open the tab without passing the URL immediately
@@ -31,16 +34,19 @@ add_task(async function clearURLBarAfter
     gBrowser.selectedTab = BrowserTestUtils.addTab(gBrowser);
     let newTabBrowser = gBrowser.getBrowserForTab(gBrowser.selectedTab);
     newTabBrowser.addEventListener("Initialized", async function() {
       resolve(gBrowser.selectedTab);
     }, {capture: true, once: true});
     newTabBrowser.loadURI("about:preferences");
   });
   document.getElementById("home-button").click();
+  if (!tab.linkedBrowser.isRemoteBrowser) {
+    await BrowserTestUtils.waitForEvent(tab.linkedBrowser, "XULFrameLoaderCreated");
+  }
   await BrowserTestUtils.browserLoaded(tab.linkedBrowser);
   is(gURLBar.value, "", "URL bar should be empty");
   is(tab.linkedBrowser.userTypedValue, null, "The browser should have no recorded userTypedValue");
   BrowserTestUtils.removeTab(tab);
 });
 
 /**
  * Load about:home directly from an about:newtab page. Because it is an
--- a/browser/base/content/test/urlbar/browser_urlbar_search_no_speculative_connect_with_client_cert.js
+++ b/browser/base/content/test/urlbar/browser_urlbar_search_no_speculative_connect_with_client_cert.js
@@ -76,17 +76,16 @@ function startServer(cert) {
 
     onStopListening() {
       info("onStopListening");
       input.close();
       output.close();
     }
   };
 
-  tlsServer.setSessionCache(false);
   tlsServer.setSessionTickets(false);
   tlsServer.setRequestClientCertificate(Ci.nsITLSServerSocket.REQUEST_ALWAYS);
 
   tlsServer.asyncListen(listener);
 
   return tlsServer;
 }
 
--- a/browser/base/content/utilityOverlay.js
+++ b/browser/base/content/utilityOverlay.js
@@ -616,37 +616,37 @@ function createUserContextMenu(event, {
     event.target.firstChild.remove();
   }
 
   let bundle = Services.strings.createBundle("chrome://browser/locale/browser.properties");
   let docfrag = document.createDocumentFragment();
 
   // If we are excluding a userContextId, we want to add a 'no-container' item.
   if (excludeUserContextId || showDefaultTab) {
-    let menuitem = document.createElement("menuitem");
+    let menuitem = document.createXULElement("menuitem");
     menuitem.setAttribute("data-usercontextid", "0");
     menuitem.setAttribute("label", bundle.GetStringFromName("userContextNone.label"));
     menuitem.setAttribute("accesskey", bundle.GetStringFromName("userContextNone.accesskey"));
 
     // We don't set an oncommand/command attribute because if we have
     // to exclude a userContextId we are generating the contextMenu and
     // isContextMenu will be true.
 
     docfrag.appendChild(menuitem);
 
-    let menuseparator = document.createElement("menuseparator");
+    let menuseparator = document.createXULElement("menuseparator");
     docfrag.appendChild(menuseparator);
   }
 
   ContextualIdentityService.getPublicIdentities().forEach(identity => {
     if (identity.userContextId == excludeUserContextId) {
       return;
     }
 
-    let menuitem = document.createElement("menuitem");
+    let menuitem = document.createXULElement("menuitem");
     menuitem.setAttribute("data-usercontextid", identity.userContextId);
     menuitem.setAttribute("label", ContextualIdentityService.getUserContextLabel(identity.userContextId));
 
     if (identity.accessKey && useAccessKeys) {
       menuitem.setAttribute("accesskey", bundle.GetStringFromName(identity.accessKey));
     }
 
     menuitem.classList.add("menuitem-iconic");
@@ -657,19 +657,19 @@ function createUserContextMenu(event, {
     }
 
     menuitem.setAttribute("data-identity-icon", identity.icon);
 
     docfrag.appendChild(menuitem);
   });
 
   if (!isContextMenu) {
-    docfrag.appendChild(document.createElement("menuseparator"));
+    docfrag.appendChild(document.createXULElement("menuseparator"));
 
-    let menuitem = document.createElement("menuitem");
+    let menuitem = document.createXULElement("menuitem");
     menuitem.setAttribute("label",
                           bundle.GetStringFromName("userContext.aboutPage.label"));
     if (useAccessKeys) {
       menuitem.setAttribute("accesskey",
                             bundle.GetStringFromName("userContext.aboutPage.accesskey"));
     }
     menuitem.setAttribute("command", "Browser:OpenAboutContainers");
     docfrag.appendChild(menuitem);
--- a/browser/components/controlcenter/content/panel.inc.xul
+++ b/browser/components/controlcenter/content/panel.inc.xul
@@ -84,17 +84,17 @@
               <label flex="1" class="identity-popup-content-blocking-category-label">&contentBlocking.fastBlock.label;</label>
               <label flex="1" class="identity-popup-content-blocking-category-state-label">&contentBlocking.fastBlock.blocked.label;</label>
               <label flex="1" class="identity-popup-content-blocking-category-add-blocking text-link"
                      onclick="ContentBlocking.openPreferences('identityPopup-CB-fastblock');">&contentBlocking.fastBlock.add.label;</label>
             </hbox>
             <hbox id="identity-popup-content-blocking-category-tracking-protection"
                   class="identity-popup-content-blocking-category" align="center" role="group">
               <image class="identity-popup-content-blocking-category-icon tracking-protection-icon"/>
-              <label flex="1" class="identity-popup-content-blocking-category-label">&contentBlocking.trackingProtection.label;</label>
+              <label flex="1" class="identity-popup-content-blocking-category-label">&contentBlocking.trackingProtection2.label;</label>
               <label flex="1" class="identity-popup-content-blocking-category-state-label">&contentBlocking.trackingProtection.blocked.label;</label>
               <label flex="1" class="identity-popup-content-blocking-category-add-blocking text-link"
                      onclick="ContentBlocking.openPreferences('identityPopup-CB-tracking-protection');">&contentBlocking.trackingProtection.add.label;</label>
             </hbox>
             <hbox id="identity-popup-content-blocking-category-3rdpartycookies"
                   class="identity-popup-content-blocking-category" align="center" role="group">
               <image class="identity-popup-content-blocking-category-icon thirdpartycookies-icon"/>
               <label flex="1" class="identity-popup-content-blocking-category-label">&contentBlocking.3rdPartyCookies.label;</label>
@@ -117,17 +117,17 @@
           <button id="tracking-action-block"
                   class="tracking-protection-button"
                   label="&trackingProtection.block5.label;"
                   accesskey="&trackingProtection.block5.accesskey;"
                   oncommand="ContentBlocking.enableForCurrentPage();" />
           <label id="identity-popup-content-blocking-report-breakage"
                  onclick="ContentBlocking.showReportBreakageSubview();"
                  class="text-link subviewkeynav"
-                 flex="1">&contentBlocking.openBreakageReportView.label;</label>
+                 flex="1">&contentBlocking.openBreakageReportView2.label;</label>
         </vbox>
       </hbox>
 
       <!-- Permissions Section -->
       <hbox class="identity-popup-section"
             when-connection="not-secure secure secure-ev secure-cert-user-overridden file extension">
         <vbox id="identity-popup-permissions-content" flex="1" role="group"
               aria-labelledby="identity-popup-permissions-headline">
@@ -246,30 +246,26 @@
 
     </panelview>
 
     <!-- Report Breakage SubView -->
     <panelview id="identity-popup-breakageReportView"
                title="&contentBlocking.breakageReportView.label;"
                descriptionheightworkaround="true">
         <vbox id="identity-popup-breakageReportView-heading">
-          <description>&contentBlocking.breakageReportView.description;</description>
+          <description>&contentBlocking.breakageReportView2.description;</description>
           <label id="identity-popup-breakageReportView-learn-more"
                  class="text-link">&contentBlocking.breakageReportView.learnMore;</label>
         </vbox>
         <vbox id="identity-popup-breakageReportView-body" class="panel-view-body-unscrollable">
           <vbox class="identity-popup-breakageReportView-collection-section">
             <label class="identity-popup-breakageReportView-collection-label">&contentBlocking.breakageReportView.collection.url.label;</label>
             <label id="identity-popup-breakageReportView-collection-url"/>
           </vbox>
           <vbox class="identity-popup-breakageReportView-collection-section">
-            <label class="identity-popup-breakageReportView-collection-label">&contentBlocking.breakageReportView.collection.userAgent.label;</label>
-            <label id="identity-popup-breakageReportView-collection-userAgent"/>
-          </vbox>
-          <vbox class="identity-popup-breakageReportView-collection-section">
             <label class="identity-popup-breakageReportView-collection-label">&contentBlocking.breakageReportView.collection.comments.label;</label>
             <textbox multiline="true" id="identity-popup-breakageReportView-collection-comments"/>
           </vbox>
         </vbox>
         <vbox id="identity-popup-breakageReportView-footer" class="identity-popup-footer">
           <button id="identity-popup-breakageReportView-cancel"
                   label="&contentBlocking.breakageReportView.cancel.label;"
                   oncommand="ContentBlocking.backToMainView();"/>
--- a/browser/components/customizableui/CustomizableWidgets.jsm
+++ b/browser/components/customizableui/CustomizableWidgets.jsm
@@ -136,17 +136,17 @@ const CustomizableWidgets = [
       let utils = RecentlyClosedTabsAndWindowsMenuUtils;
       let method = `get${viewType}Fragment`;
       let fragment = utils[method](window, "toolbarbutton", true);
       let elementCount = fragment.childElementCount;
       this._panelMenuView._setEmptyPopupStatus(panelview, !elementCount);
       if (!elementCount)
         return;
 
-      let body = document.createElement("vbox");
+      let body = document.createXULElement("vbox");
       body.className = "panel-subview-body";
       body.appendChild(fragment);
       let footer;
       while (--elementCount >= 0) {
         let element = body.children[elementCount];
         CustomizableUI.addShortcut(element);
         element.classList.add("subviewbutton");
         if (element.classList.contains("restoreallitem")) {
--- a/browser/components/customizableui/CustomizeMode.jsm
+++ b/browser/components/customizableui/CustomizeMode.jsm
@@ -811,17 +811,17 @@ CustomizeMode.prototype = {
   },
 
   createOrUpdateWrapper(aNode, aPlace, aIsUpdate) {
     let wrapper;
     if (aIsUpdate && aNode.parentNode && aNode.parentNode.localName == "toolbarpaletteitem") {
       wrapper = aNode.parentNode;
       aPlace = wrapper.getAttribute("place");
     } else {
-      wrapper = this.document.createElement("toolbarpaletteitem");
+      wrapper = this.document.createXULElement("toolbarpaletteitem");
       // "place" is used to show the label when it's sitting in the palette.
       wrapper.setAttribute("place", aPlace);
     }
 
 
     // Ensure the wrapped item doesn't look like it's in any special state, and
     // can't be interactved with when in the customization palette.
     // Note that some buttons opt out of this with the
@@ -1356,17 +1356,17 @@ CustomizeMode.prototype = {
       this._updateLWThemeButtonIcon();
       this._onUIChange();
       panel.hidePopup();
     };
 
     let doc = this.window.document;
 
     function buildToolbarButton(aTheme) {
-      let tbb = doc.createElement("toolbarbutton");
+      let tbb = doc.createXULElement("toolbarbutton");
       tbb.theme = aTheme;
       tbb.setAttribute("label", aTheme.name);
       tbb.setAttribute("image", aTheme.iconURL);
       if (aTheme.description)
         tbb.setAttribute("tooltiptext", aTheme.description);
       tbb.setAttribute("tabindex", "0");
       tbb.classList.add("customization-lwtheme-menu-theme");
       let isActive = activeThemeID == aTheme.id;
@@ -2645,17 +2645,17 @@ CustomizeMode.prototype = {
       arena: document.getElementById("customization-pong-arena")
     };
     let isRTL = document.documentElement.matches(":-moz-locale-dir(rtl)");
 
     document.addEventListener("keydown", onkeydown);
     document.addEventListener("keyup", onkeyup);
 
     for (let id of ["player1", "player2", "ball", "score", "lives"]) {
-      let el = document.createElement("box");
+      let el = document.createXULElement("box");
       el.id = "wp-" + id;
       elements[el.id] = elements.arena.appendChild(el);
     }
 
     let spacer = this.visiblePalette.querySelector("toolbarpaletteitem");
     for (let player of ["#wp-player1", "#wp-player2"]) {
       let val = "-moz-element(#" + spacer.id + ") no-repeat";
       elements.arena.querySelector(player).style.background = val;
--- a/browser/components/customizableui/content/panelUI.js
+++ b/browser/components/customizableui/content/panelUI.js
@@ -371,17 +371,17 @@ const PanelUI = {
     this.ensureLibraryInitialized(viewNode);
 
     let container = aAnchor.closest("panelmultiview");
     if (container) {
       container.showSubView(aViewId, aAnchor);
     } else if (!aAnchor.open) {
       aAnchor.open = true;
 
-      let tempPanel = document.createElement("panel");
+      let tempPanel = document.createXULElement("panel");
       tempPanel.setAttribute("type", "arrow");
       tempPanel.setAttribute("id", "customizationui-widget-panel");
       tempPanel.setAttribute("class", "cui-widget-panel");
       tempPanel.setAttribute("viewId", aViewId);
       if (aAnchor.getAttribute("tabspecific")) {
         tempPanel.setAttribute("tabspecific", true);
       }
       if (this._disableAnimations) {
@@ -389,17 +389,17 @@ const PanelUI = {
       }
       tempPanel.setAttribute("context", "");
       tempPanel.setAttribute("photon", true);
       document.getElementById(CustomizableUI.AREA_NAVBAR).appendChild(tempPanel);
       // If the view has a footer, set a convenience class on the panel.
       tempPanel.classList.toggle("cui-widget-panelWithFooter",
                                  viewNode.querySelector(".panel-subview-footer"));
 
-      let multiView = document.createElement("panelmultiview");
+      let multiView = document.createXULElement("panelmultiview");
       multiView.setAttribute("id", "customizationui-widget-multiview");
       multiView.setAttribute("viewCacheId", "appMenu-viewCache");
       multiView.setAttribute("mainViewId", viewNode.id);
       tempPanel.appendChild(multiView);
       viewNode.classList.add("cui-widget-panelview");
 
       let viewShown = false;
       let panelRemover = () => {
@@ -507,17 +507,17 @@ const PanelUI = {
       return;
     }
 
     let container = this.libraryRecentHighlights;
     container.hidden = container.previousElementSibling.hidden =
       container.previousElementSibling.previousElementSibling.hidden = false;
     let fragment = document.createDocumentFragment();
     for (let highlight of highlights) {
-      let button = document.createElement("toolbarbutton");
+      let button = document.createXULElement("toolbarbutton");
       button.classList.add("subviewbutton", "highlight", "subviewbutton-iconic", "bookmark-item");
       let title = highlight.title || highlight.url;
       button.setAttribute("label", title);
       button.setAttribute("tooltiptext", title);
       button.setAttribute("type", "highlight-" + highlight.type);
       button.setAttribute("onclick", "PanelUI.onLibraryHighlightClick(event)");
       if (highlight.favicon) {
         button.setAttribute("image", highlight.favicon);
--- a/browser/components/customizableui/test/browser_918049_skipintoolbarset_dnd.js
+++ b/browser/components/customizableui/test/browser_918049_skipintoolbarset_dnd.js
@@ -5,17 +5,17 @@
 "use strict";
 
 var navbar;
 var skippedItem;
 
 // Attempting to drag a skipintoolbarset item should work.
 add_task(async function() {
   navbar = document.getElementById("nav-bar");
-  skippedItem = document.createElement("toolbarbutton");
+  skippedItem = document.createXULElement("toolbarbutton");
   skippedItem.id = "test-skipintoolbarset-item";
   skippedItem.setAttribute("label", "Test");
   skippedItem.setAttribute("skipintoolbarset", "true");
   skippedItem.setAttribute("removable", "true");
   navbar.customizationTarget.appendChild(skippedItem);
   let libraryButton = document.getElementById("library-button");
   await startCustomizing();
   await waitForElementShown(skippedItem);
--- a/browser/components/customizableui/test/browser_940013_registerToolbarNode_calls_registerArea.js
+++ b/browser/components/customizableui/test/browser_940013_registerToolbarNode_calls_registerArea.js
@@ -7,17 +7,17 @@
 const kToolbarId = "test-registerToolbarNode-toolbar";
 const kButtonId = "test-registerToolbarNode-button";
 registerCleanupFunction(cleanup);
 
 // Registering a toolbar with defaultset attribute should work
 add_task(async function() {
   ok(CustomizableUI.inDefaultState, "Everything should be in its default state.");
   let btn = createDummyXULButton(kButtonId);
-  let toolbar = document.createElement("toolbar");
+  let toolbar = document.createXULElement("toolbar");
   toolbar.id = kToolbarId;
   toolbar.setAttribute("customizable", true);
   toolbar.setAttribute("defaultset", kButtonId);
   gNavToolbox.appendChild(toolbar);
   ok(CustomizableUI.areas.includes(kToolbarId),
      "Toolbar should have been registered automatically.");
   is(CustomizableUI.getAreaType(kToolbarId), CustomizableUI.TYPE_TOOLBAR,
      "Area should be registered as toolbar");
@@ -29,17 +29,17 @@ add_task(async function() {
   btn.remove();
 });
 
 // Registering a toolbar without a defaultset attribute should
 // wait for the registerArea call
 add_task(async function() {
   ok(CustomizableUI.inDefaultState, "Everything should be in its default state.");
   let btn = createDummyXULButton(kButtonId);
-  let toolbar = document.createElement("toolbar");
+  let toolbar = document.createXULElement("toolbar");
   toolbar.id = kToolbarId;
   toolbar.setAttribute("customizable", true);
   gNavToolbox.appendChild(toolbar);
   ok(!CustomizableUI.areas.includes(kToolbarId),
      "Toolbar should not yet have been registered automatically.");
   CustomizableUI.registerArea(kToolbarId, {defaultPlacements: [kButtonId]});
   ok(CustomizableUI.areas.includes(kToolbarId),
      "Toolbar should have been registered now.");
--- a/browser/components/customizableui/test/browser_940307_panel_click_closure_handling.js
+++ b/browser/components/customizableui/test/browser_940307_panel_click_closure_handling.js
@@ -2,17 +2,17 @@
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 "use strict";
 
 var button, menuButton;
 /* Clicking a button should close the panel */
 add_task(async function plain_button() {
-  button = document.createElement("toolbarbutton");
+  button = document.createXULElement("toolbarbutton");
   button.id = "browser_940307_button";
   button.setAttribute("label", "Button");
   gNavToolbox.palette.appendChild(button);
   CustomizableUI.addWidgetToArea(button.id, CustomizableUI.AREA_FIXED_OVERFLOW_PANEL);
 
   await waitForOverflowButtonShown();
 
   await document.getElementById("nav-bar").overflowable.show();
@@ -70,17 +70,17 @@ add_task(async function searchbar_in_pan
 
   // We focused the search bar earlier - ensure we don't keep doing that.
   gURLBar.select();
 
   CustomizableUI.reset();
 });
 
 add_task(async function disabled_button_in_panel() {
-  button = document.createElement("toolbarbutton");
+  button = document.createXULElement("toolbarbutton");
   button.id = "browser_946166_button_disabled";
   button.setAttribute("disabled", "true");
   button.setAttribute("label", "Button");
   gNavToolbox.palette.appendChild(button);
   CustomizableUI.addWidgetToArea(button.id, CustomizableUI.AREA_FIXED_OVERFLOW_PANEL);
 
   await waitForOverflowButtonShown();
 
--- a/browser/components/customizableui/test/browser_963639_customizing_attribute_non_customizable_toolbar.js
+++ b/browser/components/customizableui/test/browser_963639_customizing_attribute_non_customizable_toolbar.js
@@ -4,17 +4,17 @@
 
 "use strict";
 
 const kToolbar = "test-toolbar-963639-non-customizable-customizing-attribute";
 
 add_task(async function() {
   info("Test for Bug 963639 - CustomizeMode _onToolbarVisibilityChange sets @customizing on non-customizable toolbars");
 
-  let toolbar = document.createElement("toolbar");
+  let toolbar = document.createXULElement("toolbar");
   toolbar.id = kToolbar;
   gNavToolbox.appendChild(toolbar);
 
   let testToolbar = document.getElementById(kToolbar);
   ok(testToolbar, "Toolbar was created.");
   is(gNavToolbox.getElementsByAttribute("id", kToolbar).length, 1,
      "Toolbar was added to the navigator toolbox");
 
--- a/browser/components/customizableui/test/browser_978084_dragEnd_after_move.js
+++ b/browser/components/customizableui/test/browser_978084_dragEnd_after_move.js
@@ -6,32 +6,32 @@
 var draggedItem;
 
 /**
  * Check that customizing-movingItem gets removed on a drop when the item is moved.
  */
 
 // Drop on the palette
 add_task(async function() {
-  draggedItem = document.createElement("toolbarbutton");
+  draggedItem = document.createXULElement("toolbarbutton");
   draggedItem.id = "test-dragEnd-after-move1";
   draggedItem.setAttribute("label", "Test");
   draggedItem.setAttribute("removable", "true");
   let navbar = document.getElementById("nav-bar");
   navbar.customizationTarget.appendChild(draggedItem);
   await startCustomizing();
   simulateItemDrag(draggedItem, gCustomizeMode.visiblePalette);
   is(document.documentElement.hasAttribute("customizing-movingItem"), false,
      "Make sure customizing-movingItem is removed after dragging to the palette");
   await endCustomizing();
 });
 
 // Drop on a customization target itself
 add_task(async function() {
-  draggedItem = document.createElement("toolbarbutton");
+  draggedItem = document.createXULElement("toolbarbutton");
   draggedItem.id = "test-dragEnd-after-move2";
   draggedItem.setAttribute("label", "Test");
   draggedItem.setAttribute("removable", "true");
   let dest = createToolbarWithPlacements("test-dragEnd");
   let navbar = document.getElementById("nav-bar");
   navbar.customizationTarget.appendChild(draggedItem);
   await startCustomizing();
   simulateItemDrag(draggedItem, dest.customizationTarget);
--- a/browser/components/customizableui/test/browser_981305_separator_insertion.js
+++ b/browser/components/customizableui/test/browser_981305_separator_insertion.js
@@ -5,24 +5,24 @@
 "use strict";
 
 var tempElements = [];
 
 function insertTempItemsIntoMenu(parentMenu) {
   // Last element is null to insert at the end:
   let beforeEls = [parentMenu.firstElementChild, parentMenu.lastElementChild, null];
   for (let i = 0; i < beforeEls.length; i++) {
-    let sep = document.createElement("menuseparator");
+    let sep = document.createXULElement("menuseparator");
     tempElements.push(sep);
     parentMenu.insertBefore(sep, beforeEls[i]);
-    let menu = document.createElement("menu");
+    let menu = document.createXULElement("menu");
     tempElements.push(menu);
     parentMenu.insertBefore(menu, beforeEls[i]);
     // And another separator for good measure:
-    sep = document.createElement("menuseparator");
+    sep = document.createXULElement("menuseparator");
     tempElements.push(sep);
     parentMenu.insertBefore(sep, beforeEls[i]);
   }
 }
 
 function checkSeparatorInsertion(menuId, buttonId, subviewId) {
   return async function() {
     info("Checking for duplicate separators in " + buttonId + " widget");
--- a/browser/components/customizableui/test/browser_981418-widget-onbeforecreated-handler.js
+++ b/browser/components/customizableui/test/browser_981418-widget-onbeforecreated-handler.js
@@ -9,17 +9,17 @@ const kWidgetId = "test-981418-widget-on
 // Should be able to add broken view widget
 add_task(async function testAddOnBeforeCreatedWidget() {
   let onBeforeCreatedCalled = false;
   let widgetSpec = {
     id: kWidgetId,
     type: "view",
     viewId: kWidgetId + "idontexistyet",
     onBeforeCreated(doc) {
-      let view = doc.createElement("panelview");
+      let view = doc.createXULElement("panelview");
       view.id = kWidgetId + "idontexistyet";
       document.getElementById("appMenu-viewCache").appendChild(view);
       onBeforeCreatedCalled = true;
     }
   };
 
   CustomizableUI.createWidget(widgetSpec);
   CustomizableUI.addWidgetToArea(kWidgetId, CustomizableUI.AREA_NAVBAR);
--- a/browser/components/customizableui/test/browser_996635_remove_non_widgets.js
+++ b/browser/components/customizableui/test/browser_996635_remove_non_widgets.js
@@ -6,17 +6,17 @@
 // NB: This is testing what happens if something that /isn't/ a customizable
 // widget gets used in CustomizableUI APIs. Don't use this as an example of
 // what should happen in a "normal" case or how you should use the API.
 function test() {
   // First create a button that isn't customizable, and add it in the nav-bar,
   // but not in the customizable part of it (the customization target) but
   // next to the main (hamburger) menu button.
   const buttonID = "Test-non-widget-non-removable-button";
-  let btn = document.createElement("toolbarbutton");
+  let btn = document.createXULElement("toolbarbutton");
   btn.id = buttonID;
   btn.label = "Hi";
   btn.setAttribute("style", "width: 20px; height: 20px; background-color: red");
   document.getElementById("nav-bar").appendChild(btn);
   registerCleanupFunction(function() {
     btn.remove();
   });
 
--- a/browser/components/customizableui/test/browser_PanelMultiView.js
+++ b/browser/components/customizableui/test/browser_PanelMultiView.js
@@ -161,38 +161,38 @@ function stopRecordingEvents(element) {
  *       <label/>            ->  gPanelViewLabels[viewIndex]
  *     </panelview>
  * </toolbar>
  */
 add_task(async function test_setup() {
   let navBar = document.getElementById("nav-bar");
 
   for (let i = 0; i < PANELS_COUNT; i++) {
-    gPanelAnchors[i] = document.createElement("toolbarbutton");
+    gPanelAnchors[i] = document.createXULElement("toolbarbutton");
     gPanelAnchors[i].classList.add("toolbarbutton-1",
                                    "chromeclass-toolbar-additional");
     navBar.appendChild(gPanelAnchors[i]);
 
-    gPanels[i] = document.createElement("panel");
+    gPanels[i] = document.createXULElement("panel");
     gPanels[i].id = "panel-" + i;
     gPanels[i].setAttribute("type", "arrow");
     gPanels[i].setAttribute("photon", true);
     navBar.appendChild(gPanels[i]);
 
-    gPanelMultiViews[i] = document.createElement("panelmultiview");
+    gPanelMultiViews[i] = document.createXULElement("panelmultiview");
     gPanelMultiViews[i].id = "panelmultiview-" + i;
     gPanels[i].appendChild(gPanelMultiViews[i]);
   }
 
   for (let i = 0; i < PANELVIEWS_COUNT; i++) {
-    gPanelViews[i] = document.createElement("panelview");
+    gPanelViews[i] = document.createXULElement("panelview");
     gPanelViews[i].id = "panelview-" + i;
     navBar.appendChild(gPanelViews[i]);
 
-    gPanelViewLabels[i] = document.createElement("label");
+    gPanelViewLabels[i] = document.createXULElement("label");
     gPanelViewLabels[i].setAttribute("value", "PanelView " + i);
     gPanelViews[i].appendChild(gPanelViewLabels[i]);
   }
 
   registerCleanupFunction(() => {
     [...gPanelAnchors, ...gPanels, ...gPanelViews].forEach(e => e.remove());
   });
 });
--- a/browser/components/customizableui/test/browser_bootstrapped_custom_toolbar.js
+++ b/browser/components/customizableui/test/browser_bootstrapped_custom_toolbar.js
@@ -5,17 +5,17 @@
 "use strict";
 
 requestLongerTimeout(2);
 
 const kTestBarID = "testBar";
 const kWidgetID = "characterencoding-button";
 
 function createTestBar(aLegacy) {
-  let testBar = document.createElement("toolbar");
+  let testBar = document.createXULElement("toolbar");
   testBar.id = kTestBarID;
   testBar.setAttribute("customizable", "true");
   CustomizableUI.registerArea(kTestBarID, {
     type: CustomizableUI.TYPE_TOOLBAR,
     legacy: aLegacy,
   });
   gNavToolbox.appendChild(testBar);
   return testBar;
--- a/browser/components/customizableui/test/browser_insert_before_moved_node.js
+++ b/browser/components/customizableui/test/browser_insert_before_moved_node.js
@@ -7,17 +7,17 @@
 add_task(async function() {
   for (let toolbar of ["nav-bar", "TabsToolbar"]) {
     CustomizableUI.createWidget({id: "real-button", label: "test real button"});
     CustomizableUI.addWidgetToArea("real-button", toolbar);
     CustomizableUI.addWidgetToArea("moved-button-not-here", toolbar);
     let placements = CustomizableUI.getWidgetIdsInArea(toolbar);
     Assert.deepEqual(placements.slice(-2), ["real-button", "moved-button-not-here"],
       "Should have correct placements");
-    let otherButton = document.createElement("toolbarbutton");
+    let otherButton = document.createXULElement("toolbarbutton");
     otherButton.id = "moved-button-not-here";
     if (toolbar == "nav-bar") {
       gURLBar.parentNode.appendChild(otherButton);
     } else {
       gBrowser.tabContainer.appendChild(otherButton);
     }
     CustomizableUI.destroyWidget("real-button");
     CustomizableUI.createWidget({id: "real-button", label: "test real button"});
--- a/browser/components/downloads/content/allDownloadsView.js
+++ b/browser/components/downloads/content/allDownloadsView.js
@@ -33,17 +33,17 @@ XPCOMUtils.defineLazyModuleGetters(this,
  * the onChanged method.
  *
  * @param download
  *        The Download object from the DownloadHistoryList.
  */
 function HistoryDownloadElementShell(download) {
   this._download = download;
 
-  this.element = document.createElement("richlistitem");
+  this.element = document.createXULElement("richlistitem");
   this.element._shell = this;
 
   this.element.classList.add("download");
   this.element.classList.add("download-state");
 }
 
 HistoryDownloadElementShell.prototype = {
   __proto__: DownloadsViewUI.DownloadElementShell.prototype,
--- a/browser/components/downloads/content/downloads.js
+++ b/browser/components/downloads/content/downloads.js
@@ -718,17 +718,17 @@ var DownloadsView = {
   /**
    * Creates a new view item associated with the specified data item, and adds
    * it to the top or the bottom of the list.
    */
   _addViewItem(download, aNewest) {
     DownloadsCommon.log("Adding a new DownloadsViewItem to the downloads list.",
                         "aNewest =", aNewest);
 
-    let element = document.createElement("richlistitem");
+    let element = document.createXULElement("richlistitem");
     let viewItem = new DownloadsViewItem(download, element);
     this._visibleViewItems.set(download, viewItem);
     this._itemsForElements.set(element, viewItem);
     if (aNewest) {
       this.richListBox.insertBefore(element, this.richListBox.firstElementChild);
     } else {
       this.richListBox.appendChild(element);
     }
--- a/browser/components/extensions/ExtensionControlledPopup.jsm
+++ b/browser/components/extensions/ExtensionControlledPopup.jsm
@@ -265,17 +265,17 @@ class ExtensionControlledPopup {
     panel.hidden = false;
     popupnotification.hidden = false;
     panel.openPopup(anchor);
   }
 
   getAddonDetails(doc, addon) {
     const defaultIcon = "chrome://mozapps/skin/extensions/extensionGeneric.svg";
 
-    let image = doc.createElement("image");
+    let image = doc.createXULElement("image");
     image.setAttribute("src", addon.iconURL || defaultIcon);
     image.classList.add("extension-controlled-icon");
 
     let addonDetails = doc.createDocumentFragment();
     addonDetails.appendChild(image);
     addonDetails.appendChild(doc.createTextNode(" " + addon.name));
 
     return addonDetails;
@@ -290,15 +290,15 @@ class ExtensionControlledPopup {
     if (this.getLocalizedDescription) {
       description.appendChild(
         this.getLocalizedDescription(doc, message, addonDetails));
     } else {
       description.appendChild(
         BrowserUtils.getLocalizedFragment(doc, message, addonDetails));
     }
 
-    let link = doc.createElement("label");
+    let link = doc.createXULElement("label");
     link.setAttribute("class", "learnMore text-link");
     link.href = Services.urlFormatter.formatURLPref("app.support.baseURL") + this.learnMoreLink;
     link.textContent = strBundle.GetStringFromName(this.learnMoreMessageId);
     description.appendChild(link);
   }
 }
--- a/browser/components/extensions/ExtensionPopups.jsm
+++ b/browser/components/extensions/ExtensionPopups.jsm
@@ -381,17 +381,17 @@ class BasePopup {
  * A map of active popups for a given browser window.
  *
  * WeakMap[window -> WeakMap[Extension -> BasePopup]]
  */
 BasePopup.instances = new DefaultWeakMap(() => new WeakMap());
 
 class PanelPopup extends BasePopup {
   constructor(extension, document, popupURL, browserStyle) {
-    let panel = document.createElement("panel");
+    let panel = document.createXULElement("panel");
     panel.setAttribute("id", makeWidgetId(extension.id) + "-panel");
     panel.setAttribute("class", "browser-extension-panel");
     panel.setAttribute("tabspecific", "true");
     panel.setAttribute("type", "arrow");
     panel.setAttribute("role", "group");
     if (extension.remote) {
       panel.setAttribute("remote", "true");
     }
@@ -429,17 +429,17 @@ class PanelPopup extends BasePopup {
   }
 }
 
 class ViewPopup extends BasePopup {
   constructor(extension, window, popupURL, browserStyle, fixedWidth, blockParser) {
     let document = window.document;
 
     let createPanel = remote => {
-      let panel = document.createElement("panel");
+      let panel = document.createXULElement("panel");
       panel.setAttribute("type", "arrow");
       if (remote) {
         panel.setAttribute("remote", "true");
       }
 
       document.getElementById("mainPopupSet").appendChild(panel);
       return panel;
     };
--- a/browser/components/extensions/parent/ext-menus.js
+++ b/browser/components/extensions/parent/ext-menus.js
@@ -70,17 +70,17 @@ var gMenuBuilder = {
 
     const children = this.buildChildren(root, contextData);
     const visible = children.slice(0, ACTION_MENU_TOP_LEVEL_LIMIT);
 
     this.xulMenu = menu;
     menu.addEventListener("popuphidden", this);
 
     if (visible.length) {
-      const separator = menu.ownerDocument.createElement("menuseparator");
+      const separator = menu.ownerDocument.createXULElement("menuseparator");
       menu.insertBefore(separator, menu.firstElementChild);
       this.itemsToCleanUp.add(separator);
 
       for (const child of visible) {
         this.itemsToCleanUp.add(child);
         menu.insertBefore(child, separator);
       }
     }
@@ -134,17 +134,17 @@ var gMenuBuilder = {
       rootElement.removeAttribute("class");
       rootElement.removeAttribute("image");
     }
     return rootElement;
   },
 
   appendTopLevelElement(rootElement) {
     if (this.itemsToCleanUp.size === 0) {
-      const separator = this.xulMenu.ownerDocument.createElement("menuseparator");
+      const separator = this.xulMenu.ownerDocument.createXULElement("menuseparator");
       this.itemsToCleanUp.add(separator);
       this.xulMenu.append(separator);
     }
 
     this.xulMenu.appendChild(rootElement);
     this.itemsToCleanUp.add(rootElement);
   },
 
@@ -178,28 +178,28 @@ var gMenuBuilder = {
   },
 
   buildSingleElement(item, contextData) {
     let doc = contextData.menu.ownerDocument;
     let element;
     if (item.children.length > 0) {
       element = this.createMenuElement(doc, item);
     } else if (item.type == "separator") {
-      element = doc.createElement("menuseparator");
+      element = doc.createXULElement("menuseparator");
     } else {
-      element = doc.createElement("menuitem");
+      element = doc.createXULElement("menuitem");
     }
 
     return this.customizeElement(element, item, contextData);
   },
 
   createMenuElement(doc, item) {
-    let element = doc.createElement("menu");
+    let element = doc.createXULElement("menu");
     // Menu elements need to have a menupopup child for its menu items.
-    let menupopup = doc.createElement("menupopup");
+    let menupopup = doc.createXULElement("menupopup");
     element.appendChild(menupopup);
     return element;
   },
 
   customizeElement(element, item, contextData) {
     let label = item.title;
     if (label) {
       let accessKey;
--- a/browser/components/extensions/parent/ext-tabs.js
+++ b/browser/components/extensions/parent/ext-tabs.js
@@ -35,17 +35,17 @@ const TAB_HIDE_CONFIRMED_TYPE = "tabHide
 XPCOMUtils.defineLazyGetter(this, "tabHidePopup", () => {
   return new ExtensionControlledPopup({
     confirmedType: TAB_HIDE_CONFIRMED_TYPE,
     anchorId: "alltabs-button",
     popupnotificationId: "extension-tab-hide-notification",
     descriptionId: "extension-tab-hide-notification-description",
     descriptionMessageId: "tabHideControlled.message",
     getLocalizedDescription: (doc, message, addonDetails) => {
-      let image = doc.createElement("image");
+      let image = doc.createXULElement("image");
       image.setAttribute("class", "extension-controlled-icon alltabs-icon");
       return BrowserUtils.getLocalizedFragment(doc, message, addonDetails, image);
     },
     learnMoreMessageId: "tabHideControlled.learnMore",
     learnMoreLink: "extension-hiding-tabs",
   });
 });
 
--- a/browser/components/extensions/test/browser/browser_ExtensionControlledPopup.js
+++ b/browser/components/extensions/test/browser/browser_ExtensionControlledPopup.js
@@ -11,17 +11,17 @@ registerCleanupFunction(() => {
 
 ChromeUtils.defineModuleGetter(this, "ExtensionSettingsStore",
                                "resource://gre/modules/ExtensionSettingsStore.jsm");
 ChromeUtils.defineModuleGetter(this, "ExtensionControlledPopup",
                                "resource:///modules/ExtensionControlledPopup.jsm");
 
 function createMarkup(doc) {
   let panel = doc.getElementById("extension-notification-panel");
-  let popupnotification = doc.createElement("popupnotification");
+  let popupnotification = doc.createXULElement("popupnotification");
   let attributes = {
     id: "extension-controlled-notification",
     class: "extension-controlled-notification",
     popupid: "extension-controlled",
     hidden: "true",
     label: "ExtControlled",
     buttonlabel: "Keep Changes",
     buttonaccesskey: "K",
@@ -29,19 +29,19 @@ function createMarkup(doc) {
     secondarybuttonaccesskey: "R",
     closebuttonhidden: "true",
     dropmarkerhidden: "true",
     checkboxhidden: "true",
   };
   Object.entries(attributes).forEach(([key, value]) => {
     popupnotification.setAttribute(key, value);
   });
-  let content = doc.createElement("popupnotificationcontent");
+  let content = doc.createXULElement("popupnotificationcontent");
   content.setAttribute("orient", "vertical");
-  let description = doc.createElement("description");
+  let description = doc.createXULElement("description");
   description.setAttribute("id", "extension-controlled-description");
   content.appendChild(description);
   popupnotification.appendChild(content);
   panel.appendChild(popupnotification);
 
   registerCleanupFunction(function removePopup() {
     popupnotification.remove();
   });
--- a/browser/components/newtab/lib/TopSitesFeed.jsm
+++ b/browser/components/newtab/lib/TopSitesFeed.jsm
@@ -1,14 +1,15 @@
 /* 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";
 
 ChromeUtils.import("resource://gre/modules/Services.jsm");
+ChromeUtils.import("resource://gre/modules/XPCOMUtils.jsm");
 
 const {actionCreators: ac, actionTypes: at} = ChromeUtils.import("resource://activity-stream/common/Actions.jsm", {});
 const {TippyTopProvider} = ChromeUtils.import("resource://activity-stream/lib/TippyTopProvider.jsm", {});
 const {insertPinned, TOP_SITES_MAX_SITES_PER_ROW} = ChromeUtils.import("resource://activity-stream/common/Reducers.jsm", {});
 const {Dedupe} = ChromeUtils.import("resource://activity-stream/common/Dedupe.jsm", {});
 const {shortURL} = ChromeUtils.import("resource://activity-stream/lib/ShortURL.jsm", {});
 const {getDefaultOptions} = ChromeUtils.import("resource://activity-stream/lib/ActivityStreamStorage.jsm", {});
 const {
@@ -71,17 +72,17 @@ this.TopSitesFeed = class TopSitesFeed {
   }
 
   init() {
     // If the feed was previously disabled PREFS_INITIAL_VALUES was never received
     this.refreshDefaults(this.store.getState().Prefs.values[DEFAULT_SITES_PREF]);
     this._storage = this.store.dbStorage.getDbTable("sectionPrefs");
     this.refresh({broadcast: true});
     Services.obs.addObserver(this, "browser-search-engine-modified");
-    this._currentSearchHostname = getShortURLForCurrentSearch();
+    XPCOMUtils.defineLazyGetter(this, "_currentSearchHostname", getShortURLForCurrentSearch);
   }
 
   uninit() {
     PageThumbs.removeExpirationFilter(this);
     Services.obs.removeObserver(this, "browser-search-engine-modified");
     this._currentSearchHostname = null;
   }
 
@@ -209,16 +210,19 @@ this.TopSitesFeed = class TopSitesFeed {
     }
 
     return false;
   }
 
   async getLinksWithDefaults() {
     const numItems = this.store.getState().Prefs.values[ROWS_PREF] * TOP_SITES_MAX_SITES_PER_ROW;
     const searchShortcutsExperiment = this.store.getState().Prefs.values[SEARCH_SHORTCUTS_EXPERIMENT];
+    // We must wait for search services to initialize in order to access default
+    // search engine properties without triggering a synchronous initialization
+    await new Promise(resolve => Services.search.init(resolve));
 
     // Get all frecent sites from history
     const frecent = (await this.frecentCache.request({
       // We need to overquery due to the top 5 alexa search + default search possibly being removed
       numItems: numItems + SEARCH_FILTERS.length + 1,
       topsiteFrecency: FRECENCY_THRESHOLD
     }))
     .reduce((validLinks, link) => {
--- a/browser/components/newtab/test/unit/unit-entry.js
+++ b/browser/components/newtab/test/unit/unit-entry.js
@@ -180,17 +180,23 @@ const TEST_GLOBAL = {
       createNullPrincipal() {},
       getSystemPrincipal() {}
     },
     wm: {getMostRecentWindow: () => window, getEnumerator: () => ({hasMoreElements: () => false})},
     ww: {registerNotification() {}, unregisterNotification() {}},
     appinfo: {appBuildID: "20180710100040"}
   },
   XPCOMUtils: {
-    defineLazyGetter(_1, _2, f) { f(); },
+    defineLazyGetter(object, name, f) {
+      if (object && name) {
+        object[name] = f();
+      } else {
+        f();
+      }
+    },
     defineLazyGlobalGetters() {},
     defineLazyModuleGetter() {},
     defineLazyServiceGetter() {},
     generateQI() { return {}; }
   },
   EventEmitter,
   ShellService: {isDefaultBrowser: () => true},
   FilterExpressions: {eval() { return Promise.resolve(true); }},
--- a/browser/components/nsBrowserContentHandler.js
+++ b/browser/components/nsBrowserContentHandler.js
@@ -14,16 +14,19 @@ XPCOMUtils.defineLazyModuleGetters(this,
   PrivateBrowsingUtils: "resource://gre/modules/PrivateBrowsingUtils.jsm",
   SessionStartup: "resource:///modules/sessionstore/SessionStartup.jsm",
   ShellService: "resource:///modules/ShellService.jsm",
   UpdatePing: "resource://gre/modules/UpdatePing.jsm"
 });
 XPCOMUtils.defineLazyServiceGetter(this, "WindowsUIUtils",
   "@mozilla.org/windows-ui-utils;1", "nsIWindowsUIUtils");
 
+XPCOMUtils.defineLazyGetter(this, "gSystemPrincipal",
+  () => Services.scriptSecurityManager.getSystemPrincipal());
+
 function shouldLoadURI(aURI) {
   if (aURI && !aURI.schemeIs("chrome"))
     return true;
 
   dump("*** Preventing external load of chrome: URI into browser window\n");
   dump("    Use --chrome <uri> instead\n");
   return false;
 }
@@ -157,55 +160,69 @@ function getPostUpdateOverridePage(defau
  * Open a browser window. If this is the initial launch, this function will
  * attempt to use the navigator:blank window opened by nsBrowserGlue.js during
  * early startup.
  *
  * @param cmdLine
  *        The nsICommandLine object given to nsICommandLineHandler's handle
  *        method.
  *        Used to check if we are processing the command line for the initial launch.
+ * @param triggeringPrincipal
+ *        The nsIPrincipal to use as triggering principal for the page load(s).
  * @param urlOrUrlList (optional)
  *        When omitted, the browser window will be opened with the default
  *        arguments, which will usually load the homepage.
  *        This can be a JS array of urls provided as strings, each url will be
  *        loaded in a tab. postData will be ignored in this case.
  *        This can be a single url to load in the new window, provided as a string.
  *        postData will be used in this case if provided.
  * @param postData (optional)
  *        An nsIInputStream object to use as POST data when loading the provided
  *        url, or null.
  * @param forcePrivate (optional)
  *        Boolean. If set to true, the new window will be a private browsing one.
  */
-function openBrowserWindow(cmdLine, urlOrUrlList, postData = null,
+function openBrowserWindow(cmdLine, triggeringPrincipal, urlOrUrlList, postData = null,
                            forcePrivate = false) {
   let chromeURL = AppConstants.BROWSER_CHROME_URL;
 
   let args;
   if (!urlOrUrlList) {
-    // Just pass in the defaultArgs directly
+    // Just pass in the defaultArgs directly. We'll use system principal on the other end.
     args = [gBrowserContentHandler.defaultArgs];
   } else if (Array.isArray(urlOrUrlList)) {
+    // There isn't an explicit way to pass a principal here, so we load multiple URLs
+    // with system principal when we get to actually loading them.
+    if (!triggeringPrincipal || !triggeringPrincipal.equals(gSystemPrincipal)) {
+      throw new Error("Can't open multiple URLs with something other than system principal.");
+    }
     // Passing an nsIArray for the url disables the "|"-splitting behavior.
     let uriArray = Cc["@mozilla.org/array;1"]
                      .createInstance(Ci.nsIMutableArray);
     urlOrUrlList.forEach(function(uri) {
       var sstring = Cc["@mozilla.org/supports-string;1"]
                       .createInstance(Ci.nsISupportsString);
       sstring.data = uri;
       uriArray.appendElement(sstring);
     });
     args = [uriArray];
   } else {
     // Always pass at least 3 arguments to avoid the "|"-splitting behavior,
     // ie. avoid the loadOneOrMoreURIs function.
+    // Also, we need to pass the triggering principal.
     args = [urlOrUrlList,
             null, // charset
             null, // referer
-            postData];
+            postData,
+            undefined, // allowThirdPartyFixup; this would be `false` but that
+                       // needs a conversion. Hopefully bug 1485961 will fix.
+            undefined, // referrer policy
+            undefined, // user context id
+            null, // origin principal
+            triggeringPrincipal];
   }
 
   if (cmdLine.state == Ci.nsICommandLine.STATE_INITIAL_LAUNCH) {
     let win = Services.wm.getMostRecentWindow("navigator:blank");
     if (win) {
       // Remove the windowtype of our blank window so that we don't close it
       // later on when seeing cmdLine.preventDefault is true.
       win.document.documentElement.removeAttribute("windowtype");
@@ -252,30 +269,30 @@ function openBrowserWindow(cmdLine, urlO
 }
 
 function openPreferences(cmdLine, extraArgs) {
   if (extraArgs && extraArgs.origin) {
     Services.telemetry.getHistogramById("FX_PREFERENCES_OPENED_VIA").add(extraArgs.origin);
   } else {
     Services.telemetry.getHistogramById("FX_PREFERENCES_OPENED_VIA").add("other");
   }
-  openBrowserWindow(cmdLine, "about:preferences");
+  openBrowserWindow(cmdLine, gSystemPrincipal, "about:preferences");
 }
 
 function doSearch(searchTerm, cmdLine) {
   var engine = Services.search.defaultEngine;
   var countId = (engine.identifier || ("other-" + engine.name)) + ".system";
   var count = Services.telemetry.getKeyedHistogramById("SEARCH_COUNTS");
   count.add(countId);
 
   var submission = engine.getSubmission(searchTerm, null, "system");
 
   // XXXbsmedberg: use handURIToExistingBrowser to obey tabbed-browsing
   // preferences, but need nsIBrowserDOMWindow extensions
-  openBrowserWindow(cmdLine, submission.uri.spec, submission.postData);
+  openBrowserWindow(cmdLine, gSystemPrincipal, submission.uri.spec, submission.postData);
 }
 
 function nsBrowserContentHandler() {
 }
 nsBrowserContentHandler.prototype = {
   classID: Components.ID("{5d0ce354-df01-421a-83fb-7ead0990c24e}"),
 
   _xpcom_factory: {
@@ -290,17 +307,17 @@ nsBrowserContentHandler.prototype = {
   QueryInterface: ChromeUtils.generateQI([Ci.nsICommandLineHandler,
                                           Ci.nsIBrowserHandler,
                                           Ci.nsIContentHandler,
                                           Ci.nsICommandLineValidator]),
 
   /* nsICommandLineHandler */
   handle: function bch_handle(cmdLine) {
     if (cmdLine.handleFlag("browser", false)) {
-      openBrowserWindow(cmdLine);
+      openBrowserWindow(cmdLine, gSystemPrincipal);
       cmdLine.preventDefault = true;
     }
 
     // In the past, when an instance was not already running, the -remote
     // option returned an error code. Any script or application invoking the
     // -remote option is expected to be handling this case, otherwise they
     // wouldn't be doing anything when there is no Firefox already running.
     // Making the -remote option always return an error code makes those
@@ -311,29 +328,29 @@ nsBrowserContentHandler.prototype = {
     }
 
     var uriparam;
     try {
       while ((uriparam = cmdLine.handleFlagWithParam("new-window", false))) {
         let uri = resolveURIInternal(cmdLine, uriparam);
         if (!shouldLoadURI(uri))
           continue;
-        openBrowserWindow(cmdLine, uri.spec);
+        openBrowserWindow(cmdLine, gSystemPrincipal, uri.spec);
         cmdLine.preventDefault = true;
       }
     } catch (e) {
       Cu.reportError(e);
     }
 
     try {
       while ((uriparam = cmdLine.handleFlagWithParam("new-tab", false))) {
         let uri = resolveURIInternal(cmdLine, uriparam);
         handURIToExistingBrowser(uri, Ci.nsIBrowserDOMWindow.OPEN_NEWTAB,
                                  cmdLine, false,
-                                 Services.scriptSecurityManager.getSystemPrincipal());
+                                 gSystemPrincipal);
         cmdLine.preventDefault = true;
       }
     } catch (e) {
       Cu.reportError(e);
     }
 
     var chromeParam = cmdLine.handleFlagWithParam("chrome", false);
     if (chromeParam) {
@@ -386,27 +403,26 @@ nsBrowserContentHandler.prototype = {
           // Load about:privatebrowsing in a normal tab, which will display an error indicating
           // access to private browsing has been disabled.
           forcePrivate = false;
           resolvedURI = Services.io.newURI("about:privatebrowsing");
         } else {
           resolvedURI = resolveURIInternal(cmdLine, privateWindowParam);
         }
         handURIToExistingBrowser(resolvedURI, Ci.nsIBrowserDOMWindow.OPEN_NEWTAB,
-                                 cmdLine, forcePrivate,
-                                 Services.scriptSecurityManager.getSystemPrincipal());
+                                 cmdLine, forcePrivate, gSystemPrincipal);
         cmdLine.preventDefault = true;
       }
     } catch (e) {
       if (e.result != Cr.NS_ERROR_INVALID_ARG) {
         throw e;
       }
       // NS_ERROR_INVALID_ARG is thrown when flag exists, but has no param.
       if (cmdLine.handleFlag("private-window", false)) {
-        openBrowserWindow(cmdLine, "about:privatebrowsing", null,
+        openBrowserWindow(cmdLine, gSystemPrincipal, "about:privatebrowsing", null,
                           PrivateBrowsingUtils.enabled);
         cmdLine.preventDefault = true;
       }
     }
 
     var searchParam = cmdLine.handleFlagWithParam("search", false);
     if (searchParam) {
       doSearch(searchParam, cmdLine);
@@ -429,17 +445,17 @@ nsBrowserContentHandler.prototype = {
     if (cmdLine.handleFlag("setDefaultBrowser", false)) {
       ShellService.setDefaultBrowser(true, true);
     }
 
     var fileParam = cmdLine.handleFlagWithParam("file", false);
     if (fileParam) {
       var file = cmdLine.resolveFile(fileParam);
       var fileURI = Services.io.newFileURI(file);
-      openBrowserWindow(cmdLine, fileURI.spec);
+      openBrowserWindow(cmdLine, gSystemPrincipal, fileURI.spec);
       cmdLine.preventDefault = true;
     }
 
     if (AppConstants.platform == "win") {
       // Handle "? searchterm" for Windows Vista start menu integration
       for (var i = cmdLine.length - 1; i >= 0; --i) {
         var param = cmdLine.getArgument(i);
         if (param.match(/^\? /)) {
@@ -658,17 +674,17 @@ function handURIToExistingBrowser(uri, l
     return;
 
   // Unless using a private window is forced, open external links in private
   // windows only if we're in perma-private mode.
   var allowPrivate = forcePrivate || PrivateBrowsingUtils.permanentPrivateBrowsing;
   var navWin = BrowserWindowTracker.getTopWindow({private: allowPrivate});
   if (!navWin) {
     // if we couldn't load it in an existing window, open a new one
-    openBrowserWindow(cmdLine, uri.spec, null, forcePrivate);
+    openBrowserWindow(cmdLine, triggeringPrincipal, uri.spec, null, forcePrivate);
     return;
   }
 
   var bwin = navWin.browserDOMWindow;
   bwin.openURI(uri, null, location,
                Ci.nsIBrowserDOMWindow.OPEN_EXTERNAL, triggeringPrincipal);
 }
 
@@ -739,40 +755,39 @@ nsDefaultCommandLineHandler.prototype = 
 
     if (urilist.length) {
       if (cmdLine.state != Ci.nsICommandLine.STATE_INITIAL_LAUNCH &&
           urilist.length == 1) {
         // Try to find an existing window and load our URI into the
         // current tab, new tab, or new window as prefs determine.
         try {
           handURIToExistingBrowser(urilist[0], Ci.nsIBrowserDOMWindow.OPEN_DEFAULTWINDOW,
-                                   cmdLine, false,
-                                   Services.scriptSecurityManager.getSystemPrincipal());
+                                   cmdLine, false, gSystemPrincipal);
           return;
         } catch (e) {
         }
       }
 
       var URLlist = urilist.filter(shouldLoadURI).map(u => u.spec);
       if (URLlist.length) {
-        openBrowserWindow(cmdLine, URLlist);
+        openBrowserWindow(cmdLine, gSystemPrincipal, URLlist);
       }
 
     } else if (!cmdLine.preventDefault) {
       if (AppConstants.isPlatformAndVersionAtLeast("win", "10") &&
           cmdLine.state != Ci.nsICommandLine.STATE_INITIAL_LAUNCH &&
           WindowsUIUtils.inTabletMode) {
         // In windows 10 tablet mode, do not create a new window, but reuse the existing one.
         let win = BrowserWindowTracker.getTopWindow();
         if (win) {
           win.focus();
           return;
         }
       }
-      openBrowserWindow(cmdLine);
+      openBrowserWindow(cmdLine, gSystemPrincipal);
     } else {
       // Need a better solution in the future to avoid opening the blank window
       // when command line parameters say we are not going to show a browser
       // window, but for now the blank window getting closed quickly (and
       // causing only a slight flicker) is better than leaving it open.
       let win = Services.wm.getMostRecentWindow("navigator:blank");
       if (win)
         win.close();
--- a/browser/components/payments/res/containers/address-form.css
+++ b/browser/components/payments/res/containers/address-form.css
@@ -21,17 +21,17 @@
 body[dir="ltr"] .error-text {
   left: 3px;
 }
 
 body[dir="rtl"] .error-text {
   right: 3px;
 }
 
-:-moz-any(input, textarea, select):focus + .error-text:not(:empty)::before {
+:-moz-any(input, textarea, select):focus ~ .error-text:not(:empty)::before {
   background-color: #d70022;
   top: -7px;
   content: '.';
   height: 16px;
   position: absolute;
   text-indent: -999px;
   transform: rotate(45deg);
   white-space: nowrap;
@@ -42,18 +42,18 @@ body[dir="rtl"] .error-text {
 body[dir=ltr] .error-text::before {
   left: 12px
 }
 
 body[dir=rtl] .error-text::before {
   right: 12px
 }
 
-:-moz-any(input, textarea, select):not(:focus) + .error-text,
-:-moz-any(input, textarea, select):valid + .error-text {
+:-moz-any(input, textarea, select):not(:focus) ~ .error-text,
+:-moz-any(input, textarea, select):valid ~ .error-text {
   display: none;
 }
 
 address-form > footer > .cancel-button {
   /* When cancel is shown (during onboarding), it should always be on the left with a space after it */
   margin-right: auto;
 }
 
--- a/browser/components/payments/test/mochitest/test_address_form.html
+++ b/browser/components/payments/test/mochitest/test_address_form.html
@@ -292,16 +292,17 @@ add_task(async function test_restricted_
   ok(isHidden(form.form.querySelector("#country")),
      "country should be hidden");
   ok(!isHidden(form.form.querySelector("#email")),
      "email should be visible");
   ok(!isHidden(form.form.querySelector("#tel")),
      "tel should be visible");
 
   fillField(form.form.querySelector("#given-name"), "John");
+  fillField(form.form.querySelector("#family-name"), "Smith");
   ok(form.saveButton.disabled, "Save button should be disabled due to empty fields");
   fillField(form.form.querySelector("#email"), "john@example.com");
   todo(form.saveButton.disabled,
        "Save button should be disabled due to empty fields - Bug 1483412");
   fillField(form.form.querySelector("#tel"), "+15555555555");
   ok(!form.saveButton.disabled, "Save button should be enabled with all required fields filled");
 
   form.remove();
--- a/browser/components/places/content/browserPlacesViews.js
+++ b/browser/components/places/content/browserPlacesViews.js
@@ -306,17 +306,17 @@ PlacesViewBase.prototype = {
 
     aChild.remove();
   },
 
   _setEmptyPopupStatus:
   function PVB__setEmptyPopupStatus(aPopup, aEmpty) {
     if (!aPopup._emptyMenuitem) {
       let label = PlacesUIUtils.getString("bookmarksMenuEmptyFolder");
-      aPopup._emptyMenuitem = document.createElement("menuitem");
+      aPopup._emptyMenuitem = document.createXULElement("menuitem");
       aPopup._emptyMenuitem.setAttribute("label", label);
       aPopup._emptyMenuitem.setAttribute("disabled", true);
       aPopup._emptyMenuitem.className = "bookmark-item";
       if (typeof this.options.extraClasses.entry == "string")
         aPopup._emptyMenuitem.classList.add(this.options.extraClasses.entry);
     }
 
     if (aEmpty) {
@@ -335,27 +335,27 @@ PlacesViewBase.prototype = {
 
   _createDOMNodeForPlacesNode:
   function PVB__createDOMNodeForPlacesNode(aPlacesNode) {
     this._domNodes.delete(aPlacesNode);
 
     let element;
     let type = aPlacesNode.type;
     if (type == Ci.nsINavHistoryResultNode.RESULT_TYPE_SEPARATOR) {
-      element = document.createElement("menuseparator");
+      element = document.createXULElement("menuseparator");
       element.setAttribute("class", "small-separator");
     } else {
       let itemId = aPlacesNode.itemId;
       if (type == Ci.nsINavHistoryResultNode.RESULT_TYPE_URI) {
-        element = document.createElement("menuitem");
+        element = document.createXULElement("menuitem");
         element.className = "menuitem-iconic bookmark-item menuitem-with-favicon";
         element.setAttribute("scheme",
                              PlacesUIUtils.guessUrlSchemeForUI(aPlacesNode.uri));
       } else if (PlacesUtils.containerTypes.includes(type)) {
-        element = document.createElement("menu");
+        element = document.createXULElement("menu");
         element.setAttribute("container", "true");
 
         if (aPlacesNode.type == Ci.nsINavHistoryResultNode.RESULT_TYPE_QUERY) {
           element.setAttribute("query", "true");
           if (PlacesUtils.nodeIsTagQuery(aPlacesNode))
             element.setAttribute("tagContainer", "true");
           else if (PlacesUtils.nodeIsDay(aPlacesNode))
             element.setAttribute("dayContainer", "true");
@@ -370,17 +370,17 @@ PlacesViewBase.prototype = {
                 // it doesn't have a frame (bug 733415).  Thus enforce updating.
                 element.setAttribute("image", "");
                 element.removeAttribute("image");
               }
               this.controller.cacheLivemarkInfo(aPlacesNode, aLivemark);
             }, () => undefined);
         }
 
-        let popup = document.createElement("menupopup");
+        let popup = document.createXULElement("menupopup");
         popup._placesNode = PlacesUtils.asContainer(aPlacesNode);
 
         if (!this._nativeView) {
           popup.setAttribute("placespopup", "true");
         }
 
         element.appendChild(popup);
         element.className = "menu-iconic bookmark-item";
@@ -426,17 +426,17 @@ PlacesViewBase.prototype = {
                   livemarkInfo.siteURI.spec : null;
     if (!siteUrl && aPopup._siteURIMenuitem) {
       aPopup.removeChild(aPopup._siteURIMenuitem);
       aPopup._siteURIMenuitem = null;
       aPopup.removeChild(aPopup._siteURIMenuseparator);
       aPopup._siteURIMenuseparator = null;
     } else if (siteUrl && !aPopup._siteURIMenuitem) {
       // Add "Open (Feed Name)" menuitem.
-      aPopup._siteURIMenuitem = document.createElement("menuitem");
+      aPopup._siteURIMenuitem = document.createXULElement("menuitem");
       aPopup._siteURIMenuitem.className = "openlivemarksite-menuitem";
       if (typeof this.options.extraClasses.entry == "string") {
         aPopup._siteURIMenuitem.classList.add(this.options.extraClasses.entry);
       }
       aPopup._siteURIMenuitem.setAttribute("targetURI", siteUrl);
       aPopup._siteURIMenuitem.setAttribute("oncommand",
         "openUILink(this.getAttribute('targetURI'), event, {" +
         " triggeringPrincipal: Services.scriptSecurityManager.createNullPrincipal({})});");
@@ -448,34 +448,34 @@ PlacesViewBase.prototype = {
       aPopup._siteURIMenuitem.setAttribute("onclick",
         "checkForMiddleClick(this, event); event.stopPropagation();");
       let label =
         PlacesUIUtils.getFormattedString("menuOpenLivemarkOrigin.label",
                                          [aPopup.parentNode.getAttribute("label")]);
       aPopup._siteURIMenuitem.setAttribute("label", label);
       aPopup.insertBefore(aPopup._siteURIMenuitem, aPopup._startMarker);
 
-      aPopup._siteURIMenuseparator = document.createElement("menuseparator");
+      aPopup._siteURIMenuseparator = document.createXULElement("menuseparator");
       aPopup.insertBefore(aPopup._siteURIMenuseparator, aPopup._startMarker);
     }
   },
 
   /**
    * Add, update or remove the livemark status menuitem.
    * @param aPopup
    *        The livemark container popup
    * @param aStatus
    *        The livemark status
    */
   _setLivemarkStatusMenuItem:
   function PVB_setLivemarkStatusMenuItem(aPopup, aStatus) {
     let statusMenuitem = aPopup._statusMenuitem;
     if (!statusMenuitem) {
       // Create the status menuitem and cache it in the popup object.
-      statusMenuitem = document.createElement("menuitem");
+      statusMenuitem = document.createXULElement("menuitem");
       statusMenuitem.className = "livemarkstatus-menuitem";
       if (typeof this.options.extraClasses.entry == "string") {
         statusMenuitem.classList.add(this.options.extraClasses.entry);
       }
       statusMenuitem.setAttribute("disabled", true);
       aPopup._statusMenuitem = statusMenuitem;
     }
 
@@ -840,22 +840,22 @@ PlacesViewBase.prototype = {
         aPopup.removeChild(aPopup._endOptOpenAllInTabs);
         aPopup._endOptOpenAllInTabs = null;
 
         aPopup.removeChild(aPopup._endOptSeparator);
         aPopup._endOptSeparator = null;
       }
     } else if (!aPopup._endOptOpenAllInTabs) {
       // Create a separator before options.
-      aPopup._endOptSeparator = document.createElement("menuseparator");
+      aPopup._endOptSeparator = document.createXULElement("menuseparator");
       aPopup._endOptSeparator.className = "bookmarks-actions-menuseparator";
       aPopup.appendChild(aPopup._endOptSeparator);
 
       // Add the "Open All in Tabs" menuitem.
-      aPopup._endOptOpenAllInTabs = document.createElement("menuitem");
+      aPopup._endOptOpenAllInTabs = document.createXULElement("menuitem");
       aPopup._endOptOpenAllInTabs.className = "openintabs-menuitem";
 
       if (typeof this.options.extraClasses.entry == "string")
         aPopup._endOptOpenAllInTabs.classList.add(this.options.extraClasses.entry);
       if (typeof this.options.extraClasses.footer == "string")
         aPopup._endOptOpenAllInTabs.classList.add(this.options.extraClasses.footer);
 
       if (isLiveMark) {
@@ -875,28 +875,28 @@ PlacesViewBase.prototype = {
     }
   },
 
   _ensureMarkers: function PVB__ensureMarkers(aPopup) {
     if (aPopup._startMarker)
       return;
 
     // _startMarker is an hidden menuseparator that lives before places nodes.
-    aPopup._startMarker = document.createElement("menuseparator");
+    aPopup._startMarker = document.createXULElement("menuseparator");
     aPopup._startMarker.hidden = true;
     aPopup.insertBefore(aPopup._startMarker, aPopup.firstElementChild);
 
     // _endMarker is a DOM node that lives after places nodes, specified with
     // the 'insertionPoint' option or will be a hidden menuseparator.
     let node = this.options.insertionPoint ?
                aPopup.querySelector(this.options.insertionPoint) : null;
     if (node) {
       aPopup._endMarker = node;
     } else {
-      aPopup._endMarker = document.createElement("menuseparator");
+      aPopup._endMarker = document.createXULElement("menuseparator");
       aPopup._endMarker.hidden = true;
     }
     aPopup.appendChild(aPopup._endMarker);
 
     // Move the markers to the right position.
     let firstNonStaticNodeFound = false;
     for (let i = 0; i < aPopup.children.length; i++) {
       let child = aPopup.children[i];
@@ -1104,19 +1104,19 @@ PlacesToolbar.prototype = {
 
   _insertNewItem:
   function PT__insertNewItem(aChild, aInsertionNode, aBefore = null) {
     this._domNodes.delete(aChild);
 
     let type = aChild.type;
     let button;
     if (type == Ci.nsINavHistoryResultNode.RESULT_TYPE_SEPARATOR) {
-      button = document.createElement("toolbarseparator");
+      button = document.createXULElement("toolbarseparator");
     } else {
-      button = document.createElement("toolbarbutton");
+      button = document.createXULElement("toolbarbutton");
       button.className = "bookmark-item";
       button.setAttribute("label", aChild.title || "");
 
       if (PlacesUtils.containerTypes.includes(type)) {
         button.setAttribute("type", "menu");
         button.setAttribute("container", "true");
 
         if (PlacesUtils.nodeIsQuery(aChild)) {
@@ -1126,17 +1126,17 @@ PlacesToolbar.prototype = {
         } else if (PlacesUtils.nodeIsFolder(aChild)) {
           PlacesUtils.livemarks.getLivemark({ id: aChild.itemId })
             .then(aLivemark => {
               button.setAttribute("livemark", "true");
               this.controller.cacheLivemarkInfo(aChild, aLivemark);
             }, () => undefined);
         }
 
-        let popup = document.createElement("menupopup");
+        let popup = document.createXULElement("menupopup");
         popup.setAttribute("placespopup", "true");
         button.appendChild(popup);
         popup._placesNode = PlacesUtils.asContainer(aChild);
         popup.setAttribute("context", "placesContext");
 
         this._domNodes.set(aChild, popup);
       } else if (PlacesUtils.nodeIsURI(aChild)) {
         button.setAttribute("scheme",
@@ -2002,20 +2002,20 @@ PlacesPanelMenuView.prototype = {
 
   _insertNewItem:
   function PAMV__insertNewItem(aChild, aInsertionNode, aBefore = null) {
     this._domNodes.delete(aChild);
 
     let type = aChild.type;
     let button;
     if (type == Ci.nsINavHistoryResultNode.RESULT_TYPE_SEPARATOR) {
-      button = document.createElement("toolbarseparator");
+      button = document.createXULElement("toolbarseparator");
       button.setAttribute("class", "small-separator");
     } else {
-      button = document.createElement("toolbarbutton");
+      button = document.createXULElement("toolbarbutton");
       button.className = "bookmark-item";
       if (typeof this.options.extraClasses.entry == "string")
         button.classList.add(this.options.extraClasses.entry);
       button.setAttribute("label", aChild.title || "");
       let icon = aChild.icon;
       if (icon)
         button.setAttribute("image", icon);
 
@@ -2226,22 +2226,22 @@ this.PlacesPanelview = class extends Pla
   }
 
   _createDOMNodeForPlacesNode(placesNode) {
     this._domNodes.delete(placesNode);
 
     let element;
     let type = placesNode.type;
     if (type == Ci.nsINavHistoryResultNode.RESULT_TYPE_SEPARATOR) {
-      element = document.createElement("toolbarseparator");
+      element = document.createXULElement("toolbarseparator");
     } else {
       if (type != Ci.nsINavHistoryResultNode.RESULT_TYPE_URI)
         throw "Unexpected node";
 
-      element = document.createElement("toolbarbutton");
+      element = document.createXULElement("toolbarbutton");
       element.classList.add("subviewbutton", "subviewbutton-iconic", "bookmark-item");
       element.setAttribute("scheme", PlacesUIUtils.guessUrlSchemeForUI(placesNode.uri));
       element.setAttribute("label", PlacesUIUtils.getBestTitle(placesNode));
 
       let icon = placesNode.icon;
       if (icon)
         element.setAttribute("image", icon);
     }
@@ -2251,17 +2251,17 @@ this.PlacesPanelview = class extends Pla
       this._domNodes.set(placesNode, element);
 
     return element;
   }
 
   _setEmptyPopupStatus(panelview, empty = false) {
     if (!panelview._emptyMenuitem) {
       let label = PlacesUIUtils.getString("bookmarksMenuEmptyFolder");
-      panelview._emptyMenuitem = document.createElement("toolbarbutton");
+      panelview._emptyMenuitem = document.createXULElement("toolbarbutton");
       panelview._emptyMenuitem.setAttribute("label", label);
       panelview._emptyMenuitem.setAttribute("disabled", true);
       panelview._emptyMenuitem.className = "subviewbutton";
       if (typeof this.options.extraClasses.entry == "string")
         panelview._emptyMenuitem.classList.add(this.options.extraClasses.entry);
     }
 
     if (empty) {
--- a/browser/components/places/content/editBookmark.js
+++ b/browser/components/places/content/editBookmark.js
@@ -344,17 +344,17 @@ var gEditItemOverlay = {
    * @param aTitle
    *        The title to use as a label.
    * @return the new menu item.
    */
   _appendFolderItemToMenupopup(aMenupopup, aFolderGuid, aTitle) {
     // First make sure the folders-separator is visible
     this._element("foldersSeparator").hidden = false;
 
-    var folderMenuItem = document.createElement("menuitem");
+    var folderMenuItem = document.createXULElement("menuitem");
     folderMenuItem.folderGuid = aFolderGuid;
     folderMenuItem.setAttribute("label", aTitle);
     folderMenuItem.className = "menuitem-iconic folder-icon";
     aMenupopup.appendChild(folderMenuItem);
     return folderMenuItem;
   },
 
   async _initFolderMenuList(aSelectedFolderGuid) {
@@ -755,19 +755,19 @@ var gEditItemOverlay = {
       tagsSelector.removeChild(tagsSelector.lastElementChild);
     }
 
     let tagsInField = this._getTagsArrayFromTagsInputField();
     let allTags = await PlacesUtils.bookmarks.fetchTags();
     let fragment = document.createDocumentFragment();
     for (let i = 0; i < allTags.length; i++) {
       let tag = allTags[i].name;
-      let elt = document.createElement("richlistitem");
-      elt.appendChild(document.createElement("image"));
-      let label = document.createElement("label");
+      let elt = document.createXULElement("richlistitem");
+      elt.appendChild(document.createXULElement("image"));
+      let label = document.createXULElement("label");
       label.setAttribute("value", tag);
       elt.appendChild(label);
       if (tagsInField.includes(tag))
         elt.setAttribute("checked", "true");
       fragment.appendChild(elt);
       if (selectedTag === tag)
         selectedIndex = i;
     }
--- a/browser/components/places/tests/browser/browser_sort_in_library.js
+++ b/browser/components/places/tests/browser/browser_sort_in_library.js
@@ -132,17 +132,17 @@ function setSort(aOrganizerWin, aTree, a
  *
  * @param aOrganizerWin
  *        the Places window
  * @param aPlaceContentTree
  *        the placeContent tree in aOrganizerWin
  */
 function testInvalid(aOrganizerWin, aPlaceContentTree) {
   // Invalid column should fail by throwing an exception.
-  let bogusCol = document.createElement("treecol");
+  let bogusCol = document.createXULElement("treecol");
   bogusCol.setAttribute("anonid", "bogusColumn");
   setSort(aOrganizerWin, aPlaceContentTree, true, true, bogusCol, "ascending");
 
   // Invalid direction reverts to SORT_BY_NONE.
   setSort(aOrganizerWin, aPlaceContentTree, false, false, null, "bogus dir");
   checkSort(aPlaceContentTree, Ci.nsINavHistoryQueryOptions.SORT_BY_NONE, "");
 }
 
--- a/browser/components/preferences/applicationManager.js
+++ b/browser/components/preferences/applicationManager.js
@@ -35,21 +35,21 @@ var gAppManagerDialog = {
         continue;
 
       // Ensure the XBL binding is created eagerly.
       // eslint-disable-next-line no-undef
       list.appendChild(MozXULElement.parseXULToFragment("<richlistitem/>"));
       var item = list.lastChild;
       item.app = app;
 
-      var image = document.createElement("image");
+      var image = document.createXULElement("image");
       image.setAttribute("src", gMainPane._getIconURLForHandlerApp(app));
       item.appendChild(image);
 
-      var label = document.createElement("label");
+      var label = document.createXULElement("label");
       label.setAttribute("value", app.name);
       item.appendChild(label);
     }
 
     // Triggers onSelect which populates label
     list.selectedIndex = 0;
 
     // We want to block on those elements being localized because the
--- a/browser/components/preferences/connection.js
+++ b/browser/components/preferences/connection.js
@@ -28,16 +28,18 @@ Preferences.addAll([
   { id: "signon.autologin.proxy", type: "bool" },
   { id: "pref.advanced.proxies.disable_button.reload", type: "bool" },
   { id: "network.proxy.backup.ftp", type: "string" },
   { id: "network.proxy.backup.ftp_port", type: "int" },
   { id: "network.proxy.backup.ssl", type: "string" },
   { id: "network.proxy.backup.ssl_port", type: "int" },
   { id: "network.proxy.backup.socks", type: "string" },
   { id: "network.proxy.backup.socks_port", type: "int" },
+  { id: "network.trr.mode", type: "int" },
+  { id: "network.trr.uri", type: "string" },
 ]);
 
 window.addEventListener("DOMContentLoaded", () => {
   Preferences.get("network.proxy.type").on("change",
     gConnectionsDialog.proxyTypeChanged.bind(gConnectionsDialog));
   Preferences.get("network.proxy.socks_version").on("change",
     gConnectionsDialog.updateDNSPref.bind(gConnectionsDialog));
 
@@ -272,10 +274,40 @@ var gConnectionsDialog = {
     if (isLocked) {
       // An extension can't control this setting if any pref is locked.
       hideControllingExtension(PROXY_KEY);
       setInputsDisabledState(false);
     } else {
       handleControllingExtension(PREF_SETTING_TYPE, PROXY_KEY)
         .then(setInputsDisabledState);
     }
+  },
+
+  isDnsOverHttpsEnabled() {
+    // values outside 1:4 are considered falsey/disabled in this context
+    let trrPref = Preferences.get("network.trr.mode");
+    let enabled = trrPref.value > 0 && trrPref.value < 5;
+    return enabled;
+  },
+
+  readDnsOverHttpsMode() {
+    // called to update checked element property to reflect current pref value
+    let enabled = this.isDnsOverHttpsEnabled();
+    let uriPref = Preferences.get("network.trr.uri");
+    uriPref.disabled = !enabled;
+    return enabled;
+  },
+
+  writeDnsOverHttpsMode() {
+    // called to update pref with user change
+    let trrModeCheckbox = document.getElementById("networkDnsOverHttps");
+    // we treat checked/enabled as mode 2
+    return trrModeCheckbox.checked ? 2 : 0;
+  },
+
+  writeDnsOverHttpsUri() {
+    // called to update pref with user input
+    let input = document.getElementById("networkDnsOverHttpsUrl");
+    let uriString = input.value.trim();
+    // turn an empty string into `undefined` to clear the pref back to the default
+    return uriString.length ? uriString : undefined;
   }
 };
--- a/browser/components/preferences/connection.xul
+++ b/browser/components/preferences/connection.xul
@@ -143,11 +143,23 @@
     </groupbox>
     <separator class="thin"/>
     <checkbox id="autologinProxy"
               data-l10n-id="connection-proxy-autologin"
               preference="signon.autologin.proxy" />
     <checkbox id="networkProxySOCKSRemoteDNS"
               preference="network.proxy.socks_remote_dns"
               data-l10n-id="connection-proxy-socks-remote-dns" />
+    <checkbox id="networkDnsOverHttps"
+              data-l10n-id="connection-dns-over-https"
+              preference="network.trr.mode"
+              onsyncfrompreference="return gConnectionsDialog.readDnsOverHttpsMode();"
+              onsynctopreference="return gConnectionsDialog.writeDnsOverHttpsMode()" />
+    <hbox class="indent" flex="1" align="center">
+      <label control="networkDnsOverHttpsUrl" data-l10n-id="connection-dns-over-https-url"
+             data-l10n-attrs="tooltiptext"/>
+      <textbox id="networkDnsOverHttpsUrl" flex="1" preference="network.trr.uri"
+               placeholder="https://doh.example.com/dns-query"
+               onsynctopreference="return gConnectionsDialog.writeDnsOverHttpsUri()" />
+    </hbox>
     <separator/>
   </vbox>
 </dialog>
--- a/browser/components/preferences/containers.js
+++ b/browser/components/preferences/containers.js
@@ -84,61 +84,61 @@ let gContainersManager = {
     if (!name.value) {
       btnApplyChanges.setAttribute("disabled", true);
     } else {
       btnApplyChanges.removeAttribute("disabled");
     }
   },
 
   createIconButtons(defaultIcon) {
-    let radiogroup = document.createElement("radiogroup");
+    let radiogroup = document.createXULElement("radiogroup");
     radiogroup.setAttribute("id", "icon");
     radiogroup.className = "icon-buttons radio-buttons";
 
     for (let icon of this.icons) {
-      let iconSwatch = document.createElement("radio");
+      let iconSwatch = document.createXULElement("radio");
       iconSwatch.id = "iconbutton-" + icon;
       iconSwatch.name = "icon";
       iconSwatch.type = "radio";
       iconSwatch.value = icon;
 
       if (this.identity.icon && this.identity.icon == icon) {
         iconSwatch.setAttribute("selected", true);
       }
 
       document.l10n.setAttributes(iconSwatch, `containers-icon-${icon}`);
-      let iconElement = document.createElement("hbox");
+      let iconElement = document.createXULElement("hbox");
       iconElement.className = "userContext-icon";
       iconElement.setAttribute("data-identity-icon", icon);
 
       iconSwatch.appendChild(iconElement);
       radiogroup.appendChild(iconSwatch);
     }
 
     return radiogroup;
   },
 
   createColorSwatches(defaultColor) {
-    let radiogroup = document.createElement("radiogroup");
+    let radiogroup = document.createXULElement("radiogroup");
     radiogroup.setAttribute("id", "color");
     radiogroup.className = "radio-buttons";
 
     for (let color of this.colors) {
-      let colorSwatch = document.createElement("radio");
+      let colorSwatch = document.createXULElement("radio");
       colorSwatch.id = "colorswatch-" + color;
       colorSwatch.name = "color";
       colorSwatch.type = "radio";
       colorSwatch.value = color;
 
       if (this.identity.color && this.identity.color == color) {
         colorSwatch.setAttribute("selected", true);
       }
 
       document.l10n.setAttributes(colorSwatch, `containers-color-${color}`);
-      let iconElement = document.createElement("hbox");
+      let iconElement = document.createXULElement("hbox");
       iconElement.className = "userContext-icon";
       iconElement.setAttribute("data-identity-icon", "circle");
       iconElement.setAttribute("data-identity-color", color);
 
       colorSwatch.appendChild(iconElement);
       radiogroup.appendChild(colorSwatch);
     }
     return radiogroup;
--- a/browser/components/preferences/in-content/containers.js
+++ b/browser/components/preferences/in-content/containers.js
@@ -25,50 +25,50 @@ let gContainersPane = {
   },
 
   _rebuildView() {
     const containers = ContextualIdentityService.getPublicIdentities();
     while (this._list.firstChild) {
       this._list.firstChild.remove();
     }
     for (let container of containers) {
-      let item = document.createElement("richlistitem");
+      let item = document.createXULElement("richlistitem");
 
-      let outer = document.createElement("hbox");
+      let outer = document.createXULElement("hbox");
       outer.setAttribute("flex", 1);
       outer.setAttribute("align", "center");
       item.appendChild(outer);
 
-      let userContextIcon = document.createElement("hbox");
+      let userContextIcon = document.createXULElement("hbox");
       userContextIcon.className = "userContext-icon";
       userContextIcon.setAttribute("width", 24);
       userContextIcon.setAttribute("height", 24);
       userContextIcon.setAttribute("data-identity-icon", container.icon);
       userContextIcon.setAttribute("data-identity-color", container.color);
       outer.appendChild(userContextIcon);
 
-      let label = document.createElement("label");
+      let label = document.createXULElement("label");
       label.setAttribute("flex", 1);
       label.setAttribute("crop", "end");
       label.textContent = ContextualIdentityService.getUserContextLabel(container.userContextId);
       outer.appendChild(label);
 
-      let containerButtons = document.createElement("hbox");
+      let containerButtons = document.createXULElement("hbox");
       containerButtons.className = "container-buttons";
       containerButtons.setAttribute("flex", 1);
       containerButtons.setAttribute("align", "right");
       item.appendChild(containerButtons);
 
-      let prefsButton = document.createElement("button");
+      let prefsButton = document.createXULElement("button");
       prefsButton.setAttribute("oncommand", "gContainersPane.onPreferenceCommand(event.originalTarget)");
       prefsButton.setAttribute("value", container.userContextId);
       document.l10n.setAttributes(prefsButton, "containers-preferences-button");
       containerButtons.appendChild(prefsButton);
 
-      let removeButton = document.createElement("button");
+      let removeButton = document.createXULElement("button");
       removeButton.setAttribute("oncommand", "gContainersPane.onRemoveCommand(event.originalTarget)");
       removeButton.setAttribute("value", container.userContextId);
       document.l10n.setAttributes(removeButton, "containers-remove-button");
       containerButtons.appendChild(removeButton);
 
       this._list.appendChild(item);
     }
   },
--- a/browser/components/preferences/in-content/extensionControlled.js
+++ b/browser/components/preferences/in-content/extensionControlled.js
@@ -218,24 +218,24 @@ function showEnableExtensionMessage(sett
 
   let icon = (url, name) => {
     let img = document.createElementNS("http://www.w3.org/1999/xhtml", "img");
     img.src = url;
     img.setAttribute("data-l10n-name", name);
     img.className = "extension-controlled-icon";
     return img;
   };
-  let label = document.createElement("label");
+  let label = document.createXULElement("label");
   let addonIcon = icon("chrome://mozapps/skin/extensions/extensionGeneric-16.svg", "addons-icon");
   let toolbarIcon = icon("chrome://browser/skin/menu.svg", "menu-icon");
   label.appendChild(addonIcon);
   label.appendChild(toolbarIcon);
   document.l10n.setAttributes(label, "extension-controlled-enable");
   elements.description.appendChild(label);
-  let dismissButton = document.createElement("image");
+  let dismissButton = document.createXULElement("image");
   dismissButton.setAttribute("class", "extension-controlled-icon close-icon");
   dismissButton.addEventListener("click", function dismissHandler() {
     hideControllingExtension(settingName);
     dismissButton.removeEventListener("click", dismissHandler);
   });
   elements.description.appendChild(dismissButton);
 }
 
--- a/browser/components/preferences/in-content/findInPage.js
+++ b/browser/components/preferences/in-content/findInPage.js
@@ -513,18 +513,18 @@ var gSearchResultsPane = {
    *    DOM Element
    * @param String query
    *    Word or words that are being searched for
    */
   createSearchTooltip(anchorNode, query) {
     if (anchorNode.tooltipNode) {
       return;
     }
-    let searchTooltip = anchorNode.ownerDocument.createElement("span");
-    let searchTooltipText = anchorNode.ownerDocument.createElement("span");
+    let searchTooltip = anchorNode.ownerDocument.createXULElement("span");
+    let searchTooltipText = anchorNode.ownerDocument.createXULElement("span");
     searchTooltip.className = "search-tooltip";
     searchTooltipText.textContent = query;
     searchTooltip.appendChild(searchTooltipText);
 
     // Set tooltipNode property to track corresponded tooltip node.
     anchorNode.tooltipNode = searchTooltip;
     anchorNode.parentElement.classList.add("search-tooltip-parent");
     anchorNode.parentElement.appendChild(searchTooltip);
--- a/browser/components/preferences/in-content/main.js
+++ b/browser/components/preferences/in-content/main.js
@@ -788,17 +788,17 @@ var gMainPane = {
   initBrowserLocale() {
     let localeCodes = Services.locale.getAvailableLocales();
     let localeNames = Services.intl.getLocaleDisplayNames(undefined, localeCodes);
     let locales = localeCodes.map((code, i) => ({code, name: localeNames[i]}));
     locales.sort((a, b) => a.name > b.name);
 
     let fragment = document.createDocumentFragment();
     for (let {code, name} of locales) {
-      let menuitem = document.createElement("menuitem");
+      let menuitem = document.createXULElement("menuitem");
       menuitem.setAttribute("value", code);
       menuitem.setAttribute("label", name);
       fragment.appendChild(menuitem);
     }
     let menulist = document.getElementById("defaultBrowserLanguage");
     let menupopup = menulist.querySelector("menupopup");
     menupopup.appendChild(fragment);
     menulist.value = Services.locale.getRequestedLocale();
@@ -1581,28 +1581,28 @@ var gMainPane = {
 
     // Clear out existing items.
     while (menuPopup.hasChildNodes())
       menuPopup.removeChild(menuPopup.lastChild);
 
     let internalMenuItem;
     // Add the "Preview in Firefox" option for optional internal handlers.
     if (handlerInfo instanceof InternalHandlerInfoWrapper) {
-      internalMenuItem = document.createElement("menuitem");
+      internalMenuItem = document.createXULElement("menuitem");
       internalMenuItem.setAttribute("action", Ci.nsIHandlerInfo.handleInternally);
       let label = gMainPane._prefsBundle.getFormattedString("previewInApp",
         [this._brandShortName]);
       internalMenuItem.setAttribute("label", label);
       internalMenuItem.setAttribute("tooltiptext", label);
       internalMenuItem.setAttribute(APP_ICON_ATTR_NAME, "ask");
       menuPopup.appendChild(internalMenuItem);
     }
 
     {
-      var askMenuItem = document.createElement("menuitem");
+      var askMenuItem = document.createXULElement("menuitem");
       askMenuItem.setAttribute("action", Ci.nsIHandlerInfo.alwaysAsk);
       let label;
       if (isFeedType(handlerInfo.type))
         label = gMainPane._prefsBundle.getFormattedString("previewInApp",
           [this._brandShortName]);
       else
         label = gMainPane._prefsBundle.getString("alwaysAsk");
       askMenuItem.setAttribute("label", label);
@@ -1612,45 +1612,45 @@ var gMainPane = {
     }
 
     // Create a menu item for saving to disk.
     // Note: this option isn't available to protocol types, since we don't know
     // what it means to save a URL having a certain scheme to disk, nor is it
     // available to feeds, since the feed code doesn't implement the capability.
     if ((handlerInfo.wrappedHandlerInfo instanceof Ci.nsIMIMEInfo) &&
       !isFeedType(handlerInfo.type)) {
-      var saveMenuItem = document.createElement("menuitem");
+      var saveMenuItem = document.createXULElement("menuitem");
       saveMenuItem.setAttribute("action", Ci.nsIHandlerInfo.saveToDisk);
       let label = gMainPane._prefsBundle.getString("saveFile");
       saveMenuItem.setAttribute("label", label);
       saveMenuItem.setAttribute("tooltiptext", label);
       saveMenuItem.setAttribute(APP_ICON_ATTR_NAME, "save");
       menuPopup.appendChild(saveMenuItem);
     }
 
     // If this is the feed type, add a Live Bookmarks item.
     if (isFeedType(handlerInfo.type)) {
-      internalMenuItem = document.createElement("menuitem");
+      internalMenuItem = document.createXULElement("menuitem");
       internalMenuItem.setAttribute("action", Ci.nsIHandlerInfo.handleInternally);
       let label = gMainPane._prefsBundle.getFormattedString("addLiveBookmarksInApp",
         [this._brandShortName]);
       internalMenuItem.setAttribute("label", label);
       internalMenuItem.setAttribute("tooltiptext", label);
       internalMenuItem.setAttribute(APP_ICON_ATTR_NAME, "feed");
       menuPopup.appendChild(internalMenuItem);
     }
 
     // Add a separator to distinguish these items from the helper app items
     // that follow them.
-    let menuseparator = document.createElement("menuseparator");
+    let menuseparator = document.createXULElement("menuseparator");
     menuPopup.appendChild(menuseparator);
 
     // Create a menu item for the OS default application, if any.
     if (handlerInfo.hasDefaultHandler) {
-      var defaultMenuItem = document.createElement("menuitem");
+      var defaultMenuItem = document.createXULElement("menuitem");
       defaultMenuItem.setAttribute("action", Ci.nsIHandlerInfo.useSystemDefault);
       let label = gMainPane._prefsBundle.getFormattedString("useDefault",
         [handlerInfo.defaultDescription]);
       defaultMenuItem.setAttribute("label", label);
       defaultMenuItem.setAttribute("tooltiptext", handlerInfo.defaultDescription);
       defaultMenuItem.setAttribute("image", handlerInfo.iconURLForSystemDefault);
 
       menuPopup.appendChild(defaultMenuItem);
@@ -1658,17 +1658,17 @@ var gMainPane = {
 
     // Create menu items for possible handlers.
     let preferredApp = handlerInfo.preferredApplicationHandler;
     var possibleAppMenuItems = [];
     for (let possibleApp of handlerInfo.possibleApplicationHandlers.enumerate()) {
       if (!this.isValidHandlerApp(possibleApp))
         continue;
 
-      let menuItem = document.createElement("menuitem");
+      let menuItem = document.createXULElement("menuitem");
       menuItem.setAttribute("action", Ci.nsIHandlerInfo.useHelperApp);
       let label;
       if (possibleApp instanceof Ci.nsILocalHandlerApp)
         label = getFileDisplayName(possibleApp.executable);
       else
         label = possibleApp.name;
       label = gMainPane._prefsBundle.getFormattedString("useApp", [label]);
       menuItem.setAttribute("label", label);
@@ -1699,17 +1699,17 @@ var gMainPane = {
           let app = possibleHandlers.queryElementAt(i, Ci.nsIHandlerApp);
           // nsGIOMimeApp::Equals is able to compare with nsILocalHandlerApp
           if (handler.equals(app)) {
             appAlreadyInHandlers = true;
             break;
           }
         }
         if (!appAlreadyInHandlers) {
-          let menuItem = document.createElement("menuitem");
+          let menuItem = document.createXULElement("menuitem");
           menuItem.setAttribute("action", Ci.nsIHandlerInfo.useHelperApp);
           let label = gMainPane._prefsBundle.getFormattedString("useApp", [handler.name]);
           menuItem.setAttribute("label", label);
           menuItem.setAttribute("tooltiptext", label);
           menuItem.setAttribute("image", this._getIconURLForHandlerApp(handler));
 
           // Attach the handler app object to the menu item so we can use it
           // to make changes to the datastore when the user selects the item.
@@ -1718,17 +1718,17 @@ var gMainPane = {
           menuPopup.appendChild(menuItem);
           possibleAppMenuItems.push(menuItem);
         }
       }
     }
 
     // Create a menu item for the plugin.
     if (handlerInfo.pluginName) {
-      var pluginMenuItem = document.createElement("menuitem");
+      var pluginMenuItem = document.createXULElement("menuitem");
       pluginMenuItem.setAttribute("action", kActionUsePlugin);
       let label = gMainPane._prefsBundle.getFormattedString("usePluginIn",
         [handlerInfo.pluginName,
         this._brandShortName]);
       pluginMenuItem.setAttribute("label", label);
       pluginMenuItem.setAttribute("tooltiptext", label);
       pluginMenuItem.setAttribute(APP_ICON_ATTR_NAME, "plugin");
       menuPopup.appendChild(pluginMenuItem);
@@ -1739,32 +1739,32 @@ var gMainPane = {
     if (AppConstants.platform == "win") {
       // On Windows, selecting an application to open another application
       // would be meaningless so we special case executables.
       let executableType = Cc["@mozilla.org/mime;1"].getService(Ci.nsIMIMEService)
         .getTypeFromExtension("exe");
       canOpenWithOtherApp = handlerInfo.type != executableType;
     }
     if (canOpenWithOtherApp) {
-      let menuItem = document.createElement("menuitem");
+      let menuItem = document.createXULElement("menuitem");
       menuItem.className = "choose-app-item";
       menuItem.addEventListener("command", function(e) {
         gMainPane.chooseApp(e);
       });
       let label = gMainPane._prefsBundle.getString("useOtherApp");
       menuItem.setAttribute("label", label);
       menuItem.setAttribute("tooltiptext", label);
       menuPopup.appendChild(menuItem);
     }
 
     // Create a menu item for managing applications.
     if (possibleAppMenuItems.length) {
-      let menuItem = document.createElement("menuseparator");
+      let menuItem = document.createXULElement("menuseparator");
       menuPopup.appendChild(menuItem);
-      menuItem = document.createElement("menuitem");
+      menuItem = document.createXULElement("menuitem");
       menuItem.className = "manage-app-item";
       menuItem.addEventListener("command", function(e) {
         gMainPane.manageApp(e);
       });
       menuItem.setAttribute("label", gMainPane._prefsBundle.getString("manageApp"));
       menuPopup.appendChild(menuItem);
     }
 
--- a/browser/components/preferences/in-content/main.xul
+++ b/browser/components/preferences/in-content/main.xul
@@ -647,22 +647,22 @@
             data-l10n-id="browsing-search-on-start-typing"
             preference="accessibility.typeaheadfind"/>
 </groupbox>
 
 <hbox id="networkProxyCategory"
       class="subcategory"
       hidden="true"
       data-category="paneGeneral">
-  <label class="header-name" flex="1" data-l10n-id="network-proxy-title"/>
+  <label class="header-name" flex="1" data-l10n-id="network-settings-title"/>
 </hbox>
 
-<!-- Network Proxy-->
+<!-- Network Settings-->
 <groupbox id="connectionGroup" data-category="paneGeneral" hidden="true">
-  <caption class="search-header" hidden="true"><label data-l10n-id="network-proxy-title"/></caption>
+  <caption class="search-header" hidden="true"><label data-l10n-id="network-settings-title"/></caption>
 
   <hbox align="center">
     <hbox align="center" flex="1">
       <description id="connectionSettingsDescription" control="connectionSettings"/>
       <spacer width="5"/>
       <label id="connectionSettingsLearnMore" class="learnMore text-link"
         data-l10n-id="network-proxy-connection-learn-more">
       </label>
@@ -689,13 +689,15 @@
                 connection-proxy-socks4,
                 connection-proxy-socks5,
                 connection-proxy-noproxy,
                 connection-proxy-noproxy-desc,
                 connection-proxy-http-share.label,
                 connection-proxy-autotype.label,
                 connection-proxy-reload.label,
                 connection-proxy-autologin.label,
-                connection-proxy-socks-remote-dns.label
+                connection-proxy-socks-remote-dns.label,
+                connection-dns-over-https,
+                connection-dns-over-https-url
             " />
     </hbox>
   </hbox>
 </groupbox>
--- a/browser/components/preferences/in-content/tests/browser.ini
+++ b/browser/components/preferences/in-content/tests/browser.ini
@@ -37,16 +37,17 @@ support-files =
   browser_bug1184989_prevent_scrolling_when_preferences_flipped.xul
 [browser_change_app_handler.js]
 skip-if = os != "win" # Windows-specific handler application selection dialog
 [browser_checkspelling.js]
 [browser_connection.js]
 [browser_connection_bug388287.js]
 [browser_connection_bug1445991.js]
 skip-if = (verify && debug && (os == 'linux' || os == 'mac'))
+[browser_connection_dnsoverhttps.js]
 [browser_contentblocking.js]
 [browser_cookies_exceptions.js]
 [browser_defaultbrowser_alwayscheck.js]
 [browser_healthreport.js]
 skip-if = true || !healthreport # Bug 1185403 for the "true"
 [browser_homepages_filter_aboutpreferences.js]
 [browser_homepages_use_bookmark.js]
 [browser_extension_controlled.js]
new file mode 100644
--- /dev/null
+++ b/browser/components/preferences/in-content/tests/browser_connection_dnsoverhttps.js
@@ -0,0 +1,172 @@
+ChromeUtils.import("resource://gre/modules/Services.jsm");
+
+const SUBDIALOG_URL = "chrome://browser/content/preferences/connection.xul";
+const TRR_MODE_PREF = "network.trr.mode";
+const TRR_URI_PREF = "network.trr.uri";
+
+const modeCheckboxSelector = "#networkDnsOverHttps";
+const uriTextboxSelector = "#networkDnsOverHttpsUrl";
+const defaultPrefValues = Object.freeze({
+  [TRR_MODE_PREF]: 0,
+  [TRR_URI_PREF]: "",
+});
+
+function resetPrefs() {
+  Services.prefs.clearUserPref(TRR_MODE_PREF);
+  Services.prefs.clearUserPref(TRR_URI_PREF);
+}
+
+async function setup() {
+  await new Promise(res => {
+    resetPrefs();
+    open_preferences(res);
+  });
+}
+
+async function openConnectionsSubDialog() {
+  /*
+    The connection dialog has type="child", So it has to be opened as a sub dialog
+    of the main pref tab. Prefs only get updated after the subdialog is confirmed & closed
+  */
+  let dialog = await openAndLoadSubDialog(SUBDIALOG_URL);
+  ok(dialog, "connection window opened");
+  return dialog;
+}
+
+function waitForPrefObserver(name) {
+  return new Promise(resolve => {
+    const observer = {
+      observe(aSubject, aTopic, aData) {
+        if (aData == name) {
+          Services.prefs.removeObserver(name, observer);
+          resolve();
+        }
+      }
+    };
+    Services.prefs.addObserver(name, observer);
+  });
+}
+
+async function testWithProperties(props) {
+  info("testing with " + JSON.stringify(props));
+  if (props.hasOwnProperty(TRR_MODE_PREF)) {
+    Services.prefs.setIntPref(TRR_MODE_PREF, props[TRR_MODE_PREF]);
+  }
+  if (props.hasOwnProperty(TRR_URI_PREF)) {
+    Services.prefs.setStringPref(TRR_URI_PREF, props[TRR_URI_PREF]);
+  }
+
+  let dialog = await openConnectionsSubDialog();
+  let doc = dialog.document;
+  let dialogClosingPromise = BrowserTestUtils.waitForEvent(doc.documentElement,
+                                                           "dialogclosing");
+  let modeCheckbox = doc.querySelector(modeCheckboxSelector);
+  let uriTextbox = doc.querySelector(uriTextboxSelector);
+  let uriPrefChangedPromise;
+  let modePrefChangedPromise;
+
+  if (props.hasOwnProperty("expectedModeChecked")) {
+    is(modeCheckbox.checked, props.expectedModeChecked, "mode checkbox has expected checked state");
+  }
+  if (props.hasOwnProperty("expectedUriValue")) {
+    is(uriTextbox.value, props.expectedUriValue, "URI textbox has expected value");
+  }
+  if (props.clickMode) {
+    modePrefChangedPromise = waitForPrefObserver(TRR_MODE_PREF);
+    modeCheckbox.scrollIntoView();
+    EventUtils.synthesizeMouseAtCenter(modeCheckbox, {},
+                                       modeCheckbox.ownerGlobal);
+  }
+  if (props.hasOwnProperty("inputUriKeys")) {
+    uriPrefChangedPromise = waitForPrefObserver(TRR_URI_PREF);
+    uriTextbox.focus();
+    // delete whatever is in there
+    EventUtils.synthesizeKey("a", { accelKey: true }, uriTextbox.ownerGlobal);
+    EventUtils.synthesizeKey("KEY_Backspace", {}, uriTextbox.ownerGlobal);
+    // and type in the new stuff
+    EventUtils.synthesizeKey(props.inputUriKeys, {}, uriTextbox.ownerGlobal);
+    let changePromise = waitForEvent(uriTextbox, "change");
+    // move focus to trigger change event
+    modeCheckbox.focus();
+    await changePromise;
+  }
+
+  doc.documentElement.acceptDialog();
+
+  let dialogClosingEvent = await dialogClosingPromise;
+  ok(dialogClosingEvent, "connection window closed");
+
+  await Promise.all([uriPrefChangedPromise, modePrefChangedPromise]);
+
+  if (props.hasOwnProperty("expectedFinalUriPref")) {
+    let uriPref = Services.prefs.getStringPref(TRR_URI_PREF);
+    is(uriPref, props.expectedFinalUriPref, "uri pref ended up with the expected value");
+  }
+
+  if (props.hasOwnProperty("expectedModePref")) {
+    let modePref = Services.prefs.getIntPref(TRR_MODE_PREF);
+    is(modePref, props.expectedModePref, "mode pref ended up with the expected value");
+  }
+}
+
+registerCleanupFunction(resetPrefs);
+
+add_task(async function default_values() {
+  let uriPref = Services.prefs.getStringPref(TRR_URI_PREF);
+  let modePref = Services.prefs.getIntPref(TRR_MODE_PREF);
+  is(modePref, defaultPrefValues[TRR_MODE_PREF],
+     `Actual value of ${TRR_MODE_PREF} matches expected default value`);
+  is(uriPref, defaultPrefValues[TRR_URI_PREF],
+     `Actual value of ${TRR_MODE_PREF} matches expected default value`);
+});
+
+let testVariations = [
+  // verify state with defaults
+  { expectedModePref: 0, expectedUriValue: "" },
+
+  // verify each of the modes maps to the correct checked state
+  { [TRR_MODE_PREF]: 0, expectedModeChecked: false },
+  { [TRR_MODE_PREF]: 1, expectedModeChecked: true },
+  { [TRR_MODE_PREF]: 2, expectedModeChecked: true },
+  { [TRR_MODE_PREF]: 3, expectedModeChecked: true },
+  { [TRR_MODE_PREF]: 4, expectedModeChecked: true },
+  { [TRR_MODE_PREF]: 5, expectedModeChecked: false },
+  // verify an out of bounds mode value maps to the correct checked state
+  { [TRR_MODE_PREF]: 77, expectedModeChecked: false },
+
+  // verify toggling the checkbox gives the right outcomes
+  { clickMode: true, expectedModeValue: 2, expectedUriValue: "" },
+  {
+    [TRR_MODE_PREF]: 4,
+    expectedModeChecked: true, clickMode: true, expectedModePref: 0,
+  },
+  {
+    [TRR_MODE_PREF]: 2, [TRR_URI_PREF]: "https://example.com",
+    expectedModeValue: true, expectedUriValue: "https://example.com",
+  },
+  {
+    clickMode: true, inputUriKeys: "https://example.com",
+    expectedModePref: 2, expectedFinalUriPref: "https://example.com",
+  },
+
+  // verify the uri can be cleared
+  {
+    [TRR_MODE_PREF]: 2, [TRR_URI_PREF]: "https://example.com",
+    expectedUriValue: "https://example.com", inputUriKeys: "", expectedFinalUriPref: "",
+  },
+
+  // verify uri gets sanitized
+  {
+    clickMode: true, inputUriKeys: "  https://example.com ",
+    expectedModePref: 2, expectedFinalUriPref: "https://example.com",
+  },
+];
+
+for (let props of testVariations) {
+  add_task(async function() {
+    await setup();
+    await testWithProperties(props);
+    resetPrefs();
+    gBrowser.removeCurrentTab();
+  });
+}
--- a/browser/components/preferences/languages.js
+++ b/browser/components/preferences/languages.js
@@ -87,17 +87,17 @@ var gLanguagesDialog = {
     let frag = document.createDocumentFragment();
 
     // Load the UI with the data
     for (var i = 0; i < this._availableLanguagesList.length; ++i) {
       let locale = this._availableLanguagesList[i];
       let localeCode = locale.code;
       if (locale.isVisible &&
           (!(localeCode in this._acceptLanguages) || !this._acceptLanguages[localeCode])) {
-        var menuitem = document.createElement("menuitem");
+        var menuitem = document.createXULElement("menuitem");
         menuitem.id = localeCode;
         document.l10n.setAttributes(menuitem, "languages-code-format", {
           locale: locale.name,
           code: localeCode,
         });
         frag.appendChild(menuitem);
       }
     }
@@ -128,18 +128,18 @@ var gLanguagesDialog = {
       this._activeLanguages.firstChild.remove();
 
     var selectedIndex = 0;
     var preference = Preferences.get("intl.accept_languages");
     if (preference.value == "")
       return;
     var languages = preference.value.toLowerCase().split(/\s*,\s*/);
     for (var i = 0; i < languages.length; ++i) {
-      var listitem = document.createElement("richlistitem");
-      var label = document.createElement("label");
+      var listitem = document.createXULElement("richlistitem");
+      var label = document.createXULElement("label");
       listitem.appendChild(label);
       listitem.id = languages[i];
       if (languages[i] == this._selectedItemID)
         selectedIndex = i;
       this._activeLanguages.appendChild(listitem);
       var localeName = this._getLocaleName(languages[i]);
       document.l10n.setAttributes(label, "languages-active-code-format", {
         locale: localeName,
--- a/browser/components/preferences/permissions.js
+++ b/browser/components/preferences/permissions.js
@@ -261,33 +261,33 @@ var gPermissionManager = {
   _loadPermissions() {
     // load permissions into a table.
     for (let nextPermission of Services.perms.enumerator) {
       this._addPermissionToList(nextPermission);
     }
   },
 
   _createPermissionListItem(permission) {
-    let richlistitem = document.createElement("richlistitem");
+    let richlistitem = document.createXULElement("richlistitem");
     richlistitem.setAttribute("origin", permission.origin);
-    let row = document.createElement("hbox");
+    let row = document.createXULElement("hbox");
     row.setAttribute("flex", "1");
 
-    let hbox = document.createElement("hbox");
-    let website = document.createElement("label");
+    let hbox = document.createXULElement("hbox");
+    let website = document.createXULElement("label");
     website.setAttribute("value", permission.origin);
     hbox.setAttribute("width", "0");
     hbox.setAttribute("class", "website-name");
     hbox.setAttribute("flex", "3");
     hbox.appendChild(website);
     row.appendChild(hbox);
 
     if (!this._hideStatusColumn) {
-      hbox = document.createElement("hbox");
-      let capability = document.createElement("label");
+      hbox = document.createXULElement("hbox");
+      let capability = document.createXULElement("label");
       capability.setAttribute("class", "website-capability-value");
       capability.setAttribute("value", permission.capability);
       hbox.setAttribute("width", "0");
       hbox.setAttribute("class", "website-name");
       hbox.setAttribute("flex", "1");
       hbox.appendChild(capability);
       row.appendChild(hbox);
     }
--- a/browser/components/preferences/siteDataRemoveSelected.js
+++ b/browser/components/preferences/siteDataRemoveSelected.js
@@ -8,18 +8,18 @@
 let gSiteDataRemoveSelected = {
 
   init() {
     let hosts = window.arguments[0].hosts;
     hosts.sort();
     let list = document.getElementById("removalList");
     let fragment = document.createDocumentFragment();
     for (let host of hosts) {
-      let listItem = document.createElement("richlistitem");
-      let label = document.createElement("label");
+      let listItem = document.createXULElement("richlistitem");
+      let label = document.createXULElement("label");
       label.setAttribute("value", host);
       listItem.appendChild(label);
       fragment.appendChild(listItem);
     }
     list.appendChild(fragment);
   },
 
   ondialogaccept() {
--- a/browser/components/preferences/siteDataSettings.js
+++ b/browser/components/preferences/siteDataSettings.js
@@ -21,26 +21,26 @@ let gSiteDataSettings = {
   // - usage: disk usage which site uses
   // - userAction: "remove" or "update-permission"; the action user wants to take.
   _sites: null,
 
   _list: null,
   _searchBox: null,
 
   _createSiteListItem(site) {
-    let item = document.createElement("richlistitem");
+    let item = document.createXULElement("richlistitem");
     item.setAttribute("host", site.host);
-    let container = document.createElement("hbox");
+    let container = document.createXULElement("hbox");
 
     // Creates a new column item with the specified relative width.
     function addColumnItem(l10n, flexWidth, tooltipText) {
-      let box = document.createElement("hbox");
+      let box = document.createXULElement("hbox");
       box.className = "item-box";
       box.setAttribute("flex", flexWidth);
-      let label = document.createElement("label");
+      let label = document.createXULElement("label");
       label.setAttribute("crop", "end");
       if (l10n) {
         if (l10n.hasOwnProperty("raw")) {
           box.setAttribute("tooltiptext", l10n.raw);
           label.setAttribute("value", l10n.raw);
         } else {
           document.l10n.setAttributes(label, l10n.id, l10n.args);
         }
--- a/browser/components/preferences/sitePermissions.js
+++ b/browser/components/preferences/sitePermissions.js
@@ -193,47 +193,47 @@ var gSitePermissionsManager = {
   _loadPermissions() {
     // load permissions into a table.
     for (let nextPermission of Services.perms.enumerator) {
       this._addPermissionToList(nextPermission);
     }
   },
 
   _createPermissionListItem(permission) {
-    let richlistitem = document.createElement("richlistitem");
+    let richlistitem = document.createXULElement("richlistitem");
     richlistitem.setAttribute("origin", permission.origin);
-    let row = document.createElement("hbox");
+    let row = document.createXULElement("hbox");
     row.setAttribute("flex", "1");
 
-    let hbox = document.createElement("hbox");
-    let website = document.createElement("label");
+    let hbox = document.createXULElement("hbox");
+    let website = document.createXULElement("label");
     website.setAttribute("value", permission.origin);
     website.setAttribute("width", "50");
     hbox.setAttribute("class", "website-name");
     hbox.setAttribute("flex", "3");
     hbox.appendChild(website);
 
-    let menulist = document.createElement("menulist");
-    let menupopup = document.createElement("menupopup");
+    let menulist = document.createXULElement("menulist");
+    let menupopup = document.createXULElement("menupopup");
     menulist.setAttribute("flex", "1");
     menulist.setAttribute("width", "50");
     menulist.setAttribute("class", "website-status");
     menulist.appendChild(menupopup);
     let states = SitePermissions.getAvailableStates(permission.type);
     for (let state of states) {
       // Work around the (rare) edge case when a user has changed their
       // default permission type back to UNKNOWN while still having a
       // PROMPT permission set for an origin.
       if (state == SitePermissions.UNKNOWN &&
           permission.capability == SitePermissions.PROMPT) {
         state = SitePermissions.PROMPT;
       } else if (state == SitePermissions.UNKNOWN) {
         continue;
       }
-      let m = document.createElement("menuitem");
+      let m = document.createXULElement("menuitem");
       document.l10n.setAttributes(m, this._getCapabilityString(state));
       m.setAttribute("value", state);
       menupopup.appendChild(m);
     }
     menulist.value = permission.capability;
 
     menulist.addEventListener("select", () => {
       this.onPermissionChange(permission, Number(menulist.selectedItem.value));
--- a/browser/components/uitour/UITour.jsm
+++ b/browser/components/uitour/UITour.jsm
@@ -1135,17 +1135,17 @@ var UITour = {
       tooltipIcon.src = aIconURL || "";
       tooltipIcon.hidden = !aIconURL;
 
       while (tooltipButtons.firstChild)
         tooltipButtons.firstChild.remove();
 
       for (let button of aButtons) {
         let isButton = button.style != "text";
-        let el = document.createElement(isButton ? "button" : "label");
+        let el = document.createXULElement(isButton ? "button" : "label");
         el.setAttribute(isButton ? "label" : "value", button.label);
 
         if (isButton) {
           if (button.iconURL)
             el.setAttribute("image", button.iconURL);
 
           if (button.style == "link")
             el.setAttribute("class", "button-link");
--- a/browser/extensions/formautofill/FormAutofillDoorhanger.jsm
+++ b/browser/extensions/formautofill/FormAutofillDoorhanger.jsm
@@ -231,17 +231,17 @@ let FormAutofillDoorhanger = {
    *         popupnotificationcontent
    * @param  {string} message
    *         The localized string for link title.
    * @param  {string} link
    *         Makes it possible to open and highlight a section in preferences
    */
   _appendPrivacyPanelLink(content, message, link) {
     let chromeDoc = content.ownerDocument;
-    let privacyLinkElement = chromeDoc.createElement("label");
+    let privacyLinkElement = chromeDoc.createXULElement("label");
     privacyLinkElement.className = "text-link";
     privacyLinkElement.setAttribute("useoriginprincipal", true);
     privacyLinkElement.setAttribute("href", link || "about:preferences#privacy");
     privacyLinkElement.setAttribute("value", message);
     content.appendChild(privacyLinkElement);
   },
 
   /**
@@ -252,29 +252,29 @@ let FormAutofillDoorhanger = {
    *         The label showing above description.
    * @param  {string} descriptionIcon
    *         The src of description icon.
    */
   _appendDescription(content, descriptionLabel, descriptionIcon) {
     let chromeDoc = content.ownerDocument;
     let docFragment = chromeDoc.createDocumentFragment();
 
-    let descriptionLabelElement = chromeDoc.createElement("label");
+    let descriptionLabelElement = chromeDoc.createXULElement("label");
     descriptionLabelElement.setAttribute("value", descriptionLabel);
     docFragment.appendChild(descriptionLabelElement);
 
-    let descriptionWrapper = chromeDoc.createElement("hbox");
+    let descriptionWrapper = chromeDoc.createXULElement("hbox");
     descriptionWrapper.className = "desc-message-box";
 
     if (descriptionIcon) {
-      let descriptionIconElement = chromeDoc.createElement("image");
+      let descriptionIconElement = chromeDoc.createXULElement("image");
       descriptionWrapper.appendChild(descriptionIconElement);
     }
 
-    let descriptionElement = chromeDoc.createElement("description");
+    let descriptionElement = chromeDoc.createXULElement("description");
     descriptionWrapper.appendChild(descriptionElement);
     docFragment.appendChild(descriptionWrapper);
 
     content.appendChild(docFragment);
   },
 
   _updateDescription(content, description) {
     content.querySelector("description").textContent = description;
@@ -296,17 +296,17 @@ let FormAutofillDoorhanger = {
   _setAnchor(browser, anchor) {
     let chromeDoc = browser.ownerDocument;
     let {id, URL, tooltiptext} = anchor;
     let anchorEt = chromeDoc.getElementById(id);
     if (!anchorEt) {
       let notificationPopupBox =
         chromeDoc.getElementById("notification-popup-box");
       // Icon shown on URL bar
-      let anchorElement = chromeDoc.createElement("image");
+      let anchorElement = chromeDoc.createXULElement("image");
       anchorElement.id = id;
       anchorElement.setAttribute("src", URL);
       anchorElement.classList.add("notification-anchor-icon");
       anchorElement.setAttribute("role", "button");
       anchorElement.setAttribute("tooltiptext", tooltiptext);
       notificationPopupBox.appendChild(anchorElement);
     }
   },
@@ -378,17 +378,17 @@ let FormAutofillDoorhanger = {
         // There's no preferences link or other customization in first time use doorhanger.
         if (type == "firstTimeUse") {
           return;
         }
 
         const notificationElementId = notificationId + "-notification";
         const notification = chromeDoc.getElementById(notificationElementId);
         const notificationContent = notification.querySelector("popupnotificationcontent") ||
-                                    chromeDoc.createElement("popupnotificationcontent");
+                                    chromeDoc.createXULElement("popupnotificationcontent");
         if (!notification.contains(notificationContent)) {
           notificationContent.setAttribute("orient", "vertical");
           this._appendDescription(notificationContent, descriptionLabel, descriptionIcon);
           this._appendPrivacyPanelLink(notificationContent, linkMessage, spotlightURL);
           notification.append(notificationContent);
         }
         this._updateDescription(notificationContent, description);
       };
--- a/browser/extensions/formautofill/content/editAddress.xhtml
+++ b/browser/extensions/formautofill/content/editAddress.xhtml
@@ -13,67 +13,71 @@
   <link rel="stylesheet" href="resource://formautofill/editAddress.css"/>
   <link rel="stylesheet" href="resource://formautofill/editDialog.css"/>
   <script src="chrome://formautofill/content/l10n.js"></script>
   <script src="chrome://formautofill/content/editDialog.js"></script>
   <script src="chrome://formautofill/content/autofillEditForms.js"></script>
 </head>
 <body dir="&locale.dir;">
   <form id="form" class="editAddressForm" autocomplete="off">
+    <!--
+        The <span class="label-text" …/> needs to be after the form field in the same element in
+        order to get proper label styling with :focus and :moz-ui-invalid.
+      -->
     <div>
       <div id="name-container">
         <label id="given-name-container">
+          <input id="given-name" type="text" required="required"/>
           <span data-localization="givenName" class="label-text"/>
-          <input id="given-name" type="text" required="required"/>
         </label>
         <label id="additional-name-container">
+          <input id="additional-name" type="text"/>
           <span data-localization="additionalName" class="label-text"/>
-          <input id="additional-name" type="text"/>
         </label>
         <label id="family-name-container">
+          <input id="family-name" type="text" required="required"/>
           <span data-localization="familyName" class="label-text"/>
-          <input id="family-name" type="text"/>
         </label>
       </div>
       <label id="organization-container">
+        <input id="organization" type="text"/>
         <span data-localization="organization2" class="label-text"/>
-        <input id="organization" type="text"/>
       </label>
       <label id="street-address-container">
+        <textarea id="street-address" rows="3" required="required"/>
         <span data-localization="streetAddress" class="label-text"/>
-        <textarea id="street-address" rows="3" required="required"/>
       </label>
       <label id="address-level2-container">
+        <input id="address-level2" type="text" required="required"/>
         <span data-localization="city" class="label-text"/>
-        <input id="address-level2" type="text" required="required"/>
       </label>
       <label id="address-level1-container">
+        <input id="address-level1" type="text" required="required"/>
         <span class="label-text"/>
-        <input id="address-level1" type="text" required="required"/>
       </label>
       <label id="postal-code-container">
+        <input id="postal-code" type="text" required="required"/>
         <span class="label-text"/>
-        <input id="postal-code" type="text" required="required"/>
       </label>
       <div id="country-container">
         <label id="country-label">
-          <span data-localization="country" class="label-text"/>
           <select id="country" required="required">
             <option/>
           </select>
+          <span data-localization="country" class="label-text"/>
         </label>
         <p id="country-warning-message" data-localization="countryWarningMessage2"/>
       </div>
       <label id="tel-container">
+        <input id="tel" type="tel"/>
         <span data-localization="tel" class="label-text"/>
-        <input id="tel" type="tel"/>
       </label>
       <label id="email-container">
+        <input id="email" type="email" required="required"/>
         <span data-localization="email" class="label-text"/>
-        <input id="email" type="email" required="required"/>
       </label>
     </div>
   </form>
   <div id="controls-container">
     <button id="cancel" data-localization="cancelBtnLabel"/>
     <button id="save" disabled="disabled" data-localization="saveBtnLabel"/>
   </div>
   <script type="application/javascript"><![CDATA[
--- a/browser/extensions/formautofill/content/editCreditCard.xhtml
+++ b/browser/extensions/formautofill/content/editCreditCard.xhtml
@@ -13,53 +13,57 @@
   <link rel="stylesheet" href="resource://formautofill/editCreditCard.css"/>
   <link rel="stylesheet" href="resource://formautofill/editDialog.css"/>
   <script src="chrome://formautofill/content/l10n.js"></script>
   <script src="chrome://formautofill/content/editDialog.js"></script>
   <script src="chrome://formautofill/content/autofillEditForms.js"></script>
 </head>
 <body dir="&locale.dir;">
   <form id="form" class="editCreditCardForm" autocomplete="off">
+    <!--
+        The <span class="label-text" …/> needs to be after the form field in the same element in
+        order to get proper label styling with :focus and :moz-ui-invalid.
+      -->
     <label>
-      <span data-localization="cardNumber" class="label-text"/>
       <span id="invalidCardNumberString" hidden="hidden" data-localization="invalidCardNumber"></span>
       <input id="cc-number" type="text" required="required" minlength="9" pattern="[- 0-9]+"/>
+      <span data-localization="cardNumber" class="label-text"/>
     </label>
     <label>
+      <input id="cc-name" type="text" required="required"/>
       <span data-localization="nameOnCard" class="label-text"/>
-      <input id="cc-name" type="text" required="required"/>
     </label>
     <label>
-      <span data-localization="cardExpiresMonth" class="label-text"/>
       <select id="cc-exp-month">
         <option/>
         <option value="1">01</option>
         <option value="2">02</option>
         <option value="3">03</option>
         <option value="4">04</option>
         <option value="5">05</option>
         <option value="6">06</option>
         <option value="7">07</option>
         <option value="8">08</option>
         <option value="9">09</option>
         <option value="10">10</option>
         <option value="11">11</option>
         <option value="12">12</option>
       </select>
+      <span data-localization="cardExpiresMonth" class="label-text"/>
     </label>
     <label>
-      <span data-localization="cardExpiresYear" class="label-text"/>
       <select id="cc-exp-year">
         <option/>
       </select>
+      <span data-localization="cardExpiresYear" class="label-text"/>
     </label>
     <label class="billingAddressRow">
-      <span data-localization="billingAddress" class="label-text"/>
       <select id="billingAddressGUID">
       </select>
+      <span data-localization="billingAddress" class="label-text"/>
     </label>
   </form>
   <div id="controls-container">
     <button id="cancel" data-localization="cancelBtnLabel"/>
     <button id="save" disabled="disabled" data-localization="saveBtnLabel"/>
   </div>
   <script type="application/javascript"><![CDATA[
     "use strict";
--- a/browser/extensions/formautofill/skin/shared/editAddress.css
+++ b/browser/extensions/formautofill/skin/shared/editAddress.css
@@ -3,38 +3,104 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 .editAddressForm input,
 .editAddressForm select {
   flex: 1 0 auto;
   margin: 0;
 }
 
+#name-container,
+:root[subdialog] form label,
+:root[subdialog] form > p {
+  margin: 0 0 0.5em !important;
+}
+
 #given-name-container,
 #additional-name-container,
 #address-level1-container,
 #postal-code-container,
 #country-label,
 #country-warning-message,
 #family-name-container,
 #organization-container,
 #address-level2-container,
 #tel-container {
   display: flex;
   flex: 0 1 50%;
 }
 
+
+/* Begin name field rules */
+
+#name-container input {
+  /* Override the default @size="20" on <input>, which acts like a min-width, not
+   * allowing the fields to shrink with flexbox as small as they need to to match
+   * the other rows. This is noticeable on narrow viewports e.g. in the
+   * PaymentRequest dialog on Linux due to the larger font-size. */
+  width: 0;
+}
+
+/* When there is focus within any of the name fields, the border of the inputs
+ * should be the focused color, except for inner ones which get overriden below. */
+#name-container:focus-within input {
+  border-color: var(--in-content-border-focus);
+}
+
+/* Invalid name fields should show the error outline instead of the focus border */
+#name-container:focus-within input:-moz-ui-invalid {
+  border-color: transparent;
+}
+
 #given-name-container,
 #additional-name-container,
 #family-name-container {
   display: flex;
+  /* Remove the bottom margin from the name containers so that the outer
+     #name-container provides the margin on the outside */
+  margin-bottom: 0 !important;
   margin-left: 0;
   margin-right: 0;
 }
 
+/* The name fields are placed adjacent to each other.
+   Remove the border-radius on adjacent fields. */
+#given-name:dir(ltr),
+#family-name:dir(rtl) {
+  border-top-right-radius: 0;
+  border-bottom-right-radius: 0;
+  border-right-width: 0;
+}
+
+#given-name:dir(rtl),
+#family-name:dir(ltr) {
+  border-top-left-radius: 0;
+  border-bottom-left-radius: 0;
+  border-left-width: 0;
+}
+
+#additional-name {
+  border-radius: 0;
+  /* This provides the inner separators between the fields and should never
+   * change to the focused color. */
+  border-left-color: var(--in-content-box-border-color) !important;
+  border-right-color: var(--in-content-box-border-color) !important;
+}
+
+/* Since the name fields are adjacent, there isn't room for the -moz-ui-invalid
+   box-shadow so raise invalid name fields and their labels above the siblings
+   so the shadow is shown around all 4 sides. */
+#name-container input:-moz-ui-invalid,
+#name-container input:-moz-ui-invalid ~ .label-text {
+  z-index: 1;
+}
+
+/* End name field rules */
+
+
 #name-container,
 #street-address-container,
 #country-container,
 #email-container {
   flex: 0 1 100%;
 }
 
 #street-address,
--- a/browser/extensions/formautofill/skin/shared/editDialog-shared.css
+++ b/browser/extensions/formautofill/skin/shared/editDialog-shared.css
@@ -8,21 +8,16 @@
   --animation-easing-function: cubic-bezier(.07,.95,0,1);
 }
 
 :root[subdialog] form {
   /* Add extra space to ensure invalid input box is displayed properly */
   padding: 2px;
 }
 
-:root[subdialog] form label,
-:root[subdialog] form > p {
-  margin: 0 0 0.5em !important;
-}
-
 form input[type="email"],
 form input[type="tel"],
 form input[type="text"],
 form textarea,
 form select {
   padding-top: calc(var(--in-field-label-size) + .4em);
 }
 
@@ -50,20 +45,26 @@ form :-moz-any(label, div) > .label-text
 }
 
 form :-moz-any(label, div):focus-within > .label-text,
 form :-moz-any(label, div) > .label-text[field-populated] {
   top: 0;
   font-size: var(--in-field-label-size);
 }
 
-form :-moz-any(label, div):focus-within > .label-text {
+form :-moz-any(input, select, textarea):focus ~ .label-text {
   color: var(--in-content-item-selected);
 }
 
+/* Focused error fields should get a darker text but not the blue one since it
+ * doesn't look good with the red error outline. */
+form :-moz-any(input, select, textarea):focus:-moz-ui-invalid ~ .label-text {
+  color: var(--in-content-text-color);
+}
+
 form div[required] > label > .label-text::after,
 form :-moz-any(label, div)[required] > .label-text::after {
   content: attr(fieldRequiredSymbol);
 }
 
 #controls-container {
   flex: 0 1 100%;
   justify-content: end;
--- a/browser/extensions/formautofill/test/browser/browser_editAddressDialog.js
+++ b/browser/extensions/formautofill/test/browser/browser_editAddressDialog.js
@@ -203,8 +203,94 @@ add_task(async function test_saveAddress
     keyInputs.forEach(input => EventUtils.synthesizeKey(input, {}, win));
   });
   let addresses = await getAddresses();
   for (let [fieldName, fieldValue] of Object.entries(TEST_ADDRESS_DE_1)) {
     is(addresses[0][fieldName], fieldValue, "check " + fieldName);
   }
   await removeAllRecords();
 });
+
+add_task(async function test_combined_name_fields() {
+  await testDialog(EDIT_ADDRESS_DIALOG_URL, async win => {
+    let doc = win.document;
+    let givenNameField = doc.querySelector("#given-name");
+    let addtlNameField = doc.querySelector("#additional-name");
+    let familyNameField = doc.querySelector("#family-name");
+
+    function getComputedPropertyValue(field, property) {
+      return win.getComputedStyle(field).getPropertyValue(property);
+    }
+    function checkNameComputedPropertiesMatch(field, property, value, checkFn = is) {
+      checkFn(getComputedPropertyValue(field, property), value,
+              `Check ${field.id}'s ${property} is ${value}`);
+    }
+    function checkNameFieldBorders(borderColorUnfocused, borderColorFocused) {
+      info("checking the perimeter colors");
+      checkNameComputedPropertiesMatch(givenNameField, "border-top-color", borderColorFocused);
+      checkNameComputedPropertiesMatch(addtlNameField, "border-top-color", borderColorFocused);
+      checkNameComputedPropertiesMatch(familyNameField, "border-top-color", borderColorFocused);
+      checkNameComputedPropertiesMatch(familyNameField, "border-right-color", borderColorFocused);
+      checkNameComputedPropertiesMatch(givenNameField, "border-bottom-color", borderColorFocused);
+      checkNameComputedPropertiesMatch(addtlNameField, "border-bottom-color", borderColorFocused);
+      checkNameComputedPropertiesMatch(familyNameField, "border-bottom-color", borderColorFocused);
+      checkNameComputedPropertiesMatch(givenNameField, "border-left-color", borderColorFocused);
+
+      info("checking the internal borders");
+      checkNameComputedPropertiesMatch(givenNameField, "border-right-width", "0px");
+      checkNameComputedPropertiesMatch(addtlNameField, "border-left-width", "2px");
+      checkNameComputedPropertiesMatch(addtlNameField, "border-left-color", borderColorFocused, isnot);
+      checkNameComputedPropertiesMatch(addtlNameField, "border-right-width", "2px");
+      checkNameComputedPropertiesMatch(addtlNameField, "border-right-color", borderColorFocused, isnot);
+      checkNameComputedPropertiesMatch(familyNameField, "border-left-width", "0px");
+    }
+
+    // Set these variables since the test doesn't run from a subdialog and
+    // therefore doesn't get the additional common CSS files injected.
+    let borderColor = "rgb(0, 255, 0)";
+    let borderColorFocused = "rgb(0, 0, 255)";
+    doc.body.style.setProperty("--in-content-box-border-color", borderColor);
+    doc.body.style.setProperty("--in-content-border-focus", borderColorFocused);
+
+    givenNameField.focus();
+    checkNameFieldBorders(borderColor, borderColorFocused);
+
+    addtlNameField.focus();
+    checkNameFieldBorders(borderColor, borderColorFocused);
+
+    familyNameField.focus();
+    checkNameFieldBorders(borderColor, borderColorFocused);
+
+    info("unfocusing the name fields");
+    let cancelButton = doc.querySelector("#cancel");
+    cancelButton.focus();
+    borderColor = getComputedPropertyValue(givenNameField, "border-top-color");
+    isnot(borderColor, borderColorFocused, "Check that the border color is different");
+    checkNameFieldBorders(borderColor, borderColor);
+
+    cancelButton.click();
+  });
+});
+
+add_task(async function test_combined_name_fields_error() {
+  await testDialog(EDIT_ADDRESS_DIALOG_URL, async win => {
+    let doc = win.document;
+    let givenNameField = doc.querySelector("#given-name");
+    info("mark the given name field as invalid");
+    givenNameField.value = "";
+    givenNameField.focus();
+    ok(givenNameField.matches(":-moz-ui-invalid"), "Check field is visually invalid");
+
+    let givenNameLabel = doc.querySelector("#given-name-container .label-text");
+    // Override pointer-events so that we can use elementFromPoint to know if
+    // the label text is visible.
+    givenNameLabel.style.pointerEvents = "auto";
+    let givenNameLabelRect = givenNameLabel.getBoundingClientRect();
+    // Get the center of the label
+    let el = doc.elementFromPoint(givenNameLabelRect.left + givenNameLabelRect.width / 2,
+                                  givenNameLabelRect.top + givenNameLabelRect.height / 2);
+
+    is(el, givenNameLabel, "Check that the label text is visible in the error state");
+    is(win.getComputedStyle(givenNameField).getPropertyValue("border-top-color"),
+       "rgba(0, 0, 0, 0)", "Border should be transparent so that only the error outline shows");
+    doc.querySelector("#cancel").click();
+  });
+});
--- a/browser/extensions/pocket/content/main.js
+++ b/browser/extensions/pocket/content/main.js
@@ -145,17 +145,17 @@ var pktUI = (function() {
     /**
      * Show the sign-up panel
      */
     function showSignUp() {
         // AB test: Direct logged-out users to tab vs panel
         if (pktApi.getSignupPanelTabTestVariant() == "v2") {
             let site = Services.prefs.getCharPref("extensions.pocket.site");
             openTabWithUrl("https://" + site + "/firefox_learnmore?s=ffi&t=autoredirect&tv=page_learnmore&src=ff_ext",
-                           Services.scriptSecurityManager.getSystemPrincipal());
+                           Services.scriptSecurityManager.createNullPrincipal({}));
 
             // force the panel closed before it opens
             getPanel().hidePopup();
 
             return;
         }
 
         // Control: Show panel as normal
--- a/browser/locales/en-US/browser/preferences/connection.ftl
+++ b/browser/locales/en-US/browser/preferences/connection.ftl
@@ -76,8 +76,16 @@ connection-proxy-reload =
 connection-proxy-autologin =
     .label = Do not prompt for authentication if password is saved
     .accesskey = i
     .tooltip = This option silently authenticates you to proxies when you have saved credentials for them. You will be prompted if authentication fails.
 
 connection-proxy-socks-remote-dns =
     .label = Proxy DNS when using SOCKS v5
     .accesskey = D
+
+connection-dns-over-https =
+    .label = Enable DNS over HTTPS
+    .accesskey = H
+
+connection-dns-over-https-url = URL
+    .accesskey = U
+    .tooltiptext = URL for resolving DNS over HTTPS
--- a/browser/locales/en-US/browser/preferences/preferences.ftl
+++ b/browser/locales/en-US/browser/preferences/preferences.ftl
@@ -400,17 +400,17 @@ browsing-use-cursor-navigation =
     .accesskey = k
 
 browsing-search-on-start-typing =
     .label = Search for text when you start typing
     .accesskey = x
 
 ## General Section - Proxy
 
-network-proxy-title = Network Proxy
+network-settings-title = Network Settings
 
 network-proxy-connection-description = Configure how { -brand-short-name } connects to the internet.
 
 network-proxy-connection-learn-more = Learn More
 
 network-proxy-connection-settings =
     .label = Settings…
     .accesskey = e
--- a/browser/locales/en-US/chrome/browser/browser.dtd
+++ b/browser/locales/en-US/chrome/browser/browser.dtd
@@ -933,17 +933,17 @@ you can use these alternative items. Oth
      "Slow-Loading Trackers [are] Blocked"-->
 <!ENTITY contentBlocking.fastBlock.blocked.label "Blocked">
 <!-- LOCALIZATION NOTE (contentBlocking.fastBlock.add.label):
      This is displayed as a link to preferences, where the user can add
      this specific type of content blocking. When this text is shown
      the type of content blocking is currently not enabled. -->
 <!ENTITY contentBlocking.fastBlock.add.label "Add Blocking…">
 
-<!ENTITY contentBlocking.trackingProtection.label "Trackers">
+<!ENTITY contentBlocking.trackingProtection2.label "All Detected Trackers">
 <!-- LOCALIZATION NOTE (contentBlocking.trackingProtection.blocked.label):
      This label signals that this type of content blocking is turned
      ON and is successfully blocking tracker content, so this is
      a positive thing. It forms the end of the (imaginary) sentence
      "Trackers [are] Blocked"-->
 <!ENTITY contentBlocking.trackingProtection.blocked.label "Blocked">
 <!-- LOCALIZATION NOTE (contentBlocking.trackingProtection.add.label):
      This is displayed as a link to preferences, where the user can add
@@ -959,22 +959,21 @@ you can use these alternative items. Oth
      "Third-Party Cookies [are] Blocked"-->
 <!ENTITY contentBlocking.3rdPartyCookies.blocked.label "Blocked">
 <!-- LOCALIZATION NOTE (contentBlocking.3rdPartyCookies.add.label):
      This is displayed as a link to preferences, where the user can add
      this specific type of content blocking. When this text is shown
      the type of content blocking is currently not enabled. -->
 <!ENTITY contentBlocking.3rdPartyCookies.add.label "Add Blocking…">
 
-<!ENTITY contentBlocking.openBreakageReportView.label "Report Problems">
+<!ENTITY contentBlocking.openBreakageReportView2.label "Report a problem">
 <!ENTITY contentBlocking.breakageReportView.label "Report Problems">
-<!ENTITY contentBlocking.breakageReportView.description "Content blocking can cause problems with some websites. When you report problems, you’ll help make &brandShortName; better for everyone. (This will send a URL as well as information about your privacy and content blocking settings to Mozilla.)">
+<!ENTITY contentBlocking.breakageReportView2.description "Content blocking can cause problems with some websites. When you report problems, you’ll help make &brandShortName; better for everyone. (This will send a URL as well as information about your browser settings to Mozilla.)">
 <!ENTITY contentBlocking.breakageReportView.learnMore "Learn More">
 <!ENTITY contentBlocking.breakageReportView.collection.url.label "URL">
-<!ENTITY contentBlocking.breakageReportView.collection.userAgent.label "&brandShortName; Version Number">
 <!ENTITY contentBlocking.breakageReportView.collection.comments.label "What problems did you have? (Optional)">
 <!ENTITY contentBlocking.breakageReportView.sendReport.label "Send Report">
 <!ENTITY contentBlocking.breakageReportView.cancel.label "Cancel">
 
 <!ENTITY trackingProtection.title "Tracking Protection">
 <!ENTITY trackingProtection.tooltip "Open Tracking Protection Preferences">
 
 <!-- LOCALIZATION NOTE (trackingProtection.unblock3.label, trackingProtection.unblock3.accesskey):
--- a/browser/locales/l10n-changesets.json
+++ b/browser/locales/l10n-changesets.json
@@ -778,16 +778,17 @@
         "revision": "9711f4ca9034"
     },
     "ja-JP-mac": {
         "platforms": [
             "macosx64",
             "macosx64-devedition"
         ],
         "revision": "7f61ec50b1c3"
+        "revision": "default"
     },
     "ka": {
         "platforms": [
             "linux",
             "linux-devedition",
             "linux64",
             "linux64-devedition",
             "macosx64",
@@ -1422,17 +1423,16 @@
             "linux64-devedition",
             "macosx64",
             "macosx64-devedition",
             "win32",
             "win32-devedition",
             "win64",
             "win64-devedition"
         ],
-        "revision": "7673a6208f62"
     },
     "xh": {
         "platforms": [
             "linux",
             "linux-devedition",
             "linux64",
             "linux64-devedition",
             "macosx64",
--- a/browser/modules/ProcessHangMonitor.jsm
+++ b/browser/modules/ProcessHangMonitor.jsm
@@ -453,17 +453,17 @@ var ProcessHangMonitor = {
       let addonName = aps.getExtensionName(report.addonId);
 
       let label = bundle.getFormattedString("processHang.add-on.label",
                                             [addonName, brandBundle.getString("brandShortName")]);
 
       let linkText = bundle.getString("processHang.add-on.learn-more.text");
       let linkURL = "https://support.mozilla.org/kb/warning-unresponsive-script#w_other-causes";
 
-      let link = doc.createElement("label");
+      let link = doc.createXULElement("label");
       link.setAttribute("class", "text-link");
       link.setAttribute("role", "link");
       link.setAttribute("onclick", `openTrustedLinkIn(${JSON.stringify(linkURL)}, "tab")`);
       link.setAttribute("value", linkText);
 
       message = doc.createDocumentFragment();
       message.appendChild(doc.createTextNode(label + " "));
       message.appendChild(link);
--- a/browser/modules/webrtcUI.jsm
+++ b/browser/modules/webrtcUI.jsm
@@ -564,17 +564,17 @@ function prompt(aBrowser, aRequest) {
         label.setAttribute("accesskey",
                            stringBundle.getString(gumStringId + ".accesskey"));
 
         // "Select <type>" is the default because we can't pick a
         // 'default' window to share.
         addDeviceToList(menupopup,
                         stringBundle.getString("getUserMedia.pick" + typeName + ".label"),
                         "-1");
-        menupopup.appendChild(doc.createElement("menuseparator"));
+        menupopup.appendChild(doc.createXULElement("menuseparator"));
 
         // Build the list of 'devices'.
         let monitorIndex = 1;
         for (let i = 0; i < devices.length; ++i) {
           let device = devices[i];
 
           let name;
           // Building screen list from available screens.
@@ -633,17 +633,17 @@ function prompt(aBrowser, aRequest) {
             let string;
             let bundle = chromeWin.gNavigatorBundle;
 
             let learnMoreText =
               bundle.getString("getUserMedia.shareScreen.learnMoreLabel");
             let baseURL =
               Services.urlFormatter.formatURLPref("app.support.baseURL");
 
-            let learnMore = chromeWin.document.createElement("label");
+            let learnMore = chromeWin.document.createXULElement("label");
             learnMore.className = "text-link";
             learnMore.setAttribute("href", baseURL + "screenshare-safety");
             learnMore.textContent = learnMoreText;
 
             if (type == "screen") {
               string = bundle.getFormattedString("getUserMedia.shareScreenWarning.message",
                                                  ["<>"]);
             } else {
@@ -680,17 +680,17 @@ function prompt(aBrowser, aRequest) {
               video.play();
             };
           });
         };
         menupopup.addEventListener("command", menupopup._commandEventListener);
       }
 
       function addDeviceToList(menupopup, deviceName, deviceIndex, type) {
-        let menuitem = doc.createElement("menuitem");
+        let menuitem = doc.createXULElement("menuitem");
         menuitem.setAttribute("value", deviceIndex);
         menuitem.setAttribute("label", deviceName);
         menuitem.setAttribute("tooltiptext", deviceName);
         if (type)
           menuitem.setAttribute("devicetype", type);
 
         if (deviceIndex == "-1")
           menuitem.setAttribute("disabled", true);
@@ -905,44 +905,44 @@ function getGlobalIndicator() {
       }
 
       let bundle =
         Services.strings.createBundle("chrome://browser/locale/webrtcIndicator.properties");
 
       if (activeStreams.length == 1) {
         let stream = activeStreams[0];
 
-        let menuitem = this.ownerDocument.createElement("menuitem");
+        let menuitem = this.ownerDocument.createXULElement("menuitem");
         let labelId = "webrtcIndicator.sharing" + type + "With.menuitem";
         let label = stream.browser.contentTitle || stream.uri;
         menuitem.setAttribute("label", bundle.formatStringFromName(labelId, [label], 1));
         menuitem.setAttribute("disabled", "true");
         this.appendChild(menuitem);
 
-        menuitem = this.ownerDocument.createElement("menuitem");
+        menuitem = this.ownerDocument.createXULElement("menuitem");
         menuitem.setAttribute("label",
                               bundle.GetStringFromName("webrtcIndicator.controlSharing.menuitem"));
         menuitem.stream = stream;
         menuitem.addEventListener("command", indicator._command);
 
         this.appendChild(menuitem);
         return true;
       }
 
       // We show a different menu when there are several active streams.
-      let menuitem = this.ownerDocument.createElement("menuitem");
+      let menuitem = this.ownerDocument.createXULElement("menuitem");
       let labelId = "webrtcIndicator.sharing" + type + "WithNTabs.menuitem";
       let count = activeStreams.length;
       let label = PluralForm.get(count, bundle.GetStringFromName(labelId)).replace("#1", count);
       menuitem.setAttribute("label", label);
       menuitem.setAttribute("disabled", "true");
       this.appendChild(menuitem);
 
       for (let stream of activeStreams) {
-        let item = this.ownerDocument.createElement("menuitem");
+        let item = this.ownerDocument.createXULElement("menuitem");
         labelId = "webrtcIndicator.controlSharingOn.menuitem";
         label = stream.browser.contentTitle || stream.uri;
         item.setAttribute("label", bundle.formatStringFromName(labelId, [label], 1));
         item.stream = stream;
         item.addEventListener("command", indicator._command);
         this.appendChild(item);
       }
 
@@ -952,25 +952,25 @@ function getGlobalIndicator() {
     _popupHiding(aEvent) {
       while (this.firstChild)
         this.firstChild.remove();
     },
 
     _setIndicatorState(aName, aState) {
       let field = "_" + aName.toLowerCase();
       if (aState && !this[field]) {
-        let menu = this._hiddenDoc.createElement("menu");
+        let menu = this._hiddenDoc.createXULElement("menu");
         menu.setAttribute("id", "webRTC-sharing" + aName + "-menu");
 
         // The CSS will only be applied if the menu is actually inserted in the DOM.
         this._hiddenDoc.documentElement.appendChild(menu);
 
         this._statusBar.addItem(menu);
 
-        let menupopup = this._hiddenDoc.createElement("menupopup");
+        let menupopup = this._hiddenDoc.createXULElement("menupopup");
         menupopup.setAttribute("type", aName);
         menupopup.addEventListener("popupshowing", this._popupShowing);
         menupopup.addEventListener("popuphiding", this._popupHiding);
         menupopup.addEventListener("command", this._command);
         menu.appendChild(menupopup);
 
         this[field] = menu;
       } else if (this[field] && !aState) {
@@ -1006,17 +1006,17 @@ function onTabSharingMenuPopupShowing(e)
       stringName += "Microphone";
     if (types.screen)
       stringName += types.screen;
 
     let doc = e.target.ownerDocument;
     let bundle = doc.defaultView.gNavigatorBundle;
 
     let origin = getHostOrExtensionName(null, streamInfo.uri);
-    let menuitem = doc.createElement("menuitem");
+    let menuitem = doc.createXULElement("menuitem");
     menuitem.setAttribute("label", bundle.getFormattedString(stringName, [origin]));
     menuitem.stream = streamInfo;
     menuitem.addEventListener("command", onTabSharingMenuPopupCommand);
     e.target.appendChild(menuitem);
   }
 }
 
 function onTabSharingMenuPopupHiding(e) {
@@ -1028,35 +1028,35 @@ function onTabSharingMenuPopupCommand(e)
   webrtcUI.showSharingDoorhanger(e.target.stream);
 }
 
 function showOrCreateMenuForWindow(aWindow) {
   let document = aWindow.document;
   let menu = document.getElementById("tabSharingMenu");
   if (!menu) {
     let stringBundle = aWindow.gNavigatorBundle;
-    menu = document.createElement("menu");
+    menu = document.createXULElement("menu");
     menu.id = "tabSharingMenu";
     let labelStringId = "getUserMedia.sharingMenu.label";
     menu.setAttribute("label", stringBundle.getString(labelStringId));
 
     let container, insertionPoint;
     if (AppConstants.platform == "macosx") {
       container = document.getElementById("windowPopup");
       insertionPoint = document.getElementById("sep-window-list");
-      let separator = document.createElement("menuseparator");
+      let separator = document.createXULElement("menuseparator");
       separator.id = "tabSharingSeparator";
       container.insertBefore(separator, insertionPoint);
     } else {
       let accesskeyStringId = "getUserMedia.sharingMenu.accesskey";
       menu.setAttribute("accesskey", stringBundle.getString(accesskeyStringId));
       container = document.getElementById("main-menubar");
       insertionPoint = document.getElementById("helpMenu");
     }
-    let popup = document.createElement("menupopup");
+    let popup = document.createXULElement("menupopup");
     popup.id = "tabSharingMenuPopup";
     popup.addEventListener("popupshowing", onTabSharingMenuPopupShowing);
     popup.addEventListener("popuphiding", onTabSharingMenuPopupHiding);
     menu.appendChild(popup);
     container.insertBefore(menu, insertionPoint);
   } else {
     menu.hidden = false;
     if (AppConstants.platform == "macosx") {
--- a/browser/themes/linux/syncedtabs/sidebar.css
+++ b/browser/themes/linux/syncedtabs/sidebar.css
@@ -1,32 +1,21 @@
 /* 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 ../../shared/syncedtabs/sidebar.inc.css
 
 /* These styles are intended to mimic XUL trees and the XUL search box. */
 
-html {
-  background-color: -moz-Field;
-  color: -moz-FieldText;
-  box-sizing: border-box;
-}
-
-.item {
-  padding-inline-end: 0;
-}
-
 .item-title {
   margin: 1px 0 0;
   margin-inline-end: 6px;
 }
 
-
 .search-box {
   -moz-appearance: textfield;
   cursor: text;
   margin: 4px;
 }
 
 .textbox-search-clear {
   background-image: url(moz-icon://stock/gtk-clear?size=menu);
--- a/browser/themes/osx/syncedtabs/sidebar.css
+++ b/browser/themes/osx/syncedtabs/sidebar.css
@@ -6,50 +6,37 @@
 
 /* These styles are intended to mimic XUL trees and the XUL search box. */
 
 .content-container {
   -moz-appearance: -moz-mac-source-list;
   -moz-font-smoothing-background-color: -moz-mac-source-list;
 }
 
-.item {
-  color: -moz-DialogText;
-}
-
 .item-title-container {
   box-sizing: border-box;
   align-items: center;
   height: 24px;
   font-size: 12px;
 }
 
 .item.selected > .item-title-container {
-  color: HighlightText;
-  font-weight: bold;
-}
-
-.item.selected > .item-title-container {
   -moz-appearance: -moz-mac-source-list-selection;
   -moz-font-smoothing-background-color: -moz-mac-source-list-selection;
 }
 
 .item.selected:focus > .item-title-container {
   -moz-appearance: -moz-mac-active-source-list-selection;
   -moz-font-smoothing-background-color: -moz-mac-active-source-list-selection;
 }
 
-@media (-moz-mac-yosemite-theme) {
+@media (-moz-mac-yosemite-theme: 0) {
   .item.selected > .item-title-container {
-    color: -moz-dialogtext;
-    font-weight: 500;
-  }
-
-  .item.selected:focus > .item-title-container {
     color: #fff;
+    font-weight: bold;
   }
 }
 
 .sidebar-search-container {
   padding: 4px;
 }
 
 .search-box {
--- a/browser/themes/shared/controlcenter/panel.inc.css
+++ b/browser/themes/shared/controlcenter/panel.inc.css
@@ -557,16 +557,20 @@ description#identity-popup-content-verif
   /* We need to align the action buttons and permission icons with the text.
      This is tricky because the icon height is defined in pixels, while the
      font height can vary with platform and system settings, and at least on
      Windows the default font metrics reserve more extra space for accents.
      This value is a good compromise for different platforms and font sizes. */
   margin-top: -0.1em;
 }
 
+.identity-popup-content-blocking-category-label {
+  max-width: 200px;
+}
+
 .identity-popup-content-blocking-category-label,
 .identity-popup-permission-label {
   margin-inline-start: 1em;
 }
 
 .identity-popup-content-blocking-category-state-label,
 .identity-popup-content-blocking-category-add-blocking,
 .identity-popup-permission-state-label {
--- a/browser/themes/shared/syncedtabs/sidebar.inc.css
+++ b/browser/themes/shared/syncedtabs/sidebar.inc.css
@@ -1,23 +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/.
 
 /* These styles are intended to mimic XUL trees and the XUL search box. */
 
 html {
   height: 100%;
+  box-sizing: border-box;
 }
 
 body {
   height: 100%;
   margin: 0;
   font: message-box;
-  color: #333333;
+  background-color: -moz-Field;
+  color: -moz-FieldText;
   -moz-user-select: none;
 }
 
 /* The content-container holds the non-scrollable header and the scrollable
    content area.
 */
 .content-container {
   display: flex;
--- a/browser/themes/shared/tabs.inc.css
+++ b/browser/themes/shared/tabs.inc.css
@@ -65,18 +65,18 @@
   }
 }
 
 .tabbrowser-tab {
   -moz-appearance: none;
   background-color: transparent;
   border-radius: 0;
   border-width: 0;
-  margin: 0;
-  padding: 0;
+  margin: 0 !important /* override tabbox.css */;
+  padding: 0 !important /* override tabbox.css */;
   -moz-box-align: stretch;
 }
 
 /* The selected tab should appear above the border between the tabs toolbar and
    the navigation toolbar. */
 .tabbrowser-tab[visuallyselected=true] {
   position: relative;
   z-index: 2;
--- a/browser/themes/shared/urlbar-searchbar.inc.css
+++ b/browser/themes/shared/urlbar-searchbar.inc.css
@@ -373,16 +373,127 @@
   -moz-context-properties: fill, stroke;
   stroke: var(--toolbarbutton-icon-fill-attention);
 }
 
 #star-button-box[animationsenabled] > #star-button[starred][animate]:-moz-locale-dir(rtl) + #star-button-animatable-box > #star-button-animatable-image {
   animation-name: bookmark-animation-rtl;
 }
 
+/**
+ * Contextual Feature Recommendation
+ *
+ * Animate the recommendation icon to expand outwards and display a text label.
+ * Fake the effect of a smoothly expanding width without animating any widths
+ * by (continuously) animating only `mask-position-x` and `transform`.
+ *
+ * In a few places, transition a property using the timing-function `step-start`
+ * while collapsed and `step-end` while expanded in order to (discretely) modify
+ * the value while expanded and while transitioning in either direction.
+ *
+ * This UI is part of an experiment launching in LTR locales only. Fixing the
+ * RTL issues is tracked by Bug 1485725.
+ */
+
+:root {
+  --cfr-animation-duration: 0.35s;
+  --cfr-button-icon: url(resource://activity-stream/data/content/assets/glyph-webextension-16.svg);
+  --cfr-active-color: #0060df;
+}
+
+#contextual-feature-recommendation {
+  width: 28px;
+  margin-left: 0;
+  transition: margin-left step-end var(--cfr-animation-duration);
+}
+#urlbar[cfr-recommendation-state="expanded"] #contextual-feature-recommendation {
+  margin-left: calc(var(--cfr-label-width) * -1);
+  transition: margin-left step-start var(--cfr-animation-duration);
+}
+
+#cfr-button {
+  list-style-image: var(--cfr-button-icon);
+  margin: -2px 0;
+  transition-property: fill, fill-opacity, transform;
+  transition-timing-function: ease-in-out;
+  transition-duration: var(--cfr-animation-duration);
+}
+#urlbar[cfr-recommendation-state="expanded"] #cfr-button {
+  fill: white;
+  fill-opacity: 1;
+  background-color: transparent; /* Override hover background-color */
+  transform: translateX(calc(var(--cfr-label-width) * -1));
+}
+@keyframes cfr-button-fade-through-active-color {
+  33% {
+    fill-opacity: 1;
+    fill: var(--cfr-active-color);
+  }
+  67% {
+    fill-opacity: 1;
+    fill: var(--cfr-active-color);
+  }
+}
+#urlbar[cfr-recommendation-state="collapsed"] #cfr-button {
+  animation: cfr-button-fade-through-active-color calc(3 * var(--cfr-animation-duration));
+}
+
+#cfr-label-container {
+  width: 0;
+  overflow: hidden;
+  border-radius: 5px;
+  padding-left: 28px;
+  mask-image: linear-gradient(to right, transparent 0, black 0);
+  mask-position-x: var(--cfr-label-width);
+  mask-repeat: no-repeat;
+  transition-property: background-color, mask-position-x, width, margin-right;
+  transition-timing-function: ease-in-out, ease-in-out, step-end, step-end;
+  transition-duration: var(--cfr-animation-duration);
+}
+#urlbar[cfr-recommendation-state="expanded"] #cfr-label-container {
+  width: calc(var(--cfr-label-width) + 28px);
+  background-color: var(--cfr-active-color);
+  margin-right: -28px;
+  mask-position-x: 0;
+  transition-timing-function: ease-in-out, ease-in-out, step-start, step-start;
+}
+
+#cfr-label {
+  margin: 0;
+  padding: 3px 5px 3px 0;
+  color: white;
+  mask-image: linear-gradient(to right, transparent 0, black 0);
+  mask-position-x: var(--cfr-label-width);
+  mask-repeat: no-repeat;
+  transition: mask-position-x ease-in-out var(--cfr-animation-duration);
+}
+#urlbar[cfr-recommendation-state="expanded"] #cfr-label {
+  mask-position-x: 0;
+}
+
+/* Translate the dropmarker to give illusion of increasing width of the recommendation  */
+#urlbar[cfr-recommendation-state] .urlbar-history-dropmarker {
+  transition: transform ease-in-out var(--cfr-animation-duration);
+}
+#urlbar[cfr-recommendation-state="expanded"] .urlbar-history-dropmarker {
+  transform: translateX(calc(var(--cfr-label-width) * -1));
+}
+
+/* Shift the url-bar text fading to stop the recommendation overlapping */
+#urlbar[cfr-recommendation-state] html|input.urlbar-input {
+  transition: mask-position-x ease-in-out var(--cfr-animation-duration);
+}
+#urlbar[cfr-recommendation-state="expanded"] html|input.urlbar-input {
+  /* A mask-image is only defined for the url bar when text overflows. When
+     expanded, the right end of the url bar may be obscured without overflow so
+     we need to redefine the mask-image here, as well as the mask-position-x. */
+  mask-image: linear-gradient(to left, transparent, black 3ch);
+  mask-position-x: calc(var(--cfr-label-width) * -1);
+}
+
 /* Reader mode icon */
 
 #reader-mode-button {
   list-style-image: url(chrome://browser/skin/readerMode.svg);
 }
 
 #reader-mode-button[readeractive] {
   fill: var(--toolbarbutton-icon-fill-attention);
--- a/browser/themes/windows/syncedtabs/sidebar.css
+++ b/browser/themes/windows/syncedtabs/sidebar.css
@@ -1,25 +1,18 @@
 /* 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 ../../shared/syncedtabs/sidebar.inc.css
 
 /* These styles are intended to mimic XUL trees and the XUL search box. */
 
-.item {
-  padding-inline-end: 0;
-}
-
 .item-title {
   margin: 1px 0 0;
-}
-
-.item-title {
   margin-inline-end: 6px;
 }
 
 .search-box {
   -moz-appearance: textfield;
   cursor: text;
   margin: 4px;
   padding: 2px 2px 3px;
--- a/build/build-clang/build-clang.py
+++ b/build/build-clang/build-clang.py
@@ -469,17 +469,18 @@ if __name__ == "__main__":
 
     config = json.load(args.config)
 
     llvm_revision = config["llvm_revision"]
     llvm_repo = config["llvm_repo"]
     clang_repo = config["clang_repo"]
     extra_repo = config.get("extra_repo")
     lld_repo = config.get("lld_repo")
-    compiler_repo = config["compiler_repo"]
+    # On some packages we don't use compiler_repo
+    compiler_repo = config.get("compiler_repo")
     libcxx_repo = config["libcxx_repo"]
     libcxxabi_repo = config.get("libcxxabi_repo")
     stages = 3
     if "stages" in config:
         stages = int(config["stages"])
         if stages not in (1, 2, 3):
             raise ValueError("We only know how to build 1, 2, or 3 stages")
     build_type = "Release"
@@ -538,17 +539,18 @@ if __name__ == "__main__":
         if os.path.exists(checkout_dir):
             svn_update(checkout_dir, llvm_revision)
         else:
             svn_co(source_dir, repo, checkout_dir, llvm_revision)
 
     if not args.skip_checkout:
         checkout_or_update(llvm_repo, llvm_source_dir)
         checkout_or_update(clang_repo, clang_source_dir)
-        checkout_or_update(compiler_repo, compiler_rt_source_dir)
+        if compiler_repo is not None:
+            checkout_or_update(compiler_repo, compiler_rt_source_dir)
         checkout_or_update(libcxx_repo, libcxx_source_dir)
         if lld_repo:
             checkout_or_update(lld_repo, lld_source_dir)
         if libcxxabi_repo:
             checkout_or_update(libcxxabi_repo, libcxxabi_source_dir)
         if extra_repo:
             checkout_or_update(extra_repo, extra_source_dir)
         for p in config.get("patches", []):
deleted file mode 100644
--- a/build/build-clang/clang-tidy-cxx14.patch
+++ /dev/null
@@ -1,23 +0,0 @@
-Backport cxx14 default dialect flag from clang 6.0.0 trunk to 5.0.1
-
-Index: lib/Frontend/CompilerInvocation.cpp
-===================================================================
---- a/clang/lib/Frontend/CompilerInvocation.cpp	(revision 320871)
-+++ b/clang/lib/Frontend/CompilerInvocation.cpp	(working copy)
-@@ -1690,11 +1690,11 @@
-       break;
-     case InputKind::CXX:
-     case InputKind::ObjCXX:
--      // The PS4 uses C++11 as the default C++ standard.
--      if (T.isPS4())
--        LangStd = LangStandard::lang_gnucxx11;
--      else
--        LangStd = LangStandard::lang_gnucxx98;
-+#if defined(CLANG_DEFAULT_STD_CXX)
-+      LangStd = CLANG_DEFAULT_STD_CXX;
-+#else
-+      LangStd = LangStandard::lang_gnucxx14;
-+#endif
-       break;
-     case InputKind::RenderScript:
-       LangStd = LangStandard::lang_c99;
--- a/build/build-clang/clang-tidy-linux64.json
+++ b/build/build-clang/clang-tidy-linux64.json
@@ -1,22 +1,20 @@
 {
-    "llvm_revision": "320871",
+    "llvm_revision": "340491",
     "stages": "1",
     "build_libcxx": true,
     "build_type": "Release",
     "assertions": false,
     "build_clang_tidy": true,
-    "llvm_repo": "https://llvm.org/svn/llvm-project/llvm/tags/RELEASE_501/final",
-    "clang_repo": "https://llvm.org/svn/llvm-project/cfe/tags/RELEASE_501/final",
-    "extra_repo": "https://llvm.org/svn/llvm-project/clang-tools-extra/tags/RELEASE_501/final",
-    "compiler_repo": "https://llvm.org/svn/llvm-project/compiler-rt/tags/RELEASE_501/final",
-    "libcxx_repo": "https://llvm.org/svn/llvm-project/libcxx/tags/RELEASE_501/final",
-    "libcxxabi_repo": "https://llvm.org/svn/llvm-project/libcxxabi/tags/RELEASE_501/final",
+    "llvm_repo": "https://llvm.org/svn/llvm-project/llvm/tags/RELEASE_700/rc2/",
+    "clang_repo": "https://llvm.org/svn/llvm-project/cfe/tags/RELEASE_700/rc2/",
+    "extra_repo": "https://llvm.org/svn/llvm-project/clang-tools-extra/tags/RELEASE_700/rc2/",
+    "libcxx_repo": "https://llvm.org/svn/llvm-project/libcxx/tags/RELEASE_700/rc2/",
+    "libcxxabi_repo": "https://llvm.org/svn/llvm-project/libcxxabi/tags/RELEASE_700/rc2/",
     "python_path": "/usr/bin/python2.7",
     "gcc_dir": "/builds/worker/workspace/build/src/gcc",
     "cc": "/builds/worker/workspace/build/src/gcc/bin/gcc",
     "cxx": "/builds/worker/workspace/build/src/gcc/bin/g++",
     "as": "/builds/worker/workspace/build/src/gcc/bin/gcc",
     "patches": [
-      "clang-tidy-cxx14.patch"
     ]
-}
+}
\ No newline at end of file
--- a/build/build-clang/clang-tidy-macosx64.json
+++ b/build/build-clang/clang-tidy-macosx64.json
@@ -1,29 +1,25 @@
 {
-    "llvm_revision": "320871",
+    "llvm_revision": "340491",
     "stages": "1",
     "build_libcxx": true,
     "build_type": "Release",
     "assertions": false,
     "build_clang_tidy": true,
     "osx_cross_compile": true,
-    "llvm_repo": "https://llvm.org/svn/llvm-project/llvm/tags/RELEASE_501/final",
-    "clang_repo": "https://llvm.org/svn/llvm-project/cfe/tags/RELEASE_501/final",
-    "extra_repo": "https://llvm.org/svn/llvm-project/clang-tools-extra/tags/RELEASE_501/final",
-    "compiler_repo": "https://llvm.org/svn/llvm-project/compiler-rt/tags/RELEASE_501/final",
-    "libcxx_repo": "https://llvm.org/svn/llvm-project/libcxx/tags/RELEASE_501/final",
-    "libcxxabi_repo": "https://llvm.org/svn/llvm-project/libcxxabi/tags/RELEASE_501/final",
+    "llvm_repo": "https://llvm.org/svn/llvm-project/llvm/tags/RELEASE_700/rc2",
+    "clang_repo": "https://llvm.org/svn/llvm-project/cfe/tags/RELEASE_700/rc2",
+    "extra_repo": "https://llvm.org/svn/llvm-project/clang-tools-extra/tags/RELEASE_700/rc2",
+    "libcxx_repo": "https://llvm.org/svn/llvm-project/libcxx/tags/RELEASE_700/rc2",
+    "libcxxabi_repo": "https://llvm.org/svn/llvm-project/libcxxabi/tags/RELEASE_700/rc2",
     "python_path": "/usr/bin/python2.7",
     "gcc_dir": "/builds/worker/workspace/build/src/gcc",
     "cc": "/builds/worker/workspace/build/src/clang/bin/clang",
     "cxx": "/builds/worker/workspace/build/src/clang/bin/clang++",
     "as": "/builds/worker/workspace/build/src/clang/bin/clang",
     "ar": "/builds/worker/workspace/build/src/cctools/bin/x86_64-apple-darwin11-ar",
     "ranlib": "/builds/worker/workspace/build/src/cctools/bin/x86_64-apple-darwin11-ranlib",
+    "libtool": "/builds/worker/workspace/build/src/cctools/bin/x86_64-apple-darwin11-libtool",
     "ld": "/builds/worker/workspace/build/src/clang/bin/clang",
     "patches": [
-      "llvm-debug-frame-for-5.patch",
-      "compiler-rt-cross-compile.patch",
-      "compiler-rt-no-codesign.patch",
-      "clang-tidy-cxx14.patch"
     ]
 }
--- a/build/build-clang/clang-tidy-win32.json
+++ b/build/build-clang/clang-tidy-win32.json
@@ -1,19 +1,18 @@
 {
-    "llvm_revision": "320871",
+    "llvm_revision": "340491",
     "stages": "1",
     "build_libcxx": false,
     "build_type": "Release",
     "assertions": false,
     "build_clang_tidy": true,
-    "llvm_repo": "https://llvm.org/svn/llvm-project/llvm/tags/RELEASE_501/final",
-    "clang_repo": "https://llvm.org/svn/llvm-project/cfe/tags/RELEASE_501/final",
-    "extra_repo": "https://llvm.org/svn/llvm-project/clang-tools-extra/tags/RELEASE_501/final",
-    "compiler_repo": "https://llvm.org/svn/llvm-project/compiler-rt/tags/RELEASE_501/final",
+    "llvm_repo": "https://llvm.org/svn/llvm-project/llvm/tags/RELEASE_700/rc2",
+    "clang_repo": "https://llvm.org/svn/llvm-project/cfe/tags/RELEASE_700/rc2",
+    "extra_repo": "https://llvm.org/svn/llvm-project/clang-tools-extra/tags/RELEASE_700/rc2",
+    "compiler_repo": "https://llvm.org/svn/llvm-project/compiler-rt/tags/RELEASE_700/rc2",
     "libcxx_repo": "https://llvm.org/svn/llvm-project/libcxx/trunk",
     "python_path": "c:/mozilla-build/python/python.exe",
     "cc": "cl.exe",
     "cxx": "cl.exe",
     "patches": [
-      "clang-tidy-cxx14.patch"
     ]
 }
--- a/build/build-clang/clang-tidy-win64.json
+++ b/build/build-clang/clang-tidy-win64.json
@@ -1,20 +1,19 @@
 {
-    "llvm_revision": "320871",
+    "llvm_revision": "340491",
     "stages": "1",
     "build_libcxx": false,
     "build_type": "Release",
     "assertions": false,
     "build_clang_tidy": true,
-    "llvm_repo": "https://llvm.org/svn/llvm-project/llvm/tags/RELEASE_501/final",
-    "clang_repo": "https://llvm.org/svn/llvm-project/cfe/tags/RELEASE_501/final",
-    "extra_repo": "https://llvm.org/svn/llvm-project/clang-tools-extra/tags/RELEASE_501/final",
-    "compiler_repo": "https://llvm.org/svn/llvm-project/compiler-rt/tags/RELEASE_501/final",
+    "llvm_repo": "https://llvm.org/svn/llvm-project/llvm/tags/RELEASE_700/rc2",
+    "clang_repo": "https://llvm.org/svn/llvm-project/cfe/tags/RELEASE_700/rc2",
+    "extra_repo": "https://llvm.org/svn/llvm-project/clang-tools-extra/tags/RELEASE_700/rc2",
+    "compiler_repo": "https://llvm.org/svn/llvm-project/compiler-rt/tags/RELEASE_700/rc2",
     "libcxx_repo": "https://llvm.org/svn/llvm-project/libcxx/trunk",
     "python_path": "c:/mozilla-build/python/python.exe",
     "cc": "cl.exe",
     "cxx": "cl.exe",
     "ml": "ml64.exe",
     "patches": [
-      "clang-tidy-cxx14.patch"
     ]
 }
deleted file mode 100644
--- a/build/build-clang/llvm-debug-frame-for-5.patch
+++ /dev/null
@@ -1,13 +0,0 @@
-Index: lib/CodeGen/AsmPrinter/AsmPrinter.cpp
-===================================================================
---- a/llvm/lib/CodeGen/AsmPrinter/AsmPrinter.cpp	(revision 226419)
-+++ b/llvm/lib/CodeGen/AsmPrinter/AsmPrinter.cpp	(working copy)
-@@ -210,6 +210,8 @@
-     OutStreamer->EmitFileDirective(M.getSourceFileName());
-   }
-
-+  OutStreamer->EmitCFISections(true, true);
-+
-   GCModuleInfo *MI = getAnalysisIfAvailable<GCModuleInfo>();
-   assert(MI && "AsmPrinter didn't require GCModuleInfo?");
-   for (auto &I : *MI)
--- a/build/clang-plugin/ExplicitImplicitChecker.cpp
+++ b/build/clang-plugin/ExplicitImplicitChecker.cpp
@@ -19,15 +19,18 @@ void ExplicitImplicitChecker::check(cons
   // We've already checked everything in the matcher, so we just have to report
   // the error.
 
   const CXXConstructorDecl *Ctor =
       Result.Nodes.getNodeAs<CXXConstructorDecl>("ctor");
   const CXXRecordDecl *Declaration =
       Result.Nodes.getNodeAs<CXXRecordDecl>("class");
 
+  FixItHint FixItHint =
+      FixItHint::CreateInsertion(Ctor->getLocation(), "explicit ");
   diag(Ctor->getLocation(), "bad implicit conversion constructor for %0",
        DiagnosticIDs::Error)
       << Declaration->getDeclName();
   diag(Ctor->getLocation(),
        "consider adding the explicit keyword to the constructor",
-       DiagnosticIDs::Note);
+       DiagnosticIDs::Note)
+      << FixItHint;
 }
--- a/build/clang-plugin/import_mozilla_checks.py
+++ b/build/clang-plugin/import_mozilla_checks.py
@@ -109,17 +109,17 @@ def do_import(mozilla_path, clang_tidy_p
     copy_dir_contents(mozilla_path, module_path)
     write_third_party_paths(mozilla_path, module_path)
     write_cmake(module_path)
     add_item_to_cmake_section(os.path.join(module_path, '..', 'plugin',
                                            'CMakeLists.txt'),
                               'LINK_LIBS', 'clangTidyMozillaModule')
     add_item_to_cmake_section(os.path.join(module_path, '..', 'tool',
                                            'CMakeLists.txt'),
-                              'target_link_libraries', 'clangTidyMozillaModule')
+                              'PRIVATE', 'clangTidyMozillaModule')
     with open(os.path.join(module_path, '..', 'CMakeLists.txt'), 'a') as f:
         f.write('add_subdirectory(%s)\n' % module)
     with open(os.path.join(module_path, '..', 'tool', 'ClangTidyMain.cpp'), 'a') as f:
         f.write('''
 // This anchor is used to force the linker to link the MozillaModule.
 extern volatile int MozillaModuleAnchorSource;
 static int LLVM_ATTRIBUTE_UNUSED MozillaModuleAnchorDestination =
           MozillaModuleAnchorSource;
--- a/build/clang-plugin/moz.build
+++ b/build/clang-plugin/moz.build
@@ -63,17 +63,16 @@ third_party_paths.inputs = [
 ]
 
 HOST_COMPILE_FLAGS['STL'] = []
 HOST_COMPILE_FLAGS['VISIBILITY'] = []
 
 # libc++ is required to build plugins against clang on OS X.
 if CONFIG['HOST_OS_ARCH'] == 'Darwin':
     HOST_CXXFLAGS += ['-stdlib=libc++']
-    HOST_LDFLAGS += ['-lc++']
 
 DIRS += [
     'tests',
 ]
 
 
 # In the current moz.build world, we need to override essentially every
 # variable to limit ourselves to what we need to build the clang plugin.
--- a/build/valgrind/cross-architecture.sup
+++ b/build/valgrind/cross-architecture.sup
@@ -21,16 +21,23 @@
 {
    PR_SetEnv requires its argument to be leaked, but does not appear on stacks. (See bug 793549.)
    Memcheck:Leak
    ...
    fun:_ZL13SaveWordToEnvPKcRK12nsTSubstringIcE
    ...
 }
 {
+   PR_SetEnv requires its argument to be leaked, but does not appear on stacks. (See bug 793549.)
+   Memcheck:Leak
+   ...
+   fun:SaveWordToEnv
+   ...
+}
+{
    PR_SetEnv requires its argument to be leaked, but does not appear on stacks. (See bug 944133.)
    Memcheck:Leak
    ...
    fun:_ZN13CrashReporter14SetRestartArgsEiPPc
    ...
 }
 {
    PR_SetEnv requires its argument to be leaked, but does not appear on stacks. (See bug 793548.)
--- a/devtools/client/aboutdebugging-new/aboutdebugging.css
+++ b/devtools/client/aboutdebugging-new/aboutdebugging.css
@@ -1,21 +1,22 @@
 /* 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/. */
 
 @import "chrome://global/skin/in-content/common.css";
 @import "resource://devtools/client/aboutdebugging-new/src/components/App.css";
 @import "resource://devtools/client/aboutdebugging-new/src/components/ConnectPage.css";
-@import "resource://devtools/client/aboutdebugging-new/src/components/DebugTargetItem.css";
-@import "resource://devtools/client/aboutdebugging-new/src/components/DebugTargetList.css";
 @import "resource://devtools/client/aboutdebugging-new/src/components/RuntimeInfo.css";
 @import "resource://devtools/client/aboutdebugging-new/src/components/Sidebar.css";
 @import "resource://devtools/client/aboutdebugging-new/src/components/SidebarItem.css";
+@import "resource://devtools/client/aboutdebugging-new/src/components/debugtarget/DebugTargetItem.css";
+@import "resource://devtools/client/aboutdebugging-new/src/components/debugtarget/DebugTargetList.css";
 @import "resource://devtools/client/aboutdebugging-new/src/components/debugtarget/ExtensionDetail.css";
+@import "resource://devtools/client/aboutdebugging-new/src/components/debugtarget/WorkerDetail.css";
 
 :root {
   /* Import css variables from common.css */
   --text-color: var(--in-content-page-color);
 }
 
 html, body {
   margin: 0;
--- a/devtools/client/aboutdebugging-new/src/actions/runtime.js
+++ b/devtools/client/aboutdebugging-new/src/actions/runtime.js
@@ -5,31 +5,35 @@
 "use strict";
 
 const { AddonManager } = require("resource://gre/modules/AddonManager.jsm");
 const { BrowserToolboxProcess } =
   require("resource://devtools/client/framework/ToolboxProcess.jsm");
 const { Cc, Ci } = require("chrome");
 const { DebuggerClient } = require("devtools/shared/client/debugger-client");
 const { DebuggerServer } = require("devtools/server/main");
+const { gDevToolsBrowser } = require("devtools/client/framework/devtools-browser");
 
 const {
   CONNECT_RUNTIME_FAILURE,
   CONNECT_RUNTIME_START,
   CONNECT_RUNTIME_SUCCESS,
   DEBUG_TARGETS,
   DISCONNECT_RUNTIME_FAILURE,
   DISCONNECT_RUNTIME_START,
   DISCONNECT_RUNTIME_SUCCESS,
   REQUEST_EXTENSIONS_FAILURE,
   REQUEST_EXTENSIONS_START,
   REQUEST_EXTENSIONS_SUCCESS,
   REQUEST_TABS_FAILURE,
   REQUEST_TABS_START,
   REQUEST_TABS_SUCCESS,
+  REQUEST_WORKERS_FAILURE,
+  REQUEST_WORKERS_START,
+  REQUEST_WORKERS_SUCCESS,
 } = require("../constants");
 
 let browserToolboxProcess = null;
 
 function connectRuntime() {
   return async (dispatch, getState) => {
     dispatch({ type: CONNECT_RUNTIME_START });
 
@@ -38,16 +42,17 @@ function connectRuntime() {
     const client = new DebuggerClient(DebuggerServer.connectPipe());
 
     try {
       await client.connect();
 
       dispatch({ type: CONNECT_RUNTIME_SUCCESS, client });
       dispatch(requestExtensions());
       dispatch(requestTabs());
+      dispatch(requestWorkers());
     } catch (e) {
       dispatch({ type: CONNECT_RUNTIME_FAILURE, error: e.message });
     }
   };
 }
 
 function disconnectRuntime() {
   return async (dispatch, getState) => {
@@ -62,36 +67,49 @@ function disconnectRuntime() {
       dispatch({ type: DISCONNECT_RUNTIME_SUCCESS });
     } catch (e) {
       dispatch({ type: DISCONNECT_RUNTIME_FAILURE, error: e.message });
     }
   };
 }
 
 function inspectDebugTarget(type, id) {
-  if (type === DEBUG_TARGETS.TAB) {
-    window.open(`about:devtools-toolbox?type=tab&id=${ id }`);
-  } else if (type === DEBUG_TARGETS.EXTENSION) {
-    // Close previous addon debugging toolbox.
-    if (browserToolboxProcess) {
-      browserToolboxProcess.close();
-    }
+  return async (_, getState) => {
+    switch (type) {
+      case DEBUG_TARGETS.TAB: {
+        // Open tab debugger in new window.
+        window.open(`about:devtools-toolbox?type=tab&id=${ id }`);
+        break;
+      }
+      case DEBUG_TARGETS.EXTENSION: {
+        // Close current debugging toolbox and open a new one.
+        if (browserToolboxProcess) {
+          browserToolboxProcess.close();
+        }
 
-    browserToolboxProcess = BrowserToolboxProcess.init({
-      addonID: id,
-      onClose: () => {
-        browserToolboxProcess = null;
+        browserToolboxProcess = BrowserToolboxProcess.init({
+          addonID: id,
+          onClose: () => {
+            browserToolboxProcess = null;
+          }
+        });
+        break;
       }
-    });
-  } else {
-    console.error(`Failed to inspect the debug target of type: ${ type } id: ${ id }`);
-  }
+      case DEBUG_TARGETS.WORKER: {
+        // Open worker toolbox in new window.
+        gDevToolsBrowser.openWorkerToolbox(getState().runtime.client, id);
+        break;
+      }
 
-  // We cancel the redux flow here since the inspection does not need to update the state.
-  return () => {};
+      default: {
+        console.error("Failed to inspect the debug target of " +
+                      `type: ${ type } id: ${ id }`);
+      }
+    }
+  };
 }
 
 function installTemporaryExtension() {
   const fp = Cc["@mozilla.org/filepicker;1"].createInstance(Ci.nsIFilePicker);
   fp.init(window, "Select Manifest File or Package (.xpi)", Ci.nsIFilePicker.modeOpen);
   fp.open(async res => {
     if (res == Ci.nsIFilePicker.returnCancel || !fp.file) {
       return;
@@ -111,16 +129,28 @@ function installTemporaryExtension() {
     } catch (e) {
       console.error(e);
     }
   });
 
   return () => {};
 }
 
+function pushServiceWorker(actor) {
+  return async (_, getState) => {
+    const client = getState().runtime.client;
+
+    try {
+      await client.request({ to: actor, type: "push" });
+    } catch (e) {
+      console.error(e);
+    }
+  };
+}
+
 function reloadTemporaryExtension(actor) {
   return async (_, getState) => {
     const client = getState().runtime.client;
 
     try {
       await client.request({ to: actor, type: "reload" });
     } catch (e) {
       console.error(e);
@@ -176,18 +206,58 @@ function requestExtensions() {
         temporaryExtensions,
       });
     } catch (e) {
       dispatch({ type: REQUEST_EXTENSIONS_FAILURE, error: e.message });
     }
   };
 }
 
+function requestWorkers() {
+  return async (dispatch, getState) => {
+    dispatch({ type: REQUEST_WORKERS_START });
+
+    const client = getState().runtime.client;
+
+    try {
+      const {
+        other: otherWorkers,
+        service: serviceWorkers,
+        shared: sharedWorkers,
+      } = await client.mainRoot.listAllWorkers();
+
+      dispatch({
+        type: REQUEST_WORKERS_SUCCESS,
+        otherWorkers,
+        serviceWorkers,
+        sharedWorkers,
+      });
+    } catch (e) {
+      dispatch({ type: REQUEST_WORKERS_FAILURE, error: e.message });
+    }
+  };
+}
+
+function startServiceWorker(actor) {
+  return async (_, getState) => {
+    const client = getState().runtime.client;
+
+    try {
+      await client.request({ to: actor, type: "start" });
+    } catch (e) {
+      console.error(e);
+    }
+  };
+}
+
 module.exports = {
   connectRuntime,
   disconnectRuntime,
   inspectDebugTarget,
   installTemporaryExtension,
+  pushServiceWorker,
   reloadTemporaryExtension,
   removeTemporaryExtension,
   requestTabs,
   requestExtensions,
+  requestWorkers,
+  startServiceWorker,
 };
--- a/devtools/client/aboutdebugging-new/src/components/RuntimePage.js
+++ b/devtools/client/aboutdebugging-new/src/components/RuntimePage.js
@@ -4,66 +4,113 @@
 
 "use strict";
 
 const { connect } = require("devtools/client/shared/vendor/react-redux");
 const { createFactory, PureComponent } = require("devtools/client/shared/vendor/react");
 const dom = require("devtools/client/shared/vendor/react-dom-factories");
 const PropTypes = require("devtools/client/shared/vendor/react-prop-types");
 
-const DebugTargetPane = createFactory(require("./DebugTargetPane"));
+const DebugTargetPane = createFactory(require("./debugtarget/DebugTargetPane"));
+const ExtensionDetail = createFactory(require("./debugtarget/ExtensionDetail"));
+const InspectAction = createFactory(require("./debugtarget/InspectAction"));
 const RuntimeInfo = createFactory(require("./RuntimeInfo"));
+const ServiceWorkerAction = createFactory(require("./debugtarget/ServiceWorkerAction"));
+const TabDetail = createFactory(require("./debugtarget/TabDetail"));
+const TemporaryExtensionAction = createFactory(require("./debugtarget/TemporaryExtensionAction"));
 const TemporaryExtensionInstaller =
   createFactory(require("./debugtarget/TemporaryExtensionInstaller"));
+const WorkerDetail = createFactory(require("./debugtarget/WorkerDetail"));
 
 const Services = require("Services");
 
 class RuntimePage extends PureComponent {
   static get propTypes() {
     return {
       dispatch: PropTypes.func.isRequired,
       installedExtensions: PropTypes.arrayOf(PropTypes.object).isRequired,
+      otherWorkers: PropTypes.arrayOf(PropTypes.object).isRequired,
+      serviceWorkers: PropTypes.arrayOf(PropTypes.object).isRequired,
+      sharedWorkers: PropTypes.arrayOf(PropTypes.object).isRequired,
       tabs: PropTypes.arrayOf(PropTypes.object).isRequired,
       temporaryExtensions: PropTypes.arrayOf(PropTypes.object).isRequired,
     };
   }
 
   render() {
-    const { dispatch, installedExtensions, tabs, temporaryExtensions } = this.props;
+    const {
+      dispatch,
+      installedExtensions,
+      otherWorkers,
+      serviceWorkers,
+      sharedWorkers,
+      tabs,
+      temporaryExtensions,
+    } = this.props;
 
     return dom.article(
       {
         className: "page",
       },
       RuntimeInfo({
         icon: "chrome://branding/content/icon64.png",
         name: Services.appinfo.name,
         version: Services.appinfo.version,
       }),
       TemporaryExtensionInstaller({ dispatch }),
       DebugTargetPane({
+        actionComponent: TemporaryExtensionAction,
+        detailComponent: ExtensionDetail,
         dispatch,
         name: "Temporary Extensions",
         targets: temporaryExtensions,
       }),
       DebugTargetPane({
+        actionComponent: InspectAction,
+        detailComponent: ExtensionDetail,
         dispatch,
         name: "Extensions",
         targets: installedExtensions,
       }),
       DebugTargetPane({
+        actionComponent: InspectAction,
+        detailComponent: TabDetail,
         dispatch,
         name: "Tabs",
         targets: tabs
       }),
+      DebugTargetPane({
+        actionComponent: ServiceWorkerAction,
+        detailComponent: WorkerDetail,
+        dispatch,
+        name: "Service Workers",
+        targets: serviceWorkers
+      }),
+      DebugTargetPane({
+        actionComponent: InspectAction,
+        detailComponent: WorkerDetail,
+        dispatch,
+        name: "Shared Workers",
+        targets: sharedWorkers
+      }),
+      DebugTargetPane({
+        actionComponent: InspectAction,
+        detailComponent: WorkerDetail,
+        dispatch,
+        name: "Other Workers",
+        targets: otherWorkers
+      }),
     );
   }
 }
 
 const mapStateToProps = state => {
   return {
     installedExtensions: state.runtime.installedExtensions,
+    otherWorkers: state.runtime.otherWorkers,
+    serviceWorkers: state.runtime.serviceWorkers,
+    sharedWorkers: state.runtime.sharedWorkers,
     tabs: state.runtime.tabs,
     temporaryExtensions: state.runtime.temporaryExtensions,
   };
 };
 
 module.exports = connect(mapStateToProps)(RuntimePage);
rename from devtools/client/aboutdebugging-new/src/components/DebugTargetItem.css
rename to devtools/client/aboutdebugging-new/src/components/debugtarget/DebugTargetItem.css
rename from devtools/client/aboutdebugging-new/src/components/DebugTargetItem.js
rename to devtools/client/aboutdebugging-new/src/components/debugtarget/DebugTargetItem.js
--- a/devtools/client/aboutdebugging-new/src/components/DebugTargetItem.js
+++ b/devtools/client/aboutdebugging-new/src/components/debugtarget/DebugTargetItem.js
@@ -1,93 +1,59 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 "use strict";
 
-const { createFactory, PureComponent } = require("devtools/client/shared/vendor/react");
+const { PureComponent } = require("devtools/client/shared/vendor/react");
 const dom = require("devtools/client/shared/vendor/react-dom-factories");
 const PropTypes = require("devtools/client/shared/vendor/react-prop-types");
 
-const ExtensionDetail = createFactory(require("./debugtarget/ExtensionDetail"));
-const TabDetail = createFactory(require("./debugtarget/TabDetail"));
-const TemporaryExtensionAction =
-  createFactory(require("./debugtarget/TemporaryExtensionAction"));
-
-const Actions = require("../actions/index");
-const { DEBUG_TARGETS } = require("../constants");
-
 /**
  * This component displays debug target.
  */
 class DebugTargetItem extends PureComponent {
   static get propTypes() {
     return {
+      actionComponent: PropTypes.any.isRequired,
+      detailComponent: PropTypes.any.isRequired,
       dispatch: PropTypes.func.isRequired,
       target: PropTypes.object.isRequired,
     };
   }
 
-  inspect() {
-    const { dispatch, target } = this.props;
-    dispatch(Actions.inspectDebugTarget(target.type, target.id));
-  }
-
   renderAction() {
-    const { dispatch, target } = this.props;
-
-    return dom.div(
-      {},
-      dom.button(
-        {
-          onClick: e => this.inspect(),
-          className: "aboutdebugging-button",
-        },
-        "Inspect"
-      ),
-      target.details.temporarilyInstalled
-        ? TemporaryExtensionAction({ dispatch, target }) : null,
-    );
+    const { actionComponent, dispatch, target } = this.props;
+    return actionComponent({ dispatch, target });
   }
 
   renderDetail() {
-    const { target } = this.props;
-
-    switch (target.type) {
-      case DEBUG_TARGETS.EXTENSION:
-        return ExtensionDetail({ target });
-      case DEBUG_TARGETS.TAB:
-        return TabDetail({ target });
-
-      default:
-        return null;
-    }
+    const { detailComponent, target } = this.props;
+    return detailComponent({ target });
   }
 
   renderIcon() {
     return dom.img({
       className: "debug-target-item__icon",
       src: this.props.target.icon,
     });
   }
 
   renderInfo() {
-    const { target } = this.props;
-
     return dom.div(
       {
         className: "debug-target-item__info",
       },
       dom.div(
         {
           className: "debug-target-item__info__name ellipsis-text",
-          title: target.name,
+          title: this.props.target.name,
         },
-        target.name
+        this.props.target.name
       ),
       this.renderDetail(),
     );
   }
 
   render() {
     return dom.li(
       {
rename from devtools/client/aboutdebugging-new/src/components/DebugTargetList.css
rename to devtools/client/aboutdebugging-new/src/components/debugtarget/DebugTargetList.css
rename from devtools/client/aboutdebugging-new/src/components/DebugTargetList.js
rename to devtools/client/aboutdebugging-new/src/components/debugtarget/DebugTargetList.js
--- a/devtools/client/aboutdebugging-new/src/components/DebugTargetList.js
+++ b/devtools/client/aboutdebugging-new/src/components/debugtarget/DebugTargetList.js
@@ -11,26 +11,29 @@ const PropTypes = require("devtools/clie
 const DebugTargetItem = createFactory(require("./DebugTargetItem"));
 
 /**
  * This component displays list of debug target.
  */
 class DebugTargetList extends PureComponent {
   static get propTypes() {
     return {
+      actionComponent: PropTypes.any.isRequired,
+      detailComponent: PropTypes.any.isRequired,
       dispatch: PropTypes.func.isRequired,
       targets: PropTypes.arrayOf(PropTypes.object).isRequired,
     };
   }
 
   render() {
-    const { dispatch, targets } = this.props;
+    const { actionComponent, detailComponent, dispatch, targets } = this.props;
 
     return dom.ul(
       {
         className: "debug-target-list",
       },
-      targets.map(target => DebugTargetItem({ dispatch, target })),
+      targets.map(target =>
+        DebugTargetItem({ actionComponent, detailComponent, dispatch, target })),
     );
   }
 }
 
 module.exports = DebugTargetList;
rename from devtools/client/aboutdebugging-new/src/components/DebugTargetPane.js
rename to devtools/client/aboutdebugging-new/src/components/debugtarget/DebugTargetPane.js
--- a/devtools/client/aboutdebugging-new/src/components/DebugTargetPane.js
+++ b/devtools/client/aboutdebugging-new/src/components/debugtarget/DebugTargetPane.js
@@ -11,26 +11,28 @@ const PropTypes = require("devtools/clie
 const DebugTargetList = createFactory(require("./DebugTargetList"));
 
 /**
  * This component provides list for debug target and name area.
  */
 class DebugTargetPane extends PureComponent {
   static get propTypes() {
     return {
+      actionComponent: PropTypes.any.isRequired,
+      detailComponent: PropTypes.any.isRequired,
       dispatch: PropTypes.func.isRequired,
       name: PropTypes.string.isRequired,
       targets: PropTypes.arrayOf(PropTypes.Object).isRequired,
     };
   }
 
   render() {
-    const { dispatch, name, targets } = this.props;
+    const { actionComponent, detailComponent, dispatch, name, targets } = this.props;
 
     return dom.section(
       {},
       dom.h2({}, name),
-      DebugTargetList({ dispatch, targets }),
+      DebugTargetList({ actionComponent, detailComponent, dispatch, targets }),
     );
   }
 }
 
 module.exports = DebugTargetPane;
new file mode 100644
--- /dev/null
+++ b/devtools/client/aboutdebugging-new/src/components/debugtarget/InspectAction.js
@@ -0,0 +1,40 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+const { PureComponent } = require("devtools/client/shared/vendor/react");
+const dom = require("devtools/client/shared/vendor/react-dom-factories");
+const PropTypes = require("devtools/client/shared/vendor/react-prop-types");
+
+const Actions = require("../../actions/index");
+
+/**
+ * This component provides inspect button.
+ */
+class InspectAction extends PureComponent {
+  static get propTypes() {
+    return {
+      dispatch: PropTypes.func.isRequired,
+      target: PropTypes.object.isRequired,
+    };
+  }
+
+  inspect() {
+    const { dispatch, target } = this.props;
+    dispatch(Actions.inspectDebugTarget(target.type, target.id));
+  }
+
+  render() {
+    return dom.button(
+      {
+        onClick: e => this.inspect(),
+        className: "aboutdebugging-button",
+      },
+      "Inspect"
+    );
+  }
+}
+
+module.exports = InspectAction;
new file mode 100644
--- /dev/null
+++ b/devtools/client/aboutdebugging-new/src/components/debugtarget/ServiceWorkerAction.js
@@ -0,0 +1,70 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+const { createFactory, PureComponent } = require("devtools/client/shared/vendor/react");
+const dom = require("devtools/client/shared/vendor/react-dom-factories");
+const PropTypes = require("devtools/client/shared/vendor/react-prop-types");
+
+const InspectAction = createFactory(require("./InspectAction"));
+
+const Actions = require("../../actions/index");
+
+/**
+ * This component displays buttons for service worker.
+ */
+class ServiceWorkerAction extends PureComponent {
+  static get propTypes() {
+    return {
+      dispatch: PropTypes.func.isRequired,
+      target: PropTypes.object.isRequired,
+    };
+  }
+
+  push() {
+    const { dispatch, target } = this.props;
+    dispatch(Actions.pushServiceWorker(target.id));
+  }
+
+  start() {
+    const { dispatch, target } = this.props;
+    dispatch(Actions.startServiceWorker(target.details.registrationActor));
+  }
+
+  _renderAction() {
+    const { dispatch, target } = this.props;
+    const { isActive, isRunning } = target.details;
+
+    if (!isRunning) {
+      return this._renderButton("Start", this.start.bind(this));
+    }
+
+    if (!isActive) {
+      // Only debug button is available if the service worker is not active.
+      return InspectAction({ dispatch, target });
+    }
+
+    return [
+      this._renderButton("Push", this.push.bind(this)),
+      InspectAction({ dispatch, target }),
+    ];
+  }
+
+  _renderButton(label, onClick) {
+    return dom.button(
+      {
+        className: "aboutdebugging-button",
+        onClick: e => onClick(),
+      },
+      label,
+    );
+  }
+
+  render() {
+    return dom.div({}, this._renderAction());
+  }
+}
+
+module.exports = ServiceWorkerAction;
--- a/devtools/client/aboutdebugging-new/src/components/debugtarget/TemporaryExtensionAction.js
+++ b/devtools/client/aboutdebugging-new/src/components/debugtarget/TemporaryExtensionAction.js
@@ -1,22 +1,24 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 "use strict";
 
-const { PureComponent } = require("devtools/client/shared/vendor/react");
+const { createFactory, PureComponent } = require("devtools/client/shared/vendor/react");
 const dom = require("devtools/client/shared/vendor/react-dom-factories");
 const PropTypes = require("devtools/client/shared/vendor/react-prop-types");
 
+const InspectAction = createFactory(require("./InspectAction"));
+
 const Actions = require("../../actions/index");
 
 /**
- * This component provides components that reload/remove temporary extension.
+ * This component provides components that inspect/reload/remove temporary extension.
  */
 class TemporaryExtensionAction extends PureComponent {
   static get propTypes() {
     return {
       dispatch: PropTypes.func.isRequired,
       target: PropTypes.object.isRequired,
     };
   }
@@ -27,28 +29,32 @@ class TemporaryExtensionAction extends P
   }
 
   remove() {
     const { dispatch, target } = this.props;
     dispatch(Actions.removeTemporaryExtension(target.id));
   }
 
   render() {
-    return [
+    const { dispatch, target } = this.props;
+
+    return dom.div(
+      {},
+      InspectAction({ dispatch, target }),
       dom.button(
         {
           className: "aboutdebugging-button",
           onClick: e => this.reload()
         },
         "Reload",
       ),
       dom.button(
         {
           className: "aboutdebugging-button",
           onClick: e => this.remove()
         },
         "Remove",
       ),
-    ];
+    );
   }
 }
 
 module.exports = TemporaryExtensionAction;
new file mode 100644
--- /dev/null
+++ b/devtools/client/aboutdebugging-new/src/components/debugtarget/WorkerDetail.css
@@ -0,0 +1,52 @@
+/* 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/. */
+
+.worker-detail {
+  --worker-status-font-size: 10px;
+}
+
+/*
+ * The current layout of worker detail is
+ *
+ *  +----------------+--------------------+
+ *  | detail name dt | detail value dd    |
+ *  | (60px )        | (auto)             |
+ *  +----------------+--------------------+
+ *  | detail name dt | detail value dd    |
+ *  +----------------+--------------------+
+ *  | detail name dt | detail value dd    |
+ *  +----------------+--------------------+
+ */
+.worker-detail {
+  display: grid;
+  grid-template-columns: 60px auto;
+  margin-block-start: 4px;
+}
+
+/*
+ * worker-detail__status has a ui like badge and the color change by the status.
+ * For now, the background-color of running status is palegreen, stopped is lightgrey
+ * though, might be changed since this is not Photon color.
+ */
+.worker-detail__status {
+  border-style: solid;
+  border-width: 1px;
+  box-sizing: border-box;
+  display: inline-block;
+  font-size: var(--worker-status-font-size);
+  margin-block-start: 6px;
+  padding-block-start: 2px;
+  padding-block-end: 2px;
+  text-align: center;
+}
+
+.worker-detail__status--running {
+  border-color: limegreen;
+  background-color: palegreen;
+}
+
+.worker-detail__status--stopped {
+  border-color: grey;
+  background-color: lightgrey;
+}
new file mode 100644
--- /dev/null
+++ b/devtools/client/aboutdebugging-new/src/components/debugtarget/WorkerDetail.js
@@ -0,0 +1,71 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+const { PureComponent } = require("devtools/client/shared/vendor/react");
+const dom = require("devtools/client/shared/vendor/react-dom-factories");
+const PropTypes = require("devtools/client/shared/vendor/react-prop-types");
+
+const {
+  SERVICE_WORKER_FETCH_STATES,
+} = require("../../constants");
+
+/**
+ * This component displays detail information for worker.
+ */
+class WorkerDetail extends PureComponent {
+  static get propTypes() {
+    return {
+      target: PropTypes.object.isRequired,
+    };
+  }
+
+  renderFetch() {
+    const { fetch } = this.props.target.details;
+    const label = fetch === SERVICE_WORKER_FETCH_STATES.LISTENING
+                    ? "Listening for fetch events"
+                    : "Not listening for fetch events";
+    return this.renderField("Fetch", label);
+  }
+
+  renderField(name, value) {
+    return [
+      dom.dt({}, name),
+      dom.dd(
+        {
+          className: "ellipsis-text",
+          title: value,
+        },
+        value,
+      ),
+    ];
+  }
+
+  renderStatus() {
+    const status = this.props.target.details.status.toLowerCase();
+
+    return dom.div(
+      {
+        className: `worker-detail__status worker-detail__status--${ status }`,
+      },
+      status
+    );
+  }
+
+  render() {
+    const { fetch, scope, status } = this.props.target.details;
+
+    return dom.dl(
+      {
+        className: "worker-detail",
+      },
+      fetch ? this.renderFetch() : null,
+      scope ? this.renderField("Scope", scope) : null,
+      status ? this.renderStatus() : null,
+    );
+  }
+}
+
+module.exports = WorkerDetail;
--- a/devtools/client/aboutdebugging-new/src/components/debugtarget/moz.build
+++ b/devtools/client/aboutdebugging-new/src/components/debugtarget/moz.build
@@ -1,11 +1,20 @@
 # This Source Code Form is subject to the terms of the Mozilla Public
 # License, v. 2.0. If a copy of the MPL was not distributed with this
 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
 
 DevToolsModules(
+    'DebugTargetItem.css',
+    'DebugTargetItem.js',
+    'DebugTargetList.css',
+    'DebugTargetList.js',
+    'DebugTargetPane.js',
     'ExtensionDetail.css',
     'ExtensionDetail.js',
+    'InspectAction.js',
+    'ServiceWorkerAction.js',
     'TabDetail.js',
     'TemporaryExtensionAction.js',
     'TemporaryExtensionInstaller.js',
+    'WorkerDetail.css',
+    'WorkerDetail.js',
 )
--- a/devtools/client/aboutdebugging-new/src/components/moz.build
+++ b/devtools/client/aboutdebugging-new/src/components/moz.build
@@ -6,21 +6,16 @@ DIRS += [
     'debugtarget',
 ]
 
 DevToolsModules(
     'App.css',
     'App.js',
     'ConnectPage.css',
     'ConnectPage.js',
-    'DebugTargetItem.css',
-    'DebugTargetItem.js',
-    'DebugTargetList.css',
-    'DebugTargetList.js',
-    'DebugTargetPane.js',
     'RuntimeInfo.css',
     'RuntimeInfo.js',
     'RuntimePage.js',
     'Sidebar.css',
     'Sidebar.js',
     'SidebarItem.css',
     'SidebarItem.js',
 )
--- a/devtools/client/aboutdebugging-new/src/constants.js
+++ b/devtools/client/aboutdebugging-new/src/constants.js
@@ -14,25 +14,42 @@ const actionTypes = {
   NETWORK_LOCATIONS_UPDATED: "NETWORK_LOCATIONS_UPDATED",
   PAGE_SELECTED: "PAGE_SELECTED",
   REQUEST_EXTENSIONS_FAILURE: "REQUEST_EXTENSIONS_FAILURE",
   REQUEST_EXTENSIONS_START: "REQUEST_EXTENSIONS_START",
   REQUEST_EXTENSIONS_SUCCESS: "REQUEST_EXTENSIONS_SUCCESS",
   REQUEST_TABS_FAILURE: "REQUEST_TABS_FAILURE",
   REQUEST_TABS_START: "REQUEST_TABS_START",
   REQUEST_TABS_SUCCESS: "REQUEST_TABS_SUCCESS",
+  REQUEST_WORKERS_FAILURE: "REQUEST_WORKERS_FAILURE",
+  REQUEST_WORKERS_START: "REQUEST_WORKERS_START",
+  REQUEST_WORKERS_SUCCESS: "REQUEST_WORKERS_SUCCESS",
 };
 
 const DEBUG_TARGETS = {
   EXTENSION: "EXTENSION",
   TAB: "TAB",
+  WORKER: "WORKER",
 };
 
 const PAGES = {
   THIS_FIREFOX: "this-firefox",
   CONNECT: "connect",
 };
 
+const SERVICE_WORKER_FETCH_STATES = {
+  LISTENING: "LISTENING",
+  NOT_LISTENING: "NOT_LISTENING",
+};
+
+const SERVICE_WORKER_STATUSES = {
+  RUNNING: "RUNNING",
+  REGISTERING: "REGISTERING",
+  STOPPED: "STOPPED",
+};
+
 // flatten constants
 module.exports = Object.assign({}, {
   DEBUG_TARGETS,
   PAGES,
+  SERVICE_WORKER_FETCH_STATES,
+  SERVICE_WORKER_STATUSES,
 }, actionTypes);
--- a/devtools/client/aboutdebugging-new/src/create-store.js
+++ b/devtools/client/aboutdebugging-new/src/create-store.js
@@ -6,25 +6,32 @@
 
 const { applyMiddleware, createStore } = require("devtools/client/shared/vendor/redux");
 const { thunk } = require("devtools/client/shared/redux/middleware/thunk.js");
 
 const rootReducer = require("./reducers/index");
 const { RuntimeState } = require("./reducers/runtime-state");
 const { UiState } = require("./reducers/ui-state");
 const debugTargetListenerMiddleware = require("./middleware/debug-target-listener");
+const extensionComponentDataMiddleware = require("./middleware/extension-component-data");
+const tabComponentDataMiddleware = require("./middleware/tab-component-data");
+const workerComponentDataMiddleware = require("./middleware/worker-component-data");
 const { getNetworkLocations } = require("./modules/network-locations");
 
 function configureStore() {
   const initialState = {
     runtime: new RuntimeState(),
     ui: getUiState()
   };
 
-  const middleware = applyMiddleware(thunk, debugTargetListenerMiddleware);
+  const middleware = applyMiddleware(thunk,
+                                     debugTargetListenerMiddleware,
+                                     extensionComponentDataMiddleware,
+                                     tabComponentDataMiddleware,
+                                     workerComponentDataMiddleware);
 
   return createStore(rootReducer, initialState, middleware);
 }
 
 function getUiState() {
   const locations = getNetworkLocations();
   return new UiState(locations);
 }
--- a/devtools/client/aboutdebugging-new/src/middleware/debug-target-listener.js
+++ b/devtools/client/aboutdebugging-new/src/middleware/debug-target-listener.js
@@ -7,23 +7,23 @@
 const { AddonManager } = require("resource://gre/modules/AddonManager.jsm");
 
 const {
   CONNECT_RUNTIME_SUCCESS,
   DISCONNECT_RUNTIME_START,
 } = require("../constants");
 const Actions = require("../actions/index");
 
-function debugTargetListenerMiddleware(state) {
+function debugTargetListenerMiddleware(store) {
   const onExtensionsUpdated = () => {
-    state.dispatch(Actions.requestExtensions());
+    store.dispatch(Actions.requestExtensions());
   };
 
   const onTabsUpdated = () => {
-    state.dispatch(Actions.requestTabs());
+    store.dispatch(Actions.requestTabs());
   };
 
   const extensionsListener = {
     onDisabled() {
       onExtensionsUpdated();
     },
 
     onEnabled() {
@@ -42,26 +42,42 @@ function debugTargetListenerMiddleware(s
       onExtensionsUpdated();
     },
 
     onUninstalling() {
       onExtensionsUpdated();
     },
   };
 
+  const onWorkersUpdated = () => {
+    store.dispatch(Actions.requestWorkers());
+  };
+
   return next => action => {
     switch (action.type) {
       case CONNECT_RUNTIME_SUCCESS: {
-        action.client.addListener("tabListChanged", onTabsUpdated);
+        const { client } = action;
+        client.addListener("tabListChanged", onTabsUpdated);
         AddonManager.addAddonListener(extensionsListener);
+        client.addListener("workerListChanged", onWorkersUpdated);
+        client.addListener("serviceWorkerRegistrationListChanged", onWorkersUpdated);
+        client.addListener("processListChanged", onWorkersUpdated);
+        client.addListener("registration-changed", onWorkersUpdated);
+        client.addListener("push-subscription-modified", onWorkersUpdated);
         break;
       }
       case DISCONNECT_RUNTIME_START: {
-        state.getState().runtime.client.removeListener("tabListChanged", onTabsUpdated);
+        const { client } = store.getState().runtime;
+        client.removeListener("tabListChanged", onTabsUpdated);
         AddonManager.removeAddonListener(extensionsListener);
+        client.removeListener("workerListChanged", onWorkersUpdated);
+        client.removeListener("serviceWorkerRegistrationListChanged", onWorkersUpdated);
+        client.removeListener("processListChanged", onWorkersUpdated);
+        client.removeListener("registration-changed", onWorkersUpdated);
+        client.removeListener("push-subscription-modified", onWorkersUpdated);
         break;
       }
     }
 
     return next(action);
   };
 }
 
new file mode 100644
--- /dev/null
+++ b/devtools/client/aboutdebugging-new/src/middleware/extension-component-data.js
@@ -0,0 +1,70 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+const {
+  DEBUG_TARGETS,
+  REQUEST_EXTENSIONS_SUCCESS,
+} = require("../constants");
+
+/**
+ * This middleware converts extensions object that get from DebuggerClient.listAddons()
+ * to data which is used in DebugTargetItem.
+ */
+const extensionComponentDataMiddleware = store => next => action => {
+  switch (action.type) {
+    case REQUEST_EXTENSIONS_SUCCESS: {
+      action.installedExtensions = toComponentData(action.installedExtensions);
+      action.temporaryExtensions = toComponentData(action.temporaryExtensions);
+      break;
+    }
+  }
+
+  return next(action);
+};
+
+function getFilePath(extension) {
+  // Only show file system paths, and only for temporarily installed add-ons.
+  if (!extension.temporarilyInstalled ||
+      !extension.url ||
+      !extension.url.startsWith("file://")) {
+    return null;
+  }
+
+  // Strip a leading slash from Windows drive letter URIs.
+  // file:///home/foo ~> /home/foo
+  // file:///C:/foo ~> C:/foo
+  const windowsRegex = /^file:\/\/\/([a-zA-Z]:\/.*)/;
+
+  if (windowsRegex.test(extension.url)) {
+    return windowsRegex.exec(extension.url)[1];
+  }
+
+  return extension.url.slice("file://".length);
+}
+
+function toComponentData(extensions) {
+  return extensions.map(extension => {
+    const type = DEBUG_TARGETS.EXTENSION;
+    const { actor, iconURL, id, manifestURL, name } = extension;
+    const icon = iconURL || "chrome://mozapps/skin/extensions/extensionGeneric.svg";
+    const location = getFilePath(extension);
+    const uuid = manifestURL ? /moz-extension:\/\/([^/]*)/.exec(manifestURL)[1] : null;
+    return {
+      name,
+      icon,
+      id,
+      type,
+      details: {
+        actor,
+        location,
+        manifestURL,
+        uuid,
+      },
+    };
+  });
+}
+
+module.exports = extensionComponentDataMiddleware;
--- a/devtools/client/aboutdebugging-new/src/middleware/moz.build
+++ b/devtools/client/aboutdebugging-new/src/middleware/moz.build
@@ -1,7 +1,10 @@
 # This Source Code Form is subject to the terms of the Mozilla Public
 # License, v. 2.0. If a copy of the MPL was not distributed with this
 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
 
 DevToolsModules(
     'debug-target-listener.js',
+    'extension-component-data.js',
+    'tab-component-data.js',
+    'worker-component-data.js',
 )
new file mode 100644
--- /dev/null
+++ b/devtools/client/aboutdebugging-new/src/middleware/tab-component-data.js
@@ -0,0 +1,48 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+const {
+  DEBUG_TARGETS,
+  REQUEST_TABS_SUCCESS,
+} = require("../constants");
+
+/**
+ * This middleware converts tabs object that get from DebuggerClient.listTabs() to data
+ * which is used in DebugTargetItem.
+ */
+const tabComponentDataMiddleware = store => next => action => {
+  switch (action.type) {
+    case REQUEST_TABS_SUCCESS: {
+      action.tabs = toComponentData(action.tabs);
+      break;
+    }
+  }
+
+  return next(action);
+};
+
+function toComponentData(tabs) {
+  return tabs.map(tab => {
+    const type = DEBUG_TARGETS.TAB;
+    const id = tab.outerWindowID;
+    const icon = tab.favicon
+      ? `data:image/png;base64,${ btoa(String.fromCharCode.apply(String, tab.favicon)) }`
+      : "chrome://devtools/skin/images/globe.svg";
+    const name = tab.title || tab.url;
+    const url = tab.url;
+    return {
+      name,
+      icon,
+      id,
+      type,
+      details: {
+        url,
+      },
+    };
+  });
+}
+
+module.exports = tabComponentDataMiddleware;
new file mode 100644
--- /dev/null
+++ b/devtools/client/aboutdebugging-new/src/middleware/worker-component-data.js
@@ -0,0 +1,78 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+const {
+  DEBUG_TARGETS,
+  REQUEST_WORKERS_SUCCESS,
+  SERVICE_WORKER_FETCH_STATES,
+  SERVICE_WORKER_STATUSES,
+} = require("../constants");
+
+/**
+ * This middleware converts workers object that get from DebuggerClient.listAllWorkers()
+ * to data which is used in DebugTargetItem.
+ */
+const workerComponentDataMiddleware = store => next => action => {
+  switch (action.type) {
+    case REQUEST_WORKERS_SUCCESS: {
+      action.otherWorkers = toComponentData(action.otherWorkers);
+      action.serviceWorkers = toComponentData(action.serviceWorkers, true);
+      action.sharedWorkers = toComponentData(action.sharedWorkers);
+      break;
+    }
+  }
+
+  return next(action);
+};
+
+function getServiceWorkerStatus(isActive, isRunning) {
+  if (isActive && isRunning) {
+    return SERVICE_WORKER_STATUSES.RUNNING;
+  } else if (isActive) {
+    return SERVICE_WORKER_STATUSES.STOPPED;
+  }
+  // We cannot get service worker registrations unless the registration is in
+  // ACTIVE state. Unable to know the actual state ("installing", "waiting"), we
+  // display a custom state "registering" for now. See Bug 1153292.
+  return SERVICE_WORKER_STATUSES.REGISTERING;
+}
+
+function toComponentData(workers, isServiceWorker) {
+  return workers.map(worker => {
+    const type = DEBUG_TARGETS.WORKER;
+    const id = worker.workerTargetActor;
+    const icon = "chrome://devtools/skin/images/debugging-workers.svg";
+    let { fetch, name, registrationActor, scope } = worker;
+    let isActive = false;
+    let isRunning = false;
+    let status = null;
+
+    if (isServiceWorker) {
+      fetch = fetch ? SERVICE_WORKER_FETCH_STATES.LISTENING
+                    : SERVICE_WORKER_FETCH_STATES.NOT_LISTENING;
+      isActive = worker.active;
+      isRunning = !!worker.workerTargetActor;
+      status = getServiceWorkerStatus(isActive, isRunning);
+    }
+
+    return {
+      name,
+      icon,
+      id,
+      type,
+      details: {
+        fetch,
+        isActive,
+        isRunning,
+        registrationActor,
+        scope,
+        status,
+      },
+    };
+  });
+}
+
+module.exports = workerComponentDataMiddleware;
--- a/devtools/client/aboutdebugging-new/src/reducers/runtime-state.js
+++ b/devtools/client/aboutdebugging-new/src/reducers/runtime-state.js
@@ -1,117 +1,57 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 "use strict";
 
 const {
   CONNECT_RUNTIME_SUCCESS,
-  DEBUG_TARGETS,
   DISCONNECT_RUNTIME_SUCCESS,
   REQUEST_EXTENSIONS_SUCCESS,
   REQUEST_TABS_SUCCESS,
+  REQUEST_WORKERS_SUCCESS,
 } = require("../constants");
 
 function RuntimeState() {
   return {
     client: null,
     installedExtensions: [],
+    otherWorkers: [],
+    serviceWorkers: [],
+    sharedWorkers: [],
     tabs: [],
     temporaryExtensions: [],
   };
 }
 
 function runtimeReducer(state = RuntimeState(), action) {
   switch (action.type) {
     case CONNECT_RUNTIME_SUCCESS: {
       const { client } = action;
       return Object.assign({}, state, { client });
     }
     case DISCONNECT_RUNTIME_SUCCESS: {
       return RuntimeState();
     }
     case REQUEST_EXTENSIONS_SUCCESS: {
       const { installedExtensions, temporaryExtensions } = action;
-      return Object.assign({}, state, {
-        installedExtensions: toExtensionComponentData(installedExtensions),
-        temporaryExtensions: toExtensionComponentData(temporaryExtensions),
-      });
+      return Object.assign({}, state, { installedExtensions, temporaryExtensions });
     }
     case REQUEST_TABS_SUCCESS: {
       const { tabs } = action;
-      return Object.assign({}, state, { tabs: toTabComponentData(tabs) });
+      return Object.assign({}, state, { tabs });
+    }
+    case REQUEST_WORKERS_SUCCESS: {
+      const { otherWorkers, serviceWorkers, sharedWorkers } = action;
+      return Object.assign({}, state, { otherWorkers, serviceWorkers, sharedWorkers });
     }
 
     default:
       return state;
   }
 }
 
-function getExtensionFilePath(extension) {
-  // Only show file system paths, and only for temporarily installed add-ons.
-  if (!extension.temporarilyInstalled ||
-      !extension.url ||
-      !extension.url.startsWith("file://")) {
-    return null;
-  }
-
-  // Strip a leading slash from Windows drive letter URIs.
-  // file:///home/foo ~> /home/foo
-  // file:///C:/foo ~> C:/foo
-  const windowsRegex = /^file:\/\/\/([a-zA-Z]:\/.*)/;
-
-  if (windowsRegex.test(extension.url)) {
-    return windowsRegex.exec(extension.url)[1];
-  }
-
-  return extension.url.slice("file://".length);
-}
-
-function toExtensionComponentData(extensions) {
-  return extensions.map(extension => {
-    const type = DEBUG_TARGETS.EXTENSION;
-    const { actor, iconURL, id, manifestURL, name, temporarilyInstalled } = extension;
-    const icon = iconURL || "chrome://mozapps/skin/extensions/extensionGeneric.svg";
-    const location = getExtensionFilePath(extension);
-    const uuid = manifestURL ? /moz-extension:\/\/([^/]*)/.exec(manifestURL)[1] : null;
-    return {
-      name,
-      icon,
-      id,
-      type,
-      details: {
-        actor,
-        location,
-        manifestURL,
-        temporarilyInstalled,
-        uuid,
-      },
-    };
-  });
-}
-
-function toTabComponentData(tabs) {
-  return tabs.map(tab => {
-    const type = DEBUG_TARGETS.TAB;
-    const id = tab.outerWindowID;
-    const icon = tab.favicon
-      ? `data:image/png;base64,${ btoa(String.fromCharCode.apply(String, tab.favicon)) }`
-      : "chrome://devtools/skin/images/globe.svg";
-    const name = tab.title || tab.url;
-    const url = tab.url;
-    return {
-      name,
-      icon,
-      id,
-      type,
-      details: {
-        url,
-      },
-    };
-  });
-}
-
 module.exports = {
   RuntimeState,
   runtimeReducer,
 };
--- a/devtools/client/framework/browser-menus.js
+++ b/devtools/client/framework/browser-menus.js
@@ -43,17 +43,17 @@ function l10n(key) {
  *        Access key of the menuitem, used as shortcut while opening the menu.
  * @param {Boolean} isCheckbox (optional)
  *        If true, the menuitem will act as a checkbox and have an optional
  *        tick on its left.
  *
  * @return XULMenuItemElement
  */
 function createMenuItem({ doc, id, label, accesskey, isCheckbox }) {
-  const menuitem = doc.createElement("menuitem");
+  const menuitem = doc.createXULElement("menuitem");
   menuitem.id = id;
   menuitem.setAttribute("label", label);
   if (accesskey) {
     menuitem.setAttribute("accesskey", accesskey);
   }
   if (isCheckbox) {
     menuitem.setAttribute("type", "checkbox");
     menuitem.setAttribute("autocheck", "false");
@@ -209,17 +209,17 @@ function addAllToolsToMenu(doc) {
  *        The document to which menus are to be added.
  */
 function addTopLevelItems(doc) {
   const menuItems = doc.createDocumentFragment();
 
   const { menuitems } = require("../menus");
   for (const item of menuitems) {
     if (item.separator) {
-      const separator = doc.createElement("menuseparator");
+      const separator = doc.createXULElement("menuseparator");
       separator.id = item.id;
       menuItems.appendChild(separator);
     } else {
       const { id, l10nKey } = item;
 
       // Create a <menuitem>
       const menuitem = createMenuItem({
         doc,
--- a/devtools/client/framework/toolbox-hosts.js
+++ b/devtools/client/framework/toolbox-hosts.js
@@ -48,22 +48,22 @@ BottomHost.prototype = {
    */
   create: async function() {
     await gDevToolsBrowser.loadBrowserStyleSheet(this.hostTab.ownerGlobal);
 
     const gBrowser = this.hostTab.ownerDocument.defaultView.gBrowser;
     const ownerDocument = gBrowser.ownerDocument;
     this._nbox = gBrowser.getNotificationBox(this.hostTab.linkedBrowser);
 
-    this._splitter = ownerDocument.createElement("splitter");
+    this._splitter = ownerDocument.createXULElement("splitter");
     this._splitter.setAttribute("class", "devtools-horizontal-splitter");
     // Avoid resizing notification containers
     this._splitter.setAttribute("resizebefore", "flex");
 
-    this.frame = ownerDocument.createElement("iframe");
+    this.frame = ownerDocument.createXULElement("iframe");
     this.frame.flex = 1; // Required to be able to shrink when the window shrinks
     this.frame.className = "devtools-toolbox-bottom-iframe";
     this.frame.height = Math.min(
       Services.prefs.getIntPref(this.heightPref),
       this._nbox.clientHeight - MIN_PAGE_SIZE
     );
 
     this._nbox.appendChild(this._splitter);
@@ -136,20 +136,20 @@ class SidebarHost {
    */
   async create() {
     await gDevToolsBrowser.loadBrowserStyleSheet(this.hostTab.ownerGlobal);
     const gBrowser = this.hostTab.ownerDocument.defaultView.gBrowser;
     const ownerDocument = gBrowser.ownerDocument;
     this._browser = gBrowser.getBrowserContainer(this.hostTab.linkedBrowser);
     this._sidebar = gBrowser.getSidebarContainer(this.hostTab.linkedBrowser);
 
-    this._splitter = ownerDocument.createElement("splitter");
+    this._splitter = ownerDocument.createXULElement("splitter");
     this._splitter.setAttribute("class", "devtools-side-splitter");
 
-    this.frame = ownerDocument.createElement("iframe");
+    this.frame = ownerDocument.createXULElement("iframe");
     this.frame.flex = 1; // Required to be able to shrink when the window shrinks
     this.frame.className = "devtools-toolbox-side-iframe";
 
     this.frame.width = Math.min(
       Services.prefs.getIntPref(this.widthPref),
       this._sidebar.clientWidth - MIN_PAGE_SIZE
     );
 
--- a/devtools/client/framework/toolbox.js
+++ b/devtools/client/framework/toolbox.js
@@ -994,17 +994,17 @@ Toolbox.prototype = {
     for (const item of Startup.KeyShortcuts) {
       // KeyShortcuts contain tool-specific and global key shortcuts,
       // here we only need to copy shortcut specific to each tool.
       if (!item.toolId) {
         continue;
       }
       const { toolId, shortcut, modifiers } = item;
 
-      const key = doc.createElement("key");
+      const key = doc.createXULElement("key");
 
       key.id = "key_" + toolId;
 
       if (shortcut.startsWith("VK_")) {
         key.setAttribute("keycode", shortcut);
       } else {
         key.setAttribute("key", shortcut);
       }
@@ -1015,17 +1015,17 @@ Toolbox.prototype = {
       key.addEventListener("command", () => {
         this.selectTool(toolId, "key_shortcut").then(() => this.fireCustomKey(toolId));
       }, true);
       doc.getElementById("toolbox-keyset").appendChild(key);
     }
 
     // Add key for toggling the browser console from the detached window
     if (!doc.getElementById("key_browserconsole")) {
-      const key = doc.createElement("key");
+      const key = doc.createXULElement("key");
       key.id = "key_browserconsole";
 
       key.setAttribute("key", L10N.getStr("browserConsoleCmd.commandkey"));
       key.setAttribute("modifiers", "accel,shift");
       // needed. See bug 371900
       key.setAttribute("oncommand", "void(0)");
       key.addEventListener("command", () => {
         HUDService.toggleBrowserConsole();
@@ -1435,17 +1435,17 @@ Toolbox.prototype = {
 
     if (toolDefinition.ordinal == undefined || toolDefinition.ordinal < 0) {
       toolDefinition.ordinal = MAX_ORDINAL;
     }
 
     if (!toolDefinition.bgTheme) {
       toolDefinition.bgTheme = "theme-toolbar";
     }
-    const panel = this.doc.createElement("vbox");
+    const panel = this.doc.createXULElement("vbox");
     panel.className = "toolbox-panel " + toolDefinition.bgTheme;
 
     // There is already a container for the webconsole frame.
     if (!this.doc.getElementById("toolbox-panel-" + id)) {
       panel.id = "toolbox-panel-" + id;
     }
 
     deck.appendChild(panel);
@@ -1645,17 +1645,17 @@ Toolbox.prototype = {
       // Retrieve the tool definition (from the global or the per-toolbox tool maps)
       const definition = this.getToolDefinition(id);
 
       if (!definition) {
         reject(new Error("no such tool id " + id));
         return;
       }
 
-      iframe = this.doc.createElement("iframe");
+      iframe = this.doc.createXULElement("iframe");
       iframe.className = "toolbox-panel-iframe";
       iframe.id = "toolbox-panel-iframe-" + id;
       iframe.setAttribute("flex", 1);
       iframe.setAttribute("forceOwnRefreshDriver", "");
       iframe.tooltip = "aHTMLTooltip";
       iframe.style.visibility = "hidden";
 
       gDevTools.emit(id + "-init", this, iframe);
new file mode 100644
--- /dev/null
+++ b/devtools/client/shared/TelemetryStopwatch.jsm
@@ -0,0 +1,417 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+var EXPORTED_SYMBOLS = ["TelemetryStopwatch"];
+
+ChromeUtils.import("resource://gre/modules/Services.jsm");
+ChromeUtils.defineModuleGetter(this, "Log",
+  "resource://gre/modules/Log.jsm");
+
+// Weak map does not allow using null objects as keys. These objects are used
+// as 'null' placeholders.
+const NULL_OBJECT = {};
+const NULL_KEY = {};
+
+/**
+ * Timers is a variation of a Map used for storing information about running
+ * Stopwatches. Timers has the following data structure:
+ *
+ * {
+ *    "HISTOGRAM_NAME": WeakMap {
+ *      Object || NULL_OBJECT: Map {
+ *        "KEY" || NULL_KEY: startTime
+ *        ...
+ *      }
+ *      ...
+ *    }
+ *    ...
+ * }
+ *
+ *
+ * @example
+ * // Stores current time for a keyed histogram "PLAYING_WITH_CUTE_ANIMALS".
+ * Timers.put("PLAYING_WITH_CUTE_ANIMALS", null, "CATS", Date.now());
+ *
+ * @example
+ * // Returns information about a simple Stopwatch.
+ * let startTime = Timers.get("PLAYING_WITH_CUTE_ANIMALS", null, "CATS");
+ */
+const Timers = {
+  _timers: new Map(),
+
+  _validTypes(histogram, obj, key) {
+    const nonEmptyString = value => {
+      return typeof value === "string" && value !== "" && value.length > 0;
+    };
+    return nonEmptyString(histogram) &&
+            typeof obj == "object" &&
+           (key === NULL_KEY || nonEmptyString(key));
+  },
+
+  get(histogram, obj, key) {
+    key = key === null ? NULL_KEY : key;
+    obj = obj || NULL_OBJECT;
+
+    if (!this.has(histogram, obj, key)) {
+      return null;
+    }
+
+    return this._timers.get(histogram).get(obj).get(key);
+  },
+
+  put(histogram, obj, key, startTime) {
+    key = key === null ? NULL_KEY : key;
+    obj = obj || NULL_OBJECT;
+
+    if (!this._validTypes(histogram, obj, key)) {
+      return false;
+    }
+
+    const objectMap = this._timers.get(histogram) || new WeakMap();
+    const keyedInfo = objectMap.get(obj) || new Map();
+    keyedInfo.set(key, startTime);
+    objectMap.set(obj, keyedInfo);
+    this._timers.set(histogram, objectMap);
+    return true;
+  },
+
+  has(histogram, obj, key) {
+    key = key === null ? NULL_KEY : key;
+    obj = obj || NULL_OBJECT;
+
+    return this._timers.has(histogram) &&
+      this._timers.get(histogram).has(obj) &&
+      this._timers.get(histogram).get(obj).has(key);
+  },
+
+  delete(histogram, obj, key) {
+    key = key === null ? NULL_KEY : key;
+    obj = obj || NULL_OBJECT;
+
+    if (!this.has(histogram, obj, key)) {
+      return false;
+    }
+    const objectMap = this._timers.get(histogram);
+    const keyedInfo = objectMap.get(obj);
+    if (keyedInfo.size > 1) {
+      keyedInfo.delete(key);
+      return true;
+    }
+    objectMap.delete(obj);
+    // NOTE:
+    // We never delete empty objecMaps from this._timers because there is no
+    // nice solution for tracking the number of objects in a WeakMap.
+    // WeakMap is not enumerable, so we can't deterministically say when it's
+    // empty. We accept that trade-off here, given that entries for short-lived
+    // objects will go away when they are no longer referenced
+    return true;
+  }
+};
+
+var TelemetryStopwatch = {
+  /**
+   * Starts a timer associated with a telemetry histogram. The timer can be
+   * directly associated with a histogram, or with a pair of a histogram and
+   * an object.
+   *
+   * @param {String} aHistogram - a string which must be a valid histogram name.
+   *
+   * @param {Object} aObj - Optional parameter. If specified, the timer is
+   *                        associated with this object, meaning that multiple
+   *                        timers for the same histogram may be run
+   *                        concurrently, as long as they are associated with
+   *                        different objects.
+   *
+   * @returns {Boolean} True if the timer was successfully started, false
+   *                    otherwise. If a timer already exists, it can't be
+   *                    started again, and the existing one will be cleared in
+   *                    order to avoid measurements errors.
+   */
+  start(aHistogram, aObj) {
+    return TelemetryStopwatchImpl.start(aHistogram, aObj, null);
+  },
+
+  /**
+   * Returns whether a timer associated with a telemetry histogram is currently
+   * running. The timer can be directly associated with a histogram, or with a
+   * pair of a histogram and an object.
+   *
+   * @param {String} aHistogram - a string which must be a valid histogram name.
+   *
+   * @param {Object} aObj - Optional parameter. If specified, the timer is
+   *                        associated with this object, meaning that multiple
+   *                        timers for the same histogram may be run
+   *                        concurrently, as long as they are associated with
+   *                        different objects.
+   *
+   * @returns {Boolean} True if the timer exists and is currently running.
+   */
+  running(aHistogram, aObj) {
+    return TelemetryStopwatchImpl.running(aHistogram, aObj, null);
+  },
+
+  /**
+   * Deletes the timer associated with a telemetry histogram. The timer can be
+   * directly associated with a histogram, or with a pair of a histogram and
+   * an object. Important: Only use this method when a legitimate cancellation
+   * should be done.
+   *
+   * @param {String} aHistogram - a string which must be a valid histogram name.
+   *
+   * @param {Object} aObj - Optional parameter. If specified, the timer is
+   *                        associated with this object, meaning that multiple
+   *                        timers or a same histogram may be run concurrently,
+   *                        as long as they are associated with different
+   *                        objects.
+   *
+   * @returns {Boolean} True if the timer exist and it was cleared, False
+   *                   otherwise.
+   */
+  cancel(aHistogram, aObj) {
+    return TelemetryStopwatchImpl.cancel(aHistogram, aObj, null);
+  },
+
+  /**
+   * Returns the elapsed time for a particular stopwatch. Primarily for
+   * debugging purposes. Must be called prior to finish.
+   *
+   * @param {String} aHistogram - a string which must be a valid histogram name.
+   *                              If an invalid name is given, the function will
+   *                              throw.
+   *
+   * @param (Object) aObj - Optional parameter which associates the histogram
+   *                        timer with the given object.
+   *
+   * @param {Boolean} aCanceledOkay - Optional parameter which will suppress any
+   *                                  warnings that normally fire when a stopwatch
+   *                                  is finished after being cancelled. Defaults
+   *                                  to false.
+   *
+   * @returns {Integer} time in milliseconds or -1 if the stopwatch was not
+   *                   found.
+   */
+  timeElapsed(aHistogram, aObj, aCanceledOkay) {
+    return TelemetryStopwatchImpl.timeElapsed(aHistogram, aObj, null,
+                                              aCanceledOkay);
+  },
+
+  /**
+   * Stops the timer associated with the given histogram (and object),
+   * calculates the time delta between start and finish, and adds the value
+   * to the histogram.
+   *
+   * @param {String} aHistogram - a string which must be a valid histogram name.
+   *
+   * @param {Object} aObj - Optional parameter which associates the histogram
+   *                        timer with the given object.
+   *
+   * @param {Boolean} aCanceledOkay - Optional parameter which will suppress any
+   *                                  warnings that normally fire when a stopwatch
+   *                                  is finished after being cancelled. Defaults
+   *                                  to false.
+   *
+   * @returns {Boolean} True if the timer was succesfully stopped and the data
+   *                    was added to the histogram, False otherwise.
+   */
+  finish(aHistogram, aObj, aCanceledOkay) {
+    return TelemetryStopwatchImpl.finish(aHistogram, aObj, null, aCanceledOkay);
+  },
+
+  /**
+   * Starts a timer associated with a keyed telemetry histogram. The timer can
+   * be directly associated with a histogram and its key. Similarly to
+   * @see{TelemetryStopwatch.stat} the histogram and its key can be associated
+   * with an object. Each key may have multiple associated objects and each
+   * object can be associated with multiple keys.
+   *
+   * @param {String} aHistogram - a string which must be a valid histogram name.
+   *
+   * @param {String} aKey - a string which must be a valid histgram key.
+   *
+   * @param {Object} aObj - Optional parameter. If specified, the timer is
+   *                        associated with this object, meaning that multiple
+   *                        timers for the same histogram may be run
+   *                        concurrently,as long as they are associated with
+   *                        different objects.
+   *
+   * @returns {Boolean} True if the timer was successfully started, false
+   *                    otherwise. If a timer already exists, it can't be
+   *                    started again, and the existing one will be cleared in
+   *                    order to avoid measurements errors.
+   */
+  startKeyed(aHistogram, aKey, aObj) {
+    return TelemetryStopwatchImpl.start(aHistogram, aObj, aKey);
+  },
+
+  /**
+   * Returns whether a timer associated with a telemetry histogram is currently
+   * running. Similarly to @see{TelemetryStopwatch.running} the timer and its
+   * key can be associated with an object. Each key may have multiple associated
+   * objects and each object can be associated with multiple keys.
+   *
+   * @param {String} aHistogram - a string which must be a valid histogram name.
+   *
+   * @param {String} aKey - a string which must be a valid histgram key.
+   *
+   * @param {Object} aObj - Optional parameter. If specified, the timer is
+   *                        associated with this object, meaning that multiple
+   *                        timers for the same histogram may be run
+   *                        concurrently, as long as they are associated with
+   *                        different objects.
+   *
+   * @returns {Boolean} True if the timer exists and is currently running.
+   */
+  runningKeyed(aHistogram, aKey, aObj) {
+    return TelemetryStopwatchImpl.running(aHistogram, aObj, aKey);
+  },
+
+  /**
+   * Deletes the timer associated with a keyed histogram. Important: Only use
+   * this method when a legitimate cancellation should be done.
+   *
+   * @param {String} aHistogram - a string which must be a valid histogram name.
+   *
+   * @param {String} aKey - a string which must be a valid histgram key.
+   *
+   * @param {Object} aObj - Optional parameter. If specified, the timer
+   *                        associated with this object is deleted.
+   *
+   * @return {Boolean} True if the timer exist and it was cleared, False
+   *                   otherwise.
+   */
+  cancelKeyed(aHistogram, aKey, aObj) {
+    return TelemetryStopwatchImpl.cancel(aHistogram, aObj, aKey);
+  },
+
+  /**
+   * Returns the elapsed time for a particular stopwatch. Primarily for
+   * debugging purposes. Must be called prior to finish.
+   *
+   * @param {String} aHistogram - a string which must be a valid histogram name.
+   *
+   * @param {String} aKey - a string which must be a valid histgram key.
+   *
+   * @param {Object} aObj - Optional parameter. If specified, the timer
+   *                        associated with this object is used to calculate
+   *                        the elapsed time.
+   *
+   * @return {Integer} time in milliseconds or -1 if the stopwatch was not
+   *                   found.
+   */
+  timeElapsedKeyed(aHistogram, aKey, aObj, aCanceledOkay) {
+    return TelemetryStopwatchImpl.timeElapsed(aHistogram, aObj, aKey,
+                                              aCanceledOkay);
+  },
+
+  /**
+   * Stops the timer associated with the given keyed histogram (and object),
+   * calculates the time delta between start and finish, and adds the value
+   * to the keyed histogram.
+   *
+   * @param {String} aHistogram - a string which must be a valid histogram name.
+   *
+   * @param {String} aKey - a string which must be a valid histgram key.
+   *
+   * @param {Object} aObj - optional parameter which associates the histogram
+   *                        timer with the given object.
+   *
+   * @param {Boolean} aCanceledOkay - Optional parameter which will suppress any
+   *                                  warnings that normally fire when a stopwatch
+   *                                  is finished after being cancelled. Defaults
+   *                                  to false.
+   *
+   * @returns {Boolean} True if the timer was succesfully stopped and the data
+   *                   was added to the histogram, False otherwise.
+   */
+  finishKeyed(aHistogram, aKey, aObj, aCanceledOkay) {
+    return TelemetryStopwatchImpl.finish(aHistogram, aObj, aKey, aCanceledOkay);
+  },
+
+  /**
+   * Set the testing mode. Used by tests.
+   */
+  setTestModeEnabled(testing) {
+    TelemetryStopwatchImpl.suppressErrors(true);
+  },
+};
+
+var TelemetryStopwatchImpl = {
+  // Suppress errors. Used when testing.
+  _suppressErrors: false,
+
+  suppressErrors(suppress) {
+    this._suppressErrors = suppress;
+  },
+
+  start(histogram, object, key) {
+    if (Timers.has(histogram, object, key)) {
+      Timers.delete(histogram, object, key);
+      if (!this._suppressErrors) {
+        Cu.reportError(`TelemetryStopwatch: key "${histogram}" was already ` +
+                       "initialized");
+      }
+      return false;
+    }
+
+    return Timers.put(histogram, object, key, Cu.now());
+  },
+
+  running(histogram, object, key) {
+    return Timers.has(histogram, object, key);
+  },
+
+  cancel(histogram, object, key) {
+    return Timers.delete(histogram, object, key);
+  },
+
+  timeElapsed(histogram, object, key, aCanceledOkay) {
+    const startTime = Timers.get(histogram, object, key);
+    if (startTime === null) {
+      if (!aCanceledOkay && !this._suppressErrors) {
+        Cu.reportError("TelemetryStopwatch: requesting elapsed time for " +
+                       `nonexisting stopwatch. Histogram: "${histogram}", ` +
+                       `key: "${key}"`);
+      }
+      return -1;
+    }
+
+    try {
+      const delta = Cu.now() - startTime;
+      return Math.round(delta / 1000);
+    } catch (e) {
+      if (!this._suppressErrors) {
+        Cu.reportError("TelemetryStopwatch: failed to calculate elapsed time " +
+                       `for Histogram: "${histogram}", key: "${key}", ` +
+                       `exception: ${Log.exceptionStr(e)}`);
+      }
+      return -1;
+    }
+  },
+
+  finish(histogram, object, key, aCanceledOkay) {
+    const delta = this.timeElapsed(histogram, object, key, aCanceledOkay);
+    if (delta == -1) {
+      return false;
+    }
+
+    try {
+      if (key) {
+        Services.telemetry.getKeyedHistogramById(histogram).add(key, delta);
+      } else {
+        Services.telemetry.getHistogramById(histogram).add(delta);
+      }
+    } catch (e) {
+      if (!this._suppressErrors) {
+        Cu.reportError("TelemetryStopwatch: failed to update the Histogram " +
+                       `"${histogram}", using key: "${key}", ` +
+                       `exception: ${Log.exceptionStr(e)}`);
+      }
+      return false;
+    }
+
+    return Timers.delete(histogram, object, key);
+  }
+};
--- a/devtools/client/shared/moz.build
+++ b/devtools/client/shared/moz.build
@@ -44,16 +44,17 @@ DevToolsModules(
     'prefs.js',
     'react-utils.js',
     'scroll.js',
     'source-utils.js',
     'SplitView.jsm',
     'stylesheet-utils.js',
     'suggestion-picker.js',
     'telemetry.js',
+    'TelemetryStopwatch.jsm',
     'theme.js',
     'undo.js',
     'unicode-url.js',
     'view-source.js',
     'webgl-utils.js',
     'zoom-keys.js',
 )
 
--- a/devtools/client/shared/telemetry.js
+++ b/devtools/client/shared/telemetry.js
@@ -6,17 +6,17 @@
  * This is the telemetry module to report metrics for tools.
  *
  * Comprehensive documentation is in docs/frontend/telemetry.md
  */
 
 "use strict";
 
 const Services = require("Services");
-const { TelemetryStopwatch } = require("resource://gre/modules/TelemetryStopwatch.jsm");
+const { TelemetryStopwatch } = require("devtools/client/shared/TelemetryStopwatch.jsm");
 const { getNthPathExcluding } = require("devtools/shared/platform/stack");
 const { TelemetryEnvironment } = require("resource://gre/modules/TelemetryEnvironment.jsm");
 
 // Object to be shared among all instances.
 const PENDING_EVENTS = new Map();
 const PENDING_EVENT_PROPERTIES = new Map();
 
 class Telemetry {
--- a/devtools/shared/css/generated/properties-db.js
+++ b/devtools/shared/css/generated/properties-db.js
@@ -4879,16 +4879,17 @@ exports.CSS_PROPERTIES = {
       "ellipse",
       "fill-box",
       "inherit",
       "initial",
       "inset",
       "margin-box",
       "none",
       "padding-box",
+      "path",
       "polygon",
       "stroke-box",
       "unset",
       "url",
       "view-box"
     ]
   },
   "clip-rule": {
@@ -8264,16 +8265,17 @@ exports.CSS_PROPERTIES = {
       "ellipse",
       "inherit",
       "initial",
       "inset",
       "linear-gradient",
       "margin-box",
       "none",
       "padding-box",
+      "path",
       "polygon",
       "radial-gradient",
       "repeating-linear-gradient",
       "repeating-radial-gradient",
       "unset",
       "url"
     ]
   },
--- a/devtools/shared/security/socket.js
+++ b/devtools/shared/security/socket.js
@@ -480,17 +480,16 @@ SocketListener.prototype = {
     }
     return Cc["@mozilla.org/network/server-socket;1"]
            .createInstance(Ci.nsIServerSocket);
   },
 
   async _setAdditionalSocketOptions() {
     if (this.encryption) {
       this._socket.serverCert = await cert.local.getOrCreate();
-      this._socket.setSessionCache(false);
       this._socket.setSessionTickets(false);
       const requestCert = Ci.nsITLSServerSocket.REQUEST_NEVER;
       this._socket.setRequestClientCertificate(requestCert);
     }
     this.authenticator.augmentSocketOptions(this, this._socket);
   },
 
   /**
--- a/devtools/shared/transport/local-transport.js
+++ b/devtools/shared/transport/local-transport.js
@@ -6,18 +6,16 @@
 
 /* global uneval */
 
 const { CC } = require("chrome");
 const DevToolsUtils = require("devtools/shared/DevToolsUtils");
 const { dumpn } = DevToolsUtils;
 const flags = require("devtools/shared/flags");
 const StreamUtils = require("devtools/shared/transport/stream-utils");
-const promise = require("promise");
-const defer = require("devtools/shared/defer");
 
 loader.lazyGetter(this, "Pipe", () => {
   return CC("@mozilla.org/pipe;1", "nsIPipe", "init");
 });
 
 /**
  * An adapter that handles data transfers between the debugger client and
  * server when they both run in the same process. It presents the same API as
@@ -79,73 +77,70 @@ LocalDebuggerTransport.prototype = {
    * done with it.
    */
   startBulkSend: function({actor, type, length}) {
     const serial = this._serial.count++;
 
     dumpn("Sent bulk packet " + serial + " for actor " + actor);
     if (!this.other) {
       const error = new Error("startBulkSend: other side of transport missing");
-      return promise.reject(error);
+      return Promise.reject(error);
     }
 
     const pipe = new Pipe(true, true, 0, 0, null);
 
     DevToolsUtils.executeSoon(DevToolsUtils.makeInfallible(() => {
       dumpn("Received bulk packet " + serial);
       if (!this.other.hooks) {
         return;
       }
 
       // Receiver
-      const deferred = defer();
-      const packet = {
-        actor: actor,
-        type: type,
-        length: length,
-        copyTo: (output) => {
-          const copying =
-          StreamUtils.copyStream(pipe.inputStream, output, length);
-          deferred.resolve(copying);
-          return copying;
-        },
-        stream: pipe.inputStream,
-        done: deferred
-      };
+      new Promise((receiverResolve) => {
+        const packet = {
+          actor: actor,
+          type: type,
+          length: length,
+          copyTo: (output) => {
+            const copying =
+            StreamUtils.copyStream(pipe.inputStream, output, length);
+            receiverResolve(copying);
+            return copying;
+          },
+          stream: pipe.inputStream,
+          done: receiverResolve
+        };
 
-      this.other.hooks.onBulkPacket(packet);
-
+        this.other.hooks.onBulkPacket(packet);
+      })
       // Await the result of reading from the stream
-      deferred.promise.then(() => pipe.inputStream.close(), this.close);
+      .then(() => pipe.inputStream.close(), this.close);
     }, "LocalDebuggerTransport instance's this.other.hooks.onBulkPacket"));
 
     // Sender
-    const sendDeferred = defer();
-
-    // The remote transport is not capable of resolving immediately here, so we
-    // shouldn't be able to either.
-    DevToolsUtils.executeSoon(() => {
-      const copyDeferred = defer();
-
-      sendDeferred.resolve({
-        copyFrom: (input) => {
-          const copying =
-          StreamUtils.copyStream(input, pipe.outputStream, length);
-          copyDeferred.resolve(copying);
-          return copying;
-        },
-        stream: pipe.outputStream,
-        done: copyDeferred
+    return new Promise((senderResolve) => {
+      // The remote transport is not capable of resolving immediately here, so we
+      // shouldn't be able to either.
+      DevToolsUtils.executeSoon(() => {
+        return new Promise((copyResolve) => {
+          senderResolve({
+            copyFrom: (input) => {
+              const copying =
+              StreamUtils.copyStream(input, pipe.outputStream, length);
+              copyResolve(copying);
+              return copying;
+            },
+            stream: pipe.outputStream,
+            done: copyResolve
+          });
+        })
+        // Await the result of writing to the stream
+        .then(() => pipe.outputStream.close(), this.close);
       });
-
-      // Await the result of writing to the stream
-      copyDeferred.promise.then(() => pipe.outputStream.close(), this.close);
     });
-
-    return sendDeferred.promise;
   },
 
   /**
    * Close the transport.
    */
   close: function() {
     if (this.other) {
       // Remove the reference to the other endpoint before calling close(), to
--- a/devtools/shared/transport/packets.js
+++ b/devtools/shared/transport/packets.js
@@ -24,17 +24,16 @@
  *     Called to clean up at the end of use
  */
 
 const { Cc, Ci } = require("chrome");
 const DevToolsUtils = require("devtools/shared/DevToolsUtils");
 const { dumpn, dumpv } = DevToolsUtils;
 const flags = require("devtools/shared/flags");
 const StreamUtils = require("devtools/shared/transport/stream-utils");
-const defer = require("devtools/shared/defer");
 
 DevToolsUtils.defineLazyGetter(this, "unicodeConverter", () => {
   // eslint-disable-next-line no-shadow
   const unicodeConverter = Cc["@mozilla.org/intl/scriptableunicodeconverter"]
                            .createInstance(Ci.nsIScriptableUnicodeConverter);
   unicodeConverter.charset = "UTF-8";
   return unicodeConverter;
 });
@@ -226,17 +225,21 @@ exports.JSONPacket = JSONPacket;
  * packet's type.  See the Remote Debugging Protocol Stream Transport spec for
  * more details.
  * @param transport DebuggerTransport
  *        The transport instance that will own the packet.
  */
 function BulkPacket(transport) {
   Packet.call(this, transport);
   this._done = false;
-  this._readyForWriting = defer();
+  let _resolve;
+  this._readyForWriting = new Promise((resolve) => {
+    _resolve = resolve;
+  });
+  this._readyForWriting.resolve = _resolve;
 }
 
 /**
  * Attempt to initialize a new BulkPacket based on the incoming packet header
  * we've received so far.
  * @param header string
  *        The packet header string to attempt parsing.
  * @param transport DebuggerTransport
@@ -266,34 +269,33 @@ BulkPacket.HEADER_PATTERN = /^bulk ([^: 
 BulkPacket.prototype = Object.create(Packet.prototype);
 
 BulkPacket.prototype.read = function(stream) {
   dumpv("Reading bulk packet, handing off input stream");
 
   // Temporarily pause monitoring of the input stream
   this._transport.pauseIncoming();
 
-  const deferred = defer();
-
-  this._transport._onBulkReadReady({
-    actor: this.actor,
-    type: this.type,
-    length: this.length,
-    copyTo: (output) => {
-      dumpv("CT length: " + this.length);
-      const copying = StreamUtils.copyStream(stream, output, this.length);
-      deferred.resolve(copying);
-      return copying;
-    },
-    stream: stream,
-    done: deferred
-  });
-
-  // Await the result of reading from the stream
-  deferred.promise.then(() => {
+  new Promise((resolve) => {
+    this._transport._onBulkReadReady({
+      actor: this.actor,
+      type: this.type,
+      length: this.length,
+      copyTo: (output) => {
+        dumpv("CT length: " + this.length);
+        const copying = StreamUtils.copyStream(stream, output, this.length);
+        resolve(copying);
+        return copying;
+      },
+      stream: stream,
+      done: resolve
+    });
+    // Await the result of reading from the stream
+  })
+  .then(() => {
     dumpv("onReadDone called, ending bulk mode");
     this._done = true;
     this._transport.resumeIncoming();
   }, this._transport.close);
 
   // Ensure this is only done once
   this.read = () => {
     throw new Error("Tried to read() a BulkPacket's stream multiple times.");
@@ -319,45 +321,44 @@ BulkPacket.prototype.write = function(st
     return;
   }
 
   dumpv("Handing off output stream");
 
   // Temporarily pause the monitoring of the output stream
   this._transport.pauseOutgoing();
 
-  const deferred = defer();
-
-  this._readyForWriting.resolve({
-    copyFrom: (input) => {
-      dumpv("CF length: " + this.length);
-      const copying = StreamUtils.copyStream(input, stream, this.length);
-      deferred.resolve(copying);
-      return copying;
-    },
-    stream: stream,
-    done: deferred
-  });
-
-  // Await the result of writing to the stream
-  deferred.promise.then(() => {
+  new Promise((resolve) => {
+    this._readyForWriting.resolve({
+      copyFrom: (input) => {
+        dumpv("CF length: " + this.length);
+        const copying = StreamUtils.copyStream(input, stream, this.length);
+        resolve(copying);
+        return copying;
+      },
+      stream: stream,
+      done: resolve
+    });
+    // Await the result of writing to the stream
+  })
+  .then(() => {
     dumpv("onWriteDone called, ending bulk mode");
     this._done = true;
     this._transport.resumeOutgoing();
   }, this._transport.close);
 
   // Ensure this is only done once
   this.write = () => {
     throw new Error("Tried to write() a BulkPacket's stream multiple times.");
   };
 };
 
 Object.defineProperty(BulkPacket.prototype, "streamReadyForWriting", {
   get: function() {
-    return this._readyForWriting.promise;
+    return this._readyForWriting;
   }
 });
 
 Object.defineProperty(BulkPacket.prototype, "header", {
   get: function() {
     return {
       actor: this.actor,
       type: this.type,
--- a/devtools/shared/transport/stream-utils.js
+++ b/devtools/shared/transport/stream-utils.js
@@ -4,17 +4,16 @@
 
 "use strict";
 
 const { Ci, Cc, Cr, CC } = require("chrome");
 const Services = require("Services");
 const DevToolsUtils = require("devtools/shared/DevToolsUtils");
 const { dumpv } = DevToolsUtils;
 const EventEmitter = require("devtools/shared/event-emitter");
-const defer = require("devtools/shared/defer");
 
 DevToolsUtils.defineLazyGetter(this, "IOUtil", () => {
   return Cc["@mozilla.org/io-util;1"].getService(Ci.nsIIOUtil);
 });
 
 DevToolsUtils.defineLazyGetter(this, "ScriptableInputStream", () => {
   return CC("@mozilla.org/scriptableinputstream;1",
             "nsIScriptableInputStream", "init");
@@ -70,27 +69,34 @@ function StreamCopier(input, output, len
     this.output = output;
   } else {
     this.output = Cc["@mozilla.org/network/buffered-output-stream;1"]
                   .createInstance(Ci.nsIBufferedOutputStream);
     this.output.init(output, BUFFER_SIZE);
   }
   this._length = length;
   this._amountLeft = length;
-  this._deferred = defer();
+  let _resolve;
+  let _reject;
+  this._deferred = new Promise((resolve, reject) => {
+    _resolve = resolve;
+    _reject = reject;
+  });
+  this._deferred.resolve = _resolve;
+  this._deferred.reject = _reject;
 
   this._copy = this._copy.bind(this);
   this._flush = this._flush.bind(this);
   this._destroy = this._destroy.bind(this);
 
   // Copy promise's then method up to this object.
   // Allows the copier to offer a promise interface for the simple succeed or
   // fail scenarios, but also emit events (due to the EventEmitter) for other
   // states, like progress.
-  this.then = this._deferred.promise.then.bind(this._deferred.promise);
+  this.then = this._deferred.then.bind(this._deferred);
   this.then(this._destroy, this._destroy);
 
   // Stream ready callback starts as |_copy|, but may switch to |_flush| at end
   // if flushing would block the output stream.
   this._streamReadyCallback = this._copy;
 }
 StreamCopier._nextId = 0;
 
--- a/devtools/shared/transport/tests/unit/head_dbg.js
+++ b/devtools/shared/transport/tests/unit/head_dbg.js
@@ -1,23 +1,22 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 "use strict";
 
-/* exported Cr, CC, NetUtil, defer, errorCount, initTestDebuggerServer,
+/* exported Cr, CC, NetUtil, errorCount, initTestDebuggerServer,
             writeTestTempFile, socket_transport, local_transport, really_long
 */
 
 var CC = Components.Constructor;
 
 const { require } =
   ChromeUtils.import("resource://devtools/shared/Loader.jsm", {});
 const { NetUtil } = require("resource://gre/modules/NetUtil.jsm");
-const promise = require("promise");
 const defer = require("devtools/shared/defer");
 
 const Services = require("Services");
 
 // We do not want to log packets by default, because in some tests,
 // we can be sending large amounts of data. The test harness has
 // trouble dealing with logging all the data, and we end up with
 // intermittent time outs (e.g. bug 775924).
@@ -143,17 +142,17 @@ var socket_transport = async function() 
     await debuggerListener.open();
   }
   const port = DebuggerServer._listeners[0].port;
   info("Debugger server port is " + port);
   return DebuggerClient.socketConnect({ host: "127.0.0.1", port });
 };
 
 function local_transport() {
-  return promise.resolve(DebuggerServer.connectPipe());
+  return Promise.resolve(DebuggerServer.connectPipe());
 }
 
 /** * Sample Data ***/
 
 var gReallyLong;
 function really_long() {
   if (gReallyLong) {
     return gReallyLong;
--- a/devtools/shared/transport/tests/unit/test_bulk_error.js
+++ b/devtools/shared/transport/tests/unit/test_bulk_error.js
@@ -69,27 +69,26 @@ function json_reply(client, response) {
 
   const request = client.startBulkRequest({
     actor: response.testBulk,
     type: "jsonReply",
     length: reallyLong.length
   });
 
   // Send bulk data to server
-  const copyDeferred = defer();
-  request.on("bulk-send-ready", ({writer, done}) => {
-    const input = Cc["@mozilla.org/io/string-input-stream;1"]
-                  .createInstance(Ci.nsIStringInputStream);
-    input.setData(reallyLong, reallyLong.length);
-    try {
-      writer.copyFrom(input, () => {
-        input.close();
-        done();
-      });
-      do_throw(new Error("Copying should fail, the stream is not async."));
-    } catch (e) {
-      Assert.ok(true);
-      copyDeferred.resolve();
-    }
+  return new Promise((resolve) => {
+    request.on("bulk-send-ready", ({writer, done}) => {
+      const input = Cc["@mozilla.org/io/string-input-stream;1"]
+                    .createInstance(Ci.nsIStringInputStream);
+      input.setData(reallyLong, reallyLong.length);
+      try {
+        writer.copyFrom(input, () => {
+          input.close();
+          done();
+        });
+        do_throw(new Error("Copying should fail, the stream is not async."));
+      } catch (e) {
+        Assert.ok(true);
+        resolve();
+      }
+    });
   });
-
-  return copyDeferred.promise;
 }
--- a/devtools/shared/transport/tests/unit/test_client_server_bulk.js
+++ b/devtools/shared/transport/tests/unit/test_client_server_bulk.js
@@ -100,77 +100,88 @@ function add_test_bulk_actor() {
 }
 
 /** * Reply Handlers ***/
 
 var replyHandlers = {
 
   json: function(request) {
     // Receive JSON reply from server
-    const replyDeferred = defer();
-    request.on("json-reply", (reply) => {
-      Assert.ok(reply.allDone);
-      replyDeferred.resolve();
+    return new Promise((resolve) => {
+      request.on("json-reply", (reply) => {
+        Assert.ok(reply.allDone);
+        resolve();
+      });
     });
-    return replyDeferred.promise;
   },
 
   bulk: function(request) {
     // Receive bulk data reply from server
-    const replyDeferred = defer();
-    request.on("bulk-reply", ({length, copyTo}) => {
-      Assert.equal(length, really_long().length);
+    return new Promise((resolve) => {
+      request.on("bulk-reply", ({length, copyTo}) => {
+        Assert.equal(length, really_long().length);
+
+        const outputFile = getTestTempFile("bulk-output", true);
+        outputFile.create(Ci.nsIFile.NORMAL_FILE_TYPE, parseInt("666", 8));
 
-      const outputFile = getTestTempFile("bulk-output", true);
-      outputFile.create(Ci.nsIFile.NORMAL_FILE_TYPE, parseInt("666", 8));
+        const output = FileUtils.openSafeFileOutputStream(outputFile);
 
-      const output = FileUtils.openSafeFileOutputStream(outputFile);
-
-      copyTo(output).then(() => {
-        FileUtils.closeSafeFileOutputStream(output);
-        replyDeferred.resolve(verify_files());
+        copyTo(output).then(() => {
+          FileUtils.closeSafeFileOutputStream(output);
+          resolve(verify_files());
+        });
       });
     });
-    return replyDeferred.promise;
   }
 
 };
 
 /** * Tests ***/
 
 var test_bulk_request_cs = async function(transportFactory, actorType, replyType) {
   // Ensure test files are not present from a failed run
   cleanup_files();
   writeTestTempFile("bulk-input", really_long());
 
-  const clientDeferred = defer();
-  const serverDeferred = defer();
-  const bulkCopyDeferred = defer();
+  let clientResolve;
+  const clientDeferred = new Promise((resolve) => {
+    clientResolve = resolve;
+  });
+
+  let serverResolve;
+  const serverDeferred = new Promise((resolve) => {
+    serverResolve = resolve;
+  });
+
+  let bulkCopyResolve;
+  const bulkCopyDeferred = new Promise((resolve) => {
+    bulkCopyResolve = resolve;
+  });
 
   const transport = await transportFactory();
 
   const client = new DebuggerClient(transport);
   client.connect().then(([app, traits]) => {
     Assert.equal(traits.bulk, true);
-    client.listTabs().then(clientDeferred.resolve);
+    client.listTabs().then(clientResolve);
   });
 
   function bulkSendReadyCallback({copyFrom}) {
     NetUtil.asyncFetch({
       uri: NetUtil.newURI(getTestTempFile("bulk-input")),
       loadUsingSystemPrincipal: true
     }, input => {
       copyFrom(input).then(() => {
         input.close();
-        bulkCopyDeferred.resolve();
+        bulkCopyResolve();
       });
     });
   }
 
-  clientDeferred.promise.then(response => {
+  clientDeferred.then(response => {
     const request = client.startBulkRequest({
       actor: response.testBulk,
       type: actorType,
       length: really_long().length
     });
 
     // Send bulk data to server
     request.on("bulk-send-ready", bulkSendReadyCallback);
@@ -179,93 +190,99 @@ var test_bulk_request_cs = async functio
     replyHandlers[replyType](request).then(() => {
       client.close();
       transport.close();
     });
   }).catch(do_throw);
 
   DebuggerServer.on("connectionchange", type => {
     if (type === "closed") {
-      serverDeferred.resolve();
+      serverResolve();
     }
   });
 
-  return promise.all([
-    clientDeferred.promise,
-    bulkCopyDeferred.promise,
-    serverDeferred.promise
+  return Promise.all([
+    clientDeferred,
+    bulkCopyDeferred,
+    serverDeferred
   ]);
 };
 
 var test_json_request_cs = async function(transportFactory, actorType, replyType) {
   // Ensure test files are not present from a failed run
   cleanup_files();
   writeTestTempFile("bulk-input", really_long());
 
-  const clientDeferred = defer();
-  const serverDeferred = defer();
+  let clientResolve;
+  const clientDeferred = new Promise((resolve) => {
+    clientResolve = resolve;
+  });
+
+  let serverResolve;
+  const serverDeferred = new Promise((resolve) => {
+    serverResolve = resolve;
+  });
 
   const transport = await transportFactory();
 
   const client = new DebuggerClient(transport);
   client.connect((app, traits) => {
     Assert.equal(traits.bulk, true);
-    client.listTabs().then(clientDeferred.resolve);
+    client.listTabs().then(clientResolve);
   });
 
-  clientDeferred.promise.then(response => {
+  clientDeferred.then(response => {
     const request = client.request({
       to: response.testBulk,
       type: actorType
     });
 
     // Set up reply handling for this type
     replyHandlers[replyType](request).then(() => {
       client.close();
       transport.close();
     });
   }).catch(do_throw);
 
   DebuggerServer.on("connectionchange", type => {
     if (type === "closed") {
-      serverDeferred.resolve();
+      serverResolve();
     }
   });
 
-  return promise.all([
-    clientDeferred.promise,
-    serverDeferred.promise
+  return Promise.all([
+    clientDeferred,
+    serverDeferred
   ]);
 };
 
 /** * Test Utils ***/
 
 function verify_files() {
   const reallyLong = really_long();
 
   const inputFile = getTestTempFile("bulk-input");
   const outputFile = getTestTempFile("bulk-output");
 
   Assert.equal(inputFile.fileSize, reallyLong.length);
   Assert.equal(outputFile.fileSize, reallyLong.length);
 
   // Ensure output file contents actually match
-  const compareDeferred = defer();
-  NetUtil.asyncFetch({
-    uri: NetUtil.newURI(getTestTempFile("bulk-output")),
-    loadUsingSystemPrincipal: true
-  }, input => {
-    const outputData = NetUtil.readInputStreamToString(input, reallyLong.length);
-      // Avoid do_check_eq here so we don't log the contents
-    Assert.ok(outputData === reallyLong);
-    input.close();
-    compareDeferred.resolve();
-  });
-
-  return compareDeferred.promise.then(cleanup_files);
+  return new Promise((resolve) => {
+    NetUtil.asyncFetch({
+      uri: NetUtil.newURI(getTestTempFile("bulk-output")),
+      loadUsingSystemPrincipal: true
+    }, input => {
+      const outputData = NetUtil.readInputStreamToString(input, reallyLong.length);
+        // Avoid do_check_eq here so we don't log the contents
+      Assert.ok(outputData === reallyLong);
+      input.close();
+      resolve();
+    });
+  }).then(cleanup_files);
 }
 
 function cleanup_files() {
   const inputFile = getTestTempFile("bulk-input", true);
   if (inputFile.exists()) {
     inputFile.remove(false);
   }
 
--- a/devtools/shared/transport/tests/unit/test_dbgsocket.js
+++ b/devtools/shared/transport/tests/unit/test_dbgsocket.js
@@ -45,37 +45,37 @@ async function test_socket_conn() {
     port: gPort
   });
 
   // Assert that connection settings are available on transport object
   const settings = transport.connectionSettings;
   Assert.equal(settings.host, "127.0.0.1");
   Assert.equal(settings.port, gPort);
 
-  const closedDeferred = defer();
-  transport.hooks = {
-    onPacket: function(packet) {
-      this.onPacket = function({unicode}) {
-        Assert.equal(unicode, unicodeString);
-        transport.close();
-      };
-      // Verify that things work correctly when bigger than the output
-      // transport buffers and when transporting unicode...
-      transport.send({to: "root",
-                      type: "echo",
-                      reallylong: really_long(),
-                      unicode: unicodeString});
-      Assert.equal(packet.from, "root");
-    },
-    onClosed: function(status) {
-      closedDeferred.resolve();
-    },
-  };
-  transport.ready();
-  return closedDeferred.promise;
+  return new Promise((resolve) => {
+    transport.hooks = {
+      onPacket: function(packet) {
+        this.onPacket = function({unicode}) {
+          Assert.equal(unicode, unicodeString);
+          transport.close();
+        };
+        // Verify that things work correctly when bigger than the output
+        // transport buffers and when transporting unicode...
+        transport.send({to: "root",
+                        type: "echo",
+                        reallylong: really_long(),
+                        unicode: unicodeString});
+        Assert.equal(packet.from, "root");
+      },
+      onClosed: function(status) {
+        resolve();
+      },
+    };
+    transport.ready();
+  });
 }
 
 async function test_socket_shutdown() {
   Assert.equal(DebuggerServer.listeningSockets, 2);
   gExtraListener.close();
   Assert.equal(DebuggerServer.listeningSockets, 1);
   Assert.ok(DebuggerServer.closeAllListeners());
   Assert.equal(DebuggerServer.listeningSockets, 0);
--- a/devtools/shared/transport/tests/unit/test_dbgsocket_connection_drop.js
+++ b/devtools/shared/transport/tests/unit/test_dbgsocket_connection_drop.js
@@ -55,28 +55,28 @@ var test_helper = async function(payload
   listener.portOrPath = -1;
   listener.authenticator = authenticator;
   listener.open();
 
   const transport = await DebuggerClient.socketConnect({
     host: "127.0.0.1",
     port: listener.port
   });
-  const closedDeferred = defer();
-  transport.hooks = {
-    onPacket: function(packet) {
-      this.onPacket = function() {
-        do_throw(new Error("This connection should be dropped."));
-        transport.close();
-      };
+  return new Promise((resolve) => {
+    transport.hooks = {
+      onPacket: function(packet) {
+        this.onPacket = function() {
+          do_throw(new Error("This connection should be dropped."));
+          transport.close();
+        };
 
-      // Inject the payload directly into the stream.
-      transport._outgoing.push(new RawPacket(transport, payload));
-      transport._flushOutgoing();
-    },
-    onClosed: function(status) {
-      Assert.ok(true);
-      closedDeferred.resolve();
-    },
-  };
-  transport.ready();
-  return closedDeferred.promise;
+        // Inject the payload directly into the stream.
+        transport._outgoing.push(new RawPacket(transport, payload));
+        transport._flushOutgoing();
+      },
+      onClosed: function(status) {
+        Assert.ok(true);
+        resolve();
+      },
+    };
+    transport.ready();
+  });
 };
--- a/devtools/shared/transport/tests/unit/test_queue.js
+++ b/devtools/shared/transport/tests/unit/test_queue.js
@@ -20,18 +20,25 @@ function run_test() {
   });
 
   run_next_test();
 }
 
 /** * Tests ***/
 
 var test_transport = async function(transportFactory) {
-  const clientDeferred = defer();
-  const serverDeferred = defer();
+  let clientResolve;
+  const clientDeferred = new Promise((resolve) => {
+    clientResolve = resolve;
+  });
+
+  let serverResolve;
+  const serverDeferred = new Promise((resolve) => {
+    serverResolve = resolve;
+  });
 
   // Ensure test files are not present from a failed run
   cleanup_files();
   const reallyLong = really_long();
   writeTestTempFile("bulk-input", reallyLong);
 
   Assert.equal(Object.keys(DebuggerServer._connections).length, 0);
 
@@ -61,17 +68,17 @@ var test_transport = async function(tran
     const output = FileUtils.openSafeFileOutputStream(outputFile);
 
     copyTo(output).then(() => {
       FileUtils.closeSafeFileOutputStream(output);
       return verify();
     }).then(() => {
       // It's now safe to close
       transport.hooks.onClosed = () => {
-        clientDeferred.resolve();
+        clientResolve();
       };
       transport.close();
     });
   }
 
   // Client
 
   function send_packets() {
@@ -109,17 +116,17 @@ var test_transport = async function(tran
       Assert.equal(Object.keys(DebuggerServer._connections).length, 1);
       info(Object.keys(DebuggerServer._connections));
       for (const connId in DebuggerServer._connections) {
         DebuggerServer._connections[connId].onBulkPacket = on_bulk_packet;
       }
 
       DebuggerServer.on("connectionchange", type => {
         if (type === "closed") {
-          serverDeferred.resolve();
+          serverResolve();
         }
       });
 
       send_packets();
     },
 
     onError: function(packet) {
       // The explode actor doesn't exist
@@ -129,44 +136,44 @@ var test_transport = async function(tran
 
     onClosed: function() {
       do_throw("Transport closed before we expected");
     }
   };
 
   transport.ready();
 
-  return promise.all([clientDeferred.promise, serverDeferred.promise]);
+  return Promise.all([clientDeferred, serverDeferred]);
 };
 
 /** * Test Utils ***/
 
 function verify() {
   const reallyLong = really_long();
 
   const inputFile = getTestTempFile("bulk-input");
   const outputFile = getTestTempFile("bulk-output");
 
   Assert.equal(inputFile.fileSize, reallyLong.length);
   Assert.equal(outputFile.fileSize, reallyLong.length);
 
   // Ensure output file contents actually match
-  const compareDeferred = defer();
-  NetUtil.asyncFetch({
-    uri: NetUtil.newURI(getTestTempFile("bulk-output")),
-    loadUsingSystemPrincipal: true
-  }, input => {
-    const outputData = NetUtil.readInputStreamToString(input, reallyLong.length);
+  return new Promise((resolve) => {
+    NetUtil.asyncFetch({
+      uri: NetUtil.newURI(getTestTempFile("bulk-output")),
+      loadUsingSystemPrincipal: true
+    }, input => {
+      const outputData = NetUtil.readInputStreamToString(input, reallyLong.length);
       // Avoid do_check_eq here so we don't log the contents
-    Assert.ok(outputData === reallyLong);
-    input.close();
-    compareDeferred.resolve();
-  });
-
-  return compareDeferred.promise.then(cleanup_files);
+      Assert.ok(outputData === reallyLong);
+      input.close();
+      resolve();
+    });
+  })
+  .then(cleanup_files);
 }
 
 function cleanup_files() {
   const inputFile = getTestTempFile("bulk-input", true);
   if (inputFile.exists()) {
     inputFile.remove(false);
   }
 
--- a/devtools/shared/transport/tests/unit/test_transport_bulk.js
+++ b/devtools/shared/transport/tests/unit/test_transport_bulk.js
@@ -20,18 +20,25 @@ function run_test() {
 /** * Tests ***/
 
 /**
  * This tests a one-way bulk transfer at the transport layer.
  */
 var test_bulk_transfer_transport = async function(transportFactory) {
   info("Starting bulk transfer test at " + new Date().toTimeString());
 
-  const clientDeferred = defer();
-  const serverDeferred = defer();
+  let clientResolve;
+  const clientDeferred = new Promise((resolve) => {
+    clientResolve = resolve;
+  });
+
+  let serverResolve;
+  const serverDeferred = new Promise((resolve) => {
+    serverResolve = resolve;
+  });
 
   // Ensure test files are not present from a failed run
   cleanup_files();
   const reallyLong = really_long();
   writeTestTempFile("bulk-input", reallyLong);
 
   Assert.equal(Object.keys(DebuggerServer._connections).length, 0);
 
@@ -61,17 +68,17 @@ var test_bulk_transfer_transport = async
     const output = FileUtils.openSafeFileOutputStream(outputFile);
 
     copyTo(output).then(() => {
       FileUtils.closeSafeFileOutputStream(output);
       return verify();
     }).then(() => {
       // It's now safe to close
       transport.hooks.onClosed = () => {
-        clientDeferred.resolve();
+        clientResolve();
       };
       transport.close();
     });
   }
 
   // Client
   transport.hooks = {
     onPacket: function(packet) {
@@ -82,17 +89,17 @@ var test_bulk_transfer_transport = async
       Assert.equal(Object.keys(DebuggerServer._connections).length, 1);
       info(Object.keys(DebuggerServer._connections));
       for (const connId in DebuggerServer._connections) {
         DebuggerServer._connections[connId].onBulkPacket = on_bulk_packet;
       }
 
       DebuggerServer.on("connectionchange", type => {
         if (type === "closed") {
-          serverDeferred.resolve();
+          serverResolve();
         }
       });
 
       transport.startBulkSend({
         actor: "root",
         type: "file-stream",
         length: reallyLong.length
       }).then(write_data);
@@ -100,44 +107,44 @@ var test_bulk_transfer_transport = async
 
     onClosed: function() {
       do_throw("Transport closed before we expected");
     }
   };
 
   transport.ready();
 
-  return promise.all([clientDeferred.promise, serverDeferred.promise]);
+  return Promise.all([clientDeferred, serverDeferred]);
 };
 
 /** * Test Utils ***/
 
 function verify() {
   const reallyLong = really_long();
 
   const inputFile = getTestTempFile("bulk-input");
   const outputFile = getTestTempFile("bulk-output");
 
   Assert.equal(inputFile.fileSize, reallyLong.length);
   Assert.equal(outputFile.fileSize, reallyLong.length);
 
   // Ensure output file contents actually match
-  const compareDeferred = defer();
-  NetUtil.asyncFetch({
-    uri: NetUtil.newURI(getTestTempFile("bulk-output")),
-    loadUsingSystemPrincipal: true
-  }, input => {
-    const outputData = NetUtil.readInputStreamToString(input, reallyLong.length);
-      // Avoid do_check_eq here so we don't log the contents
-    Assert.ok(outputData === reallyLong);
-    input.close();
-    compareDeferred.resolve();
-  });
-
-  return compareDeferred.promise.then(cleanup_files);
+  return new Promise((resolve) => {
+    NetUtil.asyncFetch({
+      uri: NetUtil.newURI(getTestTempFile("bulk-output")),
+      loadUsingSystemPrincipal: true
+    }, input => {
+      const outputData = NetUtil.readInputStreamToString(input, reallyLong.length);
+        // Avoid do_check_eq here so we don't log the contents
+      Assert.ok(outputData === reallyLong);
+      input.close();
+      resolve();
+    });
+  })
+  .then(cleanup_files);
 }
 
 function cleanup_files() {
   const inputFile = getTestTempFile("bulk-input", true);
   if (inputFile.exists()) {
     inputFile.remove(false);
   }
 
--- a/devtools/shared/transport/tests/unit/testactors.js
+++ b/devtools/shared/transport/tests/unit/testactors.js
@@ -2,17 +2,16 @@
    http://creativecommons.org/publicdomain/zero/1.0/ */
 "use strict";
 
 const { ActorPool, appendExtraActors, createExtraActors } =
   require("devtools/server/actors/common");
 const { RootActor } = require("devtools/server/actors/root");
 const { ThreadActor } = require("devtools/server/actors/thread");
 const { DebuggerServer } = require("devtools/server/main");
-const promise = require("promise");
 
 var gTestGlobals = [];
 DebuggerServer.addTestGlobal = function(global) {
   gTestGlobals.push(global);
 };
 
 // A mock tab list, for use by tests. This simply presents each global in
 // gTestGlobals as a tab, and the list is fixed: it never calls its
@@ -42,17 +41,17 @@ function TestTabList(connection) {
   }
 
   connection.addActorPool(this._targetActorPool);
 }
 
 TestTabList.prototype = {
   constructor: TestTabList,
   getList: function() {
-    return promise.resolve([...this._targetActors]);
+    return Promise.resolve([...this._targetActors]);
   }
 };
 
 exports.createRootActor = function createRootActor(connection) {
   const root = new RootActor(connection, {
     tabList: new TestTabList(connection),
     globalActorFactories: DebuggerServer.globalActorFactories
   });
--- a/dom/canvas/WebGLContext.cpp
+++ b/dom/canvas/WebGLContext.cpp
@@ -78,17 +78,17 @@
 #include "nsCocoaFeatures.h"
 #endif
 
 #ifdef XP_WIN
 #include "WGLLibrary.h"
 #endif
 
 #if defined(MOZ_WIDGET_ANDROID)
-    #include "../../gfx/vr/gfxVRExternal.h"
+#include "mozilla/layers/ImageBridgeChild.h"
 #endif
 
 // Generated
 #include "mozilla/dom/WebGLRenderingContextBinding.h"
 
 
 namespace mozilla {
 
@@ -231,16 +231,19 @@ WebGLContext::DestroyResourcesAndContext
     mActiveProgramLinkInfo = nullptr;
     mBoundDrawFramebuffer = nullptr;
     mBoundReadFramebuffer = nullptr;
     mBoundRenderbuffer = nullptr;
     mBoundVertexArray = nullptr;
     mDefaultVertexArray = nullptr;
     mBoundTransformFeedback = nullptr;
     mDefaultTransformFeedback = nullptr;
+#if defined(MOZ_WIDGET_ANDROID)
+    mVRScreen = nullptr;
+#endif
 
     mQuerySlot_SamplesPassed = nullptr;
     mQuerySlot_TFPrimsWritten = nullptr;
     mQuerySlot_TimeElapsed = nullptr;
 
     mIndexedUniformBufferBindings.clear();
 
     if (mAvailabilityRunnable) {
@@ -783,17 +786,17 @@ WebGLContext::SetDimensions(int32_t sign
         {
             return NS_OK;
         }
 
         if (IsContextLost())
             return NS_OK;
 
         // If we've already drawn, we should commit the current buffer.
-        PresentScreenBuffer();
+        PresentScreenBuffer(gl->Screen());
 
         if (IsContextLost()) {
             GenerateWarning("WebGL context was lost due to swap failure.");
             return NS_OK;
         }
 
         // Kill our current default fb(s), for later lazy allocation.
         mRequestedSize = {width, height};
@@ -1458,30 +1461,30 @@ WebGLContext::BlitBackbufferToCurDriverF
     if (mScissorTestEnabled) {
         gl->fEnable(LOCAL_GL_SCISSOR_TEST);
     }
 }
 
 // For an overview of how WebGL compositing works, see:
 // https://wiki.mozilla.org/Platform/GFX/WebGL/Compositing
 bool
-WebGLContext::PresentScreenBuffer()
+WebGLContext::PresentScreenBuffer(GLScreenBuffer* const targetScreen)
 {
     const FuncScope funcScope(*this, "<PresentScreenBuffer>");
     if (IsContextLost())
         return false;
 
     if (!mShouldPresent)
         return false;
 
     if (!ValidateAndInitFB(nullptr))
         return false;
 
-    const auto& screen = gl->Screen();
-    if (screen->Size() != mDefaultFB->mSize &&
+    const auto& screen = targetScreen ? targetScreen : gl->Screen();
+    if ((!screen->IsReadBufferReady() || screen->Size() != mDefaultFB->mSize) &&
         !screen->Resize(mDefaultFB->mSize))
     {
         GenerateWarning("screen->Resize failed. Losing context.");
         ForceLoseContext();
         return false;
     }
 
     gl->fBindFramebuffer(LOCAL_GL_FRAMEBUFFER, 0);
@@ -1515,20 +1518,20 @@ WebGLContext::PresentScreenBuffer()
     mShouldPresent = false;
     OnEndOfFrame();
 
     return true;
 }
 
 // Prepare the context for capture before compositing
 void
-WebGLContext::BeginComposition()
+WebGLContext::BeginComposition(GLScreenBuffer* const screen)
 {
     // Present our screenbuffer, if needed.
-    PresentScreenBuffer();
+    PresentScreenBuffer(screen);
     mDrawCallsSinceLastFlush = 0;
 }
 
 // Clean up the context after captured for compositing
 void
 WebGLContext::EndComposition()
 {
     // Mark ourselves as no longer invalidated.
@@ -2296,57 +2299,50 @@ WebGLContext::GetUnpackSize(bool isFunc3
     return totalBytes;
 }
 
 
 #if defined(MOZ_WIDGET_ANDROID)
 already_AddRefed<layers::SharedSurfaceTextureClient>
 WebGLContext::GetVRFrame()
 {
-  if (!gl)
-    return nullptr;
-
-  int frameId = gfx::impl::VRDisplayExternal::sPushIndex;
-  static int lastFrameId = -1;
-  /**
-   * Android doesn't like duplicated GetVRFrame within the same gfxVRExternal frame.
-   * Ballout forced composition calls if we are in the same VRExternal push frame index.
-   * Also discard frameId 0 because sometimes compositor is not paused yet due to channel communication delays.
-   */
-  const bool ignoreFrame = lastFrameId == frameId || frameId == 0;
-  lastFrameId = frameId;
-  if (!ignoreFrame) {
-      BeginComposition();
-      EndComposition();
-  }
-
-  if (!gl) {
-    return nullptr;
-  }
-
-  gl::GLScreenBuffer* screen = gl->Screen();
-  if (!screen) {
-    return nullptr;
-  }
-
-  RefPtr<SharedSurfaceTextureClient> sharedSurface = screen->Front();
-  if (!sharedSurface || !sharedSurface->Surf()) {
-    return nullptr;
-  }
-
-  /**
-   * Make sure that the WebGL buffer is committed to the attached SurfaceTexture on Android.
-   */
-  if (!ignoreFrame) {
+    if (!gl)
+        return nullptr;
+    
+    // Create a custom GLScreenBuffer for VR.
+    if (!mVRScreen) {
+        auto caps = gl->Screen()->mCaps;
+        mVRScreen = GLScreenBuffer::Create(gl, gfx::IntSize(1, 1), caps);
+
+        RefPtr<ImageBridgeChild> imageBridge = ImageBridgeChild::GetSingleton();
+        if (imageBridge) {
+            TextureFlags flags = TextureFlags::ORIGIN_BOTTOM_LEFT;
+            UniquePtr<gl::SurfaceFactory> factory = gl::GLScreenBuffer::CreateFactory(gl, caps, imageBridge.get(), flags);
+            mVRScreen->Morph(std::move(factory));
+        }
+    }
+
+    // Swap buffers as though composition has occurred.
+    // We will then share the resulting front buffer to be submitted to the VR compositor.
+    BeginComposition(mVRScreen.get());
+    EndComposition();
+
+    if (IsContextLost())
+        return nullptr;
+
+    RefPtr<SharedSurfaceTextureClient> sharedSurface = mVRScreen->Front();
+    if (!sharedSurface || !sharedSurface->Surf())
+        return nullptr;
+
+    // Make sure that the WebGL buffer is committed to the attached SurfaceTexture on Android.
     sharedSurface->Surf()->ProducerAcquire();
     sharedSurface->Surf()->Commit();
     sharedSurface->Surf()->ProducerRelease();
-  }
-
-  return sharedSurface.forget();
+
+    return sharedSurface.forget();
 }
 #else
 already_AddRefed<layers::SharedSurfaceTextureClient>
 WebGLContext::GetVRFrame()
 {
   /**
    * Swap buffers as though composition has occurred.
    * We will then share the resulting front buffer to be submitted to the VR
--- a/dom/canvas/WebGLContext.h
+++ b/dom/canvas/WebGLContext.h
@@ -90,16 +90,17 @@ struct WebGLContextAttributes;
 } // namespace dom
 
 namespace gfx {
 class SourceSurface;
 class VRLayerChild;
 } // namespace gfx
 
 namespace gl {
+class GLScreenBuffer;
 class MozFramebuffer;
 } // namespace gl
 
 namespace webgl {
 class AvailabilityRunnable;
 struct CachedDrawFetchLimits;
 struct LinkedProgramInfo;
 class ShaderValidator;
@@ -493,20 +494,20 @@ public:
     bool IsContextCleanForFrameCapture() override { return !mCapturedFrameInvalidated; }
 
     gl::GLContext* GL() const { return gl; }
 
     bool IsPremultAlpha() const { return mOptions.premultipliedAlpha; }
 
     bool IsPreservingDrawingBuffer() const { return mOptions.preserveDrawingBuffer; }
 
-    bool PresentScreenBuffer();
+    bool PresentScreenBuffer(gl::GLScreenBuffer* const screen = nullptr);
 
     // Prepare the context for capture before compositing
-    void BeginComposition();
+    void BeginComposition(gl::GLScreenBuffer* const screen = nullptr);
     // Clean up the context after captured for compositing
     void EndComposition();
 
     // a number that increments every time we have an event that causes
     // all context resources to be lost.
     uint32_t Generation() const { return mGeneration.value(); }
 
     // This is similar to GLContext::ClearSafely, but tries to minimize the
@@ -1975,16 +1976,19 @@ protected:
 
     mutable uint8_t mDriverColorMask = 0;
     bool mDriverDepthTest = false;
     bool mDriverStencilTest = false;
 
     bool mNeedsIndexValidation = false;
 
     const bool mAllowFBInvalidation;
+#if defined(MOZ_WIDGET_ANDROID)
+    UniquePtr<gl::GLScreenBuffer> mVRScreen;
+#endif
 
     bool Has64BitTimestamps() const;
 
     // --
 
     const uint8_t mMsaaSamples;
     mutable gfx::IntSize mRequestedSize;
     mutable UniquePtr<gl::MozFramebuffer> mDefaultFB;
--- a/dom/html/HTMLMediaElement.cpp
+++ b/dom/html/HTMLMediaElement.cpp
@@ -1372,17 +1372,16 @@ private:
   bool mCancelled = false;
 };
 
 class HTMLMediaElement::ErrorSink
 {
 public:
   explicit ErrorSink(HTMLMediaElement* aOwner)
     : mOwner(aOwner)
-    , mSrcIsUnsupportedTypeMedia(false)
   {
     MOZ_ASSERT(mOwner);
   }
 
   void SetError(uint16_t aErrorCode, const nsACString& aErrorDetails)
   {
     // Since we have multiple paths calling into DecodeError, e.g.
     // MediaKeys::Terminated and EMEH264Decoder::Error. We should take the 1st
@@ -1391,94 +1390,54 @@ public:
       return;
     }
 
     if (!IsValidErrorCode(aErrorCode)) {
       NS_ASSERTION(false, "Undefined MediaError codes!");
       return;
     }
 
-    // TODO : remove unsupported type related codes after finishing native
-    // support for HLS, see bug 1350842.
-    if (CanOwnerPlayUnsupportedTypeMedia() &&
-        aErrorCode == MEDIA_ERR_SRC_NOT_SUPPORTED) {
-      // On Fennec, we do some hack for unsupported type media, we don't set
-      // its error state in order to open it with external app.
-      mSrcIsUnsupportedTypeMedia = true;
+
+    mError = new MediaError(mOwner, aErrorCode, aErrorDetails);
+    mOwner->DispatchAsyncEvent(NS_LITERAL_STRING("error"));
+    if (mOwner->ReadyState() == HAVE_NOTHING &&
+        aErrorCode == MEDIA_ERR_ABORTED) {
+      // https://html.spec.whatwg.org/multipage/embedded-content.html#media-data-processing-steps-list
+      // "If the media data fetching process is aborted by the user"
+      mOwner->DispatchAsyncEvent(NS_LITERAL_STRING("abort"));
+      mOwner->ChangeNetworkState(NETWORK_EMPTY);
+      mOwner->DispatchAsyncEvent(NS_LITERAL_STRING("emptied"));
+      if (mOwner->mDecoder) {
+        mOwner->ShutdownDecoder();
+      }
+    } else if (aErrorCode == MEDIA_ERR_SRC_NOT_SUPPORTED) {
       mOwner->ChangeNetworkState(NETWORK_NO_SOURCE);
-      MaybeOpenUnsupportedMediaForOwner();
     } else {
-      mError = new MediaError(mOwner, aErrorCode, aErrorDetails);
-      mOwner->DispatchAsyncEvent(NS_LITERAL_STRING("error"));
-      if (mOwner->ReadyState() == HAVE_NOTHING &&
-          aErrorCode == MEDIA_ERR_ABORTED) {
-        // https://html.spec.whatwg.org/multipage/embedded-content.html#media-data-processing-steps-list
-        // "If the media data fetching process is aborted by the user"
-        mOwner->DispatchAsyncEvent(NS_LITERAL_STRING("abort"));
-        mOwner->ChangeNetworkState(NETWORK_EMPTY);
-        mOwner->DispatchAsyncEvent(NS_LITERAL_STRING("emptied"));
-        if (mOwner->mDecoder) {
-          mOwner->ShutdownDecoder();
-        }
-      } else if (aErrorCode == MEDIA_ERR_SRC_NOT_SUPPORTED) {
-        mOwner->ChangeNetworkState(NETWORK_NO_SOURCE);
-      } else {
-        mOwner->ChangeNetworkState(NETWORK_IDLE);
-      }
+      mOwner->ChangeNetworkState(NETWORK_IDLE);
     }
   }
 
   void ResetError()
   {
     mError = nullptr;
-    mSrcIsUnsupportedTypeMedia = false;
-  }
-
-  void MaybeOpenUnsupportedMediaForOwner() const
-  {
-    // Src is supported type or we don't open the pref for external app.
-    if (!mSrcIsUnsupportedTypeMedia || !CanOwnerPlayUnsupportedTypeMedia()) {
-      return;
-    }
-
-    // If media doesn't start playing, we don't need to open it.
-    if (mOwner->Paused()) {
-      return;
-    }
-
-    nsContentUtils::DispatchTrustedEvent(
-      mOwner->OwnerDoc(),
-      static_cast<nsIContent*>(mOwner),
-      NS_LITERAL_STRING("OpenMediaWithExternalApp"),
-      CanBubble::eYes, Cancelable::eYes);
   }
 
   RefPtr<MediaError> mError;
 
 private:
   bool IsValidErrorCode(const uint16_t& aErrorCode) const
   {
     return (aErrorCode == MEDIA_ERR_DECODE || aErrorCode == MEDIA_ERR_NETWORK ||
             aErrorCode == MEDIA_ERR_ABORTED ||
             aErrorCode == MEDIA_ERR_SRC_NOT_SUPPORTED);
   }
 
-  bool CanOwnerPlayUnsupportedTypeMedia() const
-  {
-#if defined(MOZ_WIDGET_ANDROID)
-    // On Fennec, we will use an external app to open unsupported media types.
-    return Preferences::GetBool("media.openUnsupportedTypeWithExternalApp");
-#endif
-    return false;
-  }
-
   // Media elememt's life cycle would be longer than error sink, so we use the
   // raw pointer and this class would only be referenced by media element.
   HTMLMediaElement* mOwner;
-  bool mSrcIsUnsupportedTypeMedia;
 };
 
 NS_IMPL_CYCLE_COLLECTION_CLASS(HTMLMediaElement)
 
 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(HTMLMediaElement,
                                                   nsGenericHTMLElement)
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mMediaSource)
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mSrcMediaSource)
@@ -7013,22 +6972,16 @@ HTMLMediaElement::GetSrcMediaStream() co
 
 MediaError*
 HTMLMediaElement::GetError() const
 {
   return mErrorSink->mError;
 }
 
 void
-HTMLMediaElement::OpenUnsupportedMediaWithExternalAppIfNeeded() const
-{
-  mErrorSink->MaybeOpenUnsupportedMediaForOwner();
-}
-
-void
 HTMLMediaElement::GetCurrentSpec(nsCString& aString)
 {
   if (mLoadingSrc) {
     mLoadingSrc->GetSpec(aString);
   } else {
     aString.Truncate();
   }
 }
@@ -7962,17 +7915,16 @@ HTMLMediaElement::MarkAsContentSource(Ca
          IsInComposedDoc(),
          static_cast<int>(aAPI)));
   }
 }
 
 void
 HTMLMediaElement::UpdateCustomPolicyAfterPlayed()
 {
-  OpenUnsupportedMediaWithExternalAppIfNeeded();
   if (mAudioChannelWrapper) {
     mAudioChannelWrapper->NotifyPlayStateChanged();
   }
 }
 
 AbstractThread*
 HTMLMediaElement::AbstractMainThread() const
 {
--- a/dom/html/HTMLMediaElement.h
+++ b/dom/html/HTMLMediaElement.h
@@ -1305,20 +1305,16 @@ protected:
 
   class nsAsyncEventRunner;
   class nsNotifyAboutPlayingRunner;
   class nsResolveOrRejectPendingPlayPromisesRunner;
   using nsGenericHTMLElement::DispatchEvent;
   // For nsAsyncEventRunner.
   nsresult DispatchEvent(const nsAString& aName);
 
-  // Open unsupported types media with the external app when the media element
-  // triggers play() after loaded fail. eg. preload the data before start play.
-  void OpenUnsupportedMediaWithExternalAppIfNeeded() const;
-
   // This method moves the mPendingPlayPromises into a temperate object. So the
   // mPendingPlayPromises is cleared after this method call.
   nsTArray<RefPtr<PlayPromise>> TakePendingPlayPromises();
 
   // This method snapshots the mPendingPlayPromises by TakePendingPlayPromises()
   // and queues a task to resolve them.
   void AsyncResolvePendingPlayPromises();
 
--- a/dom/html/nsTextEditorState.cpp
+++ b/dom/html/nsTextEditorState.cpp
@@ -6,17 +6,16 @@
 
 #include "nsTextEditorState.h"
 #include "mozilla/TextInputListener.h"
 
 #include "nsCOMPtr.h"
 #include "nsIPresShell.h"
 #include "nsView.h"
 #include "nsCaret.h"
-#include "nsEditorCID.h"
 #include "nsLayoutCID.h"
 #include "nsITextControlFrame.h"
 #include "nsContentCreatorFunctions.h"
 #include "nsTextControlFrame.h"
 #include "nsIControllers.h"
 #include "nsITransactionManager.h"
 #include "nsIControllerContext.h"
 #include "nsAttrValue.h"
--- a/dom/html/test/browser_bug1081537.js
+++ b/dom/html/test/browser_bug1081537.js
@@ -1,11 +1,11 @@
 // This test is useful because mochitest-browser runs as an addon, so we test
 // addon-scope paths here.
 var ifr;
 function test() {
-  ifr = document.createElement('iframe');
+  ifr = document.createXULElement('iframe');
   document.getElementById('main-window').appendChild(ifr);
   is(ifr.contentDocument.nodePrincipal.origin, "[System Principal]");
   ifr.contentDocument.open();
   ok(true, "Didn't throw");
 }
 registerCleanupFunction(() => ifr.remove());
--- a/dom/media/test/can_play_type_webm.js
+++ b/dom/media/test/can_play_type_webm.js
@@ -30,24 +30,15 @@ async function check_webm(v, enabled) {
   function getPref(name) {
     var pref = false;
     try {
       pref = SpecialPowers.getBoolPref(name);
     } catch(ex) { }
     return pref;
   }
 
-  function isWindows32() {
-    return navigator.userAgent.includes("Windows") &&
-           !navigator.userAgent.includes("Win64");
-  }
-
-  function isAndroid() {
-    return navigator.userAgent.includes("Android");
-  }
-
   await SpecialPowers.pushPrefEnv({"set": [["media.av1.enabled", true]]});
   // AV1 is disabled on Windows 32 bits (bug 1475564) and Android (bug 1368843)
   check("video/webm; codecs=\"av1\"", (isWindows32() || isAndroid()) ? "" : "probably");
 
   await SpecialPowers.pushPrefEnv({"set": [["media.av1.enabled", false]]});
   check("video/webm; codecs=\"av1\"", "");
 }
--- a/dom/media/test/manifest.js
+++ b/dom/media/test/manifest.js
@@ -628,16 +628,25 @@ var gCuelessWebMTests = [
 ];
 
 // These are files that are non seekable, due to problems with the media,
 // for example broken or missing indexes.
 var gUnseekableTests = [
   { name:"bogus.duh", type:"bogus/duh"}
 ];
 
+function isWindows32() {
+    return navigator.userAgent.includes("Windows") &&
+        !navigator.userAgent.includes("Win64");
+}
+
+function isAndroid() {
+    return navigator.userAgent.includes("Android");
+}
+
 var androidVersion = -1; // non-Android platforms
 if (manifestNavigator().userAgent.includes("Mobile") ||
     manifestNavigator().userAgent.includes("Tablet")) {
   // See nsSystemInfo.cpp, the getProperty('version') returns different value
   // on each platforms, so we need to distinguish the android and B2G platform.
   var versionString = manifestNavigator().userAgent.includes("Android") ?
                       'version' : 'sdk_version';
   androidVersion = SpecialPowers.Cc['@mozilla.org/system-info;1']
--- a/dom/media/test/test_can_play_type_mpeg.html
+++ b/dom/media/test/test_can_play_type_mpeg.html
@@ -93,18 +93,17 @@ function check_mp4(v, enabled) {
     "video/mp4; codecs=\"vp9.0\""
   ].forEach((codec) => {
     // canPlayType should support VP9 in MP4...
     check(codec, "probably");
     ok(MediaSource.isTypeSupported(codec), "VP9 in MP4 should be supported in MSE");
   });
   // AV1 is temporarily disabled on Win32 due to linker issues
   // https://bugzilla.mozilla.org/show_bug.cgi?id=1475564
-  if (!(manifestNavigator().userAgent.includes("Windows") &&
-      !manifestNavigator().userAgent.includes("x64"))) {
+  if (!isWindows32() && !isAndroid()) {
     check("video/mp4; codecs=\"av1\"", "probably");
   } else {
     check("video/mp4; codecs=\"av1\"", "");
   }
 }
 
 function check_mp3(v, enabled) {
   function check(type, expected) {
--- a/dom/presentation/provider/PresentationControlService.js
+++ b/dom/presentation/provider/PresentationControlService.js
@@ -85,17 +85,16 @@ PresentationControlService.prototype = {
       throw Cr.NS_ERROR_FAILURE;
     }
 
     try {
       this._serverSocket.init(aPort, false, -1);
 
       if (aCert) {
         this._serverSocket.serverCert = aCert;
-        this._serverSocket.setSessionCache(false);
         this._serverSocket.setSessionTickets(false);
         let requestCert = Ci.nsITLSServerSocket.REQUEST_NEVER;
         this._serverSocket.setRequestClientCertificate(requestCert);
       }
 
       this._serverSocket.asyncListen(this);
     } catch (e) {
       // NS_ERROR_SOCKET_ADDRESS_IN_USE
new file mode 100644
--- /dev/null
+++ b/dom/security/fuzztest/csp_fuzzer.cpp
@@ -0,0 +1,45 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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 https://mozilla.org/MPL/2.0/. */
+
+#include "FuzzingInterface.h"
+#include "nsCSPContext.h"
+#include "nsNetUtil.h"
+#include "nsStringFwd.h"
+
+static int
+LLVMFuzzerTestOneInput(const uint8_t* data, size_t size)
+{
+  nsresult ret;
+  nsCOMPtr<nsIURI> selfURI;
+  ret = NS_NewURI(getter_AddRefs(selfURI), "http://selfuri.com");
+  if (ret != NS_OK)
+    return 0;
+
+  mozilla::OriginAttributes attrs;
+  nsCOMPtr<nsIPrincipal> selfURIPrincipal =
+    mozilla::BasePrincipal::CreateCodebasePrincipal(selfURI, attrs);
+  if (!selfURIPrincipal)
+    return 0;
+
+  nsCOMPtr<nsIContentSecurityPolicy> csp =
+    do_CreateInstance(NS_CSPCONTEXT_CONTRACTID, &ret);
+  if (ret != NS_OK)
+    return 0;
+
+  ret = csp->SetRequestContext(nullptr, selfURIPrincipal);
+  if (ret != NS_OK)
+    return 0;
+
+  NS_ConvertASCIItoUTF16 policy(reinterpret_cast<const char*>(data), size);
+  if (!policy.get())
+    return 0;
+  csp->AppendPolicy(policy, false, false);
+
+  return 0;
+}
+
+MOZ_FUZZING_INTERFACE_RAW(nullptr, LLVMFuzzerTestOneInput, ContentSecurityPolicyParser);
+
new file mode 100644
--- /dev/null
+++ b/dom/security/fuzztest/csp_fuzzer.dict
@@ -0,0 +1,95 @@
+### dom/security/nsCSPParser.cpp
+# tokens
+":"
+";"
+"/"
+"+"
+"-"
+"."
+"_"
+"~"
+"*"
+"'"
+"#"
+"?"
+"%"
+"!"
+"$"
+"&"
+"("
+")"
+"="
+"@"
+
+### https://www.w3.org/TR/{CSP,CSP2,CSP3}/
+# directive names
+"default-src"
+"script-src"
+"object-src"
+"style-src"
+"img-src"
+"media-src"
+"frame-src"
+"font-src"
+"connect-src"
+"report-uri"
+"frame-ancestors"
+"reflected-xss"
+"base-uri"
+"form-action"
+"manifest-src"
+"upgrade-insecure-requests"
+"child-src"
+"block-all-mixed-content"
+"require-sri-for"
+"sandbox"
+"worker-src"
+"plugin-types"
+"disown-opener"
+"report-to"
+
+# directive values
+"'self'"
+"'unsafe-inline'"
+"'unsafe-eval'"
+"'none'"
+"'strict-dynamic'"
+"'unsafe-hashed-attributes'"
+"'nonce-AA=='"
+"'sha256-fw=='"
+"'sha384-/w=='"
+"'sha512-//8='"
+
+# subresources
+"a"
+"audio"
+"embed"
+"iframe"
+"img"
+"link"
+"object"
+"script"
+"source"
+"style"
+"track"
+"video"
+
+# sandboxing flags
+"allow-forms"
+"allow-pointer-lock"
+"allow-popups"
+"allow-same-origin"
+"allow-scripts"
+"allow-top-navigation"
+
+# URI components
+"https:"
+"ws:"
+"blob:"
+"data:"
+"filesystem:"
+"javascript:"
+"http://"
+"selfuri.com"
+"127.0.0.1"
+"::1"
new file mode 100644
--- /dev/null
+++ b/dom/security/fuzztest/moz.build
@@ -0,0 +1,21 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+Library('FuzzingDOMSecurity')
+
+LOCAL_INCLUDES += [
+    '/dom/security',
+    '/netwerk/base',
+]
+
+include('/tools/fuzzing/libfuzzer-config.mozbuild')
+
+SOURCES += [
+    'csp_fuzzer.cpp'
+]
+
+FINAL_LIBRARY = 'xul-gtest'
+
--- a/dom/security/moz.build
+++ b/dom/security/moz.build
@@ -46,8 +46,16 @@ include('/ipc/chromium/chromium-config.m
 
 FINAL_LIBRARY = 'xul'
 LOCAL_INCLUDES += [
     '/caps',
     '/docshell/base',  # for nsDocShell.h
     '/netwerk/base',
     '/netwerk/protocol/data', # for nsDataHandler.h
 ]
+
+include('/tools/fuzzing/libfuzzer-config.mozbuild')
+
+if CONFIG['FUZZING_INTERFACES']:
+    TEST_DIRS += [
+        'fuzztest'
+    ]
+
--- a/dom/serviceworkers/ServiceWorkerRegistration.cpp
+++ b/dom/serviceworkers/ServiceWorkerRegistration.cpp
@@ -217,27 +217,16 @@ ServiceWorkerRegistration::Update(ErrorR
     return nullptr;
   }
 
   RefPtr<Promise> outer = Promise::Create(global, aRv);
   if (NS_WARN_IF(aRv.Failed())) {
     return nullptr;
   }
 
-  if (RefPtr<ServiceWorkerGlobalScope> serviceWorkerGlobal =
-        do_QueryObject(global)) {
-    WorkerPrivate* wp;
-    if (serviceWorkerGlobal->Registration() == this &&
-        (wp = GetCurrentThreadWorkerPrivate()) &&
-        wp->IsLoadingWorkerScript()) {
-      outer->MaybeResolve(*this);
-      return outer.forget();
-    }
-  }
-
   RefPtr<ServiceWorkerRegistration> self = this;
 
   mPendingUpdatePromises += 1;
 
   mInner->Update(
     [outer, self](const ServiceWorkerRegistrationDescriptor& aDesc) {
       auto scopeExit = MakeScopeExit([&] { self->UpdatePromiseSettled(); });
       nsIGlobalObject* global = self->GetParentObject();
--- a/dom/serviceworkers/ServiceWorkerRegistrationImpl.cpp
+++ b/dom/serviceworkers/ServiceWorkerRegistrationImpl.cpp
@@ -821,18 +821,23 @@ ServiceWorkerRegistrationWorkerThread::U
 
   // Eventually we need to support all workers, but for right now this
   // code assumes we're on a service worker global as self.registration.
   if (NS_WARN_IF(!workerRef->Private()->IsServiceWorker())) {
     aFailureCB(CopyableErrorResult(NS_ERROR_DOM_INVALID_STATE_ERR));
     return;
   }
 
-  // This is ensured by the binding layer.
-  MOZ_ASSERT(!workerRef->Private()->IsLoadingWorkerScript());
+  // Avoid infinite update loops by ignoring update() calls during top
+  // level script evaluation.  See:
+  // https://github.com/slightlyoff/ServiceWorker/issues/800
+  if (workerRef->Private()->IsLoadingWorkerScript()) {
+    aSuccessCB(mDescriptor);
+    return;
+  }
 
   auto promise = MakeRefPtr<ServiceWorkerRegistrationPromise::Private>(__func__);
   auto holder =
     MakeRefPtr<DOMMozPromiseRequestHolder<ServiceWorkerRegistrationPromise>>(global);
 
   promise->Then(
     global->EventTargetFor(TaskCategory::Other), __func__,
     [successCB = std::move(aSuccessCB), holder] (const ServiceWorkerRegistrationDescriptor& aDescriptor) {
--- a/dom/svg/SVGPathData.cpp
+++ b/dom/svg/SVGPathData.cpp
@@ -555,17 +555,18 @@ SVGPathData::BuildPathForMeasuring() con
 
 // We could simplify this function because this is only used by CSS motion path
 // and clip-path, which don't render the SVG Path. i.e. The returned path is
 // used as a reference.
 /* static */ already_AddRefed<Path>
 SVGPathData::BuildPath(const nsTArray<StylePathCommand>& aPath,
                        PathBuilder* aBuilder,
                        uint8_t aStrokeLineCap,
-                       Float aStrokeWidth)
+                       Float aStrokeWidth,
+                       float aZoomFactor)
 {
   if (aPath.IsEmpty() || !aPath[0].IsMoveTo()) {
     return nullptr; // paths without an initial moveto are invalid
   }
 
   auto toGfxPoint = [](const StyleCoordPair& aPair) {
     return Point(aPair._0, aPair._1);
   };
@@ -587,16 +588,20 @@ SVGPathData::BuildPath(const nsTArray<St
   StylePathCommand::Tag segType = StylePathCommand::Tag::Unknown;
   StylePathCommand::Tag prevSegType = StylePathCommand::Tag::Unknown;
   Point pathStart(0.0, 0.0); // start point of [sub]path
   Point segStart(0.0, 0.0);
   Point segEnd;
   Point cp1, cp2;            // previous bezier's control points
   Point tcp1, tcp2;          // temporaries
 
+  auto scale = [aZoomFactor](const Point& p) {
+    return Point(p.x * aZoomFactor, p.y * aZoomFactor);
+  };
+
   // Regarding cp1 and cp2: If the previous segment was a cubic bezier curve,
   // then cp2 is its second control point. If the previous segment was a
   // quadratic curve, then cp1 is its (only) control point.
 
   for (const StylePathCommand& cmd: aPath) {
     segType = cmd.tag;
     switch (segType) {
       case StylePathCommand::Tag::ClosePath:
@@ -605,43 +610,43 @@ SVGPathData::BuildPath(const nsTArray<St
         MAYBE_APPROXIMATE_ZERO_LENGTH_SUBPATH_SQUARE_CAPS_TO_DT;
         segEnd = pathStart;
         aBuilder->Close();
         break;
       case StylePathCommand::Tag::MoveTo: {
         MAYBE_APPROXIMATE_ZERO_LENGTH_SUBPATH_SQUARE_CAPS_TO_DT;
         const Point& p = toGfxPoint(cmd.move_to.point);
         pathStart = segEnd = cmd.move_to.absolute ? p : segStart + p;
-        aBuilder->MoveTo(segEnd);
+        aBuilder->MoveTo(scale(segEnd));
         subpathHasLength = false;
         break;
       }
       case StylePathCommand::Tag::LineTo: {
         const Point& p = toGfxPoint(cmd.line_to.point);
         segEnd = cmd.line_to.absolute ? p : segStart + p;
         if (segEnd != segStart) {
           subpathHasLength = true;
-          aBuilder->LineTo(segEnd);
+          aBuilder->LineTo(scale(segEnd));
         }
         break;
       }
       case StylePathCommand::Tag::CurveTo:
         cp1 = toGfxPoint(cmd.curve_to.control1);
         cp2 = toGfxPoint(cmd.curve_to.control2);
         segEnd = toGfxPoint(cmd.curve_to.point);
 
         if (!cmd.curve_to.absolute) {
           cp1 += segStart;
           cp2 += segStart;
           segEnd += segStart;
         }
 
         if (segEnd != segStart || segEnd != cp1 || segEnd != cp2) {
           subpathHasLength = true;
-          aBuilder->BezierTo(cp1, cp2, segEnd);
+          aBuilder->BezierTo(scale(cp1), scale(cp2), scale(segEnd));
         }
         break;
 
       case StylePathCommand::Tag::QuadBezierCurveTo:
         cp1 = toGfxPoint(cmd.quad_bezier_curve_to.control1);
         segEnd = toGfxPoint(cmd.quad_bezier_curve_to.point);
 
         if (!cmd.quad_bezier_curve_to.absolute) {
@@ -650,96 +655,96 @@ SVGPathData::BuildPath(const nsTArray<St
         }
 
         // Convert quadratic curve to cubic curve:
         tcp1 = segStart + (cp1 - segStart) * 2 / 3;
         tcp2 = cp1 + (segEnd - cp1) / 3;
 
         if (segEnd != segStart || segEnd != cp1) {
           subpathHasLength = true;
-          aBuilder->BezierTo(tcp1, tcp2, segEnd);
+          aBuilder->BezierTo(scale(tcp1), scale(tcp2), scale(segEnd));
         }
         break;
 
       case StylePathCommand::Tag::EllipticalArc: {
         const auto& arc = cmd.elliptical_arc;
         Point radii(arc.rx, arc.ry);
         segEnd = toGfxPoint(arc.point);
         if (!arc.absolute) {
           segEnd += segStart;
         }
         if (segEnd != segStart) {
           subpathHasLength = true;
           if (radii.x == 0.0f || radii.y == 0.0f) {
-            aBuilder->LineTo(segEnd);
+            aBuilder->LineTo(scale(segEnd));
           } else {
             nsSVGArcConverter converter(segStart, segEnd, radii, arc.angle,
                                         arc.large_arc_flag, arc.sweep_flag);
             while (converter.GetNextSegment(&cp1, &cp2, &segEnd)) {
-              aBuilder->BezierTo(cp1, cp2, segEnd);
+              aBuilder->BezierTo(scale(cp1), scale(cp2), scale(segEnd));
             }
           }
         }
         break;
       }
       case StylePathCommand::Tag::HorizontalLineTo:
         if (cmd.horizontal_line_to.absolute) {
           segEnd = Point(cmd.horizontal_line_to.x, segStart.y);
         } else {
           segEnd = segStart + Point(cmd.horizontal_line_to.x, 0.0f);
         }
 
         if (segEnd != segStart) {
           subpathHasLength = true;
-          aBuilder->LineTo(segEnd);
+          aBuilder->LineTo(scale(segEnd));
         }
         break;
 
       case StylePathCommand::Tag::VerticalLineTo:
         if (cmd.vertical_line_to.absolute) {
           segEnd = Point(segStart.x, cmd.vertical_line_to.y);
         } else {
           segEnd = segStart + Point(0.0f, cmd.vertical_line_to.y);
         }
 
         if (segEnd != segStart) {
           subpathHasLength = true;
-          aBuilder->LineTo(segEnd);
+          aBuilder->LineTo(scale(segEnd));
         }
         break;
 
       case StylePathCommand::Tag::SmoothCurveTo:
         cp1 = isCubicType(prevSegType) ? segStart * 2 - cp2 : segStart;
         cp2 = toGfxPoint(cmd.smooth_curve_to.control2);
         segEnd = toGfxPoint(cmd.smooth_curve_to.point);
 
         if (!cmd.smooth_curve_to.absolute) {
           cp2 += segStart;
           segEnd += segStart;
         }
 
         if (segEnd != segStart || segEnd != cp1 || segEnd != cp2) {
           subpathHasLength = true;
-          aBuilder->BezierTo(cp1, cp2, segEnd);
+          aBuilder->BezierTo(scale(cp1), scale(cp2), scale(segEnd));
         }
         break;
 
       case StylePathCommand::Tag::SmoothQuadBezierCurveTo: {
         cp1 = isQuadraticType(prevSegType) ? segStart * 2 - cp1 : segStart;
         // Convert quadratic curve to cubic curve:
         tcp1 = segStart + (cp1 - segStart) * 2 / 3;
 
         const Point& p = toGfxPoint(cmd.smooth_quad_bezier_curve_to.point);
         // set before setting tcp2!
         segEnd = cmd.smooth_quad_bezier_curve_to.absolute ? p : segStart + p;
         tcp2 = cp1 + (segEnd - cp1) / 3;
 
         if (segEnd != segStart || segEnd != cp1) {
           subpathHasLength = true;
-          aBuilder->BezierTo(tcp1, tcp2, segEnd);
+          aBuilder->BezierTo(scale(tcp1), scale(tcp2), scale(segEnd));
         }
         break;
       }
       case StylePathCommand::Tag::Unknown:
         MOZ_ASSERT_UNREACHABLE("Unacceptable path segment type");
         return nullptr;
     }
 
--- a/dom/svg/SVGPathData.h
+++ b/dom/svg/SVGPathData.h
@@ -173,17 +173,18 @@ public:
    * This function tries to build the path by an array of StylePathCommand,
    * which is generated by cbindgen from Rust (see ServoStyleConsts.h).
    * Basically, this is a variant of the above BuildPath() functions.
    */
   static already_AddRefed<Path>
   BuildPath(const nsTArray<StylePathCommand>& aPath,
             PathBuilder* aBuilder,
             uint8_t aCapStyle,
-            Float aStrokeWidth);
+            Float aStrokeWidth,
+            float aZoomFactor = 1.0);
 
   const_iterator begin() const { return mData.Elements(); }
   const_iterator end() const { return mData.Elements() + mData.Length(); }
 
   // memory reporting methods
   size_t SizeOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf) const;
   size_t SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) const;
 
--- a/dom/workers/WorkerScope.cpp
+++ b/dom/workers/WorkerScope.cpp
@@ -672,17 +672,16 @@ SharedWorkerGlobalScope::Close()
 {
   mWorkerPrivate->AssertIsOnWorkerThread();
   mWorkerPrivate->CloseInternal();
 }
 
 NS_IMPL_CYCLE_COLLECTION_INHERITED(ServiceWorkerGlobalScope, WorkerGlobalScope,
                                    mClients, mRegistration)
 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(ServiceWorkerGlobalScope)
-  NS_INTERFACE_MAP_ENTRY_CONCRETE(ServiceWorkerGlobalScope)
 NS_INTERFACE_MAP_END_INHERITING(WorkerGlobalScope)
 
 NS_IMPL_ADDREF_INHERITED(ServiceWorkerGlobalScope, WorkerGlobalScope)
 NS_IMPL_RELEASE_INHERITED(ServiceWorkerGlobalScope, WorkerGlobalScope)
 
 ServiceWorkerGlobalScope::ServiceWorkerGlobalScope(WorkerPrivate* aWorkerPrivate,
                                                    const ServiceWorkerRegistrationDescriptor& aRegistrationDescriptor)
   : WorkerGlobalScope(aWorkerPrivate)
--- a/dom/workers/WorkerScope.h
+++ b/dom/workers/WorkerScope.h
@@ -297,29 +297,25 @@ public:
   }
 
   void
   Close();
 
   IMPL_EVENT_HANDLER(connect)
 };
 
-#define NS_DOM_SERVICEWORKERGLOBALSCOPE_IID \
-  {0x552bfa7e, 0x0dd5, 0x4e94, {0xa0, 0x43, 0xff, 0x34, 0x6b, 0x6e, 0x04, 0x46}}
-
 class ServiceWorkerGlobalScope final : public WorkerGlobalScope
 {
   const nsString mScope;
   RefPtr<Clients> mClients;
   RefPtr<ServiceWorkerRegistration> mRegistration;
 
   ~ServiceWorkerGlobalScope();
 
 public:
-  NS_DECLARE_STATIC_IID_ACCESSOR(NS_DOM_SERVICEWORKERGLOBALSCOPE_IID)
   NS_DECL_ISUPPORTS_INHERITED
   NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(ServiceWorkerGlobalScope,
                                            WorkerGlobalScope)
   IMPL_EVENT_HANDLER(notificationclick)
   IMPL_EVENT_HANDLER(notificationclose)
 
   ServiceWorkerGlobalScope(WorkerPrivate* aWorkerPrivate,
                            const ServiceWorkerRegistrationDescriptor& aRegistrationDescriptor);
@@ -354,18 +350,16 @@ public:
   GetOnfetch();
 
   void
   SetOnfetch(mozilla::dom::EventHandlerNonNull* aCallback);
 
   void EventListenerAdded(nsAtom* aType) override;
 };
 
-NS_DEFINE_STATIC_IID_ACCESSOR(ServiceWorkerGlobalScope, NS_DOM_SERVICEWORKERGLOBALSCOPE_IID)
-
 class WorkerDebuggerGlobalScope final : public DOMEventTargetHelper,
                                         public nsIGlobalObject
 {
   WorkerPrivate* mWorkerPrivate;
   RefPtr<Console> mConsole;
   nsCOMPtr<nsISerialEventTarget> mSerialEventTarget;
 
 public:
--- a/editor/libeditor/HTMLEditRules.cpp
+++ b/editor/libeditor/HTMLEditRules.cpp
@@ -2345,28 +2345,31 @@ HTMLEditRules::WillDeleteSelection(nsIEd
 
   // If there is only bogus content, cancel the operation
   if (mBogusNode) {
     *aCancel = true;
     return NS_OK;
   }
 
   // First check for table selection mode.  If so, hand off to table editor.
-  RefPtr<Element> cell;
-  nsresult rv =
-    HTMLEditorRef().GetFirstSelectedCell(nullptr, getter_AddRefs(cell));
-  if (NS_SUCCEEDED(rv) && cell) {
-    rv = HTMLEditorRef().DeleteTableCellContents();
+  ErrorResult error;
+  RefPtr<Element> cellElement =
+    HTMLEditorRef().GetFirstSelectedTableCellElement(SelectionRef(),
+                                                     error);
+  if (cellElement) {
+    error.SuppressException();
+    nsresult rv = HTMLEditorRef().DeleteTableCellContents();
     if (NS_WARN_IF(!CanHandleEditAction())) {
       return NS_ERROR_EDITOR_DESTROYED;
     }
     *aHandled = true;
     return rv;
   }
-  cell = nullptr;
+  nsresult rv = error.StealNSResult();
+  cellElement = nullptr;
 
   // origCollapsed is used later to determine whether we should join blocks. We
   // don't really care about bCollapsed because it will be modified by
   // ExtendSelectionForDelete later. TryToJoinBlocksWithTransaction() should
   // happen if the original selection is collapsed and the cursor is at the end
   // of a block element, in which case ExtendSelectionForDelete would always
   // make the selection not collapsed.
   bool join = false;
@@ -2438,17 +2441,18 @@ HTMLEditRules::WillDeleteSelection(nsIEd
     } else {
       wsObj.PriorVisibleNode(startPoint,
                              address_of(visNode), &visOffset, &wsType);
     }
 
     if (!visNode) {
       // Can't find anything to delete!
       *aCancel = true;
-      // XXX This is the result of HTMLEditorRef().GetFirstSelectedCell().
+      // XXX This is the result of
+      //     HTMLEditorRef().GetFirstSelectedTableCellElement().
       //     The value could be both an error and NS_OK.
       return rv;
     }
 
     if (wsType == WSType::normalWS) {
       // We found some visible ws to delete.  Let ws code handle it.
       *aHandled = true;
       if (aAction == nsIEditor::eNext) {
--- a/editor/libeditor/HTMLEditor.cpp
+++ b/editor/libeditor/HTMLEditor.cpp
@@ -2998,28 +2998,41 @@ HTMLEditor::SetHTMLBackgroundColorWithTr
                                                 getter_AddRefs(element));
   NS_ENSURE_SUCCESS(rv, rv);
 
   bool setColor = !aColor.IsEmpty();
 
   RefPtr<nsAtom> bgColorAtom = NS_Atomize("bgcolor");
   if (element) {
     if (selectedCount > 0) {
-      // Traverse all selected cells
-      RefPtr<Element> cell;
-      rv = GetFirstSelectedCell(nullptr, getter_AddRefs(cell));
-      if (NS_SUCCEEDED(rv) && cell) {
-        while (cell) {
-          rv = setColor ?
-                 SetAttributeWithTransaction(*cell, *bgColorAtom, aColor) :
-                 RemoveAttributeWithTransaction(*cell, *bgColorAtom);
+      RefPtr<Selection> selection = GetSelection();
+      if (NS_WARN_IF(!selection)) {
+        return NS_ERROR_FAILURE;
+      }
+      IgnoredErrorResult ignoredError;
+      RefPtr<Element> cellElement =
+        GetFirstSelectedTableCellElement(*selection, ignoredError);
+      if (cellElement) {
+        if (setColor) {
+          while (cellElement) {
+            rv =
+              SetAttributeWithTransaction(*cellElement, *bgColorAtom, aColor);
+            if (NS_WARN_IF(NS_FAILED(rv))) {
+              return rv;
+            }
+            GetNextSelectedCell(nullptr, getter_AddRefs(cellElement));
+          }
+          return NS_OK;
+        }
+        while (cellElement) {
+          rv = RemoveAttributeWithTransaction(*cellElement, *bgColorAtom);
           if (NS_FAILED(rv)) {
             return rv;
           }
-          GetNextSelectedCell(nullptr, getter_AddRefs(cell));
+          GetNextSelectedCell(nullptr, getter_AddRefs(cellElement));
         }
         return NS_OK;
       }
     }
     // If we failed to find a cell, fall through to use originally-found element
   } else {
     // No table element -- set the background color on the body tag
     element = GetRoot();
--- a/editor/libeditor/HTMLEditor.h
+++ b/editor/libeditor/HTMLEditor.h
@@ -525,16 +525,38 @@ protected: // May be called by friends.
    * If Selection selects only one element node, returns the element node.
    * If Selection is only one range, returns common ancestor of the range.
    * XXX If there are two or more Selection ranges, this returns parent node
    *     of start container of a range which starts with different node from
    *     start container of the first range.
    */
   Element* GetSelectionContainerElement(Selection& aSelection) const;
 
+  /**
+   * GetFirstSelectedTableCellElement() returns a <td> or <th> element if
+   * first range of Selection (i.e., result of Selection::GetRangeAt(0))
+   * selects a <td> element or <th> element.  Even if Selection is in
+   * a cell element, this returns nullptr.  And even if 2nd or later
+   * range of Selection selects a cell element, also returns nullptr.
+   * Note that when this looks for a cell element, this resets the internal
+   * index of ranges of Selection.  When you call GetNextSelectedCell() after
+   * a call of this, it'll return 2nd selected cell if there is.
+   *
+   * @param aSelection          Selection for this editor.
+   * @param aRv                 Returns error if there is no selection or
+   *                            first range of Selection is unexpected.
+   * @return                    A <td> or <th> element is selected by first
+   *                            range of Selection.  Note that the range must
+   *                            be: startContaienr and endContainer are same
+   *                            <tr> element, startOffset + 1 equals endOffset.
+   */
+  already_AddRefed<Element>
+  GetFirstSelectedTableCellElement(Selection& aSelection,
+                                   ErrorResult& aRv) const;
+
   void IsNextCharInNodeWhitespace(nsIContent* aContent,
                                   int32_t aOffset,
                                   bool* outIsSpace,
                                   bool* outIsNBSP,
                                   nsIContent** outNode = nullptr,
                                   int32_t* outOffset = 0);
   void IsPrevCharInNodeWhitespace(nsIContent* aContent,
                                   int32_t aOffset,
@@ -1209,17 +1231,17 @@ protected: // Shouldn't be used by frien
 
   nsresult GetCSSBackgroundColorState(bool* aMixed, nsAString& aOutColor,
                                       bool aBlockLevel);
   nsresult GetHTMLBackgroundColorState(bool* aMixed, nsAString& outColor);
 
   nsresult GetLastCellInRow(nsINode* aRowNode,
                             nsINode** aCellNode);
 
-  nsresult GetCellFromRange(nsRange* aRange, Element** aCell);
+  static nsresult GetCellFromRange(nsRange* aRange, Element** aCell);
 
   /**
    * This sets background on the appropriate container element (table, cell,)
    * or calls into nsTextEditor to set the page background.
    */
   nsresult SetCSSBackgroundColorWithTransaction(const nsAString& aColor);
   nsresult SetHTMLBackgroundColorWithTransaction(const nsAString& aColor);
 
@@ -1850,18 +1872,19 @@ protected:
   RefPtr<TypeInState> mTypeInState;
   RefPtr<ComposerCommandsUpdater> mComposerCommandsUpdater;
 
   bool mCRInParagraphCreatesParagraph;
 
   bool mCSSAware;
   UniquePtr<CSSEditUtils> mCSSEditUtils;
 
-  // Used by GetFirstSelectedCell and GetNextSelectedCell
-  int32_t  mSelectedCellIndex;
+  // mSelectedCellIndex is reset by GetFirstSelectedTableCellElement(),
+  // then, it'll be referred and incremented by GetNextSelectedCell().
+  mutable int32_t mSelectedCellIndex;
 
   nsString mLastStyleSheetURL;
   nsString mLastOverrideStyleSheetURL;
 
   // Maintain a list of associated style sheets and their urls.
   nsTArray<nsString> mStyleSheetURLs;
   nsTArray<RefPtr<StyleSheet>> mStyleSheets;
 
--- a/editor/libeditor/HTMLEditorDataTransfer.cpp
+++ b/editor/libeditor/HTMLEditorDataTransfer.cpp
@@ -198,17 +198,19 @@ HTMLEditor::DoInsertHTMLWithContext(cons
   CommitComposition();
   AutoPlaceholderBatch beginBatching(this);
   AutoTopLevelEditSubActionNotifier maybeTopLevelEditSubAction(
                                       *this, EditSubAction::ePasteHTMLContent,
                                       nsIEditor::eNext);
 
   // Get selection
   RefPtr<Selection> selection = GetSelection();
-  NS_ENSURE_STATE(selection);
+  if (NS_WARN_IF(!selection)) {
+    return NS_ERROR_FAILURE;
+  }
 
   // create a dom document fragment that represents the structure to paste
   nsCOMPtr<nsINode> fragmentAsNode, streamStartParent, streamEndParent;
   int32_t streamStartOffset = 0, streamEndOffset = 0;
 
   nsresult rv = CreateDOMFragmentFromPaste(aInputString, aContextStr, aInfoStr,
                                            address_of(fragmentAsNode),
                                            address_of(streamStartParent),
@@ -277,19 +279,20 @@ HTMLEditor::DoInsertHTMLWithContext(cons
       }
     }
     return NS_OK;
   }
 
   // Are there any table elements in the list?
   // check for table cell selection mode
   bool cellSelectionMode = false;
-  RefPtr<Element> cell;
-  rv = GetFirstSelectedCell(nullptr, getter_AddRefs(cell));
-  if (NS_SUCCEEDED(rv) && cell) {
+  IgnoredErrorResult ignoredError;
+  RefPtr<Element> cellElement =
+    GetFirstSelectedTableCellElement(*selection, ignoredError);
+  if (cellElement) {
     cellSelectionMode = true;
   }
 
   if (cellSelectionMode) {
     // do we have table content to paste?  If so, we want to delete
     // the selected table cells and replace with new table elements;
     // but if not we want to delete _contents_ of cells and replace
     // with non-table elements.  Use cellSelectionMode bool to
--- a/editor/libeditor/HTMLTableEditor.cpp
+++ b/editor/libeditor/HTMLTableEditor.cpp
@@ -759,33 +759,36 @@ HTMLEditor::DeleteTableCell(int32_t aNum
   }
 
   AutoPlaceholderBatch beginBatching(this);
   // Prevent rules testing until we're done
   AutoTopLevelEditSubActionNotifier maybeTopLevelEditSubAction(
                                       *this, EditSubAction::eDeleteNode,
                                       nsIEditor::eNext);
 
-  RefPtr<Element> firstCell;
-  rv = GetFirstSelectedCell(nullptr, getter_AddRefs(firstCell));
-  NS_ENSURE_SUCCESS(rv, rv);
+  ErrorResult error;
+  RefPtr<Element> firstSelectedCellElement =
+    GetFirstSelectedTableCellElement(*selection, error);
+  if (NS_WARN_IF(error.Failed())) {
+    return error.StealNSResult();
+  }
 
   // When 2 or more cells are selected, ignore aNumber and use selected cells.
-  if (firstCell && selection->RangeCount() > 1) {
+  if (firstSelectedCellElement && selection->RangeCount() > 1) {
     ErrorResult error;
     TableSize tableSize(*this, *table, error);
     if (NS_WARN_IF(error.Failed())) {
       return error.StealNSResult();
     }
 
-    CellIndexes firstCellIndexes(*firstCell, error);
+    CellIndexes firstCellIndexes(*firstSelectedCellElement, error);
     if (NS_WARN_IF(error.Failed())) {
       return error.StealNSResult();
     }
-    cell = firstCell;
+    cell = firstSelectedCellElement;
     startRowIndex = firstCellIndexes.mRow;
     startColIndex = firstCellIndexes.mColumn;
 
     // The setCaret object will call AutoSelectionSetterAfterTableEdit in its
     // destructor
     AutoSelectionSetterAfterTableEdit setCaret(*this, table, startRowIndex,
                                                startColIndex, ePreviousColumn,
                                                false);
@@ -971,43 +974,46 @@ HTMLEditor::DeleteTableCellContents()
   AutoPlaceholderBatch beginBatching(this);
   // Prevent rules testing until we're done
   AutoTopLevelEditSubActionNotifier maybeTopLevelEditSubAction(
                                       *this, EditSubAction::eDeleteNode,
                                       nsIEditor::eNext);
   //Don't let Rules System change the selection
   AutoTransactionsConserveSelection dontChangeSelection(*this);
 
-
-  RefPtr<Element> firstCell;
-  rv = GetFirstSelectedCell(nullptr, getter_AddRefs(firstCell));
-  NS_ENSURE_SUCCESS(rv, rv);
-
-
-  if (firstCell) {
+  ErrorResult error;
+  RefPtr<Element> firstSelectedCellElement =
+    GetFirstSelectedTableCellElement(*selection, error);
+  if (NS_WARN_IF(error.Failed())) {
+    return error.StealNSResult();
+  }
+
+  if (firstSelectedCellElement) {
     ErrorResult error;
-    CellIndexes firstCellIndexes(*firstCell, error);
+    CellIndexes firstCellIndexes(*firstSelectedCellElement, error);
     if (NS_WARN_IF(error.Failed())) {
       return error.StealNSResult();
     }
-    cell = firstCell;
+    cell = firstSelectedCellElement;
     startRowIndex = firstCellIndexes.mRow;
     startColIndex = firstCellIndexes.mColumn;
   }
 
   AutoSelectionSetterAfterTableEdit setCaret(*this, table, startRowIndex,
                                              startColIndex, ePreviousColumn,
                                              false);
 
   while (cell) {
     DeleteCellContents(cell);
-    if (firstCell) {
+    if (firstSelectedCellElement) {
       // We doing a selected cells, so do all of them
-      rv = GetNextSelectedCell(nullptr, getter_AddRefs(cell));
-      NS_ENSURE_SUCCESS(rv, rv);
+      nsresult rv = GetNextSelectedCell(nullptr, getter_AddRefs(cell));
+      if (NS_WARN_IF(NS_FAILED(rv))) {
+        return rv;
+      }
     } else {
       cell = nullptr;
     }
   }
   return NS_OK;
 }
 
 nsresult
@@ -1060,41 +1066,43 @@ HTMLEditor::DeleteTableColumn(int32_t aN
   aNumber = std::min(aNumber, (tableSize.mColumnCount - startColIndex));
 
   // Prevent rules testing until we're done
   AutoTopLevelEditSubActionNotifier maybeTopLevelEditSubAction(
                                       *this, EditSubAction::eDeleteNode,
                                       nsIEditor::eNext);
 
   // Test if deletion is controlled by selected cells
-  RefPtr<Element> firstCell;
-  rv = GetFirstSelectedCell(nullptr, getter_AddRefs(firstCell));
-  NS_ENSURE_SUCCESS(rv, rv);
+  RefPtr<Element> firstSelectedCellElement =
+    GetFirstSelectedTableCellElement(*selection, error);
+  if (NS_WARN_IF(error.Failed())) {
+    return error.StealNSResult();
+  }
 
   uint32_t rangeCount = selection->RangeCount();
 
-  if (firstCell && rangeCount > 1) {
-    CellIndexes firstCellIndexes(*firstCell, error);
+  if (firstSelectedCellElement && rangeCount > 1) {
+    CellIndexes firstCellIndexes(*firstSelectedCellElement, error);
     if (NS_WARN_IF(error.Failed())) {
       return error.StealNSResult();
     }
     startRowIndex = firstCellIndexes.mRow;
     startColIndex = firstCellIndexes.mColumn;
   }
   //We control selection resetting after the insert...
   AutoSelectionSetterAfterTableEdit setCaret(*this, table, startRowIndex,
                                              startColIndex, ePreviousRow,
                                              false);
 
-  if (firstCell && rangeCount > 1) {
+  if (firstSelectedCellElement && rangeCount > 1) {
     // Use selected cells to determine what rows to delete
-    cell = firstCell;
+    cell = firstSelectedCellElement;
 
     while (cell) {
-      if (cell != firstCell) {
+      if (cell != firstSelectedCellElement) {
         CellIndexes cellIndexes(*cell, error);
         if (NS_WARN_IF(error.Failed())) {
           return error.StealNSResult();
         }
         startRowIndex = cellIndexes.mRow;
         startColIndex = cellIndexes.mColumn;
       }
       // Find the next cell in a different column
@@ -1245,56 +1253,58 @@ HTMLEditor::DeleteTableRow(int32_t aNumb
   }
 
   AutoPlaceholderBatch beginBatching(this);
   // Prevent rules testing until we're done
   AutoTopLevelEditSubActionNotifier maybeTopLevelEditSubAction(
                                       *this, EditSubAction::eDeleteNode,
                                       nsIEditor::eNext);
 
-  RefPtr<Element> firstCell;
-  rv = GetFirstSelectedCell(nullptr, getter_AddRefs(firstCell));
-  NS_ENSURE_SUCCESS(rv, rv);
+  RefPtr<Element> firstSelectedCellElement =
+    GetFirstSelectedTableCellElement(*selection, error);
+  if (NS_WARN_IF(error.Failed())) {
+    return error.StealNSResult();
+  }
 
   uint32_t rangeCount = selection->RangeCount();
-  if (firstCell && rangeCount > 1) {
+  if (firstSelectedCellElement && rangeCount > 1) {
     // Fetch indexes again - may be different for selected cells
-    CellIndexes firstCellIndexes(*firstCell, error);
+    CellIndexes firstCellIndexes(*firstSelectedCellElement, error);
     if (NS_WARN_IF(error.Failed())) {
       return error.StealNSResult();
     }
     startRowIndex = firstCellIndexes.mRow;
     startColIndex = firstCellIndexes.mColumn;
   }
 
   //We control selection resetting after the insert...
   AutoSelectionSetterAfterTableEdit setCaret(*this, table, startRowIndex,
                                              startColIndex, ePreviousRow,
                                              false);
   // Don't change selection during deletions
   AutoTransactionsConserveSelection dontChangeSelection(*this);
 
-  if (firstCell && rangeCount > 1) {
+  if (firstSelectedCellElement && rangeCount > 1) {
     // Use selected cells to determine what rows to delete
-    cell = firstCell;
+    cell = firstSelectedCellElement;
 
     while (cell) {
-      if (cell != firstCell) {
+      if (cell != firstSelectedCellElement) {
         CellIndexes cellIndexes(*cell, error);
         if (NS_WARN_IF(error.Failed())) {
           return error.StealNSResult();
         }
         startRowIndex = cellIndexes.mRow;
         startColIndex = cellIndexes.mColumn;
       }
       // Find the next cell in a different row
       // to continue after we delete this row
       int32_t nextRow = startRowIndex;
       while (nextRow == startRowIndex) {
-        rv = GetNextSelectedCell(nullptr, getter_AddRefs(cell));
+        nsresult rv = GetNextSelectedCell(nullptr, getter_AddRefs(cell));
         NS_ENSURE_SUCCESS(rv, rv);
         if (!cell) {
           break;
         }
         CellIndexes cellIndexes(*cell, error);
         if (NS_WARN_IF(error.Failed())) {
           return error.StealNSResult();
         }
@@ -1304,17 +1314,17 @@ HTMLEditor::DeleteTableRow(int32_t aNumb
       // Delete entire row
       rv = DeleteRow(table, startRowIndex);
       NS_ENSURE_SUCCESS(rv, rv);
     }
   } else {
     // Check for counts too high
     aNumber = std::min(aNumber, (tableSize.mRowCount - startRowIndex));
     for (int32_t i = 0; i < aNumber; i++) {
-      rv = DeleteRow(table, startRowIndex);
+      nsresult rv = DeleteRow(table, startRowIndex);
       // If failed in current row, try the next
       if (NS_FAILED(rv)) {
         startRowIndex++;
       }
 
       // Check if there's a cell in the "next" row
       cell = GetTableCellElementAt(*table, startRowIndex, startColIndex);
       if (!cell) {
@@ -1531,43 +1541,48 @@ HTMLEditor::SelectBlockOfCells(Element* 
     std::min(startCellIndexes.mRow, endCellIndexes.mRow);
   int32_t maxColumn =
     std::max(startCellIndexes.mColumn, endCellIndexes.mColumn);
   int32_t maxRow =
     std::max(startCellIndexes.mRow, endCellIndexes.mRow);
 
   RefPtr<Element> cell;
   int32_t currentRowIndex, currentColIndex;
-  RefPtr<nsRange> range;
-  nsresult rv =
-    GetFirstSelectedCell(getter_AddRefs(range), getter_AddRefs(cell));
-  NS_ENSURE_SUCCESS(rv, rv);
-  if (rv == NS_SUCCESS_EDITOR_ELEMENT_NOT_FOUND) {
+  cell = GetFirstSelectedTableCellElement(*selection, error);
+  if (NS_WARN_IF(error.Failed())) {
+    return error.StealNSResult();
+  }
+  if (!cell) {
     return NS_OK;
   }
-
+  RefPtr<nsRange> range = selection->GetRangeAt(0);
+  MOZ_ASSERT(range);
   while (cell) {
     CellIndexes currentCellIndexes(*cell, error);
     if (NS_WARN_IF(error.Failed())) {
       return error.StealNSResult();
     }
     if (currentCellIndexes.mRow < maxRow ||
         currentCellIndexes.mRow > maxRow ||
         currentCellIndexes.mColumn < maxColumn ||
         currentCellIndexes.mColumn > maxColumn) {
       selection->RemoveRange(*range, IgnoreErrors());
       // Since we've removed the range, decrement pointer to next range
       mSelectedCellIndex--;
     }
-    rv = GetNextSelectedCell(getter_AddRefs(range), getter_AddRefs(cell));
-    NS_ENSURE_SUCCESS(rv, rv);
+    nsresult rv =
+      GetNextSelectedCell(getter_AddRefs(range), getter_AddRefs(cell));
+    if (NS_WARN_IF(NS_FAILED(rv))) {
+      return rv;
+    }
   }
 
   int32_t rowSpan, colSpan, actualRowSpan, actualColSpan;
   bool    isSelected;
+  nsresult rv = NS_OK;
   for (int32_t row = minRow; row <= maxRow; row++) {
     for (int32_t col = minColumn; col <= maxColumn;
         col += std::max(actualColSpan, 1)) {
       rv = GetCellDataAt(table, row, col, getter_AddRefs(cell),
                          &currentRowIndex, &currentColIndex,
                          &rowSpan, &colSpan,
                          &actualRowSpan, &actualColSpan, &isSelected);
       if (NS_FAILED(rv)) {
@@ -2360,17 +2375,17 @@ HTMLEditor::JoinTableCells(bool aMergeNo
     uint32_t rangeCount = selection->RangeCount();
 
     RefPtr<nsRange> range;
     for (uint32_t i = 0; i < rangeCount; i++) {
       range = selection->GetRangeAt(i);
       NS_ENSURE_TRUE(range, NS_ERROR_FAILURE);
 
       RefPtr<Element> deletedCell;
-      GetCellFromRange(range, getter_AddRefs(deletedCell));
+      HTMLEditor::GetCellFromRange(range, getter_AddRefs(deletedCell));
       if (!deletedCell) {
         selection->RemoveRange(*range, IgnoreErrors());
         rangeCount--;
         i--;
       }
     }
 
     // Set spans for the cell everthing merged into
@@ -3215,16 +3230,17 @@ HTMLEditor::GetCellContext(Selection** a
     // Now it's safe to hand over the reference to cellParent, since
     // we don't need it anymore.
     cellParent.forget(aCellParent);
   }
 
   return NS_OK;
 }
 
+// static
 nsresult
 HTMLEditor::GetCellFromRange(nsRange* aRange,
                              Element** aCell)
 {
   // Note: this might return a node that is outside of the range.
   // Use carefully.
   NS_ENSURE_TRUE(aRange && aCell, NS_ERROR_NULL_POINTER);
 
@@ -3259,54 +3275,97 @@ HTMLEditor::GetCellFromRange(nsRange* aR
     RefPtr<Element> cellElement = childNode->AsElement();
     cellElement.forget(aCell);
     return NS_OK;
   }
   return NS_SUCCESS_EDITOR_ELEMENT_NOT_FOUND;
 }
 
 NS_IMETHODIMP
-HTMLEditor::GetFirstSelectedCell(nsRange** aRange,
-                                 Element** aCell)
+HTMLEditor::GetFirstSelectedCell(nsRange** aFirstSelectedRange,
+                                 Element** aFirstSelectedCellElement)
 {
-  NS_ENSURE_TRUE(aCell, NS_ERROR_NULL_POINTER);
-  *aCell = nullptr;
-  if (aRange) {
-    *aRange = nullptr;
+  if (NS_WARN_IF(!aFirstSelectedCellElement)) {
+    return NS_ERROR_INVALID_ARG;
   }
 
+  *aFirstSelectedCellElement = nullptr;
+  if (aFirstSelectedRange) {
+    *aFirstSelectedRange = nullptr;
+  }
+
+  // XXX Oddly, when there is no ranges of Selection, we return error.
+  //     However, despite of that we return NS_SUCCESS_EDITOR_ELEMENT_NOT_FOUND
+  //     when first range of Selection does not select a table cell element.
   RefPtr<Selection> selection = GetSelection();
-  NS_ENSURE_TRUE(selection, NS_ERROR_FAILURE);
-
-  RefPtr<nsRange> range = selection->GetRangeAt(0);
-  NS_ENSURE_TRUE(range, NS_ERROR_FAILURE);
-
-  mSelectedCellIndex = 0;
-
-  nsresult rv = GetCellFromRange(range, aCell);
-  // Failure here probably means selection is in a text node,
-  //  so there's no selected cell
-  if (NS_FAILED(rv)) {
-    return NS_SUCCESS_EDITOR_ELEMENT_NOT_FOUND;
+  if (NS_WARN_IF(!selection)) {
+    return NS_ERROR_FAILURE;
+  }
+
+  ErrorResult error;
+  RefPtr<Element> firstSelectedCellElement =
+    GetFirstSelectedTableCellElement(*selection, error);
+  if (NS_WARN_IF(error.Failed())) {
+    return error.StealNSResult();
   }
-  // No cell means range was collapsed (cell was deleted)
-  if (!*aCell) {
-    return NS_SUCCESS_EDITOR_ELEMENT_NOT_FOUND;
+
+  if (!firstSelectedCellElement) {
+    // Just not found.  Don't return error.
+    return NS_OK;
   }
-
-  if (aRange) {
-    range.forget(aRange);
+  firstSelectedCellElement.forget(aFirstSelectedCellElement);
+
+  if (aFirstSelectedRange) {
+    // Returns the first range only when the caller requested the range.
+    RefPtr<nsRange> firstRange = selection->GetRangeAt(0);
+    MOZ_ASSERT(firstRange);
+    firstRange.forget(aFirstSelectedRange);
   }
 
-  // Setup for next cell
-  mSelectedCellIndex = 1;
-
   return NS_OK;
 }
 
+already_AddRefed<Element>
+HTMLEditor::GetFirstSelectedTableCellElement(Selection& aSelection,
+                                             ErrorResult& aRv) const
+{
+  MOZ_ASSERT(!aRv.Failed());
+
+  nsRange* firstRange = aSelection.GetRangeAt(0);
+  if (NS_WARN_IF(!firstRange)) {
+    // XXX Why don't we treat "not found" in this case?
+    aRv.Throw(NS_ERROR_FAILURE);
+    return nullptr;
+  }
+
+  // XXX It must be unclear when this is reset...
+  mSelectedCellIndex = 0;
+
+  RefPtr<Element> selectedCell;
+  nsresult rv =
+    HTMLEditor::GetCellFromRange(firstRange, getter_AddRefs(selectedCell));
+  if (NS_FAILED(rv)) {
+    // This case occurs only when Selection is in a text node in normal cases.
+    return nullptr;
+  }
+  if (!selectedCell) {
+    // This case means that the range does not select only a cell.
+    // E.g., selects non-table cell element, selects two or more cells, or
+    //       does not select any cell element.
+    return nullptr;
+  }
+
+  // Setup for GetNextSelectedCell()
+  // XXX Oh, increment it now?  Rather than when GetNextSelectedCell() is
+  //     called?
+  mSelectedCellIndex = 1;
+
+  return selectedCell.forget();
+}
+
 NS_IMETHODIMP
 HTMLEditor::GetNextSelectedCell(nsRange** aRange,
                                 Element** aCell)
 {
   NS_ENSURE_TRUE(aCell, NS_ERROR_NULL_POINTER);
   *aCell = nullptr;
   if (aRange) {
     *aRange = nullptr;
@@ -3323,17 +3382,17 @@ HTMLEditor::GetNextSelectedCell(nsRange*
   }
 
   // Scan through ranges to find next valid selected cell
   RefPtr<nsRange> range;
   for (; mSelectedCellIndex < rangeCount; mSelectedCellIndex++) {
     range = selection->GetRangeAt(mSelectedCellIndex);
     NS_ENSURE_TRUE(range, NS_ERROR_FAILURE);
 
-    nsresult rv = GetCellFromRange(range, aCell);
+    nsresult rv = HTMLEditor::GetCellFromRange(range, aCell);
     // Failure here means the range doesn't contain a cell
     NS_ENSURE_SUCCESS(rv, NS_SUCCESS_EDITOR_ELEMENT_NOT_FOUND);
 
     // We found a selected cell
     if (*aCell) {
       break;
     }
 
@@ -3361,31 +3420,40 @@ HTMLEditor::GetFirstSelectedCellInTable(
   *aCell = nullptr;
   if (aRowIndex) {
     *aRowIndex = 0;
   }
   if (aColumnIndex) {
     *aColumnIndex = 0;
   }
 
-  RefPtr<Element> cell;
-  nsresult rv = GetFirstSelectedCell(nullptr, getter_AddRefs(cell));
-  NS_ENSURE_SUCCESS(rv, rv);
-  NS_ENSURE_TRUE(cell, NS_SUCCESS_EDITOR_ELEMENT_NOT_FOUND);
+  RefPtr<Selection> selection = GetSelection();
+  if (NS_WARN_IF(!selection)) {
+    return NS_ERROR_FAILURE;
+  }
+
+  ErrorResult error;
+  RefPtr<Element> firstSelectedCellElement =
+    GetFirstSelectedTableCellElement(*selection, error);
+  if (NS_WARN_IF(error.Failed())) {
+    return error.StealNSResult();
+  }
+  if (NS_WARN_IF(!firstSelectedCellElement)) {
+    return NS_SUCCESS_EDITOR_ELEMENT_NOT_FOUND;
+  }
 
   // We don't want to cell.forget() here, because we use "cell" below.
-  *aCell = do_AddRef(cell).take();
+  firstSelectedCellElement.forget(aCell);
 
   if (!aRowIndex && !aColumnIndex) {
     return NS_OK;
   }
 
   // Also return the row and/or column if requested
-  ErrorResult error;
-  CellIndexes cellIndexes(*cell, error);
+  CellIndexes cellIndexes(*firstSelectedCellElement, error);
   if (NS_WARN_IF(error.Failed())) {
     return error.StealNSResult();
   }
   if (aRowIndex) {
     *aRowIndex = cellIndexes.mRow;
   }
   if (aColumnIndex) {
     *aColumnIndex = cellIndexes.mColumn;
@@ -3483,23 +3551,27 @@ HTMLEditor::GetSelectedOrParentTableElem
 {
   NS_ENSURE_ARG_POINTER(aTableElement);
   NS_ENSURE_ARG_POINTER(aSelectedCount);
   *aTableElement = nullptr;
   aTagName.Truncate();
   *aSelectedCount = 0;
 
   RefPtr<Selection> selection = GetSelection();
-  NS_ENSURE_TRUE(selection, NS_ERROR_FAILURE);
+  if (NS_WARN_IF(!selection)) {
+    return NS_ERROR_FAILURE;
+  }
 
   // Try to get the first selected cell
-  RefPtr<Element> tableOrCellElement;
-  nsresult rv = GetFirstSelectedCell(nullptr,
-                                     getter_AddRefs(tableOrCellElement));
-  NS_ENSURE_SUCCESS(rv, rv);
+  ErrorResult error;
+  RefPtr<Element> tableOrCellElement =
+    GetFirstSelectedTableCellElement(*selection, error);
+  if (NS_WARN_IF(error.Failed())) {
+    return error.StealNSResult();
+  }
 
   if (tableOrCellElement) {
       // Each cell is in its own selection range,
       //  so count signals multiple-cell selection
       *aSelectedCount = selection->RangeCount();
       aTagName = NS_LITERAL_STRING("td");
   } else {
     nsCOMPtr<nsINode> anchorNode = selection->GetAnchorNode();
@@ -3545,104 +3617,114 @@ HTMLEditor::GetSelectedOrParentTableElem
 
 NS_IMETHODIMP
 HTMLEditor::GetSelectedCellsType(Element* aElement,
                                  uint32_t* aSelectionType)
 {
   NS_ENSURE_ARG_POINTER(aSelectionType);
   *aSelectionType = 0;
 
+  RefPtr<Selection> selection = GetSelection();
+  if (NS_WARN_IF(!selection)) {
+    return NS_ERROR_FAILURE;
+  }
+
   // Be sure we have a table element
   //  (if aElement is null, this uses selection's anchor node)
   RefPtr<Element> table;
   if (aElement) {
     table = GetElementOrParentByTagNameInternal(*nsGkAtoms::table, *aElement);
     if (NS_WARN_IF(!table)) {
       return NS_ERROR_FAILURE;
     }
   } else {
-    RefPtr<Selection> selection = GetSelection();
-    if (NS_WARN_IF(!selection)) {
-      return NS_ERROR_FAILURE;
-    }
     table =
       GetElementOrParentByTagNameAtSelection(*selection, *nsGkAtoms::table);
     if (NS_WARN_IF(!table)) {
       return NS_ERROR_FAILURE;
     }
   }
 
   ErrorResult error;
   TableSize tableSize(*this, *table, error);
   if (NS_WARN_IF(error.Failed())) {
     return error.StealNSResult();
   }
 
   // Traverse all selected cells
-  RefPtr<Element> selectedCell;
-  nsresult rv = GetFirstSelectedCell(nullptr, getter_AddRefs(selectedCell));
-  NS_ENSURE_SUCCESS(rv, rv);
-  if (rv == NS_SUCCESS_EDITOR_ELEMENT_NOT_FOUND) {
+  RefPtr<Element> selectedCell =
+    GetFirstSelectedTableCellElement(*selection, error);
+  if (NS_WARN_IF(error.Failed())) {
+    return error.StealNSResult();
+  }
+  if (!selectedCell) {
     return NS_OK;
   }
 
   // We have at least one selected cell, so set return value
   *aSelectionType = static_cast<uint32_t>(TableSelection::Cell);
 
   // Store indexes of each row/col to avoid duplication of searches
   nsTArray<int32_t> indexArray;
 
   bool allCellsInRowAreSelected = false;
   bool allCellsInColAreSelected = false;
-  while (NS_SUCCEEDED(rv) && selectedCell) {
+  while (selectedCell) {
     CellIndexes selectedCellIndexes(*selectedCell, error);