merge autoland to mozilla-central a=merge
authorCarsten "Tomcat" Book <cbook@mozilla.com>
Mon, 24 Jul 2017 12:58:19 +0200
changeset 419205 758016d026077ebaf65aaecc8df437a7164dd9b5
parent 419161 5928d905c0bc0b28f5488b236444c7d7991cf8d4 (current diff)
parent 419204 61f7864e2701dfd68b233aa8dfd69c012b2b2424 (diff)
child 419224 60a5308fa987676fa5ed9fd5b3ad6c9938af0539
push id7566
push usermtabara@mozilla.com
push dateWed, 02 Aug 2017 08:25:16 +0000
treeherdermozilla-beta@86913f512c3c [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmerge
milestone56.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
merge autoland to mozilla-central a=merge
dom/media/AbstractMediaDecoder.h
dom/media/webaudio/BufferDecoder.cpp
dom/media/webaudio/BufferDecoder.h
--- a/browser/base/content/test/general/browser_accesskeys.js
+++ b/browser/base/content/test/general/browser_accesskeys.js
@@ -42,28 +42,98 @@ add_task(async function() {
 
   focusedId = await performAccessKey("y");
   is(focusedId, "tab2button", "button accesskey in tab2");
 
   // Press the accesskey for the chrome element while the content document is focused.
   focusedId = await performAccessKeyForChrome("z");
   is(focusedId, "chromebutton", "chromebutton accesskey");
 
-  newButton.remove();
-
   gBrowser.removeTab(tab1);
   gBrowser.removeTab(tab2);
+
+  // Test whether access key for the newButton isn't available when content
+  // consumes the key event.
+
+  // When content in the tab3 consumes all keydown events.
+  const gPageURL3 = "data:text/html,<body id='tab3body'>" +
+                    "<button id='tab3button' accesskey='y'>Button in Tab 3</button>" +
+                    "<script>" +
+                    "document.body.addEventListener('keydown', (event)=>{ event.preventDefault(); });" +
+                    "</script></body>";
+  let tab3 = await BrowserTestUtils.openNewForegroundTab(gBrowser, gPageURL3);
+  tab3.linkedBrowser.messageManager.loadFrameScript("data:,(" + childHandleFocus.toString() + ")();", false);
+
+  Services.focus.clearFocus(window);
+
+  focusedId = await performAccessKey("y");
+  is(focusedId, "tab3button", "button accesskey in tab3 should be focused");
+
+  newButton.onfocus = () => {
+    ok(false, "chromebutton shouldn't get focus during testing with tab3");
+  }
+
+  // Press the accesskey for the chrome element while the content document is focused.
+  focusedId = await performAccessKey("z");
+  is(focusedId, "tab3body", "button accesskey in tab3 should keep having focus");
+
+  newButton.onfocus = null;
+
+  gBrowser.removeTab(tab3);
+
+  // When content in the tab4 consumes all keypress events.
+  const gPageURL4 = "data:text/html,<body id='tab4body'>" +
+                    "<button id='tab4button' accesskey='y'>Button in Tab 4</button>" +
+                    "<script>" +
+                    "document.body.addEventListener('keypress', (event)=>{ event.preventDefault(); });" +
+                    "</script></body>";
+  let tab4 = await BrowserTestUtils.openNewForegroundTab(gBrowser, gPageURL4);
+  tab4.linkedBrowser.messageManager.loadFrameScript("data:,(" + childHandleFocus.toString() + ")();", false);
+
+  Services.focus.clearFocus(window);
+
+  focusedId = await performAccessKey("y");
+  is(focusedId, "tab4button", "button accesskey in tab4 should be focused");
+
+  newButton.onfocus = () => {
+    // EventStateManager handles accesskey before dispatching keypress event
+    // into the DOM tree, therefore, chrome accesskey always wins focus from
+    // content. However, this is different from shortcut keys.
+    todo(false, "chromebutton shouldn't get focus during testing with tab4");
+  }
+
+  // Press the accesskey for the chrome element while the content document is focused.
+  focusedId = await performAccessKey("z");
+  is(focusedId, "tab4body", "button accesskey in tab4 should keep having focus");
+
+  newButton.onfocus = null;
+
+  gBrowser.removeTab(tab4);
+
+  newButton.remove();
 });
 
 function childHandleFocus() {
+  var sent = false;
   content.document.body.firstChild.addEventListener("focus", function focused(event) {
+    sent = true;
     let focusedElement = content.document.activeElement;
     focusedElement.blur();
     sendAsyncMessage("Test:FocusFromAccessKey", { focus: focusedElement.id })
   }, true);
+  content.document.body.addEventListener("keydown", function keydown(event) {
+    sent = false;
+  }, true);
+  content.document.body.addEventListener("keyup", function keyup(event) {
+    if (!sent) {
+      sent = true;
+      let focusedElement = content.document.activeElement;
+      sendAsyncMessage("Test:FocusFromAccessKey", { focus: focusedElement.id });
+    }
+  });
 }
 
 function performAccessKey(key) {
   return new Promise(resolve => {
     let mm = gBrowser.selectedBrowser.messageManager;
     mm.addMessageListener("Test:FocusFromAccessKey", function listenForFocus(msg) {
       mm.removeMessageListener("Test:FocusFromAccessKey", listenForFocus);
       resolve(msg.data.focus);
--- a/browser/components/extensions/ext-menus.js
+++ b/browser/components/extensions/ext-menus.js
@@ -298,69 +298,47 @@ var gMenuBuilder = {
   itemsToCleanUp: new Set(),
 };
 
 // Called from pageAction or browserAction popup.
 global.actionContextMenu = function(contextData) {
   gMenuBuilder.buildActionContextMenu(contextData);
 };
 
+const contextsMap = {
+  onAudio: "audio",
+  onEditableArea: "editable",
+  inFrame: "frame",
+  onImage: "image",
+  onLink: "link",
+  onPassword: "password",
+  isTextSelected: "selection",
+  onVideo: "video",
+
+  onBrowserAction: "browser_action",
+  onPageAction: "page_action",
+  onTab: "tab",
+  inToolsMenu: "tools_menu",
+};
+
 const getMenuContexts = contextData => {
   let contexts = new Set();
 
-  if (contextData.inFrame) {
-    contexts.add("frame");
-  }
-
-  if (contextData.isTextSelected) {
-    contexts.add("selection");
-  }
-
-  if (contextData.onLink) {
-    contexts.add("link");
-  }
-
-  if (contextData.onEditableArea) {
-    contexts.add("editable");
-  }
-
-  if (contextData.onPassword) {
-    contexts.add("password");
-  }
-
-  if (contextData.onImage) {
-    contexts.add("image");
-  }
-
-  if (contextData.onVideo) {
-    contexts.add("video");
-  }
-
-  if (contextData.onAudio) {
-    contexts.add("audio");
-  }
-
-  if (contextData.onPageAction) {
-    contexts.add("page_action");
-  }
-
-  if (contextData.onBrowserAction) {
-    contexts.add("browser_action");
-  }
-
-  if (contextData.onTab) {
-    contexts.add("tab");
+  for (const [key, value] of Object.entries(contextsMap)) {
+    if (contextData[key]) {
+      contexts.add(value);
+    }
   }
 
   if (contexts.size === 0) {
     contexts.add("page");
   }
 
   // New non-content contexts supported in Firefox are not part of "all".
-  if (!contextData.onTab) {
+  if (!contextData.onTab && !contextData.inToolsMenu) {
     contexts.add("all");
   }
 
   return contexts;
 };
 
 function MenuItem(extension, createProperties, isRoot = false) {
   this.extension = extension;
@@ -577,47 +555,58 @@ MenuItem.prototype = {
       }
     }
 
     return true;
   },
 };
 
 // While any extensions are active, this Tracker registers to observe/listen
-// for contex-menu events from both content and chrome.
+// for menu events from both Tools and context menus, both content and chrome.
 const menuTracker = {
+  menuIds: ["menu_ToolsPopup", "tabContextMenu"],
+
   register() {
     Services.obs.addObserver(this, "on-build-contextmenu");
     for (const window of windowTracker.browserWindows()) {
       this.onWindowOpen(window);
     }
     windowTracker.addOpenListener(this.onWindowOpen);
   },
 
   unregister() {
     Services.obs.removeObserver(this, "on-build-contextmenu");
     for (const window of windowTracker.browserWindows()) {
-      const menu = window.document.getElementById("tabContextMenu");
-      menu.removeEventListener("popupshowing", this);
+      for (const id of this.menuIds) {
+        const menu = window.document.getElementById(id);
+        menu.removeEventListener("popupshowing", this);
+      }
     }
     windowTracker.removeOpenListener(this.onWindowOpen);
   },
 
   observe(subject, topic, data) {
     subject = subject.wrappedJSObject;
     gMenuBuilder.build(subject);
   },
 
   onWindowOpen(window) {
-    const menu = window.document.getElementById("tabContextMenu");
-    menu.addEventListener("popupshowing", menuTracker);
+    for (const id of this.menuIds) {
+      const menu = window.document.getElementById(id);
+      menu.addEventListener("popupshowing", menuTracker);
+    }
   },
 
   handleEvent(event) {
     const menu = event.target;
+    if (menu.id === "menu_ToolsPopup") {
+      const tab = tabTracker.activeTab;
+      const pageUrl = tab.linkedBrowser.currentURI.spec;
+      gMenuBuilder.build({menu, tab, pageUrl, inToolsMenu: true});
+    }
     if (menu.id === "tabContextMenu") {
       const trigger = menu.triggerNode;
       const tab = trigger.localName === "tab" ? trigger : tabTracker.activeTab;
       const pageUrl = tab.linkedBrowser.currentURI.spec;
       gMenuBuilder.build({menu, tab, pageUrl, onTab: true});
     }
   },
 };
--- a/browser/components/extensions/schemas/menus.json
+++ b/browser/components/extensions/schemas/menus.json
@@ -17,34 +17,42 @@
         }]
       }
     ]
   },
   {
     "namespace": "contextMenus",
     "permissions": ["contextMenus"],
     "description": "Use the browser.contextMenus API to add items to the browser's context menu. You can choose what types of objects your context menu additions apply to, such as images, hyperlinks, and pages.",
-    "$import": "menus"
+    "$import": "menus",
+    "types": [
+      {
+        "id": "ContextType",
+        "type": "string",
+        "enum": ["all", "page", "frame", "selection", "link", "editable", "password", "image", "video", "audio", "launcher", "browser_action", "page_action", "tab"],
+        "description": "The different contexts a menu can appear in. Specifying 'all' is equivalent to the combination of all other contexts except for 'tab' and 'tools_menu'."
+      }
+    ]
   },
   {
     "namespace": "menus",
     "permissions": ["menus"],
     "description": "Use the browser.menus API to add items to the browser's menus. You can choose what types of objects your context menu additions apply to, such as images, hyperlinks, and pages.",
     "properties": {
       "ACTION_MENU_TOP_LEVEL_LIMIT": {
         "value": 6,
         "description": "The maximum number of top level extension items that can be added to an extension action context menu. Any items beyond this limit will be ignored."
       }
     },
     "types": [
       {
         "id": "ContextType",
         "type": "string",
-        "enum": ["all", "page", "frame", "selection", "link", "editable", "password", "image", "video", "audio", "launcher", "browser_action", "page_action", "tab"],
-        "description": "The different contexts a menu can appear in. Specifying 'all' is equivalent to the combination of all other contexts except for 'tab'."
+        "enum": ["all", "page", "frame", "selection", "link", "editable", "password", "image", "video", "audio", "launcher", "browser_action", "page_action", "tab", "tools_menu"],
+        "description": "The different contexts a menu can appear in. Specifying 'all' is equivalent to the combination of all other contexts except for 'tab' and 'tools_menu'."
       },
       {
         "id": "ItemType",
         "type": "string",
         "enum": ["normal", "checkbox", "radio", "separator"],
         "description": "The type of menu item."
       },
       {
--- a/browser/components/extensions/test/browser/browser_ext_menus.js
+++ b/browser/components/extensions/test/browser/browser_ext_menus.js
@@ -250,8 +250,64 @@ add_task(async function test_multiple_co
   await closeExtensionContextMenu(popup.firstChild);
 
   const info = await extension.awaitMessage("click");
   is(info.menuItemId, "child", "onClicked the correct item");
 
   await BrowserTestUtils.removeTab(tab);
   await extension.unload();
 });
+
+add_task(async function test_tools_menu() {
+  const first = ExtensionTestUtils.loadExtension({
+    manifest: {
+      permissions: ["menus"],
+    },
+    async background() {
+      await browser.menus.create({title: "alpha", contexts: ["tools_menu"]});
+      await browser.menus.create({title: "beta", contexts: ["tools_menu"]});
+      browser.test.sendMessage("ready");
+    },
+  });
+
+  const second = ExtensionTestUtils.loadExtension({
+    manifest: {
+      permissions: ["menus"],
+    },
+    async background() {
+      await browser.menus.create({title: "gamma", contexts: ["tools_menu"]});
+      browser.menus.onClicked.addListener((info, tab) => {
+        browser.test.sendMessage("click", {info, tab});
+      });
+
+      const [tab] = await browser.tabs.query({active: true});
+      browser.test.sendMessage("ready", tab.id);
+    },
+  });
+
+  const tab = await BrowserTestUtils.openNewForegroundTab(gBrowser, "http://example.com/");
+  await first.startup();
+  await second.startup();
+
+  await first.awaitMessage("ready");
+  const tabId = await second.awaitMessage("ready");
+  const menu = await openToolsMenu();
+
+  const [separator, submenu, gamma] = Array.from(menu.children).slice(-3);
+  is(separator.tagName, "menuseparator", "Separator before first extension item");
+
+  is(submenu.tagName, "menu", "Correct submenu type");
+  is(submenu.getAttribute("label"), "Generated extension", "Correct submenu title");
+  is(submenu.firstChild.children.length, 2, "Correct number of submenu items");
+
+  is(gamma.tagName, "menuitem", "Third menu item type is correct");
+  is(gamma.getAttribute("label"), "gamma", "Third menu item label is correct");
+
+  closeToolsMenu(gamma);
+
+  const click = await second.awaitMessage("click");
+  is(click.info.pageUrl, "http://example.com/", "Click info pageUrl is correct");
+  is(click.tab.id, tabId, "Click event tab ID is correct");
+
+  await BrowserTestUtils.removeTab(tab);
+  await first.unload();
+  await second.unload();
+});
--- a/browser/components/extensions/test/browser/head.js
+++ b/browser/components/extensions/test/browser/head.js
@@ -8,16 +8,17 @@
  *          getBrowserActionPopup getPageActionPopup
  *          closeBrowserAction closePageAction
  *          promisePopupShown promisePopupHidden
  *          openContextMenu closeContextMenu
  *          openContextMenuInSidebar openContextMenuInPopup
  *          openExtensionContextMenu closeExtensionContextMenu
  *          openActionContextMenu openSubmenu closeActionContextMenu
  *          openTabContextMenu closeTabContextMenu
+ *          openToolsMenu closeToolsMenu
  *          imageBuffer imageBufferFromDataURI
  *          getListStyleImage getPanelForNode
  *          awaitExtensionPanel awaitPopupResize
  *          promiseContentDimensions alterContent
  *          promisePrefChangeObserved openContextMenuInFrame
  *          promiseAnimationFrame getCustomizableUIPanelID
  */
 
@@ -331,16 +332,45 @@ async function closeExtensionContextMenu
   let popupHiddenPromise = BrowserTestUtils.waitForEvent(contentAreaContextMenu, "popuphidden");
   EventUtils.synthesizeMouseAtCenter(itemToSelect, modifiers);
   await popupHiddenPromise;
 
   // Bug 1351638: parent menu fails to close intermittently, make sure it does.
   contentAreaContextMenu.hidePopup();
 }
 
+async function openToolsMenu(win = window) {
+  const node = win.document.getElementById("tools-menu");
+  const menu = win.document.getElementById("menu_ToolsPopup");
+  const shown = BrowserTestUtils.waitForEvent(menu, "popupshown");
+  if (AppConstants.platform === "macosx") {
+    // We can't open menubar items on OSX, so mocking instead.
+    menu.dispatchEvent(new MouseEvent("popupshowing"));
+    menu.dispatchEvent(new MouseEvent("popupshown"));
+  } else {
+    node.open = true;
+  }
+  await shown;
+  return menu;
+}
+
+function closeToolsMenu(itemToSelect, win = window) {
+  const menu = win.document.getElementById("menu_ToolsPopup");
+  const hidden = BrowserTestUtils.waitForEvent(menu, "popuphidden");
+  if (AppConstants.platform === "macosx") {
+    // Mocking on OSX, see above.
+    itemToSelect.doCommand();
+    menu.dispatchEvent(new MouseEvent("popuphiding"));
+    menu.dispatchEvent(new MouseEvent("popuphidden"));
+  } else {
+    EventUtils.synthesizeMouseAtCenter(itemToSelect, {}, win);
+  }
+  return hidden;
+}
+
 async function openChromeContextMenu(menuId, target, win = window) {
   const node = win.document.querySelector(target);
   const menu = win.document.getElementById(menuId);
   const shown = BrowserTestUtils.waitForEvent(menu, "popupshown");
   EventUtils.synthesizeMouseAtCenter(node, {type: "contextmenu"}, win);
   await shown;
   return menu;
 }
--- a/browser/extensions/onboarding/content/onboarding.js
+++ b/browser/extensions/onboarding/content/onboarding.js
@@ -456,16 +456,18 @@ class Onboarding {
     switch (evt.target.id) {
       case "onboarding-overlay-button":
       case "onboarding-overlay-close-btn":
       // If the clicking target is directly on the outer-most overlay,
       // that means clicking outside the tour content area.
       // Let's toggle the overlay.
       case "onboarding-overlay":
         this.toggleOverlay();
+        let selectedTour = this._tours.find(tour => !this.isTourCompleted(tour.id)) || this._tours[0];
+        this.gotoPage(selectedTour.id);
         break;
       case "onboarding-notification-close-btn":
         this.hideNotification();
         this._removeTourFromNotificationQueue(this._notificationBar.dataset.targetTourId);
         break;
       case "onboarding-notification-action-btn":
         let tourId = this._notificationBar.dataset.targetTourId;
         this.toggleOverlay();
@@ -828,25 +830,25 @@ class Onboarding {
 
       div.id = `${tour.id}-page`;
       div.classList.add("onboarding-tour-page");
       div.style.display = "none";
       pagesFrag.appendChild(div);
       // Cache elements in arrays for later use to avoid cost of querying elements
       this._tourItems.push(li);
       this._tourPages.push(div);
+
+      this.markTourCompletionState(tour.id);
     }
-    tours.forEach(tour => this.markTourCompletionState(tour.id));
 
     let dialog = this._window.document.getElementById("onboarding-overlay-dialog");
     let ul = this._window.document.getElementById("onboarding-tour-list");
     ul.appendChild(itemsFrag);
     let footer = this._window.document.getElementById("onboarding-footer");
     dialog.insertBefore(pagesFrag, footer);
-    this.gotoPage(tours[0].id);
   }
 
   _loadCSS() {
     // Returning a Promise so we can inform caller of loading complete
     // by resolving it.
     return new Promise(resolve => {
       let doc = this._window.document;
       let link = doc.createElement("link");
--- a/browser/themes/shared/privatebrowsing/aboutPrivateBrowsing.css
+++ b/browser/themes/shared/privatebrowsing/aboutPrivateBrowsing.css
@@ -1,16 +1,16 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 @import url("chrome://global/skin/in-content/info-pages.css");
 
 :root {
-  --color-grey: #b1b1b1;
+  --color-grey: #505473;
 }
 
 html.private {
   --in-content-page-color: white;
   --in-content-text-color: white;
   --in-content-page-background: #25003e;
 }
 
@@ -117,47 +117,48 @@ a.button {
   width: 0;
   pointer-events: none;
   position: absolute;
 }
 
 .toggle + .toggle-btn {
   box-sizing: border-box;
   cursor: pointer;
-  min-width: 42px;
-  height: 26px;
+  min-width: 48px;
+  height: 27px;
   border-radius: 13px;
   background-color: var(--color-grey);
-  padding: 1px;
+  border: 1px solid #202340;
 }
 
 .toggle + .toggle-btn::after {
   position: relative;
   display: block;
   content: "";
-  width: 24px;
+  width: 25px;
   height: 100%;
   left: 0;
   border-radius: 50%;
   background: white;
   transition: left .2s ease;
 }
 
 .toggle + .toggle-btn:dir(rtl)::after {
   left: auto;
   right: 0;
   transition-property: right;
 }
 
 .toggle:checked + .toggle-btn {
   background: #16da00;
+  border-color: #0CA700;
 }
 
 .toggle:checked + .toggle-btn::after {
-  left: 16px;
+  left: 21px;
 }
 
 .toggle:checked + .toggle-btn:dir(rtl)::after {
   left: auto;
   right: 16px;
 }
 
 .toggle:-moz-focusring + .toggle-btn {
--- a/devtools/client/commandline/test/helpers.js
+++ b/devtools/client/commandline/test/helpers.js
@@ -19,16 +19,17 @@
 // A copy of this code exists in firefox mochitests. They should be kept
 // in sync. Hence the exports synonym for non AMD contexts.
 var { helpers, assert } = (function () {
 
   var helpers = {};
 
   var { require } = Cu.import("resource://devtools/shared/Loader.jsm", {});
   var { TargetFactory } = require("devtools/client/framework/target");
+  var { gDevToolsBrowser } = require("devtools/client/framework/devtools-browser");
   var Services = require("Services");
 
   var assert = { ok: ok, is: is, log: info };
   var util = require("gcli/util/util");
   var cli = require("gcli/cli");
   var KeyEvent = require("gcli/util/util").KeyEvent;
 
   const { GcliFront } = require("devtools/shared/fronts/gcli");
@@ -206,18 +207,18 @@ var { helpers, assert } = (function () {
  * - requisition
  * @return A promise which resolves to the options object when the 'load' event
  * happens on the new tab
  */
   helpers.openToolbar = function (options) {
     options = options || {};
     options.chromeWindow = options.chromeWindow || window;
 
-    return options.chromeWindow.DeveloperToolbar.show(true).then(function () {
-      var toolbar = options.chromeWindow.DeveloperToolbar;
+    var toolbar = gDevToolsBrowser.getDeveloperToolbar(options.chromeWindow);
+    return toolbar.show(true).then(function () {
       options.automator = createDeveloperToolbarAutomator(toolbar);
       options.requisition = toolbar.requisition;
       return options;
     });
   };
 
 /**
  * Navigate the current tab to a URL
@@ -238,17 +239,18 @@ var { helpers, assert } = (function () {
   });
 
 /**
  * Undo the effects of |helpers.openToolbar|
  * @param options The options object passed to |helpers.openToolbar|
  * @return A promise resolved (with undefined) when the toolbar is closed
  */
   helpers.closeToolbar = function (options) {
-    return options.chromeWindow.DeveloperToolbar.hide().then(function () {
+    var toolbar = gDevToolsBrowser.getDeveloperToolbar(options.chromeWindow).hide();
+    return toolbar.then(function () {
       delete options.automator;
       delete options.requisition;
     });
   };
 
 /**
  * A helper to work with Task.spawn so you can do:
  *   return Task.spawn(realTestFunc).then(finish, helpers.handleError);
@@ -318,28 +320,28 @@ var { helpers, assert } = (function () {
  * As addTab, but that also opens the developer toolbar. In addition a new
  * 'automator' property is added to the options object which uses the
  * developer toolbar
  */
   helpers.addTabWithToolbar = function (url, callback, options) {
     return helpers.addTab(url, function (innerOptions) {
       var win = innerOptions.chromeWindow;
 
-      return win.DeveloperToolbar.show(true).then(function () {
-        var toolbar = win.DeveloperToolbar;
+      var toolbar = gDevToolsBrowser.getDeveloperToolbar(win);
+      return toolbar.show(true).then(function () {
         innerOptions.automator = createDeveloperToolbarAutomator(toolbar);
         innerOptions.requisition = toolbar.requisition;
 
         var reply = callback.call(null, innerOptions);
 
         return Promise.resolve(reply).catch(function (error) {
           ok(false, error);
           console.error(error);
         }).then(function () {
-          win.DeveloperToolbar.hide().then(function () {
+          toolbar.hide().then(function () {
             delete innerOptions.automator;
           });
         });
       });
     }, options);
   };
 
 /**
--- a/devtools/client/framework/devtools-browser.js
+++ b/devtools/client/framework/devtools-browser.js
@@ -19,16 +19,17 @@ const {gDevTools} = require("./devtools"
 
 // Load target and toolbox lazily as they need gDevTools to be fully initialized
 loader.lazyRequireGetter(this, "TargetFactory", "devtools/client/framework/target", true);
 loader.lazyRequireGetter(this, "Toolbox", "devtools/client/framework/toolbox", true);
 loader.lazyRequireGetter(this, "DebuggerServer", "devtools/server/main", true);
 loader.lazyRequireGetter(this, "DebuggerClient", "devtools/shared/client/main", true);
 loader.lazyRequireGetter(this, "BrowserMenus", "devtools/client/framework/browser-menus");
 loader.lazyRequireGetter(this, "appendStyleSheet", "devtools/client/shared/stylesheet-utils", true);
+loader.lazyRequireGetter(this, "DeveloperToolbar", "devtools/client/shared/developer-toolbar", true);
 
 loader.lazyImporter(this, "CustomizableUI", "resource:///modules/CustomizableUI.jsm");
 loader.lazyImporter(this, "CustomizableWidgets", "resource:///modules/CustomizableWidgets.jsm");
 loader.lazyImporter(this, "AppConstants", "resource://gre/modules/AppConstants.jsm");
 loader.lazyImporter(this, "LightweightThemeManager", "resource://gre/modules/LightweightThemeManager.jsm");
 
 const {LocalizationHelper} = require("devtools/shared/l10n");
 const L10N = new LocalizationHelper("devtools/client/locales/toolbox.properties");
@@ -50,16 +51,21 @@ var gDevToolsBrowser = exports.gDevTools
   _trackedBrowserWindows: new Set(),
 
   /**
    * WeakMap keeping track of the devtools-browser stylesheets loaded in the various
    * tracked windows.
    */
   _browserStyleSheets: new WeakMap(),
 
+  /**
+   * WeakMap keeping track of DeveloperToolbar instances for each firefox window.
+   */
+  _toolbars: new WeakMap(),
+
   _tabStats: {
     peakOpen: 0,
     peakPinned: 0,
     histOpen: [],
     histPinned: []
   },
 
   /**
@@ -103,17 +109,17 @@ var gDevToolsBrowser = exports.gDevTools
     toggleMenuItem("menu_devToolbar", devToolbarEnabled);
     let focusEl = doc.getElementById("menu_devToolbar");
     if (devToolbarEnabled) {
       focusEl.removeAttribute("disabled");
     } else {
       focusEl.setAttribute("disabled", "true");
     }
     if (devToolbarEnabled && Services.prefs.getBoolPref("devtools.toolbar.visible")) {
-      win.DeveloperToolbar.show(false).catch(console.error);
+      this.getDeveloperToolbar(win).show(false).catch(console.error);
     }
 
     // Enable WebIDE?
     let webIDEEnabled = Services.prefs.getBoolPref("devtools.webide.enabled");
     toggleMenuItem("menu_webide", webIDEEnabled);
 
     if (webIDEEnabled) {
       gDevToolsBrowser.installWebIDEWidget();
@@ -494,36 +500,46 @@ var gDevToolsBrowser = exports.gDevTools
     gDevToolsBrowser._trackedBrowserWindows.add(win);
 
     BrowserMenus.addMenus(win.document);
 
     // Register the Developer widget in the Hamburger menu or navbar
     // only once menus are registered as it depends on it.
     gDevToolsBrowser.installDeveloperWidget();
 
-    // Inject lazily DeveloperToolbar on the chrome window
-    loader.lazyGetter(win, "DeveloperToolbar", function () {
-      let { DeveloperToolbar } = require("devtools/client/shared/developer-toolbar");
-      return new DeveloperToolbar(win);
-    });
-
     this.updateCommandAvailability(win);
     this.updateDevtoolsThemeAttribute(win);
     this.ensurePrefObserver();
     win.addEventListener("unload", this);
 
     let tabContainer = win.gBrowser.tabContainer;
     tabContainer.addEventListener("TabSelect", this);
     tabContainer.addEventListener("TabOpen", this);
     tabContainer.addEventListener("TabClose", this);
     tabContainer.addEventListener("TabPinned", this);
     tabContainer.addEventListener("TabUnpinned", this);
   },
 
   /**
+   * Create singleton instance of the developer toolbar for a given top level window.
+   *
+   * @param {Window} win
+   *        The window to which the toolbar should be created.
+   */
+  getDeveloperToolbar(win) {
+    let toolbar = this._toolbars.get(win);
+    if (toolbar) {
+      return toolbar;
+    }
+    toolbar = new DeveloperToolbar(win);
+    this._toolbars.set(win, toolbar);
+    return toolbar;
+  },
+
+  /**
    * Hook the JS debugger tool to the "Debug Script" button of the slow script
    * dialog.
    */
   setSlowScriptDebugHandler() {
     let debugService = Cc["@mozilla.org/dom/slow-script-debug;1"]
                          .getService(Ci.nsISlowScriptDebug);
     let tm = Cc["@mozilla.org/thread-manager;1"].getService(Ci.nsIThreadManager);
 
@@ -722,21 +738,17 @@ var gDevToolsBrowser = exports.gDevTools
     }
 
     let styleSheet = this._browserStyleSheets.get(win);
     if (styleSheet) {
       styleSheet.remove();
       this._browserStyleSheets.delete(win);
     }
 
-    // Destroy the Developer toolbar if it has been accessed
-    let desc = Object.getOwnPropertyDescriptor(win, "DeveloperToolbar");
-    if (desc && !desc.get) {
-      win.DeveloperToolbar.destroy();
-    }
+    this._toolbars.delete(win);
 
     let tabContainer = win.gBrowser.tabContainer;
     tabContainer.removeEventListener("TabSelect", this);
     tabContainer.removeEventListener("TabOpen", this);
     tabContainer.removeEventListener("TabClose", this);
     tabContainer.removeEventListener("TabPinned", this);
     tabContainer.removeEventListener("TabUnpinned", this);
   },
--- a/devtools/client/menus.js
+++ b/devtools/client/menus.js
@@ -70,19 +70,19 @@ exports.menuitems = [
     l10nKey: "devToolbarMenu",
     disabled: true,
     oncommand(event) {
       let window = event.target.ownerDocument.defaultView;
       // Distinguish events when selecting a menuitem, where we either open
       // or close the toolbar and when hitting the key shortcut where we just
       // focus the toolbar if it doesn't already has it.
       if (event.target.tagName.toLowerCase() == "menuitem") {
-        window.DeveloperToolbar.toggle();
+        gDevToolsBrowser.getDeveloperToolbar(window).toggle();
       } else {
-        window.DeveloperToolbar.focusToggle();
+        gDevToolsBrowser.getDeveloperToolbar(window).focusToggle();
       }
     },
     key: {
       id: "devToolbar",
       modifiers: "shift"
     },
     checkbox: true
   },
--- a/devtools/client/shared/developer-toolbar.js
+++ b/devtools/client/shared/developer-toolbar.js
@@ -225,17 +225,19 @@ DeveloperToolbar.prototype.createToolbar
   }
   let toolbar = this._doc.createElement("toolbar");
   toolbar.setAttribute("id", "developer-toolbar");
   toolbar.setAttribute("hidden", "true");
 
   let close = this._doc.createElement("toolbarbutton");
   close.setAttribute("id", "developer-toolbar-closebutton");
   close.setAttribute("class", "close-icon");
-  close.setAttribute("oncommand", "DeveloperToolbar.hide();");
+  close.addEventListener("command", (event) => {
+    this.hide();
+  });
   let closeTooltip = L10N.getStr("toolbar.closeButton.tooltip");
   close.setAttribute("tooltiptext", closeTooltip);
 
   let stack = this._doc.createElement("stack");
   stack.setAttribute("flex", "1");
 
   let input = this._doc.createElement("textbox");
   input.setAttribute("class", "gclitoolbar-input-node");
--- a/devtools/client/shared/test/.eslintrc.js
+++ b/devtools/client/shared/test/.eslintrc.js
@@ -1,9 +1,6 @@
 "use strict";
 
 module.exports = {
   // Extend from the shared list of defined globals for mochitests.
   "extends": "../../../.eslintrc.mochitests.js",
-  "globals": {
-    "DeveloperToolbar": true
-  }
 };
--- a/devtools/client/shared/test/browser_toolbar_basic.js
+++ b/devtools/client/shared/test/browser_toolbar_basic.js
@@ -1,27 +1,30 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 "use strict";
 
 // Tests that the developer toolbar works properly
 
+const {gDevToolsBrowser} = require("devtools/client/framework/devtools-browser");
+
 const TEST_URI = TEST_URI_ROOT + "doc_toolbar_basic.html";
 
 add_task(function* () {
   info("Starting browser_toolbar_basic.js");
   yield addTab(TEST_URI);
 
-  ok(!DeveloperToolbar.visible, "DeveloperToolbar is not visible in to start");
+  let toolbar = gDevToolsBrowser.getDeveloperToolbar(window);
+  ok(!toolbar.visible, "DeveloperToolbar is not visible in to start");
 
-  let shown = oneTimeObserve(DeveloperToolbar.NOTIFICATIONS.SHOW);
+  let shown = oneTimeObserve(toolbar.NOTIFICATIONS.SHOW);
   document.getElementById("menu_devToolbar").doCommand();
   yield shown;
-  ok(DeveloperToolbar.visible, "DeveloperToolbar is visible in checkOpen");
+  ok(toolbar.visible, "DeveloperToolbar is visible in checkOpen");
 
   let close = document.getElementById("developer-toolbar-closebutton");
   ok(close, "Close button exists");
 
   let toggleToolbox =
     document.getElementById("menu_devToolbox");
   ok(!isChecked(toggleToolbox), "toggle toolbox button is not checked");
 
@@ -31,30 +34,30 @@ add_task(function* () {
 
   yield addTab("about:blank");
   info("Opened a new tab");
 
   ok(!isChecked(toggleToolbox), "toggle toolbox button is not checked");
 
   gBrowser.removeCurrentTab();
 
-  let hidden = oneTimeObserve(DeveloperToolbar.NOTIFICATIONS.HIDE);
+  let hidden = oneTimeObserve(toolbar.NOTIFICATIONS.HIDE);
   document.getElementById("menu_devToolbar").doCommand();
   yield hidden;
-  ok(!DeveloperToolbar.visible, "DeveloperToolbar is not visible in hidden");
+  ok(!toolbar.visible, "DeveloperToolbar is not visible in hidden");
 
-  shown = oneTimeObserve(DeveloperToolbar.NOTIFICATIONS.SHOW);
+  shown = oneTimeObserve(toolbar.NOTIFICATIONS.SHOW);
   document.getElementById("menu_devToolbar").doCommand();
   yield shown;
-  ok(DeveloperToolbar.visible, "DeveloperToolbar is visible in after open");
+  ok(toolbar.visible, "DeveloperToolbar is visible in after open");
 
   ok(isChecked(toggleToolbox), "toggle toolbox button is checked");
 
-  hidden = oneTimeObserve(DeveloperToolbar.NOTIFICATIONS.HIDE);
+  hidden = oneTimeObserve(toolbar.NOTIFICATIONS.HIDE);
   document.getElementById("developer-toolbar-closebutton").doCommand();
   yield hidden;
 
-  ok(!DeveloperToolbar.visible, "DeveloperToolbar is not visible after re-close");
+  ok(!toolbar.visible, "DeveloperToolbar is not visible after re-close");
 });
 
 function isChecked(b) {
   return b.getAttribute("checked") == "true";
 }
--- a/devtools/client/shared/test/browser_toolbar_tooltip.js
+++ b/devtools/client/shared/test/browser_toolbar_tooltip.js
@@ -1,99 +1,103 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 "use strict";
 
 // Tests that the developer toolbar works properly
 
+const {gDevToolsBrowser} = require("devtools/client/framework/devtools-browser");
+
 const TEST_URI = "data:text/html;charset=utf-8,<p>Tooltip Tests</p>";
 const PREF_DEVTOOLS_THEME = "devtools.theme";
 
 registerCleanupFunction(() => {
   // Set preferences back to their original values
   Services.prefs.clearUserPref(PREF_DEVTOOLS_THEME);
 });
 
+let toolbar = gDevToolsBrowser.getDeveloperToolbar(window);
+
 add_task(function* showToolbar() {
   yield addTab(TEST_URI);
 
   info("Starting browser_toolbar_tooltip.js");
 
-  ok(!DeveloperToolbar.visible, "DeveloperToolbar is not visible in runTest");
+  ok(!toolbar.visible, "DeveloperToolbar is not visible in runTest");
 
-  let showPromise = observeOnce(DeveloperToolbar.NOTIFICATIONS.SHOW);
+  let showPromise = observeOnce(toolbar.NOTIFICATIONS.SHOW);
   document.getElementById("menu_devToolbar").doCommand();
   yield showPromise;
 });
 
 add_task(function* testDimensions() {
-  let tooltipPanel = DeveloperToolbar.tooltipPanel;
+  let tooltipPanel = toolbar.tooltipPanel;
 
-  DeveloperToolbar.focusManager.helpRequest();
-  yield DeveloperToolbar.inputter.setInput("help help");
+  toolbar.focusManager.helpRequest();
+  yield toolbar.inputter.setInput("help help");
 
-  DeveloperToolbar.inputter.setCursor({ start: "help help".length });
+  toolbar.inputter.setCursor({ start: "help help".length });
   is(tooltipPanel._dimensions.start, "help ".length,
           "search param start, when cursor at end");
   ok(getLeftMargin() > 30, "tooltip offset, when cursor at end");
 
-  DeveloperToolbar.inputter.setCursor({ start: "help".length });
+  toolbar.inputter.setCursor({ start: "help".length });
   is(tooltipPanel._dimensions.start, 0,
           "search param start, when cursor at end of command");
   ok(getLeftMargin() > 9, "tooltip offset, when cursor at end of command");
 
-  DeveloperToolbar.inputter.setCursor({ start: "help help".length - 1 });
+  toolbar.inputter.setCursor({ start: "help help".length - 1 });
   is(tooltipPanel._dimensions.start, "help ".length,
           "search param start, when cursor at penultimate position");
   ok(getLeftMargin() > 30, "tooltip offset, when cursor at penultimate position");
 
-  DeveloperToolbar.inputter.setCursor({ start: 0 });
+  toolbar.inputter.setCursor({ start: 0 });
   is(tooltipPanel._dimensions.start, 0,
           "search param start, when cursor at start");
   ok(getLeftMargin() > 9, "tooltip offset, when cursor at start");
 });
 
 add_task(function* testThemes() {
-  let tooltipPanel = DeveloperToolbar.tooltipPanel;
+  let tooltipPanel = toolbar.tooltipPanel;
   ok(tooltipPanel.document, "Tooltip panel is initialized");
 
   Services.prefs.setCharPref(PREF_DEVTOOLS_THEME, "dark");
 
-  yield DeveloperToolbar.inputter.setInput("");
-  yield DeveloperToolbar.inputter.setInput("help help");
+  yield toolbar.inputter.setInput("");
+  yield toolbar.inputter.setInput("help help");
   is(tooltipPanel.document.documentElement.getAttribute("devtoolstheme"),
      "dark", "Tooltip panel has correct theme");
 
   Services.prefs.setCharPref(PREF_DEVTOOLS_THEME, "light");
 
-  yield DeveloperToolbar.inputter.setInput("");
-  yield DeveloperToolbar.inputter.setInput("help help");
+  yield toolbar.inputter.setInput("");
+  yield toolbar.inputter.setInput("help help");
   is(tooltipPanel.document.documentElement.getAttribute("devtoolstheme"),
      "light", "Tooltip panel has correct theme");
 });
 
 add_task(function* hideToolbar() {
   info("Ending browser_toolbar_tooltip.js");
-  yield DeveloperToolbar.inputter.setInput("");
+  yield toolbar.inputter.setInput("");
 
-  ok(DeveloperToolbar.visible, "DeveloperToolbar is visible in hideToolbar");
+  ok(toolbar.visible, "DeveloperToolbar is visible in hideToolbar");
 
   info("Hide toolbar");
-  let hidePromise = observeOnce(DeveloperToolbar.NOTIFICATIONS.HIDE);
+  let hidePromise = observeOnce(toolbar.NOTIFICATIONS.HIDE);
   document.getElementById("menu_devToolbar").doCommand();
   yield hidePromise;
 
-  ok(!DeveloperToolbar.visible, "DeveloperToolbar is not visible in hideToolbar");
+  ok(!toolbar.visible, "DeveloperToolbar is not visible in hideToolbar");
 
   info("Done test");
 });
 
 function getLeftMargin() {
-  let style = DeveloperToolbar.tooltipPanel._panel.style.marginLeft;
+  let style = toolbar.tooltipPanel._panel.style.marginLeft;
   return parseInt(style.slice(0, -2), 10);
 }
 
 function observeOnce(topic, ownsWeak = false) {
   return new Promise(function (resolve, reject) {
     let resolver = function (subject) {
       Services.obs.removeObserver(resolver, topic);
       resolve(subject);
--- a/devtools/client/shared/test/browser_toolbar_webconsole_errors_count.js
+++ b/devtools/client/shared/test/browser_toolbar_webconsole_errors_count.js
@@ -2,24 +2,28 @@
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 /* eslint-disable mozilla/no-cpows-in-tests */
 
 "use strict";
 
 // Tests that the developer toolbar errors count works properly.
 
+const {gDevToolsBrowser} = require("devtools/client/framework/devtools-browser");
+
 // Use the old webconsole since this is directly accessing old DOM, and
 // the error count isn't reset when pressing the clear button in new one
 // See Bug 1304794.
 Services.prefs.setBoolPref("devtools.webconsole.new-frontend-enabled", false);
 registerCleanupFunction(function* () {
   Services.prefs.clearUserPref("devtools.webconsole.new-frontend-enabled");
 });
 
+let toolbar = gDevToolsBrowser.getDeveloperToolbar(window);
+
 function test() {
   const TEST_URI = TEST_URI_ROOT + "doc_toolbar_webconsole_errors_count.html";
 
   let tab1, tab2, webconsole;
 
   Services.prefs.setBoolPref("javascript.options.strict", true);
 
   registerCleanupFunction(() => {
@@ -30,25 +34,25 @@ function test() {
   addTab(TEST_URI).then(openToolbar);
 
   function openToolbar(tab) {
     tab1 = tab;
     ignoreAllUncaughtExceptions(false);
 
     expectUncaughtException();
 
-    if (!DeveloperToolbar.visible) {
-      DeveloperToolbar.show(true).then(onOpenToolbar);
+    if (!toolbar.visible) {
+      toolbar.show(true).then(onOpenToolbar);
     } else {
       onOpenToolbar();
     }
   }
 
   function onOpenToolbar() {
-    ok(DeveloperToolbar.visible, "DeveloperToolbar is visible");
+    ok(toolbar.visible, "DeveloperToolbar is visible");
     webconsole = document.getElementById("developer-toolbar-toolbox-button");
 
     waitForButtonUpdate({
       name: "web console button shows page errors",
       errors: 3,
       warnings: 0,
       callback: addErrors,
     });
@@ -235,19 +239,19 @@ function test() {
         // Get out of the toolbar event execution loop.
         executeSoon(options.callback);
       }
       return result;
     }
 
     if (!check()) {
       info("wait for: " + options.name);
-      DeveloperToolbar.on("errors-counter-updated", function onUpdate(event) {
+      toolbar.on("errors-counter-updated", function onUpdate(event) {
         if (check()) {
-          DeveloperToolbar.off(event, onUpdate);
+          toolbar.off(event, onUpdate);
         }
       });
     }
   }
 
   function openWebConsole(tab, callback) {
     let target = TargetFactory.forTab(tab);
     gDevTools.showToolbox(target, "webconsole").then((toolbox) =>
--- a/devtools/client/webconsole/hudservice.js
+++ b/devtools/client/webconsole/hudservice.js
@@ -2,16 +2,17 @@
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 "use strict";
 
 var WebConsoleUtils = require("devtools/client/webconsole/utils").Utils;
 const {extend} = require("devtools/shared/extend");
 var {TargetFactory} = require("devtools/client/framework/target");
+var {gDevToolsBrowser} = require("devtools/client/framework/devtools-browser");
 var {Tools} = require("devtools/client/definitions");
 const { Task } = require("devtools/shared/task");
 var promise = require("promise");
 var Services = require("Services");
 loader.lazyRequireGetter(this, "Telemetry", "devtools/client/shared/telemetry");
 loader.lazyRequireGetter(this, "WebConsoleFrame", "devtools/client/webconsole/webconsole", true);
 loader.lazyRequireGetter(this, "NewWebConsoleFrame", "devtools/client/webconsole/new-webconsole", true);
 loader.lazyRequireGetter(this, "gDevTools", "devtools/client/framework/devtools", true);
@@ -384,17 +385,18 @@ WebConsole.prototype = {
 
   /**
    * The clear output button handler.
    * @private
    */
   _onClearButton: function WC__onClearButton()
   {
     if (this.target.isLocalTab) {
-      this.browserWindow.DeveloperToolbar.resetErrorsCount(this.target.tab);
+      gDevToolsBrowser.getDeveloperToolbar(this.browserWindow)
+        .resetErrorsCount(this.target.tab);
     }
   },
 
   /**
    * Alias for the WebConsoleFrame.setFilterState() method.
    * @see webconsole.js::WebConsoleFrame.setFilterState()
    */
   setFilterState: function WC_setFilterState()
--- a/dom/animation/test/mozilla/file_hide_and_show.html
+++ b/dom/animation/test/mozilla/file_hide_and_show.html
@@ -3,16 +3,21 @@
 <script src="../testcommon.js"></script>
 <style>
 @keyframes move {
   100% {
     transform: translateX(100px);
   }
 }
 
+div.pseudo::before {
+  animation: move 0.01s;
+  content: 'content';
+}
+
 </style>
 <body>
 <script>
 'use strict';
 
 test(function(t) {
   var div = addDiv(t, { style: 'animation: move 100s infinite' });
   assert_equals(div.getAnimations().length, 1,
@@ -152,11 +157,40 @@ test(function(t) {
                 'animations again');
 
   assert_not_equals(div.getAnimations()[0], animation,
                     'Restarted animation is a newly-generated animation');
 
 }, 'CSS Animation which has already finished starts playing when its parent ' +
    'element is shown from "display:none" state');
 
+promise_test(function(t) {
+  var div = addDiv(t, { 'class': 'pseudo' });
+  var eventWatcher = new EventWatcher(t, div, 'animationend');
+
+  assert_equals(document.getAnimations().length, 1,
+                'CSS animation on pseudo element');
+
+  return eventWatcher.wait_for('animationend').then(function() {
+    assert_equals(document.getAnimations().length, 0,
+                  'No CSS animation on pseudo element after the animation ' +
+                  'finished');
+
+    // Remove the class which generated this pseudo element.
+    div.classList.remove('pseudo');
+
+    // We need to wait for two frames to process re-framing.
+    // The callback of 'animationend' is processed just before rAF callbacks,
+    // and rAF callbacks are processed before re-framing process, so waiting for
+    // one rAF callback is not sufficient.
+    return waitForAnimationFrames(2);
+  }).then(function() {
+    // Add the class again to re-generate pseudo element.
+    div.classList.add('pseudo');
+    assert_equals(document.getAnimations().length, 1,
+                  'A new CSS animation on pseudo element');
+  });
+}, 'CSS animation on pseudo element restarts after the pseudo element that ' +
+   'had a finished CSS animation is re-generated');
+
 done();
 </script>
 </body>
--- a/dom/events/EventStateManager.cpp
+++ b/dom/events/EventStateManager.cpp
@@ -181,23 +181,16 @@ PrintDocTreeAll(nsIDocShellTreeItem* aIt
       break;
     item = parent;
   }
 
   PrintDocTree(item, 0);
 }
 #endif
 
-// mask values for ui.key.chromeAccess and ui.key.contentAccess
-#define NS_MODIFIER_SHIFT    1
-#define NS_MODIFIER_CONTROL  2
-#define NS_MODIFIER_ALT      4
-#define NS_MODIFIER_META     8
-#define NS_MODIFIER_OS       16
-
 /******************************************************************/
 /* mozilla::UITimerCallback                                       */
 /******************************************************************/
 
 class UITimerCallback final :
     public nsITimerCallback,
     public nsINamed
 {
@@ -763,40 +756,44 @@ EventStateManager::PreHandleEvent(nsPres
   case eDragOver:
     // Send the enter/exit events before eDrop.
     GenerateDragDropEnterExit(aPresContext, aEvent->AsDragEvent());
     break;
 
   case eKeyPress:
     {
       WidgetKeyboardEvent* keyEvent = aEvent->AsKeyboardEvent();
-
-      int32_t modifierMask = 0;
-      if (keyEvent->IsShift())
-        modifierMask |= NS_MODIFIER_SHIFT;
-      if (keyEvent->IsControl())
-        modifierMask |= NS_MODIFIER_CONTROL;
-      if (keyEvent->IsAlt())
-        modifierMask |= NS_MODIFIER_ALT;
-      if (keyEvent->IsMeta())
-        modifierMask |= NS_MODIFIER_META;
-      if (keyEvent->IsOS())
-        modifierMask |= NS_MODIFIER_OS;
-
-      // Prevent keyboard scrolling while an accesskey modifier is in use.
-      if (modifierMask) {
-        bool matchesContentAccessKey = (modifierMask == Prefs::ContentAccessModifierMask());
-
-        if (modifierMask == Prefs::ChromeAccessModifierMask() ||
-            matchesContentAccessKey) {
+      if (keyEvent->ModifiersMatchWithAccessKey(AccessKeyType::eChrome) ||
+          keyEvent->ModifiersMatchWithAccessKey(AccessKeyType::eContent)) {
+        // If the eKeyPress event will be sent to a remote process, this
+        // process needs to wait reply from the remote process for checking if
+        // preceding eKeyDown event is consumed.  If preceding eKeyDown event
+        // is consumed in the remote process, TabChild won't send the event
+        // back to this process.  So, only when this process receives a reply
+        // eKeyPress event in TabParent, we should handle accesskey in this
+        // process.
+        if (IsRemoteTarget(GetFocusedContent())) {
+          // However, if there is no accesskey target for the key combination,
+          // we don't need to wait reply from the remote process.  Otherwise,
+          // Mark the event as waiting reply from remote process and stop
+          // propagation in this process.
+          if (CheckIfEventMatchesAccessKey(keyEvent, aPresContext)) {
+            keyEvent->StopPropagation();
+            keyEvent->MarkAsWaitingReplyFromRemoteProcess();
+          }
+        }
+        // If the event target is in this process, we can handle accesskey now
+        // since if preceding eKeyDown event was consumed, eKeyPress event
+        // won't be dispatched by widget.  So, coming eKeyPress event means
+        // that the preceding eKeyDown event wasn't consumed in this case.
+        else {
           AutoTArray<uint32_t, 10> accessCharCodes;
           keyEvent->GetAccessKeyCandidates(accessCharCodes);
 
-          if (HandleAccessKey(keyEvent, aPresContext, accessCharCodes,
-                              modifierMask, matchesContentAccessKey)) {
+          if (HandleAccessKey(keyEvent, aPresContext, accessCharCodes)) {
             *aStatus = nsEventStatus_eConsumeNoDefault;
           }
         }
       }
     }
     // then fall through...
     MOZ_FALLTHROUGH;
   case eKeyDown:
@@ -812,16 +809,28 @@ EventStateManager::PreHandleEvent(nsPres
       //       TextComposition::IsComposing() is false even before
       //       compositionend if there is no composing string.
       //       And also don't expose other document's composition state.
       //       A native IME context is typically shared by multiple documents.
       //       So, don't use GetTextCompositionFor(nsIWidget*) here.
       RefPtr<TextComposition> composition =
         IMEStateManager::GetTextCompositionFor(aPresContext);
       aEvent->AsKeyboardEvent()->mIsComposing = !!composition;
+
+      // Widget may need to perform default action for specific keyboard
+      // event if it's not consumed.  In this case, widget has already marked
+      // the event as "waiting reply from remote process".  However, we need
+      // to reset it if the target (focused content) isn't in a remote process
+      // because PresShell needs to check if it's marked as so before
+      // dispatching events into the DOM tree.
+      if (aEvent->IsWaitingReplyFromRemoteProcess() &&
+          !aEvent->PropagationStopped() &&
+          !IsRemoteTarget(content)) {
+        aEvent->ResetWaitingReplyFromRemoteProcessState();
+      }
     }
     break;
   case eWheel:
   case eWheelOperationStart:
   case eWheelOperationEnd:
     {
       NS_ASSERTION(aEvent->IsTrusted(),
                    "Untrusted wheel event shouldn't be here");
@@ -919,33 +928,31 @@ EventStateManager::HandleQueryContentEve
     contentObserver->HandleQueryContentEvent(aEvent);
     return;
   }
 
   ContentEventHandler handler(mPresContext);
   handler.HandleQueryContentEvent(aEvent);
 }
 
-// static
-int32_t
-EventStateManager::GetAccessModifierMaskFor(nsISupports* aDocShell)
+static AccessKeyType
+GetAccessKeyTypeFor(nsISupports* aDocShell)
 {
   nsCOMPtr<nsIDocShellTreeItem> treeItem(do_QueryInterface(aDocShell));
-  if (!treeItem)
-    return -1; // invalid modifier
+  if (!treeItem) {
+    return AccessKeyType::eNone;
+  }
 
   switch (treeItem->ItemType()) {
-  case nsIDocShellTreeItem::typeChrome:
-    return Prefs::ChromeAccessModifierMask();
-
-  case nsIDocShellTreeItem::typeContent:
-    return Prefs::ContentAccessModifierMask();
-
-  default:
-    return -1; // invalid modifier
+    case nsIDocShellTreeItem::typeChrome:
+      return AccessKeyType::eChrome;
+    case nsIDocShellTreeItem::typeContent:
+      return AccessKeyType::eContent;
+    default:
+      return AccessKeyType::eNone;
   }
 }
 
 static bool
 IsAccessKeyTarget(nsIContent* aContent, nsIFrame* aFrame, nsAString& aKey)
 {
   // Use GetAttr because we want Unicode case=insensitive matching
   // XXXbz shouldn't this be case-sensitive, per spec?
@@ -987,18 +994,32 @@ IsAccessKeyTarget(nsIContent* aContent, 
   if (aContent->IsXULElement(nsGkAtoms::label)) {
     return true;
   }
 
   return false;
 }
 
 bool
-EventStateManager::ExecuteAccessKey(nsTArray<uint32_t>& aAccessCharCodes,
-                                    bool aIsTrustedEvent)
+EventStateManager::CheckIfEventMatchesAccessKey(WidgetKeyboardEvent* aEvent,
+                                                nsPresContext* aPresContext)
+{
+  AutoTArray<uint32_t, 10> accessCharCodes;
+  aEvent->GetAccessKeyCandidates(accessCharCodes);
+  return WalkESMTreeToHandleAccessKey(const_cast<WidgetKeyboardEvent*>(aEvent),
+                                      aPresContext, accessCharCodes,
+                                      nullptr, eAccessKeyProcessingNormal,
+                                      false);
+}
+
+bool
+EventStateManager::LookForAccessKeyAndExecute(
+                     nsTArray<uint32_t>& aAccessCharCodes,
+                     bool aIsTrustedEvent,
+                     bool aExecute)
 {
   int32_t count, start = -1;
   nsIContent* focusedContent = GetFocusedContent();
   if (focusedContent) {
     start = mAccessKeys.IndexOf(focusedContent);
     if (start == -1 && focusedContent->GetBindingParent())
       start = mAccessKeys.IndexOf(focusedContent->GetBindingParent());
   }
@@ -1008,16 +1029,19 @@ EventStateManager::ExecuteAccessKey(nsTA
   for (uint32_t i = 0; i < aAccessCharCodes.Length(); ++i) {
     uint32_t ch = aAccessCharCodes[i];
     nsAutoString accessKey;
     AppendUCS4ToUTF16(ch, accessKey);
     for (count = 1; count <= length; ++count) {
       content = mAccessKeys[(start + count) % length];
       frame = content->GetPrimaryFrame();
       if (IsAccessKeyTarget(content, frame, accessKey)) {
+        if (!aExecute) {
+          return true;
+        }
         bool shouldActivate = Prefs::KeyCausesActivation();
         while (shouldActivate && ++count <= length) {
           nsIContent *oc = mAccessKeys[(start + count) % length];
           nsIFrame *of = oc->GetPrimaryFrame();
           if (IsAccessKeyTarget(oc, of, accessKey))
             shouldActivate = false;
         }
 
@@ -1054,98 +1078,109 @@ EventStateManager::ExecuteAccessKey(nsTA
 // static
 void
 EventStateManager::GetAccessKeyLabelPrefix(Element* aElement, nsAString& aPrefix)
 {
   aPrefix.Truncate();
   nsAutoString separator, modifierText;
   nsContentUtils::GetModifierSeparatorText(separator);
 
-  nsCOMPtr<nsISupports> container = aElement->OwnerDoc()->GetDocShell();
-  int32_t modifierMask = GetAccessModifierMaskFor(container);
-
-  if (modifierMask == -1) {
+  AccessKeyType accessKeyType =
+    GetAccessKeyTypeFor(aElement->OwnerDoc()->GetDocShell());
+  if (accessKeyType == AccessKeyType::eNone) {
     return;
   }
-
-  if (modifierMask & NS_MODIFIER_CONTROL) {
+  Modifiers modifiers = WidgetKeyboardEvent::AccessKeyModifiers(accessKeyType);
+  if (modifiers == MODIFIER_NONE) {
+    return;
+  }
+
+  if (modifiers & MODIFIER_CONTROL) {
     nsContentUtils::GetControlText(modifierText);
     aPrefix.Append(modifierText + separator);
   }
-  if (modifierMask & NS_MODIFIER_META) {
+  if (modifiers & MODIFIER_META) {
     nsContentUtils::GetMetaText(modifierText);
     aPrefix.Append(modifierText + separator);
   }
-  if (modifierMask & NS_MODIFIER_OS) {
+  if (modifiers & MODIFIER_OS) {
     nsContentUtils::GetOSText(modifierText);
     aPrefix.Append(modifierText + separator);
   }
-  if (modifierMask & NS_MODIFIER_ALT) {
+  if (modifiers & MODIFIER_ALT) {
     nsContentUtils::GetAltText(modifierText);
     aPrefix.Append(modifierText + separator);
   }
-  if (modifierMask & NS_MODIFIER_SHIFT) {
+  if (modifiers & MODIFIER_SHIFT) {
     nsContentUtils::GetShiftText(modifierText);
     aPrefix.Append(modifierText + separator);
   }
 }
 
 struct MOZ_STACK_CLASS AccessKeyInfo
 {
   WidgetKeyboardEvent* event;
   nsTArray<uint32_t>& charCodes;
-  int32_t modifierMask;
-
-  AccessKeyInfo(WidgetKeyboardEvent* aEvent, nsTArray<uint32_t>& aCharCodes, int32_t aModifierMask)
+
+  AccessKeyInfo(WidgetKeyboardEvent* aEvent,
+                nsTArray<uint32_t>& aCharCodes)
     : event(aEvent)
     , charCodes(aCharCodes)
-    , modifierMask(aModifierMask)
   {
   }
 };
 
 static bool
 HandleAccessKeyInRemoteChild(TabParent* aTabParent, void* aArg)
 {
   AccessKeyInfo* accessKeyInfo = static_cast<AccessKeyInfo*>(aArg);
 
   // Only forward accesskeys for the active tab.
   bool active;
   aTabParent->GetDocShellIsActive(&active);
   if (active) {
-    accessKeyInfo->event->mAccessKeyForwardedToChild = true;
+    // Even if there is no target for the accesskey in this process,
+    // the event may match with a content accesskey.  If so, the keyboard
+    // event should be handled with reply event for preventing double action.
+    // (e.g., Alt+Shift+F on Windows may focus a content in remote and open
+    // "File" menu.)
+    accessKeyInfo->event->StopPropagation();
+    accessKeyInfo->event->MarkAsWaitingReplyFromRemoteProcess();
     aTabParent->HandleAccessKey(*accessKeyInfo->event,
-                                accessKeyInfo->charCodes,
-                                accessKeyInfo->modifierMask);
+                                accessKeyInfo->charCodes);
     return true;
   }
 
   return false;
 }
 
 bool
-EventStateManager::HandleAccessKey(WidgetKeyboardEvent* aEvent,
-                                   nsPresContext* aPresContext,
-                                   nsTArray<uint32_t>& aAccessCharCodes,
-                                   bool aMatchesContentAccessKey,
-                                   nsIDocShellTreeItem* aBubbledFrom,
-                                   ProcessingAccessKeyState aAccessKeyState,
-                                   int32_t aModifierMask)
+EventStateManager::WalkESMTreeToHandleAccessKey(
+                     WidgetKeyboardEvent* aEvent,
+                     nsPresContext* aPresContext,
+                     nsTArray<uint32_t>& aAccessCharCodes,
+                     nsIDocShellTreeItem* aBubbledFrom,
+                     ProcessingAccessKeyState aAccessKeyState,
+                     bool aExecute)
 {
   EnsureDocument(mPresContext);
   nsCOMPtr<nsIDocShell> docShell = aPresContext->GetDocShell();
   if (NS_WARN_IF(!docShell) || NS_WARN_IF(!mDocument)) {
     return false;
   }
-
+  AccessKeyType accessKeyType = GetAccessKeyTypeFor(docShell);
+  if (accessKeyType == AccessKeyType::eNone) {
+    return false;
+  }
   // Alt or other accesskey modifier is down, we may need to do an accesskey.
   if (mAccessKeys.Count() > 0 &&
-      aModifierMask == GetAccessModifierMaskFor(docShell)) {
+      aEvent->ModifiersMatchWithAccessKey(accessKeyType)) {
     // Someone registered an accesskey.  Find and activate it.
-    if (ExecuteAccessKey(aAccessCharCodes, aEvent->IsTrusted())) {
+    if (LookForAccessKeyAndExecute(aAccessCharCodes,
+                                   aEvent->IsTrusted(), aExecute)) {
       return true;
     }
   }
 
   int32_t childCount;
   docShell->GetChildCount(&childCount);
   for (int32_t counter = 0; counter < childCount; counter++) {
     // Not processing the child which bubbles up the handling
@@ -1168,19 +1203,19 @@ EventStateManager::HandleAccessKey(Widge
       }
 
       nsPresContext *subPC = subPS->GetPresContext();
 
       EventStateManager* esm =
         static_cast<EventStateManager*>(subPC->EventStateManager());
 
       if (esm &&
-          esm->HandleAccessKey(aEvent, subPC, aAccessCharCodes,
-                               aMatchesContentAccessKey, nullptr,
-                               eAccessKeyProcessingDown, aModifierMask)) {
+          esm->WalkESMTreeToHandleAccessKey(aEvent, subPC, aAccessCharCodes,
+                                            nullptr, eAccessKeyProcessingDown,
+                                            aExecute)) {
         return true;
       }
     }
   }// if end . checking all sub docshell ends here.
 
   // bubble up the process to the parent docshell if necessary
   if (eAccessKeyProcessingDown != aAccessKeyState) {
     nsCOMPtr<nsIDocShellTreeItem> parentShellItem;
@@ -1191,39 +1226,48 @@ EventStateManager::HandleAccessKey(Widge
       NS_ASSERTION(parentPS, "Our PresShell exists but the parent's does not?");
 
       nsPresContext *parentPC = parentPS->GetPresContext();
       NS_ASSERTION(parentPC, "PresShell without PresContext");
 
       EventStateManager* esm =
         static_cast<EventStateManager*>(parentPC->EventStateManager());
       if (esm &&
-          esm->HandleAccessKey(aEvent, parentPC, aAccessCharCodes,
-                               aMatchesContentAccessKey, docShell,
-                               eAccessKeyProcessingDown, aModifierMask)) {
+          esm->WalkESMTreeToHandleAccessKey(aEvent, parentPC, aAccessCharCodes,
+                                            docShell, eAccessKeyProcessingDown,
+                                            aExecute)) {
         return true;
       }
     }
   }// if end. bubble up process
 
   // If the content access key modifier is pressed, try remote children
-  if (aMatchesContentAccessKey && mDocument && mDocument->GetWindow()) {
-    // If the focus is currently on a node with a TabParent, the key event will
-    // get forwarded to the child process and HandleAccessKey called from there.
+  if (aExecute &&
+      aEvent->ModifiersMatchWithAccessKey(AccessKeyType::eContent) &&
+      mDocument && mDocument->GetWindow()) {
+    // If the focus is currently on a node with a TabParent, the key event
+    // should've gotten forwarded to the child process and HandleAccessKey
+    // called from there.
+    if (TabParent::GetFrom(GetFocusedContent())) {
+      // If access key may be only in remote contents, this method won't handle
+      // access key synchronously.  In this case, only reply event should reach
+      // here.
+      MOZ_ASSERT(aEvent->IsHandledInRemoteProcess() ||
+                 !aEvent->IsWaitingReplyFromRemoteProcess());
+    }
     // If focus is somewhere else, then we need to check the remote children.
-    nsFocusManager* fm = nsFocusManager::GetFocusManager();
-    nsIContent* focusedContent = fm ? fm->GetFocusedContent() : nullptr;
-    if (TabParent::GetFrom(focusedContent)) {
-      // A remote child process is focused. The key event should get sent to
-      // the child process.
-      aEvent->mAccessKeyForwardedToChild = true;
-    } else {
-      AccessKeyInfo accessKeyInfo(aEvent, aAccessCharCodes, aModifierMask);
+    // However, if the event has already been handled in a remote process,
+    // then, focus is moved from the remote process after posting the event.
+    // In such case, we shouldn't retry to handle access keys in remote
+    // processes.
+    else if (!aEvent->IsHandledInRemoteProcess()) {
+      AccessKeyInfo accessKeyInfo(aEvent, aAccessCharCodes);
       nsContentUtils::CallOnAllRemoteChildren(mDocument->GetWindow(),
-                                              HandleAccessKeyInRemoteChild, &accessKeyInfo);
+                                              HandleAccessKeyInRemoteChild,
+                                              &accessKeyInfo);
     }
   }
 
   return false;
 }// end of HandleAccessKey
 
 void
 EventStateManager::DispatchCrossProcessEvent(WidgetEvent* aEvent,
@@ -5814,19 +5858,16 @@ EventStateManager::WheelPrefs::IsOverOne
 }
 
 /******************************************************************/
 /* mozilla::EventStateManager::Prefs                              */
 /******************************************************************/
 
 bool EventStateManager::Prefs::sKeyCausesActivation = true;
 bool EventStateManager::Prefs::sClickHoldContextMenu = false;
-int32_t EventStateManager::Prefs::sGenericAccessModifierKey = -1;
-int32_t EventStateManager::Prefs::sChromeAccessModifierMask = 0;
-int32_t EventStateManager::Prefs::sContentAccessModifierMask = 0;
 
 // static
 void
 EventStateManager::Prefs::Init()
 {
   DebugOnly<nsresult> rv = Preferences::RegisterCallback(OnChange, "dom.popup_allowed_events");
   MOZ_ASSERT(NS_SUCCEEDED(rv),
              "Failed to observe \"dom.popup_allowed_events\"");
@@ -5841,31 +5882,16 @@ EventStateManager::Prefs::Init()
                                     sKeyCausesActivation);
   MOZ_ASSERT(NS_SUCCEEDED(rv),
              "Failed to observe \"accessibility.accesskeycausesactivation\"");
   rv = Preferences::AddBoolVarCache(&sClickHoldContextMenu,
                                     "ui.click_hold_context_menus",
                                     sClickHoldContextMenu);
   MOZ_ASSERT(NS_SUCCEEDED(rv),
              "Failed to observe \"ui.click_hold_context_menus\"");
-  rv = Preferences::AddIntVarCache(&sGenericAccessModifierKey,
-                                   "ui.key.generalAccessKey",
-                                   sGenericAccessModifierKey);
-  MOZ_ASSERT(NS_SUCCEEDED(rv),
-             "Failed to observe \"ui.key.generalAccessKey\"");
-  rv = Preferences::AddIntVarCache(&sChromeAccessModifierMask,
-                                   "ui.key.chromeAccess",
-                                   sChromeAccessModifierMask);
-  MOZ_ASSERT(NS_SUCCEEDED(rv),
-             "Failed to observe \"ui.key.chromeAccess\"");
-  rv = Preferences::AddIntVarCache(&sContentAccessModifierMask,
-                                   "ui.key.contentAccess",
-                                   sContentAccessModifierMask);
-  MOZ_ASSERT(NS_SUCCEEDED(rv),
-             "Failed to observe \"ui.key.contentAccess\"");
   sPrefsAlreadyCached = true;
 }
 
 // static
 void
 EventStateManager::Prefs::OnChange(const char* aPrefName, void*)
 {
   nsDependentCString prefName(aPrefName);
@@ -5876,54 +5902,16 @@ EventStateManager::Prefs::OnChange(const
 
 // static
 void
 EventStateManager::Prefs::Shutdown()
 {
   Preferences::UnregisterCallback(OnChange, "dom.popup_allowed_events");
 }
 
-// static
-int32_t
-EventStateManager::Prefs::ChromeAccessModifierMask()
-{
-  return GetAccessModifierMask(nsIDocShellTreeItem::typeChrome);
-}
-
-// static
-int32_t
-EventStateManager::Prefs::ContentAccessModifierMask()
-{
-  return GetAccessModifierMask(nsIDocShellTreeItem::typeContent);
-}
-
-// static
-int32_t
-EventStateManager::Prefs::GetAccessModifierMask(int32_t aItemType)
-{
-  switch (sGenericAccessModifierKey) {
-    case -1:                             break; // use the individual prefs
-    case nsIDOMKeyEvent::DOM_VK_SHIFT:   return NS_MODIFIER_SHIFT;
-    case nsIDOMKeyEvent::DOM_VK_CONTROL: return NS_MODIFIER_CONTROL;
-    case nsIDOMKeyEvent::DOM_VK_ALT:     return NS_MODIFIER_ALT;
-    case nsIDOMKeyEvent::DOM_VK_META:    return NS_MODIFIER_META;
-    case nsIDOMKeyEvent::DOM_VK_WIN:     return NS_MODIFIER_OS;
-    default:                             return 0;
-  }
-
-  switch (aItemType) {
-    case nsIDocShellTreeItem::typeChrome:
-      return sChromeAccessModifierMask;
-    case nsIDocShellTreeItem::typeContent:
-      return sContentAccessModifierMask;
-    default:
-      return 0;
-  }
-}
-
 /******************************************************************/
 /* mozilla::AutoHandlingUserInputStatePusher                      */
 /******************************************************************/
 
 AutoHandlingUserInputStatePusher::AutoHandlingUserInputStatePusher(
                                     bool aIsHandlingUserInput,
                                     WidgetEvent* aEvent,
                                     nsIDocument* aDocument) :
--- a/dom/events/EventStateManager.h
+++ b/dom/events/EventStateManager.h
@@ -179,27 +179,57 @@ public:
    *
    * @param  aContent  the given element (must not be null)
    * @return           registered accesskey
    */
   uint32_t GetRegisteredAccessKey(nsIContent* aContent);
 
   static void GetAccessKeyLabelPrefix(dom::Element* aElement, nsAString& aPrefix);
 
+  /**
+   * HandleAccessKey() looks for access keys which matches with aEvent and
+   * execute when it matches with a chrome access key or some content access
+   * keys.
+   * If the event may match chrome access keys, this handles the access key
+   * synchronously (if there are nested ESMs, their HandleAccessKey() are
+   * also called recursively).
+   * If the event may match content access keys and focused target is a remote
+   * process, this does nothing for the content because when this is called,
+   * it should already have been handled in the remote process.
+   * If the event may match content access keys and focused target is not in
+   * remote process but there are some remote children, this will post
+   * HandleAccessKey messages to all remote children.
+   *
+   * @return            true if there is accesskey which aEvent and
+   *                    aAccessCharCodes match with.  Otherwise, false.
+   *                    I.e., when this returns true, a target is executed
+   *                    or focused.
+   *                    Note that even if this returns false, a target in
+   *                    remote process may be executed or focused
+   *                    asynchronously.
+   */
   bool HandleAccessKey(WidgetKeyboardEvent* aEvent,
                        nsPresContext* aPresContext,
-                       nsTArray<uint32_t>& aAccessCharCodes,
-                       int32_t aModifierMask,
-                       bool aMatchesContentAccessKey)
+                       nsTArray<uint32_t>& aAccessCharCodes)
   {
-    return HandleAccessKey(aEvent, aPresContext, aAccessCharCodes,
-                           aMatchesContentAccessKey, nullptr,
-                           eAccessKeyProcessingNormal, aModifierMask);
+    return WalkESMTreeToHandleAccessKey(aEvent, aPresContext, aAccessCharCodes,
+                                        nullptr, eAccessKeyProcessingNormal,
+                                        true);
   }
 
+  /**
+   * CheckIfEventMatchesAccessKey() looks for access key which matches with
+   * aEvent in the process but won't execute it.
+   *
+   * @return            true if there is accesskey which aEvent matches with
+   *                    in this process.  Otherwise, false.
+   */
+  bool CheckIfEventMatchesAccessKey(WidgetKeyboardEvent* aEvent,
+                                    nsPresContext* aPresContext);
+
   nsresult SetCursor(int32_t aCursor, imgIContainer* aContainer,
                      bool aHaveHotspot, float aHotspotX, float aHotspotY,
                      nsIWidget* aWidget, bool aLockCursor);
 
   static void StartHandlingUserInput()
   {
     ++sUserInputEventDepth;
     ++sUserInputCounter;
@@ -311,39 +341,28 @@ protected:
   /**
    * Prefs class capsules preference management.
    */
   class Prefs
   {
   public:
     static bool KeyCausesActivation() { return sKeyCausesActivation; }
     static bool ClickHoldContextMenu() { return sClickHoldContextMenu; }
-    static int32_t ChromeAccessModifierMask();
-    static int32_t ContentAccessModifierMask();
 
     static void Init();
     static void OnChange(const char* aPrefName, void*);
     static void Shutdown();
 
   private:
     static bool sKeyCausesActivation;
     static bool sClickHoldContextMenu;
-    static int32_t sGenericAccessModifierKey;
-    static int32_t sChromeAccessModifierMask;
-    static int32_t sContentAccessModifierMask;
 
     static int32_t GetAccessModifierMask(int32_t aItemType);
   };
 
-  /**
-   * Get appropriate access modifier mask for the aDocShell.  Returns -1 if
-   * access key isn't available.
-   */
-  static int32_t GetAccessModifierMaskFor(nsISupports* aDocShell);
-
   /*
    * If aTargetFrame's widget has a cached cursor value, resets the cursor
    * such that the next call to SetCursor on the widget will force an update
    * of the native cursor. For use in getting puppet widget to update its
    * cursor between mouse exit / enter transitions. This call basically wraps
    * nsIWidget ClearCachedCursor.
    */
   void ClearCachedWidgetCursor(nsIFrame* aTargetFrame);
@@ -426,54 +445,74 @@ protected:
                                             bool aNoContentDispatch);
   nsresult SetClickCount(WidgetMouseEvent* aEvent, nsEventStatus* aStatus);
   nsresult CheckForAndDispatchClick(WidgetMouseEvent* aEvent,
                                     nsEventStatus* aStatus);
   void EnsureDocument(nsPresContext* aPresContext);
   void FlushPendingEvents(nsPresContext* aPresContext);
 
   /**
-   * The phases of HandleAccessKey processing. See below.
+   * The phases of WalkESMTreeToHandleAccessKey processing. See below.
    */
   typedef enum {
     eAccessKeyProcessingNormal = 0,
     eAccessKeyProcessingUp,
     eAccessKeyProcessingDown
   } ProcessingAccessKeyState;
 
   /**
-   * Access key handling.  If there is registered content for the accesskey
-   * given by the key event and modifier mask then call
-   * content.PerformAccesskey(), otherwise call HandleAccessKey() recursively,
-   * on descendant docshells first, then on the ancestor (with |aBubbledFrom|
-   * set to the docshell associated with |this|), until something matches.
+   * Walk EMS to look for access key and execute found access key when aExecute
+   * is true.
+   * If there is registered content for the accesskey given by the key event
+   * and modifier mask then call content.PerformAccesskey(), otherwise call
+   * WalkESMTreeToHandleAccessKey() recursively, on descendant docshells first,
+   * then on the ancestor (with |aBubbledFrom| set to the docshell associated
+   * with |this|), until something matches.
    *
    * @param aEvent the keyboard event triggering the acccess key
    * @param aPresContext the presentation context
    * @param aAccessCharCodes list of charcode candidates
-   * @param aMatchesContentAccessKey true if the content accesskey modifier is pressed
-   * @param aBubbledFrom is used by an ancestor to avoid calling HandleAccessKey()
-   *        on the child the call originally came from, i.e. this is the child
-   *        that recursively called us in its Up phase. The initial caller
-   *        passes |nullptr| here. This is to avoid an infinite loop.
+   * @param aBubbledFrom is used by an ancestor to avoid calling
+   *        WalkESMTreeToHandleAccessKey() on the child the call originally
+   *        came from, i.e. this is the child that recursively called us in
+   *        its Up phase. The initial caller passes |nullptr| here. This is to
+   *        avoid an infinite loop.
    * @param aAccessKeyState Normal, Down or Up processing phase (see enums
    *        above). The initial event receiver uses 'normal', then 'down' when
    *        processing children and Up when recursively calling its ancestor.
-   * @param aModifierMask modifier mask for the key event
+   * @param aExecute is true, execute an accesskey if it's found.  Otherwise,
+   *        found accesskey won't be executed.
+   *
+   * @return            true if there is a target which aEvent and
+   *                    aAccessCharCodes match with in this process.
+   *                    Otherwise, false.  I.e., when this returns true and
+   *                    aExecute is true, a target is executed or focused.
+   *                    Note that even if this returns false, a target in
+   *                    remote process may be executed or focused
+   *                    asynchronously.
    */
-  bool HandleAccessKey(WidgetKeyboardEvent* aEvent,
-                       nsPresContext* aPresContext,
-                       nsTArray<uint32_t>& aAccessCharCodes,
-                       bool aMatchesContentAccessKey,
-                       nsIDocShellTreeItem* aBubbledFrom,
-                       ProcessingAccessKeyState aAccessKeyState,
-                       int32_t aModifierMask);
+  bool WalkESMTreeToHandleAccessKey(WidgetKeyboardEvent* aEvent,
+                                    nsPresContext* aPresContext,
+                                    nsTArray<uint32_t>& aAccessCharCodes,
+                                    nsIDocShellTreeItem* aBubbledFrom,
+                                    ProcessingAccessKeyState aAccessKeyState,
+                                    bool aExecute);
 
-  bool ExecuteAccessKey(nsTArray<uint32_t>& aAccessCharCodes,
-                        bool aIsTrustedEvent);
+  /**
+   * Look for access key and execute found access key if aExecute is true in
+   * the instance.
+   *
+   * @return            true if there is a target which matches with
+   *                    aAccessCharCodes and aIsTrustedEvent.  Otherwise,
+   *                    false.  I.e., when this returns true and aExecute
+   *                    is true, a target is executed or focused.
+   */
+  bool LookForAccessKeyAndExecute(nsTArray<uint32_t>& aAccessCharCodes,
+                                  bool aIsTrustedEvent,
+                                  bool aExecute);
 
   //---------------------------------------------
   // DocShell Focus Traversal Methods
   //---------------------------------------------
 
   nsIContent* GetFocusedContent();
   bool IsShellVisible(nsIDocShell* aShell);
 
--- a/dom/html/HTMLVideoElement.cpp
+++ b/dom/html/HTMLVideoElement.cpp
@@ -18,16 +18,17 @@
 #include "ImageContainer.h"
 #include "VideoFrameContainer.h"
 
 #include "nsIScriptSecurityManager.h"
 #include "nsIXPConnect.h"
 
 #include "nsITimer.h"
 
+#include "FrameStatistics.h"
 #include "MediaError.h"
 #include "MediaDecoder.h"
 #include "mozilla/Preferences.h"
 #include "mozilla/dom/WakeLock.h"
 #include "mozilla/dom/power/PowerManagerService.h"
 #include "mozilla/dom/Performance.h"
 #include "mozilla/dom/VideoPlaybackQuality.h"
 
--- a/dom/ipc/PBrowser.ipdl
+++ b/dom/ipc/PBrowser.ipdl
@@ -801,20 +801,19 @@ child:
     async SwappedWithOtherRemoteLoader(IPCTabContext context);
 
     /**
      * A potential accesskey was just pressed. Look for accesskey targets
      * using the list of provided charCodes.
      *
      * @param event keyboard event
      * @param isTrusted true if triggered by a trusted key event
-     * @param modifierMask indicates which accesskey modifiers are pressed
      */
     async HandleAccessKey(WidgetKeyboardEvent event,
-                          uint32_t[] charCodes, int32_t modifierMask);
+                          uint32_t[] charCodes);
 
     /**
      * 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);
 
--- a/dom/ipc/TabChild.cpp
+++ b/dom/ipc/TabChild.cpp
@@ -1594,17 +1594,17 @@ TabChild::RecvRealMouseButtonEvent(const
   if (pendingLayerization) {
     context.SetPendingLayerization();
   }
 
   WidgetMouseEvent localEvent(aEvent);
   localEvent.mWidget = mPuppetWidget;
   APZCCallbackHelper::ApplyCallbackTransform(localEvent, aGuid,
       mPuppetWidget->GetDefaultScale());
-  APZCCallbackHelper::DispatchWidgetEvent(localEvent);
+  DispatchWidgetEventViaAPZ(localEvent);
 
   if (aInputBlockId && aEvent.mFlags.mHandledByAPZ) {
     mAPZEventState->ProcessMouseEvent(aEvent, aGuid, aInputBlockId);
   }
   return IPC_OK();
 }
 
 
@@ -1643,16 +1643,23 @@ TabChild::MaybeCoalesceWheelEvent(const 
          mCoalescedWheelData.CanCoalesce(aEvent, aGuid, aInputBlockId))) {
       mCoalescedWheelData.Coalesce(aEvent, aGuid, aInputBlockId);
       return true;
     }
   }
   return false;
 }
 
+nsEventStatus
+TabChild::DispatchWidgetEventViaAPZ(WidgetGUIEvent& aEvent)
+{
+  aEvent.ResetWaitingReplyFromRemoteProcessState();
+  return APZCCallbackHelper::DispatchWidgetEvent(aEvent);
+}
+
 void
 TabChild::MaybeDispatchCoalescedWheelEvent()
 {
   if (mCoalescedWheelData.IsEmpty()) {
     return;
   }
   const WidgetWheelEvent* wheelEvent =
     mCoalescedWheelData.GetCoalescedWheelEvent();
@@ -1673,17 +1680,17 @@ TabChild::DispatchWheelEvent(const Widge
     nsCOMPtr<nsIDocument> document(GetDocument());
     APZCCallbackHelper::SendSetTargetAPZCNotification(
       mPuppetWidget, document, aEvent, aGuid, aInputBlockId);
   }
 
   localEvent.mWidget = mPuppetWidget;
   APZCCallbackHelper::ApplyCallbackTransform(localEvent, aGuid,
                                              mPuppetWidget->GetDefaultScale());
-  APZCCallbackHelper::DispatchWidgetEvent(localEvent);
+  DispatchWidgetEventViaAPZ(localEvent);
 
   if (localEvent.mCanTriggerSwipe) {
     SendRespondStartSwipeEvent(aInputBlockId, localEvent.TriggersSwipe());
   }
 
   if (aInputBlockId && aEvent.mFlags.mHandledByAPZ) {
     mAPZEventState->ProcessWheelEvent(localEvent, aGuid, aInputBlockId);
   }
@@ -1740,17 +1747,17 @@ TabChild::RecvRealTouchEvent(const Widge
         mSetAllowedTouchBehaviorCallback);
     }
     APZCCallbackHelper::SendSetTargetAPZCNotification(mPuppetWidget, document,
                                                       localEvent, aGuid,
                                                       aInputBlockId);
   }
 
   // Dispatch event to content (potentially a long-running operation)
-  nsEventStatus status = APZCCallbackHelper::DispatchWidgetEvent(localEvent);
+  nsEventStatus status = DispatchWidgetEventViaAPZ(localEvent);
 
   if (!AsyncPanZoomEnabled()) {
     // We shouldn't have any e10s platforms that have touch events enabled
     // without APZ.
     MOZ_ASSERT(false);
     return IPC_OK();
   }
 
@@ -1800,26 +1807,26 @@ TabChild::RecvRealDragEvent(const Widget
       do_GetService("@mozilla.org/widget/dragservice;1");
     if (dragService) {
       // This will dispatch 'drag' event at the source if the
       // drag transaction started in this process.
       dragService->FireDragEventAtSource(eDrag, aEvent.mModifiers);
     }
   }
 
-  APZCCallbackHelper::DispatchWidgetEvent(localEvent);
+  DispatchWidgetEventViaAPZ(localEvent);
   return IPC_OK();
 }
 
 mozilla::ipc::IPCResult
 TabChild::RecvPluginEvent(const WidgetPluginEvent& aEvent)
 {
   WidgetPluginEvent localEvent(aEvent);
   localEvent.mWidget = mPuppetWidget;
-  nsEventStatus status = APZCCallbackHelper::DispatchWidgetEvent(localEvent);
+  nsEventStatus status = DispatchWidgetEventViaAPZ(localEvent);
   if (status != nsEventStatus_eConsumeNoDefault) {
     // If not consumed, we should call default action
     SendDefaultProcOfPluginEvent(aEvent);
   }
   return IPC_OK();
 }
 
 void
@@ -1911,41 +1918,46 @@ TabChild::RecvRealKeyEvent(const WidgetK
   // want to process any following keypress events.
   if (aEvent.mMessage == eKeyPress && mIgnoreKeyPressEvent) {
     return IPC_OK();
   }
 
   WidgetKeyboardEvent localEvent(aEvent);
   localEvent.mWidget = mPuppetWidget;
   localEvent.mUniqueId = aEvent.mUniqueId;
-  nsEventStatus status = APZCCallbackHelper::DispatchWidgetEvent(localEvent);
+  nsEventStatus status = DispatchWidgetEventViaAPZ(localEvent);
 
   // Update the end time of the possible repeated event so that we can skip
   // some incoming events in case event handling took long time.
   UpdateRepeatedKeyEventEndTime(localEvent);
 
   if (aEvent.mMessage == eKeyDown) {
     mIgnoreKeyPressEvent = status == nsEventStatus_eConsumeNoDefault;
   }
 
   if (localEvent.mFlags.mIsSuppressedOrDelayed) {
     localEvent.PreventDefault();
   }
 
   // If a response is desired from the content process, resend the key event.
-  // If mAccessKeyForwardedToChild is set, then don't resend the key event yet
-  // as RecvHandleAccessKey will do this.
-  if (localEvent.WantReplyFromContentProcess()) {
+  if (aEvent.WantReplyFromContentProcess()) {
+    // If the event's default isn't prevented but the status is no default,
+    // That means that the event was consumed by EventStateManager or something
+    // which is not a usual event handler.  In such case, prevent its default
+    // as a default handler.  For example, when an eKeyPress event matches
+    // with a content accesskey, and it's executed, peventDefault() of the
+    // event won't be called but the status is set to "no default".  Then,
+    // the event shouldn't be handled by nsMenuBarListener in the main process.
+    if (!localEvent.DefaultPrevented() &&
+        status == nsEventStatus_eConsumeNoDefault) {
+      localEvent.PreventDefault();
+    }
     SendReplyKeyEvent(localEvent);
   }
 
-  if (localEvent.mAccessKeyForwardedToChild) {
-    SendAccessKeyNotHandled(localEvent);
-  }
-
   return IPC_OK();
 }
 
 mozilla::ipc::IPCResult
 TabChild::RecvKeyEvent(const nsString& aType,
                        const int32_t& aKeyCode,
                        const int32_t& aCharCode,
                        const int32_t& aModifiers,
@@ -1957,27 +1969,27 @@ TabChild::RecvKeyEvent(const nsString& a
   return IPC_OK();
 }
 
 mozilla::ipc::IPCResult
 TabChild::RecvCompositionEvent(const WidgetCompositionEvent& aEvent)
 {
   WidgetCompositionEvent localEvent(aEvent);
   localEvent.mWidget = mPuppetWidget;
-  APZCCallbackHelper::DispatchWidgetEvent(localEvent);
+  DispatchWidgetEventViaAPZ(localEvent);
   Unused << SendOnEventNeedingAckHandled(aEvent.mMessage);
   return IPC_OK();
 }
 
 mozilla::ipc::IPCResult
 TabChild::RecvSelectionEvent(const WidgetSelectionEvent& aEvent)
 {
   WidgetSelectionEvent localEvent(aEvent);
   localEvent.mWidget = mPuppetWidget;
-  APZCCallbackHelper::DispatchWidgetEvent(localEvent);
+  DispatchWidgetEventViaAPZ(localEvent);
   Unused << SendOnEventNeedingAckHandled(aEvent.mMessage);
   return IPC_OK();
 }
 
 mozilla::ipc::IPCResult
 TabChild::RecvPasteTransferable(const IPCDataTransfer& aDataTransfer,
                                 const bool& aIsPrivateData,
                                 const IPC::Principal& aRequestingPrincipal)
@@ -2239,28 +2251,26 @@ TabChild::RecvSwappedWithOtherRemoteLoad
 
   docShell->SetInFrameSwap(false);
 
   return IPC_OK();
 }
 
 mozilla::ipc::IPCResult
 TabChild::RecvHandleAccessKey(const WidgetKeyboardEvent& aEvent,
-                              nsTArray<uint32_t>&& aCharCodes,
-                              const int32_t& aModifierMask)
+                              nsTArray<uint32_t>&& aCharCodes)
 {
   nsCOMPtr<nsIDocument> document(GetDocument());
   nsCOMPtr<nsIPresShell> presShell = document->GetShell();
   if (presShell) {
     nsPresContext* pc = presShell->GetPresContext();
     if (pc) {
       if (!pc->EventStateManager()->
                  HandleAccessKey(&(const_cast<WidgetKeyboardEvent&>(aEvent)),
-                                 pc, aCharCodes,
-                                 aModifierMask, true)) {
+                                 pc, aCharCodes)) {
         // If no accesskey was found, inform the parent so that accesskeys on
         // menus can be handled.
         WidgetKeyboardEvent localEvent(aEvent);
         localEvent.mWidget = mPuppetWidget;
         SendAccessKeyNotHandled(localEvent);
       }
     }
   }
--- a/dom/ipc/TabChild.h
+++ b/dom/ipc/TabChild.h
@@ -590,19 +590,19 @@ public:
 
   virtual mozilla::ipc::IPCResult RecvUIResolutionChanged(const float& aDpi,
                                                           const int32_t& aRounding,
                                                           const double& aScale) override;
 
   virtual mozilla::ipc::IPCResult
   RecvThemeChanged(nsTArray<LookAndFeelInt>&& aLookAndFeelIntCache) override;
 
-  virtual mozilla::ipc::IPCResult RecvHandleAccessKey(const WidgetKeyboardEvent& aEvent,
-                                                      nsTArray<uint32_t>&& aCharCodes,
-                                                      const int32_t& aModifierMask) override;
+  virtual mozilla::ipc::IPCResult
+  RecvHandleAccessKey(const WidgetKeyboardEvent& aEvent,
+                      nsTArray<uint32_t>&& aCharCodes) override;
 
   virtual mozilla::ipc::IPCResult RecvSetUseGlobalHistory(const bool& aUse) override;
 
   virtual mozilla::ipc::IPCResult RecvHandledWindowedPluginKeyEvent(
     const mozilla::NativeEventData& aKeyEventData,
     const bool& aIsConsumed) override;
 
   virtual mozilla::ipc::IPCResult RecvPrint(const uint64_t& aOuterWindowID,
@@ -797,16 +797,21 @@ private:
 
   bool MaybeCoalesceWheelEvent(const WidgetWheelEvent& aEvent,
                                const ScrollableLayerGuid& aGuid,
                                const uint64_t& aInputBlockId,
                                bool* aIsNextWheelEvent);
 
   void MaybeDispatchCoalescedWheelEvent();
 
+  /**
+   * Dispatch aEvent on aEvent.mWidget.
+   */
+  nsEventStatus DispatchWidgetEventViaAPZ(WidgetGUIEvent& aEvent);
+
   void DispatchWheelEvent(const WidgetWheelEvent& aEvent,
                           const ScrollableLayerGuid& aGuid,
                           const uint64_t& aInputBlockId);
 
   void InternalSetDocShellIsActive(bool aIsActive,
                                    bool aPreserveLayers);
 
   class DelayedDeleteRunnable;
--- a/dom/ipc/TabParent.cpp
+++ b/dom/ipc/TabParent.cpp
@@ -812,25 +812,24 @@ TabParent::ThemeChanged()
     // send down to the child. We do this for every remote tab for now,
     // but bug 1156934 has been filed to do it once per content process.
     Unused << SendThemeChanged(LookAndFeel::GetIntCache());
   }
 }
 
 void
 TabParent::HandleAccessKey(const WidgetKeyboardEvent& aEvent,
-                           nsTArray<uint32_t>& aCharCodes,
-                           const int32_t& aModifierMask)
+                           nsTArray<uint32_t>& aCharCodes)
 {
   if (!mIsDestroyed) {
     // Note that we don't need to mark aEvent is posted to a remote process
     // because the event may be dispatched to it as normal keyboard event.
     // Therefore, we should use local copy to send it.
     WidgetKeyboardEvent localEvent(aEvent);
-    Unused << SendHandleAccessKey(localEvent, aCharCodes, aModifierMask);
+    Unused << SendHandleAccessKey(localEvent, aCharCodes);
   }
 }
 
 void
 TabParent::Activate()
 {
   if (!mIsDestroyed) {
     Unused << Manager()->SendActivate(this);
@@ -2042,17 +2041,33 @@ TabParent::RecvReplyKeyEvent(const Widge
   nsIPresShell* presShell = doc->GetShell();
   NS_ENSURE_TRUE(presShell, IPC_OK());
   nsPresContext* presContext = presShell->GetPresContext();
   NS_ENSURE_TRUE(presContext, IPC_OK());
 
   AutoHandlingUserInputStatePusher userInpStatePusher(localEvent.IsTrusted(),
                                                       &localEvent, doc);
 
-  EventDispatcher::Dispatch(mFrameElement, presContext, &localEvent);
+  nsEventStatus status = nsEventStatus_eIgnore;
+
+  // Handle access key in this process before dispatching reply event because
+  // ESM handles it before dispatching the event to the DOM tree.
+  if (localEvent.mMessage == eKeyPress &&
+      (localEvent.ModifiersMatchWithAccessKey(AccessKeyType::eChrome) ||
+       localEvent.ModifiersMatchWithAccessKey(AccessKeyType::eContent))) {
+    RefPtr<EventStateManager> esm = presContext->EventStateManager();
+    AutoTArray<uint32_t, 10> accessCharCodes;
+    localEvent.GetAccessKeyCandidates(accessCharCodes);
+    if (esm->HandleAccessKey(&localEvent, presContext, accessCharCodes)) {
+      status = nsEventStatus_eConsumeNoDefault;
+    }
+  }
+
+  EventDispatcher::Dispatch(mFrameElement, presContext, &localEvent, nullptr,
+                            &status);
 
   if (!localEvent.DefaultPrevented() &&
       !localEvent.mFlags.mIsSynthesizedForTests) {
     nsCOMPtr<nsIWidget> widget = GetWidget();
     if (widget) {
       widget->PostHandleKeyEvent(&localEvent);
       localEvent.StopPropagation();
     }
@@ -2061,20 +2076,26 @@ TabParent::RecvReplyKeyEvent(const Widge
   return IPC_OK();
 }
 
 mozilla::ipc::IPCResult
 TabParent::RecvAccessKeyNotHandled(const WidgetKeyboardEvent& aEvent)
 {
   NS_ENSURE_TRUE(mFrameElement, IPC_OK());
 
+  // This is called only when this process had focus and HandleAccessKey
+  // message was posted to all remote process and each remote process didn't
+  // execute any content access keys.
+  // XXX If there were two or more remote processes, this may be called
+  //     twice or more for a keyboard event, that must be a bug.  But how to
+  //     detect if received event has already been handled?
+
   WidgetKeyboardEvent localEvent(aEvent);
   localEvent.MarkAsHandledInRemoteProcess();
   localEvent.mMessage = eAccessKeyNotFound;
-  localEvent.mAccessKeyForwardedToChild = false;
 
   // Here we convert the WidgetEvent that we received to an nsIDOMEvent
   // to be able to dispatch it to the <browser> element as the target element.
   nsIDocument* doc = mFrameElement->OwnerDoc();
   nsIPresShell* presShell = doc->GetShell();
   NS_ENSURE_TRUE(presShell, IPC_OK());
 
   if (presShell->CanDispatchEvent()) {
--- a/dom/ipc/TabParent.h
+++ b/dom/ipc/TabParent.h
@@ -356,18 +356,17 @@ public:
 
   void SizeModeChanged(const nsSizeMode& aSizeMode);
 
   void UIResolutionChanged();
 
   void ThemeChanged();
 
   void HandleAccessKey(const WidgetKeyboardEvent& aEvent,
-                       nsTArray<uint32_t>& aCharCodes,
-                       const int32_t& aModifierMask);
+                       nsTArray<uint32_t>& aCharCodes);
 
   void Activate();
 
   void Deactivate();
 
   bool MapEventCoordinatesForChildProcess(mozilla::WidgetEvent* aEvent);
 
   void MapEventCoordinatesForChildProcess(const LayoutDeviceIntPoint& aOffset,
--- a/dom/media/ADTSDecoder.cpp
+++ b/dom/media/ADTSDecoder.cpp
@@ -20,18 +20,19 @@ ADTSDecoder::Clone(MediaDecoderInit& aIn
     return nullptr;
 
   return new ADTSDecoder(aInit);
 }
 
 MediaDecoderStateMachine*
 ADTSDecoder::CreateStateMachine()
 {
-  MediaFormatReaderInit init(this);
+  MediaFormatReaderInit init;
   init.mCrashHelper = GetOwner()->CreateGMPCrashHelper();
+  init.mFrameStats = mFrameStats;
   mReader = new MediaFormatReader(init, new ADTSDemuxer(mResource));
   return new MediaDecoderStateMachine(this, mReader);
 }
 
 /* static */ bool
 ADTSDecoder::IsEnabled()
 {
   RefPtr<PDMFactory> platform = new PDMFactory();
deleted file mode 100644
--- a/dom/media/AbstractMediaDecoder.h
+++ /dev/null
@@ -1,95 +0,0 @@
-/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
-/* vim:set ts=2 sw=2 sts=2 et cindent: */
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-#ifndef AbstractMediaDecoder_h_
-#define AbstractMediaDecoder_h_
-
-#include "mozilla/Attributes.h"
-#include "mozilla/StateMirroring.h"
-
-#include "FrameStatistics.h"
-#include "MediaEventSource.h"
-#include "MediaInfo.h"
-#include "nsISupports.h"
-#include "nsDataHashtable.h"
-#include "nsThreadUtils.h"
-
-namespace mozilla {
-
-namespace layers {
-class ImageContainer;
-class KnowsCompositor;
-} // namespace layers
-
-class AbstractThread;
-class MediaResource;
-class ReentrantMonitor;
-class VideoFrameContainer;
-class MediaDecoderOwner;
-class CDMProxy;
-class GMPCrashHelper;
-
-/**
- * The AbstractMediaDecoder class describes the public interface for a media decoder
- * and is used by the MediaReader classes.
- */
-class AbstractMediaDecoder : public nsIObserver
-{
-public:
-  // Increments the parsed, decoded and dropped frame counters by the passed in
-  // counts.
-  // Can be called on any thread.
-  virtual void NotifyDecodedFrames(const FrameStatisticsData& aStats) = 0;
-
-  // Return an abstract thread on which to run main thread runnables.
-  virtual AbstractThread* AbstractMainThread() const = 0;
-  virtual VideoFrameContainer* GetVideoFrameContainer() = 0;
-  virtual mozilla::layers::ImageContainer* GetImageContainer() = 0;
-
-  // Returns the owner of this decoder or null when the decoder is shutting
-  // down. The owner should only be used on the main thread.
-  virtual MediaDecoderOwner* GetOwner() const = 0;
-
-  // Set by Reader if the current audio track can be offloaded
-  virtual void SetPlatformCanOffloadAudio(bool aCanOffloadAudio) { }
-
-  // Stack based class to assist in notifying the frame statistics of
-  // parsed and decoded frames. Use inside video demux & decode functions
-  // to ensure all parsed and decoded frames are reported on all return paths.
-  class AutoNotifyDecoded
-  {
-  public:
-    explicit AutoNotifyDecoded(AbstractMediaDecoder* aDecoder)
-      : mDecoder(aDecoder)
-    {
-    }
-    ~AutoNotifyDecoded()
-    {
-      if (mDecoder) {
-        mDecoder->NotifyDecodedFrames(mStats);
-      }
-    }
-
-    FrameStatisticsData mStats;
-
-  private:
-    AbstractMediaDecoder* mDecoder;
-  };
-
-  // Classes directly inheriting from AbstractMediaDecoder do not support
-  // Observe and it should never be called directly.
-  NS_IMETHOD Observe(nsISupports* aSubject, const char* aTopic,
-                     const char16_t* aData) override
-  {
-    MOZ_CRASH("Forbidden method");
-    return NS_OK;
-  }
-};
-
-} // namespace mozilla
-
-#endif
-
--- a/dom/media/DecoderTraits.cpp
+++ b/dom/media/DecoderTraits.cpp
@@ -291,21 +291,16 @@ DecoderTraits::CreateDecoder(MediaDecode
 
 /* static */
 MediaFormatReader*
 DecoderTraits::CreateReader(const MediaContainerType& aType,
                             MediaFormatReaderInit& aInit)
 {
   MOZ_ASSERT(NS_IsMainThread());
   MediaFormatReader* decoderReader = nullptr;
-
-  if (!aInit.mDecoder) {
-    return decoderReader;
-  }
-
   MediaResource* resource = aInit.mResource;
 
 #ifdef MOZ_FMP4
   if (MP4Decoder::IsSupportedType(aType,
                                   /* DecoderDoctorDiagnostics* */ nullptr)) {
     decoderReader = new MediaFormatReader(aInit, new MP4Demuxer(resource));
   } else
 #endif
--- a/dom/media/DecoderTraits.h
+++ b/dom/media/DecoderTraits.h
@@ -9,17 +9,16 @@
 
 #include "nsCOMPtr.h"
 
 class nsAString;
 class nsACString;
 
 namespace mozilla {
 
-class AbstractMediaDecoder;
 class ChannelMediaDecoder;
 class DecoderDoctorDiagnostics;
 class MediaContainerType;
 struct MediaDecoderInit;
 struct MediaFormatReaderInit;
 class MediaDecoderOwner;
 class MediaFormatReader;
 class MediaResource;
--- a/dom/media/FrameStatistics.h
+++ b/dom/media/FrameStatistics.h
@@ -124,16 +124,39 @@ public:
   // Increments the presented frame counters.
   // Can be called on any thread.
   void NotifyPresentedFrame()
   {
     ReentrantMonitorAutoEnter mon(mReentrantMonitor);
     ++mFrameStatisticsData.mPresentedFrames;
   }
 
+  // Stack based class to assist in notifying the frame statistics of
+  // parsed and decoded frames. Use inside video demux & decode functions
+  // to ensure all parsed and decoded frames are reported on all return paths.
+  class AutoNotifyDecoded
+  {
+  public:
+    explicit AutoNotifyDecoded(FrameStatistics* aFrameStats)
+      : mFrameStats(aFrameStats)
+    {
+    }
+    ~AutoNotifyDecoded()
+    {
+      if (mFrameStats) {
+        mFrameStats->NotifyDecodedFrames(mStats);
+      }
+    }
+
+    FrameStatisticsData mStats;
+
+  private:
+    FrameStatistics* mFrameStats;
+  };
+
 private:
   ~FrameStatistics() {}
 
   // ReentrantMonitor to protect access of playback statistics.
   mutable ReentrantMonitor mReentrantMonitor;
 
   FrameStatisticsData mFrameStatisticsData;
 };
--- a/dom/media/MediaDecoder.cpp
+++ b/dom/media/MediaDecoder.cpp
@@ -279,18 +279,16 @@ constexpr TimeUnit MediaDecoder::DEFAULT
 void
 MediaDecoder::InitStatics()
 {
   MOZ_ASSERT(NS_IsMainThread());
 }
 
 NS_IMPL_ISUPPORTS(MediaMemoryTracker, nsIMemoryReporter)
 
-NS_IMPL_ISUPPORTS0(MediaDecoder)
-
 void
 MediaDecoder::NotifyOwnerActivityChanged(bool aIsDocumentVisible,
                                          Visibility aElementVisibility,
                                          bool aIsElementInTree)
 {
   MOZ_ASSERT(NS_IsMainThread());
   MOZ_DIAGNOSTIC_ASSERT(!IsShutdown());
   SetElementVisibility(aIsDocumentVisible, aElementVisibility, aIsElementInTree);
--- a/dom/media/MediaDecoder.h
+++ b/dom/media/MediaDecoder.h
@@ -2,17 +2,16 @@
 /* vim:set ts=2 sw=2 sts=2 et cindent: */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #if !defined(MediaDecoder_h_)
 #define MediaDecoder_h_
 
-#include "AbstractMediaDecoder.h"
 #include "DecoderDoctorDiagnostics.h"
 #include "MediaDecoderOwner.h"
 #include "MediaEventSource.h"
 #include "MediaMetadataManager.h"
 #include "MediaResource.h"
 #include "MediaStatistics.h"
 #include "MediaStreamGraph.h"
 #include "SeekTarget.h"
@@ -31,16 +30,17 @@
 #include "nsISupports.h"
 #include "nsITimer.h"
 
 class nsIPrincipal;
 
 namespace mozilla {
 
 class AbstractThread;
+class FrameStatistics;
 class VideoFrameContainer;
 class MediaFormatReader;
 class MediaDecoderStateMachine;
 
 enum class MediaEventType : int8_t;
 enum class Visibility : uint8_t;
 
 // GetCurrentTime is defined in winbase.h as zero argument macro forwarding to
@@ -78,24 +78,24 @@ struct MOZ_STACK_CLASS MediaDecoderInit
     , mMinimizePreroll(aMinimizePreroll)
     , mHasSuspendTaint(aHasSuspendTaint)
     , mLooping(aLooping)
     , mContainerType(aContainerType)
   {
   }
 };
 
-class MediaDecoder : public AbstractMediaDecoder
+class MediaDecoder
 {
 public:
   typedef MozPromise<bool /* aIgnored */, bool /* aIgnored */,
                      /* IsExclusive = */ true>
     SeekPromise;
 
-  NS_DECL_THREADSAFE_ISUPPORTS
+  NS_INLINE_DECL_THREADSAFE_REFCOUNTING(MediaDecoder)
 
   // Enumeration for the valid play states (see mPlayState)
   enum PlayState
   {
     PLAY_STATE_START,
     PLAY_STATE_LOADING,
     PLAY_STATE_PAUSED,
     PLAY_STATE_PLAYING,
@@ -290,21 +290,22 @@ private:
       mCallback.ResolveIfExists(mByteSize, __func__);
     }
 
     MozPromiseHolder<SizeOfPromise> mCallback;
   };
 
   virtual void AddSizeOfResources(ResourceSizes* aSizes);
 
-  VideoFrameContainer* GetVideoFrameContainer() final override
+  VideoFrameContainer* GetVideoFrameContainer()
   {
     return mVideoFrameContainer;
   }
-  layers::ImageContainer* GetImageContainer() override;
+
+  layers::ImageContainer* GetImageContainer();
 
   // Fire timeupdate events if needed according to the time constraints
   // outlined in the specification.
   void FireTimeUpdate();
 
   // Something has changed that could affect the computed playback rate,
   // so recompute it. The monitor must be held.
   virtual void UpdatePlaybackRate();
@@ -380,19 +381,19 @@ private:
   int64_t GetDownloadPosition();
 
   // Notifies the element that decoding has failed.
   void DecodeError(const MediaResult& aError);
 
   // Indicate whether the media is same-origin with the element.
   void UpdateSameOriginStatus(bool aSameOrigin);
 
-  MediaDecoderOwner* GetOwner() const override;
+  MediaDecoderOwner* GetOwner() const;
 
-  AbstractThread* AbstractMainThread() const final override
+  AbstractThread* AbstractMainThread() const
   {
     return mAbstractMainThread;
   }
 
   typedef MozPromise<RefPtr<CDMProxy>, bool /* aIgnored */,
                      /* IsExclusive = */ true>
     CDMProxyPromise;
 
@@ -417,23 +418,16 @@ private:
   // This can be called from any thread. It's only a snapshot of the
   // current state, since other threads might be changing the state
   // at any time.
   MediaStatistics GetStatistics();
 
   // Return the frame decode/paint related statistics.
   FrameStatistics& GetFrameStatistics() { return *mFrameStats; }
 
-  // Increments the parsed and decoded frame counters by the passed in counts.
-  // Can be called on any thread.
-  virtual void NotifyDecodedFrames(const FrameStatisticsData& aStats) override
-  {
-    GetFrameStatistics().NotifyDecodedFrames(aStats);
-  }
-
   void UpdateReadyState()
   {
     MOZ_ASSERT(NS_IsMainThread());
     MOZ_DIAGNOSTIC_ASSERT(!IsShutdown());
     GetOwner()->UpdateReadyState();
   }
 
   virtual MediaDecoderOwner::NextFrameStatus NextFrameStatus()
--- a/dom/media/MediaDecoderOwner.h
+++ b/dom/media/MediaDecoderOwner.h
@@ -1,16 +1,17 @@
 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 /* vim:set ts=2 sw=2 sts=2 et cindent: */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 #ifndef MediaDecoderOwner_h_
 #define MediaDecoderOwner_h_
-#include "AbstractMediaDecoder.h"
+
+#include "MediaInfo.h"
 #include "nsAutoPtr.h"
 
 namespace mozilla {
 
 class AbstractThread;
 class GMPCrashHelper;
 class VideoFrameContainer;
 class MediaInfo;
--- a/dom/media/MediaDecoderStateMachine.cpp
+++ b/dom/media/MediaDecoderStateMachine.cpp
@@ -1952,25 +1952,22 @@ private:
 class MediaDecoderStateMachine::CompletedState
   : public MediaDecoderStateMachine::StateObject
 {
 public:
   explicit CompletedState(Master* aPtr) : StateObject(aPtr) { }
 
   void Enter()
   {
-    // TODO : use more approriate way to decide whether need to release
-    // resource in bug1367983.
-#ifndef MOZ_WIDGET_ANDROID
     if (!mMaster->mLooping) {
       // We've decoded all samples.
       // We don't need decoders anymore if not looping.
       Reader()->ReleaseResources();
     }
-#endif
+
     bool hasNextFrame = (!mMaster->HasAudio() || !mMaster->mAudioCompleted)
                         && (!mMaster->HasVideo() || !mMaster->mVideoCompleted);
 
     mMaster->UpdateNextFrameStatus(
       hasNextFrame ? MediaDecoderOwner::NEXT_FRAME_AVAILABLE
                    : MediaDecoderOwner::NEXT_FRAME_UNAVAILABLE);
 
     Step();
--- a/dom/media/MediaFormatReader.cpp
+++ b/dom/media/MediaFormatReader.cpp
@@ -1108,20 +1108,20 @@ MediaFormatReader::MediaFormatReader(Med
   , mKnowsCompositor(aInit.mKnowsCompositor)
   , mInitDone(false)
   , mTrackDemuxersMayBlock(false)
   , mSeekScheduled(false)
   , mVideoFrameContainer(aInit.mVideoFrameContainer)
   , mCrashHelper(aInit.mCrashHelper)
   , mDecoderFactory(new DecoderFactory(this))
   , mShutdownPromisePool(new ShutdownPromisePool())
-  , mDecoder(aInit.mDecoder)
   , mBuffered(mTaskQueue,
               TimeIntervals(),
               "MediaFormatReader::mBuffered (Canonical)")
+  , mFrameStats(aInit.mFrameStats)
 {
   MOZ_ASSERT(aDemuxer);
   MOZ_COUNT_CTOR(MediaFormatReader);
   mOnTrackWaitingForKeyListener = OnTrackWaitingForKey().Connect(
     mTaskQueue, this, &MediaFormatReader::NotifyWaitingForKey);
 }
 
 MediaFormatReader::~MediaFormatReader()
@@ -1207,17 +1207,16 @@ MediaFormatReader::TearDownDecoders()
   }
 
   mDecoderFactory = nullptr;
   mPlatform = nullptr;
   mVideoFrameContainer = nullptr;
 
   ReleaseResources();
   mBuffered.DisconnectAll();
-  mDecoder = nullptr;
   return mTaskQueue->BeginShutdown();
 }
 
 nsresult
 MediaFormatReader::Init()
 {
   MOZ_ASSERT(NS_IsMainThread(), "Must be on main thread.");
 
@@ -1919,17 +1918,17 @@ MediaFormatReader::DecodeDemuxedSamples(
              decoder.mDecodeRequest.Complete();
              NotifyError(aTrack, aError);
            })
     ->Track(decoder.mDecodeRequest);
 }
 
 void
 MediaFormatReader::HandleDemuxedSamples(
-  TrackType aTrack, AbstractMediaDecoder::AutoNotifyDecoded& aA)
+  TrackType aTrack, FrameStatistics::AutoNotifyDecoded& aA)
 {
   MOZ_ASSERT(OnTaskQueue());
 
   auto& decoder = GetDecoderData(aTrack);
 
   if (decoder.mFlushing) {
     LOGV("Decoder operation in progress, let it complete.");
     return;
@@ -2145,17 +2144,17 @@ MediaFormatReader::Update(TrackType aTra
 
   MOZ_DIAGNOSTIC_ASSERT(
     !decoder.HasInternalSeekPending()
     || (!decoder.mOutput.Length() && !decoder.mQueuedSamples.Length()),
     "No frames can be demuxed or decoded while an internal seek is pending");
 
   // Record number of frames decoded and parsed. Automatically update the
   // stats counters using the AutoNotifyDecoded stack-based class.
-  AbstractMediaDecoder::AutoNotifyDecoded a(mDecoder);
+  FrameStatistics::AutoNotifyDecoded a(mFrameStats);
 
   // Drop any frames found prior our internal seek target.
   while (decoder.mTimeThreshold && decoder.mOutput.Length()) {
     RefPtr<MediaData>& output = decoder.mOutput[0];
     InternalSeekTarget target = decoder.mTimeThreshold.ref();
     auto time = output->mTime;
     if (time >= target.Time()) {
       // We have reached our internal seek target.
@@ -2515,18 +2514,18 @@ MediaFormatReader::DropDecodedSamples(Tr
     auto time = decoder.mOutput.LastElement()->mTime;
     if (time >= decoder.mTimeThreshold.ref().Time()) {
       // We would have reached our internal seek target.
       decoder.mTimeThreshold.reset();
     }
   }
   decoder.mOutput.Clear();
   decoder.mSizeOfQueue -= lengthDecodedQueue;
-  if (aTrack == TrackInfo::kVideoTrack && mDecoder) {
-    mDecoder->NotifyDecodedFrames({ 0, 0, lengthDecodedQueue });
+  if (aTrack == TrackInfo::kVideoTrack && mFrameStats) {
+    mFrameStats->NotifyDecodedFrames({ 0, 0, lengthDecodedQueue });
   }
 }
 
 void
 MediaFormatReader::SkipVideoDemuxToNextKeyFrame(TimeUnit aTimeThreshold)
 {
   MOZ_ASSERT(OnTaskQueue());
   LOG("Skipping up to %" PRId64, aTimeThreshold.ToMicroseconds());
@@ -2547,26 +2546,26 @@ void
 MediaFormatReader::VideoSkipReset(uint32_t aSkipped)
 {
   MOZ_ASSERT(OnTaskQueue());
 
   // Some frames may have been output by the decoder since we initiated the
   // videoskip process and we know they would be late.
   DropDecodedSamples(TrackInfo::kVideoTrack);
   // Report the pending frames as dropped.
-  if (mDecoder) {
-    mDecoder->NotifyDecodedFrames({ 0, 0, SizeOfVideoQueueInFrames() });
+  if (mFrameStats) {
+    mFrameStats->NotifyDecodedFrames({ 0, 0, SizeOfVideoQueueInFrames() });
   }
 
   // Cancel any pending demux request and pending demuxed samples.
   mVideo.mDemuxRequest.DisconnectIfExists();
   Reset(TrackType::kVideoTrack);
 
-  if (mDecoder) {
-    mDecoder->NotifyDecodedFrames({ aSkipped, 0, aSkipped });
+  if (mFrameStats) {
+    mFrameStats->NotifyDecodedFrames({ aSkipped, 0, aSkipped });
   }
 
   mVideo.mNumSamplesSkippedTotal += aSkipped;
 }
 
 void
 MediaFormatReader::OnVideoSkipCompleted(uint32_t aSkipped)
 {
--- a/dom/media/MediaFormatReader.h
+++ b/dom/media/MediaFormatReader.h
@@ -4,30 +4,31 @@
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #if !defined(MediaFormatReader_h_)
 #define MediaFormatReader_h_
 
 #include "mozilla/Atomics.h"
 #include "mozilla/Maybe.h"
+#include "mozilla/StateMirroring.h"
 #include "mozilla/TaskQueue.h"
 #include "mozilla/Mutex.h"
 
+#include "FrameStatistics.h"
 #include "MediaEventSource.h"
 #include "MediaDataDemuxer.h"
 #include "MediaMetadataManager.h"
 #include "MediaPrefs.h"
 #include "nsAutoPtr.h"
 #include "PDMFactory.h"
 #include "SeekTarget.h"
 
 namespace mozilla {
 
-class AbstractMediaDecoder;
 class CDMProxy;
 class GMPCrashHelper;
 class MediaResource;
 class VideoFrameContainer;
 
 struct WaitForDataRejectValue
 {
   enum Reason
@@ -69,26 +70,21 @@ struct SeekRejectValue
 struct MetadataHolder
 {
   UniquePtr<MediaInfo> mInfo;
   UniquePtr<MetadataTags> mTags;
 };
 
 struct MOZ_STACK_CLASS MediaFormatReaderInit
 {
-  AbstractMediaDecoder* const mDecoder;
   MediaResource* mResource = nullptr;
   VideoFrameContainer* mVideoFrameContainer = nullptr;
+  FrameStatistics* mFrameStats = nullptr;
   already_AddRefed<layers::KnowsCompositor> mKnowsCompositor;
   already_AddRefed<GMPCrashHelper> mCrashHelper;
-
-  explicit MediaFormatReaderInit(AbstractMediaDecoder* aDecoder)
-    : mDecoder(aDecoder)
-  {
-  }
 };
 
 class MediaFormatReader final
 {
   static const bool IsExclusive = true;
   typedef TrackInfo::TrackType TrackType;
   typedef MozPromise<bool, MediaResult, IsExclusive> NotifyDataArrivedPromise;
 
@@ -275,17 +271,17 @@ private:
   void Update(TrackType aTrack);
   // Handle actions should more data be received.
   // Returns true if no more action is required.
   bool UpdateReceivedNewData(TrackType aTrack);
   // Called when new samples need to be demuxed.
   void RequestDemuxSamples(TrackType aTrack);
   // Handle demuxed samples by the input behavior.
   void HandleDemuxedSamples(TrackType aTrack,
-                            AbstractMediaDecoder::AutoNotifyDecoded& aA);
+                            FrameStatistics::AutoNotifyDecoded& aA);
   // Decode any pending already demuxed samples.
   void DecodeDemuxedSamples(TrackType aTrack,
                             MediaRawData* aSample);
 
   struct InternalSeekTarget
   {
     InternalSeekTarget(const media::TimeInterval& aTime, bool aDropTarget)
       : mTime(aTime)
@@ -740,19 +736,16 @@ private:
   UniquePtr<MetadataTags> mTags;
 
   // A flag indicating if the start time is known or not.
   bool mHasStartTime = false;
 
   void ShutdownDecoder(TrackType aTrack);
   RefPtr<ShutdownPromise> TearDownDecoders();
 
-  // Reference to the owning decoder object.
-  AbstractMediaDecoder* mDecoder;
-
   bool mShutdown = false;
 
   // Buffered range.
   Canonical<media::TimeIntervals> mBuffered;
 
   // Used to send TimedMetadata to the listener.
   TimedMetadataEventProducer mTimedMetadataEvent;
 
@@ -762,13 +755,15 @@ private:
   // Notify if we are waiting for a decryption key.
   MediaEventProducer<TrackInfo::TrackType> mOnTrackWaitingForKey;
 
   MediaEventProducer<nsTArray<uint8_t>, nsString> mOnEncrypted;
 
   MediaEventProducer<void> mOnWaitingForKey;
 
   MediaEventProducer<MediaResult> mOnDecodeWarning;
+
+  RefPtr<FrameStatistics> mFrameStats;
 };
 
 } // namespace mozilla
 
 #endif
--- a/dom/media/MediaMetadataManager.h
+++ b/dom/media/MediaMetadataManager.h
@@ -6,17 +6,16 @@
 
 #if !defined(MediaMetadataManager_h__)
 #define MediaMetadataManager_h__
 
 #include "mozilla/AbstractThread.h"
 #include "mozilla/LinkedList.h"
 
 #include "nsAutoPtr.h"
-#include "AbstractMediaDecoder.h"
 #include "MediaEventSource.h"
 #include "TimeUnits.h"
 #include "VideoUtils.h"
 
 namespace mozilla {
 
 class TimedMetadata;
 typedef MediaEventProducerExc<TimedMetadata> TimedMetadataEventProducer;
--- a/dom/media/flac/FlacDecoder.cpp
+++ b/dom/media/flac/FlacDecoder.cpp
@@ -21,18 +21,19 @@ FlacDecoder::Clone(MediaDecoderInit& aIn
   }
 
   return new FlacDecoder(aInit);
 }
 
 MediaDecoderStateMachine*
 FlacDecoder::CreateStateMachine()
 {
-  MediaFormatReaderInit init(this);
+  MediaFormatReaderInit init;
   init.mCrashHelper = GetOwner()->CreateGMPCrashHelper();
+  init.mFrameStats = mFrameStats;
   mReader = new MediaFormatReader(init, new FlacDemuxer(mResource));
   return new MediaDecoderStateMachine(this, mReader);
 }
 
 /* static */ bool
 FlacDecoder::IsEnabled()
 {
 #ifdef MOZ_FFVPX
--- a/dom/media/fmp4/MP4Decoder.cpp
+++ b/dom/media/fmp4/MP4Decoder.cpp
@@ -27,20 +27,21 @@ namespace mozilla {
 
 MP4Decoder::MP4Decoder(MediaDecoderInit& aInit)
   : ChannelMediaDecoder(aInit)
 {
 }
 
 MediaDecoderStateMachine* MP4Decoder::CreateStateMachine()
 {
-  MediaFormatReaderInit init(this);
+  MediaFormatReaderInit init;
   init.mVideoFrameContainer = GetVideoFrameContainer();
   init.mKnowsCompositor = GetCompositor();
   init.mCrashHelper = GetOwner()->CreateGMPCrashHelper();
+  init.mFrameStats = mFrameStats;
   mReader = new MediaFormatReader(init, new MP4Demuxer(mResource));
   return new MediaDecoderStateMachine(this, mReader);
 }
 
 static bool
 IsWhitelistedH264Codec(const nsAString& aCodec)
 {
   int16_t profile = 0, level = 0;
--- a/dom/media/hls/HLSDecoder.cpp
+++ b/dom/media/hls/HLSDecoder.cpp
@@ -23,20 +23,21 @@ MediaDecoderStateMachine*
 HLSDecoder::CreateStateMachine()
 {
   MOZ_ASSERT(NS_IsMainThread());
 
   MediaResource* resource = GetResource();
   MOZ_ASSERT(resource);
   auto resourceWrapper = static_cast<HLSResource*>(resource)->GetResourceWrapper();
   MOZ_ASSERT(resourceWrapper);
-  MediaFormatReaderInit init(this);
+  MediaFormatReaderInit init;
   init.mVideoFrameContainer = GetVideoFrameContainer();
   init.mKnowsCompositor = GetCompositor();
   init.mCrashHelper = GetOwner()->CreateGMPCrashHelper();
+  init.mFrameStats = mFrameStats;
   mReader =
     new MediaFormatReader(init, new HLSDemuxer(resourceWrapper->GetPlayerId()));
 
   return new MediaDecoderStateMachine(this, mReader);
 }
 
 ChannelMediaDecoder*
 HLSDecoder::Clone(MediaDecoderInit& aInit)
--- a/dom/media/mediasource/MediaSourceDecoder.cpp
+++ b/dom/media/mediasource/MediaSourceDecoder.cpp
@@ -34,20 +34,21 @@ MediaSourceDecoder::MediaSourceDecoder(M
   mExplicitDuration.Set(Some(UnspecifiedNaN<double>()));
 }
 
 MediaDecoderStateMachine*
 MediaSourceDecoder::CreateStateMachine()
 {
   MOZ_ASSERT(NS_IsMainThread());
   mDemuxer = new MediaSourceDemuxer(AbstractMainThread());
-  MediaFormatReaderInit init(this);
+  MediaFormatReaderInit init;
   init.mVideoFrameContainer = GetVideoFrameContainer();
   init.mKnowsCompositor = GetCompositor();
   init.mCrashHelper = GetOwner()->CreateGMPCrashHelper();
+  init.mFrameStats = mFrameStats;
   mReader = new MediaFormatReader(init, mDemuxer);
   return new MediaDecoderStateMachine(this, mReader);
 }
 
 nsresult
 MediaSourceDecoder::Load(nsIPrincipal* aPrincipal)
 {
   MOZ_ASSERT(NS_IsMainThread());
--- a/dom/media/mediasource/TrackBuffersManager.cpp
+++ b/dom/media/mediasource/TrackBuffersManager.cpp
@@ -57,17 +57,17 @@ AppendStateToStr(SourceBufferAttributes:
       return "IMPOSSIBLE";
   }
 }
 
 static Atomic<uint32_t> sStreamSourceID(0u);
 
 class DispatchKeyNeededEvent : public Runnable {
 public:
-  DispatchKeyNeededEvent(AbstractMediaDecoder* aDecoder,
+  DispatchKeyNeededEvent(MediaSourceDecoder* aDecoder,
                          const nsTArray<uint8_t>& aInitData,
                          const nsString& aInitDataType)
     : Runnable("DispatchKeyNeededEvent")
     , mDecoder(aDecoder)
     , mInitData(aInitData)
     , mInitDataType(aInitDataType)
   {
   }
@@ -77,17 +77,17 @@ public:
     MediaDecoderOwner* owner = mDecoder->GetOwner();
     if (owner) {
       owner->DispatchEncrypted(mInitData, mInitDataType);
     }
     mDecoder = nullptr;
     return NS_OK;
   }
 private:
-  RefPtr<AbstractMediaDecoder> mDecoder;
+  RefPtr<MediaSourceDecoder> mDecoder;
   nsTArray<uint8_t> mInitData;
   nsString mInitDataType;
 };
 
 TrackBuffersManager::TrackBuffersManager(MediaSourceDecoder* aParentDecoder,
                                          const MediaContainerType& aType)
   : mInputBuffer(new MediaByteBuffer)
   , mBufferFull(false)
--- a/dom/media/moz.build
+++ b/dom/media/moz.build
@@ -79,17 +79,16 @@ if CONFIG['MOZ_WEBRTC']:
 XPIDL_SOURCES += [
     'nsIDOMNavigatorUserMedia.idl',
     'nsIMediaManager.idl',
 ]
 
 XPIDL_MODULE = 'dom_media'
 
 EXPORTS += [
-    'AbstractMediaDecoder.h',
     'ADTSDecoder.h',
     'ADTSDemuxer.h',
     'AudioBufferUtils.h',
     'AudioChannelFormat.h',
     'AudioCompactor.h',
     'AudioConverter.h',
     'AudioMixer.h',
     'AudioPacketizer.h',
--- a/dom/media/mp3/MP3Decoder.cpp
+++ b/dom/media/mp3/MP3Decoder.cpp
@@ -21,18 +21,19 @@ MP3Decoder::Clone(MediaDecoderInit& aIni
   if (!IsEnabled()) {
     return nullptr;
   }
   return new MP3Decoder(aInit);
 }
 
 MediaDecoderStateMachine*
 MP3Decoder::CreateStateMachine() {
-  MediaFormatReaderInit init(this);
+  MediaFormatReaderInit init;
   init.mCrashHelper = GetOwner()->CreateGMPCrashHelper();
+  init.mFrameStats = mFrameStats;
   mReader = new MediaFormatReader(init, new MP3Demuxer(mResource));
   return new MediaDecoderStateMachine(this, mReader);
 }
 
 /* static */
 bool
 MP3Decoder::IsEnabled() {
   RefPtr<PDMFactory> platform = new PDMFactory();
--- a/dom/media/ogg/OggDecoder.cpp
+++ b/dom/media/ogg/OggDecoder.cpp
@@ -11,20 +11,21 @@
 #include "OggDemuxer.h"
 #include "OggDecoder.h"
 
 namespace mozilla {
 
 MediaDecoderStateMachine* OggDecoder::CreateStateMachine()
 {
   RefPtr<OggDemuxer> demuxer = new OggDemuxer(mResource);
-  MediaFormatReaderInit init(this);
+  MediaFormatReaderInit init;
   init.mVideoFrameContainer = GetVideoFrameContainer();
   init.mKnowsCompositor = GetCompositor();
   init.mCrashHelper = GetOwner()->CreateGMPCrashHelper();
+  init.mFrameStats = mFrameStats;
   mReader = new MediaFormatReader(init, demuxer);
   demuxer->SetChainingEvents(&mReader->TimedMetadataProducer(),
                              &mReader->MediaNotSeekableProducer());
   return new MediaDecoderStateMachine(this, mReader);
 }
 
 /* static */
 bool
--- a/dom/media/ogg/OggDemuxer.cpp
+++ b/dom/media/ogg/OggDemuxer.cpp
@@ -1,17 +1,16 @@
  /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 /* vim:set ts=2 sw=2 sts=2 et cindent: */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "nsError.h"
 #include "MediaDecoderStateMachine.h"
-#include "AbstractMediaDecoder.h"
 #include "OggDemuxer.h"
 #include "OggCodecState.h"
 #include "mozilla/AbstractThread.h"
 #include "mozilla/Atomics.h"
 #include "mozilla/PodOperations.h"
 #include "mozilla/SharedThreadPool.h"
 #include "mozilla/Telemetry.h"
 #include "mozilla/TimeStamp.h"
--- a/dom/media/test/test_mediarecorder_principals.html
+++ b/dom/media/test/test_mediarecorder_principals.html
@@ -10,84 +10,80 @@ https://bugzilla.mozilla.org/show_bug.cg
   <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
   <script type="text/javascript" src="manifest.js"></script>
 </head>
 <body>
 <div>
   <a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1018299">Test for MediaRecorder Principal Handling</a>
 </div>
 
-<video id="v1" preload="auto"></video>
-<video id="v2" preload="auto"></video>
+<video id="v1" preload="metadata"></video>
+<video id="v2" preload="metadata"></video>
 
 <pre id="test">
 <script type="text/javascript">
 SimpleTest.waitForExplicitFinish();
 
-var pushPrefs = (...p) => SpecialPowers.pushPrefEnv({set: p});
-var throwOutside = e => setTimeout(() => { throw e; });
+let throwOutside = e => setTimeout(() => { throw e; });
 
 // Generate a random key. The first load with that key will return
 // data, the second and subsequent loads with that key will return a redirect
 // to a different origin ('localhost:8888' will be redirected to 'example.org',
-// and 'example.org' will be redirected to 'localhost:8888'). We rely on the
-// fact that Ogg will do a seek to the end of the resource, triggering a new
-// load with the same key which will return a same-origin resource.
+// and 'example.org' will be redirected to 'localhost:8888').
 // Loading data from two different origins should be detected by the media
 // cache and result in a null principal so that the MediaRecorder usages below
 // fail.
+// This test relies on that preloading the metadata then loading a sufficiently
+// long video will result in two separate requests to load the resource. This
+// ends up relying on the impl of MediaCache and friends and we should probably
+// replace this test with a more robust gtest or the like.
 let key = Math.floor(Math.random()*100000000);
 let interval;
 
 function testPrincipals(resource) {
   if (!resource) {
     todo(false, "No types supported");
     return;
   }
-  // Reduce cache size and cache-ahead to make HTMLMediaElement do partial requests.
-  return pushPrefs(['media.cache_readahead_limit', 2],
-                   ['media.cache_size', 192])
-  .then(() => {
-    // First test: Load file from same-origin first, then get redirected to
-    // another origin before attempting to record stream.
-    let video = document.getElementById("v1");
-    video.src =
-        "http://mochi.test:8888/tests/dom/media/test/dynamic_redirect.sjs?key=v1_" +
-        key + "&res=" + resource.name;
-    video.load();
-    // To limit readahead, avoid racing with playback and "catching up" mode.
-    return new Promise(resolve => video.oncanplaythrough = resolve).then(() => {
-      video.play();
-      interval = setInterval(() => info("video.currentTime = "+ video.currentTime), 1000);
+  // First test: Load file from same-origin first, then get redirected to
+  // another origin before attempting to record stream.
+  let video = document.getElementById("v1");
+  video.src =
+      "http://mochi.test:8888/tests/dom/media/test/dynamic_redirect.sjs?key=v1_" +
+      key + "&res=" + resource.name;
+  video.load();
+  // To limit readahead, avoid racing with playback and "catching up" mode.
+  return new Promise(resolve => video.onloadeddata = resolve).then(() => {
+    video.play();
+    interval = setInterval(() => info("video.currentTime = "+ video.currentTime), 1000);
 
-      let msg = "mediaRecorder.start() must throw SecurityError";
-      return new Promise(resolve => video.onplaying = resolve)
-      .then(() => waitUntil(() => video.currentTime > resource.duration / 2))
-      // Test failure of the next step only, so "catch-bypass" any errors above.
-      .then(() => Promise.resolve()
-        .then(() => new MediaRecorder(video.mozCaptureStreamUntilEnded()).start())
-        .then(() => ok(false, msg), e => is(e.name, "SecurityError", msg)), 0)
-      .then(() => clearInterval(interval));
-    });
+    let msg = "mediaRecorder.start() must throw SecurityError";
+    return new Promise(resolve => video.onplaying = resolve)
+    .then(() => waitUntil(() => video.currentTime > resource.duration / 5))
+    // Test failure of the next step only, so "catch-bypass" any errors above.
+    .then(() => Promise.resolve()
+      .then(() => new MediaRecorder(video.mozCaptureStreamUntilEnded()).start())
+      .then(() => ok(false, msg), e => is(e.name, "SecurityError", msg)), 0)
+    .then(() => clearInterval(interval));
   })
   .then(() => {
     // Second test: Load file from same-origin first, but record ASAP, before
     // getting redirected to another origin.
     let video = document.getElementById("v2");
     video.src =
         "http://mochi.test:8888/tests/dom/media/test/dynamic_redirect.sjs?key=v2_" +
         key + "&res=" + resource.name;
     video.load();
     let rec, hasStopped, hasEnded = new Promise(r => video.onended = r);
     let data = [];
 
     let msgNoThrow = "mediaRecorder.start() should not throw here";
     let msgSecErr = "mediaRecorder.onerror must fire SecurityError";
     let msgOnStop = "mediaRecorder.onstop must also have fired";
-    return new Promise(resolve => video.onloadedmetadata = resolve).then(() => {
+    return new Promise(resolve => video.onloadeddata = resolve).then(() => {
       rec = new MediaRecorder(video.mozCaptureStreamUntilEnded());
       rec.ondataavailable = e => data.push(e.data);
       rec.start();
       hasStopped = new Promise(resolve => rec.onstop = resolve);
       video.play();
     })
     .then(() => ok(true, msgNoThrow), e => is(e.name, null, msgNoThrow))
     .then(() => Promise.race([
@@ -96,24 +92,24 @@ function testPrincipals(resource) {
     ]))
     .then(() => ok(false, msgSecErr), e => is(e.name, "SecurityError", msgSecErr))
     .then(() => Promise.race([hasStopped, hasEnded.then(() => Promise.reject())]))
     .then(() => ok(true, msgOnStop), e => ok(false, msgOnStop))
     .then(() => clearInterval(interval));
   });
 }
 
-testPrincipals(getPlayableVideo(gSeekTests))
+testPrincipals({ name:"pixel_aspect_ratio.mp4", type:"video/mp4", duration:28 })
 .catch(e => throwOutside(e))
 .then(() => SimpleTest.finish())
 .catch(e => throwOutside(e));
 
-var stop = stream => stream.getTracks().forEach(track => track.stop());
-var wait = ms => new Promise(resolve => setTimeout(resolve, ms));
-var waitUntil = f => new Promise(resolve => {
-  var ival = setInterval(() => f() && resolve(clearInterval(ival)), 100);
+let stop = stream => stream.getTracks().forEach(track => track.stop());
+let wait = ms => new Promise(resolve => setTimeout(resolve, ms));
+let waitUntil = f => new Promise(resolve => {
+  let ival = setInterval(() => f() && resolve(clearInterval(ival)), 100);
 });
 
 </script>
 </pre>
 
 </body>
 </html>
--- a/dom/media/wave/WaveDecoder.cpp
+++ b/dom/media/wave/WaveDecoder.cpp
@@ -17,18 +17,19 @@ ChannelMediaDecoder*
 WaveDecoder::Clone(MediaDecoderInit& aInit)
 {
   return new WaveDecoder(aInit);
 }
 
 MediaDecoderStateMachine*
 WaveDecoder::CreateStateMachine()
 {
-  MediaFormatReaderInit init(this);
+  MediaFormatReaderInit init;
   init.mCrashHelper = GetOwner()->CreateGMPCrashHelper();
+  init.mFrameStats = mFrameStats;
   mReader = new MediaFormatReader(init, new WAVDemuxer(mResource));
   return new MediaDecoderStateMachine(this, mReader);
 }
 
 /* static */ bool
 WaveDecoder::IsSupportedType(const MediaContainerType& aContainerType)
 {
   if (!IsWaveEnabled()) {
deleted file mode 100644
--- a/dom/media/webaudio/BufferDecoder.cpp
+++ /dev/null
@@ -1,69 +0,0 @@
-/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
-/* vim: set ts=8 sts=2 et sw=2 tw=80: */
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-#include "BufferDecoder.h"
-
-#include "nsISupports.h"
-#include "MediaResource.h"
-
-namespace mozilla {
-
-NS_IMPL_ISUPPORTS0(BufferDecoder)
-
-BufferDecoder::BufferDecoder(MediaResource* aResource,
-                             AbstractThread* aMainThread)
-  : mResource(aResource)
-  , mAbstractMainThread(aMainThread)
-{
-  MOZ_ASSERT(NS_IsMainThread());
-}
-
-BufferDecoder::~BufferDecoder()
-{
-  // The dtor may run on any thread, we cannot be sure.
-}
-
-void
-BufferDecoder::BeginDecoding(TaskQueue* aTaskQueueIdentity)
-{
-  MOZ_ASSERT(!mTaskQueueIdentity && aTaskQueueIdentity);
-  mTaskQueueIdentity = aTaskQueueIdentity;
-}
-
-void
-BufferDecoder::NotifyDecodedFrames(const FrameStatisticsData& aStats)
-{
-  // ignore
-}
-
-VideoFrameContainer*
-BufferDecoder::GetVideoFrameContainer()
-{
-  // no video frame
-  return nullptr;
-}
-
-layers::ImageContainer*
-BufferDecoder::GetImageContainer()
-{
-  // no image container
-  return nullptr;
-}
-
-MediaDecoderOwner*
-BufferDecoder::GetOwner() const
-{
-  // unknown
-  return nullptr;
-}
-
-AbstractThread*
-BufferDecoder::AbstractMainThread() const
-{
-  return mAbstractMainThread;
-}
-
-} // namespace mozilla
deleted file mode 100644
--- a/dom/media/webaudio/BufferDecoder.h
+++ /dev/null
@@ -1,53 +0,0 @@
-/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
-/* vim: set ts=8 sts=2 et sw=2 tw=80: */
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-#ifndef BUFFER_DECODER_H_
-#define BUFFER_DECODER_H_
-
-#include "mozilla/Attributes.h"
-#include "mozilla/ReentrantMonitor.h"
-#include "mozilla/TaskQueue.h"
-
-#include "AbstractMediaDecoder.h"
-
-namespace mozilla {
-
-/**
- * This class provides a decoder object which decodes a media file that lives in
- * a memory buffer.
- */
-class BufferDecoder final : public AbstractMediaDecoder
-{
-public:
-  // This class holds a weak pointer to MediaResource.  It's the responsibility
-  // of the caller to manage the memory of the MediaResource object.
-  explicit BufferDecoder(MediaResource* aResource,
-                         AbstractThread* aMainThread);
-
-  NS_DECL_THREADSAFE_ISUPPORTS
-
-  // This has to be called before decoding begins
-  void BeginDecoding(TaskQueue* aTaskQueueIdentity);
-
-  void NotifyDecodedFrames(const FrameStatisticsData& aStats) final override;
-
-  VideoFrameContainer* GetVideoFrameContainer() final override;
-  layers::ImageContainer* GetImageContainer() final override;
-
-  MediaDecoderOwner* GetOwner() const final override;
-
-  AbstractThread* AbstractMainThread() const final override;
-
-private:
-  virtual ~BufferDecoder();
-  RefPtr<TaskQueue> mTaskQueueIdentity;
-  RefPtr<MediaResource> mResource;
-  const RefPtr<AbstractThread> mAbstractMainThread;
-};
-
-} // namespace mozilla
-
-#endif /* BUFFER_DECODER_H_ */
--- a/dom/media/webaudio/MediaBufferDecoder.cpp
+++ b/dom/media/webaudio/MediaBufferDecoder.cpp
@@ -1,16 +1,15 @@
 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 /* vim:set ts=2 sw=2 sts=2 et cindent: */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "MediaBufferDecoder.h"
-#include "BufferDecoder.h"
 #include "mozilla/dom/AudioContextBinding.h"
 #include "mozilla/dom/BaseAudioContextBinding.h"
 #include "mozilla/dom/DOMException.h"
 #include "mozilla/dom/ScriptSettings.h"
 #include "mozilla/AbstractThread.h"
 #include <speex/speex_resampler.h>
 #include "nsXPCOMCIDInternal.h"
 #include "nsComponentManagerUtils.h"
@@ -128,41 +127,36 @@ private:
   void SampleNotDecoded(const MediaResult& aError);
   void FinishDecode();
   void AllocateBuffer();
   void CallbackTheResult();
 
   void Cleanup()
   {
     MOZ_ASSERT(NS_IsMainThread());
-    // MediaFormatReader expects that BufferDecoder is alive.
-    // Destruct MediaFormatReader first.
     mDecoderReader = nullptr;
-    mBufferDecoder = nullptr;
     JS_free(nullptr, mBuffer);
   }
 
 private:
   MediaContainerType mContainerType;
   uint8_t* mBuffer;
   uint32_t mLength;
   WebAudioDecodeJob& mDecodeJob;
   PhaseEnum mPhase;
-  RefPtr<BufferDecoder> mBufferDecoder;
   RefPtr<MediaFormatReader> mDecoderReader;
   MediaInfo mMediaInfo;
   MediaQueue<AudioData> mAudioQueue;
   RefPtr<AbstractThread> mMainThread;
   bool mFirstFrameDecoded;
 };
 
 NS_IMETHODIMP
 MediaDecodeTask::Run()
 {
-  MOZ_ASSERT(mBufferDecoder);
   MOZ_ASSERT(mDecoderReader);
   switch (mPhase) {
   case PhaseEnum::Decode:
     Decode();
     break;
   case PhaseEnum::AllocateBuffer:
     AllocateBuffer();
     break;
@@ -185,25 +179,23 @@ MediaDecodeTask::CreateReader()
   nsCOMPtr<nsIScriptObjectPrincipal> sop = do_QueryInterface(parent);
   if (sop) {
     principal = sop->GetPrincipal();
   }
 
   RefPtr<BufferMediaResource> resource =
     new BufferMediaResource(static_cast<uint8_t*>(mBuffer), mLength, principal);
 
-  MOZ_ASSERT(!mBufferDecoder);
   mMainThread =
     mDecodeJob.mContext->GetOwnerGlobal()->AbstractMainThreadFor(TaskCategory::Other);
-  mBufferDecoder = new BufferDecoder(resource, mMainThread);
 
   // If you change this list to add support for new decoders, please consider
   // updating HTMLMediaElement::CreateDecoder as well.
 
-  MediaFormatReaderInit init(mBufferDecoder);
+  MediaFormatReaderInit init;
   init.mResource = resource;
   mDecoderReader = DecoderTraits::CreateReader(mContainerType, init);
 
   if (!mDecoderReader) {
     return false;
   }
 
   nsresult rv = mDecoderReader->Init();
@@ -240,17 +232,16 @@ private:
   SpeexResamplerState* mResampler;
 };
 
 void
 MediaDecodeTask::Decode()
 {
   MOZ_ASSERT(!NS_IsMainThread());
 
-  mBufferDecoder->BeginDecoding(mDecoderReader->OwnerThread());
 
   mDecoderReader->AsyncReadMetadata()->Then(mDecoderReader->OwnerThread(), __func__, this,
                                        &MediaDecodeTask::OnMetadataRead,
                                        &MediaDecodeTask::OnMetadataNotRead);
 }
 
 void
 MediaDecodeTask::OnMetadataRead(MetadataHolder&& aMetadata)
--- a/dom/media/webaudio/moz.build
+++ b/dom/media/webaudio/moz.build
@@ -83,17 +83,16 @@ UNIFIED_SOURCES += [
     'AudioNode.cpp',
     'AudioNodeEngine.cpp',
     'AudioNodeExternalInputStream.cpp',
     'AudioNodeStream.cpp',
     'AudioParam.cpp',
     'AudioProcessingEvent.cpp',
     'AudioScheduledSourceNode.cpp',
     'BiquadFilterNode.cpp',
-    'BufferDecoder.cpp',
     'ChannelMergerNode.cpp',
     'ChannelSplitterNode.cpp',
     'ConstantSourceNode.cpp',
     'ConvolverNode.cpp',
     'DelayBuffer.cpp',
     'DelayNode.cpp',
     'DynamicsCompressorNode.cpp',
     'FFTBlock.cpp',
--- a/dom/media/webm/WebMDecoder.cpp
+++ b/dom/media/webm/WebMDecoder.cpp
@@ -13,20 +13,21 @@
 #include "WebMDemuxer.h"
 #include "WebMDecoder.h"
 #include "VideoUtils.h"
 
 namespace mozilla {
 
 MediaDecoderStateMachine* WebMDecoder::CreateStateMachine()
 {
-  MediaFormatReaderInit init(this);
+  MediaFormatReaderInit init;
   init.mVideoFrameContainer = GetVideoFrameContainer();
   init.mKnowsCompositor = GetCompositor();
   init.mCrashHelper = GetOwner()->CreateGMPCrashHelper();
+  init.mFrameStats = mFrameStats;
   mReader = new MediaFormatReader(init, new WebMDemuxer(mResource));
   return new MediaDecoderStateMachine(this, mReader);
 }
 
 /* static */
 bool
 WebMDecoder::IsSupportedType(const MediaContainerType& aContainerType)
 {
--- a/dom/media/webm/WebMDemuxer.cpp
+++ b/dom/media/webm/WebMDemuxer.cpp
@@ -1,17 +1,16 @@
 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 /* vim:set ts=2 sw=2 sts=2 et cindent: */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "nsError.h"
 #include "MediaDecoderStateMachine.h"
-#include "AbstractMediaDecoder.h"
 #include "MediaResource.h"
 #ifdef MOZ_AV1
 #include "AOMDecoder.h"
 #endif
 #include "OpusDecoder.h"
 #include "VPXDecoder.h"
 #include "WebMDemuxer.h"
 #include "WebMBufferedParser.h"
--- a/dom/xbl/nsXBLWindowKeyHandler.cpp
+++ b/dom/xbl/nsXBLWindowKeyHandler.cpp
@@ -528,24 +528,20 @@ nsXBLWindowKeyHandler::HandleEventOnCapt
 
 void
 nsXBLWindowKeyHandler::HandleEventOnCaptureInSystemEventGroup(
                          nsIDOMKeyEvent* aEvent)
 {
   WidgetKeyboardEvent* widgetEvent =
     aEvent->AsEvent()->WidgetEventPtr()->AsKeyboardEvent();
 
-  if (widgetEvent->IsCrossProcessForwardingStopped() ||
-      widgetEvent->mFlags.mOnlySystemGroupDispatchInContent) {
-    return;
-  }
-
-  nsCOMPtr<mozilla::dom::Element> originalTarget =
-    do_QueryInterface(aEvent->AsEvent()->WidgetEventPtr()->mOriginalTarget);
-  if (!EventStateManager::IsRemoteTarget(originalTarget)) {
+  // If the event won't be sent to remote process, this listener needs to do
+  // nothing.
+  if (widgetEvent->mFlags.mOnlySystemGroupDispatchInContent ||
+      !widgetEvent->WillBeSentToRemoteProcess()) {
     return;
   }
 
   if (!HasHandlerForEvent(aEvent)) {
     return;
   }
 
   // If this event wasn't marked as IsCrossProcessForwardingStopped,
--- a/js/src/devtools/rootAnalysis/analyzeHeapWrites.js
+++ b/js/src/devtools/rootAnalysis/analyzeHeapWrites.js
@@ -414,17 +414,16 @@ function ignoreContents(entry)
         "Gecko_SetNullImageValue",
 
         // The analysis thinks we'll write to mBits in the DoGetStyleFoo<false>
         // call.  Maybe the template parameter confuses it?
         /nsStyleContext::PeekStyle/,
 
         // Needs main thread assertions or other fixes.
         /UndisplayedMap::GetEntryFor/,
-        /nsStyleContext::CalcStyleDifferenceInternal/,
         /EffectCompositor::GetServoAnimationRule/,
         /LookAndFeel::GetColor/,
         "Gecko_CopyStyleContentsFrom",
         "Gecko_CSSValue_SetAbsoluteLength",
         "Gecko_UnsetDirtyStyleAttr",
         /nsCSSPropertyIDSet::AddProperty/,
     ];
     if (entry.matches(whitelist))
--- a/layout/base/PresShell.cpp
+++ b/layout/base/PresShell.cpp
@@ -8156,17 +8156,29 @@ PresShell::HandleEventInternal(WidgetEve
 
     // 2. Give event to the DOM for third party and JS use.
     if (NS_SUCCEEDED(rv)) {
       bool wasHandlingKeyBoardEvent =
         nsContentUtils::IsHandlingKeyBoardEvent();
       if (aEvent->mClass == eKeyboardEventClass) {
         nsContentUtils::SetIsHandlingKeyBoardEvent(true);
       }
-      if (aEvent->IsAllowedToDispatchDOMEvent()) {
+      // If EventStateManager or something wants reply from remote process and
+      // needs to win any other event listeners in chrome, the event is both
+      // stopped its propagation and marked as "waiting reply from remote
+      // process".  In this case, PresShell shouldn't dispatch the event into
+      // the DOM tree because they don't have a chance to stop propagation in
+      // the system event group.  On the other hand, if its propagation is not
+      // stopped, that means that the event may be reserved by chrome.  If it's
+      // reserved by chrome, the event shouldn't be sent to any remote
+      // processes.  In this case, PresShell needs to dispatch the event to
+      // the DOM tree for checking if it's reserved.
+      if (aEvent->IsAllowedToDispatchDOMEvent() &&
+          !(aEvent->PropagationStopped() &&
+            aEvent->IsWaitingReplyFromRemoteProcess())) {
         MOZ_ASSERT(nsContentUtils::IsSafeToRunScript(),
           "Somebody changed aEvent to cause a DOM event!");
         nsPresShellEventCB eventCB(this);
         if (nsIFrame* target = GetCurrentEventFrame()) {
           if (target->OnlySystemGroupDispatch(aEvent->mMessage)) {
               aEvent->StopPropagation();
           }
         }
--- a/layout/base/RestyleManager.cpp
+++ b/layout/base/RestyleManager.cpp
@@ -1809,18 +1809,28 @@ RestyleManager::AnimationsWithDestroyedF
                   nsTArray<RefPtr<nsIContent>>& aArray,
                   CSSPseudoElementType aPseudoType)
 {
   nsAnimationManager* animationManager =
     mRestyleManager->PresContext()->AnimationManager();
   nsTransitionManager* transitionManager =
     mRestyleManager->PresContext()->TransitionManager();
   for (nsIContent* content : aArray) {
-    if (content->GetPrimaryFrame()) {
-      continue;
+    if (aPseudoType == CSSPseudoElementType::NotPseudo) {
+      if (content->GetPrimaryFrame()) {
+        continue;
+      }
+    } else if (aPseudoType == CSSPseudoElementType::before) {
+      if (nsLayoutUtils::GetBeforeFrame(content)) {
+        continue;
+      }
+    } else if (aPseudoType == CSSPseudoElementType::after) {
+      if (nsLayoutUtils::GetAfterFrame(content)) {
+        continue;
+      }
     }
     dom::Element* element = content->AsElement();
 
     animationManager->StopAnimationsForElement(element, aPseudoType);
     transitionManager->StopAnimationsForElement(element, aPseudoType);
 
     // All other animations should keep running but not running on the
     // *compositor* at this point.
--- a/layout/base/ServoRestyleManager.cpp
+++ b/layout/base/ServoRestyleManager.cpp
@@ -598,22 +598,19 @@ ServoRestyleManager::ProcessPostTraversa
   // We can't really assume as used changes from display: contents elements (or
   // other elements without frames).
   ServoRestyleState& childrenRestyleState =
     thisFrameRestyleState ? *thisFrameRestyleState : aRestyleState;
 
   RefPtr<ServoStyleContext> newContext = nullptr;
   if (wasRestyled && oldStyleContext) {
     MOZ_ASSERT(styleFrame || displayContentsNode);
-    RefPtr<ServoStyleContext> currentContext =
+    newContext =
       aRestyleState.StyleSet().ResolveServoStyle(aElement, aRestyleBehavior);
-    MOZ_ASSERT(oldStyleContext->ComputedData() != currentContext->ComputedData());
-
-    newContext = currentContext;
-    newContext->UpdateWithElementState(aElement);
+    MOZ_ASSERT(oldStyleContext->ComputedData() != newContext->ComputedData());
 
     newContext->ResolveSameStructsAs(oldStyleContext);
 
     // We want to walk all the continuations here, even the ones with different
     // styles.  In practice, the only reason we get continuations with different
     // styles here is ::first-line (::first-letter never affects element
     // styles).  But in that case, newContext is the right context for the
     // _later_ continuations anyway (the ones not affected by ::first-line), not
--- a/layout/reftests/css-animations/reftest.list
+++ b/layout/reftests/css-animations/reftest.list
@@ -46,9 +46,11 @@ fails == background-position-important.h
 == mask-position-after-finish-1b.html mask-anim-ref.html
 == mask-position-in-delay-1a.html mask-anim-ref.html
 == mask-position-in-delay-1b.html mask-anim-ref.html
 == mask-size-after-finish-1a.html mask-anim-ref.html
 == mask-size-after-finish-1b.html mask-anim-ref.html
 == mask-size-in-delay-1a.html mask-anim-ref.html
 == mask-size-in-delay-1b.html mask-anim-ref.html
 
+== stop-animation-on-discarded-pseudo-element.html about:blank
+
 == updating-animation-on-pseudo-element.html updating-animation-on-pseudo-element-ref.html
new file mode 100644
--- /dev/null
+++ b/layout/reftests/css-animations/stop-animation-on-discarded-pseudo-element.html
@@ -0,0 +1,36 @@
+<!DOCTYPE html>
+<html class="reftest-wait">
+<style>
+@keyframes anim {
+  0% { background-color: red; }
+  100% { background-color: red; }
+}
+#target.x::before,
+#target.y::before {
+  content: "";
+  position: absolute;
+  width: 100px;
+  height: 100px;
+}
+#target.x::before {
+  animation: anim 100s infinite;
+}
+</style>
+<div id="target"></div>
+<script>
+var target = document.getElementById('target');
+requestAnimationFrame(() => {
+  // Create ::before, start animation
+  target.className = 'x';
+  requestAnimationFrame(() => {
+    // Remove ::before, stop animation
+    target.className = '';
+
+    requestAnimationFrame(() => {
+      // Create ::before, should not be animating
+      target.className = 'y';
+      document.documentElement.classList.remove('reftest-wait');
+    });
+  });
+});
+</script>
--- a/layout/style/ServoBindings.cpp
+++ b/layout/style/ServoBindings.cpp
@@ -21,16 +21,17 @@
 #include "nsCSSRuleProcessor.h"
 #include "nsCSSRules.h"
 #include "nsContentUtils.h"
 #include "nsDOMTokenList.h"
 #include "nsDeviceContext.h"
 #include "nsIContentInlines.h"
 #include "nsIDOMNode.h"
 #include "nsIDocumentInlines.h"
+#include "nsILoadContext.h"
 #include "nsIFrame.h"
 #include "nsINode.h"
 #include "nsIPresShell.h"
 #include "nsIPresShellInlines.h"
 #include "nsIPrincipal.h"
 #include "nsIURI.h"
 #include "nsFontMetrics.h"
 #include "nsHTMLStyleSheet.h"
@@ -277,16 +278,32 @@ Gecko_ElementBindingAnonymousContent(Raw
 
 RawGeckoNodeBorrowed
 Gecko_GetNextStyleChild(RawGeckoStyleChildrenIteratorBorrowedMut aIterator)
 {
   MOZ_ASSERT(aIterator);
   return aIterator->GetNextChild();
 }
 
+bool
+Gecko_IsPrivateBrowsingEnabled(const nsIDocument* aDoc)
+{
+  MOZ_ASSERT(aDoc);
+  MOZ_ASSERT(NS_IsMainThread());
+
+  nsILoadContext* loadContext = aDoc->GetLoadContext();
+  return loadContext && loadContext->UsePrivateBrowsing();
+}
+
+bool
+Gecko_AreVisitedLinksEnabled()
+{
+  return nsCSSRuleProcessor::VisitedLinksEnabled();
+}
+
 EventStates::ServoType
 Gecko_ElementState(RawGeckoElementBorrowed aElement)
 {
   return aElement->StyleState().ServoValue();
 }
 
 EventStates::ServoType
 Gecko_DocumentState(const nsIDocument* aDocument)
--- a/layout/style/ServoBindings.h
+++ b/layout/style/ServoBindings.h
@@ -224,16 +224,23 @@ RawServoDeclarationBlockStrongBorrowedOr
 Gecko_GetExtraContentStyleDeclarations(RawGeckoElementBorrowed element);
 RawServoDeclarationBlockStrongBorrowedOrNull
 Gecko_GetUnvisitedLinkAttrDeclarationBlock(RawGeckoElementBorrowed element);
 RawServoDeclarationBlockStrongBorrowedOrNull
 Gecko_GetVisitedLinkAttrDeclarationBlock(RawGeckoElementBorrowed element);
 RawServoDeclarationBlockStrongBorrowedOrNull
 Gecko_GetActiveLinkAttrDeclarationBlock(RawGeckoElementBorrowed element);
 
+// Visited handling.
+
+// Returns whether private browsing is enabled for a given element.
+bool Gecko_IsPrivateBrowsingEnabled(const nsIDocument* aDoc);
+// Returns whether visited links are enabled.
+bool Gecko_AreVisitedLinksEnabled();
+
 // Animations
 bool
 Gecko_GetAnimationRule(RawGeckoElementBorrowed aElementOrPseudo,
                        mozilla::EffectCompositor::CascadeLevel aCascadeLevel,
                        RawServoAnimationValueMapBorrowedMut aAnimationValues);
 RawServoDeclarationBlockStrongBorrowedOrNull
 Gecko_GetSMILOverrideDeclarationBlock(RawGeckoElementBorrowed element);
 bool Gecko_StyleAnimationsEquals(RawGeckoStyleAnimationListBorrowed,
--- a/layout/style/ServoStyleContext.cpp
+++ b/layout/style/ServoStyleContext.cpp
@@ -17,58 +17,19 @@ namespace mozilla {
 
 ServoStyleContext::ServoStyleContext(
     nsStyleContext* aParent,
     nsPresContext* aPresContext,
     nsIAtom* aPseudoTag,
     CSSPseudoElementType aPseudoType,
     ServoComputedDataForgotten aComputedValues)
   : nsStyleContext(aParent, aPseudoTag, aPseudoType)
+  , mPresContext(aPresContext)
   , mSource(aComputedValues)
 {
-  mPresContext = aPresContext;
   AddStyleBit(Servo_ComputedValues_GetStyleBits(this));
-
   FinishConstruction();
 
   // No need to call ApplyStyleFixups here, since fixups are handled by Servo when
   // producing the ServoComputedData.
 }
 
-void
-ServoStyleContext::UpdateWithElementState(Element* aElementForAnimation)
-{
-  bool isLink = false;
-  bool isVisitedLink = false;
-  // If we need visited styles for callers where `aElementForAnimation` is null,
-  // we can precompute these and pass them as flags, similar to nsStyleSet.cpp.
-  if (aElementForAnimation) {
-    isLink = nsCSSRuleProcessor::IsLink(aElementForAnimation);
-    isVisitedLink = nsCSSRuleProcessor::GetContentState(aElementForAnimation)
-                                       .HasState(NS_EVENT_STATE_VISITED);
-  }
-
-
-  auto parent = GetParentAllowServo();
-
-  // The true visited state of the relevant link is used to decided whether
-  // visited styles should be consulted for all visited dependent properties.
-  bool relevantLinkVisited = isLink ? isVisitedLink :
-    (parent && parent->RelevantLinkVisited());
-
-  if (relevantLinkVisited && GetStyleIfVisited()) {
-    AddStyleBit(NS_STYLE_RELEVANT_LINK_VISITED);
-  }
-
-  // Set the body color on the pres context. See nsStyleSet::GetContext
-  if (aElementForAnimation &&
-      aElementForAnimation->IsHTMLElement(nsGkAtoms::body) &&
-      GetPseudoType() == CSSPseudoElementType::NotPseudo &&
-      mPresContext->CompatibilityMode() == eCompatibility_NavQuirks) {
-    nsIDocument* doc = aElementForAnimation->GetUncomposedDoc();
-    if (doc && doc->GetBodyElement() == aElementForAnimation) {
-      // Update the prescontext's body color
-      mPresContext->SetBodyTextColor(StyleColor()->mColor);
-    }
-  }
-}
-
 } // namespace mozilla
--- a/layout/style/ServoStyleContext.h
+++ b/layout/style/ServoStyleContext.h
@@ -30,20 +30,16 @@ public:
   void AddRef() { Servo_StyleContext_AddRef(this); }
   void Release() { Servo_StyleContext_Release(this); }
 
   ServoStyleContext* GetStyleIfVisited() const
   {
     return ComputedData()->visited_style.mPtr;
   }
 
-  // Update visited state for a given element, and set the prescontext's
-  // body text color if applicable.
-  void UpdateWithElementState(dom::Element* aElement);
-
   /**
    * Makes this context match |aOther| in terms of which style structs have
    * been resolved.
    */
   inline void ResolveSameStructsAs(const ServoStyleContext* aOther);
 
 private:
   nsPresContext* mPresContext;
--- a/layout/style/ServoStyleSet.cpp
+++ b/layout/style/ServoStyleSet.cpp
@@ -170,43 +170,24 @@ ServoStyleSet::EndUpdate()
   return NS_OK;
 }
 
 already_AddRefed<ServoStyleContext>
 ServoStyleSet::ResolveStyleFor(Element* aElement,
                                ServoStyleContext* aParentContext,
                                LazyComputeBehavior aMayCompute)
 {
-  return GetContext(aElement, aParentContext, nullptr,
-                    CSSPseudoElementType::NotPseudo, aMayCompute);
-}
-
-already_AddRefed<ServoStyleContext>
-ServoStyleSet::GetContext(nsIContent* aContent,
-                          ServoStyleContext* aParentContext,
-                          nsIAtom* aPseudoTag,
-                          CSSPseudoElementType aPseudoType,
-                          LazyComputeBehavior aMayCompute)
-{
-  MOZ_ASSERT(aContent->IsElement());
-  Element* element = aContent->AsElement();
-
   RefPtr<ServoStyleContext> computedValues;
   if (aMayCompute == LazyComputeBehavior::Allow) {
     PreTraverseSync();
-    computedValues =
-      ResolveStyleLazily(element, CSSPseudoElementType::NotPseudo, aPseudoTag, aParentContext);
-      computedValues->UpdateWithElementState(element);
-  } else {
-    computedValues = ResolveServoStyle(element,
-                                       TraversalRestyleBehavior::Normal);
+    return ResolveStyleLazily(
+        aElement, CSSPseudoElementType::NotPseudo, nullptr, aParentContext);
   }
 
-  MOZ_ASSERT(computedValues);
-  return computedValues.forget();
+  return ResolveServoStyle(aElement, TraversalRestyleBehavior::Normal);
 }
 
 
 const ServoElementSnapshotTable&
 ServoStyleSet::Snapshots()
 {
   return mPresContext->RestyleManager()->AsServo()->Snapshots();
 }
@@ -379,32 +360,29 @@ ServoStyleSet::ResolveStyleForText(nsICo
   // rules: inherit the inherit structs, reset the reset structs. This is cheap
   // enough to do on the main thread, which means that the parallel style system
   // can avoid worrying about text nodes.
   RefPtr<ServoStyleContext> computedValues =
     Servo_ComputedValues_Inherit(mRawSet.get(),
                                  nsCSSAnonBoxes::mozText,
                                  aParentContext,
                                  InheritTarget::Text).Consume();
-  computedValues->UpdateWithElementState(nullptr);
   return computedValues.forget();
 }
 
 already_AddRefed<ServoStyleContext>
 ServoStyleSet::ResolveStyleForFirstLetterContinuation(ServoStyleContext* aParentContext)
 {
   RefPtr<ServoStyleContext> computedValues =
     Servo_ComputedValues_Inherit(mRawSet.get(),
                                  nsCSSAnonBoxes::firstLetterContinuation,
                                  aParentContext,
                                  InheritTarget::FirstLetterContinuation)
                                  .Consume();
   MOZ_ASSERT(computedValues);
-
-  computedValues->UpdateWithElementState(nullptr);
   return computedValues.forget();
 }
 
 already_AddRefed<ServoStyleContext>
 ServoStyleSet::ResolveStyleForPlaceholder()
 {
   RefPtr<ServoStyleContext>& cache =
     mNonInheritingStyleContexts[nsCSSAnonBoxes::NonInheriting::oofPlaceholder];
@@ -416,17 +394,16 @@ ServoStyleSet::ResolveStyleForPlaceholde
   RefPtr<ServoStyleContext> computedValues =
     Servo_ComputedValues_Inherit(mRawSet.get(),
                                  nsCSSAnonBoxes::oofPlaceholder,
                                  nullptr,
                                  InheritTarget::PlaceholderFrame)
                                  .Consume();
   MOZ_ASSERT(computedValues);
 
-  computedValues->UpdateWithElementState(nullptr);
   cache = computedValues;
   return computedValues.forget();
 }
 
 already_AddRefed<ServoStyleContext>
 ServoStyleSet::ResolvePseudoElementStyle(Element* aOriginatingElement,
                                          CSSPseudoElementType aType,
                                          ServoStyleContext* aParentContext,
@@ -449,32 +426,27 @@ ServoStyleSet::ResolvePseudoElementStyle
       Servo_ResolvePseudoStyle(aOriginatingElement,
                                aType,
                                /* is_probe = */ false,
                                aParentContext,
                                mRawSet.get()).Consume();
   }
 
   MOZ_ASSERT(computedValues);
-
-  bool isBeforeOrAfter = aType == CSSPseudoElementType::before ||
-                         aType == CSSPseudoElementType::after;
-  computedValues->UpdateWithElementState(isBeforeOrAfter ? aOriginatingElement : nullptr);
   return computedValues.forget();
 }
 
 already_AddRefed<ServoStyleContext>
 ServoStyleSet::ResolveTransientStyle(Element* aElement,
                                      CSSPseudoElementType aPseudoType,
                                      nsIAtom* aPseudoTag,
                                      StyleRuleInclusion aRuleInclusion)
 {
   RefPtr<ServoStyleContext> result =
     ResolveTransientServoStyle(aElement, aPseudoType, aPseudoTag, aRuleInclusion);
-  result->UpdateWithElementState(nullptr);
   return result.forget();
 }
 
 already_AddRefed<ServoStyleContext>
 ServoStyleSet::ResolveTransientServoStyle(
     Element* aElement,
     CSSPseudoElementType aPseudoType,
     nsIAtom* aPseudoTag,
@@ -502,17 +474,16 @@ ServoStyleSet::ResolveInheritingAnonymou
     nsString pseudo;
     aPseudoTag->ToString(pseudo);
     NS_ERROR(nsPrintfCString("stylo: could not get anon-box: %s",
              NS_ConvertUTF16toUTF8(pseudo).get()).get());
     MOZ_CRASH();
   }
 #endif
 
-  computedValues->UpdateWithElementState(nullptr);
   return computedValues.forget();
 }
 
 already_AddRefed<ServoStyleContext>
 ServoStyleSet::ResolveNonInheritingAnonymousBoxStyle(nsIAtom* aPseudoTag)
 {
   MOZ_ASSERT(nsCSSAnonBoxes::IsAnonBox(aPseudoTag) &&
              nsCSSAnonBoxes::IsNonInheritingAnonBox(aPseudoTag));
@@ -547,17 +518,16 @@ ServoStyleSet::ResolveNonInheritingAnony
     nsString pseudo;
     aPseudoTag->ToString(pseudo);
     NS_ERROR(nsPrintfCString("stylo: could not get anon-box: %s",
              NS_ConvertUTF16toUTF8(pseudo).get()).get());
     MOZ_CRASH();
   }
 #endif
 
-  computedValues->UpdateWithElementState(nullptr);
   cache = computedValues;
   return computedValues.forget();
 }
 
 // manage the set of style sheets in the style set
 nsresult
 ServoStyleSet::AppendStyleSheet(SheetType aType,
                                 ServoStyleSheet* aSheet)
@@ -779,40 +749,38 @@ ServoStyleSet::ProbePseudoElementStyle(E
   // For :before and :after pseudo-elements, having display: none or no
   // 'content' property is equivalent to not having the pseudo-element
   // at all.
   bool isBeforeOrAfter = aType == CSSPseudoElementType::before ||
                          aType == CSSPseudoElementType::after;
   if (isBeforeOrAfter) {
     const nsStyleDisplay* display = computedValues->ComputedData()->GetStyleDisplay();
     const nsStyleContent* content = computedValues->ComputedData()->GetStyleContent();
-    // XXXldb What is contentCount for |content: ""|?
     if (display->mDisplay == StyleDisplay::None ||
         content->ContentCount() == 0) {
       return nullptr;
     }
   }
 
-  computedValues->UpdateWithElementState(isBeforeOrAfter ? aOriginatingElement : nullptr);
   return computedValues.forget();
 }
 
 nsRestyleHint
 ServoStyleSet::HasStateDependentStyle(dom::Element* aElement,
                                       EventStates aStateMask)
 {
   NS_WARNING("stylo: HasStateDependentStyle always returns zero!");
   return nsRestyleHint(0);
 }
 
 nsRestyleHint
 ServoStyleSet::HasStateDependentStyle(dom::Element* aElement,
                                       CSSPseudoElementType aPseudoType,
-                                     dom::Element* aPseudoElement,
-                                     EventStates aStateMask)
+                                      dom::Element* aPseudoElement,
+                                      EventStates aStateMask)
 {
   NS_WARNING("stylo: HasStateDependentStyle always returns zero!");
   return nsRestyleHint(0);
 }
 
 bool
 ServoStyleSet::StyleDocument(TraversalRestyleBehavior aRestyleBehavior)
 {
@@ -1083,24 +1051,55 @@ ServoStyleSet::ClearDataAndMarkDeviceDir
 }
 
 void
 ServoStyleSet::CompatibilityModeChanged()
 {
   Servo_StyleSet_CompatModeChanged(mRawSet.get());
 }
 
+inline static void
+UpdateBodyTextColorIfNeeded(
+    const Element& aElement,
+    ServoStyleContext& aStyleContext,
+    nsPresContext& aPresContext)
+{
+  if (aPresContext.CompatibilityMode() != eCompatibility_NavQuirks) {
+    return;
+  }
+
+  if (!aElement.IsHTMLElement(nsGkAtoms::body)) {
+    return;
+  }
+
+  nsIDocument* doc = aElement.GetUncomposedDoc();
+  if (!doc || doc->GetBodyElement() != &aElement) {
+    return;
+  }
+
+  MOZ_ASSERT(!aStyleContext.GetPseudo());
+
+  // NOTE(emilio): We do the ComputedData() dance to avoid triggering the
+  // IsInServoTraversal() assertion in StyleColor(), which seems useful enough
+  // in the general case, I guess...
+  aPresContext.SetBodyTextColor(
+      aStyleContext.ComputedData()->GetStyleColor()->mColor);
+}
+
 already_AddRefed<ServoStyleContext>
 ServoStyleSet::ResolveServoStyle(Element* aElement,
                                  TraversalRestyleBehavior aRestyleBehavior)
 {
   UpdateStylistIfNeeded();
-  return Servo_ResolveStyle(aElement,
-                            mRawSet.get(),
-                            aRestyleBehavior).Consume();
+  RefPtr<ServoStyleContext> result =
+    Servo_ResolveStyle(aElement,
+                       mRawSet.get(),
+                       aRestyleBehavior).Consume();
+  UpdateBodyTextColorIfNeeded(*aElement, *result, *mPresContext);
+  return result.forget();
 }
 
 void
 ServoStyleSet::ClearNonInheritingStyleContexts()
 {
   for (RefPtr<ServoStyleContext>& ptr : mNonInheritingStyleContexts) {
     ptr = nullptr;
   }
@@ -1154,16 +1153,20 @@ ServoStyleSet::ResolveStyleLazily(Elemen
     computedValues =
       Servo_ResolveStyleLazily(elementForStyleResolution,
                                pseudoTypeForStyleResolution,
                                aRuleInclusion,
                                &Snapshots(),
                                mRawSet.get()).Consume();
   }
 
+  if (aPseudoType == CSSPseudoElementType::NotPseudo) {
+    UpdateBodyTextColorIfNeeded(*aElement, *computedValues, *mPresContext);
+  }
+
   return computedValues.forget();
 }
 
 bool
 ServoStyleSet::AppendFontFaceRules(nsTArray<nsFontFaceRuleContainer>& aArray)
 {
   UpdateStylistIfNeeded();
   Servo_StyleSet_GetFontFaceRules(mRawSet.get(), &aArray);
--- a/layout/style/ServoStyleSet.h
+++ b/layout/style/ServoStyleSet.h
@@ -476,22 +476,16 @@ private:
       sInServoTraversal = nullptr;
       mSet->RunPostTraversalTasks();
     }
 
   private:
     ServoStyleSet* mSet;
   };
 
-  already_AddRefed<ServoStyleContext> GetContext(nsIContent* aContent,
-                                                 ServoStyleContext* aParentContext,
-                                                 nsIAtom* aPseudoTag,
-                                                 CSSPseudoElementType aPseudoType,
-                                                 LazyComputeBehavior aMayCompute);
-
   /**
    * Rebuild the style data. This will force a stylesheet flush, and also
    * recompute the default computed styles.
    */
   void RebuildData();
 
   /**
    * Gets the pending snapshots to handle from the restyle manager.
--- a/layout/style/nsCSSRuleProcessor.cpp
+++ b/layout/style/nsCSSRuleProcessor.cpp
@@ -1063,16 +1063,22 @@ nsCSSRuleProcessor::ClearSheets()
 
 /* static */ void
 nsCSSRuleProcessor::Startup()
 {
   Preferences::AddBoolVarCache(&gSupportVisitedPseudo, VISITED_PSEUDO_PREF,
                                true);
 }
 
+/* static */ bool
+nsCSSRuleProcessor::VisitedLinksEnabled()
+{
+  return gSupportVisitedPseudo;
+}
+
 /* static */ void
 nsCSSRuleProcessor::InitSystemMetrics()
 {
   if (sSystemMetrics)
     return;
 
   MOZ_ASSERT(NS_IsMainThread());
 
@@ -1235,17 +1241,18 @@ nsCSSRuleProcessor::GetWindowsThemeIdent
 {
   nsCSSRuleProcessor::InitSystemMetrics();
   return sWinThemeId;
 }
 #endif
 
 /* static */
 EventStates
-nsCSSRuleProcessor::GetContentState(Element* aElement, bool aUsingPrivateBrowsing)
+nsCSSRuleProcessor::GetContentState(const Element* aElement,
+                                    bool aUsingPrivateBrowsing)
 {
   EventStates state = aElement->StyleState();
 
   // If we are not supposed to mark visited links as such, be sure to
   // flip the bits appropriately.  We want to do this here, rather
   // than in GetContentStateForVisitedHandling, so that we don't
   // expose that :visited support is disabled to the Web page.
   if (state.HasState(NS_EVENT_STATE_VISITED) &&
@@ -1255,27 +1262,28 @@ nsCSSRuleProcessor::GetContentState(Elem
     state &= ~NS_EVENT_STATE_VISITED;
     state |= NS_EVENT_STATE_UNVISITED;
   }
   return state;
 }
 
 /* static */
 EventStates
-nsCSSRuleProcessor::GetContentState(Element* aElement, const TreeMatchContext& aTreeMatchContext)
+nsCSSRuleProcessor::GetContentState(const Element* aElement,
+                                    const TreeMatchContext& aTreeMatchContext)
 {
   return nsCSSRuleProcessor::GetContentState(
     aElement,
     aTreeMatchContext.mUsingPrivateBrowsing
   );
 }
 
 /* static */
 EventStates
-nsCSSRuleProcessor::GetContentState(Element* aElement)
+nsCSSRuleProcessor::GetContentState(const Element* aElement)
 {
   nsILoadContext* loadContext = aElement->OwnerDoc()->GetLoadContext();
   bool usingPrivateBrowsing = loadContext && loadContext->UsePrivateBrowsing();
   return nsCSSRuleProcessor::GetContentState(aElement, usingPrivateBrowsing);
 }
 
 /* static */
 bool
@@ -1283,17 +1291,17 @@ nsCSSRuleProcessor::IsLink(const Element
 {
   EventStates state = aElement->StyleState();
   return state.HasAtLeastOneOfStates(NS_EVENT_STATE_VISITED | NS_EVENT_STATE_UNVISITED);
 }
 
 /* static */
 EventStates
 nsCSSRuleProcessor::GetContentStateForVisitedHandling(
-                     Element* aElement,
+                     const Element* aElement,
                      nsRuleWalker::VisitedHandlingType aVisitedHandling,
                      bool aIsRelevantLink)
 {
   // It's unnecessary to call GetContentState() here (which may flip visited to
   // unvisited) since this function will remove both unvisited and visited if
   // either is set and produce a new value.
   EventStates state = aElement->StyleState();
   if (state.HasAtLeastOneOfStates(NS_EVENT_STATE_VISITED | NS_EVENT_STATE_UNVISITED)) {
--- a/layout/style/nsCSSRuleProcessor.h
+++ b/layout/style/nsCSSRuleProcessor.h
@@ -76,16 +76,17 @@ public:
                      bool aIsShared = false);
 
   NS_DECL_CYCLE_COLLECTING_ISUPPORTS
   NS_DECL_CYCLE_COLLECTION_CLASS(nsCSSRuleProcessor)
 
 public:
   nsresult ClearRuleCascades();
 
+  static bool VisitedLinksEnabled();
   static void Startup();
   static void InitSystemMetrics();
   static void Shutdown();
   static void FreeSystemMetrics();
   static bool HasSystemMetric(nsIAtom* aMetric);
 
   /*
    * Returns true if the given aElement matches one of the
@@ -98,29 +99,29 @@ public:
                                     TreeMatchContext& aTreeMatchContext,
                                     nsCSSSelectorList* aSelectorList);
 
   /*
    * Helper to get the content state for a content node.  This may be
    * slightly adjusted from IntrinsicState().
    */
   static mozilla::EventStates GetContentState(
-                                mozilla::dom::Element* aElement,
+                                const mozilla::dom::Element* aElement,
                                 bool aUsingPrivateBrowsing);
   static mozilla::EventStates GetContentState(
-                                mozilla::dom::Element* aElement,
+                                const mozilla::dom::Element* aElement,
                                 const TreeMatchContext& aTreeMatchContext);
   static mozilla::EventStates GetContentState(
-                                mozilla::dom::Element* aElement);
+                                const mozilla::dom::Element* aElement);
 
   /*
    * Helper to get the content state for :visited handling for an element
    */
   static mozilla::EventStates GetContentStateForVisitedHandling(
-             mozilla::dom::Element* aElement,
+             const mozilla::dom::Element* aElement,
              nsRuleWalker::VisitedHandlingType aVisitedHandling,
              bool aIsRelevantLink);
 
   /*
    * Helper to test whether a node is a link
    */
   static bool IsLink(const mozilla::dom::Element* aElement);
 
--- a/layout/style/nsCSSValue.cpp
+++ b/layout/style/nsCSSValue.cpp
@@ -2887,41 +2887,45 @@ css::URLValueData::IsLocalRef() const
   }
 
   return mIsLocalRef.value();
 }
 
 bool
 css::URLValueData::HasRef() const
 {
+  bool result = false;
+
   if (IsLocalRef()) {
-    return true;
-  }
-
-  nsIURI* uri = GetURI();
-  if (!uri) {
-    return false;
+    result = true;
+  } else {
+    if (nsIURI* uri = GetURI()) {
+      nsAutoCString ref;
+      nsresult rv = uri->GetRef(ref);
+      if (NS_SUCCEEDED(rv) && !ref.IsEmpty()) {
+        result = true;
+      }
+    }
   }
 
-  nsAutoCString ref;
-  nsresult rv = uri->GetRef(ref);
-  if (NS_SUCCEEDED(rv) && !ref.IsEmpty()) {
-    return true;
-  }
-
-  return false;
+  mMightHaveRef = Some(result);
+
+  return result;
 }
 
 bool
 css::URLValueData::MightHaveRef() const
 {
   if (mMightHaveRef.isNothing()) {
-    // ::MightHaveRef is O(N), use it only use it only when MightHaveRef is
-    // called.
-    mMightHaveRef.emplace(::MightHaveRef(mString));
+    bool result = ::MightHaveRef(mString);
+    if (!ServoStyleSet::IsInServoTraversal()) {
+      // Can only cache the result if we're not on a style worker thread.
+      mMightHaveRef.emplace(result);
+    }
+    return result;
   }
 
   return mMightHaveRef.value();
 }
 
 already_AddRefed<nsIURI>
 css::URLValueData::ResolveLocalRef(nsIURI* aURI) const
 {
--- a/layout/style/nsCSSValue.h
+++ b/layout/style/nsCSSValue.h
@@ -1750,16 +1750,19 @@ struct nsCSSValueTokenStream final {
 
 private:
   // Private destructor, to discourage deletion outside of Release():
   ~nsCSSValueTokenStream();
 
 public:
   bool operator==(const nsCSSValueTokenStream& aOther) const
   {
+    // This is not safe to call OMT, due to the URI/Principal Equals calls.
+    MOZ_ASSERT(NS_IsMainThread());
+
     bool eq;
     return mPropertyID == aOther.mPropertyID &&
            mShorthandPropertyID == aOther.mShorthandPropertyID &&
            mTokenStream.Equals(aOther.mTokenStream) &&
            mLevel == aOther.mLevel &&
            (mBaseURI == aOther.mBaseURI ||
             (mBaseURI && aOther.mBaseURI &&
              NS_SUCCEEDED(mBaseURI->Equals(aOther.mBaseURI, &eq)) &&
--- a/layout/style/nsStyleContext.cpp
+++ b/layout/style/nsStyleContext.cpp
@@ -167,16 +167,20 @@ nsStyleContext::CalcStyleDifference(nsSt
   // nobody ever looked at that struct's data).  In other words, we
   // can't skip later structs if we get a big change up front, because
   // we could later get a small change in one of those structs that we
   // don't want to miss.
 
   DebugOnly<uint32_t> structsFound = 0;
 
   if (IsGecko()) {
+    // CalcStyleDifference is always called on the main thread for Gecko
+    // style contexts.  This assertion helps the heap write static analysis.
+    MOZ_ASSERT(NS_IsMainThread());
+
     // FIXME(heycam): We should just do the comparison in
     // nsStyleVariables::CalcDifference, returning NeutralChange if there are
     // any Variables differences.
     const nsStyleVariables* thisVariables = PeekStyleVariables();
     if (thisVariables) {
       structsFound |= NS_STYLE_INHERIT_BIT(Variables);
       const nsStyleVariables* otherVariables = aNewContext->StyleVariables();
       if (thisVariables->mVariables == otherVariables->mVariables) {
--- a/layout/xul/nsMenuBarListener.cpp
+++ b/layout/xul/nsMenuBarListener.cpp
@@ -266,18 +266,17 @@ nsMenuBarListener::KeyPress(nsIDOMEvent*
   InitAccessKey();
 
   if (mAccessKey)
   {
     // If accesskey handling was forwarded to a child process, wait for
     // the mozaccesskeynotfound event before handling accesskeys.
     WidgetKeyboardEvent* nativeKeyEvent =
       aKeyEvent->WidgetEventPtr()->AsKeyboardEvent();
-    if (!nativeKeyEvent ||
-        (nativeKeyEvent && nativeKeyEvent->mAccessKeyForwardedToChild)) {
+    if (!nativeKeyEvent) {
       return NS_OK;
     }
 
     nsCOMPtr<nsIDOMKeyEvent> keyEvent = do_QueryInterface(aKeyEvent);
     uint32_t keyCode, charCode;
     keyEvent->GetKeyCode(&keyCode);
     keyEvent->GetCharCode(&charCode);
 
@@ -296,32 +295,54 @@ nsMenuBarListener::KeyPress(nsIDOMEvent*
     }
 
     if (IsAccessKeyPressed(keyEvent) && hasAccessKeyCandidates) {
       // Do shortcut navigation.
       // A letter was pressed. We want to see if a shortcut gets matched. If
       // so, we'll know the menu got activated.
       nsMenuFrame* result = mMenuBarFrame->FindMenuWithShortcut(keyEvent);
       if (result) {
+        // If the keyboard event matches with a menu item's accesskey and
+        // will be sent to a remote process, it should be executed with
+        // reply event from the focused remote process.  Note that if the
+        // menubar is active, the event is already marked as "stop cross
+        // process dispatching".  So, in that case, this won't wait
+        // reply from the remote content.
+        if (nativeKeyEvent->WillBeSentToRemoteProcess()) {
+          nativeKeyEvent->StopImmediatePropagation();
+          nativeKeyEvent->MarkAsWaitingReplyFromRemoteProcess();
+          return NS_OK;
+        }
         mMenuBarFrame->SetActiveByKeyboard();
         mMenuBarFrame->SetActive(true);
         result->OpenMenu(true);
 
         // The opened menu will listen next keyup event.
         // Therefore, we should clear the keydown flags here.
         mAccessKeyDown = mAccessKeyDownCanceled = false;
 
         aKeyEvent->StopPropagation();
         aKeyEvent->PreventDefault();
       }
     }
 #ifndef XP_MACOSX
     // Also need to handle F10 specially on Non-Mac platform.
     else if (nativeKeyEvent->mMessage == eKeyPress && keyCode == NS_VK_F10) {
       if ((GetModifiersForAccessKey(keyEvent) & ~MODIFIER_CONTROL) == 0) {
+        // If the keyboard event should activate the menubar and will be
+        // sent to a remote process, it should be executed with reply
+        // event from the focused remote process.  Note that if the menubar
+        // is active, the event is already marked as "stop cross
+        // process dispatching".  So, in that case, this won't wait
+        // reply from the remote content.
+        if (nativeKeyEvent->WillBeSentToRemoteProcess()) {
+          nativeKeyEvent->StopImmediatePropagation();
+          nativeKeyEvent->MarkAsWaitingReplyFromRemoteProcess();
+          return NS_OK;
+        }
         // The F10 key just went down by itself or with ctrl pressed.
         // In Windows, both of these activate the menu bar.
         mMenuBarFrame->SetActiveByKeyboard();
         ToggleMenuActiveState();
 
         if (mMenuBarFrame->IsActive()) {
 #ifdef MOZ_WIDGET_GTK
           // In GTK, this also opens the first menu.
--- a/mobile/android/base/java/org/mozilla/gecko/db/BrowserProvider.java
+++ b/mobile/android/base/java/org/mozilla/gecko/db/BrowserProvider.java
@@ -2240,16 +2240,19 @@ public class BrowserProvider extends Sha
         values.putNull(Bookmarks.PARENT);
         values.putNull(Bookmarks.URL);
         values.putNull(Bookmarks.TITLE);
         values.putNull(Bookmarks.DESCRIPTION);
         values.putNull(Bookmarks.KEYWORD);
         values.putNull(Bookmarks.TAGS);
         values.putNull(Bookmarks.FAVICON_ID);
 
+        // Bump the lastModified timestamp for sync to know to update bookmark records.
+        values.put(Bookmarks.DATE_MODIFIED, System.currentTimeMillis());
+
         // Leave space for variables in values.
         final int maxVariableNumber = DBUtils.SQLITE_MAX_VARIABLE_NUMBER - values.size();
 
         for (int chunk = 0; chunk <= bookmarkGUIDs.size() / maxVariableNumber; chunk++) {
             final int chunkStart = chunk * maxVariableNumber;
             int chunkEnd = (chunk + 1) * maxVariableNumber;
             if (chunkEnd > bookmarkGUIDs.size()) {
                 chunkEnd = bookmarkGUIDs.size();
--- a/mobile/android/tests/background/junit4/src/org/mozilla/gecko/db/BrowserProviderBookmarksTest.java
+++ b/mobile/android/tests/background/junit4/src/org/mozilla/gecko/db/BrowserProviderBookmarksTest.java
@@ -31,16 +31,17 @@ import static org.junit.Assert.assertTru
 
 /**
  * Testing direct interactions with bookmarks through BrowserProvider
  */
 @RunWith(TestRunner.class)
 public class BrowserProviderBookmarksTest {
 
     private static final long INVALID_ID = -1;
+    private static final long INVALID_TIMESTAMP = -1;
 
     private ContentProviderClient bookmarksClient;
     private Uri bookmarksTestUri;
     private BrowserProvider provider;
 
     @Before
     public void setUp() throws Exception {
         provider = new BrowserProvider();
@@ -137,16 +138,60 @@ public class BrowserProviderBookmarksTes
                 BrowserContract.Bookmarks.GUID + " = ?",
                 new String[] { "parent" });
 
         // 4 = parent, its two children, mobile root.
         assertEquals(4, changed);
     }
 
     @Test
+    public void testBookmarkFolderLastModifiedOnDeletion() throws RemoteException {
+        final long rootId = getBookmarkIdFromGuid(BrowserContract.Bookmarks.MOBILE_FOLDER_GUID);
+
+        // root
+        // -> sibling
+        // -> parent -- timestamp must change
+        // ---> child-1
+        // ---> child-2 -- delete this one
+
+        final Uri parentUri = insertBookmark("parent", null, "parent", rootId,
+                                             BrowserContract.Bookmarks.TYPE_FOLDER);
+        final Uri siblingUri = insertBookmark("sibling", null, "sibling", rootId,
+                                              BrowserContract.Bookmarks.TYPE_FOLDER);
+
+        final long parentId = Long.parseLong(parentUri.getLastPathSegment());
+        final Uri child1Uri = insertBookmark("child-1", null, "child-1", parentId,
+                                             BrowserContract.Bookmarks.TYPE_FOLDER);
+        final Uri child2Uri = insertBookmark("child-2", null, "child-2", parentId,
+                                             BrowserContract.Bookmarks.TYPE_FOLDER);
+
+        final long parentLastModifiedBeforeDeletion = getLastModified(parentUri);
+        final long siblingLastModifiedBeforeDeletion = getLastModified(siblingUri);
+
+        final long child1LastModifiedBeforeDeletion = getLastModified(child1Uri);
+        final long child2LastModifiedBeforeDeletion = getLastModified(child2Uri);
+
+        bookmarksClient.delete(child2Uri, null, null);
+
+        final long parentLastModifiedAfterDeletion = getLastModified(parentUri);
+        final long siblingLastModifiedAfterDeletion = getLastModified(siblingUri);
+
+        final long child1LastModifiedAfterDeletion = getLastModified(child1Uri);
+        final long child2LastModifiedAfterDeletion = getLastModified(withDeleted(child2Uri));
+
+        // Check last modified timestamp of parent and child-2 is increased.
+        assertTrue(parentLastModifiedAfterDeletion > parentLastModifiedBeforeDeletion);
+        assertTrue(child2LastModifiedAfterDeletion > child2LastModifiedBeforeDeletion);
+
+        // Check last modified timestamp of sibling and child-1 is not changed.
+        assertTrue(siblingLastModifiedBeforeDeletion == siblingLastModifiedAfterDeletion);
+        assertTrue(child1LastModifiedBeforeDeletion == child1LastModifiedAfterDeletion);
+    }
+
+    @Test
     public void testDeleteBookmarkFolder() throws RemoteException {
         final long rootId = getBookmarkIdFromGuid(BrowserContract.Bookmarks.MOBILE_FOLDER_GUID);
 
         // Create a folder.
         final Uri parentUri = insertBookmark("parent", null, "parent", rootId,
                                              BrowserContract.Bookmarks.TYPE_FOLDER);
         final long parentId = Long.parseLong(parentUri.getLastPathSegment());
 
@@ -235,16 +280,36 @@ public class BrowserProviderBookmarksTes
     private Uri testUri(Uri baseUri) {
         return baseUri.buildUpon().appendQueryParameter(BrowserContract.PARAM_IS_TEST, "1").build();
     }
 
     private Uri withDeleted(Uri baseUri) {
         return baseUri.buildUpon().appendQueryParameter(BrowserContract.PARAM_SHOW_DELETED, "true").build();
     }
 
+    private long getLastModified(final Uri uri) throws RemoteException {
+        final Cursor cursor = bookmarksClient.query(uri,
+                                                    new String[] { BrowserContract.Bookmarks.DATE_MODIFIED },
+                                                    null,
+                                                    null,
+                                                    null);
+        assertNotNull(cursor);
+
+        long lastModified = INVALID_TIMESTAMP;
+        try {
+            assertTrue(cursor.moveToFirst());
+            assertEquals(1, cursor.getCount());
+            lastModified = cursor.getLong(cursor.getColumnIndexOrThrow(BrowserContract.Bookmarks.DATE_MODIFIED));
+        } finally {
+            cursor.close();
+        }
+        assertNotEquals(lastModified, INVALID_TIMESTAMP);
+        return lastModified;
+    }
+
     private long getBookmarkIdFromGuid(String guid) throws RemoteException {
         Cursor cursor = bookmarksClient.query(BrowserContract.Bookmarks.CONTENT_URI,
                                               new String[] { BrowserContract.Bookmarks._ID },
                                               BrowserContract.Bookmarks.GUID + " = ?",
                                               new String[] { guid },
                                               null);
         assertNotNull(cursor);
 
--- a/servo/Cargo.lock
+++ b/servo/Cargo.lock
@@ -3460,17 +3460,17 @@ dependencies = [
  "url 1.5.1 (registry+https://github.com/rust-lang/crates.io-index)",
  "uuid 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "webdriver 0.22.0 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
 [[package]]
 name = "webrender"
 version = "0.48.0"
-source = "git+https://github.com/servo/webrender#b83c200c657f6b6fb17d09f329ba77803420b46a"
+source = "git+https://github.com/servo/webrender#8fd634882111415a65da67e947f26eb170234f2f"
 dependencies = [
  "app_units 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "bincode 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "bit-set 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "bitflags 0.9.1 (registry+https://github.com/rust-lang/crates.io-index)",
  "byteorder 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "core-graphics 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)",
  "core-text 6.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
@@ -3489,17 +3489,17 @@ dependencies = [
  "thread_profiler 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)",
  "time 0.1.37 (registry+https://github.com/rust-lang/crates.io-index)",
  "webrender_api 0.48.0 (git+https://github.com/servo/webrender)",
 ]
 
 [[package]]
 name = "webrender_api"
 version = "0.48.0"
-source = "git+https://github.com/servo/webrender#b83c200c657f6b6fb17d09f329ba77803420b46a"
+source = "git+https://github.com/servo/webrender#8fd634882111415a65da67e947f26eb170234f2f"
 dependencies = [
  "app_units 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "bincode 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "byteorder 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "core-foundation 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "core-graphics 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)",
  "dwrote 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "euclid 0.15.1 (registry+https://github.com/rust-lang/crates.io-index)",
--- a/servo/components/gfx/display_list/mod.rs
+++ b/servo/components/gfx/display_list/mod.rs
@@ -1141,17 +1141,18 @@ impl<T> BorderRadii<T> where T: PartialE
 #[derive(Clone, HeapSizeOf, Deserialize, Serialize)]
 pub struct LineDisplayItem {
     pub base: BaseDisplayItem,
 
     /// The line segment color.
     pub color: ColorF,
 
     /// The line segment style.
-    pub style: border_style::T
+    #[ignore_heap_size_of = "enum type in webrender"]
+    pub style: webrender_api::LineStyle,
 }
 
 /// Paints a box shadow per CSS-BACKGROUNDS.
 #[derive(Clone, HeapSizeOf, Deserialize, Serialize)]
 pub struct BoxShadowDisplayItem {
     /// Fields common to all display items.
     pub base: BaseDisplayItem,
 
--- a/servo/components/layout/display_list_builder.rs
+++ b/servo/components/layout/display_list_builder.rs
@@ -66,17 +66,17 @@ use style::values::generics::image::{Cir
 use style::values::generics::image::{GradientItem as GenericGradientItem, GradientKind};
 use style::values::generics::image::{Image, ShapeExtent};
 use style::values::generics::image::PaintWorklet;
 use style::values::specified::position::{X, Y};
 use style_traits::CSSPixel;
 use style_traits::cursor::Cursor;
 use table_cell::CollapsedBordersForCell;
 use webrender_api::{ClipId, ColorF, ComplexClipRegion, GradientStop, LocalClip, RepeatMode};
-use webrender_api::{ScrollPolicy, TransformStyle};
+use webrender_api::{LineStyle, ScrollPolicy, TransformStyle};
 use webrender_helpers::{ToBorderRadius, ToMixBlendMode, ToRectF, ToTransformStyle};
 
 trait ResolvePercentage {
     fn resolve(&self, length: u32) -> u32;
 }
 
 impl ResolvePercentage for NumberOrPercentage {
     fn resolve(&self, length: u32) -> u32 {
@@ -1646,17 +1646,17 @@ impl FragmentDisplayListBuilding for Fra
         let base = state.create_base_display_item(&baseline,
                                                   LocalClip::from(clip.to_rectf()),
                                                   self.node,
                                                   style.get_cursor(Cursor::Default),
                                                   DisplayListSection::Content);
         state.add_display_item(DisplayItem::Line(box LineDisplayItem {
             base: base,
             color: ColorF::rgb(0, 200, 0),
-            style: border_style::T::dashed,
+            style: LineStyle::Dashed,
         }));
     }
 
     fn build_debug_borders_around_fragment(&self,
                                            state: &mut DisplayListBuildState,
                                            stacking_relative_border_box: &Rect<Au>,
                                            clip: &Rect<Au>) {
         // This prints a debug border around the border of this fragment.
@@ -2212,19 +2212,20 @@ impl FragmentDisplayListBuilding for Fra
                                                                       container_size);
         let base = state.create_base_display_item(
             &stacking_relative_box,
             LocalClip::from(clip.to_rectf()),
             self.node,
             self.style.get_cursor(Cursor::Default),
             DisplayListSection::Content);
 
-        state.add_display_item(DisplayItem::SolidColor(box SolidColorDisplayItem {
+        state.add_display_item(DisplayItem::Line(box LineDisplayItem {
             base: base,
             color: color.to_gfx_color(),
+            style: LineStyle::Solid,
         }));
     }
 
     fn unique_id(&self, id_type: IdType) -> u64 {
         let fragment_type = self.fragment_type();
         let id = match id_type {
             IdType::StackingContext | IdType::OverflowClip  => self.node.id() as usize,
             IdType::CSSClip => self as *const _ as usize,
--- a/servo/components/layout/webrender_helpers.rs
+++ b/servo/components/layout/webrender_helpers.rs
@@ -427,18 +427,26 @@ impl WebRenderDisplayItemConverter for D
                                                               item.gradient.stops.clone(),
                                                               extend_mode);
                 builder.push_radial_gradient(rect,
                                              Some(item.base.local_clip),
                                              gradient,
                                              rect.size,
                                              webrender_api::LayoutSize::zero());
             }
-            DisplayItem::Line(..) => {
-                println!("TODO DisplayItem::Line");
+            DisplayItem::Line(ref item) => {
+                let box_bounds = item.base.bounds.to_rectf();
+                builder.push_line(Some(item.base.local_clip),
+                                  box_bounds.origin.y + box_bounds.size.height,
+                                  box_bounds.origin.x,
+                                  box_bounds.origin.x + box_bounds.size.width,
+                                  webrender_api::LineOrientation::Horizontal,
+                                  box_bounds.size.height,
+                                  item.color,
+                                  item.style);
             }
             DisplayItem::BoxShadow(ref item) => {
                 let rect = item.base.bounds.to_rectf();
                 let box_bounds = item.box_bounds.to_rectf();
                 builder.push_box_shadow(rect,
                                         Some(item.base.local_clip),
                                         box_bounds,
                                         item.offset.to_vectorf(),
--- a/servo/components/layout_thread/lib.rs
+++ b/servo/components/layout_thread/lib.rs
@@ -589,16 +589,17 @@ impl LayoutThread {
             ThreadLocalStyleContextCreationInfo::new(self.new_animations_sender.clone());
 
         LayoutContext {
             id: self.id,
             style_context: SharedStyleContext {
                 stylist: &self.stylist,
                 options: StyleSystemOptions::default(),
                 guards: guards,
+                visited_styles_enabled: false,
                 running_animations: self.running_animations.clone(),
                 expired_animations: self.expired_animations.clone(),
                 local_context_creation_data: Mutex::new(thread_local_style_context_creation_data),
                 timer: self.timer.clone(),
                 quirks_mode: self.quirks_mode.unwrap(),
                 traversal_flags: TraversalFlags::empty(),
                 snapshot_map: snapshot_map,
             },
--- a/servo/components/script/dom/htmlscriptelement.rs
+++ b/servo/components/script/dom/htmlscriptelement.rs
@@ -337,19 +337,21 @@ impl HTMLScriptElement {
             return;
         }
 
         // Step 10.
         if !doc.is_scripting_enabled() {
             return;
         }
 
-        // TODO(#4577): Step 11: CSP.
+        // TODO: Step 11: nomodule content attribute
 
-        // Step 12.
+        // TODO(#4577): Step 12: CSP.
+
+        // Step 13.
         let for_attribute = element.get_attribute(&ns!(), &local_name!("for"));
         let event_attribute = element.get_attribute(&ns!(), &local_name!("event"));
         match (for_attribute.r(), event_attribute.r()) {
             (Some(for_attribute), Some(event_attribute)) => {
                 let for_value = for_attribute.value().to_ascii_lowercase();
                 let for_value = for_value.trim_matches(HTML_SPACE_CHARACTERS);
                 if for_value != "window" {
                     return;
@@ -359,103 +361,103 @@ impl HTMLScriptElement {
                 let event_value = event_value.trim_matches(HTML_SPACE_CHARACTERS);
                 if event_value != "onload" && event_value != "onload()" {
                     return;
                 }
             },
             (_, _) => (),
         }
 
-        // Step 13.
+        // Step 14.
         let encoding = element.get_attribute(&ns!(), &local_name!("charset"))
                               .and_then(|charset| encoding_from_whatwg_label(&charset.value()))
                               .unwrap_or_else(|| doc.encoding());
 
-        // Step 14.
+        // Step 15.
         let cors_setting = cors_setting_for_element(element);
 
-        // TODO: Step 15: Module script credentials mode.
+        // TODO: Step 16: Module script credentials mode.
 
-        // TODO: Step 16: Nonce.
+        // TODO: Step 17: Nonce.
 
-        // Step 17: Integrity metadata.
+        // Step 18: Integrity metadata.
         let im_attribute = element.get_attribute(&ns!(), &local_name!("integrity"));
         let integrity_val = im_attribute.r().map(|a| a.value());
         let integrity_metadata = match integrity_val {
             Some(ref value) => &***value,
             None => "",
         };
 
-        // TODO: Step 18: parser state.
+        // TODO: Step 19: parser state.
 
-        // TODO: Step 19: environment settings object.
+        // TODO: Step 20: environment settings object.
 
         let base_url = doc.base_url();
         if let Some(src) = element.get_attribute(&ns!(), &local_name!("src")) {
-            // Step 20.
+            // Step 21.
 
-            // Step 20.1.
+            // Step 21.1.
             let src = src.value();
 
-            // Step 20.2.
+            // Step 21.2.
             if src.is_empty() {
                 self.queue_error_event();
                 return;
             }
 
-            // Step 20.3: The "from an external file"" flag is stored in ClassicScript.
+            // Step 21.3: The "from an external file"" flag is stored in ClassicScript.
 
-            // Step 20.4-20.5.
+            // Step 21.4-21.5.
             let url = match base_url.join(&src) {
                 Ok(url) => url,
                 Err(_) => {
                     warn!("error parsing URL for script {}", &**src);
                     self.queue_error_event();
                     return;
                 },
             };
 
-            // Preparation for step 22.
+            // Preparation for step 23.
             let kind = if element.has_attribute(&local_name!("defer")) && was_parser_inserted && !async {
-                // Step 22.a: classic, has src, has defer, was parser-inserted, is not async.
+                // Step 23.a: classic, has src, has defer, was parser-inserted, is not async.
                 ExternalScriptKind::Deferred
             } else if was_parser_inserted && !async {
-                // Step 22.b: classic, has src, was parser-inserted, is not async.
+                // Step 23.c: classic, has src, was parser-inserted, is not async.
                 ExternalScriptKind::ParsingBlocking
             } else if !async && !self.non_blocking.get() {
-                // Step 22.c: classic, has src, is not async, is not non-blocking.
+                // Step 23.d: classic, has src, is not async, is not non-blocking.
                 ExternalScriptKind::AsapInOrder
             } else {
-                // Step 22.d: classic, has src.
+                // Step 23.f: classic, has src.
                 ExternalScriptKind::Asap
             };
 
-            // Step 20.6.
+            // Step 21.6.
             fetch_a_classic_script(self, kind, url, cors_setting, integrity_metadata.to_owned(), encoding);
 
-            // Step 22.
+            // Step 23.
             match kind {
                 ExternalScriptKind::Deferred => doc.add_deferred_script(self),
                 ExternalScriptKind::ParsingBlocking => doc.set_pending_parsing_blocking_script(self, None),
                 ExternalScriptKind::AsapInOrder => doc.push_asap_in_order_script(self),
                 ExternalScriptKind::Asap => doc.add_asap_script(self),
             }
         } else {
-            // Step 21.
+            // Step 22.
             assert!(!text.is_empty());
             let result = Ok(ClassicScript::internal(text, base_url));
 
-            // Step 22.
+            // Step 23.
             if was_parser_inserted &&
                doc.get_current_parser().map_or(false, |parser| parser.script_nesting_level() <= 1) &&
                doc.get_script_blocking_stylesheets_count() > 0 {
-                // Step 22.e: classic, has no src, was parser-inserted, is blocked on stylesheet.
+                // Step 23.h: classic, has no src, was parser-inserted, is blocked on stylesheet.
                 doc.set_pending_parsing_blocking_script(self, Some(result));
             } else {
-                // Step 22.f: otherwise.
+                // Step 23.i: otherwise.
                 self.execute(result);
             }
         }
     }
 
     fn unminify_js(&self, script: &mut ClassicScript) {
         if !opts::get().unminify_js {
             return;
--- a/servo/components/style/context.rs
+++ b/servo/components/style/context.rs
@@ -112,16 +112,22 @@ impl Default for StyleSystemOptions {
 /// A shared style context.
 ///
 /// There's exactly one of these during a given restyle traversal, and it's
 /// shared among the worker threads.
 pub struct SharedStyleContext<'a> {
     /// The CSS selector stylist.
     pub stylist: &'a Stylist,
 
+    /// Whether visited styles are enabled.
+    ///
+    /// They may be disabled when Gecko's pref layout.css.visited_links_enabled
+    /// is false, or when in private browsing mode.
+    pub visited_styles_enabled: bool,
+
     /// Configuration options.
     pub options: StyleSystemOptions,
 
     /// Guards for pre-acquired locks
     pub guards: StylesheetGuards<'a>,
 
     /// The current timer for transitions and animations. This is needed to test
     /// them.
--- a/servo/components/style/dom.rs
+++ b/servo/components/style/dom.rs
@@ -545,16 +545,21 @@ pub trait TElement : Eq + PartialEq + De
     }
 
     /// Flag that this element has no descendant for animation-only restyle processing.
     ///
     /// Only safe to call with exclusive access to the element.
     unsafe fn unset_animation_only_dirty_descendants(&self) {
     }
 
+    /// Returns true if this element is a visited link.
+    ///
+    /// Servo doesn't support visited styles yet.
+    fn is_visited_link(&self) -> bool { false }
+
     /// Returns true if this element is native anonymous (only Gecko has native
     /// anonymous content).
     fn is_native_anonymous(&self) -> bool { false }
 
     /// Returns the pseudo-element implemented by this element, if any.
     ///
     /// Gecko traverses pseudo-elements during the style traversal, and we need
     /// to know this so we can properly grab the pseudo-element style from the
--- a/servo/components/style/gecko/data.rs
+++ b/servo/components/style/gecko/data.rs
@@ -182,16 +182,23 @@ impl PerDocumentStyleDataImpl {
             &StylesheetGuards::same(guard),
             /* ua_sheets = */ None,
             /* stylesheets_changed = */ true,
             author_style_disabled,
             &mut extra_data
         );
     }
 
+    /// Returns whether private browsing is enabled.
+    pub fn is_private_browsing_enabled(&self) -> bool {
+        let doc =
+            self.stylist.device().pres_context().mDocument.raw::<nsIDocument>();
+        unsafe { bindings::Gecko_IsPrivateBrowsingEnabled(doc) }
+    }
+
     /// Get the default computed values for this document.
     pub fn default_computed_values(&self) -> &Arc<ComputedValues> {
         self.stylist.device().default_computed_values_arc()
     }
 
     /// Clear the stylist.  This will be a no-op if the stylist is
     /// already cleared; the stylist handles that.
     pub fn clear_stylist(&mut self) {
--- a/servo/components/style/gecko/generated/bindings.rs
+++ b/servo/components/style/gecko/generated/bindings.rs
@@ -742,16 +742,22 @@ extern "C" {
      -> RawServoDeclarationBlockStrongBorrowedOrNull;
 }
 extern "C" {
     pub fn Gecko_GetActiveLinkAttrDeclarationBlock(element:
                                                        RawGeckoElementBorrowed)
      -> RawServoDeclarationBlockStrongBorrowedOrNull;
 }
 extern "C" {
+    pub fn Gecko_IsPrivateBrowsingEnabled(aDoc: *const nsIDocument) -> bool;
+}
+extern "C" {
+    pub fn Gecko_AreVisitedLinksEnabled() -> bool;
+}
+extern "C" {
     pub fn Gecko_GetAnimationRule(aElementOrPseudo: RawGeckoElementBorrowed,
                                   aCascadeLevel:
                                       EffectCompositor_CascadeLevel,
                                   aAnimationValues:
                                       RawServoAnimationValueMapBorrowedMut)
      -> bool;
 }
 extern "C" {
--- a/servo/components/style/gecko/wrapper.rs
+++ b/servo/components/style/gecko/wrapper.rs
@@ -1006,16 +1006,21 @@ impl<'le> TElement for GeckoElement<'le>
     unsafe fn set_animation_only_dirty_descendants(&self) {
         self.set_flags(ELEMENT_HAS_ANIMATION_ONLY_DIRTY_DESCENDANTS_FOR_SERVO as u32)
     }
 
     unsafe fn unset_animation_only_dirty_descendants(&self) {
         self.unset_flags(ELEMENT_HAS_ANIMATION_ONLY_DIRTY_DESCENDANTS_FOR_SERVO as u32)
     }
 
+    fn is_visited_link(&self) -> bool {
+        use element_state::IN_VISITED_STATE;
+        self.get_state().intersects(IN_VISITED_STATE)
+    }
+
     fn is_native_anonymous(&self) -> bool {
         self.flags() & (NODE_IS_NATIVE_ANONYMOUS as u32) != 0
     }
 
     fn implemented_pseudo_element(&self) -> Option<PseudoElement> {
         if !self.is_native_anonymous() {
             return None;
         }
--- a/servo/components/style/properties/computed_value_flags.rs
+++ b/servo/components/style/properties/computed_value_flags.rs
@@ -28,10 +28,14 @@ bitflags! {
         /// This bit is propagated to all children of line participants.
         /// It is currently used by ruby to make its content unbreakable.
         const SHOULD_SUPPRESS_LINEBREAK = 1 << 1,
 
         /// A flag used to mark text that that has text-combine-upright.
         ///
         /// This is used from Gecko's layout engine.
         const IS_TEXT_COMBINED = 1 << 2,
+
+        /// A flag used to mark styles under a relevant link that is also
+        /// visited.
+        const IS_RELEVANT_LINK_VISITED = 1 << 3,
     }
 }
--- a/servo/components/style/properties/properties.mako.rs
+++ b/servo/components/style/properties/properties.mako.rs
@@ -2645,16 +2645,21 @@ impl<'a> StyleBuilder<'a> {
             parent.custom_properties(),
             parent.writing_mode,
             parent.font_computation_data.font_size_keyword,
             parent.flags,
             parent.clone_visited_style()
         )
     }
 
+    /// Returns whether we have a visited style.
+    pub fn has_visited_style(&self) -> bool {
+        self.visited_style.is_some()
+    }
+
     /// Returns the style we're inheriting from.
     pub fn inherited_style(&self) -> &'a ComputedValues {
         self.inherited_style
     }
 
     /// Returns the style we're getting reset properties from.
     pub fn default_style(&self) -> &'a ComputedValues {
         self.reset_style
@@ -2800,42 +2805,50 @@ static CASCADE_PROPERTY: [CascadePropert
     % endfor
 ];
 
 bitflags! {
     /// A set of flags to tweak the behavior of the `cascade` function.
     pub flags CascadeFlags: u8 {
         /// Whether to inherit all styles from the parent. If this flag is not
         /// present, non-inherited styles are reset to their initial values.
-        const INHERIT_ALL = 0x01,
+        const INHERIT_ALL = 1,
 
         /// Whether to skip any display style fixup for root element, flex/grid
         /// item, and ruby descendants.
-        const SKIP_ROOT_AND_ITEM_BASED_DISPLAY_FIXUP = 0x02,
+        const SKIP_ROOT_AND_ITEM_BASED_DISPLAY_FIXUP = 1 << 1,
 
         /// Whether to only cascade properties that are visited dependent.
-        const VISITED_DEPENDENT_ONLY = 0x04,
+        const VISITED_DEPENDENT_ONLY = 1 << 2,
 
         /// Whether the given element we're styling is the document element,
         /// that is, matches :root.
         ///
         /// Not set for native anonymous content since some NAC form their own
         /// root, but share the device.
         ///
         /// This affects some style adjustments, like blockification, and means
         /// that it may affect global state, like the Device's root font-size.
-        const IS_ROOT_ELEMENT = 0x08,
+        const IS_ROOT_ELEMENT = 1 << 3,
 
         /// Whether to convert display:contents into display:inline.  This
         /// is used by Gecko to prevent display:contents on generated
         /// content.
-        const PROHIBIT_DISPLAY_CONTENTS = 0x10,
+        const PROHIBIT_DISPLAY_CONTENTS = 1 << 4,
 
         /// Whether we're styling the ::-moz-fieldset-content anonymous box.
-        const IS_FIELDSET_CONTENT = 0x20,
+        const IS_FIELDSET_CONTENT = 1 << 5,
+
+        /// Whether we're computing the style of a link, either visited or
+        /// unvisited.
+        const IS_LINK = 1 << 6,
+
+        /// Whether we're computing the style of a link element that happens to
+        /// be visited.
+        const IS_VISITED_LINK = 1 << 7,
     }
 }
 
 /// Performs the CSS cascade, computing new styles for an element from its parent style.
 ///
 /// The arguments are:
 ///
 ///   * `device`: Used to get the initial viewport and other external state.
--- a/servo/components/style/style_adjuster.rs
+++ b/servo/components/style/style_adjuster.rs
@@ -421,24 +421,54 @@ impl<'a, 'b: 'a> StyleAdjuster<'a, 'b> {
                 _ => None,
             };
             if let Some(new_value) = new_value {
                 self.style.mutate_text().set_unicode_bidi(new_value);
             }
         }
     }
 
+    /// Computes the RELEVANT_LINK_VISITED flag based on the parent style and on
+    /// whether we're a relevant link.
+    ///
+    /// NOTE(emilio): We don't do this for text styles, which is... dubious, but
+    /// Gecko doesn't seem to do it either. It's extremely easy to do if needed
+    /// though.
+    ///
+    /// FIXME(emilio): This isn't technically a style adjustment thingie, could
+    /// it move somewhere else?
+    fn adjust_for_visited(&mut self, flags: CascadeFlags) {
+        use properties::{IS_LINK, IS_VISITED_LINK};
+        use properties::computed_value_flags::IS_RELEVANT_LINK_VISITED;
+
+        if !self.style.has_visited_style() {
+            return;
+        }
+
+        let relevant_link_visited = if flags.contains(IS_LINK) {
+            flags.contains(IS_VISITED_LINK)
+        } else {
+            self.style.inherited_style().flags.contains(IS_RELEVANT_LINK_VISITED)
+        };
+
+        if relevant_link_visited {
+            self.style.flags.insert(IS_RELEVANT_LINK_VISITED);
+        }
+    }
+
     /// Adjusts the style to account for various fixups that don't fit naturally
     /// into the cascade.
     ///
     /// When comparing to Gecko, this is similar to the work done by
-    /// `nsStyleContext::ApplyStyleFixups`.
+    /// `nsStyleContext::ApplyStyleFixups`, plus some parts of
+    /// `nsStyleSet::GetContext`.
     pub fn adjust(&mut self,
                   layout_parent_style: &ComputedValues,
                   flags: CascadeFlags) {
+        self.adjust_for_visited(flags);
         #[cfg(feature = "gecko")]
         {
             self.adjust_for_prohibited_display_contents(flags);
             self.adjust_for_fieldset_content(layout_parent_style, flags);
         }
         self.adjust_for_top_layer();
         self.blockify_if_necessary(layout_parent_style, flags);
         self.adjust_for_position();
--- a/servo/components/style/style_resolver.rs
+++ b/servo/components/style/style_resolver.rs
@@ -7,17 +7,18 @@
 use applicable_declarations::ApplicableDeclarationList;
 use cascade_info::CascadeInfo;
 use context::{CascadeInputs, ElementCascadeInputs, StyleContext};
 use data::{ElementStyles, EagerPseudoStyles};
 use dom::TElement;
 use log::LogLevel::Trace;
 use matching::{CascadeVisitedMode, MatchMethods};
 use properties::{AnimationRules, CascadeFlags, ComputedValues};
-use properties::{IS_ROOT_ELEMENT, PROHIBIT_DISPLAY_CONTENTS, SKIP_ROOT_AND_ITEM_BASED_DISPLAY_FIXUP};
+use properties::{IS_LINK, IS_ROOT_ELEMENT, IS_VISITED_LINK};
+use properties::{PROHIBIT_DISPLAY_CONTENTS, SKIP_ROOT_AND_ITEM_BASED_DISPLAY_FIXUP};
 use properties::{VISITED_DEPENDENT_ONLY, cascade};
 use rule_tree::StrongRuleNode;
 use selector_parser::{PseudoElement, SelectorImpl};
 use selectors::matching::{ElementSelectorFlags, MatchingContext, MatchingMode, VisitedHandlingMode};
 use servo_arc::Arc;
 use stylist::RuleInclusion;
 
 /// A struct that takes care of resolving the style of a given element.
@@ -468,16 +469,25 @@ where
         pseudo: Option<&PseudoElement>,
     ) -> Arc<ComputedValues> {
         let mut cascade_info = CascadeInfo::new();
         let mut cascade_flags = CascadeFlags::empty();
 
         if self.element.skip_root_and_item_based_display_fixup() {
             cascade_flags.insert(SKIP_ROOT_AND_ITEM_BASED_DISPLAY_FIXUP);
         }
+
+        if pseudo.is_none() && self.element.is_link() {
+            cascade_flags.insert(IS_LINK);
+            if self.element.is_visited_link() &&
+                self.context.shared.visited_styles_enabled {
+                cascade_flags.insert(IS_VISITED_LINK);
+            }
+        }
+
         if cascade_visited.visited_dependent_only() {
             // If this element is a link, we want its visited style to inherit
             // from the regular style of its parent, because only the
             // visitedness of the relevant link should influence style.
             if pseudo.is_some() || !self.element.is_link() {
                 parent_style = parent_style.map(|s| {
                     s.get_visited_style().unwrap_or(s)
                 });
--- a/servo/ports/geckolib/glue.rs
+++ b/servo/ports/geckolib/glue.rs
@@ -177,18 +177,23 @@ unsafe fn dummy_url_data() -> &'static R
 }
 
 fn create_shared_context<'a>(global_style_data: &GlobalStyleData,
                              guard: &'a SharedRwLockReadGuard,
                              per_doc_data: &'a PerDocumentStyleDataImpl,
                              traversal_flags: TraversalFlags,
                              snapshot_map: &'a ServoElementSnapshotTable)
                              -> SharedStyleContext<'a> {
+    let visited_styles_enabled =
+        unsafe { bindings::Gecko_AreVisitedLinksEnabled() } &&
+        !per_doc_data.is_private_browsing_enabled();
+
     SharedStyleContext {
         stylist: &per_doc_data.stylist,
+        visited_styles_enabled: visited_styles_enabled,
         options: global_style_data.options.clone(),
         guards: StylesheetGuards::same(guard),
         timer: Timer::new(),
         quirks_mode: per_doc_data.stylist.quirks_mode(),
         traversal_flags: traversal_flags,
         snapshot_map: snapshot_map,
     }
 }
@@ -1731,16 +1736,19 @@ pub extern "C" fn Servo_ComputedValues_I
     style.into()
 }
 
 #[no_mangle]
 pub extern "C" fn Servo_ComputedValues_GetStyleBits(values: ServoStyleContextBorrowed) -> u64 {
     use style::properties::computed_value_flags::*;
     let flags = values.flags;
     let mut result = 0;
+    if flags.contains(IS_RELEVANT_LINK_VISITED) {
+        result |= structs::NS_STYLE_RELEVANT_LINK_VISITED as u64;
+    }
     if flags.contains(HAS_TEXT_DECORATION_LINES) {
         result |= structs::NS_STYLE_HAS_TEXT_DECORATION_LINES as u64;
     }
     if flags.contains(SHOULD_SUPPRESS_LINEBREAK) {
         result |= structs::NS_STYLE_SUPPRESS_LINEBREAK as u64;
     }
     if flags.contains(IS_TEXT_COMBINED) {
         result |= structs::NS_STYLE_IS_TEXT_COMBINED as u64;
--- a/taskcluster/scripts/builder/hazard-analysis.sh
+++ b/taskcluster/scripts/builder/hazard-analysis.sh
@@ -155,16 +155,16 @@ function check_hazards () {
     echo "TinderboxPrint: heap write hazards<br/>$NUM_WRITE_HAZARDS"
 
     if [ $NUM_HAZARDS -gt 0 ]; then
         echo "TEST-UNEXPECTED-FAIL $NUM_HAZARDS rooting hazards detected" >&2
         echo "TinderboxPrint: documentation<br/><a href='https://wiki.mozilla.org/Javascript:Hazard_Builds#Diagnosing_a_rooting_hazards_failure'>static rooting hazard analysis failures</a>, visit \"Inspect Task\" link for hazard details"
         exit 1
     fi
 
-    NUM_ALLOWED_WRITE_HAZARDS=4
+    NUM_ALLOWED_WRITE_HAZARDS=3
     if [ $NUM_WRITE_HAZARDS -gt $NUM_ALLOWED_WRITE_HAZARDS ]; then
         echo "TEST-UNEXPECTED-FAIL $NUM_WRITE_HAZARDS heap write hazards detected out of $NUM_ALLOWED_WRITE_HAZARDS allowed" >&2
         echo "TinderboxPrint: documentation<br/><a href='https://wiki.mozilla.org/Javascript:Hazard_Builds#Diagnosing_a_heap_write_hazard_failure'>heap write hazard analysis failures</a>, visit \"Inspect Task\" link for hazard details"
         exit 1
     fi
     )
 }
--- a/widget/BasicEvents.h
+++ b/widget/BasicEvents.h
@@ -235,29 +235,45 @@ public:
    * Return true if the event shouldn't be dispatched to remote process.
    */
   inline bool IsCrossProcessForwardingStopped() const
   {
     return mNoRemoteProcessDispatch;
   }
   /**
    * Mark the event as waiting reply from remote process.
+   * If the caller needs to win other keyboard event handlers in chrome,
+   * the caller should call StopPropagation() too.
+   * Otherwise, if the caller just needs to know if the event is consumed by
+   * either content or chrome, it should just call this because the event
+   * may be reserved by chrome and it needs to be dispatched into the DOM
+   * tree in chrome for checking if it's reserved before being sent to any
+   * remote processes.
    */
   inline void MarkAsWaitingReplyFromRemoteProcess()
   {
     MOZ_ASSERT(!mPostedToRemoteProcess);
-    // When this is called, it means that event handlers in this process need
-    // a reply from content in a remote process.  So, callers should stop
-    // propagation in this process first.
-    NS_ASSERTION(PropagationStopped(),
-                 "Why didn't you stop propagation in this process?");
     mNoRemoteProcessDispatch = false;
     mWantReplyFromContentProcess = true;
   }
   /**
+   * Reset "waiting reply from remote process" state.  This is useful when
+   * you dispatch a copy of an event coming from different process.
+   */
+  inline void ResetWaitingReplyFromRemoteProcessState()
+  {
+    if (IsWaitingReplyFromRemoteProcess()) {
+      // FYI: mWantReplyFromContentProcess is also used for indicating
+      //      "handled in remote process" state.  Therefore, only when
+      //      IsWaitingReplyFromRemoteProcess() returns true, this should
+      //      reset the flag.
+      mWantReplyFromContentProcess = false;
+    }
+  }
+  /**
    * Return true if the event handler should wait reply event.  I.e., if this
    * returns true, any event handler should do nothing with the event.
    */
   inline bool IsWaitingReplyFromRemoteProcess() const
   {
     return !mNoRemoteProcessDispatch && mWantReplyFromContentProcess;
   }
   /**
@@ -296,16 +312,22 @@ public:
   /**
    * Reset the cross process dispatching state.  This should be used when a
    * process receives the event because the state is in the sender.
    */
   inline void ResetCrossProcessDispatchingState()
   {
     MOZ_ASSERT(!IsCrossProcessForwardingStopped());
     mPostedToRemoteProcess = false;
+    // Ignore propagation state in the different process if it's marked as
+    // "waiting reply from remote process" because the process needs to
+    // stop propagation in the process until receiving a reply event.
+    if (IsWaitingReplyFromRemoteProcess()) {
+      mPropagationStopped = mImmediatePropagationStopped = false;
+    }
   }
   /**
    * Return true if the event has been posted to a remote process.
    * Note that MarkAsPostedToRemoteProcess() is called by
    * ParamTraits<mozilla::WidgetEvent>.  Therefore, it *might* be possible
    * that posting the event failed even if this returns true.  But that must
    * really rare.  If that'd be problem for you, you should unmark this in
    * TabParent or somewhere.
@@ -625,16 +647,24 @@ public:
    * Mark the event as waiting reply from remote process.
    * Note that this also stops immediate propagation in current process.
    */
   inline void MarkAsWaitingReplyFromRemoteProcess()
   {
     mFlags.MarkAsWaitingReplyFromRemoteProcess();
   }
   /**
+   * Reset "waiting reply from remote process" state.  This is useful when
+   * you dispatch a copy of an event coming from different process.
+   */
+  inline void ResetWaitingReplyFromRemoteProcessState()
+  {
+    mFlags.ResetWaitingReplyFromRemoteProcessState();
+  }
+  /**
    * Return true if the event handler should wait reply event.  I.e., if this
    * returns true, any event handler should do nothing with the event.
    */
   inline bool IsWaitingReplyFromRemoteProcess() const
   {
     return mFlags.IsWaitingReplyFromRemoteProcess();
   }
   /**
@@ -756,16 +786,21 @@ public:
    */
   bool HasPluginActivationEventMessage() const;
 
   /**
    * Returns true if the event can be sent to remote process.
    */
   bool CanBeSentToRemoteProcess() const;
   /**
+   * Returns true if the original target is a remote process and the event
+   * will be posted to the remote process later.
+   */
+  bool WillBeSentToRemoteProcess() const;
+  /**
    * Returns true if the event is native event deliverer event for plugin and
    * it should be retarted to focused document.
    */
   bool IsRetargetedNativeEventDelivererForPlugin() const;
   /**
    * Returns true if the event is native event deliverer event for plugin and
    * it should NOT be retarted to focused document.
    */
--- a/widget/EventForwards.h
+++ b/widget/EventForwards.h
@@ -145,16 +145,18 @@ namespace mozilla {
 struct BaseEventFlags;
 struct EventFlags;
 
 class WidgetEventTime;
 
 class NativeEventData;
 
 // TextEvents.h
+enum class AccessKeyType;
+
 struct AlternativeCharCode;
 struct ShortcutKeyCandidate;
 
 typedef nsTArray<ShortcutKeyCandidate> ShortcutKeyCandidateArray;
 typedef AutoTArray<ShortcutKeyCandidate, 10> AutoShortcutKeyCandidateArray;
 
 // TextRange.h
 typedef uint8_t RawTextRangeType;
--- a/widget/TextEvents.h
+++ b/widget/TextEvents.h
@@ -54,16 +54,26 @@ const nsCString GetDOMKeyCodeName(uint32
 namespace dom {
   class PBrowserParent;
   class PBrowserChild;
 } // namespace dom
 namespace plugins {
   class PPluginInstanceChild;
 } // namespace plugins
 
+enum class AccessKeyType
+{
+  // Handle access key for chrome.
+  eChrome,
+  // Handle access key for content.
+  eContent,
+  // Don't handle access key.
+  eNone
+};
+
 /******************************************************************************
  * mozilla::AlternativeCharCode
  *
  * This stores alternative charCode values of a key event with some modifiers.
  * The stored values proper for testing shortcut key or access key.
  ******************************************************************************/
 
 struct AlternativeCharCode
@@ -139,17 +149,16 @@ private:
 
 protected:
   WidgetKeyboardEvent()
     : mNativeKeyEvent(nullptr)
     , mKeyCode(0)
     , mCharCode(0)
     , mPseudoCharCode(0)
     , mLocation(eKeyLocationStandard)
-    , mAccessKeyForwardedToChild(false)
     , mUniqueId(0)
 #ifdef XP_MACOSX
     , mNativeModifierFlags(0)
     , mNativeKeyCode(0)
 #endif // #ifdef XP_MACOSX
     , mKeyNameIndex(KEY_NAME_INDEX_Unidentified)
     , mCodeNameIndex(CODE_NAME_INDEX_UNKNOWN)
     , mIsRepeat(false)
@@ -168,17 +177,16 @@ public:
                       nsIWidget* aWidget,
                       EventClassID aEventClassID = eKeyboardEventClass)
     : WidgetInputEvent(aIsTrusted, aMessage, aWidget, aEventClassID)
     , mNativeKeyEvent(nullptr)
     , mKeyCode(0)
     , mCharCode(0)
     , mPseudoCharCode(0)
     , mLocation(eKeyLocationStandard)
-    , mAccessKeyForwardedToChild(false)
     , mUniqueId(0)
 #ifdef XP_MACOSX
     , mNativeModifierFlags(0)
     , mNativeKeyCode(0)
 #endif // #ifdef XP_MACOSX
     , mKeyNameIndex(KEY_NAME_INDEX_Unidentified)
     , mCodeNameIndex(CODE_NAME_INDEX_UNKNOWN)
     , mIsRepeat(false)
@@ -264,21 +272,16 @@ public:
   // unmodified value except Shift and AltGr.
   uint32_t mCharCode;
   // mPseudoCharCode is valid only when mMessage is an eKeyDown event.
   // This stores mCharCode value of keypress event which is fired with same
   // key value and same modifier state.
   uint32_t mPseudoCharCode;
   // One of eKeyLocation*
   uint32_t mLocation;
-  // True if accesskey handling was forwarded to the child via
-  // TabParent::HandleAccessKey. In this case, parent process menu access key
-  // handling should be delayed until it is determined that there exists no
-  // overriding access key in the content process.
-  bool mAccessKeyForwardedToChild;
   // Unique id associated with a keydown / keypress event. It's ok if this wraps
   // over long periods.
   uint32_t mUniqueId;
 
 #ifdef XP_MACOSX
   // Values given by a native NSEvent, for use with Cocoa NPAPI plugins.
   uint32_t mNativeModifierFlags;
   uint16_t mNativeKeyCode;
@@ -426,16 +429,34 @@ public:
   /**
    * Get the candidates for access key.
    *
    * @param aCandidates [out] the candidate access key list.
    *                          the first item is most preferred.
    */
   void GetAccessKeyCandidates(nsTArray<uint32_t>& aCandidates) const;
 
+  /**
+   * Check whether the modifiers match with chrome access key or
+   * content access key.
+   */
+  bool ModifiersMatchWithAccessKey(AccessKeyType aType) const;
+
+  /**
+   * Return active modifiers which may match with access key.
+   * For example, even if Alt is access key modifier, then, when Control,
+   * CapseLock and NumLock are active, this returns only MODIFIER_CONTROL.
+   */
+  Modifiers ModifiersForAccessKeyMatching() const;
+
+  /**
+   * Return access key modifiers.
+   */
+  static Modifiers AccessKeyModifiers(AccessKeyType aType);
+
   static void Shutdown();
 
   /**
    * ComputeLocationFromCodeValue() returns one of .mLocation value
    * (eKeyLocation*) which is the most preferred value for the specified code
    * value.
    */
   static uint32_t ComputeLocationFromCodeValue(CodeNameIndex aCodeNameIndex);
@@ -476,17 +497,16 @@ public:
 
     mKeyCode = aEvent.mKeyCode;
     mCharCode = aEvent.mCharCode;
     mPseudoCharCode = aEvent.mPseudoCharCode;
     mLocation = aEvent.mLocation;
     mAlternativeCharCodes = aEvent.mAlternativeCharCodes;
     mIsRepeat = aEvent.mIsRepeat;
     mIsComposing = aEvent.mIsComposing;
-    mAccessKeyForwardedToChild = aEvent.mAccessKeyForwardedToChild;
     mKeyNameIndex = aEvent.mKeyNameIndex;
     mCodeNameIndex = aEvent.mCodeNameIndex;
     mKeyValue = aEvent.mKeyValue;
     mCodeValue = aEvent.mCodeValue;
     // Don't copy mNativeKeyEvent because it may be referred after its instance
     // is destroyed.
     mNativeKeyEvent = nullptr;
     mUniqueId = aEvent.mUniqueId;
@@ -560,16 +580,20 @@ private:
         return mEditCommandsForMultiLineEditorInitialized;
       case nsIWidget::NativeKeyBindingsForRichTextEditor:
         return mEditCommandsForRichTextEditorInitialized;
       default:
         MOZ_MAKE_COMPILER_ASSUME_IS_UNREACHABLE(
           "Invalid native key binding type");
     }
   }
+
+  static int32_t GenericAccessModifierKeyPref();
+  static int32_t ChromeAccessModifierMaskPref();
+  static int32_t ContentAccessModifierMaskPref();
 };
 
 /******************************************************************************
  * mozilla::WidgetCompositionEvent
  ******************************************************************************/
 
 class WidgetCompositionEvent : public WidgetGUIEvent
 {
--- a/widget/WidgetEventImpl.cpp
+++ b/widget/WidgetEventImpl.cpp
@@ -1,22 +1,24 @@
 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "gfxPrefs.h"
 #include "mozilla/BasicEvents.h"
 #include "mozilla/ContentEvents.h"
+#include "mozilla/EventStateManager.h"
 #include "mozilla/InternalMutationEvent.h"
 #include "mozilla/MiscEvents.h"
 #include "mozilla/MouseEvents.h"
 #include "mozilla/Preferences.h"
 #include "mozilla/TextEvents.h"
 #include "mozilla/TouchEvents.h"
+#include "nsIContent.h"
 #include "nsIDOMEventTarget.h"
 #include "nsPrintfCString.h"
 
 namespace mozilla {
 
 /******************************************************************************
  * Global helper methods
  ******************************************************************************/
@@ -345,16 +347,34 @@ WidgetEvent::CanBeSentToRemoteProcess() 
     case eDrop:
       return true;
     default:
       return false;
   }
 }
 
 bool
+WidgetEvent::WillBeSentToRemoteProcess() const
+{
+  // This event won't be posted to remote process if it's already explicitly
+  // stopped.
+  if (IsCrossProcessForwardingStopped()) {
+    return false;
+  }
+
+  // When mOriginalTarget is nullptr, this method shouldn't be used.
+  if (NS_WARN_IF(!mOriginalTarget)) {
+    return false;
+  }
+
+  nsCOMPtr<nsIContent> originalTarget = do_QueryInterface(mOriginalTarget);
+  return EventStateManager::IsRemoteTarget(originalTarget);
+}
+
+bool
 WidgetEvent::IsRetargetedNativeEventDelivererForPlugin() const
 {
   const WidgetPluginEvent* pluginEvent = AsPluginEvent();
   return pluginEvent && pluginEvent->mRetargetToFocusedDocument;
 }
 
 bool
 WidgetEvent::IsNonRetargetedNativeEventDelivererForPlugin() const
@@ -864,16 +884,138 @@ WidgetKeyboardEvent::GetAccessKeyCandida
   // press.  However, if the space key is assigned to a function key, it
   // shouldn't work as a space key.
   if (mKeyNameIndex == KEY_NAME_INDEX_USE_STRING &&
       mCodeNameIndex == CODE_NAME_INDEX_Space && mCharCode != ' ') {
     aCandidates.AppendElement(' ');
   }
 }
 
+// mask values for ui.key.chromeAccess and ui.key.contentAccess
+#define NS_MODIFIER_SHIFT    1
+#define NS_MODIFIER_CONTROL  2
+#define NS_MODIFIER_ALT      4
+#define NS_MODIFIER_META     8
+#define NS_MODIFIER_OS       16
+
+static Modifiers PrefFlagsToModifiers(int32_t aPrefFlags)
+{
+  Modifiers result = 0;
+  if (aPrefFlags & NS_MODIFIER_SHIFT) {
+    result |= MODIFIER_SHIFT;
+  }
+  if (aPrefFlags & NS_MODIFIER_CONTROL) {
+    result |= MODIFIER_CONTROL;
+  }
+  if (aPrefFlags & NS_MODIFIER_ALT) {
+    result |= MODIFIER_ALT;
+  }
+  if (aPrefFlags & NS_MODIFIER_META) {
+    result |= MODIFIER_META;
+  }
+  if (aPrefFlags & NS_MODIFIER_OS) {
+    result |= MODIFIER_OS;
+  }
+  return result;
+}
+
+bool
+WidgetKeyboardEvent::ModifiersMatchWithAccessKey(AccessKeyType aType) const
+{
+  if (!ModifiersForAccessKeyMatching()) {
+    return false;
+  }
+  return ModifiersForAccessKeyMatching() == AccessKeyModifiers(aType);
+}
+
+Modifiers
+WidgetKeyboardEvent::ModifiersForAccessKeyMatching() const
+{
+  static const Modifiers kModifierMask =
+    MODIFIER_SHIFT | MODIFIER_CONTROL |
+    MODIFIER_ALT | MODIFIER_META | MODIFIER_OS;
+  return mModifiers & kModifierMask;
+}
+
+/* static */
+Modifiers
+WidgetKeyboardEvent::AccessKeyModifiers(AccessKeyType aType)
+{
+  switch (GenericAccessModifierKeyPref()) {
+    case -1:
+      break; // use the individual prefs
+    case NS_VK_SHIFT:
+      return MODIFIER_SHIFT;
+    case NS_VK_CONTROL:
+      return MODIFIER_CONTROL;
+    case NS_VK_ALT:
+      return MODIFIER_ALT;
+    case NS_VK_META:
+      return MODIFIER_META;
+    case NS_VK_WIN:
+      return MODIFIER_OS;
+    default:
+      return MODIFIER_NONE;
+  }
+
+  switch (aType) {
+    case AccessKeyType::eChrome:
+      return PrefFlagsToModifiers(ChromeAccessModifierMaskPref());
+    case AccessKeyType::eContent:
+      return PrefFlagsToModifiers(ContentAccessModifierMaskPref());
+    default:
+      return MODIFIER_NONE;
+  }
+}
+
+/* static */
+int32_t
+WidgetKeyboardEvent::GenericAccessModifierKeyPref()
+{
+  static bool sInitialized = false;
+  static int32_t sValue = -1;
+  if (!sInitialized) {
+    nsresult rv =
+      Preferences::AddIntVarCache(&sValue, "ui.key.generalAccessKey", sValue);
+    sInitialized = NS_SUCCEEDED(rv);
+    MOZ_ASSERT(sInitialized);
+  }
+  return sValue;
+}
+
+/* static */
+int32_t
+WidgetKeyboardEvent::ChromeAccessModifierMaskPref()
+{
+  static bool sInitialized = false;
+  static int32_t sValue = 0;
+  if (!sInitialized) {
+    nsresult rv =
+      Preferences::AddIntVarCache(&sValue, "ui.key.chromeAccess", sValue);
+    sInitialized = NS_SUCCEEDED(rv);
+    MOZ_ASSERT(sInitialized);
+  }
+  return sValue;
+}
+
+/* static */
+int32_t
+WidgetKeyboardEvent::ContentAccessModifierMaskPref()
+{
+  static bool sInitialized = false;
+  static int32_t sValue = 0;
+  if (!sInitialized) {
+    nsresult rv =
+      Preferences::AddIntVarCache(&sValue, "ui.key.contentAccess", sValue);
+    sInitialized = NS_SUCCEEDED(rv);
+    MOZ_ASSERT(sInitialized);
+  }
+  return sValue;
+}
+
 /* static */ void
 WidgetKeyboardEvent::Shutdown()
 {
   delete sKeyNameIndexHashtable;
   sKeyNameIndexHashtable = nullptr;
   delete sCodeNameIndexHashtable;
   sCodeNameIndexHashtable = nullptr;
 }
--- a/widget/cocoa/TextInputHandler.mm
+++ b/widget/cocoa/TextInputHandler.mm
@@ -2810,17 +2810,22 @@ IMEInputHandler::WillDispatchKeyboardEve
 
   KeyEventState* currentKeyEvent = static_cast<KeyEventState*>(aData);
   NSEvent* nativeEvent = currentKeyEvent->mKeyEvent;
   nsAString* insertString = currentKeyEvent->mInsertString;
   if (aKeyboardEvent.mMessage == eKeyPress && aIndexOfKeypress == 0 &&
       (!insertString || insertString->IsEmpty())) {
     // Inform the child process that this is an event that we want a reply
     // from.
-    aKeyboardEvent.mFlags.mWantReplyFromContentProcess = true;
+    // XXX This should be called only when the target is a remote process.
+    //     However, it's difficult to check it under widget/.
+    //     So, let's do this here for now, then,
+    //     EventStateManager::PreHandleEvent() will reset the flags if
+    //     the event target isn't in remote process.
+    aKeyboardEvent.MarkAsWaitingReplyFromRemoteProcess();
   }
   if (KeyboardLayoutOverrideRef().mOverrideEnabled) {
     TISInputSourceWrapper tis;
     tis.InitByLayoutID(KeyboardLayoutOverrideRef().mKeyboardLayout, true);
     tis.WillDispatchKeyboardEvent(nativeEvent, insertString, aKeyboardEvent);
     return;
   }
   TISInputSourceWrapper::CurrentInputSource().
--- a/widget/nsGUIEventIPC.h
+++ b/widget/nsGUIEventIPC.h
@@ -444,17 +444,16 @@ struct ParamTraits<mozilla::WidgetKeyboa
                static_cast<mozilla::CodeNameIndexType>(aParam.mCodeNameIndex));
     WriteParam(aMsg, aParam.mKeyValue);
     WriteParam(aMsg, aParam.mCodeValue);
     WriteParam(aMsg, aParam.mKeyCode);
     WriteParam(aMsg, aParam.mCharCode);
     WriteParam(aMsg, aParam.mPseudoCharCode);
     WriteParam(aMsg, aParam.mAlternativeCharCodes);
     WriteParam(aMsg, aParam.mIsRepeat);
-    WriteParam(aMsg, aParam.mAccessKeyForwardedToChild);
     WriteParam(aMsg, aParam.mLocation);
     WriteParam(aMsg, aParam.mUniqueId);
     WriteParam(aMsg, aParam.mIsSynthesizedByTIP);
 #ifdef XP_MACOSX
     WriteParam(aMsg, aParam.mNativeKeyCode);
     WriteParam(aMsg, aParam.mNativeModifierFlags);
     WriteParam(aMsg, aParam.mNativeCharacters);
     WriteParam(aMsg, aParam.mNativeCharactersIgnoringModifiers);
@@ -482,17 +481,16 @@ struct ParamTraits<mozilla::WidgetKeyboa
         ReadParam(aMsg, aIter, &codeNameIndex) &&
         ReadParam(aMsg, aIter, &aResult->mKeyValue) &&
         ReadParam(aMsg, aIter, &aResult->mCodeValue) &&
         ReadParam(aMsg, aIter, &aResult->mKeyCode) &&
         ReadParam(aMsg, aIter, &aResult->mCharCode) &&
         ReadParam(aMsg, aIter, &aResult->mPseudoCharCode) &&
         ReadParam(aMsg, aIter, &aResult->mAlternativeCharCodes) &&
         ReadParam(aMsg, aIter, &aResult->mIsRepeat) &&
-        ReadParam(aMsg, aIter, &aResult->mAccessKeyForwardedToChild) &&
         ReadParam(aMsg, aIter, &aResult->mLocation) &&
         ReadParam(aMsg, aIter, &aResult->mUniqueId) &&
         ReadParam(aMsg, aIter, &aResult->mIsSynthesizedByTIP) &&
 #ifdef XP_MACOSX
         ReadParam(aMsg, aIter, &aResult->mNativeKeyCode) &&
         ReadParam(aMsg, aIter, &aResult->mNativeModifierFlags) &&
         ReadParam(aMsg, aIter, &aResult->mNativeCharacters) &&
         ReadParam(aMsg, aIter, &aResult->mNativeCharactersIgnoringModifiers) &&