Merge mozilla-central to inbound. a=merge CLOSED TREE
authorBogdan Tara <btara@mozilla.com>
Wed, 15 Aug 2018 20:58:28 +0300
changeset 431776 0fd6d03ce4a1dff0d9d3fe9904cd940532a4a1a8
parent 431775 3999df394b25906c656d7329ab15043ffaa0be7f (current diff)
parent 431633 2cc6ec2f7f0e1f945858b493133c6a26c0b4c09f (diff)
child 431777 da3100049ef2c03f287fc700b620f6b622aa4749
push id34451
push userebalazs@mozilla.com
push dateThu, 16 Aug 2018 09:25:15 +0000
treeherdermozilla-central@161817e6d127 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmerge
milestone63.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Merge mozilla-central to inbound. a=merge CLOSED TREE
devtools/client/netmonitor/src/assets/icons/drop-down.svg
devtools/client/responsive.html/images/select-arrow.svg
testing/web-platform/meta/xhr/overridemimetype-blob.html.ini
--- a/browser/base/content/test/performance/browser_preferences_usage.js
+++ b/browser/base/content/test/performance/browser_preferences_usage.js
@@ -81,17 +81,17 @@ add_task(async function startup() {
       min: 135,
       max: 170,
     },
     "layout.css.dpi": {
       min: 45,
       max: 75,
     },
     "network.loadinfo.skip_type_assertion": {
-      max: 658,
+      // This is accessed in debug only.
     },
     "extensions.getAddons.cache.enabled": {
       min: 7,
       max: 55,
     },
   };
 
   let startupRecorder = Cc["@mozilla.org/test/startuprecorder;1"].getService().wrappedJSObject;
@@ -136,17 +136,17 @@ add_task(async function open_10_tabs() {
     },
     "browser.startup.record": {
       max: 20,
     },
     "browser.tabs.remote.logSwitchTiming": {
       max: 25,
     },
     "network.loadinfo.skip_type_assertion": {
-      max: 70,
+      // This is accessed in debug only.
     },
     "toolkit.cosmeticAnimations.enabled": {
       min: 5,
       max: 20,
     },
   };
 
   Services.prefs.resetStats();
@@ -168,17 +168,17 @@ add_task(async function navigate_around(
   let max = 40;
 
   let whitelist = {
     "browser.zoom.full": {
       min: 100,
       max: 110,
     },
     "network.loadinfo.skip_type_assertion": {
-      max: 130,
+      // This is accessed in debug only.
     },
     "security.insecure_connection_icon.pbmode.enabled": {
       min: 20,
       max: 30,
     },
     "security.insecure_connection_icon.enabled": {
       min: 20,
       max: 30,
--- a/browser/components/extensions/test/browser/browser_ext_browserAction_popup_resize.js
+++ b/browser/components/extensions/test/browser/browser_ext_browserAction_popup_resize.js
@@ -3,16 +3,20 @@
 "use strict";
 
 function openPanel(extension, win = window, awaitLoad = false) {
   clickBrowserAction(extension, win);
 
   return awaitExtensionPanel(extension, win, awaitLoad);
 }
 
+add_task(async function testSetup() {
+  Services.prefs.setBoolPref("toolkit.cosmeticAnimations.enabled", false);
+});
+
 add_task(async function testBrowserActionPopupResize() {
   let extension = ExtensionTestUtils.loadExtension({
     manifest: {
       "browser_action": {
         "default_popup": "popup.html",
         "browser_style": true,
       },
     },
@@ -58,20 +62,16 @@ add_task(async function testBrowserActio
   }
 
   await closeBrowserAction(extension);
   await extension.unload();
 });
 
 async function testPopupSize(standardsMode, browserWin = window, arrowSide = "top") {
   let docType = standardsMode ? "<!DOCTYPE html>" : "";
-  let overflowView = browserWin.document.getElementById("widget-overflow-mainView");
-  if (overflowView) {
-    overflowView.style.minHeight = "600px";
-  }
 
   let extension = ExtensionTestUtils.loadExtension({
     manifest: {
       "browser_action": {
         "default_popup": "popup.html",
         "browser_style": false,
       },
     },
@@ -148,16 +148,18 @@ async function testPopupSize(standardsMo
   }
 
 
   // Test the PanelUI panel for a menu panel button.
   let widget = getBrowserActionWidget(extension);
   CustomizableUI.addWidgetToArea(widget.id, getCustomizableUIPanelID());
 
   let panel = browserWin.PanelUI.overflowPanel;
+  panel.setAttribute("animate", "false");
+
   let panelMultiView = panel.firstChild;
   let widgetId = makeWidgetId(extension.id);
   // The 'ViewShown' event is the only way to correctly determine when the extensions'
   // panelview has finished transitioning and is fully in view.
   let shownPromise = BrowserTestUtils.waitForEvent(panelMultiView, "ViewShown",
                                                    e => (e.originalTarget.id || "").includes(widgetId));
   let browser = await openPanel(extension, browserWin);
   let origPanelRect = panel.getBoundingClientRect();
@@ -180,16 +182,17 @@ async function testPopupSize(standardsMo
 
       let panelTop = browserWin.mozInnerScreenY + panelRect.top;
       ok(panelTop >= browserWin.screen.availTop, `Top of popup should be on-screen. (${panelTop} >= ${browserWin.screen.availTop})`);
     }
   };
 
   await awaitBrowserLoaded(browser);
   await shownPromise;
+
   // Wait long enough to make sure the initial resize debouncing timer has
   // expired.
   await delay(500);
 
   let dims = await promiseContentDimensions(browser);
 
   is(dims.isStandards, standardsMode, "Document has the expected compat mode");
 
@@ -264,19 +267,16 @@ async function testPopupSize(standardsMo
   is(win.innerWidth, innerWidth, "Window width should not change");
   is(win.innerHeight, innerHeight, "Window height should return to its original value");
   Assert.lessOrEqual(win.scrollMaxY, 1, "Document should not be vertically scrollable");
 
   checkPanelPosition();
 
   await closeBrowserAction(extension, browserWin);
 
-  if (overflowView) {
-    overflowView.style.removeProperty("min-height");
-  }
   await extension.unload();
 }
 
 add_task(async function testBrowserActionMenuResizeStandards() {
   await testPopupSize(true);
 });
 
 add_task(async function testBrowserActionMenuResizeQuirks() {
@@ -311,8 +311,12 @@ add_task(async function testBrowserActio
   }
 
   await SimpleTest.promiseFocus(win);
 
   await testPopupSize(true, win, "bottom");
 
   await BrowserTestUtils.closeWindow(win);
 });
+
+add_task(async function testTeardown() {
+  Services.prefs.clearUserPref("toolkit.cosmeticAnimations.enabled");
+});
--- a/browser/themes/shared/sidebar.inc.css
+++ b/browser/themes/shared/sidebar.inc.css
@@ -2,17 +2,16 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 %endif
 
 .sidebar-header,
 #sidebar-header {
   font-size: 1.333em;
-  font-weight: lighter;
   padding: 8px;
 }
 
 %ifndef MOZ_WIDGET_GTK
 % We don't let the splitter overlap the sidebar on Linux since the sidebar's
 % scrollbar is too narrow on Linux.
 .sidebar-splitter {
   -moz-appearance: none;
--- a/build/build-clang/build-clang.py
+++ b/build/build-clang/build-clang.py
@@ -351,16 +351,19 @@ if __name__ == "__main__":
     parser.add_argument('-b', '--base-dir', required=False,
                         help="Base directory for code and build artifacts")
     parser.add_argument('--clean', required=False,
                         action='store_true',
                         help="Clean the build directory")
     parser.add_argument('--skip-tar', required=False,
                         action='store_true',
                         help="Skip tar packaging stage")
+    parser.add_argument('--skip-checkout', required=False,
+                        action='store_true',
+                        help="Do not checkout/revert source")
 
     args = parser.parse_args()
 
     # The directories end up in the debug info, so the easy way of getting
     # a reproducible build is to run it in a know absolute directory.
     # We use a directory that is registered as a volume in the Docker image.
 
     if args.base_dir:
@@ -482,28 +485,29 @@ if __name__ == "__main__":
         os.makedirs(source_dir)
 
     def checkout_or_update(repo, checkout_dir):
         if os.path.exists(checkout_dir):
             svn_update(checkout_dir, llvm_revision)
         else:
             svn_co(source_dir, repo, checkout_dir, llvm_revision)
 
-    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)
-    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", []):
-        patch(p, source_dir)
+    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)
+        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", []):
+            patch(p, source_dir)
 
     symlinks = [(clang_source_dir,
                  llvm_source_dir + "/tools/clang"),
                 (extra_source_dir,
                  llvm_source_dir + "/tools/clang/tools/extra"),
                 (lld_source_dir,
                  llvm_source_dir + "/tools/lld"),
                 (compiler_rt_source_dir,
--- a/devtools/client/inspector/fonts/components/FontEditor.js
+++ b/devtools/client/inspector/fonts/components/FontEditor.js
@@ -53,54 +53,48 @@ class FontEditor extends PureComponent {
     } else if (delta <= 100) {
       step = 0.1;
     }
 
     return step.toString();
   }
 
   /**
-   * Get a container with the rendered FontPropertyValue components with editing controls
+   * Get an array of FontPropertyValue components with editing controls
    * for of the given variable font axes. If no axes were given, return null.
    * If an axis has a value in the fontEditor store (i.e.: it was declared in CSS or
    * it was changed using the font editor), use its value, otherwise use the font axis
    * default.
    *
    * @param  {Array} fontAxes
    *         Array of font axis instances
    * @param  {Object} editedAxes
    *         Object with axes and values edited by the user or predefined in the CSS
    *         declaration for font-variation-settings.
-   * @return {DOMNode|null}
+   * @return {Array|null}
    */
   renderAxes(fontAxes = [], editedAxes) {
     if (!fontAxes.length) {
       return null;
     }
 
-    const controls = fontAxes.map(axis => {
+    return fontAxes.map(axis => {
       return FontPropertyValue({
         key: axis.tag,
+        className: "font-control-axis",
+        label: axis.name,
         min: axis.minValue,
         max: axis.maxValue,
-        value: editedAxes[axis.tag] || axis.defaultValue,
-        step: this.getAxisStep(axis.minValue, axis.maxValue),
-        label: axis.name,
         name: axis.tag,
         onChange: this.props.onPropertyChange,
-        unit: null
+        step: this.getAxisStep(axis.minValue, axis.maxValue),
+        unit: null,
+        value: editedAxes[axis.tag] || axis.defaultValue,
       });
     });
-
-    return dom.div(
-      {
-        className: "font-axes-controls"
-      },
-      controls
-    );
   }
 
   renderFamilesNotUsed(familiesNotUsed = []) {
     if (!familiesNotUsed.length) {
       return null;
     }
 
     const familiesList = familiesNotUsed.map(family => {
@@ -252,17 +246,17 @@ class FontEditor extends PureComponent {
         },
         instance.name
       )
     );
 
     // Generate the dropdown.
     const instanceSelect = dom.select(
       {
-        className: "font-control-input",
+        className: "font-control-input font-value-select",
         onChange: (e) => {
           const instance = fontInstances.find(inst => e.target.value === inst.name);
           instance && this.props.onInstanceChange(instance.name, instance.values);
         }
       },
       instanceOptions
     );
 
--- a/devtools/client/inspector/fonts/components/FontPropertyValue.js
+++ b/devtools/client/inspector/fonts/components/FontPropertyValue.js
@@ -12,16 +12,17 @@ const { KeyCodes } = require("devtools/c
 // Milliseconds between auto-increment interval iterations.
 const AUTOINCREMENT_DELAY = 300;
 const UNITS = ["em", "rem", "%", "px", "vh", "vw"];
 
 class FontPropertyValue extends PureComponent {
   static get propTypes() {
     return {
       allowAutoIncrement: PropTypes.bool,
+      className: PropTypes.string,
       defaultValue: PropTypes.oneOfType([ PropTypes.string, PropTypes.number ]),
       label: PropTypes.string.isRequired,
       min: PropTypes.oneOfType([ PropTypes.string, PropTypes.number ]),
       max: PropTypes.oneOfType([ PropTypes.string, PropTypes.number ]),
       name: PropTypes.string.isRequired,
       onChange: PropTypes.func.isRequired,
       showUnit: PropTypes.bool,
       step: PropTypes.oneOfType([ PropTypes.string, PropTypes.number ]),
@@ -283,34 +284,34 @@ class FontPropertyValue extends PureComp
       // The unit conversion function will use a 1-to-1 scale for unrecognized units.
       const options = UNITS.includes(this.props.unit) ?
         UNITS
         :
         UNITS.concat([this.props.unit]);
 
       unitDropdown = dom.select(
         {
-          className: "font-unit-select",
+          className: "font-value-select",
           onChange: this.onUnitChange,
         },
         options.map(unit => {
           return dom.option(
             {
               selected: unit === this.props.unit,
               value: unit,
             },
             unit
           );
         })
       );
     }
 
     return dom.label(
       {
-        className: "font-control",
+        className: `font-control ${this.props.className || ""}`,
       },
       dom.span(
         {
           className: "font-control-label",
         },
         this.props.label
       ),
       dom.div(
--- a/devtools/client/inspector/fonts/test/browser_fontinspector_editor-values.js
+++ b/devtools/client/inspector/fonts/test/browser_fontinspector_editor-values.js
@@ -9,32 +9,22 @@ add_task(async function() {
   await pushPref("devtools.inspector.fonteditor.enabled", true);
   const { inspector, view } = await openFontInspectorForURL(TEST_URI);
   const viewDoc = view.document;
 
   await testDiv(inspector, viewDoc);
   await testNestedSpan(inspector, viewDoc);
 });
 
-function getFontSize(viewDoc) {
-  const number =
-    viewDoc.querySelector("#font-editor .font-value-slider[name=font-size]").value;
-  const units =
-    viewDoc.querySelector(`#font-editor .font-value-slider[name=font-size]
-    ~ .font-unit-select`).value;
-
-  return number + units;
-}
-
 async function testDiv(inspector, viewDoc) {
   await selectNode("DIV", inspector);
-  const fontSize = getFontSize(viewDoc);
+  const { value, unit } = getPropertyValue(viewDoc, "font-size");
 
-  is(fontSize, "1em", "DIV should be have font-size of 1em");
+  is(value + unit, "1em", "DIV should be have font-size of 1em");
 }
 
 async function testNestedSpan(inspector, viewDoc) {
   await selectNode(".nested-span", inspector);
-  const fontSize = getFontSize(viewDoc);
+  const { value, unit } = getPropertyValue(viewDoc, "font-size");
 
-  isnot(fontSize, "1em", "Nested span should not reflect parent's font size.");
-  is(fontSize, "36px", "Nested span should have computed font-size of 36px");
+  isnot(value + unit, "1em", "Nested span should not reflect parent's font size.");
+  is(value + unit, "36px", "Nested span should have computed font-size of 36px");
 }
--- a/devtools/client/inspector/fonts/test/head.js
+++ b/devtools/client/inspector/fonts/test/head.js
@@ -224,18 +224,18 @@ function getFamilyName(fontEl) {
  */
 function getPropertyValue(viewDoc, name) {
   const selector = `#font-editor .font-value-slider[name=${name}]`;
   return {
     // Ensure value input exists before querying its value
     value: viewDoc.querySelector(selector) &&
            parseFloat(viewDoc.querySelector(selector).value),
     // Ensure unit dropdown exists before querying its value
-    unit: viewDoc.querySelector(selector + ` ~ .font-unit-select`) &&
-          viewDoc.querySelector(selector + ` ~ .font-unit-select`).value
+    unit: viewDoc.querySelector(selector + ` ~ .font-value-select`) &&
+          viewDoc.querySelector(selector + ` ~ .font-value-select`).value
   };
 }
 
 /**
  * Wait for a predicate to return a result.
  *
  * @param  {Function} condition
  *         Invoked every 10ms for a maximum of 500 retries until it returns a truthy
--- a/devtools/client/jar.mn
+++ b/devtools/client/jar.mn
@@ -259,16 +259,17 @@ devtools.jar:
     skin/images/security-state-weak.svg (themes/images/security-state-weak.svg)
     skin/images/diff.svg (themes/images/diff.svg)
     skin/images/import.svg (themes/images/import.svg)
     skin/images/pane-collapse.svg (themes/images/pane-collapse.svg)
     skin/images/pane-expand.svg (themes/images/pane-expand.svg)
     skin/images/help.svg (themes/images/help.svg)
     skin/images/read-only.svg (themes/images/read-only.svg)
     skin/images/reveal.svg (themes/images/reveal.svg)
+    skin/images/select-arrow.svg (themes/images/select-arrow.svg)
 
     # Debugger
     skin/images/debugger/angular.svg (themes/images/debugger/angular.svg)
     skin/images/debugger/arrow.svg (themes/images/debugger/arrow.svg)
     skin/images/debugger/back.svg (themes/images/debugger/back.svg)
     skin/images/debugger/blackBox.svg (themes/images/debugger/blackBox.svg)
     skin/images/debugger/breakpoint.svg (themes/images/debugger/breakpoint.svg)
     skin/images/debugger/close.svg (themes/images/debugger/close.svg)
@@ -296,17 +297,16 @@ devtools.jar:
     content/netmonitor/src/assets/styles/netmonitor.css (netmonitor/src/assets/styles/netmonitor.css)
     content/netmonitor/src/assets/styles/NetworkDetailsPanel.css (netmonitor/src/assets/styles/NetworkDetailsPanel.css)
     content/netmonitor/src/assets/styles/RequestList.css (netmonitor/src/assets/styles/RequestList.css)
     content/netmonitor/src/assets/styles/StatisticsPanel.css (netmonitor/src/assets/styles/StatisticsPanel.css)
     content/netmonitor/src/assets/styles/StatusBar.css (netmonitor/src/assets/styles/StatusBar.css)
     content/netmonitor/src/assets/styles/Toolbar.css (netmonitor/src/assets/styles/Toolbar.css)
     content/netmonitor/src/assets/styles/variables.css (netmonitor/src/assets/styles/variables.css)
     content/netmonitor/src/assets/icons/play.svg (netmonitor/src/assets/icons/play.svg)
-    content/netmonitor/src/assets/icons/drop-down.svg (netmonitor/src/assets/icons/drop-down.svg)
     content/netmonitor/index.html (netmonitor/index.html)
 
     # Application panel
     content/application/index.html (application/index.html)
 
     # Devtools-components
     skin/images/devtools-components/arrow.svg (themes/images/devtools-components/arrow.svg)
 
--- a/devtools/client/netmonitor/src/assets/styles/Toolbar.css
+++ b/devtools/client/netmonitor/src/assets/styles/Toolbar.css
@@ -75,17 +75,17 @@
 
 .devtools-button.devtools-har-button {
   margin: 0 0 0 2px;
   padding: 0;
 }
 
 /* style for dropdown button */
 .devtools-drop-down-button {
-  background-image: var(--drop-down-icon-url)  !important;
+  background-image: var(--select-arrow-image)  !important;
   background-repeat: no-repeat !important;
   margin-inline-start: 6px;
   fill: var(--theme-toolbar-photon-icon-color);
   -moz-context-properties: fill;
 }
 
 /* style for title holder inside a dropdown button */
 .devtools-drop-down-button .title {
--- a/devtools/client/netmonitor/src/assets/styles/variables.css
+++ b/devtools/client/netmonitor/src/assets/styles/variables.css
@@ -37,17 +37,16 @@
 }
 
 :root {
   --primary-toolbar-height: 29px;
 
   /* Icons */
   --play-icon-url: url("chrome://devtools/content/netmonitor/src/assets/icons/play.svg");
   --pause-icon-url: url("chrome://devtools/skin/images/pause.svg");
-  --drop-down-icon-url: url("chrome://devtools/content/netmonitor/src/assets/icons/drop-down.svg");
 
   /* HTTP status codes */
   --status-code-color-1xx: var(--theme-highlight-blue);
   --status-code-color-2xx: var(--theme-highlight-green);
   --status-code-color-3xx: transparent;
   --status-code-color-4xx: var(--theme-highlight-pink);
   --status-code-color-5xx: var(--theme-highlight-red);
 }
--- a/devtools/client/netmonitor/src/connector/firefox-connector.js
+++ b/devtools/client/netmonitor/src/connector/firefox-connector.js
@@ -437,16 +437,18 @@ class FirefoxConnector {
       const data = throttlingProfiles.find(({ id }) => id == profile);
       const { download, upload, latency } = data;
       await this.emulationFront.setNetworkThrottling({
         downloadThroughput: download,
         uploadThroughput: upload,
         latency,
       });
     }
+
+    this.emit(EVENTS.THROTTLING_CHANGED, { profile });
   }
 
   /**
    * Fire events for the owner object.
    */
   emit(type, data) {
     if (this.owner) {
       this.owner.emit(type, data);
--- a/devtools/client/netmonitor/src/constants.js
+++ b/devtools/client/netmonitor/src/constants.js
@@ -100,16 +100,19 @@ const EVENTS = {
   UPDATING_RESPONSE_CACHE: "NetMonitor:NetworkEventUpdating:ResponseCache",
   RECEIVED_RESPONSE_CACHE: "NetMonitor:NetworkEventUpdated:ResponseCache",
 
   // Fired once the connection is established
   CONNECTED: "connected",
 
   // When request payload (HTTP details data) are fetched from the backend.
   PAYLOAD_READY: "NetMonitor:PayloadReady",
+
+  // When throttling is set on the backend.
+  THROTTLING_CHANGED: "NetMonitor:ThrottlingChanged",
 };
 
 const UPDATE_PROPS = [
   "method",
   "url",
   "remotePort",
   "remoteAddress",
   "status",
--- a/devtools/client/netmonitor/src/middleware/event-telemetry.js
+++ b/devtools/client/netmonitor/src/middleware/event-telemetry.js
@@ -9,16 +9,20 @@ const { gDevTools } = require("devtools/
 const {
   TOGGLE_REQUEST_FILTER_TYPE,
   ENABLE_REQUEST_FILTER_TYPE_ONLY,
   SET_REQUEST_FILTER_TEXT,
   SELECT_DETAILS_PANEL_TAB,
   SEND_CUSTOM_REQUEST,
 } = require("../constants");
 
+const {
+  CHANGE_NETWORK_THROTTLING,
+} = require("devtools/client/shared/components/throttling/actions");
+
 /**
  * Event telemetry middleware is responsible for logging
  * various events to telemetry. This helps to track Network
  * panel usage.
  */
 function eventTelemetryMiddleware(connector, telemetry) {
   return store => next => action => {
     const oldState = store.getState();
@@ -61,16 +65,25 @@ function eventTelemetryMiddleware(connec
     // Record telemetry event when a request is resent.
     if (action.type == SEND_CUSTOM_REQUEST) {
       sendCustomRequest({
         telemetry,
         sessionId,
       });
     }
 
+    // Record telemetry event when throttling is changed.
+    if (action.type == CHANGE_NETWORK_THROTTLING) {
+      throttlingChange({
+        action,
+        telemetry,
+        sessionId,
+      });
+    }
+
     return res;
   };
 }
 
 /**
  * This helper function is executed when filter related action is fired.
  * It's responsible for recording "filters_changed" telemetry event.
  */
@@ -127,9 +140,20 @@ function sidePanelChange({state, oldStat
  * It's responsible for recording "edit_resend" telemetry event.
  */
 function sendCustomRequest({telemetry, sessionId}) {
   telemetry.recordEvent("devtools.main", "edit_resend", "netmonitor", null, {
     "session_id": sessionId,
   });
 }
 
+/**
+ * This helper function is executed when network throttling is changed.
+ * It's responsible for recording "throttle_changed" telemetry event.
+ */
+function throttlingChange({action, telemetry, sessionId}) {
+  telemetry.recordEvent("devtools.main", "throttle_changed", "netmonitor", null, {
+    "mode": action.profile,
+    "session_id": sessionId,
+  });
+}
+
 module.exports = eventTelemetryMiddleware;
--- a/devtools/client/netmonitor/test/browser.ini
+++ b/devtools/client/netmonitor/test/browser.ini
@@ -183,15 +183,16 @@ skip-if = true # Bug 1258809
 skip-if = true # Bug 1373558
 [browser_net_statistics-02.js]
 [browser_net_status-bar.js]
 [browser_net_status-codes.js]
 [browser_net_streaming-response.js]
 [browser_net_telemetry_edit_resend.js]
 [browser_net_telemetry_filters_changed.js]
 [browser_net_telemetry_sidepanel_changed.js]
+[browser_net_telemetry_throttle_changed.js]
 [browser_net_throttle.js]
 [browser_net_timeline_ticks.js]
 skip-if = true # TODO: fix the test
 [browser_net_timing-division.js]
 [browser_net_truncate.js]
 [browser_net_view-source-debugger.js]
 [browser_net_waterfall-click.js]
new file mode 100644
--- /dev/null
+++ b/devtools/client/netmonitor/test/browser_net_telemetry_throttle_changed.js
@@ -0,0 +1,40 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+const OPTOUT = Ci.nsITelemetry.DATASET_RELEASE_CHANNEL_OPTOUT;
+
+/**
+ * Test the throttle_change telemetry event.
+ */
+add_task(async function() {
+  const { monitor } = await initNetMonitor(SIMPLE_URL);
+  info("Starting test... ");
+
+  const { document, store, windowRequire } = monitor.panelWin;
+  const Actions = windowRequire("devtools/client/netmonitor/src/actions/index");
+  store.dispatch(Actions.batchEnable(false));
+
+  // Remove all telemetry events.
+  Services.telemetry.clearEvents();
+
+  // Ensure no events have been logged
+  const snapshot = Services.telemetry.snapshotEvents(OPTOUT, true);
+  ok(!snapshot.parent, "No events have been logged for the main process");
+
+  document.querySelector("#global-network-throttling-selector").click();
+  monitor.panelWin.parent.document.querySelector("menuitem[label='GPRS']").click();
+  await waitFor(monitor.panelWin.api, EVENTS.THROTTLING_CHANGED);
+
+  // Verify existence of the telemetry event.
+  checkTelemetryEvent({
+    mode: "GPRS",
+  }, {
+    method: "throttle_changed",
+  });
+
+  return teardown(monitor);
+});
--- a/devtools/client/responsive.html/images/moz.build
+++ b/devtools/client/responsive.html/images/moz.build
@@ -3,11 +3,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(
     'grippers.svg',
     'rotate-viewport.svg',
     'screenshot.svg',
-    'select-arrow.svg',
     'touch-events.svg',
 )
deleted file mode 100644
--- a/devtools/client/responsive.html/images/select-arrow.svg
+++ /dev/null
@@ -1,8 +0,0 @@
-<!-- This Source Code Form is subject to the terms of the Mozilla Public
-   - License, v. 2.0. If a copy of the MPL was not distributed with this
-   - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
-<svg xmlns="http://www.w3.org/2000/svg"
-     width="16" height="16" viewBox="0 0 16 16">
-  <path fill="context-fill" d="M7.9 16.3c-.3 0-.6-.1-.8-.4l-4-4.8c-.2-.3-.3-.5-.1-.8.1-.3.5-.3.9-.3h8c.4 0 .7 0 .9.3.2.4.1.6-.1.9l-4 4.8c-.2.3-.5.3-.8.3zM7.8 0c.3 0 .6.1.7.4L12.4 5c.2.3.3.4.1.7-.1.4-.5.3-.8.3H3.9c-.4 0-.8.1-.9-.2-.2-.4-.1-.6.1-.9L7 .3c.2-.3.5-.3.8-.3z"/>
-</svg>
-
--- a/devtools/client/responsive.html/index.css
+++ b/devtools/client/responsive.html/index.css
@@ -7,27 +7,25 @@
 
 .theme-light {
   --rdm-box-shadow: 0 4px 4px 0 rgba(155, 155, 155, 0.26);
   --submit-button-active-background-color: rgba(0,0,0,0.12);
   --submit-button-active-color: var(--theme-body-color);
   --viewport-color: #999797;
   --viewport-hover-color: var(--theme-body-color);
   --viewport-active-color: #3b3b3b;
-  --viewport-selection-arrow: url("./images/select-arrow.svg");
 }
 
 .theme-dark {
   --rdm-box-shadow: 0 4px 4px 0 rgba(105, 105, 105, 0.26);
   --submit-button-active-background-color: var(--theme-toolbar-hover-active);
   --submit-button-active-color: var(--theme-selection-color);
   --viewport-color: #c6ccd0;
   --viewport-hover-color: #dde1e4;
   --viewport-active-color: #fcfcfc;
-  --viewport-selection-arrow: url("./images/select-arrow.svg");
 }
 
 * {
   box-sizing: border-box;
 }
 
 :root,
 input,
@@ -166,17 +164,17 @@ select > option.divider {
 
 /**
  * Common background for dropdowns like select and toggle menu
  */
 .toolbar-dropdown,
 .toolbar-dropdown.devtools-button,
 .toolbar-dropdown.devtools-button:hover:not(:empty):not(:disabled):not(.checked) {
   background-color: var(--theme-toolbar-background);
-  background-image: var(--viewport-selection-arrow);
+  background-image: var(--select-arrow-image);
   background-position: 100% 50%;
   background-repeat: no-repeat;
   background-size: 7px;
   -moz-context-properties: fill;
   fill: currentColor;
 }
 
 /**
--- a/devtools/client/themes/fonts.css
+++ b/devtools/client/themes/fonts.css
@@ -1,36 +1,61 @@
 /* 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/. */
 
+/* CSS Variables specific to the font editor that aren't defined by the themes */
+:root {
+  --slider-thumb-color: var(--grey-50);
+  --slider-track-color: var(--grey-30);
+  --toggle-thumb-color: white;
+  --toggle-track-color: var(--grey-30);
+  --input-background-color: white;
+  --input-border-color: var(--grey-30);
+  --input-text-color: var(--grey-90);
+}
+
+:root.theme-dark {
+  --slider-thumb-color: var(--grey-40);
+  --slider-track-color: var(--grey-50);
+  --toggle-thumb-color: var(--grey-40);
+  --toggle-track-color: var(--grey-50);
+  --input-background-color: var(--grey-70);
+  --input-border-color: var(--grey-70);
+  --input-text-color: var(--grey-40);
+}
+
 #sidebar-panel-fontinspector {
   margin: 0;
   display: flex;
   flex-direction: column;
   width: 100%;
   height: 100%;
   overflow: auto;
 }
 
 #font-container {
   flex: auto;
 }
 
 #font-editor {
-  padding-bottom: .8em;
+  padding-bottom: .5em;
 }
 
 #font-editor summary {
   -moz-user-select: none;
   cursor: pointer;
-  margin-bottom: .7em;
+  margin-bottom: .4em;
   width: -moz-fit-content;
 }
 
+#font-editor details {
+  padding-bottom: .5em;
+}
+
 #font-editor details .label-open,
 #font-editor details .label-close {
   display: none;
 }
 
 #font-editor details[open] .label-close {
   display: inline-block;
 }
@@ -42,17 +67,17 @@
 .fonts-list {
   padding: 0;
   margin: 0;
   list-style: none;
 }
 
 .font {
   border: 1px solid var(--theme-splitter-color);
-  border-width: 0 1px 1px 0;
+  border-width: 0 0 1px 0;
   display: grid;
   grid-template-columns: 1fr auto;
   grid-column-gap: 10px;
   padding: 10px 20px;
   overflow: auto;
 }
 
 #font-container .theme-twisty {
@@ -139,37 +164,45 @@
   color: var(--theme-body-color-inactive);
   border-radius: 3px;
   border-style: solid;
   border-width: 1px;
   text-align: center;
   vertical-align: middle;
 }
 
-.font-axes-controls {
-  border-top: 1px solid var(--theme-splitter-color);
-  margin-top: .8em;
-  padding-top: .8em;
-}
-
 .font-control {
   display: flex;
   flex-direction: row;
   flex-wrap: nowrap;
   justify-content: space-between;
   align-items: center;
-  padding: 4px 18px;
+  padding: 0 18px;
+  margin: .6em 0;
+}
+
+/* Style *all* axis controls with a top separator. See reset below. */
+.font-control-axis {
+  border-top: 1px solid var(--theme-splitter-color);
+  padding-top: 1.1em;
+}
+
+/* Remove styles form all axis controls aside from the first one.
+   Workaround for :first-of-type which doesn't work with class names. */
+.font-control-axis ~ .font-control-axis {
+  border-top: unset;
+  padding-top: unset;
 }
 
 .font-control-family {
   align-items: flex-start;
   border-bottom: 1px solid var(--theme-splitter-color);
-  margin-bottom: .8em;
-  padding-top: .6em;
-  padding-bottom: .4em;
+  margin-top: 0;
+  margin-bottom: 1em;
+  padding-top: 1em;
 }
 
 .font-control-box,
 .font-control-input {
   flex: 4;
   min-width: 100px;
 }
 
@@ -185,70 +218,105 @@
   font-size: 12px;
   max-width: 70px;
   margin-right: 10px;
   -moz-user-select: none;
 }
 
 .font-family-unused {
   margin-bottom: .3em;
+  margin-left: 1.15em;
   color: var(--grey-50);
 }
 
-.font-instance-select:active{
-  outline: none;
-}
-
 .font-value-input {
   margin-left: 10px;
   text-align: right;
   width: 60px;
+  padding: 2px 3px;
+}
+
+.font-value-input,
+.font-value-select {
+  color: var(--input-text-color);
+  border: 1px solid var(--input-border-color);
+  background-color: var(--input-background-color);
 }
 
 /* Do not use browser "invalid" state */
 .font-value-slider:-moz-ui-invalid,
 .font-value-input:-moz-ui-invalid {
   box-shadow: none;
 }
 
 /* Do not show dotted line focus outline */
 .font-value-input:-moz-focusring {
   outline: none;
 }
 
 /* Add space between input text from number stepper */
-.font-value-input[type=number]::-moz-number-spin-box{
+.font-value-input[type=number]::-moz-number-spin-box {
   margin-left: 3px;
 }
 
+/* Make native number steppers darker to fit the dark theme */
+.theme-dark .font-value-input[type=number]::-moz-number-spin-box {
+  filter: invert(25%);
+}
+
 /* Do not show number stepper for font-size */
 .font-value-input[name=font-size] {
   -moz-appearance: textfield;
-  margin-right: 5px;
   padding-right: 5px;
+  border-right: none;
+}
+
+/* Mock separator because inputs don't have distinguishable borders in dark theme */
+.theme-dark .font-value-input + .font-value-select {
+  margin-left: 2px;
 }
 
-.font-unit-select {
-  padding: 2px;
+/* Custom styles for <select> elements within the font editor. */
+.font-value-select {
+  background-image: var(--select-arrow-image);
+  background-repeat: no-repeat;
+  background-position: right 4px center;
+  fill: var(--theme-toolbar-photon-icon-color);
+  -moz-context-properties: fill;
+  -moz-appearance: none;
+  box-shadow: none;
+  padding: 1px 10px 1px 2px;
+}
+
+.font-value-select:-moz-focusring {
+  color: transparent;
+  text-shadow: 0 0 0 var(--input-text-color);
+}
+
+.font-value-input:focus,
+.font-value-select:focus {
+  outline: 1px solid var(--blue-55);
+  outline-offset: -1px;
 }
 
 .font-value-slider {
   flex: 1;
   margin: 0;
   min-width: 50px;
 }
 
 /*
   The value of font-weight goes from 100 to 900 in increments of 100.
   Decorate the slider for font-weight to have 9 vertical notches using a linear gradient.
 */
 .font-value-slider[name=font-weight] {
   --notch-size: 3px;
   /* Draw a vertical line to get one notch per background-image instance */
-  background-image: linear-gradient(90deg, var(--grey-30) var(--notch-size), transparent 0);
+  background-image: linear-gradient(90deg, var(--slider-track-color) var(--notch-size),
+   transparent 0);
   /* Offset the background so the notch aligns with the center of the slider thumb */
   background-position: 5px center;
   /* Repeat the background-image horizontally */
   background-repeat: repeat-x;
   /* Size the background to get nine visible notch instances. */
   background-size: calc(12.5% - var(--notch-size) / 2) 7px;
 }
 
@@ -256,65 +324,84 @@
   outline: none;
 }
 
 .font-value-slider::-moz-focus-outer {
   border: 0;
 }
 
 .font-value-slider::-moz-range-thumb {
-  background: var(--grey-50);
+  background-color: var(--slider-thumb-color);
   border: 0;
 }
 
+.font-value-slider:focus::-moz-range-thumb {
+  background-color: var(--blue-55);
+}
+
 .font-value-slider::-moz-range-track {
-  background: var(--grey-30);
+  background-color: var(--slider-track-color);
   height: 3px;
 }
 
 /*
   Restyle a native checkbox input to look like a toggle with a "thumb".
   Build the decoration using solid shapes created with radial- and linear-gradient
   background images. Animate the position of the "thumb" using background-position.
  */
 .font-value-toggle {
   --x-pos: .15em;
   /* Reset native checkbox styling. */
   -moz-appearance: none;
-  background-color: var(--grey-30);
+  background-color: var(--toggle-track-color);
   cursor: pointer;
   /* Change font-size to scale. */
   font-size: 16px;
   width: 2em;
   height: 1em;
   border-radius: 1em;
   /* Animate the thumb position between states of the checkbox. */
   transition: background-color .1s ease-out;
+  /* border: 1px solid transparent; */
+  /* box-sizing: content-box; */
+}
+
+.font-value-toggle:focus {
+  box-shadow: 0 0 0 1px var(--blue-55);
+}
+
+.font-value-toggle:checked:focus {
+  background-color: var(--blue-40);
+  box-shadow: none;
 }
 
 .font-value-toggle:checked {
   --x-pos: 1.15em;
   background-color: var(--blue-55);
 }
 
 .font-value-toggle::before {
   position: relative;
   width: calc(1em - .3em);
   height: calc(1em - .3em);
   transform: translateY(.15em) translateX(var(--x-pos));
   border-radius: 100%;
   display: block;
   content: "";
-  background-color: white;
+  background-color: var(--toggle-thumb-color);
   transition: transform .1s ease-out;
 }
 
+.font-value-toggle:checked::before {
+  background-color: white;
+}
+
 .font-origin {
-  margin-top: .2em;
-  color: var(--grey-50);
+  margin-top: -.25em;
+  color: var(--theme-comment);
   justify-self: start;
 }
 
 .font-origin.system {
   text-transform: capitalize;
 }
 
 .font-origin.remote {
@@ -336,19 +423,22 @@
   width: 12px;
   height: 12px;
   place-self: center;
 
   background: url(chrome://devtools/skin/images/copy.svg) no-repeat;
   background-size: 12px;
   background-position-x: -1px;
   -moz-context-properties: fill;
-  fill: var(--theme-toolbar-color);
-
+  fill: var(--grey-50);
 }
 
 #font-container .accordion {
   border-top: 1px solid var(--theme-splitter-color);
 }
 
+#font-container .accordion ._content {
+  padding: 0;
+}
+
 #font-container .accordion + .accordion {
   border-top: none;
 }
rename from devtools/client/netmonitor/src/assets/icons/drop-down.svg
rename to devtools/client/themes/images/select-arrow.svg
--- a/devtools/client/themes/variables.css
+++ b/devtools/client/themes/variables.css
@@ -76,20 +76,16 @@
   --theme-graphs-purple: #b693eb;
   --theme-graphs-yellow: #efc052;
   --theme-graphs-orange: #d97e00;
   --theme-graphs-red: #e57180;
   --theme-graphs-grey: #cccccc;
   --theme-graphs-full-red: #f00;
   --theme-graphs-full-blue: #00f;
 
-  /* Images */
-  --theme-pane-collapse-image: url(chrome://devtools/skin/images/pane-collapse.svg);
-  --theme-pane-expand-image: url(chrome://devtools/skin/images/pane-expand.svg);
-
   /* Icon filters */
   --theme-icon-checked-filter: url(chrome://devtools/skin/images/filters.svg#icon-checked-light);
 
   /* Tooltips */
   --theme-tooltip-border: #d9e1e8;
   --theme-tooltip-background: rgba(255, 255, 255, .9);
   --theme-tooltip-shadow: rgba(155, 155, 155, 0.26);
 
@@ -185,20 +181,16 @@
   --theme-graphs-purple: #df80ff;
   --theme-graphs-yellow: #d99b28;
   --theme-graphs-orange: #d96629;
   --theme-graphs-red: #eb5368;
   --theme-graphs-grey: #757873;
   --theme-graphs-full-red: #f00;
   --theme-graphs-full-blue: #00f;
 
-  /* Images */
-  --theme-pane-collapse-image: url(chrome://devtools/skin/images/pane-collapse.svg);
-  --theme-pane-expand-image: url(chrome://devtools/skin/images/pane-expand.svg);
-
   /* Icon filters */
   --theme-icon-checked-filter: url(chrome://devtools/skin/images/filters.svg#icon-checked-dark);
 
   /* Tooltips */
   --theme-tooltip-border: #434850;
   --theme-tooltip-background: rgba(19, 28, 38, .9);
   --theme-tooltip-shadow: rgba(25, 25, 25, 0.76);
 
@@ -239,16 +231,21 @@
   --toolbarbutton-checked-background: var(--theme-selection-background);
   --toolbarbutton-checked-color: var(--theme-selection-color);
   --toolbarbutton-checked-border-color: var(--toolbarbutton-border-color);
   --toolbarbutton-checked-focus-background: var(--blue-60);
 
   /* The photon animation curve */
   --animation-curve: cubic-bezier(.07,.95,0,1);
 
+  /* Images */
+  --select-arrow-image: url(chrome://devtools/skin/images/select-arrow.svg);
+  --theme-pane-collapse-image: url(chrome://devtools/skin/images/pane-collapse.svg);
+  --theme-pane-expand-image: url(chrome://devtools/skin/images/pane-expand.svg);
+
   /* Firefox Colors CSS Variables v1.0.3
    * Colors are taken from: https://github.com/FirefoxUX/design-tokens */
   --magenta-50: #ff1ad9;
   --magenta-65: #dd00a9;
   --magenta-70: #b5007f;
 
   --purple-50: #9400ff;
   --purple-60: #8000d7;
--- a/dom/base/CustomElementRegistry.cpp
+++ b/dom/base/CustomElementRegistry.cpp
@@ -5,16 +5,17 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "mozilla/dom/CustomElementRegistry.h"
 
 #include "mozilla/AsyncEventDispatcher.h"
 #include "mozilla/CycleCollectedJSContext.h"
 #include "mozilla/dom/CustomElementRegistryBinding.h"
 #include "mozilla/dom/HTMLElementBinding.h"
+#include "mozilla/dom/XULElementBinding.h"
 #include "mozilla/dom/Promise.h"
 #include "mozilla/dom/WebComponentsBinding.h"
 #include "mozilla/dom/DocGroup.h"
 #include "mozilla/dom/CustomEvent.h"
 #include "mozilla/dom/ShadowRoot.h"
 #include "nsHTMLTags.h"
 #include "jsapi.h"
 #include "xpcprivate.h"
@@ -351,16 +352,17 @@ CustomElementRegistry::RunCustomElementC
       "chrome JavaScript error in custom element construction.");
   }
 
   return NS_OK;
 }
 
 CustomElementDefinition*
 CustomElementRegistry::LookupCustomElementDefinition(nsAtom* aNameAtom,
+                                                     int32_t aNameSpaceID,
                                                      nsAtom* aTypeAtom)
 {
   CustomElementDefinition* data = mCustomDefinitions.GetWeak(aTypeAtom);
 
   if (!data) {
     RefPtr<CustomElementCreationCallback> callback;
     mElementCreationCallbacks.Get(aTypeAtom, getter_AddRefs(callback));
     if (callback) {
@@ -368,17 +370,17 @@ CustomElementRegistry::LookupCustomEleme
       mElementCreationCallbacksUpgradeCandidatesMap.LookupOrAdd(aTypeAtom);
       RefPtr<Runnable> runnable =
         new RunCustomElementCreationCallback(this, aTypeAtom, callback);
       nsContentUtils::AddScriptRunner(runnable);
       data = mCustomDefinitions.GetWeak(aTypeAtom);
     }
   }
 
-  if (data && data->mLocalName == aNameAtom) {
+  if (data && data->mLocalName == aNameAtom && data->mNamespaceID == aNameSpaceID) {
     return data;
   }
 
   return nullptr;
 }
 
 CustomElementDefinition*
 CustomElementRegistry::LookupCustomElementDefinition(JSContext* aCx,
@@ -683,16 +685,34 @@ nsISupports* CustomElementRegistry::GetP
 }
 
 DocGroup*
 CustomElementRegistry::GetDocGroup() const
 {
   return mWindow ? mWindow->GetDocGroup() : nullptr;
 }
 
+int32_t
+CustomElementRegistry::InferNamespace(JSContext* aCx,
+                                      JS::Handle<JSObject*> constructor)
+{
+  JSObject* XULConstructor = XULElement_Binding::GetConstructorObject(aCx);
+
+  JS::Rooted<JSObject*> proto(aCx, constructor);
+  while (proto) {
+    if (proto == XULConstructor) {
+      return kNameSpaceID_XUL;
+    }
+
+    JS_GetPrototype(aCx, proto, &proto);
+  }
+
+  return kNameSpaceID_XHTML;
+}
+
 // https://html.spec.whatwg.org/multipage/scripting.html#element-definition
 void
 CustomElementRegistry::Define(JSContext* aCx,
                               const nsAString& aName,
                               Function& aFunctionConstructor,
                               const ElementDefinitionOptions& aOptions,
                               ErrorResult& aRv)
 {
@@ -715,22 +735,23 @@ CustomElementRegistry::Define(JSContext*
     return;
   }
 
   if (!JS::IsConstructor(constructorUnwrapped)) {
     aRv.ThrowTypeError<MSG_NOT_CONSTRUCTOR>(NS_LITERAL_STRING("Argument 2 of CustomElementRegistry.define"));
     return;
   }
 
+  int32_t nameSpaceID = InferNamespace(aCx, constructor);
+
   /**
    * 2. If name is not a valid custom element name, then throw a "SyntaxError"
    *    DOMException and abort these steps.
    */
   nsIDocument* doc = mWindow->GetExtantDoc();
-  uint32_t nameSpaceID = doc ? doc->GetDefaultNamespaceID() : kNameSpaceID_XHTML;
   RefPtr<nsAtom> nameAtom(NS_Atomize(aName));
   if (!nsContentUtils::IsCustomElementName(nameAtom, nameSpaceID)) {
     aRv.Throw(NS_ERROR_DOM_SYNTAX_ERR);
     return;
   }
 
   /**
    * 3. If this CustomElementRegistry contains an entry with name name, then
@@ -940,16 +961,17 @@ CustomElementRegistry::Define(JSContext*
   if (!mConstructors.put(constructorUnwrapped, nameAtom)) {
     aRv.Throw(NS_ERROR_FAILURE);
     return;
   }
 
   RefPtr<CustomElementDefinition> definition =
     new CustomElementDefinition(nameAtom,
                                 localNameAtom,
+                                nameSpaceID,
                                 &aFunctionConstructor,
                                 std::move(observedAttributes),
                                 std::move(callbacksHolder));
 
   CustomElementDefinition* def = definition.get();
   mCustomDefinitions.Put(nameAtom, definition.forget());
 
   MOZ_ASSERT(mCustomDefinitions.Count() == mConstructors.count(),
@@ -1466,21 +1488,23 @@ NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN(Cus
 NS_IMPL_CYCLE_COLLECTION_TRACE_END
 
 NS_IMPL_CYCLE_COLLECTION_ROOT_NATIVE(CustomElementDefinition, AddRef)
 NS_IMPL_CYCLE_COLLECTION_UNROOT_NATIVE(CustomElementDefinition, Release)
 
 
 CustomElementDefinition::CustomElementDefinition(nsAtom* aType,
                                                  nsAtom* aLocalName,
+                                                 int32_t aNamespaceID,
                                                  Function* aConstructor,
                                                  nsTArray<RefPtr<nsAtom>>&& aObservedAttributes,
                                                  UniquePtr<LifecycleCallbacks>&& aCallbacks)
   : mType(aType),
     mLocalName(aLocalName),
+    mNamespaceID(aNamespaceID),
     mConstructor(new CustomElementConstructor(aConstructor)),
     mObservedAttributes(std::move(aObservedAttributes)),
     mCallbacks(std::move(aCallbacks))
 {
 }
 
 } // namespace dom
 } // namespace mozilla
--- a/dom/base/CustomElementRegistry.h
+++ b/dom/base/CustomElementRegistry.h
@@ -152,27 +152,31 @@ private:
 // https://html.spec.whatwg.org/multipage/scripting.html#custom-element-definition
 struct CustomElementDefinition
 {
   NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_NATIVE_CLASS(CustomElementDefinition)
   NS_INLINE_DECL_CYCLE_COLLECTING_NATIVE_REFCOUNTING(CustomElementDefinition)
 
   CustomElementDefinition(nsAtom* aType,
                           nsAtom* aLocalName,
+                          int32_t aNamespaceID,
                           Function* aConstructor,
                           nsTArray<RefPtr<nsAtom>>&& aObservedAttributes,
                           UniquePtr<LifecycleCallbacks>&& aCallbacks);
 
   // The type (name) for this custom element, for <button is="x-foo"> or <x-foo>
   // this would be x-foo.
   RefPtr<nsAtom> mType;
 
   // The localname to (e.g. <button is=type> -- this would be button).
   RefPtr<nsAtom> mLocalName;
 
+  // The namespace for this custom element
+  int32_t mNamespaceID;
+
   // The custom element constructor.
   RefPtr<CustomElementConstructor> mConstructor;
 
   // The list of attributes that this custom element observes.
   nsTArray<RefPtr<nsAtom>> mObservedAttributes;
 
   // The lifecycle callbacks to call for this custom element.
   UniquePtr<LifecycleCallbacks> mCallbacks;
@@ -394,17 +398,17 @@ private:
   };
 
 public:
   /**
    * Looking up a custom element definition.
    * https://html.spec.whatwg.org/#look-up-a-custom-element-definition
    */
   CustomElementDefinition* LookupCustomElementDefinition(
-    nsAtom* aNameAtom, nsAtom* aTypeAtom);
+    nsAtom* aNameAtom, int32_t aNameSpaceID, nsAtom* aTypeAtom);
 
   CustomElementDefinition* LookupCustomElementDefinition(
     JSContext* aCx, JSObject *aConstructor) const;
 
   static void EnqueueLifecycleCallback(nsIDocument::ElementCallbackType aType,
                                        Element* aCustomElement,
                                        LifecycleCallbackArgs* aArgs,
                                        LifecycleAdoptedCallbackArgs* aAdoptedCallbackArgs,
@@ -550,16 +554,18 @@ private:
       ~AutoSetRunningFlag() {
         mRegistry->mIsCustomDefinitionRunning = false;
       }
 
     private:
       CustomElementRegistry* mRegistry;
   };
 
+  int32_t InferNamespace(JSContext* aCx, JS::Handle<JSObject*> constructor);
+
 public:
   nsISupports* GetParentObject() const;
 
   DocGroup* GetDocGroup() const;
 
   virtual JSObject* WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) override;
 
   void Define(JSContext* aCx, const nsAString& aName,
new file mode 100644
--- /dev/null
+++ b/dom/base/MimeType.cpp
@@ -0,0 +1,256 @@
+#include "MimeType.h"
+#include "nsUnicharUtils.h"
+
+namespace {
+  static inline bool IsHTTPTokenPoint(const char16_t c) {
+    return c == '!' || c == '#' || c == '$' || c == '%' || c == '&' ||
+           c == '\'' || c == '*' || c == '+' || c == '-' || c == '.' ||
+           c == '^' || c == '_' || c == '`' || c == '|' || c == '~' ||
+           mozilla::IsAsciiAlphanumeric(c);
+  }
+
+  static inline bool IsHTTPQuotedStringTokenPoint(const char16_t c) {
+    return c == 0x9 || (c >= ' ' && c <= '~') || (c >= 0x80 && c <= 0xFF);
+  }
+}
+
+/* static */ mozilla::UniquePtr<MimeType>
+MimeType::Parse(const nsAString& aMimeType)
+{
+  // See https://mimesniff.spec.whatwg.org/#parsing-a-mime-type
+
+  // Steps 1-2
+  const char16_t* pos = aMimeType.BeginReading();
+  const char16_t* end = aMimeType.EndReading();
+  while (pos < end && mozilla::IsAsciiWhitespace(*pos)) {
+    ++pos;
+  }
+  if (pos == end) {
+    return nullptr;
+  }
+  while (end > pos && mozilla::IsAsciiWhitespace(*(end - 1))) {
+    --end;
+  }
+
+  // Steps 3-4
+  const char16_t* typeStart = pos;
+  while (pos < end && *pos != '/') {
+    if (!IsHTTPTokenPoint(*pos)) {
+      return nullptr;
+    }
+    ++pos;
+  }
+  const char16_t* typeEnd = pos;
+  if (typeStart == typeEnd) {
+    return nullptr;
+  }
+
+  // Step 5
+  if (pos == end) {
+    return nullptr;
+  }
+
+  // Step 6
+  ++pos;
+
+  // Step 7-9
+  const char16_t* subtypeStart = pos;
+  const char16_t* subtypeEnd = nullptr;
+  while (pos < end && *pos != ';') {
+    if (!IsHTTPTokenPoint(*pos)) {
+      // If we hit a whitespace, check that the rest of
+      // the subtype is whitespace, otherwise fail.
+      if (mozilla::IsAsciiWhitespace(*pos)) {
+        subtypeEnd = pos;
+        ++pos;
+        while (pos < end && *pos != ';') {
+          if (!mozilla::IsAsciiWhitespace(*pos)) {
+            return nullptr;
+          }
+          ++pos;
+        }
+        break;
+      } else {
+        return nullptr;
+      }
+    }
+    ++pos;
+  }
+  if (subtypeEnd == nullptr) {
+    subtypeEnd = pos;
+  }
+  if (subtypeStart == subtypeEnd) {
+    return nullptr;
+  }
+
+  // Step 10
+  nsString type;
+  nsString subtype;
+  for (const char16_t* c = typeStart; c < typeEnd; ++c) {
+    type.Append(ToLowerCaseASCII(*c));
+  }
+  for (const char16_t* c = subtypeStart; c < subtypeEnd; ++c) {
+    subtype.Append(ToLowerCaseASCII(*c));
+  }
+  mozilla::UniquePtr<MimeType> mimeType(mozilla::MakeUnique<MimeType>(type, subtype));
+
+  // Step 11
+  while (pos < end) {
+    // Step 11.1
+    ++pos;
+
+    // Step 11.2
+    while (pos < end && mozilla::IsAsciiWhitespace(*pos)) {
+      ++pos;
+    }
+
+    // Steps 11.3 and 11.4
+    nsString paramName;
+    bool paramNameHadInvalidChars = false;
+    while (pos < end && *pos != ';' && *pos != '=') {
+      if (!IsHTTPTokenPoint(*pos)) {
+        paramNameHadInvalidChars = true;
+      }
+      paramName.Append(ToLowerCaseASCII(*pos));
+      ++pos;
+    }
+
+    // Step 11.5
+    if (pos < end) {
+      if (*pos == ';') {
+        continue;
+      }
+      ++pos;
+    }
+
+    // Step 11.6
+    ParameterValue paramValue;
+    bool paramValueHadInvalidChars = false;
+
+    // Step 11.7
+    if (pos < end) {
+
+      // Step 11.7.1
+      if (*pos == '"') {
+
+        // Step 11.7.1.1
+        ++pos;
+
+        // Step 11.7.1.2
+        while (true) {
+
+          // Step 11.7.1.2.1
+          while (pos < end && *pos != '"' && *pos != '\\') {
+            if (!IsHTTPQuotedStringTokenPoint(*pos)) {
+              paramValueHadInvalidChars = true;
+            }
+            if (!IsHTTPTokenPoint(*pos)) {
+              paramValue.mRequiresQuoting = true;
+            }
+            paramValue.Append(*pos);
+            ++pos;
+          }
+
+          // Step 11.7.1.2.2
+          if (pos < end && *pos == '\\') {
+            // Step 11.7.1.2.2.1
+            ++pos;
+
+            // Step 11.7.1.2.2.2
+            if (pos < end) {
+              if (!IsHTTPQuotedStringTokenPoint(*pos)) {
+                paramValueHadInvalidChars = true;
+              }
+              if (!IsHTTPTokenPoint(*pos)) {
+                paramValue.mRequiresQuoting = true;
+              }
+              paramValue.Append(*pos);
+              ++pos;
+              continue;
+            }
+
+            // Step 11.7.1.2.2.3
+            paramValue.Append('\\');
+            paramValue.mRequiresQuoting = true;
+            break;
+          } else {
+            // Step 11.7.1.2.3
+            break;
+          }
+        }
+
+        // Step 11.7.1.3
+        while (pos < end && *pos != ';') {
+          ++pos;
+        }
+
+      } else {
+
+        const char16_t* paramValueStart = pos;
+
+        // Step 11.7.2.1
+        while (pos < end && *pos != ';') {
+          if (!IsHTTPQuotedStringTokenPoint(*pos)) {
+            paramValueHadInvalidChars = true;
+          }
+          if (!IsHTTPTokenPoint(*pos)) {
+            paramValue.mRequiresQuoting = true;
+          }
+          ++pos;
+        }
+
+        // Step 11.7.2.2
+        const char16_t* paramValueEnd = pos - 1;
+        while (paramValueEnd >= paramValueStart &&
+               mozilla::IsAsciiWhitespace(*paramValueEnd)) {
+          --paramValueEnd;
+        }
+
+        for (const char16_t* c = paramValueStart; c <= paramValueEnd; ++c) {
+          paramValue.Append(*c);
+        }
+      }
+
+      // Step 11.8
+      if (!paramName.IsEmpty() && !paramValue.IsEmpty() &&
+          !paramNameHadInvalidChars && !paramValueHadInvalidChars &&
+          !mimeType->mParameters.Get(paramName, &paramValue)) {
+        mimeType->mParameters.Put(paramName, paramValue);
+        mimeType->mParameterNames.AppendElement(paramName);
+      }
+    }
+  }
+
+  return mimeType;
+}
+
+void
+MimeType::Serialize(nsAString& aOutput) const
+{
+  aOutput.Assign(mType);
+  aOutput.AppendLiteral("/");
+  aOutput.Append(mSubtype);
+  for (uint32_t i = 0; i < mParameterNames.Length(); i++) {
+    auto name = mParameterNames[i];
+    ParameterValue value;
+    mParameters.Get(name, &value);
+    aOutput.AppendLiteral(";");
+    aOutput.Append(name);
+    aOutput.AppendLiteral("=");
+    if (value.mRequiresQuoting) {
+      aOutput.AppendLiteral("\"");
+      const char16_t* vcur = value.BeginReading();
+      const char16_t* vend = value.EndReading();
+      while (vcur < vend) {
+        if (*vcur == '"' || *vcur == '\\') {
+          aOutput.AppendLiteral("\\");
+        }
+        aOutput.Append(*vcur);
+        vcur++;
+      }
+      aOutput.AppendLiteral("\"");
+    } else {
+      aOutput.Append(value);
+    }
+  }
+}
new file mode 100644
--- /dev/null
+++ b/dom/base/MimeType.h
@@ -0,0 +1,42 @@
+/* -*- 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 http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_dom_MimeType_h
+#define mozilla_dom_MimeType_h
+
+#include "mozilla/TextUtils.h"
+#include "mozilla/UniquePtr.h"
+#include "nsDataHashtable.h"
+#include "nsTArray.h"
+
+class MimeType final
+{
+private:
+  class ParameterValue : public nsString
+  {
+  public:
+    bool mRequiresQuoting;
+
+    ParameterValue()
+      : mRequiresQuoting(false)
+    {}
+  };
+
+  nsString mType;
+  nsString mSubtype;
+  nsDataHashtable<nsStringHashKey, ParameterValue> mParameters;
+  nsTArray<nsString> mParameterNames;
+
+public:
+  MimeType(const nsAString& aType, const nsAString& aSubtype)
+    : mType(aType), mSubtype(aSubtype)
+  {}
+
+  static mozilla::UniquePtr<MimeType> Parse(const nsAString& aStr);
+  void Serialize(nsAString& aStr) const;
+};
+
+#endif // mozilla_dom_MimeType_h
--- a/dom/base/moz.build
+++ b/dom/base/moz.build
@@ -189,16 +189,17 @@ EXPORTS.mozilla.dom += [
     'ImageTracker.h',
     'IntlUtils.h',
     'Link.h',
     'Location.h',
     'MessageBroadcaster.h',
     'MessageListenerManager.h',
     'MessageManagerGlobal.h',
     'MessageSender.h',
+    'MimeType.h',
     'MozQueryInterface.h',
     'NameSpaceConstants.h',
     'Navigator.h',
     'NodeInfo.h',
     'NodeInfoInlines.h',
     'NodeIterator.h',
     'ParentProcessMessageManager.h',
     'PlacesEvent.h',
@@ -283,16 +284,17 @@ UNIFIED_SOURCES += [
     'InProcessTabChildMessageManager.cpp',
     'IntlUtils.cpp',
     'Link.cpp',
     'Location.cpp',
     'MessageBroadcaster.cpp',
     'MessageListenerManager.cpp',
     'MessageManagerGlobal.cpp',
     'MessageSender.cpp',
+    'MimeType.cpp',
     'MozQueryInterface.cpp',
     'Navigator.cpp',
     'NodeInfo.cpp',
     'NodeIterator.cpp',
     'NodeUbiReporting.cpp',
     'nsAttrValue.cpp',
     'nsAttrValueOrString.cpp',
     'nsCCUncollectableMarker.cpp',
--- a/dom/base/nsContentUtils.cpp
+++ b/dom/base/nsContentUtils.cpp
@@ -9997,16 +9997,25 @@ nsContentUtils::NewXULOrHTMLElement(Elem
     bool synchronousCustomElements = aFromParser != dom::FROM_PARSER_FRAGMENT ||
                                      aFromParser == dom::NOT_FROM_PARSER;
     // Per discussion in https://github.com/w3c/webcomponents/issues/635,
     // use entry global in those places that are called from JS APIs and use the
     // node document's global object if it is called from parser.
     nsIGlobalObject* global;
     if (aFromParser == dom::NOT_FROM_PARSER) {
       global = GetEntryGlobal();
+
+      // XUL documents always use NOT_FROM_PARSER for non-XUL elements. We can
+      // get the global from the document in that case.
+      if (!global) {
+        nsIDocument* doc = nodeInfo->GetDocument();
+        if (doc && doc->IsXULDocument()) {
+          global = doc->GetScopeObject();
+        }
+      }
     } else {
       global = nodeInfo->GetDocument()->GetScopeObject();
     }
     if (!global) {
       // In browser chrome code, one may have access to a document which doesn't
       // have scope object anymore.
       return NS_ERROR_FAILURE;
     }
@@ -10117,17 +10126,17 @@ nsContentUtils::LookupCustomElementDefin
     return nullptr;
   }
 
   RefPtr<CustomElementRegistry> registry(GetCustomElementRegistry(aDoc));
   if (!registry) {
     return nullptr;
   }
 
-  return registry->LookupCustomElementDefinition(aNameAtom, aTypeAtom);
+  return registry->LookupCustomElementDefinition(aNameAtom, aNameSpaceID, aTypeAtom);
 }
 
 /* static */ void
 nsContentUtils::RegisterCallbackUpgradeElement(Element* aElement,
                                                nsAtom* aTypeName)
 {
   MOZ_ASSERT(aElement);
 
new file mode 100644
--- /dev/null
+++ b/dom/base/test/gtest/TestMimeType.cpp
@@ -0,0 +1,708 @@
+/* -*- 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 http://mozilla.org/MPL/2.0/. */
+
+#include "gtest/gtest.h"
+
+#include "MimeType.h"
+#include "nsString.h"
+
+using mozilla::UniquePtr;
+
+TEST(MimeType, EmptyString)
+{
+  const auto in = NS_LITERAL_STRING("");
+  UniquePtr<MimeType> parsed = MimeType::Parse(in);
+  ASSERT_FALSE(parsed) <<
+    "Empty string";
+}
+
+TEST(MimeType, JustWhitespace)
+{
+  const auto in = NS_LITERAL_STRING(" \t\r\n ");
+  UniquePtr<MimeType> parsed = MimeType::Parse(in);
+  ASSERT_FALSE(parsed) <<
+    "Just whitespace";
+}
+
+TEST(MimeType, JustBackslash)
+{
+  const auto in = NS_LITERAL_STRING("\\");
+  UniquePtr<MimeType> parsed = MimeType::Parse(in);
+  ASSERT_FALSE(parsed) <<
+    "Just backslash";
+}
+
+TEST(MimeType, JustForwardslash)
+{
+  const auto in = NS_LITERAL_STRING("/");
+  UniquePtr<MimeType> parsed = MimeType::Parse(in);
+  ASSERT_FALSE(parsed) <<
+    "Just forward slash";
+}
+
+TEST(MimeType, MissingType1)
+{
+  const auto in = NS_LITERAL_STRING("/bogus");
+  UniquePtr<MimeType> parsed = MimeType::Parse(in);
+  ASSERT_FALSE(parsed) <<
+    "Missing type #1";
+}
+
+TEST(MimeType, MissingType2)
+{
+  const auto in = NS_LITERAL_STRING(" \r\n\t/bogus");
+  UniquePtr<MimeType> parsed = MimeType::Parse(in);
+  ASSERT_FALSE(parsed) <<
+    "Missing type #2";
+}
+
+TEST(MimeType, MissingSubtype1)
+{
+  const auto in = NS_LITERAL_STRING("bogus");
+  UniquePtr<MimeType> parsed = MimeType::Parse(in);
+  ASSERT_FALSE(parsed) <<
+    "Missing subtype #1";
+}
+
+TEST(MimeType, MissingSubType2)
+{
+  const auto in = NS_LITERAL_STRING("bogus/");
+  UniquePtr<MimeType> parsed = MimeType::Parse(in);
+  ASSERT_FALSE(parsed) <<
+    "Missing subtype #2";
+}
+
+TEST(MimeType, MissingSubType3)
+{
+  const auto in = NS_LITERAL_STRING("bogus;");
+  UniquePtr<MimeType> parsed = MimeType::Parse(in);
+  ASSERT_FALSE(parsed) <<
+    "Missing subtype #3";
+}
+
+TEST(MimeType, MissingSubType4)
+{
+  const auto in = NS_LITERAL_STRING("bogus; \r\n\t");
+  UniquePtr<MimeType> parsed = MimeType::Parse(in);
+  ASSERT_FALSE(parsed) <<
+    "Missing subtype #3";
+}
+
+TEST(MimeType, ExtraForwardSlash)
+{
+  const auto in = NS_LITERAL_STRING("bogus/bogus/;");
+  UniquePtr<MimeType> parsed = MimeType::Parse(in);
+  ASSERT_FALSE(parsed) <<
+    "Extra forward slash";
+}
+
+TEST(MimeType, WhitespaceInType)
+{
+  const auto in = NS_LITERAL_STRING("t\re\nx\tt /html");
+  UniquePtr<MimeType> parsed = MimeType::Parse(in);
+  ASSERT_FALSE(parsed) <<
+    "Type with whitespace";
+}
+
+TEST(MimeType, WhitespaceInSubtype)
+{
+  const auto in = NS_LITERAL_STRING("text/ h\rt\nm\tl");
+  UniquePtr<MimeType> parsed = MimeType::Parse(in);
+  ASSERT_FALSE(parsed) <<
+    "Subtype with whitespace";
+}
+
+TEST(MimeType, NonAlphanumericMediaType1)
+{
+  const auto in = NS_LITERAL_STRING("</>");
+  UniquePtr<MimeType> parsed = MimeType::Parse(in);
+  ASSERT_FALSE(parsed) <<
+    "Non-alphanumeric media type #1";
+}
+
+TEST(MimeType, NonAlphanumericMediaType2)
+{
+  const auto in = NS_LITERAL_STRING("(/)");
+  UniquePtr<MimeType> parsed = MimeType::Parse(in);
+  ASSERT_FALSE(parsed) <<
+    "Non-alphanumeric media type #2";
+}
+
+TEST(MimeType, NonAlphanumericMediaType3)
+{
+  const auto in = NS_LITERAL_STRING("{/}");
+  UniquePtr<MimeType> parsed = MimeType::Parse(in);
+  ASSERT_FALSE(parsed) <<
+    "Non-alphanumeric media type #3";
+}
+
+TEST(MimeType, NonAlphanumericMediaType4)
+{
+  const auto in = NS_LITERAL_STRING("\"/\"");
+  UniquePtr<MimeType> parsed = MimeType::Parse(in);
+  ASSERT_FALSE(parsed) <<
+    "Non-alphanumeric media type #4";
+}
+
+TEST(MimeType, NonAlphanumericMediaType5)
+{
+  const auto in = NS_LITERAL_STRING("\0/\0");
+  UniquePtr<MimeType> parsed = MimeType::Parse(in);
+  ASSERT_FALSE(parsed) <<
+    "Non-alphanumeric media type #5";
+}
+
+TEST(MimeType, NonAlphanumericMediaType6)
+{
+  const auto in = NS_LITERAL_STRING("text/html(;doesnot=matter");
+  UniquePtr<MimeType> parsed = MimeType::Parse(in);
+  ASSERT_FALSE(parsed) <<
+    "Non-alphanumeric media type #6";
+}
+
+TEST(MimeType, NonLatin1MediaType1)
+{
+  const auto in = NS_LITERAL_STRING("ÿ/ÿ");
+  UniquePtr<MimeType> parsed = MimeType::Parse(in);
+  ASSERT_FALSE(parsed) <<
+    "Non-latin1 media type #1";
+}
+
+TEST(MimeType, NonLatin1MediaType2)
+{
+  const auto in = NS_LITERAL_STRING("\x0100/\x0100");
+  UniquePtr<MimeType> parsed = MimeType::Parse(in);
+  ASSERT_FALSE(parsed) <<
+    "Non-latin1 media type #2";
+}
+
+TEST(MimeType, MultipleParameters)
+{
+  const auto in = NS_LITERAL_STRING("text/html;charset=gbk;no=1;charset_=gbk_;yes=2");
+  UniquePtr<MimeType> parsed = MimeType::Parse(in);
+  ASSERT_TRUE(parsed) << "Parsing succeeded";
+  nsString out;
+  parsed->Serialize(out);
+  ASSERT_TRUE(out.Equals(NS_LITERAL_STRING("text/html;charset=gbk;no=1;charset_=gbk_;yes=2"))) <<
+    "Multiple parameters";
+}
+
+TEST(MimeType, DuplicateParameter1)
+{
+  const auto in = NS_LITERAL_STRING("text/html;charset=gbk;charset=windows-1255");
+  UniquePtr<MimeType> parsed = MimeType::Parse(in);
+  ASSERT_TRUE(parsed) << "Parsing succeeded";
+  nsString out;
+  parsed->Serialize(out);
+  ASSERT_TRUE(out.Equals(NS_LITERAL_STRING("text/html;charset=gbk"))) <<
+    "Duplicate parameter #1";
+}
+
+TEST(MimeType, DuplicateParameter2)
+{
+  const auto in = NS_LITERAL_STRING("text/html;charset=();charset=GBK");
+  UniquePtr<MimeType> parsed = MimeType::Parse(in);
+  ASSERT_TRUE(parsed) << "Parsing succeeded";
+  nsString out;
+  parsed->Serialize(out);
+  ASSERT_TRUE(out.Equals(NS_LITERAL_STRING("text/html;charset=\"()\""))) <<
+    "Duplicate parameter #2";
+}
+
+TEST(MimeType, NonAlphanumericParametersAreQuoted)
+{
+  const auto in = NS_LITERAL_STRING("text/html;test=\x00FF\\;charset=gbk");
+  UniquePtr<MimeType> parsed = MimeType::Parse(in);
+  ASSERT_TRUE(parsed) << "Parsing succeeded";
+  nsString out;
+  parsed->Serialize(out);
+  ASSERT_TRUE(out.Equals(NS_LITERAL_STRING("text/html;test=\"\x00FF\\\\\";charset=gbk"))) <<
+    "Non-alphanumeric parameters are quoted";
+}
+
+TEST(MimeType, ParameterQuotedIfHasLeadingWhitespace1)
+{
+  const auto in = NS_LITERAL_STRING("text/html;charset= g\\\"bk");
+  UniquePtr<MimeType> parsed = MimeType::Parse(in);
+  ASSERT_TRUE(parsed) << "Parsing succeeded";
+  nsAutoString out;
+  parsed->Serialize(out);
+  ASSERT_TRUE(out.EqualsLiteral("text/html;charset=\" g\\\\\\\"bk\"")) <<
+    "Parameter is quoted if has leading whitespace #1";
+}
+
+TEST(MimeType, ParameterQuotedIfHasLeadingWhitespace2)
+{
+  const auto in = NS_LITERAL_STRING("text/html;charset= \"g\\bk\"");
+  UniquePtr<MimeType> parsed = MimeType::Parse(in);
+  ASSERT_TRUE(parsed) << "Parsing succeeded";
+  nsAutoString out;
+  parsed->Serialize(out);
+  ASSERT_TRUE(out.EqualsLiteral("text/html;charset=\" \\\"g\\\\bk\\\"\"")) <<
+    "Parameter is quoted if has leading whitespace #2";
+}
+
+TEST(MimeType, ParameterQuotedIfHasInternalWhitespace)
+{
+  const auto in = NS_LITERAL_STRING("text/html;charset=g \\b\"k");
+  UniquePtr<MimeType> parsed = MimeType::Parse(in);
+  ASSERT_TRUE(parsed) << "Parsing succeeded";
+  nsAutoString out;
+  parsed->Serialize(out);
+  ASSERT_TRUE(out.EqualsLiteral("text/html;charset=\"g \\\\b\\\"k\"")) <<
+    "Parameter is quoted if has internal whitespace";
+}
+
+TEST(MimeType, ImproperlyQuotedParameter1)
+{
+  const auto in = NS_LITERAL_STRING("x/x;test=\"");
+  UniquePtr<MimeType> parsed = MimeType::Parse(in);
+  ASSERT_TRUE(parsed) << "Parsing succeeded";
+  nsAutoString out;
+  parsed->Serialize(out);
+  ASSERT_TRUE(out.EqualsLiteral("x/x")) <<
+    "Improperly-quoted parameter is handled properly #1";
+}
+
+TEST(MimeType, ImproperlyQuotedParameter2)
+{
+  const auto in = NS_LITERAL_STRING("x/x;test=\"\\");
+  UniquePtr<MimeType> parsed = MimeType::Parse(in);
+  ASSERT_TRUE(parsed) << "Parsing succeeded";
+  nsAutoString out;
+  parsed->Serialize(out);
+  ASSERT_TRUE(out.EqualsLiteral("x/x;test=\"\\\\\"")) <<
+    "Improperly-quoted parameter is handled properly #2";
+}
+
+TEST(MimeType, NonLatin1ParameterIgnored)
+{
+  const auto in = NS_LITERAL_STRING("x/x;test=\xFFFD;x=x");
+  UniquePtr<MimeType> parsed = MimeType::Parse(in);
+  ASSERT_TRUE(parsed) << "Parsing succeeded";
+  nsAutoString out;
+  parsed->Serialize(out);
+  ASSERT_TRUE(out.EqualsLiteral("x/x;x=x")) <<
+    "Non latin-1 parameters are ignored";
+}
+
+TEST(MimeType, ParameterIgnoredIfWhitespaceInName1)
+{
+  const auto in = NS_LITERAL_STRING("text/html;charset =gbk;charset=123");
+  UniquePtr<MimeType> parsed = MimeType::Parse(in);
+  ASSERT_TRUE(parsed) << "Parsing succeeded";
+  nsAutoString out;
+  parsed->Serialize(out);
+  ASSERT_TRUE(out.EqualsLiteral("text/html;charset=123")) <<
+    "Parameter ignored if whitespace in name #1";
+}
+
+TEST(MimeType, ParameterIgnoredIfWhitespaceInName2)
+{
+  const auto in = NS_LITERAL_STRING("text/html;cha rset =gbk;charset=123");
+  UniquePtr<MimeType> parsed = MimeType::Parse(in);
+  ASSERT_TRUE(parsed) << "Parsing succeeded";
+  nsAutoString out;
+  parsed->Serialize(out);
+  ASSERT_TRUE(out.EqualsLiteral("text/html;charset=123")) <<
+    "Parameter ignored if whitespace in name #2";
+}
+
+TEST(MimeType, WhitespaceTrimmed)
+{
+  const auto in = NS_LITERAL_STRING("\n\r\t  text/plain\n\r\t  ;\n\r\t  charset=123\n\r\t ");
+  UniquePtr<MimeType> parsed = MimeType::Parse(in);
+  ASSERT_TRUE(parsed) << "Parsing succeeded";
+  nsAutoString out;
+  parsed->Serialize(out);
+  ASSERT_TRUE(out.EqualsLiteral("text/plain;charset=123")) <<
+    "Whitespace appropriately ignored";
+}
+
+TEST(MimeType, WhitespaceOnlyParameterIgnored)
+{
+  const auto in = NS_LITERAL_STRING("x/x;x= \r\n\t");
+  UniquePtr<MimeType> parsed = MimeType::Parse(in);
+  ASSERT_TRUE(parsed) << "Parsing succeeded";
+  nsAutoString out;
+  parsed->Serialize(out);
+  ASSERT_TRUE(out.EqualsLiteral("x/x")) <<
+    "Whitespace-only parameter is ignored";
+}
+
+TEST(MimeType, IncompleteParameterIgnored1)
+{
+  const auto in = NS_LITERAL_STRING("x/x;test");
+  UniquePtr<MimeType> parsed = MimeType::Parse(in);
+  ASSERT_TRUE(parsed) << "Parsing succeeded";
+  nsAutoString out;
+  parsed->Serialize(out);
+  ASSERT_TRUE(out.EqualsLiteral("x/x")) <<
+    "Incomplete parameter is ignored #1";
+}
+
+TEST(MimeType, IncompleteParameterIgnored2)
+{
+  const auto in = NS_LITERAL_STRING("x/x;test=");
+  UniquePtr<MimeType> parsed = MimeType::Parse(in);
+  ASSERT_TRUE(parsed) << "Parsing succeeded";
+  nsAutoString out;
+  parsed->Serialize(out);
+  ASSERT_TRUE(out.EqualsLiteral("x/x")) <<
+    "Incomplete parameter is ignored #2";
+}
+
+TEST(MimeType, IncompleteParameterIgnored3)
+{
+  const auto in = NS_LITERAL_STRING("x/x;test= \r\n\t");
+  UniquePtr<MimeType> parsed = MimeType::Parse(in);
+  ASSERT_TRUE(parsed) << "Parsing succeeded";
+  nsAutoString out;
+  parsed->Serialize(out);
+  ASSERT_TRUE(out.EqualsLiteral("x/x")) <<
+    "Incomplete parameter is ignored #3";
+}
+
+TEST(MimeType, IncompleteParameterIgnored4)
+{
+  const auto in = NS_LITERAL_STRING("text/html;test;charset=gbk");
+  UniquePtr<MimeType> parsed = MimeType::Parse(in);
+  ASSERT_TRUE(parsed) << "Parsing succeeded";
+  nsAutoString out;
+  parsed->Serialize(out);
+  ASSERT_TRUE(out.EqualsLiteral("text/html;charset=gbk")) <<
+    "Incomplete parameter is ignored #4";
+}
+
+TEST(MimeType, IncompleteParameterIgnored5)
+{
+  const auto in = NS_LITERAL_STRING("text/html;test=;charset=gbk");
+  UniquePtr<MimeType> parsed = MimeType::Parse(in);
+  ASSERT_TRUE(parsed) << "Parsing succeeded";
+  nsAutoString out;
+  parsed->Serialize(out);
+  ASSERT_TRUE(out.EqualsLiteral("text/html;charset=gbk")) <<
+    "Incomplete parameter is ignored #5";
+}
+
+TEST(MimeType, EmptyParameterIgnored1)
+{
+  const auto in = NS_LITERAL_STRING("text/html ; ; charset=gbk");
+  UniquePtr<MimeType> parsed = MimeType::Parse(in);
+  ASSERT_TRUE(parsed) << "Parsing succeeded";
+  nsAutoString out;
+  parsed->Serialize(out);
+  ASSERT_TRUE(out.EqualsLiteral("text/html;charset=gbk")) <<
+    "Empty parameter ignored #1";
+}
+
+TEST(MimeType, EmptyParameterIgnored2)
+{
+  const auto in = NS_LITERAL_STRING("text/html;;;;charset=gbk");
+  UniquePtr<MimeType> parsed = MimeType::Parse(in);
+  ASSERT_TRUE(parsed) << "Parsing succeeded";
+  nsAutoString out;
+  parsed->Serialize(out);
+  ASSERT_TRUE(out.EqualsLiteral("text/html;charset=gbk")) <<
+    "Empty parameter ignored #2";
+}
+
+TEST(MimeType, InvalidParameterIgnored1)
+{
+  const auto in = NS_LITERAL_STRING("text/html;';charset=gbk");
+  UniquePtr<MimeType> parsed = MimeType::Parse(in);
+  ASSERT_TRUE(parsed) << "Parsing succeeded";
+  nsAutoString out;
+  parsed->Serialize(out);
+  ASSERT_TRUE(out.EqualsLiteral("text/html;charset=gbk")) <<
+    "Invalid parameter ignored #1";
+}
+
+TEST(MimeType, InvalidParameterIgnored2)
+{
+  const auto in = NS_LITERAL_STRING("text/html;\";charset=gbk;=123; =321");
+  UniquePtr<MimeType> parsed = MimeType::Parse(in);
+  ASSERT_TRUE(parsed) << "Parsing succeeded";
+  nsAutoString out;
+  parsed->Serialize(out);
+  ASSERT_TRUE(out.EqualsLiteral("text/html;charset=gbk")) <<
+    "Invalid parameter ignored #2";
+}
+
+TEST(MimeType, InvalidParameterIgnored3)
+{
+  const auto in = NS_LITERAL_STRING("text/html;charset= \"\u007F;charset=GBK");
+  UniquePtr<MimeType> parsed = MimeType::Parse(in);
+  ASSERT_TRUE(parsed) << "Parsing succeeded";
+  nsAutoString out;
+  parsed->Serialize(out);
+  ASSERT_TRUE(out.EqualsLiteral("text/html;charset=GBK")) <<
+    "Invalid parameter ignored #3";
+}
+
+TEST(MimeType, InvalidParameterIgnored4)
+{
+  const auto in = NS_LITERAL_STRING("text/html;charset=\"\u007F;charset=foo\";charset=GBK;charset=");
+  UniquePtr<MimeType> parsed = MimeType::Parse(in);
+  ASSERT_TRUE(parsed) << "Parsing succeeded";
+  nsAutoString out;
+  parsed->Serialize(out);
+  ASSERT_TRUE(out.EqualsLiteral("text/html;charset=GBK")) <<
+    "Invalid parameter ignored #4";
+}
+
+TEST(MimeType, SingleQuotes1)
+{
+  const auto in = NS_LITERAL_STRING("text/html;charset='gbk'");
+  UniquePtr<MimeType> parsed = MimeType::Parse(in);
+  ASSERT_TRUE(parsed) << "Parsing succeeded";
+  nsAutoString out;
+  parsed->Serialize(out);
+  ASSERT_TRUE(out.EqualsLiteral("text/html;charset='gbk'")) <<
+    "Single quotes handled properly #1";
+}
+
+TEST(MimeType, SingleQuotes2)
+{
+  const auto in = NS_LITERAL_STRING("text/html;charset='gbk");
+  UniquePtr<MimeType> parsed = MimeType::Parse(in);
+  ASSERT_TRUE(parsed) << "Parsing succeeded";
+  nsAutoString out;
+  parsed->Serialize(out);
+  ASSERT_TRUE(out.EqualsLiteral("text/html;charset='gbk")) <<
+    "Single quotes handled properly #2";
+}
+
+TEST(MimeType, SingleQuotes3)
+{
+  const auto in = NS_LITERAL_STRING("text/html;charset=gbk'");
+  UniquePtr<MimeType> parsed = MimeType::Parse(in);
+  ASSERT_TRUE(parsed) << "Parsing succeeded";
+  nsAutoString out;
+  parsed->Serialize(out);
+  ASSERT_TRUE(out.EqualsLiteral("text/html;charset=gbk'")) <<
+    "Single quotes handled properly #3";
+}
+
+TEST(MimeType, SingleQuotes4)
+{
+  const auto in = NS_LITERAL_STRING("text/html;charset=';charset=GBK");
+  UniquePtr<MimeType> parsed = MimeType::Parse(in);
+  ASSERT_TRUE(parsed) << "Parsing succeeded";
+  nsAutoString out;
+  parsed->Serialize(out);
+  ASSERT_TRUE(out.EqualsLiteral("text/html;charset='")) <<
+    "Single quotes handled properly #4";
+}
+
+TEST(MimeType, SingleQuotes5)
+{
+  const auto in = NS_LITERAL_STRING("text/html;charset=''';charset=GBK");
+  UniquePtr<MimeType> parsed = MimeType::Parse(in);
+  ASSERT_TRUE(parsed) << "Parsing succeeded";
+  nsAutoString out;
+  parsed->Serialize(out);
+  ASSERT_TRUE(out.EqualsLiteral("text/html;charset='''")) <<
+    "Single quotes handled properly #5";
+}
+
+TEST(MimeType, DoubleQuotes1)
+{
+  const auto in = NS_LITERAL_STRING("text/html;charset=\"gbk\"");
+  UniquePtr<MimeType> parsed = MimeType::Parse(in);
+  ASSERT_TRUE(parsed) << "Parsing succeeded";
+  nsAutoString out;
+  parsed->Serialize(out);
+  ASSERT_TRUE(out.EqualsLiteral("text/html;charset=gbk")) <<
+    "Double quotes handled properly #1";
+}
+
+TEST(MimeType, DoubleQuotes2)
+{
+  const auto in = NS_LITERAL_STRING("text/html;charset=\"gbk");
+  UniquePtr<MimeType> parsed = MimeType::Parse(in);
+  ASSERT_TRUE(parsed) << "Parsing succeeded";
+  nsAutoString out;
+  parsed->Serialize(out);
+  ASSERT_TRUE(out.EqualsLiteral("text/html;charset=gbk")) <<
+    "Double quotes handled properly #2";
+}
+
+TEST(MimeType, DoubleQuotes3)
+{
+  const auto in = NS_LITERAL_STRING("text/html;charset=gbk\"");
+  UniquePtr<MimeType> parsed = MimeType::Parse(in);
+  ASSERT_TRUE(parsed) << "Parsing succeeded";
+  nsAutoString out;
+  parsed->Serialize(out);
+  ASSERT_TRUE(out.EqualsLiteral("text/html;charset=\"gbk\\\"\"")) <<
+    "Double quotes handled properly #3";
+}
+
+TEST(MimeType, DoubleQuotes4)
+{
+  const auto in = NS_LITERAL_STRING("text/html;charset=\" gbk\"");
+  UniquePtr<MimeType> parsed = MimeType::Parse(in);
+  ASSERT_TRUE(parsed) << "Parsing succeeded";
+  nsAutoString out;
+  parsed->Serialize(out);
+  ASSERT_TRUE(out.EqualsLiteral("text/html;charset=\" gbk\"")) <<
+    "Double quotes handled properly #4";
+}
+
+TEST(MimeType, DoubleQuotes5)
+{
+  const auto in = NS_LITERAL_STRING("text/html;charset=\"gbk \"");
+  UniquePtr<MimeType> parsed = MimeType::Parse(in);
+  ASSERT_TRUE(parsed) << "Parsing succeeded";
+  nsAutoString out;
+  parsed->Serialize(out);
+  ASSERT_TRUE(out.EqualsLiteral("text/html;charset=\"gbk \"")) <<
+    "Double quotes handled properly #5";
+}
+
+TEST(MimeType, DoubleQuotes6)
+{
+  const auto in = NS_LITERAL_STRING("text/html;charset=\"\\ gbk\"");
+  UniquePtr<MimeType> parsed = MimeType::Parse(in);
+  ASSERT_TRUE(parsed) << "Parsing succeeded";
+  nsAutoString out;
+  parsed->Serialize(out);
+  ASSERT_TRUE(out.EqualsLiteral("text/html;charset=\" gbk\"")) <<
+    "Double quotes handled properly #6";
+}
+
+TEST(MimeType, DoubleQuotes7)
+{
+  const auto in = NS_LITERAL_STRING("text/html;charset=\"\\g\\b\\k\"");
+  UniquePtr<MimeType> parsed = MimeType::Parse(in);
+  ASSERT_TRUE(parsed) << "Parsing succeeded";
+  nsAutoString out;
+  parsed->Serialize(out);
+  ASSERT_TRUE(out.EqualsLiteral("text/html;charset=gbk")) <<
+    "Double quotes handled properly #7";
+}
+
+TEST(MimeType, DoubleQuotes8)
+{
+  const auto in = NS_LITERAL_STRING("text/html;charset=\"gbk\"x");
+  UniquePtr<MimeType> parsed = MimeType::Parse(in);
+  ASSERT_TRUE(parsed) << "Parsing succeeded";
+  nsAutoString out;
+  parsed->Serialize(out);
+  ASSERT_TRUE(out.EqualsLiteral("text/html;charset=gbk")) <<
+    "Double quotes handled properly #8";
+}
+
+TEST(MimeType, DoubleQuotes9)
+{
+  const auto in = NS_LITERAL_STRING("text/html;charset=\"\";charset=GBK");
+  UniquePtr<MimeType> parsed = MimeType::Parse(in);
+  ASSERT_TRUE(parsed) << "Parsing succeeded";
+  nsAutoString out;
+  parsed->Serialize(out);
+  ASSERT_TRUE(out.EqualsLiteral("text/html;charset=GBK")) <<
+    "Double quotes handled properly #9";
+}
+
+TEST(MimeType, DoubleQuotes10)
+{
+  const auto in = NS_LITERAL_STRING("text/html;charset=\";charset=GBK");
+  UniquePtr<MimeType> parsed = MimeType::Parse(in);
+  ASSERT_TRUE(parsed) << "Parsing succeeded";
+  nsAutoString out;
+  parsed->Serialize(out);
+  ASSERT_TRUE(out.EqualsLiteral("text/html;charset=\";charset=GBK\"")) <<
+    "Double quotes handled properly #10";
+}
+
+TEST(MimeType, UnexpectedCodePoints)
+{
+  const auto in = NS_LITERAL_STRING("text/html;charset={gbk}");
+  UniquePtr<MimeType> parsed = MimeType::Parse(in);
+  ASSERT_TRUE(parsed) << "Parsing succeeded";
+  nsAutoString out;
+  parsed->Serialize(out);
+  ASSERT_TRUE(out.EqualsLiteral("text/html;charset=\"{gbk}\"")) <<
+    "Unexpected code points handled properly";
+}
+
+TEST(MimeType, LongTypesSubtypesAccepted)
+{
+  const auto in = NS_LITERAL_STRING("0123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789/0123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789");
+  UniquePtr<MimeType> parsed = MimeType::Parse(in);
+  ASSERT_TRUE(parsed) << "Parsing succeeded";
+  nsAutoString out;
+  parsed->Serialize(out);
+  ASSERT_TRUE(out.Equals(in)) <<
+    "Long type/subtype accepted";
+}
+
+TEST(MimeType, LongParametersAccepted)
+{
+  const auto in = NS_LITERAL_STRING("text/html;0123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789=x;charset=gbk");
+  UniquePtr<MimeType> parsed = MimeType::Parse(in);
+  ASSERT_TRUE(parsed) << "Parsing succeeded";
+  nsAutoString out;
+  parsed->Serialize(out);
+  ASSERT_TRUE(out.Equals(in)) <<
+    "Long parameters accepted";
+}
+
+TEST(MimeType, AllValidCharactersAccepted1)
+{
+  const auto in = NS_LITERAL_STRING("x/x;x=\"\t !\\\"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\\\]^_`abcdefghijklmnopqrstuvwxyz{|}~\u0080\u0081\u0082\u0083\u0084\u0085\u0086\u0087\u0088\u0089\u008A\u008B\u008C\u008D\u008E\u008F\u0090\u0091\u0092\u0093\u0094\u0095\u0096\u0097\u0098\u0099\u009A\u009B\u009C\u009D\u009E\u009F\u00A0\u00A1\u00A2\u00A3\u00A4\u00A5\u00A6\u00A7\u00A8\u00A9\u00AA\u00AB\u00AC\u00AD\u00AE\u00AF\u00B0\u00B1\u00B2\u00B3\u00B4\u00B5\u00B6\u00B7\u00B8\u00B9\u00BA\u00BB\u00BC\u00BD\u00BE\u00BF\u00C0\u00C1\u00C2\u00C3\u00C4\u00C5\u00C6\u00C7\u00C8\u00C9\u00CA\u00CB\u00CC\u00CD\u00CE\u00CF\u00D0\u00D1\u00D2\u00D3\u00D4\u00D5\u00D6\u00D7\u00D8\u00D9\u00DA\u00DB\u00DC\u00DD\u00DE\u00DF\u00E0\u00E1\u00E2\u00E3\u00E4\u00E5\u00E6\u00E7\u00E8\u00E9\u00EA\u00EB\u00EC\u00ED\u00EE\u00EF\u00F0\u00F1\u00F2\u00F3\u00F4\u00F5\u00F6\u00F7\u00F8\u00F9\u00FA\u00FB\u00FC\u00FD\u00FE\u00FF\"");
+  UniquePtr<MimeType> parsed = MimeType::Parse(in);
+  ASSERT_TRUE(parsed) << "Parsing succeeded";
+  nsAutoString out;
+  parsed->Serialize(out);
+  ASSERT_TRUE(out.Equals(in)) <<
+    "All valid characters accepted #1";
+}
+
+TEST(MimeType, CaseNormalization1)
+{
+  const auto in = NS_LITERAL_STRING("TEXT/PLAIN;CHARSET=TEST");
+  UniquePtr<MimeType> parsed = MimeType::Parse(in);
+  ASSERT_TRUE(parsed) << "Parsing succeeded";
+  nsAutoString out;
+  parsed->Serialize(out);
+  ASSERT_TRUE(out.EqualsLiteral("text/plain;charset=TEST")) <<
+    "Case normalized properly #1";
+}
+
+TEST(MimeType, CaseNormalization2)
+{
+  const auto in = NS_LITERAL_STRING("!#$%&'*+-.^_`|~0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz/!#$%&'*+-.^_`|~0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz;!#$%&'*+-.^_`|~0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz=!#$%&'*+-.^_`|~0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz");
+  UniquePtr<MimeType> parsed = MimeType::Parse(in);
+  ASSERT_TRUE(parsed) << "Parsing succeeded";
+  nsAutoString out;
+  parsed->Serialize(out);
+  ASSERT_TRUE(out.EqualsLiteral("!#$%&'*+-.^_`|~0123456789abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz/!#$%&'*+-.^_`|~0123456789abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz;!#$%&'*+-.^_`|~0123456789abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz=!#$%&'*+-.^_`|~0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz")) <<
+    "Case normalized properly #2";
+}
+
+TEST(MimeType, LegacyCommentSyntax1)
+{
+  const auto in = NS_LITERAL_STRING("text/html;charset=gbk(");
+  UniquePtr<MimeType> parsed = MimeType::Parse(in);
+  ASSERT_TRUE(parsed) << "Parsing succeeded";
+  nsAutoString out;
+  parsed->Serialize(out);
+  ASSERT_TRUE(out.EqualsLiteral("text/html;charset=\"gbk(\"")) <<
+    "Legacy comment syntax #1";
+}
+
+TEST(MimeType, LegacyCommentSyntax2)
+{
+  const auto in = NS_LITERAL_STRING("text/html;x=(;charset=gbk");
+  UniquePtr<MimeType> parsed = MimeType::Parse(in);
+  ASSERT_TRUE(parsed) << "Parsing succeeded";
+  nsAutoString out;
+  parsed->Serialize(out);
+  ASSERT_TRUE(out.EqualsLiteral("text/html;x=\"(\";charset=gbk")) <<
+    "Legacy comment syntax #2";
+}
--- a/dom/base/test/gtest/moz.build
+++ b/dom/base/test/gtest/moz.build
@@ -1,16 +1,17 @@
 # -*- 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/.
 
 UNIFIED_SOURCES += [
     'TestContentUtils.cpp',
+    'TestMimeType.cpp',
     'TestPlainTextSerializer.cpp',
     'TestXPathGenerator.cpp',
 ]
 
 LOCAL_INCLUDES += [
     '/dom/base'
 ]
 
--- a/dom/bindings/BindingUtils.cpp
+++ b/dom/bindings/BindingUtils.cpp
@@ -3806,20 +3806,17 @@ HTMLConstructor(JSContext* aCx, unsigned
   if (!definition) {
     return ThrowErrorMessage(aCx, MSG_ILLEGAL_CONSTRUCTOR);
   }
 
   // Steps 4 and 5 do some sanity checks on our callee.  We add to those a
   // determination of what sort of element we're planning to construct.
   // Technically, this should happen (implicitly) in step 8, but this
   // determination is side-effect-free, so it's OK.
-  int32_t ns = doc->GetDefaultNamespaceID();
-  if (ns != kNameSpaceID_XUL) {
-    ns = kNameSpaceID_XHTML;
-  }
+  int32_t ns = definition->mNamespaceID;
 
   constructorGetterCallback cb = nullptr;
   if (ns == kNameSpaceID_XUL) {
     if (definition->mLocalName == nsGkAtoms::menupopup ||
         definition->mLocalName == nsGkAtoms::popup ||
         definition->mLocalName == nsGkAtoms::panel ||
         definition->mLocalName == nsGkAtoms::tooltip) {
       cb = XULPopupElement_Binding::GetConstructorObject;
--- a/dom/storage/moz.build
+++ b/dom/storage/moz.build
@@ -1,16 +1,16 @@
 # -*- 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/.
 
 with Files("**"):
-    BUG_COMPONENT = ("Core", "DOM")
+    BUG_COMPONENT = ("Core", "DOM: Web Storage")
 
 EXPORTS.mozilla.dom += [
     'LocalStorage.h',
     'LocalStorageManager.h',
     'SessionStorageManager.h',
     'Storage.h',
     'StorageActivityService.h',
     'StorageIPC.h',
--- a/dom/tests/mochitest/webcomponents/chrome_disabled.ini
+++ b/dom/tests/mochitest/webcomponents/chrome_disabled.ini
@@ -1,5 +1,8 @@
 [DEFAULT]
 prefs =
   dom.webcomponents.customelements.enabled=false
 
 [test_xul_custom_element.xul]
+[test_custom_element_namespace.html]
+[test_custom_element_namespace.xhtml]
+[test_custom_element_namespace.xul]
new file mode 100644
--- /dev/null
+++ b/dom/tests/mochitest/webcomponents/test_custom_element_namespace.html
@@ -0,0 +1,95 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+  <title>Custom Elements in an HTML document</title>
+  <script type="text/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+  <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css" />
+  <script>
+    SimpleTest.waitForExplicitFinish();
+
+    const HTML_NS = "http://www.w3.org/1999/xhtml";
+    const XUL_NS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
+
+    class TestXULCustomElement extends XULElement {
+      constructor() {
+        super();
+      }
+
+      get connected() {
+        return true;
+      }
+    }
+
+    customElements.define("test-xul-element", TestXULCustomElement);
+
+    class TestHTMLCustomElement extends HTMLElement {
+      constructor() {
+        super();
+      }
+
+      get connected() {
+        return true;
+      }
+    }
+
+    customElements.define("test-html-element", TestHTMLCustomElement);
+
+    function checkElement(element, ns, connected, type) {
+      is(element.namespaceURI, ns, `${type} should have the correct namespace`);
+      if (connected) {
+        ok(element.connected, `${type} should have applied the class`);
+      } else {
+        is(element.connected, undefined, `${type} should not have applied the class`);
+      }
+    }
+
+    function runTest() {
+      let element = new TestXULCustomElement();
+      checkElement(element, XUL_NS, true, "instantiated XUL");
+
+      element = document.getElementById("xul2");
+      checkElement(element, HTML_NS, false, "parsed XUL as HTML");
+
+      element = document.createElement("test-xul-element");
+      checkElement(element, HTML_NS, false, "document.createElement(XUL)");
+
+      element = document.createXULElement("test-xul-element");
+      checkElement(element, XUL_NS, true, "document.createXULElement(XUL)");
+
+      element = document.createElementNS(XUL_NS, "test-xul-element");
+      checkElement(element, XUL_NS, true, "document.createElementNS(XUL, XUL)");
+
+      element = document.createElementNS(HTML_NS, "test-xul-element");
+      checkElement(element, HTML_NS, false, "document.createElementNS(HTML, XUL)");
+
+      element = new TestHTMLCustomElement();
+      checkElement(element, HTML_NS, true, "instantiated HTML");
+
+      element = document.getElementById("html2");
+      checkElement(element, HTML_NS, true, "parsed HTML as HTML");
+
+      element = document.createElement("test-html-element");
+      checkElement(element, HTML_NS, true, "document.createElement(HTML)");
+
+      element = document.createXULElement("test-html-element");
+      checkElement(element, XUL_NS, false, "document.createXULElement(HTML)");
+
+      element = document.createElementNS(XUL_NS, "test-html-element");
+      checkElement(element, XUL_NS, false, "document.createElementNS(XUL, HTML)");
+
+      element = document.createElementNS(HTML_NS, "test-html-element");
+      checkElement(element, HTML_NS, true, "document.createElementNS(HTML, HTML)");
+
+      SimpleTest.finish();
+    }
+  </script>
+</head>
+<body onload="runTest();">
+  <p id="display"></p>
+  <div id="content">
+    <test-xul-element id="xul2"/>
+    <test-html-element id="html2"/>
+  </div>
+  <pre id="test"></pre>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/dom/tests/mochitest/webcomponents/test_custom_element_namespace.xhtml
@@ -0,0 +1,104 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+  <title>Custom Elements in an XHTML document</title>
+  <script type="text/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+  <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css" />
+  <script>
+    SimpleTest.waitForExplicitFinish();
+
+    const HTML_NS = "http://www.w3.org/1999/xhtml";
+    const XUL_NS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
+
+    class TestXULCustomElement extends XULElement {
+      constructor() {
+        super();
+      }
+
+      get connected() {
+        return true;
+      }
+    }
+
+    customElements.define("test-xul-element", TestXULCustomElement);
+
+    class TestHTMLCustomElement extends HTMLElement {
+      constructor() {
+        super();
+      }
+
+      get connected() {
+        return true;
+      }
+    }
+
+    customElements.define("test-html-element", TestHTMLCustomElement);
+
+    function checkElement(element, ns, connected, type) {
+      is(element.namespaceURI, ns, `${type} should have the correct namespace`);
+      if (connected) {
+        ok(element.connected, `${type} should have applied the class`);
+      } else {
+        is(element.connected, undefined, `${type} should not have applied the class`);
+      }
+    }
+
+    function runTest() {
+      let element = new TestXULCustomElement();
+      checkElement(element, XUL_NS, true, "instantiated XUL");
+
+      element = document.getElementById("xul1");
+      checkElement(element, XUL_NS, true, "parsed XUL as XUL");
+
+      element = document.getElementById("xul2");
+      checkElement(element, HTML_NS, false, "parsed XUL as HTML");
+
+      element = document.createElement("test-xul-element");
+      checkElement(element, HTML_NS, false, "document.createElement(XUL)");
+
+      element = document.createXULElement("test-xul-element");
+      checkElement(element, XUL_NS, true, "document.createXULElement(XUL)");
+
+      element = document.createElementNS(XUL_NS, "test-xul-element");
+      checkElement(element, XUL_NS, true, "document.createElementNS(XUL, XUL)");
+
+      element = document.createElementNS(HTML_NS, "test-xul-element");
+      checkElement(element, HTML_NS, false, "document.createElementNS(HTML, XUL)");
+
+      element = new TestHTMLCustomElement();
+      checkElement(element, HTML_NS, true, "instantiated HTML");
+
+      element = document.getElementById("html1");
+      checkElement(element, XUL_NS, false, "parsed HTML as XUL");
+
+      element = document.getElementById("html2");
+      checkElement(element, HTML_NS, true, "parsed HTML as HTML");
+
+      element = document.createElement("test-html-element");
+      checkElement(element, HTML_NS, true, "document.createElement(HTML)");
+
+      element = document.createXULElement("test-html-element");
+      checkElement(element, XUL_NS, false, "document.createXULElement(HTML)");
+
+      element = document.createElementNS(XUL_NS, "test-html-element");
+      checkElement(element, XUL_NS, false, "document.createElementNS(XUL, HTML)");
+
+      element = document.createElementNS(HTML_NS, "test-html-element");
+      checkElement(element, HTML_NS, true, "document.createElementNS(HTML, HTML)");
+
+      SimpleTest.finish();
+    }
+  </script>
+</head>
+<body onload="runTest();">
+  <p id="display"></p>
+  <div id="content">
+    <test-xul-element xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" id="xul1"/>
+    <test-html-element xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" id="html1"/>
+    <test-xul-element id="xul2"/>
+    <test-html-element id="html2"/>
+  </div>
+  <pre id="test"></pre>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/dom/tests/mochitest/webcomponents/test_custom_element_namespace.xul
@@ -0,0 +1,112 @@
+<?xml version="1.0"?>
+<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
+<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css" type="text/css"?>
+
+<window title="XUL Custom Elements"
+        xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+        onload="runTest();">
+  <title>Custom Elements in a XUL document</title>
+
+  <script type="application/javascript"
+          src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"/>
+
+  <script type="application/javascript">
+  <![CDATA[
+    SimpleTest.waitForExplicitFinish();
+
+    const HTML_NS = "http://www.w3.org/1999/xhtml";
+    const XUL_NS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
+
+    class TestXULCustomElement extends XULElement {
+      constructor() {
+        super();
+      }
+
+      get connected() {
+        return true;
+      }
+    }
+
+    customElements.define("test-xul-element", TestXULCustomElement);
+
+    class TestHTMLCustomElement extends HTMLElement {
+      constructor() {
+        super();
+      }
+
+      get connected() {
+        return true;
+      }
+    }
+
+    customElements.define("test-html-element", TestHTMLCustomElement);
+
+    function checkElement(element, ns, connected, type) {
+      is(element.namespaceURI, ns, `${type} should have the correct namespace`);
+      if (connected) {
+        ok(element.connected, `${type} should have applied the class`);
+      } else {
+        is(element.connected, undefined, `${type} should not have applied the class`);
+      }
+    }
+
+    function runTest() {
+      let element = new TestXULCustomElement();
+      checkElement(element, XUL_NS, true, "instantiated XUL");
+
+      element = document.getElementById("xul1");
+      checkElement(element, XUL_NS, true, "parsed XUL as XUL");
+
+      element = document.getElementById("xul2");
+      checkElement(element, HTML_NS, false, "parsed XUL as HTML");
+
+      element = document.createElement("test-xul-element");
+      checkElement(element, XUL_NS, true, "document.createElement(XUL)");
+
+      element = document.createXULElement("test-xul-element");
+      checkElement(element, XUL_NS, true, "document.createXULElement(XUL)");
+
+      element = document.createElementNS(XUL_NS, "test-xul-element");
+      checkElement(element, XUL_NS, true, "document.createElementNS(XUL, XUL)");
+
+      element = document.createElementNS(HTML_NS, "test-xul-element");
+      checkElement(element, HTML_NS, false, "document.createElementNS(HTML, XUL)");
+
+      element = new TestHTMLCustomElement();
+      checkElement(element, HTML_NS, true, "instantiated HTML");
+
+      element = document.getElementById("html1");
+      checkElement(element, XUL_NS, false, "parsed HTML as XUL");
+
+      element = document.getElementById("html2");
+      checkElement(element, HTML_NS, true, "parsed HTML as HTML");
+
+      element = document.createElement("test-html-element");
+      checkElement(element, XUL_NS, false, "document.createElement(HTML)");
+
+      element = document.createXULElement("test-html-element");
+      checkElement(element, XUL_NS, false, "document.createXULElement(HTML)");
+
+      element = document.createElementNS(XUL_NS, "test-html-element");
+      checkElement(element, XUL_NS, false, "document.createElementNS(XUL, HTML)");
+
+      element = document.createElementNS(HTML_NS, "test-html-element");
+      checkElement(element, HTML_NS, true, "document.createElementNS(HTML, HTML)");
+
+      SimpleTest.finish();
+    }
+  ]]>
+  </script>
+
+  <test-xul-element id="xul1"/>
+  <test-html-element id="html1"/>
+
+  <body xmlns="http://www.w3.org/1999/xhtml">
+    <p id="display"></p>
+    <div id="content" style="display: none">
+      <test-xul-element id="xul2"/>
+      <test-html-element id="html2"/>
+    </div>
+    <pre id="test"></pre>
+  </body>
+</window>
--- a/dom/tests/moz.build
+++ b/dom/tests/moz.build
@@ -75,35 +75,35 @@ with Files("mochitest/gamepad/**"):
 # TODO: big mix of components here
 with Files("mochitest/general/**"):
     BUG_COMPONENT = ("Core", "DOM")
 
 with Files("mochitest/geolocation/**"):
     BUG_COMPONENT = ("Core", "Geolocation")
 
 with Files("mochitest/localstorage/**"):
-    BUG_COMPONENT = ("Core", "DOM")
+    BUG_COMPONENT = ("Core", "DOM: Web Storage")
 
 with Files("mochitest/orientation/**"):
     BUG_COMPONENT = ("Core", "DOM: Device Interfaces")
 
 with Files("mochitest/orientation/*507902*"):
     BUG_COMPONENT = ("Core", "Layout: Images")
 
 with Files("mochitest/pointerlock/**"):
     BUG_COMPONENT = ("Core", "DOM")
 
 with Files("mochitest/script/**"):
     BUG_COMPONENT = ("Core", "DOM: Core & HTML")
 
 with Files("mochitest/sessionstorage/**"):
-    BUG_COMPONENT = ("Core", "DOM: Core & HTML")
+    BUG_COMPONENT = ("Core", "DOM: Web Storage")
 
 with Files("mochitest/storageevent/**"):
-    BUG_COMPONENT = ("Core", "DOM")
+    BUG_COMPONENT = ("Core", "DOM: Web Storage")
 
 with Files("mochitest/webcomponents/**"):
     BUG_COMPONENT = ("Core", "DOM")
 
 with Files("mochitest/whatwg/**"):
     BUG_COMPONENT = ("Core", "DOM")
 
 with Files("reftest/**"):
--- a/dom/xhr/XMLHttpRequestMainThread.cpp
+++ b/dom/xhr/XMLHttpRequestMainThread.cpp
@@ -2167,17 +2167,21 @@ XMLHttpRequestMainThread::OnStopRequest(
   }
 
   if (NS_SUCCEEDED(status) &&
       mResponseType == XMLHttpRequestResponseType::Blob &&
       !waitingForBlobCreation) {
     // Smaller files may be written in cache map instead of separate files.
     // Also, no-store response cannot be written in persistent cache.
     nsAutoCString contentType;
-    mChannel->GetContentType(contentType);
+    if (!mOverrideMimeType.IsEmpty()) {
+      contentType.Assign(NS_ConvertUTF16toUTF8(mOverrideMimeType));
+    } else {
+      mChannel->GetContentType(contentType);
+    }
 
     // mBlobStorage can be null if the channel is non-file non-cacheable
     // and if the response length is zero.
     MaybeCreateBlobStorage();
     mBlobStorage->GetBlobWhenReady(GetOwner(), contentType, this);
     waitingForBlobCreation = true;
 
     NS_ASSERTION(mResponseBody.IsEmpty(), "mResponseBody should be empty");
@@ -3130,17 +3134,22 @@ XMLHttpRequestMainThread::OverrideMimeTy
   NOT_CALLABLE_IN_SYNC_SEND_RV
 
   if (mState == XMLHttpRequest_Binding::LOADING ||
       mState == XMLHttpRequest_Binding::DONE) {
     aRv.Throw(NS_ERROR_DOM_INVALID_STATE_XHR_MUST_NOT_BE_LOADING_OR_DONE);
     return;
   }
 
-  mOverrideMimeType = aMimeType;
+  UniquePtr<MimeType> parsed = MimeType::Parse(aMimeType);
+  if (parsed) {
+    parsed->Serialize(mOverrideMimeType);
+  } else {
+    mOverrideMimeType.AssignLiteral(APPLICATION_OCTET_STREAM);
+  }
 }
 
 bool
 XMLHttpRequestMainThread::MozBackgroundRequest() const
 {
   return mFlagBackgroundRequest;
 }
 
--- a/dom/xhr/XMLHttpRequestMainThread.h
+++ b/dom/xhr/XMLHttpRequestMainThread.h
@@ -34,16 +34,17 @@
 #include "mozilla/MemoryReporting.h"
 #include "mozilla/NotNull.h"
 #include "mozilla/dom/MutableBlobStorage.h"
 #include "mozilla/dom/BodyExtractor.h"
 #include "mozilla/dom/ClientInfo.h"
 #include "mozilla/dom/TypedArray.h"
 #include "mozilla/dom/File.h"
 #include "mozilla/dom/FormData.h"
+#include "mozilla/dom/MimeType.h"
 #include "mozilla/dom/PerformanceStorage.h"
 #include "mozilla/dom/ServiceWorkerDescriptor.h"
 #include "mozilla/dom/URLSearchParams.h"
 #include "mozilla/dom/XMLHttpRequest.h"
 #include "mozilla/dom/XMLHttpRequestBinding.h"
 #include "mozilla/dom/XMLHttpRequestEventTarget.h"
 #include "mozilla/dom/XMLHttpRequestString.h"
 #include "mozilla/Encoding.h"
--- a/editor/libeditor/HTMLEditor.cpp
+++ b/editor/libeditor/HTMLEditor.cpp
@@ -781,22 +781,22 @@ HTMLEditor::HandleKeyPressEvent(WidgetKe
         // TabInTable might cause reframe
         if (Destroyed()) {
           return NS_OK;
         }
         if (handled) {
           ScrollSelectionIntoView(false);
         }
       } else if (HTMLEditUtils::IsListItem(blockParent)) {
-        rv = Indent(aKeyboardEvent->IsShift()
-                    ? NS_LITERAL_STRING("outdent")
-                    : NS_LITERAL_STRING("indent"));
+        rv = !aKeyboardEvent->IsShift() ? IndentAsAction() : OutdentAsAction();
         handled = true;
       }
-      NS_ENSURE_SUCCESS(rv, rv);
+      if (NS_WARN_IF(NS_FAILED(rv))) {
+        return rv;
+      }
       if (handled) {
         aKeyboardEvent->PreventDefault(); // consumed
         return NS_OK;
       }
       if (aKeyboardEvent->IsShift()) {
         return NS_OK; // don't type text for shift tabs
       }
       aKeyboardEvent->PreventDefault();
@@ -1632,18 +1632,20 @@ HTMLEditor::InsertElementAtSelection(Ele
         InsertNodeIntoProperAncestorWithTransaction(
           *aElement, pointToInsert, SplitAtEdges::eAllowToCreateEmptyContainer);
       if (NS_WARN_IF(!insertedPoint.IsSet())) {
         return NS_ERROR_FAILURE;
       }
       // Set caret after element, but check for special case
       //  of inserting table-related elements: set in first cell instead
       if (!SetCaretInTableCell(aElement)) {
-        rv = SetCaretAfterElement(aElement);
-        NS_ENSURE_SUCCESS(rv, rv);
+        rv = CollapseSelectionAfter(*selection, *aElement);
+        if (NS_WARN_IF(NS_FAILED(rv))) {
+          return rv;
+        }
       }
       // check for inserting a whole table at the end of a block. If so insert
       // a br after it.
       if (HTMLEditUtils::IsTable(aElement) &&
           IsLastEditableChild(aElement)) {
         DebugOnly<bool> advanced = insertedPoint.AdvanceOffset();
         NS_WARNING_ASSERTION(advanced,
           "Failed to advance offset from inserted point");
@@ -1726,57 +1728,103 @@ HTMLEditor::InsertNodeIntoProperAncestor
     }
   }
   return pointToInsert;
 }
 
 NS_IMETHODIMP
 HTMLEditor::SelectElement(Element* aElement)
 {
+  if (NS_WARN_IF(!aElement)) {
+    return NS_ERROR_INVALID_ARG;
+  }
+  RefPtr<Selection> selection = GetSelection();
+  if (NS_WARN_IF(!selection)) {
+    return NS_ERROR_FAILURE;
+  }
+  nsresult rv = SelectContentInternal(*selection, *aElement);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+  return NS_OK;
+}
+
+nsresult
+HTMLEditor::SelectContentInternal(Selection& aSelection,
+                                  nsIContent& aContentToSelect)
+{
   // Must be sure that element is contained in the document body
-  if (!IsDescendantOfEditorRoot(aElement)) {
-    return NS_ERROR_NULL_POINTER;
-  }
-
-  RefPtr<Selection> selection = GetSelection();
-  NS_ENSURE_TRUE(selection, NS_ERROR_NULL_POINTER);
-  nsINode* parent = aElement->GetParentNode();
+  if (!IsDescendantOfEditorRoot(&aContentToSelect)) {
+    return NS_ERROR_FAILURE;
+  }
+
+  nsINode* parent = aContentToSelect.GetParentNode();
   if (NS_WARN_IF(!parent)) {
     return NS_ERROR_FAILURE;
   }
 
-  int32_t offsetInParent = parent->ComputeIndexOf(aElement);
+  // Don't notify selection change at collapse.
+  AutoUpdateViewBatch notifySelectionChangeOnce(this);
+
+  // XXX Perhaps, Selection should have SelectNode(nsIContent&).
+  int32_t offsetInParent = parent->ComputeIndexOf(&aContentToSelect);
 
   // Collapse selection to just before desired element,
-  nsresult rv = selection->Collapse(parent, offsetInParent);
-  if (NS_SUCCEEDED(rv)) {
-    // then extend it to just after
-    rv = selection->Extend(parent, offsetInParent + 1);
-  }
-  return rv;
+  nsresult rv = aSelection.Collapse(parent, offsetInParent);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+  // then extend it to just after
+  rv = aSelection.Extend(parent, offsetInParent + 1);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+  return NS_OK;
 }
 
 NS_IMETHODIMP
 HTMLEditor::SetCaretAfterElement(Element* aElement)
 {
-  // Be sure the element is contained in the document body
-  if (!aElement || !IsDescendantOfEditorRoot(aElement)) {
-    return NS_ERROR_NULL_POINTER;
-  }
-
+  if (NS_WARN_IF(!aElement)) {
+    return NS_ERROR_INVALID_ARG;
+  }
   RefPtr<Selection> selection = GetSelection();
-  NS_ENSURE_TRUE(selection, NS_ERROR_NULL_POINTER);
-  nsCOMPtr<nsINode> parent = aElement->GetParentNode();
-  NS_ENSURE_TRUE(parent, NS_ERROR_NULL_POINTER);
+  if (NS_WARN_IF(!selection)) {
+    return NS_ERROR_FAILURE;
+  }
+  nsresult rv = CollapseSelectionAfter(*selection, *aElement);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+  return NS_OK;
+}
+
+nsresult
+HTMLEditor::CollapseSelectionAfter(Selection& aSelection,
+                                   Element& aElement)
+{
+  // Be sure the element is contained in the document body
+  if (NS_WARN_IF(!IsDescendantOfEditorRoot(&aElement))) {
+    return NS_ERROR_INVALID_ARG;
+  }
+  nsINode* parent = aElement.GetParentNode();
+  if (NS_WARN_IF(!parent)) {
+    return NS_ERROR_FAILURE;
+  }
   // Collapse selection to just after desired element,
-  EditorRawDOMPoint afterElement(aElement);
+  EditorRawDOMPoint afterElement(&aElement);
   if (NS_WARN_IF(!afterElement.AdvanceOffset())) {
     return NS_ERROR_FAILURE;
   }
-  return selection->Collapse(afterElement);
+  ErrorResult error;
+  aSelection.Collapse(afterElement, error);
+  if (NS_WARN_IF(error.Failed())) {
+    return error.StealNSResult();
+  }
+  return NS_OK;
 }
 
 NS_IMETHODIMP
 HTMLEditor::SetParagraphFormat(const nsAString& aParagraphFormat)
 {
   nsAutoString lowerCaseTagName(aParagraphFormat);
   ToLowerCase(lowerCaseTagName);
   RefPtr<nsAtom> tagName = NS_Atomize(lowerCaseTagName);
@@ -2268,43 +2316,93 @@ HTMLEditor::InsertBasicBlockWithTransact
   }
 
   return rules->DidDoAction(selection, subActionInfo, rv);
 }
 
 NS_IMETHODIMP
 HTMLEditor::Indent(const nsAString& aIndent)
 {
+  if (aIndent.LowerCaseEqualsLiteral("indent")) {
+    nsresult rv = IndentAsAction();
+    if (NS_WARN_IF(NS_FAILED(rv))) {
+      return rv;
+    }
+    return NS_OK;
+  }
+  if (aIndent.LowerCaseEqualsLiteral("outdent")) {
+    nsresult rv = OutdentAsAction();
+    if (NS_WARN_IF(NS_FAILED(rv))) {
+      return rv;
+    }
+    return NS_OK;
+  }
+  return NS_ERROR_INVALID_ARG;
+}
+
+nsresult
+HTMLEditor::IndentAsAction()
+{
   if (!mRules) {
     return NS_ERROR_NOT_INITIALIZED;
   }
 
+  AutoPlaceholderBatch beginBatching(this);
+  nsresult rv = IndentOrOutdentAsSubAction(EditSubAction::eIndent);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+  return NS_OK;
+}
+
+nsresult
+HTMLEditor::OutdentAsAction()
+{
+  if (!mRules) {
+    return NS_ERROR_NOT_INITIALIZED;
+  }
+
+  AutoPlaceholderBatch beginBatching(this);
+  nsresult rv = IndentOrOutdentAsSubAction(EditSubAction::eOutdent);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+  return NS_OK;
+}
+
+nsresult
+HTMLEditor::IndentOrOutdentAsSubAction(EditSubAction aIndentOrOutdent)
+{
+  MOZ_ASSERT(mRules);
+  MOZ_ASSERT(mPlaceholderBatch);
+  MOZ_ASSERT(aIndentOrOutdent == EditSubAction::eIndent ||
+             aIndentOrOutdent == EditSubAction::eOutdent);
+
   // Protect the edit rules object from dying
   RefPtr<TextEditRules> rules(mRules);
 
   bool cancel, handled;
-  EditSubAction indentOrOutdent =
-    aIndent.LowerCaseEqualsLiteral("outdent") ? EditSubAction::eOutdent :
-                                                EditSubAction::eIndent;
-  AutoPlaceholderBatch beginBatching(this);
   AutoTopLevelEditSubActionNotifier maybeTopLevelEditSubAction(
-                                      *this, indentOrOutdent, nsIEditor::eNext);
-
-  // pre-process
+                                      *this, aIndentOrOutdent,
+                                      nsIEditor::eNext);
+
   RefPtr<Selection> selection = GetSelection();
-  NS_ENSURE_TRUE(selection, NS_ERROR_NULL_POINTER);
-
-  EditSubActionInfo subActionInfo(indentOrOutdent);
+  if (NS_WARN_IF(!selection)) {
+    return NS_ERROR_FAILURE;
+  }
+
+  EditSubActionInfo subActionInfo(aIndentOrOutdent);
   nsresult rv =
     rules->WillDoAction(selection, subActionInfo, &cancel, &handled);
   if (cancel || NS_FAILED(rv)) {
     return rv;
   }
 
-  if (!handled && selection->IsCollapsed() && aIndent.EqualsLiteral("indent")) {
+  if (!handled && selection->IsCollapsed() &&
+      aIndentOrOutdent == EditSubAction::eIndent) {
     nsRange* firstRange = selection->GetRangeAt(0);
     if (NS_WARN_IF(!firstRange)) {
       return NS_ERROR_FAILURE;
     }
 
     EditorRawDOMPoint atStartOfSelection(firstRange->StartRef());
     if (NS_WARN_IF(!atStartOfSelection.IsSet()) ||
         NS_WARN_IF(!atStartOfSelection.GetContainerAsContent())) {
@@ -2364,17 +2462,21 @@ HTMLEditor::Indent(const nsAString& aInd
       return NS_ERROR_FAILURE;
     }
     selection->Collapse(RawRangeBoundary(firstRange->GetStartContainer(), 0),
                         error);
     if (NS_WARN_IF(error.Failed())) {
       return error.StealNSResult();
     }
   }
-  return rules->DidDoAction(selection, subActionInfo, rv);
+  rv = rules->DidDoAction(selection, subActionInfo, rv);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+  return NS_OK;
 }
 
 //TODO: IMPLEMENT ALIGNMENT!
 
 NS_IMETHODIMP
 HTMLEditor::Align(const nsAString& aAlignType)
 {
   // Protect the edit rules object from dying
--- a/editor/libeditor/HTMLEditor.h
+++ b/editor/libeditor/HTMLEditor.h
@@ -176,16 +176,22 @@ public:
 
   /**
    * OnInputLineBreak() is called when user inputs a line break with
    * Shift + Enter or something.
    */
   nsresult OnInputLineBreak();
 
   /**
+   * Indent or outdent content around Selection.
+   */
+  nsresult IndentAsAction();
+  nsresult OutdentAsAction();
+
+  /**
    * event callback when a mouse button is pressed
    * @param aX      [IN] horizontal position of the pointer
    * @param aY      [IN] vertical position of the pointer
    * @param aTarget [IN] the element triggering the event
    * @param aMouseEvent [IN] the event
    */
   nsresult OnMouseDown(int32_t aX, int32_t aY, Element* aTarget,
                        dom::Event* aMouseEvent);
@@ -767,16 +773,34 @@ protected: // Called by helper classes.
   virtual void OnEndHandlingTopLevelEditSubAction() override;
 
 protected: // Shouldn't be used by friend classes
   virtual ~HTMLEditor();
 
   virtual nsresult SelectAllInternal() override;
 
   /**
+   * SelectContentInternal() sets Selection to aContentToSelect to
+   * aContentToSelect + 1 in parent of aContentToSelect.
+   *
+   * @param aSelection          The Selection, callers have to guarantee the
+   *                            lifetime.
+   * @param aContentToSelect    The content which should be selected.
+   */
+  nsresult SelectContentInternal(Selection& aSelection,
+                                 nsIContent& aContentToSelect);
+
+  /**
+   * CollapseSelectionAfter() collapses Selection after aElement.
+   * If aElement is an orphan node or not in editing host, returns error.
+   */
+  nsresult CollapseSelectionAfter(Selection& aSelection,
+                                  Element& aElement);
+
+  /**
    * PasteInternal() pasts text with replacing selected content.
    * This tries to dispatch ePaste event first.  If its defaultPrevent() is
    * called, this does nothing but returns NS_OK.
    *
    * @param aClipboardType  nsIClipboard::kGlobalClipboard or
    *                        nsIClipboard::kSelectionClipboard.
    */
   nsresult PasteInternal(int32_t aClipboardType);
@@ -817,16 +841,26 @@ protected: // Shouldn't be used by frien
    * First, this method splits aStringToInsert to multiple chunks which start
    * with non-linebreaker except first chunk and end with a linebreaker except
    * last chunk.  Then, each chunk starting with ">" is inserted after wrapping
    * with <span _moz_quote="true">, and each chunk not starting with ">" is
    * inserted as normal text.
    */
   nsresult InsertTextWithQuotationsInternal(const nsAString& aStringToInsert);
 
+  /**
+   * IndentOrOutdentAsSubAction() indents or outdents the content around
+   * Selection.  Callers have to guarantee that there is a placeholder
+   * transaction.
+   *
+   * @param aEditSubAction      Must be EditSubAction::eIndent or
+   *                            EditSubAction::eOutdent.
+   */
+  nsresult IndentOrOutdentAsSubAction(EditSubAction aEditSubAction);
+
   nsresult LoadHTML(const nsAString& aInputString);
 
   nsresult SetInlinePropertyInternal(nsAtom& aProperty,
                                      nsAtom* aAttribute,
                                      const nsAString& aValue);
   nsresult RemoveInlinePropertyInternal(nsAtom* aProperty,
                                         nsAtom* aAttribute);
 
--- a/editor/libeditor/HTMLEditorCommands.cpp
+++ b/editor/libeditor/HTMLEditorCommands.cpp
@@ -510,17 +510,21 @@ IndentCommand::DoCommand(const char* aCo
   nsCOMPtr<nsIEditor> editor = do_QueryInterface(refCon);
   if (!editor) {
     return NS_OK;
   }
   mozilla::HTMLEditor* htmlEditor = editor->AsHTMLEditor();
   if (!htmlEditor) {
     return NS_OK;
   }
-  return htmlEditor->Indent(NS_LITERAL_STRING("indent"));
+  nsresult rv = htmlEditor->IndentAsAction();
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+  return NS_OK;
 }
 
 NS_IMETHODIMP
 IndentCommand::DoCommandParams(const char* aCommandName,
                                nsICommandParams* aParams,
                                nsISupports* refCon)
 {
   return DoCommand(aCommandName, refCon);
@@ -561,17 +565,21 @@ OutdentCommand::DoCommand(const char* aC
   nsCOMPtr<nsIEditor> editor = do_QueryInterface(refCon);
   if (!editor) {
     return NS_OK;
   }
   mozilla::HTMLEditor* htmlEditor = editor->AsHTMLEditor();
   if (!htmlEditor) {
     return NS_OK;
   }
-  return htmlEditor->Indent(NS_LITERAL_STRING("outdent"));
+  nsresult rv = htmlEditor->OutdentAsAction();
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+  return NS_OK;
 }
 
 NS_IMETHODIMP
 OutdentCommand::DoCommandParams(const char* aCommandName,
                                 nsICommandParams* aParams,
                                 nsISupports* refCon)
 {
   return DoCommand(aCommandName, refCon);
--- a/editor/libeditor/HTMLTableEditor.cpp
+++ b/editor/libeditor/HTMLTableEditor.cpp
@@ -3070,17 +3070,19 @@ HTMLEditor::SetSelectionAfterTableEdit(E
     nsresult rv = GetCellAt(aTable, aRow, aCol, getter_AddRefs(cell));
     if (NS_FAILED(rv)) {
       break;
     }
 
     if (cell) {
       if (aSelected) {
         // Reselect the cell
-        SelectElement(cell);
+        DebugOnly<nsresult> rv = SelectContentInternal(*selection, *cell);
+        NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
+          "Failed to select the cell");
         return;
       }
 
       // Set the caret to deepest first child
       //   but don't go into nested tables
       // TODO: Should we really be placing the caret at the END
       //  of the cell content?
       CollapseSelectionToDeepestNonTableFirstChild(selection, cell);
--- a/editor/libeditor/tests/mochitest.ini
+++ b/editor/libeditor/tests/mochitest.ini
@@ -276,16 +276,20 @@ subsuite = clipboard
 skip-if = android_version == '24'
 [test_inline_style_cache.html]
 [test_inlineTableEditing.html]
 [test_insertParagraph_in_inline_editing_host.html]
 [test_keypress_untrusted_event.html]
 [test_middle_click_paste.html]
 subsuite = clipboard
 skip-if = android_version == '24'
+[test_nsIHTMLEditor_selectElement.html]
+skip-if = toolkit == 'android' && debug # bug 1480702, causes permanent failure of non-related test
+[test_nsIHTMLEditor_setCaretAfterElement.html]
+skip-if = toolkit == 'android' && debug # bug 1480702, causes permanent failure of non-related test
 [test_objectResizing.html]
 [test_root_element_replacement.html]
 [test_select_all_without_body.html]
 [test_spellcheck_pref.html]
 skip-if = toolkit == 'android'
 [test_undo_after_spellchecker_replaces_word.html]
 skip-if = toolkit == 'android'
 [test_undo_redo_stack_after_setting_value.html]
new file mode 100644
--- /dev/null
+++ b/editor/libeditor/tests/test_nsIHTMLEditor_selectElement.html
@@ -0,0 +1,131 @@
+<!DOCTYPE>
+<html>
+<head>
+  <title>Test for nsIHTMLEditor.selectElement()</title>
+  <script src="/tests/SimpleTest/SimpleTest.js"></script>
+  <link rel="stylesheet" href="/tests/SimpleTest/test.css">
+</head>
+<body>
+<div id="display">
+</div>
+<div id="content" contenteditable></div>
+<pre id="test">
+</pre>
+
+<script class="testbody" type="application/javascript">
+
+SimpleTest.waitForExplicitFinish();
+SimpleTest.waitForFocus(function() {
+  let editor = document.getElementById("content");
+  let selection = window.getSelection();
+
+  editor.innerHTML = "<p>p1<b>b1</b><i>i1</i></p><p><b>b2</b><i>i2</i>p2</p>";
+
+  editor.focus();
+  try {
+    getHTMLEditor().selectElement(editor.firstChild.firstChild);
+    ok(false, "nsIHTMLEditor.selectElement() should throw an exception if given node is not an element");
+  } catch (e) {
+    ok(true, `nsIHTMLEditor.selectElement() should throw an exception if given node is not an element: ${e}`);
+  }
+
+  editor.focus();
+  try {
+    getHTMLEditor().selectElement(editor.firstChild.firstChild.nextSibling);
+    is(selection.anchorNode, editor.firstChild,
+       "nsIHTMLEditor.selectElement() should set anchor node to parent of <b> element in the first paragraph");
+    is(selection.anchorOffset, 1,
+       "nsIHTMLEditor.selectElement() should set anchor offset to the index of <b> element in the first paragraph");
+    is(selection.focusNode, editor.firstChild,
+       "nsIHTMLEditor.selectElement() should set focus node to parent of <b> element in the first paragraph");
+    is(selection.focusOffset, 2,
+       "nsIHTMLEditor.selectElement() should set anchor offset to the index of <b> element + 1 in the first paragraph");
+  } catch (e) {
+    ok(false, `nsIHTMLEditor.selectElement() shouldn't throw exception when selecting an element in focused editor #1: ${e}`);
+  }
+
+  editor.focus();
+  try {
+    getHTMLEditor().selectElement(editor.firstChild.nextSibling.firstChild);
+    is(selection.anchorNode, editor.firstChild.nextSibling,
+       "nsIHTMLEditor.selectElement() should set anchor node to parent of <b> element in the second paragraph");
+    is(selection.anchorOffset, 0,
+       "nsIHTMLEditor.selectElement() should set anchor offset to the index of <b> element in the second paragraph");
+    is(selection.focusNode, editor.firstChild.nextSibling,
+       "nsIHTMLEditor.selectElement() should set focus node to parent of <b> element in the second paragraph");
+    is(selection.focusOffset, 1,
+       "nsIHTMLEditor.selectElement() should set anchor offset to the index of <b> element + 1 in the second paragraph");
+  } catch (e) {
+    ok(false, `nsIHTMLEditor.selectElement() shouldn't throw exception when selecting an element in focused editor #2: ${e}`);
+  }
+
+  editor.focus();
+  try {
+    getHTMLEditor().selectElement(editor);
+    ok(false, "nsIHTMLEditor.selectElement() should throw an exception if given node is the editing host");
+  } catch (e) {
+    ok(true, `nsIHTMLEditor.selectElement() should throw an exception if given node is the editing host: ${e}`);
+  }
+
+  editor.focus();
+  try {
+    getHTMLEditor().selectElement(editor.parentElement);
+    ok(false, "nsIHTMLEditor.selectElement() should throw an exception if given node is outside of the editing host");
+  } catch (e) {
+    ok(true, `nsIHTMLEditor.selectElement() should throw an exception if given node is outside of the editing host: ${e}`);
+  }
+
+  selection.removeAllRanges();
+  editor.blur();
+  try {
+    getHTMLEditor().selectElement(editor.firstChild.nextSibling.firstChild);
+    ok(false, "nsIHTMLEditor.selectElement() should throw an exception if there is no active editing host");
+  } catch (e) {
+    ok(true, `nsIHTMLEditor.selectElement() should throw an exception if there is no active editing host: ${e}`);
+  }
+
+  editor.focus();
+  editor.firstChild.firstChild.nextSibling.nextSibling.setAttribute("contenteditable", "false");
+  try {
+    getHTMLEditor().selectElement(editor.firstChild.firstChild.nextSibling.nextSibling);
+    is(selection.anchorNode, editor.firstChild,
+       "nsIHTMLEditor.selectElement() should set anchor node to parent of <i contenteditable=\"false\"> element in the first paragraph");
+    is(selection.anchorOffset, 2,
+       "nsIHTMLEditor.selectElement() should set anchor offset to the index of <i contenteditable=\"false\"> element in the first paragraph");
+    is(selection.focusNode, editor.firstChild,
+       "nsIHTMLEditor.selectElement() should set focus node to parent of <i contenteditable=\"false\"> element in the first paragraph");
+    is(selection.focusOffset, 3,
+       "nsIHTMLEditor.selectElement() should set anchor offset to the index of <i contenteditable=\"false\"> element + 1 in the first paragraph");
+  } catch (e) {
+    ok(false, `nsIHTMLEditor.selectElement() shouldn't throw exception when selecting an element in focused editor #3: ${e}`);
+  }
+
+  editor.focus();
+  editor.firstChild.nextSibling.setAttribute("contenteditable", "false");
+  try {
+    getHTMLEditor().selectElement(editor.firstChild.nextSibling.firstChild.nextSibling);
+    is(selection.anchorNode, editor.firstChild.nextSibling,
+       "nsIHTMLEditor.selectElement() should set anchor node to parent of <i> element in the second paragraph which is not editable");
+    is(selection.anchorOffset, 1,
+       "nsIHTMLEditor.selectElement() should set anchor offset to the index of <i> element in the second paragraph which is not editable");
+    is(selection.focusNode, editor.firstChild.nextSibling,
+       "nsIHTMLEditor.selectElement() should set focus node to parent of <i> element in the second paragraph which is not editable");
+    is(selection.focusOffset, 2,
+       "nsIHTMLEditor.selectElement() should set anchor offset to the index of <i> element + 1 in the second paragraph which is not editable");
+  } catch (e) {
+    ok(false, `nsIHTMLEditor.selectElement() shouldn't throw exception when selecting an element in focused editor #4: ${e}`);
+  }
+
+  SimpleTest.finish();
+});
+
+function getHTMLEditor() {
+  var Ci = SpecialPowers.Ci;
+  var editingSession = SpecialPowers.wrap(window).docShell.editingSession;
+  return editingSession.getEditorForWindow(window).QueryInterface(Ci.nsIHTMLEditor);
+}
+
+</script>
+</body>
+
+</html>
new file mode 100644
--- /dev/null
+++ b/editor/libeditor/tests/test_nsIHTMLEditor_setCaretAfterElement.html
@@ -0,0 +1,149 @@
+<!DOCTYPE>
+<html>
+<head>
+  <title>Test for nsIHTMLEditor.setCaretAfterElement()</title>
+  <script src="/tests/SimpleTest/SimpleTest.js"></script>
+  <link rel="stylesheet" href="/tests/SimpleTest/test.css">
+</head>
+<body>
+<div id="display">
+</div>
+<div id="content" contenteditable></div>
+<pre id="test">
+</pre>
+
+<script class="testbody" type="application/javascript">
+
+SimpleTest.waitForExplicitFinish();
+SimpleTest.waitForFocus(function() {
+  let editor = document.getElementById("content");
+  let selection = window.getSelection();
+
+  editor.innerHTML = "<p>p1<b>b1</b><i>i1</i></p><p><b>b2</b><i>i2</i>p2</p>";
+
+  editor.focus();
+  try {
+    getHTMLEditor().setCaretAfterElement(editor.firstChild.firstChild);
+    ok(false, "nsIHTMLEditor.setCaretAfterElement() should throw an exception if given node is not an element");
+  } catch (e) {
+    ok(true, `nsIHTMLEditor.setCaretAfterElement() should throw an exception if given node is not an element: ${e}`);
+  }
+
+  editor.focus();
+  try {
+    getHTMLEditor().setCaretAfterElement(editor.firstChild.firstChild.nextSibling);
+    ok(selection.isCollapsed,
+       "nsIHTMLEditor.setCaretAfterElement() should collapse Selection #1");
+    is(selection.anchorNode, editor.firstChild,
+       "nsIHTMLEditor.setCaretAfterElement() should set anchor node to parent of <b> element in the first paragraph");
+    is(selection.anchorOffset, 2,
+       "nsIHTMLEditor.setCaretAfterElement() should set anchor offset to the index of <b> element + 1 in the first paragraph");
+  } catch (e) {
+    ok(false, `nsIHTMLEditor.setCaretAfterElement() shouldn't throw exception when selecting an element in focused editor #1: ${e}`);
+  }
+
+  editor.focus();
+  try {
+    getHTMLEditor().setCaretAfterElement(editor.firstChild.firstChild.nextSibling.nextSibling);
+    ok(selection.isCollapsed,
+       "nsIHTMLEditor.setCaretAfterElement() should collapse Selection #2");
+    is(selection.anchorNode, editor.firstChild,
+       "nsIHTMLEditor.setCaretAfterElement() should set anchor node to parent of <i> element in the first paragraph");
+    is(selection.anchorOffset, 3,
+       "nsIHTMLEditor.setCaretAfterElement() should set anchor offset to the index of <i> element + 1 in the first paragraph");
+  } catch (e) {
+    ok(false, `nsIHTMLEditor.setCaretAfterElement() shouldn't throw exception when selecting an element in focused editor #2: ${e}`);
+  }
+
+  editor.focus();
+  try {
+    getHTMLEditor().setCaretAfterElement(editor.firstChild.nextSibling.firstChild);
+    ok(selection.isCollapsed,
+       "nsIHTMLEditor.setCaretAfterElement() should collapse Selection #3");
+    is(selection.anchorNode, editor.firstChild.nextSibling,
+       "nsIHTMLEditor.setCaretAfterElement() should set anchor node to parent of <b> element in the second paragraph");
+    is(selection.anchorOffset, 1,
+       "nsIHTMLEditor.setCaretAfterElement() should set anchor offset to the index of <b> element + 1 in the second paragraph");
+  } catch (e) {
+    ok(false, `nsIHTMLEditor.setCaretAfterElement() shouldn't throw exception when selecting an element in focused editor #3: ${e}`);
+  }
+
+  editor.focus();
+  try {
+    getHTMLEditor().setCaretAfterElement(editor);
+    ok(false, "nsIHTMLEditor.setCaretAfterElement() should throw an exception if given node is the editing host");
+  } catch (e) {
+    ok(true, `nsIHTMLEditor.setCaretAfterElement() should throw an exception if given node is the editing host: ${e}`);
+  }
+
+  editor.focus();
+  try {
+    getHTMLEditor().setCaretAfterElement(editor.parentElement);
+    ok(false, "nsIHTMLEditor.setCaretAfterElement() should throw an exception if given node is outside of the editing host");
+  } catch (e) {
+    ok(true, `nsIHTMLEditor.setCaretAfterElement() should throw an exception if given node is outside of the editing host: ${e}`);
+  }
+
+  selection.removeAllRanges();
+  editor.blur();
+  try {
+    getHTMLEditor().setCaretAfterElement(editor.firstChild.nextSibling.firstChild);
+    ok(false, "nsIHTMLEditor.setCaretAfterElement() should throw an exception if there is no active editing host");
+  } catch (e) {
+    ok(true, `nsIHTMLEditor.setCaretAfterElement() should throw an exception if there is no active editing host: ${e}`);
+  }
+
+  editor.focus();
+  editor.firstChild.firstChild.nextSibling.nextSibling.setAttribute("contenteditable", "false");
+  try {
+    getHTMLEditor().setCaretAfterElement(editor.firstChild.firstChild.nextSibling.nextSibling);
+    ok(selection.isCollapsed,
+       "nsIHTMLEditor.setCaretAfterElement() should collapse Selection #4");
+    is(selection.anchorNode, editor.firstChild,
+       "nsIHTMLEditor.setCaretAfterElement() should set anchor node to parent of <i contenteditable=\"false\"> element in the first paragraph");
+    is(selection.anchorOffset, 3,
+       "nsIHTMLEditor.setCaretAfterElement() should set anchor offset to the index of <i contenteditable=\"false\"> element + 1 in the first paragraph");
+  } catch (e) {
+    ok(false, `nsIHTMLEditor.setCaretAfterElement() shouldn't throw exception when selecting an element in focused editor #4: ${e}`);
+  }
+
+  editor.focus();
+  try {
+    getHTMLEditor().setCaretAfterElement(editor.firstChild.firstChild.nextSibling);
+    ok(selection.isCollapsed,
+       "nsIHTMLEditor.setCaretAfterElement() should collapse Selection #5");
+    is(selection.anchorNode, editor.firstChild,
+       "nsIHTMLEditor.setCaretAfterElement() should set anchor node to parent of <b> element in the first paragraph");
+    is(selection.anchorOffset, 2,
+       "nsIHTMLEditor.setCaretAfterElement() should set anchor offset to the index of <b> element in the first paragraph");
+  } catch (e) {
+    ok(false, `nsIHTMLEditor.setCaretAfterElement() shouldn't throw exception when selecting an element in focused editor #5: ${e}`);
+  }
+
+  editor.focus();
+  editor.firstChild.nextSibling.setAttribute("contenteditable", "false");
+  try {
+    getHTMLEditor().setCaretAfterElement(editor.firstChild.nextSibling.firstChild.nextSibling);
+    ok(selection.isCollapsed,
+       "nsIHTMLEditor.setCaretAfterElement() should collapse Selection #6");
+    is(selection.anchorNode, editor.firstChild.nextSibling,
+       "nsIHTMLEditor.setCaretAfterElement() should set anchor node to parent of <i> element in the second paragraph which is not editable");
+    is(selection.anchorOffset, 2,
+       "nsIHTMLEditor.setCaretAfterElement() should set anchor offset to the index of <i> element + 1 in the second paragraph which is not editable");
+  } catch (e) {
+    ok(false, `nsIHTMLEditor.selectElement() shouldn't throw exception when selecting an element in focused editor #6: ${e}`);
+  }
+
+  SimpleTest.finish();
+});
+
+function getHTMLEditor() {
+  var Ci = SpecialPowers.Ci;
+  var editingSession = SpecialPowers.wrap(window).docShell.editingSession;
+  return editingSession.getEditorForWindow(window).QueryInterface(Ci.nsIHTMLEditor);
+}
+
+</script>
+</body>
+
+</html>
--- a/intl/unicharutil/util/nsUnicharUtils.cpp
+++ b/intl/unicharutil/util/nsUnicharUtils.cpp
@@ -54,16 +54,43 @@ ToLowerCase(nsAString& aString)
 
 void
 ToLowerCaseASCII(nsAString& aString)
 {
   char16_t *buf = aString.BeginWriting();
   ToLowerCaseASCII(buf, buf, aString.Length());
 }
 
+char
+ToLowerCaseASCII(char aChar)
+{
+  if (aChar >= 'A' && aChar <= 'Z') {
+    return aChar + 0x20;
+  }
+  return aChar;
+}
+
+char16_t
+ToLowerCaseASCII(char16_t aChar)
+{
+  if (aChar >= 'A' && aChar <= 'Z') {
+    return aChar + 0x20;
+  }
+  return aChar;
+}
+
+char32_t
+ToLowerCaseASCII(char32_t aChar)
+{
+  if (aChar >= 'A' && aChar <= 'Z') {
+    return aChar + 0x20;
+  }
+  return aChar;
+}
+
 void
 ToLowerCase(const nsAString& aSource,
             nsAString& aDest)
 {
   const char16_t *in = aSource.BeginReading();
   uint32_t len = aSource.Length();
 
   aDest.SetLength(len);
--- a/intl/unicharutil/util/nsUnicharUtils.h
+++ b/intl/unicharutil/util/nsUnicharUtils.h
@@ -35,16 +35,20 @@ void ToUpperCase(const nsAString& aSourc
 uint32_t ToLowerCase(uint32_t aChar);
 uint32_t ToUpperCase(uint32_t aChar);
 uint32_t ToTitleCase(uint32_t aChar);
 
 void ToLowerCase(const char16_t *aIn, char16_t *aOut, uint32_t aLen);
 void ToLowerCaseASCII(const char16_t *aIn, char16_t *aOut, uint32_t aLen);
 void ToUpperCase(const char16_t *aIn, char16_t *aOut, uint32_t aLen);
 
+char ToLowerCaseASCII(const char aChar);
+char16_t ToLowerCaseASCII(const char16_t aChar);
+char32_t ToLowerCaseASCII(const char32_t aChar);
+
 inline bool IsUpperCase(uint32_t c) {
   return ToLowerCase(c) != c;
 }
 
 inline bool IsLowerCase(uint32_t c) {
   return ToUpperCase(c) != c;
 }
 
--- a/mfbt/TextUtils.h
+++ b/mfbt/TextUtils.h
@@ -43,16 +43,31 @@ constexpr bool
 IsAscii(Char aChar)
 {
   using UnsignedChar = typename detail::MakeUnsignedChar<Char>::Type;
   auto uc = static_cast<UnsignedChar>(aChar);
   return uc < 0x80;
 }
 
 /**
+ * Returns true iff |aChar| matches Ascii Whitespace.
+ *
+ * This function is intended to match the Infra standard
+ * (https://infra.spec.whatwg.org/#ascii-whitespace)
+ */
+template<typename Char>
+constexpr bool
+IsAsciiWhitespace(Char aChar)
+{
+  using UnsignedChar = typename detail::MakeUnsignedChar<Char>::Type;
+  auto uc = static_cast<UnsignedChar>(aChar);
+  return uc == 0x9 || uc == 0xA || uc == 0xC || uc == 0xD || uc == 0x20;
+}
+
+/**
  * Returns true iff |aChar| matches [a-z].
  *
  * This function is basically what you thought islower was, except its behavior
  * doesn't depend on the user's current locale.
  */
 template<typename Char>
 constexpr bool
 IsAsciiLowercaseAlpha(Char aChar)
--- a/parser/html/nsHtml5TreeOperation.cpp
+++ b/parser/html/nsHtml5TreeOperation.cpp
@@ -377,17 +377,17 @@ nsHtml5TreeOperation::CreateHTMLElement(
   dom::Element* newContent = nullptr;
   nsIDocument* document = nodeInfo->GetDocument();
   bool willExecuteScript = false;
   bool isCustomElement = false;
   RefPtr<nsAtom> isAtom;
   dom::CustomElementDefinition* definition = nullptr;
 
   // Avoid overhead by checking if custom elements pref is enabled or not.
-  if (nsContentUtils::IsCustomElementsEnabled()) {
+  if (dom::CustomElementRegistry::IsCustomElementEnabled(document)) {
     if (aAttributes) {
       nsHtml5String is = aAttributes->getValue(nsHtml5AttributeName::ATTR_IS);
       if (is) {
         nsAutoString isValue;
         is.ToString(isValue);
         isAtom = NS_Atomize(isValue);
       }
     }
--- a/python/mozboot/mozboot/base.py
+++ b/python/mozboot/mozboot/base.py
@@ -300,17 +300,17 @@ class BaseBootstrapper(object):
 
     def which(self, name):
         """Python implementation of which.
 
         It returns the path of an executable or None if it couldn't be found.
         """
         for path in os.environ['PATH'].split(os.pathsep):
             test = os.path.join(path, name)
-            if os.path.exists(test) and os.access(test, os.X_OK):
+            if os.path.isfile(test) and os.access(test, os.X_OK):
                 return test
 
         return None
 
     def run_as_root(self, command):
         if os.geteuid() != 0:
             if self.which('sudo'):
                 command.insert(0, 'sudo')
--- a/taskcluster/ci/config.yml
+++ b/taskcluster/ci/config.yml
@@ -1,14 +1,15 @@
 trust-domain: gecko
 project-repo-param-prefix: ''
 treeherder:
     group-names:
         'cram': 'Cram tests'
-        'js-bench': 'JavaScript shell benchmarks'
+        'js-bench-sm': 'JavaScript shell benchmarks with Spidermonkey'
+        'js-bench-v8': 'JavaScript shell benchmarks with Google V8'
         'mocha': 'Mocha unit tests'
         'py2': 'Python 2 unit tests'
         'py3': 'Python 3 unit tests'
         'A': 'Android Gradle tests'
         'Fetch-URL': 'Fetch and store content'
         'Fxfn-l': 'Firefox functional tests (local)'
         'Fxfn-l-e10s': 'Firefox functional tests (local) with e10s'
         'Fxfn-r': 'Firefox functional tests (remote)'
--- a/taskcluster/ci/fetch/benchmarks.yml
+++ b/taskcluster/ci/fetch/benchmarks.yml
@@ -1,26 +1,31 @@
+d8:
+  description: V8 debug shell
+  fetch:
+    type: static-url
+    url: https://github.com/mozilla/perf-automation/releases/download/d8-6.7.17/d8-6.7.17.zip
+    sha256: 0aa1c4e630de78373185fc1c0fa34bc87826f63fd4cbb664668891d6f6a6b24e
+    size: 20578358
+
 unity-webgl:
   description: unity-webgl benchmark
   fetch:
     type: static-url
-    artifact-name: unity-webgl.zip
     url: https://github.com/mozilla/perf-automation/releases/download/unity-webgl-v1/unity-webgl-6beb3d3e22ab.zip
     sha256: f71ee3a3f5b9513f041e1dd01c032d51f2071e1ad130e8ac2cf0c553c468b9ea
     size: 27062962
 
 assorted-dom:
   description: assorted-dom benchmark
   fetch:
     type: static-url
-    artifact-name: assorted-dom.zip
     url: https://github.com/mozilla/perf-automation/releases/download/assorted-dom-v1/assorted-dom-4befd28725c6.zip
     sha256: e4eafe4a8e70c7ae6d42d668d3b1640b9fd9b696c486ff35aab754c368f78c2c
     size: 402665
 
 web-tooling-benchmark:
   description: Web Tooling Benchmark
   fetch:
     type: static-url
-    artifact-name: web-tooling-benchmark.zip
     url: https://github.com/mozilla/perf-automation/releases/download/V1/web-tooling-benchmark-b2ac25c897c9.zip
     sha256: 93b0b51df0cec3ca9bfa0bdf81d782306dcf18532e39b3ff3180409125daaff1
     size: 5444135
--- a/taskcluster/ci/source-test/jsshell.yml
+++ b/taskcluster/ci/source-test/jsshell.yml
@@ -3,52 +3,62 @@ job-defaults:
     require-build: true
     worker-type:
         by-platform:
             linux64.*: releng-hardware/gecko-t-linux-talos
     worker:
         by-platform:
             linux64.*:
                 env:
-                    SHELL: /bin/bash
-                    JSSHELL: /home/cltbld/fetches/js
+                    by-shell:
+                        sm:
+                            SHELL: /bin/bash
+                            JSSHELL: /home/cltbld/fetches/js
+                        v8:
+                            SHELL: /bin/bash
+                            JSSHELL: /home/cltbld/fetches/d8/d8
                 max-run-time: 1800
     treeherder:
         kind: test
         tier: 2
     run:
-        using: mach
+        using: run-task
         workdir: /home/cltbld
+        command: >
+            cd $GECKO_PATH &&
+            ./mach jsshell-bench --perfherder={shell} --binary=$JSSHELL {test}
     run-on-projects: ['mozilla-central', 'try']
     fetches:
         build:
             - target.jsshell.zip
+        fetch:
+            - d8
 
 bench-ares6:
     description: Ares6 JavaScript shell benchmark suite
+    shell: ['sm', 'v8']
+    test: ares6
     treeherder:
-        symbol: js-bench(ares6)
-    run:
-        mach: jsshell-bench --binary $JSSHELL --perfherder ares6
+        symbol: ares6
 
 bench-sixspeed:
     description: Six-Speed JavaScript shell benchmark suite
+    shell: ['sm', 'v8']
+    test: six-speed
     treeherder:
-        symbol: js-bench(6speed)
-    run:
-        mach: jsshell-bench --binary $JSSHELL --perfherder six-speed
+        symbol: 6speed
 
 bench-sunspider:
     description: SunSpider JavaScript shell benchmark suite
+    shell: ['sm']
+    test: sunspider
     treeherder:
-        symbol: js-bench(sunspider)
-    run:
-        mach: jsshell-bench --binary $JSSHELL --perfherder sunspider
+        symbol: sunspider
 
 bench-web-tooling:
     description: Web Tooling shell benchmark suite
+    shell: ['sm', 'v8']
+    test: web-tooling-benchmark
     treeherder:
-        symbol: js-bench(webtool)
-    run:
-        mach: jsshell-bench --binary $JSSHELL --perfherder web-tooling-benchmark
+        symbol: webtool
     fetches:
         fetch:
             - web-tooling-benchmark
--- a/taskcluster/taskgraph/transforms/source_test.py
+++ b/taskcluster/taskgraph/transforms/source_test.py
@@ -123,16 +123,46 @@ def split_python(config, jobs):
             else:
                 pyjob['label'] += '-{0}'.format(group)
             symbol = split_symbol(pyjob['treeherder']['symbol'])[1]
             pyjob['treeherder']['symbol'] = join_symbol(group, symbol)
             pyjob['run'][key] = version
             yield pyjob
 
 
+@transforms.add
+def split_jsshell(config, jobs):
+    all_shells = {
+        'sm': "Spidermonkey",
+        'v8': "Google V8"
+    }
+
+    for job in jobs:
+        if not job['name'].startswith('jsshell'):
+            yield job
+            continue
+
+        test = job.pop('test')
+        for shell in job.get('shell', all_shells.keys()):
+            assert shell in all_shells
+
+            new_job = copy.deepcopy(job)
+            new_job['name'] = '{}-{}'.format(new_job['name'], shell)
+            new_job['description'] = '{} on {}'.format(new_job['description'], all_shells[shell])
+            new_job['shell'] = shell
+
+            group = 'js-bench-{}'.format(shell)
+            symbol = split_symbol(new_job['treeherder']['symbol'])[1]
+            new_job['treeherder']['symbol'] = join_symbol(group, symbol)
+
+            run = new_job['run']
+            run['command'] = run['command'].format(shell=shell, SHELL=shell.upper(), test=test)
+            yield new_job
+
+
 def add_build_dependency(config, job):
     """
     Add build dependency to the job and installer_url to env.
     """
     key = job['platform']
     build_labels = config.config.get('dependent-build-platforms', {})
     matches = keymatch(build_labels, key)
     if not matches:
@@ -167,8 +197,30 @@ def handle_platform(config, jobs):
         if 'treeherder' in job:
             job['treeherder']['platform'] = platform
 
         if job.pop('require-build'):
             add_build_dependency(config, job)
 
         del job['platform']
         yield job
+
+
+@transforms.add
+def handle_shell(config, jobs):
+    """
+    Handle the 'shell' property.
+    """
+    fields = [
+        'run-on-projects',
+        'worker.env',
+    ]
+
+    for job in jobs:
+        if not job.get('shell'):
+            yield job
+            continue
+
+        for field in fields:
+            resolve_keyed_by(job, field, item_name=job['name'])
+
+        del job['shell']
+        yield job
--- a/testing/jsshell/benchmark.py
+++ b/testing/jsshell/benchmark.py
@@ -26,19 +26,20 @@ with `ac_add_options --enable-js-shell` 
 
 
 class Benchmark(object):
     __metaclass__ = ABCMeta
     lower_is_better = True
     should_alert = False
     units = 'score'
 
-    def __init__(self, shell, args=None):
+    def __init__(self, shell, args=None, shell_name=None):
         self.shell = shell
         self.args = args
+        self.shell_name = shell_name
 
     @abstractproperty
     def name(self):
         """Returns the string name of the benchmark."""
 
     @abstractproperty
     def path(self):
         """Return the path to the benchmark relative to topsrcdir."""
@@ -65,24 +66,28 @@ class Benchmark(object):
             return self._version
 
         with open(os.path.join(self.path, 'VERSION'), 'r') as fh:
             self._version = fh.read().strip("\r\n\r\n \t")
         return self._version
 
     def reset(self):
         """Resets state between runs."""
+        name = self.name
+        if self.shell_name:
+            name = '{}-{}'.format(name, self.shell_name)
+
         self.perfherder_data = {
             'framework': {
                 'name': 'js-bench',
             },
             'suites': [
                 {
                     'lowerIsBetter': self.lower_is_better,
-                    'name': self.name,
+                    'name': name,
                     'shouldAlert': self.should_alert,
                     'subtests': [],
                     'units': self.units,
                     'value': None
                 },
             ],
         }
         self.suite = self.perfherder_data['suites'][0]
@@ -261,68 +266,71 @@ class WebToolingBenchmark(Benchmark):
         for bench, scores in self.scores.items():
             for score_name, values in scores.items():
                 test_name = "{}-{}".format(self.name, score_name)
                 mean = sum(values) / len(values)
                 self.suite['subtests'].append({'name': test_name, 'value': mean})
                 if score_name == 'mean':
                     bench_mean = mean
         self.suite['value'] = bench_mean
-    
+
     def _provision_benchmark_script(self):
+        if os.path.isdir(self.path):
+            return
+
         # Some benchmarks may have been downloaded from a fetch task, make
         # sure they get copied over.
         fetches_dir = os.environ.get('MOZ_FETCHES_DIR')
         if fetches_dir and os.path.isdir(fetches_dir):
             webtool_fetchdir = os.path.join(fetches_dir, 'web-tooling-benchmark')
             if os.path.isdir(webtool_fetchdir):
                 shutil.copytree(webtool_fetchdir, self.path)
-    
+
     def run(self):
         self._provision_benchmark_script()
         return super(WebToolingBenchmark, self).run()
 
 
 all_benchmarks = {
     'ares6': Ares6,
     'six-speed': SixSpeed,
     'sunspider': SunSpider,
     'web-tooling-benchmark': WebToolingBenchmark
 }
 
 
-def run(benchmark, binary=None, extra_args=None, perfherder=False):
+def run(benchmark, binary=None, extra_args=None, perfherder=None):
     if not binary:
         try:
             binary = os.path.join(build.bindir, 'js' + build.substs['BIN_SUFFIX'])
         except BuildEnvironmentNotFoundException:
             binary = None
 
         if not binary or not os.path.isfile(binary):
             print(JSSHELL_NOT_FOUND)
             return 1
 
-    bench = all_benchmarks.get(benchmark)(binary, args=extra_args)
+    bench = all_benchmarks.get(benchmark)(binary, args=extra_args, shell_name=perfherder)
     res = bench.run()
 
     if perfherder:
         print("PERFHERDER_DATA: {}".format(json.dumps(bench.perfherder_data)))
     return res
 
 
 def get_parser():
     parser = ArgumentParser()
     parser.add_argument('benchmark', choices=all_benchmarks.keys(),
                         help="The name of the benchmark to run.")
     parser.add_argument('-b', '--binary', default=None,
                         help="Path to the JS shell binary to use.")
     parser.add_argument('--arg', dest='extra_args', action='append', default=None,
                         help="Extra arguments to pass to the JS shell.")
-    parser.add_argument('--perfherder', action='store_true', default=False,
-                        help="Log PERFHERDER_DATA to stdout.")
+    parser.add_argument('--perfherder', default=None,
+                        help="Log PERFHERDER_DATA to stdout using the given suite name.")
     return parser
 
 
 def cli(args=sys.argv[1:]):
     parser = get_parser()
     args = parser.parser_args(args)
     return run(**vars(args))
 
--- a/testing/raptor/raptor/raptor.py
+++ b/testing/raptor/raptor/raptor.py
@@ -120,16 +120,25 @@ class Raptor(object):
                         test['name'],
                         self.control_server.port,
                         benchmark_port)
 
         # must intall raptor addon each time because we dynamically update some content
         raptor_webext = os.path.join(webext_dir, 'raptor')
         self.log.info("installing webext %s" % raptor_webext)
         self.profile.addons.install(raptor_webext)
+
+        # add test specific preferences
+        if test.get("preferences", None) is not None:
+            if self.config['app'] == "firefox":
+                self.profile.set_preferences(json.loads(test['preferences']))
+            else:
+                self.log.info("preferences were configured for the test, \
+                              but we do not install them on non Firefox browsers.")
+
         # on firefox we can get an addon id; chrome addon actually is just cmd line arg
         if self.config['app'] == "firefox":
             webext_id = self.profile.addons.addon_details(raptor_webext)['id']
 
         # some tests require tools to playback the test pages
         if test.get('playback', None) is not None:
             self.get_playback_config(test)
             # startup the playback tool
deleted file mode 100644
--- a/testing/web-platform/meta/xhr/overridemimetype-blob.html.ini
+++ /dev/null
@@ -1,286 +0,0 @@
-[overridemimetype-blob.html]
-  [Bogus MIME type should end up as application/octet-stream, 2]
-    expected: FAIL
-
-  [Bogus MIME type should end up as application/octet-stream, 3]
-    expected: FAIL
-
-  [Bogus MIME type should end up as application/octet-stream, 4]
-    expected: FAIL
-
-  [Bogus MIME type should end up as application/octet-stream, 5]
-    expected: FAIL
-
-  [Bogus MIME type should end up as application/octet-stream, 6]
-    expected: FAIL
-
-  [Bogus MIME type should end up as application/octet-stream, 7]
-    expected: FAIL
-
-  [Bogus MIME type should end up as application/octet-stream, 9]
-    expected: FAIL
-
-  [Valid MIME types need to be parsed and serialized 1]
-    expected: FAIL
-
-  [Valid MIME types need to be parsed and serialized 4]
-    expected: FAIL
-
-  [Valid MIME types need to be parsed and serialized 5]
-    expected: FAIL
-
-  [1) MIME types need to be parsed and serialized: text/html;charset=gbk]
-    expected: FAIL
-
-  [2) MIME types need to be parsed and serialized: TEXT/HTML;CHARSET=GBK]
-    expected: FAIL
-
-  [3) MIME types need to be parsed and serialized: text/html;charset=gbk(]
-    expected: FAIL
-
-  [4) MIME types need to be parsed and serialized: text/html;x=(;charset=gbk]
-    expected: FAIL
-
-  [5) MIME types need to be parsed and serialized: text/html;charset=gbk;charset=windows-1255]
-    expected: FAIL
-
-  [7) MIME types need to be parsed and serialized: text/html ;charset=gbk]
-    expected: FAIL
-
-  [8) MIME types need to be parsed and serialized: text/html; charset=gbk]
-    expected: FAIL
-
-  [9) MIME types need to be parsed and serialized: text/html;charset= gbk]
-    expected: FAIL
-
-  [10) MIME types need to be parsed and serialized: text/html;charset='gbk']
-    expected: FAIL
-
-  [11) MIME types need to be parsed and serialized: text/html;charset='gbk]
-    expected: FAIL
-
-  [12) MIME types need to be parsed and serialized: text/html;charset=gbk']
-    expected: FAIL
-
-  [13) MIME types need to be parsed and serialized: text/html;test;charset=gbk]
-    expected: FAIL
-
-  [14) MIME types need to be parsed and serialized: text/html;test=;charset=gbk]
-    expected: FAIL
-
-  [15) MIME types need to be parsed and serialized: text/html;';charset=gbk]
-    expected: FAIL
-
-  [16) MIME types need to be parsed and serialized: text/html;";charset=gbk]
-    expected: FAIL
-
-  [17) MIME types need to be parsed and serialized: text/html ; ; charset=gbk]
-    expected: FAIL
-
-  [18) MIME types need to be parsed and serialized: text/html;;;;charset=gbk]
-    expected: FAIL
-
-  [19) MIME types need to be parsed and serialized: text/html;charset="gbk"]
-    expected: FAIL
-
-  [20) MIME types need to be parsed and serialized: text/html;charset="gbk]
-    expected: FAIL
-
-  [21) MIME types need to be parsed and serialized: text/html;charset=gbk"]
-    expected: FAIL
-
-  [22) MIME types need to be parsed and serialized: text/html;charset=" gbk"]
-    expected: FAIL
-
-  [23) MIME types need to be parsed and serialized: text/html;charset="\\ gbk"]
-    expected: FAIL
-
-  [24) MIME types need to be parsed and serialized: text/html;charset="\\g\\b\\k"]
-    expected: FAIL
-
-  [25) MIME types need to be parsed and serialized: text/html;charset="gbk"x]
-    expected: FAIL
-
-  [26) MIME types need to be parsed and serialized: text/html;charset={gbk}]
-    expected: FAIL
-
-  [27) MIME types need to be parsed and serialized: text/html;0123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789=x;charset=gbk]
-    expected: FAIL
-
-  [29) MIME types need to be parsed and serialized: !#$%&'*+-.^_`|~0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz/!#$%&'*+-.^_`|~0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz;!#$%&'*+-.^_`|~0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz=!#$%&'*+-.^_`|~0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz]
-    expected: FAIL
-
-  [30) MIME types need to be parsed and serialized: x/x;x="\t !\\"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\\\\]^_`abcdefghijklmnopqrstuvwxyz{|}~€‚ƒ„…†‡ˆ‰Š‹ŒŽ‘’“”•–—˜™š›œžŸ ¡¢£¤¥¦§¨©ª«¬­®¯°±²³´µ¶·¸¹º»¼½¾¿ÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖ×ØÙÚÛÜÝÞßàáâãäåæçèéêëìíîïðñòóôõö÷øùúûüýþÿ"]
-    expected: FAIL
-
-  [32) MIME types need to be parsed and serialized: x/x;test="\\]
-    expected: FAIL
-
-  [35) MIME types need to be parsed and serialized: text/html;test=ÿ;charset=gbk]
-    expected: FAIL
-
-  [36) MIME types need to be parsed and serialized: x/x;test=�;x=x]
-    expected: FAIL
-
-  [37) MIME types need to be parsed and serialized: ]
-    expected: FAIL
-
-  [39) MIME types need to be parsed and serialized: /]
-    expected: FAIL
-
-  [41) MIME types need to be parsed and serialized: bogus/]
-    expected: FAIL
-
-  [42) MIME types need to be parsed and serialized: bogus/ ]
-    expected: FAIL
-
-  [43) MIME types need to be parsed and serialized: bogus/bogus/;]
-    expected: FAIL
-
-  [44) MIME types need to be parsed and serialized: </>]
-    expected: FAIL
-
-  [46) MIME types need to be parsed and serialized: ÿ/ÿ]
-    expected: FAIL
-
-  [47) MIME types need to be parsed and serialized: text/html(;doesnot=matter]
-    expected: FAIL
-
-  [48) MIME types need to be parsed and serialized: {/}]
-    expected: FAIL
-
-  [49) MIME types need to be parsed and serialized: Ā/Ā]
-    expected: FAIL
-
-  [6) MIME types need to be parsed and serialized: text/html;charset=();charset=GBK]
-    expected: FAIL
-
-  [8) MIME types need to be parsed and serialized: text/html ;charset=gbk]
-    expected: FAIL
-
-  [9) MIME types need to be parsed and serialized: text/html; charset=gbk]
-    expected: FAIL
-
-  [10) MIME types need to be parsed and serialized: text/html;charset= gbk]
-    expected: FAIL
-
-  [11) MIME types need to be parsed and serialized: text/html;charset= "gbk"]
-    expected: FAIL
-
-  [12) MIME types need to be parsed and serialized: text/html;charset='gbk']
-    expected: FAIL
-
-  [13) MIME types need to be parsed and serialized: text/html;charset='gbk]
-    expected: FAIL
-
-  [14) MIME types need to be parsed and serialized: text/html;charset=gbk']
-    expected: FAIL
-
-  [15) MIME types need to be parsed and serialized: text/html;charset=';charset=GBK]
-    expected: FAIL
-
-  [16) MIME types need to be parsed and serialized: text/html;test;charset=gbk]
-    expected: FAIL
-
-  [17) MIME types need to be parsed and serialized: text/html;test=;charset=gbk]
-    expected: FAIL
-
-  [18) MIME types need to be parsed and serialized: text/html;';charset=gbk]
-    expected: FAIL
-
-  [19) MIME types need to be parsed and serialized: text/html;";charset=gbk]
-    expected: FAIL
-
-  [20) MIME types need to be parsed and serialized: text/html ; ; charset=gbk]
-    expected: FAIL
-
-  [21) MIME types need to be parsed and serialized: text/html;;;;charset=gbk]
-    expected: FAIL
-
-  [22) MIME types need to be parsed and serialized: text/html;charset= ";charset=GBK]
-    expected: FAIL
-
-  [23) MIME types need to be parsed and serialized: text/html;charset=";charset=foo";charset=GBK]
-    expected: FAIL
-
-  [24) MIME types need to be parsed and serialized: text/html;charset="gbk"]
-    expected: FAIL
-
-  [25) MIME types need to be parsed and serialized: text/html;charset="gbk]
-    expected: FAIL
-
-  [26) MIME types need to be parsed and serialized: text/html;charset=gbk"]
-    expected: FAIL
-
-  [27) MIME types need to be parsed and serialized: text/html;charset=" gbk"]
-    expected: FAIL
-
-  [28) MIME types need to be parsed and serialized: text/html;charset="gbk "]
-    expected: FAIL
-
-  [29) MIME types need to be parsed and serialized: text/html;charset="\\ gbk"]
-    expected: FAIL
-
-  [30) MIME types need to be parsed and serialized: text/html;charset="\\g\\b\\k"]
-    expected: FAIL
-
-  [31) MIME types need to be parsed and serialized: text/html;charset="gbk"x]
-    expected: FAIL
-
-  [32) MIME types need to be parsed and serialized: text/html;charset="";charset=GBK]
-    expected: FAIL
-
-  [33) MIME types need to be parsed and serialized: text/html;charset=";charset=GBK]
-    expected: FAIL
-
-  [34) MIME types need to be parsed and serialized: text/html;charset={gbk}]
-    expected: FAIL
-
-  [35) MIME types need to be parsed and serialized: text/html;0123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789=x;charset=gbk]
-    expected: FAIL
-
-  [37) MIME types need to be parsed and serialized: !#$%&'*+-.^_`|~0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz/!#$%&'*+-.^_`|~0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz;!#$%&'*+-.^_`|~0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz=!#$%&'*+-.^_`|~0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz]
-    expected: FAIL
-
-  [38) MIME types need to be parsed and serialized: x/x;x="\t !\\"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\\\\]^_`abcdefghijklmnopqrstuvwxyz{|}~€‚ƒ„…†‡ˆ‰Š‹ŒŽ‘’“”•–—˜™š›œžŸ ¡¢£¤¥¦§¨©ª«¬­®¯°±²³´µ¶·¸¹º»¼½¾¿ÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖ×ØÙÚÛÜÝÞßàáâãäåæçèéêëìíîïðñòóôõö÷øùúûüýþÿ"]
-    expected: FAIL
-
-  [40) MIME types need to be parsed and serialized: x/x;test="\\]
-    expected: FAIL
-
-  [43) MIME types need to be parsed and serialized: text/html;test=ÿ;charset=gbk]
-    expected: FAIL
-
-  [44) MIME types need to be parsed and serialized: x/x;test=�;x=x]
-    expected: FAIL
-
-  [45) MIME types need to be parsed and serialized: ]
-    expected: FAIL
-
-  [47) MIME types need to be parsed and serialized: /]
-    expected: FAIL
-
-  [49) MIME types need to be parsed and serialized: bogus/]
-    expected: FAIL
-
-  [50) MIME types need to be parsed and serialized: bogus/ ]
-    expected: FAIL
-
-  [51) MIME types need to be parsed and serialized: bogus/bogus/;]
-    expected: FAIL
-
-  [52) MIME types need to be parsed and serialized: </>]
-    expected: FAIL
-
-  [54) MIME types need to be parsed and serialized: ÿ/ÿ]
-    expected: FAIL
-
-  [55) MIME types need to be parsed and serialized: text/html(;doesnot=matter]
-    expected: FAIL
-
-  [56) MIME types need to be parsed and serialized: {/}]
-    expected: FAIL
-
-  [57) MIME types need to be parsed and serialized: Ā/Ā]
-    expected: FAIL
-
--- a/testing/web-platform/moz.build
+++ b/testing/web-platform/moz.build
@@ -569,17 +569,17 @@ with Files("tests/webrtc/**"):
 
 with Files("tests/web-share/**"):
     BUG_COMPONENT = ("Core", "DOM")
 
 with Files("tests/websockets/**"):
     BUG_COMPONENT = ("Core", "Networking: WebSockets")
 
 with Files("tests/webstorage/**"):
-    BUG_COMPONENT = ("Core", "DOM")
+    BUG_COMPONENT = ("Core", "DOM: Web Storage")
 
 with Files("tests/webusb/**"):
     BUG_COMPONENT = ("Core", "DOM: Device Interfaces")
 
 with Files("tests/webvr/**"):
     BUG_COMPONENT = ("Core", "DOM")
 
 with Files("tests/webvtt/**"):
--- a/toolkit/components/telemetry/Events.yaml
+++ b/toolkit/components/telemetry/Events.yaml
@@ -544,16 +544,27 @@ devtools.main:
     bug_numbers: [1463171]
     notification_emails: ["dev-developer-tools@lists.mozilla.org", "hkirschner@mozilla.com"]
     record_in_processes: ["main"]
     description: User has executed edit / resend in the netmonitor.
     release_channel_collection: opt-out
     expiry_version: never
     extra_keys:
       session_id: The toolbox session start time e.g. 13963.
+  throttle_changed:
+    objects: ["netmonitor"]
+    bug_numbers: [1463147]
+    notification_emails: ["dev-developer-tools@lists.mozilla.org", "hkirschner@mozilla.com"]
+    record_in_processes: ["main"]
+    description: User has changed the throttle setting in the netmonitor.
+    release_channel_collection: opt-out
+    expiry_version: never
+    extra_keys:
+      mode: No throttling, GPRS, Regular 2G, Good 2G, Regular 3G, Good 3G, Regular 4G / LTE, DSL or WI-FI.
+      session_id: The toolbox session start time e.g. 13963.
   execute_js:
     objects: ["webconsole"]
     bug_numbers: [1463083]
     notification_emails: ["dev-developer-tools@lists.mozilla.org", "hkirschner@mozilla.com"]
     record_in_processes: ["main"]
     description: User has executed some JS in the Web Console.
     release_channel_collection: opt-out
     expiry_version: never