Merge autoland to central, a=merge
authorWes Kocher <wkocher@mozilla.com>
Tue, 10 Jan 2017 17:22:08 -0800
changeset 328820 dbc2084de15a8a3b71843c1c1000df54bc1b0beb
parent 328819 20094311ab5ec56d5be7afc6d5c2e2b09e656fb2 (current diff)
parent 328818 c85f27d150311c74cfc1dd958c44b2efc83d8840 (diff)
child 328821 b079c9833e3ed047e1b984e26b8d62d739baa40b
push id85546
push userkwierso@gmail.com
push dateWed, 11 Jan 2017 02:36:30 +0000
treeherdermozilla-inbound@c5bce4cd684a [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmerge
milestone53.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Merge autoland to central, a=merge MozReview-Commit-ID: AzdmcFWgfx2
mobile/android/base/resources/drawable/ic_menu_bookmark_add.xml
mobile/android/base/resources/drawable/star_blue.xml
--- a/browser/base/content/test/general/browser.ini
+++ b/browser/base/content/test/general/browser.ini
@@ -387,17 +387,17 @@ skip-if = e10s && debug && os == "win" #
 [browser_save_video.js]
 [browser_save_video_frame.js]
 [browser_scope.js]
 [browser_contentSearchUI.js]
 support-files =
   contentSearchUI.html
   contentSearchUI.js
 [browser_selectpopup.js]
-run-if = e10s
+skip-if = os == "linux" # Bug 1329991 - test fails intermittently on Linux builds
 [browser_selectTabAtIndex.js]
 [browser_ssl_error_reports.js]
 [browser_star_hsts.js]
 [browser_subframe_favicons_not_used.js]
 [browser_syncui.js]
 skip-if = os == 'linux' # Bug 1304272
 [browser_tab_close_dependent_window.js]
 [browser_tabDrop.js]
--- a/browser/base/content/test/general/browser_selectpopup.js
+++ b/browser/base/content/test/general/browser_selectpopup.js
@@ -190,16 +190,24 @@ function* doSelectTests(contentType, dtd
   is((yield getChangeEvents()), isWindows ? 2 : 1, "Tab away from select with change - number of change events");
 
   is(selectPopup.lastChild.previousSibling.label, "Seven", "Spaces collapsed");
   is(selectPopup.lastChild.label, "\xA0\xA0Eight\xA0\xA0", "Non-breaking spaces not collapsed");
 
   yield BrowserTestUtils.removeTab(tab);
 }
 
+add_task(function* setup() {
+  yield SpecialPowers.pushPrefEnv({
+    "set": [
+      ["dom.select_popup_in_parent.enabled", true],
+    ]
+  });
+});
+
 add_task(function*() {
   yield doSelectTests("text/html", "");
 });
 
 add_task(function*() {
   yield doSelectTests("application/xhtml+xml", XHTML_DTD);
 });
 
@@ -434,19 +442,24 @@ function* performLargePopupTests(win) {
     ok(rect.top >= browserRect.top, "Popup top position in within browser area");
     ok(rect.bottom <= browserRect.bottom, "Popup bottom position in within browser area");
 
     // Don't check the scroll position for the last step as the popup will be cut off.
     if (positions.length > 0) {
       let cs = win.getComputedStyle(selectPopup);
       let bpBottom = parseFloat(cs.paddingBottom) + parseFloat(cs.borderBottomWidth);
 
-      is(selectPopup.childNodes[60].getBoundingClientRect().bottom,
-         selectPopup.getBoundingClientRect().bottom - bpBottom,
-         "Popup scroll at correct position " + bpBottom);
+      // Some of the styles applied to the menuitems are percentages, meaning
+      // that the final layout calculations returned by getBoundingClientRect()
+      // might return floating point values. We don't care about sub-pixel
+      // accuracy, and only care about the final pixel value, so we add a
+      // fuzz-factor of 1.
+      SimpleTest.isfuzzy(selectPopup.childNodes[60].getBoundingClientRect().bottom,
+                         selectPopup.getBoundingClientRect().bottom - bpBottom,
+                         1, "Popup scroll at correct position " + bpBottom);
     }
 
     yield hideSelectPopup(selectPopup, "enter", win);
 
     position = positions.shift();
     if (!position) {
       break;
     }
--- a/browser/base/content/urlbarBindings.xml
+++ b/browser/base/content/urlbarBindings.xml
@@ -756,18 +756,18 @@ file, You can obtain one at http://mozil
 
       <method name="_getSelectedValueForClipboard">
         <body><![CDATA[
           // Grab the actual input field's value, not our value, which could include moz-action:
           var inputVal = this.inputField.value;
           var selectedVal = inputVal.substring(this.selectionStart, this.selectionEnd);
 
           // If the selection doesn't start at the beginning or doesn't span the full domain or
-          // the URL bar is modified, nothing else to do here.
-          if (this.selectionStart > 0 || this.valueIsTyped)
+          // the URL bar is modified or there is no text at all, nothing else to do here.
+          if (this.selectionStart > 0 || this.valueIsTyped || selectedVal == "")
             return selectedVal;
           // The selection doesn't span the full domain if it doesn't contain a slash and is
           // followed by some character other than a slash.
           if (!selectedVal.includes("/")) {
             let remainder = inputVal.replace(selectedVal, "");
             if (remainder != "" && remainder[0] != "/")
               return selectedVal;
           }
--- a/browser/components/downloads/DownloadsCommon.jsm
+++ b/browser/components/downloads/DownloadsCommon.jsm
@@ -1190,22 +1190,27 @@ DownloadsIndicatorDataCtor.prototype = {
         case Downloads.Error.BLOCK_VERDICT_MALWARE:
           this.attention = DownloadsCommon.ATTENTION_SEVERE;
           break;
         default:
           this.attention = DownloadsCommon.ATTENTION_SEVERE;
           Cu.reportError("Unknown reputation verdict: " +
                          download.error.reputationCheckVerdict);
       }
-    } else if (download.succeeded || download.error) {
+    } else if (download.succeeded) {
       // Existing higher level attention indication trumps ATTENTION_SUCCESS.
       if (this._attention != DownloadsCommon.ATTENTION_SEVERE &&
           this._attention != DownloadsCommon.ATTENTION_WARNING) {
         this.attention = DownloadsCommon.ATTENTION_SUCCESS;
       }
+    } else if (download.error) {
+      // Existing higher level attention indication trumps ATTENTION_WARNING.
+      if (this._attention != DownloadsCommon.ATTENTION_SEVERE) {
+        this.attention = DownloadsCommon.ATTENTION_WARNING;
+      }
     }
 
     // Since the state of a download changed, reset the estimated time left.
     this._lastRawTimeLeft = -1;
     this._lastTimeLeft = -1;
   },
 
   onDownloadChanged(download) {
--- a/browser/components/preferences/in-content/advanced.xul
+++ b/browser/components/preferences/in-content/advanced.xul
@@ -205,52 +205,49 @@
 #ifdef MOZ_TELEMETRY_REPORTING
       <groupbox>
         <caption>
           <checkbox id="submitHealthReportBox" label="&enableHealthReport.label;"
                     accesskey="&enableHealthReport.accesskey;"/>
         </caption>
         <vbox>
           <hbox class="indent">
-            <label flex="1">&healthReportDesc.label;</label>
-            <spacer flex="10"/>
+            <label>&healthReportDesc.label;</label>
             <label id="FHRLearnMore"
-                   class="text-link">&healthReportLearnMore.label;</label>
+                   class="learnMore text-link">&healthReportLearnMore.label;</label>
           </hbox>
           <hbox class="indent">
             <groupbox flex="1">
               <caption>
                 <checkbox id="submitTelemetryBox" preference="toolkit.telemetry.enabled"
                           label="&enableTelemetryData.label;"
                           accesskey="&enableTelemetryData.accesskey;"/>
               </caption>
               <hbox class="indent">
-                <label id="telemetryDataDesc" flex="1">&telemetryDesc.label;</label>
-                <spacer flex="10"/>
+                <label id="telemetryDataDesc">&telemetryDesc.label;</label>
                 <label id="telemetryLearnMore"
-                       class="text-link">&telemetryLearnMore.label;</label>
+                       class="learnMore text-link">&telemetryLearnMore.label;</label>
               </hbox>
             </groupbox>
           </hbox>
         </vbox>
       </groupbox>
 #endif
 #ifdef MOZ_CRASHREPORTER
       <groupbox>
         <caption>
           <checkbox id="automaticallySubmitCrashesBox"
                     preference="browser.crashReports.unsubmittedCheck.autoSubmit"
                     label="&alwaysSubmitCrashReports.label;"
                     accesskey="&alwaysSubmitCrashReports.accesskey;"/>
         </caption>
         <hbox class="indent">
-          <label flex="1">&crashReporterDesc2.label;</label>
-          <spacer flex="10"/>
+          <label>&crashReporterDesc2.label;</label>
           <label id="crashReporterLearnMore"
-                 class="text-link">&crashReporterLearnMore.label;</label>
+                 class="learnMore text-link">&crashReporterLearnMore.label;</label>
         </hbox>
       </groupbox>
 #endif
     </tabpanel>
 #endif
 
     <!-- Network -->
     <tabpanel id="networkPanel" orient="vertical">
--- a/browser/components/preferences/in-content/content.xul
+++ b/browser/components/preferences/in-content/content.xul
@@ -44,22 +44,20 @@
   <caption><label>&drmContent.label;</label></caption>
   <grid id="contentGrid2">
     <columns>
       <column flex="1"/>
       <column/>
     </columns>
     <rows id="contentRows-2">
       <row id="playDRMContentRow">
-        <vbox align="start">
+        <hbox align="center">
           <checkbox id="playDRMContent" preference="media.eme.enabled"
                     label="&playDRMContent.label;" accesskey="&playDRMContent.accesskey;"/>
-        </vbox>
-        <hbox pack="end" align="center">
-          <label id="playDRMContentLink" class="text-link" value="&playDRMContent.learnMore.label;"/>
+          <label id="playDRMContentLink" class="learnMore text-link" value="&playDRMContent.learnMore.label;"/>
         </hbox>
       </row>
     </rows>
   </grid>
 </groupbox>
 
 <groupbox id="notificationsGroup" data-category="paneContent" hidden="true">
   <caption><label>&notificationsPolicy.label;</label></caption>
@@ -68,17 +66,17 @@
       <column flex="1"/>
       <column/>
     </columns>
     <rows>
       <row id="notificationsPolicyRow" align="center">
         <hbox align="start">
           <label id="notificationsPolicy">&notificationsPolicyDesc3.label;</label>
           <label id="notificationsPolicyLearnMore"
-                 class="text-link"
+                 class="learnMore text-link"
                  value="&notificationsPolicyLearnMore.label;"/>
         </hbox>
         <hbox pack="end">
           <button id="notificationsPolicyButton" label="&notificationsPolicyButton.label;"
                   accesskey="&notificationsPolicyButton.accesskey;"/>
         </hbox>
       </row>
       <row id="notificationsDoNotDisturbRow" hidden="true">
--- a/browser/components/preferences/in-content/privacy.xul
+++ b/browser/components/preferences/in-content/privacy.xul
@@ -87,17 +87,17 @@
 </hbox>
 
 <!-- Tracking -->
 <groupbox id="trackingGroup" data-category="panePrivacy" hidden="true">
   <vbox id="trackingprotectionbox" hidden="true">
     <hbox align="start">
       <vbox>
         <caption><label>&trackingProtectionHeader.label;
-          <label id="trackingProtectionLearnMore" class="text-link"
+          <label id="trackingProtectionLearnMore" class="learnMore text-link"
                  value="&trackingProtectionLearnMore.label;"/>
         </label></caption>
         <radiogroup id="trackingProtectionRadioGroup">
           <radio value="always"
                  label="&trackingProtectionAlways.label;"
                  accesskey="&trackingProtectionAlways.accesskey;"/>
           <radio value="private"
                  label="&trackingProtectionPrivate.label;"
@@ -123,17 +123,17 @@
   <vbox id="trackingprotectionpbmbox">
     <caption><label>&tracking.label;</label></caption>
     <hbox align="center">
       <checkbox id="trackingProtectionPBM"
                 preference="privacy.trackingprotection.pbmode.enabled"
                 accesskey="&trackingProtectionPBM5.accesskey;"
                 label="&trackingProtectionPBM5.label;" />
       <label id="trackingProtectionPBMLearnMore"
-             class="text-link"
+             class="learnMore text-link"
              value="&trackingProtectionPBMLearnMore.label;"/>
       <spacer flex="1" />
       <button id="changeBlockListPBM"
               label="&changeBlockList.label;" accesskey="&changeBlockList.accesskey;"
               preference="pref.privacy.disable_button.change_blocklist"/>
     </hbox>
   </vbox>
   <vbox>
@@ -284,17 +284,17 @@
     &suggestionSettings.label;
   </label>
 </groupbox>
 
 <!-- Containers -->
 <groupbox id="browserContainersGroup" data-category="panePrivacy" hidden="true">
   <vbox id="browserContainersbox" hidden="true">
     <caption><label>&browserContainersHeader.label;
-      <label id="browserContainersLearnMore" class="text-link"
+      <label id="browserContainersLearnMore" class="learnMore text-link"
              value="&browserContainersLearnMore.label;"/>
     </label></caption>
     <hbox align="start">
       <vbox>
         <checkbox id="browserContainersCheckbox"
                   label="&browserContainersEnabled.label;"
                   accesskey="&browserContainersEnabled.accesskey;"
                   preference="privacy.userContext.enabled"
--- a/browser/config/mozconfigs/win32/common-opt
+++ b/browser/config/mozconfigs/win32/common-opt
@@ -16,17 +16,10 @@ ac_add_options --with-mozilla-api-keyfil
 
 # Needed to enable breakpad in application.ini
 export MOZILLA_OFFICIAL=1
 
 export MOZ_TELEMETRY_REPORTING=1
 
 . $topsrcdir/build/win32/mozconfig.vs-latest
 
-# Enable Adobe Primetime CDM on 32-bit Windows in Mozilla builds.
-# Enabled here on the assumption that downstream vendors will not be using
-# these build configs.
-# Note: Widevine is automatically enabled by the build, and can be
-# disabled with -widevine.
-ac_add_options --enable-eme=+adobe
-
 # Package js shell.
 export MOZ_PACKAGE_JSSHELL=1
--- a/browser/config/mozconfigs/win64/common-opt
+++ b/browser/config/mozconfigs/win64/common-opt
@@ -15,17 +15,10 @@ ac_add_options --with-mozilla-api-keyfil
 
 # Needed to enable breakpad in application.ini
 export MOZILLA_OFFICIAL=1
 
 export MOZ_TELEMETRY_REPORTING=1
 
 . $topsrcdir/build/win64/mozconfig.vs-latest
 
-# Enable Adobe Primetime CDM on 64-bit Windows in Mozilla builds.
-# Enabled here on the assumption that downstream vendors will not be using
-# these build configs.
-# Note: Widevine is automatically enabled by the build, and can be
-# disabled with -widevine.
-ac_add_options --enable-eme=+adobe
-
 # Package js shell.
 export MOZ_PACKAGE_JSSHELL=1
--- a/browser/themes/shared/icon-colors.inc.svg
+++ b/browser/themes/shared/icon-colors.inc.svg
@@ -1,13 +1,13 @@
 <style>
 
 .fieldtext {
   fill: -moz-fieldtext;
-#ifdef XP_LINUX
+#ifdef MOZ_WIDGET_GTK
   /* The fill-opacity needs to be sufficient for high-contrast settings, and
      pathological Gtk themes where -moz-fieldtext provides low contrast by
      default. */
   fill-opacity: .7;
 #else
   fill-opacity: .5;
 #endif
 }
--- a/browser/themes/shared/incontentprefs/preferences.inc.css
+++ b/browser/themes/shared/incontentprefs/preferences.inc.css
@@ -38,16 +38,22 @@ treecol {
 #blocklistsTree treechildren::-moz-tree-row {
   min-height: 36px;
 }
 
 #selectionCol {
   min-width: 26px;
 }
 
+/* For the "learn more" links, line up after text */
+.learnMore {
+  margin-inline-start: 1.5em;
+  font-weight: normal;
+}
+
 /* Category List */
 
 #categories {
   max-height: 100vh;
 }
 
 #categories > scrollbox {
   overflow-x: hidden !important;
@@ -169,25 +175,16 @@ treecol {
   font-weight: bold;
 }
 
 #downloadFolder {
   margin-inline-start: 0;
 }
 
 /* Content pane */
-#playDRMContentLink {
-  /* Line up with the buttons in the other grid bits: */
-  margin-left: 4px !important;
-  margin-right: 4px !important;
-}
-
-#notificationsPolicyLearnMore {
-  margin-inline-start: 1.5em !important;
-}
 
 #defaultFontSizeLabel {
   /* !important needed to override common !important rule */
   margin-inline-start: 4px !important;
 }
 
 /* Applications Pane Styles */
 
@@ -226,24 +223,16 @@ treecol {
 }
 
 .actionsMenu > menupopup > menuitem > .menu-iconic-left {
   margin-inline-end: 8px !important;
 }
 
 /* Privacy pane */
 
-#trackingProtectionPBMLearnMore,
-#trackingProtectionLearnMore,
-#browserContainersLearnMore {
-  margin-inline-start: 1.5em !important;
-  margin-top: 0;
-  font-weight: normal;
-}
-
 .doNotTrackLearnMore {
   margin-inline-start: calc(1em + 30px);
   margin-bottom: 1em;
   font-weight: normal;
 }
 
 .doNotTrackLearnMore > label {
   font-size: 1em !important;
@@ -279,27 +268,16 @@ description > html|a {
   /* no margin-inline-start for elements at the beginning of a line */
   margin-inline-start: 0;
 }
 
 #tabsElement {
   margin-inline-end: 4px; /* add the 4px end-margin of other elements */
 }
 
-#telemetryLearnMore,
-#FHRLearnMore,
-#crashReporterLearnMore {
-  /* provide some margin between the links and the label text */
-  /* !important is needed to override the rules defined in common.css */
-  margin-inline-start: 20px !important;
-  /* center the links */
-  margin-top: 8px;
-  margin-bottom: 8px;
-}
-
 .indent {
   /* !important needed to override margin-inline-start:0 !important; rule
      define in common.css for labels */
   margin-inline-start: 33px !important;
 }
 
 .text-link {
   margin-bottom: 0;
--- a/devtools/client/netmonitor/components/request-list-header.js
+++ b/devtools/client/netmonitor/components/request-list-header.js
@@ -3,16 +3,17 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 /* globals document */
 
 "use strict";
 
 const { createClass, PropTypes, DOM } = require("devtools/client/shared/vendor/react");
 const { div, button } = DOM;
+const { findDOMNode } = require("devtools/client/shared/vendor/react-dom");
 const { connect } = require("devtools/client/shared/vendor/react-redux");
 const { L10N } = require("../l10n");
 const { getWaterfallScale } = require("../selectors/index");
 const Actions = require("../actions/index");
 const WaterfallBackground = require("../waterfall-background");
 
 // ms
 const REQUESTS_WATERFALL_HEADER_TICKS_MULTIPLE = 5;
@@ -44,16 +45,27 @@ const RequestListHeader = createClass({
   propTypes: {
     sort: PropTypes.object,
     scale: PropTypes.number,
     waterfallWidth: PropTypes.number,
     onHeaderClick: PropTypes.func.isRequired,
   },
 
   componentDidMount() {
+    // This is the first time the waterfall column header is actually rendered.
+    // Measure its width and update the 'waterfallWidth' property in the store.
+    // The 'waterfallWidth' will be further updated on every window resize.
+    const waterfallHeaderEl = findDOMNode(this)
+      .querySelector("#requests-menu-waterfall-header-box");
+    if (waterfallHeaderEl) {
+      const { width } = waterfallHeaderEl.getBoundingClientRect();
+      this.props.resizeWaterfall(width);
+    }
+
+    // Create the object that takes care of drawing the waterfall canvas background
     this.background = new WaterfallBackground(document);
     this.background.draw(this.props);
   },
 
   componentDidUpdate() {
     this.background.draw(this.props);
   },
 
@@ -170,17 +182,17 @@ function waterfallDivisionLabels(waterfa
   }
 
   return labels;
 }
 
 function WaterfallLabel(waterfallWidth, scale, label) {
   let className = "button-text requests-menu-waterfall-label-wrapper";
 
-  if (scale != null) {
+  if (waterfallWidth != null && scale != null) {
     label = waterfallDivisionLabels(waterfallWidth, scale);
     className += " requests-menu-waterfall-visible";
   }
 
   return div({ className }, label);
 }
 
 module.exports = connect(
@@ -188,10 +200,11 @@ module.exports = connect(
     sort: state.sort,
     scale: getWaterfallScale(state),
     waterfallWidth: state.ui.waterfallWidth,
     firstRequestStartedMillis: state.requests.firstStartedMillis,
     timingMarkers: state.timingMarkers,
   }),
   dispatch => ({
     onHeaderClick: type => dispatch(Actions.sortBy(type)),
+    resizeWaterfall: width => dispatch(Actions.resizeWaterfall(width)),
   })
 )(RequestListHeader);
--- a/devtools/client/netmonitor/components/request-list-item.js
+++ b/devtools/client/netmonitor/components/request-list-item.js
@@ -272,17 +272,18 @@ const CauseColumn = createFactory(create
   render() {
     const { cause } = this.props.item;
 
     let causeType = "";
     let causeUri = undefined;
     let causeHasStack = false;
 
     if (cause) {
-      causeType = cause.type;
+      // Legacy server might send a numeric value. Display it as "unknown"
+      causeType = typeof cause.type === "string" ? cause.type : "unknown";
       causeUri = cause.loadingDocumentUri;
       causeHasStack = cause.stacktrace && cause.stacktrace.length > 0;
     }
 
     return div(
       { className: "requests-menu-subitem requests-menu-cause", title: causeUri },
       span({ className: "requests-menu-cause-stack", hidden: !causeHasStack }, "JS"),
       span({ className: "subitem-label" }, causeType)
--- a/devtools/client/netmonitor/netmonitor-view.js
+++ b/devtools/client/netmonitor/netmonitor-view.js
@@ -198,17 +198,17 @@ var NetMonitorView = {
         yield whenDataAvailable(requestsView.store, [
           "responseHeaders", "status", "contentSize", "mimeType", "totalTime"
         ]);
       } catch (ex) {
         // Timed out while waiting for data. Continue with what we have.
         console.error(ex);
       }
 
-      const requests = requestsView.store.getState().requests.requests;
+      const requests = requestsView.store.getState().requests.requests.valueSeq();
       statisticsView.createPrimedCacheChart(requests);
       statisticsView.createEmptyCacheChart(requests);
     });
   },
 
   reloadPage: function () {
     NetMonitorController.triggerActivity(
       ACTIVITY_TYPE.RELOAD.WITH_CACHE_DEFAULT);
--- a/devtools/client/netmonitor/reducers/requests.js
+++ b/devtools/client/netmonitor/reducers/requests.js
@@ -50,18 +50,18 @@ const Request = I.Record({
   responseHeaders: undefined,
   responseCookies: undefined,
   responseContent: undefined,
   responseContentDataUri: undefined,
   formDataSections: undefined,
 });
 
 const Requests = I.Record({
-  // The request list
-  requests: I.List(),
+  // The collection of requests (keyed by id)
+  requests: I.Map(),
   // Selection state
   selectedId: null,
   preselectedId: null,
   // Auxiliary fields to hold requests stats
   firstStartedMillis: +Infinity,
   lastEndedMillis: -Infinity,
 });
 
@@ -96,17 +96,17 @@ function requestsReducer(state = new Req
   switch (action.type) {
     case ADD_REQUEST: {
       return state.withMutations(st => {
         let newRequest = new Request(Object.assign(
           { id: action.id },
           action.data,
           { urlDetails: getUrlDetails(action.data.url) }
         ));
-        st.requests = st.requests.push(newRequest);
+        st.requests = st.requests.set(newRequest.id, newRequest);
 
         // Update the started/ended timestamps
         let { startedMillis } = action.data;
         if (startedMillis < st.firstStartedMillis) {
           st.firstStartedMillis = startedMillis;
         }
         if (startedMillis > st.lastEndedMillis) {
           st.lastEndedMillis = startedMillis;
@@ -118,22 +118,22 @@ function requestsReducer(state = new Req
           st.preselectedId = null;
         }
       });
     }
 
     case UPDATE_REQUEST: {
       let { requests, lastEndedMillis } = state;
 
-      let updateIdx = requests.findIndex(r => r.id === action.id);
-      if (updateIdx === -1) {
+      let updatedRequest = requests.get(action.id);
+      if (!updatedRequest) {
         return state;
       }
 
-      requests = requests.update(updateIdx, r => r.withMutations(request => {
+      updatedRequest = updatedRequest.withMutations(request => {
         for (let [key, value] of Object.entries(action.data)) {
           if (!UPDATE_PROPS.includes(key)) {
             continue;
           }
 
           request[key] = value;
 
           switch (key) {
@@ -148,20 +148,20 @@ function requestsReducer(state = new Req
             case "requestPostData":
               request.requestHeadersFromUploadStream = {
                 headers: [],
                 headersSize: 0,
               };
               break;
           }
         }
-      }));
+      });
 
       return state.withMutations(st => {
-        st.requests = requests;
+        st.requests = requests.set(updatedRequest.id, updatedRequest);
         st.lastEndedMillis = lastEndedMillis;
       });
     }
     case CLEAR_REQUESTS: {
       return new Requests();
     }
     case SELECT_REQUEST: {
       return state.set("selectedId", action.id);
@@ -171,68 +171,61 @@ function requestsReducer(state = new Req
     }
     case CLONE_SELECTED_REQUEST: {
       let { requests, selectedId } = state;
 
       if (!selectedId) {
         return state;
       }
 
-      let clonedIdx = requests.findIndex(r => r.id === selectedId);
-      if (clonedIdx === -1) {
+      let clonedRequest = requests.get(selectedId);
+      if (!clonedRequest) {
         return state;
       }
 
-      let clonedRequest = requests.get(clonedIdx);
       let newRequest = new Request({
         id: clonedRequest.id + "-clone",
         method: clonedRequest.method,
         url: clonedRequest.url,
         urlDetails: clonedRequest.urlDetails,
         requestHeaders: clonedRequest.requestHeaders,
         requestPostData: clonedRequest.requestPostData,
         isCustom: true
       });
 
-      // Insert the clone right after the original. This ensures that the requests
-      // are always sorted next to each other, even when multiple requests are
-      // equal according to the sorting criteria.
-      requests = requests.insert(clonedIdx + 1, newRequest);
-
       return state.withMutations(st => {
-        st.requests = requests;
+        st.requests = requests.set(newRequest.id, newRequest);
         st.selectedId = newRequest.id;
       });
     }
     case REMOVE_SELECTED_CUSTOM_REQUEST: {
       let { requests, selectedId } = state;
 
       if (!selectedId) {
         return state;
       }
 
-      let removedRequest = requests.find(r => r.id === selectedId);
-
       // Only custom requests can be removed
+      let removedRequest = requests.get(selectedId);
       if (!removedRequest || !removedRequest.isCustom) {
         return state;
       }
 
       return state.withMutations(st => {
-        st.requests = requests.filter(r => r !== removedRequest);
+        st.requests = requests.delete(selectedId);
         st.selectedId = null;
       });
     }
     case OPEN_SIDEBAR: {
       if (!action.open) {
         return state.set("selectedId", null);
       }
 
       if (!state.selectedId && !state.requests.isEmpty()) {
-        return state.set("selectedId", state.requests.get(0).id);
+        return state.set("selectedId", state.requests.first().id);
       }
 
       return state;
     }
 
     default:
       return state;
   }
--- a/devtools/client/netmonitor/reducers/ui.js
+++ b/devtools/client/netmonitor/reducers/ui.js
@@ -9,17 +9,17 @@ const {
   OPEN_SIDEBAR,
   OPEN_STATISTICS,
   WATERFALL_RESIZE,
 } = require("../constants");
 
 const UI = I.Record({
   sidebarOpen: false,
   statisticsOpen: false,
-  waterfallWidth: 300,
+  waterfallWidth: null,
 });
 
 // Safe bounds for waterfall width (px)
 const REQUESTS_WATERFALL_SAFE_BOUNDS = 90;
 
 function resizeWaterfall(state, action) {
   return state.set("waterfallWidth", action.width - REQUESTS_WATERFALL_SAFE_BOUNDS);
 }
--- a/devtools/client/netmonitor/request-utils.js
+++ b/devtools/client/netmonitor/request-utils.js
@@ -1,17 +1,16 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 /* eslint-disable mozilla/reject-some-requires */
 
 "use strict";
 
-const { Ci } = require("chrome");
 const { KeyCodes } = require("devtools/client/shared/keycodes");
 const { Task } = require("devtools/shared/task");
 
 /**
  * Helper method to get a wrapped function which can be bound to as
  * an event listener directly and is executed only when data-key is
  * present in event.target.
  *
@@ -234,57 +233,23 @@ function parseQueryString(query) {
     let param = e.split("=");
     return {
       name: param[0] ? decodeUnicodeUrl(param[0]) : "",
       value: param[1] ? decodeUnicodeUrl(param[1]) : "",
     };
   });
 }
 
-/**
- * Convert a nsIContentPolicy constant to a display string
- */
-const LOAD_CAUSE_STRINGS = {
-  [Ci.nsIContentPolicy.TYPE_INVALID]: "invalid",
-  [Ci.nsIContentPolicy.TYPE_OTHER]: "other",
-  [Ci.nsIContentPolicy.TYPE_SCRIPT]: "script",
-  [Ci.nsIContentPolicy.TYPE_IMAGE]: "img",
-  [Ci.nsIContentPolicy.TYPE_STYLESHEET]: "stylesheet",
-  [Ci.nsIContentPolicy.TYPE_OBJECT]: "object",
-  [Ci.nsIContentPolicy.TYPE_DOCUMENT]: "document",
-  [Ci.nsIContentPolicy.TYPE_SUBDOCUMENT]: "subdocument",
-  [Ci.nsIContentPolicy.TYPE_REFRESH]: "refresh",
-  [Ci.nsIContentPolicy.TYPE_XBL]: "xbl",
-  [Ci.nsIContentPolicy.TYPE_PING]: "ping",
-  [Ci.nsIContentPolicy.TYPE_XMLHTTPREQUEST]: "xhr",
-  [Ci.nsIContentPolicy.TYPE_OBJECT_SUBREQUEST]: "objectSubdoc",
-  [Ci.nsIContentPolicy.TYPE_DTD]: "dtd",
-  [Ci.nsIContentPolicy.TYPE_FONT]: "font",
-  [Ci.nsIContentPolicy.TYPE_MEDIA]: "media",
-  [Ci.nsIContentPolicy.TYPE_WEBSOCKET]: "websocket",
-  [Ci.nsIContentPolicy.TYPE_CSP_REPORT]: "csp",
-  [Ci.nsIContentPolicy.TYPE_XSLT]: "xslt",
-  [Ci.nsIContentPolicy.TYPE_BEACON]: "beacon",
-  [Ci.nsIContentPolicy.TYPE_FETCH]: "fetch",
-  [Ci.nsIContentPolicy.TYPE_IMAGESET]: "imageset",
-  [Ci.nsIContentPolicy.TYPE_WEB_MANIFEST]: "webManifest"
-};
-
-function loadCauseString(causeType) {
-  return LOAD_CAUSE_STRINGS[causeType] || "unknown";
-}
-
 module.exports = {
   getKeyWithEvent,
   getFormDataSections,
   formDataURI,
   writeHeaderText,
   decodeUnicodeUrl,
   getAbbreviatedMimeType,
   getUrlBaseName,
   getUrlQuery,
   getUrlBaseNameWithQuery,
   getUrlHostName,
   getUrlHost,
   getUrlDetails,
   parseQueryString,
-  loadCauseString,
 };
--- a/devtools/client/netmonitor/requests-menu-view.js
+++ b/devtools/client/netmonitor/requests-menu-view.js
@@ -18,17 +18,16 @@ const { Provider } = require("devtools/c
 const RequestList = createFactory(require("./components/request-list"));
 const RequestListContextMenu = require("./request-list-context-menu");
 const Actions = require("./actions/index");
 const { Prefs } = require("./prefs");
 
 const {
   formDataURI,
   writeHeaderText,
-  loadCauseString,
   getFormDataSections,
 } = require("./request-utils");
 
 const {
   getActiveFilters,
   getSortedRequests,
   getDisplayedRequests,
   getRequestById,
@@ -224,22 +223,16 @@ RequestsMenuView.prototype = {
 
   addRequest(id, data) {
     let { method, url, isXHR, cause, startedDateTime, fromCache,
           fromServiceWorker } = data;
 
     // Convert the received date/time string to a unix timestamp.
     let startedMillis = Date.parse(startedDateTime);
 
-    // Convert the cause from a Ci.nsIContentPolicy constant to a string
-    if (cause) {
-      let type = loadCauseString(cause.type);
-      cause = Object.assign({}, cause, { type });
-    }
-
     const action = Actions.addRequest(
       id,
       {
         startedMillis,
         method,
         url,
         isXHR,
         cause,
--- a/devtools/client/netmonitor/selectors/requests.js
+++ b/devtools/client/netmonitor/selectors/requests.js
@@ -4,75 +4,74 @@
 
 "use strict";
 
 const { createSelector } = require("devtools/client/shared/vendor/reselect");
 const { Filters, isFreetextMatch } = require("../filter-predicates");
 const { Sorters } = require("../sort-predicates");
 
 /**
- * Check if the given requests is a clone, find and return the original request if it is.
- * Cloned requests are sorted by comparing the original ones.
+ * Take clones into account when sorting.
+ * If a request is a clone, use the original request for comparison.
+ * If one of the compared request is a clone of the other, sort them next to each other.
  */
-function getOrigRequest(requests, req) {
-  if (!req.id.endsWith("-clone")) {
-    return req;
+function sortWithClones(requests, sorter, a, b) {
+  const aId = a.id, bId = b.id;
+
+  if (aId.endsWith("-clone")) {
+    const aOrigId = aId.replace(/-clone$/, "");
+    if (aOrigId === bId) {
+      return +1;
+    }
+    a = requests.get(aOrigId);
   }
 
-  const origId = req.id.replace(/-clone$/, "");
-  return requests.find(r => r.id === origId);
+  if (bId.endsWith("-clone")) {
+    const bOrigId = bId.replace(/-clone$/, "");
+    if (bOrigId === aId) {
+      return -1;
+    }
+    b = requests.get(bOrigId);
+  }
+
+  return sorter(a, b);
 }
 
 const getFilterFn = createSelector(
   state => state.filters,
   filters => r => {
     const matchesType = filters.requestFilterTypes.some((enabled, filter) => {
       return enabled && Filters[filter] && Filters[filter](r);
     });
     return matchesType && isFreetextMatch(r, filters.requestFilterText);
   }
 );
 
 const getSortFn = createSelector(
   state => state.requests.requests,
   state => state.sort,
   (requests, sort) => {
-    let dataSorter = Sorters[sort.type || "waterfall"];
-
-    function sortWithClones(a, b) {
-      // If one request is a clone of the other, sort them next to each other
-      if (a.id == b.id + "-clone") {
-        return +1;
-      } else if (a.id + "-clone" == b.id) {
-        return -1;
-      }
-
-      // Otherwise, get the original requests and compare them
-      return dataSorter(
-        getOrigRequest(requests, a),
-        getOrigRequest(requests, b)
-      );
-    }
-
+    const sorter = Sorters[sort.type || "waterfall"];
     const ascending = sort.ascending ? +1 : -1;
-    return (a, b) => ascending * sortWithClones(a, b, dataSorter);
+    return (a, b) => ascending * sortWithClones(requests, sorter, a, b);
   }
 );
 
 const getSortedRequests = createSelector(
   state => state.requests.requests,
   getSortFn,
-  (requests, sortFn) => requests.sort(sortFn)
+  (requests, sortFn) => requests.valueSeq().sort(sortFn).toList()
 );
 
 const getDisplayedRequests = createSelector(
   state => state.requests.requests,
   getFilterFn,
   getSortFn,
-  (requests, filterFn, sortFn) => requests.filter(filterFn).sort(sortFn)
+  (requests, filterFn, sortFn) => requests.valueSeq()
+    .filter(filterFn).sort(sortFn).toList()
 );
 
 const getDisplayedRequestsSummary = createSelector(
   getDisplayedRequests,
   state => state.requests.lastEndedMillis - state.requests.firstStartedMillis,
   (requests, totalMillis) => {
     if (requests.size == 0) {
       return { count: 0, bytes: 0, millis: 0 };
@@ -90,27 +89,21 @@ const getDisplayedRequestsSummary = crea
       bytes: totalBytes,
       millis: totalMillis,
     };
   }
 );
 
 const getSelectedRequest = createSelector(
   state => state.requests,
-  requests => {
-    if (!requests.selectedId) {
-      return null;
-    }
-
-    return requests.requests.find(r => r.id === requests.selectedId);
-  }
+  ({ selectedId, requests }) => selectedId ? requests.get(selectedId) : null
 );
 
 function getRequestById(state, id) {
-  return state.requests.requests.find(r => r.id === id);
+  return state.requests.requests.get(id);
 }
 
 function getDisplayedRequestById(state, id) {
   return getDisplayedRequests(state).find(r => r.id === id);
 }
 
 module.exports = {
   getDisplayedRequestById,
--- a/devtools/client/netmonitor/selectors/ui.js
+++ b/devtools/client/netmonitor/selectors/ui.js
@@ -14,16 +14,20 @@ const EPSILON = 0.001;
 
 function getWaterfallScale(state) {
   const { requests, timingMarkers, ui } = state;
 
   if (requests.firstStartedMillis == +Infinity) {
     return null;
   }
 
+  if (ui.waterfallWidth == null) {
+    return null;
+  }
+
   const lastEventMillis = Math.max(requests.lastEndedMillis,
                                    timingMarkers.firstDocumentDOMContentLoadedTimestamp,
                                    timingMarkers.firstDocumentLoadTimestamp);
   const longestWidth = lastEventMillis - requests.firstStartedMillis;
   return Math.min(Math.max(ui.waterfallWidth / longestWidth, EPSILON), 1);
 }
 
 module.exports = {
--- a/devtools/client/netmonitor/sort-predicates.js
+++ b/devtools/client/netmonitor/sort-predicates.js
@@ -1,92 +1,89 @@
 /* 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 {
   getAbbreviatedMimeType,
-  getUrlBaseNameWithQuery,
-  getUrlHost,
 } = require("./request-utils");
 
 /**
  * Predicates used when sorting items.
  *
  * @param object first
  *        The first item used in the comparison.
  * @param object second
  *        The second item used in the comparison.
  * @return number
  *         <0 to sort first to a lower index than second
  *         =0 to leave first and second unchanged with respect to each other
  *         >0 to sort second to a lower index than first
  */
 
+function compareValues(first, second) {
+  if (first === second) {
+    return 0;
+  }
+  return first > second ? 1 : -1;
+}
+
 function waterfall(first, second) {
-  return first.startedMillis - second.startedMillis;
+  const result = compareValues(first.startedMillis, second.startedMillis);
+  return result || compareValues(first.id, second.id);
 }
 
 function status(first, second) {
-  return first.status == second.status
-         ? first.startedMillis - second.startedMillis
-         : first.status - second.status;
+  const result = compareValues(first.status, second.status);
+  return result || waterfall(first, second);
 }
 
 function method(first, second) {
-  if (first.method == second.method) {
-    return first.startedMillis - second.startedMillis;
-  }
-  return first.method > second.method ? 1 : -1;
+  const result = compareValues(first.method, second.method);
+  return result || waterfall(first, second);
 }
 
 function file(first, second) {
-  let firstUrl = getUrlBaseNameWithQuery(first.url).toLowerCase();
-  let secondUrl = getUrlBaseNameWithQuery(second.url).toLowerCase();
-  if (firstUrl == secondUrl) {
-    return first.startedMillis - second.startedMillis;
-  }
-  return firstUrl > secondUrl ? 1 : -1;
+  const firstUrl = first.urlDetails.baseNameWithQuery.toLowerCase();
+  const secondUrl = second.urlDetails.baseNameWithQuery.toLowerCase();
+  const result = compareValues(firstUrl, secondUrl);
+  return result || waterfall(first, second);
 }
 
 function domain(first, second) {
-  let firstDomain = getUrlHost(first.url).toLowerCase();
-  let secondDomain = getUrlHost(second.url).toLowerCase();
-  if (firstDomain == secondDomain) {
-    return first.startedMillis - second.startedMillis;
-  }
-  return firstDomain > secondDomain ? 1 : -1;
+  const firstDomain = first.urlDetails.host.toLowerCase();
+  const secondDomain = second.urlDetails.host.toLowerCase();
+  const result = compareValues(firstDomain, secondDomain);
+  return result || waterfall(first, second);
 }
 
 function cause(first, second) {
-  let firstCause = first.cause.type;
-  let secondCause = second.cause.type;
-  if (firstCause == secondCause) {
-    return first.startedMillis - second.startedMillis;
-  }
-  return firstCause > secondCause ? 1 : -1;
+  const firstCause = first.cause.type;
+  const secondCause = second.cause.type;
+  const result = compareValues(firstCause, secondCause);
+  return result || waterfall(first, second);
 }
 
 function type(first, second) {
-  let firstType = getAbbreviatedMimeType(first.mimeType).toLowerCase();
-  let secondType = getAbbreviatedMimeType(second.mimeType).toLowerCase();
-  if (firstType == secondType) {
-    return first.startedMillis - second.startedMillis;
-  }
-  return firstType > secondType ? 1 : -1;
+  const firstType = getAbbreviatedMimeType(first.mimeType).toLowerCase();
+  const secondType = getAbbreviatedMimeType(second.mimeType).toLowerCase();
+  const result = compareValues(firstType, secondType);
+  return result || waterfall(first, second);
 }
 
 function transferred(first, second) {
-  return first.transferredSize - second.transferredSize;
+  const result = compareValues(first.transferredSize, second.transferredSize);
+  return result || waterfall(first, second);
 }
 
 function size(first, second) {
-  return first.contentSize - second.contentSize;
+  const result = compareValues(first.contentSize, second.contentSize);
+  return result || waterfall(first, second);
 }
 
 exports.Sorters = {
   status,
   method,
   file,
   domain,
   cause,
--- a/devtools/client/netmonitor/waterfall-background.js
+++ b/devtools/client/netmonitor/waterfall-background.js
@@ -40,17 +40,17 @@ WaterfallBackground.prototype = {
     // Do a shallow compare of the previous and the new state
     const shouldUpdate = STATE_KEYS.some(key => this.prevState[key] !== state[key]);
     if (!shouldUpdate) {
       return;
     }
 
     this.prevState = state;
 
-    if (state.scale == null) {
+    if (state.waterfallWidth == null || state.scale == null) {
       this.document.mozSetImageElement("waterfall-background", null);
       return;
     }
 
     // Nuke the context.
     let canvasWidth = this.canvas.width = state.waterfallWidth;
     // Awww yeah, 1px, repeats on Y axis.
     let canvasHeight = this.canvas.height = 1;
--- a/devtools/shared/webconsole/network-monitor.js
+++ b/devtools/shared/webconsole/network-monitor.js
@@ -1095,17 +1095,17 @@ NetworkMonitor.prototype = {
 
     // If this is the parent process, there is no stackTraceCollector - the stack
     // trace will be added in NetworkMonitorChild._onNewEvent.
     if (this.owner.stackTraceCollector) {
       stacktrace = this.owner.stackTraceCollector.getStackTrace(event.channelId);
     }
 
     event.cause = {
-      type: causeType,
+      type: causeTypeToString(causeType),
       loadingDocumentUri: causeUri,
       stacktrace
     };
 
     httpActivity.isXHR = event.isXHR =
         (causeType === Ci.nsIContentPolicy.TYPE_XMLHTTPREQUEST ||
          causeType === Ci.nsIContentPolicy.TYPE_FETCH);
 
@@ -2047,8 +2047,41 @@ ConsoleProgressListener.prototype = {
     this.owner = null;
   },
 };
 
 function gSequenceId() {
   return gSequenceId.n++;
 }
 gSequenceId.n = 1;
+
+/**
+ * Convert a nsIContentPolicy constant to a display string
+ */
+const LOAD_CAUSE_STRINGS = {
+  [Ci.nsIContentPolicy.TYPE_INVALID]: "invalid",
+  [Ci.nsIContentPolicy.TYPE_OTHER]: "other",
+  [Ci.nsIContentPolicy.TYPE_SCRIPT]: "script",
+  [Ci.nsIContentPolicy.TYPE_IMAGE]: "img",
+  [Ci.nsIContentPolicy.TYPE_STYLESHEET]: "stylesheet",
+  [Ci.nsIContentPolicy.TYPE_OBJECT]: "object",
+  [Ci.nsIContentPolicy.TYPE_DOCUMENT]: "document",
+  [Ci.nsIContentPolicy.TYPE_SUBDOCUMENT]: "subdocument",
+  [Ci.nsIContentPolicy.TYPE_REFRESH]: "refresh",
+  [Ci.nsIContentPolicy.TYPE_XBL]: "xbl",
+  [Ci.nsIContentPolicy.TYPE_PING]: "ping",
+  [Ci.nsIContentPolicy.TYPE_XMLHTTPREQUEST]: "xhr",
+  [Ci.nsIContentPolicy.TYPE_OBJECT_SUBREQUEST]: "objectSubdoc",
+  [Ci.nsIContentPolicy.TYPE_DTD]: "dtd",
+  [Ci.nsIContentPolicy.TYPE_FONT]: "font",
+  [Ci.nsIContentPolicy.TYPE_MEDIA]: "media",
+  [Ci.nsIContentPolicy.TYPE_WEBSOCKET]: "websocket",
+  [Ci.nsIContentPolicy.TYPE_CSP_REPORT]: "csp",
+  [Ci.nsIContentPolicy.TYPE_XSLT]: "xslt",
+  [Ci.nsIContentPolicy.TYPE_BEACON]: "beacon",
+  [Ci.nsIContentPolicy.TYPE_FETCH]: "fetch",
+  [Ci.nsIContentPolicy.TYPE_IMAGESET]: "imageset",
+  [Ci.nsIContentPolicy.TYPE_WEB_MANIFEST]: "webManifest"
+};
+
+function causeTypeToString(causeType) {
+  return LOAD_CAUSE_STRINGS[causeType] || "unknown";
+}
--- a/dom/base/Navigator.cpp
+++ b/dom/base/Navigator.cpp
@@ -39,16 +39,17 @@
 #include "mozilla/dom/FlyWebPublishedServer.h"
 #include "mozilla/dom/FlyWebService.h"
 #include "mozilla/dom/Permissions.h"
 #include "mozilla/dom/Presentation.h"
 #include "mozilla/dom/ServiceWorkerContainer.h"
 #include "mozilla/dom/StorageManager.h"
 #include "mozilla/dom/TCPSocket.h"
 #include "mozilla/dom/VRDisplay.h"
+#include "mozilla/dom/WebAuthentication.h"
 #include "mozilla/dom/workers/RuntimeService.h"
 #include "mozilla/Hal.h"
 #include "nsISiteSpecificUserAgent.h"
 #include "mozilla/ClearOnShutdown.h"
 #include "mozilla/SSE.h"
 #include "mozilla/StaticPtr.h"
 #include "Connection.h"
 #include "mozilla/dom/Event.h" // for nsIDOMEvent::InternalDOMEvent()
@@ -199,16 +200,17 @@ NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mPermissions)
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mGeolocation)
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mNotification)
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mBatteryManager)
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mBatteryPromise)
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mPowerManager)
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mConnection)
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mStorageManager)
+  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mAuthentication)
 #ifdef MOZ_AUDIO_CHANNEL_MANAGER
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mAudioChannelManager)
 #endif
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mMediaDevices)
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mTimeManager)
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mServiceWorkerContainer)
 
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mWindow)
@@ -2177,10 +2179,19 @@ Navigator::GetPresentation(ErrorResult& 
       return nullptr;
     }
     mPresentation = Presentation::Create(mWindow);
   }
 
   return mPresentation;
 }
 
+WebAuthentication*
+Navigator::Authentication()
+{
+  if (!mAuthentication) {
+    mAuthentication = new WebAuthentication(GetWindow());
+  }
+  return mAuthentication;
+}
+
 } // namespace dom
 } // namespace mozilla
--- a/dom/base/Navigator.h
+++ b/dom/base/Navigator.h
@@ -36,16 +36,17 @@ class systemMessageCallback;
 class MediaDevices;
 struct MediaStreamConstraints;
 class WakeLock;
 class ArrayBufferViewOrBlobOrStringOrFormData;
 class ServiceWorkerContainer;
 class DOMRequest;
 struct FlyWebPublishOptions;
 struct FlyWebFilter;
+class WebAuthentication;
 } // namespace dom
 } // namespace mozilla
 
 //*****************************************************************************
 // Navigator: Script "navigator" object
 //*****************************************************************************
 
 namespace mozilla {
@@ -235,16 +236,18 @@ public:
                               MozGetUserMediaDevicesSuccessCallback& aOnSuccess,
                               NavigatorUserMediaErrorCallback& aOnError,
                               uint64_t aInnerWindowID,
                               const nsAString& aCallID,
                               ErrorResult& aRv);
 
   already_AddRefed<ServiceWorkerContainer> ServiceWorker();
 
+  mozilla::dom::WebAuthentication* Authentication();
+
   void GetLanguages(nsTArray<nsString>& aLanguages);
 
   bool MozE10sEnabled();
 
   StorageManager* Storage();
 
   static void GetAcceptLanguages(nsTArray<nsString>& aLanguages);
 
@@ -292,16 +295,17 @@ private:
   RefPtr<nsPluginArray> mPlugins;
   RefPtr<Permissions> mPermissions;
   RefPtr<Geolocation> mGeolocation;
   RefPtr<DesktopNotificationCenter> mNotification;
   RefPtr<battery::BatteryManager> mBatteryManager;
   RefPtr<Promise> mBatteryPromise;
   RefPtr<PowerManager> mPowerManager;
   RefPtr<network::Connection> mConnection;
+  RefPtr<WebAuthentication> mAuthentication;
 #ifdef MOZ_AUDIO_CHANNEL_MANAGER
   RefPtr<system::AudioChannelManager> mAudioChannelManager;
 #endif
   RefPtr<MediaDevices> mMediaDevices;
   nsTArray<nsWeakPtr> mDeviceStorageStores;
   RefPtr<time::TimeManager> mTimeManager;
   RefPtr<ServiceWorkerContainer> mServiceWorkerContainer;
   nsCOMPtr<nsPIDOMWindowInner> mWindow;
--- a/dom/bindings/Bindings.conf
+++ b/dom/bindings/Bindings.conf
@@ -1057,16 +1057,20 @@ DOMInterfaces = {
 'VTTCue': {
     'nativeType': 'mozilla::dom::TextTrackCue'
 },
 
 'VTTRegion': {
   'nativeType': 'mozilla::dom::TextTrackRegion',
 },
 
+'WebAuthentication': {
+    'implicitJSContext': 'makeCredential',
+},
+
 'WindowClient': {
     'nativeType': 'mozilla::dom::workers::ServiceWorkerWindowClient',
     'headerFile': 'mozilla/dom/workers/bindings/ServiceWorkerWindowClient.h',
 },
 
 'WebGLActiveInfo': {
     'nativeType': 'mozilla::WebGLActiveInfo',
     'headerFile': 'WebGLActiveInfo.h'
--- a/dom/crypto/CryptoBuffer.cpp
+++ b/dom/crypto/CryptoBuffer.cpp
@@ -119,17 +119,17 @@ CryptoBuffer::FromJwkBase64(const nsStri
   nsresult rv = Base64URLDecode(temp, Base64URLDecodePaddingPolicy::Reject,
                                 *this);
   NS_ENSURE_SUCCESS(rv, rv);
 
   return NS_OK;
 }
 
 nsresult
-CryptoBuffer::ToJwkBase64(nsString& aBase64)
+CryptoBuffer::ToJwkBase64(nsString& aBase64) const
 {
   // Shortcut for the empty octet string
   if (Length() == 0) {
     aBase64.Truncate();
     return NS_OK;
   }
 
   nsAutoCString base64;
--- a/dom/crypto/CryptoBuffer.h
+++ b/dom/crypto/CryptoBuffer.h
@@ -39,17 +39,17 @@ public:
   uint8_t* Assign(const TypedArray_base<T, UnwrapArray,
                                         GetLengthAndDataAndSharedness>& aArray)
   {
     aArray.ComputeLengthAndData();
     return Assign(aArray.Data(), aArray.Length());
   }
 
   nsresult FromJwkBase64(const nsString& aBase64);
-  nsresult ToJwkBase64(nsString& aBase64);
+  nsresult ToJwkBase64(nsString& aBase64) const;
   bool ToSECItem(PLArenaPool* aArena, SECItem* aItem) const;
   JSObject* ToUint8Array(JSContext* aCx) const;
   bool ToNewUnsignedBuffer(uint8_t** aBuf, uint32_t* aBufLen) const;
 
   bool GetBigIntValue(unsigned long& aRetVal);
 };
 
 } // namespace dom
--- a/dom/media/MediaDecoderStateMachine.cpp
+++ b/dom/media/MediaDecoderStateMachine.cpp
@@ -189,25 +189,51 @@ public:
   virtual void Exit() {};  // Exit action.
   virtual void Step() {}   // Perform a 'cycle' of this state object.
   virtual State GetState() const = 0;
 
   // Event handlers for various events.
   virtual void HandleCDMProxyReady() {}
   virtual void HandleAudioDecoded(MediaData* aAudio) {}
   virtual void HandleVideoDecoded(MediaData* aVideo, TimeStamp aDecodeStart) {}
-  virtual void HandleAudioNotDecoded(const MediaResult& aError);
-  virtual void HandleVideoNotDecoded(const MediaResult& aError);
   virtual void HandleAudioWaited(MediaData::Type aType);
   virtual void HandleVideoWaited(MediaData::Type aType);
   virtual void HandleNotWaited(const WaitForDataRejectValue& aRejection);
-  virtual void HandleEndOfStream() {}
-  virtual void HandleWaitingForData() {}
   virtual void HandleAudioCaptured() {}
 
+  virtual void HandleWaitingForAudio()
+  {
+    mMaster->WaitForData(MediaData::AUDIO_DATA);
+  }
+
+  virtual void HandleAudioCanceled()
+  {
+    mMaster->EnsureAudioDecodeTaskQueued();
+  }
+
+  virtual void HandleEndOfAudio()
+  {
+    AudioQueue().Finish();
+  }
+
+  virtual void HandleWaitingForVideo()
+  {
+    mMaster->WaitForData(MediaData::VIDEO_DATA);
+  }
+
+  virtual void HandleVideoCanceled()
+  {
+    mMaster->EnsureVideoDecodeTaskQueued();
+  }
+
+  virtual void HandleEndOfVideo()
+  {
+    VideoQueue().Finish();
+  }
+
   virtual RefPtr<MediaDecoder::SeekPromise> HandleSeek(SeekTarget aTarget);
 
   virtual RefPtr<ShutdownPromise> HandleShutdown();
 
   virtual void HandleVideoSuspendTimeout() = 0;
 
   virtual void HandleResumeVideoDecoding();
 
@@ -493,18 +519,47 @@ public:
   }
 
   void HandleVideoDecoded(MediaData* aVideo, TimeStamp aDecodeStart) override
   {
     mMaster->PushVideo(aVideo);
     MaybeFinishDecodeFirstFrame();
   }
 
-  void HandleAudioNotDecoded(const MediaResult& aError) override;
-  void HandleVideoNotDecoded(const MediaResult& aError) override;
+  void HandleWaitingForAudio() override
+  {
+    mMaster->WaitForData(MediaData::AUDIO_DATA);
+  }
+
+  void HandleAudioCanceled() override
+  {
+    mMaster->RequestAudioData();
+  }
+
+  void HandleEndOfAudio() override
+  {
+    AudioQueue().Finish();
+    MaybeFinishDecodeFirstFrame();
+  }
+
+  void HandleWaitingForVideo() override
+  {
+    mMaster->WaitForData(MediaData::VIDEO_DATA);
+  }
+
+  void HandleVideoCanceled() override
+  {
+    mMaster->RequestVideoData(false, media::TimeUnit());
+  }
+
+  void HandleEndOfVideo() override
+  {
+    VideoQueue().Finish();
+    MaybeFinishDecodeFirstFrame();
+  }
 
   void HandleAudioWaited(MediaData::Type aType) override
   {
     mMaster->RequestAudioData();
   }
 
   void HandleVideoWaited(MediaData::Type aType) override
   {
@@ -599,20 +654,28 @@ public:
   void HandleVideoDecoded(MediaData* aVideo, TimeStamp aDecodeStart) override
   {
     mMaster->PushVideo(aVideo);
     DispatchDecodeTasksIfNeeded();
     MaybeStopPrerolling();
     CheckSlowDecoding(aDecodeStart);
   }
 
-  void HandleEndOfStream() override;
-
-  void HandleWaitingForData() override
+  void HandleEndOfAudio() override;
+  void HandleEndOfVideo() override;
+
+  void HandleWaitingForAudio() override
   {
+    mMaster->WaitForData(MediaData::AUDIO_DATA);
+    MaybeStopPrerolling();
+  }
+
+  void HandleWaitingForVideo() override
+  {
+    mMaster->WaitForData(MediaData::VIDEO_DATA);
     MaybeStopPrerolling();
   }
 
   void HandleAudioCaptured() override
   {
     MaybeStopPrerolling();
     // MediaSink is changed. Schedule Step() to check if we can start playback.
     mMaster->ScheduleStateMachine();
@@ -814,18 +877,16 @@ public:
 
   State GetState() const override
   {
     return DECODER_STATE_SEEKING;
   }
 
   void HandleAudioDecoded(MediaData* aAudio) override = 0;
   void HandleVideoDecoded(MediaData* aVideo, TimeStamp aDecodeStart) override = 0;
-  void HandleAudioNotDecoded(const MediaResult& aError) override = 0;
-  void HandleVideoNotDecoded(const MediaResult& aError) override = 0;
   void HandleAudioWaited(MediaData::Type aType) override = 0;
   void HandleVideoWaited(MediaData::Type aType) override = 0;
   void HandleNotWaited(const WaitForDataRejectValue& aRejection) override = 0;
 
   void HandleVideoSuspendTimeout() override
   {
     // Do nothing since we want a valid video frame to show when seek is done.
   }
@@ -929,18 +990,66 @@ public:
 
     if (!mDoneVideoSeeking) {
       RequestVideoData();
       return;
     }
     MaybeFinishSeek();
   }
 
-  void HandleAudioNotDecoded(const MediaResult& aError) override;
-  void HandleVideoNotDecoded(const MediaResult& aError) override;
+  void HandleWaitingForAudio() override
+  {
+    if (!mSeekJob.mTarget->IsVideoOnly()) {
+      MOZ_ASSERT(!mDoneAudioSeeking);
+      mMaster->WaitForData(MediaData::AUDIO_DATA);
+    }
+  }
+
+  void HandleAudioCanceled() override
+  {
+    if (!mSeekJob.mTarget->IsVideoOnly()) {
+      MOZ_ASSERT(!mDoneAudioSeeking);
+      RequestAudioData();
+    }
+  }
+
+  void HandleEndOfAudio() override
+  {
+    if (!mSeekJob.mTarget->IsVideoOnly()) {
+      MOZ_ASSERT(!mDoneAudioSeeking);
+      AudioQueue().Finish();
+      mDoneAudioSeeking = true;
+      MaybeFinishSeek();
+    }
+  }
+
+  void HandleWaitingForVideo() override
+  {
+    MOZ_ASSERT(!mDoneVideoSeeking);
+    mMaster->WaitForData(MediaData::VIDEO_DATA);
+  }
+
+  void HandleVideoCanceled() override
+  {
+    MOZ_ASSERT(!mDoneVideoSeeking);
+    RequestVideoData();
+  }
+
+  void HandleEndOfVideo() override
+  {
+    MOZ_ASSERT(!mDoneVideoSeeking);
+    if (mFirstVideoFrameAfterSeek) {
+      // Hit the end of stream. Move mFirstVideoFrameAfterSeek into
+      // mSeekedVideoData so we have something to display after seeking.
+      mMaster->PushVideo(mFirstVideoFrameAfterSeek);
+    }
+    VideoQueue().Finish();
+    mDoneVideoSeeking = true;
+    MaybeFinishSeek();
+  }
 
   void HandleAudioWaited(MediaData::Type aType) override
   {
     MOZ_ASSERT(!mDoneAudioSeeking || !mDoneVideoSeeking, "Seek shouldn't be finished");
 
     // Ignore pending requests from video-only seek.
     if (mSeekJob.mTarget->IsVideoOnly()) {
       return;
@@ -1343,18 +1452,61 @@ private:
     if (aVideo->mTime > mCurrentTime) {
       mMaster->PushVideo(aVideo);
       FinishSeek();
     } else {
       RequestVideoData();
     }
   }
 
-  void HandleAudioNotDecoded(const MediaResult& aError) override;
-  void HandleVideoNotDecoded(const MediaResult& aError) override;
+  void HandleWaitingForAudio() override
+  {
+    MOZ_ASSERT(!mSeekJob.mPromise.IsEmpty(), "Seek shouldn't be finished");
+    MOZ_ASSERT(NeedMoreVideo());
+    // We don't care about audio decode errors in this state which will be
+    // handled by other states after seeking.
+  }
+
+  void HandleAudioCanceled() override
+  {
+    MOZ_ASSERT(!mSeekJob.mPromise.IsEmpty(), "Seek shouldn't be finished");
+    MOZ_ASSERT(NeedMoreVideo());
+    // We don't care about audio decode errors in this state which will be
+    // handled by other states after seeking.
+  }
+
+  void HandleEndOfAudio() override
+  {
+    MOZ_ASSERT(!mSeekJob.mPromise.IsEmpty(), "Seek shouldn't be finished");
+    MOZ_ASSERT(NeedMoreVideo());
+    // We don't care about audio decode errors in this state which will be
+    // handled by other states after seeking.
+  }
+
+  void HandleWaitingForVideo() override
+  {
+    MOZ_ASSERT(!mSeekJob.mPromise.IsEmpty(), "Seek shouldn't be finished");
+    MOZ_ASSERT(NeedMoreVideo());
+    mMaster->WaitForData(MediaData::VIDEO_DATA);
+  }
+
+  void HandleVideoCanceled() override
+  {
+    MOZ_ASSERT(!mSeekJob.mPromise.IsEmpty(), "Seek shouldn't be finished");
+    MOZ_ASSERT(NeedMoreVideo());
+    RequestVideoData();
+  }
+
+  void HandleEndOfVideo() override
+  {
+    MOZ_ASSERT(!mSeekJob.mPromise.IsEmpty(), "Seek shouldn't be finished");
+    MOZ_ASSERT(NeedMoreVideo());
+    VideoQueue().Finish();
+    FinishSeek();
+  }
 
   void HandleAudioWaited(MediaData::Type aType) override
   {
     // We don't care about audio in this state.
   }
 
   void HandleVideoWaited(MediaData::Type aType) override
   {
@@ -1487,17 +1639,18 @@ public:
   void HandleVideoDecoded(MediaData* aVideo, TimeStamp aDecodeStart) override
   {
     // This might be the sample we need to exit buffering.
     // Schedule Step() to check it.
     mMaster->PushVideo(aVideo);
     mMaster->ScheduleStateMachine();
   }
 
-  void HandleEndOfStream() override;
+  void HandleEndOfAudio() override;
+  void HandleEndOfVideo() override;
 
   void HandleVideoSuspendTimeout() override
   {
     if (mMaster->HasVideo()) {
       mMaster->mVideoDecodeSuspended = true;
       mMaster->mOnPlaybackEvent.Notify(MediaEventType::EnterVideoSuspend);
       Reader()->SetVideoBlankDecode(true);
     }
@@ -1637,19 +1790,16 @@ public:
     MOZ_DIAGNOSTIC_ASSERT(false, "Shouldn't escape the SHUTDOWN state.");
   }
 
   State GetState() const override
   {
     return DECODER_STATE_SHUTDOWN;
   }
 
-  void HandleAudioNotDecoded(const MediaResult& aError) override {}
-  void HandleVideoNotDecoded(const MediaResult& aError) override {}
-
   RefPtr<MediaDecoder::SeekPromise> HandleSeek(SeekTarget aTarget) override
   {
     MOZ_DIAGNOSTIC_ASSERT(false, "Can't seek in shutdown state.");
     return MediaDecoder::SeekPromise::CreateAndReject(true, __func__);
   }
 
   RefPtr<ShutdownPromise> HandleShutdown() override
   {
@@ -1684,58 +1834,16 @@ StateObject::HandleVideoWaited(MediaData
 
 void
 MediaDecoderStateMachine::
 StateObject::HandleNotWaited(const WaitForDataRejectValue& aRejection)
 {
 
 }
 
-void
-MediaDecoderStateMachine::
-StateObject::HandleAudioNotDecoded(const MediaResult& aError)
-{
-  switch (aError.Code()) {
-    case NS_ERROR_DOM_MEDIA_WAITING_FOR_DATA:
-      mMaster->WaitForData(MediaData::AUDIO_DATA);
-      HandleWaitingForData();
-      break;
-    case NS_ERROR_DOM_MEDIA_CANCELED:
-      mMaster->EnsureAudioDecodeTaskQueued();
-      break;
-    case NS_ERROR_DOM_MEDIA_END_OF_STREAM:
-      AudioQueue().Finish();
-      HandleEndOfStream();
-      break;
-    default:
-      mMaster->DecodeError(aError);
-  }
-}
-
-void
-MediaDecoderStateMachine::
-StateObject::HandleVideoNotDecoded(const MediaResult& aError)
-{
-  switch (aError.Code()) {
-    case NS_ERROR_DOM_MEDIA_WAITING_FOR_DATA:
-      mMaster->WaitForData(MediaData::VIDEO_DATA);
-      HandleWaitingForData();
-      break;
-    case NS_ERROR_DOM_MEDIA_CANCELED:
-      mMaster->EnsureVideoDecodeTaskQueued();
-      break;
-    case NS_ERROR_DOM_MEDIA_END_OF_STREAM:
-      VideoQueue().Finish();
-      HandleEndOfStream();
-      break;
-    default:
-      mMaster->DecodeError(aError);
-  }
-}
-
 RefPtr<MediaDecoder::SeekPromise>
 MediaDecoderStateMachine::
 StateObject::HandleSeek(SeekTarget aTarget)
 {
   SLOG("Changed state to SEEKING (to %lld)", aTarget.GetTime().ToMicroseconds());
   SeekJob seekJob;
   seekJob.mTarget = Some(aTarget);
   return SetSeekingState(Move(seekJob), EventVisibility::Observable);
@@ -1924,56 +2032,16 @@ DecodingFirstFrameState::Enter()
   }
   if (mMaster->HasVideo()) {
     mMaster->RequestVideoData(false, media::TimeUnit());
   }
 }
 
 void
 MediaDecoderStateMachine::
-DecodingFirstFrameState::HandleAudioNotDecoded(const MediaResult& aError)
-{
-  switch (aError.Code()) {
-    case NS_ERROR_DOM_MEDIA_WAITING_FOR_DATA:
-      mMaster->WaitForData(MediaData::AUDIO_DATA);
-      break;
-    case NS_ERROR_DOM_MEDIA_CANCELED:
-      mMaster->RequestAudioData();
-      break;
-    case NS_ERROR_DOM_MEDIA_END_OF_STREAM:
-      AudioQueue().Finish();
-      MaybeFinishDecodeFirstFrame();
-      break;
-    default:
-      mMaster->DecodeError(aError);
-  }
-}
-
-void
-MediaDecoderStateMachine::
-DecodingFirstFrameState::HandleVideoNotDecoded(const MediaResult& aError)
-{
-  switch (aError.Code()) {
-    case NS_ERROR_DOM_MEDIA_WAITING_FOR_DATA:
-      mMaster->WaitForData(MediaData::VIDEO_DATA);
-      break;
-    case NS_ERROR_DOM_MEDIA_CANCELED:
-      mMaster->RequestVideoData(false, media::TimeUnit());
-      break;
-    case NS_ERROR_DOM_MEDIA_END_OF_STREAM:
-      VideoQueue().Finish();
-      MaybeFinishDecodeFirstFrame();
-      break;
-    default:
-      mMaster->DecodeError(aError);
-  }
-}
-
-void
-MediaDecoderStateMachine::
 DecodingFirstFrameState::MaybeFinishDecodeFirstFrame()
 {
   MOZ_ASSERT(!mMaster->mSentFirstFrameLoadedEvent);
 
   if ((mMaster->IsAudioDecoding() && AudioQueue().GetSize() == 0) ||
       (mMaster->IsVideoDecoding() && VideoQueue().GetSize() == 0)) {
     return;
   }
@@ -2028,18 +2096,31 @@ DecodingState::Enter()
   // Will enter dormant when playback is paused for a while.
   if (mMaster->mPlayState == MediaDecoder::PLAY_STATE_PAUSED) {
     StartDormantTimer();
   }
 }
 
 void
 MediaDecoderStateMachine::
-DecodingState::HandleEndOfStream()
+DecodingState::HandleEndOfAudio()
 {
+  AudioQueue().Finish();
+  if (mMaster->CheckIfDecodeComplete()) {
+    SetState<CompletedState>();
+  } else {
+    MaybeStopPrerolling();
+  }
+}
+
+void
+MediaDecoderStateMachine::
+DecodingState::HandleEndOfVideo()
+{
+  VideoQueue().Finish();
   if (mMaster->CheckIfDecodeComplete()) {
     SetState<CompletedState>();
   } else {
     MaybeStopPrerolling();
   }
 }
 
 void
@@ -2143,102 +2224,16 @@ SeekingState::SeekCompleted()
     mMaster->mOnPlaybackEvent.Notify(MediaEventType::Invalidate);
   }
 
   SetState<DecodingState>();
 }
 
 void
 MediaDecoderStateMachine::
-AccurateSeekingState::HandleAudioNotDecoded(const MediaResult& aError)
-{
-  if (mSeekJob.mTarget->IsVideoOnly()) {
-    return;
-  }
-  MOZ_ASSERT(!mDoneAudioSeeking);
-  switch (aError.Code()) {
-    case NS_ERROR_DOM_MEDIA_WAITING_FOR_DATA:
-      mMaster->WaitForData(MediaData::AUDIO_DATA);
-      break;
-    case NS_ERROR_DOM_MEDIA_CANCELED:
-      RequestAudioData();
-      break;
-    case NS_ERROR_DOM_MEDIA_END_OF_STREAM:
-      AudioQueue().Finish();
-      mDoneAudioSeeking = true;
-      MaybeFinishSeek();
-      break;
-    default:
-      mMaster->DecodeError(aError);
-  }
-}
-
-void
-MediaDecoderStateMachine::
-AccurateSeekingState::HandleVideoNotDecoded(const MediaResult& aError)
-{
-  MOZ_ASSERT(!mDoneVideoSeeking);
-  switch (aError.Code()) {
-    case NS_ERROR_DOM_MEDIA_WAITING_FOR_DATA:
-      mMaster->WaitForData(MediaData::VIDEO_DATA);
-      break;
-    case NS_ERROR_DOM_MEDIA_CANCELED:
-      RequestVideoData();
-      break;
-    case NS_ERROR_DOM_MEDIA_END_OF_STREAM:
-      if (mFirstVideoFrameAfterSeek) {
-        // Hit the end of stream. Move mFirstVideoFrameAfterSeek into
-        // mSeekedVideoData so we have something to display after seeking.
-        mMaster->PushVideo(mFirstVideoFrameAfterSeek);
-      }
-      VideoQueue().Finish();
-      mDoneVideoSeeking = true;
-      MaybeFinishSeek();
-      break;
-    default:
-      mMaster->DecodeError(aError);
-  }
-}
-
-void
-MediaDecoderStateMachine::
-NextFrameSeekingState::HandleAudioNotDecoded(const MediaResult& aError)
-{
-  MOZ_ASSERT(!mSeekJob.mPromise.IsEmpty(), "Seek shouldn't be finished");
-  MOZ_ASSERT(NeedMoreVideo());
-  // We don't care about audio decode errors in this state which will be
-  // handled by other states after seeking.
-}
-
-void
-MediaDecoderStateMachine::
-NextFrameSeekingState::HandleVideoNotDecoded(const MediaResult& aError)
-{
-  MOZ_ASSERT(!mSeekJob.mPromise.IsEmpty(), "Seek shouldn't be finished");
-  MOZ_ASSERT(NeedMoreVideo());
-  // Video seek not finished.
-  switch (aError.Code()) {
-    case NS_ERROR_DOM_MEDIA_WAITING_FOR_DATA:
-      mMaster->WaitForData(MediaData::VIDEO_DATA);
-      break;
-    case NS_ERROR_DOM_MEDIA_CANCELED:
-      RequestVideoData();
-      break;
-    case NS_ERROR_DOM_MEDIA_END_OF_STREAM:
-      VideoQueue().Finish();
-      FinishSeek();
-      break;
-    default:
-      // Raise an error since we can't finish video seek anyway.
-      mMaster->DecodeError(aError);
-  }
-}
-
-void
-MediaDecoderStateMachine::
 BufferingState::DispatchDecodeTasksIfNeeded()
 {
   if (mMaster->IsAudioDecoding() &&
       !mMaster->HaveEnoughDecodedAudio()) {
     mMaster->EnsureAudioDecodeTaskQueued();
   }
 
   if (mMaster->IsVideoDecoding() &&
@@ -2286,18 +2281,32 @@ BufferingState::Step()
   }
 
   SLOG("Buffered for %.3lfs", (now - mBufferingStart).ToSeconds());
   SetState<DecodingState>();
 }
 
 void
 MediaDecoderStateMachine::
-BufferingState::HandleEndOfStream()
+BufferingState::HandleEndOfAudio()
 {
+  AudioQueue().Finish();
+  if (mMaster->CheckIfDecodeComplete()) {
+    SetState<CompletedState>();
+  } else {
+    // Check if we can exit buffering.
+    mMaster->ScheduleStateMachine();
+  }
+}
+
+void
+MediaDecoderStateMachine::
+BufferingState::HandleEndOfVideo()
+{
+  VideoQueue().Finish();
   if (mMaster->CheckIfDecodeComplete()) {
     SetState<CompletedState>();
   } else {
     // Check if we can exit buffering.
     mMaster->ScheduleStateMachine();
   }
 }
 
@@ -3006,17 +3015,29 @@ MediaDecoderStateMachine::RequestAudioDa
         // audio->GetEndTime() is not always mono-increasing in chained ogg.
         mDecodedAudioEndTime = std::max(aAudio->GetEndTime(), mDecodedAudioEndTime);
         SAMPLE_LOG("OnAudioDecoded [%lld,%lld]", aAudio->mTime, aAudio->GetEndTime());
         mStateObj->HandleAudioDecoded(aAudio);
       },
       [this] (const MediaResult& aError) {
         SAMPLE_LOG("OnAudioNotDecoded aError=%u", aError.Code());
         mAudioDataRequest.Complete();
-        mStateObj->HandleAudioNotDecoded(aError);
+        switch (aError.Code()) {
+          case NS_ERROR_DOM_MEDIA_WAITING_FOR_DATA:
+            mStateObj->HandleWaitingForAudio();
+            break;
+          case NS_ERROR_DOM_MEDIA_CANCELED:
+            mStateObj->HandleAudioCanceled();
+            break;
+          case NS_ERROR_DOM_MEDIA_END_OF_STREAM:
+            mStateObj->HandleEndOfAudio();
+            break;
+          default:
+            DecodeError(aError);
+        }
       })
   );
 }
 
 void
 MediaDecoderStateMachine::EnsureVideoDecodeTaskQueued()
 {
   MOZ_ASSERT(OnTaskQueue());
@@ -3063,17 +3084,29 @@ MediaDecoderStateMachine::RequestVideoDa
         // Handle abnormal or negative timestamps.
         mDecodedVideoEndTime = std::max(mDecodedVideoEndTime, aVideo->GetEndTime());
         SAMPLE_LOG("OnVideoDecoded [%lld,%lld]", aVideo->mTime, aVideo->GetEndTime());
         mStateObj->HandleVideoDecoded(aVideo, videoDecodeStartTime);
       },
       [this] (const MediaResult& aError) {
         SAMPLE_LOG("OnVideoNotDecoded aError=%u", aError.Code());
         mVideoDataRequest.Complete();
-        mStateObj->HandleVideoNotDecoded(aError);
+        switch (aError.Code()) {
+          case NS_ERROR_DOM_MEDIA_WAITING_FOR_DATA:
+            mStateObj->HandleWaitingForVideo();
+            break;
+          case NS_ERROR_DOM_MEDIA_CANCELED:
+            mStateObj->HandleVideoCanceled();
+            break;
+          case NS_ERROR_DOM_MEDIA_END_OF_STREAM:
+            mStateObj->HandleEndOfVideo();
+            break;
+          default:
+            DecodeError(aError);
+        }
       })
   );
 }
 
 void
 MediaDecoderStateMachine::WaitForData(MediaData::Type aType)
 {
   MOZ_ASSERT(OnTaskQueue());
--- a/dom/media/platforms/wmf/DXVA2Manager.cpp
+++ b/dom/media/platforms/wmf/DXVA2Manager.cpp
@@ -99,27 +99,36 @@ public:
   // Copies a region (aRegion) of the video frame stored in aVideoSample
   // into an image which is returned by aOutImage.
   HRESULT CopyToImage(IMFSample* aVideoSample,
                       const nsIntRect& aRegion,
                       Image** aOutImage) override;
 
   bool SupportsConfig(IMFMediaType* aType, float aFramerate) override;
 
+  bool CreateDXVA2Decoder(const VideoInfo& aVideoInfo,
+                          nsACString& aFailureReason) override;
+
 private:
+  bool CanCreateDecoder(const DXVA2_VideoDesc& aDesc,
+                        const float aFramerate) const;
+
+  already_AddRefed<IDirectXVideoDecoder>
+  CreateDecoder(const DXVA2_VideoDesc& aDesc) const;
+
   RefPtr<IDirect3D9Ex> mD3D9;
   RefPtr<IDirect3DDevice9Ex> mDevice;
   RefPtr<IDirect3DDeviceManager9> mDeviceManager;
   RefPtr<D3D9RecycleAllocator> mTextureClientAllocator;
   RefPtr<IDirectXVideoDecoderService> mDecoderService;
   RefPtr<IDirect3DSurface9> mSyncSurface;
+  RefPtr<IDirectXVideoDecoder> mDecoder;
   GUID mDecoderGUID;
   UINT32 mResetToken;
   bool mFirstFrame;
-  bool mIsAMDPreUVD4;
 };
 
 void GetDXVA2ExtendedFormatFromMFMediaType(IMFMediaType *pType,
                                            DXVA2_ExtendedFormat *pFormat)
 {
   // Get the interlace mode.
   MFVideoInterlaceMode interlace =
     (MFVideoInterlaceMode)MFGetAttributeUINT32(pType, MF_MT_INTERLACE_MODE, MFVideoInterlace_Unknown);
@@ -189,66 +198,25 @@ static const GUID DXVA2_Intel_ModeH264_E
 // This tests if a DXVA video decoder can be created for the given media type/resolution.
 // It uses the same decoder device (DXVA2_ModeH264_E - DXVA2_ModeH264_VLD_NoFGT) as the H264
 // decoder MFT provided by windows (CLSID_CMSH264DecoderMFT) uses, so we can use it to determine
 // if the MFT will use software fallback or not.
 bool
 D3D9DXVA2Manager::SupportsConfig(IMFMediaType* aType, float aFramerate)
 {
   MOZ_ASSERT(NS_IsMainThread());
-  gfx::D3D9VideoCrashGuard crashGuard;
-  if (crashGuard.Crashed()) {
-    NS_WARNING("DXVA2D3D9 crash detected");
-    return false;
-  }
-
   DXVA2_VideoDesc desc;
   HRESULT hr = ConvertMFTypeToDXVAType(aType, &desc);
   NS_ENSURE_TRUE(SUCCEEDED(hr), false);
-
-  // AMD cards with UVD3 or earlier perform poorly trying to decode 1080p60 in hardware,
-  // so use software instead. Pick 45 as an arbitrary upper bound for the framerate we can
-  // handle.
-  if (mIsAMDPreUVD4 &&
-      (desc.SampleWidth >= 1920 || desc.SampleHeight >= 1088) &&
-      aFramerate > 45) {
-    return false;
-  }
-
-  UINT configCount;
-  DXVA2_ConfigPictureDecode* configs = nullptr;
-  hr = mDecoderService->GetDecoderConfigurations(mDecoderGUID, &desc, nullptr, &configCount, &configs);
-  NS_ENSURE_TRUE(SUCCEEDED(hr), false);
-
-  RefPtr<IDirect3DSurface9> surface;
-  hr = mDecoderService->CreateSurface(desc.SampleWidth, desc.SampleHeight, 0, (D3DFORMAT)MAKEFOURCC('N', 'V', '1', '2'),
-                                      D3DPOOL_DEFAULT, 0, DXVA2_VideoDecoderRenderTarget,
-                                      surface.StartAssignment(), NULL);
-  if (!SUCCEEDED(hr)) {
-    CoTaskMemFree(configs);
-    return false;
-  }
-
-  for (UINT i = 0; i < configCount; i++) {
-    RefPtr<IDirectXVideoDecoder> decoder;
-    IDirect3DSurface9* surfaces = surface;
-    hr = mDecoderService->CreateVideoDecoder(mDecoderGUID, &desc, &configs[i], &surfaces, 1, decoder.StartAssignment());
-    if (SUCCEEDED(hr) && decoder) {
-      CoTaskMemFree(configs);
-      return true;
-    }
-  }
-  CoTaskMemFree(configs);
-  return false;
+  return CanCreateDecoder(desc, aFramerate);
 }
 
 D3D9DXVA2Manager::D3D9DXVA2Manager()
   : mResetToken(0)
   , mFirstFrame(true)
-  , mIsAMDPreUVD4(false)
 {
   MOZ_COUNT_CTOR(D3D9DXVA2Manager);
   MOZ_ASSERT(NS_IsMainThread());
 }
 
 D3D9DXVA2Manager::~D3D9DXVA2Manager()
 {
   MOZ_COUNT_DTOR(D3D9DXVA2Manager);
@@ -515,16 +483,88 @@ DXVA2Manager::CreateD3D9DXVA(layers::Kno
   if (SUCCEEDED(hr)) {
     return d3d9Manager.forget();
   }
 
   // No hardware accelerated video decoding. :(
   return nullptr;
 }
 
+bool
+D3D9DXVA2Manager::CreateDXVA2Decoder(const VideoInfo& aVideoInfo,
+                                     nsACString& aFailureReason)
+{
+  MOZ_ASSERT(NS_IsMainThread());
+  DXVA2_VideoDesc desc;
+  desc.SampleWidth = aVideoInfo.mImage.width;
+  desc.SampleHeight = aVideoInfo.mImage.height;
+  desc.Format = (D3DFORMAT)MAKEFOURCC('N','V','1','2');
+
+  // Assume the current duration is representative for the entire video.
+  float framerate = 1000000.0 / aVideoInfo.mDuration;
+  if (IsUnsupportedResolution(desc.SampleWidth, desc.SampleHeight , framerate)) {
+    return false;
+  }
+
+  mDecoder = CreateDecoder(desc);
+  if (!mDecoder) {
+    aFailureReason = nsPrintfCString("Fail to create video decoder in D3D9DXVA2Manager.");
+    return false;
+  }
+  return true;
+}
+
+bool
+D3D9DXVA2Manager::CanCreateDecoder(const DXVA2_VideoDesc& aDesc,
+                                   const float aFramerate) const
+{
+  MOZ_ASSERT(NS_IsMainThread());
+  if (IsUnsupportedResolution(aDesc.SampleWidth, aDesc.SampleHeight, aFramerate)) {
+    return false;
+  }
+  RefPtr<IDirectXVideoDecoder> decoder = CreateDecoder(aDesc);
+  return decoder.get() != nullptr;
+}
+
+already_AddRefed<IDirectXVideoDecoder>
+D3D9DXVA2Manager::CreateDecoder(const DXVA2_VideoDesc& aDesc) const
+{
+  MOZ_ASSERT(NS_IsMainThread());
+  gfx::D3D9VideoCrashGuard crashGuard;
+  if (crashGuard.Crashed()) {
+    NS_WARNING("DXVA2D3D9 crash detected");
+    return nullptr;
+  }
+
+  UINT configCount;
+  DXVA2_ConfigPictureDecode* configs = nullptr;
+  HRESULT hr = mDecoderService->GetDecoderConfigurations(mDecoderGUID, &aDesc, nullptr, &configCount, &configs);
+  NS_ENSURE_TRUE(SUCCEEDED(hr), nullptr);
+
+  RefPtr<IDirect3DSurface9> surface;
+  hr = mDecoderService->CreateSurface(aDesc.SampleWidth, aDesc.SampleHeight, 0, (D3DFORMAT)MAKEFOURCC('N', 'V', '1', '2'),
+                                      D3DPOOL_DEFAULT, 0, DXVA2_VideoDecoderRenderTarget,
+                                      surface.StartAssignment(), NULL);
+  if (!SUCCEEDED(hr)) {
+    CoTaskMemFree(configs);
+    return nullptr;
+  }
+
+  for (UINT i = 0; i < configCount; i++) {
+    RefPtr<IDirectXVideoDecoder> decoder;
+    IDirect3DSurface9* surfaces = surface;
+    hr = mDecoderService->CreateVideoDecoder(mDecoderGUID, &aDesc, &configs[i], &surfaces, 1, decoder.StartAssignment());
+    CoTaskMemFree(configs);
+    return decoder.forget();
+  }
+
+  CoTaskMemFree(configs);
+  return nullptr;
+}
+
 class D3D11DXVA2Manager : public DXVA2Manager
 {
 public:
   D3D11DXVA2Manager();
   virtual ~D3D11DXVA2Manager();
 
   HRESULT Init(layers::KnowsCompositor* aKnowsCompositor,
                nsACString& aFailureReason);
@@ -538,93 +578,60 @@ public:
                       Image** aOutImage) override;
 
   HRESULT ConfigureForSize(uint32_t aWidth, uint32_t aHeight) override;
 
   bool IsD3D11() override { return true; }
 
   bool SupportsConfig(IMFMediaType* aType, float aFramerate) override;
 
+  bool CreateDXVA2Decoder(const VideoInfo& aVideoInfo,
+                          nsACString& aFailureReason) override;
+
 private:
   HRESULT CreateFormatConverter();
 
   HRESULT CreateOutputSample(RefPtr<IMFSample>& aSample,
                              ID3D11Texture2D* aTexture);
 
+  bool CanCreateDecoder(const D3D11_VIDEO_DECODER_DESC& aDesc,
+                        const float aFramerate) const;
+
+  already_AddRefed<ID3D11VideoDecoder>
+  CreateDecoder(const D3D11_VIDEO_DECODER_DESC& aDesc) const;
+
   RefPtr<ID3D11Device> mDevice;
   RefPtr<ID3D11DeviceContext> mContext;
   RefPtr<IMFDXGIDeviceManager> mDXGIDeviceManager;
   RefPtr<MFTDecoder> mTransform;
   RefPtr<D3D11RecycleAllocator> mTextureClientAllocator;
   RefPtr<ID3D11Texture2D> mSyncSurface;
+  RefPtr<ID3D11VideoDecoder> mDecoder;
   GUID mDecoderGUID;
   uint32_t mWidth;
   uint32_t mHeight;
   UINT mDeviceManagerToken;
-  bool mIsAMDPreUVD4;
 };
 
 bool
 D3D11DXVA2Manager::SupportsConfig(IMFMediaType* aType, float aFramerate)
 {
   MOZ_ASSERT(NS_IsMainThread());
-  gfx::D3D11VideoCrashGuard crashGuard;
-  if (crashGuard.Crashed()) {
-    NS_WARNING("DXVA2D3D9 crash detected");
-    return false;
-  }
-
-  RefPtr<ID3D11VideoDevice> videoDevice;
-  HRESULT hr = mDevice->QueryInterface(static_cast<ID3D11VideoDevice**>(getter_AddRefs(videoDevice)));
-  NS_ENSURE_TRUE(SUCCEEDED(hr), false);
-
   D3D11_VIDEO_DECODER_DESC desc;
   desc.Guid = mDecoderGUID;
-
-  UINT32 width = 0;
-  UINT32 height = 0;
-  hr = MFGetAttributeSize(aType, MF_MT_FRAME_SIZE, &width, &height);
+  desc.OutputFormat = DXGI_FORMAT_NV12;
+  HRESULT hr = MFGetAttributeSize(aType, MF_MT_FRAME_SIZE, &desc.SampleWidth, &desc.SampleHeight);
   NS_ENSURE_TRUE(SUCCEEDED(hr), hr);
-  desc.SampleWidth = width;
-  desc.SampleHeight = height;
-
-  desc.OutputFormat = DXGI_FORMAT_NV12;
-
-  // AMD cards with UVD3 or earlier perform poorly trying to decode 1080p60 in hardware,
-  // so use software instead. Pick 45 as an arbitrary upper bound for the framerate we can
-  // handle.
-  if (mIsAMDPreUVD4 &&
-    (desc.SampleWidth >= 1920 || desc.SampleHeight >= 1088) &&
-    aFramerate > 45) {
-    return false;
-  }
-
-  UINT configCount = 0;
-  hr = videoDevice->GetVideoDecoderConfigCount(&desc, &configCount);
-  NS_ENSURE_TRUE(SUCCEEDED(hr), false);
-
-  for (UINT i = 0; i < configCount; i++) {
-    D3D11_VIDEO_DECODER_CONFIG config;
-    hr = videoDevice->GetVideoDecoderConfig(&desc, i, &config);
-    if (SUCCEEDED(hr)) {
-      RefPtr<ID3D11VideoDecoder> decoder;
-      hr = videoDevice->CreateVideoDecoder(&desc, &config, decoder.StartAssignment());
-      if (SUCCEEDED(hr) && decoder) {
-        return true;
-      }
-    }
-  }
-  return false;
+  return CanCreateDecoder(desc, aFramerate);
 }
 
 D3D11DXVA2Manager::D3D11DXVA2Manager()
   : mWidth(0)
   , mHeight(0)
   , mDeviceManagerToken(0)
-  , mIsAMDPreUVD4(false)
 {
 }
 
 D3D11DXVA2Manager::~D3D11DXVA2Manager()
 {
 }
 
 IUnknown*
@@ -908,16 +915,83 @@ D3D11DXVA2Manager::ConfigureForSize(uint
 
   gfx::IntSize size(mWidth, mHeight);
   hr = mTransform->SetMediaTypes(inputType, outputType, ConfigureOutput, &size);
   NS_ENSURE_TRUE(SUCCEEDED(hr), hr);
 
   return S_OK;
 }
 
+bool
+D3D11DXVA2Manager::CreateDXVA2Decoder(const VideoInfo& aVideoInfo,
+                                      nsACString& aFailureReason)
+{
+  MOZ_ASSERT(NS_IsMainThread());
+  D3D11_VIDEO_DECODER_DESC desc;
+  desc.Guid = mDecoderGUID;
+  desc.OutputFormat = DXGI_FORMAT_NV12;
+  desc.SampleWidth = aVideoInfo.mImage.width;
+  desc.SampleHeight = aVideoInfo.mImage.height;
+
+  // Assume the current duration is representative for the entire video.
+  float framerate = 1000000.0 / aVideoInfo.mDuration;
+  if (IsUnsupportedResolution(desc.SampleWidth, desc.SampleHeight , framerate)) {
+    return false;
+  }
+
+  mDecoder = CreateDecoder(desc);
+  if (!mDecoder) {
+    aFailureReason = nsPrintfCString("Fail to create video decoder in D3D11DXVA2Manager.");
+    return false;
+  }
+  return true;
+}
+
+bool
+D3D11DXVA2Manager::CanCreateDecoder(const D3D11_VIDEO_DECODER_DESC& aDesc,
+                                    const float aFramerate) const
+{
+  MOZ_ASSERT(NS_IsMainThread());
+  if (IsUnsupportedResolution(aDesc.SampleWidth, aDesc.SampleHeight, aFramerate)) {
+    return false;
+  }
+  RefPtr<ID3D11VideoDecoder> decoder = CreateDecoder(aDesc);
+  return decoder.get() != nullptr;
+}
+
+already_AddRefed<ID3D11VideoDecoder>
+D3D11DXVA2Manager::CreateDecoder(const D3D11_VIDEO_DECODER_DESC& aDesc) const
+{
+  MOZ_ASSERT(NS_IsMainThread());
+  gfx::D3D11VideoCrashGuard crashGuard;
+  if (crashGuard.Crashed()) {
+    NS_WARNING("DXVA2D3D9 crash detected");
+    return nullptr;
+  }
+
+  RefPtr<ID3D11VideoDevice> videoDevice;
+  HRESULT hr = mDevice->QueryInterface(static_cast<ID3D11VideoDevice**>(getter_AddRefs(videoDevice)));
+  NS_ENSURE_TRUE(SUCCEEDED(hr), nullptr);
+
+  UINT configCount = 0;
+  hr = videoDevice->GetVideoDecoderConfigCount(&aDesc, &configCount);
+  NS_ENSURE_TRUE(SUCCEEDED(hr), nullptr);
+
+  for (UINT i = 0; i < configCount; i++) {
+    D3D11_VIDEO_DECODER_CONFIG config;
+    hr = videoDevice->GetVideoDecoderConfig(&aDesc, i, &config);
+    if (SUCCEEDED(hr)) {
+      RefPtr<ID3D11VideoDecoder> decoder;
+      hr = videoDevice->CreateVideoDecoder(&aDesc, &config, decoder.StartAssignment());
+      return decoder.forget();
+    }
+  }
+  return nullptr;
+}
+
 /* static */
 DXVA2Manager*
 DXVA2Manager::CreateD3D11DXVA(layers::KnowsCompositor* aKnowsCompositor,
                               nsACString& aFailureReason)
 {
   // DXVA processing takes up a lot of GPU resources, so limit the number of
   // videos we use DXVA with at any one time.
   uint32_t dxvaLimit = gfxPrefs::PDMWMFMaxDXVAVideos();
@@ -931,20 +1005,34 @@ DXVA2Manager::CreateD3D11DXVA(layers::Kn
   HRESULT hr = manager->Init(aKnowsCompositor, aFailureReason);
   NS_ENSURE_TRUE(SUCCEEDED(hr), nullptr);
 
   return manager.forget();
 }
 
 DXVA2Manager::DXVA2Manager()
   : mLock("DXVA2Manager")
+  , mIsAMDPreUVD4(false)
 {
   MOZ_ASSERT(NS_IsMainThread());
   ++sDXVAVideosCount;
 }
 
 DXVA2Manager::~DXVA2Manager()
 {
   MOZ_ASSERT(NS_IsMainThread());
   --sDXVAVideosCount;
 }
 
+bool
+DXVA2Manager::IsUnsupportedResolution(const uint32_t& aWidth,
+                                      const uint32_t& aHeight,
+                                      const float& aFramerate) const
+{
+  // AMD cards with UVD3 or earlier perform poorly trying to decode 1080p60 in
+  // hardware, so use software instead. Pick 45 as an arbitrary upper bound for
+  // the framerate we can handle.
+  return (mIsAMDPreUVD4 &&
+          (aWidth >= 1920 || aHeight >= 1088) &&
+          aFramerate > 45);
+}
+
 } // namespace mozilla
--- a/dom/media/platforms/wmf/DXVA2Manager.h
+++ b/dom/media/platforms/wmf/DXVA2Manager.h
@@ -2,16 +2,17 @@
 /* vim:set ts=2 sw=2 sts=2 et cindent: */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 #if !defined(DXVA2Manager_h_)
 #define DXVA2Manager_h_
 
 #include "WMF.h"
+#include "MediaInfo.h"
 #include "nsAutoPtr.h"
 #include "mozilla/Mutex.h"
 #include "nsRect.h"
 
 namespace mozilla {
 
 namespace layers {
 class Image;
@@ -19,18 +20,20 @@ class ImageContainer;
 class KnowsCompositor;
 }
 
 class DXVA2Manager {
 public:
 
   // Creates and initializes a DXVA2Manager. We can use DXVA2 via either
   // D3D9Ex or D3D11.
-  static DXVA2Manager* CreateD3D9DXVA(layers::KnowsCompositor* aKnowsCompositor, nsACString& aFailureReason);
-  static DXVA2Manager* CreateD3D11DXVA(layers::KnowsCompositor* aKnowsCompositor, nsACString& aFailureReason);
+  static DXVA2Manager* CreateD3D9DXVA(layers::KnowsCompositor* aKnowsCompositor,
+                                      nsACString& aFailureReason);
+  static DXVA2Manager* CreateD3D11DXVA(layers::KnowsCompositor* aKnowsCompositor,
+                                       nsACString& aFailureReason);
 
   // Returns a pointer to the D3D device manager responsible for managing the
   // device we're using for hardware accelerated video decoding. If we're using
   // D3D9Ex, this is an IDirect3DDeviceManager9. For D3D11 this is an
   // IMFDXGIDeviceManager. It is safe to call this on any thread.
   virtual IUnknown* GetDXVADeviceManager() = 0;
 
   // Creates an Image for the video frame stored in aVideoSample.
@@ -41,16 +44,28 @@ public:
   virtual HRESULT ConfigureForSize(uint32_t aWidth, uint32_t aHeight) { return S_OK; }
 
   virtual bool IsD3D11() { return false; }
 
   virtual ~DXVA2Manager();
 
   virtual bool SupportsConfig(IMFMediaType* aType, float aFramerate) = 0;
 
+  // When we want to decode with DXVA2 directly instead of using it by MFT, we
+  // need to take responsibility for creating a decoder and handle the related
+  // decoding operations by ourself.
+  virtual bool CreateDXVA2Decoder(const VideoInfo& aVideoInfo,
+                                  nsACString& aFailureReason) = 0;
+
 protected:
   Mutex mLock;
   DXVA2Manager();
+
+  bool IsUnsupportedResolution(const uint32_t& aWidth,
+                               const uint32_t& aHeight,
+                               const float& aFramerate) const;
+
+  bool mIsAMDPreUVD4;
 };
 
 } // namespace mozilla
 
 #endif // DXVA2Manager_h_
new file mode 100644
--- /dev/null
+++ b/dom/u2f/ScopedCredential.cpp
@@ -0,0 +1,66 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "mozilla/dom/ScopedCredential.h"
+#include "mozilla/dom/WebAuthenticationBinding.h"
+
+namespace mozilla {
+namespace dom {
+
+// Only needed for refcounted objects.
+NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(ScopedCredential, mParent)
+NS_IMPL_CYCLE_COLLECTING_ADDREF(ScopedCredential)
+NS_IMPL_CYCLE_COLLECTING_RELEASE(ScopedCredential)
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(ScopedCredential)
+  NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
+  NS_INTERFACE_MAP_ENTRY(nsISupports)
+NS_INTERFACE_MAP_END
+
+ScopedCredential::ScopedCredential(WebAuthentication* aParent)
+  : mParent(aParent)
+  , mType(ScopedCredentialType::ScopedCred)
+{}
+
+ScopedCredential::~ScopedCredential()
+{}
+
+JSObject*
+ScopedCredential::WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto)
+{
+  return ScopedCredentialBinding::Wrap(aCx, this, aGivenProto);
+}
+
+ScopedCredentialType
+ScopedCredential::Type() const
+{
+  return mType;
+}
+
+void
+ScopedCredential::GetId(JSContext* aCx,
+                        JS::MutableHandle<JSObject*> aRetVal) const
+{
+  aRetVal.set(mIdBuffer.ToUint8Array(aCx));
+}
+
+nsresult
+ScopedCredential::SetType(ScopedCredentialType aType)
+{
+  mType = aType;
+  return NS_OK;
+}
+
+nsresult
+ScopedCredential::SetId(CryptoBuffer& aBuffer)
+{
+  if (!mIdBuffer.Assign(aBuffer)) {
+    return NS_ERROR_OUT_OF_MEMORY;
+  }
+  return NS_OK;
+}
+
+} // namespace dom
+} // namespace mozilla
new file mode 100644
--- /dev/null
+++ b/dom/u2f/ScopedCredential.h
@@ -0,0 +1,66 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_dom_ScopedCredential_h
+#define mozilla_dom_ScopedCredential_h
+
+#include "js/TypeDecls.h"
+#include "mozilla/Attributes.h"
+#include "mozilla/ErrorResult.h"
+#include "mozilla/dom/BindingDeclarations.h"
+#include "mozilla/dom/CryptoBuffer.h"
+#include "mozilla/dom/WebAuthenticationBinding.h"
+#include "nsCycleCollectionParticipant.h"
+#include "nsWrapperCache.h"
+
+namespace mozilla {
+namespace dom {
+
+class ScopedCredential final : public nsISupports
+                             , public nsWrapperCache
+{
+public:
+  NS_DECL_CYCLE_COLLECTING_ISUPPORTS
+  NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(ScopedCredential)
+
+public:
+  explicit ScopedCredential(WebAuthentication* aParent);
+
+protected:
+  ~ScopedCredential();
+
+public:
+  WebAuthentication*
+  GetParentObject() const
+  {
+    return mParent;
+  }
+
+  virtual JSObject*
+  WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) override;
+
+  ScopedCredentialType
+  Type() const;
+
+  void
+  GetId(JSContext* aCx, JS::MutableHandle<JSObject*> aRetVal) const;
+
+  nsresult
+  SetType(ScopedCredentialType aType);
+
+  nsresult
+  SetId(CryptoBuffer& aBuffer);
+
+private:
+  RefPtr<WebAuthentication> mParent;
+  CryptoBuffer mIdBuffer;
+  ScopedCredentialType mType;
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif // mozilla_dom_ScopedCredential_h
new file mode 100644
--- /dev/null
+++ b/dom/u2f/ScopedCredentialInfo.cpp
@@ -0,0 +1,64 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "mozilla/dom/ScopedCredentialInfo.h"
+#include "mozilla/dom/WebAuthenticationBinding.h"
+
+namespace mozilla {
+namespace dom {
+
+// Only needed for refcounted objects.
+NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(ScopedCredentialInfo, mParent)
+NS_IMPL_CYCLE_COLLECTING_ADDREF(ScopedCredentialInfo)
+NS_IMPL_CYCLE_COLLECTING_RELEASE(ScopedCredentialInfo)
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(ScopedCredentialInfo)
+  NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
+  NS_INTERFACE_MAP_ENTRY(nsISupports)
+NS_INTERFACE_MAP_END
+
+ScopedCredentialInfo::ScopedCredentialInfo(WebAuthentication* aParent)
+  : mParent(aParent)
+{}
+
+ScopedCredentialInfo::~ScopedCredentialInfo()
+{}
+
+JSObject*
+ScopedCredentialInfo::WrapObject(JSContext* aCx,
+                                 JS::Handle<JSObject*> aGivenProto)
+{
+  return ScopedCredentialInfoBinding::Wrap(aCx, this, aGivenProto);
+}
+
+already_AddRefed<ScopedCredential>
+ScopedCredentialInfo::Credential() const
+{
+  RefPtr<ScopedCredential> temp(mCredential);
+  return temp.forget();
+}
+
+already_AddRefed<WebAuthnAttestation>
+ScopedCredentialInfo::Attestation() const
+{
+  RefPtr<WebAuthnAttestation> temp(mAttestation);
+  return temp.forget();
+}
+
+void
+ScopedCredentialInfo::SetCredential(RefPtr<ScopedCredential> aCredential)
+{
+  mCredential = aCredential;
+}
+
+void
+ScopedCredentialInfo::SetAttestation(RefPtr<WebAuthnAttestation> aAttestation)
+{
+  mAttestation = aAttestation;
+}
+
+
+} // namespace dom
+} // namespace mozilla
new file mode 100644
--- /dev/null
+++ b/dom/u2f/ScopedCredentialInfo.h
@@ -0,0 +1,74 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_dom_ScopedCredentialInfo_h
+#define mozilla_dom_ScopedCredentialInfo_h
+
+#include "js/TypeDecls.h"
+#include "mozilla/Attributes.h"
+#include "mozilla/dom/BindingDeclarations.h"
+#include "mozilla/ErrorResult.h"
+#include "nsCycleCollectionParticipant.h"
+#include "nsWrapperCache.h"
+
+namespace mozilla {
+namespace dom {
+
+class CryptoKey;
+class ScopedCredential;
+class WebAuthnAttestation;
+
+} // namespace dom
+} // namespace mozilla
+
+namespace mozilla {
+namespace dom {
+
+class ScopedCredentialInfo final : public nsISupports
+                                 , public nsWrapperCache
+{
+public:
+  NS_DECL_CYCLE_COLLECTING_ISUPPORTS
+  NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(ScopedCredentialInfo)
+
+public:
+  explicit ScopedCredentialInfo(WebAuthentication* aParent);
+
+protected:
+  ~ScopedCredentialInfo();
+
+public:
+  WebAuthentication*
+  GetParentObject() const
+  {
+    return mParent;
+  }
+
+  virtual JSObject*
+  WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) override;
+
+  already_AddRefed<ScopedCredential>
+  Credential() const;
+
+  already_AddRefed<WebAuthnAttestation>
+  Attestation() const;
+
+  void
+  SetCredential(RefPtr<ScopedCredential>);
+
+  void
+  SetAttestation(RefPtr<WebAuthnAttestation>);
+
+private:
+  RefPtr<WebAuthentication> mParent;
+  RefPtr<WebAuthnAttestation> mAttestation;
+  RefPtr<ScopedCredential> mCredential;
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif // mozilla_dom_ScopedCredentialInfo_h
--- a/dom/u2f/U2F.cpp
+++ b/dom/u2f/U2F.cpp
@@ -44,17 +44,17 @@ NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(U2
 
 static mozilla::LazyLogModule gWebauthLog("webauth_u2f");
 
 static nsresult
 AssembleClientData(const nsAString& aOrigin, const nsAString& aTyp,
                    const nsAString& aChallenge, CryptoBuffer& aClientData)
 {
   MOZ_ASSERT(NS_IsMainThread());
-  ClientData clientDataObject;
+  U2FClientData clientDataObject;
   clientDataObject.mTyp.Construct(aTyp); // "Typ" from the U2F specification
   clientDataObject.mChallenge.Construct(aChallenge);
   clientDataObject.mOrigin.Construct(aOrigin);
 
   nsAutoString json;
   if (NS_WARN_IF(!clientDataObject.ToJSON(json))) {
     return NS_ERROR_FAILURE;
   }
--- a/dom/u2f/U2F.h
+++ b/dom/u2f/U2F.h
@@ -18,16 +18,17 @@
 #include "mozilla/SharedThreadPool.h"
 #include "nsCycleCollectionParticipant.h"
 #include "nsIU2FToken.h"
 #include "nsNSSShutDown.h"
 #include "nsPIDOMWindow.h"
 #include "nsProxyRelease.h"
 #include "nsWrapperCache.h"
 
+#include "U2FAuthenticator.h"
 #include "USBToken.h"
 
 namespace mozilla {
 namespace dom {
 
 class U2FRegisterCallback;
 class U2FSignCallback;
 
@@ -48,29 +49,16 @@ struct LocalRegisteredKey
 {
   nsString mKeyHandle;
   nsString mVersion;
   Nullable<nsString> mAppId;
   // TODO: Support transport preferences
   // Nullable<nsTArray<Transport>> mTransports;
 };
 
-// These enumerations are defined in the FIDO U2F Javascript API under the
-// interface "ErrorCode" as constant integers, and thus in the U2F.webidl file.
-// Any changes to these must occur in both locations.
-enum class ErrorCode {
-  OK = 0,
-  OTHER_ERROR = 1,
-  BAD_REQUEST = 2,
-  CONFIGURATION_UNSUPPORTED = 3,
-  DEVICE_INELIGIBLE = 4,
-  TIMEOUT = 5
-};
-
-typedef nsCOMPtr<nsIU2FToken> Authenticator;
 typedef MozPromise<nsString, ErrorCode, false> U2FPromise;
 typedef MozPromise<Authenticator, ErrorCode, false> U2FPrepPromise;
 
 // U2FPrepTasks return lists of Authenticators that are OK to
 // proceed; they're useful for culling incompatible Authenticators.
 // Currently, only IsRegistered is supported.
 class U2FPrepTask : public Runnable
 {
new file mode 100644
--- /dev/null
+++ b/dom/u2f/U2FAuthenticator.h
@@ -0,0 +1,33 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_dom_U2FAuthenticator_h
+#define mozilla_dom_U2FAuthenticator_h
+
+#include "nsIU2FToken.h"
+#include "USBToken.h"
+
+namespace mozilla {
+namespace dom {
+
+ // These enumerations are defined in the FIDO U2F Javascript API under the
+// interface "ErrorCode" as constant integers, and thus in the U2F.webidl file.
+// Any changes to these must occur in both locations.
+enum class ErrorCode {
+  OK = 0,
+  OTHER_ERROR = 1,
+  BAD_REQUEST = 2,
+  CONFIGURATION_UNSUPPORTED = 3,
+  DEVICE_INELIGIBLE = 4,
+  TIMEOUT = 5
+};
+
+typedef nsCOMPtr<nsIU2FToken> Authenticator;
+
+} // namespace dom
+} // namespace mozilla
+
+#endif // mozilla_dom_U2FAuthenticator_h
new file mode 100644
--- /dev/null
+++ b/dom/u2f/WebAuthentication.cpp
@@ -0,0 +1,1054 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "mozilla/dom/WebAuthentication.h"
+#include "mozilla/dom/WebAuthnAssertion.h"
+#include "mozilla/dom/WebAuthnAttestation.h"
+
+#include "mozilla/dom/Promise.h"
+#include "nsICryptoHash.h"
+#include "pkix/Input.h"
+#include "pkixutil.h"
+
+namespace mozilla {
+namespace dom {
+
+extern mozilla::LazyLogModule gWebauthLog; // defined in U2F.cpp
+
+// Only needed for refcounted objects.
+NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(WebAuthentication, mParent)
+NS_IMPL_CYCLE_COLLECTING_ADDREF(WebAuthentication)
+NS_IMPL_CYCLE_COLLECTING_RELEASE(WebAuthentication)
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(WebAuthentication)
+  NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
+  NS_INTERFACE_MAP_ENTRY(nsISupports)
+NS_INTERFACE_MAP_END
+
+template<class OOS>
+static nsresult
+GetAlgorithmName(JSContext* aCx, const OOS& aAlgorithm,
+                 /* out */ nsString& aName)
+{
+  MOZ_ASSERT(aAlgorithm.IsString()); // TODO: remove assertion when we coerce.
+
+  if (aAlgorithm.IsString()) {
+    // If string, then treat as algorithm name
+    aName.Assign(aAlgorithm.GetAsString());
+  } else {
+    // TODO: Coerce to string and extract name. See WebCryptoTask.cpp
+  }
+
+  if (!NormalizeToken(aName, aName)) {
+    return NS_ERROR_DOM_SYNTAX_ERR;
+  }
+
+  return NS_OK;
+}
+
+static nsresult
+HashCString(nsICryptoHash* aHashService, const nsACString& aIn,
+            /* out */ CryptoBuffer& aOut)
+{
+  MOZ_ASSERT(aHashService);
+
+  nsresult rv = aHashService->Init(nsICryptoHash::SHA256);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
+  rv = aHashService->Update(
+         reinterpret_cast<const uint8_t*>(aIn.BeginReading()),aIn.Length());
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
+  nsAutoCString fullHash;
+  // Passing false below means we will get a binary result rather than a
+  // base64-encoded string.
+  rv = aHashService->Finish(false, fullHash);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
+  aOut.Assign(fullHash);
+  return rv;
+}
+
+static nsresult
+AssembleClientData(const nsAString& aOrigin, const CryptoBuffer& aChallenge,
+                   /* out */ nsACString& aJsonOut)
+{
+  MOZ_ASSERT(NS_IsMainThread());
+
+  nsString challengeBase64;
+  nsresult rv = aChallenge.ToJwkBase64(challengeBase64);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return NS_ERROR_FAILURE;
+  }
+
+  WebAuthnClientData clientDataObject;
+  clientDataObject.mOrigin.Assign(aOrigin);
+  clientDataObject.mHashAlg.SetAsString().Assign(NS_LITERAL_STRING("S256"));
+  clientDataObject.mChallenge.Assign(challengeBase64);
+
+  nsAutoString temp;
+  if (NS_WARN_IF(!clientDataObject.ToJSON(temp))) {
+    return NS_ERROR_FAILURE;
+  }
+
+  aJsonOut.Assign(NS_ConvertUTF16toUTF8(temp));
+  return NS_OK;
+}
+
+static nsresult
+ScopedCredentialGetData(const ScopedCredentialDescriptor& aSCD,
+                        /* out */ uint8_t** aBuf, /* out */ uint32_t* aBufLen)
+{
+  MOZ_ASSERT(aBuf);
+  MOZ_ASSERT(aBufLen);
+
+  if (aSCD.mId.IsArrayBufferView()) {
+    const ArrayBufferView& view = aSCD.mId.GetAsArrayBufferView();
+    view.ComputeLengthAndData();
+    *aBuf = view.Data();
+    *aBufLen = view.Length();
+  } else if (aSCD.mId.IsArrayBuffer()) {
+    const ArrayBuffer& buffer = aSCD.mId.GetAsArrayBuffer();
+    buffer.ComputeLengthAndData();
+    *aBuf = buffer.Data();
+    *aBufLen = buffer.Length();
+  } else {
+    MOZ_ASSERT(false);
+    return NS_ERROR_FAILURE;
+  }
+
+  return NS_OK;
+}
+
+static nsresult
+ReadToCryptoBuffer(pkix::Reader& aSrc, /* out */ CryptoBuffer& aDest,
+                   uint32_t aLen)
+{
+  if (aSrc.EnsureLength(aLen) != pkix::Success) {
+    return NS_ERROR_DOM_UNKNOWN_ERR;
+  }
+
+  aDest.ClearAndRetainStorage();
+
+  for (uint32_t offset = 0; offset < aLen; ++offset) {
+    uint8_t b;
+    if (aSrc.Read(b) != pkix::Success) {
+      return NS_ERROR_DOM_UNKNOWN_ERR;
+    }
+    if (!aDest.AppendElement(b, mozilla::fallible)) {
+      return NS_ERROR_OUT_OF_MEMORY;
+    }
+  }
+
+  return NS_OK;
+}
+
+static nsresult
+U2FAssembleAuthenticatorData(/* out */ CryptoBuffer& aAuthenticatorData,
+                             const CryptoBuffer& aRpIdHash,
+                             const CryptoBuffer& aSignatureData)
+{
+  // The AuthenticatorData for U2F devices is the concatenation of the
+  // RP ID with the output of the U2F Sign operation.
+  if (aRpIdHash.Length() != 32) {
+    return NS_ERROR_INVALID_ARG;
+  }
+
+  if (!aAuthenticatorData.AppendElements(aRpIdHash, mozilla::fallible)) {
+    return NS_ERROR_OUT_OF_MEMORY;
+  }
+
+  if (!aAuthenticatorData.AppendElements(aSignatureData, mozilla::fallible)) {
+    return NS_ERROR_OUT_OF_MEMORY;
+  }
+
+  return NS_OK;
+}
+
+static nsresult
+U2FDecomposeRegistrationResponse(const CryptoBuffer& aResponse,
+                                 /* out */ CryptoBuffer& aPubKeyBuf,
+                                 /* out */ CryptoBuffer& aKeyHandleBuf,
+                                 /* out */ CryptoBuffer& aAttestationCertBuf,
+                                 /* out */ CryptoBuffer& aSignatureBuf)
+{
+  // U2F v1.1 Format via
+  // http://fidoalliance.org/specs/fido-u2f-v1.1-id-20160915/fido-u2f-raw-message-formats-v1.1-id-20160915.html
+  //
+  // Bytes  Value
+  // 1      0x05
+  // 65     public key
+  // 1      key handle length
+  // *      key handle
+  // ASN.1  attestation certificate
+  // *      attestation signature
+
+  pkix::Input u2fResponse;
+  u2fResponse.Init(aResponse.Elements(), aResponse.Length());
+
+  pkix::Reader input(u2fResponse);
+
+  uint8_t b;
+  if (input.Read(b) != pkix::Success) {
+    return NS_ERROR_DOM_UNKNOWN_ERR;
+  }
+  if (b != 0x05) {
+    return NS_ERROR_DOM_UNKNOWN_ERR;
+  }
+
+  nsresult rv = ReadToCryptoBuffer(input, aPubKeyBuf, 65);
+  if (NS_FAILED(rv)) {
+    return rv;
+  }
+
+  uint8_t handleLen;
+  if (input.Read(handleLen) != pkix::Success) {
+    return NS_ERROR_DOM_UNKNOWN_ERR;
+  }
+
+  rv = ReadToCryptoBuffer(input, aKeyHandleBuf, handleLen);
+  if (NS_FAILED(rv)) {
+    return rv;
+  }
+
+  // We have to parse the ASN.1 SEQUENCE on the outside to determine the cert's
+  // length.
+  pkix::Input cert;
+  if (pkix::der::ExpectTagAndGetValue(input, pkix::der::SEQUENCE, cert)
+        != pkix::Success) {
+    return NS_ERROR_DOM_UNKNOWN_ERR;
+  }
+
+  pkix::Reader certInput(cert);
+  rv = ReadToCryptoBuffer(certInput, aAttestationCertBuf, cert.GetLength());
+  if (NS_FAILED(rv)) {
+    return rv;
+  }
+
+  // The remainder of u2fResponse is the signature
+  pkix::Input u2fSig;
+  input.SkipToEnd(u2fSig);
+  pkix::Reader sigInput(u2fSig);
+  rv = ReadToCryptoBuffer(sigInput, aSignatureBuf, u2fSig.GetLength());
+  if (NS_FAILED(rv)) {
+    return rv;
+  }
+
+  return NS_OK;
+}
+
+WebAuthentication::WebAuthentication(nsPIDOMWindowInner* aParent)
+  : mInitialized(false)
+{
+  mParent = do_QueryInterface(aParent);
+  MOZ_ASSERT(mParent);
+}
+
+WebAuthentication::~WebAuthentication()
+{}
+
+nsresult
+WebAuthentication::InitLazily()
+{
+  if (mInitialized) {
+    return NS_OK;
+  }
+
+  MOZ_ASSERT(mParent);
+  if (!mParent) {
+    return NS_ERROR_FAILURE;
+  }
+
+  nsCOMPtr<nsIDocument> doc = mParent->GetDoc();
+  MOZ_ASSERT(doc);
+
+  nsIPrincipal* principal = doc->NodePrincipal();
+  nsresult rv = nsContentUtils::GetUTFOrigin(principal, mOrigin);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return NS_ERROR_FAILURE;
+  }
+
+  if (NS_WARN_IF(mOrigin.IsEmpty())) {
+    return NS_ERROR_FAILURE;
+  }
+
+  // This only functions in e10s mode
+  // TODO: Remove in Bug 1323339
+  if (XRE_IsParentProcess()) {
+    MOZ_LOG(gWebauthLog, LogLevel::Debug,
+            ("Is non-e10s Process, WebAuthn not available"));
+    return NS_ERROR_FAILURE;
+  }
+
+  if (Preferences::GetBool(PREF_U2F_SOFTTOKEN_ENABLED)) {
+    if (!mAuthenticators.AppendElement(new NSSU2FTokenRemote(),
+                                       mozilla::fallible)) {
+      return NS_ERROR_OUT_OF_MEMORY;
+    }
+  }
+
+  mInitialized = true;
+  return NS_OK;
+}
+
+JSObject*
+WebAuthentication::WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto)
+{
+  return WebAuthenticationBinding::Wrap(aCx, this, aGivenProto);
+}
+
+// NOTE: This method represents a theoretical way to use a U2F-compliant token
+// to produce the result of the WebAuthn MakeCredential method. The exact
+// mapping of U2F data fields to WebAuthn data fields is still a matter of
+// ongoing discussion, and this should not be taken as anything but a point-in-
+// time possibility.
+void
+WebAuthentication::U2FAuthMakeCredential(
+             const RefPtr<CredentialRequest>& aRequest,
+             const Authenticator& aToken, CryptoBuffer& aRpIdHash,
+             const nsACString& aClientData, CryptoBuffer& aClientDataHash,
+             const Account& aAccount,
+             const nsTArray<ScopedCredentialParameters>& aNormalizedParams,
+             const Optional<Sequence<ScopedCredentialDescriptor>>& aExcludeList,
+             const WebAuthnExtensions& aExtensions)
+{
+  MOZ_LOG(gWebauthLog, LogLevel::Debug, ("U2FAuthMakeCredential"));
+  aRequest->AddActiveToken(__func__);
+
+  // 5.1.1 When this operation is invoked, the authenticator must perform the
+  // following procedure:
+
+  // 5.1.1.a Check if all the supplied parameters are syntactically well-
+  // formed and of the correct length. If not, return an error code equivalent
+  // to UnknownError and terminate the operation.
+
+  if ((aRpIdHash.Length() != SHA256_LENGTH) ||
+      (aClientDataHash.Length() != SHA256_LENGTH)) {
+    aRequest->SetFailure(NS_ERROR_DOM_UNKNOWN_ERR);
+    return;
+  }
+
+  // 5.1.1.b Check if at least one of the specified combinations of
+  // ScopedCredentialType and cryptographic parameters is supported. If not,
+  // return an error code equivalent to NotSupportedError and terminate the
+  // operation.
+
+  bool isValidCombination = false;
+
+  for (size_t a = 0; a < aNormalizedParams.Length(); ++a) {
+    if (aNormalizedParams[a].mType == ScopedCredentialType::ScopedCred &&
+        aNormalizedParams[a].mAlgorithm.IsString() &&
+        aNormalizedParams[a].mAlgorithm.GetAsString().EqualsLiteral(
+          WEBCRYPTO_NAMED_CURVE_P256)) {
+      isValidCombination = true;
+      break;
+    }
+  }
+  if (!isValidCombination) {
+    aRequest->SetFailure(NS_ERROR_DOM_NOT_SUPPORTED_ERR);
+    return;
+  }
+
+  // 5.1.1.c Check if a credential matching any of the supplied
+  // ScopedCredential identifiers is present on this authenticator. If so,
+  // return an error code equivalent to NotAllowedError and terminate the
+  // operation.
+
+  if (aExcludeList.WasPassed()) {
+    const Sequence<ScopedCredentialDescriptor>& list = aExcludeList.Value();
+
+    for (const ScopedCredentialDescriptor& scd : list) {
+      bool isRegistered = false;
+
+      uint8_t *data;
+      uint32_t len;
+
+      // data is owned by the Descriptor, do don't free it here.
+      if (NS_FAILED(ScopedCredentialGetData(scd, &data, &len))) {
+        aRequest->SetFailure(NS_ERROR_DOM_UNKNOWN_ERR);
+        return;
+      }
+
+      nsresult rv = aToken->IsRegistered(data, len, &isRegistered);
+      if (NS_WARN_IF(NS_FAILED(rv))) {
+        aRequest->SetFailure(rv);
+        return;
+      }
+
+      if (isRegistered) {
+        aRequest->SetFailure(NS_ERROR_DOM_NOT_ALLOWED_ERR);
+        return;
+      }
+    }
+  }
+
+  // 5.1.1.d Prompt the user for consent to create a new credential. The
+  // prompt for obtaining this consent is shown by the authenticator if it has
+  // its own output capability, or by the user agent otherwise. If the user
+  // denies consent, return an error code equivalent to NotAllowedError and
+  // terminate the operation.
+
+  // 5.1.1.d Once user consent has been obtained, generate a new credential
+  // object
+
+  // 5.1.1.e If any error occurred while creating the new credential object,
+  // return an error code equivalent to UnknownError and terminate the
+  // operation.
+
+  // 5.1.1.f Process all the supported extensions requested by the client, and
+  // generate an attestation statement. If no authority key is available to
+  // sign such an attestation statement, then the authenticator performs self
+  // attestation of the credential with its own private key. For more details
+  // on attestation, see §5.3 Credential Attestation Statements.
+
+  // No extensions are supported
+
+  // 4.1.1.11 While issuedRequests is not empty, perform the following actions
+  // depending upon the adjustedTimeout timer and responses from the
+  // authenticators:
+
+  // 4.1.1.11.a If the adjustedTimeout timer expires, then for each entry in
+  // issuedRequests invoke the authenticatorCancel operation on that
+  // authenticator and remove its entry from the list.
+
+  uint8_t* buffer;
+  uint32_t bufferlen;
+
+  nsresult rv = aToken->Register(aRpIdHash.Elements(), aRpIdHash.Length(),
+                                 aClientDataHash.Elements(),
+                                 aClientDataHash.Length(), &buffer, &bufferlen);
+
+  // 4.1.1.11.b If any authenticator returns a status indicating that the user
+  // cancelled the operation, delete that authenticator’s entry from
+  // issuedRequests. For each remaining entry in issuedRequests invoke the
+  // authenticatorCancel operation on that authenticator and remove its entry
+  // from the list.
+
+  // 4.1.1.11.c If any authenticator returns an error status, delete the
+  // corresponding entry from issuedRequests.
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    aRequest->SetFailure(NS_ERROR_DOM_UNKNOWN_ERR);
+    return;
+  }
+
+  MOZ_ASSERT(buffer);
+  CryptoBuffer regData;
+  if (NS_WARN_IF(!regData.Assign(buffer, bufferlen))) {
+    free(buffer);
+    aRequest->SetFailure(NS_ERROR_OUT_OF_MEMORY);
+    return;
+  }
+  free(buffer);
+
+  // Decompose the U2F registration packet
+  CryptoBuffer pubKeyBuf;
+  CryptoBuffer keyHandleBuf;
+  CryptoBuffer attestationCertBuf;
+  CryptoBuffer signatureBuf;
+
+  rv = U2FDecomposeRegistrationResponse(regData, pubKeyBuf, keyHandleBuf,
+                                        attestationCertBuf, signatureBuf);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    aRequest->SetFailure(rv);
+    return;
+  }
+
+  // Sign the aClientDataHash explicitly to get the format needed for
+  // the AuthenticatorData parameter of WebAuthnAttestation. This might
+  // be temporary while the spec settles down how to incorporate U2F.
+  rv = aToken->Sign(aRpIdHash.Elements(), aRpIdHash.Length(),
+                    aClientDataHash.Elements(), aClientDataHash.Length(),
+                    keyHandleBuf.Elements(), keyHandleBuf.Length(), &buffer,
+                    &bufferlen);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    aRequest->SetFailure(rv);
+    return;
+  }
+
+  MOZ_ASSERT(buffer);
+  CryptoBuffer signatureData;
+  if (NS_WARN_IF(!signatureData.Assign(buffer, bufferlen))) {
+    free(buffer);
+    aRequest->SetFailure(NS_ERROR_OUT_OF_MEMORY);
+    return;
+  }
+  free(buffer);
+
+  CryptoBuffer clientDataBuf;
+  if (!clientDataBuf.Assign(aClientData)) {
+    aRequest->SetFailure(NS_ERROR_OUT_OF_MEMORY);
+    return;
+  }
+
+  CryptoBuffer authenticatorDataBuf;
+  rv = U2FAssembleAuthenticatorData(authenticatorDataBuf, aRpIdHash,
+                                    signatureData);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    aRequest->SetFailure(rv);
+    return;
+  }
+
+  // 4.1.1.11.d If any authenticator indicates success:
+
+  // 4.1.1.11.d.1 Remove this authenticator’s entry from issuedRequests.
+
+  // 4.1.1.11.d.2 Create a new ScopedCredentialInfo object named value and
+  // populate its fields with the values returned from the authenticator as well
+  // as the clientDataJSON computed earlier.
+
+  RefPtr<ScopedCredential> credential = new ScopedCredential(this);
+  credential->SetType(ScopedCredentialType::ScopedCred);
+  credential->SetId(keyHandleBuf);
+
+  RefPtr<WebAuthnAttestation> attestation = new WebAuthnAttestation(this);
+  attestation->SetFormat(NS_LITERAL_STRING("u2f"));
+  attestation->SetClientData(clientDataBuf);
+  attestation->SetAuthenticatorData(authenticatorDataBuf);
+  attestation->SetAttestation(regData);
+
+  CredentialPtr info = new ScopedCredentialInfo(this);
+  info->SetCredential(credential);
+  info->SetAttestation(attestation);
+
+  // 4.1.1.11.d.3 For each remaining entry in issuedRequests invoke the
+  // authenticatorCancel operation on that authenticator and remove its entry
+  // from the list.
+
+  // 4.1.1.11.d.4 Resolve promise with value and terminate this algorithm.
+  aRequest->SetSuccess(info);
+}
+
+// NOTE: This method represents a theoretical way to use a U2F-compliant token
+// to produce the result of the WebAuthn GetAssertion method. The exact mapping
+// of U2F data fields to WebAuthn data fields is still a matter of ongoing
+// discussion, and this should not be taken as anything but a point-in- time
+// possibility.
+void
+WebAuthentication::U2FAuthGetAssertion(const RefPtr<AssertionRequest>& aRequest,
+                    const Authenticator& aToken, CryptoBuffer& aRpIdHash,
+                    const nsACString& aClientData, CryptoBuffer& aClientDataHash,
+                    nsTArray<CryptoBuffer>& aAllowList,
+                    const WebAuthnExtensions& aExtensions)
+{
+  MOZ_LOG(gWebauthLog, LogLevel::Debug, ("U2FAuthGetAssertion"));
+
+  // 4.1.2.7.e Add an entry to issuedRequests, corresponding to this request.
+  aRequest->AddActiveToken(__func__);
+
+  // 4.1.2.8 While issuedRequests is not empty, perform the following actions
+  // depending upon the adjustedTimeout timer and responses from the
+  // authenticators:
+
+  // 4.1.2.8.a If the timer for adjustedTimeout expires, then for each entry
+  // in issuedRequests invoke the authenticatorCancel operation on that
+  // authenticator and remove its entry from the list.
+
+  for (CryptoBuffer& allowedCredential : aAllowList) {
+    bool isRegistered = false;
+    nsresult rv = aToken->IsRegistered(allowedCredential.Elements(),
+                                       allowedCredential.Length(),
+                                       &isRegistered);
+
+    // 4.1.2.8.b If any authenticator returns a status indicating that the user
+    // cancelled the operation, delete that authenticator’s entry from
+    // issuedRequests. For each remaining entry in issuedRequests invoke the
+    // authenticatorCancel operation on that authenticator, and remove its entry
+    // from the list.
+
+    // 4.1.2.8.c If any authenticator returns an error status, delete the
+    // corresponding entry from issuedRequests.
+    if (NS_WARN_IF(NS_FAILED(rv))) {
+      aRequest->SetFailure(rv);
+      return;
+    }
+
+    if (!isRegistered) {
+      continue;
+    }
+
+    // Sign
+    uint8_t* buffer;
+    uint32_t bufferlen;
+    rv = aToken->Sign(aRpIdHash.Elements(), aRpIdHash.Length(),
+                      aClientDataHash.Elements(), aClientDataHash.Length(),
+                      allowedCredential.Elements(), allowedCredential.Length(),
+                      &buffer, &bufferlen);
+    if (NS_WARN_IF(NS_FAILED(rv))) {
+      aRequest->SetFailure(rv);
+      return;
+    }
+
+    MOZ_ASSERT(buffer);
+    CryptoBuffer signatureData;
+    if (NS_WARN_IF(!signatureData.Assign(buffer, bufferlen))) {
+      free(buffer);
+      aRequest->SetFailure(NS_ERROR_OUT_OF_MEMORY);
+      return;
+    }
+    free(buffer);
+
+    // 4.1.2.8.d If any authenticator returns success:
+
+    // 4.1.2.8.d.1 Remove this authenticator’s entry from issuedRequests.
+
+    // 4.1.2.8.d.2 Create a new WebAuthnAssertion object named value and
+    // populate its fields with the values returned from the authenticator as
+    // well as the clientDataJSON computed earlier.
+
+    CryptoBuffer clientDataBuf;
+    if (!clientDataBuf.Assign(aClientData)) {
+      aRequest->SetFailure(NS_ERROR_OUT_OF_MEMORY);
+      return;
+    }
+
+    CryptoBuffer authenticatorDataBuf;
+    rv = U2FAssembleAuthenticatorData(authenticatorDataBuf, aRpIdHash,
+                                      signatureData);
+    if (NS_WARN_IF(NS_FAILED(rv))) {
+      aRequest->SetFailure(rv);
+      return;
+    }
+
+    RefPtr<ScopedCredential> credential = new ScopedCredential(this);
+    credential->SetType(ScopedCredentialType::ScopedCred);
+    credential->SetId(allowedCredential);
+
+    AssertionPtr assertion = new WebAuthnAssertion(this);
+    assertion->SetCredential(credential);
+    assertion->SetClientData(clientDataBuf);
+    assertion->SetAuthenticatorData(authenticatorDataBuf);
+    assertion->SetSignature(signatureData);
+
+    // 4.1.2.8.d.3 For each remaining entry in issuedRequests invoke the
+    // authenticatorCancel operation on that authenticator and remove its entry
+    // from the list.
+
+    // 4.1.2.8.d.4 Resolve promise with value and terminate this algorithm.
+    aRequest->SetSuccess(assertion);
+    return;
+  }
+
+  // 4.1.2.9 Reject promise with a DOMException whose name is "NotAllowedError",
+  // and terminate this algorithm.
+  aRequest->SetFailure(NS_ERROR_DOM_NOT_ALLOWED_ERR);
+}
+
+nsresult
+WebAuthentication::RelaxSameOrigin(const nsAString& aInputRpId,
+                                   /* out */ nsACString& aRelaxedRpId)
+{
+  MOZ_ASSERT(mParent);
+  nsCOMPtr<nsIDocument> document = mParent->GetDoc();
+  if (!document || !document->IsHTMLDocument()) {
+    return NS_ERROR_FAILURE;
+  }
+
+  // TODO: Bug 1329764: Invoke the Relax Algorithm, once properly defined
+  aRelaxedRpId.Assign(NS_ConvertUTF16toUTF8(aInputRpId));
+  return NS_OK;
+}
+
+already_AddRefed<Promise>
+WebAuthentication::MakeCredential(JSContext* aCx, const Account& aAccount,
+                  const Sequence<ScopedCredentialParameters>& aCryptoParameters,
+                  const ArrayBufferViewOrArrayBuffer& aChallenge,
+                  const ScopedCredentialOptions& aOptions)
+{
+  MOZ_ASSERT(mParent);
+  nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(GetParentObject());
+  if (!global) {
+    return nullptr;
+  }
+
+  ErrorResult rv;
+  RefPtr<Promise> promise = Promise::Create(global, rv);
+
+  nsresult initRv = InitLazily();
+  if (NS_FAILED(initRv)) {
+    promise->MaybeReject(initRv);
+    return promise.forget();
+  }
+
+  // 4.1.1.1 If timeoutSeconds was specified, check if its value lies within a
+  // reasonable range as defined by the platform and if not, correct it to the
+  // closest value lying within that range.
+
+  double adjustedTimeout = 30.0;
+  if (aOptions.mTimeoutSeconds.WasPassed()) {
+    adjustedTimeout = aOptions.mTimeoutSeconds.Value();
+    adjustedTimeout = std::max(15.0, adjustedTimeout);
+    adjustedTimeout = std::min(120.0, adjustedTimeout);
+  }
+
+  // 4.1.1.2 Let promise be a new Promise. Return promise and start a timer for
+  // adjustedTimeout seconds.
+
+  RefPtr<CredentialRequest> requestMonitor = new CredentialRequest();
+  requestMonitor->SetDeadline(TimeDuration::FromSeconds(adjustedTimeout));
+
+  if (mOrigin.EqualsLiteral("null")) {
+    // 4.1.1.3 If callerOrigin is an opaque origin, reject promise with a
+    // DOMException whose name is "NotAllowedError", and terminate this
+    // algorithm
+    MOZ_LOG(gWebauthLog, LogLevel::Debug, ("Rejecting due to opaque origin"));
+    promise->MaybeReject(NS_ERROR_DOM_NOT_ALLOWED_ERR);
+    return promise.forget();
+  }
+
+  nsCString rpId;
+  if (!aOptions.mRpId.WasPassed()) {
+    // 4.1.1.3.a If rpId is not specified, then set rpId to callerOrigin, and
+    // rpIdHash to the SHA-256 hash of rpId.
+    rpId.Assign(NS_ConvertUTF16toUTF8(mOrigin));
+  } else {
+    // 4.1.1.3.b If rpId is specified, then invoke the procedure used for
+    // relaxing the same-origin restriction by setting the document.domain
+    // attribute, using rpId as the given value but without changing the current
+    // document’s domain. If no errors are thrown, set rpId to the value of host
+    // as computed by this procedure, and rpIdHash to the SHA-256 hash of rpId.
+    // Otherwise, reject promise with a DOMException whose name is
+    // "SecurityError", and terminate this algorithm.
+
+    if (NS_FAILED(RelaxSameOrigin(aOptions.mRpId.Value(), rpId))) {
+      promise->MaybeReject(NS_ERROR_DOM_SECURITY_ERR);
+      return promise.forget();
+    }
+  }
+
+  CryptoBuffer rpIdHash;
+  if (!rpIdHash.SetLength(SHA256_LENGTH, fallible)) {
+    promise->MaybeReject(NS_ERROR_OUT_OF_MEMORY);
+    return promise.forget();
+  }
+
+  nsresult srv;
+  nsCOMPtr<nsICryptoHash> hashService =
+    do_CreateInstance(NS_CRYPTO_HASH_CONTRACTID, &srv);
+  if (NS_WARN_IF(NS_FAILED(srv))) {
+    promise->MaybeReject(NS_ERROR_DOM_SECURITY_ERR);
+    return promise.forget();
+  }
+
+  srv = HashCString(hashService, rpId, rpIdHash);
+  if (NS_WARN_IF(NS_FAILED(srv))) {
+    promise->MaybeReject(NS_ERROR_DOM_SECURITY_ERR);
+    return promise.forget();
+  }
+
+  // 4.1.1.4 Process each element of cryptoParameters using the following steps,
+  // to produce a new sequence normalizedParameters.
+  nsTArray<ScopedCredentialParameters> normalizedParams;
+  for (size_t a = 0; a < aCryptoParameters.Length(); ++a) {
+    // 4.1.1.4.a Let current be the currently selected element of
+    // cryptoParameters.
+
+    // 4.1.1.4.b If current.type does not contain a ScopedCredentialType
+    // supported by this implementation, then stop processing current and move
+    // on to the next element in cryptoParameters.
+    if (aCryptoParameters[a].mType != ScopedCredentialType::ScopedCred) {
+      continue;
+    }
+
+    // 4.1.1.4.c Let normalizedAlgorithm be the result of normalizing an
+    // algorithm using the procedure defined in [WebCryptoAPI], with alg set to
+    // current.algorithm and op set to 'generateKey'. If an error occurs during
+    // this procedure, then stop processing current and move on to the next
+    // element in cryptoParameters.
+
+    nsString algName;
+    if (NS_FAILED(GetAlgorithmName(aCx, aCryptoParameters[a].mAlgorithm,
+                                   algName))) {
+      continue;
+    }
+
+    // 4.1.1.4.d Add a new object of type ScopedCredentialParameters to
+    // normalizedParameters, with type set to current.type and algorithm set to
+    // normalizedAlgorithm.
+    ScopedCredentialParameters normalizedObj;
+    normalizedObj.mType = aCryptoParameters[a].mType;
+    normalizedObj.mAlgorithm.SetAsString().Assign(algName);
+
+    if (!normalizedParams.AppendElement(normalizedObj, mozilla::fallible)){
+      promise->MaybeReject(NS_ERROR_OUT_OF_MEMORY);
+      return promise.forget();
+    }
+  }
+
+  // 4.1.1.5 If normalizedAlgorithm is empty and cryptoParameters was not empty,
+  // cancel the timer started in step 2, reject promise with a DOMException
+  // whose name is "NotSupportedError", and terminate this algorithm.
+  if (normalizedParams.IsEmpty() && !aCryptoParameters.IsEmpty()) {
+    promise->MaybeReject(NS_ERROR_DOM_NOT_SUPPORTED_ERR);
+    return promise.forget();
+  }
+
+  // 4.1.1.6 If excludeList is undefined, set it to the empty list.
+
+  // 4.1.1.7 If extensions was specified, process any extensions supported by
+  // this client platform, to produce the extension data that needs to be sent
+  // to the authenticator. If an error is encountered while processing an
+  // extension, skip that extension and do not produce any extension data for
+  // it. Call the result of this processing clientExtensions.
+
+  // Currently no extensions are supported
+
+  // 4.1.1.8 Use attestationChallenge, callerOrigin and rpId, along with the
+  // token binding key associated with callerOrigin (if any), to create a
+  // ClientData structure representing this request. Choose a hash algorithm for
+  // hashAlg and compute the clientDataJSON and clientDataHash.
+
+  CryptoBuffer challenge;
+  if (!challenge.Assign(aChallenge)) {
+    promise->MaybeReject(NS_ERROR_DOM_SECURITY_ERR);
+    return promise.forget();
+  }
+
+  nsAutoCString clientDataJSON;
+  srv = AssembleClientData(mOrigin, challenge, clientDataJSON);
+  if (NS_WARN_IF(NS_FAILED(srv))) {
+    promise->MaybeReject(NS_ERROR_DOM_SECURITY_ERR);
+    return promise.forget();
+  }
+
+  CryptoBuffer clientDataHash;
+  if (!clientDataHash.SetLength(SHA256_LENGTH, fallible)) {
+    promise->MaybeReject(NS_ERROR_DOM_SECURITY_ERR);
+    return promise.forget();
+  }
+
+  srv = HashCString(hashService, clientDataJSON, clientDataHash);
+  if (NS_WARN_IF(NS_FAILED(srv))) {
+    promise->MaybeReject(NS_ERROR_DOM_SECURITY_ERR);
+    return promise.forget();
+  }
+
+  // 4.1.1.9 Initialize issuedRequests to an empty list.
+  RefPtr<CredentialPromise> monitorPromise = requestMonitor->Ensure();
+
+  // 4.1.1.10 For each authenticator currently available on this platform:
+  // asynchronously invoke the authenticatorMakeCredential operation on that
+  // authenticator with rpIdHash, clientDataHash, accountInformation,
+  // normalizedParameters, excludeList and clientExtensions as parameters. Add a
+  // corresponding entry to issuedRequests.
+  for (Authenticator u2ftoken : mAuthenticators) {
+    // 4.1.1.10.a For each credential C in excludeList that has a non-empty
+    // transports list, optionally use only the specified transports to test for
+    // the existence of C.
+    U2FAuthMakeCredential(requestMonitor, u2ftoken, rpIdHash, clientDataJSON,
+                          clientDataHash, aAccount, normalizedParams,
+                          aOptions.mExcludeList, aOptions.mExtensions);
+  }
+
+  requestMonitor->CompleteTask();
+
+  monitorPromise->Then(AbstractThread::MainThread(), __func__,
+    [promise] (CredentialPtr aInfo) {
+      promise->MaybeResolve(aInfo);
+    },
+    [promise] (nsresult aErrorCode) {
+      promise->MaybeReject(aErrorCode);
+  });
+
+  return promise.forget();
+}
+
+already_AddRefed<Promise>
+WebAuthentication::GetAssertion(const ArrayBufferViewOrArrayBuffer& aChallenge,
+                                const AssertionOptions& aOptions)
+{
+  MOZ_ASSERT(mParent);
+  nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(GetParentObject());
+  if (!global) {
+    return nullptr;
+  }
+
+  // 4.1.2.1 If timeoutSeconds was specified, check if its value lies within a
+  // reasonable range as defined by the platform and if not, correct it to the
+  // closest value lying within that range.
+
+  double adjustedTimeout = 30.0;
+  if (aOptions.mTimeoutSeconds.WasPassed()) {
+    adjustedTimeout = aOptions.mTimeoutSeconds.Value();
+    adjustedTimeout = std::max(15.0, adjustedTimeout);
+    adjustedTimeout = std::min(120.0, adjustedTimeout);
+  }
+
+  // 4.1.2.2 Let promise be a new Promise. Return promise and start a timer for
+  // adjustedTimeout seconds.
+
+  RefPtr<AssertionRequest> requestMonitor = new AssertionRequest();
+  requestMonitor->SetDeadline(TimeDuration::FromSeconds(adjustedTimeout));
+
+  ErrorResult rv;
+  RefPtr<Promise> promise = Promise::Create(global, rv);
+
+  nsresult initRv = InitLazily();
+  if (NS_FAILED(initRv)) {
+    promise->MaybeReject(initRv);
+    return promise.forget();
+  }
+
+  if (mOrigin.EqualsLiteral("null")) {
+    // 4.1.2.3 If callerOrigin is an opaque origin, reject promise with a
+    // DOMException whose name is "NotAllowedError", and terminate this algorithm
+    promise->MaybeReject(NS_ERROR_DOM_NOT_ALLOWED_ERR);
+    return promise.forget();
+  }
+
+  nsCString rpId;
+  if (!aOptions.mRpId.WasPassed()) {
+    // 4.1.2.3.a If rpId is not specified, then set rpId to callerOrigin, and
+    // rpIdHash to the SHA-256 hash of rpId.
+    rpId.Assign(NS_ConvertUTF16toUTF8(mOrigin));
+  } else {
+    // 4.1.2.3.b If rpId is specified, then invoke the procedure used for
+    // relaxing the same-origin restriction by setting the document.domain
+    // attribute, using rpId as the given value but without changing the current
+    // document’s domain. If no errors are thrown, set rpId to the value of host
+    // as computed by this procedure, and rpIdHash to the SHA-256 hash of rpId.
+    // Otherwise, reject promise with a DOMException whose name is
+    // "SecurityError", and terminate this algorithm.
+
+    if (NS_FAILED(RelaxSameOrigin(aOptions.mRpId.Value(), rpId))) {
+      promise->MaybeReject(NS_ERROR_DOM_SECURITY_ERR);
+      return promise.forget();
+    }
+  }
+
+  CryptoBuffer rpIdHash;
+  if (!rpIdHash.SetLength(SHA256_LENGTH, fallible)) {
+    promise->MaybeReject(NS_ERROR_DOM_SECURITY_ERR);
+    return promise.forget();
+  }
+
+  nsresult srv;
+  nsCOMPtr<nsICryptoHash> hashService =
+    do_CreateInstance(NS_CRYPTO_HASH_CONTRACTID, &srv);
+  if (NS_WARN_IF(NS_FAILED(srv))) {
+    promise->MaybeReject(NS_ERROR_DOM_SECURITY_ERR);
+    return promise.forget();
+  }
+
+  srv = HashCString(hashService, rpId, rpIdHash);
+  if (NS_WARN_IF(NS_FAILED(srv))) {
+    promise->MaybeReject(NS_ERROR_DOM_SECURITY_ERR);
+    return promise.forget();
+  }
+
+  // 4.1.2.4 If extensions was specified, process any extensions supported by
+  // this client platform, to produce the extension data that needs to be sent
+  // to the authenticator. If an error is encountered while processing an
+  // extension, skip that extension and do not produce any extension data for
+  // it. Call the result of this processing clientExtensions.
+
+  // TODO
+
+  // 4.1.2.5 Use assertionChallenge, callerOrigin and rpId, along with the token
+  // binding key associated with callerOrigin (if any), to create a ClientData
+  // structure representing this request. Choose a hash algorithm for hashAlg
+  // and compute the clientDataJSON and clientDataHash.
+  CryptoBuffer challenge;
+  if (!challenge.Assign(aChallenge)) {
+    promise->MaybeReject(NS_ERROR_DOM_SECURITY_ERR);
+    return promise.forget();
+  }
+
+  nsAutoCString clientDataJSON;
+  srv = AssembleClientData(mOrigin, challenge, clientDataJSON);
+  if (NS_WARN_IF(NS_FAILED(srv))) {
+    promise->MaybeReject(NS_ERROR_DOM_SECURITY_ERR);
+    return promise.forget();
+  }
+
+  CryptoBuffer clientDataHash;
+  if (!clientDataHash.SetLength(SHA256_LENGTH, fallible)) {
+    promise->MaybeReject(NS_ERROR_DOM_SECURITY_ERR);
+    return promise.forget();
+  }
+
+  srv = HashCString(hashService, clientDataJSON, clientDataHash);
+  if (NS_WARN_IF(NS_FAILED(srv))) {
+    promise->MaybeReject(NS_ERROR_DOM_SECURITY_ERR);
+    return promise.forget();
+  }
+
+  // Note: we only support U2F-style authentication for now, so we effectively
+  // require an AllowList.
+  if (!aOptions.mAllowList.WasPassed()) {
+    promise->MaybeReject(NS_ERROR_DOM_NOT_ALLOWED_ERR);
+    return promise.forget();
+  }
+
+  const Sequence<ScopedCredentialDescriptor>& allowList =
+    aOptions.mAllowList.Value();
+
+  // 4.1.2.6 Initialize issuedRequests to an empty list.
+  RefPtr<AssertionPromise> monitorPromise = requestMonitor->Ensure();
+
+  // 4.1.2.7 For each authenticator currently available on this platform,
+  // perform the following steps:
+  for(Authenticator u2ftoken : mAuthenticators) {
+    // 4.1.2.7.a If allowList is undefined or empty, let credentialList be an
+    // empty list. Otherwise, execute a platform-specific procedure to determine
+    // which, if any, credentials listed in allowList might be present on this
+    // authenticator, and set credentialList to this filtered list. If no such
+    // filtering is possible, set credentialList to an empty list.
+
+    nsTArray<CryptoBuffer> credentialList;
+
+    for (const ScopedCredentialDescriptor& scd : allowList) {
+      CryptoBuffer buf;
+      if (NS_WARN_IF(!buf.Assign(scd.mId))) {
+        continue;
+      }
+
+      // 4.1.2.7.b For each credential C within the credentialList that has a
+      // non- empty transports list, optionally use only the specified
+      // transports to get assertions using credential C.
+
+      // TODO: Filter using Transport
+      if (!credentialList.AppendElement(buf, mozilla::fallible)) {
+        requestMonitor->CancelNow();
+        promise->MaybeReject(NS_ERROR_OUT_OF_MEMORY);
+        return promise.forget();
+      }
+    }
+
+    // 4.1.2.7.c If the above filtering process concludes that none of the
+    // credentials on allowList can possibly be on this authenticator, do not
+    // perform any of the following steps for this authenticator, and proceed to
+    // the next authenticator (if any).
+    if (credentialList.IsEmpty()) {
+      continue;
+    }
+
+    // 4.1.2.7.d Asynchronously invoke the authenticatorGetAssertion operation
+    // on this authenticator with rpIdHash, clientDataHash, credentialList, and
+    // clientExtensions as parameters.
+    U2FAuthGetAssertion(requestMonitor, u2ftoken, rpIdHash, clientDataJSON,
+                        clientDataHash, credentialList, aOptions.mExtensions);
+  }
+
+  requestMonitor->CompleteTask();
+
+  monitorPromise->Then(AbstractThread::MainThread(), __func__,
+    [promise] (AssertionPtr aAssertion) {
+      promise->MaybeResolve(aAssertion);
+    },
+    [promise] (nsresult aErrorCode) {
+      promise->MaybeReject(aErrorCode);
+  });
+
+  return promise.forget();
+}
+
+} // namespace dom
+} // namespace mozilla
new file mode 100644
--- /dev/null
+++ b/dom/u2f/WebAuthentication.h
@@ -0,0 +1,113 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_dom_WebAuthentication_h
+#define mozilla_dom_WebAuthentication_h
+
+#include "js/TypeDecls.h"
+#include "mozilla/Attributes.h"
+#include "mozilla/dom/BindingDeclarations.h"
+#include "mozilla/dom/DOMException.h"
+#include "mozilla/dom/WebAuthenticationBinding.h"
+#include "mozilla/dom/WebCryptoCommon.h"
+#include "mozilla/ErrorResult.h"
+#include "mozilla/MozPromise.h"
+#include "mozilla/ReentrantMonitor.h"
+#include "mozilla/SharedThreadPool.h"
+#include "nsCycleCollectionParticipant.h"
+#include "nsWrapperCache.h"
+
+#include "U2FAuthenticator.h"
+#include "WebAuthnRequest.h"
+
+namespace mozilla {
+namespace dom {
+
+struct Account;
+class ArrayBufferViewOrArrayBuffer;
+struct AssertionOptions;
+class OwningArrayBufferViewOrArrayBuffer;
+struct ScopedCredentialOptions;
+struct ScopedCredentialParameters;
+
+} // namespace dom
+} // namespace mozilla
+
+namespace mozilla {
+namespace dom {
+
+typedef RefPtr<ScopedCredentialInfo> CredentialPtr;
+typedef RefPtr<WebAuthnAssertion> AssertionPtr;
+typedef WebAuthnRequest<CredentialPtr> CredentialRequest;
+typedef WebAuthnRequest<AssertionPtr> AssertionRequest;
+typedef MozPromise<CredentialPtr, nsresult, false> CredentialPromise;
+typedef MozPromise<AssertionPtr, nsresult, false> AssertionPromise;
+
+class WebAuthentication final : public nsISupports
+                              , public nsWrapperCache
+{
+public:
+  NS_DECL_CYCLE_COLLECTING_ISUPPORTS
+  NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(WebAuthentication)
+
+public:
+  explicit WebAuthentication(nsPIDOMWindowInner* aParent);
+
+protected:
+  ~WebAuthentication();
+
+public:
+  nsPIDOMWindowInner*
+  GetParentObject() const
+  {
+    return mParent;
+  }
+
+  virtual JSObject*
+  WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) override;
+
+  already_AddRefed<Promise>
+  MakeCredential(JSContext* aCx, const Account& accountInformation,
+                 const Sequence<ScopedCredentialParameters>& cryptoParameters,
+                 const ArrayBufferViewOrArrayBuffer& attestationChallenge,
+                 const ScopedCredentialOptions& options);
+
+  already_AddRefed<Promise>
+  GetAssertion(const ArrayBufferViewOrArrayBuffer& assertionChallenge,
+               const AssertionOptions& options);
+
+private:
+  nsresult
+  InitLazily();
+
+  void
+  U2FAuthMakeCredential(const RefPtr<CredentialRequest>& aRequest,
+             const Authenticator& aToken, CryptoBuffer& aRpIdHash,
+             const nsACString& aClientData, CryptoBuffer& aClientDataHash,
+             const Account& aAccount,
+             const nsTArray<ScopedCredentialParameters>& aNormalizedParams,
+             const Optional<Sequence<ScopedCredentialDescriptor>>& aExcludeList,
+             const WebAuthnExtensions& aExtensions);
+  void
+  U2FAuthGetAssertion(const RefPtr<AssertionRequest>& aRequest,
+                   const Authenticator& aToken, CryptoBuffer& aRpIdHash,
+                   const nsACString& aClientData, CryptoBuffer& aClientDataHash,
+                   nsTArray<CryptoBuffer>& aAllowList,
+                   const WebAuthnExtensions& aExtensions);
+
+  nsresult
+  RelaxSameOrigin(const nsAString& aInputRpId, nsACString& aRelaxedRpId);
+
+  nsCOMPtr<nsPIDOMWindowInner> mParent;
+  nsString mOrigin;
+  Sequence<Authenticator> mAuthenticators;
+  bool mInitialized;
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif // mozilla_dom_WebAuthentication_h
new file mode 100644
--- /dev/null
+++ b/dom/u2f/WebAuthnAssertion.cpp
@@ -0,0 +1,98 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "mozilla/dom/WebAuthenticationBinding.h"
+#include "mozilla/dom/WebAuthnAssertion.h"
+
+namespace mozilla {
+namespace dom {
+
+// Only needed for refcounted objects.
+NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(WebAuthnAssertion, mParent)
+NS_IMPL_CYCLE_COLLECTING_ADDREF(WebAuthnAssertion)
+NS_IMPL_CYCLE_COLLECTING_RELEASE(WebAuthnAssertion)
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(WebAuthnAssertion)
+  NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
+  NS_INTERFACE_MAP_ENTRY(nsISupports)
+NS_INTERFACE_MAP_END
+
+WebAuthnAssertion::WebAuthnAssertion(WebAuthentication* aParent)
+  : mParent(aParent)
+{}
+
+WebAuthnAssertion::~WebAuthnAssertion()
+{}
+
+JSObject*
+WebAuthnAssertion::WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto)
+{
+  return WebAuthnAssertionBinding::Wrap(aCx, this, aGivenProto);
+}
+
+already_AddRefed<ScopedCredential>
+WebAuthnAssertion::Credential() const
+{
+  RefPtr<ScopedCredential> temp(mCredential);
+  return temp.forget();
+}
+
+void
+WebAuthnAssertion::GetClientData(JSContext* aCx,
+                                 JS::MutableHandle<JSObject*> aRetVal) const
+{
+  aRetVal.set(mClientData.ToUint8Array(aCx));
+}
+
+void
+WebAuthnAssertion::GetAuthenticatorData(JSContext* aCx,
+                                        JS::MutableHandle<JSObject*> aRetVal) const
+{
+  aRetVal.set(mAuthenticatorData.ToUint8Array(aCx));
+}
+
+void
+WebAuthnAssertion::GetSignature(JSContext* aCx,
+                                JS::MutableHandle<JSObject*> aRetVal) const
+{
+  aRetVal.set(mSignature.ToUint8Array(aCx));
+}
+
+nsresult
+WebAuthnAssertion::SetCredential(RefPtr<ScopedCredential> aCredential)
+{
+  mCredential = aCredential;
+  return NS_OK;
+}
+
+nsresult
+WebAuthnAssertion::SetClientData(CryptoBuffer& aBuffer)
+{
+  if (!mClientData.Assign(aBuffer)) {
+    return NS_ERROR_OUT_OF_MEMORY;
+  }
+  return NS_OK;
+}
+
+nsresult
+WebAuthnAssertion::SetAuthenticatorData(CryptoBuffer& aBuffer)
+{
+  if (!mAuthenticatorData.Assign(aBuffer)) {
+    return NS_ERROR_OUT_OF_MEMORY;
+  }
+  return NS_OK;
+}
+
+nsresult
+WebAuthnAssertion::SetSignature(CryptoBuffer& aBuffer)
+{
+  if (!mSignature.Assign(aBuffer)) {
+    return NS_ERROR_OUT_OF_MEMORY;
+  }
+  return NS_OK;
+}
+
+} // namespace dom
+} // namespace mozilla
new file mode 100644
--- /dev/null
+++ b/dom/u2f/WebAuthnAssertion.h
@@ -0,0 +1,86 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_dom_WebAuthnAssertion_h
+#define mozilla_dom_WebAuthnAssertion_h
+
+#include "js/TypeDecls.h"
+#include "mozilla/Attributes.h"
+#include "mozilla/ErrorResult.h"
+#include "mozilla/dom/BindingDeclarations.h"
+#include "nsCycleCollectionParticipant.h"
+#include "nsWrapperCache.h"
+
+namespace mozilla {
+namespace dom {
+
+class ScopedCredential;
+
+} // namespace dom
+} // namespace mozilla
+
+namespace mozilla {
+namespace dom {
+
+class WebAuthnAssertion final : public nsISupports
+                              , public nsWrapperCache
+{
+public:
+  NS_DECL_CYCLE_COLLECTING_ISUPPORTS
+  NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(WebAuthnAssertion)
+
+public:
+  explicit WebAuthnAssertion(WebAuthentication* aParent);
+
+protected:
+  ~WebAuthnAssertion();
+
+public:
+  WebAuthentication*
+  GetParentObject() const
+  {
+    return mParent;
+  }
+
+  virtual JSObject*
+  WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) override;
+
+  already_AddRefed<ScopedCredential>
+  Credential() const;
+
+  void
+  GetClientData(JSContext* cx, JS::MutableHandle<JSObject*> aRetVal) const;
+
+  void
+  GetAuthenticatorData(JSContext* cx, JS::MutableHandle<JSObject*> aRetVal) const;
+
+  void
+  GetSignature(JSContext* cx, JS::MutableHandle<JSObject*> aRetVal) const;
+
+  nsresult
+  SetCredential(RefPtr<ScopedCredential> aCredential);
+
+  nsresult
+  SetClientData(CryptoBuffer& aBuffer);
+
+  nsresult
+  SetAuthenticatorData(CryptoBuffer& aBuffer);
+
+  nsresult
+  SetSignature(CryptoBuffer& aBuffer);
+
+private:
+  RefPtr<WebAuthentication> mParent;
+  RefPtr<ScopedCredential> mCredential;
+  CryptoBuffer mAuthenticatorData;
+  CryptoBuffer mClientData;
+  CryptoBuffer mSignature;
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif // mozilla_dom_WebAuthnAssertion_h
new file mode 100644
--- /dev/null
+++ b/dom/u2f/WebAuthnAttestation.cpp
@@ -0,0 +1,100 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "mozilla/dom/WebAuthenticationBinding.h"
+#include "mozilla/dom/WebAuthnAttestation.h"
+
+namespace mozilla {
+namespace dom {
+
+// Only needed for refcounted objects.
+NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(WebAuthnAttestation, mParent)
+NS_IMPL_CYCLE_COLLECTING_ADDREF(WebAuthnAttestation)
+NS_IMPL_CYCLE_COLLECTING_RELEASE(WebAuthnAttestation)
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(WebAuthnAttestation)
+  NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
+  NS_INTERFACE_MAP_ENTRY(nsISupports)
+NS_INTERFACE_MAP_END
+
+WebAuthnAttestation::WebAuthnAttestation(WebAuthentication* aParent)
+  : mParent(aParent)
+{}
+
+WebAuthnAttestation::~WebAuthnAttestation()
+{}
+
+JSObject*
+WebAuthnAttestation::WrapObject(JSContext* aCx,
+                                JS::Handle<JSObject*> aGivenProto)
+{
+  return WebAuthnAttestationBinding::Wrap(aCx, this, aGivenProto);
+}
+
+void
+WebAuthnAttestation::GetFormat(nsString& aRetVal) const
+{
+  aRetVal = mFormat;
+}
+
+void
+WebAuthnAttestation::GetClientData(JSContext* aCx,
+                                   JS::MutableHandle<JSObject*> aRetVal) const
+{
+  aRetVal.set(mClientData.ToUint8Array(aCx));
+}
+
+void
+WebAuthnAttestation::GetAuthenticatorData(JSContext* aCx,
+                                          JS::MutableHandle<JSObject*> aRetVal) const
+{
+  aRetVal.set(mAuthenticatorData.ToUint8Array(aCx));
+}
+
+void
+WebAuthnAttestation::GetAttestation(JSContext* aCx,
+                                    JS::MutableHandle<JS::Value> aRetVal) const
+{
+  // JS::RootedObject obj(aCx);
+  // obj.set();
+  aRetVal.setObject(*mAttestation.ToUint8Array(aCx));
+}
+
+nsresult
+WebAuthnAttestation::SetFormat(nsString aFormat)
+{
+  mFormat = aFormat;
+  return NS_OK;
+}
+
+nsresult
+WebAuthnAttestation::SetClientData(CryptoBuffer& aBuffer)
+{
+  if (!mClientData.Assign(aBuffer)) {
+    return NS_ERROR_OUT_OF_MEMORY;
+  }
+  return NS_OK;
+}
+
+nsresult
+WebAuthnAttestation::SetAuthenticatorData(CryptoBuffer& aBuffer)
+{
+  if (!mAuthenticatorData.Assign(aBuffer)) {
+    return NS_ERROR_OUT_OF_MEMORY;
+  }
+  return NS_OK;
+}
+
+nsresult
+WebAuthnAttestation::SetAttestation(CryptoBuffer& aBuffer)
+{
+  if (!mAttestation.Assign(aBuffer)) {
+    return NS_ERROR_OUT_OF_MEMORY;
+  }
+  return NS_OK;
+}
+
+} // namespace dom
+} // namespace mozilla
new file mode 100644
--- /dev/null
+++ b/dom/u2f/WebAuthnAttestation.h
@@ -0,0 +1,79 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_dom_WebAuthnAttestation_h
+#define mozilla_dom_WebAuthnAttestation_h
+
+#include "js/TypeDecls.h"
+#include "mozilla/Attributes.h"
+#include "mozilla/ErrorResult.h"
+#include "mozilla/dom/BindingDeclarations.h"
+#include "mozilla/dom/CryptoBuffer.h"
+#include "nsCycleCollectionParticipant.h"
+#include "nsWrapperCache.h"
+
+namespace mozilla {
+namespace dom {
+
+class WebAuthnAttestation final : public nsISupports
+                                , public nsWrapperCache
+{
+public:
+  NS_DECL_CYCLE_COLLECTING_ISUPPORTS
+  NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(WebAuthnAttestation)
+
+public:
+  explicit WebAuthnAttestation(WebAuthentication* aParent);
+
+protected:
+  ~WebAuthnAttestation();
+
+public:
+  WebAuthentication*
+  GetParentObject() const
+  {
+    return mParent;
+  }
+
+  virtual JSObject*
+  WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) override;
+
+  void
+  GetFormat(nsString& aRetVal) const;
+
+  void
+  GetClientData(JSContext* aCx, JS::MutableHandle<JSObject*> aRetVal) const;
+
+  void
+  GetAuthenticatorData(JSContext* caCxx, JS::MutableHandle<JSObject*> aRetVal) const;
+
+  void
+  GetAttestation(JSContext* aCx, JS::MutableHandle<JS::Value> aRetVal) const;
+
+  nsresult
+  SetFormat(nsString aFormat);
+
+  nsresult
+  SetClientData(CryptoBuffer& aBuffer);
+
+  nsresult
+  SetAuthenticatorData(CryptoBuffer& aBuffer);
+
+  nsresult
+  SetAttestation(CryptoBuffer& aBuffer);
+
+private:
+  RefPtr<WebAuthentication> mParent;
+  nsString mFormat;
+  CryptoBuffer mClientData;
+  CryptoBuffer mAuthenticatorData;
+  CryptoBuffer mAttestation;
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif // mozilla_dom_WebAuthnAttestation_h
new file mode 100644
--- /dev/null
+++ b/dom/u2f/WebAuthnRequest.h
@@ -0,0 +1,137 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_dom_WebAuthnAsync_h
+#define mozilla_dom_WebAuthnAsync_h
+
+#include "mozilla/MozPromise.h"
+#include "mozilla/ReentrantMonitor.h"
+#include "mozilla/SharedThreadPool.h"
+#include "mozilla/TimeStamp.h"
+
+namespace mozilla {
+namespace dom {
+
+extern mozilla::LazyLogModule gWebauthLog; // defined in U2F.cpp
+
+// WebAuthnRequest tracks the completion of a single WebAuthn request that
+// may run on multiple kinds of authenticators, and be subject to a deadline.
+template<class Success>
+class WebAuthnRequest {
+public:
+  WebAuthnRequest()
+    : mCancelled(false)
+    , mSuccess(false)
+    , mCountTokens(0)
+    , mTokensFailed(0)
+    , mReentrantMonitor("WebAuthnRequest")
+  {}
+
+  void AddActiveToken(const char* aCallSite)
+  {
+    MOZ_LOG(gWebauthLog, LogLevel::Debug,
+           ("WebAuthnRequest is tracking a new token, called from [%s]",
+            aCallSite));
+    ReentrantMonitorAutoEnter mon(mReentrantMonitor);
+    MOZ_ASSERT(!IsComplete());
+    mCountTokens += 1;
+  }
+
+  bool IsComplete()
+  {
+    ReentrantMonitorAutoEnter mon(mReentrantMonitor);
+    return mCancelled || mSuccess ||
+      (mCountTokens > 0 && mTokensFailed == mCountTokens);
+  }
+
+  void CancelNow()
+  {
+    ReentrantMonitorAutoEnter mon(mReentrantMonitor);
+
+    // It's possible for a race to cause CancelNow to get called after
+    // a success or a cancel. We only complete once.
+    if (IsComplete()) {
+      return;
+    }
+
+    mCancelled = true;
+    mPromise.Reject(NS_ERROR_DOM_NOT_ALLOWED_ERR, __func__);
+  }
+
+  void SetFailure(nsresult aError)
+  {
+    ReentrantMonitorAutoEnter mon(mReentrantMonitor);
+
+    // It's possible for a race to cause SetFailure to get called after
+    // a success or a cancel. We only complete once.
+    if (IsComplete()) {
+      return;
+    }
+
+    mTokensFailed += 1;
+    MOZ_ASSERT(mTokensFailed <= mCountTokens);
+
+    if (mTokensFailed == mCountTokens) {
+      // Provide the final error as being indicitive of the whole set.
+      mPromise.Reject(aError, __func__);
+    }
+  }
+
+  void SetSuccess(const Success& aResult)
+  {
+    ReentrantMonitorAutoEnter mon(mReentrantMonitor);
+
+    // It's possible for a race to cause multiple calls to SetSuccess
+    // in succession. We will only select the earliest.
+    if (IsComplete()) {
+      return;
+    }
+
+    mSuccess = true;
+    mPromise.Resolve(aResult, __func__);
+  }
+
+  void SetDeadline(TimeDuration aDeadline)
+  {
+    ReentrantMonitorAutoEnter mon(mReentrantMonitor);
+    MOZ_ASSERT(!IsComplete());
+    // TODO: Monitor the deadline and stop with a timeout error if it expires.
+  }
+
+  RefPtr<MozPromise<Success, nsresult, false>> Ensure()
+  {
+    ReentrantMonitorAutoEnter mon(mReentrantMonitor);
+    MOZ_ASSERT(!IsComplete());
+    return mPromise.Ensure(__func__);
+  }
+
+  void CompleteTask()
+  {
+    ReentrantMonitorAutoEnter mon(mReentrantMonitor);
+
+    if (mCountTokens == 0) {
+      // Special case for there being no tasks to complete
+      mPromise.Reject(NS_ERROR_DOM_NOT_ALLOWED_ERR, __func__);
+    }
+  }
+
+  NS_INLINE_DECL_THREADSAFE_REFCOUNTING(WebAuthnRequest)
+
+private:
+  ~WebAuthnRequest() {};
+
+  bool mCancelled;
+  bool mSuccess;
+  int mCountTokens;
+  int mTokensFailed;
+  ReentrantMonitor mReentrantMonitor;
+  MozPromiseHolder<MozPromise<Success, nsresult, false>> mPromise;
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif // mozilla_dom_WebAuthnAsync_h
--- a/dom/u2f/moz.build
+++ b/dom/u2f/moz.build
@@ -1,30 +1,43 @@
 # -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
 # vim: set filetype=python:
 # This Source Code Form is subject to the terms of the Mozilla Public
 # License, v. 2.0. If a copy of the MPL was not distributed with this
 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
 
 EXPORTS.mozilla.dom += [
     'NSSU2FTokenRemote.h',
+    'ScopedCredential.h',
+    'ScopedCredentialInfo.h',
     'U2F.h',
+    'U2FAuthenticator.h',
     'USBToken.h',
+    'WebAuthentication.h',
+    'WebAuthnAssertion.h',
+    'WebAuthnAttestation.h',
+    'WebAuthnRequest.h',
 ]
 
 UNIFIED_SOURCES += [
     'NSSU2FTokenRemote.cpp',
+    'ScopedCredential.cpp',
+    'ScopedCredentialInfo.cpp',
     'U2F.cpp',
     'USBToken.cpp',
+    'WebAuthentication.cpp',
+    'WebAuthnAssertion.cpp',
+    'WebAuthnAttestation.cpp',
 ]
 
 include('/ipc/chromium/chromium-config.mozbuild')
 
 FINAL_LIBRARY = 'xul'
 
 LOCAL_INCLUDES += [
     '/dom/base',
     '/dom/crypto',
     '/security/manager/ssl',
     '/security/pkix/include',
+    '/security/pkix/lib',
 ]
 
-MOCHITEST_MANIFESTS += ['tests/mochitest.ini']
\ No newline at end of file
+MOCHITEST_MANIFESTS += ['tests/mochitest.ini']
--- a/dom/u2f/tests/u2futil.js
+++ b/dom/u2f/tests/u2futil.js
@@ -190,15 +190,17 @@ function verifySignature(key, data, derS
   let sigS = new Uint8Array(sigAsn1.result.value_block.value[1].value_block.value_hex);
 
   // The resulting R and S values from the ASN.1 Sequence must be fit into 32
   // bytes. Sometimes they have leading zeros, sometimes they're too short, it
   // all depends on what lib generated the signature.
   let R = sanitizeSigArray(sigR);
   let S = sanitizeSigArray(sigS);
 
+  console.log("Verifying these bytes: " + bytesToBase64UrlSafe(data));
+
   let sigData = new Uint8Array(R.length + S.length);
   sigData.set(R);
   sigData.set(S, R.length);
 
   let alg = {name: "ECDSA", hash: "SHA-256"};
   return crypto.subtle.verify(alg, key, sigData, data);
 }
--- a/dom/webidl/Navigator.webidl
+++ b/dom/webidl/Navigator.webidl
@@ -375,8 +375,13 @@ partial interface Navigator {
   readonly attribute boolean mozE10sEnabled;
 };
 #endif
 
 [NoInterfaceObject, Exposed=(Window,Worker)]
 interface NavigatorConcurrentHardware {
   readonly attribute unsigned long long hardwareConcurrency;
 };
+
+partial interface Navigator {
+  [Pref="security.webauth.w3c", SameObject]
+  readonly attribute WebAuthentication authentication;
+};
--- a/dom/webidl/U2F.webidl
+++ b/dom/webidl/U2F.webidl
@@ -21,17 +21,17 @@ typedef sequence<Transport> Transports;
 
 enum Transport {
     "bt",
     "ble",
     "nfc",
     "usb"
 };
 
-dictionary ClientData {
+dictionary U2FClientData {
     DOMString             typ; // Spelling is from the specification
     DOMString             challenge;
     DOMString             origin;
     // cid_pubkey for Token Binding is not implemented
 };
 
 dictionary RegisterRequest {
     DOMString version;
new file mode 100644
--- /dev/null
+++ b/dom/webidl/WebAuthentication.webidl
@@ -0,0 +1,113 @@
+/* -*- 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/.
+ *
+ * The origin of this IDL file is
+ * https://www.w3.org/TR/webauthn/
+ */
+
+/***** Interfaces to Data *****/
+
+[SecureContext]
+interface ScopedCredentialInfo {
+    readonly attribute ScopedCredential    credential;
+    readonly attribute WebAuthnAttestation attestation;
+};
+
+dictionary Account {
+    required DOMString rpDisplayName;
+    required DOMString displayName;
+    required DOMString id;
+    DOMString          name;
+    DOMString          imageURL;
+};
+
+typedef (boolean or DOMString) WebAuthnAlgorithmID; // Fix when upstream there's a definition of how to serialize AlgorithmIdentifier
+
+dictionary ScopedCredentialParameters {
+    required ScopedCredentialType type;
+    required WebAuthnAlgorithmID  algorithm; // NOTE: changed from AllgorithmIdentifier because typedef (object or DOMString) not serializable
+};
+
+dictionary ScopedCredentialOptions {
+    unsigned long                        timeoutSeconds;
+    USVString                            rpId;
+    sequence<ScopedCredentialDescriptor> excludeList;
+    WebAuthnExtensions                   extensions;
+};
+
+[SecureContext]
+interface WebAuthnAssertion {
+    readonly attribute ScopedCredential credential;
+    readonly attribute ArrayBuffer      clientData;
+    readonly attribute ArrayBuffer      authenticatorData;
+    readonly attribute ArrayBuffer      signature;
+};
+
+dictionary AssertionOptions {
+    unsigned long                        timeoutSeconds;
+    USVString                            rpId;
+    sequence<ScopedCredentialDescriptor> allowList;
+    WebAuthnExtensions                   extensions;
+};
+
+dictionary WebAuthnExtensions {
+};
+
+[SecureContext]
+interface WebAuthnAttestation {
+    readonly    attribute USVString     format;
+    readonly    attribute ArrayBuffer   clientData;
+    readonly    attribute ArrayBuffer   authenticatorData;
+    readonly    attribute any           attestation;
+};
+
+// Renamed from "ClientData" to avoid a collision with U2F
+dictionary WebAuthnClientData {
+    required DOMString           challenge;
+    required DOMString           origin;
+    required WebAuthnAlgorithmID hashAlg; // NOTE: changed from AllgorithmIdentifier because typedef (object or DOMString) not serializable
+    DOMString                    tokenBinding;
+    WebAuthnExtensions           extensions;
+};
+
+enum ScopedCredentialType {
+    "ScopedCred"
+};
+
+[SecureContext]
+interface ScopedCredential {
+    readonly attribute ScopedCredentialType type;
+    readonly attribute ArrayBuffer          id;
+};
+
+dictionary ScopedCredentialDescriptor {
+    required ScopedCredentialType type;
+    required BufferSource         id;
+    sequence <WebAuthnTransport>  transports;
+};
+
+// Renamed from "Transport" to avoid a collision with U2F
+enum WebAuthnTransport {
+    "usb",
+    "nfc",
+    "ble"
+};
+
+/***** The Main API *****/
+
+[SecureContext]
+interface WebAuthentication {
+    Promise<ScopedCredentialInfo> makeCredential (
+        Account                                 accountInformation,
+        sequence<ScopedCredentialParameters>    cryptoParameters,
+        BufferSource                            attestationChallenge,
+        optional ScopedCredentialOptions        options
+    );
+
+    Promise<WebAuthnAssertion> getAssertion (
+        BufferSource               assertionChallenge,
+        optional AssertionOptions  options
+    );
+};
--- a/dom/webidl/moz.build
+++ b/dom/webidl/moz.build
@@ -557,16 +557,17 @@ WEBIDL_FILES = [
     'VideoPlaybackQuality.webidl',
     'VideoStreamTrack.webidl',
     'VideoTrack.webidl',
     'VideoTrackList.webidl',
     'VRDisplay.webidl',
     'VTTCue.webidl',
     'VTTRegion.webidl',
     'WaveShaperNode.webidl',
+    'WebAuthentication.webidl',
     'WebComponents.webidl',
     'WebGL2RenderingContext.webidl',
     'WebGLRenderingContext.webidl',
     'WebKitCSSMatrix.webidl',
     'WebSocket.webidl',
     'WheelEvent.webidl',
     'WidevineCDMManifest.webidl',
     'WindowOrWorkerGlobalScope.webidl',
--- a/gfx/layers/Layers.cpp
+++ b/gfx/layers/Layers.cpp
@@ -1177,16 +1177,53 @@ ContainerLayer::InsertAfter(Layer* aChil
     next->SetPrevSibling(aChild);
   }
   aAfter->SetNextSibling(aChild);
   NS_ADDREF(aChild);
   DidInsertChild(aChild);
   return true;
 }
 
+void
+ContainerLayer::RemoveAllChildren()
+{
+  // Optimizes "while (mFirstChild) ContainerLayer::RemoveChild(mFirstChild);"
+  Layer* current = mFirstChild;
+
+  // This is inlining DidRemoveChild() on each layer; we can skip the calls
+  // to NotifyPaintedLayerRemoved as it gets taken care of when as we call
+  // NotifyRemoved prior to removing any layers.
+  while (current) {
+    Layer* next = current->GetNextSibling();
+    if (current->GetType() == TYPE_READBACK) {
+      static_cast<ReadbackLayer*>(current)->NotifyRemoved();
+    }
+    current = next;
+  }
+
+  current = mFirstChild;
+  mFirstChild = nullptr;
+  while (current) {
+    MOZ_ASSERT(!current->GetPrevSibling());
+
+    Layer* next = current->GetNextSibling();
+    current->SetParent(nullptr);
+    current->SetNextSibling(nullptr);
+    if (next) {
+      next->SetPrevSibling(nullptr);
+    }
+    NS_RELEASE(current);
+    current = next;
+  }
+}
+
+// Note that ContainerLayer::RemoveAllChildren is an optimized
+// version of this code; if you make changes to ContainerLayer::RemoveChild
+// consider whether the matching changes need to be made to
+// ContainerLayer::RemoveAllChildren
 bool
 ContainerLayer::RemoveChild(Layer *aChild)
 {
   if (aChild->Manager() != Manager()) {
     NS_ERROR("Child has wrong manager");
     return false;
   }
   if (aChild->GetParent() != this) {
@@ -1620,16 +1657,20 @@ ContainerLayer::HasOpaqueAncestorLayer(L
 {
   for (Layer* l = aLayer->GetParent(); l; l = l->GetParent()) {
     if (l->GetContentFlags() & Layer::CONTENT_OPAQUE)
       return true;
   }
   return false;
 }
 
+// Note that ContainerLayer::RemoveAllChildren contains an optimized
+// version of this code; if you make changes to ContainerLayer::DidRemoveChild
+// consider whether the matching changes need to be made to
+// ContainerLayer::RemoveAllChildren
 void
 ContainerLayer::DidRemoveChild(Layer* aLayer)
 {
   PaintedLayer* tl = aLayer->AsPaintedLayer();
   if (tl && tl->UsedForReadback()) {
     for (Layer* l = mFirstChild; l; l = l->GetNextSibling()) {
       if (l->GetType() == TYPE_READBACK) {
         static_cast<ReadbackLayer*>(l)->NotifyPaintedLayerRemoved(tl);
--- a/gfx/layers/Layers.h
+++ b/gfx/layers/Layers.h
@@ -2252,16 +2252,22 @@ public:
 
   EventRegionsOverride GetEventRegionsOverride() const {
     return mEventRegionsOverride;
   }
 
 protected:
   friend class ReadbackProcessor;
 
+  // Note that this is not virtual, and is based on the implementation of
+  // ContainerLayer::RemoveChild, so it should only be called where you would
+  // want to explicitly call the base class implementation of RemoveChild;
+  // e.g., while (mFirstChild) ContainerLayer::RemoveChild(mFirstChild);
+  void RemoveAllChildren();
+
   void DidInsertChild(Layer* aLayer);
   void DidRemoveChild(Layer* aLayer);
 
   bool AnyAncestorOrThisIs3DContextLeaf();
 
   void Collect3DContextLeaves(nsTArray<Layer*>& aToSort);
 
   // Collects child layers that do not extend 3D context. For ContainerLayers
--- a/gfx/layers/basic/BasicContainerLayer.cpp
+++ b/gfx/layers/basic/BasicContainerLayer.cpp
@@ -18,20 +18,17 @@
 
 using namespace mozilla::gfx;
 
 namespace mozilla {
 namespace layers {
 
 BasicContainerLayer::~BasicContainerLayer()
 {
-  while (mFirstChild) {
-    ContainerLayer::RemoveChild(mFirstChild);
-  }
-
+  ContainerLayer::RemoveAllChildren();
   MOZ_COUNT_DTOR(BasicContainerLayer);
 }
 
 void
 BasicContainerLayer::ComputeEffectiveTransforms(const Matrix4x4& aTransformToSurface)
 {
   // We push groups for container layers if we need to, which always
   // are aligned in device space, so it doesn't really matter how we snap
--- a/gfx/layers/client/ClientContainerLayer.h
+++ b/gfx/layers/client/ClientContainerLayer.h
@@ -31,20 +31,17 @@ public:
   {
     MOZ_COUNT_CTOR(ClientContainerLayer);
     mSupportsComponentAlphaChildren = true;
   }
 
 protected:
   virtual ~ClientContainerLayer()
   {
-    while (mFirstChild) {
-      ContainerLayer::RemoveChild(mFirstChild);
-    }
-
+    ContainerLayer::RemoveAllChildren();
     MOZ_COUNT_DTOR(ClientContainerLayer);
   }
 
 public:
   virtual void RenderLayer() override
   {
     RenderMaskLayers(this);
 
--- a/gfx/vr/VRManager.cpp
+++ b/gfx/vr/VRManager.cpp
@@ -165,17 +165,19 @@ void
 VRManager::NotifyVsync(const TimeStamp& aVsyncTimestamp)
 {
   const double kVRDisplayRefreshMaxDuration = 5000; // milliseconds
 
   bool bHaveEventListener = false;
 
   for (auto iter = mVRManagerParents.Iter(); !iter.Done(); iter.Next()) {
     VRManagerParent *vmp = iter.Get()->GetKey();
-    Unused << vmp->SendNotifyVSync();
+    if (mVRDisplays.Count()) {
+      Unused << vmp->SendNotifyVSync();
+    }
     bHaveEventListener |= vmp->HaveEventListener();
   }
 
   for (auto iter = mVRDisplays.Iter(); !iter.Done(); iter.Next()) {
     gfx::VRDisplayHost* display = iter.UserData();
     display->NotifyVSync();
   }
 
--- a/layout/forms/nsListControlFrame.cpp
+++ b/layout/forms/nsListControlFrame.cpp
@@ -113,16 +113,22 @@ nsListControlFrame::nsListControlFrame(n
 }
 
 //---------------------------------------------------------
 nsListControlFrame::~nsListControlFrame()
 {
   mComboboxFrame = nullptr;
 }
 
+static bool ShouldFireDropDownEvent() {
+  return (XRE_IsContentProcess() &&
+          Preferences::GetBool("browser.tabs.remote.desktopbehavior", false)) ||
+         Preferences::GetBool("dom.select_popup_in_parent.enabled", false);
+}
+
 // for Bug 47302 (remove this comment later)
 void
 nsListControlFrame::DestroyFrom(nsIFrame* aDestructRoot)
 {
   // get the receiver interface from the browser button's content node
   ENSURE_TRUE(mContent);
 
   // Clear the frame pointer on our event listener, just in case the
@@ -136,18 +142,17 @@ nsListControlFrame::DestroyFrom(nsIFrame
                                       mEventListener, false);
   mContent->RemoveSystemEventListener(NS_LITERAL_STRING("mousedown"),
                                       mEventListener, false);
   mContent->RemoveSystemEventListener(NS_LITERAL_STRING("mouseup"),
                                       mEventListener, false);
   mContent->RemoveSystemEventListener(NS_LITERAL_STRING("mousemove"),
                                       mEventListener, false);
 
-  if (XRE_IsContentProcess() &&
-      Preferences::GetBool("browser.tabs.remote.desktopbehavior", false)) {
+  if (ShouldFireDropDownEvent()) {
     nsContentUtils::AddScriptRunner(
       new AsyncEventDispatcher(mContent,
                                NS_LITERAL_STRING("mozhidedropdown"), true,
                                true));
   }
 
   nsFormControlFrame::RegUnRegAccessKey(static_cast<nsIFrame*>(this), false);
   nsHTMLScrollFrame::DestroyFrom(aDestructRoot);
@@ -1784,18 +1789,17 @@ nsListControlFrame::GetIndexFromDOMEvent
   }
 
   return NS_ERROR_FAILURE;
 }
 
 static bool
 FireShowDropDownEvent(nsIContent* aContent, bool aShow, bool aIsSourceTouchEvent)
 {
-  if (XRE_IsContentProcess() &&
-      Preferences::GetBool("browser.tabs.remote.desktopbehavior", false)) {
+  if (ShouldFireDropDownEvent()) {
     nsString eventName;
     if (aShow) {
       eventName = aIsSourceTouchEvent ? NS_LITERAL_STRING("mozshowdropdown-sourcetouch") :
                                         NS_LITERAL_STRING("mozshowdropdown");
     } else {
       eventName = NS_LITERAL_STRING("mozhidedropdown");
     }
     nsContentUtils::DispatchChromeEvent(aContent->OwnerDoc(), aContent,
--- a/media/webrtc/signaling/src/mediapipeline/MediaPipeline.cpp
+++ b/media/webrtc/signaling/src/mediapipeline/MediaPipeline.cpp
@@ -114,17 +114,16 @@ protected:
 class VideoFrameConverter
 {
 public:
   NS_INLINE_DECL_THREADSAFE_REFCOUNTING(VideoFrameConverter)
 
   VideoFrameConverter()
     : mLength(0)
     , last_img_(-1) // -1 is not a guaranteed invalid serial. See bug 1262134.
-    , disabled_frame_sent_(false)
 #ifdef DEBUG
     , mThrottleCount(0)
     , mThrottleRecord(0)
 #endif
     , mMutex("VideoFrameConverter")
   {
     MOZ_COUNT_CTOR(VideoFrameConverter);
 
@@ -178,27 +177,32 @@ public:
 
     bool forceBlack = aForceBlack || aChunk.mFrame.GetForceBlack();
 
     if (forceBlack) {
       // Reset the last-img check.
       // -1 is not a guaranteed invalid serial. See bug 1262134.
       last_img_ = -1;
 
-      if (disabled_frame_sent_) {
-        // After disabling we just pass one black frame to the encoder.
-        // Allocating and setting it to black steals some performance
-        // that can be avoided. We don't handle resolution changes while
-        // disabled for now.
+      // After disabling, we still want *some* frames to flow to the other side.
+      // It could happen that we drop the packet that carried the first disabled
+      // frame, for instance. Note that this still requires the application to
+      // send a frame, or it doesn't trigger at all.
+      const double disabledMinFps = 1.0;
+      TimeStamp t = aChunk.mTimeStamp;
+      MOZ_ASSERT(!t.IsNull());
+      if (!disabled_frame_sent_.IsNull() &&
+          (t - disabled_frame_sent_).ToSeconds() < (1.0 / disabledMinFps)) {
         return;
       }
 
-      disabled_frame_sent_ = true;
+      disabled_frame_sent_ = t;
     } else {
-      disabled_frame_sent_ = false;
+      // This sets it to the Null time.
+      disabled_frame_sent_ = TimeStamp();
     }
 
     ++mLength; // Atomic
 
     nsCOMPtr<nsIRunnable> runnable =
       NewRunnableMethod<StoreRefPtrPassByPtr<Image>, bool>(
         this, &VideoFrameConverter::ProcessVideoFrame,
         aChunk.mFrame.GetImage(), forceBlack);
@@ -447,17 +451,17 @@ protected:
     VideoFrameConverted(yuv, buffer_size, size.width, size.height, mozilla::kVideoI420, 0);
   }
 
   Atomic<int32_t, Relaxed> mLength;
   RefPtr<TaskQueue> mTaskQueue;
 
   // Written and read from the queueing thread (normally MSG).
   int32_t last_img_; // serial number of last Image
-  bool disabled_frame_sent_; // If a black frame has been sent after disabling.
+  TimeStamp disabled_frame_sent_; // The time we sent the last disabled frame.
 #ifdef DEBUG
   uint32_t mThrottleCount;
   uint32_t mThrottleRecord;
 #endif
 
   // mMutex guards the below variables.
   Mutex mMutex;
   nsTArray<RefPtr<VideoConverterListener>> mListeners;
--- a/mobile/android/app/findbugs-exclude.xml
+++ b/mobile/android/app/findbugs-exclude.xml
@@ -1,19 +1,14 @@
 <?xml version="1.0" encoding="UTF-8"?>
 <!-- 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/. -->
 <FindBugsFilter>
 
-  <!-- Bug 1315982 -->
-  <Match>
-    <Bug pattern="DE_MIGHT_IGNORE" />
-  </Match>
-
   <!-- Bug 1316008 -->
   <Match>
     <Bug pattern="DM_DEFAULT_ENCODING" />
   </Match>
 
   <!-- Bug 1320298 -->
   <Match>
     <Bug pattern="MS_MUTABLE_ARRAY" />
@@ -24,21 +19,16 @@
     <Bug pattern="MS_SHOULD_BE_FINAL" />
   </Match>
 
   <!-- Bug 1316010 -->
   <Match>
     <Bug pattern="UL_UNRELEASED_LOCK" />
   </Match>
 
-  <!-- Bug 1316011 -->
-  <Match>
-    <Bug pattern="VO_VOLATILE_INCREMENT" />
-  </Match>
-
   <!-- Bug 1316021 -->
   <Match>
     <Bug pattern="ST_WRITE_TO_STATIC_FROM_INSTANCE_METHOD" />
   </Match>
 
   <!-- Bug 1320316 -->
   <Match>
     <Bug pattern="DM_BOXED_PRIMITIVE_TOSTRING" />
@@ -48,9 +38,23 @@
   </Match>
     <Match>
     <Bug pattern="NP_NULL_ON_SOME_PATH" />
   </Match>
     <Match>
     <Bug pattern="NP_NULL_PARAM_DEREF_NONVIRTUAL" />
   </Match>
 
+  <!-- We explicitly want to swallow exceptions in releaseProviders() (Bug 1315982) -->
+  <Match>
+    <Class name="org.mozilla.gecko.sync.repositories.android.FennecTabsRepository$FennecTabsRepositorySession" />
+    <Method name="releaseProviders" />
+    <Bug pattern="DE_MIGHT_IGNORE" />
+  </Match>
+
+  <!-- Ignore false positive in SerialRecordConsumer (Bug 1316011) -->
+  <Match>
+    <Class name="org.mozilla.gecko.sync.synchronizer.SerialRecordConsumer" />
+    <Method name="stored" />
+    <Bug pattern="VO_VOLATILE_INCREMENT" />
+  </Match>
+
 </FindBugsFilter>
--- a/mobile/android/base/java/org/mozilla/gecko/home/TwoLinePageRow.java
+++ b/mobile/android/base/java/org/mozilla/gecko/home/TwoLinePageRow.java
@@ -264,17 +264,19 @@ public class TwoLinePageRow extends Line
         if (mOngoingIconLoad != null) {
             mOngoingIconLoad.cancel(true);
         }
 
         // Displayed RecentTabsPanel URLs may refer to pages opened in reader mode, so we
         // remove the about:reader prefix to ensure the Favicon loads properly.
         final String pageURL = ReaderModeUtils.stripAboutReaderUrl(url);
 
-        if (bookmarkId < BrowserContract.Bookmarks.FAKE_PARTNER_BOOKMARKS_START) {
+        if (TextUtils.isEmpty(pageURL)) {
+            // If url is empty, display the item as-is but do not load an icon if we do not have a page URL (bug 1310622)
+        } else if (bookmarkId < BrowserContract.Bookmarks.FAKE_PARTNER_BOOKMARKS_START) {
             mOngoingIconLoad = Icons.with(getContext())
                     .pageUrl(pageURL)
                     .skipNetwork()
                     .privileged(true)
                     .icon(IconDescriptor.createGenericIcon(
                             PartnerBookmarksProviderProxy.getUriForIcon(getContext(), bookmarkId).toString()))
                     .build()
                     .execute(mFavicon.createIconCallback());
deleted file mode 100644
--- a/mobile/android/base/resources/drawable/ic_menu_bookmark_add.xml
+++ /dev/null
@@ -1,13 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- 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/. -->
-
-<!-- This asset is properly available in large-* dirs so this null
-     reference exists for build time on API 9 builds. -->
-<shape xmlns:android="http://schemas.android.com/apk/res/android"
-       android:shape="rectangle">
-
-    <solid android:color="@android:color/transparent"/>
-
-</shape>
deleted file mode 100644
--- a/mobile/android/base/resources/drawable/star_blue.xml
+++ /dev/null
@@ -1,13 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- 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/. -->
-
-<!-- This asset is properly available in large-* dirs so this null
-     reference exists for build time on API 9 builds. -->
-<shape xmlns:android="http://schemas.android.com/apk/res/android"
-       android:shape="rectangle">
-
-    <solid android:color="@android:color/transparent"/>
-
-</shape>
--- a/modules/libpref/init/all.js
+++ b/modules/libpref/init/all.js
@@ -112,16 +112,17 @@ pref("offline-apps.quota.warn",        5
 // cache compression turned off for now - see bug #715198
 pref("browser.cache.compression_level", 0);
 
 // Don't show "Open with" option on download dialog if true.
 pref("browser.download.forbid_open_with", false);
 
 // Whether or not testing features are enabled.
 pref("dom.quotaManager.testing", false);
+pref("dom.select_popup_in_parent.enabled", false);
 
 // Whether or not indexedDB is enabled.
 pref("dom.indexedDB.enabled", true);
 // Whether or not indexedDB experimental features are enabled.
 pref("dom.indexedDB.experimental", false);
 // Enable indexedDB logging.
 pref("dom.indexedDB.logging.enabled", true);
 // Detailed output in log messages.
--- a/netwerk/base/security-prefs.js
+++ b/netwerk/base/security-prefs.js
@@ -88,16 +88,17 @@ pref("security.pki.netscape_step_up_poli
 // Configures Certificate Transparency support mode:
 // 0: Fully disabled.
 // 1: Only collect telemetry. CT qualification checks are not performed.
 pref("security.pki.certificate_transparency.mode", 1);
 
 pref("security.webauth.u2f", false);
 pref("security.webauth.u2f_enable_softtoken", false);
 pref("security.webauth.u2f_enable_usbtoken", false);
+pref("security.webauth.w3c", false);
 
 pref("security.ssl.errorReporting.enabled", true);
 pref("security.ssl.errorReporting.url", "https://incoming.telemetry.mozilla.org/submit/sslreports/");
 pref("security.ssl.errorReporting.automatic", false);
 
 // Impose a maximum age on HPKP headers, to avoid sites getting permanently
 // blacking themselves out by setting a bad pin.  (60 days by default)
 // https://tools.ietf.org/html/rfc7469#section-4.1
--- a/security/manager/ssl/nsNSSU2FToken.cpp
+++ b/security/manager/ssl/nsNSSU2FToken.cpp
@@ -2,16 +2,17 @@
 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "nsNSSU2FToken.h"
 
 #include "CryptoBuffer.h"
+#include "mozilla/Base64.h"
 #include "mozilla/Casting.h"
 #include "nsNSSComponent.h"
 #include "pk11pub.h"
 #include "prerror.h"
 #include "secerr.h"
 #include "WebCryptoCommon.h"
 
 using namespace mozilla;
@@ -717,16 +718,28 @@ nsNSSU2FToken::Sign(uint8_t* aApplicatio
 
   // It's OK to ignore the return values here because we're writing into
   // pre-allocated space
   signedDataBuf.AppendElements(aApplication, aApplicationLen, mozilla::fallible);
   signedDataBuf.AppendElement(0x01, mozilla::fallible);
   signedDataBuf.AppendSECItem(counterItem);
   signedDataBuf.AppendElements(aChallenge, aChallengeLen, mozilla::fallible);
 
+  if (MOZ_LOG_TEST(gNSSTokenLog, LogLevel::Debug)) {
+    nsAutoCString base64;
+    nsresult rv = Base64URLEncode(signedDataBuf.Length(), signedDataBuf.Elements(),
+                                  Base64URLEncodePaddingPolicy::Omit, base64);
+    if (NS_WARN_IF(NS_FAILED(rv))) {
+      return NS_ERROR_FAILURE;
+    }
+
+    MOZ_LOG(gNSSTokenLog, LogLevel::Debug,
+            ("U2F Token signing bytes (base64): %s", base64.get()));
+  }
+
   ScopedAutoSECItem signatureItem;
   SECStatus srv = SEC_SignData(&signatureItem, signedDataBuf.Elements(),
                                signedDataBuf.Length(), privKey.get(),
                                SEC_OID_ANSIX962_ECDSA_SHA256_SIGNATURE);
   if (srv != SECSuccess) {
     MOZ_LOG(gNSSTokenLog, LogLevel::Warning,
             ("Signature failure: %d", PORT_GetError()));
     return NS_ERROR_FAILURE;
--- a/toolkit/content/aboutServiceWorkers.xhtml
+++ b/toolkit/content/aboutServiceWorkers.xhtml
@@ -13,17 +13,17 @@
 
 <html xmlns="http://www.w3.org/1999/xhtml">
     <head>
         <title>&aboutServiceWorkers.title;</title>
         <link rel="stylesheet" href="chrome://global/skin/about.css" type="text/css" />
         <link rel="stylesheet" href="chrome://mozapps/skin/aboutServiceWorkers.css" type="text/css" />
         <script type="application/javascript;version=1.7" src="chrome://global/content/aboutServiceWorkers.js" />
     </head>
-    <body id="body">
+    <body id="body" dir="&locale.dir;">
         <div id="warning_not_enabled" class="warningBackground">
             <div class="warningMessage">&aboutServiceWorkers.warning_not_enabled;</div>
         </div>
 
         <div id="warning_no_serviceworkers" class="warningBackground">
             <div class="warningMessage">&aboutServiceWorkers.warning_no_serviceworkers;</div>
         </div>
 
--- a/toolkit/content/browser-content.js
+++ b/toolkit/content/browser-content.js
@@ -10,16 +10,18 @@ var Cr = Components.results;
 
 Cu.import("resource://gre/modules/Services.jsm");
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 
 XPCOMUtils.defineLazyModuleGetter(this, "ReaderMode",
   "resource://gre/modules/ReaderMode.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "BrowserUtils",
   "resource://gre/modules/BrowserUtils.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "SelectContentHelper",
+  "resource://gre/modules/SelectContentHelper.jsm");
 
 var global = this;
 
 
 // Lazily load the finder code
 addMessageListener("Finder:Initialize", function() {
   let {RemoteFinderListener} = Cu.import("resource://gre/modules/RemoteFinder.jsm", {});
   new RemoteFinderListener(global);
@@ -1820,8 +1822,27 @@ let TelemetryScrollTracker = {
 
   shouldIgnorePage() {
     return content.location == "" ||
            content.location.protocol === "about:";
   }
 };
 
 TelemetryScrollTracker.init();
+
+addEventListener("mozshowdropdown", event => {
+  if (!event.isTrusted)
+    return;
+
+  if (!SelectContentHelper.open) {
+    new SelectContentHelper(event.target, {isOpenedViaTouch: false}, this);
+  }
+});
+
+addEventListener("mozshowdropdown-sourcetouch", event => {
+  if (!event.isTrusted)
+    return;
+
+  if (!SelectContentHelper.open) {
+    new SelectContentHelper(event.target, {isOpenedViaTouch: true}, this);
+  }
+});
+
--- a/toolkit/content/jar.mn
+++ b/toolkit/content/jar.mn
@@ -57,17 +57,16 @@ toolkit.jar:
    content/global/filepicker.properties
    content/global/globalOverlay.js
    content/global/mozilla.xhtml
    content/global/process-content.js
    content/global/resetProfile.css
    content/global/resetProfile.js
    content/global/resetProfile.xul
    content/global/resetProfileProgress.xul
-   content/global/select-child.js
    content/global/TopLevelVideoDocument.js
    content/global/timepicker.xhtml
    content/global/treeUtils.js
    content/global/viewZoomOverlay.js
    content/global/bindings/autocomplete.xml    (widgets/autocomplete.xml)
    content/global/bindings/browser.xml         (widgets/browser.xml)
    content/global/bindings/button.xml          (widgets/button.xml)
    content/global/bindings/calendar.js         (widgets/calendar.js)
--- a/toolkit/content/widgets/autocomplete.xml
+++ b/toolkit/content/widgets/autocomplete.xml
@@ -1245,16 +1245,25 @@ extends="chrome://global/content/binding
             if (numRows > this.maxRows) {
               // Set a fixed max-height to avoid flicker when growing the panel.
               let lastVisibleRowRect = rows[this.maxRows - 1].getBoundingClientRect();
               let visibleHeight = lastVisibleRowRect.bottom - firstRowRect.top;
               this.richlistbox.style.maxHeight =
                 visibleHeight + this._rlbPadding + "px";
             }
 
+            // The class `forceHandleUnderflow` is for the item might need to
+            // handle OverUnderflow or Overflow when the height of an item will
+            // be changed dynamically.
+            for (let i = 0; i < numRows; i++) {
+              if (rows[i].classList.contains("forceHandleUnderflow")) {
+                rows[i].handleOverUnderflow();
+              }
+            }
+
             let lastRowRect = rows[numRows - 1].getBoundingClientRect();
             // Calculate the height to have the first row to last row shown
             height = lastRowRect.bottom - firstRowRect.top +
                      this._rlbPadding;
           }
 
           let animate = this._rlbAnimated &&
                         this.getAttribute("dontanimate") != "true";
@@ -1506,18 +1515,19 @@ extends="chrome://global/content/binding
                            class="ac-action-text"
                            xbl:inherits="selected"/>
         </xul:description>
       </xul:hbox>
     </content>
     <implementation>
       <constructor><![CDATA[
         // Unlike other autocomplete items, the height of the insecure warning
-        // increases by wrapping.
-        this._handleOverflow();
+        // increases by wrapping. So "forceHandleUnderflow" is for container to
+        // recalculate an item's height and width.
+        this.classList.add("forceHandleUnderflow");
       ]]></constructor>
     </implementation>
   </binding>
 
   <binding id="autocomplete-richlistitem" extends="chrome://global/content/bindings/richlistbox.xml#richlistitem">
 
     <content align="center"
              onoverflow="this._onOverflow();"
--- a/toolkit/content/widgets/browser.xml
+++ b/toolkit/content/widgets/browser.xml
@@ -951,16 +951,22 @@
 
           if (this.messageManager) {
             this.messageManager.addMessageListener("PopupBlocking:UpdateBlockedPopups", this);
             this.messageManager.addMessageListener("Autoscroll:Start", this);
             this.messageManager.addMessageListener("Autoscroll:Cancel", this);
             this.messageManager.addMessageListener("AudioPlayback:Start", this);
             this.messageManager.addMessageListener("AudioPlayback:Stop", this);
             this.messageManager.addMessageListener("AudioPlayback:Block", this);
+
+            if (this.hasAttribute("selectmenulist")) {
+              this.messageManager.addMessageListener("Forms:ShowDropDown", this);
+              this.messageManager.addMessageListener("Forms:HideDropDown", this);
+            }
+
           }
         ]]>
       </constructor>
 
       <destructor>
         <![CDATA[
           this.destroy();
         ]]>
@@ -969,16 +975,21 @@
       <!-- This is necessary because the destructor doesn't always get called when
            we are removed from a tabbrowser. This will be explicitly called by tabbrowser.
 
            Note: this function is overriden in remote-browser.xml, so any clean-up that
            also applies to browser.isRemoteBrowser = true must be duplicated there. -->
       <method name="destroy">
         <body>
           <![CDATA[
+          // Make sure that any open select is closed.
+          if (this._selectParentHelper) {
+            let menulist = document.getElementById(this.getAttribute("selectmenulist"));
+            this._selectParentHelper.hide(menulist, this);
+          }
           if (this.mDestroyed)
             return;
           this.mDestroyed = true;
 
           if (this.docShell && this.webNavigation.sessionHistory) {
             var os = Components.classes["@mozilla.org/observer-service;1"]
                                .getService(Components.interfaces.nsIObserverService);
             try {
@@ -1040,16 +1051,37 @@
               this.audioPlaybackStarted();
               break;
             case "AudioPlayback:Stop":
               this.audioPlaybackStopped();
               break;
             case "AudioPlayback:Block":
               this.audioPlaybackBlocked();
               break;
+            case "Forms:ShowDropDown": {
+              if (!this._selectParentHelper) {
+                this._selectParentHelper =
+                  Cu.import("resource://gre/modules/SelectParentHelper.jsm", {}).SelectParentHelper;
+              }
+
+              let menulist = document.getElementById(this.getAttribute("selectmenulist"));
+              menulist.menupopup.style.direction = data.direction;
+              this._selectParentHelper.populate(menulist, data.options, data.selectedIndex, this._fullZoom);
+              this._selectParentHelper.open(this, menulist, data.rect, data.isOpenedViaTouch);
+              break;
+            }
+
+            case "Forms:HideDropDown": {
+              if (this._selectParentHelper) {
+                let menulist = document.getElementById(this.getAttribute("selectmenulist"));
+                this._selectParentHelper.hide(menulist, this);
+              }
+              break;
+            }
+
           }
           return undefined;
         ]]></body>
       </method>
 
       <method name="receiveMessage">
         <parameter name="aMessage"/>
         <body><![CDATA[
--- a/toolkit/content/widgets/remote-browser.xml
+++ b/toolkit/content/widgets/remote-browser.xml
@@ -384,17 +384,16 @@
           this.messageManager.addMessageListener("DOMFullscreen:RequestExit", this);
           this.messageManager.addMessageListener("DOMFullscreen:RequestRollback", this);
           this.messageManager.addMessageListener("MozApplicationManifest", this);
           this.messageManager.loadFrameScript("chrome://global/content/browser-child.js", true);
 
           if (this.hasAttribute("selectmenulist")) {
             this.messageManager.addMessageListener("Forms:ShowDropDown", this);
             this.messageManager.addMessageListener("Forms:HideDropDown", this);
-            this.messageManager.loadFrameScript("chrome://global/content/select-child.js", true);
           }
 
           if (!this.hasAttribute("disablehistory")) {
             Services.obs.addObserver(this, "browser:purge-session-history", true);
           }
 
           let jsm = "resource://gre/modules/RemoteController.jsm";
           let RemoteController = Components.utils.import(jsm, {}).RemoteController;
@@ -495,24 +494,16 @@
 
             case "ZoomChangeUsingMouseWheel": {
               let event = document.createEvent("Events");
               event.initEvent("ZoomChangeUsingMouseWheel", true, false);
               this.dispatchEvent(event);
               break;
             }
 
-            case "Forms:HideDropDown": {
-              if (this._selectParentHelper) {
-                let menulist = document.getElementById(this.getAttribute("selectmenulist"));
-                this._selectParentHelper.hide(menulist, this);
-              }
-              break;
-            }
-
             case "DOMFullscreen:RequestExit": {
               let windowUtils = window.QueryInterface(Components.interfaces.nsIInterfaceRequestor)
                                       .getInterface(Components.interfaces.nsIDOMWindowUtils);
               windowUtils.exitFullscreen();
               break;
             }
 
             case "DOMFullscreen:RequestRollback": {
--- a/toolkit/themes/shared/in-content/common.inc.css
+++ b/toolkit/themes/shared/in-content/common.inc.css
@@ -105,17 +105,17 @@ xul|groupbox {
   -moz-appearance: none;
   border: none;
   margin: 15px 0 0;
   padding-inline-start: 0;
   padding-inline-end: 0;
   font-size: 1.25rem;
 }
 
-xul|groupbox xul|label:not(.menu-accel):not(.menu-text):not(.indent),
+xul|groupbox xul|label:not(.menu-accel):not(.menu-text):not(.indent):not(.learnMore),
 xul|groupbox xul|description {
   /* !important needed to override toolkit !important rule */
   margin-inline-start: 0 !important;
   margin-inline-end: 0 !important;
 }
 
 /* tabpanels and tabs */