merge mozilla-inbound to mozilla-central a=merge FIREFOX_AURORA_48_BASE
authorCarsten "Tomcat" Book <cbook@mozilla.com>
Mon, 25 Apr 2016 11:55:12 +0200
changeset 332602 1c6385ae1fe7e37d8f23f958ce14582f07af729e
parent 332404 0225961ad7adbef25e670abf314ec67f5e6c224d (current diff)
parent 332601 4c3b5f9bd44a64ff81a49653881c5415b088a504 (diff)
child 332603 da4a5ee70fd5c3d960aa97f290f5dfdfbd6f3afc
child 333724 a0868d83ff41797c0c1361d6fe6b8a6a41f048a3
child 333746 9dbb62d87b6b5801d17754670382bd085f79507c
push id6048
push userkmoir@mozilla.com
push dateMon, 06 Jun 2016 19:02:08 +0000
treeherdermozilla-beta@46d72a56c57d [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmerge
milestone48.0a1
first release with
nightly linux32
1c6385ae1fe7 / 48.0a1 / 20160425030548 / files
nightly linux64
1c6385ae1fe7 / 48.0a1 / 20160425030548 / files
nightly mac
1c6385ae1fe7 / 48.0a1 / 20160425030548 / files
nightly win32
1c6385ae1fe7 / 48.0a1 / 20160425030548 / files
nightly win64
1c6385ae1fe7 / 48.0a1 / 20160425030548 / files
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
releases
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
merge mozilla-inbound to mozilla-central a=merge
browser/base/content/browser-eme.js
browser/components/extensions/ext-tabs.js
dom/u2f/tests/facet/facetList-good
dom/u2f/tests/facet/facetList-good^headers^
dom/u2f/tests/facet/facetList-invalid_format
dom/u2f/tests/facet/facetList-invalid_format^headers^
dom/u2f/tests/facet/facetList-no_overlap
dom/u2f/tests/facet/facetList-no_overlap^headers^
dom/u2f/tests/facet/facetList.txt
dom/u2f/tests/test_frame_appid_facet_remoteload.html
layout/reftests/apng-mime/animated.apng
layout/reftests/apng-mime/expected.html
layout/reftests/apng-mime/reftest.list
layout/reftests/apng-mime/static.png
layout/reftests/apng-mime/test.html
mobile/android/base/moz.build
toolkit/components/extensions/schemas/manifest.json
toolkit/components/prompts/content/commonDialog.xul
toolkit/components/prompts/test/chromeScript.js
toolkit/components/prompts/test/mochitest.ini
toolkit/components/prompts/test/prompt_common.js
toolkit/components/prompts/test/test_bug619644.html
toolkit/components/prompts/test/test_bug620145.html
toolkit/components/prompts/test/test_bug625187.html
toolkit/components/prompts/test/test_dom_prompts.html
toolkit/components/prompts/test/test_modal_prompts.html
toolkit/components/prompts/test/test_modal_select.html
--- a/b2g/app/b2g.js
+++ b/b2g/app/b2g.js
@@ -1053,16 +1053,19 @@ pref("layout.accessiblecaret.enabled", t
 // by the spec in bug 921965.
 pref("layout.accessiblecaret.bar.enabled", true);
 
 // APZ on real devices supports long tap events.
 #ifdef MOZ_WIDGET_GONK
 pref("layout.accessiblecaret.use_long_tap_injector", false);
 #endif
 
+// Hide carets and text selection dialog during scrolling.
+pref("layout.accessiblecaret.always_show_when_scrolling", false);
+
 // Enable sync and mozId with Firefox Accounts.
 pref("services.sync.fxaccounts.enabled", true);
 pref("identity.fxaccounts.enabled", true);
 
 // Mobile Identity API.
 pref("services.mobileid.server.uri", "https://msisdn.services.mozilla.com");
 
 pref("identity.fxaccounts.remote.oauth.uri", "https://oauth.accounts.firefox.com/v1");
--- a/browser/app/profile/firefox.js
+++ b/browser/app/profile/firefox.js
@@ -91,16 +91,20 @@ pref("extensions.hotfix.certs.2.sha1Fing
 
 // Check AUS for system add-on updates.
 pref("extensions.systemAddon.update.url", "https://aus5.mozilla.org/update/3/SystemAddons/%VERSION%/%BUILD_ID%/%BUILD_TARGET%/%LOCALE%/%CHANNEL%/%OS_VERSION%/%DISTRIBUTION%/%DISTRIBUTION_VERSION%/update.xml");
 
 // Disable add-ons that are not installed by the user in all scopes by default.
 // See the SCOPE constants in AddonManager.jsm for values to use here.
 pref("extensions.autoDisableScopes", 15);
 
+// Add-on content security policies.
+pref("extensions.webextensions.base-content-security-policy", "script-src 'self' https://* moz-extension: blob: filesystem: 'unsafe-eval' 'unsafe-inline'; object-src 'self' https://* moz-extension: blob: filesystem:;");
+pref("extensions.webextensions.default-content-security-policy", "script-src 'self'; object-src 'self';");
+
 // Require signed add-ons by default
 pref("xpinstall.signatures.required", true);
 pref("xpinstall.signatures.devInfoURL", "https://wiki.mozilla.org/Addons/Extension_Signing");
 
 // Dictionary download preference
 pref("browser.dictionaries.download.url", "https://addons.mozilla.org/%LOCALE%/firefox/dictionaries/");
 
 // At startup, should we check to see if the installation
--- a/browser/base/content/aboutNetError.xhtml
+++ b/browser/base/content/aboutNetError.xhtml
@@ -324,16 +324,27 @@
           if (getErrorCode() == "weakCryptoUsed" || getErrorCode() == "sslv3Used") {
             showAdvancedButton(getErrorCode() == "weakCryptoUsed");
           }
         }.bind(this), true, true);
 
         var event = new CustomEvent("AboutNetErrorLoad", {bubbles:true});
         document.dispatchEvent(event);
 
+        if (err == "inadequateSecurityError") {
+          // Remove the "Try again" button for HTTP/2 inadequate security as it
+          // is useless.
+          document.getElementById("errorTryAgain").style.display = "none";
+
+          var container = document.getElementById("errorLongDesc");
+          for (var span of container.querySelectorAll("span.hostname")) {
+            span.textContent = document.location.hostname;
+          }
+        }
+
         addDomainErrorLinks();
       }
 
       /* Try to preserve the links contained in the error description, like
          the error code.
 
          Also, in the case of SSL error pages about domain mismatch, see if
          we can hyperlink the user to the correct site.  We don't want
@@ -488,16 +499,17 @@
         <h1 id="et_unsafeContentType">&unsafeContentType.title;</h1>
         <h1 id="et_nssFailure2">&nssFailure2.title;</h1>
         <h1 id="et_nssBadCert">&certerror.longpagetitle1;</h1>
         <h1 id="et_cspBlocked">&cspBlocked.title;</h1>
         <h1 id="et_remoteXUL">&remoteXUL.title;</h1>
         <h1 id="et_corruptedContentError">&corruptedContentError.title;</h1>
         <h1 id="et_sslv3Used">&sslv3Used.title;</h1>
         <h1 id="et_weakCryptoUsed">&weakCryptoUsed.title;</h1>
+        <h1 id="et_inadequateSecurityError">&inadequateSecurityError.title;</h1>
       </div>
       <div id="errorDescriptionsContainer">
         <div id="ed_generic">&generic.longDesc;</div>
         <div id="ed_dnsNotFound">&dnsNotFound.longDesc;</div>
         <div id="ed_fileNotFound">&fileNotFound.longDesc;</div>
         <div id="ed_fileAccessDenied">&fileAccessDenied.longDesc;</div>
         <div id="ed_malformedURI">&malformedURI.longDesc;</div>
         <div id="ed_unknownProtocolFound">&unknownProtocolFound.longDesc;</div>
@@ -516,16 +528,17 @@
         <div id="ed_unsafeContentType">&unsafeContentType.longDesc;</div>
         <div id="ed_nssFailure2">&nssFailure2.longDesc2;</div>
         <div id="ed_nssBadCert">&certerror.introPara;</div>
         <div id="ed_cspBlocked">&cspBlocked.longDesc;</div>
         <div id="ed_remoteXUL">&remoteXUL.longDesc;</div>
         <div id="ed_corruptedContentError">&corruptedContentError.longDesc;</div>
         <div id="ed_sslv3Used">&sslv3Used.longDesc2;</div>
         <div id="ed_weakCryptoUsed">&weakCryptoUsed.longDesc2;</div>
+        <div id="ed_inadequateSecurityError">&inadequateSecurityError.longDesc;</div>
       </div>
     </div>
 
     <!-- PAGE CONTAINER (for styling purposes only) -->
     <div id="errorPageContainer" class="container">
 
       <!-- Error Title -->
       <div class="title">
rename from browser/base/content/browser-eme.js
rename to browser/base/content/browser-media.js
--- a/browser/base/content/browser-eme.js
+++ b/browser/base/content/browser-media.js
@@ -176,12 +176,89 @@ var gEMEHandler = {
   },
   QueryInterface: XPCOMUtils.generateQI([Ci.nsIMessageListener])
 };
 
 XPCOMUtils.defineLazyGetter(gEMEHandler, "_brandShortName", function() {
   return document.getElementById("bundle_brand").getString("brandShortName");
 });
 
+let gDecoderDoctorHandler = {
+  shouldShowLearnMoreButton() {
+    return AppConstants.platform == "win";
+  },
+
+  getLabelForNotificationBox(type) {
+    if (type == "adobe-cdm-not-found" &&
+        AppConstants.platform == "win") {
+      if (AppConstants.isPlatformAndVersionAtMost("win", "5.9")) {
+        // We supply our own Learn More button so we don't need to populate the message here.
+        return gNavigatorBundle.getFormattedString("emeNotifications.drmContentDisabled.message", [""]);
+      }
+      return gNavigatorBundle.getString("decoder.noCodecs.message");
+    }
+    if (type == "adobe-cdm-not-activated" &&
+        AppConstants.platform == "win") {
+      if (AppConstants.isPlatformAndVersionAtMost("win", "5.9")) {
+        return gNavigatorBundle.getString("decoder.noCodecsXP.message");
+      }
+      return gNavigatorBundle.getString("decoder.noCodecs.message");
+    }
+    if (type == "platform-decoder-not-found") {
+      if (AppConstants.isPlatformAndVersionAtLeast("win", "6")) {
+        return gNavigatorBundle.getString("decoder.noHWAcceleration.message");
+      }
+      if (AppConstants.platform == "linux") {
+        return gNavigatorBundle.getString("decoder.noCodecsLinux.message");
+      }
+    }
+    return "";
+  },
+
+  receiveMessage({target: browser, data: data}) {
+    let box = gBrowser.getNotificationBox(browser);
+    let notificationId = "decoder-doctor-notification";
+    if (box.getNotificationWithValue(notificationId)) {
+      return;
+    }
+
+    let parsedData;
+    try {
+      parsedData = JSON.parse(data);
+    } catch (ex) {
+      Cu.reportError("Malformed Decoder Doctor message with data: " + data);
+      return;
+    }
+    let {type} = parsedData;
+    type = type.toLowerCase();
+    let title = gDecoderDoctorHandler.getLabelForNotificationBox(type);
+    if (!title) {
+      return;
+    }
+
+    let buttons = [];
+    if (gDecoderDoctorHandler.shouldShowLearnMoreButton()) {
+      buttons.push({
+        label: gNavigatorBundle.getString("decoder.noCodecs.button"),
+        accessKey: gNavigatorBundle.getString("decoder.noCodecs.accesskey"),
+        callback() {
+          let baseURL = Services.urlFormatter.formatURLPref("app.support.baseURL");
+          openUILinkIn(baseURL + "fix-video-audio-problems-firefox-windows", "tab");
+        }
+      });
+    }
+
+    box.appendNotification(
+      title,
+      notificationId,
+      "", // This uses the info icon as specified below.
+      box.PRIORITY_INFO_LOW,
+      buttons
+    );
+  },
+}
+
+window.messageManager.addMessageListener("DecoderDoctor:Notification", gDecoderDoctorHandler);
 window.messageManager.addMessageListener("EMEVideo:ContentMediaKeysRequest", gEMEHandler);
 window.addEventListener("unload", function() {
   window.messageManager.removeMessageListener("EMEVideo:ContentMediaKeysRequest", gEMEHandler);
+  window.messageManager.removeMessageListener("DecoderDoctor:Notification", gDecoderDoctorHandler);
 }, false);
--- a/browser/base/content/global-scripts.inc
+++ b/browser/base/content/global-scripts.inc
@@ -10,21 +10,21 @@
 <script type="application/javascript" src="chrome://browser/content/customizableui/panelUI.js"/>
 <script type="application/javascript" src="chrome://global/content/inlineSpellCheckUI.js"/>
 <script type="application/javascript" src="chrome://global/content/viewSourceUtils.js"/>
 
 <script type="application/javascript" src="chrome://browser/content/browser-addons.js"/>
 <script type="application/javascript" src="chrome://browser/content/browser-ctrlTab.js"/>
 <script type="application/javascript" src="chrome://browser/content/browser-customization.js"/>
 <script type="application/javascript" src="chrome://browser/content/browser-devedition.js"/>
-<script type="application/javascript" src="chrome://browser/content/browser-eme.js"/>
 <script type="application/javascript" src="chrome://browser/content/browser-feeds.js"/>
 <script type="application/javascript" src="chrome://browser/content/browser-fullScreen.js"/>
 <script type="application/javascript" src="chrome://browser/content/browser-fullZoom.js"/>
 <script type="application/javascript" src="chrome://browser/content/browser-gestureSupport.js"/>
+<script type="application/javascript" src="chrome://browser/content/browser-media.js"/>
 <script type="application/javascript" src="chrome://browser/content/browser-places.js"/>
 <script type="application/javascript" src="chrome://browser/content/browser-plugins.js"/>
 <script type="application/javascript" src="chrome://browser/content/browser-refreshblocker.js"/>
 #ifdef MOZ_SAFE_BROWSING
 <script type="application/javascript" src="chrome://browser/content/browser-safebrowsing.js"/>
 #endif
 <script type="application/javascript" src="chrome://browser/content/browser-sidebar.js"/>
 <script type="application/javascript" src="chrome://browser/content/browser-social.js"/>
--- a/browser/base/content/tab-content.js
+++ b/browser/base/content/tab-content.js
@@ -627,30 +627,33 @@ var DOMFullscreenHandler = {
     if (!content) {
       return null;
     }
     return content.QueryInterface(Ci.nsIInterfaceRequestor)
                   .getInterface(Ci.nsIDOMWindowUtils);
   },
 
   receiveMessage: function(aMessage) {
+    let windowUtils = this._windowUtils;
     switch(aMessage.name) {
       case "DOMFullscreen:Entered": {
-        if (!this._windowUtils.handleFullscreenRequests() &&
+        this._lastTransactionId = windowUtils.lastTransactionId;
+        if (!windowUtils.handleFullscreenRequests() &&
             !content.document.fullscreenElement) {
           // If we don't actually have any pending fullscreen request
           // to handle, neither we have been in fullscreen, tell the
           // parent to just exit.
           sendAsyncMessage("DOMFullscreen:Exit");
         }
         break;
       }
       case "DOMFullscreen:CleanUp": {
-        if (this._windowUtils) {
-          this._windowUtils.exitFullscreen();
+        if (windowUtils) {
+          this._lastTransactionId = windowUtils.lastTransactionId;
+          windowUtils.exitFullscreen();
         }
         this._fullscreenDoc = null;
         break;
       }
     }
   },
 
   handleEvent: function(aEvent) {
@@ -677,18 +680,22 @@ var DOMFullscreenHandler = {
           // If we receive any fullscreen change event, and find we are
           // actually not in fullscreen, also ask the parent to exit to
           // ensure that the parent always exits fullscreen when we do.
           sendAsyncMessage("DOMFullscreen:Exit");
         }
         break;
       }
       case "MozAfterPaint": {
-        removeEventListener("MozAfterPaint", this);
-        sendAsyncMessage("DOMFullscreen:Painted");
+        // Only send Painted signal after we actually finish painting
+        // the transition for the fullscreen change.
+        if (aEvent.transactionId > this._lastTransactionId) {
+          removeEventListener("MozAfterPaint", this);
+          sendAsyncMessage("DOMFullscreen:Painted");
+        }
         break;
       }
     }
   }
 };
 DOMFullscreenHandler.init();
 
 var RefreshBlocker = {
--- a/browser/base/content/test/general/browser.ini
+++ b/browser/base/content/test/general/browser.ini
@@ -278,16 +278,18 @@ skip-if = os == 'win'
 [browser_contextmenu.js]
 tags = fullscreen
 skip-if = toolkit == "gtk2" || toolkit == "gtk3" # disabled on Linux due to bug 513558
 [browser_contextmenu_input.js]
 skip-if = toolkit == "gtk2" || toolkit == "gtk3" # disabled on Linux due to bug 513558
 [browser_ctrlTab.js]
 [browser_datachoices_notification.js]
 skip-if = !datareporting
+[browser_decoderDoctor.js]
+skip-if = os == "mac" # decoder doctor isn't implemented on osx
 [browser_devedition.js]
 [browser_devices_get_user_media.js]
 skip-if = buildapp == 'mulet' || (os == "linux" && debug) # linux: bug 976544
 [browser_devices_get_user_media_about_urls.js]
 skip-if = e10s && debug
 [browser_devices_get_user_media_in_frame.js]
 [browser_discovery.js]
 [browser_double_close_tab.js]
new file mode 100644
--- /dev/null
+++ b/browser/base/content/test/general/browser_decoderDoctor.js
@@ -0,0 +1,94 @@
+"use strict";
+
+function* test_decoder_doctor_notification(type, notificationMessage, options) {
+  yield BrowserTestUtils.withNewTab({ gBrowser }, function*(browser) {
+    let awaitNotificationBar =
+      BrowserTestUtils.waitForNotificationBar(gBrowser, browser, "decoder-doctor-notification");
+
+    yield ContentTask.spawn(browser, type, function*(type) {
+      Services.obs.notifyObservers(content.window,
+                                   "decoder-doctor-notification",
+                                   JSON.stringify({type: type}));
+    });
+
+    let notification;
+    try {
+      notification = yield awaitNotificationBar;
+    } catch (ex) {
+      ok(false, ex);
+      return;
+    }
+    ok(notification, "Got decoder-doctor-notification notification");
+
+    is(notification.getAttribute("label"), notificationMessage,
+      "notification message should match expectation");
+    let button = notification.childNodes[0];
+    if (options && options.noLearnMoreButton) {
+      ok(!button, "There should not be a Learn More button");
+      return;
+    }
+
+    is(button.getAttribute("label"), gNavigatorBundle.getString("decoder.noCodecs.button"),
+      "notification button should be 'Learn more'");
+    is(button.getAttribute("accesskey"), gNavigatorBundle.getString("decoder.noCodecs.accesskey"),
+      "notification button should have accesskey");
+
+    let baseURL = Services.urlFormatter.formatURLPref("app.support.baseURL");
+    let url = baseURL + "fix-video-audio-problems-firefox-windows";
+    let awaitNewTab = BrowserTestUtils.waitForNewTab(gBrowser, url);
+    button.click();
+    let sumoTab = yield awaitNewTab;
+    yield BrowserTestUtils.removeTab(sumoTab);
+  });
+}
+
+add_task(function* test_adobe_cdm_not_found() {
+  // This is only sent on Windows.
+  if (AppConstants.platform != "win") {
+    return;
+  }
+
+  let message;
+  if (AppConstants.isPlatformAndVersionAtMost("win", "5.9")) {
+    message = gNavigatorBundle.getFormattedString("emeNotifications.drmContentDisabled.message", [""]);
+  } else {
+    message = gNavigatorBundle.getString("decoder.noCodecs.message");
+  }
+
+  yield test_decoder_doctor_notification("adobe-cdm-not-found", message);
+});
+
+add_task(function* test_adobe_cdm_not_activated() {
+  // This is only sent on Windows.
+  if (AppConstants.platform != "win") {
+    return;
+  }
+
+  let message;
+  if (AppConstants.isPlatformAndVersionAtMost("win", "5.9")) {
+    message = gNavigatorBundle.getString("decoder.noCodecsXP.message");
+  } else {
+    message = gNavigatorBundle.getString("decoder.noCodecs.message");
+  }
+
+  yield test_decoder_doctor_notification("adobe-cdm-not-activated", message);
+});
+
+add_task(function* test_platform_decoder_not_found() {
+  // Not sent on Windows XP.
+  if (AppConstants.isPlatformAndVersionAtMost("win", "5.9")) {
+    return;
+  }
+
+  let message;
+  let isLinux = AppConstants.platform == "linux";
+  if (isLinux) {
+    message = gNavigatorBundle.getString("decoder.noCodecsLinux.message");
+  } else {
+    message = gNavigatorBundle.getString("decoder.noHWAcceleration.message");
+  }
+
+  yield test_decoder_doctor_notification("platform-decoder-not-found",
+                                         message,
+                                         {noLearnMoreButton: isLinux});
+});
--- a/browser/base/content/test/general/browser_misused_characters_in_strings.js
+++ b/browser/base/content/test/general/browser_misused_characters_in_strings.js
@@ -24,16 +24,20 @@ let gWhitelist = [{
     file: "netError.dtd",
     key: "weakCryptoAdvanced.longDesc",
     type: "single-quote"
   }, {
     file: "netError.dtd",
     key: "weakCryptoAdvanced.override",
     type: "single-quote"
   }, {
+    file: "netError.dtd",
+    key: "inadequateSecurityError.longDesc",
+    type: "single-quote"
+  }, {
     file: "phishing-afterload-warning-message.dtd",
     key: "safeb.blocked.malwarePage.shortDesc",
     type: "single-quote"
   }, {
     file: "phishing-afterload-warning-message.dtd",
     key: "safeb.blocked.unwantedPage.shortDesc",
     type: "single-quote"
   }, {
--- a/browser/base/jar.mn
+++ b/browser/base/jar.mn
@@ -74,22 +74,22 @@ browser.jar:
 *       content/browser/browser.css                   (content/browser.css)
         content/browser/browser.js                    (content/browser.js)
 *       content/browser/browser.xul                   (content/browser.xul)
         content/browser/browser-addons.js             (content/browser-addons.js)
         content/browser/browser-ctrlTab.js            (content/browser-ctrlTab.js)
         content/browser/browser-customization.js      (content/browser-customization.js)
         content/browser/browser-data-submission-info-bar.js (content/browser-data-submission-info-bar.js)
         content/browser/browser-devedition.js         (content/browser-devedition.js)
-        content/browser/browser-eme.js                (content/browser-eme.js)
         content/browser/browser-feeds.js              (content/browser-feeds.js)
         content/browser/browser-fullScreen.js         (content/browser-fullScreen.js)
         content/browser/browser-fullZoom.js           (content/browser-fullZoom.js)
         content/browser/browser-fxaccounts.js         (content/browser-fxaccounts.js)
         content/browser/browser-gestureSupport.js     (content/browser-gestureSupport.js)
+        content/browser/browser-media.js              (content/browser-media.js)
         content/browser/browser-places.js             (content/browser-places.js)
         content/browser/browser-plugins.js            (content/browser-plugins.js)
         content/browser/browser-refreshblocker.js     (content/browser-refreshblocker.js)
 #ifdef MOZ_SAFE_BROWSING
         content/browser/browser-safebrowsing.js       (content/browser-safebrowsing.js)
 #endif
         content/browser/browser-sidebar.js            (content/browser-sidebar.js)
         content/browser/browser-social.js             (content/browser-social.js)
--- a/browser/components/extensions/ext-tabs.js
+++ b/browser/components/extensions/ext-tabs.js
@@ -29,32 +29,32 @@ function getSender(context, target, send
     let tabbrowser = target.ownerDocument.defaultView.gBrowser;
     if (!tabbrowser) {
       return;
     }
     let tab = tabbrowser.getTabForBrowser(target);
 
     sender.tab = TabManager.convert(context.extension, tab);
   } else if ("tabId" in sender) {
-    // The message came from an ExtensionPage. In that case, it should
+    // The message came from an ExtensionContext. In that case, it should
     // include a tabId property (which is filled in by the page-open
     // listener below).
     sender.tab = TabManager.convert(context.extension, TabManager.getTab(sender.tabId));
     delete sender.tabId;
   }
 }
 
-// WeakMap[ExtensionPage -> {tab, parentWindow}]
+// WeakMap[ExtensionContext -> {tab, parentWindow}]
 var pageDataMap = new WeakMap();
 
 /* eslint-disable mozilla/balanced-listeners */
 // This listener fires whenever an extension page opens in a tab
 // (either initiated by the extension or the user). Its job is to fill
 // in some tab-specific details and keep data around about the
-// ExtensionPage.
+// ExtensionContext.
 extensions.on("page-load", (type, page, params, sender, delegate) => {
   if (params.type == "tab" || params.type == "popup") {
     let browser = params.docShell.chromeEventHandler;
 
     let parentWindow = browser.ownerDocument.defaultView;
     page.windowId = WindowManager.getId(parentWindow);
 
     let tab = null;
--- a/browser/components/extensions/ext-utils.js
+++ b/browser/components/extensions/ext-utils.js
@@ -274,17 +274,17 @@ class BasePopup {
       this.browser.addEventListener("load", loadListener, true);
     }).then(() => {
       let {contentWindow} = this.browser;
 
       contentWindow.QueryInterface(Ci.nsIInterfaceRequestor)
                    .getInterface(Ci.nsIDOMWindowUtils)
                    .allowScriptsToClose();
 
-      this.context = new ExtensionPage(this.extension, {
+      this.context = new ExtensionContext(this.extension, {
         type: "popup",
         contentWindow,
         uri: popupURI,
         docShell: this.browser.docShell,
       });
 
       GlobalManager.injectInDocShell(this.browser.docShell, this.extension, this.context);
       this.browser.setAttribute("src", this.context.uri.spec);
--- a/browser/components/extensions/test/browser/browser_ext_popup_api_injection.js
+++ b/browser/components/extensions/test/browser/browser_ext_popup_api_injection.js
@@ -11,23 +11,23 @@ add_task(function* testPageActionPopup()
         "default_popup": `${BASE}/file_popup_api_injection_a.html`,
       },
       "page_action": {
         "default_popup": `${BASE}/file_popup_api_injection_b.html`,
       },
     },
 
     files: {
-      "popup-a.html": String.raw`<html><head><meta charset="utf-8"><script type="application/javascript">
-        browser.test.sendMessage("from-popup-a");
-      </script></head></html>`,
+      "popup-a.html": `<html><head><meta charset="utf-8">
+                       <script type="application/javascript" src="popup-a.js"></script></head></html>`,
+      "popup-a.js": 'browser.test.sendMessage("from-popup-a");',
 
-      "popup-b.html": String.raw`<html><head><meta charset="utf-8"><script type="application/javascript">
-        browser.test.sendMessage("from-popup-b");
-      </script></head></html>`,
+      "popup-b.html": `<html><head><meta charset="utf-8">
+                       <script type="application/javascript" src="popup-b.js"></script></head></html>`,
+      "popup-b.js": 'browser.test.sendMessage("from-popup-b");',
     },
 
     background: function() {
       let tabId;
       browser.tabs.query({active: true, currentWindow: true}, tabs => {
         tabId = tabs[0].id;
         browser.pageAction.show(tabId);
         browser.test.sendMessage("ready");
--- a/browser/components/feeds/WebContentConverter.js
+++ b/browser/components/feeds/WebContentConverter.js
@@ -144,64 +144,79 @@ const Utils = {
       let baseURI = aContentWindow.document.baseURIObject;
       uri = this.makeURI(aURIString, null, baseURI);
     } catch (ex) {
       throw NS_ERROR_DOM_SYNTAX_ERR;
     }
 
     // For security reasons we reject non-http(s) urls (see bug 354316),
     // we may need to revise this once we support more content types
-    // XXX this should be a "security exception" according to spec, but that
-    // isn't defined yet.
-    if (uri.scheme != "http" && uri.scheme != "https")
-      throw("Permission denied to add " + uri.spec + " as a content or protocol handler");
+    if (uri.scheme != "http" && uri.scheme != "https") {
+      throw this.getSecurityError(
+        "Permission denied to add " + uri.spec + " as a content or protocol handler",
+        aContentWindow);
+    }
 
     // We also reject handlers registered from a different host (see bug 402287)
     // The pref allows us to test the feature
     let pb = Services.prefs;
     if (!pb.getBoolPref(PREF_ALLOW_DIFFERENT_HOST) &&
         (!["http:", "https:"].includes(aContentWindow.location.protocol) ||
          aContentWindow.location.hostname != uri.host)) {
-      throw("Permission denied to add " + uri.spec + " as a content or protocol handler");
+      throw this.getSecurityError(
+        "Permission denied to add " + uri.spec + " as a content or protocol handler",
+        aContentWindow);
     }
 
     // If the uri doesn't contain '%s', it won't be a good handler
     if (uri.spec.indexOf("%s") < 0)
       throw NS_ERROR_DOM_SYNTAX_ERR;
 
     return uri;
   },
 
   // NB: Throws if aProtocol is not allowed.
-  checkProtocolHandlerAllowed(aProtocol, aURIString) {
+  checkProtocolHandlerAllowed(aProtocol, aURIString, aWindowOrNull) {
     // First, check to make sure this isn't already handled internally (we don't
     // want to let them take over, say "chrome").
     let handler = Services.io.getProtocolHandler(aProtocol);
     if (!(handler instanceof Ci.nsIExternalProtocolHandler)) {
       // This is handled internally, so we don't want them to register
-      // XXX this should be a "security exception" according to spec, but that
-      // isn't defined yet.
-      throw(`Permission denied to add ${aURIString} as a protocol handler`);
+      throw this.getSecurityError(
+        `Permission denied to add ${aURIString} as a protocol handler`,
+        aWindowOrNull);
     }
 
     // check if it is in the black list
     let pb = Services.prefs;
     let allowed;
     try {
       allowed = pb.getBoolPref(PREF_HANDLER_EXTERNAL_PREFIX + "." + aProtocol);
     }
     catch (e) {
       allowed = pb.getBoolPref(PREF_HANDLER_EXTERNAL_PREFIX + "-default");
     }
     if (!allowed) {
-      // XXX this should be a "security exception" according to spec
-      throw(`Not allowed to register a protocol handler for ${aProtocol}`);
+      throw this.getSecurityError(
+        `Not allowed to register a protocol handler for ${aProtocol}`,
+        aWindowOrNull);
     }
   },
 
+  // Return a SecurityError exception from the given Window if one is given.  If
+  // none is given, just return the given error string, for lack of anything
+  // better.
+  getSecurityError(errorString, aWindowOrNull) {
+    if (!aWindowOrNull) {
+      return errorString;
+    }
+
+    return new aWindowOrNull.DOMException(errorString, "SecurityError");
+  },
+
   /**
    * Mappings from known feed types to our internal content type.
    */
   _mappings: {
     "application/rss+xml": TYPE_MAYBE_FEED,
     "application/atom+xml": TYPE_MAYBE_FEED,
   },
 
@@ -399,17 +414,18 @@ WebContentConverterRegistrar.prototype =
       // Inside the private browsing mode, we don't want to alert the user to save
       // a protocol handler.  We log it to the error console so that web developers
       // would have some way to tell what's going wrong.
       Services.console.
       logStringMessage("Web page denied access to register a protocol handler inside private browsing mode");
       return;
     }
 
-    Utils.checkProtocolHandlerAllowed(aProtocol, aURIString);
+    Utils.checkProtocolHandlerAllowed(aProtocol, aURIString,
+                                      haveWindow ? aBrowserOrWindow : null);
 
     // Now Ask the user and provide the proper callback
     let message = this._getFormattedString("addProtocolHandler",
                                            [aTitle, uri.host, aProtocol]);
 
     let notificationIcon = uri.prePath + "/favicon.ico";
     let notificationValue = "Protocol Registration: " + aProtocol;
     let addButton = {
@@ -454,36 +470,43 @@ WebContentConverterRegistrar.prototype =
   /**
    * See nsIWebContentHandlerRegistrar
    * If a DOM window is provided, then the request came from content, so we
    * prompt the user to confirm the registration.
    */
   registerContentHandler(aContentType, aURIString, aTitle, aWindowOrBrowser) {
     LOG("registerContentHandler(" + aContentType + "," + aURIString + "," + aTitle + ")");
 
+    // Make sure to do our URL checks up front, before our content type check,
+    // just like the WebContentConverterRegistrarContent does.
+    let haveWindow = aWindowOrBrowser &&
+                     (aWindowOrBrowser instanceof Ci.nsIDOMWindow);
+    let uri;
+    if (haveWindow) {
+      uri = Utils.checkAndGetURI(aURIString, aWindowOrBrowser);
+    } else if (aWindowOrBrowser) {
+      // uri was vetted in the content process.
+      uri = Utils.makeURI(aURIString, null);
+    }
+
     // We only support feed types at present.
-    // XXX this should be a "security exception" according to spec, but that
-    // isn't defined yet.
     let contentType = Utils.resolveContentType(aContentType);
-    if (contentType != TYPE_MAYBE_FEED)
+    // XXX We should be throwing a Utils.getSecurityError() here in at least
+    // some cases.  See bug 1266492.
+    if (contentType != TYPE_MAYBE_FEED) {
       return;
+    }
 
     if (aWindowOrBrowser) {
-      let haveWindow = (aWindowOrBrowser instanceof Ci.nsIDOMWindow);
-      let uri;
       let notificationBox;
       if (haveWindow) {
-        uri = Utils.checkAndGetURI(aURIString, aWindowOrBrowser);
-
         let browserWindow = this._getBrowserWindowForContentWindow(aWindowOrBrowser);
         let browserElement = this._getBrowserForContentWindow(browserWindow, aWindowOrBrowser);
         notificationBox = browserElement.getTabBrowser().getNotificationBox(browserElement);
       } else {
-        // uri was vetted in the content process.
-        uri = Utils.makeURI(aURIString, null);
         notificationBox = aWindowOrBrowser.getTabBrowser()
                                           .getNotificationBox(aWindowOrBrowser);
       }
 
       this._appendFeedReaderNotification(uri, aTitle, notificationBox);
     }
     else {
       this._registerContentHandler(contentType, aURIString, aTitle);
@@ -987,16 +1010,18 @@ WebContentConverterRegistrarContent.prot
     let messageManager = aBrowserOrWindow.QueryInterface(Ci.nsIInterfaceRequestor)
                                          .getInterface(Ci.nsIWebNavigation)
                                          .QueryInterface(Ci.nsIDocShell)
                                          .QueryInterface(Ci.nsIInterfaceRequestor)
                                          .getInterface(Ci.nsITabChild)
                                          .messageManager;
 
     let uri = Utils.checkAndGetURI(aURIString, aBrowserOrWindow);
+    // XXX We should be throwing a Utils.getSecurityError() here in at least
+    // some cases.  See bug 1266492.
     if (Utils.resolveContentType(aContentType) != TYPE_MAYBE_FEED) {
       return;
     }
 
     messageManager.sendAsyncMessage("WCCR:registerContentHandler",
                                     { contentType: aContentType,
                                       uri: uri.spec,
                                       title: aTitle });
@@ -1007,17 +1032,17 @@ WebContentConverterRegistrarContent.prot
     let messageManager = aBrowserOrWindow.QueryInterface(Ci.nsIInterfaceRequestor)
                                          .getInterface(Ci.nsIWebNavigation)
                                          .QueryInterface(Ci.nsIDocShell)
                                          .QueryInterface(Ci.nsIInterfaceRequestor)
                                          .getInterface(Ci.nsITabChild)
                                          .messageManager;
 
     let uri = Utils.checkAndGetURI(aURIString, aBrowserOrWindow);
-    Utils.checkProtocolHandlerAllowed(aProtocol, aURIString);
+    Utils.checkProtocolHandlerAllowed(aProtocol, aURIString, aBrowserOrWindow);
 
     messageManager.sendAsyncMessage("WCCR:registerProtocolHandler",
                                     { protocol: aProtocol,
                                       uri: uri.spec,
                                       title: aTitle });
   },
 
   /**
--- a/browser/components/preferences/in-content/subdialogs.js
+++ b/browser/components/preferences/in-content/subdialogs.js
@@ -285,18 +285,18 @@ var gSubDialog = {
     // or their presence will restrict the contents of the <browser>
     // from resizing to a smaller size.
     frame.style.removeProperty("width");
     frame.style.removeProperty("height");
 
     let docEl = frame.contentDocument.documentElement;
     let persistedAttributes = docEl.getAttribute("persist");
     if (!persistedAttributes ||
-        (!persistedAttributes.contains("width") &&
-         !persistedAttributes.contains("height"))) {
+        (!persistedAttributes.includes("width") &&
+         !persistedAttributes.includes("height"))) {
       return;
     }
 
     for (let mutation of mutations) {
       if (mutation.attributeName == "width") {
         docEl.setAttribute("width", docEl.scrollWidth);
       } else if (mutation.attributeName == "height") {
         docEl.setAttribute("height", docEl.scrollHeight);
--- a/browser/config/tooltool-manifests/win32/releng.manifest
+++ b/browser/config/tooltool-manifests/win32/releng.manifest
@@ -16,16 +16,16 @@
 {
 "size": 167175,
 "digest": "0b71a936edf5bd70cf274aaa5d7abc8f77fe8e7b5593a208f805cc9436fac646b9c4f0b43c2b10de63ff3da671497d35536077ecbc72dba7f8159a38b580f831",
 "algorithm": "sha512",
 "filename": "sccache.tar.bz2",
 "unpack": true
 },
 {
-"version": "Visual Studio 2015 Update 1 / SDK 10.0.10586.0",
-"size": 330570496,
-"digest": "0379fd087705f54aeb335449e6c623cd550b656d7110acafd1e5b315e1fc9272b7cdd1e37f99d575b16ecba4e8e4fe3af965967a3944c023b83caf68fa684888",
+"version": "Visual Studio 2015 Update 2 / SDK 10.0.10586.0/212",
+"size": 332343834,
+"digest": "55814aaabcd4aa51fe85918ec02a8c29bc067d41ee79ddcfd628daaba5a06d4241a73a51bf5a8bc69cc762b52551009f44b05e65682c45b4684c17fb2d017c2c",
 "algorithm": "sha512",
-"filename": "vs2015u1.zip",
+"filename": "vs2015u2.zip",
 "unpack": true
 }
 ]
--- a/browser/config/tooltool-manifests/win64/releng.manifest
+++ b/browser/config/tooltool-manifests/win64/releng.manifest
@@ -17,16 +17,16 @@
 {
 "size": 167175,
 "digest": "0b71a936edf5bd70cf274aaa5d7abc8f77fe8e7b5593a208f805cc9436fac646b9c4f0b43c2b10de63ff3da671497d35536077ecbc72dba7f8159a38b580f831",
 "algorithm": "sha512",
 "filename": "sccache.tar.bz2",
 "unpack": true
 },
 {
-"version": "Visual Studio 2015 Update 1 / SDK 10.0.10586.0",
-"size": 330570496,
-"digest": "0379fd087705f54aeb335449e6c623cd550b656d7110acafd1e5b315e1fc9272b7cdd1e37f99d575b16ecba4e8e4fe3af965967a3944c023b83caf68fa684888",
+"version": "Visual Studio 2015 Update 2 / SDK 10.0.10586.0/212",
+"size": 332343834,
+"digest": "55814aaabcd4aa51fe85918ec02a8c29bc067d41ee79ddcfd628daaba5a06d4241a73a51bf5a8bc69cc762b52551009f44b05e65682c45b4684c17fb2d017c2c",
 "algorithm": "sha512",
-"filename": "vs2015u1.zip",
+"filename": "vs2015u2.zip",
 "unpack": true
 }
 ]
--- a/browser/locales/en-US/chrome/overrides/appstrings.properties
+++ b/browser/locales/en-US/chrome/overrides/appstrings.properties
@@ -36,8 +36,9 @@ deceptiveBlocked=This web page at %S has
 forbiddenBlocked=The site at %S has been blocked by your browser configuration.
 cspBlocked=This page has a content security policy that prevents it from being loaded in this way.
 corruptedContentError=The page you are trying to view cannot be shown because an error in the data transmission was detected.
 remoteXUL=This page uses an unsupported technology that is no longer available by default in Firefox.
 ## LOCALIZATION NOTE (sslv3Used) - Do not translate "%S".
 sslv3Used=Firefox cannot guarantee the safety of your data on %S because it uses SSLv3, a broken security protocol.
 ## LOCALIZATION NOTE (weakCryptoUsed) - Do not translate "%S".
 weakCryptoUsed=The owner of %S has configured their website improperly. To protect your information from being stolen, Firefox has not connected to this website.
+inadequateSecurityError=The website tried to negotiate an inadequate level of security.
--- a/browser/locales/en-US/chrome/overrides/netError.dtd
+++ b/browser/locales/en-US/chrome/overrides/netError.dtd
@@ -190,9 +190,14 @@ was trying to connect. -->
 <!ENTITY weakCryptoAdvanced.longDesc "<span class='hostname'></span> uses security technology that is outdated and vulnerable to attack. An attacker could easily reveal information which you thought to be safe.">
 <!ENTITY weakCryptoAdvanced.override "(Not secure) Try loading <span class='hostname'></span> using outdated security">
 
 <!ENTITY certerror.pagetitle1  "Insecure Connection">
 <!ENTITY certerror.whatShouldIDo.badStsCertExplanation "This site uses HTTP
 Strict Transport Security (HSTS) to specify that &brandShortName; only connect
 to it securely. As a result, it is not possible to add an exception for this
 certificate.">
-<!ENTITY certerror.copyToClipboard.label "Copy text to clipboard">
\ No newline at end of file
+<!ENTITY certerror.copyToClipboard.label "Copy text to clipboard">
+
+<!ENTITY inadequateSecurityError.title "Your connection is not secure">
+<!-- LOCALIZATION NOTE (inadequateSecurityError.longDesc) - Do not translate
+     "NS_ERROR_NET_INADEQUATE_SECURITY". -->
+<!ENTITY inadequateSecurityError.longDesc "<p><span class='hostname'></span> uses security technology that is outdated and vulnerable to attack. An attacker could easily reveal information which you thought to be safe. The website administrator will need to fix the server first before you can visit the site.</p><p>Error code: NS_ERROR_NET_INADEQUATE_SECURITY</p>">
--- a/browser/modules/ContentObservers.jsm
+++ b/browser/modules/ContentObservers.jsm
@@ -22,16 +22,24 @@ Cu.import("resource://gre/modules/Servic
 var gEMEUIObserver = function(subject, topic, data) {
   let win = subject.top;
   let mm = getMessageManagerForWindow(win);
   if (mm) {
     mm.sendAsyncMessage("EMEVideo:ContentMediaKeysRequest", data);
   }
 };
 
+var gDecoderDoctorObserver = function(subject, topic, data) {
+  let win = subject.top;
+  let mm = getMessageManagerForWindow(win);
+  if (mm) {
+    mm.sendAsyncMessage("DecoderDoctor:Notification", data);
+  }
+};
+
 function getMessageManagerForWindow(aContentWindow) {
   let ir = aContentWindow.QueryInterface(Ci.nsIInterfaceRequestor)
                          .getInterface(Ci.nsIDocShell)
                          .sameTypeRootTreeItem
                          .QueryInterface(Ci.nsIInterfaceRequestor);
   try {
     // If e10s is disabled, this throws NS_NOINTERFACE for closed tabs.
     return ir.getInterface(Ci.nsIContentFrameMessageManager);
@@ -39,8 +47,9 @@ function getMessageManagerForWindow(aCon
     if (e.result == Cr.NS_NOINTERFACE) {
       return null;
     }
     throw e;
   }
 }
 
 Services.obs.addObserver(gEMEUIObserver, "mediakeys-request", false);
+Services.obs.addObserver(gDecoderDoctorObserver, "decoder-doctor-notification", false);
--- a/build/autoconf/android.m4
+++ b/build/autoconf/android.m4
@@ -220,18 +220,16 @@ if test -n "$MOZ_NATIVE_DEVICES" ; then
 fi
 
 ])
 
 AC_DEFUN([MOZ_ANDROID_GOOGLE_CLOUD_MESSAGING],
 [
 
 if test -n "$MOZ_ANDROID_GCM" ; then
-    AC_SUBST(MOZ_ANDROID_GCM)
-
     MOZ_ANDROID_AAR(play-services-base, 8.1.0, google, com/google/android/gms)
     MOZ_ANDROID_AAR(play-services-basement, 8.1.0, google, com/google/android/gms)
     MOZ_ANDROID_AAR(play-services-gcm, 8.1.0, google, com/google/android/gms)
 fi
 
 ])
 
 AC_DEFUN([MOZ_ANDROID_INSTALL_TRACKING],
--- a/build/docs/toolchains.rst
+++ b/build/docs/toolchains.rst
@@ -40,22 +40,25 @@ Next, install Visual Studio 2015 Communi
 found at https://www.visualstudio.com/en-us/products/visual-studio-community-vs.aspx.
 Be sure to follow these install instructions:
 
 1. Choose a ``Custom`` installation and click ``Next``
 2. Select ``Programming Languages`` -> ``Visual C++`` (make sure all sub items are
    selected)
 3. Under ``Windows and Web Development`` uncheck everything except
    ``Universal Windows App Development Tools`` and the items under it
-   (should be ``Tools (1.2)...`` and the ``Windows 10 SDK``).
+   (should be ``Tools (1.3.1)...`` and the ``Windows 10 SDK``).
 
 Once Visual Studio 2015 Community has been installed, from a checkout
-of mozilla-central, run the following to produce a ZIP archive::
+of mozilla-central, run something like the following to produce a ZIP
+archive::
 
-   $ ./mach python build/windows_toolchain.py create-zip vs2015.zip
+   $ ./mach python build/windows_toolchain.py create-zip vs2015u2
+
+The produced archive will be the argument to ``create-zip`` + ``.zip``.
 
 Firefox for Android with Gradle
 ===============================
 
 To build Firefox for Android with Gradle in automation, archives
 containing both the Gradle executable and a Maven repository
 comprising the exact build dependencies are produced and uploaded to
 an internal Mozilla server.  The build automation will download,
--- a/build/moz.configure/init.configure
+++ b/build/moz.configure/init.configure
@@ -649,19 +649,19 @@ set_define('MOZ_BUILD_APP', build_projec
 add_old_configure_assignment('MOZ_BUILD_APP', build_project)
 
 
 # set RELEASE_BUILD and NIGHTLY_BUILD variables depending on the cycle we're in
 # The logic works like this:
 # - if we have "a1" in GRE_MILESTONE, we're building Nightly (define NIGHTLY_BUILD)
 # - otherwise, if we have "a" in GRE_MILESTONE, we're building Nightly or Aurora
 # - otherwise, we're building Release/Beta (define RELEASE_BUILD)
-@depends(check_build_environment)
+@depends(check_build_environment, '--help')
 @imports(_from='__builtin__', _import='open')
-def milestone(build_env):
+def milestone(build_env, _):
     milestone_path = os.path.join(build_env.topsrcdir,
                                   'config',
                                   'milestone.txt')
     with open(milestone_path, 'r') as fh:
         milestone = fh.read().splitlines()[-1]
 
     is_nightly = is_release = None
 
@@ -679,16 +679,49 @@ set_config('NIGHTLY_BUILD', delayed_geta
 set_define('NIGHTLY_BUILD', delayed_getattr(milestone, 'is_nightly'))
 add_old_configure_assignment('NIGHTLY_BUILD',
                              delayed_getattr(milestone, 'is_nightly'))
 set_config('RELEASE_BUILD', delayed_getattr(milestone, 'is_release'))
 set_define('RELEASE_BUILD', delayed_getattr(milestone, 'is_release'))
 add_old_configure_assignment('RELEASE_BUILD',
                              delayed_getattr(milestone, 'is_release'))
 
+# A template providing a shorthand for tying an environment-set option to
+# the corresponding variable in set_config, where the value of that variable
+# passed to set_config only depends on the provided value and default.
+# If required, the set_as_define and set_for_old_configure arguments
+# will additionally cause the variable to be set using set_define and
+# add_old_configure_assignment. util.configure would be an appropriate place for
+# this, but it uses add_old_configure_assignment, which is defined in this file.
+@template
+def env_flag(env=None, set_for_old_configure=False, set_as_define=False,
+             **kwargs):
+
+    if not env:
+        configure_error("An env_flag must be passed a variable name to set.")
+
+    opt = option(env=env, **kwargs)
+
+    @depends(opt.option)
+    def option_implementation(value):
+        if value:
+            if len(value):
+                return value
+            return bool(value)
+
+    set_config(env, option_implementation)
+    if set_as_define:
+        set_define(env, option_implementation)
+    if set_for_old_configure:
+        add_old_configure_assignment(env, option_implementation)
+
+# milestone.is_nightly corresponds to cases NIGHTLY_BUILD is set.
+@depends(milestone, '--help')
+def enabled_in_nightly(milestone, _):
+    return milestone.is_nightly
 
 # Set the MOZ_CONFIGURE_OPTIONS variable with all the options that
 # were passed somehow (environment, command line, mozconfig)
 @depends(mozconfig_options)
 @imports(_from='mozbuild.shellutil', _import='quote')
 @imports('__sandbox__')
 def all_configure_options(_):
     result = []
--- a/build/moz.configure/old.configure
+++ b/build/moz.configure/old.configure
@@ -153,17 +153,16 @@ def old_configure_options(*options):
                    *options)
 
 
 @old_configure_options(
     '--cache-file',
     '--enable-accessibility',
     '--enable-address-sanitizer',
     '--enable-alsa',
-    '--enable-android-apz',
     '--enable-android-omx',
     '--enable-android-resource-constrained',
     '--enable-approximate-location',
     '--enable-b2g-bt',
     '--enable-b2g-camera',
     '--enable-b2g-ril',
     '--enable-bundled-fonts',
     '--enable-clang-plugin',
@@ -198,17 +197,16 @@ def old_configure_options(*options):
     '--enable-ion',
     '--enable-ios-target',
     '--enable-ipdl-tests',
     '--enable-jitspew',
     '--enable-libjpeg-turbo',
     '--enable-libproxy',
     '--enable-llvm-hacks',
     '--enable-logrefcnt',
-    '--enable-macos-target',
     '--enable-maintenance-service',
     '--enable-media-navigator',
     '--enable-memory-sanitizer',
     '--enable-mobile-optimize',
     '--enable-more-deterministic',
     '--enable-mozril-geoloc',
     '--enable-necko-protocols',
     '--enable-necko-wifi',
--- a/build/moz.configure/toolchain.configure
+++ b/build/moz.configure/toolchain.configure
@@ -65,16 +65,39 @@ add_old_configure_assignment('YASM', hav
 @depends('--disable-compile-environment', build_project, '--help')
 def android_ndk_include(compile_env, build_project, _):
     if compile_env and build_project in ('mobile/android', 'js'):
         return 'android-ndk.configure'
 
 include(android_ndk_include)
 
 
+# MacOS deployment target version
+# ==============================================================
+# This needs to happen before any compilation test is done.
+
+option('--enable-macos-target', env='MACOSX_DEPLOYMENT_TARGET', nargs=1,
+       default='10.6', help='Set the minimum MacOS version needed at runtime')
+
+@depends('--enable-macos-target', target)
+@imports(_from='os', _import='environ')
+def macos_target(value, target):
+    if value and target.os == 'OSX':
+        # Ensure every compiler process we spawn uses this value.
+        environ['MACOSX_DEPLOYMENT_TARGET'] = value[0]
+        return value[0]
+    if value and value.origin != 'default':
+        die('--enable-macos-target cannot be used when targeting %s',
+            target.os)
+
+
+set_config('MACOSX_DEPLOYMENT_TARGET', macos_target)
+add_old_configure_assignment('MACOSX_DEPLOYMENT_TARGET', macos_target)
+
+
 # Compiler wrappers
 # ==============================================================
 # Normally, we'd use js_option and automatically have those variables
 # propagated to js/src, but things are complicated by possible additional
 # wrappers in CC/CXX, and by other subconfigures that do not handle those
 # options and do need CC/CXX altered.
 option('--with-compiler-wrapper', env='COMPILER_WRAPPER', nargs=1,
        help='Enable compiling with wrappers such as distcc and ccache')
--- a/build/win32/mozconfig.vs2015-win64
+++ b/build/win32/mozconfig.vs2015-win64
@@ -1,11 +1,11 @@
 if [ -z "${VSPATH}" ]; then
     TOOLTOOL_DIR=${TOOLTOOL_DIR:-$topsrcdir}
-    VSPATH="$(cd ${TOOLTOOL_DIR} && pwd)/vs2015u1"
+    VSPATH="$(cd ${TOOLTOOL_DIR} && pwd)/vs2015u2"
 fi
 
 export WINDOWSSDKDIR="${VSPATH}/SDK"
 export WIN32_REDIST_DIR="${VSPATH}/VC/redist/x86/Microsoft.VC140.CRT"
 export WIN_UCRT_REDIST_DIR="${VSPATH}/SDK/Redist/ucrt/DLLs/x86"
 
 export PATH="${VSPATH}/VC/bin/amd64_x86:${VSPATH}/VC/bin/amd64:${VSPATH}/VC/bin:${VSPATH}/SDK/bin/x86:${VSPATH}/SDK/bin/x64:${VSPATH}/DIASDK/bin:${PATH}"
 export PATH="${VSPATH}/VC/redist/x86/Microsoft.VC140.CRT:${VSPATH}/VC/redist/x64/Microsoft.VC140.CRT:${VSPATH}/SDK/Redist/ucrt/DLLs/x86:${VSPATH}/SDK/Redist/ucrt/DLLs/x64:${PATH}"
--- a/build/win64/mozconfig.vs2015
+++ b/build/win64/mozconfig.vs2015
@@ -1,11 +1,11 @@
 if [ -z "${VSPATH}" ]; then
     TOOLTOOL_DIR=${TOOLTOOL_DIR:-$topsrcdir}
-    VSPATH="$(cd ${TOOLTOOL_DIR} && pwd)/vs2015u1"
+    VSPATH="$(cd ${TOOLTOOL_DIR} && pwd)/vs2015u2"
 fi
 
 export WINDOWSSDKDIR="${VSPATH}/SDK"
 export WIN32_REDIST_DIR=${VSPATH}/VC/redist/x64/Microsoft.VC140.CRT
 export WIN_UCRT_REDIST_DIR="${VSPATH}/SDK/Redist/ucrt/DLLs/x64"
 
 export PATH="${VSPATH}/VC/bin/amd64:${VSPATH}/VC/bin:${VSPATH}/SDK/bin/x64:${VSPATH}/VC/redist/x64/Microsoft.VC140.CRT:${VSPATH}/SDK/Redist/ucrt/DLLs/x64:${VSPATH}/DIASDK/bin/amd64:${PATH}"
 
--- a/build/windows_toolchain.py
+++ b/build/windows_toolchain.py
@@ -232,23 +232,23 @@ def write_zip(zip_path, prefix=None):
             sha256_path = mozpath.join(prefix, sha256_path)
 
         zip.add(sdk_path, SDK_RELEASE.encode('utf-8'))
         zip.add(sha256_path, sha256_manifest)
 
 
 if __name__ == '__main__':
     if len(sys.argv) != 3:
-        print('usage: %s create-zip <filename.zip>' % sys.argv[0])
+        print('usage: %s create-zip <path-prefix>' % sys.argv[0])
         sys.exit(1)
 
     assert sys.argv[1] == 'create-zip'
-    destzip = sys.argv[2]
-    # TODO make prefix a CLI argument
-    write_zip(destzip, prefix='vs2015u1')
+    prefix = os.path.basename(sys.argv[2])
+    destzip = '%s.zip' % sys.argv[2]
+    write_zip(destzip, prefix=prefix)
 
     sha1 = hashlib.sha1()
     sha256 = hashlib.sha256()
     sha512 = hashlib.sha512()
 
     with open(destzip, 'rb') as fh:
         data = fh.read()
         sha1.update(data)
--- a/caps/BasePrincipal.cpp
+++ b/caps/BasePrincipal.cpp
@@ -511,16 +511,23 @@ BasePrincipal::GetAppId(uint32_t* aAppId
     return NS_OK;
   }
 
   *aAppId = AppId();
   return NS_OK;
 }
 
 NS_IMETHODIMP
+BasePrincipal::GetAddonId(nsAString& aAddonId)
+{
+  aAddonId.Assign(mOriginAttributes.mAddonId);
+  return NS_OK;
+}
+
+NS_IMETHODIMP
 BasePrincipal::GetUserContextId(uint32_t* aUserContextId)
 {
   *aUserContextId = UserContextId();
   return NS_OK;
 }
 
 NS_IMETHODIMP
 BasePrincipal::GetIsInIsolatedMozBrowserElement(bool* aIsInIsolatedMozBrowserElement)
--- a/caps/BasePrincipal.h
+++ b/caps/BasePrincipal.h
@@ -210,16 +210,17 @@ public:
   NS_IMETHOD GetIsCodebasePrincipal(bool* aResult) override;
   NS_IMETHOD GetIsExpandedPrincipal(bool* aResult) override;
   NS_IMETHOD GetIsSystemPrincipal(bool* aResult) override;
   NS_IMETHOD GetJarPrefix(nsACString& aJarPrefix) final;
   NS_IMETHOD GetOriginAttributes(JSContext* aCx, JS::MutableHandle<JS::Value> aVal) final;
   NS_IMETHOD GetOriginSuffix(nsACString& aOriginSuffix) final;
   NS_IMETHOD GetAppStatus(uint16_t* aAppStatus) final;
   NS_IMETHOD GetAppId(uint32_t* aAppStatus) final;
+  NS_IMETHOD GetAddonId(nsAString& aAddonId) final;
   NS_IMETHOD GetIsInIsolatedMozBrowserElement(bool* aIsInIsolatedMozBrowserElement) final;
   NS_IMETHOD GetUnknownAppId(bool* aUnknownAppId) final;
   NS_IMETHOD GetUserContextId(uint32_t* aUserContextId) final;
 
   virtual bool IsOnCSSUnprefixingWhitelist() override { return false; }
 
   virtual bool IsCodebasePrincipal() const { return false; };
 
--- a/caps/nsIAddonPolicyService.idl
+++ b/caps/nsIAddonPolicyService.idl
@@ -10,23 +10,57 @@
 /**
  * This interface allows the security manager to query custom per-addon security
  * policy.
  */
 [scriptable,uuid(8a034ef9-9d14-4c5d-8319-06c1ab574baa)]
 interface nsIAddonPolicyService : nsISupports
 {
   /**
+   * Returns the base content security policy, which is applied to all
+   * extension documents, in addition to any custom policies.
+   */
+  readonly attribute AString baseCSP;
+
+  /**
+   * Returns the default content security policy which applies to extension
+   * documents which do not specify any custom policies.
+   */
+  readonly attribute AString defaultCSP;
+
+  /**
+   * Returns the content security policy which applies to documents belonging
+   * to the extension with the given ID. This may be either a custom policy,
+   * if one was supplied, or the default policy if one was not.
+   */
+  AString getAddonCSP(in AString aAddonId);
+
+  /**
    * Returns true if unprivileged code associated with the given addon may load
    * data from |aURI|.
    */
   boolean addonMayLoadURI(in AString aAddonId, in nsIURI aURI);
 
   /**
    * Returns true if a given extension:// URI is web-accessible.
    */
   boolean extensionURILoadableByAnyone(in nsIURI aURI);
 
   /**
    * Maps an extension URI to the ID of the addon it belongs to.
    */
   AString extensionURIToAddonId(in nsIURI aURI);
 };
+
+/**
+ * This interface exposes functionality related to add-on content policy
+ * enforcement.
+ */
+[scriptable,uuid(7a4fe60b-9131-45f5-83f3-dc63b5d71a5d)]
+interface nsIAddonContentPolicy : nsISupports
+{
+  /**
+   * Checks a custom content security policy string, to ensure that it meets
+   * minimum security requirements. Returns null for valid policies, or a
+   * string describing the error for invalid policies.
+   */
+  AString validateAddonCSP(in AString aPolicyString);
+};
--- a/caps/nsIPrincipal.idl
+++ b/caps/nsIPrincipal.idl
@@ -291,16 +291,21 @@ interface nsIPrincipal : nsISerializable
      * origin as the app.
      *
      * If you're doing a security check based on appId, you must check
      * appStatus as well.
      */
     [infallible] readonly attribute unsigned long appId;
 
     /**
+     * Gets the ID of the add-on this principal belongs to.
+     */
+    readonly attribute AString addonId;
+
+    /**
      * Gets the id of the user context this principal is inside.  If this
      * principal is inside the default userContext, this returns
      * nsIScriptSecurityManager::DEFAULT_USER_CONTEXT_ID.
      */
     [infallible] readonly attribute unsigned long userContextId;
 
     /**
      * Returns true iff the principal is inside an isolated mozbrowser element.
--- a/devtools/client/aboutdebugging/aboutdebugging.css
+++ b/devtools/client/aboutdebugging/aboutdebugging.css
@@ -45,16 +45,21 @@ button {
 }
 
 /* Targets */
 
 .targets {
   margin-bottom: 35px;
 }
 
+.target-list {
+  margin: 0;
+  padding: 0;
+}
+
 .target-container {
   margin-top: 5px;
   min-height: 34px;
   display: flex;
   flex-direction: row;
   align-items: baseline;
 }
 
--- a/devtools/client/aboutdebugging/components/aboutdebugging.js
+++ b/devtools/client/aboutdebugging/components/aboutdebugging.js
@@ -18,21 +18,23 @@ loader.lazyGetter(this, "AddonsTab",
 loader.lazyGetter(this, "WorkersTab",
   () => createFactory(require("./workers-tab")));
 
 const Strings = Services.strings.createBundle(
   "chrome://devtools/locale/aboutdebugging.properties");
 
 const tabs = [{
   id: "addons",
+  panelId: "tab-addons",
   name: Strings.GetStringFromName("addons"),
   icon: "chrome://devtools/skin/images/debugging-addons.svg",
   component: AddonsTab
 }, {
   id: "workers",
+  panelId: "tab-workers",
   name: Strings.GetStringFromName("workers"),
   icon: "chrome://devtools/skin/images/debugging-workers.svg",
   component: WorkersTab
 }];
 
 const defaultTabId = "addons";
 
 module.exports = createClass({
@@ -78,13 +80,13 @@ module.exports = createClass({
     let { selectedTabId } = this.state;
     let selectTab = this.selectTab;
 
     let selectedTab = tabs.find(t => t.id == selectedTabId);
 
     return dom.div({ className: "app" },
       TabMenu({ tabs, selectedTabId, selectTab }),
       dom.div({ className: "main-content" },
-        selectedTab.component({ client })
+        selectedTab.component({ client, id: selectedTab.panelId })
       )
     );
   }
 });
--- a/devtools/client/aboutdebugging/components/addon-target.js
+++ b/devtools/client/aboutdebugging/components/addon-target.js
@@ -36,17 +36,17 @@ module.exports = createClass({
       throw new Error(
         "Error reloading addon " + target.addonID + ": " + error);
     });
   },
 
   render() {
     let { target, debugDisabled } = this.props;
 
-    return dom.div({ className: "target-container" },
+    return dom.li({ className: "target-container" },
       dom.img({
         className: "target-icon",
         role: "presentation",
         src: target.icon
       }),
       dom.div({ className: "target" },
         dom.div({ className: "target-name" }, target.name)
       ),
--- a/devtools/client/aboutdebugging/components/addons-tab.js
+++ b/devtools/client/aboutdebugging/components/addons-tab.js
@@ -101,23 +101,23 @@ module.exports = createClass({
   /**
    * Mandatory callback as AddonManager listener.
    */
   onDisabled() {
     this.updateAddonsList();
   },
 
   render() {
-    let { client } = this.props;
+    let { client, id } = this.props;
     let { debugDisabled, extensions: targets } = this.state;
     let name = Strings.GetStringFromName("extensions");
     let targetClass = AddonTarget;
 
     return dom.div({
-      id: "tab-addons",
+      id: id,
       className: "tab",
       role: "tabpanel",
       "aria-labelledby": "tab-addons-header-name"
     },
     TabHeader({
       id: "tab-addons-header-name",
       name: Strings.GetStringFromName("addons")
     }),
--- a/devtools/client/aboutdebugging/components/tab-menu-entry.js
+++ b/devtools/client/aboutdebugging/components/tab-menu-entry.js
@@ -9,23 +9,32 @@ const { createClass, DOM: dom } =
 
 module.exports = createClass({
   displayName: "TabMenuEntry",
 
   onClick() {
     this.props.selectTab(this.props.tabId);
   },
 
+  onKeyUp(event) {
+    if ([" ", "Enter"].includes(event.key)) {
+      this.props.selectTab(this.props.tabId);
+    }
+  },
+
   render() {
-    let { icon, name, selected } = this.props;
+    let { panelId, icon, name, selected } = this.props;
 
     // Here .category, .category-icon, .category-name classnames are used to
     // apply common styles defined.
     let className = "category" + (selected ? " selected" : "");
     return dom.div({
       "aria-selected": selected,
+      "aria-controls": panelId,
       className,
       onClick: this.onClick,
+      onKeyUp: this.onKeyUp,
+      tabIndex: "0",
       role: "tab" },
     dom.img({ className: "category-icon", src: icon, role: "presentation" }),
     dom.div({ className: "category-name" }, name));
   }
 });
--- a/devtools/client/aboutdebugging/components/tab-menu.js
+++ b/devtools/client/aboutdebugging/components/tab-menu.js
@@ -8,17 +8,19 @@ const { createClass, createFactory, DOM:
   require("devtools/client/shared/vendor/react");
 const TabMenuEntry = createFactory(require("./tab-menu-entry"));
 
 module.exports = createClass({
   displayName: "TabMenu",
 
   render() {
     let { tabs, selectedTabId, selectTab } = this.props;
-    let tabLinks = tabs.map(({ id, name, icon }) => {
+    let tabLinks = tabs.map(({ panelId, id, name, icon }) => {
       let selected = id == selectedTabId;
-      return TabMenuEntry({ tabId: id, name, icon, selected, selectTab });
+      return TabMenuEntry({
+        tabId: id, panelId, name, icon, selected, selectTab
+      });
     });
 
     // "categories" id used for styling purposes
-    return dom.div({ id: "categories" }, tabLinks);
+    return dom.div({ id: "categories", role: "tablist" }, tabLinks);
   },
 });
--- a/devtools/client/aboutdebugging/components/target-list.js
+++ b/devtools/client/aboutdebugging/components/target-list.js
@@ -20,15 +20,15 @@ module.exports = createClass({
 
   render() {
     let { client, debugDisabled, targetClass } = this.props;
     let targets = this.props.targets.sort(LocaleCompare).map(target => {
       return targetClass({ client, target, debugDisabled });
     });
 
     return dom.div({ id: this.props.id, className: "targets" },
-      dom.h4(null, this.props.name),
+      dom.h2(null, this.props.name),
       targets.length > 0 ?
-        targets :
+        dom.ul({ className: "target-list" }, targets) :
         dom.p(null, Strings.GetStringFromName("nothing"))
     );
   },
 });
--- a/devtools/client/aboutdebugging/components/worker-target.js
+++ b/devtools/client/aboutdebugging/components/worker-target.js
@@ -20,17 +20,17 @@ module.exports = createClass({
   debug() {
     let { client, target } = this.props;
     debugWorker(client, target.workerActor);
   },
 
   render() {
     let { target, debugDisabled } = this.props;
 
-    return dom.div({ className: "target-container" },
+    return dom.li({ className: "target-container" },
       dom.img({
         className: "target-icon",
         role: "presentation",
         src: target.icon
       }),
       dom.div({ className: "target" },
         dom.div({ className: "target-name" }, target.name)
       ),
--- a/devtools/client/aboutdebugging/components/workers-tab.js
+++ b/devtools/client/aboutdebugging/components/workers-tab.js
@@ -95,21 +95,21 @@ module.exports = createClass({
       // find the scriptSpec.
       workers.service = workers.service.filter(reg => !!reg.url);
 
       this.setState({ workers });
     });
   },
 
   render() {
-    let { client } = this.props;
+    let { client, id } = this.props;
     let { workers } = this.state;
 
     return dom.div({
-      id: "tab-workers",
+      id: id,
       className: "tab",
       role: "tabpanel",
       "aria-labelledby": "tab-workers-header-name"
     },
     TabHeader({
       id: "tab-workers-header-name",
       name: Strings.GetStringFromName("workers")
     }),
--- a/devtools/client/aboutdebugging/test/.eslintrc
+++ b/devtools/client/aboutdebugging/test/.eslintrc
@@ -1,4 +1,22 @@
 {
   // Extend from the shared list of defined globals for mochitests.
-  "extends": "../../../.eslintrc.mochitests"
+  "extends": "../../../.eslintrc.mochitests",
+  // All globals made available in aboutdebugging head.js file.
+  "globals": {
+    "AddonManager": true,
+    "addTab": true,
+    "assertHasTarget": true,
+    "CHROME_ROOT": true,
+    "closeAboutDebugging": true,
+    "getServiceWorkerList": true,
+    "getSupportsFile": true,
+    "installAddon": true,
+    "openAboutDebugging": true,
+    "removeTab": true,
+    "uninstallAddon": true,
+    "unregisterServiceWorker": true,
+    "waitForInitialAddonList": true,
+    "waitForMutation": true,
+    "waitForServiceWorkerRegistered": true
+  }
 }
--- a/devtools/client/aboutdebugging/test/browser_service_workers.js
+++ b/devtools/client/aboutdebugging/test/browser_service_workers.js
@@ -21,17 +21,17 @@ add_task(function* () {
     ]};
     SpecialPowers.pushPrefEnv(options, done);
   });
 
   let { tab, document } = yield openAboutDebugging("workers");
 
   let swTab = yield addTab(TAB_URL);
 
-  let serviceWorkersElement = document.getElementById("service-workers");
+  let serviceWorkersElement = getServiceWorkerList(document);
 
   yield waitForMutation(serviceWorkersElement, { childList: true });
 
   // Check that the service worker appears in the UI
   let names = [...document.querySelectorAll("#service-workers .target-name")];
   names = names.map(element => element.textContent);
   ok(names.includes(SERVICE_WORKER),
     "The service worker url appears in the list: " + names);
--- a/devtools/client/aboutdebugging/test/browser_service_workers_push.js
+++ b/devtools/client/aboutdebugging/test/browser_service_workers_push.js
@@ -25,17 +25,17 @@ add_task(function* () {
       ["dom.serviceWorkers.testing.enabled", true],
     ]};
     SpecialPowers.pushPrefEnv(options, done);
   });
 
   let { tab, document } = yield openAboutDebugging("workers");
 
   // Listen for mutations in the service-workers list.
-  let serviceWorkersElement = document.getElementById("service-workers");
+  let serviceWorkersElement = getServiceWorkerList(document);
   let onMutation = waitForMutation(serviceWorkersElement, { childList: true });
 
   // Open a tab that registers a push service worker.
   let swTab = yield addTab(TAB_URL);
 
   info("Make the test page notify us when the service worker sends a message.");
   let frameScript = function() {
     let win = content.wrappedJSObject;
--- a/devtools/client/aboutdebugging/test/browser_service_workers_start.js
+++ b/devtools/client/aboutdebugging/test/browser_service_workers_start.js
@@ -27,17 +27,17 @@ add_task(function* () {
       ["dom.serviceWorkers.idle_extended_timeout", SW_TIMEOUT],
     ]};
     SpecialPowers.pushPrefEnv(options, done);
   });
 
   let { tab, document } = yield openAboutDebugging("workers");
 
   // Listen for mutations in the service-workers list.
-  let serviceWorkersElement = document.getElementById("service-workers");
+  let serviceWorkersElement = getServiceWorkerList(document);
   let onMutation = waitForMutation(serviceWorkersElement, { childList: true });
 
   // Open a tab that registers an empty service worker.
   let swTab = yield addTab(TAB_URL);
 
   // Wait for the service-workers list to update.
   yield onMutation;
 
--- a/devtools/client/aboutdebugging/test/browser_service_workers_timeout.js
+++ b/devtools/client/aboutdebugging/test/browser_service_workers_timeout.js
@@ -24,17 +24,17 @@ add_task(function* () {
     ]};
     SpecialPowers.pushPrefEnv(options, done);
   });
 
   let { tab, document } = yield openAboutDebugging("workers");
 
   let swTab = yield addTab(TAB_URL);
 
-  let serviceWorkersElement = document.getElementById("service-workers");
+  let serviceWorkersElement = getServiceWorkerList(document);
   yield waitForMutation(serviceWorkersElement, { childList: true });
 
   assertHasTarget(true, document, "service-workers", SERVICE_WORKER);
 
   // Ensure that the registration resolved before trying to connect to the sw
   yield waitForServiceWorkerRegistered(swTab);
   ok(true, "Service worker registration resolved");
 
--- a/devtools/client/aboutdebugging/test/browser_service_workers_unregister.js
+++ b/devtools/client/aboutdebugging/test/browser_service_workers_unregister.js
@@ -1,13 +1,12 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 /* eslint-disable mozilla/no-cpows-in-tests */
-/* global sendAsyncMessage */
 
 "use strict";
 
 // Test that clicking on the unregister link in the Service Worker details works
 // as intended in about:debugging.
 // It should unregister the service worker, which should trigger an update of
 // the displayed list of service workers.
 
@@ -27,17 +26,17 @@ add_task(function* () {
       ["dom.serviceWorkers.testing.enabled", true],
     ]};
     SpecialPowers.pushPrefEnv(options, done);
   });
 
   let { tab, document } = yield openAboutDebugging("workers");
 
   // Listen for mutations in the service-workers list.
-  let serviceWorkersElement = document.getElementById("service-workers");
+  let serviceWorkersElement = getServiceWorkerList(document);
   let onMutation = waitForMutation(serviceWorkersElement, { childList: true });
 
   // Open a tab that registers an empty service worker.
   let swTab = yield addTab(TAB_URL);
 
   // Wait for the service workers-list to update.
   yield onMutation;
 
--- a/devtools/client/aboutdebugging/test/head.js
+++ b/devtools/client/aboutdebugging/test/head.js
@@ -1,15 +1,15 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 /* eslint-env browser */
 /* eslint-disable mozilla/no-cpows-in-tests */
 /* exported openAboutDebugging, closeAboutDebugging, installAddon,
-   uninstallAddon, waitForMutation, assertHasTarget,
+   uninstallAddon, waitForMutation, assertHasTarget, getServiceWorkerList,
    waitForInitialAddonList, waitForServiceWorkerRegistered,
    unregisterServiceWorker */
 /* global sendAsyncMessage */
 
 "use strict";
 
 var { utils: Cu, classes: Cc, interfaces: Ci } = Components;
 
@@ -88,24 +88,46 @@ function removeTab(tab, win) {
 function getSupportsFile(path) {
   let cr = Cc["@mozilla.org/chrome/chrome-registry;1"]
     .getService(Ci.nsIChromeRegistry);
   let uri = Services.io.newURI(CHROME_ROOT + path, null, null);
   let fileurl = cr.convertChromeURL(uri);
   return fileurl.QueryInterface(Ci.nsIFileURL);
 }
 
+/**
+ * Depending on whether there are addons installed, return either a target list
+ * element or its container.
+ * @param  {DOMDocument}  document   #addons section container document
+ * @return {DOMNode}                 target list or container element
+ */
+function getAddonList(document) {
+  return document.querySelector("#addons .target-list") ||
+    document.querySelector("#addons .targets");
+}
+
+/**
+ * Depending on whether there are service workers installed, return either a
+ * target list element or its container.
+ * @param  {DOMDocument}  document   #service-workers section container document
+ * @return {DOMNode}                 target list or container element
+ */
+function getServiceWorkerList(document) {
+  return document.querySelector("#service-workers .target-list") ||
+    document.querySelector("#service-workers.targets");
+}
+
 function* installAddon(document, path, name, evt) {
   // Mock the file picker to select a test addon
   let MockFilePicker = SpecialPowers.MockFilePicker;
   MockFilePicker.init(null);
   let file = getSupportsFile(path);
   MockFilePicker.returnFiles = [file.file];
 
-  let addonList = document.querySelector("#addons .targets");
+  let addonList = getAddonList(document);
   let addonListMutation = waitForMutation(addonList, { childList: true });
 
   // Wait for a message sent by the addon's bootstrap.js file
   let onAddonInstalled = new Promise(done => {
     Services.obs.addObserver(function listener() {
       Services.obs.removeObserver(listener, evt);
 
       done();
@@ -121,17 +143,17 @@ function* installAddon(document, path, n
   yield addonListMutation;
   let names = [...addonList.querySelectorAll(".target-name")];
   names = names.map(element => element.textContent);
   ok(names.includes(name),
     "The addon name appears in the list of addons: " + names);
 }
 
 function* uninstallAddon(document, addonId, addonName) {
-  let addonList = document.querySelector("#addons .targets");
+  let addonList = getAddonList(document);
   let addonListMutation = waitForMutation(addonList, { childList: true });
 
   // Now uninstall this addon
   yield new Promise(done => {
     AddonManager.getAddonByID(addonId, addon => {
       let listener = {
         onUninstalled: function(uninstalledAddon) {
           if (uninstalledAddon != addon) {
@@ -158,17 +180,17 @@ function* uninstallAddon(document, addon
 
 /**
  * Returns a promise that will resolve when the add-on list has been updated.
  *
  * @param {Node} document
  * @return {Promise}
  */
 function waitForInitialAddonList(document) {
-  const addonListContainer = document.querySelector("#addons .targets");
+  const addonListContainer = getAddonList(document);
   let addonCount = addonListContainer.querySelectorAll(".target");
   addonCount = addonCount ? [...addonCount].length : -1;
   info("Waiting for add-ons to load. Current add-on count: " + addonCount);
 
   // This relies on the network speed of the actor responding to the
   // listAddons() request and also the speed of openAboutDebugging().
   let result;
   if (addonCount > 0) {
--- a/devtools/client/framework/test/browser_toolbox_options_enable_serviceworkers_testing.js
+++ b/devtools/client/framework/test/browser_toolbox_options_enable_serviceworkers_testing.js
@@ -55,16 +55,20 @@ function testSelectTool(aToolbox) {
 function register() {
   return executeInContent("devtools:sw-test:register");
 }
 
 function unregister(swr) {
   return executeInContent("devtools:sw-test:unregister");
 }
 
+function registerAndUnregisterInFrame() {
+  return executeInContent("devtools:sw-test:iframe:register-and-unregister");
+}
+
 function testRegisterFails(data) {
   is(data.success, false, "Register should fail with security error");
   return promise.resolve();
 }
 
 function toggleServiceWorkersTestingCheckbox() {
   let panel = toolbox.getCurrentPanel();
   let cbx = panel.panelDoc.getElementById(ELEMENT_ID);
@@ -102,16 +106,18 @@ function testRegisterSuccesses(data) {
 function start() {
   register()
     .then(testRegisterFails)
     .then(toggleServiceWorkersTestingCheckbox)
     .then(reload)
     .then(register)
     .then(testRegisterSuccesses)
     .then(unregister)
+    .then(registerAndUnregisterInFrame)
+    .then(testRegisterSuccesses)
     // Workers should be turned back off when we closes the toolbox
     .then(toolbox.destroy.bind(toolbox))
     .then(reload)
     .then(register)
     .then(testRegisterFails)
     .catch(function(e) {
       ok(false, "Some test failed with error " + e);
     }).then(finishUp);
--- a/devtools/client/framework/test/browser_toolbox_options_enable_serviceworkers_testing_frame_script.js
+++ b/devtools/client/framework/test/browser_toolbox_options_enable_serviceworkers_testing_frame_script.js
@@ -19,8 +19,28 @@ addMessageListener("devtools:sw-test:reg
 addMessageListener("devtools:sw-test:unregister", function(msg) {
   content.navigator.serviceWorker.getRegistration().then(swr => {
     swr.unregister().then(result => {
       sendAsyncMessage("devtools:sw-test:unregister",
                        {success: result ? true : false});
     });
   });
 });
+
+addMessageListener("devtools:sw-test:iframe:register-and-unregister", function(msg) {
+  var frame = content.document.createElement("iframe");
+  frame.addEventListener("load", function onLoad() {
+    frame.removeEventListener("load", onLoad);
+    frame.contentWindow.navigator.serviceWorker.register("serviceworker.js")
+      .then(swr => {
+        return swr.unregister();
+      }).then(_ => {
+        frame.remove();
+        sendAsyncMessage("devtools:sw-test:iframe:register-and-unregister",
+                         {success: true});
+      }).catch(error => {
+        sendAsyncMessage("devtools:sw-test:iframe:register-and-unregister",
+                         {success: false});
+      });
+  });
+  frame.src = "browser_toolbox_options_enabled_serviceworkers_testing.html";
+  content.document.body.appendChild(frame);
+});
--- a/docshell/base/nsDocShell.cpp
+++ b/docshell/base/nsDocShell.cpp
@@ -5052,16 +5052,21 @@ nsDocShell::DisplayLoadError(nsresult aE
       case NS_ERROR_CORRUPTED_CONTENT:
         // Broken Content Detected. e.g. Content-MD5 check failure.
         error.AssignLiteral("corruptedContentError");
         break;
       case NS_ERROR_INTERCEPTION_FAILED:
         // ServiceWorker intercepted request, but something went wrong.
         error.AssignLiteral("corruptedContentError");
         break;
+      case NS_ERROR_NET_INADEQUATE_SECURITY:
+        // Server negotiated bad TLS for HTTP/2.
+        error.AssignLiteral("inadequateSecurityError");
+        addHostPort = true;
+        break;
       default:
         break;
     }
   }
 
   // Test if the error should be displayed
   if (error.IsEmpty()) {
     return NS_OK;
@@ -7791,16 +7796,17 @@ nsDocShell::EndPageLoad(nsIWebProgress* 
                aStatus == NS_ERROR_OFFLINE ||
                aStatus == NS_ERROR_MALWARE_URI ||
                aStatus == NS_ERROR_PHISHING_URI ||
                aStatus == NS_ERROR_UNWANTED_URI ||
                aStatus == NS_ERROR_FORBIDDEN_URI ||
                aStatus == NS_ERROR_UNSAFE_CONTENT_TYPE ||
                aStatus == NS_ERROR_REMOTE_XUL ||
                aStatus == NS_ERROR_INTERCEPTION_FAILED ||
+               aStatus == NS_ERROR_NET_INADEQUATE_SECURITY ||
                NS_ERROR_GET_MODULE(aStatus) == NS_ERROR_MODULE_SECURITY) {
       // Errors to be shown for any frame
       DisplayLoadError(aStatus, url, nullptr, aChannel);
     } else if (aStatus == NS_ERROR_DOCUMENT_NOT_CACHED) {
       // Non-caching channels will simply return NS_ERROR_OFFLINE.
       // Caching channels would have to look at their flags to work
       // out which error to return. Or we can fix up the error here.
       if (!(mLoadType & LOAD_CMD_HISTORY)) {
--- a/docshell/resources/content/netError.xhtml
+++ b/docshell/resources/content/netError.xhtml
@@ -172,16 +172,27 @@
           addDomainErrorLink();
         }
         else {
           // Remove the override block for non-certificate errors.  CSS-hiding
           // isn't good enough here, because of bug 39098
           var secOverride = document.getElementById("securityOverrideDiv");
           secOverride.parentNode.removeChild(secOverride);
         }
+
+        if (err == "inadequateSecurityError") {
+          // Remove the "Try again" button for HTTP/2 inadequate security as it
+          // is useless.
+          document.getElementById("errorTryAgain").style.display = "none";
+
+          var container = document.getElementById("errorLongDesc");
+          for (var span of container.querySelectorAll("span.hostname")) {
+            span.textContent = document.location.hostname;
+          }
+        }
       }
 
       function showSecuritySection() {
         // Swap link out, content in
         document.getElementById('securityOverrideContent').style.display = '';
         document.getElementById('securityOverrideLink').style.display = 'none';
       }
 
@@ -292,16 +303,17 @@
         <h1 id="et_proxyConnectFailure">&proxyConnectFailure.title;</h1>
         <h1 id="et_contentEncodingError">&contentEncodingError.title;</h1>
         <h1 id="et_unsafeContentType">&unsafeContentType.title;</h1>
         <h1 id="et_nssFailure2">&nssFailure2.title;</h1>
         <h1 id="et_nssBadCert">&nssBadCert.title;</h1>
         <h1 id="et_cspBlocked">&cspBlocked.title;</h1>
         <h1 id="et_remoteXUL">&remoteXUL.title;</h1>
         <h1 id="et_corruptedContentError">&corruptedContentError.title;</h1>
+        <h1 id="et_inadequateSecurityError">&inadequateSecurityError.title;</h1>
       </div>
       <div id="errorDescriptionsContainer">
         <div id="ed_generic">&generic.longDesc;</div>
         <div id="ed_dnsNotFound">&dnsNotFound.longDesc;</div>
         <div id="ed_fileNotFound">&fileNotFound.longDesc;</div>
         <div id="ed_fileAccessDenied">&fileAccessDenied.longDesc;</div>
         <div id="ed_malformedURI">&malformedURI.longDesc;</div>
         <div id="ed_unknownProtocolFound">&unknownProtocolFound.longDesc;</div>
@@ -318,16 +330,17 @@
         <div id="ed_proxyConnectFailure">&proxyConnectFailure.longDesc;</div>
         <div id="ed_contentEncodingError">&contentEncodingError.longDesc;</div>
         <div id="ed_unsafeContentType">&unsafeContentType.longDesc;</div>
         <div id="ed_nssFailure2">&nssFailure2.longDesc2;</div>
         <div id="ed_nssBadCert">&nssBadCert.longDesc2;</div>
         <div id="ed_cspBlocked">&cspBlocked.longDesc;</div>
         <div id="ed_remoteXUL">&remoteXUL.longDesc;</div>
         <div id="ed_corruptedContentError">&corruptedContentError.longDesc;</div>
+        <div id="ed_inadequateSecurityError">&inadequateSecurityError.longDesc;</div>
       </div>
     </div>
 
     <!-- PAGE CONTAINER (for styling purposes only) -->
     <div id="errorPageContainer">
 
       <!-- Error Title -->
       <div id="errorTitle">
--- a/dom/animation/test/chrome/test_animation_performance_warning.html
+++ b/dom/animation/test/chrome/test_animation_performance_warning.html
@@ -650,19 +650,20 @@ function start() {
       assert_animation_property_state_equals(
         animation.effect.getProperties(),
         [ { property: 'transform', runningOnCompositor: true } ]);
       animation.effect.target.style = 'width: 10000px; height: 10000px';
       return waitForFrame();
     }).then(function() {
       // viewport depends on test environment.
       var expectedWarning = new RegExp(
-        "Async animation disabled because frame size \\(10000, 10000\\) is " +
-        "bigger than the viewport \\(\\d+, \\d+\\) or the visual rectangle " +
-        "\\(10000, 10000\\) is larger than the max allowed value \\(\\d+\\)");
+        "Animation cannot be run on the compositor because the frame size " +
+        "\\(10000, 10000\\) is bigger than the viewport \\(\\d+, \\d+\\) " +
+        "or the visual rectangle \\(10000, 10000\\) is larger than the " +
+        "maximum allowed value \\(\\d+\\)");
       assert_animation_property_state_equals(
         animation.effect.getProperties(),
         [ {
           property: 'transform',
           runningOnCompositor: false,
           warning: expectedWarning
         } ]);
       animation.effect.target.style = 'width: 100px; height: 100px';
--- a/dom/base/nsDocument.cpp
+++ b/dom/base/nsDocument.cpp
@@ -195,16 +195,17 @@
 #include "mozilla/dom/TouchEvent.h"
 
 #include "mozilla/Preferences.h"
 
 #include "imgILoader.h"
 #include "imgRequestProxy.h"
 #include "nsWrapperCacheInlines.h"
 #include "nsSandboxFlags.h"
+#include "nsIAddonPolicyService.h"
 #include "nsIAppsService.h"
 #include "mozilla/dom/AnimatableBinding.h"
 #include "mozilla/dom/AnonymousContent.h"
 #include "mozilla/dom/BindingUtils.h"
 #include "mozilla/dom/DocumentFragment.h"
 #include "mozilla/dom/DocumentTimeline.h"
 #include "mozilla/dom/Event.h"
 #include "mozilla/dom/HTMLBodyElement.h"
@@ -1673,21 +1674,16 @@ nsDocument::~nsDocument()
   mPendingTitleChangeEvent.Revoke();
 
   // We don't want to leave residual locks on images. Make sure we're in an
   // unlocked state, and then clear the table.
   SetImageLockingState(false);
   mImageTracker.Clear();
 
   mPlugins.Clear();
-
-  nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService();
-  if (os) {
-    os->RemoveObserver(this, "service-worker-get-client");
-  }
 }
 
 NS_INTERFACE_TABLE_HEAD(nsDocument)
   NS_WRAPPERCACHE_INTERFACE_TABLE_ENTRY
   NS_INTERFACE_TABLE_BEGIN
     NS_INTERFACE_TABLE_ENTRY_AMBIGUOUS(nsDocument, nsISupports, nsINode)
     NS_INTERFACE_TABLE_ENTRY(nsDocument, nsINode)
     NS_INTERFACE_TABLE_ENTRY(nsDocument, nsIDocument)
@@ -2079,21 +2075,16 @@ nsDocument::Init()
   NS_ENSURE_TRUE(global, NS_ERROR_FAILURE);
   mScopeObject = do_GetWeakReference(global);
   MOZ_ASSERT(mScopeObject);
 
   mScriptLoader = new nsScriptLoader(this);
 
   mozilla::HoldJSObjects(this);
 
-  nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService();
-  if (os) {
-    os->AddObserver(this, "service-worker-get-client", /* ownsWeak */ true);
-  }
-
   return NS_OK;
 }
 
 void
 nsIDocument::DeleteAllProperties()
 {
   for (uint32_t i = 0; i < GetPropertyTableCount(); ++i) {
     PropertyTable(i)->DeleteAllProperties();
@@ -2809,22 +2800,28 @@ nsDocument::InitCSP(nsIChannel* aChannel
       }
       appsService->GetDefaultCSPByLocalId(appId, appDefaultCSP);
       if (!appDefaultCSP.IsEmpty()) {
         applyAppDefaultCSP = true;
       }
     }
   }
 
- // Check if this is part of the Loop/Hello service
- bool applyLoopCSP = IsLoopDocument(aChannel);
+  // Check if this is a document from a WebExtension.
+  nsString addonId;
+  principal->GetAddonId(addonId);
+  bool applyAddonCSP = !addonId.IsEmpty();
+
+  // Check if this is part of the Loop/Hello service
+  bool applyLoopCSP = IsLoopDocument(aChannel);
 
   // If there's no CSP to apply, go ahead and return early
   if (!applyAppDefaultCSP &&
       !applyAppManifestCSP &&
+      !applyAddonCSP &&
       !applyLoopCSP &&
       cspHeaderValue.IsEmpty() &&
       cspROHeaderValue.IsEmpty()) {
     if (MOZ_LOG_TEST(gCspPRLog, LogLevel::Debug)) {
       nsCOMPtr<nsIURI> chanURI;
       aChannel->GetURI(getter_AddRefs(chanURI));
       nsAutoCString aspec;
       chanURI->GetAsciiSpec(aspec);
@@ -2872,16 +2869,32 @@ nsDocument::InitCSP(nsIChannel* aChannel
     csp->AppendPolicy(appDefaultCSP, false, false);
   }
 
   // ----- if the doc is an app and specifies a CSP in its manifest, apply it.
   if (applyAppManifestCSP) {
     csp->AppendPolicy(appManifestCSP, false, false);
   }
 
+  // ----- if the doc is an addon, apply its CSP.
+  if (applyAddonCSP) {
+    nsCOMPtr<nsIAddonPolicyService> aps = do_GetService("@mozilla.org/addons/policy-service;1");
+
+    nsAutoString addonCSP;
+    rv = aps->GetBaseCSP(addonCSP);
+    if (NS_SUCCEEDED(rv)) {
+      csp->AppendPolicy(addonCSP, false, false);
+    }
+
+    rv = aps->GetAddonCSP(addonId, addonCSP);
+    if (NS_SUCCEEDED(rv)) {
+      csp->AppendPolicy(addonCSP, false, false);
+    }
+  }
+
   // ----- if the doc is part of Loop, apply the loop CSP
   if (applyLoopCSP) {
     nsAdoptingString loopCSP;
     loopCSP = Preferences::GetString("loop.CSP");
     NS_ASSERTION(loopCSP, "Missing loop.CSP preference");
     // If the pref has been removed, we continue without setting a CSP
     if (loopCSP) {
       csp->AppendPolicy(loopCSP, false, false);
@@ -4664,16 +4677,58 @@ nsDocument::SetScriptGlobalObject(nsIScr
 
     // Also make sure to remove our onload blocker now if we haven't done it yet
     if (mOnloadBlockCount != 0) {
       nsCOMPtr<nsILoadGroup> loadGroup = GetDocumentLoadGroup();
       if (loadGroup) {
         loadGroup->RemoveRequest(mOnloadBlocker, nullptr, NS_OK);
       }
     }
+
+    using mozilla::dom::workers::ServiceWorkerManager;
+    RefPtr<ServiceWorkerManager> swm = ServiceWorkerManager::GetInstance();
+    if (swm) {
+      ErrorResult error;
+      if (swm->IsControlled(this, error)) {
+        imgLoader* loader = nsContentUtils::GetImgLoaderForDocument(this);
+        if (loader) {
+          loader->ClearCacheForControlledDocument(this);
+        }
+
+        // We may become controlled again if this document comes back out
+        // of bfcache.  Clear our state to allow that to happen.  Only
+        // clear this flag if we are actually controlled, though, so pages
+        // that were force reloaded don't become controlled when they
+        // come out of bfcache.
+        mMaybeServiceWorkerControlled = false;
+      }
+      swm->MaybeStopControlling(this);
+    }
+
+    // Remove ourself from the list of clients.  We only register
+    // content principal documents in this list.
+    if (!nsContentUtils::IsSystemPrincipal(GetPrincipal()) &&
+        !GetPrincipal()->GetIsNullPrincipal()) {
+      nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService();
+      if (os) {
+        os->RemoveObserver(this, "service-worker-get-client");
+      }
+    }
+
+  } else if (!mScriptGlobalObject && aScriptGlobalObject &&
+             mDocumentContainer && GetChannel() &&
+             !nsContentUtils::IsSystemPrincipal(GetPrincipal()) &&
+             !GetPrincipal()->GetIsNullPrincipal()) {
+    // This document is being activated.  Register it in the list of
+    // clients.  We only do this for content principal documents
+    // since we can never observe system or null principals.
+    nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService();
+    if (os) {
+      os->AddObserver(this, "service-worker-get-client", /* ownsWeak */ false);
+    }
   }
 
   mScriptGlobalObject = aScriptGlobalObject;
 
   if (aScriptGlobalObject) {
     mHasHadScriptHandlingObject = true;
     mHasHadDefaultView = true;
     // Go back to using the docshell for the layout history state
@@ -4769,18 +4824,23 @@ nsDocument::SetScriptGlobalObject(nsIScr
     // If we are shift-reloaded, don't associate with a ServiceWorker.
     if (IsForceReloadType(loadType)) {
       NS_WARNING("Page was shift reloaded, skipping ServiceWorker control");
       return;
     }
 
     nsCOMPtr<nsIServiceWorkerManager> swm = mozilla::services::GetServiceWorkerManager();
     if (swm) {
-      nsAutoString documentId;
-      static_cast<nsDocShell*>(docShell.get())->GetInterceptedDocumentId(documentId);
+      // If this document is being resurrected from the bfcache, then we may
+      // already have a document ID.  In that case reuse the same ID.  Otherwise
+      // get our document ID from the docshell.
+      nsString documentId(GetId());
+      if (documentId.IsEmpty()) {
+        static_cast<nsDocShell*>(docShell.get())->GetInterceptedDocumentId(documentId);
+      }
 
       swm->MaybeStartControlling(this, documentId);
       mMaybeServiceWorkerControlled = true;
     }
   }
 }
 
 nsIScriptGlobalObject*
@@ -8913,16 +8973,17 @@ nsDocument::Destroy()
 {
   // The ContentViewer wants to release the document now.  So, tell our content
   // to drop any references to the document so that it can be destroyed.
   if (mIsGoingAway)
     return;
 
   mIsGoingAway = true;
 
+  SetScriptGlobalObject(nullptr);
   RemovedFromDocShell();
 
   bool oldVal = mInUnlinkOrDeletion;
   mInUnlinkOrDeletion = true;
   uint32_t i, count = mChildren.ChildCount();
   for (i = 0; i < count; ++i) {
     mChildren.ChildAt(i)->DestroyContent();
   }
@@ -8939,29 +9000,16 @@ nsDocument::Destroy()
 }
 
 void
 nsDocument::RemovedFromDocShell()
 {
   if (mRemovedFromDocShell)
     return;
 
-  using mozilla::dom::workers::ServiceWorkerManager;
-  RefPtr<ServiceWorkerManager> swm = ServiceWorkerManager::GetInstance();
-  if (swm) {
-    ErrorResult error;
-    if (swm->IsControlled(this, error)) {
-      imgLoader* loader = nsContentUtils::GetImgLoaderForDocument(this);
-      if (loader) {
-        loader->ClearCacheForControlledDocument(this);
-      }
-    }
-    swm->MaybeStopControlling(this);
-  }
-
   mRemovedFromDocShell = true;
   EnumerateActivityObservers(NotifyActivityChanged, nullptr);
 
   uint32_t i, count = mChildren.ChildCount();
   for (i = 0; i < count; ++i) {
     mChildren.ChildAt(i)->SaveSubtreeState();
   }
 }
@@ -12428,21 +12476,28 @@ nsDocument::Observe(nsISupports *aSubjec
 {
   if (strcmp("app-theme-changed", aTopic) == 0) {
     if (!nsContentUtils::IsSystemPrincipal(NodePrincipal()) &&
         !IsUnstyledDocument()) {
       // We don't want to style the chrome window, only app ones.
       OnAppThemeChanged();
     }
   } else if (strcmp("service-worker-get-client", aTopic) == 0) {
-    nsAutoString clientId;
-    GetOrCreateId(clientId);
+    // No need to generate the ID if it doesn't exist here.  The ID being
+    // requested must already be generated in order to passed in as
+    // aSubject.
+    nsString clientId = GetId();
     if (!clientId.IsEmpty() && clientId.Equals(aData)) {
       nsCOMPtr<nsISupportsInterfacePointer> ifptr = do_QueryInterface(aSubject);
       if (ifptr) {
+#ifdef DEBUG
+        nsCOMPtr<nsISupports> value;
+        MOZ_ALWAYS_SUCCEEDS(ifptr->GetData(getter_AddRefs(value)));
+        MOZ_ASSERT(!value);
+#endif
         ifptr->SetData(static_cast<nsIDocument*>(this));
         ifptr->SetDataIID(&NS_GET_IID(nsIDocument));
       }
     }
   }
   return NS_OK;
 }
 
@@ -13313,17 +13368,19 @@ nsIDocument::GetOrCreateId(nsAString& aI
 
   aId = mId;
   return NS_OK;
 }
 
 void
 nsIDocument::SetId(const nsAString& aId)
 {
-  MOZ_ASSERT(mId.IsEmpty(), "Cannot set the document ID after we have one");
+  // The ID should only be set one time, but we may get the same value
+  // more than once if the document is controlled coming out of bfcache.
+  MOZ_ASSERT_IF(mId != aId, mId.IsEmpty());
   mId = aId;
 }
 
 bool
 MarkDocumentTreeToBeInSyncOperation(nsIDocument* aDoc, void* aData)
 {
   nsCOMArray<nsIDocument>* documents =
     static_cast<nsCOMArray<nsIDocument>*>(aData);
--- a/dom/base/nsGkAtomList.h
+++ b/dom/base/nsGkAtomList.h
@@ -831,16 +831,18 @@ GK_ATOM(onMozMouseHittest, "onMozMouseHi
 GK_ATOM(onmouseup, "onmouseup")
 GK_ATOM(onMozAfterPaint, "onMozAfterPaint")
 GK_ATOM(onmozbrowserafterkeydown, "onmozbrowserafterkeydown")
 GK_ATOM(onmozbrowserafterkeyup, "onmozbrowserafterkeyup")
 GK_ATOM(onmozbrowserbeforekeydown, "onmozbrowserbeforekeydown")
 GK_ATOM(onmozbrowserbeforekeyup, "onmozbrowserbeforekeyup")
 GK_ATOM(onmozfullscreenchange, "onmozfullscreenchange")
 GK_ATOM(onmozfullscreenerror, "onmozfullscreenerror")
+GK_ATOM(onmozkeydownonplugin, "onmozkeydownonplugin")
+GK_ATOM(onmozkeyuponplugin, "onmozkeyuponplugin")
 GK_ATOM(onmozpointerlockchange, "onmozpointerlockchange")
 GK_ATOM(onmozpointerlockerror, "onmozpointerlockerror")
 GK_ATOM(onmoztimechange, "onmoztimechange")
 GK_ATOM(onMozMousePixelScroll, "onMozMousePixelScroll")
 GK_ATOM(onMozScrolledAreaChanged, "onMozScrolledAreaChanged")
 GK_ATOM(onmoznetworkupload, "onmoznetworkupload")
 GK_ATOM(onmoznetworkdownload, "onmoznetworkdownload")
 GK_ATOM(onmapfolderlistingreq, "onmapfolderlistingreq")
--- a/dom/base/nsGlobalWindow.cpp
+++ b/dom/base/nsGlobalWindow.cpp
@@ -3759,16 +3759,38 @@ void
 nsPIDOMWindowOuter::RefreshMediaElements()
 {
   RefPtr<AudioChannelService> service = AudioChannelService::GetOrCreate();
   if (service) {
     service->RefreshAgentsVolume(GetOuterWindow());
   }
 }
 
+void
+nsPIDOMWindowOuter::SetServiceWorkersTestingEnabled(bool aEnabled)
+{
+  // Devtools should only be setting this on the top level window.  Its
+  // ok if devtools clears the flag on clean up of nested windows, though.
+  // It will have no affect.
+#ifdef DEBUG
+  nsCOMPtr<nsPIDOMWindowOuter> topWindow = GetScriptableTop();
+  MOZ_ASSERT_IF(aEnabled, this == topWindow);
+#endif
+  mServiceWorkersTestingEnabled = aEnabled;
+}
+
+bool
+nsPIDOMWindowOuter::GetServiceWorkersTestingEnabled()
+{
+  // Automatically get this setting from the top level window so that nested
+  // iframes get the correct devtools setting.
+  nsCOMPtr<nsPIDOMWindowOuter> topWindow = GetScriptableTop();
+  return topWindow->mServiceWorkersTestingEnabled;
+}
+
 bool
 nsPIDOMWindowInner::GetAudioCaptured() const
 {
   MOZ_ASSERT(IsInnerWindow());
   return mAudioCaptured;
 }
 
 nsresult
--- a/dom/base/nsIDocument.h
+++ b/dom/base/nsIDocument.h
@@ -2788,16 +2788,21 @@ protected:
 
   mozilla::dom::XPathEvaluator* XPathEvaluator();
 
   void HandleRebuildUserFontSet() {
     mPostedFlushUserFontSet = false;
     FlushUserFontSet();
   }
 
+  const nsString& GetId() const
+  {
+    return mId;
+  }
+
   nsCString mReferrer;
   nsString mLastModified;
 
   nsCOMPtr<nsIURI> mDocumentURI;
   nsCOMPtr<nsIURI> mOriginalURI;
   nsCOMPtr<nsIURI> mChromeXHRDocURI;
   nsCOMPtr<nsIURI> mDocumentBaseURI;
   nsCOMPtr<nsIURI> mChromeXHRDocBaseURI;
--- a/dom/base/nsPIDOMWindow.h
+++ b/dom/base/nsPIDOMWindow.h
@@ -860,25 +860,18 @@ public:
   void SetMediaSuspended(bool aSuspended);
 
   bool GetAudioMuted() const;
   void SetAudioMuted(bool aMuted);
 
   float GetAudioVolume() const;
   nsresult SetAudioVolume(float aVolume);
 
-  void SetServiceWorkersTestingEnabled(bool aEnabled)
-  {
-    mServiceWorkersTestingEnabled = aEnabled;
-  }
-
-  bool GetServiceWorkersTestingEnabled()
-  {
-    return mServiceWorkersTestingEnabled;
-  }
+  void SetServiceWorkersTestingEnabled(bool aEnabled);
+  bool GetServiceWorkersTestingEnabled();
 };
 
 NS_DEFINE_STATIC_IID_ACCESSOR(nsPIDOMWindowOuter, NS_PIDOMWINDOWOUTER_IID)
 
 #include "nsPIDOMWindowInlines.h"
 
 #ifdef MOZILLA_INTERNAL_API
 PopupControlState
--- a/dom/bindings/Exceptions.cpp
+++ b/dom/bindings/Exceptions.cpp
@@ -61,16 +61,34 @@ ThrowExceptionObject(JSContext* aCx, Exc
 
   // If we stored the original thrown JS value in the exception
   // (see XPCConvert::ConstructException) and we are in a web context
   // (i.e., not chrome), rethrow the original value. This only applies to JS
   // implemented components so we only need to check for this on the main
   // thread.
   if (NS_IsMainThread() && !nsContentUtils::IsCallerChrome() &&
       aException->StealJSVal(thrown.address())) {
+    // Now check for the case when thrown is a number which matches
+    // aException->GetResult().  This would indicate that what actually got
+    // thrown was an nsresult value.  In that situation, we should go back
+    // through dom::Throw with that nsresult value, because it will make sure to
+    // create the right sort of Exception or DOMException, with the right
+    // global.
+    if (thrown.isNumber()) {
+      nsresult exceptionResult;
+      if (NS_SUCCEEDED(aException->GetResult(&exceptionResult)) &&
+          double(exceptionResult) == thrown.toNumber()) {
+        // The return value semantics here are a bit weird.  Throw() always
+        // returns false.  But we want to return true if we managed to throw an
+        // exception (otherwise our caller will assume OOM)... which Throw()
+        // always will.  So we just return true unconditionally.
+        Throw(aCx, exceptionResult);
+        return true;
+      }
+    }
     if (!JS_WrapValue(aCx, &thrown)) {
       return false;
     }
     JS_SetPendingException(aCx, thrown);
     return true;
   }
 
   JS::Rooted<JSObject*> glob(aCx, JS::CurrentGlobalOrNull(aCx));
@@ -98,25 +116,27 @@ Throw(JSContext* aCx, nsresult aRv, cons
 
   if (JS_IsExceptionPending(aCx)) {
     // Don't clobber the existing exception.
     return false;
   }
 
   CycleCollectedJSRuntime* runtime = CycleCollectedJSRuntime::Get();
   nsCOMPtr<nsIException> existingException = runtime->GetPendingException();
-  if (existingException) {
+  // Make sure to clear the pending exception now.  Either we're going to reuse
+  // it (and we already grabbed it), or we plan to throw something else and this
+  // pending exception is no longer relevant.
+  runtime->SetPendingException(nullptr);
+
+  // Ignore the pending exception if we have a non-default message passed in.
+  if (aMessage.IsEmpty() && existingException) {
     nsresult nr;
     if (NS_SUCCEEDED(existingException->GetResult(&nr)) &&
         aRv == nr) {
       // Reuse the existing exception.
-
-      // Clear pending exception
-      runtime->SetPendingException(nullptr);
-
       if (!ThrowExceptionObject(aCx, existingException)) {
         // If we weren't able to throw an exception we're
         // most likely out of memory
         JS_ReportOutOfMemory(aCx);
       }
       return false;
     }
   }
--- a/dom/canvas/CanvasRenderingContext2D.cpp
+++ b/dom/canvas/CanvasRenderingContext2D.cpp
@@ -106,17 +106,16 @@
 #include "mozilla/dom/SVGMatrix.h"
 #include "mozilla/dom/TextMetrics.h"
 #include "mozilla/dom/SVGMatrix.h"
 #include "mozilla/FloatingPoint.h"
 #include "nsGlobalWindow.h"
 #include "GLContext.h"
 #include "GLContextProvider.h"
 #include "SVGContentUtils.h"
-#include "SVGImageContext.h"
 #include "nsIScreenManager.h"
 #include "nsFilterInstance.h"
 #include "nsSVGLength2.h"
 #include "nsDeviceContext.h"
 #include "nsFontMetrics.h"
 #include "Units.h"
 #include "CanvasUtils.h"
 #include "mozilla/StyleSetHandle.h"
--- a/dom/events/DataTransfer.cpp
+++ b/dom/events/DataTransfer.cpp
@@ -203,18 +203,19 @@ DataTransfer::GetDropEffect(nsAString& a
 NS_IMETHODIMP
 DataTransfer::SetDropEffect(const nsAString& aDropEffect)
 {
   // the drop effect can only be 'none', 'copy', 'move' or 'link'.
   for (uint32_t e = 0; e <= nsIDragService::DRAGDROP_ACTION_LINK; e++) {
     if (aDropEffect.EqualsASCII(sEffects[e])) {
       // don't allow copyMove
       if (e != (nsIDragService::DRAGDROP_ACTION_COPY |
-                nsIDragService::DRAGDROP_ACTION_MOVE))
+                nsIDragService::DRAGDROP_ACTION_MOVE)) {
         mDropEffect = e;
+      }
       break;
     }
   }
 
   return NS_OK;
 }
 
 NS_IMETHODIMP
@@ -308,28 +309,30 @@ DataTransfer::GetFileListInternal(ErrorR
     mFileList = new FileList(static_cast<nsIDOMDataTransfer*>(this));
 
     uint32_t count = mItems.Length();
 
     for (uint32_t i = 0; i < count; i++) {
       nsCOMPtr<nsIVariant> variant;
       aRv = GetDataAtInternal(NS_ConvertUTF8toUTF16(kFileMime), i,
                               aSubjectPrincipal, getter_AddRefs(variant));
-      if (aRv.Failed()) {
+      if (NS_WARN_IF(aRv.Failed())) {
         return nullptr;
       }
 
-      if (!variant)
+      if (!variant) {
         continue;
+      }
 
       nsCOMPtr<nsISupports> supports;
       nsresult rv = variant->GetAsISupports(getter_AddRefs(supports));
 
-      if (NS_FAILED(rv))
+      if (NS_WARN_IF(NS_FAILED(rv))) {
         continue;
+      }
 
       nsCOMPtr<nsIFile> file = do_QueryInterface(supports);
 
       RefPtr<File> domFile;
       if (file) {
         MOZ_ASSERT(XRE_GetProcessType() == GeckoProcessType_Default,
                    "nsIFile objects are not expected on the content process");
 
@@ -362,22 +365,23 @@ DataTransfer::GetFileListInternal(ErrorR
 
   return mFileList;
 }
 
 NS_IMETHODIMP
 DataTransfer::GetFiles(nsIDOMFileList** aFileList)
 {
   ErrorResult rv;
-  NS_IF_ADDREF(*aFileList = GetFileListInternal(rv, nsContentUtils::GetSystemPrincipal()));
+  NS_IF_ADDREF(*aFileList =
+    GetFileListInternal(rv, nsContentUtils::GetSystemPrincipal()));
   return rv.StealNSResult();
 }
 
 already_AddRefed<DOMStringList>
-DataTransfer::Types()
+DataTransfer::Types() const
 {
   RefPtr<DOMStringList> types = new DOMStringList();
   if (mItems.Length()) {
     bool addFile = false;
     const nsTArray<TransferItem>& item = mItems[0];
     for (uint32_t i = 0; i < item.Length(); i++) {
       const nsString& format = item[i].mFormat;
       types->Add(format);
@@ -407,17 +411,19 @@ DataTransfer::GetTypes(nsISupports** aTy
 void
 DataTransfer::GetData(const nsAString& aFormat, nsAString& aData,
                       ErrorResult& aRv)
 {
   // return an empty string if data for the format was not found
   aData.Truncate();
 
   nsCOMPtr<nsIVariant> data;
-  nsresult rv = GetDataAtInternal(aFormat, 0, nsContentUtils::SubjectPrincipal(), getter_AddRefs(data));
+  nsresult rv =
+    GetDataAtInternal(aFormat, 0, nsContentUtils::SubjectPrincipal(),
+                      getter_AddRefs(data));
   if (NS_FAILED(rv)) {
     if (rv != NS_ERROR_DOM_INDEX_SIZE_ERR) {
       aRv.Throw(rv);
     }
     return;
   }
 
   if (data) {
@@ -431,25 +437,28 @@ DataTransfer::GetData(const nsAString& a
 
     if (lowercaseFormat.EqualsLiteral("url")) {
       int32_t lastidx = 0, idx;
       int32_t length = stringdata.Length();
       while (lastidx < length) {
         idx = stringdata.FindChar('\n', lastidx);
         // lines beginning with # are comments
         if (stringdata[lastidx] == '#') {
-          if (idx == -1)
+          if (idx == -1) {
             break;
+          }
         }
         else {
-          if (idx == -1)
+          if (idx == -1) {
             aData.Assign(Substring(stringdata, lastidx));
-          else
+          } else {
             aData.Assign(Substring(stringdata, lastidx, idx - lastidx));
-          aData = nsContentUtils::TrimWhitespace<nsCRT::IsAsciiSpace>(aData, true);
+          }
+          aData = nsContentUtils::TrimWhitespace<nsCRT::IsAsciiSpace>(aData,
+                                                                      true);
           return;
         }
         lastidx = idx + 1;
       }
     }
     else {
       aData = stringdata;
     }
@@ -466,17 +475,18 @@ DataTransfer::GetData(const nsAString& a
 
 void
 DataTransfer::SetData(const nsAString& aFormat, const nsAString& aData,
                       ErrorResult& aRv)
 {
   RefPtr<nsVariantCC> variant = new nsVariantCC();
   variant->SetAsAString(aData);
 
-  aRv = SetDataAtInternal(aFormat, variant, 0, nsContentUtils::SubjectPrincipal());
+  aRv = SetDataAtInternal(aFormat, variant, 0,
+                          nsContentUtils::SubjectPrincipal());
 }
 
 void
 DataTransfer::ClearData(const Optional<nsAString>& aFormat, ErrorResult& aRv)
 {
   if (mReadOnly) {
     aRv.Throw(NS_ERROR_DOM_NO_MODIFICATION_ALLOWED_ERR);
     return;
@@ -569,47 +579,52 @@ DataTransfer::MozTypesAt(uint32_t aIndex
     aRv.Throw(NS_ERROR_DOM_INDEX_SIZE_ERR);
     return nullptr;
   }
 
   RefPtr<DOMStringList> types = new DOMStringList();
   if (aIndex < mItems.Length()) {
     // note that you can retrieve the types regardless of their principal
     nsTArray<TransferItem>& item = mItems[aIndex];
-    for (uint32_t i = 0; i < item.Length(); i++)
+    for (uint32_t i = 0; i < item.Length(); i++) {
       types->Add(item[i].mFormat);
+    }
   }
 
   return types.forget();
 }
 
 NS_IMETHODIMP
 DataTransfer::MozTypesAt(uint32_t aIndex, nsISupports** aTypes)
 {
   ErrorResult rv;
   RefPtr<DOMStringList> types = MozTypesAt(aIndex, rv);
   types.forget(aTypes);
   return rv.StealNSResult();
 }
 
 nsresult
-DataTransfer::GetDataAtNoSecurityCheck(const nsAString& aFormat, uint32_t aIndex,
+DataTransfer::GetDataAtNoSecurityCheck(const nsAString& aFormat,
+                                       uint32_t aIndex,
                                        nsIVariant** aData)
 {
-  return GetDataAtInternal(aFormat, aIndex, nsContentUtils::GetSystemPrincipal(), aData);
+  return GetDataAtInternal(aFormat, aIndex,
+                           nsContentUtils::GetSystemPrincipal(), aData);
 }
 
 nsresult
 DataTransfer::GetDataAtInternal(const nsAString& aFormat, uint32_t aIndex,
-                                nsIPrincipal* aSubjectPrincipal, nsIVariant** aData)
+                                nsIPrincipal* aSubjectPrincipal,
+                                nsIVariant** aData)
 {
   *aData = nullptr;
 
-  if (aFormat.IsEmpty())
+  if (aFormat.IsEmpty()) {
     return NS_OK;
+  }
 
   if (aIndex >= mItems.Length()) {
     return NS_ERROR_DOM_INDEX_SIZE_ERR;
   }
 
   // Only the first item is valid for clipboard events
   if (aIndex > 0 &&
       (mEventMessage == eCut || mEventMessage == eCopy ||
@@ -654,17 +669,18 @@ DataTransfer::GetDataAtInternal(const ns
           nsIScriptContext* c = pt->GetContextForEventHandlers(&rv);
           NS_ENSURE_TRUE(c && NS_SUCCEEDED(rv), NS_ERROR_DOM_SECURITY_ERR);
           nsIGlobalObject* go = c->GetGlobalObject();
           NS_ENSURE_TRUE(go, NS_ERROR_DOM_SECURITY_ERR);
           nsCOMPtr<nsIScriptObjectPrincipal> sp = do_QueryInterface(go);
           MOZ_ASSERT(sp, "This cannot fail on the main thread.");
           nsIPrincipal* dataPrincipal = sp->GetPrincipal();
           NS_ENSURE_TRUE(dataPrincipal, NS_ERROR_DOM_SECURITY_ERR);
-          NS_ENSURE_TRUE(aSubjectPrincipal->Subsumes(dataPrincipal), NS_ERROR_DOM_SECURITY_ERR);
+          NS_ENSURE_TRUE(aSubjectPrincipal->Subsumes(dataPrincipal),
+                                                     NS_ERROR_DOM_SECURITY_ERR);
         }
       }
       *aData = formatitem.mData;
       NS_IF_ADDREF(*aData);
       return NS_OK;
     }
   }
 
@@ -673,17 +689,18 @@ DataTransfer::GetDataAtInternal(const ns
 
 void
 DataTransfer::MozGetDataAt(JSContext* aCx, const nsAString& aFormat,
                            uint32_t aIndex,
                            JS::MutableHandle<JS::Value> aRetval,
                            mozilla::ErrorResult& aRv)
 {
   nsCOMPtr<nsIVariant> data;
-  aRv = GetDataAtInternal(aFormat, aIndex, nsContentUtils::SubjectPrincipal(), getter_AddRefs(data));
+  aRv = GetDataAtInternal(aFormat, aIndex, nsContentUtils::SubjectPrincipal(),
+                          getter_AddRefs(data));
   if (aRv.Failed()) {
     return;
   }
 
   if (!data) {
     aRetval.setNull();
     return;
   }
@@ -692,17 +709,18 @@ DataTransfer::MozGetDataAt(JSContext* aC
   if (!VariantToJsval(aCx, data, aRetval)) {
     aRv = NS_ERROR_FAILURE;
     return;
   }
 }
 
 nsresult
 DataTransfer::SetDataAtInternal(const nsAString& aFormat, nsIVariant* aData,
-                                uint32_t aIndex, nsIPrincipal* aSubjectPrincipal)
+                                uint32_t aIndex,
+                                nsIPrincipal* aSubjectPrincipal)
 {
   if (aFormat.IsEmpty()) {
     return NS_OK;
   }
 
   if (mReadOnly) {
     return NS_ERROR_DOM_NO_MODIFICATION_ALLOWED_ERR;
   }
@@ -748,17 +766,18 @@ void
 DataTransfer::MozSetDataAt(JSContext* aCx, const nsAString& aFormat,
                            JS::Handle<JS::Value> aData,
                            uint32_t aIndex, ErrorResult& aRv)
 {
   nsCOMPtr<nsIVariant> data;
   aRv = nsContentUtils::XPConnect()->JSValToVariant(aCx, aData,
                                                     getter_AddRefs(data));
   if (!aRv.Failed()) {
-    aRv = SetDataAtInternal(aFormat, data, aIndex, nsContentUtils::SubjectPrincipal());
+    aRv = SetDataAtInternal(aFormat, data, aIndex,
+                            nsContentUtils::SubjectPrincipal());
   }
 }
 
 void
 DataTransfer::MozClearDataAt(const nsAString& aFormat, uint32_t aIndex,
                              ErrorResult& aRv)
 {
   if (mReadOnly) {
@@ -804,33 +823,36 @@ DataTransfer::MozClearDataAtHelper(const
   // count backwards so that the count and index don't have to be adjusted
   // after removing an element
   for (int32_t i = item.Length() - 1; i >= 0; i--) {
     TransferItem& formatitem = item[i];
     if (clearall || formatitem.mFormat.Equals(format)) {
       // don't allow removing data that has a stronger principal
       bool subsumes;
       if (formatitem.mPrincipal && principal &&
-          (NS_FAILED(principal->Subsumes(formatitem.mPrincipal, &subsumes)) || !subsumes)) {
+          (NS_FAILED(principal->Subsumes(formatitem.mPrincipal, &subsumes)) ||
+           !subsumes)) {
         aRv.Throw(NS_ERROR_DOM_SECURITY_ERR);
         return;
       }
 
       item.RemoveElementAt(i);
 
       // if a format was specified, break out. Otherwise, loop around until
       // all formats have been removed
-      if (!clearall)
+      if (!clearall) {
         break;
+      }
     }
   }
 
   // if the last format for an item is removed, remove the entire item
-  if (!item.Length())
+  if (!item.Length()) {
      mItems.RemoveElementAt(aIndex);
+  }
 }
 
 NS_IMETHODIMP
 DataTransfer::MozClearDataAt(const nsAString& aFormat, uint32_t aIndex)
 {
   ErrorResult rv;
   MozClearDataAt(aFormat, aIndex, rv);
   return rv.StealNSResult();
@@ -1001,40 +1023,44 @@ DataTransfer::GetTransferable(uint32_t a
 
   nsCOMPtr<nsIStorageStream> storageStream;
   nsCOMPtr<nsIBinaryOutputStream> stream;
 
   bool added = false;
   bool handlingCustomFormats = true;
   uint32_t totalCustomLength = 0;
 
-  const char* knownFormats[] = { kTextMime, kHTMLMime, kNativeHTMLMime, kRTFMime,
-                                 kURLMime, kURLDataMime, kURLDescriptionMime, kURLPrivateMime,
-                                 kPNGImageMime, kJPEGImageMime, kGIFImageMime, kNativeImageMime,
-                                 kFileMime, kFilePromiseMime, kFilePromiseDirectoryMime,
-                                 kMozTextInternal, kHTMLContext, kHTMLInfo };
+  const char* knownFormats[] = {
+    kTextMime, kHTMLMime, kNativeHTMLMime, kRTFMime,
+    kURLMime, kURLDataMime, kURLDescriptionMime, kURLPrivateMime,
+    kPNGImageMime, kJPEGImageMime, kGIFImageMime, kNativeImageMime,
+    kFileMime, kFilePromiseMime, kFilePromiseDirectoryMime,
+    kMozTextInternal, kHTMLContext, kHTMLInfo };
 
   /*
    * Two passes are made here to iterate over all of the types. First, look for
-   * any types that are not in the list of known types. For this pass, handlingCustomFormats
-   * will be true. Data that corresponds to unknown types will be pulled out and
-   * inserted into a single type (kCustomTypesMime) by writing the data into a stream.
+   * any types that are not in the list of known types. For this pass,
+   * handlingCustomFormats will be true. Data that corresponds to unknown types
+   * will be pulled out and inserted into a single type (kCustomTypesMime) by
+   * writing the data into a stream.
    *
-   * The second pass will iterate over the formats looking for known types. These are
-   * added as is. The unknown types are all then inserted as a single type (kCustomTypesMime)
-   * in the same position of the first custom type. This model is used to maintain the
-   * format order as best as possible.
+   * The second pass will iterate over the formats looking for known types.
+   * These are added as is. The unknown types are all then inserted as a single
+   * type (kCustomTypesMime) in the same position of the first custom type. This
+   * model is used to maintain the format order as best as possible.
    *
-   * The format of the kCustomTypesMime type is one or more of the following stored sequentially:
+   * The format of the kCustomTypesMime type is one or more of the following
+   * stored sequentially:
    *   <32-bit> type (only none or string is supported)
    *   <32-bit> length of format
    *   <wide string> format
    *   <32-bit> length of data
    *   <wide string> data
-   * A type of eCustomClipboardTypeId_None ends the list, without any following data.
+   * A type of eCustomClipboardTypeId_None ends the list, without any following
+   * data.
    */
   do {
     for (uint32_t f = 0; f < count; f++) {
       const TransferItem& formatitem = item[f];
       if (!formatitem.mData) { // skip empty items
         continue;
       }
 
@@ -1046,114 +1072,129 @@ DataTransfer::GetTransferable(uint32_t a
           break;
         }
       }
 
       uint32_t lengthInBytes;
       nsCOMPtr<nsISupports> convertedData;
 
       if (handlingCustomFormats) {
-        if (!ConvertFromVariant(formatitem.mData, getter_AddRefs(convertedData), &lengthInBytes)) {
+        if (!ConvertFromVariant(formatitem.mData, getter_AddRefs(convertedData),
+                                &lengthInBytes)) {
           continue;
         }
 
         // When handling custom types, add the data to the stream if this is a
         // custom type.
         if (isCustomFormat) {
-          // If it isn't a string, just ignore it. The dataTransfer is cached in the
-          // drag sesion during drag-and-drop, so non-strings will be available when
-          // dragging locally.
+          // If it isn't a string, just ignore it. The dataTransfer is cached in
+          // the drag sesion during drag-and-drop, so non-strings will be
+          // available when dragging locally.
           nsCOMPtr<nsISupportsString> str(do_QueryInterface(convertedData));
           if (str) {
             nsAutoString data;
             str->GetData(data);
 
             if (!stream) {
               // Create a storage stream to write to.
               NS_NewStorageStream(1024, UINT32_MAX, getter_AddRefs(storageStream));
 
               nsCOMPtr<nsIOutputStream> outputStream;
               storageStream->GetOutputStream(0, getter_AddRefs(outputStream));
 
               stream = do_CreateInstance("@mozilla.org/binaryoutputstream;1");
               stream->SetOutputStream(outputStream);
             }
 
-            int32_t formatLength = formatitem.mFormat.Length() * sizeof(nsString::char_type);
+            int32_t formatLength =
+              formatitem.mFormat.Length() * sizeof(nsString::char_type);
 
             stream->Write32(eCustomClipboardTypeId_String);
             stream->Write32(formatLength);
-            stream->WriteBytes((const char *)formatitem.mFormat.get(), formatLength);
+            stream->WriteBytes((const char *)formatitem.mFormat.get(),
+                               formatLength);
             stream->Write32(lengthInBytes);
             stream->WriteBytes((const char *)data.get(), lengthInBytes);
 
-            // The total size of the stream is the format length, the data length,
-            // two integers to hold the lengths and one integer for the string flag.
-            totalCustomLength += formatLength + lengthInBytes + (sizeof(uint32_t) * 3);
+            // The total size of the stream is the format length, the data
+            // length, two integers to hold the lengths and one integer for the
+            // string flag.
+            totalCustomLength +=
+              formatLength + lengthInBytes + (sizeof(uint32_t) * 3);
           }
         }
       } else if (isCustomFormat && stream) {
         // This is the second pass of the loop (handlingCustomFormats is false).
         // When encountering the first custom format, append all of the stream
         // at this position.
 
         // Write out a terminator.
         totalCustomLength += sizeof(uint32_t);
         stream->Write32(eCustomClipboardTypeId_None);
 
         nsCOMPtr<nsIInputStream> inputStream;
         storageStream->NewInputStream(0, getter_AddRefs(inputStream));
 
-        RefPtr<nsStringBuffer> stringBuffer = nsStringBuffer::Alloc(totalCustomLength + 1);
+        RefPtr<nsStringBuffer> stringBuffer =
+          nsStringBuffer::Alloc(totalCustomLength + 1);
 
-        // Read the data from the string and add a null-terminator as ToString needs it.
+        // Read the data from the string and add a null-terminator as ToString
+        // needs it.
         uint32_t amountRead;
-        inputStream->Read(static_cast<char*>(stringBuffer->Data()), totalCustomLength, &amountRead);
+        inputStream->Read(static_cast<char*>(stringBuffer->Data()),
+                          totalCustomLength, &amountRead);
         static_cast<char*>(stringBuffer->Data())[amountRead] = 0;
 
         nsCString str;
         stringBuffer->ToString(totalCustomLength, str);
-        nsCOMPtr<nsISupportsCString> strSupports(do_CreateInstance(NS_SUPPORTS_CSTRING_CONTRACTID));
+        nsCOMPtr<nsISupportsCString>
+          strSupports(do_CreateInstance(NS_SUPPORTS_CSTRING_CONTRACTID));
         strSupports->SetData(str);
 
-        nsresult rv = transferable->SetTransferData(kCustomTypesMime, strSupports, totalCustomLength);
+        nsresult rv = transferable->SetTransferData(kCustomTypesMime,
+                                                    strSupports,
+                                                    totalCustomLength);
         if (NS_FAILED(rv)) {
           return nullptr;
         }
 
         added = true;
 
         // Clear the stream so it doesn't get used again.
         stream = nullptr;
       } else {
         // This is the second pass of the loop and a known type is encountered.
         // Add it as is.
-        if (!ConvertFromVariant(formatitem.mData, getter_AddRefs(convertedData), &lengthInBytes)) {
+        if (!ConvertFromVariant(formatitem.mData, getter_AddRefs(convertedData),
+                                &lengthInBytes)) {
           continue;
         }
 
-        // The underlying drag code uses text/unicode, so use that instead of text/plain
+        // The underlying drag code uses text/unicode, so use that instead of
+        // text/plain
         const char* format;
         NS_ConvertUTF16toUTF8 utf8format(formatitem.mFormat);
         if (utf8format.EqualsLiteral(kTextMime)) {
           format = kUnicodeMime;
         } else {
           format = utf8format.get();
         }
 
         // If a converter is set for a format, set the converter for the
         // transferable and don't add the item
-        nsCOMPtr<nsIFormatConverter> converter = do_QueryInterface(convertedData);
+        nsCOMPtr<nsIFormatConverter> converter =
+          do_QueryInterface(convertedData);
         if (converter) {
           transferable->AddDataFlavor(format);
           transferable->SetConverter(converter);
           continue;
         }
 
-        nsresult rv = transferable->SetTransferData(format, convertedData, lengthInBytes);
+        nsresult rv = transferable->SetTransferData(format, convertedData,
+                                                    lengthInBytes);
         if (NS_FAILED(rv)) {
           return nullptr;
         }
 
         added = true;
       }
     }
 
@@ -1166,65 +1207,69 @@ DataTransfer::GetTransferable(uint32_t a
   }
 
   return nullptr;
 }
 
 bool
 DataTransfer::ConvertFromVariant(nsIVariant* aVariant,
                                  nsISupports** aSupports,
-                                 uint32_t* aLength)
+                                 uint32_t* aLength) const
 {
   *aSupports = nullptr;
   *aLength = 0;
 
   uint16_t type;
   aVariant->GetDataType(&type);
   if (type == nsIDataType::VTYPE_INTERFACE ||
       type == nsIDataType::VTYPE_INTERFACE_IS) {
     nsCOMPtr<nsISupports> data;
-    if (NS_FAILED(aVariant->GetAsISupports(getter_AddRefs(data))))
-       return false;
+    if (NS_FAILED(aVariant->GetAsISupports(getter_AddRefs(data)))) {
+     return false;
+    }
 
     nsCOMPtr<nsIFlavorDataProvider> fdp = do_QueryInterface(data);
     if (fdp) {
       // for flavour data providers, use kFlavorHasDataProvider (which has the
       // value 0) as the length.
       fdp.forget(aSupports);
       *aLength = nsITransferable::kFlavorHasDataProvider;
     }
     else {
       // wrap the item in an nsISupportsInterfacePointer
       nsCOMPtr<nsISupportsInterfacePointer> ptrSupports =
         do_CreateInstance(NS_SUPPORTS_INTERFACE_POINTER_CONTRACTID);
-      if (!ptrSupports)
+      if (!ptrSupports) {
         return false;
+      }
 
       ptrSupports->SetData(data);
       ptrSupports.forget(aSupports);
 
       *aLength = sizeof(nsISupportsInterfacePointer *);
     }
 
     return true;
   }
 
   char16_t* chrs;
   uint32_t len = 0;
   nsresult rv = aVariant->GetAsWStringWithSize(&len, &chrs);
-  if (NS_FAILED(rv))
+  if (NS_FAILED(rv)) {
     return false;
+  }
 
   nsAutoString str;
   str.Adopt(chrs, len);
 
   nsCOMPtr<nsISupportsString>
     strSupports(do_CreateInstance(NS_SUPPORTS_STRING_CONTRACTID));
-  if (!strSupports)
+  if (!strSupports) {
     return false;
+  }
 
   strSupports->SetData(str);
 
   strSupports.forget(aSupports);
 
   // each character is two bytes
   *aLength = str.Length() << 1;
 
@@ -1253,18 +1298,20 @@ DataTransfer::SetDataWithPrincipal(const
     nsTArray<TransferItem>& item = mItems[aIndex];
     uint32_t count = item.Length();
     for (uint32_t i = 0; i < count; i++) {
       TransferItem& itemformat = item[i];
       if (itemformat.mFormat.Equals(format)) {
         // don't allow replacing data that has a stronger principal
         bool subsumes;
         if (itemformat.mPrincipal && aPrincipal &&
-            (NS_FAILED(aPrincipal->Subsumes(itemformat.mPrincipal, &subsumes)) || !subsumes))
+            (NS_FAILED(aPrincipal->Subsumes(itemformat.mPrincipal,
+                                            &subsumes)) || !subsumes)) {
           return NS_ERROR_DOM_SECURITY_ERR;
+        }
 
         itemformat.mPrincipal = aPrincipal;
         itemformat.mData = aData;
         return NS_OK;
       }
     }
 
     // add a new format
@@ -1298,54 +1345,69 @@ DataTransfer::SetDataWithPrincipalFromOt
   if (aFormat.EqualsLiteral(kCustomTypesMime)) {
     FillInExternalCustomTypes(aData, aIndex, aPrincipal);
   } else {
     SetDataWithPrincipal(aFormat, aData, aIndex, aPrincipal);
   }
 }
 
 void
-DataTransfer::GetRealFormat(const nsAString& aInFormat, nsAString& aOutFormat)
+DataTransfer::GetRealFormat(const nsAString& aInFormat,
+                            nsAString& aOutFormat) const
 {
   // treat text/unicode as equivalent to text/plain
   nsAutoString lowercaseFormat;
   nsContentUtils::ASCIIToLower(aInFormat, lowercaseFormat);
-  if (lowercaseFormat.EqualsLiteral("text") || lowercaseFormat.EqualsLiteral("text/unicode"))
+  if (lowercaseFormat.EqualsLiteral("text") ||
+      lowercaseFormat.EqualsLiteral("text/unicode")) {
     aOutFormat.AssignLiteral("text/plain");
-  else if (lowercaseFormat.EqualsLiteral("url"))
+    return;
+  }
+
+  if (lowercaseFormat.EqualsLiteral("url")) {
     aOutFormat.AssignLiteral("text/uri-list");
-  else
-    aOutFormat.Assign(lowercaseFormat);
+    return;
+  }
+
+  aOutFormat.Assign(lowercaseFormat);
 }
 
 void
-DataTransfer::CacheExternalData(const char* aFormat, uint32_t aIndex, nsIPrincipal* aPrincipal)
+DataTransfer::CacheExternalData(const char* aFormat, uint32_t aIndex,
+                                nsIPrincipal* aPrincipal)
 {
   if (strcmp(aFormat, kUnicodeMime) == 0) {
-    SetDataWithPrincipal(NS_LITERAL_STRING("text/plain"), nullptr, aIndex, aPrincipal);
-  } else {
-    if (strcmp(aFormat, kURLDataMime) == 0) {
-      SetDataWithPrincipal(NS_LITERAL_STRING("text/uri-list"), nullptr, aIndex, aPrincipal);
-    }
-    SetDataWithPrincipal(NS_ConvertUTF8toUTF16(aFormat), nullptr, aIndex, aPrincipal);
+    SetDataWithPrincipal(NS_LITERAL_STRING("text/plain"), nullptr, aIndex,
+                         aPrincipal);
+    return;
   }
+
+  if (strcmp(aFormat, kURLDataMime) == 0) {
+    SetDataWithPrincipal(NS_LITERAL_STRING("text/uri-list"), nullptr, aIndex,
+                         aPrincipal);
+    return;
+  }
+
+  SetDataWithPrincipal(NS_ConvertUTF8toUTF16(aFormat), nullptr, aIndex,
+                       aPrincipal);
 }
 
 void
 DataTransfer::CacheExternalDragFormats()
 {
   // Called during the constructor to cache the formats available from an
   // external drag. The data associated with each format will be set to null.
   // This data will instead only be retrieved in FillInExternalDragData when
   // asked for, as it may be time consuming for the source application to
   // generate it.
 
   nsCOMPtr<nsIDragSession> dragSession = nsContentUtils::GetDragSession();
-  if (!dragSession)
+  if (!dragSession) {
     return;
+  }
 
   // make sure that the system principal is used for external drags
   nsIScriptSecurityManager* ssm = nsContentUtils::GetSecurityManager();
   nsCOMPtr<nsIPrincipal> sysPrincipal;
   ssm->GetSystemPrincipal(getter_AddRefs(sysPrincipal));
 
   // there isn't a way to get a list of the formats that might be available on
   // all platforms, so just check for the types that can actually be imported
@@ -1384,17 +1446,18 @@ DataTransfer::CacheExternalClipboardForm
 {
   NS_ASSERTION(mEventMessage == ePaste,
                "caching clipboard data for invalid event");
 
   // Called during the constructor for paste events to cache the formats
   // available on the clipboard. As with CacheExternalDragFormats, the
   // data will only be retrieved when needed.
 
-  nsCOMPtr<nsIClipboard> clipboard = do_GetService("@mozilla.org/widget/clipboard;1");
+  nsCOMPtr<nsIClipboard> clipboard =
+    do_GetService("@mozilla.org/widget/clipboard;1");
   if (!clipboard || mClipboardType < 0) {
     return;
   }
 
   nsIScriptSecurityManager* ssm = nsContentUtils::GetSecurityManager();
   nsCOMPtr<nsIPrincipal> sysPrincipal;
   ssm->GetSystemPrincipal(getter_AddRefs(sysPrincipal));
 
@@ -1402,17 +1465,18 @@ DataTransfer::CacheExternalClipboardForm
   // all platforms, so just check for the types that can actually be imported.
   // Note that the loop below assumes that kCustomTypesMime will be first.
   const char* formats[] = { kCustomTypesMime, kFileMime, kHTMLMime, kRTFMime,
                             kURLMime, kURLDataMime, kUnicodeMime };
 
   for (uint32_t f = 0; f < mozilla::ArrayLength(formats); ++f) {
     // check each format one at a time
     bool supported;
-    clipboard->HasDataMatchingFlavors(&(formats[f]), 1, mClipboardType, &supported);
+    clipboard->HasDataMatchingFlavors(&(formats[f]), 1, mClipboardType,
+                                      &supported);
     // if the format is supported, add an item to the array with null as
     // the data. When retrieved, GetRealData will read the data.
     if (supported) {
       if (f == 0) {
         FillInExternalCustomTypes(0, sysPrincipal);
       } else {
         CacheExternalData(formats[f], 0, sysPrincipal);
       }
@@ -1430,33 +1494,36 @@ DataTransfer::FillInExternalData(Transfe
   }
 
   // only drag and paste events should be calling FillInExternalData
   NS_ASSERTION(mEventMessage != eCut && mEventMessage != eCopy,
                "clipboard event with empty data");
 
   NS_ConvertUTF16toUTF8 utf8format(aItem.mFormat);
   const char* format = utf8format.get();
-  if (strcmp(format, "text/plain") == 0)
+  if (strcmp(format, "text/plain") == 0) {
     format = kUnicodeMime;
-  else if (strcmp(format, "text/uri-list") == 0)
+  } else if (strcmp(format, "text/uri-list") == 0) {
     format = kURLDataMime;
+  }
 
   nsCOMPtr<nsITransferable> trans =
     do_CreateInstance("@mozilla.org/widget/transferable;1");
-  if (!trans)
+  if (!trans) {
     return;
+  }
 
   trans->Init(nullptr);
   trans->AddDataFlavor(format);
 
   if (mEventMessage == ePaste) {
     MOZ_ASSERT(aIndex == 0, "index in clipboard must be 0");
 
-    nsCOMPtr<nsIClipboard> clipboard = do_GetService("@mozilla.org/widget/clipboard;1");
+    nsCOMPtr<nsIClipboard> clipboard =
+      do_GetService("@mozilla.org/widget/clipboard;1");
     if (!clipboard || mClipboardType < 0) {
       return;
     }
 
     clipboard->GetData(trans, mClipboardType);
   } else {
     nsCOMPtr<nsIDragSession> dragSession = nsContentUtils::GetDragSession();
     if (!dragSession) {
@@ -1512,68 +1579,73 @@ DataTransfer::FillAllExternalData()
           FillInExternalData(itemArray[j], i);
         }
       }
     }
   }
 }
 
 void
-DataTransfer::FillInExternalCustomTypes(uint32_t aIndex, nsIPrincipal* aPrincipal)
+DataTransfer::FillInExternalCustomTypes(uint32_t aIndex,
+                                        nsIPrincipal* aPrincipal)
 {
   TransferItem item;
   item.mFormat.AssignLiteral(kCustomTypesMime);
 
   FillInExternalData(item, aIndex);
   if (!item.mData) {
     return;
   }
 
   FillInExternalCustomTypes(item.mData, aIndex, aPrincipal);
 }
 
 void
-DataTransfer::FillInExternalCustomTypes(nsIVariant* aData, uint32_t aIndex, nsIPrincipal* aPrincipal)
+DataTransfer::FillInExternalCustomTypes(nsIVariant* aData, uint32_t aIndex,
+                                        nsIPrincipal* aPrincipal)
 {
   char* chrs;
   uint32_t len = 0;
   nsresult rv = aData->GetAsStringWithSize(&len, &chrs);
   if (NS_FAILED(rv)) {
     return;
   }
 
   nsAutoCString str;
   str.Adopt(chrs, len);
 
   nsCOMPtr<nsIInputStream> stringStream;
   NS_NewCStringInputStream(getter_AddRefs(stringStream), str);
 
-  nsCOMPtr<nsIBinaryInputStream> stream = do_CreateInstance("@mozilla.org/binaryinputstream;1");
+  nsCOMPtr<nsIBinaryInputStream> stream =
+    do_CreateInstance("@mozilla.org/binaryinputstream;1");
   stream->SetInputStream(stringStream);
   if (!stream) {
     return;
   }
 
   uint32_t type;
   do {
     stream->Read32(&type);
     if (type == eCustomClipboardTypeId_String) {
       uint32_t formatLength;
       stream->Read32(&formatLength);
       char* formatBytes;
       stream->ReadBytes(formatLength, &formatBytes);
       nsAutoString format;
-      format.Adopt(reinterpret_cast<char16_t*>(formatBytes), formatLength / sizeof(char16_t));
+      format.Adopt(reinterpret_cast<char16_t*>(formatBytes),
+                   formatLength / sizeof(char16_t));
 
       uint32_t dataLength;
       stream->Read32(&dataLength);
       char* dataBytes;
       stream->ReadBytes(dataLength, &dataBytes);
       nsAutoString data;
-      data.Adopt(reinterpret_cast<char16_t*>(dataBytes), dataLength / sizeof(char16_t));
+      data.Adopt(reinterpret_cast<char16_t*>(dataBytes),
+                 dataLength / sizeof(char16_t));
 
       RefPtr<nsVariantCC> variant = new nsVariantCC();
       variant->SetAsAString(data);
 
       SetDataWithPrincipal(format, variant, aIndex, aPrincipal);
     }
   } while (type != eCustomClipboardTypeId_None);
 }
--- a/dom/events/DataTransfer.h
+++ b/dom/events/DataTransfer.h
@@ -63,17 +63,20 @@ public:
 
   NS_DECL_CYCLE_COLLECTING_ISUPPORTS
   NS_DECL_NSIDOMDATATRANSFER
 
   NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(DataTransfer)
 
   friend class mozilla::EventStateManager;
 
-  static DataTransfer* Cast(nsIDOMDataTransfer* aArg) { return static_cast<DataTransfer*>(aArg); }
+  static DataTransfer* Cast(nsIDOMDataTransfer* aArg)
+  {
+    return static_cast<DataTransfer*>(aArg);
+  }
 
 protected:
 
   // hide the default constructor
   DataTransfer();
 
   // this constructor is used only by the Clone method to copy the fields as
   // needed to a new data transfer.
@@ -98,23 +101,25 @@ public:
 
   // Constructor for DataTransfer.
   //
   // aIsExternal must only be true when used to create a dataTransfer for a
   // paste or a drag that was started without using a data transfer. The
   // latter will occur when an external drag occurs, that is, a drag where the
   // source is another application, or a drag is started by calling the drag
   // service directly. For clipboard operations, aClipboardType indicates
-  // which clipboard to use, from nsIClipboard, or -1 for non-clipboard operations,
-  // or if access to the system clipboard should not be allowed.
+  // which clipboard to use, from nsIClipboard, or -1 for non-clipboard
+  // operations, or if access to the system clipboard should not be allowed.
   DataTransfer(nsISupports* aParent, EventMessage aEventMessage,
                bool aIsExternal, int32_t aClipboardType);
 
-  virtual JSObject* WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) override;
-  nsISupports* GetParentObject()
+  virtual JSObject*
+  WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) override;
+
+  nsISupports* GetParentObject() const
   {
     return mParent;
   }
 
   void SetParentObject(nsISupports* aNewParent)
   {
     MOZ_ASSERT(aNewParent);
     // Setting the parent after we've been wrapped is pointless, so
@@ -126,91 +131,109 @@ public:
   static already_AddRefed<DataTransfer>
   Constructor(const GlobalObject& aGlobal, const nsAString& aEventType,
               bool aIsExternal, ErrorResult& aRv);
 
   void GetDropEffect(nsString& aDropEffect)
   {
     aDropEffect.AssignASCII(sEffects[mDropEffect]);
   }
+
   void GetEffectAllowed(nsString& aEffectAllowed)
   {
     if (mEffectAllowed == nsIDragService::DRAGDROP_ACTION_UNINITIALIZED) {
       aEffectAllowed.AssignLiteral("uninitialized");
     } else {
       aEffectAllowed.AssignASCII(sEffects[mEffectAllowed]);
     }
   }
+
   void SetDragImage(Element& aElement, int32_t aX, int32_t aY,
                     ErrorResult& aRv);
-  already_AddRefed<DOMStringList> Types();
+
+  already_AddRefed<DOMStringList> Types() const;
+
   void GetData(const nsAString& aFormat, nsAString& aData, ErrorResult& aRv);
+
   void SetData(const nsAString& aFormat, const nsAString& aData,
                ErrorResult& aRv);
+
   void ClearData(const mozilla::dom::Optional<nsAString>& aFormat,
                  mozilla::ErrorResult& aRv);
+
   FileList* GetFiles(mozilla::ErrorResult& aRv);
 
   already_AddRefed<Promise> GetFilesAndDirectories(ErrorResult& aRv);
 
   void AddElement(Element& aElement, mozilla::ErrorResult& aRv);
-  uint32_t MozItemCount()
+
+  uint32_t MozItemCount() const
   {
     return mItems.Length();
   }
+
   void GetMozCursor(nsString& aCursor)
   {
     if (mCursorState) {
       aCursor.AssignLiteral("default");
     } else {
       aCursor.AssignLiteral("auto");
     }
   }
+
   already_AddRefed<DOMStringList> MozTypesAt(uint32_t aIndex,
                                              mozilla::ErrorResult& aRv);
+
   void MozClearDataAt(const nsAString& aFormat, uint32_t aIndex,
                       mozilla::ErrorResult& aRv);
+
   void MozSetDataAt(JSContext* aCx, const nsAString& aFormat,
                     JS::Handle<JS::Value> aData, uint32_t aIndex,
                     mozilla::ErrorResult& aRv);
+
   void MozGetDataAt(JSContext* aCx, const nsAString& aFormat,
                     uint32_t aIndex, JS::MutableHandle<JS::Value> aRetval,
                     mozilla::ErrorResult& aRv);
-  bool MozUserCancelled()
+
+  bool MozUserCancelled() const
   {
     return mUserCancelled;
   }
+
   already_AddRefed<nsINode> GetMozSourceNode();
 
-  mozilla::dom::Element* GetDragTarget()
+  mozilla::dom::Element* GetDragTarget() const
   {
     return mDragTarget;
   }
 
   nsresult GetDataAtNoSecurityCheck(const nsAString& aFormat, uint32_t aIndex,
                                     nsIVariant** aData);
 
-  // a readonly dataTransfer cannot have new data added or existing data removed.
-  // Only the dropEffect and effectAllowed may be modified.
+  // a readonly dataTransfer cannot have new data added or existing data
+  // removed. Only the dropEffect and effectAllowed may be modified.
   void SetReadOnly() { mReadOnly = true; }
 
   // converts the data into an array of nsITransferable objects to be used for
   // drag and drop or clipboard operations.
   already_AddRefed<nsISupportsArray> GetTransferables(nsIDOMNode* aDragTarget);
-  already_AddRefed<nsISupportsArray> GetTransferables(nsILoadContext* aLoadContext);
+
+  already_AddRefed<nsISupportsArray>
+  GetTransferables(nsILoadContext* aLoadContext);
 
-  // converts the data for a single item at aIndex into an nsITransferable object.
-  already_AddRefed<nsITransferable> GetTransferable(uint32_t aIndex,
-                                                    nsILoadContext* aLoadContext);
+  // converts the data for a single item at aIndex into an nsITransferable
+  // object.
+  already_AddRefed<nsITransferable>
+  GetTransferable(uint32_t aIndex, nsILoadContext* aLoadContext);
 
   // converts the data in the variant to an nsISupportString if possible or
   // an nsISupports or null otherwise.
   bool ConvertFromVariant(nsIVariant* aVariant,
-                            nsISupports** aSupports,
-                            uint32_t* aLength);
+                          nsISupports** aSupports,
+                          uint32_t* aLength) const;
 
   // clears all of the data
   void ClearAll();
 
   // Similar to SetData except also specifies the principal to store.
   // aData may be null when called from CacheExternalDragFormats or
   // CacheExternalClipboardFormats.
   nsresult SetDataWithPrincipal(const nsAString& aFormat,
@@ -221,67 +244,74 @@ public:
   // Variation of SetDataWithPrincipal with handles extracting
   // kCustomTypesMime data into separate types.
   void SetDataWithPrincipalFromOtherProcess(const nsAString& aFormat,
                                             nsIVariant* aData,
                                             uint32_t aIndex,
                                             nsIPrincipal* aPrincipal);
 
   // returns a weak reference to the drag image
-  Element* GetDragImage(int32_t* aX, int32_t* aY)
+  Element* GetDragImage(int32_t* aX, int32_t* aY) const
   {
     *aX = mDragImageX;
     *aY = mDragImageY;
     return mDragImage;
   }
 
   nsresult Clone(nsISupports* aParent, EventMessage aEventMessage,
                  bool aUserCancelled, bool aIsCrossDomainSubFrameDrop,
                  DataTransfer** aResult);
 
 protected:
 
   // converts some formats used for compatibility in aInFormat into aOutFormat.
   // Text and text/unicode become text/plain, and URL becomes text/uri-list
-  void GetRealFormat(const nsAString& aInFormat, nsAString& aOutFormat);
+  void GetRealFormat(const nsAString& aInFormat, nsAString& aOutFormat) const;
 
   // caches text and uri-list data formats that exist in the drag service or
   // clipboard for retrieval later.
-  void CacheExternalData(const char* aFormat, uint32_t aIndex, nsIPrincipal* aPrincipal);
+  void CacheExternalData(const char* aFormat, uint32_t aIndex,
+                         nsIPrincipal* aPrincipal);
 
   // caches the formats that exist in the drag service that were added by an
   // external drag
   void CacheExternalDragFormats();
 
   // caches the formats that exist in the clipboard
   void CacheExternalClipboardFormats();
 
   // fills in the data field of aItem with the data from the drag service or
   // clipboard for a given index.
   void FillInExternalData(TransferItem& aItem, uint32_t aIndex);
 
 
-  FileList* GetFileListInternal(ErrorResult& aRv, nsIPrincipal* aSubjectPrincipal);
+  FileList* GetFileListInternal(ErrorResult& aRv,
+                                nsIPrincipal* aSubjectPrincipal);
+
   nsresult GetDataAtInternal(const nsAString& aFormat, uint32_t aIndex,
-                             nsIPrincipal* aSubjectPrincipal, nsIVariant** aData);
-  nsresult SetDataAtInternal(const nsAString& aFormat, nsIVariant* aData, uint32_t aIndex,
-                             nsIPrincipal* aSubjectPrincipal);
+                             nsIPrincipal* aSubjectPrincipal,
+                             nsIVariant** aData);
+
+  nsresult SetDataAtInternal(const nsAString& aFormat, nsIVariant* aData,
+                             uint32_t aIndex, nsIPrincipal* aSubjectPrincipal);
 
   friend class ContentParent;
+
   void FillAllExternalData();
 
   void FillInExternalCustomTypes(uint32_t aIndex, nsIPrincipal* aPrincipal);
-  void FillInExternalCustomTypes(nsIVariant* aData, uint32_t aIndex, nsIPrincipal* aPrincipal);
+
+  void FillInExternalCustomTypes(nsIVariant* aData, uint32_t aIndex,
+                                 nsIPrincipal* aPrincipal);
 
   void MozClearDataAtHelper(const nsAString& aFormat, uint32_t aIndex,
                             mozilla::ErrorResult& aRv);
 
   nsCOMPtr<nsISupports> mParent;
 
-
   // the drop effect and effect allowed
   uint32_t mDropEffect;
   uint32_t mEffectAllowed;
 
   // the event message this data transfer is for. This will correspond to an
   // event->mMessage value.
   EventMessage mEventMessage;
 
--- a/dom/events/EventDispatcher.cpp
+++ b/dom/events/EventDispatcher.cpp
@@ -7,17 +7,27 @@
 #include "nsPresContext.h"
 #include "nsContentUtils.h"
 #include "nsError.h"
 #include <new>
 #include "nsIContent.h"
 #include "nsIDocument.h"
 #include "nsINode.h"
 #include "nsPIDOMWindow.h"
+#include "AnimationEvent.h"
+#include "BeforeAfterKeyboardEvent.h"
+#include "BeforeUnloadEvent.h"
+#include "ClipboardEvent.h"
+#include "CommandEvent.h"
+#include "CompositionEvent.h"
+#include "DataContainerEvent.h"
+#include "DeviceMotionEvent.h"
+#include "DragEvent.h"
 #include "GeckoProfiler.h"
+#include "KeyboardEvent.h"
 #include "mozilla/ContentEvents.h"
 #include "mozilla/dom/CloseEvent.h"
 #include "mozilla/dom/CustomEvent.h"
 #include "mozilla/dom/DeviceOrientationEvent.h"
 #include "mozilla/dom/EventTarget.h"
 #include "mozilla/dom/FocusEvent.h"
 #include "mozilla/dom/HashChangeEvent.h"
 #include "mozilla/dom/InputEvent.h"
--- a/dom/events/EventNameList.h
+++ b/dom/events/EventNameList.h
@@ -241,16 +241,24 @@ EVENT(keydown,
 EVENT(keypress,
       eKeyPress,
       EventNameType_HTMLXUL,
       eKeyboardEventClass)
 EVENT(keyup,
       eKeyUp,
       EventNameType_HTMLXUL,
       eKeyboardEventClass)
+EVENT(mozkeydownonplugin,
+      eKeyDownOnPlugin,
+      EventNameType_None,
+      eKeyboardEventClass)
+EVENT(mozkeyuponplugin,
+      eKeyUpOnPlugin,
+      EventNameType_None,
+      eKeyboardEventClass)
 NON_IDL_EVENT(mozbrowserbeforekeydown,
               eBeforeKeyDown,
               EventNameType_None,
               eBeforeAfterKeyboardEventClass)
 NON_IDL_EVENT(mozbrowserafterkeydown,
               eAfterKeyDown,
               EventNameType_None,
               eBeforeAfterKeyboardEventClass)
--- a/dom/events/IMEContentObserver.cpp
+++ b/dom/events/IMEContentObserver.cpp
@@ -1547,22 +1547,28 @@ IMEContentObserver::IMENotificationSende
       SendPositionChange();
     }
   }
 
   mIMEContentObserver->mQueuedSender = nullptr;
 
   // If notifications caused some new change, we should notify them now.
   if (mIMEContentObserver->NeedsToNotifyIMEOfSomething()) {
-    MOZ_LOG(sIMECOLog, LogLevel::Debug,
-      ("IMECO: 0x%p IMEContentObserver::IMENotificationSender::Run(), "
-       "posting IMENotificationSender to current thread", this));
-    mIMEContentObserver->mQueuedSender =
-      new IMENotificationSender(mIMEContentObserver);
-    NS_DispatchToCurrentThread(mIMEContentObserver->mQueuedSender);
+    if (mIMEContentObserver->GetState() == eState_StoppedObserving) {
+      MOZ_LOG(sIMECOLog, LogLevel::Debug,
+        ("IMECO: 0x%p IMEContentObserver::IMENotificationSender::Run(), "
+         "waiting IMENotificationSender to be reinitialized", this));
+    } else {
+      MOZ_LOG(sIMECOLog, LogLevel::Debug,
+        ("IMECO: 0x%p IMEContentObserver::IMENotificationSender::Run(), "
+         "posting IMENotificationSender to current thread", this));
+      mIMEContentObserver->mQueuedSender =
+        new IMENotificationSender(mIMEContentObserver);
+      NS_DispatchToCurrentThread(mIMEContentObserver->mQueuedSender);
+    }
   }
   return NS_OK;
 }
 
 void
 IMEContentObserver::IMENotificationSender::SendFocusSet()
 {
   if (!CanNotifyIME(eChangeEventType_Focus)) {
--- a/dom/events/KeyboardEvent.cpp
+++ b/dom/events/KeyboardEvent.cpp
@@ -185,19 +185,21 @@ KeyboardEvent::CharCode()
   // If this event is initialized with ctor, we shouldn't check event type.
   if (mInitializedByCtor) {
     return mEvent->AsKeyboardEvent()->charCode;
   }
 
   switch (mEvent->mMessage) {
   case eBeforeKeyDown:
   case eKeyDown:
+  case eKeyDownOnPlugin:
   case eAfterKeyDown:
   case eBeforeKeyUp:
   case eKeyUp:
+  case eKeyUpOnPlugin:
   case eAfterKeyUp:
     return 0;
   case eKeyPress:
     return mEvent->AsKeyboardEvent()->charCode;
   default:
     break;
   }
   return 0;
@@ -231,19 +233,21 @@ KeyboardEvent::Which()
   // If this event is initialized with ctor, which can have independent value.
   if (mInitializedByCtor) {
     return mInitializedWhichValue;
   }
 
   switch (mEvent->mMessage) {
     case eBeforeKeyDown:
     case eKeyDown:
+    case eKeyDownOnPlugin:
     case eAfterKeyDown:
     case eBeforeKeyUp:
     case eKeyUp:
+    case eKeyUpOnPlugin:
     case eAfterKeyUp:
       return KeyCode();
     case eKeyPress:
       //Special case for 4xp bug 62878.  Try to make value of which
       //more closely mirror the values that 4.x gave for RETURN and BACKSPACE
       {
         uint32_t keyCode = mEvent->AsKeyboardEvent()->keyCode;
         if (keyCode == NS_VK_RETURN || keyCode == NS_VK_BACK) {
--- a/dom/geolocation/nsGeolocation.cpp
+++ b/dom/geolocation/nsGeolocation.cpp
@@ -786,17 +786,17 @@ nsGeolocationRequest::Shutdown()
 // nsGeolocationRequest::TimerCallbackHolder
 ////////////////////////////////////////////////////
 
 NS_IMPL_ISUPPORTS(nsGeolocationRequest::TimerCallbackHolder, nsISupports, nsITimerCallback)
 
 NS_IMETHODIMP
 nsGeolocationRequest::TimerCallbackHolder::Notify(nsITimer*)
 {
-  if (mRequest) {
+  if (mRequest && mRequest->mLocator) {
     RefPtr<nsGeolocationRequest> request(mRequest);
     request->Notify();
   }
   return NS_OK;
 }
 
 
 ////////////////////////////////////////////////////
--- a/dom/html/HTMLSharedElement.cpp
+++ b/dom/html/HTMLSharedElement.cpp
@@ -56,23 +56,25 @@ NS_IMPL_URI_ATTR(HTMLSharedElement, Cite
 NS_IMPL_STRING_ATTR(HTMLSharedElement, Version, version)
 
 // nsIDOMHTMLBaseElement
 NS_IMPL_STRING_ATTR(HTMLSharedElement, Target, target)
 
 NS_IMETHODIMP
 HTMLSharedElement::GetHref(nsAString& aValue)
 {
+  MOZ_ASSERT(mNodeInfo->Equals(nsGkAtoms::base),
+             "This should only get called for <base> elements");
   nsAutoString href;
   GetAttr(kNameSpaceID_None, nsGkAtoms::href, href);
 
   nsCOMPtr<nsIURI> uri;
   nsIDocument* doc = OwnerDoc();
   nsContentUtils::NewURIWithDocumentCharset(
-    getter_AddRefs(uri), href, doc, doc->GetDocumentURI());
+    getter_AddRefs(uri), href, doc, doc->GetFallbackBaseURI());
 
   if (!uri) {
     aValue = href;
     return NS_OK;
   }
   
   nsAutoCString spec;
   uri->GetSpec(spec);
--- a/dom/interfaces/security/nsIContentSecurityPolicy.idl
+++ b/dom/interfaces/security/nsIContentSecurityPolicy.idl
@@ -16,16 +16,22 @@ interface nsIURI;
  * nsIContentSecurityPolicy
  * Describes an XPCOM component used to model and enforce CSPs.  Instances of
  * this class may have multiple policies within them, but there should only be
  * one of these per document/principal.
  */
 
 typedef unsigned short CSPDirective;
 
+%{C++
+class nsCSPPolicy;
+%}
+
+[ptr] native CSPPolicyPtr(const nsCSPPolicy);
+
 [scriptable, builtinclass, uuid(b3c4c0ae-bd5e-4cad-87e0-8d210dbb3f9f)]
 interface nsIContentSecurityPolicy : nsISerializable
 {
   /**
    * Directives supported by Content Security Policy.  These are enums for
    * the CSPDirective type.
    * The NO_DIRECTIVE entry is  used for checking default permissions and
    * returning failure when asking CSP which directive to check.
@@ -56,16 +62,23 @@ interface nsIContentSecurityPolicy : nsI
 
   /**
    * Accessor method for a read-only string version of the policy at a given
    * index.
    */
   AString getPolicy(in unsigned long index);
 
   /**
+   * Accessor method for a read-only pointer the policy object at a given
+   * index. Returns a null pointer if the index is larger than the current
+   * policy count.
+   */
+  [noscript,notxpcom,nostdcall] CSPPolicyPtr GetPolicy(in unsigned long index);
+
+  /**
    * Returns the number of policies attached to this CSP instance.  Useful with
    * getPolicy().
    */
   readonly attribute unsigned long policyCount;
 
   /**
    * Returns whether this policy uses the directive upgrade-insecure-requests.
    * Please note that upgrade-insecure-reqeusts also applies if the parent or
--- a/dom/ipc/ContentParent.cpp
+++ b/dom/ipc/ContentParent.cpp
@@ -1994,24 +1994,16 @@ ContentParent::RecvDeallocateLayerTreeId
     // You can't deallocate layer tree ids that you didn't allocate
     KillHard("DeallocateLayerTreeId");
   }
   return true;
 }
 
 namespace {
 
-void
-DelayedDeleteSubprocess(GeckoChildProcessHost* aSubprocess)
-{
-  XRE_GetIOMessageLoop()
-    ->PostTask(FROM_HERE,
-           new DeleteTask<GeckoChildProcessHost>(aSubprocess));
-}
-
 // This runnable only exists to delegate ownership of the
 // ContentParent to this runnable, until it's deleted by the event
 // system.
 struct DelayedDeleteContentParentTask : public nsRunnable
 {
   explicit DelayedDeleteContentParentTask(ContentParent* aObj) : mObj(aObj) { }
 
   // No-op
@@ -2133,20 +2125,20 @@ ContentParent::ActorDestroy(ActorDestroy
   MOZ_ASSERT(idleService);
   RefPtr<ParentIdleListener> listener;
   for (int32_t i = mIdleListeners.Length() - 1; i >= 0; --i) {
     listener = static_cast<ParentIdleListener*>(mIdleListeners[i].get());
     idleService->RemoveIdleObserver(listener, listener->mTime);
   }
   mIdleListeners.Clear();
 
-  MessageLoop::current()->
-    PostTask(FROM_HERE,
-             NewRunnableFunction(DelayedDeleteSubprocess, mSubprocess));
-  mSubprocess = nullptr;
+  if (mSubprocess) {
+    mSubprocess->DissociateActor();
+    mSubprocess = nullptr;
+  }
 
   // IPDL rules require actors to live on past ActorDestroy, but it
   // may be that the kungFuDeathGrip above is the last reference to
   // |this|.  If so, when we go out of scope here, we're deleted and
   // all hell breaks loose.
   //
   // This runnable ensures that a reference to |this| lives on at
   // least until after the current task finishes running.
@@ -3348,31 +3340,31 @@ ContentParent::DeallocPAPZParent(PAPZPar
 {
   return true;
 }
 
 PCompositorBridgeParent*
 ContentParent::AllocPCompositorBridgeParent(mozilla::ipc::Transport* aTransport,
                                             base::ProcessId aOtherProcess)
 {
-  return CompositorBridgeParent::Create(aTransport, aOtherProcess);
+  return CompositorBridgeParent::Create(aTransport, aOtherProcess, mSubprocess);
 }
 
 gfx::PVRManagerParent*
 ContentParent::AllocPVRManagerParent(Transport* aTransport,
                                      ProcessId aOtherProcess)
 {
   return gfx::VRManagerParent::CreateCrossProcess(aTransport, aOtherProcess);
 }
 
 PImageBridgeParent*
 ContentParent::AllocPImageBridgeParent(mozilla::ipc::Transport* aTransport,
                                        base::ProcessId aOtherProcess)
 {
-  return ImageBridgeParent::Create(aTransport, aOtherProcess);
+  return ImageBridgeParent::Create(aTransport, aOtherProcess, mSubprocess);
 }
 
 PBackgroundParent*
 ContentParent::AllocPBackgroundParent(Transport* aTransport,
                                       ProcessId aOtherProcess)
 {
   return BackgroundParent::Alloc(this, aTransport, aOtherProcess);
 }
--- a/dom/ipc/PBrowser.ipdl
+++ b/dom/ipc/PBrowser.ipdl
@@ -62,16 +62,17 @@ using mozilla::WritingMode from "mozilla
 using mozilla::layers::TouchBehaviorFlags from "mozilla/layers/APZUtils.h";
 using nsIWidget::TouchPointerState from "nsIWidget.h";
 using struct LookAndFeelInt from "mozilla/widget/WidgetMessageUtils.h";
 using class mozilla::dom::ipc::StructuredCloneData from "ipc/IPCMessageUtils.h";
 using mozilla::EventMessage from "mozilla/EventForwards.h";
 using nsEventStatus from "mozilla/EventForwards.h";
 using nsSizeMode from "nsIWidgetListener.h";
 using mozilla::widget::CandidateWindowPosition from "ipc/nsGUIEventIPC.h";
+using class mozilla::NativeEventData from "ipc/nsGUIEventIPC.h";
 
 namespace mozilla {
 namespace dom {
 
 struct NativeKeyBinding
 {
   CommandInt[] singleLineCommands;
   CommandInt[] multiLineCommands;
@@ -279,16 +280,26 @@ parent:
     prio(urgent) async SetPluginFocused(bool aFocused);
 
     /**
      * Set IME candidate window by windowless plugin if plugin has focus.
      */
     async SetCandidateWindowForPlugin(CandidateWindowPosition aPosition);
 
     /**
+     * Notifies the parent process of native key event data received in a
+     * plugin process directly.
+     *
+     * aKeyEventData    The native key event data.  The actual type copied into
+     *                  NativeEventData depending on the caller.  Please check
+     *                  PluginInstanceChild.
+     */
+    prio(urgent) async OnWindowedPluginKeyEvent(NativeEventData aKeyEventData);
+
+    /**
      *  When plugin event isn't consumed, call this
      */
     async DefaultProcOfPluginEvent(WidgetPluginEvent aEvent);
 
     /**
      * Request that the parent process move focus to the browser's frame. If
      * canRaise is true, the window can be raised if it is inactive.
      */
@@ -727,16 +738,28 @@ child:
 
     /**
      * Tells the root child docShell whether or not to use
      * global history. This is sent right after the PBrowser
      * is bound to a frameloader element.
      */
     async SetUseGlobalHistory(bool aUse);
 
+    /**
+     * HandledWindowedPluginKeyEvent() is always called after posting a native
+     * key event with OnWindowedPluginKeyEvent().
+     *
+     * @param aKeyEventData      The key event which was posted to the parent
+     *                           process.
+     * @param aIsConsumed        true if aKeyEventData is consumed in the
+     *                           parent process.  Otherwise, false.
+     */
+    async HandledWindowedPluginKeyEvent(NativeEventData aKeyEventData,
+                                        bool aIsConsumed);
+
 /*
  * FIXME: write protocol!
 
 state LIVE:
     send LoadURL goto LIVE;
 //etc.
     send Destroy goto DYING;
 
--- a/dom/ipc/TabChild.cpp
+++ b/dom/ipc/TabChild.cpp
@@ -1650,23 +1650,25 @@ TabChild::RecvUpdateDimensions(const CSS
 
     return true;
 }
 
 bool
 TabChild::RecvSizeModeChanged(const nsSizeMode& aSizeMode)
 {
   mPuppetWidget->SetSizeMode(aSizeMode);
+  if (!mPuppetWidget->IsVisible()) {
+    return true;
+  }
   nsCOMPtr<nsIDocument> document(GetDocument());
   nsCOMPtr<nsIPresShell> presShell = document->GetShell();
   if (presShell) {
     nsPresContext* presContext = presShell->GetPresContext();
     if (presContext) {
-      presContext->MediaFeatureValuesChangedAllDocuments(eRestyle_Subtree,
-                                                         NS_STYLE_HINT_REFLOW);
+      presContext->SizeModeChanged(aSizeMode);
     }
   }
   return true;
 }
 
 bool
 TabChild::UpdateFrame(const FrameMetrics& aFrameMetrics)
 {
@@ -2478,16 +2480,28 @@ TabChild::RecvNavigateByKey(const bool& 
     }
 
     SendRequestFocus(false);
   }
 
   return true;
 }
 
+bool
+TabChild::RecvHandledWindowedPluginKeyEvent(
+            const NativeEventData& aKeyEventData,
+            const bool& aIsConsumed)
+{
+  if (NS_WARN_IF(!mPuppetWidget)) {
+    return true;
+  }
+  mPuppetWidget->HandledWindowedPluginKeyEvent(aKeyEventData, aIsConsumed);
+  return true;
+}
+
 PRenderFrameChild*
 TabChild::AllocPRenderFrameChild()
 {
     return new RenderFrameChild();
 }
 
 bool
 TabChild::DeallocPRenderFrameChild(PRenderFrameChild* aFrame)
--- a/dom/ipc/TabChild.h
+++ b/dom/ipc/TabChild.h
@@ -570,16 +570,20 @@ public:
                                    const int32_t& aModifierMask) override;
 
   virtual bool RecvAudioChannelChangeNotification(const uint32_t& aAudioChannel,
                                                   const float& aVolume,
                                                   const bool& aMuted) override;
 
   virtual bool RecvSetUseGlobalHistory(const bool& aUse) override;
 
+  virtual bool RecvHandledWindowedPluginKeyEvent(
+                 const mozilla::NativeEventData& aKeyEventData,
+                 const bool& aIsConsumed) override;
+
   /**
    * Native widget remoting protocol for use with windowed plugins with e10s.
    */
   PPluginWidgetChild* AllocPPluginWidgetChild() override;
 
   bool DeallocPPluginWidgetChild(PPluginWidgetChild* aActor) override;
 
   nsresult CreatePluginWidget(nsIWidget* aParent, nsIWidget** aOut);
--- a/dom/ipc/TabParent.cpp
+++ b/dom/ipc/TabParent.cpp
@@ -1894,16 +1894,54 @@ TabParent::RecvOnEventNeedingAckHandled(
 
   // While calling OnEventNeedingAckHandled(), TabParent *might* be destroyed
   // since it may send notifications to IME.
   RefPtr<TabParent> kungFuDeathGrip(this);
   mContentCache.OnEventNeedingAckHandled(widget, aMessage);
   return true;
 }
 
+void
+TabParent::HandledWindowedPluginKeyEvent(const NativeEventData& aKeyEventData,
+                                         bool aIsConsumed)
+{
+  bool ok = SendHandledWindowedPluginKeyEvent(aKeyEventData, aIsConsumed);
+  NS_WARN_IF(!ok);
+}
+
+bool
+TabParent::RecvOnWindowedPluginKeyEvent(const NativeEventData& aKeyEventData)
+{
+  nsCOMPtr<nsIWidget> widget = GetWidget();
+  if (NS_WARN_IF(!widget)) {
+    // Notifies the plugin process of the key event being not consumed by us.
+    HandledWindowedPluginKeyEvent(aKeyEventData, false);
+    return true;
+  }
+  nsresult rv = widget->OnWindowedPluginKeyEvent(aKeyEventData, this);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    // Notifies the plugin process of the key event being not consumed by us.
+    HandledWindowedPluginKeyEvent(aKeyEventData, false);
+    return true;
+  }
+
+  // If the key event is posted to another process, we need to wait a call
+  // of HandledWindowedPluginKeyEvent().  So, nothing to do here in this case.
+  if (rv == NS_SUCCESS_EVENT_HANDLED_ASYNCHRONOUSLY) {
+    return true;
+  }
+
+  // Otherwise, the key event is handled synchronously.  Let's notify the
+  // plugin process of the key event's result.
+  bool consumed = (rv == NS_SUCCESS_EVENT_CONSUMED);
+  HandledWindowedPluginKeyEvent(aKeyEventData, consumed);
+
+  return true;
+}
+
 bool
 TabParent::RecvRequestFocus(const bool& aCanRaise)
 {
   nsCOMPtr<nsIFocusManager> fm = nsFocusManager::GetFocusManager();
   if (!fm) {
     return true;
   }
 
--- a/dom/ipc/TabParent.h
+++ b/dom/ipc/TabParent.h
@@ -17,16 +17,17 @@
 #include "mozilla/dom/TabContext.h"
 #include "mozilla/EventForwards.h"
 #include "mozilla/dom/File.h"
 #include "mozilla/RefPtr.h"
 #include "nsCOMPtr.h"
 #include "nsIAuthPromptProvider.h"
 #include "nsIBrowserDOMWindow.h"
 #include "nsIDOMEventListener.h"
+#include "nsIKeyEventInPluginCallback.h"
 #include "nsISecureBrowserUI.h"
 #include "nsITabParent.h"
 #include "nsIWebBrowserPersistable.h"
 #include "nsIXULBrowserWindow.h"
 #include "nsRefreshDriver.h"
 #include "nsWeakReference.h"
 #include "Units.h"
 #include "nsIWidget.h"
@@ -77,16 +78,17 @@ namespace ipc {
 class StructuredCloneData;
 } // ipc namespace
 
 class TabParent final : public PBrowserParent
                       , public nsIDOMEventListener
                       , public nsITabParent
                       , public nsIAuthPromptProvider
                       , public nsISecureBrowserUI
+                      , public nsIKeyEventInPluginCallback
                       , public nsSupportsWeakReference
                       , public TabContext
                       , public nsAPostRefreshObserver
                       , public nsIWebBrowserPersistable
 {
   typedef mozilla::dom::ClonedMessageData ClonedMessageData;
 
   virtual ~TabParent();
@@ -250,16 +252,25 @@ public:
   virtual bool RecvSetInputContext(const int32_t& aIMEEnabled,
                                    const int32_t& aIMEOpen,
                                    const nsString& aType,
                                    const nsString& aInputmode,
                                    const nsString& aActionHint,
                                    const int32_t& aCause,
                                    const int32_t& aFocusChange) override;
 
+
+  // See nsIKeyEventInPluginCallback
+  virtual void HandledWindowedPluginKeyEvent(
+                 const NativeEventData& aKeyEventData,
+                 bool aIsConsumed) override;
+
+  virtual bool RecvOnWindowedPluginKeyEvent(
+                 const NativeEventData& aKeyEventData) override;
+
   virtual bool RecvRequestFocus(const bool& aCanRaise) override;
 
   virtual bool
   RecvEnableDisableCommands(const nsString& aAction,
                             nsTArray<nsCString>&& aEnabledCommands,
                             nsTArray<nsCString>&& aDisabledCommands) override;
 
   virtual bool
--- a/dom/locales/en-US/chrome/appstrings.properties
+++ b/dom/locales/en-US/chrome/appstrings.properties
@@ -33,8 +33,9 @@ malwareBlocked=The site at %S has been r
 unwantedBlocked=The site at %S has been reported as serving unwanted software and has been blocked based on your security preferences.
 deceptiveBlocked=This web page at %S has been reported as a deceptive site and has been blocked based on your security preferences.
 forbiddenBlocked=The site at %S has been blocked by your browser configuration.
 cspBlocked=This page has a content security policy that prevents it from being loaded in this way.
 corruptedContentError=The page you are trying to view cannot be shown because an error in the data transmission was detected.
 remoteXUL=This page uses an unsupported technology that is no longer available by default.
 sslv3Used=The safety of your data on %S could not be guaranteed because it uses SSLv3, a broken security protocol.
 weakCryptoUsed=The owner of %S has configured their website improperly. To protect your information from being stolen, the connection to this website has not been established.
+inadequateSecurityError=The website tried to negotiate an inadequate level of security.
--- a/dom/locales/en-US/chrome/layout/layout_errors.properties
+++ b/dom/locales/en-US/chrome/layout/layout_errors.properties
@@ -11,24 +11,24 @@ ImageMapPolyOddNumberOfCoords=The "coords" attribute of the <area shape="poly"> tag is missing the last "y" coordinate (the correct format is "x1,y1,x2,y2 …").
 TablePartRelPosWarning=Relative positioning of table rows and row groups is now supported. This site may need to be updated because it may depend on this feature having no effect.
 ScrollLinkedEffectFound2=This site appears to use a scroll-linked positioning effect. This may not work well with asynchronous panning; see https://developer.mozilla.org/docs/Mozilla/Performance/ScrollLinkedEffects for further details and to join the discussion on related tools and features!
 
 ## LOCALIZATION NOTE(AnimationWarningContentTooLarge):
 ## (%1$S, %2$S) is a pair of integer values of the frame size
 ## (%3$S, %4$S) is a pair of integer values of the viewport size
 ## (%5$S, %6$S) is a pair of integer values of the visual rectangle size
 ## (%7$S) is an integer value
-AnimationWarningContentTooLarge=Async animation disabled because frame size (%1$S, %2$S) is bigger than the viewport (%3$S, %4$S) or the visual rectangle (%5$S, %6$S) is larger than the max allowed value (%7$S)
+AnimationWarningContentTooLarge=Animation cannot be run on the compositor because the frame size (%1$S, %2$S) is bigger than the viewport (%3$S, %4$S) or the visual rectangle (%5$S, %6$S) is larger than the maximum allowed value (%7$S)
 ## LOCALIZATION NOTE(AnimationWarningTransformBackfaceVisibilityHidde):
 ## 'backface-visibility: hidden' is a CSS property, don't translate it.
-AnimationWarningTransformBackfaceVisibilityHidden=Async animation of 'backface-visibility: hidden' transforms is not supported
+AnimationWarningTransformBackfaceVisibilityHidden=Animations of 'backface-visibility: hidden' transforms cannot be run on the compositor
 ## LOCALIZATION NOTE(AnimationWarningTransformPreserve3D):
 ## 'transform-style: preserve-3d' is a CSS property, don't translate it.
-AnimationWarningTransformPreserve3D=Async animation of 'transform-style: preserve-3d' transforms is not supported
+AnimationWarningTransformPreserve3D=Animations of 'transform-style: preserve-3d' transforms cannot be run on the compositor
 ## LOCALIZATION NOTE(AnimationWarningTransformSVG,
 ##                   AnimationWarningTransformWithGeometricProperties,
 ##                   AnimationWarningTransformFrameInactive,
 ##                   AnimationWarningOpacityFrameInactive):
 ## 'transform' and 'opacity' mean CSS property names, don't translate it.
-AnimationWarningTransformSVG=Async 'transform' animations of aFrames with SVG transforms is not supported
-AnimationWarningTransformWithGeometricProperties=Async animation of 'transform' not possible due to animation of geometric properties on the same element
-AnimationWarningTransformFrameInactive=Async animation disabled because frame was not marked active for 'transform' animation
-AnimationWarningOpacityFrameInactive=Async animation disabled because frame was not marked active for 'opacity' animation
+AnimationWarningTransformSVG=Animations of 'transform' on elements with SVG transforms cannot be run on the compositor
+AnimationWarningTransformWithGeometricProperties=Animations of 'transform' cannot be run on the compositor when geometric properties are animated on the same element at the same time
+AnimationWarningTransformFrameInactive=Animation cannot be run on the compositor because the frame was not marked active for 'transform' animation
+AnimationWarningOpacityFrameInactive=Animation cannot be run on the compositor because the frame was not marked active for 'opacity' animation
--- a/dom/locales/en-US/chrome/netError.dtd
+++ b/dom/locales/en-US/chrome/netError.dtd
@@ -87,8 +87,13 @@
 <!ENTITY cspBlocked.title "Blocked by Content Security Policy">
 <!ENTITY cspBlocked.longDesc "<p>The browser prevented this page from loading in this way because the page has a content security policy that disallows it.</p>">
 
 <!ENTITY corruptedContentError.title "Corrupted Content Error">
 <!ENTITY corruptedContentError.longDesc "<p>The page you are trying to view cannot be shown because an error in the data transmission was detected.</p><ul><li>Please contact the website owners to inform them of this problem.</li></ul>">
 
 <!ENTITY remoteXUL.title "Remote XUL">
 <!ENTITY remoteXUL.longDesc "<p><ul><li>Please contact the website owners to inform them of this problem.</li></ul></p>">
+
+<!ENTITY inadequateSecurityError.title "Your connection is not secure">
+<!-- LOCALIZATION NOTE (inadequateSecurityError.longDesc) - Do not translate
+     "NS_ERROR_NET_INADEQUATE_SECURITY". -->
+<!ENTITY inadequateSecurityError.longDesc "<p><span class='hostname'></span> uses security technology that is outdated and vulnerable to attack. An attacker could easily reveal information which you thought to be safe. The website administrator will need to fix the server first before you can visit the site.</p><p>Error code: NS_ERROR_NET_INADEQUATE_SECURITY</p>">
--- a/dom/media/DOMMediaStream.cpp
+++ b/dom/media/DOMMediaStream.cpp
@@ -94,20 +94,20 @@ DOMMediaStream::TrackPort::GetSource() c
 
 TrackID
 DOMMediaStream::TrackPort::GetSourceTrackId() const
 {
   return mInputPort ? mInputPort->GetSourceTrackId() : TRACK_INVALID;
 }
 
 already_AddRefed<Pledge<bool>>
-DOMMediaStream::TrackPort::BlockTrackId(TrackID aTrackId)
+DOMMediaStream::TrackPort::BlockSourceTrackId(TrackID aTrackId)
 {
   if (mInputPort) {
-    return mInputPort->BlockTrackId(aTrackId);
+    return mInputPort->BlockSourceTrackId(aTrackId);
   }
   RefPtr<Pledge<bool>> rejected = new Pledge<bool>();
   rejected->Reject(NS_ERROR_FAILURE);
   return rejected.forget();
 }
 
 NS_IMPL_CYCLE_COLLECTION(DOMMediaStream::TrackPort, mTrack)
 NS_IMPL_CYCLE_COLLECTION_ROOT_NATIVE(DOMMediaStream::TrackPort, AddRef)
@@ -1236,17 +1236,17 @@ DOMMediaStream::CreateAndAddPlaybackStre
   aStream->AddListener(mPlaybackListener);
 }
 
 void
 DOMMediaStream::BlockPlaybackTrack(TrackPort* aTrack)
 {
   MOZ_ASSERT(aTrack);
   ++mTracksPendingRemoval;
-  RefPtr<Pledge<bool>> p = aTrack->BlockTrackId(aTrack->GetTrack()->mTrackID);
+  RefPtr<Pledge<bool>> p = aTrack->BlockSourceTrackId(aTrack->GetTrack()->mTrackID);
   RefPtr<DOMMediaStream> self = this;
   p->Then([self] (const bool& aIgnore) { self->NotifyPlaybackTrackBlocked(); },
           [] (const nsresult& aIgnore) { NS_ERROR("Could not remove track from MSG"); }
   );
 }
 
 void
 DOMMediaStream::NotifyPlaybackTrackBlocked()
--- a/dom/media/DOMMediaStream.h
+++ b/dom/media/DOMMediaStream.h
@@ -298,17 +298,17 @@ public:
     MediaInputPort* GetInputPort() const { return mInputPort; }
     MediaStreamTrack* GetTrack() const { return mTrack; }
 
     /**
      * Blocks aTrackId from going into mInputPort unless the port has been
      * destroyed. Returns a pledge that gets resolved when the MediaStreamGraph
      * has applied the block in the playback stream.
      */
-    already_AddRefed<media::Pledge<bool, nsresult>> BlockTrackId(TrackID aTrackId);
+    already_AddRefed<media::Pledge<bool, nsresult>> BlockSourceTrackId(TrackID aTrackId);
 
   private:
     RefPtr<MediaInputPort> mInputPort;
     RefPtr<MediaStreamTrack> mTrack;
 
     // Defines if we've been given ownership of the input port or if it's owned
     // externally. The owner is responsible for destroying the port.
     const InputPortOwnership mOwnership;
--- a/dom/media/GraphDriver.cpp
+++ b/dom/media/GraphDriver.cpp
@@ -83,28 +83,16 @@ void GraphDriver::SetGraphTime(GraphDriv
                                  ? "AudioCallbackDriver"
                                  : "SystemClockDriver"));
   SetPreviousDriver(aPreviousDriver);
 }
 
 void GraphDriver::SwitchAtNextIteration(GraphDriver* aNextDriver)
 {
   GraphImpl()->GetMonitor().AssertCurrentThreadOwns();
-  // This is the situation where `mPreviousDriver` is an AudioCallbackDriver
-  // that is switching device, and the graph has found the current driver is not
-  // an AudioCallbackDriver, but tries to switch to a _new_ AudioCallbackDriver
-  // because it found audio has to be output. In this case, simply ignore the
-  // request to switch, since we know we will switch back to the old
-  // AudioCallbackDriver when it has recovered from the device switching.
-  if (aNextDriver->AsAudioCallbackDriver() &&
-      PreviousDriver() &&
-      PreviousDriver()->AsAudioCallbackDriver()->IsSwitchingDevice() &&
-      PreviousDriver() != aNextDriver) {
-    return;
-  }
   LIFECYCLE_LOG("Switching to new driver: %p (%s)",
       aNextDriver, aNextDriver->AsAudioCallbackDriver() ?
       "AudioCallbackDriver" : "SystemClockDriver");
   if (mNextDriver &&
       mNextDriver != GraphImpl()->CurrentDriver()) {
     LIFECYCLE_LOG("Discarding previous next driver: %p (%s)",
                   mNextDriver.get(), mNextDriver->AsAudioCallbackDriver() ?
                   "AudioCallbackDriver" : "SystemClockDriver");
@@ -195,33 +183,29 @@ public:
       previousDriver = mDriver->PreviousDriver();
     }
     if (previousDriver) {
       LIFECYCLE_LOG("%p releasing an AudioCallbackDriver(%p), for graph %p\n",
                     mDriver,
                     previousDriver,
                     mDriver->GraphImpl());
       MOZ_ASSERT(!mDriver->AsAudioCallbackDriver());
-      // Stop and release the previous driver off-main-thread, but only if we're
-      // not in the situation where we've fallen back to a system clock driver
-      // because the osx audio stack is currently switching output device.
-      if (!previousDriver->AsAudioCallbackDriver()->IsSwitchingDevice()) {
-        RefPtr<AsyncCubebTask> releaseEvent =
-          new AsyncCubebTask(previousDriver->AsAudioCallbackDriver(), AsyncCubebOperation::SHUTDOWN);
-        releaseEvent->Dispatch();
+      RefPtr<AsyncCubebTask> releaseEvent =
+        new AsyncCubebTask(previousDriver->AsAudioCallbackDriver(), AsyncCubebOperation::SHUTDOWN);
+      releaseEvent->Dispatch();
 
-        MonitorAutoLock mon(mDriver->mGraphImpl->GetMonitor());
-        mDriver->SetPreviousDriver(nullptr);
-      }
+      MonitorAutoLock mon(mDriver->mGraphImpl->GetMonitor());
+      mDriver->SetPreviousDriver(nullptr);
     } else {
       MonitorAutoLock mon(mDriver->mGraphImpl->GetMonitor());
       MOZ_ASSERT(mDriver->mGraphImpl->MessagesQueued() ||
                  mDriver->mGraphImpl->mForceShutDown, "Don't start a graph without messages queued.");
       mDriver->mGraphImpl->SwapMessageQueues();
     }
+
     mDriver->RunThread();
     return NS_OK;
   }
 private:
   ThreadedDriver* mDriver;
 };
 
 void
@@ -556,20 +540,17 @@ AudioCallbackDriver::AudioCallbackDriver
   , mSampleRate(0)
   , mInputChannels(1)
   , mIterationDurationMS(MEDIA_GRAPH_TARGET_PERIOD_MS)
   , mStarted(false)
   , mAudioInput(nullptr)
   , mAudioChannel(aGraphImpl->AudioChannel())
   , mAddedMixer(false)
   , mInCallback(false)
-#ifdef XP_MACOSX
   , mMicrophoneActive(false)
-  , mCallbackReceivedWhileSwitching(0)
-#endif
 {
   STREAM_LOG(LogLevel::Debug, ("AudioCallbackDriver ctor for graph %p", aGraphImpl));
 }
 
 AudioCallbackDriver::~AudioCallbackDriver()
 {
   MOZ_ASSERT(mPromisesForOperation.IsEmpty());
 }
@@ -655,22 +636,19 @@ AudioCallbackDriver::Init()
       MonitorAutoLock lock(GraphImpl()->GetMonitor());
       SetNextDriver(new SystemClockDriver(GraphImpl()));
       NextDriver()->SetGraphTime(this, mIterationStart, mIterationEnd);
       mGraphImpl->SetCurrentDriver(NextDriver());
       NextDriver()->Start();
       return;
     }
   }
-#ifdef XP_MACOSX
-  // Currently, only mac cares about this
   bool aec;
   Unused << mGraphImpl->AudioTrackPresent(aec);
   SetMicrophoneActive(aec);
-#endif
 
   cubeb_stream_register_device_changed_callback(mAudioStream,
                                                 AudioCallbackDriver::DeviceChangedCallback_s);
 
   StartStream();
 
   STREAM_LOG(LogLevel::Debug, ("AudioCallbackDriver started."));
 }
@@ -817,69 +795,28 @@ AudioCallbackDriver::AutoInCallback::Aut
 {
   mDriver->mInCallback = true;
 }
 
 AudioCallbackDriver::AutoInCallback::~AutoInCallback() {
   mDriver->mInCallback = false;
 }
 
-#ifdef XP_MACOSX
-bool
-AudioCallbackDriver::OSXDeviceSwitchingWorkaround()
-{
-  MonitorAutoLock mon(GraphImpl()->GetMonitor());
-  if (mSelfReference) {
-    // Apparently, depending on the osx version, on device switch, the
-    // callback is called "some" number of times, and then stops being called,
-    // and then gets called again. 10 is to be safe, it's a low-enough number
-    // of milliseconds anyways (< 100ms)
-    //STREAM_LOG(LogLevel::Debug, ("Callbacks during switch: %d", mCallbackReceivedWhileSwitching+1));
-    if (mCallbackReceivedWhileSwitching++ >= 10) {
-      STREAM_LOG(LogLevel::Debug, ("Got %d callbacks, switching back to CallbackDriver", mCallbackReceivedWhileSwitching));
-      // If we have a self reference, we have fallen back temporarily on a
-      // system clock driver, but we just got called back, that means the osx
-      // audio backend has switched to the new device.
-      // Ask the graph to switch back to the previous AudioCallbackDriver
-      // (`this`), and when the graph has effectively switched, we can drop
-      // the self reference and unref the SystemClockDriver we fallen back on.
-      if (GraphImpl()->CurrentDriver() == this) {
-        mSelfReference.Drop(this);
-        SetNextDriver(nullptr);
-      } else {
-        GraphImpl()->CurrentDriver()->SwitchAtNextIteration(this);
-      }
-
-    }
-    return true;
-  }
-
-  return false;
-}
-#endif // XP_MACOSX
-
 long
 AudioCallbackDriver::DataCallback(const AudioDataValue* aInputBuffer,
                                   AudioDataValue* aOutputBuffer, long aFrames)
 {
   bool stillProcessing;
 
   // Don't add the callback until we're inited and ready
   if (!mAddedMixer) {
     mGraphImpl->mMixer.AddCallback(this);
     mAddedMixer = true;
   }
 
-#ifdef XP_MACOSX
-  if (OSXDeviceSwitchingWorkaround()) {
-    PodZero(aOutputBuffer, aFrames * mGraphImpl->AudioChannelCount());
-    return aFrames;
-  }
-#endif
-
 #ifdef DEBUG
   // DebugOnly<> doesn't work here... it forces an initialization that will cause
   // mInCallback to be set back to false before we exit the statement.  Do it by
   // hand instead.
   AutoInCallback aic(this);
 #endif
 
   GraphTime stateComputedTime = StateComputedTime();
@@ -1065,55 +1002,35 @@ void AudioCallbackDriver::PanOutputIfNee
       cubeb_stream_device_destroy(mAudioStream, out);
     }
   }
 #endif
 }
 
 void
 AudioCallbackDriver::DeviceChangedCallback() {
+  // Tell the audio engine the device has changed, it might want to reset some
+  // state.
+  MonitorAutoLock mon(mGraphImpl->GetMonitor());
+  if (mAudioInput) {
+    mAudioInput->DeviceChanged();
+  }
 #ifdef XP_MACOSX
-  MonitorAutoLock mon(mGraphImpl->GetMonitor());
   PanOutputIfNeeded(mMicrophoneActive);
-  // On OSX, changing the output device causes the audio thread to no call the
-  // audio callback, so we're unable to process real-time input data, and this
-  // results in latency building up.
-  // We switch to a system driver until audio callbacks are called again, so we
-  // still pull from the input stream, so that everything works apart from the
-  // audio output.
-
-  // Don't bother doing the device switching dance if the graph is not RUNNING
-  // (starting up, shutting down), because we haven't started pulling from the
-  // SourceMediaStream.
-  if (!GraphImpl()->Running()) {
-    return;
-  }
-
-  if (mSelfReference) {
-    return;
-  }
-  STREAM_LOG(LogLevel::Error, ("Switching to SystemClockDriver during output switch"));
-  mSelfReference.Take(this);
-  mCallbackReceivedWhileSwitching = 0;
-  SetNextDriver(new SystemClockDriver(GraphImpl()));
-  RemoveCallback();
-  mNextDriver->SetGraphTime(this, mIterationStart, mIterationEnd);
-  mGraphImpl->SetCurrentDriver(mNextDriver);
-  mNextDriver->Start();
 #endif
 }
 
 void
 AudioCallbackDriver::SetMicrophoneActive(bool aActive)
 {
-#ifdef XP_MACOSX
   MonitorAutoLock mon(mGraphImpl->GetMonitor());
 
   mMicrophoneActive = aActive;
 
+#ifdef XP_MACOSX
   PanOutputIfNeeded(mMicrophoneActive);
 #endif
 }
 
 uint32_t
 AudioCallbackDriver::IterationDuration()
 {
   // The real fix would be to have an API in cubeb to give us the number. Short
--- a/dom/media/GraphDriver.h
+++ b/dom/media/GraphDriver.h
@@ -425,24 +425,16 @@ public:
   }
 
   /* Enqueue a promise that is going to be resolved when a specific operation
    * occurs on the cubeb stream. */
   void EnqueueStreamAndPromiseForOperation(MediaStream* aStream,
                                          void* aPromise,
                                          dom::AudioContextOperation aOperation);
 
-  bool IsSwitchingDevice() {
-#ifdef XP_MACOSX
-    return mSelfReference;
-#else
-    return false;
-#endif
-  }
-
   /**
    * Whether the audio callback is processing. This is for asserting only.
    */
   bool InCallback();
 
   bool OnThread() override { return !mStarted || InCallback(); }
 
   /* Whether the underlying cubeb stream has been started. See comment for
@@ -525,33 +517,20 @@ private:
   /* This is set during initialization, and can be read safely afterwards. */
   dom::AudioChannel mAudioChannel;
   /* Used to queue us to add the mixer callback on first run. */
   bool mAddedMixer;
 
   /* This is atomic and is set by the audio callback thread. It can be read by
    * any thread safely. */
   Atomic<bool> mInCallback;
-
-#ifdef XP_MACOSX
   /**
    * True if microphone is being used by this process. This is synchronized by
    * the graph's monitor. */
   bool mMicrophoneActive;
-
-  /* Implements the workaround for the osx audio stack when changing output
-   * devices. See comments in .cpp */
-  bool OSXDeviceSwitchingWorkaround();
-  /* Self-reference that keep this driver alive when switching output audio
-   * device and making the graph running temporarily off a SystemClockDriver.  */
-  SelfReference<AudioCallbackDriver> mSelfReference;
-  /* While switching devices, we keep track of the number of callbacks received,
-   * since OSX seems to still call us _sometimes_. */
-  uint32_t mCallbackReceivedWhileSwitching;
-#endif
 };
 
 class AsyncCubebTask : public nsRunnable
 {
 public:
 
   AsyncCubebTask(AudioCallbackDriver* aDriver, AsyncCubebOperation aOperation);
 
--- a/dom/media/MediaStreamGraph.cpp
+++ b/dom/media/MediaStreamGraph.cpp
@@ -133,17 +133,17 @@ MediaStreamGraphImpl::RemoveStreamGraphT
 
   if (aStream->IsSuspended()) {
     mSuspendedStreams.RemoveElement(aStream);
   } else {
     mStreams.RemoveElement(aStream);
   }
 
   STREAM_LOG(LogLevel::Debug, ("Removed media stream %p from graph %p, count %lu",
-                               aStream, this, mStreams.Length()))
+                               aStream, this, mStreams.Length()));
   LIFECYCLE_LOG("Removed media stream %p from graph %p, count %lu",
                 aStream, this, mStreams.Length());
 
   NS_RELEASE(aStream); // probably destroying it
 }
 
 void
 MediaStreamGraphImpl::ExtractPendingInput(SourceMediaStream* aStream,
@@ -3062,34 +3062,34 @@ MediaInputPort::Graph()
 void
 MediaInputPort::SetGraphImpl(MediaStreamGraphImpl* aGraph)
 {
   MOZ_ASSERT(!mGraph || !aGraph, "Should only be set once");
   mGraph = aGraph;
 }
 
 void
-MediaInputPort::BlockTrackIdImpl(TrackID aTrackId)
+MediaInputPort::BlockSourceTrackIdImpl(TrackID aTrackId)
 {
   mBlockedTracks.AppendElement(aTrackId);
 }
 
 already_AddRefed<Pledge<bool>>
-MediaInputPort::BlockTrackId(TrackID aTrackId)
+MediaInputPort::BlockSourceTrackId(TrackID aTrackId)
 {
   class Message : public ControlMessage {
   public:
     explicit Message(MediaInputPort* aPort,
                      TrackID aTrackId,
                      already_AddRefed<nsIRunnable> aRunnable)
       : ControlMessage(aPort->GetDestination()),
         mPort(aPort), mTrackId(aTrackId), mRunnable(aRunnable) {}
     void Run() override
     {
-      mPort->BlockTrackIdImpl(mTrackId);
+      mPort->BlockSourceTrackIdImpl(mTrackId);
       if (mRunnable) {
         mStream->Graph()->DispatchToMainThreadAfterStreamStateUpdate(mRunnable.forget());
       }
     }
     void RunDuringShutdown() override
     {
       Run();
     }
@@ -3145,17 +3145,17 @@ ProcessedMediaStream::AllocateInputPort(
              "Only TRACK_ANY and explicit ID are allowed for destination track");
   MOZ_ASSERT(aTrackID != TRACK_ANY || aDestTrackID == TRACK_ANY,
              "Generic MediaInputPort cannot produce a single destination track");
   RefPtr<MediaInputPort> port =
     new MediaInputPort(aStream, aTrackID, this, aDestTrackID,
                        aInputNumber, aOutputNumber);
   if (aBlockedTracks) {
     for (TrackID trackID : *aBlockedTracks) {
-      port->BlockTrackIdImpl(trackID);
+      port->BlockSourceTrackIdImpl(trackID);
     }
   }
   port->SetGraphImpl(GraphImpl());
   GraphImpl()->AppendMessage(MakeUnique<Message>(port));
   return port.forget();
 }
 
 void
--- a/dom/media/MediaStreamGraph.h
+++ b/dom/media/MediaStreamGraph.h
@@ -204,16 +204,21 @@ public:
                                 TrackRate aRate, uint32_t aChannels) = 0;
   /**
    * Input data from a microphone (or other audio source.  This is not
    * guaranteed to be in any particular size chunks.
    */
   virtual void NotifyInputData(MediaStreamGraph* aGraph,
                                const AudioDataValue* aBuffer, size_t aFrames,
                                TrackRate aRate, uint32_t aChannels) = 0;
+
+  /**
+   * Called when the underlying audio device has changed.
+   */
+  virtual void DeviceChanged() = 0;
 };
 
 class AudioDataListener : public AudioDataListenerInterface {
 protected:
   // Protected destructor, to discourage deletion outside of Release():
   virtual ~AudioDataListener() {}
 
 public:
@@ -1233,23 +1238,24 @@ public:
 
   // Any thread
   MediaStream* GetSource() { return mSource; }
   TrackID GetSourceTrackId() { return mSourceTrack; }
   ProcessedMediaStream* GetDestination() { return mDest; }
   TrackID GetDestinationTrackId() { return mDestTrack; }
 
   /**
-   * Block aTrackId in the port. Consumers will interpret this track as ended.
+   * Block aTrackId in the source stream from being passed through the port.
+   * Consumers will interpret this track as ended.
    * Returns a pledge that resolves on the main thread after the track block has
    * been applied by the MSG.
    */
-  already_AddRefed<media::Pledge<bool, nsresult>> BlockTrackId(TrackID aTrackId);
+  already_AddRefed<media::Pledge<bool, nsresult>> BlockSourceTrackId(TrackID aTrackId);
 private:
-  void BlockTrackIdImpl(TrackID aTrackId);
+  void BlockSourceTrackIdImpl(TrackID aTrackId);
 
 public:
   // Returns true if aTrackId has not been blocked and this port has not
   // been locked to another track.
   bool PassTrackThrough(TrackID aTrackId) {
     return !mBlockedTracks.Contains(aTrackId) &&
            (mSourceTrack == TRACK_ANY || mSourceTrack == aTrackId);
   }
--- a/dom/media/MediaStreamTrack.cpp
+++ b/dom/media/MediaStreamTrack.cpp
@@ -217,16 +217,23 @@ MediaStreamTrack::Stop()
   }
 
   if (!mSource) {
     MOZ_ASSERT(false);
     return;
   }
 
   mSource->UnregisterSink(this);
+
+  MOZ_ASSERT(mOwningStream, "Every MediaStreamTrack needs an owning DOMMediaStream");
+  DOMMediaStream::TrackPort* port = mOwningStream->FindOwnedTrackPort(*this);
+  MOZ_ASSERT(port, "A MediaStreamTrack must exist in its owning DOMMediaStream");
+  RefPtr<Pledge<bool>> p = port->BlockSourceTrackId(mInputTrackID);
+  Unused << p;
+
   mStopped = true;
 }
 
 already_AddRefed<Promise>
 MediaStreamTrack::ApplyConstraints(const MediaTrackConstraints& aConstraints,
                                    ErrorResult &aRv)
 {
   if (MOZ_LOG_TEST(gMediaStreamTrackLog, LogLevel::Info)) {
--- a/dom/media/webaudio/AudioNodeEngine.cpp
+++ b/dom/media/webaudio/AudioNodeEngine.cpp
@@ -5,16 +5,17 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "AudioNodeEngine.h"
 #ifdef BUILD_ARM_NEON
 #include "mozilla/arm.h"
 #include "AudioNodeEngineNEON.h"
 #endif
 #ifdef USE_SSE2
+#include "mozilla/SSE.h"
 #include "AlignmentUtils.h"
 #include "AudioNodeEngineSSE2.h"
 #endif
 
 namespace mozilla {
 
 already_AddRefed<ThreadSharedFloatArrayBufferList>
 ThreadSharedFloatArrayBufferList::Create(uint32_t aChannelCount,
--- a/dom/media/webrtc/MediaEngineDefault.h
+++ b/dom/media/webrtc/MediaEngineDefault.h
@@ -144,16 +144,18 @@ public:
   void NotifyOutputData(MediaStreamGraph* aGraph,
                         AudioDataValue* aBuffer, size_t aFrames,
                         TrackRate aRate, uint32_t aChannels) override
   {}
   void NotifyInputData(MediaStreamGraph* aGraph,
                        const AudioDataValue* aBuffer, size_t aFrames,
                        TrackRate aRate, uint32_t aChannels) override
   {}
+  void DeviceChanged() override
+  {}
   bool IsFake() override {
     return true;
   }
 
   dom::MediaSourceEnum GetMediaSource() const override {
     return dom::MediaSourceEnum::Microphone;
   }
 
--- a/dom/media/webrtc/MediaEngineWebRTC.h
+++ b/dom/media/webrtc/MediaEngineWebRTC.h
@@ -98,16 +98,18 @@ public:
                    const MediaEnginePrefs &aPrefs,
                    const nsString& aDeviceId) override;
   void SetDirectListeners(bool aDirect) override
   {}
   void NotifyOutputData(MediaStreamGraph* aGraph,
                         AudioDataValue* aBuffer, size_t aFrames,
                         TrackRate aRate, uint32_t aChannels) override
   {}
+  void DeviceChanged() override
+  {}
   void NotifyInputData(MediaStreamGraph* aGraph,
                        const AudioDataValue* aBuffer, size_t aFrames,
                        TrackRate aRate, uint32_t aChannels) override
   {}
   void NotifyPull(MediaStreamGraph* aGraph,
                   SourceMediaStream* aSource,
                   TrackID aID,
                   StreamTime aDesiredTime,
@@ -386,16 +388,22 @@ public:
                                const AudioDataValue* aBuffer, size_t aFrames,
                                TrackRate aRate, uint32_t aChannels) override
   {
     MutexAutoLock lock(mMutex);
     if (mAudioSource) {
       mAudioSource->NotifyInputData(aGraph, aBuffer, aFrames, aRate, aChannels);
     }
   }
+  virtual void DeviceChanged() override
+  {
+    if (mAudioSource) {
+      mAudioSource->DeviceChanged();
+    }
+  }
 
   void Shutdown()
   {
     MutexAutoLock lock(mMutex);
     mAudioSource = nullptr;
   }
 
 private:
@@ -466,16 +474,18 @@ public:
   // AudioDataListenerInterface methods
   void NotifyOutputData(MediaStreamGraph* aGraph,
                         AudioDataValue* aBuffer, size_t aFrames,
                         TrackRate aRate, uint32_t aChannels) override;
   void NotifyInputData(MediaStreamGraph* aGraph,
                        const AudioDataValue* aBuffer, size_t aFrames,
                        TrackRate aRate, uint32_t aChannels) override;
 
+  void DeviceChanged() override;
+
   bool IsFake() override {
     return false;
   }
 
   dom::MediaSourceEnum GetMediaSource() const override {
     return dom::MediaSourceEnum::Microphone;
   }
 
--- a/dom/media/webrtc/MediaEngineWebRTCAudio.cpp
+++ b/dom/media/webrtc/MediaEngineWebRTCAudio.cpp
@@ -494,16 +494,56 @@ MediaEngineWebRTCMicrophoneSource::Notif
     }
     int16_t *packet = mInputBuffer.get();
     mPacketizer->Output(packet);
 
     mVoERender->ExternalRecordingInsertData(packet, samplesPerPacket, aRate, 0);
   }
 }
 
+#define ResetProcessingIfNeeded(_processing)                        \
+do {                                                                \
+  webrtc::_processing##Modes mode;                                  \
+  int rv = mVoEProcessing->Get##_processing##Status(enabled, mode); \
+  if (rv) {                                                         \
+    NS_WARNING("Could not get the status of the "                   \
+     #_processing " on device change.");                            \
+    return;                                                         \
+  }                                                                 \
+                                                                    \
+  if (enabled) {                                                    \
+    rv = mVoEProcessing->Set##_processing##Status(!enabled);        \
+    if (rv) {                                                       \
+      NS_WARNING("Could not reset the status of the "               \
+      #_processing " on device change.");                           \
+      return;                                                       \
+    }                                                               \
+                                                                    \
+    rv = mVoEProcessing->Set##_processing##Status(enabled);         \
+    if (rv) {                                                       \
+      NS_WARNING("Could not reset the status of the "               \
+      #_processing " on device change.");                           \
+      return;                                                       \
+    }                                                               \
+  }                                                                 \
+}  while(0)
+
+
+
+
+
+void
+MediaEngineWebRTCMicrophoneSource::DeviceChanged() {
+  // Reset some processing
+  bool enabled;
+  ResetProcessingIfNeeded(Agc);
+  ResetProcessingIfNeeded(Ec);
+  ResetProcessingIfNeeded(Ns);
+}
+
 void
 MediaEngineWebRTCMicrophoneSource::Init()
 {
   mVoEBase = webrtc::VoEBase::GetInterface(mVoiceEngine);
 
   mVoEBase->Init();
 
   mVoERender = webrtc::VoEExternalMedia::GetInterface(mVoiceEngine);
--- a/dom/plugins/base/PluginPRLibrary.cpp
+++ b/dom/plugins/base/PluginPRLibrary.cpp
@@ -329,9 +329,22 @@ PluginPRLibrary::GetScrollCaptureContain
 }
 nsresult
 PluginPRLibrary::UpdateScrollState(NPP aInstance, bool aIsScrolling)
 {
   return NS_ERROR_NOT_IMPLEMENTED;
 }
 #endif
 
+nsresult
+PluginPRLibrary::HandledWindowedPluginKeyEvent(
+                   NPP aInstance,
+                   const NativeEventData& aNativeKeyData,
+                   bool aIsConsumed)
+{
+  nsNPAPIPluginInstance* instance = (nsNPAPIPluginInstance*)aInstance->ndata;
+  if (NS_WARN_IF(!instance)) {
+    return NS_ERROR_NULL_POINTER;
+  }
+  return NS_OK;
+}
+
 } // namespace mozilla
--- a/dom/plugins/base/PluginPRLibrary.h
+++ b/dom/plugins/base/PluginPRLibrary.h
@@ -124,16 +124,20 @@ public:
     virtual void DidComposite(NPP aInstance) override { }
     virtual void GetLibraryPath(nsACString& aPath) { aPath.Assign(mFilePath); }
     virtual nsresult GetRunID(uint32_t* aRunID) override { return NS_ERROR_NOT_IMPLEMENTED; }
     virtual void SetHasLocalInstance() override { }
 #if defined(XP_WIN)
     virtual nsresult GetScrollCaptureContainer(NPP aInstance, mozilla::layers::ImageContainer** aContainer) override;
     virtual nsresult UpdateScrollState(NPP aInstance, bool aIsScrolling) override;
 #endif
+    virtual nsresult HandledWindowedPluginKeyEvent(
+                       NPP aInstance,
+                       const mozilla::NativeEventData& aNativeKeyData,
+                       bool aIsCOnsumed) override;
 
 private:
     NP_InitializeFunc mNP_Initialize;
     NP_ShutdownFunc mNP_Shutdown;
     NP_GetMIMEDescriptionFunc mNP_GetMIMEDescription;
 #if defined(XP_UNIX) && !defined(XP_MACOSX)
     NP_GetValueFunc mNP_GetValue;
 #endif
--- a/dom/plugins/base/nsNPAPIPluginInstance.cpp
+++ b/dom/plugins/base/nsNPAPIPluginInstance.cpp
@@ -1165,16 +1165,33 @@ nsNPAPIPluginInstance::UpdateScrollState
   if (RUNNING != mRunning)
     return NS_OK;
 
   AutoPluginLibraryCall library(this);
   return !library ? NS_ERROR_FAILURE : library->UpdateScrollState(&mNPP, aIsScrolling);
 }
 #endif
 
+nsresult
+nsNPAPIPluginInstance::HandledWindowedPluginKeyEvent(
+                         const NativeEventData& aKeyEventData,
+                         bool aIsConsumed)
+{
+  if (NS_WARN_IF(!mPlugin)) {
+    return NS_ERROR_FAILURE;
+  }
+
+  PluginLibrary* library = mPlugin->GetLibrary();
+  if (NS_WARN_IF(!library)) {
+    return NS_ERROR_FAILURE;
+  }
+  return library->HandledWindowedPluginKeyEvent(&mNPP, aKeyEventData,
+                                                aIsConsumed);
+}
+
 void
 nsNPAPIPluginInstance::DidComposite()
 {
   if (RUNNING != mRunning)
     return;
 
   AutoPluginLibraryCall library(this);
   library->DidComposite(&mNPP);
--- a/dom/plugins/base/nsNPAPIPluginInstance.h
+++ b/dom/plugins/base/nsNPAPIPluginInstance.h
@@ -23,16 +23,17 @@
 #include "nsIRunnable.h"
 #include "GLContextTypes.h"
 #include "AndroidSurfaceTexture.h"
 #include "AndroidBridge.h"
 #include <map>
 class PluginEventRunnable;
 #endif
 
+#include "mozilla/EventForwards.h"
 #include "mozilla/TimeStamp.h"
 #include "mozilla/PluginLibrary.h"
 #include "mozilla/WeakPtr.h"
 
 class nsPluginStreamListenerPeer; // browser-initiated stream class
 class nsNPAPIPluginStreamListener; // plugin-initiated stream class
 class nsIPluginInstanceOwner;
 class nsIOutputStream;
@@ -120,16 +121,19 @@ public:
   nsresult InvalidateRect(NPRect *invalidRect);
   nsresult InvalidateRegion(NPRegion invalidRegion);
   nsresult GetMIMEType(const char* *result);
   nsresult GetJSContext(JSContext* *outContext);
 #if defined(XP_WIN)
   nsresult GetScrollCaptureContainer(mozilla::layers::ImageContainer **aContainer);
   nsresult UpdateScrollState(bool aIsScrolling);
 #endif
+  nsresult HandledWindowedPluginKeyEvent(
+             const mozilla::NativeEventData& aKeyEventData,
+             bool aIsConsumed);
   nsPluginInstanceOwner* GetOwner();
   void SetOwner(nsPluginInstanceOwner *aOwner);
   void DidComposite();
 
   bool HasAudioChannelAgent() const
   {
     return !!mAudioChannelAgent;
   }
--- a/dom/plugins/base/nsPluginInstanceOwner.cpp
+++ b/dom/plugins/base/nsPluginInstanceOwner.cpp
@@ -68,23 +68,18 @@ using mozilla::DefaultXDisplay;
 #include "nsContentCID.h"
 #include "nsWidgetsCID.h"
 static NS_DEFINE_CID(kWidgetCID, NS_CHILD_CID);
 static NS_DEFINE_CID(kAppShellCID, NS_APPSHELL_CID);
 
 #ifdef XP_WIN
 #include <wtypes.h>
 #include <winuser.h>
-#ifndef WM_MOUSEHWHEEL
-#define WM_MOUSEHWHEEL (0x020E)
-#endif
-#ifndef SPI_GETWHEELSCROLLCHARS
-#define SPI_GETWHEELSCROLLCHARS (0x006C)
-#endif
-#endif
+#include "mozilla/widget/WinMessages.h"
+#endif // #ifdef XP_WIN
 
 #ifdef XP_MACOSX
 #include "ComplexTextInputPanel.h"
 #include "nsIDOMXULDocument.h"
 #include "nsIDOMXULCommandDispatcher.h"
 #endif
 
 #ifdef MOZ_WIDGET_GTK
@@ -418,16 +413,17 @@ nsPluginInstanceOwner::~nsPluginInstance
     mInstance->SetOwner(nullptr);
   }
 }
 
 NS_IMPL_ISUPPORTS(nsPluginInstanceOwner,
                   nsIPluginInstanceOwner,
                   nsIDOMEventListener,
                   nsIPrivacyTransitionObserver,
+                  nsIKeyEventInPluginCallback,
                   nsISupportsWeakReference)
 
 nsresult
 nsPluginInstanceOwner::SetInstance(nsNPAPIPluginInstance *aInstance)
 {
   NS_ASSERTION(!mInstance || !aInstance, "mInstance should only be set or unset!");
 
   // If we're going to null out mInstance after use, be sure to call
@@ -993,18 +989,68 @@ nsPluginInstanceOwner::RequestCommitOrCa
 
   if (aCommitted) {
     widget->NotifyIME(widget::REQUEST_TO_COMMIT_COMPOSITION);
   } else {
     widget->NotifyIME(widget::REQUEST_TO_CANCEL_COMPOSITION);
   }
   return true;
 }
+
 #endif // #ifdef XP_WIN
 
+void
+nsPluginInstanceOwner::HandledWindowedPluginKeyEvent(
+                         const NativeEventData& aKeyEventData,
+                         bool aIsConsumed)
+{
+  if (NS_WARN_IF(!mInstance)) {
+    return;
+  }
+  nsresult rv =
+    mInstance->HandledWindowedPluginKeyEvent(aKeyEventData, aIsConsumed);
+  NS_WARN_IF(NS_FAILED(rv));
+}
+
+void
+nsPluginInstanceOwner::OnWindowedPluginKeyEvent(
+                         const NativeEventData& aKeyEventData)
+{
+  if (NS_WARN_IF(!mPluginFrame)) {
+    // Notifies the plugin process of the key event being not consumed by us.
+    HandledWindowedPluginKeyEvent(aKeyEventData, false);
+    return;
+  }
+
+  nsCOMPtr<nsIWidget> widget = mPluginFrame->PresContext()->GetRootWidget();
+  if (NS_WARN_IF(!widget)) {
+    // Notifies the plugin process of the key event being not consumed by us.
+    HandledWindowedPluginKeyEvent(aKeyEventData, false);
+    return;
+  }
+
+  nsresult rv = widget->OnWindowedPluginKeyEvent(aKeyEventData, this);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    // Notifies the plugin process of the key event being not consumed by us.
+    HandledWindowedPluginKeyEvent(aKeyEventData, false);
+    return;
+  }
+
+  // If the key event is posted to another process, we need to wait a call
+  // of HandledWindowedPluginKeyEvent().  So, nothing to do here in this case.
+  if (rv == NS_SUCCESS_EVENT_HANDLED_ASYNCHRONOUSLY) {
+    return;
+  }
+
+  // Otherwise, the key event is handled synchronously.  Let's notify the
+  // plugin process of the key event's result.
+  bool consumed = (rv == NS_SUCCESS_EVENT_CONSUMED);
+  HandledWindowedPluginKeyEvent(aKeyEventData, consumed);
+}
+
 NS_IMETHODIMP nsPluginInstanceOwner::SetEventModel(int32_t eventModel)
 {
 #ifdef XP_MACOSX
   mEventModel = static_cast<NPEventModel>(eventModel);
   return NS_OK;
 #else
   return NS_ERROR_NOT_IMPLEMENTED;
 #endif
@@ -3275,39 +3321,29 @@ nsresult nsPluginInstanceOwner::Init(nsI
     true);
   aContent->AddSystemEventListener(NS_LITERAL_STRING("text"), this, true);
 
   return NS_OK;
 }
 
 void* nsPluginInstanceOwner::GetPluginPort()
 {
-//!!! Port must be released for windowless plugins on Windows, because it is HDC !!!
-
   void* result = nullptr;
   if (mWidget) {
 #ifdef XP_WIN
-    if (mPluginWindow && (mPluginWindow->type == NPWindowTypeDrawable))
-      result = mWidget->GetNativeData(NS_NATIVE_GRAPHIC); // HDC
-    else
+    if (!mPluginWindow || mPluginWindow->type == NPWindowTypeWindow)
 #endif
       result = mWidget->GetNativeData(NS_NATIVE_PLUGIN_PORT); // HWND/gdk window
   }
 
   return result;
 }
 
 void nsPluginInstanceOwner::ReleasePluginPort(void * pluginPort)
 {
-#ifdef XP_WIN
-  if (mWidget && mPluginWindow &&
-      mPluginWindow->type == NPWindowTypeDrawable) {
-    mWidget->FreeNativeData((HDC)pluginPort, NS_NATIVE_GRAPHIC);
-  }
-#endif
 }
 
 NS_IMETHODIMP nsPluginInstanceOwner::CreateWidget(void)
 {
   NS_ENSURE_TRUE(mPluginWindow, NS_ERROR_NULL_POINTER);
 
   nsresult rv = NS_ERROR_FAILURE;
 
--- a/dom/plugins/base/nsPluginInstanceOwner.h
+++ b/dom/plugins/base/nsPluginInstanceOwner.h
@@ -5,16 +5,17 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #ifndef nsPluginInstanceOwner_h_
 #define nsPluginInstanceOwner_h_
 
 #include "mozilla/Attributes.h"
 #include "npapi.h"
 #include "nsCOMPtr.h"
+#include "nsIKeyEventInPluginCallback.h"
 #include "nsIPluginInstanceOwner.h"
 #include "nsIPrivacyTransitionObserver.h"
 #include "nsIDOMEventListener.h"
 #include "nsPluginHost.h"
 #include "nsPluginNativeWindow.h"
 #include "nsWeakReference.h"
 #include "gfxRect.h"
 
@@ -47,20 +48,21 @@ using mozilla::widget::PuppetWidget;
 #ifdef MOZ_X11
 #ifdef MOZ_WIDGET_QT
 #include "gfxQtNativeRenderer.h"
 #else
 #include "gfxXlibNativeRenderer.h"
 #endif
 #endif
 
-class nsPluginInstanceOwner final : public nsIPluginInstanceOwner,
-                                    public nsIDOMEventListener,
-                                    public nsIPrivacyTransitionObserver,
-                                    public nsSupportsWeakReference
+class nsPluginInstanceOwner final : public nsIPluginInstanceOwner
+                                  , public nsIDOMEventListener
+                                  , public nsIPrivacyTransitionObserver
+                                  , public nsIKeyEventInPluginCallback
+                                  , public nsSupportsWeakReference
 {
 public:
   typedef mozilla::gfx::DrawTarget DrawTarget;
 
   nsPluginInstanceOwner();
   
   NS_DECL_ISUPPORTS
   NS_DECL_NSIPLUGININSTANCEOWNER
@@ -273,16 +275,31 @@ public:
   void NotifyDestroyPending();
 
   bool GetCompositionString(uint32_t aIndex, nsTArray<uint8_t>* aString,
                             int32_t* aLength);
   bool SetCandidateWindow(
            const mozilla::widget::CandidateWindowPosition& aPosition);
   bool RequestCommitOrCancel(bool aCommitted);
 
+  // See nsIKeyEventInPluginCallback
+  virtual void HandledWindowedPluginKeyEvent(
+                 const mozilla::NativeEventData& aKeyEventData,
+                 bool aIsConsumed) override;
+
+  /**
+   * OnWindowedPluginKeyEvent() is called when the plugin process receives
+   * native key event directly.
+   *
+   * @param aNativeKeyData      The key event which was received by the
+   *                            plugin process directly.
+   */
+  void OnWindowedPluginKeyEvent(
+         const mozilla::NativeEventData& aNativeKeyData);
+
   void GetCSSZoomFactor(float *result);
 private:
   virtual ~nsPluginInstanceOwner();
 
   // return FALSE if LayerSurface dirty (newly created and don't have valid plugin content yet)
   bool IsUpToDate()
   {
     nsIntSize size;
--- a/dom/plugins/ipc/PPluginInstance.ipdl
+++ b/dom/plugins/ipc/PPluginInstance.ipdl
@@ -27,16 +27,17 @@ using mozilla::gfx::IntRect from "mozill
 using struct mozilla::null_t from "ipc/IPCMessageUtils.h";
 using mozilla::WindowsHandle from "ipc/IPCMessageUtils.h";
 using mozilla::plugins::WindowsSharedMemoryHandle from "mozilla/plugins/PluginMessageUtils.h";
 using mozilla::layers::SurfaceDescriptorX11 from "gfxipc/ShadowLayerUtils.h";
 using nsIntRect from "nsRect.h";
 using mozilla::gfx::SurfaceFormat from "mozilla/gfx/Types.h";
 using struct DxgiAdapterDesc from "mozilla/D3DMessageUtils.h";
 using struct mozilla::widget::CandidateWindowPosition from "ipc/nsGUIEventIPC.h";
+using class mozilla::NativeEventData from "ipc/nsGUIEventIPC.h";
 
 namespace mozilla {
 namespace plugins {
 
 struct IOSurfaceDescriptor {
   uint32_t surfaceId;
   double contentsScaleFactor;
 };
@@ -135,16 +136,26 @@ child:
   // refer to the existing background or a fresh descriptor.
   async UpdateBackground(SurfaceDescriptor background, nsIntRect rect);
 
   async NPP_DidComposite();
 
   intr NPP_Destroy()
     returns (NPError rv);
 
+  // HandledWindowedPluginKeyEvent() is always called after posting a native
+  // key event with OnWindowedPluginKeyEvent().
+  //
+  // @param aKeyEventData      The key event which was posted to the parent
+  //                           process.
+  // @param aIsConsumed        true if aKeyEventData is consumed in the
+  //                           parent process.  Otherwise, false.
+  async HandledWindowedPluginKeyEvent(NativeEventData aKeyEventData,
+                                      bool aIsConsumed);
+
 parent:
   intr NPN_GetValue_NPNVWindowNPObject()
     returns (nullable PPluginScriptableObject value, NPError result);
   intr NPN_GetValue_NPNVPluginElementNPObject()
     returns (nullable PPluginScriptableObject value, NPError result);
   intr NPN_GetValue_NPNVprivateModeBool()
     returns (bool value, NPError result);
   intr NPN_GetValue_NPNVnetscapeWindow()
@@ -266,16 +277,23 @@ parent:
   sync GetCompositionString(uint32_t aType)
                             returns (uint8_t[] aDist, int32_t aLength);
   // Set candidate window position.
   //
   // @param aPosition  position information of candidate window
   async SetCandidateWindow(CandidateWindowPosition aPosition);
   async RequestCommitOrCancel(bool aCommitted);
 
+  // Notifies the parent process of a plugin instance receiving key event
+  // directly.
+  //
+  // @param aKeyEventData       The native key event which will be sent to
+  //                            plugin from native event handler.
+  async OnWindowedPluginKeyEvent(NativeEventData aKeyEventData);
+
 both:
   async PPluginScriptableObject();
 
 child:
   /* NPP_NewStream */
   async PBrowserStream(nsCString url,
                        uint32_t length,
                        uint32_t lastmodified,
--- a/dom/plugins/ipc/PluginInstanceChild.cpp
+++ b/dom/plugins/ipc/PluginInstanceChild.cpp
@@ -27,44 +27,49 @@
 extern const wchar_t* kFlashFullscreenClass;
 using mozilla::gfx::SharedDIBSurface;
 #endif
 #include "gfxSharedImageSurface.h"
 #include "gfxUtils.h"
 #include "gfxAlphaRecovery.h"
 
 #include "mozilla/ArrayUtils.h"
+#include "mozilla/BasicEvents.h"
 #include "mozilla/ipc/MessageChannel.h"
 #include "mozilla/AutoRestore.h"
 #include "mozilla/StaticPtr.h"
 #include "mozilla/UniquePtr.h"
 #include "ImageContainer.h"
 
 using namespace mozilla;
 using mozilla::ipc::ProcessChild;
 using namespace mozilla::plugins;
 using namespace mozilla::layers;
 using namespace mozilla::gfx;
+using namespace mozilla::widget;
 using namespace std;
 
 #ifdef MOZ_WIDGET_GTK
 
 #include <gtk/gtk.h>
 #include <gdk/gdkx.h>
 #include <gdk/gdk.h>
 #include "gtk2xtbin.h"
 
 #elif defined(MOZ_WIDGET_QT)
 #undef KeyPress
 #undef KeyRelease
 #elif defined(OS_WIN)
-#ifndef WM_MOUSEHWHEEL
-#define WM_MOUSEHWHEEL     0x020E
-#endif
-
+
+#include <windows.h>
+#include <windowsx.h>
+
+#include "mozilla/widget/WinMessages.h"
+#include "mozilla/widget/WinModifierKeyState.h"
+#include "mozilla/widget/WinNativeEventData.h"
 #include "nsWindowsDllInterceptor.h"
 
 typedef BOOL (WINAPI *User32TrackPopupMenu)(HMENU hMenu,
                                             UINT uFlags,
                                             int x,
                                             int y,
                                             int nReserved,
                                             HWND hWnd,
@@ -89,19 +94,16 @@ static Imm32ImmReleaseContext sImm32ImmR
 static Imm32ImmGetCompositionString sImm32ImmGetCompositionStringStub = nullptr;
 static Imm32ImmSetCandidateWindow sImm32ImmSetCandidateWindowStub = nullptr;
 static Imm32ImmNotifyIME sImm32ImmNotifyIME = nullptr;
 static PluginInstanceChild* sCurrentPluginInstance = nullptr;
 static const HIMC sHookIMC = (const HIMC)0xefefefef;
 
 using mozilla::gfx::SharedDIB;
 
-#include <windows.h>
-#include <windowsx.h>
-
 // Flash WM_USER message delay time for PostDelayedTask. Borrowed
 // from Chromium's web plugin delegate src. See 'flash msg throttling
 // helpers' section for details.
 const int kFlashWMUSERMessageThrottleDelayMs = 5;
 
 static const TCHAR kPluginIgnoreSubclassProperty[] = TEXT("PluginIgnoreSubclassProperty");
 
 #elif defined(XP_MACOSX)
@@ -133,29 +135,33 @@ CreateDrawTargetForSurface(gfxASurface *
                                              &format);
   if (!drawTarget) {
     NS_RUNTIMEABORT("CreateDrawTargetForSurface failed in plugin");
   }
   aSurface->SetData(&kDrawTarget, drawTarget, nullptr);
   return drawTarget;
 }
 
+bool PluginInstanceChild::sIsIMEComposing = false;
+
 PluginInstanceChild::PluginInstanceChild(const NPPluginFuncs* aPluginIface,
                                          const nsCString& aMimeType,
                                          const uint16_t& aMode,
                                          const InfallibleTArray<nsCString>& aNames,
                                          const InfallibleTArray<nsCString>& aValues)
     : mPluginIface(aPluginIface)
     , mMimeType(aMimeType)
     , mMode(aMode)
     , mNames(aNames)
     , mValues(aValues)
 #if defined(XP_DARWIN)
     , mContentsScaleFactor(1.0)
 #endif
+    , mPostingKeyEvents(0)
+    , mPostingKeyEventsOutdated(0)
     , mDrawingModel(kDefaultDrawingModel)
     , mCurrentDirectSurface(nullptr)
     , mAsyncInvalidateMutex("PluginInstanceChild::mAsyncInvalidateMutex")
     , mAsyncInvalidateTask(0)
     , mCachedWindowActor(nullptr)
     , mCachedElementActor(nullptr)
 #ifdef MOZ_WIDGET_GTK
     , mXEmbed(false)
@@ -191,16 +197,19 @@ PluginInstanceChild::PluginInstanceChild
     , mSurfaceType(gfxSurfaceType::Max)
     , mCurrentInvalidateTask(nullptr)
     , mCurrentAsyncSetWindowTask(nullptr)
     , mPendingPluginCall(false)
     , mDoAlphaExtraction(false)
     , mHasPainted(false)
     , mSurfaceDifferenceRect(0,0,0,0)
     , mDestroyed(false)
+#ifdef XP_WIN
+    , mLastKeyEventConsumed(false)
+#endif // #ifdef XP_WIN
     , mStackDepth(0)
 {
     memset(&mWindow, 0, sizeof(mWindow));
     mWindow.type = NPWindowTypeWindow;
     mData.ndata = (void*) this;
     mData.pdata = nullptr;
 #if defined(MOZ_X11) && defined(XP_UNIX) && !defined(XP_MACOSX)
     mWindow.ws_info = &mWsInfo;
@@ -1427,16 +1436,105 @@ PluginInstanceChild::Initialize()
     else {
         mWsInfo.display = xt_client_get_display();
     }
 #endif 
 
     return true;
 }
 
+bool
+PluginInstanceChild::RecvHandledWindowedPluginKeyEvent(
+                       const NativeEventData& aKeyEventData,
+                       const bool& aIsConsumed)
+{
+#if defined(OS_WIN)
+    const WinNativeKeyEventData* eventData =
+        static_cast<const WinNativeKeyEventData*>(aKeyEventData);
+    switch (eventData->mMessage) {
+        case WM_KEYDOWN:
+        case WM_SYSKEYDOWN:
+        case WM_KEYUP:
+        case WM_SYSKEYUP:
+            mLastKeyEventConsumed = aIsConsumed;
+            break;
+        case WM_CHAR:
+        case WM_SYSCHAR:
+        case WM_DEADCHAR:
+        case WM_SYSDEADCHAR:
+            // If preceding keydown or keyup event is consumed by the chrome
+            // process, we should consume WM_*CHAR messages too.
+            if (mLastKeyEventConsumed) {
+              return true;
+            }
+        default:
+            MOZ_CRASH("Needs to handle all messages posted to the parent");
+    }
+#endif // #if defined(OS_WIN)
+
+    // Unknown key input shouldn't be sent to plugin for security.
+    // XXX Is this possible if a plugin process which posted the message
+    //     already crashed and this plugin process is recreated?
+    if (NS_WARN_IF(!mPostingKeyEvents && !mPostingKeyEventsOutdated)) {
+        return true;
+    }
+
+    // If there is outdated posting key events, we should consume the key
+    // events.
+    if (mPostingKeyEventsOutdated) {
+        mPostingKeyEventsOutdated--;
+        return true;
+    }
+
+    mPostingKeyEvents--;
+
+    // If composition has been started after posting the key event,
+    // we should discard the event since if we send the event to plugin,
+    // the plugin may be confused and the result may be broken because
+    // the event order is shuffled.
+    if (aIsConsumed || sIsIMEComposing) {
+        return true;
+    }
+
+#if defined(OS_WIN)
+    UINT message = 0;
+    switch (eventData->mMessage) {
+        case WM_KEYDOWN:
+            message = MOZ_WM_KEYDOWN;
+            break;
+        case WM_SYSKEYDOWN:
+            message = MOZ_WM_SYSKEYDOWN;
+            break;
+        case WM_KEYUP:
+            message = MOZ_WM_KEYUP;
+            break;
+        case WM_SYSKEYUP:
+            message = MOZ_WM_SYSKEYUP;
+            break;
+        case WM_CHAR:
+            message = MOZ_WM_CHAR;
+            break;
+        case WM_SYSCHAR:
+            message = MOZ_WM_SYSCHAR;
+            break;
+        case WM_DEADCHAR:
+            message = MOZ_WM_DEADCHAR;
+            break;
+        case WM_SYSDEADCHAR:
+            message = MOZ_WM_SYSDEADCHAR;
+            break;
+        default:
+            MOZ_CRASH("Needs to handle all messages posted to the parent");
+    }
+    PluginWindowProcInternal(mPluginWindowHWND, message,
+                             eventData->mWParam, eventData->mLParam);
+#endif
+    return true;
+}
+
 #if defined(OS_WIN)
 
 static const TCHAR kWindowClassName[] = TEXT("GeckoPluginWindow");
 static const TCHAR kPluginInstanceChildProperty[] = TEXT("PluginInstanceChildProperty");
 static const TCHAR kFlashThrottleProperty[] = TEXT("MozillaFlashThrottleProperty");
 
 // static
 bool
@@ -1561,40 +1659,117 @@ PluginInstanceChild::PluginWindowProcInt
     if (!self) {
         NS_NOTREACHED("Badness!");
         return 0;
     }
 
     NS_ASSERTION(self->mPluginWindowHWND == hWnd, "Wrong window!");
     NS_ASSERTION(self->mPluginWndProc != PluginWindowProc, "Self-referential windowproc. Infinite recursion will happen soon.");
 
-    // Adobe's shockwave positions the plugin window relative to the browser
-    // frame when it initializes. With oopp disabled, this wouldn't have an
-    // effect. With oopp, GeckoPluginWindow is a child of the parent plugin
-    // window, so the move offsets the child within the parent. Generally
-    // we don't want plugins moving or sizing our window, so we prevent these
-    // changes here.
-    if (message == WM_WINDOWPOSCHANGING) {
-      WINDOWPOS* pos = reinterpret_cast<WINDOWPOS*>(lParam);
-      if (pos && (!(pos->flags & SWP_NOMOVE) || !(pos->flags & SWP_NOSIZE))) {
-        pos->x = pos->y = 0;
-        pos->cx = self->mPluginSize.x;
-        pos->cy = self->mPluginSize.y;
-        LRESULT res = CallWindowProc(self->mPluginWndProc, hWnd, message, wParam,
-                                     lParam);
-        pos->x = pos->y = 0;
-        pos->cx = self->mPluginSize.x;
-        pos->cy = self->mPluginSize.y;
-        return res;
-      }
-    }
-
-    // The plugin received keyboard focus, let the parent know so the dom is up to date.
-    if (message == WM_MOUSEACTIVATE) {
-      self->CallPluginFocusChange(true);
+    bool isIMECompositionMessage = false;
+    switch (message) {
+        // Adobe's shockwave positions the plugin window relative to the browser
+        // frame when it initializes. With oopp disabled, this wouldn't have an
+        // effect. With oopp, GeckoPluginWindow is a child of the parent plugin
+        // window, so the move offsets the child within the parent. Generally
+        // we don't want plugins moving or sizing our window, so we prevent
+        // these changes here.
+        case WM_WINDOWPOSCHANGING: {
+            WINDOWPOS* pos = reinterpret_cast<WINDOWPOS*>(lParam);
+            if (pos &&
+                (!(pos->flags & SWP_NOMOVE) || !(pos->flags & SWP_NOSIZE))) {
+                pos->x = pos->y = 0;
+                pos->cx = self->mPluginSize.x;
+                pos->cy = self->mPluginSize.y;
+                LRESULT res = CallWindowProc(self->mPluginWndProc,
+                                             hWnd, message, wParam, lParam);
+                pos->x = pos->y = 0;
+                pos->cx = self->mPluginSize.x;
+                pos->cy = self->mPluginSize.y;
+                return res;
+            }
+            break;
+        }
+
+        case WM_SETFOCUS:
+            // If this gets focus, ensure that there is no pending key events.
+            // Even if there were, we should ignore them for performance reason.
+            // Although, such case shouldn't occur.
+            NS_WARN_IF(self->mPostingKeyEvents > 0);
+            self->mPostingKeyEvents = 0;
+            self->mLastKeyEventConsumed = false;
+            break;
+
+        case WM_KEYDOWN:
+        case WM_SYSKEYDOWN:
+        case WM_KEYUP:
+        case WM_SYSKEYUP:
+            if (self->MaybePostKeyMessage(message, wParam, lParam)) {
+                // If PreHandleKeyMessage() posts the message to the parent
+                // process, we need to wait RecvOnKeyEventHandledBeforePlugin()
+                // to be called.
+                return 0; // Consume current message temporarily.
+            }
+            break;
+
+        case MOZ_WM_KEYDOWN:
+            message = WM_KEYDOWN;
+            break;
+        case MOZ_WM_SYSKEYDOWN:
+            message = WM_SYSKEYDOWN;
+            break;
+        case MOZ_WM_KEYUP:
+            message = WM_KEYUP;
+            break;
+        case MOZ_WM_SYSKEYUP:
+            message = WM_SYSKEYUP;
+            break;
+        case MOZ_WM_CHAR:
+            message = WM_CHAR;
+            break;
+        case MOZ_WM_SYSCHAR:
+            message = WM_SYSCHAR;
+            break;
+        case MOZ_WM_DEADCHAR:
+            message = WM_DEADCHAR;
+            break;
+        case MOZ_WM_SYSDEADCHAR:
+            message = WM_SYSDEADCHAR;
+            break;
+
+        case WM_IME_STARTCOMPOSITION:
+            isIMECompositionMessage = true;
+            sIsIMEComposing = true;
+            break;
+        case WM_IME_ENDCOMPOSITION:
+            isIMECompositionMessage = true;
+            sIsIMEComposing = false;
+            break;
+        case WM_IME_COMPOSITION:
+            isIMECompositionMessage = true;
+            // XXX Some old IME may not send WM_IME_COMPOSITION_START or
+            //     WM_IME_COMPSOITION_END properly.  So, we need to check
+            //     WM_IME_COMPSOITION and if it includes commit string.
+            sIsIMEComposing = !(lParam & GCS_RESULTSTR);
+            break;
+
+        // The plugin received keyboard focus, let the parent know so the dom
+        // is up to date.
+        case WM_MOUSEACTIVATE:
+            self->CallPluginFocusChange(true);
+            break;
+    }
+
+    // When a composition is committed, there may be pending key
+    // events which were posted to the parent process before starting
+    // the composition.  Then, we shouldn't handle it since they are
+    // now outdated.
+    if (isIMECompositionMessage && !sIsIMEComposing) {
+        self->mPostingKeyEventsOutdated += self->mPostingKeyEvents;
+        self->mPostingKeyEvents = 0;
     }
 
     // Prevent lockups due to plugins making rpc calls when the parent
     // is making a synchronous SendMessage call to the child window. Add
     // more messages as needed.
     if ((InSendMessageEx(nullptr)&(ISMEX_REPLIED|ISMEX_SEND)) == ISMEX_SEND) {
         switch(message) {
             case WM_CHILDACTIVATE:
@@ -1643,16 +1818,105 @@ PluginInstanceChild::PluginWindowProcInt
 
     if (message == WM_NCDESTROY) {
         RemoveProp(hWnd, kPluginInstanceChildProperty);
     }
 
     return res;
 }
 
+bool
+PluginInstanceChild::ShouldPostKeyMessage(UINT message,
+                                          WPARAM wParam,
+                                          LPARAM lParam)
+{
+    // If there is a composition, we shouldn't post the key message to the
+    // parent process because we cannot handle IME messages asynchronously.
+    // Therefore, if we posted key events to the parent process, the event
+    // order of the posted key events and IME events are shuffled.
+    if (sIsIMEComposing) {
+      return false;
+    }
+
+    // If there are some pending keyboard events which are not handled in
+    // the parent process, we should post the message for avoiding to shuffle
+    // the key event order.
+    if (mPostingKeyEvents) {
+        return true;
+    }
+
+    // If we are not waiting calls of RecvOnKeyEventHandledBeforePlugin(),
+    // we don't need to post WM_*CHAR messages.
+    switch (message) {
+        case WM_CHAR:
+        case WM_SYSCHAR:
+        case WM_DEADCHAR:
+        case WM_SYSDEADCHAR:
+            return false;
+    }
+
+    // Otherwise, we should post key message which might match with a
+    // shortcut key.
+    ModifierKeyState modifierState;
+    if (!modifierState.MaybeMatchShortcutKey()) {
+        // For better UX, we shouldn't use IPC when user tries to
+        // input character(s).
+        return false;
+    }
+
+    // Ignore modifier key events and keys already handled by IME.
+    switch (wParam) {
+        case VK_SHIFT:
+        case VK_CONTROL:
+        case VK_MENU:
+        case VK_LWIN:
+        case VK_RWIN:
+        case VK_CAPITAL:
+        case VK_NUMLOCK:
+        case VK_SCROLL:
+        // Following virtual keycodes shouldn't come with WM_(SYS)KEY* message
+        // but check it for avoiding unnecessary cross process communication.
+        case VK_LSHIFT:
+        case VK_RSHIFT:
+        case VK_LCONTROL:
+        case VK_RCONTROL:
+        case VK_LMENU:
+        case VK_RMENU:
+        case VK_PROCESSKEY:
+        case VK_PACKET:
+        case 0xFF: // 0xFF could be sent with unidentified key by the layout.
+            return false;
+        default:
+            break;
+    }
+    return true;
+}
+
+bool
+PluginInstanceChild::MaybePostKeyMessage(UINT message,
+                                         WPARAM wParam,
+                                         LPARAM lParam)
+{
+    if (!ShouldPostKeyMessage(message, wParam, lParam)) {
+        return false;
+    }
+
+    ModifierKeyState modifierState;
+    WinNativeKeyEventData winNativeKeyData(message, wParam, lParam,
+                                           modifierState);
+    NativeEventData nativeKeyData;
+    nativeKeyData.Copy(winNativeKeyData);
+    if (NS_WARN_IF(!SendOnWindowedPluginKeyEvent(nativeKeyData))) {
+        return false;
+     }
+
+    mPostingKeyEvents++;
+    return true;
+}
+
 /* set window long ptr hook for flash */
 
 /*
  * Flash will reset the subclass of our widget at various times.
  * (Notably when entering and exiting full screen mode.) This
  * occurs independent of the main plugin window event procedure.
  * We trap these subclass calls to prevent our subclass hook from
  * getting dropped.
--- a/dom/plugins/ipc/PluginInstanceChild.h
+++ b/dom/plugins/ipc/PluginInstanceChild.h
@@ -2,16 +2,17 @@
  * vim: sw=4 ts=4 et :
  * 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 dom_plugins_PluginInstanceChild_h
 #define dom_plugins_PluginInstanceChild_h 1
 
+#include "mozilla/EventForwards.h"
 #include "mozilla/plugins/PPluginInstanceChild.h"
 #include "mozilla/plugins/PluginScriptableObjectChild.h"
 #include "mozilla/plugins/StreamNotifyChild.h"
 #include "mozilla/plugins/PPluginSurfaceChild.h"
 #include "mozilla/ipc/CrossProcessMutex.h"
 #include "nsRefPtrHashtable.h"
 #if defined(OS_WIN)
 #include "mozilla/gfx/SharedDIBWin.h"
@@ -267,16 +268,21 @@ public:
 
     NPError NPN_InitAsyncSurface(NPSize *size, NPImageFormat format,
                                  void *initData, NPAsyncSurface *surface);
     NPError NPN_FinalizeAsyncSurface(NPAsyncSurface *surface);
 
     void NPN_SetCurrentAsyncSurface(NPAsyncSurface *surface, NPRect *changed);
 
     void DoAsyncRedraw();
+
+    virtual bool RecvHandledWindowedPluginKeyEvent(
+                   const NativeEventData& aKeyEventData,
+                   const bool& aIsConsumed) override;
+
 private:
     friend class PluginModuleChild;
 
     NPError
     InternalGetNPObjectForValue(NPNVariable aValue,
                                 NPObject** aObject);
 
     bool IsUsingDirectDrawing();
@@ -389,28 +395,32 @@ private:
       private:
         HWND                 mWnd;
         UINT                 mMsg;
         WPARAM               mWParam;
         LPARAM               mLParam;
         bool                 mWindowed;
     };
 
-#endif
+    bool ShouldPostKeyMessage(UINT message, WPARAM wParam, LPARAM lParam);
+    bool MaybePostKeyMessage(UINT message, WPARAM wParam, LPARAM lParam);
+#endif // #if defined(OS_WIN)
     const NPPluginFuncs* mPluginIface;
     nsCString                   mMimeType;
     uint16_t                    mMode;
     InfallibleTArray<nsCString> mNames;
     InfallibleTArray<nsCString> mValues;
     NPP_t mData;
     NPWindow mWindow;
 #if defined(XP_DARWIN)
     double mContentsScaleFactor;
 #endif
     double mCSSZoomFactor;
+    uint32_t mPostingKeyEvents;
+    uint32_t mPostingKeyEventsOutdated;
     int16_t               mDrawingModel;
 
     NPAsyncSurface* mCurrentDirectSurface;
 
     // The surface hashtables below serve a few purposes. They let us verify
     // and retain extra information about plugin surfaces, and they let us
     // free shared memory that the plugin might forget to release.
     struct DirectBitmap {
@@ -656,16 +666,28 @@ private:
     // Cached rectangle rendered to previous surface(mBackSurface)
     // Used for reading back to current surface and syncing data,
     // in plugin coordinates.
     nsIntRect mSurfaceDifferenceRect;
 
     // Has this instance been destroyed, either by ActorDestroy or NPP_Destroy?
     bool mDestroyed;
 
+#ifdef XP_WIN
+    // WM_*CHAR messages are never consumed by chrome process's widget.
+    // So, if preceding keydown or keyup event is consumed by reserved
+    // shortcut key in the chrome process, we shouldn't send the following
+    // WM_*CHAR messages to the plugin.
+    bool mLastKeyEventConsumed;
+#endif // #ifdef XP_WIN
+
+    // While IME in the process has composition, this is set to true.
+    // Otherwise, false.
+    static bool sIsIMEComposing;
+
     // A counter is incremented by AutoStackHelper to indicate that there is an
     // active plugin call which should be preventing shutdown.
 public:
     class AutoStackHelper {
     public:
         explicit AutoStackHelper(PluginInstanceChild* instance)
             : mInstance(instance)
         {
--- a/dom/plugins/ipc/PluginInstanceParent.cpp
+++ b/dom/plugins/ipc/PluginInstanceParent.cpp
@@ -2,16 +2,17 @@
  * vim: sw=4 ts=4 et :
  * This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "mozilla/DebugOnly.h"
 #include <stdint.h> // for intptr_t
 
+#include "mozilla/BasicEvents.h"
 #include "mozilla/Preferences.h"
 #include "mozilla/Telemetry.h"
 #include "PluginInstanceParent.h"
 #include "BrowserStreamParent.h"
 #include "PluginAsyncSurrogate.h"
 #include "PluginBackgroundDestroyer.h"
 #include "PluginModuleParent.h"
 #include "PluginStreamParent.h"
@@ -2657,16 +2658,43 @@ PluginInstanceParent::RecvRequestCommitO
     nsPluginInstanceOwner* owner = GetOwner();
     if (owner) {
         owner->RequestCommitOrCancel(aCommitted);
     }
 #endif
     return true;
 }
 
+nsresult
+PluginInstanceParent::HandledWindowedPluginKeyEvent(
+                        const NativeEventData& aKeyEventData,
+                        bool aIsConsumed)
+{
+    if (NS_WARN_IF(!SendHandledWindowedPluginKeyEvent(aKeyEventData,
+                                                      aIsConsumed))) {
+        return NS_ERROR_FAILURE;
+    }
+    return NS_OK;
+}
+
+bool
+PluginInstanceParent::RecvOnWindowedPluginKeyEvent(
+                        const NativeEventData& aKeyEventData)
+{
+    nsPluginInstanceOwner* owner = GetOwner();
+    if (NS_WARN_IF(!owner)) {
+        // Notifies the plugin process of the key event being not consumed
+        // by us.
+        HandledWindowedPluginKeyEvent(aKeyEventData, false);
+        return true;
+    }
+    owner->OnWindowedPluginKeyEvent(aKeyEventData);
+    return true;
+}
+
 void
 PluginInstanceParent::RecordDrawingModel()
 {
     int mode = -1;
     switch (mWindowType) {
     case NPWindowTypeWindow:
         // We use 0=windowed since there is no specific NPDrawingModel value.
         mode = 0;
--- a/dom/plugins/ipc/PluginInstanceParent.h
+++ b/dom/plugins/ipc/PluginInstanceParent.h
@@ -20,16 +20,17 @@
 #include "npfunctions.h"
 #include "nsAutoPtr.h"
 #include "nsDataHashtable.h"
 #include "nsHashKeys.h"
 #include "nsRect.h"
 #include "PluginDataResolver.h"
 
 #include "mozilla/unused.h"
+#include "mozilla/EventForwards.h"
 
 class gfxASurface;
 class gfxContext;
 class nsPluginInstanceOwner;
 
 namespace mozilla {
 namespace layers {
 class Image;
@@ -351,16 +352,24 @@ public:
                              nsTArray<uint8_t>* aBuffer,
                              int32_t* aLength) override;
     virtual bool
     RecvSetCandidateWindow(
         const mozilla::widget::CandidateWindowPosition& aPosition) override;
     virtual bool
     RecvRequestCommitOrCancel(const bool& aCommitted) override;
 
+    // for reserved shortcut key handling with windowed plugin on Windows
+    nsresult HandledWindowedPluginKeyEvent(
+      const mozilla::NativeEventData& aKeyEventData,
+      bool aIsConsumed);
+    virtual bool
+    RecvOnWindowedPluginKeyEvent(
+      const mozilla::NativeEventData& aKeyEventData) override;
+
 private:
     // Create an appropriate platform surface for a background of size
     // |aSize|.  Return true if successful.
     bool CreateBackground(const nsIntSize& aSize);
     void DestroyBackground();
     SurfaceDescriptor BackgroundDescriptor() /*const*/;
 
     typedef mozilla::layers::ImageContainer ImageContainer;
--- a/dom/plugins/ipc/PluginLibrary.h
+++ b/dom/plugins/ipc/PluginLibrary.h
@@ -86,16 +86,20 @@ public:
 #if defined(XP_MACOSX)
   virtual nsresult IsRemoteDrawingCoreAnimation(NPP instance, bool *aDrawing) = 0;
   virtual nsresult ContentsScaleFactorChanged(NPP instance, double aContentsScaleFactor) = 0;
 #endif
 #if defined(XP_WIN)
     virtual nsresult GetScrollCaptureContainer(NPP aInstance, mozilla::layers::ImageContainer** aContainer) = 0;
     virtual nsresult UpdateScrollState(NPP aInstance, bool aIsScrolling) = 0;
 #endif
+  virtual nsresult HandledWindowedPluginKeyEvent(
+                     NPP aInstance,
+                     const mozilla::NativeEventData& aNativeKeyData,
+                     bool aIsCOnsumed) = 0;
 
   /**
    * The next three methods are the third leg in the trip to
    * PluginInstanceParent.  They approximately follow the ReadbackSink
    * API.
    */
   virtual nsresult SetBackgroundUnknown(NPP instance) = 0;
   virtual nsresult BeginUpdateBackground(NPP instance,
--- a/dom/plugins/ipc/PluginModuleParent.cpp
+++ b/dom/plugins/ipc/PluginModuleParent.cpp
@@ -2009,16 +2009,29 @@ PluginModuleParent::GetScrollCaptureCont
 nsresult
 PluginModuleParent::UpdateScrollState(NPP aInstance, bool aIsScrolling)
 {
     PluginInstanceParent* inst = PluginInstanceParent::Cast(aInstance);
     return !inst ? NS_ERROR_FAILURE : inst->UpdateScrollState(aIsScrolling);
 }
 #endif
 
+nsresult
+PluginModuleParent::HandledWindowedPluginKeyEvent(
+                        NPP aInstance,
+                        const NativeEventData& aNativeKeyData,
+                        bool aIsConsumed)
+{
+    PluginInstanceParent* parent = PluginInstanceParent::Cast(aInstance);
+    if (NS_WARN_IF(!parent)) {
+        return NS_ERROR_FAILURE;
+    }
+    return parent->HandledWindowedPluginKeyEvent(aNativeKeyData, aIsConsumed);
+}
+
 void
 PluginModuleParent::OnInitFailure()
 {
     if (GetIPCChannel()->CanSend()) {
         Close();
     }
 
     mShutdown = true;
--- a/dom/plugins/ipc/PluginModuleParent.h
+++ b/dom/plugins/ipc/PluginModuleParent.h
@@ -261,16 +261,21 @@ protected:
     virtual nsresult EndUpdateBackground(NPP instance,
                                          const nsIntRect& aRect) override;
 
 #if defined(XP_WIN)
     virtual nsresult GetScrollCaptureContainer(NPP aInstance, mozilla::layers::ImageContainer** aContainer) override;
     virtual nsresult UpdateScrollState(NPP aInstance, bool aIsScrolling);
 #endif
 
+    virtual nsresult HandledWindowedPluginKeyEvent(
+                       NPP aInstance,
+                       const mozilla::NativeEventData& aNativeKeyData,
+                       bool aIsConsumed) override;
+
 #if defined(XP_UNIX) && !defined(XP_MACOSX) && !defined(MOZ_WIDGET_GONK)
     virtual nsresult NP_Initialize(NPNetscapeFuncs* bFuncs, NPPluginFuncs* pFuncs, NPError* error) override;
 #else
     virtual nsresult NP_Initialize(NPNetscapeFuncs* bFuncs, NPError* error) override;
 #endif
     virtual nsresult NP_Shutdown(NPError* error) override;
 
     virtual nsresult NP_GetMIMEDescription(const char** mimeDesc) override;
--- a/dom/security/nsCSPContext.cpp
+++ b/dom/security/nsCSPContext.cpp
@@ -283,16 +283,25 @@ nsCSPContext::GetPolicy(uint32_t aIndex,
 {
   if (aIndex >= mPolicies.Length()) {
     return NS_ERROR_ILLEGAL_VALUE;
   }
   mPolicies[aIndex]->toString(outStr);
   return NS_OK;
 }
 
+const nsCSPPolicy*
+nsCSPContext::GetPolicy(uint32_t aIndex)
+{
+  if (aIndex >= mPolicies.Length()) {
+    return nullptr;
+  }
+  return mPolicies[aIndex];
+}
+
 NS_IMETHODIMP
 nsCSPContext::GetPolicyCount(uint32_t *outPolicyCount)
 {
   *outPolicyCount = mPolicies.Length();
   return NS_OK;
 }
 
 NS_IMETHODIMP
--- a/dom/security/nsCSPUtils.cpp
+++ b/dom/security/nsCSPUtils.cpp
@@ -370,16 +370,22 @@ nsCSPSchemeSrc::permits(nsIURI* aUri, co
     nsAutoCString spec;
     aUri->GetSpec(spec);
     CSPUTILSLOG(("nsCSPSchemeSrc::permits, aUri: %s", spec.get()));
   }
   MOZ_ASSERT((!mScheme.EqualsASCII("")), "scheme can not be the empty string");
   return permitsScheme(mScheme, aUri, aReportOnly, aUpgradeInsecure);
 }
 
+bool
+nsCSPSchemeSrc::visit(nsCSPSrcVisitor* aVisitor) const
+{
+  return aVisitor->visitSchemeSrc(*this);
+}
+
 void
 nsCSPSchemeSrc::toString(nsAString& outStr) const
 {
   outStr.Append(mScheme);
   outStr.AppendASCII(":");
 }
 
 /* ===== nsCSPHostSrc ======================== */
@@ -529,16 +535,22 @@ nsCSPHostSrc::permits(nsIURI* aUri, cons
       return false;
     }
   }
 
   // At the end: scheme, host, path, and port match -> allow the load.
   return true;
 }
 
+bool
+nsCSPHostSrc::visit(nsCSPSrcVisitor* aVisitor) const
+{
+  return aVisitor->visitHostSrc(*this);
+}
+
 void
 nsCSPHostSrc::toString(nsAString& outStr) const
 {
   // If mHost is a single "*", we append the wildcard and return.
   if (mHost.EqualsASCII("*") &&
       mScheme.IsEmpty() &&
       mPort.IsEmpty()) {
     outStr.Append(mHost);
@@ -606,16 +618,22 @@ nsCSPKeywordSrc::allows(enum CSPKeyword 
   if (mInvalidated) {
     NS_ASSERTION(mKeyword == CSP_UNSAFE_INLINE,
                  "should only invalidate unsafe-inline within script-src");
     return false;
   }
   return mKeyword == aKeyword;
 }
 
+bool
+nsCSPKeywordSrc::visit(nsCSPSrcVisitor* aVisitor) const
+{
+  return aVisitor->visitKeywordSrc(*this);
+}
+
 void
 nsCSPKeywordSrc::toString(nsAString& outStr) const
 {
   if (mInvalidated) {
     MOZ_ASSERT(mKeyword == CSP_UNSAFE_INLINE,
                "can only ignore 'unsafe-inline' within toString()");
     return;
   }
@@ -662,16 +680,22 @@ nsCSPNonceSrc::allows(enum CSPKeyword aK
               CSP_EnumToKeyword(aKeyword), NS_ConvertUTF16toUTF8(aHashOrNonce).get()));
 
   if (aKeyword != CSP_NONCE) {
     return false;
   }
   return mNonce.Equals(aHashOrNonce);
 }
 
+bool
+nsCSPNonceSrc::visit(nsCSPSrcVisitor* aVisitor) const
+{
+  return aVisitor->visitNonceSrc(*this);
+}
+
 void
 nsCSPNonceSrc::toString(nsAString& outStr) const
 {
   outStr.AppendASCII(CSP_EnumToKeyword(CSP_NONCE));
   outStr.Append(mNonce);
   outStr.AppendASCII("'");
 }
 
@@ -719,16 +743,22 @@ nsCSPHashSrc::allows(enum CSPKeyword aKe
 
   // The NSS Base64 encoder automatically adds linebreaks "\r\n" every 64
   // characters. We need to remove these so we can properly validate longer
   // (SHA-512) base64-encoded hashes
   hash.StripChars("\r\n");
   return NS_ConvertUTF16toUTF8(mHash).Equals(hash);
 }
 
+bool
+nsCSPHashSrc::visit(nsCSPSrcVisitor* aVisitor) const
+{
+  return aVisitor->visitHashSrc(*this);
+}
+
 void
 nsCSPHashSrc::toString(nsAString& outStr) const
 {
   outStr.AppendASCII("'");
   outStr.Append(mAlgorithm);
   outStr.AppendASCII("-");
   outStr.Append(mHash);
   outStr.AppendASCII("'");
@@ -740,16 +770,22 @@ nsCSPReportURI::nsCSPReportURI(nsIURI *a
   :mReportURI(aURI)
 {
 }
 
 nsCSPReportURI::~nsCSPReportURI()
 {
 }
 
+bool
+nsCSPReportURI::visit(nsCSPSrcVisitor* aVisitor) const
+{
+  return false;
+}
+
 void
 nsCSPReportURI::toString(nsAString& outStr) const
 {
   nsAutoCString spec;
   nsresult rv = mReportURI->GetSpec(spec);
   if (NS_FAILED(rv)) {
     return;
   }
@@ -944,16 +980,27 @@ nsCSPDirective::getReportURIs(nsTArray<n
   nsString tmpReportURI;
   for (uint32_t i = 0; i < mSrcs.Length(); i++) {
     tmpReportURI.Truncate();
     mSrcs[i]->toString(tmpReportURI);
     outReportURIs.AppendElement(tmpReportURI);
   }
 }
 
+bool
+nsCSPDirective::visitSrcs(nsCSPSrcVisitor* aVisitor) const
+{
+  for (uint32_t i = 0; i < mSrcs.Length(); i++) {
+    if (!mSrcs[i]->visit(aVisitor)) {
+      return false;
+    }
+  }
+  return true;
+}
+
 bool nsCSPDirective::equals(CSPDirective aDirective) const
 {
   return (mDirective == aDirective);
 }
 
 /* =============== nsCSPChildSrcDirective ============= */
 
 nsCSPChildSrcDirective::nsCSPChildSrcDirective(CSPDirective aDirective)
@@ -1251,8 +1298,19 @@ nsCSPPolicy::getReportURIs(nsTArray<nsSt
 {
   for (uint32_t i = 0; i < mDirectives.Length(); i++) {
     if (mDirectives[i]->equals(nsIContentSecurityPolicy::REPORT_URI_DIRECTIVE)) {
       mDirectives[i]->getReportURIs(outReportURIs);
       return;
     }
   }
 }
+
+bool
+nsCSPPolicy::visitDirectiveSrcs(CSPDirective aDir, nsCSPSrcVisitor* aVisitor) const
+{
+  for (uint32_t i = 0; i < mDirectives.Length(); i++) {
+    if (mDirectives[i]->equals(aDir)) {
+      return mDirectives[i]->visitSrcs(aVisitor);
+    }
+  }
+  return false;
+}
--- a/dom/security/nsCSPUtils.h
+++ b/dom/security/nsCSPUtils.h
@@ -140,17 +140,21 @@ static const char* CSPStrKeywords[] = {
 };
 
 inline const char* CSP_EnumToKeyword(enum CSPKeyword aKey)
 {
   // Make sure all elements in enum CSPKeyword got added to CSPStrKeywords.
   static_assert((sizeof(CSPStrKeywords) / sizeof(CSPStrKeywords[0]) ==
                 static_cast<uint32_t>(CSP_LAST_KEYWORD_VALUE)),
                 "CSP_LAST_KEYWORD_VALUE does not match length of CSPStrKeywords");
-  return CSPStrKeywords[static_cast<uint32_t>(aKey)];
+
+  if (static_cast<uint32_t>(aKey) < static_cast<uint32_t>(CSP_LAST_KEYWORD_VALUE)) {
+      return CSPStrKeywords[static_cast<uint32_t>(aKey)];
+  }
+  return "error: invalid keyword in CSP_EnumToKeyword";
 }
 
 inline CSPKeyword CSP_KeywordToEnum(const nsAString& aKey)
 {
   nsString lowerKey = PromiseFlatString(aKey);
   ToLowerCase(lowerKey);
 
   static_assert(CSP_LAST_KEYWORD_VALUE ==
@@ -172,128 +176,182 @@ class nsCSPHostSrc;
 
 nsCSPHostSrc* CSP_CreateHostSrcFromURI(nsIURI* aURI);
 bool CSP_IsValidDirective(const nsAString& aDir);
 bool CSP_IsDirective(const nsAString& aValue, CSPDirective aDir);
 bool CSP_IsKeyword(const nsAString& aValue, enum CSPKeyword aKey);
 bool CSP_IsQuotelessKeyword(const nsAString& aKey);
 CSPDirective CSP_ContentTypeToDirective(nsContentPolicyType aType);
 
+class nsCSPSrcVisitor;
 
 /* =============== nsCSPSrc ================== */
 
 class nsCSPBaseSrc {
   public:
     nsCSPBaseSrc();
     virtual ~nsCSPBaseSrc();
 
     virtual bool permits(nsIURI* aUri, const nsAString& aNonce, bool aWasRedirected,
                          bool aReportOnly, bool aUpgradeInsecure) const;
     virtual bool allows(enum CSPKeyword aKeyword, const nsAString& aHashOrNonce) const;
+    virtual bool visit(nsCSPSrcVisitor* aVisitor) const = 0;
     virtual void toString(nsAString& outStr) const = 0;
 };
 
 /* =============== nsCSPSchemeSrc ============ */
 
 class nsCSPSchemeSrc : public nsCSPBaseSrc {
   public:
     explicit nsCSPSchemeSrc(const nsAString& aScheme);
     virtual ~nsCSPSchemeSrc();
 
     bool permits(nsIURI* aUri, const nsAString& aNonce, bool aWasRedirected,
                  bool aReportOnly, bool aUpgradeInsecure) const;
+    bool visit(nsCSPSrcVisitor* aVisitor) const;
     void toString(nsAString& outStr) const;
 
+    inline void getScheme(nsAString& outStr) const
+      { outStr.Assign(mScheme); };
+
   private:
     nsString mScheme;
 };
 
 /* =============== nsCSPHostSrc ============== */
 
 class nsCSPHostSrc : public nsCSPBaseSrc {
   public:
     explicit nsCSPHostSrc(const nsAString& aHost);
     virtual ~nsCSPHostSrc();
 
     bool permits(nsIURI* aUri, const nsAString& aNonce, bool aWasRedirected,
                  bool aReportOnly, bool aUpgradeInsecure) const;
+    bool visit(nsCSPSrcVisitor* aVisitor) const;
     void toString(nsAString& outStr) const;
 
     void setScheme(const nsAString& aScheme);
     void setPort(const nsAString& aPort);
     void appendPath(const nsAString &aPath);
 
+    inline void getScheme(nsAString& outStr) const
+      { outStr.Assign(mScheme); };
+
+    inline void getHost(nsAString& outStr) const
+      { outStr.Assign(mHost); };
+
+    inline void getPort(nsAString& outStr) const
+      { outStr.Assign(mPort); };
+
+    inline void getPath(nsAString& outStr) const
+      { outStr.Assign(mPath); };
+
   private:
     nsString mScheme;
     nsString mHost;
     nsString mPort;
     nsString mPath;
 };
 
 /* =============== nsCSPKeywordSrc ============ */
 
 class nsCSPKeywordSrc : public nsCSPBaseSrc {
   public:
     explicit nsCSPKeywordSrc(CSPKeyword aKeyword);
     virtual ~nsCSPKeywordSrc();
 
     bool allows(enum CSPKeyword aKeyword, const nsAString& aHashOrNonce) const;
+    bool visit(nsCSPSrcVisitor* aVisitor) const;
     void toString(nsAString& outStr) const;
     void invalidate();
 
+    inline CSPKeyword getKeyword() const
+      { return mKeyword; };
+
   private:
     CSPKeyword mKeyword;
     // invalidate 'unsafe-inline' if nonce- or hash-source specified
     bool       mInvalidated;
 };
 
 /* =============== nsCSPNonceSource =========== */
 
 class nsCSPNonceSrc : public nsCSPBaseSrc {
   public:
     explicit nsCSPNonceSrc(const nsAString& aNonce);
     virtual ~nsCSPNonceSrc();
 
     bool permits(nsIURI* aUri, const nsAString& aNonce, bool aWasRedirected,
                  bool aReportOnly, bool aUpgradeInsecure) const;
     bool allows(enum CSPKeyword aKeyword, const nsAString& aHashOrNonce) const;
+    bool visit(nsCSPSrcVisitor* aVisitor) const;
     void toString(nsAString& outStr) const;
 
+    inline void getNonce(nsAString& outStr) const
+      { outStr.Assign(mNonce); };
+
   private:
     nsString mNonce;
 };
 
 /* =============== nsCSPHashSource ============ */
 
 class nsCSPHashSrc : public nsCSPBaseSrc {
   public:
     nsCSPHashSrc(const nsAString& algo, const nsAString& hash);
     virtual ~nsCSPHashSrc();
 
     bool allows(enum CSPKeyword aKeyword, const nsAString& aHashOrNonce) const;
     void toString(nsAString& outStr) const;
+    bool visit(nsCSPSrcVisitor* aVisitor) const;
+
+    inline void getAlgorithm(nsAString& outStr) const
+      { outStr.Assign(mAlgorithm); };
+
+    inline void getHash(nsAString& outStr) const
+      { outStr.Assign(mHash); };
 
   private:
     nsString mAlgorithm;
     nsString mHash;
 };
 
 /* =============== nsCSPReportURI ============ */
 
 class nsCSPReportURI : public nsCSPBaseSrc {
   public:
     explicit nsCSPReportURI(nsIURI* aURI);
     virtual ~nsCSPReportURI();
 
+    bool visit(nsCSPSrcVisitor* aVisitor) const;
     void toString(nsAString& outStr) const;
 
   private:
     nsCOMPtr<nsIURI> mReportURI;
 };
 
+/* =============== nsCSPSrcVisitor ================== */
+
+class nsCSPSrcVisitor {
+  public:
+    virtual bool visitSchemeSrc(const nsCSPSchemeSrc& src) = 0;
+
+    virtual bool visitHostSrc(const nsCSPHostSrc& src) = 0;
+
+    virtual bool visitKeywordSrc(const nsCSPKeywordSrc& src) = 0;
+
+    virtual bool visitNonceSrc(const nsCSPNonceSrc& src) = 0;
+
+    virtual bool visitHashSrc(const nsCSPHashSrc& src) = 0;
+
+  protected:
+    explicit nsCSPSrcVisitor() {};
+    virtual ~nsCSPSrcVisitor() {};
+};
+
 /* =============== nsCSPDirective ============= */
 
 class nsCSPDirective {
   public:
     explicit nsCSPDirective(CSPDirective aDirective);
     virtual ~nsCSPDirective();
 
     virtual bool permits(nsIURI* aUri, const nsAString& aNonce, bool aWasRedirected,
@@ -309,16 +367,18 @@ class nsCSPDirective {
 
     inline bool isDefaultDirective() const
      { return mDirective == nsIContentSecurityPolicy::DEFAULT_SRC_DIRECTIVE; }
 
     virtual bool equals(CSPDirective aDirective) const;
 
     void getReportURIs(nsTArray<nsString> &outReportURIs) const;
 
+    bool visitSrcs(nsCSPSrcVisitor* aVisitor) const;
+
   private:
     CSPDirective            mDirective;
     nsTArray<nsCSPBaseSrc*> mSrcs;
 };
 
 /* =============== nsCSPChildSrcDirective ============= */
 
 /*
@@ -471,16 +531,18 @@ class nsCSPPolicy {
     void getDirectiveStringForContentType(nsContentPolicyType aContentType,
                                           nsAString& outDirective) const;
 
     void getDirectiveAsString(CSPDirective aDir, nsAString& outDirective) const;
 
     inline uint32_t getNumDirectives() const
       { return mDirectives.Length(); }
 
+    bool visitDirectiveSrcs(CSPDirective aDir, nsCSPSrcVisitor* aVisitor) const;
+
   private:
     nsUpgradeInsecureDirective* mUpgradeInsecDir;
     nsTArray<nsCSPDirective*>   mDirectives;
     bool                        mReportOnly;
     nsString                    mReferrerPolicy;
 };
 
 #endif /* nsCSPUtils_h___ */
--- a/dom/u2f/U2F.cpp
+++ b/dom/u2f/U2F.cpp
@@ -6,59 +6,639 @@
 
 #include "hasht.h"
 #include "mozilla/dom/ContentChild.h"
 #include "mozilla/dom/CryptoBuffer.h"
 #include "mozilla/dom/U2F.h"
 #include "mozilla/dom/U2FBinding.h"
 #include "mozilla/Preferences.h"
 #include "nsContentUtils.h"
-#include "nsIEffectiveTLDService.h"
 #include "nsNetCID.h"
 #include "nsNSSComponent.h"
 #include "nsURLParsers.h"
 #include "pk11pub.h"
 
 using mozilla::dom::ContentChild;
 
 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
-};
-
 #define PREF_U2F_SOFTTOKEN_ENABLED "security.webauth.u2f_enable_softtoken"
 #define PREF_U2F_USBTOKEN_ENABLED  "security.webauth.u2f_enable_usbtoken"
 
-const nsString U2F::FinishEnrollment =
-  NS_LITERAL_STRING("navigator.id.finishEnrollment");
-
-const nsString U2F::GetAssertion =
-  NS_LITERAL_STRING("navigator.id.getAssertion");
+NS_NAMED_LITERAL_STRING(kFinishEnrollment, "navigator.id.finishEnrollment");
+NS_NAMED_LITERAL_STRING(kGetAssertion, "navigator.id.getAssertion");
 
 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(U2F)
   NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
   NS_INTERFACE_MAP_ENTRY(nsISupports)
 NS_INTERFACE_MAP_END
 
 NS_IMPL_CYCLE_COLLECTING_ADDREF(U2F)
 NS_IMPL_CYCLE_COLLECTING_RELEASE(U2F)
 
 NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(U2F, mParent)
 
-static mozilla::LazyLogModule gU2FLog("fido_u2f");
+static mozilla::LazyLogModule gU2FLog("webauth_u2f");
+
+template <class CB, class Rsp>
+void
+SendError(CB* aCallback, ErrorCode aErrorCode)
+{
+  Rsp response;
+  response.mErrorCode.Construct(static_cast<uint32_t>(aErrorCode));
+
+  ErrorResult rv;
+  aCallback->Call(response, rv);
+  NS_WARN_IF(rv.Failed());
+  // Useful exceptions already got reported.
+  rv.SuppressException();
+}
+
+static nsresult
+AssembleClientData(const nsAString& aOrigin, const nsAString& aTyp,
+                   const nsAString& aChallenge, CryptoBuffer& aClientData)
+{
+  ClientData 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;
+  }
+
+  if (NS_WARN_IF(!aClientData.Assign(NS_ConvertUTF16toUTF8(json)))) {
+    return NS_ERROR_FAILURE;
+  }
+
+  return NS_OK;
+}
+
+static nsresult
+NSSTokenIsCompatible(nsINSSU2FToken* aNSSToken, const nsString& aVersionString,
+                     bool* aIsCompatible)
+{
+  MOZ_ASSERT(aIsCompatible);
+
+  if (XRE_IsParentProcess()) {
+    MOZ_ASSERT(aNSSToken);
+    return aNSSToken->IsCompatibleVersion(aVersionString, aIsCompatible);
+  }
+
+  ContentChild* cc = ContentChild::GetSingleton();
+  MOZ_ASSERT(cc);
+  if (!cc->SendNSSU2FTokenIsCompatibleVersion(aVersionString, aIsCompatible)) {
+    return NS_ERROR_FAILURE;
+  }
+  return NS_OK;
+}
+
+static nsresult
+NSSTokenIsRegistered(nsINSSU2FToken* aNSSToken, CryptoBuffer& aKeyHandle,
+                     bool* aIsRegistered)
+{
+  MOZ_ASSERT(aIsRegistered);
+
+  if (XRE_IsParentProcess()) {
+    MOZ_ASSERT(aNSSToken);
+    return aNSSToken->IsRegistered(aKeyHandle.Elements(), aKeyHandle.Length(),
+                                   aIsRegistered);
+  }
+
+  ContentChild* cc = ContentChild::GetSingleton();
+  MOZ_ASSERT(cc);
+  if (!cc->SendNSSU2FTokenIsRegistered(aKeyHandle, aIsRegistered)) {
+    return NS_ERROR_FAILURE;
+  }
+  return NS_OK;
+}
+
+static nsresult
+NSSTokenSign(nsINSSU2FToken* aNSSToken, CryptoBuffer& aKeyHandle,
+             CryptoBuffer& aApplication, CryptoBuffer& aChallenge,
+             CryptoBuffer& aSignatureData)
+{
+  if (XRE_IsParentProcess()) {
+    MOZ_ASSERT(aNSSToken);
+    uint8_t* buffer;
+    uint32_t bufferlen;
+    nsresult rv = aNSSToken->Sign(aApplication.Elements(), aApplication.Length(),
+                                  aChallenge.Elements(), aChallenge.Length(),
+                                  aKeyHandle.Elements(), aKeyHandle.Length(),
+                                  &buffer, &bufferlen);
+    if (NS_WARN_IF(NS_FAILED(rv))) {
+      return rv;
+    }
+
+    MOZ_ASSERT(buffer);
+    aSignatureData.Assign(buffer, bufferlen);
+    free(buffer);
+    return NS_OK;
+  }
+
+  nsTArray<uint8_t> signatureBuffer;
+  ContentChild* cc = ContentChild::GetSingleton();
+  MOZ_ASSERT(cc);
+  if (!cc->SendNSSU2FTokenSign(aApplication, aChallenge, aKeyHandle,
+                               &signatureBuffer)) {
+    return NS_ERROR_FAILURE;
+  }
+
+  aSignatureData.Assign(signatureBuffer);
+  return NS_OK;
+}
+
+static nsresult
+NSSTokenRegister(nsINSSU2FToken* aNSSToken, CryptoBuffer& aApplication,
+                 CryptoBuffer& aChallenge, CryptoBuffer& aRegistrationData)
+{
+  if (XRE_IsParentProcess()) {
+    MOZ_ASSERT(aNSSToken);
+    uint8_t* buffer;
+    uint32_t bufferlen;
+    nsresult rv;
+    rv = aNSSToken->Register(aApplication.Elements(), aApplication.Length(),
+                             aChallenge.Elements(), aChallenge.Length(),
+                             &buffer, &bufferlen);
+    if (NS_WARN_IF(NS_FAILED(rv))) {
+      return rv;
+    }
+
+    MOZ_ASSERT(buffer);
+    aRegistrationData.Assign(buffer, bufferlen);
+    free(buffer);
+    return NS_OK;
+  }
+
+  nsTArray<uint8_t> registrationBuffer;
+  ContentChild* cc = ContentChild::GetSingleton();
+  MOZ_ASSERT(cc);
+  if (!cc->SendNSSU2FTokenRegister(aApplication, aChallenge,
+                                   &registrationBuffer)) {
+    return NS_ERROR_FAILURE;
+  }
+
+  aRegistrationData.Assign(registrationBuffer);
+  return NS_OK;
+}
+
+U2FTask::U2FTask(const nsAString& aOrigin, const nsAString& aAppId)
+  : mOrigin(aOrigin)
+  , mAppId(aAppId)
+{}
+
+U2FTask::~U2FTask()
+{}
+
+U2FRegisterTask::U2FRegisterTask(const nsAString& aOrigin,
+                                 const nsAString& aAppId,
+                                 const Sequence<RegisterRequest>& aRegisterRequests,
+                                 const Sequence<RegisteredKey>& aRegisteredKeys,
+                                 U2FRegisterCallback* aCallback,
+                                 const nsCOMPtr<nsINSSU2FToken>& aNSSToken)
+  : U2FTask(aOrigin, aAppId)
+  , mRegisterRequests(aRegisterRequests)
+  , mRegisteredKeys(aRegisteredKeys)
+  , mCallback(aCallback)
+  , mNSSToken(aNSSToken)
+{}
+
+U2FRegisterTask::~U2FRegisterTask()
+{
+  nsNSSShutDownPreventionLock locker;
+
+  if (isAlreadyShutDown()) {
+    return;
+  }
+  shutdown(calledFromObject);
+}
+
+void
+U2FRegisterTask::ReturnError(ErrorCode aCode)
+{
+  SendError<U2FRegisterCallback, RegisterResponse>(mCallback.get(), aCode);
+}
+
+NS_IMETHODIMP
+U2FRegisterTask::Run()
+{
+  nsNSSShutDownPreventionLock locker;
+  if (isAlreadyShutDown()) {
+    ReturnError(ErrorCode::OTHER_ERROR);
+    return NS_ERROR_FAILURE;
+  }
+
+  // TODO: Implement USB Tokens in Bug 1245527
+  const bool softTokenEnabled =
+    Preferences::GetBool(PREF_U2F_SOFTTOKEN_ENABLED);
+
+  for (size_t i = 0; i < mRegisteredKeys.Length(); ++i) {
+    RegisteredKey request(mRegisteredKeys[i]);
+
+    // Check for required attributes
+    if (!(request.mKeyHandle.WasPassed() &&
+          request.mVersion.WasPassed())) {
+      continue;
+    }
+
+    // Do not permit an individual RegisteredKey to assert a different AppID
+    if (request.mAppId.WasPassed() && mAppId != request.mAppId.Value()) {
+      continue;
+    }
+
+    // Decode the key handle
+    CryptoBuffer keyHandle;
+    nsresult rv = keyHandle.FromJwkBase64(request.mKeyHandle.Value());
+    if (NS_WARN_IF(NS_FAILED(rv))) {
+      ReturnError(ErrorCode::BAD_REQUEST);
+      return NS_ERROR_FAILURE;
+    }
+
+    // We ignore mTransports, as it is intended to be used for sorting the
+    // available devices by preference, but is not an exclusion factor.
+
+    bool isCompatible = false;
+    bool isRegistered = false;
+
+    // Determine if the provided keyHandle is registered at any device. If so,
+    // then we'll return DEVICE_INELIGIBLE to signify we're already registered.
+    if (softTokenEnabled) {
+      rv = NSSTokenIsCompatible(mNSSToken, request.mVersion.Value(),
+                                &isCompatible);
+      if (NS_FAILED(rv)) {
+        ReturnError(ErrorCode::OTHER_ERROR);
+        return NS_ERROR_FAILURE;
+      }
+
+      rv = NSSTokenIsRegistered(mNSSToken, keyHandle, &isRegistered);
+      if (NS_FAILED(rv)) {
+        ReturnError(ErrorCode::OTHER_ERROR);
+        return NS_ERROR_FAILURE;
+      }
+
+      if (isCompatible && isRegistered) {
+        ReturnError(ErrorCode::DEVICE_INELIGIBLE);
+        return NS_OK;
+      }
+    }
+  }
+
+  // Search the requests in order for the first some token can fulfill
+  for (size_t i = 0; i < mRegisterRequests.Length(); ++i) {
+    RegisterRequest request(mRegisterRequests[i]);
+
+    // Check for equired attributes
+    if (!(request.mVersion.WasPassed() &&
+        request.mChallenge.WasPassed())) {
+      continue;
+    }
+
+    CryptoBuffer clientData;
+    nsresult rv = AssembleClientData(mOrigin, kFinishEnrollment,
+                                     request.mChallenge.Value(),
+                                     clientData);
+    if (NS_WARN_IF(NS_FAILED(rv))) {
+      ReturnError(ErrorCode::OTHER_ERROR);
+      return NS_ERROR_FAILURE;
+    }
+
+    // Hash the AppID and the ClientData into the AppParam and ChallengeParam
+    SECStatus srv;
+    nsCString cAppId = NS_ConvertUTF16toUTF8(mAppId);
+    CryptoBuffer appParam;
+    CryptoBuffer challengeParam;
+    if (!appParam.SetLength(SHA256_LENGTH, fallible) ||
+        !challengeParam.SetLength(SHA256_LENGTH, fallible)) {
+      ReturnError(ErrorCode::OTHER_ERROR);
+      return NS_ERROR_FAILURE;
+    }
+
+    srv = PK11_HashBuf(SEC_OID_SHA256, appParam.Elements(),
+                       reinterpret_cast<const uint8_t*>(cAppId.BeginReading()),
+                       cAppId.Length());
+    if (srv != SECSuccess) {
+      ReturnError(ErrorCode::OTHER_ERROR);
+      return NS_ERROR_FAILURE;
+    }
+
+    srv = PK11_HashBuf(SEC_OID_SHA256, challengeParam.Elements(),
+                       clientData.Elements(), clientData.Length());
+    if (srv != SECSuccess) {
+      ReturnError(ErrorCode::OTHER_ERROR);
+      return NS_ERROR_FAILURE;
+    }
+
+    // Get the registration data from the token
+    CryptoBuffer regData;
+    bool registerSuccess = false;
+    bool isCompatible = false;
+
+    if (!registerSuccess && softTokenEnabled) {
+      rv = NSSTokenIsCompatible(mNSSToken, request.mVersion.Value(),
+                                &isCompatible);
+      if (NS_FAILED(rv)) {
+        ReturnError(ErrorCode::OTHER_ERROR);
+        return NS_ERROR_FAILURE;
+      }
+
+      if (isCompatible) {
+        rv = NSSTokenRegister(mNSSToken, appParam, challengeParam, regData);
+        if (NS_FAILED(rv)) {
+          ReturnError(ErrorCode::OTHER_ERROR);
+          return NS_ERROR_FAILURE;
+        }
+        registerSuccess = true;
+      }
+    }
+
+    if (!registerSuccess) {
+      // Try another request
+      continue;
+    }
+
+    // Assemble a response object to return
+    nsString clientDataBase64, registrationDataBase64;
+    nsresult rvClientData =
+      clientData.ToJwkBase64(clientDataBase64);
+    nsresult rvRegistrationData =
+      regData.ToJwkBase64(registrationDataBase64);
+    if (NS_WARN_IF(NS_FAILED(rvClientData)) ||
+        NS_WARN_IF(NS_FAILED(rvRegistrationData))) {
+      ReturnError(ErrorCode::OTHER_ERROR);
+      return NS_ERROR_FAILURE;
+    }
+
+    RegisterResponse response;
+    response.mClientData.Construct(clientDataBase64);
+    response.mRegistrationData.Construct(registrationDataBase64);
+    response.mErrorCode.Construct(static_cast<uint32_t>(ErrorCode::OK));
+
+    ErrorResult result;
+    mCallback->Call(response, result);
+    NS_WARN_IF(result.Failed());
+    // Useful exceptions already got reported.
+    result.SuppressException();
+    return NS_OK;
+  }
+
+  // Nothing could satisfy
+  ReturnError(ErrorCode::BAD_REQUEST);
+  return NS_ERROR_FAILURE;
+}
+
+U2FSignTask::U2FSignTask(const nsAString& aOrigin,
+                         const nsAString& aAppId,
+                         const nsAString& aChallenge,
+                         const Sequence<RegisteredKey>& aRegisteredKeys,
+                         U2FSignCallback* aCallback,
+                         const nsCOMPtr<nsINSSU2FToken>& aNSSToken)
+  : U2FTask(aOrigin, aAppId)
+  , mChallenge(aChallenge)
+  , mRegisteredKeys(aRegisteredKeys)
+  , mCallback(aCallback)
+  , mNSSToken(aNSSToken)
+{}
+
+U2FSignTask::~U2FSignTask()
+{
+  nsNSSShutDownPreventionLock locker;
+  if (isAlreadyShutDown()) {
+    return;
+  }
+  shutdown(calledFromObject);
+}
+
+void
+U2FSignTask::ReturnError(ErrorCode aCode)
+{
+  SendError<U2FSignCallback, SignResponse>(mCallback.get(), aCode);
+}
+
+NS_IMETHODIMP
+U2FSignTask::Run()
+{
+  nsNSSShutDownPreventionLock locker;
+  if (isAlreadyShutDown()) {
+    ReturnError(ErrorCode::OTHER_ERROR);
+    return NS_ERROR_FAILURE;
+  }
+
+  // TODO: Implement USB Tokens in Bug 1245527
+  const bool softTokenEnabled =
+    Preferences::GetBool(PREF_U2F_SOFTTOKEN_ENABLED);
+
+  // Search the requests for one a token can fulfill
+  for (size_t i = 0; i < mRegisteredKeys.Length(); i += 1) {
+    RegisteredKey request(mRegisteredKeys[i]);
+
+    // Check for required attributes
+    if (!(request.mVersion.WasPassed() &&
+          request.mKeyHandle.WasPassed())) {
+      continue;
+    }
+
+    // Do not permit an individual RegisteredKey to assert a different AppID
+    if (request.mAppId.WasPassed() && mAppId != request.mAppId.Value()) {
+      continue;
+    }
+
+    // Assemble a clientData object
+    CryptoBuffer clientData;
+    nsresult rv = AssembleClientData(mOrigin, kGetAssertion, mChallenge,
+                                     clientData);
+    if (NS_WARN_IF(NS_FAILED(rv))) {
+      ReturnError(ErrorCode::OTHER_ERROR);
+      return NS_ERROR_FAILURE;
+    }
+
+    // Hash the AppID and the ClientData into the AppParam and ChallengeParam
+    SECStatus srv;
+    nsCString cAppId = NS_ConvertUTF16toUTF8(mAppId);
+    CryptoBuffer appParam;
+    CryptoBuffer challengeParam;
+    if (!appParam.SetLength(SHA256_LENGTH, fallible) ||
+        !challengeParam.SetLength(SHA256_LENGTH, fallible)) {
+      ReturnError(ErrorCode::OTHER_ERROR);
+      return NS_ERROR_FAILURE;
+    }
+
+    srv = PK11_HashBuf(SEC_OID_SHA256, appParam.Elements(),
+                       reinterpret_cast<const uint8_t*>(cAppId.BeginReading()),
+                       cAppId.Length());
+    if (srv != SECSuccess) {
+      ReturnError(ErrorCode::OTHER_ERROR);
+      return NS_ERROR_FAILURE;
+    }
+
+    srv = PK11_HashBuf(SEC_OID_SHA256, challengeParam.Elements(),
+                       clientData.Elements(), clientData.Length());
+    if (srv != SECSuccess) {
+      ReturnError(ErrorCode::OTHER_ERROR);
+      return NS_ERROR_FAILURE;
+    }
+
+    // Decode the key handle
+    CryptoBuffer keyHandle;
+    rv = keyHandle.FromJwkBase64(request.mKeyHandle.Value());
+    if (NS_WARN_IF(NS_FAILED(rv))) {
+      ReturnError(ErrorCode::OTHER_ERROR);
+      return NS_ERROR_FAILURE;
+    }
+
+    // Get the signature from the token
+    CryptoBuffer signatureData;
+    bool signSuccess = false;
+
+    // We ignore mTransports, as it is intended to be used for sorting the
+    // available devices by preference, but is not an exclusion factor.
+
+    if (!signSuccess && softTokenEnabled) {
+      bool isCompatible = false;
+      bool isRegistered = false;
+
+      rv = NSSTokenIsCompatible(mNSSToken, request.mVersion.Value(),
+                                &isCompatible);
+      if (NS_FAILED(rv)) {
+        ReturnError(ErrorCode::OTHER_ERROR);
+        return NS_ERROR_FAILURE;
+      }
+
+      rv = NSSTokenIsRegistered(mNSSToken, keyHandle, &isRegistered);
+      if (NS_FAILED(rv)) {
+        ReturnError(ErrorCode::OTHER_ERROR);
+        return NS_ERROR_FAILURE;
+      }
+
+      if (isCompatible && isRegistered) {
+        rv = NSSTokenSign(mNSSToken, keyHandle, appParam, challengeParam,
+                          signatureData);
+        if (NS_FAILED(rv)) {
+          ReturnError(ErrorCode::OTHER_ERROR);
+          return NS_ERROR_FAILURE;
+        }
+        signSuccess = true;
+      }
+    }
+
+    if (!signSuccess) {
+      // Try another request
+      continue;
+    }
+
+    // Assemble a response object to return
+    nsString clientDataBase64, signatureDataBase64;
+    nsresult rvClientData =
+      clientData.ToJwkBase64(clientDataBase64);
+    nsresult rvSignatureData =
+      signatureData.ToJwkBase64(signatureDataBase64);
+    if (NS_WARN_IF(NS_FAILED(rvClientData)) ||
+        NS_WARN_IF(NS_FAILED(rvSignatureData))) {
+      ReturnError(ErrorCode::OTHER_ERROR);
+      return NS_ERROR_FAILURE;
+    }
+    SignResponse response;
+    response.mKeyHandle.Construct(request.mKeyHandle.Value());
+    response.mClientData.Construct(clientDataBase64);
+    response.mSignatureData.Construct(signatureDataBase64);
+    response.mErrorCode.Construct(static_cast<uint32_t>(ErrorCode::OK));
+
+    ErrorResult result;
+    mCallback->Call(response, result);
+    NS_WARN_IF(result.Failed());
+    // Useful exceptions already got reported.
+    result.SuppressException();
+    return NS_OK;
+  }
+
+  // Nothing could satisfy
+  ReturnError(ErrorCode::DEVICE_INELIGIBLE);
+  return NS_ERROR_FAILURE;
+}
+
+// EvaluateAppIDAndRunTask determines whether the supplied FIDO AppID is valid for
+// the current FacetID, e.g., the current origin.
+// See https://fidoalliance.org/specs/fido-u2f-v1.0-nfc-bt-amendment-20150514/fido-appid-and-facets.html
+// for a description of the algorithm.
+static void
+EvaluateAppIDAndRunTask(U2FTask* aTask)
+{
+  MOZ_ASSERT(aTask);
+
+  nsCOMPtr<nsIURLParser> urlParser =
+      do_GetService(NS_STDURLPARSER_CONTRACTID);
+
+  MOZ_ASSERT(urlParser);
+
+  uint32_t facetSchemePos;
+  int32_t facetSchemeLen;
+  uint32_t facetAuthPos;
+  int32_t facetAuthLen;
+  // Facet is the specification's way of referring to the web origin.
+  nsAutoCString facetUrl = NS_ConvertUTF16toUTF8(aTask->mOrigin);
+  nsresult rv = urlParser->ParseURL(facetUrl.get(), aTask->mOrigin.Length(),
+                                    &facetSchemePos, &facetSchemeLen,
+                                    &facetAuthPos, &facetAuthLen,
+                                    nullptr, nullptr);      // ignore path
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    aTask->ReturnError(ErrorCode::BAD_REQUEST);
+    return;
+  }
+
+  nsAutoCString facetScheme(Substring(facetUrl, facetSchemePos, facetSchemeLen));
+  nsAutoCString facetAuth(Substring(facetUrl, facetAuthPos, facetAuthLen));
+
+  uint32_t appIdSchemePos;
+  int32_t appIdSchemeLen;
+  uint32_t appIdAuthPos;
+  int32_t appIdAuthLen;
+  // AppID is user-supplied. It's quite possible for this parse to fail.
+  nsAutoCString appIdUrl = NS_ConvertUTF16toUTF8(aTask->mAppId);
+  rv = urlParser->ParseURL(appIdUrl.get(), aTask->mAppId.Length(),
+                           &appIdSchemePos, &appIdSchemeLen,
+                           &appIdAuthPos, &appIdAuthLen,
+                           nullptr, nullptr);      // ignore path
+  if (NS_FAILED(rv)) {
+    aTask->ReturnError(ErrorCode::BAD_REQUEST);
+    return;
+  }
+
+  nsAutoCString appIdScheme(Substring(appIdUrl, appIdSchemePos, appIdSchemeLen));
+  nsAutoCString appIdAuth(Substring(appIdUrl, appIdAuthPos, appIdAuthLen));
+
+  // If the facetId (origin) is not HTTPS, reject
+  if (!facetScheme.LowerCaseEqualsLiteral("https")) {
+    aTask->ReturnError(ErrorCode::BAD_REQUEST);
+    return;
+  }
+
+  // If the appId is empty or null, overwrite it with the facetId and accept
+  if (aTask->mAppId.IsEmpty() || aTask->mAppId.EqualsLiteral("null")) {
+    aTask->mAppId.Assign(aTask->mOrigin);
+    aTask->Run();
+    return;
+  }
+
+  // if the appId URL is not HTTPS, reject.
+  if (!appIdScheme.LowerCaseEqualsLiteral("https")) {
+    aTask->ReturnError(ErrorCode::BAD_REQUEST);
+    return;
+  }
+
+  // If the facetId and the appId auths match, accept
+  if (facetAuth == appIdAuth) {
+    aTask->Run();
+    return;
+  }
+
+  // TODO(Bug 1244959) Implement the remaining algorithm.
+  aTask->ReturnError(ErrorCode::BAD_REQUEST);
+  return;
+}
 
 U2F::U2F()
 {}
 
 U2F::~U2F()
 {
   nsNSSShutDownPreventionLock locker;
 
@@ -86,16 +666,17 @@ U2F::Init(nsPIDOMWindowInner* aParent, E
 
   nsIPrincipal* principal = doc->NodePrincipal();
   aRv = nsContentUtils::GetUTFOrigin(principal, mOrigin);
   if (NS_WARN_IF(aRv.Failed())) {
     return;
   }
 
   if (NS_WARN_IF(mOrigin.IsEmpty())) {
+    aRv.Throw(NS_ERROR_FAILURE);
     return;
   }
 
   if (!EnsureNSSInitializedChromeOrContent()) {
     MOZ_LOG(gU2FLog, LogLevel::Debug, ("Failed to get NSS context for U2F"));
     aRv.Throw(NS_ERROR_FAILURE);
     return;
   }
@@ -109,607 +690,42 @@ U2F::Init(nsPIDOMWindowInner* aParent, E
   }
 
   aRv = mUSBToken.Init();
   if (NS_WARN_IF(aRv.Failed())) {
     return;
   }
 }
 
-nsresult
-U2F::NSSTokenIsCompatible(const nsString& aVersionString, bool* aIsCompatible)
-{
-  MOZ_ASSERT(aIsCompatible);
-
-  if (XRE_IsParentProcess()) {
-    MOZ_ASSERT(mNSSToken);
-    return mNSSToken->IsCompatibleVersion(aVersionString, aIsCompatible);
-  }
-
-  ContentChild* cc = ContentChild::GetSingleton();
-  MOZ_ASSERT(cc);
-  if (!cc->SendNSSU2FTokenIsCompatibleVersion(aVersionString, aIsCompatible)) {
-    return NS_ERROR_FAILURE;
-  }
-  return NS_OK;
-}
-
-nsresult
-U2F::NSSTokenIsRegistered(CryptoBuffer& aKeyHandle, bool* aIsRegistered)
-{
-  MOZ_ASSERT(aIsRegistered);
-
-  if (XRE_IsParentProcess()) {
-    MOZ_ASSERT(mNSSToken);
-    return mNSSToken->IsRegistered(aKeyHandle.Elements(), aKeyHandle.Length(),
-                                   aIsRegistered);
-  }
-
-  ContentChild* cc = ContentChild::GetSingleton();
-  MOZ_ASSERT(cc);
-  if (!cc->SendNSSU2FTokenIsRegistered(aKeyHandle, aIsRegistered)) {
-    return NS_ERROR_FAILURE;
-  }
-  return NS_OK;
-}
-
-nsresult
-U2F::NSSTokenRegister(CryptoBuffer& aApplication, CryptoBuffer& aChallenge,
-                      CryptoBuffer& aRegistrationData)
-{
-  if (XRE_IsParentProcess()) {
-    MOZ_ASSERT(mNSSToken);
-    uint8_t* buffer;
-    uint32_t bufferlen;
-    nsresult rv;
-    rv = mNSSToken->Register(aApplication.Elements(), aApplication.Length(),
-                             aChallenge.Elements(), aChallenge.Length(),
-                             &buffer, &bufferlen);
-    if (NS_WARN_IF(NS_FAILED(rv))) {
-      return rv;
-    }
-
-    MOZ_ASSERT(buffer);
-    aRegistrationData.Assign(buffer, bufferlen);
-    free(buffer);
-    return NS_OK;
-  }
-
-  nsTArray<uint8_t> registrationBuffer;
-  ContentChild* cc = ContentChild::GetSingleton();
-  MOZ_ASSERT(cc);
-  if (!cc->SendNSSU2FTokenRegister(aApplication, aChallenge,
-                                   &registrationBuffer)) {
-    return NS_ERROR_FAILURE;
-  }
-
-  aRegistrationData.Assign(registrationBuffer);
-  return NS_OK;
-}
-
-nsresult
-U2F::NSSTokenSign(CryptoBuffer& aKeyHandle, CryptoBuffer& aApplication,
-                  CryptoBuffer& aChallenge, CryptoBuffer& aSignatureData)
-{
-  if (XRE_IsParentProcess()) {
-    MOZ_ASSERT(mNSSToken);
-    uint8_t* buffer;
-    uint32_t bufferlen;
-    nsresult rv = mNSSToken->Sign(aApplication.Elements(), aApplication.Length(),
-                                  aChallenge.Elements(), aChallenge.Length(),
-                                  aKeyHandle.Elements(), aKeyHandle.Length(),
-                                  &buffer, &bufferlen);
-    if (NS_WARN_IF(NS_FAILED(rv))) {
-      return rv;
-    }
-
-    MOZ_ASSERT(buffer);
-    aSignatureData.Assign(buffer, bufferlen);
-    free(buffer);
-    return NS_OK;
-  }
-
-  nsTArray<uint8_t> signatureBuffer;
-  ContentChild* cc = ContentChild::GetSingleton();
-  MOZ_ASSERT(cc);
-  if (!cc->SendNSSU2FTokenSign(aApplication, aChallenge, aKeyHandle,
-                               &signatureBuffer)) {
-    return NS_ERROR_FAILURE;
-  }
-
-  aSignatureData.Assign(signatureBuffer);
-  return NS_OK;
-}
-
-nsresult
-U2F::AssembleClientData(const nsAString& aTyp,
-                        const nsAString& aChallenge,
-                        CryptoBuffer& aClientData) const
-{
-  ClientData clientDataObject;
-  clientDataObject.mTyp.Construct(aTyp); // "Typ" from the U2F specification
-  clientDataObject.mChallenge.Construct(aChallenge);
-  clientDataObject.mOrigin.Construct(mOrigin);
-
-  nsAutoString json;
-  if (NS_WARN_IF(!clientDataObject.ToJSON(json))) {
-    return NS_ERROR_FAILURE;
-  }
-
-  if (NS_WARN_IF(!aClientData.Assign(NS_ConvertUTF16toUTF8(json)))) {
-    return NS_ERROR_FAILURE;
-  }
-
-  return NS_OK;
-}
-
-bool
-U2F::ValidAppID(/* in/out */ nsString& aAppId) const
-{
-  nsCOMPtr<nsIURLParser> urlParser =
-      do_GetService(NS_STDURLPARSER_CONTRACTID);
-  nsCOMPtr<nsIEffectiveTLDService> tldService =
-      do_GetService(NS_EFFECTIVETLDSERVICE_CONTRACTID);
-
-  MOZ_ASSERT(urlParser);
-  MOZ_ASSERT(tldService);
-
-  uint32_t facetSchemePos;
-  int32_t facetSchemeLen;
-  uint32_t facetAuthPos;
-  int32_t facetAuthLen;
-  // Facet is the specification's way of referring to the web origin.
-  nsAutoCString facetUrl = NS_ConvertUTF16toUTF8(mOrigin);
-  nsresult rv = urlParser->ParseURL(facetUrl.get(), mOrigin.Length(),
-                                    &facetSchemePos, &facetSchemeLen,
-                                    &facetAuthPos, &facetAuthLen,
-                                    nullptr, nullptr);      // ignore path
-  if (NS_WARN_IF(NS_FAILED(rv))) {
-    return false;
-  }
-
-  nsAutoCString facetScheme(Substring(facetUrl, facetSchemePos, facetSchemeLen));
-  nsAutoCString facetAuth(Substring(facetUrl, facetAuthPos, facetAuthLen));
-
-  uint32_t appIdSchemePos;
-  int32_t appIdSchemeLen;
-  uint32_t appIdAuthPos;
-  int32_t appIdAuthLen;
-  nsAutoCString appIdUrl = NS_ConvertUTF16toUTF8(aAppId);
-  rv = urlParser->ParseURL(appIdUrl.get(), aAppId.Length(),
-                           &appIdSchemePos, &appIdSchemeLen,
-                           &appIdAuthPos, &appIdAuthLen,
-                           nullptr, nullptr);      // ignore path
-  if (NS_WARN_IF(NS_FAILED(rv))) {
-    return false;
-  }
-
-  nsAutoCString appIdScheme(Substring(appIdUrl, appIdSchemePos, appIdSchemeLen));
-  nsAutoCString appIdAuth(Substring(appIdUrl, appIdAuthPos, appIdAuthLen));
-
-  // If the facetId (origin) is not HTTPS, reject
-  if (!facetScheme.LowerCaseEqualsLiteral("https")) {
-    return false;
-  }
-
-  // If the appId is empty or null, overwrite it with the facetId and accept
-  if (aAppId.IsEmpty() || aAppId.EqualsLiteral("null")) {
-    aAppId.Assign(mOrigin);
-    return true;
-  }
-
-  // if the appId URL is not HTTPS, reject.
-  if (!appIdScheme.LowerCaseEqualsLiteral("https")) {
-    return false;
-  }
-
-  // If the facetId and the appId auths match, accept
-  if (facetAuth == appIdAuth) {
-    return true;
-  }
-
-  // TODO(Bug 1244959) Implement the remaining algorithm.
-  return false;
-}
-
-template <class CB, class Rsp>
-void
-SendError(CB& aCallback, ErrorCode aErrorCode)
-{
-  Rsp response;
-  response.mErrorCode.Construct(static_cast<uint32_t>(aErrorCode));
-
-  ErrorResult rv;
-  aCallback.Call(response, rv);
-  NS_WARN_IF(rv.Failed());
-  // Useful exceptions already got reported.
-  rv.SuppressException();
-}
-
 void
 U2F::Register(const nsAString& aAppId,
               const Sequence<RegisterRequest>& aRegisterRequests,
               const Sequence<RegisteredKey>& aRegisteredKeys,
               U2FRegisterCallback& aCallback,
               const Optional<Nullable<int32_t>>& opt_aTimeoutSeconds,
               ErrorResult& aRv)
 {
-  nsNSSShutDownPreventionLock locker;
-  if (isAlreadyShutDown()) {
-    SendError<U2FRegisterCallback, RegisterResponse>(aCallback,
-                                                     ErrorCode::OTHER_ERROR);
-    return;
-  }
-
-  const bool softTokenEnabled =
-    Preferences::GetBool(PREF_U2F_SOFTTOKEN_ENABLED);
-
-  const bool usbTokenEnabled =
-    Preferences::GetBool(PREF_U2F_USBTOKEN_ENABLED);
-
-  nsAutoString appId(aAppId);
-
-  // Verify the global appId first.
-  if (!ValidAppID(appId)) {
-    SendError<U2FRegisterCallback, RegisterResponse>(aCallback,
-                                                     ErrorCode::BAD_REQUEST);
-    return;
-  }
-
-  for (size_t i = 0; i < aRegisteredKeys.Length(); ++i) {
-    RegisteredKey request(aRegisteredKeys[i]);
-
-    // Check for required attributes
-    if (!(request.mKeyHandle.WasPassed() &&
-          request.mVersion.WasPassed())) {
-      continue;
-    }
-
-    // Verify the appId for this Registered Key, if set
-    if (request.mAppId.WasPassed() &&
-        !ValidAppID(request.mAppId.Value())) {
-      continue;
-    }
-
-    // Decode the key handle
-    CryptoBuffer keyHandle;
-    nsresult rv = keyHandle.FromJwkBase64(request.mKeyHandle.Value());
-    if (NS_WARN_IF(NS_FAILED(rv))) {
-      SendError<U2FRegisterCallback, RegisterResponse>(aCallback,
-                                                       ErrorCode::BAD_REQUEST);
-      return;
-    }
-
-    // We ignore mTransports, as it is intended to be used for sorting the
-    // available devices by preference, but is not an exclusion factor.
-
-    bool isCompatible = false;
-    bool isRegistered = false;
-
-    // Determine if the provided keyHandle is registered at any device. If so,
-    // then we'll return DEVICE_INELIGIBLE to signify we're already registered.
-    if (usbTokenEnabled &&
-        mUSBToken.IsCompatibleVersion(request.mVersion.Value()) &&
-        mUSBToken.IsRegistered(keyHandle)) {
-      SendError<U2FRegisterCallback, RegisterResponse>(aCallback,
-                                                  ErrorCode::DEVICE_INELIGIBLE);
-      return;
-    }
-    if (softTokenEnabled) {
-      rv = NSSTokenIsCompatible(request.mVersion.Value(), &isCompatible);
-      if (NS_FAILED(rv)) {
-        SendError<U2FRegisterCallback, RegisterResponse>(aCallback,
-                                                         ErrorCode::OTHER_ERROR);
-        return;
-      }
-
-      rv = NSSTokenIsRegistered(keyHandle, &isRegistered);
-      if (NS_FAILED(rv)) {
-        SendError<U2FRegisterCallback, RegisterResponse>(aCallback,
-                                                         ErrorCode::OTHER_ERROR);
-        return;
-      }
-
-      if (isCompatible && isRegistered) {
-        SendError<U2FRegisterCallback, RegisterResponse>(aCallback,
-                                                         ErrorCode::DEVICE_INELIGIBLE);
-        return;
-      }
-    }
-  }
-
-  // Search the requests in order for the first some token can fulfill
-  for (size_t i = 0; i < aRegisterRequests.Length(); ++i) {
-    RegisterRequest request(aRegisterRequests[i]);
-
-    // Check for equired attributes
-    if (!(request.mVersion.WasPassed() &&
-        request.mChallenge.WasPassed())) {
-      continue;
-    }
+  RefPtr<U2FRegisterTask> registerTask = new U2FRegisterTask(mOrigin, aAppId,
+                                                             aRegisterRequests,
+                                                             aRegisteredKeys,
+                                                             &aCallback,
+                                                             mNSSToken);
 
-    CryptoBuffer clientData;
-    nsresult rv = AssembleClientData(FinishEnrollment,
-                                     request.mChallenge.Value(),
-                                     clientData);
-    if (NS_WARN_IF(NS_FAILED(rv))) {
-      SendError<U2FRegisterCallback, RegisterResponse>(aCallback,
-                                                       ErrorCode::OTHER_ERROR);
-      return;
-    }
-
-    // Hash the AppID and the ClientData into the AppParam and ChallengeParam
-    SECStatus srv;
-    nsCString cAppId = NS_ConvertUTF16toUTF8(appId);
-    CryptoBuffer appParam;
-    CryptoBuffer challengeParam;
-    if (!appParam.SetLength(SHA256_LENGTH, fallible) ||
-        !challengeParam.SetLength(SHA256_LENGTH, fallible)) {
-      SendError<U2FRegisterCallback, RegisterResponse>(aCallback,
-                                                       ErrorCode::OTHER_ERROR);
-      return;
-    }
-
-    srv = PK11_HashBuf(SEC_OID_SHA256, appParam.Elements(),
-                       reinterpret_cast<const uint8_t*>(cAppId.BeginReading()),
-                       cAppId.Length());
-    if (srv != SECSuccess) {
-      SendError<U2FRegisterCallback, RegisterResponse>(aCallback,
-                                                       ErrorCode::OTHER_ERROR);
-      return;
-    }
-
-    srv = PK11_HashBuf(SEC_OID_SHA256, challengeParam.Elements(),
-                       clientData.Elements(), clientData.Length());
-    if (srv != SECSuccess) {
-      SendError<U2FRegisterCallback, RegisterResponse>(aCallback,
-                                                       ErrorCode::OTHER_ERROR);
-      return;
-    }
-
-    // Get the registration data from the token
-    CryptoBuffer registrationData;
-    bool registerSuccess = false;
-    bool isCompatible = false;
-    if (usbTokenEnabled) {
-      // TODO: Implement in Bug 1245527
-      SendError<U2FRegisterCallback, RegisterResponse>(aCallback,
-                                                       ErrorCode::OTHER_ERROR);
-      return;
-    }
-
-    if (!registerSuccess && softTokenEnabled) {
-      rv = NSSTokenIsCompatible(request.mVersion.Value(), &isCompatible);
-      if (NS_FAILED(rv)) {
-        SendError<U2FRegisterCallback, RegisterResponse>(aCallback,
-                                                        ErrorCode::OTHER_ERROR);
-        return;
-      }
-
-      if (isCompatible) {
-        rv = NSSTokenRegister(appParam, challengeParam, registrationData);
-        if (NS_FAILED(rv)) {
-          SendError<U2FRegisterCallback, RegisterResponse>(aCallback,
-                                                        ErrorCode::OTHER_ERROR);
-          return;
-        }
-        registerSuccess = true;
-      }
-    }
-
-    if (!registerSuccess) {
-      // Try another request
-      continue;
-    }
-
-    // Assemble a response object to return
-    nsString clientDataBase64, registrationDataBase64;
-    nsresult rvClientData =
-      clientData.ToJwkBase64(clientDataBase64);
-    nsresult rvRegistrationData =
-      registrationData.ToJwkBase64(registrationDataBase64);
-    if (NS_WARN_IF(NS_FAILED(rvClientData)) ||
-        NS_WARN_IF(NS_FAILED(rvRegistrationData))) {
-      SendError<U2FRegisterCallback, RegisterResponse>(aCallback,
-                                                       ErrorCode::OTHER_ERROR);
-      return;
-    }
-
-    RegisterResponse response;
-    response.mClientData.Construct(clientDataBase64);
-    response.mRegistrationData.Construct(registrationDataBase64);
-    response.mErrorCode.Construct(static_cast<uint32_t>(ErrorCode::OK));
-
-    ErrorResult result;
-    aCallback.Call(response, result);
-    NS_WARN_IF(result.Failed());
-    // Useful exceptions already got reported.
-    result.SuppressException();
-    return;
-  }
-
-  // Nothing could satisfy
-  SendError<U2FRegisterCallback, RegisterResponse>(aCallback,
-                                                   ErrorCode::BAD_REQUEST);
-  return;
+  EvaluateAppIDAndRunTask(registerTask);
 }
 
 void
 U2F::Sign(const nsAString& aAppId,
           const nsAString& aChallenge,
           const Sequence<RegisteredKey>& aRegisteredKeys,
           U2FSignCallback& aCallback,
           const Optional<Nullable<int32_t>>& opt_aTimeoutSeconds,
           ErrorResult& aRv)
 {
-  nsNSSShutDownPreventionLock locker;
-  if (isAlreadyShutDown()) {
-    SendError<U2FSignCallback, SignResponse>(aCallback,
-                                             ErrorCode::OTHER_ERROR);
-    return;
-  }
-
-  const bool softTokenEnabled =
-    Preferences::GetBool(PREF_U2F_SOFTTOKEN_ENABLED);
-
-  const bool usbTokenEnabled =
-    Preferences::GetBool(PREF_U2F_USBTOKEN_ENABLED);
-
-  nsAutoString appId(aAppId);
-
-  // Verify the global appId first.
-  if (!ValidAppID(appId)) {
-    SendError<U2FSignCallback, SignResponse>(aCallback,
-                                             ErrorCode::BAD_REQUEST);
-    return;
-  }
-
-  // Search the requests for one a token can fulfill
-  for (size_t i = 0; i < aRegisteredKeys.Length(); i += 1) {
-    RegisteredKey request(aRegisteredKeys[i]);
-
-    // Check for required attributes
-    if (!(request.mVersion.WasPassed() &&
-          request.mKeyHandle.WasPassed())) {
-      SendError<U2FSignCallback, SignResponse>(aCallback,
-                                               ErrorCode::OTHER_ERROR);
-      continue;
-    }
-
-    // Allow an individual RegisteredKey to assert a different AppID
-    nsAutoString regKeyAppId(appId);
-    if (request.mAppId.WasPassed()) {
-      regKeyAppId.Assign(request.mAppId.Value());
-      if (!ValidAppID(regKeyAppId)) {
-        continue;
-      }
-    }
-
-    // Assemble a clientData object
-    CryptoBuffer clientData;
-    nsresult rv = AssembleClientData(GetAssertion, aChallenge, clientData);
-    if (NS_WARN_IF(NS_FAILED(rv))) {
-      SendError<U2FSignCallback, SignResponse>(aCallback,
-                                               ErrorCode::OTHER_ERROR);
-      return;
-    }
-
-    // Hash the AppID and the ClientData into the AppParam and ChallengeParam
-    SECStatus srv;
-    nsCString cAppId = NS_ConvertUTF16toUTF8(regKeyAppId);
-    CryptoBuffer appParam;
-    CryptoBuffer challengeParam;
-    if (!appParam.SetLength(SHA256_LENGTH, fallible) ||
-        !challengeParam.SetLength(SHA256_LENGTH, fallible)) {
-      SendError<U2FSignCallback, SignResponse>(aCallback,
-                                               ErrorCode::OTHER_ERROR);
-      return;
-    }
-
-    srv = PK11_HashBuf(SEC_OID_SHA256, appParam.Elements(),
-                       reinterpret_cast<const uint8_t*>(cAppId.BeginReading()),
-                       cAppId.Length());
-    if (srv != SECSuccess) {
-      SendError<U2FSignCallback, SignResponse>(aCallback,
-                                               ErrorCode::OTHER_ERROR);
-      return;
-    }
-
-    srv = PK11_HashBuf(SEC_OID_SHA256, challengeParam.Elements(),
-                       clientData.Elements(), clientData.Length());
-    if (srv != SECSuccess) {
-      SendError<U2FSignCallback, SignResponse>(aCallback,
-                                               ErrorCode::OTHER_ERROR);
-      return;
-    }
+  RefPtr<U2FSignTask> signTask = new U2FSignTask(mOrigin, aAppId, aChallenge,
+                                                 aRegisteredKeys, &aCallback,
+                                                 mNSSToken);
 
-    // Decode the key handle
-    CryptoBuffer keyHandle;
-    rv = keyHandle.FromJwkBase64(request.mKeyHandle.Value());
-    if (NS_WARN_IF(NS_FAILED(rv))) {
-      SendError<U2FSignCallback, SignResponse>(aCallback,
-                                               ErrorCode::OTHER_ERROR);
-      return;
-    }
-
-    // Get the signature from the token
-    CryptoBuffer signatureData;
-    bool signSuccess = false;
-
-    // We ignore mTransports, as it is intended to be used for sorting the
-    // available devices by preference, but is not an exclusion factor.
-
-    if (usbTokenEnabled &&
-        mUSBToken.IsCompatibleVersion(request.mVersion.Value())) {
-      // TODO: Implement in Bug 1245527
-      SendError<U2FSignCallback, SignResponse>(aCallback,
-                                               ErrorCode::OTHER_ERROR);
-      return;
-    }
-
-    if (!signSuccess && softTokenEnabled) {
-      bool isCompatible = false;
-      bool isRegistered = false;
-
-      rv = NSSTokenIsCompatible(request.mVersion.Value(), &isCompatible);
-      if (NS_FAILED(rv)) {
-        SendError<U2FSignCallback, SignResponse>(aCallback,
-                                                 ErrorCode::OTHER_ERROR);
-        return;
-      }
-
-      rv = NSSTokenIsRegistered(keyHandle, &isRegistered);
-      if (NS_FAILED(rv)) {
-        SendError<U2FSignCallback, SignResponse>(aCallback,
-                                                 ErrorCode::OTHER_ERROR);
-        return;
-      }
-
-      if (isCompatible && isRegistered) {
-        rv = NSSTokenSign(keyHandle, appParam, challengeParam, signatureData);
-        if (NS_FAILED(rv)) {
-          SendError<U2FSignCallback, SignResponse>(aCallback,
-                                                   ErrorCode::OTHER_ERROR);
-          return;
-        }
-        signSuccess = true;
-      }
-    }
-
-    if (!signSuccess) {
-      // Try another request
-      continue;
-    }
-
-    // Assemble a response object to return
-    nsString clientDataBase64, signatureDataBase64;
-    nsresult rvClientData =
-      clientData.ToJwkBase64(clientDataBase64);
-    nsresult rvSignatureData =
-      signatureData.ToJwkBase64(signatureDataBase64);
-    if (NS_WARN_IF(NS_FAILED(rvClientData)) ||
-        NS_WARN_IF(NS_FAILED(rvSignatureData))) {
-      SendError<U2FSignCallback, SignResponse>(aCallback,
-                                               ErrorCode::OTHER_ERROR);
-      return;
-    }
-    SignResponse response;
-    response.mKeyHandle.Construct(request.mKeyHandle.Value());
-    response.mClientData.Construct(clientDataBase64);
-    response.mSignatureData.Construct(signatureDataBase64);
-    response.mErrorCode.Construct(static_cast<uint32_t>(ErrorCode::OK));
-
-    ErrorResult result;
-    aCallback.Call(response, result);
-    NS_WARN_IF(result.Failed());
-    // Useful exceptions already got reported.
-    result.SuppressException();
-    return;
-  }
-
-  // Nothing could satisfy
-  SendError<U2FSignCallback, SignResponse>(aCallback,
-                                           ErrorCode::DEVICE_INELIGIBLE);
-  return;
+  EvaluateAppIDAndRunTask(signTask);
 }
 
 } // namespace dom
 } // namespace mozilla
--- a/dom/u2f/U2F.h
+++ b/dom/u2f/U2F.h
@@ -29,16 +29,98 @@ class U2FRegisterCallback;
 class U2FSignCallback;
 
 } // namespace dom
 } // namespace mozilla
 
 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
+};
+
+class U2FTask : public nsRunnable
+{
+public:
+  U2FTask(const nsAString& aOrigin,
+          const nsAString& aAppId);
+
+  nsString mOrigin;
+  nsString mAppId;
+
+  virtual
+  void ReturnError(ErrorCode code) = 0;
+
+protected:
+  virtual ~U2FTask();
+};
+
+class U2FRegisterTask final : public nsNSSShutDownObject,
+                              public U2FTask
+{
+public:
+  U2FRegisterTask(const nsAString& aOrigin,
+                  const nsAString& aAppId,
+                  const Sequence<RegisterRequest>& aRegisterRequests,
+                  const Sequence<RegisteredKey>& aRegisteredKeys,
+                  U2FRegisterCallback* aCallback,
+                  const nsCOMPtr<nsINSSU2FToken>& aNSSToken);
+
+  // No NSS resources to release.
+  virtual
+  void virtualDestroyNSSReference() override {};
+
+  void ReturnError(ErrorCode code) override;
+
+  NS_DECL_NSIRUNNABLE
+private:
+  ~U2FRegisterTask();
+
+  Sequence<RegisterRequest> mRegisterRequests;
+  Sequence<RegisteredKey> mRegisteredKeys;
+  RefPtr<U2FRegisterCallback> mCallback;
+  nsCOMPtr<nsINSSU2FToken> mNSSToken;
+};
+
+class U2FSignTask final : public nsNSSShutDownObject,
+                          public U2FTask
+{
+public:
+  U2FSignTask(const nsAString& aOrigin,
+              const nsAString& aAppId,
+              const nsAString& aChallenge,
+              const Sequence<RegisteredKey>& aRegisteredKeys,
+              U2FSignCallback* aCallback,
+              const nsCOMPtr<nsINSSU2FToken>& aNSSToken);
+
+  // No NSS resources to release.
+  virtual
+  void virtualDestroyNSSReference() override {};
+
+  void ReturnError(ErrorCode code) override;
+
+  NS_DECL_NSIRUNNABLE
+private:
+  ~U2FSignTask();
+
+  nsString mChallenge;
+  Sequence<RegisteredKey> mRegisteredKeys;
+  RefPtr<U2FSignCallback> mCallback;
+  nsCOMPtr<nsINSSU2FToken> mNSSToken;
+};
+
 class U2F final : public nsISupports,
                   public nsWrapperCache,
                   public nsNSSShutDownObject
 {
 public:
   NS_DECL_CYCLE_COLLECTING_ISUPPORTS
   NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(U2F)
 
@@ -77,45 +159,15 @@ public:
   void virtualDestroyNSSReference() override {};
 
 private:
   nsCOMPtr<nsPIDOMWindowInner> mParent;
   nsString mOrigin;
   USBToken mUSBToken;
   nsCOMPtr<nsINSSU2FToken> mNSSToken;
 
-  static const nsString FinishEnrollment;
-  static const nsString GetAssertion;
-
   ~U2F();
-
-  nsresult
-  AssembleClientData(const nsAString& aTyp,
-                     const nsAString& aChallenge,
-                     CryptoBuffer& aClientData) const;
-
-  // ValidAppID determines whether the supplied FIDO AppID is valid for
-  // the current FacetID, e.g., the current origin. If the supplied
-  // aAppId param is null or empty, it will be filled in per the algorithm.
-  // See https://fidoalliance.org/specs/fido-u2f-v1.0-nfc-bt-amendment-20150514/fido-appid-and-facets.html
-  // for a description of the algorithm.
-  bool
-  ValidAppID(/* in/out */ nsString& aAppId) const;
-
-  nsresult
-  NSSTokenIsCompatible(const nsString& versionString, bool* isCompatible);
-
-  nsresult
-  NSSTokenIsRegistered(CryptoBuffer& keyHandle, bool* isRegistered);
-
-  nsresult
-  NSSTokenRegister(CryptoBuffer& application, CryptoBuffer& challenge,
-                   CryptoBuffer& registrationData);
-
-  nsresult
-  NSSTokenSign(CryptoBuffer& keyHandle, CryptoBuffer& application,
-               CryptoBuffer& challenge, CryptoBuffer& signatureData);
 };
 
 } // namespace dom
 } // namespace mozilla
 
 #endif // mozilla_dom_U2F_h
deleted file mode 100644
--- a/dom/u2f/tests/facet/facetList-good
+++ /dev/null
@@ -1,8 +0,0 @@
-{
-  "trustedFacets" : [{
-    "version": { "major": 1, "minor" : 0 },
-    "ids": [
-     "https://fido.example.com"
-    ]
-  }]
-}
\ No newline at end of file
deleted file mode 100644
--- a/dom/u2f/tests/facet/facetList-good^headers^
+++ /dev/null
@@ -1,1 +0,0 @@
-Content-Type: application/fido.trusted-apps+json
\ No newline at end of file
deleted file mode 100644
--- a/dom/u2f/tests/facet/facetList-invalid_format
+++ /dev/null
@@ -1,6 +0,0 @@
-# This file isn't actually JSON, so it shouldn't successfully parse.
-{
-  "trustedFacets" : [{
-    "version": { "major": 1, "minor" : 0 },
-  },{}]
-}
\ No newline at end of file
deleted file mode 100644
--- a/dom/u2f/tests/facet/facetList-invalid_format^headers^
+++ /dev/null
@@ -1,1 +0,0 @@
-Content-Type: application/fido.trusted-apps+json
\ No newline at end of file
deleted file mode 100644
--- a/dom/u2f/tests/facet/facetList-no_overlap
+++ /dev/null
@@ -1,9 +0,0 @@
-{
-  "trustedFacets" : [{
-    "version": { "major": 1, "minor" : 0 },
-    "ids": [
-     "https://example.net",
-     "http://www.example.com"
-    ]
-  }]
-}
\ No newline at end of file
deleted file mode 100644
--- a/dom/u2f/tests/facet/facetList-no_overlap^headers^
+++ /dev/null
@@ -1,1 +0,0 @@
-Content-Type: application/fido.trusted-apps+json
\ No newline at end of file
deleted file mode 100644
--- a/dom/u2f/tests/facet/facetList.txt
+++ /dev/null
@@ -1,8 +0,0 @@
-{
-  "trustedFacets" : [{
-    "version": { "major": 1, "minor" : 0 },
-    "ids": [
-     "https://fido.example.com"
-    ]
-  }]
-}
\ No newline at end of file
--- a/dom/u2f/tests/mochitest.ini
+++ b/dom/u2f/tests/mochitest.ini
@@ -1,25 +1,17 @@
 [DEFAULT]
 support-files =
   frame_no_token.html
   u2futil.js
   test_frame_appid_facet.html
   test_frame_register.html
   test_frame_register_sign.html
-  test_frame_appid_facet_remoteload.html
   test_frame_appid_facet_insecure.html
   test_frame_appid_facet_subdomain.html
-  facet/facetList.txt
-  facet/facetList-good
-  facet/facetList-good^headers^
-  facet/facetList-no_overlap
-  facet/facetList-no_overlap^headers^
-  facet/facetList-invalid_format
-  facet/facetList-invalid_format^headers^
   pkijs/common.js
   pkijs/asn1.js
   pkijs/x509_schema.js
   pkijs/x509_simpl.js
 
 [test_util_methods.html]
 [test_no_token.html]
 [test_frame.html]
--- a/dom/u2f/tests/test_frame.html
+++ b/dom/u2f/tests/test_frame.html
@@ -21,17 +21,16 @@
 SpecialPowers.pushPrefEnv({"set": [["security.webauth.u2f", true],
                                    ["security.webauth.u2f_enable_softtoken", true]]},
 function() {
   var testList = [
     "https://example.com/tests/dom/u2f/tests/test_frame_register.html",
     "https://example.com/tests/dom/u2f/tests/test_frame_register_sign.html",
     "http://mochi.test:8888/tests/dom/u2f/tests/test_frame_appid_facet_insecure.html",
     "https://example.com/tests/dom/u2f/tests/test_frame_appid_facet.html",
-    "https://example.com/tests/dom/u2f/tests/test_frame_appid_facet_remoteload.html",
     "https://test1.example.com/tests/dom/u2f/tests/test_frame_appid_facet_subdomain.html"
   ];
 
   function log(msg) {
     document.getElementById("log").textContent += "\n" + msg;
   }
 
   function nextTest() {
deleted file mode 100644
--- a/dom/u2f/tests/test_frame_appid_facet_remoteload.html
+++ /dev/null
@@ -1,57 +0,0 @@
-<!DOCTYPE html>
-<meta charset=utf-8>
-<head>
-  <script src="u2futil.js"></script>
-</head>
-<body>
-<p>Test for Remote AppId Load behavior for FIDO Universal Second Factor</p>
-<script class="testbody" type="text/javascript">
-"use strict";
-
-var version = "U2F_V2";
-var challenge = new Uint8Array(16);
-
-local_is(window.location.origin, "https://example.com", "Is loaded correctly");
-
-// TODO: Must support remote loads of AppID manifests first.
-//
-// u2f.register("https://test1.example.com/dom/u2f/tests/facet/facetList.txt", [{
-//   version: version,
-//   challenge: bytesToBase64UrlSafe(challenge),
-// }], [], function(res){
-//   local_is(res.errorCode, 2, "Should not permit this AppId contentType");
-// });
-
-// u2f.register("https://test1.example.com/dom/u2f/tests/facet/facetListMissing", [{
-//   version: version,
-//   challenge: bytesToBase64UrlSafe(challenge),
-// }], [], function(res){
-//   local_is(res.errorCode, 2, "Should not permit with a missing AppID list");
-// });
-
-// u2f.register("https://test1.example.com/dom/u2f/tests/facet/facetList-good", [{
-//   version: version,
-//   challenge: bytesToBase64UrlSafe(challenge),
-// }], [], function(res){
-//   local_is(res.errorCode, 0, "The AppId should permit example.com");
-// });
-
-// u2f.register("https://test1.example.com/dom/u2f/tests/facet/facetList-no_overlap", [{
-//   version: version,
-//   challenge: bytesToBase64UrlSafe(challenge),
-// }], [], function(res){
-//   local_is(res.errorCode, 2, "Should not permit with a missing AppID list");
-// });
-
-// u2f.register("https://test1.example.com/dom/u2f/tests/facet/facetList-invalid_format", [{
-//   version: version,
-//   challenge: bytesToBase64UrlSafe(challenge),
-// }], [], function(res){
-//   local_is(res.errorCode, 2, "Should not fail gracefully on invalid formatted facet lists");
-// });
-
-local_finished();
-
-</script>
-</body>
-</html>
--- a/dom/u2f/tests/test_frame_register_sign.html
+++ b/dom/u2f/tests/test_frame_register_sign.html
@@ -77,19 +77,19 @@ function() {
       local_is(state.attestationCert.subject.types_and_values[0].value.value_block.value, "Firefox U2F Soft Token", "Expected Subject");
       local_is(state.attestationCert.issuer.types_and_values[0].value.value_block.value, "Firefox U2F Soft Token", "Expected Issuer");
       local_is(state.attestationCert.notAfter.value - state.attestationCert.notBefore.value, 1000*60*60*48, "Valid 48 hours (in millis)");
 
       // Verify that the clientData from the U2F token makes sense
       var clientDataJSON = "";
       base64ToBytesUrlSafe(regResponse.clientData).map(x => clientDataJSON += String.fromCharCode(x));
       var clientData = JSON.parse(clientDataJSON);
-      local_is(clientData.typ, "navigator.id.finishEnrollment", "Data type matches");
-      local_is(clientData.challenge, state.regRequest.challenge, "Register challenge matches");
-      local_is(clientData.origin, window.location.origin, "Origins are the same");
+      local_is(clientData.typ, "navigator.id.finishEnrollment", "Register - Data type matches");
+      local_is(clientData.challenge, state.regRequest.challenge, "Register - Challenge matches");
+      local_is(clientData.origin, window.location.origin, "Register - Origins are the same");
 
       // Verify the signature from the attestation certificate
       deriveAppAndChallengeParam(state.appId, string2buffer(clientDataJSON))
       .then(function(params){
         state.appParam = params.appParam;
         state.challengeParam = params.challengeParam;
         return state.attestationCert.getPublicKey();
       }).then(function(attestationPublicKey) {
@@ -156,19 +156,19 @@ function() {
         local_finished();
         return;
       }
 
       // Decode the clientData that was returned from the module
       var clientDataJSON = "";
       base64ToBytesUrlSafe(signResponse.clientData).map(x => clientDataJSON += String.fromCharCode(x));
       var clientData = JSON.parse(clientDataJSON);
-      local_is(clientData.typ, "navigator.id.getAssertion", "Data type matches");
-      local_is(clientData.challenge, state.signChallenge, "Sign challenge matches");
-      local_is(clientData.origin, window.location.origin, "Origins are the same");
+      local_is(clientData.typ, "navigator.id.getAssertion", "Sign - Data type matches");
+      local_is(clientData.challenge, state.signChallenge, "Sign - Challenge matches");
+      local_is(clientData.origin, window.location.origin, "Sign - Origins are the same");
 
       // Parse the signature data
       var signatureData = base64ToBytesUrlSafe(signResponse.signatureData);
       if (signatureData[0] != 0x01) {
         throw "User presence byte not set";
       }
       var presenceAndCounter = signatureData.slice(0,5);
       var signatureValue = signatureData.slice(5);
--- a/dom/webidl/CaretStateChangedEvent.webidl
+++ b/dom/webidl/CaretStateChangedEvent.webidl
@@ -5,17 +5,18 @@
  */
 
 enum CaretChangedReason {
   "visibilitychange",
   "updateposition",
   "longpressonemptycontent",
   "taponcaret",
   "presscaret",
-  "releasecaret"
+  "releasecaret",
+  "scroll"
 };
 
 dictionary CaretStateChangedEventInit : EventInit {
   boolean collapsed = true;
   DOMRectReadOnly? boundingClientRect = null;
   CaretChangedReason reason = "visibilitychange";
   boolean caretVisible = false;
   boolean caretVisuallyVisible = false;
--- a/dom/workers/ServiceWorkerEvents.cpp
+++ b/dom/workers/ServiceWorkerEvents.cpp
@@ -262,43 +262,43 @@ public:
     return decision == nsIContentPolicy::ACCEPT;
   }
 };
 
 class RespondWithHandler final : public PromiseNativeHandler
 {
   nsMainThreadPtrHandle<nsIInterceptedChannel> mInterceptedChannel;
   const RequestMode mRequestMode;
+  const RequestRedirect mRequestRedirectMode;
 #ifdef DEBUG
   const bool mIsClientRequest;
 #endif
-  const bool mIsNavigationRequest;
   const nsCString mScriptSpec;
   const nsString mRequestURL;
   const nsCString mRespondWithScriptSpec;
   const uint32_t mRespondWithLineNumber;
   const uint32_t mRespondWithColumnNumber;
   bool mRequestWasHandled;
 public:
   NS_DECL_ISUPPORTS
 
   RespondWithHandler(nsMainThreadPtrHandle<nsIInterceptedChannel>& aChannel,
                      RequestMode aRequestMode, bool aIsClientRequest,
-                     bool aIsNavigationRequest,
+                     RequestRedirect aRedirectMode,
                      const nsACString& aScriptSpec,
                      const nsAString& aRequestURL,
                      const nsACString& aRespondWithScriptSpec,
                      uint32_t aRespondWithLineNumber,
                      uint32_t aRespondWithColumnNumber)
     : mInterceptedChannel(aChannel)
     , mRequestMode(aRequestMode)
+    , mRequestRedirectMode(aRedirectMode)
 #ifdef DEBUG
     , mIsClientRequest(aIsClientRequest)
 #endif
-    , mIsNavigationRequest(aIsNavigationRequest)
     , mScriptSpec(aScriptSpec)
     , mRequestURL(aRequestURL)
     , mRespondWithScriptSpec(aRespondWithScriptSpec)
     , mRespondWithLineNumber(aRespondWithLineNumber)
     , mRespondWithColumnNumber(aRespondWithColumnNumber)
     , mRequestWasHandled(false)
   {
   }
@@ -564,21 +564,21 @@ RespondWithHandler::ResolvedCallback(JSC
                                            mRequestURL, valueString);
     return;
   }
 
   WorkerPrivate* worker = GetCurrentThreadWorkerPrivate();
   MOZ_ASSERT(worker);
   worker->AssertIsOnWorkerThread();
 
-  // Section "HTTP Fetch", step 2.2:
+  // Section "HTTP Fetch", step 3.3:
   //  If one of the following conditions is true, return a network error:
   //    * response's type is "error".
   //    * request's mode is not "no-cors" and response's type is "opaque".
-  //    * request is not a navigation request and response's type is
+  //    * request's redirect mode is not "manual" and response's type is
   //      "opaqueredirect".
 
   if (response->Type() == ResponseType::Error) {
     autoCancel.SetCancelMessage(
       NS_LITERAL_CSTRING("InterceptedErrorResponseWithURL"), mRequestURL);
     return;
   }
 
@@ -591,17 +591,18 @@ RespondWithHandler::ResolvedCallback(JSC
                                       RequestModeValues::strings[mode].length);
 
     autoCancel.SetCancelMessage(
       NS_LITERAL_CSTRING("BadOpaqueInterceptionRequestModeWithURL"),
       mRequestURL, modeString);
     return;
   }
 
-  if (!mIsNavigationRequest && response->Type() == ResponseType::Opaqueredirect) {
+  if (mRequestRedirectMode != RequestRedirect::Manual &&
+      response->Type() == ResponseType::Opaqueredirect) {
     autoCancel.SetCancelMessage(
       NS_LITERAL_CSTRING("BadOpaqueRedirectInterceptionWithURL"), mRequestURL);
     return;
   }
 
   if (NS_WARN_IF(response->BodyUsed())) {
     autoCancel.SetCancelMessage(
       NS_LITERAL_CSTRING("InterceptedUsedResponseWithURL"), mRequestURL);
@@ -733,17 +734,17 @@ FetchEvent::RespondWith(JSContext* aCx, 
 
   nsAutoCString requestURL;
   ir->GetURL(requestURL);
 
   StopImmediatePropagation();
   mWaitToRespond = true;
   RefPtr<RespondWithHandler> handler =
     new RespondWithHandler(mChannel, mRequest->Mode(), ir->IsClientRequest(),
-                           ir->IsNavigationRequest(), mScriptSpec,
+                           mRequest->Redirect(), mScriptSpec,
                            NS_ConvertUTF8toUTF16(requestURL),
                            spec, line, column);
   aArg.AppendNativeHandler(handler);
 
   // Append directly to the lifecycle promises array.  Don't call WaitUntil()
   // because that will lead to double-reporting any errors.
   mPromises.AppendElement(&aArg);
 }
--- a/dom/workers/ServiceWorkerManager.cpp
+++ b/dom/workers/ServiceWorkerManager.cpp
@@ -1941,44 +1941,38 @@ ServiceWorkerManager::MaybeRemoveRegistr
   }
 }
 
 void
 ServiceWorkerManager::MaybeStartControlling(nsIDocument* aDoc,
                                             const nsAString& aDocumentId)
 {
   AssertIsOnMainThread();
-
-  // We keep a set of documents that service workers may choose to start
-  // controlling using claim().
-  MOZ_ASSERT(!mAllDocuments.Contains(aDoc));
-  mAllDocuments.PutEntry(aDoc);
-
+  MOZ_ASSERT(aDoc);
   RefPtr<ServiceWorkerRegistrationInfo> registration =
     GetServiceWorkerRegistrationInfo(aDoc);
   if (registration) {
     MOZ_ASSERT(!mControlledDocuments.Contains(aDoc));
     StartControllingADocument(registration, aDoc, aDocumentId);
   }
 }
 
 void
 ServiceWorkerManager::MaybeStopControlling(nsIDocument* aDoc)
 {
+  AssertIsOnMainThread();
   MOZ_ASSERT(aDoc);
   RefPtr<ServiceWorkerRegistrationInfo> registration;
   mControlledDocuments.Remove(aDoc, getter_AddRefs(registration));
   // A document which was uncontrolled does not maintain that state itself, so
   // it will always call MaybeStopControlling() even if there isn't an
   // associated registration. So this check is required.
   if (registration) {
     StopControllingADocument(registration);
   }
-
-  mAllDocuments.RemoveEntry(aDoc);
 }
 
 void
 ServiceWorkerManager::MaybeCheckNavigationUpdate(nsIDocument* aDoc)
 {
   AssertIsOnMainThread();
   MOZ_ASSERT(aDoc);
   // We perform these success path navigation update steps when the
@@ -2782,16 +2776,20 @@ ServiceWorkerManager::GetAllClients(nsIP
     for (auto iter = mControlledDocuments.Iter(); !iter.Done(); iter.Next()) {
       ServiceWorkerRegistrationInfo* thisRegistration = iter.UserData();
       MOZ_ASSERT(thisRegistration);
       if (!registration->mScope.Equals(thisRegistration->mScope)) {
         continue;
       }
 
       nsCOMPtr<nsIDocument> doc = do_QueryInterface(iter.Key());
+
+      // All controlled documents must have an outer window.
+      MOZ_ASSERT(doc->GetWindow());
+
       ProcessDocument(aPrincipal, doc);
     }
   }
 }
 
 void
 ServiceWorkerManager::MaybeClaimClient(nsIDocument* aDocument,
                                        ServiceWorkerRegistrationInfo* aWorkerRegistration)
@@ -2833,20 +2831,38 @@ ServiceWorkerManager::ClaimClients(nsIPr
     GetRegistration(aPrincipal, aScope);
 
   if (!registration || !registration->GetActive() ||
       !(registration->GetActive()->ID() == aId)) {
     // The worker is not active.
     return NS_ERROR_DOM_INVALID_STATE_ERR;
   }
 
-  RefPtr<ServiceWorkerManager> swm = ServiceWorkerManager::GetInstance();
-  for (auto iter = mAllDocuments.Iter(); !iter.Done(); iter.Next()) {
-    nsCOMPtr<nsIDocument> document = do_QueryInterface(iter.Get()->GetKey());
-    swm->MaybeClaimClient(document, registration);
+  nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
+  if (NS_WARN_IF(!obs)) {
+    return NS_ERROR_FAILURE;
+  }
+
+  nsCOMPtr<nsISimpleEnumerator> enumerator;
+  nsresult rv = obs->EnumerateObservers("service-worker-get-client",
+                                        getter_AddRefs(enumerator));
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
+  bool loop = true;
+  while (NS_SUCCEEDED(enumerator->HasMoreElements(&loop)) && loop) {
+    nsCOMPtr<nsISupports> ptr;
+    rv = enumerator->GetNext(getter_AddRefs(ptr));
+    if (NS_WARN_IF(NS_FAILED(rv))) {
+      continue;
+    }
+
+    nsCOMPtr<nsIDocument> doc = do_QueryInterface(ptr);
+    MaybeClaimClient(doc, registration);
   }
 
   return NS_OK;
 }
 
 nsresult
 ServiceWorkerManager::SetSkipWaitingFlag(nsIPrincipal* aPrincipal,
                                          const nsCString& aScope,
--- a/dom/workers/ServiceWorkerManager.h
+++ b/dom/workers/ServiceWorkerManager.h
@@ -101,19 +101,16 @@ public:
 
   struct RegistrationDataPerPrincipal;
   nsClassHashtable<nsCStringHashKey, RegistrationDataPerPrincipal> mRegistrationInfos;
 
   nsTObserverArray<ServiceWorkerRegistrationListener*> mServiceWorkerRegistrationListeners;
 
   nsRefPtrHashtable<nsISupportsHashKey, ServiceWorkerRegistrationInfo> mControlledDocuments;
 
-  // Set of all documents that may be controlled by a service worker.
-  nsTHashtable<nsISupportsHashKey> mAllDocuments;
-
   // Track all documents that have attempted to register a service worker for a
   // given scope.
   typedef nsTArray<nsCOMPtr<nsIWeakReference>> WeakDocumentList;
   nsClassHashtable<nsCStringHashKey, WeakDocumentList> mRegisteringDocuments;
 
   // Track all intercepted navigation channels for a given scope.  Channels are
   // placed in the appropriate list before dispatch the FetchEvent to the worker
   // thread and removed once FetchEvent processing dispatches back to the main
--- a/dom/workers/test/serviceworkers/browser_cached_force_refresh.html
+++ b/dom/workers/test/serviceworkers/browser_cached_force_refresh.html
@@ -3,15 +3,62 @@
   http://creativecommons.org/publicdomain/zero/1.0/
 -->
 <!DOCTYPE HTML>
 <html>
 <head>
 </head>
 <body>
 <script type="text/javascript">
+function ok(exp, msg) {
+  if (!exp) {
+    throw(msg);
+  }
+}
+
+function is(actual, expected, msg) {
+  if (actual !== expected) {
+    throw('got "' + actual + '", but expected "' + expected + '" - ' + msg);
+  }
+}
+
+function fail(err) {
+  var custom = new CustomEvent('cached-failure', {
+    bubbles: true,
+    detail: err
+  });
+  document.dispatchEvent(custom);
+}
+
+function getUncontrolledClients(sw) {
+  return new Promise(function(resolve, reject) {
+    navigator.serviceWorker.addEventListener('message', function onMsg(evt) {
+      if (evt.data.type === 'CLIENTS') {
+        navigator.serviceWorker.removeEventListener('message', onMsg);
+        resolve(evt.data.detail);
+      }
+    });
+    sw.postMessage({ type: 'GET_UNCONTROLLED_CLIENTS' })
+  });
+}
+
 addEventListener('load', function(event) {
-  var custom = new Event('cached-load', { bubbles: true });
-  document.dispatchEvent(custom);
+  if (!navigator.serviceWorker.controller) {
+    return fail(window.location.href + ' is not controlled!');
+  }
+
+  getUncontrolledClients(navigator.serviceWorker.controller)
+    .then(function(clientList) {
+      is(clientList.length, 1, 'should only have one client');
+      is(clientList[0].url, window.location.href,
+         'client url should match current window');
+      is(clientList[0].frameType, 'top-level',
+         'client should be a top-level window');
+      var custom = new Event('cached-load', { bubbles: true });
+      document.dispatchEvent(custom);
+    })
+    .catch(function(err) {
+      fail(err);
+    });
 });
 </script>
 </body>
 </html>
--- a/dom/workers/test/serviceworkers/browser_force_refresh.js
+++ b/dom/workers/test/serviceworkers/browser_force_refresh.js
@@ -9,24 +9,25 @@ function refresh() {
 }
 
 function forceRefresh() {
   EventUtils.synthesizeKey('R', { accelKey: true, shiftKey: true });
 }
 
 function frameScript() {
   function eventHandler(event) {
-    sendAsyncMessage("test:event", {type: event.type});
+    sendAsyncMessage("test:event", {type: event.type, detail: event.detail});
   }
 
   // These are tab-local, so no need to unregister them.
   addEventListener('base-load', eventHandler, true, true);
   addEventListener('base-register', eventHandler, true, true);
   addEventListener('base-sw-ready', eventHandler, true, true);
   addEventListener('cached-load', eventHandler, true, true);
+  addEventListener('cached-failure', eventHandler, true, true);
 }
 
 function test() {
   waitForExplicitFinish();
   SpecialPowers.pushPrefEnv({'set': [['dom.serviceWorkers.enabled', true],
                                      ['dom.serviceWorkers.exemptFromPerDomainMax', true],
                                      ['dom.serviceWorkers.testing.enabled', true],
                                      ['dom.caches.enabled', true],
@@ -42,42 +43,49 @@ function test() {
 
     function done() {
       tab.linkedBrowser.messageManager.removeMessageListener("test:event", eventHandler);
 
       gBrowser.removeTab(tab);
       executeSoon(finish);
     }
 
-    var cachedLoad = false;
+    var maxCacheLoadCount = 3;
+    var cachedLoadCount = 0;
     var baseLoadCount = 0;
 
     function eventHandler(msg) {
       if (msg.data.type === 'base-load') {
         baseLoadCount += 1;
-        if (cachedLoad) {
+        if (cachedLoadCount === maxCacheLoadCount) {
           is(baseLoadCount, 2, 'cached load should occur before second base load');
           return done();
         }
         if (baseLoadCount !== 1) {
           ok(false, 'base load without cached load should only occur once');
           return done();
         }
       } else if (msg.data.type === 'base-register') {
-        ok(!cachedLoad, 'cached load should not occur before base register');
+        ok(!cachedLoadCount, 'cached load should not occur before base register');
         is(baseLoadCount, 1, 'register should occur after first base load');
       } else if (msg.data.type === 'base-sw-ready') {
-        ok(!cachedLoad, 'cached load should not occur before base ready');
+        ok(!cachedLoadCount, 'cached load should not occur before base ready');
         is(baseLoadCount, 1, 'ready should occur after first base load');
         refresh();
       } else if (msg.data.type === 'cached-load') {
-        ok(!cachedLoad, 'cached load should not occur twice');
+        ok(cachedLoadCount < maxCacheLoadCount, 'cached load should not occur too many times');
         is(baseLoadCount, 1, 'cache load occur after first base load');
-        cachedLoad = true;
+        cachedLoadCount += 1;
+        if (cachedLoadCount < maxCacheLoadCount) {
+          return refresh();
+        }
         forceRefresh();
+      } else if (msg.data.type === 'cached-failure') {
+        ok(false, 'failure: ' + msg.data.detail);
+        done();
       }
 
       return;
     }
 
     tab.linkedBrowser.messageManager.addMessageListener("test:event", eventHandler);
   });
 }
--- a/dom/workers/test/serviceworkers/force_refresh_browser_worker.js
+++ b/dom/workers/test/serviceworkers/force_refresh_browser_worker.js
@@ -15,8 +15,20 @@ self.addEventListener('fetch', function 
   event.respondWith(
     caches.open(name).then(function(cache) {
       return cache.match(event.request);
     }).then(function(response) {
       return response || fetch(event.request);
     })
   );
 });
+
+self.addEventListener('message', function (event) {
+  if (event.data.type === 'GET_UNCONTROLLED_CLIENTS') {
+    event.waitUntil(clients.matchAll({ includeUncontrolled: true })
+      .then(function(clientList) {
+        var resultList = clientList.map(function(c) {
+          return { url: c.url, frameType: c.frameType };
+        });
+        event.source.postMessage({ type: 'CLIENTS', detail: resultList });
+      }));
+  }
+});
--- a/dom/xbl/nsXBLWindowKeyHandler.cpp
+++ b/dom/xbl/nsXBLWindowKeyHandler.cpp
@@ -309,74 +309,128 @@ nsXBLWindowKeyHandler::InstallKeyboardEv
                            this, NS_LITERAL_STRING("keydown"),
                            TrustedEventsAtCapture());
   aEventListenerManager->AddEventListenerByType(
                            this, NS_LITERAL_STRING("keyup"),
                            TrustedEventsAtCapture());
   aEventListenerManager->AddEventListenerByType(
                            this, NS_LITERAL_STRING("keypress"),
                            TrustedEventsAtCapture());
+  aEventListenerManager->AddEventListenerByType(
+                           this, NS_LITERAL_STRING("mozkeydownonplugin"),
+                           TrustedEventsAtCapture());
+  aEventListenerManager->AddEventListenerByType(
+                           this, NS_LITERAL_STRING("mozkeyuponplugin"),
+                           TrustedEventsAtCapture());
 
   // For reducing the IPC cost, preventing to dispatch reserved keyboard
   // events into the content process.
   aEventListenerManager->AddEventListenerByType(
                            this, NS_LITERAL_STRING("keydown"),
                            TrustedEventsAtSystemGroupCapture());
   aEventListenerManager->AddEventListenerByType(
                            this, NS_LITERAL_STRING("keyup"),
                            TrustedEventsAtSystemGroupCapture());
   aEventListenerManager->AddEventListenerByType(
                            this, NS_LITERAL_STRING("keypress"),
                            TrustedEventsAtSystemGroupCapture());
+  aEventListenerManager->AddEventListenerByType(
+                           this, NS_LITERAL_STRING("mozkeydownonplugin"),
+                           TrustedEventsAtSystemGroupCapture());
+  aEventListenerManager->AddEventListenerByType(
+                           this, NS_LITERAL_STRING("mozkeyuponplugin"),
+                           TrustedEventsAtSystemGroupCapture());
 
   // Handle keyboard events in bubbling phase of the system event group.
   aEventListenerManager->AddEventListenerByType(
                            this, NS_LITERAL_STRING("keydown"),
                            TrustedEventsAtSystemGroupBubble());
   aEventListenerManager->AddEventListenerByType(
                            this, NS_LITERAL_STRING("keyup"),
                            TrustedEventsAtSystemGroupBubble());
   aEventListenerManager->AddEventListenerByType(
                            this, NS_LITERAL_STRING("keypress"),
                            TrustedEventsAtSystemGroupBubble());
+  aEventListenerManager->AddEventListenerByType(
+                           this, NS_LITERAL_STRING("mozkeydownonplugin"),
+                           TrustedEventsAtSystemGroupBubble());
+  aEventListenerManager->AddEventListenerByType(
+                           this, NS_LITERAL_STRING("mozkeyuponplugin"),
+                           TrustedEventsAtSystemGroupBubble());
 }
 
 void
 nsXBLWindowKeyHandler::RemoveKeyboardEventListenersFrom(
                          EventListenerManager* aEventListenerManager)
 {
   aEventListenerManager->RemoveEventListenerByType(
                            this, NS_LITERAL_STRING("keydown"),
                            TrustedEventsAtCapture());
   aEventListenerManager->RemoveEventListenerByType(
                            this, NS_LITERAL_STRING("keyup"),
                            TrustedEventsAtCapture());
   aEventListenerManager->RemoveEventListenerByType(
                            this, NS_LITERAL_STRING("keypress"),
                            TrustedEventsAtCapture());
+  aEventListenerManager->RemoveEventListenerByType(
+                           this, NS_LITERAL_STRING("mozkeydownonplugin"),
+                           TrustedEventsAtCapture());
+  aEventListenerManager->RemoveEventListenerByType(
+                           this, NS_LITERAL_STRING("mozkeyuponplugin"),
+                           TrustedEventsAtCapture());
 
   aEventListenerManager->RemoveEventListenerByType(
                            this, NS_LITERAL_STRING("keydown"),
                            TrustedEventsAtSystemGroupCapture());
   aEventListenerManager->RemoveEventListenerByType(
                            this, NS_LITERAL_STRING("keyup"),
                            TrustedEventsAtSystemGroupCapture());
   aEventListenerManager->RemoveEventListenerByType(
                            this, NS_LITERAL_STRING("keypress"),
                            TrustedEventsAtSystemGroupCapture());
+  aEventListenerManager->RemoveEventListenerByType(
+                           this, NS_LITERAL_STRING("mozkeydownonplugin"),
+                           TrustedEventsAtSystemGroupCapture());
+  aEventListenerManager->RemoveEventListenerByType(
+                           this, NS_LITERAL_STRING("mozkeyuponplugin"),
+                           TrustedEventsAtSystemGroupCapture());
 
   aEventListenerManager->RemoveEventListenerByType(
                            this, NS_LITERAL_STRING("keydown"),
                            TrustedEventsAtSystemGroupBubble());
   aEventListenerManager->RemoveEventListenerByType(
                            this, NS_LITERAL_STRING("keyup"),
                            TrustedEventsAtSystemGroupBubble());
   aEventListenerManager->RemoveEventListenerByType(
                            this, NS_LITERAL_STRING("keypress"),
                            TrustedEventsAtSystemGroupBubble());
+  aEventListenerManager->RemoveEventListenerByType(
+                           this, NS_LITERAL_STRING("mozkeydownonplugin"),
+                           TrustedEventsAtSystemGroupBubble());
+  aEventListenerManager->RemoveEventListenerByType(
+                           this, NS_LITERAL_STRING("mozkeyuponplugin"),
+                           TrustedEventsAtSystemGroupBubble());
+}
+
+nsIAtom*
+nsXBLWindowKeyHandler::ConvertEventToDOMEventType(
+                         const WidgetKeyboardEvent& aWidgetKeyboardEvent) const
+{
+  if (aWidgetKeyboardEvent.IsKeyDownOrKeyDownOnPlugin()) {
+    return nsGkAtoms::keydown;
+  }
+  if (aWidgetKeyboardEvent.IsKeyUpOrKeyUpOnPlugin()) {
+    return nsGkAtoms::keyup;
+  }
+  if (aWidgetKeyboardEvent.mMessage == eKeyPress) {
+    return nsGkAtoms::keypress;
+  }
+  MOZ_ASSERT_UNREACHABLE(
+    "All event messages which this instance listens to should be handled");
+  return nullptr;
 }
 
 NS_IMETHODIMP
 nsXBLWindowKeyHandler::HandleEvent(nsIDOMEvent* aEvent)
 {
   nsCOMPtr<nsIDOMKeyEvent> keyEvent(do_QueryInterface(aEvent));
   NS_ENSURE_TRUE(keyEvent, NS_ERROR_INVALID_ARG);
 
@@ -386,39 +440,64 @@ nsXBLWindowKeyHandler::HandleEvent(nsIDO
     if (aEvent->WidgetEventPtr()->mFlags.mInSystemGroup) {
       HandleEventOnCaptureInSystemEventGroup(keyEvent);
     } else {
       HandleEventOnCaptureInDefaultEventGroup(keyEvent);
     }
     return NS_OK;
   }
 
-  nsAutoString eventType;
-  aEvent->GetType(eventType);
-  nsCOMPtr<nsIAtom> eventTypeAtom = NS_Atomize(eventType);
-  NS_ENSURE_TRUE(eventTypeAtom, NS_ERROR_OUT_OF_MEMORY);
+  WidgetKeyboardEvent* widgetKeyboardEvent =
+    aEvent->WidgetEventPtr()->AsKeyboardEvent();
+  if (widgetKeyboardEvent->IsKeyEventOnPlugin()) {
+    // key events on plugin shouldn't execute shortcut key handlers which are
+    // not reserved.
+    if (!widgetKeyboardEvent->mIsReserved) {
+      return NS_OK;
+    }
 
+    // If the event is untrusted event or was already consumed, do nothing.
+    if (!widgetKeyboardEvent->IsTrusted() ||
+        widgetKeyboardEvent->DefaultPrevented()) {
+      return NS_OK;
+    }
+
+    // XXX Don't check isReserved here because even if the handler in this
+    //     instance isn't reserved but another instance reserves the key
+    //     combination, it will be executed when the event is normal keyboard
+    //     events...
+    bool isReserved = false;
+    if (!HasHandlerForEvent(keyEvent, &isReserved)) {
+      return NS_OK;
+    }
+  }
+
+  nsCOMPtr<nsIAtom> eventTypeAtom =
+    ConvertEventToDOMEventType(*widgetKeyboardEvent);
   return WalkHandlers(keyEvent, eventTypeAtom);
 }
 
 void
 nsXBLWindowKeyHandler::HandleEventOnCaptureInDefaultEventGroup(
                          nsIDOMKeyEvent* aEvent)
 {
   WidgetKeyboardEvent* widgetKeyboardEvent =
     aEvent->AsEvent()->WidgetEventPtr()->AsKeyboardEvent();
 
-  if (widgetKeyboardEvent->mFlags.mOnlySystemGroupDispatchInContent) {
+  if (widgetKeyboardEvent->mIsReserved) {
+    MOZ_RELEASE_ASSERT(
+      widgetKeyboardEvent->mFlags.mOnlySystemGroupDispatchInContent);
     MOZ_RELEASE_ASSERT(
       widgetKeyboardEvent->mFlags.mNoCrossProcessBoundaryForwarding);
     return;
   }
 
   bool isReserved = false;
   if (HasHandlerForEvent(aEvent, &isReserved) && isReserved) {
+    widgetKeyboardEvent->mIsReserved = true;
     // For reserved commands (such as Open New Tab), we don't to wait for
     // the content to answer (so mWantReplyFromContentProcess remains false),
     // neither to give a chance for content to override its behavior.
     widgetKeyboardEvent->StopCrossProcessForwarding();
     // If the key combination is reserved by chrome, we shouldn't expose the
     // keyboard event to web contents because such keyboard events shouldn't be
     // cancelable.  So, it's not good behavior to fire keyboard events but
     // to ignore the defaultPrevented attribute value in chrome.
@@ -557,30 +636,48 @@ nsXBLWindowKeyHandler::WalkHandlersAndEx
                          nsIDOMKeyEvent* aKeyEvent,
                          nsIAtom* aEventType,
                          nsXBLPrototypeHandler* aFirstHandler,
                          uint32_t aCharCode,
                          const IgnoreModifierState& aIgnoreModifierState,
                          bool aExecute,
                          bool* aOutReservedForChrome)
 {
+  if (aOutReservedForChrome) {
+    *aOutReservedForChrome = false;
+  }
+
+  WidgetKeyboardEvent* widgetKeyboardEvent =
+    aKeyEvent->AsEvent()->WidgetEventPtr()->AsKeyboardEvent();
+  if (NS_WARN_IF(!widgetKeyboardEvent)) {
+    return false;
+  }
+
   // Try all of the handlers until we find one that matches the event.
   for (nsXBLPrototypeHandler* handler = aFirstHandler;
        handler;
        handler = handler->GetNextHandler()) {
     bool stopped = aKeyEvent->AsEvent()->IsDispatchStopped();
     if (stopped) {
       // The event is finished, don't execute any more handlers
       return false;
     }
 
     if (aExecute) {
-      // If it's executing matched handlers, the event type should exactly be
-      // matched.
-      if (!handler->EventTypeEquals(aEventType)) {
+      // If the event is eKeyDownOnPlugin, it should execute either keydown
+      // handler or keypress handler because eKeyDownOnPlugin events are
+      // never followed by keypress events.
+      if (widgetKeyboardEvent->mMessage == eKeyDownOnPlugin) {
+        if (!handler->EventTypeEquals(nsGkAtoms::keydown) &&
+            !handler->EventTypeEquals(nsGkAtoms::keypress)) {
+          continue;
+        }
+      // The other event types should exactly be matched with the handler's
+      // event type.
+      } else if (!handler->EventTypeEquals(aEventType)) {
         continue;
       }
     } else {
       if (handler->EventTypeEquals(nsGkAtoms::keypress)) {
         // If the handler is a keypress event handler, we also need to check
         // if coming keydown event is a preceding event of reserved key
         // combination because if default action of a keydown event is
         // prevented, following keypress event won't be fired.  However, if
@@ -634,16 +731,22 @@ nsXBLWindowKeyHandler::WalkHandlersAndEx
           aEventType == nsGkAtoms::keydown &&
           handler->EventTypeEquals(nsGkAtoms::keypress)) {
         return true;
       }
       // Otherwise, we've not found a handler for the event yet.
       continue;
     }
 
+    // If it's not reserved and the event is a key event on a plugin,
+    // the handler shouldn't be executed.
+    if (!isReserved && widgetKeyboardEvent->IsKeyEventOnPlugin()) {
+      return false;
+    }
+
     nsCOMPtr<EventTarget> target;
     nsCOMPtr<Element> chromeHandlerElement = GetElement();
     if (chromeHandlerElement) {
       // XXX commandElement may be nullptr...
       target = commandElement;
     } else {
       target = mTarget;
     }
@@ -656,53 +759,48 @@ nsXBLWindowKeyHandler::WalkHandlersAndEx
     }
   }
 
 #ifdef XP_WIN
   // Windows native applications ignore Windows-Logo key state when checking
   // shortcut keys even if the key is pressed.  Therefore, if there is no
   // shortcut key which exactly matches current modifier state, we should
   // retry to look for a shortcut key without the Windows-Logo key press.
-  if (!aIgnoreModifierState.mOS) {
-    WidgetKeyboardEvent* keyEvent =
-      aKeyEvent->AsEvent()->WidgetEventPtr()->AsKeyboardEvent();
-    if (keyEvent && keyEvent->IsOS()) {
-      IgnoreModifierState ignoreModifierState(aIgnoreModifierState);
-      ignoreModifierState.mOS = true;
-      return WalkHandlersAndExecute(aKeyEvent, aEventType, aFirstHandler,
-                                    aCharCode, ignoreModifierState, aExecute);
-    }
+  if (!aIgnoreModifierState.mOS && widgetKeyboardEvent->IsOS()) {
+    IgnoreModifierState ignoreModifierState(aIgnoreModifierState);
+    ignoreModifierState.mOS = true;
+    return WalkHandlersAndExecute(aKeyEvent, aEventType, aFirstHandler,
+                                  aCharCode, ignoreModifierState, aExecute);
   }
 #endif
 
   return false;
 }
 
 bool
 nsXBLWindowKeyHandler::HasHandlerForEvent(nsIDOMKeyEvent* aEvent,
                                           bool* aOutReservedForChrome)
 {
-  if (!aEvent->AsEvent()->InternalDOMEvent()->IsTrusted()) {
+  WidgetKeyboardEvent* widgetKeyboardEvent =
+    aEvent->AsEvent()->WidgetEventPtr()->AsKeyboardEvent();
+  if (NS_WARN_IF(!widgetKeyboardEvent) || !widgetKeyboardEvent->IsTrusted()) {
     return false;
   }
 
   nsresult rv = EnsureHandlers();
   NS_ENSURE_SUCCESS(rv, false);
 
   bool isDisabled;
   nsCOMPtr<Element> el = GetElement(&isDisabled);
   if (el && isDisabled) {
     return false;
   }
 
-  nsAutoString eventType;
-  aEvent->AsEvent()->GetType(eventType);
-  nsCOMPtr<nsIAtom> eventTypeAtom = NS_Atomize(eventType);
-  NS_ENSURE_TRUE(eventTypeAtom, false);
-
+  nsCOMPtr<nsIAtom> eventTypeAtom =
+    ConvertEventToDOMEventType(*widgetKeyboardEvent);
   return WalkHandlersInternal(aEvent, eventTypeAtom, mHandler, false,
                               aOutReservedForChrome);
 }
 
 already_AddRefed<Element>
 nsXBLWindowKeyHandler::GetElement(bool* aIsDisabled)
 {
   nsCOMPtr<Element> element = do_QueryReferent(mWeakPtrForElement);
--- a/dom/xbl/nsXBLWindowKeyHandler.h
+++ b/dom/xbl/nsXBLWindowKeyHandler.h
@@ -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/. */
 
 #ifndef nsXBLWindowKeyHandler_h__
 #define nsXBLWindowKeyHandler_h__
 
+#include "mozilla/EventForwards.h"
 #include "nsWeakPtr.h"
 #include "nsIDOMEventListener.h"
 
 class nsIAtom;
 class nsIDOMElement;
 class nsIDOMKeyEvent;
 class nsXBLSpecialDocInfo;
 class nsXBLPrototypeHandler;
@@ -68,16 +69,22 @@ protected:
   void HandleEventOnCaptureInSystemEventGroup(nsIDOMKeyEvent* aEvent);
 
   // Check if any handler would handle the given event. Optionally returns
   // whether the command handler for the event is marked with the "reserved"
   // attribute.
   bool HasHandlerForEvent(nsIDOMKeyEvent* aEvent,
                           bool* aOutReservedForChrome = nullptr);
 
+  // Returns event type for matching between aWidgetKeyboardEvent and
+  // shortcut key handlers.  This is used for calling WalkHandlers(),
+  // WalkHandlersInternal() and WalkHandlersAndExecute().
+  nsIAtom* ConvertEventToDOMEventType(
+             const mozilla::WidgetKeyboardEvent& aWidgetKeyboardEvent) const;
+
   // lazily load the handlers. Overridden to handle being attached
   // to a particular element rather than the document
   nsresult EnsureHandlers();
 
   // Is an HTML editable element focused
   bool IsHTMLEditableFieldFocused();
 
   // Returns the element which was passed as a parameter to the constructor,
--- a/dom/xul/XULDocument.cpp
+++ b/dom/xul/XULDocument.cpp
@@ -909,16 +909,21 @@ XULDocument::AttributeWillChange(nsIDocu
         RemoveElementFromRefMap(aElement);
     }
 }
 
 static bool
 ShouldPersistAttribute(Element* aElement, nsIAtom* aAttribute)
 {
     if (aElement->IsXULElement(nsGkAtoms::window)) {
+        // This is not an element of the top document, its owner is
+        // not an nsXULWindow. Persist it.
+        if (aElement->OwnerDoc()->GetParentDocument()) {
+            return true;
+        }
         // The following attributes of xul:window should be handled in
         // nsXULWindow::SavePersistentAttributes instead of here.
         if (aAttribute == nsGkAtoms::screenX ||
             aAttribute == nsGkAtoms::screenY ||
             aAttribute == nsGkAtoms::width ||
             aAttribute == nsGkAtoms::height ||
             aAttribute == nsGkAtoms::sizemode) {
             return false;
--- a/editor/libeditor/nsHTMLEditRules.cpp
+++ b/editor/libeditor/nsHTMLEditRules.cpp
@@ -630,17 +630,17 @@ nsHTMLEditRules::WillDoAction(Selection*
     case EditAction::insertIMEText:
       UndefineCaretBidiLevel(aSelection);
       return WillInsertText(info->action, aSelection, aCancel, aHandled,
                             info->inString, info->outString, info->maxLength);
     case EditAction::loadHTML:
       return WillLoadHTML(aSelection, aCancel);
     case EditAction::insertBreak:
       UndefineCaretBidiLevel(aSelection);
-      return WillInsertBreak(aSelection, aCancel, aHandled);
+      return WillInsertBreak(*aSelection, aCancel, aHandled);
     case EditAction::deleteSelection:
       return WillDeleteSelection(aSelection, info->collapsedAction,
                                  info->stripWrappers, aCancel, aHandled);
     case EditAction::makeList:
       return WillMakeList(aSelection, info->blockType, info->entireList,
                           info->bulletType, aCancel, aHandled);
     case EditAction::indent:
       return WillIndent(aSelection, aCancel, aHandled);
@@ -655,17 +655,18 @@ nsHTMLEditRules::WillDoAction(Selection*
     case EditAction::makeBasicBlock:
       return WillMakeBasicBlock(aSelection, info->blockType, aCancel, aHandled);
     case EditAction::removeList:
       return WillRemoveList(aSelection, info->bOrdered, aCancel, aHandled);
     case EditAction::makeDefListItem:
       return WillMakeDefListItem(aSelection, info->blockType, info->entireList,
                                  aCancel, aHandled);
     case EditAction::insertElement:
-      return WillInsert(aSelection, aCancel);
+      WillInsert(*aSelection, aCancel);
+      return NS_OK;
     case EditAction::decreaseZIndex:
       return WillRelativeChangeZIndex(aSelection, -1, aCancel, aHandled);
     case EditAction::increaseZIndex:
       return WillRelativeChangeZIndex(aSelection, 1, aCancel, aHandled);
     default:
       return nsTextEditRules::WillDoAction(aSelection, aInfo,
                                            aCancel, aHandled);
   }
@@ -1201,81 +1202,71 @@ nsHTMLEditRules::GetFormatString(nsIDOMN
 
   return NS_OK;
 }
 
 /********************************************************
  *  Protected rules methods
  ********************************************************/
 
-nsresult
-nsHTMLEditRules::WillInsert(Selection* aSelection, bool* aCancel)
-{
-  nsresult res = nsTextEditRules::WillInsert(aSelection, aCancel);
-  NS_ENSURE_SUCCESS(res, res);
-
-  // Adjust selection to prevent insertion after a moz-BR.
-  // this next only works for collapsed selections right now,
-  // because selection is a pain to work with when not collapsed.
-  // (no good way to extend start or end of selection), so we ignore
-  // those types of selections.
-  if (!aSelection->Collapsed()) {
-    return NS_OK;
-  }
-
-  // if we are after a mozBR in the same block, then move selection
-  // to be before it
-  nsCOMPtr<nsIDOMNode> selNode, priorNode;
-  int32_t selOffset;
-  // get the (collapsed) selection location
-  NS_ENSURE_STATE(mHTMLEditor);
-  res = mHTMLEditor->GetStartNodeAndOffset(aSelection, getter_AddRefs(selNode),
-                                           &selOffset);
-  NS_ENSURE_SUCCESS(res, res);
-  // get prior node
-  NS_ENSURE_STATE(mHTMLEditor);
-  res = mHTMLEditor->GetPriorHTMLNode(selNode, selOffset,
-                                      address_of(priorNode));
-  if (NS_SUCCEEDED(res) && priorNode && nsTextEditUtils::IsMozBR(priorNode))
-  {
-    nsCOMPtr<nsIDOMNode> block1, block2;
-    if (IsBlockNode(selNode)) {
-      block1 = selNode;
-    }
-    else {
-      NS_ENSURE_STATE(mHTMLEditor);
-      block1 = mHTMLEditor->GetBlockNodeParent(selNode);
-    }
-    NS_ENSURE_STATE(mHTMLEditor);
-    block2 = mHTMLEditor->GetBlockNodeParent(priorNode);
+void
+nsHTMLEditRules::WillInsert(Selection& aSelection, bool* aCancel)
+{
+  MOZ_ASSERT(aCancel);
+
+  nsTextEditRules::WillInsert(aSelection, aCancel);
+
+  NS_ENSURE_TRUE_VOID(mHTMLEditor);
+  nsCOMPtr<nsIEditor> kungFuDeathGrip(mHTMLEditor);
+
+  // Adjust selection to prevent insertion after a moz-BR.  This next only
+  // works for collapsed selections right now, because selection is a pain to
+  // work with when not collapsed.  (no good way to extend start or end of
+  // selection), so we ignore those types of selections.
+  if (!aSelection.Collapsed()) {
+    return;
+  }
+
+  // If we are after a mozBR in the same block, then move selection to be
+  // before it
+  NS_ENSURE_TRUE_VOID(aSelection.GetRangeAt(0) &&
+                      aSelection.GetRangeAt(0)->GetStartParent());
+  OwningNonNull<nsINode> selNode = *aSelection.GetRangeAt(0)->GetStartParent();
+  int32_t selOffset = aSelection.GetRangeAt(0)->StartOffset();
+
+  // Get prior node
+  nsCOMPtr<nsIContent> priorNode = mHTMLEditor->GetPriorHTMLNode(selNode,
+                                                                 selOffset);
+  if (priorNode && nsTextEditUtils::IsMozBR(priorNode)) {
+    nsCOMPtr<Element> block1 = mHTMLEditor->GetBlock(selNode);
+    nsCOMPtr<Element> block2 = mHTMLEditor->GetBlockNodeParent(priorNode);
 
     if (block1 && block1 == block2) {
-      // if we are here then the selection is right after a mozBR
-      // that is in the same block as the selection.  We need to move
-      // the selection start to be before the mozBR.
-      selNode = nsEditor::GetNodeLocation(priorNode, &selOffset);
-      res = aSelection->Collapse(selNode,selOffset);
-      NS_ENSURE_SUCCESS(res, res);
+      // If we are here then the selection is right after a mozBR that is in
+      // the same block as the selection.  We need to move the selection start
+      // to be before the mozBR.
+      selNode = priorNode->GetParentNode();
+      selOffset = selNode->IndexOf(priorNode);
+      nsresult res = aSelection.Collapse(selNode, selOffset);
+      NS_ENSURE_SUCCESS_VOID(res);
     }
   }
 
   if (mDidDeleteSelection &&
       (mTheAction == EditAction::insertText ||
        mTheAction == EditAction::insertIMEText ||
        mTheAction == EditAction::deleteSelection)) {
-    res = ReapplyCachedStyles();
-    NS_ENSURE_SUCCESS(res, res);
+    nsresult res = ReapplyCachedStyles();
+    NS_ENSURE_SUCCESS_VOID(res);
   }
   // For most actions we want to clear the cached styles, but there are
   // exceptions
   if (!IsStyleCachePreservingAction(mTheAction)) {
     ClearCachedStyles();
   }
-
-  return NS_OK;
 }
 
 nsresult
 nsHTMLEditRules::WillInsertText(EditAction aAction,
                                 Selection*       aSelection,
                                 bool            *aCancel,
                                 bool            *aHandled,
                                 const nsAString *inString,
@@ -1302,18 +1293,17 @@ nsHTMLEditRules::WillInsertText(EditActi
   // If the selection isn't collapsed, delete it.  Don't delete existing inline
   // tags, because we're hopefully going to insert text (bug 787432).
   if (!aSelection->Collapsed()) {
     NS_ENSURE_STATE(mHTMLEditor);
     res = mHTMLEditor->DeleteSelection(nsIEditor::eNone, nsIEditor::eNoStrip);
     NS_ENSURE_SUCCESS(res, res);
   }
 
-  res = WillInsert(aSelection, aCancel);
-  NS_ENSURE_SUCCESS(res, res);
+  WillInsert(*aSelection, aCancel);
   // initialize out param
   // we want to ignore result of WillInsert()
   *aCancel = false;
 
   // we need to get the doc
   NS_ENSURE_STATE(mHTMLEditor);
   nsCOMPtr<nsIDocument> doc = mHTMLEditor->GetDocument();
   nsCOMPtr<nsIDOMDocument> domDoc = mHTMLEditor->GetDOMDocument();
@@ -1520,234 +1510,209 @@ nsHTMLEditRules::WillLoadHTML(Selection*
     mEditor->DeleteNode(mBogusNode);
     mBogusNode = nullptr;
   }
 
   return NS_OK;
 }
 
 nsresult
-nsHTMLEditRules::WillInsertBreak(Selection* aSelection,
-                                 bool* aCancel, bool* aHandled)
-{
-  if (!aSelection || !aCancel || !aHandled) {
-    return NS_ERROR_NULL_POINTER;
-  }
-  // initialize out params
+nsHTMLEditRules::WillInsertBreak(Selection& aSelection, bool* aCancel,
+                                 bool* aHandled)
+{
+  MOZ_ASSERT(aCancel && aHandled);
   *aCancel = false;
   *aHandled = false;
 
-  // if the selection isn't collapsed, delete it.
-  nsresult res = NS_OK;
-  if (!aSelection->Collapsed()) {
-    NS_ENSURE_STATE(mHTMLEditor);
+  NS_ENSURE_STATE(mHTMLEditor);
+  nsCOMPtr<nsIEditor> kungFuDeathGrip(mHTMLEditor);
+
+  // If the selection isn't collapsed, delete it.
+  nsresult res;
+  if (!aSelection.Collapsed()) {
     res = mHTMLEditor->DeleteSelection(nsIEditor::eNone, nsIEditor::eStrip);
     NS_ENSURE_SUCCESS(res, res);
   }
 
-  res = WillInsert(aSelection, aCancel);
-  NS_ENSURE_SUCCESS(res, res);
-
-  // initialize out param
-  // we want to ignore result of WillInsert()
+  WillInsert(aSelection, aCancel);
+
+  // Initialize out param.  We want to ignore result of WillInsert().
   *aCancel = false;
 
-  // split any mailcites in the way.
-  // should we abort this if we encounter table cell boundaries?
+  // Split any mailcites in the way.  Should we abort this if we encounter
+  // table cell boundaries?
   if (IsMailEditor()) {
-    res = SplitMailCites(aSelection, aHandled);
+    res = SplitMailCites(&aSelection, aHandled);
     NS_ENSURE_SUCCESS(res, res);
     if (*aHandled) {
       return NS_OK;
     }
   }
 
-  // smart splitting rules
-  nsCOMPtr<nsIDOMNode> node;
-  int32_t offset;
-
-  NS_ENSURE_STATE(mHTMLEditor);
-  res = mHTMLEditor->GetStartNodeAndOffset(aSelection, getter_AddRefs(node),
-                                           &offset);
-  NS_ENSURE_SUCCESS(res, res);
-  NS_ENSURE_TRUE(node, NS_ERROR_FAILURE);
-
-  // do nothing if the node is read-only
-  NS_ENSURE_STATE(mHTMLEditor);
+  // Smart splitting rules
+  NS_ENSURE_TRUE(aSelection.GetRangeAt(0) &&
+                 aSelection.GetRangeAt(0)->GetStartParent(),
+                 NS_ERROR_FAILURE);
+  OwningNonNull<nsINode> node = *aSelection.GetRangeAt(0)->GetStartParent();
+  int32_t offset = aSelection.GetRangeAt(0)->StartOffset();
+
+  // Do nothing if the node is read-only
   if (!mHTMLEditor->IsModifiableNode(node)) {
     *aCancel = true;
     return NS_OK;
   }
 
-  // identify the block
-  nsCOMPtr<nsIDOMNode> blockParent;
-  if (IsBlockNode(node)) {
-    blockParent = node;
-  } else {
-    NS_ENSURE_STATE(mHTMLEditor);
-    blockParent = mHTMLEditor->GetBlockNodeParent(node);
-  }
+  // Identify the block
+  nsCOMPtr<Element> blockParent = mHTMLEditor->GetBlock(node);
   NS_ENSURE_TRUE(blockParent, NS_ERROR_FAILURE);
 
-  // if the active editing host is an inline element, or if the active editing
+  // If the active editing host is an inline element, or if the active editing
   // host is the block parent itself, just append a br.
-  NS_ENSURE_STATE(mHTMLEditor);
-  nsCOMPtr<nsIContent> hostContent = mHTMLEditor->GetActiveEditingHost();
-  nsCOMPtr<nsIDOMNode> hostNode = do_QueryInterface(hostContent);
-  if (!nsEditorUtils::IsDescendantOf(blockParent, hostNode)) {
+  nsCOMPtr<Element> host = mHTMLEditor->GetActiveEditingHost();
+  if (!nsEditorUtils::IsDescendantOf(blockParent, host)) {
     res = StandardBreakImpl(node, offset, aSelection);
     NS_ENSURE_SUCCESS(res, res);
     *aHandled = true;
     return NS_OK;
   }
 
-  // if block is empty, populate with br.  (for example, imagine a div that
-  // contains the word "text".  the user selects "text" and types return.
-  // "text" is deleted leaving an empty block.  we want to put in one br to
-  // make block have a line.  then code further below will put in a second br.)
+  // If block is empty, populate with br.  (For example, imagine a div that
+  // contains the word "text".  The user selects "text" and types return.
+  // "Text" is deleted leaving an empty block.  We want to put in one br to
+  // make block have a line.  Then code further below will put in a second br.)
   bool isEmpty;
-  IsEmptyBlock(blockParent, &isEmpty);
+  IsEmptyBlock(GetAsDOMNode(blockParent), &isEmpty);
   if (isEmpty) {
-    uint32_t blockLen;
-    NS_ENSURE_STATE(mHTMLEditor);
-    res = mHTMLEditor->GetLengthOfDOMNode(blockParent, blockLen);
-    NS_ENSURE_SUCCESS(res, res);
-    nsCOMPtr<nsIDOMNode> brNode;
-    NS_ENSURE_STATE(mHTMLEditor);
-    res = mHTMLEditor->CreateBR(blockParent, blockLen, address_of(brNode));
-    NS_ENSURE_SUCCESS(res, res);
-  }
-
-  nsCOMPtr<nsIDOMNode> listItem = IsInListItem(blockParent);
-  if (listItem && listItem != hostNode) {
-    ReturnInListItem(aSelection, listItem, node, offset);
+    nsCOMPtr<Element> br = mHTMLEditor->CreateBR(blockParent,
+                                                 blockParent->Length());
+    NS_ENSURE_STATE(br);
+  }
+
+  nsCOMPtr<Element> listItem = IsInListItem(blockParent);
+  if (listItem && listItem != host) {
+    ReturnInListItem(&aSelection, GetAsDOMNode(listItem), GetAsDOMNode(node),
+                     offset);
     *aHandled = true;
     return NS_OK;
-  } else if (nsHTMLEditUtils::IsHeader(blockParent)) {
-    // headers: close (or split) header
-    ReturnInHeader(aSelection, blockParent, node, offset);
+  } else if (nsHTMLEditUtils::IsHeader(*blockParent)) {
+    // Headers: close (or split) header
+    ReturnInHeader(&aSelection, GetAsDOMNode(blockParent), GetAsDOMNode(node),
+                   offset);
     *aHandled = true;
     return NS_OK;
-  } else if (nsHTMLEditUtils::IsParagraph(blockParent)) {
-    // paragraphs: special rules to look for <br>s
-    res = ReturnInParagraph(aSelection, blockParent, node, offset,
-                            aCancel, aHandled);
+  } else if (blockParent->IsHTMLElement(nsGkAtoms::p)) {
+    // Paragraphs: special rules to look for <br>s
+    res = ReturnInParagraph(&aSelection, GetAsDOMNode(blockParent),
+                            GetAsDOMNode(node), offset, aCancel, aHandled);
     NS_ENSURE_SUCCESS(res, res);
-    // fall through, we may not have handled it in ReturnInParagraph()
-  }
-
-  // if not already handled then do the standard thing
+    // Fall through, we may not have handled it in ReturnInParagraph()
+  }
+
+  // If not already handled then do the standard thing
   if (!(*aHandled)) {
     *aHandled = true;
     return StandardBreakImpl(node, offset, aSelection);
   }
   return NS_OK;
 }
 
 nsresult
-nsHTMLEditRules::StandardBreakImpl(nsIDOMNode* aNode, int32_t aOffset,
-                                   Selection* aSelection)
-{
-  nsCOMPtr<nsIDOMNode> brNode;
+nsHTMLEditRules::StandardBreakImpl(nsINode& aNode, int32_t aOffset,
+                                   Selection& aSelection)
+{
+  NS_ENSURE_STATE(mHTMLEditor);
+  nsCOMPtr<nsIEditor> kungFuDeathGrip(mHTMLEditor);
+
+  nsCOMPtr<Element> brNode;
   bool bAfterBlock = false;
   bool bBeforeBlock = false;
-  nsresult res = NS_OK;
-  nsCOMPtr<nsIDOMNode> node(aNode);
+  nsCOMPtr<nsINode> node = &aNode;
+  nsresult res;
 
   if (IsPlaintextEditor()) {
-    NS_ENSURE_STATE(mHTMLEditor);
-    res = mHTMLEditor->CreateBR(node, aOffset, address_of(brNode));
+    brNode = mHTMLEditor->CreateBR(node, aOffset);
+    NS_ENSURE_STATE(brNode);
   } else {
-    NS_ENSURE_STATE(mHTMLEditor);
     nsWSRunObject wsObj(mHTMLEditor, node, aOffset);
     int32_t visOffset = 0;
     WSType wsType;
-    nsCOMPtr<nsINode> node_(do_QueryInterface(node)), visNode;
-    wsObj.PriorVisibleNode(node_, aOffset, address_of(visNode),
+    nsCOMPtr<nsINode> visNode;
+    wsObj.PriorVisibleNode(node, aOffset, address_of(visNode),
                            &visOffset, &wsType);
     if (wsType & WSType::block) {
       bAfterBlock = true;
     }
-    wsObj.NextVisibleNode(node_, aOffset, address_of(visNode),
+    wsObj.NextVisibleNode(node, aOffset, address_of(visNode),
                           &visOffset, &wsType);
     if (wsType & WSType::block) {
       bBeforeBlock = true;
     }
-    NS_ENSURE_STATE(mHTMLEditor);
-    nsCOMPtr<nsIDOMNode> linkNode;
-    if (mHTMLEditor->IsInLink(node, address_of(linkNode))) {
-      // split the link
-      nsCOMPtr<nsIDOMNode> linkParent;
-      res = linkNode->GetParentNode(getter_AddRefs(linkParent));
-      NS_ENSURE_SUCCESS(res, res);
-      NS_ENSURE_STATE(mHTMLEditor);
-      nsCOMPtr<nsIContent> linkNodeContent = do_QueryInterface(linkNode);
-      NS_ENSURE_STATE(linkNodeContent || !linkNode);
-      aOffset = mHTMLEditor->SplitNodeDeep(*linkNodeContent,
-          *node_->AsContent(), aOffset, nsHTMLEditor::EmptyContainers::no);
+    nsCOMPtr<nsIDOMNode> linkDOMNode;
+    if (mHTMLEditor->IsInLink(GetAsDOMNode(node), address_of(linkDOMNode))) {
+      // Split the link
+      nsCOMPtr<Element> linkNode = do_QueryInterface(linkDOMNode);
+      NS_ENSURE_STATE(linkNode || !linkDOMNode);
+      nsCOMPtr<nsINode> linkParent = linkNode->GetParentNode();
+      aOffset = mHTMLEditor->SplitNodeDeep(*linkNode, *node->AsContent(),
+                                           aOffset,
+                                           nsHTMLEditor::EmptyContainers::no);
       NS_ENSURE_STATE(aOffset != -1);
       node = linkParent;
     }
-    node_ = do_QueryInterface(node);
-    nsCOMPtr<Element> br =
-      wsObj.InsertBreak(address_of(node_), &aOffset, nsIEditor::eNone);
-    node = GetAsDOMNode(node_);
-    brNode = GetAsDOMNode(br);
+    brNode = wsObj.InsertBreak(address_of(node), &aOffset, nsIEditor::eNone);
     NS_ENSURE_TRUE(brNode, NS_ERROR_FAILURE);
   }
-  NS_ENSURE_SUCCESS(res, res);
-  node = nsEditor::GetNodeLocation(brNode, &aOffset);
+  node = brNode->GetParentNode();
   NS_ENSURE_TRUE(node, NS_ERROR_NULL_POINTER);
+  int32_t offset = node->IndexOf(brNode);
   if (bAfterBlock && bBeforeBlock) {
-    // we just placed a br between block boundaries.  This is the one case
+    // We just placed a br between block boundaries.  This is the one case
     // where we want the selection to be before the br we just placed, as the
     // br will be on a new line, rather than at end of prior line.
-    aSelection->SetInterlinePosition(true);
-    res = aSelection->Collapse(node, aOffset);
+    aSelection.SetInterlinePosition(true);
+    res = aSelection.Collapse(node, offset);
+    NS_ENSURE_SUCCESS(res, res);
   } else {
-    NS_ENSURE_STATE(mHTMLEditor);
-    nsWSRunObject wsObj(mHTMLEditor, node, aOffset+1);
+    nsWSRunObject wsObj(mHTMLEditor, node, offset + 1);
     nsCOMPtr<nsINode> secondBR;
     int32_t visOffset = 0;
     WSType wsType;
-    nsCOMPtr<nsINode> node_(do_QueryInterface(node));
-    wsObj.NextVisibleNode(node_, aOffset+1, address_of(secondBR),
+    wsObj.NextVisibleNode(node, offset + 1, address_of(secondBR),
                           &visOffset, &wsType);
     if (wsType == WSType::br) {
-      // the next thing after the break we inserted is another break.  Move
-      // the 2nd break to be the first breaks sibling.  This will prevent them
+      // The next thing after the break we inserted is another break.  Move the
+      // second break to be the first break's sibling.  This will prevent them
       // from being in different inline nodes, which would break
       // SetInterlinePosition().  It will also assure that if the user clicks
-      // away and then clicks back on their new blank line, they will still
-      // get the style from the line above.
-      int32_t brOffset;
-      nsCOMPtr<nsIDOMNode> brParent = nsEditor::GetNodeLocation(GetAsDOMNode(secondBR), &brOffset);
-      if (brParent != node || brOffset != aOffset + 1) {
-        NS_ENSURE_STATE(mHTMLEditor);
-        res = mHTMLEditor->MoveNode(secondBR->AsContent(), node_, aOffset + 1);
+      // away and then clicks back on their new blank line, they will still get
+      // the style from the line above.
+      nsCOMPtr<nsINode> brParent = secondBR->GetParentNode();
+      int32_t brOffset = brParent ? brParent->IndexOf(secondBR) : -1;
+      if (brParent != node || brOffset != offset + 1) {
+        res = mHTMLEditor->MoveNode(secondBR->AsContent(), node, offset + 1);
         NS_ENSURE_SUCCESS(res, res);
       }
     }
     // SetInterlinePosition(true) means we want the caret to stick to the
     // content on the "right".  We want the caret to stick to whatever is past
     // the break.  This is because the break is on the same line we were on,
     // but the next content will be on the following line.
 
     // An exception to this is if the break has a next sibling that is a block
     // node.  Then we stick to the left to avoid an uber caret.
-    nsCOMPtr<nsIDOMNode> siblingNode;
-    brNode->GetNextSibling(getter_AddRefs(siblingNode));
-    if (siblingNode && IsBlockNode(siblingNode)) {
-      aSelection->SetInterlinePosition(false);
+    nsCOMPtr<nsIContent> siblingNode = brNode->GetNextSibling();
+    if (siblingNode && IsBlockNode(GetAsDOMNode(siblingNode))) {
+      aSelection.SetInterlinePosition(false);
     } else {
-      aSelection->SetInterlinePosition(true);
-    }
-    res = aSelection->Collapse(node, aOffset+1);
-  }
-  return res;
+      aSelection.SetInterlinePosition(true);
+    }
+    res = aSelection.Collapse(node, offset + 1);
+    NS_ENSURE_SUCCESS(res, res);
+  }
+  return NS_OK;
 }
 
 nsresult
 nsHTMLEditRules::DidInsertBreak(Selection* aSelection, nsresult aResult)
 {
   return NS_OK;
 }
 
@@ -2226,17 +2191,19 @@ nsHTMLEditRules::WillDeleteSelection(Sel
       // Else we are joining content to block
 
       nsCOMPtr<nsINode> selPointNode = startNode;
       int32_t selPointOffset = startOffset;
       {
         NS_ENSURE_STATE(mHTMLEditor);
         nsAutoTrackDOMPoint tracker(mHTMLEditor->mRangeUpdater,
                                     address_of(selPointNode), &selPointOffset);
-        res = JoinBlocks(GetAsDOMNode(leftNode), GetAsDOMNode(rightNode),
+        NS_ENSURE_STATE(leftNode && leftNode->IsContent() &&
+                        rightNode && rightNode->IsContent());
+        res = JoinBlocks(*leftNode->AsContent(), *rightNode->AsContent(),
                          aCancel);
         *aHandled = true;
         NS_ENSURE_SUCCESS(res, res);
       }
       aSelection->Collapse(selPointNode, selPointOffset);
       return NS_OK;
     }
 
@@ -2276,17 +2243,18 @@ nsHTMLEditRules::WillDeleteSelection(Sel
       }
 
       nsCOMPtr<nsINode> selPointNode = startNode;
       int32_t selPointOffset = startOffset;
       {
         NS_ENSURE_STATE(mHTMLEditor);
         nsAutoTrackDOMPoint tracker(mHTMLEditor->mRangeUpdater,
                                     address_of(selPointNode), &selPointOffset);
-        res = JoinBlocks(GetAsDOMNode(leftNode), GetAsDOMNode(rightNode),
+        NS_ENSURE_STATE(leftNode->IsContent() && rightNode->IsContent());
+        res = JoinBlocks(*leftNode->AsContent(), *rightNode->AsContent(),
                          aCancel);
         *aHandled = true;
         NS_ENSURE_SUCCESS(res, res);
       }
       aSelection->Collapse(selPointNode, selPointOffset);
       return NS_OK;
     }
   }
@@ -2462,18 +2430,17 @@ nsHTMLEditRules::WillDeleteSelection(Sel
           NS_ENSURE_STATE(mHTMLEditor);
           OwningNonNull<nsGenericDOMDataNode> dataNode =
             *static_cast<nsGenericDOMDataNode*>(endNode.get());
           res = mHTMLEditor->DeleteText(dataNode, 0, endOffset);
           NS_ENSURE_SUCCESS(res, res);
         }
 
         if (join) {
-          res = JoinBlocks(GetAsDOMNode(leftParent), GetAsDOMNode(rightParent),
-                           aCancel);
+          res = JoinBlocks(*leftParent, *rightParent, aCancel);
           NS_ENSURE_SUCCESS(res, res);
         }
       }
     }
   }
 
   // We might have left only collapsed whitespace in the start/end nodes
   {
@@ -2592,304 +2559,286 @@ nsHTMLEditRules::GetGoodSelPointForNode(
        mHTMLEditor->IsVisBreak(&aNode)) &&
       aAction == nsIEditor::ePrevious) {
     ret.offset++;
   }
   return ret;
 }
 
 
-/*****************************************************************************************************
-*    JoinBlocks: this method is used to join two block elements.  The right element is always joined
-*    to the left element.  If the elements are the same type and not nested within each other,
-*    JoinNodesSmart is called (example, joining two list items together into one).  If the elements
-*    are not the same type, or one is a descendant of the other, we instead destroy the right block
-*    placing its children into leftblock.  DTD containment rules are followed throughout.
-*         nsCOMPtr<nsIDOMNode> *aLeftBlock         pointer to the left block
-*         nsCOMPtr<nsIDOMNode> *aRightBlock        pointer to the right block; will have contents moved to left block
-*         bool *aCanceled                        return TRUE if we had to cancel operation
-*/
+/**
+ * This method is used to join two block elements.  The right element is always
+ * joined to the left element.  If the elements are the same type and not
+ * nested within each other, JoinNodesSmart is called (example, joining two
+ * list items together into one).  If the elements are not the same type, or
+ * one is a descendant of the other, we instead destroy the right block placing
+ * its children into leftblock.  DTD containment rules are followed throughout.
+ */
 nsresult
-nsHTMLEditRules::JoinBlocks(nsIDOMNode *aLeftNode,
-                            nsIDOMNode *aRightNode,
-                            bool *aCanceled)
-{
-  NS_ENSURE_ARG_POINTER(aLeftNode && aRightNode);
-
-  nsCOMPtr<nsIDOMNode> aLeftBlock, aRightBlock;
-
-  if (IsBlockNode(aLeftNode)) {
-    aLeftBlock = aLeftNode;
-  } else if (aLeftNode) {
-    NS_ENSURE_STATE(mHTMLEditor);
-    aLeftBlock = mHTMLEditor->GetBlockNodeParent(aLeftNode);
-  }
-
-  if (IsBlockNode(aRightNode)) {
-    aRightBlock = aRightNode;
-  } else if (aRightNode) {
-    NS_ENSURE_STATE(mHTMLEditor);
-    aRightBlock = mHTMLEditor->GetBlockNodeParent(aRightNode);
-  }
-
-  // sanity checks
-  NS_ENSURE_TRUE(aLeftBlock && aRightBlock, NS_ERROR_NULL_POINTER);
-  NS_ENSURE_STATE(aLeftBlock != aRightBlock);
-
-  if (nsHTMLEditUtils::IsTableElement(aLeftBlock) ||
-      nsHTMLEditUtils::IsTableElement(aRightBlock)) {
-    // do not try to merge table elements
+nsHTMLEditRules::JoinBlocks(nsIContent& aLeftNode, nsIContent& aRightNode,
+                            bool* aCanceled)
+{
+  MOZ_ASSERT(aCanceled);
+
+  NS_ENSURE_STATE(mHTMLEditor);
+  nsCOMPtr<nsIEditor> kungFuDeathGrip(mHTMLEditor);
+
+  nsCOMPtr<Element> leftBlock = mHTMLEditor->GetBlock(aLeftNode);
+  nsCOMPtr<Element> rightBlock = mHTMLEditor->GetBlock(aRightNode);
+
+  // Sanity checks
+  NS_ENSURE_TRUE(leftBlock && rightBlock, NS_ERROR_NULL_POINTER);
+  NS_ENSURE_STATE(leftBlock != rightBlock);
+
+  if (nsHTMLEditUtils::IsTableElement(leftBlock) ||
+      nsHTMLEditUtils::IsTableElement(rightBlock)) {
+    // Do not try to merge table elements
     *aCanceled = true;
     return NS_OK;
   }
 
-  // make sure we don't try to move thing's into HR's, which look like blocks but aren't containers
-  if (nsHTMLEditUtils::IsHR(aLeftBlock)) {
-    NS_ENSURE_STATE(mHTMLEditor);
-    nsCOMPtr<nsIDOMNode> realLeft = mHTMLEditor->GetBlockNodeParent(aLeftBlock);
-    aLeftBlock = realLeft;
-  }
-  if (nsHTMLEditUtils::IsHR(aRightBlock)) {
-    NS_ENSURE_STATE(mHTMLEditor);
-    nsCOMPtr<nsIDOMNode> realRight = mHTMLEditor->GetBlockNodeParent(aRightBlock);
-    aRightBlock = realRight;
-  }
-  NS_ENSURE_STATE(aLeftBlock && aRightBlock);
-
-  // bail if both blocks the same
-  if (aLeftBlock == aRightBlock) {
+  // Make sure we don't try to move things into HR's, which look like blocks
+  // but aren't containers
+  if (leftBlock->IsHTMLElement(nsGkAtoms::hr)) {
+    leftBlock = mHTMLEditor->GetBlockNodeParent(leftBlock);
+  }
+  if (rightBlock->IsHTMLElement(nsGkAtoms::hr)) {
+    rightBlock = mHTMLEditor->GetBlockNodeParent(rightBlock);
+  }
+  NS_ENSURE_STATE(leftBlock && rightBlock);
+
+  // Bail if both blocks the same
+  if (leftBlock == rightBlock) {
     *aCanceled = true;
     return NS_OK;
   }
 
   // Joining a list item to its parent is a NOP.
-  if (nsHTMLEditUtils::IsList(aLeftBlock) &&
-      nsHTMLEditUtils::IsListItem(aRightBlock)) {
-    nsCOMPtr<nsIDOMNode> rightParent;
-    aRightBlock->GetParentNode(getter_AddRefs(rightParent));
-    if (rightParent == aLeftBlock) {
-      return NS_OK;
-    }
-  }
-
-  // special rule here: if we are trying to join list items, and they are in different lists,
-  // join the lists instead.
-  bool bMergeLists = false;
+  if (nsHTMLEditUtils::IsList(leftBlock) &&
+      nsHTMLEditUtils::IsListItem(rightBlock) &&
+      rightBlock->GetParentNode() == leftBlock) {
+    return NS_OK;
+  }
+
+  // Special rule here: if we are trying to join list items, and they are in
+  // different lists, join the lists instead.
+  bool mergeLists = false;
   nsIAtom* existingList = nsGkAtoms::_empty;
-  int32_t theOffset;
-  nsCOMPtr<nsIDOMNode> leftList, rightList;
-  if (nsHTMLEditUtils::IsListItem(aLeftBlock) &&
-      nsHTMLEditUtils::IsListItem(aRightBlock)) {
-    aLeftBlock->GetParentNode(getter_AddRefs(leftList));
-    aRightBlock->GetParentNode(getter_AddRefs(rightList));
-    if (leftList && rightList && (leftList!=rightList))
-    {
-      // there are some special complications if the lists are descendants of
-      // the other lists' items.  Note that it is ok for them to be descendants
-      // of the other lists themselves, which is the usual case for sublists
-      // in our impllementation.
-      if (!nsEditorUtils::IsDescendantOf(leftList, aRightBlock, &theOffset) &&
-          !nsEditorUtils::IsDescendantOf(rightList, aLeftBlock, &theOffset))
-      {
-        aLeftBlock = leftList;
-        aRightBlock = rightList;
-        bMergeLists = true;
-        NS_ENSURE_STATE(mHTMLEditor);
-        existingList = mHTMLEditor->GetTag(leftList);
-      }
-    }
-  }
-
-  NS_ENSURE_STATE(mHTMLEditor);
+  int32_t offset;
+  nsCOMPtr<Element> leftList, rightList;
+  if (nsHTMLEditUtils::IsListItem(leftBlock) &&
+      nsHTMLEditUtils::IsListItem(rightBlock)) {
+    leftList = leftBlock->GetParentElement();
+    rightList = rightBlock->GetParentElement();
+    if (leftList && rightList && leftList != rightList &&
+        !nsEditorUtils::IsDescendantOf(leftList, rightBlock, &offset) &&
+        !nsEditorUtils::IsDescendantOf(rightList, leftBlock, &offset)) {
+      // There are some special complications if the lists are descendants of
+      // the other lists' items.  Note that it is okay for them to be
+      // descendants of the other lists themselves, which is the usual case for
+      // sublists in our implementation.
+      leftBlock = leftList;
+      rightBlock = rightList;
+      mergeLists = true;
+      existingList = leftList->NodeInfo()->NameAtom();
+    }
+  }
+
   nsAutoTxnsConserveSelection dontSpazMySelection(mHTMLEditor);
 
   nsresult res = NS_OK;
-  int32_t  rightOffset = 0;
-  int32_t  leftOffset  = -1;
-
-  // theOffset below is where you find yourself in aRightBlock when you traverse upwards
-  // from aLeftBlock
-  if (nsEditorUtils::IsDescendantOf(aLeftBlock, aRightBlock, &rightOffset)) {
-    // tricky case.  left block is inside right block.
-    // Do ws adjustment.  This just destroys non-visible ws at boundaries we will be joining.
+  int32_t rightOffset = 0;
+  int32_t leftOffset = -1;
+
+  // offset below is where you find yourself in rightBlock when you traverse
+  // upwards from leftBlock
+  if (nsEditorUtils::IsDescendantOf(leftBlock, rightBlock, &rightOffset)) {
+    // Tricky case.  Left block is inside right block.  Do ws adjustment.  This
+    // just destroys non-visible ws at boundaries we will be joining.
     rightOffset++;
-    NS_ENSURE_STATE(mHTMLEditor);
-    nsCOMPtr<nsINode> leftBlock(do_QueryInterface(aLeftBlock));
     res = nsWSRunObject::ScrubBlockBoundary(mHTMLEditor,
                                             nsWSRunObject::kBlockEnd,
                                             leftBlock);
     NS_ENSURE_SUCCESS(res, res);
 
     {
+      // We can't just track rightBlock because it's an Element.
+      nsCOMPtr<nsINode> trackingRightBlock(rightBlock);
       nsAutoTrackDOMPoint tracker(mHTMLEditor->mRangeUpdater,
-                                  address_of(aRightBlock), &rightOffset);
-      nsCOMPtr<nsINode> rightBlock(do_QueryInterface(aRightBlock));
+                                  address_of(trackingRightBlock),
+                                  &rightOffset);
       res = nsWSRunObject::ScrubBlockBoundary(mHTMLEditor,
                                               nsWSRunObject::kAfterBlock,
                                               rightBlock, rightOffset);
       NS_ENSURE_SUCCESS(res, res);
+      if (trackingRightBlock->IsElement()) {
+        rightBlock = trackingRightBlock->AsElement();
+      } else {
+        NS_ENSURE_STATE(trackingRightBlock->GetParentElement());
+        rightBlock = trackingRightBlock->GetParentElement();
+      }
     }
     // Do br adjustment.
     nsCOMPtr<nsIDOMNode> brNode;
-    res = CheckForInvisibleBR(aLeftBlock, kBlockEnd, address_of(brNode));
+    res = CheckForInvisibleBR(GetAsDOMNode(leftBlock), kBlockEnd,
+                              address_of(brNode));
     NS_ENSURE_SUCCESS(res, res);
-    if (bMergeLists)
-    {
-      // idea here is to take all children in  rightList that are past
-      // theOffset, and pull them into leftlist.
-      nsCOMPtr<nsIContent> parent(do_QueryInterface(rightList));
-      NS_ENSURE_TRUE(parent, NS_ERROR_NULL_POINTER);
-
-      nsIContent *child = parent->GetChildAt(theOffset);
-      nsCOMPtr<nsINode> leftList_ = do_QueryInterface(leftList);
-      NS_ENSURE_STATE(leftList_);
-      while (child)
-      {
-        NS_ENSURE_STATE(mHTMLEditor);
-        res = mHTMLEditor->MoveNode(child, leftList_, -1);
+    if (mergeLists) {
+      // The idea here is to take all children in rightList that are past
+      // offset, and pull them into leftlist.
+      for (nsCOMPtr<nsIContent> child = rightList->GetChildAt(offset);
+           child; child = rightList->GetChildAt(rightOffset)) {
+        res = mHTMLEditor->MoveNode(child, leftList, -1);
         NS_ENSURE_SUCCESS(res, res);
-
-        child = parent->GetChildAt(rightOffset);
-      }
-    }
-    else
-    {
-      res = MoveBlock(aLeftBlock, aRightBlock, leftOffset, rightOffset);
-    }
-    NS_ENSURE_STATE(mHTMLEditor);
-    if (brNode) mHTMLEditor->DeleteNode(brNode);
-  // theOffset below is where you find yourself in aLeftBlock when you traverse upwards
-  // from aRightBlock
-  } else if (nsEditorUtils::IsDescendantOf(aRightBlock, aLeftBlock, &leftOffset)) {
-    // tricky case.  right block is inside left block.
-    // Do ws adjustment.  This just destroys non-visible ws at boundaries we will be joining.
-    NS_ENSURE_STATE(mHTMLEditor);
-    nsCOMPtr<nsINode> rightBlock(do_QueryInterface(aRightBlock));
+      }
+    } else {
+      res = MoveBlock(GetAsDOMNode(leftBlock), GetAsDOMNode(rightBlock),
+                      leftOffset, rightOffset);
+    }
+    if (brNode) {
+      mHTMLEditor->DeleteNode(brNode);
+    }
+  // Offset below is where you find yourself in leftBlock when you traverse
+  // upwards from rightBlock
+  } else if (nsEditorUtils::IsDescendantOf(rightBlock, leftBlock,
+                                           &leftOffset)) {
+    // Tricky case.  Right block is inside left block.  Do ws adjustment.  This
+    // just destroys non-visible ws at boundaries we will be joining.
     res = nsWSRunObject::ScrubBlockBoundary(mHTMLEditor,
                                             nsWSRunObject::kBlockStart,
                                             rightBlock);
     NS_ENSURE_SUCCESS(res, res);
-    NS_ENSURE_STATE(mHTMLEditor);
     {
+      // We can't just track leftBlock because it's an Element, so track
+      // something else.
+      nsCOMPtr<nsINode> trackingLeftBlock(leftBlock);
       nsAutoTrackDOMPoint tracker(mHTMLEditor->mRangeUpdater,
-                                  address_of(aLeftBlock), &leftOffset);
-      nsCOMPtr<nsINode> leftBlock(do_QueryInterface(aLeftBlock));
+                                  address_of(trackingLeftBlock), &leftOffset);
       res = nsWSRunObject::ScrubBlockBoundary(mHTMLEditor,
                                               nsWSRunObject::kBeforeBlock,
                                               leftBlock, leftOffset);
       NS_ENSURE_SUCCESS(res, res);
+      if (trackingLeftBlock->IsElement()) {
+        leftBlock = trackingLeftBlock->AsElement();
+      } else {
+        NS_ENSURE_STATE(trackingLeftBlock->GetParentElement());
+        leftBlock = trackingLeftBlock->GetParentElement();
+      }
     }
     // Do br adjustment.
     nsCOMPtr<nsIDOMNode> brNode;
-    res = CheckForInvisibleBR(aLeftBlock, kBeforeBlock, address_of(brNode),
-                              leftOffset);
+    res = CheckForInvisibleBR(GetAsDOMNode(leftBlock), kBeforeBlock,
+                              address_of(brNode), leftOffset);
     NS_ENSURE_SUCCESS(res, res);
-    if (bMergeLists)
-    {
-      res = MoveContents(rightList, leftList, &leftOffset);
-    }
-    else
-    {
+    if (mergeLists) {
+      res = MoveContents(GetAsDOMNode(rightList), GetAsDOMNode(leftList),
+                         &leftOffset);
+    } else {
       // Left block is a parent of right block, and the parent of the previous
       // visible content.  Right block is a child and contains the contents we
       // want to move.
 
       int32_t previousContentOffset;
-      nsCOMPtr<nsIDOMNode> previousContentParent;
-
-      if (aLeftNode == aLeftBlock) {
+      nsCOMPtr<nsINode> previousContentParent;
+
+      if (&aLeftNode == leftBlock) {
         // We are working with valid HTML, aLeftNode is a block node, and is
-        // therefore allowed to contain aRightBlock.  This is the simple case,
-        // we will simply move the content in aRightBlock out of its block.
-        previousContentParent = aLeftBlock;
+        // therefore allowed to contain rightBlock.  This is the simple case,
+        // we will simply move the content in rightBlock out of its block.
+        previousContentParent = leftBlock;
         previousContentOffset = leftOffset;
       } else {
         // We try to work as well as possible with HTML that's already invalid.
         // Although "right block" is a block, and a block must not be contained
         // in inline elements, reality is that broken documents do exist.  The
         // DIRECT parent of "left NODE" might be an inline element.  Previous
         // versions of this code skipped inline parents until the first block
         // parent was found (and used "left block" as the destination).
         // However, in some situations this strategy moves the content to an
         // unexpected position.  (see bug 200416) The new idea is to make the
         // moving content a sibling, next to the previous visible content.
 
-        previousContentParent =
-          nsEditor::GetNodeLocation(aLeftNode, &previousContentOffset);
+        previousContentParent = aLeftNode.GetParentNode();
+        previousContentOffset = previousContentParent ?
+          previousContentParent->IndexOf(&aLeftNode) : -1;
 
         // We want to move our content just after the previous visible node.
         previousContentOffset++;
       }
 
       // Because we don't want the moving content to receive the style of the
       // previous content, we split the previous content's style.
 
-      NS_ENSURE_STATE(mHTMLEditor);
-      nsCOMPtr<nsINode> editorRoot = mHTMLEditor->GetEditorRoot();
-      if (!editorRoot || aLeftNode != editorRoot->AsDOMNode()) {
-        nsCOMPtr<nsIDOMNode> splittedPreviousContent;
-        NS_ENSURE_STATE(mHTMLEditor);
-        res = mHTMLEditor->SplitStyleAbovePoint(address_of(previousContentParent),
+      nsCOMPtr<Element> editorRoot = mHTMLEditor->GetEditorRoot();
+      if (!editorRoot || &aLeftNode != editorRoot) {
+        nsCOMPtr<nsIDOMNode> previousContentParentDOM =
+          GetAsDOMNode(previousContentParent);
+        nsCOMPtr<nsIDOMNode> splittedPreviousContentDOM;
+        res = mHTMLEditor->SplitStyleAbovePoint(address_of(previousContentParentDOM),
                                                 &previousContentOffset,
                                                 nullptr, nullptr, nullptr,
-                                                address_of(splittedPreviousContent));
+                                                address_of(splittedPreviousContentDOM));
         NS_ENSURE_SUCCESS(res, res);
-
-        if (splittedPreviousContent) {
-          previousContentParent =
-            nsEditor::GetNodeLocation(splittedPreviousContent,
-                                      &previousContentOffset);
+        previousContentParent = do_QueryInterface(previousContentParentDOM);
+        NS_ENSURE_STATE(previousContentParent || !previousContentParentDOM);
+
+        if (splittedPreviousContentDOM) {
+          nsCOMPtr<nsINode> splittedPreviousContent =
+            do_QueryInterface(splittedPreviousContentDOM);
+          NS_ENSURE_STATE(splittedPreviousContent ||
+                          !splittedPreviousContentDOM);
+          previousContentParent = splittedPreviousContent->GetParentNode();
+          previousContentOffset = previousContentParent ?
+            previousContentParent->IndexOf(splittedPreviousContent) : -1;
         }
       }
 
-      res = MoveBlock(previousContentParent, aRightBlock,
+      res = MoveBlock(GetAsDOMNode(previousContentParent),
+                      GetAsDOMNode(rightBlock),
                       previousContentOffset, rightOffset);
-    }
-    NS_ENSURE_STATE(mHTMLEditor);
-    if (brNode) mHTMLEditor->DeleteNode(brNode);
-  }
-  else
-  {
-    // normal case.  blocks are siblings, or at least close enough to siblings.  An example
-    // of the latter is a <p>paragraph</p><ul><li>one<li>two<li>three</ul>.  The first
-    // li and the p are not true siblings, but we still want to join them if you backspace
-    // from li into p.
-
-    // adjust whitespace at block boundaries
-    NS_ENSURE_STATE(mHTMLEditor);
-    nsCOMPtr<Element> leftBlock(do_QueryInterface(aLeftBlock));
-    nsCOMPtr<Element> rightBlock(do_QueryInterface(aRightBlock));
+      NS_ENSURE_SUCCESS(res, res);
+    }
+    if (brNode) {
+      mHTMLEditor->DeleteNode(brNode);
+    }
+  } else {
+    // Normal case.  Blocks are siblings, or at least close enough.  An example
+    // of the latter is <p>paragraph</p><ul><li>one<li>two<li>three</ul>.  The
+    // first li and the p are not true siblings, but we still want to join them
+    // if you backspace from li into p.
+
+    // Adjust whitespace at block boundaries
     res = nsWSRunObject::PrepareToJoinBlocks(mHTMLEditor, leftBlock, rightBlock);
     NS_ENSURE_SUCCESS(res, res);
     // Do br adjustment.
     nsCOMPtr<nsIDOMNode> brNode;
-    res = CheckForInvisibleBR(aLeftBlock, kBlockEnd, address_of(brNode));
+    res = CheckForInvisibleBR(GetAsDOMNode(leftBlock), kBlockEnd,
+                              address_of(brNode));
     NS_ENSURE_SUCCESS(res, res);
-    NS_ENSURE_STATE(mHTMLEditor);
-    if (bMergeLists || mHTMLEditor->NodesSameType(aLeftBlock, aRightBlock)) {
-      // nodes are same type.  merge them.
+    if (mergeLists || leftBlock->NodeI