Merge inbound to central, a=merge CLOSED TREE
authorWes Kocher <wkocher@mozilla.com>
Fri, 21 Jul 2017 18:12:55 -0700
changeset 418939 057d626fc5e1afbf4086eb9400ebb3718a4cabca
parent 418889 a8bb5530a0925ac0823bdb6d525cbc6f304f8928 (current diff)
parent 418938 98d3f0b9791d22f30da5aa9824209118c22a3d03 (diff)
child 419019 4fe0649bd462a3332e4429910a6396c5dd00859e
child 419067 66f0d5a2c077325dcd716a2a0bc6192bc4fc9fae
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 inbound to central, a=merge CLOSED TREE MozReview-Commit-ID: GYc8r8gnS0j
browser/components/sessionstore/PageStyle.jsm
browser/components/sessionstore/test/browser_form_restore_events.js
browser/components/sessionstore/test/browser_form_restore_events_sample.html
browser/components/sessionstore/test/browser_pageStyle.js
browser/components/sessionstore/test/browser_pageStyle_sample.html
browser/components/sessionstore/test/browser_pageStyle_sample_nested.html
gfx/layers/wr/WebRenderLayerManager.cpp
layout/painting/nsDisplayList.cpp
layout/painting/nsDisplayList.h
modules/libpref/init/all.js
--- a/browser/app/profile/firefox.js
+++ b/browser/app/profile/firefox.js
@@ -1634,21 +1634,18 @@ pref("toolkit.pageThumbs.minHeight", 190
 pref("media.webspeech.synth.enabled", true);
 
 pref("browser.esedbreader.loglevel", "Error");
 
 pref("browser.laterrun.enabled", false);
 
 // Disable prelaunch in the same way activity-stream is enabled addressing
 // bug 1381804 memory usage until bug 1376895 is fixed.
-#ifdef NIGHTLY_BUILD
+// Because of frequent crashes on Beta, it is turned off on all channels, see: bug 1363601.
 pref("dom.ipc.processPrelaunch.enabled", false);
-#else
-pref("dom.ipc.processPrelaunch.enabled", true);
-#endif
 
 pref("browser.migrate.automigrate.enabled", false);
 // 4 here means the suggestion notification will be automatically
 // hidden the 4th day, so it will actually be shown on 3 different days.
 pref("browser.migrate.automigrate.daysToOfferUndo", 4);
 pref("browser.migrate.automigrate.ui.enabled", true);
 pref("browser.migrate.automigrate.inpage.ui.enabled", false);
 
--- a/browser/components/customizableui/test/browser_1008559_anchor_undo_restore.js
+++ b/browser/components/customizableui/test/browser_1008559_anchor_undo_restore.js
@@ -12,17 +12,19 @@ const kAnchorAttribute = "cui-anchorid";
 add_task(async function() {
   await SpecialPowers.pushPrefEnv({set: [["browser.photon.structure.enabled", false]]});
   await startCustomizing();
   let button = document.getElementById("history-panelmenu");
   is(button.getAttribute(kAnchorAttribute), "PanelUI-menu-button",
      "Button (" + button.id + ") starts out with correct anchor");
 
   let navbar = document.getElementById("nav-bar").customizationTarget;
+  let onMouseUp = BrowserTestUtils.waitForEvent(navbar, "mouseup");
   simulateItemDrag(button, navbar);
+  await onMouseUp;
   is(CustomizableUI.getPlacementOfWidget(button.id).area, "nav-bar",
      "Button (" + button.id + ") ends up in nav-bar");
 
   ok(!button.hasAttribute(kAnchorAttribute),
      "Button (" + button.id + ") has no anchor in toolbar");
 
   let resetButton = document.getElementById("customization-reset-button");
   ok(!resetButton.hasAttribute("disabled"), "Should be able to reset now.");
@@ -50,17 +52,19 @@ add_task(async function() {
  */
 add_task(async function() {
   await startCustomizing();
   let button = document.getElementById("bookmarks-menu-button");
   ok(!button.hasAttribute(kAnchorAttribute),
      "Button (" + button.id + ") has no anchor in toolbar");
 
   let panel = document.getElementById("PanelUI-contents");
+  let onMouseUp = BrowserTestUtils.waitForEvent(panel, "mouseup");
   simulateItemDrag(button, panel);
+  await onMouseUp;
   is(CustomizableUI.getPlacementOfWidget(button.id).area, "PanelUI-contents",
      "Button (" + button.id + ") ends up in panel");
   is(button.getAttribute(kAnchorAttribute), "PanelUI-menu-button",
      "Button (" + button.id + ") has correct anchor in the panel");
 
   let resetButton = document.getElementById("customization-reset-button");
   ok(!resetButton.hasAttribute("disabled"), "Should be able to reset now.");
   await gCustomizeMode.reset();
--- a/browser/components/sessionstore/ContentRestore.jsm
+++ b/browser/components/sessionstore/ContentRestore.jsm
@@ -11,18 +11,16 @@ const Ci = Components.interfaces;
 
 Cu.import("resource://gre/modules/XPCOMUtils.jsm", this);
 Cu.import("resource://gre/modules/Services.jsm", this);
 
 XPCOMUtils.defineLazyModuleGetter(this, "DocShellCapabilities",
   "resource:///modules/sessionstore/DocShellCapabilities.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "FormData",
   "resource://gre/modules/FormData.jsm");
-XPCOMUtils.defineLazyModuleGetter(this, "PageStyle",
-  "resource:///modules/sessionstore/PageStyle.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "ScrollPosition",
   "resource://gre/modules/ScrollPosition.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "SessionHistory",
   "resource://gre/modules/sessionstore/SessionHistory.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "SessionStorage",
   "resource:///modules/sessionstore/SessionStorage.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "Utils",
   "resource://gre/modules/sessionstore/Utils.jsm");
@@ -78,17 +76,17 @@ function ContentRestoreInternal(chromeGl
 
   // The following fields are only valid during certain phases of the restore
   // process.
 
   // The tabData for the restore. Set in restoreHistory and removed in
   // restoreTabContent.
   this._tabData = null;
 
-  // Contains {entry, pageStyle, scrollPositions, formdata}, where entry is a
+  // Contains {entry, scrollPositions, formdata}, where entry is a
   // single entry from the tabData.entries array. Set in
   // restoreTabContent and removed in restoreDocument.
   this._restoringDocument = null;
 
   // This listener is used to detect reloads on restoring tabs. Set in
   // restoreHistory and removed in restoreTabContent.
   this._historyListener = null;
 
@@ -225,17 +223,16 @@ ContentRestoreInternal.prototype = {
                               Ci.nsIWebNavigation.LOAD_FLAGS_ALLOW_THIRD_PARTY_FIXUP,
                               null, null, null,
                               Services.scriptSecurityManager.getSystemPrincipal());
       } else if (tabData.entries.length) {
         // Stash away the data we need for restoreDocument.
         let activeIndex = tabData.index - 1;
         this._restoringDocument = {entry: tabData.entries[activeIndex] || {},
                                    formdata: tabData.formdata || {},
-                                   pageStyle: tabData.pageStyle || {},
                                    scrollPositions: tabData.scroll || {}};
 
         // In order to work around certain issues in session history, we need to
         // force session history to update its internal index and call reload
         // instead of gotoIndex. See bug 597315.
         history.reloadCurrentEntry();
       } else {
         // If there's nothing to restore, we should still blank the page.
@@ -286,23 +283,22 @@ ContentRestoreInternal.prototype = {
    * Finish restoring the tab by filling in form data and setting the scroll
    * position. The restore is complete when this function exits. It should be
    * called when the "load" event fires for the restoring tab.
    */
   restoreDocument() {
     if (!this._restoringDocument) {
       return;
     }
-    let {pageStyle, formdata, scrollPositions} = this._restoringDocument;
+    let {formdata, scrollPositions} = this._restoringDocument;
     this._restoringDocument = null;
 
     let window = this.docShell.QueryInterface(Ci.nsIInterfaceRequestor)
                                .getInterface(Ci.nsIDOMWindow);
 
-    PageStyle.restoreTree(this.docShell, pageStyle);
     FormData.restoreTree(window, formdata);
     ScrollPosition.restoreTree(window, scrollPositions);
   },
 
   /**
    * Cancel an ongoing restore. This function can be called any time between
    * restoreHistory and restoreDocument.
    *
deleted file mode 100644
--- a/browser/components/sessionstore/PageStyle.jsm
+++ /dev/null
@@ -1,100 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
-* License, v. 2.0. If a copy of the MPL was not distributed with this file,
-* You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-"use strict";
-
-this.EXPORTED_SYMBOLS = ["PageStyle"];
-
-const Ci = Components.interfaces;
-
-/**
- * The external API exported by this module.
- */
-this.PageStyle = Object.freeze({
-  collect(docShell, frameTree) {
-    return PageStyleInternal.collect(docShell, frameTree);
-  },
-
-  restoreTree(docShell, data) {
-    PageStyleInternal.restoreTree(docShell, data);
-  }
-});
-
-// Signifies that author style level is disabled for the page.
-const NO_STYLE = "_nostyle";
-
-var PageStyleInternal = {
-  /**
-   * Collects the selected style sheet sets for all reachable frames.
-   */
-  collect(docShell, frameTree) {
-    let result = frameTree.map(({document: doc}) => {
-      let style;
-
-      if (doc) {
-        // http://dev.w3.org/csswg/cssom/#persisting-the-selected-css-style-sheet-set
-        style = doc.selectedStyleSheetSet || doc.lastStyleSheetSet;
-      }
-
-      return style ? {pageStyle: style} : null;
-    });
-
-    let markupDocumentViewer =
-      docShell.contentViewer;
-
-    if (markupDocumentViewer.authorStyleDisabled) {
-      result = result || {};
-      result.disabled = true;
-    }
-
-    return result && Object.keys(result).length ? result : null;
-  },
-
-  /**
-   * Restores pageStyle data for the current frame hierarchy starting at the
-   * |docShell's| current DOMWindow using the given pageStyle |data|.
-   *
-   * Warning: If the current frame hierarchy doesn't match that of the given
-   * |data| object we will silently discard data for unreachable frames. We may
-   * as well assign page styles to the wrong frames if some were reordered or
-   * removed.
-   *
-   * @param docShell (nsIDocShell)
-   * @param data (object)
-   *        {
-   *          disabled: true, // when true, author styles will be disabled
-   *          pageStyle: "Dusk",
-   *          children: [
-   *            null,
-   *            {pageStyle: "Mozilla", children: [ ... ]}
-   *          ]
-   *        }
-   */
-  restoreTree(docShell, data) {
-    let disabled = data.disabled || false;
-    let markupDocumentViewer =
-      docShell.contentViewer;
-    markupDocumentViewer.authorStyleDisabled = disabled;
-
-    function restoreFrame(root, frameData) {
-      if (frameData.hasOwnProperty("pageStyle")) {
-        root.document.selectedStyleSheetSet = frameData.pageStyle;
-      }
-
-      if (!frameData.hasOwnProperty("children")) {
-        return;
-      }
-
-      let frames = root.frames;
-      frameData.children.forEach((child, index) => {
-        if (child && index < frames.length) {
-          restoreFrame(frames[index], child);
-        }
-      });
-    }
-
-    let ifreq = docShell.QueryInterface(Ci.nsIInterfaceRequestor);
-    restoreFrame(ifreq.getInterface(Ci.nsIDOMWindow), data);
-  }
-};
--- a/browser/components/sessionstore/SessionStore.jsm
+++ b/browser/components/sessionstore/SessionStore.jsm
@@ -3685,17 +3685,16 @@ var SessionStoreInternal = {
     TabStateCache.update(browser, {
       // NOTE: Copy the entries array shallowly, so as to not screw with the
       // original tabData's history when getting history updates.
       history: {entries: [...tabData.entries], index: tabData.index},
       scroll: tabData.scroll || null,
       storage: tabData.storage || null,
       formdata: tabData.formdata || null,
       disallow: tabData.disallow || null,
-      pageStyle: tabData.pageStyle || null,
       userContextId: tabData.userContextId || 0,
 
       // This information is only needed until the tab has finished restoring.
       // When that's done it will be removed from the cache and we always
       // collect it in TabState._collectBaseTabData().
       image: tabData.image || "",
       iconLoadingPrincipal: tabData.iconLoadingPrincipal || null,
       userTypedValue: tabData.userTypedValue || "",
--- a/browser/components/sessionstore/content/content-sessionStore.js
+++ b/browser/components/sessionstore/content/content-sessionStore.js
@@ -24,18 +24,16 @@ function debug(msg) {
 
 XPCOMUtils.defineLazyModuleGetter(this, "FormData",
   "resource://gre/modules/FormData.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "Preferences",
   "resource://gre/modules/Preferences.jsm");
 
 XPCOMUtils.defineLazyModuleGetter(this, "DocShellCapabilities",
   "resource:///modules/sessionstore/DocShellCapabilities.jsm");
-XPCOMUtils.defineLazyModuleGetter(this, "PageStyle",
-  "resource:///modules/sessionstore/PageStyle.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "ScrollPosition",
   "resource://gre/modules/ScrollPosition.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "SessionHistory",
   "resource://gre/modules/sessionstore/SessionHistory.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "SessionStorage",
   "resource:///modules/sessionstore/SessionStorage.jsm");
 
 Cu.import("resource:///modules/sessionstore/FrameTree.jsm", this);
@@ -448,17 +446,16 @@ var ScrollPositionListener = {
  *       null,
  *       {url: "http://sub.mozilla.org/", id: {input_id: "input value 2"}}
  *     ]
  *   }
  */
 var FormDataListener = {
   init() {
     addEventListener("input", this, true);
-    addEventListener("change", this, true);
     gFrameTree.addObserver(this);
   },
 
   handleEvent(event) {
     let frame = event.target.ownerGlobal;
 
     // Don't collect form data for frames created at or after the load event
     // as SessionStore can't restore form data for those.
@@ -472,60 +469,16 @@ var FormDataListener = {
   },
 
   collect() {
     return gFrameTree.map(FormData.collect);
   }
 };
 
 /**
- * Listens for changes to the page style. Whenever a different page style is
- * selected or author styles are enabled/disabled we send a message with the
- * currently applied style to the chrome process.
- *
- * Causes a SessionStore:update message to be sent that contains the currently
- * selected pageStyle for all reachable frames.
- *
- * Example:
- *   {pageStyle: "Dusk", children: [null, {pageStyle: "Mozilla"}]}
- */
-var PageStyleListener = {
-  init() {
-    Services.obs.addObserver(this, "author-style-disabled-changed");
-    Services.obs.addObserver(this, "style-sheet-applicable-state-changed");
-    gFrameTree.addObserver(this);
-  },
-
-  uninit() {
-    Services.obs.removeObserver(this, "author-style-disabled-changed");
-    Services.obs.removeObserver(this, "style-sheet-applicable-state-changed");
-  },
-
-  observe(subject, topic) {
-    let frame = subject.defaultView;
-
-    if (frame && gFrameTree.contains(frame)) {
-      MessageQueue.push("pageStyle", () => this.collect());
-    }
-  },
-
-  collect() {
-    return PageStyle.collect(docShell, gFrameTree);
-  },
-
-  onFrameTreeCollected() {
-    MessageQueue.push("pageStyle", () => this.collect());
-  },
-
-  onFrameTreeReset() {
-    MessageQueue.push("pageStyle", () => null);
-  }
-};
-
-/**
  * Listens for changes to docShell capabilities. Whenever a new load is started
  * we need to re-check the list of capabilities and send message when it has
  * changed.
  *
  * Causes a SessionStore:update message to be sent that contains the currently
  * disabled docShell capabilities (all nsIDocShell.allow* properties set to
  * false) as a string - i.e. capability names separate by commas.
  */
@@ -842,17 +795,16 @@ var MessageQueue = {
       }
     }
   },
 };
 
 EventListener.init();
 MessageListener.init();
 FormDataListener.init();
-PageStyleListener.init();
 SessionHistoryListener.init();
 SessionStorageListener.init();
 ScrollPositionListener.init();
 DocShellCapabilitiesListener.init();
 PrivacyListener.init();
 MessageQueue.init();
 
 function handleRevivedTab() {
@@ -887,17 +839,16 @@ addEventListener("unload", () => {
   // If we're browsing from the tab crashed UI to a URI that causes the tab
   // to go remote again, we catch this in the unload event handler, because
   // swapping out the non-remote browser for a remote one in
   // tabbrowser.xml's updateBrowserRemoteness doesn't cause the pagehide
   // event to be fired.
   handleRevivedTab();
 
   // Remove all registered nsIObservers.
-  PageStyleListener.uninit();
   SessionStorageListener.uninit();
   SessionHistoryListener.uninit();
   MessageQueue.uninit();
 
   // Remove progress listeners.
   gContentRestore.resetRestore();
 
   // We don't need to take care of any gFrameTree observers as the gFrameTree
--- a/browser/components/sessionstore/moz.build
+++ b/browser/components/sessionstore/moz.build
@@ -22,17 +22,16 @@ EXTRA_COMPONENTS += [
     'nsSessionStore.manifest',
 ]
 
 EXTRA_JS_MODULES.sessionstore = [
     'ContentRestore.jsm',
     'DocShellCapabilities.jsm',
     'FrameTree.jsm',
     'GlobalState.jsm',
-    'PageStyle.jsm',
     'PrivacyFilter.jsm',
     'RecentlyClosedTabsAndWindowsMenuUtils.jsm',
     'RunState.jsm',
     'SessionCookies.jsm',
     'SessionFile.jsm',
     'SessionMigration.jsm',
     'SessionSaver.jsm',
     'SessionStorage.jsm',
--- a/browser/components/sessionstore/test/browser.ini
+++ b/browser/components/sessionstore/test/browser.ini
@@ -20,20 +20,17 @@ support-files =
   browser_frame_history_index.html
   browser_frame_history_index2.html
   browser_frame_history_index_blank.html
   browser_frame_history_a.html
   browser_frame_history_b.html
   browser_frame_history_c.html
   browser_frame_history_c1.html
   browser_frame_history_c2.html
-  browser_form_restore_events_sample.html
   browser_formdata_format_sample.html
-  browser_pageStyle_sample.html
-  browser_pageStyle_sample_nested.html
   browser_sessionHistory_slow.sjs
   browser_scrollPositions_sample.html
   browser_scrollPositions_sample2.html
   browser_scrollPositions_sample_frameset.html
   browser_scrollPositions_readerModeArticle.html
   browser_sessionStorage.html
   browser_speculative_connect.html
   browser_248970_b_sample.html
@@ -85,29 +82,27 @@ skip-if = debug # bug 1211084
 skip-if = !e10s || !crashreporter
 [browser_unrestored_crashedTabs.js]
 skip-if = !e10s || !crashreporter
 [browser_revive_crashed_bg_tabs.js]
 skip-if = !e10s || !crashreporter
 [browser_dying_cache.js]
 skip-if = (os == 'win') # bug 1331853
 [browser_dynamic_frames.js]
-[browser_form_restore_events.js]
 [browser_formdata.js]
 [browser_formdata_cc.js]
 [browser_formdata_format.js]
 [browser_formdata_xpath.js]
 [browser_frametree.js]
 [browser_frame_history.js]
 [browser_global_store.js]
 [browser_history_persist.js]
 [browser_label_and_icon.js]
 [browser_merge_closed_tabs.js]
 [browser_page_title.js]
-[browser_pageStyle.js]
 [browser_pending_tabs.js]
 [browser_privatetabs.js]
 [browser_purge_shistory.js]
 skip-if = e10s # Bug 1271024
 [browser_replace_load.js]
 [browser_restore_redirect.js]
 [browser_restore_cookies_noOriginAttributes.js]
 [browser_scrollPositions.js]
deleted file mode 100644
--- a/browser/components/sessionstore/test/browser_form_restore_events.js
+++ /dev/null
@@ -1,63 +0,0 @@
-/* Any copyright is dedicated to the Public Domain.
- * http://creativecommons.org/publicdomain/zero/1.0/ */
-
-"use strict";
-
-const URL = ROOT + "browser_form_restore_events_sample.html";
-
-/**
- * Originally a test for Bug 476161, but then expanded to include all input
- * types in bug 640136.
- */
-add_task(async function() {
-  // Load a page with some form elements.
-  let tab = BrowserTestUtils.addTab(gBrowser, URL);
-  let browser = tab.linkedBrowser;
-  await promiseBrowserLoaded(browser);
-
-  // text fields
-  await setInputValue(browser, {id: "modify01", value: Math.random()});
-  await setInputValue(browser, {id: "modify02", value: Date.now()});
-
-  // textareas
-  await setInputValue(browser, {id: "modify03", value: Math.random()});
-  await setInputValue(browser, {id: "modify04", value: Date.now()});
-
-  // file
-  let file = Services.dirsvc.get("TmpD", Ci.nsIFile);
-  await setInputValue(browser, {id: "modify05", value: file.path});
-
-  // select
-  await setSelectedIndex(browser, {id: "modify06", index: 1});
-  await setMultipleSelected(browser, {id: "modify07", indices: [0, 1, 2]});
-
-  // checkbox
-  await setInputChecked(browser, {id: "modify08", checked: true});
-  await setInputChecked(browser, {id: "modify09", checked: false});
-
-  // radio
-  await setInputChecked(browser, {id: "modify10", checked: true});
-  await setInputChecked(browser, {id: "modify11", checked: true});
-
-  // Duplicate the tab and check that restoring form data yields the expected
-  // input and change events for modified form fields.
-  let tab2 = gBrowser.duplicateTab(tab);
-  let browser2 = tab2.linkedBrowser;
-  await promiseTabRestored(tab2);
-
-  let inputFired = await getTextContent(browser2, {id: "inputFired"});
-  inputFired = inputFired.trim().split().sort().join(" ");
-
-  let changeFired = await getTextContent(browser2, {id: "changeFired"});
-  changeFired = changeFired.trim().split().sort().join(" ");
-
-  is(inputFired, "modify01 modify02 modify03 modify04 modify05",
-     "input events were only dispatched for modified input, textarea fields");
-
-  is(changeFired, "modify06 modify07 modify08 modify09 modify11",
-     "change events were only dispatched for modified select, checkbox, radio fields");
-
-  // Cleanup.
-  gBrowser.removeTab(tab2);
-  gBrowser.removeTab(tab);
-});
deleted file mode 100644
--- a/browser/components/sessionstore/test/browser_form_restore_events_sample.html
+++ /dev/null
@@ -1,99 +0,0 @@
-<!DOCTYPE html>
-<meta charset="utf-8">
-<title>Test for form restore events (originally bug 476161)</title>
-
-<script>
-
-document.addEventListener("input", function(aEvent) {
-  var inputEl = aEvent.originalTarget;
-  var changedEl = document.getElementById("inputFired");
-  changedEl.textContent += " " + inputEl.id;
-});
-
-document.addEventListener("change", function(aEvent) {
-  var inputEl = aEvent.originalTarget;
-  var changedEl = document.getElementById("changeFired");
-  changedEl.textContent += " " + inputEl.id;
-});
-
-</script>
-
-<!-- input events -->
-<h3>Text fields with changed text</h3>
-<input type="text" id="modify1">
-<input type="text" id="modify2" value="preset value">
-<input type="text" id="modify01">
-<input type="text" id="modify02" value="preset value">
-
-<h3>Text fields with unchanged text</h3>
-<input type="text" id="unchanged1">
-<input type="text" id="unchanged2" value="preset value">
-<input type="text" id="unchanged01">
-<input type="text" id="unchanged02" value="preset value">
-
-<h3>Textarea with changed text</h3>
-<textarea id="modify03"></textarea>
-<textarea id="modify04">preset value</textarea>
-
-<h3>Textarea with unchanged text</h3>
-<textarea id="unchanged03"></textarea>
-<textarea id="unchanged04">preset value</textarea>
-
-<h3>file field with changed value</h3>
-<input type="file" id="modify05">
-
-<h3>file field with unchanged value</h3>
-<input type="file" id="unchanged05">
-
-<!-- change events -->
-
-<h3>Select menu with changed selection</h3>
-<select id="modify06">
-  <option value="one">one</option>
-  <option value="two">two</option>
-  <option value="three">three</option>
-</select>
-
-<h3>Select menu with unchanged selection (change event still fires)</h3>
-<select id="unchanged06">
-  <option value="one">one</option>
-  <option value="two" selected>two</option>
-  <option value="three">three</option>
-</select>
-
-<h3>Multiple Select menu with changed selection</h3>
-<select id="modify07" multiple>
-  <option value="one">one</option>
-  <option value="two" selected>two</option>
-  <option value="three">three</option>
-</select>
-
-<h3>Select menu with unchanged selection</h3>
-<select id="unchanged07" multiple>
-  <option value="one">one</option>
-  <option value="two" selected>two</option>
-  <option value="three" selected>three</option>
-</select>
-
-<h3>checkbox with changed value</h3>
-<input type="checkbox" id="modify08">
-<input type="checkbox" id="modify09" checked>
-
-<h3>checkbox with unchanged value</h3>
-<input type="checkbox" id="unchanged08">
-<input type="checkbox" id="unchanged09" checked>
-
-<h3>radio with changed value</h3>
-<input type="radio" id="modify10"  name="group">Radio 1</input>
-<input type="radio" id="modify11"  name="group">Radio 2</input>
-<input type="radio" id="modify12" name="group" checked>Radio 3</input>
-
-<h3>radio with unchanged value</h3>
-<input type="radio" id="unchanged10"  name="group2">Radio 4</input>
-<input type="radio" id="unchanged11"  name="group2">Radio 5</input>
-<input type="radio" id="unchanged12" name="group2" checked>Radio 6</input>
-
-<h3>Changed field IDs</h3>
-<div id="changed"></div>
-<div id="inputFired"></div>
-<div id="changeFired"></div>
deleted file mode 100644
--- a/browser/components/sessionstore/test/browser_pageStyle.js
+++ /dev/null
@@ -1,89 +0,0 @@
-/* Any copyright is dedicated to the Public Domain.
- * http://creativecommons.org/publicdomain/zero/1.0/ */
-
-"use strict";
-
-const URL = getRootDirectory(gTestPath) + "browser_pageStyle_sample.html";
-const URL_NESTED = getRootDirectory(gTestPath) + "browser_pageStyle_sample_nested.html";
-
-/**
- * This test ensures that page style information is correctly persisted.
- */
-add_task(async function page_style() {
-  let tab = BrowserTestUtils.addTab(gBrowser, URL);
-  let browser = tab.linkedBrowser;
-  await promiseBrowserLoaded(browser);
-  let sheets = await getStyleSheets(browser);
-
-  // Enable all style sheets one by one.
-  for (let [title, /*disabled */] of sheets) {
-    await enableStyleSheetsForSet(browser, title);
-
-    let tab2 = gBrowser.duplicateTab(tab);
-    await promiseTabRestored(tab2);
-
-    let tab2Sheets = await getStyleSheets(tab2.linkedBrowser);
-    let enabled = tab2Sheets.filter(([, disabled]) => !disabled);
-
-    if (title.startsWith("fail_")) {
-      ok(!enabled.length, "didn't restore " + title);
-    } else {
-      is(enabled.length, 1, "restored one style sheet");
-      is(enabled[0][0], title, "restored correct sheet");
-    }
-
-    gBrowser.removeTab(tab2);
-  }
-
-  // Disable all styles and verify that this is correctly persisted.
-  await setAuthorStyleDisabled(browser, true);
-
-  let tab2 = gBrowser.duplicateTab(tab);
-  await promiseTabRestored(tab2);
-
-  let authorStyleDisabled = await getAuthorStyleDisabled(tab2.linkedBrowser);
-  ok(authorStyleDisabled, "disabled all stylesheets");
-
-  // Clean up.
-  gBrowser.removeTab(tab);
-  gBrowser.removeTab(tab2);
-});
-
-/**
- * This test ensures that page style notification from nested documents are
- * received and the page style is persisted correctly.
- */
-add_task(async function nested_page_style() {
-  let tab = BrowserTestUtils.addTab(gBrowser, URL_NESTED);
-  let browser = tab.linkedBrowser;
-  await promiseBrowserLoaded(browser);
-
-  await enableSubDocumentStyleSheetsForSet(browser, "alternate");
-  await promiseRemoveTab(tab);
-
-  let [{state: {pageStyle}}] = JSON.parse(ss.getClosedTabData(window));
-  let expected = JSON.stringify({children: [{pageStyle: "alternate"}]});
-  is(JSON.stringify(pageStyle), expected, "correct pageStyle persisted");
-});
-
-function getStyleSheets(browser) {
-  return sendMessage(browser, "ss-test:getStyleSheets");
-}
-
-function enableStyleSheetsForSet(browser, name) {
-  return sendMessage(browser, "ss-test:enableStyleSheetsForSet", name);
-}
-
-function enableSubDocumentStyleSheetsForSet(browser, name) {
-  return sendMessage(browser, "ss-test:enableSubDocumentStyleSheetsForSet", {
-    id: "iframe", set: name
-  });
-}
-
-function getAuthorStyleDisabled(browser) {
-  return sendMessage(browser, "ss-test:getAuthorStyleDisabled");
-}
-
-function setAuthorStyleDisabled(browser, val) {
-  return sendMessage(browser, "ss-test:setAuthorStyleDisabled", val)
-}
deleted file mode 100644
--- a/browser/components/sessionstore/test/browser_pageStyle_sample.html
+++ /dev/null
@@ -1,16 +0,0 @@
-<html>
-<head>
-  <meta charset="utf-8">
-  <title>pageStyle sample</title>
-
-  <link href="404.css" title="default" rel="stylesheet">
-  <link href="404.css" title="alternate" rel="alternate stylesheet">
-  <link href="404.css" title="altERnate" rel=" styLEsheet altERnate ">
-  <link href="404.css" title="media_empty" rel="alternate stylesheet" media="">
-  <link href="404.css" title="media_all" rel="alternate stylesheet" media="all">
-  <link href="404.css" title="media_ALL" rel="alternate stylesheet" media=" ALL ">
-  <link href="404.css" title="media_screen" rel="alternate stylesheet" media="screen">
-  <link href="404.css" title="media_print_screen" rel="alternate stylesheet" media="print,screen">
-</head>
-<body></body>
-</html>
deleted file mode 100644
--- a/browser/components/sessionstore/test/browser_pageStyle_sample_nested.html
+++ /dev/null
@@ -1,9 +0,0 @@
-<html>
-<head>
-  <meta charset="utf-8">
-  <title>pageStyle sample (nested)</title>
-</head>
-<body>
-  <iframe id="iframe" src="browser_pageStyle_sample.html"/>
-</body>
-</html>
--- a/browser/components/sessionstore/test/content-forms.js
+++ b/browser/components/sessionstore/test/content-forms.js
@@ -87,39 +87,39 @@ defineListener("setInputValue", function
 
 defineListener("getInputChecked", function(data) {
   return queryElement(data).checked;
 });
 
 defineListener("setInputChecked", function(data) {
   let input = queryElement(data);
   input.checked = data.checked;
-  dispatchUIEvent(input, "change");
+  dispatchUIEvent(input, "input");
 });
 
 defineListener("getSelectedIndex", function(data) {
   return queryElement(data).selectedIndex;
 });
 
 defineListener("setSelectedIndex", function(data) {
   let input = queryElement(data);
   input.selectedIndex = data.index;
-  dispatchUIEvent(input, "change");
+  dispatchUIEvent(input, "input");
 });
 
 defineListener("getMultipleSelected", function(data) {
   let input = queryElement(data);
   return Array.map(input.options, (opt, idx) => idx)
               .filter(idx => input.options[idx].selected);
 });
 
 defineListener("setMultipleSelected", function(data) {
   let input = queryElement(data);
   Array.forEach(input.options, (opt, idx) => opt.selected = data.indices.indexOf(idx) > -1);
-  dispatchUIEvent(input, "change");
+  dispatchUIEvent(input, "input");
 });
 
 defineListener("getFileNameArray", function(data) {
   return queryElement(data).mozGetFileNameArray();
 });
 
 defineListener("setFileNameArray", function(data) {
   let input = queryElement(data);
--- a/devtools/client/framework/test/browser_toolbox_races.js
+++ b/devtools/client/framework/test/browser_toolbox_races.js
@@ -6,16 +6,17 @@
 "use strict";
 
 // Toggling the toolbox three time can take more than 45s on slow test machine
 requestLongerTimeout(2);
 
 // Test toggling the toolbox quickly and see if there is any race breaking it.
 
 const URL = "data:text/html;charset=utf-8,Toggling devtools quickly";
+const {gDevToolsBrowser} = require("devtools/client/framework/devtools-browser");
 
 add_task(function* () {
   // Make sure this test starts with the selectedTool pref cleared. Previous
   // tests select various tools, and that sets this pref.
   Services.prefs.clearUserPref("devtools.toolbox.selectedTool");
 
   let tab = yield addTab(URL);
 
@@ -75,10 +76,15 @@ add_task(function* () {
   gDevTools.off("toolbox-ready", onReady);
   gDevTools.off("toolbox-destroy", onDestroy);
   gDevTools.off("toolbox-destroyed", onDestroyed);
 
   gBrowser.removeCurrentTab();
 });
 
 function toggle() {
-  EventUtils.synthesizeKey("VK_F12", {});
+  // When enabling the input event prioritization, we'll reserve some time to
+  // process input events in each frame. In that case, the synthesized input
+  // events may delay the normal events. Replace synthesized key events by
+  // toggleToolboxCommand to prevent the synthesized input events jam the
+  // content process and cause the test timeout.
+  gDevToolsBrowser.toggleToolboxCommand(window.gBrowser);
 }
--- a/devtools/client/inspector/test/browser_inspector_highlighter-eyedropper-events.js
+++ b/devtools/client/inspector/test/browser_inspector_highlighter-eyedropper-events.js
@@ -108,34 +108,34 @@ function* respondsToMoveEvents(helper, t
     } else {
       info(`Simulating ${type} event: ${desc}`);
     }
 
     if (type === "mouse") {
       yield mouse.move(x, y);
     } else if (type === "keyboard") {
       let options = shift ? {shiftKey: true} : {};
-      yield EventUtils.synthesizeKey(key, options);
+      yield EventUtils.synthesizeAndWaitKey(key, options);
     }
     yield checkPosition(expected, helper);
   }
 }
 
 function* checkPosition({x, y}, {getElementAttribute}) {
   let style = yield getElementAttribute("root", "style");
   is(style, `top:${y}px;left:${x}px;`,
      `The eyedropper is at the expected ${x} ${y} position`);
 }
 
 function* respondsToReturnAndEscape({isElementHidden, show}) {
   info("Simulating return to select the color and hide the eyedropper");
 
-  yield EventUtils.synthesizeKey("VK_RETURN", {});
+  yield EventUtils.synthesizeAndWaitKey("VK_RETURN", {});
   let hidden = yield isElementHidden("root");
   ok(hidden, "The eyedropper has been hidden");
 
   info("Showing the eyedropper again and simulating escape to hide it");
 
   yield show("html");
-  yield EventUtils.synthesizeKey("VK_ESCAPE", {});
+  yield EventUtils.synthesizeAndWaitKey("VK_ESCAPE", {});
   hidden = yield isElementHidden("root");
   ok(hidden, "The eyedropper has been hidden again");
 }
--- a/dom/base/nsDOMWindowUtils.cpp
+++ b/dom/base/nsDOMWindowUtils.cpp
@@ -169,16 +169,40 @@ private:
     return item;
   }
 
   static LinkedList<OldWindowSize> sList;
   nsWeakPtr mWindowRef;
   nsSize mSize;
 };
 
+namespace {
+
+class NativeInputRunnable final : public PrioritizableRunnable
+{
+  explicit NativeInputRunnable(already_AddRefed<nsIRunnable>&& aEvent);
+  ~NativeInputRunnable() {}
+public:
+  static already_AddRefed<nsIRunnable> Create(already_AddRefed<nsIRunnable>&& aEvent);
+};
+
+NativeInputRunnable::NativeInputRunnable(already_AddRefed<nsIRunnable>&& aEvent)
+  : PrioritizableRunnable(Move(aEvent), nsIRunnablePriority::PRIORITY_INPUT)
+{
+}
+
+/* static */ already_AddRefed<nsIRunnable>
+NativeInputRunnable::Create(already_AddRefed<nsIRunnable>&& aEvent)
+{
+  nsCOMPtr<nsIRunnable> event(new NativeInputRunnable(Move(aEvent)));
+  return event.forget();
+}
+
+} // unnamed namespace
+
 LinkedList<OldWindowSize> OldWindowSize::sList;
 
 NS_INTERFACE_MAP_BEGIN(nsDOMWindowUtils)
   NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIDOMWindowUtils)
   NS_INTERFACE_MAP_ENTRY(nsIDOMWindowUtils)
   NS_INTERFACE_MAP_ENTRY(nsISupportsWeakReference)
 NS_INTERFACE_MAP_END
 
@@ -1114,76 +1138,77 @@ nsDOMWindowUtils::SendNativeKeyEvent(int
                                      const nsAString& aUnmodifiedCharacters,
                                      nsIObserver* aObserver)
 {
   // get the widget to send the event to
   nsCOMPtr<nsIWidget> widget = GetWidget();
   if (!widget)
     return NS_ERROR_FAILURE;
 
-  NS_DispatchToMainThread(
+  NS_DispatchToMainThread(NativeInputRunnable::Create(
     NewRunnableMethod<int32_t,
                       int32_t,
                       uint32_t,
                       nsString,
                       nsString,
                       nsIObserver*>("nsIWidget::SynthesizeNativeKeyEvent",
                                     widget,
                                     &nsIWidget::SynthesizeNativeKeyEvent,
                                     aNativeKeyboardLayout,
                                     aNativeKeyCode,
                                     aModifiers,
                                     aCharacters,
                                     aUnmodifiedCharacters,
-                                    aObserver));
+                                    aObserver)));
   return NS_OK;
 }
 
 NS_IMETHODIMP
 nsDOMWindowUtils::SendNativeMouseEvent(int32_t aScreenX,
                                        int32_t aScreenY,
                                        int32_t aNativeMessage,
                                        int32_t aModifierFlags,
                                        nsIDOMElement* aElement,
                                        nsIObserver* aObserver)
 {
   // get the widget to send the event to
   nsCOMPtr<nsIWidget> widget = GetWidgetForElement(aElement);
   if (!widget)
     return NS_ERROR_FAILURE;
 
-  NS_DispatchToMainThread(
+  NS_DispatchToMainThread(NativeInputRunnable::Create(
     NewRunnableMethod<LayoutDeviceIntPoint, int32_t, int32_t, nsIObserver*>(
       "nsIWidget::SynthesizeNativeMouseEvent",
       widget,
       &nsIWidget::SynthesizeNativeMouseEvent,
       LayoutDeviceIntPoint(aScreenX, aScreenY),
       aNativeMessage,
       aModifierFlags,
-      aObserver));
+      aObserver)));
   return NS_OK;
 }
 
 NS_IMETHODIMP
 nsDOMWindowUtils::SendNativeMouseMove(int32_t aScreenX,
                                       int32_t aScreenY,
                                       nsIDOMElement* aElement,
                                       nsIObserver* aObserver)
 {
   // get the widget to send the event to
   nsCOMPtr<nsIWidget> widget = GetWidgetForElement(aElement);
   if (!widget)
     return NS_ERROR_FAILURE;
 
-  NS_DispatchToMainThread(NewRunnableMethod<LayoutDeviceIntPoint, nsIObserver*>(
-    "nsIWidget::SynthesizeNativeMouseMove",
-    widget,
-    &nsIWidget::SynthesizeNativeMouseMove,
-    LayoutDeviceIntPoint(aScreenX, aScreenY),
-    aObserver));
+  NS_DispatchToMainThread(NativeInputRunnable::Create(
+    NewRunnableMethod<LayoutDeviceIntPoint, nsIObserver*>(
+      "nsIWidget::SynthesizeNativeMouseMove",
+      widget,
+      &nsIWidget::SynthesizeNativeMouseMove,
+      LayoutDeviceIntPoint(aScreenX, aScreenY),
+      aObserver)));
   return NS_OK;
 }
 
 NS_IMETHODIMP
 nsDOMWindowUtils::SendNativeMouseScrollEvent(int32_t aScreenX,
                                              int32_t aScreenY,
                                              uint32_t aNativeMessage,
                                              double aDeltaX,
@@ -1195,35 +1220,36 @@ nsDOMWindowUtils::SendNativeMouseScrollE
                                              nsIObserver* aObserver)
 {
   // get the widget to send the event to
   nsCOMPtr<nsIWidget> widget = GetWidgetForElement(aElement);
   if (!widget) {
     return NS_ERROR_FAILURE;
   }
 
-  NS_DispatchToMainThread(NewRunnableMethod<mozilla::LayoutDeviceIntPoint,
-                                            uint32_t,
-                                            double,
-                                            double,
-                                            double,
-                                            uint32_t,
-                                            uint32_t,
-                                            nsIObserver*>(
-    "nsIWidget::SynthesizeNativeMouseScrollEvent",
-    widget,
-    &nsIWidget::SynthesizeNativeMouseScrollEvent,
-    LayoutDeviceIntPoint(aScreenX, aScreenY),
-    aNativeMessage,
-    aDeltaX,
-    aDeltaY,
-    aDeltaZ,
-    aModifierFlags,
-    aAdditionalFlags,
-    aObserver));
+  NS_DispatchToMainThread(NativeInputRunnable::Create(
+    NewRunnableMethod<mozilla::LayoutDeviceIntPoint,
+                      uint32_t,
+                      double,
+                      double,
+                      double,
+                      uint32_t,
+                      uint32_t,
+                      nsIObserver*>(
+      "nsIWidget::SynthesizeNativeMouseScrollEvent",
+      widget,
+      &nsIWidget::SynthesizeNativeMouseScrollEvent,
+      LayoutDeviceIntPoint(aScreenX, aScreenY),
+      aNativeMessage,
+      aDeltaX,
+      aDeltaY,
+      aDeltaZ,
+      aModifierFlags,
+      aAdditionalFlags,
+      aObserver)));
   return NS_OK;
 }
 
 NS_IMETHODIMP
 nsDOMWindowUtils::SendNativeTouchPoint(uint32_t aPointerId,
                                        uint32_t aTouchState,
                                        int32_t aScreenX,
                                        int32_t aScreenY,
@@ -1235,53 +1261,53 @@ nsDOMWindowUtils::SendNativeTouchPoint(u
   if (!widget) {
     return NS_ERROR_FAILURE;
   }
 
   if (aPressure < 0 || aPressure > 1 || aOrientation > 359) {
     return NS_ERROR_INVALID_ARG;
   }
 
-  NS_DispatchToMainThread(
+  NS_DispatchToMainThread(NativeInputRunnable::Create(
     NewRunnableMethod<uint32_t,
                       nsIWidget::TouchPointerState,
                       LayoutDeviceIntPoint,
                       double,
                       uint32_t,
                       nsIObserver*>("nsIWidget::SynthesizeNativeTouchPoint",
                                     widget,
                                     &nsIWidget::SynthesizeNativeTouchPoint,
                                     aPointerId,
                                     (nsIWidget::TouchPointerState)aTouchState,
                                     LayoutDeviceIntPoint(aScreenX, aScreenY),
                                     aPressure,
                                     aOrientation,
-                                    aObserver));
+                                    aObserver)));
   return NS_OK;
 }
 
 NS_IMETHODIMP
 nsDOMWindowUtils::SendNativeTouchTap(int32_t aScreenX,
                                      int32_t aScreenY,
                                      bool aLongTap,
                                      nsIObserver* aObserver)
 {
   nsCOMPtr<nsIWidget> widget = GetWidget();
   if (!widget) {
     return NS_ERROR_FAILURE;
   }
 
-  NS_DispatchToMainThread(
+  NS_DispatchToMainThread(NativeInputRunnable::Create(
     NewRunnableMethod<LayoutDeviceIntPoint, bool, nsIObserver*>(
       "nsIWidget::SynthesizeNativeTouchTap",
       widget,
       &nsIWidget::SynthesizeNativeTouchTap,
       LayoutDeviceIntPoint(aScreenX, aScreenY),
       aLongTap,
-      aObserver));
+      aObserver)));
   return NS_OK;
 }
 
 NS_IMETHODIMP
 nsDOMWindowUtils::SuppressAnimation(bool aSuppress)
 {
   nsIWidget* widget = GetWidget();
   if (widget) {
@@ -1293,21 +1319,21 @@ nsDOMWindowUtils::SuppressAnimation(bool
 NS_IMETHODIMP
 nsDOMWindowUtils::ClearNativeTouchSequence(nsIObserver* aObserver)
 {
   nsCOMPtr<nsIWidget> widget = GetWidget();
   if (!widget) {
     return NS_ERROR_FAILURE;
   }
 
-  NS_DispatchToMainThread(
+  NS_DispatchToMainThread(NativeInputRunnable::Create(
     NewRunnableMethod<nsIObserver*>("nsIWidget::ClearNativeTouchSequence",
                                     widget,
                                     &nsIWidget::ClearNativeTouchSequence,
-                                    aObserver));
+                                    aObserver)));
   return NS_OK;
 }
 
 NS_IMETHODIMP
 nsDOMWindowUtils::ActivateNativeMenuItemAt(const nsAString& indexString)
 {
   // get the widget to send the event to
   nsCOMPtr<nsIWidget> widget = GetWidget();
--- a/dom/events/EventStateManager.cpp
+++ b/dom/events/EventStateManager.cpp
@@ -2824,23 +2824,42 @@ NodeAllowsClickThrough(nsINode* aNode)
     aNode = nsContentUtils::GetCrossDocParentNode(aNode);
   }
   return true;
 }
 #endif
 
 void
 EventStateManager::PostHandleKeyboardEvent(WidgetKeyboardEvent* aKeyboardEvent,
+                                           nsIFrame* aTargetFrame,
                                            nsEventStatus& aStatus)
 {
   if (aStatus == nsEventStatus_eConsumeNoDefault) {
     return;
   }
 
   if (!aKeyboardEvent->HasBeenPostedToRemoteProcess()) {
+    if (aKeyboardEvent->IsWaitingReplyFromRemoteProcess()) {
+      RefPtr<TabParent> remote = aTargetFrame ?
+        TabParent::GetFrom(aTargetFrame->GetContent()) : nullptr;
+      if (remote && !remote->IsReadyToHandleInputEvents()) {
+        // We need to dispatch the event to the browser element again if we were
+        // waiting for the key reply but the event wasn't sent to the content
+        // process due to the remote browser wasn't ready.
+        WidgetKeyboardEvent keyEvent(*aKeyboardEvent);
+        aKeyboardEvent->MarkAsHandledInRemoteProcess();
+        EventDispatcher::Dispatch(remote->GetOwnerElement(), mPresContext,
+                                  &keyEvent);
+        if (keyEvent.DefaultPrevented()) {
+          aKeyboardEvent->PreventDefault(!keyEvent.DefaultPreventedByContent());
+          aStatus = nsEventStatus_eConsumeNoDefault;
+          return;
+        }
+      }
+    }
     // The widget expects a reply for every keyboard event. If the event wasn't
     // dispatched to a content process (non-e10s or no content process
     // running), we need to short-circuit here. Otherwise, we need to wait for
     // the content process to handle the event.
     aKeyboardEvent->mWidget->PostHandleKeyEvent(aKeyboardEvent);
     if (aKeyboardEvent->DefaultPrevented()) {
       aStatus = nsEventStatus_eConsumeNoDefault;
       return;
@@ -3482,17 +3501,17 @@ EventStateManager::PostHandleEvent(nsPre
     break;
 
   case eKeyUp:
     break;
 
   case eKeyPress:
     {
       WidgetKeyboardEvent* keyEvent = aEvent->AsKeyboardEvent();
-      PostHandleKeyboardEvent(keyEvent, *aStatus);
+      PostHandleKeyboardEvent(keyEvent, mCurrentTarget, *aStatus);
     }
     break;
 
   case eMouseEnterIntoWidget:
     if (mCurrentTarget) {
       nsCOMPtr<nsIContent> targetContent;
       mCurrentTarget->GetContentForEvent(aEvent, getter_AddRefs(targetContent));
       SetContentState(targetContent, NS_EVENT_STATE_HOVER);
--- a/dom/events/EventStateManager.h
+++ b/dom/events/EventStateManager.h
@@ -104,17 +104,17 @@ public:
    * DOM and frame processing.
    */
   nsresult PostHandleEvent(nsPresContext* aPresContext,
                            WidgetEvent* aEvent,
                            nsIFrame* aTargetFrame,
                            nsEventStatus* aStatus);
 
   void PostHandleKeyboardEvent(WidgetKeyboardEvent* aKeyboardEvent,
-                               nsEventStatus& aStatus);
+                               nsIFrame* aTargetFrame, nsEventStatus& aStatus);
 
   /**
    * DispatchLegacyMouseScrollEvents() dispatches eLegacyMouseLineOrPageScroll
    * event and eLegacyMousePixelScroll event for compatibility with old Gecko.
    */
   void DispatchLegacyMouseScrollEvents(nsIFrame* aTargetFrame,
                                        WidgetWheelEvent* aEvent,
                                        nsEventStatus* aStatus);
--- a/dom/events/test/window_wheel_default_action.html
+++ b/dom/events/test/window_wheel_default_action.html
@@ -1136,37 +1136,61 @@ function doTestZoom(aSettings, aCallback
     event.ctrlKey  = true;
 
     // NOTE: Zooming might change scrollTop and scrollLeft by rounding fraction.
     //       This test assume that zoom happens synchronously and scrolling
     //       happens asynchronously.
     var scrollTop = gScrollableElement.scrollTop;
     var scrollLeft = gScrollableElement.scrollLeft;
 
+    fullZoomChangePromise = new Promise(resolve => {
+      if (currentTest.expected & (kNegative | kPositive)) {
+        SpecialPowers.addChromeEventListener("FullZoomChange", function onFullZoomChange() {
+          if (SpecialPowers.getFullZoom(window) != 1) {
+            SpecialPowers.removeChromeEventListener("FullZoomChange", onFullZoomChange)
+            setTimeout(() => resolve(), 0);
+          }
+        });
+      } else {
+        resolve();
+      }
+    });
+
     sendWheelAndWait(10, 10, event, function () {
       is(gScrollableElement.scrollTop, scrollTop, description + "scrolled vertical");
       is(gScrollableElement.scrollLeft, scrollLeft, description + "scrolled horizontal");
-      if (!(currentTest.expected & (kNegative | kPositive))) {
-        is(SpecialPowers.getFullZoom(window), 1.0, description + "zoomed");
-      } else {
-        var isReverted = (currentTest.expected & kUseX) ? isXReverted :
-                         (currentTest.expected & kUseY) ? isYReverted : false;
-        if ((!isReverted && (currentTest.expected & kNegative)) ||
-            (isReverted && (currentTest.expected & kPositive))) {
-          ok(SpecialPowers.getFullZoom(window) > 1.0,
-             description + "not zoomed in, got " + SpecialPowers.getFullZoom(window));
+
+      fullZoomChangePromise.then(() => {
+        // When input event prioritization is enabled, the wheel event may be
+        // dispatched to the content process before the message 'FullZoom' to
+        // zoom in/out. Waiting for the event 'FullZoomChange' and then check
+        // the result.
+        if (!(currentTest.expected & (kNegative | kPositive))) {
+          is(SpecialPowers.getFullZoom(window), 1.0, description + "zoomed");
         } else {
-          ok(SpecialPowers.getFullZoom(window) < 1.0,
-             description + "not zoomed out, got " + SpecialPowers.getFullZoom(window));
+          var isReverted = (currentTest.expected & kUseX) ? isXReverted :
+                           (currentTest.expected & kUseY) ? isYReverted : false;
+          if ((!isReverted && (currentTest.expected & kNegative)) ||
+              (isReverted && (currentTest.expected & kPositive))) {
+            ok(SpecialPowers.getFullZoom(window) > 1.0,
+               description + "not zoomed in, got " + SpecialPowers.getFullZoom(window));
+          } else {
+            ok(SpecialPowers.getFullZoom(window) < 1.0,
+               description + "not zoomed out, got " + SpecialPowers.getFullZoom(window));
+          }
         }
-      }
-
-      synthesizeKey("0", { accelKey: true });
-      onZoomReset(function () {
-        hitEventLoop(doNextTest, 20);
+        if (SpecialPowers.getFullZoom(window) != 1) {
+          // Only synthesizes key event to reset zoom when necessary to avoid
+          // triggering the next test before the key event is handled. In that
+          // case, the key event may break the next test.
+          synthesizeKey("0", { accelKey: true });
+        }
+        onZoomReset(function () {
+          hitEventLoop(doNextTest, 20);
+        });
       });
     });
   }
   doNextTest();
 }
 
 function doTestZoomedScroll(aCallback)
 {
--- a/dom/html/test/test_bug1261673.html
+++ b/dom/html/test/test_bug1261673.html
@@ -3,16 +3,17 @@
 <!--
 https://bugzilla.mozilla.org/show_bug.cgi?id=1261673
 -->
 <head>
   <meta charset="utf-8">
   <title>Test for Bug 1261673</title>
   <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
   <script type="text/javascript" src="/tests/SimpleTest/EventUtils.js"></script>
+  <script type="text/javascript" src="/tests/SimpleTest/paint_listener.js"></script>
   <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
 </head>
 <body>
 <a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1261673">Mozilla Bug 1261673</a>
 <p id="display"></p>
 <div id="content" style="display: none">
 </div>
 <input id="test_number" type="number" value=5>
@@ -49,29 +50,23 @@ function runTests() {
     ++numberChange;
   });
 
   function runNext() {
     let p = params[testIdx];
     (p["focus"]) ? input.focus() : input.blur();
     expectChange = p["valueChanged"] == 0 ? expectChange : expectChange + 1;
     result += parseInt(p["valueChanged"]);
-    synthesizeWheel(input, 1, 1, { deltaY: p["deltaY"], deltaMode: p["deltaMode"] });
-    window.postMessage("finished", "http://mochi.test:8888");
-    testIdx++;
-  }
-
-  window.addEventListener("message", event => {
-    if (event.data == "finished") {
+    sendWheelAndPaint(input, 1, 1, { deltaY: p["deltaY"], deltaMode: p["deltaMode"] }, () => {
       ok(input.value == result,
         "Handle wheel in number input test-" + testIdx + " expect " + result +
         " get " + input.value);
       ok(numberChange == expectChange,
          "UA should fire change event when input's value changed, expect " + expectChange + " get " + numberChange);
-      (testIdx >= params.length) ? SimpleTest.finish() : runNext();
-    }
-  });
+      (++testIdx >= params.length) ? SimpleTest.finish() : runNext();
+    });
+  }
   runNext();
 }
 
 </script>
 </body>
 </html>
--- a/dom/ipc/ContentBridgeParent.cpp
+++ b/dom/ipc/ContentBridgeParent.cpp
@@ -166,16 +166,34 @@ ContentBridgeParent::AllocPBrowserParent
 }
 
 bool
 ContentBridgeParent::DeallocPBrowserParent(PBrowserParent* aParent)
 {
   return nsIContentParent::DeallocPBrowserParent(aParent);
 }
 
+mozilla::ipc::IPCResult
+ContentBridgeParent::RecvPBrowserConstructor(PBrowserParent* actor,
+                                             const TabId& tabId,
+                                             const TabId& sameTabGroupAs,
+                                             const IPCTabContext& context,
+                                             const uint32_t& chromeFlags,
+                                             const ContentParentId& cpId,
+                                             const bool& isForBrowser)
+{
+  return nsIContentParent::RecvPBrowserConstructor(actor,
+                                                   tabId,
+                                                   sameTabGroupAs,
+                                                   context,
+                                                   chromeFlags,
+                                                   cpId,
+                                                   isForBrowser);
+}
+
 void
 ContentBridgeParent::NotifyTabDestroyed()
 {
   int32_t numLiveTabs = ManagedPBrowserParent().Count();
   if (numLiveTabs == 1) {
     MessageLoop::current()->PostTask(NewRunnableMethod(
       "dom::ContentBridgeParent::Close", this, &ContentBridgeParent::Close));
   }
--- a/dom/ipc/ContentBridgeParent.h
+++ b/dom/ipc/ContentBridgeParent.h
@@ -133,16 +133,25 @@ protected:
                       const TabId& aSameTabGroupAs,
                       const IPCTabContext &aContext,
                       const uint32_t& aChromeFlags,
                       const ContentParentId& aCpID,
                       const bool& aIsForBrowser) override;
 
   virtual bool DeallocPBrowserParent(PBrowserParent*) override;
 
+  virtual mozilla::ipc::IPCResult
+  RecvPBrowserConstructor(PBrowserParent* actor,
+                          const TabId& tabId,
+                          const TabId& sameTabGroupAs,
+                          const IPCTabContext& context,
+                          const uint32_t& chromeFlags,
+                          const ContentParentId& cpId,
+                          const bool& isForBrowser) override;
+
   virtual PIPCBlobInputStreamParent*
   SendPIPCBlobInputStreamConstructor(PIPCBlobInputStreamParent* aActor,
                                      const nsID& aID,
                                      const uint64_t& aSize) override;
 
   virtual PIPCBlobInputStreamParent*
   AllocPIPCBlobInputStreamParent(const nsID& aID,
                                  const uint64_t& aSize) override;
--- a/dom/ipc/ContentChild.cpp
+++ b/dom/ipc/ContentChild.cpp
@@ -1171,16 +1171,19 @@ ContentChild::InitXPCOM(const XPCOMInitD
   nsLayoutStylesheetCache::SetUserContentCSSURL(ucsURL);
 
   // This will register cross-process observer.
   mozilla::dom::time::InitializeDateCacheCleaner();
 
   GfxInfoBase::SetFeatureStatus(aXPCOMInit.gfxFeatureStatus());
 
   DataStorage::SetCachedStorageEntries(aXPCOMInit.dataStorage());
+
+  // Enable input event prioritization.
+  nsThreadManager::get().EnableMainThreadEventPrioritization();
 }
 
 mozilla::ipc::IPCResult
 ContentChild::RecvRequestMemoryReport(const uint32_t& aGeneration,
                                       const bool& aAnonymize,
                                       const bool& aMinimizeMemoryUsage,
                                       const MaybeFileDesc& aDMDFile)
 {
--- a/dom/ipc/ContentParent.cpp
+++ b/dom/ipc/ContentParent.cpp
@@ -2896,16 +2896,34 @@ ContentParent::AllocPBrowserParent(const
 }
 
 bool
 ContentParent::DeallocPBrowserParent(PBrowserParent* frame)
 {
   return nsIContentParent::DeallocPBrowserParent(frame);
 }
 
+mozilla::ipc::IPCResult
+ContentParent::RecvPBrowserConstructor(PBrowserParent* actor,
+                                       const TabId& tabId,
+                                       const TabId& sameTabGroupAs,
+                                       const IPCTabContext& context,
+                                       const uint32_t& chromeFlags,
+                                       const ContentParentId& cpId,
+                                       const bool& isForBrowser)
+{
+  return nsIContentParent::RecvPBrowserConstructor(actor,
+                                                   tabId,
+                                                   sameTabGroupAs,
+                                                   context,
+                                                   chromeFlags,
+                                                   cpId,
+                                                   isForBrowser);
+}
+
 PIPCBlobInputStreamParent*
 ContentParent::AllocPIPCBlobInputStreamParent(const nsID& aID,
                                               const uint64_t& aSize)
 {
   return nsIContentParent::AllocPIPCBlobInputStreamParent(aID, aSize);
 }
 
 bool
--- a/dom/ipc/ContentParent.h
+++ b/dom/ipc/ContentParent.h
@@ -843,16 +843,25 @@ private:
                                               const TabId& aSameTabGroupAs,
                                               const IPCTabContext& aContext,
                                               const uint32_t& aChromeFlags,
                                               const ContentParentId& aCpId,
                                               const bool& aIsForBrowser) override;
 
   virtual bool DeallocPBrowserParent(PBrowserParent* frame) override;
 
+  virtual mozilla::ipc::IPCResult
+  RecvPBrowserConstructor(PBrowserParent* actor,
+                          const TabId& tabId,
+                          const TabId& sameTabGroupAs,
+                          const IPCTabContext& context,
+                          const uint32_t& chromeFlags,
+                          const ContentParentId& cpId,
+                          const bool& isForBrowser) override;
+
   virtual PIPCBlobInputStreamParent*
   SendPIPCBlobInputStreamConstructor(PIPCBlobInputStreamParent* aActor,
                                      const nsID& aID,
                                      const uint64_t& aSize) override;
 
   virtual PIPCBlobInputStreamParent*
   AllocPIPCBlobInputStreamParent(const nsID& aID,
                                  const uint64_t& aSize) override;
--- a/dom/ipc/PBrowser.ipdl
+++ b/dom/ipc/PBrowser.ipdl
@@ -538,16 +538,22 @@ parent:
      * compositing.  This is sent when all pending changes have been
      * sent to the compositor and are ready to be shown on the next composite.
      * @see PCompositor
      * @see RequestNotifyAfterRemotePaint
      */
     async RemotePaintIsReady();
 
     /**
+     * Child informs the parent that the content is ready to handle input
+     * events. This is sent when the TabChild is created.
+     */
+    async RemoteIsReadyToHandleInputEvents();
+
+    /**
      * Child informs the parent that the layer tree is already available.
      */
     async ForcePaintNoOp(uint64_t aLayerObserverEpoch);
 
     /**
      * Sent by the child to the parent to inform it that an update to the
      * dimensions has been requested, likely through win.moveTo or resizeTo
      */
@@ -640,39 +646,49 @@ child:
                      int32_t aClickCount,
                      int32_t aModifiers,
                      bool aIgnoreRootScrollFrame);
 
     /**
      * When two consecutive mouse move events would be added to the message queue,
      * they are 'compressed' by dumping the oldest one.
      */
-    async RealMouseMoveEvent(WidgetMouseEvent event, ScrollableLayerGuid aGuid, uint64_t aInputBlockId) compress;
+    prio(input) async RealMouseMoveEvent(WidgetMouseEvent event,
+                                         ScrollableLayerGuid aGuid,
+                                         uint64_t aInputBlockId) compress;
     /**
      * Mouse move events with |reason == eSynthesized| are sent via a separate
      * message because they do not generate DOM 'mousemove' events, and the
      * 'compress' attribute on RealMouseMoveEvent() could result in a
      * |reason == eReal| event being dropped in favour of an |eSynthesized|
      * event, and thus a DOM 'mousemove' event to be lost.
      */
-    async SynthMouseMoveEvent(WidgetMouseEvent event, ScrollableLayerGuid aGuid, uint64_t aInputBlockId);
-    async RealMouseButtonEvent(WidgetMouseEvent event, ScrollableLayerGuid aGuid, uint64_t aInputBlockId);
-    async RealKeyEvent(WidgetKeyboardEvent event);
-    async MouseWheelEvent(WidgetWheelEvent event, ScrollableLayerGuid aGuid, uint64_t aInputBlockId);
-    async RealTouchEvent(WidgetTouchEvent aEvent,
-                         ScrollableLayerGuid aGuid,
-                         uint64_t aInputBlockId,
-                         nsEventStatus aApzResponse);
-    async HandleTap(TapType aType, LayoutDevicePoint point, Modifiers aModifiers,
-                    ScrollableLayerGuid aGuid, uint64_t aInputBlockId);
-    async RealTouchMoveEvent(WidgetTouchEvent aEvent,
-                             ScrollableLayerGuid aGuid,
-                             uint64_t aInputBlockId,
-                             nsEventStatus aApzResponse);
-    async RealDragEvent(WidgetDragEvent aEvent, uint32_t aDragAction, uint32_t aDropEffect);
+    prio(input) async SynthMouseMoveEvent(WidgetMouseEvent event,
+                                          ScrollableLayerGuid aGuid,
+                                          uint64_t aInputBlockId);
+    prio(input) async RealMouseButtonEvent(WidgetMouseEvent event,
+                                           ScrollableLayerGuid aGuid,
+                                           uint64_t aInputBlockId);
+    prio(input) async RealKeyEvent(WidgetKeyboardEvent event);
+    prio(input) async MouseWheelEvent(WidgetWheelEvent event,
+                                      ScrollableLayerGuid aGuid,
+                                      uint64_t aInputBlockId);
+    prio(input) async RealTouchEvent(WidgetTouchEvent aEvent,
+                                     ScrollableLayerGuid aGuid,
+                                     uint64_t aInputBlockId,
+                                     nsEventStatus aApzResponse);
+    prio(input) async HandleTap(TapType aType, LayoutDevicePoint point,
+                                Modifiers aModifiers, ScrollableLayerGuid aGuid,
+                                uint64_t aInputBlockId);
+    prio(input) async RealTouchMoveEvent(WidgetTouchEvent aEvent,
+                                         ScrollableLayerGuid aGuid,
+                                         uint64_t aInputBlockId,
+                                         nsEventStatus aApzResponse);
+    prio(input) async RealDragEvent(WidgetDragEvent aEvent,
+                                    uint32_t aDragAction, uint32_t aDropEffect);
     async PluginEvent(WidgetPluginEvent aEvent);
 
     /**
      * @see nsIDOMWindowUtils sendKeyEvent.
      */
     async KeyEvent(nsString aType,
                    int32_t aKeyCode,
                    int32_t aCharCode,
--- a/dom/ipc/TabChild.cpp
+++ b/dom/ipc/TabChild.cpp
@@ -85,16 +85,17 @@
 #include "nsIWebBrowserFocus.h"
 #include "nsIWebBrowserSetup.h"
 #include "nsIWebProgress.h"
 #include "nsIXULRuntime.h"
 #include "nsPIDOMWindow.h"
 #include "nsPIWindowRoot.h"
 #include "nsLayoutUtils.h"
 #include "nsPrintfCString.h"
+#include "nsThreadManager.h"
 #include "nsThreadUtils.h"
 #include "nsViewManager.h"
 #include "nsWeakReference.h"
 #include "nsWindowWatcher.h"
 #include "PermissionMessageUtils.h"
 #include "PuppetWidget.h"
 #include "StructuredCloneData.h"
 #include "nsViewportInfo.h"
@@ -316,17 +317,29 @@ private:
         MOZ_ASSERT(!mTabChild);
     }
 
     NS_IMETHOD
     Run() override
     {
         MOZ_ASSERT(NS_IsMainThread());
         MOZ_ASSERT(mTabChild);
-
+        // When enabling input event prioritization, we reserve limited time
+        // to process input events. We may handle the rest in the next frame
+        // when running out of time of the current frame. In that case, input
+        // events may be dispatched after ActorDestroy. Delay
+        // DelayedDeleteRunnable to avoid it to happen.
+        nsThread* thread = nsThreadManager::get().GetCurrentThread();
+        MOZ_ASSERT(thread);
+        bool eventPrioritizationEnabled = false;
+        thread->IsEventPrioritizationEnabled(&eventPrioritizationEnabled);
+        if (eventPrioritizationEnabled && thread->HasPendingInputEvents()) {
+          MOZ_ALWAYS_SUCCEEDS(NS_DispatchToCurrentThread(this));
+          return NS_OK;
+        }
         // Check in case ActorDestroy was called after RecvDestroy message.
         if (mTabChild->IPCOpen()) {
             Unused << PBrowserChild::Send__delete__(mTabChild);
         }
 
         mTabChild = nullptr;
         return NS_OK;
     }
--- a/dom/ipc/TabParent.cpp
+++ b/dom/ipc/TabParent.cpp
@@ -167,16 +167,17 @@ TabParent::TabParent(nsIContentParent* a
   , mHasContentOpener(false)
 #ifdef DEBUG
   , mActiveSupressDisplayportCount(0)
 #endif
   , mLayerTreeEpoch(0)
   , mPreserveLayers(false)
   , mHasPresented(false)
   , mHasBeforeUnload(false)
+  , mIsReadyToHandleInputEvents(false)
 {
   MOZ_ASSERT(aManager);
 }
 
 TabParent::~TabParent()
 {
 }
 
@@ -1077,27 +1078,27 @@ TabParent::SendMouseEvent(const nsAStrin
 
 void
 TabParent::SendKeyEvent(const nsAString& aType,
                         int32_t aKeyCode,
                         int32_t aCharCode,
                         int32_t aModifiers,
                         bool aPreventDefault)
 {
-  if (mIsDestroyed) {
+  if (mIsDestroyed || !mIsReadyToHandleInputEvents) {
     return;
   }
   Unused << PBrowserParent::SendKeyEvent(nsString(aType), aKeyCode, aCharCode,
                                          aModifiers, aPreventDefault);
 }
 
 void
 TabParent::SendRealMouseEvent(WidgetMouseEvent& aEvent)
 {
-  if (mIsDestroyed) {
+  if (mIsDestroyed || !mIsReadyToHandleInputEvents) {
     return;
   }
   aEvent.mRefPoint += GetChildProcessOffset();
 
   nsCOMPtr<nsIWidget> widget = GetWidget();
   if (widget) {
     // When we mouseenter the tab, the tab's cursor should
     // become the current cursor.  When we mouseexit, we stop.
@@ -1147,17 +1148,17 @@ TabParent::GetLayoutDeviceToCSSScale()
     ? (float)ctx->AppUnitsPerDevPixel() / nsPresContext::AppUnitsPerCSSPixel()
     : 0.0f);
 }
 
 void
 TabParent::SendRealDragEvent(WidgetDragEvent& aEvent, uint32_t aDragAction,
                              uint32_t aDropEffect)
 {
-  if (mIsDestroyed) {
+  if (mIsDestroyed || !mIsReadyToHandleInputEvents) {
     return;
   }
   aEvent.mRefPoint += GetChildProcessOffset();
   DebugOnly<bool> ret =
     PBrowserParent::SendRealDragEvent(aEvent, aDragAction, aDropEffect);
   NS_WARNING_ASSERTION(ret, "PBrowserParent::SendRealDragEvent() failed");
   MOZ_ASSERT(!ret || aEvent.HasBeenPostedToRemoteProcess());
 }
@@ -1166,17 +1167,17 @@ LayoutDevicePoint
 TabParent::AdjustTapToChildWidget(const LayoutDevicePoint& aPoint)
 {
   return aPoint + LayoutDevicePoint(GetChildProcessOffset());
 }
 
 void
 TabParent::SendMouseWheelEvent(WidgetWheelEvent& aEvent)
 {
-  if (mIsDestroyed) {
+  if (mIsDestroyed || !mIsReadyToHandleInputEvents) {
     return;
   }
 
   ScrollableLayerGuid guid;
   uint64_t blockId;
   ApzAwareEventRoutingToChild(&guid, &blockId, nullptr);
   aEvent.mRefPoint += GetChildProcessOffset();
   DebugOnly<bool> ret =
@@ -1445,17 +1446,17 @@ TabParent::RecvClearNativeTouchSequence(
     widget->ClearNativeTouchSequence(responder.GetObserver());
   }
   return IPC_OK();
 }
 
 void
 TabParent::SendRealKeyEvent(WidgetKeyboardEvent& aEvent)
 {
-  if (mIsDestroyed) {
+  if (mIsDestroyed || !mIsReadyToHandleInputEvents) {
     return;
   }
   aEvent.mRefPoint += GetChildProcessOffset();
 
   if (aEvent.mMessage == eKeyPress) {
     // XXX Should we do this only when input context indicates an editor having
     //     focus and the key event won't cause inputting text?
     aEvent.InitAllEditCommands();
@@ -1466,17 +1467,17 @@ TabParent::SendRealKeyEvent(WidgetKeyboa
   DebugOnly<bool> ret = PBrowserParent::SendRealKeyEvent(aEvent);
   NS_WARNING_ASSERTION(ret, "PBrowserParent::SendRealKeyEvent() failed");
   MOZ_ASSERT(!ret || aEvent.HasBeenPostedToRemoteProcess());
 }
 
 void
 TabParent::SendRealTouchEvent(WidgetTouchEvent& aEvent)
 {
-  if (mIsDestroyed) {
+  if (mIsDestroyed || !mIsReadyToHandleInputEvents) {
     return;
   }
 
   // PresShell::HandleEventInternal adds touches on touch end/cancel.  This
   // confuses remote content and the panning and zooming logic into thinking
   // that the added touches are part of the touchend/cancel, when actually
   // they're not.
   if (aEvent.mMessage == eTouchEnd || aEvent.mMessage == eTouchCancel) {
@@ -1527,17 +1528,17 @@ TabParent::SendPluginEvent(WidgetPluginE
 
 bool
 TabParent::SendHandleTap(TapType aType,
                          const LayoutDevicePoint& aPoint,
                          Modifiers aModifiers,
                          const ScrollableLayerGuid& aGuid,
                          uint64_t aInputBlockId)
 {
-  if (mIsDestroyed) {
+  if (mIsDestroyed || !mIsReadyToHandleInputEvents) {
     return false;
   }
   if ((aType == TapType::eSingleTap || aType == TapType::eSecondTap) &&
       GetRenderFrame()) {
     GetRenderFrame()->TakeFocusForClickFromTap();
   }
   LayoutDeviceIntPoint offset = GetChildProcessOffset();
   return PBrowserParent::SendHandleTap(aType, aPoint + offset, aModifiers,
@@ -2910,16 +2911,28 @@ TabParent::RecvRemotePaintIsReady()
   event->InitEvent(NS_LITERAL_STRING("MozAfterRemotePaint"), false, false);
   event->SetTrusted(true);
   event->WidgetEventPtr()->mFlags.mOnlyChromeDispatch = true;
   bool dummy;
   mFrameElement->DispatchEvent(event, &dummy);
   return IPC_OK();
 }
 
+mozilla::ipc::IPCResult
+TabParent::RecvRemoteIsReadyToHandleInputEvents()
+{
+  // When enabling input event prioritization, input events may preempt other
+  // normal priority IPC messages. To prevent the input events preempt
+  // PBrowserConstructor, we use an IPC 'RemoteIsReadyToHandleInputEvents' to
+  // notify the parent that TabChild is created and ready to handle input
+  // events.
+  SetReadyToHandleInputEvents();
+  return IPC_OK();
+}
+
 mozilla::plugins::PPluginWidgetParent*
 TabParent::AllocPPluginWidgetParent()
 {
 #ifdef XP_WIN
   return new mozilla::plugins::PluginWidgetParent();
 #else
   MOZ_ASSERT_UNREACHABLE();
   return nullptr;
--- a/dom/ipc/TabParent.h
+++ b/dom/ipc/TabParent.h
@@ -599,16 +599,19 @@ public:
                           uint64_t* aLayersId);
 
   mozilla::ipc::IPCResult RecvEnsureLayersConnected(CompositorOptions* aCompositorOptions) override;
 
   // LiveResizeListener implementation
   void LiveResizeStarted() override;
   void LiveResizeStopped() override;
 
+  void SetReadyToHandleInputEvents() { mIsReadyToHandleInputEvents = true; }
+  bool IsReadyToHandleInputEvents() { return mIsReadyToHandleInputEvents; }
+
 protected:
   bool ReceiveMessage(const nsString& aMessage,
                       bool aSync,
                       ipc::StructuredCloneData* aData,
                       mozilla::jsipc::CpowHolder* aCpows,
                       nsIPrincipal* aPrincipal,
                       nsTArray<ipc::StructuredCloneData>* aJSONRetVal = nullptr);
 
@@ -624,16 +627,18 @@ protected:
   nsCOMPtr<nsIBrowserDOMWindow> mBrowserDOMWindow;
 
   virtual PRenderFrameParent* AllocPRenderFrameParent() override;
 
   virtual bool DeallocPRenderFrameParent(PRenderFrameParent* aFrame) override;
 
   virtual mozilla::ipc::IPCResult RecvRemotePaintIsReady() override;
 
+  virtual mozilla::ipc::IPCResult RecvRemoteIsReadyToHandleInputEvents() override;
+
   virtual mozilla::ipc::IPCResult RecvForcePaintNoOp(const uint64_t& aLayerObserverEpoch) override;
 
   virtual mozilla::ipc::IPCResult RecvSetDimensions(const uint32_t& aFlags,
                                                     const int32_t& aX, const int32_t& aY,
                                                     const int32_t& aCx, const int32_t& aCy) override;
 
   virtual mozilla::ipc::IPCResult RecvGetTabCount(uint32_t* aValue) override;
 
@@ -774,16 +779,19 @@ private:
   // True if this TabParent has had its layer tree sent to the compositor
   // at least once.
   bool mHasPresented;
 
   // True if at least one window hosted in the TabChild has added a
   // beforeunload event listener.
   bool mHasBeforeUnload;
 
+  // True when the remote browser is created and ready to handle input events.
+  bool mIsReadyToHandleInputEvents;
+
 public:
   static TabParent* GetTabParentFromLayersId(uint64_t aLayersId);
 };
 
 struct MOZ_STACK_CLASS TabParent::AutoUseNewTab final
 {
 public:
   AutoUseNewTab(TabParent* aNewTab, nsCString* aURLToLoad)
--- a/dom/ipc/nsIContentChild.cpp
+++ b/dom/ipc/nsIContentChild.cpp
@@ -97,17 +97,18 @@ nsIContentChild::RecvPBrowserConstructor
   if (NS_WARN_IF(NS_FAILED(tabChild->Init()))) {
     return IPC_FAIL(tabChild, "TabChild::Init failed");
   }
 
   nsCOMPtr<nsIObserverService> os = services::GetObserverService();
   if (os) {
     os->NotifyObservers(static_cast<nsITabChild*>(tabChild), "tab-child-created", nullptr);
   }
-
+  // Notify parent that we are ready to handle input events.
+  tabChild->SendRemoteIsReadyToHandleInputEvents();
   return IPC_OK();
 }
 
 PIPCBlobInputStreamChild*
 nsIContentChild::AllocPIPCBlobInputStreamChild(const nsID& aID,
                                                const uint64_t& aSize)
 {
   // IPCBlobInputStreamChild is refcounted. Here it's created and in
--- a/dom/ipc/nsIContentParent.cpp
+++ b/dom/ipc/nsIContentParent.cpp
@@ -201,16 +201,35 @@ nsIContentParent::AllocPBrowserParent(co
 bool
 nsIContentParent::DeallocPBrowserParent(PBrowserParent* aFrame)
 {
   TabParent* parent = TabParent::GetFrom(aFrame);
   NS_RELEASE(parent);
   return true;
 }
 
+mozilla::ipc::IPCResult
+nsIContentParent::RecvPBrowserConstructor(PBrowserParent* actor,
+                                          const TabId& tabId,
+                                          const TabId& sameTabGroupAs,
+                                          const IPCTabContext& context,
+                                          const uint32_t& chromeFlags,
+                                          const ContentParentId& cpId,
+                                          const bool& isForBrowser)
+{
+  TabParent* parent = TabParent::GetFrom(actor);
+  // When enabling input event prioritization, input events may preempt other
+  // normal priority IPC messages. To prevent the input events preempt
+  // PBrowserConstructor, we use an IPC 'RemoteIsReadyToHandleInputEvents' to
+  // notify parent that TabChild is created. In this case, PBrowser is initiated
+  // from content so that we can set TabParent as ready to handle input events.
+  parent->SetReadyToHandleInputEvents();
+  return IPC_OK();
+}
+
 PIPCBlobInputStreamParent*
 nsIContentParent::AllocPIPCBlobInputStreamParent(const nsID& aID,
                                                  const uint64_t& aSize)
 {
   MOZ_CRASH("PIPCBlobInputStreamParent actors should be manually constructed!");
   return nullptr;
 }
 
--- a/dom/ipc/nsIContentParent.h
+++ b/dom/ipc/nsIContentParent.h
@@ -112,16 +112,25 @@ protected: // IPDL methods
   virtual PBrowserParent* AllocPBrowserParent(const TabId& aTabId,
                                               const TabId& aSameTabGroupsAs,
                                               const IPCTabContext& aContext,
                                               const uint32_t& aChromeFlags,
                                               const ContentParentId& aCpId,
                                               const bool& aIsForBrowser);
   virtual bool DeallocPBrowserParent(PBrowserParent* frame);
 
+  virtual mozilla::ipc::IPCResult
+  RecvPBrowserConstructor(PBrowserParent* actor,
+                          const TabId& tabId,
+                          const TabId& sameTabGroupAs,
+                          const IPCTabContext& context,
+                          const uint32_t& chromeFlags,
+                          const ContentParentId& cpId,
+                          const bool& isForBrowser);
+
   virtual mozilla::ipc::PIPCBlobInputStreamParent*
   AllocPIPCBlobInputStreamParent(const nsID& aID, const uint64_t& aSize);
 
   virtual bool
   DeallocPIPCBlobInputStreamParent(mozilla::ipc::PIPCBlobInputStreamParent* aActor);
 
   virtual mozilla::ipc::PFileDescriptorSetParent*
   AllocPFileDescriptorSetParent(const mozilla::ipc::FileDescriptor& aFD);
--- a/dom/tests/browser/browser_bug1316330.js
+++ b/dom/tests/browser/browser_bug1316330.js
@@ -13,24 +13,24 @@ const URL =
   "  if (e.charCode == 'p'.charCodeAt(0)) while (Date.now() - startTime < 500) {}" +
   "};" +
   "</script>";
 
 add_task(async function() {
   let tab = await BrowserTestUtils.openNewForegroundTab(gBrowser, URL);
   let browser = tab.linkedBrowser;
 
-  EventUtils.synthesizeKey("d", { code: "KeyD", repeat: 3 });
+  await EventUtils.synthesizeAndWaitKey("d", { code: "KeyD", repeat: 3 });
 
   await ContentTask.spawn(browser, null, async function() {
     is(content.document.body.getAttribute("data-down"), "2", "Correct number of events");
     is(content.document.body.getAttribute("data-press"), "2", "Correct number of events");
   });
 
-  EventUtils.synthesizeKey("p", { code: "KeyP", repeat: 3 });
+  await EventUtils.synthesizeAndWaitKey("p", { code: "KeyP", repeat: 3 });
 
   await ContentTask.spawn(browser, null, async function() {
     is(content.document.body.getAttribute("data-down"), "4", "Correct number of events");
     is(content.document.body.getAttribute("data-press"), "4", "Correct number of events");
   });
 
   gBrowser.removeCurrentTab();
 });
--- a/editor/libeditor/tests/browser_bug629172.js
+++ b/editor/libeditor/tests/browser_bug629172.js
@@ -38,21 +38,20 @@ add_task(async function() {
       };
     });
 
     function simulateCtrlShiftX(aBrowser) {
       // In e10s, the keypress event will be dispatched to the content process,
       // but in non-e10s it is handled by the browser UI code and hence won't
       // reach the web page.  As a result, we need to observe the event in
       // the content process only in e10s mode.
-      var waitForKeypressContent = BrowserTestUtils.waitForContentEvent(aBrowser, "keypress");
+      if (gMultiProcessBrowser) {
+        return EventUtils.synthesizeAndWaitKey("x", {accelKey: true, shiftKey: true});
+      }
       EventUtils.synthesizeKey("x", {accelKey: true, shiftKey: true});
-      if (gMultiProcessBrowser) {
-        return waitForKeypressContent;
-      }
       return Promise.resolve();
     }
 
     async function testDirection(initialDir, aBrowser) {
       await ContentTask.spawn(aBrowser, {initialDir}, function({initialDir}) {
         var window = content.window.wrappedJSObject;
         var document = window.document;
 
new file mode 100644
--- /dev/null
+++ b/gfx/layers/AnimationInfo.cpp
@@ -0,0 +1,173 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * vim: sw=2 ts=8 et :
+ */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "AnimationInfo.h"
+#include "mozilla/layers/WebRenderLayerManager.h"
+#include "mozilla/layers/AnimationHelper.h"
+
+namespace mozilla {
+namespace layers {
+
+AnimationInfo::AnimationInfo(LayerManager* aManager) :
+  mManager(aManager),
+  mCompositorAnimationsId(0),
+  mAnimationGeneration(0),
+  mMutated(false)
+{
+}
+
+AnimationInfo::~AnimationInfo()
+{
+}
+
+void
+AnimationInfo::EnsureAnimationsId()
+{
+  if (!mCompositorAnimationsId) {
+    mCompositorAnimationsId = AnimationHelper::GetNextCompositorAnimationsId();
+  }
+}
+
+Animation*
+AnimationInfo::AddAnimation()
+{
+  // Here generates a new id when the first animation is added and
+  // this id is used to represent the animations in this layer.
+  EnsureAnimationsId();
+
+  MOZ_ASSERT(!mPendingAnimations, "should have called ClearAnimations first");
+
+  Animation* anim = mAnimations.AppendElement();
+
+  mMutated = true;
+
+  return anim;
+}
+
+Animation*
+AnimationInfo::AddAnimationForNextTransaction()
+{
+  MOZ_ASSERT(mPendingAnimations,
+             "should have called ClearAnimationsForNextTransaction first");
+
+  Animation* anim = mPendingAnimations->AppendElement();
+
+  return anim;
+}
+
+void
+AnimationInfo::ClearAnimations()
+{
+  mPendingAnimations = nullptr;
+
+  if (mAnimations.IsEmpty() && mAnimationData.IsEmpty()) {
+    return;
+  }
+
+  if (mManager->AsWebRenderLayerManager()) {
+    mManager->AsWebRenderLayerManager()->
+      AddCompositorAnimationsIdForDiscard(mCompositorAnimationsId);
+  }
+
+  mAnimations.Clear();
+  mAnimationData.Clear();
+
+  mMutated = true;
+}
+
+void
+AnimationInfo::ClearAnimationsForNextTransaction()
+{
+  // Ensure we have a non-null mPendingAnimations to mark a future clear.
+  if (!mPendingAnimations) {
+    mPendingAnimations = new AnimationArray;
+  }
+
+  mPendingAnimations->Clear();
+}
+
+void
+AnimationInfo::SetCompositorAnimations(const CompositorAnimations& aCompositorAnimations)
+{
+  mAnimations = aCompositorAnimations.animations();
+  mCompositorAnimationsId = aCompositorAnimations.id();
+  mAnimationData.Clear();
+  AnimationHelper::SetAnimations(mAnimations,
+                                 mAnimationData,
+                                 mBaseAnimationStyle);
+}
+
+bool
+AnimationInfo::StartPendingAnimations(const TimeStamp& aReadyTime)
+{
+  bool updated = false;
+  for (size_t animIdx = 0, animEnd = mAnimations.Length();
+       animIdx < animEnd; animIdx++) {
+    Animation& anim = mAnimations[animIdx];
+
+    // If the animation is play-pending, resolve the start time.
+    // This mirrors the calculation in Animation::StartTimeFromReadyTime.
+    if (anim.startTime().type() == MaybeTimeDuration::Tnull_t &&
+        !anim.originTime().IsNull() &&
+        !anim.isNotPlaying()) {
+      TimeDuration readyTime = aReadyTime - anim.originTime();
+      anim.startTime() =
+        anim.playbackRate() == 0
+        ? readyTime
+        : readyTime - anim.holdTime().MultDouble(1.0 /
+                                                 anim.playbackRate());
+      updated = true;
+    }
+  }
+  return updated;
+}
+
+void
+AnimationInfo::TransferMutatedFlagToLayer(Layer* aLayer)
+{
+  if (mMutated) {
+    aLayer->Mutated();
+    mMutated = false;
+  }
+}
+
+bool
+AnimationInfo::ApplyPendingUpdatesForThisTransaction()
+{
+  if (mPendingAnimations) {
+    mPendingAnimations->SwapElements(mAnimations);
+    mPendingAnimations = nullptr;
+    return true;
+  }
+
+  return false;
+}
+
+bool
+AnimationInfo::HasOpacityAnimation() const
+{
+  for (uint32_t i = 0; i < mAnimations.Length(); i++) {
+    if (mAnimations[i].property() == eCSSProperty_opacity) {
+      return true;
+    }
+  }
+  return false;
+}
+
+bool
+AnimationInfo::HasTransformAnimation() const
+{
+  for (uint32_t i = 0; i < mAnimations.Length(); i++) {
+    if (mAnimations[i].property() == eCSSProperty_transform) {
+      return true;
+    }
+  }
+  return false;
+}
+
+} // namespace layers
+} // namespace mozilla
new file mode 100644
--- /dev/null
+++ b/gfx/layers/AnimationInfo.h
@@ -0,0 +1,76 @@
+/* -*- 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/. */
+
+#ifndef GFX_ANIMATIONINFO_H
+#define GFX_ANIMATIONINFO_H
+
+#include "mozilla/StyleAnimationValue.h"
+
+namespace mozilla {
+namespace layers {
+
+class Animation;
+class CompositorAnimations;
+class Layer;
+class LayerManager;
+struct AnimData;
+
+class AnimationInfo
+{
+  typedef InfallibleTArray<Animation> AnimationArray;
+public:
+  explicit AnimationInfo(LayerManager* aManager);
+  virtual ~AnimationInfo();
+
+  // Ensure that this AnimationInfo has a valid (non-zero) animations id. This value is
+  // unique across layers.
+  void EnsureAnimationsId();
+
+  // Call AddAnimation to add a new animation to this layer from layout code.
+  // Caller must fill in all the properties of the returned animation.
+  // A later animation overrides an earlier one.
+  Animation* AddAnimation();
+
+  // These are a parallel to AddAnimation and clearAnimations, except
+  // they add pending animations that apply only when the next
+  // transaction is begun.  (See also
+  // SetBaseTransformForNextTransaction.)
+  Animation* AddAnimationForNextTransaction();
+
+  void SetAnimationGeneration(uint64_t aCount) { mAnimationGeneration = aCount; }
+  uint64_t GetAnimationGeneration() { return mAnimationGeneration; }
+
+  // ClearAnimations clears animations on this layer.
+  void ClearAnimations();
+  void ClearAnimationsForNextTransaction();
+  void SetCompositorAnimations(const CompositorAnimations& aCompositorAnimations);
+  bool StartPendingAnimations(const TimeStamp& aReadyTime);
+  void TransferMutatedFlagToLayer(Layer* aLayer);
+
+  uint64_t GetCompositorAnimationsId() { return mCompositorAnimationsId; }
+  StyleAnimationValue GetBaseAnimationStyle() const { return mBaseAnimationStyle; }
+  InfallibleTArray<AnimData>& GetAnimationData() { return mAnimationData; }
+  AnimationArray& GetAnimations() { return mAnimations; }
+  bool ApplyPendingUpdatesForThisTransaction();
+  bool HasOpacityAnimation() const;
+  bool HasTransformAnimation() const;
+
+protected:
+  LayerManager* mManager;
+  AnimationArray mAnimations;
+  uint64_t mCompositorAnimationsId;
+  nsAutoPtr<AnimationArray> mPendingAnimations;
+  InfallibleTArray<AnimData> mAnimationData;
+  // If this layer is used for OMTA, then this counter is used to ensure we
+  // stay in sync with the animation manager
+  uint64_t mAnimationGeneration;
+  StyleAnimationValue mBaseAnimationStyle;
+  bool mMutated;
+};
+
+} // namespace layers
+} // namespace mozilla
+
+#endif // GFX_ANIMATIONINFO_H
--- a/gfx/layers/ImageLayers.cpp
+++ b/gfx/layers/ImageLayers.cpp
@@ -28,17 +28,17 @@ void ImageLayer::SetContainer(ImageConta
 
 void ImageLayer::ComputeEffectiveTransforms(const gfx::Matrix4x4& aTransformToSurface)
 {
   gfx::Matrix4x4 local = GetLocalTransform();
 
   // Snap image edges to pixel boundaries
   gfxRect sourceRect(0, 0, 0, 0);
   if (mContainer) {
-    sourceRect.SizeTo(SizeDouble(mContainer->GetCurrentSize()));
+    sourceRect.SizeTo(gfx::SizeDouble(mContainer->GetCurrentSize()));
   }
   // Snap our local transform first, and snap the inherited transform as well.
   // This makes our snapping equivalent to what would happen if our content
   // was drawn into a PaintedLayer (gfxContext would snap using the local
   // transform, then we'd snap again when compositing the PaintedLayer).
   mEffectiveTransform =
       SnapTransform(local, sourceRect, nullptr) *
       SnapTransformTranslation(aTransformToSurface, nullptr);
--- a/gfx/layers/Layers.cpp
+++ b/gfx/layers/Layers.cpp
@@ -24,17 +24,16 @@
 #include "mozilla/DebugOnly.h"          // for DebugOnly
 #include "mozilla/IntegerPrintfMacros.h"
 #include "mozilla/Telemetry.h"          // for Accumulate
 #include "mozilla/ToString.h"
 #include "mozilla/gfx/2D.h"             // for DrawTarget
 #include "mozilla/gfx/BaseSize.h"       // for BaseSize
 #include "mozilla/gfx/Matrix.h"         // for Matrix4x4
 #include "mozilla/gfx/Polygon.h"        // for Polygon
-#include "mozilla/layers/AnimationHelper.h"
 #include "mozilla/layers/AsyncCanvasRenderer.h"
 #include "mozilla/layers/BSPTree.h"     // for BSPTree
 #include "mozilla/layers/CompositableClient.h"  // for CompositableClient
 #include "mozilla/layers/Compositor.h"  // for Compositor
 #include "mozilla/layers/CompositorTypes.h"
 #include "mozilla/layers/LayerManagerComposite.h"  // for LayerComposite
 #include "mozilla/layers/LayerMetricsWrapper.h" // for LayerMetricsWrapper
 #include "mozilla/layers/LayersMessages.h"  // for TransformFunction, etc
@@ -181,141 +180,53 @@ LayerManager::RemoveUserData(void* aKey)
 {
   UniquePtr<LayerUserData> d(static_cast<LayerUserData*>(mUserData.Remove(static_cast<gfx::UserDataKey*>(aKey))));
   return d;
 }
 
 //--------------------------------------------------
 // Layer
 
-Layer::Layer(LayerManager* aManager, void* aImplData) :
-  mManager(aManager),
-  mParent(nullptr),
-  mNextSibling(nullptr),
-  mPrevSibling(nullptr),
-  mImplData(aImplData),
-  mCompositorAnimationsId(0),
-  mUseTileSourceRect(false),
+Layer::Layer(LayerManager* aManager, void* aImplData)
+  : mManager(aManager)
+  , mParent(nullptr)
+  , mNextSibling(nullptr)
+  , mPrevSibling(nullptr)
+  , mImplData(aImplData)
+  , mAnimationInfo(aManager)
+  , mUseTileSourceRect(false)
 #ifdef DEBUG
-  mDebugColorIndex(0),
+  , mDebugColorIndex(0)
 #endif
-  mAnimationGeneration(0)
 {
 }
 
 Layer::~Layer()
 {
 }
 
 void
-Layer::EnsureAnimationsId()
-{
-  if (!mCompositorAnimationsId) {
-    mCompositorAnimationsId = AnimationHelper::GetNextCompositorAnimationsId();
-  }
-}
-
-Animation*
-Layer::AddAnimation()
-{
-  // Here generates a new id when the first animation is added and
-  // this id is used to represent the animations in this layer.
-  EnsureAnimationsId();
-
-  MOZ_LAYERS_LOG_IF_SHADOWABLE(
-    this, ("Layer::Mutated(%p) AddAnimation with id=%" PRIu64, this, mCompositorAnimationsId));
-
-  MOZ_ASSERT(!mPendingAnimations, "should have called ClearAnimations first");
-
-  Animation* anim = mAnimations.AppendElement();
-
-  Mutated();
-  return anim;
-}
-
-void
-Layer::ClearAnimations()
-{
-  mPendingAnimations = nullptr;
-
-  if (mAnimations.IsEmpty() && mAnimationData.IsEmpty()) {
-    return;
-  }
-
-  MOZ_LAYERS_LOG_IF_SHADOWABLE(this, ("Layer::Mutated(%p) ClearAnimations", this));
-  mAnimations.Clear();
-  mAnimationData.Clear();
-  Mutated();
-}
-
-Animation*
-Layer::AddAnimationForNextTransaction()
-{
-  MOZ_ASSERT(mPendingAnimations,
-             "should have called ClearAnimationsForNextTransaction first");
-
-  Animation* anim = mPendingAnimations->AppendElement();
-
-  return anim;
-}
-
-void
-Layer::ClearAnimationsForNextTransaction()
-{
-  // Ensure we have a non-null mPendingAnimations to mark a future clear.
-  if (!mPendingAnimations) {
-    mPendingAnimations = new AnimationArray;
-  }
-
-  mPendingAnimations->Clear();
-}
-
-void
 Layer::SetCompositorAnimations(const CompositorAnimations& aCompositorAnimations)
 {
   MOZ_LAYERS_LOG_IF_SHADOWABLE(
-    this, ("Layer::Mutated(%p) SetCompositorAnimations with id=%" PRIu64, this, mCompositorAnimationsId));
+    this, ("Layer::Mutated(%p) SetCompositorAnimations with id=%" PRIu64, this, mAnimationInfo.GetCompositorAnimationsId()));
 
-  mAnimations = aCompositorAnimations.animations();
-  mCompositorAnimationsId = aCompositorAnimations.id();
-  mAnimationData.Clear();
-  AnimationHelper::SetAnimations(mAnimations,
-                                 mAnimationData,
-                                 mBaseAnimationStyle);
+  mAnimationInfo.SetCompositorAnimations(aCompositorAnimations);
 
   Mutated();
 }
 
 void
 Layer::StartPendingAnimations(const TimeStamp& aReadyTime)
 {
   ForEachNode<ForwardIterator>(
       this,
       [&aReadyTime](Layer *layer)
       {
-        bool updated = false;
-        for (size_t animIdx = 0, animEnd = layer->mAnimations.Length();
-             animIdx < animEnd; animIdx++) {
-          Animation& anim = layer->mAnimations[animIdx];
-
-          // If the animation is play-pending, resolve the start time.
-          // This mirrors the calculation in Animation::StartTimeFromReadyTime.
-          if (anim.startTime().type() == MaybeTimeDuration::Tnull_t &&
-              !anim.originTime().IsNull() &&
-              !anim.isNotPlaying()) {
-            TimeDuration readyTime = aReadyTime - anim.originTime();
-            anim.startTime() =
-              anim.playbackRate() == 0
-              ? readyTime
-              : readyTime - anim.holdTime().MultDouble(1.0 /
-                                                       anim.playbackRate());
-            updated = true;
-          }
-        }
-        if (updated) {
+        if (layer->mAnimationInfo.StartPendingAnimations(aReadyTime)) {
           layer->Mutated();
         }
       });
 }
 
 void
 Layer::SetAsyncPanZoomController(uint32_t aIndex, AsyncPanZoomController *controller)
 {
@@ -705,49 +616,37 @@ const LayerToParentLayerMatrix4x4
 Layer::GetLocalTransformTyped()
 {
   return ViewAs<LayerToParentLayerMatrix4x4>(GetLocalTransform());
 }
 
 bool
 Layer::HasOpacityAnimation() const
 {
-  for (uint32_t i = 0; i < mAnimations.Length(); i++) {
-    if (mAnimations[i].property() == eCSSProperty_opacity) {
-      return true;
-    }
-  }
-  return false;
+  return mAnimationInfo.HasOpacityAnimation();
 }
 
 bool
 Layer::HasTransformAnimation() const
 {
-  for (uint32_t i = 0; i < mAnimations.Length(); i++) {
-    if (mAnimations[i].property() == eCSSProperty_transform) {
-      return true;
-    }
-  }
-  return false;
+  return mAnimationInfo.HasTransformAnimation();
 }
 
 void
 Layer::ApplyPendingUpdatesForThisTransaction()
 {
   if (mPendingTransform && *mPendingTransform != mSimpleAttrs.Transform()) {
     MOZ_LAYERS_LOG_IF_SHADOWABLE(this, ("Layer::Mutated(%p) PendingUpdatesForThisTransaction", this));
     mSimpleAttrs.SetTransform(*mPendingTransform);
     MutatedSimple();
   }
   mPendingTransform = nullptr;
 
-  if (mPendingAnimations) {
+  if (mAnimationInfo.ApplyPendingUpdatesForThisTransaction()) {
     MOZ_LAYERS_LOG_IF_SHADOWABLE(this, ("Layer::Mutated(%p) PendingUpdatesForThisTransaction", this));
-    mPendingAnimations->SwapElements(mAnimations);
-    mPendingAnimations = nullptr;
     Mutated();
   }
 
   for (size_t i = 0; i < mScrollMetadata.Length(); i++) {
     FrameMetrics& fm = mScrollMetadata[i].GetMetrics();
     Maybe<ScrollUpdateInfo> update = Manager()->GetPendingScrollInfoUpdate(fm.GetScrollId());
     if (update) {
       fm.UpdatePendingScrollInfo(update.value());
@@ -892,16 +791,22 @@ Layer::GetVisibleRegionRelativeToRootLay
     // positioning code.
     offset += currentLayerOffset;
   }
 
   *aLayerOffset = IntPoint(offset.x, offset.y);
   return true;
 }
 
+InfallibleTArray<AnimData>&
+Layer::GetAnimationData()
+{
+  return mAnimationInfo.GetAnimationData();
+}
+
 Maybe<ParentLayerIntRect>
 Layer::GetCombinedClipRect() const
 {
   Maybe<ParentLayerIntRect> clip = GetClipRect();
 
   clip = IntersectMaybeRects(clip, GetScrolledClipRect());
 
   for (size_t i = 0; i < mScrollMetadata.Length(); i++) {
@@ -1933,20 +1838,20 @@ Layer::PrintInfo(std::stringstream& aStr
     aStream << nsPrintfCString(" [mMaskLayer=%p]", mMaskLayer.get()).get();
   }
   for (uint32_t i = 0; i < mScrollMetadata.Length(); i++) {
     if (!mScrollMetadata[i].IsDefault()) {
       aStream << nsPrintfCString(" [metrics%d=", i).get();
       AppendToString(aStream, mScrollMetadata[i], "", "]");
     }
   }
-  if (!mAnimations.IsEmpty()) {
+  if (!mAnimationInfo.GetAnimations().IsEmpty()) {
     aStream << nsPrintfCString(" [%d animations with id=%" PRIu64 " ]",
-                               (int) mAnimations.Length(),
-                               mCompositorAnimationsId).get();
+                               (int) mAnimationInfo.GetAnimations().Length(),
+                               mAnimationInfo.GetCompositorAnimationsId()).get();
   }
 }
 
 // The static helper function sets the transform matrix into the packet
 static void
 DumpTransform(layerscope::LayersPacket::Layer::Matrix* aLayerMatrix, const Matrix4x4& aMatrix)
 {
   aLayerMatrix->set_is2d(aMatrix.Is2D());
--- a/gfx/layers/Layers.h
+++ b/gfx/layers/Layers.h
@@ -28,16 +28,17 @@
 #include "mozilla/TimeStamp.h"          // for TimeStamp, TimeDuration
 #include "mozilla/UniquePtr.h"          // for UniquePtr
 #include "mozilla/gfx/BaseMargin.h"     // for BaseMargin
 #include "mozilla/gfx/BasePoint.h"      // for BasePoint
 #include "mozilla/gfx/Point.h"          // for IntSize
 #include "mozilla/gfx/TiledRegion.h"    // for TiledIntRegion
 #include "mozilla/gfx/Types.h"          // for SurfaceFormat
 #include "mozilla/gfx/UserData.h"       // for UserData, etc
+#include "mozilla/layers/AnimationInfo.h" // for AnimationInfo
 #include "mozilla/layers/BSPTree.h"     // for LayerPolygon
 #include "mozilla/layers/LayerAttributes.h"
 #include "mozilla/layers/LayersTypes.h"
 #include "mozilla/mozalloc.h"           // for operator delete, etc
 #include "nsAutoPtr.h"                  // for nsAutoPtr, nsRefPtr, etc
 #include "nsCOMPtr.h"                   // for already_AddRefed
 #include "nsCSSPropertyID.h"              // for nsCSSPropertyID
 #include "nsDebug.h"                    // for NS_ASSERTION
@@ -70,17 +71,16 @@ class GLContext;
 
 namespace gfx {
 class DrawTarget;
 } // namespace gfx
 
 namespace layers {
 
 class Animation;
-class AnimationData;
 class AsyncCanvasRenderer;
 class AsyncPanZoomController;
 class BasicLayerManager;
 class ClientLayerManager;
 class HostLayerManager;
 class Layer;
 class LayerMetricsWrapper;
 class PaintedLayer;
@@ -1240,42 +1240,25 @@ public:
    */
   void SetTransformIsPerspective(bool aTransformIsPerspective)
   {
     if (mSimpleAttrs.SetTransformIsPerspective(aTransformIsPerspective)) {
       MOZ_LAYERS_LOG_IF_SHADOWABLE(this, ("Layer::Mutated(%p) TransformIsPerspective", this));
       MutatedSimple();
     }
   }
-
-  // Ensure that this layer has a valid (non-zero) animations id. This value is
-  // unique across layers.
-  void EnsureAnimationsId();
-  // Call AddAnimation to add a new animation to this layer from layout code.
-  // Caller must fill in all the properties of the returned animation.
-  // A later animation overrides an earlier one.
-  Animation* AddAnimation();
-  // ClearAnimations clears animations on this layer.
-  virtual void ClearAnimations();
   // This is only called when the layer tree is updated. Do not call this from
   // layout code.  To add an animation to this layer, use AddAnimation.
   void SetCompositorAnimations(const CompositorAnimations& aCompositorAnimations);
   // Go through all animations in this layer and its children and, for
   // any animations with a null start time, update their start time such
   // that at |aReadyTime| the animation's current time corresponds to its
   // 'initial current time' value.
   void StartPendingAnimations(const TimeStamp& aReadyTime);
 
-  // These are a parallel to AddAnimation and clearAnimations, except
-  // they add pending animations that apply only when the next
-  // transaction is begun.  (See also
-  // SetBaseTransformForNextTransaction.)
-  Animation* AddAnimationForNextTransaction();
-  void ClearAnimationsForNextTransaction();
-
   /**
    * CONSTRUCTION PHASE ONLY
    * If a layer represents a fixed position element, this data is stored on the
    * layer for use by the compositor.
    *
    *   - |aScrollId| identifies the scroll frame that this element is fixed
    *     with respect to.
    *
@@ -1444,29 +1427,28 @@ public:
    *  in this case, results will not be valid. Returns true on successful
    *  traversal.
    */
   bool GetVisibleRegionRelativeToRootLayer(nsIntRegion& aResult,
                                            nsIntPoint* aLayerOffset);
 
   // Note that all lengths in animation data are either in CSS pixels or app
   // units and must be converted to device pixels by the compositor.
-  AnimationArray& GetAnimations() { return mAnimations; }
-  uint64_t GetCompositorAnimationsId() { return mCompositorAnimationsId; }
-  InfallibleTArray<AnimData>& GetAnimationData() { return mAnimationData; }
-
-  uint64_t GetAnimationGeneration() { return mAnimationGeneration; }
-  void SetAnimationGeneration(uint64_t aCount) { mAnimationGeneration = aCount; }
+  AnimationArray& GetAnimations() { return mAnimationInfo.GetAnimations(); }
+  uint64_t GetCompositorAnimationsId() { return mAnimationInfo.GetCompositorAnimationsId(); }
+  InfallibleTArray<AnimData>& GetAnimationData();
+
+  uint64_t GetAnimationGeneration() { return mAnimationInfo.GetAnimationGeneration(); }
 
   bool HasTransformAnimation() const;
   bool HasOpacityAnimation() const;
 
   StyleAnimationValue GetBaseAnimationStyle() const
   {
-    return mBaseAnimationStyle;
+    return mAnimationInfo.GetBaseAnimationStyle();
   }
 
   /**
    * Returns the local transform for this layer: either mTransform or,
    * for shadow layers, GetShadowBaseTransform(), in either case with the
    * pre- and post-scales applied.
    */
   gfx::Matrix4x4 GetLocalTransform();
@@ -1885,16 +1867,18 @@ public:
    */
   void ClearExtraDumpInfo()
   {
 #ifdef MOZ_DUMP_PAINTING
      mExtraDumpInfo.Clear();
 #endif
   }
 
+  AnimationInfo& GetAnimationInfo() { return mAnimationInfo; }
+
 protected:
   Layer(LayerManager* aManager, void* aImplData);
 
   // Protected destructor, to discourage deletion outside of Release():
   virtual ~Layer();
 
   /**
    * We can snap layer transforms for two reasons:
@@ -1954,39 +1938,30 @@ protected:
   LayerIntRegion mVisibleRegion;
   nsTArray<ScrollMetadata> mScrollMetadata;
   EventRegions mEventRegions;
   // A mutation of |mTransform| that we've queued to be applied at the
   // end of the next transaction (if nothing else overrides it in the
   // meantime).
   nsAutoPtr<gfx::Matrix4x4> mPendingTransform;
   gfx::Matrix4x4 mEffectiveTransform;
-  AnimationArray mAnimations;
-  uint64_t mCompositorAnimationsId;
-  // See mPendingTransform above.
-  nsAutoPtr<AnimationArray> mPendingAnimations;
-  InfallibleTArray<AnimData> mAnimationData;
+  AnimationInfo mAnimationInfo;
   Maybe<ParentLayerIntRect> mClipRect;
   gfx::IntRect mTileSourceRect;
   gfx::TiledIntRegion mInvalidRegion;
   nsTArray<RefPtr<AsyncPanZoomController> > mApzcs;
   bool mUseTileSourceRect;
 #ifdef DEBUG
   uint32_t mDebugColorIndex;
 #endif
-  // If this layer is used for OMTA, then this counter is used to ensure we
-  // stay in sync with the animation manager
-  uint64_t mAnimationGeneration;
 #ifdef MOZ_DUMP_PAINTING
   nsTArray<nsCString> mExtraDumpInfo;
 #endif
   // Store display list log.
   nsCString mDisplayListLog;
-
-  StyleAnimationValue mBaseAnimationStyle;
 };
 
 /**
  * A Layer which we can paint into. It is a conceptually
  * infinite surface, but each PaintedLayer has an associated "valid region"
  * of contents that it is currently storing, which is finite. PaintedLayer
  * implementations can store content between paints.
  *
--- a/gfx/layers/apz/src/AndroidDynamicToolbarAnimator.cpp
+++ b/gfx/layers/apz/src/AndroidDynamicToolbarAnimator.cpp
@@ -8,17 +8,19 @@
 
 #include <cmath>
 #include "FrameMetrics.h"
 #include "gfxPrefs.h"
 #include "mozilla/EventForwards.h"
 #include "mozilla/FloatingPoint.h"
 #include "mozilla/gfx/2D.h"
 #include "mozilla/gfx/Types.h"
+#include "mozilla/layers/APZCTreeManager.h"
 #include "mozilla/layers/APZThreadUtils.h"
+#include "mozilla/layers/AsyncCompositionManager.h"
 #include "mozilla/layers/CompositorBridgeParent.h"
 #include "mozilla/layers/CompositorOGL.h"
 #include "mozilla/layers/CompositorThread.h"
 #include "mozilla/layers/UiCompositorControllerMessageTypes.h"
 #include "mozilla/layers/UiCompositorControllerParent.h"
 #include "mozilla/MathAlgorithms.h"
 #include "mozilla/Move.h"
 #include "mozilla/Unused.h"
@@ -504,20 +506,20 @@ AndroidDynamicToolbarAnimator::UpdateToo
 {
   MOZ_ASSERT(CompositorThreadHolder::IsInCompositorThread());
   // if the compositor has shutdown, do not create any new rendering objects.
   if (mCompositorShutdown) {
     return;
   }
 
   if (mCompositorToolbarPixels) {
-    RefPtr<DataSourceSurface> surface = Factory::CreateWrappingDataSourceSurface(
+    RefPtr<gfx::DataSourceSurface> surface = gfx::Factory::CreateWrappingDataSourceSurface(
         mCompositorToolbarPixels.ref().get<uint8_t>(),
         mCompositorToolbarPixelsSize.width * 4,
-        IntSize(mCompositorToolbarPixelsSize.width, mCompositorToolbarPixelsSize.height),
+        gfx::IntSize(mCompositorToolbarPixelsSize.width, mCompositorToolbarPixelsSize.height),
         gfx::SurfaceFormat::B8G8R8A8);
 
     if (!mCompositorToolbarTexture) {
       mCompositorToolbarTexture = gl->CreateDataTextureSource();
       mCompositorToolbarEffect = nullptr;
     }
 
     if (!mCompositorToolbarTexture->Update(surface)) {
@@ -543,17 +545,17 @@ AndroidDynamicToolbarAnimator::GetToolba
   MOZ_ASSERT(CompositorThreadHolder::IsInCompositorThread());
   // if the compositor has shutdown, do not create any new rendering objects.
   if (mCompositorShutdown) {
     return nullptr;
   }
 
   if (mCompositorToolbarTexture) {
     if (!mCompositorToolbarEffect) {
-      mCompositorToolbarEffect = new EffectRGB(mCompositorToolbarTexture, true, SamplingFilter::LINEAR);
+      mCompositorToolbarEffect = new EffectRGB(mCompositorToolbarTexture, true, gfx::SamplingFilter::LINEAR);
     }
 
     float ratioVisible = (float)mCompositorToolbarHeight / (float)mCompositorMaxToolbarHeight;
     mCompositorToolbarEffect->mTextureCoords.y = 1.0f - ratioVisible;
     mCompositorToolbarEffect->mTextureCoords.height = ratioVisible;
   }
 
   return mCompositorToolbarEffect.get();
--- a/gfx/layers/apz/test/mochitest/helper_touch_action_regions.html
+++ b/gfx/layers/apz/test/mochitest/helper_touch_action_regions.html
@@ -88,16 +88,21 @@ function waitFor(eventType, count) {
     if (Date.now() - start > 10000) {
       // It's taking too long, let's abort
       return false;
     }
   }
   return true;
 }
 
+function RunAfterProcessedQueuedInputEvents(aCallback) {
+  let tm = SpecialPowers.Services.tm;
+  tm.dispatchToMainThread(aCallback, SpecialPowers.Ci.nsIRunnablePriority.PRIORITY_INPUT);
+}
+
 function* test(testDriver) {
   // The main part of this test should run completely before the child process'
   // main-thread deals with the touch event, so check to make sure that happens.
   document.body.addEventListener('touchstart', failure, { passive: true });
 
   // What we want here is to synthesize all of the touch events (from this code in
   // the child process), and have the chrome process generate and process them,
   // but not allow the events to be dispatched back into the child process until
@@ -134,26 +139,29 @@ function* test(testDriver) {
   //     handle DOM touchstart  <-- touchstart goes at end of queue
   //
   // As we continue yielding one at a time, the synthesizations run, and the
   // touch events get added to the end of the queue. As we yield, we take
   // snapshots in the chrome process, to make sure that the APZ has started
   // scrolling even though we know we haven't yet processed the DOM touch events
   // in the child process yet.
   //
-  // Note that the "async callback" we use here is SpecialPowers.executeSoon,
-  // because nothing else does exactly what we want:
+  // Note that the "async callback" we use here is SpecialPowers.tm.dispatchToMainThread
+  // with priority = input, because nothing else does exactly what we want:
   // - setTimeout(..., 0) does not maintain ordering, because it respects the
   //   time delta provided (i.e. the callback can jump the queue to meet its
   //   deadline).
   // - SpecialPowers.spinEventLoop and SpecialPowers.executeAfterFlushingMessageQueue
   //   are not e10s friendly, and can get arbitrarily delayed due to IPC
   //   round-trip time.
   // - SimpleTest.executeSoon has a codepath that delegates to setTimeout, so
   //   is less reliable if it ever decides to switch to that codepath.
+  // - SpecialPowers.executeSoon dispatches a task to main thread. However,
+  //   normal runnables may be preempted by input events and be executed in an
+  //   unexpected order.
 
   // The other problem we need to deal with is the asynchronicity in the chrome
   // process. That is, we might request a snapshot before the chrome process has
   // actually synthesized the event and processed it. To guard against this, we
   // register a thing in the chrome process that counts the touch events that
   // have been dispatched, and poll that thing synchronously in order to make
   // sure we only snapshot after the event in question has been processed.
   // That's what the chromeTouchEventCounter business is all about. The sync
@@ -164,23 +172,23 @@ function* test(testDriver) {
   // So, here we go...
 
   // Set up the chrome process touch listener
   ok(chromeTouchEventCounter('start'), "Chrome touch counter registered");
 
   // Set up the child process events and callbacks
   var scroller = document.getElementById('scroller');
   synthesizeNativeTouch(scroller, 10, 110, SpecialPowers.DOMWindowUtils.TOUCH_CONTACT, null, 0);
-  SpecialPowers.executeSoon(testDriver);
+  RunAfterProcessedQueuedInputEvents(testDriver);
   for (var i = 1; i < 10; i++) {
     synthesizeNativeTouch(scroller, 10, 110 - (i * 10), SpecialPowers.DOMWindowUtils.TOUCH_CONTACT, null, 0);
-    SpecialPowers.executeSoon(testDriver);
+    RunAfterProcessedQueuedInputEvents(testDriver);
   }
   synthesizeNativeTouch(scroller, 10, 10, SpecialPowers.DOMWindowUtils.TOUCH_REMOVE, null, 0);
-  SpecialPowers.executeSoon(testDriver);
+  RunAfterProcessedQueuedInputEvents(testDriver);
   ok(true, "Finished setting up event queue");
 
   // Get our baseline snapshot
   var rect = rectRelativeToScreen(scroller);
   var lastSnapshot = getSnapshot(rect);
   ok(true, "Got baseline snapshot");
   var numDifferentSnapshotPairs = 0;
 
--- a/gfx/layers/composite/TextureHost.cpp
+++ b/gfx/layers/composite/TextureHost.cpp
@@ -93,17 +93,17 @@ public:
   wr::MaybeExternalImageId mExternalImageId;
 };
 
 static bool
 WrapWithWebRenderTextureHost(ISurfaceAllocator* aDeallocator,
                              LayersBackend aBackend,
                              TextureFlags aFlags)
 {
-  if (!gfxVars::UseWebRender() ||
+  if (!gfx::gfxVars::UseWebRender() ||
       (aFlags & TextureFlags::SNAPSHOT) ||
       (aBackend != LayersBackend::LAYERS_WR) ||
       (!aDeallocator->UsesImageBridge() && !aDeallocator->AsCompositorBridgeParentBase())) {
     return false;
   }
   return true;
 }
 
--- a/gfx/layers/mlgpu/FrameBuilder.cpp
+++ b/gfx/layers/mlgpu/FrameBuilder.cpp
@@ -4,16 +4,17 @@
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "FrameBuilder.h"
 #include "ContainerLayerMLGPU.h"
 #include "GeckoProfiler.h"              // for profiler_*
 #include "LayerMLGPU.h"
 #include "LayerManagerMLGPU.h"
 #include "MaskOperation.h"
+#include "MLGDevice.h"                  // for MLGSwapChain
 #include "RenderPassMLGPU.h"
 #include "RenderViewMLGPU.h"
 #include "mozilla/gfx/Polygon.h"
 #include "mozilla/layers/BSPTree.h"
 #include "mozilla/layers/LayersHelpers.h"
 
 namespace mozilla {
 namespace layers {
--- a/gfx/layers/moz.build
+++ b/gfx/layers/moz.build
@@ -87,16 +87,17 @@ EXPORTS.gfxipc += [
 ]
 
 EXPORTS.mozilla.dom += [
     'apz/util/CheckerboardReportService.h',
 ]
 
 EXPORTS.mozilla.layers += [
     'AnimationHelper.h',
+    'AnimationInfo.h',
     'apz/public/CompositorController.h',
     'apz/public/GeckoContentController.h',
     'apz/public/IAPZCTreeManager.h',
     'apz/public/MetricsSharingController.h',
     # exporting things from apz/src is temporary until we extract a
     # proper interface for the code there
     'apz/src/APZCTreeManager.h',
     'apz/src/APZUtils.h',
@@ -281,16 +282,17 @@ if CONFIG['MOZ_WIDGET_TOOLKIT'] == 'andr
         'apz/src/AndroidDynamicToolbarAnimator.cpp',
     ]
     EXPORTS.mozilla.layers += [
         'apz/src/AndroidDynamicToolbarAnimator.h',
     ]
 
 UNIFIED_SOURCES += [
     'AnimationHelper.cpp',
+    'AnimationInfo.cpp',
     'apz/public/IAPZCTreeManager.cpp',
     'apz/src/APZCTreeManager.cpp',
     'apz/src/AsyncPanZoomController.cpp',
     'apz/src/Axis.cpp',
     'apz/src/CheckerboardEvent.cpp',
     'apz/src/DragTracker.cpp',
     'apz/src/FocusState.cpp',
     'apz/src/FocusTarget.cpp',
--- a/gfx/layers/wr/WebRenderContainerLayer.cpp
+++ b/gfx/layers/wr/WebRenderContainerLayer.cpp
@@ -13,31 +13,19 @@
 #include "mozilla/layers/WebRenderBridgeChild.h"
 #include "mozilla/webrender/WebRenderTypes.h"
 #include "UnitTransforms.h"
 
 namespace mozilla {
 namespace layers {
 
 void
-WebRenderContainerLayer::ClearAnimations()
-{
-
-  if (!GetAnimations().IsEmpty()) {
-    mManager->AsWebRenderLayerManager()->
-      AddCompositorAnimationsIdForDiscard(GetCompositorAnimationsId());
-  }
-
-  Layer::ClearAnimations();
-}
-
-void
 WebRenderContainerLayer::UpdateTransformDataForAnimation()
 {
-  for (Animation& animation : mAnimations) {
+  for (Animation& animation : mAnimationInfo.GetAnimations()) {
     if (animation.property() == eCSSProperty_transform) {
       TransformData& transformData = animation.data().get_TransformData();
       transformData.inheritedXScale() = GetInheritedXScale();
       transformData.inheritedYScale() = GetInheritedYScale();
       transformData.hasPerspectiveParent() =
         GetParent() && GetParent()->GetTransformIsPerspective();
     }
   }
@@ -95,17 +83,17 @@ WebRenderContainerLayer::RenderLayer(wr:
   // ensure that there is an animations id set on it, we will use this to give
   // WebRender updated transforms for composition.
   if (WrManager()->AsyncPanZoomEnabled() &&
       GetScrollThumbData().mDirection != ScrollDirection::NONE) {
     // A scroll thumb better not have a transform animation already or we're
     // going to end up clobbering it with APZ animating it too.
     MOZ_ASSERT(transformForSC);
 
-    EnsureAnimationsId();
+    mAnimationInfo.EnsureAnimationsId();
     animationsId = GetCompositorAnimationsId();
     // We need to set the transform in the stacking context to null for it to
     // pick up and install the animation id.
     transformForSC = nullptr;
   }
 
   if (transformForSC && transform.IsIdentity()) {
     // If the transform is an identity transform, strip it out so that WR
--- a/gfx/layers/wr/WebRenderContainerLayer.h
+++ b/gfx/layers/wr/WebRenderContainerLayer.h
@@ -40,17 +40,16 @@ protected:
 
   void UpdateTransformDataForAnimation();
 
 public:
   Layer* GetLayer() override { return this; }
   void RenderLayer(wr::DisplayListBuilder& aBuilder,
                    const StackingContextHelper& aSc) override;
 
-  void ClearAnimations() override;
   virtual void ComputeEffectiveTransforms(const gfx::Matrix4x4& aTransformToSurface) override
   {
     DefaultComputeEffectiveTransforms(aTransformToSurface);
   }
 };
 
 class WebRenderRefLayer : public WebRenderLayer,
                           public RefLayer {
--- a/gfx/layers/wr/WebRenderLayerManager.cpp
+++ b/gfx/layers/wr/WebRenderLayerManager.cpp
@@ -414,25 +414,31 @@ WebRenderLayerManager::PushItemAsImage(n
   LayerPoint offset = ViewAs<LayerPixel>(
       LayoutDevicePoint::FromAppUnits(aItem->ToReferenceFrame() + shift, appUnitsPerDevPixel),
       PixelCastJustification::WebRenderHasUnitResolution);
 
   nsRegion invalidRegion;
   nsAutoPtr<nsDisplayItemGeometry> geometry = fallbackData->GetGeometry();
 
   if (geometry) {
-    nsPoint shift = itemBounds.TopLeft() - geometry->mBounds.TopLeft();
-    geometry->MoveBy(shift);
-    aItem->ComputeInvalidationRegion(aDisplayListBuilder, geometry, &invalidRegion);
-    nsRect lastBounds = fallbackData->GetBounds();
-    lastBounds.MoveBy(shift);
+    nsRect invalid;
+    if (aItem->IsInvalid(invalid)) {
+      invalidRegion.OrWith(clippedBounds);
+    } else {
+      nsPoint shift = itemBounds.TopLeft() - geometry->mBounds.TopLeft();
+      geometry->MoveBy(shift);
+      aItem->ComputeInvalidationRegion(aDisplayListBuilder, geometry, &invalidRegion);
 
-    if (!lastBounds.IsEqualInterior(clippedBounds)) {
-      invalidRegion.OrWith(lastBounds);
-      invalidRegion.OrWith(clippedBounds);
+      nsRect lastBounds = fallbackData->GetBounds();
+      lastBounds.MoveBy(shift);
+
+      if (!lastBounds.IsEqualInterior(clippedBounds)) {
+        invalidRegion.OrWith(lastBounds);
+        invalidRegion.OrWith(clippedBounds);
+      }
     }
   }
 
   if (!geometry || !invalidRegion.IsEmpty()) {
     if (gfxPrefs::WebRenderBlobImages()) {
       RefPtr<gfx::DrawEventRecorderMemory> recorder = MakeAndAddRef<gfx::DrawEventRecorderMemory>();
       RefPtr<gfx::DrawTarget> dummyDt =
         gfx::Factory::CreateDrawTarget(gfx::BackendType::SKIA, gfx::IntSize(1, 1), gfx::SurfaceFormat::B8G8R8X8);
--- a/gfx/layers/wr/WebRenderUserData.cpp
+++ b/gfx/layers/wr/WebRenderUserData.cpp
@@ -164,10 +164,16 @@ WebRenderFallbackData::GetGeometry()
 }
 
 void
 WebRenderFallbackData::SetGeometry(nsAutoPtr<nsDisplayItemGeometry> aGeometry)
 {
   mGeometry = aGeometry;
 }
 
+WebRenderAnimationData::WebRenderAnimationData(WebRenderLayerManager* aWRManager)
+  : WebRenderUserData(aWRManager),
+    mAnimationInfo(aWRManager)
+{
+}
+
 } // namespace layers
 } // namespace mozilla
--- a/gfx/layers/wr/WebRenderUserData.h
+++ b/gfx/layers/wr/WebRenderUserData.h
@@ -3,16 +3,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/. */
 
 #ifndef GFX_WEBRENDERUSERDATA_H
 #define GFX_WEBRENDERUSERDATA_H
 
 #include "mozilla/layers/StackingContextHelper.h"
 #include "mozilla/webrender/WebRenderAPI.h"
+#include "mozilla/layers/AnimationInfo.h"
 
 class nsDisplayItemGeometry;
 
 namespace mozilla {
 namespace layers {
 class ImageClient;
 class ImageContainer;
 class WebRenderBridgeChild;
@@ -28,16 +29,17 @@ public:
     : mWRManager(aWRManager)
   { }
 
   virtual WebRenderImageData* AsImageData() { return nullptr; }
 
   enum class UserDataType {
     eImage,
     eFallback,
+    eAnimation,
   };
 
   virtual UserDataType GetType() = 0;
 
 protected:
   virtual ~WebRenderUserData() {}
 
   WebRenderBridgeChild* WrBridge() const;
@@ -95,12 +97,26 @@ public:
   nsRect GetBounds() { return mBounds; }
   void SetBounds(const nsRect& aRect) { mBounds = aRect; }
 
 protected:
   nsAutoPtr<nsDisplayItemGeometry> mGeometry;
   nsRect mBounds;
 };
 
+class WebRenderAnimationData : public WebRenderUserData
+{
+public:
+  explicit WebRenderAnimationData(WebRenderLayerManager* aWRManager);
+  virtual ~WebRenderAnimationData() {}
+
+  virtual UserDataType GetType() override { return UserDataType::eAnimation; }
+  static UserDataType Type() { return UserDataType::eAnimation; }
+  AnimationInfo& GetAnimationInfo() { return mAnimationInfo; }
+
+protected:
+  AnimationInfo mAnimationInfo;
+};
+
 } // namespace layers
 } // namespace mozilla
 
 #endif /* GFX_WEBRENDERUSERDATA_H */
--- a/ipc/chromium/src/chrome/common/ipc_message.cc
+++ b/ipc/chromium/src/chrome/common/ipc_message.cc
@@ -62,18 +62,17 @@ Message::Message(int32_t routing_id,
                  MessageCompression compression,
                  const char* const aName,
                  bool recordWriteLatency)
     : Pickle(MSG_HEADER_SZ, segment_capacity) {
   MOZ_COUNT_CTOR(IPC::Message);
   header()->routing = routing_id;
   header()->type = type;
   header()->flags = nestedLevel;
-  if (priority == HIGH_PRIORITY)
-    header()->flags |= PRIO_BIT;
+  set_priority(priority);
   if (compression == COMPRESSION_ENABLED)
     header()->flags |= COMPRESS_BIT;
   else if (compression == COMPRESSION_ALL)
     header()->flags |= COMPRESSALL_BIT;
 #if defined(OS_POSIX)
   header()->num_fds = 0;
 #endif
   header()->interrupt_remote_stack_depth_guess = static_cast<uint32_t>(-1);
--- a/ipc/chromium/src/chrome/common/ipc_message.h
+++ b/ipc/chromium/src/chrome/common/ipc_message.h
@@ -41,18 +41,19 @@ class Message : public Pickle {
 
   enum NestedLevel {
     NOT_NESTED = 1,
     NESTED_INSIDE_SYNC = 2,
     NESTED_INSIDE_CPOW = 3
   };
 
   enum PriorityValue {
-    NORMAL_PRIORITY,
-    HIGH_PRIORITY,
+    NORMAL_PRIORITY = 0,
+    INPUT_PRIORITY = 1,
+    HIGH_PRIORITY = 2,
   };
 
   enum MessageCompression {
     COMPRESSION_NONE,
     COMPRESSION_ENABLED,
     COMPRESSION_ALL
   };
 
@@ -86,27 +87,22 @@ class Message : public Pickle {
   }
 
   void set_nested_level(NestedLevel nestedLevel) {
     DCHECK((nestedLevel & ~NESTED_MASK) == 0);
     header()->flags = (header()->flags & ~NESTED_MASK) | nestedLevel;
   }
 
   PriorityValue priority() const {
-    if (header()->flags & PRIO_BIT) {
-      return HIGH_PRIORITY;
-    }
-    return NORMAL_PRIORITY;
+    return static_cast<PriorityValue>((header()->flags & PRIO_MASK) >> 2);
   }
 
   void set_priority(PriorityValue prio) {
-    header()->flags &= ~PRIO_BIT;
-    if (prio == HIGH_PRIORITY) {
-      header()->flags |= PRIO_BIT;
-    }
+    DCHECK(((prio << 2) & ~PRIO_MASK) == 0);
+    header()->flags = (header()->flags & ~PRIO_MASK) | (prio << 2);
   }
 
   bool is_constructor() const {
     return (header()->flags & CONSTRUCTOR_BIT) != 0;
   }
 
   void set_constructor() {
     header()->flags |= CONSTRUCTOR_BIT;
@@ -310,26 +306,26 @@ class Message : public Pickle {
 
 #if !defined(OS_MACOSX)
  protected:
 #endif
 
   // flags
   enum {
     NESTED_MASK     = 0x0003,
-    PRIO_BIT        = 0x0004,
-    SYNC_BIT        = 0x0008,
-    REPLY_BIT       = 0x0010,
-    REPLY_ERROR_BIT = 0x0020,
-    INTERRUPT_BIT   = 0x0040,
-    COMPRESS_BIT    = 0x0080,
-    COMPRESSALL_BIT = 0x0100,
-    CONSTRUCTOR_BIT = 0x0200,
+    PRIO_MASK       = 0x000C,
+    SYNC_BIT        = 0x0010,
+    REPLY_BIT       = 0x0020,
+    REPLY_ERROR_BIT = 0x0040,
+    INTERRUPT_BIT   = 0x0080,
+    COMPRESS_BIT    = 0x0100,
+    COMPRESSALL_BIT = 0x0200,
+    CONSTRUCTOR_BIT = 0x0400,
 #ifdef MOZ_TASK_TRACER
-    TASKTRACER_BIT  = 0x0400,
+    TASKTRACER_BIT  = 0x0800,
 #endif
   };
 
   struct Header : Pickle::Header {
     int32_t routing;  // ID of the view that this message is destined for
     msgid_t type;   // specifies the user-defined message type
     uint32_t flags;   // specifies control flags for the message
 #if defined(OS_POSIX)
--- a/ipc/glue/MessageChannel.cpp
+++ b/ipc/glue/MessageChannel.cpp
@@ -1972,18 +1972,30 @@ MessageChannel::MessageTask::Clear()
     mChannel->AssertWorkerThread();
 
     mChannel = nullptr;
 }
 
 NS_IMETHODIMP
 MessageChannel::MessageTask::GetPriority(uint32_t* aPriority)
 {
-  *aPriority = mMessage.priority() == Message::HIGH_PRIORITY ?
-               PRIORITY_HIGH : PRIORITY_NORMAL;
+  switch (mMessage.priority()) {
+  case Message::NORMAL_PRIORITY:
+    *aPriority = PRIORITY_NORMAL;
+    break;
+  case Message::INPUT_PRIORITY:
+    *aPriority = PRIORITY_INPUT;
+    break;
+  case Message::HIGH_PRIORITY:
+    *aPriority = PRIORITY_HIGH;
+    break;
+  default:
+    MOZ_ASSERT(false);
+    break;
+  }
   return NS_OK;
 }
 
 void
 MessageChannel::DispatchMessage(Message &&aMsg)
 {
     AssertWorkerThread();
     mMonitor->AssertCurrentThreadOwns();
--- a/ipc/ipdl/ipdl/ast.py
+++ b/ipc/ipdl/ipdl/ast.py
@@ -4,17 +4,18 @@
 
 import sys
 
 NOT_NESTED = 1
 INSIDE_SYNC_NESTED = 2
 INSIDE_CPOW_NESTED = 3
 
 NORMAL_PRIORITY = 1
-HIGH_PRIORITY = 2
+INPUT_PRIORITY = 2
+HIGH_PRIORITY = 3
 
 class Visitor:
     def defaultVisit(self, node):
         raise Exception, "INTERNAL ERROR: no visitor for node type `%s'"% (
             node.__class__.__name__)
 
     def visitTranslationUnit(self, tu):
         for cxxInc in tu.cxxIncludes:
--- a/ipc/ipdl/ipdl/lower.py
+++ b/ipc/ipdl/ipdl/lower.py
@@ -1722,18 +1722,19 @@ def _generateMessageConstructor(clsname,
     elif nested == ipdl.ast.INSIDE_SYNC_NESTED:
         nestedEnum = 'IPC::Message::NESTED_INSIDE_SYNC'
     else:
         assert nested == ipdl.ast.INSIDE_CPOW_NESTED
         nestedEnum = 'IPC::Message::NESTED_INSIDE_CPOW'
 
     if prio == ipdl.ast.NORMAL_PRIORITY:
         prioEnum = 'IPC::Message::NORMAL_PRIORITY'
+    elif prio == ipdl.ast.INPUT_PRIORITY:
+        prioEnum = 'IPC::Message::INPUT_PRIORITY'
     else:
-        assert prio == ipdl.ast.HIGH_PRIORITY
         prioEnum = 'IPC::Message::HIGH_PRIORITY'
 
     func.addstmt(
         StmtReturn(ExprNew(Type('IPC::Message'),
                            args=[ routingId,
                                   ExprVar(msgid),
                                   ExprLiteral.Int(int(segmentSize)),
                                   ExprVar(nestedEnum),
--- a/ipc/ipdl/ipdl/parser.py
+++ b/ipc/ipdl/ipdl/parser.py
@@ -498,17 +498,18 @@ def p_Nested(p):
     if p[1] not in kinds:
         _error(locFromTok(p, 1), "Expected not, inside_sync, or inside_cpow for nested()")
 
     p[0] = { 'nested': kinds[p[1]] }
 
 def p_Priority(p):
     """Priority : ID"""
     kinds = {'normal': 1,
-             'high': 2}
+             'input': 2,
+             'high': 3}
     if p[1] not in kinds:
         _error(locFromTok(p, 1), "Expected normal or high for prio()")
 
     p[0] = { 'prio': kinds[p[1]] }
 
 def p_SendQualifier(p):
     """SendQualifier : NESTED '(' Nested ')'
                      | PRIO '(' Priority ')'"""
--- a/ipc/ipdl/test/cxx/PTestPriority.ipdl
+++ b/ipc/ipdl/test/cxx/PTestPriority.ipdl
@@ -1,14 +1,17 @@
 namespace mozilla {
 namespace _ipdltest {
 
 sync protocol PTestPriority {
 parent:
-    prio(high) async Msg1();
-    prio(high) sync Msg2();
+    prio(input) async PMsg1();
+    prio(input) sync PMsg2();
+    prio(high) async PMsg3();
+    prio(high) sync PMsg4();
 
 child:
-    prio(high) async Msg3();
+    prio(input) async CMsg1();
+    prio(high) async CMsg2();
 };
 
 } // namespace _ipdltest
 } // namespace mozilla
--- a/js/src/gc/Zone.h
+++ b/js/src/gc/Zone.h
@@ -211,24 +211,21 @@ struct Zone : public JS::shadow::Zone,
     void unscheduleGC() { gcScheduled_ = false; }
     bool isGCScheduled() { return gcScheduled_ && canCollect(); }
 
     void setPreservingCode(bool preserving) { gcPreserveCode_ = preserving; }
     bool isPreservingCode() const { return gcPreserveCode_; }
 
     bool canCollect();
 
-    void notifyObservingDebuggers();
-
-    void setGCState(GCState state) {
+    void changeGCState(GCState prev, GCState next) {
         MOZ_ASSERT(CurrentThreadIsHeapBusy());
-        MOZ_ASSERT_IF(state != NoGC, canCollect());
-        gcState_ = state;
-        if (state == Finished)
-            notifyObservingDebuggers();
+        MOZ_ASSERT(gcState() == prev);
+        MOZ_ASSERT_IF(next != NoGC, canCollect());
+        gcState_ = next;
     }
 
     bool isCollecting() const {
         MOZ_ASSERT(CurrentThreadCanAccessRuntime(runtimeFromActiveCooperatingThread()));
         return isCollectingFromAnyThread();
     }
 
     bool isCollectingFromAnyThread() const {
@@ -297,16 +294,18 @@ struct Zone : public JS::shadow::Zone,
 
     js::gc::UniqueIdMap& uniqueIds() { return uniqueIds_.ref(); }
 
   public:
     bool hasDebuggers() const { return debuggers && debuggers->length(); }
     DebuggerVector* getDebuggers() const { return debuggers; }
     DebuggerVector* getOrCreateDebuggers(JSContext* cx);
 
+    void notifyObservingDebuggers();
+
     void clearTables();
 
     /*
      * When true, skip calling the metadata callback. We use this:
      * - to avoid invoking the callback recursively;
      * - to avoid observing lazy prototype setup (which confuses callbacks that
      *   want to use the types being set up!);
      * - to avoid attaching allocation stacks to allocation stack nodes, which
--- a/js/src/jsgc.cpp
+++ b/js/src/jsgc.cpp
@@ -3871,17 +3871,17 @@ GCRuntime::prepareZonesForCollection(JS:
 
     int64_t currentTime = PRMJ_Now();
 
     for (ZonesIter zone(rt, WithAtoms); !zone.done(); zone.next()) {
         /* Set up which zones will be collected. */
         if (ShouldCollectZone(zone, reason)) {
             if (!zone->isAtomsZone()) {
                 any = true;
-                zone->setGCState(Zone::Mark);
+                zone->changeGCState(Zone::NoGC, Zone::Mark);
             }
         } else {
             *isFullOut = false;
         }
 
         zone->setPreservingCode(false);
     }
 
@@ -3919,17 +3919,17 @@ GCRuntime::prepareZonesForCollection(JS:
      * Otherwise, we always schedule a GC in the atoms zone so that atoms which
      * the other collected zones are using are marked, and we can update the
      * set of atoms in use by the other collected zones at the end of the GC.
      */
     if (!TlsContext.get()->keepAtoms || rt->hasHelperThreadZones()) {
         Zone* atomsZone = rt->atomsCompartment(lock)->zone();
         if (atomsZone->isGCScheduled()) {
             MOZ_ASSERT(!atomsZone->isCollecting());
-            atomsZone->setGCState(Zone::Mark);
+            atomsZone->changeGCState(Zone::NoGC, Zone::Mark);
             any = true;
         }
     }
 
     /* Check that at least one zone is scheduled for collection. */
     return any;
 }
 
@@ -4385,30 +4385,26 @@ js::gc::MarkingValidator::nonIncremental
     gc->incrementalState = State::Sweep;
     {
         gcstats::AutoPhase ap1(gc->stats(), gcstats::PhaseKind::SWEEP);
         gcstats::AutoPhase ap2(gc->stats(), gcstats::PhaseKind::SWEEP_MARK);
 
         gc->markAllWeakReferences(gcstats::PhaseKind::SWEEP_MARK_WEAK);
 
         /* Update zone state for gray marking. */
-        for (GCZonesIter zone(runtime); !zone.done(); zone.next()) {
-            MOZ_ASSERT(zone->isGCMarkingBlack());
-            zone->setGCState(Zone::MarkGray);
-        }
+        for (GCZonesIter zone(runtime); !zone.done(); zone.next())
+            zone->changeGCState(Zone::Mark, Zone::MarkGray);
         gc->marker.setMarkColorGray();
 
         gc->markAllGrayReferences(gcstats::PhaseKind::SWEEP_MARK_GRAY);
         gc->markAllWeakReferences(gcstats::PhaseKind::SWEEP_MARK_GRAY_WEAK);
 
         /* Restore zone state. */
-        for (GCZonesIter zone(runtime); !zone.done(); zone.next()) {
-            MOZ_ASSERT(zone->isGCMarkingGray());
-            zone->setGCState(Zone::Mark);
-        }
+        for (GCZonesIter zone(runtime); !zone.done(); zone.next())
+            zone->changeGCState(Zone::MarkGray, Zone::Mark);
         MOZ_ASSERT(gc->marker.isDrained());
         gc->marker.setMarkColorBlack();
     }
 
     /* Take a copy of the non-incremental mark state and restore the original. */
     for (auto chunk = gc->allNonEmptyChunks(); !chunk.done(); chunk.next()) {
         ChunkBitmap* bitmap = &chunk->bitmap;
         ChunkBitmap* entry = map.lookup(chunk)->value();
@@ -4706,19 +4702,18 @@ GCRuntime::getNextSweepGroup()
 
     if (!isIncremental)
         ZoneComponentFinder::mergeGroups(currentSweepGroup);
 
     if (abortSweepAfterCurrentGroup) {
         MOZ_ASSERT(!isIncremental);
         for (GCSweepGroupIter zone(rt); !zone.done(); zone.next()) {
             MOZ_ASSERT(!zone->gcNextGraphComponent);
-            MOZ_ASSERT(zone->isGCMarking());
             zone->setNeedsIncrementalBarrier(false);
-            zone->setGCState(Zone::NoGC);
+            zone->changeGCState(Zone::Mark, Zone::NoGC);
             zone->gcGrayRoots().clearAndFree();
         }
 
         for (GCCompartmentGroupIter comp(rt); !comp.done(); comp.next())
             ResetGrayList(comp);
 
         abortSweepAfterCurrentGroup = false;
         currentSweepGroup = nullptr;
@@ -4979,34 +4974,30 @@ GCRuntime::endMarkingSweepGroup()
     markWeakReferencesInCurrentGroup(gcstats::PhaseKind::SWEEP_MARK_WEAK);
 
     /*
      * Change state of current group to MarkGray to restrict marking to this
      * group.  Note that there may be pointers to the atoms compartment, and
      * these will be marked through, as they are not marked with
      * MarkCrossCompartmentXXX.
      */
-    for (GCSweepGroupIter zone(rt); !zone.done(); zone.next()) {
-        MOZ_ASSERT(zone->isGCMarkingBlack());
-        zone->setGCState(Zone::MarkGray);
-    }
+    for (GCSweepGroupIter zone(rt); !zone.done(); zone.next())
+        zone->changeGCState(Zone::Mark, Zone::MarkGray);
     marker.setMarkColorGray();
 
     /* Mark incoming gray pointers from previously swept compartments. */
     MarkIncomingCrossCompartmentPointers(rt, MarkColor::Gray);
 
     /* Mark gray roots and mark transitively inside the current compartment group. */
     markGrayReferencesInCurrentGroup(gcstats::PhaseKind::SWEEP_MARK_GRAY);
     markWeakReferencesInCurrentGroup(gcstats::PhaseKind::SWEEP_MARK_GRAY_WEAK);
 
     /* Restore marking state. */
-    for (GCSweepGroupIter zone(rt); !zone.done(); zone.next()) {
-        MOZ_ASSERT(zone->isGCMarkingGray());
-        zone->setGCState(Zone::Mark);
-    }
+    for (GCSweepGroupIter zone(rt); !zone.done(); zone.next())
+        zone->changeGCState(Zone::MarkGray, Zone::Mark);
     MOZ_ASSERT(marker.isDrained());
     marker.setMarkColorBlack();
 }
 
 // Causes the given WeakCache to be swept when run.
 class ImmediateSweepWeakCacheTask : public GCParallelTask
 {
     JS::detail::WeakCacheBase& cache;
@@ -5301,18 +5292,17 @@ GCRuntime::beginSweepingSweepGroup()
 
     using namespace gcstats;
 
     AutoSCC scc(stats(), sweepGroupIndex);
 
     bool sweepingAtoms = false;
     for (GCSweepGroupIter zone(rt); !zone.done(); zone.next()) {
         /* Set the GC state to sweeping. */
-        MOZ_ASSERT(zone->isGCMarking());
-        zone->setGCState(Zone::Sweep);
+        zone->changeGCState(Zone::Mark, Zone::Sweep);
 
         /* Purge the ArenaLists before sweeping. */
         zone->arenas.purge();
 
         if (zone->isAtomsZone())
             sweepingAtoms = true;
 
 #ifdef DEBUG
@@ -5407,19 +5397,18 @@ GCRuntime::endSweepingSweepGroup()
     {
         gcstats::AutoPhase ap(stats(), gcstats::PhaseKind::FINALIZE_END);
         FreeOp fop(rt);
         callFinalizeCallbacks(&fop, JSFINALIZE_GROUP_END);
     }
 
     /* Update the GC state for zones we have swept. */
     for (GCSweepGroupIter zone(rt); !zone.done(); zone.next()) {
-        MOZ_ASSERT(zone->isGCSweeping());
         AutoLockGC lock(rt);
-        zone->setGCState(Zone::Finished);
+        zone->changeGCState(Zone::Sweep, Zone::Finished);
         zone->threshold.updateAfterGC(zone->usage.gcBytes(), invocationKind, tunables,
                                       schedulingState, lock);
     }
 
     /* Start background thread to sweep zones if required. */
     ZoneList zones;
     for (GCSweepGroupIter zone(rt); !zone.done(); zone.next())
         zones.append(zone);
@@ -6081,37 +6070,36 @@ GCRuntime::compactPhase(JS::gcreason::Re
     ZoneList relocatedZones;
     Arena* relocatedArenas = nullptr;
     while (!zonesToMaybeCompact.ref().isEmpty()) {
 
         Zone* zone = zonesToMaybeCompact.ref().front();
         zonesToMaybeCompact.ref().removeFront();
 
         MOZ_ASSERT(zone->group()->nursery().isEmpty());
-        MOZ_ASSERT(zone->isGCFinished());
-        zone->setGCState(Zone::Compact);
+        zone->changeGCState(Zone::Finished, Zone::Compact);
 
         if (relocateArenas(zone, reason, relocatedArenas, sliceBudget)) {
             updateZonePointersToRelocatedCells(zone, lock);
             relocatedZones.append(zone);
         } else {
-            zone->setGCState(Zone::Finished);
+            zone->changeGCState(Zone::Compact, Zone::Finished);
         }
 
         if (sliceBudget.isOverBudget())
             break;
     }
 
     if (!relocatedZones.isEmpty()) {
         updateRuntimePointersToRelocatedCells(lock);
 
         do {
             Zone* zone = relocatedZones.front();
             relocatedZones.removeFront();
-            zone->setGCState(Zone::Finished);
+            zone->changeGCState(Zone::Compact, Zone::Finished);
         }
         while (!relocatedZones.isEmpty());
     }
 
     if (ShouldProtectRelocatedArenas(reason))
         protectAndHoldArenas(relocatedArenas);
     else
         releaseRelocatedArenas(relocatedArenas);
@@ -6143,18 +6131,18 @@ GCRuntime::finishCollection(JS::gcreason
     clearBufferedGrayRoots();
     MemProfiler::SweepTenured(rt);
 
     uint64_t currentTime = PRMJ_Now();
     schedulingState.updateHighFrequencyMode(lastGCTime, currentTime, tunables);
 
     for (ZonesIter zone(rt, WithAtoms); !zone.done(); zone.next()) {
         if (zone->isCollecting()) {
-            MOZ_ASSERT(zone->isGCFinished());
-            zone->setGCState(Zone::NoGC);
+            zone->changeGCState(Zone::Finished, Zone::NoGC);
+            zone->notifyObservingDebuggers();
         }
 
         MOZ_ASSERT(!zone->isCollectingFromAnyThread());
         MOZ_ASSERT(!zone->wasGCStarted());
     }
 
     MOZ_ASSERT(zonesToMaybeCompact.ref().isEmpty());
 
@@ -6248,19 +6236,18 @@ GCRuntime::resetIncrementalGC(gc::AbortR
         marker.reset();
         marker.stop();
         clearBufferedGrayRoots();
 
         for (GCCompartmentsIter c(rt); !c.done(); c.next())
             ResetGrayList(c);
 
         for (GCZonesIter zone(rt); !zone.done(); zone.next()) {
-            MOZ_ASSERT(zone->isGCMarking());
             zone->setNeedsIncrementalBarrier(false);
-            zone->setGCState(Zone::NoGC);
+            zone->changeGCState(Zone::Mark, Zone::NoGC);
         }
 
         blocksToFreeAfterSweeping.ref().freeAll();
 
         incrementalState = State::NotActive;
 
         MOZ_ASSERT(!marker.shouldCheckCompartments());
 
--- a/layout/base/nsRefreshDriver.cpp
+++ b/layout/base/nsRefreshDriver.cpp
@@ -250,16 +250,23 @@ public:
       return aDefault;
     }
 
     idleEnd = idleEnd - TimeDuration::FromMilliseconds(
       nsLayoutUtils::IdlePeriodDeadlineLimit());
     return idleEnd < aDefault ? idleEnd : aDefault;
   }
 
+  Maybe<TimeStamp> GetNextTickHint()
+  {
+    MOZ_ASSERT(NS_IsMainThread());
+    TimeStamp nextTick = MostRecentRefresh() + GetTimerRate();
+    return nextTick < TimeStamp::Now() ? Nothing() : Some(nextTick);
+  }
+
 protected:
   virtual void StartTimer() = 0;
   virtual void StopTimer() = 0;
   virtual void ScheduleNextTick(TimeStamp aNowTime) = 0;
 
   bool IsRootRefreshDriver(nsRefreshDriver* aDriver)
   {
     nsPresContext* pc = aDriver->GetPresContext();
@@ -2398,16 +2405,27 @@ nsRefreshDriver::GetIdleDeadlineHint(Tim
   // sRegularRateTimer, since we consider refresh drivers attached to
   // sThrottledRateTimer to be inactive. This implies that tasks
   // resulting from a tick on the sRegularRateTimer counts as being
   // busy but tasks resulting from a tick on sThrottledRateTimer
   // counts as being idle.
   return sRegularRateTimer->GetIdleDeadlineHint(aDefault);
 }
 
+/* static */ Maybe<TimeStamp>
+nsRefreshDriver::GetNextTickHint()
+{
+  MOZ_ASSERT(NS_IsMainThread());
+
+  if (!sRegularRateTimer) {
+    return Nothing();
+  }
+  return sRegularRateTimer->GetNextTickHint();
+}
+
 void
 nsRefreshDriver::Disconnect()
 {
   MOZ_ASSERT(NS_IsMainThread());
 
   StopTimer();
 
   if (mPresContext) {
--- a/layout/base/nsRefreshDriver.h
+++ b/layout/base/nsRefreshDriver.h
@@ -340,16 +340,22 @@ public:
    * expected deadline. If the next expected deadline is later than
    * the default value, the default value is returned.
    *
    * If we're animating and we have skipped paints a time in the past
    * is returned.
    */
   static mozilla::TimeStamp GetIdleDeadlineHint(mozilla::TimeStamp aDefault);
 
+  /**
+   * It returns the expected timestamp of the next tick or nothing if the next
+   * tick is missed.
+   */
+  static mozilla::Maybe<mozilla::TimeStamp> GetNextTickHint();
+
   static void DispatchIdleRunnableAfterTick(nsIRunnable* aRunnable,
                                             uint32_t aDelay);
   static void CancelIdleRunnable(nsIRunnable* aRunnable);
 
   bool SkippedPaints() const
   {
     return mSkippedPaints;
   }
--- a/layout/painting/nsDisplayList.cpp
+++ b/layout/painting/nsDisplayList.cpp
@@ -480,35 +480,34 @@ SetAnimatable(nsCSSPropertyID aProperty,
     }
     default:
       MOZ_ASSERT_UNREACHABLE("Unsupported property");
   }
 }
 
 static void
 AddAnimationForProperty(nsIFrame* aFrame, const AnimationProperty& aProperty,
-                        dom::Animation* aAnimation, Layer* aLayer,
+                        dom::Animation* aAnimation, AnimationInfo& aAnimationInfo,
                         AnimationData& aData, bool aPending)
 {
-  MOZ_ASSERT(aLayer->AsContainerLayer(), "Should only animate ContainerLayer");
   MOZ_ASSERT(aAnimation->GetEffect(),
              "Should not be adding an animation without an effect");
   MOZ_ASSERT(!aAnimation->GetCurrentOrPendingStartTime().IsNull() ||
              !aAnimation->IsPlaying() ||
              (aAnimation->GetTimeline() &&
               aAnimation->GetTimeline()->TracksWallclockTime()),
              "If the animation has an unresolved start time it should either"
              " be static (so we don't need a start time) or else have a"
              " timeline capable of converting TimeStamps (so we can calculate"
              " one later");
 
   layers::Animation* animation =
     aPending ?
-    aLayer->AddAnimationForNextTransaction() :
-    aLayer->AddAnimation();
+    aAnimationInfo.AddAnimationForNextTransaction() :
+    aAnimationInfo.AddAnimation();
 
   const TimingParams& timing = aAnimation->GetEffect()->SpecifiedTiming();
 
   // If we are starting a new transition that replaces an existing transition
   // running on the compositor, it is possible that the animation on the
   // compositor will have advanced ahead of the main thread. If we use as
   // the starting point of the new transition, the current value of the
   // replaced transition as calculated on the main thread using the refresh
@@ -596,32 +595,108 @@ AddAnimationForProperty(nsIFrame* aFrame
       static_cast<uint8_t>(segment.mFromComposite);
     animSegment->endComposite() =
       static_cast<uint8_t>(segment.mToComposite);
     animSegment->sampleFn() = ToTimingFunction(segment.mTimingFunction);
   }
 }
 
 static void
-AddAnimationsForProperty(nsIFrame* aFrame, nsCSSPropertyID aProperty,
-                         nsTArray<RefPtr<dom::Animation>>& aAnimations,
-                         Layer* aLayer, AnimationData& aData,
-                         bool aPending)
-{
+AddAnimationsForProperty(nsIFrame* aFrame, nsDisplayListBuilder* aBuilder,
+                         nsDisplayItem* aItem, nsCSSPropertyID aProperty,
+                         AnimationInfo& aAnimationInfo, bool aPending)
+{
+  if (aPending) {
+    aAnimationInfo.ClearAnimationsForNextTransaction();
+  } else {
+    aAnimationInfo.ClearAnimations();
+  }
+
+  // Update the animation generation on the layer. We need to do this before
+  // any early returns since even if we don't add any animations to the
+  // layer, we still need to mark it as up-to-date with regards to animations.
+  // Otherwise, in RestyleManager we'll notice the discrepancy between the
+  // animation generation numbers and update the layer indefinitely.
+  uint64_t animationGeneration =
+    RestyleManager::GetAnimationGenerationForFrame(aFrame);
+  aAnimationInfo.SetAnimationGeneration(animationGeneration);
+
+  EffectCompositor::ClearIsRunningOnCompositor(aFrame, aProperty);
+  nsTArray<RefPtr<dom::Animation>> compositorAnimations =
+    EffectCompositor::GetAnimationsForCompositor(aFrame, aProperty);
+  if (compositorAnimations.IsEmpty()) {
+    return;
+  }
+
+  // If the frame is not prerendered, bail out.
+  // Do this check only during layer construction; during updating the
+  // caller is required to check it appropriately.
+  if (aItem && !aItem->CanUseAsyncAnimations(aBuilder)) {
+    // EffectCompositor needs to know that we refused to run this animation
+    // asynchronously so that it will not throttle the main thread
+    // animation.
+    aFrame->SetProperty(nsIFrame::RefusedAsyncAnimationProperty(), true);
+
+    // We need to schedule another refresh driver run so that EffectCompositor
+    // gets a chance to unthrottle the animation.
+    aFrame->SchedulePaint();
+    return;
+  }
+
+  AnimationData data;
+  if (aProperty == eCSSProperty_transform) {
+    // XXX Performance here isn't ideal for SVG. We'd prefer to avoid resolving
+    // the dimensions of refBox. That said, we only get here if there are CSS
+    // animations or transitions on this element, and that is likely to be a
+    // lot rarer than transforms on SVG (the frequency of which drives the need
+    // for TransformReferenceBox).
+    TransformReferenceBox refBox(aFrame);
+    nsRect bounds(0, 0, refBox.Width(), refBox.Height());
+    // all data passed directly to the compositor should be in dev pixels
+    int32_t devPixelsToAppUnits = aFrame->PresContext()->AppUnitsPerDevPixel();
+    float scale = devPixelsToAppUnits;
+    Point3D offsetToTransformOrigin =
+      nsDisplayTransform::GetDeltaToTransformOrigin(aFrame, scale, &bounds);
+    nsPoint origin;
+    float scaleX = 1.0f;
+    float scaleY = 1.0f;
+    bool hasPerspectiveParent = false;
+    if (aItem) {
+      // This branch is for display items to leverage the cache of
+      // nsDisplayListBuilder.
+      origin = aItem->ToReferenceFrame();
+    } else {
+      // This branch is running for restyling.
+      // Animations are animated at the coordination of the reference
+      // frame outside, not the given frame itself.  The given frame
+      // is also reference frame too, so the parent's reference frame
+      // are used.
+      nsIFrame* referenceFrame =
+        nsLayoutUtils::GetReferenceFrame(nsLayoutUtils::GetCrossDocParentFrame(aFrame));
+      origin = aFrame->GetOffsetToCrossDoc(referenceFrame);
+    }
+
+    data = TransformData(origin, offsetToTransformOrigin,
+                         bounds, devPixelsToAppUnits,
+                         scaleX, scaleY, hasPerspectiveParent);
+  } else if (aProperty == eCSSProperty_opacity) {
+    data = null_t();
+  }
+
   MOZ_ASSERT(nsCSSProps::PropHasFlags(aProperty,
                                       CSS_PROPERTY_CAN_ANIMATE_ON_COMPOSITOR),
              "inconsistent property flags");
 
   EffectSet* effects = EffectSet::GetEffectSet(aFrame);
   MOZ_ASSERT(effects);
 
   bool sentAnimations = false;
   // Add from first to last (since last overrides)
-  for (size_t animIdx = 0; animIdx < aAnimations.Length(); animIdx++) {
-    dom::Animation* anim = aAnimations[animIdx];
+  for (size_t animIdx = 0; animIdx < compositorAnimations.Length(); animIdx++) {
+    dom::Animation* anim = compositorAnimations[animIdx];
     if (!anim->IsRelevant()) {
       continue;
     }
 
     dom::KeyframeEffectReadOnly* keyframeEffect =
       anim->GetEffect() ? anim->GetEffect()->AsKeyframeEffect() : nullptr;
     MOZ_ASSERT(keyframeEffect,
                "A playing animation should have a keyframe effect");
@@ -653,17 +728,17 @@ AddAnimationsForProperty(nsIFrame* aFram
     // driver under test control. In this case, the next time the refresh
     // driver is advanced it will trigger any pending animations.
     if (anim->PlayState() == AnimationPlayState::Pending &&
         (anim->GetTimeline() &&
          !anim->GetTimeline()->TracksWallclockTime())) {
       continue;
     }
 
-    AddAnimationForProperty(aFrame, *property, anim, aLayer, aData, aPending);
+    AddAnimationForProperty(aFrame, *property, anim, aAnimationInfo, data, aPending);
     keyframeEffect->SetIsRunningOnCompositor(aProperty, true);
     sentAnimations = true;
   }
 
   if (sentAnimations && aProperty == eCSSProperty_transform) {
     TimeStamp now = aFrame->PresContext()->RefreshDriver()->MostRecentRefresh();
     effects->UpdateLastTransformSyncTime(now);
   }
@@ -772,96 +847,20 @@ nsDisplayListBuilder::AddAnimationsAndTr
   // off-main-thread compositing.
   LayersBackend backend = aLayer->Manager()->GetBackendType();
   if (!(backend == layers::LayersBackend::LAYERS_CLIENT ||
         backend == layers::LayersBackend::LAYERS_WR)) {
     return;
   }
 
   bool pending = !aBuilder;
-
-  if (pending) {
-    aLayer->ClearAnimationsForNextTransaction();
-  } else {
-    aLayer->ClearAnimations();
-  }
-
-  // Update the animation generation on the layer. We need to do this before
-  // any early returns since even if we don't add any animations to the
-  // layer, we still need to mark it as up-to-date with regards to animations.
-  // Otherwise, in RestyleManager we'll notice the discrepancy between the
-  // animation generation numbers and update the layer indefinitely.
-  uint64_t animationGeneration =
-    RestyleManager::GetAnimationGenerationForFrame(aFrame);
-  aLayer->SetAnimationGeneration(animationGeneration);
-
-  EffectCompositor::ClearIsRunningOnCompositor(aFrame, aProperty);
-  nsTArray<RefPtr<dom::Animation>> compositorAnimations =
-    EffectCompositor::GetAnimationsForCompositor(aFrame, aProperty);
-  if (compositorAnimations.IsEmpty()) {
-    return;
-  }
-
-  // If the frame is not prerendered, bail out.
-  // Do this check only during layer construction; during updating the
-  // caller is required to check it appropriately.
-  if (aItem && !aItem->CanUseAsyncAnimations(aBuilder)) {
-    // EffectCompositor needs to know that we refused to run this animation
-    // asynchronously so that it will not throttle the main thread
-    // animation.
-    aFrame->SetProperty(nsIFrame::RefusedAsyncAnimationProperty(), true);
-
-    // We need to schedule another refresh driver run so that EffectCompositor
-    // gets a chance to unthrottle the animation.
-    aFrame->SchedulePaint();
-    return;
-  }
-
-  AnimationData data;
-  if (aProperty == eCSSProperty_transform) {
-    // XXX Performance here isn't ideal for SVG. We'd prefer to avoid resolving
-    // the dimensions of refBox. That said, we only get here if there are CSS
-    // animations or transitions on this element, and that is likely to be a
-    // lot rarer than transforms on SVG (the frequency of which drives the need
-    // for TransformReferenceBox).
-    TransformReferenceBox refBox(aFrame);
-    nsRect bounds(0, 0, refBox.Width(), refBox.Height());
-    // all data passed directly to the compositor should be in dev pixels
-    int32_t devPixelsToAppUnits = aFrame->PresContext()->AppUnitsPerDevPixel();
-    float scale = devPixelsToAppUnits;
-    Point3D offsetToTransformOrigin =
-      nsDisplayTransform::GetDeltaToTransformOrigin(aFrame, scale, &bounds);
-    nsPoint origin;
-    float scaleX = 1.0f;
-    float scaleY = 1.0f;
-    bool hasPerspectiveParent = false;
-    if (aItem) {
-      // This branch is for display items to leverage the cache of
-      // nsDisplayListBuilder.
-      origin = aItem->ToReferenceFrame();
-    } else {
-      // This branch is running for restyling.
-      // Animations are animated at the coordination of the reference
-      // frame outside, not the given frame itself.  The given frame
-      // is also reference frame too, so the parent's reference frame
-      // are used.
-      nsIFrame* referenceFrame =
-        nsLayoutUtils::GetReferenceFrame(nsLayoutUtils::GetCrossDocParentFrame(aFrame));
-      origin = aFrame->GetOffsetToCrossDoc(referenceFrame);
-    }
-
-    data = TransformData(origin, offsetToTransformOrigin,
-                         bounds, devPixelsToAppUnits,
-                         scaleX, scaleY, hasPerspectiveParent);
-  } else if (aProperty == eCSSProperty_opacity) {
-    data = null_t();
-  }
-
-  AddAnimationsForProperty(aFrame, aProperty, compositorAnimations,
-                           aLayer, data, pending);
+  AnimationInfo& animationInfo = aLayer->GetAnimationInfo();
+  AddAnimationsForProperty(aFrame, aBuilder, aItem, aProperty,
+                           animationInfo, pending);
+  animationInfo.TransferMutatedFlagToLayer(aLayer);
 }
 
 void
 nsDisplayListBuilder::AutoCurrentActiveScrolledRootSetter::InsertScrollFrame(nsIScrollableFrame* aScrollableFrame)
 {
   MOZ_ASSERT(!mUsed);
   size_t descendantsEndIndex = mBuilder->mActiveScrolledRoots.Length();
   const ActiveScrolledRoot* parentASR = mBuilder->mCurrentActiveScrolledRoot;
@@ -6066,16 +6065,69 @@ bool nsDisplayOpacity::TryMerge(nsDispla
 }
 
 void
 nsDisplayOpacity::WriteDebugInfo(std::stringstream& aStream)
 {
   aStream << " (opacity " << mOpacity << ")";
 }
 
+bool
+nsDisplayOpacity::CreateWebRenderCommands(mozilla::wr::DisplayListBuilder& aBuilder,
+                                          const StackingContextHelper& aSc,
+                                          nsTArray<WebRenderParentCommand>& aParentCommands,
+                                          mozilla::layers::WebRenderLayerManager* aManager,
+                                          nsDisplayListBuilder* aDisplayListBuilder)
+{
+  nsRect itemBounds = mList.GetClippedBoundsWithRespectToASR(aDisplayListBuilder, mActiveScrolledRoot);
+  nsRect childrenVisible = GetVisibleRectForChildren();
+  nsRect visibleRect = itemBounds.Intersect(childrenVisible);
+  float appUnitsPerDevPixel = mFrame->PresContext()->AppUnitsPerDevPixel();
+  LayerRect bounds = ViewAs<LayerPixel>(LayoutDeviceRect::FromAppUnits(visibleRect, appUnitsPerDevPixel),
+                                        PixelCastJustification::WebRenderHasUnitResolution);
+  LayerPoint origin = bounds.TopLeft();
+  float* opacityForSC = &mOpacity;
+
+  RefPtr<WebRenderAnimationData> animationData = aManager->CreateOrRecycleWebRenderUserData<WebRenderAnimationData>(this);
+  AnimationInfo& animationInfo = animationData->GetAnimationInfo();
+  AddAnimationsForProperty(Frame(), aDisplayListBuilder,
+                           this, eCSSProperty_opacity,
+                           animationInfo, false);
+  animationInfo.StartPendingAnimations(aManager->GetAnimationReadyTime());
+  uint64_t animationsId = 0;
+
+  if (gfxPrefs::WebRenderOMTAEnabled() &&
+      !animationInfo.GetAnimations().IsEmpty()) {
+    animationsId = animationInfo.GetCompositorAnimationsId();
+    opacityForSC = nullptr;
+    OptionalOpacity opacityForCompositor = mOpacity;
+
+    OpAddCompositorAnimations
+      anim(CompositorAnimations(animationInfo.GetAnimations(), animationsId),
+           void_t(), opacityForCompositor);
+    aManager->WrBridge()->AddWebRenderParentCommand(anim);
+  }
+
+  nsTArray<mozilla::wr::WrFilterOp> filters;
+  StackingContextHelper sc(aSc,
+                           aBuilder,
+                           bounds,
+                           origin,
+                           animationsId,
+                           opacityForSC,
+                           nullptr,
+                           filters);
+
+  aManager->CreateWebRenderCommandsFromDisplayList(&mList,
+                                                   aDisplayListBuilder,
+                                                   sc,
+                                                   aBuilder);
+  return true;
+}
+
 nsDisplayBlendMode::nsDisplayBlendMode(nsDisplayListBuilder* aBuilder,
                                              nsIFrame* aFrame, nsDisplayList* aList,
                                              uint8_t aBlendMode,
                                              const ActiveScrolledRoot* aActiveScrolledRoot,
                                              uint32_t aIndex)
   : nsDisplayWrapList(aBuilder, aFrame, aList, aActiveScrolledRoot)
   , mBlendMode(aBlendMode)
   , mIndex(aIndex)
@@ -7621,19 +7673,45 @@ nsDisplayTransform::CreateWebRenderComma
   boundTransform._41 = 0.0f;
   boundTransform._42 = 0.0f;
   boundTransform._43 = 0.0f;
   if (!boundTransform.IsIdentity()) {
     // WR will only apply the 'translate' of the transform, so we need to do the scale/rotation manually.
     bounds.MoveTo(boundTransform.TransformPoint(bounds.TopLeft()));
   }
 
-  // TODO: generate animationsId for OMTA.
+  RefPtr<WebRenderAnimationData> animationData = aManager->CreateOrRecycleWebRenderUserData<WebRenderAnimationData>(this);
+
+  AnimationInfo& animationInfo = animationData->GetAnimationInfo();
+  AddAnimationsForProperty(Frame(), aDisplayListBuilder,
+                           this, eCSSProperty_transform,
+                           animationInfo, false);
+  animationInfo.StartPendingAnimations(aManager->GetAnimationReadyTime());
   uint64_t animationsId = 0;
-  nsTArray<wr::WrFilterOp> filters;
+
+  if (gfxPrefs::WebRenderOMTAEnabled() &&
+      !animationInfo.GetAnimations().IsEmpty()) {
+    animationsId = animationInfo.GetCompositorAnimationsId();
+
+    // Update transfrom as nullptr in stacking context if there exists
+    // transform animation, the transform value will be resolved
+    // after animation sampling on the compositor
+    transformForSC = nullptr;
+
+    // Pass default transform to compositor in case gecko fails to
+    // get animated value after animation sampling.
+    OptionalTransform transformForCompositor = newTransformMatrix;
+
+    OpAddCompositorAnimations
+      anim(CompositorAnimations(animationInfo.GetAnimations(), animationsId),
+           transformForCompositor, void_t());
+    aManager->WrBridge()->AddWebRenderParentCommand(anim);
+  }
+
+  nsTArray<mozilla::wr::WrFilterOp> filters;
   StackingContextHelper sc(aSc,
                            aBuilder,
                            bounds,
                            origin,
                            animationsId,
                            nullptr,
                            transformForSC,
                            filters);
--- a/layout/painting/nsDisplayList.h
+++ b/layout/painting/nsDisplayList.h
@@ -3978,16 +3978,22 @@ public:
   virtual bool CanApplyOpacity() const override;
   virtual bool ShouldFlattenAway(nsDisplayListBuilder* aBuilder) override;
   static bool NeedsActiveLayer(nsDisplayListBuilder* aBuilder, nsIFrame* aFrame);
   NS_DISPLAY_DECL_NAME("Opacity", TYPE_OPACITY)
   virtual void WriteDebugInfo(std::stringstream& aStream) override;
 
   bool CanUseAsyncAnimations(nsDisplayListBuilder* aBuilder) override;
 
+  virtual bool CreateWebRenderCommands(mozilla::wr::DisplayListBuilder& aBuilder,
+                                       const StackingContextHelper& aSc,
+                                       nsTArray<WebRenderParentCommand>& aParentCommands,
+                                       mozilla::layers::WebRenderLayerManager* aManager,
+                                       nsDisplayListBuilder* aDisplayListBuilder) override;
+
 private:
   float mOpacity;
   bool mForEventsAndPluginsOnly;
 };
 
 class nsDisplayBlendMode : public nsDisplayWrapList {
 public:
   nsDisplayBlendMode(nsDisplayListBuilder* aBuilder, nsIFrame* aFrame,
--- a/media/webrtc/signaling/src/sdp/sipcc/sdp_attr.c
+++ b/media/webrtc/signaling/src/sdp/sipcc/sdp_attr.c
@@ -1348,17 +1348,22 @@ sdp_result_e sdp_parse_attr_fmtp (sdp_t 
                                        &result1);
           if (result1 != SDP_SUCCESS) {
             fmtp_ptr = sdp_getnextstrtok(fmtp_ptr, tmp, sizeof(tmp), " \t", &result1);
             if (result1 != SDP_SUCCESS) {
               // hmmm, no ; or spaces or tabs; continue on
             }
           }
         }
-        fmtp_ptr++;
+        if (*fmtp_ptr == '\n') {
+          // reached end of line, stop parsing
+          done = TRUE;
+        } else {
+          fmtp_ptr++;
+        }
       } else {
           done = TRUE;
       }
     } /* while  - done loop*/
 
     if (codec_info_found) {
 
         if (sdp_p->debug_flag[SDP_DEBUG_TRACE]) {
--- a/modules/libpref/init/all.js
+++ b/modules/libpref/init/all.js
@@ -3104,16 +3104,36 @@ pref("dom.max_script_run_time", 10);
 pref("dom.global_stop_script", true);
 
 // Time (milliseconds) between throttled idle callbacks.
 pref("dom.idle_period.throttled_length", 10000);
 
 // The amount of idle time (milliseconds) reserved for a long idle period
 pref("idle_queue.long_period", 50);
 
+// Control the event prioritization on content main thread
+#ifdef NIGHTLY_BUILD
+pref("prioritized_input_events.enabled", true);
+#else
+pref("prioritized_input_events.enabled", false);
+#endif
+
+// The maximum and minimum time (milliseconds) we reserve for handling input
+// events in each frame.
+pref("prioritized_input_events.duration.max", 8);
+pref("prioritized_input_events.duration.min", 1);
+
+// The default amount of time (milliseconds) required for handling a input
+// event.
+pref("prioritized_input_events.default_duration_per_event", 1);
+
+// The number of processed input events we use to predict the amount of time
+// required to process the following input events.
+pref("prioritized_input_events.count_for_prediction", 9);
+
 // The minimum amount of time (milliseconds) required for an idle
 // period to be scheduled on the main thread. N.B. that
 // layout.idle_period.time_limit adds padding at the end of the idle
 // period, which makes the point in time that we expect to become busy
 // again be:
 // now + idle_queue.min_period + layout.idle_period.time_limit
 pref("idle_queue.min_period", 3);
 
--- a/testing/mochitest/tests/SimpleTest/EventUtils.js
+++ b/testing/mochitest/tests/SimpleTest/EventUtils.js
@@ -673,16 +673,54 @@ function synthesizeNativeMouseMove(aTarg
       if (aCallback && topic == "mouseevent") {
         aCallback(data);
       }
     }
   };
   utils.sendNativeMouseMove(x * scale, y * scale, null, observer);
 }
 
+/**
+ * This is a wrapper around synthesizeNativeMouseMove that waits for the mouse
+ * event to be dispatched to the target content.
+ *
+ * This API is supposed to be used in those test cases that synthesize some
+ * input events to chrome process and have some checks in content.
+ */
+function synthesizeAndWaitNativeMouseMove(aTarget, aOffsetX, aOffsetY,
+                                          aCallback, aWindow = window) {
+  let browser = gBrowser.selectedTab.linkedBrowser;
+  let mm = browser.messageManager;
+  let ContentTask =
+    _EU_Cu.import("resource://testing-common/ContentTask.jsm", null).ContentTask;
+
+  let eventRegisteredPromise = new Promise(resolve => {
+    mm.addMessageListener("Test:MouseMoveRegistered", function processed(message) {
+      mm.removeMessageListener("Test:MouseMoveRegistered", processed);
+      resolve();
+    });
+  });
+  let eventReceivedPromise = ContentTask.spawn(browser, [aOffsetX, aOffsetY],
+                                               ([clientX, clientY]) => {
+    return new Promise(resolve => {
+      addEventListener("mousemove", function onMouseMoveEvent(e) {
+        if (e.clientX == clientX && e.clientY == clientY) {
+          removeEventListener("mousemove", onMouseMoveEvent);
+          resolve();
+        }
+      });
+      sendAsyncMessage("Test:MouseMoveRegistered");
+    });
+  });
+  eventRegisteredPromise.then(() => {
+    synthesizeNativeMouseMove(aTarget, aOffsetX, aOffsetY, null, aWindow);
+  });
+  return eventReceivedPromise;
+}
+
 function _computeKeyCodeFromChar(aChar)
 {
   if (aChar.length != 1) {
     return 0;
   }
   var KeyEvent = _EU_Ci.nsIDOMKeyEvent;
   if (aChar >= 'a' && aChar <= 'z') {
     return KeyEvent.DOM_VK_A + aChar.charCodeAt(0) - 'a'.charCodeAt(0);
@@ -824,16 +862,61 @@ function synthesizeKey(aKey, aEvent, aWi
     if (dispatchKeyup) {
       TIP.keyup(keyEvent, keyEventDict.flags);
     }
   } finally {
     _emulateToInactivateModifiers(TIP, modifiers, aWindow);
   }
 }
 
+/**
+ * This is a wrapper around synthesizeKey that waits for the key event to be
+ * dispatched to the target content. It returns a promise which is resolved
+ * when the content receives the key event.
+ *
+ * This API is supposed to be used in those test cases that synthesize some
+ * input events to chrome process and have some checks in content.
+ */
+function synthesizeAndWaitKey(aKey, aEvent, aWindow = window,
+                              checkBeforeSynthesize, checkAfterSynthesize)
+{
+  let browser = gBrowser.selectedTab.linkedBrowser;
+  let mm = browser.messageManager;
+  let keyCode = _createKeyboardEventDictionary(aKey, aEvent, aWindow).dictionary.keyCode;
+  let ContentTask = _EU_Cu.import("resource://testing-common/ContentTask.jsm", null).ContentTask;
+
+  let keyRegisteredPromise = new Promise(resolve => {
+    mm.addMessageListener("Test:KeyRegistered", function processed(message) {
+      mm.removeMessageListener("Test:KeyRegistered", processed);
+      resolve();
+    });
+  });
+  let keyReceivedPromise = ContentTask.spawn(browser, keyCode, (keyCode) => {
+    return new Promise(resolve => {
+      addEventListener("keyup", function onKeyEvent(e) {
+        if (e.keyCode == keyCode) {
+          removeEventListener("keyup", onKeyEvent);
+          resolve();
+        }
+      });
+      sendAsyncMessage("Test:KeyRegistered");
+    });
+  });
+  keyRegisteredPromise.then(() => {
+    if (checkBeforeSynthesize) {
+      checkBeforeSynthesize();
+    }
+    synthesizeKey(aKey, aEvent, aWindow);
+    if (checkAfterSynthesize) {
+      checkAfterSynthesize();
+    }
+  });
+  return keyReceivedPromise;
+}
+
 function _parseNativeModifiers(aModifiers, aWindow = window)
 {
   var navigator = _getNavigator(aWindow);
   var modifiers;
   if (aModifiers.capsLockKey) {
     modifiers |= 0x00000001;
   }
   if (aModifiers.numLockKey) {
--- a/toolkit/components/telemetry/Scalars.yaml
+++ b/toolkit/components/telemetry/Scalars.yaml
@@ -150,16 +150,32 @@ browser.engagement:
     expires: '62'
     kind: uint
     notification_emails:
       - chutten@mozilla.com
     release_channel_collection: opt-out
     record_in_processes:
       - 'main'
 
+  active_ticks:
+    bug_numbers:
+      - 1376942
+    description: >
+      The count of the number of five-second intervals ('ticks') the user
+      was considered 'active' in a subsession. Session activity involves keyboard or mouse
+      interaction with the application. It does not take into account whether or not the window
+      has focus or is in the foreground, only if it is receiving these interaction events.
+    expires: never
+    kind: uint
+    notification_emails:
+      - bcolloran@mozilla.com
+    release_channel_collection: opt-out
+    record_in_processes:
+      - 'main'
+
 # The following section contains the browser engagement scalars.
 browser.engagement.navigation:
   urlbar:
     bug_numbers:
       - 1271313
     description: >
       The count URI loads triggered in a subsession from the urlbar (awesomebar),
       broken down by the originating action.
--- a/toolkit/components/telemetry/TelemetryScalar.cpp
+++ b/toolkit/components/telemetry/TelemetryScalar.cpp
@@ -175,17 +175,17 @@ ScalarInfo::name() const
 
 const char *
 ScalarInfo::expiration() const
 {
   return &gScalarsStringTable[this->expiration_offset];
 }
 
 /**
- * The base scalar object, that servers as a common ancestor for storage
+ * The base scalar object, that serves as a common ancestor for storage
  * purposes.
  */
 class ScalarBase
 {
 public:
   virtual ~ScalarBase() = default;
 
   // Set, Add and SetMaximum functions as described in the Telemetry IDL.
--- a/toolkit/components/telemetry/TelemetrySession.jsm
+++ b/toolkit/components/telemetry/TelemetrySession.jsm
@@ -1963,16 +1963,17 @@ var Impl = {
   _onActiveTick(aUserActive) {
     const needsUpdate = aUserActive && this._isUserActive;
     this._isUserActive = aUserActive;
 
     // Don't count the first active tick after we get out of
     // inactivity, because it is just the start of this active tick.
     if (needsUpdate) {
       this._sessionActiveTicks++;
+      Telemetry.scalarAdd("browser.engagement.active_ticks", 1);
     }
   },
 
   /**
    * This observer drives telemetry.
    */
   observe(aSubject, aTopic, aData) {
     // Prevent the cycle collector begin topic from cluttering the log.
--- a/toolkit/components/telemetry/tests/unit/test_TelemetrySession.js
+++ b/toolkit/components/telemetry/tests/unit/test_TelemetrySession.js
@@ -945,39 +945,46 @@ add_task(async function test_checkSubses
 
   await TelemetryController.testReset();
 
   // Both classic and subsession payload data should be the same on the first subsession.
   incrementActiveTicks();
   let classic = TelemetrySession.getPayload();
   let subsession = TelemetrySession.getPayload("environment-change");
   Assert.equal(classic.simpleMeasurements.activeTicks, expectedActiveTicks,
-               "Classic pings must count active ticks since the beginning of the session.");
+    "Classic pings must count active ticks (in simpleMeasurements) since the beginning of the session.");
   Assert.equal(subsession.simpleMeasurements.activeTicks, expectedActiveTicks,
-               "Subsessions must count active ticks as classic pings on the first subsession.");
+    "Subsessions must count active ticks (in simpleMeasurements) as classic pings on the first subsession.");
+  Assert.equal(subsession.processes.parent.scalars["browser.engagement.active_ticks"], expectedActiveTicks,
+    "Subsessions must count active ticks (in scalars) as classic pings on the first subsession.");
 
   // Start a new subsession and check that the active ticks are correctly reported.
   incrementActiveTicks();
   activeTicksAtSubsessionStart = getActiveTicks();
   classic = TelemetrySession.getPayload();
   subsession = TelemetrySession.getPayload("environment-change", true);
   Assert.equal(classic.simpleMeasurements.activeTicks, expectedActiveTicks,
-               "Classic pings must count active ticks since the beginning of the session.");
+    "Classic pings must count active ticks (in simpleMeasurements) since the beginning of the session.");
   Assert.equal(subsession.simpleMeasurements.activeTicks, expectedActiveTicks,
-               "Pings must not loose the tick count when starting a new subsession.");
+    "Pings must not lose the tick count when starting a new subsession.");
+  Assert.equal(subsession.processes.parent.scalars["browser.engagement.active_ticks"], expectedActiveTicks,
+    "Active ticks (in scalars) must not lose count for a previous subsession when starting a new subsession.");
 
   // Get a new subsession payload without clearing the subsession.
   incrementActiveTicks();
   classic = TelemetrySession.getPayload();
   subsession = TelemetrySession.getPayload("environment-change");
   Assert.equal(classic.simpleMeasurements.activeTicks, expectedActiveTicks,
-               "Classic pings must count active ticks since the beginning of the session.");
+    "Classic pings must count active ticks (in simpleMeasurements) since the beginning of the session.");
   Assert.equal(subsession.simpleMeasurements.activeTicks,
-               expectedActiveTicks - activeTicksAtSubsessionStart,
-               "Subsessions must count active ticks since the last new subsession.");
+    expectedActiveTicks - activeTicksAtSubsessionStart,
+    "Subsessions must count active ticks (in simpleMeasurements) since the last new subsession.");
+  Assert.equal(subsession.processes.parent.scalars["browser.engagement.active_ticks"],
+    expectedActiveTicks - activeTicksAtSubsessionStart,
+    "Subsessions must count active ticks (in scalars) since the last new subsession.");
 
   await TelemetryController.testShutdown();
 });
 
 add_task(async function test_dailyCollection() {
   if (gIsAndroid) {
     // We don't do daily collections yet on Android.
     return;
--- a/toolkit/components/telemetry/tests/unit/test_TelemetrySession_activeTicks.js
+++ b/toolkit/components/telemetry/tests/unit/test_TelemetrySession_activeTicks.js
@@ -16,20 +16,26 @@ add_task(async function test_setup() {
 
   Services.prefs.setBoolPref(TelemetryUtils.Preferences.TelemetryEnabled, true);
 });
 
 add_task(async function test_record_activeTicks() {
   await TelemetryController.testSetup();
 
   let checkActiveTicks = (expected) => {
-    let payload = TelemetrySession.getPayload();
+    // Scalars are only present in subsession payloads.
+    let payload = TelemetrySession.getPayload("main");
     Assert.equal(payload.simpleMeasurements.activeTicks, expected,
-                 "TelemetrySession must record the expected number of active ticks.");
-  };
+                 "TelemetrySession must record the expected number of active ticks (in simpleMeasurements).");
+    // Subsessions are not yet supported on Android.
+    if (!gIsAndroid) {
+      Assert.equal(payload.processes.parent.scalars["browser.engagement.active_ticks"], expected,
+                   "TelemetrySession must record the expected number of active ticks (in scalars).");
+    }
+  }
 
   for (let i = 0; i < 3; i++) {
     Services.obs.notifyObservers(null, "user-interaction-active");
   }
   checkActiveTicks(3);
 
   // Now send inactive. This must not increment the active ticks.
   Services.obs.notifyObservers(null, "user-interaction-inactive");
--- a/toolkit/components/tooltiptext/tests/browser_input_file_tooltips.js
+++ b/toolkit/components/tooltiptext/tests/browser_input_file_tooltips.js
@@ -29,19 +29,17 @@ add_task(async function test_requiredset
 });
 
 async function do_test(test) {
   info(`starting test ${JSON.stringify(test)}`);
 
   let tab = await BrowserTestUtils.openNewForegroundTab(gBrowser);
 
   info("Moving mouse out of the way.");
-  await new Promise(resolve => {
-    EventUtils.synthesizeNativeMouseMove(tab.linkedBrowser, 300, 300, resolve);
-  });
+  await EventUtils.synthesizeAndWaitNativeMouseMove(tab.linkedBrowser, 300, 300);
 
   info("creating input field");
   await ContentTask.spawn(tab.linkedBrowser, test, async function(test) {
     let doc = content.document;
     let input = doc.createElement("input");
     doc.body.appendChild(input);
     input.id = "test_input";
     input.setAttribute("style", "position: absolute; top: 0; left: 0;");
@@ -87,25 +85,21 @@ async function do_test(test) {
                       "remoteBrowserTooltip" :
                       "aHTMLTooltip";
     let tooltip = document.getElementById(tooltipId);
     tooltip.addEventListener("popupshown", function(event) {
       resolve(event.target);
     }, {once: true});
   });
   info("Initial mouse move");
-  await new Promise(resolve => {
-    EventUtils.synthesizeNativeMouseMove(tab.linkedBrowser, 50, 5, resolve);
-  });
+  await EventUtils.synthesizeAndWaitNativeMouseMove(tab.linkedBrowser, 50, 5);
   info("Waiting");
   await new Promise(resolve => setTimeout(resolve, 400));
   info("Second mouse move");
-  await new Promise(resolve => {
-    EventUtils.synthesizeNativeMouseMove(tab.linkedBrowser, 70, 5, resolve);
-  });
+  await EventUtils.synthesizeAndWaitNativeMouseMove(tab.linkedBrowser, 70, 5);
   info("Waiting for tooltip to open");
   let tooltip = await awaitTooltipOpen;
 
   is(tooltip.getAttribute("label"), test.result, "tooltip label should match expectation");
 
   info("Closing tab");
   await BrowserTestUtils.removeTab(tab);
 }
--- a/toolkit/content/tests/browser/browser_findbar.js
+++ b/toolkit/content/tests/browser/browser_findbar.js
@@ -170,26 +170,28 @@ add_task(async function() {
   let tab = await BrowserTestUtils.openNewForegroundTab(gBrowser, TEST_PAGE_URI);
   let browser = tab.linkedBrowser;
 
   ok(!gFindBarInitialized, "findbar isn't initialized yet");
 
   let findBar = gFindBar;
   let initialValue = findBar._findField.value;
 
-  EventUtils.synthesizeKey("f", { accelKey: true }, window);
+  await EventUtils.synthesizeAndWaitKey("f", { accelKey: true }, window, null,
+                                        () => {
+    isnot(document.activeElement, findBar._findField.inputField,
+      "findbar is not yet focused");
+  });
 
   let promises = [
     BrowserTestUtils.sendChar("a", browser),
     BrowserTestUtils.sendChar("b", browser),
     BrowserTestUtils.sendChar("c", browser)
   ];
 
-  isnot(document.activeElement, findBar._findField.inputField,
-    "findbar is not yet focused");
   is(findBar._findField.value, initialValue, "still has initial find query");
 
   await Promise.all(promises);
   is(document.activeElement, findBar._findField.inputField,
     "findbar is now focused");
   is(findBar._findField.value, "abc", "abc fully entered as find query");
 
   await BrowserTestUtils.removeTab(tab);
--- a/toolkit/modules/sessionstore/FormData.jsm
+++ b/toolkit/modules/sessionstore/FormData.jsm
@@ -305,17 +305,17 @@ var FormDataInternal = {
       let retrieveNode = xpath => this.resolve(doc, xpath);
       this.restoreManyInputValues(data.xpath, retrieveNode);
     }
 
     if ("innerHTML" in data) {
       if (doc.body && doc.designMode == "on") {
       // eslint-disable-next-line no-unsanitized/property
         doc.body.innerHTML = data.innerHTML;
-        this.fireEvent(doc.body, "input");
+        this.fireInputEvent(doc.body);
       }
     }
   },
 
   /**
    * Iterates the given form data, retrieving nodes for all the keys and
    * restores their appropriate values.
    *
@@ -340,85 +340,84 @@ var FormDataInternal = {
    * the appropriate DOM event should the input's value change.
    *
    * @param  aNode
    *         DOMNode to set form value on.
    * @param  aValue
    *         Value to set form element to.
    */
   restoreSingleInputValue(aNode, aValue) {
-    let eventType;
+    let fireEvent = false;
 
     if (typeof aValue == "string" && aNode.type != "file") {
       // Don't dispatch an input event if there is no change.
       if (aNode.value == aValue) {
         return;
       }
 
       aNode.value = aValue;
-      eventType = "input";
+      fireEvent = true;
     } else if (typeof aValue == "boolean") {
       // Don't dispatch a change event for no change.
       if (aNode.checked == aValue) {
         return;
       }
 
       aNode.checked = aValue;
-      eventType = "change";
+      fireEvent = true;
     } else if (aValue && aValue.selectedIndex >= 0 && aValue.value) {
       // Don't dispatch a change event for no change
       if (aNode.options[aNode.selectedIndex].value == aValue.value) {
         return;
       }
 
       // find first option with matching aValue if possible
       for (let i = 0; i < aNode.options.length; i++) {
         if (aNode.options[i].value == aValue.value) {
           aNode.selectedIndex = i;
-          eventType = "change";
+          fireEvent = true;
           break;
         }
       }
     } else if (aValue && aValue.fileList && aValue.type == "file" &&
       aNode.type == "file") {
       try {
         // FIXME (bug 1122855): This won't work in content processes.
         aNode.mozSetFileNameArray(aValue.fileList, aValue.fileList.length);
       } catch (e) {
         Cu.reportError("mozSetFileNameArray: " + e);
       }
-      eventType = "input";
+      fireEvent = true;
     } else if (Array.isArray(aValue) && aNode.options) {
       Array.forEach(aNode.options, function(opt, index) {
         // don't worry about malformed options with same values
         opt.selected = aValue.indexOf(opt.value) > -1;
 
         // Only fire the event here if this wasn't selected by default
         if (!opt.defaultSelected) {
-          eventType = "change";
+          fireEvent = true;
         }
       });
     }
 
     // Fire events for this node if applicable
-    if (eventType) {
-      this.fireEvent(aNode, eventType);
+    if (fireEvent) {
+      this.fireInputEvent(aNode);
     }
   },
 
   /**
-   * Dispatches an event of type |type| to the given |node|.
+   * Dispatches an event of type "input" to the given |node|.
    *
    * @param node (DOMNode)
-   * @param type (string)
    */
-  fireEvent(node, type) {
+  fireInputEvent(node) {
     let doc = node.ownerDocument;
     let event = doc.createEvent("UIEvents");
-    event.initUIEvent(type, true, true, doc.defaultView, 0);
+    event.initUIEvent("input", true, true, doc.defaultView, 0);
     node.dispatchEvent(event);
   },
 
   /**
    * Restores form data for the current frame hierarchy starting at |root|
    * using the given form |data|.
    *
    * If the given |root| frame's hierarchy doesn't match that of the given
--- a/widget/tests/test_assign_event_data.html
+++ b/widget/tests/test_assign_event_data.html
@@ -95,16 +95,26 @@ function onEvent(aEvent)
         attr != "multipleActionsPrevented" && // multipleActionsPrevented isn't defined in any DOM event specs.
         typeof(aEvent[attr]) != "function") {
       gCopiedEvent.push({ name: attr, value: aEvent[attr]});
     }
   }
   setTimeout(gCallback, 0);
 }
 
+function observeKeyUpOnContent(aKeyCode, aCallback)
+{
+  document.addEventListener("keyup", function keyUp(ev) {
+    if (ev.keyCode == aKeyCode) {
+      document.removeEventListener("keyup", keyUp);
+      SimpleTest.executeSoon(aCallback);
+    }
+  });
+}
+
 const kTests = [
   { description: "InternalScrollPortEvent (overflow, vertical)",
     targetID: "scrollable-div", eventType: "overflow",
     dispatchEvent: function () {
       document.getElementById("scrolled-div").style.height = "500px";
     },
     canRun: function () {
       return true;
@@ -145,55 +155,69 @@ const kTests = [
   },
   { description: "WidgetKeyboardEvent (keydown of 'a' key without modifiers)",
     targetID: "input-text", eventType: "keydown",
     dispatchEvent: function () {
       document.getElementById(this.targetID).value = "";
       document.getElementById(this.targetID).focus();
       synthesizeNativeKey(KEYBOARD_LAYOUT_EN_US, kIsWin ? WIN_VK_A : MAC_VK_ANSI_A,
                           {}, "a", "a");
+      observeKeyUpOnContent(KeyboardEvent.DOM_VK_A, runNextTest);
+      return true;
     },
     canRun: function () {
       return (kIsMac || kIsWin);
     },
     todoMismatch: [],
   },
   { description: "WidgetKeyboardEvent (keyup of 'a' key without modifiers)",
     targetID: "input-text", eventType: "keydown",
     dispatchEvent: function () {
       document.getElementById(this.targetID).value = "";
       document.getElementById(this.targetID).focus();
       synthesizeNativeKey(KEYBOARD_LAYOUT_EN_US, kIsWin ? WIN_VK_A : MAC_VK_ANSI_A,
                           {}, "a", "a");
+      observeKeyUpOnContent(KeyboardEvent.DOM_VK_A, runNextTest);
+      return true;
     },
     canRun: function () {
       return (kIsMac || kIsWin);
     },
     todoMismatch: [],
   },
   { description: "WidgetKeyboardEvent (keypress of 'b' key with Shift)",
     targetID: "input-text", eventType: "keypress",
     dispatchEvent: function () {
       document.getElementById(this.targetID).value = "";
       document.getElementById(this.targetID).focus();
       synthesizeNativeKey(KEYBOARD_LAYOUT_EN_US, kIsWin ? WIN_VK_B : MAC_VK_ANSI_B,
                           { shiftKey: true }, "B", "B");
+      observeKeyUpOnContent(KeyboardEvent.DOM_VK_B, runNextTest);
+      return true;
     },
     canRun: function () {
       return (kIsMac || kIsWin);
     },
     todoMismatch: [],
   },
   { description: "WidgetKeyboardEvent (keypress of 'c' key with Accel)",
     targetID: "input-text", eventType: "keypress",
     dispatchEvent: function () {
       document.getElementById(this.targetID).value = "";
       document.getElementById(this.targetID).focus();
       synthesizeNativeKey(KEYBOARD_LAYOUT_EN_US, kIsWin ? WIN_VK_C : MAC_VK_ANSI_C,
                           { accelKey: true }, kIsWin ? "\u0003" : "c", "c");
+
+      // On Windows, synthesizeNativeKey will also fire keyup for accelKey
+      // (control key on Windows). We have to wait for it to prevent the key
+      // event break the next test case.
+      let waitKeyCode = _EU_isWin(window) ? KeyboardEvent.DOM_VK_CONTROL :
+                                            KeyboardEvent.DOM_VK_C;
+      observeKeyUpOnContent(waitKeyCode, runNextTest);
+      return true;
     },
     canRun: function () {
       return (kIsMac || kIsWin);
     },
     todoMismatch: [],
   },
   { description: "WidgetKeyboardEvent (keyup during composition)",
     targetID: "input-text", eventType: "keyup",
@@ -320,16 +344,18 @@ const kTests = [
   },
   { description: "InternalEditorInputEvent (input at key input)",
     targetID: "input-text", eventType: "input",
     dispatchEvent: function () {
       document.getElementById(this.targetID).value = "";
       document.getElementById(this.targetID).focus();
       synthesizeNativeKey(KEYBOARD_LAYOUT_EN_US, kIsWin ? WIN_VK_B : MAC_VK_ANSI_B,
                           { shiftKey: true }, "B", "B");
+      observeKeyUpOnContent(KeyboardEvent.DOM_VK_B, runNextTest);
+      return true;
     },
     canRun: function () {
       return (kIsMac || kIsWin);
     },
     todoMismatch: [],
   },
   { description: "InternalEditorInputEvent (input at composing)",
     targetID: "input-text", eventType: "input",
new file mode 100644
--- /dev/null
+++ b/xpcom/threads/InputEventStatistics.cpp
@@ -0,0 +1,68 @@
+/* -*- 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 "InputEventStatistics.h"
+
+#include "nsRefreshDriver.h"
+
+namespace mozilla {
+
+TimeDuration
+InputEventStatistics::TimeDurationCircularBuffer::GetMean()
+{
+  return mTotal / (int64_t)mSize;
+}
+
+InputEventStatistics::InputEventStatistics()
+  : mEnable(false)
+{
+  MOZ_ASSERT(Preferences::IsServiceAvailable());
+  uint32_t inputDuration =
+    Preferences::GetUint("prioritized_input_events.default_duration_per_event",
+                         sDefaultInputDuration);
+
+  TimeDuration defaultDuration = TimeDuration::FromMilliseconds(inputDuration);
+
+  uint32_t count =
+    Preferences::GetUint("prioritized_input_events.count_for_prediction",
+                         sInputCountForPrediction);
+
+  mLastInputDurations =
+    MakeUnique<TimeDurationCircularBuffer>(count, defaultDuration);
+
+  uint32_t maxDuration =
+    Preferences::GetUint("prioritized_input_events.duration.max",
+                         sMaxReservedTimeForHandlingInput);
+
+  uint32_t minDuration =
+    Preferences::GetUint("prioritized_input_events.duration.min",
+                         sMinReservedTimeForHandlingInput);
+
+  mMaxInputDuration = TimeDuration::FromMilliseconds(maxDuration);
+  mMinInputDuration = TimeDuration::FromMilliseconds(minDuration);
+}
+
+TimeStamp
+InputEventStatistics::GetInputHandlingStartTime(uint32_t aInputCount)
+{
+  MOZ_ASSERT(mEnable);
+  Maybe<TimeStamp> nextTickHint = nsRefreshDriver::GetNextTickHint();
+
+  if (nextTickHint.isNothing()) {
+    // Return a past time to process input events immediately.
+    return TimeStamp::Now() - TimeDuration::FromMilliseconds(1);
+  }
+  TimeDuration inputCost = mLastInputDurations->GetMean() * aInputCount;
+  inputCost = inputCost > mMaxInputDuration
+              ? mMaxInputDuration
+              : inputCost < mMinInputDuration
+              ? mMinInputDuration
+              : inputCost;
+
+  return nextTickHint.value() - inputCost;
+}
+
+} // namespace mozilla
new file mode 100644
--- /dev/null
+++ b/xpcom/threads/InputEventStatistics.h
@@ -0,0 +1,114 @@
+/* -*- 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/. */
+
+#if !defined(InputEventStatistics_h_)
+#define InputEventStatistics_h_
+
+#include "mozilla/Maybe.h"
+#include "mozilla/Preferences.h"
+#include "mozilla/TimeStamp.h"
+
+namespace mozilla {
+
+class InputEventStatistics
+{
+  // The default amount of time (milliseconds) required for handling a input
+  // event.
+  static const uint16_t sDefaultInputDuration = 1;
+
+  // The number of processed input events we use to predict the amount of time
+  // required to process the following input events.
+  static const uint16_t sInputCountForPrediction = 9;
+
+  // The default maximum and minimum time (milliseconds) we reserve for handling
+  // input events in each frame.
+  static const uint16_t sMaxReservedTimeForHandlingInput = 8;
+  static const uint16_t sMinReservedTimeForHandlingInput = 1;
+
+  class TimeDurationCircularBuffer
+  {
+    int16_t mSize;
+    int16_t mCurrentIndex;
+    nsTArray<TimeDuration> mBuffer;
+    TimeDuration mTotal;
+
+  public:
+    TimeDurationCircularBuffer(uint32_t aSize, TimeDuration& aDefaultValue)
+      : mSize(aSize)
+      , mCurrentIndex(0)
+    {
+      mSize = mSize == 0 ? sInputCountForPrediction : mSize;
+      for (int16_t index = 0; index < mSize; ++index) {
+        mBuffer.AppendElement(aDefaultValue);
+        mTotal += aDefaultValue;
+      }
+    }
+
+    void Insert(TimeDuration& aDuration)
+    {
+      mTotal += (aDuration - mBuffer[mCurrentIndex]);
+      mBuffer[mCurrentIndex++] = aDuration;
+      if (mCurrentIndex == mSize) {
+        mCurrentIndex = 0;
+      }
+    }
+
+    TimeDuration GetMean();
+  };
+
+  UniquePtr<TimeDurationCircularBuffer> mLastInputDurations;
+  TimeDuration mMaxInputDuration;
+  TimeDuration mMinInputDuration;
+  bool mEnable;
+
+  InputEventStatistics();
+  ~InputEventStatistics()
+  {
+  }
+
+public:
+  static InputEventStatistics& Get()
+  {
+    static InputEventStatistics sInstance;
+    return sInstance;
+  }
+
+  void UpdateInputDuration(TimeDuration aDuration)
+  {
+    if (!mEnable) {
+      return;
+    }
+    mLastInputDurations->Insert(aDuration);
+  }
+
+  TimeStamp GetInputHandlingStartTime(uint32_t aInputCount);
+
+  void SetEnable(bool aEnable)
+  {
+    mEnable = aEnable;
+  }
+};
+
+class MOZ_RAII AutoTimeDurationHelper final
+{
+public:
+  AutoTimeDurationHelper()
+  {
+    mStartTime = TimeStamp::Now();
+  }
+
+  ~AutoTimeDurationHelper()
+  {
+    InputEventStatistics::Get().UpdateInputDuration(TimeStamp::Now() - mStartTime);
+  }
+
+private:
+  TimeStamp mStartTime;
+};
+
+} // namespace mozilla
+
+#endif // InputEventStatistics_h_
--- a/xpcom/threads/LazyIdleThread.cpp
+++ b/xpcom/threads/LazyIdleThread.cpp
@@ -508,16 +508,28 @@ LazyIdleThread::HasPendingEvents(bool* a
 
 NS_IMETHODIMP
 LazyIdleThread::IdleDispatch(already_AddRefed<nsIRunnable> aEvent)
 {
   return NS_ERROR_NOT_IMPLEMENTED;
 }
 
 NS_IMETHODIMP
+LazyIdleThread::EnableEventPrioritization()
+{
+  return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+LazyIdleThread::IsEventPrioritizationEnabled(bool* aResult)
+{
+  return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
 LazyIdleThread::RegisterIdlePeriod(already_AddRefed<nsIIdlePeriod> aIdlePeriod)
 {
   return NS_ERROR_NOT_IMPLEMENTED;
 }
 
 NS_IMETHODIMP
 LazyIdleThread::ProcessNextEvent(bool aMayWait,
                                  bool* aEventWasProcessed)
--- a/xpcom/threads/SchedulerGroup.cpp
+++ b/xpcom/threads/SchedulerGroup.cpp
@@ -370,18 +370,27 @@ SchedulerGroup::Runnable::Run()
   // The runnable's destructor can have side effects, so try to execute it in
   // the scope of the TabGroup.
   mRunnable = nullptr;
 
   mGroup->SetValidatingAccess(EndValidation);
   return result;
 }
 
+NS_IMETHODIMP
+SchedulerGroup::Runnable::GetPriority(uint32_t* aPriority)
+{
+  *aPriority = nsIRunnablePriority::PRIORITY_NORMAL;
+  nsCOMPtr<nsIRunnablePriority> runnablePrio = do_QueryInterface(mRunnable);
+  return runnablePrio ? runnablePrio->GetPriority(aPriority) : NS_OK;
+}
+
 NS_IMPL_ISUPPORTS_INHERITED(SchedulerGroup::Runnable,
                             mozilla::Runnable,
+                            nsIRunnablePriority,
                             SchedulerGroup::Runnable)
 
 SchedulerGroup::AutoProcessEvent::AutoProcessEvent()
  : mPrevRunningDispatcher(SchedulerGroup::sRunningDispatcher)
 {
   SchedulerGroup* prev = sRunningDispatcher;
   if (prev) {
     MOZ_ASSERT(prev->mAccessValid);
--- a/xpcom/threads/SchedulerGroup.h
+++ b/xpcom/threads/SchedulerGroup.h
@@ -76,30 +76,31 @@ public:
   }
 
   // Ensure that it's valid to access the TabGroup at this time.
   void ValidateAccess() const
   {
     MOZ_ASSERT(IsSafeToRun());
   }
 
-  class Runnable final : public mozilla::Runnable
+  class Runnable final : public mozilla::Runnable, public nsIRunnablePriority
   {
   public:
     Runnable(already_AddRefed<nsIRunnable>&& aRunnable,
              SchedulerGroup* aGroup);
 
     SchedulerGroup* Group() const { return mGroup; }
 
     NS_IMETHOD GetName(nsACString& aName) override;
 
     bool IsBackground() const { return mGroup->IsBackground(); }
 
     NS_DECL_ISUPPORTS_INHERITED
     NS_DECL_NSIRUNNABLE
+    NS_DECL_NSIRUNNABLEPRIORITY
 
     NS_DECLARE_STATIC_IID_ACCESSOR(NS_SCHEDULERGROUPRUNNABLE_IID);
 
  private:
     ~Runnable() = default;
 
     nsCOMPtr<nsIRunnable> mRunnable;
     RefPtr<SchedulerGroup> mGroup;
--- a/xpcom/threads/TimerThread.h
+++ b/xpcom/threads/TimerThread.h
@@ -14,16 +14,17 @@
 #include "nsTimerImpl.h"
 #include "nsThreadUtils.h"
 
 #include "nsTArray.h"
 
 #include "mozilla/Atomics.h"
 #include "mozilla/Attributes.h"
 #include "mozilla/Monitor.h"
+#include "mozilla/UniquePtr.h"
 
 #include <algorithm>
 
 namespace mozilla {
 class TimeStamp;
 } // namespace mozilla
 
 class TimerThread final
@@ -105,30 +106,31 @@ private:
     {
       if (mTimerImpl) {
         mTimerImpl->SetHolder(nullptr);
       }
       return mTimerImpl.forget();
     }
 
     static bool
-    UniquePtrLessThan(UniquePtr<Entry>& aLeft, UniquePtr<Entry>& aRight)
+    UniquePtrLessThan(mozilla::UniquePtr<Entry>& aLeft,
+                      mozilla::UniquePtr<Entry>& aRight)
     {
       // This is reversed because std::push_heap() sorts the "largest" to
       // the front of the heap.  We want that to be the earliest timer.
       return aRight->mTimeout < aLeft->mTimeout;
     }
 
     TimeStamp Timeout() const
     {
       return mTimeout;
     }
   };
 
-  nsTArray<UniquePtr<Entry>> mTimers;
+  nsTArray<mozilla::UniquePtr<Entry>> mTimers;
   uint32_t mAllowedEarlyFiringMicroseconds;
 };
 
 struct TimerAdditionComparator
 {
   TimerAdditionComparator(const mozilla::TimeStamp& aNow,
                           nsTimerImpl* aTimerToInsert) :
     now(aNow)
--- a/xpcom/threads/moz.build
+++ b/xpcom/threads/moz.build
@@ -38,16 +38,17 @@ EXPORTS += [
 EXPORTS.mozilla += [
     'AbstractThread.h',
     'BackgroundHangMonitor.h',
     'BlockingResourceBase.h',
     'CondVar.h',
     'DeadlockDetector.h',
     'HangAnnotations.h',
     'HangMonitor.h',
+    'InputEventStatistics.h',
     'LazyIdleThread.h',
     'MainThreadIdlePeriod.h',
     'Monitor.h',
     'MozPromise.h',
     'Mutex.h',
     'ReentrantMonitor.h',
     'RWLock.h',
     'SchedulerGroup.h',
@@ -63,16 +64,17 @@ EXPORTS.mozilla += [
 ]
 
 UNIFIED_SOURCES += [
     'AbstractThread.cpp',
     'BackgroundHangMonitor.cpp',
     'BlockingResourceBase.cpp',
     'HangAnnotations.cpp',
     'HangMonitor.cpp',
+    'InputEventStatistics.cpp',
     'LazyIdleThread.cpp',
     'MainThreadIdlePeriod.cpp',
     'nsEnvironment.cpp',
     'nsEventQueue.cpp',
     'nsMemoryPressure.cpp',
     'nsProcessCommon.cpp',
     'nsProxyRelease.cpp',
     'nsThread.cpp',
--- a/xpcom/threads/nsIRunnable.idl
+++ b/xpcom/threads/nsIRunnable.idl
@@ -13,15 +13,16 @@
 interface nsIRunnable : nsISupports
 {
     /**
      * The function implementing the task to be run.
      */
     void run();
 };
 
-[uuid(e75aa42a-80a9-11e6-afb5-e89d87348e2c)]
+[scriptable, uuid(e75aa42a-80a9-11e6-afb5-e89d87348e2c)]
 interface nsIRunnablePriority : nsISupports
 {
     const unsigned short PRIORITY_NORMAL = 0;
-    const unsigned short PRIORITY_HIGH = 1;
+    const unsigned short PRIORITY_INPUT = 1;
+    const unsigned short PRIORITY_HIGH = 2;
     readonly attribute unsigned long priority;
 };
--- a/xpcom/threads/nsIThread.idl
+++ b/xpcom/threads/nsIThread.idl
@@ -147,16 +147,19 @@ interface nsIThread : nsISerialEventTarg
    * @throws NS_ERROR_INVALID_ARG
    *   Indicates that event is null.
    * @throws NS_ERROR_UNEXPECTED
    *   Indicates that the thread is shutting down and has finished processing
    * events, so this event would never run and has not been dispatched.
    */
   [noscript] void idleDispatch(in alreadyAddRefed_nsIRunnable event);
 
+  [noscript] void enableEventPrioritization();
+  [noscript] bool isEventPrioritizationEnabled();
+
   /**
    * Use this attribute to dispatch runnables to the thread. Eventually, the
    * eventTarget attribute will be the only way to dispatch events to a
    * thread--nsIThread will no longer inherit from nsIEventTarget.
    */
   readonly attribute nsIEventTarget eventTarget;
 
   /**
--- a/xpcom/threads/nsIThreadManager.idl
+++ b/xpcom/threads/nsIThreadManager.idl
@@ -87,17 +87,17 @@ interface nsIThreadManager : nsISupports
   /**
    * This queues a runnable to the main thread. It's a shortcut for JS callers
    * to be used instead of
    *   .mainThread.dispatch(runnable, Ci.nsIEventTarget.DISPATCH_NORMAL);
    * or
    *   .currentThread.dispatch(runnable, Ci.nsIEventTarget.DISPATCH_NORMAL);
    * C++ callers should instead use NS_DispatchToMainThread.
    */
-  void dispatchToMainThread(in nsIRunnable event);
+  void dispatchToMainThread(in nsIRunnable event, [optional] in uint32_t priority);
 
   /**
    * This queues a runnable to the main thread's idle queue.
    *
    * @param event
    *   The event to dispatch.
    * @param timeout
    *   The time in milliseconds until this event should be moved from the idle
--- a/xpcom/threads/nsThread.cpp
+++ b/xpcom/threads/nsThread.cpp
@@ -36,16 +36,17 @@
 #include "mozilla/TimeStamp.h"
 #include "mozilla/Unused.h"
 #include "mozilla/dom/ScriptSettings.h"
 #include "nsIIdlePeriod.h"
 #include "nsIIdleRunnable.h"
 #include "nsThreadSyncDispatch.h"
 #include "LeakRefPtr.h"
 #include "GeckoProfiler.h"
+#include "InputEventStatistics.h"
 
 #ifdef MOZ_CRASHREPORTER
 #include "nsServiceManagerUtils.h"
 #include "nsICrashReporter.h"
 #include "mozilla/dom/ContentChild.h"
 #endif
 
 #ifdef XP_LINUX
@@ -810,55 +811,166 @@ nsThread::DispatchInternal(already_AddRe
     return NS_OK;
   }
 
   NS_ASSERTION(aFlags == NS_DISPATCH_NORMAL ||
                aFlags == NS_DISPATCH_AT_END, "unexpected dispatch flags");
   return PutEvent(event.take(), aTarget);
 }
 
+NS_IMPL_ISUPPORTS(nsThread::nsChainedEventQueue::EnablePrioritizationRunnable,
+                  nsIRunnable)
+
+void
+nsThread::nsChainedEventQueue::EnablePrioritization(MutexAutoLock& aProofOfLock)
+{
+  MOZ_ASSERT(!mIsInputPrioritizationEnabled);
+  // When enabling event prioritization, there may be some pending events with
+  // different priorities in the normal queue. Create an event in the normal
+  // queue to consume all pending events in the time order to make sure we won't
+  // preempt a pending event (e.g. input) in the normal queue by another newly
+  // created event with the same priority.
+  mNormalQueue->PutEvent(new EnablePrioritizationRunnable(this), aProofOfLock);
+  mInputHandlingStartTime = TimeStamp();
+  mIsInputPrioritizationEnabled = true;
+}
+
 bool
-nsThread::nsChainedEventQueue::GetEvent(bool aMayWait, nsIRunnable** aEvent,
-                                        unsigned short* aPriority,
-                                        mozilla::MutexAutoLock& aProofOfLock)
+nsThread::nsChainedEventQueue::
+GetNormalOrInputOrHighPriorityEvent(bool aMayWait, nsIRunnable** aEvent,
+                                    unsigned short* aPriority,
+                                    MutexAutoLock& aProofOfLock)
 {
   bool retVal = false;
   do {
-    if (mProcessSecondaryQueueRunnable) {
-      MOZ_ASSERT(mSecondaryQueue->HasPendingEvent(aProofOfLock));
-      retVal = mSecondaryQueue->GetEvent(aMayWait, aEvent, aProofOfLock);
+    // Use mProcessHighPriorityQueueRunnable to prevent the high priority events
+    // from consuming all cpu time and causing starvation.
+    if (mProcessHighPriorityQueueRunnable) {
+      MOZ_ASSERT(mHighQueue->HasPendingEvent(aProofOfLock));
+      retVal = mHighQueue->GetEvent(false, aEvent, aProofOfLock);
       MOZ_ASSERT(*aEvent);
-      if (aPriority) {
-        *aPriority = nsIRunnablePriority::PRIORITY_HIGH;
+      SetPriorityIfNotNull(aPriority, nsIRunnablePriority::PRIORITY_HIGH);
+      mInputHandlingStartTime = TimeStamp();
+      mProcessHighPriorityQueueRunnable = false;
+      return retVal;
+    }
+    mProcessHighPriorityQueueRunnable =
+      mHighQueue->HasPendingEvent(aProofOfLock);
+
+    uint32_t pendingInputCount = mInputQueue->Count(aProofOfLock);
+    if (pendingInputCount > 0) {
+      if (mInputHandlingStartTime.IsNull()) {
+        mInputHandlingStartTime =
+          InputEventStatistics::Get()
+            .GetInputHandlingStartTime(mInputQueue->Count(aProofOfLock));
       }
-      mProcessSecondaryQueueRunnable = false;
-      return retVal;
+      if (TimeStamp::Now() > mInputHandlingStartTime) {
+        retVal = mInputQueue->GetEvent(false, aEvent, aProofOfLock);
+        MOZ_ASSERT(*aEvent);
+        SetPriorityIfNotNull(aPriority, nsIRunnablePriority::PRIORITY_INPUT);
+        return retVal;
+      }
     }
 
-    // We don't want to wait if mSecondaryQueue has some events.
-    bool reallyMayWait =
-      aMayWait && !mSecondaryQueue->HasPendingEvent(aProofOfLock);
-    retVal =
-      mNormalQueue->GetEvent(reallyMayWait, aEvent, aProofOfLock);
-    if (aPriority) {
-      *aPriority = nsIRunnablePriority::PRIORITY_NORMAL;
-    }
+    // We don't want to wait if there are some high priority events or input
+    // events in the queues.
+    bool reallyMayWait = aMayWait && !mProcessHighPriorityQueueRunnable &&
+                         pendingInputCount == 0;
 
-    // Let's see if we should next time process an event from the secondary
-    // queue.
-    mProcessSecondaryQueueRunnable =
-      mSecondaryQueue->HasPendingEvent(aProofOfLock);
-
+    retVal = mNormalQueue->GetEvent(reallyMayWait, aEvent, aProofOfLock);
     if (*aEvent) {
       // We got an event, return early.
+      SetPriorityIfNotNull(aPriority, nsIRunnablePriority::PRIORITY_NORMAL);
+      return retVal;
+    }
+    if (pendingInputCount > 0 && !mProcessHighPriorityQueueRunnable) {
+      // Handle input events if we have time for them.
+      MOZ_ASSERT(mInputQueue->HasPendingEvent(aProofOfLock));
+      retVal = mInputQueue->GetEvent(false, aEvent, aProofOfLock);
+      MOZ_ASSERT(*aEvent);
+      SetPriorityIfNotNull(aPriority, nsIRunnablePriority::PRIORITY_INPUT);
+      return retVal;
+    }
+  } while (aMayWait || mProcessHighPriorityQueueRunnable);
+  return retVal;
+}
+
+bool
+nsThread::nsChainedEventQueue::
+GetNormalOrHighPriorityEvent(bool aMayWait, nsIRunnable** aEvent,
+                             unsigned short* aPriority,
+                             MutexAutoLock& aProofOfLock)
+{
+  bool retVal = false;
+  do {
+    // Use mProcessHighPriorityQueueRunnable to prevent the high priority events
+    // from consuming all cpu time and causing starvation.
+    if (mProcessHighPriorityQueueRunnable) {
+      MOZ_ASSERT(mHighQueue->HasPendingEvent(aProofOfLock));
+      retVal = mHighQueue->GetEvent(false, aEvent, aProofOfLock);
+      MOZ_ASSERT(*aEvent);
+      SetPriorityIfNotNull(aPriority, nsIRunnablePriority::PRIORITY_HIGH);
+      mProcessHighPriorityQueueRunnable = false;
       return retVal;
     }
-  } while(aMayWait || mProcessSecondaryQueueRunnable);
+    mProcessHighPriorityQueueRunnable =
+      mHighQueue->HasPendingEvent(aProofOfLock);
+
+    // We don't want to wait if there are some events in the high priority
+    // queue.
+    bool reallyMayWait = aMayWait && !mProcessHighPriorityQueueRunnable;
+
+    retVal = mNormalQueue->GetEvent(reallyMayWait, aEvent, aProofOfLock);
+    if (*aEvent) {
+      // We got an event, return early.
+      SetPriorityIfNotNull(aPriority, nsIRunnablePriority::PRIORITY_NORMAL);
+      return retVal;
+    }
+  } while (aMayWait || mProcessHighPriorityQueueRunnable);
+  return retVal;
+}
 
-  return retVal;
+void
+nsThread::nsChainedEventQueue::PutEvent(already_AddRefed<nsIRunnable> aEvent,
+                                        MutexAutoLock& aProofOfLock)
+{
+  RefPtr<nsIRunnable> event(aEvent);
+  nsCOMPtr<nsIRunnablePriority> runnablePrio(do_QueryInterface(event));
+  uint32_t prio = nsIRunnablePriority::PRIORITY_NORMAL;
+  if (runnablePrio) {
+    runnablePrio->GetPriority(&prio);
+  }
+  switch (prio) {
+  case nsIRunnablePriority::PRIORITY_NORMAL:
+    mNormalQueue->PutEvent(event.forget(), aProofOfLock);
+    break;
+  case nsIRunnablePriority::PRIORITY_INPUT:
+    if (mIsInputPrioritizationEnabled) {
+      mInputQueue->PutEvent(event.forget(), aProofOfLock);
+    } else {
+      mNormalQueue->PutEvent(event.forget(), aProofOfLock);
+    }
+    break;
+  case nsIRunnablePriority::PRIORITY_HIGH:
+    if (mIsInputPrioritizationEnabled) {
+      mHighQueue->PutEvent(event.forget(), aProofOfLock);
+    } else {
+      // During startup, ContentParent sends SetXPCOMProcessAttributes to
+      // initialize ContentChild and enable input event prioritization. After
+      // that, ContentParent sends PBrowserConstructor to create PBrowserChild.
+      // To prevent PBrowserConstructor preempt SetXPCOMProcessAttributes and
+      // cause problems, we have to put high priority events in mNormalQueue to
+      // keep the correct order of initialization.
+      mNormalQueue->PutEvent(event.forget(), aProofOfLock);
+    }
+    break;
+  default:
+    MOZ_ASSERT(false);
+    break;
+  }
 }
 
 //-----------------------------------------------------------------------------
 // nsIEventTarget
 
 NS_IMETHODIMP
 nsThread::DispatchFromScript(nsIRunnable* aEvent, uint32_t aFlags)
 {
@@ -1160,16 +1272,34 @@ nsThread::IdleDispatch(already_AddRefed<
     NS_WARNING("An idle event was posted to a thread that will never run it (rejected)");
     return NS_ERROR_UNEXPECTED;
   }
 
   mIdleEvents.PutEvent(event.take(), lock);
   return NS_OK;
 }
 
+NS_IMETHODIMP
+nsThread::EnableEventPrioritization()
+{
+  MOZ_ASSERT(NS_IsMainThread());
+  MutexAutoLock lock(mLock);
+  // Only support event prioritization for main event queue.
+  mEventsRoot.EnablePrioritization(lock);
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+nsThread::IsEventPrioritizationEnabled(bool* aResult)
+{
+  MOZ_ASSERT(NS_IsMainThread());
+  *aResult = mEventsRoot.IsPrioritizationEnabled();
+  return NS_OK;
+}
+
 #ifdef MOZ_CANARY
 void canary_alarm_handler(int signum);
 
 class Canary
 {
   //XXX ToDo: support nested loops
 public:
   Canary()
@@ -1437,17 +1567,20 @@ nsThread::ProcessNextEvent(bool aMayWait
         // Copy the name into sMainThreadRunnableName's buffer, and append a
         // terminating null.
         uint32_t length = std::min((uint32_t) kRunnableNameBufSize - 1,
                                    (uint32_t) name.Length());
         memcpy(sMainThreadRunnableName.begin(), name.BeginReading(), length);
         sMainThreadRunnableName[length] = '\0';
       }
 #endif
-
+      Maybe<AutoTimeDurationHelper> timeDurationHelper;
+      if (priority == nsIRunnablePriority::PRIORITY_INPUT) {
+        timeDurationHelper.emplace();
+      }
       event->Run();
     } else if (aMayWait) {
       MOZ_ASSERT(ShuttingDown(),
                  "This should only happen when shutting down");
       rv = NS_ERROR_UNEXPECTED;
     }
   }
 
--- a/xpcom/threads/nsThread.h
+++ b/xpcom/threads/nsThread.h
@@ -92,16 +92,26 @@ public:
   };
 
   static bool SaveMemoryReportNearOOM(ShouldSaveMemoryReport aShouldSave);
 #endif
 
   static const uint32_t kRunnableNameBufSize = 1000;
   static mozilla::Array<char, kRunnableNameBufSize> sMainThreadRunnableName;
 
+  // Query whether there are some pending input events in the queue. This method
+  // is supposed to be called on main thread with input event prioritization
+  // enabled.
+  bool HasPendingInputEvents()
+  {
+    MOZ_ASSERT(NS_IsMainThread());
+    mozilla::MutexAutoLock lock(mLock);
+    return mEventsRoot.HasPendingEventsInInputQueue(lock);
+  }
+
 private:
   void DoMainThreadSpecificProcessing(bool aReallyWait);
 
   // Returns a null TimeStamp if we're not in the idle period.
   mozilla::TimeStamp GetIdleDeadline();
   void GetIdleEvent(nsIRunnable** aEvent, mozilla::MutexAutoLock& aProofOfLock);
   void GetEvent(bool aWait, nsIRunnable** aEvent,
                 unsigned short* aPriority,
@@ -137,82 +147,137 @@ protected:
   nsresult PutEvent(already_AddRefed<nsIRunnable> aEvent,
                     nsNestedEventTarget* aTarget);
 
   nsresult DispatchInternal(already_AddRefed<nsIRunnable> aEvent,
                             uint32_t aFlags, nsNestedEventTarget* aTarget);
 
   struct nsThreadShutdownContext* ShutdownInternal(bool aSync);
 
-  // Wrapper for nsEventQueue that supports chaining.
+  // Wrapper for nsEventQueue that supports chaining and prioritization.
   class nsChainedEventQueue
   {
   public:
     explicit nsChainedEventQueue(mozilla::Mutex& aLock)
       : mNext(nullptr)
       , mEventsAvailable(aLock, "[nsChainedEventQueue.mEventsAvailable]")
-      , mProcessSecondaryQueueRunnable(false)
+      , mIsInputPrioritizationEnabled(false)
+      , mIsReadyToPrioritizeEvents(false)
+      , mProcessHighPriorityQueueRunnable(false)
     {
       mNormalQueue =
         mozilla::MakeUnique<nsEventQueue>(mEventsAvailable,
                                           nsEventQueue::eSharedCondVarQueue);
-      // Both queues need to use the same CondVar!
-      mSecondaryQueue =
+      // All queues need to use the same CondVar!
+      mInputQueue =
+        mozilla::MakeUnique<nsEventQueue>(mEventsAvailable,
+                                          nsEventQueue::eSharedCondVarQueue);
+      mHighQueue =
         mozilla::MakeUnique<nsEventQueue>(mEventsAvailable,
                                           nsEventQueue::eSharedCondVarQueue);
     }
 
+    void EnablePrioritization(mozilla::MutexAutoLock& aProofOfLock);
+
+    bool IsPrioritizationEnabled()
+    {
+      return mIsInputPrioritizationEnabled;
+    }
+
     bool GetEvent(bool aMayWait, nsIRunnable** aEvent,
                   unsigned short* aPriority,
-                  mozilla::MutexAutoLock& aProofOfLock);
+                  mozilla::MutexAutoLock& aProofOfLock) {
+      return mIsReadyToPrioritizeEvents
+        ? GetNormalOrInputOrHighPriorityEvent(aMayWait, aEvent, aPriority, aProofOfLock)
+        : GetNormalOrHighPriorityEvent(aMayWait, aEvent, aPriority, aProofOfLock);
+    }
 
     void PutEvent(nsIRunnable* aEvent, mozilla::MutexAutoLock& aProofOfLock)
     {
       RefPtr<nsIRunnable> event(aEvent);
       PutEvent(event.forget(), aProofOfLock);
     }
 
     void PutEvent(already_AddRefed<nsIRunnable> aEvent,
-                  mozilla::MutexAutoLock& aProofOfLock)
-    {
-      RefPtr<nsIRunnable> event(aEvent);
-      nsCOMPtr<nsIRunnablePriority> runnablePrio =
-        do_QueryInterface(event);
-      uint32_t prio = nsIRunnablePriority::PRIORITY_NORMAL;
-      if (runnablePrio) {
-        runnablePrio->GetPriority(&prio);
-      }
-      MOZ_ASSERT(prio == nsIRunnablePriority::PRIORITY_NORMAL ||
-                 prio == nsIRunnablePriority::PRIORITY_HIGH);
-      if (prio == nsIRunnablePriority::PRIORITY_NORMAL) {
-        mNormalQueue->PutEvent(event.forget(), aProofOfLock);
-      } else {
-        mSecondaryQueue->PutEvent(event.forget(), aProofOfLock);
-      }
-    }
+                  mozilla::MutexAutoLock& aProofOfLock);
 
     bool HasPendingEvent(mozilla::MutexAutoLock& aProofOfLock)
     {
       return mNormalQueue->HasPendingEvent(aProofOfLock) ||
-             mSecondaryQueue->HasPendingEvent(aProofOfLock);
+             mInputQueue->HasPendingEvent(aProofOfLock) ||
+             mHighQueue->HasPendingEvent(aProofOfLock);
+    }
+
+    bool HasPendingEventsInInputQueue(mozilla::MutexAutoLock& aProofOfLock)
+    {
+      MOZ_ASSERT(mIsInputPrioritizationEnabled);
+      return mInputQueue->HasPendingEvent(aProofOfLock);
     }
 
     nsChainedEventQueue* mNext;
     RefPtr<nsNestedEventTarget> mEventTarget;
 
   private:
+    bool GetNormalOrInputOrHighPriorityEvent(bool aMayWait,
+                                             nsIRunnable** aEvent,
+                                             unsigned short* aPriority,
+                                             mozilla::MutexAutoLock& aProofOfLock);
+
+    bool GetNormalOrHighPriorityEvent(bool aMayWait, nsIRunnable** aEvent,
+                                      unsigned short* aPriority,
+                                      mozilla::MutexAutoLock& aProofOfLock);
+
+    // This is used to flush pending events in nsChainedEventQueue::mNormalQueue
+    // before starting event prioritization.
+    class EnablePrioritizationRunnable final : public nsIRunnable
+    {
+      nsChainedEventQueue* mEventQueue;
+
+    public:
+      NS_DECL_ISUPPORTS
+
+      explicit EnablePrioritizationRunnable(nsChainedEventQueue* aQueue)
+        : mEventQueue(aQueue)
+      {
+      }
+
+      NS_IMETHOD Run() override
+      {
+        mEventQueue->mIsReadyToPrioritizeEvents = true;
+        return NS_OK;
+      }
+    private:
+      ~EnablePrioritizationRunnable()
+      {
+      }
+    };
+
+    static void SetPriorityIfNotNull(unsigned short* aPriority, short aValue)
+    {
+      if (aPriority) {
+        *aPriority = aValue;
+      }
+    }
     mozilla::CondVar mEventsAvailable;
+    mozilla::TimeStamp mInputHandlingStartTime;
     mozilla::UniquePtr<nsEventQueue> mNormalQueue;
-    mozilla::UniquePtr<nsEventQueue> mSecondaryQueue;
+    mozilla::UniquePtr<nsEventQueue> mInputQueue;
+    mozilla::UniquePtr<nsEventQueue> mHighQueue;
+    bool mIsInputPrioritizationEnabled;
 
+    // When enabling input event prioritization, there may be some events in the
+    // queue. We have to process all of them before the new coming events to
+    // prevent the queued events are preempted by the newly ones with the same
+    // priority.
+    bool mIsReadyToPrioritizeEvents;
     // Try to process one high priority runnable after each normal
     // priority runnable. This gives the processing model HTML spec has for
     // 'Update the rendering' in the case only vsync messages are in the
     // secondary queue and prevents starving the normal queue.
-    bool mProcessSecondaryQueueRunnable;
+    bool mProcessHighPriorityQueueRunnable;
   };
 
   class nsNestedEventTarget final : public nsIEventTarget
   {
   public:
     NS_DECL_THREADSAFE_ISUPPORTS
     NS_DECL_NSIEVENTTARGET_FULL
 
--- a/xpcom/threads/nsThreadManager.cpp
+++ b/xpcom/threads/nsThreadManager.cpp
@@ -6,17 +6,19 @@
 
 #include "nsThreadManager.h"
 #include "nsThread.h"
 #include "nsThreadUtils.h"
 #include "nsIClassInfoImpl.h"
 #include "nsTArray.h"
 #include "nsAutoPtr.h"
 #include "mozilla/AbstractThread.h"
+#include "mozilla/InputEventStatistics.h"
 #include "mozilla/ThreadLocal.h"
+#include "mozilla/Preferences.h"
 #ifdef MOZ_CANARY
 #include <fcntl.h>
 #include <unistd.h>
 #endif
 
 #include "MainThreadIdlePeriod.h"
 
 using namespace mozilla;
@@ -379,27 +381,50 @@ nsThreadManager::SpinEventLoopUntilEmpty
 uint32_t
 nsThreadManager::GetHighestNumberOfThreads()
 {
   OffTheBooksMutexAutoLock lock(mLock);
   return mHighestNumberOfThreads;
 }
 
 NS_IMETHODIMP
-nsThreadManager::DispatchToMainThread(nsIRunnable *aEvent)
+nsThreadManager::DispatchToMainThread(nsIRunnable *aEvent, uint32_t aPriority)
 {
   // Note: C++ callers should instead use NS_DispatchToMainThread.
   MOZ_ASSERT(NS_IsMainThread());
 
   // Keep this functioning during Shutdown
   if (NS_WARN_IF(!mMainThread)) {
     return NS_ERROR_NOT_INITIALIZED;
   }
+  if (aPriority != nsIRunnablePriority::PRIORITY_NORMAL) {
+    nsCOMPtr<nsIRunnable> event(aEvent);
+    return mMainThread->DispatchFromScript(
+             new PrioritizableRunnable(event.forget(), aPriority), 0);
+  }
+  return mMainThread->DispatchFromScript(aEvent, 0);
+}
 
-  return mMainThread->DispatchFromScript(aEvent, 0);
+void
+nsThreadManager::EnableMainThreadEventPrioritization()
+{
+  static bool sIsInitialized = false;
+  if (sIsInitialized) {
+    return;
+  }
+  sIsInitialized = true;
+  MOZ_ASSERT(Preferences::IsServiceAvailable());
+  bool enable =
+    Preferences::GetBool("prioritized_input_events.enabled", false);
+
+  if (!enable) {
+    return;
+  }
+  InputEventStatistics::Get().SetEnable(true);
+  mMainThread->EnableEventPrioritization();
 }
 
 NS_IMETHODIMP
 nsThreadManager::IdleDispatchToMainThread(nsIRunnable *aEvent, uint32_t aTimeout)
 {
   // Note: C++ callers should instead use NS_IdleDispatchToThread or
   // NS_IdleDispatchToCurrentThread.
   MOZ_ASSERT(NS_IsMainThread());
--- a/xpcom/threads/nsThreadManager.h
+++ b/xpcom/threads/nsThreadManager.h
@@ -48,16 +48,17 @@ public:
   // simultaneously during the execution of the thread manager.
   uint32_t GetHighestNumberOfThreads();
 
   // This needs to be public in order to support static instantiation of this
   // class with older compilers (e.g., egcs-2.91.66).
   ~nsThreadManager()
   {
   }
+  void EnableMainThreadEventPrioritization();
 
 private:
   nsThreadManager()
     : mCurThreadIndex(0)
     , mMainPRThread(nullptr)
     , mLock("nsThreadManager.mLock")
     , mInitialized(false)
     , mCurrentNumberOfThreads(1)
--- a/xpcom/threads/nsThreadUtils.cpp
+++ b/xpcom/threads/nsThreadUtils.cpp
@@ -92,16 +92,57 @@ namespace detail {
 already_AddRefed<nsITimer> CreateTimer()
 {
   nsCOMPtr<nsITimer> timer = do_CreateInstance(NS_TIMER_CONTRACTID);
   return timer.forget();
 }
 } // namespace detail
 } // namespace mozilla
 
+NS_IMPL_ISUPPORTS_INHERITED(PrioritizableRunnable, Runnable,
+                            nsIRunnablePriority)
+
+PrioritizableRunnable::PrioritizableRunnable(already_AddRefed<nsIRunnable>&& aRunnable,
+                                             uint32_t aPriority)
+ // Real runnable name is managed by overridding the GetName function.
+ : Runnable("PrioritizableRunnable")
+ , mRunnable(Move(aRunnable))
+ , mPriority(aPriority)
+{
+#if DEBUG
+  nsCOMPtr<nsIRunnablePriority> runnablePrio = do_QueryInterface(mRunnable);
+  MOZ_ASSERT(!runnablePrio);
+#endif
+}
+
+NS_IMETHODIMP
+PrioritizableRunnable::GetName(nsACString& aName)
+{
+  // Try to get a name from the underlying runnable.
+  nsCOMPtr<nsINamed> named = do_QueryInterface(mRunnable);
+  if (named) {
+    named->GetName(aName);
+  }
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+PrioritizableRunnable::Run()
+{
+  MOZ_RELEASE_ASSERT(NS_IsMainThread());
+  return mRunnable->Run();
+}
+
+NS_IMETHODIMP
+PrioritizableRunnable::GetPriority(uint32_t* aPriority)
+{
+  *aPriority = mPriority;
+  return NS_OK;
+}
+
 #endif  // XPCOM_GLUE_AVOID_NSPR
 
 //-----------------------------------------------------------------------------
 
 nsresult
 NS_NewNamedThread(const nsACString& aName,
                   nsIThread** aResult,
                   nsIRunnable* aEvent,
--- a/xpcom/threads/nsThreadUtils.h
+++ b/xpcom/threads/nsThreadUtils.h
@@ -481,16 +481,36 @@ public:
 protected:
   virtual ~IdleRunnable() {}
 private:
   IdleRunnable(const IdleRunnable&) = delete;
   IdleRunnable& operator=(const IdleRunnable&) = delete;
   IdleRunnable& operator=(const IdleRunnable&&) = delete;
 };
 
+// This class is designed to be a wrapper of a real runnable to support event
+// prioritizable.
+class PrioritizableRunnable : public Runnable, public nsIRunnablePriority
+{
+public:
+  PrioritizableRunnable(already_AddRefed<nsIRunnable>&& aRunnable,
+                        uint32_t aPriority);
+
+  NS_IMETHOD GetName(nsACString& aName) override;
+
+  NS_DECL_ISUPPORTS_INHERITED
+  NS_DECL_NSIRUNNABLE
+  NS_DECL_NSIRUNNABLEPRIORITY
+
+protected:
+  virtual ~PrioritizableRunnable() {};
+  nsCOMPtr<nsIRunnable> mRunnable;
+  uint32_t mPriority;
+};
+
 namespace detail {
 
 // An event that can be used to call a C++11 functions or function objects,
 // including lambdas. The function must have no required arguments, and must
 // return void.
 template<typename StoredFunction>
 class RunnableFunction : public Runnable
 {
--- a/xpcom/threads/nsTimerImpl.h
+++ b/xpcom/threads/nsTimerImpl.h
@@ -127,17 +127,17 @@ public:
     Name mName;
 
     void*                 mClosure;
   };
 
   nsresult InitCommon(uint32_t aDelayMS, uint32_t aType,
                       Callback&& newCallback);
 
-  nsresult InitCommon(const TimeDuration& aDelay, uint32_t aType,
+  nsresult InitCommon(const mozilla::TimeDuration& aDelay, uint32_t aType,
                       Callback&& newCallback);
 
   Callback& GetCallback()
   {
     mMutex.AssertCurrentThreadOwns();
     if (mCallback.mType == Callback::Type::Unknown) {
       return mCallbackDuringFire;
     }
@@ -195,19 +195,19 @@ public:
   uint8_t               mType;
 
   // The generation number of this timer, re-generated each time the timer is
   // initialized so one-shot timers can be canceled and re-initialized by the
   // arming thread without any bad race conditions.
   // Updated only after this timer has been removed from the timer thread.
   int32_t               mGeneration;
 
-  TimeDuration          mDelay;
+  mozilla::TimeDuration mDelay;
   // Updated only after this timer has been removed from the timer thread.
-  TimeStamp             mTimeout;
+  mozilla::TimeStamp    mTimeout;
 
 #ifdef MOZ_TASK_TRACER
   mozilla::tasktracer::TracedTaskCommon mTracedTask;
 #endif
 
   static double         sDeltaSum;
   static double         sDeltaSumSquared;
   static double         sDeltaNum;