Merge m-c to inbound, a=merge
authorWes Kocher <wkocher@mozilla.com>
Tue, 16 Aug 2016 22:06:58 -0700
changeset 309796 5aa02060f7c47a1d4c2c198c18ab7fc59771467b
parent 309795 c7e5f970ee618bf454a14aae498a8031b1dfa100 (current diff)
parent 309642 fe895421dfbe1f1f8f1fc6a39bb20774423a6d74 (diff)
child 309797 0f38d18da57e776e09ea8b90e592809459a1cd54
push id30570
push userkwierso@gmail.com
push dateWed, 17 Aug 2016 23:38:48 +0000
treeherdermozilla-central@a70835fe9f55 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmerge
milestone51.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 m-c to inbound, a=merge
browser/base/content/browser.js
build/moz.configure/old.configure
devtools/client/responsive.html/manager.js
devtools/client/webconsole/new-console-output/components/filter-toggle-button.js
devtools/server/actors/moz.build
devtools/server/main.js
devtools/shared/fronts/moz.build
devtools/shared/specs/moz.build
devtools/shared/touch/moz.build
devtools/shared/touch/simulator-content.js
docshell/base/nsDocShell.cpp
dom/animation/KeyframeEffect.cpp
dom/animation/KeyframeEffect.h
dom/media/RtspMediaResource.cpp
dom/media/RtspMediaResource.h
dom/media/omx/RtspExtractor.cpp
dom/media/omx/RtspExtractor.h
dom/media/omx/RtspOmxDecoder.cpp
dom/media/omx/RtspOmxDecoder.h
dom/media/omx/RtspOmxReader.cpp
dom/media/omx/RtspOmxReader.h
layout/base/nsDisplayList.cpp
layout/base/nsLayoutUtils.cpp
layout/style/nsCSSPropertyID.h
layout/style/nsCSSPropertyIDSet.h
layout/style/nsCSSValue.cpp
layout/style/nsCSSValue.h
layout/style/nsStyleStruct.cpp
layout/style/nsTransitionManager.cpp
testing/web-platform/meta/MANIFEST.json
--- a/browser/base/content/aboutNetError.xhtml
+++ b/browser/base/content/aboutNetError.xhtml
@@ -414,21 +414,21 @@
           sd.appendChild(document.createTextNode(desc.slice(desc.lastIndexOf("</a>") + "</a>".length)));
         }
 
         if (gIsCertError) {
           // Initialize the error code link embedded in the error message to
           // display debug information about the cert error.
           var errorCode = document.getElementById("errorCode");
           if (errorCode) {
-            errorCode.href = "#technicalInformation";
+            errorCode.href = "javascript:void(0)";
             errorCode.addEventListener("click", () => {
-              var div = document.getElementById("certificateErrorDebugInformation");
-              if (toggleDisplay(div) == "block") {
-                div.scrollIntoView({block: "start", behavior: "smooth"});
+              let debugInfo = document.getElementById("certificateErrorDebugInformation");
+              if (toggleDisplay(debugInfo) == "block") {
+                debugInfo.scrollIntoView({block: "start", behavior: "smooth"});
               }
             }, false);
           }
         }
 
         // Initialize the cert domain link.
         var link = document.getElementById("cert_domain_link");
         if (!link)
@@ -641,17 +641,16 @@
           <p id="badCertTechnicalInfo"/>
           <button id="exceptionDialogButton">&securityOverride.exceptionButtonLabel;</button>
         </div>
       </div>
 
     </div>
 
     <div id="certificateErrorDebugInformation">
-      <a name="technicalInformation"></a>
       <button id="copyToClipboard">&certerror.copyToClipboard.label;</button>
       <div id="certificateErrorText"/>
       <button id="copyToClipboard">&certerror.copyToClipboard.label;</button>
     </div>
 
     <!--
     - Note: It is important to run the script this way, instead of using
     - an onload handler. This is because error pages are loaded as
--- a/browser/base/content/browser.js
+++ b/browser/base/content/browser.js
@@ -6793,17 +6793,16 @@ var gIdentityHandler = {
       this._identityBox.setAttribute("sharing", sharing);
     else
       this._identityBox.removeAttribute("sharing");
 
     this._sharingState = tab._sharingState;
 
     if (this._identityPopup.state == "open") {
       this.updateSitePermissions();
-      this._identityPopupMultiView.setHeightToFit();
     }
   },
 
   /**
    * Attempt to provide proper IDN treatment for host names
    */
   getEffectiveHost: function() {
     if (!this._IDNService)
@@ -7312,17 +7311,16 @@ var gIdentityHandler = {
     stateLabel.setAttribute("class", "identity-popup-permission-state-label");
     stateLabel.textContent = SitePermissions.getStateLabel(
       aPermission.id, aPermission.state, aPermission.inUse || false);
 
     let button = document.createElement("button");
     button.setAttribute("class", "identity-popup-permission-remove-button");
     button.addEventListener("command", () => {
       this._permissionList.removeChild(container);
-      this._identityPopupMultiView.setHeightToFit();
       if (aPermission.inUse &&
           ["camera", "microphone", "screen"].includes(aPermission.id)) {
         let windowId = this._sharingState.windowId;
         if (aPermission.id == "screen") {
           windowId = "screen:" + windowId;
         } else {
           // If we set persistent permissions or the sharing has
           // started due to existing persistent permissions, we need
--- a/browser/components/controlcenter/content/panel.inc.xul
+++ b/browser/components/controlcenter/content/panel.inc.xul
@@ -11,17 +11,17 @@
 
   <broadcasterset>
     <broadcaster id="identity-popup-mcb-learn-more" class="text-link plain" value="&identity.learnMore;"/>
     <broadcaster id="identity-popup-insecure-login-forms-learn-more" class="text-link plain" value="&identity.learnMore;"/>
   </broadcasterset>
 
   <panelmultiview id="identity-popup-multiView"
                   mainViewId="identity-popup-mainView">
-    <panelview id="identity-popup-mainView" flex="1">
+    <panelview id="identity-popup-mainView">
 
       <!-- Security Section -->
       <hbox id="identity-popup-security" class="identity-popup-section">
         <vbox id="identity-popup-security-content" flex="1">
           <label class="plain">
             <label class="identity-popup-headline host"></label>
             <label class="identity-popup-headline hostless" crop="end"/>
           </label>
--- a/browser/components/customizableui/content/panelUI.xml
+++ b/browser/components/customizableui/content/panelUI.xml
@@ -313,17 +313,17 @@
               });
 
               break;
             case "popupshown":
               this._setMaxHeight();
               break;
             case "popuphidden":
               this.removeAttribute("panelopen");
-              this._mainView.style.removeProperty("height");
+              this._mainView.style.removeProperty("max-height");
               this.showMainView();
               this._mainViewObserver.disconnect();
               break;
           }
         ]]></body>
       </method>
 
       <method name="_shouldSetPosition">
@@ -341,17 +341,17 @@
       <method name="_setMaxHeight">
         <body><![CDATA[
           if (!this._shouldSetHeight())
             return;
 
           // Ignore the mutation that'll fire when we set the height of
           // the main view.
           this.ignoreMutations = true;
-          this._mainView.style.height =
+          this._mainView.style.maxHeight =
             this.getBoundingClientRect().height + "px";
           this.ignoreMutations = false;
         ]]></body>
       </method>
       <method name="_adjustContainerHeight">
         <body><![CDATA[
           if (!this.ignoreMutations && !this.showingSubView && !this._transitioning) {
             let height;
--- a/browser/components/extensions/ext-tabs.js
+++ b/browser/components/extensions/ext-tabs.js
@@ -804,21 +804,21 @@ extensions.registerSchemaAPI("tabs", (ex
         return context.sendMessage(mm, "Extension:Execute", {options}, {recipient});
       },
 
       executeScript: function(tabId, details) {
         return self.tabs._execute(tabId, details, "js", "executeScript");
       },
 
       insertCSS: function(tabId, details) {
-        return self.tabs._execute(tabId, details, "css", "insertCSS");
+        return self.tabs._execute(tabId, details, "css", "insertCSS").then(() => {});
       },
 
       removeCSS: function(tabId, details) {
-        return self.tabs._execute(tabId, details, "css", "removeCSS");
+        return self.tabs._execute(tabId, details, "css", "removeCSS").then(() => {});
       },
 
       connect: function(tabId, connectInfo) {
         let tab = TabManager.getTab(tabId, context);
         let mm = tab.linkedBrowser.messageManager;
 
         let name = "";
         if (connectInfo && connectInfo.name !== null) {
--- a/browser/components/extensions/test/browser/browser_ext_browserAction_pageAction_icon_permissions.js
+++ b/browser/components/extensions/test/browser/browser_ext_browserAction_pageAction_icon_permissions.js
@@ -65,19 +65,24 @@ add_task(function* testInvalidIconSizes(
 // correctly.
 add_task(function* testDefaultDetails() {
   // TODO: Test localized variants.
   let icons = [
     "foo/bar.png",
     "/foo/bar.png",
     {"19": "foo/bar.png"},
     {"38": "foo/bar.png"},
-    {"19": "foo/bar.png", "38": "baz/quux.png"},
   ];
 
+  if (window.devicePixelRatio > 1) {
+    icons.push({"19": "baz/quux.png", "38": "foo/bar.png"});
+  } else {
+    icons.push({"19": "foo/bar.png", "38": "baz/quux@2x.png"});
+  }
+
   let expectedURL = new RegExp(String.raw`^moz-extension://[^/]+/foo/bar\.png$`);
 
   for (let icon of icons) {
     let extension = ExtensionTestUtils.loadExtension({
       manifest: {
         "browser_action": {"default_icon": icon},
         "page_action": {"default_icon": icon},
       },
@@ -90,16 +95,17 @@ add_task(function* testDefaultDetails() 
             browser.test.sendMessage("ready");
           });
         });
       },
 
       files: {
         "foo/bar.png": imageBuffer,
         "baz/quux.png": imageBuffer,
+        "baz/quux@2x.png": imageBuffer,
       },
     });
 
     yield Promise.all([extension.startup(), extension.awaitMessage("ready")]);
 
     let browserActionId = makeWidgetId(extension.id) + "-browser-action";
     let pageActionId = makeWidgetId(extension.id) + "-page-action";
 
--- a/browser/components/extensions/test/browser/browser_ext_tabs_executeScript.js
+++ b/browser/components/extensions/test/browser/browser_ext_tabs_executeScript.js
@@ -16,39 +16,42 @@ add_task(function* testExecuteScript() {
     browser.tabs.query({active: true, currentWindow: true}).then(tabs => {
       return browser.webNavigation.getAllFrames({tabId: tabs[0].id});
     }).then(frames => {
       browser.test.log(`FRAMES: ${frames[1].frameId} ${JSON.stringify(frames)}\n`);
       return Promise.all([
         browser.tabs.executeScript({
           code: "42",
         }).then(result => {
-          browser.test.assertEq(42, result, "Expected callback result");
+          browser.test.assertEq(1, result.length, "Expected one callback result");
+          browser.test.assertEq(42, result[0], "Expected callback result");
         }),
 
         browser.tabs.executeScript({
           file: "script.js",
           code: "42",
         }).then(result => {
           browser.test.fail("Expected not to be able to execute a script with both file and code");
         }, error => {
           browser.test.assertTrue(/a 'code' or a 'file' property, but not both/.test(error.message),
                                   "Got expected error");
         }),
 
         browser.tabs.executeScript({
           file: "script.js",
         }).then(result => {
-          browser.test.assertEq(undefined, result, "Expected callback result");
+          browser.test.assertEq(1, result.length, "Expected one callback result");
+          browser.test.assertEq(undefined, result[0], "Expected callback result");
         }),
 
         browser.tabs.executeScript({
           file: "script2.js",
         }).then(result => {
-          browser.test.assertEq(27, result, "Expected callback result");
+          browser.test.assertEq(1, result.length, "Expected one callback result");
+          browser.test.assertEq(27, result[0], "Expected callback result");
         }),
 
         browser.tabs.executeScript({
           code: "location.href;",
           allFrames: true,
         }).then(result => {
           browser.test.assertTrue(Array.isArray(result), "Result is an array");
 
@@ -57,19 +60,20 @@ add_task(function* testExecuteScript() {
           browser.test.assertTrue(/\/file_iframe_document\.html$/.test(result[0]), "First result is correct");
           browser.test.assertEq("http://mochi.test:8888/", result[1], "Second result is correct");
         }),
 
         browser.tabs.executeScript({
           code: "location.href;",
           runAt: "document_end",
         }).then(result => {
-          browser.test.assertTrue(typeof(result) == "string", "Result is a string");
+          browser.test.assertEq(1, result.length, "Expected callback result");
+          browser.test.assertEq("string", typeof result[0], "Result is a string");
 
-          browser.test.assertTrue(/\/file_iframe_document\.html$/.test(result), "Result is correct");
+          browser.test.assertTrue(/\/file_iframe_document\.html$/.test(result[0]), "Result is correct");
         }),
 
         browser.tabs.executeScript({
           code: "window",
         }).then(result => {
           browser.test.fail("Expected error when returning non-structured-clonable object");
         }, error => {
           browser.test.assertEq("Script returned non-structured-clonable data",
@@ -113,17 +117,17 @@ add_task(function* testExecuteScript() {
           }).then(() => {
             return browser.tabs.remove(tab.id);
           });
         }),
 
         browser.tabs.executeScript({
           code: "Promise.resolve(42)",
         }).then(result => {
-          browser.test.assertEq(42, result, "Got expected promise resolution value as result");
+          browser.test.assertEq(42, result[0], "Got expected promise resolution value as result");
         }),
 
         browser.tabs.executeScript({
           code: "location.href;",
           runAt: "document_end",
           allFrames: true,
         }).then(result => {
           browser.test.assertTrue(Array.isArray(result), "Result is an array");
@@ -133,29 +137,31 @@ add_task(function* testExecuteScript() {
           browser.test.assertTrue(/\/file_iframe_document\.html$/.test(result[0]), "First result is correct");
           browser.test.assertEq("http://mochi.test:8888/", result[1], "Second result is correct");
         }),
 
         browser.tabs.executeScript({
           code: "location.href;",
           frameId: frames[0].frameId,
         }).then(result => {
-          browser.test.assertTrue(/\/file_iframe_document\.html$/.test(result), `Result for frameId[0] is correct: ${result}`);
+          browser.test.assertEq(1, result.length, "Expected one result");
+          browser.test.assertTrue(/\/file_iframe_document\.html$/.test(result[0]), `Result for frameId[0] is correct: ${result[0]}`);
         }),
 
         browser.tabs.executeScript({
           code: "location.href;",
           frameId: frames[1].frameId,
         }).then(result => {
-          browser.test.assertEq("http://mochi.test:8888/", result, "Result for frameId[1] is correct");
+          browser.test.assertEq(1, result.length, "Expected one result");
+          browser.test.assertEq("http://mochi.test:8888/", result[0], "Result for frameId[1] is correct");
         }),
 
         browser.tabs.create({url: "http://example.com/"}).then(tab => {
           return browser.tabs.executeScript(tab.id, {code: "location.href"}).then(result => {
-            browser.test.assertEq("http://example.com/", result, "Script executed correctly in new tab");
+            browser.test.assertEq("http://example.com/", result[0], "Script executed correctly in new tab");
 
             return browser.tabs.remove(tab.id);
           });
         }),
 
         new Promise(resolve => {
           browser.runtime.onMessage.addListener(message => {
             browser.test.assertEq("script ran", message, "Expected runtime message");
--- a/browser/components/extensions/test/browser/browser_ext_tabs_insertCSS.js
+++ b/browser/components/extensions/test/browser/browser_ext_tabs_insertCSS.js
@@ -44,17 +44,17 @@ add_task(function* testExecuteScript() {
 
       let {promise, background, foreground} = promises.shift();
       return promise().then(result => {
         browser.test.assertEq(undefined, result, "Expected callback result");
 
         return browser.tabs.executeScript({
           code: `(${checkCSS})()`,
         });
-      }).then(result => {
+      }).then(([result]) => {
         browser.test.assertEq(background, result[0], "Expected background color");
         browser.test.assertEq(foreground, result[1], "Expected foreground color");
         return next();
       });
     }
 
     next().then(() => {
       browser.test.notifyPass("insertCSS");
--- a/browser/components/extensions/test/browser/browser_ext_tabs_reload_bypass_cache.js
+++ b/browser/components/extensions/test/browser/browser_ext_tabs_reload_bypass_cache.js
@@ -28,24 +28,24 @@ add_task(function* () {
         tabId = tab.id;
         return awaitLoad(tabId);
       }).then(() => {
         return browser.tabs.reload(tabId, {bypassCache: false});
       }).then(() => {
         return awaitLoad(tabId);
       }).then(() => {
         return browser.tabs.executeScript(tabId, {code: "document.body.textContent"});
-      }).then(textContent => {
+      }).then(([textContent]) => {
         browser.test.assertEq("", textContent, "`textContent` should be empty when bypassCache=false");
         return browser.tabs.reload(tabId, {bypassCache: true});
       }).then(() => {
         return awaitLoad(tabId);
       }).then(() => {
         return browser.tabs.executeScript(tabId, {code: "document.body.textContent"});
-      }).then(textContent => {
+      }).then(([textContent]) => {
         let [pragma, cacheControl] = textContent.split(":");
         browser.test.assertEq("no-cache", pragma, "`pragma` should be set to `no-cache` when bypassCache is true");
         browser.test.assertEq("no-cache", cacheControl, "`cacheControl` should be set to `no-cache` when bypassCache is true");
         browser.tabs.remove(tabId);
         browser.test.notifyPass("tabs.reload_bypass_cache");
       }).catch(error => {
         browser.test.fail(`${error} :: ${error.stack}`);
         browser.test.notifyFail("tabs.reload_bypass_cache");
--- a/browser/components/extensions/test/browser/browser_ext_tabs_removeCSS.js
+++ b/browser/components/extensions/test/browser/browser_ext_tabs_removeCSS.js
@@ -61,17 +61,17 @@ add_task(function* testExecuteScript() {
 
       let {promise, background, foreground} = promises.shift();
       return promise().then(result => {
         browser.test.assertEq(undefined, result, "Expected callback result");
 
         return browser.tabs.executeScript({
           code: `(${checkCSS})()`,
         });
-      }).then(result => {
+      }).then(([result]) => {
         browser.test.assertEq(background, result[0], "Expected background color");
         browser.test.assertEq(foreground, result[1], "Expected foreground color");
         return next();
       });
     }
 
     next().then(() => {
       browser.test.notifyPass("removeCSS");
--- a/browser/components/preferences/in-content/tests/browser_subdialogs.js
+++ b/browser/components/preferences/in-content/tests/browser_subdialogs.js
@@ -183,27 +183,22 @@ add_task(function* back_navigation_on_br
 
   info("canceling the dialog");
   yield close_subdialog_and_test_generic_end_state(tab.linkedBrowser,
     function() { tab.linkedBrowser.goBack(); },
     null, undefined, {runClosingFnOutsideOfContentTask: true});
 });
 
 add_task(function* escape_should_close_dialog() {
-  todo(false, "BrowserTestUtils.sendChar('VK_ESCAPE') triggers " +
-              "'can't access dead object' on `navigator` in this test. " +
-              "See bug 1238065.")
-  return;
-
   yield open_subdialog_and_test_generic_start_state(tab.linkedBrowser);
 
   info("canceling the dialog");
   yield close_subdialog_and_test_generic_end_state(tab.linkedBrowser,
-    function() { return BrowserTestUtils.sendChar("VK_ESCAPE", tab.linkedBrowser); },
-    null, undefined, {runClosingFnOutsideOfContentTask: true});
+    function() { return BrowserTestUtils.synthesizeKey("VK_ESCAPE", {}, tab.linkedBrowser); },
+    "cancel", 0, {runClosingFnOutsideOfContentTask: true});
 });
 
 add_task(function* correct_width_and_height_should_be_used_for_dialog() {
   yield open_subdialog_and_test_generic_start_state(tab.linkedBrowser);
 
   yield ContentTask.spawn(tab.linkedBrowser, null, function*() {
     let frameStyle = content.window.gSubDialog._frame.style;
     Assert.equal(frameStyle.width, "32em",
--- a/browser/themes/shared/customizableui/customizeMode.inc.css
+++ b/browser/themes/shared/customizableui/customizeMode.inc.css
@@ -121,24 +121,43 @@
   border-top: 1px solid rgb(221,221,221);
   padding: 10px;
 }
 
 .customizationmode-button {
   border: 1px solid rgb(192,192,192);
   border-radius: 3px;
   margin: 5px;
-  padding: 2px 12px;
+  padding: 2px 10px;
   background-color: rgb(251,251,251);
   color: rgb(71,71,71);
   box-shadow: 0 1px rgba(255, 255, 255, 0.5),
               inset 0 1px rgba(255, 255, 255, 0.5);
   -moz-appearance: none;
 }
 
+.customizationmode-button > .box-inherit {
+  border-width: 0;
+  padding-inline-start: 0;
+  padding-inline-end: 0;
+}
+
+.customizationmode-button > .button-icon {
+  margin-inline-start: 0;
+}
+
+.customizationmode-button:not([type=menu]) > .button-text {
+  margin-inline-end: 0;
+}
+
+.customizationmode-button > .button-menu-dropmarker {
+  margin-inline-end: 0;
+  padding-inline-end: 0;
+}
+
 #customization-titlebar-visibility-button[checked],
 #customization-devedition-theme-button[checked] {
   background-color: rgb(218, 218, 218);
   border-color: rgb(168, 168, 168);
   text-shadow: 0 1px rgb(236, 236, 236);
   box-shadow: 0 1px rgba(255, 255, 255, 0.5),
               inset 0 1px rgb(196, 196, 196);
 }
@@ -152,21 +171,16 @@
   height: 24px;
 }
 
 #customization-titlebar-visibility-button {
   list-style-image: url("chrome://browser/skin/customizableui/customize-titleBar-toggle.png");
   -moz-image-region: rect(0, 24px, 24px, 0);
 }
 
-#customization-lwtheme-button,
-#customization-titlebar-visibility-button  {
-  padding: 2px 7px;
-}
-
 #customization-lwtheme-button > .box-inherit > .box-inherit > .button-text,
 #customization-titlebar-visibility-button > .button-box > .button-text {
   /* Sadly, button.css thinks its margins are perfect for everyone. */
   margin-inline-start: 6px !important;
 }
 
 #customization-lwtheme-button > .box-inherit > .box-inherit > .button-icon {
   width: 20px;
--- a/build/moz.configure/old.configure
+++ b/build/moz.configure/old.configure
@@ -150,16 +150,17 @@ def old_configure_options(*options):
         return list(options)
 
     return depends(prepare_configure, extra_old_configure_args, all_options,
                    *options)
 
 
 @old_configure_options(
     '--cache-file',
+    '--datadir',
     '--enable-accessibility',
     '--enable-address-sanitizer',
     '--enable-alsa',
     '--enable-android-omx',
     '--enable-b2g-bt',
     '--enable-b2g-camera',
     '--enable-b2g-ril',
     '--enable-bundled-fonts',
@@ -244,16 +245,18 @@ def old_configure_options(*options):
     '--enable-universalchardet',
     '--enable-updater',
     '--enable-url-classifier',
     '--enable-valgrind',
     '--enable-verify-mar',
     '--enable-webrtc',
     '--enable-xul',
     '--enable-zipwriter',
+    '--includedir',
+    '--libdir',
     '--no-create',
     '--prefix',
     '--with-android-cxx-stl',
     '--with-android-distribution-directory',
     '--with-android-max-sdk',
     '--with-android-min-sdk',
     '--with-android-sdk',
     '--with-app-basename',
--- a/build/moz.configure/util.configure
+++ b/build/moz.configure/util.configure
@@ -59,16 +59,42 @@ def is_absolute_or_relative(path):
 
 @imports(_import='mozpack.path', _as='mozpath')
 def normsep(path):
     return mozpath.normsep(path)
 
 
 @imports('ctypes')
 @imports(_from='ctypes', _import='wintypes')
+@imports(_from='mozbuild.configure.constants', _import='WindowsBinaryType')
+def windows_binary_type(path):
+    """Obtain the type of a binary on Windows.
+
+    Returns WindowsBinaryType constant.
+    """
+    GetBinaryTypeW = ctypes.windll.kernel32.GetBinaryTypeW
+    GetBinaryTypeW.argtypes = [wintypes.LPWSTR, wintypes.POINTER(wintypes.DWORD)]
+    GetBinaryTypeW.restype = wintypes.BOOL
+
+    bin_type = wintypes.DWORD()
+    res = GetBinaryTypeW(path, ctypes.byref(bin_type))
+    if not res:
+        die('could not obtain binary type of %s' % path)
+
+    if bin_type.value == 0:
+        return WindowsBinaryType('win32')
+    elif bin_type.value == 6:
+        return WindowsBinaryType('win64')
+    # If we see another binary type, something is likely horribly wrong.
+    else:
+        die('unsupported binary type on %s: %s' % (path, bin_type))
+
+
+@imports('ctypes')
+@imports(_from='ctypes', _import='wintypes')
 def get_GetShortPathNameW():
     GetShortPathNameW = ctypes.windll.kernel32.GetShortPathNameW
     GetShortPathNameW.argtypes = [wintypes.LPCWSTR, wintypes.LPWSTR,
                                   wintypes.DWORD]
     GetShortPathNameW.restype = wintypes.DWORD
     return GetShortPathNameW
 
 
--- a/devtools/client/debugger/test/mochitest/browser.ini
+++ b/devtools/client/debugger/test/mochitest/browser.ini
@@ -114,16 +114,17 @@ support-files =
   doc_script-switching-02.html
   doc_script_webext_contentscript.html
   doc_split-console-paused-reload.html
   doc_step-many-statements.html
   doc_step-out.html
   doc_terminate-on-tab-close.html
   doc_watch-expressions.html
   doc_watch-expression-button.html
+  doc_whitespace-property-names.html
   doc_with-frame.html
   doc_worker-source-map.html
   doc_WorkerActor.attach-tab1.html
   doc_WorkerActor.attach-tab2.html
   doc_WorkerActor.attachThread-tab.html
   head.js
   sjs_post-page.sjs
   sjs_random-javascript.sjs
@@ -516,16 +517,18 @@ skip-if = e10s && debug
 [browser_dbg_variables-view-04.js]
 skip-if = e10s && debug
 [browser_dbg_variables-view-05.js]
 skip-if = e10s && debug
 [browser_dbg_variables-view-06.js]
 skip-if = e10s && debug
 [browser_dbg_variables-view-07.js]
 skip-if = e10s && debug
+[browser_dbg_variables-view-08.js]
+skip-if = e10s && debug
 [browser_dbg_variables-view-accessibility.js]
 subsuite = clipboard
 skip-if = e10s && debug
 [browser_dbg_variables-view-data.js]
 skip-if = e10s && debug
 [browser_dbg_variables-view-edit-cancel.js]
 skip-if = e10s && debug
 [browser_dbg_variables-view-edit-click.js]
--- a/devtools/client/debugger/test/mochitest/browser_dbg_variables-view-06.js
+++ b/devtools/client/debugger/test/mochitest/browser_dbg_variables-view-06.js
@@ -116,12 +116,10 @@ var test = Task.async(function* () {
                   "A rejected promise shouldn't have a value");
           }
         }
         ok(foundState, "We should have found the <state> property.");
         break;
     }
   }
 
-  debugger;
-
   resumeDebuggerThenCloseAndFinish(panel);
 });
--- a/devtools/client/debugger/test/mochitest/browser_dbg_variables-view-07.js
+++ b/devtools/client/debugger/test/mochitest/browser_dbg_variables-view-07.js
@@ -23,19 +23,21 @@ var test = Task.async(function* () {
 
   const variables = panel.panelWin.DebuggerView.Variables;
   ok(variables, "Should get the variables view.");
 
   const scope = [...variables][0];
   ok(scope, "Should get the current function's scope.");
 
   let proxy;
-  [...scope].forEach(function([name, value]) {
-    if(name === "proxy") proxy = value;
-  });
+  for (let [name, value] of scope) {
+    if (name === "proxy") {
+      proxy = value;
+    }
+  }
   ok(proxy, "Should have found the proxy variable");
 
   info("Expanding variable 'proxy'");
   let expanded = once(variables, "fetched");
   proxy.expand();
   yield expanded;
 
   let foundTarget = false;
@@ -47,21 +49,21 @@ var test = Task.async(function* () {
     yield expanded;
     if (property === "<target>") {
       for(let [subprop, subdata] of data) if(subprop === "name") {
         is(subdata.value, "target", "The value of '<target>' should be the [[ProxyTarget]]");
         foundTarget = true;
       }
     } else {
       is(property, "<handler>", "There shouldn't be properties other than <target> and <handler>");
-      for(let [subprop, subdata] of data) if(subprop === "name") {
-        is(subdata.value, "handler", "The value of '<handler>' should be the [[ProxyHandler]]");
-        foundHandler = true;
+      for (let [subprop, subdata] of data) {
+        if(subprop === "name") {
+          is(subdata.value, "handler", "The value of '<handler>' should be the [[ProxyHandler]]");
+          foundHandler = true;
+        }
       }
     }
   }
   ok(foundTarget, "Should have found the '<target>' property containing the [[ProxyTarget]]");
   ok(foundHandler, "Should have found the '<handler>' property containing the [[ProxyHandler]]");
 
-  debugger;
-
   resumeDebuggerThenCloseAndFinish(panel);
 });
new file mode 100644
--- /dev/null
+++ b/devtools/client/debugger/test/mochitest/browser_dbg_variables-view-08.js
@@ -0,0 +1,61 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Test that property values are not missing when the property names only contain whitespace.
+ */
+
+const TAB_URL = EXAMPLE_URL + "doc_whitespace-property-names.html";
+
+var test = Task.async(function* () {
+  let options = {
+    source: TAB_URL,
+    line: 1
+  };
+  var dbg = initDebugger(TAB_URL, options);
+  const [tab,, panel] = yield dbg;
+  const debuggerLineNumber = 24;
+  const scopes = waitForCaretAndScopes(panel, debuggerLineNumber);
+  callInTab(tab, "doPause");
+  yield scopes;
+
+  const variables = panel.panelWin.DebuggerView.Variables;
+  ok(variables, "Should get the variables view.");
+
+  const scope = [...variables][0];
+  ok(scope, "Should get the current function's scope.");
+
+  let obj;
+  for (let [name, value] of scope) {
+    if (name === "obj") {
+      obj = value;
+    }
+  }
+  ok(obj, "Should have found the 'obj' variable");
+
+  info("Expanding variable 'obj'");
+  let expanded = once(variables, "fetched");
+  obj.expand();
+  yield expanded;
+
+  let values = [" ", "\r", "\n", "\t", "\f", "\uFEFF", "\xA0"];
+  let count = values.length;
+
+  for (let [property, value] of obj) {
+    let index = values.indexOf(property);
+    if (index >= 0) {
+      --count;
+      is(value._nameString, property,
+         "The _nameString is different than the property name");
+      is(value._valueString, index + "",
+         "The _valueString is different than the stringified value");
+      is(value._valueLabel.getAttribute("value"), index + "",
+         "The _valueLabel value is different than the stringified value");
+    }
+  }
+  is(count, 0, "There are " + count + " missing properties");
+
+  resumeDebuggerThenCloseAndFinish(panel);
+});
new file mode 100644
--- /dev/null
+++ b/devtools/client/debugger/test/mochitest/doc_whitespace-property-names.html
@@ -0,0 +1,29 @@
+<!-- Any copyright is dedicated to the Public Domain.
+     http://creativecommons.org/publicdomain/zero/1.0/ -->
+<!DOCTYPE html>
+
+<html>
+  <head>
+    <meta charset="utf-8"/>
+    <title>Debugger + Whitespace property name test page</title>
+  </head>
+
+  <body>
+    <script>
+    window.obj = {
+      " ": 0,
+      "\r": 1,
+      "\n": 2,
+      "\t": 3,
+      "\f": 4,
+      "\uFEFF": 5,
+      "\xA0": 6
+    };
+    window.doPause = function () {
+      var obj = window.obj;
+      debugger;
+    };
+    </script>
+  </body>
+
+</html>
--- a/devtools/client/inspector/breadcrumbs.js
+++ b/devtools/client/inspector/breadcrumbs.js
@@ -18,16 +18,19 @@ const ELLIPSIS = Services.prefs.getCompl
 const MAX_LABEL_LENGTH = 40;
 
 const NS_XHTML = "http://www.w3.org/1999/xhtml";
 const SCROLL_REPEAT_MS = 100;
 
 const EventEmitter = require("devtools/shared/event-emitter");
 const {KeyShortcuts} = require("devtools/client/shared/key-shortcuts");
 
+// Some margin may be required for visible element detection.
+const SCROLL_MARGIN = 1;
+
 /**
  * Component to replicate functionality of XUL arrowscrollbox
  * for breadcrumbs
  *
  * @param {Window} win The window containing the breadcrumbs
  * @parem {DOMNode} container The element in which to put the scroll box
  */
 function ArrowScrollBox(win, container) {
@@ -35,16 +38,19 @@ function ArrowScrollBox(win, container) 
   this.doc = win.document;
   this.container = container;
   EventEmitter.decorate(this);
   this.init();
 }
 
 ArrowScrollBox.prototype = {
 
+  // Scroll behavior, exposed for testing
+  scrollBehavior: "smooth",
+
   /**
    * Build the HTML, add to the DOM and start listening to
    * events
    */
   init: function () {
     this.constructHtml();
 
     this.onUnderflow();
@@ -64,19 +70,35 @@ ArrowScrollBox.prototype = {
     this.endBtn.addEventListener("dblclick", this.onEndBtnDblClick, false);
 
     // Overflow and underflow are moz specific events
     this.inner.addEventListener("underflow", this.onUnderflow, false);
     this.inner.addEventListener("overflow", this.onOverflow, false);
   },
 
   /**
+   * Determine whether the current text directionality is RTL
+   */
+  isRtl: function () {
+    return this.win.getComputedStyle(this.container).direction === "rtl";
+  },
+
+  /**
+   * Scroll to the specified element using the current scroll behavior
+   * @param {Element} element element to scroll
+   * @param {String} block desired alignment of element after scrolling
+   */
+  scrollToElement: function (element, block) {
+    element.scrollIntoView({ block: block, behavior: this.scrollBehavior });
+  },
+
+  /**
    * Call the given function once; then continuously
    * while the mouse button is held
-   * @param {repeatFn} the function to repeat while the button is held
+   * @param {Function} repeatFn the function to repeat while the button is held
    */
   clickOrHold: function (repeatFn) {
     let timer;
     let container = this.container;
 
     function handleClick() {
       cancelHold();
       repeatFn();
@@ -104,59 +126,61 @@ ArrowScrollBox.prototype = {
    */
   onStartBtnDblClick: function () {
     let children = this.inner.childNodes;
     if (children.length < 1) {
       return;
     }
 
     let element = this.inner.childNodes[0];
-    element.scrollIntoView({ block: "start", behavior: "smooth" });
+    this.scrollToElement(element, "start");
   },
 
   /**
    * When end button is dbl clicked scroll to last element
    */
   onEndBtnDblClick: function () {
     let children = this.inner.childNodes;
     if (children.length < 1) {
       return;
     }
 
     let element = children[children.length - 1];
-    element.scrollIntoView({ block: "start", behavior: "smooth" });
+    this.scrollToElement(element, "start");
   },
 
   /**
    * When start arrow button is clicked scroll towards first element
    */
   onStartBtnClick: function () {
     let scrollToStart = () => {
       let element = this.getFirstInvisibleElement();
       if (!element) {
         return;
       }
 
-      element.scrollIntoView({ block: "start", behavior: "smooth" });
+      let block = this.isRtl() ? "end" : "start";
+      this.scrollToElement(element, block);
     };
 
     this.clickOrHold(scrollToStart);
   },
 
   /**
    * When end arrow button is clicked scroll towards last element
    */
   onEndBtnClick: function () {
     let scrollToEnd = () => {
       let element = this.getLastInvisibleElement();
       if (!element) {
         return;
       }
 
-      element.scrollIntoView({ block: "end", behavior: "smooth" });
+      let block = this.isRtl() ? "start" : "end";
+      this.scrollToElement(element, block);
     };
 
     this.clickOrHold(scrollToEnd);
   },
 
   /**
    * Event handler for scrolling, update the
    * enabled/disabled status of the arrow buttons
@@ -191,56 +215,82 @@ ArrowScrollBox.prototype = {
    */
   onOverflow: function () {
     this.startBtn.style.visibility = "visible";
     this.endBtn.style.visibility = "visible";
     this.emit("overflow");
   },
 
   /**
+   * Check whether the element is to the left of its container but does
+   * not also span the entire container.
+   * @param {Number} left the left scroll point of the container
+   * @param {Number} right the right edge of the container
+   * @param {Number} elementLeft the left edge of the element
+   * @param {Number} elementRight the right edge of the element
+   */
+  elementLeftOfContainer: function (left, right, elementLeft, elementRight) {
+    return elementLeft < (left - SCROLL_MARGIN)
+           && elementRight < (right - SCROLL_MARGIN);
+  },
+
+  /**
+   * Check whether the element is to the right of its container but does
+   * not also span the entire container.
+   * @param {Number} left the left scroll point of the container
+   * @param {Number} right the right edge of the container
+   * @param {Number} elementLeft the left edge of the element
+   * @param {Number} elementRight the right edge of the element
+   */
+  elementRightOfContainer: function (left, right, elementLeft, elementRight) {
+    return elementLeft > (left + SCROLL_MARGIN)
+           && elementRight > (right + SCROLL_MARGIN);
+  },
+
+  /**
    * Get the first (i.e. furthest left for LTR)
-   * non visible element in the scroll box
+   * non or partly visible element in the scroll box
    */
   getFirstInvisibleElement: function () {
-    let start = this.inner.scrollLeft;
-    let end = this.inner.scrollLeft + this.inner.clientWidth;
-    let crumbs = this.inner.childNodes;
-    for (let i = crumbs.length - 1; i > -1; i--) {
-      let element = crumbs[i];
-      let elementRight = element.offsetLeft + element.offsetWidth;
-      if (element.offsetLeft < start) {
-        // edge case, check the element isn't already visible
-        if (elementRight >= end) {
-          continue;
-        }
-        return element;
-      }
-    }
+    let elementsList = Array.from(this.inner.childNodes).reverse();
 
-    return null;
+    let predicate = this.isRtl() ?
+      this.elementRightOfContainer : this.elementLeftOfContainer;
+    return this.findFirstWithBounds(elementsList, predicate);
   },
 
   /**
    * Get the last (i.e. furthest right for LTR)
-   * non-visible element in the scroll box
+   * non or partly visible element in the scroll box
    */
   getLastInvisibleElement: function () {
-    let end = this.inner.scrollLeft + this.inner.clientWidth;
-    let elementStart = 0;
-    for (let element of this.inner.childNodes) {
-      let elementEnd = elementStart + element.offsetWidth;
-      if (elementEnd > end) {
-        // Edge case: check the element isn't bigger than the
-        // container and thus already in view
-        if (elementStart > this.inner.scrollLeft) {
-          return element;
-        }
+    let predicate = this.isRtl() ?
+      this.elementLeftOfContainer : this.elementRightOfContainer;
+    return this.findFirstWithBounds(this.inner.childNodes, predicate);
+  },
+
+  /**
+   * Find the first element that matches the given predicate, called with bounds
+   * information
+   * @param {Array} elements an ordered list of elements
+   * @param {Function} predicate a function to be called with bounds
+   * information
+   */
+  findFirstWithBounds: function (elements, predicate) {
+    let left = this.inner.scrollLeft;
+    let right = left + this.inner.clientWidth;
+    for (let element of elements) {
+      let elementLeft = element.offsetLeft - element.parentElement.offsetLeft;
+      let elementRight = elementLeft + element.offsetWidth;
+
+      // Check that the starting edge of the element is out of the visible area
+      // and that the ending edge does not span the whole container
+      if (predicate(left, right, elementLeft, elementRight)) {
+        return element;
       }
-
-      elementStart = elementEnd;
     }
 
     return null;
   },
 
   /**
    * Build the HTML for the scroll box and insert it into the DOM
    */
@@ -720,17 +770,17 @@ HTMLBreadcrumbs.prototype = {
 
   /**
    * Ensure the selected node is visible.
    */
   scroll: function () {
     // FIXME bug 684352: make sure its immediate neighbors are visible too.
     if (!this.isDestroyed) {
       let element = this.nodeHierarchy[this.currentIndex].button;
-      element.scrollIntoView({ block: "end", behavior: "smooth" });
+      this.arrowScrollBox.scrollToElement(element, "end");
     }
   },
 
   /**
    * Update all button outputs.
    */
   updateSelectors: function () {
     if (this.isDestroyed) {
@@ -810,34 +860,40 @@ HTMLBreadcrumbs.prototype = {
 
     if (!this.selection.isConnected()) {
       // remove all the crumbs
       this.cutAfter(-1);
       return;
     }
 
     // If this was an interesting deletion; then trim the breadcrumb trail
+    let trimmed = false;
     if (reason === "markupmutation") {
       for (let {type, removed} of mutations) {
         if (type !== "childList") {
           continue;
         }
 
         for (let node of removed) {
           let removedIndex = this.indexOf(node);
           if (removedIndex > -1) {
             this.cutAfter(removedIndex - 1);
+            trimmed = true;
           }
         }
       }
     }
 
     if (!this.selection.isElementNode()) {
       // no selection
       this.setCursor(-1);
+      if (trimmed) {
+        // Since something changed, notify the interested parties.
+        this.inspector.emit("breadcrumbs-updated", this.selection.nodeFront);
+      }
       return;
     }
 
     let idx = this.indexOf(this.selection.nodeFront);
 
     // Is the node already displayed in the breadcrumbs?
     // (and there are no mutations that need re-display of the crumbs)
     if (idx > -1 && !hasInterestingMutations) {
--- a/devtools/client/inspector/computed/computed.js
+++ b/devtools/client/inspector/computed/computed.js
@@ -478,16 +478,19 @@ CssComputedView.prototype = {
       this._darkStripe = true;
 
       let deferred = defer();
       this._refreshProcess = new UpdateProcess(
         this.styleWindow, this.propertyViews, {
           onItem: (propView) => {
             propView.refresh();
           },
+          onCancel: () => {
+            deferred.reject("_refreshProcess of computed view cancelled");
+          },
           onDone: () => {
             this._refreshProcess = null;
             this.noResults.hidden = this.numVisibleProperties > 0;
 
             if (this.searchField.value.length > 0 &&
                 !this.numVisibleProperties) {
               this.searchField.classList
                               .add("devtools-style-searchbox-no-match");
--- a/devtools/client/inspector/inspector-panel.js
+++ b/devtools/client/inspector/inspector-panel.js
@@ -177,27 +177,31 @@ InspectorPanel.prototype = {
   /**
    * Figure out what features the backend supports
    */
   _detectActorFeatures: function () {
     this._supportsDuplicateNode = false;
     this._supportsScrollIntoView = false;
     this._supportsResolveRelativeURL = false;
 
-    return promise.all([
-      this._target.actorHasMethod("domwalker", "duplicateNode").then(value => {
-        this._supportsDuplicateNode = value;
-      }).catch(e => console.error(e)),
-      this._target.actorHasMethod("domnode", "scrollIntoView").then(value => {
-        this._supportsScrollIntoView = value;
-      }).catch(e => console.error(e)),
-      this._target.actorHasMethod("inspector", "resolveRelativeURL").then(value => {
-        this._supportsResolveRelativeURL = value;
-      }).catch(e => console.error(e)),
-    ]);
+    // Use getActorDescription first so that all actorHasMethod calls use
+    // a cached response from the server.
+    return this._target.getActorDescription("domwalker").then(desc => {
+      return promise.all([
+        this._target.actorHasMethod("domwalker", "duplicateNode").then(value => {
+          this._supportsDuplicateNode = value;
+        }).catch(e => console.error(e)),
+        this._target.actorHasMethod("domnode", "scrollIntoView").then(value => {
+          this._supportsScrollIntoView = value;
+        }).catch(e => console.error(e)),
+        this._target.actorHasMethod("inspector", "resolveRelativeURL").then(value => {
+          this._supportsResolveRelativeURL = value;
+        }).catch(e => console.error(e)),
+      ]);
+    });
   },
 
   _deferredOpen: function (defaultSelection) {
     let deferred = defer();
 
     this.walker.on("new-root", this.onNewRoot);
 
     this.selection.on("new-node-front", this.onNewSelection);
--- a/devtools/client/inspector/inspector-search.js
+++ b/devtools/client/inspector/inspector-search.js
@@ -127,16 +127,17 @@ InspectorSearch.prototype = {
                         ? event.metaKey : event.ctrlKey;
     if (event.keyCode === event.DOM_VK_G && modifierKey) {
       this._onSearch(event.shiftKey);
       event.preventDefault();
     }
   },
 
   _onClearSearch: function () {
+    this.searchBox.classList.remove("devtools-style-searchbox-no-match");
     this.searchBox.value = "";
     this.searchClearButton.hidden = true;
   }
 };
 
 /**
  * Converts any input box on a page to a CSS selector search and suggestion box.
  *
--- a/devtools/client/inspector/layout/test/browser.ini
+++ b/devtools/client/inspector/layout/test/browser.ini
@@ -18,14 +18,12 @@ support-files =
 # Disabled for too many intermittent failures (bug 1009322)
 [browser_layout_editablemodel_bluronclick.js]
 [browser_layout_editablemodel_border.js]
 [browser_layout_editablemodel_stylerules.js]
 [browser_layout_guides.js]
 [browser_layout_rotate-labels-on-sides.js]
 [browser_layout_sync.js]
 [browser_layout_tooltips.js]
-# [browser_layout_update-after-navigation.js]
-# Disabled for too many intermittent failures (bug 1288213)
-# [browser_layout_update-after-reload.js]
-# Disabled for too many intermittent failures (bug 1287745)
+[browser_layout_update-after-navigation.js]
+[browser_layout_update-after-reload.js]
 # [browser_layout_update-in-iframes.js]
 # Bug 1020038 layout-view updates for iframe elements changes
--- a/devtools/client/inspector/test/browser.ini
+++ b/devtools/client/inspector/test/browser.ini
@@ -1,14 +1,15 @@
 [DEFAULT]
 tags = devtools
 subsuite = devtools
 support-files =
   doc_inspector_add_node.html
   doc_inspector_breadcrumbs.html
+  doc_inspector_breadcrumbs_visibility.html
   doc_inspector_delete-selected-node-01.html
   doc_inspector_delete-selected-node-02.html
   doc_inspector_embed.html
   doc_inspector_gcli-inspect-command.html
   doc_inspector_highlight_after_transition.html
   doc_inspector_highlighter-comments.html
   doc_inspector_highlighter-geometry_01.html
   doc_inspector_highlighter-geometry_02.html
@@ -43,16 +44,17 @@ support-files =
 [browser_inspector_addNode_03.js]
 [browser_inspector_breadcrumbs.js]
 [browser_inspector_breadcrumbs_highlight_hover.js]
 [browser_inspector_breadcrumbs_keybinding.js]
 [browser_inspector_breadcrumbs_keyboard_trap.js]
 skip-if = os == "mac" # Full keyboard navigation on OSX only works if Full Keyboard Access setting is set to All Control in System Keyboard Preferences
 [browser_inspector_breadcrumbs_mutations.js]
 [browser_inspector_breadcrumbs_namespaced.js]
+[browser_inspector_breadcrumbs_visibility.js]
 [browser_inspector_delete-selected-node-01.js]
 [browser_inspector_delete-selected-node-02.js]
 [browser_inspector_delete-selected-node-03.js]
 [browser_inspector_destroy-after-navigation.js]
 [browser_inspector_destroy-before-ready.js]
 [browser_inspector_expand-collapse.js]
 [browser_inspector_gcli-inspect-command.js]
 [browser_inspector_highlighter-01.js]
new file mode 100644
--- /dev/null
+++ b/devtools/client/inspector/test/browser_inspector_breadcrumbs_visibility.js
@@ -0,0 +1,106 @@
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+"use strict";
+
+// Test that the start and end buttons on the breadcrumb trail bring the right
+// crumbs into the visible area, for both LTR and RTL
+
+let { Toolbox } = require("devtools/client/framework/toolbox");
+
+const TEST_URI = URL_ROOT + "doc_inspector_breadcrumbs_visibility.html";
+const NODE_ONE = "div#aVeryLongIdToExceedTheBreadcrumbTruncationLimit";
+const NODE_TWO = "div#anotherVeryLongIdToExceedTheBreadcrumbTruncationLimit";
+const NODE_THREE = "div#aThirdVeryLongIdToExceedTheTruncationLimit";
+const NODE_FOUR = "div#aFourthOneToExceedTheTruncationLimit";
+const NODE_FIVE = "div#aFifthOneToExceedTheTruncationLimit";
+const NODE_SIX = "div#aSixthOneToExceedTheTruncationLimit";
+const NODE_SEVEN = "div#aSeventhOneToExceedTheTruncationLimit";
+
+const NODES = [
+  { action: "start", title: NODE_SIX },
+  { action: "start", title: NODE_FIVE },
+  { action: "start", title: NODE_FOUR },
+  { action: "start", title: NODE_THREE },
+  { action: "start", title: NODE_TWO },
+  { action: "start", title: NODE_ONE },
+  { action: "end", title: NODE_TWO },
+  { action: "end", title: NODE_THREE },
+  { action: "end", title: NODE_FOUR },
+  { action: "end", title: NODE_FIVE },
+  { action: "end", title: NODE_SIX }
+];
+
+add_task(function* () {
+  let { inspector, toolbox } = yield openInspectorForURL(TEST_URI);
+
+  // No way to wait for scrolling to end (Bug 1172171)
+  // Rather than wait a max time; limit test to instant scroll behavior
+  inspector.breadcrumbs.arrowScrollBox.scrollBehavior = "instant";
+
+  yield toolbox.switchHost(Toolbox.HostType.WINDOW);
+  let hostWindow = toolbox._host._window;
+  let originalWidth = hostWindow.outerWidth;
+  let originalHeight = hostWindow.outerHeight;
+  hostWindow.resizeTo(640, 300);
+
+  info("Testing transitions ltr");
+  yield pushPref("intl.uidirection.en-US", "ltr");
+  yield testBreadcrumbTransitions(hostWindow, inspector);
+
+  info("Testing transitions rtl");
+  yield pushPref("intl.uidirection.en-US", "rtl");
+  yield testBreadcrumbTransitions(hostWindow, inspector);
+
+  hostWindow.resizeTo(originalWidth, originalHeight);
+});
+
+function* testBreadcrumbTransitions(hostWindow, inspector) {
+  let breadcrumbs = inspector.panelDoc.getElementById("inspector-breadcrumbs");
+  let startBtn = breadcrumbs.querySelector(".scrollbutton-up");
+  let endBtn = breadcrumbs.querySelector(".scrollbutton-down");
+  let container = breadcrumbs.querySelector(".html-arrowscrollbox-inner");
+  let breadcrumbsUpdated = inspector.once("breadcrumbs-updated");
+
+  info("Selecting initial node");
+  yield selectNode(NODE_SEVEN, inspector);
+
+  // So just need to wait for a duration
+  yield breadcrumbsUpdated;
+  let initialCrumb = container.querySelector("button[checked]");
+  is(isElementInViewport(hostWindow, initialCrumb), true,
+     "initial element was visible");
+
+  for (let node of NODES) {
+    info("Checking for visibility of crumb " + node.title);
+    if (node.action === "end") {
+      info("Simulating click of end button");
+      EventUtils.synthesizeMouseAtCenter(endBtn, {}, inspector.panelWin);
+    } else if (node.action === "start") {
+      info("Simulating click of start button");
+      EventUtils.synthesizeMouseAtCenter(startBtn, {}, inspector.panelWin);
+    }
+
+    yield breadcrumbsUpdated;
+    let selector = "button[title=\"" + node.title + "\"]";
+    let relevantCrumb = container.querySelector(selector);
+    is(isElementInViewport(hostWindow, relevantCrumb), true,
+       node.title + " crumb is visible");
+  }
+}
+
+function isElementInViewport(window, el) {
+  let rect = el.getBoundingClientRect();
+
+  return (
+    rect.top >= 0 &&
+    rect.left >= 0 &&
+    rect.bottom <= window.innerHeight &&
+    rect.right <= window.innerWidth
+  );
+}
+
+registerCleanupFunction(function () {
+  // Restore the host type for other tests.
+  Services.prefs.clearUserPref("devtools.toolbox.host");
+});
--- a/devtools/client/inspector/test/browser_inspector_delete-selected-node-02.js
+++ b/devtools/client/inspector/test/browser_inspector_delete-selected-node-02.js
@@ -88,16 +88,17 @@ add_task(function* () {
 
     expectedCrumbs = ["html", "body", "div#deleteToMakeSingleTextNode"];
     yield assertNodeSelectedAndCrumbsUpdated(expectedCrumbs,
                                              Node.ELEMENT_NODE);
   }
 
   function* deleteNodeWithContextMenu(selector) {
     yield selectNode(selector, inspector);
+    let nodeToBeDeleted = inspector.selection.nodeFront;
 
     info("Getting the node container in the markup view.");
     let container = yield getContainerForSelector(selector, inspector);
 
     let allMenuItems = openContextMenuAndGetAllItems(inspector, {
       target: container.tagLine,
     });
     let menuItem = allMenuItems.find(item => item.id === "node-menu-delete");
@@ -106,16 +107,26 @@ add_task(function* () {
     is(menuItem.disabled, false, "delete menu item is enabled");
     menuItem.click();
 
     // close the open context menu
     EventUtils.synthesizeKey("VK_ESCAPE", {});
 
     info("Waiting for inspector to update.");
     yield inspector.once("inspector-updated");
+
+    // Since the mutations are sent asynchronously from the server, the
+    // inspector-updated event triggered by the deletion might happen before
+    // the mutation is received and the element is removed from the
+    // breadcrumbs. See bug 1284125.
+    if (inspector.breadcrumbs.indexOf(nodeToBeDeleted) > -1) {
+      info("Crumbs haven't seen deletion. Waiting for breadcrumbs-updated.");
+      yield inspector.once("breadcrumbs-updated");
+    }
+
     return menuItem;
   }
 
   function* assertNodeSelectedAndCrumbsUpdated(expectedCrumbs,
                                                expectedNodeType) {
     info("Performing checks");
     let actualNodeType = inspector.selection.nodeFront.nodeType;
     is(actualNodeType, expectedNodeType, "The node has the right type");
new file mode 100644
--- /dev/null
+++ b/devtools/client/inspector/test/doc_inspector_breadcrumbs_visibility.html
@@ -0,0 +1,22 @@
+<html>
+    <head>
+        <meta http-equiv="content-type" content="text/html; charset=windows-1252">
+    </head>
+    <body>
+        <div id="aVeryLongIdToExceedTheBreadcrumbTruncationLimit">
+            <div id="anotherVeryLongIdToExceedTheBreadcrumbTruncationLimit">
+                <div id="aThirdVeryLongIdToExceedTheTruncationLimit">
+                    <div id="aFourthOneToExceedTheTruncationLimit">
+                        <div id="aFifthOneToExceedTheTruncationLimit">
+                            <div id="aSixthOneToExceedTheTruncationLimit">
+                                <div id="aSeventhOneToExceedTheTruncationLimit">
+                                    A text node at the end
+                                </div>
+                            </div>
+                        </div>
+                    </div>
+                </div>
+            </div>
+        </div>
+    </body>
+</html>
--- a/devtools/client/netmonitor/har/har-builder.js
+++ b/devtools/client/netmonitor/har/har-builder.js
@@ -160,16 +160,17 @@ HarBuilder.prototype = {
       bodySize: 0
     };
 
     request.method = file.method;
     request.url = file.url;
     request.httpVersion = file.httpVersion || "";
 
     request.headers = this.buildHeaders(file.requestHeaders);
+    request.headers = this.appendHeadersPostData(request.headers, file);
     request.cookies = this.buildCookies(file.requestCookies);
 
     request.queryString = NetworkHelper.parseQueryString(
       NetworkHelper.nsIURL(file.url).query) || [];
 
     request.postData = this.buildPostData(file);
 
     request.headersSize = file.requestHeaders.headersSize;
@@ -194,16 +195,43 @@ HarBuilder.prototype = {
   buildHeaders: function (input) {
     if (!input) {
       return [];
     }
 
     return this.buildNameValuePairs(input.headers);
   },
 
+  appendHeadersPostData: function (input = [], file) {
+    if (!file.requestPostData) {
+      return input;
+    }
+
+    this.fetchData(file.requestPostData.postData.text).then(value => {
+      let contentType = value.match(/Content-Type: ([^;\s]+)/);
+      let contentLength = value.match(/Content-Length: (.+)/);
+
+      if (contentType && contentType.length > 1) {
+        input.push({
+          name: "Content-Type",
+          value: contentType[1]
+        });
+      }
+
+      if (contentLength && contentLength.length > 1) {
+        input.push({
+          name: "Content-Length",
+          value: contentLength[1]
+        });
+      }
+    });
+
+    return input;
+  },
+
   buildCookies: function (input) {
     if (!input) {
       return [];
     }
 
     return this.buildNameValuePairs(input.cookies);
   },
 
--- a/devtools/client/netmonitor/test/browser_net_service-worker-status.js
+++ b/devtools/client/netmonitor/test/browser_net_service-worker-status.js
@@ -7,18 +7,18 @@
  * Tests if requests intercepted by service workers have the correct status code
  */
 
 // Service workers only work on https
 const URL = EXAMPLE_URL.replace("http:", "https:");
 
 const TEST_URL = URL + "service-workers/status-codes.html";
 
-var test = Task.async(function* () {
-  let [, debuggee, monitor] = yield initNetMonitor(TEST_URL, null, true);
+add_task(function* () {
+  let [tab, , monitor] = yield initNetMonitor(TEST_URL, null, true);
   info("Starting test... ");
 
   let { NetMonitorView } = monitor.panelWin;
   let { RequestsMenu } = NetMonitorView;
 
   const REQUEST_DATA = [
     {
       method: "GET",
@@ -30,21 +30,26 @@ var test = Task.async(function* () {
         type: "plain",
         fullMimeType: "text/plain; charset=UTF-8"
       },
       stackFunctions: ["doXHR", "performRequests"]
     },
   ];
 
   info("Registering the service worker...");
-  yield debuggee.registerServiceWorker();
+  yield ContentTask.spawn(tab.linkedBrowser, {}, function* () {
+    yield content.wrappedJSObject.registerServiceWorker();
+  });
 
   info("Performing requests...");
-  debuggee.performRequests();
-  yield waitForNetworkEvents(monitor, REQUEST_DATA.length);
+  let wait = waitForNetworkEvents(monitor, REQUEST_DATA.length);
+  yield ContentTask.spawn(tab.linkedBrowser, {}, function* () {
+    content.wrappedJSObject.performRequests();
+  });
+  yield wait;
 
   let index = 0;
   for (let request of REQUEST_DATA) {
     let item = RequestsMenu.getItemAtIndex(index);
 
     info(`Verifying request #${index}`);
     yield verifyRequestItemTarget(item, request.method, request.uri, request.details);
 
@@ -59,13 +64,14 @@ var test = Task.async(function* () {
       is(stacktrace[j].functionName, functionName,
       `Request #${index} has the correct function at position #${j} on the stack`);
     });
 
     index++;
   }
 
   info("Unregistering the service worker...");
-  yield debuggee.unregisterServiceWorker();
+  yield ContentTask.spawn(tab.linkedBrowser, {}, function* () {
+    yield content.wrappedJSObject.unregisterServiceWorker();
+  });
 
   yield teardown(monitor);
-  finish();
 });
--- a/devtools/client/shared/components/reps/array.js
+++ b/devtools/client/shared/components/reps/array.js
@@ -32,34 +32,29 @@ define(function (require, exports, modul
       let delim;
 
       for (let i = 0; i < array.length && i < max; i++) {
         try {
           let value = array[i];
 
           delim = (i == array.length - 1 ? "" : ", ");
 
-          if (value === array) {
-            items.push(Reference({
-              key: i,
-              object: value,
-              delim: delim
-            }));
-          } else {
-            items.push(ItemRep({
-              key: i,
-              object: value,
-              delim: delim
-            }));
-          }
+          items.push(ItemRep({
+            key: i,
+            object: value,
+            // Hardcode tiny mode to avoid recursive handling.
+            mode: "tiny",
+            delim: delim
+          }));
         } catch (exc) {
           items.push(ItemRep({
+            key: i,
             object: exc,
-            delim: delim,
-            key: i
+            mode: "tiny",
+            delim: delim
           }));
         }
       }
 
       if (array.length > max) {
         let objectLink = this.props.objectLink || DOM.span;
         items.push(Caption({
           key: "more",
@@ -168,40 +163,26 @@ define(function (require, exports, modul
   let ItemRep = React.createFactory(React.createClass({
     displayName: "ItemRep",
 
     render: function () {
       const { Rep } = createFactories(require("./rep"));
 
       let object = this.props.object;
       let delim = this.props.delim;
+      let mode = this.props.mode;
       return (
         DOM.span({},
-          Rep({object: object}),
+          Rep({object: object, mode: mode}),
           delim
         )
       );
     }
   }));
 
-  /**
-   * Renders cycle references in an array.
-   */
-  let Reference = React.createFactory(React.createClass({
-    displayName: "Reference",
-
-    render: function () {
-      let tooltip = "Circular reference";
-      return (
-        DOM.span({title: tooltip},
-          "[…]")
-      );
-    }
-  }));
-
   function supportsObject(object, type) {
     return Array.isArray(object) ||
       Object.prototype.toString.call(object) === "[object Arguments]";
   }
 
   // Exports from this module
   exports.ArrayRep = {
     rep: ArrayRep,
--- a/devtools/client/shared/components/reps/event.js
+++ b/devtools/client/shared/components/reps/event.js
@@ -6,65 +6,41 @@
 "use strict";
 
 // Make this available to both AMD and CJS environments
 define(function (require, exports, module) {
   // ReactJS
   const React = require("devtools/client/shared/vendor/react");
 
   // Reps
-  const { isGrip } = require("./rep-utils");
-
-  // Shortcuts
-  const { span } = React.DOM;
+  const { createFactories, isGrip } = require("./rep-utils");
+  const { rep } = createFactories(require("./grip").Grip);
 
   /**
    * Renders DOM event objects.
    */
   let Event = React.createClass({
     displayName: "event",
 
     propTypes: {
       object: React.PropTypes.object.isRequired
     },
 
-    getTitle: function (grip) {
-      if (this.props.objectLink) {
-        return this.props.objectLink({
-          object: grip
-        }, grip.preview.type);
-      }
-      return grip.preview.type;
-    },
-
-    summarizeEvent: function (grip) {
-      let info = [];
-
-      let eventFamily = grip.class;
-      let props = grip.preview.properties;
-
-      if (eventFamily == "MouseEvent") {
-        info.push("clientX=", props.clientX, ", clientY=", props.clientY);
-      } else if (eventFamily == "KeyboardEvent") {
-        info.push("charCode=", props.charCode, ", keyCode=", props.keyCode);
-      } else if (eventFamily == "MessageEvent") {
-        info.push("origin=", props.origin, ", data=", props.data);
-      }
-
-      return info.join("");
-    },
-
     render: function () {
-      let grip = this.props.object;
-      return (
-        span({className: "objectBox objectBox-event"},
-          this.getTitle(grip),
-          this.summarizeEvent(grip)
-        )
-      );
+      // Use `Object.assign` to keep `this.props` without changes becuase:
+      // 1. JSON.stringify/JSON.parse is slow.
+      // 2. Immutable.js is planned for the future.
+      let props = Object.assign({}, this.props);
+      props.object = Object.assign({}, this.props.object);
+      props.object.preview = Object.assign({}, this.props.object.preview);
+      props.object.preview.ownProperties = props.object.preview.properties;
+      delete props.object.preview.properties;
+      props.object.ownPropertyLength =
+        Object.keys(props.object.preview.ownProperties).length;
+      return rep(props);
     },
   });
 
   // Registration
 
   function supportsObject(grip, type) {
     if (!isGrip(grip)) {
       return false;
--- a/devtools/client/shared/components/reps/grip-array.js
+++ b/devtools/client/shared/components/reps/grip-array.js
@@ -51,24 +51,28 @@ define(function (require, exports, modul
       }
 
       let array = grip.preview.items;
       if (!array) {
         return items;
       }
 
       let delim;
+      // number of grip.preview.items is limited to 10, but we may have more
+      // items in grip-array
+      let delimMax = grip.preview.length > array.length ?
+        array.length : array.length - 1;
       let provider = this.props.provider;
 
       for (let i = 0; i < array.length && i < max; i++) {
         try {
           let itemGrip = array[i];
           let value = provider ? provider.getValue(itemGrip) : itemGrip;
 
-          delim = (i == array.length - 1 ? "" : ", ");
+          delim = (i == delimMax ? "" : ", ");
 
           if (value === array) {
             items.push(Reference({
               key: i,
               object: value,
               delim: delim}
             ));
           } else {
@@ -81,24 +85,25 @@ define(function (require, exports, modul
         } catch (exc) {
           items.push(GripArrayItem(Object.assign({}, this.props, {
             object: exc,
             delim: delim,
             key: i}
           )));
         }
       }
-
-      if (array.length > max) {
+      if (array.length > max || grip.preview.length > array.length) {
         let objectLink = this.props.objectLink || span;
+        let leftItemNum = grip.preview.length - max > 0 ?
+          grip.preview.length - max : grip.preview.length - array.length;
         items.push(Caption({
           key: "more",
           object: objectLink({
             object: this.props.object
-          }, (grip.preview.length - max) + " more…")
+          }, leftItemNum + " more…")
         }));
       }
 
       return items;
     },
 
     render: function () {
       let mode = this.props.mode || "short";
--- a/devtools/client/shared/components/reps/grip.js
+++ b/devtools/client/shared/components/reps/grip.js
@@ -105,17 +105,18 @@ define(function (require, exports, modul
 
       // Make indexes ordered by ascending.
       indexes.sort(function (a, b) {
         return a - b;
       });
 
       indexes.forEach((i) => {
         let name = Object.keys(ownProperties)[i];
-        let value = ownProperties[name].value;
+        let prop = ownProperties[name];
+        let value = prop.value !== undefined ? prop.value : prop;
         props.push(PropRep(Object.assign({}, this.props, {
           key: name,
           mode: "tiny",
           name: name,
           object: value,
           equal: ": ",
           delim: ", ",
           defaultRep: Grip
@@ -139,17 +140,17 @@ define(function (require, exports, modul
       try {
         let i = 0;
         for (let name in ownProperties) {
           if (indexes.length >= max) {
             return indexes;
           }
 
           let prop = ownProperties[name];
-          let value = prop.value;
+          let value = prop.value !== undefined ? prop.value : prop;
 
           // Type is specified in grip's "class" field and for primitive
           // values use typeof.
           let type = (value.class || typeof value);
           type = type.toLowerCase();
 
           if (filter(type, value)) {
             indexes.push(i);
--- a/devtools/client/shared/components/reps/object.js
+++ b/devtools/client/shared/components/reps/object.js
@@ -91,17 +91,18 @@ define(function (require, exports, modul
     getProps: function (object, max, filter) {
       let props = [];
 
       max = max || 3;
       if (!object) {
         return props;
       }
 
-      let mode = this.props.mode;
+      // Hardcode tiny mode to avoid recursive handling.
+      let mode = "tiny";
 
       try {
         for (let name in object) {
           if (props.length > max) {
             return props;
           }
 
           let value;
--- a/devtools/client/shared/components/reps/string.js
+++ b/devtools/client/shared/components/reps/string.js
@@ -30,18 +30,21 @@ define(function (require, exports, modul
             "\"" + text + "\""
           )
         );
       }
 
       let croppedString = this.props.cropLimit ?
         cropMultipleLines(text, this.props.cropLimit) : cropMultipleLines(text);
 
+      let formattedString = this.props.omitQuotes ?
+        croppedString : "\"" + croppedString + "\"";
+
       return (
-        span({className: "objectBox objectBox-string"}, "\"" + croppedString + "\""
+        span({className: "objectBox objectBox-string"}, formattedString
         )
       );
     },
   });
 
   function supportsObject(object, type) {
     return (type == "string");
   }
--- a/devtools/client/shared/components/tabs/tabs.css
+++ b/devtools/client/shared/components/tabs/tabs.css
@@ -30,17 +30,16 @@
 }
 
 /* Make sure panel content takes entire vertical space.
   (minus the height of the tab bar) */
 .tabs .panels {
   height: calc(100% - 24px);
 }
 
-.tabs .tab-panel-box,
 .tabs .tab-panel {
   height: 100%;
 }
 
 /* Light Theme */
 
 .theme-dark .tabs,
 .theme-light .tabs {
--- a/devtools/client/shared/components/test/mochitest/test_reps_array.html
+++ b/devtools/client/shared/components/test/mochitest/test_reps_array.html
@@ -156,17 +156,17 @@ window.onload = Task.async(function* () 
     ];
 
     testRepRenderModes(modeTests, "testMoreThanMaxProps", componentUnderTest, stub);
   }
 
   function testRecursiveArray() {
     let stub = [1];
     stub.push(stub);
-    const defaultOutput = `[ 1, […] ]`;
+    const defaultOutput = `[ 1, [2] ]`;
 
     const modeTests = [
       {
         mode: undefined,
         expectedOutput: defaultOutput,
       },
       {
         mode: "tiny",
@@ -189,17 +189,17 @@ window.onload = Task.async(function* () 
     let stub = [
       {
         p1: "s1",
         p2: ["a1", "a2", "a3"],
         p3: "s3",
         p4: "s4"
       }
     ];
-    const defaultOutput = `[ Object { p1: "s1", p3: "s3", p4: "s4", 1 more… } ]`;
+    const defaultOutput = `[ Object ]`;
 
     const modeTests = [
       {
         mode: undefined,
         expectedOutput: defaultOutput,
       },
       {
         mode: "tiny",
--- a/devtools/client/shared/components/test/mochitest/test_reps_event.html
+++ b/devtools/client/shared/components/test/mochitest/test_reps_event.html
@@ -30,32 +30,40 @@ window.onload = Task.async(function* () 
   } catch(e) {
     ok(false, "Got an error: " + DevToolsUtils.safeErrorString(e));
   } finally {
     SimpleTest.finish();
   }
 
   function testEvent() {
     const renderedComponent = renderComponent(Event.rep, { object: getGripStub("testEvent") });
-    is(renderedComponent.textContent, "beforeprint", "Event rep has expected text content for an event");
+    is(renderedComponent.textContent,
+       "Event { isTrusted: true, eventPhase: 2, bubbles: false, 7 more… }",
+       "Event rep has expected text content for an event");
   }
 
   function testMouseEvent() {
     const renderedComponent = renderComponent(Event.rep, { object: getGripStub("testMouseEvent") });
-    is(renderedComponent.textContent, "clickclientX=62, clientY=18", "Event rep has expected text content for a mouse event");
+    is(renderedComponent.textContent,
+       "MouseEvent { buttons: 0, clientX: 62, clientY: 18, 2 more… }",
+       "Event rep has expected text content for a mouse event");
   }
 
   function testKeyboardEvent() {
     const renderedComponent = renderComponent(Event.rep, { object: getGripStub("testKeyboardEvent") });
-    is(renderedComponent.textContent, "keyupcharCode=0, keyCode=17", "Event rep has expected text content for a keyboard event");
+    is(renderedComponent.textContent,
+       "KeyboardEvent { key: \"Control\", charCode: 0, keyCode: 17 }",
+       "Event rep has expected text content for a keyboard event");
   }
 
   function testMessageEvent() {
     const renderedComponent = renderComponent(Event.rep, { object: getGripStub("testMessageEvent") });
-    is(renderedComponent.textContent, "messageorigin=null, data=test data", "Event rep has expected text content for a message event");
+    is(renderedComponent.textContent,
+       "MessageEvent { isTrusted: false, data: \"test data\", origin: \"null\", 7 more… }",
+       "Event rep has expected text content for a message event");
   }
 
   function getGripStub(name) {
     switch (name) {
       case "testEvent":
         return {
           "type": "object",
           "class": "Event",
--- a/devtools/client/shared/components/test/mochitest/test_reps_grip-array.html
+++ b/devtools/client/shared/components/test/mochitest/test_reps_grip-array.html
@@ -27,16 +27,17 @@ window.onload = Task.async(function* () 
   try {
     yield testBasic();
 
     // Test property iterator
     yield testMaxProps();
     yield testMoreThanShortMaxProps();
     yield testMoreThanLongMaxProps();
     yield testRecursiveArray();
+    yield testPreviewLimit();
 
     yield testNamedNodeMap();
   } catch(e) {
     ok(false, "Got an error: " + DevToolsUtils.safeErrorString(e));
   } finally {
     SimpleTest.finish();
   }
 
@@ -185,16 +186,44 @@ window.onload = Task.async(function* () 
         mode: "long",
         expectedOutput: defaultOutput,
       }
     ];
 
     testRepRenderModes(modeTests, testName, componentUnderTest, getGripStub(testName));
   }
 
+  function testPreviewLimit() {
+    const testName = "testPreviewLimit";
+
+    const shortOutput = `Array[ 0, 1, 2, 8 more… ]`;
+    const defaultOutput = `Array[ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 1 more… ]`;
+
+    const modeTests = [
+      {
+        mode: undefined,
+        expectedOutput: shortOutput,
+      },
+      {
+        mode: "tiny",
+        expectedOutput: `[11]`,
+      },
+      {
+        mode: "short",
+        expectedOutput: shortOutput,
+      },
+      {
+        mode: "long",
+        expectedOutput: defaultOutput,
+      }
+    ];
+
+    testRepRenderModes(modeTests, testName, componentUnderTest, getGripStub(testName));
+  }
+
   function testNamedNodeMap() {
     const testName = "testNamedNodeMap";
 
     const defaultOutput = `NamedNodeMap[ class="myclass", cellpadding="7", border="3" ]`;
 
     const modeTests = [
       {
         mode: undefined,
@@ -306,16 +335,32 @@ window.onload = Task.async(function* () 
         // Generate array grip with length 301, which is more that the maximum
         // limit in case of the 'long' mode.
         for (let i = 0; i < maxLength.long + 1; i++) {
           longArrayGrip.preview.items.push("test string");
         }
 
         return longArrayGrip;
 
+      case "testPreviewLimit":
+        return {
+          "type": "object",
+          "class": "Array",
+          "actor": "server1.conn1.obj31",
+          "extensible": true,
+          "frozen": false,
+          "sealed": false,
+          "ownPropertyLength": 12,
+          "preview": {
+            "kind": "ArrayLike",
+            "length": 11,
+            "items": [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
+          }
+        };
+
       case "testRecursiveArray":
         return {
           "type": "object",
           "class": "Array",
           "actor": "server1.conn3.obj42",
           "extensible": true,
           "frozen": false,
           "sealed": false,
--- a/devtools/client/shared/components/test/mochitest/test_reps_object.html
+++ b/devtools/client/shared/components/test/mochitest/test_reps_object.html
@@ -155,17 +155,17 @@ window.onload = Task.async(function* () 
     const stub = {
       objProp: {
         id: 1,
         arr: [2]
       },
       strProp: "test string",
       arrProp: [1]
     };
-    const defaultOutput = `Object { strProp: "test string", objProp: Object { id: 1, arr: [ 2 ] }, arrProp: [ 1 ] }`;
+    const defaultOutput = `Object { strProp: "test string", objProp: Object, arrProp: [1] }`;
 
     const modeTests = [
       {
         mode: undefined,
         expectedOutput: defaultOutput,
       },
       {
         mode: "tiny",
--- a/devtools/client/shared/components/test/mochitest/test_reps_string.html
+++ b/devtools/client/shared/components/test/mochitest/test_reps_string.html
@@ -22,16 +22,17 @@ window.onload = Task.async(function* () 
     // Test that correct rep is chosen
     const renderedRep = shallowRenderComponent(Rep, { object: getGripStub("testMultiline") });
     is(renderedRep.type, StringRep.rep, `Rep correctly selects ${StringRep.rep.displayName}`);
 
     // Test rendering
     yield testMultiline();
     yield testMultilineOpen();
     yield testMultilineLimit();
+    yield testOmitQuotes();
   } catch(e) {
     ok(false, "Got an error: " + DevToolsUtils.safeErrorString(e));
   } finally {
     SimpleTest.finish();
   }
 
   function testMultiline() {
     const renderedComponent = renderComponent(StringRep.rep, { object: getGripStub("testMultiline") });
@@ -43,19 +44,27 @@ window.onload = Task.async(function* () 
     is(renderedComponent.textContent, "\"aaaaaaaaaa…cccccccc\\n\"", "String rep has expected text content for multiline string with specified number of characters");
   }
 
   function testMultilineOpen() {
     const renderedComponent = renderComponent(StringRep.rep, { object: getGripStub("testMultiline"), member: {open: true} });
     is(renderedComponent.textContent, "\"aaaaaaaaaaaaaaaaaaaaa\nbbbbbbbbbbbbbbbbbbb\ncccccccccccccccc\n\"", "String rep has expected text content for multiline string when open");
   }
 
+  function testOmitQuotes(){
+     const renderedComponent = renderComponent(StringRep.rep, { object: getGripStub("testOmitQuotes"), omitQuotes: true });
+     is(renderedComponent.textContent, "abc","String rep has expected to omit quotes");
+  }
+
   function getGripStub(name) {
     switch (name) {
       case "testMultiline":
-        return "aaaaaaaaaaaaaaaaaaaaa\nbbbbbbbbbbbbbbbbbbb\ncccccccccccccccc\n";
+      	 return "aaaaaaaaaaaaaaaaaaaaa\nbbbbbbbbbbbbbbbbbbb\ncccccccccccccccc\n";
+	 break;
+      case "testOmitQuotes":
+	 return "abc";
     }
   }
 });
 </script>
 </pre>
 </body>
 </html>
--- a/devtools/client/shared/widgets/VariablesView.jsm
+++ b/devtools/client/shared/widgets/VariablesView.jsm
@@ -1252,17 +1252,17 @@ function Scope(aView, aName, aFlags = {}
   this.editableNameTooltip = aView.editableNameTooltip;
   this.editableValueTooltip = aView.editableValueTooltip;
   this.editButtonTooltip = aView.editButtonTooltip;
   this.deleteButtonTooltip = aView.deleteButtonTooltip;
   this.domNodeValueTooltip = aView.domNodeValueTooltip;
   this.contextMenuId = aView.contextMenuId;
   this.separatorStr = aView.separatorStr;
 
-  this._init(aName.trim(), aFlags);
+  this._init(aName, aFlags);
 }
 
 Scope.prototype = {
   /**
    * Whether this Scope should be prefetched when it is remoted.
    */
   shouldPrefetch: true,
 
@@ -1817,17 +1817,17 @@ Scope.prototype = {
     element.id = this._idString;
     element.className = aTargetClassName;
 
     let arrow = this._arrow = document.createElement("hbox");
     arrow.className = "arrow theme-twisty";
 
     let name = this._name = document.createElement("label");
     name.className = "plain name";
-    name.setAttribute("value", aName);
+    name.setAttribute("value", aName.trim());
     name.setAttribute("crop", "end");
 
     let title = this._title = document.createElement("hbox");
     title.className = "title " + aTitleClassName;
     title.setAttribute("align", "center");
 
     let enumerable = this._enum = document.createElement("vbox");
     let nonenum = this._nonenum = document.createElement("vbox");
--- a/devtools/client/themes/inspector.css
+++ b/devtools/client/themes/inspector.css
@@ -32,17 +32,16 @@
 
 #inspector-search {
   flex: unset;
 }
 
 /* TODO: bug 1265759: should apply to .devtools-searchinput once all searchbox
    is converted to html*/
 #inspector-searchbox {
-  flex: 1;
   width: 100%;
 }
 
 /* Make sure the text is vertically centered in Inspector's
    search box. This can be removed when the search box is
    switched to HTML.
    See also: https://bugzilla.mozilla.org/show_bug.cgi?id=1265759 */
 .theme-dark #inspector-searchbox,
--- a/devtools/client/themes/toolbars.css
+++ b/devtools/client/themes/toolbars.css
@@ -457,17 +457,17 @@
 .devtools-style-searchbox-no-match {
   background-color: var(--searcbox-no-match-background-color) !important;
   border-color: var(--searcbox-no-match-border-color) !important;
 }
 
 .devtools-searchinput-clear {
   position: absolute;
   top: 3.5px;
-  right: 7px;
+  offset-inline-end: 7px;
   padding: 0;
   border: 0;
   width: 16px;
   height: 16px;
   background-position: 0 0;
   background-repeat: no-repeat;
   background-color: transparent;
 }
--- a/devtools/client/webconsole/jsterm.js
+++ b/devtools/client/webconsole/jsterm.js
@@ -442,18 +442,16 @@ JSTerm.prototype = {
     if (inspectorSelection && inspectorSelection.nodeFront) {
       selectedNodeActor = inspectorSelection.nodeFront.actorID;
     }
 
     if (this.hud.NEW_CONSOLE_OUTPUT_ENABLED) {
       const { ConsoleCommand } = require("devtools/client/webconsole/new-console-output/types");
       let message = new ConsoleCommand({
         messageText: executeString,
-        source: "javascript",
-        type: "command",
         // @TODO remove category and severity
         category: "input",
         severity: "log",
       });
       this.hud.newConsoleOutput.dispatchMessageAdd(message);
     } else {
       let message = new Messages.Simple(executeString, {
         category: "input",
new file mode 100644
--- /dev/null
+++ b/devtools/client/webconsole/new-console-output/actions/filters.js
@@ -0,0 +1,39 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+const {
+  FILTER_TEXT_SET,
+  FILTER_TOGGLE,
+  FILTERS_CLEAR
+} = require("../constants");
+
+function filterTextSet(text) {
+  return {
+    type: FILTER_TEXT_SET,
+    text
+  };
+}
+
+function filterToggle(filter) {
+  return {
+    type: FILTER_TOGGLE,
+    filter,
+  };
+}
+
+function filtersClear() {
+  return {
+    type: FILTERS_CLEAR
+  };
+}
+
+module.exports = {
+  filterTextSet,
+  filterToggle,
+  filtersClear
+};
--- a/devtools/client/webconsole/new-console-output/actions/messages.js
+++ b/devtools/client/webconsole/new-console-output/actions/messages.js
@@ -8,20 +8,17 @@
 
 const {
   prepareMessage
 } = require("devtools/client/webconsole/new-console-output/utils/messages");
 const { IdGenerator } = require("devtools/client/webconsole/new-console-output/utils/id-generator");
 
 const {
   MESSAGE_ADD,
-  MESSAGES_CLEAR,
-  SEVERITY_FILTER,
-  MESSAGES_SEARCH,
-  FILTERS_CLEAR,
+  MESSAGES_CLEAR
 } = require("../constants");
 
 const defaultIdGenerator = new IdGenerator();
 
 function messageAdd(packet, idGenerator = null) {
   if (idGenerator == null) {
     idGenerator = defaultIdGenerator;
   }
@@ -34,34 +31,10 @@ function messageAdd(packet, idGenerator 
 }
 
 function messagesClear() {
   return {
     type: MESSAGES_CLEAR
   };
 }
 
-function severityFilter(filter, toggled) {
-  return {
-    type: SEVERITY_FILTER,
-    filter,
-    toggled
-  };
-}
-
-function filtersClear() {
-  return {
-    type: FILTERS_CLEAR
-  };
-}
-
-function messagesSearch(searchText) {
-  return {
-    type: MESSAGES_SEARCH,
-    searchText
-  };
-}
-
 exports.messageAdd = messageAdd;
 exports.messagesClear = messagesClear;
-exports.severityFilter = severityFilter;
-exports.filtersClear = filtersClear;
-exports.messagesSearch = messagesSearch;
--- a/devtools/client/webconsole/new-console-output/actions/moz.build
+++ b/devtools/client/webconsole/new-console-output/actions/moz.build
@@ -1,9 +1,10 @@
 # vim: set filetype=python:
 # This Source Code Form is subject to the terms of the Mozilla Public
 # License, v. 2.0. If a copy of the MPL was not distributed with this
 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
 
 DevToolsModules(
+    'filters.js',
     'messages.js',
     'ui.js',
 )
--- a/devtools/client/webconsole/new-console-output/actions/ui.js
+++ b/devtools/client/webconsole/new-console-output/actions/ui.js
@@ -2,18 +2,18 @@
 /* vim: set ft=javascript ts=2 et sw=2 tw=80: */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 "use strict";
 
 const {
-  FILTERBAR_TOGGLE,
+  FILTER_BAR_TOGGLE,
 } = require("../constants");
 
 function filterBarToggle(show) {
   return {
-    type: FILTERBAR_TOGGLE
+    type: FILTER_BAR_TOGGLE
   };
 }
 
 exports.filterBarToggle = filterBarToggle;
--- a/devtools/client/webconsole/new-console-output/components/filter-bar.js
+++ b/devtools/client/webconsole/new-console-output/components/filter-bar.js
@@ -7,103 +7,100 @@ const {
   createFactory,
   createClass,
   DOM: dom,
   PropTypes
 } = require("devtools/client/shared/vendor/react");
 const { connect } = require("devtools/client/shared/vendor/react-redux");
 const { getAllFilters } = require("devtools/client/webconsole/new-console-output/selectors/filters");
 const { getAllUi } = require("devtools/client/webconsole/new-console-output/selectors/ui");
-const messagesActions = require("devtools/client/webconsole/new-console-output/actions/messages");
+const { filterTextSet, filtersClear } = require("devtools/client/webconsole/new-console-output/actions/filters");
+const { messagesClear } = require("devtools/client/webconsole/new-console-output/actions/messages");
 const uiActions = require("devtools/client/webconsole/new-console-output/actions/ui");
 const {
-  SEVERITY_FILTER
+  MESSAGE_LEVEL
 } = require("../constants");
-const FilterToggleButton = createFactory(require("devtools/client/webconsole/new-console-output/components/filter-toggle-button").FilterToggleButton);
+const FilterButton = createFactory(require("devtools/client/webconsole/new-console-output/components/filter-button").FilterButton);
 
 const FilterBar = createClass({
 
   displayName: "FilterBar",
 
   propTypes: {
     filter: PropTypes.object.isRequired,
     ui: PropTypes.object.isRequired
   },
 
-  onClearOutputButtonClick: function () {
-    this.props.dispatch(messagesActions.messagesClear());
+  onClickMessagesClear: function () {
+    this.props.dispatch(messagesClear());
   },
 
-  onToggleFilterConfigBarButtonClick: function () {
+  onClickFilterBarToggle: function () {
     this.props.dispatch(uiActions.filterBarToggle());
   },
 
-  onClearFiltersButtonClick: function () {
-    this.props.dispatch(messagesActions.filtersClear());
+  onClickFiltersClear: function () {
+    this.props.dispatch(filtersClear());
   },
 
   onSearchInput: function (e) {
-    this.props.dispatch(messagesActions.messagesSearch(e.target.value));
+    this.props.dispatch(filterTextSet(e.target.value));
   },
 
   render() {
     const {dispatch, filter, ui} = this.props;
-    let configFilterBarVisible = ui.configFilterBarVisible;
+    let filterBarVisible = ui.filterBarVisible;
     let children = [];
 
     children.push(dom.div({className: "devtools-toolbar webconsole-filterbar-primary"},
       dom.button({
         className: "devtools-button devtools-clear-icon",
         title: "Clear output",
-        onClick: this.onClearOutputButtonClick
+        onClick: this.onClickMessagesClear
       }),
       dom.button({
         className: "devtools-button devtools-filter-icon" + (
-          configFilterBarVisible ? " checked" : ""),
+          filterBarVisible ? " checked" : ""),
         title: "Toggle filter bar",
-        onClick: this.onToggleFilterConfigBarButtonClick
+        onClick: this.onClickFilterBarToggle
       }),
       dom.input({
         className: "devtools-plaininput",
         type: "search",
-        value: filter.searchText,
+        value: filter.text,
         placeholder: "Filter output",
         onInput: this.onSearchInput
       })
     ));
 
-    if (configFilterBarVisible) {
+    if (filterBarVisible) {
       children.push(
         dom.div({className: "devtools-toolbar"},
-          FilterToggleButton({
+          FilterButton({
             active: filter.error,
             label: "Errors",
-            filterType: SEVERITY_FILTER,
-            filterKey: "error",
+            filterKey: MESSAGE_LEVEL.ERROR,
             dispatch
           }),
-          FilterToggleButton({
+          FilterButton({
             active: filter.warn,
             label: "Warnings",
-            filterType: SEVERITY_FILTER,
-            filterKey: "warn",
+            filterKey: MESSAGE_LEVEL.WARN,
             dispatch
           }),
-          FilterToggleButton({
+          FilterButton({
             active: filter.log,
             label: "Logs",
-            filterType: SEVERITY_FILTER,
-            filterKey: "log",
+            filterKey: MESSAGE_LEVEL.LOG,
             dispatch
           }),
-          FilterToggleButton({
+          FilterButton({
             active: filter.info,
             label: "Info",
-            filterType: SEVERITY_FILTER,
-            filterKey: "info",
+            filterKey: MESSAGE_LEVEL.INFO,
             dispatch
           })
         )
       );
     }
 
     if (ui.filteredMessageVisible) {
       children.push(
@@ -111,17 +108,17 @@ const FilterBar = createClass({
           dom.span({
             className: "clear"},
             "You have filters set that may hide some results. " +
             "Learn more about our filtering syntax ",
             dom.a({}, "here"),
             "."),
           dom.button({
             className: "menu-filter-button",
-            onClick: this.onClearFiltersButtonClick
+            onClick: this.onClickFiltersClear
           }, "Remove filters")
         )
       );
     }
 
     return (
       dom.div({className: "webconsole-filteringbar-wrapper"},
         children
new file mode 100644
--- /dev/null
+++ b/devtools/client/webconsole/new-console-output/components/filter-button.js
@@ -0,0 +1,43 @@
+/* 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 {
+  createClass,
+  DOM: dom,
+  PropTypes
+} = require("devtools/client/shared/vendor/react");
+const actions = require("devtools/client/webconsole/new-console-output/actions/filters");
+
+const FilterButton = createClass({
+
+  displayName: "FilterButton",
+
+  propTypes: {
+    label: PropTypes.string.isRequired,
+    filterKey: PropTypes.string.isRequired,
+    active: PropTypes.bool.isRequired,
+    dispatch: PropTypes.func.isRequired,
+  },
+
+  onClick: function () {
+    this.props.dispatch(actions.filterToggle(this.props.filterKey));
+  },
+
+  render() {
+    const {label, active} = this.props;
+
+    let classList = ["menu-filter-button"];
+    if (active) {
+      classList.push("checked");
+    }
+
+    return dom.button({
+      className: classList.join(" "),
+      onClick: this.onClick
+    }, label);
+  }
+});
+
+exports.FilterButton = FilterButton;
deleted file mode 100644
--- a/devtools/client/webconsole/new-console-output/components/filter-toggle-button.js
+++ /dev/null
@@ -1,50 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-"use strict";
-
-const {
-  createClass,
-  DOM: dom,
-  PropTypes
-} = require("devtools/client/shared/vendor/react");
-const actions = require("devtools/client/webconsole/new-console-output/actions/messages");
-const {
-  SEVERITY_FILTER
-} = require("../constants");
-
-const FilterToggleButton = createClass({
-
-  displayName: "FilterToggleButton",
-
-  propTypes: {
-    label: PropTypes.string.isRequired,
-    filterType: PropTypes.string.isRequired,
-    filterKey: PropTypes.string.isRequired,
-    active: PropTypes.bool.isRequired,
-    dispatch: PropTypes.func.isRequired,
-  },
-
-  onClick: function () {
-    if (this.props.filterType === SEVERITY_FILTER) {
-      this.props.dispatch(actions.severityFilter(
-        this.props.filterKey, !this.props.active));
-    }
-  },
-
-  render() {
-    const {label, active} = this.props;
-
-    let classList = ["menu-filter-button"];
-    if (active) {
-      classList.push("checked");
-    }
-
-    return dom.button({
-      className: classList.join(" "),
-      onClick: this.onClick
-    }, label);
-  }
-});
-
-exports.FilterToggleButton = FilterToggleButton;
--- a/devtools/client/webconsole/new-console-output/components/moz.build
+++ b/devtools/client/webconsole/new-console-output/components/moz.build
@@ -5,15 +5,15 @@
 
 DIRS += [
     'message-types'
 ]
 
 DevToolsModules(
     'console-output.js',
     'filter-bar.js',
-    'filter-toggle-button.js',
+    'filter-button.js',
     'grip-message-body.js',
     'message-container.js',
     'message-icon.js',
     'message-repeat.js',
     'variables-view-link.js'
 )
--- a/devtools/client/webconsole/new-console-output/constants.js
+++ b/devtools/client/webconsole/new-console-output/constants.js
@@ -3,20 +3,20 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 "use strict";
 
 const actionTypes = {
   MESSAGE_ADD: "MESSAGE_ADD",
   MESSAGES_CLEAR: "MESSAGES_CLEAR",
-  SEVERITY_FILTER: "SEVERITY_FILTER",
-  MESSAGES_SEARCH: "MESSAGES_SEARCH",
+  FILTER_TOGGLE: "FILTER_TOGGLE",
+  FILTER_TEXT_SET: "FILTER_TEXT_SET",
   FILTERS_CLEAR: "FILTERS_CLEAR",
-  FILTERBAR_TOGGLE: "FILTERBAR_TOGGLE",
+  FILTER_BAR_TOGGLE: "FILTER_BAR_TOGGLE",
 };
 
 const categories = {
   CATEGORY_NETWORK: "network",
   CATEGORY_CSS: "cssparser",
   CATEGORY_JS: "exception",
   CATEGORY_WEBDEV: "console",
   CATEGORY_INPUT: "input",
@@ -92,14 +92,14 @@ const chromeRDPEnums = {
     ERROR: "error",
     WARN: "warn",
     DEBUG: "debug",
     INFO: "info"
   }
 };
 
 const filterTypes = {
-  SEVERITY_FILTER: "SEVERITY_FILTER"
+  FILTER_TOGGLE: "FILTER_TOGGLE"
 };
 
 // Combine into a single constants object
 module.exports = Object.assign({}, actionTypes, categories, severities, levels,
   chromeRDPEnums, filterTypes);
--- a/devtools/client/webconsole/new-console-output/reducers/filters.js
+++ b/devtools/client/webconsole/new-console-output/reducers/filters.js
@@ -8,28 +8,29 @@
 const Immutable = require("devtools/client/shared/vendor/immutable");
 const constants = require("devtools/client/webconsole/new-console-output/constants");
 
 const FilterState = Immutable.Record({
   error: true,
   warn: true,
   info: true,
   log: true,
-  searchText: ""
+  text: ""
 });
 
 function filters(state = new FilterState(), action) {
   switch (action.type) {
-    case constants.SEVERITY_FILTER:
-      let {filter, toggled} = action;
-      return state.set(filter, toggled);
+    case constants.FILTER_TOGGLE:
+      const {filter} = action;
+      const active = !state.get(filter);
+      return state.set(filter, active);
     case constants.FILTERS_CLEAR:
       return new FilterState();
-    case constants.MESSAGES_SEARCH:
-      let {searchText} = action;
-      return state.set("searchText", searchText);
+    case constants.FILTER_TEXT_SET:
+      let {text} = action;
+      return state.set("text", text);
   }
 
   return state;
 }
 
 exports.FilterState = FilterState;
 exports.filters = filters;
--- a/devtools/client/webconsole/new-console-output/reducers/ui.js
+++ b/devtools/client/webconsole/new-console-output/reducers/ui.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";
 
 const constants = require("devtools/client/webconsole/new-console-output/constants");
 const Immutable = require("devtools/client/shared/vendor/immutable");
 
 const Ui = Immutable.Record({
-  configFilterBarVisible: false,
+  filterBarVisible: false,
   filteredMessageVisible: false
 });
 
 function ui(state = new Ui(), action) {
   switch (action.type) {
-    case constants.FILTERBAR_TOGGLE:
-      return state.set("configFilterBarVisible", !state.configFilterBarVisible);
+    case constants.FILTER_BAR_TOGGLE:
+      return state.set("filterBarVisible", !state.filterBarVisible);
   }
 
   return state;
 }
 
 exports.ui = ui;
--- a/devtools/client/webconsole/new-console-output/selectors/messages.js
+++ b/devtools/client/webconsole/new-console-output/selectors/messages.js
@@ -11,40 +11,40 @@ const { getLogLimit } = require("devtool
 function getAllMessages(state) {
   let messages = state.messages;
   let logLimit = getLogLimit(state);
   let filters = getAllFilters(state);
 
   return prune(
     search(
       filterSeverity(messages, filters),
-      filters.searchText
+      filters.text
     ),
     logLimit
   );
 }
 
 function filterSeverity(messages, filters) {
-  return messages.filter((message) => filters[message.severity] === true);
+  return messages.filter((message) => filters[message.level] === true);
 }
 
-function search(messages, searchText = "") {
-  if (searchText === "") {
+function search(messages, text = "") {
+  if (text === "") {
     return messages;
   }
 
   return messages.filter(function (message) {
     // @TODO: message.parameters can be a grip, see how we can handle that
     if (!Array.isArray(message.parameters)) {
       return true;
     }
     return message
       .parameters.join("")
       .toLocaleLowerCase()
-      .includes(searchText.toLocaleLowerCase());
+      .includes(text.toLocaleLowerCase());
   });
 }
 
 function prune(messages, logLimit) {
   let messageCount = messages.count();
   if (messageCount > logLimit) {
     return messages.splice(0, messageCount - logLimit);
   }
--- a/devtools/client/webconsole/new-console-output/store.js
+++ b/devtools/client/webconsole/new-console-output/store.js
@@ -17,17 +17,16 @@ function configureStore(Services) {
     prefs: new PrefState({
       logLimit: Math.max(Services.prefs.getIntPref("devtools.hud.loglimit"), 1),
     }),
     filters: new FilterState({
       error: Services.prefs.getBoolPref("devtools.webconsole.filter.error"),
       warn: Services.prefs.getBoolPref("devtools.webconsole.filter.warn"),
       info: Services.prefs.getBoolPref("devtools.webconsole.filter.info"),
       log: Services.prefs.getBoolPref("devtools.webconsole.filter.log"),
-      searchText: ""
     })
   };
 
   return createStore(combineReducers(reducers), initialState);
 }
 
 // Provide the store factory for test code so that each test is working with
 // its own instance.
new file mode 100644
--- /dev/null
+++ b/devtools/client/webconsole/new-console-output/test/actions/filters.test.js
@@ -0,0 +1,54 @@
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+"use strict";
+
+const actions = require("devtools/client/webconsole/new-console-output/actions/filters");
+const {
+  FILTER_TEXT_SET,
+  FILTER_TOGGLE,
+  FILTERS_CLEAR,
+  MESSAGE_LEVEL
+} = require("devtools/client/webconsole/new-console-output/constants");
+
+const expect = require("expect");
+
+describe("Filter actions:", () => {
+  describe("filterTextSet", () => {
+    it("creates expected action", () => {
+      const action = actions.filterTextSet("test");
+      const expected = {
+        type: FILTER_TEXT_SET,
+        text: "test"
+      };
+
+      expect(action).toEqual(expected);
+    });
+  });
+
+  describe("filterToggle", () => {
+    it("creates expected action", () => {
+      const action = actions.filterToggle(MESSAGE_LEVEL.ERROR);
+      const expected = {
+        type: FILTER_TOGGLE,
+        filter: "error"
+      };
+
+      expect(action).toEqual(expected);
+    });
+  });
+
+  describe("filterTextApply", () => {
+
+  });
+
+  describe("filtersClear", () => {
+    it("creates expected action", () => {
+      const action = actions.filtersClear();
+      const expected = {
+        type: FILTERS_CLEAR
+      };
+
+      expect(action).toEqual(expected);
+    });
+  });
+});
new file mode 100644
--- /dev/null
+++ b/devtools/client/webconsole/new-console-output/test/actions/ui.test.js
@@ -0,0 +1,23 @@
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+"use strict";
+
+const actions = require("devtools/client/webconsole/new-console-output/actions/ui");
+const {
+  FILTER_BAR_TOGGLE
+} = require("devtools/client/webconsole/new-console-output/constants");
+
+const expect = require("expect");
+
+describe("UI actions:", () => {
+  describe("filterBarToggle", () => {
+    it("creates expected action", () => {
+      const action = actions.filterBarToggle();
+      const expected = {
+        type: FILTER_BAR_TOGGLE
+      };
+
+      expect(action).toEqual(expected);
+    });
+  });
+});
--- a/devtools/client/webconsole/new-console-output/test/chrome/test_render_perf.html
+++ b/devtools/client/webconsole/new-console-output/test/chrome/test_render_perf.html
@@ -53,31 +53,31 @@ function timeit(cb) {
     cb();
     let elapsed = performance.now() - start;
     resolve(elapsed / 1000);
   });
 }
 
 window.onload = Task.async(function* () {
   const { configureStore } = browserRequire("devtools/client/webconsole/new-console-output/store");
-  const { messagesSearch, filtersClear } = browserRequire("devtools/client/webconsole/new-console-output/actions/messages");
+  const { filterTextSet, filtersClear } = browserRequire("devtools/client/webconsole/new-console-output/actions/filters");
   const NewConsoleOutputWrapper = browserRequire("devtools/client/webconsole/new-console-output/new-console-output-wrapper");
   const wrapper = new NewConsoleOutputWrapper(document.querySelector("#output"), {});
 
   const store = configureStore();
 
   let time = yield timeit(() => {
     testPackets.forEach((message) => {
       wrapper.dispatchMessageAdd(message);
     });
   });
   info("took " + time + " seconds to render messages");
 
   time = yield timeit(() => {
-    store.dispatch(messagesSearch("Odd text"));
+    store.dispatch(filterTextSet("Odd text"));
   });
   info("took " + time + " seconds to search filter half the messages");
 
   time = yield timeit(() => {
     store.dispatch(filtersClear());
   });
   info("took " + time + " seconds to clear the filter");
 
--- a/devtools/client/webconsole/new-console-output/test/components/console-api-call.test.js
+++ b/devtools/client/webconsole/new-console-output/test/components/console-api-call.test.js
@@ -1,23 +1,21 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 "use strict";
 
 const { stubConsoleMessages } = require("devtools/client/webconsole/new-console-output/test/fixtures/stubs");
 const { ConsoleApiCall } = require("devtools/client/webconsole/new-console-output/components/message-types/console-api-call");
-const jsdom = require("mocha-jsdom");
 const expect = require("expect");
 
 const {
   renderComponent
 } = require("devtools/client/webconsole/new-console-output/test/helpers");
 
 describe("ConsoleAPICall component:", () => {
-  jsdom();
   describe("console.log", () => {
     it("renders string grips", () => {
       const message = stubConsoleMessages.get("console.log('foobar', 'test')");
       const rendered = renderComponent(ConsoleApiCall, {message});
 
       const messageBody = getMessageBody(rendered);
       // @TODO should output: foobar test
       expect(messageBody.textContent).toBe("\"foobar\"\"test\"");
--- a/devtools/client/webconsole/new-console-output/test/components/evaluation-result.test.js
+++ b/devtools/client/webconsole/new-console-output/test/components/evaluation-result.test.js
@@ -1,24 +1,22 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 "use strict";
 
 const { stubConsoleMessages } = require("devtools/client/webconsole/new-console-output/test/fixtures/stubs");
 const { EvaluationResult } = require("devtools/client/webconsole/new-console-output/components/message-types/evaluation-result");
 
-const jsdom = require("mocha-jsdom");
 const expect = require("expect");
 
 const {
   renderComponent
 } = require("devtools/client/webconsole/new-console-output/test/helpers");
 
 describe("EvaluationResult component:", () => {
-  jsdom();
   it("renders a grip result", () => {
     const message = stubConsoleMessages.get("new Date(0)");
     const props = {
       message
     };
     const rendered = renderComponent(EvaluationResult, props);
 
     const messageBody = getMessageBody(rendered);
new file mode 100644
--- /dev/null
+++ b/devtools/client/webconsole/new-console-output/test/components/filter-bar.test.js
@@ -0,0 +1,96 @@
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+"use strict";
+
+// Require helper is necessary to load certain modules.
+require("devtools/client/webconsole/new-console-output/test/requireHelper")();
+const { render, mount } = require("enzyme");
+
+const { createFactory } = require("devtools/client/shared/vendor/react");
+const Provider = createFactory(require("react-redux").Provider);
+
+const FilterButton = createFactory(require("devtools/client/webconsole/new-console-output/components/filter-button").FilterButton);
+const FilterBar = createFactory(require("devtools/client/webconsole/new-console-output/components/filter-bar"));
+const { getAllUi } = require("devtools/client/webconsole/new-console-output/selectors/ui");
+const {
+  MESSAGES_CLEAR,
+  MESSAGE_LEVEL
+} = require("devtools/client/webconsole/new-console-output/constants");
+
+const { setupStore } = require("devtools/client/webconsole/new-console-output/test/helpers");
+
+const expect = require("expect");
+const sinon = require("sinon");
+
+describe("FilterBar component:", () => {
+  it("initial render", () => {
+    const store = setupStore([]);
+
+    const wrapper = render(Provider({store}, FilterBar({})));
+    const toolbar = wrapper.find(
+      ".devtools-toolbar.webconsole-filterbar-primary"
+    );
+
+    // Clear button
+    expect(toolbar.children().eq(0).attr("class"))
+      .toBe("devtools-button devtools-clear-icon");
+    expect(toolbar.children().eq(0).attr("title")).toBe("Clear output");
+
+    // Filter bar toggle
+    expect(toolbar.children().eq(1).attr("class"))
+      .toBe("devtools-button devtools-filter-icon");
+    expect(toolbar.children().eq(1).attr("title")).toBe("Toggle filter bar");
+
+    // Text filter
+    expect(toolbar.children().eq(2).attr("class")).toBe("devtools-plaininput");
+    expect(toolbar.children().eq(2).attr("placeholder")).toBe("Filter output");
+    expect(toolbar.children().eq(2).attr("type")).toBe("search");
+    expect(toolbar.children().eq(2).attr("value")).toBe("");
+  });
+
+  it("displays filter bar when button is clicked", () => {
+    const store = setupStore([]);
+
+    expect(getAllUi(store.getState()).filterBarVisible).toBe(false);
+
+    const wrapper = mount(Provider({store}, FilterBar({})));
+    wrapper.find(".devtools-filter-icon").simulate("click");
+
+    expect(getAllUi(store.getState()).filterBarVisible).toBe(true);
+
+    // Buttons are displayed
+    const buttonProps = {
+      active: true,
+      dispatch: store.dispatch
+    };
+    const logButton = FilterButton(Object.assign({}, buttonProps,
+      { label: "Logs", filterKey: MESSAGE_LEVEL.LOG }));
+    const infoButton = FilterButton(Object.assign({}, buttonProps,
+      { label: "Info", filterKey: MESSAGE_LEVEL.INFO }));
+    const warnButton = FilterButton(Object.assign({}, buttonProps,
+      { label: "Warnings", filterKey: MESSAGE_LEVEL.WARN }));
+    const errorButton = FilterButton(Object.assign({}, buttonProps,
+      { label: "Errors", filterKey: MESSAGE_LEVEL.ERROR }));
+    expect(wrapper.contains([errorButton, warnButton, logButton, infoButton])).toBe(true);
+  });
+
+  it("fires MESSAGES_CLEAR action when clear button is clicked", () => {
+    const store = setupStore([]);
+    store.dispatch = sinon.spy();
+
+    const wrapper = mount(Provider({store}, FilterBar({})));
+    wrapper.find(".devtools-clear-icon").simulate("click");
+    const call = store.dispatch.getCall(0);
+    expect(call.args[0]).toEqual({
+      type: MESSAGES_CLEAR
+    });
+  });
+
+  it("sets filter text when text is typed", () => {
+    const store = setupStore([]);
+
+    const wrapper = mount(Provider({store}, FilterBar({})));
+    wrapper.find(".devtools-plaininput").simulate("input", { target: { value: "a" } });
+    expect(store.getState().filters.text).toBe("a");
+  });
+});
new file mode 100644
--- /dev/null
+++ b/devtools/client/webconsole/new-console-output/test/components/filter-button.test.js
@@ -0,0 +1,52 @@
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+"use strict";
+
+// Require helper is necessary to load certain modules.
+require("devtools/client/webconsole/new-console-output/test/requireHelper")();
+const { render, shallow } = require("enzyme");
+
+const { createFactory } = require("devtools/client/shared/vendor/react");
+
+const FilterButton = createFactory(require("devtools/client/webconsole/new-console-output/components/filter-button").FilterButton);
+const {
+  FILTER_TOGGLE,
+  MESSAGE_LEVEL
+} = require("devtools/client/webconsole/new-console-output/constants");
+
+const expect = require("expect");
+const sinon = require("sinon");
+
+describe("FilterButton component:", () => {
+  const props = {
+    active: true,
+    label: "Error",
+    filterKey: MESSAGE_LEVEL.ERROR,
+    dispatch: sinon.spy()
+  };
+
+  it("displays as active when turned on", () => {
+    const wrapper = render(FilterButton(props));
+    expect(wrapper.html()).toBe(
+      "<button class=\"menu-filter-button checked\">Error</button>"
+    );
+  });
+
+  it("displays as inactive when turned off", () => {
+    const inactiveProps = Object.assign({}, props, { active: false });
+    const wrapper = render(FilterButton(inactiveProps));
+    expect(wrapper.html()).toBe(
+      "<button class=\"menu-filter-button\">Error</button>"
+    );
+  });
+
+  it("fires FILTER_TOGGLE action when clicked", () => {
+    const wrapper = shallow(FilterButton(props));
+    wrapper.find("button").simulate("click");
+    const call = props.dispatch.getCall(0);
+    expect(call.args[0]).toEqual({
+      type: FILTER_TOGGLE,
+      filter: MESSAGE_LEVEL.ERROR
+    });
+  });
+});
--- a/devtools/client/webconsole/new-console-output/test/components/message-container.test.js
+++ b/devtools/client/webconsole/new-console-output/test/components/message-container.test.js
@@ -4,26 +4,24 @@
 
 const { stubConsoleMessages } = require("devtools/client/webconsole/new-console-output/test/fixtures/stubs");
 
 const { MessageContainer } = require("devtools/client/webconsole/new-console-output/components/message-container");
 const { ConsoleApiCall } = require("devtools/client/webconsole/new-console-output/components/message-types/console-api-call");
 const { EvaluationResult } = require("devtools/client/webconsole/new-console-output/components/message-types/evaluation-result");
 const { PageError } = require("devtools/client/webconsole/new-console-output/components/message-types/page-error");
 
-const jsdom = require("mocha-jsdom");
 const expect = require("expect");
 
 const {
   renderComponent,
   shallowRenderComponent
 } = require("devtools/client/webconsole/new-console-output/test/helpers");
 
 describe("MessageContainer component:", () => {
-  jsdom();
   it("pipes data to children as expected", () => {
     const message = stubConsoleMessages.get("console.log('foobar', 'test')");
     const rendered = renderComponent(MessageContainer, {message});
 
     expect(rendered.textContent.includes("foobar")).toBe(true);
   });
   it("picks correct child component", () => {
     const messageTypes = [
--- a/devtools/client/webconsole/new-console-output/test/components/message-icon.test.js
+++ b/devtools/client/webconsole/new-console-output/test/components/message-icon.test.js
@@ -2,25 +2,22 @@
    http://creativecommons.org/publicdomain/zero/1.0/ */
 "use strict";
 
 const {
   SEVERITY_ERROR,
 } = require("devtools/client/webconsole/new-console-output/constants");
 const { MessageIcon } = require("devtools/client/webconsole/new-console-output/components/message-icon");
 
-const jsdom = require("mocha-jsdom");
 const expect = require("expect");
 
 const {
   renderComponent
 } = require("devtools/client/webconsole/new-console-output/test/helpers");
 
 describe("MessageIcon component:", () => {
-  jsdom();
-
   it("renders icon based on severity", () => {
     const rendered = renderComponent(MessageIcon, { severity: SEVERITY_ERROR });
 
     expect(rendered.classList.contains("icon")).toBe(true);
     expect(rendered.getAttribute("title")).toBe("Error");
   });
 });
--- a/devtools/client/webconsole/new-console-output/test/components/page-error.test.js
+++ b/devtools/client/webconsole/new-console-output/test/components/page-error.test.js
@@ -1,25 +1,23 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 "use strict";
 
 const { stubConsoleMessages } = require("devtools/client/webconsole/new-console-output/test/fixtures/stubs");
 
 const { PageError } = require("devtools/client/webconsole/new-console-output/components/message-types/page-error");
 
-const jsdom = require("mocha-jsdom");
 const expect = require("expect");
 
 const {
   renderComponent
 } = require("devtools/client/webconsole/new-console-output/test/helpers");
 
 describe("PageError component:", () => {
-  jsdom();
   it("renders a page error", () => {
     const message = stubConsoleMessages.get("ReferenceError");
     const rendered = renderComponent(PageError, {message});
 
     const messageBody = getMessageBody(rendered);
     expect(messageBody.textContent).toBe("ReferenceError: asdf is not defined");
   });
 });
--- a/devtools/client/webconsole/new-console-output/test/components/repeat.test.js
+++ b/devtools/client/webconsole/new-console-output/test/components/repeat.test.js
@@ -1,24 +1,21 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 "use strict";
 
 const { MessageRepeat } = require("devtools/client/webconsole/new-console-output/components/message-repeat");
 
-const jsdom = require("mocha-jsdom");
 const expect = require("expect");
 
 const {
   renderComponent
 } = require("devtools/client/webconsole/new-console-output/test/helpers");
 
 describe("MessageRepeat component:", () => {
-  jsdom();
-
   it("renders repeated value correctly", () => {
     const rendered = renderComponent(MessageRepeat, { repeat: 99 });
     expect(rendered.classList.contains("message-repeats")).toBe(true);
     expect(rendered.style.visibility).toBe("visible");
     expect(rendered.textContent).toBe("99");
   });
 
   it("renders an un-repeated value correctly", () => {
--- a/devtools/client/webconsole/new-console-output/test/helpers.js
+++ b/devtools/client/webconsole/new-console-output/test/helpers.js
@@ -1,13 +1,15 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 "use strict";
 
+require("devtools/client/webconsole/new-console-output/test/requireHelper")();
+
 let ReactDOM = require("devtools/client/shared/vendor/react-dom");
 let React = require("devtools/client/shared/vendor/react");
 var TestUtils = React.addons.TestUtils;
 
 const actions = require("devtools/client/webconsole/new-console-output/actions/messages");
 const { configureStore } = require("devtools/client/webconsole/new-console-output/store");
 const { IdGenerator } = require("devtools/client/webconsole/new-console-output/utils/id-generator");
 const { stubConsoleMessages } = require("devtools/client/webconsole/new-console-output/test/fixtures/stubs");
new file mode 100644
--- /dev/null
+++ b/devtools/client/webconsole/new-console-output/test/requireHelper.js
@@ -0,0 +1,26 @@
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+"use strict";
+
+const requireHacker = require("require-hacker");
+
+module.exports = () => {
+  try {
+    requireHacker.global_hook("default", path => {
+      switch (path) {
+        case "react-dom/server":
+          return `const React = require('react-dev'); module.exports = React`;
+        case "react-addons-test-utils":
+          return `const React = require('react-dev'); module.exports = React.addons.TestUtils`;
+        case "react":
+          return `const React = require('react-dev'); module.exports = React`;
+        case "devtools/client/shared/vendor/react":
+          return `const React = require('react-dev'); module.exports = React`;
+        case "devtools/client/shared/vendor/react.default":
+          return `const React = require('react-dev'); module.exports = React`;
+      }
+    });
+  } catch (e) {
+    // Do nothing. This means the hook is already registered.
+  }
+};
--- a/devtools/client/webconsole/new-console-output/test/store/filters.test.js
+++ b/devtools/client/webconsole/new-console-output/test/store/filters.test.js
@@ -1,24 +1,102 @@
 /* Any copyright is dedicated to the Public Domain.
  * http://creativecommons.org/publicdomain/zero/1.0/ */
 
 "use strict";
 
 const expect = require("expect");
 
-const actions = require("devtools/client/webconsole/new-console-output/actions/messages");
+const actions = require("devtools/client/webconsole/new-console-output/actions/filters");
+const { messageAdd } = require("devtools/client/webconsole/new-console-output/actions/messages");
+const { ConsoleCommand } = require("devtools/client/webconsole/new-console-output/types");
 const { getAllMessages } = require("devtools/client/webconsole/new-console-output/selectors/messages");
+const { getAllFilters } = require("devtools/client/webconsole/new-console-output/selectors/filters");
 const { setupStore } = require("devtools/client/webconsole/new-console-output/test/helpers");
+const { MESSAGE_LEVEL } = require("devtools/client/webconsole/new-console-output/constants");
+
+describe("Filtering", () => {
+  const numMessages = 5;
+  const store = setupStore([
+    "console.log('foobar', 'test')",
+    "console.warn('danger, will robinson!')",
+    "console.log(undefined)",
+    "ReferenceError"
+  ]);
+  // Add a console command as well
+  store.dispatch(messageAdd(new ConsoleCommand({ messageText: `console.warn("x")` })));
+
+  beforeEach(() => {
+    store.dispatch(actions.filtersClear());
+  });
+
+  describe("Severity filter", () => {
+    it("filters log messages", () => {
+      store.dispatch(actions.filterToggle(MESSAGE_LEVEL.LOG));
+
+      let messages = getAllMessages(store.getState());
+      // @TODO It currently filters console command. This should be -2, not -3.
+      expect(messages.size).toEqual(numMessages - 3);
+    });
 
-describe("Search", () => {
-  it("matches on value grips", () => {
-    const store = setupStore([
-      "console.log('foobar', 'test')",
-      "console.warn('danger, will robinson!')",
-      "console.log(undefined)"
-    ]);
-    store.dispatch(actions.messagesSearch("danger"));
+    // @TODO add info stub
+    it("filters info messages");
+
+    it("filters warning messages", () => {
+      store.dispatch(actions.filterToggle(MESSAGE_LEVEL.WARN));
+
+      let messages = getAllMessages(store.getState());
+      expect(messages.size).toEqual(numMessages - 1);
+    });
+
+    it("filters error messages", () => {
+      store.dispatch(actions.filterToggle(MESSAGE_LEVEL.ERROR));
 
-    let messages = getAllMessages(store.getState());
-    expect(messages.size).toEqual(1);
+      let messages = getAllMessages(store.getState());
+      expect(messages.size).toEqual(numMessages - 1);
+    });
+  });
+
+  describe("Text filter", () => {
+    it("matches on value grips", () => {
+      store.dispatch(actions.filterTextSet("danger"));
+
+      let messages = getAllMessages(store.getState());
+      // @TODO figure out what this should filter
+      // This does not filter out PageErrors or console commands
+      expect(messages.size).toEqual(3);
+    });
+  });
+
+  describe("Combined filters", () => {
+    // @TODO add test
+    it("filters");
   });
 });
+
+describe("Clear filters", () => {
+  it("clears all filters", () => {
+    const store = setupStore([]);
+
+    // Setup test case
+    store.dispatch(actions.filterToggle(MESSAGE_LEVEL.ERROR));
+    store.dispatch(actions.filterTextSet("foobar"));
+    let filters = getAllFilters(store.getState());
+    expect(filters.toJS()).toEqual({
+      "error": false,
+      "info": true,
+      "log": true,
+      "warn": true,
+      "text": "foobar"
+    });
+
+    store.dispatch(actions.filtersClear());
+
+    filters = getAllFilters(store.getState());
+    expect(filters.toJS()).toEqual({
+      "error": true,
+      "info": true,
+      "log": true,
+      "warn": true,
+      "text": ""
+    });
+  });
+});
--- a/devtools/client/webconsole/new-console-output/types.js
+++ b/devtools/client/webconsole/new-console-output/types.js
@@ -2,24 +2,31 @@
 /* vim: set ft=javascript ts=2 et sw=2 tw=80: */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 "use strict";
 
 const Immutable = require("devtools/client/shared/vendor/immutable");
 
+const {
+  MESSAGE_SOURCE,
+  MESSAGE_TYPE,
+  MESSAGE_LEVEL
+} = require("devtools/client/webconsole/new-console-output/constants");
+
 exports.ConsoleCommand = Immutable.Record({
   id: null,
   allowRepeating: false,
   messageText: null,
-  source: null,
-  type: null,
-  category: null,
-  severity: null,
+  source: MESSAGE_SOURCE.JAVASCRIPT,
+  type: MESSAGE_TYPE.COMMAND,
+  level: MESSAGE_LEVEL.LOG,
+  category: "input",
+  severity: MESSAGE_TYPE.LOG,
 });
 
 exports.ConsoleMessage = Immutable.Record({
   id: null,
   allowRepeating: true,
   source: null,
   type: null,
   level: null,
--- a/devtools/client/webconsole/package.json
+++ b/devtools/client/webconsole/package.json
@@ -1,16 +1,19 @@
 {
   "name": "webconsole",
   "version": "0.0.1",
   "devDependencies": {
     "amd-loader": "0.0.5",
     "babel-preset-es2015": "^6.6.0",
     "babel-register": "^6.7.2",
+    "enzyme": "^2.4.1",
     "expect": "^1.16.0",
     "jsdom": "^9.4.1",
+    "jsdom-global": "^2.0.0",
     "mocha": "^2.5.3",
-    "mocha-jsdom": "^1.1.0"
+    "require-hacker": "^2.1.4",
+    "sinon": "^1.17.5"
   },
   "scripts": {
-    "test": "NODE_PATH=`pwd`/../../../ mocha new-console-output/test/**/*.test.js --compilers js:babel-register"
+    "test": "NODE_PATH=`pwd`/../../../:`pwd`/../../../devtools/client/shared/vendor/ mocha new-console-output/test/**/*.test.js --compilers js:babel-register -r jsdom-global/register"
   }
 }
--- a/devtools/shared/gcli/source/lib/gcli/util/util.js
+++ b/devtools/shared/gcli/source/lib/gcli/util/util.js
@@ -562,130 +562,124 @@ exports.createEmptyNodeList = function(d
  * however only Webkit supports them, and there isn't a shim on Monernizr:
  * https://github.com/Modernizr/Modernizr/wiki/HTML5-Cross-browser-Polyfills
  * and when the code that uses this KeyEvent was written, nothing was clear,
  * so instead, we're using this unmodern shim:
  * http://stackoverflow.com/questions/5681146/chrome-10-keyevent-or-something-similar-to-firefoxs-keyevent
  * See BUG 664991: GCLI's keyboard handling should be updated to use DOM-L3
  * https://bugzilla.mozilla.org/show_bug.cgi?id=664991
  */
-if (typeof 'KeyEvent' === 'undefined') {
-  /* jshint -W040 */
-  exports.KeyEvent = this.KeyEvent;
-}
-else {
-  exports.KeyEvent = {
-    DOM_VK_CANCEL: 3,
-    DOM_VK_HELP: 6,
-    DOM_VK_BACK_SPACE: 8,
-    DOM_VK_TAB: 9,
-    DOM_VK_CLEAR: 12,
-    DOM_VK_RETURN: 13,
-    DOM_VK_SHIFT: 16,
-    DOM_VK_CONTROL: 17,
-    DOM_VK_ALT: 18,
-    DOM_VK_PAUSE: 19,
-    DOM_VK_CAPS_LOCK: 20,
-    DOM_VK_ESCAPE: 27,
-    DOM_VK_SPACE: 32,
-    DOM_VK_PAGE_UP: 33,
-    DOM_VK_PAGE_DOWN: 34,
-    DOM_VK_END: 35,
-    DOM_VK_HOME: 36,
-    DOM_VK_LEFT: 37,
-    DOM_VK_UP: 38,
-    DOM_VK_RIGHT: 39,
-    DOM_VK_DOWN: 40,
-    DOM_VK_PRINTSCREEN: 44,
-    DOM_VK_INSERT: 45,
-    DOM_VK_DELETE: 46,
-    DOM_VK_0: 48,
-    DOM_VK_1: 49,
-    DOM_VK_2: 50,
-    DOM_VK_3: 51,
-    DOM_VK_4: 52,
-    DOM_VK_5: 53,
-    DOM_VK_6: 54,
-    DOM_VK_7: 55,
-    DOM_VK_8: 56,
-    DOM_VK_9: 57,
-    DOM_VK_SEMICOLON: 59,
-    DOM_VK_EQUALS: 61,
-    DOM_VK_A: 65,
-    DOM_VK_B: 66,
-    DOM_VK_C: 67,
-    DOM_VK_D: 68,
-    DOM_VK_E: 69,
-    DOM_VK_F: 70,
-    DOM_VK_G: 71,
-    DOM_VK_H: 72,
-    DOM_VK_I: 73,
-    DOM_VK_J: 74,
-    DOM_VK_K: 75,
-    DOM_VK_L: 76,
-    DOM_VK_M: 77,
-    DOM_VK_N: 78,
-    DOM_VK_O: 79,
-    DOM_VK_P: 80,
-    DOM_VK_Q: 81,
-    DOM_VK_R: 82,
-    DOM_VK_S: 83,
-    DOM_VK_T: 84,
-    DOM_VK_U: 85,
-    DOM_VK_V: 86,
-    DOM_VK_W: 87,
-    DOM_VK_X: 88,
-    DOM_VK_Y: 89,
-    DOM_VK_Z: 90,
-    DOM_VK_CONTEXT_MENU: 93,
-    DOM_VK_NUMPAD0: 96,
-    DOM_VK_NUMPAD1: 97,
-    DOM_VK_NUMPAD2: 98,
-    DOM_VK_NUMPAD3: 99,
-    DOM_VK_NUMPAD4: 100,
-    DOM_VK_NUMPAD5: 101,
-    DOM_VK_NUMPAD6: 102,
-    DOM_VK_NUMPAD7: 103,
-    DOM_VK_NUMPAD8: 104,
-    DOM_VK_NUMPAD9: 105,
-    DOM_VK_MULTIPLY: 106,
-    DOM_VK_ADD: 107,
-    DOM_VK_SEPARATOR: 108,
-    DOM_VK_SUBTRACT: 109,
-    DOM_VK_DECIMAL: 110,
-    DOM_VK_DIVIDE: 111,
-    DOM_VK_F1: 112,
-    DOM_VK_F2: 113,
-    DOM_VK_F3: 114,
-    DOM_VK_F4: 115,
-    DOM_VK_F5: 116,
-    DOM_VK_F6: 117,
-    DOM_VK_F7: 118,
-    DOM_VK_F8: 119,
-    DOM_VK_F9: 120,
-    DOM_VK_F10: 121,
-    DOM_VK_F11: 122,
-    DOM_VK_F12: 123,
-    DOM_VK_F13: 124,
-    DOM_VK_F14: 125,
-    DOM_VK_F15: 126,
-    DOM_VK_F16: 127,
-    DOM_VK_F17: 128,
-    DOM_VK_F18: 129,
-    DOM_VK_F19: 130,
-    DOM_VK_F20: 131,
-    DOM_VK_F21: 132,
-    DOM_VK_F22: 133,
-    DOM_VK_F23: 134,
-    DOM_VK_F24: 135,
-    DOM_VK_NUM_LOCK: 144,
-    DOM_VK_SCROLL_LOCK: 145,
-    DOM_VK_COMMA: 188,
-    DOM_VK_PERIOD: 190,
-    DOM_VK_SLASH: 191,
-    DOM_VK_BACK_QUOTE: 192,
-    DOM_VK_OPEN_BRACKET: 219,
-    DOM_VK_BACK_SLASH: 220,
-    DOM_VK_CLOSE_BRACKET: 221,
-    DOM_VK_QUOTE: 222,
-    DOM_VK_META: 224
-  };
-}
+exports.KeyEvent = {
+  DOM_VK_CANCEL: 3,
+  DOM_VK_HELP: 6,
+  DOM_VK_BACK_SPACE: 8,
+  DOM_VK_TAB: 9,
+  DOM_VK_CLEAR: 12,
+  DOM_VK_RETURN: 13,
+  DOM_VK_SHIFT: 16,
+  DOM_VK_CONTROL: 17,
+  DOM_VK_ALT: 18,
+  DOM_VK_PAUSE: 19,
+  DOM_VK_CAPS_LOCK: 20,
+  DOM_VK_ESCAPE: 27,
+  DOM_VK_SPACE: 32,
+  DOM_VK_PAGE_UP: 33,
+  DOM_VK_PAGE_DOWN: 34,
+  DOM_VK_END: 35,
+  DOM_VK_HOME: 36,
+  DOM_VK_LEFT: 37,
+  DOM_VK_UP: 38,
+  DOM_VK_RIGHT: 39,
+  DOM_VK_DOWN: 40,
+  DOM_VK_PRINTSCREEN: 44,
+  DOM_VK_INSERT: 45,
+  DOM_VK_DELETE: 46,
+  DOM_VK_0: 48,
+  DOM_VK_1: 49,
+  DOM_VK_2: 50,
+  DOM_VK_3: 51,
+  DOM_VK_4: 52,
+  DOM_VK_5: 53,
+  DOM_VK_6: 54,
+  DOM_VK_7: 55,
+  DOM_VK_8: 56,
+  DOM_VK_9: 57,
+  DOM_VK_SEMICOLON: 59,
+  DOM_VK_EQUALS: 61,
+  DOM_VK_A: 65,
+  DOM_VK_B: 66,
+  DOM_VK_C: 67,
+  DOM_VK_D: 68,
+  DOM_VK_E: 69,
+  DOM_VK_F: 70,
+  DOM_VK_G: 71,
+  DOM_VK_H: 72,
+  DOM_VK_I: 73,
+  DOM_VK_J: 74,
+  DOM_VK_K: 75,
+  DOM_VK_L: 76,
+  DOM_VK_M: 77,
+  DOM_VK_N: 78,
+  DOM_VK_O: 79,
+  DOM_VK_P: 80,
+  DOM_VK_Q: 81,
+  DOM_VK_R: 82,
+  DOM_VK_S: 83,
+  DOM_VK_T: 84,
+  DOM_VK_U: 85,
+  DOM_VK_V: 86,
+  DOM_VK_W: 87,
+  DOM_VK_X: 88,
+  DOM_VK_Y: 89,
+  DOM_VK_Z: 90,
+  DOM_VK_CONTEXT_MENU: 93,
+  DOM_VK_NUMPAD0: 96,
+  DOM_VK_NUMPAD1: 97,
+  DOM_VK_NUMPAD2: 98,
+  DOM_VK_NUMPAD3: 99,
+  DOM_VK_NUMPAD4: 100,
+  DOM_VK_NUMPAD5: 101,
+  DOM_VK_NUMPAD6: 102,
+  DOM_VK_NUMPAD7: 103,
+  DOM_VK_NUMPAD8: 104,
+  DOM_VK_NUMPAD9: 105,
+  DOM_VK_MULTIPLY: 106,
+  DOM_VK_ADD: 107,
+  DOM_VK_SEPARATOR: 108,
+  DOM_VK_SUBTRACT: 109,
+  DOM_VK_DECIMAL: 110,
+  DOM_VK_DIVIDE: 111,
+  DOM_VK_F1: 112,
+  DOM_VK_F2: 113,
+  DOM_VK_F3: 114,
+  DOM_VK_F4: 115,
+  DOM_VK_F5: 116,
+  DOM_VK_F6: 117,
+  DOM_VK_F7: 118,
+  DOM_VK_F8: 119,
+  DOM_VK_F9: 120,
+  DOM_VK_F10: 121,
+  DOM_VK_F11: 122,
+  DOM_VK_F12: 123,
+  DOM_VK_F13: 124,
+  DOM_VK_F14: 125,
+  DOM_VK_F15: 126,
+  DOM_VK_F16: 127,
+  DOM_VK_F17: 128,
+  DOM_VK_F18: 129,
+  DOM_VK_F19: 130,
+  DOM_VK_F20: 131,
+  DOM_VK_F21: 132,
+  DOM_VK_F22: 133,
+  DOM_VK_F23: 134,
+  DOM_VK_F24: 135,
+  DOM_VK_NUM_LOCK: 144,
+  DOM_VK_SCROLL_LOCK: 145,
+  DOM_VK_COMMA: 188,
+  DOM_VK_PERIOD: 190,
+  DOM_VK_SLASH: 191,
+  DOM_VK_BACK_QUOTE: 192,
+  DOM_VK_OPEN_BRACKET: 219,
+  DOM_VK_BACK_SLASH: 220,
+  DOM_VK_CLOSE_BRACKET: 221,
+  DOM_VK_QUOTE: 222,
+  DOM_VK_META: 224
+};
--- a/dom/animation/AnimValuesStyleRule.cpp
+++ b/dom/animation/AnimValuesStyleRule.cpp
@@ -51,16 +51,24 @@ AnimValuesStyleRule::MapRuleInfoInto(nsR
 }
 
 bool
 AnimValuesStyleRule::MightMapInheritedStyleData()
 {
   return mStyleBits & NS_STYLE_INHERITED_STRUCT_MASK;
 }
 
+bool
+AnimValuesStyleRule::GetDiscretelyAnimatedCSSValue(nsCSSPropertyID aProperty,
+                                                   nsCSSValue* aValue)
+{
+  MOZ_ASSERT(false, "GetDiscretelyAnimatedCSSValue is not implemented yet");
+  return false;
+}
+
 #ifdef DEBUG
 void
 AnimValuesStyleRule::List(FILE* out, int32_t aIndent) const
 {
   nsAutoCString str;
   for (int32_t index = aIndent; --index >= 0; ) {
     str.AppendLiteral("  ");
   }
--- a/dom/animation/AnimValuesStyleRule.h
+++ b/dom/animation/AnimValuesStyleRule.h
@@ -27,16 +27,18 @@ public:
     : mStyleBits(0) {}
 
   // nsISupports implementation
   NS_DECL_ISUPPORTS
 
   // nsIStyleRule implementation
   void MapRuleInfoInto(nsRuleData* aRuleData) override;
   bool MightMapInheritedStyleData() override;
+  bool GetDiscretelyAnimatedCSSValue(nsCSSPropertyID aProperty,
+                                     nsCSSValue* aValue) override;
 #ifdef DEBUG
   void List(FILE* out = stdout, int32_t aIndent = 0) const override;
 #endif
 
   void AddValue(nsCSSPropertyID aProperty, const StyleAnimationValue &aStartValue)
   {
     PropertyStyleAnimationValuePair pair = { aProperty, aStartValue };
     mPropertyValuePairs.AppendElement(pair);
--- a/dom/animation/AnimationUtils.cpp
+++ b/dom/animation/AnimationUtils.cpp
@@ -57,9 +57,24 @@ AnimationUtils::IsOffscreenThrottlingEna
     sPrefCached = true;
     Preferences::AddBoolVarCache(&sOffscreenThrottlingEnabled,
                                  "dom.animations.offscreen-throttling");
   }
 
   return sOffscreenThrottlingEnabled;
 }
 
+/* static */ bool
+AnimationUtils::IsCoreAPIEnabled()
+{
+  static bool sCoreAPIEnabled;
+  static bool sPrefCached = false;
+
+  if (!sPrefCached) {
+    sPrefCached = true;
+    Preferences::AddBoolVarCache(&sCoreAPIEnabled,
+                                 "dom.animations-api.core.enabled");
+  }
+
+  return sCoreAPIEnabled;
+}
+
 } // namespace mozilla
--- a/dom/animation/AnimationUtils.h
+++ b/dom/animation/AnimationUtils.h
@@ -55,13 +55,20 @@ public:
   static nsIDocument*
   GetCurrentRealmDocument(JSContext* aCx);
 
   /**
    * Checks if offscreen animation throttling is enabled.
    */
   static bool
   IsOffscreenThrottlingEnabled();
+
+  /**
+   * Returns true if the preference to enable the core Web Animations API is
+   * true.
+   */
+  static bool
+  IsCoreAPIEnabled();
 };
 
 } // namespace mozilla
 
 #endif
--- a/dom/animation/KeyframeEffectParams.cpp
+++ b/dom/animation/KeyframeEffectParams.cpp
@@ -1,16 +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 "mozilla/KeyframeEffectParams.h"
 
+#include "mozilla/AnimationUtils.h"
 #include "mozilla/KeyframeUtils.h"
 #include "mozilla/RangedPtr.h"
 #include "nsReadableUtils.h"
 
 namespace mozilla {
 
 static inline bool
 IsLetter(char16_t aCh)
@@ -106,16 +107,23 @@ ConsumeIdentToken(RangedPtr<const char16
 KeyframeEffectParams::ParseSpacing(const nsAString& aSpacing,
                                    SpacingMode& aSpacingMode,
                                    nsCSSPropertyID& aPacedProperty,
                                    nsAString& aInvalidPacedProperty,
                                    ErrorResult& aRv)
 {
   aInvalidPacedProperty.Truncate();
 
+  // Ignore spacing if the core API is not enabled since it is not yet ready to
+  // ship.
+  if (!AnimationUtils::IsCoreAPIEnabled()) {
+    aSpacingMode = SpacingMode::distribute;
+    return;
+  }
+
   // Parse spacing.
   // distribute | paced({ident})
   // https://w3c.github.io/web-animations/#dom-keyframeeffectreadonly-spacing
   // 1. distribute spacing.
   if (aSpacing.EqualsLiteral("distribute")) {
     aSpacingMode = SpacingMode::distribute;
     return;
   }
--- a/dom/animation/test/mochitest.ini
+++ b/dom/animation/test/mochitest.ini
@@ -37,16 +37,17 @@ support-files =
   document-timeline/file_document-timeline.html
   mozilla/file_cubic_bezier_limits.html
   mozilla/file_deferred_start.html
   mozilla/file_disabled_properties.html
   mozilla/file_document-timeline-origin-time-range.html
   mozilla/file_hide_and_show.html
   mozilla/file_partial_keyframes.html
   mozilla/file_transform_limits.html
+  mozilla/file_underlying-discrete-value.html
   style/file_animation-seeking-with-current-time.html
   style/file_animation-seeking-with-start-time.html
   testcommon.js
 
 [css-animations/test_animations-dynamic-changes.html]
 [css-animations/test_animation-cancel.html]
 [css-animations/test_animation-computed-timing.html]
 [css-animations/test_animation-currenttime.html]
@@ -86,10 +87,11 @@ skip-if = buildapp == 'mulet'
 [mozilla/test_deferred_start.html]
 skip-if = (toolkit == 'gonk' && debug)
 [mozilla/test_disabled_properties.html]
 [mozilla/test_document-timeline-origin-time-range.html]
 [mozilla/test_hide_and_show.html]
 [mozilla/test_partial_keyframes.html]
 [mozilla/test_set-easing.html]
 [mozilla/test_transform_limits.html]
+[mozilla/test_underlying-discrete-value.html]
 [style/test_animation-seeking-with-current-time.html]
 [style/test_animation-seeking-with-start-time.html]
new file mode 100644
--- /dev/null
+++ b/dom/animation/test/mozilla/file_underlying-discrete-value.html
@@ -0,0 +1,192 @@
+<!doctype html>
+<meta charset=utf-8>
+<script src="../testcommon.js"></script>
+<body>
+<script>
+"use strict";
+
+// Tests that we correctly extract the underlying value when the animation
+// type is 'discrete'.
+const discreteTests = [
+  {
+    stylesheet: {
+      "@keyframes keyframes":
+      "from { align-content: flex-start; } to { align-content: flex-end; } "
+    },
+    expectedKeyframes: [
+      { computedOffset: 0, alignContent: "flex-start" },
+      { computedOffset: 1, alignContent: "flex-end" }
+    ],
+    explanation: "Test for fully-specified keyframes"
+  },
+  {
+    stylesheet: {
+      "@keyframes keyframes": "from { align-content: flex-start; }"
+    },
+    // The value of 100% should be 'stretch',
+    // but we are not supporting underlying value.
+    // https://bugzilla.mozilla.org/show_bug.cgi?id=1295401
+    expectedKeyframes: [
+      { computedOffset: 0, alignContent: "flex-start" },
+      { computedOffset: 1, alignContent: "unset" }
+    ],
+    explanation: "Test for 0% keyframe only"
+  },
+  {
+    stylesheet: {
+      "@keyframes keyframes": "to { align-content: flex-end; }"
+    },
+    // The value of 0% should be 'stretch',
+    // but we are not supporting underlying value.
+    // https://bugzilla.mozilla.org/show_bug.cgi?id=1295401
+    expectedKeyframes: [
+      { computedOffset: 0, alignContent: "unset" },
+      { computedOffset: 1, alignContent: "flex-end" }
+    ],
+    explanation: "Test for 100% keyframe only"
+  },
+  {
+    stylesheet: {
+      "@keyframes keyframes": "50% { align-content: center; }",
+      "#target": "align-content: space-between;"
+    },
+    expectedKeyframes: [
+      { computedOffset: 0, alignContent: "space-between" },
+      { computedOffset: 0.5, alignContent: "center" },
+      { computedOffset: 1, alignContent: "space-between" }
+    ],
+    explanation: "Test for no 0%/100% keyframes " +
+                 "and specified style on target element"
+  },
+  {
+    stylesheet: {
+      "@keyframes keyframes": "50% { align-content: center; }"
+    },
+    attributes: {
+      style: "align-content: space-between"
+    },
+    expectedKeyframes: [
+      { computedOffset: 0, alignContent: "space-between" },
+      { computedOffset: 0.5, alignContent: "center" },
+      { computedOffset: 1, alignContent: "space-between" }
+    ],
+    explanation: "Test for no 0%/100% keyframes " +
+                 "and specified style on target element using style attribute"
+  },
+  {
+    stylesheet: {
+      "@keyframes keyframes": "50% { align-content: center; }",
+      "#target": "align-content: inherit;"
+    },
+    // The value of 0%/100% should be 'stretch',
+    // but we are not supporting underlying value.
+    // https://bugzilla.mozilla.org/show_bug.cgi?id=1295401
+    expectedKeyframes: [
+      { computedOffset: 0, alignContent: "inherit" },
+      { computedOffset: 0.5, alignContent: "center" },
+      { computedOffset: 1, alignContent: "inherit" }
+    ],
+    explanation: "Test for no 0%/100% keyframes " +
+                 "and 'inherit' specified on target element"
+  },
+  {
+    stylesheet: {
+      "@keyframes keyframes": "50% { align-content: center; }",
+      ".target": "align-content: space-between;"
+    },
+    attributes: {
+      class: "target"
+    },
+    expectedKeyframes: [
+      { computedOffset: 0, alignContent: "space-between" },
+      { computedOffset: 0.5, alignContent: "center" },
+      { computedOffset: 1, alignContent: "space-between" }
+    ],
+    explanation: "Test for no 0%/100% keyframes " +
+                 "and specified style on target element using class selector"
+  },
+  {
+    stylesheet: {
+      "@keyframes keyframes": "50% { align-content: center; }",
+      "div": "align-content: space-between;"
+    },
+    expectedKeyframes: [
+      { computedOffset: 0, alignContent: "space-between" },
+      { computedOffset: 0.5, alignContent: "center" },
+      { computedOffset: 1, alignContent: "space-between" }
+    ],
+    explanation: "Test for no 0%/100% keyframes " +
+                 "and specified style on target element using type selector"
+  },
+  {
+    stylesheet: {
+      "@keyframes keyframes": "50% { align-content: center; }",
+      "div": "align-content: space-between;",
+      ".target": "align-content: flex-start;",
+      "#target": "align-content: flex-end;"
+    },
+    attributes: {
+      class: "target"
+    },
+    expectedKeyframes: [
+      { computedOffset: 0, alignContent: "flex-end" },
+      { computedOffset: 0.5, alignContent: "center" },
+      { computedOffset: 1, alignContent: "flex-end" }
+    ],
+    explanation: "Test for no 0%/100% keyframes " +
+                 "and specified style on target element " +
+                 "using ID selector that overrides class selector"
+  },
+  {
+    stylesheet: {
+      "@keyframes keyframes": "50% { align-content: center; }",
+      "div": "align-content: space-between !important;",
+      ".target": "align-content: flex-start;",
+      "#target": "align-content: flex-end;"
+    },
+    attributes: {
+      class: "target"
+    },
+    expectedKeyframes: [
+      { computedOffset: 0, alignContent: "space-between" },
+      { computedOffset: 0.5, alignContent: "center" },
+      { computedOffset: 1, alignContent: "space-between" }
+    ],
+    explanation: "Test for no 0%/100% keyframes " +
+                 "and specified style on target element " +
+                 "using important type selector that overrides other rules"
+  },
+];
+
+discreteTests.forEach(testcase => {
+  test(t => {
+    addStyle(t, testcase.stylesheet);
+
+    const div = addDiv(t, { "id": "target" });
+    if (testcase.attributes) {
+      for (let attributeName in testcase.attributes) {
+        div.setAttribute(attributeName, testcase.attributes[attributeName]);
+      }
+    }
+    div.style.animation = "keyframes 100s";
+
+    const keyframes = div.getAnimations()[0].effect.getKeyframes();
+    const expectedKeyframes = testcase.expectedKeyframes;
+    assert_equals(keyframes.length, expectedKeyframes.length,
+                  `keyframes.length should be ${ expectedKeyframes.length }`);
+
+    keyframes.forEach((keyframe, index) => {
+      const expectedKeyframe = expectedKeyframes[index];
+      assert_equals(keyframe.computedOffset, expectedKeyframe.computedOffset,
+                    `computedOffset of keyframes[${ index }] should be ` +
+                    `${ expectedKeyframe.computedOffset }`);
+      assert_equals(keyframe.alignContent, expectedKeyframe.alignContent,
+                    `alignContent of keyframes[${ index }] should be ` +
+                    `${ expectedKeyframe.alignContent }`);
+    });
+  }, testcase.explanation);
+});
+
+done();
+</script>
+</body>
new file mode 100644
--- /dev/null
+++ b/dom/animation/test/mozilla/test_underlying-discrete-value.html
@@ -0,0 +1,15 @@
+<!doctype html>
+<meta charset=utf-8>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<div id="log"></div>
+<script>
+'use strict';
+setup({explicit_done: true});
+SpecialPowers.pushPrefEnv(
+  { "set": [["dom.animations-api.core.enabled", true]]},
+  function() {
+    window.open("file_underlying-discrete-value.html");
+  });
+</script>
+</html>
--- a/dom/base/nsMappedAttributes.cpp
+++ b/dom/base/nsMappedAttributes.cpp
@@ -187,16 +187,24 @@ nsMappedAttributes::MapRuleInfoInto(nsRu
 /* virtual */ bool
 nsMappedAttributes::MightMapInheritedStyleData()
 {
   // Just assume that we do, rather than adding checks to all of the different
   // kinds of attribute mapping functions we have.
   return true;
 }
 
+/* virtual */ bool
+nsMappedAttributes::GetDiscretelyAnimatedCSSValue(nsCSSPropertyID aProperty,
+                                                  nsCSSValue* aValue)
+{
+  MOZ_ASSERT(false, "GetDiscretelyAnimatedCSSValue is not implemented yet");
+  return false;
+}
+
 #ifdef DEBUG
 /* virtual */ void
 nsMappedAttributes::List(FILE* out, int32_t aIndent) const
 {
   nsAutoCString str;
   nsAutoString tmp;
   uint32_t i;
 
--- a/dom/base/nsMappedAttributes.h
+++ b/dom/base/nsMappedAttributes.h
@@ -70,16 +70,18 @@ public:
   void RemoveAttrAt(uint32_t aPos, nsAttrValue& aValue);
   const nsAttrName* GetExistingAttrNameFromQName(const nsAString& aName) const;
   int32_t IndexOfAttr(nsIAtom* aLocalName) const;
   
 
   // nsIStyleRule 
   virtual void MapRuleInfoInto(nsRuleData* aRuleData) override;
   virtual bool MightMapInheritedStyleData() override;
+  virtual bool GetDiscretelyAnimatedCSSValue(nsCSSPropertyID aProperty,
+                                             nsCSSValue* aValue) override;
 #ifdef DEBUG
   virtual void List(FILE* out = stdout, int32_t aIndent = 0) const override;
 #endif
 
   size_t SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) const;
 
 private:
   nsMappedAttributes(const nsMappedAttributes& aCopy);
--- a/dom/camera/test/test_camera_fake_parameters.html
+++ b/dom/camera/test/test_camera_fake_parameters.html
@@ -464,36 +464,32 @@ suite.test('bug-1054803', function() {
       cap.pictureSizes.forEach(function(capSize) {
         if (capSize.height == size.height && capSize.width == size.width) {
           ++found;
         }
       });
       ok(found == 1, "found size " + size.toSource() + " in pictureSizes");
     });
 
-    var sizeGenerator = Iterator(expSizes);
+    var sizeIterator = expSizes.values();
     return new Promise(function(resolve, reject) {
       function nextSize() {
-        try {
-          var size = sizeGenerator.next()[1];
-          var sync = suite.waitParameterPush();
-          cam.setPictureSize(size);
-          sync.then(function() {
-            var got = cam.getPictureSize();
-            ok(got.width == size.width && got.height == size.height,
-              "Set size " + size.toSource() + ", got size " + got.toSource());
-            nextSize();
-          }, reject);
-        } catch(e) {
-          if (e instanceof StopIteration) {
-            resolve();
-          } else {
-            reject(e);
-          }
+        var {value:size, done} = sizeIterator.next();
+        if (done) {
+          resolve();
+          return;
         }
+        var sync = suite.waitParameterPush();
+        cam.setPictureSize(size);
+        sync.then(function() {
+          var got = cam.getPictureSize();
+          ok(got.width == size.width && got.height == size.height,
+            "Set size " + size.toSource() + ", got size " + got.toSource());
+          nextSize();
+        }, reject);
       }
 
       nextSize();
     });
   }
 
   return suite.getCamera()
     .then(verify, suite.rejectGetCamera);
--- a/dom/html/HTMLBodyElement.cpp
+++ b/dom/html/HTMLBodyElement.cpp
@@ -169,16 +169,24 @@ BodyRule::MapRuleInfoInto(nsRuleData* aD
 }
 
 /* virtual */ bool
 BodyRule::MightMapInheritedStyleData()
 {
   return false;
 }
 
+/* virtual */ bool
+BodyRule::GetDiscretelyAnimatedCSSValue(nsCSSPropertyID aProperty,
+                                        nsCSSValue* aValue)
+{
+  MOZ_ASSERT(false, "GetDiscretelyAnimatedCSSValue is not implemented yet");
+  return false;
+}
+
 #ifdef DEBUG
 /* virtual */ void
 BodyRule::List(FILE* out, int32_t aIndent) const
 {
   nsAutoCString indent;
   for (int32_t index = aIndent; --index >= 0; ) {
     indent.AppendLiteral("  ");
   }
--- a/dom/html/HTMLBodyElement.h
+++ b/dom/html/HTMLBodyElement.h
@@ -24,16 +24,18 @@ class BodyRule: public nsIStyleRule
 public:
   explicit BodyRule(HTMLBodyElement* aPart);
 
   NS_DECL_ISUPPORTS
 
   // nsIStyleRule interface
   virtual void MapRuleInfoInto(nsRuleData* aRuleData) override;
   virtual bool MightMapInheritedStyleData() override;
+  virtual bool GetDiscretelyAnimatedCSSValue(nsCSSPropertyID aProperty,
+                                             nsCSSValue* aValue) override;
 #ifdef DEBUG
   virtual void List(FILE* out = stdout, int32_t aIndent = 0) const override;
 #endif
 
   HTMLBodyElement*  mPart;  // not ref-counted, cleared by content 
 };
 
 class HTMLBodyElement final : public nsGenericHTMLElement,
--- a/dom/html/HTMLMediaElement.cpp
+++ b/dom/html/HTMLMediaElement.cpp
@@ -2613,30 +2613,16 @@ void HTMLMediaElement::SetPlayedOrSeeked
 
 void
 HTMLMediaElement::NotifyXPCOMShutdown()
 {
   ShutdownDecoder();
 }
 
 void
-HTMLMediaElement::ResetConnectionState()
-{
-  SetCurrentTime(0);
-  FireTimeUpdate(false);
-  DispatchAsyncEvent(NS_LITERAL_STRING("ended"));
-  ChangeNetworkState(nsIDOMHTMLMediaElement::NETWORK_EMPTY);
-  ChangeDelayLoadStatus(false);
-  ChangeReadyState(nsIDOMHTMLMediaElement::HAVE_NOTHING);
-  if (mDecoder) {
-    ShutdownDecoder();
-  }
-}
-
-void
 HTMLMediaElement::Play(ErrorResult& aRv)
 {
   nsresult rv = PlayInternal(nsContentUtils::IsCallerChrome());
   if (NS_FAILED(rv)) {
     aRv.Throw(rv);
   }
 }
 
@@ -3373,29 +3359,17 @@ nsresult HTMLMediaElement::InitializeDec
   if (!resource)
     return NS_ERROR_OUT_OF_MEMORY;
 
   if (mChannelLoader) {
     mChannelLoader->Done();
     mChannelLoader = nullptr;
   }
 
-  // We postpone the |FinishDecoderSetup| function call until we get
-  // |OnConnected| signal from MediaStreamController which is held by
-  // RtspMediaResource.
-  if (DecoderTraits::DecoderWaitsForOnConnected(mimeType)) {
-    decoder->SetResource(resource);
-    SetDecoder(decoder);
-    if (aListener) {
-      *aListener = nullptr;
-    }
-    return NS_OK;
-  } else {
-    return FinishDecoderSetup(decoder, resource, aListener);
-  }
+  return FinishDecoderSetup(decoder, resource, aListener);
 }
 
 nsresult HTMLMediaElement::FinishDecoderSetup(MediaDecoder* aDecoder,
                                               MediaResource* aStream,
                                               nsIStreamListener** aListener)
 {
   ChangeNetworkState(nsIDOMHTMLMediaElement::NETWORK_LOADING);
 
--- a/dom/html/HTMLMediaElement.h
+++ b/dom/html/HTMLMediaElement.h
@@ -434,20 +434,16 @@ public:
     SetOrRemoveNullableStringAttr(nsGkAtoms::crossorigin, aCrossOrigin, aError);
   }
 
   uint16_t NetworkState() const
   {
     return mNetworkState;
   }
 
-  // Called by the media decoder object, on the main thread,
-  // when the connection between Rtsp server and client gets lost.
-  virtual void ResetConnectionState() final override;
-
   void NotifyXPCOMShutdown() final override;
 
   // Called by media decoder when the audible state changed or when input is
   // a media stream.
   virtual void SetAudibleState(bool aAudible) final override;
 
   // Notify agent when the MediaElement changes its audible state.
   void NotifyAudioPlaybackChanged(AudibleChangedReasons aReason);
@@ -714,23 +710,16 @@ public:
   bool GetHasUserInteraction()
   {
     return mHasUserInteraction;
   }
 
   // A method to check whether we are currently playing.
   bool IsCurrentlyPlaying() const;
 
-  /**
-   * A public wrapper for FinishDecoderSetup()
-   */
-  nsresult FinishDecoderSetup(MediaDecoder* aDecoder, MediaResource* aStream) {
-    return FinishDecoderSetup(aDecoder, aStream, nullptr);
-  }
-
   // Returns true if the media element is being destroyed. Used in
   // dormancy checks to prevent dormant processing for an element
   // that will soon be gone.
   bool IsBeingDestroyed();
 
   IMPL_EVENT_HANDLER(mozinterruptbegin)
   IMPL_EVENT_HANDLER(mozinterruptend)
 
--- a/dom/html/TextTrackManager.cpp
+++ b/dom/html/TextTrackManager.cpp
@@ -556,17 +556,17 @@ TextTrackManager::DispatchTimeMarchesOn(
 void
 TextTrackManager::TimeMarchesOn()
 {
   NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
 
   mTimeMarchesOnDispatched = false;
 
   // Early return if we don't have any TextTracks.
-  if (mTextTracks->Length() == 0) {
+  if (!mTextTracks || mTextTracks->Length() == 0) {
     return;
   }
 
   nsISupports* parentObject =
     mMediaElement->OwnerDoc()->GetParentObject();
   if (NS_WARN_IF(!parentObject)) {
     return;
   }
--- a/dom/ipc/ContentChild.cpp
+++ b/dom/ipc/ContentChild.cpp
@@ -917,21 +917,23 @@ ContentChild::InitXPCOM()
   if (NS_FAILED(svc->RegisterListener(mConsoleListener)))
     NS_WARNING("Couldn't register console listener for child process");
 
   bool isOffline, isLangRTL, haveBidiKeyboards;
   bool isConnected;
   ClipboardCapabilities clipboardCaps;
   DomainPolicyClone domainPolicy;
   StructuredCloneData initialData;
+  OptionalURIParams userContentSheetURL;
 
   SendGetXPCOMProcessAttributes(&isOffline, &isConnected,
                                 &isLangRTL, &haveBidiKeyboards,
                                 &mAvailableDictionaries,
-                                &clipboardCaps, &domainPolicy, &initialData);
+                                &clipboardCaps, &domainPolicy, &initialData,
+                                &userContentSheetURL);
   RecvSetOffline(isOffline);
   RecvSetConnectivity(isConnected);
   RecvBidiKeyboardNotify(isLangRTL, haveBidiKeyboards);
 
   // Create the CPOW manager as soon as possible.
   SendPJavaScriptConstructor();
 
   if (domainPolicy.active()) {
@@ -959,16 +961,20 @@ ContentChild::InitXPCOM()
     initialData.Read(jsapi.cx(), &data, rv);
     if (NS_WARN_IF(rv.Failed())) {
       MOZ_CRASH();
     }
     ProcessGlobal* global = ProcessGlobal::Get();
     global->SetInitialProcessData(data);
   }
 
+  // The stylesheet cache is not ready yet. Store this URL for future use.
+  nsCOMPtr<nsIURI> ucsURL = DeserializeURI(userContentSheetURL);
+  nsLayoutStylesheetCache::SetUserContentCSSURL(ucsURL);
+
   // This will register cross-process observer.
   mozilla::dom::time::InitializeDateCacheCleaner();
 }
 
 PMemoryReportRequestChild*
 ContentChild::AllocPMemoryReportRequestChild(const uint32_t& aGeneration,
                                              const bool &aAnonymize,
                                              const bool &aMinimizeMemoryUsage,
--- a/dom/ipc/ContentParent.cpp
+++ b/dom/ipc/ContentParent.cpp
@@ -190,16 +190,18 @@
 #include "nsPluginTags.h"
 #include "nsIBlocklistService.h"
 #include "mozilla/StyleSheetHandle.h"
 #include "mozilla/StyleSheetHandleInlines.h"
 #include "nsHostObjectProtocolHandler.h"
 
 #include "nsIBidiKeyboard.h"
 
+#include "nsLayoutStylesheetCache.h"
+
 #ifdef MOZ_WEBRTC
 #include "signaling/src/peerconnection/WebrtcGlobalParent.h"
 #endif
 
 #if defined(ANDROID) || defined(LINUX)
 #include "nsSystemInfo.h"
 #endif
 
@@ -2970,17 +2972,18 @@ ContentParent::RecvGetProcessAttributes(
 bool
 ContentParent::RecvGetXPCOMProcessAttributes(bool* aIsOffline,
                                              bool* aIsConnected,
                                              bool* aIsLangRTL,
                                              bool* aHaveBidiKeyboards,
                                              InfallibleTArray<nsString>* dictionaries,
                                              ClipboardCapabilities* clipboardCaps,
                                              DomainPolicyClone* domainPolicy,
-                                             StructuredCloneData* aInitialData)
+                                             StructuredCloneData* aInitialData,
+                                             OptionalURIParams* aUserContentCSSURL)
 {
   nsCOMPtr<nsIIOService> io(do_GetIOService());
   MOZ_ASSERT(io, "No IO service?");
   DebugOnly<nsresult> rv = io->GetOffline(aIsOffline);
   MOZ_ASSERT(NS_SUCCEEDED(rv), "Failed getting offline?");
 
   rv = io->GetConnectivity(aIsConnected);
   MOZ_ASSERT(NS_SUCCEEDED(rv), "Failed getting connectivity?");
@@ -3027,16 +3030,26 @@ ContentParent::RecvGetXPCOMProcessAttrib
     ErrorResult rv;
     aInitialData->Write(jsapi.cx(), init, rv);
     if (NS_WARN_IF(rv.Failed())) {
       rv.SuppressException();
       return false;
     }
   }
 
+  // XXX bug 1046166
+  // Content processes have no permission to read profile, so we send the
+  // file URL instead.
+  StyleSheetHandle::RefPtr ucs = nsLayoutStylesheetCache::For(StyleBackendType::Gecko)->UserContentSheet();
+  if (ucs) {
+    SerializeURI(ucs->GetSheetURI(), *aUserContentCSSURL);
+  } else {
+    SerializeURI(nullptr, *aUserContentCSSURL);
+  }
+
   return true;
 }
 
 mozilla::jsipc::PJavaScriptParent *
 ContentParent::AllocPJavaScriptParent()
 {
   MOZ_ASSERT(ManagedPJavaScriptParent().IsEmpty());
   return nsIContentParent::AllocPJavaScriptParent();
--- a/dom/ipc/ContentParent.h
+++ b/dom/ipc/ContentParent.h
@@ -727,17 +727,18 @@ private:
   virtual bool
   RecvGetXPCOMProcessAttributes(bool* aIsOffline,
                                 bool* aIsConnected,
                                 bool* aIsLangRTL,
                                 bool* aHaveBidiKeyboards,
                                 InfallibleTArray<nsString>* dictionaries,
                                 ClipboardCapabilities* clipboardCaps,
                                 DomainPolicyClone* domainPolicy,
-                                StructuredCloneData* initialData) override;
+                                StructuredCloneData* initialData,
+                                OptionalURIParams* aUserContentSheetURL) override;
 
   virtual bool
   DeallocPJavaScriptParent(mozilla::jsipc::PJavaScriptParent*) override;
 
   virtual bool
   DeallocPRemoteSpellcheckEngineParent(PRemoteSpellcheckEngineParent*) override;
 
   virtual PBrowserParent* AllocPBrowserParent(const TabId& aTabId,
--- a/dom/ipc/PContent.ipdl
+++ b/dom/ipc/PContent.ipdl
@@ -717,17 +717,18 @@ parent:
      */
     sync GetProcessAttributes()
         returns (ContentParentId cpId, bool isForApp, bool isForBrowser);
     sync GetXPCOMProcessAttributes()
         returns (bool isOffline, bool isConnected, bool isLangRTL,
                  bool haveBidiKeyboards, nsString[] dictionaries,
                  ClipboardCapabilities clipboardCaps,
                  DomainPolicyClone domainPolicy,
-                 StructuredCloneData initialData);
+                 StructuredCloneData initialData,
+                 OptionalURIParams userContentSheetURL);
 
     sync CreateChildProcess(IPCTabContext context,
                             ProcessPriority priority,
                             TabId openerTabId)
         returns (ContentParentId cpId, bool isForApp, bool isForBrowser, TabId tabId);
     sync BridgeToChildProcess(ContentParentId cpId);
 
     async CreateGMPService();
--- a/dom/media/DecoderTraits.cpp
+++ b/dom/media/DecoderTraits.cpp
@@ -28,20 +28,16 @@
 #include "AndroidMediaPluginHost.h"
 #endif
 #ifdef MOZ_OMX_DECODER
 #include "MediaOmxDecoder.h"
 #include "MediaOmxReader.h"
 #include "nsIPrincipal.h"
 #include "mozilla/dom/HTMLMediaElement.h"
 #endif
-#ifdef NECKO_PROTOCOL_rtsp
-#include "RtspOmxDecoder.h"
-#include "RtspOmxReader.h"
-#endif
 #ifdef MOZ_DIRECTSHOW
 #include "DirectShowDecoder.h"
 #include "DirectShowReader.h"
 #endif
 #ifdef MOZ_FMP4
 #include "MP4Decoder.h"
 #include "MP4Demuxer.h"
 #endif
@@ -268,39 +264,16 @@ static char const *const gOMXWebMCodecs[
   "vp9.0",
 #endif
   nullptr
 };
 #endif //MOZ_OMX_WEBM_DECODER
 
 #endif
 
-#ifdef NECKO_PROTOCOL_rtsp
-static const char* const gRtspTypes[2] = {
-    "RTSP",
-    nullptr
-};
-
-static bool
-IsRtspSupportedType(const nsACString& aMimeType)
-{
-  return MediaDecoder::IsRtspEnabled() &&
-    CodecListContains(gRtspTypes, aMimeType);
-}
-#endif
-
-/* static */
-bool DecoderTraits::DecoderWaitsForOnConnected(const nsACString& aMimeType) {
-#ifdef NECKO_PROTOCOL_rtsp
-  return CodecListContains(gRtspTypes, aMimeType);
-#else
-  return false;
-#endif
-}
-
 #ifdef MOZ_ANDROID_OMX
 static bool
 IsAndroidMediaType(const nsACString& aType)
 {
   if (!MediaDecoder::IsAndroidMediaPluginEnabled()) {
     return false;
   }
 
@@ -543,21 +516,16 @@ DecoderTraits::CanHandleMediaType(const 
   }
 #endif
 #ifdef MOZ_ANDROID_OMX
   if (MediaDecoder::IsAndroidMediaPluginEnabled() &&
       EnsureAndroidMediaPluginHost()->FindDecoder(nsDependentCString(aMIMEType), nullptr)) {
     return CANPLAY_MAYBE;
   }
 #endif
-#ifdef NECKO_PROTOCOL_rtsp
-  if (IsRtspSupportedType(nsDependentCString(aMIMEType))) {
-    return CANPLAY_MAYBE;
-  }
-#endif
   return CANPLAY_NO;
 }
 
 // Instantiates but does not initialize decoder.
 static
 already_AddRefed<MediaDecoder>
 InstantiateDecoder(const nsACString& aType,
                    MediaDecoderOwner* aOwner,
@@ -610,22 +578,16 @@ InstantiateDecoder(const nsACString& aTy
       if (principal->GetAppStatus() < nsIPrincipal::APP_STATUS_PRIVILEGED) {
         return nullptr;
       }
     }
     decoder = new MediaOmxDecoder(aOwner);
     return decoder.forget();
   }
 #endif
-#ifdef NECKO_PROTOCOL_rtsp
-  if (IsRtspSupportedType(aType)) {
-    decoder = new RtspOmxDecoder(aOwner);
-    return decoder.forget();
-  }
-#endif
 #ifdef MOZ_ANDROID_OMX
   if (MediaDecoder::IsAndroidMediaPluginEnabled() &&
       EnsureAndroidMediaPluginHost()->FindDecoder(aType, nullptr)) {
     decoder = new AndroidMediaDecoder(aOwner, aType);
     return decoder.forget();
   }
 #endif
 
@@ -749,15 +711,12 @@ bool DecoderTraits::IsSupportedInVideoDo
 #ifdef MOZ_FMP4
     IsMP4SupportedType(aType, /* DecoderDoctorDiagnostics* */ nullptr) ||
 #endif
     IsMP3SupportedType(aType) ||
     IsAACSupportedType(aType) ||
 #ifdef MOZ_DIRECTSHOW
     IsDirectShowSupportedType(aType) ||
 #endif
-#ifdef NECKO_PROTOCOL_rtsp
-    IsRtspSupportedType(aType) ||
-#endif
     false;
 }
 
 } // namespace mozilla
--- a/dom/media/DecoderTraits.h
+++ b/dom/media/DecoderTraits.h
@@ -66,20 +66,16 @@ public:
   static MediaDecoderReader* CreateReader(const nsACString& 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);
 
-  // Returns true if we should not start decoder until we receive
-  // OnConnected signal. (currently RTSP only)
-  static bool DecoderWaitsForOnConnected(const nsACString& aType);
-
   static bool IsWebMTypeAndEnabled(const nsACString& aType);
   static bool IsWebMAudioType(const nsACString& aType);
   static bool IsMP4TypeAndEnabled(const nsACString& aType,
                                   DecoderDoctorDiagnostics* aDiagnostics);
 };
 
 } // namespace mozilla
 
--- a/dom/media/MediaDecoder.cpp
+++ b/dom/media/MediaDecoder.cpp
@@ -179,32 +179,16 @@ MediaDecoder::ResourceCallback::SetMedia
 {
   MOZ_ASSERT(NS_IsMainThread());
   if (mDecoder) {
     mDecoder->SetMediaSeekable(aMediaSeekable);
   }
 }
 
 void
-MediaDecoder::ResourceCallback::ResetConnectionState()
-{
-  MOZ_ASSERT(NS_IsMainThread());
-  if (mDecoder) {
-    mDecoder->ResetConnectionState();
-  }
-}
-
-nsresult
-MediaDecoder::ResourceCallback::FinishDecoderSetup(MediaResource* aResource)
-{
-  MOZ_ASSERT(NS_IsMainThread());
-  return mDecoder ? mDecoder->FinishDecoderSetup(aResource) : NS_ERROR_FAILURE;
-}
-
-void
 MediaDecoder::ResourceCallback::NotifyNetworkError()
 {
   MOZ_ASSERT(NS_IsMainThread());
   if (mDecoder) {
     mDecoder->NetworkError();
   }
 }
 
@@ -494,16 +478,21 @@ MediaDecoder::SetInfinite(bool aInfinite
 
 bool
 MediaDecoder::IsInfinite() const
 {
   MOZ_ASSERT(NS_IsMainThread());
   return mInfiniteStream;
 }
 
+#define INIT_MIRROR(name, val) \
+  name(AbstractThread::MainThread(), val, "MediaDecoder::" #name " (Mirror)")
+#define INIT_CANONICAL(name, val) \
+  name(AbstractThread::MainThread(), val, "MediaDecoder::" #name " (Canonical)")
+
 MediaDecoder::MediaDecoder(MediaDecoderOwner* aOwner)
   : mWatchManager(this, AbstractThread::MainThread())
   , mDormantSupported(false)
   , mLogicalPosition(0.0)
   , mDuration(std::numeric_limits<double>::quiet_NaN())
   , mResourceCallback(new ResourceCallback())
 #ifdef MOZ_EME
   , mCDMProxyPromise(mCDMProxyPromiseHolder.Ensure(__func__))
@@ -521,63 +510,39 @@ MediaDecoder::MediaDecoder(MediaDecoderO
   , mFiredMetadataLoaded(false)
   , mIsDormant(false)
   , mIsHeuristicDormantSupported(
       Preferences::GetBool("media.decoder.heuristic.dormant.enabled", false))
   , mHeuristicDormantTimeout(
       Preferences::GetInt("media.decoder.heuristic.dormant.timeout",
                           DEFAULT_HEURISTIC_DORMANT_TIMEOUT_MSECS))
   , mIsHeuristicDormant(false)
-  , mStateMachineIsShutdown(AbstractThread::MainThread(), true,
-                            "MediaDecoder::mStateMachineIsShutdown (Mirror)")
-  , mBuffered(AbstractThread::MainThread(), TimeIntervals(),
-              "MediaDecoder::mBuffered (Mirror)")
-  , mNextFrameStatus(AbstractThread::MainThread(),
-                     MediaDecoderOwner::NEXT_FRAME_UNINITIALIZED,
-                     "MediaDecoder::mNextFrameStatus (Mirror)")
-  , mCurrentPosition(AbstractThread::MainThread(), 0,
-                     "MediaDecoder::mCurrentPosition (Mirror)")
-  , mStateMachineDuration(AbstractThread::MainThread(), NullableTimeUnit(),
-                          "MediaDecoder::mStateMachineDuration (Mirror)")
-  , mPlaybackPosition(AbstractThread::MainThread(), 0,
-                      "MediaDecoder::mPlaybackPosition (Mirror)")
-  , mIsAudioDataAudible(AbstractThread::MainThread(), false,
-                        "MediaDecoder::mIsAudioDataAudible (Mirror)")
-  , mVolume(AbstractThread::MainThread(), 0.0,
-            "MediaDecoder::mVolume (Canonical)")
-  , mPlaybackRate(AbstractThread::MainThread(), 1.0,
-                  "MediaDecoder::mPlaybackRate (Canonical)")
-  , mPreservesPitch(AbstractThread::MainThread(), true,
-                    "MediaDecoder::mPreservesPitch (Canonical)")
-  , mEstimatedDuration(AbstractThread::MainThread(), NullableTimeUnit(),
-                       "MediaDecoder::mEstimatedDuration (Canonical)")
-  , mExplicitDuration(AbstractThread::MainThread(), Maybe<double>(),
-                      "MediaDecoder::mExplicitDuration (Canonical)")
-  , mPlayState(AbstractThread::MainThread(), PLAY_STATE_LOADING,
-               "MediaDecoder::mPlayState (Canonical)")
-  , mNextState(AbstractThread::MainThread(), PLAY_STATE_PAUSED,
-               "MediaDecoder::mNextState (Canonical)")
-  , mLogicallySeeking(AbstractThread::MainThread(), false,
-                      "MediaDecoder::mLogicallySeeking (Canonical)")
-  , mSameOriginMedia(AbstractThread::MainThread(), false,
-                     "MediaDecoder::mSameOriginMedia (Canonical)")
-  , mMediaPrincipalHandle(AbstractThread::MainThread(), PRINCIPAL_HANDLE_NONE,
-                          "MediaDecoder::mMediaPrincipalHandle (Canonical)")
-  , mPlaybackBytesPerSecond(AbstractThread::MainThread(), 0.0,
-                            "MediaDecoder::mPlaybackBytesPerSecond (Canonical)")
-  , mPlaybackRateReliable(AbstractThread::MainThread(), true,
-                          "MediaDecoder::mPlaybackRateReliable (Canonical)")
-  , mDecoderPosition(AbstractThread::MainThread(), 0,
-                     "MediaDecoder::mDecoderPosition (Canonical)")
-  , mMediaSeekable(AbstractThread::MainThread(), true,
-                   "MediaDecoder::mMediaSeekable (Canonical)")
-  , mMediaSeekableOnlyInBufferedRanges(AbstractThread::MainThread(), false,
-                   "MediaDecoder::mMediaSeekableOnlyInBufferedRanges (Canonical)")
-  , mIsVisible(AbstractThread::MainThread(), !aOwner->IsHidden(),
-               "MediaDecoder::mIsVisible (Canonical)")
+  , INIT_MIRROR(mStateMachineIsShutdown, true)
+  , INIT_MIRROR(mBuffered, TimeIntervals())
+  , INIT_MIRROR(mNextFrameStatus, MediaDecoderOwner::NEXT_FRAME_UNINITIALIZED)
+  , INIT_MIRROR(mCurrentPosition, 0)
+  , INIT_MIRROR(mStateMachineDuration, NullableTimeUnit())
+  , INIT_MIRROR(mPlaybackPosition, 0)
+  , INIT_MIRROR(mIsAudioDataAudible, false)
+  , INIT_CANONICAL(mVolume, 0.0)
+  , INIT_CANONICAL(mPlaybackRate, 1.0)
+  , INIT_CANONICAL(mPreservesPitch, true)
+  , INIT_CANONICAL(mEstimatedDuration, NullableTimeUnit())
+  , INIT_CANONICAL(mExplicitDuration, Maybe<double>())
+  , INIT_CANONICAL(mPlayState, PLAY_STATE_LOADING)
+  , INIT_CANONICAL(mNextState, PLAY_STATE_PAUSED)
+  , INIT_CANONICAL(mLogicallySeeking, false)
+  , INIT_CANONICAL(mSameOriginMedia, false)
+  , INIT_CANONICAL(mMediaPrincipalHandle, PRINCIPAL_HANDLE_NONE)
+  , INIT_CANONICAL(mPlaybackBytesPerSecond, 0.0)
+  , INIT_CANONICAL(mPlaybackRateReliable, true)
+  , INIT_CANONICAL(mDecoderPosition, 0)
+  , INIT_CANONICAL(mMediaSeekable, true)
+  , INIT_CANONICAL(mMediaSeekableOnlyInBufferedRanges, false)
+  , INIT_CANONICAL(mIsVisible, !aOwner->IsHidden())
   , mTelemetryReported(false)
 {
   MOZ_COUNT_CTOR(MediaDecoder);
   MOZ_ASSERT(NS_IsMainThread());
   MediaMemoryTracker::AddMediaDecoder(this);
 
   mAudioChannel = AudioChannelService::GetDefaultAudioChannel();
   mResourceCallback->Connect(this);
@@ -607,16 +572,19 @@ MediaDecoder::MediaDecoder(MediaDecoderO
   // mIgnoreProgressData
   mWatchManager.Watch(mLogicallySeeking, &MediaDecoder::SeekingChanged);
 
   mWatchManager.Watch(mIsAudioDataAudible, &MediaDecoder::NotifyAudibleStateChanged);
 
   MediaShutdownManager::Instance().Register(this);
 }
 
+#undef INIT_MIRROR
+#undef INIT_CANONICAL
+
 void
 MediaDecoder::Shutdown()
 {
   MOZ_ASSERT(NS_IsMainThread());
   MOZ_ASSERT(!IsShutdown());
 
   // Unwatch all watch targets to prevent further notifications.
   mWatchManager.Shutdown();
@@ -1021,36 +989,16 @@ MediaDecoder::FirstFrameLoaded(nsAutoPtr
     ChangeState(mNextState);
   }
 
   // Run NotifySuspendedStatusChanged now to give us a chance to notice
   // that autoplay should run.
   NotifySuspendedStatusChanged();
 }
 
-nsresult
-MediaDecoder::FinishDecoderSetup(MediaResource* aResource)
-{
-  MOZ_ASSERT(NS_IsMainThread());
-  MOZ_ASSERT(!IsShutdown());
-  HTMLMediaElement* element = mOwner->GetMediaElement();
-  NS_ENSURE_TRUE(element, NS_ERROR_FAILURE);
-  element->FinishDecoderSetup(this, aResource);
-  return NS_OK;
-}
-
-void
-MediaDecoder::ResetConnectionState()
-{
-  MOZ_ASSERT(NS_IsMainThread());
-  MOZ_ASSERT(!IsShutdown());
-  mOwner->ResetConnectionState();
-  MOZ_ASSERT(IsShutdown());
-}
-
 void
 MediaDecoder::NetworkError()
 {
   MOZ_ASSERT(NS_IsMainThread());
   MOZ_ASSERT(!IsShutdown());
   mOwner->NetworkError();
   MOZ_ASSERT(IsShutdown());
 }
@@ -1750,25 +1698,16 @@ MediaDecoder::IsWaveEnabled()
 }
 
 bool
 MediaDecoder::IsWebMEnabled()
 {
   return Preferences::GetBool("media.webm.enabled");
 }
 
-#ifdef NECKO_PROTOCOL_rtsp
-bool
-MediaDecoder::IsRtspEnabled()
-{
-  //Currently the Rtsp decoded by omx.
-  return (Preferences::GetBool("media.rtsp.enabled", false) && IsOmxEnabled());
-}
-#endif
-
 #ifdef MOZ_OMX_DECODER
 bool
 MediaDecoder::IsOmxEnabled()
 {
   return Preferences::GetBool("media.omx.enabled", false);
 }
 #endif
 
--- a/dom/media/MediaDecoder.h
+++ b/dom/media/MediaDecoder.h
@@ -80,18 +80,16 @@ public:
     // Called upon shutdown to stop receiving notifications.
     void Disconnect();
 
   private:
     /* MediaResourceCallback functions */
     MediaDecoderOwner* GetMediaOwner() const override;
     void SetInfinite(bool aInfinite) override;
     void SetMediaSeekable(bool aMediaSeekable) override;
-    void ResetConnectionState() override;
-    nsresult FinishDecoderSetup(MediaResource* aResource) override;
     void NotifyNetworkError() override;
     void NotifyDecodeError() override;
     void NotifyDataArrived() override;
     void NotifyBytesDownloaded() override;
     void NotifyDataEnded(nsresult aStatus) override;
     void NotifyPrincipalChanged() override;
     void NotifySuspendedStatusChanged() override;
     void NotifyBytesConsumed(int64_t aBytes, int64_t aOffset) override;
@@ -457,20 +455,16 @@ private:
   static bool IsRawEnabled();
 #endif
 
   static bool IsOggEnabled();
   static bool IsOpusEnabled();
   static bool IsWaveEnabled();
   static bool IsWebMEnabled();
 
-#ifdef NECKO_PROTOCOL_rtsp
-  static bool IsRtspEnabled();
-#endif
-
 #ifdef MOZ_OMX_DECODER
   static bool IsOmxEnabled();
 #endif
 
 #ifdef MOZ_ANDROID_OMX
   static bool IsAndroidMediaPluginEnabled();
 #endif
 
@@ -882,22 +876,16 @@ private:
   // no headers give a hint of a possible duration (Content-Length,
   // Content-Duration, and variants), and we cannot seek in the media
   // stream to determine the duration.
   //
   // When the media stream ends, we can know the duration, thus the stream is
   // no longer considered to be infinite.
   void SetInfinite(bool aInfinite);
 
-  // Reset the decoder and notify the media element that
-  // server connection is closed.
-  void ResetConnectionState();
-
-  nsresult FinishDecoderSetup(MediaResource* aResource);
-
   // Called by MediaResource when the principal of the resource has
   // changed. Called on main thread only.
   void NotifyPrincipalChanged();
 
   // Called by MediaResource when the "cache suspended" status changes.
   // If MediaResource::IsSuspendedByCache returns true, then the decoder
   // should stop buffering or otherwise waiting for download progress and
   // start consuming data, if possible, because the cache is full.
--- a/dom/media/MediaDecoderOwner.h
+++ b/dom/media/MediaDecoderOwner.h
@@ -129,22 +129,16 @@ public:
 
   // Check if the decoder owner is hidden.
   virtual bool IsHidden() const = 0;
 
   // Called by the media decoder and the video frame to get the
   // ImageContainer containing the video data.
   virtual VideoFrameContainer* GetVideoFrameContainer() = 0;
 
-  // Called by the decoder object, on the main thread,
-  // when the connection between Rtsp server and client gets lost.
-  // The decoder owner should call Shutdown() on the decoder and drop the
-  // reference to the decoder to prevent further calls into the decoder.
-  virtual void ResetConnectionState() = 0;
-
   // Called by media decoder when the audible state changed
   virtual void SetAudibleState(bool aAudible) = 0;
 
   // Notified by the shutdown manager that XPCOM shutdown has begun.
   // The decoder owner should call Shutdown() on the decoder and drop the
   // reference to the decoder to prevent further calls into the decoder.
   virtual void NotifyXPCOMShutdown() = 0;
 
--- a/dom/media/MediaDecoderStateMachine.cpp
+++ b/dom/media/MediaDecoderStateMachine.cpp
@@ -1134,77 +1134,77 @@ void
 MediaDecoderStateMachine::SetDormant(bool aDormant)
 {
   MOZ_ASSERT(OnTaskQueue());
 
   if (IsShutdown()) {
     return;
   }
 
+  bool wasDormant = mState == DECODER_STATE_DORMANT;
+  if (wasDormant == aDormant) {
+    return;
+  }
+
   if (mMetadataRequest.Exists()) {
     mPendingDormant = aDormant;
     return;
   }
 
   DECODER_LOG("SetDormant=%d", aDormant);
 
+  // Enter dormant state.
   if (aDormant) {
     if (mState == DECODER_STATE_SEEKING) {
-      if (mQueuedSeek.Exists()) {
-        // Keep latest seek target
-      } else if (mCurrentSeek.Exists()) {
-        // Because both audio and video decoders are going to be reset in this
-        // method later, we treat a VideoOnly seek task as a normal Accurate
-        // seek task so that while it is resumed, both audio and video playback
-        // are handled.
-        if (mCurrentSeek.mTarget.IsVideoOnly()) {
-          mCurrentSeek.mTarget.SetType(SeekTarget::Accurate);
-          mCurrentSeek.mTarget.SetVideoOnly(false);
-        }
-        mQueuedSeek = Move(mCurrentSeek);
-        mSeekTaskRequest.DisconnectIfExists();
-      } else {
-        mQueuedSeek.mTarget = SeekTarget(mCurrentPosition,
-                                         SeekTarget::Accurate,
-                                         MediaDecoderEventVisibility::Suppressed);
-        // XXXbholley - Nobody is listening to this promise. Do we need to pass it
-        // back to MediaDecoder when we come out of dormant?
-        RefPtr<MediaDecoder::SeekPromise> unused = mQueuedSeek.mPromise.Ensure(__func__);
+      MOZ_ASSERT(!mQueuedSeek.Exists());
+      MOZ_ASSERT(mCurrentSeek.Exists());
+      // Because both audio and video decoders are going to be reset in this
+      // method later, we treat a VideoOnly seek task as a normal Accurate
+      // seek task so that while it is resumed, both audio and video playback
+      // are handled.
+      if (mCurrentSeek.mTarget.IsVideoOnly()) {
+        mCurrentSeek.mTarget.SetType(SeekTarget::Accurate);
+        mCurrentSeek.mTarget.SetVideoOnly(false);
       }
+      mQueuedSeek = Move(mCurrentSeek);
     } else {
       mQueuedSeek.mTarget = SeekTarget(mCurrentPosition,
                                        SeekTarget::Accurate,
                                        MediaDecoderEventVisibility::Suppressed);
-      // XXXbholley - Nobody is listening to this promise. Do we need to pass it
-      // back to MediaDecoder when we come out of dormant?
+      // SeekJob asserts |mTarget.IsValid() == !mPromise.IsEmpty()| so we
+      // need to create the promise even it is not used at all.
       RefPtr<MediaDecoder::SeekPromise> unused = mQueuedSeek.mPromise.Ensure(__func__);
     }
 
+    SetState(DECODER_STATE_DORMANT);
+
     // Discard the current seek task.
     DiscardSeekTaskIfExist();
 
-    SetState(DECODER_STATE_DORMANT);
     if (IsPlaying()) {
       StopPlayback();
     }
 
     Reset();
 
     // Note that we do not wait for the decode task queue to go idle before
     // queuing the ReleaseMediaResources task - instead, we disconnect promises,
     // reset state, and put a ResetDecode in the decode task queue. Any tasks
-    // that run after ResetDecode are supposed to run with a clean slate. We rely
-    // on that in other places (i.e. seeking), so it seems reasonable to rely on
-    // it here as well.
+    // that run after ResetDecode are supposed to run with a clean slate. We
+    // rely on that in other places (i.e. seeking), so it seems reasonable to
+    // rely on it here as well.
     mReader->ReleaseMediaResources();
-  } else if (mState == DECODER_STATE_DORMANT) {
-    mDecodingFirstFrame = true;
-    SetState(DECODER_STATE_DECODING_METADATA);
-    ReadMetadata();
+
+    return;
   }
+
+  // Exit dormant state.
+  SetState(DECODER_STATE_DECODING_METADATA);
+  mDecodingFirstFrame = true;
+  ReadMetadata();
 }
 
 RefPtr<ShutdownPromise>
 MediaDecoderStateMachine::Shutdown()
 {
   MOZ_ASSERT(OnTaskQueue());
 
   // Once we've entered the shutdown state here there's no going back.
@@ -1378,20 +1378,26 @@ void MediaDecoderStateMachine::Visibilit
     // If an existing seek is in flight don't bother creating a new
     // one to catch up.
     if (mSeekTask || mQueuedSeek.Exists()) {
       return;
     }
 
     // Start video-only seek to the current time...
     SeekJob seekJob;
+
+    const SeekTarget::Type type = HasAudio()
+                                  ? SeekTarget::Type::Accurate
+                                  : SeekTarget::Type::PrevSyncPoint;
+
     seekJob.mTarget = SeekTarget(GetMediaTime(),
-                                 SeekTarget::Type::Accurate,
+                                 type,
                                  MediaDecoderEventVisibility::Suppressed,
                                  true /* aVideoOnly */);
+
     InitiateSeek(Move(seekJob));
   }
 }
 
 void MediaDecoderStateMachine::BufferedRangeUpdated()
 {
   MOZ_ASSERT(OnTaskQueue());
 
@@ -1603,16 +1609,17 @@ MediaDecoderStateMachine::InitiateSeek(S
   }
 
   // Do the seek.
   mSeekTaskRequest.Begin(mSeekTask->Seek(Duration())
     ->Then(OwnerThread(), __func__, this,
            &MediaDecoderStateMachine::OnSeekTaskResolved,
            &MediaDecoderStateMachine::OnSeekTaskRejected));
 
+  MOZ_ASSERT(!mQueuedSeek.Exists());
   MOZ_ASSERT(!mCurrentSeek.Exists());
   mCurrentSeek = Move(aSeekJob);
   return mCurrentSeek.mPromise.Ensure(__func__);
 }
 
 void
 MediaDecoderStateMachine::OnSeekTaskResolved(SeekTaskResolveValue aValue)
 {
--- a/dom/media/MediaFormatReader.cpp
+++ b/dom/media/MediaFormatReader.cpp
@@ -390,17 +390,17 @@ MediaFormatReader::OnDemuxerInitFailed(D
   mDemuxerInitRequest.Complete();
   mMetadataPromise.Reject(ReadMetadataFailureReason::METADATA_ERROR, __func__);
 }
 
 bool
 MediaFormatReader::EnsureDecoderCreated(TrackType aTrack)
 {
   MOZ_ASSERT(OnTaskQueue());
-  MOZ_ASSERT(!IsSuspended());
+  MOZ_DIAGNOSTIC_ASSERT(!IsSuspended());
 
   auto& decoder = GetDecoderData(aTrack);
 
   if (decoder.mDecoder) {
     return true;
   }
 
   if (!mPlatform) {
@@ -457,34 +457,35 @@ MediaFormatReader::EnsureDecoderCreated(
   }
   return decoder.mDecoder != nullptr;
 }
 
 bool
 MediaFormatReader::EnsureDecoderInitialized(TrackType aTrack)
 {
   MOZ_ASSERT(OnTaskQueue());
-  MOZ_ASSERT(!IsSuspended());
+  MOZ_DIAGNOSTIC_ASSERT(!IsSuspended());
 
   auto& decoder = GetDecoderData(aTrack);
 
   if (!decoder.mDecoder || decoder.mInitPromise.Exists()) {
     MOZ_ASSERT(decoder.mDecoder);
     return false;
   }
   if (decoder.mDecoderInitialized) {
     return true;
   }
 
   RefPtr<MediaFormatReader> self = this;
   decoder.mInitPromise.Begin(decoder.mDecoder->Init()
        ->Then(OwnerThread(), __func__,
               [self] (TrackType aTrack) {
-                MOZ_ASSERT(!self->IsSuspended());
+                MOZ_DIAGNOSTIC_ASSERT(!self->IsSuspended());
                 auto& decoder = self->GetDecoderData(aTrack);
+                MOZ_DIAGNOSTIC_ASSERT(decoder.mDecoder);
                 decoder.mInitPromise.Complete();
                 decoder.mDecoderInitialized = true;
                 MonitorAutoLock mon(decoder.mMonitor);
                 decoder.mDescription = decoder.mDecoder->GetDescriptionName();
                 self->SetVideoDecodeThreshold();
                 self->ScheduleUpdate(aTrack);
               },
               [self, aTrack] (MediaDataDecoder::DecoderFailureReason aResult) {
@@ -948,17 +949,17 @@ MediaFormatReader::HandleDemuxedSamples(
                                         AbstractMediaDecoder::AutoNotifyDecoded& aA)
 {
   MOZ_ASSERT(OnTaskQueue());
 
   // Don't try to create or initialize decoders
   // (which might allocate hardware resources) when suspended.
   if (IsSuspended()) {
     // Should've deleted decoders when suspended.
-    MOZ_ASSERT(!mAudio.mDecoder && !mVideo.mDecoder);
+    MOZ_DIAGNOSTIC_ASSERT(!mAudio.mDecoder && !mVideo.mDecoder);
     return;
   }
 
   auto& decoder = GetDecoderData(aTrack);
 
   if (decoder.mQueuedSamples.IsEmpty()) {
     return;
   }
--- a/dom/media/MediaQueue.h
+++ b/dom/media/MediaQueue.h
@@ -40,16 +40,17 @@ public:
     ReentrantMonitorAutoEnter mon(mReentrantMonitor);
     return nsDeque::GetSize();
   }
 
   inline void Push(T* aItem) {
     ReentrantMonitorAutoEnter mon(mReentrantMonitor);
     MOZ_ASSERT(aItem);
     NS_ADDREF(aItem);
+    MOZ_ASSERT(aItem->GetEndTime() >= aItem->mTime);
     nsDeque::Push(aItem);
     mPushEvent.Notify(RefPtr<T>(aItem));
   }
 
   inline void PushFront(T* aItem) {
     ReentrantMonitorAutoEnter mon(mReentrantMonitor);
     MOZ_ASSERT(aItem);
     NS_ADDREF(aItem);
--- a/dom/media/MediaResource.cpp
+++ b/dom/media/MediaResource.cpp
@@ -3,17 +3,16 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "mozilla/DebugOnly.h"
 
 #include "MediaResource.h"
 #include "MediaResourceCallback.h"
-#include "RtspMediaResource.h"
 
 #include "mozilla/Mutex.h"
 #include "nsDebug.h"
 #include "nsNetUtil.h"
 #include "nsThreadUtils.h"
 #include "nsIFile.h"
 #include "nsIFileChannel.h"
 #include "nsIFileStreams.h"
@@ -1501,18 +1500,16 @@ MediaResource::Create(MediaResourceCallb
 
   nsAutoCString contentType;
   aChannel->GetContentType(contentType);
 
   nsCOMPtr<nsIFileChannel> fc = do_QueryInterface(aChannel);
   RefPtr<MediaResource> resource;
   if (fc || IsBlobURI(uri)) {
     resource = new FileMediaResource(aCallback, aChannel, uri, contentType);
-  } else if (IsRtspURI(uri)) {
-    resource = new RtspMediaResource(aCallback, aChannel, uri, contentType);
   } else {
     resource = new ChannelMediaResource(aCallback, aChannel, uri, contentType);
   }
   return resource.forget();
 }
 
 void BaseMediaResource::SetLoadInBackground(bool aLoadInBackground) {
   if (aLoadInBackground == mLoadInBackground) {
--- a/dom/media/MediaResource.h
+++ b/dom/media/MediaResource.h
@@ -135,18 +135,16 @@ private:
 };
 
 // Represents a section of contiguous media, with a start and end offset.
 // Used to denote ranges of data which are cached.
 
 typedef media::Interval<int64_t> MediaByteRange;
 typedef media::IntervalSet<int64_t> MediaByteRangeSet;
 
-class RtspMediaResource;
-
 /**
  * Provides a thread-safe, seek/read interface to resources
  * loaded from a URI. Uses MediaCache to cache data received over
  * Necko's async channel API, thus resolving the mismatch between clients
  * that need efficient random access to the data and protocols that do not
  * support efficient random access, such as HTTP.
  *
  * Instances of this class must be created on the main thread.
@@ -339,22 +337,16 @@ public:
   // Notify that the last data byte range was loaded.
   virtual void NotifyLastByteRange() { }
 
   // Returns the 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;
 
-  // Get the RtspMediaResource pointer if this MediaResource really is a
-  // RtspMediaResource. For calling Rtsp specific functions.
-  virtual RtspMediaResource* GetRtspPointer() {
-    return nullptr;
-  }
-
   // 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()
   {
--- a/dom/media/MediaResourceCallback.h
+++ b/dom/media/MediaResourceCallback.h
@@ -32,25 +32,16 @@ public:
   virtual MediaDecoderOwner* GetMediaOwner() const { return nullptr; }
 
   // Notify is duration is known to this MediaResource.
   virtual void SetInfinite(bool aInfinite) {}
 
   // Notify if seeking is supported by this MediaResource.
   virtual void SetMediaSeekable(bool aMediaSeekable) {}
 
-  // Notify that server connection is closed.
-  virtual void ResetConnectionState() {}
-
-  // Used by RtspMediaResource which has an unusual sequence
-  // to setup the decoder.
-  virtual nsresult FinishDecoderSetup(MediaResource* aResource) {
-    return NS_OK;
-  }
-
   // Notify that a network error is encountered.
   virtual void NotifyNetworkError() {}
 
   // Notify that decoding has failed.
   virtual void NotifyDecodeError() {}
 
   // Notify that data arrives on the stream and is read into the cache.
   virtual void NotifyDataArrived() {}
deleted file mode 100644
--- a/dom/media/RtspMediaResource.cpp
+++ /dev/null
@@ -1,888 +0,0 @@
-/* -*- 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/DebugOnly.h"
-
-#include "RtspMediaResource.h"
-
-#include "MediaDecoder.h"
-#include "mozilla/dom/HTMLMediaElement.h"
-#include "mozilla/Monitor.h"
-#include "mozilla/Preferences.h"
-#include "mozilla/UniquePtr.h"
-#include "nsContentUtils.h"
-#include "nsIScriptSecurityManager.h"
-#include "nsIStreamingProtocolService.h"
-#include "nsServiceManagerUtils.h"
-#ifdef NECKO_PROTOCOL_rtsp
-#include "mozilla/net/RtspChannelChild.h"
-#endif
-using namespace mozilla::net;
-using namespace mozilla::media;
-
-mozilla::LazyLogModule gRtspMediaResourceLog("RtspMediaResource");
-#define RTSP_LOG(msg, ...) MOZ_LOG(gRtspMediaResourceLog, mozilla::LogLevel::Debug, \
-                                  (msg, ##__VA_ARGS__))
-// Debug logging macro with object pointer and class name.
-#define RTSPMLOG(msg, ...) \
-        RTSP_LOG("%p [RtspMediaResource]: " msg, this, ##__VA_ARGS__)
-
-namespace mozilla {
-
-/* class RtspTrackBuffer: a ring buffer implementation for audio/video track
- * un-decoded data.
- * The ring buffer is divided into BUFFER_SLOT_NUM slots,
- * and each slot's size is fixed(mSlotSize).
- * Even though the ring buffer is divided into fixed size slots, it still can
- * store the data which size is larger than one slot size.
- * */
-#define BUFFER_SLOT_NUM 8192
-#define BUFFER_SLOT_DEFAULT_SIZE 256
-#define BUFFER_SLOT_MAX_SIZE 512
-#define BUFFER_SLOT_INVALID -1
-#define BUFFER_SLOT_EMPTY 0
-
-struct BufferSlotData {
-  int32_t mLength;
-  uint64_t mTime;
-  int32_t  mFrameType;
-};
-
-// This constant is used to determine if the buffer usage is over a threshold.
-const float kBufferThresholdPerc = 0.8f;
-// The default value of playout delay duration.
-const uint32_t kPlayoutDelayMs = 3000;
-
-//-----------------------------------------------------------------------------
-// RtspTrackBuffer
-//-----------------------------------------------------------------------------
-class RtspTrackBuffer
-{
-public:
-  RtspTrackBuffer(const char *aMonitor, int32_t aTrackIdx, uint32_t aSlotSize)
-  : mMonitor(aMonitor)
-  , mSlotSize(aSlotSize)
-  , mTotalBufferSize(BUFFER_SLOT_NUM * mSlotSize)
-  , mFrameType(0)
-  , mIsStarted(false)
-  , mDuringPlayoutDelay(false)
-  , mPlayoutDelayMs(kPlayoutDelayMs)
-  , mPlayoutDelayTimer(nullptr) {
-    MOZ_COUNT_CTOR(RtspTrackBuffer);
-    mTrackIdx = aTrackIdx;
-    MOZ_ASSERT(mSlotSize < UINT32_MAX / BUFFER_SLOT_NUM);
-    mRingBuffer = MakeUnique<uint8_t[]>(mTotalBufferSize);
-    Reset();
-  };
-  ~RtspTrackBuffer() {
-    MOZ_COUNT_DTOR(RtspTrackBuffer);
-    mRingBuffer = nullptr;
-  };
-
-  size_t SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const {
-    // including this
-    size_t size = aMallocSizeOf(this);
-
-    // excluding this
-    size += aMallocSizeOf(mRingBuffer.get());
-
-    return size;
-  }
-
-  void Start() {
-    MonitorAutoLock monitor(mMonitor);
-    mIsStarted = true;
-    mFrameType = 0;
-  }
-  void Stop() {
-    MonitorAutoLock monitor(mMonitor);
-    mIsStarted = false;
-    StopPlayoutDelay();
-  }
-
-  // Read the data from mRingBuffer[mConsumerIdx*mSlotSize] into aToBuffer.
-  // If the aToBufferSize is smaller than mBufferSlotDataLength[mConsumerIdx],
-  // early return and set the aFrameSize to notify the reader the aToBuffer
-  // doesn't have enough space. The reader must realloc the aToBuffer if it
-  // wishes to read the data.
-  nsresult ReadBuffer(uint8_t* aToBuffer, uint32_t aToBufferSize,
-                      uint32_t& aReadCount, uint64_t& aFrameTime,
-                      uint32_t& aFrameSize);
-  // Write the data from aFromBuffer into mRingBuffer[mProducerIdx*mSlotSize].
-  void WriteBuffer(const char *aFromBuffer, uint32_t aWriteCount,
-                   uint64_t aFrameTime, uint32_t aFrameType);
-  // Reset the mProducerIdx, mConsumerIdx, mBufferSlotDataLength[],
-  // mBufferSlotDataTime[].
-  void Reset();
-
-  // We should call SetFrameType first then reset().
-  // If we call reset() first, the queue may still has some "garbage" frame
-  // from another thread's |OnMediaDataAvailable| before |SetFrameType|.
-  void ResetWithFrameType(uint32_t aFrameType) {
-    SetFrameType(aFrameType);
-    Reset();
-  }
-
-  // When RtspTrackBuffer is in playout delay duration, it should suspend
-  // reading data from the buffer until the playout-delay-ended event occurs,
-  // which wil be trigger by mPlayoutDelayTimer.
-  void StartPlayoutDelay() {
-    mDuringPlayoutDelay = true;
-  }
-  void LockStartPlayoutDelay() {
-    MonitorAutoLock monitor(mMonitor);
-    StartPlayoutDelay();
-  }
-
-  // If the playout delay is stopped, mPlayoutDelayTimer should be canceled.
-  void StopPlayoutDelay() {
-    if (mPlayoutDelayTimer) {
-      mPlayoutDelayTimer->Cancel();
-      mPlayoutDelayTimer = nullptr;
-    }
-    mDuringPlayoutDelay = false;
-  }
-  void LockStopPlayoutDelay() {
-    MonitorAutoLock monitor(mMonitor);
-    StopPlayoutDelay();
-  }
-
-  bool IsBufferOverThreshold();
-  void CreatePlayoutDelayTimer(unsigned long delayMs);
-  static void PlayoutDelayTimerCallback(nsITimer *aTimer, void *aClosure);
-
-private:
-  // The FrameType is sync to nsIStreamingProtocolController.h
-  void SetFrameType(uint32_t aFrameType) {
-    MonitorAutoLock monitor(mMonitor);
-    mFrameType = mFrameType | aFrameType;
-  }
-
-  // A monitor lock to prevent racing condition.
-  Monitor mMonitor;
-  // Indicate the track number for Rtsp.
-  int32_t mTrackIdx;
-  // mProducerIdx: A slot index that we store data from
-  // nsIStreamingProtocolController.
-  // mConsumerIdx: A slot index that we read when decoder need(from OMX decoder).
-  int32_t mProducerIdx;
-  int32_t mConsumerIdx;
-
-  // Because each slot's size is fixed, we need an array to record the real
-  // data length and data time stamp.
-  // The value in mBufferSlotData[index].mLength represents:
-  // -1(BUFFER_SLOT_INVALID): The index of slot data is invalid, mConsumerIdx
-  //                          should go forward.
-  // 0(BUFFER_SLOT_EMPTY): The index slot is empty. mConsumerIdx should wait here.
-  // positive value: The index slot contains valid data and the value is data size.
-  BufferSlotData mBufferSlotData[BUFFER_SLOT_NUM];
-
-  // The ring buffer pointer.
-  UniquePtr<uint8_t[]> mRingBuffer;
-  // Each slot's size.
-  uint32_t mSlotSize;
-  // Total mRingBuffer's total size.
-  uint32_t mTotalBufferSize;
-  // A flag that that indicate the incoming data should be dropped or stored.
-  // When we are seeking, the incoming data should be dropped.
-  // Bit definition in |nsIStreamingProtocolController.h|
-  uint32_t mFrameType;
-
-  // Set true/false when |Start()/Stop()| is called.
-  bool mIsStarted;
-
-  // Indicate the buffer is in playout delay duration or not.
-  bool mDuringPlayoutDelay;
-  // Playout delay duration defined in milliseconds.
-  uint32_t mPlayoutDelayMs;
-  // Timer used to fire playout-delay-ended event.
-  nsCOMPtr<nsITimer> mPlayoutDelayTimer;
-};
-
-nsresult RtspTrackBuffer::ReadBuffer(uint8_t* aToBuffer, uint32_t aToBufferSize,
-                                     uint32_t& aReadCount, uint64_t& aFrameTime,
-                                     uint32_t& aFrameSize)
-{
-  MonitorAutoLock monitor(mMonitor);
-  RTSPMLOG("ReadBuffer mTrackIdx %d mProducerIdx %d mConsumerIdx %d "
-           "mBufferSlotData[mConsumerIdx].mLength %d"
-           ,mTrackIdx ,mProducerIdx ,mConsumerIdx
-           ,mBufferSlotData[mConsumerIdx].mLength);
-  // Reader should skip the slots with mLength==BUFFER_SLOT_INVALID.
-  // The loop ends when
-  // 1. Read data successfully
-  // 2. Fail to read data due to aToBuffer's space
-  // 3. No data in this buffer
-  // 4. mIsStarted is not set
-  while (1) {
-    // Make sure the track buffer is started.
-    // It could be stopped when RTSP connection is disconnected.
-    if (!mIsStarted) {
-      RTSPMLOG("ReadBuffer: mIsStarted is false");
-      return NS_ERROR_FAILURE;
-    }
-
-    // Do not read from buffer if we are still in the playout delay duration.
-    if (mDuringPlayoutDelay) {
-      monitor.Wait();
-      continue;
-    }
-
-    if (mBufferSlotData[mConsumerIdx].mFrameType & MEDIASTREAM_FRAMETYPE_END_OF_STREAM) {
-      return NS_BASE_STREAM_CLOSED;
-    }
-
-    if (mBufferSlotData[mConsumerIdx].mLength > 0) {
-      // Check the aToBuffer space is enough for data copy.
-      if ((int32_t)aToBufferSize < mBufferSlotData[mConsumerIdx].mLength) {
-        aFrameSize = mBufferSlotData[mConsumerIdx].mLength;
-        break;
-      }
-      uint32_t slots = mBufferSlotData[mConsumerIdx].mLength / mSlotSize;
-      if (mBufferSlotData[mConsumerIdx].mLength % mSlotSize > 0) {
-        slots++;
-      }
-      // we have data, copy to aToBuffer
-      MOZ_ASSERT(mBufferSlotData[mConsumerIdx].mLength <=
-                 (int32_t)((BUFFER_SLOT_NUM - mConsumerIdx) * mSlotSize));
-      memcpy(aToBuffer,
-             (void *)(&mRingBuffer[mSlotSize * mConsumerIdx]),
-             mBufferSlotData[mConsumerIdx].mLength);
-
-      aFrameSize = aReadCount = mBufferSlotData[mConsumerIdx].mLength;
-      aFrameTime = mBufferSlotData[mConsumerIdx].mTime;
-      RTSPMLOG("DataLength %d, data time %lld"
-               ,mBufferSlotData[mConsumerIdx].mLength
-               ,mBufferSlotData[mConsumerIdx].mTime);
-      // After reading the data, we set current index of mBufferSlotDataLength
-      // to BUFFER_SLOT_EMPTY to indicate these slots are free.
-      for (uint32_t i = mConsumerIdx; i < mConsumerIdx + slots; ++i) {
-        mBufferSlotData[i].mLength = BUFFER_SLOT_EMPTY;
-        mBufferSlotData[i].mTime = BUFFER_SLOT_EMPTY;
-      }
-      mConsumerIdx = (mConsumerIdx + slots) % BUFFER_SLOT_NUM;
-      break;
-    } else if (mBufferSlotData[mConsumerIdx].mLength == BUFFER_SLOT_INVALID) {
-      mConsumerIdx = (mConsumerIdx + 1) % BUFFER_SLOT_NUM;
-      RTSPMLOG("BUFFER_SLOT_INVALID move forward");
-    } else {
-      // No data, the decode thread is blocked here until we receive
-      // OnMediaDataAvailable. The OnMediaDataAvailable will call WriteBuffer()
-      // to wake up the decode thread.
-      RTSPMLOG("monitor.Wait()");
-      monitor.Wait();
-    }
-  }
-  return NS_OK;
-}
-
-/* When we perform a WriteBuffer, we check mIsStarted and aFrameType first.
- * These flags prevent "garbage" frames from being written into the buffer.
- *
- * After writing the data into the buffer, we check to see if we wrote over a
- * slot, and update mConsumerIdx if necessary.
- * This ensures that the decoder will get the "oldest" data available in the
- * buffer.
- *
- * If the incoming data is larger than one slot size (isMultipleSlots), we do
- * |mBufferSlotData[].mLength = BUFFER_SLOT_INVALID;| for other slots except the
- * first slot, in order to notify the reader that some slots are unavailable.
- *
- * If the incoming data is isMultipleSlots and crosses the end of
- * BUFFER_SLOT_NUM, returnToHead is set to true and the data will continue to
- * be written from head(index 0).
- *
- * MEDIASTREAM_FRAMETYPE_DISCONTINUITY currently is used when we are seeking.
- * */
-void RtspTrackBuffer::WriteBuffer(const char *aFromBuffer, uint32_t aWriteCount,
-                                  uint64_t aFrameTime, uint32_t aFrameType)
-{
-  MonitorAutoLock monitor(mMonitor);
-  if (!mIsStarted) {
-    RTSPMLOG("mIsStarted is false");
-    return;
-  }
-  if (mTotalBufferSize < aWriteCount) {
-    RTSPMLOG("mTotalBufferSize < aWriteCount, incoming data is too large");
-    return;
-  }
-  // Checking the incoming data's frame type.
-  // If we receive MEDIASTREAM_FRAMETYPE_DISCONTINUITY, clear the mFrameType
-  // imply the RtspTrackBuffer is ready for receive data.
-  if (aFrameType & MEDIASTREAM_FRAMETYPE_DISCONTINUITY) {
-    mFrameType = mFrameType & (~MEDIASTREAM_FRAMETYPE_DISCONTINUITY);
-    RTSPMLOG("Clear mFrameType");
-    return;
-  }
-  // Checking current buffer frame type.
-  // If the MEDIASTREAM_FRAMETYPE_DISCONTINUNITY bit is set, imply the
-  // RtspTrackBuffer can't receive data now. So we drop the frame until we
-  // receive MEDIASTREAM_FRAMETYPE_DISCONTINUNITY.
-  if (mFrameType & MEDIASTREAM_FRAMETYPE_DISCONTINUITY) {
-    RTSPMLOG("Return because the mFrameType is set");
-    return;
-  }
-
-  // Create a timer to delay ReadBuffer() for a duration.
-  if (mDuringPlayoutDelay && !mPlayoutDelayTimer) {
-    CreatePlayoutDelayTimer(mPlayoutDelayMs);
-  }
-
-  // The flag is true if the incoming data is larger than one slot size.
-  bool isMultipleSlots = false;
-  // The flag is true if the incoming data is larger than remainder free slots
-  bool returnToHead = false;
-  // Calculate how many slots the incoming data needed.
-  int32_t slots = aWriteCount / mSlotSize;
-  if (aWriteCount % mSlotSize > 0) {
-    slots++;
-  }
-  int32_t i;
-  RTSPMLOG("WriteBuffer mTrackIdx %d mProducerIdx %d mConsumerIdx %d",
-           mTrackIdx, mProducerIdx,mConsumerIdx);
-  if (aWriteCount > mSlotSize) {
-    isMultipleSlots = true;
-  }
-  if (isMultipleSlots &&
-      (aWriteCount > (BUFFER_SLOT_NUM - mProducerIdx) * mSlotSize)) {
-    returnToHead = true;
-  }
-  RTSPMLOG("slots %d isMultipleSlots %d returnToHead %d",
-           slots, isMultipleSlots, returnToHead);
-  if (returnToHead) {
-    // Clear the rest index of mBufferSlotData[].mLength
-    for (i = mProducerIdx; i < BUFFER_SLOT_NUM; ++i) {
-      mBufferSlotData[i].mLength = BUFFER_SLOT_INVALID;
-    }
-    // We wrote one or more slots that the decode thread has not yet read.
-    // So the mConsumerIdx returns to the head of slot buffer and moves forward
-    // to the oldest slot.
-    if (mProducerIdx <= mConsumerIdx && mConsumerIdx < mProducerIdx + slots) {
-      mConsumerIdx = 0;
-      for (i = mConsumerIdx; i < BUFFER_SLOT_NUM; ++i) {
-        if (mBufferSlotData[i].mLength > 0) {
-          mConsumerIdx = i;
-          break;
-        }
-      }
-    }
-    mProducerIdx = 0;
-  }
-
-  if (!(aFrameType & MEDIASTREAM_FRAMETYPE_END_OF_STREAM)) {
-    memcpy(&(mRingBuffer[mSlotSize * mProducerIdx]), aFromBuffer, aWriteCount);
-  }
-
-  // If the buffer is almost full, stop the playout delay to let ReadBuffer()
-  // consume data in the buffer.
-  if (mDuringPlayoutDelay && IsBufferOverThreshold()) {
-    StopPlayoutDelay();
-  }
-
-  if (mProducerIdx <= mConsumerIdx && mConsumerIdx < mProducerIdx + slots
-      && mBufferSlotData[mConsumerIdx].mLength > 0) {
-    // Wrote one or more slots that the decode thread has not yet read.
-    RTSPMLOG("overwrite!! %d time %lld"
-             ,mTrackIdx,mBufferSlotData[mConsumerIdx].mTime);
-    if (aFrameType & MEDIASTREAM_FRAMETYPE_END_OF_STREAM) {
-      mBufferSlotData[mProducerIdx].mLength = 0;
-      mBufferSlotData[mProducerIdx].mTime = 0;
-      StopPlayoutDelay();
-    } else {
-      mBufferSlotData[mProducerIdx].mLength = aWriteCount;
-      mBufferSlotData[mProducerIdx].mTime = aFrameTime;
-    }
-    mBufferSlotData[mProducerIdx].mFrameType = aFrameType;
-    // Clear the mBufferSlotDataLength except the start slot.
-    if (isMultipleSlots) {
-      for (i = mProducerIdx + 1; i < mProducerIdx + slots; ++i) {
-        mBufferSlotData[i].mLength = BUFFER_SLOT_INVALID;
-      }
-    }
-    mProducerIdx = (mProducerIdx + slots) % BUFFER_SLOT_NUM;
-    // Move the mConsumerIdx forward to ensure that the decoder reads the
-    // oldest data available.
-    mConsumerIdx = mProducerIdx;
-  } else {
-    // Normal case, the writer doesn't take over the reader.
-    if (aFrameType & MEDIASTREAM_FRAMETYPE_END_OF_STREAM) {
-      mBufferSlotData[mProducerIdx].mLength = 0;
-      mBufferSlotData[mProducerIdx].mTime = 0;
-      StopPlayoutDelay();
-    } else {
-      mBufferSlotData[mProducerIdx].mLength = aWriteCount;
-      mBufferSlotData[mProducerIdx].mTime = aFrameTime;
-    }
-    mBufferSlotData[mProducerIdx].mFrameType = aFrameType;
-    // Clear the mBufferSlotData[].mLength except the start slot.
-    if (isMultipleSlots) {
-      for (i = mProducerIdx + 1; i < mProducerIdx + slots; ++i) {
-        mBufferSlotData[i].mLength = BUFFER_SLOT_INVALID;
-      }
-    }
-    mProducerIdx = (mProducerIdx + slots) % BUFFER_SLOT_NUM;
-  }
-
-  mMonitor.NotifyAll();
-}
-
-void RtspTrackBuffer::Reset() {
-  MonitorAutoLock monitor(mMonitor);
-  mProducerIdx = 0;
-  mConsumerIdx = 0;
-  for (uint32_t i = 0; i < BUFFER_SLOT_NUM; ++i) {
-    mBufferSlotData[i].mLength = BUFFER_SLOT_EMPTY;
-    mBufferSlotData[i].mTime = BUFFER_SLOT_EMPTY;
-    mBufferSlotData[i].mFrameType = MEDIASTREAM_FRAMETYPE_NORMAL;
-  }
-  StopPlayoutDelay();
-  mMonitor.NotifyAll();
-}
-
-bool
-RtspTrackBuffer::IsBufferOverThreshold()
-{
-  static int32_t numSlotsThreshold =
-    BUFFER_SLOT_NUM * kBufferThresholdPerc;
-
-  int32_t numSlotsUsed = mProducerIdx - mConsumerIdx;
-  if (numSlotsUsed < 0) {  // wrap-around
-    numSlotsUsed = (BUFFER_SLOT_NUM - mConsumerIdx) + mProducerIdx;
-  }
-  if (numSlotsUsed > numSlotsThreshold) {
-    return true;
-  }
-
-  return false;
-}
-
-void
-RtspTrackBuffer::CreatePlayoutDelayTimer(unsigned long delayMs)
-{
-  if (delayMs <= 0) {
-    return;
-  }
-  mPlayoutDelayTimer = do_CreateInstance("@mozilla.org/timer;1");
-  if (mPlayoutDelayTimer) {
-    mPlayoutDelayTimer->InitWithFuncCallback(PlayoutDelayTimerCallback,
-                                             this, delayMs,
-                                             nsITimer::TYPE_ONE_SHOT);
-  }
-}
-
-// static
-void
-RtspTrackBuffer::PlayoutDelayTimerCallback(nsITimer *aTimer,
-                                           void *aClosure)
-{
-  MOZ_ASSERT(aTimer);
-  MOZ_ASSERT(aClosure);
-
-  RtspTrackBuffer *self = static_cast<RtspTrackBuffer*>(aClosure);
-  MonitorAutoLock lock(self->mMonitor);
-  self->StopPlayoutDelay();
-  lock.NotifyAll();
-}
-
-//-----------------------------------------------------------------------------
-// RtspMediaResource
-//-----------------------------------------------------------------------------
-RtspMediaResource::RtspMediaResource(MediaResourceCallback* aCallback,
-    nsIChannel* aChannel, nsIURI* aURI, const nsACString& aContentType)
-  : BaseMediaResource(aCallback, aChannel, aURI, aContentType)
-  , mIsConnected(false)
-  , mIsLiveStream(false)
-  , mHasTimestamp(true)
-  , mIsSuspend(true)
-{
-#ifndef NECKO_PROTOCOL_rtsp
-  MOZ_CRASH("Should not be called except for B2G platform");
-#else
-  MOZ_ASSERT(aChannel);
-  mMediaStreamController =
-    static_cast<RtspChannelChild*>(aChannel)->GetController();
-  MOZ_ASSERT(mMediaStreamController);
-  mListener = new Listener(this);
-  mMediaStreamController->AsyncOpen(mListener);
-#endif
-}
-
-RtspMediaResource::~RtspMediaResource()
-{
-  RTSPMLOG("~RtspMediaResource");
-  if (mListener) {
-    // Kill its reference to us since we're going away
-    mListener->Revoke();
-  }
-}
-
-void RtspMediaResource::SetSuspend(bool aIsSuspend)
-{
-  NS_ASSERTION(!NS_IsMainThread(), "Don't call on main thread");
-  RTSPMLOG("SetSuspend %d",aIsSuspend);
-
-  nsCOMPtr<nsIRunnable> runnable =
-    NewRunnableMethod<bool>(this, &RtspMediaResource::NotifySuspend,
-                            aIsSuspend);
-  NS_DispatchToMainThread(runnable);
-}
-
-void RtspMediaResource::NotifySuspend(bool aIsSuspend)
-{
-  NS_ASSERTION(NS_IsMainThread(), "Only call on main thread");
-  RTSPMLOG("NotifySuspend %d",aIsSuspend);
-
-  mIsSuspend = aIsSuspend;
-  if (mCallback) {
-    mCallback->NotifySuspendedStatusChanged();
-  }
-}
-
-size_t
-RtspMediaResource::SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const
-{
-  size_t size = BaseMediaResource::SizeOfExcludingThis(aMallocSizeOf);
-  size += mTrackBuffer.ShallowSizeOfExcludingThis(aMallocSizeOf);
-
-  // Include the size of each track buffer.
-  for (size_t i = 0; i < mTrackBuffer.Length(); i++) {
-    size += mTrackBuffer[i]->SizeOfIncludingThis(aMallocSizeOf);
-  }
-
-  // Could add in the future:
-  // - mMediaStreamController
-
-  return size;
-}
-
-//----------------------------------------------------------------------------
-// RtspMediaResource::Listener
-//----------------------------------------------------------------------------
-NS_IMPL_ISUPPORTS(RtspMediaResource::Listener,
-                  nsIInterfaceRequestor, nsIStreamingProtocolListener);
-
-nsresult
-RtspMediaResource::Listener::OnMediaDataAvailable(uint8_t aTrackIdx,
-                                                  const nsACString &data,
-                                                  uint32_t length,
-                                                  uint32_t offset,
-                                                  nsIStreamingProtocolMetaData *meta)
-{
-  if (!mResource)
-    return NS_OK;
-  return mResource->OnMediaDataAvailable(aTrackIdx, data, length, offset, meta);
-}
-
-nsresult
-RtspMediaResource::Listener::OnConnected(uint8_t aTrackIdx,
-                                         nsIStreamingProtocolMetaData *meta)
-{
-  if (!mResource)
-    return NS_OK;
-  return mResource->OnConnected(aTrackIdx, meta);
-}
-
-nsresult
-RtspMediaResource::Listener::OnDisconnected(uint8_t aTrackIdx, nsresult reason)
-{
-  if (!mResource)
-    return NS_OK;
-  return mResource->OnDisconnected(aTrackIdx, reason);
-}
-
-nsresult
-RtspMediaResource::Listener::GetInterface(const nsIID & aIID, void **aResult)
-{
-  return QueryInterface(aIID, aResult);
-}
-
-void
-RtspMediaResource::Listener::Revoke()
-{
-  NS_ASSERTION(NS_IsMainThread(), "Don't call on non-main thread");
-  if (mResource) {
-    mResource = nullptr;
-  }
-}
-
-nsresult
-RtspMediaResource::ReadFrameFromTrack(uint8_t* aBuffer, uint32_t aBufferSize,
-                                      uint32_t aTrackIdx, uint32_t& aBytes,
-                                      uint64_t& aTime, uint32_t& aFrameSize)
-{
-  NS_ASSERTION(!NS_IsMainThread(), "Don't call on main thread");
-  NS_ASSERTION(aTrackIdx < mTrackBuffer.Length(),
-               "ReadTrack index > mTrackBuffer");
-  MOZ_ASSERT(aBuffer);
-
-  if (!mIsConnected) {
-    RTSPMLOG("ReadFrameFromTrack: RTSP not connected");
-    return NS_ERROR_FAILURE;
-  }
-
-  return mTrackBuffer[aTrackIdx]->ReadBuffer(aBuffer, aBufferSize, aBytes,
-                                             aTime, aFrameSize);
-}
-
-nsresult
-RtspMediaResource::OnMediaDataAvailable(uint8_t aTrackIdx,
-                                        const nsACString &data,
-                                        uint32_t length,
-                                        uint32_t offset,
-                                        nsIStreamingProtocolMetaData *meta)
-{
-  uint64_t time;
-  uint32_t frameType;
-  meta->GetTimeStamp(&time);
-  meta->GetFrameType(&frameType);
-  mTrackBuffer[aTrackIdx]->WriteBuffer(data.BeginReading(), length, time,
-                                       frameType);
-  return NS_OK;
-}
-
-// Bug 962309 - Video RTSP support should be disabled in 1.3
-bool
-RtspMediaResource::IsVideoEnabled()
-{
-  return Preferences::GetBool("media.rtsp.video.enabled", false);
-}
-
-bool
-RtspMediaResource::IsVideo(uint8_t tracks, nsIStreamingProtocolMetaData *meta)
-{
-  bool isVideo = false;
-  for (int i = 0; i < tracks; ++i) {
-    nsCOMPtr<nsIStreamingProtocolMetaData> trackMeta;
-    mMediaStreamController->GetTrackMetaData(i, getter_AddRefs(trackMeta));
-    MOZ_ASSERT(trackMeta);
-    uint32_t w = 0, h = 0;
-    trackMeta->GetWidth(&w);
-    trackMeta->GetHeight(&h);
-    if (w > 0 || h > 0) {
-      isVideo = true;
-      break;
-    }
-  }
-  return isVideo;
-}
-
-nsresult
-RtspMediaResource::OnConnected(uint8_t aTrackIdx,
-                               nsIStreamingProtocolMetaData *meta)
-{
-  if (mIsConnected) {
-    for (uint32_t i = 0 ; i < mTrackBuffer.Length(); ++i) {
-      mTrackBuffer[i]->Start();
-    }
-    return NS_OK;
-  }
-
-  uint8_t tracks;
-  mMediaStreamController->GetTotalTracks(&tracks);
-
-  // If the preference of RTSP video feature is not enabled and the streaming is
-  // video, we give up moving forward.
-  if (!IsVideoEnabled() && IsVideo(tracks, meta)) {
-    // Give up, report error to media element.
-    mCallback->NotifyDecodeError();
-    return NS_ERROR_FAILURE;
-  }
-  uint64_t durationUs = 0;
-  for (int i = 0; i < tracks; ++i) {
-    nsCString rtspTrackId("RtspTrack");
-    rtspTrackId.AppendInt(i);
-    nsCOMPtr<nsIStreamingProtocolMetaData> trackMeta;
-    mMediaStreamController->GetTrackMetaData(i, getter_AddRefs(trackMeta));
-    MOZ_ASSERT(trackMeta);
-    trackMeta->GetDuration(&durationUs);
-
-    // Here is a heuristic to estimate the slot size.
-    // For video track, calculate the width*height.
-    // For audio track, use the BUFFER_SLOT_DEFAULT_SIZE because the w*h is 0.
-    // Finally clamp them into (BUFFER_SLOT_DEFAULT_SIZE,BUFFER_SLOT_MAX_SIZE)
-    uint32_t w, h;
-    uint32_t slotSize;
-    trackMeta->GetWidth(&w);
-    trackMeta->GetHeight(&h);
-    slotSize = clamped((int32_t)(w * h), BUFFER_SLOT_DEFAULT_SIZE,
-                       BUFFER_SLOT_MAX_SIZE);
-    mTrackBuffer.AppendElement(new RtspTrackBuffer(rtspTrackId.get(),
-                                                   i, slotSize));
-    mTrackBuffer[i]->Start();
-  }
-
-  if (!mCallback) {
-    return NS_ERROR_FAILURE;
-  }
-
-  // If the durationUs is 0, imply the stream is live stream.
-  if (durationUs) {
-    // Not live stream.
-    mIsLiveStream = false;
-    mCallback->SetInfinite(false);
-  } else {
-    // Live stream.
-    // Check the preference "media.realtime_decoder.enabled".
-    if (!Preferences::GetBool("media.realtime_decoder.enabled", false)) {
-      // Give up, report error to media element.
-      mCallback->NotifyDecodeError();
-      return NS_ERROR_FAILURE;
-    } else {
-      mIsLiveStream = true;
-      bool seekable = false;
-      mCallback->SetInfinite(true);
-      mCallback->SetMediaSeekable(seekable);
-    }
-  }
-  MediaDecoderOwner* owner = mCallback->GetMediaOwner();
-  NS_ENSURE_TRUE(owner, NS_ERROR_FAILURE);
-  // Fires an initial progress event.
-  owner->DownloadProgressed();
-
-  nsresult rv = mCallback->FinishDecoderSetup(this);
-  NS_ENSURE_SUCCESS(rv, rv);
-
-  mIsConnected = true;
-  return NS_OK;
-}
-
-nsresult
-RtspMediaResource::OnDisconnected(uint8_t aTrackIdx, nsresult aReason)
-{
-  NS_ASSERTION(NS_IsMainThread(), "Don't call on non-main thread");
-
-  for (uint32_t i = 0 ; i < mTrackBuffer.Length(); ++i) {
-    mTrackBuffer[i]->Stop();
-    mTrackBuffer[i]->Reset();
-  }
-
-  if (mCallback) {
-    if (aReason == NS_ERROR_NOT_INITIALIZED ||
-        aReason == NS_ERROR_CONNECTION_REFUSED ||
-        aReason == NS_ERROR_NOT_CONNECTED ||
-        aReason == NS_ERROR_NET_TIMEOUT) {
-      // Report error code to Decoder.
-      RTSPMLOG("Error in OnDisconnected 0x%x", aReason);
-      mIsConnected = false;
-      mCallback->NotifyNetworkError();
-    } else {
-      // Resetting the decoder and media element when the connection
-      // between RTSP client and server goes down.
-      mCallback->ResetConnectionState();
-    }
-  }
-
-  if (mListener) {
-    // Note: Listener's Revoke() kills its reference to us, which means it would
-    // release |this| object. So, ensure it is called in the end of this method.
-    mListener->Revoke();
-  }
-
-  return NS_OK;
-}
-
-void RtspMediaResource::Suspend(bool aCloseImmediately)
-{
-  NS_ASSERTION(NS_IsMainThread(), "Don't call on non-main thread");
-
-  mIsSuspend = true;
-  if (NS_WARN_IF(!mCallback)) {
-    return;
-  }
-
-  MediaDecoderOwner* owner = mCallback->GetMediaOwner();
-  NS_ENSURE_TRUE_VOID(owner);
-  dom::HTMLMediaElement* element = owner->GetMediaElement();
-  NS_ENSURE_TRUE_VOID(element);
-
-  mMediaStreamController->Suspend();
-  element->DownloadSuspended();
-  mCallback->NotifySuspendedStatusChanged();
-}
-
-void RtspMediaResource::Resume()
-{
-  NS_ASSERTION(NS_IsMainThread(), "Don't call on non-main thread");
-
-  mIsSuspend = false;
-  if (NS_WARN_IF(!mCallback)) {
-    return;
-  }
-
-  MediaDecoderOwner* owner = mCallback->GetMediaOwner();
-  NS_ENSURE_TRUE_VOID(owner);
-  dom::HTMLMediaElement* element = owner->GetMediaElement();
-  NS_ENSURE_TRUE_VOID(element);
-
-  if (mChannel) {
-    element->DownloadResumed();
-  }
-  mMediaStreamController->Resume();
-  mCallback->NotifySuspendedStatusChanged();
-}
-
-nsresult RtspMediaResource::Open(nsIStreamListener **aStreamListener)
-{
-  return NS_OK;
-}
-
-nsresult RtspMediaResource::Close()
-{
-  NS_ASSERTION(NS_IsMainThread(), "Only call on main thread");
-  mMediaStreamController->Stop();
-  // Since mCallback is not an nsCOMPtr in BaseMediaResource, we have to
-  // explicitly set it as null pointer in order to prevent misuse from this
-  // object (RtspMediaResource).
-  if (mCallback) {
-    mCallback = nullptr;
-  }
-  return NS_OK;
-}
-
-already_AddRefed<nsIPrincipal> RtspMediaResource::GetCurrentPrincipal()
-{
-  NS_ASSERTION(NS_IsMainThread(), "Only call on main thread");
-
-  nsCOMPtr<nsIPrincipal> principal;
-  nsIScriptSecurityManager* secMan = nsContentUtils::GetSecurityManager();
-  if (!secMan || !mChannel)
-    return nullptr;
-  secMan->GetChannelResultPrincipal(mChannel, getter_AddRefs(principal));
-  return principal.forget();
-}
-
-nsresult RtspMediaResource::SeekTime(int64_t aOffset)
-{
-  NS_ASSERTION(!NS_IsMainThread(), "Don't call on main thread");
-
-  RTSPMLOG("Seek requested for aOffset [%lld] for decoder [%p]",
-           aOffset, mCallback.get());
-  // Clear buffer and raise the frametype flag.
-  for(uint32_t i = 0 ; i < mTrackBuffer.Length(); ++i) {
-    mTrackBuffer[i]->ResetWithFrameType(MEDIASTREAM_FRAMETYPE_DISCONTINUITY);
-  }
-
-  return mMediaStreamController->Seek(aOffset);
-}
-
-void
-RtspMediaResource::EnablePlayoutDelay()
-{
-  for (uint32_t i = 0; i < mTrackBuffer.Length(); ++i) {
-    mTrackBuffer[i]->LockStartPlayoutDelay();
-  }
-}
-
-void
-RtspMediaResource::DisablePlayoutDelay()
-{
-  for (uint32_t i = 0; i < mTrackBuffer.Length(); ++i) {
-    mTrackBuffer[i]->LockStopPlayoutDelay();
-  }
-}
-
-} // namespace mozilla
-
deleted file mode 100644
--- a/dom/media/RtspMediaResource.h
+++ /dev/null
@@ -1,252 +0,0 @@
-/* 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(RtspMediaResource_h_)
-#define RtspMediaResource_h_
-
-#include "MediaResource.h"
-#include "mozilla/Monitor.h"
-#include "nsAutoPtr.h"
-#include "nsITimer.h"
-#include "VideoUtils.h"
-
-namespace mozilla {
-
-class RtspTrackBuffer;
-
-/* RtspMediaResource
- * RtspMediaResource provides an interface to deliver and control RTSP media
- * data to RtspDecoder.
- *
- * RTSP Flow Start vs HTTP Flow Start:
- * For HTTP (and files stored on disk), once the channel is created and response
- * data is available, HTMLMediaElement::MediaLoadListener::OnStartRequest is
- * called. (Note, this is an asynchronous call following channel->AsyncOpen).
- * The decoder and MediaResource are set up to talk to each other:
- * InitializeDecoderForChannel and FinishDecoderSetup.
- * RtspMediaResource is different from this, in that FinishDecoderSetup is
- * postponed until after the initial connection with the server is made.
- * RtspController, owned by RtspMediaResource, provides the interface to setup
- * the connection, and calls RtspMediaResource::Listener::OnConnected
- * (from nsIStreamingProtocolListener). FinishDecoderSetup is then called to
- * connect RtspMediaResource with RtspDecoder and allow HTMLMediaElement to
- * request playback etc.
- *
- * Playback:
- * When the user presses play/pause, HTMLMediaElement::Play/::Pause is called,
- * subsequently making calls to the decoder state machine. Upon these state
- * changes, the decoder is told to start reading and decoding data. This causes
- * the nsIStreamingMediaController object to send play/pause commands to the
- * server.
- * Data is then delivered to the host and eventually written to the
- * RtspTrackBuffer objects. Note that RtspMediaResource does not know about the
- * play or pause state. It only knows about the data written into its buffers.
- *
- * Data Structures and Flow:
- * Unlike HTTP, RTSP provides separate streams for audio and video.
- * As such, it creates two RtspTrackBuffer objects for the audio and video data.
- * Data is read using the function ReadFrameFromTrack. These buffer objects are
- * ring buffers, implying that data from the network may be discarded if the
- * decoder cannot read at a high enough rate.
- *
- * Data is delivered via RtspMediaResource::Listener::OnMediaDataAvailable.
- * This Listener implements nsIStreamingProtocolListener, and writes the data to
- * the appropriate RtspTrackBuffer. The decoder then reads the data by calling
- * RtspMediaResource::ReadFrameFromTrack. Note that the decoder and decode
- * thread will be blocked until data is available in one of the two buffers.
- *
- * Seeking:
- * Since the frame data received after seek is not continuous with existing
- * frames in RtspTrackBuffer, the buffer must be cleared. If we don't clear the
- * old frame data in RtspTrackBuffer, the decoder's behavior will be
- * unpredictable. So we add |mFrameType| in RtspTrackBuffer to do this:
- * When we are seeking, the mFrameType flag is set, and RtspTrackBuffer will
- * drop the incoming data until the RTSP server completes the seek operation.
- * Note: seeking for RTSP is carried out based on sending the seek time to the
- * server, unlike HTTP in which the seek time is converted to a byte offset.
- * Thus, RtspMediaResource has a SeekTime function which should be called
- * instead of Seek.
- * */
-class RtspMediaResource : public BaseMediaResource
-{
-public:
-  RtspMediaResource(MediaResourceCallback* aCallback, nsIChannel* aChannel, nsIURI* aURI,
-                    const nsACString& aContentType);
-  virtual ~RtspMediaResource();
-
-  // The following methods can be called on any thread.
-
-  // Get the RtspMediaResource pointer if this MediaResource is a
-  // RtspMediaResource. For calling Rtsp specific functions.
-  RtspMediaResource* GetRtspPointer() override final {
-    return this;
-  }
-
-  // Returns the nsIStreamingProtocolController in the RtspMediaResource.
-  // RtspMediaExtractor: request it to get mime type for creating decoder.
-  // RtspOmxDecoder: request it to send play/pause commands to RTSP server.
-  // The lifetime of mMediaStreamController is controlled by RtspMediaResource
-  // because the RtspMediaExtractor and RtspOmxDecoder won't hold the reference.
-  nsIStreamingProtocolController* GetMediaStreamController() {
-    return mMediaStreamController;
-  }
-
-  // Even it is a live stream, as long as it provides valid timestamps,
-  // we tell state machine it's not a live stream.
-  bool IsRealTime() override {
-    return !mHasTimestamp;
-  }
-
-  // Called by RtspOmxReader, dispatch a runnable to notify mDecoder.
-  // Other thread only.
-  void SetSuspend(bool aIsSuspend);
-
-  // The following methods can be called on any thread except main thread.
-
-  // Read data from track.
-  // Parameters:
-  //   aToBuffer, aToBufferSize: buffer pointer and buffer size.
-  //   aReadCount: output actual read bytes.
-  //   aFrameTime: output frame time stamp.
-  //   aFrameSize: actual data size in track.
-  nsresult ReadFrameFromTrack(uint8_t* aBuffer, uint32_t aBufferSize,
-                              uint32_t aTrackIdx, uint32_t& aBytes,
-                              uint64_t& aTime, uint32_t& aFrameSize);
-
-  // Seek to the given time offset
-  nsresult SeekTime(int64_t aOffset);
-
-  // The idea of playout delay is to hold frames in the playout buffer
-  // (RtspTrackBuffer) for a period of time in order to smooth timing variations
-  // caused by the network.
-  void EnablePlayoutDelay();
-  void DisablePlayoutDelay();
-
-  // dummy
-  nsresult ReadAt(int64_t aOffset, char* aBuffer,
-                  uint32_t aCount, uint32_t* aBytes)  override{
-    return NS_ERROR_FAILURE;
-  }
-  // dummy
-  void     SetReadMode(MediaCacheStream::ReadMode aMode) override {}
-  // dummy
-  void     SetPlaybackRate(uint32_t aBytesPerSecond) override {}
-  // dummy
-  int64_t  Tell() override { return 0; }
-
-  // Any thread
-  void    Pin() override {}
-  void    Unpin() override {}
-
-  bool    IsSuspendedByCache() override { return mIsSuspend; }
-
-  bool    IsSuspended() override { return false; }
-  bool    IsTransportSeekable() override { return true; }
-  // dummy
-  double  GetDownloadRate(bool* aIsReliable) override { *aIsReliable = false; return 0; }
-
-  int64_t GetLength() override {
-    if (mIsLiveStream) {
-      return -1;
-    }
-    return 0;
-  }
-
-  // dummy
-  int64_t GetNextCachedData(int64_t aOffset) override { return 0; }
-  // dummy
-  int64_t GetCachedDataEnd(int64_t aOffset) override { return 0; }
-  // dummy
-  bool    IsDataCachedToEndOfResource(int64_t aOffset) override {
-    return false;
-  }
-  // dummy
-  nsresult GetCachedRanges(MediaByteRangeSet& aRanges) override {
-    return NS_ERROR_FAILURE;
-  }
-
-  // The following methods can be called on main thread only.
-
-  nsresult Open(nsIStreamListener** aStreamListener) override;
-  nsresult Close() override;
-  void     Suspend(bool aCloseImmediately) override;
-  void     Resume() override;
-  already_AddRefed<nsIPrincipal> GetCurrentPrincipal() override;
-  bool     CanClone() override {
-    return false;
-  }
-  already_AddRefed<MediaResource> CloneData(MediaResourceCallback*) override {
-    return nullptr;
-  }
-  // dummy
-  nsresult ReadFromCache(char* aBuffer, int64_t aOffset,
-                                 uint32_t aCount) override {
-    return NS_ERROR_FAILURE;
-  }
-
-  size_t SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const override;
-
-  size_t SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const override {
-    return aMallocSizeOf(this) + SizeOfExcludingThis(aMallocSizeOf);
-  }
-
-  // Listener implements nsIStreamingProtocolListener as
-  // mMediaStreamController's callback function.
-  // It holds RtspMediaResource reference to notify the connection status and
-  // data arrival. The Revoke function releases the reference when
-  // RtspMediaResource::OnDisconnected is called.
-  class Listener final : public nsIInterfaceRequestor,
-                         public nsIStreamingProtocolListener
-  {
-    ~Listener() {}
-  public:
-    explicit Listener(RtspMediaResource* aResource) : mResource(aResource) {}
-
-    NS_DECL_ISUPPORTS
-    NS_DECL_NSIINTERFACEREQUESTOR
-    NS_DECL_NSISTREAMINGPROTOCOLLISTENER
-
-    void Revoke();
-
-  private:
-    RefPtr<RtspMediaResource> mResource;
-  };
-  friend class Listener;
-
-protected:
-  // Main thread access only.
-  // These are called on the main thread by Listener.
-  nsresult OnMediaDataAvailable(uint8_t aIndex, const nsACString& aData,
-                                uint32_t aLength, uint32_t aOffset,
-                                nsIStreamingProtocolMetaData* aMeta);
-  nsresult OnConnected(uint8_t aIndex, nsIStreamingProtocolMetaData* aMeta);
-  nsresult OnDisconnected(uint8_t aIndex, nsresult aReason);
-
-  RefPtr<Listener> mListener;
-
-private:
-  // Notify mDecoder the rtsp stream is suspend. Main thread only.
-  void NotifySuspend(bool aIsSuspend);
-  bool IsVideoEnabled();
-  bool IsVideo(uint8_t tracks, nsIStreamingProtocolMetaData *meta);
-  // These two members are created at |RtspMediaResource::OnConnected|.
-  nsCOMPtr<nsIStreamingProtocolController> mMediaStreamController;
-  nsTArray<nsAutoPtr<RtspTrackBuffer>> mTrackBuffer;
-
-  // A flag that indicates the |RtspMediaResource::OnConnected| has already been
-  // called.
-  bool mIsConnected;
-  // Whether it's a live stream.
-  bool mIsLiveStream;
-  // Whether it provides timestamps.
-  bool mHasTimestamp;
-  // Indicate the rtsp controller is suspended or not. Main thread only.
-  bool mIsSuspend;
-};
-
-} // namespace mozilla
-
-#endif
-
--- a/dom/media/gtest/MockMediaDecoderOwner.h
+++ b/dom/media/gtest/MockMediaDecoderOwner.h
@@ -43,15 +43,14 @@ public:
   void DownloadSuspended() override {}
   void DownloadResumed(bool aForceNetworkLoading) override {}
   void NotifySuspendedByCache(bool aIsSuspended) override {}
   void NotifyDecoderPrincipalChanged() override {}
   VideoFrameContainer* GetVideoFrameContainer() override
   {
     return nullptr;
   }
-  void ResetConnectionState() override {}
   void SetAudibleState(bool aAudible) override {}
   void NotifyXPCOMShutdown() override {}
 };
 }
 
 #endif
--- a/dom/media/mediasink/VideoSink.cpp
+++ b/dom/media/mediasink/VideoSink.cpp
@@ -382,65 +382,63 @@ VideoSink::RenderVideoFrames(int32_t aMa
 }
 
 void
 VideoSink::UpdateRenderedVideoFrames()
 {
   AssertOwnerThread();
   MOZ_ASSERT(mAudioSink->IsPlaying(), "should be called while playing.");
 
+  // Get the current playback position.
   TimeStamp nowTime;
   const int64_t clockTime = mAudioSink->GetPosition(&nowTime);
-  // Skip frames up to the frame at the playback position, and figure out
-  // the time remaining until it's time to display the next frame and drop
-  // the current frame.
   NS_ASSERTION(clockTime >= 0, "Should have positive clock time.");
 
-  int64_t remainingTime = -1;
-  if (VideoQueue().GetSize() > 0) {
-    RefPtr<MediaData> currentFrame = VideoQueue().PopFront();
-    int32_t framesRemoved = 0;
-    while (VideoQueue().GetSize() > 0) {
-      RefPtr<MediaData> nextFrame = VideoQueue().PeekFront();
-      if (nextFrame->mTime > clockTime) {
-        remainingTime = nextFrame->mTime - clockTime;
-        break;
-      }
-      ++framesRemoved;
-      if (!currentFrame->As<VideoData>()->mSentToCompositor) {
-        mFrameStats.NotifyDecodedFrames({ 0, 0, 1 });
-        VSINK_LOG_V("discarding video frame mTime=%lld clock_time=%lld",
-                    currentFrame->mTime, clockTime);
-      }
-      currentFrame = VideoQueue().PopFront();
-    }
-    VideoQueue().PushFront(currentFrame);
-    if (framesRemoved > 0) {
-      mVideoFrameEndTime = currentFrame->GetEndTime();
+  // Skip frames up to the playback position.
+  int64_t lastDisplayedFrameEndTime = 0;
+  while (VideoQueue().GetSize() > 0 &&
+         clockTime > VideoQueue().PeekFront()->GetEndTime()) {
+    RefPtr<MediaData> frame = VideoQueue().PopFront();
+    if (frame->As<VideoData>()->mSentToCompositor) {
+      lastDisplayedFrameEndTime = frame->GetEndTime();
       mFrameStats.NotifyPresentedFrame();
+    } else {
+      mFrameStats.NotifyDecodedFrames({ 0, 0, 1 });
+      VSINK_LOG_V("discarding video frame mTime=%lld clock_time=%lld",
+                  frame->mTime, clockTime);
     }
   }
 
+  // The presentation end time of the last video frame displayed is either
+  // the end time of the current frame, or if we dropped all frames in the
+  // queue, the end time of the last frame we removed from the queue.
+  RefPtr<MediaData> currentFrame = VideoQueue().PeekFront();
+  mVideoFrameEndTime = currentFrame ? currentFrame->GetEndTime() : lastDisplayedFrameEndTime;
+
   // All frames are rendered, Let's resolve the promise.
   if (VideoQueue().IsFinished() &&
       VideoQueue().GetSize() <= 1 &&
       !mVideoSinkEndRequest.Exists()) {
     mEndPromiseHolder.ResolveIfExists(true, __func__);
   }
 
   RenderVideoFrames(mVideoQueueSendToCompositorSize, clockTime, nowTime);
 
-  // No next fame to render. There is no need to schedule next render
-  // loop. We will run render loops again upon incoming frames.
-  if (remainingTime < 0) {
+  // Get the timestamp of the next frame. Schedule the next update at
+  // the start time of the next frame. If we don't have a next frame,
+  // we will run render loops again upon incoming frames.
+  nsTArray<RefPtr<MediaData>> frames;
+  VideoQueue().GetFirstElements(2, &frames);
+  if (frames.Length() < 2) {
     return;
   }
 
+  int64_t nextFrameTime = frames[1]->mTime;
   TimeStamp target = nowTime + TimeDuration::FromMicroseconds(
-    remainingTime / mAudioSink->GetPlaybackParams().mPlaybackRate);
+    (nextFrameTime - clockTime) / mAudioSink->GetPlaybackParams().mPlaybackRate);
 
   RefPtr<VideoSink> self = this;
   mUpdateScheduler.Ensure(target, [self] () {
     self->UpdateRenderedVideoFramesByTimer();
   }, [self] () {
     self->UpdateRenderedVideoFramesByTimer();
   });
 }
--- a/dom/media/moz.build
+++ b/dom/media/moz.build
@@ -133,17 +133,16 @@ EXPORTS += [
     'MediaTrackList.h',
     'MP3Decoder.h',
     'MP3Demuxer.h',
     'MP3FrameParser.h',
     'NextFrameSeekTask.h',
     'nsIDocumentActivity.h',
     'PrincipalChangeObserver.h',
     'QueueObject.h',
-    'RtspMediaResource.h',
     'SeekJob.h',
     'SeekTarget.h',
     'SeekTask.h',
     'SelfRef.h',
     'SharedBuffer.h',
     'StreamTracks.h',
     'ThreadPoolCOMListener.h',
     'TimeUnits.h',
@@ -244,17 +243,16 @@ UNIFIED_SOURCES += [
     'MediaTimer.cpp',
     'MediaTrack.cpp',
     'MediaTrackList.cpp',
     'MP3Decoder.cpp',
     'MP3Demuxer.cpp',
     'MP3FrameParser.cpp',
     'NextFrameSeekTask.cpp',
     'QueueObject.cpp',
-    'RtspMediaResource.cpp',
     'SeekJob.cpp',
     'SeekTask.cpp',
     'StreamTracks.cpp',
     'TextTrack.cpp',
     'TextTrackCue.cpp',
     'TextTrackCueList.cpp',
     'TextTrackList.cpp',
     'TextTrackRegion.cpp',
deleted file mode 100644
--- a/dom/media/omx/RtspExtractor.cpp
+++ /dev/null
@@ -1,222 +0,0 @@
-/* -*- 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 "RtspExtractor.h"
-
-#include "mozilla/ReentrantMonitor.h"
-#include "nsAutoPtr.h"
-
-using namespace android;
-
-#define FRAME_DEFAULT_SIZE 1024
-
-namespace mozilla {
-
-/* class RtspMediaSource : implements MediaSource for OMX.
- * The decoder thread will trigger the MediaDecodeStateMachine to read a/v frame.
- * Then RtspOmxReader calls OMX decoder to decode a/v frame. Finally the code
- * path run into the read() here, it reads un-decoded frame data from mResource
- * and construct a MediaBuffer for output to OMX decoder.
- * */
-class RtspMediaSource final : public MediaSource {
-public:
-  RtspMediaSource(RtspMediaResource* aRtspMediaResource,
-                  ssize_t aTrackIdx,
-                  uint32_t aFrameMaxSize,
-                  const sp<MetaData>& aMeta)
-  : mRtspResource(aRtspMediaResource)
-  , mFormat(aMeta)
-  , mTrackIdx(aTrackIdx)
-  , mMonitor("RtspMediaSource.mMonitor")
-  , mIsStarted(false)
-  , mGroup(nullptr)
-  , mBuffer(nullptr)
-  , mFrameMaxSize(aFrameMaxSize) {}
-  virtual ~RtspMediaSource() {}
-  status_t start(MetaData* params = nullptr) override;
-  status_t stop() override;
-  sp<MetaData> getFormat() override {
-    ReentrantMonitorAutoEnter mon(mMonitor);
-    return mFormat;
-  }
-  status_t read(MediaBuffer** buffer,
-                const ReadOptions* options = nullptr) override ;
-private:
-  RefPtr<RtspMediaResource> mRtspResource;
-  sp<MetaData> mFormat;
-  uint32_t mTrackIdx;
-  ReentrantMonitor mMonitor;
-  bool mIsStarted;
-
-  // mGroup owns the mBuffer. mFrameMaxSize is the mBuffer size.
-  // mBuffer is the input buffer for omx decoder.
-  nsAutoPtr<MediaBufferGroup> mGroup;
-  MediaBuffer* mBuffer;
-  uint32_t mFrameMaxSize;
-};
-
-status_t
-RtspMediaSource::start(MetaData* params)
-{
-  ReentrantMonitorAutoEnter mon(mMonitor);
-  if (!mIsStarted) {
-    // RtspMediaSource relinquish the ownership of MediaBuffer |buf| to mGroup.
-    mGroup = new MediaBufferGroup();
-    MediaBuffer* buf = new MediaBuffer(mFrameMaxSize);
-    mGroup->add_buffer(buf);
-    mIsStarted = true;
-  }
-  return OK;
-}
-
-status_t
-RtspMediaSource::stop()
-{
-  ReentrantMonitorAutoEnter mon(mMonitor);
-  if (mIsStarted) {
-    if (mBuffer) {
-      mBuffer->release();
-      mBuffer = nullptr;
-    }
-    mGroup = nullptr;
-    mIsStarted = false;
-  }
-  return OK;
-}
-
-status_t
-RtspMediaSource::read(MediaBuffer** out, const ReadOptions* options)
-{
-  ReentrantMonitorAutoEnter mon(mMonitor);
-  NS_ENSURE_TRUE(mIsStarted, MEDIA_ERROR_BASE);
-  NS_ENSURE_TRUE(out, MEDIA_ERROR_BASE);
-  *out = nullptr;
-
-  // Video/audio track's initial frame size is FRAME_DEFAULT_SIZE.
-  // We need to realloc the mBuffer if the mBuffer doesn't have enough space
-  // for next ReadFrameFromTrack function. (actualFrameSize > mFrameMaxSize)
-  status_t err;
-  uint32_t readCount;
-  uint32_t actualFrameSize;
-  uint64_t time;
-  nsresult rv;
-
-  while (1) {
-    err = mGroup->acquire_buffer(&mBuffer);
-    NS_ENSURE_TRUE(err == OK, err);
-    rv = mRtspResource->ReadFrameFromTrack((uint8_t *)mBuffer->data(),
-                                           mFrameMaxSize, mTrackIdx, readCount,
-                                           time, actualFrameSize);
-    if (NS_FAILED(rv)) {
-      // Release mGroup and mBuffer.
-      stop();
-      // Since RtspMediaSource is an implementation of Android media source,
-      // it's held by OMXCodec and isn't released yet. So we have to re-construct
-      // mGroup and mBuffer.
-      start();
-      NS_WARNING("ReadFrameFromTrack failed; releasing buffers and returning.");
-      return ERROR_END_OF_STREAM;
-    }
-    if (actualFrameSize > mFrameMaxSize) {
-      // release mGroup and mBuffer
-      stop();
-      // re-construct mGroup and mBuffer
-      mFrameMaxSize = actualFrameSize;
-      err = start();
-      NS_ENSURE_TRUE(err == OK, err);
-    } else {
-      // ReadFrameFromTrack success, break the while loop.
-      break;
-    }
-  }
-  mBuffer->set_range(0, readCount);
-  if (NS_SUCCEEDED(rv)) {
-    mBuffer->meta_data()->clear();
-    // fill the meta data
-    mBuffer->meta_data()->setInt64(kKeyTime, time);
-    *out = mBuffer;
-    mBuffer = nullptr;
-    return OK;
-  }
-
-  return ERROR_END_OF_STREAM;
-}
-
-size_t
-RtspExtractor::countTracks()
-{
-  uint8_t tracks = 0;
-  if (mController) {
-    mController->GetTotalTracks(&tracks);
-  }
-  return size_t(tracks);
-}
-
-sp<MediaSource>
-RtspExtractor::getTrack(size_t index)
-{
-  NS_ENSURE_TRUE(index < countTracks(), nullptr);
-  sp<MetaData> meta = getTrackMetaData(index);
-  sp<MediaSource> source = new RtspMediaSource(mRtspResource,
-                                               index,
-                                               FRAME_DEFAULT_SIZE,
-                                               meta);
-  return source;
-}
-
-sp<MetaData>
-RtspExtractor::getTrackMetaData(size_t index, uint32_t flag)
-{
-  NS_ENSURE_TRUE(index < countTracks(), nullptr);
-  sp<MetaData> meta = new MetaData();
-  nsCOMPtr<nsIStreamingProtocolMetaData> rtspMetadata;
-  mController->GetTrackMetaData(index, getter_AddRefs(rtspMetadata));
-
-  if (rtspMetadata) {
-    // Convert msMeta into meta.
-    // The getter function of nsIStreamingProtocolMetaData will initialize the
-    // metadata values to 0 before setting them.
-    nsCString mime;
-    rtspMetadata->GetMimeType(mime);
-    meta->setCString(kKeyMIMEType, mime.get());
-    uint32_t temp32;
-    rtspMetadata->GetWidth(&temp32);
-    meta->setInt32(kKeyWidth, temp32);
-    rtspMetadata->GetHeight(&temp32);
-    meta->setInt32(kKeyHeight, temp32);
-    rtspMetadata->GetSampleRate(&temp32);
-    meta->setInt32(kKeySampleRate, temp32);
-    rtspMetadata->GetChannelCount(&temp32);
-    meta->setInt32(kKeyChannelCount, temp32);
-    uint64_t temp64;
-    rtspMetadata->GetDuration(&temp64);
-    meta->setInt64(kKeyDuration, temp64);
-
-    nsCString tempCString;
-    rtspMetadata->GetEsdsData(tempCString);
-    if (tempCString.Length()) {
-      meta->setData(kKeyESDS, 0, tempCString.get(), tempCString.Length());
-    }
-    rtspMetadata->GetAvccData(tempCString);
-    if (tempCString.Length()) {
-      meta->setData(kKeyAVCC, 0, tempCString.get(), tempCString.Length());
-    }
-  }
-  return meta;
-}
-
-uint32_t
-RtspExtractor::flags() const
-{
-  if (mRtspResource->IsRealTime()) {
-    return 0;
-  } else {
-    return MediaExtractor::CAN_SEEK;
-  }
-}
-
-} // namespace mozilla
-
deleted file mode 100644
--- a/dom/media/omx/RtspExtractor.h
+++ /dev/null
@@ -1,53 +0,0 @@
-/* -*- 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/. */
-
-#if !defined(RtspExtractor_h_)
-#define RtspExtractor_h_
-
-#include "RtspMediaResource.h"
-
-#include <stagefright/MediaBufferGroup.h>
-#include <stagefright/MediaExtractor.h>
-#include <stagefright/MediaSource.h>
-#include <stagefright/MetaData.h>
-
-namespace mozilla {
-
-// RtspExtractor is a custom extractor for Rtsp stream, whereas the other
-// XXXExtractors are made for container media content.
-// The extractor is used for |OmxDecoder::Init|, it provides the essential
-// information for creating OMXCodec instance.
-// For example, the |getTrackMetaData| returns metadata that includes the
-// codec type.
-class RtspExtractor: public android::MediaExtractor
-{
-public:
-  size_t countTracks() final override;
-  android::sp<android::MediaSource> getTrack(size_t index)
-    final override;
-  android::sp<android::MetaData> getTrackMetaData(
-    size_t index, uint32_t flag = 0) final override;
-  uint32_t flags() const final override;
-
-  RtspExtractor(RtspMediaResource* aResource)
-    : mRtspResource(aResource) {
-    MOZ_ASSERT(aResource);
-    mController = mRtspResource->GetMediaStreamController();
-    MOZ_ASSERT(mController);
-  }
-  virtual ~RtspExtractor() override {}
-private:
-  // mRtspResource is a pointer to RtspMediaResource. When |getTrack| is called
-  // we use mRtspResource to construct a RtspMediaSource.
-  RtspMediaResource* mRtspResource;
-  // Through the mController in mRtspResource, we can get the essential
-  // information for the extractor.
-  RefPtr<nsIStreamingProtocolController> mController;
-};
-
-} // namespace mozilla
-
-#endif
deleted file mode 100644
--- a/dom/media/omx/RtspOmxDecoder.cpp
+++ /dev/null
@@ -1,49 +0,0 @@
-/* -*- 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 "RtspMediaResource.h"
-#include "RtspOmxDecoder.h"
-#include "RtspOmxReader.h"
-#include "MediaDecoderStateMachine.h"
-
-namespace mozilla {
-
-MediaDecoder* RtspOmxDecoder::Clone(MediaDecoderOwner* aOwner)
-{
-  return new RtspOmxDecoder(aOwner);
-}
-
-MediaDecoderStateMachine*
-RtspOmxDecoder::CreateStateMachine()
-{
-  return new MediaDecoderStateMachine(this,
-                                      new RtspOmxReader(this),
-                                      mResource->IsRealTime());
-}
-
-void
-RtspOmxDecoder::ChangeState(PlayState aState)
-{
-  MOZ_ASSERT(NS_IsMainThread());
-
-  MediaDecoder::ChangeState(aState);
-
-  // Notify RTSP controller if the play state is ended.
-  // This is necessary for RTSP controller to transit its own state.
-  if (mPlayState == PLAY_STATE_ENDED) {
-    RefPtr<RtspMediaResource> resource = mResource->GetRtspPointer();
-    if (resource) {
-      nsIStreamingProtocolController* controller =
-        resource->GetMediaStreamController();
-      if (controller) {
-        controller->PlaybackEnded();
-      }
-    }
-  }
-}
-
-} // namespace mozilla
-
deleted file mode 100644
--- a/dom/media/omx/RtspOmxDecoder.h
+++ /dev/null
@@ -1,39 +0,0 @@
-/* -*- 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/. */
-#if !defined(RtspOmxDecoder_h_)
-#define RtspOmxDecoder_h_
-
-#include "base/basictypes.h"
-#include "MediaDecoder.h"
-
-namespace mozilla {
-
-/* RtspOmxDecoder is a subclass of MediaDecoder but not a subclass of
- * MediaOmxDecoder. Because the MediaOmxDecoder doesn't extend any functionality
- * for MediaDecoder.
- * It creates the RtspOmxReader for the MediaDecoderStateMachine and override
- * the ApplyStateToStateMachine to send rtsp play/pause command to rtsp server.
- *
- * */
-class RtspOmxDecoder : public MediaDecoder
-{
-public:
-  explicit RtspOmxDecoder(MediaDecoderOwner* aOwner) : MediaDecoder(aOwner) {
-    MOZ_COUNT_CTOR(RtspOmxDecoder);
-  }
-
-  ~RtspOmxDecoder() {
-    MOZ_COUNT_DTOR(RtspOmxDecoder);
-  }
-
-  MediaDecoder* Clone(MediaDecoderOwner* aOwner) override final;
-  MediaDecoderStateMachine* CreateStateMachine() override final;
-  void ChangeState(PlayState aState) override final;
-};
-
-} // namespace mozilla
-
-#endif
deleted file mode 100644
--- a/dom/media/omx/RtspOmxReader.cpp
+++ /dev/null
@@ -1,112 +0,0 @@
-/* -*- 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 "RtspOmxReader.h"
-
-#include "AbstractMediaDecoder.h"
-#include "MediaDecoderStateMachine.h"
-#include "OmxDecoder.h"
-#include "RtspExtractor.h"
-#include "RtspMediaResource.h"
-#include "RtspOmxDecoder.h"
-
-using namespace android;
-
-namespace mozilla {
-
-nsresult RtspOmxReader::InitOmxDecoder()
-{
-  if (!mOmxDecoder.get()) {
-    NS_ASSERTION(mDecoder, "RtspOmxReader mDecoder is null.");
-    NS_ASSERTION(mDecoder->GetResource(),
-                 "RtspOmxReader mDecoder->GetResource() is null.");
-    mExtractor = new RtspExtractor(mRtspResource);
-    mOmxDecoder = new OmxDecoder(mDecoder, OwnerThread());
-    if (!mOmxDecoder->Init(mExtractor)) {
-      return NS_ERROR_FAILURE;
-    }
-  }
-  return NS_OK;
-}
-
-RefPtr<MediaDecoderReader::SeekPromise>
-RtspOmxReader::Seek(SeekTarget aTarget, int64_t aEndTime)
-{
-  // The seek function of Rtsp is time-based, we call the SeekTime function in
-  // RtspMediaResource. The SeekTime function finally send a seek command to
-  // Rtsp stream server through network and also clear the buffer data in
-  // RtspMediaResource.
-  if (mRtspResource) {
-    mRtspResource->SeekTime(aTarget.GetTime().ToMicroseconds());
-    mRtspResource->EnablePlayoutDelay();
-  }
-
-  // Call |MediaOmxReader::Seek| to notify the OMX decoder we are performing a
-  // seek operation. The function will clear the |mVideoQueue| and |mAudioQueue|
-  // that store the decoded data and also call the |DecodeToTarget| to pass
-  // the seek time to OMX a/v decoders.
-  mEnsureActiveFromSeek = true;
-  return MediaOmxReader::Seek(aTarget, aEndTime);
-}
-
-void RtspOmxReader::SetIdle() {
-  // Call parent class to set OMXCodec idle.
-  MediaOmxReader::SetIdle();
-
-  // Need to pause RTSP streaming OMXCodec decoding.
-  if (mRtspResource) {
-    nsIStreamingProtocolController* controller =
-        mRtspResource->GetMediaStreamController();
-    if (controller) {
-      controller->Pause();
-    }
-    mRtspResource->SetSuspend(true);
-  }
-}
-
-void RtspOmxReader::EnsureActive() {
-  // Need to start RTSP streaming OMXCodec decoding.
-  if (mRtspResource) {
-    nsIStreamingProtocolController* controller =
-        mRtspResource->GetMediaStreamController();
-    // We do not have to call Play if the EnsureActive request is from Seek
-    // operation because RTSP connection must already be established before
-    // performing Seek.
-    if (controller && !mEnsureActiveFromSeek) {
-      controller->Play();
-    }
-    mEnsureActiveFromSeek = false;
-    mRtspResource->SetSuspend(false);
-  }
-
-  // Call parent class to set OMXCodec active.
-  MediaOmxReader::EnsureActive();
-}
-
-RefPtr<MediaDecoderReader::MetadataPromise>
-RtspOmxReader::AsyncReadMetadata()
-{
-  // Send a PLAY command to the RTSP server before reading metadata.
-  // Because we might need some decoded samples to ensure we have configuration.
-  mRtspResource->DisablePlayoutDelay();
-
-  RefPtr<MediaDecoderReader::MetadataPromise> p =
-    MediaOmxReader::AsyncReadMetadata();
-
-  // Send a PAUSE to the RTSP server because the underlying media resource is
-  // not ready.
-  SetIdle();
-
-  return p;
-}
-
-void RtspOmxReader::HandleResourceAllocated()
-{
-  MediaOmxReader::HandleResourceAllocated();
-  mRtspResource->EnablePlayoutDelay();
-}
-
-} // namespace mozilla
deleted file mode 100644
--- a/dom/media/omx/RtspOmxReader.h
+++ /dev/null
@@ -1,84 +0,0 @@
-/* -*- 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/. */
-#if !defined(RtspOmxReader_h_)
-#define RtspOmxReader_h_
-
-#include "MediaResource.h"
-#include "MediaDecoderReader.h"
-#include "MediaOmxReader.h"
-
-namespace mozilla {
-
-namespace dom {
-  class TimeRanges;
-}
-
-class AbstractMediaDecoder;
-class RtspMediaResource;
-
-/* RtspOmxReader is a subclass of MediaOmxReader.
- * The major reason that RtspOmxReader inherit from MediaOmxReader is the
- * same video/audio decoding logic we can reuse.
- */
-class RtspOmxReader : public MediaOmxReader
-{
-protected:
-  // Provide a Rtsp extractor.
-  nsresult InitOmxDecoder() final override;
-  void EnsureActive() override;
-
-public:
-  RtspOmxReader(AbstractMediaDecoder* aDecoder)
-    : MediaOmxReader(aDecoder)
-    , mEnsureActiveFromSeek(false)
-  {
-    MOZ_COUNT_CTOR(RtspOmxReader);
-    NS_ASSERTION(mDecoder, "RtspOmxReader mDecoder is null.");
-    NS_ASSERTION(mDecoder->GetResource(),
-                 "RtspOmxReader mDecoder->GetResource() is null.");
-    mRtspResource = mDecoder->GetResource()->GetRtspPointer();
-    MOZ_ASSERT(mRtspResource);
-  }
-
-  virtual ~RtspOmxReader() {
-    MOZ_COUNT_DTOR(RtspOmxReader);
-  }
-
-  // Implement a time-based seek instead of byte-based..
-  RefPtr<SeekPromise> Seek(SeekTarget aTarget, int64_t aEndTime) final override;
-
-  // Override GetBuffered() to do nothing for below reasons:
-  // 1. Because the Rtsp stream is a/v separated. The buffered data in a/v
-  // tracks are not consistent with time stamp.
-  // For example: audio buffer: 1~2s, video buffer: 1.5~2.5s
-  // 2. Since the Rtsp is a realtime streaming, the buffer we made for
-  // RtspMediaResource is quite small. The small buffer implies the time ranges
-  // we returned are not useful for the MediaDecodeStateMachine. Unlike the
-  // ChannelMediaResource, it has a "cache" that can store the whole streaming
-  // data so the |GetBuffered| function can retrieve useful time ranges.
-  media::TimeIntervals GetBuffered() final override {
-    return media::TimeIntervals::Invalid();
-  }
-
-  void SetIdle() override;
-
-  RefPtr<MediaDecoderReader::MetadataPromise> AsyncReadMetadata() override;
-
-  void HandleResourceAllocated() override;
-
-private:
-  // A pointer to RtspMediaResource for calling the Rtsp specific function.
-  // The lifetime of mRtspResource is controlled by MediaDecoder. MediaDecoder
-  // holds the MediaDecoderStateMachine and RtspMediaResource.
-  // And MediaDecoderStateMachine holds this RtspOmxReader.
-  RtspMediaResource* mRtspResource;
-
-  bool mEnsureActiveFromSeek;
-};
-
-} // namespace mozilla
-
-#endif
--- a/dom/media/test/mochitest.ini
+++ b/dom/media/test/mochitest.ini
@@ -602,17 +602,16 @@ skip-if = buildapp == 'mulet' || os == '
 skip-if = (os == 'mac' && os_version == '10.6') # bug 1021174
 [test_bug495300.html]
 [test_bug654550.html]
 [test_bug686942.html]
 [test_bug726904.html]
 [test_bug874897.html]
 [test_bug879717.html]
 tags=capturestream
-skip-if = os == 'win' && !debug # bug 1140675
 [test_bug883173.html]
 [test_bug895091.html]
 tags=webvtt
 [test_bug895305.html]
 [test_bug919265.html]
 [test_bug957847.html]
 [test_bug1018933.html]
 [test_bug1113600.html]
--- a/dom/plugins/test/mochitest/mochitest.ini
+++ b/dom/plugins/test/mochitest/mochitest.ini
@@ -1,10 +1,11 @@
 [DEFAULT]
 skip-if = ((buildapp == 'mulet' || buildapp == 'b2g') && toolkit != 'gonk') #b2g-desktop(tests that use plugins)
+subsuite = clipboard
 support-files =
   307-xo-redirect.sjs
   crashing_subpage.html
   file_authident.js
   file_bug738396.html
   file_bug771202.html
   file_bug863792.html
   file_bug1245545.js
--- a/dom/svg/nsSVGPathGeometryElement.cpp
+++ b/dom/svg/nsSVGPathGeometryElement.cpp
@@ -105,29 +105,27 @@ already_AddRefed<Path>
 nsSVGPathGeometryElement::GetOrBuildPathForMeasuring()
 {
   return nullptr;
 }
 
 FillRule
 nsSVGPathGeometryElement::GetFillRule()
 {
-  FillRule fillRule = FillRule::FILL_WINDING; // Equivalent to NS_STYLE_FILL_RULE_NONZERO
+  FillRule fillRule = FillRule::FILL_WINDING; // Equivalent to StyleFillRule::NonZero
 
   RefPtr<nsStyleContext> styleContext =
     nsComputedDOMStyle::GetStyleContextForElementNoFlush(this, nullptr,
                                                          nullptr);
   
   if (styleContext) {
-    MOZ_ASSERT(styleContext->StyleSVG()->mFillRule ==
-                                           NS_STYLE_FILL_RULE_NONZERO ||
-               styleContext->StyleSVG()->mFillRule ==
-                                           NS_STYLE_FILL_RULE_EVENODD);
+    MOZ_ASSERT(styleContext->StyleSVG()->mFillRule == StyleFillRule::NonZero ||
+               styleContext->StyleSVG()->mFillRule == StyleFillRule::EvenOdd);
 
-    if (styleContext->StyleSVG()->mFillRule == NS_STYLE_FILL_RULE_EVENODD) {
+    if (styleContext->StyleSVG()->mFillRule == StyleFillRule::EvenOdd) {
       fillRule = FillRule::FILL_EVEN_ODD;
     }
   } else {
     // ReportToConsole
     NS_WARNING("Couldn't get style context for content in GetFillRule");
   }
 
   return fillRule;
--- a/dom/svg/test/test_pathAnimInterpolation.xhtml
+++ b/dom/svg/test/test_pathAnimInterpolation.xhtml
@@ -188,17 +188,17 @@ function argumentNames(aType)
   return gArgumentNames[aType.toUpperCase()];
 }
 
 // Creates and returns a new element and sets some attributes on it.
 function newElement(aNamespaceURI, aLocalName, aAttributes)
 {
   var e = document.createElementNS(aNamespaceURI, aLocalName);
   if (aAttributes) {
-    for (let [name, value] in Iterator(aAttributes)) {
+    for (let [name, value] of Object.entries(aAttributes)) {
       e.setAttribute(name, value);
     }
   }
   return e;
 }
 
 // Creates and returns a new SVG element and sets some attributes on it.
 function newSVGElement(aLocalName, aAttributes)
@@ -267,17 +267,17 @@ function isValidInterpolation(aFromType,
 // Runs the test.
 function run()
 {
   for (let additive of [false, true]) {
     let indexOfExpectedArguments = additive ? 3 : 2;
 
     // Add subtests for each combination of prefix and suffix, and additive
     // or not.
-    for (let [typePair, suffixEntry] in Iterator(gSuffixes)) {
+    for (let [typePair, suffixEntry] of Object.entries(gSuffixes)) {
       let fromType = typePair[0],
           toType = typePair[1],
           fromArguments = suffixEntry[0],
           toArguments = suffixEntry[1],
           expectedArguments = suffixEntry[indexOfExpectedArguments];
 
       for (let prefixEntry of gPrefixes) {
         let [prefixLength, prefix] = prefixEntry;
--- a/dom/system/gonk/ril_worker.js
+++ b/dom/system/gonk/ril_worker.js
@@ -10442,18 +10442,17 @@ StkProactiveCmdHelperObject.prototype = 
     let nextActionList = [];
     for (let i = 0; i < length; i++) {
       nextActionList.push(GsmPDUHelper.readHexOctet());
     }
     return nextActionList;
   },
 
   searchForTag: function(tag, ctlvs) {
-    let iter = Iterator(ctlvs);
-    for (let [index, ctlv] in iter) {
+    for (let ctlv of ctlvs) {
       if ((ctlv.tag & ~COMPREHENSIONTLV_FLAG_CR) == tag) {
         return ctlv;
       }
     }
     return null;
   },
 
   searchForSelectedTags: function(ctlvs, tags) {
@@ -11050,17 +11049,17 @@ BerTlvHelperObject.prototype = {
    */
   retrieveFileIdentifier: function(length) {
     let GsmPDUHelper = this.context.GsmPDUHelper;
     return {fileId : (GsmPDUHelper.readHexOctet() << 8) +
                       GsmPDUHelper.readHexOctet()};
   },
 
   searchForNextTag: function(tag, iter) {
-    for (let [index, tlv] in iter) {
+    for (let tlv of iter) {
       if (tlv.tag === tag) {
         return tlv;
       }
     }
     return null;
   }
 };
 BerTlvHelperObject.prototype[BER_FCP_TEMPLATE_TAG] = function BER_FCP_TEMPLATE_TAG(length) {
@@ -11401,17 +11400,17 @@ ICCIOHelperObject.prototype = {
   /**
    * Helper function for processing USIM get response.
    */
   processUSimGetResponse: function(options, octetLen) {
     let BerTlvHelper = this.context.BerTlvHelper;
 
     let berTlv = BerTlvHelper.decode(octetLen);
     // See TS 102 221 Table 11.4 for the content order of getResponse.
-    let iter = Iterator(berTlv.value);
+    let iter = berTlv.value.values();
     let tlv = BerTlvHelper.searchForNextTag(BER_FCP_FILE_DESCRIPTOR_TAG,
                                             iter);
     if (!tlv ||
         (tlv.value.fileStructure !== UICC_EF_STRUCTURE[options.structure])) {
       throw new Error("Expected EF structure " +
                       UICC_EF_STRUCTURE[options.structure] +
                       " but read " + tlv.value.fileStructure);
     }
--- a/dom/system/gonk/tests/test_ril_worker_icc_BerTlvHelper.js
+++ b/dom/system/gonk/tests/test_ril_worker_icc_BerTlvHelper.js
@@ -28,17 +28,17 @@ add_test(function test_fcp_template_for_
     0x80, 0x02, 0x00, 0x0A,
     0x88, 0x01, 0x10];
 
   for (let i = 0; i < tag_test.length; i++) {
     pduHelper.writeHexOctet(tag_test[i]);
   }
 
   let berTlv = berHelper.decode(tag_test.length);
-  let iter = Iterator(berTlv.value);
+  let iter = berTlv.value.values();
   let tlv = berHelper.searchForNextTag(BER_FCP_FILE_DESCRIPTOR_TAG, iter);
   equal(tlv.value.fileStructure, UICC_EF_STRUCTURE[EF_STRUCTURE_TRANSPARENT]);
 
   tlv = berHelper.searchForNextTag(BER_FCP_FILE_IDENTIFIER_TAG, iter);
   equal(tlv.value.fileId, 0x2FE2);
 
   tlv = berHelper.searchForNextTag(BER_FCP_FILE_SIZE_DATA_TAG, iter);
   equal(tlv.value.fileSizeData, 0x0A);
@@ -66,17 +66,17 @@ add_test(function test_fcp_template_for_
     0x80, 0x02, 0x00, 0x1A,
     0x88, 0x00];
 
   for (let i = 0; i < tag_test.length; i++) {
     pduHelper.writeHexOctet(tag_test[i]);
   }
 
   let berTlv = berHelper.decode(tag_test.length);
-  let iter = Iterator(berTlv.value);
+  let iter = berTlv.value.values();
   let tlv = berHelper.searchForNextTag(BER_FCP_FILE_DESCRIPTOR_TAG, iter);
   equal(tlv.value.fileStructure, UICC_EF_STRUCTURE[EF_STRUCTURE_LINEAR_FIXED]);
   equal(tlv.value.recordLength, 0x1A);
   equal(tlv.value.numOfRecords, 0x01);
 
   tlv = berHelper.searchForNextTag(BER_FCP_FILE_IDENTIFIER_TAG, iter);
   equal(tlv.value.fileId, 0x6F40);
 
--- a/dom/tests/mochitest/general/mochitest.ini
+++ b/dom/tests/mochitest/general/mochitest.ini
@@ -122,16 +122,17 @@ skip-if = buildapp == 'b2g' || buildapp 
 [test_bug1012662_editor.html]
 skip-if = (toolkit == 'android') # Disabled on Android, see bug 1230231
 subsuite = clipboard
 [test_bug1012662_noeditor.html]
 subsuite = clipboard
 skip-if = (toolkit == 'android') # Disabled on Android, see bug 1230231
 [test_bug1161721.html]
 [test_bug1170911.html]
+subsuite = clipboard
 [test_storagePermissionsAccept.html]
 skip-if = buildapp == 'b2g' # Bug 1184427 - no SSL certs on b2g
 [test_storagePermissionsRejectForeign.html]
 skip-if = buildapp == 'b2g' # Bug 1184427 - no SSL certs on b2g
 [test_storagePermissionsReject.html]
 skip-if = buildapp == 'b2g' # Bug 1184427 - no SSL certs on b2g
 [test_storagePermissionsLimitForeign.html]
 skip-if = buildapp == 'b2g' # Bug 1184427 - no SSL certs on b2g
--- a/editor/libeditor/tests/mochitest.ini
+++ b/editor/libeditor/tests/mochitest.ini
@@ -113,16 +113,17 @@ skip-if = toolkit == 'android' #bug 9577
 [test_bug629845.html]
 [test_bug638596.html]
 [test_bug640321.html]
 skip-if = android_version == '18' # bug 1147989
 [test_bug641466.html]
 [test_bug645914.html]
 [test_bug668599.html]
 [test_bug674770-1.html]
+subsuite = clipboard
 skip-if = toolkit == 'android' || (os == 'linux' && e10s && (debug||asan)) # Bug 972110
 [test_bug674770-2.html]
 subsuite = clipboard
 skip-if = toolkit == 'android'
 [test_bug674861.html]
 [test_bug676401.html]
 [test_bug677752.html]
 [test_bug681229.html]
--- a/gfx/2d/ScaledFontBase.cpp
+++ b/gfx/2d/ScaledFontBase.cpp
@@ -221,17 +221,17 @@ ScaledFontBase::GetGlyphDesignMetrics(co
 
       if (cairo_font_options_get_antialias(options) != CAIRO_ANTIALIAS_NONE) {
         if (cairo_scaled_font_get_type(mScaledFont) == CAIRO_FONT_TYPE_WIN32) {
           if (aGlyphMetrics[i].mWidth > 0 && aGlyphMetrics[i].mHeight > 0) {
             aGlyphMetrics[i].mWidth -= 3.0f;
             aGlyphMetrics[i].mXBearing += 1.0f;
           }
         }
-#ifdef MOZ2D_HAS_MOZ_CAIRO
+#if defined(MOZ2D_HAS_MOZ_CAIRO) && defined(CAIRO_HAS_DWRITE_FONT)
         else if (cairo_scaled_font_get_type(mScaledFont) == CAIRO_FONT_TYPE_DWRITE) {
           if (aGlyphMetrics[i].mWidth > 0 && aGlyphMetrics[i].mHeight > 0) {
             aGlyphMetrics[i].mWidth -= 2.0f;
             aGlyphMetrics[i].mXBearing += 1.0f;
           }
         }
 #endif
       }
--- a/js/src/asmjs/WasmFrameIterator.cpp
+++ b/js/src/asmjs/WasmFrameIterator.cpp
@@ -45,39 +45,42 @@ CallerFPFromFP(void* fp)
 }
 
 FrameIterator::FrameIterator()
   : activation_(nullptr),
     code_(nullptr),
     callsite_(nullptr),
     codeRange_(nullptr),
     fp_(nullptr),
+    pc_(nullptr),
     missingFrameMessage_(false)
 {
     MOZ_ASSERT(done());
 }
 
 FrameIterator::FrameIterator(const WasmActivation& activation)
   : activation_(&activation),
     code_(nullptr),
     callsite_(nullptr),
     codeRange_(nullptr),
     fp_(activation.fp()),
+    pc_(nullptr),
     missingFrameMessage_(false)
 {
     if (fp_) {
         settle();
         return;
     }
 
     void* pc = activation.resumePC();
     if (!pc) {
         MOZ_ASSERT(done());
         return;
     }
+    pc_ = (uint8_t*)pc;
 
     code_ = activation_->compartment()->wasm.lookupCode(pc);
     MOZ_ASSERT(code_);
 
     const CodeRange* codeRange = code_->lookupRange(pc);
     MOZ_ASSERT(codeRange);
 
     if (codeRange->kind() == CodeRange::Function)
@@ -121,21 +124,23 @@ FrameIterator::settle()
     code_ = activation_->compartment()->wasm.lookupCode(returnAddress);
     MOZ_ASSERT(code_);
 
     codeRange_ = code_->lookupRange(returnAddress);
     MOZ_ASSERT(codeRange_);
 
     switch (codeRange_->kind()) {
       case CodeRange::Function:
+        pc_ = (uint8_t*)returnAddress;
         callsite_ = code_->lookupCallSite(returnAddress);
         MOZ_ASSERT(callsite_);
         break;
       case CodeRange::Entry:
         fp_ = nullptr;
+        pc_ = nullptr;
         code_ = nullptr;
         codeRange_ = nullptr;
         MOZ_ASSERT(done());
         break;
       case CodeRange::ImportJitExit:
       case CodeRange::ImportInterpExit:
       case CodeRange::Inline:
       case CodeRange::CallThunk:
--- a/js/src/asmjs/WasmFrameIterator.h
+++ b/js/src/asmjs/WasmFrameIterator.h
@@ -50,30 +50,33 @@ struct ProfilingOffsets;
 // function stack frame.
 class FrameIterator
 {
     const WasmActivation* activation_;
     const Code* code_;
     const CallSite* callsite_;
     const CodeRange* codeRange_;
     uint8_t* fp_;
+    uint8_t* pc_;
     bool missingFrameMessage_;
 
     void settle();
 
   public:
     explicit FrameIterator();
     explicit FrameIterator(const WasmActivation& activation);
     void operator++();
     bool done() const;
     const char* filename() const;
     const char16_t* displayURL() const;
     bool mutedErrors() const;
     JSAtom* functionDisplayAtom() const;
     unsigned lineOrBytecode() const;
+    inline void* fp() const { return fp_; }
+    inline uint8_t* pc() const { return pc_; }
 };
 
 // An ExitReason describes the possible reasons for leaving compiled wasm code
 // or the state of not having left compiled wasm code (ExitReason::None).
 enum class ExitReason : uint32_t
 {
     None,          // default state, the pc is in wasm code
     ImportJit,     // fast-path call directly into JIT code
--- a/js/src/builtin/TestingFunctions.cpp
+++ b/js/src/builtin/TestingFunctions.cpp
@@ -468,17 +468,17 @@ GCParameter(JSContext* cx, unsigned argc
 
 static void
 SetAllowRelazification(JSContext* cx, bool allow)
 {
     JSRuntime* rt = cx->runtime();
     MOZ_ASSERT(rt->allowRelazificationForTesting != allow);
     rt->allowRelazificationForTesting = allow;
 
-    for (AllFramesIter i(cx); !i.done(); ++i)
+    for (AllScriptFramesIter i(cx); !i.done(); ++i)
         i.script()->setDoNotRelazify(allow);
 }
 
 static bool
 RelazifyFunctions(JSContext* cx, unsigned argc, Value* vp)
 {
     // Relazifying functions on GC is usually only done for compartments that are
     // not active. To aid fuzzing, this testing function allows us to relazify
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/wasm/backtrace.js
@@ -0,0 +1,23 @@
+// |jit-test| test-also-wasm-baseline
+load(libdir + "wasm.js");
+
+var code = `(module
+  (import $i "env" "test")
+  (func $t (call_import $i))
+  (export "test" $t)
+)`;
+var mod = wasmEvalText(code, {
+  env: {
+    test: function() {
+       // Expecting 3 lines in the backtrace (plus last empty).
+       // The middle one is for the wasm function.
+       var s = getBacktrace();
+       assertEq(s.split('\n').length, 4);
+       assertEq(s.split('\n')[1].startsWith("1 wasm-function[0]("), true);
+
+       // Let's also run DumpBacktrace() to check if we are not crashing.
+       backtrace();
+    }
+  }
+});
+mod.test();
\ No newline at end of file
--- a/js/src/jsfriendapi.cpp
+++ b/js/src/jsfriendapi.cpp
@@ -772,17 +772,17 @@ sprintf_append(JSContext* cx, char* buf,
         ReportOutOfMemory(cx);
         return nullptr;
     }
 
     return result;
 }
 
 static char*
-FormatFrame(JSContext* cx, const ScriptFrameIter& iter, char* buf, int num,
+FormatFrame(JSContext* cx, const FrameIter& iter, char* buf, int num,
             bool showArgs, bool showLocals, bool showThisProps)
 {
     MOZ_ASSERT(!cx->isExceptionPending());
     RootedScript script(cx, iter.script());
     jsbytecode* pc = iter.pc();
 
     RootedObject scopeChain(cx, iter.scopeChain(cx));
     JSAutoCompartment ac(cx, scopeChain);
@@ -976,23 +976,49 @@ FormatFrame(JSContext* cx, const ScriptF
                 return nullptr;
         }
     }
 
     MOZ_ASSERT(!cx->isExceptionPending());
     return buf;
 }
 
+static char*
+FormatWasmFrame(JSContext* cx, const FrameIter& iter, char* buf, int num, bool showArgs)
+{
+    JSAtom* functionDisplayAtom = iter.functionDisplayAtom();
+    UniqueChars nameStr;
+    if (functionDisplayAtom)
+        nameStr = StringToNewUTF8CharsZ(cx, *functionDisplayAtom);
+
+    buf = sprintf_append(cx, buf, "%d %s()",
+                         num,
+                         nameStr ? nameStr.get() : "<wasm-function>");
+    if (!buf)
+        return nullptr;
+    const char* filename = iter.filename();
+    uint32_t lineno = iter.computeLine();
+    buf = sprintf_append(cx, buf, " [\"%s\":%d]\n",
+                         filename ? filename : "<unknown>",
+                         lineno);
+
+    MOZ_ASSERT(!cx->isExceptionPending());
+    return buf;
+}
+
 JS_FRIEND_API(char*)
 JS::FormatStackDump(JSContext* cx, char* buf, bool showArgs, bool showLocals, bool showThisProps)
 {
     int num = 0;
 
     for (AllFramesIter i(cx); !i.done(); ++i) {
-        buf = FormatFrame(cx, i, buf, num, showArgs, showLocals, showThisProps);
+        if (i.hasScript())
+            buf = FormatFrame(cx, i, buf, num, showArgs, showLocals, showThisProps);
+        else
+            buf = FormatWasmFrame(cx, i, buf, num, showArgs);
         if (!buf)
             return nullptr;
         num++;
     }
 
     if (!num)
         buf = JS_sprintf_append(buf, "JavaScript stack is empty\n");
 
--- a/js/src/jsobj.cpp
+++ b/js/src/jsobj.cpp
@@ -3635,29 +3635,41 @@ js::DumpBacktrace(JSContext* cx, FILE* f
 {
     Sprinter sprinter(cx, false);
     if (!sprinter.init()) {
         fprintf(fp, "js::DumpBacktrace: OOM\n");
         return;
     }
     size_t depth = 0;
     for (AllFramesIter i(cx); !i.done(); ++i, ++depth) {
-        const char* filename = JS_GetScriptFilename(i.script());
-        unsigned line = PCToLineNumber(i.script(), i.pc());
-        JSScript* script = i.script();
+        const char* filename;
+        unsigned line;
+        if (i.hasScript()) {
+            filename = JS_GetScriptFilename(i.script());
+            line = PCToLineNumber(i.script(), i.pc());
+        } else {
+            filename = i.filename();
+            line = i.computeLine();
+        }
         char frameType =
             i.isInterp() ? 'i' :
             i.isBaseline() ? 'b' :
             i.isIon() ? 'I' :
             i.isWasm() ? 'W' :
             '?';
 
-        sprinter.printf("#%d %14p %c   %s:%d (%p @ %d)\n",
-                        depth, i.rawFramePtr(), frameType, filename, line,
-                        script, script->pcToOffset(i.pc()));
+        sprinter.printf("#%d %14p %c   %s:%d",
+                        depth, i.rawFramePtr(), frameType, filename, line);
+
+        if (i.hasScript()) {
+            sprinter.printf(" (%p @ %d)\n",
+                            i.script(), i.script()->pcToOffset(i.pc()));
+        } else {
+            sprinter.printf(" (%p)\n", i.pc());
+        }
     }
     fprintf(fp, "%s", sprinter.string());
 #ifdef XP_WIN32
     if (IsDebuggerPresent())
         OutputDebugStringA(sprinter.string());
 #endif
 }
 
--- a/js/src/jsscript.cpp
+++ b/js/src/jsscript.cpp
@@ -4133,17 +4133,17 @@ JSScript::argumentsOptimizationFailed(JS
      * MagicValue(JS_OPTIMIZED_ARGUMENTS) on the stack. However, there are
      * three things that need fixup:
      *  - there may be any number of activations of this script that don't have
      *    an argsObj that now need one.
      *  - jit code compiled (and possible active on the stack) with the static
      *    assumption of !script->needsArgsObj();
      *  - type inference data for the script assuming script->needsArgsObj
      */
-    for (AllFramesIter i(cx); !i.done(); ++i) {
+    for (AllScriptFramesIter i(cx); !i.done(); ++i) {
         /*
          * We cannot reliably create an arguments object for Ion activations of
          * this script.  To maintain the invariant that "script->needsArgsObj
          * implies fp->hasArgsObj", the Ion bail mechanism will create an
          * arguments object right after restoring the BaselineFrame and before
          * entering Baseline code (in jit::FinishBailoutToBaseline).
          */
         if (i.isIon())
--- a/js/src/vm/Debugger.cpp
+++ b/js/src/vm/Debugger.cpp
@@ -3623,18 +3623,18 @@ Debugger::getDebuggees(JSContext* cx, un
     return true;
 }
 
 /* static */ bool
 Debugger::getNewestFrame(JSContext* cx, unsigned argc, Value* vp)
 {
     THIS_DEBUGGER(cx, argc, vp, "getNewestFrame", args, dbg);
 
-    /* Since there may be multiple contexts, use AllFramesIter. */
-    for (AllFramesIter i(cx); !i.done(); ++i) {
+    /* Since there may be multiple contexts, use AllScriptFramesIter. */
+    for (AllScriptFramesIter i(cx); !i.done(); ++i) {
         if (dbg->observesFrame(i)) {
             // Ensure that Ion frames are rematerialized. Only rematerialized
             // Ion frames may be used as AbstractFramePtrs.
             if (i.isIon() && !i.ensureHasRematerializedFrame(cx))
                 return false;
             AbstractFramePtr frame = i.abstractFramePtr();
             ScriptFrameIter iter(i.activation()->cx());
             while (!iter.hasUsableAbstractFramePtr() || iter.abstractFramePtr() != frame)
@@ -6101,24 +6101,26 @@ DebuggerScript_getLineOffsets(JSContext*
 
 bool
 Debugger::observesFrame(AbstractFramePtr frame) const
 {
     return observesScript(frame.script());
 }
 
 bool
-Debugger::observesFrame(const ScriptFrameIter& iter) const
+Debugger::observesFrame(const FrameIter& iter) const
 {
     // Skip frames not yet fully initialized during their prologue.
     if (iter.isInterp() && iter.isFunctionFrame()) {
         const Value& thisVal = iter.interpFrame()->thisArgument();
         if (thisVal.isMagic() && thisVal.whyMagic() == JS_IS_CONSTRUCTING)
             return false;;
     }
+    if (iter.isWasm())
+        return false;
     return observesScript(iter.script());
 }
 
 bool
 Debugger::observesScript(JSScript* script) const
 {
     if (!enabled)
         return false;
--- a/js/src/vm/Debugger.h
+++ b/js/src/vm/Debugger.h
@@ -895,17 +895,17 @@ class Debugger : private mozilla::Linked
 
     /************************************* Functions for use by Debugger.cpp. */
 
     inline bool observesEnterFrame() const;
     inline bool observesNewScript() const;
     inline bool observesNewGlobalObject() const;
     inline bool observesGlobal(GlobalObject* global) const;
     bool observesFrame(AbstractFramePtr frame) const;
-    bool observesFrame(const ScriptFrameIter& iter) const;
+    bool observesFrame(const FrameIter& iter) const;
     bool observesScript(JSScript* script) const;
 
     /*
      * If env is nullptr, call vp->setNull() and return true. Otherwise, find
      * or create a Debugger.Environment object for the given Env. On success,
      * store the Environment object in *vp and return true.
      */
     MOZ_MUST_USE bool wrapEnvironment(JSContext* cx, Handle<Env*> env, MutableHandleValue vp);
--- a/js/src/vm/Stack.cpp
+++ b/js/src/vm/Stack.cpp
@@ -553,16 +553,17 @@ FrameIter::settleOnActivation()
         if (activation->isWasm()) {
             data_.wasmFrames_ = wasm::FrameIterator(*data_.activations_->asWasm());
 
             if (data_.wasmFrames_.done()) {
                 ++data_.activations_;
                 continue;
             }
 
+            data_.pc_ = (jsbytecode*)data_.wasmFrames_.pc();
             data_.state_ = WASM;
             return;
         }
 
         MOZ_ASSERT(activation->isInterpreter());
 
         InterpreterActivation* interpAct = activation->asInterpreter();
         data_.interpFrames_ = InterpreterFrameIterator(interpAct);
@@ -685,16 +686,17 @@ FrameIter::popJitFrame()
 }
 
 void
 FrameIter::popWasmFrame()
 {
     MOZ_ASSERT(data_.state_ == WASM);
 
     ++data_.wasmFrames_;
+    data_.pc_ = (jsbytecode*)data_.wasmFrames_.pc();
     if (data_.wasmFrames_.done())
         popActivation();
 }
 
 FrameIter&
 FrameIter::operator++()
 {
     switch (data_.state_) {
@@ -752,22 +754,23 @@ FrameIter::copyDataAsAbstractFramePtr() 
     return frame;
 }
 
 void*
 FrameIter::rawFramePtr() const
 {
     switch (data_.state_) {
       case DONE:
-      case WASM:
         return nullptr;
       case JIT:
         return data_.jitFrames_.fp();
       case INTERP:
         return interpFrame();
+      case WASM:
+        return data_.wasmFrames_.fp();
     }
     MOZ_CRASH("Unexpected state");
 }
 
 JSCompartment*
 FrameIter::compartment() const
 {
     switch (data_.state_) {
@@ -989,17 +992,16 @@ FrameIter::abstractFramePtr() const
     MOZ_CRASH("Unexpected state");
 }
 
 void
 FrameIter::updatePcQuadratic()
 {
     switch (data_.state_) {
       case DONE:
-      case WASM:
         break;
       case INTERP: {
         InterpreterFrame* frame = interpFrame();
         InterpreterActivation* activation = data_.activations_->asInterpreter();
 
         // Look for the current frame.
         data_.interpFrames_ = InterpreterFrameIterator(activation);
         while (data_.interpFrames_.frame() != frame)
@@ -1027,16 +1029,20 @@ FrameIter::updatePcQuadratic()
                 ++data_.jitFrames_;
 
             // Update the pc.
             MOZ_ASSERT(data_.jitFrames_.baselineFrame() == frame);
             data_.jitFrames_.baselineScriptAndPc(nullptr, &data_.pc_);
             return;
         }
         break;
+      case WASM:
+        // Update the pc.
+        data_.pc_ = (jsbytecode*)data_.wasmFrames_.pc();
+        break;
     }
     MOZ_CRASH("Unexpected state");
 }
 
 JSFunction*
 FrameIter::calleeTemplate() const
 {
     switch (data_.state_) {
--- a/js/src/vm/Stack.h
+++ b/js/src/vm/Stack.h
@@ -1994,20 +1994,31 @@ class NonBuiltinScriptFrameIter : public
         return *this;
     }
 };
 
 /*
  * Blindly iterate over all frames in the current thread's stack. These frames
  * can be from different contexts and compartments, so beware.
  */
-class AllFramesIter : public ScriptFrameIter
+class AllFramesIter : public FrameIter
 {
   public:
     explicit AllFramesIter(JSContext* cx)
+      : FrameIter(cx, ScriptFrameIter::IGNORE_DEBUGGER_EVAL_PREV_LINK)
+    {}
+};
+
+/* Iterates over all script frame in the current thread's stack.
+ * See also AllFramesIter and ScriptFrameIter.
+ */
+class AllScriptFramesIter : public ScriptFrameIter
+{
+  public:
+    explicit AllScriptFramesIter(JSContext* cx)
       : ScriptFrameIter(cx, ScriptFrameIter::IGNORE_DEBUGGER_EVAL_PREV_LINK)
     {}
 };
 
 /* Popular inline definitions. */
 
 inline JSScript*
 FrameIter::script() const
--- a/layout/base/RestyleManagerBase.cpp
+++ b/layout/base/RestyleManagerBase.cpp
@@ -148,18 +148,20 @@ RestyleManagerBase::ChangeHintToString(n
     "UpdateCursor", "UpdateEffects", "UpdateOpacityLayer",
     "UpdateTransformLayer", "ReconstructFrame", "UpdateOverflow",
     "UpdateSubtreeOverflow", "UpdatePostTransformOverflow",
     "UpdateParentOverflow",
     "ChildrenOnlyTransform", "RecomputePosition", "AddOrRemoveTransform",
     "BorderStyleNoneChange", "UpdateTextPath", "SchedulePaint",
     "NeutralChange", "InvalidateRenderingObservers",
     "ReflowChangesSizeOrPosition", "UpdateComputedBSize",
-    "UpdateUsesOpacity"
+    "UpdateUsesOpacity", "UpdateBackgroundPosition"
   };
+  static_assert(nsChangeHint_AllHints == (1 << ArrayLength(names)) - 1,
+                "Name list doesn't match change hints.");
   uint32_t hint = aHint & ((1 << ArrayLength(names)) - 1);
   uint32_t rest = aHint & ~((1 << ArrayLength(names)) - 1);
   if (hint == nsChangeHint_Hints_NotHandledForDescendants) {
     result.AppendLiteral("nsChangeHint_Hints_NotHandledForDescendants");
     hint = 0;
     any = true;
   } else {
     if ((hint & NS_STYLE_HINT_REFLOW) == NS_STYLE_HINT_REFLOW) {
--- a/layout/base/ServoRestyleManager.cpp
+++ b/layout/base/ServoRestyleManager.cpp
@@ -71,17 +71,17 @@ ServoRestyleManager::RecreateStyleContex
   if (!primaryFrame && !aContent->IsDirtyForServo()) {
     // This happens when, for example, a display: none child of a
     // HAS_DIRTY_DESCENDANTS content is reached as part of the traversal.
     return;
   }
 
   if (aContent->IsDirtyForServo()) {
     RefPtr<ServoComputedValues> computedValues =
-      dont_AddRef(Servo_GetComputedValues(aContent));
+      Servo_GetComputedValues(aContent).Consume();
     MOZ_ASSERT(computedValues);
 
     // NB: Change hint processing only applies to elements, at least until we
     // support display: contents.
     if (aContent->IsElement()) {
       nsChangeHint changeHint = nsChangeHint(0);
       Element* element = aContent->AsElement();
 
--- a/layout/base/nsChangeHint.h
+++ b/layout/base/nsChangeHint.h
@@ -201,19 +201,25 @@ enum nsChangeHint {
    * Indicates that the 'background-position' property changed.
    * Regular frames can invalidate these changes using DLBI, but
    * for some frame types we need to repaint the whole frame because
    * the frame does not build individual background image display items
    * for each background layer.
    */
   nsChangeHint_UpdateBackgroundPosition = 1 << 26,
 
-  // IMPORTANT NOTE: When adding new hints, consider whether you need to
-  // add them to NS_HintsNotHandledForDescendantsIn() below.  Please also
-  // add them to RestyleManager::ChangeHintToString.
+  // IMPORTANT NOTE: When adding new hints, consider whether you need
+  // to add them to NS_HintsNotHandledForDescendantsIn() below. Please
+  // also add them to RestyleManager::ChangeHintToString and modify
+  // nsChangeHint_AllHints below accordingly.
+
+  /**
+   * Dummy hint value for all hints. It exists for compile time check.
+   */
+  nsChangeHint_AllHints = (1 << 27) - 1,
 };
 
 // Redefine these operators to return nothing. This will catch any use
 // of these operators on hints. We should not be using these operators
 // on nsChangeHints
 inline void operator<(nsChangeHint s1, nsChangeHint s2) {}
 inline void operator>(nsChangeHint s1, nsChangeHint s2) {}
 inline void operator!=(nsChangeHint s1, nsChangeHint s2) {}
--- a/layout/generic/BlockReflowInput.cpp
+++ b/layout/generic/BlockReflowInput.cpp
@@ -39,30 +39,29 @@ BlockReflowInput::BlockReflowInput(const
     mPresContext(aPresContext),
     mReflowInput(aReflowInput),
     mContentArea(aReflowInput.GetWritingMode()),
     mPushedFloats(nullptr),
     mOverflowTracker(nullptr),
     mBorderPadding(mReflowInput.ComputedLogicalBorderPadding()),
     mPrevBEndMargin(),
     mLineNumber(0),
-    mFlags(0),
     mFloatBreakType(NS_STYLE_CLEAR_NONE),
     mConsumedBSize(aConsumedBSize)
 {
   if (!sFloatFragmentsInsideColumnPrefCached) {
     sFloatFragmentsInsideColumnPrefCached = true;
     Preferences::AddBoolVarCache(&sFloatFragmentsInsideColumnEnabled,
                                  "layout.float-fragments-inside-column.enabled");
   }
-  SetFlag(BRS_FLOAT_FRAGMENTS_INSIDE_COLUMN_ENABLED, sFloatFragmentsInsideColumnEnabled);
-  
+  mFlags.mFloatFragmentsInsideColumnEnabled = sFloatFragmentsInsideColumnEnabled;
+
   WritingMode wm = aReflowInput.GetWritingMode();
-  SetFlag(BRS_ISFIRSTINFLOW, aFrame->GetPrevInFlow() == nullptr);
-  SetFlag(BRS_ISOVERFLOWCONTAINER, IS_TRUE_OVERFLOW_CONTAINER(aFrame));
+  mFlags.mIsFirstInflow = !aFrame->GetPrevInFlow();
+  mFlags.mIsOverflowContainer = IS_TRUE_OVERFLOW_CONTAINER(aFrame);
 
   nsIFrame::LogicalSides logicalSkipSides =
     aFrame->GetLogicalSkipSides(&aReflowInput);
   mBorderPadding.ApplySkipSides(logicalSkipSides);
 
   // Note that mContainerSize is the physical size, needed to
   // convert logical block-coordinates in vertical-rl writing mode
   // (measured from a RHS origin) to physical coordinates within the
@@ -82,27 +81,27 @@ BlockReflowInput::BlockReflowInput(const
   // For now at least, we don't do that fix-up for mContainerHeight.
   // It's only used in nsBidiUtils::ReorderFrames for vertical rtl
   // writing modes, which aren't fully supported for the time being.
   mContainerSize.height = aReflowInput.ComputedHeight() +
                           mBorderPadding.TopBottom(wm);
 
   if ((aBStartMarginRoot && !logicalSkipSides.BStart()) ||
       0 != mBorderPadding.BStart(wm)) {
-    SetFlag(BRS_ISBSTARTMARGINROOT, true);
-    SetFlag(BRS_APPLYBSTARTMARGIN, true);
+    mFlags.mIsBStartMarginRoot = true;
+    mFlags.mShouldApplyBStartMargin = true;
   }
   if ((aBEndMarginRoot && !logicalSkipSides.BEnd()) ||
       0 != mBorderPadding.BEnd(wm)) {
-    SetFlag(BRS_ISBENDMARGINROOT, true);
+    mFlags.mIsBEndMarginRoot = true;
   }
   if (aBlockNeedsFloatManager) {
-    SetFlag(BRS_FLOAT_MGR, true);
+    mFlags.mBlockNeedsFloatManager = true;
   }
-  
+
   mFloatManager = aReflowInput.mFloatManager;
 
   NS_ASSERTION(mFloatManager,
                "FloatManager should be set in BlockReflowInput" );
   if (mFloatManager) {
     // Save the coordinate system origin for later.
     mFloatManager->GetTranslation(mFloatManagerI, mFloatManagerB);
     mFloatManager->PushState(&mFloatManagerStateBefore); // never popped
@@ -128,18 +127,18 @@ BlockReflowInput::BlockReflowInput(const
     // We are in a paginated situation. The bottom edge is just inside
     // the bottom border and padding. The content area height doesn't
     // include either border or padding edge.
     mBEndEdge = aReflowInput.AvailableBSize() - mBorderPadding.BEnd(wm);
     mContentArea.BSize(wm) = std::max(0, mBEndEdge - mBorderPadding.BStart(wm));
   }
   else {
     // When we are not in a paginated situation then we always use
-    // an constrained height.
-    SetFlag(BRS_UNCONSTRAINEDBSIZE, true);
+    // a constrained height.
+    mFlags.mHasUnconstrainedBSize = true;
     mContentArea.BSize(wm) = mBEndEdge = NS_UNCONSTRAINEDSIZE;
   }
   mContentArea.IStart(wm) = mBorderPadding.IStart(wm);
   mBCoord = mContentArea.BStart(wm) = mBorderPadding.BStart(wm);
 
   mPrevChild = nullptr;
   mCurrentLine = aFrame->end_lines();
 
@@ -229,17 +228,17 @@ BlockReflowInput::ComputeBlockAvailSpace
                                            LogicalRect& aResult)
 {
 #ifdef REALLY_NOISY_REFLOW
   printf("CBAS frame=%p has floats %d\n",
          aFrame, aFloatAvailableSpace.mHasFloats);
 #endif
   WritingMode wm = mReflowInput.GetWritingMode();
   aResult.BStart(wm) = mBCoord;
-  aResult.BSize(wm) = GetFlag(BRS_UNCONSTRAINEDBSIZE)
+  aResult.BSize(wm) = mFlags.mHasUnconstrainedBSize
     ? NS_UNCONSTRAINEDSIZE
     : mReflowInput.AvailableBSize() - mBCoord
       - GetBEndMarginClone(aFrame, mReflowInput.mRenderingContext, mContentArea, wm);
   // mBCoord might be greater than mBEndEdge if the block's top margin pushes
   // it off the page/column. Negative available height can confuse other code
   // and is nonsense in principle.
 
   // XXX Do we really want this condition to be this restrictive (i.e.,
@@ -426,39 +425,39 @@ BlockReflowInput::ReconstructMarginBefor
       break;
     }
     if (!aLine->IsEmpty()) {
       break;
     }
     if (aLine == firstLine) {
       // If the top margin was carried out (and thus already applied),
       // set it to zero.  Either way, we're done.
-      if (!GetFlag(BRS_ISBSTARTMARGINROOT)) {
+      if (!mFlags.mIsBStartMarginRoot) {
         mPrevBEndMargin.Zero();
       }
       break;
     }
   }
 }
 
 void
 BlockReflowInput::SetupPushedFloatList()
 {
-  MOZ_ASSERT(!GetFlag(BRS_PROPTABLE_FLOATCLIST) == !mPushedFloats,
+  MOZ_ASSERT(!mFlags.mIsFloatListInBlockPropertyTable == !mPushedFloats,
              "flag mismatch");
-  if (!GetFlag(BRS_PROPTABLE_FLOATCLIST)) {
+  if (!mFlags.mIsFloatListInBlockPropertyTable) {
     // If we're being re-Reflow'd without our next-in-flow having been
     // reflowed, some pushed floats from our previous reflow might
     // still be on our pushed floats list.  However, that's
     // actually fine, since they'll all end up being stolen and
     // reordered into the correct order again.
     // (nsBlockFrame::ReflowDirtyLines ensures that any lines with
     // pushed floats are reflowed.)
     mPushedFloats = mBlock->EnsurePushedFloats();
-    SetFlag(BRS_PROPTABLE_FLOATCLIST, true);
+    mFlags.mIsFloatListInBlockPropertyTable = true;
   }
 }
 
 void
 BlockReflowInput::AppendPushedFloatChain(nsIFrame* aFloatCont)
 {
   SetupPushedFloatList();
   while (true) {
@@ -913,17 +912,17 @@ BlockReflowInput::FlowAndPlaceFloat(nsIF
   // In the case that we're in columns and not splitting floats, we need
   // to check here that the float's height fit, and if it didn't, bail.
   // (controlled by the pref "layout.float-fragments-inside-column.enabled")
   //
   // Likewise, if none of the float fit, and it needs to be pushed in
   // its entirety to the next page (NS_FRAME_IS_TRUNCATED or
   // NS_INLINE_IS_BREAK_BEFORE), we need to do the same.
   if ((ContentBSize() != NS_UNCONSTRAINEDSIZE &&
-       !GetFlag(BRS_FLOAT_FRAGMENTS_INSIDE_COLUMN_ENABLED) &&
+       !mFlags.mFloatFragmentsInsideColumnEnabled &&
        adjustedAvailableSpace.BSize(wm) == NS_UNCONSTRAINEDSIZE &&
        !mustPlaceFloat &&
        aFloat->BSize(wm) + floatMargin.BStartEnd(wm) >
        ContentBEnd() - floatPos.B(wm)) ||
       NS_FRAME_IS_TRUNCATED(reflowStatus) ||
       NS_INLINE_IS_BREAK_BEFORE(reflowStatus)) {
     PushFloatPastBreak(aFloat);
     return false;
--- a/layout/generic/BlockReflowInput.h
+++ b/layout/generic/BlockReflowInput.h
@@ -11,78 +11,101 @@
 #include "nsFloatManager.h"
 #include "nsLineBox.h"
 #include "mozilla/ReflowInput.h"
 
 class nsBlockFrame;
 class nsFrameList;
 class nsOverflowContinuationTracker;
 
-// Block reflow state flags.
-//
-// BRS_UNCONSTRAINEDBSIZE is set in the BlockReflowInput constructor when the
-// frame being reflowed has been given NS_UNCONSTRAINEDSIZE as its available
-// BSize in the ReflowInput. If set, NS_UNCONSTRAINEDSIZE is passed to
-// nsLineLayout as the available BSize.
-#define BRS_UNCONSTRAINEDBSIZE    0x00000001
-// BRS_ISBSTARTMARGINROOT is set in the BlockReflowInput constructor when
-// reflowing a "block margin root" frame (i.e. a frame with the
-// NS_BLOCK_MARGIN_ROOT flag set, for which margins apply by default).
-//
-// The flag is also set when reflowing a frame whose computed BStart border
-// padding is non-zero.
-#define BRS_ISBSTARTMARGINROOT    0x00000002
-// BRS_ISBENDMARGINROOT is set in the BlockReflowInput constructor when
-// reflowing a "block margin root" frame (i.e. a frame with the
-// NS_BLOCK_MARGIN_ROOT flag set, for which margins apply by default).
-//
-// The flag is also set when reflowing a frame whose computed BEnd border
-// padding is non-zero.
-#define BRS_ISBENDMARGINROOT      0x00000004
-// BRS_APPLYBSTARTMARGIN is set if the BStart margin should be considered when
-// placing a linebox that contains a block frame. It may be set as a side-effect
-// of calling nsBlockFrame::ShouldApplyBStartMargin(); once set,
-// ShouldApplyBStartMargin() uses it as a fast-path way to return whether the
-// BStart margin should apply.
-//
-// If the flag hasn't been set in the block reflow state, then
-// ShouldApplyBStartMargin() will crawl the line list to see if a block frame
-// precedes the specified frame. If so, the BStart margin should be applied, and
-// the flag is set to cache the result. (If not, the BStart margin will be
-// applied as a result of the generational margin collapsing logic in
-// nsBlockReflowContext::ComputeCollapsedBStartMargin(). In this case, the flag
-// won't be set, so subsequent calls to ShouldApplyBStartMargin() will continue
-// crawl the line list.)
-//
-// This flag is also set in the BlockReflowInput constructor if
-// BRS_ISBSTARTMARGINROOT is set; that is, the frame being reflowed is a margin
-// root by default.
-#define BRS_APPLYBSTARTMARGIN     0x00000008
-#define BRS_ISFIRSTINFLOW         0x00000010
-// Set when mLineAdjacentToTop is valid
-#define BRS_HAVELINEADJACENTTOTOP 0x00000020
-// Set when the block has the equivalent of NS_BLOCK_FLOAT_MGR
-#define BRS_FLOAT_MGR             0x00000040
-// Set when nsLineLayout::LineIsEmpty was true at the end of reflowing
-// the current line
-#define BRS_LINE_LAYOUT_EMPTY     0x00000080
-#define BRS_ISOVERFLOWCONTAINER   0x00000100
-// Our mPushedFloats list is stored on the blocks' proptable
-#define BRS_PROPTABLE_FLOATCLIST  0x00000200
-// Set when the pref layout.float-fragments-inside-column.enabled is true.
-#define BRS_FLOAT_FRAGMENTS_INSIDE_COLUMN_ENABLED 0x00000400
-#define BRS_LASTFLAG              BRS_FLOAT_FRAGMENTS_INSIDE_COLUMN_ENABLED
-
 namespace mozilla {
 
-// BlockReflowInput contains additional reflow state information that the
+// BlockReflowInput contains additional reflow input information that the
 // block frame uses along with ReflowInput. Like ReflowInput, this
 // is read-only data that is passed down from a parent frame to its children.
 class BlockReflowInput {
-  using ReflowInput = mozilla::ReflowInput;
+
+  // Block reflow input flags.
+  struct Flags {
+    Flags()
+      : mHasUnconstrainedBSize(false)
+      , mIsBStartMarginRoot(false)
+      , mIsBEndMarginRoot(false)
+      , mShouldApplyBStartMargin(false)
+      , mIsFirstInflow(false)
+      , mHasLineAdjacentToTop(false)
+      , mBlockNeedsFloatManager(false)
+      , mIsLineLayoutEmpty(false)
+      , mIsOverflowContainer(false)
+      , mIsFloatListInBlockPropertyTable(false)
+      , mFloatFragmentsInsideColumnEnabled(false)
+    {}
+
+    // Set in the BlockReflowInput constructor when the frame being reflowed has
+    // been given NS_UNCONSTRAINEDSIZE as its available BSize in the
+    // ReflowInput. If set, NS_UNCONSTRAINEDSIZE is passed to nsLineLayout as
+    // the available BSize.
+    bool mHasUnconstrainedBSize : 1;
+
+    // Set in the BlockReflowInput constructor when reflowing a "block margin
+    // root" frame (i.e. a frame with the NS_BLOCK_MARGIN_ROOT flag set, for
+    // which margins apply by default).
+    //
+    // The flag is also set when reflowing a frame whose computed BStart border
+    // padding is non-zero.
+    bool mIsBStartMarginRoot : 1;
+
+    // Set in the BlockReflowInput constructor when reflowing a "block margin
+    // root" frame (i.e. a frame with the NS_BLOCK_MARGIN_ROOT flag set, for
+    // which margins apply by default).
+    //
+    // The flag is also set when reflowing a frame whose computed BEnd border
+    // padding is non-zero.
+    bool mIsBEndMarginRoot : 1;
+
+    // Set if the BStart margin should be considered when placing a linebox that
+    // contains a block frame. It may be set as a side-effect of calling
+    // nsBlockFrame::ShouldApplyBStartMargin(); once set,
+    // ShouldApplyBStartMargin() uses it as a fast-path way to return whether
+    // the BStart margin should apply.
+    //
+    // If the flag hasn't been set in the block reflow input, then
+    // ShouldApplyBStartMargin() will crawl the line list to see if a block frame
+    // precedes the specified frame. If so, the BStart margin should be applied, and
+    // the flag is set to cache the result. (If not, the BStart margin will be
+    // applied as a result of the generational margin collapsing logic in
+    // nsBlockReflowContext::ComputeCollapsedBStartMargin(). In this case, the flag
+    // won't be set, so subsequent calls to ShouldApplyBStartMargin() will continue
+    // crawl the line list.)
+    //
+    // This flag is also set in the BlockReflowInput constructor if
+    // mIsBStartMarginRoot is set; that is, the frame being reflowed is a margin
+    // root by default.
+    bool mShouldApplyBStartMargin : 1;
+
+    bool mIsFirstInflow : 1;
+
+    // Set when mLineAdjacentToTop is valid.
+    bool mHasLineAdjacentToTop : 1;
+
+    // Set when the block has the equivalent of NS_BLOCK_FLOAT_MGR.
+    bool mBlockNeedsFloatManager : 1;
+
+    // Set when nsLineLayout::LineIsEmpty was true at the end of reflowing
+    // the current line.
+    bool mIsLineLayoutEmpty : 1;
+
+    bool mIsOverflowContainer : 1;
+
+    // Set when our mPushedFloats list is stored on the block's property table.
+    bool mIsFloatListInBlockPropertyTable : 1;
+
+    // Set when the pref layout.float-fragments-inside-column.enabled is true.
+    bool mFloatFragmentsInsideColumnEnabled : 1;
+  };
 
 public:
   BlockReflowInput(const ReflowInput& aReflowInput,
                      nsPresContext* aPresContext,
                      nsBlockFrame* aFrame,
                      bool aBStartMarginRoot, bool aBEndMarginRoot,
                      bool aBlockNeedsFloatManager,
                      nscoord aConsumedBSize = NS_INTRINSICSIZE);
@@ -187,18 +210,18 @@ public:
                               const nsStyleDisplay* aDisplay,
                               const nsFlowAreaRect& aFloatAvailableSpace,
                               bool aBlockAvoidsFloats,
                               mozilla::LogicalRect& aResult);
 
   void RecoverStateFrom(nsLineList::iterator aLine, nscoord aDeltaBCoord);
 
   void AdvanceToNextLine() {
-    if (GetFlag(BRS_LINE_LAYOUT_EMPTY)) {
-      SetFlag(BRS_LINE_LAYOUT_EMPTY, false);
+    if (mFlags.mIsLineLayoutEmpty) {
+      mFlags.mIsLineLayoutEmpty = false;
     } else {
       mLineNumber++;
     }
   }
 
   //----------------------------------------
 
   // This state is the "global" state computed once for the reflow of
@@ -292,17 +315,17 @@ public:
   // This state is "running" state updated by the reflow of each line
   // in the block. This same state is "recovered" when a line is not
   // dirty and is passed over during incremental reflow.
 
   // The current line being reflowed
   // If it is mBlock->end_lines(), then it is invalid.
   nsLineList::iterator mCurrentLine;
 
-  // When BRS_HAVELINEADJACENTTOTOP is set, this refers to a line
+  // When mHasLineAdjacentToTop is set, this refers to a line
   // which we know is adjacent to the top of the block (in other words,
   // all lines before it are empty and do not have clearance. This line is
   // always before the current line.
   nsLineList::iterator mLineAdjacentToTop;
 
   // The current block-direction coordinate in the block
   nscoord mBCoord;
 
@@ -342,40 +365,23 @@ public:
   // and placed. Again, this is done to keep the list fiddling from
   // being N^2.
   nsFloatCacheFreeList mBelowCurrentLineFloats;
 
   nscoord mMinLineHeight;
 
   int32_t mLineNumber;
 
-  int16_t mFlags;
+  Flags mFlags;
 
   uint8_t mFloatBreakType;
 
   // The amount of computed block-direction size "consumed" by previous-in-flows.
   nscoord mConsumedBSize;
 
-  void SetFlag(uint32_t aFlag, bool aValue)
-  {
-    NS_ASSERTION(aFlag<=BRS_LASTFLAG, "bad flag");
-    if (aValue) { // set flag
-      mFlags |= aFlag;
-    }
-    else {        // unset flag
-      mFlags &= ~aFlag;
-    }
-  }
-
-  bool GetFlag(uint32_t aFlag) const
-  {
-    NS_ASSERTION(aFlag<=BRS_LASTFLAG, "bad flag");
-    return !!(mFlags & aFlag);
-  }
-
 private:
   bool CanPlaceFloat(nscoord aFloatISize,
                      const nsFlowAreaRect& aFloatAvailableSpace);
 
   void PushFloatPastBreak(nsIFrame* aFloat);
 
   void RecoverFloats(nsLineList::iterator aLine, nscoord aDeltaBCoord);
 };
--- a/layout/generic/nsBlockFrame.cpp
+++ b/layout/generic/nsBlockFrame.cpp
@@ -1484,17 +1484,17 @@ nsBlockFrame::ComputeFinalSize(const Ref
                                ReflowOutput&     aMetrics,
                                nscoord*                 aBEndEdgeOfChildren)
 {
   WritingMode wm = aState.mReflowInput.GetWritingMode();
   const LogicalMargin& borderPadding = aState.BorderPadding();
 #ifdef NOISY_FINAL_SIZE
   ListTag(stdout);
   printf(": mBCoord=%d mIsBEndMarginRoot=%s mPrevBEndMargin=%d bp=%d,%d\n",
-         aState.mBCoord, aState.GetFlag(BRS_ISBENDMARGINROOT) ? "yes" : "no",
+         aState.mBCoord, aState.mFlags.mIsBEndMarginRoot ? "yes" : "no",
          aState.mPrevBEndMargin.get(),
          borderPadding.BStart(wm), borderPadding.BEnd(wm));
 #endif
 
   // Compute final inline size
   LogicalSize finalSize(wm);
   finalSize.ISize(wm) =
     NSCoordSaturatingAdd(NSCoordSaturatingAdd(borderPadding.IStart(wm),
@@ -1502,17 +1502,17 @@ nsBlockFrame::ComputeFinalSize(const Ref
                          borderPadding.IEnd(wm));
 
   // Return block-end margin information
   // rbs says he hit this assertion occasionally (see bug 86947), so
   // just set the margin to zero and we'll figure out why later
   //NS_ASSERTION(aMetrics.mCarriedOutBEndMargin.IsZero(),
   //             "someone else set the margin");
   nscoord nonCarriedOutBDirMargin = 0;
-  if (!aState.GetFlag(BRS_ISBENDMARGINROOT)) {
+  if (!aState.mFlags.mIsBEndMarginRoot) {
     // Apply rule from CSS 2.1 section 8.3.1. If we have some empty
     // line with clearance and a non-zero block-start margin and all
     // subsequent lines are empty, then we do not allow our children's
     // carried out block-end margin to be carried out of us and collapse
     // with our own block-end margin.
     if (CheckForCollapsedBEndMarginFromClearanceLine()) {
       // Convert the children's carried out margin to something that
       // we will include in our height
@@ -1521,33 +1521,33 @@ nsBlockFrame::ComputeFinalSize(const Ref
     }
     aMetrics.mCarriedOutBEndMargin = aState.mPrevBEndMargin;
   } else {
     aMetrics.mCarriedOutBEndMargin.Zero();
   }
 
   nscoord blockEndEdgeOfChildren = aState.mBCoord + nonCarriedOutBDirMargin;
   // Shrink wrap our height around our contents.
-  if (aState.GetFlag(BRS_ISBENDMARGINROOT) ||
+  if (aState.mFlags.mIsBEndMarginRoot ||
       NS_UNCONSTRAINEDSIZE != aReflowInput.ComputedBSize()) {
     // When we are a block-end-margin root make sure that our last
     // childs block-end margin is fully applied. We also do this when
     // we have a computed height, since in that case the carried out
     // margin is not going to be applied anywhere, so we should note it
     // here to be included in the overflow area.
     // Apply the margin only if there's space for it.
     if (blockEndEdgeOfChildren < aState.mReflowInput.AvailableBSize())
     {
       // Truncate block-end margin if it doesn't fit to our available BSize.
       blockEndEdgeOfChildren =
         std::min(blockEndEdgeOfChildren + aState.mPrevBEndMargin.get(),
                aState.mReflowInput.AvailableBSize());
     }
   }
-  if (aState.GetFlag(BRS_FLOAT_MGR)) {
+  if (aState.mFlags.mBlockNeedsFloatManager) {
     // Include the float manager's state to properly account for the
     // block-end margin of any floated elements; e.g., inside a table cell.
     nscoord floatHeight =
       aState.ClearFloats(blockEndEdgeOfChildren, NS_STYLE_CLEAR_BOTH,
                          nullptr, nsFloatManager::DONT_CLEAR_PUSHED_FLOATS);
     blockEndEdgeOfChildren = std::max(blockEndEdgeOfChildren, floatHeight);
   }
 
@@ -3071,47 +3071,47 @@ nsBlockFrame::IsEmpty()
   return true;
 }
 
 bool
 nsBlockFrame::ShouldApplyBStartMargin(BlockReflowInput& aState,
                                       nsLineBox* aLine,
                                       nsIFrame* aChildFrame)
 {
-  if (aState.GetFlag(BRS_APPLYBSTARTMARGIN)) {
+  if (aState.mFlags.mShouldApplyBStartMargin) {
     // Apply short-circuit check to avoid searching the line list
     return true;
   }
 
   if (!aState.IsAdjacentWithTop() ||
       aChildFrame->StyleBorder()->mBoxDecorationBreak ==
         NS_STYLE_BOX_DECORATION_BREAK_CLONE) {
     // If we aren't at the start block-coordinate then something of non-zero
     // height must have been placed. Therefore the childs block-start margin
     // applies.
-    aState.SetFlag(BRS_APPLYBSTARTMARGIN, true);
+    aState.mFlags.mShouldApplyBStartMargin = true;
     return true;
   }
 
   // Determine if this line is "essentially" the first line
   line_iterator line = begin_lines();
-  if (aState.GetFlag(BRS_HAVELINEADJACENTTOTOP)) {
+  if (aState.mFlags.mHasLineAdjacentToTop) {
     line = aState.mLineAdjacentToTop;
   }
   while (line != aLine) {
     if (!line->CachedIsEmpty() || line->HasClearance()) {
       // A line which precedes aLine is non-empty, or has clearance,
       // so therefore the block-start margin applies.
-      aState.SetFlag(BRS_APPLYBSTARTMARGIN, true);
+      aState.mFlags.mShouldApplyBStartMargin = true;
       return true;
     }
     // No need to apply the block-start margin if the line has floats.  We
     // should collapse anyway (bug 44419)
     ++line;
-    aState.SetFlag(BRS_HAVELINEADJACENTTOTOP, true);
+    aState.mFlags.mHasLineAdjacentToTop = true;
     aState.mLineAdjacentToTop = line;
   }
 
   // The line being reflowed is "essentially" the first line in the
   // block. Therefore its block-start margin will be collapsed by the
   // generational collapsing logic with its parent (us).
   return false;
 }
@@ -3836,17 +3836,17 @@ nsBlockFrame::DoReflowInlineFrames(Block
   WritingMode lineWM = GetWritingMode(aLine->mFirstChild);
   LogicalRect lineRect =
     aFloatAvailableSpace.mRect.ConvertTo(lineWM, outerWM,
                                          aState.ContainerSize());
 
   nscoord iStart = lineRect.IStart(lineWM);
   nscoord availISize = lineRect.ISize(lineWM);
   nscoord availBSize;
-  if (aState.GetFlag(BRS_UNCONSTRAINEDBSIZE)) {
+  if (aState.mFlags.mHasUnconstrainedBSize) {
     availBSize = NS_UNCONSTRAINEDSIZE;
   }
   else {
     /* XXX get the height right! */
     availBSize = lineRect.BSize(lineWM);
   }
 
   // Make sure to enable resize optimization before we call BeginLineReflow
@@ -3854,17 +3854,17 @@ nsBlockFrame::DoReflowInlineFrames(Block
   aLine->EnableResizeReflowOptimization();
 
   aLineLayout.BeginLineReflow(iStart, aState.mBCoord,
                               availISize, availBSize,
                               aFloatAvailableSpace.mHasFloats,
                               false, /*XXX isTopOfPage*/
                               lineWM, aState.mContainerSize);
 
-  aState.SetFlag(BRS_LINE_LAYOUT_EMPTY, false);
+  aState.mFlags.mIsLineLayoutEmpty = false;
 
   // XXX Unfortunately we need to know this before reflowing the first
   // inline frame in the line. FIX ME.
   if ((0 == aLineLayout.GetLineNumber()) &&
       (NS_BLOCK_HAS_FIRST_LETTER_CHILD & mState) &&
       (NS_BLOCK_HAS_FIRST_LETTER_STYLE & mState)) {
     aLineLayout.SetFirstLetterStyleOK(true);
   }
@@ -3933,23 +3933,23 @@ nsBlockFrame::DoReflowInlineFrames(Block
         }
         else {
           break;
         }
       }
     }
   }
 
-  aState.SetFlag(BRS_LINE_LAYOUT_EMPTY, aLineLayout.LineIsEmpty());
+  aState.mFlags.mIsLineLayoutEmpty = aLineLayout.LineIsEmpty();
 
   // We only need to backup if the line isn't going to be reflowed again anyway
   bool needsBackup = aLineLayout.NeedsBackup() &&
     (lineReflowStatus == LINE_REFLOW_STOP || lineReflowStatus == LINE_REFLOW_OK);
   if (needsBackup && aLineLayout.HaveForcedBreakPosition()) {
-  	NS_WARNING("We shouldn't be backing up more than once! "
+    NS_WARNING("We shouldn't be backing up more than once! "
                "Someone must have set a break opportunity beyond the available width, "
                "even though there were better break opportunities before it");
     needsBackup = false;
   }
   if (needsBackup) {
     // We need to try backing up to before a text run
     // XXX It's possible, in fact not unusual, for the break opportunity to already
     // be the end of the line. We should detect that and optimize to not
@@ -4533,18 +4533,18 @@ nsBlockFrame::PlaceLine(BlockReflowInput
     aState.mPrevBEndMargin.Zero();
     newBCoord = aLine->BEnd();
   }
   else {
     // Don't let the previous-bottom-margin value affect the newBCoord
     // coordinate (it was applied in ReflowInlineFrames speculatively)
     // since the line is empty.
     // We already called |ShouldApplyBStartMargin|, and if we applied it
-    // then BRS_APPLYBSTARTMARGIN is set.
-    nscoord dy = aState.GetFlag(BRS_APPLYBSTARTMARGIN)
+    // then mShouldApplyBStartMargin is set.
+    nscoord dy = aState.mFlags.mShouldApplyBStartMargin
                    ? -aState.mPrevBEndMargin.get() : 0;
     newBCoord = aState.mBCoord + dy;
   }
 
   if (!NS_FRAME_IS_FULLY_COMPLETE(aState.mReflowStatus) &&
       ShouldAvoidBreakInside(aState.mReflowInput)) {
     aLine->AppendFloats(aState.mCurrentLineFloats);
     aState.mReflowStatus = NS_INLINE_LINE_BREAK_BEFORE();
@@ -6067,17 +6067,17 @@ nsBlockFrame::AdjustFloatAvailableSpace(
     availISize = aFloatAvailableSpace.ISize(wm);
   }
 
   nscoord availBSize = NS_UNCONSTRAINEDSIZE == aState.ContentBSize()
                        ? NS_UNCONSTRAINEDSIZE
                        : std::max(0, aState.ContentBEnd() - aState.mBCoord);
 
   if (availBSize != NS_UNCONSTRAINEDSIZE &&
-      !aState.GetFlag(BRS_FLOAT_FRAGMENTS_INSIDE_COLUMN_ENABLED) &&
+      !aState.mFlags.mFloatFragmentsInsideColumnEnabled &&
       nsLayoutUtils::GetClosestFrameOfType(this, nsGkAtoms::columnSetFrame)) {
     // Tell the float it has unrestricted block-size, so it won't break.
     // If the float doesn't actually fit in the column it will fail to be
     // placed, and either move to the block-start of the next column or just
     // overflow.
     availBSize = NS_UNCONSTRAINEDSIZE;
   }
 
--- a/layout/generic/nsFrameStateBits.h
+++ b/layout/generic/nsFrameStateBits.h
@@ -460,17 +460,17 @@ FRAME_STATE_BIT(Block, 21, NS_BLOCK_HAS_
 
 // This indicates that this is a frame from which child margins can be
 // calculated. The absence of this flag implies that child margin calculations
 // should ignore the frame and look further up the parent chain. Used in
 // nsBlockReflowContext::ComputeCollapsedBStartMargin() via
 // nsBlockFrame::IsMarginRoot().
 //
 // This causes the BlockReflowInput's constructor to set the
-// BRS_ISBSTARTMARGINROOT and BRS_ISBENDMARGINROOT flags.
+// mIsBStartMarginRoot and mIsBEndMarginRoot flags.
 FRAME_STATE_BIT(Block, 22, NS_BLOCK_MARGIN_ROOT)
 
 // This indicates that a block frame should create its own float manager. This
 // is required by each block frame that can contain floats. The float manager is
 // used to reserve space for the floated frames.
 FRAME_STATE_BIT(Block, 23, NS_BLOCK_FLOAT_MGR)
 
 FRAME_STATE_BIT(Block, 24, NS_BLOCK_HAS_LINE_CURSOR)
--- a/layout/style/Declaration.cpp
+++ b/layout/style/Declaration.cpp
@@ -30,16 +30,24 @@ ImportantStyleData::MapRuleInfoInto(nsRu
 }
 
 /* virtual */ bool
 ImportantStyleData::MightMapInheritedStyleData()
 {
   return Declaration()->MapsImportantInheritedStyleData();
 }
 
+/* virtual */ bool
+ImportantStyleData::GetDiscretelyAnimatedCSSValue(nsCSSPropertyID aProperty,
+                                                  nsCSSValue* aValue)
+{
+  return Declaration()->GetDiscretelyAnimatedCSSValue(aProperty, aValue);
+}
+
+
 #ifdef DEBUG
 /* virtual */ void
 ImportantStyleData::List(FILE* out, int32_t aIndent) const
 {
   // Indent
   nsAutoCString str;
   for (int32_t index = aIndent; --index >= 0; ) {
     str.AppendLiteral("  ");
@@ -106,16 +114,31 @@ Declaration::MightMapInheritedStyleData(
 {
   MOZ_ASSERT(mData, "must call only while compressed");
   if (mVariables && mVariables->Count() != 0) {
     return true;
   }
   return mData->HasInheritedStyleData();
 }
 
+/* virtual */ bool
+Declaration::GetDiscretelyAnimatedCSSValue(nsCSSPropertyID aProperty,
+                                           nsCSSValue* aValue)
+{
+  nsCSSCompressedDataBlock* data = GetValueIsImportant(aProperty)
+                                   ? mImportantData : mData;
+  const nsCSSValue* value = data->ValueFor(aProperty);
+  if (!value) {
+    return false;
+  }
+  *aValue = *value;
+  return true;
+}
+
+
 bool
 Declaration::MapsImportantInheritedStyleData() const
 {
   MOZ_ASSERT(mData, "must call only while compressed");
   MOZ_ASSERT(HasImportantData(), "must only be called for Declarations with "
                                  "important data");
   if (mImportantVariables && mImportantVariables->Count() != 0) {
     return true;
--- a/layout/style/Declaration.h
+++ b/layout/style/Declaration.h
@@ -55,16 +55,18 @@ public:
 
   NS_DECL_ISUPPORTS
 
   inline ::mozilla::css::Declaration* Declaration();
 
   // nsIStyleRule interface
   virtual void MapRuleInfoInto(nsRuleData* aRuleData) override;
   virtual bool MightMapInheritedStyleData() override;
+  virtual bool GetDiscretelyAnimatedCSSValue(nsCSSPropertyID aProperty,
+                                             nsCSSValue* aValue) override;
 #ifdef DEBUG
   virtual void List(FILE* out = stdout, int32_t aIndent = 0) const override;
 #endif
 
 private:
   ImportantStyleData() {}
   ~ImportantStyleData() {}
 
@@ -98,16 +100,18 @@ public:
 private:
   ~Declaration();
 
 public:
 
   // nsIStyleRule implementation
   virtual void MapRuleInfoInto(nsRuleData *aRuleData) override;
   virtual bool MightMapInheritedStyleData() override;
+  virtual bool GetDiscretelyAnimatedCSSValue(nsCSSPropertyID aProperty,
+                                             nsCSSValue* aValue) override;
 #ifdef DEBUG
   virtual void List(FILE* out = stdout, int32_t aIndent = 0) const override;
 #endif
 
   /**
    * |ValueAppended| must be called to maintain this declaration's
    * |mOrder| whenever a property is parsed into an expanded data block
    * for this declaration.  aProperty must not be a shorthand.
--- a/layout/style/ServoBindings.cpp
+++ b/layout/style/ServoBindings.cpp
@@ -21,16 +21,27 @@
 #include "nsStyleStruct.h"
 #include "nsStyleUtil.h"
 #include "nsTArray.h"
 
 #include "mozilla/EventStates.h"
 #include "mozilla/ServoElementSnapshot.h"
 #include "mozilla/dom/Element.h"
 
+#define IMPL_STRONG_REF_TYPE(name, T)           \
+  already_AddRefed<T> name::Consume() {         \
+    RefPtr<T> result = dont_AddRef(mPtr);       \
+    mPtr = nullptr;                             \
+    return result.forget();                     \
+  };
+
+
+IMPL_STRONG_REF_TYPE(ServoComputedValuesStrong, ServoComputedValues);
+IMPL_STRONG_REF_TYPE(RawServoStyleSheetStrong, RawServoStyleSheet);
+
 uint32_t
 Gecko_ChildrenCount(RawGeckoNode* aNode)
 {
   return aNode->GetChildCount();
 }
 
 bool
 Gecko_NodeIsElement(RawGeckoNode* aNode)
@@ -193,17 +204,17 @@ Gecko_GetStyleContext(RawGeckoNode* aNod
     return nullptr;
   }
 
   return primaryFrame->StyleContext();
 }
 
 nsChangeHint
 Gecko_CalcStyleDifference(nsStyleContext* aOldStyleContext,
-                          ServoComputedValues* aComputedValues)
+                          ServoComputedValuesBorrowed aComputedValues)
 {
   MOZ_ASSERT(aOldStyleContext);
   MOZ_ASSERT(aComputedValues);
 
   // Pass the safe thing, which causes us to miss a potential optimization. See
   // bug 1289863.
   nsChangeHint forDescendants = nsChangeHint_Hints_NotHandledForDescendants;
 
@@ -765,72 +776,72 @@ Gecko_Destroy_nsStyle##name(nsStyle##nam
 #ifndef MOZ_STYLO
 void
 Servo_DropNodeData(ServoNodeData* data)
 {
   MOZ_CRASH("stylo: shouldn't be calling Servo_DropNodeData in a "
             "non-MOZ_STYLO build");
 }
 
-RawServoStyleSheet*
+RawServoStyleSheetStrong
 Servo_StylesheetFromUTF8Bytes(const uint8_t* bytes, uint32_t length,
                               mozilla::css::SheetParsingMode mode,
                               const uint8_t* base_bytes, uint32_t base_length,
                               ThreadSafeURIHolder* base,
                               ThreadSafeURIHolder* referrer,
                               ThreadSafePrincipalHolder* principal)
 {
   MOZ_CRASH("stylo: shouldn't be calling Servo_StylesheetFromUTF8Bytes in a "
             "non-MOZ_STYLO build");
 }
 
 void
-Servo_AddRefStyleSheet(RawServoStyleSheet* sheet)
+Servo_AddRefStyleSheet(RawServoStyleSheetBorrowed sheet)
 {
   MOZ_CRASH("stylo: shouldn't be calling Servo_AddRefStylesheet in a "
             "non-MOZ_STYLO build");
 }
 
 void
-Servo_ReleaseStyleSheet(RawServoStyleSheet* sheet)
+Servo_ReleaseStyleSheet(RawServoStyleSheetBorrowed sheet)
 {
   MOZ_CRASH("stylo: shouldn't be calling Servo_ReleaseStylesheet in a "
             "non-MOZ_STYLO build");
 }
 
 void
-Servo_AppendStyleSheet(RawServoStyleSheet* sheet, RawServoStyleSet* set)
+Servo_AppendStyleSheet(RawServoStyleSheetBorrowed sheet, RawServoStyleSet* set)
 {
   MOZ_CRASH("stylo: shouldn't be calling Servo_AppendStyleSheet in a "
             "non-MOZ_STYLO build");
 }
 
-void Servo_PrependStyleSheet(RawServoStyleSheet* sheet, RawServoStyleSet* set)
+void Servo_PrependStyleSheet(RawServoStyleSheetBorrowed sheet, RawServoStyleSet* set)
 {
   MOZ_CRASH("stylo: shouldn't be calling Servo_PrependStyleSheet in a "
             "non-MOZ_STYLO build");
 }
 
-void Servo_RemoveStyleSheet(RawServoStyleSheet* sheet, RawServoStyleSet* set)
+void Servo_RemoveStyleSheet(RawServoStyleSheetBorrowed sheet, RawServoStyleSet* set)
 {
   MOZ_CRASH("stylo: shouldn't be calling Servo_RemoveStyleSheet in a "
             "non-MOZ_STYLO build");
 }
 
 void
-Servo_InsertStyleSheetBefore(RawServoStyleSheet* sheet,
-                             RawServoStyleSheet* reference,
+Servo_InsertStyleSheetBefore(RawServoStyleSheetBorrowed sheet,
+                             RawServoStyleSheetBorrowed reference,
                              RawServoStyleSet* set)
 {
   MOZ_CRASH("stylo: shouldn't be calling Servo_InsertStyleSheetBefore in a "
             "non-MOZ_STYLO build");
 }
 
 bool
-Servo_StyleSheetHasRules(RawServoStyleSheet* sheet)
+Servo_StyleSheetHasRules(RawServoStyleSheetBorrowed sheet)
 {
   MOZ_CRASH("stylo: shouldn't be calling Servo_StyleSheetHasRules in a "
             "non-MOZ_STYLO build");
 }
 
 RawServoStyleSet*
 Servo_InitStyleSet()
 {
@@ -884,59 +895,59 @@ Servo_ClearDeclarationBlockCachePointer(
 bool
 Servo_CSSSupports(const uint8_t* name, uint32_t name_length,
                   const uint8_t* value, uint32_t value_length)
 {
   MOZ_CRASH("stylo: shouldn't be calling Servo_CSSSupports in a "
             "non-MOZ_STYLO build");
 }
 
-ServoComputedValues*
+ServoComputedValuesStrong
 Servo_GetComputedValues(RawGeckoNode* node)
 {
   MOZ_CRASH("stylo: shouldn't be calling Servo_GetComputedValues in a "
             "non-MOZ_STYLO build");
 }
 
-ServoComputedValues*
-Servo_GetComputedValuesForAnonymousBox(ServoComputedValues* parentStyleOrNull,
+ServoComputedValuesStrong
+Servo_GetComputedValuesForAnonymousBox(ServoComputedValuesBorrowed parentStyleOrNull,
                                        nsIAtom* pseudoTag,
                                        RawServoStyleSet* set)
 {
   MOZ_CRASH("stylo: shouldn't be calling Servo_GetComputedValuesForAnonymousBox in a "
             "non-MOZ_STYLO build");
 }
 
-ServoComputedValues*
-Servo_GetComputedValuesForPseudoElement(ServoComputedValues* parent_style,
+ServoComputedValuesStrong
+Servo_GetComputedValuesForPseudoElement(ServoComputedValuesBorrowed parent_style,
                                         RawGeckoElement* match_element,
                                         nsIAtom* pseudo_tag,
                                         RawServoStyleSet* set,
                                         bool is_probe)
 {
   MOZ_CRASH("stylo: shouldn't be calling Servo_GetComputedValuesForPseudoElement in a "
             "non-MOZ_STYLO build");
 }
 
-ServoComputedValues*
-Servo_InheritComputedValues(ServoComputedValues* parent_style)
+ServoComputedValuesStrong
+Servo_InheritComputedValues(ServoComputedValuesBorrowed parent_style)
 {
   MOZ_CRASH("stylo: shouldn't be calling Servo_InheritComputedValues in a "
             "non-MOZ_STYLO build");
 }
 
 void
-Servo_AddRefComputedValues(ServoComputedValues*)
+Servo_AddRefComputedValues(ServoComputedValuesBorrowed)
 {
   MOZ_CRASH("stylo: shouldn't be calling Servo_AddRefComputedValues in a "
             "non-MOZ_STYLO build");
 }
 
 void
-Servo_ReleaseComputedValues(ServoComputedValues*)
+Servo_ReleaseComputedValues(ServoComputedValuesBorrowed)
 {
   MOZ_CRASH("stylo: shouldn't be calling Servo_ReleaseComputedValues in a "
             "non-MOZ_STYLO build");
 }
 
 void
 Servo_Initialize()
 {
@@ -970,28 +981,28 @@ Servo_RestyleDocument(RawGeckoDocument* 
 void Servo_RestyleSubtree(RawGeckoNode* node, RawServoStyleSet* set)
 {
   MOZ_CRASH("stylo: shouldn't be calling Servo_RestyleSubtree in a "
             "non-MOZ_STYLO build");
 }
 
 #define STYLE_STRUCT(name_, checkdata_cb_)                                     \
 const nsStyle##name_*                                                          \
-Servo_GetStyle##name_(ServoComputedValues*)                                    \
+Servo_GetStyle##name_(ServoComputedValuesBorrowed)                             \
 {                                                                              \
   MOZ_CRASH("stylo: shouldn't be calling Servo_GetStyle" #name_ " in a "       \
             "non-MOZ_STYLO build");                                            \
 }
 #include "nsStyleStructList.h"
 #undef STYLE_STRUCT
 #endif
 
 #ifdef MOZ_STYLO
 const nsStyleVariables*
-Servo_GetStyleVariables(ServoComputedValues* aComputedValues)
+Servo_GetStyleVariables(ServoComputedValuesBorrowed aComputedValues)
 {
   // Servo can't provide us with Variables structs yet, so instead of linking
   // to a Servo_GetStyleVariables defined in Servo we define one here that
   // always returns the same, empty struct.
   static nsStyleVariables variables(StyleStructContext::ServoContext());
   return &variables;
 }
 #endif
--- a/layout/style/ServoBindings.h
+++ b/layout/style/ServoBindings.h
@@ -11,16 +11,34 @@
 #include "mozilla/css/SheetParsingMode.h"
 #include "nsChangeHint.h"
 #include "nsColor.h"
 #include "nsProxyRelease.h"
 #include "nsStyleCoord.h"
 #include "nsStyleStruct.h"
 #include "stdint.h"
 
+#define DECL_STRONG_REF_TYPE(name, T)                   \
+  struct MOZ_MUST_USE_TYPE name {                         \
+    T* mPtr;                                              \
+    already_AddRefed<T> Consume();                        \
+  }
+
+#define DECL_BORROWED_REF_TYPE(name, T)                 \
+  struct name {                                           \
+    T* mPtr;                                              \
+    MOZ_IMPLICIT                                          \
+    name(T* x): mPtr(x) {};                               \
+    MOZ_IMPLICIT                                          \
+    name(const RefPtr<T>& aPtr) : mPtr(aPtr.get()) {};    \
+    operator T*() const & {                               \
+      return mPtr;                                        \
+    }                                                     \
+  }
+
 /*
  * API for Servo to access Gecko data structures. This file must compile as valid
  * C code in order for the binding generator to parse it.
  *
  * Functions beginning with Gecko_ are implemented in Gecko and invoked from Servo.
  * Functions beginning with Servo_ are implemented in Servo and invoked from Gecko.
  */
 
@@ -39,17 +57,21 @@ using mozilla::FontFamilyList;
 using mozilla::FontFamilyType;
 using mozilla::dom::Element;
 using mozilla::ServoElementSnapshot;
 typedef mozilla::dom::Element RawGeckoElement;
 class nsIDocument;
 typedef nsIDocument RawGeckoDocument;
 struct ServoNodeData;
 struct ServoComputedValues;
+DECL_STRONG_REF_TYPE(ServoComputedValuesStrong, ServoComputedValues);
+DECL_BORROWED_REF_TYPE(ServoComputedValuesBorrowed, ServoComputedValues);
 struct RawServoStyleSheet;
+DECL_STRONG_REF_TYPE(RawServoStyleSheetStrong, RawServoStyleSheet);
+DECL_BORROWED_REF_TYPE(RawServoStyleSheetBorrowed, RawServoStyleSheet);
 struct RawServoStyleSet;
 class nsHTMLCSSStyleSheet;
 struct nsStyleList;
 struct nsStyleImage;
 struct nsStyleGradientStop;
 class nsStyleGradient;
 class nsStyleCoord;
 struct nsStyleDisplay;
@@ -190,17 +212,17 @@ void Gecko_UnsetNodeFlags(RawGeckoNode* 
 // TODO: We would avoid a few ffi calls if we decide to make an API like the
 // former CalcAndStoreStyleDifference, but that would effectively mean breaking
 // some safety guarantees in the servo side.
 //
 // Also, we might want a ComputedValues to ComputedValues API for animations?
 // Not if we do them in Gecko...
 nsStyleContext* Gecko_GetStyleContext(RawGeckoNode* node);
 nsChangeHint Gecko_CalcStyleDifference(nsStyleContext* oldstyle,
-                                       ServoComputedValues* newstyle);
+                                       ServoComputedValuesBorrowed newstyle);
 void Gecko_StoreStyleDifference(RawGeckoNode* node, nsChangeHint change);
 
 // `array` must be an nsTArray
 // If changing this signature, please update the
 // friend function declaration in nsTArray.h
 void Gecko_EnsureTArrayCapacity(void* array, size_t capacity, size_t elem_size);
 
 
@@ -216,32 +238,32 @@ void Gecko_ResetStyleCoord(nsStyleUnit* 
 void Gecko_SetStyleCoordCalcValue(nsStyleUnit* unit, nsStyleUnion* value, nsStyleCoord::CalcValue calc);
 
 NS_DECL_THREADSAFE_FFI_REFCOUNTING(nsStyleCoord::Calc, Calc);
 
 // Styleset and Stylesheet management.
 //
 // TODO: Make these return already_AddRefed and UniquePtr when the binding
 // generator is smart enough to handle them.
-RawServoStyleSheet* Servo_StylesheetFromUTF8Bytes(
+RawServoStyleSheetStrong Servo_StylesheetFromUTF8Bytes(
     const uint8_t* bytes, uint32_t length,
     mozilla::css::SheetParsingMode parsing_mode,
     const uint8_t* base_bytes, uint32_t base_length,
     ThreadSafeURIHolder* base,
     ThreadSafeURIHolder* referrer,
     ThreadSafePrincipalHolder* principal);
-void Servo_AddRefStyleSheet(RawServoStyleSheet* sheet);
-void Servo_ReleaseStyleSheet(RawServoStyleSheet* sheet);
-void Servo_AppendStyleSheet(RawServoStyleSheet* sheet, RawServoStyleSet* set);
-void Servo_PrependStyleSheet(RawServoStyleSheet* sheet, RawServoStyleSet* set);
-void Servo_RemoveStyleSheet(RawServoStyleSheet* sheet, RawServoStyleSet* set);
-void Servo_InsertStyleSheetBefore(RawServoStyleSheet* sheet,
-                                  RawServoStyleSheet* reference,
+void Servo_AddRefStyleSheet(RawServoStyleSheetBorrowed sheet);
+void Servo_ReleaseStyleSheet(RawServoStyleSheetBorrowed sheet);
+void Servo_AppendStyleSheet(RawServoStyleSheetBorrowed sheet, RawServoStyleSet* set);
+void Servo_PrependStyleSheet(RawServoStyleSheetBorrowed sheet, RawServoStyleSet* set);
+void Servo_RemoveStyleSheet(RawServoStyleSheetBorrowed sheet, RawServoStyleSet* set);
+void Servo_InsertStyleSheetBefore(RawServoStyleSheetBorrowed sheet,
+                                  RawServoStyleSheetBorrowed reference,
                                   RawServoStyleSet* set);
-bool Servo_StyleSheetHasRules(RawServoStyleSheet* sheet);
+bool Servo_StyleSheetHasRules(RawServoStyleSheetBorrowed sheet);
 RawServoStyleSet* Servo_InitStyleSet();
 void Servo_DropStyleSet(RawServoStyleSet* set);
 
 // Style attributes.
 ServoDeclarationBlock* Servo_ParseStyleAttribute(const uint8_t* bytes,
                                                  uint32_t length,
                                                  nsHTMLCSSStyleSheet* cache);
 void Servo_DropDeclarationBlock(ServoDeclarationBlock* declarations);
@@ -250,28 +272,28 @@ nsHTMLCSSStyleSheet* Servo_GetDeclaratio
 void Servo_SetDeclarationBlockImmutable(ServoDeclarationBlock* declarations);
 void Servo_ClearDeclarationBlockCachePointer(ServoDeclarationBlock* declarations);
 
 // CSS supports().
 bool Servo_CSSSupports(const uint8_t* name, uint32_t name_length,
                        const uint8_t* value, uint32_t value_length);
 
 // Computed style data.
-ServoComputedValues* Servo_GetComputedValues(RawGeckoNode* node);
-ServoComputedValues* Servo_GetComputedValuesForAnonymousBox(ServoComputedValues* parentStyleOrNull,
+ServoComputedValuesStrong Servo_GetComputedValues(RawGeckoNode* node);
+ServoComputedValuesStrong Servo_GetComputedValuesForAnonymousBox(ServoComputedValuesBorrowed parentStyleOrNull,
                                                             nsIAtom* pseudoTag,
                                                             RawServoStyleSet* set);
-ServoComputedValues* Servo_GetComputedValuesForPseudoElement(ServoComputedValues* parent_style,
+ServoComputedValuesStrong Servo_GetComputedValuesForPseudoElement(ServoComputedValuesBorrowed parent_style,
                                                              RawGeckoElement* match_element,
                                                              nsIAtom* pseudo_tag,
                                                              RawServoStyleSet* set,
                                                              bool is_probe);
-ServoComputedValues* Servo_InheritComputedValues(ServoComputedValues* parent_style);
-void Servo_AddRefComputedValues(ServoComputedValues*);
-void Servo_ReleaseComputedValues(ServoComputedValues*);
+ServoComputedValuesStrong Servo_InheritComputedValues(ServoComputedValuesBorrowed parent_style);
+void Servo_AddRefComputedValues(ServoComputedValuesBorrowed);
+void Servo_ReleaseComputedValues(ServoComputedValuesBorrowed);
 
 // Initialize Servo components. Should be called exactly once at startup.
 void Servo_Initialize();
 
 // Shut down Servo components. Should be called exactly once at shutdown.
 void Servo_Shutdown();
 
 // Restyle the given document or subtree.
@@ -286,15 +308,15 @@ nsRestyleHint Servo_ComputeRestyleHint(R
 // Style-struct management.
 #define STYLE_STRUCT(name, checkdata_cb)                                       \
   struct nsStyle##name;                                                        \
   void Gecko_Construct_nsStyle##name(nsStyle##name* ptr);                      \
   void Gecko_CopyConstruct_nsStyle##name(nsStyle##name* ptr,                   \
                                          const nsStyle##name* other);          \
   void Gecko_Destroy_nsStyle##name(nsStyle##name* ptr);                        \
   const nsStyle##name* Servo_GetStyle##name(                                   \
-    ServoComputedValues* computedValues);
+    ServoComputedValuesBorrowed computedValues);
 #include "nsStyleStructList.h"
 #undef STYLE_STRUCT
 
 } // extern "C"
 
 #endif // mozilla_ServoBindings_h
--- a/layout/style/ServoStyleSet.cpp
+++ b/layout/style/ServoStyleSet.cpp
@@ -91,17 +91,17 @@ ServoStyleSet::ResolveStyleFor(Element* 
 }
 
 already_AddRefed<nsStyleContext>
 ServoStyleSet::GetContext(nsIContent* aContent,
                           nsStyleContext* aParentContext,
                           nsIAtom* aPseudoTag,
                           CSSPseudoElementType aPseudoType)
 {
-  RefPtr<ServoComputedValues> computedValues = dont_AddRef(Servo_GetComputedValues(aContent));
+  RefPtr<ServoComputedValues> computedValues = Servo_GetComputedValues(aContent).Consume();
   MOZ_ASSERT(computedValues);
   return GetContext(computedValues.forget(), aParentContext, aPseudoTag, aPseudoType);
 }
 
 already_AddRefed<nsStyleContext>
 ServoStyleSet::GetContext(already_AddRefed<ServoComputedValues> aComputedValues,
                           nsStyleContext* aParentContext,
                           nsIAtom* aPseudoTag,
@@ -140,17 +140,17 @@ ServoStyleSet::ResolveStyleForText(nsICo
 
 already_AddRefed<nsStyleContext>
 ServoStyleSet::ResolveStyleForOtherNonElement(nsStyleContext* aParentContext)
 {
   // The parent context can be null if the non-element share a style context
   // with the root of an anonymous subtree.
   ServoComputedValues* parent =
     aParentContext ? aParentContext->StyleSource().AsServoComputedValues() : nullptr;
-  RefPtr<ServoComputedValues> computedValues = dont_AddRef(Servo_InheritComputedValues(parent));
+  RefPtr<ServoComputedValues> computedValues = Servo_InheritComputedValues(parent).Consume();
   MOZ_ASSERT(computedValues);
 
   return GetContext(computedValues.forget(), aParentContext,
                     nsCSSAnonBoxes::mozOtherNonElement,
                     CSSPseudoElementType::AnonBox);
 }
 
 already_AddRefed<nsStyleContext>
@@ -162,19 +162,19 @@ ServoStyleSet::ResolvePseudoElementStyle
   if (aPseudoElement) {
     NS_ERROR("stylo: We don't support CSS_PSEUDO_ELEMENT_SUPPORTS_USER_ACTION_STATE yet");
   }
   MOZ_ASSERT(aParentContext);
   MOZ_ASSERT(aType < CSSPseudoElementType::Count);
   nsIAtom* pseudoTag = nsCSSPseudoElements::GetPseudoAtom(aType);
 
   RefPtr<ServoComputedValues> computedValues =
-    dont_AddRef(Servo_GetComputedValuesForPseudoElement(
+    Servo_GetComputedValuesForPseudoElement(
       aParentContext->StyleSource().AsServoComputedValues(),
-      aParentElement, pseudoTag, mRawSet.get(), /* is_probe = */ false));
+      aParentElement, pseudoTag, mRawSet.get(), /* is_probe = */ false).Consume();
   MOZ_ASSERT(computedValues);
 
   return GetContext(computedValues.forget(), aParentContext, pseudoTag, aType);
 }
 
 // aFlags is an nsStyleSet flags bitfield
 already_AddRefed<nsStyleContext>
 ServoStyleSet::ResolveAnonymousBoxStyle(nsIAtom* aPseudoTag,
@@ -186,18 +186,18 @@ ServoStyleSet::ResolveAnonymousBoxStyle(
   MOZ_ASSERT(aFlags == 0 ||
              aFlags == nsStyleSet::eSkipParentDisplayBasedStyleFixup);
   bool skipFixup = aFlags & nsStyleSet::eSkipParentDisplayBasedStyleFixup;
 
   ServoComputedValues* parentStyle =
     aParentContext ? aParentContext->StyleSource().AsServoComputedValues()
                    : nullptr;
   RefPtr<ServoComputedValues> computedValues =
-    dont_AddRef(Servo_GetComputedValuesForAnonymousBox(parentStyle, aPseudoTag,
-                                                       mRawSet.get()));
+    Servo_GetComputedValuesForAnonymousBox(parentStyle, aPseudoTag,
+                                                       mRawSet.get()).Consume();
 #ifdef DEBUG
   if (!computedValues) {
     nsString pseudo;
     aPseudoTag->ToString(pseudo);
     NS_ERROR(nsPrintfCString("stylo: could not get anon-box: %s",
              NS_ConvertUTF16toUTF8(pseudo).get()).get());
     MOZ_CRASH();
   }
@@ -357,19 +357,19 @@ ServoStyleSet::ProbePseudoElementStyle(E
                                        CSSPseudoElementType aType,
                                        nsStyleContext* aParentContext)
 {
   MOZ_ASSERT(aParentContext);
   MOZ_ASSERT(aType < CSSPseudoElementType::Count);
   nsIAtom* pseudoTag = nsCSSPseudoElements::GetPseudoAtom(aType);
 
   RefPtr<ServoComputedValues> computedValues =
-    dont_AddRef(Servo_GetComputedValuesForPseudoElement(
+    Servo_GetComputedValuesForPseudoElement(
       aParentContext->StyleSource().AsServoComputedValues(),
-      aParentElement, pseudoTag, mRawSet.get(), /* is_probe = */ true));
+      aParentElement, pseudoTag, mRawSet.get(), /* is_probe = */ true).Consume();
 
   if (!computedValues) {
     return nullptr;
   }
 
   // For :before and :after pseudo-elements, having display: none or no
   // 'content' property is equivalent to not having the pseudo-element
   // at all.
--- a/layout/style/ServoStyleSheet.cpp
+++ b/layout/style/ServoStyleSheet.cpp
@@ -81,21 +81,21 @@ ServoStyleSheet::ParseSheet(const nsAStr
   RefPtr<ThreadSafeURIHolder> referrer = new ThreadSafeURIHolder(aSheetURI);
   RefPtr<ThreadSafePrincipalHolder> principal =
     new ThreadSafePrincipalHolder(aSheetPrincipal);
 
   nsCString baseString;
   aBaseURI->GetSpec(baseString);
 
   NS_ConvertUTF16toUTF8 input(aInput);
-  mSheet = already_AddRefed<RawServoStyleSheet>(Servo_StylesheetFromUTF8Bytes(
+  mSheet = Servo_StylesheetFromUTF8Bytes(
       reinterpret_cast<const uint8_t*>(input.get()), input.Length(),
       mParsingMode,
       reinterpret_cast<const uint8_t*>(baseString.get()), baseString.Length(),
-      base, referrer, principal));
+      base, referrer, principal).Consume();
 }
 
 void
 ServoStyleSheet::DropSheet()
 {
   mSheet = nullptr;
 }
 
--- a/layout/style/StyleAnimationValue.cpp
+++ b/layout/style/StyleAnimationValue.cpp
@@ -494,16 +494,17 @@ StyleAnimationValue::ComputeDistance(nsC
   switch (commonUnit) {
     case eUnit_Null:
     case eUnit_Auto:
     case eUnit_None:
     case eUnit_Normal:
     case eUnit_UnparsedString:
     case eUnit_URL:
     case eUnit_CurrentColor:
+    case eUnit_DiscreteCSSValue:
       return false;
 
     case eUnit_Enumerated:
       switch (aProperty) {
         case eCSSProperty_font_stretch: {
           // just like eUnit_Integer.
           int32_t startInt = aStartValue.GetIntValue();
           int32_t endInt = aEndValue.GetIntValue();
@@ -2264,16 +2265,17 @@ StyleAnimationValue::AddWeighted(nsCSSPr
   switch (commonUnit) {
     case eUnit_Null:
     case eUnit_Auto:
     case eUnit_None:
     case eUnit_Normal:
     case eUnit_UnparsedString:
     case eUnit_URL:
     case eUnit_CurrentColor:
+    case eUnit_DiscreteCSSValue:
       return false;
 
     case eUnit_Enumerated:
       switch (aProperty) {
         case eCSSProperty_font_stretch: {
           // Animate just like eUnit_Integer.
           int32_t result = floor(aCoeff1 * double(aValue1.GetIntValue()) +
                                  aCoeff2 * double(aValue2.GetIntValue()));
@@ -3056,24 +3058,26 @@ StyleAnimationValue::UncomputeValue(nsCS
       // colors can be alone, or part of a paint server
       aSpecifiedValue.SetColorValue(aComputedValue.GetColorValue());
       break;
     case eUnit_CurrentColor:
       aSpecifiedValue.SetIntValue(NS_COLOR_CURRENTCOLOR, eCSSUnit_EnumColor);
       break;
     case eUnit_Calc:
     case eUnit_ObjectPosition:
-    case eUnit_URL: {
+    case eUnit_URL:
+    case eUnit_DiscreteCSSValue: {
       nsCSSValue* val = aComputedValue.GetCSSValueValue();
       // Sanity-check that the underlying unit in the nsCSSValue is what we
       // expect for our StyleAnimationValue::Unit:
       MOZ_ASSERT((unit == eUnit_Calc && val->GetUnit() == eCSSUnit_Calc) ||
                  (unit == eUnit_ObjectPosition &&
                   val->GetUnit() == eCSSUnit_Array) ||
-                 (unit == eUnit_URL && val->GetUnit() == eCSSUnit_URL),
+                 (unit == eUnit_URL && val->GetUnit() == eCSSUnit_URL) ||
+                 unit == eUnit_DiscreteCSSValue,
                  "unexpected unit");
       aSpecifiedValue = *val;
       break;
     }
     case eUnit_CSSValuePair: {
       // Rule node processing expects pair values to be collapsed to a
       // single value if both halves would be equal, for most but not
       // all properties.  At present, all animatable properties that
@@ -3592,18 +3596,21 @@ StyleAnimationValue::ExtractComputedValu
                                           StyleAnimationValue& aComputedValue)
 {
   MOZ_ASSERT(0 <= aProperty && aProperty < eCSSProperty_COUNT_no_shorthands,
              "bad property");
   const void* styleStruct =
     aStyleContext->StyleData(nsCSSProps::kSIDTable[aProperty]);
   ptrdiff_t ssOffset = nsCSSProps::kStyleStructOffsetTable[aProperty];
   nsStyleAnimType animType = nsCSSProps::kAnimTypeTable[aProperty];
-  MOZ_ASSERT(0 <= ssOffset || animType == eStyleAnimType_Custom,
-             "must be dealing with animatable property");
+  MOZ_ASSERT(0 <= ssOffset ||
+             animType == eStyleAnimType_Custom ||
+             animType == eStyleAnimType_Discrete,
+             "all animation types other than Custom and Discrete must " \
+             "specify a style struct offset to extract values from");
   switch (animType) {
     case eStyleAnimType_Custom:
       switch (aProperty) {
         // For border-width, ignore the border-image business (which
         // only exists until we update our implementation to the current
         // spec) and use GetComputedBorder
 
         #define BORDER_WIDTH_CASE(prop_, side_)                               \
@@ -4127,24 +4134,16 @@ StyleAnimationValue::ExtractComputedValu
       aComputedValue.SetAndAdoptCSSValuePairValue(pair.forget(),
                                                   eUnit_CSSValuePair);
       return true;
     }
     case eStyleAnimType_nscoord:
       aComputedValue.SetCoordValue(*static_cast<const nscoord*>(
         StyleDataAtOffset(styleStruct, ssOffset)));
       return true;
-    case eStyleAnimType_EnumU8:
-      aComputedValue.SetIntValue(*static_cast<const uint8_t*>(
-        StyleDataAtOffset(styleStruct, ssOffset)), eUnit_Enumerated);
-      if (aProperty == eCSSProperty_visibility) {
-        aComputedValue.SetIntValue(aComputedValue.GetIntValue(),
-                                   eUnit_Visibility);
-      }
-      return true;
     case eStyleAnimType_float:
       aComputedValue.SetFloatValue(*static_cast<const float*>(
         StyleDataAtOffset(styleStruct, ssOffset)));
       if (aProperty == eCSSProperty_font_size_adjust &&
           aComputedValue.GetFloatValue() == -1.0f) {
         // In nsStyleFont, we set mFont.sizeAdjust to -1.0 to represent
         // font-size-adjust: none.  Here, we have to treat this as a keyword
         // instead of a float value, to make sure we don't end up doing
@@ -4208,16 +4207,30 @@ StyleAnimationValue::ExtractComputedValu
       nsCSSValueList **resultTail = getter_Transfers(result);
       for (uint32_t i = 0, i_end = shadowArray->Length(); i < i_end; ++i) {
         AppendCSSShadowValue(shadowArray->ShadowAt(i), resultTail);
       }
       aComputedValue.SetAndAdoptCSSValueListValue(result.forget(),
                                                   eUnit_Shadow);
       return true;
     }
+    case eStyleAnimType_Discrete: {
+      if (aProperty == eCSSProperty_visibility) {
+        aComputedValue.SetIntValue(
+          static_cast<const nsStyleVisibility*>(styleStruct)->mVisible,
+          eUnit_Visibility);
+        return true;
+      }
+      auto cssValue = MakeUnique<nsCSSValue>(eCSSUnit_Unset);
+      aStyleContext->RuleNode()->GetDiscretelyAnimatedCSSValue(aProperty,
+                                                               cssValue.get());
+      aComputedValue.SetAndAdoptCSSValueValue(cssValue.release(),
+                                              eUnit_DiscreteCSSValue);
+      return true;
+    }
     case eStyleAnimType_None:
       NS_NOTREACHED("shouldn't use on non-animatable properties");
   }
   return false;
 }
 
 gfxSize
 StyleAnimationValue::GetScaleValue(const nsIFrame* aForFrame) const
@@ -4313,16 +4326,17 @@ StyleAnimationValue::operator=(const Sty
       MOZ_ASSERT(!mozilla::IsNaN(mValue.mFloat));
       break;
     case eUnit_Color:
       mValue.mColor = aOther.mValue.mColor;
       break;
     case eUnit_Calc:
     case eUnit_ObjectPosition:
     case eUnit_URL:
+    case eUnit_DiscreteCSSValue:
       MOZ_ASSERT(IsCSSValueUnit(mUnit),
                  "This clause is for handling nsCSSValue-backed units");
       MOZ_ASSERT(aOther.mValue.mCSSValue, "values may not be null");
       mValue.mCSSValue = new nsCSSValue(*aOther.mValue.mCSSValue);
       break;
     case eUnit_CSSValuePair:
       MOZ_ASSERT(aOther.mValue.mCSSValuePair,
                  "value pairs may not be null");
@@ -4590,16 +4604,17 @@ StyleAnimationValue::operator==(const St
     case eUnit_Percent:
     case eUnit_Float:
       return mValue.mFloat == aOther.mValue.mFloat;
     case eUnit_Color:
       return mValue.mColor == aOther.mValue.mColor;
     case eUnit_Calc:
     case eUnit_ObjectPosition:
     case eUnit_URL:
+    case eUnit_DiscreteCSSValue:
       MOZ_ASSERT(IsCSSValueUnit(mUnit),
                  "This clause is for handling nsCSSValue-backed units");
       return *mValue.mCSSValue == *aOther.mValue.mCSSValue;
     case eUnit_CSSValuePair:
       return *mValue.mCSSValuePair == *aOther.mValue.mCSSValuePair;
     case eUnit_CSSValueTriplet:
       return *mValue.mCSSValueTriplet == *aOther.mValue.mCSSValueTriplet;
     case eUnit_CSSRect:
--- a/layout/style/StyleAnimationValue.h
+++ b/layout/style/StyleAnimationValue.h
@@ -272,16 +272,17 @@ public:
     eUnit_Float,
     eUnit_Color,
     eUnit_CurrentColor,
     eUnit_Calc, // nsCSSValue* (never null), always with a single
                 // calc() expression that's either length or length+percent
     eUnit_ObjectPosition, // nsCSSValue* (never null), always with a
                           // 4-entry nsCSSValue::Array
     eUnit_URL, // nsCSSValue* (never null), always with a css::URLValue
+    eUnit_DiscreteCSSValue, // nsCSSValue* (never null)
     eUnit_CSSValuePair, // nsCSSValuePair* (never null)
     eUnit_CSSValueTriplet, // nsCSSValueTriplet* (never null)
     eUnit_CSSRect, // nsCSSRect* (never null)
     eUnit_Dasharray, // nsCSSValueList* (never null)
     eUnit_Shadow, // nsCSSValueList* (may be null)
     eUnit_Shape,  // nsCSSValue::Array* (never null)
     eUnit_Filter, // nsCSSValueList* (may be null)
     eUnit_Transform, // nsCSSValueList* (never null)
@@ -472,17 +473,18 @@ private:
 
   static bool IsIntUnit(Unit aUnit) {
     return aUnit == eUnit_Enumerated || aUnit == eUnit_Visibility ||
            aUnit == eUnit_Integer;
   }
   static bool IsCSSValueUnit(Unit aUnit) {
     return aUnit == eUnit_Calc ||
            aUnit == eUnit_ObjectPosition ||
-           aUnit == eUnit_URL;
+           aUnit == eUnit_URL ||
+           aUnit == eUnit_DiscreteCSSValue;
   }
   static bool IsCSSValuePairUnit(Unit aUnit) {
     return aUnit == eUnit_CSSValuePair;
   }
   static bool IsCSSValueTripletUnit(Unit aUnit) {
     return aUnit == eUnit_CSSValueTriplet;
   }
   static bool IsCSSRectUnit(Unit aUnit) {
--- a/layout/style/nsCSSPropList.h
+++ b/layout/style/nsCSSPropList.h
@@ -329,37 +329,37 @@ CSS_PROP_POSITION(
     align-content,
     align_content,
     AlignContent,
     CSS_PROPERTY_PARSE_FUNCTION,
     "",
     VARIANT_HK,
     kAutoCompletionAlignJustifyContent,
     CSS_PROP_NO_OFFSET,
-    eStyleAnimType_None)
+    eStyleAnimType_Discrete)
 CSS_PROP_POSITION(
     align-items,
     align_items,
     AlignItems,
     CSS_PROPERTY_PARSE_FUNCTION,
     "",
     VARIANT_HK,
     kAutoCompletionAlignItems,
     CSS_PROP_NO_OFFSET,
-    eStyleAnimType_None)
+    eStyleAnimType_Discrete)
 CSS_PROP_POSITION(
     align-self,
     align_self,
     AlignSelf,
     CSS_PROPERTY_PARSE_FUNCTION,
     "",
     VARIANT_HK,
     kAutoCompletionAlignJustifySelf,
     CSS_PROP_NO_OFFSET,
-    eStyleAnimType_None)
+    eStyleAnimType_Discrete)
 CSS_PROP_SHORTHAND(
     all,
     all,
     All,
     CSS_PROPERTY_PARSE_FUNCTION,
     "layout.css.all-shorthand.enabled")
 CSS_PROP_SHORTHAND(
     animation,
@@ -1429,18 +1429,18 @@ CSS_PROP_SVGRESET(
 CSS_PROP_SVG(
     clip-rule,
     clip_rule,
     ClipRule,
     CSS_PROPERTY_PARSE_VALUE,
     "",
     VARIANT_HK,
     kFillRuleKTable,
-    offsetof(nsStyleSVG, mClipRule),
-    eStyleAnimType_EnumU8)
+    CSS_PROP_NO_OFFSET,
+    eStyleAnimType_Discrete)
 CSS_PROP_COLOR(
     color,
     color,
     Color,
     CSS_PROPERTY_PARSE_VALUE |
         CSS_PROPERTY_APPLIES_TO_FIRST_LETTER_AND_FIRST_LINE |
         CSS_PROPERTY_APPLIES_TO_PLACEHOLDER |
         CSS_PROPERTY_IGNORED_WHEN_COLORS_DISABLED |
@@ -1463,28 +1463,28 @@ CSS_PROP_VISIBILITY(
 CSS_PROP_SVG(
     color-interpolation,
     color_interpolation,
     ColorInterpolation,
     CSS_PROPERTY_PARSE_VALUE,
     "",
     VARIANT_HK,
     kColorInterpolationKTable,
-    offsetof(nsStyleSVG, mColorInterpolation),
-    eStyleAnimType_EnumU8)
+    CSS_PROP_NO_OFFSET,
+    eStyleAnimType_Discrete)
 CSS_PROP_SVG(
     color-interpolation-filters,
     color_interpolation_filters,
     ColorInterpolationFilters,
     CSS_PROPERTY_PARSE_VALUE,
     "",
     VARIANT_HK,
     kColorInterpolationKTable,
-    offsetof(nsStyleSVG, mColorInterpolationFilters),
-    eStyleAnimType_EnumU8)
+    CSS_PROP_NO_OFFSET,
+    eStyleAnimType_Discrete)
 CSS_PROP_COLUMN(
     -moz-column-count,
     _moz_column_count,
     CSS_PROP_DOMPROP_PREFIXED(ColumnCount),
     CSS_PROPERTY_PARSE_VALUE |
         // Need to reject 0 in addition to negatives.  If we accept 0, we
         // need to change NS_STYLE_COLUMN_COUNT_AUTO to something else.
         CSS_PROPERTY_VALUE_AT_LEAST_ONE,
@@ -1668,18 +1668,18 @@ CSS_PROP_DISPLAY(
 CSS_PROP_SVGRESET(
     dominant-baseline,
     dominant_baseline,
     DominantBaseline,
     CSS_PROPERTY_PARSE_VALUE,
     "",
     VARIANT_HK,
     kDominantBaselineKTable,
-    offsetof(nsStyleSVGReset, mDominantBaseline),
-    eStyleAnimType_EnumU8)
+    CSS_PROP_NO_OFFSET,
+    eStyleAnimType_Discrete)
 CSS_PROP_TABLEBORDER(
     empty-cells,
     empty_cells,
     EmptyCells,
     CSS_PROPERTY_PARSE_VALUE,
     "",
     VARIANT_HK,
     kEmptyCellsKTable,
@@ -1708,18 +1708,18 @@ CSS_PROP_SVG(
 CSS_PROP_SVG(
     fill-rule,
     fill_rule,
     FillRule,
     CSS_PROPERTY_PARSE_VALUE,
     "",
     VARIANT_HK,
     kFillRuleKTable,
-    offsetof(nsStyleSVG, mFillRule),
-    eStyleAnimType_EnumU8)
+    CSS_PROP_NO_OFFSET,
+    eStyleAnimType_Discrete)
 CSS_PROP_EFFECTS(
     filter,
     filter,
     Filter,
     CSS_PROPERTY_PARSE_FUNCTION |
         CSS_PROPERTY_CREATES_STACKING_CONTEXT |
         CSS_PROPERTY_FIXPOS_CB,
     "",
@@ -1751,18 +1751,18 @@ CSS_PROP_POSITION(
 CSS_PROP_POSITION(
     flex-direction,
     flex_direction,
     FlexDirection,
     CSS_PROPERTY_PARSE_VALUE,
     "",
     VARIANT_HK,
     kFlexDirectionKTable,
-    offsetof(nsStylePosition, mFlexDirection),
-    eStyleAnimType_EnumU8)
+    CSS_PROP_NO_OFFSET,
+    eStyleAnimType_Discrete)
 CSS_PROP_SHORTHAND(
     flex-flow,
     flex_flow,
     FlexFlow,
     CSS_PROPERTY_PARSE_FUNCTION,
     "")
 CSS_PROP_POSITION(
     flex-grow,
@@ -1795,18 +1795,18 @@ CSS_PROP_POSITION(
 CSS_PROP_POSITION(
     flex-wrap,
     flex_wrap,
     FlexWrap,
     CSS_PROPERTY_PARSE_VALUE,
     "",
     VARIANT_HK,
     kFlexWrapKTable,
-    offsetof(nsStylePosition, mFlexWrap),
-    eStyleAnimType_EnumU8)
+    CSS_PROP_NO_OFFSET,
+    eStyleAnimType_Discrete)
 CSS_PROP_DISPLAY(
     float,
     float,
     CSS_PROP_PUBLIC_OR_PRIVATE(CssFloat, Float),
     CSS_PROPERTY_PARSE_VALUE |
         CSS_PROPERTY_APPLIES_TO_FIRST_LETTER,
     "",
     VARIANT_HK,
@@ -1945,18 +1945,18 @@ CSS_PROP_FONT(
     font_style,
     FontStyle,
     CSS_PROPERTY_PARSE_VALUE |
         CSS_PROPERTY_APPLIES_TO_FIRST_LETTER_AND_FIRST_LINE |
         CSS_PROPERTY_APPLIES_TO_PLACEHOLDER,
     "",
     VARIANT_HK | VARIANT_SYSFONT,
     kFontStyleKTable,
-    offsetof(nsStyleFont, mFont.style),
-    eStyleAnimType_EnumU8)
+    CSS_PROP_NO_OFFSET,
+    eStyleAnimType_Discrete)
 CSS_PROP_FONT(
     font-synthesis,
     font_synthesis,
     FontSynthesis,
     CSS_PROPERTY_PARSE_VALUE |
         CSS_PROPERTY_VALUE_PARSER_FUNCTION |
         CSS_PROPERTY_APPLIES_TO_FIRST_LETTER_AND_FIRST_LINE |
         CSS_PROPERTY_APPLIES_TO_PLACEHOLDER,
@@ -2303,18 +2303,18 @@ CSS_PROP_LIST(
 CSS_PROP_VISIBILITY(
     image-rendering,
     image_rendering,
     ImageRendering,
     CSS_PROPERTY_PARSE_VALUE,
     "",
     VARIANT_HK,
     kImageRenderingKTable,
-    offsetof(nsStyleVisibility, mImageRendering),
-    eStyleAnimType_EnumU8)
+    CSS_PROP_NO_OFFSET,
+    eStyleAnimType_Discrete)
 CSS_PROP_UIRESET(
     ime-mode,
     ime_mode,
     ImeMode,
     CSS_PROPERTY_PARSE_VALUE,
     "",
     VARIANT_HK,
     kIMEModeKTable,
@@ -2353,38 +2353,38 @@ CSS_PROP_POSITION(
     justify-content,
     justify_content,
     JustifyContent,
     CSS_PROPERTY_PARSE_FUNCTION,
     "",
     VARIANT_HK,
     kAutoCompletionAlignJustifyContent,
     CSS_PROP_NO_OFFSET,
-    eStyleAnimType_None)
+    eStyleAnimType_Discrete)
 CSS_PROP_POSITION(
     justify-items,
     justify_items,
     JustifyItems,
     CSS_PROPERTY_PARSE_FUNCTION,
     "",
     VARIANT_HK,
     // for auto-completion we use same values as justify-self:
     kAutoCompletionAlignJustifySelf,
     CSS_PROP_NO_OFFSET,
-    eStyleAnimType_None)
+    eStyleAnimType_Discrete)
 CSS_PROP_POSITION(
     justify-self,
     justify_self,
     JustifySelf,
     CSS_PROPERTY_PARSE_FUNCTION,
     "",
     VARIANT_HK,
     kAutoCompletionAlignJustifySelf,
     CSS_PROP_NO_OFFSET,
-    eStyleAnimType_None)
+    eStyleAnimType_Discrete)
 #ifndef CSS_PROP_LIST_ONLY_COMPONENTS_OF_ALL_SHORTHAND
 #ifndef CSS_PROP_LIST_EXCLUDE_INTERNAL
 CSS_PROP_FONT(
     -x-lang,
     _x_lang,
     Lang,
     CSS_PROPERTY_INTERNAL |
         CSS_PROPERTY_PARSE_INACCESSIBLE,
@@ -2804,18 +2804,18 @@ CSS_PROP_SVGRESET(
 CSS_PROP_SVGRESET(
     mask-type,
     mask_type,
     MaskType,
     CSS_PROPERTY_PARSE_VALUE,
     "layout.css.masking.enabled",
     VARIANT_HK,
     kMaskTypeKTable,
-    offsetof(nsStyleSVGReset, mMaskType),
-    eStyleAnimType_EnumU8)
+    CSS_PROP_NO_OFFSET,
+    eStyleAnimType_Discrete)
 #ifndef CSS_PROP_LIST_ONLY_COMPONENTS_OF_ALL_SHORTHAND
 #ifndef CSS_PROP_LIST_EXCLUDE_INTERNAL
 CSS_PROP_FONT(
     -moz-math-display,
     math_display,
     MathDisplay,
     CSS_PROPERTY_INTERNAL |
         CSS_PROPERTY_ENABLED_IN_UA_SHEETS |
@@ -3503,18 +3503,18 @@ CSS_PROP_USERINTERFACE(
     pointer-events,
     pointer_events,
     PointerEvents,
     CSS_PROPERTY_PARSE_VALUE |
         CSS_PROPERTY_APPLIES_TO_PLACEHOLDER,
     "",
     VARIANT_HK,
     kPointerEventsKTable,
-    offsetof(nsStyleUserInterface, mPointerEvents),
-    eStyleAnimType_EnumU8)
+    CSS_PROP_NO_OFFSET,
+    eStyleAnimType_Discrete)
 CSS_PROP_DISPLAY(
     position,
     position,
     Position,
     CSS_PROPERTY_PARSE_VALUE |
         // For position: sticky/fixed
         CSS_PROPERTY_CREATES_STACKING_CONTEXT |
         CSS_PROPERTY_ABSPOS_CB,
@@ -3563,28 +3563,28 @@ CSS_PROP_POSITION(
 CSS_PROP_TEXT(
     ruby-align,
     ruby_align,
     RubyAlign,
     CSS_PROPERTY_PARSE_VALUE,
     "",
     VARIANT_HK,
     kRubyAlignKTable,
-    offsetof(nsStyleText, mRubyAlign),
-    eStyleAnimType_EnumU8)
+    CSS_PROP_NO_OFFSET,
+    eStyleAnimType_Discrete)
 CSS_PROP_TEXT(
     ruby-position,
     ruby_position,
     RubyPosition,
     CSS_PROPERTY_PARSE_VALUE,
     "",
     VARIANT_HK,
     kRubyPositionKTable,
-    offsetof(nsStyleText, mRubyPosition),
-    eStyleAnimType_EnumU8)
+    CSS_PROP_NO_OFFSET,
+    eStyleAnimType_Discrete)
 #ifndef CSS_PROP_LIST_ONLY_COMPONENTS_OF_ALL_SHORTHAND
 #ifndef CSS_PROP_LIST_EXCLUDE_INTERNAL
 CSS_PROP_FONT(
     -moz-script-level,
     script_level,
     ScriptLevel,
     // We only allow 'script-level' when unsafe rules are enabled, because
     // otherwise it could interfere with rulenode optimizations if used in
@@ -3724,18 +3724,18 @@ CSS_PROP_DISPLAY(
 CSS_PROP_SVG(
     shape-rendering,
     shape_rendering,
     ShapeRendering,
     CSS_PROPERTY_PARSE_VALUE,
     "",
     VARIANT_HK,
     kShapeRenderingKTable,
-    offsetof(nsStyleSVG, mShapeRendering),
-    eStyleAnimType_EnumU8)
+    CSS_PROP_NO_OFFSET,
+    eStyleAnimType_Discrete)
 #ifndef CSS_PROP_LIST_ONLY_COMPONENTS_OF_ALL_SHORTHAND
 #ifndef CSS_PROP_LIST_EXCLUDE_INTERNAL
 CSS_PROP_TABLE(
     -x-span,
     _x_span,
     Span,
     CSS_PROPERTY_INTERNAL |
         CSS_PROPERTY_PARSE_INACCESSIBLE,
@@ -3813,28 +3813,28 @@ CSS_PROP_SVG(
 CSS_PROP_SVG(
     stroke-linecap,
     stroke_linecap,
     StrokeLinecap,
     CSS_PROPERTY_PARSE_VALUE,
     "",
     VARIANT_HK,
     kStrokeLinecapKTable,
-    offsetof(nsStyleSVG, mStrokeLinecap),
-    eStyleAnimType_EnumU8)
+    CSS_PROP_NO_OFFSET,
+    eStyleAnimType_Discrete)
 CSS_PROP_SVG(
     stroke-linejoin,
     stroke_linejoin,
     StrokeLinejoin,
     CSS_PROPERTY_PARSE_VALUE,
     "",
     VARIANT_HK,
     kStrokeLinejoinKTable,
-    offsetof(nsStyleSVG, mStrokeLinejoin),
-    eStyleAnimType_EnumU8)
+    CSS_PROP_NO_OFFSET,
+    eStyleAnimType_Discrete)
 CSS_PROP_SVG(
     stroke-miterlimit,
     stroke_miterlimit,
     StrokeMiterlimit,
     CSS_PROPERTY_PARSE_VALUE |
         CSS_PROPERTY_VALUE_AT_LEAST_ONE,
     "",
     VARIANT_HN,
@@ -3925,29 +3925,29 @@ CSS_PROP_TEXT(
 CSS_PROP_SVG(
     text-anchor,
     text_anchor,
     TextAnchor,
     CSS_PROPERTY_PARSE_VALUE,
     "",
     VARIANT_HK,
     kTextAnchorKTable,
-    offsetof(nsStyleSVG, mTextAnchor),
-    eStyleAnimType_EnumU8)
+    CSS_PROP_NO_OFFSET,
+    eStyleAnimType_Discrete)
 CSS_PROP_TEXT(
     text-combine-upright,
     text_combine_upright,
     TextCombineUpright,
     CSS_PROPERTY_PARSE_VALUE |
         CSS_PROPERTY_VALUE_PARSER_FUNCTION,
     "layout.css.text-combine-upright.enabled",
     0,
     kTextCombineUprightKTable,
-    offsetof(nsStyleText, mTextCombineUpright),
-    eStyleAnimType_EnumU8)
+    CSS_PROP_NO_OFFSET,
+    eStyleAnimType_Discrete)
 CSS_PROP_SHORTHAND(
     text-decoration,
     text_decoration,
     TextDecoration,
     CSS_PROPERTY_PARSE_FUNCTION,
     "")
 CSS_PROP_TEXTRESET(
     text-decoration-color,
@@ -3968,18 +3968,18 @@ CSS_PROP_TEXTRESET(
     TextDecorationLine,
     CSS_PROPERTY_PARSE_VALUE |
         CSS_PROPERTY_VALUE_PARSER_FUNCTION |
         CSS_PROPERTY_APPLIES_TO_FIRST_LETTER_AND_FIRST_LINE |
         CSS_PROPERTY_APPLIES_TO_PLACEHOLDER,
     "",
     0,
     kTextDecorationLineKTable,
-    offsetof(nsStyleTextReset, mTextDecorationLine),
-    eStyleAnimType_EnumU8)
+    CSS_PROP_NO_OFFSET,
+    eStyleAnimType_Discrete)
 CSS_PROP_TEXTRESET(
     text-decoration-style,
     text_decoration_style,
     TextDecorationStyle,
     CSS_PROPERTY_PARSE_VALUE |
         CSS_PROPERTY_APPLIES_TO_FIRST_LETTER_AND_FIRST_LINE |
         CSS_PROPERTY_APPLIES_TO_PLACEHOLDER,
     "",
@@ -4056,18 +4056,18 @@ CSS_PROP_VISIBILITY(
     text-orientation,
     text_orientation,
     TextOrientation,
     CSS_PROPERTY_PARSE_VALUE |
         CSS_PROPERTY_ENABLED_IN_UA_SHEETS,
     "layout.css.vertical-text.enabled",
     VARIANT_HK,
     kTextOrientationKTable,
-    offsetof(nsStyleVisibility, mTextOrientation),
-    eStyleAnimType_EnumU8)
+    CSS_PROP_NO_OFFSET,
+    eStyleAnimType_Discrete)
 CSS_PROP_TEXTRESET(
     text-overflow,
     text_overflow,
     TextOverflow,
     CSS_PROPERTY_PARSE_VALUE |
         CSS_PROPERTY_VALUE_PARSER_FUNCTION |
         CSS_PROPERTY_APPLIES_TO_PLACEHOLDER,
     "",
@@ -4078,18 +4078,18 @@ CSS_PROP_TEXTRESET(
 CSS_PROP_TEXT(
     text-rendering,
     text_rendering,
     TextRendering,
     CSS_PROPERTY_PARSE_VALUE,
     "",
     VARIANT_HK,
     kTextRenderingKTable,
-    offsetof(nsStyleText, mTextRendering),
-    eStyleAnimType_EnumU8)
+    CSS_PROP_NO_OFFSET,
+    eStyleAnimType_Discrete)
 CSS_PROP_TEXT(
     text-shadow,
     text_shadow,
     TextShadow,
     CSS_PROPERTY_PARSE_FUNCTION |
         CSS_PROPERTY_APPLIES_TO_FIRST_LETTER_AND_FIRST_LINE |
         CSS_PROPERTY_APPLIES_TO_PLACEHOLDER |
         CSS_PROPERTY_VALUE_LIST_USES_COMMAS |
@@ -4369,18 +4369,18 @@ CSS_PROP_UIRESET(
 CSS_PROP_SVGRESET(
     vector-effect,
     vector_effect,
     VectorEffect,
     CSS_PROPERTY_PARSE_VALUE,
     "",
     VARIANT_HK,
     kVectorEffectKTable,
-    offsetof(nsStyleSVGReset, mVectorEffect),
-    eStyleAnimType_EnumU8)
+    CSS_PROP_NO_OFFSET,
+    eStyleAnimType_Discrete)
 // NOTE: vertical-align is only supposed to apply to :first-letter when
 // 'float' is 'none', but we don't worry about that since it has no
 // effect otherwise
 CSS_PROP_DISPLAY(
     vertical-align,
     vertical_align,
     VerticalAlign,
     CSS_PROPERTY_PARSE_VALUE |
@@ -4397,18 +4397,18 @@ CSS_PROP_DISPLAY(
 CSS_PROP_VISIBILITY(
     visibility,
     visibility,
     Visibility,
     CSS_PROPERTY_PARSE_VALUE,
     "",
     VARIANT_HK,
     kVisibilityKTable,
-    offsetof(nsStyleVisibility, mVisible),
-    eStyleAnimType_EnumU8)  // reflow for collapse
+    CSS_PROP_NO_OFFSET,
+    eStyleAnimType_Discrete)  // reflow for collapse
 CSS_PROP_TEXT(
     white-space,
     white_space,
     WhiteSpace,
     CSS_PROPERTY_PARSE_VALUE |
         // This is required by the UA stylesheet and can't be overridden.
         CSS_PROPERTY_APPLIES_TO_PLACEHOLDER,
     "",
@@ -4468,18 +4468,18 @@ CSS_PROP_UIRESET(
 CSS_PROP_TEXT(
     word-break,
     word_break,
     WordBreak,
     CSS_PROPERTY_PARSE_VALUE,
     "",
     VARIANT_HK,
     kWordBreakKTable,
-    offsetof(nsStyleText, mWordBreak),
-    eStyleAnimType_EnumU8)
+    CSS_PROP_NO_OFFSET,
+    eStyleAnimType_Discrete)
 CSS_PROP_TEXT(
     word-spacing,
     word_spacing,
     WordSpacing,
     CSS_PROPERTY_PARSE_VALUE |
         CSS_PROPERTY_APPLIES_TO_FIRST_LETTER_AND_FIRST_LINE |
         CSS_PROPERTY_APPLIES_TO_PLACEHOLDER |
         CSS_PROPERTY_UNITLESS_LENGTH_QUIRK |
@@ -4503,18 +4503,18 @@ CSS_PROP_VISIBILITY(
     writing-mode,
     writing_mode,
     WritingMode,
     CSS_PROPERTY_PARSE_VALUE |
         CSS_PROPERTY_ENABLED_IN_UA_SHEETS,
     "layout.css.vertical-text.enabled",
     VARIANT_HK,
     kWritingModeKTable,
-    offsetof(nsStyleVisibility, mWritingMode),
-    eStyleAnimType_EnumU8)
+    CSS_PROP_NO_OFFSET,
+    eStyleAnimType_Discrete)
 CSS_PROP_POSITION(
     z-index,
     z_index,
     ZIndex,
     CSS_PROPERTY_PARSE_VALUE |
         CSS_PROPERTY_CREATES_STACKING_CONTEXT,
     "",
     VARIANT_AHI,
--- a/layout/style/nsCSSProps.cpp
+++ b/layout/style/nsCSSProps.cpp
@@ -2286,18 +2286,18 @@ const KTableEntry nsCSSProps::kDominantB
   { eCSSKeyword_central, NS_STYLE_DOMINANT_BASELINE_CENTRAL },
   { eCSSKeyword_middle, NS_STYLE_DOMINANT_BASELINE_MIDDLE },
   { eCSSKeyword_text_after_edge, NS_STYLE_DOMINANT_BASELINE_TEXT_AFTER_EDGE },
   { eCSSKeyword_text_before_edge, NS_STYLE_DOMINANT_BASELINE_TEXT_BEFORE_EDGE },
   { eCSSKeyword_UNKNOWN, -1 }
 };
 
 const KTableEntry nsCSSProps::kFillRuleKTable[] = {
-  { eCSSKeyword_nonzero, NS_STYLE_FILL_RULE_NONZERO },
-  { eCSSKeyword_evenodd, NS_STYLE_FILL_RULE_EVENODD },
+  { eCSSKeyword_nonzero, StyleFillRule::NonZero },
+  { eCSSKeyword_evenodd, StyleFillRule::EvenOdd },
   { eCSSKeyword_UNKNOWN, -1 }
 };
 
 const KTableEntry nsCSSProps::kClipPathGeometryBoxKTable[] = {
   { eCSSKeyword_content_box, StyleClipPathGeometryBox::Content },
   { eCSSKeyword_padding_box, StyleClipPathGeometryBox::Padding },
   { eCSSKeyword_border_box, StyleClipPathGeometryBox::Border },
   { eCSSKeyword_margin_box, StyleClipPathGeometryBox::Margin },
--- a/layout/style/nsCSSProps.h
+++ b/layout/style/nsCSSProps.h
@@ -303,34 +303,31 @@ enum nsStyleAnimType {
   eStyleAnimType_Corner_TopLeft,
   eStyleAnimType_Corner_TopRight,
   eStyleAnimType_Corner_BottomRight,
   eStyleAnimType_Corner_BottomLeft,
 
   // nscoord values
   eStyleAnimType_nscoord,
 
-  // enumerated values (stored in a uint8_t)
-  // In order for a property to use this unit, _all_ of its enumerated values
-  // must be listed in its keyword table, so that any enumerated value can be
-  // converted into a string via a nsCSSValue of type eCSSUnit_Enumerated.
-  eStyleAnimType_EnumU8,
-
   // float values
   eStyleAnimType_float,
 
   // nscolor values
   eStyleAnimType_Color,
 
   // nsStyleSVGPaint values
   eStyleAnimType_PaintServer,
 
   // RefPtr<nsCSSShadowArray> values
   eStyleAnimType_Shadow,
 
+  // discrete values
+  eStyleAnimType_Discrete,
+
   // property not animatable
   eStyleAnimType_None
 };
 
 namespace mozilla {
 
 // Type trait that determines whether the integral or enum type Type can fit
 // within the integral type Storage without loss.
--- a/layout/style/nsComputedDOMStyle.cpp
+++ b/layout/style/nsComputedDOMStyle.cpp
@@ -5925,17 +5925,17 @@ nsComputedDOMStyle::CreatePrimitiveValue
   nsAutoString shapeFunctionString;
   AppendASCIItoUTF16(nsCSSKeywords::GetStringValue(
                        aStyleBasicShape->GetShapeTypeName()),
                      shapeFunctionString);
   shapeFunctionString.Append('(');
   switch (type) {
     case StyleBasicShapeType::Polygon: {
       bool hasEvenOdd = aStyleBasicShape->GetFillRule() ==
-        NS_STYLE_FILL_RULE_EVENODD;
+        StyleFillRule::EvenOdd;
       if (hasEvenOdd) {
         shapeFunctionString.AppendLiteral("evenodd");
       }
       for (size_t i = 0;
            i < aStyleBasicShape->Coordinates().Length(); i += 2) {
         nsAutoString coordString;
         if (i > 0 || hasEvenOdd) {
           shapeFunctionString.AppendLiteral(", ");
--- a/layout/style/nsHTMLStyleSheet.cpp
+++ b/layout/style/nsHTMLStyleSheet.cpp
@@ -53,16 +53,24 @@ nsHTMLStyleSheet::HTMLColorRule::MapRule
 }
 
 /* virtual */ bool
 nsHTMLStyleSheet::HTMLColorRule::MightMapInheritedStyleData()
 {
   return true;
 }
 
+/* virtual */ bool
+nsHTMLStyleSheet::HTMLColorRule::
+GetDiscretelyAnimatedCSSValue(nsCSSPropertyID aProperty, nsCSSValue* aValue)
+{
+  MOZ_ASSERT(false, "GetDiscretelyAnimatedCSSValue is not implemented yet");
+  return false;
+}
+
 #ifdef DEBUG
 /* virtual */ void
 nsHTMLStyleSheet::HTMLColorRule::List(FILE* out, int32_t aIndent) const
 {
   nsAutoCString indentStr;
   for (int32_t index = aIndent; --index >= 0; ) {
     indentStr.AppendLiteral("  ");
   }
@@ -98,16 +106,24 @@ nsHTMLStyleSheet::TableTHRule::MapRuleIn
 }
 
 /* virtual */ bool
 nsHTMLStyleSheet::TableTHRule::MightMapInheritedStyleData()
 {
   return true;
 }
 
+/* virtual */ bool
+nsHTMLStyleSheet::TableTHRule::
+GetDiscretelyAnimatedCSSValue(nsCSSPropertyID aProperty, nsCSSValue* aValue)
+{
+  MOZ_ASSERT(false, "GetDiscretelyAnimatedCSSValue is not implemented yet");
+  return false;
+}
+
 /* virtual */ void
 nsHTMLStyleSheet::TableQuirkColorRule::MapRuleInfoInto(nsRuleData* aRuleData)
 {
   if (aRuleData->mSIDs & NS_STYLE_INHERIT_BIT(Color)) {
     nsCSSValue* color = aRuleData->ValueForColor();
     // We do not check UseDocumentColors() here, because we want to
     // use the body color no matter what.
     if (color->GetUnit() == eCSSUnit_Null)
@@ -117,16 +133,23 @@ nsHTMLStyleSheet::TableQuirkColorRule::M
 }
 
 /* virtual */ bool
 nsHTMLStyleSheet::TableQuirkColorRule::MightMapInheritedStyleData()
 {
   return true;
 }
 
+/* virtual */ bool
+nsHTMLStyleSheet::TableQuirkColorRule::
+GetDiscretelyAnimatedCSSValue(nsCSSPropertyID aProperty, nsCSSValue* aValue)
+{
+  MOZ_ASSERT(false, "GetDiscretelyAnimatedCSSValue is not implemented yet");
+  return false;
+}
 
 NS_IMPL_ISUPPORTS(nsHTMLStyleSheet::LangRule, nsIStyleRule)
 
 /* virtual */ void
 nsHTMLStyleSheet::LangRule::MapRuleInfoInto(nsRuleData* aRuleData)
 {
   if (aRuleData->mSIDs & NS_STYLE_INHERIT_BIT(Font)) {
     nsCSSValue* lang = aRuleData->ValueForLang();
@@ -137,16 +160,24 @@ nsHTMLStyleSheet::LangRule::MapRuleInfoI
 }
 
 /* virtual */ bool
 nsHTMLStyleSheet::LangRule::MightMapInheritedStyleData()
 {
   return true;
 }
 
+/* virtual */ bool
+nsHTMLStyleSheet::LangRule::
+GetDiscretelyAnimatedCSSValue(nsCSSPropertyID aProperty, nsCSSValue* aValue)
+{
+  MOZ_ASSERT(false, "GetDiscretelyAnimatedCSSValue is not implemented yet");
+  return false;
+}
+
 #ifdef DEBUG
 /* virtual */ void
 nsHTMLStyleSheet::LangRule::List(FILE* out, int32_t aIndent) const
 {
   nsAutoCString str;
   for (int32_t index = aIndent; --index >= 0; ) {
     str.AppendLiteral("  ");
   }
--- a/layout/style/nsHTMLStyleSheet.h
+++ b/layout/style/nsH