Merge autoland to m-c. a=merge
authorRyan VanderMeulen <ryanvm@gmail.com>
Sat, 29 Oct 2016 09:12:45 -0400
changeset 320144 dc422956242bacfbf88d716f5b967d2c985b913b
parent 320143 16cdd6273c48ea8e7dbc99e343a50b9ca9789715 (current diff)
parent 320108 a3937bf608bc8b5479dcfb1a7bc92b5f9037a430 (diff)
child 320145 14dad7588de8ea91e5eefb4045eb32993c98b7ae
child 320184 309ecb16acfe18bcf53d42497d0c3a489b43bc9e
push id20749
push userryanvm@gmail.com
push dateSat, 29 Oct 2016 13:21:21 +0000
treeherderfx-team@1b170b39ed6b [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmerge
milestone52.0a1
Merge autoland to m-c. a=merge
browser/base/content/test/general/domplate_test.js
browser/base/content/urlbarBindings.xml
browser/components/extensions/ext-utils.js
browser/components/extensions/schemas/tabs.json
browser/components/extensions/test/browser/browser.ini
browser/modules/test/browser.ini
dom/html/HTMLMediaElement.cpp
dom/media/MediaPrefs.h
dom/webidl/PresentationConnectionClosedEvent.webidl
dom/webidl/moz.build
js/src/builtin/Date.js
js/src/builtin/Intl.cpp
js/src/builtin/Intl.h
js/src/builtin/Intl.js
js/src/builtin/make_intl_data.py
js/src/jsapi-tests/testGCOutOfMemory.cpp
js/src/moz.build
js/src/tests/jstests.list
js/src/vm/SelfHosting.cpp
modules/libpref/init/all.js
testing/mochitest/runtests.py
testing/web-platform/meta/presentation-api/controlling-ua/startNewPresentation_mixedcontent-manual.https.html.ini
testing/web-platform/meta/presentation-api/controlling-ua/startNewPresentation_mixedcontent_multiple-manual.https.html.ini
testing/web-platform/tests/presentation-api/controlling-ua/startNewPresentation_error-manual.html
toolkit/components/telemetry/Histograms.json
--- a/browser/base/content/browser-media.js
+++ b/browser/base/content/browser-media.js
@@ -351,14 +351,14 @@ let gDecoderDoctorHandler = {
       // first time we get this resolution -> Clear prefs and report telemetry.
       Services.prefs.clearUserPref(formatsPref);
       Services.prefs.clearUserPref(buttonClickedPref);
       histogram.add(decoderDoctorReportId, TELEMETRY_DDSTAT_SOLVED);
     }
   },
 }
 
-window.messageManager.addMessageListener("DecoderDoctor:Notification", gDecoderDoctorHandler);
-window.messageManager.addMessageListener("EMEVideo:ContentMediaKeysRequest", gEMEHandler);
+window.getGroupMessageManager("browsers").addMessageListener("DecoderDoctor:Notification", gDecoderDoctorHandler);
+window.getGroupMessageManager("browsers").addMessageListener("EMEVideo:ContentMediaKeysRequest", gEMEHandler);
 window.addEventListener("unload", function() {
-  window.messageManager.removeMessageListener("EMEVideo:ContentMediaKeysRequest", gEMEHandler);
-  window.messageManager.removeMessageListener("DecoderDoctor:Notification", gDecoderDoctorHandler);
+  window.getGroupMessageManager("browsers").removeMessageListener("EMEVideo:ContentMediaKeysRequest", gEMEHandler);
+  window.getGroupMessageManager("browsers").removeMessageListener("DecoderDoctor:Notification", gDecoderDoctorHandler);
 }, false);
--- a/browser/base/content/browser-places.js
+++ b/browser/base/content/browser-places.js
@@ -840,17 +840,17 @@ var BookmarksEventHandler = {
 
     if (aEvent.button == 2 || (aEvent.button == 0 && !modifKey))
       return;
 
     var target = aEvent.originalTarget;
     // If this event bubbled up from a menu or menuitem, close the menus.
     // Do this before opening tabs, to avoid hiding the open tabs confirm-dialog.
     if (target.localName == "menu" || target.localName == "menuitem") {
-      for (node = target.parentNode; node; node = node.parentNode) {
+      for (let node = target.parentNode; node; node = node.parentNode) {
         if (node.localName == "menupopup")
           node.hidePopup();
         else if (node.localName != "menu" &&
                  node.localName != "splitmenu" &&
                  node.localName != "hbox" &&
                  node.localName != "vbox" )
           break;
       }
@@ -1996,9 +1996,8 @@ var AutoShowBookmarksToolbar = {
     let placement = CustomizableUI.getPlacementOfWidget("personal-bookmarks");
     let area = placement && placement.area;
     if (area != CustomizableUI.AREA_BOOKMARKS)
       return;
 
     setToolbarVisibility(toolbar, true);
   }
 };
-
--- a/browser/base/content/test/general/browser.ini
+++ b/browser/base/content/test/general/browser.ini
@@ -24,17 +24,16 @@ support-files =
   bug792517-2.html
   bug792517.html
   bug792517.sjs
   bug839103.css
   clipboard_pastefile.html
   contextmenu_common.js
   ctxmenu-image.png
   discovery.html
-  domplate_test.js
   download_page.html
   dummy_page.html
   feed_tab.html
   file_generic_favicon.ico
   file_with_favicon.html
   file_bug822367_1.html
   file_bug822367_1.js
   file_bug822367_2.html
--- a/browser/base/content/test/general/browser_bug409624.js
+++ b/browser/base/content/test/general/browser_bug409624.js
@@ -3,20 +3,20 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 XPCOMUtils.defineLazyModuleGetter(this, "FormHistory",
                                   "resource://gre/modules/FormHistory.jsm");
 
 add_task(function* test() {
   // This test relies on the form history being empty to start with delete
   // all the items first.
-  yield new Promise(resolve => {
+  yield new Promise((resolve, reject) => {
     FormHistory.update({ op: "remove" },
                        { handleError(error) {
-                           do_throw("Error occurred updating form history: " + error);
+                           reject(error);
                          },
                          handleCompletion(reason) {
                            if (!reason) {
                              resolve();
                            } else {
                              reject();
                            }
                          },
--- a/browser/base/content/test/general/browser_bug553455.js
+++ b/browser/base/content/test/general/browser_bug553455.js
@@ -143,32 +143,32 @@ function waitForInstallDialog() {
   return Task.spawn(function* () {
     if (Preferences.get("xpinstall.customConfirmationUI", false)) {
       yield waitForNotification("addon-install-confirmation");
       return;
     }
 
     info("Waiting for install dialog");
 
-    yield new Promise(resolve => {
+    let window = yield new Promise(resolve => {
       Services.wm.addListener({
         onOpenWindow: function(aXULWindow) {
           Services.wm.removeListener(this);
-          resolve();
+          resolve(aXULWindow);
         },
         onCloseWindow: function(aXULWindow) {
         },
         onWindowTitleChange: function(aXULWindow, aNewTitle) {
         }
       });
     });
     info("Install dialog opened, waiting for focus");
 
-    let domwindow = aXULWindow.QueryInterface(Ci.nsIInterfaceRequestor)
-                              .getInterface(Ci.nsIDOMWindow);
+    let domwindow = window.QueryInterface(Ci.nsIInterfaceRequestor)
+                          .getInterface(Ci.nsIDOMWindow);
     yield new Promise(resolve => {
       waitForFocus(function() {
         resolve();
       }, domwindow);
     });
     info("Saw install dialog");
     is(domwindow.document.location.href, XPINSTALL_URL, "Should have seen the right window open");
 
@@ -743,17 +743,16 @@ function test_localFile() {
     let notification = PopupNotifications.panel.childNodes[0];
     is(notification.id, "addon-install-failed-notification", "Should have seen the install fail");
     is(notification.getAttribute("label"),
        "This add-on could not be installed because it appears to be corrupt.",
        "Should have seen the right message");
 
     yield removeTab();
   });
-    path = CHROMEROOT + "corrupt.xpi";
 },
 
 function test_tabClose() {
   return Task.spawn(function* () {
     if (!Preferences.get("xpinstall.customConfirmationUI", false)) {
       runNextTest();
       return;
     }
--- a/browser/base/content/test/general/browser_sanitize-timespans.js
+++ b/browser/base/content/test/general/browser_sanitize-timespans.js
@@ -54,17 +54,17 @@ function countEntries(name, message, che
 
   var obj = {};
   if (name !== null)
     obj.fieldname = name;
 
   let count;
   FormHistory.count(obj, { handleResult: result => count = result,
                            handleError: function (error) {
-                             do_throw("Error occurred searching form history: " + error);
+                             throw new Error("Error occurred searching form history: " + error);
                              deferred.reject(error)
                            },
                            handleCompletion: function (reason) {
                              if (!reason) {
                                check(count, message);
                                deferred.resolve();
                              }
                            },
@@ -489,29 +489,29 @@ function setupHistory() {
 function* setupFormHistory() {
 
   function searchEntries(terms, params) {
     let deferred = Promise.defer();
 
     let results = [];
     FormHistory.search(terms, params, { handleResult: result => results.push(result),
                                         handleError: function (error) {
-                                          do_throw("Error occurred searching form history: " + error);
+                                          throw new Error("Error occurred searching form history: " + error);
                                           deferred.reject(error);
                                         },
                                         handleCompletion: function (reason) { deferred.resolve(results); }
                                       });
     return deferred.promise;
   }
 
   function update(changes)
   {
     let deferred = Promise.defer();
     FormHistory.update(changes, { handleError: function (error) {
-                                    do_throw("Error occurred searching form history: " + error);
+                                    throw new Error("Error occurred searching form history: " + error);
                                     deferred.reject(error);
                                   },
                                   handleCompletion: function (reason) { deferred.resolve(); }
                                 });
     return deferred.promise;
   }
 
   // Make sure we've got a clean DB to start with, then add the entries we'll be testing.
--- a/browser/base/content/test/general/browser_sanitizeDialog.js
+++ b/browser/base/content/test/general/browser_sanitizeDialog.js
@@ -897,17 +897,17 @@ function promiseAddFormEntryWithMinutesA
   let name = aMinutesAgo + "-minutes-ago";
 
   // Artifically age the entry to the proper vintage.
   let timestamp = now_uSec - (aMinutesAgo * kUsecPerMin);
 
   return new Promise((resolve, reject) =>
     FormHistory.update({ op: "add", fieldname: name, value: "dummy", firstUsed: timestamp },
                        { handleError: function (error) {
-                           do_throw("Error occurred updating form history: " + error);
+                           throw new Error("Error occurred updating form history: " + error);
                            reject();
                          },
                          handleCompletion: function (reason) {
                            resolve(name);
                          }
                        })
    )
 }
@@ -917,17 +917,17 @@ function promiseAddFormEntryWithMinutesA
  */
 function formNameExists(name)
 {
   return new Promise((resolve, reject) => {
     let count = 0;
     FormHistory.count({ fieldname: name },
                       { handleResult: result => count = result,
                         handleError: function (error) {
-                          do_throw("Error occurred searching form history: " + error);
+                          throw new Error("Error occurred searching form history: " + error);
                           reject(error);
                         },
                         handleCompletion: function (reason) {
                           if (!reason) {
                             resolve(count);
                           }
                         }
                       });
@@ -948,17 +948,17 @@ function* blankSlate() {
   yield new Promise((resolve, reject) => {
     FormHistory.update({op: "remove"}, {
       handleCompletion(reason) {
         if (!reason) {
           resolve();
         }
       },
       handleError(error) {
-        do_throw("Error occurred updating form history: " + error);
+        throw new Error("Error occurred updating form history: " + error);
         reject(error);
       }
     });
   });
 
   yield PlacesTestUtils.clearHistory();
 }
 
deleted file mode 100644
--- a/browser/base/content/test/general/domplate_test.js
+++ /dev/null
@@ -1,51 +0,0 @@
-/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
-/* vim: set 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/. */
-
-var doc;
-var div;
-var plate;
-
-Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
-Components.utils.import("resource:///modules/domplate.jsm");
-
-function createDocument()
-{
-  doc.body.innerHTML = '<div id="first">no</div>';
-  doc.title = "Domplate Test";
-  setupDomplateTests();
-}
-
-function setupDomplateTests()
-{
-  ok(domplate, "domplate is defined");
-  plate = domplate({tag: domplate.DIV("Hello!")});
-  ok(plate, "template is defined");
-  div = doc.getElementById("first");
-  ok(div, "we have our div");
-  plate.tag.replace({}, div, template);
-  is(div.innerText, "Hello!", "Is the div's innerText replaced?");
-  finishUp();
-}
-
-function finishUp()
-{
-  gBrowser.removeCurrentTab();
-  finish();
-}
-
-function test()
-{
-  waitForExplicitFinish();
-  gBrowser.selectedTab = gBrowser.addTab();
-  gBrowser.selectedBrowser.addEventListener("load", function() {
-    gBrowser.selectedBrowser.removeEventListener("load", arguments.callee, true);
-    doc = content.document;
-    waitForFocus(createDocument, content);
-  }, true);
-
-  content.location = "data:text/html;charset=utf-8,domplate_test.js";
-}
-
new file mode 100644
--- /dev/null
+++ b/browser/base/content/test/tabPrompts/.eslintrc.js
@@ -0,0 +1,7 @@
+"use strict";
+
+module.exports = {
+  "extends": [
+    "../../../../../testing/mochitest/browser.eslintrc.js"
+  ]
+};
new file mode 100644
--- /dev/null
+++ b/browser/base/content/test/tabs/.eslintrc.js
@@ -0,0 +1,7 @@
+"use strict";
+
+module.exports = {
+  "extends": [
+    "../../../../../testing/mochitest/browser.eslintrc.js"
+  ]
+};
new file mode 100644
--- /dev/null
+++ b/browser/base/content/test/urlbar/.eslintrc.js
@@ -0,0 +1,7 @@
+"use strict";
+
+module.exports = {
+  "extends": [
+    "../../../../../testing/mochitest/browser.eslintrc.js"
+  ]
+};
new file mode 100644
--- /dev/null
+++ b/browser/base/content/test/webrtc/.eslintrc.js
@@ -0,0 +1,7 @@
+"use strict";
+
+module.exports = {
+  "extends": [
+    "../../../../../testing/mochitest/browser.eslintrc.js"
+  ]
+};
--- a/browser/base/content/urlbarBindings.xml
+++ b/browser/base/content/urlbarBindings.xml
@@ -1362,16 +1362,29 @@ file, You can obtain one at http://mozil
 
       <field name="_oneOffSearchesEnabled">false</field>
 
       <field name="_overrideValue">null</field>
       <property name="overrideValue"
                 onget="return this._overrideValue;"
                 onset="this._overrideValue = val; return val;"/>
 
+      <method name="onPopupClick">
+        <parameter name="aEvent"/>
+        <body><![CDATA[
+          if (aEvent.button == 2) {
+            // Ignore right-clicks.
+            return;
+          }
+          // Otherwise "call super" -- do what autocomplete-base-popup does.
+          let controller = this.view.QueryInterface(Components.interfaces.nsIAutoCompleteController);
+          controller.handleEnter(true, aEvent);
+        ]]></body>
+      </method>
+
       <method name="enableOneOffSearches">
         <parameter name="enable"/>
         <body><![CDATA[
           this._oneOffSearchesEnabled = enable;
           if (enable) {
             this.oneOffSearchButtons.telemetryOrigin = "urlbar";
             this.oneOffSearchButtons.style.display = "-moz-box";
             this.oneOffSearchButtons.popup = this;
--- a/browser/components/extensions/test/browser/browser_ext_contentscript_connect.js
+++ b/browser/components/extensions/test/browser/browser_ext_contentscript_connect.js
@@ -15,31 +15,33 @@ add_task(function* () {
       let port_messages_received = 0;
 
       browser.runtime.onConnect.addListener((port) => {
         browser.test.assertTrue(!!port, "port1 received");
 
         ports_received++;
         browser.test.assertEq(1, ports_received, "1 port received");
 
-        port.onMessage.addListener((msg, sender) => {
+        port.onMessage.addListener((msg, msgPort) => {
           browser.test.assertEq("port message", msg, "listener1 port message received");
+          browser.test.assertEq(port, msgPort, "onMessage should receive port as second argument");
 
           port_messages_received++;
           browser.test.assertEq(1, port_messages_received, "1 port message received");
         });
       });
       browser.runtime.onConnect.addListener((port) => {
         browser.test.assertTrue(!!port, "port2 received");
 
         ports_received++;
         browser.test.assertEq(2, ports_received, "2 ports received");
 
-        port.onMessage.addListener((msg, sender) => {
+        port.onMessage.addListener((msg, msgPort) => {
           browser.test.assertEq("port message", msg, "listener2 port message received");
+          browser.test.assertEq(port, msgPort, "onMessage should receive port as second argument");
 
           port_messages_received++;
           browser.test.assertEq(2, port_messages_received, "2 port messages received");
 
           browser.test.notifyPass("contentscript_connect.pass");
         });
       });
 
--- a/browser/components/places/PlacesUIUtils.jsm
+++ b/browser/components/places/PlacesUIUtils.jsm
@@ -975,17 +975,17 @@ this.PlacesUIUtils = {
   },
 
   openLiveMarkNodesInTabs:
   function PUIU_openLiveMarkNodesInTabs(aNode, aEvent, aView) {
     let window = aView.ownerWindow;
 
     PlacesUtils.livemarks.getLivemark({id: aNode.itemId})
       .then(aLivemark => {
-        urlsToOpen = [];
+        let urlsToOpen = [];
 
         let nodes = aLivemark.getNodesForContainer(aNode);
         for (let node of nodes) {
           urlsToOpen.push({uri: node.uri, isBookmark: false});
         }
 
         if (this.confirmOpenInTabs(urlsToOpen.length, window)) {
           this._openTabset(urlsToOpen, aEvent, window);
--- a/browser/components/preferences/translation.js
+++ b/browser/components/preferences/translation.js
@@ -233,17 +233,17 @@ var gTranslationExceptions = {
     this.onSiteSelected();
   },
 
   onSiteKeyPress: function(aEvent) {
     if (aEvent.keyCode == KeyEvent.DOM_VK_DELETE)
       this.onSiteDeleted();
   },
 
-  onLanguageKeyPress: function() {
+  onLanguageKeyPress: function(aEvent) {
     if (aEvent.keyCode == KeyEvent.DOM_VK_DELETE)
       this.onLanguageDeleted();
   },
 
   onWindowKeyPress: function(aEvent) {
     if (aEvent.keyCode == KeyEvent.DOM_VK_ESCAPE)
       window.close();
   },
--- a/browser/components/syncedtabs/test/browser/head.js
+++ b/browser/components/syncedtabs/test/browser/head.js
@@ -2,16 +2,17 @@ const {classes: Cc, interfaces: Ci, util
 
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 Cu.import("resource://gre/modules/Services.jsm");
 Cu.import("resource://gre/modules/Promise.jsm");
 
 
 // Load mocking/stubbing library, sinon
 // docs: http://sinonjs.org/docs/
+/* global sinon */
 let loader = Cc["@mozilla.org/moz/jssubscript-loader;1"].getService(Ci.mozIJSSubScriptLoader);
 loader.loadSubScript("resource://testing-common/sinon-1.16.1.js");
 
 registerCleanupFunction(function*() {
   // Cleanup window or the test runner will throw an error
   delete window.sinon;
   delete window.setImmediate;
   delete window.clearImmediate;
--- a/browser/components/syncedtabs/test/xpcshell/head.js
+++ b/browser/components/syncedtabs/test/xpcshell/head.js
@@ -19,11 +19,11 @@ let window = {
     setInterval: setInterval,
     clearTimeout: clearTimeout,
     clearinterval: clearInterval
 };
 let self = window;
 
 // Load mocking/stubbing library, sinon
 // docs: http://sinonjs.org/docs/
+/* global sinon */
 let loader = Cc["@mozilla.org/moz/jssubscript-loader;1"].getService(Ci.mozIJSSubScriptLoader);
 loader.loadSubScript("resource://testing-common/sinon-1.16.1.js");
-
new file mode 100644
--- /dev/null
+++ b/browser/config/mozconfigs/linux32/debug-artifact
@@ -0,0 +1,12 @@
+MOZ_AUTOMATION_BUILD_SYMBOLS=0
+MOZ_AUTOMATION_L10N_CHECK=0
+
+. "$topsrcdir/build/unix/mozconfig.linux32"
+. "$topsrcdir/build/mozconfig.common.override"
+
+ac_add_options --enable-artifact-builds
+ac_add_options --enable-artifact-build-symbols
+unset CC
+unset CXX
+
+ac_add_options --enable-debug
new file mode 100644
--- /dev/null
+++ b/browser/config/mozconfigs/linux64/debug-artifact
@@ -0,0 +1,13 @@
+MOZ_AUTOMATION_BUILD_SYMBOLS=0
+MOZ_AUTOMATION_L10N_CHECK=0
+
+. "$topsrcdir/build/unix/mozconfig.linux"
+. "$topsrcdir/build/mozconfig.common.override"
+
+ac_add_options --enable-artifact-builds
+ac_add_options --enable-artifact-build-symbols
+unset CC
+unset CXX
+
+ac_add_options --enable-debug
+
new file mode 100644
--- /dev/null
+++ b/browser/config/mozconfigs/macosx64/debug-artifact
@@ -0,0 +1,12 @@
+MOZ_AUTOMATION_BUILD_SYMBOLS=0
+MOZ_AUTOMATION_L10N_CHECK=0
+
+. "$topsrcdir/build/macosx/mozconfig.common"
+. "$topsrcdir/build/mozconfig.common.override"
+
+ac_add_options --enable-artifact-builds
+ac_add_options --enable-artifact-build-symbols
+unset CC
+unset CXX
+
+ac_add_options --enable-debug
new file mode 100644
--- /dev/null
+++ b/browser/config/mozconfigs/win32/debug-artifact
@@ -0,0 +1,12 @@
+MOZ_AUTOMATION_BUILD_SYMBOLS=0
+MOZ_AUTOMATION_L10N_CHECK=0
+
+. "$topsrcdir/browser/config/mozconfigs/common"
+. "$topsrcdir/build/mozconfig.win-common"
+. "$topsrcdir/build/win32/mozconfig.vs-latest"
+. "$topsrcdir/build/mozconfig.common.override"
+
+ac_add_options --enable-artifact-builds
+ac_add_options --enable-artifact-build-symbols
+
+ac_add_options --enable-debug
new file mode 100644
--- /dev/null
+++ b/browser/config/mozconfigs/win64/debug-artifact
@@ -0,0 +1,13 @@
+MOZ_AUTOMATION_BUILD_SYMBOLS=0
+MOZ_AUTOMATION_L10N_CHECK=0
+
+. "$topsrcdir/browser/config/mozconfigs/win64/common-win64"
+. "$topsrcdir/browser/config/mozconfigs/common"
+. "$topsrcdir/build/mozconfig.win-common"
+. "$topsrcdir/build/win64/mozconfig.vs-latest"
+. "$topsrcdir/build/mozconfig.common.override"
+
+ac_add_options --enable-artifact-builds
+ac_add_options --enable-artifact-build-symbols
+
+ac_add_options --enable-debug
--- a/browser/extensions/pdfjs/README.mozilla
+++ b/browser/extensions/pdfjs/README.mozilla
@@ -1,3 +1,3 @@
 This is the pdf.js project output, https://github.com/mozilla/pdf.js
 
-Current extension version is: 1.6.263
+Current extension version is: 1.6.274
--- a/browser/extensions/pdfjs/content/build/pdf.js
+++ b/browser/extensions/pdfjs/content/build/pdf.js
@@ -19,18 +19,18 @@
   } else if (typeof exports !== 'undefined') {
     factory(exports);
   } else {
     factory(root['pdfjsDistBuildPdf'] = {});
   }
 }(this, function (exports) {
   // Use strict in our context only - users might not want it
   'use strict';
-  var pdfjsVersion = '1.6.263';
-  var pdfjsBuild = '7e392c0';
+  var pdfjsVersion = '1.6.274';
+  var pdfjsBuild = '1c3fb17';
   var pdfjsFilePath = typeof document !== 'undefined' && document.currentScript ? document.currentScript.src : null;
   var pdfjsLibs = {};
   (function pdfjsWrapper() {
     (function (root, factory) {
       factory(root.pdfjsSharedUtil = {});
     }(this, function (exports) {
       var globalScope = typeof window !== 'undefined' ? window : typeof global !== 'undefined' ? global : typeof self !== 'undefined' ? self : this;
       var FONT_IDENTITY_MATRIX = [
@@ -2226,17 +2226,17 @@
             addLinkAttributes(link, {
               url: this.data.url,
               target: this.data.newWindow ? LinkTarget.BLANK : undefined
             });
             if (!this.data.url) {
               if (this.data.action) {
                 this._bindNamedAction(link, this.data.action);
               } else {
-                this._bindLink(link, this.data.dest || null);
+                this._bindLink(link, this.data.dest);
               }
             }
             this.container.appendChild(link);
             return this.container;
           },
           /**
            * Bind internal links to the link element.
            *
--- a/browser/extensions/pdfjs/content/build/pdf.worker.js
+++ b/browser/extensions/pdfjs/content/build/pdf.worker.js
@@ -19,18 +19,18 @@
   } else if (typeof exports !== 'undefined') {
     factory(exports);
   } else {
     factory(root['pdfjsDistBuildPdfWorker'] = {});
   }
 }(this, function (exports) {
   // Use strict in our context only - users might not want it
   'use strict';
-  var pdfjsVersion = '1.6.263';
-  var pdfjsBuild = '7e392c0';
+  var pdfjsVersion = '1.6.274';
+  var pdfjsBuild = '1c3fb17';
   var pdfjsFilePath = typeof document !== 'undefined' && document.currentScript ? document.currentScript.src : null;
   var pdfjsLibs = {};
   (function pdfjsWrapper() {
     (function (root, factory) {
       factory(root.pdfjsCoreArithmeticDecoder = {});
     }(this, function (exports) {
       /* This class implements the QM Coder decoding as defined in
        *   JPEG 2000 Part I Final Committee Draft Version 1.0
--- a/browser/extensions/pdfjs/content/web/viewer.js
+++ b/browser/extensions/pdfjs/content/web/viewer.js
@@ -954,20 +954,22 @@ var pdfjsWebLibs;
           _bindLink: function PDFOutlineViewer_bindLink(element, item) {
             if (item.url) {
               pdfjsLib.addLinkAttributes(element, {
                 url: item.url,
                 target: item.newWindow ? PDFJS.LinkTarget.BLANK : undefined
               });
               return;
             }
-            var linkService = this.linkService;
-            element.href = linkService.getDestinationHash(item.dest);
-            element.onclick = function goToDestination(e) {
-              linkService.navigateTo(item.dest);
+            var self = this, destination = item.dest;
+            element.href = self.linkService.getDestinationHash(destination);
+            element.onclick = function () {
+              if (destination) {
+                self.linkService.navigateTo(destination);
+              }
               return false;
             };
           },
           /**
            * @private
            */
           _setStyles: function PDFOutlineViewer_setStyles(element, item) {
             var styleStr = '';
@@ -4179,56 +4181,66 @@ var pdfjsWebLibs;
           /**
            * @param dest - The PDF destination object.
            */
           navigateTo: function PDFLinkService_navigateTo(dest) {
             var destString = '';
             var self = this;
             var goToDestination = function (destRef) {
               // dest array looks like that: <page-ref> </XYZ|/FitXXX> <args..>
-              var pageNumber = destRef instanceof Object ? self._pagesRefCache[destRef.num + ' ' + destRef.gen + ' R'] : destRef + 1;
+              var pageNumber;
+              if (destRef instanceof Object) {
+                pageNumber = self._cachedPageNumber(destRef);
+              } else if ((destRef | 0) === destRef) {
+                // Integer
+                pageNumber = destRef + 1;
+              } else {
+                console.error('PDFLinkService_navigateTo: "' + destRef + '" is not a valid destination reference.');
+                return;
+              }
               if (pageNumber) {
-                if (pageNumber > self.pagesCount) {
-                  console.error('PDFLinkService_navigateTo: ' + 'Trying to navigate to a non-existent page.');
+                if (pageNumber < 1 || pageNumber > self.pagesCount) {
+                  console.error('PDFLinkService_navigateTo: "' + pageNumber + '" is a non-existent page number.');
                   return;
                 }
                 self.pdfViewer.scrollPageIntoView({
                   pageNumber: pageNumber,
                   destArray: dest
                 });
                 if (self.pdfHistory) {
                   // Update the browsing history.
                   self.pdfHistory.push({
                     dest: dest,
                     hash: destString,
                     page: pageNumber
                   });
                 }
               } else {
                 self.pdfDocument.getPageIndex(destRef).then(function (pageIndex) {
-                  var pageNum = pageIndex + 1;
-                  var cacheKey = destRef.num + ' ' + destRef.gen + ' R';
-                  self._pagesRefCache[cacheKey] = pageNum;
+                  self.cachePageRef(pageIndex + 1, destRef);
                   goToDestination(destRef);
+                }).catch(function () {
+                  console.error('PDFLinkService_navigateTo: "' + destRef + '" is not a valid page reference.');
+                  return;
                 });
               }
             };
             var destinationPromise;
             if (typeof dest === 'string') {
               destString = dest;
               destinationPromise = this.pdfDocument.getDestination(dest);
             } else {
               destinationPromise = Promise.resolve(dest);
             }
             destinationPromise.then(function (destination) {
               dest = destination;
               if (!(destination instanceof Array)) {
+                console.error('PDFLinkService_navigateTo: "' + destination + '" is not a valid destination array.');
                 return;
               }
-              // invalid destination
               goToDestination(destination[0]);
             });
           },
           /**
            * @param dest - The PDF destination object.
            * @returns {string} The hyperlink to the PDF object.
            */
           getDestinationHash: function PDFLinkService_getDestinationHash(dest) {
@@ -4335,24 +4347,25 @@ var pdfjsWebLibs;
               }
               // simple page
               if ('pagemode' in params) {
                 this.eventBus.dispatch('pagemode', {
                   source: this,
                   mode: params.pagemode
                 });
               }
-            } else if (isPageNumber(hash)) {
-              // Page number.
-              this.page = hash | 0;
             } else {
-              // Named (or explicit) destination.
               dest = unescape(hash);
               try {
                 dest = JSON.parse(dest);
+                if (!(dest instanceof Array)) {
+                  // Avoid incorrectly rejecting a valid named destination, such as
+                  // e.g. "4.3" or "true", because `JSON.parse` converted its type.
+                  dest = dest.toString();
+                }
               } catch (ex) {
               }
               if (typeof dest === 'string' || isValidExplicitDestination(dest)) {
                 if (this.pdfHistory) {
                   this.pdfHistory.updateNextHashParam(dest);
                 }
                 this.navigateTo(dest);
                 return;
@@ -4403,16 +4416,20 @@ var pdfjsWebLibs;
           },
           /**
            * @param {number} pageNum - page number.
            * @param {Object} pageRef - reference to the page.
            */
           cachePageRef: function PDFLinkService_cachePageRef(pageNum, pageRef) {
             var refStr = pageRef.num + ' ' + pageRef.gen + ' R';
             this._pagesRefCache[refStr] = pageNum;
+          },
+          _cachedPageNumber: function PDFLinkService_cachedPageNumber(pageRef) {
+            var refStr = pageRef.num + ' ' + pageRef.gen + ' R';
+            return this._pagesRefCache && this._pagesRefCache[refStr] || null;
           }
         };
         function isValidExplicitDestination(dest) {
           if (!(dest instanceof Array)) {
             return false;
           }
           var destLength = dest.length, allowNull = true;
           if (destLength < 2) {
--- a/browser/locales/en-US/chrome/browser/browser.properties
+++ b/browser/locales/en-US/chrome/browser/browser.properties
@@ -732,16 +732,25 @@ decoder.noCodecs.message = To play video, you may need to install Microsoft’s Media Feature Pack.
 decoder.noCodecsVista.message = To play video, you may need to install Microsoft’s Platform Update Supplement for Windows Vista.
 decoder.noCodecsXP.message = To play video, you may need to enable Adobe’s Primetime Content Decryption Module.
 decoder.noCodecsLinux.message = To play video, you may need to install the required video codecs.
 decoder.noHWAcceleration.message = To improve video quality, you may need to install Microsoft’s Media Feature Pack.
 decoder.noHWAccelerationVista.message = To improve video quality, you may need to install Microsoft’s Platform Update Supplement for Windows Vista.
 decoder.noPulseAudio.message = To play audio, you may need to install the required PulseAudio software.
 decoder.unsupportedLibavcodec.message = libavcodec may be vulnerable or is not supported, and should be updated to play video.
 
+# LOCALIZATION NOTE (captivePortal.infoMessage):
+# This string is shown in a notification bar when we detect a captive portal is blocking network access
+# and requires the user to log in before browsing. %1$S is replaced with brandShortName.
+captivePortal.infoMessage=This network may require you to login to use the internet. %1$S has opened the login page for you.
+# LOCALIZATION NOTE (captivePortal.showLoginPage):
+# The label for a button shown in the info bar in all tabs except the login page tab.
+# The button shows the portal login page tab when clicked.
+captivePortal.showLoginPage=Show Login Page
+
 permissions.remove.tooltip = Clear this permission and ask again
 
 # LOCALIZATION NOTE (aboutDialog.architecture.*):
 # The sixtyFourBit and thirtyTwoBit strings describe the architecture of the
 # current Firefox build: 32-bit or 64-bit. These strings are used in parentheses
 # between the Firefox version and the "What's new" link in the About dialog,
 # e.g.: "48.0.2 (32-bit) <What's new>" or "51.0a1 (2016-09-05) (64-bit)".
 aboutDialog.architecture.sixtyFourBit = 64-bit
--- a/browser/modules/CaptivePortalWatcher.jsm
+++ b/browser/modules/CaptivePortalWatcher.jsm
@@ -7,32 +7,38 @@
 const { classes: Cc, interfaces: Ci, utils: Cu } = Components;
 /**
  * This constant is chosen to be large enough for a portal recheck to complete,
  * and small enough that the delay in opening a tab isn't too noticeable.
  * Please see comments for _delayedAddCaptivePortalTab for more details.
  */
 const PORTAL_RECHECK_DELAY_MS = 150;
 
+// This is the value used to identify the captive portal notification.
+const PORTAL_NOTIFICATION_VALUE = "captive-portal-detected";
+
 this.EXPORTED_SYMBOLS = [ "CaptivePortalWatcher" ];
 
 Cu.import("resource://gre/modules/Services.jsm");
 Cu.import("resource://gre/modules/Timer.jsm");
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 Cu.import("resource:///modules/RecentWindow.jsm");
 
 XPCOMUtils.defineLazyServiceGetter(this, "cps",
                                    "@mozilla.org/network/captive-portal-service;1",
                                    "nsICaptivePortalService");
 
 this.CaptivePortalWatcher = {
   // This holds a weak reference to the captive portal tab so that we
   // don't leak it if the user closes it.
   _captivePortalTab: null,
 
+  // This holds a weak reference to the captive portal notification.
+  _captivePortalNotification: null,
+
   _initialized: false,
 
   /**
    * If a portal is detected when we don't have focus, we first wait for focus
    * and then add the tab after a small delay. This is set to true while we wait
    * so that in the unlikely event that we receive another notification while
    * waiting, we can avoid adding a second tab.
    */
@@ -45,17 +51,17 @@ this.CaptivePortalWatcher = {
   init() {
     Services.obs.addObserver(this, "captive-portal-login", false);
     Services.obs.addObserver(this, "captive-portal-login-abort", false);
     Services.obs.addObserver(this, "captive-portal-login-success", false);
     this._initialized = true;
 
     if (cps.state == cps.LOCKED_PORTAL) {
       // A captive portal has already been detected.
-      this._addCaptivePortalTab();
+      this._captivePortalDetected();
       return;
     }
 
     cps.recheckCaptivePortal();
   },
 
   uninit() {
     if (!this._initialized) {
@@ -64,48 +70,63 @@ this.CaptivePortalWatcher = {
     Services.obs.removeObserver(this, "captive-portal-login");
     Services.obs.removeObserver(this, "captive-portal-login-abort");
     Services.obs.removeObserver(this, "captive-portal-login-success");
   },
 
   observe(subject, topic, data) {
     switch (topic) {
       case "captive-portal-login":
-        this._addCaptivePortalTab();
+        this._captivePortalDetected();
         break;
       case "captive-portal-login-abort":
       case "captive-portal-login-success":
         this._captivePortalGone();
         break;
       case "xul-window-visible":
         this._delayedAddCaptivePortalTab();
         break;
     }
   },
 
-  _addCaptivePortalTab() {
+  _captivePortalDetected() {
     if (this._waitingToAddTab) {
       return;
     }
 
     let win = RecentWindow.getMostRecentBrowserWindow();
     // If there's no browser window or none have focus, open and show the
     // tab when we regain focus. This is so that if a different application was
     // focused, when the user (re-)focuses a browser window, we open the tab
     // immediately in that window so they can login before continuing to browse.
-    if (!win || !win.document.hasFocus()) {
+    if (!win || win != Services.ww.activeWindow) {
       this._waitingToAddTab = true;
       Services.obs.addObserver(this, "xul-window-visible", false);
       return;
     }
 
-    // The browser is in use - add the tab without selecting it.
-    let tab = win.gBrowser.addTab(this.canonicalURL);
+    // The browser is in use - show a notification and add the tab without
+    // selecting it, unless the caller specifically requested selection.
+    this._ensureCaptivePortalTab(win);
+    this._showNotification(win);
+  },
+
+  _ensureCaptivePortalTab(win) {
+    let tab;
+    if (this._captivePortalTab) {
+      tab = this._captivePortalTab.get();
+    }
+
+    // If the tab is gone or going, we need to open a new one.
+    if (!tab || tab.closing || !tab.parentNode) {
+      tab = win.gBrowser.addTab(this.canonicalURL);
+    }
+
     this._captivePortalTab = Cu.getWeakReference(tab);
-    return;
+    return tab;
   },
 
   /**
    * Called after we regain focus if we detect a portal while a browser window
    * doesn't have focus. Triggers a portal recheck to reaffirm state, and adds
    * the tab if needed after a short delay to allow the recheck to complete.
    */
   _delayedAddCaptivePortalTab() {
@@ -130,35 +151,37 @@ this.CaptivePortalWatcher = {
     // - If it is, the delay is chosen to not be extremely noticeable.
     setTimeout(() => {
       this._waitingToAddTab = false;
       if (cps.state != cps.LOCKED_PORTAL) {
         // We're free of the portal!
         return;
       }
 
-      let tab = win.gBrowser.addTab(this.canonicalURL);
+      this._showNotification(win);
+      let tab = this._ensureCaptivePortalTab(win);
+
       // Focus the tab only if the recheck has completed, i.e. we're sure
       // that the portal is still locked. This way, if the recheck completes
       // after we add the tab and we're free of the portal, the tab contents
       // won't flicker.
       if (cps.lastChecked != lastChecked) {
         win.gBrowser.selectedTab = tab;
       }
-
-      this._captivePortalTab = Cu.getWeakReference(tab);
     }, PORTAL_RECHECK_DELAY_MS);
   },
 
   _captivePortalGone() {
     if (this._waitingToAddTab) {
       Services.obs.removeObserver(this, "xul-window-visible");
       this._waitingToAddTab = false;
     }
 
+    this._removeNotification();
+
     if (!this._captivePortalTab) {
       return;
     }
 
     let tab = this._captivePortalTab.get();
     // In all the cases below, we want to stop treating the tab as a
     // captive portal tab.
     this._captivePortalTab = null;
@@ -176,9 +199,86 @@ this.CaptivePortalWatcher = {
     if (tab.linkedBrowser.currentURI.spec != this.canonicalURL &&
         tabbrowser.selectedTab == tab) {
       return;
     }
 
     // Remove the tab.
     tabbrowser.removeTab(tab);
   },
+
+  get _productName() {
+    delete this._productName;
+    return this._productName =
+      Services.strings.createBundle("chrome://branding/locale/brand.properties")
+                      .GetStringFromName("brandShortName");
+  },
+
+  get _browserBundle() {
+    delete this._browserBundle;
+    return this._browserBundle =
+      Services.strings.createBundle("chrome://browser/locale/browser.properties");
+  },
+
+  handleEvent(aEvent) {
+    if (aEvent.type != "TabSelect" || !this._captivePortalTab || !this._captivePortalNotification) {
+      return;
+    }
+
+    let tab = this._captivePortalTab.get();
+    let n = this._captivePortalNotification.get();
+    if (!tab || !n) {
+      return;
+    }
+
+    let doc = tab.ownerDocument;
+    let button = n.querySelector("button.notification-button");
+    if (doc.defaultView.gBrowser.selectedTab == tab) {
+      button.style.visibility = "hidden";
+    } else {
+      button.style.visibility = "visible";
+    }
+  },
+
+  _showNotification(win) {
+    let buttons = [
+      {
+        label: this._browserBundle.GetStringFromName("captivePortal.showLoginPage"),
+        callback: () => {
+          win.gBrowser.selectedTab = this._ensureCaptivePortalTab(win);
+
+          // Returning true prevents the notification from closing.
+          return true;
+        },
+        isDefault: true,
+      },
+    ];
+
+    let message = this._browserBundle.formatStringFromName("captivePortal.infoMessage",
+                                                           [this._productName], 1);
+
+    let closeHandler = (aEventName) => {
+      if (aEventName != "removed") {
+        return;
+      }
+      win.gBrowser.tabContainer.removeEventListener("TabSelect", this);
+    };
+
+    let nb = win.document.getElementById("high-priority-global-notificationbox");
+    let n = nb.appendNotification(message, PORTAL_NOTIFICATION_VALUE, "",
+                                  nb.PRIORITY_INFO_MEDIUM, buttons, closeHandler);
+
+    this._captivePortalNotification = Cu.getWeakReference(n);
+
+    win.gBrowser.tabContainer.addEventListener("TabSelect", this);
+  },
+
+  _removeNotification() {
+    if (!this._captivePortalNotification)
+      return;
+    let n = this._captivePortalNotification.get();
+    this._captivePortalNotification = null;
+    if (!n || !n.parentNode) {
+      return;
+    }
+    n.close();
+  },
 };
--- a/browser/modules/SocialService.jsm
+++ b/browser/modules/SocialService.jsm
@@ -505,17 +505,17 @@ this.SocialService = {
       if (!data['origin']) {
         Cu.reportError("SocialService.manifestFromData directory service provided manifest without origin.");
         return null;
       }
       installOrigin = data.origin;
     }
     // force/fixup origin
     let URI = Services.io.newURI(installOrigin, null, null);
-    principal = Services.scriptSecurityManager.createCodebasePrincipal(URI, {});
+    let principal = Services.scriptSecurityManager.createCodebasePrincipal(URI, {});
     data.origin = principal.origin;
 
     // iconURL and name are required
     let providerHasFeatures = featureURLs.some(url => data[url]);
     if (!providerHasFeatures) {
       Cu.reportError("SocialService.manifestFromData manifest missing required urls.");
       return null;
     }
--- a/browser/modules/test/browser.ini
+++ b/browser/modules/test/browser.ini
@@ -2,17 +2,16 @@
 support-files =
   head.js
 
 [browser_BrowserUITelemetry_buckets.js]
 [browser_BrowserUITelemetry_defaults.js]
 [browser_BrowserUITelemetry_sidebar.js]
 [browser_BrowserUITelemetry_syncedtabs.js]
 [browser_CaptivePortalWatcher.js]
-skip-if = os == "linux" || os == "win" || (os == "mac" && debug && e10s) # Bug 1279491, Bug 1287714 for OS X debug e10s
 [browser_ContentSearch.js]
 support-files =
   contentSearch.js
   contentSearchBadImage.xml
   contentSearchSuggestions.sjs
   contentSearchSuggestions.xml
 [browser_NetworkPrioritizer.js]
 [browser_PermissionUI.js]
--- a/browser/modules/test/browser_CaptivePortalWatcher.js
+++ b/browser/modules/test/browser_CaptivePortalWatcher.js
@@ -1,15 +1,16 @@
 "use strict";
 
 Components.utils.import("resource:///modules/RecentWindow.jsm");
 
 const CANONICAL_CONTENT = "success";
 const CANONICAL_URL = "data:text/plain;charset=utf-8," + CANONICAL_CONTENT;
 const CANONICAL_URL_REDIRECTED = "data:text/plain;charset=utf-8,redirected";
+const PORTAL_NOTIFICATION_VALUE = "captive-portal-detected";
 
 add_task(function* setup() {
   yield SpecialPowers.pushPrefEnv({
     set: [["captivedetect.canonicalURL", CANONICAL_URL],
           ["captivedetect.canonicalContent", CANONICAL_CONTENT]],
   });
 });
 
@@ -20,170 +21,306 @@ add_task(function* setup() {
  */
 function* portalDetectedNoBrowserWindow() {
   let getMostRecentBrowserWindow = RecentWindow.getMostRecentBrowserWindow;
   RecentWindow.getMostRecentBrowserWindow = () => {};
   Services.obs.notifyObservers(null, "captive-portal-login", null);
   RecentWindow.getMostRecentBrowserWindow = getMostRecentBrowserWindow;
 }
 
-function* openWindowAndWaitForPortalTab() {
+function* openWindowAndWaitForPortalTabAndNotification() {
   let win = yield BrowserTestUtils.openNewBrowserWindow();
-  let tab = yield BrowserTestUtils.waitForNewTab(win.gBrowser, CANONICAL_URL);
+  // Thanks to things being async, at this point we now have a new browser window
+  // but the portal notification and tab may or may not have opened. So first we
+  // check if there's already a portal notification, and if not, wait.
+  let notification = win.document.getElementById("high-priority-global-notificationbox")
+                        .getNotificationWithValue(PORTAL_NOTIFICATION_VALUE);
+  if (!notification) {
+    notification =
+      yield BrowserTestUtils.waitForGlobalNotificationBar(win, PORTAL_NOTIFICATION_VALUE);
+  }
+  // Then we see if there's already a portal tab. If it's open, it'll be the second one.
+  let tab = win.gBrowser.tabs[1];
+  if (!tab || tab.linkedBrowser.currentURI.spec != CANONICAL_URL) {
+    // The tab either hasn't been opened yet or it hasn't loaded the portal URL.
+    // Waiting for a location change in the tabbrowser covers both cases.
+    yield BrowserTestUtils.waitForLocationChange(win.gBrowser, CANONICAL_URL);
+    // At this point the portal tab should be the second tab. If there is still
+    // no second tab, something is wrong, and the selectedTab test below will fail.
+    tab = win.gBrowser.tabs[1];
+  }
   is(win.gBrowser.selectedTab, tab,
     "The captive portal tab should be open and selected in the new window.");
+  testShowLoginPageButtonVisibility(notification, "hidden");
   return win;
 }
 
 function freePortal(aSuccess) {
   Services.obs.notifyObservers(null,
     "captive-portal-login-" + (aSuccess ? "success" : "abort"), null);
 }
 
+function ensurePortalTab(win) {
+  // For the tests that call this function, it's enough to ensure there
+  // are two tabs in the window - the default tab and the portal tab.
+  is(win.gBrowser.tabs.length, 2,
+    "There should be a captive portal tab in the window.");
+}
+
+function ensurePortalNotification(win) {
+  let notificationBox =
+    win.document.getElementById("high-priority-global-notificationbox");
+  let notification = notificationBox.getNotificationWithValue(PORTAL_NOTIFICATION_VALUE)
+  isnot(notification, null,
+    "There should be a captive portal notification in the window.");
+  return notification;
+}
+
+// Helper to test whether the "Show Login Page" is visible in the captive portal
+// notification (it should be hidden when the portal tab is selected).
+function testShowLoginPageButtonVisibility(notification, visibility) {
+  let showLoginPageButton = notification.querySelector("button.notification-button");
+  // If the visibility property was never changed from default, it will be
+  // an empty string, so we pretend it's "visible" (effectively the same).
+  is(showLoginPageButton.style.visibility || "visible", visibility,
+    "The \"Show Login Page\" button should be " + visibility + ".");
+}
+
+function ensureNoPortalTab(win) {
+  is(win.gBrowser.tabs.length, 1,
+    "There should be no captive portal tab in the window.");
+}
+
+function ensureNoPortalNotification(win) {
+  let notificationBox =
+    win.document.getElementById("high-priority-global-notificationbox");
+  is(notificationBox.getNotificationWithValue(PORTAL_NOTIFICATION_VALUE), null,
+    "There should be no captive portal notification in the window.");
+}
+
 // Each of the test cases below is run twice: once for login-success and once
 // for login-abort (aSuccess set to true and false respectively).
 let testCasesForBothSuccessAndAbort = [
   /**
    * A portal is detected when there's no browser window,
    * then a browser window is opened, then the portal is freed.
    * The portal tab should be added and focused when the window is
    * opened, and closed automatically when the success event is fired.
    */
   function* test_detectedWithNoBrowserWindow_Open(aSuccess) {
     yield portalDetectedNoBrowserWindow();
-    let win = yield openWindowAndWaitForPortalTab();
+    let win = yield openWindowAndWaitForPortalTabAndNotification();
     freePortal(aSuccess);
-    is(win.gBrowser.tabs.length, 1,
-      "The captive portal tab should have been closed.");
+    ensureNoPortalTab(win);
+    ensureNoPortalNotification(win);
     yield BrowserTestUtils.closeWindow(win);
   },
 
   /**
    * A portal is detected when there's no browser window, and the
    * portal is freed before a browser window is opened. No portal
    * tab should be added when a browser window is opened.
    */
   function* test_detectedWithNoBrowserWindow_GoneBeforeOpen(aSuccess) {
     yield portalDetectedNoBrowserWindow();
     freePortal(aSuccess);
     let win = yield BrowserTestUtils.openNewBrowserWindow();
     // Wait for a while to make sure no tab is opened.
     yield new Promise(resolve => {
       setTimeout(resolve, 1000);
     });
-    is(win.gBrowser.tabs.length, 1,
-      "No captive portal tab should have been opened.");
+    ensureNoPortalTab(win);
+    ensureNoPortalNotification(win);
     yield BrowserTestUtils.closeWindow(win);
   },
 
   /**
    * A portal is detected when a browser window has focus. A portal tab should be
    * opened in the background in the focused browser window. If the portal is
    * freed when the tab isn't focused, the tab should be closed automatically.
    */
   function* test_detectedWithFocus(aSuccess) {
     let win = RecentWindow.getMostRecentBrowserWindow();
     let p = BrowserTestUtils.waitForNewTab(win.gBrowser, CANONICAL_URL);
     Services.obs.notifyObservers(null, "captive-portal-login", null);
     let tab = yield p;
+    ensurePortalTab(win);
+    ensurePortalNotification(win);
     isnot(win.gBrowser.selectedTab, tab,
       "The captive portal tab should be open in the background in the current window.");
     freePortal(aSuccess);
-    is(win.gBrowser.tabs.length, 1,
-      "The portal tab should have been closed.");
+    ensureNoPortalTab(win);
+    ensureNoPortalNotification(win);
   },
 
   /**
    * A portal is detected when a browser window has focus. A portal tab should be
    * opened in the background in the focused browser window. If the portal is
    * freed when the tab has focus, the tab should be closed automatically.
    */
   function* test_detectedWithFocus_selectedTab(aSuccess) {
     let win = RecentWindow.getMostRecentBrowserWindow();
     let p = BrowserTestUtils.waitForNewTab(win.gBrowser, CANONICAL_URL);
     Services.obs.notifyObservers(null, "captive-portal-login", null);
     let tab = yield p;
+    ensurePortalTab(win);
+    ensurePortalNotification(win);
     isnot(win.gBrowser.selectedTab, tab,
       "The captive portal tab should be open in the background in the current window.");
     win.gBrowser.selectedTab = tab;
     freePortal(aSuccess);
-    is(win.gBrowser.tabs.length, 1,
-      "The portal tab should have been closed.");
+    ensureNoPortalTab(win);
+    ensureNoPortalNotification(win);
   },
 ];
 
 let singleRunTestCases = [
   /**
    * A portal is detected when there's no browser window,
    * then a browser window is opened, and the portal is logged into
    * and redirects to a different page. The portal tab should be added
    * and focused when the window is opened, and left open after login
    * since it redirected.
    */
   function* test_detectedWithNoBrowserWindow_Redirect() {
     yield portalDetectedNoBrowserWindow();
-    let win = yield openWindowAndWaitForPortalTab();
+    let win = yield openWindowAndWaitForPortalTabAndNotification();
     let browser = win.gBrowser.selectedTab.linkedBrowser;
     let loadPromise =
       BrowserTestUtils.browserLoaded(browser, false, CANONICAL_URL_REDIRECTED);
     BrowserTestUtils.loadURI(browser, CANONICAL_URL_REDIRECTED);
     yield loadPromise;
     freePortal(true);
-    is(win.gBrowser.tabs.length, 2,
-      "The captive portal tab should not have been closed.");
+    ensurePortalTab(win);
+    ensureNoPortalNotification(win);
     yield BrowserTestUtils.closeWindow(win);
   },
 
   /**
    * A portal is detected when a browser window has focus. A portal tab should be
    * opened in the background in the focused browser window. If the portal is
    * freed when the tab isn't focused, the tab should be closed automatically,
    * even if the portal has redirected to a URL other than CANONICAL_URL.
    */
   function* test_detectedWithFocus_redirectUnselectedTab() {
     let win = RecentWindow.getMostRecentBrowserWindow();
     let p = BrowserTestUtils.waitForNewTab(win.gBrowser, CANONICAL_URL);
     Services.obs.notifyObservers(null, "captive-portal-login", null);
     let tab = yield p;
+    ensurePortalTab(win);
+    ensurePortalNotification(win);
     isnot(win.gBrowser.selectedTab, tab,
       "The captive portal tab should be open in the background in the current window.");
     let browser = tab.linkedBrowser;
     let loadPromise =
       BrowserTestUtils.browserLoaded(browser, false, CANONICAL_URL_REDIRECTED);
     BrowserTestUtils.loadURI(browser, CANONICAL_URL_REDIRECTED);
     yield loadPromise;
     freePortal(true);
-    is(win.gBrowser.tabs.length, 1,
-      "The portal tab should have been closed.");
+    ensureNoPortalTab(win);
+    ensureNoPortalNotification(win);
   },
 
   /**
    * A portal is detected when a browser window has focus. A portal tab should be
    * opened in the background in the focused browser window. If the portal is
    * freed when the tab has focus, and it has redirected to another page, the
    * tab should be kept open.
    */
   function* test_detectedWithFocus_redirectSelectedTab() {
     let win = RecentWindow.getMostRecentBrowserWindow();
     let p = BrowserTestUtils.waitForNewTab(win.gBrowser, CANONICAL_URL);
     Services.obs.notifyObservers(null, "captive-portal-login", null);
     let tab = yield p;
+    ensurePortalNotification(win);
     isnot(win.gBrowser.selectedTab, tab,
       "The captive portal tab should be open in the background in the current window.");
     win.gBrowser.selectedTab = tab;
     let browser = tab.linkedBrowser;
     let loadPromise =
       BrowserTestUtils.browserLoaded(browser, false, CANONICAL_URL_REDIRECTED);
     BrowserTestUtils.loadURI(browser, CANONICAL_URL_REDIRECTED);
     yield loadPromise;
     freePortal(true);
-    is(win.gBrowser.tabs.length, 2,
-      "The portal tab should not have been closed.");
+    ensurePortalTab(win);
+    ensureNoPortalNotification(win);
     yield BrowserTestUtils.removeTab(tab);
   },
+
+  /**
+   * Test the various expected behaviors of the "Show Login Page" button
+   * in the captive portal notification. The button should be visible for
+   * all tabs except the captive portal tab, and when clicked, should
+   * ensure a captive portal tab is open and select it.
+   */
+  function* test_showLoginPageButton() {
+    let win = RecentWindow.getMostRecentBrowserWindow();
+    let p = BrowserTestUtils.waitForNewTab(win.gBrowser, CANONICAL_URL);
+    Services.obs.notifyObservers(null, "captive-portal-login", null);
+    let tab = yield p;
+    let notification = ensurePortalNotification(win);
+    isnot(win.gBrowser.selectedTab, tab,
+      "The captive portal tab should be open in the background in the current window.");
+    testShowLoginPageButtonVisibility(notification, "visible");
+
+    function testPortalTabSelectedAndButtonNotVisible() {
+      is(win.gBrowser.selectedTab, tab, "The captive portal tab should be selected.");
+      testShowLoginPageButtonVisibility(notification, "hidden");
+    }
+
+    // Select the captive portal tab. The button should hide.
+    let otherTab = win.gBrowser.selectedTab;
+    win.gBrowser.selectedTab = tab;
+    testShowLoginPageButtonVisibility(notification, "hidden");
+
+    // Select the other tab. The button should become visible.
+    win.gBrowser.selectedTab = otherTab;
+    testShowLoginPageButtonVisibility(notification, "visible");
+
+    // Simulate clicking the button. The portal tab should be selected and
+    // the button should hide.
+    let button = notification.querySelector("button.notification-button");
+    button.click();
+    testPortalTabSelectedAndButtonNotVisible();
+
+    // Close the tab. The button should become visible.
+    yield BrowserTestUtils.removeTab(tab);
+    ensureNoPortalTab(win);
+    testShowLoginPageButtonVisibility(notification, "visible");
+
+    function* clickButtonAndExpectNewPortalTab() {
+      p = BrowserTestUtils.waitForNewTab(win.gBrowser, CANONICAL_URL);
+      button.click();
+      tab = yield p;
+      is(win.gBrowser.selectedTab, tab, "The captive portal tab should be selected.");
+    }
+
+    // When the button is clicked, a new portal tab should be opened and
+    // selected.
+    yield clickButtonAndExpectNewPortalTab();
+
+    // Open another arbitrary tab. The button should become visible. When it's clicked,
+    // the portal tab should be selected.
+    let anotherTab = yield BrowserTestUtils.openNewForegroundTab(win.gBrowser);
+    testShowLoginPageButtonVisibility(notification, "visible");
+    button.click();
+    is(win.gBrowser.selectedTab, tab, "The captive portal tab should be selected.");
+
+    // Close the portal tab and select the arbitrary tab. The button should become
+    // visible and when it's clicked, a new portal tab should be opened.
+    yield BrowserTestUtils.removeTab(tab);
+    win.gBrowser.selectedTab = anotherTab;
+    testShowLoginPageButtonVisibility(notification, "visible");
+    yield clickButtonAndExpectNewPortalTab();
+
+    yield BrowserTestUtils.removeTab(anotherTab);
+    freePortal(true);
+    ensureNoPortalTab(win);
+    ensureNoPortalNotification(win);
+  },
 ];
 
 for (let testcase of testCasesForBothSuccessAndAbort) {
   add_task(testcase.bind(null, true));
   add_task(testcase.bind(null, false));
 }
 
 for (let testcase of singleRunTestCases) {
--- a/devtools/client/netmonitor/moz.build
+++ b/devtools/client/netmonitor/moz.build
@@ -13,16 +13,17 @@ DIRS += [
 
 DevToolsModules(
     'constants.js',
     'custom-request-view.js',
     'events.js',
     'filter-predicates.js',
     'l10n.js',
     'panel.js',
+    'performance-statistics-view.js',
     'prefs.js',
     'request-utils.js',
     'requests-menu-view.js',
     'sort-predicates.js',
     'store.js',
     'toolbar-view.js',
 )
 
--- a/devtools/client/netmonitor/netmonitor-controller.js
+++ b/devtools/client/netmonitor/netmonitor-controller.js
@@ -38,16 +38,17 @@ const Services = require("Services");
 /* eslint-disable mozilla/reject-some-requires */
 const {XPCOMUtils} = require("resource://gre/modules/XPCOMUtils.jsm");
 const EventEmitter = require("devtools/shared/event-emitter");
 const Editor = require("devtools/client/sourceeditor/editor");
 const {TimelineFront} = require("devtools/shared/fronts/timeline");
 const {Task} = require("devtools/shared/task");
 const {Prefs} = require("./prefs");
 const {EVENTS} = require("./events");
+const Actions = require("./actions/index");
 
 XPCOMUtils.defineConstant(this, "EVENTS", EVENTS);
 XPCOMUtils.defineConstant(this, "ACTIVITY_TYPE", ACTIVITY_TYPE);
 XPCOMUtils.defineConstant(this, "Editor", Editor);
 XPCOMUtils.defineConstant(this, "Prefs", Prefs);
 
 XPCOMUtils.defineLazyModuleGetter(this, "Chart",
   "resource://devtools/client/shared/widgets/Chart.jsm");
--- a/devtools/client/netmonitor/netmonitor-view.js
+++ b/devtools/client/netmonitor/netmonitor-view.js
@@ -14,46 +14,43 @@ XPCOMUtils.defineLazyGetter(this, "Netwo
 
 /* eslint-disable mozilla/reject-some-requires */
 const {VariablesView} = require("resource://devtools/client/shared/widgets/VariablesView.jsm");
 /* eslint-disable mozilla/reject-some-requires */
 const {VariablesViewController} = require("resource://devtools/client/shared/widgets/VariablesViewController.jsm");
 const {ToolSidebar} = require("devtools/client/framework/sidebar");
 const {testing: isTesting} = require("devtools/shared/flags");
 const {ViewHelpers, Heritage} = require("devtools/client/shared/widgets/view-helpers");
-const {PluralForm} = require("devtools/shared/plural-form");
 const {Filters} = require("./filter-predicates");
 const {getFormDataSections,
        formDataURI,
        getUriHostPort} = require("./request-utils");
 const {L10N} = require("./l10n");
 const {RequestsMenuView} = require("./requests-menu-view");
 const {CustomRequestView} = require("./custom-request-view");
 const {ToolbarView} = require("./toolbar-view");
 const {configureStore} = require("./store");
-const Actions = require("./actions/index");
+const {PerformanceStatisticsView} = require("./performance-statistics-view");
 
 // Initialize the global redux variables
 var gStore = configureStore();
 
 // ms
 const WDA_DEFAULT_VERIFY_INTERVAL = 50;
 
 // Use longer timeout during testing as the tests need this process to succeed
 // and two seconds is quite short on slow debug builds. The timeout here should
 // be at least equal to the general mochitest timeout of 45 seconds so that this
 // never gets hit during testing.
 // ms
 const WDA_DEFAULT_GIVE_UP_TIMEOUT = isTesting ? 45000 : 2000;
 
 // 100 KB in bytes
 const SOURCE_SYNTAX_HIGHLIGHT_MAX_FILE_SIZE = 102400;
-const REQUEST_TIME_DECIMALS = 2;
 const HEADERS_SIZE_DECIMALS = 3;
-const CONTENT_SIZE_DECIMALS = 2;
 const CONTENT_MIME_TYPE_MAPPINGS = {
   "/ecmascript": Editor.modes.js,
   "/javascript": Editor.modes.js,
   "/x-javascript": Editor.modes.js,
   "/html": Editor.modes.html,
   "/xhtml": Editor.modes.html,
   "/xml": Editor.modes.html,
   "/atom": Editor.modes.html,
@@ -75,33 +72,32 @@ const GENERIC_VARIABLES_VIEW_SETTINGS = 
   lazyEmptyDelay: 10,
   searchEnabled: true,
   editableValueTooltip: "",
   editableNameTooltip: "",
   preventDisableOnChange: true,
   preventDescriptorModifiers: true,
   eval: () => {}
 };
-// px
-const NETWORK_ANALYSIS_PIE_CHART_DIAMETER = 200;
 
 /**
  * Object defining the network monitor view components.
  */
 var NetMonitorView = {
   /**
    * Initializes the network monitor view.
    */
   initialize: function () {
     this._initializePanes();
 
     this.Toolbar.initialize(gStore);
     this.RequestsMenu.initialize(gStore);
     this.NetworkDetails.initialize();
     this.CustomRequest.initialize();
+    this.PerformanceStatistics.initialize(gStore);
   },
 
   /**
    * Destroys the network monitor view.
    */
   destroy: function () {
     this._isDestroyed = true;
     this.Toolbar.destroy();
@@ -1192,259 +1188,23 @@ NetworkDetailsView.prototype = {
   _paramsPostPayload: "",
   _requestHeaders: "",
   _responseHeaders: "",
   _requestCookies: "",
   _responseCookies: ""
 };
 
 /**
- * Functions handling the performance statistics view.
- */
-function PerformanceStatisticsView() {
-}
-
-PerformanceStatisticsView.prototype = {
-  /**
-   * Initializes and displays empty charts in this container.
-   */
-  displayPlaceholderCharts: function () {
-    this._createChart({
-      id: "#primed-cache-chart",
-      title: "charts.cacheEnabled"
-    });
-    this._createChart({
-      id: "#empty-cache-chart",
-      title: "charts.cacheDisabled"
-    });
-    window.emit(EVENTS.PLACEHOLDER_CHARTS_DISPLAYED);
-  },
-
-  /**
-   * Populates and displays the primed cache chart in this container.
-   *
-   * @param array items
-   *        @see this._sanitizeChartDataSource
-   */
-  createPrimedCacheChart: function (items) {
-    this._createChart({
-      id: "#primed-cache-chart",
-      title: "charts.cacheEnabled",
-      data: this._sanitizeChartDataSource(items),
-      strings: this._commonChartStrings,
-      totals: this._commonChartTotals,
-      sorted: true
-    });
-    window.emit(EVENTS.PRIMED_CACHE_CHART_DISPLAYED);
-  },
-
-  /**
-   * Populates and displays the empty cache chart in this container.
-   *
-   * @param array items
-   *        @see this._sanitizeChartDataSource
-   */
-  createEmptyCacheChart: function (items) {
-    this._createChart({
-      id: "#empty-cache-chart",
-      title: "charts.cacheDisabled",
-      data: this._sanitizeChartDataSource(items, true),
-      strings: this._commonChartStrings,
-      totals: this._commonChartTotals,
-      sorted: true
-    });
-    window.emit(EVENTS.EMPTY_CACHE_CHART_DISPLAYED);
-  },
-
-  /**
-   * Common stringifier predicates used for items and totals in both the
-   * "primed" and "empty" cache charts.
-   */
-  _commonChartStrings: {
-    size: value => {
-      let string = L10N.numberWithDecimals(value / 1024, CONTENT_SIZE_DECIMALS);
-      return L10N.getFormatStr("charts.sizeKB", string);
-    },
-    time: value => {
-      let string = L10N.numberWithDecimals(value / 1000, REQUEST_TIME_DECIMALS);
-      return L10N.getFormatStr("charts.totalS", string);
-    }
-  },
-  _commonChartTotals: {
-    size: total => {
-      let string = L10N.numberWithDecimals(total / 1024, CONTENT_SIZE_DECIMALS);
-      return L10N.getFormatStr("charts.totalSize", string);
-    },
-    time: total => {
-      let seconds = total / 1000;
-      let string = L10N.numberWithDecimals(seconds, REQUEST_TIME_DECIMALS);
-      return PluralForm.get(seconds,
-        L10N.getStr("charts.totalSeconds")).replace("#1", string);
-    },
-    cached: total => {
-      return L10N.getFormatStr("charts.totalCached", total);
-    },
-    count: total => {
-      return L10N.getFormatStr("charts.totalCount", total);
-    }
-  },
-
-  /**
-   * Adds a specific chart to this container.
-   *
-   * @param object
-   *        An object containing all or some the following properties:
-   *          - id: either "#primed-cache-chart" or "#empty-cache-chart"
-   *          - title/data/strings/totals/sorted: @see Chart.jsm for details
-   */
-  _createChart: function ({ id, title, data, strings, totals, sorted }) {
-    let container = $(id);
-
-    // Nuke all existing charts of the specified type.
-    while (container.hasChildNodes()) {
-      container.firstChild.remove();
-    }
-
-    // Create a new chart.
-    let chart = Chart.PieTable(document, {
-      diameter: NETWORK_ANALYSIS_PIE_CHART_DIAMETER,
-      title: L10N.getStr(title),
-      data: data,
-      strings: strings,
-      totals: totals,
-      sorted: sorted
-    });
-
-    chart.on("click", (_, item) => {
-      // Reset FilterButtons and enable one filter exclusively
-      gStore.dispatch(Actions.enableFilterOnly(item.label));
-      NetMonitorView.showNetworkInspectorView();
-    });
-
-    container.appendChild(chart.node);
-  },
-
-  /**
-   * Sanitizes the data source used for creating charts, to follow the
-   * data format spec defined in Chart.jsm.
-   *
-   * @param array items
-   *        A collection of request items used as the data source for the chart.
-   * @param boolean emptyCache
-   *        True if the cache is considered enabled, false for disabled.
-   */
-  _sanitizeChartDataSource: function (items, emptyCache) {
-    let data = [
-      "html", "css", "js", "xhr", "fonts", "images", "media", "flash", "ws", "other"
-    ].map(e => ({
-      cached: 0,
-      count: 0,
-      label: e,
-      size: 0,
-      time: 0
-    }));
-
-    for (let requestItem of items) {
-      let details = requestItem.attachment;
-      let type;
-
-      if (Filters.html(details)) {
-        // "html"
-        type = 0;
-      } else if (Filters.css(details)) {
-        // "css"
-        type = 1;
-      } else if (Filters.js(details)) {
-        // "js"
-        type = 2;
-      } else if (Filters.fonts(details)) {
-        // "fonts"
-        type = 4;
-      } else if (Filters.images(details)) {
-        // "images"
-        type = 5;
-      } else if (Filters.media(details)) {
-        // "media"
-        type = 6;
-      } else if (Filters.flash(details)) {
-        // "flash"
-        type = 7;
-      } else if (Filters.ws(details)) {
-        // "ws"
-        type = 8;
-      } else if (Filters.xhr(details)) {
-        // Verify XHR last, to categorize other mime types in their own blobs.
-        // "xhr"
-        type = 3;
-      } else {
-        // "other"
-        type = 9;
-      }
-
-      if (emptyCache || !responseIsFresh(details)) {
-        data[type].time += details.totalTime || 0;
-        data[type].size += details.contentSize || 0;
-      } else {
-        data[type].cached++;
-      }
-      data[type].count++;
-    }
-
-    return data.filter(e => e.count > 0);
-  },
-};
-
-/**
  * DOM query helper.
+ * TODO: Move it into "dom-utils.js" module and "require" it when needed.
  */
 var $ = (selector, target = document) => target.querySelector(selector);
 var $all = (selector, target = document) => target.querySelectorAll(selector);
 
 /**
- * Checks if the "Expiration Calculations" defined in section 13.2.4 of the
- * "HTTP/1.1: Caching in HTTP" spec holds true for a collection of headers.
- *
- * @param object
- *        An object containing the { responseHeaders, status } properties.
- * @return boolean
- *         True if the response is fresh and loaded from cache.
- */
-function responseIsFresh({ responseHeaders, status }) {
-  // Check for a "304 Not Modified" status and response headers availability.
-  if (status != 304 || !responseHeaders) {
-    return false;
-  }
-
-  let list = responseHeaders.headers;
-  let cacheControl = list.filter(e => {
-    return e.name.toLowerCase() == "cache-control";
-  })[0];
-
-  let expires = list.filter(e => e.name.toLowerCase() == "expires")[0];
-
-  // Check the "Cache-Control" header for a maximum age value.
-  if (cacheControl) {
-    let maxAgeMatch =
-      cacheControl.value.match(/s-maxage\s*=\s*(\d+)/) ||
-      cacheControl.value.match(/max-age\s*=\s*(\d+)/);
-
-    if (maxAgeMatch && maxAgeMatch.pop() > 0) {
-      return true;
-    }
-  }
-
-  // Check the "Expires" header for a valid date.
-  if (expires && Date.parse(expires.value)) {
-    return true;
-  }
-
-  return false;
-}
-
-/**
  * Makes sure certain properties are available on all objects in a data store.
  *
  * @param array dataStore
  *        A list of objects for which to check the availability of properties.
  * @param array mandatoryFields
  *        A list of strings representing properties of objects in dataStore.
  * @return object
  *         A promise resolved when all objects in dataStore contain the
copy from devtools/client/netmonitor/netmonitor-view.js
copy to devtools/client/netmonitor/performance-statistics-view.js
--- a/devtools/client/netmonitor/netmonitor-view.js
+++ b/devtools/client/netmonitor/performance-statistics-view.js
@@ -1,1214 +1,43 @@
 /* -*- 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/. */
 /* import-globals-from ./netmonitor-controller.js */
-/* globals Prefs, gNetwork, setInterval, setTimeout, clearInterval, clearTimeout, btoa */
-/* exported $, $all */
+/* globals $ */
 "use strict";
 
-XPCOMUtils.defineLazyGetter(this, "NetworkHelper", function () {
-  return require("devtools/shared/webconsole/network-helper");
-});
-
-/* eslint-disable mozilla/reject-some-requires */
-const {VariablesView} = require("resource://devtools/client/shared/widgets/VariablesView.jsm");
-/* eslint-disable mozilla/reject-some-requires */
-const {VariablesViewController} = require("resource://devtools/client/shared/widgets/VariablesViewController.jsm");
-const {ToolSidebar} = require("devtools/client/framework/sidebar");
-const {testing: isTesting} = require("devtools/shared/flags");
-const {ViewHelpers, Heritage} = require("devtools/client/shared/widgets/view-helpers");
 const {PluralForm} = require("devtools/shared/plural-form");
 const {Filters} = require("./filter-predicates");
-const {getFormDataSections,
-       formDataURI,
-       getUriHostPort} = require("./request-utils");
 const {L10N} = require("./l10n");
-const {RequestsMenuView} = require("./requests-menu-view");
-const {CustomRequestView} = require("./custom-request-view");
-const {ToolbarView} = require("./toolbar-view");
-const {configureStore} = require("./store");
 const Actions = require("./actions/index");
 
-// Initialize the global redux variables
-var gStore = configureStore();
-
-// ms
-const WDA_DEFAULT_VERIFY_INTERVAL = 50;
-
-// Use longer timeout during testing as the tests need this process to succeed
-// and two seconds is quite short on slow debug builds. The timeout here should
-// be at least equal to the general mochitest timeout of 45 seconds so that this
-// never gets hit during testing.
-// ms
-const WDA_DEFAULT_GIVE_UP_TIMEOUT = isTesting ? 45000 : 2000;
-
-// 100 KB in bytes
-const SOURCE_SYNTAX_HIGHLIGHT_MAX_FILE_SIZE = 102400;
 const REQUEST_TIME_DECIMALS = 2;
-const HEADERS_SIZE_DECIMALS = 3;
 const CONTENT_SIZE_DECIMALS = 2;
-const CONTENT_MIME_TYPE_MAPPINGS = {
-  "/ecmascript": Editor.modes.js,
-  "/javascript": Editor.modes.js,
-  "/x-javascript": Editor.modes.js,
-  "/html": Editor.modes.html,
-  "/xhtml": Editor.modes.html,
-  "/xml": Editor.modes.html,
-  "/atom": Editor.modes.html,
-  "/soap": Editor.modes.html,
-  "/vnd.mpeg.dash.mpd": Editor.modes.html,
-  "/rdf": Editor.modes.css,
-  "/rss": Editor.modes.css,
-  "/css": Editor.modes.css
-};
 
-const DEFAULT_EDITOR_CONFIG = {
-  mode: Editor.modes.text,
-  readOnly: true,
-  lineNumbers: true
-};
-const GENERIC_VARIABLES_VIEW_SETTINGS = {
-  lazyEmpty: true,
-  // ms
-  lazyEmptyDelay: 10,
-  searchEnabled: true,
-  editableValueTooltip: "",
-  editableNameTooltip: "",
-  preventDisableOnChange: true,
-  preventDescriptorModifiers: true,
-  eval: () => {}
-};
 // px
 const NETWORK_ANALYSIS_PIE_CHART_DIAMETER = 200;
 
 /**
- * Object defining the network monitor view components.
- */
-var NetMonitorView = {
-  /**
-   * Initializes the network monitor view.
-   */
-  initialize: function () {
-    this._initializePanes();
-
-    this.Toolbar.initialize(gStore);
-    this.RequestsMenu.initialize(gStore);
-    this.NetworkDetails.initialize();
-    this.CustomRequest.initialize();
-  },
-
-  /**
-   * Destroys the network monitor view.
-   */
-  destroy: function () {
-    this._isDestroyed = true;
-    this.Toolbar.destroy();
-    this.RequestsMenu.destroy();
-    this.NetworkDetails.destroy();
-    this.CustomRequest.destroy();
-
-    this._destroyPanes();
-  },
-
-  /**
-   * Initializes the UI for all the displayed panes.
-   */
-  _initializePanes: function () {
-    dumpn("Initializing the NetMonitorView panes");
-
-    this._body = $("#body");
-    this._detailsPane = $("#details-pane");
-    this._detailsPaneToggleButton = $("#details-pane-toggle");
-
-    this._collapsePaneString = L10N.getStr("collapseDetailsPane");
-    this._expandPaneString = L10N.getStr("expandDetailsPane");
-
-    this._detailsPane.setAttribute("width", Prefs.networkDetailsWidth);
-    this._detailsPane.setAttribute("height", Prefs.networkDetailsHeight);
-    this.toggleDetailsPane({ visible: false });
-
-    // Disable the performance statistics mode.
-    if (!Prefs.statistics) {
-      $("#request-menu-context-perf").hidden = true;
-      $("#notice-perf-message").hidden = true;
-      $("#requests-menu-network-summary-button").hidden = true;
-    }
-  },
-
-  /**
-   * Destroys the UI for all the displayed panes.
-   */
-  _destroyPanes: Task.async(function* () {
-    dumpn("Destroying the NetMonitorView panes");
-
-    Prefs.networkDetailsWidth = this._detailsPane.getAttribute("width");
-    Prefs.networkDetailsHeight = this._detailsPane.getAttribute("height");
-
-    this._detailsPane = null;
-    this._detailsPaneToggleButton = null;
-
-    for (let p of this._editorPromises.values()) {
-      let editor = yield p;
-      editor.destroy();
-    }
-  }),
-
-  /**
-   * Gets the visibility state of the network details pane.
-   * @return boolean
-   */
-  get detailsPaneHidden() {
-    return this._detailsPane.classList.contains("pane-collapsed");
-  },
-
-  /**
-   * Sets the network details pane hidden or visible.
-   *
-   * @param object flags
-   *        An object containing some of the following properties:
-   *        - visible: true if the pane should be shown, false to hide
-   *        - animated: true to display an animation on toggle
-   *        - delayed: true to wait a few cycles before toggle
-   *        - callback: a function to invoke when the toggle finishes
-   * @param number tabIndex [optional]
-   *        The index of the intended selected tab in the details pane.
-   */
-  toggleDetailsPane: function (flags, tabIndex) {
-    let pane = this._detailsPane;
-    let button = this._detailsPaneToggleButton;
-
-    ViewHelpers.togglePane(flags, pane);
-
-    if (flags.visible) {
-      this._body.classList.remove("pane-collapsed");
-      button.classList.remove("pane-collapsed");
-      button.setAttribute("tooltiptext", this._collapsePaneString);
-    } else {
-      this._body.classList.add("pane-collapsed");
-      button.classList.add("pane-collapsed");
-      button.setAttribute("tooltiptext", this._expandPaneString);
-    }
-
-    if (tabIndex !== undefined) {
-      $("#event-details-pane").selectedIndex = tabIndex;
-    }
-  },
-
-  /**
-   * Gets the current mode for this tool.
-   * @return string (e.g, "network-inspector-view" or "network-statistics-view")
-   */
-  get currentFrontendMode() {
-    // The getter may be called from a timeout after the panel is destroyed.
-    if (!this._body.selectedPanel) {
-      return null;
-    }
-    return this._body.selectedPanel.id;
-  },
-
-  /**
-   * Toggles between the frontend view modes ("Inspector" vs. "Statistics").
-   */
-  toggleFrontendMode: function () {
-    if (this.currentFrontendMode != "network-inspector-view") {
-      this.showNetworkInspectorView();
-    } else {
-      this.showNetworkStatisticsView();
-    }
-  },
-
-  /**
-   * Switches to the "Inspector" frontend view mode.
-   */
-  showNetworkInspectorView: function () {
-    this._body.selectedPanel = $("#network-inspector-view");
-    this.RequestsMenu._flushWaterfallViews(true);
-  },
-
-  /**
-   * Switches to the "Statistics" frontend view mode.
-   */
-  showNetworkStatisticsView: function () {
-    this._body.selectedPanel = $("#network-statistics-view");
-
-    let controller = NetMonitorController;
-    let requestsView = this.RequestsMenu;
-    let statisticsView = this.PerformanceStatistics;
-
-    Task.spawn(function* () {
-      statisticsView.displayPlaceholderCharts();
-      yield controller.triggerActivity(ACTIVITY_TYPE.RELOAD.WITH_CACHE_ENABLED);
-
-      try {
-        // • The response headers and status code are required for determining
-        // whether a response is "fresh" (cacheable).
-        // • The response content size and request total time are necessary for
-        // populating the statistics view.
-        // • The response mime type is used for categorization.
-        yield whenDataAvailable(requestsView.attachments, [
-          "responseHeaders", "status", "contentSize", "mimeType", "totalTime"
-        ]);
-      } catch (ex) {
-        // Timed out while waiting for data. Continue with what we have.
-        console.error(ex);
-      }
-
-      statisticsView.createPrimedCacheChart(requestsView.items);
-      statisticsView.createEmptyCacheChart(requestsView.items);
-    });
-  },
-
-  reloadPage: function () {
-    NetMonitorController.triggerActivity(
-      ACTIVITY_TYPE.RELOAD.WITH_CACHE_DEFAULT);
-  },
-
-  /**
-   * Lazily initializes and returns a promise for a Editor instance.
-   *
-   * @param string id
-   *        The id of the editor placeholder node.
-   * @return object
-   *         A promise that is resolved when the editor is available.
-   */
-  editor: function (id) {
-    dumpn("Getting a NetMonitorView editor: " + id);
-
-    if (this._editorPromises.has(id)) {
-      return this._editorPromises.get(id);
-    }
-
-    let deferred = promise.defer();
-    this._editorPromises.set(id, deferred.promise);
-
-    // Initialize the source editor and store the newly created instance
-    // in the ether of a resolved promise's value.
-    let editor = new Editor(DEFAULT_EDITOR_CONFIG);
-    editor.appendTo($(id)).then(() => deferred.resolve(editor));
-
-    return deferred.promise;
-  },
-
-  _body: null,
-  _detailsPane: null,
-  _detailsPaneToggleButton: null,
-  _collapsePaneString: "",
-  _expandPaneString: "",
-  _editorPromises: new Map()
-};
-
-/**
- * Functions handling the sidebar details view.
- */
-function SidebarView() {
-  dumpn("SidebarView was instantiated");
-}
-
-SidebarView.prototype = {
-  /**
-   * Sets this view hidden or visible. It's visible by default.
-   *
-   * @param boolean visibleFlag
-   *        Specifies the intended visibility.
-   */
-  toggle: function (visibleFlag) {
-    NetMonitorView.toggleDetailsPane({ visible: visibleFlag });
-    NetMonitorView.RequestsMenu._flushWaterfallViews(true);
-  },
-
-  /**
-   * Populates this view with the specified data.
-   *
-   * @param object data
-   *        The data source (this should be the attachment of a request item).
-   * @return object
-   *        Returns a promise that resolves upon population of the subview.
-   */
-  populate: Task.async(function* (data) {
-    let isCustom = data.isCustom;
-    let view = isCustom ?
-      NetMonitorView.CustomRequest :
-      NetMonitorView.NetworkDetails;
-
-    yield view.populate(data);
-    $("#details-pane").selectedIndex = isCustom ? 0 : 1;
-
-    window.emit(EVENTS.SIDEBAR_POPULATED);
-  })
-};
-
-/**
- * Functions handling the requests details view.
- */
-function NetworkDetailsView() {
-  dumpn("NetworkDetailsView was instantiated");
-
-  // The ToolSidebar requires the panel object to be able to emit events.
-  EventEmitter.decorate(this);
-
-  this._onTabSelect = this._onTabSelect.bind(this);
-}
-
-NetworkDetailsView.prototype = {
-  /**
-   * An object containing the state of tabs.
-   */
-  _viewState: {
-    // if updating[tab] is true a task is currently updating the given tab.
-    updating: [],
-    // if dirty[tab] is true, the tab needs to be repopulated once current
-    // update task finishes
-    dirty: [],
-    // the most recently received attachment data for the request
-    latestData: null,
-  },
-
-  /**
-   * Initialization function, called when the network monitor is started.
-   */
-  initialize: function () {
-    dumpn("Initializing the NetworkDetailsView");
-
-    this.widget = $("#event-details-pane");
-    this.sidebar = new ToolSidebar(this.widget, this, "netmonitor", {
-      disableTelemetry: true,
-      showAllTabsMenu: true
-    });
-
-    this._headers = new VariablesView($("#all-headers"),
-      Heritage.extend(GENERIC_VARIABLES_VIEW_SETTINGS, {
-        emptyText: L10N.getStr("headersEmptyText"),
-        searchPlaceholder: L10N.getStr("headersFilterText")
-      }));
-    this._cookies = new VariablesView($("#all-cookies"),
-      Heritage.extend(GENERIC_VARIABLES_VIEW_SETTINGS, {
-        emptyText: L10N.getStr("cookiesEmptyText"),
-        searchPlaceholder: L10N.getStr("cookiesFilterText")
-      }));
-    this._params = new VariablesView($("#request-params"),
-      Heritage.extend(GENERIC_VARIABLES_VIEW_SETTINGS, {
-        emptyText: L10N.getStr("paramsEmptyText"),
-        searchPlaceholder: L10N.getStr("paramsFilterText")
-      }));
-    this._json = new VariablesView($("#response-content-json"),
-      Heritage.extend(GENERIC_VARIABLES_VIEW_SETTINGS, {
-        onlyEnumVisible: true,
-        searchPlaceholder: L10N.getStr("jsonFilterText")
-      }));
-    VariablesViewController.attach(this._json);
-
-    this._paramsQueryString = L10N.getStr("paramsQueryString");
-    this._paramsFormData = L10N.getStr("paramsFormData");
-    this._paramsPostPayload = L10N.getStr("paramsPostPayload");
-    this._requestHeaders = L10N.getStr("requestHeaders");
-    this._requestHeadersFromUpload = L10N.getStr("requestHeadersFromUpload");
-    this._responseHeaders = L10N.getStr("responseHeaders");
-    this._requestCookies = L10N.getStr("requestCookies");
-    this._responseCookies = L10N.getStr("responseCookies");
-
-    $("tabpanels", this.widget).addEventListener("select", this._onTabSelect);
-  },
-
-  /**
-   * Destruction function, called when the network monitor is closed.
-   */
-  destroy: function () {
-    dumpn("Destroying the NetworkDetailsView");
-    this.sidebar.destroy();
-    $("tabpanels", this.widget).removeEventListener("select",
-      this._onTabSelect);
-  },
-
-  /**
-   * Populates this view with the specified data.
-   *
-   * @param object data
-   *        The data source (this should be the attachment of a request item).
-   * @return object
-   *        Returns a promise that resolves upon population the view.
-   */
-  populate: function (data) {
-    $("#request-params-box").setAttribute("flex", "1");
-    $("#request-params-box").hidden = false;
-    $("#request-post-data-textarea-box").hidden = true;
-    $("#response-content-info-header").hidden = true;
-    $("#response-content-json-box").hidden = true;
-    $("#response-content-textarea-box").hidden = true;
-    $("#raw-headers").hidden = true;
-    $("#response-content-image-box").hidden = true;
-
-    let isHtml = Filters.html(data);
-
-    // Show the "Preview" tabpanel only for plain HTML responses.
-    this.sidebar.toggleTab(isHtml, "preview-tab");
-
-    // Show the "Security" tab only for requests that
-    //   1) are https (state != insecure)
-    //   2) come from a target that provides security information.
-    let hasSecurityInfo = data.securityState &&
-                          data.securityState !== "insecure";
-    this.sidebar.toggleTab(hasSecurityInfo, "security-tab");
-
-    // Switch to the "Headers" tabpanel if the "Preview" previously selected
-    // and this is not an HTML response or "Security" was selected but this
-    // request has no security information.
-
-    if (!isHtml && this.widget.selectedPanel === $("#preview-tabpanel") ||
-        !hasSecurityInfo && this.widget.selectedPanel ===
-          $("#security-tabpanel")) {
-      this.widget.selectedIndex = 0;
-    }
-
-    this._headers.empty();
-    this._cookies.empty();
-    this._params.empty();
-    this._json.empty();
-
-    this._dataSrc = { src: data, populated: [] };
-    this._onTabSelect();
-    window.emit(EVENTS.NETWORKDETAILSVIEW_POPULATED);
-
-    return promise.resolve();
-  },
-
-  /**
-   * Listener handling the tab selection event.
-   */
-  _onTabSelect: function () {
-    let { src, populated } = this._dataSrc || {};
-    let tab = this.widget.selectedIndex;
-    let view = this;
-
-    // Make sure the data source is valid and don't populate the same tab twice.
-    if (!src || populated[tab]) {
-      return;
-    }
-
-    let viewState = this._viewState;
-    if (viewState.updating[tab]) {
-      // A task is currently updating this tab. If we started another update
-      // task now it would result in a duplicated content as described in bugs
-      // 997065 and 984687. As there's no way to stop the current task mark the
-      // tab dirty and refresh the panel once the current task finishes.
-      viewState.dirty[tab] = true;
-      viewState.latestData = src;
-      return;
-    }
-
-    Task.spawn(function* () {
-      viewState.updating[tab] = true;
-      switch (tab) {
-        // "Headers"
-        case 0:
-          yield view._setSummary(src);
-          yield view._setResponseHeaders(src.responseHeaders);
-          yield view._setRequestHeaders(
-            src.requestHeaders,
-            src.requestHeadersFromUploadStream);
-          break;
-        // "Cookies"
-        case 1:
-          yield view._setResponseCookies(src.responseCookies);
-          yield view._setRequestCookies(src.requestCookies);
-          break;
-        // "Params"
-        case 2:
-          yield view._setRequestGetParams(src.url);
-          yield view._setRequestPostParams(
-            src.requestHeaders,
-            src.requestHeadersFromUploadStream,
-            src.requestPostData);
-          break;
-        // "Response"
-        case 3:
-          yield view._setResponseBody(src.url, src.responseContent);
-          break;
-        // "Timings"
-        case 4:
-          yield view._setTimingsInformation(src.eventTimings);
-          break;
-        // "Security"
-        case 5:
-          yield view._setSecurityInfo(src.securityInfo, src.url);
-          break;
-        // "Preview"
-        case 6:
-          yield view._setHtmlPreview(src.responseContent);
-          break;
-      }
-      viewState.updating[tab] = false;
-    }).then(() => {
-      if (tab == this.widget.selectedIndex) {
-        if (viewState.dirty[tab]) {
-          // The request information was updated while the task was running.
-          viewState.dirty[tab] = false;
-          view.populate(viewState.latestData);
-        } else {
-          // Tab is selected but not dirty. We're done here.
-          populated[tab] = true;
-          window.emit(EVENTS.TAB_UPDATED);
-
-          if (NetMonitorController.isConnected()) {
-            NetMonitorView.RequestsMenu.ensureSelectedItemIsVisible();
-          }
-        }
-      } else if (viewState.dirty[tab]) {
-        // Tab is dirty but no longer selected. Don't refresh it now, it'll be
-        // done if the tab is shown again.
-        viewState.dirty[tab] = false;
-      }
-    }, e => console.error(e));
-  },
-
-  /**
-   * Sets the network request summary shown in this view.
-   *
-   * @param object data
-   *        The data source (this should be the attachment of a request item).
-   */
-  _setSummary: function (data) {
-    if (data.url) {
-      let unicodeUrl = NetworkHelper.convertToUnicode(unescape(data.url));
-      $("#headers-summary-url-value").setAttribute("value", unicodeUrl);
-      $("#headers-summary-url-value").setAttribute("tooltiptext", unicodeUrl);
-      $("#headers-summary-url").removeAttribute("hidden");
-    } else {
-      $("#headers-summary-url").setAttribute("hidden", "true");
-    }
-
-    if (data.method) {
-      $("#headers-summary-method-value").setAttribute("value", data.method);
-      $("#headers-summary-method").removeAttribute("hidden");
-    } else {
-      $("#headers-summary-method").setAttribute("hidden", "true");
-    }
-
-    if (data.remoteAddress) {
-      let address = data.remoteAddress;
-      if (address.indexOf(":") != -1) {
-        address = `[${address}]`;
-      }
-      if (data.remotePort) {
-        address += `:${data.remotePort}`;
-      }
-      $("#headers-summary-address-value").setAttribute("value", address);
-      $("#headers-summary-address-value").setAttribute("tooltiptext", address);
-      $("#headers-summary-address").removeAttribute("hidden");
-    } else {
-      $("#headers-summary-address").setAttribute("hidden", "true");
-    }
-
-    if (data.status) {
-      // "code" attribute is only used by css to determine the icon color
-      let code;
-      if (data.fromCache) {
-        code = "cached";
-      } else if (data.fromServiceWorker) {
-        code = "service worker";
-      } else {
-        code = data.status;
-      }
-      $("#headers-summary-status-circle").setAttribute("code", code);
-      $("#headers-summary-status-value").setAttribute("value",
-        data.status + " " + data.statusText);
-      $("#headers-summary-status").removeAttribute("hidden");
-    } else {
-      $("#headers-summary-status").setAttribute("hidden", "true");
-    }
-
-    if (data.httpVersion) {
-      $("#headers-summary-version-value").setAttribute("value",
-        data.httpVersion);
-      $("#headers-summary-version").removeAttribute("hidden");
-    } else {
-      $("#headers-summary-version").setAttribute("hidden", "true");
-    }
-  },
-
-  /**
-   * Sets the network request headers shown in this view.
-   *
-   * @param object headers
-   *        The "requestHeaders" message received from the server.
-   * @param object uploadHeaders
-   *        The "requestHeadersFromUploadStream" inferred from the POST payload.
-   * @return object
-   *        A promise that resolves when request headers are set.
-   */
-  _setRequestHeaders: Task.async(function* (headers, uploadHeaders) {
-    if (headers && headers.headers.length) {
-      yield this._addHeaders(this._requestHeaders, headers);
-    }
-    if (uploadHeaders && uploadHeaders.headers.length) {
-      yield this._addHeaders(this._requestHeadersFromUpload, uploadHeaders);
-    }
-  }),
-
-  /**
-   * Sets the network response headers shown in this view.
-   *
-   * @param object response
-   *        The message received from the server.
-   * @return object
-   *        A promise that resolves when response headers are set.
-   */
-  _setResponseHeaders: Task.async(function* (response) {
-    if (response && response.headers.length) {
-      response.headers.sort((a, b) => a.name > b.name);
-      yield this._addHeaders(this._responseHeaders, response);
-    }
-  }),
-
-  /**
-   * Populates the headers container in this view with the specified data.
-   *
-   * @param string name
-   *        The type of headers to populate (request or response).
-   * @param object response
-   *        The message received from the server.
-   * @return object
-   *        A promise that resolves when headers are added.
-   */
-  _addHeaders: Task.async(function* (name, response) {
-    let kb = response.headersSize / 1024;
-    let size = L10N.numberWithDecimals(kb, HEADERS_SIZE_DECIMALS);
-    let text = L10N.getFormatStr("networkMenu.sizeKB", size);
-
-    let headersScope = this._headers.addScope(name + " (" + text + ")");
-    headersScope.expanded = true;
-
-    for (let header of response.headers) {
-      let headerVar = headersScope.addItem(header.name, {}, {relaxed: true});
-      let headerValue = yield gNetwork.getString(header.value);
-      headerVar.setGrip(headerValue);
-    }
-  }),
-
-  /**
-   * Sets the network request cookies shown in this view.
-   *
-   * @param object response
-   *        The message received from the server.
-   * @return object
-   *        A promise that is resolved when the request cookies are set.
-   */
-  _setRequestCookies: Task.async(function* (response) {
-    if (response && response.cookies.length) {
-      response.cookies.sort((a, b) => a.name > b.name);
-      yield this._addCookies(this._requestCookies, response);
-    }
-  }),
-
-  /**
-   * Sets the network response cookies shown in this view.
-   *
-   * @param object response
-   *        The message received from the server.
-   * @return object
-   *        A promise that is resolved when the response cookies are set.
-   */
-  _setResponseCookies: Task.async(function* (response) {
-    if (response && response.cookies.length) {
-      yield this._addCookies(this._responseCookies, response);
-    }
-  }),
-
-  /**
-   * Populates the cookies container in this view with the specified data.
-   *
-   * @param string name
-   *        The type of cookies to populate (request or response).
-   * @param object response
-   *        The message received from the server.
-   * @return object
-   *        Returns a promise that resolves upon the adding of cookies.
-   */
-  _addCookies: Task.async(function* (name, response) {
-    let cookiesScope = this._cookies.addScope(name);
-    cookiesScope.expanded = true;
-
-    for (let cookie of response.cookies) {
-      let cookieVar = cookiesScope.addItem(cookie.name, {}, {relaxed: true});
-      let cookieValue = yield gNetwork.getString(cookie.value);
-      cookieVar.setGrip(cookieValue);
-
-      // By default the cookie name and value are shown. If this is the only
-      // information available, then nothing else is to be displayed.
-      let cookieProps = Object.keys(cookie);
-      if (cookieProps.length == 2) {
-        continue;
-      }
-
-      // Display any other information other than the cookie name and value
-      // which may be available.
-      let rawObject = Object.create(null);
-      let otherProps = cookieProps.filter(e => e != "name" && e != "value");
-      for (let prop of otherProps) {
-        rawObject[prop] = cookie[prop];
-      }
-      cookieVar.populate(rawObject);
-      cookieVar.twisty = true;
-      cookieVar.expanded = true;
-    }
-  }),
-
-  /**
-   * Sets the network request get params shown in this view.
-   *
-   * @param string url
-   *        The request's url.
-   */
-  _setRequestGetParams: function (url) {
-    let query = NetworkHelper.nsIURL(url).query;
-    if (query) {
-      this._addParams(this._paramsQueryString, query);
-    }
-  },
-
-  /**
-   * Sets the network request post params shown in this view.
-   *
-   * @param object headers
-   *        The "requestHeaders" message received from the server.
-   * @param object uploadHeaders
-   *        The "requestHeadersFromUploadStream" inferred from the POST payload.
-   * @param object postData
-   *        The "requestPostData" message received from the server.
-   * @return object
-   *        A promise that is resolved when the request post params are set.
-   */
-  _setRequestPostParams: Task.async(function* (headers, uploadHeaders,
-    postData) {
-    if (!headers || !uploadHeaders || !postData) {
-      return;
-    }
-
-    let formDataSections = yield getFormDataSections(
-      headers,
-      uploadHeaders,
-      postData,
-      gNetwork.getString.bind(gNetwork));
-
-    this._params.onlyEnumVisible = false;
-
-    // Handle urlencoded form data sections (e.g. "?foo=bar&baz=42").
-    if (formDataSections.length > 0) {
-      formDataSections.forEach(section => {
-        this._addParams(this._paramsFormData, section);
-      });
-    } else {
-      // Handle JSON and actual forms ("multipart/form-data" content type).
-      let postDataLongString = postData.postData.text;
-      let text = yield gNetwork.getString(postDataLongString);
-      let jsonVal = null;
-      try {
-        jsonVal = JSON.parse(text);
-      } catch (ex) { // eslint-disable-line
-      }
-
-      if (jsonVal) {
-        this._params.onlyEnumVisible = true;
-        let jsonScopeName = L10N.getStr("jsonScopeName");
-        let jsonScope = this._params.addScope(jsonScopeName);
-        jsonScope.expanded = true;
-        let jsonItem = jsonScope.addItem(undefined, { enumerable: true });
-        jsonItem.populate(jsonVal, { sorted: true });
-      } else {
-        // This is really awkward, but hey, it works. Let's show an empty
-        // scope in the params view and place the source editor containing
-        // the raw post data directly underneath.
-        $("#request-params-box").removeAttribute("flex");
-        let paramsScope = this._params.addScope(this._paramsPostPayload);
-        paramsScope.expanded = true;
-        paramsScope.locked = true;
-
-        $("#request-post-data-textarea-box").hidden = false;
-        let editor = yield NetMonitorView.editor("#request-post-data-textarea");
-        editor.setMode(Editor.modes.text);
-        editor.setText(text);
-      }
-    }
-
-    window.emit(EVENTS.REQUEST_POST_PARAMS_DISPLAYED);
-  }),
-
-  /**
-   * Populates the params container in this view with the specified data.
-   *
-   * @param string name
-   *        The type of params to populate (get or post).
-   * @param string queryString
-   *        A query string of params (e.g. "?foo=bar&baz=42").
-   */
-  _addParams: function (name, queryString) {
-    let paramsArray = NetworkHelper.parseQueryString(queryString);
-    if (!paramsArray) {
-      return;
-    }
-    let paramsScope = this._params.addScope(name);
-    paramsScope.expanded = true;
-
-    for (let param of paramsArray) {
-      let paramVar = paramsScope.addItem(param.name, {}, {relaxed: true});
-      paramVar.setGrip(param.value);
-    }
-  },
-
-  /**
-   * Sets the network response body shown in this view.
-   *
-   * @param string url
-   *        The request's url.
-   * @param object response
-   *        The message received from the server.
-   * @return object
-   *         A promise that is resolved when the response body is set.
-   */
-  _setResponseBody: Task.async(function* (url, response) {
-    if (!response) {
-      return;
-    }
-    let { mimeType, text, encoding } = response.content;
-    let responseBody = yield gNetwork.getString(text);
-
-    // Handle json, which we tentatively identify by checking the MIME type
-    // for "json" after any word boundary. This works for the standard
-    // "application/json", and also for custom types like "x-bigcorp-json".
-    // Additionally, we also directly parse the response text content to
-    // verify whether it's json or not, to handle responses incorrectly
-    // labeled as text/plain instead.
-    let jsonMimeType, jsonObject, jsonObjectParseError;
-    try {
-      jsonMimeType = /\bjson/.test(mimeType);
-      jsonObject = JSON.parse(responseBody);
-    } catch (e) {
-      jsonObjectParseError = e;
-    }
-    if (jsonMimeType || jsonObject) {
-      // Extract the actual json substring in case this might be a "JSONP".
-      // This regex basically parses a function call and captures the
-      // function name and arguments in two separate groups.
-      let jsonpRegex = /^\s*([\w$]+)\s*\(\s*([^]*)\s*\)\s*;?\s*$/;
-      let [_, callbackPadding, jsonpString] = // eslint-disable-line
-        responseBody.match(jsonpRegex) || [];
-
-      // Make sure this is a valid JSON object first. If so, nicely display
-      // the parsing results in a variables view. Otherwise, simply show
-      // the contents as plain text.
-      if (callbackPadding && jsonpString) {
-        try {
-          jsonObject = JSON.parse(jsonpString);
-        } catch (e) {
-          jsonObjectParseError = e;
-        }
-      }
-
-      // Valid JSON or JSONP.
-      if (jsonObject) {
-        $("#response-content-json-box").hidden = false;
-        let jsonScopeName = callbackPadding
-          ? L10N.getFormatStr("jsonpScopeName", callbackPadding)
-          : L10N.getStr("jsonScopeName");
-
-        let jsonVar = { label: jsonScopeName, rawObject: jsonObject };
-        yield this._json.controller.setSingleVariable(jsonVar).expanded;
-      } else {
-        // Malformed JSON.
-        $("#response-content-textarea-box").hidden = false;
-        let infoHeader = $("#response-content-info-header");
-        infoHeader.setAttribute("value", jsonObjectParseError);
-        infoHeader.setAttribute("tooltiptext", jsonObjectParseError);
-        infoHeader.hidden = false;
-
-        let editor = yield NetMonitorView.editor("#response-content-textarea");
-        editor.setMode(Editor.modes.js);
-        editor.setText(responseBody);
-      }
-    } else if (mimeType.includes("image/")) {
-      // Handle images.
-      $("#response-content-image-box").setAttribute("align", "center");
-      $("#response-content-image-box").setAttribute("pack", "center");
-      $("#response-content-image-box").hidden = false;
-      $("#response-content-image").src = formDataURI(mimeType, encoding, responseBody);
-
-      // Immediately display additional information about the image:
-      // file name, mime type and encoding.
-      $("#response-content-image-name-value").setAttribute("value",
-        NetworkHelper.nsIURL(url).fileName);
-      $("#response-content-image-mime-value").setAttribute("value", mimeType);
-
-      // Wait for the image to load in order to display the width and height.
-      $("#response-content-image").onload = e => {
-        // XUL images are majestic so they don't bother storing their dimensions
-        // in width and height attributes like the rest of the folk. Hack around
-        // this by getting the bounding client rect and subtracting the margins.
-        let { width, height } = e.target.getBoundingClientRect();
-        let dimensions = (width - 2) + " \u00D7 " + (height - 2);
-        $("#response-content-image-dimensions-value").setAttribute("value",
-          dimensions);
-      };
-    } else {
-      $("#response-content-textarea-box").hidden = false;
-      let editor = yield NetMonitorView.editor("#response-content-textarea");
-      editor.setMode(Editor.modes.text);
-      editor.setText(responseBody);
-
-      // Maybe set a more appropriate mode in the Source Editor if possible,
-      // but avoid doing this for very large files.
-      if (responseBody.length < SOURCE_SYNTAX_HIGHLIGHT_MAX_FILE_SIZE) {
-        let mapping = Object.keys(CONTENT_MIME_TYPE_MAPPINGS).find(key => {
-          return mimeType.includes(key);
-        });
-
-        if (mapping) {
-          editor.setMode(CONTENT_MIME_TYPE_MAPPINGS[mapping]);
-        }
-      }
-    }
-
-    window.emit(EVENTS.RESPONSE_BODY_DISPLAYED);
-  }),
-
-  /**
-   * Sets the timings information shown in this view.
-   *
-   * @param object response
-   *        The message received from the server.
-   */
-  _setTimingsInformation: function (response) {
-    if (!response) {
-      return;
-    }
-    let { blocked, dns, connect, send, wait, receive } = response.timings;
-
-    let tabboxWidth = $("#details-pane").getAttribute("width");
-
-    // Other nodes also take some space.
-    let availableWidth = tabboxWidth / 2;
-    let scale = (response.totalTime > 0 ?
-                 Math.max(availableWidth / response.totalTime, 0) :
-                 0);
-
-    $("#timings-summary-blocked .requests-menu-timings-box")
-      .setAttribute("width", blocked * scale);
-    $("#timings-summary-blocked .requests-menu-timings-total")
-      .setAttribute("value", L10N.getFormatStr("networkMenu.totalMS", blocked));
-
-    $("#timings-summary-dns .requests-menu-timings-box")
-      .setAttribute("width", dns * scale);
-    $("#timings-summary-dns .requests-menu-timings-total")
-      .setAttribute("value", L10N.getFormatStr("networkMenu.totalMS", dns));
-
-    $("#timings-summary-connect .requests-menu-timings-box")
-      .setAttribute("width", connect * scale);
-    $("#timings-summary-connect .requests-menu-timings-total")
-      .setAttribute("value", L10N.getFormatStr("networkMenu.totalMS", connect));
-
-    $("#timings-summary-send .requests-menu-timings-box")
-      .setAttribute("width", send * scale);
-    $("#timings-summary-send .requests-menu-timings-total")
-      .setAttribute("value", L10N.getFormatStr("networkMenu.totalMS", send));
-
-    $("#timings-summary-wait .requests-menu-timings-box")
-      .setAttribute("width", wait * scale);
-    $("#timings-summary-wait .requests-menu-timings-total")
-      .setAttribute("value", L10N.getFormatStr("networkMenu.totalMS", wait));
-
-    $("#timings-summary-receive .requests-menu-timings-box")
-      .setAttribute("width", receive * scale);
-    $("#timings-summary-receive .requests-menu-timings-total")
-      .setAttribute("value", L10N.getFormatStr("networkMenu.totalMS", receive));
-
-    $("#timings-summary-dns .requests-menu-timings-box")
-      .style.transform = "translateX(" + (scale * blocked) + "px)";
-    $("#timings-summary-connect .requests-menu-timings-box")
-      .style.transform = "translateX(" + (scale * (blocked + dns)) + "px)";
-    $("#timings-summary-send .requests-menu-timings-box")
-      .style.transform =
-        "translateX(" + (scale * (blocked + dns + connect)) + "px)";
-    $("#timings-summary-wait .requests-menu-timings-box")
-      .style.transform =
-        "translateX(" + (scale * (blocked + dns + connect + send)) + "px)";
-    $("#timings-summary-receive .requests-menu-timings-box")
-      .style.transform =
-        "translateX(" + (scale * (blocked + dns + connect + send + wait)) +
-          "px)";
-
-    $("#timings-summary-dns .requests-menu-timings-total")
-      .style.transform = "translateX(" + (scale * blocked) + "px)";
-    $("#timings-summary-connect .requests-menu-timings-total")
-      .style.transform = "translateX(" + (scale * (blocked + dns)) + "px)";
-    $("#timings-summary-send .requests-menu-timings-total")
-      .style.transform =
-        "translateX(" + (scale * (blocked + dns + connect)) + "px)";
-    $("#timings-summary-wait .requests-menu-timings-total")
-      .style.transform =
-        "translateX(" + (scale * (blocked + dns + connect + send)) + "px)";
-    $("#timings-summary-receive .requests-menu-timings-total")
-      .style.transform =
-        "translateX(" + (scale * (blocked + dns + connect + send + wait)) +
-         "px)";
-  },
-
-  /**
-   * Sets the preview for HTML responses shown in this view.
-   *
-   * @param object response
-   *        The message received from the server.
-   * @return object
-   *        A promise that is resolved when the html preview is rendered.
-   */
-  _setHtmlPreview: Task.async(function* (response) {
-    if (!response) {
-      return promise.resolve();
-    }
-    let { text } = response.content;
-    let responseBody = yield gNetwork.getString(text);
-
-    // Always disable JS when previewing HTML responses.
-    let iframe = $("#response-preview");
-    iframe.contentDocument.docShell.allowJavascript = false;
-    iframe.contentDocument.documentElement.innerHTML = responseBody;
-
-    window.emit(EVENTS.RESPONSE_HTML_PREVIEW_DISPLAYED);
-    return undefined;
-  }),
-
-  /**
-   * Sets the security information shown in this view.
-   *
-   * @param object securityInfo
-   *        The data received from server
-   * @param string url
-   *        The URL of this request
-   * @return object
-   *        A promise that is resolved when the security info is rendered.
-   */
-  _setSecurityInfo: Task.async(function* (securityInfo, url) {
-    if (!securityInfo) {
-      // We don't have security info. This could mean one of two things:
-      // 1) This connection is not secure and this tab is not visible and thus
-      //    we shouldn't be here.
-      // 2) We have already received securityState and the tab is visible BUT
-      //    the rest of the information is still on its way. Once it arrives
-      //    this method is called again.
-      return;
-    }
-
-    /**
-     * A helper that sets value and tooltiptext attributes of an element to
-     * specified value.
-     *
-     * @param string selector
-     *        A selector for the element.
-     * @param string value
-     *        The value to set. If this evaluates to false a placeholder string
-     *        <Not Available> is used instead.
-     */
-    function setValue(selector, value) {
-      let label = $(selector);
-      if (!value) {
-        label.setAttribute("value", L10N.getStr(
-          "netmonitor.security.notAvailable"));
-        label.setAttribute("tooltiptext", label.getAttribute("value"));
-      } else {
-        label.setAttribute("value", value);
-        label.setAttribute("tooltiptext", value);
-      }
-    }
-
-    let errorbox = $("#security-error");
-    let infobox = $("#security-information");
-
-    if (securityInfo.state === "secure" || securityInfo.state === "weak") {
-      infobox.hidden = false;
-      errorbox.hidden = true;
-
-      // Warning icons
-      let cipher = $("#security-warning-cipher");
-
-      if (securityInfo.state === "weak") {
-        cipher.hidden = securityInfo.weaknessReasons.indexOf("cipher") === -1;
-      } else {
-        cipher.hidden = true;
-      }
-
-      let enabledLabel = L10N.getStr("netmonitor.security.enabled");
-      let disabledLabel = L10N.getStr("netmonitor.security.disabled");
-
-      // Connection parameters
-      setValue("#security-protocol-version-value",
-        securityInfo.protocolVersion);
-      setValue("#security-ciphersuite-value", securityInfo.cipherSuite);
-
-      // Host header
-      let domain = getUriHostPort(url);
-      let hostHeader = L10N.getFormatStr("netmonitor.security.hostHeader",
-        domain);
-      setValue("#security-info-host-header", hostHeader);
-
-      // Parameters related to the domain
-      setValue("#security-http-strict-transport-security-value",
-                securityInfo.hsts ? enabledLabel : disabledLabel);
-
-      setValue("#security-public-key-pinning-value",
-                securityInfo.hpkp ? enabledLabel : disabledLabel);
-
-      // Certificate parameters
-      let cert = securityInfo.cert;
-      setValue("#security-cert-subject-cn", cert.subject.commonName);
-      setValue("#security-cert-subject-o", cert.subject.organization);
-      setValue("#security-cert-subject-ou", cert.subject.organizationalUnit);
-
-      setValue("#security-cert-issuer-cn", cert.issuer.commonName);
-      setValue("#security-cert-issuer-o", cert.issuer.organization);
-      setValue("#security-cert-issuer-ou", cert.issuer.organizationalUnit);
-
-      setValue("#security-cert-validity-begins", cert.validity.start);
-      setValue("#security-cert-validity-expires", cert.validity.end);
-
-      setValue("#security-cert-sha1-fingerprint", cert.fingerprint.sha1);
-      setValue("#security-cert-sha256-fingerprint", cert.fingerprint.sha256);
-    } else {
-      infobox.hidden = true;
-      errorbox.hidden = false;
-
-      // Strip any HTML from the message.
-      let plain = new DOMParser().parseFromString(securityInfo.errorMessage,
-        "text/html");
-      setValue("#security-error-message", plain.body.textContent);
-    }
-  }),
-
-  _dataSrc: null,
-  _headers: null,
-  _cookies: null,
-  _params: null,
-  _json: null,
-  _paramsQueryString: "",
-  _paramsFormData: "",
-  _paramsPostPayload: "",
-  _requestHeaders: "",
-  _responseHeaders: "",
-  _requestCookies: "",
-  _responseCookies: ""
-};
-
-/**
  * Functions handling the performance statistics view.
  */
 function PerformanceStatisticsView() {
 }
 
 PerformanceStatisticsView.prototype = {
   /**
+   * Initialization function, called when the debugger is started.
+   */
+  initialize: function (store) {
+    this.store = store;
+  },
+
+  /**
    * Initializes and displays empty charts in this container.
    */
   displayPlaceholderCharts: function () {
     this._createChart({
       id: "#primed-cache-chart",
       title: "charts.cacheEnabled"
     });
     this._createChart({
@@ -1310,17 +139,17 @@ PerformanceStatisticsView.prototype = {
       data: data,
       strings: strings,
       totals: totals,
       sorted: sorted
     });
 
     chart.on("click", (_, item) => {
       // Reset FilterButtons and enable one filter exclusively
-      gStore.dispatch(Actions.enableFilterOnly(item.label));
+      this.store.dispatch(Actions.enableFilterOnly(item.label));
       NetMonitorView.showNetworkInspectorView();
     });
 
     container.appendChild(chart.node);
   },
 
   /**
    * Sanitizes the data source used for creating charts, to follow the
@@ -1388,22 +217,16 @@ PerformanceStatisticsView.prototype = {
       data[type].count++;
     }
 
     return data.filter(e => e.count > 0);
   },
 };
 
 /**
- * DOM query helper.
- */
-var $ = (selector, target = document) => target.querySelector(selector);
-var $all = (selector, target = document) => target.querySelectorAll(selector);
-
-/**
  * Checks if the "Expiration Calculations" defined in section 13.2.4 of the
  * "HTTP/1.1: Caching in HTTP" spec holds true for a collection of headers.
  *
  * @param object
  *        An object containing the { responseHeaders, status } properties.
  * @return boolean
  *         True if the response is fresh and loaded from cache.
  */
@@ -1434,49 +257,9 @@ function responseIsFresh({ responseHeade
   // Check the "Expires" header for a valid date.
   if (expires && Date.parse(expires.value)) {
     return true;
   }
 
   return false;
 }
 
-/**
- * Makes sure certain properties are available on all objects in a data store.
- *
- * @param array dataStore
- *        A list of objects for which to check the availability of properties.
- * @param array mandatoryFields
- *        A list of strings representing properties of objects in dataStore.
- * @return object
- *         A promise resolved when all objects in dataStore contain the
- *         properties defined in mandatoryFields.
- */
-function whenDataAvailable(dataStore, mandatoryFields) {
-  let deferred = promise.defer();
-
-  let interval = setInterval(() => {
-    if (dataStore.every(item => {
-      return mandatoryFields.every(field => field in item);
-    })) {
-      clearInterval(interval);
-      clearTimeout(timer);
-      deferred.resolve();
-    }
-  }, WDA_DEFAULT_VERIFY_INTERVAL);
-
-  let timer = setTimeout(() => {
-    clearInterval(interval);
-    deferred.reject(new Error("Timed out while waiting for data"));
-  }, WDA_DEFAULT_GIVE_UP_TIMEOUT);
-
-  return deferred.promise;
-}
-
-/**
- * Preliminary setup for the NetMonitorView object.
- */
-NetMonitorView.Toolbar = new ToolbarView();
-NetMonitorView.RequestsMenu = new RequestsMenuView();
-NetMonitorView.Sidebar = new SidebarView();
-NetMonitorView.CustomRequest = new CustomRequestView();
-NetMonitorView.NetworkDetails = new NetworkDetailsView();
-NetMonitorView.PerformanceStatistics = new PerformanceStatisticsView();
+exports.PerformanceStatisticsView = PerformanceStatisticsView;
--- a/devtools/client/shared/components/reps/grip.js
+++ b/devtools/client/shared/components/reps/grip.js
@@ -45,16 +45,26 @@ define(function (require, exports, modul
         return this.propIterator(object, max);
       } catch (err) {
         console.error(err);
       }
       return [];
     },
 
     propIterator: function (object, max) {
+      if (Object.keys(object.preview).includes("wrappedValue")) {
+        const { Rep } = createFactories(require("./rep"));
+
+        return [Rep({
+          object: object.preview.wrappedValue,
+          mode: this.props.mode || "tiny",
+          defaultRep: Grip,
+        })];
+      }
+
       // Property filter. Show only interesting properties to the user.
       let isInterestingProp = this.props.isInterestingProp || ((type, value) => {
         return (
           type == "boolean" ||
           type == "number" ||
           (type == "string" && value.length != 0)
         );
       });
--- a/devtools/client/shared/components/reps/moz.build
+++ b/devtools/client/shared/components/reps/moz.build
@@ -18,16 +18,17 @@ DevToolsModules(
     'grip.js',
     'infinity.js',
     'nan.js',
     'null.js',
     'number.js',
     'object-with-text.js',
     'object-with-url.js',
     'object.js',
+    'promise.js',
     'prop-rep.js',
     'regexp.js',
     'rep-utils.js',
     'rep.js',
     'reps.css',
     'string.js',
     'stylesheet.js',
     'symbol.js',
new file mode 100644
--- /dev/null
+++ b/devtools/client/shared/components/reps/promise.js
@@ -0,0 +1,111 @@
+/* -*- 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";
+
+// Make this available to both AMD and CJS environments
+define(function (require, exports, module) {
+  // ReactJS
+  const React = require("devtools/client/shared/vendor/react");
+  // Dependencies
+  const { createFactories, isGrip } = require("./rep-utils");
+  const { PropRep } = createFactories(require("./prop-rep"));
+  // Shortcuts
+  const { span } = React.DOM;
+
+  /**
+   * Renders a DOM Promise object.
+   */
+  const PromiseRep = React.createClass({
+    displayName: "Promise",
+
+    propTypes: {
+      object: React.PropTypes.object.isRequired,
+      mode: React.PropTypes.string,
+    },
+
+    getTitle: function (object) {
+      const title = object.class;
+      if (this.props.objectLink) {
+        return this.props.objectLink({
+          object: object
+        }, title);
+      }
+      return title;
+    },
+
+    getProps: function (promiseState) {
+      const keys = ["state"];
+      if (Object.keys(promiseState).includes("value")) {
+        keys.push("value");
+      }
+
+      return keys.map((key, i) => {
+        return PropRep(Object.assign({}, this.props, {
+          mode: "tiny",
+          name: `<${key}>`,
+          object: promiseState[key],
+          equal: ": ",
+          delim: i < keys.length - 1 ? ", " : ""
+        }));
+      });
+    },
+
+    render: function () {
+      const object = this.props.object;
+      const {promiseState} = object;
+      let objectLink = this.props.objectLink || span;
+
+      if (this.props.mode == "tiny") {
+        let { Rep } = createFactories(require("./rep"));
+
+        return (
+          span({className: "objectBox objectBox-object"},
+            this.getTitle(object),
+            objectLink({
+              className: "objectLeftBrace",
+              object: object
+            }, " { "),
+            Rep({object: promiseState.state}),
+            objectLink({
+              className: "objectRightBrace",
+              object: object
+            }, " }")
+          )
+        );
+      }
+
+      const props = this.getProps(promiseState);
+      return (
+        span({className: "objectBox objectBox-object"},
+          this.getTitle(object),
+          objectLink({
+            className: "objectLeftBrace",
+            object: object
+          }, " { "),
+          ...props,
+          objectLink({
+            className: "objectRightBrace",
+            object: object
+          }, " }")
+        )
+      );
+    },
+  });
+
+  // Registration
+  function supportsObject(object, type) {
+    if (!isGrip(object)) {
+      return false;
+    }
+    return type === "Promise";
+  }
+
+  // Exports from this module
+  exports.PromiseRep = {
+    rep: PromiseRep,
+    supportsObject: supportsObject
+  };
+});
--- a/devtools/client/shared/components/reps/rep.js
+++ b/devtools/client/shared/components/reps/rep.js
@@ -25,16 +25,17 @@ define(function (require, exports, modul
   const { NaNRep } = require("./nan");
 
   // DOM types (grips)
   const { Attribute } = require("./attribute");
   const { DateTime } = require("./date-time");
   const { Document } = require("./document");
   const { Event } = require("./event");
   const { Func } = require("./function");
+  const { PromiseRep } = require("./promise");
   const { RegExp } = require("./regexp");
   const { StyleSheet } = require("./stylesheet");
   const { CommentNode } = require("./comment-node");
   const { TextNode } = require("./text-node");
   const { Window } = require("./window");
   const { ObjectWithText } = require("./object-with-text");
   const { ObjectWithURL } = require("./object-with-url");
   const { GripArray } = require("./grip-array");
@@ -48,16 +49,17 @@ define(function (require, exports, modul
     RegExp,
     StyleSheet,
     Event,
     DateTime,
     CommentNode,
     TextNode,
     Attribute,
     Func,
+    PromiseRep,
     ArrayRep,
     Document,
     Window,
     ObjectWithText,
     ObjectWithURL,
     GripArray,
     GripMap,
     Grip,
--- a/devtools/client/shared/components/test/mochitest/chrome.ini
+++ b/devtools/client/shared/components/test/mochitest/chrome.ini
@@ -19,16 +19,17 @@ support-files =
 [test_reps_grip-map.html]
 [test_reps_infinity.html]
 [test_reps_nan.html]
 [test_reps_null.html]
 [test_reps_number.html]
 [test_reps_object.html]
 [test_reps_object-with-text.html]
 [test_reps_object-with-url.html]
+[test_reps_promise.html]
 [test_reps_regexp.html]
 [test_reps_string.html]
 [test_reps_stylesheet.html]
 [test_reps_symbol.html]
 [test_reps_text-node.html]
 [test_reps_undefined.html]
 [test_reps_window.html]
 [test_sidebar_toggle.html]
--- a/devtools/client/shared/components/test/mochitest/test_reps_grip.html
+++ b/devtools/client/shared/components/test/mochitest/test_reps_grip.html
@@ -17,16 +17,19 @@ Test grip rep
 window.onload = Task.async(function* () {
   let { Rep } = browserRequire("devtools/client/shared/components/reps/rep");
   let { Grip } = browserRequire("devtools/client/shared/components/reps/grip");
 
   const componentUnderTest = Grip;
 
   try {
     yield testBasic();
+    yield testBooleanObject();
+    yield testNumberObject();
+    yield testStringObject();
 
     // Test property iterator
     yield testMaxProps();
     yield testMoreThanMaxProps();
     yield testUninterestingProps();
     yield testNonEnumerableProps();
 
     // Test that properties are rendered as expected by PropRep
@@ -70,16 +73,118 @@ window.onload = Task.async(function* () 
         mode: "long",
         expectedOutput: defaultOutput,
       }
     ];
 
     testRepRenderModes(modeTests, testName, componentUnderTest, getGripStub(testName));
   }
 
+  function testBooleanObject() {
+    // Test object: `new Boolean(true)`
+    const testName = "testBooleanObject";
+
+    // Test that correct rep is chosen
+    const gripStub = getGripStub(testName);
+    const renderedRep = shallowRenderComponent(Rep, { object: gripStub });
+    is(renderedRep.type, Grip.rep, `Rep correctly selects ${Grip.rep.displayName}`);
+
+    // Test rendering
+    const defaultOutput = `Boolean { true }`;
+
+    const modeTests = [
+      {
+        mode: undefined,
+        expectedOutput: defaultOutput,
+      },
+      {
+        mode: "tiny",
+        expectedOutput: `Boolean`,
+      },
+      {
+        mode: "short",
+        expectedOutput: defaultOutput,
+      },
+      {
+        mode: "long",
+        expectedOutput: defaultOutput,
+      }
+    ];
+
+    testRepRenderModes(modeTests, testName, componentUnderTest, getGripStub(testName));
+  }
+
+  function testNumberObject() {
+    // Test object: `new Number(42)`
+    const testName = "testNumberObject";
+
+    // Test that correct rep is chosen
+    const gripStub = getGripStub(testName);
+    const renderedRep = shallowRenderComponent(Rep, { object: gripStub });
+    is(renderedRep.type, Grip.rep, `Rep correctly selects ${Grip.rep.displayName}`);
+
+    // Test rendering
+    const defaultOutput = `Number { 42 }`;
+
+    const modeTests = [
+      {
+        mode: undefined,
+        expectedOutput: defaultOutput,
+      },
+      {
+        mode: "tiny",
+        expectedOutput: `Number`,
+      },
+      {
+        mode: "short",
+        expectedOutput: defaultOutput,
+      },
+      {
+        mode: "long",
+        expectedOutput: defaultOutput,
+      }
+    ];
+
+    testRepRenderModes(modeTests, testName, componentUnderTest, getGripStub(testName));
+  }
+
+  function testStringObject() {
+    // Test object: `new String("foo")`
+    const testName = "testStringObject";
+
+    // Test that correct rep is chosen
+    const gripStub = getGripStub(testName);
+    const renderedRep = shallowRenderComponent(Rep, { object: gripStub });
+    is(renderedRep.type, Grip.rep, `Rep correctly selects ${Grip.rep.displayName}`);
+
+    // Test rendering
+    const defaultOutput = `String { "foo" }`;
+
+    const modeTests = [
+      {
+        mode: undefined,
+        expectedOutput: defaultOutput,
+      },
+      {
+        mode: "tiny",
+        expectedOutput: `String`,
+      },
+      {
+        mode: "short",
+        expectedOutput: defaultOutput,
+      },
+      {
+        mode: "long",
+        expectedOutput: defaultOutput,
+      }
+    ];
+
+    testRepRenderModes(modeTests, testName, componentUnderTest, getGripStub(testName));
+  }
+
   function testMaxProps() {
     // Test object: `{a: "a", b: "b", c: "c"}`;
     const testName = "testMaxProps";
 
     const defaultOutput = `Object { a: "a", b: "b", c: "c" }`;
 
     const modeTests = [
       {
@@ -525,16 +630,57 @@ window.onload = Task.async(function* () 
                 "writable": true,
                 "value": 3
               }
             },
             "ownPropertiesLength": 4,
             "safeGetterValues": {}
           }
         };
-
+      case "testBooleanObject":
+        return {
+          "type": "object",
+          "actor": "server1.conn1.child1/obj57",
+          "class": "Boolean",
+          "ownPropertyLength": 0,
+          "preview": {
+            "kind": "Object",
+            "ownProperties": {},
+            "ownPropertiesLength": 0,
+            "safeGetterValues": {},
+            "wrappedValue": true
+          }
+        };
+      case "testNumberObject":
+        return {
+          "type": "object",
+          "actor": "server1.conn1.child1/obj59",
+          "class": "Number",
+          "ownPropertyLength": 0,
+          "preview": {
+            "kind": "Object",
+            "ownProperties": {},
+            "ownPropertiesLength": 0,
+            "safeGetterValues": {},
+            "wrappedValue": 42
+          }
+        };
+      case "testStringObject":
+        return {
+          "type": "object",
+          "actor": "server1.conn1.child1/obj61",
+          "class": "String",
+          "ownPropertyLength": 4,
+          "preview": {
+            "kind": "Object",
+            "ownProperties": {},
+            "ownPropertiesLength": 4,
+            "safeGetterValues": {},
+            "wrappedValue": "foo"
+          }
+        };
     }
   }
 });
 </script>
 </pre>
 </body>
 </html>
new file mode 100644
--- /dev/null
+++ b/devtools/client/shared/components/test/mochitest/test_reps_promise.html
@@ -0,0 +1,331 @@
+
+<!DOCTYPE HTML>
+<html>
+<!--
+Test Promise rep
+-->
+<head>
+  <meta charset="utf-8">
+  <title>Rep test - Promise</title>
+  <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+  <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css">
+</head>
+<body>
+<pre id="test">
+<script src="head.js" type="application/javascript;version=1.8"></script>
+<script type="application/javascript;version=1.8">
+"use strict";
+
+window.onload = Task.async(function* () {
+  let { Rep } = browserRequire("devtools/client/shared/components/reps/rep");
+  let { PromiseRep } = browserRequire("devtools/client/shared/components/reps/promise");
+
+  const componentUnderTest = PromiseRep;
+
+  try {
+    yield testPending();
+    yield testFulfilledWithNumber();
+    yield testFulfilledWithString();
+    yield testFulfilledWithObject();
+    yield testFulfilledWithArray();
+  } catch (e) {
+    ok(false, "Got an error: " + DevToolsUtils.safeErrorString(e));
+  } finally {
+    SimpleTest.finish();
+  }
+
+  function testPending() {
+    // Test object = `new Promise((resolve, reject) => true)`
+    const stub = getGripStub("testPending");
+
+    // Test that correct rep is chosen.
+    const renderedRep = shallowRenderComponent(Rep, { object: stub });
+    is(renderedRep.type, PromiseRep.rep,
+      `Rep correctly selects ${PromiseRep.rep.displayName} for pending Promise`);
+
+    // Test rendering
+    const defaultOutput = `Promise { <state>: "pending" }`;
+
+    const modeTests = [
+      {
+        mode: undefined,
+        expectedOutput: defaultOutput,
+      },
+      {
+        mode: "tiny",
+        expectedOutput: `Promise { "pending" }`,
+      },
+      {
+        mode: "short",
+        expectedOutput: defaultOutput,
+      },
+      {
+        mode: "long",
+        expectedOutput: defaultOutput,
+      }
+    ];
+
+    testRepRenderModes(modeTests, "testPending", componentUnderTest, stub);
+  }
+  function testFulfilledWithNumber() {
+    // Test object = `Promise.resolve(42)`
+    const stub = getGripStub("testFulfilledWithNumber");
+
+    // Test that correct rep is chosen.
+    const renderedRep = shallowRenderComponent(Rep, { object: stub });
+    const {displayName} = PromiseRep.rep;
+    is(renderedRep.type, PromiseRep.rep,
+      `Rep correctly selects ${displayName} for Promise fulfilled with a number`);
+
+    // Test rendering
+    const defaultOutput = `Promise { <state>: "fulfilled", <value>: 42 }`;
+
+    const modeTests = [
+      {
+        mode: undefined,
+        expectedOutput: defaultOutput,
+      },
+      {
+        mode: "tiny",
+        expectedOutput: `Promise { "fulfilled" }`,
+      },
+      {
+        mode: "short",
+        expectedOutput: defaultOutput,
+      },
+      {
+        mode: "long",
+        expectedOutput: defaultOutput,
+      }
+    ];
+
+    testRepRenderModes(modeTests, "testFulfilledWithNumber", componentUnderTest, stub);
+  }
+  function testFulfilledWithString() {
+    // Test object = `Promise.resolve("foo")`
+    const stub = getGripStub("testFulfilledWithString");
+
+    // Test that correct rep is chosen.
+    const renderedRep = shallowRenderComponent(Rep, { object: stub });
+    const {displayName} = PromiseRep.rep;
+    is(renderedRep.type, PromiseRep.rep,
+      `Rep correctly selects ${displayName} for Promise fulfilled with a string`);
+
+    // Test rendering
+    const defaultOutput = `Promise { <state>: "fulfilled", <value>: "foo" }`;
+
+    const modeTests = [
+      {
+        mode: undefined,
+        expectedOutput: defaultOutput,
+      },
+      {
+        mode: "tiny",
+        expectedOutput: `Promise { "fulfilled" }`,
+      },
+      {
+        mode: "short",
+        expectedOutput: defaultOutput,
+      },
+      {
+        mode: "long",
+        expectedOutput: defaultOutput,
+      }
+    ];
+
+    testRepRenderModes(modeTests, "testFulfilledWithString", componentUnderTest, stub);
+  }
+
+  function testFulfilledWithObject() {
+    // Test object = `Promise.resolve({foo: "bar", baz: "boo"})`
+    const stub = getGripStub("testFulfilledWithObject");
+
+    // Test that correct rep is chosen.
+    const renderedRep = shallowRenderComponent(Rep, { object: stub });
+    const {displayName} = PromiseRep.rep;
+    is(renderedRep.type, PromiseRep.rep,
+      `Rep correctly selects ${displayName} for Promise fulfilled with an object`);
+
+    // Test rendering
+    const defaultOutput = `Promise { <state>: "fulfilled", <value>: Object }`;
+
+    const modeTests = [
+      {
+        mode: undefined,
+        expectedOutput: defaultOutput,
+      },
+      {
+        mode: "tiny",
+        expectedOutput: `Promise { "fulfilled" }`,
+      },
+      {
+        mode: "short",
+        expectedOutput: defaultOutput,
+      },
+      {
+        mode: "long",
+        expectedOutput: defaultOutput,
+      }
+    ];
+
+    testRepRenderModes(modeTests, "testFulfilledWithObject", componentUnderTest, stub);
+  }
+
+  function testFulfilledWithArray() {
+    // Test object = `Promise.resolve([1,2,3])`
+    const stub = getGripStub("testFulfilledWithArray");
+
+    // Test that correct rep is chosen.
+    const renderedRep = shallowRenderComponent(Rep, { object: stub });
+    const {displayName} = PromiseRep.rep;
+    is(renderedRep.type, PromiseRep.rep,
+      `Rep correctly selects ${displayName} for Promise fulfilled with an array`);
+
+    // Test rendering
+    const defaultOutput = `Promise { <state>: "fulfilled", <value>: [3] }`;
+
+    const modeTests = [
+      {
+        mode: undefined,
+        expectedOutput: defaultOutput,
+      },
+      {
+        mode: "tiny",
+        expectedOutput: `Promise { "fulfilled" }`,
+      },
+      {
+        mode: "short",
+        expectedOutput: defaultOutput,
+      },
+      {
+        mode: "long",
+        expectedOutput: defaultOutput,
+      }
+    ];
+
+    testRepRenderModes(modeTests, "testFulfilledWithArray", componentUnderTest, stub);
+  }
+
+  function getGripStub(name) {
+    switch (name) {
+      case "testPending":
+        return {
+          "type": "object",
+          "actor": "server1.conn1.child1/obj54",
+          "class": "Promise",
+          "promiseState": {
+            "state": "pending",
+            "creationTimestamp": 1477327760242.5752
+          },
+          "ownPropertyLength": 0,
+          "preview": {
+            "kind": "Object",
+            "ownProperties": {},
+            "ownPropertiesLength": 0,
+            "safeGetterValues": {}
+          }
+        };
+      case "testFulfilledWithNumber":
+        return {
+          "type": "object",
+          "actor": "server1.conn1.child1/obj55",
+          "class": "Promise",
+          "promiseState": {
+            "state": "fulfilled",
+            "value": 42,
+            "creationTimestamp": 1477327760242.721,
+            "timeToSettle": 0.018497000000479602
+          },
+          "ownPropertyLength": 0,
+          "preview": {
+            "kind": "Object",
+            "ownProperties": {},
+            "ownPropertiesLength": 0,
+            "safeGetterValues": {}
+          }
+        };
+      case "testFulfilledWithString":
+        return {
+          "type": "object",
+          "actor": "server1.conn1.child1/obj56",
+          "class": "Promise",
+          "promiseState": {
+            "state": "fulfilled",
+            "value": "foo",
+            "creationTimestamp": 1477327760243.2483,
+            "timeToSettle": 0.0019969999998465937
+          },
+          "ownPropertyLength": 0,
+          "preview": {
+            "kind": "Object",
+            "ownProperties": {},
+            "ownPropertiesLength": 0,
+            "safeGetterValues": {}
+          }
+        };
+      case "testFulfilledWithObject":
+        return {
+          "type": "object",
+          "actor": "server1.conn1.child1/obj59",
+          "class": "Promise",
+          "promiseState": {
+            "state": "fulfilled",
+            "value": {
+              "type": "object",
+              "actor": "server1.conn1.child1/obj60",
+              "class": "Object",
+              "extensible": true,
+              "frozen": false,
+              "sealed": false,
+              "ownPropertyLength": 2
+            },
+            "creationTimestamp": 1477327760243.2214,
+            "timeToSettle": 0.002035999999861815
+          },
+          "ownPropertyLength": 0,
+          "preview": {
+            "kind": "Object",
+            "ownProperties": {},
+            "ownPropertiesLength": 0,
+            "safeGetterValues": {}
+          }
+        };
+      case "testFulfilledWithArray":
+        return {
+          "type": "object",
+          "actor": "server1.conn1.child1/obj57",
+          "class": "Promise",
+          "promiseState": {
+            "state": "fulfilled",
+            "value": {
+              "type": "object",
+              "actor": "server1.conn1.child1/obj58",
+              "class": "Array",
+              "extensible": true,
+              "frozen": false,
+              "sealed": false,
+              "ownPropertyLength": 4,
+              "preview": {
+                "kind": "ArrayLike",
+                "length": 3
+              }
+            },
+            "creationTimestamp": 1477327760242.9597,
+            "timeToSettle": 0.006158000000141328
+          },
+          "ownPropertyLength": 0,
+          "preview": {
+            "kind": "Object",
+            "ownProperties": {},
+            "ownPropertiesLength": 0,
+            "safeGetterValues": {}
+          }
+        };
+    }
+    return null;
+  }
+});
+</script>
+</pre>
+</body>
+</html>
--- a/dom/base/nsContentUtils.cpp
+++ b/dom/base/nsContentUtils.cpp
@@ -9336,16 +9336,41 @@ nsContentUtils::SetScrollbarsVisibility(
   }
 }
 
 /* static */ void
 nsContentUtils::GetPresentationURL(nsIDocShell* aDocShell, nsAString& aPresentationUrl)
 {
   MOZ_ASSERT(aDocShell);
 
+  // Simulate receiver context for web platform test
+  if (Preferences::GetBool("dom.presentation.testing.simulate-receiver")) {
+    nsCOMPtr<nsIDocument> doc;
+
+    nsCOMPtr<nsPIDOMWindowOuter> docShellWin =
+      do_QueryInterface(aDocShell->GetScriptGlobalObject());
+    if (docShellWin) {
+      doc = docShellWin->GetExtantDoc();
+    }
+
+    if (NS_WARN_IF(!doc)) {
+      return;
+    }
+
+    nsCOMPtr<nsIURI> uri = doc->GetDocumentURI();
+    if (NS_WARN_IF(!uri)) {
+      return;
+    }
+
+    nsAutoCString uriStr;
+    uri->GetSpec(uriStr);
+    aPresentationUrl = NS_ConvertUTF8toUTF16(uriStr);
+    return;
+  }
+
   if (XRE_IsContentProcess()) {
     nsCOMPtr<nsIDocShellTreeItem> sameTypeRoot;
     aDocShell->GetSameTypeRootTreeItem(getter_AddRefs(sameTypeRoot));
     nsCOMPtr<nsIDocShellTreeItem> root;
     aDocShell->GetRootTreeItem(getter_AddRefs(root));
     if (sameTypeRoot.get() == root.get()) {
       // presentation URL is stored in TabChild for the top most
       // <iframe mozbrowser> in content process.
--- a/dom/base/nsINode.h
+++ b/dom/base/nsINode.h
@@ -756,19 +756,18 @@ public:
    * @param aPropertyName  name of property to get.
    * @param aStatus        out parameter for storing resulting status.
    *                       Set to NS_PROPTABLE_PROP_NOT_THERE if the property
    *                       is not set.
    * @return               the property. Null if the property is not set
    *                       (though a null return value does not imply the
    *                       property was not set, i.e. it can be set to null).
    */
-  virtual void* GetProperty(uint16_t aCategory,
-                            nsIAtom *aPropertyName,
-                            nsresult *aStatus = nullptr) const;
+  void* GetProperty(uint16_t aCategory, nsIAtom *aPropertyName,
+                    nsresult *aStatus = nullptr) const;
 
   /**
    * Set a property to be associated with this node. This will overwrite an
    * existing value if one exists. The existing value is destroyed using the
    * destructor function given when that value was set.
    *
    * @param aPropertyName  name of property to set.
    * @param aValue         new value of property.
@@ -777,18 +776,17 @@ public:
    * @param aTransfer      if true the property will not be deleted when the
    *                       ownerDocument of the node changes, if false it
    *                       will be deleted.
    *
    * @return NS_PROPTABLE_PROP_OVERWRITTEN (success value) if the property
    *                                       was already set
    * @throws NS_ERROR_OUT_OF_MEMORY if that occurs
    */
-  nsresult SetProperty(nsIAtom *aPropertyName,
-                       void *aValue,
+  nsresult SetProperty(nsIAtom *aPropertyName, void *aValue,
                        NSPropertyDtorFunc aDtor = nullptr,
                        bool aTransfer = false)
   {
     return SetProperty(0, aPropertyName, aValue, aDtor, aTransfer);
   }
 
   /**
    * Set a property to be associated with this node. This will overwrite an
@@ -804,22 +802,21 @@ public:
    *                        ownerDocument of the node changes, if false it
    *                        will be deleted.
    * @param aOldValue [out] previous value of property.
    *
    * @return NS_PROPTABLE_PROP_OVERWRITTEN (success value) if the property
    *                                       was already set
    * @throws NS_ERROR_OUT_OF_MEMORY if that occurs
    */
-  virtual nsresult SetProperty(uint16_t aCategory,
-                               nsIAtom *aPropertyName,
-                               void *aValue,
-                               NSPropertyDtorFunc aDtor = nullptr,
-                               bool aTransfer = false,
-                               void **aOldValue = nullptr);
+  nsresult SetProperty(uint16_t aCategory,
+                       nsIAtom *aPropertyName, void *aValue,
+                       NSPropertyDtorFunc aDtor = nullptr,
+                       bool aTransfer = false,
+                       void **aOldValue = nullptr);
 
   /**
    * A generic destructor for property values allocated with new.
    */
   template<class T>
   static void DeleteProperty(void *, nsIAtom *, void *aPropertyValue, void *)
   {
     delete static_cast<T *>(aPropertyValue);
@@ -838,17 +835,17 @@ public:
 
   /**
    * Destroys a property associated with this node. The value is destroyed
    * using the destruction function given when that value was set.
    *
    * @param aCategory      category of property to destroy.
    * @param aPropertyName  name of property to destroy.
    */
-  virtual void DeleteProperty(uint16_t aCategory, nsIAtom *aPropertyName);
+  void DeleteProperty(uint16_t aCategory, nsIAtom *aPropertyName);
 
   /**
    * Unset a property associated with this node. The value will not be
    * destroyed but rather returned. It is the caller's responsibility to
    * destroy the value after that point.
    *
    * @param aPropertyName  name of property to unset.
    * @param aStatus        out parameter for storing resulting status.
@@ -873,19 +870,18 @@ public:
    * @param aPropertyName  name of property to unset.
    * @param aStatus        out parameter for storing resulting status.
    *                       Set to NS_PROPTABLE_PROP_NOT_THERE if the property
    *                       is not set.
    * @return               the property. Null if the property is not set
    *                       (though a null return value does not imply the
    *                       property was not set, i.e. it can be set to null).
    */
-  virtual void* UnsetProperty(uint16_t aCategory,
-                              nsIAtom *aPropertyName,
-                              nsresult *aStatus = nullptr);
+  void* UnsetProperty(uint16_t aCategory, nsIAtom *aPropertyName,
+                      nsresult *aStatus = nullptr);
 
   bool HasProperties() const
   {
     return HasFlag(NODE_HAS_PROPERTIES);
   }
 
   /**
    * Return the principal of this node.  This is guaranteed to never be a null
--- a/dom/html/HTMLMediaElement.cpp
+++ b/dom/html/HTMLMediaElement.cpp
@@ -4702,17 +4702,19 @@ HTMLMediaElement::UpdateReadyStateIntern
     }
     if (hasVideoTracks) {
       mediaInfo.EnableVideo();
     }
     MetadataLoaded(&mediaInfo, nsAutoPtr<const MetadataTags>(nullptr));
   }
 
   enum NextFrameStatus nextFrameStatus = NextFrameStatus();
-  if (nextFrameStatus == NEXT_FRAME_UNAVAILABLE) {
+  if (nextFrameStatus == NEXT_FRAME_UNAVAILABLE ||
+      (nextFrameStatus == NEXT_FRAME_UNAVAILABLE_BUFFERING &&
+       mWaitingForKey == WAITING_FOR_KEY)) {
     if (mWaitingForKey != NOT_WAITING_FOR_KEY) {
       // http://w3c.github.io/encrypted-media/#wait-for-key
       // Continuing 7.3.4 Queue a "waitingforkey" Event
       // 4. Queue a task to fire a simple event named waitingforkey
       // at the media element.
       if (mWaitingForKey == WAITING_FOR_KEY) {
         mWaitingForKey = WAITING_FOR_KEY_DISPATCHED;
         DispatchAsyncEvent(NS_LITERAL_STRING("waitingforkey"));
@@ -4791,17 +4793,17 @@ HTMLMediaElement::UpdateReadyStateIntern
   // Now see if we should set HAVE_ENOUGH_DATA.
   // If it's something we don't know the size of, then we can't
   // make a real estimate, so we go straight to HAVE_ENOUGH_DATA once
   // we've downloaded enough data that our download rate is considered
   // reliable. We have to move to HAVE_ENOUGH_DATA at some point or
   // autoplay elements for live streams will never play. Otherwise we
   // move to HAVE_ENOUGH_DATA if we can play through the entire media
   // without stopping to buffer.
-  if (mDecoder->CanPlayThrough()) {
+  if (mWaitingForKey == NOT_WAITING_FOR_KEY && mDecoder->CanPlayThrough()) {
     LOG(LogLevel::Debug, ("MediaElement %p UpdateReadyStateInternal() "
                           "Decoder can play through", this));
     ChangeReadyState(nsIDOMHTMLMediaElement::HAVE_ENOUGH_DATA);
     return;
   }
   LOG(LogLevel::Debug, ("MediaElement %p UpdateReadyStateInternal() "
                         "Default; Decoder has future data", this));
   ChangeReadyState(nsIDOMHTMLMediaElement::HAVE_FUTURE_DATA);
--- a/dom/media/ADTSDemuxer.h
+++ b/dom/media/ADTSDemuxer.h
@@ -27,17 +27,16 @@ public:
   // MediaDataDemuxer interface.
   explicit ADTSDemuxer(MediaResource* aSource);
   RefPtr<InitPromise> Init() override;
   bool HasTrackType(TrackInfo::TrackType aType) const override;
   uint32_t GetNumberTracks(TrackInfo::TrackType aType) const override;
   already_AddRefed<MediaTrackDemuxer> GetTrackDemuxer(
     TrackInfo::TrackType aType, uint32_t aTrackNumber) override;
   bool IsSeekable() const override;
-  bool ShouldComputeStartTime() const override { return false; }
 
 private:
   bool InitInternal();
 
   RefPtr<MediaResource> mSource;
   RefPtr<ADTSTrackDemuxer> mTrackDemuxer;
 };
 
--- a/dom/media/MediaDecoderStateMachine.cpp
+++ b/dom/media/MediaDecoderStateMachine.cpp
@@ -218,29 +218,41 @@ public:
   virtual void HandleVideoSuspendTimeout() = 0;
 
   virtual void HandleResumeVideoDecoding();
 
   virtual void HandlePlayStateChanged(MediaDecoder::PlayState aPlayState) {}
 
   virtual void DumpDebugInfo() {}
 
+private:
+  template <class S, typename R, typename... As>
+  auto ReturnTypeHelper(R(S::*)(As...)) -> R;
+
 protected:
   using Master = MediaDecoderStateMachine;
   explicit StateObject(Master* aPtr) : mMaster(aPtr) {}
   TaskQueue* OwnerThread() const { return mMaster->mTaskQueue; }
   MediaResource* Resource() const { return mMaster->mResource; }
   MediaDecoderReaderWrapper* Reader() const { return mMaster->mReader; }
   const MediaInfo& Info() const { return mMaster->Info(); }
+  bool IsExpectingMoreData() const
+  {
+    // We are expecting more data if either the resource states so, or if we
+    // have a waiting promise pending (such as with non-MSE EME).
+    return Resource()->IsExpectingMoreData() ||
+           (Reader()->IsWaitForDataSupported() &&
+            (Reader()->IsWaitingAudioData() || Reader()->IsWaitingVideoData()));
+  }
 
   // Note this function will delete the current state object.
   // Don't access members to avoid UAF after this call.
   template <class S, typename... Ts>
   auto SetState(Ts... aArgs)
-    -> decltype(DeclVal<S>().Enter(Move(aArgs)...))
+    -> decltype(ReturnTypeHelper(&S::Enter))
   {
     // keep mMaster in a local object because mMaster will become invalid after
     // the current state object is deleted.
     auto master = mMaster;
 
     auto s = new S(master);
 
     MOZ_ASSERT(master->mState != s->GetState() ||
@@ -1464,24 +1476,21 @@ DecodingState::MaybeStartBuffering()
   }
 
   // Don't enter buffering while prerolling so that the decoder has a chance to
   // enqueue some decoded data before we give up and start buffering.
   if (!mMaster->IsPlaying()) {
     return;
   }
 
-  // No more data to download. No need to enter buffering.
-  if (!Resource()->IsExpectingMoreData()) {
-    return;
-  }
-
   bool shouldBuffer;
   if (Reader()->UseBufferingHeuristics()) {
-    shouldBuffer = mMaster->HasLowDecodedData() && mMaster->HasLowBufferedData();
+    shouldBuffer = IsExpectingMoreData() &&
+                   mMaster->HasLowDecodedData() &&
+                   mMaster->HasLowBufferedData();
   } else {
     MOZ_ASSERT(Reader()->IsWaitForDataSupported());
     shouldBuffer =
       (mMaster->OutOfDecodedAudio() && Reader()->IsWaitingAudioData()) ||
       (mMaster->OutOfDecodedVideo() && Reader()->IsWaitingVideoData());
   }
   if (shouldBuffer) {
     SetState<BufferingState>();
@@ -1609,17 +1618,17 @@ BufferingState::Step()
   // we've not decoded enough data to begin playback, or if we've not
   // downloaded a reasonable amount of data inside our buffering time.
   if (Reader()->UseBufferingHeuristics()) {
     TimeDuration elapsed = now - mBufferingStart;
     bool isLiveStream = Resource()->IsLiveStream();
     if ((isLiveStream || !mMaster->CanPlayThrough()) &&
         elapsed < TimeDuration::FromSeconds(mBufferingWait * mMaster->mPlaybackRate) &&
         mMaster->HasLowBufferedData(mBufferingWait * USECS_PER_S) &&
-        Resource()->IsExpectingMoreData()) {
+        IsExpectingMoreData()) {
       SLOG("Buffering: wait %ds, timeout in %.3lfs",
            mBufferingWait, mBufferingWait - elapsed.ToSeconds());
       mMaster->ScheduleStateMachineIn(USECS_PER_S);
       return;
     }
   } else if (mMaster->OutOfDecodedAudio() || mMaster->OutOfDecodedVideo()) {
     MOZ_ASSERT(Reader()->IsWaitForDataSupported(),
                "Don't yet have a strategy for non-heuristic + non-WaitForData");
--- a/dom/media/MediaFormatReader.cpp
+++ b/dom/media/MediaFormatReader.cpp
@@ -735,16 +735,31 @@ MediaFormatReader::NotifyWaitingForData(
   decoder.mWaitingForData = true;
   if (decoder.mTimeThreshold) {
     decoder.mTimeThreshold.ref().mWaiting = true;
   }
   ScheduleUpdate(aTrack);
 }
 
 void
+MediaFormatReader::NotifyWaitingForKey(TrackType aTrack)
+{
+  MOZ_ASSERT(OnTaskQueue());
+  auto& decoder = GetDecoderData(aTrack);
+  if (mDecoder) {
+    mDecoder->NotifyWaitingForKey();
+  }
+  if (!decoder.mDecodePending) {
+    LOGV("WaitingForKey received while no pending decode. Ignoring");
+  }
+  decoder.mWaitingForKey = true;
+  ScheduleUpdate(aTrack);
+}
+
+void
 MediaFormatReader::NotifyEndOfStream(TrackType aTrack)
 {
   MOZ_ASSERT(OnTaskQueue());
   auto& decoder = GetDecoderData(aTrack);
   decoder.mDemuxEOS = true;
   ScheduleUpdate(aTrack);
 }
 
@@ -861,17 +876,17 @@ MediaFormatReader::UpdateReceivedNewData
     ScheduleSeek();
     return true;
   }
   if (decoder.HasInternalSeekPending() || decoder.HasWaitingPromise()) {
     if (decoder.HasInternalSeekPending()) {
       LOG("Attempting Internal Seek");
       InternalSeek(aTrack, decoder.mTimeThreshold.ref());
     }
-    if (decoder.HasWaitingPromise()) {
+    if (decoder.HasWaitingPromise() && !decoder.IsWaiting()) {
       MOZ_ASSERT(!decoder.HasPromise());
       LOG("We have new data. Resolving WaitingPromise");
       decoder.mWaitingPromise.Resolve(decoder.mType, __func__);
     }
     return true;
   }
   return false;
 }
@@ -1234,16 +1249,20 @@ MediaFormatReader::Update(TrackType aTra
     } else if (decoder.mDemuxEOS && !decoder.mNeedDraining &&
                !decoder.HasPendingDrain() && decoder.mQueuedSamples.IsEmpty()) {
       // It is possible to transition from WAITING_FOR_DATA directly to EOS
       // state during the internal seek; in which case no draining would occur.
       // There is no more samples left to be decoded and we are already in
       // EOS state. We can immediately reject the data promise.
       LOG("Rejecting %s promise: EOS", TrackTypeToStr(aTrack));
       decoder.RejectPromise(NS_ERROR_DOM_MEDIA_END_OF_STREAM, __func__);
+    } else if (decoder.mWaitingForKey) {
+      LOG("Rejecting %s promise: WAITING_FOR_DATA due to waiting for key",
+          TrackTypeToStr(aTrack));
+      decoder.RejectPromise(NS_ERROR_DOM_MEDIA_WAITING_FOR_DATA, __func__);
     }
   }
 
   if (decoder.mNeedDraining) {
     DrainDecoder(aTrack);
     return;
   }
 
@@ -1263,29 +1282,40 @@ MediaFormatReader::Update(TrackType aTra
       return;
     } else if (aTrack == TrackType::kAudioTrack) {
       decoder.Flush();
     }
   }
 
   bool needInput = NeedInput(decoder);
 
-  LOGV("Update(%s) ni=%d no=%d ie=%d, in:%llu out:%llu qs=%u pending:%u waiting:%d promise:%d sid:%u",
+  LOGV("Update(%s) ni=%d no=%d ie=%d, in:%llu out:%llu qs=%u pending:%u waiting:%d promise:%d wfk:%d sid:%u",
        TrackTypeToStr(aTrack), needInput, needOutput, decoder.mDecodePending,
        decoder.mNumSamplesInput, decoder.mNumSamplesOutput,
        uint32_t(size_t(decoder.mSizeOfQueue)), uint32_t(decoder.mOutput.Length()),
-       decoder.mWaitingForData, decoder.HasPromise(), decoder.mLastStreamSourceID);
+       decoder.mWaitingForData, decoder.HasPromise(),
+       decoder.mWaitingForKey, decoder.mLastStreamSourceID);
 
-  if (decoder.mWaitingForData &&
-      (!decoder.mTimeThreshold || decoder.mTimeThreshold.ref().mWaiting)) {
+  if ((decoder.mWaitingForData &&
+       (!decoder.mTimeThreshold || decoder.mTimeThreshold.ref().mWaiting)) ||
+      (decoder.mWaitingForKey && decoder.mDecodePending)) {
     // Nothing more we can do at present.
-    LOGV("Still waiting for data.");
+    LOGV("Still waiting for data or key.");
     return;
   }
 
+  if (decoder.mWaitingForKey) {
+    decoder.mWaitingForKey = false;
+    if (decoder.HasWaitingPromise() && !decoder.IsWaiting()) {
+      LOGV("No longer waiting for key. Resolving waiting promise");
+      decoder.mWaitingPromise.Resolve(decoder.mType, __func__);
+      return;
+    }
+  }
+
   if (!needInput) {
     LOGV("No need for additional input (pending:%u)",
          uint32_t(decoder.mOutput.Length()));
     return;
   }
 
   // Demux samples if we don't have some.
   RequestDemuxSamples(aTrack);
@@ -1351,18 +1381,18 @@ MediaFormatReader::SizeOfQueue(TrackType
 
 RefPtr<MediaDecoderReader::WaitForDataPromise>
 MediaFormatReader::WaitForData(MediaData::Type aType)
 {
   MOZ_ASSERT(OnTaskQueue());
   TrackType trackType = aType == MediaData::VIDEO_DATA ?
     TrackType::kVideoTrack : TrackType::kAudioTrack;
   auto& decoder = GetDecoderData(trackType);
-  if (!decoder.mWaitingForData) {
-    // We aren't waiting for data any longer.
+  if (!decoder.IsWaiting()) {
+    // We aren't waiting for anything.
     return WaitForDataPromise::CreateAndResolve(decoder.mType, __func__);
   }
   RefPtr<WaitForDataPromise> p = decoder.mWaitingPromise.Ensure(__func__);
   ScheduleUpdate(trackType);
   return p;
 }
 
 nsresult
@@ -1451,16 +1481,25 @@ MediaFormatReader::Error(TrackType aTrac
 {
   RefPtr<nsIRunnable> task =
     NewRunnableMethod<TrackType, MediaResult>(
       this, &MediaFormatReader::NotifyError, aTrack, aError);
   OwnerThread()->Dispatch(task.forget());
 }
 
 void
+MediaFormatReader::WaitingForKey(TrackType aTrack)
+{
+  RefPtr<nsIRunnable> task =
+    NewRunnableMethod<TrackType>(
+      this, &MediaFormatReader::NotifyWaitingForKey, aTrack);
+  OwnerThread()->Dispatch(task.forget());
+}
+
+void
 MediaFormatReader::Reset(TrackType aTrack)
 {
   MOZ_ASSERT(OnTaskQueue());
   LOG("Reset(%s) BEGIN", TrackTypeToStr(aTrack));
 
   auto& decoder = GetDecoderData(aTrack);
 
   decoder.ResetState();
@@ -1486,24 +1525,16 @@ MediaFormatReader::DropDecodedSamples(Tr
   decoder.mOutput.Clear();
   decoder.mSizeOfQueue -= lengthDecodedQueue;
   if (aTrack == TrackInfo::kVideoTrack && mDecoder) {
     mDecoder->NotifyDecodedFrames({ 0, 0, lengthDecodedQueue });
   }
 }
 
 void
-MediaFormatReader::WaitingForKey(TrackType aTrack)
-{
-  if (mDecoder) {
-    mDecoder->NotifyWaitingForKey();
-  }
-}
-
-void
 MediaFormatReader::SkipVideoDemuxToNextKeyFrame(media::TimeUnit aTimeThreshold)
 {
   MOZ_ASSERT(OnTaskQueue());
   LOG("Skipping up to %lld", aTimeThreshold.ToMicroseconds());
 
   // We've reached SkipVideoDemuxToNextKeyFrame when our decoding is late.
   // As such we can drop all already decoded samples and discard all pending
   // samples.
@@ -1983,54 +2014,56 @@ MediaFormatReader::GetMozDebugReaderData
     MonitorAutoLock mon(mVideo.mMonitor);
     videoName = mVideo.mDescription;
   }
 
   result += nsPrintfCString("audio decoder: %s\n", audioName);
   result += nsPrintfCString("audio frames decoded: %lld\n",
                             mAudio.mNumSamplesOutputTotal);
   if (HasAudio()) {
-    result += nsPrintfCString("audio state: ni=%d no=%d ie=%d demuxr:%d demuxq:%d tt:%f tths:%d in:%llu out:%llu qs=%u pending:%u waiting:%d sid:%u\n",
+    result += nsPrintfCString("audio state: ni=%d no=%d ie=%d demuxr:%d demuxq:%d tt:%f tths:%d in:%llu out:%llu qs=%u pending:%u waiting:%d wfk:%d sid:%u\n",
                               NeedInput(mAudio), mAudio.HasPromise(),
                               mAudio.mDecodePending,
                               mAudio.mDemuxRequest.Exists(),
                               int(mAudio.mQueuedSamples.Length()),
                               mAudio.mTimeThreshold
                               ? mAudio.mTimeThreshold.ref().Time().ToSeconds()
                               : -1.0,
                               mAudio.mTimeThreshold
                               ? mAudio.mTimeThreshold.ref().mHasSeeked
                               : -1,
                               mAudio.mNumSamplesInput, mAudio.mNumSamplesOutput,
                               unsigned(size_t(mAudio.mSizeOfQueue)),
                               unsigned(mAudio.mOutput.Length()),
-                              mAudio.mWaitingForData, mAudio.mLastStreamSourceID);
+                              mAudio.mWaitingForData, mAudio.mWaitingForKey,
+                              mAudio.mLastStreamSourceID);
   }
   result += nsPrintfCString("video decoder: %s\n", videoName);
   result += nsPrintfCString("hardware video decoding: %s\n",
                             VideoIsHardwareAccelerated() ? "enabled" : "disabled");
   result += nsPrintfCString("video frames decoded: %lld (skipped:%lld)\n",
                             mVideo.mNumSamplesOutputTotal,
                             mVideo.mNumSamplesSkippedTotal);
   if (HasVideo()) {
-    result += nsPrintfCString("video state: ni=%d no=%d ie=%d demuxr:%d demuxq:%d tt:%f tths:%d in:%llu out:%llu qs=%u pending:%u waiting:%d sid:%u\n",
+    result += nsPrintfCString("video state: ni=%d no=%d ie=%d demuxr:%d demuxq:%d tt:%f tths:%d in:%llu out:%llu qs=%u pending:%u waiting:%d wfk:%d, sid:%u\n",
                               NeedInput(mVideo), mVideo.HasPromise(),
                               mVideo.mDecodePending,
                               mVideo.mDemuxRequest.Exists(),
                               int(mVideo.mQueuedSamples.Length()),
                               mVideo.mTimeThreshold
                               ? mVideo.mTimeThreshold.ref().Time().ToSeconds()
                               : -1.0,
                               mVideo.mTimeThreshold
                               ? mVideo.mTimeThreshold.ref().mHasSeeked
                               : -1,
                               mVideo.mNumSamplesInput, mVideo.mNumSamplesOutput,
                               unsigned(size_t(mVideo.mSizeOfQueue)),
                               unsigned(mVideo.mOutput.Length()),
-                              mVideo.mWaitingForData, mVideo.mLastStreamSourceID);
+                              mVideo.mWaitingForData, mVideo.mWaitingForKey,
+                              mVideo.mLastStreamSourceID);
   }
   aString += NS_ConvertUTF8toUTF16(result);
 }
 
 void
 MediaFormatReader::SetVideoBlankDecode(bool aIsBlankDecode)
 {
   MOZ_ASSERT(OnTaskQueue());
--- a/dom/media/MediaFormatReader.h
+++ b/dom/media/MediaFormatReader.h
@@ -160,16 +160,17 @@ private:
 
   // Drain the current decoder.
   void DrainDecoder(TrackType aTrack);
   void NotifyNewOutput(TrackType aTrack, MediaData* aSample);
   void NotifyInputExhausted(TrackType aTrack);
   void NotifyDrainComplete(TrackType aTrack);
   void NotifyError(TrackType aTrack, const MediaResult& aError);
   void NotifyWaitingForData(TrackType aTrack);
+  void NotifyWaitingForKey(TrackType aTrack);
   void NotifyEndOfStream(TrackType aTrack);
 
   void ExtractCryptoInitData(nsTArray<uint8_t>& aInitData);
 
   // Initializes mLayersBackendType if possible.
   void InitLayersBackendType();
 
   // DecoderCallback proxies the MediaDataDecoderCallback calls to these
@@ -230,16 +231,17 @@ private:
                 uint32_t aNumOfMaxError)
       : mOwner(aOwner)
       , mType(aType)
       , mMonitor("DecoderData")
       , mDescription("shutdown")
       , mUpdateScheduled(false)
       , mDemuxEOS(false)
       , mWaitingForData(false)
+      , mWaitingForKey(false)
       , mReceivedNewData(false)
       , mDecoderInitialized(false)
       , mOutputRequested(false)
       , mDecodePending(false)
       , mNeedDraining(false)
       , mDraining(false)
       , mDrainComplete(false)
       , mNumOfConsecutiveError(0)
@@ -279,43 +281,50 @@ private:
       mDescription = "shutdown";
       mDecoder = nullptr;
     }
 
     // Only accessed from reader's task queue.
     bool mUpdateScheduled;
     bool mDemuxEOS;
     bool mWaitingForData;
+    bool mWaitingForKey;
     bool mReceivedNewData;
 
     // Pending seek.
     MozPromiseRequestHolder<MediaTrackDemuxer::SeekPromise> mSeekRequest;
 
     // Queued demux samples waiting to be decoded.
     nsTArray<RefPtr<MediaRawData>> mQueuedSamples;
     MozPromiseRequestHolder<MediaTrackDemuxer::SamplesPromise> mDemuxRequest;
+    // A WaitingPromise is pending if the demuxer is waiting for data or
+    // if the decoder is waiting for a key.
     MozPromiseHolder<WaitForDataPromise> mWaitingPromise;
-    bool HasWaitingPromise()
+    bool HasWaitingPromise() const
     {
       MOZ_ASSERT(mOwner->OnTaskQueue());
       return !mWaitingPromise.IsEmpty();
     }
+    bool IsWaiting() const
+    {
+      MOZ_ASSERT(mOwner->OnTaskQueue());
+      return mWaitingForData || mWaitingForKey;
+    }
 
     // MediaDataDecoder handler's variables.
     // Decoder initialization promise holder.
     MozPromiseRequestHolder<MediaDataDecoder::InitPromise> mInitPromise;
     // False when decoder is created. True when decoder Init() promise is resolved.
     bool mDecoderInitialized;
     bool mOutputRequested;
     // Set to true once the MediaDataDecoder has been fed a compressed sample.
-    // No more sample will be passed to the decoder while true.
+    // No more samples will be passed to the decoder while true.
     // mDecodePending is reset when:
-    // 1- The decoder returns a sample
-    // 2- The decoder calls InputExhausted
-    // 3- The decoder is Flushed or Reset.
+    // 1- The decoder calls InputExhausted
+    // 2- The decoder is Flushed or Reset.
     bool mDecodePending;
     bool mNeedDraining;
     bool mDraining;
     bool mDrainComplete;
 
     bool HasPendingDrain() const
     {
       return mDraining || mDrainComplete;
@@ -387,16 +396,17 @@ private:
     // (pending demuxed and decoded).
     // Decoding will be suspended until mInputRequested is set again.
     // The track demuxer is *not* reset.
     void ResetState()
     {
       MOZ_ASSERT(mOwner->OnTaskQueue());
       mDemuxEOS = false;
       mWaitingForData = false;
+      mWaitingForKey = false;
       mQueuedSamples.Clear();
       mOutputRequested = false;
       mNeedDraining = false;
       mDecodePending = false;
       mDraining = false;
       mDrainComplete = false;
       mTimeThreshold.reset();
       mLastSampleTime.reset();
--- a/dom/media/MediaPrefs.h
+++ b/dom/media/MediaPrefs.h
@@ -118,17 +118,17 @@ private:
   DECL_MEDIA_PREF("media.libavcodec.allow-obsolete",          LibavcodecAllowObsolete, bool, false);
 #endif
 #ifdef MOZ_FFVPX
   DECL_MEDIA_PREF("media.ffvpx.enabled",                      PDMFFVPXEnabled, bool, true);
 #endif
 #ifdef XP_WIN
   DECL_MEDIA_PREF("media.wmf.enabled",                        PDMWMFEnabled, bool, true);
   DECL_MEDIA_PREF("media.decoder-doctor.wmf-disabled-is-failure", DecoderDoctorWMFDisabledIsFailure, bool, false);
-  DECL_MEDIA_PREF("media.webm.intel_decoder.enabled",         PDMWMFIntelDecoderEnabled, bool, false);
+  DECL_MEDIA_PREF("media.wmf.vp9.enabled",                    PDMWMFVP9DecoderEnabled, bool, true);
   DECL_MEDIA_PREF("media.wmf.low-latency.enabled",            PDMWMFLowLatencyEnabled, bool, false);
   DECL_MEDIA_PREF("media.wmf.decoder.thread-count",           PDMWMFThreadCount, int32_t, -1);
   DECL_MEDIA_PREF("media.wmf.skip-blacklist",                 PDMWMFSkipBlacklist, bool, false);
   DECL_MEDIA_PREF("media.windows-media-foundation.max-dxva-videos", PDMWMFMaxDXVAVideos, uint32_t, 8);
   DECL_MEDIA_PREF("media.windows-media-foundation.allow-d3d11-dxva", PDMWMFAllowD3D11, bool, true);
 #endif
   DECL_MEDIA_PREF("media.decoder.fuzzing.enabled",            PDMFuzzingEnabled, bool, false);
   DECL_MEDIA_PREF("media.decoder.fuzzing.video-output-minimum-interval-ms", PDMFuzzingInterval, uint32_t, 0);
--- a/dom/media/ipc/VideoDecoderParent.cpp
+++ b/dom/media/ipc/VideoDecoderParent.cpp
@@ -71,29 +71,29 @@ VideoDecoderParent::Destroy()
   mIPDLSelfRef = nullptr;
 }
 
 bool
 VideoDecoderParent::RecvInit(const VideoInfo& aInfo, const layers::TextureFactoryIdentifier& aIdentifier)
 {
   mKnowsCompositor->IdentifyTextureHost(aIdentifier);
 
-  CreateDecoderParams params(aInfo);
-  params.mTaskQueue = mDecodeTaskQueue;
-  params.mCallback = this;
-  params.mKnowsCompositor = mKnowsCompositor;
-  params.mImageContainer = new layers::ImageContainer();
-
 #ifdef XP_WIN
   // TODO: Ideally we wouldn't hardcode the WMF PDM, and we'd use the normal PDM
   // factory logic for picking a decoder.
   WMFDecoderModule::Init();
   RefPtr<WMFDecoderModule> pdm(new WMFDecoderModule());
   pdm->Startup();
 
+  CreateDecoderParams params(aInfo);
+  params.mTaskQueue = mDecodeTaskQueue;
+  params.mCallback = this;
+  params.mKnowsCompositor = mKnowsCompositor;
+  params.mImageContainer = new layers::ImageContainer();
+
   mDecoder = pdm->CreateVideoDecoder(params);
   if (!mDecoder) {
     Unused << SendInitFailed(NS_ERROR_DOM_MEDIA_FATAL_ERR);
     return true;
   }
 #else
   MOZ_ASSERT(false, "Can't use RemoteVideoDecoder on non-Windows platforms yet");
 #endif
--- a/dom/media/platforms/agnostic/gmp/MediaDataDecoderProxy.cpp
+++ b/dom/media/platforms/agnostic/gmp/MediaDataDecoderProxy.cpp
@@ -19,18 +19,16 @@ void
 MediaDataDecoderCallbackProxy::FlushComplete()
 {
   mProxyDecoder->FlushComplete();
 }
 
 RefPtr<MediaDataDecoder::InitPromise>
 MediaDataDecoderProxy::InternalInit()
 {
-  MOZ_ASSERT(!mIsShutdown);
-
   return mProxyDecoder->Init();
 }
 
 RefPtr<MediaDataDecoder::InitPromise>
 MediaDataDecoderProxy::Init()
 {
   MOZ_ASSERT(!mIsShutdown);
 
--- a/dom/media/platforms/wmf/WMFAudioMFTManager.h
+++ b/dom/media/platforms/wmf/WMFAudioMFTManager.h
@@ -7,18 +7,17 @@
 #if !defined(WMFAudioOutputSource_h_)
 #define WMFAudioOutputSource_h_
 
 #include "WMF.h"
 #include "MFTDecoder.h"
 #include "mozilla/RefPtr.h"
 #include "WMFMediaDataDecoder.h"
 
-extern const GUID CLSID_WebmMfVp8Dec;
-extern const GUID CLSID_WebmMfVp9Dec;
+extern const GUID CLSID_WebmMfVpxDec;
 
 namespace mozilla {
 
 class WMFAudioMFTManager : public MFTManager {
 public:
   WMFAudioMFTManager(const AudioInfo& aConfig);
   ~WMFAudioMFTManager();
 
--- a/dom/media/platforms/wmf/WMFDecoderModule.cpp
+++ b/dom/media/platforms/wmf/WMFDecoderModule.cpp
@@ -227,23 +227,20 @@ WMFDecoderModule::Supports(const TrackIn
       }
     }
     return true;
   }
   if (aTrackInfo.mMimeType.EqualsLiteral("audio/mpeg") &&
       CanCreateWMFDecoder<CLSID_CMP3DecMediaObject>()) {
     return true;
   }
-  if (MediaPrefs::PDMWMFIntelDecoderEnabled() && sDXVAEnabled) {
-    if (VPXDecoder::IsVP8(aTrackInfo.mMimeType) &&
-        CanCreateWMFDecoder<CLSID_WebmMfVp8Dec>()) {
-      return true;
-    }
-    if (VPXDecoder::IsVP9(aTrackInfo.mMimeType) &&
-        CanCreateWMFDecoder<CLSID_WebmMfVp9Dec>()) {
+  if (MediaPrefs::PDMWMFVP9DecoderEnabled() && sDXVAEnabled) {
+    if ((VPXDecoder::IsVP8(aTrackInfo.mMimeType) ||
+         VPXDecoder::IsVP9(aTrackInfo.mMimeType)) &&
+        CanCreateWMFDecoder<CLSID_WebmMfVpxDec>()) {
       return true;
     }
   }
 
   // Some unsupported codec.
   return false;
 }
 
--- a/dom/media/platforms/wmf/WMFVideoMFTManager.cpp
+++ b/dom/media/platforms/wmf/WMFVideoMFTManager.cpp
@@ -54,30 +54,22 @@ const GUID MFVideoFormat_VP90 =
 {
   0x30395056,
   0x0000,
   0x0010,
   {0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71}
 };
 #endif
 
-const CLSID CLSID_WebmMfVp8Dec =
+const CLSID CLSID_WebmMfVpxDec =
 {
-  0x451e3cb7,
-  0x2622,
-  0x4ba5,
-  {0x8e, 0x1d, 0x44, 0xb3, 0xc4, 0x1d, 0x09, 0x24}
-};
-
-const CLSID CLSID_WebmMfVp9Dec =
-{
-  0x7ab4bd2,
-  0x1979,
-  0x4fcd,
-  {0xa6, 0x97, 0xdf, 0x9a, 0xd1, 0x5b, 0x34, 0xfe}
+  0xe3aaf548,
+  0xc9a4,
+  0x4c6e,
+  { 0x23, 0x4d, 0x5a, 0xda, 0x37, 0x4b, 0x00, 0x00 }
 };
 
 namespace mozilla {
   
 LayersBackend
 GetCompositorBackendType(layers::KnowsCompositor* aKnowsCompositor)
 {
   if (aKnowsCompositor) {
@@ -142,18 +134,18 @@ WMFVideoMFTManager::~WMFVideoMFTManager(
 }
 
 const GUID&
 WMFVideoMFTManager::GetMFTGUID()
 {
   MOZ_ASSERT(mStreamType != Unknown);
   switch (mStreamType) {
     case H264: return CLSID_CMSH264DecoderMFT;
-    case VP8: return CLSID_WebmMfVp8Dec;
-    case VP9: return CLSID_WebmMfVp9Dec;
+    case VP8: return CLSID_WebmMfVpxDec;
+    case VP9: return CLSID_WebmMfVpxDec;
     default: return GUID_NULL;
   };
 }
 
 const GUID&
 WMFVideoMFTManager::GetMediaSubtypeGUID()
 {
   MOZ_ASSERT(mStreamType != Unknown);
@@ -437,18 +429,17 @@ WMFVideoMFTManager::Init()
 
   return success;
 }
 
 bool
 WMFVideoMFTManager::InitInternal(bool aForceD3D9)
 {
   mUseHwAccel = false; // default value; changed if D3D setup succeeds.
-  bool useDxva = InitializeDXVA(aForceD3D9 ||
-                                mStreamType == VP8 || mStreamType == VP9);
+  bool useDxva = InitializeDXVA(aForceD3D9);
 
   RefPtr<MFTDecoder> decoder(new MFTDecoder());
 
   HRESULT hr = decoder->Create(GetMFTGUID());
   NS_ENSURE_TRUE(SUCCEEDED(hr), false);
 
   RefPtr<IMFAttributes> attr(decoder->GetAttributes());
   UINT32 aware = 0;
@@ -483,16 +474,20 @@ WMFVideoMFTManager::InitInternal(bool aF
       }
     }
     else {
       mDXVAFailureReason.AssignLiteral("Decoder returned false for MF_SA_D3D_AWARE");
     }
   }
 
   if (!mUseHwAccel) {
+    // Use VP8/9 MFT only if HW acceleration is available
+    if (mStreamType == VP9 || mStreamType == VP8) {
+      return false;
+    }
     Telemetry::Accumulate(Telemetry::MEDIA_DECODER_BACKEND_USED,
                           uint32_t(media::MediaDecoderBackend::WMFSoftware));
   }
 
   mDecoder = decoder;
   hr = SetDecoderMediaTypes();
   NS_ENSURE_TRUE(SUCCEEDED(hr), false);
 
@@ -513,16 +508,25 @@ WMFVideoMFTManager::SetDecoderMediaTypes
   NS_ENSURE_TRUE(SUCCEEDED(hr), hr);
 
   hr = inputType->SetGUID(MF_MT_SUBTYPE, GetMediaSubtypeGUID());
   NS_ENSURE_TRUE(SUCCEEDED(hr), hr);
 
   hr = inputType->SetUINT32(MF_MT_INTERLACE_MODE, MFVideoInterlace_MixedInterlaceOrProgressive);
   NS_ENSURE_TRUE(SUCCEEDED(hr), hr);
 
+  // MSFT MFT needs this frame size set for VP9?
+  if (mStreamType == VP9 || mStreamType == VP8) {
+    hr = inputType->SetUINT32(MF_MT_INTERLACE_MODE, MFVideoInterlace_Progressive);
+    NS_ENSURE_TRUE(SUCCEEDED(hr), hr);
+
+    hr = MFSetAttributeSize(inputType, MF_MT_FRAME_SIZE, mVideoInfo.ImageRect().width, mVideoInfo.ImageRect().height);
+    NS_ENSURE_TRUE(SUCCEEDED(hr), hr);
+  }
+
   RefPtr<IMFMediaType> outputType;
   hr = wmf::MFCreateMediaType(getter_AddRefs(outputType));
   NS_ENSURE_TRUE(SUCCEEDED(hr), hr);
 
   hr = outputType->SetGUID(MF_MT_MAJOR_TYPE, MFMediaType_Video);
   NS_ENSURE_TRUE(SUCCEEDED(hr), hr);
 
   GUID outputSubType = mUseHwAccel ? MFVideoFormat_NV12 : MFVideoFormat_YV12;
--- a/dom/media/webrtc/MediaEngineCameraVideoSource.cpp
+++ b/dom/media/webrtc/MediaEngineCameraVideoSource.cpp
@@ -341,16 +341,28 @@ MediaEngineCameraVideoSource::SetName(ns
 #endif // ANDROID
 #ifdef XP_MACOSX
   // Kludge to test user-facing cameras on OSX.
   if (aName.Find(NS_LITERAL_STRING("Face")) != -1) {
     hasFacingMode = true;
     facingMode = VideoFacingModeEnum::User;
   }
 #endif
+#ifdef XP_WIN
+  // The cameras' name of Surface book are "Microsoft Camera Front" and
+  // "Microsoft Camera Rear" respectively.
+
+  if (aName.Find(NS_LITERAL_STRING("Front")) != kNotFound) {
+    hasFacingMode = true;
+    facingMode = VideoFacingModeEnum::User;
+  } else if (aName.Find(NS_LITERAL_STRING("Rear")) != kNotFound) {
+    hasFacingMode = true;
+    facingMode = VideoFacingModeEnum::Environment;
+  }
+#endif // WINDOWS
   if (hasFacingMode) {
     mFacingMode.Assign(NS_ConvertUTF8toUTF16(
         VideoFacingModeEnumValues::strings[uint32_t(facingMode)].value));
   } else {
     mFacingMode.Truncate();
   }
 }
 
--- a/dom/media/webrtc/RTCCertificate.cpp
+++ b/dom/media/webrtc/RTCCertificate.cpp
@@ -77,17 +77,17 @@ public:
     if (ok && expval > 0) {
       mExpires = std::min(expval, EXPIRATION_MAX);
     }
   }
 
 private:
   PRTime mExpires;
   SSLKEAType mAuthType;
-  ScopedCERTCertificate mCertificate;
+  UniqueCERTCertificate mCertificate;
   SECOidTag mSignatureAlg;
 
   static CERTName* GenerateRandomName(PK11SlotInfo* aSlot)
   {
     uint8_t randomName[RTCCertificateCommonNameLength];
     SECStatus rv = PK11_GenerateRandomOnSlot(aSlot, randomName,
                                              sizeof(randomName));
     if (rv != SECSuccess) {
@@ -167,17 +167,17 @@ private:
       return NS_ERROR_DOM_UNKNOWN_ERR;
     }
 
     // Set version to X509v3.
     *(mCertificate->version.data) = SEC_CERTIFICATE_VERSION_3;
     mCertificate->version.len = 1;
 
     SECItem innerDER = { siBuffer, nullptr, 0 };
-    if (!SEC_ASN1EncodeItem(arena, &innerDER, mCertificate,
+    if (!SEC_ASN1EncodeItem(arena, &innerDER, mCertificate.get(),
                             SEC_ASN1_GET(CERT_CertificateTemplate))) {
       return NS_ERROR_DOM_UNKNOWN_ERR;
     }
 
     SECItem *signedCert = PORT_ArenaZNew(arena, SECItem);
     if (!signedCert) {
       return NS_ERROR_DOM_UNKNOWN_ERR;
     }
@@ -236,17 +236,17 @@ private:
     return NS_OK;
   }
 
   virtual void Resolve() override
   {
     // Make copies of the private key and certificate, otherwise, when this
     // object is deleted, the structures they reference will be deleted too.
     SECKEYPrivateKey* key = mKeyPair->mPrivateKey.get()->GetPrivateKey();
-    CERTCertificate* cert = CERT_DupCertificate(mCertificate);
+    CERTCertificate* cert = CERT_DupCertificate(mCertificate.get());
     RefPtr<RTCCertificate> result =
         new RTCCertificate(mResultPromise->GetParentObject(),
                            key, cert, mAuthType, mExpires);
     mResultPromise->MaybeResolve(result);
   }
 };
 
 already_AddRefed<Promise>
@@ -315,18 +315,18 @@ RTCCertificate::~RTCCertificate()
 // creates before the RTCCertificate reference is released.
 RefPtr<DtlsIdentity>
 RTCCertificate::CreateDtlsIdentity() const
 {
   nsNSSShutDownPreventionLock locker;
   if (isAlreadyShutDown() || !mPrivateKey || !mCertificate) {
     return nullptr;
   }
-  SECKEYPrivateKey* key = SECKEY_CopyPrivateKey(mPrivateKey);
-  CERTCertificate* cert = CERT_DupCertificate(mCertificate);
+  SECKEYPrivateKey* key = SECKEY_CopyPrivateKey(mPrivateKey.get());
+  CERTCertificate* cert = CERT_DupCertificate(mCertificate.get());
   RefPtr<DtlsIdentity> id = new DtlsIdentity(key, cert, mAuthType);
   return id;
 }
 
 JSObject*
 RTCCertificate::WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto)
 {
   return RTCCertificateBinding::Wrap(aCx, this, aGivenProto);
@@ -336,26 +336,26 @@ void
 RTCCertificate::virtualDestroyNSSReference()
 {
   destructorSafeDestroyNSSReference();
 }
 
 void
 RTCCertificate::destructorSafeDestroyNSSReference()
 {
-  mPrivateKey.dispose();
-  mCertificate.dispose();
+  mPrivateKey.reset();
+  mCertificate.reset();
 }
 
 bool
 RTCCertificate::WritePrivateKey(JSStructuredCloneWriter* aWriter,
                                 const nsNSSShutDownPreventionLock& aLockProof) const
 {
   JsonWebKey jwk;
-  nsresult rv = CryptoKey::PrivateKeyToJwk(mPrivateKey, jwk, aLockProof);
+  nsresult rv = CryptoKey::PrivateKeyToJwk(mPrivateKey.get(), jwk, aLockProof);
   if (NS_FAILED(rv)) {
     return false;
   }
   nsString json;
   if (!jwk.ToJSON(json)) {
     return false;
   }
   return WriteString(aWriter, json);
@@ -397,33 +397,33 @@ RTCCertificate::ReadPrivateKey(JSStructu
   nsString json;
   if (!ReadString(aReader, json)) {
     return false;
   }
   JsonWebKey jwk;
   if (!jwk.Init(json)) {
     return false;
   }
-  mPrivateKey = CryptoKey::PrivateKeyFromJwk(jwk, aLockProof);
+  mPrivateKey.reset(CryptoKey::PrivateKeyFromJwk(jwk, aLockProof));
   return !!mPrivateKey;
 }
 
 bool
 RTCCertificate::ReadCertificate(JSStructuredCloneReader* aReader,
                                 const nsNSSShutDownPreventionLock& /*proof*/)
 {
   CryptoBuffer cert;
   if (!ReadBuffer(aReader, cert) || cert.Length() == 0) {
     return false;
   }
 
   SECItem der = { siBuffer, cert.Elements(),
                   static_cast<unsigned int>(cert.Length()) };
-  mCertificate = CERT_NewTempCertificate(CERT_GetDefaultCertDB(),
-                                         &der, nullptr, true, true);
+  mCertificate.reset(CERT_NewTempCertificate(CERT_GetDefaultCertDB(),
+                                             &der, nullptr, true, true));
   return !!mCertificate;
 }
 
 bool
 RTCCertificate::ReadStructuredClone(JSStructuredCloneReader* aReader)
 {
   nsNSSShutDownPreventionLock locker;
   if (isAlreadyShutDown()) {
--- a/dom/media/webrtc/RTCCertificate.h
+++ b/dom/media/webrtc/RTCCertificate.h
@@ -55,17 +55,17 @@ public:
   // NSPR PRTime is in microseconds since the same epoch.
   uint64_t Expires() const
   {
     return mExpires / PR_USEC_PER_MSEC;
   }
 
   // Accessors for use by PeerConnectionImpl.
   RefPtr<DtlsIdentity> CreateDtlsIdentity() const;
-  CERTCertificate* Certificate() const { return mCertificate; }
+  const UniqueCERTCertificate& Certificate() const { return mCertificate; }
 
   // For nsNSSShutDownObject
   virtual void virtualDestroyNSSReference() override;
   void destructorSafeDestroyNSSReference();
 
   // Structured clone methods
   bool WriteStructuredClone(JSStructuredCloneWriter* aWriter) const;
   bool ReadStructuredClone(JSStructuredCloneReader* aReader);
@@ -80,18 +80,18 @@ private:
   bool ReadPrivateKey(JSStructuredCloneReader* aReader,
                       const nsNSSShutDownPreventionLock& aLockProof);
   bool WriteCertificate(JSStructuredCloneWriter* aWriter,
                         const nsNSSShutDownPreventionLock& /*lockproof*/) const;
   bool WritePrivateKey(JSStructuredCloneWriter* aWriter,
                        const nsNSSShutDownPreventionLock& aLockProof) const;
 
   RefPtr<nsIGlobalObject> mGlobal;
-  ScopedSECKEYPrivateKey mPrivateKey;
-  ScopedCERTCertificate mCertificate;
+  UniqueSECKEYPrivateKey mPrivateKey;
+  UniqueCERTCertificate mCertificate;
   SSLKEAType mAuthType;
   PRTime mExpires;
 };
 
 } // namespace dom
 } // namespace mozilla
 
 #endif // mozilla_dom_RTCCertificate_h
--- a/dom/presentation/Presentation.cpp
+++ b/dom/presentation/Presentation.cpp
@@ -12,31 +12,35 @@
 #include "mozilla/dom/Promise.h"
 #include "nsContentUtils.h"
 #include "nsCycleCollectionParticipant.h"
 #include "nsIDocShell.h"
 #include "nsIPresentationService.h"
 #include "nsIScriptSecurityManager.h"
 #include "nsJSUtils.h"
 #include "nsNetUtil.h"
+#include "nsPIDOMWindow.h"
 #include "nsSandboxFlags.h"
 #include "nsServiceManagerUtils.h"
 #include "PresentationReceiver.h"
 
-using namespace mozilla;
-using namespace mozilla::dom;
+namespace mozilla {
+namespace dom {
 
-NS_IMPL_CYCLE_COLLECTION_INHERITED(Presentation, DOMEventTargetHelper,
-                                   mDefaultRequest, mReceiver)
+NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(Presentation,
+                                      mWindow,
+                                      mDefaultRequest, mReceiver)
 
-NS_IMPL_ADDREF_INHERITED(Presentation, DOMEventTargetHelper)
-NS_IMPL_RELEASE_INHERITED(Presentation, DOMEventTargetHelper)
+NS_IMPL_CYCLE_COLLECTING_ADDREF(Presentation)
+NS_IMPL_CYCLE_COLLECTING_RELEASE(Presentation)
 
-NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(Presentation)
-NS_INTERFACE_MAP_END_INHERITING(DOMEventTargetHelper)
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(Presentation)
+  NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
+  NS_INTERFACE_MAP_ENTRY(nsISupports)
+NS_INTERFACE_MAP_END
 
 /* static */ already_AddRefed<Presentation>
 Presentation::Create(nsPIDOMWindowInner* aWindow)
 {
   RefPtr<Presentation> presentation = new Presentation(aWindow);
   return presentation.forget();
 }
 
@@ -53,17 +57,18 @@ Presentation::HasReceiverSupport(JSConte
 
   // Grant access to browser receiving pages and their same-origin iframes. (App
   // pages should be controlled by "presentation" permission in app manifests.)
   nsCOMPtr<nsIDocShell> docshell = inner->GetDocShell();
   if (!docshell) {
     return false;
   }
 
-  if (!docshell->GetIsInMozBrowserOrApp()) {
+  if (!Preferences::GetBool("dom.presentation.testing.simulate-receiver") &&
+      !docshell->GetIsInMozBrowserOrApp()) {
     return false;
   }
 
   nsAutoString presentationURL;
   nsContentUtils::GetPresentationURL(docshell, presentationURL);
 
   if (presentationURL.IsEmpty()) {
     return false;
@@ -83,17 +88,17 @@ Presentation::HasReceiverSupport(JSConte
 
   nsCOMPtr<nsIURI> docURI = inner->GetDocumentURI();
   return NS_SUCCEEDED(securityManager->CheckSameOriginURI(presentationURI,
                                                           docURI,
                                                           false));
 }
 
 Presentation::Presentation(nsPIDOMWindowInner* aWindow)
-  : DOMEventTargetHelper(aWindow)
+  : mWindow(aWindow)
 {
 }
 
 Presentation::~Presentation()
 {
 }
 
 /* virtual */ JSObject*
@@ -105,17 +110,17 @@ Presentation::WrapObject(JSContext* aCx,
 
 void
 Presentation::SetDefaultRequest(PresentationRequest* aRequest)
 {
   if (IsInPresentedContent()) {
     return;
   }
 
-  nsCOMPtr<nsIDocument> doc = GetOwner() ? GetOwner()->GetExtantDoc() : nullptr;
+  nsCOMPtr<nsIDocument> doc = mWindow ? mWindow->GetExtantDoc() : nullptr;
   if (NS_WARN_IF(!doc)) {
     return;
   }
 
   if (doc->GetSandboxFlags() & SANDBOXED_PRESENTATION) {
     return;
   }
 
@@ -141,29 +146,36 @@ Presentation::GetReceiver()
     RefPtr<PresentationReceiver> receiver = mReceiver;
     return receiver.forget();
   }
 
   if (!IsInPresentedContent()) {
     return nullptr;
   }
 
-  mReceiver = PresentationReceiver::Create(GetOwner());
+  mReceiver = PresentationReceiver::Create(mWindow);
   if (NS_WARN_IF(!mReceiver)) {
     MOZ_ASSERT(mReceiver);
     return nullptr;
   }
 
   RefPtr<PresentationReceiver> receiver = mReceiver;
   return receiver.forget();
 }
 
 bool
 Presentation::IsInPresentedContent() const
 {
-  nsCOMPtr<nsIDocShell> docShell = GetOwner()->GetDocShell();
+  if (!mWindow) {
+    return false;
+  }
+
+  nsCOMPtr<nsIDocShell> docShell = mWindow->GetDocShell();
   MOZ_ASSERT(docShell);
 
   nsAutoString presentationURL;
   nsContentUtils::GetPresentationURL(docShell, presentationURL);
 
   return !presentationURL.IsEmpty();
 }
+
+} // namespace dom
+} // namespace mozilla
--- a/dom/presentation/Presentation.h
+++ b/dom/presentation/Presentation.h
@@ -2,53 +2,64 @@
 /* vim:set ts=2 sw=2 sts=2 et cindent: */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #ifndef mozilla_dom_Presentation_h
 #define mozilla_dom_Presentation_h
 
-#include "mozilla/DOMEventTargetHelper.h"
+#include "nsCOMPtr.h"
+#include "nsCycleCollectionParticipant.h"
+#include "nsISupportsImpl.h"
+#include "nsWrapperCache.h"
+
+class nsPIDOMWindowInner;
 
 namespace mozilla {
 namespace dom {
 
 class Promise;
 class PresentationReceiver;
 class PresentationRequest;
 
-class Presentation final : public DOMEventTargetHelper
+class Presentation final : public nsISupports
+                         , public nsWrapperCache
 {
 public:
-  NS_DECL_ISUPPORTS_INHERITED
-  NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(Presentation,
-                                           DOMEventTargetHelper)
+  NS_DECL_CYCLE_COLLECTING_ISUPPORTS
+  NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(Presentation)
 
   static already_AddRefed<Presentation> Create(nsPIDOMWindowInner* aWindow);
 
   static bool HasReceiverSupport(JSContext* aCx, JSObject* aGlobal);
 
   virtual JSObject* WrapObject(JSContext* aCx,
                                JS::Handle<JSObject*> aGivenProto) override;
 
+  nsPIDOMWindowInner* GetParentObject() const
+  {
+    return mWindow;
+  }
+
   // WebIDL (public APIs)
   void SetDefaultRequest(PresentationRequest* aRequest);
 
   already_AddRefed<PresentationRequest> GetDefaultRequest() const;
 
   already_AddRefed<PresentationReceiver> GetReceiver();
 
 private:
   explicit Presentation(nsPIDOMWindowInner* aWindow);
 
-  ~Presentation();
+  virtual ~Presentation();
 
   bool IsInPresentedContent() const;
 
   RefPtr<PresentationRequest> mDefaultRequest;
   RefPtr<PresentationReceiver> mReceiver;
+  nsCOMPtr<nsPIDOMWindowInner> mWindow;
 };
 
 } // namespace dom
 } // namespace mozilla
 
 #endif // mozilla_dom_Presentation_h
--- a/dom/presentation/PresentationConnection.cpp
+++ b/dom/presentation/PresentationConnection.cpp
@@ -7,17 +7,17 @@
 #include "PresentationConnection.h"
 
 #include "ControllerConnectionCollection.h"
 #include "mozilla/AsyncEventDispatcher.h"
 #include "mozilla/dom/DOMException.h"
 #include "mozilla/dom/File.h"
 #include "mozilla/dom/MessageEvent.h"
 #include "mozilla/dom/MessageEventBinding.h"
-#include "mozilla/dom/PresentationConnectionClosedEvent.h"
+#include "mozilla/dom/PresentationConnectionCloseEvent.h"
 #include "mozilla/ErrorNames.h"
 #include "mozilla/DebugOnly.h"
 #include "nsContentUtils.h"
 #include "nsCycleCollectionParticipant.h"
 #include "nsIPresentationService.h"
 #include "nsServiceManagerUtils.h"
 #include "nsStringStream.h"
 #include "PresentationConnectionList.h"
@@ -429,17 +429,17 @@ PresentationConnection::ProcessStateChan
                                                          message))) {
           mozilla::GetErrorName(aReason, message);
           message.InsertLiteral("Internal error: ", 0);
         }
         CopyUTF8toUTF16(message, errorMsg);
       }
 
       Unused <<
-        NS_WARN_IF(NS_FAILED(DispatchConnectionClosedEvent(reason, errorMsg)));
+        NS_WARN_IF(NS_FAILED(DispatchConnectionCloseEvent(reason, errorMsg)));
 
       return RemoveFromLoadGroup();
     }
     case PresentationConnectionState::Terminated: {
       // Ensure onterminate event is fired.
       RefPtr<AsyncEventDispatcher> asyncDispatcher =
         new AsyncEventDispatcher(this, NS_LITERAL_STRING("terminate"), false);
       Unused << NS_WARN_IF(NS_FAILED(asyncDispatcher->PostDOMEvent()));
@@ -528,32 +528,32 @@ PresentationConnection::DoReceiveMessage
       return NS_ERROR_FAILURE;
     }
   }
 
   return DispatchMessageEvent(jsData);
 }
 
 nsresult
-PresentationConnection::DispatchConnectionClosedEvent(
+PresentationConnection::DispatchConnectionCloseEvent(
   PresentationConnectionClosedReason aReason,
   const nsAString& aMessage,
   bool aDispatchNow)
 {
   if (mState != PresentationConnectionState::Closed) {
     MOZ_ASSERT(false, "The connection state should be closed.");
     return NS_ERROR_FAILURE;
   }
 
-  PresentationConnectionClosedEventInit init;
+  PresentationConnectionCloseEventInit init;
   init.mReason = aReason;
   init.mMessage = aMessage;
 
-  RefPtr<PresentationConnectionClosedEvent> closedEvent =
-    PresentationConnectionClosedEvent::Constructor(this,
+  RefPtr<PresentationConnectionCloseEvent> closedEvent =
+    PresentationConnectionCloseEvent::Constructor(this,
                                                    NS_LITERAL_STRING("close"),
                                                    init);
   closedEvent->SetTrusted(true);
 
   if (aDispatchNow) {
     bool ignore;
     return DOMEventTargetHelper::DispatchEvent(closedEvent, &ignore);
   }
@@ -737,17 +737,17 @@ PresentationConnection::AsyncCloseConnec
     NS_NewRunnableFunction([this, message]() -> void {
       // Set |mState| to |PresentationConnectionState::Closed| here to avoid
       // calling |ProcessStateChanged|.
       mState = PresentationConnectionState::Closed;
 
       // Make sure dispatching the event and closing the connection are invoked
       // at the same time by setting |aDispatchNow| to true.
       Unused << NS_WARN_IF(NS_FAILED(
-        DispatchConnectionClosedEvent(PresentationConnectionClosedReason::Error,
+        DispatchConnectionCloseEvent(PresentationConnectionClosedReason::Error,
                                       message,
                                       true)));
 
       nsCOMPtr<nsIPresentationService> service =
         do_GetService(PRESENTATION_SERVICE_CONTRACTID);
       if(NS_WARN_IF(!service)) {
         return;
       }
--- a/dom/presentation/PresentationConnection.h
+++ b/dom/presentation/PresentationConnection.h
@@ -6,17 +6,17 @@
 
 #ifndef mozilla_dom_PresentationConnection_h
 #define mozilla_dom_PresentationConnection_h
 
 #include "mozilla/DOMEventTargetHelper.h"
 #include "mozilla/dom/TypedArray.h"
 #include "mozilla/WeakPtr.h"
 #include "mozilla/dom/PresentationConnectionBinding.h"
-#include "mozilla/dom/PresentationConnectionClosedEventBinding.h"
+#include "mozilla/dom/PresentationConnectionCloseEventBinding.h"
 #include "nsIPresentationListener.h"
 #include "nsIRequest.h"
 #include "nsWeakReference.h"
 
 namespace mozilla {
 namespace dom {
 
 class Blob;
@@ -92,17 +92,17 @@ private:
   ~PresentationConnection();
 
   bool Init();
 
   void Shutdown();
 
   nsresult ProcessStateChanged(nsresult aReason);
 
-  nsresult DispatchConnectionClosedEvent(PresentationConnectionClosedReason aReason,
+  nsresult DispatchConnectionCloseEvent(PresentationConnectionClosedReason aReason,
                                          const nsAString& aMessage,
                                          bool aDispatchNow = false);
 
   nsresult DispatchMessageEvent(JS::Handle<JS::Value> aData);
 
   nsresult ProcessConnectionWentAway();
 
   nsresult AddIntoLoadGroup();
--- a/dom/presentation/PresentationReceiver.cpp
+++ b/dom/presentation/PresentationReceiver.cpp
@@ -5,16 +5,17 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "PresentationReceiver.h"
 
 #include "mozilla/dom/PresentationReceiverBinding.h"
 #include "mozilla/dom/Promise.h"
 #include "nsContentUtils.h"
 #include "nsIPresentationService.h"
+#include "nsPIDOMWindow.h"
 #include "nsServiceManagerUtils.h"
 #include "nsThreadUtils.h"
 #include "PresentationConnection.h"
 #include "PresentationConnectionList.h"
 #include "PresentationLog.h"
 
 namespace mozilla {
 namespace dom {
@@ -25,16 +26,17 @@ NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(Pr
                                       mConnectionList)
 
 NS_IMPL_CYCLE_COLLECTING_ADDREF(PresentationReceiver)
 NS_IMPL_CYCLE_COLLECTING_RELEASE(PresentationReceiver)
 
 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(PresentationReceiver)
   NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
   NS_INTERFACE_MAP_ENTRY(nsIPresentationRespondingListener)
+  NS_INTERFACE_MAP_ENTRY(nsISupports)
 NS_INTERFACE_MAP_END
 
 /* static */ already_AddRefed<PresentationReceiver>
 PresentationReceiver::Create(nsPIDOMWindowInner* aWindow)
 {
   RefPtr<PresentationReceiver> receiver = new PresentationReceiver(aWindow);
   return NS_WARN_IF(!receiver->Init()) ? nullptr : receiver.forget();
 }
--- a/dom/presentation/PresentationReceiver.h
+++ b/dom/presentation/PresentationReceiver.h
@@ -2,18 +2,24 @@
 /* vim:set ts=2 sw=2 sts=2 et cindent: */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #ifndef mozilla_dom_PresentationReceiver_h
 #define mozilla_dom_PresentationReceiver_h
 
+#include "mozilla/ErrorResult.h"
+#include "nsCOMPtr.h"
 #include "nsCycleCollectionParticipant.h"
 #include "nsIPresentationListener.h"
+#include "nsWrapperCache.h"
+#include "nsString.h"
+
+class nsPIDOMWindowInner;
 
 namespace mozilla {
 namespace dom {
 
 class PresentationConnection;
 class PresentationConnectionList;
 class Promise;
 
@@ -25,17 +31,17 @@ public:
   NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(PresentationReceiver)
   NS_DECL_NSIPRESENTATIONRESPONDINGLISTENER
 
   static already_AddRefed<PresentationReceiver> Create(nsPIDOMWindowInner* aWindow);
 
   virtual JSObject* WrapObject(JSContext* aCx,
                                JS::Handle<JSObject*> aGivenProto) override;
 
-  nsISupports* GetParentObject() const
+  nsPIDOMWindowInner* GetParentObject() const
   {
     return mOwner;
   }
 
   // WebIDL (public APIs)
   already_AddRefed<Promise> GetConnectionList(ErrorResult& aRv);
 
 private:
--- a/dom/presentation/PresentationService.cpp
+++ b/dom/presentation/PresentationService.cpp
@@ -637,17 +637,17 @@ PresentationService::StartSession(
                                   aCallback,
                                   aBuilderConstructor);
 
   if (aDeviceId.IsVoid()) {
     // Pop up a prompt and ask user to select a device.
     nsCOMPtr<nsIPresentationDevicePrompt> prompt =
       do_GetService(PRESENTATION_DEVICE_PROMPT_CONTRACTID);
     if (NS_WARN_IF(!prompt)) {
-      return aCallback->NotifyError(NS_ERROR_DOM_OPERATION_ERR);
+      return aCallback->NotifyError(NS_ERROR_DOM_INVALID_ACCESS_ERR);
     }
 
     nsresult rv = prompt->PromptDeviceSelection(request);
     if (NS_WARN_IF(NS_FAILED(rv))) {
       return aCallback->NotifyError(NS_ERROR_DOM_OPERATION_ERR);
     }
 
     return NS_OK;
@@ -791,17 +791,17 @@ PresentationService::CloseSession(const 
 
   RefPtr<PresentationSessionInfo> info = GetSessionInfo(aSessionId, aRole);
   if (NS_WARN_IF(!info)) {
     return NS_ERROR_NOT_AVAILABLE;
   }
 
   if (aClosedReason == nsIPresentationService::CLOSED_REASON_WENTAWAY) {
     // Remove nsIPresentationSessionListener since we don't want to dispatch
-    // PresentationConnectionClosedEvent if the page is went away.
+    // PresentationConnectionCloseEvent if the page is went away.
     info->SetListener(nullptr);
   }
 
   return info->Close(NS_OK, nsIPresentationSessionListener::STATE_CLOSED);
 }
 
 NS_IMETHODIMP
 PresentationService::TerminateSession(const nsAString& aSessionId,
--- a/dom/presentation/tests/mochitest/test_presentation_1ua_connection_wentaway_inproc.html
+++ b/dom/presentation/tests/mochitest/test_presentation_1ua_connection_wentaway_inproc.html
@@ -6,13 +6,13 @@
   <head>
     <meta charset="utf-8">
     <title>Test for B2G Presentation API when sender and receiver at the same side</title>
     <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
     <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
   </head>
   <body>
     <a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1258600">
-      Test for PresentationConnectionClosedEvent with wentaway reason</a>
+      Test for PresentationConnectionCloseEvent with wentaway reason</a>
     <script type="application/javascript;version=1.8" src="test_presentation_1ua_connection_wentaway.js">
     </script>
   </body>
 </html>
--- a/dom/presentation/tests/mochitest/test_presentation_1ua_connection_wentaway_oop.html
+++ b/dom/presentation/tests/mochitest/test_presentation_1ua_connection_wentaway_oop.html
@@ -6,13 +6,13 @@
   <head>
     <meta charset="utf-8">
     <title>Test for B2G Presentation API when sender and receiver at the same side</title>
     <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
     <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
   </head>
   <body>
     <a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1258600">
-      Test for PresentationConnectionClosedEvent with wentaway reason</a>
+      Test for PresentationConnectionCloseEvent with wentaway reason</a>
     <script type="application/javascript;version=1.8" src="test_presentation_1ua_connection_wentaway.js">
     </script>
   </body>
 </html>
--- a/dom/push/PushServiceWebSocket.jsm
+++ b/dom/push/PushServiceWebSocket.jsm
@@ -336,21 +336,24 @@ this.PushServiceWebSocket = {
   _reconnect: function () {
     console.debug("reconnect()");
     this._shutdownWS(false);
     this._startBackoffTimer();
   },
 
   _shutdownWS: function(shouldCancelPending = true) {
     console.debug("shutdownWS()");
+
+    if (this._currentState == STATE_READY) {
+      prefs.ignore("userAgentID", this);
+    }
+
     this._currentState = STATE_SHUT_DOWN;
     this._skipReconnect = false;
 
-    prefs.ignore("userAgentID", this);
-
     if (this._wsListener) {
       this._wsListener._pushService = null;
     }
     try {
         this._ws.close(0, null);
     } catch (e) {}
     this._ws = null;
 
--- a/dom/webidl/Presentation.webidl
+++ b/dom/webidl/Presentation.webidl
@@ -1,16 +1,16 @@
 /* -*- Mode: IDL; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this file,
  * You can obtain one at http://mozilla.org/MPL/2.0/.
  */
 
 [Pref="dom.presentation.enabled"]
-interface Presentation : EventTarget {
+interface Presentation {
  /*
   * This should be used by the UA as the default presentation request for the
   * controller. When the UA wishes to initiate a PresentationConnection on the
   * controller's behalf, it MUST start a presentation connection using the default
   * presentation request (as if the controller had called |defaultRequest.start()|).
   *
   * Only used by controlling browsing context (senders).
   *
rename from dom/webidl/PresentationConnectionClosedEvent.webidl
rename to dom/webidl/PresentationConnectionCloseEvent.webidl
--- a/dom/webidl/PresentationConnectionClosedEvent.webidl
+++ b/dom/webidl/PresentationConnectionCloseEvent.webidl
@@ -17,25 +17,25 @@ enum PresentationConnectionClosedReason
   "closed",
 
   // The connection is closed because the destination browsing context
   // that owned the connection navigated or was discarded.
   "wentaway"
 };
 
 [Constructor(DOMString type,
-             PresentationConnectionClosedEventInit eventInitDict),
+             PresentationConnectionCloseEventInit eventInitDict),
  Pref="dom.presentation.enabled"]
-interface PresentationConnectionClosedEvent : Event
+interface PresentationConnectionCloseEvent : Event
 {
   readonly attribute PresentationConnectionClosedReason reason;
 
   // The message is a human readable description of
   // how the communication channel encountered an error.
   // It is empty when the closed reason is closed or wentaway.
   readonly attribute DOMString message;
 };
 
-dictionary PresentationConnectionClosedEventInit : EventInit
+dictionary PresentationConnectionCloseEventInit : EventInit
 {
   required PresentationConnectionClosedReason reason;
   DOMString message = "";
 };
--- a/dom/webidl/moz.build
+++ b/dom/webidl/moz.build
@@ -761,17 +761,17 @@ GENERATED_EVENTS_WEBIDL_FILES = [
     'MozSettingsTransactionEvent.webidl',
     'MozStkCommandEvent.webidl',
     'PageTransitionEvent.webidl',
     'PerformanceEntryEvent.webidl',
     'PluginCrashedEvent.webidl',
     'PopStateEvent.webidl',
     'PopupBlockedEvent.webidl',
     'PresentationConnectionAvailableEvent.webidl',
-    'PresentationConnectionClosedEvent.webidl',
+    'PresentationConnectionCloseEvent.webidl',
     'ProgressEvent.webidl',
     'RecordErrorEvent.webidl',
     'ScrollViewChangeEvent.webidl',
     'ServiceWorkerMessageEvent.webidl',
     'StyleRuleChangeEvent.webidl',
     'StyleSheetApplicableStateChangeEvent.webidl',
     'StyleSheetChangeEvent.webidl',
     'TCPServerSocketEvent.webidl',
--- a/layout/base/nsDocumentViewer.cpp
+++ b/layout/base/nsDocumentViewer.cpp
@@ -1136,18 +1136,21 @@ nsDocumentViewer::PermitUnloadInternal(b
     // Never permit dialogs from the beforeunload handler
     nsGlobalWindow* globalWindow = nsGlobalWindow::Cast(window);
     dialogsAreEnabled = globalWindow->AreDialogsEnabled();
     nsGlobalWindow::TemporarilyDisableDialogs disableDialogs(globalWindow);
 
     nsIDocument::PageUnloadingEventTimeStamp timestamp(mDocument);
 
     mInPermitUnload = true;
-    EventDispatcher::DispatchDOMEvent(window, nullptr, event, mPresContext,
-                                      nullptr);
+    {
+      Telemetry::AutoTimer<Telemetry::HANDLE_BEFOREUNLOAD_MS> telemetryTimer;
+      EventDispatcher::DispatchDOMEvent(window, nullptr, event, mPresContext,
+                                        nullptr);
+    }
     mInPermitUnload = false;
   }
 
   nsCOMPtr<nsIDocShell> docShell(mContainer);
   nsAutoString text;
   beforeUnload->GetReturnValue(text);
 
   // NB: we nullcheck mDocument because it might now be dead as a result of
@@ -1320,17 +1323,20 @@ nsDocumentViewer::PageHide(bool aIsUnloa
     event.mTarget = mDocument;
 
     // Never permit popups from the unload handler, no matter how we get
     // here.
     nsAutoPopupStatePusher popupStatePusher(openAbused, true);
 
     nsIDocument::PageUnloadingEventTimeStamp timestamp(mDocument);
 
-    EventDispatcher::Dispatch(window, mPresContext, &event, nullptr, &status);
+    {
+      Telemetry::AutoTimer<Telemetry::HANDLE_UNLOAD_MS> telemetryTimer;
+      EventDispatcher::Dispatch(window, mPresContext, &event, nullptr, &status);
+    }
   }
 
 #ifdef MOZ_XUL
   // look for open menupopups and close them after the unload event, in case
   // the unload event listeners open any new popups
   nsContentUtils::HidePopupsInDocument(mDocument);
 #endif
 
--- a/layout/style/StyleAnimationValue.cpp
+++ b/layout/style/StyleAnimationValue.cpp
@@ -710,29 +710,23 @@ AddTransformLists(double aCoeff1, const 
 
 static double
 ComputeTransform2DMatrixDistance(const Matrix& aMatrix1,
                                  const Matrix& aMatrix2)
 {
   Point3D scale1(1, 1, 1);
   Point3D translate1;
   gfxQuaternion rotate1;
-  nsStyleTransformMatrix::ShearArray shear1;
-  for (auto&& s : shear1) {
-    s = 0.0f;
-  }
+  nsStyleTransformMatrix::ShearArray shear1{0.0f, 0.0f, 0.0f};
   Decompose2DMatrix(aMatrix1, scale1, shear1, rotate1, translate1);
 
   Point3D scale2(1, 1, 1);
   Point3D translate2;
   gfxQuaternion rotate2;
-  nsStyleTransformMatrix::ShearArray shear2;
-  for (auto&& s : shear2) {
-    s = 0.0f;
-  }
+  nsStyleTransformMatrix::ShearArray shear2{0.0f, 0.0f, 0.0f};
   Decompose2DMatrix(aMatrix2, scale2, shear2, rotate2, translate2);
 
   // Note:
   // 1. Shear factor is the tangent value of shear angle, so we need to
   //    call atan() to get the angle. For 2D transform, we only have XYSHEAR.
   // 2. The quaternion vector of the decomposed 2d matrix is got by
   //    "gfxQuaternion(0, 0, sin(rotate/2), cos(rotate/2))"
   //                         ^^^^^^^^^^^^^  ^^^^^^^^^^^^^
@@ -767,31 +761,25 @@ ComputeTransform2DMatrixDistance(const M
 static double
 ComputeTransform3DMatrixDistance(const Matrix4x4& aMatrix1,
                                  const Matrix4x4& aMatrix2)
 {
   Point3D scale1(1, 1, 1);
   Point3D translate1;
   Point4D perspective1(0, 0, 0, 1);
   gfxQuaternion rotate1;
-  nsStyleTransformMatrix::ShearArray shear1;
-  for (auto&& s : shear1) {
-    s = 0.0f;
-  }
+  nsStyleTransformMatrix::ShearArray shear1{0.0f, 0.0f, 0.0f};
   Decompose3DMatrix(aMatrix1, scale1, shear1, rotate1, translate1,
                     perspective1);
 
   Point3D scale2(1, 1, 1);
   Point3D translate2;
   Point4D perspective2(0, 0, 0, 1);
   gfxQuaternion rotate2;
-  nsStyleTransformMatrix::ShearArray shear2;
-  for (auto&& s : shear2) {
-    s = 0.0f;
-  }
+  nsStyleTransformMatrix::ShearArray shear2{0.0f, 0.0f, 0.0f};
   Decompose3DMatrix(aMatrix2, scale2, shear2, rotate2, translate2,
                     perspective2);
 
   // Note:
   // 1. Shear factor is the tangent value of shear angle, so we need to
   //    call atan() to get the angle.
   // 2. We use the same way to get the rotate angle of two quaternion vectors as
   //    what we do for rotate3d.
@@ -1787,28 +1775,22 @@ StyleAnimationValue::InterpolateTransfor
                                                 double aProgress)
 {
   // Decompose both matrices
 
   // TODO: What do we do if one of these returns false (singular matrix)
   Point3D scale1(1, 1, 1), translate1;
   Point4D perspective1(0, 0, 0, 1);
   gfxQuaternion rotate1;
-  nsStyleTransformMatrix::ShearArray shear1;
-  for (auto&& s : shear1) {
-    s = 0.0f;
-  }
+  nsStyleTransformMatrix::ShearArray shear1{0.0f, 0.0f, 0.0f};
 
   Point3D scale2(1, 1, 1), translate2;
   Point4D perspective2(0, 0, 0, 1);
   gfxQuaternion rotate2;
-  nsStyleTransformMatrix::ShearArray shear2;
-  for (auto&& s : shear2) {
-    s = 0.0f;
-  }
+  nsStyleTransformMatrix::ShearArray shear2{0.0f, 0.0f, 0.0f};
 
   Matrix matrix2d1, matrix2d2;
   if (aMatrix1.Is2D(&matrix2d1) && aMatrix2.Is2D(&matrix2d2)) {
     Decompose2DMatrix(matrix2d1, scale1, shear1, rotate1, translate1);
     Decompose2DMatrix(matrix2d2, scale2, shear2, rotate2, translate2);
   } else {
     Decompose3DMatrix(aMatrix1, scale1, shear1,
                       rotate1, translate1, perspective1);
--- a/layout/style/nsStyleStruct.cpp
+++ b/layout/style/nsStyleStruct.cpp
@@ -1908,126 +1908,135 @@ nsStyleGradient::HasCalc()
   return mBgPosX.IsCalcUnit() || mBgPosY.IsCalcUnit() || mAngle.IsCalcUnit() ||
          mRadiusX.IsCalcUnit() || mRadiusY.IsCalcUnit();
 }
 
 
 // --------------------
 // nsStyleImageRequest
 
+static void
+MaybeUntrackAndUnlock(nsStyleImageRequest::Mode aModeFlags,
+                      imgRequestProxy* aRequestProxy,
+                      ImageTracker* aImageTracker)
+{
+  MOZ_ASSERT(NS_IsMainThread());
+  MOZ_ASSERT(aRequestProxy);
+  MOZ_ASSERT(aImageTracker);
+
+  if (aModeFlags & nsStyleImageRequest::Mode::Lock) {
+    aRequestProxy->UnlockImage();
+  }
+
+  if (aModeFlags & nsStyleImageRequest::Mode::Discard) {
+    aRequestProxy->RequestDiscard();
+  }
+
+  if (aModeFlags & nsStyleImageRequest::Mode::Track) {
+    aImageTracker->Remove(aRequestProxy);
+  }
+}
+
 /**
  * Runnable to release the nsStyleImageRequest's mRequestProxy,
- * mImageValue and mImageValue on the main thread, and to perform
+ * mImageValue and mImageTracker on the main thread, and to perform
  * any necessary unlocking and untracking of the image.
  */
 class StyleImageRequestCleanupTask : public mozilla::Runnable
 {
 public:
   typedef nsStyleImageRequest::Mode Mode;
 
   StyleImageRequestCleanupTask(Mode aModeFlags,
                                already_AddRefed<imgRequestProxy> aRequestProxy,
                                already_AddRefed<css::ImageValue> aImageValue,
                                already_AddRefed<ImageTracker> aImageTracker)
     : mModeFlags(aModeFlags)
     , mRequestProxy(aRequestProxy)
     , mImageValue(aImageValue)
     , mImageTracker(aImageTracker)
   {
+    MOZ_ASSERT(!!mRequestProxy == !!mImageTracker);
   }
 
   NS_IMETHOD Run() final
   {
-    MOZ_ASSERT(NS_IsMainThread());
-
-    if (!mRequestProxy) {
-      return NS_OK;
+    if (mRequestProxy) {
+      MaybeUntrackAndUnlock(mModeFlags, mRequestProxy, mImageTracker);
     }
-
-    MOZ_ASSERT(mImageTracker);
-
-    if (mModeFlags & Mode::Lock) {
-      mRequestProxy->UnlockImage();
-    }
-
-    if (mModeFlags & Mode::Discard) {
-      mRequestProxy->RequestDiscard();
-    }
-
-    if (mModeFlags & Mode::Track) {
-      mImageTracker->Remove(mRequestProxy);
-    }
-
     return NS_OK;
   }
 
 protected:
   virtual ~StyleImageRequestCleanupTask() { MOZ_ASSERT(NS_IsMainThread()); }
 
 private:
   Mode mModeFlags;
-  // Since we always dispatch this runnable to the main thread, these will be
-  // released on the main thread when the runnable itself is released.
+  // These will be released on the main thread when the
+  // StyleImageRequestCleanupTask is deleted.
   RefPtr<imgRequestProxy> mRequestProxy;
   RefPtr<css::ImageValue> mImageValue;
   RefPtr<ImageTracker> mImageTracker;
 };
 
 nsStyleImageRequest::nsStyleImageRequest(Mode aModeFlags,
                                          imgRequestProxy* aRequestProxy,
                                          css::ImageValue* aImageValue,
                                          ImageTracker* aImageTracker)
   : mRequestProxy(aRequestProxy)
-  , mImageValue(aImageValue)
   , mImageTracker(aImageTracker)
+  , mBaseURI(aImageValue->mBaseURI)
+  , mURIString(aImageValue->mString)
   , mModeFlags(aModeFlags)
   , mResolved(true)
 {
   MOZ_ASSERT(NS_IsMainThread());
   MOZ_ASSERT(aRequestProxy);
-  MOZ_ASSERT(aImageValue);
   MOZ_ASSERT(aImageTracker);
 
   MaybeTrackAndLock();
 }
 
 nsStyleImageRequest::nsStyleImageRequest(
     Mode aModeFlags,
     nsStringBuffer* aURLBuffer,
     already_AddRefed<PtrHolder<nsIURI>> aBaseURI,
     already_AddRefed<PtrHolder<nsIURI>> aReferrer,
     already_AddRefed<PtrHolder<nsIPrincipal>> aPrincipal)
   : mModeFlags(aModeFlags)
   , mResolved(false)
 {
   mImageValue = new css::ImageValue(aURLBuffer, Move(aBaseURI),
                                     Move(aReferrer), Move(aPrincipal));
+  mBaseURI = mImageValue->mBaseURI;
+  mURIString = mImageValue->mString;
 }
 
 nsStyleImageRequest::~nsStyleImageRequest()
 {
-  // We may or may not be being destroyed on the main thread.  To clean
-  // up, we must untrack and unlock the image (depending on mModeFlags),
-  // and release mRequestProxy and mImageValue, all on the main thread.
-  {
-    RefPtr<StyleImageRequestCleanupTask> task =
-        new StyleImageRequestCleanupTask(mModeFlags,
-                                         mRequestProxy.forget(),
-                                         mImageValue.forget(),
-                                         mImageTracker.forget());
-    if (NS_IsMainThread()) {
-      task->Run();
-    } else {
+  if (NS_IsMainThread()) {
+    // In the main thread case, mRequestProxy, mImageValue and
+    // mImageTracker will be released as normal after the nsStyleImageRequest
+    // has run.
+    if (mRequestProxy) {
+      MaybeUntrackAndUnlock(mModeFlags, mRequestProxy, mImageTracker);
+    }
+  } else {
+    // In the OMT case, we transfer ownership of these objects to the
+    // cleanup task runnable, which will call MaybeUntrackAndUnlock and
+    // then release the objects.
+    if (mRequestProxy || mImageValue) {
+      RefPtr<StyleImageRequestCleanupTask> task =
+          new StyleImageRequestCleanupTask(mModeFlags,
+                                           mRequestProxy.forget(),
+                                           mImageValue.forget(),
+                                           mImageTracker.forget());
       NS_DispatchToMainThread(task.forget());
     }
   }
-
-  MOZ_ASSERT(!mRequestProxy);
-  MOZ_ASSERT(!mImageValue);
-  MOZ_ASSERT(!mImageTracker);
 }
 
 bool
 nsStyleImageRequest::Resolve(nsPresContext* aPresContext)
 {
   MOZ_ASSERT(NS_IsMainThread());
   MOZ_ASSERT(!IsResolved(), "already resolved");
 
@@ -2039,16 +2048,19 @@ nsStyleImageRequest::Resolve(nsPresConte
   // document.
   mImageValue->Initialize(aPresContext->Document());
 
   nsCSSValue value;
   value.SetImageValue(mImageValue);
   mRequestProxy = value.GetPossiblyStaticImageValue(aPresContext->Document(),
                                                     aPresContext);
 
+  // We no longer need the ImageValue.
+  mImageValue = nullptr;
+
   if (!mRequestProxy) {
     // The URL resolution or image load failed.
     return false;
   }
 
   mImageTracker = aPresContext->Document()->ImageTracker();
   MaybeTrackAndLock();
   return true;
@@ -2066,20 +2078,32 @@ nsStyleImageRequest::MaybeTrackAndLock()
     mImageTracker->Add(mRequestProxy);
   }
 
   if (mModeFlags & Mode::Lock) {
     mRequestProxy->LockImage();
   }
 }
 
+static const char16_t*
+GetBufferValue(nsStringBuffer* aBuffer)
+{
+  // Since the nsStringBuffers we work with come from a css::ImageValue, we
+  // can assume (just like nsCSSValue::GetBufferValue does) that we have
+  // a 16 bit, null terminated string.
+  return static_cast<char16_t*>(aBuffer->Data());
+}
+
 bool
 nsStyleImageRequest::DefinitelyEquals(const nsStyleImageRequest& aOther) const
 {
-  return DefinitelyEqualURIs(mImageValue, aOther.mImageValue);
+  return mBaseURI == aOther.mBaseURI &&
+         (mURIString == aOther.mURIString ||
+          NS_strcmp(GetBufferValue(mURIString),
+                    GetBufferValue(aOther.mURIString)) == 0);
 }
 
 // --------------------
 // CachedBorderImageData
 //
 void
 CachedBorderImageData::SetCachedSVGViewportSize(
   const mozilla::Maybe<nsSize>& aSVGViewportSize)
--- a/layout/style/nsStyleStruct.h
+++ b/layout/style/nsStyleStruct.h
@@ -300,22 +300,28 @@ private:
  *    any thread.  The nsStyleImageRequest is not considered "resolved"
  *    at this point, and the Resolve() method must be called later
  *    to initiate the image load and make calls to get() valid.
  *
  * Calls to TrackImage(), UntrackImage(), LockImage(), UnlockImage() and
  * RequestDiscard() are made to the imgRequestProxy and ImageTracker as
  * appropriate, according to the mode flags passed in to the constructor.
  *
- * The main thread constructor takes a pointer to the css::ImageValue that
- * is the specified url() value, while the off-main-thread constructor
- * creates a new css::ImageValue to represent the url() information passed
- * to the constructor.  This ImageValue is held on to for the comparisons done
- * in DefinitelyEquals(), so that we don't need to call into the non-OMT-safe
- * Equals() on the nsIURI objects returned from imgRequestProxy::GetURI().
+ * The main thread constructor takes a pointer to the already-created
+ * imgRequestProxy, and the css::ImageValue that was used while creating it.
+ * The ImageValue object is only used to grab the URL details to store
+ * into mBaseURI and mURIString.
+ *
+ * The off-main-thread constructor creates a new css::ImageValue to
+ * hold all the data required to resolve the imgRequestProxy later.  This
+ * constructor also stores the URL details into mbaseURI and mURIString.
+ * The ImageValue is held on to in mImageTracker until the Resolve call.
+ *
+ * We use mBaseURI and mURIString so that we can perform nsStyleImageRequest
+ * equality comparisons without needing an imgRequestProxy.
  */
 class nsStyleImageRequest
 {
 public:
   // Flags describing whether the imgRequestProxy must be tracked in the
   // ImageTracker, whether LockImage/UnlockImage calls will be made
   // when obtaining and releasing the imgRequestProxy, and whether
   // RequestDiscard will be called on release.
@@ -347,32 +353,39 @@ public:
     MOZ_ASSERT(IsResolved(), "Resolve() must be called first");
     MOZ_ASSERT(NS_IsMainThread());
     return mRequestProxy.get();
   }
   const imgRequestProxy* get() const {
     return const_cast<nsStyleImageRequest*>(this)->get();
   }
 
-  // Returns whether the ImageValue objects in the two nsStyleImageRequests
-  // return true from URLValueData::DefinitelyEqualURIs.
+  // Returns whether the mBaseURI and mURIString values in the two
+  // nsStyleImageRequests are equal.
   bool DefinitelyEquals(const nsStyleImageRequest& aOther) const;
 
   NS_INLINE_DECL_THREADSAFE_REFCOUNTING(nsStyleImageRequest);
 
 private:
   ~nsStyleImageRequest();
   nsStyleImageRequest& operator=(const nsStyleImageRequest& aOther) = delete;
 
   void MaybeTrackAndLock();
 
   RefPtr<imgRequestProxy> mRequestProxy;
   RefPtr<mozilla::css::ImageValue> mImageValue;
   RefPtr<mozilla::dom::ImageTracker> mImageTracker;
 
+  // Components that are (or were) used to produce the nsIURI for resolving
+  // the imgRequestProxy.  We use these in DefinitelyEquals, rather than
+  // comparing the URI information in the imgRequestProxy objects, since
+  // they may not yet be resolved (or might have failed to resolve).
+  mozilla::PtrHandle<nsIURI> mBaseURI;
+  RefPtr<nsStringBuffer> mURIString;
+
   Mode mModeFlags;
   bool mResolved;
 };
 
 MOZ_MAKE_ENUM_CLASS_BITWISE_OPERATORS(nsStyleImageRequest::Mode)
 
 enum nsStyleImageType {
   eStyleImageType_Null,
--- a/media/mtransport/dtlsidentity.cpp
+++ b/media/mtransport/dtlsidentity.cpp
@@ -13,22 +13,16 @@
 #include "pk11pub.h"
 #include "sechash.h"
 #include "ssl.h"
 
 #include "mozilla/Sprintf.h"
 
 namespace mozilla {
 
-DtlsIdentity::~DtlsIdentity() {
-  if (cert_) {
-    CERT_DestroyCertificate(cert_);
-  }
-}
-
 RefPtr<DtlsIdentity> DtlsIdentity::Generate() {
   ScopedPK11SlotInfo slot(PK11_GetInternalSlot());
   if (!slot) {
     return nullptr;
   }
 
   uint8_t random_name[16];
 
@@ -159,23 +153,23 @@ RefPtr<DtlsIdentity> DtlsIdentity::Gener
 }
 
 const std::string DtlsIdentity::DEFAULT_HASH_ALGORITHM = "sha-256";
 
 nsresult DtlsIdentity::ComputeFingerprint(const std::string algorithm,
                                           uint8_t *digest,
                                           size_t size,
                                           size_t *digest_length) const {
-  const CERTCertificate* c = cert();
+  const UniqueCERTCertificate& c = cert();
   MOZ_ASSERT(c);
 
   return ComputeFingerprint(c, algorithm, digest, size, digest_length);
 }
 
-nsresult DtlsIdentity::ComputeFingerprint(const CERTCertificate *cert,
+nsresult DtlsIdentity::ComputeFingerprint(const UniqueCERTCertificate& cert,
                                           const std::string algorithm,
                                           uint8_t *digest,
                                           size_t size,
                                           size_t *digest_length) {
   MOZ_ASSERT(cert);
 
   HASH_HashType ht;
 
--- a/media/mtransport/dtlsidentity.h
+++ b/media/mtransport/dtlsidentity.h
@@ -28,44 +28,43 @@ class DtlsIdentity final {
       : private_key_(privkey), cert_(cert), auth_type_(authType) {}
 
   // This is only for use in tests, or for external linkage.  It makes a (bad)
   // instance of this class.
   static RefPtr<DtlsIdentity> Generate();
 
   // These don't create copies or transfer ownership. If you want these to live
   // on, make a copy.
-  CERTCertificate *cert() const { return cert_; }
+  const UniqueCERTCertificate& cert() const { return cert_; }
   SECKEYPrivateKey *privkey() const { return private_key_; }
   // Note: this uses SSLKEAType because that is what the libssl API requires.
   // This is a giant confusing mess, but libssl indexes certificates based on a
   // key exchange type, not authentication type (as you might have reasonably
   // expected).
   SSLKEAType auth_type() const { return auth_type_; }
 
   nsresult ComputeFingerprint(const std::string algorithm,
                               uint8_t *digest,
                               size_t size,
                               size_t *digest_length) const;
-  static nsresult ComputeFingerprint(const CERTCertificate *cert,
+  static nsresult ComputeFingerprint(const UniqueCERTCertificate& cert,
                                      const std::string algorithm,
                                      uint8_t *digest,
                                      size_t size,
                                      size_t *digest_length);
 
   static const std::string DEFAULT_HASH_ALGORITHM;
   enum {
     HASH_ALGORITHM_MAX_LENGTH = 64
   };
 
   NS_INLINE_DECL_THREADSAFE_REFCOUNTING(DtlsIdentity)
 
  private:
-  ~DtlsIdentity();
+  ~DtlsIdentity() {}
   DISALLOW_COPY_ASSIGN(DtlsIdentity);
 
   ScopedSECKEYPrivateKey private_key_;
-  CERTCertificate *cert_;  // TODO: Using a smart pointer here causes link
-                           // errors.
+  UniqueCERTCertificate cert_;
   SSLKEAType auth_type_;
 };
 }  // close namespace
 #endif
--- a/media/mtransport/third_party/nICEr/src/ice/ice_component.c
+++ b/media/mtransport/third_party/nICEr/src/ice/ice_component.c
@@ -52,29 +52,29 @@ static char *RCSSTRING __UNUSED__="$Id: 
 #include "r_time.h"
 
 static int nr_ice_component_stun_server_default_cb(void *cb_arg,nr_stun_server_ctx *stun_ctx,nr_socket *sock, nr_stun_server_request *req, int *dont_free, int *error);
 static int nr_ice_pre_answer_request_destroy(nr_ice_pre_answer_request **parp);
 void nr_ice_component_consent_schedule_consent_timer(nr_ice_component *comp);
 void nr_ice_component_consent_destroy(nr_ice_component *comp);
 
 /* This function takes ownership of the contents of req (but not req itself) */
-static int nr_ice_pre_answer_request_create(nr_socket *sock, nr_stun_server_request *req, nr_ice_pre_answer_request **parp)
+static int nr_ice_pre_answer_request_create(nr_transport_addr *dst, nr_stun_server_request *req, nr_ice_pre_answer_request **parp)
   {
     int r, _status;
     nr_ice_pre_answer_request *par = 0;
     nr_stun_message_attribute *attr;
 
     if (!(par = RCALLOC(sizeof(nr_ice_pre_answer_request))))
       ABORT(R_NO_MEMORY);
 
     par->req = *req; /* Struct assignment */
     memset(req, 0, sizeof(*req)); /* Zero contents to avoid confusion */
 
-    if (r=nr_socket_getaddr(sock, &par->local_addr))
+    if (r=nr_transport_addr_copy(&par->local_addr, dst))
       ABORT(r);
     if (!nr_stun_message_has_attribute(par->req.request, NR_STUN_ATTR_USERNAME, &attr))
       ABORT(R_INTERNAL);
     if (!(par->username = r_strdup(attr->u.username)))
       ABORT(R_NO_MEMORY);
 
     *parp=par;
     _status=0;
@@ -1136,34 +1136,71 @@ int nr_ice_component_pair_candidates(nr_
 
     pcomp->state = NR_ICE_COMPONENT_RUNNING;
 
     _status=0;
   abort:
     return(_status);
   }
 
+int nr_ice_pre_answer_enqueue(nr_ice_component *comp, nr_socket *sock, nr_stun_server_request *req, int *dont_free)
+  {
+    int r = 0;
+    int _status;
+    nr_ice_pre_answer_request *r1, *r2;
+    nr_transport_addr dst_addr;
+    nr_ice_pre_answer_request *par = 0;
+
+    if (r=nr_socket_getaddr(sock, &dst_addr))
+      ABORT(r);
+
+    STAILQ_FOREACH_SAFE(r1, &comp->pre_answer_reqs, entry, r2) {
+      if (!nr_transport_addr_cmp(&r1->local_addr, &dst_addr,
+                                 NR_TRANSPORT_ADDR_CMP_MODE_ALL) &&
+          !nr_transport_addr_cmp(&r1->req.src_addr, &req->src_addr,
+                                 NR_TRANSPORT_ADDR_CMP_MODE_ALL)) {
+        return(0);
+      }
+    }
+
+    if (r=nr_ice_pre_answer_request_create(&dst_addr, req, &par))
+      ABORT(r);
+
+    r_log(LOG_ICE,LOG_DEBUG, "ICE(%s)/STREAM(%s)/COMP(%d): Enqueuing STUN request pre-answer from %s",
+          comp->ctx->label, comp->stream->label, comp->component_id,
+          req->src_addr.as_string);
+
+    *dont_free = 1;
+    STAILQ_INSERT_TAIL(&comp->pre_answer_reqs, par, entry);
+
+    _status=0;
+abort:
+    return(_status);
+  }
+
 /* Fires when we have an incoming candidate that doesn't correspond to an existing
    remote peer. This is either pre-answer or just spurious. Store it in the
    component for use when we see the actual answer, at which point we need
    to do the procedures from S 7.2.1 in nr_ice_component_stun_server_cb.
  */
 static int nr_ice_component_stun_server_default_cb(void *cb_arg,nr_stun_server_ctx *stun_ctx,nr_socket *sock, nr_stun_server_request *req, int *dont_free, int *error)
   {
     int r, _status;
     nr_ice_component *comp = (nr_ice_component *)cb_arg;
-    nr_ice_pre_answer_request *par = 0;
+
     r_log(LOG_ICE,LOG_DEBUG,"ICE(%s)/STREAM(%s)/COMP(%d): Received STUN request pre-answer from %s",
-          comp->ctx->label, comp->stream->label, comp->component_id, req->src_addr.as_string);
+          comp->ctx->label, comp->stream->label, comp->component_id,
+          req->src_addr.as_string);
 
-    if (r=nr_ice_pre_answer_request_create(sock, req, &par))
+    if (r=nr_ice_pre_answer_enqueue(comp, sock, req, dont_free)) {
+      r_log(LOG_ICE,LOG_ERR,"ICE(%s)/STREAM(%s)/COMP(%d): Failed (%d) to enque pre-answer request from %s",
+          comp->ctx->label, comp->stream->label, comp->component_id, r,
+          req->src_addr.as_string);
       ABORT(r);
-
-    *dont_free = 1;
-    STAILQ_INSERT_TAIL(&comp->pre_answer_reqs, par, entry);
+    }
 
     _status=0;
  abort:
     return(_status);
   }
 
 #define NR_ICE_CONSENT_TIMER_DEFAULT 5000
 #define NR_ICE_CONSENT_TIMEOUT_DEFAULT 30000
--- a/media/mtransport/third_party/nICEr/src/stun/nr_socket_buffered_stun.c
+++ b/media/mtransport/third_party/nICEr/src/stun/nr_socket_buffered_stun.c
@@ -280,61 +280,60 @@ static int nr_socket_buffered_stun_recvf
   nr_socket_buffered_stun *sock = (nr_socket_buffered_stun *)obj;
   nr_frame_header *frame = (nr_frame_header *)sock->buffer;
   size_t skip_hdr_size = (sock->framing_type == ICE_TCP_FRAMING) ? sizeof(nr_frame_header) : 0;
 
   if (sock->read_state == NR_ICE_SOCKET_READ_FAILED) {
     ABORT(R_FAILED);
   }
 
-reread:
-  /* Read all the expected bytes */
-  assert(sock->bytes_needed <= sock->buffer_size - sock->bytes_read);
+  while (sock->bytes_needed) {
+    /* Read all the expected bytes */
+    assert(sock->bytes_needed <= sock->buffer_size - sock->bytes_read);
 
-  if(r=nr_socket_read(sock->inner,
-                      sock->buffer + sock->bytes_read,
-                      sock->bytes_needed, &bytes_read, 0))
-    ABORT(r);
+    if(r=nr_socket_read(sock->inner,
+                        sock->buffer + sock->bytes_read,
+                        sock->bytes_needed, &bytes_read, 0))
+      ABORT(r);
+
+    assert(bytes_read <= sock->bytes_needed);
+    sock->bytes_needed -= bytes_read;
+    sock->bytes_read += bytes_read;
+
+    /* Unfinished */
+    if (sock->bytes_needed)
+      ABORT(R_WOULDBLOCK);
 
-  assert(bytes_read <= sock->bytes_needed);
-  sock->bytes_needed -= bytes_read;
-  sock->bytes_read += bytes_read;
+    /* No more bytes expected */
+    if (sock->read_state == NR_ICE_SOCKET_READ_NONE) {
+      size_t remaining_length;
+      if (sock->framing_type == ICE_TCP_FRAMING) {
+        if (sock->bytes_read < sizeof(nr_frame_header))
+          ABORT(R_BAD_DATA);
+        remaining_length = ntohs(frame->frame_length);
+      } else {
+        int tmp_length;
 
-  /* Unfinished */
-  if (sock->bytes_needed)
-    ABORT(R_WOULDBLOCK);
+        /* Parse the header */
+        if (r = nr_stun_message_length(sock->buffer, sock->bytes_read, &tmp_length))
+          ABORT(r);
+        assert(tmp_length >= 0);
+        if (tmp_length < 0)
+          ABORT(R_BAD_DATA);
+        remaining_length = tmp_length;
 
-  /* No more bytes expected */
-  if (sock->read_state == NR_ICE_SOCKET_READ_NONE) {
-    size_t remaining_length;
-    if (sock->framing_type == ICE_TCP_FRAMING) {
-      if (sock->bytes_read < sizeof(nr_frame_header))
+      }
+      /* Check to see if we have enough room */
+      if ((sock->buffer_size - sock->bytes_read) < remaining_length)
         ABORT(R_BAD_DATA);
-      remaining_length = ntohs(frame->frame_length);
-    } else {
-      int tmp_length;
 
-      /* Parse the header */
-      if (r = nr_stun_message_length(sock->buffer, sock->bytes_read, &tmp_length))
-        ABORT(r);
-      assert(tmp_length >= 0);
-      if (tmp_length < 0)
-        ABORT(R_BAD_DATA);
-      remaining_length = tmp_length;
-
+      sock->read_state = NR_ICE_SOCKET_READ_HDR;
+      /* Set ourselves up to read the rest of the data */
+      sock->bytes_needed = remaining_length;
     }
-    /* Check to see if we have enough room */
-    if ((sock->buffer_size - sock->bytes_read) < remaining_length)
-      ABORT(R_BAD_DATA);
-
-    /* Set ourselves up to read the rest of the data */
-    sock->bytes_needed = remaining_length;
-
-    sock->read_state = NR_ICE_SOCKET_READ_HDR;
-    goto reread;
   }
 
   assert(skip_hdr_size <= sock->bytes_read);
   if (skip_hdr_size > sock->bytes_read)
     ABORT(R_BAD_DATA);
   sock->bytes_read -= skip_hdr_size;
 
   if (maxlen < sock->bytes_read)
--- a/media/mtransport/third_party/nICEr/src/stun/stun_build.c
+++ b/media/mtransport/third_party/nICEr/src/stun/stun_build.c
@@ -70,21 +70,21 @@ nr_stun_form_request_or_indication(int m
        ABORT(r);
 
    req->header.type = msg_type;
 
    nr_crypto_random_bytes((UCHAR*)&req->header.id,sizeof(req->header.id));
 
    switch (mode) {
    default:
-       req->header.magic_cookie = NR_STUN_MAGIC_COOKIE;
-
        if ((r=nr_stun_message_add_fingerprint_attribute(req)))
            ABORT(r);
-
+       /* fall through */
+   case NR_STUN_MODE_STUN_NO_AUTH:
+       req->header.magic_cookie = NR_STUN_MAGIC_COOKIE;
        break;
 
 #ifdef USE_STUND_0_96
    case NR_STUN_MODE_STUND_0_96:
        req->header.magic_cookie = NR_STUN_MAGIC_COOKIE2;
 
        /* actually, stund 0.96 just ignores the fingerprint
         * attribute, but don't bother to send it */
@@ -159,17 +159,17 @@ nr_stun_build_req_st_auth(nr_stun_client
 }
 
 int
 nr_stun_build_req_no_auth(nr_stun_client_stun_binding_request_params *params, nr_stun_message **msg)
 {
    int r,_status;
    nr_stun_message *req = 0;
 
-   if ((r=nr_stun_form_request_or_indication(NR_STUN_MODE_STUN, NR_STUN_MSG_BINDING_REQUEST, &req)))
+   if ((r=nr_stun_form_request_or_indication(NR_STUN_MODE_STUN_NO_AUTH, NR_STUN_MSG_BINDING_REQUEST, &req)))
        ABORT(r);
 
    *msg = req;
 
    _status=0;
  abort:
    if (_status) nr_stun_message_destroy(&req);
    return _status;
--- a/media/mtransport/third_party/nICEr/src/stun/stun_build.h
+++ b/media/mtransport/third_party/nICEr/src/stun/stun_build.h
@@ -35,16 +35,17 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 #define _stun_build_h
 
 #include "stun.h"
 
 #define NR_STUN_MODE_STUN               1
 #ifdef USE_STUND_0_96
 #define NR_STUN_MODE_STUND_0_96         2    /* backwards compatibility mode */
 #endif /* USE_STUND_0_96 */
+#define NR_STUN_MODE_STUN_NO_AUTH       3
 int nr_stun_form_request_or_indication(int mode, int msg_type, nr_stun_message **msg);
 
 typedef struct nr_stun_client_stun_binding_request_params_ {
     char *username;
     Data *password;
     char *nonce;
     char *realm;
 } nr_stun_client_stun_binding_request_params;
--- a/media/mtransport/transportlayerdtls.cpp
+++ b/media/mtransport/transportlayerdtls.cpp
@@ -10,17 +10,19 @@
 
 #include <algorithm>
 #include <queue>
 #include <sstream>
 
 #include "dtlsidentity.h"
 #include "keyhi.h"
 #include "logging.h"
+#include "mozilla/Move.h"
 #include "mozilla/UniquePtr.h"
+#include "mozilla/Unused.h"
 #include "nsCOMPtr.h"
 #include "nsComponentManagerUtils.h"
 #include "nsComponentManagerUtils.h"
 #include "nsIEventTarget.h"
 #include "nsNetCID.h"
 #include "nsServiceManagerUtils.h"
 #include "ssl.h"
 #include "sslerr.h"
@@ -484,174 +486,175 @@ bool TransportLayerDtls::Setup() {
               "Can't start DTLS without specifying a verification mode");
     return false;
   }
 
   if (transport_layer_identity == PR_INVALID_IO_LAYER) {
     transport_layer_identity = PR_GetUniqueIdentity("nssstreamadapter");
   }
 
-  ScopedPRFileDesc pr_fd(PR_CreateIOLayerStub(transport_layer_identity,
+  UniquePRFileDesc pr_fd(PR_CreateIOLayerStub(transport_layer_identity,
                                               &TransportLayerMethods));
   MOZ_ASSERT(pr_fd != nullptr);
   if (!pr_fd)
     return false;
   pr_fd->secret = reinterpret_cast<PRFilePrivate *>(nspr_io_adapter_.get());
 
-  ScopedPRFileDesc ssl_fd(DTLS_ImportFD(nullptr, pr_fd));
+  UniquePRFileDesc ssl_fd(DTLS_ImportFD(nullptr, pr_fd.get()));
   MOZ_ASSERT(ssl_fd != nullptr);  // This should never happen
   if (!ssl_fd) {
     return false;
   }
 
-  pr_fd.forget(); // ownership transfered to ssl_fd;
+  Unused << pr_fd.release(); // ownership transfered to ssl_fd;
 
   if (role_ == CLIENT) {
     MOZ_MTLOG(ML_INFO, "Setting up DTLS as client");
-    rv = SSL_GetClientAuthDataHook(ssl_fd, GetClientAuthDataHook,
+    rv = SSL_GetClientAuthDataHook(ssl_fd.get(), GetClientAuthDataHook,
                                    this);
     if (rv != SECSuccess) {
       MOZ_MTLOG(ML_ERROR, "Couldn't set identity");
       return false;
     }
   } else {
     MOZ_MTLOG(ML_INFO, "Setting up DTLS as server");
     // Server side
-    rv = SSL_ConfigSecureServer(ssl_fd, identity_->cert(),
+    rv = SSL_ConfigSecureServer(ssl_fd.get(), identity_->cert().get(),
                                 identity_->privkey(),
                                 identity_->auth_type());
     if (rv != SECSuccess) {
       MOZ_MTLOG(ML_ERROR, "Couldn't set identity");
       return false;
     }
 
     // Insist on a certificate from the client
-    rv = SSL_OptionSet(ssl_fd, SSL_REQUEST_CERTIFICATE, PR_TRUE);
+    rv = SSL_OptionSet(ssl_fd.get(), SSL_REQUEST_CERTIFICATE, PR_TRUE);
     if (rv != SECSuccess) {
       MOZ_MTLOG(ML_ERROR, "Couldn't request certificate");
       return false;
     }
 
-    rv = SSL_OptionSet(ssl_fd, SSL_REQUIRE_CERTIFICATE, PR_TRUE);
+    rv = SSL_OptionSet(ssl_fd.get(), SSL_REQUIRE_CERTIFICATE, PR_TRUE);
     if (rv != SECSuccess) {
       MOZ_MTLOG(ML_ERROR, "Couldn't require certificate");
       return false;
     }
   }
 
   // Require TLS 1.1 or 1.2. Perhaps some day in the future we will allow TLS
   // 1.0 for stream modes.
   SSLVersionRange version_range = {
     SSL_LIBRARY_VERSION_TLS_1_1,
     SSL_LIBRARY_VERSION_TLS_1_2
   };
 
-  rv = SSL_VersionRangeSet(ssl_fd, &version_range);
+  rv = SSL_VersionRangeSet(ssl_fd.get(), &version_range);
   if (rv != SECSuccess) {
     MOZ_MTLOG(ML_ERROR, "Can't disable SSLv3");
     return false;
   }
 
-  rv = SSL_OptionSet(ssl_fd, SSL_ENABLE_SESSION_TICKETS, PR_FALSE);
+  rv = SSL_OptionSet(ssl_fd.get(), SSL_ENABLE_SESSION_TICKETS, PR_FALSE);
   if (rv != SECSuccess) {
     MOZ_MTLOG(ML_ERROR, "Couldn't disable session tickets");
     return false;
   }
 
-  rv = SSL_OptionSet(ssl_fd, SSL_NO_CACHE, PR_TRUE);
+  rv = SSL_OptionSet(ssl_fd.get(), SSL_NO_CACHE, PR_TRUE);
   if (rv != SECSuccess) {
     MOZ_MTLOG(ML_ERROR, "Couldn't disable session caching");
     return false;
   }
 
-  rv = SSL_OptionSet(ssl_fd, SSL_ENABLE_DEFLATE, PR_FALSE);
+  rv = SSL_OptionSet(ssl_fd.get(), SSL_ENABLE_DEFLATE, PR_FALSE);
   if (rv != SECSuccess) {
     MOZ_MTLOG(ML_ERROR, "Couldn't disable deflate");
     return false;
   }
 
-  rv = SSL_OptionSet(ssl_fd, SSL_ENABLE_RENEGOTIATION, SSL_RENEGOTIATE_NEVER);
+  rv = SSL_OptionSet(ssl_fd.get(), SSL_ENABLE_RENEGOTIATION,
+                     SSL_RENEGOTIATE_NEVER);
   if (rv != SECSuccess) {
     MOZ_MTLOG(ML_ERROR, "Couldn't disable renegotiation");
     return false;
   }
 
-  rv = SSL_OptionSet(ssl_fd, SSL_ENABLE_FALSE_START, PR_FALSE);
+  rv = SSL_OptionSet(ssl_fd.get(), SSL_ENABLE_FALSE_START, PR_FALSE);
   if (rv != SECSuccess) {
     MOZ_MTLOG(ML_ERROR, "Couldn't disable false start");
     return false;
   }
 
-  rv = SSL_OptionSet(ssl_fd, SSL_NO_LOCKS, PR_TRUE);
+  rv = SSL_OptionSet(ssl_fd.get(), SSL_NO_LOCKS, PR_TRUE);
   if (rv != SECSuccess) {
     MOZ_MTLOG(ML_ERROR, "Couldn't disable locks");
     return false;
   }
 
-  rv = SSL_OptionSet(ssl_fd, SSL_REUSE_SERVER_ECDHE_KEY, PR_FALSE);
+  rv = SSL_OptionSet(ssl_fd.get(), SSL_REUSE_SERVER_ECDHE_KEY, PR_FALSE);
   if (rv != SECSuccess) {
     MOZ_MTLOG(ML_ERROR, "Couldn't disable ECDHE key reuse");
     return false;
   }
 
   if (!SetupCipherSuites(ssl_fd)) {
     return false;
   }
 
-  rv = SSL_NamedGroupConfig(ssl_fd, NamedGroupPreferences,
+  rv = SSL_NamedGroupConfig(ssl_fd.get(), NamedGroupPreferences,
                             mozilla::ArrayLength(NamedGroupPreferences));
   if (rv != SECSuccess) {
     MOZ_MTLOG(ML_ERROR, "Couldn't set named groups");
     return false;
   }
 
   // Certificate validation
-  rv = SSL_AuthCertificateHook(ssl_fd, AuthCertificateHook,
+  rv = SSL_AuthCertificateHook(ssl_fd.get(), AuthCertificateHook,
                                reinterpret_cast<void *>(this));
   if (rv != SECSuccess) {
     MOZ_MTLOG(ML_ERROR, "Couldn't set certificate validation hook");
     return false;
   }
 
   if (!SetupAlpn(ssl_fd)) {
     return false;
   }
 
   // Now start the handshake
-  rv = SSL_ResetHandshake(ssl_fd, role_ == SERVER ? PR_TRUE : PR_FALSE);
+  rv = SSL_ResetHandshake(ssl_fd.get(), role_ == SERVER ? PR_TRUE : PR_FALSE);
   if (rv != SECSuccess) {
     MOZ_MTLOG(ML_ERROR, "Couldn't reset handshake");
     return false;
   }
-  ssl_fd_ = ssl_fd.forget();
+  ssl_fd_ = Move(ssl_fd);
 
   // Finally, get ready to receive data
   downward_->SignalStateChange.connect(this, &TransportLayerDtls::StateChange);
   downward_->SignalPacketReceived.connect(this, &TransportLayerDtls::PacketReceived);
 
   if (downward_->state() == TS_OPEN) {
     TL_SET_STATE(TS_CONNECTING);
     Handshake();
   }
 
   return true;
 }
 
-bool TransportLayerDtls::SetupAlpn(PRFileDesc* ssl_fd) const {
+bool TransportLayerDtls::SetupAlpn(UniquePRFileDesc& ssl_fd) const {
   if (alpn_allowed_.empty()) {
     return true;
   }
 
-  SECStatus rv = SSL_OptionSet(ssl_fd, SSL_ENABLE_NPN, PR_FALSE);
+  SECStatus rv = SSL_OptionSet(ssl_fd.get(), SSL_ENABLE_NPN, PR_FALSE);
   if (rv != SECSuccess) {
     MOZ_MTLOG(ML_ERROR, "Couldn't disable NPN");
     return false;
   }
 
-  rv = SSL_OptionSet(ssl_fd, SSL_ENABLE_ALPN, PR_TRUE);
+  rv = SSL_OptionSet(ssl_fd.get(), SSL_ENABLE_ALPN, PR_TRUE);
   if (rv != SECSuccess) {
     MOZ_MTLOG(ML_ERROR, "Couldn't enable ALPN");
     return false;
   }
 
   unsigned char buf[MAX_ALPN_LENGTH];
   size_t offset = 0;
   for (auto tag = alpn_allowed_.begin();
@@ -659,17 +662,17 @@ bool TransportLayerDtls::SetupAlpn(PRFil
     if ((offset + 1 + tag->length()) >= sizeof(buf)) {
       MOZ_MTLOG(ML_ERROR, "ALPN too long");
       return false;
     }
     buf[offset++] = tag->length();
     memcpy(buf + offset, tag->c_str(), tag->length());
     offset += tag->length();
   }
-  rv = SSL_SetNextProtoNego(ssl_fd, buf, offset);
+  rv = SSL_SetNextProtoNego(ssl_fd.get(), buf, offset);
   if (rv != SECSuccess) {
     MOZ_MTLOG(ML_ERROR, "Couldn't set ALPN string");
     return false;
   }
   return true;
 }
 
 // Ciphers we need to enable.  These are on by default in standard firefox
@@ -736,52 +739,52 @@ static const uint32_t DisabledCiphers[] 
   TLS_ECDHE_RSA_WITH_NULL_SHA,
   TLS_ECDH_ECDSA_WITH_NULL_SHA,
   TLS_ECDH_RSA_WITH_NULL_SHA,
   TLS_RSA_WITH_NULL_SHA,
   TLS_RSA_WITH_NULL_SHA256,
   TLS_RSA_WITH_NULL_MD5,
 };
 
-bool TransportLayerDtls::SetupCipherSuites(PRFileDesc* ssl_fd) const {
+bool TransportLayerDtls::SetupCipherSuites(UniquePRFileDesc& ssl_fd) const {
   SECStatus rv;
 
   // Set the SRTP ciphers
   if (!srtp_ciphers_.empty()) {
     // Note: std::vector is guaranteed to contiguous
-    rv = SSL_SetSRTPCiphers(ssl_fd, &srtp_ciphers_[0], srtp_ciphers_.size());
-
+    rv = SSL_SetSRTPCiphers(ssl_fd.get(), &srtp_ciphers_[0],
+                            srtp_ciphers_.size());
     if (rv != SECSuccess) {
       MOZ_MTLOG(ML_ERROR, "Couldn't set SRTP cipher suite");
       return false;
     }
   }
 
   for (const auto& cipher : EnabledCiphers) {
     MOZ_MTLOG(ML_DEBUG, LAYER_INFO << "Enabling: " << cipher);
-    rv = SSL_CipherPrefSet(ssl_fd, cipher, PR_TRUE);
+    rv = SSL_CipherPrefSet(ssl_fd.get(), cipher, PR_TRUE);
     if (rv != SECSuccess) {
       MOZ_MTLOG(ML_ERROR, LAYER_INFO <<
                 "Unable to enable suite: " << cipher);
       return false;
     }
   }
 
   for (const auto& cipher : DisabledCiphers) {
     MOZ_MTLOG(ML_DEBUG, LAYER_INFO << "Disabling: " << cipher);
 
     PRBool enabled = false;
-    rv = SSL_CipherPrefGet(ssl_fd, cipher, &enabled);
+    rv = SSL_CipherPrefGet(ssl_fd.get(), cipher, &enabled);
     if (rv != SECSuccess) {
       MOZ_MTLOG(ML_NOTICE, LAYER_INFO <<
                 "Unable to check if suite is enabled: " << cipher);
       return false;
     }
     if (enabled) {
-      rv = SSL_CipherPrefSet(ssl_fd, cipher, PR_FALSE);
+      rv = SSL_CipherPrefSet(ssl_fd.get(), cipher, PR_FALSE);
       if (rv != SECSuccess) {
         MOZ_MTLOG(ML_NOTICE, LAYER_INFO <<
                   "Unable to disable suite: " << cipher);
         return false;
       }
     }
   }
 
@@ -793,17 +796,17 @@ nsresult TransportLayerDtls::GetCipherSu
   if (!cipherSuite) {
     MOZ_MTLOG(ML_ERROR, LAYER_INFO << "GetCipherSuite passed a nullptr");
     return NS_ERROR_NULL_POINTER;
   }
   if (state_ != TS_OPEN) {
     return NS_ERROR_NOT_AVAILABLE;
   }
   SSLChannelInfo info;
-  SECStatus rv = SSL_GetChannelInfo(ssl_fd_, &info, sizeof(info));
+  SECStatus rv = SSL_GetChannelInfo(ssl_fd_.get(), &info, sizeof(info));
   if (rv != SECSuccess) {
     MOZ_MTLOG(ML_NOTICE, LAYER_INFO << "GetCipherSuite can't get channel info");
     return NS_ERROR_FAILURE;
   }
   *cipherSuite = info.cipherSuite;
   return NS_OK;
 }
 
@@ -854,17 +857,17 @@ void TransportLayerDtls::StateChange(Tra
       break;
   }
 }
 
 void TransportLayerDtls::Handshake() {
   // Clear the retransmit timer
   timer_->Cancel();
 
-  SECStatus rv = SSL_ForceHandshake(ssl_fd_);
+  SECStatus rv = SSL_ForceHandshake(ssl_fd_.get());
 
   if (rv == SECSuccess) {
     MOZ_MTLOG(ML_NOTICE,
               LAYER_INFO << "****** SSL handshake completed ******");
     if (!cert_ok_) {
       MOZ_MTLOG(ML_ERROR, LAYER_INFO << "Certificate check never occurred");
       TL_SET_STATE(TS_ERROR);
       return;
@@ -885,17 +888,17 @@ void TransportLayerDtls::Handshake() {
       case SSL_ERROR_RX_MALFORMED_HANDSHAKE:
         MOZ_MTLOG(ML_ERROR, LAYER_INFO << "Malformed DTLS message; ignoring");
         // If this were TLS (and not DTLS), this would be fatal, but
         // here we're required to ignore bad messages, so fall through
         MOZ_FALLTHROUGH;
       case PR_WOULD_BLOCK_ERROR:
         MOZ_MTLOG(ML_NOTICE, LAYER_INFO << "Handshake would have blocked");
         PRIntervalTime timeout;
-        rv = DTLS_GetHandshakeTimeout(ssl_fd_, &timeout);
+        rv = DTLS_GetHandshakeTimeout(ssl_fd_.get(), &timeout);
         if (rv == SECSuccess) {
           uint32_t timeout_ms = PR_IntervalToMilliseconds(timeout);
 
           MOZ_MTLOG(ML_DEBUG,
                     LAYER_INFO << "Setting DTLS timeout to " << timeout_ms);
           timer_->SetTarget(target_);
           timer_->InitWithFuncCallback(TimerCallback,
                                        this, timeout_ms,
@@ -918,17 +921,17 @@ void TransportLayerDtls::Handshake() {
 bool TransportLayerDtls::CheckAlpn() {
   if (alpn_allowed_.empty()) {
     return true;
   }
 
   SSLNextProtoState alpnState;
   char chosenAlpn[MAX_ALPN_LENGTH];
   unsigned int chosenAlpnLen;
-  SECStatus rv = SSL_GetNextProto(ssl_fd_, &alpnState,
+  SECStatus rv = SSL_GetNextProto(ssl_fd_.get(), &alpnState,
                                   reinterpret_cast<unsigned char*>(chosenAlpn),
                                   &chosenAlpnLen, sizeof(chosenAlpn));
   if (rv != SECSuccess) {
     MOZ_MTLOG(ML_ERROR, LAYER_INFO << "ALPN error");
     return false;
   }
   switch (alpnState) {
     case SSL_NEXT_PROTO_SELECTED:
@@ -997,17 +1000,17 @@ void TransportLayerDtls::PacketReceived(
 
   // Now try a recv if we're open, since there might be data left
   if (state_ == TS_OPEN) {
     // nICEr uses a 9216 bytes buffer to allow support for jumbo frames
     unsigned char buf[9216];
     int32_t rv;
     // One packet might contain several DTLS packets
     do {
-      rv = PR_Recv(ssl_fd_, buf, sizeof(buf), 0, PR_INTERVAL_NO_WAIT);
+      rv = PR_Recv(ssl_fd_.get(), buf, sizeof(buf), 0, PR_INTERVAL_NO_WAIT);
       if (rv > 0) {
         // We have data
         MOZ_MTLOG(ML_DEBUG, LAYER_INFO << "Read " << rv << " bytes from NSS");
         SignalPacketReceived(this, buf, rv);
       } else if (rv == 0) {
         TL_SET_STATE(TS_CLOSED);
       } else {
         int32_t err = PR_GetError();
@@ -1028,17 +1031,17 @@ TransportResult TransportLayerDtls::Send
                                                size_t len) {
   CheckThread();
   if (state_ != TS_OPEN) {
     MOZ_MTLOG(ML_ERROR, LAYER_INFO << "Can't call SendPacket() in state "
               << state_);
     return TE_ERROR;
   }
 
-  int32_t rv = PR_Send(ssl_fd_, data, len, 0, PR_INTERVAL_NO_WAIT);
+  int32_t rv = PR_Send(ssl_fd_.get(), data, len, 0, PR_INTERVAL_NO_WAIT);
 
   if (rv > 0) {
     // We have data
     MOZ_MTLOG(ML_DEBUG, LAYER_INFO << "Wrote " << rv << " bytes to SSL Layer");
     return rv;
   }
 
   if (rv == 0) {
@@ -1069,17 +1072,17 @@ SECStatus TransportLayerDtls::GetClientA
   stream->CheckThread();
 
   if (!stream->identity_) {
     MOZ_MTLOG(ML_ERROR, "No identity available");
     PR_SetError(SSL_ERROR_NO_CERTIFICATE, 0);
     return SECFailure;
   }
 
-  *pRetCert = CERT_DupCertificate(stream->identity_->cert());
+  *pRetCert = CERT_DupCertificate(stream->identity_->cert().get());
   if (!*pRetCert) {
     PR_SetError(PR_OUT_OF_MEMORY_ERROR, 0);
     return SECFailure;
   }
 
   *pRetKey = SECKEY_CopyPrivateKey(stream->identity_->privkey());
   if (!*pRetKey) {
     CERT_DestroyCertificate(*pRetCert);
@@ -1098,17 +1101,17 @@ nsresult TransportLayerDtls::SetSrtpCiph
   return NS_OK;
 }
 
 nsresult TransportLayerDtls::GetSrtpCipher(uint16_t *cipher) const {
   CheckThread();
   if (state_ != TS_OPEN) {
     return NS_ERROR_NOT_AVAILABLE;
   }
-  SECStatus rv = SSL_GetSRTPCipher(ssl_fd_, cipher);
+  SECStatus rv = SSL_GetSRTPCipher(ssl_fd_.get(), cipher);
   if (rv != SECSuccess) {
     MOZ_MTLOG(ML_DEBUG, "No SRTP cipher negotiated");
     return NS_ERROR_FAILURE;
   }
 
   return NS_OK;
 }
 
@@ -1117,17 +1120,17 @@ nsresult TransportLayerDtls::ExportKeyin
                                                   const std::string& context,
                                                   unsigned char *out,
                                                   unsigned int outlen) {
   CheckThread();
   if (state_ != TS_OPEN) {
     MOZ_ASSERT(false, "Transport must be open for ExportKeyingMaterial");
     return NS_ERROR_NOT_AVAILABLE;
   }
-  SECStatus rv = SSL_ExportKeyingMaterial(ssl_fd_,
+  SECStatus rv = SSL_ExportKeyingMaterial(ssl_fd_.get(),
                                           label.c_str(),
                                           label.size(),
                                           use_context,
                                           reinterpret_cast<const unsigned char *>(
                                               context.c_str()),
                                           context.size(),
                                           out,
                                           outlen);
@@ -1144,19 +1147,18 @@ SECStatus TransportLayerDtls::AuthCertif
                                                   PRBool checksig,
                                                   PRBool isServer) {
   TransportLayerDtls *stream = reinterpret_cast<TransportLayerDtls *>(arg);
   stream->CheckThread();
   return stream->AuthCertificateHook(fd, checksig, isServer);
 }
 
 SECStatus
-TransportLayerDtls::CheckDigest(const RefPtr<VerificationDigest>&
-                                digest,
-                                CERTCertificate *peer_cert) {
+TransportLayerDtls::CheckDigest(const RefPtr<VerificationDigest>& digest,
+                                UniqueCERTCertificate& peer_cert) const {
   unsigned char computed_digest[kMaxDigestLength];
   size_t computed_digest_len;
 
   MOZ_MTLOG(ML_DEBUG, LAYER_INFO << "Checking digest, algorithm="
             << digest->algorithm_);
   nsresult res =
       DtlsIdentity::ComputeFingerprint(peer_cert,
                                        digest->algorithm_,
@@ -1188,40 +1190,36 @@ TransportLayerDtls::CheckDigest(const Re
   return SECSuccess;
 }
 
 
 SECStatus TransportLayerDtls::AuthCertificateHook(PRFileDesc *fd,
                                                   PRBool checksig,
                                                   PRBool isServer) {
   CheckThread();
-  ScopedCERTCertificate peer_cert;
-  peer_cert = SSL_PeerCertificate(fd);
-
+  UniqueCERTCertificate peer_cert(SSL_PeerCertificate(fd));
 
   // We are not set up to take this being called multiple
   // times. Change this if we ever add renegotiation.
   MOZ_ASSERT(!auth_hook_called_);
   if (auth_hook_called_) {
     PR_SetError(PR_UNKNOWN_ERROR, 0);
     return SECFailure;
   }
   auth_hook_called_ = true;
 
   MOZ_ASSERT(verification_mode_ != VERIFY_UNSET);
-  MOZ_ASSERT(peer_cert_ == nullptr);
 
   switch (verification_mode_) {
     case VERIFY_UNSET:
       // Break out to error exit
       PR_SetError(PR_UNKNOWN_ERROR, 0);
       break;
 
     case VERIFY_ALLOW_ALL:
-      peer_cert_ = peer_cert.forget();
       cert_ok_ = true;
       return SECSuccess;
 
     case VERIFY_DIGEST:
       {
         MOZ_ASSERT(digests_.size() != 0);
         // Check all the provided digests
 
@@ -1229,17 +1227,16 @@ SECStatus TransportLayerDtls::AuthCertif
         SECStatus rv = SECFailure;
         for (size_t i = 0; i < digests_.size(); i++) {
           RefPtr<VerificationDigest> digest = digests_[i];
           rv = CheckDigest(digest, peer_cert);
 
           // Matches a digest, we are good to go
           if (rv == SECSuccess) {
             cert_ok_ = true;
-            peer_cert = peer_cert.forget();
             return SECSuccess;
           }
         }
       }
       break;
     default:
       MOZ_CRASH();  // Can't happen
   }
--- a/media/mtransport/transportlayerdtls.h
+++ b/media/mtransport/transportlayerdtls.h
@@ -86,32 +86,28 @@ class TransportLayerDtls final : public 
   nsresult GetSrtpCipher(uint16_t *cipher) const;
 
   nsresult ExportKeyingMaterial(const std::string& label,
                                 bool use_context,
                                 const std::string& context,
                                 unsigned char *out,
                                 unsigned int outlen);
 
-  const CERTCertificate *GetPeerCert() const {
-    return peer_cert_;
-  }
-
   // Transport layer overrides.
   virtual nsresult InitInternal();
   virtual void WasInserted();
   virtual TransportResult SendPacket(const unsigned char *data, size_t len);
 
   // Signals
   void StateChange(TransportLayer *layer, State state);
   void PacketReceived(TransportLayer* layer, const unsigned char *data,
                       size_t len);
 
   // For testing use only.  Returns the fd.
-  PRFileDesc* internal_fd() { CheckThread(); return ssl_fd_.rwget(); }
+  PRFileDesc* internal_fd() { CheckThread(); return ssl_fd_.get(); }
 
   TRANSPORT_LAYER_ID("dtls")
 
   private:
   DISALLOW_COPY_ASSIGN(TransportLayerDtls);
 
   // A single digest to check
   class VerificationDigest {
@@ -133,18 +129,18 @@ class TransportLayerDtls final : public 
 
    private:
     ~VerificationDigest() {}
     DISALLOW_COPY_ASSIGN(VerificationDigest);
   };
 
 
   bool Setup();
-  bool SetupCipherSuites(PRFileDesc* ssl_fd) const;
-  bool SetupAlpn(PRFileDesc* ssl_fd) const;
+  bool SetupCipherSuites(UniquePRFileDesc& ssl_fd) const;
+  bool SetupAlpn(UniquePRFileDesc& ssl_fd) const;
   void Handshake();
 
   bool CheckAlpn();
 
   static SECStatus GetClientAuthDataHook(void *arg, PRFileDesc *fd,
                                          CERTDistNames *caNames,
                                          CERTCertificate **pRetCert,
                                          SECKEYPrivateKey **pRetKey);
@@ -154,17 +150,17 @@ class TransportLayerDtls final : public 
                                        PRBool isServer);
   SECStatus AuthCertificateHook(PRFileDesc *fd,
                                 PRBool checksig,
                                 PRBool isServer);
 
   static void TimerCallback(nsITimer *timer, void *arg);
 
   SECStatus CheckDigest(const RefPtr<VerificationDigest>& digest,
-                        CERTCertificate *cert);
+                        UniqueCERTCertificate& cert) const;
 
   RefPtr<DtlsIdentity> identity_;
   // What ALPN identifiers are permitted.
   std::set<std::string> alpn_allowed_;
   // What ALPN identifier is used if ALPN is not supported.
   // The empty string indicates that ALPN is required.
   std::string alpn_default_;
   // What ALPN string was negotiated.
@@ -173,19 +169,18 @@ class TransportLayerDtls final : public 
 
   Role role_;
   Verification verification_mode_;
   std::vector<RefPtr<VerificationDigest> > digests_;
 
   // Must delete nspr_io_adapter after ssl_fd_ b/c ssl_fd_ causes an alert
   // (ssl_fd_ contains an un-owning pointer to nspr_io_adapter_)
   UniquePtr<TransportLayerNSPRAdapter> nspr_io_adapter_;
-  ScopedPRFileDesc ssl_fd_;
+  UniquePRFileDesc ssl_fd_;
 
-  ScopedCERTCertificate peer_cert_;
   nsCOMPtr<nsITimer> timer_;
   bool auth_hook_called_;
   bool cert_ok_;
 };
 
 
 }  // close namespace
 #endif
--- a/media/webrtc/signaling/src/peerconnection/PeerConnectionImpl.cpp
+++ b/media/webrtc/signaling/src/peerconnection/PeerConnectionImpl.cpp
@@ -2833,23 +2833,22 @@ PeerConnectionImpl::GetParameters(
 }
 
 nsresult
 PeerConnectionImpl::CalculateFingerprint(
     const std::string& algorithm,
     std::vector<uint8_t>* fingerprint) const {
   uint8_t buf[DtlsIdentity::HASH_ALGORITHM_MAX_LENGTH];
   size_t len = 0;
-  CERTCertificate* cert;
 
   MOZ_ASSERT(fingerprint);
 #if !defined(MOZILLA_EXTERNAL_LINKAGE)
-  cert = mCertificate->Certificate();
+  const UniqueCERTCertificate& cert = mCertificate->Certificate();
 #else
-  cert = mIdentity->cert();
+  const UniqueCERTCertificate& cert = mIdentity->cert();
 #endif
   nsresult rv = DtlsIdentity::ComputeFingerprint(cert, algorithm,
                                                  &buf[0], sizeof(buf),
                                                  &len);
   if (NS_FAILED(rv)) {
     CSFLogError(logTag, "Unable to calculate certificate fingerprint, rv=%u",
                         static_cast<unsigned>(rv));
     return rv;
--- a/media/webrtc/signaling/src/sdp/sipcc/sdp_attr.c
+++ b/media/webrtc/signaling/src/sdp/sipcc/sdp_attr.c
@@ -531,16 +531,73 @@ static sdp_result_e sdp_verify_attr_fmtp
  *        single number or a range separated by a '-'.
  *        Example:  fmtp:101 1,3-15,20
  * Video codecs have annexes that can be listed in the following legal formats:
  * a) a=fmtp:34 param1=token;D;I;J;K=1;N=2;P=1,3
  * b) a=fmtp:34 param1=token;D;I;J;K=1;N=2;P=1,3;T
  * c) a=fmtp:34 param1=token;D;I;J
  *
  */
+sdp_result_e sdp_get_fmtp_tok(sdp_t *sdp_p,
+                              const char** fmtp_ptr,
+                              const char* fmtp_name,
+                              char* buf,
+                              size_t buf_size,
+                              char** tok)
+{
+    sdp_result_e result1 = SDP_SUCCESS;
+
+    *fmtp_ptr = sdp_getnextstrtok(*fmtp_ptr, buf, buf_size, "; \t", &result1);
+    if (result1 != SDP_SUCCESS) {
+        *fmtp_ptr = sdp_getnextstrtok(*fmtp_ptr, buf, buf_size, " \t", &result1);
+        if (result1 != SDP_SUCCESS) {
+            sdp_attr_fmtp_no_value(sdp_p, fmtp_name);
+            return SDP_INVALID_PARAMETER;
+        }
+    }
+    *tok = buf;
+    (*tok)++;
+
+    return SDP_SUCCESS;
+}
+
+sdp_result_e sdp_get_fmtp_tok_val(sdp_t *sdp_p,
+                              const char** fmtp_ptr,
+                              const char* fmtp_name,
+                              char* buf,
+                              size_t buf_size,
+                              char** tok,
+                              unsigned long* strtoul_result,
+                              unsigned long illegal_value,
+                              unsigned long min_limit,
+                              unsigned long max_limit)
+{
+  sdp_result_e result1 = SDP_SUCCESS;
+  unsigned long value;
+  char* strtoul_end;
+
+  result1 = sdp_get_fmtp_tok(sdp_p, fmtp_ptr, fmtp_name, buf, buf_size, tok);
+  if (result1 != SDP_SUCCESS) return result1;
+
+  errno = 0;
+  value = strtoul(*tok, &strtoul_end, 10);
+
+  if (errno
+      || (*tok == strtoul_end)
+      || (illegal_value != -1UL && value == illegal_value)
+      || (min_limit != -1UL && value < min_limit)
+      || (max_limit != -1UL && value > max_limit)) {
+    sdp_attr_fmtp_invalid_value(sdp_p, fmtp_name, *tok);
+    return SDP_INVALID_PARAMETER;
+  }
+  *strtoul_result = value;
+
+  return SDP_SUCCESS;
+}
+
 
 sdp_result_e sdp_parse_attr_fmtp (sdp_t *sdp_p, sdp_attr_t *attr_p,
                                   const char *ptr)
 {
     uint16_t           i;
     uint32_t           mapword;
     uint32_t           bmap;
     uint8_t            low_val;
@@ -598,27 +655,19 @@ sdp_result_e sdp_parse_attr_fmtp (sdp_t 
     fmtp_ptr = src_ptr = temp_ptr;
 
     src_ptr = temp_ptr;
     while (!done) {
       fmtp_ptr = sdp_getnextstrtok(fmtp_ptr, tmp, sizeof(tmp), "= \t", &result1);
       if (result1 == SDP_SUCCESS) {
         if (cpr_strncasecmp(tmp, sdp_fmtp_codec_param[1].name,
                         sdp_fmtp_codec_param[1].strlen) == 0) {
-            fmtp_ptr = sdp_getnextstrtok(fmtp_ptr, tmp, sizeof(tmp), "; \t", &result1);
-            if (result1 != SDP_SUCCESS) {
-                fmtp_ptr  = sdp_getnextstrtok(fmtp_ptr, tmp, sizeof(tmp), " \t", &result1);
-                if (result1 != SDP_SUCCESS) {
-                    sdp_attr_fmtp_no_value(sdp_p, "annexb");
-                    SDP_FREE(temp_ptr);
-                    return SDP_INVALID_PARAMETER;
-                }
-            }
-            tok = tmp;
-            tok++;
+            result1 = sdp_get_fmtp_tok(sdp_p, &fmtp_ptr, "annexb", tmp, sizeof(tmp), &tok);
+            if (result1 != SDP_SUCCESS) { SDP_FREE(temp_ptr); return result1; }
+
             if (cpr_strncasecmp(tok,sdp_fmtp_codec_param_val[0].name,
                             sdp_fmtp_codec_param_val[0].strlen) == 0) {
                 fmtp_p->fmtp_format = SDP_FMTP_CODEC_INFO;
                 fmtp_p->annexb_required = TRUE;
                 fmtp_p->annexb = TRUE;
             } else if (cpr_strncasecmp(tok,sdp_fmtp_codec_param_val[1].name,
                                    sdp_fmtp_codec_param_val[1].strlen) == 0) {
                 fmtp_p->fmtp_format = SDP_FMTP_CODEC_INFO;
@@ -628,28 +677,19 @@ sdp_result_e sdp_parse_attr_fmtp (sdp_t 
                 sdp_attr_fmtp_invalid_value(sdp_p, "annexb", tok);
                 SDP_FREE(temp_ptr);
                 return SDP_INVALID_PARAMETER;
             }
             codec_info_found = TRUE;
 
         } else if (cpr_strncasecmp(tmp, sdp_fmtp_codec_param[0].name,
                                sdp_fmtp_codec_param[0].strlen) == 0) {
-
-            fmtp_ptr = sdp_getnextstrtok(fmtp_ptr, tmp, sizeof(tmp), "; \t", &result1);
-            if (result1 != SDP_SUCCESS) {
-                fmtp_ptr = sdp_getnextstrtok(fmtp_ptr, tmp, sizeof(tmp), " \t", &result1);
-                if (result1 != SDP_SUCCESS) {
-                    sdp_attr_fmtp_no_value(sdp_p, "annexa");
-                    SDP_FREE(temp_ptr);
-                    return SDP_INVALID_PARAMETER;
-                }
-            }
-            tok = tmp;
-            tok++;
+            result1 = sdp_get_fmtp_tok(sdp_p, &fmtp_ptr, "annexa", tmp, sizeof(tmp), &tok);
+            if (result1 != SDP_SUCCESS) { SDP_FREE(temp_ptr); return result1; }
+
             if (cpr_strncasecmp(tok,sdp_fmtp_codec_param_val[0].name,
                             sdp_fmtp_codec_param_val[0].strlen) == 0) {
                 fmtp_p->fmtp_format = SDP_FMTP_CODEC_INFO;
                 fmtp_p->annexa = TRUE;
                 fmtp_p->annexa_required = TRUE;
             } else if (cpr_strncasecmp(tok,sdp_fmtp_codec_param_val[1].name,
                                    sdp_fmtp_codec_param_val[1].strlen) == 0) {
                 fmtp_p->fmtp_format = SDP_FMTP_CODEC_INFO;
@@ -659,243 +699,100 @@ sdp_result_e sdp_parse_attr_fmtp (sdp_t 
                 sdp_attr_fmtp_invalid_value(sdp_p, "annexa", tok);
                 SDP_FREE(temp_ptr);
                 return SDP_INVALID_PARAMETER;
             }
             codec_info_found = TRUE;
 
         } else if (cpr_strncasecmp(tmp,sdp_fmtp_codec_param[2].name,
                                sdp_fmtp_codec_param[2].strlen) == 0) {
-            fmtp_ptr = sdp_getnextstrtok(fmtp_ptr, tmp, sizeof(tmp), "; \t", &result1);
-            if (result1 != SDP_SUCCESS) {
-                fmtp_ptr = sdp_getnextstrtok(fmtp_ptr, tmp, sizeof(tmp), " \t", &result1);
-                if (result1 != SDP_SUCCESS) {
-                    sdp_attr_fmtp_no_value(sdp_p, "bitrate");
-                    SDP_FREE(temp_ptr);
-                    return SDP_INVALID_PARAMETER;
-                }
-            }
-            tok = tmp;
-            tok++;
-
-            errno = 0;
-            strtoul_result = strtoul(tok, &strtoul_end, 10);
-
-            if (errno || tok == strtoul_end || strtoul_result == 0 || strtoul_result > UINT_MAX) {
-                sdp_attr_fmtp_invalid_value(sdp_p, "bitrate", tok);
-                SDP_FREE(temp_ptr);
-                return SDP_INVALID_PARAMETER;
-            }
+            result1 = sdp_get_fmtp_tok_val(sdp_p, &fmtp_ptr, "bitrate", tmp, sizeof(tmp),
+                                           &tok, &strtoul_result, 0, -1, UINT_MAX);
+            if (result1 != SDP_SUCCESS) { SDP_FREE(temp_ptr); return result1; }
 
             fmtp_p->fmtp_format = SDP_FMTP_CODEC_INFO;
             fmtp_p->bitrate = (uint32_t) strtoul_result;
             codec_info_found = TRUE;
 
          } else if (cpr_strncasecmp(tmp,sdp_fmtp_codec_param[41].name,
                                sdp_fmtp_codec_param[41].strlen) == 0) {
-            fmtp_ptr = sdp_getnextstrtok(fmtp_ptr, tmp, sizeof(tmp), "; \t", &result1);
-            if (result1 != SDP_SUCCESS) {
-                fmtp_ptr = sdp_getnextstrtok(fmtp_ptr, tmp, sizeof(tmp), " \t", &result1);
-                if (result1 != SDP_SUCCESS) {
-                    sdp_attr_fmtp_no_value(sdp_p, "mode");
-                    SDP_FREE(temp_ptr);
-                    return SDP_INVALID_PARAMETER;
-                }
-            }
-            tok = tmp;
-            tok++;
-
-            errno = 0;
-            strtoul_result = strtoul(tok, &strtoul_end, 10);
-
-            if (errno || tok == strtoul_end || strtoul_result > UINT_MAX) {
-                sdp_attr_fmtp_invalid_value(sdp_p, "mode", tok);
-                SDP_FREE(temp_ptr);
-                return SDP_INVALID_PARAMETER;
-            }
+            result1 = sdp_get_fmtp_tok_val(sdp_p, &fmtp_ptr, "mode", tmp, sizeof(tmp),
+                                           &tok, &strtoul_result, -1, -1, UINT_MAX);
+            if (result1 != SDP_SUCCESS) { SDP_FREE(temp_ptr); return result1; }
 
             fmtp_p->fmtp_format = SDP_FMTP_MODE;
             fmtp_p->mode = (uint32_t) strtoul_result;
             codec_info_found = TRUE;
 
         } else if (cpr_strncasecmp(tmp,sdp_fmtp_codec_param[3].name,
                                sdp_fmtp_codec_param[3].strlen) == 0) {
-            fmtp_ptr = sdp_getnextstrtok(fmtp_ptr, tmp, sizeof(tmp), "; \t", &result1);
-            if (result1 != SDP_SUCCESS) {
-           fmtp_ptr = sdp_getnextstrtok(fmtp_ptr, tmp, sizeof(tmp), " \t", &result1);
-            if (result1 != SDP_SUCCESS) {
-                sdp_attr_fmtp_no_value(sdp_p, "qcif");
-                SDP_FREE(temp_ptr);
-                return SDP_INVALID_PARAMETER;
-            }
-        }
-        tok = tmp;
-        tok++;
-
-        errno = 0;
-        strtoul_result = strtoul(tok, &strtoul_end, 10);
-
-        if (errno || tok == strtoul_end ||
-            strtoul_result < SDP_MIN_CIF_VALUE || strtoul_result > SDP_MAX_CIF_VALUE) {
-            sdp_attr_fmtp_invalid_value(sdp_p, "qcif", tok);
-            SDP_FREE(temp_ptr);
-            return SDP_INVALID_PARAMETER;
-            }
+            result1 = sdp_get_fmtp_tok_val(sdp_p, &fmtp_ptr, "qcif", tmp, sizeof(tmp),
+                                           &tok, &strtoul_result, -1, SDP_MIN_CIF_VALUE, SDP_MAX_CIF_VALUE);
+            if (result1 != SDP_SUCCESS) { SDP_FREE(temp_ptr); return result1; }
 
             fmtp_p->fmtp_format = SDP_FMTP_CODEC_INFO;
             fmtp_p->qcif = (uint16_t) strtoul_result;
             codec_info_found = TRUE;
+
         } else if (cpr_strncasecmp(tmp,sdp_fmtp_codec_param[4].name,
                                sdp_fmtp_codec_param[4].strlen) == 0) {
-            fmtp_ptr = sdp_getnextstrtok(fmtp_ptr, tmp, sizeof(tmp), "; \t", &result1);
-            if (result1 != SDP_SUCCESS) {
-            fmtp_ptr = sdp_getnextstrtok(fmtp_ptr, tmp, sizeof(tmp), " \t", &result1);
-            if (result1 != SDP_SUCCESS) {
-                sdp_attr_fmtp_no_value(sdp_p, "cif");
-                SDP_FREE(temp_ptr);
-                return SDP_INVALID_PARAMETER;
-            }
-        }
-        tok = tmp;
-        tok++;
-
-        errno = 0;
-        strtoul_result = strtoul(tok, &strtoul_end, 10);
-
-        if (errno || tok == strtoul_end ||
-            strtoul_result < SDP_MIN_CIF_VALUE || strtoul_result > SDP_MAX_CIF_VALUE) {
-            sdp_attr_fmtp_invalid_value(sdp_p, "cif", tok);
-            SDP_FREE(temp_ptr);
-            return SDP_INVALID_PARAMETER;
-        }
+            result1 = sdp_get_fmtp_tok_val(sdp_p, &fmtp_ptr, "cif", tmp, sizeof(tmp),
+                                           &tok, &strtoul_result, -1, SDP_MIN_CIF_VALUE, SDP_MAX_CIF_VALUE);
+            if (result1 != SDP_SUCCESS) { SDP_FREE(temp_ptr); return result1; }
 
             fmtp_p->fmtp_format = SDP_FMTP_CODEC_INFO;
             fmtp_p->cif = (uint16_t) strtoul_result;
             codec_info_found = TRUE;
+
         } else if (cpr_strncasecmp(tmp,sdp_fmtp_codec_param[5].name,
                                sdp_fmtp_codec_param[5].strlen) == 0) {
-            fmtp_ptr = sdp_getnextstrtok(fmtp_ptr, tmp, sizeof(tmp), "; \t", &result1);
-            if (result1 != SDP_SUCCESS) {
-            fmtp_ptr = sdp_getnextstrtok(fmtp_ptr, tmp, sizeof(tmp), " \t", &result1);
-                if (result1 != SDP_SUCCESS) {
-                    sdp_attr_fmtp_no_value(sdp_p, "maxbr");
-                SDP_FREE(temp_ptr);
-                return SDP_INVALID_PARAMETER;
-            }
-        }
-        tok = tmp;
-            tok++;
-
-            errno = 0;
-            strtoul_result = strtoul(tok, &strtoul_end, 10);
-
-            if (errno || tok == strtoul_end ||
-                strtoul_result == 0 || strtoul_result > USHRT_MAX) {
-                sdp_attr_fmtp_invalid_value(sdp_p, "maxbr", tok);
-                SDP_FREE(temp_ptr);
-                return SDP_INVALID_PARAMETER;
-            }
+            result1 = sdp_get_fmtp_tok_val(sdp_p, &fmtp_ptr, "maxbr", tmp, sizeof(tmp),
+                                           &tok, &strtoul_result, 0, -1, USHRT_MAX);
+            if (result1 != SDP_SUCCESS) { SDP_FREE(temp_ptr); return result1; }
 
             fmtp_p->fmtp_format = SDP_FMTP_CODEC_INFO;
             fmtp_p->maxbr = (uint16_t) strtoul_result;
             codec_info_found = TRUE;
+
         } else if (cpr_strncasecmp(tmp,sdp_fmtp_codec_param[6].name,
                                sdp_fmtp_codec_param[6].strlen) == 0) {
-            fmtp_ptr = sdp_getnextstrtok(fmtp_ptr, tmp, sizeof(tmp), "; \t", &result1);
-            if (result1 != SDP_SUCCESS) {
-                fmtp_ptr = sdp_getnextstrtok(fmtp_ptr, tmp, sizeof(tmp), " \t", &result1);
-                if (result1 != SDP_SUCCESS) {
-                    sdp_attr_fmtp_no_value(sdp_p, "sqcif");
-                    SDP_FREE(temp_ptr);
-                    return SDP_INVALID_PARAMETER;
-                }
-            }
-            tok = tmp;
-            tok++;
-
-        errno = 0;
-        strtoul_result = strtoul(tok, &strtoul_end, 10);
-
-        if (errno || tok == strtoul_end ||
-            strtoul_result < SDP_MIN_CIF_VALUE || strtoul_result > SDP_MAX_CIF_VALUE) {
-            sdp_attr_fmtp_invalid_value(sdp_p, "sqcif", tok);
-            SDP_FREE(temp_ptr);
-            return SDP_INVALID_PARAMETER;
-        }
+            result1 = sdp_get_fmtp_tok_val(sdp_p, &fmtp_ptr, "sqcif", tmp, sizeof(tmp),
+                                           &tok, &strtoul_result, -1, SDP_MIN_CIF_VALUE, SDP_MAX_CIF_VALUE);
+            if (result1 != SDP_SUCCESS) { SDP_FREE(temp_ptr); return result1; }
 
             fmtp_p->fmtp_format = SDP_FMTP_CODEC_INFO;
             fmtp_p->sqcif = (uint16_t) strtoul_result;
             codec_info_found = TRUE;
+
         } else if (cpr_strncasecmp(tmp,sdp_fmtp_codec_param[7].name,
                                sdp_fmtp_codec_param[7].strlen) == 0) {
-            fmtp_ptr = sdp_getnextstrtok(fmtp_ptr, tmp, sizeof(tmp), "; \t", &result1);
-            if (result1 != SDP_SUCCESS) {
-                fmtp_ptr = sdp_getnextstrtok(fmtp_ptr, tmp, sizeof(tmp), " \t", &result1);
-                if (result1 != SDP_SUCCESS) {
-                    sdp_attr_fmtp_no_value(sdp_p, "cif4");
-                    SDP_FREE(temp_ptr);
-                    return SDP_INVALID_PARAMETER;
-                }
-            }
-            tok = tmp;
-            tok++;
-
-        errno = 0;
-        strtoul_result = strtoul(tok, &strtoul_end, 10);
-
-            if (errno || tok == strtoul_end ||
-                strtoul_result < SDP_MIN_CIF_VALUE || strtoul_result > SDP_MAX_CIF_VALUE) {
-                sdp_attr_fmtp_invalid_value(sdp_p, "cif4", tok);
-                SDP_FREE(temp_ptr);
-                return SDP_INVALID_PARAMETER;
-            }
+            result1 = sdp_get_fmtp_tok_val(sdp_p, &fmtp_ptr, "cif4", tmp, sizeof(tmp),
+                                           &tok, &strtoul_result, -1, SDP_MIN_CIF_VALUE, SDP_MAX_CIF_VALUE);
+            if (result1 != SDP_SUCCESS) { SDP_FREE(temp_ptr); return result1; }
 
             fmtp_p->fmtp_format = SDP_FMTP_CODEC_INFO;
             fmtp_p->cif4 = (uint16_t) strtoul_result;
             codec_info_found = TRUE;
+
         } else if (cpr_strncasecmp(tmp,sdp_fmtp_codec_param[8].name,
                                sdp_fmtp_codec_param[8].strlen) == 0) {
-            fmtp_ptr = sdp_getnextstrtok(fmtp_ptr, tmp, sizeof(tmp), "; \t", &result1);
-            if (result1 != SDP_SUCCESS) {
-                fmtp_ptr = sdp_getnextstrtok(fmtp_ptr, tmp, sizeof(tmp), " \t", &result1);
-                if (result1 != SDP_SUCCESS) {
-                    sdp_attr_fmtp_no_value(sdp_p, "cif16");
-                    SDP_FREE(temp_ptr);
-                    return SDP_INVALID_PARAMETER;
-                }
-            }
-            tok = tmp;
-            tok++;
-
-        errno = 0;
-        strtoul_result = strtoul(tok, &strtoul_end, 10);
-
-        if (errno || tok == strtoul_end ||
-            strtoul_result < SDP_MIN_CIF_VALUE || strtoul_result > SDP_MAX_CIF_VALUE) {
-            sdp_attr_fmtp_invalid_value(sdp_p, "cif16", tok);
-            SDP_FREE(temp_ptr);
-            return SDP_INVALID_PARAMETER;
-            }
+            result1 = sdp_get_fmtp_tok_val(sdp_p, &fmtp_ptr, "cif16", tmp, sizeof(tmp),
+                                           &tok, &strtoul_result, -1, SDP_MIN_CIF_VALUE, SDP_MAX_CIF_VALUE);
+            if (result1 != SDP_SUCCESS) { SDP_FREE(temp_ptr); return result1; }
 
             fmtp_p->fmtp_format = SDP_FMTP_CODEC_INFO;
             fmtp_p->cif16 = (uint16_t) strtoul_result;
             codec_info_found = TRUE;
+
         } else  if (cpr_strncasecmp(tmp,sdp_fmtp_codec_param[9].name,
                                sdp_fmtp_codec_param[9].strlen) == 0) {
-            fmtp_ptr = sdp_getnextstrtok(fmtp_ptr, tmp, sizeof(tmp), "; \t", &result1);
-            if (result1 != SDP_SUCCESS) {
-                fmtp_ptr = sdp_getnextstrtok(fmtp_ptr, tmp, sizeof(tmp), " \t", &result1);
-                if (result1 != SDP_SUCCESS) {
-                    sdp_attr_fmtp_no_value(sdp_p, "custom");
-                    SDP_FREE(temp_ptr);
-                    return SDP_INVALID_PARAMETER;
-                }
-            }
-            tok = tmp;
-            tok++; temp=PL_strtok_r(tok, ",", &strtok_state);
+            result1 = sdp_get_fmtp_tok(sdp_p, &fmtp_ptr, "custom", tmp, sizeof(tmp), &tok);
+            if (result1 != SDP_SUCCESS) { SDP_FREE(temp_ptr); return result1; }
+
+            temp=PL_strtok_r(tok, ",", &strtok_state);
             iter++;
         if (temp) {
             iter=1;
             while (temp != NULL) {
                 errno = 0;
                 strtoul_result = strtoul(temp, &strtoul_end, 10);
 
                 if (errno || temp == strtoul_end || strtoul_result > USHRT_MAX){
@@ -921,29 +818,23 @@ sdp_result_e sdp_parse_attr_fmtp (sdp_t 
                 SDP_FREE(temp_ptr);
                 return SDP_INVALID_PARAMETER;
             }
             fmtp_p->fmtp_format = SDP_FMTP_CODEC_INFO;
             fmtp_p->custom_x = custom_x;
             fmtp_p->custom_y = custom_y;
             fmtp_p->custom_mpi = custom_mpi;
             codec_info_found = TRUE;
+
         } else  if (cpr_strncasecmp(tmp,sdp_fmtp_codec_param[10].name,
                                sdp_fmtp_codec_param[10].strlen) == 0) {
-            fmtp_ptr = sdp_getnextstrtok(fmtp_ptr, tmp, sizeof(tmp), "; \t", &result1);
-            if (result1 != SDP_SUCCESS) {
-                fmtp_ptr = sdp_getnextstrtok(fmtp_ptr, tmp, sizeof(tmp), " \t", &result1);
-                if (result1 != SDP_SUCCESS) {
-                    sdp_attr_fmtp_no_value(sdp_p, "par");
-                    SDP_FREE(temp_ptr);
-                    return SDP_INVALID_PARAMETER;
-                }
-            }
-            tok = tmp;
-            tok++; temp=PL_strtok_r(tok, ":", &strtok_state);
+            result1 = sdp_get_fmtp_tok(sdp_p, &fmtp_ptr, "par", tmp, sizeof(tmp), &tok);
+            if (result1 != SDP_SUCCESS) { SDP_FREE(temp_ptr); return result1; }
+
+            temp=PL_strtok_r(tok, ":", &strtok_state);
         if (temp) {
             iter=1;
             /* get par width and par height for the aspect ratio */
             while (temp != NULL) {
                 errno = 0;
                 strtoul_result = strtoul(temp, &strtoul_end, 10);
 
                 if (errno || temp == strtoul_end || strtoul_result > USHRT_MAX) {
@@ -964,29 +855,23 @@ sdp_result_e sdp_parse_attr_fmtp (sdp_t 
                 sdp_attr_fmtp_invalid_value(sdp_p, "par_width or par_height", temp);
                 SDP_FREE(temp_ptr);
                 return SDP_INVALID_PARAMETER;
             }
             fmtp_p->fmtp_format = SDP_FMTP_CODEC_INFO;
             fmtp_p->par_width = par_width;
             fmtp_p->par_height = par_height;
             codec_info_found = TRUE;
+
         } else  if (cpr_strncasecmp(tmp,sdp_fmtp_codec_param[11].name,
                                sdp_fmtp_codec_param[11].strlen) == 0) {
-            fmtp_ptr = sdp_getnextstrtok(fmtp_ptr, tmp, sizeof(tmp), "; \t", &result1);
-            if (result1 != SDP_SUCCESS) {
-                fmtp_ptr = sdp_getnextstrtok(fmtp_ptr, tmp, sizeof(tmp), " \t", &result1);
-                if (result1 != SDP_SUCCESS) {
-                    sdp_attr_fmtp_no_value(sdp_p, "cpcf");
-                    SDP_FREE(temp_ptr);
-                    return SDP_INVALID_PARAMETER;
-                }
-            }
-            tok = tmp;
-            tok++; temp=PL_strtok_r(tok, ".", &strtok_state);
+            result1 = sdp_get_fmtp_tok(sdp_p, &fmtp_ptr, "cpcf", tmp, sizeof(tmp), &tok);
+            if (result1 != SDP_SUCCESS) { SDP_FREE(temp_ptr); return result1; }
+
+            temp=PL_strtok_r(tok, ".", &strtok_state);
         if ( temp != NULL  ) {
             errno = 0;
             strtoul_result = strtoul(temp, &strtoul_end, 10);
 
             if (errno || temp == strtoul_end || strtoul_result > USHRT_MAX) {
                 cpcf = 0;
             } else {
                 cpcf = (uint16_t) strtoul_result;
@@ -996,594 +881,303 @@ sdp_result_e sdp_parse_attr_fmtp (sdp_t 
             if (!cpcf) {
                 sdp_attr_fmtp_invalid_value(sdp_p, "cpcf", tok);
                 SDP_FREE(temp_ptr);
                 return SDP_INVALID_PARAMETER;
             }
             fmtp_p->fmtp_format = SDP_FMTP_CODEC_INFO;
             fmtp_p->cpcf = cpcf;
             codec_info_found = TRUE;
+
         } else  if (cpr_strncasecmp(tmp,sdp_fmtp_codec_param[12].name,
                                sdp_fmtp_codec_param[12].strlen) == 0) {
-            fmtp_ptr = sdp_getnextstrtok(fmtp_ptr, tmp, sizeof(tmp), "; \t", &result1);
-            if (result1 != SDP_SUCCESS) {
-                fmtp_ptr = sdp_getnextstrtok(fmtp_ptr, tmp, sizeof(tmp), " \t", &result1);
-                if (result1 != SDP_SUCCESS) {
-                    sdp_attr_fmtp_no_value(sdp_p, "bpp");
-                    SDP_FREE(temp_ptr);
-                    return SDP_INVALID_PARAMETER;
-                }
-            }
-            tok = tmp;
-            tok++;
-
-        errno = 0;
-        strtoul_result = strtoul(tok, &strtoul_end, 10);
-
-        if (errno || tok == strtoul_end || strtoul_result == 0 || strtoul_result > USHRT_MAX) {
-            sdp_attr_fmtp_invalid_value(sdp_p, "bpp", tok);
-            SDP_FREE(temp_ptr);
-            return SDP_INVALID_PARAMETER;
-            }
+            result1 = sdp_get_fmtp_tok_val(sdp_p, &fmtp_ptr, "bpp", tmp, sizeof(tmp),
+                                           &tok, &strtoul_result, 0, -1, USHRT_MAX);
+            if (result1 != SDP_SUCCESS) { SDP_FREE(temp_ptr); return result1; }
 
             fmtp_p->fmtp_format = SDP_FMTP_CODEC_INFO;
         fmtp_p->bpp = (uint16_t) strtoul_result;
             codec_info_found = TRUE;
+
         } else  if (cpr_strncasecmp(tmp,sdp_fmtp_codec_param[13].name,
                                sdp_fmtp_codec_param[13].strlen) == 0) {
-            fmtp_ptr = sdp_getnextstrtok(fmtp_ptr, tmp, sizeof(tmp), "; \t", &result1);
-            if (result1 != SDP_SUCCESS) {
-                fmtp_ptr = sdp_getnextstrtok(fmtp_ptr, tmp, sizeof(tmp), " \t", &result1);
-                if (result1 != SDP_SUCCESS) {
-                    sdp_attr_fmtp_no_value(sdp_p, "hrd");
-                    SDP_FREE(temp_ptr);
-                    return SDP_INVALID_PARAMETER;
-                }
-            }
-            tok = tmp;
-            tok++;
-
-            errno = 0;
-            strtoul_result = strtoul(tok, &strtoul_end, 10);
-
-            if (errno || tok == strtoul_end || strtoul_result == 0 || strtoul_result > USHRT_MAX) {
-                sdp_attr_fmtp_invalid_value(sdp_p, "hrd", tok);
-                SDP_FREE(temp_ptr);
-                return SDP_INVALID_PARAMETER;
-            }
+            result1 = sdp_get_fmtp_tok_val(sdp_p, &fmtp_ptr, "hrd", tmp, sizeof(tmp),
+                                           &tok, &strtoul_result, 0, -1, USHRT_MAX);
+            if (result1 != SDP_SUCCESS) { SDP_FREE(temp_ptr); return result1; }
 
             fmtp_p->fmtp_format = SDP_FMTP_CODEC_INFO;
             fmtp_p->hrd = (uint16_t) strtoul_result;
             codec_info_found = TRUE;
+
         } else if (cpr_strncasecmp(tmp,sdp_fmtp_codec_param[14].name,
                                sdp_fmtp_codec_param[14].strlen) == 0) {
-            fmtp_ptr = sdp_getnextstrtok(fmtp_ptr, tmp, sizeof(tmp), "; \t", &result1);
-            if (result1 != SDP_SUCCESS) {
-                fmtp_ptr = sdp_getnextstrtok(fmtp_ptr, tmp, sizeof(tmp), " \t", &result1);
-                if (result1 != SDP_SUCCESS) {
-                    sdp_attr_fmtp_no_value(sdp_p, "profile");
-                    SDP_FREE(temp_ptr);
-                    return SDP_INVALID_PARAMETER;
-                }
-            }
-            tok = tmp;
-            tok++;
-
-            errno = 0;
-            strtoul_result = strtoul(tok, &strtoul_end, 10);
-
-            if (errno || tok == strtoul_end ||
-                strtoul_result > SDP_MAX_PROFILE_VALUE) {
-                sdp_attr_fmtp_invalid_value(sdp_p, "profile", tok);
-                SDP_FREE(temp_ptr);
-                return SDP_INVALID_PARAMETER;
-            }
+            result1 = sdp_get_fmtp_tok_val(sdp_p, &fmtp_ptr, "profile", tmp, sizeof(tmp),
+                                           &tok, &strtoul_result, -1, -1, SDP_MAX_PROFILE_VALUE);
+            if (result1 != SDP_SUCCESS) { SDP_FREE(temp_ptr); return result1; }
 
             fmtp_p->fmtp_format = SDP_FMTP_CODEC_INFO;
             fmtp_p->profile = (short) strtoul_result;
             codec_info_found = TRUE;
+
         } else if (cpr_strncasecmp(tmp,sdp_fmtp_codec_param[15].name,
                                sdp_fmtp_codec_param[15].strlen) == 0) {
-            fmtp_ptr = sdp_getnextstrtok(fmtp_ptr, tmp, sizeof(tmp), "; \t", &result1);
-            if (result1 != SDP_SUCCESS) {
-                fmtp_ptr = sdp_getnextstrtok(fmtp_ptr, tmp, sizeof(tmp), " \t", &result1);
-                if (result1 != SDP_SUCCESS) {
-                    sdp_attr_fmtp_no_value(sdp_p, "level");
-                    SDP_FREE(temp_ptr);
-                    return SDP_INVALID_PARAMETER;
-                }
-            }
-            tok = tmp;
-            tok++;
-
-        errno = 0;
-        strtoul_result = strtoul(tok, &strtoul_end, 10);
-
-        if (errno || tok == strtoul_end ||
-            strtoul_result > SDP_MAX_LEVEL_VALUE) {
-            sdp_attr_fmtp_invalid_value(sdp_p, "level", tok);
-            SDP_FREE(temp_ptr);
-            return SDP_INVALID_PARAMETER;
-        }
+            result1 = sdp_get_fmtp_tok_val(sdp_p, &fmtp_ptr, "level", tmp, sizeof(tmp),
+                                           &tok, &strtoul_result, -1, -1, SDP_MAX_LEVEL_VALUE);
+            if (result1 != SDP_SUCCESS) { SDP_FREE(temp_ptr); return result1; }
 
             fmtp_p->fmtp_format = SDP_FMTP_CODEC_INFO;
             fmtp_p->level = (short) strtoul_result;
             codec_info_found = TRUE;
+
         } else if (cpr_strncasecmp(tmp,sdp_fmtp_codec_param[16].name,
                                sdp_fmtp_codec_param[16].strlen) == 0) {
             fmtp_p->fmtp_format = SDP_FMTP_CODEC_INFO;
             fmtp_p->is_interlace = TRUE;
             codec_info_found = TRUE;
+
         } else if (cpr_strncasecmp(tmp,sdp_fmtp_codec_param[17].name,
                                sdp_fmtp_codec_param[17].strlen) == 0) {
-            fmtp_ptr = sdp_getnextstrtok(fmtp_ptr, tmp, sizeof(tmp), "; \t", &result1);
-            if (result1 != SDP_SUCCESS) {
-                fmtp_ptr = sdp_getnextstrtok(fmtp_ptr, tmp, sizeof(tmp), " \t", &result1);
-                if (result1 != SDP_SUCCESS) {
-                    sdp_attr_fmtp_no_value(sdp_p, "profile_level_id");
-                    SDP_FREE(temp_ptr);
-                    return SDP_INVALID_PARAMETER;
-                }
-            }
-            tok = tmp;
-            tok++;
+            result1 = sdp_get_fmtp_tok(sdp_p, &fmtp_ptr, "profile_level_id", tmp, sizeof(tmp), &tok);
+            if (result1 != SDP_SUCCESS) { SDP_FREE(temp_ptr); return result1; }
+
             fmtp_p->fmtp_format = SDP_FMTP_CODEC_INFO;
             sstrncpy(fmtp_p->profile_level_id , tok, sizeof(fmtp_p->profile_level_id));
             codec_info_found = TRUE;
+
         } else if (cpr_strncasecmp(tmp,sdp_fmtp_codec_param[18].name,
                                sdp_fmtp_codec_param[18].strlen) == 0) {
-            fmtp_ptr = sdp_getnextstrtok(fmtp_ptr, tmp, sizeof(tmp), "; \t", &result1);
-            if (result1 != SDP_SUCCESS) {
-                fmtp_ptr = sdp_getnextstrtok(fmtp_ptr, tmp, sizeof(tmp), " \t", &result1);
-                if (result1 != SDP_SUCCESS) {
-                    sdp_attr_fmtp_no_value(sdp_p, "parameter_sets");
-                    SDP_FREE(temp_ptr);
-                    return SDP_INVALID_PARAMETER;
-                }
-            }
-            tok = tmp;
-            tok++;
+            result1 = sdp_get_fmtp_tok(sdp_p, &fmtp_ptr, "parameter_sets", tmp, sizeof(tmp), &tok);
+            if (result1 != SDP_SUCCESS) { SDP_FREE(temp_ptr); return result1; }
+
             fmtp_p->fmtp_format = SDP_FMTP_CODEC_INFO;
             sstrncpy(fmtp_p->parameter_sets , tok, sizeof(fmtp_p->parameter_sets));
             codec_info_found = TRUE;
+
         } else if (cpr_strncasecmp(tmp,sdp_fmtp_codec_param[19].name,
                                sdp_fmtp_codec_param[19].strlen) == 0) {
-            fmtp_ptr = sdp_getnextstrtok(fmtp_ptr, tmp, sizeof(tmp), "; \t", &result1);
-            if (result1 != SDP_SUCCESS) {
-                fmtp_ptr = sdp_getnextstrtok(fmtp_ptr, tmp, sizeof(tmp), " \t", &result1);
-                if (result1 != SDP_SUCCESS) {
-                    sdp_attr_fmtp_no_value(sdp_p, "packetization_mode");
-                    SDP_FREE(temp_ptr);
-                    return SDP_INVALID_PARAMETER;
-                }
-            }
-            tok = tmp;
-            tok++;
-
-            errno = 0;
-            strtoul_result = strtoul(tok, &strtoul_end, 10);
-
-            if (errno || tok == strtoul_end || strtoul_result > 2) {
-                sdp_attr_fmtp_invalid_value(sdp_p, "packetization_mode", tok);
-                sdp_p->conf_p->num_invalid_param++;
-                SDP_FREE(temp_ptr);
-                return SDP_INVALID_PARAMETER;
-            }
+            result1 = sdp_get_fmtp_tok_val(sdp_p, &fmtp_ptr, "packetization_mode", tmp, sizeof(tmp),
+                                           &tok, &strtoul_result, -1, -1, 2);
+            // this one is different for some reason. Most others don't increment
+            // the num_invalid_param field. (mjf)
+            if (result1 == SDP_INVALID_PARAMETER) { sdp_p->conf_p->num_invalid_param++; }
+            if (result1 != SDP_SUCCESS) { SDP_FREE(temp_ptr); return result1; }
 
             fmtp_p->fmtp_format = SDP_FMTP_CODEC_INFO;
             fmtp_p->packetization_mode = (int16_t) strtoul_result;
             codec_info_found = TRUE;
+
         } else if (cpr_strncasecmp(tmp,sdp_fmtp_codec_param[20].name,
                                sdp_fmtp_codec_param[20].strlen) == 0) {
-            fmtp_ptr = sdp_getnextstrtok(fmtp_ptr, tmp, sizeof(tmp), "; \t", &result1);
-            if (result1 != SDP_SUCCESS) {
-                fmtp_ptr = sdp_getnextstrtok(fmtp_ptr, tmp, sizeof(tmp), " \t", &result1);
-                if (result1 != SDP_SUCCESS) {
-                    sdp_attr_fmtp_no_value(sdp_p, "interleaving_depth");
-                    SDP_FREE(temp_ptr);
-                    return SDP_INVALID_PARAMETER;
-                }
-            }
-            tok = tmp;
-            tok++;
-
-            errno = 0;
-            strtoul_result = strtoul(tok, &strtoul_end, 10);
-
-            if (errno || tok == strtoul_end || strtoul_result == 0 || strtoul_result > USHRT_MAX) {
-                sdp_attr_fmtp_invalid_value(sdp_p, "interleaving_depth", tok);
-                SDP_FREE(temp_ptr);
-                return SDP_INVALID_PARAMETER;
-            }
+            result1 = sdp_get_fmtp_tok_val(sdp_p, &fmtp_ptr, "interleaving_depth", tmp, sizeof(tmp),
+                                           &tok, &strtoul_result, 0, -1, USHRT_MAX);
+            if (result1 != SDP_SUCCESS) { SDP_FREE(temp_ptr); return result1; }
 
             fmtp_p->fmtp_format = SDP_FMTP_CODEC_INFO;
             fmtp_p->interleaving_depth = (uint16_t) strtoul_result;
             codec_info_found = TRUE;
+
         } else if (cpr_strncasecmp(tmp,sdp_fmtp_codec_param[21].name,
                                sdp_fmtp_codec_param[21].strlen) == 0) {
-            fmtp_ptr = sdp_getnextstrtok(fmtp_ptr, tmp, sizeof(tmp), "; \t", &result1);
-            if (result1 != SDP_SUCCESS) {
-                fmtp_ptr = sdp_getnextstrtok(fmtp_ptr, tmp, sizeof(tmp), " \t", &result1);
-                if (result1 != SDP_SUCCESS) {
-                    sdp_attr_fmtp_no_value(sdp_p, "deint_buf");
-                    SDP_FREE(temp_ptr);
-                    return SDP_INVALID_PARAMETER;
-                }
-            }
-            tok = tmp;
-            tok++;
+            result1 = sdp_get_fmtp_tok(sdp_p, &fmtp_ptr, "deint_buf", tmp, sizeof(tmp), &tok);
+            if (result1 != SDP_SUCCESS) { SDP_FREE(temp_ptr); return result1; }
+
             if (sdp_checkrange(sdp_p, tok, &l_val) == TRUE) {
                 fmtp_p->fmtp_format = SDP_FMTP_CODEC_INFO;
                 fmtp_p->deint_buf_req = (uint32_t) l_val;
                 fmtp_p->flag |= SDP_DEINT_BUF_REQ_FLAG;
                 codec_info_found = TRUE;
             } else {
                 sdp_attr_fmtp_invalid_value(sdp_p, "deint_buf_req", tok);
                 SDP_FREE(temp_ptr);
                 return SDP_INVALID_PARAMETER;
             }
+
         } else if (cpr_strncasecmp(tmp,sdp_fmtp_codec_param[22].name,
                                sdp_fmtp_codec_param[22].strlen) == 0) {
-            fmtp_ptr = sdp_getnextstrtok(fmtp_ptr, tmp, sizeof(tmp), "; \t", &result1);
-            if (result1 != SDP_SUCCESS) {
-                fmtp_ptr = sdp_getnextstrtok(fmtp_ptr, tmp, sizeof(tmp), " \t", &result1);
-                if (result1 != SDP_SUCCESS) {
-                    sdp_attr_fmtp_no_value(sdp_p, "max_don_diff");
-                    SDP_FREE(temp_ptr);
-                    return SDP_INVALID_PARAMETER;
-                }
-            }
-            tok = tmp;
-            tok++;
-
-            errno = 0;
-            strtoul_result = strtoul(tok, &strtoul_end, 10);
-
-            if (errno || tok == strtoul_end || strtoul_result == 0 || strtoul_result > UINT_MAX) {
-                sdp_attr_fmtp_invalid_value(sdp_p, "max_don_diff", tok);
-                SDP_FREE(temp_ptr);
-                return SDP_INVALID_PARAMETER;
-            }
+            result1 = sdp_get_fmtp_tok_val(sdp_p, &fmtp_ptr, "max_don_diff", tmp, sizeof(tmp),
+                                           &tok, &strtoul_result, 0, -1, UINT_MAX);
+            if (result1 != SDP_SUCCESS) { SDP_FREE(temp_ptr); return result1; }
 
             fmtp_p->fmtp_format = SDP_FMTP_CODEC_INFO;
             fmtp_p->max_don_diff = (uint32_t) strtoul_result;
             codec_info_found = TRUE;
+
         } else if (cpr_strncasecmp(tmp,sdp_fmtp_codec_param[23].name,
                                sdp_fmtp_codec_param[23].strlen) == 0) {
-            fmtp_ptr = sdp_getnextstrtok(fmtp_ptr, tmp, sizeof(tmp), "; \t", &result1);
-            if (result1 != SDP_SUCCESS) {
-                fmtp_ptr = sdp_getnextstrtok(fmtp_ptr, tmp, sizeof(tmp), " \t", &result1);
-                if (result1 != SDP_SUCCESS) {
-                    sdp_attr_fmtp_no_value(sdp_p, "init_buf_time");
-                    SDP_FREE(temp_ptr);
-                    return SDP_INVALID_PARAMETER;
-                }
-            }
-            tok = tmp;
-            tok++;
+            result1 = sdp_get_fmtp_tok(sdp_p, &fmtp_ptr, "init_buf_time", tmp, sizeof(tmp), &tok);
+            if (result1 != SDP_SUCCESS) { SDP_FREE(temp_ptr); return result1; }
+
             if (sdp_checkrange(sdp_p, tok, &l_val) == TRUE) {
                 fmtp_p->fmtp_format = SDP_FMTP_CODEC_INFO;
                 fmtp_p->init_buf_time = (uint32_t) l_val;
                 fmtp_p->flag |= SDP_INIT_BUF_TIME_FLAG;
                 codec_info_found = TRUE;
             } else {
                 sdp_attr_fmtp_invalid_value(sdp_p, "init_buf_time", tok);
                 SDP_FREE(temp_ptr);
                 return SDP_INVALID_PARAMETER;
             }
+
         } else if (cpr_strncasecmp(tmp,sdp_fmtp_codec_param[24].name,
                                sdp_fmtp_codec_param[24].strlen) == 0) {
-            fmtp_ptr = sdp_getnextstrtok(fmtp_ptr, tmp, sizeof(tmp), "; \t", &result1);
-            if (result1 != SDP_SUCCESS) {
-                fmtp_ptr = sdp_getnextstrtok(fmtp_ptr, tmp, sizeof(tmp), " \t", &result1);
-                if (result1 != SDP_SUCCESS) {
-                    sdp_attr_fmtp_no_value(sdp_p, "max_mbps");
-                    SDP_FREE(temp_ptr);
-                    return SDP_INVALID_PARAMETER;
-                }
-            }
-            tok = tmp;
-            tok++;
-
-            errno = 0;
-            strtoul_result = strtoul(tok, &strtoul_end, 10);
-
-            if (errno || tok == strtoul_end || strtoul_result == 0 || strtoul_result > UINT_MAX) {
-                sdp_attr_fmtp_invalid_value(sdp_p, "max_mbps", tok);
-                SDP_FREE(temp_ptr);
-                return SDP_INVALID_PARAMETER;
-            }
+            result1 = sdp_get_fmtp_tok_val(sdp_p, &fmtp_ptr, "max_mbps", tmp, sizeof(tmp),
+                                           &tok, &strtoul_result, 0, -1, UINT_MAX);
+            if (result1 != SDP_SUCCESS) { SDP_FREE(temp_ptr); return result1; }
 
             fmtp_p->fmtp_format = SDP_FMTP_CODEC_INFO;
         fmtp_p->max_mbps = (uint32_t) strtoul_result;
             codec_info_found = TRUE;
+
         } else if (cpr_strncasecmp(tmp,sdp_fmtp_codec_param[25].name,
                                sdp_fmtp_codec_param[25].strlen) == 0) {
-            fmtp_ptr = sdp_getnextstrtok(fmtp_ptr, tmp, sizeof(tmp), "; \t", &result1);
-            if (result1 != SDP_SUCCESS) {
-                fmtp_ptr = sdp_getnextstrtok(fmtp_ptr, tmp, sizeof(tmp), " \t", &result1);
-                if (result1 != SDP_SUCCESS) {
-                    sdp_attr_fmtp_no_value(sdp_p, "max-fs");
-                    SDP_FREE(temp_ptr);
-                    return SDP_INVALID_PARAMETER;
-                }
-            }
-            tok = tmp;
-            tok++;
-
-            errno = 0;
-            strtoul_result = strtoul(tok, &strtoul_end, 10);
-
-            if (errno || tok == strtoul_end || strtoul_result == 0 || strtoul_result > UINT_MAX) {
-                sdp_attr_fmtp_invalid_value(sdp_p, "max-fs", tok);
-                SDP_FREE(temp_ptr);
-                return SDP_INVALID_PARAMETER;
-            }
+            result1 = sdp_get_fmtp_tok_val(sdp_p, &fmtp_ptr, "max-fs", tmp, sizeof(tmp),
+                                           &tok, &strtoul_result, 0, -1, UINT_MAX);
+            if (result1 != SDP_SUCCESS) { SDP_FREE(temp_ptr); return result1; }
+
             fmtp_p->fmtp_format = SDP_FMTP_CODEC_INFO;
             fmtp_p->max_fs = (uint32_t) strtoul_result;
             codec_info_found = TRUE;
+
         } else if (cpr_strncasecmp(tmp,sdp_fmtp_codec_param[26].name,
                                sdp_fmtp_codec_param[26].strlen) == 0) {
-            fmtp_ptr = sdp_getnextstrtok(fmtp_ptr, tmp, sizeof(tmp), "; \t", &result1);
-            if (result1 != SDP_SUCCESS) {
-                fmtp_ptr = sdp_getnextstrtok(fmtp_ptr, tmp, sizeof(tmp), " \t", &result1);
-                if (result1 != SDP_SUCCESS) {
-                    sdp_attr_fmtp_no_value(sdp_p, "max_cbp");
-                    SDP_FREE(temp_ptr);
-                    return SDP_INVALID_PARAMETER;
-                }
-            }
-            tok = tmp;
-            tok++;
-
-            errno = 0;
-            strtoul_result = strtoul(tok, &strtoul_end, 10);
-
-            if (errno || tok == strtoul_end || strtoul_result == 0 || strtoul_result > UINT_MAX) {
-                sdp_attr_fmtp_invalid_value(sdp_p, "max_cpb", tok);
-                SDP_FREE(temp_ptr);
-                return SDP_INVALID_PARAMETER;
-            }
+            result1 = sdp_get_fmtp_tok_val(sdp_p, &fmtp_ptr, "max_cbp", tmp, sizeof(tmp),
+                                           &tok, &strtoul_result, 0, -1, UINT_MAX);
+            if (result1 != SDP_SUCCESS) { SDP_FREE(temp_ptr); return result1; }
+
             fmtp_p->fmtp_format = SDP_FMTP_CODEC_INFO;
             fmtp_p->max_cpb = (uint32_t) strtoul_result;
             codec_info_found = TRUE;
+
         } else if (cpr_strncasecmp(tmp,sdp_fmtp_codec_param[27].name,
                                sdp_fmtp_codec_param[27].strlen) == 0) {
-            fmtp_ptr = sdp_getnextstrtok(fmtp_ptr, tmp, sizeof(tmp), "; \t", &result1);
-            if (result1 != SDP_SUCCESS) {
-                fmtp_ptr = sdp_getnextstrtok(fmtp_ptr, tmp, sizeof(tmp), " \t", &result1);
-                if (result1 != SDP_SUCCESS) {
-                    sdp_attr_fmtp_no_value(sdp_p, "max_dpb");
-                    SDP_FREE(temp_ptr);
-                    return SDP_INVALID_PARAMETER;
-                }
-            }
-            tok = tmp;
-            tok++;
-
-            errno = 0;
-            strtoul_result = strtoul(tok, &strtoul_end, 10);
-
-            if (errno || tok == strtoul_end || strtoul_result == 0 || strtoul_result > UINT_MAX) {
-                sdp_attr_fmtp_invalid_value(sdp_p, "max_dpb", tok);
-                SDP_FREE(temp_ptr);
-                return SDP_INVALID_PARAMETER;
-            }
+            result1 = sdp_get_fmtp_tok_val(sdp_p, &fmtp_ptr, "max_dpb", tmp, sizeof(tmp),
+                                           &tok, &strtoul_result, 0, -1, UINT_MAX);
+            if (result1 != SDP_SUCCESS) { SDP_FREE(temp_ptr); return result1; }
+
             fmtp_p->fmtp_format = SDP_FMTP_CODEC_INFO;
             fmtp_p->max_dpb = (uint32_t) strtoul_result;
             codec_info_found = TRUE;
+
         } else if (cpr_strncasecmp(tmp,sdp_fmtp_codec_param[28].name,
                                sdp_fmtp_codec_param[28].strlen) == 0) {
-            fmtp_ptr = sdp_getnextstrtok(fmtp_ptr, tmp, sizeof(tmp), "; \t", &result1);
-            if (result1 != SDP_SUCCESS) {
-                fmtp_ptr = sdp_getnextstrtok(fmtp_ptr, tmp, sizeof(tmp), " \t", &result1);
-                if (result1 != SDP_SUCCESS) {
-                    sdp_attr_fmtp_no_value(sdp_p, "max_br");
-                    SDP_FREE(temp_ptr);
-                    return SDP_INVALID_PARAMETER;
-                }
-            }
-            tok = tmp;
-            tok++;
-
-            errno = 0;
-            strtoul_result = strtoul(tok, &strtoul_end, 10);
-
-            if (errno || tok == strtoul_end || strtoul_result == 0 || strtoul_result > UINT_MAX) {
-                sdp_attr_fmtp_invalid_value(sdp_p, "max_br", tok);
-                SDP_FREE(temp_ptr);
-                return SDP_INVALID_PARAMETER;
-            }
+            result1 = sdp_get_fmtp_tok_val(sdp_p, &fmtp_ptr, "max_br", tmp, sizeof(tmp),
+                                           &tok, &strtoul_result, 0, -1, UINT_MAX);
+            if (result1 != SDP_SUCCESS) { SDP_FREE(temp_ptr); return result1; }
+
             fmtp_p->fmtp_format = SDP_FMTP_CODEC_INFO;
             fmtp_p->max_br = (uint32_t) strtoul_result;
             codec_info_found = TRUE;
+
         } else if (cpr_strncasecmp(tmp,sdp_fmtp_codec_param[29].name,
                                sdp_fmtp_codec_param[29].strlen) == 0) {
-            fmtp_ptr = sdp_getnextstrtok(fmtp_ptr, tmp, sizeof(tmp), "; \t", &result1);
-            if (result1 != SDP_SUCCESS) {
-                fmtp_ptr = sdp_getnextstrtok(fmtp_ptr, tmp, sizeof(tmp), " \t", &result1);
-                if (result1 != SDP_SUCCESS) {
-                    sdp_attr_fmtp_no_value(sdp_p, "redundant_pic_cap");
-                    SDP_FREE(temp_ptr);
-                    return SDP_INVALID_PARAMETER;
-                }
-            }
-            tok = tmp;
-            tok++;
-
-        errno = 0;
-        strtoul_result = strtoul(tok, &strtoul_end, 10);
-
-        if (!errno && tok != strtoul_end && strtoul_result == 1) {
-            fmtp_p->redundant_pic_cap = TRUE;
-        } else {
-            fmtp_p->redundant_pic_cap = FALSE;
-        }
-
+            result1 = sdp_get_fmtp_tok_val(sdp_p, &fmtp_ptr, "redundant_pic_cap", tmp, sizeof(tmp),
+                                           &tok, &strtoul_result, 0, -1, 1);
+            fmtp_p->redundant_pic_cap = (result1 == SDP_SUCCESS);
             codec_info_found = TRUE;
+
         } else if (cpr_strncasecmp(tmp,sdp_fmtp_codec_param[30].name,
                                sdp_fmtp_codec_param[30].strlen) == 0) {
-            fmtp_ptr = sdp_getnextstrtok(fmtp_ptr, tmp, sizeof(tmp), "; \t", &result1);
-            if (result1 != SDP_SUCCESS) {
-                fmtp_ptr = sdp_getnextstrtok(fmtp_ptr, tmp, sizeof(tmp), " \t", &result1);
-                if (result1 != SDP_SUCCESS) {
-                    sdp_attr_fmtp_no_value(sdp_p, "deint_buf_cap");
-                    SDP_FREE(temp_ptr);
-                    return SDP_INVALID_PARAMETER;
-                }
-            }
-            tok = tmp;
-            tok++;
+            result1 = sdp_get_fmtp_tok(sdp_p, &fmtp_ptr, "deint_buf_cap", tmp, sizeof(tmp), &tok);
+            if (result1 != SDP_SUCCESS) { SDP_FREE(temp_ptr); return result1; }
+
             if (sdp_checkrange(sdp_p, tok, &l_val) == TRUE) {
                 fmtp_p->fmtp_format = SDP_FMTP_CODEC_INFO;
                 fmtp_p->deint_buf_cap = (uint32_t) l_val;
                 fmtp_p->flag |= SDP_DEINT_BUF_CAP_FLAG;
                 codec_info_found = TRUE;
             } else {
                 sdp_attr_fmtp_invalid_value(sdp_p, "deint_buf_cap", tok);
                 SDP_FREE(temp_ptr);
                 return SDP_INVALID_PARAMETER;
             }
+
         }  else if (cpr_strncasecmp(tmp,sdp_fmtp_codec_param[31].name,
                                sdp_fmtp_codec_param[31].strlen) == 0) {
-            fmtp_ptr = sdp_getnextstrtok(fmtp_ptr, tmp, sizeof(tmp), "; \t", &result1);
-            if (result1 != SDP_SUCCESS) {
-                fmtp_ptr = sdp_getnextstrtok(fmtp_ptr, tmp, sizeof(tmp), " \t", &result1);
-                if (result1 != SDP_SUCCESS) {
-                    sdp_attr_fmtp_no_value(sdp_p, "max_rcmd_nalu_size");
-                    SDP_FREE(temp_ptr);
-                    return SDP_INVALID_PARAMETER;
-                }
-            }
-            tok = tmp;
-            tok++;
+            result1 = sdp_get_fmtp_tok(sdp_p, &fmtp_ptr, "max_rcmd_nalu_size", tmp, sizeof(tmp), &tok);
+            if (result1 != SDP_SUCCESS) { SDP_FREE(temp_ptr); return result1; }
+
             if (sdp_checkrange(sdp_p, tok, &l_val) == TRUE) {
                 fmtp_p->fmtp_format = SDP_FMTP_CODEC_INFO;
                 fmtp_p->max_rcmd_nalu_size = (uint32_t) l_val;
                 fmtp_p->flag |= SDP_MAX_RCMD_NALU_SIZE_FLAG;
                 codec_info_found = TRUE;
             } else {
                 sdp_attr_fmtp_invalid_value(sdp_p, "max_rcmd_nalu_size", tok);
                 SDP_FREE(temp_ptr);
                 return SDP_INVALID_PARAMETER;
             }
+
         } else if (cpr_strncasecmp(tmp,sdp_fmtp_codec_param[32].name,
                                sdp_fmtp_codec_param[32].strlen) == 0) {
-            fmtp_ptr = sdp_getnextstrtok(fmtp_ptr, tmp, sizeof(tmp), "; \t", &result1);
-            if (result1 != SDP_SUCCESS) {
-                fmtp_ptr = sdp_getnextstrtok(fmtp_ptr, tmp, sizeof(tmp), " \t", &result1);
-                if (result1 != SDP_SUCCESS) {
-                    sdp_attr_fmtp_no_value(sdp_p, "parameter_add");
-                    SDP_FREE(temp_ptr);
-                    return SDP_INVALID_PARAMETER;
-                }
-            }
-            tok = tmp;
-            tok++;
-            errno = 0;
-            strtoul_result = strtoul(tok, &strtoul_end, 10);
-
-            if (errno || tok == strtoul_end || strtoul_result > 1) {
-                sdp_attr_fmtp_invalid_value(sdp_p, "parameter_add", tok);
-                SDP_FREE(temp_ptr);
-                return SDP_INVALID_PARAMETER;
-            }
+            result1 = sdp_get_fmtp_tok_val(sdp_p, &fmtp_ptr, "parameter_add", tmp, sizeof(tmp),
+                                           &tok, &strtoul_result, 0, -1, 1);
+            if (result1 != SDP_SUCCESS) { SDP_FREE(temp_ptr); return result1; }
+
             fmtp_p->fmtp_format = SDP_FMTP_CODEC_INFO;
             fmtp_p->parameter_add = (uint16_t) strtoul_result;
             codec_info_found = TRUE;
+
         } else if (cpr_strncasecmp(tmp,sdp_fmtp_codec_param[33].name,
                                sdp_fmtp_codec_param[33].strlen) == 0) {
             fmtp_p->fmtp_format = SDP_FMTP_CODEC_INFO;
             fmtp_p->annex_d = TRUE;
             codec_info_found = TRUE;
+
         } else if (cpr_strncasecmp(tmp,sdp_fmtp_codec_param[34].name,
                                sdp_fmtp_codec_param[34].strlen) == 0) {
             fmtp_p->fmtp_format = SDP_FMTP_CODEC_INFO;
             fmtp_p->annex_f = TRUE;
             codec_info_found = TRUE;
+
         } else if (cpr_strncasecmp(tmp,sdp_fmtp_codec_param[35].name,
                                sdp_fmtp_codec_param[35].strlen) == 0) {
             fmtp_p->fmtp_format = SDP_FMTP_CODEC_INFO;
             fmtp_p->annex_i = TRUE;
             codec_info_found = TRUE;
+
         } else if (cpr_strncasecmp(tmp,sdp_fmtp_codec_param[36].name,
                                sdp_fmtp_codec_param[36].strlen) == 0) {
             fmtp_p->fmtp_format = SDP_FMTP_CODEC_INFO;
             fmtp_p->annex_j = TRUE;
             codec_info_found = TRUE;
+
         } else if (cpr_strncasecmp(tmp,sdp_fmtp_codec_param[37].name,
                                sdp_fmtp_codec_param[36].strlen) == 0) {
             fmtp_p->fmtp_format = SDP_FMTP_CODEC_INFO;
             fmtp_p->annex_t = TRUE;
             codec_info_found = TRUE;
+
         } else if (cpr_strncasecmp(tmp,sdp_fmtp_codec_param[38].name,
                              sdp_fmtp_codec_param[38].strlen) == 0) {
-                fmtp_ptr = sdp_getnextstrtok(fmtp_ptr, tmp, sizeof(tmp), "; \t", &result1);
-                if (result1 != SDP_SUCCESS) {
-                    fmtp_ptr = sdp_getnextstrtok(fmtp_ptr, tmp, sizeof(tmp), " \t", &result1);
-                    if (result1 != SDP_SUCCESS) {
-                        sdp_attr_fmtp_no_value(sdp_p, "annex_k");
-                        SDP_FREE(temp_ptr);
-                        return SDP_INVALID_PARAMETER;
-                    }
-                }
-                tok = tmp;
-                tok++;
-
-                errno = 0;
-                strtoul_result = strtoul(tok, &strtoul_end, 10);
-
-                if (errno || tok == strtoul_end || strtoul_result == 0 || strtoul_result > USHRT_MAX) {
-                    sdp_attr_fmtp_invalid_value(sdp_p, "annex_k", tok);
-                    SDP_FREE(temp_ptr);
-                    return SDP_INVALID_PARAMETER;
-                }
+            result1 = sdp_get_fmtp_tok_val(sdp_p, &fmtp_ptr, "annex_k", tmp, sizeof(tmp),
+                                           &tok, &strtoul_result, 0, -1, USHRT_MAX);
+            if (result1 != SDP_SUCCESS) { SDP_FREE(temp_ptr); return result1; }
+
                 fmtp_p->fmtp_format = SDP_FMTP_CODEC_INFO;
                 fmtp_p->annex_k_val = (uint16_t) strtoul_result;
                 codec_info_found = TRUE;
+
         } else if (cpr_strncasecmp(tmp,sdp_fmtp_codec_param[39].name,
                                sdp_fmtp_codec_param[39].strlen) == 0) {
-            fmtp_ptr = sdp_getnextstrtok(fmtp_ptr, tmp, sizeof(tmp), "; \t", &result1);
-            if (result1 != SDP_SUCCESS) {
-                fmtp_ptr = sdp_getnextstrtok(fmtp_ptr, tmp, sizeof(tmp), " \t", &result1);
-                if (result1 != SDP_SUCCESS) {
-                    sdp_attr_fmtp_no_value(sdp_p, "annex_n");
-                    SDP_FREE(temp_ptr);
-                    return SDP_INVALID_PARAMETER;
-                }
-            }
-            tok = tmp;
-            tok++;
-
-            errno = 0;
-            strtoul_result = strtoul(tok, &strtoul_end, 10);
-
-            if (errno || tok == strtoul_end || strtoul_result == 0 || strtoul_result > USHRT_MAX) {
-                sdp_attr_fmtp_invalid_value(sdp_p, "annex_n", tok);
-                SDP_FREE(temp_ptr);
-                return SDP_INVALID_PARAMETER;
-            }
+            result1 = sdp_get_fmtp_tok_val(sdp_p, &fmtp_ptr, "annex_n", tmp, sizeof(tmp),
+                                           &tok, &strtoul_result, 0, -1, USHRT_MAX);
+            if (result1 != SDP_SUCCESS) { SDP_FREE(temp_ptr); return result1; }
+
             fmtp_p->fmtp_format = SDP_FMTP_CODEC_INFO;
             fmtp_p->annex_n_val = (uint16_t) strtoul_result;
             codec_info_found = TRUE;
+
         } else if (cpr_strncasecmp(tmp,sdp_fmtp_codec_param[40].name,
                                sdp_fmtp_codec_param[40].strlen) == 0) {
-            fmtp_ptr = sdp_getnextstrtok(fmtp_ptr, tmp, sizeof(tmp), "; \t", &result1);
-            if (result1 != SDP_SUCCESS) {
-                fmtp_ptr = sdp_getnextstrtok(fmtp_ptr, tmp, sizeof(tmp), " \t", &result1);
-                if (result1 != SDP_SUCCESS) {
-                    sdp_attr_fmtp_no_value(sdp_p, "annex_p");
-                    SDP_FREE(temp_ptr);
-                    return SDP_INVALID_PARAMETER;
-                }
-            }
+            result1 = sdp_get_fmtp_tok(sdp_p, &fmtp_ptr, "annex_p", tmp, sizeof(tmp), &tok);
+            if (result1 != SDP_SUCCESS) { SDP_FREE(temp_ptr); return result1; }
+
             fmtp_p->annex_p_val_picture_resize = 0;
             fmtp_p->annex_p_val_warp = 0;
-            tok = tmp;
-            tok++; temp = PL_strtok_r(tok, ",", &strtok_state);
+            temp = PL_strtok_r(tok, ",", &strtok_state);
             if (temp) {
                 iter=1;
                 while (temp != NULL) {
                     errno = 0;
                     strtoul_result = strtoul(temp, &strtoul_end, 10);
 
                     if (errno || temp == strtoul_end || strtoul_result > USHRT_MAX) {
                         break;
@@ -1596,233 +1190,105 @@ sdp_result_e sdp_parse_attr_fmtp (sdp_t 
 
                     temp = PL_strtok_r(NULL, ",", &strtok_state);
                     iter++;
                 }
             }
 
             fmtp_p->fmtp_format = SDP_FMTP_CODEC_INFO;
             codec_info_found = TRUE;
+
         } else if (cpr_strncasecmp(tmp,sdp_fmtp_codec_param[42].name,
                                sdp_fmtp_codec_param[42].strlen) == 0) {
-            fmtp_ptr = sdp_getnextstrtok(fmtp_ptr, tmp, sizeof(tmp), "; \t", &result1);
-            if (result1 != SDP_SUCCESS) {
-                fmtp_ptr = sdp_getnextstrtok(fmtp_ptr, tmp, sizeof(tmp), " \t", &result1);
-                if (result1 != SDP_SUCCESS) {
-                    sdp_attr_fmtp_no_value(sdp_p, "level_asymmetry_allowed");
-                    SDP_FREE(temp_ptr);
-                    return SDP_INVALID_PARAMETER;
-                }
-            }
-            tok = tmp;
-            tok++;
-
-            errno = 0;
-            strtoul_result = strtoul(tok, &strtoul_end, 10);
-
-            if (errno || tok == strtoul_end || strtoul_result > SDP_MAX_LEVEL_ASYMMETRY_ALLOWED_VALUE) {
-                sdp_attr_fmtp_invalid_value(sdp_p, "level_asymmetry_allowed", tok);
-                SDP_FREE(temp_ptr);
-                return SDP_INVALID_PARAMETER;
-            }
+            result1 = sdp_get_fmtp_tok_val(sdp_p, &fmtp_ptr, "level_asymmetry_allowed", tmp, sizeof(tmp),
+                                           &tok, &strtoul_result, -1, -1, SDP_MAX_LEVEL_ASYMMETRY_ALLOWED_VALUE);
+            if (result1 != SDP_SUCCESS) { SDP_FREE(temp_ptr); return result1; }
+
             fmtp_p->fmtp_format = SDP_FMTP_CODEC_INFO;
             fmtp_p->level_asymmetry_allowed = (int) strtoul_result;
             codec_info_found = TRUE;
+
         } else if (cpr_strncasecmp(tmp,sdp_fmtp_codec_param[43].name,
                                    sdp_fmtp_codec_param[43].strlen) == 0) {
-            fmtp_ptr = sdp_getnextstrtok(fmtp_ptr, tmp, sizeof(tmp), "; \t", &result1);
-            if (result1 != SDP_SUCCESS) {
-                    fmtp_ptr = sdp_getnextstrtok(fmtp_ptr, tmp, sizeof(tmp), " \t", &result1);
-                if (result1 != SDP_SUCCESS) {
-                    sdp_attr_fmtp_no_value(sdp_p, "maxaveragebitrate");
-                    SDP_FREE(temp_ptr);
-                    return SDP_INVALID_PARAMETER;
-                }
-            }
-            tok = tmp;
-            tok++;
-            errno = 0;
-            strtoul_result = strtoul(tok, &strtoul_end, 10);
-
-            if (errno || tok == strtoul_end || strtoul_result == 0 || strtoul_result > UINT_MAX) {
-                sdp_attr_fmtp_invalid_value(sdp_p, "maxaveragebitrate", tok);
-                SDP_FREE(temp_ptr);
-                return SDP_INVALID_PARAMETER;
-            }
+            result1 = sdp_get_fmtp_tok_val(sdp_p, &fmtp_ptr, "maxaveragebitrate", tmp, sizeof(tmp),
+                                           &tok, &strtoul_result, 0, -1, UINT_MAX);
+            if (result1 != SDP_SUCCESS) { SDP_FREE(temp_ptr); return result1; }
+
             fmtp_p->fmtp_format = SDP_FMTP_CODEC_INFO;
             fmtp_p->maxaveragebitrate = (uint32_t) strtoul_result;
             codec_info_found = TRUE;
 
         } else if (cpr_strncasecmp(tmp,sdp_fmtp_codec_param[44].name,
                                    sdp_fmtp_codec_param[44].strlen) == 0) {
-            fmtp_ptr = sdp_getnextstrtok(fmtp_ptr, tmp, sizeof(tmp), "; \t", &result1);
-            if (result1 != SDP_SUCCESS) {
-                    fmtp_ptr = sdp_getnextstrtok(fmtp_ptr, tmp, sizeof(tmp), " \t", &result1);
-                if (result1 != SDP_SUCCESS) {
-                    sdp_attr_fmtp_no_value(sdp_p, "usedtx");
-                    SDP_FREE(temp_ptr);
-                    return SDP_INVALID_PARAMETER;
-                }
-            }
-            tok = tmp;
-            tok++;
-            errno = 0;
-            strtoul_result = strtoul(tok, &strtoul_end, 10);
-
-            if (errno || tok == strtoul_end || strtoul_result > 1) {
-                sdp_attr_fmtp_invalid_value(sdp_p, "usedtx", tok);
-                SDP_FREE(temp_ptr);
-                return SDP_INVALID_PARAMETER;
-            }
+            result1 = sdp_get_fmtp_tok_val(sdp_p, &fmtp_ptr, "usedtx", tmp, sizeof(tmp),
+                                           &tok, &strtoul_result, -1, -1, 1);
+            if (result1 != SDP_SUCCESS) { SDP_FREE(temp_ptr); return result1; }
+
             fmtp_p->fmtp_format = SDP_FMTP_CODEC_INFO;
             fmtp_p->usedtx = (uint16_t) strtoul_result;
             codec_info_found = TRUE;
 
         } else if (cpr_strncasecmp(tmp,sdp_fmtp_codec_param[45].name,
                                    sdp_fmtp_codec_param[45].strlen) == 0) {
-            fmtp_ptr = sdp_getnextstrtok(fmtp_ptr, tmp, sizeof(tmp), "; \t", &result1);
-            if (result1 != SDP_SUCCESS) {
-                    fmtp_ptr = sdp_getnextstrtok(fmtp_ptr, tmp, sizeof(tmp), " \t", &result1);
-                if (result1 != SDP_SUCCESS) {
-                    sdp_attr_fmtp_no_value(sdp_p, "stereo");
-                    SDP_FREE(temp_ptr);
-                    return SDP_INVALID_PARAMETER;
-                }
-            }
-            tok = tmp;
-            tok++;
-            errno = 0;
-
-            strtoul_result = strtoul(tok, &strtoul_end, 10);
-
-            if (errno || tok == strtoul_end || strtoul_result > 1) {
-                sdp_attr_fmtp_invalid_value(sdp_p, "stereo", tok);
-                SDP_FREE(temp_ptr);
-                return SDP_INVALID_PARAMETER;
-            }
+            result1 = sdp_get_fmtp_tok_val(sdp_p, &fmtp_ptr, "stereo", tmp, sizeof(tmp),
+                                           &tok, &strtoul_result, -1, -1, 1);
+            if (result1 != SDP_SUCCESS) { SDP_FREE(temp_ptr); return result1; }
+
             fmtp_p->fmtp_format = SDP_FMTP_CODEC_INFO;
             fmtp_p->stereo = (uint16_t) strtoul_result;
             codec_info_found = TRUE;
 
         } else if (cpr_strncasecmp(tmp,sdp_fmtp_codec_param[46].name,
                                    sdp_fmtp_codec_param[46].strlen) == 0) {
-            fmtp_ptr = sdp_getnextstrtok(fmtp_ptr, tmp, sizeof(tmp), "; \t", &result1);
-            if (result1 != SDP_SUCCESS) {
-                    fmtp_ptr = sdp_getnextstrtok(fmtp_ptr, tmp, sizeof(tmp), " \t", &result1);
-                if (result1 != SDP_SUCCESS) {
-                    sdp_attr_fmtp_no_value(sdp_p, "useinbandfec");
-                    SDP_FREE(temp_ptr);
-                    return SDP_INVALID_PARAMETER;
-                }
-            }
-            tok = tmp;
-            tok++;
-            errno = 0;
-
-            strtoul_result = strtoul(tok, &strtoul_end, 10);
-
-            if (errno || tok == strtoul_end || strtoul_result > 1) {
-                sdp_attr_fmtp_invalid_value(sdp_p, "useinbandfec", tok);
-                SDP_FREE(temp_ptr);
-                return SDP_INVALID_PARAMETER;
-            }
+            result1 = sdp_get_fmtp_tok_val(sdp_p, &fmtp_ptr, "useinbandfec", tmp, sizeof(tmp),
+                                           &tok, &strtoul_result, -1, -1, 1);
+            if (result1 != SDP_SUCCESS) { SDP_FREE(temp_ptr); return result1; }
+
             fmtp_p->fmtp_format = SDP_FMTP_CODEC_INFO;
             fmtp_p->useinbandfec = (uint16_t) strtoul_result;
             codec_info_found = TRUE;
 
         } else if (cpr_strncasecmp(tmp,sdp_fmtp_codec_param[47].name,
                                        sdp_fmtp_codec_param[47].strlen) == 0) {
-                    fmtp_ptr = sdp_getnextstrtok(fmtp_ptr, tmp, sizeof(tmp), "; \t", &result1);
-                    if (result1 != SDP_SUCCESS) {
-                        fmtp_ptr = sdp_getnextstrtok(fmtp_ptr, tmp, sizeof(tmp), " \t", &result1);
-                        if (result1 != SDP_SUCCESS) {
-                            sdp_attr_fmtp_no_value(sdp_p, "maxcodedaudiobandwidth");
-                            sdp_p->conf_p->num_invalid_param++;
-                            SDP_FREE(temp_ptr);
-                            return SDP_INVALID_PARAMETER;
-                        }
-                    }
-                    tok = tmp;
-                    tok++;
+            result1 = sdp_get_fmtp_tok(sdp_p, &fmtp_ptr, "maxcodedaudiobandwidth", tmp, sizeof(tmp), &tok);
+            // this one is different for some reason. Most others don't increment
+            // the num_invalid_param field. (mjf)
+            if (result1 == SDP_INVALID_PARAMETER) { sdp_p->conf_p->num_invalid_param++; }
+            if (result1 != SDP_SUCCESS) { SDP_FREE(temp_ptr); return result1; }
+
                     fmtp_p->fmtp_format = SDP_FMTP_CODEC_INFO;
                     sstrncpy(fmtp_p->maxcodedaudiobandwidth , tok, sizeof(fmtp_p->maxcodedaudiobandwidth));
                     codec_info_found = TRUE;
 
         } else if (cpr_strncasecmp(tmp,sdp_fmtp_codec_param[48].name,
                                    sdp_fmtp_codec_param[48].strlen) == 0) {
-            fmtp_ptr = sdp_getnextstrtok(fmtp_ptr, tmp, sizeof(tmp), "; \t", &result1);
-            if (result1 != SDP_SUCCESS) {
-                fmtp_ptr = sdp_getnextstrtok(fmtp_ptr, tmp, sizeof(tmp), " \t", &result1);
-                if (result1 != SDP_SUCCESS) {
-                    sdp_attr_fmtp_no_value(sdp_p, "cbr");
-                    SDP_FREE(temp_ptr);
-                    return SDP_INVALID_PARAMETER;
-                }
-            }
-            tok = tmp;
-            tok++;
-            errno = 0;
-
-            strtoul_result = strtoul(tok, &strtoul_end, 10);
-
-            if (errno || tok == strtoul_end || strtoul_result > 1) {
-                sdp_attr_fmtp_invalid_value(sdp_p, "cbr", tok);
-                SDP_FREE(temp_ptr);
-                return SDP_INVALID_PARAMETER;
-            }
+            result1 = sdp_get_fmtp_tok_val(sdp_p, &fmtp_ptr, "cbr", tmp, sizeof(tmp),
+                                           &tok, &strtoul_result, -1, -1, 1);
+            if (result1 != SDP_SUCCESS) { SDP_FREE(temp_ptr); return result1; }
+
             fmtp_p->fmtp_format = SDP_FMTP_CODEC_INFO;
             fmtp_p->cbr = (uint16_t) strtoul_result;
             codec_info_found = TRUE;
+
         } else if (cpr_strncasecmp(tmp,sdp_fmtp_codec_param[49].name,
                                    sdp_fmtp_codec_param[49].strlen) == 0) {
-            fmtp_ptr = sdp_getnextstrtok(fmtp_ptr, tmp, sizeof(tmp), "; \t",
-                                         &result1);
-            if (result1 != SDP_SUCCESS) {
-                fmtp_ptr = sdp_getnextstrtok(fmtp_ptr, tmp, sizeof(tmp),
-                                             " \t", &result1);
-                if (result1 != SDP_SUCCESS) {
-                    sdp_attr_fmtp_no_value(sdp_p, "max-fr");
-                    SDP_FREE(temp_ptr);
-                    return SDP_INVALID_PARAMETER;
-                }
-            }
-            tok = tmp;
-            tok++;
-            errno = 0;
-            strtoul_result = strtoul(tok, &strtoul_end, 10);
-            if (errno || tok == strtoul_end || strtoul_result == 0 ||
-                strtoul_result > UINT_MAX) {
-                sdp_attr_fmtp_invalid_value(sdp_p, "max-fr", tok);
-                SDP_FREE(temp_ptr);
-                return SDP_INVALID_PARAMETER;
-            }
+            result1 = sdp_get_fmtp_tok_val(sdp_p, &fmtp_ptr, "max-fr", tmp, sizeof(tmp),
+                                           &tok, &strtoul_result, 0, -1, UINT_MAX);
+            if (result1 != SDP_SUCCESS) { SDP_FREE(temp_ptr); return result1; }
+
             fmtp_p->fmtp_format = SDP_FMTP_CODEC_INFO;
             fmtp_p->max_fr = (uint32_t) strtoul_result;
             codec_info_found = TRUE;
+
         } else if (cpr_strncasecmp(tmp,sdp_fmtp_codec_param[50].name,
                                    sdp_fmtp_codec_param[50].strlen) == 0) {
-            fmtp_ptr = sdp_getnextstrtok(fmtp_ptr, tmp, sizeof(tmp), "; \t", &result1);
-            if (result1 != SDP_SUCCESS) {
-                    fmtp_ptr = sdp_getnextstrtok(fmtp_ptr, tmp, sizeof(tmp), " \t", &result1);
-                if (result1 != SDP_SUCCESS) {
-                    sdp_attr_fmtp_no_value(sdp_p, "maxplaybackrate");
-                    SDP_FREE(temp_ptr);
-                    return SDP_INVALID_PARAMETER;
-                }
-            }
-            tok = tmp;
-            tok++;
-            errno = 0;
-            strtoul_result = strtoul(tok, &strtoul_end, 10);
-
-            if (errno || tok == strtoul_end || strtoul_result == 0 || strtoul_result > UINT_MAX) {
-                sdp_attr_fmtp_invalid_value(sdp_p, "maxplaybackrate", tok);
-                SDP_FREE(temp_ptr);
-                return SDP_INVALID_PARAMETER;
-            }
+            result1 = sdp_get_fmtp_tok_val(sdp_p, &fmtp_ptr, "maxplaybackrate", tmp, sizeof(tmp),
+                                           &tok, &strtoul_result, 0, -1, UINT_MAX);
+            if (result1 != SDP_SUCCESS) { SDP_FREE(temp_ptr); return result1; }
+
             fmtp_p->fmtp_format = SDP_FMTP_CODEC_INFO;
             fmtp_p->maxplaybackrate = (uint32_t) strtoul_result;
             codec_info_found = TRUE;
 
         } else if (fmtp_ptr != NULL && *fmtp_ptr == '\n') {
             temp=PL_strtok_r(tmp, ";", &strtok_state);
             if (temp) {
                 if (sdp_p->debug_flag[SDP_DEBUG_TRACE]) {
@@ -1857,18 +1323,19 @@ sdp_result_e sdp_parse_attr_fmtp (sdp_t 
             temp=PL_strtok_r(tmp, "/", &strtok_state);
             if (temp) {
                 iter = 0;
                 while (temp != NULL) {
                     errno = 0;
                     strtoul_result = strtoul(temp, &strtoul_end, 10);
 
                     if (errno ||
-                        temp == strtoul_end || strtoul_result > USHRT_MAX) {
-                        continue;
+                       temp == strtoul_end || strtoul_result > USHRT_MAX) {
+                      temp = NULL;
+                      continue;
                     }
                     fmtp_p->redundant_encodings[iter++] =
                         (uint8_t)strtoul_result;
                     temp=PL_strtok_r(NULL, "/", &strtok_state);
                 }
             } /* if (temp) */
         } else if (SDP_SUCCESS == sdp_verify_attr_fmtp_telephone_event(tmp)) {
           // XXX Note that DTMF fmtp will fall into here:
--- a/media/webrtc/signaling/src/sdp/sipcc/sdp_attr_access.c
+++ b/media/webrtc/signaling/src/sdp/sipcc/sdp_attr_access.c
@@ -2404,17 +2404,17 @@ sdp_result_e sdp_attr_copy_fmtp_ranges (
 /* Function:    sdp_attr_get_fmtp_mode
  * Description: Gets the value of the fmtp attribute mode parameter
  *              for the given attribute.
  * Parameters:  sdp_p     The SDP handle returned by sdp_init_description.
  *              level       The level to check for the attribute.
  *              cap_num     The capability number associated with the
  *                          attribute if any.  If none, should be zero.
  *              payload_type payload type.
- * Returns:     mode value
+ * Returns:     mode value or zero if mode attribute not found
  */
 uint32_t sdp_attr_get_fmtp_mode_for_payload_type (sdp_t *sdp_p, uint16_t level,
                                              uint8_t cap_num, uint32_t payload_type)
 {
     uint16_t          num_a_lines = 0;
     int          i;
     sdp_attr_t  *attr_p;
 
--- a/media/webrtc/signaling/test/sdp_unittests.cpp
+++ b/media/webrtc/signaling/test/sdp_unittests.cpp
@@ -436,16 +436,30 @@ TEST_F(SdpTest, parseRtcpFbFoo) {
 TEST_F(SdpTest, parseRtcpFbFooBar) {
   ParseSdp(kVideoSdp + "a=rtcp-fb:120 foo bar\r\n");
 }
 
 TEST_F(SdpTest, parseRtcpFbFooBarBaz) {
   ParseSdp(kVideoSdp + "a=rtcp-fb:120 foo bar baz\r\n");
 }
 
+static const std::string kVideoSdpWithUnknonwBrokenFtmp =
+  "v=0\r\n"
+  "o=- 4294967296 2 IN IP4 127.0.0.1\r\n"
+  "s=SIP Call\r\n"
+  "c=IN IP4 198.51.100.7\r\n"
+  "t=0 0\r\n"
+  "m=video 56436 RTP/SAVPF 120\r\n"
+  "a=rtpmap:120 VP8/90000\r\n"
+  "a=fmtp:122 unknown=10\n"
+  "a=rtpmap:122 red/90000\r\n";
+
+TEST_F(SdpTest, parseUnknownBrokenFtmp) {
+  ParseSdp(kVideoSdpWithUnknonwBrokenFtmp);
+}
 
 TEST_F(SdpTest, parseRtcpFbKitchenSink) {
   ParseSdp(kVideoSdp +
     "a=rtcp-fb:120 ack rpsi\r\n"
     "a=rtcp-fb:120 ack app\r\n"
     "a=rtcp-fb:120 ack app foo\r\n"
     "a=rtcp-fb:120 ack foo bar\r\n"
     "a=rtcp-fb:120 ack foo bar baz\r\n"
@@ -818,29 +832,644 @@ TEST_F(SdpTest, parseExtMap) {
     "a=extmap:1 urn:ietf:params:rtp-hdrext:ssrc-audio-level\r\n");
   ASSERT_STREQ(sdp_attr_get_extmap_uri(sdp_ptr_, 1, 1),
             SDP_EXTMAP_AUDIO_LEVEL);
   ASSERT_EQ(sdp_attr_get_extmap_id(sdp_ptr_, 1, 1),
             1);
 
 }
 
+TEST_F(SdpTest, parseFmtpBitrate) {
+  ParseSdp(kVideoSdp + "a=fmtp:120 bitrate=400\r\n");
+  ASSERT_EQ(400, sdp_attr_get_fmtp_bitrate_type(sdp_ptr_, 1, 0, 1));
+}
+
+TEST_F(SdpTest, parseFmtpBitrateWith0) {
+  ParseSdp(kVideoSdp + "a=fmtp:120 bitrate=0\r\n");
+  ASSERT_EQ(SDP_INVALID_VALUE, sdp_attr_get_fmtp_bitrate_type(sdp_ptr_, 1, 0, 1));
+}
+
+TEST_F(SdpTest, parseFmtpBitrateWith32001) {
+  ParseSdp(kVideoSdp + "a=fmtp:120 bitrate=32001\r\n");
+  ASSERT_EQ(32001, sdp_attr_get_fmtp_bitrate_type(sdp_ptr_, 1, 0, 1));
+}
+
+TEST_F(SdpTest, parseFmtpBitrateWith4294967296) {
+  ParseSdp(kVideoSdp + "a=fmtp:120 bitrate=4294967296\r\n");
+  ASSERT_EQ(SDP_INVALID_VALUE, sdp_attr_get_fmtp_bitrate_type(sdp_ptr_, 1, 0, 1));
+}
+
+TEST_F(SdpTest, parseFmtpMode) {
+  ParseSdp(kVideoSdp + "a=fmtp:120 mode=200\r\n");
+  ASSERT_EQ(200U, sdp_attr_get_fmtp_mode_for_payload_type(sdp_ptr_, 1, 0, 120));
+}
+
+TEST_F(SdpTest, parseFmtpModeWith4294967295) {
+  ParseSdp(kVideoSdp + "a=fmtp:120 mode=4294967295\r\n");
+  ASSERT_EQ(4294967295, sdp_attr_get_fmtp_mode_for_payload_type(sdp_ptr_, 1, 0, 120));
+}
+
+TEST_F(SdpTest, parseFmtpModeWith4294967296) {
+  ParseSdp(kVideoSdp + "a=fmtp:120 mode=4294967296\r\n");
+  // returns 0 if not found
+  ASSERT_EQ(0U, sdp_attr_get_fmtp_mode_for_payload_type(sdp_ptr_, 1, 0, 120));
+}
+
+TEST_F(SdpTest, parseFmtpQcif) {
+  ParseSdp(kVideoSdp + "a=fmtp:120 qcif=20\r\n");
+  ASSERT_EQ(20, sdp_attr_get_fmtp_qcif(sdp_ptr_, 1, 0, 1));
+}
+
+TEST_F(SdpTest, parseFmtpQcifWith0) {
+  ParseSdp(kVideoSdp + "a=fmtp:120 qcif=0\r\n");
+  ASSERT_EQ(SDP_INVALID_VALUE, sdp_attr_get_fmtp_qcif(sdp_ptr_, 1, 0, 1));
+}
+
+TEST_F(SdpTest, parseFmtpQcifWith33) {
+  ParseSdp(kVideoSdp + "a=fmtp:120 qcif=33\r\n");
+  ASSERT_EQ(SDP_INVALID_VALUE, sdp_attr_get_fmtp_qcif(sdp_ptr_, 1, 0, 1));
+}
+
+TEST_F(SdpTest, parseFmtpCif) {
+  ParseSdp(kVideoSdp + "a=fmtp:120 cif=11\r\n");
+  ASSERT_EQ(11, sdp_attr_get_fmtp_cif(sdp_ptr_, 1, 0, 1));
+}
+
+TEST_F(SdpTest, parseFmtpCifWith0) {
+  ParseSdp(kVideoSdp + "a=fmtp:120 cif=0\r\n");
+  ASSERT_EQ(SDP_INVALID_VALUE, sdp_attr_get_fmtp_cif(sdp_ptr_, 1, 0, 1));
+}
+
+TEST_F(SdpTest, parseFmtpCifWith33) {
+  ParseSdp(kVideoSdp + "a=fmtp:120 cif=33\r\n");
+  ASSERT_EQ(SDP_INVALID_VALUE, sdp_attr_get_fmtp_cif(sdp_ptr_, 1, 0, 1));
+}
+
+TEST_F(SdpTest, parseFmtpMaxbr) {
+  ParseSdp(kVideoSdp + "a=fmtp:120 maxbr=21\r\n");
+  ASSERT_EQ(21, sdp_attr_get_fmtp_maxbr(sdp_ptr_, 1, 0, 1));
+}
+
+TEST_F(SdpTest, parseFmtpMaxbrWith0) {
+  ParseSdp(kVideoSdp + "a=fmtp:120 maxbr=0\r\n");
+  ASSERT_EQ(SDP_INVALID_VALUE, sdp_attr_get_fmtp_maxbr(sdp_ptr_, 1, 0, 1));
+}
+
+TEST_F(SdpTest, parseFmtpMaxbrWith65536) {
+  ParseSdp(kVideoSdp + "a=fmtp:120 maxbr=65536\r\n");
+  ASSERT_EQ(SDP_INVALID_VALUE, sdp_attr_get_fmtp_maxbr(sdp_ptr_, 1, 0, 1));
+}
+
+TEST_F(SdpTest, parseFmtpSqcif) {
+  ParseSdp(kVideoSdp + "a=fmtp:120 sqcif=6\r\n");
+  ASSERT_EQ(6, sdp_attr_get_fmtp_sqcif(sdp_ptr_, 1, 0, 1));
+}
+
+TEST_F(SdpTest, parseFmtpSqcifWith0) {
+  ParseSdp(kVideoSdp + "a=fmtp:120 sqcif=0\r\n");
+  ASSERT_EQ(SDP_INVALID_VALUE, sdp_attr_get_fmtp_sqcif(sdp_ptr_, 1, 0, 1));
+}
+
+TEST_F(SdpTest, parseFmtpSqcifWith33) {
+  ParseSdp(kVideoSdp + "a=fmtp:120 sqcif=33\r\n");
+  ASSERT_EQ(SDP_INVALID_VALUE, sdp_attr_get_fmtp_sqcif(sdp_ptr_, 1, 0, 1));
+}
+
+TEST_F(SdpTest, parseFmtpCif4) {
+  ParseSdp(kVideoSdp + "a=fmtp:120 cif4=11\r\n");
+  ASSERT_EQ(11, sdp_attr_get_fmtp_cif4(sdp_ptr_, 1, 0, 1));
+}
+
+TEST_F(SdpTest, parseFmtpCif4With0) {
+  ParseSdp(kVideoSdp + "a=fmtp:120 cif4=0\r\n");
+  ASSERT_EQ(SDP_INVALID_VALUE, sdp_attr_get_fmtp_cif4(sdp_ptr_, 1, 0, 1));
+}
+
+TEST_F(SdpTest, parseFmtpCif4With33) {
+  ParseSdp(kVideoSdp + "a=fmtp:120 cif4=33\r\n");
+  ASSERT_EQ(SDP_INVALID_VALUE, sdp_attr_get_fmtp_cif4(sdp_ptr_, 1, 0, 1));
+}
+
+TEST_F(SdpTest, parseFmtpCif16) {
+  ParseSdp(kVideoSdp + "a=fmtp:120 cif16=11\r\n");
+  ASSERT_EQ(11, sdp_attr_get_fmtp_cif16(sdp_ptr_, 1, 0, 1));
+}
+
+TEST_F(SdpTest, parseFmtpCif16With0) {
+  ParseSdp(kVideoSdp + "a=fmtp:120 cif16=0\r\n");
+  ASSERT_EQ(SDP_INVALID_VALUE, sdp_attr_get_fmtp_cif16(sdp_ptr_, 1, 0, 1));
+}
+
+TEST_F(SdpTest, parseFmtpCif16With33) {
+  ParseSdp(kVideoSdp + "a=fmtp:120 cif16=33\r\n");
+  ASSERT_EQ(SDP_INVALID_VALUE, sdp_attr_get_fmtp_cif16(sdp_ptr_, 1, 0, 1));
+}
+
+TEST_F(SdpTest, parseFmtpBpp) {
+  ParseSdp(kVideoSdp + "a=fmtp:120 bpp=7\r\n");
+  ASSERT_EQ(7, sdp_attr_get_fmtp_bpp(sdp_ptr_, 1, 0, 1));
+}
+
+TEST_F(SdpTest, parseFmtpBppWith0) {
+  ParseSdp(kVideoSdp + "a=fmtp:120 bpp=0\r\n");
+  ASSERT_EQ(SDP_INVALID_VALUE, sdp_attr_get_fmtp_bpp(sdp_ptr_, 1, 0, 1));
+}
+
+TEST_F(SdpTest, parseFmtpBppWith65536) {
+  ParseSdp(kVideoSdp + "a=fmtp:120 bpp=65536\r\n");
+  ASSERT_EQ(SDP_INVALID_VALUE, sdp_attr_get_fmtp_bpp(sdp_ptr_, 1, 0, 1));
+}
+
+TEST_F(SdpTest, parseFmtpHrd) {
+  ParseSdp(kVideoSdp + "a=fmtp:120 hrd=800\r\n");
+  ASSERT_EQ(800, sdp_attr_get_fmtp_hrd(sdp_ptr_, 1, 0, 1));
+}
+
+TEST_F(SdpTest, parseFmtpHrdWith0) {
+  ParseSdp(kVideoSdp + "a=fmtp:120 hrd=0\r\n");
+  ASSERT_EQ(SDP_INVALID_VALUE, sdp_attr_get_fmtp_hrd(sdp_ptr_, 1, 0, 1));
+}
+
+TEST_F(SdpTest, parseFmtpHrdWith65536) {
+  ParseSdp(kVideoSdp + "a=fmtp:120 hrd=65536\r\n");
+  ASSERT_EQ(SDP_INVALID_VALUE, sdp_attr_get_fmtp_hrd(sdp_ptr_, 1, 0, 1));
+}
+
+TEST_F(SdpTest, parseFmtpProfile) {
+  ParseSdp(kVideoSdp + "a=fmtp:120 profile=4\r\n");
+  ASSERT_EQ(4, sdp_attr_get_fmtp_profile(sdp_ptr_, 1, 0, 1));
+}
+
+TEST_F(SdpTest, parseFmtpProfileWith11) {
+  ParseSdp(kVideoSdp + "a=fmtp:120 profile=11\r\n");
+  ASSERT_EQ(SDP_INVALID_VALUE, sdp_attr_get_fmtp_profile(sdp_ptr_, 1, 0, 1));
+}
+
+TEST_F(SdpTest, parseFmtpLevel) {
+  ParseSdp(kVideoSdp + "a=fmtp:120 level=56\r\n");
+  ASSERT_EQ(56, sdp_attr_get_fmtp_level(sdp_ptr_, 1, 0, 1));
+}
+
+TEST_F(SdpTest, parseFmtpLevelWith101) {
+  ParseSdp(kVideoSdp + "a=fmtp:120 level=101\r\n");
+  ASSERT_EQ(SDP_INVALID_VALUE, sdp_attr_get_fmtp_level(sdp_ptr_, 1, 0, 1));
+}
+
+TEST_F(SdpTest, parseFmtpPacketizationMode) {
+  ParseSdp(kVideoSdp + "a=fmtp:120 packetization-mode=1\r\n");
+  uint16_t packetizationMode;
+  ASSERT_EQ(SDP_SUCCESS, sdp_attr_get_fmtp_pack_mode(sdp_ptr_, 1, 0, 1, &packetizationMode));
+  ASSERT_EQ(1, packetizationMode);
+}
+
+TEST_F(SdpTest, parseFmtpPacketizationModeWith3) {
+  ParseSdp(kVideoSdp + "a=fmtp:120 packetization-mode=3\r\n");
+  ASSERT_EQ(SDP_INVALID_PARAMETER, sdp_attr_get_fmtp_pack_mode(sdp_ptr_, 1, 0, 1, nullptr));
+}
+
+TEST_F(SdpTest, parseFmtpInterleavingDepth) {
+  ParseSdp(kVideoSdp + "a=fmtp:120 sprop-interleaving-depth=566\r\n");
+  uint16_t interleavingDepth;
+  ASSERT_EQ(SDP_SUCCESS, sdp_attr_get_fmtp_interleaving_depth(sdp_ptr_, 1, 0, 1, &interleavingDepth));
+  ASSERT_EQ(566, interleavingDepth);
+}
+
+TEST_F(SdpTest, parseFmtpInterleavingDepthWith0) {
+  ParseSdp(kVideoSdp + "a=fmtp:120 sprop-interleaving-depth=0\r\n");
+  ASSERT_EQ(SDP_INVALID_PARAMETER, sdp_attr_get_fmtp_interleaving_depth(sdp_ptr_, 1, 0, 1, nullptr));
+}
+
+TEST_F(SdpTest, parseFmtpInterleavingDepthWith65536) {
+  ParseSdp(kVideoSdp + "a=fmtp:120 sprop-interleaving-depth=65536\r\n");
+  ASSERT_EQ(SDP_INVALID_PARAMETER, sdp_attr_get_fmtp_interleaving_depth(sdp_ptr_, 1, 0, 1, nullptr));
+}
+
+TEST_F(SdpTest, parseFmtpDeintBuf) {
+  ParseSdp(kVideoSdp + "a=fmtp:120 sprop-deint-buf-req=4294967295\r\n");
+  uint32_t deintBuf;
+  ASSERT_EQ(SDP_SUCCESS, sdp_attr_get_fmtp_deint_buf_req(sdp_ptr_, 1, 0, 1, &deintBuf));
+  ASSERT_EQ(4294967295, deintBuf);
+}
+
+TEST_F(SdpTest, parseFmtpDeintBufWith0) {
+  ParseSdp(kVideoSdp + "a=fmtp:120 sprop-deint-buf-req=0\r\n");
+  uint32_t deintBuf;
+  ASSERT_EQ(SDP_SUCCESS, sdp_attr_get_fmtp_deint_buf_req(sdp_ptr_, 1, 0, 1, &deintBuf));
+  ASSERT_EQ(0U, deintBuf);
+}
+
+TEST_F(SdpTest, parseFmtpDeintBufWith4294967296) {
+  ParseSdp(kVideoSdp + "a=fmtp:120 sprop-deint-buf-req=4294967296\r\n");
+  ASSERT_EQ(SDP_INVALID_PARAMETER, sdp_attr_get_fmtp_deint_buf_req(sdp_ptr_, 1, 0, 1, nullptr));
+}
+
+TEST_F(SdpTest, parseFmtpMaxDonDiff) {
+  ParseSdp(kVideoSdp + "a=fmtp:120 sprop-max-don-diff=5678\r\n");
+  uint32_t maxDonDiff;
+  ASSERT_EQ(SDP_SUCCESS, sdp_attr_get_fmtp_max_don_diff(sdp_ptr_, 1, 0, 1, &maxDonDiff));
+  ASSERT_EQ(5678U, maxDonDiff);
+}
+
+TEST_F(SdpTest, parseFmtpMaxDonDiffWith0) {
+  ParseSdp(kVideoSdp + "a=fmtp:120 sprop-max-don-diff=0\r\n");
+  ASSERT_EQ(SDP_INVALID_PARAMETER, sdp_attr_get_fmtp_max_don_diff(sdp_ptr_, 1, 0, 1, nullptr));
+}
+
+TEST_F(SdpTest, parseFmtpMaxDonDiffWith4294967296) {
+  ParseSdp(kVideoSdp + "a=fmtp:120 sprop-max-don-diff=4294967296\r\n");
+  ASSERT_EQ(SDP_INVALID_PARAMETER, sdp_attr_get_fmtp_max_don_diff(sdp_ptr_, 1, 0, 1, nullptr));
+}
+
+TEST_F(SdpTest, parseFmtpInitBufTime) {
+  ParseSdp(kVideoSdp + "a=fmtp:120 sprop-init-buf-time=4294967295\r\n");
+  uint32_t initBufTime;
+  ASSERT_EQ(SDP_SUCCESS, sdp_attr_get_fmtp_init_buf_time(sdp_ptr_, 1, 0, 1, &initBufTime));
+  ASSERT_EQ(4294967295, initBufTime);
+}
+
+TEST_F(SdpTest, parseFmtpInitBufTimeWith0) {
+  ParseSdp(kVideoSdp + "a=fmtp:120 sprop-init-buf-time=0\r\n");
+  uint32_t initBufTime;
+  ASSERT_EQ(SDP_SUCCESS, sdp_attr_get_fmtp_init_buf_time(sdp_ptr_, 1, 0, 1, &initBufTime));
+  ASSERT_EQ(0U, initBufTime);
+}
+
+TEST_F(SdpTest, parseFmtpInitBufTimeWith4294967296) {
+  ParseSdp(kVideoSdp + "a=fmtp:120 sprop-init-buf-time=4294967296\r\n");
+  ASSERT_EQ(SDP_INVALID_PARAMETER, sdp_attr_get_fmtp_init_buf_time(sdp_ptr_, 1, 0, 1, nullptr));
+}
+
+TEST_F(SdpTest, parseFmtpMaxMbps) {
+  ParseSdp(kVideoSdp + "a=fmtp:120 max-mbps=46789\r\n");
+  uint32_t maxMpbs;
+  ASSERT_EQ(SDP_SUCCESS, sdp_attr_get_fmtp_max_mbps(sdp_ptr_, 1, 0, 1, &maxMpbs));
+  ASSERT_EQ(46789U, maxMpbs);
+}
+
+TEST_F(SdpTest, parseFmtpMaxMbpsWith0) {
+  ParseSdp(kVideoSdp + "a=fmtp:120 max-mbps=0\r\n");
+  ASSERT_EQ(SDP_INVALID_PARAMETER, sdp_attr_get_fmtp_max_mbps(sdp_ptr_, 1, 0, 1, nullptr));
+}
+
+TEST_F(SdpTest, parseFmtpMaxMbpsWith4294967296) {
+  ParseSdp(kVideoSdp + "a=fmtp:120 max-mbps=4294967296\r\n");
+  ASSERT_EQ(SDP_INVALID_PARAMETER, sdp_attr_get_fmtp_max_mbps(sdp_ptr_, 1, 0, 1, nullptr));
+}
+
+TEST_F(SdpTest, parseFmtpMaxCpb) {
+  ParseSdp(kVideoSdp + "a=fmtp:120 max-cpb=47891\r\n");
+  uint32_t maxCpb;
+  ASSERT_EQ(SDP_SUCCESS, sdp_attr_get_fmtp_max_cpb(sdp_ptr_, 1, 0, 1, &maxCpb));
+  ASSERT_EQ(47891U, maxCpb);
+}
+
+TEST_F(SdpTest, parseFmtpMaxCpbWith0) {
+  ParseSdp(kVideoSdp + "a=fmtp:120 max-cpb=0\r\n");
+  ASSERT_EQ(SDP_INVALID_PARAMETER, sdp_attr_get_fmtp_max_cpb(sdp_ptr_, 1, 0, 1, nullptr));
+}
+
+TEST_F(SdpTest, parseFmtpMaxCpbWith4294967296) {
+  ParseSdp(kVideoSdp + "a=fmtp:120 max-cpb=4294967296\r\n");
+  ASSERT_EQ(SDP_INVALID_PARAMETER, sdp_attr_get_fmtp_max_cpb(sdp_ptr_, 1, 0, 1, nullptr));
+}
+
+TEST_F(SdpTest, parseFmtpMaxDpb) {
+  ParseSdp(kVideoSdp + "a=fmtp:120 max-dpb=47892\r\n");
+  uint32_t maxDpb;
+  ASSERT_EQ(SDP_SUCCESS, sdp_attr_get_fmtp_max_dpb(sdp_ptr_, 1, 0, 1, &maxDpb));
+  ASSERT_EQ(47892U, maxDpb);
+}
+
+TEST_F(SdpTest, parseFmtpMaxDpbWith0) {
+  ParseSdp(kVideoSdp + "a=fmtp:120 max-dpb=0\r\n");
+  ASSERT_EQ(SDP_INVALID_PARAMETER, sdp_attr_get_fmtp_max_dpb(sdp_ptr_, 1, 0, 1, nullptr));
+}
+
+TEST_F(SdpTest, parseFmtpMaxDpbWith4294967296) {
+  ParseSdp(kVideoSdp + "a=fmtp:120 max-dpb=4294967296\r\n");
+  ASSERT_EQ(SDP_INVALID_PARAMETER, sdp_attr_get_fmtp_max_dpb(sdp_ptr_, 1, 0, 1, nullptr));
+}
+
+TEST_F(SdpTest, parseFmtpMaxBr) {
+  ParseSdp(kVideoSdp + "a=fmtp:120 max-br=47893\r\n");
+  uint32_t maxBr;
+  ASSERT_EQ(SDP_SUCCESS, sdp_attr_get_fmtp_max_br(sdp_ptr_, 1, 0, 1, &maxBr));
+  ASSERT_EQ(47893U, maxBr);
+}
+
+TEST_F(SdpTest, parseFmtpMaxBrWith0) {
+  ParseSdp(kVideoSdp + "a=fmtp:120 max-br=0\r\n");
+  ASSERT_EQ(SDP_INVALID_PARAMETER, sdp_attr_get_fmtp_max_br(sdp_ptr_, 1, 0, 1, nullptr));
+}
+
+TEST_F(SdpTest, parseFmtpMaxBrWith4294967296) {
+  ParseSdp(kVideoSdp + "a=fmtp:120 max-br=4294967296\r\n");
+  ASSERT_EQ(SDP_INVALID_PARAMETER, sdp_attr_get_fmtp_max_br(sdp_ptr_, 1, 0, 1, nullptr));
+}
+
+TEST_F(SdpTest, parseFmtpRedundantPicCap) {
+  ParseSdp(kVideoSdp + "a=fmtp:120 redundant-pic-cap=1\r\n");
+  ASSERT_EQ(1, sdp_attr_fmtp_is_redundant_pic_cap(sdp_ptr_, 1, 0, 1));
+}
+
+TEST_F(SdpTest, parseFmtpRedundantPicCapWith0) {
+  ParseSdp(kVideoSdp + "a=fmtp:120 redundant-pic-cap=0\r\n");
+  ASSERT_EQ(0, sdp_attr_fmtp_is_redundant_pic_cap(sdp_ptr_, 1, 0, 1));
+}
+
+TEST_F(SdpTest, parseFmtpRedundantPicCapWith2) {
+  ParseSdp(kVideoSdp + "a=fmtp:120 redundant-pic-cap=2\r\n");
+  ASSERT_EQ(0, sdp_attr_fmtp_is_redundant_pic_cap(sdp_ptr_, 1, 0, 1));
+}
+
+TEST_F(SdpTest, parseFmtpDeintBufCap) {
+  ParseSdp(kVideoSdp + "a=fmtp:120 deint-buf-cap=4294967295\r\n");
+  uint32_t deintBufCap;
+  ASSERT_EQ(SDP_SUCCESS, sdp_attr_get_fmtp_deint_buf_cap(sdp_ptr_, 1, 0, 1, &deintBufCap));
+  ASSERT_EQ(4294967295, deintBufCap);
+}
+
+TEST_F(SdpTest, parseFmtpDeintBufCapWith0) {
+  ParseSdp(kVideoSdp + "a=fmtp:120 deint-buf-cap=0\r\n");
+  uint32_t deintBufCap;
+  ASSERT_EQ(SDP_SUCCESS, sdp_attr_get_fmtp_deint_buf_cap(sdp_ptr_, 1, 0, 1, &deintBufCap));
+  ASSERT_EQ(0U, deintBufCap);
+}
+
+TEST_F(SdpTest, parseFmtpDeintBufCapWith4294967296) {
+  ParseSdp(kVideoSdp + "a=fmtp:120 deint-buf-cap=4294967296\r\n");
+  ASSERT_EQ(SDP_INVALID_PARAMETER, sdp_attr_get_fmtp_deint_buf_cap(sdp_ptr_, 1, 0, 1, nullptr));
+}
+
+TEST_F(SdpTest, parseFmtpMaxRcmdNaluSize) {
+  ParseSdp(kVideoSdp + "a=fmtp:120 max-rcmd-nalu-size=4294967295\r\n");
+  uint32_t maxRcmdNaluSize;
+  ASSERT_EQ(SDP_SUCCESS, sdp_attr_get_fmtp_max_rcmd_nalu_size(sdp_ptr_, 1, 0, 1, &maxRcmdNaluSize));
+  ASSERT_EQ(4294967295, maxRcmdNaluSize);
+}
+
+TEST_F(SdpTest, parseFmtpMaxRcmdNaluSizeWith0) {
+  ParseSdp(kVideoSdp + "a=fmtp:120 max-rcmd-nalu-size=0\r\n");
+  uint32_t maxRcmdNaluSize;
+  ASSERT_EQ(SDP_SUCCESS, sdp_attr_get_fmtp_max_rcmd_nalu_size(sdp_ptr_, 1, 0, 1, &maxRcmdNaluSize));
+  ASSERT_EQ(0U, maxRcmdNaluSize);
+}
+
+TEST_F(SdpTest, parseFmtpMaxRcmdNaluSizeWith4294967296) {
+  ParseSdp(kVideoSdp + "a=fmtp:120 max-rcmd-nalu-size=4294967296\r\n");
+  ASSERT_EQ(SDP_INVALID_PARAMETER, sdp_attr_get_fmtp_max_rcmd_nalu_size(sdp_ptr_, 1, 0, 1, nullptr));
+}
+
+TEST_F(SdpTest, parseFmtpParameterAdd) {
+  ParseSdp(kVideoSdp + "a=fmtp:120 parameter-add=1\r\n");
+  ASSERT_EQ(1, sdp_attr_fmtp_is_parameter_add(sdp_ptr_, 1, 0, 1));
+}
+
+TEST_F(SdpTest, parseFmtpParameterAddWith0) {
+  ParseSdp(kVideoSdp + "a=fmtp:120 parameter-add=0\r\n");
+  ASSERT_EQ(0, sdp_attr_fmtp_is_parameter_add(sdp_ptr_, 1, 0, 1));
+}
+
+TEST_F(SdpTest, parseFmtpParameterAddWith2) {
+  ParseSdp(kVideoSdp + "a=fmtp:120 parameter-add=2\r\n");
+  ASSERT_EQ(0, sdp_attr_fmtp_is_parameter_add(sdp_ptr_, 1, 0, 1));
+}
+
+TEST_F(SdpTest, parseFmtpAnnexK) {
+  ParseSdp(kVideoSdp + "a=fmtp:120 K=566\r\n");
+  ASSERT_EQ(566, sdp_attr_get_fmtp_annex_k_val(sdp_ptr_, 1, 0, 1));
+}
+
+TEST_F(SdpTest, parseFmtpAnnexKWith0) {
+  ParseSdp(kVideoSdp + "a=fmtp:120 K=0\r\n");
+  ASSERT_EQ(SDP_INVALID_VALUE, sdp_attr_get_fmtp_annex_k_val(sdp_ptr_, 1, 0, 1));
+}
+
+TEST_F(SdpTest, parseFmtpAnnexKWith65536) {
+  ParseSdp(kVideoSdp + "a=fmtp:120 K=65536\r\n");
+  ASSERT_EQ(SDP_INVALID_VALUE, sdp_attr_get_fmtp_annex_k_val(sdp_ptr_, 1, 0, 1));
+}
+
+TEST_F(SdpTest, parseFmtpAnnexN) {
+  ParseSdp(kVideoSdp + "a=fmtp:120 N=4567\r\n");
+  ASSERT_EQ(4567, sdp_attr_get_fmtp_annex_n_val(sdp_ptr_, 1, 0, 1));
+}
+
+TEST_F(SdpTest, parseFmtpAnnexNWith0) {
+  ParseSdp(kVideoSdp + "a=fmtp:120 N=0\r\n");
+  ASSERT_EQ(SDP_INVALID_VALUE, sdp_attr_get_fmtp_annex_n_val(sdp_ptr_, 1, 0, 1));
+}
+
+TEST_F(SdpTest, parseFmtpAnnexNWith65536) {
+  ParseSdp(kVideoSdp + "a=fmtp:120 N=65536\r\n");
+  ASSERT_EQ(SDP_INVALID_VALUE, sdp_attr_get_fmtp_annex_n_val(sdp_ptr_, 1, 0, 1));
+}
+
+TEST_F(SdpTest, parseFmtpAnnexP) {
+  ParseSdp(kVideoSdp + "a=fmtp:120 P=5678,2\r\n");
+  ASSERT_EQ(5678, sdp_attr_get_fmtp_annex_p_picture_resize(sdp_ptr_, 1, 0, 1));
+  ASSERT_EQ(2, sdp_attr_get_fmtp_annex_p_warp(sdp_ptr_, 1, 0, 1));
+}
+
+TEST_F(SdpTest, parseFmtpAnnexPWithResize0) {
+  ParseSdp(kVideoSdp + "a=fmtp:120 P=0,3\r\n");
+  ASSERT_EQ(0, sdp_attr_get_fmtp_annex_p_picture_resize(sdp_ptr_, 1, 0, 1));
+  ASSERT_EQ(3, sdp_attr_get_fmtp_annex_p_warp(sdp_ptr_, 1, 0, 1));
+}
+
+TEST_F(SdpTest, parseFmtpAnnexPWithResize65536) {
+  ParseSdp(kVideoSdp + "a=fmtp:120 P=65536,4\r\n");
+  ASSERT_EQ(0, sdp_attr_get_fmtp_annex_p_picture_resize(sdp_ptr_, 1, 0, 1));
+  // if the first fails, the second will too.  Both default to 0 on failure.
+  ASSERT_EQ(0, sdp_attr_get_fmtp_annex_p_warp(sdp_ptr_, 1, 0, 1));
+}
+
+TEST_F(SdpTest, parseFmtpAnnexPWithWarp65536) {
+  ParseSdp(kVideoSdp + "a=fmtp:120 P=346,65536\r\n");
+  ASSERT_EQ(346, sdp_attr_get_fmtp_annex_p_picture_resize(sdp_ptr_, 1, 0, 1));
+  ASSERT_EQ(0, sdp_attr_get_fmtp_annex_p_warp(sdp_ptr_, 1, 0, 1));
+}
+
+TEST_F(SdpTest, parseFmtpLevelAsymmetryAllowed) {
+  ParseSdp(kVideoSdp + "a=fmtp:120 level-asymmetry-allowed=1\r\n");
+
+  uint16_t levelAsymmetryAllowed;
+  ASSERT_EQ(SDP_SUCCESS, sdp_attr_get_fmtp_level_asymmetry_allowed(sdp_ptr_, 1, 0, 1, &levelAsymmetryAllowed));
+  ASSERT_EQ(1U, levelAsymmetryAllowed);
+}
+
+TEST_F(SdpTest, parseFmtpLevelAsymmetryAllowedWith0) {
+  ParseSdp(kVideoSdp + "a=fmtp:120 level-asymmetry-allowed=0\r\n");
+  uint16_t levelAsymmetryAllowed;
+  ASSERT_EQ(SDP_SUCCESS, sdp_attr_get_fmtp_level_asymmetry_allowed(sdp_ptr_, 1, 0, 1, &levelAsymmetryAllowed));
+  ASSERT_EQ(0U, levelAsymmetryAllowed);
+}
+
+TEST_F(SdpTest, parseFmtpLevelAsymmetryAllowedWith2) {
+  ParseSdp(kVideoSdp + "a=fmtp:120 level-asymmetry-allowed=2\r\n");
+  ASSERT_EQ(SDP_INVALID_PARAMETER, sdp_attr_get_fmtp_level_asymmetry_allowed(sdp_ptr_, 1, 0, 1, nullptr));
+}
+
+TEST_F(SdpTest, parseFmtpMaxAverageBitrate) {
+  ParseSdp(kVideoSdp + "a=fmtp:120 maxaveragebitrate=47893\r\n");
+  uint32_t maxAverageBitrate;
+  ASSERT_EQ(SDP_SUCCESS, sdp_attr_get_fmtp_max_average_bitrate(sdp_ptr_, 1, 0, 1, &maxAverageBitrate));
+  ASSERT_EQ(47893U, maxAverageBitrate);
+}
+
+TEST_F(SdpTest, parseFmtpMaxAverageBitrateWith0) {
+  ParseSdp(kVideoSdp + "a=fmtp:120 maxaveragebitrate=0\r\n");
+  ASSERT_EQ(SDP_INVALID_PARAMETER, sdp_attr_get_fmtp_max_average_bitrate(sdp_ptr_, 1, 0, 1, nullptr));
+}
+
+TEST_F(SdpTest, parseFmtpMaxAverageBitrateWith4294967296) {
+  ParseSdp(kVideoSdp + "a=fmtp:120 maxaveragebitrate=4294967296\r\n");
+  ASSERT_EQ(SDP_INVALID_PARAMETER, sdp_attr_get_fmtp_max_average_bitrate(sdp_ptr_, 1, 0, 1, nullptr));
+}
+
+TEST_F(SdpTest, parseFmtpUsedTx) {
+  ParseSdp(kVideoSdp + "a=fmtp:120 usedtx=1\r\n");
+  tinybool usedTx;
+  ASSERT_EQ(SDP_SUCCESS, sdp_attr_get_fmtp_usedtx(sdp_ptr_, 1, 0, 1, &usedTx));
+  ASSERT_EQ(1, usedTx);
+}
+
+TEST_F(SdpTest, parseFmtpUsedTxWith0) {
+  ParseSdp(kVideoSdp + "a=fmtp:120 usedtx=0\r\n");
+  tinybool usedTx;
+  ASSERT_EQ(SDP_SUCCESS, sdp_attr_get_fmtp_usedtx(sdp_ptr_, 1, 0, 1, &usedTx));
+  ASSERT_EQ(0, usedTx);
+}
+
+TEST_F(SdpTest, parseFmtpUsedTxWith2) {
+  ParseSdp(kVideoSdp + "a=fmtp:120 usedtx=2\r\n");
+  ASSERT_EQ(SDP_INVALID_PARAMETER, sdp_attr_get_fmtp_usedtx(sdp_ptr_, 1, 0, 1, nullptr));
+}
+
+TEST_F(SdpTest, parseFmtpStereo) {
+  ParseSdp(kVideoSdp + "a=fmtp:120 stereo=1\r\n");
+  tinybool stereo;
+  ASSERT_EQ(SDP_SUCCESS, sdp_attr_get_fmtp_stereo(sdp_ptr_, 1, 0, 1, &stereo));
+  ASSERT_EQ(1, stereo);
+}
+
+TEST_F(SdpTest, parseFmtpStereoWith0) {
+  ParseSdp(kVideoSdp + "a=fmtp:120 stereo=0\r\n");
+  tinybool stereo;
+  ASSERT_EQ(SDP_SUCCESS, sdp_attr_get_fmtp_stereo(sdp_ptr_, 1, 0, 1, &stereo));
+  ASSERT_EQ(0, stereo);
+}
+
+TEST_F(SdpTest, parseFmtpStereoWith2) {
+  ParseSdp(kVideoSdp + "a=fmtp:120 stereo=2\r\n");
+  ASSERT_EQ(SDP_INVALID_PARAMETER, sdp_attr_get_fmtp_stereo(sdp_ptr_, 1, 0, 1, nullptr));
+}
+
+TEST_F(SdpTest, parseFmtpUseInBandFec) {
+  ParseSdp(kVideoSdp + "a=fmtp:120 useinbandfec=1\r\n");
+  tinybool useInbandFec;
+  ASSERT_EQ(SDP_SUCCESS, sdp_attr_get_fmtp_useinbandfec(sdp_ptr_, 1, 0, 1, &useInbandFec));
+  ASSERT_EQ(1, useInbandFec);
+}
+
+TEST_F(SdpTest, parseFmtpUseInBandWith0) {
+  ParseSdp(kVideoSdp + "a=fmtp:120 useinbandfec=0\r\n");
+  tinybool useInbandFec;
+  ASSERT_EQ(SDP_SUCCESS, sdp_attr_get_fmtp_useinbandfec(sdp_ptr_, 1, 0, 1, &useInbandFec));
+  ASSERT_EQ(0, useInbandFec);
+}
+
+TEST_F(SdpTest, parseFmtpUseInBandWith2) {
+  ParseSdp(kVideoSdp + "a=fmtp:120 useinbandfec=2\r\n");
+  ASSERT_EQ(SDP_INVALID_PARAMETER, sdp_attr_get_fmtp_useinbandfec(sdp_ptr_, 1, 0, 1, nullptr));
+}
+
+TEST_F(SdpTest, parseFmtpMaxCodedAudioBandwidth) {
+  ParseSdp(kVideoSdp + "a=fmtp:120 maxcodedaudiobandwidth=abcdefg\r\n");
+  char* maxCodedAudioBandwith = sdp_attr_get_fmtp_maxcodedaudiobandwidth(sdp_ptr_, 1, 0, 1);
+  ASSERT_EQ(0, strcmp("abcdefg", maxCodedAudioBandwith));
+}
+
+TEST_F(SdpTest, parseFmtpMaxCodedAudioBandwidthBad) {
+  ParseSdp(kVideoSdp + "a=fmtp:120 maxcodedaudiobandwidth=\r\n");
+  char* maxCodedAudioBandwith = sdp_attr_get_fmtp_maxcodedaudiobandwidth(sdp_ptr_, 1, 0, 1);
+  ASSERT_EQ(0, *maxCodedAudioBandwith);
+}
+
+TEST_F(SdpTest, parseFmtpCbr) {
+  ParseSdp(kVideoSdp + "a=fmtp:120 cbr=1\r\n");
+  tinybool cbr;
+  ASSERT_EQ(SDP_SUCCESS, sdp_attr_get_fmtp_cbr(sdp_ptr_, 1, 0, 1, &cbr));
+  ASSERT_EQ(1, cbr);
+}
+
+TEST_F(SdpTest, parseFmtpCbrWith0) {
+  ParseSdp(kVideoSdp + "a=fmtp:120 cbr=0\r\n");
+  tinybool cbr;
+  ASSERT_EQ(SDP_SUCCESS, sdp_attr_get_fmtp_cbr(sdp_ptr_, 1, 0, 1, &cbr));
+  ASSERT_EQ(0, cbr);
+}
+
+TEST_F(SdpTest, parseFmtpCbrWith2) {
+  ParseSdp(kVideoSdp + "a=fmtp:120 cbr=2\r\n");
+  ASSERT_EQ(SDP_INVALID_PARAMETER, sdp_attr_get_fmtp_cbr(sdp_ptr_, 1, 0, 1, nullptr));
+}
+
+TEST_F(SdpTest, parseFmtpMaxPlaybackRate) {
+  ParseSdp(kVideoSdp + "a=fmtp:120 maxplaybackrate=47900\r\n");
+  sdp_attr_t *attr_p = sdp_find_attr(sdp_ptr_, 1, 0, SDP_ATTR_FMTP, 1);
+  ASSERT_NE(NULL, attr_p);
+  ASSERT_EQ(47900U, attr_p->attr.fmtp.maxplaybackrate);
+}
+
+TEST_F(SdpTest, parseFmtpMaxPlaybackRateWith0) {
+  ParseSdp(kVideoSdp + "a=fmtp:120 maxplaybackrate=0\r\n");
+  sdp_attr_t *attr_p = sdp_find_attr(sdp_ptr_, 1, 0, SDP_ATTR_FMTP, 1);
+  ASSERT_EQ(NULL, attr_p);
+}
+
+TEST_F(SdpTest, parseFmtpMaxPlaybackRateWith4294967296) {
+  ParseSdp(kVideoSdp + "a=fmtp:120 maxplaybackrate=4294967296\r\n");
+  sdp_attr_t *attr_p = sdp_find_attr(sdp_ptr_, 1, 0, SDP_ATTR_FMTP, 1);
+  ASSERT_EQ(NULL, attr_p);
+}
+
 TEST_F(SdpTest, parseFmtpMaxFs) {
   uint32_t val = 0;
   ParseSdp(kVideoSdp + "a=fmtp:120 max-fs=300;max-fr=30\r\n");
   ASSERT_EQ(sdp_attr_get_fmtp_max_fs(sdp_ptr_, 1, 0, 1, &val), SDP_SUCCESS);
   ASSERT_EQ(val, 300U);
 }
+TEST_F(SdpTest, parseFmtpMaxFsWith0) {
+  ParseSdp(kVideoSdp + "a=fmtp:120 max-fs=0\r\n");
+  ASSERT_EQ(SDP_INVALID_PARAMETER, sdp_attr_get_fmtp_max_fs(sdp_ptr_, 1, 0, 1, nullptr));
+}
+
+TEST_F(SdpTest, parseFmtpMaxFsWith4294967296) {
+  ParseSdp(kVideoSdp + "a=fmtp:120 max-fs=4294967296\r\n");
+  ASSERT_EQ(SDP_INVALID_PARAMETER, sdp_attr_get_fmtp_max_fs(sdp_ptr_, 1, 0, 1, nullptr));
+}
+
 TEST_F(SdpTest, parseFmtpMaxFr) {
   uint32_t val = 0;
   ParseSdp(kVideoSdp + "a=fmtp:120 max-fs=300;max-fr=30\r\n");
   ASSERT_EQ(sdp_attr_get_fmtp_max_fr(sdp_ptr_, 1, 0, 1, &val), SDP_SUCCESS);
   ASSERT_EQ(val, 30U);
 }
 
+TEST_F(SdpTest, parseFmtpMaxFrWith0) {
+  ParseSdp(kVideoSdp + "a=fmtp:120 max-fr=0\r\n");
+  ASSERT_EQ(SDP_INVALID_PARAMETER, sdp_attr_get_fmtp_max_fr(sdp_ptr_, 1, 0, 1, nullptr));
+}
+
+TEST_F(SdpTest, parseFmtpMaxFrWith4294967296) {
+  ParseSdp(kVideoSdp + "a=fmtp:120 max-fr=4294967296\r\n");
+  ASSERT_EQ(SDP_INVALID_PARAMETER, sdp_attr_get_fmtp_max_fr(sdp_ptr_, 1, 0, 1, nullptr));
+}
+
 TEST_F(SdpTest, addFmtpMaxFs) {
   InitLocalSdp();
   int level = AddNewMedia(SDP_MEDIA_VIDEO);
   AddNewFmtpMaxFs(level, 300);
   std::string body = SerializeSdp();
   ASSERT_NE(body.find("a=fmtp:120 max-fs=300\r\n"), std::string::npos);
 }
 
--- a/modules/libpref/init/all.js
+++ b/modules/libpref/init/all.js
@@ -340,16 +340,17 @@ pref("media.mp4.enabled", true);
 // just outputs blank frames/audio instead of actually decoding. The blank
 // decoder works on all platforms.
 pref("media.use-blank-decoder", false);
 #ifdef MOZ_WMF
 pref("media.wmf.enabled", true);
 pref("media.wmf.decoder.thread-count", -1);
 pref("media.wmf.low-latency.enabled", false);
 pref("media.wmf.skip-blacklist", false);
+pref("media.wmf.vp9.enabled", true);
 pref("media.windows-media-foundation.allow-d3d11-dxva", true);
 pref("media.wmf.disable-d3d11-for-dlls", "igd10iumd32.dll: 20.19.15.4444, 20.19.15.4424, 20.19.15.4409, 20.19.15.4390, 20.19.15.4380, 20.19.15.4360, 10.18.10.4358, 20.19.15.4331, 20.19.15.4312, 20.19.15.4300, 10.18.15.4281, 10.18.15.4279, 10.18.10.4276, 10.18.15.4268, 10.18.15.4256, 10.18.10.4252, 10.18.15.4248, 10.18.14.4112, 10.18.10.3958, 10.18.10.3496, 10.18.10.3431, 10.18.10.3412, 10.18.10.3355, 9.18.10.3234, 9.18.10.3071, 9.18.10.3055, 9.18.10.3006; igd10umd32.dll: 9.17.10.4229, 9.17.10.3040, 9.17.10.2857, 8.15.10.2274, 8.15.10.2272, 8.15.10.2246, 8.15.10.1840, 8.15.10.1808; igd10umd64.dll: 9.17.10.4229, 10.18.10.3496; isonyvideoprocessor.dll: 4.1.2247.8090, 4.1.2153.6200; tosqep.dll: 1.2.15.526, 1.1.12.201, 1.0.11.318, 1.0.11.215, 1.0.10.1224; tosqep64.dll: 1.1.12.201, 1.0.11.215; nvwgf2um.dll: 10.18.13.6510, 10.18.13.5891, 10.18.13.5887, 10.18.13.5582, 10.18.13.5382, 9.18.13.4195, 9.18.13.3165; atidxx32.dll: 21.19.151.3, 21.19.137.1, 21.19.134.1, 20.19.0.32837, 20.19.0.32832, 8.17.10.682, 8.17.10.671, 8.17.10.661, 8.17.10.648, 8.17.10.644, 8.17.10.625, 8.17.10.605, 8.17.10.581, 8.17.10.569, 8.17.10.560, 8.17.10.545, 8.17.10.539, 8.17.10.531, 8.17.10.525, 8.17.10.520, 8.17.10.519, 8.17.10.514, 8.17.10.511, 8.17.10.494, 8.17.10.489, 8.17.10.483, 8.17.10.453, 8.17.10.451, 8.17.10.441, 8.17.10.436, 8.17.10.432, 8.17.10.425, 8.17.10.418, 8.17.10.414, 8.17.10.401, 8.17.10.395, 8.17.10.385, 8.17.10.378, 8.17.10.362, 8.17.10.355, 8.17.10.342, 8.17.10.331, 8.17.10.318, 8.17.10.310, 8.17.10.286, 8.17.10.269, 8.17.10.261, 8.17.10.247, 8.17.10.240, 8.15.10.212; atidxx64.dll: 21.19.151.3, 21.19.137.1, 21.19.134.1, 20.19.0.32832, 8.17.10.682, 8.17.10.661, 8.17.10.644, 8.17.10.625; nvumdshim.dll: 10.18.13.6822");
 pref("media.wmf.disable-d3d9-for-dlls", "igdumd64.dll: 8.15.10.2189, 8.15.10.2119, 8.15.10.2104, 8.15.10.2102, 8.771.1.0; atiumd64.dll: 7.14.10.833, 7.14.10.867, 7.14.10.885, 7.14.10.903, 7.14.10.911, 8.14.10.768, 9.14.10.1001, 9.14.10.1017, 9.14.10.1080, 9.14.10.1128, 9.14.10.1162, 9.14.10.1171, 9.14.10.1183, 9.14.10.1197, 9.14.10.945, 9.14.10.972, 9.14.10.984, 9.14.10.996");
 #endif
 #if defined(MOZ_FFMPEG)
 #if defined(XP_MACOSX)
 pref("media.ffmpeg.enabled", false);
 #else
@@ -366,19 +367,16 @@ pref("media.gmp.decoder.h264", 0);
 #ifdef MOZ_RAW
 pref("media.raw.enabled", true);
 #endif
 pref("media.ogg.enabled", true);
 pref("media.opus.enabled", true);
 pref("media.wave.enabled", true);
 pref("media.wave.decoder.enabled", true);
 pref("media.webm.enabled", true);
-#if defined(MOZ_FMP4) && defined(MOZ_WMF)
-pref("media.webm.intel_decoder.enabled", false);
-#endif
 
 #ifdef MOZ_APPLEMEDIA
 #ifdef MOZ_WIDGET_UIKIT
 pref("media.mp3.enabled", true);
 #endif
 pref("media.apple.mp3.enabled", true);
 pref("media.apple.mp4.enabled", true);
 #endif
--- a/netwerk/protocol/http/HSTSPrimerListener.cpp
+++ b/netwerk/protocol/http/HSTSPrimerListener.cpp
@@ -31,23 +31,38 @@ HSTSPrimingListener::GetInterface(const 
 {
   return QueryInterface(aIID, aResult);
 }
 
 NS_IMETHODIMP
 HSTSPrimingListener::OnStartRequest(nsIRequest *aRequest,
                                     nsISupports *aContext)
 {
-  nsresult rv = CheckHSTSPrimingRequestStatus(aRequest);
+  nsresult primingResult = CheckHSTSPrimingRequestStatus(aRequest);
   nsCOMPtr<nsIHstsPrimingCallback> callback(mCallback);
   mCallback = nullptr;
 
-  if (NS_FAILED(rv)) {
+  nsCOMPtr<nsITimedChannel> timingChannel =
+    do_QueryInterface(callback);
+  if (timingChannel) {
+    TimeStamp channelCreationTime;
+    nsresult rv = timingChannel->GetChannelCreation(&channelCreationTime);
+    if (NS_SUCCEEDED(rv) && !channelCreationTime.IsNull()) {
+      PRUint32 interval =
+        (PRUint32) (TimeStamp::Now() - channelCreationTime).ToMilliseconds();
+      Telemetry::Accumulate(Telemetry::HSTS_PRIMING_REQUEST_DURATION,
+          (NS_SUCCEEDED(primingResult)) ? NS_LITERAL_CSTRING("success")
+                                        : NS_LITERAL_CSTRING("failure"),
+          interval);
+    }
+  }
+
+  if (NS_FAILED(primingResult)) {
     LOG(("HSTS Priming Failed (request was not approved)"));
-    return callback->OnHSTSPrimingFailed(rv, false);
+    return callback->OnHSTSPrimingFailed(primingResult, false);
   }
 
   LOG(("HSTS Priming Succeeded (request was approved)"));
   return callback->OnHSTSPrimingSucceeded(false);
 }
 
 NS_IMETHODIMP
 HSTSPrimingListener::OnStopRequest(nsIRequest *aRequest,
--- a/security/manager/ssl/ScopedNSSTypes.h
+++ b/security/manager/ssl/ScopedNSSTypes.h
@@ -54,19 +54,16 @@ MapSECStatus(SECStatus rv)
     return NS_OK;
   }
 
   return mozilla::psm::GetXPCOMFromNSSError(PR_GetError());
 }
 
 // Alphabetical order by NSS type
 // Deprecated: use the equivalent UniquePtr templates instead.
-MOZ_TYPE_SPECIFIC_SCOPED_POINTER_TEMPLATE(ScopedPRFileDesc,
-                                          PRFileDesc,
-                                          PR_Close)
 MOZ_TYPE_SPECIFIC_SCOPED_POINTER_TEMPLATE(ScopedCERTCertificate,
                                           CERTCertificate,
                                           CERT_DestroyCertificate)
 MOZ_TYPE_SPECIFIC_SCOPED_POINTER_TEMPLATE(ScopedCERTCertificateList,
                                           CERTCertificateList,
                                           CERT_DestroyCertificateList)
 MOZ_TYPE_SPECIFIC_SCOPED_POINTER_TEMPLATE(ScopedCERTCertificateRequest,
                                           CERTCertificateRequest,
--- a/security/sandbox/mac/Sandbox.mm
+++ b/security/sandbox/mac/Sandbox.mm
@@ -267,16 +267,20 @@ static const char contentSandboxRules[] 
   "      (global-name \"com.apple.cookied\")\n"
   "      (global-name \"com.apple.cache_delete\")\n"
   "      (global-name \"com.apple.pluginkit.pkd\")\n"
   "      (global-name \"com.apple.bird\")\n"
   "      (global-name \"com.apple.ocspd\")\n"
   "      (global-name \"com.apple.cmio.AppleCameraAssistant\")\n"
   "      (global-name \"com.apple.DesktopServicesHelper\"))\n"
   "\n"
+  "; bug 1312273\n"
+  "  (if (= macosMinorVersion 9)\n"
+  "     (allow mach-lookup (global-name \"com.apple.xpcd\")))\n"
+  "\n"
   "  (allow iokit-open\n"
   "      (iokit-user-client-class \"IOHIDParamUserClient\")\n"
   "      (iokit-user-client-class \"IOAudioControlUserClient\")\n"
   "      (iokit-user-client-class \"IOAudioEngineUserClient\")\n"
   "      (iokit-user-client-class \"IGAccelDevice\")\n"
   "      (iokit-user-client-class \"nvDevice\")\n"
   "      (iokit-user-client-class \"nvSharedUserClient\")\n"
   "      (iokit-user-client-class \"nvFermiGLContext\")\n"
--- a/taskcluster/ci/desktop-test/test-sets.yml
+++ b/taskcluster/ci/desktop-test/test-sets.yml
@@ -85,11 +85,13 @@ windows-vm-tests:
 # note: on win, mochitest-a11y and mochitest-chrome come under mochitest-other
 # windows-hw-tests:
 #    - mochitest-clipboard
 #    - mochitest-gpu
 #    - mochitest-other
 
 ccov-code-coverage-tests:
     - mochitest-browser-chrome
+    - mochitest-devtools-chrome
+    - xpcshell
 
 jsdcov-code-coverage-tests:
     - mochitest-browser-chrome
\ No newline at end of file
--- a/taskcluster/ci/desktop-test/tests.yml
+++ b/taskcluster/ci/desktop-test/tests.yml
@@ -320,33 +320,43 @@ mochitest-devtools-chrome:
     suite: mochitest/mochitest-devtools-chrome-chunked
     treeherder-symbol: tc-M(dt)
     loopback-video: true
     max-run-time: 5400
     chunks:
         by-test-platform:
             win.*: 8
             default: 10
+    run-on-projects:
+        by-test-platform:
+            linux64-ccov/opt: []
+            default: ['all']
     e10s:
         by-test-platform:
             # Bug 1242986: linux64/debug mochitest-devtools-chrome e10s is not greened up yet
             linux64/debug: false
+            linux64-ccov/opt: false
             default: both
     mozharness:
         script: desktop_unittest.py
         no-read-buildbot-config: true
         config:
             by-test-platform:
                 win.*:
                     - unittests/win_taskcluster_unittest.py
                 default:
                     - unittests/linux_unittest.py
                     - remove_executables.py
         extra-options:
-            - --mochitest-suite=mochitest-devtools-chrome-chunked
+            by-test-platform:
+                linux64-ccov/opt:
+                    - --mochitest-suite=mochitest-devtools-chrome-chunked
+                    - --code-coverage
+                default:
+                    - --mochitest-suite=mochitest-devtools-chrome-chunked
     instance-size:
         by-test-platform:
             # Bug 1281241: migrating to m3.large instances
             linux64-asan/opt: legacy
             default: default
     # Bug 1296086: high number of intermittents observed with software GL and large instances
     allow-software-gl-layers: false
 
@@ -540,16 +550,20 @@ web-platform-tests-wdspec:
                     - remove_executables.py
         extra-options:
             - --test-type=wdspec
 
 xpcshell:
     description: "xpcshell test run"
     suite: xpcshell
     treeherder-symbol: tc-X()
+    run-on-projects:
+        by-test-platform:
+            linux64-ccov/opt: []
+            default: ['all']
     chunks:
         by-test-platform:
             # win.*: 1
             linux64/debug: 10
             default: 8
     max-run-time: 5400
     e10s: false
     mozharness:
@@ -558,12 +572,17 @@ xpcshell:
         config:
             by-test-platform:
                 win.*:
                     - unittests/win_taskcluster_unittest.py
                 default:
                     - unittests/linux_unittest.py
                     - remove_executables.py
         extra-options:
-            - --xpcshell-suite=xpcshell
+            by-test-platform:
+                linux64-ccov/opt:
+                    - --xpcshell-suite=xpcshell
+                    - --code-coverage
+                default:
+                    - --xpcshell-suite=xpcshell
     # Bug 1281241: migrating to m3.large instances
     instance-size: legacy
     allow-software-gl-layers: false
--- a/taskcluster/taskgraph/transforms/build.py
+++ b/taskcluster/taskgraph/transforms/build.py
@@ -16,16 +16,16 @@ transforms = TransformSequence()
 @transforms.add
 def set_defaults(config, jobs):
     """Set defaults, including those that differ per worker implementation"""
     for job in jobs:
         job['treeherder'].setdefault('kind', 'build')
         job['treeherder'].setdefault('tier', 1)
         if job['worker']['implementation'] in ('docker-worker', 'docker-engine'):
             job['worker'].setdefault('docker-image', {'in-tree': 'desktop-build'})
-            job['worker']['chainOfTrust'] = True
+            job['worker']['chain-of-trust'] = True
             job.setdefault('extra', {})
             job['extra'].setdefault('chainOfTrust', {})
             job['extra']['chainOfTrust'].setdefault('inputs', {})
             job['extra']['chainOfTrust']['inputs']['docker-image'] = {
                 "task-reference": "<docker-image>"
             }
         yield job
--- a/taskcluster/taskgraph/transforms/task.py
+++ b/taskcluster/taskgraph/transforms/task.py
@@ -152,17 +152,17 @@ task_description_schema = Schema({
             # a raw Docker image path (repo/image:tag)
             basestring,
             # an in-tree generated docker image (from `testing/docker/<name>`)
             {'in-tree': basestring}
         ),
 
         # worker features that should be enabled
         Required('relengapi-proxy', default=False): bool,
-        Required('chainOfTrust', default=False): bool,
+        Required('chain-of-trust', default=False): bool,
         Required('taskcluster-proxy', default=False): bool,
         Required('allow-ptrace', default=False): bool,
         Required('loopback-video', default=False): bool,
         Required('loopback-audio', default=False): bool,
 
         # caches to set up for the task
         Optional('caches'): [{
             # only one type is supported by any of the workers right now
@@ -337,17 +337,17 @@ def build_docker_worker_payload(config, 
 
     if worker.get('taskcluster-proxy'):
         features['taskclusterProxy'] = True
 
     if worker.get('allow-ptrace'):
         features['allowPtrace'] = True
         task_def['scopes'].append('docker-worker:feature:allowPtrace')
 
-    if worker.get('chainOfTrust'):
+    if worker.get('chain-of-trust'):
         features['chainOfTrust'] = True
 
     capabilities = {}
 
     for lo in 'audio', 'video':
         if worker.get('loopback-' + lo):
             capitalized = 'loopback' + lo.capitalize()
             devices = capabilities.setdefault('devices', {})
--- a/testing/mach_commands.py
+++ b/testing/mach_commands.py
@@ -315,17 +315,17 @@ class Test(MachCommandBase):
                 res = self._mach_context.commands.dispatch(
                     suite['mach_command'], self._mach_context,
                     **suite['kwargs'])
                 if res:
                     status = res
 
         buckets = {}
         for test in run_tests:
-            key = (test['flavor'], test['subsuite'])
+            key = (test['flavor'], test.get('subsuite', ''))
             buckets.setdefault(key, []).append(test)
 
         for (flavor, subsuite), tests in sorted(buckets.items()):
             if flavor not in TEST_FLAVORS:
                 print(UNKNOWN_FLAVOR % flavor)
                 status = 1
                 continue
 
--- a/testing/marionette/action.js
+++ b/testing/marionette/action.js
@@ -1,477 +1,463 @@
 /* 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 {classes: Cc, interfaces: Ci, utils: Cu} = Components;
 
 Cu.import("resource://gre/modules/Log.jsm");
-Cu.import("resource://gre/modules/Preferences.jsm");
 
 Cu.import("chrome://marionette/content/element.js");
-Cu.import("chrome://marionette/content/event.js");
+Cu.import("chrome://marionette/content/error.js");
 
-const CONTEXT_MENU_DELAY_PREF = "ui.click_hold_context_menus.delay";
-const DEFAULT_CONTEXT_MENU_DELAY = 750;  // ms
 
 this.EXPORTED_SYMBOLS = ["action"];
 
 const logger = Log.repository.getLogger("Marionette");
 
-this.action = {};
-
+// TODO? With ES 2016 and Symbol you can make a safer approximation
+// to an enum e.g. https://gist.github.com/xmlking/e86e4f15ec32b12c4689
 /**
- * Functionality for (single finger) action chains.
+ * Implements WebDriver Actions API: a low-level interfac for providing
+ * virtualised device input to the web browser.
  */
-action.Chain = function(checkForInterrupted) {
-  // for assigning unique ids to all touches
-  this.nextTouchId = 1000;
-  // keep track of active Touches
-  this.touchIds = {};
-  // last touch for each fingerId
-  this.lastCoordinates = null;
-  this.isTap = false;
-  this.scrolling = false;
-  // whether to send mouse event
-  this.mouseEventsOnly = false;
-  this.checkTimer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
-
-  if (typeof checkForInterrupted == "function") {
-    this.checkForInterrupted = checkForInterrupted;
-  } else {
-    this.checkForInterrupted = () => {};
-  }
-
-  // determines if we create touch events
-  this.inputSource = null;
+this.action = {
+  Pause: "pause",
+  KeyDown: "keyDown",
+  KeyUp: "keyUp",
+  PointerDown: "pointerDown",
+  PointerUp: "pointerUp",
+  PointerMove: "pointerMove",
+  PointerCancel: "pointerCancel",
 };
 
-action.Chain.prototype.dispatchActions = function(
-    args,
-    touchId,
-    container,
-    seenEls,
-    touchProvider) {
-  // Some touch events code in the listener needs to do ipc, so we can't
-  // share this code across chrome/content.
-  if (touchProvider) {
-    this.touchProvider = touchProvider;
-  }
+const ACTIONS = {
+  none: new Set([action.Pause]),
+  key: new Set([action.Pause, action.KeyDown, action.KeyUp]),
+  pointer: new Set([
+    action.Pause,
+    action.PointerDown,
+    action.PointerUp,
+    action.PointerMove,
+    action.PointerCancel,
+  ]),
+};
 
-  this.seenEls = seenEls;
-  this.container = container;
-  let commandArray = element.fromJson(
-      args, seenEls, container.frame, container.shadowRoot);
+/** Represents possible subtypes for a pointer input source. */
+action.PointerType = {
+  Mouse: "mouse",
+  Pen: "pen",
+  Touch: "touch",
+};
 
-  if (touchId == null) {
-    touchId = this.nextTouchId++;
-  }
-
-  if (!container.frame.document.createTouch) {
-    this.mouseEventsOnly = true;
+/**
+ * Look up a PointerType.
+ *
+ * @param {string} str
+ *     Name of pointer type.
+ *
+ * @return {string}
+ *     A pointer type for processing pointer parameters.
+ *
+ * @throws InvalidArgumentError
+ *     If |str| is not a valid pointer type.
+ */
+action.PointerType.get = function(str) {
+  let name = capitalize(str);
+  if (!(name in this)) {
+    throw new InvalidArgumentError(`Unknown pointerType: ${str}`);
   }
-
-  let keyModifiers = {
-    shiftKey: false,
-    ctrlKey: false,
-    altKey: false,
-    metaKey: false,
-  };
-
-  return new Promise(resolve => {
-    this.actions(commandArray, touchId, 0, keyModifiers, resolve);
-  }).catch(this.resetValues);
+  return this[name];
 };
 
 /**
- * This function emit mouse event.
- *
- * @param {Document} doc
- *     Current document.
- * @param {string} type
- *     Type of event to dispatch.
- * @param {number} clickCount
- *     Number of clicks, button notes the mouse button.
- * @param {number} elClientX
- *     X coordinate of the mouse relative to the viewport.
- * @param {number} elClientY
- *     Y coordinate of the mouse relative to the viewport.
- * @param {Object} modifiers
- *     An object of modifier keys present.
+ * Input state associated with current session. This is a map between input ID and
+ * the device state for that input source, with one entry for each active input source.
+ */
+action.inputStateMap = new Map();
+
+/**
+ * Represents device state for an input source.
  */
-action.Chain.prototype.emitMouseEvent = function(
-    doc,
-    type,
-    elClientX,
-    elClientY,
-    button,
-    clickCount,
-    modifiers) {
-  if (!this.checkForInterrupted()) {
-    logger.debug(`Emitting ${type} mouse event ` +
-        `at coordinates (${elClientX}, ${elClientY}) ` +
-        `relative to the viewport, ` +
-        `button: ${button}, ` +
-        `clickCount: ${clickCount}`);
+class InputState {
+  constructor() {
+    this.type = this.constructor.name.toLowerCase();
+  }
+
+  /**
+   * Check equality of this InputState object with another.
+   *
+   * @para{?} other
+   *     Object representing an input state.
+   * @return {boolean}
+   *     True if |this| has the same |type| as |other|.
+   */
+  is(other) {
+    if (typeof other == "undefined") {
+      return false;
+    }
+    return this.type === other.type;
+  }
+
+  toString() {
+    return `[object ${this.constructor.name}InputState]`;
+  }
+
+  /**
+   * @param {?} actionSequence
+   *     Object representing an action sequence.
+   *
+   * @return {action.InputState}
+   *     An |action.InputState| object for the type of the |actionSequence|.
+   *
+   * @throws InvalidArgumentError
+   *     If |actionSequence.type| is not valid.
+   */
+  static fromJson(actionSequence) {
+    let type = actionSequence.type;
+    if (!(type in ACTIONS)) {
+      throw new InvalidArgumentError(`Unknown action type: ${type}`);
+    }
+    let name = type == "none" ? "Null" : capitalize(type);
+    return new action.InputState[name]();
+  }
+}
 
-    let win = doc.defaultView;
-    let domUtils = win.QueryInterface(Ci.nsIInterfaceRequestor)
-        .getInterface(Ci.nsIDOMWindowUtils);
+/** Possible kinds of |InputState| for supported input sources. */
+action.InputState = {};
 
-    let mods;
-    if (typeof modifiers != "undefined") {
-      mods = event.parseModifiers_(modifiers);
-    } else {
-      mods = 0;
-    }
+/**
+ * Input state associated with a keyboard-type device.
+ */
+action.InputState.Key = class extends InputState {
+  constructor() {
+    super();
+    this.pressed = new Set();
+    this.alt = false;
+    this.shift = false;
+    this.ctrl = false;
+    this.meta = false;
+  }
+};
 
-    domUtils.sendMouseEvent(
-        type,
-        elClientX,
-        elClientY,
-        button || 0,
-        clickCount || 1,
-        mods,
-        false,
-        0,
-        this.inputSource);
+/**
+ * Input state not associated with a specific physical device.
+ */
+action.InputState.Null = class extends InputState {
+  constructor() {
+    super();
+    this.type = "none";
+  }
+};
+
+/**
+ * Input state associated with a pointer-type input device.
+ *
+ * @param {string} subtype
+ *     Kind of pointing device: mouse, pen, touch.
+ * @param {boolean} primary
+ *     Whether the pointing device is primary.
+ */
+action.InputState.Pointer = class extends InputState {
+  constructor(subtype, primary) {
+    super();
+    this.pressed = new Set();
+    this.subtype = subtype;
+    this.primary = primary;
+    this.x = 0;
+    this.y = 0;
   }
 };
 
 /**
- * Reset any persisted values after a command completes.
- */
-action.Chain.prototype.resetValues = function() {
-  this.container = null;
-  this.seenEls = null;
-  this.touchProvider = null;
-  this.mouseEventsOnly = false;
-};
-
-/**
- * Emit events for each action in the provided chain.
+ * Repesents an action for dispatch. Used in |action.Chain| and |action.Sequence|.
  *
- * To emit touch events for each finger, one might send a [["press", id],
- * ["wait", 5], ["release"]] chain.
+ * @param {string} id
+ *     Input source ID.
+ * @param {string} type
+ *     Action type: none, key, pointer.
+ * @param {string} subtype
+ *     Action subtype: pause, keyUp, keyDown, pointerUp, pointerDown, pointerMove, pointerCancel.
  *
- * @param {Array.<Array<?>>} chain
- *     A multi-dimensional array of actions.
- * @param {Object.<string, number>} touchId
- *     Represents the finger ID.
- * @param {number} i
- *     Keeps track of the current action of the chain.
- * @param {Object.<string, boolean>} keyModifiers
- *     Keeps track of keyDown/keyUp pairs through an action chain.
- * @param {function(?)} cb
- *     Called on success.
- *
- * @return {Object.&