Merge autoland to central, a=merge
authorWes Kocher <wkocher@mozilla.com>
Wed, 18 Jan 2017 13:56:39 -0800
changeset 374917 ef4c8016909ecef30588b8b30c1fa2d113f3c27f
parent 374882 a5253dce8b67c2248d7b92ef664a6dd6664e7609 (current diff)
parent 374916 232825e23296e9dad742ab85de15910d9ab2d4fe (diff)
child 374966 96cb95af530477edb66ae48d98c18533476e57bb
push id6996
push userjlorenzo@mozilla.com
push dateMon, 06 Mar 2017 20:48:21 +0000
treeherdermozilla-beta@d89512dab048 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmerge
milestone53.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Merge autoland to central, a=merge MozReview-Commit-ID: 9c5kMZqzvuh
devtools/client/netmonitor/test/browser_net_statistics-03.js
devtools/client/shared/components/test/mochitest/test_reps_array.html
devtools/client/shared/components/test/mochitest/test_reps_attribute.html
devtools/client/shared/components/test/mochitest/test_reps_comment-node.html
devtools/client/shared/components/test/mochitest/test_reps_date-time.html
devtools/client/shared/components/test/mochitest/test_reps_document.html
devtools/client/shared/components/test/mochitest/test_reps_element-node.html
devtools/client/shared/components/test/mochitest/test_reps_error.html
devtools/client/shared/components/test/mochitest/test_reps_event.html
devtools/client/shared/components/test/mochitest/test_reps_failure.html
devtools/client/shared/components/test/mochitest/test_reps_function.html
devtools/client/shared/components/test/mochitest/test_reps_grip-array.html
devtools/client/shared/components/test/mochitest/test_reps_grip-map.html
devtools/client/shared/components/test/mochitest/test_reps_grip.html
devtools/client/shared/components/test/mochitest/test_reps_infinity.html
devtools/client/shared/components/test/mochitest/test_reps_long-string.html
devtools/client/shared/components/test/mochitest/test_reps_nan.html
devtools/client/shared/components/test/mochitest/test_reps_null.html
devtools/client/shared/components/test/mochitest/test_reps_number.html
devtools/client/shared/components/test/mochitest/test_reps_object-with-text.html
devtools/client/shared/components/test/mochitest/test_reps_object-with-url.html
devtools/client/shared/components/test/mochitest/test_reps_object.html
devtools/client/shared/components/test/mochitest/test_reps_promise.html
devtools/client/shared/components/test/mochitest/test_reps_regexp.html
devtools/client/shared/components/test/mochitest/test_reps_string.html
devtools/client/shared/components/test/mochitest/test_reps_stylesheet.html
devtools/client/shared/components/test/mochitest/test_reps_symbol.html
devtools/client/shared/components/test/mochitest/test_reps_text-node.html
devtools/client/shared/components/test/mochitest/test_reps_undefined.html
devtools/client/shared/components/test/mochitest/test_reps_window.html
dom/media/MediaContentType.cpp
dom/media/MediaContentType.h
--- a/.eslintignore
+++ b/.eslintignore
@@ -85,16 +85,17 @@ devtools/client/framework/**
 !devtools/client/framework/toolbox.js
 devtools/client/netmonitor/test/**
 devtools/client/netmonitor/har/test/**
 devtools/client/projecteditor/**
 devtools/client/responsivedesign/**
 devtools/client/scratchpad/**
 devtools/client/shadereditor/**
 devtools/client/shared/*.jsm
+devtools/client/shared/components/reps/reps.js
 devtools/client/shared/webgl-utils.js
 devtools/client/shared/widgets/*.jsm
 devtools/client/webaudioeditor/**
 devtools/client/webconsole/net/**
 devtools/client/webconsole/test/**
 devtools/client/webconsole/console-output.js
 devtools/client/webconsole/hudservice.js
 devtools/client/webconsole/utils.js
--- a/browser/base/content/test/urlbar/browser_dragdropURL.js
+++ b/browser/base/content/test/urlbar/browser_dragdropURL.js
@@ -1,15 +1,38 @@
 "use strict";
 
 const TEST_URL = "data:text/html,a test page";
 const DRAG_URL = "http://www.example.com/";
+const DRAG_FORBIDDEN_URL = "chrome://browser/content/aboutDialog.xul";
+const DRAG_TEXT = "Firefox is awesome";
+const DRAG_WORD = "Firefox";
 
-add_task(function* checkURLBarUpdateForDrag() {
+add_task(function* checkDragURL() {
   yield BrowserTestUtils.withNewTab(TEST_URL, function* (browser) {
     // Have to use something other than the URL bar as a source, so picking the
     // downloads button somewhat arbitrarily:
     EventUtils.synthesizeDrop(document.getElementById("downloads-button"), gURLBar,
                               [[{type: "text/plain", data: DRAG_URL}]], "copy", window);
     is(gURLBar.value, TEST_URL, "URL bar value should not have changed");
     is(gBrowser.selectedBrowser.userTypedValue, null, "Stored URL bar value should not have changed");
   });
 });
+
+add_task(function* checkDragForbiddenURL() {
+  yield BrowserTestUtils.withNewTab(TEST_URL, function* (browser) {
+    EventUtils.synthesizeDrop(document.getElementById("downloads-button"), gURLBar,
+                              [[{type: "text/plain", data: DRAG_FORBIDDEN_URL}]], "copy", window);
+    isnot(gURLBar.value, DRAG_FORBIDDEN_URL, "Shouldn't be allowed to drop forbidden URL on URL bar");
+  });
+});
+
+add_task(function* checkDragText() {
+  yield BrowserTestUtils.withNewTab(TEST_URL, function* (browser) {
+    EventUtils.synthesizeDrop(document.getElementById("downloads-button"), gURLBar,
+                              [[{type: "text/plain", data: DRAG_TEXT}]], "copy", window);
+    is(gURLBar.value, DRAG_TEXT, "Dragging normal text should replace the URL bar value");
+
+    EventUtils.synthesizeDrop(document.getElementById("downloads-button"), gURLBar,
+                              [[{type: "text/plain", data: DRAG_WORD}]], "copy", window);
+    is(gURLBar.value, DRAG_WORD, "Dragging a single word should replace the URL bar value");
+  });
+});
--- a/browser/base/content/urlbarBindings.xml
+++ b/browser/base/content/urlbarBindings.xml
@@ -696,65 +696,91 @@ file, You can obtain one at http://mozil
       </method>
 
       <method name="_hideURLTooltip">
         <body><![CDATA[
           this.inputField.removeAttribute("tooltiptext");
         ]]></body>
       </method>
 
-      <method name="_getDroppableLink">
+      <!-- Returns:
+           null if there's a security issue and we should do nothing.
+           a URL object if there is one that we're OK with loading,
+           a text value otherwise.
+           -->
+      <method name="_getDroppableItem">
         <parameter name="aEvent"/>
         <body><![CDATA[
-          let links = browserDragAndDrop.dropLinks(aEvent);
+          let links;
+          try {
+            links = browserDragAndDrop.dropLinks(aEvent);
+          } catch (ex) {
+            // this is possibly a security exception, in which case we should return
+            // null. Always return null because we can't *know* what exception is
+            // being returned.
+            return null;
+          }
           // The URL bar automatically handles inputs with newline characters,
           // so we can get away with treating text/x-moz-url flavours as text/plain.
           if (links.length > 0 && links[0].url) {
             aEvent.preventDefault();
             let url = links[0].url;
             let strippedURL = stripUnsafeProtocolOnPaste(url);
             if (strippedURL != url) {
               aEvent.stopImmediatePropagation();
               return null;
             }
+            let urlObj;
             try {
-              urlSecurityCheck(url,
-                               gBrowser.contentPrincipal,
-                               Ci.nsIScriptSecurityManager.DISALLOW_INHERIT_PRINCIPAL);
+              // If this throws, urlSecurityCheck would also throw, as that's what it
+              // does with things that don't pass the IO service's newURI constructor
+              // without fixup. It's conceivable we may want to relax this check in
+              // the future (so e.g. www.foo.com gets fixed up), but not right now.
+              urlObj = new URL(url);
+              // If we succeed, try to pass security checks. If this works, return the
+              // URL object. If the *security checks* fail, return null.
+              try {
+                urlSecurityCheck(url,
+                                 gBrowser.contentPrincipal,
+                                 Ci.nsIScriptSecurityManager.DISALLOW_INHERIT_PRINCIPAL);
+                return urlObj;
+              } catch (ex) {
+                return null;
+              }
             } catch (ex) {
-              return null;
+              // We couldn't make a URL out of this. Continue on, and return text below.
             }
-            return url;
           }
-          return null;
+          return aEvent.dataTransfer.getData("text/unicode");
         ]]></body>
       </method>
 
       <method name="onDragOver">
         <parameter name="aEvent"/>
         <body><![CDATA[
-          // We don't need the link here, so we ignore the return value.
-          if (!this._getDroppableLink(aEvent)) {
+          if (!this._getDroppableItem(aEvent)) {
             aEvent.dataTransfer.dropEffect = "none";
           }
         ]]></body>
       </method>
 
       <method name="onDrop">
         <parameter name="aEvent"/>
         <body><![CDATA[
-          let url = this._getDroppableLink(aEvent);
-          if (url) {
-            this.value = url;
+          let droppedItem = this._getDroppableItem(aEvent);
+          if (droppedItem) {
+            this.value = droppedItem instanceof URL ? droppedItem.href : droppedItem;
             SetPageProxyState("invalid");
             this.focus();
-            this.handleCommand();
-            // Force not showing the dropped URI immediately.
-            gBrowser.userTypedValue = null;
-            URLBarSetURI();
+            if (droppedItem instanceof URL) {
+              this.handleCommand();
+              // Force not showing the dropped URI immediately.
+              gBrowser.userTypedValue = null;
+              URLBarSetURI();
+            }
           }
         ]]></body>
       </method>
 
       <method name="_getSelectedValueForClipboard">
         <body><![CDATA[
           // Grab the actual input field's value, not our value, which could include moz-action:
           var inputVal = this.inputField.value;
--- a/browser/components/migration/AutoMigrate.jsm
+++ b/browser/components/migration/AutoMigrate.jsm
@@ -171,20 +171,24 @@ const AutoMigrate = {
       return suggestedProfile;
     }
     if (profiles && profiles.length > 1) {
       throw new Error("Don't know how to pick a profile when more than 1 profile is present.");
     }
     return profiles ? profiles[0] : null;
   },
 
+  _pendingUndoTasks: false,
   canUndo: Task.async(function* () {
     if (this._savingPromise) {
       yield this._savingPromise;
     }
+    if (this._pendingUndoTasks) {
+      return false;
+    }
     let fileExists = false;
     try {
       fileExists = yield OS.File.exists(kUndoStateFullPath);
     } catch (ex) {
       Cu.reportError(ex);
     }
     return fileExists;
   }),
@@ -192,16 +196,18 @@ const AutoMigrate = {
   undo: Task.async(function* () {
     let histogram = Services.telemetry.getHistogramById("FX_STARTUP_MIGRATION_AUTOMATED_IMPORT_UNDO");
     histogram.add(0);
     if (!(yield this.canUndo())) {
       histogram.add(5);
       throw new Error("Can't undo!");
     }
 
+    this._pendingUndoTasks = true;
+    this._removeNotificationBars();
     histogram.add(10);
 
     let readPromise = OS.File.read(kUndoStateFullPath, {
       encoding: "utf-8",
       compression: "lz4",
     });
     let stateData = this._dejsonifyUndoState(yield readPromise);
     yield this._removeUnchangedBookmarks(stateData.get("bookmarks"));
@@ -213,41 +219,46 @@ const AutoMigrate = {
     yield this._removeUnchangedLogins(stateData.get("logins"));
     histogram.add(25);
 
     // This is async, but no need to wait for it.
     NewTabUtils.links.populateCache(() => {
       NewTabUtils.allPages.update();
     }, true);
 
-    this.removeUndoOption(this.UNDO_REMOVED_REASON_UNDO_USED);
+    this._purgeUndoState(this.UNDO_REMOVED_REASON_UNDO_USED);
     histogram.add(30);
   }),
 
-  removeUndoOption(reason) {
-    // We don't wait for the off-main-thread removal to complete. OS.File will
-    // ensure it happens before shutdown.
-    OS.File.remove(kUndoStateFullPath, {ignoreAbsent: true});
-
-    let migrationBrowser = Preferences.get(kAutoMigrateBrowserPref, "unknown");
-    Services.prefs.clearUserPref(kAutoMigrateBrowserPref);
-
+  _removeNotificationBars() {
     let browserWindows = Services.wm.getEnumerator("navigator:browser");
     while (browserWindows.hasMoreElements()) {
       let win = browserWindows.getNext();
       if (!win.closed) {
         for (let browser of win.gBrowser.browsers) {
           let nb = win.gBrowser.getNotificationBox(browser);
           let notification = nb.getNotificationWithValue(kNotificationId);
           if (notification) {
             nb.removeNotification(notification);
           }
         }
       }
     }
+  },
+
+  _purgeUndoState(reason) {
+    // We don't wait for the off-main-thread removal to complete. OS.File will
+    // ensure it happens before shutdown.
+    OS.File.remove(kUndoStateFullPath, {ignoreAbsent: true}).then(() => {
+      this._pendingUndoTasks = false;
+    });
+
+    let migrationBrowser = Preferences.get(kAutoMigrateBrowserPref, "unknown");
+    Services.prefs.clearUserPref(kAutoMigrateBrowserPref);
+
     let histogram =
       Services.telemetry.getKeyedHistogramById("FX_STARTUP_MIGRATION_UNDO_REASON");
     histogram.add(migrationBrowser, reason);
   },
 
   getBrowserUsedForMigration() {
     let browserId = Services.prefs.getCharPref(kAutoMigrateBrowserPref);
     if (browserId) {
@@ -273,17 +284,18 @@ const AutoMigrate = {
     if (!notificationBox || notificationBox.getNotificationWithValue(kNotificationId)) {
       return;
     }
 
     // At this stage we're committed to show the prompt - unless we shouldn't,
     // in which case we remove the undo prefs (which will cause canUndo() to
     // return false from now on.):
     if (!this.shouldStillShowUndoPrompt()) {
-      this.removeUndoOption(this.UNDO_REMOVED_REASON_OFFER_EXPIRED);
+      this._purgeUndoState(this.UNDO_REMOVED_REASON_OFFER_EXPIRED);
+      this._removeNotificationBars();
       return;
     }
 
     let browserName = this.getBrowserUsedForMigration();
     let message;
     if (browserName) {
       message = MigrationUtils.getLocalizedString("automigration.undo.message",
                                                   [browserName]);
@@ -291,17 +303,18 @@ const AutoMigrate = {
       message = MigrationUtils.getLocalizedString("automigration.undo.unknownBrowserMessage");
     }
 
     let buttons = [
       {
         label: MigrationUtils.getLocalizedString("automigration.undo.keep.label"),
         accessKey: MigrationUtils.getLocalizedString("automigration.undo.keep.accesskey"),
         callback: () => {
-          this.removeUndoOption(this.UNDO_REMOVED_REASON_OFFER_REJECTED);
+          this._purgeUndoState(this.UNDO_REMOVED_REASON_OFFER_REJECTED);
+          this._removeNotificationBars();
         },
       },
       {
         label: MigrationUtils.getLocalizedString("automigration.undo.dontkeep.label"),
         accessKey: MigrationUtils.getLocalizedString("automigration.undo.dontkeep.accesskey"),
         callback: () => {
           this.undo();
         },
--- a/browser/components/migration/tests/browser/browser.ini
+++ b/browser/components/migration/tests/browser/browser.ini
@@ -1,1 +1,2 @@
 [browser_undo_notification.js]
+[browser_undo_notification_multiple_dismissal.js]
--- a/browser/components/migration/tests/browser/browser_undo_notification.js
+++ b/browser/components/migration/tests/browser/browser_undo_notification.js
@@ -1,19 +1,17 @@
 "use strict";
 
 let scope = {};
 Cu.import("resource:///modules/AutoMigrate.jsm", scope);
 let oldCanUndo = scope.AutoMigrate.canUndo;
 let oldUndo = scope.AutoMigrate.undo;
 registerCleanupFunction(function() {
-  Cu.reportError("Cleaning up");
   scope.AutoMigrate.canUndo = oldCanUndo;
   scope.AutoMigrate.undo = oldUndo;
-  Cu.reportError("Cleaned up");
 });
 
 const kExpectedNotificationId = "automigration-undo";
 
 add_task(function* autoMigrationUndoNotificationShows() {
   let getNotification = browser =>
     gBrowser.getNotificationBox(browser).getNotificationWithValue(kExpectedNotificationId);
 
new file mode 100644
--- /dev/null
+++ b/browser/components/migration/tests/browser/browser_undo_notification_multiple_dismissal.js
@@ -0,0 +1,122 @@
+"use strict";
+
+
+const kExpectedNotificationId = "automigration-undo";
+
+/**
+ * Pretend we can undo something, trigger a notification, pick the undo option,
+ * and verify that the notifications are all dismissed immediately.
+ */
+add_task(function* checkNotificationsDismissed() {
+  yield SpecialPowers.pushPrefEnv({set: [
+    ["browser.migrate.automigrate.enabled", true],
+    ["browser.migrate.automigrate.ui.enabled", true],
+  ]});
+  let getNotification = browser =>
+    gBrowser.getNotificationBox(browser).getNotificationWithValue(kExpectedNotificationId);
+
+  Services.prefs.setCharPref("browser.migrate.automigrate.browser", "someunknownbrowser");
+
+  let {guid, lastModified} = yield PlacesUtils.bookmarks.insert(
+    {title: "Some imported bookmark", parentGuid: PlacesUtils.bookmarks.toolbarGuid, url: "http://www.example.com"}
+  );
+
+  let testUndoData = {
+    visits: [],
+    bookmarks: [{guid, lastModified: lastModified.getTime()}],
+    logins: [],
+  };
+  let path = OS.Path.join(OS.Constants.Path.profileDir, "initialMigrationMetadata.jsonlz4");
+  registerCleanupFunction(() => {
+    return OS.File.remove(path, {ignoreAbsent: true});
+  });
+  yield OS.File.writeAtomic(path, JSON.stringify(testUndoData), {
+    encoding: "utf-8",
+    compression: "lz4",
+    tmpPath: path + ".tmp",
+  });
+
+  let firstTab = yield BrowserTestUtils.openNewForegroundTab(gBrowser, "about:home", false);
+  if (!getNotification(firstTab.linkedBrowser)) {
+    info(`Notification not immediately present on first tab, waiting for it.`);
+    yield BrowserTestUtils.waitForNotificationBar(gBrowser, firstTab.linkedBrowser, kExpectedNotificationId);
+  }
+  let secondTab = yield BrowserTestUtils.openNewForegroundTab(gBrowser, "about:home", false);
+  if (!getNotification(secondTab.linkedBrowser)) {
+    info(`Notification not immediately present on second tab, waiting for it.`);
+    yield BrowserTestUtils.waitForNotificationBar(gBrowser, secondTab.linkedBrowser, kExpectedNotificationId);
+  }
+
+  // Create a listener for the removal in the first tab, and a listener for bookmarks removal,
+  // then click 'Don't keep' in the second tab, and verify that the notification is removed
+  // before we start removing bookmarks.
+  let haveRemovedBookmark = false;
+  let bmObserver;
+  let bookmarkRemovedPromise = new Promise(resolve => {
+    bmObserver = {
+      onItemRemoved(itemId, parentId, index, itemType, uri, removedGuid) {
+        if (guid == removedGuid) {
+          haveRemovedBookmark = true;
+          resolve();
+        }
+      },
+    };
+    PlacesUtils.bookmarks.addObserver(bmObserver, false);
+    registerCleanupFunction(() => PlacesUtils.bookmarks.removeObserver(bmObserver));
+  });
+
+  let firstTabNotificationRemovedPromise = new Promise(resolve => {
+    let notification = getNotification(firstTab.linkedBrowser);
+    // Save this reference because notification.parentNode will be null once it's removed.
+    let notificationBox = notification.parentNode;
+    let mut = new MutationObserver(mutations => {
+      // Yucky, but we have to detect either the removal via animation (with marginTop)
+      // or when the element is removed. We can't just detect the element being removed
+      // because this happens asynchronously (after the animation) and so it'd race
+      // with the rest of the undo happening.
+      for (let mutation of mutations) {
+        if (mutation.target == notification && mutation.attributeName == "style" &&
+            parseInt(notification.style.marginTop, 10) < 0) {
+          ok(!haveRemovedBookmark, "Should not have removed bookmark yet");
+          mut.disconnect();
+          resolve();
+          return;
+        }
+        if (mutation.target == notificationBox && mutation.removedNodes.length &&
+            mutation.removedNodes[0] == notification) {
+          ok(!haveRemovedBookmark, "Should not have removed bookmark yet");
+          mut.disconnect();
+          resolve();
+          return;
+        }
+      }
+    });
+    mut.observe(notification.parentNode, {childList: true});
+    mut.observe(notification, {attributes: true});
+  });
+
+  let prefResetPromise = new Promise(resolve => {
+    const kObservedPref = "browser.migrate.automigrate.browser";
+    let obs = () => {
+      Services.prefs.removeObserver(kObservedPref, obs);
+      ok(!Services.prefs.prefHasUserValue(kObservedPref),
+         "Pref should have been reset");
+      resolve();
+    };
+    Services.prefs.addObserver(kObservedPref, obs, false);
+  });
+
+  // Click "Don't keep" button:
+  let notificationToActivate = getNotification(secondTab.linkedBrowser);
+  notificationToActivate.querySelector("button:not(.notification-button-default)").click();
+  info("Waiting for notification to be removed in first (background) tab");
+  yield firstTabNotificationRemovedPromise;
+  info("Waiting for bookmark to be removed");
+  yield bookmarkRemovedPromise;
+  info("Waiting for prefs to be reset");
+  yield prefResetPromise;
+
+  info("Removing spare tabs");
+  yield BrowserTestUtils.removeTab(firstTab);
+  yield BrowserTestUtils.removeTab(secondTab);
+});
--- a/browser/components/preferences/in-content/advanced.js
+++ b/browser/components/preferences/in-content/advanced.js
@@ -60,16 +60,19 @@ var gAdvancedPane = {
         Services.obs.removeObserver(this, "sitedatamanager:sites-updated");
       };
       window.addEventListener("unload", unload);
       SiteDataManager.updateSites();
       setEventListener("clearSiteDataButton", "command",
                        gAdvancedPane.clearSiteData);
       setEventListener("siteDataSettings", "command",
                        gAdvancedPane.showSiteDataSettings);
+
+      let url = Services.urlFormatter.formatURLPref("app.support.baseURL") + "storage-permissions";
+      document.getElementById("siteDataLearnMoreLink").setAttribute("href", url);
     }
 
     setEventListener("layers.acceleration.disabled", "change",
                      gAdvancedPane.updateHardwareAcceleration);
     setEventListener("advancedPrefs", "select",
                      gAdvancedPane.tabSelectionChanged);
     if (AppConstants.MOZ_TELEMETRY_REPORTING) {
       setEventListener("submitHealthReportBox", "command",
--- a/browser/components/preferences/in-content/advanced.xul
+++ b/browser/components/preferences/in-content/advanced.xul
@@ -326,17 +326,19 @@
         </hbox>
       </groupbox>
 
       <!-- Site Data -->
       <groupbox id="siteDataGroup" hidden="true">
         <caption><label>&siteData.label;</label></caption>
 
         <hbox align="center">
-          <label id="totalSiteDataSize" flex="1"></label>
+          <label id="totalSiteDataSize"></label>
+          <label id="siteDataLearnMoreLink" class="learnMore text-link" value="&siteDataLearnMoreLink.label;"></label>
+          <spacer flex="1" />
           <button id="clearSiteDataButton" icon="clear"
                   label="&clearSiteData.label;" accesskey="&clearSiteData.accesskey;"/>
         </hbox>
         <vbox align="end">
           <button id="siteDataSettings"
                   label="&siteDataSettings.label;"
                   accesskey="&siteDataSettings.accesskey;"/>
         </vbox>
--- a/browser/extensions/mortar/host/pdf/chrome/js/toolbar.js
+++ b/browser/extensions/mortar/host/pdf/chrome/js/toolbar.js
@@ -199,16 +199,20 @@ class Toolbar {
         this._viewport.save();
         break;
       case 'pageRotateCw':
         this._viewport.rotateClockwise();
         break;
       case 'pageRotateCcw':
         this._viewport.rotateCounterClockwise();
         break;
+      case 'presentationMode':
+      case 'secondaryPresentationMode':
+        this._viewport.fullscreen = true;
+        break;
       case 'secondaryToolbarToggle':
         this._secondaryToolbar.toggle();
         break;
     }
   }
 
   _pageNumberChanged() {
     let newPage = parseFloat(this._elements.pageNumber.value);
--- a/browser/extensions/mortar/host/pdf/chrome/js/viewport.js
+++ b/browser/extensions/mortar/host/pdf/chrome/js/viewport.js
@@ -1,25 +1,24 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 'use strict';
 
 class Viewport {
   constructor() {
+    this._viewerContainer = document.getElementById('viewerContainer');
+    this._fullscreenWrapper = document.getElementById('fullscreenWrapper');
     this._canvasContainer = document.getElementById('canvasContainer');
     this._viewportController = document.getElementById('viewportController');
     this._sizer = document.getElementById('sizer');
 
+    this._fullscreenStatus = 'none';
     this._scrollbarWidth = this._getScrollbarWidth();
-    this._hasVisibleScrollbars = {
-      horizontal: false,
-      vertical: false
-    };
 
     this._page = 0;
     this._zoom = 1;
     this._fitting = 'auto';
 
     // Caches the next position set during a series of actions and will be set
     // to scrollbar's position until calling "_refresh".
     this._nextPosition = null;
@@ -92,16 +91,42 @@ class Viewport {
       return;
     }
 
     newPage = Math.max(0, Math.min(pageCount - 1, newPage));
     this._setPage(newPage);
     this._refresh();
   }
 
+  get fullscreen() {
+    return this._fullscreenStatus != 'none';
+  }
+
+  set fullscreen(enable) {
+    if (this._fullscreenStatus == 'changing' ||
+        this._fullscreenStatus == (enable ? 'fullscreen' : 'none')) {
+      return;
+    }
+
+    // The next step after sending "setFullscreen" will happen in the function
+    // "_handleFullscreenChange" triggered by "fullscreenChange" event. The
+    // "_fullscreenStatus" will also be reset there. Note that the viewport
+    // stops refreshing while in the "changing" status to avoid flickers.
+    //
+    // XXX: Since we rely on "fullscreenChange" event to reset the status, the
+    //      viewport might freeze if, for some reason, the event isn't sent back
+    //      and we get stuck in the "changing" status. Not sure if it's the case
+    //      we need to worry about though.
+    this._fullscreenStatus = 'changing';
+    this._doAction({
+      type: 'setFullscreen',
+      fullscreen: enable
+    });
+  }
+
   _getScrollbarWidth() {
     var div = document.createElement('div');
     div.style.visibility = 'hidden';
     div.style.overflow = 'scroll';
     div.style.width = '50px';
     div.style.height = '50px';
     div.style.position = 'absolute';
     document.body.appendChild(div);
@@ -144,29 +169,35 @@ class Viewport {
     this._setZoom(this._computeFittingZoom());
     this._setPage(this._page);
     if (typeof this.onDimensionChanged === 'function') {
       this.onDimensionChanged();
     }
     this._refresh();
   }
 
-  _computeFittingZoom() {
+  _computeFittingZoom(pageIndex) {
     let newZoom = this._zoom;
     let fitting = this._fitting;
 
-    if (fitting == 'none') {
+    if (pageIndex === undefined) {
+      pageIndex = this._page;
+    }
+
+    if (fitting == 'none' || pageIndex < 0 || pageIndex >= this.pageCount) {
       return newZoom;
     }
 
     let FITTING_PADDING = 40;
     let MAX_AUTO_ZOOM = 1.25;
 
-    let page = this._pageDimensions[this._page];
-    let viewportRect = this.getBoundingClientRect();
+    let page = this._pageDimensions[pageIndex];
+    let viewportRect = this.fullscreen ?
+      this._viewerContainer.getBoundingClientRect() :
+      this.getBoundingClientRect();
 
     let pageWidthZoom = (viewportRect.width - FITTING_PADDING) / page.width;
     let pageHeightZoom = viewportRect.height / page.height;
 
     switch (fitting) {
       case 'auto':
         let isLandscape = (page.width > page.height);
         // For pages in landscape mode, fit the page height to the viewer
@@ -284,29 +315,40 @@ class Viewport {
       this.onZoomChanged(this._zoom);
     }
   }
 
   _setPage(newPage) {
     if (newPage < 0 || newPage >= this.pageCount) {
       return;
     }
+
+    if (this.fullscreen) {
+      let pageDimension = this._pageDimensions[newPage];
+      let newZoom = this._computeFittingZoom(newPage);
+
+      this._fullscreenWrapper.style.width =
+        (pageDimension.width * newZoom) + 'px';
+      this._fullscreenWrapper.style.height =
+        (pageDimension.height * newZoom) + 'px';
+
+      if (newZoom != this._zoom) {
+        this._setZoom(newZoom);
+      }
+      this._notifyRuntimeOfResized();
+    }
+
     this._setPosition(
       this._pageDimensions[newPage].x * this._zoom,
       this._pageDimensions[newPage].y * this._zoom
     );
   }
 
   _updateCanvasSize() {
     let hasScrollbars = this._documentHasVisibleScrollbars(this._zoom);
-    if (hasScrollbars.horizontal == this._hasVisibleScrollbars.horizontal &&
-        hasScrollbars.vertical == this._hasVisibleScrollbars.vertical) {
-      return;
-    }
-    this._hasVisibleScrollbars = hasScrollbars;
     this._canvasContainer.style.bottom =
       hasScrollbars.horizontal ? this._scrollbarWidth + 'px' : 0;
     this._canvasContainer.style.right =
       hasScrollbars.vertical ? this._scrollbarWidth + 'px' : 0;
     this._notifyRuntimeOfResized();
   }
 
   _contentSizeChanged() {
@@ -344,16 +386,49 @@ class Viewport {
       if (typeof listener === 'function') {
         listener(evt);
       } else if (typeof listener.handleEvent === 'function') {
         listener.handleEvent(evt);
       }
     });
   }
 
+  _handleFullscreenChange(fullscreen) {
+    // Set status to "changing" again in case it isn't triggered by setter.
+    this._fullscreenStatus = 'changing';
+    this._viewerContainer.classList.toggle('pdfPresentationMode', fullscreen);
+
+    // XXX: DOM elements' size changing hasn't taken place when fullscreenChange
+    //      event is triggered in our setup, so "setTimeout" is necessary to get
+    //      the exact size. The 100ms delay is set based on try-and-error. We
+    //      might need to find a proper way to know when exactly the resizing is
+    //      done.
+    setTimeout(() => {
+      let currentPage = this._page;
+
+      if (fullscreen) {
+        this._previousZoom = this._zoom;
+        this._previousFitting = this._fitting;
+        this._fitting = 'page-fit';
+        // No need to call "_setZoom" here because we will deal with zooming
+        // case in the "_setPage" below.
+      } else {
+        this._zoom = this._previousZoom;
+        this._fitting = this._previousFitting;
+        this._setZoom(this._computeFittingZoom());
+      }
+
+      this._fullscreenStatus = fullscreen ? 'fullscreen' : 'none';
+
+      // Reset position to the beginning of the current page.
+      this._setPage(currentPage);
+      this._refresh();
+    }, 100);
+  }
+
   _getEventTarget(type) {
     switch(type) {
       case 'keydown':
       case 'keyup':
       case 'keypress':
         return window;
     }
     return this._viewportController;
@@ -361,16 +436,20 @@ class Viewport {
 
   _doAction(message) {
     if (this._actionHandler) {
       this._actionHandler(message);
     }
   }
 
   _refresh() {
+    if (this._fullscreenStatus == 'changing') {
+      return;
+    }
+
     if (this._nextPosition) {
       this._viewportController.scrollTo(
         this._nextPosition.x, this._nextPosition.y);
       this._nextPosition = null;
     }
 
     this._runtimePosition = this.getScrollOffset();
     this._doAction({
@@ -387,31 +466,38 @@ class Viewport {
         this.onPageChanged(newPage);
       }
     }
   }
 
   handleEvent(evt) {
     switch(evt.type) {
       case 'resize':
-        this._resize();
-        this._notifyRuntimeOfResized();
-        this._refresh();
+        this.invokeResize();
         break;
       case 'scroll':
         this._nextPosition = null;
         let position = this.getScrollOffset();
         if (this._runtimePosition.x != position.x ||
             this._runtimePosition.y != position.y) {
           this._refresh();
         }
         break;
     }
   }
 
+  invokeResize() {
+    if (this._fullscreenStatus == 'changing') {
+      return;
+    }
+    this._resize();
+    this._notifyRuntimeOfResized();
+    this._refresh();
+  }
+
   rotateClockwise() {
     this._doAction({
       type: 'rotateClockwise'
     });
   }
 
   rotateCounterClockwise() {
     this._doAction({
@@ -497,11 +583,14 @@ class Viewport {
   notify(message) {
     switch (message.type) {
       case 'loadProgress':
         this._updateProgress(message.progress);
         break;
       case 'documentDimensions':
         this._setDocumentDimensions(message);
         break;
+      case 'fullscreenChange':
+        this._handleFullscreenChange(message.fullscreen);
+        break;
     }
   }
 }
--- a/browser/extensions/mortar/host/pdf/chrome/style/viewer.css
+++ b/browser/extensions/mortar/host/pdf/chrome/style/viewer.css
@@ -48,40 +48,16 @@ select {
 
 .hidden {
   display: none !important;
 }
 [hidden] {
   display: none !important;
 }
 
-#viewerContainer.pdfPresentationMode:fullscreen {
-  top: 0px;
-  border-top: 2px solid transparent;
-  background-color: #000;
-  width: 100%;
-  height: 100%;
-  overflow: hidden;
-  cursor: none;
-  -moz-user-select: none;
-}
-
-.pdfPresentationMode:fullscreen a:not(.internalLink) {
-  display: none;
-}
-
-.pdfPresentationMode:fullscreen .textLayer > div {
-  cursor: none;
-}
-
-.pdfPresentationMode.pdfPresentationModeControls > *,
-.pdfPresentationMode.pdfPresentationModeControls .textLayer > div {
-  cursor: default;
-}
-
 /* outer/inner center provides horizontal center */
 .outerCenter {
   pointer-events: none;
   position: relative;
 }
 html[dir='ltr'] .outerCenter {
   float: right;
   right: 50%;
@@ -173,33 +149,55 @@ html[dir='ltr'] #sidebarContent {
 html[dir='rtl'] #sidebarContent {
   right: 0;
   box-shadow: inset 1px 0 0 hsla(0,0%,0%,.25);
 }
 
 #viewerContainer {
   overflow: hidden;
   position: absolute;
-  top: var(--toolbar-height);
+  top: calc(var(--toolbar-height) + 2px);
   right: 0;
   bottom: 0;
   left: 0;
   outline: none;
 }
 html[dir='ltr'] #viewerContainer {
   box-shadow: inset 1px 0 0 hsla(0,0%,100%,.05);
 }
 html[dir='rtl'] #viewerContainer {
   box-shadow: inset -1px 0 0 hsla(0,0%,100%,.05);
 }
 
+#viewerContainer.pdfPresentationMode {
+  position: fixed;
+  top: 0;
+  left: 0;
+  background-color: #000;
+  width: 100%;
+  height: 100%;
+  overflow: hidden;
+  cursor: none;
+  -moz-user-select: none;
+  z-index: 99999;
+
+  display: flex;
+  align-items: center;
+  justify-content: center;
+}
+
+.pdfPresentationMode #fullscreenWrapper {
+  position: relative;
+  overflow: hidden;
+}
+
 #canvasContainer, #viewportController {
   position: absolute;
   left: 0;
-  top: 2px;
+  top: 0;
   right: 0;
   bottom: 0;
   outline: none;
   overflow: hidden;
 }
 
 #canvasContainer canvas {
   display: block;
@@ -207,16 +205,20 @@ html[dir='rtl'] #viewerContainer {
   left: 0;
   top: 0;
 }
 
 #viewportController {
   overflow: auto;
 }
 
+.pdfPresentationMode #viewportController {
+  overflow: hidden;
+}
+
 #sizer {
   margin: 0 auto;
 }
 
 .toolbar {
   position: relative;
   left: 0;
   right: 0;
--- a/browser/extensions/mortar/host/pdf/chrome/viewer.html
+++ b/browser/extensions/mortar/host/pdf/chrome/viewer.html
@@ -209,19 +209,21 @@
                 <div class="glimmer">
                 </div>
               </div>
             </div>
           </div>
         </div>
 
         <div id="viewerContainer">
-          <div id="canvasContainer"></div>
-          <div id="viewportController" tabindex="0">
-            <div id="sizer"></div>
+          <div id="fullscreenWrapper">
+            <div id="canvasContainer"></div>
+            <div id="viewportController" tabindex="0">
+              <div id="sizer"></div>
+            </div>
           </div>
         </div>
 
         <div id="errorWrapper" hidden='true'>
           <div id="errorMessageLeft">
             <span id="errorMessage"></span>
             <button id="errorShowMore" data-l10n-id="error_more_info">
               More Information
--- a/browser/extensions/pdfjs/test/browser_pdfjs_zoom.js
+++ b/browser/extensions/pdfjs/test/browser_pdfjs_zoom.js
@@ -93,17 +93,17 @@ add_task(function* test() {
         }
 
         // check that PDF is opened with internal viewer
         Assert.ok(content.document.querySelector("div#viewer"), "document content has viewer UI");
         Assert.ok("PDFJS" in content.wrappedJSObject, "window content has PDFJS object");
 
         let initialWidth, previousWidth;
         initialWidth = previousWidth =
-          parseInt(content.document.querySelector("div#pageContainer1").style.width);
+          parseInt(content.document.querySelector('div.page[data-page-number="1"]').style.width);
 
         for (let test of TESTS) {
           // We zoom using an UI element
           var ev;
           if (test.action.selector) {
             // Get the element and trigger the action for changing the zoom
             var el = document.querySelector(test.action.selector);
             Assert.ok(el, "Element '" + test.action.selector + "' has been found");
@@ -128,17 +128,17 @@ add_task(function* test() {
           el.dispatchEvent(ev);
           yield waitForRender();
 
           var pageZoomScale = content.document.querySelector('select#scaleSelect');
 
           // The zoom value displayed in the zoom select
           var zoomValue = pageZoomScale.options[pageZoomScale.selectedIndex].innerHTML;
 
-          let pageContainer = content.document.querySelector('div#pageContainer1');
+          let pageContainer = content.document.querySelector('div.page[data-page-number="1"]');
           let actualWidth = parseInt(pageContainer.style.width);
 
           // the actual zoom of the PDF document
           let computedZoomValue = parseInt(((actualWidth/initialWidth).toFixed(2))*100) + "%";
           Assert.equal(computedZoomValue, zoomValue, "Content has correct zoom");
 
           // Check that document zooms in the expected way (in/out)
           let zoom = (actualWidth - previousWidth) * test.expectedZoom;
--- a/browser/locales/en-US/chrome/browser/preferences/advanced.dtd
+++ b/browser/locales/en-US/chrome/browser/preferences/advanced.dtd
@@ -58,16 +58,17 @@
 <!ENTITY offlineStorage2.label           "Offline Web Content and User Data">
 
 <!--  Site Data section manages sites using Storage API and is under Network -->
 <!ENTITY siteData.label                  "Site Data">
 <!ENTITY clearSiteData.label             "Clear All Data">
 <!ENTITY clearSiteData.accesskey         "l">
 <!ENTITY siteDataSettings.label          "Settings…">
 <!ENTITY siteDataSettings.accesskey      "i">
+<!ENTITY siteDataLearnMoreLink.label     "Learn more">
 
 <!-- LOCALIZATION NOTE:
   The entities limitCacheSizeBefore.label and limitCacheSizeAfter.label appear on a single
   line in preferences as follows:
 
   &limitCacheSizeBefore.label [textbox for cache size in MB] &limitCacheSizeAfter.label;
 -->
 <!ENTITY limitCacheSizeBefore.label      "Limit cache to">
--- a/devtools/client/commandline/test/browser_cmd_addon.js
+++ b/devtools/client/commandline/test/browser_cmd_addon.js
@@ -28,17 +28,21 @@ function* spawnTest() {
       setup: "addon list extension",
       check: {
         input:  "addon list extension",
         hints:                      "",
         markup: "VVVVVVVVVVVVVVVVVVVV",
         status: "VALID"
       },
       exec: {
-        output: [/The following/, /Mochitest/, /Special Powers/]
+        output: [/The following/, /Mochitest/, /Special Powers/],
+        notinoutput: [
+          /Web Compat/, /FlyWeb/, /Pocket/, /Multi-process staged rollout/,
+          /Form Autofill/, /Application Update Service Helper/, /Presentation/,
+          /Shield Recipe Client/]
       }
     },
     {
       setup: "addon list locale",
       check: {
         input:  "addon list locale",
         hints:                   "",
         markup: "VVVVVVVVVVVVVVVVV",
--- a/devtools/client/commandline/test/helpers.js
+++ b/devtools/client/commandline/test/helpers.js
@@ -1025,40 +1025,63 @@ var { helpers, assert } = (function () {
         }
         else {
           convertPromise = output.convert("dom", context).then(function (node) {
             return (node == null) ? "" : node.textContent.trim();
           });
         }
 
         return convertPromise.then(function (textOutput) {
+          // Test that a regular expression has at least one match in a string.
           var doTest = function (match, against) {
           // Only log the real textContent if the test fails
             if (against.match(match) != null) {
-              assert.ok(true, "html output for '" + name + "' " +
-                            "should match /" + (match.source || match) + "/");
+              assert.ok(true,
+                `html output for '${name}' should match /${match.source || match}/`);
             } else {
-              assert.ok(false, "html output for '" + name + "' " +
-                             "should match /" + (match.source || match) + "/. " +
-                             'Actual textContent: "' + against + '"');
+              assert.ok(false,
+                `html output for '${name}' should match /${match.source || match}/. ` +
+                `Actual textContent: "${against}"`);
+            }
+          };
+
+          // Test that a regular expression has no matches in a string.
+          var doTestNot = function (match, against) {
+          // Only log the real textContent if the test fails
+            if (against.match(match) != null) {
+              assert.ok(false,
+                `html output for '${name}' should not match /` +
+                `${match.source || match}/. Actual textContent: "${against}"`);
+            } else {
+              assert.ok(true,
+              `html output for '${name}' should not match /${match.source || match}/`);
             }
           };
 
           if (typeof expected.output === "string") {
             assert.is(textOutput,
                     expected.output,
-                    "html output for " + name);
-          }
-          else if (Array.isArray(expected.output)) {
+                    `html output for '${name}'`);
+          } else if (Array.isArray(expected.output)) {
             expected.output.forEach(function (match) {
               doTest(match, textOutput);
             });
+          } else {
+            doTest(expected.output, textOutput);
           }
-        else {
-            doTest(expected.output, textOutput);
+
+          if (typeof expected.notinoutput === "string") {
+            assert.ok(textOutput.indexOf(expected.notinoutput) === -1,
+              `html output for "${name}" doesn't contain "${expected.notinoutput}"`);
+          } else if (Array.isArray(expected.notinoutput)) {
+            expected.notinoutput.forEach(function (match) {
+              doTestNot(match, textOutput);
+            });
+          } else if (typeof expected.notinoutput !== "undefined") {
+            doTestNot(expected.notinoutput, textOutput);
           }
 
           if (expected.error) {
             cli.logErrors = origLogErrors;
           }
           return { output: output, text: textOutput };
         });
       }.bind(this)).then(function (data) {
--- a/devtools/client/dom/content/components/dom-tree.js
+++ b/devtools/client/dom/content/components/dom-tree.js
@@ -4,22 +4,22 @@
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 "use strict";
 
 // React & Redux
 const React = require("devtools/client/shared/vendor/react");
 const { connect } = require("devtools/client/shared/vendor/react-redux");
 
+const TreeView = React.createFactory(require("devtools/client/shared/components/tree/tree-view"));
+
 // Reps
-const { createFactories } = require("devtools/client/shared/components/reps/rep-utils");
-const TreeView = React.createFactory(require("devtools/client/shared/components/tree/tree-view"));
-const { Rep } = createFactories(require("devtools/client/shared/components/reps/rep"));
-const { Grip } = require("devtools/client/shared/components/reps/grip");
-const { MODE } = require("devtools/client/shared/components/reps/constants");
+const { REPS, MODE } = require("devtools/client/shared/components/reps/load-reps");
+const Rep = React.createFactory(REPS.Rep);
+const Grip = REPS.Grip;
 
 // DOM Panel
 const { GripProvider } = require("../grip-provider");
 const { DomDecorator } = require("../dom-decorator");
 
 // Shortcuts
 const PropTypes = React.PropTypes;
 
@@ -84,9 +84,8 @@ const mapStateToProps = (state) => {
   return {
     grips: state.grips,
     filter: state.filter
   };
 };
 
 // Exports from this module
 module.exports = connect(mapStateToProps)(DomTree);
-
--- a/devtools/client/dom/content/components/main-toolbar.js
+++ b/devtools/client/dom/content/components/main-toolbar.js
@@ -5,17 +5,17 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 "use strict";
 
 // React
 const React = require("devtools/client/shared/vendor/react");
 const { l10n } = require("../utils");
 
 // Reps
-const { createFactories } = require("devtools/client/shared/components/reps/rep-utils");
+const { createFactories } = require("devtools/client/shared/components/reps/load-reps");
 const { Toolbar, ToolbarButton } = createFactories(require("devtools/client/jsonview/components/reps/toolbar"));
 
 // DOM Panel
 const SearchBox = React.createFactory(require("devtools/client/shared/components/search-box"));
 
 // Actions
 const { fetchProperties } = require("../actions/grips");
 const { setVisibilityFilter } = require("../actions/filter");
--- a/devtools/client/inspector/components/box-model.js
+++ b/devtools/client/inspector/components/box-model.js
@@ -7,16 +7,17 @@
 "use strict";
 
 const {Task} = require("devtools/shared/task");
 const {InplaceEditor, editableItem} =
       require("devtools/client/shared/inplace-editor");
 const {ReflowFront} = require("devtools/shared/fronts/reflow");
 const {LocalizationHelper} = require("devtools/shared/l10n");
 const {getCssProperties} = require("devtools/shared/fronts/css-properties");
+const {KeyCodes} = require("devtools/client/shared/keycodes");
 
 const STRINGS_URI = "devtools/client/locales/shared.properties";
 const STRINGS_INSPECTOR = "devtools/shared/locales/styleinspector.properties";
 const SHARED_L10N = new LocalizationHelper(STRINGS_URI);
 const INSPECTOR_L10N = new LocalizationHelper(STRINGS_INSPECTOR);
 const NUMERIC = /^-?[\d\.]+$/;
 const LONG_TEXT_ROTATE_LIMIT = 3;
 
@@ -224,16 +225,54 @@ BoxModelView.prototype = {
     this.onFilterComputedView = this.onFilterComputedView.bind(this);
     this.inspector.on("computed-view-filtered",
       this.onFilterComputedView);
 
     this.onPickerStarted = this.onPickerStarted.bind(this);
     this.onMarkupViewLeave = this.onMarkupViewLeave.bind(this);
     this.onMarkupViewNodeHover = this.onMarkupViewNodeHover.bind(this);
     this.onWillNavigate = this.onWillNavigate.bind(this);
+    this.onKeyDown = this.onKeyDown.bind(this);
+    this.onLevelClick = this.onLevelClick.bind(this);
+    this.setAriaActive = this.setAriaActive.bind(this);
+    this.getEditBoxes = this.getEditBoxes.bind(this);
+    this.makeFocusable = this.makeFocusable.bind(this);
+    this.makeUnfocasable = this.makeUnfocasable.bind(this);
+    this.moveFocus = this.moveFocus.bind(this);
+    this.onFocus = this.onFocus.bind(this);
+
+    this.borderLayout = this.doc.getElementById("boxmodel-borders");
+    this.boxModel = this.doc.getElementById("boxmodel-wrapper");
+    this.marginLayout = this.doc.getElementById("boxmodel-margins");
+    this.paddingLayout = this.doc.getElementById("boxmodel-padding");
+
+    this.layouts = {
+      "margin": new Map([
+        [KeyCodes.DOM_VK_ESCAPE, this.marginLayout],
+        [KeyCodes.DOM_VK_DOWN, this.borderLayout],
+        [KeyCodes.DOM_VK_UP, null],
+        ["click", this.marginLayout]
+      ]),
+      "border": new Map([
+        [KeyCodes.DOM_VK_ESCAPE, this.borderLayout],
+        [KeyCodes.DOM_VK_DOWN, this.paddingLayout],
+        [KeyCodes.DOM_VK_UP, this.marginLayout],
+        ["click", this.borderLayout]
+      ]),
+      "padding": new Map([
+        [KeyCodes.DOM_VK_ESCAPE, this.paddingLayout],
+        [KeyCodes.DOM_VK_DOWN, null],
+        [KeyCodes.DOM_VK_UP, this.borderLayout],
+        ["click", this.paddingLayout]
+      ])
+    };
+
+    this.boxModel.addEventListener("click", this.onLevelClick, true);
+    this.boxModel.addEventListener("focus", this.onFocus, true);
+    this.boxModel.addEventListener("keydown", this.onKeyDown, true);
 
     this.initBoxModelHighlighter();
 
     // Store for the different dimensions of the node.
     // 'selector' refers to the element that holds the value;
     // 'property' is what we are measuring;
     // 'value' is the computed dimension, computed in update().
     this.map = {
@@ -449,16 +488,20 @@ BoxModelView.prototype = {
 
     this.expander.removeEventListener("click", this.onToggleExpander);
     let header = this.doc.getElementById("boxmodel-header");
     header.removeEventListener("dblclick", this.onToggleExpander);
 
     let nodeGeometry = this.doc.getElementById("layout-geometry-editor");
     nodeGeometry.removeEventListener("click", this.onGeometryButtonClick);
 
+    this.boxModel.removeEventListener("click", this.onLevelClick, true);
+    this.boxModel.removeEventListener("focus", this.onFocus, true);
+    this.boxModel.removeEventListener("keydown", this.onKeyDown, true);
+
     this.inspector.off("picker-started", this.onPickerStarted);
 
     // Inspector Panel will destroy `markup` object on "will-navigate" event,
     // therefore we have to check if it's still available in case BoxModelView
     // is destroyed immediately after.
     if (this.inspector.markup) {
       this.inspector.markup.off("leave", this.onMarkupViewLeave);
       this.inspector.markup.off("node-hover", this.onMarkupViewNodeHover);
@@ -473,23 +516,204 @@ BoxModelView.prototype = {
     this.inspector = null;
     this.doc = null;
     this.wrapper = null;
     this.container = null;
     this.expander = null;
     this.sizeLabel = null;
     this.sizeHeadingLabel = null;
 
+    this.marginLayout = null;
+    this.borderLayout = null;
+    this.paddingLayout = null;
+    this.boxModel = null;
+    this.layouts = null;
+
     if (this.reflowFront) {
       this.untrackReflows();
       this.reflowFront.destroy();
       this.reflowFront = null;
     }
   },
 
+  /**
+   * Set initial box model focus to the margin layout.
+   */
+  onFocus: function () {
+    let activeDescendant = this.boxModel.getAttribute("aria-activedescendant");
+
+    if (!activeDescendant) {
+      let nextLayout = this.marginLayout;
+      this.setAriaActive(nextLayout);
+    }
+  },
+
+  /**
+   * Active aria-level set to current layout.
+   *
+   * @param {Element} nextLayout
+   *        Element of next layout that user has navigated to
+   * @param {Node} target
+   *        Node to be observed
+   */
+  setAriaActive: function (nextLayout, target) {
+    this.boxModel.setAttribute("aria-activedescendant", nextLayout.id);
+    if (target && target._editable) {
+      target.blur();
+    }
+
+    // Clear all
+    this.marginLayout.classList.remove("layout-active-elm");
+    this.borderLayout.classList.remove("layout-active-elm");
+    this.paddingLayout.classList.remove("layout-active-elm");
+
+    // Set the next level's border outline
+    nextLayout.classList.add("layout-active-elm");
+  },
+
+  /**
+   * Update aria-active on mouse click.
+   *
+   * @param {Event} event
+   *         The event triggered by a mouse click on the box model
+   */
+  onLevelClick: function (event) {
+    let {target} = event;
+    let nextLayout = this.layouts[target.getAttribute("data-box")].get("click");
+
+    this.setAriaActive(nextLayout, target);
+  },
+
+  /**
+   * Handle keyboard navigation and focus for box model layouts.
+   *
+   * Updates active layout on arrow key navigation
+   * Focuses next layout's editboxes on enter key
+   * Unfocuses current layout's editboxes when active layout changes
+   * Controls tabbing between editBoxes
+   *
+   * @param {Event} event
+   *         The event triggered by a keypress on the box model
+   */
+  onKeyDown: function (event) {
+    let {target, keyCode} = event;
+    // If focused on editable value or in editing mode
+    let isEditable = target._editable || target.editor;
+    let level = this.boxModel.getAttribute("aria-activedescendant");
+    let editingMode = target.tagName === "input";
+    let nextLayout;
+
+    switch (keyCode) {
+      case KeyCodes.DOM_VK_RETURN:
+        if (!isEditable) {
+          this.makeFocusable(level);
+        }
+        break;
+      case KeyCodes.DOM_VK_DOWN:
+      case KeyCodes.DOM_VK_UP:
+        if (!editingMode) {
+          event.preventDefault();
+          this.makeUnfocasable(level);
+          let datalevel = this.doc.getElementById(level).getAttribute("data-box");
+          nextLayout = this.layouts[datalevel].get(keyCode);
+          this.boxModel.focus();
+        }
+        break;
+      case KeyCodes.DOM_VK_TAB:
+        if (isEditable) {
+          event.preventDefault();
+          this.moveFocus(event, level);
+        }
+        break;
+      case KeyCodes.DOM_VK_ESCAPE:
+        if (isEditable && target._editable) {
+          event.preventDefault();
+          event.stopPropagation();
+          this.makeUnfocasable(level);
+          this.boxModel.focus();
+        }
+        break;
+      default:
+        break;
+    }
+
+    if (nextLayout) {
+      this.setAriaActive(nextLayout, target);
+    }
+  },
+
+  /**
+   * Make previous layout's elements unfocusable.
+   *
+   * @param {String} editLevel
+   *        The previous layout
+   */
+  makeUnfocasable: function (editLevel) {
+    let editBoxes = this.getEditBoxes(editLevel);
+    editBoxes.forEach(editBox => editBox.setAttribute("tabindex", "-1"));
+  },
+
+  /**
+   * Make current layout's elements focusable.
+   *
+   * @param {String} editLevel
+   *        The current layout
+   */
+  makeFocusable: function (editLevel) {
+    let editBoxes = this.getEditBoxes(editLevel);
+    editBoxes.forEach(editBox => editBox.setAttribute("tabindex", "0"));
+    editBoxes[0].focus();
+  },
+
+  /**
+   * Keyboard navigation of edit boxes wraps around on edge
+   * elements ([layout]-top, [layout]-left).
+   *
+   * @param {Node} target
+   *        Node to be observed
+   * @param {Boolean} shiftKey
+   *        Determines if shiftKey was pressed
+   * @param {String} level
+   *        Current active layout
+   */
+  moveFocus: function ({target, shiftKey}, level) {
+    let editBoxes = this.getEditBoxes(level);
+    let editingMode = target.tagName === "input";
+    // target.nextSibling is input field
+    let position = editingMode ? editBoxes.indexOf(target.nextSibling)
+                               : editBoxes.indexOf(target);
+
+    if (position === editBoxes.length - 1 && !shiftKey) {
+      position = 0;
+    } else if (position === 0 && shiftKey) {
+      position = editBoxes.length - 1;
+    } else {
+      shiftKey ? position-- : position++;
+    }
+
+    let editBox = editBoxes[position];
+    editBox.focus();
+
+    if (editingMode) {
+      editBox.click();
+    }
+  },
+
+  /**
+   * Retrieve edit boxes for current layout.
+   *
+   * @param {String} editLevel
+   *        Current active layout
+   * @return Layout's edit boxes
+   */
+  getEditBoxes: function (editLevel) {
+    let dataLevel = this.doc.getElementById(editLevel).getAttribute("data-box");
+    return [...this.doc.querySelectorAll(`[data-box="${dataLevel}"].boxmodel-editable`)];
+  },
+
   onSidebarSelect: function (e, sidebar) {
     this.setActive(sidebar === "computedview");
   },
 
   /**
    * Selection 'new-node-front' event handler.
    */
   onNewSelection: function () {
--- a/devtools/client/inspector/components/test/browser.ini
+++ b/devtools/client/inspector/components/test/browser.ini
@@ -15,15 +15,16 @@ support-files =
 [browser_boxmodel.js]
 [browser_boxmodel_editablemodel.js]
 # [browser_boxmodel_editablemodel_allproperties.js]
 # Disabled for too many intermittent failures (bug 1009322)
 [browser_boxmodel_editablemodel_bluronclick.js]
 [browser_boxmodel_editablemodel_border.js]
 [browser_boxmodel_editablemodel_stylerules.js]
 [browser_boxmodel_guides.js]
+[browser_boxmodel_navigation.js]
 [browser_boxmodel_rotate-labels-on-sides.js]
 [browser_boxmodel_sync.js]
 [browser_boxmodel_tooltips.js]
 [browser_boxmodel_update-after-navigation.js]
 [browser_boxmodel_update-after-reload.js]
 # [browser_boxmodel_update-in-iframes.js]
 # Bug 1020038 boxmodel-view updates for iframe elements changes
new file mode 100644
--- /dev/null
+++ b/devtools/client/inspector/components/test/browser_boxmodel_navigation.js
@@ -0,0 +1,103 @@
+/* vim: set ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// Tests that keyboard and mouse navigation updates aria-active and focus
+// of elements.
+
+const TEST_URI = `
+  <style>
+  div { position: absolute; top: 42px; left: 42px;
+  height: 100.111px; width: 100px; border: 10px solid black;
+  padding: 20px; margin: 30px auto;}
+  </style><div></div>
+`;
+
+add_task(function* () {
+  yield addTab("data:text/html," + encodeURIComponent(TEST_URI));
+  let {inspector, view} = yield openBoxModelView();
+  yield selectNode("div", inspector);
+
+  yield testInitialFocus(inspector, view);
+  yield testChangingLevels(inspector, view);
+  yield testTabbingWrapAround(inspector, view);
+  yield testChangingLevelsByClicking(inspector, view);
+});
+
+function* testInitialFocus(inspector, view) {
+  info("Test that the focus is on margin layout.");
+  let viewdoc = view.doc;
+  let boxmodel = viewdoc.getElementById("boxmodel-wrapper");
+  boxmodel.focus();
+  EventUtils.synthesizeKey("VK_RETURN", {});
+
+  is(boxmodel.getAttribute("aria-activedescendant"), "boxmodel-margins",
+    "Should be set to the margin layout.");
+}
+
+function* testChangingLevels(inspector, view) {
+  info("Test that using arrow keys updates level.");
+  let viewdoc = view.doc;
+  let boxmodel = viewdoc.getElementById("boxmodel-wrapper");
+  boxmodel.focus();
+  EventUtils.synthesizeKey("VK_RETURN", {});
+  EventUtils.synthesizeKey("VK_ESCAPE", {});
+
+  EventUtils.synthesizeKey("VK_DOWN", {});
+  is(boxmodel.getAttribute("aria-activedescendant"), "boxmodel-borders",
+    "Should be set to the border layout.");
+
+  EventUtils.synthesizeKey("VK_DOWN", {});
+  is(boxmodel.getAttribute("aria-activedescendant"), "boxmodel-padding",
+    "Should be set to the padding layout.");
+
+  EventUtils.synthesizeKey("VK_UP", {});
+  is(boxmodel.getAttribute("aria-activedescendant"), "boxmodel-borders",
+    "Should be set to the border layout.");
+
+  EventUtils.synthesizeKey("VK_UP", {});
+  is(boxmodel.getAttribute("aria-activedescendant"), "boxmodel-margins",
+    "Should be set to the margin layout.");
+}
+
+function* testTabbingWrapAround(inspector, view) {
+  info("Test that using arrow keys updates level.");
+  let viewdoc = view.doc;
+  let boxmodel = viewdoc.getElementById("boxmodel-wrapper");
+  boxmodel.focus();
+  EventUtils.synthesizeKey("VK_RETURN", {});
+
+  let editLevel = boxmodel.getAttribute("aria-activedescendant");
+  let dataLevel = viewdoc.getElementById(editLevel).getAttribute("data-box");
+  let editBoxes = [...viewdoc.querySelectorAll(
+    `[data-box="${dataLevel}"].boxmodel-editable`)];
+
+  EventUtils.synthesizeKey("VK_ESCAPE", {});
+  editBoxes[3].focus();
+  EventUtils.synthesizeKey("VK_TAB", {});
+  is(editBoxes[0], viewdoc.activeElement, "Top edit box should have focus.");
+
+  editBoxes[0].focus();
+  EventUtils.synthesizeKey("VK_TAB", { shiftKey: true });
+  is(editBoxes[3], viewdoc.activeElement, "Left edit box should have focus.");
+}
+
+function* testChangingLevelsByClicking(inspector, view) {
+  info("Test that clicking on levels updates level.");
+  let viewdoc = view.doc;
+  let boxmodel = viewdoc.getElementById("boxmodel-wrapper");
+  boxmodel.focus();
+
+  let marginLayout = viewdoc.getElementById("boxmodel-margins");
+  let borderLayout = viewdoc.getElementById("boxmodel-borders");
+  let paddingLayout = viewdoc.getElementById("boxmodel-padding");
+  let layouts = [paddingLayout, borderLayout, marginLayout];
+
+  layouts.forEach(layout => {
+    layout.click();
+    is(boxmodel.getAttribute("aria-activedescendant"), layout.id,
+      "Should be set to" + layout.getAttribute("data-box") + "layout.");
+  });
+}
--- a/devtools/client/inspector/inspector.js
+++ b/devtools/client/inspector/inspector.js
@@ -164,16 +164,20 @@ Inspector.prototype = {
   get hasUrlToImageDataResolver() {
     return this._target.client.traits.urlToImageDataResolver;
   },
 
   get canGetUniqueSelector() {
     return this._target.client.traits.getUniqueSelector;
   },
 
+  get canGetCssPath() {
+    return this._target.client.traits.getCssPath;
+  },
+
   get canGetUsedFontFaces() {
     return this._target.client.traits.getUsedFontFaces;
   },
 
   get canPasteInnerOrAdjacentHTML() {
     return this._target.client.traits.pasteHTML;
   },
 
@@ -1108,16 +1112,25 @@ Inspector.prototype = {
       label: INSPECTOR_L10N.getStr("inspectorCopyCSSSelector.label"),
       accesskey:
         INSPECTOR_L10N.getStr("inspectorCopyCSSSelector.accesskey"),
       disabled: !isSelectionElement,
       hidden: !this.canGetUniqueSelector,
       click: () => this.copyUniqueSelector(),
     }));
     copySubmenu.append(new MenuItem({
+      id: "node-menu-copycsspath",
+      label: INSPECTOR_L10N.getStr("inspectorCopyCSSPath.label"),
+      accesskey:
+        INSPECTOR_L10N.getStr("inspectorCopyCSSPath.accesskey"),
+      disabled: !isSelectionElement,
+      hidden: !this.canGetCssPath,
+      click: () => this.copyCssPath(),
+    }));
+    copySubmenu.append(new MenuItem({
       id: "node-menu-copyimagedatauri",
       label: INSPECTOR_L10N.getStr("inspectorImageDataUri.label"),
       disabled: !isSelectionElement || !markupContainer ||
                 !markupContainer.isPreviewable(),
       click: () => this.copyImageDataUri(),
     }));
 
     menu.append(new MenuItem({
@@ -1710,19 +1723,34 @@ Inspector.prototype = {
   /**
    * Copy a unique selector of the selected Node to the clipboard.
    */
   copyUniqueSelector: function () {
     if (!this.selection.isNode()) {
       return;
     }
 
-    this.selection.nodeFront.getUniqueSelector().then((selector) => {
+    this.telemetry.toolOpened("copyuniquecssselector");
+    this.selection.nodeFront.getUniqueSelector().then(selector => {
       clipboardHelper.copyString(selector);
-    }).then(null, console.error);
+    }).catch(e => console.error);
+  },
+
+  /**
+   * Copy the full CSS Path of the selected Node to the clipboard.
+   */
+  copyCssPath: function () {
+    if (!this.selection.isNode()) {
+      return;
+    }
+
+    this.telemetry.toolOpened("copyfullcssselector");
+    this.selection.nodeFront.getCssPath().then(path => {
+      clipboardHelper.copyString(path);
+    }).catch(e => console.error);
   },
 
   /**
    * Initiate gcli screenshot command on selected node.
    */
   screenshotNode: function () {
     const command = Services.prefs.getBoolPref("devtools.screenshot.clipboard.enabled") ?
       "screenshot --file --clipboard --selector" :
--- a/devtools/client/inspector/package.json
+++ b/devtools/client/inspector/package.json
@@ -2,16 +2,13 @@
   "name": "inspector.html",
   "version": "0.0.1",
   "description": "The Firefox Inspector",
   "scripts": {
     "start": "node bin/dev-server"
   },
   "author": "",
   "dependencies": {
-    "devtools-local-toolbox": "0.0.10",
-    "devtools-modules": "0.0.9",
-    "devtools-sham-modules": "0.0.9",
-    "devtools-config": "0.0.9",
+    "devtools-local-toolbox": "0.0.12",
     "raw-loader": "^0.5.1",
     "json-loader": "^0.5.4"
   }
 }
--- a/devtools/client/inspector/test/browser_inspector_menu-01-sensitivity.js
+++ b/devtools/client/inspector/test/browser_inspector_menu-01-sensitivity.js
@@ -21,16 +21,17 @@ const ACTIVE_ON_DOCTYPE_ITEMS = [
   "node-menu-useinconsole"
 ];
 
 const ALL_MENU_ITEMS = [
   "node-menu-edithtml",
   "node-menu-copyinner",
   "node-menu-copyouter",
   "node-menu-copyuniqueselector",
+  "node-menu-copycsspath",
   "node-menu-copyimagedatauri",
   "node-menu-delete",
   "node-menu-pseudo-hover",
   "node-menu-pseudo-active",
   "node-menu-pseudo-focus",
   "node-menu-scrollnodeintoview",
   "node-menu-screenshotnode",
   "node-menu-add-attribute",
--- a/devtools/client/inspector/test/browser_inspector_menu-02-copy-items.js
+++ b/devtools/client/inspector/test/browser_inspector_menu-02-copy-items.js
@@ -21,16 +21,22 @@ const COPY_ITEMS_TEST_DATA = [
   },
   {
     desc: "copy unique selector",
     id: "node-menu-copyuniqueselector",
     selector: "[data-id=\"copy\"]",
     text: "body > div:nth-child(1) > p:nth-child(2)",
   },
   {
+    desc: "copy css path",
+    id: "node-menu-copycsspath",
+    selector: "[data-id=\"copy\"]",
+    text: "html body div p",
+  },
+  {
     desc: "copy image data uri",
     id: "node-menu-copyimagedatauri",
     selector: "#copyimage",
     text: "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABC" +
       "AAAAAA6fptVAAAACklEQVQYV2P4DwABAQEAWk1v8QAAAABJRU5ErkJggg==",
   },
 ];
 
--- a/devtools/client/inspector/webpack.config.js
+++ b/devtools/client/inspector/webpack.config.js
@@ -125,12 +125,12 @@ module.exports = envConfig => {
       "resource://gre/modules/XPCOMUtils.jsm": "{}",
       "resource://devtools/client/styleeditor/StyleEditorUI.jsm": "{}",
       "resource://devtools/client/styleeditor/StyleEditorUtil.jsm": "{}",
       "devtools/client/shared/developer-toolbar": "{}",
     },
   ];
 
   // Exclude all files from devtools/ or addon-sdk/ or modules/ .
-  webpackConfig.babelExcludes = /(devtools\/|addon-sdk\/|modules\/)/;
+  webpackConfig.babelExcludes = /(devtools(\/|\\)|addon-sdk(\/|\\)|modules(\/|\\))/;
 
   return toolboxConfig(webpackConfig, envConfig);
 };
--- a/devtools/client/jsonview/components/headers-panel.js
+++ b/devtools/client/jsonview/components/headers-panel.js
@@ -3,17 +3,19 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 "use strict";
 
 define(function (require, exports, module) {
   const { DOM: dom, createFactory, createClass, PropTypes } = require("devtools/client/shared/vendor/react");
-  const { createFactories } = require("devtools/client/shared/components/reps/rep-utils");
+
+  const { createFactories } = require("devtools/client/shared/components/reps/load-reps");
+
   const { Headers } = createFactories(require("./headers"));
   const { Toolbar, ToolbarButton } = createFactories(require("./reps/toolbar"));
 
   const { div } = dom;
 
   /**
    * This template represents the 'Headers' panel
    * s responsible for rendering its content.
--- a/devtools/client/jsonview/components/json-panel.js
+++ b/devtools/client/jsonview/components/json-panel.js
@@ -3,20 +3,21 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 "use strict";
 
 define(function (require, exports, module) {
   const { DOM: dom, createFactory, createClass, PropTypes } = require("devtools/client/shared/vendor/react");
-  const { createFactories } = require("devtools/client/shared/components/reps/rep-utils");
   const TreeView = createFactory(require("devtools/client/shared/components/tree/tree-view"));
-  const { Rep } = createFactories(require("devtools/client/shared/components/reps/rep"));
-  const { MODE } = require("devtools/client/shared/components/reps/constants");
+
+  const { REPS, createFactories, MODE } = require("devtools/client/shared/components/reps/load-reps");
+  const Rep = createFactory(REPS.Rep);
+
   const { SearchBox } = createFactories(require("./search-box"));
   const { Toolbar, ToolbarButton } = createFactories(require("./reps/toolbar"));
 
   const { div } = dom;
   const AUTO_EXPAND_MAX_SIZE = 100 * 1024;
   const AUTO_EXPAND_MAX_LEVEL = 7;
 
   /**
--- a/devtools/client/jsonview/components/main-tabbed-area.js
+++ b/devtools/client/jsonview/components/main-tabbed-area.js
@@ -3,17 +3,18 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 "use strict";
 
 define(function (require, exports, module) {
   const { createClass, PropTypes } = require("devtools/client/shared/vendor/react");
-  const { createFactories } = require("devtools/client/shared/components/reps/rep-utils");
+
+  const { createFactories } = require("devtools/client/shared/components/reps/load-reps");
   const { JsonPanel } = createFactories(require("./json-panel"));
   const { TextPanel } = createFactories(require("./text-panel"));
   const { HeadersPanel } = createFactories(require("./headers-panel"));
   const { Tabs, TabPanel } = createFactories(require("devtools/client/shared/components/tabs/tabs"));
 
   /**
    * This object represents the root application template
    * responsible for rendering the basic tab layout.
--- a/devtools/client/jsonview/components/text-panel.js
+++ b/devtools/client/jsonview/components/text-panel.js
@@ -3,17 +3,19 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 "use strict";
 
 define(function (require, exports, module) {
   const { DOM: dom, createFactory, createClass, PropTypes } = require("devtools/client/shared/vendor/react");
-  const { createFactories } = require("devtools/client/shared/components/reps/rep-utils");
+
+  // we'll need to make load-reps define friendly aka UMD
+  const { createFactories } = require("devtools/client/shared/components/reps/load-reps");
   const { Toolbar, ToolbarButton } = createFactories(require("./reps/toolbar"));
   const { div, pre } = dom;
 
   /**
    * This template represents the 'Raw Data' panel displaying
    * JSON as a text received from the server.
    */
   let TextPanel = createClass({
--- a/devtools/client/jsonview/json-viewer.js
+++ b/devtools/client/jsonview/json-viewer.js
@@ -3,17 +3,17 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 "use strict";
 
 define(function (require, exports, module) {
   const { render } = require("devtools/client/shared/vendor/react-dom");
-  const { createFactories } = require("devtools/client/shared/components/reps/rep-utils");
+  const { createFactories } = require("devtools/client/shared/components/reps/load-reps");
   const { MainTabbedArea } = createFactories(require("./components/main-tabbed-area"));
 
   const json = document.getElementById("json");
   const headers = document.getElementById("headers");
 
   let jsonData;
 
   try {
@@ -104,9 +104,8 @@ define(function (require, exports, modul
   onResize();
 
   // Send notification event to the window. Can be useful for
   // tests as well as extensions.
   let event = new CustomEvent("JSONViewInitialized", {});
   window.jsonViewInitialized = true;
   window.dispatchEvent(event);
 });
-
--- a/devtools/client/locales/en-US/inspector.properties
+++ b/devtools/client/locales/en-US/inspector.properties
@@ -155,16 +155,22 @@ inspectorCopyOuterHTML.label=Outer HTML
 inspectorCopyOuterHTML.accesskey=O
 
 # LOCALIZATION NOTE (inspectorCopyCSSSelector.label): This is the label
 # shown in the inspector contextual-menu for the item that lets users copy
 # the CSS Selector of the current node
 inspectorCopyCSSSelector.label=CSS Selector
 inspectorCopyCSSSelector.accesskey=S
 
+# LOCALIZATION NOTE (inspectorCopyCSSPath.label): This is the label
+# shown in the inspector contextual-menu for the item that lets users copy
+# the full CSS path of the current node
+inspectorCopyCSSPath.label=CSS Path
+inspectorCopyCSSPath.accesskey=P
+
 # LOCALIZATION NOTE (inspectorPasteOuterHTML.label): This is the label shown
 # in the inspector contextual-menu for the item that lets users paste outer
 # HTML in the current node
 inspectorPasteOuterHTML.label=Outer HTML
 inspectorPasteOuterHTML.accesskey=O
 
 # LOCALIZATION NOTE (inspectorPasteInnerHTML.label): This is the label shown
 # in the inspector contextual-menu for the item that lets users paste inner
--- a/devtools/client/netmonitor/netmonitor-controller.js
+++ b/devtools/client/netmonitor/netmonitor-controller.js
@@ -418,20 +418,16 @@ TargetEventsHandler.prototype = {
       case "will-navigate": {
         // Reset UI.
         if (!Services.prefs.getBoolPref("devtools.webconsole.persistlog")) {
           NetMonitorView.RequestsMenu.reset();
         } else {
           // If the log is persistent, just clear all accumulated timing markers.
           gStore.dispatch(Actions.clearTimingMarkers());
         }
-        // Switch to the default network traffic inspector view.
-        if (NetMonitorController.getCurrentActivity() == ACTIVITY_TYPE.NONE) {
-          NetMonitorView.showNetworkInspectorView();
-        }
 
         window.emit(EVENTS.TARGET_WILL_NAVIGATE);
         break;
       }
       case "navigate": {
         window.emit(EVENTS.TARGET_DID_NAVIGATE);
         break;
       }
--- a/devtools/client/netmonitor/shared/components/properties-view.js
+++ b/devtools/client/netmonitor/shared/components/properties-view.js
@@ -95,25 +95,31 @@ const PropertiesView = createClass({
     if (level >= 1 && path.includes(EDITOR_CONFIG_ID)) {
       return null;
     }
 
     return TreeRow(props);
   },
 
   renderValueWithRep(props) {
-    // Hide rep summary for sections
-    if (props.member.level === 0) {
+    const { member } = props;
+
+    // Hide strings with following conditions
+    // 1. this row is a togglable section
+    // 2. the `value` object has a `value` property, only happend in Cookies panel
+    // Put 2 here to not dup this method
+    if (member.level === 0 ||
+      (typeof member.value === "object" && member.value.value)) {
       return null;
     }
 
     return Rep(Object.assign(props, {
       // FIXME: A workaround for the issue in StringRep
       // Force StringRep to crop the text everytime
-      member: Object.assign({}, props.member, { open: false }),
+      member: Object.assign({}, member, { open: false }),
       mode: MODE.TINY,
       cropLimit: 60,
     }));
   },
 
   shouldRenderSearchBox(object) {
     return this.props.enableFilter && object && Object.keys(object)
       .filter((section) => !object[section][EDITOR_CONFIG_ID]).length > 0;
@@ -144,21 +150,21 @@ const PropertiesView = createClass({
         expandedNodes = new Set([...expandedNodes, ...nodes]);
       }
     }
     return expandedNodes;
   },
 
   render() {
     const {
-      object,
       decorator,
       enableInput,
       expandableStrings,
       filterPlaceHolder,
+      object,
       renderRow,
       renderValue,
       sectionNames,
     } = this.props;
 
     return (
       div({ className: "properties-view" },
         this.shouldRenderSearchBox(object) &&
@@ -186,13 +192,11 @@ const PropertiesView = createClass({
             onFilter: (props) => this.onFilter(props, sectionNames),
             renderRow: renderRow || this.renderRowWithEditor,
             renderValue: renderValue || this.renderValueWithRep,
           }),
         ),
       )
     );
   }
-
 });
 
 module.exports = PropertiesView;
-
--- a/devtools/client/netmonitor/test/browser.ini
+++ b/devtools/client/netmonitor/test/browser.ini
@@ -149,17 +149,16 @@ skip-if = true # Bug 1258809
 skip-if = true # Bug 1258809
 [browser_net_simple-request.js]
 [browser_net_sort-01.js]
 skip-if = true # Redundant for React/Redux version
 [browser_net_sort-02.js]
 [browser_net_sort-03.js]
 [browser_net_statistics-01.js]
 [browser_net_statistics-02.js]
-[browser_net_statistics-03.js]
 [browser_net_status-codes.js]
 [browser_net_streaming-response.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_persistent_logs.js]
--- a/devtools/client/netmonitor/test/browser_net_statistics-02.js
+++ b/devtools/client/netmonitor/test/browser_net_statistics-02.js
@@ -1,43 +1,46 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 "use strict";
 
 /**
- * Tests if the network inspector view is shown when the target navigates
- * away while in the statistics view.
+ * Test if the correct filtering predicates are used when filtering from
+ * the performance analysis view.
  */
 
 add_task(function* () {
-  let { tab, monitor } = yield initNetMonitor(STATISTICS_URL);
+  let { monitor } = yield initNetMonitor(FILTERING_URL);
   info("Starting test... ");
 
   let panel = monitor.panelWin;
   let { $, $all, EVENTS, NetMonitorView, gStore, windowRequire } = panel;
   let Actions = windowRequire("devtools/client/netmonitor/actions/index");
-  is(NetMonitorView.currentFrontendMode, "network-inspector-view",
-      "The initial frontend mode is correct.");
 
-  let onChartDisplayed = Promise.all([
+  EventUtils.sendMouseEvent({ type: "click" }, $("#requests-menu-filter-html-button"));
+  EventUtils.sendMouseEvent({ type: "click" }, $("#requests-menu-filter-css-button"));
+  EventUtils.sendMouseEvent({ type: "click" }, $("#requests-menu-filter-js-button"));
+  EventUtils.sendMouseEvent({ type: "click" }, $("#requests-menu-filter-ws-button"));
+  EventUtils.sendMouseEvent({ type: "click" }, $("#requests-menu-filter-other-button"));
+  testFilterButtonsCustom(monitor, [0, 1, 1, 1, 0, 0, 0, 0, 0, 1, 1]);
+  info("The correct filtering predicates are used before entering perf. analysis mode.");
+
+  let onEvents = promise.all([
     panel.once(EVENTS.PRIMED_CACHE_CHART_DISPLAYED),
     panel.once(EVENTS.EMPTY_CACHE_CHART_DISPLAYED)
   ]);
+  gStore.dispatch(Actions.openStatistics(true));
+  yield onEvents;
 
-  info("Displaying statistics view");
-  gStore.dispatch(Actions.openStatistics(true));
-  yield onChartDisplayed;
   is(NetMonitorView.currentFrontendMode, "network-statistics-view",
-        "The frontend mode is currently in the statistics view.");
+    "The frontend mode is switched to the statistics view.");
 
-  info("Reloading page");
-  let onWillNavigate = panel.once(EVENTS.TARGET_WILL_NAVIGATE);
-  let onDidNavigate = panel.once(EVENTS.TARGET_DID_NAVIGATE);
-  tab.linkedBrowser.reload();
-  yield onWillNavigate;
+  EventUtils.sendMouseEvent({ type: "click" }, $(".pie-chart-slice"));
+
   is(NetMonitorView.currentFrontendMode, "network-inspector-view",
-          "The frontend mode switched back to the inspector view.");
-  yield onDidNavigate;
-  is(NetMonitorView.currentFrontendMode, "network-inspector-view",
-            "The frontend mode is still in the inspector view.");
+    "The frontend mode is switched back to the inspector view.");
+
+  testFilterButtons(monitor, "html");
+  info("The correct filtering predicate is used when exiting perf. analysis mode.");
+
   yield teardown(monitor);
 });
deleted file mode 100644
--- a/devtools/client/netmonitor/test/browser_net_statistics-03.js
+++ /dev/null
@@ -1,46 +0,0 @@
-/* Any copyright is dedicated to the Public Domain.
-   http://creativecommons.org/publicdomain/zero/1.0/ */
-
-"use strict";
-
-/**
- * Test if the correct filtering predicates are used when filtering from
- * the performance analysis view.
- */
-
-add_task(function* () {
-  let { monitor } = yield initNetMonitor(FILTERING_URL);
-  info("Starting test... ");
-
-  let panel = monitor.panelWin;
-  let { $, $all, EVENTS, NetMonitorView, gStore, windowRequire } = panel;
-  let Actions = windowRequire("devtools/client/netmonitor/actions/index");
-
-  EventUtils.sendMouseEvent({ type: "click" }, $("#requests-menu-filter-html-button"));
-  EventUtils.sendMouseEvent({ type: "click" }, $("#requests-menu-filter-css-button"));
-  EventUtils.sendMouseEvent({ type: "click" }, $("#requests-menu-filter-js-button"));
-  EventUtils.sendMouseEvent({ type: "click" }, $("#requests-menu-filter-ws-button"));
-  EventUtils.sendMouseEvent({ type: "click" }, $("#requests-menu-filter-other-button"));
-  testFilterButtonsCustom(monitor, [0, 1, 1, 1, 0, 0, 0, 0, 0, 1, 1]);
-  info("The correct filtering predicates are used before entering perf. analysis mode.");
-
-  let onEvents = promise.all([
-    panel.once(EVENTS.PRIMED_CACHE_CHART_DISPLAYED),
-    panel.once(EVENTS.EMPTY_CACHE_CHART_DISPLAYED)
-  ]);
-  gStore.dispatch(Actions.openStatistics(true));
-  yield onEvents;
-
-  is(NetMonitorView.currentFrontendMode, "network-statistics-view",
-    "The frontend mode is switched to the statistics view.");
-
-  EventUtils.sendMouseEvent({ type: "click" }, $(".pie-chart-slice"));
-
-  is(NetMonitorView.currentFrontendMode, "network-inspector-view",
-    "The frontend mode is switched back to the inspector view.");
-
-  testFilterButtons(monitor, "html");
-  info("The correct filtering predicate is used when exiting perf. analysis mode.");
-
-  yield teardown(monitor);
-});
new file mode 100644
--- /dev/null
+++ b/devtools/client/shared/components/reps/load-reps.js
@@ -0,0 +1,37 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+// Make this available to both AMD and CJS environments
+define(function (require, exports, module) {
+  let REPS;
+  let MODE;
+  let createFactories;
+  let parseURLEncodedText;
+  let parseURLParams;
+
+  // useRepsBundle hardcoded to false while we experiment with the reps bundle in
+  // bugs 1325401 and 1330779. Set it to true to test devtools using the reps bundle.
+  let useRepsBundle = false;
+  if (useRepsBundle) {
+    const bundle = require("devtools/client/shared/components/reps/reps");
+    REPS = bundle.REPS;
+    MODE = bundle.MODE;
+    createFactories = bundle.createFactories;
+    parseURLEncodedText = bundle.parseURLEncodedText;
+    parseURLParams = bundle.parseURLParams;
+  } else {
+    ({ createFactories, parseURLEncodedText, parseURLParams } =
+      require("devtools/client/shared/components/reps/rep-utils"));
+    REPS = require("devtools/client/shared/components/reps/rep").REPS;
+    MODE = require("devtools/client/shared/components/reps/constants").MODE;
+  }
+
+  exports.REPS = REPS;
+  exports.MODE = MODE;
+  exports.createFactories = createFactories;
+  exports.parseURLEncodedText = parseURLEncodedText;
+  exports.parseURLParams = parseURLParams;
+});
--- a/devtools/client/shared/components/reps/moz.build
+++ b/devtools/client/shared/components/reps/moz.build
@@ -15,28 +15,32 @@ DevToolsModules(
     'element-node.js',
     'error.js',
     'event.js',
     'function.js',
     'grip-array.js',
     'grip-map.js',
     'grip.js',
     'infinity.js',
+    'load-reps.js',
     'long-string.js',
     'nan.js',
     'null.js',
     'number.js',
     'object-with-text.js',
     'object-with-url.js',
     'object.js',
     'promise.js',
     'prop-rep.js',
     'regexp.js',
     'rep-utils.js',
     'rep.js',
     'reps.css',
+    'reps.js',
     'string.js',
     'stylesheet.js',
     'symbol.js',
     'text-node.js',
     'undefined.js',
     'window.js',
 )
+
+MOCHITEST_CHROME_MANIFESTS += ['test/mochitest/chrome.ini']
--- a/devtools/client/shared/components/reps/rep.js
+++ b/devtools/client/shared/components/reps/rep.js
@@ -140,9 +140,41 @@ define(function (require, exports, modul
       }
     }
 
     return React.createFactory(defaultRep.rep);
   }
 
   // Exports from this module
   exports.Rep = Rep;
+
+  // Export all reps
+  exports.REPS = {
+    ArrayRep,
+    Attribute,
+    CommentNode,
+    DateTime,
+    Document,
+    ElementNode,
+    ErrorRep,
+    Event,
+    Func,
+    Grip,
+    GripArray,
+    GripMap,
+    InfinityRep,
+    LongStringRep,
+    NaNRep,
+    Null,
+    Number,
+    ObjectWithText,
+    ObjectWithURL,
+    PromiseRep,
+    RegExp,
+    Rep,
+    StringRep,
+    StyleSheet,
+    SymbolRep,
+    TextNode,
+    Undefined,
+    Window,
+  };
 });
new file mode 100644
--- /dev/null
+++ b/devtools/client/shared/components/reps/reps.js
@@ -0,0 +1,2731 @@
+(function webpackUniversalModuleDefinition(root, factory) {
+	if(typeof exports === 'object' && typeof module === 'object')
+		module.exports = factory(require("devtools/client/shared/vendor/react"));
+	else if(typeof define === 'function' && define.amd)
+		define(["devtools/client/shared/vendor/react"], factory);
+	else {
+		var a = typeof exports === 'object' ? factory(require("devtools/client/shared/vendor/react")) : factory(root["devtools/client/shared/vendor/react"]);
+		for(var i in a) (typeof exports === 'object' ? exports : root)[i] = a[i];
+	}
+})(this, function(__WEBPACK_EXTERNAL_MODULE_1__) {
+return /******/ (function(modules) { // webpackBootstrap
+/******/ 	// The module cache
+/******/ 	var installedModules = {};
+/******/
+/******/ 	// The require function
+/******/ 	function __webpack_require__(moduleId) {
+/******/ 		// SingleModulePlugin
+/******/ 		const smpCache = this.smpCache = this.smpCache || {};
+/******/ 		const smpMap = this.smpMap = this.smpMap || new Map();
+/******/ 		function sanitizeString(text) {
+/******/ 		   return text.replace(/__webpack_require__\(\d+\)/g,"");
+/******/ 		}
+/******/ 		function getModuleBody(id) {
+/******/ 		  if (smpCache.hasOwnProperty(id)) {
+/******/ 		    return smpCache[id];
+/******/ 		  }
+/******/
+/******/ 		  const body = sanitizeString(String(modules[id]));
+/******/ 		  smpCache[id] = body;
+/******/ 		  return body;
+/******/ 		}
+/******/ 		if (!installedModules[moduleId]) {
+/******/ 			const body = getModuleBody(moduleId);
+/******/ 			if (smpMap.has(body)) {
+/******/ 				installedModules[moduleId] = installedModules[smpMap.get(body)];
+/******/ 			}
+/******/ 			else {
+/******/ 				smpMap.set(body, moduleId)
+/******/ 			}
+/******/ 		}
+/******/
+/******/ 		// Check if module is in cache
+/******/ 		if(installedModules[moduleId])
+/******/ 			return installedModules[moduleId].exports;
+/******/
+/******/ 		// Create a new module (and put it into the cache)
+/******/ 		var module = installedModules[moduleId] = {
+/******/ 			exports: {},
+/******/ 			id: moduleId,
+/******/ 			loaded: false
+/******/ 		};
+/******/
+/******/ 		// Execute the module function
+/******/ 		modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);
+/******/
+/******/ 		// Flag the module as loaded
+/******/ 		module.loaded = true;
+/******/
+/******/ 		// Return the exports of the module
+/******/ 		return module.exports;
+/******/ 	}
+/******/
+/******/
+/******/ 	// expose the modules object (__webpack_modules__)
+/******/ 	__webpack_require__.m = modules;
+/******/
+/******/ 	// expose the module cache
+/******/ 	__webpack_require__.c = installedModules;
+/******/
+/******/ 	// __webpack_public_path__
+/******/ 	__webpack_require__.p = "/assets/build";
+/******/
+/******/ 	// Load entry module and return exports
+/******/ 	return __webpack_require__(0);
+/******/ })
+/************************************************************************/
+/******/ ([
+/* 0 */
+/***/ function(module, exports, __webpack_require__) {
+
+	const React = __webpack_require__(1);
+
+	const { MODE } = __webpack_require__(2);
+	const { REPS } = __webpack_require__(3);
+	const { createFactories, parseURLEncodedText, parseURLParams } = __webpack_require__(4);
+
+	module.exports = {
+	  REPS,
+	  MODE,
+	  createFactories,
+	  parseURLEncodedText,
+	  parseURLParams
+	};
+
+/***/ },
+/* 1 */
+/***/ function(module, exports) {
+
+	module.exports = __WEBPACK_EXTERNAL_MODULE_1__;
+
+/***/ },
+/* 2 */
+/***/ function(module, exports) {
+
+	module.exports = {
+	  MODE: {
+	    TINY: Symbol("TINY"),
+	    SHORT: Symbol("SHORT"),
+	    LONG: Symbol("LONG")
+	  }
+	};
+
+/***/ },
+/* 3 */
+/***/ function(module, exports, __webpack_require__) {
+
+	const React = __webpack_require__(1);
+
+	const { isGrip } = __webpack_require__(4);
+	const { MODE } = __webpack_require__(2);
+
+	// Load all existing rep templates
+	const Undefined = __webpack_require__(5);
+	const Null = __webpack_require__(6);
+	const StringRep = __webpack_require__(7);
+	const LongStringRep = __webpack_require__(8);
+	const Number = __webpack_require__(9);
+	const ArrayRep = __webpack_require__(10);
+	const Obj = __webpack_require__(12);
+	const SymbolRep = __webpack_require__(15);
+	const InfinityRep = __webpack_require__(16);
+	const NaNRep = __webpack_require__(17);
+
+	// DOM types (grips)
+	const Attribute = __webpack_require__(18);
+	const DateTime = __webpack_require__(19);
+	const Document = __webpack_require__(20);
+	const Event = __webpack_require__(21);
+	const Func = __webpack_require__(22);
+	const PromiseRep = __webpack_require__(23);
+	const RegExp = __webpack_require__(24);
+	const StyleSheet = __webpack_require__(25);
+	const CommentNode = __webpack_require__(26);
+	const ElementNode = __webpack_require__(28);
+	const TextNode = __webpack_require__(29);
+	const ErrorRep = __webpack_require__(30);
+	const Window = __webpack_require__(31);
+	const ObjectWithText = __webpack_require__(32);
+	const ObjectWithURL = __webpack_require__(33);
+	const GripArray = __webpack_require__(34);
+	const GripMap = __webpack_require__(35);
+	const Grip = __webpack_require__(14);
+
+	// List of all registered template.
+	// XXX there should be a way for extensions to register a new
+	// or modify an existing rep.
+	let reps = [RegExp, StyleSheet, Event, DateTime, CommentNode, ElementNode, TextNode, Attribute, LongStringRep, Func, PromiseRep, ArrayRep, Document, Window, ObjectWithText, ObjectWithURL, ErrorRep, GripArray, GripMap, Grip, Undefined, Null, StringRep, Number, SymbolRep, InfinityRep, NaNRep];
+
+	/**
+	 * Generic rep that is using for rendering native JS types or an object.
+	 * The right template used for rendering is picked automatically according
+	 * to the current value type. The value must be passed is as 'object'
+	 * property.
+	 */
+	const Rep = React.createClass({
+	  displayName: "Rep",
+
+	  propTypes: {
+	    object: React.PropTypes.any,
+	    defaultRep: React.PropTypes.object,
+	    // @TODO Change this to Object.values once it's supported in Node's version of V8
+	    mode: React.PropTypes.oneOf(Object.keys(MODE).map(key => MODE[key]))
+	  },
+
+	  render: function () {
+	    let rep = getRep(this.props.object, this.props.defaultRep);
+	    return rep(this.props);
+	  }
+	});
+
+	// Helpers
+
+	/**
+	 * Return a rep object that is responsible for rendering given
+	 * object.
+	 *
+	 * @param object {Object} Object to be rendered in the UI. This
+	 * can be generic JS object as well as a grip (handle to a remote
+	 * debuggee object).
+	 *
+	 * @param defaultObject {React.Component} The default template
+	 * that should be used to render given object if none is found.
+	 */
+	function getRep(object, defaultRep = Obj) {
+	  let type = typeof object;
+	  if (type == "object" && object instanceof String) {
+	    type = "string";
+	  } else if (object && type == "object" && object.type) {
+	    type = object.type;
+	  }
+
+	  if (isGrip(object)) {
+	    type = object.class;
+	  }
+
+	  for (let i = 0; i < reps.length; i++) {
+	    let rep = reps[i];
+	    try {
+	      // supportsObject could return weight (not only true/false
+	      // but a number), which would allow to priorities templates and
+	      // support better extensibility.
+	      if (rep.supportsObject(object, type)) {
+	        return React.createFactory(rep.rep);
+	      }
+	    } catch (err) {
+	      console.error(err);
+	    }
+	  }
+
+	  return React.createFactory(defaultRep.rep);
+	}
+
+	module.exports = {
+	  Rep,
+	  REPS: {
+	    ArrayRep,
+	    Attribute,
+	    CommentNode,
+	    DateTime,
+	    Document,
+	    ElementNode,
+	    ErrorRep,
+	    Event,
+	    Func,
+	    Grip,
+	    GripArray,
+	    GripMap,
+	    InfinityRep,
+	    LongStringRep,
+	    NaNRep,
+	    Null,
+	    Number,
+	    ObjectWithText,
+	    ObjectWithURL,
+	    PromiseRep,
+	    RegExp,
+	    Rep,
+	    StringRep,
+	    StyleSheet,
+	    SymbolRep,
+	    TextNode,
+	    Undefined,
+	    Window
+	  }
+	};
+
+/***/ },
+/* 4 */
+/***/ function(module, exports, __webpack_require__) {
+
+	const React = __webpack_require__(1);
+
+	/**
+	 * Create React factories for given arguments.
+	 * Example:
+	 *   const { Rep } = createFactories(require("./rep"));
+	 */
+	function createFactories(args) {
+	  let result = {};
+	  for (let p in args) {
+	    result[p] = React.createFactory(args[p]);
+	  }
+	  return result;
+	}
+
+	/**
+	 * Returns true if the given object is a grip (see RDP protocol)
+	 */
+	function isGrip(object) {
+	  return object && object.actor;
+	}
+
+	function escapeNewLines(value) {
+	  return value.replace(/\r/gm, "\\r").replace(/\n/gm, "\\n");
+	}
+
+	function cropMultipleLines(text, limit) {
+	  return escapeNewLines(cropString(text, limit));
+	}
+
+	function cropString(text, limit, alternativeText) {
+	  if (!alternativeText) {
+	    alternativeText = "\u2026";
+	  }
+
+	  // Make sure it's a string and sanitize it.
+	  text = sanitizeString(text + "");
+
+	  // Crop the string only if a limit is actually specified.
+	  if (!limit || limit <= 0) {
+	    return text;
+	  }
+
+	  // Set the limit at least to the length of the alternative text
+	  // plus one character of the original text.
+	  if (limit <= alternativeText.length) {
+	    limit = alternativeText.length + 1;
+	  }
+
+	  let halfLimit = (limit - alternativeText.length) / 2;
+
+	  if (text.length > limit) {
+	    return text.substr(0, Math.ceil(halfLimit)) + alternativeText + text.substr(text.length - Math.floor(halfLimit));
+	  }
+
+	  return text;
+	}
+
+	function sanitizeString(text) {
+	  // Replace all non-printable characters, except of
+	  // (horizontal) tab (HT: \x09) and newline (LF: \x0A, CR: \x0D),
+	  // with unicode replacement character (u+fffd).
+	  // eslint-disable-next-line no-control-regex
+	  let re = new RegExp("[\x00-\x08\x0B\x0C\x0E-\x1F\x80-\x9F]", "g");
+	  return text.replace(re, "\ufffd");
+	}
+
+	function parseURLParams(url) {
+	  url = new URL(url);
+	  return parseURLEncodedText(url.searchParams);
+	}
+
+	function parseURLEncodedText(text) {
+	  let params = [];
+
+	  // In case the text is empty just return the empty parameters
+	  if (text == "") {
+	    return params;
+	  }
+
+	  let searchParams = new URLSearchParams(text);
+	  let entries = [...searchParams.entries()];
+	  return entries.map(entry => {
+	    return {
+	      name: entry[0],
+	      value: entry[1]
+	    };
+	  });
+	}
+
+	function getFileName(url) {
+	  let split = splitURLBase(url);
+	  return split.name;
+	}
+
+	function splitURLBase(url) {
+	  if (!isDataURL(url)) {
+	    return splitURLTrue(url);
+	  }
+	  return {};
+	}
+
+	function getURLDisplayString(url) {
+	  return cropString(url);
+	}
+
+	function isDataURL(url) {
+	  return url && url.substr(0, 5) == "data:";
+	}
+
+	function splitURLTrue(url) {
+	  const reSplitFile = /(.*?):\/{2,3}([^\/]*)(.*?)([^\/]*?)($|\?.*)/;
+	  let m = reSplitFile.exec(url);
+
+	  if (!m) {
+	    return {
+	      name: url,
+	      path: url
+	    };
+	  } else if (m[4] == "" && m[5] == "") {
+	    return {
+	      protocol: m[1],
+	      domain: m[2],
+	      path: m[3],
+	      name: m[3] != "/" ? m[3] : m[2]
+	    };
+	  }
+
+	  return {
+	    protocol: m[1],
+	    domain: m[2],
+	    path: m[2] + m[3],
+	    name: m[4] + m[5]
+	  };
+	}
+
+	module.exports = {
+	  createFactories,
+	  isGrip,
+	  cropString,
+	  sanitizeString,
+	  cropMultipleLines,
+	  parseURLParams,
+	  parseURLEncodedText,
+	  getFileName,
+	  getURLDisplayString
+	};
+
+/***/ },
+/* 5 */
+/***/ function(module, exports, __webpack_require__) {
+
+	const React = __webpack_require__(1);
+
+	// Shortcuts
+	const { span } = React.DOM;
+
+	/**
+	 * Renders undefined value
+	 */
+	const Undefined = React.createClass({
+	  displayName: "UndefinedRep",
+
+	  render: function () {
+	    return span({ className: "objectBox objectBox-undefined" }, "undefined");
+	  }
+	});
+
+	function supportsObject(object, type) {
+	  if (object && object.type && object.type == "undefined") {
+	    return true;
+	  }
+
+	  return type == "undefined";
+	}
+
+	module.exports = {
+	  rep: Undefined,
+	  supportsObject: supportsObject
+	};
+
+/***/ },
+/* 6 */
+/***/ function(module, exports, __webpack_require__) {
+
+	const React = __webpack_require__(1);
+
+	// Shortcuts
+	const { span } = React.DOM;
+
+	/**
+	 * Renders null value
+	 */
+	const Null = React.createClass({
+	  displayName: "NullRep",
+
+	  render: function () {
+	    return span({ className: "objectBox objectBox-null" }, "null");
+	  }
+	});
+
+	function supportsObject(object, type) {
+	  if (object && object.type && object.type == "null") {
+	    return true;
+	  }
+
+	  return object == null;
+	}
+
+	module.exports = {
+	  rep: Null,
+	  supportsObject: supportsObject
+	};
+
+/***/ },
+/* 7 */
+/***/ function(module, exports, __webpack_require__) {
+
+	const React = __webpack_require__(1);
+	const { cropString } = __webpack_require__(4);
+
+	// Shortcuts
+	const { span } = React.DOM;
+
+	/**
+	 * Renders a string. String value is enclosed within quotes.
+	 */
+	const StringRep = React.createClass({
+	  displayName: "StringRep",
+
+	  propTypes: {
+	    useQuotes: React.PropTypes.bool,
+	    style: React.PropTypes.object
+	  },
+
+	  getDefaultProps: function () {
+	    return {
+	      useQuotes: true
+	    };
+	  },
+
+	  render: function () {
+	    let text = this.props.object;
+	    let member = this.props.member;
+	    let style = this.props.style;
+
+	    let config = { className: "objectBox objectBox-string" };
+	    if (style) {
+	      config.style = style;
+	    }
+
+	    if (member && member.open) {
+	      return span(config, "\"" + text + "\"");
+	    }
+
+	    let croppedString = this.props.cropLimit ? cropString(text, this.props.cropLimit) : cropString(text);
+
+	    let formattedString = this.props.useQuotes ? "\"" + croppedString + "\"" : croppedString;
+
+	    return span(config, formattedString);
+	  }
+	});
+
+	function supportsObject(object, type) {
+	  return type == "string";
+	}
+
+	module.exports = {
+	  rep: StringRep,
+	  supportsObject: supportsObject
+	};
+
+/***/ },
+/* 8 */
+/***/ function(module, exports, __webpack_require__) {
+
+	const React = __webpack_require__(1);
+	const { sanitizeString, isGrip } = __webpack_require__(4);
+	// Shortcuts
+	const { span } = React.DOM;
+
+	/**
+	 * Renders a long string grip.
+	 */
+	const LongStringRep = React.createClass({
+	  displayName: "LongStringRep",
+
+	  propTypes: {
+	    useQuotes: React.PropTypes.bool,
+	    style: React.PropTypes.object
+	  },
+
+	  getDefaultProps: function () {
+	    return {
+	      useQuotes: true
+	    };
+	  },
+
+	  render: function () {
+	    let {
+	      cropLimit,
+	      member,
+	      object,
+	      style,
+	      useQuotes
+	    } = this.props;
+	    let { fullText, initial, length } = object;
+
+	    let config = { className: "objectBox objectBox-string" };
+	    if (style) {
+	      config.style = style;
+	    }
+
+	    let string = member && member.open ? fullText || initial : initial.substring(0, cropLimit);
+
+	    if (string.length < length) {
+	      string += "\u2026";
+	    }
+	    let formattedString = useQuotes ? `"${ string }"` : string;
+	    return span(config, sanitizeString(formattedString));
+	  }
+	});
+
+	function supportsObject(object, type) {
+	  if (!isGrip(object)) {
+	    return false;
+	  }
+	  return object.type === "longString";
+	}
+
+	module.exports = {
+	  rep: LongStringRep,
+	  supportsObject: supportsObject
+	};
+
+/***/ },
+/* 9 */
+/***/ function(module, exports, __webpack_require__) {
+
+	const React = __webpack_require__(1);
+
+	// Shortcuts
+	const { span } = React.DOM;
+
+	/**
+	 * Renders a number
+	 */
+	const Number = React.createClass({
+	  displayName: "Number",
+
+	  stringify: function (object) {
+	    let isNegativeZero = Object.is(object, -0) || object.type && object.type == "-0";
+
+	    return isNegativeZero ? "-0" : String(object);
+	  },
+
+	  render: function () {
+	    let value = this.props.object;
+
+	    return span({ className: "objectBox objectBox-number" }, this.stringify(value));
+	  }
+	});
+
+	function supportsObject(object, type) {
+	  return ["boolean", "number", "-0"].includes(type);
+	}
+
+	module.exports = {
+	  rep: Number,
+	  supportsObject: supportsObject
+	};
+
+/***/ },
+/* 10 */
+/***/ function(module, exports, __webpack_require__) {
+
+
+	const React = __webpack_require__(1);
+	const Caption = React.createFactory(__webpack_require__(11));
+	const { MODE } = __webpack_require__(2);
+
+	// Shortcuts
+	const DOM = React.DOM;
+
+	/**
+	 * Renders an array. The array is enclosed by left and right bracket
+	 * and the max number of rendered items depends on the current mode.
+	 */
+	let ArrayRep = React.createClass({
+	  displayName: "ArrayRep",
+
+	  propTypes: {
+	    // @TODO Change this to Object.values once it's supported in Node's version of V8
+	    mode: React.PropTypes.oneOf(Object.keys(MODE).map(key => MODE[key]))
+	  },
+
+	  getTitle: function (object, context) {
+	    return "[" + object.length + "]";
+	  },
+
+	  arrayIterator: function (array, max) {
+	    let items = [];
+	    let delim;
+
+	    for (let i = 0; i < array.length && i < max; i++) {
+	      try {
+	        let value = array[i];
+
+	        delim = i == array.length - 1 ? "" : ", ";
+
+	        items.push(ItemRep({
+	          object: value,
+	          // Hardcode tiny mode to avoid recursive handling.
+	          mode: MODE.TINY,
+	          delim: delim
+	        }));
+	      } catch (exc) {
+	        items.push(ItemRep({
+	          object: exc,
+	          mode: MODE.TINY,
+	          delim: delim
+	        }));
+	      }
+	    }
+
+	    if (array.length > max) {
+	      let objectLink = this.props.objectLink || DOM.span;
+	      items.push(Caption({
+	        object: objectLink({
+	          object: this.props.object
+	        }, array.length - max + " more…")
+	      }));
+	    }
+
+	    return items;
+	  },
+
+	  /**
+	   * Returns true if the passed object is an array with additional (custom)
+	   * properties, otherwise returns false. Custom properties should be
+	   * displayed in extra expandable section.
+	   *
+	   * Example array with a custom property.
+	   * let arr = [0, 1];
+	   * arr.myProp = "Hello";
+	   *
+	   * @param {Array} array The array object.
+	   */
+	  hasSpecialProperties: function (array) {
+	    function isInteger(x) {
+	      let y = parseInt(x, 10);
+	      if (isNaN(y)) {
+	        return false;
+	      }
+	      return x === y.toString();
+	    }
+
+	    let props = Object.getOwnPropertyNames(array);
+	    for (let i = 0; i < props.length; i++) {
+	      let p = props[i];
+
+	      // Valid indexes are skipped
+	      if (isInteger(p)) {
+	        continue;
+	      }
+
+	      // Ignore standard 'length' property, anything else is custom.
+	      if (p != "length") {
+	        return true;
+	      }
+	    }
+
+	    return false;
+	  },
+
+	  // Event Handlers
+
+	  onToggleProperties: function (event) {},
+
+	  onClickBracket: function (event) {},
+
+	  render: function () {
+	    let {
+	      object,
+	      mode = MODE.SHORT
+	    } = this.props;
+
+	    let items;
+	    let brackets;
+	    let needSpace = function (space) {
+	      return space ? { left: "[ ", right: " ]" } : { left: "[", right: "]" };
+	    };
+
+	    if (mode === MODE.TINY) {
+	      let isEmpty = object.length === 0;
+	      items = [DOM.span({ className: "length" }, isEmpty ? "" : object.length)];
+	      brackets = needSpace(false);
+	    } else {
+	      let max = mode === MODE.SHORT ? 3 : 10;
+	      items = this.arrayIterator(object, max);
+	      brackets = needSpace(items.length > 0);
+	    }
+
+	    let objectLink = this.props.objectLink || DOM.span;
+
+	    return DOM.span({
+	      className: "objectBox objectBox-array" }, objectLink({
+	      className: "arrayLeftBracket",
+	      object: object
+	    }, brackets.left), ...items, objectLink({
+	      className: "arrayRightBracket",
+	      object: object
+	    }, brackets.right), DOM.span({
+	      className: "arrayProperties",
+	      role: "group" }));
+	  }
+	});
+
+	/**
+	 * Renders array item. Individual values are separated by a comma.
+	 */
+	let ItemRep = React.createFactory(React.createClass({
+	  displayName: "ItemRep",
+
+	  render: function () {
+	    const Rep = React.createFactory(__webpack_require__(3));
+
+	    let object = this.props.object;
+	    let delim = this.props.delim;
+	    let mode = this.props.mode;
+	    return DOM.span({}, Rep({ object: object, mode: mode }), delim);
+	  }
+	}));
+
+	function supportsObject(object, type) {
+	  return Array.isArray(object) || Object.prototype.toString.call(object) === "[object Arguments]";
+	}
+
+	module.exports = {
+	  rep: ArrayRep,
+	  supportsObject: supportsObject
+	};
+
+/***/ },
+/* 11 */
+/***/ function(module, exports, __webpack_require__) {
+
+	const React = __webpack_require__(1);
+	const DOM = React.DOM;
+
+	/**
+	 * Renders a caption. This template is used by other components
+	 * that needs to distinguish between a simple text/value and a label.
+	 */
+	const Caption = React.createClass({
+	  displayName: "Caption",
+
+	  render: function () {
+	    return DOM.span({ "className": "caption" }, this.props.object);
+	  }
+	});
+
+	module.exports = Caption;
+
+/***/ },
+/* 12 */
+/***/ function(module, exports, __webpack_require__) {
+
+	const React = __webpack_require__(1);
+	const Caption = React.createFactory(__webpack_require__(11));
+	const PropRep = React.createFactory(__webpack_require__(13));
+	const { MODE } = __webpack_require__(2);
+	// Shortcuts
+	const { span } = React.DOM;
+	/**
+	 * Renders an object. An object is represented by a list of its
+	 * properties enclosed in curly brackets.
+	 */
+	const Obj = React.createClass({
+	  displayName: "Obj",
+
+	  propTypes: {
+	    object: React.PropTypes.object,
+	    // @TODO Change this to Object.values once it's supported in Node's version of V8
+	    mode: React.PropTypes.oneOf(Object.keys(MODE).map(key => MODE[key]))
+	  },
+
+	  getTitle: function (object) {
+	    let className = object && object.class ? object.class : "Object";
+	    if (this.props.objectLink) {
+	      return this.props.objectLink({
+	        object: object
+	      }, className);
+	    }
+	    return className;
+	  },
+
+	  safePropIterator: function (object, max) {
+	    max = typeof max === "undefined" ? 3 : max;
+	    try {
+	      return this.propIterator(object, max);
+	    } catch (err) {
+	      console.error(err);
+	    }
+	    return [];
+	  },
+
+	  propIterator: function (object, max) {
+	    let isInterestingProp = (t, value) => {
+	      // Do not pick objects, it could cause recursion.
+	      return t == "boolean" || t == "number" || t == "string" && value;
+	    };
+
+	    // Work around https://bugzilla.mozilla.org/show_bug.cgi?id=945377
+	    if (Object.prototype.toString.call(object) === "[object Generator]") {
+	      object = Object.getPrototypeOf(object);
+	    }
+
+	    // Object members with non-empty values are preferred since it gives the
+	    // user a better overview of the object.
+	    let props = this.getProps(object, max, isInterestingProp);
+
+	    if (props.length <= max) {
+	      // There are not enough props yet (or at least, not enough props to
+	      // be able to know whether we should print "more…" or not).
+	      // Let's display also empty members and functions.
+	      props = props.concat(this.getProps(object, max, (t, value) => {
+	        return !isInterestingProp(t, value);
+	      }));
+	    }
+
+	    if (props.length > max) {
+	      props.pop();
+	      let objectLink = this.props.objectLink || span;
+
+	      props.push(Caption({
+	        object: objectLink({
+	          object: object
+	        }, Object.keys(object).length - max + " more…")
+	      }));
+	    } else if (props.length > 0) {
+	      // Remove the last comma.
+	      props[props.length - 1] = React.cloneElement(props[props.length - 1], { delim: "" });
+	    }
+
+	    return props;
+	  },
+
+	  getProps: function (object, max, filter) {
+	    let props = [];
+
+	    max = max || 3;
+	    if (!object) {
+	      return props;
+	    }
+
+	    // Hardcode tiny mode to avoid recursive handling.
+	    let mode = MODE.TINY;
+
+	    try {
+	      for (let name in object) {
+	        if (props.length > max) {
+	          return props;
+	        }
+
+	        let value;
+	        try {
+	          value = object[name];
+	        } catch (exc) {
+	          continue;
+	        }
+
+	        let t = typeof value;
+	        if (filter(t, value)) {
+	          props.push(PropRep({
+	            mode: mode,
+	            name: name,
+	            object: value,
+	            equal: ": ",
+	            delim: ", "
+	          }));
+	        }
+	      }
+	    } catch (err) {
+	      console.error(err);
+	    }
+
+	    return props;
+	  },
+
+	  render: function () {
+	    let object = this.props.object;
+	    let props = this.safePropIterator(object);
+	    let objectLink = this.props.objectLink || span;
+
+	    if (this.props.mode === MODE.TINY || !props.length) {
+	      return span({ className: "objectBox objectBox-object" }, objectLink({ className: "objectTitle" }, this.getTitle(object)));
+	    }
+
+	    return span({ className: "objectBox objectBox-object" }, this.getTitle(object), objectLink({
+	      className: "objectLeftBrace",
+	      object: object
+	    }, " { "), ...props, objectLink({
+	      className: "objectRightBrace",
+	      object: object
+	    }, " }"));
+	  }
+	});
+	function supportsObject(object, type) {
+	  return true;
+	}
+
+	module.exports = {
+	  rep: Obj,
+	  supportsObject: supportsObject
+	};
+
+/***/ },
+/* 13 */
+/***/ function(module, exports, __webpack_require__) {
+
+	const React = __webpack_require__(1);
+	const { MODE } = __webpack_require__(2);
+	// Shortcuts
+	const { span } = React.DOM;
+
+	/**
+	 * Property for Obj (local JS objects), Grip (remote JS objects)
+	 * and GripMap (remote JS maps and weakmaps) reps.
+	 * It's used to render object properties.
+	 */
+	let PropRep = React.createFactory(React.createClass({
+	  displayName: "PropRep",
+
+	  propTypes: {
+	    // Property name.
+	    name: React.PropTypes.oneOfType([React.PropTypes.string, React.PropTypes.object]).isRequired,
+	    // Equal character rendered between property name and value.
+	    equal: React.PropTypes.string,
+	    // Delimiter character used to separate individual properties.
+	    delim: React.PropTypes.string,
+	    // @TODO Change this to Object.values once it's supported in Node's version of V8
+	    mode: React.PropTypes.oneOf(Object.keys(MODE).map(key => MODE[key]))
+	  },
+
+	  render: function () {
+	    const Grip = __webpack_require__(14);
+	    let Rep = React.createFactory(__webpack_require__(3));
+
+	    let key;
+	    // The key can be a simple string, for plain objects,
+	    // or another object for maps and weakmaps.
+	    if (typeof this.props.name === "string") {
+	      key = span({ "className": "nodeName" }, this.props.name);
+	    } else {
+	      key = Rep({
+	        object: this.props.name,
+	        mode: this.props.mode || MODE.TINY,
+	        defaultRep: Grip,
+	        objectLink: this.props.objectLink
+	      });
+	    }
+
+	    return span({}, key, span({
+	      "className": "objectEqual"
+	    }, this.props.equal), Rep(this.props), span({
+	      "className": "objectComma"
+	    }, this.props.delim));
+	  }
+	}));
+
+	module.exports = PropRep;
+
+/***/ },
+/* 14 */
+/***/ function(module, exports, __webpack_require__) {
+
+	const React = __webpack_require__(1);
+	// Dependencies
+	const { isGrip } = __webpack_require__(4);
+	const Caption = React.createFactory(__webpack_require__(11));
+	const PropRep = React.createFactory(__webpack_require__(13));
+	const { MODE } = __webpack_require__(2);
+	// Shortcuts
+	const { span } = React.DOM;
+
+	/**
+	 * Renders generic grip. Grip is client representation
+	 * of remote JS object and is used as an input object
+	 * for this rep component.
+	 */
+	const GripRep = React.createClass({
+	  displayName: "Grip",
+
+	  propTypes: {
+	    object: React.PropTypes.object.isRequired,
+	    // @TODO Change this to Object.values once it's supported in Node's version of V8
+	    mode: React.PropTypes.oneOf(Object.keys(MODE).map(key => MODE[key])),
+	    isInterestingProp: React.PropTypes.func,
+	    title: React.PropTypes.string
+	  },
+
+	  getTitle: function (object) {
+	    let title = this.props.title || object.class || "Object";
+	    if (this.props.objectLink) {
+	      return this.props.objectLink({
+	        object: object
+	      }, title);
+	    }
+	    return title;
+	  },
+
+	  safePropIterator: function (object, max) {
+	    max = typeof max === "undefined" ? 3 : max;
+	    try {
+	      return this.propIterator(object, max);
+	    } catch (err) {
+	      console.error(err);
+	    }
+	    return [];
+	  },
+
+	  propIterator: function (object, max) {
+	    if (object.preview && Object.keys(object.preview).includes("wrappedValue")) {
+	      const Rep = React.createFactory(__webpack_require__(3));
+
+	      return [Rep({
+	        object: object.preview.wrappedValue,
+	        mode: this.props.mode || MODE.TINY,
+	        defaultRep: Grip
+	      })];
+	    }
+
+	    // Property filter. Show only interesting properties to the user.
+	    let isInterestingProp = this.props.isInterestingProp || ((type, value) => {
+	      return type == "boolean" || type == "number" || type == "string" && value.length != 0;
+	    });
+
+	    let properties = object.preview ? object.preview.ownProperties : {};
+	    let propertiesLength = object.preview && object.preview.ownPropertiesLength ? object.preview.ownPropertiesLength : object.ownPropertyLength;
+
+	    if (object.preview && object.preview.safeGetterValues) {
+	      properties = Object.assign({}, properties, object.preview.safeGetterValues);
+	      propertiesLength += Object.keys(object.preview.safeGetterValues).length;
+	    }
+
+	    let indexes = this.getPropIndexes(properties, max, isInterestingProp);
+	    if (indexes.length < max && indexes.length < propertiesLength) {
+	      // There are not enough props yet. Then add uninteresting props to display them.
+	      indexes = indexes.concat(this.getPropIndexes(properties, max - indexes.length, (t, value, name) => {
+	        return !isInterestingProp(t, value, name);
+	      }));
+	    }
+
+	    const truncate = Object.keys(properties).length > max;
+	    let props = this.getProps(properties, indexes, truncate);
+	    if (truncate) {
+	      // There are some undisplayed props. Then display "more...".
+	      let objectLink = this.props.objectLink || span;
+
+	      props.push(Caption({
+	        object: objectLink({
+	          object: object
+	        }, `${ object.ownPropertyLength - max } more…`)
+	      }));
+	    }
+
+	    return props;
+	  },
+
+	  /**
+	   * Get props ordered by index.
+	   *
+	   * @param {Object} properties Props object.
+	   * @param {Array} indexes Indexes of props.
+	   * @param {Boolean} truncate true if the grip will be truncated.
+	   * @return {Array} Props.
+	   */
+	  getProps: function (properties, indexes, truncate) {
+	    let props = [];
+
+	    // Make indexes ordered by ascending.
+	    indexes.sort(function (a, b) {
+	      return a - b;
+	    });
+
+	    indexes.forEach(i => {
+	      let name = Object.keys(properties)[i];
+	      let value = this.getPropValue(properties[name]);
+
+	      props.push(PropRep(Object.assign({}, this.props, {
+	        mode: MODE.TINY,
+	        name: name,
+	        object: value,
+	        equal: ": ",
+	        delim: i !== indexes.length - 1 || truncate ? ", " : "",
+	        defaultRep: Grip
+	      })));
+	    });
+
+	    return props;
+	  },
+
+	  /**
+	   * Get the indexes of props in the object.
+	   *
+	   * @param {Object} properties Props object.
+	   * @param {Number} max The maximum length of indexes array.
+	   * @param {Function} filter Filter the props you want.
+	   * @return {Array} Indexes of interesting props in the object.
+	   */
+	  getPropIndexes: function (properties, max, filter) {
+	    let indexes = [];
+
+	    try {
+	      let i = 0;
+	      for (let name in properties) {
+	        if (indexes.length >= max) {
+	          return indexes;
+	        }
+
+	        // Type is specified in grip's "class" field and for primitive
+	        // values use typeof.
+	        let value = this.getPropValue(properties[name]);
+	        let type = value.class || typeof value;
+	        type = type.toLowerCase();
+
+	        if (filter(type, value, name)) {
+	          indexes.push(i);
+	        }
+	        i++;
+	      }
+	    } catch (err) {
+	      console.error(err);
+	    }
+	    return indexes;
+	  },
+
+	  /**
+	   * Get the actual value of a property.
+	   *
+	   * @param {Object} property
+	   * @return {Object} Value of the property.
+	   */
+	  getPropValue: function (property) {
+	    let value = property;
+	    if (typeof property === "object") {
+	      let keys = Object.keys(property);
+	      if (keys.includes("value")) {
+	        value = property.value;
+	      } else if (keys.includes("getterValue")) {
+	        value = property.getterValue;
+	      }
+	    }
+	    return value;
+	  },
+
+	  render: function () {
+	    let object = this.props.object;
+	    let props = this.safePropIterator(object, this.props.mode === MODE.LONG ? 10 : 3);
+
+	    let objectLink = this.props.objectLink || span;
+	    if (this.props.mode === MODE.TINY) {
+	      return span({ className: "objectBox objectBox-object" }, this.getTitle(object), objectLink({
+	        className: "objectLeftBrace",
+	        object: object
+	      }, ""));
+	    }
+
+	    return span({ className: "objectBox objectBox-object" }, this.getTitle(object), objectLink({
+	      className: "objectLeftBrace",
+	      object: object
+	    }, " { "), ...props, objectLink({
+	      className: "objectRightBrace",
+	      object: object
+	    }, " }"));
+	  }
+	});
+
+	// Registration
+	function supportsObject(object, type) {
+	  if (!isGrip(object)) {
+	    return false;
+	  }
+	  return object.preview && object.preview.ownProperties;
+	}
+
+	let Grip = {
+	  rep: GripRep,
+	  supportsObject: supportsObject
+	};
+
+	module.exports = Grip;
+
+/***/ },
+/* 15 */
+/***/ function(module, exports, __webpack_require__) {
+
+	const React = __webpack_require__(1);
+
+	// Shortcuts
+	const { span } = React.DOM;
+
+	/**
+	 * Renders a symbol.
+	 */
+	const SymbolRep = React.createClass({
+	  displayName: "SymbolRep",
+
+	  propTypes: {
+	    object: React.PropTypes.object.isRequired
+	  },
+
+	  render: function () {
+	    let { object } = this.props;
+	    let { name } = object;
+
+	    return span({ className: "objectBox objectBox-symbol" }, `Symbol(${ name || "" })`);
+	  }
+	});
+
+	function supportsObject(object, type) {
+	  return type == "symbol";
+	}
+
+	module.exports = {
+	  rep: SymbolRep,
+	  supportsObject: supportsObject
+	};
+
+/***/ },
+/* 16 */
+/***/ function(module, exports, __webpack_require__) {
+
+	const React = __webpack_require__(1);
+
+	// Shortcuts
+	const { span } = React.DOM;
+
+	/**
+	 * Renders a Infinity object
+	 */
+	const InfinityRep = React.createClass({
+	  displayName: "Infinity",
+
+	  render: function () {
+	    return span({ className: "objectBox objectBox-number" }, this.props.object.type);
+	  }
+	});
+
+	function supportsObject(object, type) {
+	  return type == "Infinity" || type == "-Infinity";
+	}
+
+	module.exports = {
+	  rep: InfinityRep,
+	  supportsObject: supportsObject
+	};
+
+/***/ },
+/* 17 */
+/***/ function(module, exports, __webpack_require__) {
+
+	const React = __webpack_require__(1);
+
+	// Shortcuts
+	const { span } = React.DOM;
+
+	/**
+	 * Renders a NaN object
+	 */
+	const NaNRep = React.createClass({
+	  displayName: "NaN",
+
+	  render: function () {
+	    return span({ className: "objectBox objectBox-nan" }, "NaN");
+	  }
+	});
+
+	function supportsObject(object, type) {
+	  return type == "NaN";
+	}
+
+	module.exports = {
+	  rep: NaNRep,
+	  supportsObject: supportsObject
+	};
+
+/***/ },
+/* 18 */
+/***/ function(module, exports, __webpack_require__) {
+
+	const React = __webpack_require__(1);
+
+	// Reps
+	const { isGrip } = __webpack_require__(4);
+	const StringRep = React.createFactory(__webpack_require__(7).rep);
+
+	// Shortcuts
+	const { span } = React.DOM;
+
+	/**
+	 * Renders DOM attribute
+	 */
+	let Attribute = React.createClass({
+	  displayName: "Attr",
+
+	  propTypes: {
+	    object: React.PropTypes.object.isRequired
+	  },
+
+	  getTitle: function (grip) {
+	    return grip.preview.nodeName;
+	  },
+
+	  render: function () {
+	    let object = this.props.object;
+	    let value = object.preview.value;
+	    let objectLink = this.props.objectLink || span;
+
+	    return objectLink({ className: "objectLink-Attr", object }, span({}, span({ className: "attrTitle" }, this.getTitle(object)), span({ className: "attrEqual" }, "="), StringRep({ object: value })));
+	  }
+	});
+
+	// Registration
+
+	function supportsObject(grip, type) {
+	  if (!isGrip(grip)) {
+	    return false;
+	  }
+
+	  return type == "Attr" && grip.preview;
+	}
+
+	module.exports = {
+	  rep: Attribute,
+	  supportsObject: supportsObject
+	};
+
+/***/ },
+/* 19 */
+/***/ function(module, exports, __webpack_require__) {
+
+	const React = __webpack_require__(1);
+
+	// Reps
+	const { isGrip } = __webpack_require__(4);
+
+	// Shortcuts
+	const { span } = React.DOM;
+
+	/**
+	 * Used to render JS built-in Date() object.
+	 */
+	let DateTime = React.createClass({
+	  displayName: "Date",
+
+	  propTypes: {
+	    object: React.PropTypes.object.isRequired
+	  },
+
+	  getTitle: function (grip) {
+	    if (this.props.objectLink) {
+	      return this.props.objectLink({
+	        object: grip
+	      }, grip.class + " ");
+	    }
+	    return "";
+	  },
+
+	  render: function () {
+	    let grip = this.props.object;
+	    let date;
+	    try {
+	      date = span({ className: "objectBox" }, this.getTitle(grip), span({ className: "Date" }, new Date(grip.preview.timestamp).toISOString()));
+	    } catch (e) {
+	      date = span({ className: "objectBox" }, "Invalid Date");
+	    }
+	    return date;
+	  }
+	});
+
+	// Registration
+
+	function supportsObject(grip, type) {
+	  if (!isGrip(grip)) {
+	    return false;
+	  }
+
+	  return type == "Date" && grip.preview;
+	}
+
+	module.exports = {
+	  rep: DateTime,
+	  supportsObject: supportsObject
+	};
+
+/***/ },
+/* 20 */
+/***/ function(module, exports, __webpack_require__) {
+
+	const React = __webpack_require__(1);
+
+	// Reps
+	const { isGrip, getURLDisplayString } = __webpack_require__(4);
+
+	// Shortcuts
+	const { span } = React.DOM;
+
+	/**
+	 * Renders DOM document object.
+	 */
+	let Document = React.createClass({
+	  displayName: "Document",
+
+	  propTypes: {
+	    object: React.PropTypes.object.isRequired
+	  },
+
+	  getLocation: function (grip) {
+	    let location = grip.preview.location;
+	    return location ? getURLDisplayString(location) : "";
+	  },
+
+	  getTitle: function (grip) {
+	    if (this.props.objectLink) {
+	      return span({ className: "objectBox" }, this.props.objectLink({
+	        object: grip
+	      }, grip.class + " "));
+	    }
+	    return "";
+	  },
+
+	  getTooltip: function (doc) {
+	    return doc.location.href;
+	  },
+
+	  render: function () {
+	    let grip = this.props.object;
+
+	    return span({ className: "objectBox objectBox-object" }, this.getTitle(grip), span({ className: "objectPropValue" }, this.getLocation(grip)));
+	  }
+	});
+
+	// Registration
+
+	function supportsObject(object, type) {
+	  if (!isGrip(object)) {
+	    return false;
+	  }
+
+	  return object.preview && type == "HTMLDocument";
+	}
+
+	module.exports = {
+	  rep: Document,
+	  supportsObject: supportsObject
+	};
+
+/***/ },
+/* 21 */
+/***/ function(module, exports, __webpack_require__) {
+
+	const React = __webpack_require__(1);
+
+	// Reps
+	const { isGrip } = __webpack_require__(4);
+	const rep = React.createFactory(__webpack_require__(14).rep);
+
+	/**
+	 * Renders DOM event objects.
+	 */
+	let Event = React.createClass({
+	  displayName: "event",
+
+	  propTypes: {
+	    object: React.PropTypes.object.isRequired
+	  },
+
+	  getTitle: function (props) {
+	    let preview = props.object.preview;
+	    let title = preview.type;
+
+	    if (preview.eventKind == "key" && preview.modifiers && preview.modifiers.length) {
+	      title = `${ title } ${ preview.modifiers.join("-") }`;
+	    }
+	    return title;
+	  },
+
+	  render: function () {
+	    // Use `Object.assign` to keep `this.props` without changes because:
+	    // 1. JSON.stringify/JSON.parse is slow.
+	    // 2. Immutable.js is planned for the future.
+	    let props = Object.assign({
+	      title: this.getTitle(this.props)
+	    }, this.props);
+	    props.object = Object.assign({}, this.props.object);
+	    props.object.preview = Object.assign({}, this.props.object.preview);
+
+	    props.object.preview.ownProperties = {};
+	    if (props.object.preview.target) {
+	      Object.assign(props.object.preview.ownProperties, {
+	        target: props.object.preview.target
+	      });
+	    }
+	    Object.assign(props.object.preview.ownProperties, props.object.preview.properties);
+
+	    delete props.object.preview.properties;
+	    props.object.ownPropertyLength = Object.keys(props.object.preview.ownProperties).length;
+
+	    switch (props.object.class) {
+	      case "MouseEvent":
+	        props.isInterestingProp = (type, value, name) => {
+	          return ["target", "clientX", "clientY", "layerX", "layerY"].includes(name);
+	        };
+	        break;
+	      case "KeyboardEvent":
+	        props.isInterestingProp = (type, value, name) => {
+	          return ["target", "key", "charCode", "keyCode"].includes(name);
+	        };
+	        break;
+	      case "MessageEvent":
+	        props.isInterestingProp = (type, value, name) => {
+	          return ["target", "isTrusted", "data"].includes(name);
+	        };
+	        break;
+	      default:
+	        props.isInterestingProp = (type, value, name) => {
+	          // We want to show the properties in the order they are declared.
+	          return Object.keys(props.object.preview.ownProperties).includes(name);
+	        };
+	    }
+
+	    return rep(props);
+	  }
+	});
+
+	// Registration
+	function supportsObject(grip, type) {
+	  if (!isGrip(grip)) {
+	    return false;
+	  }
+
+	  return grip.preview && grip.preview.kind == "DOMEvent";
+	}
+
+	module.exports = {
+	  rep: Event,
+	  supportsObject: supportsObject
+	};
+
+/***/ },
+/* 22 */
+/***/ function(module, exports, __webpack_require__) {
+
+	const React = __webpack_require__(1);
+
+	// Reps
+	const { isGrip, cropString } = __webpack_require__(4);
+
+	// Shortcuts
+	const { span } = React.DOM;
+
+	/**
+	 * This component represents a template for Function objects.
+	 */
+	let Func = React.createClass({
+	  displayName: "Func",
+
+	  propTypes: {
+	    object: React.PropTypes.object.isRequired
+	  },
+
+	  getTitle: function (grip) {
+	    if (this.props.objectLink) {
+	      return this.props.objectLink({
+	        object: grip
+	      }, "function ");
+	    }
+	    return "";
+	  },
+
+	  summarizeFunction: function (grip) {
+	    let name = grip.userDisplayName || grip.displayName || grip.name || "function";
+	    return cropString(name + "()", 100);
+	  },
+
+	  render: function () {
+	    let grip = this.props.object;
+
+	    return (
+	      // Set dir="ltr" to prevent function parentheses from
+	      // appearing in the wrong direction
+	      span({ dir: "ltr", className: "objectBox objectBox-function" }, this.getTitle(grip), this.summarizeFunction(grip))
+	    );
+	  }
+	});
+
+	// Registration
+
+	function supportsObject(grip, type) {
+	  if (!isGrip(grip)) {
+	    return type == "function";
+	  }
+
+	  return type == "Function";
+	}
+
+	module.exports = {
+	  rep: Func,
+	  supportsObject: supportsObject
+	};
+
+/***/ },
+/* 23 */
+/***/ function(module, exports, __webpack_require__) {
+
+	const React = __webpack_require__(1);
+	// Dependencies
+	const { isGrip } = __webpack_require__(4);
+	const PropRep = React.createFactory(__webpack_require__(13));
+	const { MODE } = __webpack_require__(2);
+	// Shortcuts
+	const { span } = React.DOM;
+
+	/**
+	 * Renders a DOM Promise object.
+	 */
+	const PromiseRep = React.createClass({
+	  displayName: "Promise",
+
+	  propTypes: {
+	    object: React.PropTypes.object.isRequired,
+	    // @TODO Change this to Object.values once it's supported in Node's version of V8
+	    mode: React.PropTypes.oneOf(Object.keys(MODE).map(key => MODE[key]))
+	  },
+
+	  getTitle: function (object) {
+	    const title = object.class;
+	    if (this.props.objectLink) {
+	      return this.props.objectLink({
+	        object: object
+	      }, title);
+	    }
+	    return title;
+	  },
+
+	  getProps: function (promiseState) {
+	    const keys = ["state"];
+	    if (Object.keys(promiseState).includes("value")) {
+	      keys.push("value");
+	    }
+
+	    return keys.map((key, i) => {
+	      return PropRep(Object.assign({}, this.props, {
+	        mode: MODE.TINY,
+	        name: `<${ key }>`,
+	        object: promiseState[key],
+	        equal: ": ",
+	        delim: i < keys.length - 1 ? ", " : ""
+	      }));
+	    });
+	  },
+
+	  render: function () {
+	    const object = this.props.object;
+	    const { promiseState } = object;
+	    let objectLink = this.props.objectLink || span;
+
+	    if (this.props.mode === MODE.TINY) {
+	      let Rep = React.createFactory(__webpack_require__(3));
+
+	      return span({ className: "objectBox objectBox-object" }, this.getTitle(object), objectLink({
+	        className: "objectLeftBrace",
+	        object: object
+	      }, " { "), Rep({ object: promiseState.state }), objectLink({
+	        className: "objectRightBrace",
+	        object: object
+	      }, " }"));
+	    }
+
+	    const props = this.getProps(promiseState);
+	    return span({ className: "objectBox objectBox-object" }, this.getTitle(object), objectLink({
+	      className: "objectLeftBrace",
+	      object: object
+	    }, " { "), ...props, objectLink({
+	      className: "objectRightBrace",
+	      object: object
+	    }, " }"));
+	  }
+	});
+
+	// Registration
+	function supportsObject(object, type) {
+	  if (!isGrip(object)) {
+	    return false;
+	  }
+	  return type === "Promise";
+	}
+
+	module.exports = {
+	  rep: PromiseRep,
+	  supportsObject: supportsObject
+	};
+
+/***/ },
+/* 24 */
+/***/ function(module, exports, __webpack_require__) {
+
+	const React = __webpack_require__(1);
+
+	// Reps
+	const { isGrip } = __webpack_require__(4);
+
+	// Shortcuts
+	const { span } = React.DOM;
+
+	/**
+	 * Renders a grip object with regular expression.
+	 */
+	let RegExp = React.createClass({
+	  displayName: "regexp",
+
+	  propTypes: {
+	    object: React.PropTypes.object.isRequired
+	  },
+
+	  getSource: function (grip) {
+	    return grip.displayString;
+	  },
+
+	  render: function () {
+	    let grip = this.props.object;
+	    let objectLink = this.props.objectLink || span;
+
+	    return span({ className: "objectBox objectBox-regexp" }, objectLink({
+	      object: grip,
+	      className: "regexpSource"
+	    }, this.getSource(grip)));
+	  }
+	});
+
+	// Registration
+
+	function supportsObject(object, type) {
+	  if (!isGrip(object)) {
+	    return false;
+	  }
+
+	  return type == "RegExp";
+	}
+
+	module.exports = {
+	  rep: RegExp,
+	  supportsObject: supportsObject
+	};
+
+/***/ },
+/* 25 */
+/***/ function(module, exports, __webpack_require__) {
+
+	const React = __webpack_require__(1);
+
+	// Reps
+	const { isGrip, getURLDisplayString } = __webpack_require__(4);
+
+	// Shortcuts
+	const DOM = React.DOM;
+
+	/**
+	 * Renders a grip representing CSSStyleSheet
+	 */
+	let StyleSheet = React.createClass({
+	  displayName: "object",
+
+	  propTypes: {
+	    object: React.PropTypes.object.isRequired
+	  },
+
+	  getTitle: function (grip) {
+	    let title = "StyleSheet ";
+	    if (this.props.objectLink) {
+	      return DOM.span({ className: "objectBox" }, this.props.objectLink({
+	        object: grip
+	      }, title));
+	    }
+	    return title;
+	  },
+
+	  getLocation: function (grip) {
+	    // Embedded stylesheets don't have URL and so, no preview.
+	    let url = grip.preview ? grip.preview.url : "";
+	    return url ? getURLDisplayString(url) : "";
+	  },
+
+	  render: function () {
+	    let grip = this.props.object;
+
+	    return DOM.span({ className: "objectBox objectBox-object" }, this.getTitle(grip), DOM.span({ className: "objectPropValue" }, this.getLocation(grip)));
+	  }
+	});
+
+	// Registration
+
+	function supportsObject(object, type) {
+	  if (!isGrip(object)) {
+	    return false;
+	  }
+
+	  return type == "CSSStyleSheet";
+	}
+
+	module.exports = {
+	  rep: StyleSheet,
+	  supportsObject: supportsObject
+	};
+
+/***/ },
+/* 26 */
+/***/ function(module, exports, __webpack_require__) {
+
+	const React = __webpack_require__(1);
+	const { isGrip, cropString, cropMultipleLines } = __webpack_require__(4);
+	const { MODE } = __webpack_require__(2);
+	const nodeConstants = __webpack_require__(27);
+
+	// Shortcuts
+	const { span } = React.DOM;
+
+	/**
+	 * Renders DOM comment node.
+	 */
+	const CommentNode = React.createClass({
+	  displayName: "CommentNode",
+
+	  propTypes: {
+	    object: React.PropTypes.object.isRequired,
+	    // @TODO Change this to Object.values once it's supported in Node's version of V8
+	    mode: React.PropTypes.oneOf(Object.keys(MODE).map(key => MODE[key]))
+	  },
+
+	  render: function () {
+	    let {
+	      object,
+	      mode = MODE.SHORT
+	    } = this.props;
+
+	    let { textContent } = object.preview;
+	    if (mode === MODE.TINY) {
+	      textContent = cropMultipleLines(textContent, 30);
+	    } else if (mode === MODE.SHORT) {
+	      textContent = cropString(textContent, 50);
+	    }
+
+	    return span({ className: "objectBox theme-comment" }, `<!-- ${ textContent } -->`);
+	  }
+	});
+
+	// Registration
+	function supportsObject(object, type) {
+	  if (!isGrip(object)) {
+	    return false;
+	  }
+	  return object.preview && object.preview.nodeType === nodeConstants.COMMENT_NODE;
+	}
+
+	module.exports = {
+	  rep: CommentNode,
+	  supportsObject: supportsObject
+	};
+
+/***/ },
+/* 27 */
+/***/ function(module, exports) {
+
+	module.exports = {
+	  ELEMENT_NODE: 1,
+	  ATTRIBUTE_NODE: 2,
+	  TEXT_NODE: 3,
+	  CDATA_SECTION_NODE: 4,
+	  ENTITY_REFERENCE_NODE: 5,
+	  ENTITY_NODE: 6,
+	  PROCESSING_INSTRUCTION_NODE: 7,
+	  COMMENT_NODE: 8,
+	  DOCUMENT_NODE: 9,
+	  DOCUMENT_TYPE_NODE: 10,
+	  DOCUMENT_FRAGMENT_NODE: 11,
+	  NOTATION_NODE: 12,
+
+	  // DocumentPosition
+	  DOCUMENT_POSITION_DISCONNECTED: 0x01,
+	  DOCUMENT_POSITION_PRECEDING: 0x02,
+	  DOCUMENT_POSITION_FOLLOWING: 0x04,
+	  DOCUMENT_POSITION_CONTAINS: 0x08,
+	  DOCUMENT_POSITION_CONTAINED_BY: 0x10,
+	  DOCUMENT_POSITION_IMPLEMENTATION_SPECIFIC: 0x20
+	};
+
+/***/ },
+/* 28 */
+/***/ function(module, exports, __webpack_require__) {
+
+	const React = __webpack_require__(1);
+
+	// Utils
+	const { isGrip } = __webpack_require__(4);
+	const { MODE } = __webpack_require__(2);
+	const nodeConstants = __webpack_require__(27);
+
+	// Shortcuts
+	const { span } = React.DOM;
+
+	/**
+	 * Renders DOM element node.
+	 */
+	const ElementNode = React.createClass({
+	  displayName: "ElementNode",
+
+	  propTypes: {
+	    object: React.PropTypes.object.isRequired,
+	    // @TODO Change this to Object.values once it's supported in Node's version of V8
+	    mode: React.PropTypes.oneOf(Object.keys(MODE).map(key => MODE[key]))
+	  },
+
+	  getElements: function (grip, mode) {
+	    let { attributes, nodeName } = grip.preview;
+	    const nodeNameElement = span({
+	      className: "tag-name theme-fg-color3"
+	    }, nodeName);
+
+	    if (mode === MODE.TINY) {
+	      let elements = [nodeNameElement];
+	      if (attributes.id) {
+	        elements.push(span({ className: "attr-name theme-fg-color2" }, `#${ attributes.id }`));
+	      }
+	      if (attributes.class) {
+	        elements.push(span({ className: "attr-name theme-fg-color2" }, attributes.class.replace(/(^\s+)|(\s+$)/g, "").split(" ").map(cls => `.${ cls }`).join("")));
+	      }
+	      return elements;
+	    }
+	    let attributeElements = Object.keys(attributes).sort(function getIdAndClassFirst(a1, a2) {
+	      if ([a1, a2].includes("id")) {
+	        return 3 * (a1 === "id" ? -1 : 1);
+	      }
+	      if ([a1, a2].includes("class")) {
+	        return 2 * (a1 === "class" ? -1 : 1);
+	      }
+
+	      // `id` and `class` excepted,
+	      // we want to keep the same order that in `attributes`.
+	      return 0;
+	    }).reduce((arr, name, i, keys) => {
+	      let value = attributes[name];
+	      let attribute = span({}, span({ className: "attr-name theme-fg-color2" }, `${ name }`), `="`, span({ className: "attr-value theme-fg-color6" }, `${ value }`), `"`);
+
+	      return arr.concat([" ", attribute]);
+	    }, []);
+
+	    return ["<", nodeNameElement, ...attributeElements, ">"];
+	  },
+
+	  render: function () {
+	    let {
+	      object,
+	      mode,
+	      onDOMNodeMouseOver,
+	      onDOMNodeMouseOut
+	    } = this.props;
+	    let elements = this.getElements(object, mode);
+	    let objectLink = this.props.objectLink || span;
+
+	    let baseConfig = { className: "objectBox objectBox-node" };
+	    if (onDOMNodeMouseOver) {
+	      Object.assign(baseConfig, {
+	        onMouseOver: _ => onDOMNodeMouseOver(object)
+	      });
+	    }
+
+	    if (onDOMNodeMouseOut) {
+	      Object.assign(baseConfig, {
+	        onMouseOut: onDOMNodeMouseOut
+	      });
+	    }
+
+	    return objectLink({ object }, span(baseConfig, ...elements));
+	  }
+	});
+
+	// Registration
+	function supportsObject(object, type) {
+	  if (!isGrip(object)) {
+	    return false;
+	  }
+	  return object.preview && object.preview.nodeType === nodeConstants.ELEMENT_NODE;
+	}
+
+	module.exports = {
+	  rep: ElementNode,
+	  supportsObject: supportsObject
+	};
+
+/***/ },
+/* 29 */
+/***/ function(module, exports, __webpack_require__) {
+
+	const React = __webpack_require__(1);
+
+	// Reps
+	const { isGrip, cropString } = __webpack_require__(4);
+	const { MODE } = __webpack_require__(2);
+
+	// Shortcuts
+	const DOM = React.DOM;
+
+	/**
+	 * Renders DOM #text node.
+	 */
+	let TextNode = React.createClass({
+	  displayName: "TextNode",
+
+	  propTypes: {
+	    object: React.PropTypes.object.isRequired,
+	    // @TODO Change this to Object.values once it's supported in Node's version of V8
+	    mode: React.PropTypes.oneOf(Object.keys(MODE).map(key => MODE[key]))
+	  },
+
+	  getTextContent: function (grip) {
+	    return cropString(grip.preview.textContent);
+	  },
+
+	  getTitle: function (grip) {
+	    const title = "#text";
+	    if (this.props.objectLink) {
+	      return this.props.objectLink({
+	        object: grip
+	      }, title);
+	    }
+	    return title;
+	  },
+
+	  render: function () {
+	    let {
+	      object: grip,
+	      mode = MODE.SHORT
+	    } = this.props;
+
+	    let baseConfig = { className: "objectBox objectBox-textNode" };
+	    if (this.props.onDOMNodeMouseOver) {
+	      Object.assign(baseConfig, {
+	        onMouseOver: _ => this.props.onDOMNodeMouseOver(grip)
+	      });
+	    }
+
+	    if (this.props.onDOMNodeMouseOut) {
+	      Object.assign(baseConfig, {
+	        onMouseOut: this.props.onDOMNodeMouseOut
+	      });
+	    }
+
+	    if (mode === MODE.TINY) {
+	      return DOM.span(baseConfig, this.getTitle(grip));
+	    }
+
+	    return DOM.span(baseConfig, this.getTitle(grip), DOM.span({ className: "nodeValue" }, " ", `"${ this.getTextContent(grip) }"`));
+	  }
+	});
+
+	// Registration
+
+	function supportsObject(grip, type) {
+	  if (!isGrip(grip)) {
+	    return false;
+	  }
+
+	  return grip.preview && grip.class == "Text";
+	}
+
+	module.exports = {
+	  rep: TextNode,
+	  supportsObject: supportsObject
+	};
+
+/***/ },
+/* 30 */
+/***/ function(module, exports, __webpack_require__) {
+
+	const React = __webpack_require__(1);
+	// Utils
+	const { isGrip } = __webpack_require__(4);
+	const { MODE } = __webpack_require__(2);
+	// Shortcuts
+	const { span } = React.DOM;
+
+	/**
+	 * Renders Error objects.
+	 */
+	const ErrorRep = React.createClass({
+	  displayName: "Error",
+
+	  propTypes: {
+	    object: React.PropTypes.object.isRequired,
+	    // @TODO Change this to Object.values once it's supported in Node's version of V8
+	    mode: React.PropTypes.oneOf(Object.keys(MODE).map(key => MODE[key]))
+	  },
+
+	  render: function () {
+	    let object = this.props.object;
+	    let preview = object.preview;
+	    let name = preview && preview.name ? preview.name : "Error";
+
+	    let content = this.props.mode === MODE.TINY ? name : `${ name }: ${ preview.message }`;
+
+	    if (preview.stack && this.props.mode !== MODE.TINY) {
+	      /*
+	       * Since Reps are used in the JSON Viewer, we can't localize
+	       * the "Stack trace" label (defined in debugger.properties as
+	       * "variablesViewErrorStacktrace" property), until Bug 1317038 lands.
+	       */
+	      content = `${ content }\nStack trace:\n${ preview.stack }`;
+	    }
+
+	    let objectLink = this.props.objectLink || span;
+	    return objectLink({ object, className: "objectBox-stackTrace" }, span({}, content));
+	  }
+	});
+
+	// Registration
+	function supportsObject(object, type) {
+	  if (!isGrip(object)) {
+	    return false;
+	  }
+	  return object.preview && type === "Error";
+	}
+
+	module.exports = {
+	  rep: ErrorRep,
+	  supportsObject: supportsObject
+	};
+
+/***/ },
+/* 31 */
+/***/ function(module, exports, __webpack_require__) {
+
+	const React = __webpack_require__(1);
+	const { MODE } = __webpack_require__(2);
+
+	// Reps
+	const { isGrip, getURLDisplayString } = __webpack_require__(4);
+
+	// Shortcuts
+	const DOM = React.DOM;
+
+	/**
+	 * Renders a grip representing a window.
+	 */
+	let Window = React.createClass({
+	  displayName: "Window",
+
+	  propTypes: {
+	    object: React.PropTypes.object.isRequired,
+	    mode: React.PropTypes.oneOf(Object.keys(MODE).map(key => MODE[key]))
+	  },
+
+	  getTitle: function (grip) {
+	    if (this.props.objectLink) {
+	      return DOM.span({ className: "objectBox" }, this.props.objectLink({
+	        object: grip
+	      }, grip.class + " "));
+	    }
+	    return "";
+	  },
+
+	  getLocation: function (grip) {
+	    return getURLDisplayString(grip.preview.url);
+	  },
+
+	  getDisplayValue: function (grip) {
+	    if (this.props.mode === MODE.TINY) {
+	      return grip.isGlobal ? "Global" : "Window";
+	    }
+
+	    return this.getLocation(grip);
+	  },
+
+	  render: function () {
+	    let grip = this.props.object;
+
+	    return DOM.span({ className: "objectBox objectBox-Window" }, this.getTitle(grip), DOM.span({ className: "objectPropValue" }, this.getDisplayValue(grip)));
+	  }
+	});
+
+	// Registration
+
+	function supportsObject(object, type) {
+	  if (!isGrip(object)) {
+	    return false;
+	  }
+
+	  return object.preview && type == "Window";
+	}
+
+	module.exports = {
+	  rep: Window,
+	  supportsObject: supportsObject
+	};
+
+/***/ },
+/* 32 */
+/***/ function(module, exports, __webpack_require__) {
+
+	const React = __webpack_require__(1);
+
+	// Reps
+	const { isGrip } = __webpack_require__(4);
+
+	// Shortcuts
+	const { span } = React.DOM;
+
+	/**
+	 * Renders a grip object with textual data.
+	 */
+	let ObjectWithText = React.createClass({
+	  displayName: "ObjectWithText",
+
+	  propTypes: {
+	    object: React.PropTypes.object.isRequired
+	  },
+
+	  getTitle: function (grip) {
+	    if (this.props.objectLink) {
+	      return span({ className: "objectBox" }, this.props.objectLink({
+	        object: grip
+	      }, this.getType(grip) + " "));
+	    }
+	    return "";
+	  },
+
+	  getType: function (grip) {
+	    return grip.class;
+	  },
+
+	  getDescription: function (grip) {
+	    return "\"" + grip.preview.text + "\"";
+	  },
+
+	  render: function () {
+	    let grip = this.props.object;
+	    return span({ className: "objectBox objectBox-" + this.getType(grip) }, this.getTitle(grip), span({ className: "objectPropValue" }, this.getDescription(grip)));
+	  }
+	});
+
+	// Registration
+
+	function supportsObject(grip, type) {
+	  if (!isGrip(grip)) {
+	    return false;
+	  }
+
+	  return grip.preview && grip.preview.kind == "ObjectWithText";
+	}
+
+	module.exports = {
+	  rep: ObjectWithText,
+	  supportsObject: supportsObject
+	};
+
+/***/ },
+/* 33 */
+/***/ function(module, exports, __webpack_require__) {
+
+	const React = __webpack_require__(1);
+
+	// Reps
+	const { isGrip, getURLDisplayString } = __webpack_require__(4);
+
+	// Shortcuts
+	const { span } = React.DOM;
+
+	/**
+	 * Renders a grip object with URL data.
+	 */
+	let ObjectWithURL = React.createClass({
+	  displayName: "ObjectWithURL",
+
+	  propTypes: {
+	    object: React.PropTypes.object.isRequired
+	  },
+
+	  getTitle: function (grip) {
+	    if (this.props.objectLink) {
+	      return span({ className: "objectBox" }, this.props.objectLink({
+	        object: grip
+	      }, this.getType(grip) + " "));
+	    }
+	    return "";
+	  },
+
+	  getType: function (grip) {
+	    return grip.class;
+	  },
+
+	  getDescription: function (grip) {
+	    return getURLDisplayString(grip.preview.url);
+	  },
+
+	  render: function () {
+	    let grip = this.props.object;
+	    return span({ className: "objectBox objectBox-" + this.getType(grip) }, this.getTitle(grip), span({ className: "objectPropValue" }, this.getDescription(grip)));
+	  }
+	});
+
+	// Registration
+
+	function supportsObject(grip, type) {
+	  if (!isGrip(grip)) {
+	    return false;
+	  }
+
+	  return grip.preview && grip.preview.kind == "ObjectWithURL";
+	}
+
+	module.exports = {
+	  rep: ObjectWithURL,
+	  supportsObject: supportsObject
+	};
+
+/***/ },
+/* 34 */
+/***/ function(module, exports, __webpack_require__) {
+
+	const React = __webpack_require__(1);
+	const { isGrip } = __webpack_require__(4);
+	const Caption = React.createFactory(__webpack_require__(11));
+	const { MODE } = __webpack_require__(2);
+
+	// Shortcuts
+	const { span } = React.DOM;
+
+	/**
+	 * Renders an array. The array is enclosed by left and right bracket
+	 * and the max number of rendered items depends on the current mode.
+	 */
+	let GripArray = React.createClass({
+	  displayName: "GripArray",
+
+	  propTypes: {
+	    object: React.PropTypes.object.isRequired,
+	    // @TODO Change this to Object.values once it's supported in Node's version of V8
+	    mode: React.PropTypes.oneOf(Object.keys(MODE).map(key => MODE[key])),
+	    provider: React.PropTypes.object
+	  },
+
+	  getLength: function (grip) {
+	    if (!grip.preview) {
+	      return 0;
+	    }
+
+	    return grip.preview.length || grip.preview.childNodesLength || 0;
+	  },
+
+	  getTitle: function (object, context) {
+	    let objectLink = this.props.objectLink || span;
+	    if (this.props.mode !== MODE.TINY) {
+	      return objectLink({
+	        object: object
+	      }, object.class + " ");
+	    }
+	    return "";
+	  },
+
+	  getPreviewItems: function (grip) {
+	    if (!grip.preview) {
+	      return null;
+	    }
+
+	    return grip.preview.items || grip.preview.childNodes || null;
+	  },
+
+	  arrayIterator: function (grip, max) {
+	    let items = [];
+	    const gripLength = this.getLength(grip);
+
+	    if (!gripLength) {
+	      return items;
+	    }
+
+	    const previewItems = this.getPreviewItems(grip);
+	    if (!previewItems) {
+	      return items;
+	    }
+
+	    let delim;
+	    // number of grip preview items is limited to 10, but we may have more
+	    // items in grip-array.
+	    let delimMax = gripLength > previewItems.length ? previewItems.length : previewItems.length - 1;
+	    let provider = this.props.provider;
+
+	    for (let i = 0; i < previewItems.length && i < max; i++) {
+	      try {
+	        let itemGrip = previewItems[i];
+	        let value = provider ? provider.getValue(itemGrip) : itemGrip;
+
+	        delim = i == delimMax ? "" : ", ";
+
+	        items.push(GripArrayItem(Object.assign({}, this.props, {
+	          object: value,
+	          delim: delim
+	        })));
+	      } catch (exc) {
+	        items.push(GripArrayItem(Object.assign({}, this.props, {
+	          object: exc,
+	          delim: delim
+	        })));
+	      }
+	    }
+	    if (previewItems.length > max || gripLength > previewItems.length) {
+	      let objectLink = this.props.objectLink || span;
+	      let leftItemNum = gripLength - max > 0 ? gripLength - max : gripLength - previewItems.length;
+	      items.push(Caption({
+	        object: objectLink({
+	          object: this.props.object
+	        }, leftItemNum + " more…")
+	      }));
+	    }
+
+	    return items;
+	  },
+
+	  render: function () {
+	    let {
+	      object,
+	      mode = MODE.SHORT
+	    } = this.props;
+
+	    let items;
+	    let brackets;
+	    let needSpace = function (space) {
+	      return space ? { left: "[ ", right: " ]" } : { left: "[", right: "]" };
+	    };
+
+	    if (mode === MODE.TINY) {
+	      let objectLength = this.getLength(object);
+	      let isEmpty = objectLength === 0;
+	      items = [span({ className: "length" }, isEmpty ? "" : objectLength)];
+	      brackets = needSpace(false);
+	    } else {
+	      let max = mode === MODE.SHORT ? 3 : 10;
+	      items = this.arrayIterator(object, max);
+	      brackets = needSpace(items.length > 0);
+	    }
+
+	    let objectLink = this.props.objectLink || span;
+	    let title = this.getTitle(object);
+
+	    return span({
+	      className: "objectBox objectBox-array" }, title, objectLink({
+	      className: "arrayLeftBracket",
+	      object: object
+	    }, brackets.left), ...items, objectLink({
+	      className: "arrayRightBracket",
+	      object: object
+	    }, brackets.right), span({
+	      className: "arrayProperties",
+	      role: "group" }));
+	  }
+	});
+
+	/**
+	 * Renders array item. Individual values are separated by
+	 * a delimiter (a comma by default).
+	 */
+	let GripArrayItem = React.createFactory(React.createClass({
+	  displayName: "GripArrayItem",
+
+	  propTypes: {
+	    delim: React.PropTypes.string
+	  },
+
+	  render: function () {
+	    let Rep = React.createFactory(__webpack_require__(3));
+
+	    return span({}, Rep(Object.assign({}, this.props, {
+	      mode: MODE.TINY
+	    })), this.props.delim);
+	  }
+	}));
+
+	function supportsObject(grip, type) {
+	  if (!isGrip(grip)) {
+	    return false;
+	  }
+
+	  return grip.preview && (grip.preview.kind == "ArrayLike" || type === "DocumentFragment");
+	}
+
+	module.exports = {
+	  rep: GripArray,
+	  supportsObject: supportsObject
+	};
+
+/***/ },
+/* 35 */
+/***/ function(module, exports, __webpack_require__) {
+
+	const React = __webpack_require__(1);
+	const { isGrip } = __webpack_require__(4);
+	const Caption = React.createFactory(__webpack_require__(11));
+	const PropRep = React.createFactory(__webpack_require__(13));
+	const { MODE } = __webpack_require__(2);
+	// Shortcuts
+	const { span } = React.DOM;
+	/**
+	 * Renders an map. A map is represented by a list of its
+	 * entries enclosed in curly brackets.
+	 */
+	const GripMap = React.createClass({
+	  displayName: "GripMap",
+
+	  propTypes: {
+	    object: React.PropTypes.object,
+	    // @TODO Change this to Object.values once it's supported in Node's version of V8
+	    mode: React.PropTypes.oneOf(Object.keys(MODE).map(key => MODE[key]))
+	  },
+
+	  getTitle: function (object) {
+	    let title = object && object.class ? object.class : "Map";
+	    if (this.props.objectLink) {
+	      return this.props.objectLink({
+	        object: object
+	      }, title);
+	    }
+	    return title;
+	  },
+
+	  safeEntriesIterator: function (object, max) {
+	    max = typeof max === "undefined" ? 3 : max;
+	    try {
+	      return this.entriesIterator(object, max);
+	    } catch (err) {
+	      console.error(err);
+	    }
+	    return [];
+	  },
+
+	  entriesIterator: function (object, max) {
+	    // Entry filter. Show only interesting entries to the user.
+	    let isInterestingEntry = this.props.isInterestingEntry || ((type, value) => {
+	      return type == "boolean" || type == "number" || type == "string" && value.length != 0;
+	    });
+
+	    let mapEntries = object.preview && object.preview.entries ? object.preview.entries : [];
+
+	    let indexes = this.getEntriesIndexes(mapEntries, max, isInterestingEntry);
+	    if (indexes.length < max && indexes.length < mapEntries.length) {
+	      // There are not enough entries yet, so we add uninteresting entries.
+	      indexes = indexes.concat(this.getEntriesIndexes(mapEntries, max - indexes.length, (t, value, name) => {
+	        return !isInterestingEntry(t, value, name);
+	      }));
+	    }
+
+	    let entries = this.getEntries(mapEntries, indexes);
+	    if (entries.length < mapEntries.length) {
+	      // There are some undisplayed entries. Then display "more…".
+	      let objectLink = this.props.objectLink || span;
+
+	      entries.push(Caption({
+	        key: "more",
+	        object: objectLink({
+	          object: object
+	        }, `${ mapEntries.length - max } more…`)
+	      }));
+	    }
+
+	    return entries;
+	  },
+
+	  /**
+	   * Get entries ordered by index.
+	   *
+	   * @param {Array} entries Entries array.
+	   * @param {Array} indexes Indexes of entries.
+	   * @return {Array} Array of PropRep.
+	   */
+	  getEntries: function (entries, indexes) {
+	    // Make indexes ordered by ascending.
+	    indexes.sort(function (a, b) {
+	      return a - b;
+	    });
+
+	    return indexes.map((index, i) => {
+	      let [key, entryValue] = entries[index];
+	      let value = entryValue.value !== undefined ? entryValue.value : entryValue;
+
+	      return PropRep({
+	        // key,
+	        name: key,
+	        equal: ": ",
+	        object: value,
+	        // Do not add a trailing comma on the last entry
+	        // if there won't be a "more..." item.
+	        delim: i < indexes.length - 1 || indexes.length < entries.length ? ", " : "",
+	        mode: MODE.TINY,
+	        objectLink: this.props.objectLink
+	      });
+	    });
+	  },
+
+	  /**
+	   * Get the indexes of entries in the map.
+	   *
+	   * @param {Array} entries Entries array.
+	   * @param {Number} max The maximum length of indexes array.
+	   * @param {Function} filter Filter the entry you want.
+	   * @return {Array} Indexes of filtered entries in the map.
+	   */
+	  getEntriesIndexes: function (entries, max, filter) {
+	    return entries.reduce((indexes, [key, entry], i) => {
+	      if (indexes.length < max) {
+	        let value = entry && entry.value !== undefined ? entry.value : entry;
+	        // Type is specified in grip's "class" field and for primitive
+	        // values use typeof.
+	        let type = (value && value.class ? value.class : typeof value).toLowerCase();
+
+	        if (filter(type, value, key)) {
+	          indexes.push(i);
+	        }
+	      }
+
+	      return indexes;
+	    }, []);
+	  },
+
+	  render: function () {
+	    let object = this.props.object;
+	    let props = this.safeEntriesIterator(object, this.props.mode === MODE.LONG ? 10 : 3);
+
+	    let objectLink = this.props.objectLink || span;
+	    if (this.props.mode === MODE.TINY) {
+	      return span({ className: "objectBox objectBox-object" }, this.getTitle(object), objectLink({
+	        className: "objectLeftBrace",
+	        object: object
+	      }, ""));
+	    }
+
+	    return span({ className: "objectBox objectBox-object" }, this.getTitle(object), objectLink({
+	      className: "objectLeftBrace",
+	      object: object
+	    }, " { "), props, objectLink({
+	      className: "objectRightBrace",
+	      object: object
+	    }, " }"));
+	  }
+	});
+
+	function supportsObject(grip, type) {
+	  if (!isGrip(grip)) {
+	    return false;
+	  }
+	  return grip.preview && grip.preview.kind == "MapLike";
+	}
+
+	module.exports = {
+	  rep: GripMap,
+	  supportsObject: supportsObject
+	};
+
+/***/ }
+/******/ ])
+});
+;
+//# sourceMappingURL=reps.js.map
\ No newline at end of file
new file mode 100644
--- /dev/null
+++ b/devtools/client/shared/components/reps/test/mochitest/.eslintrc.js
@@ -0,0 +1,6 @@
+"use strict";
+
+module.exports = {
+  // Extend from the shared list of defined globals for mochitests.
+  "extends": "../../../../../../.eslintrc.mochitests.js"
+};
new file mode 100644
--- /dev/null
+++ b/devtools/client/shared/components/reps/test/mochitest/chrome.ini
@@ -0,0 +1,33 @@
+[DEFAULT]
+support-files =
+  head.js
+
+[test_reps_array.html]
+[test_reps_attribute.html]
+[test_reps_comment-node.html]
+[test_reps_date-time.html]
+[test_reps_document.html]
+[test_reps_element-node.html]
+[test_reps_error.html]
+[test_reps_event.html]
+[test_reps_failure.html]
+[test_reps_function.html]
+[test_reps_grip.html]
+[test_reps_grip-array.html]
+[test_reps_grip-map.html]
+[test_reps_infinity.html]
+[test_reps_long-string.html]
+[test_reps_nan.html]
+[test_reps_null.html]
+[test_reps_number.html]
+[test_reps_object.html]
+[test_reps_object-with-text.html]
+[test_reps_object-with-url.html]
+[test_reps_promise.html]
+[test_reps_regexp.html]
+[test_reps_string.html]
+[test_reps_stylesheet.html]
+[test_reps_symbol.html]
+[test_reps_text-node.html]
+[test_reps_undefined.html]
+[test_reps_window.html]
\ No newline at end of file
copy from devtools/client/shared/components/test/mochitest/head.js
copy to devtools/client/shared/components/reps/test/mochitest/head.js
--- a/devtools/client/shared/components/test/mochitest/head.js
+++ b/devtools/client/shared/components/reps/test/mochitest/head.js
@@ -6,183 +6,29 @@
 "use strict";
 
 var { classes: Cc, interfaces: Ci, utils: Cu, results: Cr } = Components;
 
 var { require } = Cu.import("resource://devtools/shared/Loader.jsm", {});
 var { Assert } = require("resource://testing-common/Assert.jsm");
 var { gDevTools } = require("devtools/client/framework/devtools");
 var { BrowserLoader } = Cu.import("resource://devtools/client/shared/browser-loader.js", {});
-var promise = require("promise");
-var defer = require("devtools/shared/defer");
-var Services = require("Services");
-var { DebuggerServer } = require("devtools/server/main");
-var { DebuggerClient } = require("devtools/shared/client/main");
-var DevToolsUtils = require("devtools/shared/DevToolsUtils");
 var flags = require("devtools/shared/flags");
 var { Task } = require("devtools/shared/task");
-var { TargetFactory } = require("devtools/client/framework/target");
-var { Toolbox } = require("devtools/client/framework/toolbox");
 
 flags.testing = true;
 var { require: browserRequire } = BrowserLoader({
   baseURI: "resource://devtools/client/shared/",
   window
 });
 
 let ReactDOM = browserRequire("devtools/client/shared/vendor/react-dom");
 let React = browserRequire("devtools/client/shared/vendor/react");
 var TestUtils = React.addons.TestUtils;
 
-var EXAMPLE_URL = "http://example.com/browser/browser/devtools/shared/test/";
-
-function forceRender(comp) {
-  return setState(comp, {})
-    .then(() => setState(comp, {}));
-}
-
-// All tests are asynchronous.
-SimpleTest.waitForExplicitFinish();
-
-function onNextAnimationFrame(fn) {
-  return () =>
-    requestAnimationFrame(() =>
-      requestAnimationFrame(fn));
-}
-
-function setState(component, newState) {
-  return new Promise(resolve => {
-    component.setState(newState, onNextAnimationFrame(resolve));
-  });
-}
-
-function dumpn(msg) {
-  dump(`SHARED-COMPONENTS-TEST: ${msg}\n`);
-}
-
-/**
- * Tree
- */
-
-var TEST_TREE_INTERFACE = {
-  getParent: x => TEST_TREE.parent[x],
-  getChildren: x => TEST_TREE.children[x],
-  renderItem: (x, depth, focused) => "-".repeat(depth) + x + ":" + focused + "\n",
-  getRoots: () => ["A", "M"],
-  getKey: x => "key-" + x,
-  itemHeight: 1,
-  onExpand: x => TEST_TREE.expanded.add(x),
-  onCollapse: x => TEST_TREE.expanded.delete(x),
-  isExpanded: x => TEST_TREE.expanded.has(x),
-};
-
-function isRenderedTree(actual, expectedDescription, msg) {
-  const expected = expectedDescription.map(x => x + "\n").join("");
-  dumpn(`Expected tree:\n${expected}`);
-  dumpn(`Actual tree:\n${actual}`);
-  is(actual, expected, msg);
-}
-
-// Encoding of the following tree/forest:
-//
-// A
-// |-- B
-// |   |-- E
-// |   |   |-- K
-// |   |   `-- L
-// |   |-- F
-// |   `-- G
-// |-- C
-// |   |-- H
-// |   `-- I
-// `-- D
-//     `-- J
-// M
-// `-- N
-//     `-- O
-var TEST_TREE = {
-  children: {
-    A: ["B", "C", "D"],
-    B: ["E", "F", "G"],
-    C: ["H", "I"],
-    D: ["J"],
-    E: ["K", "L"],
-    F: [],
-    G: [],
-    H: [],
-    I: [],
-    J: [],
-    K: [],
-    L: [],
-    M: ["N"],
-    N: ["O"],
-    O: []
-  },
-  parent: {
-    A: null,
-    B: "A",
-    C: "A",
-    D: "A",
-    E: "B",
-    F: "B",
-    G: "B",
-    H: "C",
-    I: "C",
-    J: "D",
-    K: "E",
-    L: "E",
-    M: null,
-    N: "M",
-    O: "N"
-  },
-  expanded: new Set(),
-};
-
-/**
- * Frame
- */
-function checkFrameString({
-  el, file, line, column, source, functionName, shouldLink, tooltip
-}) {
-  let $ = selector => el.querySelector(selector);
-
-  let $func = $(".frame-link-function-display-name");
-  let $source = $(".frame-link-source");
-  let $sourceInner = $(".frame-link-source-inner");
-  let $filename = $(".frame-link-filename");
-  let $line = $(".frame-link-line");
-
-  is($filename.textContent, file, "Correct filename");
-  is(el.getAttribute("data-line"), line ? `${line}` : null, "Expected `data-line` found");
-  is(el.getAttribute("data-column"),
-     column ? `${column}` : null, "Expected `data-column` found");
-  is($sourceInner.getAttribute("title"), tooltip, "Correct tooltip");
-  is($source.tagName, shouldLink ? "A" : "SPAN", "Correct linkable status");
-  if (shouldLink) {
-    is($source.getAttribute("href"), source, "Correct source");
-  }
-
-  if (line != null) {
-    let lineText = `:${line}`;
-    if (column != null) {
-      lineText += `:${column}`;
-    }
-
-    is($line.textContent, lineText, "Correct line number");
-  } else {
-    ok(!$line, "Should not have an element for `line`");
-  }
-
-  if (functionName != null) {
-    is($func.textContent, functionName, "Correct function name");
-  } else {
-    ok(!$func, "Should not have an element for `functionName`");
-  }
-}
-
 function renderComponent(component, props) {
   const el = React.createElement(component, props, {});
   // By default, renderIntoDocument() won't work for stateless components, but
   // it will work if the stateless component is wrapped in a stateful one.
   // See https://github.com/facebook/react/issues/4839
   const wrappedEl = React.DOM.span({}, [el]);
   const renderedComponent = TestUtils.renderIntoDocument(wrappedEl);
   return ReactDOM.findDOMNode(renderedComponent).children[0];
rename from devtools/client/shared/components/test/mochitest/test_reps_array.html
rename to devtools/client/shared/components/reps/test/mochitest/test_reps_array.html
rename from devtools/client/shared/components/test/mochitest/test_reps_attribute.html
rename to devtools/client/shared/components/reps/test/mochitest/test_reps_attribute.html
rename from devtools/client/shared/components/test/mochitest/test_reps_comment-node.html
rename to devtools/client/shared/components/reps/test/mochitest/test_reps_comment-node.html
rename from devtools/client/shared/components/test/mochitest/test_reps_date-time.html
rename to devtools/client/shared/components/reps/test/mochitest/test_reps_date-time.html
rename from devtools/client/shared/components/test/mochitest/test_reps_document.html
rename to devtools/client/shared/components/reps/test/mochitest/test_reps_document.html
rename from devtools/client/shared/components/test/mochitest/test_reps_element-node.html
rename to devtools/client/shared/components/reps/test/mochitest/test_reps_element-node.html
rename from devtools/client/shared/components/test/mochitest/test_reps_error.html
rename to devtools/client/shared/components/reps/test/mochitest/test_reps_error.html
rename from devtools/client/shared/components/test/mochitest/test_reps_event.html
rename to devtools/client/shared/components/reps/test/mochitest/test_reps_event.html
rename from devtools/client/shared/components/test/mochitest/test_reps_failure.html
rename to devtools/client/shared/components/reps/test/mochitest/test_reps_failure.html
rename from devtools/client/shared/components/test/mochitest/test_reps_function.html
rename to devtools/client/shared/components/reps/test/mochitest/test_reps_function.html
rename from devtools/client/shared/components/test/mochitest/test_reps_grip-array.html
rename to devtools/client/shared/components/reps/test/mochitest/test_reps_grip-array.html
rename from devtools/client/shared/components/test/mochitest/test_reps_grip-map.html
rename to devtools/client/shared/components/reps/test/mochitest/test_reps_grip-map.html
rename from devtools/client/shared/components/test/mochitest/test_reps_grip.html
rename to devtools/client/shared/components/reps/test/mochitest/test_reps_grip.html
rename from devtools/client/shared/components/test/mochitest/test_reps_infinity.html
rename to devtools/client/shared/components/reps/test/mochitest/test_reps_infinity.html
rename from devtools/client/shared/components/test/mochitest/test_reps_long-string.html
rename to devtools/client/shared/components/reps/test/mochitest/test_reps_long-string.html
rename from devtools/client/shared/components/test/mochitest/test_reps_nan.html
rename to devtools/client/shared/components/reps/test/mochitest/test_reps_nan.html
rename from devtools/client/shared/components/test/mochitest/test_reps_null.html
rename to devtools/client/shared/components/reps/test/mochitest/test_reps_null.html
rename from devtools/client/shared/components/test/mochitest/test_reps_number.html
rename to devtools/client/shared/components/reps/test/mochitest/test_reps_number.html
rename from devtools/client/shared/components/test/mochitest/test_reps_object-with-text.html
rename to devtools/client/shared/components/reps/test/mochitest/test_reps_object-with-text.html
rename from devtools/client/shared/components/test/mochitest/test_reps_object-with-url.html
rename to devtools/client/shared/components/reps/test/mochitest/test_reps_object-with-url.html
rename from devtools/client/shared/components/test/mochitest/test_reps_object.html
rename to devtools/client/shared/components/reps/test/mochitest/test_reps_object.html
rename from devtools/client/shared/components/test/mochitest/test_reps_promise.html
rename to devtools/client/shared/components/reps/test/mochitest/test_reps_promise.html
rename from devtools/client/shared/components/test/mochitest/test_reps_regexp.html
rename to devtools/client/shared/components/reps/test/mochitest/test_reps_regexp.html
rename from devtools/client/shared/components/test/mochitest/test_reps_string.html
rename to devtools/client/shared/components/reps/test/mochitest/test_reps_string.html
rename from devtools/client/shared/components/test/mochitest/test_reps_stylesheet.html
rename to devtools/client/shared/components/reps/test/mochitest/test_reps_stylesheet.html
rename from devtools/client/shared/components/test/mochitest/test_reps_symbol.html
rename to devtools/client/shared/components/reps/test/mochitest/test_reps_symbol.html
rename from devtools/client/shared/components/test/mochitest/test_reps_text-node.html
rename to devtools/client/shared/components/reps/test/mochitest/test_reps_text-node.html
rename from devtools/client/shared/components/test/mochitest/test_reps_undefined.html
rename to devtools/client/shared/components/reps/test/mochitest/test_reps_undefined.html
rename from devtools/client/shared/components/test/mochitest/test_reps_window.html
rename to devtools/client/shared/components/reps/test/mochitest/test_reps_window.html
--- a/devtools/client/shared/components/test/mochitest/chrome.ini
+++ b/devtools/client/shared/components/test/mochitest/chrome.ini
@@ -2,45 +2,16 @@
 support-files =
   head.js
 
 [test_frame_01.html]
 [test_HSplitBox_01.html]
 [test_notification_box_01.html]
 [test_notification_box_02.html]
 [test_notification_box_03.html]
-[test_reps_array.html]
-[test_reps_attribute.html]
-[test_reps_comment-node.html]
-[test_reps_date-time.html]
-[test_reps_document.html]
-[test_reps_element-node.html]
-[test_reps_error.html]
-[test_reps_event.html]
-[test_reps_failure.html]
-[test_reps_function.html]
-[test_reps_grip.html]
-[test_reps_grip-array.html]
-[test_reps_grip-map.html]
-[test_reps_infinity.html]
-[test_reps_long-string.html]
-[test_reps_nan.html]
-[test_reps_null.html]
-[test_reps_number.html]
-[test_reps_object.html]
-[test_reps_object-with-text.html]
-[test_reps_object-with-url.html]
-[test_reps_promise.html]
-[test_reps_regexp.html]
-[test_reps_string.html]
-[test_reps_stylesheet.html]
-[test_reps_symbol.html]
-[test_reps_text-node.html]
-[test_reps_undefined.html]
-[test_reps_window.html]
 [test_sidebar_toggle.html]
 [test_stack-trace.html]
 [test_tabs_accessibility.html]
 [test_tabs_menu.html]
 [test_tree_01.html]
 [test_tree_02.html]
 [test_tree_03.html]
 [test_tree_04.html]
--- a/devtools/client/shared/components/test/mochitest/head.js
+++ b/devtools/client/shared/components/test/mochitest/head.js
@@ -189,23 +189,8 @@ function renderComponent(component, prop
 }
 
 function shallowRenderComponent(component, props) {
   const el = React.createElement(component, props);
   const renderer = TestUtils.createRenderer();
   renderer.render(el, {});
   return renderer.getRenderOutput();
 }
-
-/**
- * Test that a rep renders correctly across different modes.
- */
-function testRepRenderModes(modeTests, testName, componentUnderTest, gripStub) {
-  modeTests.forEach(({mode, expectedOutput, message}) => {
-    const modeString = typeof mode === "undefined" ? "no mode" : mode.toString();
-    if (!message) {
-      message = `${testName}: ${modeString} renders correctly.`;
-    }
-
-    const rendered = renderComponent(componentUnderTest.rep, { object: gripStub, mode });
-    is(rendered.textContent, expectedOutput, message);
-  });
-}
--- a/devtools/client/shared/components/tree/tree-view.js
+++ b/devtools/client/shared/components/tree/tree-view.js
@@ -44,17 +44,17 @@ define(function (require, exports, modul
    *
    * Complete tree decorator interface:
    * var TreeDecorator = {
    *   getRowClass: function(object);
    *   getCellClass: function(object, colId);
    *   getHeaderClass: function(colId);
    *   renderValue: function(object, colId);
    *   renderRow: function(object);
-   *   renderCelL: function(object, colId);
+   *   renderCell: function(object, colId);
    *   renderLabelCell: function(object);
    * }
    */
   let TreeView = React.createClass({
     displayName: "TreeView",
 
     // The only required property (not set by default) is the input data
     // object that is used to puputate the tree.
@@ -73,17 +73,17 @@ define(function (require, exports, modul
       }).isRequired,
       // Tree decorator (see also the interface above)
       decorator: PropTypes.shape({
         getRowClass: PropTypes.func,
         getCellClass: PropTypes.func,
         getHeaderClass: PropTypes.func,
         renderValue: PropTypes.func,
         renderRow: PropTypes.func,
-        renderCelL: PropTypes.func,
+        renderCell: PropTypes.func,
         renderLabelCell: PropTypes.func,
       }),
       // Custom tree row (node) renderer
       renderRow: PropTypes.func,
       // Custom cell renderer
       renderCell: PropTypes.func,
       // Custom value renderef
       renderValue: PropTypes.func,
--- a/devtools/client/shared/telemetry.js
+++ b/devtools/client/shared/telemetry.js
@@ -157,16 +157,22 @@ Telemetry.prototype = {
       histogram: "DEVTOOLS_MENU_EYEDROPPER_OPENED_COUNT",
     },
     pickereyedropper: {
       histogram: "DEVTOOLS_PICKER_EYEDROPPER_OPENED_COUNT",
     },
     toolbareyedropper: {
       histogram: "DEVTOOLS_TOOLBAR_EYEDROPPER_OPENED_COUNT",
     },
+    copyuniquecssselector: {
+      histogram: "DEVTOOLS_COPY_UNIQUE_CSS_SELECTOR_OPENED_COUNT",
+    },
+    copyfullcssselector: {
+      histogram: "DEVTOOLS_COPY_FULL_CSS_SELECTOR_OPENED_COUNT",
+    },
     developertoolbar: {
       histogram: "DEVTOOLS_DEVELOPERTOOLBAR_OPENED_COUNT",
       timerHistogram: "DEVTOOLS_DEVELOPERTOOLBAR_TIME_ACTIVE_SECONDS"
     },
     aboutdebugging: {
       histogram: "DEVTOOLS_ABOUTDEBUGGING_OPENED_COUNT",
       timerHistogram: "DEVTOOLS_ABOUTDEBUGGING_TIME_ACTIVE_SECONDS"
     },
--- a/devtools/client/shared/vendor/react-dom.js
+++ b/devtools/client/shared/vendor/react-dom.js
@@ -131,28 +131,32 @@
 
   /**
    * Try to access the containing toolbox XUL document, but only if all of the iframes
    * in the heirarchy are XUL documents. Events dispatch differently in the case of all
    * privileged XUL documents. Events that fire in an iframe propagate up to the parent
    * frame. This does not happen when HTML is in the mix. Only return the toolbox if
    * it matches the proper case of a XUL iframe inside of a XUL document.
    *
+   * In addition to the XUL case, if the panel uses the toolbox's ReactDOM instance,
+   * this patch needs to be applied as well. This is the case for the inspector.
+   *
    * @param {HTMLElement} node - The DOM node inside of an iframe.
    * @return {XULDocument|null} The toolbox.xul document, or null.
    */
   function getToolboxDocIfXulOnly(node) {
     // This execution context doesn't know about XULDocuments, so don't get the toolbox.
     if (typeof XULDocument !== "function") {
       return null;
     }
 
     let doc = node.ownerDocument;
+    const inspectorUrl = "chrome://devtools/content/inspector/inspector.xhtml";
 
-    while (doc instanceof XULDocument) {
+    while (doc instanceof XULDocument || doc.location.href === inspectorUrl) {
       const {frameElement} = doc.defaultView;
 
       if (!frameElement) {
         // We're at the root element, and no toolbox was found.
         return null;
       }
 
       doc = frameElement.parentElement.ownerDocument;
--- a/devtools/client/themes/netmonitor.css
+++ b/devtools/client/themes/netmonitor.css
@@ -1096,22 +1096,16 @@
   width: 100%;
 }
 
 .treeTable .textbox-input:focus {
   outline: 0;
   box-shadow: var(--theme-focus-box-shadow-textbox);
 }
 
-.panel-container {
-  /* FIXME: To avoid the issue which panel exceeds visible range,
-   * we set view heigh - 24px * 2 for toolbox height + tabs height */
-  height: calc(100vh - 48px);
-}
-
 .panel-container,
 .properties-view {
   display: flex;
   flex-direction: column;
   flex-grow: 1;
 }
 
 .properties-view .searchbox-section {
--- a/devtools/client/webconsole/net/components/net-info-body.js
+++ b/devtools/client/webconsole/net/components/net-info-body.js
@@ -1,15 +1,15 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 "use strict";
 
 const React = require("devtools/client/shared/vendor/react");
-const { createFactories } = require("devtools/client/shared/components/reps/rep-utils");
+const { createFactories } = require("devtools/client/shared/components/reps/load-reps");
 const { Tabs, TabPanel } = createFactories(require("devtools/client/shared/components/tabs/tabs"));
 
 // Network
 const HeadersTab = React.createFactory(require("./headers-tab"));
 const ResponseTab = React.createFactory(require("./response-tab"));
 const ParamsTab = React.createFactory(require("./params-tab"));
 const CookiesTab = React.createFactory(require("./cookies-tab"));
 const PostTab = React.createFactory(require("./post-tab"));
--- a/devtools/client/webconsole/net/components/post-tab.js
+++ b/devtools/client/webconsole/net/components/post-tab.js
@@ -1,20 +1,19 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 "use strict";
 
 const React = require("devtools/client/shared/vendor/react");
 
-// Reps
-const { createFactories, parseURLEncodedText } = require("devtools/client/shared/components/reps/rep-utils");
 const TreeView = React.createFactory(require("devtools/client/shared/components/tree/tree-view"));
-const { Rep } = createFactories(require("devtools/client/shared/components/reps/rep"));
-const { MODE } = require("devtools/client/shared/components/reps/constants");
+
+const { REPS, MODE, parseURLEncodedText } = require("devtools/client/shared/components/reps/load-reps");
+const Rep = React.createFactory(REPS.Rep);
 
 // Network
 const NetInfoParams = React.createFactory(require("./net-info-params"));
 const NetInfoGroupList = React.createFactory(require("./net-info-group-list"));
 const Spinner = React.createFactory(require("./spinner"));
 const SizeLimit = React.createFactory(require("./size-limit"));
 const NetUtils = require("../utils/net");
 const Json = require("../utils/json");
--- a/devtools/client/webconsole/net/components/response-tab.js
+++ b/devtools/client/webconsole/net/components/response-tab.js
@@ -1,20 +1,19 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 "use strict";
 
 const React = require("devtools/client/shared/vendor/react");
 
 // Reps
-const { createFactories } = require("devtools/client/shared/components/reps/rep-utils");
 const TreeView = React.createFactory(require("devtools/client/shared/components/tree/tree-view"));
-const { Rep } = createFactories(require("devtools/client/shared/components/reps/rep"));
-const { MODE } = require("devtools/client/shared/components/reps/constants");
+const { REPS, MODE } = require("devtools/client/shared/components/reps/load-reps");
+const Rep = React.createFactory(REPS.Rep);
 
 // Network
 const SizeLimit = React.createFactory(require("./size-limit"));
 const NetInfoGroupList = React.createFactory(require("./net-info-group-list"));
 const Spinner = React.createFactory(require("./spinner"));
 const Json = require("../utils/json");
 const NetUtils = require("../utils/net");
 
--- a/devtools/client/webconsole/net/main.js
+++ b/devtools/client/webconsole/net/main.js
@@ -21,17 +21,17 @@ const { loadSheet } = require("sdk/style
 // Localization
 const {LocalizationHelper} = require("devtools/shared/l10n");
 const L10N = new LocalizationHelper("devtools/client/locales/netmonitor.properties");
 
 // Stylesheets
 var styleSheets = [
   "resource://devtools/client/jsonview/css/toolbar.css",
   "resource://devtools/client/shared/components/tree/tree-view.css",
-  "resource://devtools/client/shared/components/reps/reps.css",
+  "resource://devtools/client/shared/components/reps.css",
   "resource://devtools/client/webconsole/net/net-request.css",
   "resource://devtools/client/webconsole/net/components/size-limit.css",
   "resource://devtools/client/webconsole/net/components/net-info-body.css",
   "resource://devtools/client/webconsole/net/components/net-info-group.css",
   "resource://devtools/client/webconsole/net/components/net-info-params.css",
   "resource://devtools/client/webconsole/net/components/response-tab.css"
 ];
 
--- a/devtools/client/webconsole/net/net-request.js
+++ b/devtools/client/webconsole/net/net-request.js
@@ -3,17 +3,17 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 "use strict";
 
 // React
 const React = require("devtools/client/shared/vendor/react");
 const ReactDOM = require("devtools/client/shared/vendor/react-dom");
 
 // Reps
-const { parseURLParams } = require("devtools/client/shared/components/reps/rep-utils");
+const { parseURLParams } = require("devtools/client/shared/components/reps/load-reps");
 
 // Network
 const { cancelEvent, isLeftClick } = require("./utils/events");
 const NetInfoBody = React.createFactory(require("./components/net-info-body"));
 const DataProvider = require("./data-provider");
 
 // Constants
 const XHTML_NS = "http://www.w3.org/1999/xhtml";
--- a/devtools/client/webconsole/new-console-output/components/console-table.js
+++ b/devtools/client/webconsole/new-console-output/components/console-table.js
@@ -6,18 +6,18 @@
 const {
   createClass,
   createFactory,
   DOM: dom,
   PropTypes
 } = require("devtools/client/shared/vendor/react");
 const { ObjectClient } = require("devtools/shared/client/main");
 const actions = require("devtools/client/webconsole/new-console-output/actions/messages");
-const {l10n} = require("devtools/client/webconsole/new-console-output/utils/messages");
-const { MODE } = require("devtools/client/shared/components/reps/constants");
+const { l10n } = require("devtools/client/webconsole/new-console-output/utils/messages");
+const { MODE } = require("devtools/client/shared/components/reps/load-reps");
 const GripMessageBody = createFactory(require("devtools/client/webconsole/new-console-output/components/grip-message-body"));
 
 const TABLE_ROW_MAX_ITEMS = 1000;
 const TABLE_COLUMN_MAX_ITEMS = 10;
 
 const ConsoleTable = createClass({
 
   displayName: "ConsoleTable",
--- a/devtools/client/webconsole/new-console-output/components/grip-message-body.js
+++ b/devtools/client/webconsole/new-console-output/components/grip-message-body.js
@@ -12,22 +12,23 @@ if (typeof define === "undefined") {
   require("amd-loader");
 }
 
 // React
 const {
   createFactory,
   PropTypes
 } = require("devtools/client/shared/vendor/react");
-const { createFactories } = require("devtools/client/shared/components/reps/rep-utils");
-const { Rep } = createFactories(require("devtools/client/shared/components/reps/rep"));
-const StringRep = createFactories(require("devtools/client/shared/components/reps/string").StringRep).rep;
+
 const VariablesViewLink = createFactory(require("devtools/client/webconsole/new-console-output/components/variables-view-link"));
-const { Grip } = require("devtools/client/shared/components/reps/grip");
-const { MODE } = require("devtools/client/shared/components/reps/constants");
+
+const { REPS, MODE, createFactories } = require("devtools/client/shared/components/reps/load-reps");
+const Rep = createFactory(REPS.Rep);
+const Grip = REPS.Grip;
+const StringRep = createFactories(REPS.StringRep).rep;
 
 GripMessageBody.displayName = "GripMessageBody";
 
 GripMessageBody.propTypes = {
   grip: PropTypes.oneOfType([
     PropTypes.string,
     PropTypes.number,
     PropTypes.object,
--- a/devtools/server/actors/inspector.js
+++ b/devtools/server/actors/inspector.js
@@ -146,16 +146,17 @@ loader.lazyGetter(this, "DOMParser", fun
 
 loader.lazyGetter(this, "eventListenerService", function () {
   return Cc["@mozilla.org/eventlistenerservice;1"]
            .getService(Ci.nsIEventListenerService);
 });
 
 loader.lazyRequireGetter(this, "CssLogic", "devtools/server/css-logic", true);
 loader.lazyRequireGetter(this, "findCssSelector", "devtools/shared/inspector/css-logic", true);
+loader.lazyRequireGetter(this, "getCssPath", "devtools/shared/inspector/css-logic", true);
 
 /**
  * We only send nodeValue up to a certain size by default.  This stuff
  * controls that size.
  */
 exports.DEFAULT_VALUE_SUMMARY_LENGTH = 50;
 var gValueSummaryLength = exports.DEFAULT_VALUE_SUMMARY_LENGTH;
 
@@ -641,16 +642,28 @@ var NodeActor = exports.NodeActor = prot
   getUniqueSelector: function () {
     if (Cu.isDeadWrapper(this.rawNode)) {
       return "";
     }
     return findCssSelector(this.rawNode);
   },
 
   /**
+   * Get the full CSS path for this node.
+   *
+   * @return {String} A CSS selector with a part for the node and each of its ancestors.
+   */
+  getCssPath: function () {
+    if (Cu.isDeadWrapper(this.rawNode)) {
+      return "";
+    }
+    return getCssPath(this.rawNode);
+  },
+
+  /**
    * Scroll the selected node into view.
    */
   scrollIntoView: function () {
     this.rawNode.scrollIntoView(true);
   },
 
   /**
    * Get the node's image data if any (for canvas and img nodes).
--- a/devtools/server/actors/root.js
+++ b/devtools/server/actors/root.js
@@ -142,16 +142,18 @@ RootActor.prototype = {
     // Whether the style rule actor implements the modifySelector method
     // that modifies the rule's selector
     selectorEditable: true,
     // Whether the page style actor implements the addNewRule method that
     // adds new rules to the page
     addNewRule: true,
     // Whether the dom node actor implements the getUniqueSelector method
     getUniqueSelector: true,
+    // Whether the dom node actor implements the getCssPath method
+    getCssPath: true,
     // Whether the director scripts are supported
     directorScripts: true,
     // Whether the debugger server supports
     // blackboxing/pretty-printing (not supported in Fever Dream yet)
     noBlackBoxing: false,
     noPrettyPrinting: false,
     // Whether the page style actor implements the getUsedFontFaces method
     // that returns the font faces used on a node
--- a/devtools/server/css-logic.js
+++ b/devtools/server/css-logic.js
@@ -29,18 +29,16 @@
 
 "use strict";
 
 const { Cc, Ci, Cu } = require("chrome");
 const DevToolsUtils = require("devtools/shared/DevToolsUtils");
 const nodeConstants = require("devtools/shared/dom-node-constants");
 const {l10n, isContentStylesheet, shortSource, FILTER, STATUS} = require("devtools/shared/inspector/css-logic");
 
-loader.lazyRequireGetter(this, "CSSLexer", "devtools/shared/css/lexer");
-
 /**
  * @param {function} isInherited A function that determines if the CSS property
  *                   is inherited.
  */
 function CssLogic(isInherited) {
   // The cache of examined CSS properties.
   this._isInherited = isInherited;
   this._propertyInfos = {};
--- a/devtools/shared/gcli/commands/addon.js
+++ b/devtools/shared/gcli/commands/addon.js
@@ -109,24 +109,31 @@ var items = [
         data: [ "dictionary", "extension", "locale", "plugin", "theme", "all" ]
       },
       defaultValue: "all",
       description: l10n.lookup("addonListTypeDesc")
     }],
     exec: function (args, context) {
       let types = (args.type === "all") ? null : [ args.type ];
       return getAddonsByTypes(types).then(addons => {
+        // Remove all hidden add-ons.
+        addons = addons.filter(addon => {
+          return !addon.hidden;
+        });
+
+        // Change the add-ons array to something we can work with.
         addons = addons.map(function (addon) {
           return {
             name: addon.name,
             version: addon.version,
             isActive: addon.isActive,
             pendingOperations: pendingOperations(addon)
           };
         });
+
         return { addons: addons, type: args.type };
       });
     }
   },
   {
     item: "converter",
     from: "addonsInfo",
     to: "view",
--- a/devtools/shared/inspector/css-logic.js
+++ b/devtools/shared/inspector/css-logic.js
@@ -405,8 +405,58 @@ function findCssSelector(ele) {
     index = positionInNodeList(ele, ele.parentNode.children) + 1;
     selector = findCssSelector(ele.parentNode) + " > " +
       tagName + ":nth-child(" + index + ")";
   }
 
   return selector;
 }
 exports.findCssSelector = findCssSelector;
+
+/**
+ * Get the full CSS path for a given element.
+ * @returns a string that can be used as a CSS selector for the element. It might not
+ * match the element uniquely. It does however, represent the full path from the root
+ * node to the element.
+ */
+function getCssPath(ele) {
+  ele = getRootBindingParent(ele);
+  const document = ele.ownerDocument;
+  if (!document || !document.contains(ele)) {
+    throw new Error("getCssPath received element not inside document");
+  }
+
+  const getElementSelector = element => {
+    if (!element.localName) {
+      return "";
+    }
+
+    let label = element.nodeName == element.nodeName.toUpperCase()
+                ? element.localName.toLowerCase()
+                : element.localName;
+
+    if (element.id) {
+      label += "#" + element.id;
+    }
+
+    if (element.classList) {
+      for (let cl of element.classList) {
+        label += "." + cl;
+      }
+    }
+
+    return label;
+  };
+
+  let paths = [];
+
+  while (ele) {
+    if (!ele || ele.nodeType !== Node.ELEMENT_NODE) {
+      break;
+    }
+
+    paths.splice(0, 0, getElementSelector(ele));
+    ele = ele.parentNode;
+  }
+
+  return paths.length ? paths.join(" ") : "";
+}
+exports.getCssPath = getCssPath;
--- a/devtools/shared/specs/node.js
+++ b/devtools/shared/specs/node.js
@@ -32,16 +32,22 @@ const nodeSpec = generateActorSpec({
       response: {}
     },
     getUniqueSelector: {
       request: {},
       response: {
         value: RetVal("string")
       }
     },
+    getCssPath: {
+      request: {},
+      response: {
+        value: RetVal("string")
+      }
+    },
     scrollIntoView: {
       request: {},
       response: {}
     },
     getImageData: {
       request: {maxDim: Arg(0, "nullable:number")},
       response: RetVal("imageData")
     },
--- a/devtools/shared/tests/mochitest/chrome.ini
+++ b/devtools/shared/tests/mochitest/chrome.ini
@@ -1,8 +1,9 @@
 [DEFAULT]
 tags = devtools
 skip-if = os == 'android'
 
-[test_eventemitter_basic.html]
+[test_css-logic-getCssPath.html]
+[test_css-logic.html]
 [test_devtools_extensions.html]
+[test_eventemitter_basic.html]
 skip-if = os == 'linux' && debug # Bug 1205739
-[test_css-logic.html]
new file mode 100644
--- /dev/null
+++ b/devtools/shared/tests/mochitest/test_css-logic-getCssPath.html
@@ -0,0 +1,121 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1323700
+-->
+<head>
+  <meta charset="utf-8">
+  <title>Test for Bug 1323700</title>
+
+  <script type="application/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 type="application/javascript;version=1.8">
+const { classes: Cc, interfaces: Ci, utils: Cu } = Components;
+
+let { require } = Cu.import("resource://devtools/shared/Loader.jsm", {});
+const CssLogic = require("devtools/shared/inspector/css-logic");
+
+var _tests = [];
+function addTest(test) {
+  _tests.push(test);
+}
+
+function runNextTest() {
+  if (_tests.length == 0) {
+    SimpleTest.finish()
+    return;
+  }
+  _tests.shift()();
+}
+
+window.onload = function() {
+  SimpleTest.waitForExplicitFinish();
+  runNextTest();
+}
+
+addTest(function getCssPathForUnattachedElement() {
+  var unattached = document.createElement("div");
+  unattached.id = "unattached";
+  try {
+    CssLogic.getCssPath(unattached);
+    ok(false, "Unattached node did not throw")
+  } catch(e) {
+    ok(e, "Unattached node throws an exception");
+  }
+
+  var unattachedChild = document.createElement("div");
+  unattached.appendChild(unattachedChild);
+  try {
+    CssLogic.getCssPath(unattachedChild);
+    ok(false, "Unattached child node did not throw")
+  } catch(e) {
+    ok(e, "Unattached child node throws an exception");
+  }
+
+  var unattachedBody = document.createElement("body");
+  try {
+    CssLogic.getCssPath(unattachedBody);
+    ok(false, "Unattached body node did not throw")
+  } catch(e) {
+    ok(e, "Unattached body node throws an exception");
+  }
+
+  runNextTest();
+});
+
+addTest(function cssPathHasOneStepForEachAncestor() {
+  for (let el of [...document.querySelectorAll('*')]) {
+    let splitPath = CssLogic.getCssPath(el).split(" ");
+
+    let expectedNbOfParts = 0;
+    var parent = el.parentNode;
+    while (parent) {
+      expectedNbOfParts ++;
+      parent = parent.parentNode;
+    }
+
+    is(splitPath.length, expectedNbOfParts, "There are enough parts in the full path");
+  }
+
+  runNextTest();
+});
+
+addTest(function getCssPath() {
+  let data = [{
+    selector: "#id",
+    path: "html body div div div.class div#id"
+  }, {
+    selector: "html",
+    path: "html"
+  }, {
+    selector: "body",
+    path: "html body"
+  }, {
+    selector: ".c1.c2.c3",
+    path: "html body span.c1.c2.c3"
+  }, {
+    selector: "#i",
+    path: "html body span#i.c1.c2"
+  }];
+
+  for (let {selector, path} of data) {
+    let node = document.querySelector(selector);
+    is (CssLogic.getCssPath(node), path, `Full css path is correct for ${selector}`);
+  }
+
+  runNextTest();
+});
+  </script>
+</head>
+<body>
+  <div>
+    <div>
+      <div class="class">
+        <div id="id"></div>
+      </div>
+    </div>
+  </div>
+  <span class="c1 c2 c3"></span>
+  <span id="i" class="c1 c2"></span>
+</body>
+</html>
--- a/dom/canvas/test/reftest/filters/reftest-stylo.list
+++ b/dom/canvas/test/reftest/filters/reftest-stylo.list
@@ -1,13 +1,13 @@
 # DO NOT EDIT! This is a auto-generated temporary list for Stylo testing
 default-preferences pref(canvas.filters.enabled,true)
 
 fails == default-color.html default-color.html
-fails == drop-shadow.html drop-shadow.html
+# == drop-shadow.html drop-shadow.html
 fails == drop-shadow-transformed.html drop-shadow-transformed.html
 fails == global-alpha.html global-alpha.html
 fails == global-composite-operation.html global-composite-operation.html
 fails == liveness.html liveness.html
 fails == multiple-drop-shadows.html multiple-drop-shadows.html
 fails == shadow.html shadow.html
 fails == subregion-fill-paint.html subregion-fill-paint.html
 fails == subregion-stroke-paint.html subregion-stroke-paint.html
--- a/dom/encoding/test/reftest/reftest-stylo.list
+++ b/dom/encoding/test/reftest/reftest-stylo.list
@@ -1,6 +1,6 @@
 # DO NOT EDIT! This is a auto-generated temporary list for Stylo testing
-fails == bug863728-1.html bug863728-1.html
+# == bug863728-1.html bug863728-1.html
 fails == bug863728-2.html bug863728-2.html
 == bug863728-3.html bug863728-3.html
-fails == bug945215-1.html bug945215-1.html
+# == bug945215-1.html bug945215-1.html
 fails == bug945215-2.html bug945215-2.html
--- a/dom/html/HTMLMediaElement.cpp
+++ b/dom/html/HTMLMediaElement.cpp
@@ -90,17 +90,17 @@
 #include "mozilla/dom/MediaErrorBinding.h"
 #include "mozilla/dom/VideoTrack.h"
 #include "mozilla/dom/VideoTrackList.h"
 #include "mozilla/dom/TextTrack.h"
 #include "nsIContentPolicy.h"
 #include "mozilla/Telemetry.h"
 #include "DecoderDoctorDiagnostics.h"
 #include "DecoderTraits.h"
-#include "MediaContentType.h"
+#include "MediaContainerType.h"
 
 #include "ImageContainer.h"
 #include "nsRange.h"
 #include <algorithm>
 #include <cmath>
 
 static mozilla::LazyLogModule gMediaElementLog("nsMediaElement");
 static mozilla::LazyLogModule gMediaElementEventsLog("nsMediaElementEvents");
@@ -4468,21 +4468,21 @@ void HTMLMediaElement::UnbindFromTree(bo
   RunInStableState(task);
 }
 
 /* static */
 CanPlayStatus
 HTMLMediaElement::GetCanPlay(const nsAString& aType,
                              DecoderDoctorDiagnostics* aDiagnostics)
 {
-  Maybe<MediaContentType> contentType = MakeMediaContentType(aType);
-  if (!contentType) {
+  Maybe<MediaContainerType> containerType = MakeMediaContainerType(aType);
+  if (!containerType) {
     return CANPLAY_NO;
   }
-  return DecoderTraits::CanHandleContentType(*contentType, aDiagnostics);
+  return DecoderTraits::CanHandleContainerType(*containerType, aDiagnostics);
 }
 
 NS_IMETHODIMP
 HTMLMediaElement::CanPlayType(const nsAString& aType, nsAString& aResult)
 {
   DecoderDoctorDiagnostics diagnostics;
   CanPlayStatus canPlay = GetCanPlay(aType, &diagnostics);
   diagnostics.StoreFormatDiagnostics(
--- a/dom/html/reftests/reftest-stylo.list
+++ b/dom/html/reftests/reftest-stylo.list
@@ -7,59 +7,59 @@ fails == 41464-1a.html 41464-1a.html
 fails == 41464-1b.html 41464-1b.html
 fails == 52019-1.html 52019-1.html
 fails == 82711-1.html 82711-1.html
 fails == 82711-2.html 82711-2.html
 # == 82711-1-ref.html 82711-1-ref.html
 == 468263-1a.html 468263-1a.html
 == 468263-1b.html 468263-1b.html
 == 468263-1c.html 468263-1c.html
-fails == 468263-1d.html 468263-1d.html
+# == 468263-1d.html 468263-1d.html
 # == 468263-2.html 468263-2.html
 # == 468263-2.html 468263-2.html
 fails == 484200-1.html 484200-1.html
-fails == 485377.html 485377.html
+# == 485377.html 485377.html
 == 557840.html 557840.html
 == 560059-video-dimensions.html 560059-video-dimensions.html
 fails == 573322-quirks.html 573322-quirks.html
 fails == 573322-no-quirks.html 573322-no-quirks.html
 fails == 596455-1a.html 596455-1a.html
 fails == 596455-1b.html 596455-1b.html
 fails == 596455-2a.html 596455-2a.html
 fails == 596455-2b.html 596455-2b.html
 fails == 610935.html 610935.html
-fails == 649134-1.html 649134-1.html
-fails == 649134-2.html 649134-2.html
+== 649134-1.html 649134-1.html
+# == 649134-2.html 649134-2.html
 fails == 741776-1.vtt 741776-1.vtt
 
 # == bug448564-1_malformed.html bug448564-1_malformed.html
 # == bug448564-1_malformed.html bug448564-1_malformed.html
 
-fails == bug448564-4a.html bug448564-4a.html
-fails == bug502168-1_malformed.html bug502168-1_malformed.html
+# == bug448564-4a.html bug448564-4a.html
+# == bug502168-1_malformed.html bug502168-1_malformed.html
 
-fails == responsive-image-load-shortcircuit.html responsive-image-load-shortcircuit.html
+# == responsive-image-load-shortcircuit.html responsive-image-load-shortcircuit.html
 == image-load-shortcircuit-1.html image-load-shortcircuit-1.html
 == image-load-shortcircuit-2.html image-load-shortcircuit-2.html
 
 # Test that image documents taken into account CSS properties like
 # image-orientation when determining the size of the image.
 # (Fuzzy necessary due to pixel-wise comparison of different JPEGs.
 # The vast majority of the fuzziness comes from Linux and WinXP.)
 # == bug917595-iframe-1.html bug917595-iframe-1.html
 fails == bug917595-exif-rotated.jpg bug917595-exif-rotated.jpg
 
 # Test support for SVG-as-image in <picture> elements.
 == bug1106522-1.html bug1106522-1.html
 == bug1106522-2.html bug1106522-2.html
 
 fails == href-attr-change-restyles.html href-attr-change-restyles.html
-fails == figure.html figure.html
+# == figure.html figure.html
 fails == pre-1.html pre-1.html
-fails == table-border-1.html table-border-1.html
+# == table-border-1.html table-border-1.html
 # == table-border-2.html table-border-2.html
 # == table-border-2.html table-border-2.html
 
 # Test imageset is using permissions.default.image
 # pref(permissions.default.image,1) HTTP == bug1196784-with-srcset.html bug1196784-with-srcset.html
 # pref(permissions.default.image,2) HTTP == bug1196784-with-srcset.html bug1196784-with-srcset.html
 
 # Test video with rotation information can be rotated.
--- a/dom/media/ADTSDecoder.cpp
+++ b/dom/media/ADTSDecoder.cpp
@@ -1,17 +1,17 @@
 /* -*- 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 "ADTSDecoder.h"
 #include "ADTSDemuxer.h"
-#include "MediaContentType.h"
+#include "MediaContainerType.h"
 #include "MediaDecoderStateMachine.h"
 #include "MediaFormatReader.h"
 #include "PDMFactory.h"
 
 namespace mozilla {
 
 MediaDecoder*
 ADTSDecoder::Clone(MediaDecoderOwner* aOwner)
@@ -34,23 +34,23 @@ ADTSDecoder::CreateStateMachine()
 ADTSDecoder::IsEnabled()
 {
   RefPtr<PDMFactory> platform = new PDMFactory();
   return platform->SupportsMimeType(NS_LITERAL_CSTRING("audio/mp4a-latm"),
                                     /* DecoderDoctorDiagnostics* */ nullptr);
 }
 
 /* static */ bool
-ADTSDecoder::IsSupportedType(const MediaContentType& aContentType)
+ADTSDecoder::IsSupportedType(const MediaContainerType& aContainerType)
 {
-  if (aContentType.Type() == MEDIAMIMETYPE("audio/aac")
-      || aContentType.Type() == MEDIAMIMETYPE("audio/aacp")
-      || aContentType.Type() == MEDIAMIMETYPE("audio/x-aac")) {
+  if (aContainerType.Type() == MEDIAMIMETYPE("audio/aac")
+      || aContainerType.Type() == MEDIAMIMETYPE("audio/aacp")
+      || aContainerType.Type() == MEDIAMIMETYPE("audio/x-aac")) {
     return
       IsEnabled()
-      && (aContentType.ExtendedType().Codecs().IsEmpty()
-          || aContentType.ExtendedType().Codecs().AsString().EqualsASCII("aac"));
+      && (aContainerType.ExtendedType().Codecs().IsEmpty()
+          || aContainerType.ExtendedType().Codecs().AsString().EqualsASCII("aac"));
   }
 
   return false;
 }
 
 } // namespace mozilla
--- a/dom/media/ADTSDecoder.h
+++ b/dom/media/ADTSDecoder.h
@@ -6,27 +6,27 @@
 
 #ifndef ADTS_DECODER_H_
 #define ADTS_DECODER_H_
 
 #include "MediaDecoder.h"
 
 namespace mozilla {
 
-class MediaContentType;
+class MediaContainerType;
 
 class ADTSDecoder : public MediaDecoder
 {
 public:
   // MediaDecoder interface.
   explicit ADTSDecoder(MediaDecoderOwner* aOwner) : MediaDecoder(aOwner) {}
   MediaDecoder* Clone(MediaDecoderOwner* aOwner) override;
   MediaDecoderStateMachine* CreateStateMachine() override;
 
   // Returns true if the ADTS backend is pref'ed on, and we're running on a
   // platform that is likely to have decoders for the format.
   static bool IsEnabled();
-  static bool IsSupportedType(const MediaContentType& aContentType);
+  static bool IsSupportedType(const MediaContainerType& aContainerType);
 };
 
 } // namespace mozilla
 
 #endif // !ADTS_DECODER_H_
--- a/dom/media/Benchmark.cpp
+++ b/dom/media/Benchmark.cpp
@@ -39,18 +39,19 @@ VP9Benchmark::IsVP9DecodeFast()
 #else
   bool hasPref = Preferences::HasUserValue(sBenchmarkFpsPref);
   uint32_t hadRecentUpdate = Preferences::GetUint(sBenchmarkFpsVersionCheck, 0U);
 
   if (!sHasRunTest && (!hasPref || hadRecentUpdate != sBenchmarkVersionID)) {
     sHasRunTest = true;
 
     RefPtr<WebMDemuxer> demuxer =
-      new WebMDemuxer(new BufferMediaResource(sWebMSample, sizeof(sWebMSample), nullptr,
-                                              NS_LITERAL_CSTRING("video/webm")));
+      new WebMDemuxer(
+        new BufferMediaResource(sWebMSample, sizeof(sWebMSample), nullptr,
+                                MediaContainerType(MEDIAMIMETYPE("video/webm"))));
     RefPtr<Benchmark> estimiser =
       new Benchmark(demuxer,
                     {
                       Preferences::GetInt("media.benchmark.frames", 300), // frames to measure
                       1, // start benchmarking after decoding this frame.
                       8, // loop after decoding that many frames.
                       TimeDuration::FromMilliseconds(
                         Preferences::GetUint("media.benchmark.timeout", 1000))
--- a/dom/media/BufferMediaResource.h
+++ b/dom/media/BufferMediaResource.h
@@ -18,22 +18,22 @@ namespace mozilla {
 // on top of it.  The Read implementation involves copying memory, which is
 // unfortunate, but the MediaResource interface mandates that.
 class BufferMediaResource : public MediaResource
 {
 public:
   BufferMediaResource(const uint8_t* aBuffer,
                       uint32_t aLength,
                       nsIPrincipal* aPrincipal,
-                      const nsACString& aContentType) :
+                      const MediaContainerType& aContainerType) :
     mBuffer(aBuffer),
     mLength(aLength),
     mOffset(0),
     mPrincipal(aPrincipal),
-    mContentType(aContentType)
+    mContainerType(aContainerType)
   {
   }
 
 protected:
   virtual ~BufferMediaResource()
   {
   }
 
@@ -100,40 +100,40 @@ private:
   nsresult GetCachedRanges(MediaByteRangeSet& aRanges) override
   {
     aRanges += MediaByteRange(0, int64_t(mLength));
     return NS_OK;
   }
 
   bool IsTransportSeekable() override { return true; }
 
-  const nsCString& GetContentType() const override
+  const MediaContainerType& GetContentType() const override
   {
-    return mContentType;
+    return mContainerType;
   }
 
   size_t SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const override
   {
     // Not owned:
     // - mBuffer
     // - mPrincipal
     size_t size = MediaResource::SizeOfExcludingThis(aMallocSizeOf);
-    size += mContentType.SizeOfExcludingThisIfUnshared(aMallocSizeOf);
+    size += mContainerType.SizeOfExcludingThis(aMallocSizeOf);
 
     return size;
   }
 
   size_t SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const override
   {
     return aMallocSizeOf(this) + SizeOfExcludingThis(aMallocSizeOf);
   }
 
 private:
   const uint8_t * mBuffer;
   uint32_t mLength;
   uint32_t mOffset;
   nsCOMPtr<nsIPrincipal> mPrincipal;
-  const nsCString mContentType;
+  const MediaContainerType mContainerType;
 };
 
 } // namespace mozilla
 
 #endif
--- a/dom/media/DecoderTraits.cpp
+++ b/dom/media/DecoderTraits.cpp
@@ -1,16 +1,16 @@
 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 /* vim:set ts=2 sw=2 sts=2 et cindent: */
 /* 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 "DecoderTraits.h"
-#include "MediaContentType.h"
+#include "MediaContainerType.h"
 #include "MediaDecoder.h"
 #include "nsMimeTypes.h"
 #include "mozilla/Preferences.h"
 #include "mozilla/Telemetry.h"
 
 #include "OggDecoder.h"
 #include "OggDemuxer.h"
 
@@ -46,73 +46,73 @@
 
 #include "nsPluginHost.h"
 #include "MediaPrefs.h"
 
 namespace mozilla
 {
 
 static bool
-IsHttpLiveStreamingType(const MediaContentType& aType)
+IsHttpLiveStreamingType(const MediaContainerType& aType)
 {
   return // For m3u8.
          // https://tools.ietf.org/html/draft-pantos-http-live-streaming-19#section-10
          aType.Type() == MEDIAMIMETYPE("application/vnd.apple.mpegurl")
          // Some sites serve these as the informal m3u type.
          || aType.Type() == MEDIAMIMETYPE("application/x-mpegurl")
          || aType.Type() == MEDIAMIMETYPE("audio/x-mpegurl");
 }
 
 #ifdef MOZ_ANDROID_OMX
 static bool
-IsAndroidMediaType(const MediaContentType& aType)
+IsAndroidMediaType(const MediaContainerType& aType)
 {
   if (!MediaDecoder::IsAndroidMediaPluginEnabled()) {
     return false;
   }
 
   return aType.Type() == MEDIAMIMETYPE("audio/mpeg")
          || aType.Type() == MEDIAMIMETYPE("audio/mp4")
          || aType.Type() == MEDIAMIMETYPE("video/mp4")
          || aType.Type() == MEDIAMIMETYPE("video/x-m4v");
 }
 #endif
 
 /* static */ bool
-DecoderTraits::IsMP4SupportedType(const MediaContentType& aType,
+DecoderTraits::IsMP4SupportedType(const MediaContainerType& aType,
                                   DecoderDoctorDiagnostics* aDiagnostics)
 {
 #ifdef MOZ_FMP4
   return MP4Decoder::IsSupportedType(aType, aDiagnostics);
 #else
   return false;
 #endif
 }
 
 static
 CanPlayStatus
-CanHandleCodecsType(const MediaContentType& aType,
+CanHandleCodecsType(const MediaContainerType& aType,
                     DecoderDoctorDiagnostics* aDiagnostics)
 {
   // We should have been given a codecs string, though it may be empty.
   MOZ_ASSERT(aType.ExtendedType().HaveCodecs());
 
-  // Content type with the the MIME type, no codecs.
-  const MediaContentType mimeType(aType.Type());
+  // Container type with the MIME type, no codecs.
+  const MediaContainerType mimeType(aType.Type());
 
   if (OggDecoder::IsSupportedType(mimeType)) {
     if (OggDecoder::IsSupportedType(aType)) {
       return CANPLAY_YES;
     } else {
       // We can only reach this position if a particular codec was requested,
       // ogg is supported and working: the codec must be invalid.
       return CANPLAY_NO;
     }
   }
-  if (WaveDecoder::IsSupportedType(MediaContentType(mimeType))) {
+  if (WaveDecoder::IsSupportedType(MediaContainerType(mimeType))) {
     if (WaveDecoder::IsSupportedType(aType)) {
       return CANPLAY_YES;
     } else {
       // We can only reach this position if a particular codec was requested,
       // ogg is supported and working: the codec must be invalid.
       return CANPLAY_NO;
     }
   }
@@ -167,34 +167,34 @@ CanHandleCodecsType(const MediaContentTy
     return CANPLAY_NO;
   }
 
   return CANPLAY_YES;
 }
 
 static
 CanPlayStatus
-CanHandleMediaType(const MediaContentType& aType,
+CanHandleMediaType(const MediaContainerType& aType,
                    DecoderDoctorDiagnostics* aDiagnostics)
 {
   MOZ_ASSERT(NS_IsMainThread());
 
   if (IsHttpLiveStreamingType(aType)) {
     Telemetry::Accumulate(Telemetry::MEDIA_HLS_CANPLAY_REQUESTED, true);
   }
 
   if (aType.ExtendedType().HaveCodecs()) {
     CanPlayStatus result = CanHandleCodecsType(aType, aDiagnostics);
     if (result == CANPLAY_NO || result == CANPLAY_YES) {
       return result;
     }
   }
 
-  // Content type with just the MIME type/subtype, no codecs.
-  const MediaContentType mimeType(aType.Type());
+  // Container type with just the MIME type/subtype, no codecs.
+  const MediaContainerType mimeType(aType.Type());
 
   if (OggDecoder::IsSupportedType(mimeType)) {
     return CANPLAY_MAYBE;
   }
   if (WaveDecoder::IsSupportedType(mimeType)) {
     return CANPLAY_MAYBE;
   }
 #ifdef MOZ_FMP4
@@ -227,58 +227,58 @@ CanHandleMediaType(const MediaContentTyp
     return CANPLAY_MAYBE;
   }
 #endif
   return CANPLAY_NO;
 }
 
 /* static */
 CanPlayStatus
-DecoderTraits::CanHandleContentType(const MediaContentType& aContentType,
-                                    DecoderDoctorDiagnostics* aDiagnostics)
+DecoderTraits::CanHandleContainerType(const MediaContainerType& aContainerType,
+                                      DecoderDoctorDiagnostics* aDiagnostics)
 {
-  return CanHandleMediaType(aContentType, aDiagnostics);
+  return CanHandleMediaType(aContainerType, aDiagnostics);
 }
 
 /* static */
 bool DecoderTraits::ShouldHandleMediaType(const char* aMIMEType,
                                           DecoderDoctorDiagnostics* aDiagnostics)
 {
-  Maybe<MediaContentType> contentType = MakeMediaContentType(aMIMEType);
-  if (!contentType) {
+  Maybe<MediaContainerType> containerType = MakeMediaContainerType(aMIMEType);
+  if (!containerType) {
     return false;
   }
 
-  if (WaveDecoder::IsSupportedType(*contentType)) {
+  if (WaveDecoder::IsSupportedType(*containerType)) {
     // We should not return true for Wave types, since there are some
     // Wave codecs actually in use in the wild that we don't support, and
     // we should allow those to be handled by plugins or helper apps.
     // Furthermore people can play Wave files on most platforms by other
     // means.
     return false;
   }
 
   // If an external plugin which can handle quicktime video is available
   // (and not disabled), prefer it over native playback as there several
   // codecs found in the wild that we do not handle.
-  if (contentType->Type() == MEDIAMIMETYPE("video/quicktime")) {
+  if (containerType->Type() == MEDIAMIMETYPE("video/quicktime")) {
     RefPtr<nsPluginHost> pluginHost = nsPluginHost::GetInst();
     if (pluginHost &&
-        pluginHost->HavePluginForType(contentType->Type().AsString())) {
+        pluginHost->HavePluginForType(containerType->Type().AsString())) {
       return false;
     }
   }
 
-  return CanHandleMediaType(*contentType, aDiagnostics) != CANPLAY_NO;
+  return CanHandleMediaType(*containerType, aDiagnostics) != CANPLAY_NO;
 }
 
 // Instantiates but does not initialize decoder.
 static
 already_AddRefed<MediaDecoder>
-InstantiateDecoder(const MediaContentType& aType,
+InstantiateDecoder(const MediaContainerType& aType,
                    MediaDecoderOwner* aOwner,
                    DecoderDoctorDiagnostics* aDiagnostics)
 {
   MOZ_ASSERT(NS_IsMainThread());
   RefPtr<MediaDecoder> decoder;
 
 #ifdef MOZ_FMP4
   if (MP4Decoder::IsSupportedType(aType, aDiagnostics)) {
@@ -338,26 +338,26 @@ InstantiateDecoder(const MediaContentTyp
 
 /* static */
 already_AddRefed<MediaDecoder>
 DecoderTraits::CreateDecoder(const nsACString& aType,
                              MediaDecoderOwner* aOwner,
                              DecoderDoctorDiagnostics* aDiagnostics)
 {
   MOZ_ASSERT(NS_IsMainThread());
-  Maybe<MediaContentType> type = MakeMediaContentType(aType);
+  Maybe<MediaContainerType> type = MakeMediaContainerType(aType);
   if (!type) {
     return nullptr;
   }
   return InstantiateDecoder(*type, aOwner, aDiagnostics);
 }
 
 /* static */
 MediaDecoderReader*
-DecoderTraits::CreateReader(const MediaContentType& aType,
+DecoderTraits::CreateReader(const MediaContainerType& aType,
                             AbstractMediaDecoder* aDecoder)
 {
   MOZ_ASSERT(NS_IsMainThread());
   MediaDecoderReader* decoderReader = nullptr;
 
   if (!aDecoder) {
     return decoderReader;
   }
@@ -409,17 +409,17 @@ bool DecoderTraits::IsSupportedInVideoDo
   // Forbid playing media in video documents if the user has opted
   // not to, using either the legacy WMF specific pref, or the newer
   // catch-all pref.
   if (!Preferences::GetBool("media.windows-media-foundation.play-stand-alone", true) ||
       !Preferences::GetBool("media.play-stand-alone", true)) {
     return false;
   }
 
-  Maybe<MediaContentType> type = MakeMediaContentType(aType);
+  Maybe<MediaContainerType> type = MakeMediaContainerType(aType);
   if (!type) {
     return false;
   }
 
   return
     OggDecoder::IsSupportedType(*type) ||
     WebMDecoder::IsSupportedType(*type) ||
 #ifdef MOZ_ANDROID_OMX
--- a/dom/media/DecoderTraits.h
+++ b/dom/media/DecoderTraits.h
@@ -11,58 +11,58 @@
 
 class nsAString;
 class nsACString;
 
 namespace mozilla {
 
 class AbstractMediaDecoder;
 class DecoderDoctorDiagnostics;
-class MediaContentType;
+class MediaContainerType;
 class MediaDecoder;
 class MediaDecoderOwner;
 class MediaDecoderReader;
 
 enum CanPlayStatus {
   CANPLAY_NO,
   CANPLAY_MAYBE,
   CANPLAY_YES
 };
 
 class DecoderTraits {
 public:
-  // Returns the CanPlayStatus indicating if we can handle this content type.
-  static CanPlayStatus CanHandleContentType(const MediaContentType& aContentType,
-                                            DecoderDoctorDiagnostics* aDiagnostics);
+  // Returns the CanPlayStatus indicating if we can handle this container type.
+  static CanPlayStatus CanHandleContainerType(const MediaContainerType& aContainerType,
+                                              DecoderDoctorDiagnostics* aDiagnostics);
 
   // Returns true if we should handle this MIME type when it appears
   // as an <object> or as a toplevel page. If, in practice, our support
   // for the type is more limited than appears in the wild, we should return
   // false here even if CanHandleMediaType would return true.
   static bool ShouldHandleMediaType(const char* aMIMEType,
                                     DecoderDoctorDiagnostics* aDiagnostics);
 
   // Create a decoder for the given aType. Returns null if we
   // were unable to create the decoder.
   static already_AddRefed<MediaDecoder> CreateDecoder(const nsACString& aType,
                                                       MediaDecoderOwner* aOwner,
                                                       DecoderDoctorDiagnostics* aDiagnostics);
 
   // Create a reader for thew given MIME type aType. Returns null
   // if we were unable to create the reader.
-  static MediaDecoderReader* CreateReader(const MediaContentType& aType,
+  static MediaDecoderReader* CreateReader(const MediaContainerType& aType,
                                           AbstractMediaDecoder* aDecoder);
 
   // Returns true if MIME type aType is supported in video documents,
   // or false otherwise. Not all platforms support all MIME types, and
   // vice versa.
   static bool IsSupportedInVideoDocument(const nsACString& aType);
 
   // Convenience function that returns false if MOZ_FMP4 is not defined,
   // otherwise defers to MP4Decoder::IsSupportedType().
-  static bool IsMP4SupportedType(const MediaContentType& aType,
+  static bool IsMP4SupportedType(const MediaContainerType& aType,
                                  DecoderDoctorDiagnostics* aDiagnostics);
 };
 
 } // namespace mozilla
 
 #endif
 
--- a/dom/media/MP3Decoder.cpp
+++ b/dom/media/MP3Decoder.cpp
@@ -1,17 +1,17 @@
 
 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 /* vim:set ts=2 sw=2 sts=2 et cindent: */
 /* 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 "MP3Decoder.h"
-#include "MediaContentType.h"
+#include "MediaContainerType.h"
 #include "MediaDecoderStateMachine.h"
 #include "MediaFormatReader.h"
 #include "MP3Demuxer.h"
 #include "PDMFactory.h"
 
 namespace mozilla {
 
 MediaDecoder*
@@ -33,21 +33,21 @@ MP3Decoder::CreateStateMachine() {
 bool
 MP3Decoder::IsEnabled() {
   RefPtr<PDMFactory> platform = new PDMFactory();
   return platform->SupportsMimeType(NS_LITERAL_CSTRING("audio/mpeg"),
                                     /* DecoderDoctorDiagnostics* */ nullptr);
 }
 
 /* static */
-bool MP3Decoder::IsSupportedType(const MediaContentType& aContentType)
+bool MP3Decoder::IsSupportedType(const MediaContainerType& aContainerType)
 {
-  if (aContentType.Type() == MEDIAMIMETYPE("audio/mp3")
-      || aContentType.Type() == MEDIAMIMETYPE("audio/mpeg")) {
+  if (aContainerType.Type() == MEDIAMIMETYPE("audio/mp3")
+      || aContainerType.Type() == MEDIAMIMETYPE("audio/mpeg")) {
     return
       IsEnabled()
-      && (aContentType.ExtendedType().Codecs().IsEmpty()
-          || aContentType.ExtendedType().Codecs().AsString().EqualsASCII("mp3"));
+      && (aContainerType.ExtendedType().Codecs().IsEmpty()
+          || aContainerType.ExtendedType().Codecs().AsString().EqualsASCII("mp3"));
   }
   return false;
 }
 
 } // namespace mozilla
--- a/dom/media/MP3Decoder.h
+++ b/dom/media/MP3Decoder.h
@@ -5,27 +5,27 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 #ifndef MP3Decoder_h_
 #define MP3Decoder_h_
 
 #include "MediaDecoder.h"
 
 namespace mozilla {
 
-class MediaContentType;
+class MediaContainerType;
 
 class MP3Decoder : public MediaDecoder
 {
 public:
   // MediaDecoder interface.
   explicit MP3Decoder(MediaDecoderOwner* aOwner) : MediaDecoder(aOwner) {}
   MediaDecoder* Clone(MediaDecoderOwner* aOwner) override;
   MediaDecoderStateMachine* CreateStateMachine() override;
 
   // Returns true if the MP3 backend is preffed on, and we're running on a
   // platform that is likely to have decoders for the format.
   static bool IsEnabled();
-  static bool IsSupportedType(const MediaContentType& aContentType);
+  static bool IsSupportedType(const MediaContainerType& aContainerType);
 };
 
 } // namespace mozilla
 
 #endif
rename from dom/media/MediaContentType.cpp
rename to dom/media/MediaContainerType.cpp
--- a/dom/media/MediaContentType.cpp
+++ b/dom/media/MediaContainerType.cpp
@@ -1,44 +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/. */
 
-#include "MediaContentType.h"
-
-#include "nsContentTypeParser.h"
+#include "MediaContainerType.h"
 
 namespace mozilla {
 
 size_t
-MediaContentType::SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const
+MediaContainerType::SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const
 {
   return mExtendedMIMEType.SizeOfExcludingThis(aMallocSizeOf);
 }
 
-Maybe<MediaContentType>
-MakeMediaContentType(const nsAString& aType)
+Maybe<MediaContainerType>
+MakeMediaContainerType(const nsAString& aType)
 {
   Maybe<MediaExtendedMIMEType> mime = MakeMediaExtendedMIMEType(aType);
   if (mime) {
-    return Some(MediaContentType(Move(*mime)));
+    return Some(MediaContainerType(Move(*mime)));
   }
   return Nothing();
 }
 
-Maybe<MediaContentType>
-MakeMediaContentType(const nsACString& aType)
+Maybe<MediaContainerType>
+MakeMediaContainerType(const nsACString& aType)
 {
-  return MakeMediaContentType(NS_ConvertUTF8toUTF16(aType));
+  return MakeMediaContainerType(NS_ConvertUTF8toUTF16(aType));
 }
 
-Maybe<MediaContentType>
-MakeMediaContentType(const char* aType)
+Maybe<MediaContainerType>
+MakeMediaContainerType(const char* aType)
 {
   if (!aType) {
     return Nothing();
   }
-  return MakeMediaContentType(nsDependentCString(aType));
+  return MakeMediaContainerType(nsDependentCString(aType));
 }
 
 } // namespace mozilla
rename from dom/media/MediaContentType.h
rename to dom/media/MediaContainerType.h
--- a/dom/media/MediaContentType.h
+++ b/dom/media/MediaContainerType.h
@@ -1,38 +1,38 @@
 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 /* vim:set ts=2 sw=2 sts=2 et cindent: */
 /* 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 MediaContentType_h_
-#define MediaContentType_h_
+#ifndef MediaContainerType_h_
+#define MediaContainerType_h_
 
 #include "MediaMIMETypes.h"
 #include "mozilla/Maybe.h"
 #include "nsString.h"
 
 namespace mozilla {
 
 // Class containing media type information for containers.
-class MediaContentType
+class MediaContainerType
 {
 public:
-  explicit MediaContentType(const MediaMIMEType& aType)
+  explicit MediaContainerType(const MediaMIMEType& aType)
     : mExtendedMIMEType(aType)
   {}
-  explicit MediaContentType(MediaMIMEType&& aType)
+  explicit MediaContainerType(MediaMIMEType&& aType)
     : mExtendedMIMEType(Move(aType))
   {}
-  explicit MediaContentType(const MediaExtendedMIMEType& aType)
+  explicit MediaContainerType(const MediaExtendedMIMEType& aType)
     : mExtendedMIMEType(aType)
   {
   }
-  explicit MediaContentType(MediaExtendedMIMEType&& aType)
+  explicit MediaContainerType(MediaExtendedMIMEType&& aType)
     : mExtendedMIMEType(Move(aType))
   {
   }
 
   const MediaMIMEType& Type() const { return mExtendedMIMEType.Type(); }
   const MediaExtendedMIMEType& ExtendedType() const { return mExtendedMIMEType; }
 
   // Original string. Note that "type/subtype" may not be lowercase,
@@ -40,15 +40,15 @@ public:
   const nsACString& OriginalString() const { return mExtendedMIMEType.OriginalString(); }
 
   size_t SizeOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf) const;
 
 private:
   MediaExtendedMIMEType mExtendedMIMEType;
 };
 
-Maybe<MediaContentType> MakeMediaContentType(const nsAString& aType);
-Maybe<MediaContentType> MakeMediaContentType(const nsACString& aType);
-Maybe<MediaContentType> MakeMediaContentType(const char* aType);
+Maybe<MediaContainerType> MakeMediaContainerType(const nsAString& aType);
+Maybe<MediaContainerType> MakeMediaContainerType(const nsACString& aType);
+Maybe<MediaContainerType> MakeMediaContainerType(const char* aType);
 
 } // namespace mozilla
 
-#endif // MediaContentType_h_
+#endif // MediaContainerType_h_
--- a/dom/media/MediaDecoder.cpp
+++ b/dom/media/MediaDecoder.cpp
@@ -832,21 +832,18 @@ MediaDecoder::EnsureTelemetryReported()
   nsTArray<nsCString> codecs;
   if (mInfo->HasAudio() && !mInfo->mAudio.GetAsAudioInfo()->mMimeType.IsEmpty()) {
     codecs.AppendElement(mInfo->mAudio.GetAsAudioInfo()->mMimeType);
   }
   if (mInfo->HasVideo() && !mInfo->mVideo.GetAsVideoInfo()->mMimeType.IsEmpty()) {
     codecs.AppendElement(mInfo->mVideo.GetAsVideoInfo()->mMimeType);
   }
   if (codecs.IsEmpty()) {
-    if (mResource->GetContentType().IsEmpty()) {
-      NS_WARNING("Somehow the resource's content type is empty");
-      return;
-    }
-    codecs.AppendElement(nsPrintfCString("resource; %s", mResource->GetContentType().get()));
+    codecs.AppendElement(nsPrintfCString("resource; %s",
+                                         mResource->GetContentType().OriginalString().Data()));
   }
   for (const nsCString& codec : codecs) {
     DECODER_LOG("Telemetry MEDIA_CODEC_USED= '%s'", codec.get());
     Telemetry::Accumulate(Telemetry::ID::MEDIA_CODEC_USED, codec);
   }
 
   mTelemetryReported = true;
 }
--- a/dom/media/MediaFormatReader.cpp
+++ b/dom/media/MediaFormatReader.cpp
@@ -2721,31 +2721,28 @@ MediaFormatReader::SetVideoBlankDecode(b
   MOZ_ASSERT(OnTaskQueue());
   return SetBlankDecode(TrackType::kVideoTrack, aIsBlankDecode);
 }
 
 void
 MediaFormatReader::SetBlankDecode(TrackType aTrack, bool aIsBlankDecode)
 {
   MOZ_ASSERT(OnTaskQueue());
+
   auto& decoder = GetDecoderData(aTrack);
+  if (decoder.mIsBlankDecode == aIsBlankDecode) {
+    return;
+  }
 
   LOG("%s, decoder.mIsBlankDecode = %d => aIsBlankDecode = %d",
       TrackTypeToStr(aTrack), decoder.mIsBlankDecode, aIsBlankDecode);
 
-  if (decoder.mIsBlankDecode == aIsBlankDecode) {
-    return;
-  }
-
   decoder.mIsBlankDecode = aIsBlankDecode;
   decoder.Flush();
   decoder.ShutdownDecoder();
-  ScheduleUpdate(TrackInfo::kVideoTrack);
-
-  return;
 }
 
 void
 MediaFormatReader::OnFirstDemuxCompleted(TrackInfo::TrackType aType,
                                          RefPtr<MediaTrackDemuxer::SamplesHolder> aSamples)
 {
   MOZ_ASSERT(OnTaskQueue());
 
--- a/dom/media/MediaResource.cpp
+++ b/dom/media/MediaResource.cpp
@@ -60,18 +60,18 @@ MediaResource::Destroy()
 
 NS_IMPL_ADDREF(MediaResource)
 NS_IMPL_RELEASE_WITH_DESTROY(MediaResource, Destroy())
 NS_IMPL_QUERY_INTERFACE0(MediaResource)
 
 ChannelMediaResource::ChannelMediaResource(MediaResourceCallback* aCallback,
                                            nsIChannel* aChannel,
                                            nsIURI* aURI,
-                                           const nsACString& aContentType)
-  : BaseMediaResource(aCallback, aChannel, aURI, aContentType),
+                                           const MediaContainerType& aContainerType)
+  : BaseMediaResource(aCallback, aChannel, aURI, aContainerType),
     mOffset(0),
     mReopenOnError(false),
     mIgnoreClose(false),
     mCacheStream(this),
     mLock("ChannelMediaResource.mLock"),
     mIgnoreResume(false),
     mSuspendAgent(mChannel)
 {
@@ -832,19 +832,17 @@ ChannelMediaResource::RecreateChannel()
                               nullptr,  // aCallbacks
                               loadFlags);
   NS_ENSURE_SUCCESS(rv, rv);
 
   // We have cached the Content-Type, which should not change. Give a hint to
   // the channel to avoid a sniffing failure, which would be expected because we
   // are probably seeking in the middle of the bitstream, and sniffing relies
   // on the presence of a magic number at the beginning of the stream.
-  NS_ASSERTION(!GetContentType().IsEmpty(),
-      "When recreating a channel, we should know the Content-Type.");
-  mChannel->SetContentType(GetContentType());
+  mChannel->SetContentType(GetContentType().OriginalString());
   mSuspendAgent.NotifyChannelOpened(mChannel);
 
   // Tell the cache to reset the download status when the channel is reopened.
   mCacheStream.NotifyChannelRecreated();
 
   return rv;
 }
 
@@ -1106,18 +1104,18 @@ ChannelSuspendAgent::IsSuspended()
 // FileMediaResource
 
 class FileMediaResource : public BaseMediaResource
 {
 public:
   FileMediaResource(MediaResourceCallback* aCallback,
                     nsIChannel* aChannel,
                     nsIURI* aURI,
-                    const nsACString& aContentType) :
-    BaseMediaResource(aCallback, aChannel, aURI, aContentType),
+                    const MediaContainerType& aContainerType) :
+    BaseMediaResource(aCallback, aChannel, aURI, aContainerType),
     mSize(-1),
     mLock("FileMediaResource.mLock"),
     mSizeInitialized(false)
   {
   }
   ~FileMediaResource()
   {
   }
@@ -1492,25 +1490,29 @@ MediaResource::Create(MediaResourceCallb
 
   // If the channel was redirected, we want the post-redirect URI;
   // but if the URI scheme was expanded, say from chrome: to jar:file:,
   // we want the original URI.
   nsCOMPtr<nsIURI> uri;
   nsresult rv = NS_GetFinalChannelURI(aChannel, getter_AddRefs(uri));
   NS_ENSURE_SUCCESS(rv, nullptr);
 
-  nsAutoCString contentType;
-  aChannel->GetContentType(contentType);
+  nsAutoCString contentTypeString;
+  aChannel->GetContentType(contentTypeString);
+  Maybe<MediaContainerType> containerType = MakeMediaContainerType(contentTypeString);
+  if (!containerType) {
+    return nullptr;
+  }
 
   nsCOMPtr<nsIFileChannel> fc = do_QueryInterface(aChannel);
   RefPtr<MediaResource> resource;
   if (fc || IsBlobURI(uri)) {
-    resource = new FileMediaResource(aCallback, aChannel, uri, contentType);
+    resource = new FileMediaResource(aCallback, aChannel, uri, *containerType);
   } else {
-    resource = new ChannelMediaResource(aCallback, aChannel, uri, contentType);
+    resource = new ChannelMediaResource(aCallback, aChannel, uri, *containerType);
   }
   return resource.forget();
 }
 
 void BaseMediaResource::SetLoadInBackground(bool aLoadInBackground) {
   if (aLoadInBackground == mLoadInBackground) {
     return;
   }
--- a/dom/media/MediaResource.h
+++ b/dom/media/MediaResource.h
@@ -11,16 +11,17 @@
 #include "nsIURI.h"
 #include "nsISeekableStream.h"
 #include "nsIStreamingProtocolController.h"
 #include "nsIStreamListener.h"
 #include "nsIChannelEventSink.h"
 #include "nsIInterfaceRequestor.h"
 #include "Intervals.h"
 #include "MediaCache.h"
+#include "MediaContainerType.h"
 #include "MediaData.h"
 #include "MediaResourceCallback.h"
 #include "mozilla/Atomics.h"
 #include "mozilla/Attributes.h"
 #include "mozilla/TimeStamp.h"
 #include "nsThreadUtils.h"
 #include <algorithm>
 
@@ -332,20 +333,20 @@ public:
 
   // Ensure that the media cache writes any data held in its partial block.
   // Called on the main thread only.
   virtual void FlushCache() { }
 
   // Notify that the last data byte range was loaded.
   virtual void NotifyLastByteRange() { }
 
-  // Returns the content type of the resource. This is copied from the
+  // Returns the container content type of the resource. This is copied from the
   // nsIChannel when the MediaResource is created. Safe to call from
   // any thread.
-  virtual const nsCString& GetContentType() const = 0;
+  virtual const MediaContainerType& GetContentType() const = 0;
 
   // Return true if the stream is a live stream
   virtual bool IsRealTime() {
     return false;
   }
 
   // Returns true if the resource is a live stream.
   virtual bool IsLiveStream()
@@ -378,17 +379,17 @@ public:
   size_t SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const override
   {
     // Might be useful to track in the future:
     // - mChannel
     // - mURI (possibly owned, looks like just a ref from mChannel)
     // Not owned:
     // - mCallback
     size_t size = MediaResource::SizeOfExcludingThis(aMallocSizeOf);
-    size += mContentType.SizeOfExcludingThisIfUnshared(aMallocSizeOf);
+    size += mContainerType.SizeOfExcludingThis(aMallocSizeOf);
 
     return size;
   }
 
   size_t SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const override
   {
     return aMallocSizeOf(this) + SizeOfExcludingThis(aMallocSizeOf);
   }
@@ -398,33 +399,32 @@ public:
   {
     return mContentURL;
   }
 
 protected:
   BaseMediaResource(MediaResourceCallback* aCallback,
                     nsIChannel* aChannel,
                     nsIURI* aURI,
-                    const nsACString& aContentType) :
+                    const MediaContainerType& aContainerType) :
     mCallback(aCallback),
     mChannel(aChannel),
     mURI(aURI),
-    mContentType(aContentType),
+    mContainerType(aContainerType),
     mLoadInBackground(false)
   {
-    NS_ASSERTION(!mContentType.IsEmpty(), "Must know content type");
     mURI->GetSpec(mContentURL);
   }
   virtual ~BaseMediaResource()
   {
   }
 
-  const nsCString& GetContentType() const override
+  const MediaContainerType& GetContentType() const override
   {
-    return mContentType;
+    return mContainerType;
   }
 
   // Set the request's load flags to aFlags.  If the request is part of a
   // load group, the request is removed from the group, the flags are set, and
   // then the request is added back to the load group.
   void ModifyLoadFlags(nsLoadFlags aFlags);
 
   // Dispatches an event to call MediaDecoder::NotifyBytesConsumed(aNumBytes, aOffset)
@@ -439,17 +439,17 @@ protected:
 
   // URI in case the stream needs to be re-opened. Access from
   // main thread only.
   nsCOMPtr<nsIURI> mURI;
 
   // Content-Type of the channel. This is copied from the nsIChannel when the
   // MediaResource is created. This is constant, so accessing from any thread
   // is safe.
-  const nsCString mContentType;
+  const MediaContainerType mContainerType;
 
   // Copy of the url of the channel resource.
   nsCString mContentURL;
 
   // True if SetLoadInBackground() has been called with
   // aLoadInBackground = true, i.e. when the document load event is not
   // blocked by this resource, and all channel loads will be in the
   // background.
@@ -506,17 +506,17 @@ private:
  * thread operations are delegated directly to that object.
  */
 class ChannelMediaResource : public BaseMediaResource
 {
 public:
   ChannelMediaResource(MediaResourceCallback* aDecoder,
                        nsIChannel* aChannel,
                        nsIURI* aURI,
-                       const nsACString& aContentType);
+                       const MediaContainerType& aContainerType);
   ~ChannelMediaResource();
 
   // These are called on the main thread by MediaCache. These must
   // not block or grab locks, because the media cache is holding its lock.
   // Notify that data is available from the cache. This can happen even
   // if this stream didn't read any data, since another stream might have
   // received data for the same resource.
   void CacheClientNotifyDataReceived();
--- a/dom/media/VideoUtils.cpp
+++ b/dom/media/VideoUtils.cpp
@@ -3,17 +3,17 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "VideoUtils.h"
 
 #include "mozilla/Base64.h"
 #include "mozilla/TaskQueue.h"
 #include "mozilla/Telemetry.h"
 
-#include "MediaContentType.h"
+#include "MediaContainerType.h"
 #include "MediaPrefs.h"
 #include "MediaResource.h"
 #include "TimeUnits.h"
 #include "nsMathUtils.h"
 #include "nsSize.h"
 #include "VorbisUtils.h"
 #include "ImageContainer.h"
 #include "mozilla/SharedThreadPool.h"
@@ -496,29 +496,29 @@ CreateTrackInfoWithMIMEType(const nsACSt
   } else if (StartsWith(aCodecMIMEType, "video/")) {
     trackInfo.reset(new VideoInfo());
     trackInfo->mMimeType = aCodecMIMEType;
   }
   return trackInfo;
 }
 
 UniquePtr<TrackInfo>
-CreateTrackInfoWithMIMETypeAndContentTypeExtraParameters(
+CreateTrackInfoWithMIMETypeAndContainerTypeExtraParameters(
   const nsACString& aCodecMIMEType,
-  const MediaContentType& aContentType)
+  const MediaContainerType& aContainerType)
 {
   UniquePtr<TrackInfo> trackInfo = CreateTrackInfoWithMIMEType(aCodecMIMEType);
   if (trackInfo) {
     VideoInfo* videoInfo = trackInfo->GetAsVideoInfo();
     if (videoInfo) {
-      Maybe<int32_t> maybeWidth = aContentType.ExtendedType().GetWidth();
+      Maybe<int32_t> maybeWidth = aContainerType.ExtendedType().GetWidth();
       if (maybeWidth && *maybeWidth > 0) {
         videoInfo->mImage.width = *maybeWidth;
       }
-      Maybe<int32_t> maybeHeight = aContentType.ExtendedType().GetHeight();
+      Maybe<int32_t> maybeHeight = aContainerType.ExtendedType().GetHeight();
       if (maybeHeight && *maybeHeight > 0) {
         videoInfo->mImage.height = *maybeHeight;
       }
     }
   }
   return trackInfo;
 }
 
--- a/dom/media/VideoUtils.h
+++ b/dom/media/VideoUtils.h
@@ -36,17 +36,17 @@ using mozilla::CheckedUint32;
 // dependent on other changes which we don't want to wait for. We plan to
 // remove this file in the near future.
 
 
 // This belongs in xpcom/monitor/Monitor.h, once we've made
 // mozilla::Monitor non-reentrant.
 namespace mozilla {
 
-class MediaContentType;
+class MediaContainerType;
 
 // EME Key System String.
 extern const nsLiteralCString kEMEKeySystemClearkey;
 extern const nsLiteralCString kEMEKeySystemWidevine;
 
 /**
  * ReentrantMonitorConditionallyEnter
  *
@@ -350,21 +350,21 @@ IsVP8CodecString(const nsAString& aCodec
 bool
 IsVP9CodecString(const nsAString& aCodec);
 
 // Try and create a TrackInfo with a given codec MIME type.
 UniquePtr<TrackInfo>
 CreateTrackInfoWithMIMEType(const nsACString& aCodecMIMEType);
 
 // Try and create a TrackInfo with a given codec MIME type, and optional extra
-// parameters from a content type (its MIME type and codecs are ignored).
+// parameters from a container type (its MIME type and codecs are ignored).
 UniquePtr<TrackInfo>
-CreateTrackInfoWithMIMETypeAndContentTypeExtraParameters(
+CreateTrackInfoWithMIMETypeAndContainerTypeExtraParameters(
   const nsACString& aCodecMIMEType,
-  const MediaContentType& aContentType);
+  const MediaContainerType& aContainerType);
 
 namespace detail {
 
 // aString should start with aMajor + '/'.
 constexpr bool
 StartsWithMIMETypeMajor(const char* aString,
                         const char* aMajor, size_t aMajorRemaining)
 {
--- a/dom/media/android/AndroidMediaDecoder.cpp
+++ b/dom/media/android/AndroidMediaDecoder.cpp
@@ -6,17 +6,17 @@
 
 #include "MediaDecoderStateMachine.h"
 #include "AndroidMediaDecoder.h"
 #include "AndroidMediaReader.h"
 
 namespace mozilla {
 
 AndroidMediaDecoder::AndroidMediaDecoder(MediaDecoderOwner* aOwner,
-                                         const MediaContentType& aType)
+                                         const MediaContainerType& aType)
   : MediaDecoder(aOwner), mType(aType)
 {
 }
 
 MediaDecoderStateMachine* AndroidMediaDecoder::CreateStateMachine()
 {
   return new MediaDecoderStateMachine(this, new AndroidMediaReader(this, mType));
 }
--- a/dom/media/android/AndroidMediaDecoder.h
+++ b/dom/media/android/AndroidMediaDecoder.h
@@ -3,25 +3,25 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this file,
  * You can obtain one at http://mozilla.org/MPL/2.0/. */
 #if !defined(AndroidMediaDecoder_h_)
 #define AndroidMediaDecoder_h_
 
 #include "MediaDecoder.h"
 #include "AndroidMediaDecoder.h"
-#include "MediaContentType.h"
+#include "MediaContainerType.h"
 
 namespace mozilla {
 
 class AndroidMediaDecoder : public MediaDecoder
 {
-  MediaContentType mType;
+  MediaContainerType mType;
 public:
-  AndroidMediaDecoder(MediaDecoderOwner* aOwner, const MediaContentType& aType);
+  AndroidMediaDecoder(MediaDecoderOwner* aOwner, const MediaContainerType& aType);
 
   MediaDecoder* Clone(MediaDecoderOwner* aOwner) override {
     return new AndroidMediaDecoder(aOwner, mType);
   }
   MediaDecoderStateMachine* CreateStateMachine() override;
 };
 
 } // namespace mozilla
--- a/dom/media/android/AndroidMediaPluginHost.cpp
+++ b/dom/media/android/AndroidMediaPluginHost.cpp
@@ -1,15 +1,15 @@
 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 /* vim:set ts=2 sw=2 sts=2 et cindent: */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this file,
  * You can obtain one at http://mozilla.org/MPL/2.0/. */
 #include "mozilla/Preferences.h"
-#include "MediaContentType.h"
+#include "MediaContainerType.h"
 #include "MediaResource.h"
 #include "mozilla/dom/HTMLMediaElement.h"
 #include "mozilla/Services.h"
 #include "AndroidMediaPluginHost.h"
 #include "nsAutoPtr.h"
 #include "nsXPCOMStrings.h"
 #include "nsISeekableStream.h"
 #include "nsIGfxInfo.h"
@@ -222,17 +222,17 @@ AndroidMediaPluginHost::AndroidMediaPlug
   }
 }
 
 AndroidMediaPluginHost::~AndroidMediaPluginHost() {
   mResourceServer->Stop();
   MOZ_COUNT_DTOR(AndroidMediaPluginHost);
 }
 
-bool AndroidMediaPluginHost::FindDecoder(const MediaContentType& aMimeType,
+bool AndroidMediaPluginHost::FindDecoder(const MediaContainerType& aMimeType,
                                          MediaCodecs* aCodecs)
 {
   const char *chars;
   size_t len = NS_CStringGetData(aMimeType.Type().AsString(), &chars, nullptr);
   for (size_t n = 0; n < mPlugins.Length(); ++n) {
     Manifest *plugin = mPlugins[n];
     const char* const *codecs;
     if (plugin->CanDecode(chars, len, &codecs)) {
@@ -248,17 +248,17 @@ bool AndroidMediaPluginHost::FindDecoder
       }
       return true;
     }
   }
   return false;
 }
 
 MPAPI::Decoder *AndroidMediaPluginHost::CreateDecoder(MediaResource *aResource,
-                                                      const MediaContentType& aMimeType)
+                                                      const MediaContainerType& aMimeType)
 {
   NS_ENSURE_TRUE(aResource, nullptr);
 
   nsAutoPtr<Decoder> decoder(new Decoder());
   if (!decoder) {
     return nullptr;
   }
 
--- a/dom/media/android/AndroidMediaPluginHost.h
+++ b/dom/media/android/AndroidMediaPluginHost.h
@@ -8,32 +8,32 @@
 
 #include "nsTArray.h"
 #include "MediaResource.h"
 #include "MPAPI.h"
 #include "AndroidMediaResourceServer.h"
 
 namespace mozilla {
 
-class MediaContentType;
+class MediaContainerType;
 class MediaCodecs;
 
 class AndroidMediaPluginHost {
   RefPtr<AndroidMediaResourceServer> mResourceServer;
   nsTArray<MPAPI::Manifest *> mPlugins;
 
   MPAPI::Manifest *FindPlugin(const nsACString& aMimeType);
 public:
   AndroidMediaPluginHost();
   ~AndroidMediaPluginHost();
 
   static void Shutdown();
 
-  bool FindDecoder(const MediaContentType& aMimeType, MediaCodecs* aCodecs);
-  MPAPI::Decoder *CreateDecoder(mozilla::MediaResource *aResource, const MediaContentType& aMimeType);
+  bool FindDecoder(const MediaContainerType& aMimeType, MediaCodecs* aCodecs);
+  MPAPI::Decoder *CreateDecoder(mozilla::MediaResource *aResource, const MediaContainerType& aMimeType);
   void DestroyDecoder(MPAPI::Decoder *aDecoder);
 };
 
 // Must be called on the main thread. Creates the plugin host if it doesn't
 // already exist.
 AndroidMediaPluginHost *EnsureAndroidMediaPluginHost();
 
 // May be called on any thread after EnsureAndroidMediaPluginHost has been called.
--- a/dom/media/android/AndroidMediaReader.cpp
+++ b/dom/media/android/AndroidMediaReader.cpp
@@ -20,19 +20,19 @@ namespace mozilla {
 
 using namespace mozilla::gfx;
 using namespace mozilla::media;
 
 typedef mozilla::layers::Image Image;
 typedef mozilla::layers::PlanarYCbCrImage PlanarYCbCrImage;
 
 AndroidMediaReader::AndroidMediaReader(AbstractMediaDecoder *aDecoder,
-                                       const MediaContentType& aContentType) :
+                                       const MediaContainerType& aContainerType) :
   MediaDecoderReader(aDecoder),
-  mType(aContentType),
+  mType(aContainerType),
   mPlugin(nullptr),
   mHasAudio(false),
   mHasVideo(false),
   mVideoSeekTimeUs(-1),
   mAudioSeekTimeUs(-1)
 {
 }
 
--- a/dom/media/android/AndroidMediaReader.h
+++ b/dom/media/android/AndroidMediaReader.h
@@ -2,17 +2,17 @@
 /* vim:set ts=2 sw=2 sts=2 et cindent: */
 /* 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/. */
 #if !defined(AndroidMediaReader_h_)
 #define AndroidMediaReader_h_
 
 #include "ImageContainer.h"
-#include "MediaContentType.h"
+#include "MediaContainerType.h"
 #include "MediaDecoderReader.h"
 #include "MediaResource.h"
 #include "mozilla/Attributes.h"
 #include "mozilla/layers/SharedRGBImage.h"
 
 #include "MPAPI.h"
 
 namespace mozilla {
@@ -20,30 +20,30 @@ namespace mozilla {
 class AbstractMediaDecoder;
 
 namespace layers {
 class ImageContainer;
 }
 
 class AndroidMediaReader : public MediaDecoderReader
 {
-  MediaContentType mType;
+  MediaContainerType mType;
   MPAPI::Decoder *mPlugin;
   bool mHasAudio;
   bool mHasVideo;
   nsIntRect mPicture;
   nsIntSize mInitialFrame;
   int64_t mVideoSeekTimeUs;
   int64_t mAudioSeekTimeUs;
   RefPtr<VideoData> mLastVideoFrame;
   MozPromiseHolder<MediaDecoderReader::SeekPromise> mSeekPromise;
   MozPromiseRequestHolder<MediaDecoderReader::MediaDataPromise> mSeekRequest;
 public:
   AndroidMediaReader(AbstractMediaDecoder* aDecoder,
-                     const MediaContentType& aContentType);
+                     const MediaContainerType& aContainerType);
 
   nsresult ResetDecode(TrackSet aTracks = TrackSet(TrackInfo::kAudioTrack,
                                                    TrackInfo::kVideoTrack)) override;
 
   bool DecodeAudioData() override;
   bool DecodeVideoFrame(bool &aKeyframeSkip, int64_t aTimeThreshold) override;
 
   nsresult ReadMetadata(MediaInfo* aInfo, MetadataTags** aTags) override;
--- a/dom/media/directshow/DirectShowDecoder.cpp
+++ b/dom/media/directshow/DirectShowDecoder.cpp
@@ -2,31 +2,31 @@
 /* vim:set ts=2 sw=2 sts=2 et cindent: */
 /* 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 "DirectShowDecoder.h"
 #include "DirectShowReader.h"
 #include "DirectShowUtils.h"
-#include "MediaContentType.h"
+#include "MediaContainerType.h"
 #include "MediaDecoderStateMachine.h"
 #include "mozilla/Preferences.h"
 #include "mozilla/WindowsVersion.h"
 
 namespace mozilla {
 
 MediaDecoderStateMachine* DirectShowDecoder::CreateStateMachine()
 {
   return new MediaDecoderStateMachine(this, new DirectShowReader(this));
 }
 
 /* static */
 bool
-DirectShowDecoder::GetSupportedCodecs(const MediaContentType& aType,
+DirectShowDecoder::GetSupportedCodecs(const MediaContainerType& aType,
                                       MediaCodecs* aOutCodecs)
 {
   if (!IsEnabled()) {
     return false;
   }
 
   if (aType.Type() == MEDIAMIMETYPE("audio/mpeg")
       || aType.Type() == MEDIAMIMETYPE("audio/mp3")) {
--- a/dom/media/directshow/DirectShowDecoder.h
+++ b/dom/media/directshow/DirectShowDecoder.h
@@ -7,17 +7,17 @@
 #if !defined(DirectShowDecoder_h_)
 #define DirectShowDecoder_h_
 
 #include "MediaDecoder.h"
 
 namespace mozilla {
 
 class MediaCodecs;
-class MediaContentType;
+class MediaContainerType;
 
 // Decoder that uses DirectShow to playback MP3 files only.
 class DirectShowDecoder : public MediaDecoder
 {
 public:
 
   explicit DirectShowDecoder(MediaDecoderOwner* aOwner);
   virtual ~DirectShowDecoder();
@@ -31,17 +31,17 @@ public:
 
   MediaDecoderStateMachine* CreateStateMachine() override;
 
   // Returns true if aType is a MIME type that we render with the
   // DirectShow backend. If aCodecList is non null,
   // it is filled with a (static const) null-terminated list of strings
   // denoting the codecs we'll playback. Note that playback is strictly
   // limited to MP3 only.
-  static bool GetSupportedCodecs(const MediaContentType& aType,
+  static bool GetSupportedCodecs(const MediaContainerType& aType,
                                  MediaCodecs* aOutCodecs);
 
   // Returns true if the DirectShow backend is preffed on.
   static bool IsEnabled();
 };
 
 } // namespace mozilla
 
--- a/dom/media/eme/MediaKeySystemAccess.cpp
+++ b/dom/media/eme/MediaKeySystemAccess.cpp
@@ -2,17 +2,17 @@
 /* vim:set ts=2 sw=2 sts=2 et cindent: */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "mozilla/dom/MediaKeySystemAccess.h"
 #include "mozilla/dom/MediaKeySystemAccessBinding.h"
 #include "mozilla/Preferences.h"
-#include "MediaContentType.h"
+#include "MediaContainerType.h"
 #include "MediaPrefs.h"
 #ifdef MOZ_FMP4
 #include "MP4Decoder.h"
 #endif
 #ifdef XP_WIN
 #include "WMFDecoderModule.h"
 #endif
 #include "nsContentCID.h"
@@ -569,31 +569,31 @@ GetSupportedCapabilities(const CodecType
               "audio or video capability has empty contentType.",
               NS_ConvertUTF16toUTF8(aPartialConfig.mLabel).get(),
               NS_ConvertUTF16toUTF8(contentTypeString).get(),
               NS_ConvertUTF16toUTF8(robustness).get());
       return Sequence<MediaKeySystemMediaCapability>();
     }
     // If content type is an invalid or unrecognized MIME type, continue
     // to the next iteration.
-    Maybe<MediaContentType> maybeContentType =
-      MakeMediaContentType(contentTypeString);
-    if (!maybeContentType) {
+    Maybe<MediaContainerType> maybeContainerType =
+      MakeMediaContainerType(contentTypeString);
+    if (!maybeContainerType) {
       EME_LOG("MediaKeySystemConfiguration (label='%s') "
               "MediaKeySystemMediaCapability('%s','%s') unsupported; "
-              "failed to parse contentType as MIME type.",
+              "failed to parse contentTypeString as MIME type.",
               NS_ConvertUTF16toUTF8(aPartialConfig.mLabel).get(),
               NS_ConvertUTF16toUTF8(contentTypeString).get(),
               NS_ConvertUTF16toUTF8(robustness).get());
       continue;
     }
-    const MediaContentType& contentType = *maybeContentType;
+    const MediaContainerType& containerType = *maybeContainerType;
     bool invalid = false;
     nsTArray<EMECodecString> codecs;
-    for (const auto& codecString : contentType.ExtendedType().Codecs().Range()) {
+    for (const auto& codecString : containerType.ExtendedType().Codecs().Range()) {
       EMECodecString emeCodec = ToEMEAPICodecString(nsString(codecString));
       if (emeCodec.IsEmpty()) {
         invalid = true;
         EME_LOG("MediaKeySystemConfiguration (label='%s') "
                 "MediaKeySystemMediaCapability('%s','%s') unsupported; "
                 "'%s' is an invalid codec string.",
                 NS_ConvertUTF16toUTF8(aPartialConfig.mLabel).get(),
                 NS_ConvertUTF16toUTF8(contentTypeString).get(),
@@ -608,27 +608,27 @@ GetSupportedCapabilities(const CodecType
     }
 
     // If the user agent does not support container, continue to the next iteration.
     // The case-sensitivity of string comparisons is determined by the appropriate RFC.
     // (Note: Per RFC 6838 [RFC6838], "Both top-level type and subtype names are
     // case-insensitive."'. We're using nsContentTypeParser and that is
     // case-insensitive and converts all its parameter outputs to lower case.)
     const bool isMP4 =
-      DecoderTraits::IsMP4SupportedType(contentType, aDiagnostics);
+      DecoderTraits::IsMP4SupportedType(containerType, aDiagnostics);
     if (isMP4 && !aKeySystem.mMP4.IsSupported()) {
       EME_LOG("MediaKeySystemConfiguration (label='%s') "
               "MediaKeySystemMediaCapability('%s','%s') unsupported; "
               "MP4 requested but unsupported.",
               NS_ConvertUTF16toUTF8(aPartialConfig.mLabel).get(),
               NS_ConvertUTF16toUTF8(contentTypeString).get(),
               NS_ConvertUTF16toUTF8(robustness).get());
       continue;
     }
-    const bool isWebM = WebMDecoder::IsSupportedType(contentType);
+    const bool isWebM = WebMDecoder::IsSupportedType(containerType);
     if (isWebM && !aKeySystem.mWebM.IsSupported()) {
       EME_LOG("MediaKeySystemConfiguration (label='%s') "
               "MediaKeySystemMediaCapability('%s','%s') unsupported; "
               "WebM requested but unsupported.",
               NS_ConvertUTF16toUTF8(aPartialConfig.mLabel).get(),
               NS_ConvertUTF16toUTF8(contentTypeString).get(),
               NS_ConvertUTF16toUTF8(robustness).get());
       continue;
@@ -672,18 +672,18 @@ GetSupportedCapabilities(const CodecType
         } else if (aCodecType == Video) {
           codecs.AppendElement(EME_CODEC_VP8);
         }
       }
       // Otherwise: Continue to the next iteration.
       // (Note: all containers we support have implied codecs, so don't continue here.)
     }
 
-    // If content type is not strictly a audio/video type, continue to the next iteration.
-    const auto majorType = GetMajorType(contentType.Type());
+    // If container type is not strictly a audio/video type, continue to the next iteration.
+    const auto majorType = GetMajorType(containerType.Type());
     if (majorType == Invalid) {
       EME_LOG("MediaKeySystemConfiguration (label='%s') "
               "MediaKeySystemMediaCapability('%s','%s') unsupported; "
               "MIME type is not an audio or video MIME type.",
               NS_ConvertUTF16toUTF8(aPartialConfig.mLabel).get(),
               NS_ConvertUTF16toUTF8(contentTypeString).get(),
               NS_ConvertUTF16toUTF8(robustness).get());
       continue;
--- a/dom/media/flac/FlacDecoder.cpp
+++ b/dom/media/flac/FlacDecoder.cpp
@@ -1,17 +1,17 @@
 /* -*- 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 "FlacDecoder.h"
 #include "FlacDemuxer.h"
-#include "MediaContentType.h"
+#include "MediaContainerType.h"
 #include "MediaDecoderStateMachine.h"
 #include "MediaFormatReader.h"
 #include "MediaPrefs.h"
 
 namespace mozilla {
 
 MediaDecoder*
 FlacDecoder::Clone(MediaDecoderOwner* aOwner)
@@ -38,17 +38,17 @@ FlacDecoder::IsEnabled()
   return MediaPrefs::FlacEnabled();
 #else
   // Until bug 1295886 is fixed.
   return false;
 #endif
 }
 
 /* static */ bool
-FlacDecoder::IsSupportedType(const MediaContentType& aContentType)
+FlacDecoder::IsSupportedType(const MediaContainerType& aContainerType)
 {
   return IsEnabled()
-         && (aContentType.Type() == MEDIAMIMETYPE("audio/flac")
-             || aContentType.Type() == MEDIAMIMETYPE("audio/x-flac")
-             || aContentType.Type() == MEDIAMIMETYPE("application/x-flac"));
+         && (aContainerType.Type() == MEDIAMIMETYPE("audio/flac")
+             || aContainerType.Type() == MEDIAMIMETYPE("audio/x-flac")
+             || aContainerType.Type() == MEDIAMIMETYPE("application/x-flac"));
 }
 
 } // namespace mozilla
--- a/dom/media/flac/FlacDecoder.h
+++ b/dom/media/flac/FlacDecoder.h
@@ -6,27 +6,27 @@
 
 #ifndef FLAC_DECODER_H_
 #define FLAC_DECODER_H_
 
 #include "MediaDecoder.h"
 
 namespace mozilla {
 
-class MediaContentType;
+class MediaContainerType;
 
 class FlacDecoder : public MediaDecoder
 {
 public:
   // MediaDecoder interface.
   explicit FlacDecoder(MediaDecoderOwner* aOwner) : MediaDecoder(aOwner) {}
   MediaDecoder* Clone(MediaDecoderOwner* aOwner) override;
   MediaDecoderStateMachine* CreateStateMachine() override;
 
   // Returns true if the Flac backend is pref'ed on, and we're running on a
   // platform that is likely to have decoders for the format.
   static bool IsEnabled();
-  static bool IsSupportedType(const MediaContentType& aContentType);
+  static bool IsSupportedType(const MediaContainerType& aContainerType);
 };
 
 } // namespace mozilla
 
 #endif // !FLAC_DECODER_H_
--- a/dom/media/fmp4/MP4Decoder.cpp
+++ b/dom/media/fmp4/MP4Decoder.cpp
@@ -1,16 +1,16 @@
 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 /* vim:set ts=2 sw=2 sts=2 et cindent: */
 /* 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 "MP4Decoder.h"
-#include "MediaContentType.h"
+#include "MediaContainerType.h"
 #include "MediaDecoderStateMachine.h"
 #include "MP4Demuxer.h"
 #include "mozilla/Preferences.h"
 #include "nsCharSeparatedTokenizer.h"
 #include "mozilla/CDMProxy.h"
 #include "mozilla/Logging.h"
 #include "mozilla/SharedThreadPool.h"
 #include "nsMimeTypes.h"
@@ -62,17 +62,17 @@ IsWhitelistedH264Codec(const nsAString& 
          (profile == H264_PROFILE_BASE ||
           profile == H264_PROFILE_MAIN ||
           profile == H264_PROFILE_EXTENDED ||
           profile == H264_PROFILE_HIGH);
 }
 
 /* static */
 bool
-MP4Decoder::IsSupportedType(const MediaContentType& aType,
+MP4Decoder::IsSupportedType(const MediaContainerType& aType,
                             DecoderDoctorDiagnostics* aDiagnostics)
 {
   if (!IsEnabled()) {
     return false;
   }
 
   // Whitelist MP4 types, so they explicitly match what we encounter on
   // the web, as opposed to what we use internally (i.e. what our demuxers
@@ -91,57 +91,57 @@ MP4Decoder::IsSupportedType(const MediaC
     return false;
   }
 
   nsTArray<UniquePtr<TrackInfo>> trackInfos;
   if (aType.ExtendedType().Codecs().IsEmpty()) {
     // No codecs specified. Assume H.264
     if (isAudio) {
       trackInfos.AppendElement(
-        CreateTrackInfoWithMIMETypeAndContentTypeExtraParameters(
+        CreateTrackInfoWithMIMETypeAndContainerTypeExtraParameters(
           NS_LITERAL_CSTRING("audio/mp4a-latm"), aType));
     } else {
       MOZ_ASSERT(isVideo);
       trackInfos.AppendElement(
-        CreateTrackInfoWithMIMETypeAndContentTypeExtraParameters(
+        CreateTrackInfoWithMIMETypeAndContainerTypeExtraParameters(
           NS_LITERAL_CSTRING("video/avc"), aType));
     }
   } else {
     // Verify that all the codecs specified are ones that we expect that
     // we can play.
     for (const auto& codec : aType.ExtendedType().Codecs().Range()) {
       if (IsAACCodecString(codec)) {
         trackInfos.AppendElement(
-          CreateTrackInfoWithMIMETypeAndContentTypeExtraParameters(
+          CreateTrackInfoWithMIMETypeAndContainerTypeExtraParameters(
             NS_LITERAL_CSTRING("audio/mp4a-latm"), aType));
         continue;
       }
       if (codec.EqualsLiteral("mp3")) {
         trackInfos.AppendElement(
-          CreateTrackInfoWithMIMETypeAndContentTypeExtraParameters(
+          CreateTrackInfoWithMIMETypeAndContainerTypeExtraParameters(
             NS_LITERAL_CSTRING("audio/mpeg"), aType));
         continue;
       }
       if (codec.EqualsLiteral("opus")) {
         trackInfos.AppendElement(
-          CreateTrackInfoWithMIMETypeAndContentTypeExtraParameters(
+          CreateTrackInfoWithMIMETypeAndContainerTypeExtraParameters(
             NS_LITERAL_CSTRING("audio/opus"), aType));
         continue;
       }
       if (codec.EqualsLiteral("flac")) {
         trackInfos.AppendElement(
-          CreateTrackInfoWithMIMETypeAndContentTypeExtraParameters(
+          CreateTrackInfoWithMIMETypeAndContainerTypeExtraParameters(
             NS_LITERAL_CSTRING("audio/flac"), aType));
         continue;
       }
       // Note: Only accept H.264 in a video content type, not in an audio
       // content type.
       if (IsWhitelistedH264Codec(codec) && isVideo) {
         trackInfos.AppendElement(
-          CreateTrackInfoWithMIMETypeAndContentTypeExtraParameters(
+          CreateTrackInfoWithMIMETypeAndContainerTypeExtraParameters(
             NS_LITERAL_CSTRING("video/avc"), aType));
         continue;
       }
       // Some unsupported codec.
       return false;
     }
   }
 
--- a/dom/media/fmp4/MP4Decoder.h
+++ b/dom/media/fmp4/MP4Decoder.h
@@ -8,37 +8,37 @@
 
 #include "MediaDecoder.h"
 #include "MediaFormatReader.h"
 #include "mozilla/dom/Promise.h"
 #include "mozilla/layers/KnowsCompositor.h"
 
 namespace mozilla {
 
-class MediaContentType;
+class MediaContainerType;
 
 // Decoder that uses a bundled MP4 demuxer and platform decoders to play MP4.
 class MP4Decoder : public MediaDecoder
 {
 public:
   explicit MP4Decoder(MediaDecoderOwner* aOwner);
 
   MediaDecoder* Clone(MediaDecoderOwner* aOwner) override {
     if (!IsEnabled()) {
       return nullptr;
     }
     return new MP4Decoder(aOwner);
   }
 
   MediaDecoderStateMachine* CreateStateMachine() override;
 
-  // Returns true if aContentType is an MP4 type that we think we can render
+  // Returns true if aContainerType is an MP4 type that we think we can render
   // with the a platform decoder backend.
   // If provided, codecs are checked for support.
-  static bool IsSupportedType(const MediaContentType& aContentType,
+  static bool IsSupportedType(const MediaContainerType& aContainerType,
                               DecoderDoctorDiagnostics* aDiagnostics);
 
   // Return true if aMimeType is a one of the strings used by our demuxers to
   // identify H264. Does not parse general content type strings, i.e. white
   // space matters.
   static bool IsH264(const nsACString& aMimeType);
 
   // Return true if aMimeType is a one of the strings used by our demuxers to
--- a/dom/media/gtest/MockMediaResource.cpp
+++ b/dom/media/gtest/MockMediaResource.cpp
@@ -5,20 +5,21 @@
 #include "MockMediaResource.h"
 
 #include <sys/types.h>
 #include <sys/stat.h>
 
 namespace mozilla
 {
 
-MockMediaResource::MockMediaResource(const char* aFileName, const nsACString& aContentType)
+MockMediaResource::MockMediaResource(const char* aFileName,
+                                     const MediaContainerType& aContainerType)
   : mFileHandle(nullptr)
   , mFileName(aFileName)
-  , mContentType(aContentType)
+  , mContainerType(aContainerType)
 {
 }
 
 nsresult
 MockMediaResource::Open(nsIStreamListener** aStreamListener)
 {
   mFileHandle = fopen(mFileName, "rb");
   if (mFileHandle == nullptr) {
--- a/dom/media/gtest/MockMediaResource.h
+++ b/dom/media/gtest/MockMediaResource.h
@@ -10,17 +10,19 @@
 #include "mozilla/Atomics.h"
 
 namespace mozilla
 {
 
 class MockMediaResource : public MediaResource
 {
 public:
-  explicit MockMediaResource(const char* aFileName, const nsACString& aMimeType = NS_LITERAL_CSTRING("video/mp4"));
+  explicit MockMediaResource(const char* aFileName,
+                             const MediaContainerType& aMimeType =
+                               MediaContainerType(MEDIAMIMETYPE("video/mp4")));
   nsIURI* URI() const override { return nullptr; }
   nsresult Close() override { return NS_OK; }
   void Suspend(bool aCloseImmediately) override {}
   void Resume() override {}
   already_AddRefed<nsIPrincipal> GetCurrentPrincipal() override
   {
     return nullptr;
   }
@@ -54,30 +56,30 @@ public:
     nsresult rv = ReadAt(aOffset, aBuffer, aCount, &bytesRead);
     NS_ENSURE_SUCCESS(rv, rv);
     return bytesRead == aCount ? NS_OK : NS_ERROR_FAILURE;
   }
 
   bool IsTransportSeekable() override { return true; }
   nsresult Open(nsIStreamListener** aStreamListener) override;
   nsresult GetCachedRanges(MediaByteRangeSet& aRanges) override;
-  const nsCString& GetContentType() const override
+  const MediaContainerType& GetContentType() const override
   {
-    return mContentType;
+    return mContainerType;
   }
 
   void MockClearBufferedRanges();
   void MockAddBufferedRange(int64_t aStart, int64_t aEnd);
 
 protected:
   virtual ~MockMediaResource();
 
 private:
   FILE* mFileHandle;
   const char* mFileName;
   MediaByteRangeSet mRanges;
   Atomic<int> mEntry;
-  const nsCString mContentType;
+  const MediaContainerType mContainerType;
 };
 
 } // namespace mozilla
 
 #endif
--- a/dom/media/gtest/TestMediaDataDecoder.cpp
+++ b/dom/media/gtest/TestMediaDataDecoder.cpp
@@ -2,17 +2,17 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "gtest/gtest.h"
 #include "Benchmark.h"
 #include "MockMediaResource.h"
 #include "DecoderTraits.h"
-#include "MediaContentType.h"
+#include "MediaContainerType.h"
 #include "MP4Decoder.h"
 #include "MP4Demuxer.h"
 #include "WebMDecoder.h"
 #include "WebMDemuxer.h"
 
 using namespace mozilla;
 
 class BenchmarkRunner
@@ -41,36 +41,36 @@ public:
 
 private:
   RefPtr<Benchmark> mBenchmark;
 };
 
 TEST(MediaDataDecoder, H264)
 {
   if (!DecoderTraits::IsMP4SupportedType(
-         MediaContentType(MEDIAMIMETYPE("video/mp4")),
+         MediaContainerType(MEDIAMIMETYPE("video/mp4")),
          /* DecoderDoctorDiagnostics* */ nullptr)) {
     EXPECT_TRUE(true);
   } else {
     RefPtr<MediaResource> resource =
-      new MockMediaResource("gizmo.mp4", NS_LITERAL_CSTRING("video/mp4"));
+      new MockMediaResource("gizmo.mp4", MediaContainerType(MEDIAMIMETYPE("video/mp4")));
     nsresult rv = resource->Open(nullptr);
     EXPECT_TRUE(NS_SUCCEEDED(rv));
 
     BenchmarkRunner runner(new Benchmark(new MP4Demuxer(resource)));
     EXPECT_GT(runner.Run(), 0u);
   }
 }
 
 TEST(MediaDataDecoder, VP9)
 {
-  if (!WebMDecoder::IsSupportedType(MediaContentType(MEDIAMIMETYPE("video/webm")))) {
+  if (!WebMDecoder::IsSupportedType(MediaContainerType(MEDIAMIMETYPE("video/webm")))) {
     EXPECT_TRUE(true);
   } else {
     RefPtr<MediaResource> resource =
-      new MockMediaResource("vp9cake.webm", NS_LITERAL_CSTRING("video/webm"));
+      new MockMediaResource("vp9cake.webm", MediaContainerType(MEDIAMIMETYPE("video/webm")));
     nsresult rv = resource->Open(nullptr);
     EXPECT_TRUE(NS_SUCCEEDED(rv));
 
     BenchmarkRunner runner(new Benchmark(new WebMDemuxer(resource)));
     EXPECT_GT(runner.Run(), 0u);
   }
 }
--- a/dom/media/gtest/TestMediaMIMETypes.cpp
+++ b/dom/media/gtest/TestMediaMIMETypes.cpp
@@ -1,15 +1,15 @@
 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "gtest/gtest.h"
-#include "MediaContentType.h"
+#include "MediaMIMETypes.h"
 
 using namespace mozilla;
 
 TEST(MediaMIMETypes, DependentMIMEType)
 {
   static const struct
   {
     const char* mString;
--- a/dom/media/mediasource/ContainerParser.cpp
+++ b/dom/media/mediasource/ContainerParser.cpp
@@ -21,22 +21,22 @@
 #include "nsAutoPtr.h"
 #include "SourceBufferResource.h"
 #include <algorithm>
 
 extern mozilla::LogModule* GetMediaSourceSamplesLog();
 
 #define STRINGIFY(x) #x
 #define TOSTRING(x) STRINGIFY(x)
-#define MSE_DEBUG(name, arg, ...) MOZ_LOG(GetMediaSourceSamplesLog(), mozilla::LogLevel::Debug, (TOSTRING(name) "(%p:%s)::%s: " arg, this, mType.get(), __func__, ##__VA_ARGS__))
-#define MSE_DEBUGV(name, arg, ...) MOZ_LOG(GetMediaSourceSamplesLog(), mozilla::LogLevel::Verbose, (TOSTRING(name) "(%p:%s)::%s: " arg, this, mType.get(), __func__, ##__VA_ARGS__))
+#define MSE_DEBUG(name, arg, ...) MOZ_LOG(GetMediaSourceSamplesLog(), mozilla::LogLevel::Debug, (TOSTRING(name) "(%p:%s)::%s: " arg, this, mType.OriginalString().Data(), __func__, ##__VA_ARGS__))
+#define MSE_DEBUGV(name, arg, ...) MOZ_LOG(GetMediaSourceSamplesLog(), mozilla::LogLevel::Verbose, (TOSTRING(name) "(%p:%s)::%s: " arg, this, mType.OriginalString().Data(), __func__, ##__VA_ARGS__))
 
 namespace mozilla {
 
-ContainerParser::ContainerParser(const nsACString& aType)
+ContainerParser::ContainerParser(const MediaContainerType& aType)
   : mHasInitData(false)
   , mType(aType)
 {
 }
 
 ContainerParser::~ContainerParser() = default;
 
 MediaResult
@@ -110,17 +110,17 @@ ContainerParser::MediaHeaderRange()
 MediaByteRange
 ContainerParser::MediaSegmentRange()
 {
   return mCompleteMediaSegmentRange;
 }
 
 class WebMContainerParser : public ContainerParser {
 public:
-  explicit WebMContainerParser(const nsACString& aType)
+  explicit WebMContainerParser(const MediaContainerType& aType)
     : ContainerParser(aType)
     , mParser(0)
     , mOffset(0)
   {}
 
   static const unsigned NS_PER_USEC = 1000;
   static const unsigned USEC_PER_SEC = 1000000;
 
@@ -200,17 +200,18 @@ public:
       return NS_ERROR_NOT_AVAILABLE;
     }
 
     if (initSegment) {
       mOffset = 0;
       mParser = WebMBufferedParser(0);
       mOverlappedMapping.Clear();
       mInitData = new MediaByteBuffer();
-      mResource = new SourceBufferResource(NS_LITERAL_CSTRING("video/webm"));
+      mResource = new SourceBufferResource(
+                        MediaContainerType(MEDIAMIMETYPE("video/webm")));
       mCompleteMediaHeaderRange = MediaByteRange();
       mCompleteMediaSegmentRange = MediaByteRange();
     }
 
     // XXX if it only adds new mappings, overlapped but not available
     // (e.g. overlap < 0) frames are "lost" from the reported mappings here.
     nsTArray<WebMTimeDataOffset> mapping;
     mapping.AppendElements(mOverlappedMapping);
@@ -334,17 +335,17 @@ private:
   nsTArray<WebMTimeDataOffset> mOverlappedMapping;
   int64_t mOffset;
   Maybe<WebMTimeDataOffset> mLastMapping;
 };
 
 #ifdef MOZ_FMP4
 class MP4ContainerParser : public ContainerParser {
 public:
-  explicit MP4ContainerParser(const nsACString& aType)
+  explicit MP4ContainerParser(const MediaContainerType& aType)
     : ContainerParser(aType)
   {}
 
   MediaResult IsInitSegmentPresent(MediaByteBuffer* aData) override
   {
     ContainerParser::IsInitSegmentPresent(aData);
     // Each MP4 atom has a chunk size and chunk type. The root chunk in an MP4
     // file is the 'ftyp' atom followed by a file type. We just check for a
@@ -373,19 +374,19 @@ public:
         RESULT_DETAIL("Invalid Box:%s", parser.LastInvalidBox()));
     }
     return parser.StartWithMediaSegment() ? NS_OK : NS_ERROR_NOT_AVAILABLE;
   }
 
 private:
   class AtomParser {
   public:
-    AtomParser(const nsACString& aType, const MediaByteBuffer* aData)
+    AtomParser(const MediaContainerType& aType, const MediaByteBuffer* aData)
     {
-      const nsCString mType(aType); // for logging macro.
+      const MediaContainerType mType(aType); // for logging macro.
       mp4_demuxer::ByteReader reader(aData);
       mp4_demuxer::AtomType initAtom("ftyp");
       mp4_demuxer::AtomType mediaAtom("moof");
 
       // Valid top-level boxes defined in ISO/IEC 14496-12 (Table 1)
       static const mp4_demuxer::AtomType validBoxes[] = {
         "ftyp", "moov", // init segment
         "pdin", "free", "sidx", // optional prior moov box
@@ -466,17 +467,18 @@ private:
 
 public:
   MediaResult ParseStartAndEndTimestamps(MediaByteBuffer* aData,
                                          int64_t& aStart,
                                          int64_t& aEnd) override
   {
     bool initSegment = NS_SUCCEEDED(IsInitSegmentPresent(aData));
     if (initSegment) {
-      mResource = new SourceBufferResource(NS_LITERAL_CSTRING("video/mp4"));
+      mResource = new SourceBufferResource(
+                        MediaContainerType(MEDIAMIMETYPE("video/mp4")));
       mStream = new MP4Stream(mResource);
       // We use a timestampOffset of 0 for ContainerParser, and require
       // consumers of ParseStartAndEndTimestamps to add their timestamp offset
       // manually. This allows the ContainerParser to be shared across different
       // timestampOffsets.
       mParser = new mp4_demuxer::MoofParser(mStream, 0, /* aIsAudio = */ false);
       mInitData = new MediaByteBuffer();
     } else if (!mStream || !mParser) {
@@ -542,17 +544,17 @@ private:
   RefPtr<MP4Stream> mStream;
   nsAutoPtr<mp4_demuxer::MoofParser> mParser;
 };
 #endif // MOZ_FMP4
 
 #ifdef MOZ_FMP4
 class ADTSContainerParser : public ContainerParser {
 public:
-  explicit ADTSContainerParser(const nsACString& aType)
+  explicit ADTSContainerParser(const MediaContainerType& aType)
     : ContainerParser(aType)
   {}
 
   typedef struct {
     size_t header_length; // Length of just the initialization data.
     size_t frame_length;  // Includes header_length;
     uint8_t aac_frames;   // Number of AAC frames in the ADTS frame.
     bool have_crc;
@@ -687,27 +689,29 @@ public:
   int64_t GetRoundingError() override
   {
     return 0;
   }
 };
 #endif // MOZ_FMP4
 
 /*static*/ ContainerParser*
-ContainerParser::CreateForMIMEType(const nsACString& aType)
+ContainerParser::CreateForMIMEType(const MediaContainerType& aType)
 {
-  if (aType.LowerCaseEqualsLiteral("video/webm") || aType.LowerCaseEqualsLiteral("audio/webm")) {
+  if (aType.Type() == MEDIAMIMETYPE("video/webm")
+      || aType.Type() == MEDIAMIMETYPE("audio/webm")) {
     return new WebMContainerParser(aType);
   }
 
 #ifdef MOZ_FMP4
-  if (aType.LowerCaseEqualsLiteral("video/mp4") || aType.LowerCaseEqualsLiteral("audio/mp4")) {
+  if (aType.Type() == MEDIAMIMETYPE("video/mp4")
+      || aType.Type() == MEDIAMIMETYPE("audio/mp4")) {
     return new MP4ContainerParser(aType);
   }
-  if (aType.LowerCaseEqualsLiteral("audio/aac")) {
+  if (aType.Type() == MEDIAMIMETYPE("audio/aac")) {
     return new ADTSContainerParser(aType);
   }
 #endif
 
   return new ContainerParser(aType);
 }
 
 #undef MSE_DEBUG
--- a/dom/media/mediasource/ContainerParser.h
+++ b/dom/media/mediasource/ContainerParser.h
@@ -3,28 +3,28 @@
 /* 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_CONTAINERPARSER_H_
 #define MOZILLA_CONTAINERPARSER_H_
 
 #include "mozilla/RefPtr.h"
-#include "nsString.h"
+#include "MediaContainerType.h"
 #include "MediaResource.h"
 #include "MediaResult.h"
 
 namespace mozilla {
 
 class MediaByteBuffer;
 class SourceBufferResource;
 
 class ContainerParser {
 public:
-  explicit ContainerParser(const nsACString& aType);
+  explicit ContainerParser(const MediaContainerType& aType);
   virtual ~ContainerParser();
 
   // Return true if aData starts with an initialization segment.
   // The base implementation exists only for debug logging and is expected
   // to be called first from the overriding implementation.
   // Return NS_OK if segment is present, NS_ERROR_NOT_AVAILABLE if no sufficient
   // data is currently available to make a determination. Any other value
   // indicates an error.
@@ -68,23 +68,23 @@ public:
   MediaByteRange InitSegmentRange();
   // Returns the byte range of the first complete media segment header,
   // or an empty range if not complete.
   MediaByteRange MediaHeaderRange();
   // Returns the byte range of the first complete media segment or an empty
   // range if not complete.
   MediaByteRange MediaSegmentRange();
 
-  static ContainerParser* CreateForMIMEType(const nsACString& aType);
+  static ContainerParser* CreateForMIMEType(const MediaContainerType& aType);
 
 protected:
   RefPtr<MediaByteBuffer> mInitData;
   RefPtr<SourceBufferResource> mResource;
   bool mHasInitData;
   MediaByteRange mCompleteInitSegmentRange;
   MediaByteRange mCompleteMediaHeaderRange;
   MediaByteRange mCompleteMediaSegmentRange;
-  const nsCString mType;
+  const MediaContainerType mType;
 };
 
 } // namespace mozilla
 
 #endif /* MOZILLA_CONTAINERPARSER_H_ */
--- a/dom/media/mediasource/MediaSource.cpp
+++ b/dom/media/mediasource/MediaSource.cpp
@@ -5,17 +5,17 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "MediaSource.h"
 
 #include "AsyncEventRunner.h"
 #include "DecoderTraits.h"
 #include "Benchmark.h"
 #include "DecoderDoctorDiagnostics.h"
-#include "MediaContentType.h"
+#include "MediaContainerType.h"
 #include "MediaResult.h"
 #include "MediaSourceUtils.h"
 #include "SourceBuffer.h"
 #include "SourceBufferList.h"
 #include "mozilla/ErrorResult.h"
 #include "mozilla/FloatingPoint.h"
 #include "mozilla/Preferences.h"
 #include "mozilla/dom/BindingDeclarations.h"
@@ -67,17 +67,17 @@ namespace mozilla {
 //   * N/KN editions (Europe and Korea) of Windows 7/8/8.1/10 without the
 //     optional "Windows Media Feature Pack"
 // 2. If H264 hardware acceleration is not available.
 // 3. The CPU is considered to be fast enough
 static bool
 IsWebMForced(DecoderDoctorDiagnostics* aDiagnostics)
 {
   bool mp4supported =
-    DecoderTraits::IsMP4SupportedType(MediaContentType(MEDIAMIMETYPE("video/mp4")),
+    DecoderTraits::IsMP4SupportedType(MediaContainerType(MEDIAMIMETYPE("video/mp4")),
                                       aDiagnostics);
   bool hwsupported = gfx::gfxVars::CanUseHardwareVideoDecoding();
 #ifdef MOZ_WIDGET_ANDROID
   return !mp4supported || !hwsupported || VP9Benchmark::IsVP9DecodeFast() ||
          java::HardwareCodecCapabilityUtils::HasHWVP9();
 #else
   return !mp4supported || !hwsupported || VP9Benchmark::IsVP9DecodeFast();
 #endif
@@ -88,29 +88,29 @@ namespace dom {
 /* static */
 nsresult
 MediaSource::IsTypeSupported(const nsAString& aType, DecoderDoctorDiagnostics* aDiagnostics)
 {
   if (aType.IsEmpty()) {
     return NS_ERROR_DOM_TYPE_ERR;
   }
 
-  Maybe<MediaContentType> contentType = MakeMediaContentType(aType);
-  if (!contentType) {
+  Maybe<MediaContainerType> containerType = MakeMediaContainerType(aType);
+  if (!containerType) {
     return NS_ERROR_DOM_NOT_SUPPORTED_ERR;
   }
 
-  if (DecoderTraits::CanHandleContentType(*contentType, aDiagnostics)
+  if (DecoderTraits::CanHandleContainerType(*containerType, aDiagnostics)
       == CANPLAY_NO) {
     return NS_ERROR_DOM_NOT_SUPPORTED_ERR;
   }
 
   // Now we know that this media type could be played.
   // MediaSource imposes extra restrictions, and some prefs.
-  const MediaMIMEType& mimeType = contentType->Type();
+  const MediaMIMEType& mimeType = containerType->Type();
   if (mimeType == MEDIAMIMETYPE("video/mp4") ||
       mimeType == MEDIAMIMETYPE("audio/mp4")) {
     if (!Preferences::GetBool("media.mediasource.mp4.enabled", false)) {
       return NS_ERROR_DOM_NOT_SUPPORTED_ERR;
     }
     return NS_OK;
   }
   if (mimeType == MEDIAMIMETYPE("video/webm")) {
@@ -233,23 +233,22 @@ MediaSource::AddSourceBuffer(const nsASt
   if (mSourceBuffers->Length() >= MAX_SOURCE_BUFFERS) {
     aRv.Throw(NS_ERROR_DOM_QUOTA_EXCEEDED_ERR);
     return nullptr;
   }
   if (mReadyState != MediaSourceReadyState::Open) {
     aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
     return nullptr;
   }
-  Maybe<MediaContentType> contentType = MakeMediaContentType(aType);
-  if (!contentType) {
+  Maybe<MediaContainerType> containerType = MakeMediaContainerType(aType);
+  if (!containerType) {
     aRv.Throw(NS_ERROR_DOM_NOT_SUPPORTED_ERR);
     return nullptr;
   }
-  const nsACString& mimeType = contentType->Type().AsString();
-  RefPtr<SourceBuffer> sourceBuffer = new SourceBuffer(this, mimeType);
+  RefPtr<SourceBuffer> sourceBuffer = new SourceBuffer(this, *containerType);
   if (!sourceBuffer) {
     aRv.Throw(NS_ERROR_FAILURE); // XXX need a better error here
     return nullptr;
   }
   mSourceBuffers->Append(sourceBuffer);
   MSE_DEBUG("sourceBuffer=%p", sourceBuffer.get());
   return sourceBuffer.forget();
 }
--- a/dom/media/mediasource/MediaSourceResource.h
+++ b/dom/media/mediasource/MediaSourceResource.h
@@ -8,27 +8,29 @@
 #define MOZILLA_MEDIASOURCERESOURCE_H_
 
 #include "MediaResource.h"
 #include "mozilla/Monitor.h"
 #include "mozilla/Logging.h"
 
 extern mozilla::LogModule* GetMediaSourceLog();
 
-#define MSE_DEBUG(arg, ...) MOZ_LOG(GetMediaSourceLog(), mozilla::LogLevel::Debug, ("MediaSourceResource(%p:%s)::%s: " arg, this, mType.get(), __func__, ##__VA_ARGS__))
+#define MSE_DEBUG(arg, ...) MOZ_LOG(GetMediaSourceLog(), mozilla::LogLevel::Debug, ("MediaSourceResource(%p:%s)::%s: " arg, this, mType.OriginalString().Data(), __func__, ##__VA_ARGS__))
 
 #define UNIMPLEMENTED() MSE_DEBUG("UNIMPLEMENTED FUNCTION at %s:%d", __FILE__, __LINE__)
 
 namespace mozilla {
 
 class MediaSourceResource final : public MediaResource
 {
 public:
   explicit MediaSourceResource(nsIPrincipal* aPrincipal = nullptr)
     : mPrincipal(aPrincipal)
+      // Fake-but-valid MIME type, unused but necessary to implement GetContentType().
+    , mType(MEDIAMIMETYPE("application/x.mediasource"))
     , mMonitor("MediaSourceResource")
     , mEnded(false)
     {}
 
   nsresult Close() override { return NS_OK; }
   void Suspend(bool aCloseImmediately) override { UNIMPLEMENTED(); }
   void Resume() override { UNIMPLEMENTED(); }
   bool CanClone() override { UNIMPLEMENTED(); return false; }
@@ -57,17 +59,17 @@ public:
   nsresult GetCachedRanges(MediaByteRangeSet& aRanges) override
   {
     UNIMPLEMENTED();
     aRanges += MediaByteRange(0, GetLength());
     return NS_OK;
   }
 
   bool IsTransportSeekable() override { return true; }
-  const nsCString& GetContentType() const override { return mType; }
+  const MediaContainerType& GetContentType() const override { return mType; }
 
   bool IsLiveStream() override
   {
     MonitorAutoLock mon(mMonitor);
     return !mEnded;
   }
   void SetEnded(bool aEnded)
   {
@@ -80,28 +82,28 @@ public:
     MonitorAutoLock mon(mMonitor);
     return !mEnded;
   }
 
 private:
   size_t SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const override
   {
     size_t size = MediaResource::SizeOfExcludingThis(aMallocSizeOf);
-    size += mType.SizeOfExcludingThisIfUnshared(aMallocSizeOf);
+    size += mType.SizeOfExcludingThis(aMallocSizeOf);
 
     return size;
   }
 
   size_t SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const override
   {
     return aMallocSizeOf(this) + SizeOfExcludingThis(aMallocSizeOf);
   }
 
   RefPtr<nsIPrincipal> mPrincipal;
-  const nsCString mType;
+  const MediaContainerType mType;
   Monitor mMonitor;
   bool mEnded; // protected by mMonitor
 };
 
 } // namespace mozilla
 
 #undef MSE_DEBUG
 #undef UNIMPLEMENTED
--- a/dom/media/mediasource/SourceBuffer.cpp
+++ b/dom/media/mediasource/SourceBuffer.cpp
@@ -30,19 +30,19 @@
 #endif
 
 struct JSContext;
 class JSObject;
 
 extern mozilla::LogModule* GetMediaSourceLog();
 extern mozilla::LogModule* GetMediaSourceAPILog();
 
-#define MSE_DEBUG(arg, ...) MOZ_LOG(GetMediaSourceLog(), mozilla::LogLevel::Debug, ("SourceBuffer(%p:%s)::%s: " arg, this, mType.get(), __func__, ##__VA_ARGS__))
-#define MSE_DEBUGV(arg, ...) MOZ_LOG(GetMediaSourceLog(), mozilla::LogLevel::Verbose, ("SourceBuffer(%p:%s)::%s: " arg, this, mType.get(), __func__, ##__VA_ARGS__))
-#define MSE_API(arg, ...) MOZ_LOG(GetMediaSourceAPILog(), mozilla::LogLevel::Debug, ("SourceBuffer(%p:%s)::%s: " arg, this, mType.get(), __func__, ##__VA_ARGS__))
+#define MSE_DEBUG(arg, ...) MOZ_LOG(GetMediaSourceLog(), mozilla::LogLevel::Debug, ("SourceBuffer(%p:%s)::%s: " arg, this, mType.OriginalString().Data(), __func__, ##__VA_ARGS__))
+#define MSE_DEBUGV(arg, ...) MOZ_LOG(GetMediaSourceLog(), mozilla::LogLevel::Verbose, ("SourceBuffer(%p:%s)::%s: " arg, this, mType.OriginalString().Data(), __func__, ##__VA_ARGS__))
+#define MSE_API(arg, ...) MOZ_LOG(GetMediaSourceAPILog(), mozilla::LogLevel::Debug, ("SourceBuffer(%p:%s)::%s: " arg, this, mType.OriginalString().Data(), __func__, ##__VA_ARGS__))
 
 namespace mozilla {
 
 using media::TimeUnit;
 typedef SourceBufferAttributes::AppendState AppendState;
 
 namespace dom {
 
@@ -292,21 +292,22 @@ void
 SourceBuffer::Ended()
 {
   MOZ_ASSERT(NS_IsMainThread());
   MOZ_ASSERT(IsAttached());
   MSE_DEBUG("Ended");
   mTrackBuffersManager->Ended();
 }
 
-SourceBuffer::SourceBuffer(MediaSource* aMediaSource, const nsACString& aType)
+SourceBuffer::SourceBuffer(MediaSource* aMediaSource,
+                           const MediaContainerType& aType)
   : DOMEventTargetHelper(aMediaSource->GetParentObject())
   , mMediaSource(aMediaSource)
-  , mCurrentAttributes(aType.LowerCaseEqualsLiteral("audio/mpeg") ||
-                       aType.LowerCaseEqualsLiteral("audio/aac"))
+  , mCurrentAttributes(aType.Type() == MEDIAMIMETYPE("audio/mpeg") ||
+                       aType.Type() == MEDIAMIMETYPE("audio/aac"))
   , mUpdating(false)
   , mActive(false)
   , mType(aType)
 {
   MOZ_ASSERT(NS_IsMainThread());
   MOZ_ASSERT(aMediaSource);
 
   mTrackBuffersManager =
--- a/dom/media/mediasource/SourceBuffer.h
+++ b/dom/media/mediasource/SourceBuffer.h
@@ -3,31 +3,31 @@
 /* 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_SourceBuffer_h_
 #define mozilla_dom_SourceBuffer_h_
 
 #include "mozilla/MozPromise.h"
+#include "MediaContainerType.h"
 #include "MediaSource.h"
 #include "js/RootingAPI.h"
 #include "mozilla/Assertions.h"
 #include "mozilla/Atomics.h"
 #include "mozilla/Attributes.h"
 #include "mozilla/DOMEventTargetHelper.h"
 #include "mozilla/UniquePtr.h"
 #include "mozilla/dom/SourceBufferBinding.h"
 #include "mozilla/dom/TypedArray.h"
 #include "mozilla/mozalloc.h"
 #include "nsCOMPtr.h"
 #include "nsCycleCollectionNoteChild.h"
 #include "nsCycleCollectionParticipant.h"
 #include "nsISupports.h"
-#include "nsString.h"
 #include "nscore.h"
 #include "TrackBuffersManager.h"
 #include "SourceBufferTask.h"
 
 class JSObject;
 struct JSContext;
 
 namespace mozilla {
@@ -94,17 +94,17 @@ public:
   IMPL_EVENT_HANDLER(error);
   IMPL_EVENT_HANDLER(abort);
 
   /** End WebIDL Methods. */
 
   NS_DECL_ISUPPORTS_INHERITED
   NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(SourceBuffer, DOMEventTargetHelper)
 
-  SourceBuffer(MediaSource* aMediaSource, const nsACString& aType);
+  SourceBuffer(MediaSource* aMediaSource, const MediaContainerType& aType);
 
   MediaSource* GetParentObject() const;
 
   JSObject* WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) override;
 
   // Notify the SourceBuffer that it has been detached from the
   // MediaSource's sourceBuffer list.
   void Detach();
@@ -172,17 +172,17 @@ private:
   SourceBufferAttributes mCurrentAttributes;
 
   bool mUpdating;
 
   mozilla::Atomic<bool> mActive;
 
   MozPromiseRequestHolder<SourceBufferTask::AppendPromise> mPendingAppend;
   MozPromiseRequestHolder<SourceBufferTask::RangeRemovalPromise> mPendingRemoval;
-  const nsCString mType;
+  const MediaContainerType mType;
 
   RefPtr<TimeRanges> mBuffered;
 };
 
 } // namespace dom
 
 } // namespace mozilla
 
--- a/dom/media/mediasource/SourceBufferResource.cpp
+++ b/dom/media/mediasource/SourceBufferResource.cpp
@@ -14,18 +14,18 @@
 #include "MediaData.h"
 
 mozilla::LogModule* GetSourceBufferResourceLog()
 {
   static mozilla::LazyLogModule sLogModule("SourceBufferResource");
   return sLogModule;
 }
 
-#define SBR_DEBUG(arg, ...) MOZ_LOG(GetSourceBufferResourceLog(), mozilla::LogLevel::Debug, ("SourceBufferResource(%p:%s)::%s: " arg, this, mType.get(), __func__, ##__VA_ARGS__))
-#define SBR_DEBUGV(arg, ...) MOZ_LOG(GetSourceBufferResourceLog(), mozilla::LogLevel::Verbose, ("SourceBufferResource(%p:%s)::%s: " arg, this, mType.get(), __func__, ##__VA_ARGS__))
+#define SBR_DEBUG(arg, ...) MOZ_LOG(GetSourceBufferResourceLog(), mozilla::LogLevel::Debug, ("SourceBufferResource(%p:%s)::%s: " arg, this, mType.OriginalString().Data(), __func__, ##__VA_ARGS__))
+#define SBR_DEBUGV(arg, ...) MOZ_LOG(GetSourceBufferResourceLog(), mozilla::LogLevel::Verbose, ("SourceBufferResource(%p:%s)::%s: " arg, this, mType.OriginalString().Data(), __func__, ##__VA_ARGS__))
 
 namespace mozilla {
 
 nsresult
 SourceBufferResource::Close()
 {
   ReentrantMonitorAutoEnter mon(mMonitor);
   SBR_DEBUG("Close");
@@ -163,17 +163,17 @@ SourceBufferResource::Ended()
   mon.NotifyAll();
 }
 
 SourceBufferResource::~SourceBufferResource()
 {
   SBR_DEBUG("");
 }
 
-SourceBufferResource::SourceBufferResource(const nsACString& aType)
+SourceBufferResource::SourceBufferResource(const MediaContainerType& aType)
   : mType(aType)
   , mMonitor("mozilla::SourceBufferResource::mMonitor")
   , mOffset(0)
   , mClosed(false)
   , mEnded(false)
 {
   SBR_DEBUG("");
 }
--- a/dom/media/mediasource/SourceBufferResource.h
+++ b/dom/media/mediasource/SourceBufferResource.h
@@ -10,17 +10,16 @@
 #include "MediaCache.h"
 #include "MediaResource.h"
 #include "ResourceQueue.h"
 #include "mozilla/Attributes.h"
 #include "mozilla/ReentrantMonitor.h"
 #include "nsCOMPtr.h"
 #include "nsError.h"
 #include "nsIPrincipal.h"
-#include "nsString.h"
 #include "nsTArray.h"
 #include "nscore.h"
 #include "mozilla/Logging.h"
 
 #define UNIMPLEMENTED() { /* Logging this is too spammy to do by default */ }
 
 class nsIStreamListener;
 
@@ -33,17 +32,17 @@ namespace dom {
 
 class SourceBuffer;
 
 } // namespace dom
 
 class SourceBufferResource final : public MediaResource
 {
 public:
-  explicit SourceBufferResource(const nsACString& aType);
+  explicit SourceBufferResource(const MediaContainerType& aType);
   nsresult Close() override;
   void Suspend(bool aCloseImmediately) override { UNIMPLEMENTED(); }
   void Resume() override { UNIMPLEMENTED(); }
   already_AddRefed<nsIPrincipal> GetCurrentPrincipal() override { UNIMPLEMENTED(); return nullptr; }
   already_AddRefed<MediaResource> CloneData(MediaResourceCallback*) override { UNIMPLEMENTED(); return nullptr; }
   void SetReadMode(MediaCacheStream::ReadMode aMode) override { UNIMPLEMENTED(); }
   void SetPlaybackRate(uint32_t aBytesPerSecond) override { UNIMPLEMENTED(); }
   nsresult ReadAt(int64_t aOffset, char* aBuffer, uint32_t aCount, uint32_t* aBytes) override;
@@ -75,24 +74,24 @@ public:
     ReentrantMonitorAutoEnter mon(mMonitor);
     if (mInputBuffer.GetLength()) {
       aRanges += MediaByteRange(mInputBuffer.GetOffset(),
                                 mInputBuffer.GetLength());
     }
     return NS_OK;
   }
 
-  const nsCString& GetContentType() const override { return mType; }
+  const MediaContainerType& GetContentType() const override { return mType; }
 
   size_t SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const override
   {
     ReentrantMonitorAutoEnter mon(mMonitor);
 
     size_t size = MediaResource::SizeOfExcludingThis(aMallocSizeOf);
-    size += mType.SizeOfExcludingThisIfUnshared(aMallocSizeOf);
+    size += mType.SizeOfExcludingThis(aMallocSizeOf);
     size += mInputBuffer.SizeOfExcludingThis(aMallocSizeOf);
 
     return size;
   }
 
   size_t SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const override
   {
     return aMallocSizeOf(this) + SizeOfExcludingThis(aMallocSizeOf);
@@ -133,17 +132,17 @@ public:
     mInputBuffer.Dump(aPath);
   }
 #endif
 
 private:
   virtual ~SourceBufferResource();
   nsresult ReadAtInternal(int64_t aOffset, char* aBuffer, uint32_t aCount, uint32_t* aBytes, bool aMayBlock);
 
-  const nsCString mType;
+  const MediaContainerType mType;
 
   // Provides synchronization between SourceBuffers and InputAdapters.
   // Protects all of the member variables below.  Read() will await a
   // Notify() (from Seek, AppendData, Ended, or Close) when insufficient
   // data is available in mData.
   mutable ReentrantMonitor mMonitor;
 
   // The buffer holding resource data.
--- a/dom/media/mediasource/TrackBuffersManager.cpp
+++ b/dom/media/mediasource/TrackBuffersManager.cpp
@@ -18,25 +18,25 @@
 #ifdef MOZ_FMP4
 #include "MP4Demuxer.h"
 #endif
 
 #include <limits>
 
 extern mozilla::LogModule* GetMediaSourceLog();
 
-#define MSE_DEBUG(arg, ...) MOZ_LOG(GetMediaSourceLog(), mozilla::LogLevel::Debug, ("TrackBuffersManager(%p:%s)::%s: " arg, this, mType.get(), __func__, ##__VA_ARGS__))
-#define MSE_DEBUGV(arg, ...) MOZ_LOG(GetMediaSourceLog(), mozilla::LogLevel::Verbose, ("TrackBuffersManager(%p:%s)::%s: " arg, this, mType.get(), __func__, ##__VA_ARGS__))
+#define MSE_DEBUG(arg, ...) MOZ_LOG(GetMediaSourceLog(), mozilla::LogLevel::Debug, ("TrackBuffersManager(%p:%s)::%s: " arg, this, mType.OriginalString().Data(), __func__, ##__VA_ARGS__))
+#define MSE_DEBUGV(arg, ...) MOZ_LOG(GetMediaSourceLog(), mozilla::LogLevel::Verbose, ("TrackBuffersManager(%p:%s)::%s: " arg, this, mType.OriginalString().Data(), __func__, ##__VA_ARGS__))
 
 mozilla::LogModule* GetMediaSourceSamplesLog()
 {
   static mozilla::LazyLogModule sLogModule("MediaSourceSamples");
   return sLogModule;
 }
-#define SAMPLE_DEBUG(arg, ...) MOZ_LOG(GetMediaSourceSamplesLog(), mozilla::LogLevel::Debug, ("TrackBuffersManager(%p:%s)::%s: " arg, this, mType.get(), __func__, ##__VA_ARGS__))
+#define SAMPLE_DEBUG(arg, ...) MOZ_LOG(GetMediaSourceSamplesLog(), mozilla::LogLevel::Debug, ("TrackBuffersManager(%p:%s)::%s: " arg, this, mType.OriginalString().Data(), __func__, ##__VA_ARGS__))
 
 namespace mozilla {
 
 using dom::SourceBufferAppendMode;
 using media::TimeUnit;
 using media::TimeInterval;
 using media::TimeIntervals;
 typedef SourceBufferTask::AppendBufferResult AppendBufferResult;
@@ -80,17 +80,17 @@ public:
   }
 private:
   RefPtr<AbstractMediaDecoder> mDecoder;
   nsTArray<uint8_t> mInitData;
   nsString mInitDataType;
 };
 
 TrackBuffersManager::TrackBuffersManager(MediaSourceDecoder* aParentDecoder,
-                                         const nsACString& aType)
+                                         const MediaContainerType& aType)
   : mInputBuffer(new MediaByteBuffer)
   , mBufferFull(false)
   , mFirstInitializationSegmentReceived(false)
   , mNewMediaSegmentStarted(false)
   , mActiveTrack(false)
   , mType(aType)
   , mParser(ContainerParser::CreateForMIMEType(aType))
   , mProcessedInput(0)
@@ -805,25 +805,25 @@ TrackBuffersManager::ShutdownDemuxers()
   mLastParsedEndTime.reset();
 }
 
 void
 TrackBuffersManager::CreateDemuxerforMIMEType()
 {
   ShutdownDemuxers();
 
-  if (mType.LowerCaseEqualsLiteral("video/webm") ||
-      mType.LowerCaseEqualsLiteral("audio/webm")) {
+  if (mType.Type() == MEDIAMIMETYPE("video/webm") ||
+      mType.Type() == MEDIAMIMETYPE("audio/webm")) {
     mInputDemuxer = new WebMDemuxer(mCurrentInputBuffer, true /* IsMediaSource*/ );
     return;
   }
 
 #ifdef MOZ_FMP4
-  if (mType.LowerCaseEqualsLiteral("video/mp4") ||
-      mType.LowerCaseEqualsLiteral("audio/mp4")) {
+  if (mType.Type() == MEDIAMIMETYPE("video/mp4")
+      || mType.Type() == MEDIAMIMETYPE("audio/mp4")) {
     mInputDemuxer = new MP4Demuxer(mCurrentInputBuffer);
     return;
   }
 #endif
   NS_WARNING("Not supported (yet)");
   return;
 }
 
@@ -1693,18 +1693,18 @@ TrackBuffersManager::InsertFrames(TrackB
   // 15. Remove decoding dependencies of the coded frames removed in the previous step:
   // Remove all coded frames between the coded frames removed in the previous step and the next random access point after those removed frames.
 
   TimeIntervals intersection = trackBuffer.mBufferedRanges;
   intersection.Intersection(aIntervals);
 
   if (intersection.Length()) {
     if (aSamples[0]->mKeyframe &&
-        (mType.LowerCaseEqualsLiteral("video/webm") ||
-         mType.LowerCaseEqualsLiteral("audio/webm"))) {
+        (mType.Type() == MEDIAMIMETYPE("video/webm")
+         || mType.Type() == MEDIAMIMETYPE("audio/webm"))) {
       // We are starting a new GOP, we do not have to worry about breaking an
       // existing current coded frame group. Reset the next insertion index
       // so the search for when to start our frames removal can be exhaustive.
       // This is a workaround for bug 1276184 and only until either bug 1277733
       // or bug 1209386 is fixed.
       // With the webm container, we can't always properly determine the
       // duration of the last frame, which may cause the last frame of a cluster
       // to overlap the following frame.
--- a/dom/media/mediasource/TrackBuffersManager.h
+++ b/dom/media/mediasource/TrackBuffersManager.h
@@ -8,25 +8,25 @@
 #define MOZILLA_TRACKBUFFERSMANAGER_H_
 
 #include "mozilla/Atomics.h"
 #include "mozilla/Maybe.h"
 #include "mozilla/Monitor.h"
 #include "AutoTaskQueue.h"
 #include "mozilla/dom/SourceBufferBinding.h"
 
+#include "MediaContainerType.h"
 #include "MediaData.h"
 #include "MediaDataDemuxer.h"
 #include "MediaResult.h"
 #include "MediaSourceDecoder.h"
 #include "SourceBufferTask.h"
 #include "TimeUnits.h"
 #include "nsAutoPtr.h"
 #include "nsProxyRelease.h"
-#include "nsString.h"
 #include "nsTArray.h"
 
 namespace mozilla {
 
 class ContainerParser;
 class MediaByteBuffer;
 class MediaRawData;
 class MediaSourceDemuxer;
@@ -86,17 +86,17 @@ public:
   typedef TrackInfo::TrackType TrackType;
   typedef MediaData::Type MediaType;
   typedef nsTArray<RefPtr<MediaRawData>> TrackBuffer;
   typedef SourceBufferTask::AppendPromise AppendPromise;
   typedef SourceBufferTask::RangeRemovalPromise RangeRemovalPromise;
 
   // Interface for SourceBuffer
   TrackBuffersManager(MediaSourceDecoder* aParentDecoder,
-                      const nsACString& aType);
+                      const MediaContainerType& aType);
 
   // Queue a task to add data to the end of the input buffer and run the MSE
   // Buffer Append Algorithm
   // 3.5.5 Buffer Append Algorithm.
   // http://w3c.github.io/media-source/index.html#sourcebuffer-buffer-append
   RefPtr<AppendPromise> AppendData(MediaByteBuffer* aData,
                                    const SourceBufferAttributes& aAttributes);
 
@@ -207,17 +207,17 @@ private:
   RefPtr<MediaByteBuffer> mInputBuffer;
   // Buffer full flag as per https://w3c.github.io/media-source/#sourcebuffer-buffer-full-flag.
   // Accessed on both the main thread and the task queue.
   Atomic<bool> mBufferFull;
   bool mFirstInitializationSegmentReceived;
   // Set to true once a new segment is started.
   bool mNewMediaSegmentStarted;
   bool mActiveTrack;
-  nsCString mType;
+  MediaContainerType mType;
 
   // ContainerParser objects and methods.
   // Those are used to parse the incoming input buffer.
 
   // Recreate the ContainerParser and if aReuseInitData is true then
   // feed it with the previous init segment found.
   void RecreateParser(bool aReuseInitData);
   nsAutoPtr<ContainerParser> mParser;
--- a/dom/media/mediasource/gtest/TestContainerParser.cpp
+++ b/dom/media/mediasource/gtest/TestContainerParser.cpp
@@ -8,43 +8,45 @@
 
 #include "ContainerParser.h"
 #include "mozilla/ArrayUtils.h"
 #include "nsAutoPtr.h"
 
 using namespace mozilla;
 
 TEST(ContainerParser, MIMETypes) {
-  const char* content_types[] = {
+  const char* containerTypes[] = {
     "video/webm",
     "audio/webm",
     "video/mp4",
     "audio/mp4",
     "audio/aac"
   };
   nsAutoPtr<ContainerParser> parser;
-  for (size_t i = 0; i < ArrayLength(content_types); ++i) {
-    nsAutoCString content_type(content_types[i]);
-    parser = ContainerParser::CreateForMIMEType(content_type);
+  for (size_t i = 0; i < ArrayLength(containerTypes); ++i) {
+    Maybe<MediaContainerType> containerType = MakeMediaContainerType(containerTypes[i]);
+    ASSERT_TRUE(containerType.isSome());
+    parser = ContainerParser::CreateForMIMEType(*containerType);
     ASSERT_NE(parser, nullptr);
   }
 }
 
 
 already_AddRefed<MediaByteBuffer> make_adts_header()
 {
   const uint8_t test[] = { 0xff, 0xf1, 0x50, 0x80, 0x03, 0x1f, 0xfc };
   RefPtr<MediaByteBuffer> buffer(new MediaByteBuffer);
   buffer->AppendElements(test, ArrayLength(test));
   return buffer.forget();
 }
 
 TEST(ContainerParser, ADTSHeader) {
   nsAutoPtr<ContainerParser> parser;
-  parser = ContainerParser::CreateForMIMEType(NS_LITERAL_CSTRING("audio/aac"));
+  parser = ContainerParser::CreateForMIMEType(MediaContainerType(
+                                                MEDIAMIMETYPE("audio/aac")));
   ASSERT_NE(parser, nullptr);
 
   // Audio data should have no gaps.
   EXPECT_EQ(parser->GetRoundingError(), 0);
 
   // Test a valid header.
   RefPtr<MediaByteBuffer> header = make_adts_header();
   EXPECT_TRUE(NS_SUCCEEDED(parser->IsInitSegmentPresent(header)));
@@ -96,17 +98,18 @@ TEST(ContainerParser, ADTSHeader) {
   EXPECT_EQ(parser->InitSegmentRange(), MediaByteRange(0, int64_t(header->Length())));
   // Media segment range should be empty here.
   EXPECT_EQ(parser->MediaHeaderRange(), MediaByteRange());
   EXPECT_EQ(parser->MediaSegmentRange(), MediaByteRange());
 }
 
 TEST(ContainerParser, ADTSBlankMedia) {
   nsAutoPtr<ContainerParser> parser;
-  parser = ContainerParser::CreateForMIMEType(NS_LITERAL_CSTRING("audio/aac"));
+  parser = ContainerParser::CreateForMIMEType(MediaContainerType(
+                                                MEDIAMIMETYPE("audio/aac")));
   ASSERT_NE(parser, nullptr);
 
   // Audio data should have no gaps.
   EXPECT_EQ(parser->GetRoundingError(), 0);
 
   // Test the header only.
   RefPtr<MediaByteBuffer> header = make_adts_header();
   EXPECT_TRUE(NS_SUCCEEDED(parser->IsInitSegmentPresent(header)));
--- a/dom/media/moz.build
+++ b/dom/media/moz.build
@@ -95,17 +95,17 @@ EXPORTS += [
     'DecoderTraits.h',
     'DOMMediaStream.h',
     'EncodedBufferCache.h',
     'FileBlockCache.h',
     'FrameStatistics.h',
     'Intervals.h',
     'Latency.h',
     'MediaCache.h',
-    'MediaContentType.h',
+    'MediaContainerType.h',
     'MediaData.h',
     'MediaDataDemuxer.h',
     'MediaDecoder.h',
     'MediaDecoderOwner.h',
     'MediaDecoderReader.h',
     'MediaDecoderStateMachine.h',
     'MediaEventSource.h',
     'MediaFormatReader.h',
@@ -203,17 +203,17 @@ UNIFIED_SOURCES += [
     'DecoderDoctorDiagnostics.cpp',
     'DOMMediaStream.cpp',
     'EncodedBufferCache.cpp',
     'FileBlockCache.cpp',
     'GetUserMediaRequest.cpp',
     'GraphDriver.cpp',
     'Latency.cpp',
     'MediaCache.cpp',
-    'MediaContentType.cpp',
+    'MediaContainerType.cpp',
     'MediaData.cpp',
     'MediaDecoder.cpp',
     'MediaDecoderReader.cpp',
     'MediaDecoderReaderWrapper.cpp',
     'MediaDecoderStateMachine.cpp',
     'MediaDeviceInfo.cpp',
     'MediaDevices.cpp',
     'MediaFormatReader.cpp',
--- a/dom/media/ogg/OggDecoder.cpp
+++ b/dom/media/ogg/OggDecoder.cpp
@@ -1,65 +1,64 @@
 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 /* vim:set ts=2 sw=2 sts=2 et cindent: */
 /* 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 "MediaPrefs.h"
-#include "MediaContentType.h"
+#include "MediaContainerType.h"
 #include "MediaDecoderStateMachine.h"
 #include "MediaFormatReader.h"
 #include "OggDemuxer.h"
 #include "OggDecoder.h"
-#include "nsContentTypeParser.h"
 
 namespace mozilla {
 
 MediaDecoderStateMachine* OggDecoder::CreateStateMachine()
 {
   RefPtr<OggDemuxer> demuxer = new OggDemuxer(GetResource());
   RefPtr<MediaFormatReader> reader =
     new MediaFormatReader(this, demuxer, GetVideoFrameContainer());
   demuxer->SetChainingEvents(&reader->TimedMetadataProducer(),
                              &reader->MediaNotSeekableProducer());
   return new MediaDecoderStateMachine(this, reader);
 }
 
 /* static */
 bool
-OggDecoder::IsSupportedType(const MediaContentType& aContentType)
+OggDecoder::IsSupportedType(const MediaContainerType& aContainerType)
 {
   if (!MediaPrefs::OggEnabled()) {
     return false;
   }
 
-  if (aContentType.Type() != MEDIAMIMETYPE("audio/ogg") &&
-      aContentType.Type() != MEDIAMIMETYPE("video/ogg") &&
-      aContentType.Type() != MEDIAMIMETYPE("application/ogg")) {
+  if (aContainerType.Type() != MEDIAMIMETYPE("audio/ogg") &&
+      aContainerType.Type() != MEDIAMIMETYPE("video/ogg") &&
+      aContainerType.Type() != MEDIAMIMETYPE("application/ogg")) {
     return false;
   }
 
-  const bool isOggVideo = (aContentType.Type() != MEDIAMIMETYPE("audio/ogg"));
+  const bool isOggVideo = (aContainerType.Type() != MEDIAMIMETYPE("audio/ogg"));
 
-  const MediaCodecs& codecs = aContentType.ExtendedType().Codecs();
+  const MediaCodecs& codecs = aContainerType.ExtendedType().Codecs();
   if (codecs.IsEmpty()) {
     // WebM guarantees that the only codecs it contained are vp8, vp9, opus or vorbis.
     return true;
   }
   // Verify that all the codecs specified are ones that we expect that
   // we can play.
   for (const auto& codec : codecs.Range()) {
     if ((IsOpusEnabled() && codec.EqualsLiteral("opus")) ||
         codec.EqualsLiteral("vorbis") ||
         (MediaPrefs::FlacInOgg() && codec.EqualsLiteral("flac"))) {
       continue;
     }
-    // Note: Only accept Theora in a video content type, not in an audio
-    // content type.
+    // Note: Only accept Theora in a video container type, not in an audio
+    // container type.
     if (isOggVideo && codec.EqualsLiteral("theora")) {
       continue;
     }
     // Some unsupported codec.
     return false;
   }
   return true;
 }
--- a/dom/media/ogg/OggDecoder.h
+++ b/dom/media/ogg/OggDecoder.h
@@ -5,17 +5,17 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 #if !defined(OggDecoder_h_)
 #define OggDecoder_h_
 
 #include "MediaDecoder.h"
 
 namespace mozilla {
 
-class MediaContentType;
+class MediaContainerType;
 
 class OggDecoder : public MediaDecoder
 {
 public:
   explicit OggDecoder(MediaDecoderOwner* aOwner)
     : MediaDecoder(aOwner)
     , mShutdownBitMonitor("mShutdownBitMonitor")
     , mShutdownBit(false)
@@ -34,20 +34,20 @@ public:
   // protect the general state with a lock, so we make a special copy and a
   // special-purpose lock. This method may be called on any thread.
   bool IsOggDecoderShutdown() override
   {
     MonitorAutoLock lock(mShutdownBitMonitor);
     return mShutdownBit;
   }
 
-  // Returns true if aContentType is an Ogg type that we think we can render
+  // Returns true if aContainerType is an Ogg type that we think we can render
   // with an enabled platform decoder backend.
   // If provided, codecs are checked for support.
-  static bool IsSupportedType(const MediaContentType& aContentType);
+  static bool IsSupportedType(const MediaContainerType& aContainerType);
 
 protected:
   void ShutdownBitChanged() override
   {
     MonitorAutoLock lock(mShutdownBitMonitor);
     mShutdownBit = mStateMachineIsShutdown;
   }
 
--- a/dom/media/wave/WaveDecoder.cpp
+++ b/dom/media/wave/WaveDecoder.cpp
@@ -1,16 +1,16 @@
 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 /* vim:set ts=2 sw=2 sts=2 et cindent: */
 /* 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 "WaveDemuxer.h"
-#include "MediaContentType.h"
+#include "MediaContainerType.h"
 #include "MediaDecoderStateMachine.h"
 #include "WaveDecoder.h"
 #include "MediaFormatReader.h"
 #include "PDMFactory.h"
 
 namespace mozilla {
 
 MediaDecoder*
@@ -22,27 +22,27 @@ WaveDecoder::Clone(MediaDecoderOwner* aO
 MediaDecoderStateMachine*
 WaveDecoder::CreateStateMachine()
 {
   return new MediaDecoderStateMachine(
     this, new MediaFormatReader(this, new WAVDemuxer(GetResource())));
 }
 
 /* static */ bool
-WaveDecoder::IsSupportedType(const MediaContentType& aContentType)
+WaveDecoder::IsSupportedType(const MediaContainerType& aContainerType)
 {
   if (!IsWaveEnabled()) {
     return false;
   }
-  if (aContentType.Type() == MEDIAMIMETYPE("audio/wave")
-      || aContentType.Type() == MEDIAMIMETYPE("audio/x-wav")
-      || aContentType.Type() == MEDIAMIMETYPE("audio/wav")
-      || aContentType.Type() == MEDIAMIMETYPE("audio/x-pn-wav")) {
-    return (aContentType.ExtendedType().Codecs().IsEmpty()
-            || aContentType.ExtendedType().Codecs().AsString().EqualsASCII("1")
-            || aContentType.ExtendedType().Codecs().AsString().EqualsASCII("6")
-            || aContentType.ExtendedType().Codecs().AsString().EqualsASCII("7"));
+  if (aContainerType.Type() == MEDIAMIMETYPE("audio/wave")
+      || aContainerType.Type() == MEDIAMIMETYPE("audio/x-wav")
+      || aContainerType.Type() == MEDIAMIMETYPE("audio/wav")
+      || aContainerType.Type() == MEDIAMIMETYPE("audio/x-pn-wav")) {
+    return (aContainerType.ExtendedType().Codecs().IsEmpty()
+            || aContainerType.ExtendedType().Codecs().AsString().EqualsASCII("1")
+            || aContainerType.ExtendedType().Codecs().AsString().EqualsASCII("6")
+            || aContainerType.ExtendedType().Codecs().AsString().EqualsASCII("7"));
   }
 
   return false;
 }
 
 } // namespace mozilla
--- a/dom/media/wave/WaveDecoder.h
+++ b/dom/media/wave/WaveDecoder.h
@@ -5,26 +5,26 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 #if !defined(WaveDecoder_h_)
 #define WaveDecoder_h_
 
 #include "MediaDecoder.h"
 
 namespace mozilla {
 
-class MediaContentType;
+class MediaContainerType;
 
 class WaveDecoder : public MediaDecoder
 {
 public:
   // MediaDecoder interface.
   explicit WaveDecoder(MediaDecoderOwner* aOwner) : MediaDecoder(aOwner) {}
   MediaDecoder* Clone(MediaDecoderOwner* aOwner) override;
   MediaDecoderStateMachine* CreateStateMachine() override;
 
   // Returns true if the Wave backend is pref'ed on, and we're running on a
   // platform that is likely to have decoders for the format.
-  static bool IsSupportedType(const MediaContentType& aContentType);
+  static bool IsSupportedType(const MediaContainerType& aContainerType);
 };
 
 } // namespace mozilla
 
 #endif
--- a/dom/media/webaudio/MediaBufferDecoder.cpp
+++ b/dom/media/webaudio/MediaBufferDecoder.cpp
@@ -13,17 +13,17 @@
 #include <speex/speex_resampler.h>
 #include "nsXPCOMCIDInternal.h"
 #include "nsComponentManagerUtils.h"
 #include "MediaDecoderReader.h"
 #include "BufferMediaResource.h"
 #include "DecoderTraits.h"
 #include "AudioContext.h"
 #include "AudioBuffer.h"
-#include "MediaContentType.h"
+#include "MediaContainerType.h"
 #include "nsContentUtils.h"
 #include "nsIScriptObjectPrincipal.h"
 #include "nsIScriptError.h"
 #include "nsMimeTypes.h"
 #include "VideoUtils.h"
 #include "WebAudioUtils.h"
 #include "mozilla/dom/Promise.h"
 #include "mozilla/Telemetry.h"
@@ -73,20 +73,20 @@ enum class PhaseEnum : int
   Decode,
   AllocateBuffer,
   Done
 };
 
 class MediaDecodeTask final : public Runnable
 {
 public:
-  MediaDecodeTask(const MediaContentType& aContentType, uint8_t* aBuffer,
+  MediaDecodeTask(const MediaContainerType& aContainerType, uint8_t* aBuffer,
                   uint32_t aLength,
                   WebAudioDecodeJob& aDecodeJob)
-    : mContentType(aContentType)
+    : mContainerType(aContainerType)
     , mBuffer(aBuffer)
     , mLength(aLength)
     , mDecodeJob(aDecodeJob)
     , mPhase(PhaseEnum::Decode)
     , mFirstFrameDecoded(false)
   {
     MOZ_ASSERT(aBuffer);
     MOZ_ASSERT(NS_IsMainThread());
@@ -127,17 +127,17 @@ private:
     // MediaDecoderReader expects that BufferDecoder is alive.
     // Destruct MediaDecoderReader first.
     mDecoderReader = nullptr;
     mBufferDecoder = nullptr;
     JS_free(nullptr, mBuffer);
   }
 
 private:
-  MediaContentType mContentType;
+  MediaContainerType mContainerType;
   uint8_t* mBuffer;
   uint32_t mLength;
   WebAudioDecodeJob& mDecodeJob;
   PhaseEnum mPhase;
   RefPtr<BufferDecoder> mBufferDecoder;
   RefPtr<MediaDecoderReader> mDecoderReader;
   MediaInfo mMediaInfo;
   MediaQueue<MediaData> mAudioQueue;
@@ -190,26 +190,26 @@ MediaDecodeTask::CreateReader()
   nsCOMPtr<nsIPrincipal> principal;
   nsCOMPtr<nsIScriptObjectPrincipal> sop = do_QueryInterface(mDecodeJob.mContext->GetParentObject());
   if (sop) {
     principal = sop->GetPrincipal();
   }
 
   RefPtr<BufferMediaResource> resource =
     new BufferMediaResource(static_cast<uint8_t*> (mBuffer),
-                            mLength, principal, mContentType.Type().AsString());
+                            mLength, principal, mContainerType);
 
   MOZ_ASSERT(!mBufferDecoder);
   mBufferDecoder = new BufferDecoder(resource,
     new BufferDecoderGMPCrashHelper(mDecodeJob.mContext->GetParentObject()));
 
   // If you change this list to add support for new decoders, please consider
   // updating HTMLMediaElement::CreateDecoder as well.
 
-  mDecoderReader = DecoderTraits::CreateReader(mContentType, mBufferDecoder);
+  mDecoderReader = DecoderTraits::CreateReader(mContainerType, mBufferDecoder);
 
   if (!mDecoderReader) {
     return false;
   }
 
   nsresult rv = mDecoderReader->Init();
   if (NS_FAILED(rv)) {
     return false;
@@ -271,17 +271,17 @@ MediaDecodeTask::OnMetadataRead(Metadata
     return;
   }
 
   nsCString codec;
   if (!mMediaInfo.mAudio.GetAsAudioInfo()->mMimeType.IsEmpty()) {
     codec = nsPrintfCString("webaudio; %s", mMediaInfo.mAudio.GetAsAudioInfo()->mMimeType.get());
   } else {
     codec = nsPrintfCString("webaudio;resource; %s",
-                            mContentType.Type().AsString().Data());
+                            mContainerType.Type().AsString().Data());
   }
 
   nsCOMPtr<nsIRunnable> task = NS_NewRunnableFunction([codec]() -> void {
     MOZ_ASSERT(!codec.IsEmpty());
     MOZ_LOG(gMediaDecoderLog,
             LogLevel::Debug,
             ("Telemetry (WebAudio) MEDIA_CODEC_USED= '%s'", codec.get()));
     Telemetry::Accumulate(Telemetry::ID::MEDIA_CODEC_USED, codec);
@@ -477,33 +477,33 @@ WebAudioDecodeJob::AllocateBuffer()
                                 mBuffer.forget(), rv);
   return !rv.Failed();
 }
 
 void
 AsyncDecodeWebAudio(const char* aContentType, uint8_t* aBuffer,
                     uint32_t aLength, WebAudioDecodeJob& aDecodeJob)
 {
-  Maybe<MediaContentType> contentType = MakeMediaContentType(aContentType);
+  Maybe<MediaContainerType> containerType = MakeMediaContainerType(aContentType);
   // Do not attempt to decode the media if we were not successful at sniffing
-  // the content type.
+  // the container type.
   if (!*aContentType ||
       strcmp(aContentType, APPLICATION_OCTET_STREAM) == 0 ||
-      !contentType) {
+      !containerType) {
     nsCOMPtr<nsIRunnable> event =
       new ReportResultTask(aDecodeJob,
                            &WebAudioDecodeJob::OnFailure,
                            WebAudioDecodeJob::UnknownContent);
     JS_free(nullptr, aBuffer);
     NS_DispatchToMainThread(event);
     return;
   }
 
   RefPtr<MediaDecodeTask> task =
-    new MediaDecodeTask(*contentType, aBuffer, aLength, aDecodeJob);
+    new MediaDecodeTask(*containerType, aBuffer, aLength, aDecodeJob);
   if (!task->CreateReader()) {
     nsCOMPtr<nsIRunnable> event =
       new ReportResultTask(aDecodeJob,
                            &WebAudioDecodeJob::OnFailure,
                            WebAudioDecodeJob::UnknownError);
     NS_DispatchToMainThread(event);
   } else {
     // If we did this without a temporary:
--- a/dom/media/webm/WebMDecoder.cpp
+++ b/dom/media/webm/WebMDecoder.cpp
@@ -1,56 +1,56 @@
 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 /* vim:set ts=2 sw=2 sts=2 et cindent: */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "mozilla/Preferences.h"
-#include "MediaContentType.h"
+#include "MediaContainerType.h"
 #include "MediaDecoderStateMachine.h"
 #include "WebMDemuxer.h"
 #include "WebMDecoder.h"
 #include "VideoUtils.h"
 
 namespace mozilla {
 
 MediaDecoderStateMachine* WebMDecoder::CreateStateMachine()
 {
   mReader =
     new MediaFormatReader(this, new WebMDemuxer(GetResource()), GetVideoFrameContainer());
   return new MediaDecoderStateMachine(this, mReader);
 }
 
 /* static */
 bool
-WebMDecoder::IsSupportedType(const MediaContentType& aContentType)
+WebMDecoder::IsSupportedType(const MediaContainerType& aContainerType)
 {
   if (!Preferences::GetBool("media.webm.enabled")) {
     return false;
   }
 
-  bool isVideo = aContentType.Type() == MEDIAMIMETYPE("video/webm");
-  if (aContentType.Type() != MEDIAMIMETYPE("audio/webm") && !isVideo) {
+  bool isVideo = aContainerType.Type() == MEDIAMIMETYPE("video/webm");
+  if (aContainerType.Type() != MEDIAMIMETYPE("audio/webm") && !isVideo) {
     return false;
   }
 
-  const MediaCodecs& codecs = aContentType.ExtendedType().Codecs();
+  const MediaCodecs& codecs = aContainerType.ExtendedType().Codecs();
   if (codecs.IsEmpty()) {
     // WebM guarantees that the only codecs it contained are vp8, vp9, opus or vorbis.
     return true;
   }
   // Verify that all the codecs specified are ones that we expect that
   // we can play.
   for (const auto& codec : codecs.Range()) {
     if (codec.EqualsLiteral("opus") || codec.EqualsLiteral("vorbis")) {
       continue;
     }
-    // Note: Only accept VP8/VP9 in a video content type, not in an audio
-    // content type.
+    // Note: Only accept VP8/VP9 in a video container type, not in an audio
+    // container type.
     if (isVideo &&
         (codec.EqualsLiteral("vp8") || codec.EqualsLiteral("vp8.0") ||
          codec.EqualsLiteral("vp9") || codec.EqualsLiteral("vp9.0"))) {
       continue;
     }
     // Some unsupported codec.
     return false;
   }
--- a/dom/media/webm/WebMDecoder.h
+++ b/dom/media/webm/WebMDecoder.h
@@ -6,34 +6,34 @@
 #if !defined(WebMDecoder_h_)
 #define WebMDecoder_h_
 
 #include "MediaDecoder.h"
 #include "MediaFormatReader.h"
 
 namespace mozilla {
 
-class MediaContentType;
+class MediaContainerType;
 
 class WebMDecoder : public MediaDecoder
 {
 public:
   explicit WebMDecoder(MediaDecoderOwner* aOwner) : MediaDecoder(aOwner) {}
   MediaDecoder* Clone(MediaDecoderOwner* aOwner) override {
     if (!IsWebMEnabled()) {
       return nullptr;
     }
     return new WebMDecoder(aOwner);
   }
   MediaDecoderStateMachine* CreateStateMachine() override;
 
-  // Returns true if aContentType is a WebM type that we think we can render
+  // Returns true if aContainerType is a WebM type that we think we can render
   // with an enabled platform decoder backend.
   // If provided, codecs are checked for support.
-  static bool IsSupportedType(const MediaContentType& aContentType);
+  static bool IsSupportedType(const MediaContainerType& aContainerType);
 
   void GetMozDebugReaderData(nsAString& aString) override;
 
 private:
   RefPtr<MediaFormatReader> mReader;
 };
 
 } // namespace mozilla
--- a/dom/tests/reftest/reftest-stylo.list
+++ b/dom/tests/reftest/reftest-stylo.list
@@ -1,14 +1,14 @@
 # DO NOT EDIT! This is a auto-generated temporary list for Stylo testing
 fails == bug453105.html bug453105.html
 fails == optiontext.html optiontext.html
 fails == bug456008.xhtml bug456008.xhtml
-fails == bug439965.html bug439965.html
+== bug439965.html bug439965.html
 == bug427779.xml bug427779.xml
 fails == bug559996.html bug559996.html
-fails == bug591981-1.html bug591981-1.html
-fails == bug591981-2.html bug591981-2.html
-fails == bug592366-1.html bug592366-1.html
-fails == bug592366-2.html bug592366-2.html
-fails == bug592366-1.xhtml bug592366-1.xhtml
-fails == bug592366-2.xhtml bug592366-2.xhtml
-fails == bug798068.xhtml bug798068.xhtml
+== bug591981-1.html bug591981-1.html
+== bug591981-2.html bug591981-2.html
+== bug592366-1.html bug592366-1.html
+== bug592366-2.html bug592366-2.html
+== bug592366-1.xhtml bug592366-1.xhtml
+== bug592366-2.xhtml bug592366-2.xhtml
+== bug798068.xhtml bug798068.xhtml
--- a/dom/url/tests/test_url.html
+++ b/dom/url/tests/test_url.html
@@ -404,18 +404,19 @@
     is(url.pathname, "path/to/file");
     is(url.search, "?query");
     is(url.hash, "#hash");
 
     // pathname cannot be overwritten.
     url.pathname = "new/path?newquery#newhash";
     is(url.href, "scheme:path/to/file?query#hash");
 
+    // don't escape '#' until we implement a spec-compliant parser.
     url.search = "?newquery#newhash";
-    is(url.href, "scheme:path/to/file?newquery%23newhash#hash");
+    is(url.href, "scheme:path/to/file?newquery#newhash#hash");
 
     // nulls get encoded, whitespace gets stripped
     url = new URL("scheme:pa\0\nth/to/fi\0\nle?qu\0\nery#ha\0\nsh");
     is(url.href, "scheme:pa%00th/to/fi%00le?qu%00ery#ha%00sh");
 
     url.search = "new\0\nquery";
     is(url.href, "scheme:pa%00th/to/fi%00le?new%00%0Aquery#ha%00sh");
     url.hash = "new\0\nhash";
@@ -426,11 +427,16 @@
     url.search = "query";
     is(url.href, "scheme:path?query#hash");
     url.hash = "";
     is(url.href, "scheme:path?query");
     url.hash = "newhash";
     is(url.href, "scheme:path?query#newhash");
     url.search = "";
     is(url.href, "scheme:path#newhash");
+
+    // we don't implement a spec-compliant parser yet.
+    // make sure we are bug compatible with existing implementations.
+    url = new URL("data:text/html,<a href=\"http://example.org/?q\">Link</a>");
+    is(url.href, "data:text/html,<a%20href=\"http://example.org/?q\">Link</a>");
   </script>
 </body>
 </html>
--- a/editor/reftests/reftest-stylo.list
+++ b/editor/reftests/reftest-stylo.list
@@ -58,18 +58,18 @@ fails == spellcheck-textarea-attr-dynami
 fails == spellcheck-textarea-attr-dynamic-override-inherit.html spellcheck-textarea-attr-dynamic-override-inherit.html
 fails == spellcheck-textarea-property-dynamic-override.html spellcheck-textarea-property-dynamic-override.html
 fails == spellcheck-textarea-property-dynamic-override-inherit.html spellcheck-textarea-property-dynamic-override-inherit.html
 fails needs-focus == caret_on_focus.html caret_on_focus.html
 fails needs-focus == caret_on_textarea_lastline.html caret_on_textarea_lastline.html
 fails needs-focus == input-text-onfocus-reframe.html input-text-onfocus-reframe.html
 fails needs-focus == input-text-notheme-onfocus-reframe.html input-text-notheme-onfocus-reframe.html
 fails needs-focus == caret_after_reframe.html caret_after_reframe.html
-fails == nobogusnode-1.html nobogusnode-1.html
-fails == nobogusnode-2.html nobogusnode-2.html
+# == nobogusnode-1.html nobogusnode-1.html
+# == nobogusnode-2.html nobogusnode-2.html
 fails == spellcheck-hyphen-valid.html spellcheck-hyphen-valid.html
 fails == spellcheck-hyphen-invalid.html spellcheck-hyphen-invalid.html
 fails == spellcheck-slash-valid.html spellcheck-slash-valid.html
 fails == spellcheck-period-valid.html spellcheck-period-valid.html
 fails == spellcheck-space-valid.html spellcheck-space-valid.html
 fails == spellcheck-comma-valid.html spellcheck-comma-valid.html
 fails == spellcheck-hyphen-multiple-valid.html spellcheck-hyphen-multiple-valid.html
 fails == spellcheck-hyphen-multiple-invalid.html spellcheck-hyphen-multiple-invalid.html
@@ -77,18 +77,18 @@ fails == spellcheck-dotafterquote-valid.
 fails == spellcheck-url-valid.html spellcheck-url-valid.html
 fails needs-focus == spellcheck-non-latin-arabic.html spellcheck-non-latin-arabic.html
 fails needs-focus == spellcheck-non-latin-chinese-simplified.html spellcheck-non-latin-chinese-simplified.html
 fails needs-focus == spellcheck-non-latin-chinese-traditional.html spellcheck-non-latin-chinese-traditional.html
 fails needs-focus == spellcheck-non-latin-hebrew.html spellcheck-non-latin-hebrew.html
 fails needs-focus == spellcheck-non-latin-japanese.html spellcheck-non-latin-japanese.html
 fails needs-focus == spellcheck-non-latin-korean.html spellcheck-non-latin-korean.html
 fails == unneeded_scroll.html unneeded_scroll.html
-fails == caret_on_presshell_reinit.html caret_on_presshell_reinit.html
-fails == caret_on_presshell_reinit-2.html caret_on_presshell_reinit-2.html
+# == caret_on_presshell_reinit.html caret_on_presshell_reinit.html
+# == caret_on_presshell_reinit-2.html caret_on_presshell_reinit-2.html
 fails == 642800.html 642800.html
 fails == selection_visibility_after_reframe.html selection_visibility_after_reframe.html
 fails == selection_visibility_after_reframe-2.html selection_visibility_after_reframe-2.html
 fails == selection_visibility_after_reframe-3.html selection_visibility_after_reframe-3.html
 fails == 672709.html 672709.html
 fails == 338427-1.html 338427-1.html
 fails == 674212-spellcheck.html 674212-spellcheck.html
 fails == 338427-2.html 338427-2.html
@@ -102,37 +102,37 @@ fails == dynamic-overflow-change.html dy
 fails == 694880-1.html 694880-1.html
 fails == 694880-2.html 694880-2.html
 fails == 694880-3.html 694880-3.html
 fails == 388980-1.html 388980-1.html
 fails needs-focus == spellcheck-superscript-1.html spellcheck-superscript-1.html
 fails == spellcheck-superscript-2.html spellcheck-superscript-2.html
 == 824080-1.html 824080-1.html
 # == 824080-2.html 824080-2.html
-fails == 824080-3.html 824080-3.html
+# == 824080-3.html 824080-3.html
 # needs-focus == 824080-2.html 824080-2.html
 # == 824080-4.html 824080-4.html
-fails == 824080-5.html 824080-5.html
+# == 824080-5.html 824080-5.html
 # needs-focus == 824080-4.html 824080-4.html
 # needs-focus == 824080-6.html 824080-6.html
 fails needs-focus pref(layout.accessiblecaret.enabled,false) pref(layout.accessiblecaret.enabled_on_touch,false) == 824080-7.html 824080-7.html
 # needs-focus == 824080-6.html 824080-6.html
 # Bug 674927: copy spellcheck-textarea tests to contenteditable
 # == spellcheck-contenteditable-attr.html spellcheck-contenteditable-attr.html
 # == spellcheck-contenteditable-attr.html spellcheck-contenteditable-attr.html
-fails needs-focus == spellcheck-contenteditable-focused.html spellcheck-contenteditable-focused.html
-fails needs-focus == spellcheck-contenteditable-focused-reframe.html spellcheck-contenteditable-focused-reframe.html
-fails == spellcheck-contenteditable-nofocus.html spellcheck-contenteditable-nofocus.html
-fails == spellcheck-contenteditable-disabled.html spellcheck-contenteditable-disabled.html
-fails == spellcheck-contenteditable-disabled-partial.html spellcheck-contenteditable-disabled-partial.html
-fails == spellcheck-contenteditable-attr-inherit.html spellcheck-contenteditable-attr-inherit.html
-fails == spellcheck-contenteditable-attr-dynamic.html spellcheck-contenteditable-attr-dynamic.html
-fails == spellcheck-contenteditable-attr-dynamic-inherit.html spellcheck-contenteditable-attr-dynamic-inherit.html
-fails == spellcheck-contenteditable-property-dynamic.html spellcheck-contenteditable-property-dynamic.html
-fails == spellcheck-contenteditable-property-dynamic-inherit.html spellcheck-contenteditable-property-dynamic-inherit.html
-fails == spellcheck-contenteditable-attr-dynamic-override.html spellcheck-contenteditable-attr-dynamic-override.html
-fails == spellcheck-contenteditable-attr-dynamic-override-inherit.html spellcheck-contenteditable-attr-dynamic-override-inherit.html
-fails == spellcheck-contenteditable-property-dynamic-override.html spellcheck-contenteditable-property-dynamic-override.html
-fails == spellcheck-contenteditable-property-dynamic-override-inherit.html spellcheck-contenteditable-property-dynamic-override-inherit.html
-fails == 911201.html 911201.html
+# needs-focus == spellcheck-contenteditable-focused.html spellcheck-contenteditable-focused.html
+# needs-focus == spellcheck-contenteditable-focused-reframe.html spellcheck-contenteditable-focused-reframe.html
+== spellcheck-contenteditable-nofocus.html spellcheck-contenteditable-nofocus.html
+# == spellcheck-contenteditable-disabled.html spellcheck-contenteditable-disabled.html
+# == spellcheck-contenteditable-disabled-partial.html spellcheck-contenteditable-disabled-partial.html
+# == spellcheck-contenteditable-attr-inherit.html spellcheck-contenteditable-attr-inherit.html
+# == spellcheck-contenteditable-attr-dynamic.html spellcheck-contenteditable-attr-dynamic.html
+# == spellcheck-contenteditable-attr-dynamic-inherit.html spellcheck-contenteditable-attr-dynamic-inherit.html
+== spellcheck-contenteditable-property-dynamic.html spellcheck-contenteditable-property-dynamic.html
+# == spellcheck-contenteditable-property-dynamic-inherit.html spellcheck-contenteditable-property-dynamic-inherit.html
+# == spellcheck-contenteditable-attr-dynamic-override.html spellcheck-contenteditable-attr-dynamic-override.html
+== spellcheck-contenteditable-attr-dynamic-override-inherit.html spellcheck-contenteditable-attr-dynamic-override-inherit.html
+# == spellcheck-contenteditable-property-dynamic-override.html spellcheck-contenteditable-property-dynamic-override.html
+== spellcheck-contenteditable-property-dynamic-override-inherit.html spellcheck-contenteditable-property-dynamic-override-inherit.html
+# == 911201.html 911201.html
 fails needs-focus == 969773.html 969773.html
 fails == 997805.html 997805.html
 fails == 1088158.html 1088158.html
--- a/gfx/layers/apz/test/reftest/reftest-stylo.list
+++ b/gfx/layers/apz/test/reftest/reftest-stylo.list
@@ -1,20 +1,20 @@
 # DO NOT EDIT! This is a auto-generated temporary list for Stylo testing
 # The following tests test the async positioning of the scrollbars.
 # Basic root-frame scrollbar with async scrolling
 fuzzy-if(Android,1,2) skip-if(!Android) pref(apz.allow_zooming,true) == async-scrollbar-1-v.html async-scrollbar-1-v.html
 fuzzy-if(Android,4,5) skip-if(!Android) pref(apz.allow_zooming,true) == async-scrollbar-1-h.html async-scrollbar-1-h.html
 fuzzy-if(Android,3,5) skip-if(!Android) pref(apz.allow_zooming,true) == async-scrollbar-1-vh.html async-scrollbar-1-vh.html
-# fails == async-scrollbar-1-v-rtl.html async-scrollbar-1-v-rtl.html
-# fails == async-scrollbar-1-h-rtl.html async-scrollbar-1-h-rtl.html
-# fails == async-scrollbar-1-vh-rtl.html async-scrollbar-1-vh-rtl.html
+# == async-scrollbar-1-v-rtl.html async-scrollbar-1-v-rtl.html
+# == async-scrollbar-1-h-rtl.html async-scrollbar-1-h-rtl.html
+# == async-scrollbar-1-vh-rtl.html async-scrollbar-1-vh-rtl.html
 
 # Different async zoom levels. Since the scrollthumb gets async-scaled in the
 # compositor, the border-radius ends of the scrollthumb are going to be a little
 # off, hence the fuzzy-if clauses.
 fails == async-scrollbar-zoom-1.html async-scrollbar-zoom-1.html
 fails == async-scrollbar-zoom-2.html async-scrollbar-zoom-2.html
 
 # Meta-viewport tag support
-fails == initial-scale-1.html initial-scale-1.html
+# == initial-scale-1.html initial-scale-1.html
 
 skip-if(!asyncPan) == frame-reconstruction-scroll-clamping.html frame-reconstruction-scroll-clamping.html
--- a/image/test/reftest/bmp/bmp-1bpp/reftest-stylo.list
+++ b/image/test/reftest/bmp/bmp-1bpp/reftest-stylo.list
@@ -1,22 +1,22 @@
 # DO NOT EDIT! This is a auto-generated temporary list for Stylo testing
 # BMP 1BPP tests
 
 # Images of various sizes
-fails == bmp-size-1x1-1bpp.bmp bmp-size-1x1-1bpp.bmp
-fails == bmp-size-2x2-1bpp.bmp bmp-size-2x2-1bpp.bmp
-fails == bmp-size-3x3-1bpp.bmp bmp-size-3x3-1bpp.bmp
-fails == bmp-size-4x4-1bpp.bmp bmp-size-4x4-1bpp.bmp
-fails == bmp-size-5x5-1bpp.bmp bmp-size-5x5-1bpp.bmp
-fails == bmp-size-6x6-1bpp.bmp bmp-size-6x6-1bpp.bmp
-fails == bmp-size-7x7-1bpp.bmp bmp-size-7x7-1bpp.bmp
-fails == bmp-size-8x8-1bpp.bmp bmp-size-8x8-1bpp.bmp
-fails == bmp-size-9x9-1bpp.bmp bmp-size-9x9-1bpp.bmp
-fails == bmp-size-15x15-1bpp.bmp bmp-size-15x15-1bpp.bmp
-fails == bmp-size-16x16-1bpp.bmp bmp-size-16x16-1bpp.bmp
-fails == bmp-size-17x17-1bpp.bmp bmp-size-17x17-1bpp.bmp
-fails == bmp-size-31x31-1bpp.bmp bmp-size-31x31-1bpp.bmp
-fails == bmp-size-32x32-1bpp.bmp bmp-size-32x32-1bpp.bmp
-fails == bmp-size-33x33-1bpp.bmp bmp-size-33x33-1bpp.bmp
-fails == bmp-not-square-1bpp.bmp bmp-not-square-1bpp.bmp
-fails == os2bmp-size-32x32-1bpp.bmp os2bmp-size-32x32-1bpp.bmp
-fails == top-to-bottom-16x16-1bpp.bmp top-to-bottom-16x16-1bpp.bmp
+== bmp-size-1x1-1bpp.bmp bmp-size-1x1-1bpp.bmp
+== bmp-size-2x2-1bpp.bmp bmp-size-2x2-1bpp.bmp
+== bmp-size-3x3-1bpp.bmp bmp-size-3x3-1bpp.bmp
+== bmp-size-4x4-1bpp.bmp bmp-size-4x4-1bpp.bmp
+== bmp-size-5x5-1bpp.bmp bmp-size-5x5-1bpp.bmp
+== bmp-size-6x6-1bpp.bmp bmp-size-6x6-1bpp.bmp
+== bmp-size-7x7-1bpp.bmp bmp-size-7x7-1bpp.bmp
+== bmp-size-8x8-1bpp.bmp bmp-size-8x8-1bpp.bmp
+== bmp-size-9x9-1bpp.bmp bmp-size-9x9-1bpp.bmp
+== bmp-size-15x15-1bpp.bmp bmp-size-15x15-1bpp.bmp
+== bmp-size-16x16-1bpp.bmp bmp-size-16x16-1bpp.bmp
+== bmp-size-17x17-1bpp.bmp bmp-size-17x17-1bpp.bmp
+== bmp-size-31x31-1bpp.bmp bmp-size-31x31-1bpp.bmp
+== bmp-size-32x32-1bpp.bmp bmp-size-32x32-1bpp.bmp
+== bmp-size-33x33-1bpp.bmp bmp-size-33x33-1bpp.bmp
+== bmp-not-square-1bpp.bmp bmp-not-square-1bpp.bmp
+== os2bmp-size-32x32-1bpp.bmp os2bmp-size-32x32-1bpp.bmp
+== top-to-bottom-16x16-1bpp.bmp top-to-bottom-16x16-1bpp.bmp
--- a/image/test/reftest/bmp/bmp-24bpp/reftest-stylo.list
+++ b/image/test/reftest/bmp/bmp-24bpp/reftest-stylo.list
@@ -1,22 +1,22 @@
 # DO NOT EDIT! This is a auto-generated temporary list for Stylo testing
 # BMP 24BPP tests
 
 # Images of various sizes
-fails == bmp-size-1x1-24bpp.bmp bmp-size-1x1-24bpp.bmp
-fails == bmp-size-2x2-24bpp.bmp bmp-size-2x2-24bpp.bmp
-fails == bmp-size-3x3-24bpp.bmp bmp-size-3x3-24bpp.bmp
-fails == bmp-size-4x4-24bpp.bmp bmp-size-4x4-24bpp.bmp
-fails == bmp-size-5x5-24bpp.bmp bmp-size-5x5-24bpp.bmp
-fails == bmp-size-6x6-24bpp.bmp bmp-size-6x6-24bpp.bmp
-fails == bmp-size-7x7-24bpp.bmp bmp-size-7x7-24bpp.bmp
-fails == bmp-size-8x8-24bpp.bmp bmp-size-8x8-24bpp.bmp
-fails == bmp-size-9x9-24bpp.bmp bmp-size-9x9-24bpp.bmp
-fails == bmp-size-15x15-24bpp.bmp bmp-size-15x15-24bpp.bmp
-fails == bmp-size-16x16-24bpp.bmp bmp-size-16x16-24bpp.bmp
-fails == bmp-size-17x17-24bpp.bmp bmp-size-17x17-24bpp.bmp
-fails == bmp-size-31x31-24bpp.bmp bmp-size-31x31-24bpp.bmp
-fails == bmp-size-32x32-24bpp.bmp bmp-size-32x32-24bpp.bmp
-fails == bmp-size-33x33-24bpp.bmp bmp-size-33x33-24bpp.bmp
-fails == bmp-not-square-24bpp.bmp bmp-not-square-24bpp.bmp
-fails == os2bmp-size-32x32-24bpp.bmp os2bmp-size-32x32-24bpp.bmp
-fails == top-to-bottom-16x16-24bpp.bmp top-to-bottom-16x16-24bpp.bmp
+== bmp-size-1x1-24bpp.bmp bmp-size-1x1-24bpp.bmp
+== bmp-size-2x2-24bpp.bmp bmp-size-2x2-24bpp.bmp
+== bmp-size-3x3-24bpp.bmp bmp-size-3x3-24bpp.bmp
+== bmp-size-4x4-24bpp.bmp bmp-size-4x4-24bpp.bmp
+== bmp-size-5x5-24bpp.bmp bmp-size-5x5-24bpp.bmp
+== bmp-size-6x6-24bpp.bmp bmp-size-6x6-24bpp.bmp
+== bmp-size-7x7-24bpp.bmp bmp-size-7x7-24bpp.bmp
+== bmp-size-8x8-24bpp.bmp bmp-size-8x8-24bpp.bmp
+== bmp-size-9x9-24bpp.bmp bmp-size-9x9-24bpp.bmp
+== bmp-size-15x15-24bpp.bmp bmp-size-15x15-24bpp.bmp
+== bmp-size-16x16-24bpp.bmp bmp-size-16x16-24bpp.bmp
+== bmp-size-17x17-24bpp.bmp bmp-size-17x17-24bpp.bmp
+== bmp-size-31x31-24bpp.bmp bmp-size-31x31-24bpp.bmp
+== bmp-size-32x32-24bpp.bmp bmp-size-32x32-24bpp.bmp
+== bmp-size-33x33-24bpp.bmp bmp-size-33x33-24bpp.bmp
+== bmp-not-square-24bpp.bmp bmp-not-square-24bpp.bmp
+== os2bmp-size-32x32-24bpp.bmp os2bmp-size-32x32-24bpp.bmp
+== top-to-bottom-16x16-24bpp.bmp top-to-bottom-16x16-24bpp.bmp
--- a/image/test/reftest/bmp/bmp-4bpp/reftest-stylo.list
+++ b/image/test/reftest/bmp/bmp-4bpp/reftest-stylo.list
@@ -1,25 +1,25 @@
 # DO NOT EDIT! This is a auto-generated temporary list for Stylo testing
 # BMP 4BPP tests
 
 # Images of various sizes
-fails == bmp-size-1x1-4bpp.bmp bmp-size-1x1-4bpp.bmp
-fails == bmp-size-2x2-4bpp.bmp bmp-size-2x2-4bpp.bmp
-fails == bmp-size-3x3-4bpp.bmp bmp-size-3x3-4bpp.bmp
-fails == bmp-size-4x4-4bpp.bmp bmp-size-4x4-4bpp.bmp
-fails == bmp-size-5x5-4bpp.bmp bmp-size-5x5-4bpp.bmp
-fails == bmp-size-6x6-4bpp.bmp bmp-size-6x6-4bpp.bmp
-fails == bmp-size-7x7-4bpp.bmp bmp-size-7x7-4bpp.bmp
-fails == bmp-size-8x8-4bpp.bmp bmp-size-8x8-4bpp.bmp
-fails == bmp-size-9x9-4bpp.bmp bmp-size-9x9-4bpp.bmp
-fails == bmp-size-15x15-4bpp.bmp bmp-size-15x15-4bpp.bmp
-fails == bmp-size-16x16-4bpp.bmp bmp-size-16x16-4bpp.bmp
-fails == bmp-size-17x17-4bpp.bmp bmp-size-17x17-4bpp.bmp
-fails == bmp-size-31x31-4bpp.bmp bmp-size-31x31-4bpp.bmp
-fails == bmp-size-32x32-4bpp.bmp bmp-size-32x32-4bpp.bmp
-fails == bmp-size-33x33-4bpp.bmp bmp-size-33x33-4bpp.bmp
-fails == bmp-not-square-4bpp.bmp bmp-not-square-4bpp.bmp
-fails == os2bmp-size-32x32-4bpp.bmp os2bmp-size-32x32-4bpp.bmp
-fails == top-to-bottom-16x16-4bpp.bmp top-to-bottom-16x16-4bpp.bmp
+== bmp-size-1x1-4bpp.bmp bmp-size-1x1-4bpp.bmp
+== bmp-size-2x2-4bpp.bmp bmp-size-2x2-4bpp.bmp
+== bmp-size-3x3-4bpp.bmp bmp-size-3x3-4bpp.bmp
+== bmp-size-4x4-4bpp.bmp bmp-size-4x4-4bpp.bmp
+== bmp-size-5x5-4bpp.bmp bmp-size-5x5-4bpp.bmp
+== bmp-size-6x6-4bpp.bmp bmp-size-6x6-4bpp.bmp
+== bmp-size-7x7-4bpp.bmp bmp-size-7x7-4bpp.bmp
+== bmp-size-8x8-4bpp.bmp bmp-size-8x8-4bpp.bmp
+== bmp-size-9x9-4bpp.bmp bmp-size-9x9-4bpp.bmp
+== bmp-size-15x15-4bpp.bmp bmp-size-15x15-4bpp.bmp
+== bmp-size-16x16-4bpp.bmp bmp-size-16x16-4bpp.bmp
+== bmp-size-17x17-4bpp.bmp bmp-size-17x17-4bpp.bmp
+== bmp-size-31x31-4bpp.bmp bmp-size-31x31-4bpp.bmp
+== bmp-size-32x32-4bpp.bmp bmp-size-32x32-4bpp.bmp
+== bmp-size-33x33-4bpp.bmp bmp-size-33x33-4bpp.bmp
+== bmp-not-square-4bpp.bmp bmp-not-square-4bpp.bmp
+== os2bmp-size-32x32-4bpp.bmp os2bmp-size-32x32-4bpp.bmp
+== top-to-bottom-16x16-4bpp.bmp top-to-bottom-16x16-4bpp.bmp
 # test that delta skips are drawn as transparent
 # taken from http://bmptestsuite.sourceforge.net/
 fails == rle4-delta-320x240.bmp rle4-delta-320x240.bmp
--- a/image/test/reftest/bmp/bmp-8bpp/reftest-stylo.list
+++ b/image/test/reftest/bmp/bmp-8bpp/reftest-stylo.list
@@ -1,25 +1,25 @@
 # DO NOT EDIT! This is a auto-generated temporary list for Stylo testing
 # BMP 8BPP tests
 
 # Images of various sizes
-fails == bmp-size-1x1-8bpp.bmp bmp-size-1x1-8bpp.bmp
-fails == bmp-size-2x2-8bpp.bmp bmp-size-2x2-8bpp.bmp
-fails == bmp-size-3x3-8bpp.bmp bmp-size-3x3-8bpp.bmp
-fails == bmp-size-4x4-8bpp.bmp bmp-size-4x4-8bpp.bmp
-fails == bmp-size-5x5-8bpp.bmp bmp-size-5x5-8bpp.bmp
-fails == bmp-size-6x6-8bpp.bmp bmp-size-6x6-8bpp.bmp
-fails == bmp-size-7x7-8bpp.bmp bmp-size-7x7-8bpp.bmp
-fails == bmp-size-8x8-8bpp.bmp bmp-size-8x8-8bpp.bmp
-fails == bmp-size-9x9-8bpp.bmp bmp-size-9x9-8bpp.bmp
-fails == bmp-size-15x15-8bpp.bmp bmp-size-15x15-8bpp.bmp
-fails == bmp-size-16x16-8bpp.bmp bmp-size-16x16-8bpp.bmp
-fails == bmp-size-17x17-8bpp.bmp bmp-size-17x17-8bpp.bmp
-fails == bmp-size-31x31-8bpp.bmp bmp-size-31x31-8bpp.bmp
-fails == bmp-size-32x32-8bpp.bmp bmp-size-32x32-8bpp.bmp
-fails == bmp-size-33x33-8bpp.bmp bmp-size-33x33-8bpp.bmp
-fails == bmp-not-square-8bpp.bmp bmp-not-square-8bpp.bmp
-fails == rle-bmp-not-square-8bpp.bmp rle-bmp-not-square-8bpp.bmp
-fails == os2-bmp-size-32x32-8bpp.bmp os2-bmp-size-32x32-8bpp.bmp
-fails == rle-bmp-size-32x32-8bpp.bmp rle-bmp-size-32x32-8bpp.bmp
-fails == top-to-bottom-rle-bmp-size-32x32-8bpp.bmp top-to-bottom-rle-bmp-size-32x32-8bpp.bmp
-fails == top-to-bottom-16x16-8bpp.bmp top-to-bottom-16x16-8bpp.bmp
+== bmp-size-1x1-8bpp.bmp bmp-size-1x1-8bpp.bmp
+== bmp-size-2x2-8bpp.bmp bmp-size-2x2-8bpp.bmp
+== bmp-size-3x3-8bpp.bmp bmp-size-3x3-8bpp.bmp
+== bmp-size-4x4-8bpp.bmp bmp-size-4x4-8bpp.bmp
+== bmp-size-5x5-8bpp.bmp bmp-size-5x5-8bpp.bmp
+== bmp-size-6x6-8bpp.bmp bmp-size-6x6-8bpp.bmp
+== bmp-size-7x7-8bpp.bmp bmp-size-7x7-8bpp.bmp
+== bmp-size-8x8-8bpp.bmp bmp-size-8x8-8bpp.bmp
+== bmp-size-9x9-8bpp.bmp bmp-size-9x9-8bpp.bmp
+== bmp-size-15x15-8bpp.bmp bmp-size-15x15-8bpp.bmp
+== bmp-size-16x16-8bpp.bmp bmp-size-16x16-8bpp.bmp
+== bmp-size-17x17-8bpp.bmp bmp-size-17x17-8bpp.bmp
+== bmp-size-31x31-8bpp.bmp bmp-size-31x31-8bpp.bmp
+== bmp-size-32x32-8bpp.bmp bmp-size-32x32-8bpp.bmp
+== bmp-size-33x33-8bpp.bmp bmp-size-33x33-8bpp.bmp
+== bmp-not-square-8bpp.bmp bmp-not-square-8bpp.bmp
+== rle-bmp-not-square-8bpp.bmp rle-bmp-not-square-8bpp.bmp
+== os2-bmp-size-32x32-8bpp.bmp os2-bmp-size-32x32-8bpp.bmp
+== rle-bmp-size-32x32-8bpp.bmp rle-bmp-size-32x32-8bpp.bmp
+== top-to-bottom-rle-bmp-size-32x32-8bpp.bmp top-to-bottom-rle-bmp-size-32x32-8bpp.bmp
+== top-to-bottom-16x16-8bpp.bmp top-to-bottom-16x16-8bpp.bmp
--- a/image/test/reftest/bmp/bmpsuite/b/reftest-stylo.list
+++ b/image/test/reftest/bmp/bmpsuite/b/reftest-stylo.list
@@ -6,30 +6,30 @@
 # BMP: bihsize=40, 127 x 64, bpp=30000, compression=0, colors=2
 # "Header indicates an absurdly large number of bits/pixel."
 # [We reject it. So does Chromium.]
 == wrapper.html?badbitcount.bmp wrapper.html?badbitcount.bmp
 
 # BMP: bihsize=40, 127 x 64, bpp=1, compression=0, colors=2
 # "Header incorrectly indicates that the bitmap is several GB in size."
 # [We accept it. So does Chromium.]
-fails == badbitssize.bmp badbitssize.bmp
+== badbitssize.bmp badbitssize.bmp
 
 # BMP: bihsize=40, 127 x 64, bpp=1, compression=0, colors=2
 # BMP: bihsize=40, 127 x 64, bpp=1, compression=0, colors=2
 # "Density (pixels per meter) suggests the image is much larger in one
 # dimension than the other."
 # [We accept them. So does Chromium.]
-fails == baddens1.bmp baddens1.bmp
-fails == baddens2.bmp baddens2.bmp
+== baddens1.bmp baddens1.bmp
+== baddens2.bmp baddens2.bmp
 
 # BMP: bihsize=40, 127 x 64, bpp=1, compression=0, colors=2
 # "Header incorrectly indicates that the file is several GB in size."
 # [We accept it. So does Chromium.]
-fails == badfilesize.bmp badfilesize.bmp
+== badfilesize.bmp badfilesize.bmp
 
 # BMP: <no "BMP:" output is produced for files with bad header sizes>
 # "Header size is 66 bytes, which is not a valid size for any known BMP
 # version."
 # [We reject it. So does Chromium.]
 == wrapper.html?badheadersize.bmp wrapper.html?badheadersize.bmp
 
 # BMP: bihsize=40, 127 x 64, bpp=8, compression=0, colors=305402420
@@ -37,49 +37,49 @@ fails == badfilesize.bmp badfilesize.bmp
 # number of colors."
 # [We reject it. Chromium accepts it but draws nothing. Rejecting seems
 # preferable give that the data is clearly untrustworthy.]
 == wrapper.html?badpalettesize.bmp wrapper.html?badpalettesize.bmp
 
 # BMP: bihsize=40, 127 x 64, bpp=1, compression=0, colors=2
 # "The 'planes' setting, which is required to be 1, is not 1."
 # [We accept it. So does Chromium.]
-fails == badplanes.bmp badplanes.bmp
+== badplanes.bmp badplanes.bmp
 
 # BMP: bihsize=40, 127 x 64, bpp=8, compression=1, colors=253
 # "An invalid RLE-compressed image that tries to cause buffer overruns."
 # [We accept it, drawing the valid first part and leaving the rest black.
 # Chromium accepts it, drawing the valid first part and leaving the rest
 # transparent. Using black for the invalid part is arguably better because it
 # makes the image edges more obvious.]
-fails == badrle.bmp badrle.bmp
+== badrle.bmp badrle.bmp
 
 # BMP: bihsize=40, -127 x 64, bpp=1, compression=0, colors=2
 # "The image claims to be a negative number of pixels in width."
 # [We reject it. So does Chromium.]
 == wrapper.html?badwidth.bmp wrapper.html?badwidth.bmp
 
 # BMP: bihsize=40, 127 x 64, bpp=8, compression=0, colors=101
 # "Many of the palette indices used in the image are not present in the
 # palette."
 # [We accept it and use black for the missing colors. So does Chromium.]
-fails == pal8badindex.bmp pal8badindex.bmp
+== pal8badindex.bmp pal8badindex.bmp
 
 # BMP: bihsize=40, 3000000 x 2000000, bpp=24, compression=0, colors=0
 # "An image with a very large reported width and height."
 # [We reject it. So does Chromium.]
 == wrapper.html?reallybig.bmp wrapper.html?reallybig.bmp
 
 # BMP: bihsize=40, 127 x -64, bpp=8, compression=1, colors=252
 # "An RLE-compressed image that tries to use top-down orientation, which isn’t
 # allowed."
 # [We accept it. Chromium rejects it. Accepting seems better given that we can
 # decode it perfectly well.]
-fails == rletopdown.bmp rletopdown.bmp
+== rletopdown.bmp rletopdown.bmp
 
 # BMP: bihsize=40, 127 x 64, bpp=1, compression=0, colors=2
 # "A file that has been truncated in the middle of the bitmap."
 # [We accept it, drawing the part that is present and leaving the rest black.
 # Chromium draws the part that is present and leaves the rest transparent.
 # Using black for the invalid part is arguably better because it makes the
 # image edges more obvious.]
-fails == shortfile.bmp shortfile.bmp
+== shortfile.bmp shortfile.bmp
 
--- a/image/test/reftest/bmp/bmpsuite/g/reftest-stylo.list
+++ b/image/test/reftest/bmp/bmpsuite/g/reftest-stylo.list
@@ -1,82 +1,82 @@
 # DO NOT EDIT! This is a auto-generated temporary list for Stylo testing
 # bmpsuite "good" tests
 
 # See ../README.mozilla for details.
 
 # BMP: bihsize=40, 127 x 64, bpp=1, compression=0, colors=2
 # "1 bit/pixel paletted image, in which black is the first color in the
 # palette."
-fails == pal1.bmp pal1.bmp
+== pal1.bmp pal1.bmp
 
 # BMP: bihsize=40, 127 x 64, bpp=1, compression=0, colors=2
 # "1 bit/pixel paletted image, in which white is the first color in the
 # palette."
-fails == pal1wb.bmp pal1wb.bmp
+== pal1wb.bmp pal1wb.bmp
 
 # BMP: bihsize=40, 127 x 64, bpp=1, compression=0, colors=2
 # "1 bit/pixel paletted image, with colors other than black and white."
-fails == pal1bg.bmp pal1bg.bmp
+== pal1bg.bmp pal1bg.bmp
 
 # BMP: bihsize=40, 127 x 64, bpp=4, compression=0, colors=12
 # "Paletted image with 12 palette colors, and 4 bits/pixel."
-fails == pal4.bmp pal4.bmp
+== pal4.bmp pal4.bmp
 
 # BMP: bihsize=40, 127 x 64, bpp=4, compression=2, colors=12
 # "4-bit image that uses RLE compression."
-fails == pal4rle.bmp pal4rle.bmp
+== pal4rle.bmp pal4rle.bmp
 
 # BMP: bihsize=40, 127 x 64, bpp=8, compression=0, colors=252
 # "Our standard paletted image, with 252 palette colors, and 8 bits/pixel."
-fails == pal8.bmp pal8.bmp
+== pal8.bmp pal8.bmp
 
 # BMP: bihsize=40, 127 x 64, bpp=8, compression=0, colors=0
 # "Every field that can be set to 0 is set to 0: pixels/meter=0; colors used=0
 # (meaning the default 256); size-of-image=0."
-fails == pal8-0.bmp pal8-0.bmp
+== pal8-0.bmp pal8-0.bmp
 
 # BMP: bihsize=40, 127 x 64, bpp=8, compression=1, colors=252
 # "8-bit image that uses RLE compression."
-fails == pal8rle.bmp pal8rle.bmp
+== pal8rle.bmp pal8rle.bmp
 
 # BMP: bihsize=40, 126 x 63, bpp=8, compression=0, colors=252
 # BMP: bihsize=40, 125 x 62, bpp=8, compression=0, colors=252
 # BMP: bihsize=40, 124 x 61, bpp=8, compression=0, colors=252
 # "Images with different widths and heights. In BMP format, rows are padded to
 # a multiple of four bytes, so we test all four possibilities."
-fails == pal8w126.bmp pal8w126.bmp
-fails == pal8w125.bmp pal8w125.bmp
-fails == pal8w124.bmp pal8w124.bmp
+== pal8w126.bmp pal8w126.bmp
+== pal8w125.bmp pal8w125.bmp
+== pal8w124.bmp pal8w124.bmp
 
 # BMP: bihsize=40, 127 x -64, bpp=8, compression=0, colors=252
 # "BMP images are normally stored from the bottom up, but there is a way to
 # store them from the top down."
-fails == pal8topdown.bmp pal8topdown.bmp
+== pal8topdown.bmp pal8topdown.bmp
 
 # BMP: bihsize=40, 127 x 32, bpp=8, compression=0, colors=252
 # "An image with non-square pixels: the X pixels/meter is twice the Y
 # pixels/meter. Image editors can be expected to leave the image 'squashed';
 # image viewers should consider stretching it to its correct proportions."
 # [We leave it squashed, as does Chromium.]
-fails == pal8nonsquare.bmp pal8nonsquare.bmp
+== pal8nonsquare.bmp pal8nonsquare.bmp
 
 # BMP: bihsize=12, 127 x 64, bpp=8, compression=0, colors=0
 # "An OS/2-style bitmap."
-fails == pal8os2.bmp pal8os2.bmp
+== pal8os2.bmp pal8os2.bmp
 
 # BMP: bihsize=108, 127 x 64, bpp=8, compression=0, colors=252
 # "A v4 bitmap. I’m not sure that the gamma and chromaticity values in this
 # file are sensible, because I can’t find any detailed documentation of them."
-fails == pal8v4.bmp pal8v4.bmp
+== pal8v4.bmp pal8v4.bmp
 
 # BMP: bihsize=124, 127 x 64, bpp=8, compression=0, colors=252
 # "A v5 bitmap. Version 5 has additional colorspace options over v4, so it is
 # easier to create, and ought to be more portable."
-fails == pal8v5.bmp pal8v5.bmp
+== pal8v5.bmp pal8v5.bmp
 
 # BMP: bihsize=40, 127 x 64, bpp=16, compression=0, colors=0
 # "A 16-bit image with the default color format: 5 bits each for red, green, and
 # blue, and 1 unused bit. The whitest colors should (I assume) be displayed as
 # pure white: (255,255,255), not (248,248,248)."
 # == rgb16.bmp rgb16.bmp
 
 # BMP: bihsize=40, 127 x 64, bpp=16, compression=3, colors=0
@@ -87,27 +87,27 @@ fails == pal8v5.bmp pal8v5.bmp
 # == rgb16.bmp rgb16.bmp
 
 # BMP: bihsize=40, 127 x 64, bpp=16, compression=3, colors=256
 # "A 16-bit image with both a BITFIELDS segment and a palette."
 # == rgb16.bmp rgb16.bmp
 
 # BMP: bihsize=40, 127 x 64, bpp=24, compression=0, colors=0
 # "A perfectly ordinary 24-bit (truecolor) image."
-fails == rgb24.bmp rgb24.bmp
+== rgb24.bmp rgb24.bmp
 
 # BMP: bihsize=40, 127 x 64, bpp=24, compression=0, colors=256
 # "A 24-bit image, with a palette containing 256 colors. There is little if any
 # reason for a truecolor image to contain a palette, but it is legal."
-fails == rgb24pal.bmp rgb24pal.bmp
+== rgb24pal.bmp rgb24pal.bmp
 
 # BMP: bihsize=40, 127 x 64, bpp=32, compression=0, colors=0
 # "A 32-bit image using the default color format for 32-bit images (no
 # BITFIELDS segment). There are 8 bits per color channel, and 8 unused bits.
 # The unused bits are set to 0."
-fails == rgb32.bmp rgb32.bmp
+== rgb32.bmp rgb32.bmp
 
 # BMP: bihsize=40, 127 x 64, bpp=32, compression=3, colors=0
 # "A 32-bit image with a BITFIELDS segment. As usual, there are 8 bits per color
 # channel, and 8 unused bits. But the color channels are in an unusual order,
 # so the viewer must read the BITFIELDS, and not just guess."
-fails == rgb32bf.bmp rgb32bf.bmp
+== rgb32bf.bmp rgb32bf.bmp
 
--- a/image/test/reftest/bmp/bmpsuite/q/reftest-stylo.list
+++ b/image/test/reftest/bmp/bmpsuite/q/reftest-stylo.list
@@ -3,17 +3,17 @@
 
 # See ../README.mozilla for details.
 
 # BMP: bihsize=40, 127 x 64, bpp=1, compression=0, colors=1
 # "1 bit/pixel paletted image, with only one color in the palette. The
 # documentation says that 1-bpp images have a palette size of 2 (not 'up to
 # 2'), but it would be silly for a viewer not to support a size of 1."
 # [We accept it. So does Chromium.]
-fails == pal1p1.bmp pal1p1.bmp
+== pal1p1.bmp pal1p1.bmp
 
 # BMP: bihsize=40, 127 x 64, bpp=2, compression=0, colors=4
 # "A paletted image with 2 bits/pixel. Usually only 1, 4, and 8 are allowed,
 # but 2 is legal on Windows CE."
 # [We reject it. So does Chromium.]
 == wrapper.html?pal2.bmp wrapper.html?pal2.bmp
 
 # BMP: bihsize=40, 127 x 64, bpp=4, compression=2, colors=13
@@ -28,97 +28,97 @@ fails == pal4rletrns.bmp pal4rletrns.bmp
 # "8-bit version of q/pal4rletrns.bmp."
 # [Ditto.]
 fails == pal8rletrns.bmp pal8rletrns.bmp
 
 # BMP: bihsize=40, 127 x 64, bpp=8, compression=0, colors=252
 # "A file with some unused bytes between the palette and the image. This is
 # probably valid, but I’m not 100% sure."
 # [We accept it. So does Chromium.]
-fails == pal8offs.bmp pal8offs.bmp
+== pal8offs.bmp pal8offs.bmp
 
 # BMP: bihsize=40, 127 x 64, bpp=8, compression=0, colors=300
 # "An 8-bit image with 300 palette colors. This may be invalid, because the
 # documentation could be interpreted to imply that 8-bit images aren’t allowed
 # to have more than 256 colors."
 # [We accept it. So does Chromium.]
-fails == pal8oversizepal.bmp pal8oversizepal.bmp
+== pal8oversizepal.bmp pal8oversizepal.bmp
 
 # BMP: bihsize=12, 127 x 64, bpp=8, compression=0, colors=0
 # "An OS/2v1 with a less-than-full-sized palette. Probably not valid, but such
 # files have been seen in the wild."
 # [We reject it. Chromium accepts it but draws nothing. Rejecting seems
 # preferable given that the color and pixel data must overlap, which can only
 # lead to rubbish results.]
 == wrapper.html?pal8os2sp.bmp wrapper.html?pal8os2sp.bmp
 
 # BMP: bihsize=64, 127 x 64, bpp=8, compression=0, colors=252
 # "My attempt to make an OS/2v2 bitmap."
 # [We accept it. So does Chromium.]
-fails == pal8os2v2.bmp pal8os2v2.bmp
+== pal8os2v2.bmp pal8os2v2.bmp
 
 # BMP: bihsize=16, 127 x 64, bpp=8, compression=0, colors=0
 # "An OS/2v2 bitmap whose header has only 16 bytes, instead of the full 64."
 # [We accept it. So does Chromium.]
-fails == pal8os2v2-16.bmp pal8os2v2-16.bmp
+== pal8os2v2-16.bmp pal8os2v2-16.bmp
 
 # BMP: bihsize=40, 127 x 64, bpp=16, compression=3, colors=0
 # "An unusual and silly 16-bit image, with 2 red bits, 3 green bits, and 1 blue
 # bit. Most viewers do support this image, but the colors may be darkened with
 # a yellow-green shadow. That’s because they’re doing simple bit-shifting
 # (possibly including one round of bit replication), instead of proper
 # scaling."
-fails == rgb16-231.bmp rgb16-231.bmp
+== rgb16-231.bmp rgb16-231.bmp
 
 # BMP: bihsize=124, 127 x 64, bpp=16, compression=3, colors=0
 # "A 16-bit image with an alpha channel. There are 4 bits for each color
 # channel, and 4 bits for the alpha channel. It’s not clear if this is valid,
 # but I can’t find anything that suggests it isn’t."
 fails == rgba16-4444.bmp rgba16-4444.bmp
 
 # BMP: bihsize=40, 127 x 64, bpp=24, compression=0, colors=300
 # "A 24-bit image, with a palette containing 300 colors. The fact that the
 # palette has more than 256 colors may cause some viewers to complain, but the
 # documentation does not mention a size limit."
 # [We accept it. So does Chromium.]
-fails == rgb24largepal.bmp rgb24largepal.bmp
+== rgb24largepal.bmp rgb24largepal.bmp
 
 # BMP: bihsize=124, 127 x 64, bpp=24, compression=0, colors=0
 # "My attempt to make a BMP file with an embedded color profile."
 # [We support it, though we don't do anything with the color profile. Chromium
 # also handles it.]
-fails == rgb24prof.bmp rgb24prof.bmp
+== rgb24prof.bmp rgb24prof.bmp
 
 # BMP: bihsize=124, 127 x 64, bpp=24, compression=0, colors=0
 # "My attempt to make a BMP file with a linked color profile."
 # [We accept it, though we don't do anything with the color profile. Chromium
 # also handles it.]
-fails == rgb24lprof.bmp rgb24lprof.bmp
+== rgb24lprof.bmp rgb24lprof.bmp
 
 # BMP: bihsize=124, 127 x 64, bpp=0, compression=4, colors=0
 # BMP: bihsize=124, 127 x 64, bpp=0, compression=5, colors=0
 # "My attempt to make BMP files with embedded JPEG and PNG images. These are
 # not likely to be supported by much of anything (they’re intended for 
 # printers)."
 # [We reject them. So does Chromium.]
 == wrapper.html?rgb24jpeg.bmp wrapper.html?rgb24jpeg.bmp
 == wrapper.html?rgb24png.bmp wrapper.html?rgb24png.bmp
 
 # BMP: bihsize=40, 127 x 64, bpp=32, compression=0, colors=0
 # "Same as g/rgb32.bmp, except that the unused bits are set to something other
 # than 0. If the image becomes transparent toward the bottom, it probably means
 # the viewer uses heuristics to guess whether the undefined data represents
 # transparency."
 # [We don't apply transparency here. Chromium does the same.]
-fails == rgb32fakealpha.bmp rgb32fakealpha.bmp
+== rgb32fakealpha.bmp rgb32fakealpha.bmp
 
 # BMP: bihsize=40, 127 x 64, bpp=32, compression=3, colors=0
 # "A 32 bits/pixel image, with all 32 bits used: 11 each for red and green, and
 # 10 for blue. As far as I know, this is perfectly valid, but it is unusual."
-fails == rgb32-111110.bmp rgb32-111110.bmp
+== rgb32-111110.bmp rgb32-111110.bmp
 
 # BMP: bihsize=124, 127 x 64, bpp=32, compression=3, colors=0
 # "A BMP with an alpha channel. Transparency is barely documented, so it’s
 # possible that this file is not correctly formed. The color channels are in an
 # unusual order, to prevent viewers from passing this test by making a lucky
 # guess."
 fails == rgba32.bmp rgba32.bmp
 
--- a/image/test/reftest/bmp/reftest-stylo.list
+++ b/image/test/reftest/bmp/reftest-stylo.list
@@ -9,9 +9,9 @@ include bmp-corrupted/reftest-stylo.list
 include bmpsuite/reftest-stylo.list
 
 # Two bmp files where the offset to the start of the image data in the file
 # is past the end of the file. In 1240629-1.bmp the offset us uint32_max,
 # so we are testing that we don't try to allocate a buffer that size (and
 # fail on 32 bit platforms) and declare the image in error state. If in the
 # future we decide that such bmps (offset past the end of the file) are
 # invalid the test will still pass, but won't be testing much.
-fails == 1240629-1.bmp 1240629-1.bmp
+== 1240629-1.bmp 1240629-1.bmp
--- a/image/test/reftest/color-management/reftest-stylo.list
+++ b/image/test/reftest/color-management/reftest-stylo.list
@@ -1,8 +1,8 @@
 # DO NOT EDIT! This is a auto-generated temporary list for Stylo testing
 # Colormangement
 
 # test for bug 489133, test for bug 460520
-fails == invalid-chrm.png invalid-chrm.png
-fails == invalid-whitepoint.png invalid-whitepoint.png
+== invalid-chrm.png invalid-chrm.png
+== invalid-whitepoint.png invalid-whitepoint.png
 # test for bug 488955
 == trc-type.html trc-type.html
--- a/image/test/reftest/downscaling/reftest-stylo.list
+++ b/image/test/reftest/downscaling/reftest-stylo.list
@@ -83,17 +83,17 @@ fails == downscale-2d.html?203,52,bottom
 fails == downscale-2e.html?203,52,bottom downscale-2e.html?203,52,bottom
 
 fails == downscale-2a.html?205,53,bottom downscale-2a.html?205,53,bottom
 fails == downscale-2b.html?205,53,bottom downscale-2b.html?205,53,bottom
 fails == downscale-2c.html?205,53,bottom downscale-2c.html?205,53,bottom
 fails == downscale-2d.html?205,53,bottom downscale-2d.html?205,53,bottom
 fails == downscale-2e.html?205,53,bottom downscale-2e.html?205,53,bottom
 
-fails == downscale-moz-icon-1.html downscale-moz-icon-1.html
+== downscale-moz-icon-1.html downscale-moz-icon-1.html
 
 fails == downscale-png.html?16,16,interlaced downscale-png.html?16,16,interlaced
 fails == downscale-png.html?24,24,interlaced downscale-png.html?24,24,interlaced
 
 # Non-transparent and transparent ICO images
 fails == downscale-16px.html?ff-0RGB.ico downscale-16px.html?ff-0RGB.ico
 fails == downscale-16px.html?ff-ARGB.ico downscale-16px.html?ff-ARGB.ico
 
@@ -167,17 +167,17 @@ fails == downscale-2f.html?203,52,bottom
 fails == downscale-2a.html?205,53,bottom downscale-2a.html?205,53,bottom
 fails == downscale-2b.html?205,53,bottom downscale-2b.html?205,53,bottom
 fails == downscale-2c.html?205,53,bottom downscale-2c.html?205,53,bottom
 fails == downscale-2d.html?205,53,bottom downscale-2d.html?205,53,bottom
 fails == downscale-2e.html?205,53,bottom downscale-2e.html?205,53,bottom
 fails == downscale-2f.html?205,53,bottom downscale-2f.html?205,53,bottom
 
 # Skip on WinXP with skia content
-fails == downscale-moz-icon-1.html downscale-moz-icon-1.html
+== downscale-moz-icon-1.html downscale-moz-icon-1.html
 
 fails == downscale-png.html?16,16,interlaced downscale-png.html?16,16,interlaced
 fails == downscale-png.html?24,24,interlaced downscale-png.html?24,24,interlaced
 
 # Non-transparent and transparent ICO images
 fails == downscale-16px.html?ff-0RGB.ico downscale-16px.html?ff-0RGB.ico
 fails == downscale-16px.html?ff-ARGB.ico downscale-16px.html?ff-ARGB.ico
 
--- a/image/test/reftest/encoders-lossless/reftest-stylo.list
+++ b/image/test/reftest/encoders-lossless/reftest-stylo.list
@@ -14,147 +14,147 @@
 # - mime=<mime type>
 # - options=<canvas toDataURL encoder options>
 # Example: 
 # encoder.html?img=escape(reference_image.png)
 #             &mime=escape(image/vnd.microsoft.icon)
 #             &options=escape(-moz-parse-options:bpp=24;format=png)
 
 # PNG
-fails HTTP == size-1x1.png size-1x1.png
-fails HTTP == size-2x2.png size-2x2.png
-fails HTTP == size-3x3.png size-3x3.png
-fails HTTP == size-4x4.png size-4x4.png
-fails HTTP == size-5x5.png size-5x5.png
-fails HTTP == size-6x6.png size-6x6.png
-fails HTTP == size-7x7.png size-7x7.png
-fails HTTP == size-8x8.png size-8x8.png
-fails HTTP == size-9x9.png size-9x9.png
-fails HTTP == size-15x15.png size-15x15.png
-fails HTTP == size-16x16.png size-16x16.png
-fails HTTP == size-17x17.png size-17x17.png
-fails HTTP == size-31x31.png size-31x31.png