Merge mozilla-central to autoland. CLOSED TREE
authorCsoregi Natalia <ncsoregi@mozilla.com>
Thu, 25 Oct 2018 07:47:13 +0300
changeset 491268 034a7a732317492c49636902cb760ea2703daa9d
parent 491267 4ef080fed9d2246509f6ebf04652e60f7d76402e (current diff)
parent 491249 76d5b62fb151f9d28edd7a626c7757ec8fdeda47 (diff)
child 491269 6437c4d882fd5375b5ba58646f0bf4b219b0f75e
push id247
push userfmarier@mozilla.com
push dateSat, 27 Oct 2018 01:06:44 +0000
milestone65.0a1
Merge mozilla-central to autoland. CLOSED TREE
build/build-clang/clang-6-macosx64.json
dom/xbl/builtin/android/jar.mn
dom/xbl/builtin/android/platformHTMLBindings.xml
dom/xbl/builtin/browser-base.inc
dom/xbl/builtin/editor-base.inc
dom/xbl/builtin/emacs/jar.mn
dom/xbl/builtin/emacs/platformHTMLBindings.xml
dom/xbl/builtin/input-fields-base.inc
dom/xbl/builtin/mac/jar.mn
dom/xbl/builtin/mac/platformHTMLBindings.xml
dom/xbl/builtin/textareas-base.inc
dom/xbl/builtin/unix/jar.mn
dom/xbl/builtin/unix/platformHTMLBindings.xml
dom/xbl/builtin/win/jar.mn
dom/xbl/builtin/win/platformHTMLBindings.xml
taskcluster/scripts/misc/build-clang-6-linux-macosx-cross.sh
--- a/browser/base/content/test/performance/browser.ini
+++ b/browser/base/content/test/performance/browser.ini
@@ -19,16 +19,18 @@ skip-if = !debug
 [browser_startup.js]
 [browser_startup_content.js]
 skip-if = !e10s
 [browser_startup_flicker.js]
 run-if = debug || devedition || nightly_build # Requires startupRecorder.js, which isn't shipped everywhere by default
 [browser_tabclose_grow.js]
 [browser_tabclose.js]
 skip-if = (os == 'win' && bits == 32) # Bug 1488537
+[browser_tabdetach.js]
+skip-if = debug # Bug 1501789
 [browser_tabopen.js]
 skip-if = (verify && (os == 'mac'))
 [browser_tabopen_squeeze.js]
 [browser_tabstrip_overflow_underflow.js]
 skip-if = (verify && !debug && (os == 'win'))
 [browser_tabswitch.js]
 [browser_toolbariconcolor_restyles.js]
 [browser_urlbar_keyed_search.js]
new file mode 100644
--- /dev/null
+++ b/browser/base/content/test/performance/browser_tabdetach.js
@@ -0,0 +1,106 @@
+"use strict";
+
+/**
+ * WHOA THERE: We should never be adding new things to EXPECTED_REFLOWS. This
+ * is a whitelist that should slowly go away as we improve the performance of
+ * the front-end. Instead of adding more reflows to the whitelist, you should
+ * be modifying your code to avoid the reflow.
+ *
+ * See https://developer.mozilla.org/en-US/Firefox/Performance_best_practices_for_Firefox_fe_engineers
+ * for tips on how to do that.
+ */
+const EXPECTED_REFLOWS = [
+  {
+    stack: [
+      "clientX@chrome://browser/content/tabbrowser.xml",
+      "onxbldragstart@chrome://browser/content/tabbrowser.xml",
+      "synthesizeMouseAtPoint@chrome://mochikit/content/tests/SimpleTest/EventUtils.js",
+      "synthesizeMouse@chrome://mochikit/content/tests/SimpleTest/EventUtils.js",
+      "synthesizePlainDragAndDrop@chrome://mochikit/content/tests/SimpleTest/EventUtils.js",
+    ],
+    maxCount: 2,
+  },
+
+  {
+    stack: [
+      "onxbldragstart@chrome://browser/content/tabbrowser.xml",
+      "synthesizeMouseAtPoint@chrome://mochikit/content/tests/SimpleTest/EventUtils.js",
+      "synthesizeMouse@chrome://mochikit/content/tests/SimpleTest/EventUtils.js",
+      "synthesizePlainDragAndDrop@chrome://mochikit/content/tests/SimpleTest/EventUtils.js",
+    ],
+  },
+];
+
+/**
+ * This test ensures that there are no unexpected uninterruptible reflows when
+ * detaching a tab via drag and drop. The first testcase tests a non-overflowed
+ * tab strip, and the second tests an overflowed one.
+ */
+
+add_task(async function test_detach_not_overflowed() {
+  await ensureNoPreloadedBrowser();
+  await createTabs(1);
+
+  // Make sure we didn't overflow, as expected
+  await BrowserTestUtils.waitForCondition(() => {
+    return !gBrowser.tabContainer.hasAttribute("overflow");
+  });
+
+  let win;
+  await withPerfObserver(async function() {
+    win = await detachTab(gBrowser.tabs[1]);
+  }, {
+    expectedReflows: EXPECTED_REFLOWS,
+    // we are opening a whole new window, so there's no point in tracking
+    // rects being painted
+    frames: { filter: rects => [] },
+  });
+
+  await BrowserTestUtils.closeWindow(win);
+  win = null;
+});
+
+add_task(async function test_detach_overflowed() {
+  const TAB_COUNT_FOR_OVERFLOW = computeMaxTabCount();
+  await createTabs(TAB_COUNT_FOR_OVERFLOW + 1);
+
+  // Make sure we overflowed, as expected
+  await BrowserTestUtils.waitForCondition(() => {
+    return gBrowser.tabContainer.hasAttribute("overflow");
+  });
+
+  let win;
+  await withPerfObserver(async function() {
+    win = await detachTab(gBrowser.tabs[Math.floor(TAB_COUNT_FOR_OVERFLOW / 2)]);
+  }, {
+    expectedReflows: EXPECTED_REFLOWS,
+    // we are opening a whole new window, so there's no point in tracking
+    // rects being painted
+    frames: { filter: rects => [] },
+  });
+
+  await BrowserTestUtils.closeWindow(win);
+  win = null;
+
+  await removeAllButFirstTab();
+});
+
+async function detachTab(tab) {
+  let newWindowPromise = BrowserTestUtils.waitForNewWindow();
+
+  await EventUtils.synthesizePlainDragAndDrop({
+    srcElement: tab,
+
+    // destElement is null because tab detaching happens due
+    // to a drag'n'drop on an invalid drop target.
+    destElement: null,
+
+    // don't move horizontally because that could cause a tab move
+    // animation, and there's code to prevent a tab detaching if
+    // the dragged tab is released while the animation is running.
+    stepX: 0,
+    stepY: 100,
+  });
+
+  return newWindowPromise;
+}
--- a/browser/base/content/test/performance/head.js
+++ b/browser/base/content/test/performance/head.js
@@ -125,31 +125,39 @@ async function recordReflows(testPromise
  */
 function reportUnexpectedReflows(reflows, expectedReflows = []) {
   let knownReflows = expectedReflows.map(r => {
     return {stack: r.stack, path: r.stack.join("|"),
             count: 0, maxCount: r.maxCount || 1,
             actualStacks: new Map()};
   });
   let unexpectedReflows = new Map();
+
+  if (knownReflows.some(r => r.path.includes("*"))) {
+    Assert.ok(false,
+              "Do not include async frames in the stack, as " +
+              "that feature is not available on all trees.");
+  }
+
   for (let stack of reflows) {
     let path =
       stack.split("\n").slice(1) // the first frame which is our test code.
            .map(line => line.replace(/:\d+:\d+$/, "")) // strip line numbers.
            .join("|");
 
     // Stack trace is empty. Reflow was triggered by native code, which
     // we ignore.
     if (path === "") {
       continue;
     }
 
-    // synthesizeKey from EventUtils.js causes us to reflow. That's the test
+    // Functions from EventUtils.js calculate coordinates and
+    // dimensions, causing us to reflow. That's the test
     // harness and we don't care about that, so we'll filter that out.
-    if (path.startsWith("synthesizeKey@chrome://mochikit/content/tests/SimpleTest/EventUtils.js")) {
+    if (/^(synthesize|send|createDragEventObject).*?@chrome:\/\/mochikit.*?EventUtils\.js/.test(path)) {
       continue;
     }
 
     let index = knownReflows.findIndex(reflow => path.startsWith(reflow.path));
     if (index != -1) {
       let reflow = knownReflows[index];
       ++reflow.count;
       reflow.actualStacks.set(stack, (reflow.actualStacks.get(stack) || 0) + 1);
--- a/browser/installer/windows/nsis/defines.nsi.in
+++ b/browser/installer/windows/nsis/defines.nsi.in
@@ -134,8 +134,14 @@ VIAddVersionKey "ProductVersion"  "${App
 !define PROFILE_CLEANUP_LABEL_TOP_DU 39u
 !define NOW_INSTALLING_TOP_DU 70u
 !define INSTALL_BLURB_TOP_DU 137u
 !define INSTALL_FOOTER_TOP_DU -48u
 !define INSTALL_FOOTER_WIDTH_DU 250u
 !define PROGRESS_BAR_TOP_DU 112u
 !define APPNAME_BMP_EDGE_DU 19u
 !define APPNAME_BMP_TOP_DU 12u
+
+# Constants for parts of the telemetry submission URL
+!define TELEMETRY_BASE_URL https://incoming.telemetry.mozilla.org/submit
+!define TELEMETRY_NAMESPACE firefox-installer
+!define TELEMETRY_INSTALL_PING_VERSION 1
+!define TELEMETRY_INSTALL_PING_DOCTYPE install
--- a/browser/installer/windows/nsis/installer.nsi
+++ b/browser/installer/windows/nsis/installer.nsi
@@ -1,16 +1,17 @@
 # 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/.
 
 # Required Plugins:
 # AppAssocReg    http://nsis.sourceforge.net/Application_Association_Registration_plug-in
 # ApplicationID  http://nsis.sourceforge.net/ApplicationID_plug-in
 # CityHash       http://dxr.mozilla.org/mozilla-central/source/other-licenses/nsis/Contrib/CityHash
+# nsJSON         http://nsis.sourceforge.net/NsJSON_plug-in
 # ShellLink      http://nsis.sourceforge.net/ShellLink_plug-in
 # UAC            http://nsis.sourceforge.net/UAC_plug-in
 # ServicesHelper Mozilla specific plugin that is located in /other-licenses/nsis
 
 ; Set verbosity to 3 (e.g. no script) to lessen the noise in the build logs
 !verbose 3
 
 ; 7-Zip provides better compression than the lzma from NSIS so we add the files
@@ -34,16 +35,28 @@ Var AddTaskbarSC
 Var AddQuickLaunchSC
 Var AddDesktopSC
 Var InstallMaintenanceService
 Var InstallOptionalExtensions
 Var ExtensionRecommender
 Var PageName
 Var PreventRebootRequired
 
+; Telemetry ping fields
+Var SetAsDefault
+Var HadOldInstall
+Var DefaultInstDir
+Var IntroPhaseStart
+Var OptionsPhaseStart
+Var InstallPhaseStart
+Var FinishPhaseStart
+Var FinishPhaseEnd
+Var InstallResult
+Var LaunchedNewApp
+
 ; By defining NO_STARTMENU_DIR an installer that doesn't provide an option for
 ; an application's Start Menu PROGRAMS directory and doesn't define the
 ; StartMenuDir variable can use the common InstallOnInitCommon macro.
 !define NO_STARTMENU_DIR
 
 ; Attempt to elevate Standard Users in addition to users that
 ; are a member of the Administrators group.
 !define NONADMIN_ELEVATE
@@ -79,16 +92,17 @@ VIAddVersionKey "OriginalFilename" "setu
 !insertmacro _LoggingCommon
 
 !insertmacro AddDisabledDDEHandlerValues
 !insertmacro ChangeMUIHeaderImage
 !insertmacro CheckForFilesInUse
 !insertmacro CleanUpdateDirectories
 !insertmacro CopyFilesFromDir
 !insertmacro CreateRegKey
+!insertmacro GetFirstInstallPath
 !insertmacro GetLongPath
 !insertmacro GetPathFromString
 !insertmacro GetParent
 !insertmacro InitHashAppModelId
 !insertmacro IsHandlerForInstallDir
 !insertmacro IsPinnedToTaskBar
 !insertmacro IsUserAdmin
 !insertmacro LogDesktopShortcut
@@ -183,26 +197,30 @@ Page custom preSummary leaveSummary
 !insertmacro MUI_PAGE_INSTFILES
 
 ; Finish Page
 !define MUI_FINISHPAGE_TITLE_3LINES
 !define MUI_FINISHPAGE_RUN
 !define MUI_FINISHPAGE_RUN_FUNCTION LaunchApp
 !define MUI_FINISHPAGE_RUN_TEXT $(LAUNCH_TEXT)
 !define MUI_PAGE_CUSTOMFUNCTION_PRE preFinish
+!define MUI_PAGE_CUSTOMFUNCTION_LEAVE postFinish
 !insertmacro MUI_PAGE_FINISH
 
 ; Use the default dialog for IDD_VERIFY for a simple Banner
 ChangeUI IDD_VERIFY "${NSISDIR}\Contrib\UIs\default.exe"
 
 ################################################################################
 # Install Sections
 
 ; Cleanup operations to perform at the start of the installation.
 Section "-InstallStartCleanup"
+  System::Call "kernel32::GetTickCount()l .s"
+  Pop $InstallPhaseStart
+
   SetDetailsPrint both
   DetailPrint $(STATUS_CLEANUP)
   SetDetailsPrint none
 
   SetOutPath "$INSTDIR"
   ${StartInstallLog} "${BrandFullName}" "${AB_CD}" "${AppVersion}" "${GREVersion}"
 
   StrCpy $R9 "true"
@@ -658,17 +676,17 @@ Section "-InstallEndCleanup"
   SetDetailsPrint both
   DetailPrint "$(STATUS_CLEANUP)"
   SetDetailsPrint none
 
   ${Unless} ${Silent}
     ClearErrors
     ${MUI_INSTALLOPTIONS_READ} $0 "summary.ini" "Field 4" "State"
     ${If} "$0" == "1"
-      ; NB: this code is duplicated in stub.nsi. Please keep in sync.
+      StrCpy $SetAsDefault true
       ; For data migration in the app, we want to know what the default browser
       ; value was before we changed it. To do so, we read it here and store it
       ; in our own registry key.
       StrCpy $0 ""
       AppAssocReg::QueryCurrentDefault "http" "protocol" "effective"
       Pop $1
       ; If the method hasn't failed, $1 will contain the progid. Check:
       ${If} "$1" != "method failed"
@@ -693,16 +711,17 @@ Section "-InstallEndCleanup"
       ${GetOptions} "$0" "/UAC:" $0
       ${If} ${Errors}
         Call SetAsDefaultAppUserHKCU
       ${Else}
         GetFunctionAddress $0 SetAsDefaultAppUserHKCU
         UAC::ExecCodeSegment $0
       ${EndIf}
     ${ElseIfNot} ${Errors}
+      StrCpy $SetAsDefault false
       ${LogHeader} "Writing default-browser opt-out"
       ClearErrors
       WriteRegStr HKCU "Software\Mozilla\Firefox" "DefaultBrowserOptOut" "True"
       ${If} ${Errors}
         ${LogMsg} "Error writing default-browser opt-out"
       ${EndIf}
     ${EndIf}
   ${EndUnless}
@@ -751,16 +770,24 @@ Section "-InstallEndCleanup"
         FileWrite $0 "Will be deleted on restart"
         Rename /REBOOTOK "$INSTDIR\${FileMainEXE}.moz-upgrade" "$INSTDIR\${FileMainEXE}"
         FileClose $0
         Delete "$INSTDIR\${FileMainEXE}"
         Rename "$INSTDIR\helper.exe" "$INSTDIR\${FileMainEXE}"
       ${EndUnless}
     ${EndIf}
   ${EndIf}
+
+  StrCpy $InstallResult "success"
+
+  ; When we're using the GUI, .onGUIEnd sends the ping, but of course that isn't
+  ; invoked when we're running silently.
+  ${If} ${Silent}
+    Call SendPing
+  ${EndIf}
 SectionEnd
 
 ################################################################################
 # Install Abort Survey Functions
 
 Function CustomAbort
   ${If} "${AB_CD}" == "en-US"
   ${AndIf} "$PageName" != ""
@@ -899,25 +926,191 @@ Function LaunchApp
   ${GetParameters} $0
   ${GetOptions} "$0" "/UAC:" $1
   ${If} ${Errors}
     ${ExecAndWaitForInputIdle} "$\"$INSTDIR\${FileMainEXE}$\""
   ${Else}
     GetFunctionAddress $0 LaunchAppFromElevatedProcess
     UAC::ExecCodeSegment $0
   ${EndIf}
+
+  StrCpy $LaunchedNewApp true
 FunctionEnd
 
 Function LaunchAppFromElevatedProcess
   ; Set our current working directory to the application's install directory
   ; otherwise the 7-Zip temp directory will be in use and won't be deleted.
   SetOutPath "$INSTDIR"
   ${ExecAndWaitForInputIdle} "$\"$INSTDIR\${FileMainEXE}$\""
 FunctionEnd
 
+Function SendPing
+  ${GetParameters} $0
+  ${GetOptions} $0 "/LaunchedFromStub" $0
+  ${IfNot} ${Errors}
+    Return
+  ${EndIf}
+
+  ; Create a GUID to use as the unique document ID.
+  System::Call "rpcrt4::UuidCreate(g . r0)i"
+  ; StringFromGUID2 (which is what System::Call uses internally to stringify
+  ; GUIDs) includes braces in its output, and we don't want those.
+  StrCpy $0 $0 -1 1
+
+  ; Configure the HTTP request for the ping
+  nsJSON::Set /tree ping /value "{}"
+  nsJSON::Set /tree ping "Url" /value \
+    '"${TELEMETRY_BASE_URL}/${TELEMETRY_NAMESPACE}/${TELEMETRY_INSTALL_PING_DOCTYPE}/${TELEMETRY_INSTALL_PING_VERSION}/$0"'
+  nsJSON::Set /tree ping "Verb" /value '"POST"'
+  nsJSON::Set /tree ping "DataType" /value '"JSON"'
+  nsJSON::Set /tree ping "AccessType" /value '"PreConfig"'
+
+  ; Fill in the ping payload
+  nsJSON::Set /tree ping "Data" /value "{}"
+  nsJSON::Set /tree ping "Data" "installer_type" /value '"full"'
+  nsJSON::Set /tree ping "Data" "installer_version" /value '"${AppVersion}"'
+  nsJSON::Set /tree ping "Data" "build_channel" /value '"${Channel}"'
+  nsJSON::Set /tree ping "Data" "update_channel" /value '"${UpdateChannel}"'
+  nsJSON::Set /tree ping "Data" "locale" /value '"${AB_CD}"'
+
+  ReadINIStr $0 "$INSTDIR\application.ini" "App" "Version"
+  nsJSON::Set /tree ping "Data" "version" /value '"$0"'
+  ReadINIStr $0 "$INSTDIR\application.ini" "App" "BuildID"
+  nsJSON::Set /tree ping "Data" "build_id" /value '"$0"'
+
+  ${GetParameters} $0
+  ${GetOptions} $0 "/LaunchedFromMSI" $0
+  ${IfNot} ${Errors}
+    nsJSON::Set /tree ping "Data" "from_msi" /value true
+  ${EndIf}
+
+  !ifdef HAVE_64BIT_BUILD
+    nsJSON::Set /tree ping "Data" "64bit_build" /value true
+  !else
+    nsJSON::Set /tree ping "Data" "64bit_build" /value false
+  !endif
+
+  ${If} ${RunningX64}
+    nsJSON::Set /tree ping "Data" "64bit_os" /value true
+  ${Else}
+    nsJSON::Set /tree ping "Data" "64bit_os" /value false
+  ${EndIf}
+
+  ; Though these values are sometimes incorrect due to bug 444664 it happens
+  ; so rarely it isn't worth working around it by reading the registry values.
+  ${WinVerGetMajor} $0
+  ${WinVerGetMinor} $1
+  ${WinVerGetBuild} $2
+  nsJSON::Set /tree ping "Data" "os_version" /value '"$0.$1.$2"'
+  ${If} ${IsServerOS}
+    nsJSON::Set /tree ping "Data" "server_os" /value true
+  ${Else}
+    nsJSON::Set /tree ping "Data" "server_os" /value false
+  ${EndIf}
+
+  ClearErrors
+  WriteRegStr HKLM "Software\Mozilla" "${BrandShortName}InstallerTest" \
+                   "Write Test"
+  ${If} ${Errors}
+    nsJSON::Set /tree ping "Data" "admin_user" /value false
+  ${Else}
+    DeleteRegValue HKLM "Software\Mozilla" "${BrandShortName}InstallerTest"
+    nsJSON::Set /tree ping "Data" "admin_user" /value true
+  ${EndIf}
+
+  ${If} $DefaultInstDir == $INSTDIR
+    nsJSON::Set /tree ping "Data" "default_path" /value true
+  ${Else}
+    nsJSON::Set /tree ping "Data" "default_path" /value false
+  ${EndIf}
+
+  nsJSON::Set /tree ping "Data" "set_default" /value "$SetAsDefault"
+
+  nsJSON::Set /tree ping "Data" "new_default" /value false
+  nsJSON::Set /tree ping "Data" "old_default" /value false
+
+  AppAssocReg::QueryCurrentDefault "http" "protocol" "effective"
+  Pop $0
+  ReadRegStr $0 HKCR "$0\shell\open\command" ""
+  ${If} $0 != ""
+    ${GetPathFromString} "$0" $0
+    ${GetParent} "$0" $1
+    ${GetLongPath} "$1" $1
+    ${If} $1 == $INSTDIR
+      nsJSON::Set /tree ping "Data" "new_default" /value true
+    ${Else}
+      StrCpy $0 "$0" "" -11 # 11 == length of "firefox.exe"
+      ${If} "$0" == "${FileMainEXE}"
+        nsJSON::Set /tree ping "Data" "old_default" /value true
+      ${EndIf}
+    ${EndIf}
+  ${EndIf}
+
+  nsJSON::Set /tree ping "Data" "had_old_install" /value "$HadOldInstall"
+
+  ${If} ${Silent}
+    ; In silent mode, only the install phase is executed, and the GUI events
+    ; that initialize most of the phase times are never called; only
+    ; $InstallPhaseStart and $FinishPhaseStart have usable values.
+    ${GetSecondsElapsed} $InstallPhaseStart $FinishPhaseStart $0
+
+    nsJSON::Set /tree ping "Data" "intro_time" /value 0
+    nsJSON::Set /tree ping "Data" "options_time" /value 0
+    nsJSON::Set /tree ping "Data" "install_time" /value "$0"
+    nsJSON::Set /tree ping "Data" "finish_time" /value 0
+  ${Else}
+    ; In GUI mode, all we can be certain of is that the intro phase has started;
+    ; the user could have canceled at any time and phases after that won't
+    ; have run at all. So we have to be prepared for anything after
+    ; $IntroPhaseStart to be uninitialized. For anything that isn't filled in
+    ; yet we'll use the current tick count. That means that any phases that
+    ; weren't entered at all will get 0 for their times because the start and
+    ; end tick counts will be the same.
+    System::Call "kernel32::GetTickCount()l .s"
+    Pop $0
+
+    ${If} $OptionsPhaseStart == 0
+      StrCpy $OptionsPhaseStart $0
+    ${EndIf}
+    ${GetSecondsElapsed} $IntroPhaseStart $OptionsPhaseStart $1
+    nsJSON::Set /tree ping "Data" "intro_time" /value "$1"
+
+    ${If} $InstallPhaseStart == 0
+      StrCpy $InstallPhaseStart $0
+    ${EndIf}
+    ${GetSecondsElapsed} $OptionsPhaseStart $InstallPhaseStart $1
+    nsJSON::Set /tree ping "Data" "options_time" /value "$1"
+
+    ${If} $FinishPhaseStart == 0
+      StrCpy $FinishPhaseStart $0
+    ${EndIf}
+    ${GetSecondsElapsed} $InstallPhaseStart $FinishPhaseStart $1
+    nsJSON::Set /tree ping "Data" "install_time" /value "$1"
+
+    ${If} $FinishPhaseEnd == 0
+      StrCpy $FinishPhaseEnd $0
+    ${EndIf}
+    ${GetSecondsElapsed} $FinishPhaseStart $FinishPhaseEnd $1
+    nsJSON::Set /tree ping "Data" "finish_time" /value "$1"
+  ${EndIf}
+
+  nsJSON::Set /tree ping "Data" "new_launched" /value "$LaunchedNewApp"
+
+  nsJSON::Set /tree ping "Data" "succeeded" /value false
+  ${If} $InstallResult == "cancel"
+    nsJSON::Set /tree ping "Data" "user_cancelled" /value true
+  ${ElseIf} $InstallResult == "success"
+    nsJSON::Set /tree ping "Data" "succeeded" /value true
+  ${EndIf}
+
+  ; Send the ping request. This call will block until a response is received,
+  ; but we shouldn't have any windows still open, so we won't jank anything.
+  nsJSON::Set /http ping
+FunctionEnd
+
 ################################################################################
 # Language
 
 !insertmacro MOZ_MUI_LANGUAGE 'baseLocale'
 !verbose push
 !verbose 3
 !include "overrideLocale.nsh"
 !include "customLocale.nsh"
@@ -934,19 +1127,25 @@ BrandingText " "
 # Page pre, show, and leave functions
 
 Function preWelcome
   StrCpy $PageName "Welcome"
   ${If} ${FileExists} "$EXEDIR\core\distribution\modern-wizard.bmp"
     Delete "$PLUGINSDIR\modern-wizard.bmp"
     CopyFiles /SILENT "$EXEDIR\core\distribution\modern-wizard.bmp" "$PLUGINSDIR\modern-wizard.bmp"
   ${EndIf}
+
+  System::Call "kernel32::GetTickCount()l .s"
+  Pop $IntroPhaseStart
 FunctionEnd
 
 Function preOptions
+  System::Call "kernel32::GetTickCount()l .s"
+  Pop $OptionsPhaseStart
+
   StrCpy $PageName "Options"
   ${If} ${FileExists} "$EXEDIR\core\distribution\modern-header.bmp"
   ${AndIf} $hHeaderBitmap == ""
     Delete "$PLUGINSDIR\modern-header.bmp"
     CopyFiles /SILENT "$EXEDIR\core\distribution\modern-header.bmp" "$PLUGINSDIR\modern-header.bmp"
     ${ChangeMUIHeaderImage} "$PLUGINSDIR\modern-header.bmp"
   ${EndIf}
   !insertmacro MUI_HEADER_TEXT "$(OPTIONS_PAGE_TITLE)" "$(OPTIONS_PAGE_SUBTITLE)"
@@ -970,16 +1169,18 @@ Function leaveOptions
   ${If} $InstallType == ${INSTALLTYPE_BASIC}
     Call CheckExistingInstall
   ${EndIf}
 FunctionEnd
 
 Function preDirectory
   StrCpy $PageName "Directory"
   ${PreDirectoryCommon}
+
+  StrCpy $DefaultInstDir $INSTDIR
 FunctionEnd
 
 Function leaveDirectory
   ${If} $InstallType == ${INSTALLTYPE_BASIC}
     Call CheckExistingInstall
   ${EndIf}
   ${LeaveDirectoryCommon} "$(WARN_DISK_SPACE)" "$(WARN_WRITE_ACCESS)"
 FunctionEnd
@@ -1283,29 +1484,49 @@ Function leaveSummary
   ${If} ${Errors}
     ${ManualCloseAppPrompt} "${WindowClass}" "$(WARN_MANUALLY_CLOSE_APP_INSTALL)"
   ${EndIf}
 FunctionEnd
 
 ; When we add an optional action to the finish page the cancel button is
 ; enabled. This disables it and leaves the finish button as the only choice.
 Function preFinish
+  System::Call "kernel32::GetTickCount()l .s"
+  Pop $FinishPhaseStart
+
   StrCpy $PageName ""
   ${EndInstallLog} "${BrandFullName}"
   !insertmacro MUI_INSTALLOPTIONS_WRITE "ioSpecial.ini" "settings" "cancelenabled" "0"
 FunctionEnd
 
+Function postFinish
+  System::Call "kernel32::GetTickCount()l .s"
+  Pop $FinishPhaseEnd
+FunctionEnd
+
 ################################################################################
 # Initialization Functions
 
 Function .onInit
   ; Remove the current exe directory from the search order.
   ; This only effects LoadLibrary calls and not implicitly loaded DLLs.
   System::Call 'kernel32::SetDllDirectoryW(w "")'
 
+  ; Initialize the variables used for telemetry
+  StrCpy $SetAsDefault true
+  StrCpy $HadOldInstall false
+  StrCpy $DefaultInstDir $INSTDIR
+  StrCpy $IntroPhaseStart 0
+  StrCpy $OptionsPhaseStart 0
+  StrCpy $InstallPhaseStart 0
+  StrCpy $FinishPhaseStart 0
+  StrCpy $FinishPhaseEnd 0
+  StrCpy $InstallResult "cancel"
+  StrCpy $LaunchedNewApp false
+
   StrCpy $PageName ""
   StrCpy $LANGUAGE 0
   ${SetBrandNameVars} "$EXEDIR\core\distribution\setup.ini"
 
   ; Don't install on systems that don't support SSE2. The parameter value of
   ; 10 is for PF_XMMI64_INSTRUCTIONS_AVAILABLE which will check whether the
   ; SSE2 instruction set is available. Result returned in $R7.
   System::Call "kernel32::IsProcessorFeaturePresent(i 10)i .R7"
@@ -1333,16 +1554,30 @@ Function .onInit
   ${Unless} ${RunningX64}
     MessageBox MB_OKCANCEL|MB_ICONSTOP "$(WARN_MIN_SUPPORTED_OSVER_MSG)" IDCANCEL +2
     ExecShell "open" "${URLSystemRequirements}"
     Quit
   ${EndUnless}
   SetRegView 64
 !endif
 
+  SetShellVarContext all
+  ${GetFirstInstallPath} "Software\Mozilla\${BrandFullNameInternal}" $0
+  ${If} "$0" == "false"
+    SetShellVarContext current
+    ${GetFirstInstallPath} "Software\Mozilla\${BrandFullNameInternal}" $0
+    ${If} "$0" == "false"
+      StrCpy $HadOldInstall false
+    ${Else}
+      StrCpy $HadOldInstall true
+    ${EndIf}
+  ${Else}
+    StrCpy $HadOldInstall true
+  ${EndIf}
+
   ${InstallOnInitCommon} "$(WARN_MIN_SUPPORTED_OSVER_CPU_MSG)"
 
   !insertmacro InitInstallOptionsFile "options.ini"
   !insertmacro InitInstallOptionsFile "shortcuts.ini"
   !insertmacro InitInstallOptionsFile "components.ini"
   !insertmacro InitInstallOptionsFile "extensions.ini"
   !insertmacro InitInstallOptionsFile "summary.ini"
 
@@ -1522,9 +1757,10 @@ Function .onInit
 
   ; Initialize $hHeaderBitmap to prevent redundant changing of the bitmap if
   ; the user clicks the back button
   StrCpy $hHeaderBitmap ""
 FunctionEnd
 
 Function .onGUIEnd
   ${OnEndCommon}
+  Call SendPing
 FunctionEnd
--- a/browser/installer/windows/nsis/stub.nsi
+++ b/browser/installer/windows/nsis/stub.nsi
@@ -1213,17 +1213,17 @@ Function LaunchFullInstaller
   ; since it being present will require an OS restart for the full
   ; installer.
   Delete "$INSTDIR\${FileMainEXE}.moz-upgrade"
   Delete "$INSTDIR\${FileMainEXE}.moz-delete"
 
   System::Call "kernel32::GetTickCount()l .s"
   Pop $EndPreInstallPhaseTickCount
 
-  Exec "$\"$PLUGINSDIR\download.exe$\" /INI=$PLUGINSDIR\${CONFIG_INI}"
+  Exec "$\"$PLUGINSDIR\download.exe$\" /LaunchedFromStub /INI=$PLUGINSDIR\${CONFIG_INI}"
   ${NSD_CreateTimer} CheckInstall ${InstallIntervalMS}
 FunctionEnd
 
 Function SendPing
   ${NSD_KillTimer} NextBlurb
   ${NSD_KillTimer} ClearBlurb
   HideWindow
 
--- a/build/build-clang/build-clang.py
+++ b/build/build-clang/build-clang.py
@@ -230,16 +230,18 @@ def build_one_stage(cc, cxx, asm, ld, ar
             cmake_args += ["-DCMAKE_RANLIB=%s" % slashify_path(ranlib)]
         if libtool is not None:
             cmake_args += ["-DCMAKE_LIBTOOL=%s" % slashify_path(libtool)]
         if osx_cross_compile:
             cmake_args += [
                 "-DCMAKE_SYSTEM_NAME=Darwin",
                 "-DCMAKE_SYSTEM_VERSION=10.10",
                 "-DLLVM_ENABLE_THREADS=OFF",
+                # Xray requires a OSX 10.12 SDK (https://bugs.llvm.org/show_bug.cgi?id=38959)
+                "-DCOMPILER_RT_BUILD_XRAY=OFF",
                 "-DLIBCXXABI_LIBCXX_INCLUDES=%s" % libcxx_include_dir,
                 "-DCMAKE_OSX_SYSROOT=%s" % slashify_path(os.getenv("CROSS_SYSROOT")),
                 "-DCMAKE_FIND_ROOT_PATH=%s" % slashify_path(os.getenv("CROSS_CCTOOLS_PATH")), # noqa
                 "-DCMAKE_FIND_ROOT_PATH_MODE_PROGRAM=NEVER",
                 "-DCMAKE_FIND_ROOT_PATH_MODE_LIBRARY=ONLY",
                 "-DCMAKE_FIND_ROOT_PATH_MODE_INCLUDE=ONLY",
                 "-DCMAKE_MACOSX_RPATH=ON",
                 "-DCMAKE_OSX_ARCHITECTURES=x86_64",
deleted file mode 100644
--- a/build/build-clang/clang-6-macosx64.json
+++ /dev/null
@@ -1,31 +0,0 @@
-{
-    "llvm_revision": "335538",
-    "stages": "1",
-    "build_libcxx": true,
-    "build_type": "Release",
-    "assertions": false,
-    "osx_cross_compile": true,
-    "llvm_repo": "https://llvm.org/svn/llvm-project/llvm/tags/RELEASE_601/final",
-    "clang_repo": "https://llvm.org/svn/llvm-project/cfe/tags/RELEASE_601/final",
-    "lld_repo": "https://llvm.org/svn/llvm-project/lld/tags/RELEASE_601/final",
-    "compiler_repo": "https://llvm.org/svn/llvm-project/compiler-rt/tags/RELEASE_601/final",
-    "libcxx_repo": "https://llvm.org/svn/llvm-project/libcxx/tags/RELEASE_601/final",
-    "libcxxabi_repo": "https://llvm.org/svn/llvm-project/libcxxabi/tags/RELEASE_601/final",
-    "python_path": "/usr/bin/python2.7",
-    "gcc_dir": "/builds/worker/workspace/build/src/gcc",
-    "cc": "/builds/worker/workspace/build/src/clang/bin/clang",
-    "cxx": "/builds/worker/workspace/build/src/clang/bin/clang++",
-    "as": "/builds/worker/workspace/build/src/clang/bin/clang",
-    "ar": "/builds/worker/workspace/build/src/cctools/bin/x86_64-apple-darwin11-ar",
-    "ranlib": "/builds/worker/workspace/build/src/cctools/bin/x86_64-apple-darwin11-ranlib",
-    "libtool": "/builds/worker/workspace/build/src/cctools/bin/x86_64-apple-darwin11-libtool",
-    "ld": "/builds/worker/workspace/build/src/clang/bin/clang",
-    "patches": [
-      "static-llvm-symbolizer.patch",
-      "compiler-rt-cross-compile.patch",
-      "compiler-rt-no-codesign.patch",
-      "r322401.patch",
-      "r325356.patch",
-      "r339636.patch"
-    ]
-}
new file mode 100644
--- /dev/null
+++ b/build/build-clang/clang-7-macosx64.json
@@ -0,0 +1,28 @@
+{
+    "llvm_revision": "342383",
+    "stages": "1",
+    "build_libcxx": true,
+    "build_type": "Release",
+    "assertions": false,
+    "osx_cross_compile": true,
+    "llvm_repo": "https://llvm.org/svn/llvm-project/llvm/tags/RELEASE_700/final",
+    "clang_repo": "https://llvm.org/svn/llvm-project/cfe/tags/RELEASE_700/final",
+    "lld_repo": "https://llvm.org/svn/llvm-project/lld/tags/RELEASE_700/final",
+    "compiler_repo": "https://llvm.org/svn/llvm-project/compiler-rt/tags/RELEASE_700/final",
+    "libcxx_repo": "https://llvm.org/svn/llvm-project/libcxx/tags/RELEASE_700/final",
+    "libcxxabi_repo": "https://llvm.org/svn/llvm-project/libcxxabi/tags/RELEASE_700/final",
+    "python_path": "/usr/bin/python2.7",
+    "gcc_dir": "/builds/worker/workspace/build/src/gcc",
+    "cc": "/builds/worker/workspace/build/src/clang/bin/clang",
+    "cxx": "/builds/worker/workspace/build/src/clang/bin/clang++",
+    "as": "/builds/worker/workspace/build/src/clang/bin/clang",
+    "ar": "/builds/worker/workspace/build/src/cctools/bin/x86_64-apple-darwin11-ar",
+    "ranlib": "/builds/worker/workspace/build/src/cctools/bin/x86_64-apple-darwin11-ranlib",
+    "libtool": "/builds/worker/workspace/build/src/cctools/bin/x86_64-apple-darwin11-libtool",
+    "ld": "/builds/worker/workspace/build/src/clang/bin/clang",
+    "patches": [
+      "static-llvm-symbolizer.patch",
+      "compiler-rt-cross-compile.patch",
+      "compiler-rt-no-codesign.patch"
+    ]
+}
--- a/build/build-clang/compiler-rt-no-codesign.patch
+++ b/build/build-clang/compiler-rt-no-codesign.patch
@@ -1,21 +1,21 @@
 Disable codesign for macosx cross-compile toolchain. Codesign only works on OSX.
 
 Index: cmake/Modules/AddCompilerRT.cmake
 ===================================================================
---- a/compiler-rt/cmake/Modules/AddCompilerRT.cmake	(revision 312553)
+--- a/compiler-rt/cmake/Modules/AddCompilerRT.cmake	(revision 342374)
 +++ b/compiler-rt/cmake/Modules/AddCompilerRT.cmake	(working copy)
-@@ -224,14 +224,6 @@
+@@ -290,14 +290,6 @@
          set_target_properties(${libname} PROPERTIES IMPORT_PREFIX "")
          set_target_properties(${libname} PROPERTIES IMPORT_SUFFIX ".lib")
        endif()
 -      if(APPLE)
 -        # Ad-hoc sign the dylibs
 -        add_custom_command(TARGET ${libname}
 -          POST_BUILD  
 -          COMMAND codesign --sign - $<TARGET_FILE:${libname}>
 -          WORKING_DIRECTORY ${COMPILER_RT_LIBRARY_OUTPUT_DIR}
 -        )
 -      endif()
      endif()
      install(TARGETS ${libname}
-       ARCHIVE DESTINATION ${COMPILER_RT_LIBRARY_INSTALL_DIR}
+       ARCHIVE DESTINATION ${install_dir_${libname}}
--- a/dom/base/Element.cpp
+++ b/dom/base/Element.cpp
@@ -883,36 +883,47 @@ Element::ScrollTo(const ScrollToOptions&
   Scroll(aOptions);
 }
 
 void
 Element::ScrollBy(double aXScrollDif, double aYScrollDif)
 {
   nsIScrollableFrame *sf = GetScrollFrame();
   if (sf) {
-    CSSIntPoint scrollPos = sf->GetScrollPositionCSSPixels();
-    scrollPos += CSSIntPoint::Truncate(mozilla::ToZeroIfNonfinite(aXScrollDif),
-                                       mozilla::ToZeroIfNonfinite(aYScrollDif));
-    Scroll(scrollPos, ScrollOptions());
+    ScrollToOptions options;
+    options.mLeft.Construct(aXScrollDif);
+    options.mTop.Construct(aYScrollDif);
+    ScrollBy(options);
   }
 }
 
 void
 Element::ScrollBy(const ScrollToOptions& aOptions)
 {
   nsIScrollableFrame *sf = GetScrollFrame();
   if (sf) {
-    CSSIntPoint scrollPos = sf->GetScrollPositionCSSPixels();
+    CSSIntPoint scrollDelta;
     if (aOptions.mLeft.WasPassed()) {
-      scrollPos.x += mozilla::ToZeroIfNonfinite(aOptions.mLeft.Value());
+      scrollDelta.x = mozilla::ToZeroIfNonfinite(aOptions.mLeft.Value());
     }
     if (aOptions.mTop.WasPassed()) {
-      scrollPos.y += mozilla::ToZeroIfNonfinite(aOptions.mTop.Value());
+      scrollDelta.y = mozilla::ToZeroIfNonfinite(aOptions.mTop.Value());
     }
-    Scroll(scrollPos, aOptions);
+
+    nsIScrollableFrame::ScrollMode scrollMode = nsIScrollableFrame::INSTANT;
+    if (aOptions.mBehavior == ScrollBehavior::Smooth) {
+      scrollMode = nsIScrollableFrame::SMOOTH_MSD;
+    } else if (aOptions.mBehavior == ScrollBehavior::Auto) {
+      ScrollStyles styles = sf->GetScrollStyles();
+      if (styles.mScrollBehavior == NS_STYLE_SCROLL_BEHAVIOR_SMOOTH) {
+        scrollMode = nsIScrollableFrame::SMOOTH_MSD;
+      }
+    }
+
+    sf->ScrollByCSSPixels(scrollDelta, scrollMode, nsGkAtoms::relative);
   }
 }
 
 int32_t
 Element::ScrollTop()
 {
   nsIScrollableFrame* sf = GetScrollFrame();
   return sf ? sf->GetScrollPositionCSSPixels().y : 0;
--- a/dom/base/nsGlobalWindowInner.cpp
+++ b/dom/base/nsGlobalWindowInner.cpp
@@ -3937,42 +3937,52 @@ nsGlobalWindowInner::ScrollTo(const CSSI
 
 void
 nsGlobalWindowInner::ScrollBy(double aXScrollDif, double aYScrollDif)
 {
   FlushPendingNotifications(FlushType::Layout);
   nsIScrollableFrame *sf = GetScrollFrame();
 
   if (sf) {
-    // Convert -Inf, Inf, and NaN to 0; otherwise, convert by C-style cast.
-    auto scrollDif = CSSIntPoint::Truncate(mozilla::ToZeroIfNonfinite(aXScrollDif),
-                                           mozilla::ToZeroIfNonfinite(aYScrollDif));
     // It seems like it would make more sense for ScrollBy to use
     // SMOOTH mode, but tests seem to depend on the synchronous behaviour.
     // Perhaps Web content does too.
-    ScrollTo(sf->GetScrollPositionCSSPixels() + scrollDif, ScrollOptions());
+    ScrollToOptions options;
+    options.mLeft.Construct(aXScrollDif);
+    options.mTop.Construct(aYScrollDif);
+    ScrollBy(options);
   }
 }
 
 void
 nsGlobalWindowInner::ScrollBy(const ScrollToOptions& aOptions)
 {
   FlushPendingNotifications(FlushType::Layout);
   nsIScrollableFrame *sf = GetScrollFrame();
 
   if (sf) {
-    CSSIntPoint scrollPos = sf->GetScrollPositionCSSPixels();
+    CSSIntPoint scrollDelta;
     if (aOptions.mLeft.WasPassed()) {
-      scrollPos.x += mozilla::ToZeroIfNonfinite(aOptions.mLeft.Value());
+      scrollDelta.x = mozilla::ToZeroIfNonfinite(aOptions.mLeft.Value());
     }
     if (aOptions.mTop.WasPassed()) {
-      scrollPos.y += mozilla::ToZeroIfNonfinite(aOptions.mTop.Value());
-    }
-
-    ScrollTo(scrollPos, aOptions);
+      scrollDelta.y = mozilla::ToZeroIfNonfinite(aOptions.mTop.Value());
+    }
+
+    nsIScrollableFrame::ScrollMode scrollMode = nsIScrollableFrame::INSTANT;
+    if (aOptions.mBehavior == ScrollBehavior::Smooth) {
+      scrollMode = nsIScrollableFrame::SMOOTH_MSD;
+    } else if (aOptions.mBehavior == ScrollBehavior::Auto) {
+      ScrollStyles styles = sf->GetScrollStyles();
+      if (styles.mScrollBehavior == NS_STYLE_SCROLL_BEHAVIOR_SMOOTH) {
+        scrollMode = nsIScrollableFrame::SMOOTH_MSD;
+      }
+    }
+
+    sf->ScrollByCSSPixels(scrollDelta, scrollMode, nsGkAtoms::relative);
   }
 }
 
 void
 nsGlobalWindowInner::ScrollByLines(int32_t numLines,
                                    const ScrollOptions& aOptions)
 {
   FlushPendingNotifications(FlushType::Layout);
--- a/dom/bindings/BindingUtils.cpp
+++ b/dom/bindings/BindingUtils.cpp
@@ -3407,41 +3407,16 @@ CreateGlobalOptionsWithXPConnect::PostCr
   NS_ENSURE_SUCCESS(rv, false);
 
   // Invoking the XPCWrappedNativeScope constructor automatically hooks it
   // up to the realm of aGlobal.
   (void) new XPCWrappedNativeScope(aCx, aGlobal, site);
   return true;
 }
 
-static bool sRegisteredDOMNames = false;
-
-static void
-RegisterDOMNames()
-{
-  if (sRegisteredDOMNames) {
-    return;
-  }
-
-  // Register new DOM bindings
-  WebIDLGlobalNameHash::Init();
-
-  sRegisteredDOMNames = true;
-}
-
-/* static */
-bool
-CreateGlobalOptions<nsGlobalWindowInner>::PostCreateGlobal(JSContext* aCx,
-                                                           JS::Handle<JSObject*> aGlobal)
-{
-  RegisterDOMNames();
-
-  return CreateGlobalOptionsWithXPConnect::PostCreateGlobal(aCx, aGlobal);
-}
-
 #ifdef DEBUG
 void
 AssertReturnTypeMatchesJitinfo(const JSJitInfo* aJitInfo,
                                JS::Handle<JS::Value> aValue)
 {
   switch (aJitInfo->returnType()) {
   case JSVAL_TYPE_UNKNOWN:
     // Any value is good.
--- a/dom/bindings/BindingUtils.h
+++ b/dom/bindings/BindingUtils.h
@@ -3099,17 +3099,16 @@ struct CreateGlobalOptions
 };
 
 template <>
 struct CreateGlobalOptions<nsGlobalWindowInner>
   : public CreateGlobalOptionsWithXPConnect
 {
   static constexpr ProtoAndIfaceCache::Kind ProtoAndIfaceCacheKind =
     ProtoAndIfaceCache::WindowLike;
-  static bool PostCreateGlobal(JSContext* aCx, JS::Handle<JSObject*> aGlobal);
 };
 
 // The return value is true if we created and successfully performed our part of
 // the setup for the global, false otherwise.
 //
 // Typically this method's caller will want to ensure that
 // xpc::InitGlobalObjectOptions is called before, and xpc::InitGlobalObject is
 // called after, this method, to ensure that this global object and its
--- a/dom/bindings/Codegen.py
+++ b/dom/bindings/Codegen.py
@@ -6,16 +6,18 @@
 
 import os
 import re
 import string
 import math
 import textwrap
 import functools
 
+from perfecthash import PerfectHash
+
 from WebIDL import BuiltinTypes, IDLBuiltinType, IDLNullValue, IDLSequenceType, IDLType, IDLAttribute, IDLInterfaceMember, IDLUndefinedValue, IDLEmptySequenceValue, IDLDictionary
 from Configuration import NoSuchDescriptorError, getTypesFromDescriptor, getTypesFromDictionary, getTypesFromCallback, getAllTypes, Descriptor, MemberIsUnforgeable, iteratorNativeType
 
 AUTOGENERATED_WARNING_COMMENT = \
     "/* THIS FILE IS AUTOGENERATED BY Codegen.py - DO NOT EDIT */\n\n"
 AUTOGENERATED_WITH_SOURCE_WARNING_COMMENT = \
     "/* THIS FILE IS AUTOGENERATED FROM %s BY Codegen.py - DO NOT EDIT */\n\n"
 ADDPROPERTY_HOOK_NAME = '_addProperty'
@@ -24,16 +26,21 @@ OBJECT_MOVED_HOOK_NAME = '_objectMoved'
 CONSTRUCT_HOOK_NAME = '_constructor'
 LEGACYCALLER_HOOK_NAME = '_legacycaller'
 RESOLVE_HOOK_NAME = '_resolve'
 MAY_RESOLVE_HOOK_NAME = '_mayResolve'
 NEW_ENUMERATE_HOOK_NAME = '_newEnumerate'
 ENUM_ENTRY_VARIABLE_NAME = 'strings'
 INSTANCE_RESERVED_SLOTS = 1
 
+# This size is arbitrary. It is a power of 2 to make using it as a modulo
+# operand cheap, and is usually around 1/3-1/5th of the set size (sometimes
+# smaller for very large sets).
+GLOBAL_NAMES_PHF_SIZE=256
+
 
 def memberReservedSlot(member, descriptor):
     return ("(DOM_INSTANCE_RESERVED_SLOTS + %d)" %
             member.slotIndices[descriptor.interface.identifier.name])
 
 
 def memberXrayExpandoReservedSlot(member, descriptor):
     return ("(xpc::JSSLOT_EXPANDO_COUNT + %d)" %
@@ -13686,59 +13693,94 @@ class CGGetSystemBindingNames(CGAbstract
 
 def getGlobalNames(config):
     names = []
     for desc in config.getDescriptors(registersGlobalNamesOnWindow=True):
         names.append((desc.name, desc))
         names.extend((n.identifier.name, desc) for n in desc.interface.namedConstructors)
     return names
 
-class CGGlobalNamesString(CGGeneric):
+class CGGlobalNames(CGGeneric):
     def __init__(self, config):
-        globalNames = getGlobalNames(config)
         currentOffset = 0
         strings = []
-        for (name, _) in globalNames:
-            strings.append('/* %i */ "%s\\0"' % (currentOffset, name))
-            currentOffset += len(name) + 1 # Add trailing null.
+        entries = []
+        for name, desc in getGlobalNames(config):
+            # Add a string to the list.
+            offset = currentOffset
+            strings.append('/* %i */ "%s\\0"' % (offset, name))
+            currentOffset += len(name) + 1  # Add trailing null.
+
+            # Generate the entry declaration
+            # XXX(nika): mCreate & mEnabled require relocations. If we want to
+            # reduce those, we could move them into separate tables.
+            nativeEntry = fill("""
+                {
+                  /* mNameOffset */ ${nameOffset}, // "${name}"
+                  /* mNameLength */ ${nameLength},
+                  /* mConstructorId */ constructors::id::${realname},
+                  /* mCreate */ ${realname}_Binding::CreateInterfaceObjects,
+                  /* mEnabled */ ${enabled}
+                }
+                """,
+                nameOffset=offset,
+                nameLength=len(name),
+                name=name,
+                realname=desc.name,
+                enabled=("%s_Binding::ConstructorEnabled" % desc.name
+                         if desc.isExposedConditionally() else "nullptr"))
+
+            entries.append((name, nativeEntry))
+
+        # Unfortunately, when running tests, we may have no entries.
+        # PerfectHash will assert if we give it an empty set of entries, so we
+        # just generate a dummy value.
+        if len(entries) == 0:
+            CGGeneric.__init__(self, define=dedent('''
+                static_assert(false, "No WebIDL global name entries!");
+                '''))
+            return
+
+        # Build the perfect hash function.
+        phf = PerfectHash(entries, GLOBAL_NAMES_PHF_SIZE)
+
+        # Generate code for the PHF
+        phfCodegen = phf.codegen('WebIDLGlobalNameHash::sEntries',
+                                 'WebIDLNameTableEntry')
+        entries = phfCodegen.gen_entries(lambda e: e[1])
+        getter = phfCodegen.gen_jsflatstr_getter(
+            name='WebIDLGlobalNameHash::GetEntry',
+            return_type='const WebIDLNameTableEntry*',
+            # XXX(nika): It would be nice to have a length overload for
+            # JS_FlatStringEqualsAscii.
+            return_entry=dedent("""
+                if (JS_FlatStringEqualsAscii(aKey, sNames + entry.mNameOffset)) {
+                  return &entry;
+                }
+                return nullptr;
+                """))
+
         define = fill("""
             const uint32_t WebIDLGlobalNameHash::sCount = ${count};
 
             const char WebIDLGlobalNameHash::sNames[] =
               $*{strings}
 
-            """,
-            count=len(globalNames),
-            strings="\n".join(strings) + ";\n")
-
+            $*{entries}
+
+            $*{getter}
+
+            """,
+            count=len(phf.entries),
+            strings="\n".join(strings) + ";\n",
+            entries=entries,
+            getter=getter)
         CGGeneric.__init__(self, define=define)
 
 
-class CGRegisterGlobalNames(CGAbstractMethod):
-    def __init__(self, config):
-        CGAbstractMethod.__init__(self, None, 'RegisterWebIDLGlobalNames',
-                                  'void', [])
-        self.config = config
-
-    def definition_body(self):
-        def getCheck(desc):
-            if not desc.isExposedConditionally():
-                return "nullptr"
-            return "%s_Binding::ConstructorEnabled" % desc.name
-
-        define = ""
-        currentOffset = 0
-        for (name, desc) in getGlobalNames(self.config):
-            length = len(name)
-            define += "WebIDLGlobalNameHash::Register(%i, %i, %s_Binding::CreateInterfaceObjects, %s, constructors::id::%s);\n" % (
-                currentOffset, length, desc.name, getCheck(desc), desc.name)
-            currentOffset += length + 1 # Add trailing null.
-        return define
-
-
 def dependencySortObjects(objects, dependencyGetter, nameGetter):
     """
     Sort IDL objects with dependencies on each other such that if A
     depends on B then B will come before A.  This is needed for
     declaring C++ classes in the right order, for example.  Objects
     that have no dependencies are just sorted by name.
 
     objects should be something that can produce a set of objects
@@ -17114,30 +17156,27 @@ class GlobalGenRoots():
         curr = CGWrapper(curr, pre=AUTOGENERATED_WARNING_COMMENT)
 
         # Done.
         return curr
 
     @staticmethod
     def RegisterBindings(config):
 
-        curr = CGList([CGGlobalNamesString(config), CGRegisterGlobalNames(config)])
-
-        # Wrap all of that in our namespaces.
-        curr = CGNamespace.build(['mozilla', 'dom'],
-                                 CGWrapper(curr, post='\n'))
+        curr = CGNamespace.build(['mozilla', 'dom'], CGGlobalNames(config))
         curr = CGWrapper(curr, post='\n')
 
         # Add the includes
         defineIncludes = [CGHeaders.getDeclarationFilename(desc.interface)
                           for desc in config.getDescriptors(hasInterfaceObject=True,
                                                             isExposedInWindow=True,
                                                             register=True)]
         defineIncludes.append('mozilla/dom/WebIDLGlobalNameHash.h')
         defineIncludes.append('mozilla/dom/PrototypeList.h')
+        defineIncludes.append('mozilla/PerfectHash.h')
         defineIncludes.extend([CGHeaders.getDeclarationFilename(desc.interface)
                                for desc in config.getDescriptors(isNavigatorProperty=True,
                                                                  register=True)])
         curr = CGHeaders([], [], [], [], [], defineIncludes, 'RegisterBindings',
                          curr)
 
         # Add include guards.
         curr = CGIncludeGuard('RegisterBindings', curr)
--- a/dom/bindings/WebIDLGlobalNameHash.cpp
+++ b/dom/bindings/WebIDLGlobalNameHash.cpp
@@ -23,178 +23,16 @@
 #include "nsGlobalWindow.h"
 #include "nsIMemoryReporter.h"
 #include "nsTHashtable.h"
 #include "WrapperFactory.h"
 
 namespace mozilla {
 namespace dom {
 
-struct MOZ_STACK_CLASS WebIDLNameTableKey
-{
-  explicit WebIDLNameTableKey(JSFlatString* aJSString)
-    : mLength(js::GetFlatStringLength(aJSString))
-  {
-    mNogc.emplace();
-    JSLinearString* jsString = js::FlatStringToLinearString(aJSString);
-    if (js::LinearStringHasLatin1Chars(jsString)) {
-      mLatin1String = reinterpret_cast<const char*>(
-        js::GetLatin1LinearStringChars(*mNogc, jsString));
-      mTwoBytesString = nullptr;
-      mHash = mLatin1String ? HashString(mLatin1String, mLength) : 0;
-    } else {
-      mLatin1String = nullptr;
-      mTwoBytesString = js::GetTwoByteLinearStringChars(*mNogc, jsString);
-      mHash = mTwoBytesString ? HashString(mTwoBytesString, mLength) : 0;
-    }
-  }
-  explicit WebIDLNameTableKey(const char* aString, size_t aLength)
-    : mLatin1String(aString),
-      mTwoBytesString(nullptr),
-      mLength(aLength),
-      mHash(HashString(aString, aLength))
-  {
-    MOZ_ASSERT(aString[aLength] == '\0');
-  }
-
-  Maybe<JS::AutoCheckCannotGC> mNogc;
-  const char* mLatin1String;
-  const char16_t* mTwoBytesString;
-  size_t mLength;
-  PLDHashNumber mHash;
-};
-
-struct WebIDLNameTableEntry : public PLDHashEntryHdr
-{
-  typedef const WebIDLNameTableKey& KeyType;
-  typedef const WebIDLNameTableKey* KeyTypePointer;
-
-  explicit WebIDLNameTableEntry(KeyTypePointer aKey)
-    : mNameOffset(0),
-      mNameLength(0),
-      mConstructorId(constructors::id::_ID_Count),
-      mCreate(nullptr),
-      mEnabled(nullptr)
-  {}
-  WebIDLNameTableEntry(WebIDLNameTableEntry&& aEntry)
-    : mNameOffset(aEntry.mNameOffset),
-      mNameLength(aEntry.mNameLength),
-      mConstructorId(aEntry.mConstructorId),
-      mCreate(aEntry.mCreate),
-      mEnabled(aEntry.mEnabled)
-  {}
-  ~WebIDLNameTableEntry()
-  {}
-
-  bool KeyEquals(KeyTypePointer aKey) const
-  {
-    if (mNameLength != aKey->mLength) {
-      return false;
-    }
-
-    const char* name = WebIDLGlobalNameHash::sNames + mNameOffset;
-
-    if (aKey->mLatin1String) {
-      return ArrayEqual(aKey->mLatin1String, name, aKey->mLength);
-    }
-
-    return nsCharTraits<char16_t>::compareASCII(aKey->mTwoBytesString, name,
-                                                aKey->mLength) == 0;
-  }
-
-  static KeyTypePointer KeyToPointer(KeyType aKey)
-  {
-    return &aKey;
-  }
-
-  static PLDHashNumber HashKey(KeyTypePointer aKey)
-  {
-    return aKey->mHash;
-  }
-
-  enum { ALLOW_MEMMOVE = true };
-
-  uint16_t mNameOffset;
-  uint16_t mNameLength;
-  constructors::id::ID mConstructorId;
-  CreateInterfaceObjectsMethod mCreate;
-  // May be null if enabled unconditionally
-  WebIDLGlobalNameHash::ConstructorEnabled mEnabled;
-};
-
-static nsTHashtable<WebIDLNameTableEntry>* sWebIDLGlobalNames;
-
-class WebIDLGlobalNamesHashReporter final : public nsIMemoryReporter
-{
-  MOZ_DEFINE_MALLOC_SIZE_OF(MallocSizeOf)
-
-  ~WebIDLGlobalNamesHashReporter() {}
-
-public:
-  NS_DECL_ISUPPORTS
-
-  NS_IMETHOD CollectReports(nsIHandleReportCallback* aHandleReport,
-                            nsISupports* aData, bool aAnonymize) override
-  {
-    int64_t amount =
-      sWebIDLGlobalNames ?
-      sWebIDLGlobalNames->ShallowSizeOfIncludingThis(MallocSizeOf) : 0;
-
-    MOZ_COLLECT_REPORT(
-      "explicit/dom/webidl-globalnames", KIND_HEAP, UNITS_BYTES, amount,
-      "Memory used by the hash table for WebIDL's global names.");
-
-    return NS_OK;
-  }
-};
-
-NS_IMPL_ISUPPORTS(WebIDLGlobalNamesHashReporter, nsIMemoryReporter)
-
-/* static */
-void
-WebIDLGlobalNameHash::Init()
-{
-  sWebIDLGlobalNames = new nsTHashtable<WebIDLNameTableEntry>(sCount);
-  RegisterWebIDLGlobalNames();
-
-  RegisterStrongMemoryReporter(new WebIDLGlobalNamesHashReporter());
-}
-
-/* static */
-void
-WebIDLGlobalNameHash::Shutdown()
-{
-  delete sWebIDLGlobalNames;
-}
-
-/* static */
-void
-WebIDLGlobalNameHash::Register(uint16_t aNameOffset, uint16_t aNameLength,
-                               CreateInterfaceObjectsMethod aCreate,
-                               ConstructorEnabled aEnabled,
-                               constructors::id::ID aConstructorId)
-{
-  const char* name = sNames + aNameOffset;
-  WebIDLNameTableKey key(name, aNameLength);
-  WebIDLNameTableEntry* entry = sWebIDLGlobalNames->PutEntry(key);
-  entry->mNameOffset = aNameOffset;
-  entry->mNameLength = aNameLength;
-  entry->mCreate = aCreate;
-  entry->mEnabled = aEnabled;
-  entry->mConstructorId = aConstructorId;
-}
-
-/* static */
-void
-WebIDLGlobalNameHash::Remove(const char* aName, uint32_t aLength)
-{
-  WebIDLNameTableKey key(aName, aLength);
-  sWebIDLGlobalNames->RemoveEntry(key);
-}
-
 static JSObject*
 FindNamedConstructorForXray(JSContext* aCx, JS::Handle<jsid> aId,
                             const WebIDLNameTableEntry* aEntry)
 {
   JSObject* interfaceObject =
     GetPerInterfaceObjectHandle(aCx, aEntry->mConstructorId,
                                 aEntry->mCreate,
                                 /* aDefineOnGlobal = */ false);
@@ -226,22 +64,17 @@ WebIDLGlobalNameHash::DefineIfEnabled(JS
                                       JS::Handle<jsid> aId,
                                       JS::MutableHandle<JS::PropertyDescriptor> aDesc,
                                       bool* aFound)
 {
   MOZ_ASSERT(JSID_IS_STRING(aId), "Check for string id before calling this!");
 
   const WebIDLNameTableEntry* entry;
   {
-    WebIDLNameTableKey key(JSID_TO_FLAT_STRING(aId));
-    // Rooting analysis thinks nsTHashtable<...>::GetEntry may GC because it
-    // ends up calling through PLDHashTableOps' matchEntry function pointer, but
-    // we know WebIDLNameTableEntry::KeyEquals can't cause a GC.
-    JS::AutoSuppressGCAnalysis suppress;
-    entry = sWebIDLGlobalNames->GetEntry(key);
+    entry = GetEntry(JSID_TO_FLAT_STRING(aId));
   }
 
   if (!entry) {
     *aFound = false;
     return true;
   }
 
   *aFound = true;
@@ -340,40 +173,35 @@ WebIDLGlobalNameHash::DefineIfEnabled(JS
 
   return true;
 }
 
 /* static */
 bool
 WebIDLGlobalNameHash::MayResolve(jsid aId)
 {
-  WebIDLNameTableKey key(JSID_TO_FLAT_STRING(aId));
-  // Rooting analysis thinks nsTHashtable<...>::Contains may GC because it ends
-  // up calling through PLDHashTableOps' matchEntry function pointer, but we
-  // know WebIDLNameTableEntry::KeyEquals can't cause a GC.
-  JS::AutoSuppressGCAnalysis suppress;
-  return sWebIDLGlobalNames->Contains(key);
+  return GetEntry(JSID_TO_FLAT_STRING(aId)) != nullptr;
 }
 
 /* static */
 bool
 WebIDLGlobalNameHash::GetNames(JSContext* aCx, JS::Handle<JSObject*> aObj,
                                NameType aNameType, JS::AutoIdVector& aNames)
 {
   // aObj is always a Window here, so GetProtoAndIfaceCache on it is safe.
   ProtoAndIfaceCache* cache = GetProtoAndIfaceCache(aObj);
-  for (auto iter = sWebIDLGlobalNames->Iter(); !iter.Done(); iter.Next()) {
-    const WebIDLNameTableEntry* entry = iter.Get();
+  for (size_t i = 0; i < sCount; ++i) {
+    const WebIDLNameTableEntry& entry = sEntries[i];
     // If aNameType is not AllNames, only include things whose entry slot in the
     // ProtoAndIfaceCache is null.
     if ((aNameType == AllNames ||
-         !cache->HasEntryInSlot(entry->mConstructorId)) &&
-        (!entry->mEnabled || entry->mEnabled(aCx, aObj))) {
-      JSString* str = JS_AtomizeStringN(aCx, sNames + entry->mNameOffset,
-                                        entry->mNameLength);
+         !cache->HasEntryInSlot(entry.mConstructorId)) &&
+        (!entry.mEnabled || entry.mEnabled(aCx, aObj))) {
+      JSString* str = JS_AtomizeStringN(aCx, sNames + entry.mNameOffset,
+                                        entry.mNameLength);
       if (!str || !aNames.append(NON_INTEGER_ATOM_TO_JSID(str))) {
         return false;
       }
     }
   }
 
   return true;
 }
--- a/dom/bindings/WebIDLGlobalNameHash.h
+++ b/dom/bindings/WebIDLGlobalNameHash.h
@@ -6,48 +6,46 @@
 
 #ifndef mozilla_dom_WebIDLGlobalNameHash_h__
 #define mozilla_dom_WebIDLGlobalNameHash_h__
 
 #include "js/RootingAPI.h"
 #include "nsTArray.h"
 #include "mozilla/dom/BindingDeclarations.h"
 
+class JSFlatString;
+
 namespace mozilla {
 namespace dom {
 
-struct WebIDLNameTableEntry;
-
 namespace constructors {
 namespace id {
 enum ID : uint16_t;
 } // namespace id
 } // namespace constructors
 
-class WebIDLGlobalNameHash
+struct WebIDLNameTableEntry
 {
-public:
-  static void Init();
-  static void Shutdown();
-
   // Check whether a constructor should be enabled for the given object.
   // Note that the object should NOT be an Xray, since Xrays will end up
   // defining constructors on the underlying object.
-  // This is a typedef for the function type itself, not the function
-  // pointer, so it's more obvious that pointers to a ConstructorEnabled
-  // can be null.
-  typedef bool
-  (*ConstructorEnabled)(JSContext* cx, JS::Handle<JSObject*> obj);
+  typedef bool (*ConstructorEnabled)(JSContext* cx, JS::Handle<JSObject*> obj);
 
-  static void Register(uint16_t aNameOffset, uint16_t aNameLength,
-                       CreateInterfaceObjectsMethod aCreate,
-                       ConstructorEnabled aEnabled,
-                       constructors::id::ID aConstructorId);
+  uint16_t mNameOffset;
+  uint16_t mNameLength;
+  constructors::id::ID mConstructorId;
+  CreateInterfaceObjectsMethod mCreate;
+  // May be null if enabled unconditionally
+  ConstructorEnabled mEnabled;
+};
 
-  static void Remove(const char* aName, uint32_t aLength);
+class WebIDLGlobalNameHash
+{
+public:
+  typedef WebIDLNameTableEntry::ConstructorEnabled ConstructorEnabled;
 
   // Returns false if something failed. aFound is set to true if the name is in
   // the hash, whether it's enabled or not.
   static bool DefineIfEnabled(JSContext* aCx, JS::Handle<JSObject*> aObj,
                               JS::Handle<jsid> aId,
                               JS::MutableHandle<JS::PropertyDescriptor> aDesc,
                               bool* aFound);
 
@@ -64,22 +62,30 @@ public:
   // Returns false if an exception has been thrown on aCx.
   static bool GetNames(JSContext* aCx, JS::Handle<JSObject*> aObj,
                        NameType aNameType,
                        JS::AutoIdVector& aNames);
 
 private:
   friend struct WebIDLNameTableEntry;
 
-  // The total number of names that we will add to the hash.
+  // Look up an entry by key name. `nullptr` if the entry was not found.
+  // The impl of GetEntry is generated by Codegen.py in RegisterBindings.cpp
+  static const WebIDLNameTableEntry* GetEntry(JSFlatString* aKey);
+
+  // The total number of names in the hash.
   // The value of sCount is generated by Codegen.py in RegisterBindings.cpp.
   static const uint32_t sCount;
 
-  // The names that will be registered in the hash, concatenated as one big
-  // string with \0 as a separator between names.
+  // The name table entries in the hash.
+  // The value of sEntries is generated by Codegen.py in RegisterBindings.cpp.
+  static const WebIDLNameTableEntry sEntries[];
+
+  // The names registered in the hash, concatenated as one big string with \0
+  // as a separator between names.
   // The value of sNames is generated by Codegen.py in RegisterBindings.cpp.
   static const char sNames[];
 };
 
 } // namespace dom
 } // namespace mozilla
 
 #endif // mozilla_dom_WebIDLGlobalNameHash_h__
--- a/dom/canvas/WebGL2ContextSamplers.cpp
+++ b/dom/canvas/WebGL2ContextSamplers.cpp
@@ -38,17 +38,20 @@ WebGL2Context::DeleteSampler(WebGLSample
 
 bool
 WebGL2Context::IsSampler(const WebGLSampler* const obj)
 {
     const FuncScope funcScope(*this, "isSampler");
     if (!ValidateIsObject(obj))
         return false;
 
-    return gl->fIsSampler(obj->mGLName);
+    if (obj->IsDeleteRequested())
+        return false;
+
+    return true;
 }
 
 void
 WebGL2Context::BindSampler(GLuint unit, WebGLSampler* sampler)
 {
     const FuncScope funcScope(*this, "bindSampler");
     if (IsContextLost())
         return;
--- a/dom/canvas/WebGL2ContextTransformFeedback.cpp
+++ b/dom/canvas/WebGL2ContextTransformFeedback.cpp
@@ -49,17 +49,20 @@ WebGL2Context::DeleteTransformFeedback(W
 
 bool
 WebGL2Context::IsTransformFeedback(const WebGLTransformFeedback* const obj)
 {
     const FuncScope funcScope(*this, "isTransformFeedback");
     if (!ValidateIsObject(obj))
         return false;
 
-    return gl->fIsTransformFeedback(obj->mGLName);
+    if (obj->IsDeleteRequested())
+        return false;
+
+    return obj->mHasBeenBound;
 }
 
 void
 WebGL2Context::BindTransformFeedback(GLenum target, WebGLTransformFeedback* tf)
 {
     const FuncScope funcScope(*this, "bindTransformFeedback");
     if (IsContextLost())
         return;
@@ -85,16 +88,17 @@ WebGL2Context::BindTransformFeedback(GLe
     }
 
     mBoundTransformFeedback = (tf ? tf : mDefaultTransformFeedback);
 
     gl->fBindTransformFeedback(target, mBoundTransformFeedback->mGLName);
 
     if (mBoundTransformFeedback) {
         mBoundTransformFeedback->AddBufferBindCounts(+1);
+        mBoundTransformFeedback->mHasBeenBound = true;
     }
 }
 
 void
 WebGL2Context::BeginTransformFeedback(GLenum primMode)
 {
     const FuncScope funcScope(*this, "beginTransformFeedback");
     if (IsContextLost())
--- a/dom/canvas/WebGLContext.cpp
+++ b/dom/canvas/WebGLContext.cpp
@@ -2496,17 +2496,20 @@ WebGLContext::ValidateIsObject(const Web
         return false;
 
     if (!object)
         return false;
 
     if (!object->IsCompatibleWithContext(this))
         return false;
 
-    return !object->IsDeleted();
+    if (object->IsDeleted())
+        return false;
+
+    return true;
 }
 
 bool
 WebGLContext::ValidateDeleteObject(const WebGLDeletableObject* const object)
 {
     if (IsContextLost())
         return false;
 
--- a/dom/canvas/WebGLContextGL.cpp
+++ b/dom/canvas/WebGLContextGL.cpp
@@ -142,19 +142,17 @@ WebGLContext::BindFramebuffer(GLenum tar
     if (wfb && !ValidateObject("fb", *wfb))
         return;
 
     if (!wfb) {
         gl->fBindFramebuffer(target, 0);
     } else {
         GLuint framebuffername = wfb->mGLName;
         gl->fBindFramebuffer(target, framebuffername);
-#ifdef ANDROID
-        wfb->mIsFB = true;
-#endif
+        wfb->mHasBeenBound = true;
     }
 
     switch (target) {
     case LOCAL_GL_FRAMEBUFFER:
         mBoundDrawFramebuffer = wfb;
         mBoundReadFramebuffer = wfb;
         break;
     case LOCAL_GL_DRAW_FRAMEBUFFER:
@@ -1060,61 +1058,65 @@ WebGLContext::Hint(GLenum target, GLenum
 
 bool
 WebGLContext::IsBuffer(const WebGLBuffer* const obj)
 {
     const FuncScope funcScope(*this, "isBuffer");
     if (!ValidateIsObject(obj))
         return false;
 
-    return gl->fIsBuffer(obj->mGLName);
+    if (obj->IsDeleteRequested())
+        return false;
+
+    return obj->Content() != WebGLBuffer::Kind::Undefined;
 }
 
 bool
 WebGLContext::IsFramebuffer(const WebGLFramebuffer* const obj)
 {
     const FuncScope funcScope(*this, "isFramebuffer");
     if (!ValidateIsObject(obj))
         return false;
 
-#ifdef ANDROID
-    if (gl->WorkAroundDriverBugs() &&
-        gl->Renderer() == GLRenderer::AndroidEmulator)
-    {
-        return obj->mIsFB;
-    }
-#endif
-
-    return gl->fIsFramebuffer(obj->mGLName);
+    if (obj->IsDeleteRequested())
+        return false;
+
+    return obj->mHasBeenBound;
 }
 
 bool
 WebGLContext::IsProgram(const WebGLProgram* const obj)
 {
     const FuncScope funcScope(*this, "isProgram");
     return ValidateIsObject(obj);
 }
 
 bool
 WebGLContext::IsQuery(const WebGLQuery* const obj)
 {
     const FuncScope funcScope(*this, "isQuery");
     if (!ValidateIsObject(obj))
         return false;
 
-    return obj->IsQuery();
+    if (obj->IsDeleteRequested())
+        return false;
+
+    return bool(obj->Target());
 }
 
 bool
 WebGLContext::IsRenderbuffer(const WebGLRenderbuffer* const obj)
 {
     const FuncScope funcScope(*this, "isRenderbuffer");
     if (!ValidateIsObject(obj))
         return false;
 
+    if (obj->IsDeleteRequested())
+        return false;
+
     return obj->mHasBeenBound;
 }
 
 bool
 WebGLContext::IsShader(const WebGLShader* const obj)
 {
     const FuncScope funcScope(*this, "isShader");
     return ValidateIsObject(obj);
@@ -1122,27 +1124,33 @@ WebGLContext::IsShader(const WebGLShader
 
 bool
 WebGLContext::IsTexture(const WebGLTexture* const obj)
 {
     const FuncScope funcScope(*this, "isTexture");
     if (!ValidateIsObject(obj))
         return false;
 
-    return obj->IsTexture();
+    if (obj->IsDeleteRequested())
+        return false;
+
+    return bool(obj->Target());
 }
 
 bool
 WebGLContext::IsVertexArray(const WebGLVertexArray* const obj)
 {
     const FuncScope funcScope(*this, "isVertexArray");
     if (!ValidateIsObject(obj))
         return false;
 
-    return obj->IsVertexArray();
+    if (obj->IsDeleteRequested())
+        return false;
+
+    return obj->mHasBeenBound;
 }
 
 // -
 
 void
 WebGLContext::LinkProgram(WebGLProgram& prog)
 {
     const FuncScope funcScope(*this, "linkProgram");
--- a/dom/canvas/WebGLContextUtils.cpp
+++ b/dom/canvas/WebGLContextUtils.cpp
@@ -697,18 +697,18 @@ AssertUintParamCorrect(gl::GLContext*, G
 
 void
 WebGLContext::AssertCachedBindings() const
 {
 #ifdef DEBUG
     GetAndFlushUnderlyingGLErrors();
 
     if (IsWebGL2() || IsExtensionEnabled(WebGLExtensionID::OES_vertex_array_object)) {
-        GLuint bound = mBoundVertexArray ? mBoundVertexArray->GLName() : 0;
-        AssertUintParamCorrect(gl, LOCAL_GL_VERTEX_ARRAY_BINDING, bound);
+        AssertUintParamCorrect(gl, LOCAL_GL_VERTEX_ARRAY_BINDING,
+                               mBoundVertexArray->mGLName);
     }
 
     GLint stencilBits = 0;
     if (GetStencilBits(&stencilBits)) { // Depends on current draw framebuffer.
         const GLuint stencilRefMask = (1 << stencilBits) - 1;
 
         AssertMaskedUintParamCorrect(gl, LOCAL_GL_STENCIL_REF,      stencilRefMask, mStencilRefFront);
         AssertMaskedUintParamCorrect(gl, LOCAL_GL_STENCIL_BACK_REF, stencilRefMask, mStencilRefBack);
--- a/dom/canvas/WebGLContextValidate.cpp
+++ b/dom/canvas/WebGLContextValidate.cpp
@@ -616,33 +616,27 @@ WebGLContext::InitAndValidateGL(FailureR
     }
 
     if (!gl->IsSupported(GLFeature::vertex_array_object)) {
         *out_failReason = { "FEATURE_FAILURE_WEBGL_VAOS",
                             "Requires vertex_array_object." };
         return false;
     }
 
-    mDefaultVertexArray = WebGLVertexArray::Create(this);
-    mDefaultVertexArray->mAttribs.SetLength(mGLMaxVertexAttribs);
-    mBoundVertexArray = mDefaultVertexArray;
-
     // OpenGL core profiles remove the default VAO object from version
     // 4.0.0. We create a default VAO for all core profiles,
     // regardless of version.
     //
     // GL Spec 4.0.0:
     // (https://www.opengl.org/registry/doc/glspec40.core.20100311.pdf)
     // in Section E.2.2 "Removed Features", pg 397: "[...] The default
     // vertex array object (the name zero) is also deprecated. [...]"
-
-    if (gl->IsCoreProfile()) {
-        mDefaultVertexArray->GenVertexArray();
-        mDefaultVertexArray->BindVertexArray();
-    }
+    mDefaultVertexArray = WebGLVertexArray::Create(this);
+    mDefaultVertexArray->BindVertexArray();
+    mDefaultVertexArray->mAttribs.SetLength(mGLMaxVertexAttribs);
 
     mPixelStore_FlipY = false;
     mPixelStore_PremultiplyAlpha = false;
     mPixelStore_ColorspaceConversion = BROWSER_DEFAULT_WEBGL;
     mPixelStore_RequireFastPath = false;
 
     // GLES 3.0.4, p259:
     mPixelStore_UnpackImageHeight = 0;
--- a/dom/canvas/WebGLContextVertexArray.cpp
+++ b/dom/canvas/WebGLContextVertexArray.cpp
@@ -30,30 +30,28 @@ WebGLContext::BindVertexArray(WebGLVerte
         array = mDefaultVertexArray;
     }
 
     array->BindVertexArray();
 
     MOZ_ASSERT(mBoundVertexArray == array);
     if (mBoundVertexArray) {
         mBoundVertexArray->AddBufferBindCounts(+1);
+        mBoundVertexArray->mHasBeenBound = true;
     }
 }
 
 already_AddRefed<WebGLVertexArray>
 WebGLContext::CreateVertexArray()
 {
     const FuncScope funcScope(*this, "createVertexArray");
     if (IsContextLost())
         return nullptr;
 
     RefPtr<WebGLVertexArray> globj = CreateVertexArrayImpl();
-
-    globj->GenVertexArray();
-
     return globj.forget();
 }
 
 WebGLVertexArray*
 WebGLContext::CreateVertexArrayImpl()
 {
     return WebGLVertexArray::Create(this);
 }
--- a/dom/canvas/WebGLFramebuffer.cpp
+++ b/dom/canvas/WebGLFramebuffer.cpp
@@ -126,17 +126,18 @@ WebGLFBAttachPoint::IsComplete(WebGLCont
     if (tex) {
         // ES 3.0 spec, pg 213 has giant blocks of text that bake down to requiring that
         // attached tex images are within the valid mip-levels of the texture.
         // While it draws distinction to only test non-immutable textures, that's because
         // immutable textures are *always* texture-complete.
         // We need to check immutable textures though, because checking completeness is
         // also when we zero invalidated/no-data tex images.
         const bool complete = [&]() {
-            const auto texCompleteness = tex->CalcCompletenessInfo();
+            const bool ensureInit = false;
+            const auto texCompleteness = tex->CalcCompletenessInfo(ensureInit);
             if (!texCompleteness) // OOM
                 return false;
             if (!texCompleteness->levels)
                 return false;
 
             const auto baseLevel = tex->BaseMipmapLevel();
             const auto maxLevel = baseLevel + texCompleteness->levels - 1;
             return baseLevel <= mTexImageLevel && mTexImageLevel <= maxLevel;
@@ -503,20 +504,16 @@ WebGLFramebuffer::Delete()
 
     for (auto& cur : mColorAttachments) {
         cur.Clear();
     }
 
     mContext->gl->fDeleteFramebuffers(1, &mGLName);
 
     LinkedListElement<WebGLFramebuffer>::removeFrom(mContext->mFramebuffers);
-
-#ifdef ANDROID
-    mIsFB = false;
-#endif
 }
 
 ////
 
 Maybe<WebGLFBAttachPoint*>
 WebGLFramebuffer::GetColorAttachPoint(GLenum attachPoint)
 {
     if (attachPoint == LOCAL_GL_NONE)
@@ -1227,17 +1224,17 @@ WebGLFramebuffer::FramebufferTexture2D(G
         return;
     }
 
     // `texture`
     if (tex) {
         if (!mContext->ValidateObject("texture", *tex))
             return;
 
-        if (!tex->HasEverBeenBound()) {
+        if (!tex->Target()) {
             mContext->ErrorInvalidOperation("`texture` has never been bound.");
             return;
         }
 
         const TexTarget destTexTarget = TexImageTargetToTexTarget(texImageTarget);
         if (tex->Target() != destTexTarget) {
             mContext->ErrorInvalidOperation("Mismatched texture and texture target.");
             return;
@@ -1312,17 +1309,17 @@ WebGLFramebuffer::FramebufferTextureLaye
         return mContext->ErrorInvalidValue("`level` must be >= 0.");
 
     // `texture`
     GLenum texImageTarget = LOCAL_GL_TEXTURE_3D;
     if (tex) {
         if (!mContext->ValidateObject("texture", *tex))
             return;
 
-        if (!tex->HasEverBeenBound()) {
+        if (!tex->Target()) {
             mContext->ErrorInvalidOperation("`texture` has never been bound.");
             return;
         }
 
         texImageTarget = tex->Target().get();
         switch (texImageTarget) {
         case LOCAL_GL_TEXTURE_3D:
             if (uint32_t(layer) >= mContext->mGLMax3DTextureSize) {
--- a/dom/canvas/WebGLFramebuffer.h
+++ b/dom/canvas/WebGLFramebuffer.h
@@ -135,38 +135,28 @@ public:
 
 class WebGLFramebuffer final
     : public nsWrapperCache
     , public WebGLRefCountedObject<WebGLFramebuffer>
     , public LinkedListElement<WebGLFramebuffer>
     , public SupportsWeakPtr<WebGLFramebuffer>
     , public CacheInvalidator
 {
-    friend class WebGLContext;
-
 public:
     MOZ_DECLARE_WEAKREFERENCE_TYPENAME(WebGLFramebuffer)
 
     const GLuint mGLName;
+    bool mHasBeenBound = false;
 
 private:
     mutable uint64_t mNumFBStatusInvals = 0;
 
-protected:
-#ifdef ANDROID
-    // Bug 1140459: Some drivers (including our test slaves!) don't
-    // give reasonable answers for IsRenderbuffer, maybe others.
-    // This shows up on Android 2.3 emulator.
-    //
-    // So we track the `is a Framebuffer` state ourselves.
-    bool mIsFB = false;
-#endif
-
     ////
 
+protected:
     WebGLFBAttachPoint mDepthAttachment;
     WebGLFBAttachPoint mStencilAttachment;
     WebGLFBAttachPoint mDepthStencilAttachment;
 
     // In theory, this number can be unbounded based on the driver. However, no driver
     // appears to expose more than 8. We might as well stop there too, for now.
     // (http://opengl.gpuinfo.org/gl_stats_caps_single.php?listreportsbycap=GL_MAX_COLOR_ATTACHMENTS)
     static const size_t kMaxColorAttachments = 8; // jgilbert's MacBook Pro exposes 8.
--- a/dom/canvas/WebGLRenderbuffer.cpp
+++ b/dom/canvas/WebGLRenderbuffer.cpp
@@ -47,17 +47,16 @@ EmulatePackedDepthStencil(gl::GLContext*
     return !gl->IsSupported(gl::GLFeature::packed_depth_stencil);
 }
 
 WebGLRenderbuffer::WebGLRenderbuffer(WebGLContext* webgl)
     : WebGLRefCountedObject(webgl)
     , mPrimaryRB( DoCreateRenderbuffer(webgl->gl) )
     , mEmulatePackedDepthStencil( EmulatePackedDepthStencil(webgl->gl) )
     , mSecondaryRB(0)
-    , mHasBeenBound(false)
 {
     mContext->mRenderbuffers.insertBack(this);
 
     // Bind our RB, or we might end up calling FramebufferRenderbuffer before we ever call
     // BindRenderbuffer, since webgl.bindRenderbuffer doesn't actually call
     // glBindRenderbuffer anymore.
     mContext->gl->fBindRenderbuffer(LOCAL_GL_RENDERBUFFER, mPrimaryRB);
 }
--- a/dom/canvas/WebGLRenderbuffer.h
+++ b/dom/canvas/WebGLRenderbuffer.h
@@ -21,26 +21,25 @@ struct FormatUsageInfo;
 
 class WebGLRenderbuffer final
     : public nsWrapperCache
     , public WebGLRefCountedObject<WebGLRenderbuffer>
     , public LinkedListElement<WebGLRenderbuffer>
     , public WebGLRectangleObject
     , public CacheInvalidator
 {
-    friend class WebGLContext;
     friend class WebGLFramebuffer;
     friend class WebGLFBAttachPoint;
 
 public:
     const GLuint mPrimaryRB;
+    bool mHasBeenBound = false;
 protected:
     const bool mEmulatePackedDepthStencil;
     GLuint mSecondaryRB;
-    bool mHasBeenBound;
     webgl::ImageInfo mImageInfo;
 
 public:
     NS_INLINE_DECL_CYCLE_COLLECTING_NATIVE_REFCOUNTING(WebGLRenderbuffer)
     NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_NATIVE_CLASS(WebGLRenderbuffer)
 
     explicit WebGLRenderbuffer(WebGLContext* webgl);
 
--- a/dom/canvas/WebGLSampler.cpp
+++ b/dom/canvas/WebGLSampler.cpp
@@ -6,18 +6,16 @@
 #include "WebGLSampler.h"
 
 #include "GLContext.h"
 #include "mozilla/dom/WebGL2RenderingContextBinding.h"
 #include "WebGLContext.h"
 
 namespace mozilla {
 
-
-
 WebGLSampler::WebGLSampler(WebGLContext* const webgl)
     : WebGLRefCountedObject(webgl)
     , mGLName([&]() {
         GLuint ret = 0;
         webgl->gl->fGenSamplers(1, &ret);
         return ret;
     }())
 {
--- a/dom/canvas/WebGLSampler.h
+++ b/dom/canvas/WebGLSampler.h
@@ -15,19 +15,16 @@
 namespace mozilla {
 
 class WebGLSampler final
     : public nsWrapperCache
     , public WebGLRefCountedObject<WebGLSampler>
     , public LinkedListElement<WebGLSampler>
     , public CacheInvalidator
 {
-    friend class WebGLContext2;
-    friend class WebGLTexture;
-
     NS_INLINE_DECL_CYCLE_COLLECTING_NATIVE_REFCOUNTING(WebGLSampler)
     NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_NATIVE_CLASS(WebGLSampler)
 
 public:
     const GLuint mGLName;
 private:
     webgl::SamplingState mState;
 
--- a/dom/canvas/WebGLStrongTypes.h
+++ b/dom/canvas/WebGLStrongTypes.h
@@ -150,16 +150,18 @@ public:
     bool operator<=(const StrongGLenum& other) const {
         return get() <= other.get();
     }
 
     bool operator>=(const StrongGLenum& other) const {
         return get() >= other.get();
     }
 
+    explicit operator bool() const { return bool(get()); }
+
     static bool IsValueLegal(GLenum value) {
         if (value > UINT16_MAX) {
             return false;
         }
         return std::binary_search(Details::values(),
                                   Details::values() + Details::valuesCount(),
                                   uint16_t(value));
     }
--- a/dom/canvas/WebGLTexture.cpp
+++ b/dom/canvas/WebGLTexture.cpp
@@ -150,17 +150,17 @@ WebGLTexture::PopulateMipChain(const uin
 
 static bool
 ZeroTextureData(const WebGLContext* webgl, GLuint tex,
                 TexImageTarget target, uint32_t level,
                 const webgl::FormatUsageInfo* usage, uint32_t width, uint32_t height,
                 uint32_t depth);
 
 bool
-WebGLTexture::IsMipAndCubeComplete(const uint32_t maxLevel,
+WebGLTexture::IsMipAndCubeComplete(const uint32_t maxLevel, const bool ensureInit,
                                    bool* const out_initFailed) const
 {
     *out_initFailed = false;
 
     // Reference dimensions based on baseLevel.
     auto ref = BaseImageInfo();
     MOZ_ASSERT(ref.mWidth && ref.mHeight && ref.mDepth);
 
@@ -182,17 +182,17 @@ WebGLTexture::IsMipAndCubeComplete(const
             if (cur.mWidth != ref.mWidth ||
                 cur.mHeight != ref.mHeight ||
                 cur.mDepth != ref.mDepth ||
                 cur.mFormat != ref.mFormat)
             {
                 return false;
             }
 
-            if (MOZ_UNLIKELY( !cur.mHasData )) {
+            if (MOZ_UNLIKELY( ensureInit && !cur.mHasData )) {
                 auto imageTarget = mTarget.get();
                 if (imageTarget == LOCAL_GL_TEXTURE_CUBE_MAP) {
                     imageTarget = LOCAL_GL_TEXTURE_CUBE_MAP_POSITIVE_X + face;
                 }
                 if (!ZeroTextureData(mContext, mGLName, imageTarget, level,
                                      cur.mFormat, cur.mWidth, cur.mHeight, cur.mDepth))
                 {
                     mContext->ErrorOutOfMemory("Failed to zero tex image data.");
@@ -208,17 +208,17 @@ WebGLTexture::IsMipAndCubeComplete(const
             break;
         ref = next.ref();
     }
 
     return true;
 }
 
 Maybe<const WebGLTexture::CompletenessInfo>
-WebGLTexture::CalcCompletenessInfo() const
+WebGLTexture::CalcCompletenessInfo(const bool ensureInit, const bool skipMips) const
 {
     Maybe<CompletenessInfo> ret = Some(CompletenessInfo());
 
     // -
 
     if (mBaseMipmapLevel > kMaxLevelCount - 1) {
         ret->incompleteReason = "`level_base` too high.";
         return ret;
@@ -238,17 +238,17 @@ WebGLTexture::CalcCompletenessInfo() con
 
     if (!baseImageInfo.mWidth || !baseImageInfo.mHeight || !baseImageInfo.mDepth) {
         ret->incompleteReason = "The dimensions of `level_base` are not all positive.";
         return ret;
     }
 
     // "* The texture is a cube map texture, and is not cube complete."
     bool initFailed = false;
-    if (!IsMipAndCubeComplete(mBaseMipmapLevel, &initFailed)) {
+    if (!IsMipAndCubeComplete(mBaseMipmapLevel, ensureInit, &initFailed)) {
         if (initFailed)
             return {};
 
         // Can only fail if not cube-complete.
         ret->incompleteReason = "Cubemaps must be \"cube complete\".";
         return ret;
     }
     ret->levels = 1;
@@ -274,17 +274,20 @@ WebGLTexture::CalcCompletenessInfo() con
     // "* `level_base <= level_max`"
 
     const auto maxLevel = EffectiveMaxLevel();
     if (mBaseMipmapLevel > maxLevel) {
         ret->incompleteReason = "`level_base > level_max`.";
         return ret;
     }
 
-    if (!IsMipAndCubeComplete(maxLevel, &initFailed)) {
+    if (skipMips)
+        return ret;
+
+    if (!IsMipAndCubeComplete(maxLevel, ensureInit, &initFailed)) {
         if (initFailed)
             return {};
 
         ret->incompleteReason = "Bad mipmap dimension or format.";
         return ret;
     }
     ret->levels = maxLevel - mBaseMipmapLevel + 1;
     ret->mipmapComplete = true;
@@ -294,17 +297,18 @@ WebGLTexture::CalcCompletenessInfo() con
     return ret;
 }
 
 Maybe<const webgl::SampleableInfo>
 WebGLTexture::CalcSampleableInfo(const WebGLSampler* const sampler) const
 {
     Maybe<webgl::SampleableInfo> ret = Some(webgl::SampleableInfo());
 
-    const auto completeness = CalcCompletenessInfo();
+    const bool ensureInit = true;
+    const auto completeness = CalcCompletenessInfo(ensureInit);
     if (!completeness)
         return {};
 
     ret->incompleteReason = completeness->incompleteReason;
 
     if (!completeness->levels)
         return ret;
 
@@ -572,18 +576,19 @@ ZeroTextureData(const WebGLContext* webg
     // 1. Lazy zeroing of uninitialized textures:
     //    a. Before draw.
     //    b. Before partial upload. (TexStorage + TexSubImage)
     // 2. Zero subrects from out-of-bounds blits. (CopyTex(Sub)Image)
 
     // We have no sympathy for any of these cases.
 
     // "Doctor, it hurts when I do this!" "Well don't do that!"
-    webgl->GenerateWarning("This operation requires zeroing texture data. This is"
-                           " slow.");
+    const auto targetStr = EnumString(target.get());
+    webgl->GeneratePerfWarning("Tex image %s level %u is incurring lazy initialization.",
+                               targetStr.c_str(), level);
 
     gl::GLContext* gl = webgl->GL();
 
     GLenum scopeBindTarget;
     switch (target.get()) {
     case LOCAL_GL_TEXTURE_CUBE_MAP_POSITIVE_X:
     case LOCAL_GL_TEXTURE_CUBE_MAP_NEGATIVE_X:
     case LOCAL_GL_TEXTURE_CUBE_MAP_POSITIVE_Y:
@@ -693,17 +698,17 @@ WebGLTexture::ClampLevelBaseAndMax()
 bool
 WebGLTexture::BindTexture(TexTarget texTarget)
 {
     if (IsDeleted()) {
         mContext->ErrorInvalidOperation("bindTexture: Cannot bind a deleted object.");
         return false;
     }
 
-    const bool isFirstBinding = !HasEverBeenBound();
+    const bool isFirstBinding = !mTarget;
     if (!isFirstBinding && mTarget != texTarget) {
         mContext->ErrorInvalidOperation("bindTexture: This texture has already been bound"
                                         " to a different target.");
         return false;
     }
 
     mTarget = texTarget;
 
@@ -732,17 +737,20 @@ WebGLTexture::BindTexture(TexTarget texT
 void
 WebGLTexture::GenerateMipmap()
 {
     // GLES 3.0.4 p160:
     // "Mipmap generation replaces texel array levels level base + 1 through q with arrays
     //  derived from the level base array, regardless of their previous contents. All
     //  other mipmap arrays, including the level base array, are left unchanged by this
     //  computation."
-    const auto completeness = CalcCompletenessInfo();
+    // But only check and init the base level.
+    const bool ensureInit = true;
+    const bool skipMips = true;
+    const auto completeness = CalcCompletenessInfo(ensureInit, skipMips);
     if (!completeness || !completeness->levels) {
         mContext->ErrorInvalidOperation("The texture's base level must be complete.");
         return;
     }
     const auto& usage = completeness->usage;
     const auto& format = usage->format;
     if (!mContext->IsWebGL2()) {
         if (!completeness->powerOfTwo) {
@@ -851,22 +859,16 @@ WebGLTexture::GetTexParameter(TexTarget 
         mContext->gl->fGetTexParameterfv(texTarget.get(), pname, &f);
         return JS::NumberValue(float(f));
 
     default:
         MOZ_CRASH("GFX: Unhandled pname.");
     }
 }
 
-bool
-WebGLTexture::IsTexture() const
-{
-    return HasEverBeenBound() && !IsDeleted();
-}
-
 // Here we have to support all pnames with both int and float params.
 // See this discussion:
 //   https://www.khronos.org/webgl/public-mailing-list/archives/1008/msg00014.html
 void
 WebGLTexture::TexParameter(TexTarget texTarget, GLenum pname, const FloatOrInt& param)
 {
     bool isPNameValid = false;
     switch (pname) {
--- a/dom/canvas/WebGLTexture.h
+++ b/dom/canvas/WebGLTexture.h
@@ -140,17 +140,18 @@ protected:
         bool mipmapComplete = false;
         const webgl::FormatUsageInfo* usage = nullptr;
         const char* incompleteReason = nullptr;
     };
 
     mutable CacheWeakMap<const WebGLSampler*, webgl::SampleableInfo> mSamplingCache;
 
 public:
-    Maybe<const CompletenessInfo> CalcCompletenessInfo() const;
+    Maybe<const CompletenessInfo> CalcCompletenessInfo(bool ensureInit,
+                                                       bool skipMips = false) const;
     Maybe<const webgl::SampleableInfo> CalcSampleableInfo(const WebGLSampler*) const;
 
     const webgl::SampleableInfo* GetSampleableInfo(const WebGLSampler*) const;
 
 
     // -
 
     const auto& Immutable() const { return mImmutable; }
@@ -167,17 +168,16 @@ public:
 
     NS_INLINE_DECL_CYCLE_COLLECTING_NATIVE_REFCOUNTING(WebGLTexture)
     NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_NATIVE_CLASS(WebGLTexture)
 
     WebGLTexture(WebGLContext* webgl, GLuint tex);
 
     void Delete();
 
-    bool HasEverBeenBound() const { return mTarget != LOCAL_GL_NONE; }
     TexTarget Target() const { return mTarget; }
 
     WebGLContext* GetParentObject() const {
         return mContext;
     }
 
     virtual JSObject* WrapObject(JSContext* cx, JS::Handle<JSObject*> givenProto) override;
 
@@ -187,17 +187,16 @@ protected:
     }
 
 public:
     ////////////////////////////////////
     // GL calls
     bool BindTexture(TexTarget texTarget);
     void GenerateMipmap();
     JS::Value GetTexParameter(TexTarget texTarget, GLenum pname);
-    bool IsTexture() const;
     void TexParameter(TexTarget texTarget, GLenum pname, const FloatOrInt& param);
 
     ////////////////////////////////////
     // WebGLTextureUpload.cpp
 
 protected:
     void TexOrSubImageBlob(bool isSubImage, TexImageTarget target,
                            GLint level, GLenum internalFormat, GLint xOffset,
@@ -305,17 +304,18 @@ public:
         return ImageInfoAtFace(0, mBaseMipmapLevel);
     }
 
     size_t MemoryUsage() const;
 
     bool EnsureImageDataInitialized(TexImageTarget target,
                                     uint32_t level);
     void PopulateMipChain(uint32_t maxLevel);
-    bool IsMipAndCubeComplete(uint32_t maxLevel, bool* out_initFailed) const;
+    bool IsMipAndCubeComplete(uint32_t maxLevel, bool ensureInit,
+                              bool* out_initFailed) const;
 
     bool IsCubeMap() const { return (mTarget == LOCAL_GL_TEXTURE_CUBE_MAP); }
 };
 
 inline TexImageTarget
 TexImageTargetForTargetAndFace(TexTarget target, uint8_t face)
 {
     switch (target.get()) {
--- a/dom/canvas/WebGLTransformFeedback.h
+++ b/dom/canvas/WebGLTransformFeedback.h
@@ -25,16 +25,17 @@ class WebGLTransformFeedback final
     friend class WebGL2Context;
     friend class WebGLProgram;
 
     friend const webgl::CachedDrawFetchLimits*
         ValidateDraw(WebGLContext*, GLenum, uint32_t);
 
 public:
     const GLuint mGLName;
+    bool mHasBeenBound = false;
 private:
     // GLES 3.0.4 p267, Table 6.24 "Transform Feedback State"
     // It's not yet in the ES3 spec, but the generic TF buffer bind point has been moved
     // to context state, instead of TFO state.
     std::vector<IndexedBufferBinding> mIndexedBindings;
     bool mIsPaused;
     bool mIsActive;
     // Not in state tables:
--- a/dom/canvas/WebGLVertexArray.cpp
+++ b/dom/canvas/WebGLVertexArray.cpp
@@ -15,19 +15,19 @@
 namespace mozilla {
 
 JSObject*
 WebGLVertexArray::WrapObject(JSContext* cx, JS::Handle<JSObject*> givenProto)
 {
     return dom::WebGLVertexArrayObject_Binding::Wrap(cx, this, givenProto);
 }
 
-WebGLVertexArray::WebGLVertexArray(WebGLContext* webgl)
+WebGLVertexArray::WebGLVertexArray(WebGLContext* const webgl, const GLuint name)
     : WebGLRefCountedObject(webgl)
-    , mGLName(0)
+    , mGLName(name)
 {
     mAttribs.SetLength(mContext->mGLMaxVertexAttribs);
     mContext->mVertexArrays.insertBack(this);
 }
 
 WebGLVertexArray::~WebGLVertexArray()
 {
     MOZ_ASSERT(IsDeleted());
@@ -60,22 +60,16 @@ WebGLVertexArray::Delete()
 {
     DeleteImpl();
 
     LinkedListElement<WebGLVertexArray>::removeFrom(mContext->mVertexArrays);
     mElementArrayBuffer = nullptr;
     mAttribs.Clear();
 }
 
-bool
-WebGLVertexArray::IsVertexArray() const
-{
-    return IsVertexArrayImpl();
-}
-
 NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(WebGLVertexArray,
                                       mAttribs,
                                       mElementArrayBuffer)
 
 NS_IMPL_CYCLE_COLLECTION_ROOT_NATIVE(WebGLVertexArray, AddRef)
 NS_IMPL_CYCLE_COLLECTION_UNROOT_NATIVE(WebGLVertexArray, Release)
 
 } // namespace mozilla
--- a/dom/canvas/WebGLVertexArray.h
+++ b/dom/canvas/WebGLVertexArray.h
@@ -26,49 +26,40 @@ class WebGLVertexArray
     : public nsWrapperCache
     , public WebGLRefCountedObject<WebGLVertexArray>
     , public LinkedListElement<WebGLVertexArray>
     , public CacheInvalidator
 {
 public:
     static WebGLVertexArray* Create(WebGLContext* webgl);
 
-    void BindVertexArray() {
-        // Bind to dummy value to signal that this vertex array has ever been
-        // bound.
-        BindVertexArrayImpl();
-    };
-
-    // Implement parent classes:
     void Delete();
-    bool IsVertexArray() const;
 
     WebGLContext* GetParentObject() const {
         return mContext;
     }
 
     virtual JSObject* WrapObject(JSContext* cx, JS::Handle<JSObject*> givenProto) override;
 
     NS_INLINE_DECL_CYCLE_COLLECTING_NATIVE_REFCOUNTING(WebGLVertexArray)
     NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_NATIVE_CLASS(WebGLVertexArray)
 
-    GLuint GLName() const { return mGLName; }
-
     void AddBufferBindCounts(int8_t addVal) const;
 
 protected:
-    explicit WebGLVertexArray(WebGLContext* webgl);
+    WebGLVertexArray(WebGLContext* webgl, GLuint name);
     virtual ~WebGLVertexArray();
 
-    virtual void GenVertexArray() = 0;
-    virtual void BindVertexArrayImpl() = 0;
+    virtual void BindVertexArray() = 0;
     virtual void DeleteImpl() = 0;
-    virtual bool IsVertexArrayImpl() const = 0;
 
-    GLuint mGLName;
+public:
+    const GLuint mGLName;
+    bool mHasBeenBound = false;
+protected:
     nsTArray<WebGLVertexAttribData> mAttribs;
     WebGLRefPtr<WebGLBuffer> mElementArrayBuffer;
 
     friend class ScopedDrawHelper;
     friend class WebGLContext;
     friend class WebGLVertexArrayFake;
     friend class WebGL2Context;
     friend struct webgl::LinkedProgramInfo;
--- a/dom/canvas/WebGLVertexArrayFake.cpp
+++ b/dom/canvas/WebGLVertexArrayFake.cpp
@@ -6,22 +6,21 @@
 #include "WebGLVertexArrayFake.h"
 
 #include "GLContext.h"
 #include "WebGLContext.h"
 
 namespace mozilla {
 
 WebGLVertexArrayFake::WebGLVertexArrayFake(WebGLContext* webgl)
-    : WebGLVertexArray(webgl)
-    , mIsVAO(false)
+    : WebGLVertexArray(webgl, 0)
 { }
 
 void
-WebGLVertexArrayFake::BindVertexArrayImpl()
+WebGLVertexArrayFake::BindVertexArray()
 {
     // Go through and re-bind all buffers and setup all
     // vertex attribute pointers
     gl::GLContext* gl = mContext->gl;
 
     WebGLRefPtr<WebGLVertexArray> prevVertexArray = mContext->mBoundVertexArray;
 
     mContext->mBoundVertexArray = this;
@@ -47,24 +46,11 @@ WebGLVertexArrayFake::BindVertexArrayImp
         const auto& vd = prevVertexArray->mAttribs[i];
 
         if (vd.mEnabled) {
             gl->fDisableVertexAttribArray(i);
         }
     }
 
     mContext->BindBuffer(LOCAL_GL_ARRAY_BUFFER, prevBuffer);
-    mIsVAO = true;
-}
-
-void
-WebGLVertexArrayFake::DeleteImpl()
-{
-    mIsVAO = false;
-}
-
-bool
-WebGLVertexArrayFake::IsVertexArrayImpl() const
-{
-    return mIsVAO;
 }
 
 } // namespace mozilla
--- a/dom/canvas/WebGLVertexArrayFake.h
+++ b/dom/canvas/WebGLVertexArrayFake.h
@@ -11,26 +11,22 @@
 namespace mozilla {
 
 class WebGLVertexArrayFake final
     : public WebGLVertexArray
 {
     friend class WebGLVertexArray;
 
 protected:
-    virtual void BindVertexArrayImpl() override;
-    virtual void DeleteImpl() override;
-    virtual void GenVertexArray() override {};
-    virtual bool IsVertexArrayImpl() const override;
+    virtual void BindVertexArray() override;
+    virtual void DeleteImpl() override {}
 
 private:
     explicit WebGLVertexArrayFake(WebGLContext* webgl);
 
     ~WebGLVertexArrayFake() {
         DeleteOnce();
     }
-
-    bool mIsVAO;
 };
 
 } // namespace mozilla
 
 #endif // WEBGL_VERTEX_ARRAY_FAKE_H_
--- a/dom/canvas/WebGLVertexArrayGL.cpp
+++ b/dom/canvas/WebGLVertexArrayGL.cpp
@@ -6,55 +6,37 @@
 #include "WebGLVertexArrayGL.h"
 
 #include "GLContext.h"
 #include "WebGLContext.h"
 
 namespace mozilla {
 
 WebGLVertexArrayGL::WebGLVertexArrayGL(WebGLContext* webgl)
-    : WebGLVertexArray(webgl)
-    , mIsVAO(false)
+    : WebGLVertexArray(webgl,
+                       [&]() {
+                           GLuint ret = 0;
+                           webgl->gl->fGenVertexArrays(1, &ret);
+                           return ret;
+                       }())
 { }
 
 WebGLVertexArrayGL::~WebGLVertexArrayGL()
 {
     DeleteOnce();
 }
 
 void
 WebGLVertexArrayGL::DeleteImpl()
 {
     mElementArrayBuffer = nullptr;
 
     mContext->gl->fDeleteVertexArrays(1, &mGLName);
-
-    mIsVAO = false;
-}
-
-void
-WebGLVertexArrayGL::BindVertexArrayImpl()
-{
-    mContext->mBoundVertexArray = this;
-    mContext->gl->fBindVertexArray(mGLName);
-
-    mIsVAO = true;
 }
 
 void
-WebGLVertexArrayGL::GenVertexArray()
-{
-    mContext->gl->fGenVertexArrays(1, &mGLName);
-}
-
-bool
-WebGLVertexArrayGL::IsVertexArrayImpl() const
+WebGLVertexArrayGL::BindVertexArray()
 {
-    gl::GLContext* gl = mContext->gl;
-    if (gl->WorkAroundDriverBugs())
-    {
-        return mIsVAO;
-    }
-
-    return mContext->gl->fIsVertexArray(mGLName) != 0;
+    mContext->mBoundVertexArray = this;
+    mContext->gl->fBindVertexArray(mGLName);
 }
 
 } // namespace mozilla
--- a/dom/canvas/WebGLVertexArrayGL.h
+++ b/dom/canvas/WebGLVertexArrayGL.h
@@ -12,26 +12,18 @@ namespace mozilla {
 
 class WebGLVertexArrayGL
     : public WebGLVertexArray
 {
     friend class WebGLVertexArray;
 
 public:
     virtual void DeleteImpl() override;
-    virtual void BindVertexArrayImpl() override;
-    virtual void GenVertexArray() override;
-    virtual bool IsVertexArrayImpl() const override;
+    virtual void BindVertexArray() override;
 
 protected:
     explicit WebGLVertexArrayGL(WebGLContext* webgl);
     ~WebGLVertexArrayGL();
-
-    // Bug 1140459: Some drivers (including our test slaves!) don't
-    // give reasonable answers for IsVertexArray, maybe others.
-    //
-    // So we track the `is a VAO` state ourselves.
-    bool mIsVAO;
 };
 
 } // namespace mozilla
 
 #endif // WEBGL_VERTEX_ARRAY_GL_H_
--- a/dom/canvas/test/webgl-conf/checkout/conformance/extensions/oes-vertex-array-object.html
+++ b/dom/canvas/test/webgl-conf/checkout/conformance/extensions/oes-vertex-array-object.html
@@ -640,21 +640,21 @@ function runBoundDeleteTests() {
         }
 
         // If retained, everything should still work. If cleared, drawing should now fail.
         gl.drawElements(gl.TRIANGLES, 6, gl.UNSIGNED_BYTE, 0);
         var expectedError = (expectRetained ? gl.NO_ERROR : gl.INVALID_OPERATION);
         wtu.glErrorShouldBe(gl, expectedError,
                             "Draw call should " + (expectRetained ? "not " : "") + "fail.");
 
-        if (!gl.isBuffer(positionBuffer)) {
-            testFailed("References from unbound VAOs keep Position buffer alive.");
+        if (gl.isBuffer(positionBuffer)) {
+            testFailed("References from unbound VAOs don't keep Position buffer alive.");
         }
-        if (!gl.isBuffer(colorBuffer)) {
-            testFailed("References from unbound VAOs keep Color buffer alive");
+        if (gl.isBuffer(colorBuffer)) {
+            testFailed("References from unbound VAOs don't keep Color buffer alive");
         }
     }
 }
 
 function runArrayBufferBindTests() {
     debug("");
     debug("Testing that buffer bindings on VAOs don't affect default VAO ARRAY_BUFFER binding.");
 
--- a/dom/canvas/test/webgl-conf/checkout/conformance2/vertex_arrays/vertex-array-object.html
+++ b/dom/canvas/test/webgl-conf/checkout/conformance2/vertex_arrays/vertex-array-object.html
@@ -585,21 +585,21 @@ function runBoundDeleteTests() {
       }
 
       // If retained, everything should still work. If cleared, drawing should now fail.
       gl.drawElements(gl.TRIANGLES, 6, gl.UNSIGNED_BYTE, 0);
       var expectedError = (expectRetained ? gl.NO_ERROR : gl.INVALID_OPERATION);
       wtu.glErrorShouldBe(gl, expectedError,
                           "Draw call should " + (expectRetained ? "not " : "") + "fail.");
 
-      if (!gl.isBuffer(positionBuffer)) {
-        testFailed("References from unbound VAOs keep Position buffer alive.");
+      if (gl.isBuffer(positionBuffer)) {
+        testFailed("References from unbound VAOs don't keep Position buffer alive.");
       }
-      if (!gl.isBuffer(colorBuffer)) {
-        testFailed("References from unbound VAOs keep Color buffer alive");
+      if (gl.isBuffer(colorBuffer)) {
+        testFailed("References from unbound VAOs don't keep Color buffer alive");
       }
     }
 }
 
 function runArrayBufferBindTests() {
     debug("Testing that VAOs don't effect ARRAY_BUFFER binding.");
 
     gl.bindVertexArray(null);
--- a/dom/canvas/test/webgl-conf/generated-mochitest.ini
+++ b/dom/canvas/test/webgl-conf/generated-mochitest.ini
@@ -5319,16 +5319,17 @@ fail-if = (os == 'win')
 [generated/test_2_conformance2__rendering__element-index-uint.html]
 subsuite = webgl2-core
 [generated/test_2_conformance2__rendering__framebuffer-completeness-draw-framebuffer.html]
 subsuite = webgl2-core
 [generated/test_2_conformance2__rendering__framebuffer-completeness-unaffected.html]
 subsuite = webgl2-core
 [generated/test_2_conformance2__rendering__framebuffer-texture-changing-base-level.html]
 subsuite = webgl2-core
+fail-if = (os == 'win')
 [generated/test_2_conformance2__rendering__framebuffer-texture-level1.html]
 subsuite = webgl2-core
 fail-if = (os == 'mac')
 [generated/test_2_conformance2__rendering__framebuffer-unsupported.html]
 subsuite = webgl2-core
 [generated/test_2_conformance2__rendering__fs-color-type-mismatch-color-buffer-type.html]
 subsuite = webgl2-core
 fail-if = (os == 'mac') || (os == 'win')
--- a/dom/canvas/test/webgl-conf/mochitest-errata.ini
+++ b/dom/canvas/test/webgl-conf/mochitest-errata.ini
@@ -1096,8 +1096,11 @@ skip-if = (os == 'win')
 [generated/test_conformance__misc__webgl-specific-stencil-settings.html]
 skip-if = (os == 'win')
 [generated/test_conformance__textures__misc__tex-video-using-tex-unit-non-zero.html]
 # Fails on QuantumRender configs, but passes on standard configs?
 # Might be intermittant.
 skip-if = (os == 'win')
 [generated/test_2_conformance__textures__misc__tex-video-using-tex-unit-non-zero.html]
 skip-if = (os == 'win')
+[generated/test_2_conformance2__rendering__framebuffer-texture-changing-base-level.html]
+# https://bugzilla.mozilla.org/show_bug.cgi?id=1501868 (ANGLE bug)
+fail-if = (os == 'win')
--- a/dom/html/nsTextEditorState.cpp
+++ b/dom/html/nsTextEditorState.cpp
@@ -40,16 +40,19 @@
 #include "mozilla/dom/ScriptSettings.h"
 #include "mozilla/dom/HTMLInputElement.h"
 #include "mozilla/dom/HTMLTextAreaElement.h"
 #include "mozilla/dom/Text.h"
 #include "nsNumberControlFrame.h"
 #include "nsFrameSelection.h"
 #include "mozilla/ErrorResult.h"
 #include "mozilla/Telemetry.h"
+#include "mozilla/ShortcutKeys.h"
+#include "nsXBLPrototypeHandler.h"
+#include "mozilla/dom/KeyboardEvent.h"
 
 using namespace mozilla;
 using namespace mozilla::dom;
 
 inline nsresult
 SetEditorFlagsIfNecessary(EditorBase& aEditorBase, uint32_t aFlags)
 {
   if (aEditorBase.Flags() == aFlags) {
@@ -952,46 +955,77 @@ TextInputListener::HandleEvent(Event* aE
   if (aEvent->DefaultPrevented()) {
     return NS_OK;
   }
 
   if (!aEvent->IsTrusted()) {
     return NS_OK;
   }
 
-  WidgetKeyboardEvent* keyEvent =
-    aEvent->WidgetEventPtr()->AsKeyboardEvent();
+  RefPtr<KeyboardEvent> keyEvent = aEvent->AsKeyboardEvent();
+  if (!keyEvent) {
+    return NS_ERROR_UNEXPECTED;
+  }
+
+  WidgetKeyboardEvent* widgetKeyEvent =
+      aEvent->WidgetEventPtr()->AsKeyboardEvent();
   if (!keyEvent) {
     return NS_ERROR_UNEXPECTED;
   }
 
-  if (keyEvent->mMessage != eKeyPress) {
+  nsXBLPrototypeHandler* keyHandlers =
+      ShortcutKeys::GetHandlers(mTxtCtrlElement->IsTextArea() ?
+                                HandlerType::eTextArea : HandlerType::eInput);
+
+  RefPtr<nsAtom> eventTypeAtom =
+    ShortcutKeys::ConvertEventToDOMEventType(widgetKeyEvent);
+  for (nsXBLPrototypeHandler* handler = keyHandlers;
+       handler;
+       handler = handler->GetNextHandler()) {
+    if (!handler->EventTypeEquals(eventTypeAtom)) {
+      continue;
+    }
+
+    if (!handler->KeyEventMatched(keyEvent, 0, IgnoreModifierState())) {
+      continue;
+    }
+
+    // XXX Do we execute only one handler even if the handler neither stops
+    //     propagation nor prevents default of the event?
+    nsCOMPtr<EventTarget> target = do_QueryInterface(mTxtCtrlElement);
+    nsresult rv = handler->ExecuteHandler(target, aEvent);
+    if (NS_SUCCEEDED(rv)) {
+      return rv;
+    }
+  }
+
+  if (widgetKeyEvent->mMessage != eKeyPress) {
     return NS_OK;
   }
 
   nsIWidget::NativeKeyBindingsType nativeKeyBindingsType =
     mTxtCtrlElement->IsTextArea() ?
       nsIWidget::NativeKeyBindingsForMultiLineEditor :
       nsIWidget::NativeKeyBindingsForSingleLineEditor;
 
-  nsIWidget* widget = keyEvent->mWidget;
+  nsIWidget* widget = widgetKeyEvent->mWidget;
   // If the event is created by chrome script, the widget is nullptr.
   if (!widget) {
     widget = mFrame->GetNearestWidget();
     NS_ENSURE_TRUE(widget, NS_OK);
   }
 
   // WidgetKeyboardEvent::ExecuteEditCommands() requires non-nullptr mWidget.
   // If the event is created by chrome script, it is nullptr but we need to
   // execute native key bindings.  Therefore, we need to set widget to
   // WidgetEvent::mWidget temporarily.
-  AutoRestore<nsCOMPtr<nsIWidget>> saveWidget(keyEvent->mWidget);
-  keyEvent->mWidget = widget;
-  if (keyEvent->ExecuteEditCommands(nativeKeyBindingsType,
-                                    DoCommandCallback, mFrame)) {
+  AutoRestore<nsCOMPtr<nsIWidget>> saveWidget(widgetKeyEvent->mWidget);
+  widgetKeyEvent->mWidget = widget;
+  if (widgetKeyEvent->ExecuteEditCommands(nativeKeyBindingsType,
+                                          DoCommandCallback, mFrame)) {
     aEvent->PreventDefault();
   }
   return NS_OK;
 }
 
 void
 TextInputListener::OnEditActionHandled()
 {
--- a/dom/ipc/TabChild.cpp
+++ b/dom/ipc/TabChild.cpp
@@ -239,48 +239,46 @@ TabChildBase::DispatchMessageManagerMess
     RefPtr<TabChildMessageManager> kungFuDeathGrip(mTabChildMessageManager);
     RefPtr<nsFrameMessageManager> mm = kungFuDeathGrip->GetMessageManager();
     mm->ReceiveMessage(static_cast<EventTarget*>(kungFuDeathGrip), nullptr,
                        aMessageName, false, &data, nullptr, nullptr, nullptr,
                        IgnoreErrors());
 }
 
 bool
-TabChildBase::UpdateFrameHandler(const FrameMetrics& aFrameMetrics)
+TabChildBase::UpdateFrameHandler(const RepaintRequest& aRequest)
 {
-  MOZ_ASSERT(aFrameMetrics.GetScrollId() != FrameMetrics::NULL_SCROLL_ID);
-
-  if (aFrameMetrics.IsRootContent()) {
+  MOZ_ASSERT(aRequest.GetScrollId() != FrameMetrics::NULL_SCROLL_ID);
+
+  if (aRequest.IsRootContent()) {
     if (nsCOMPtr<nsIPresShell> shell = GetPresShell()) {
       // Guard against stale updates (updates meant for a pres shell which
       // has since been torn down and destroyed).
-      if (aFrameMetrics.GetPresShellId() == shell->GetPresShellId()) {
-        ProcessUpdateFrame(aFrameMetrics);
+      if (aRequest.GetPresShellId() == shell->GetPresShellId()) {
+        ProcessUpdateFrame(aRequest);
         return true;
       }
     }
   } else {
-    // aFrameMetrics.mIsRoot is false, so we are trying to update a subframe.
+    // aRequest.mIsRoot is false, so we are trying to update a subframe.
     // This requires special handling.
-    FrameMetrics newSubFrameMetrics(aFrameMetrics);
-    APZCCallbackHelper::UpdateSubFrame(newSubFrameMetrics);
+    APZCCallbackHelper::UpdateSubFrame(aRequest);
     return true;
   }
   return true;
 }
 
 void
-TabChildBase::ProcessUpdateFrame(const FrameMetrics& aFrameMetrics)
+TabChildBase::ProcessUpdateFrame(const RepaintRequest& aRequest)
 {
-    if (!mTabChildMessageManager) {
-        return;
-    }
-
-    FrameMetrics newMetrics = aFrameMetrics;
-    APZCCallbackHelper::UpdateRootFrame(newMetrics);
+  if (!mTabChildMessageManager) {
+      return;
+  }
+
+  APZCCallbackHelper::UpdateRootFrame(aRequest);
 }
 
 NS_IMETHODIMP
 ContentListener::HandleEvent(Event* aEvent)
 {
   RemoteDOMEvent remoteEvent;
   remoteEvent.mEvent = aEvent;
   NS_ENSURE_STATE(remoteEvent.mEvent);
@@ -1298,19 +1296,19 @@ TabChild::RecvSizeModeChanged(const nsSi
   nsPresContext* presContext = document->GetPresContext();
   if (presContext) {
     presContext->SizeModeChanged(aSizeMode);
   }
   return IPC_OK();
 }
 
 bool
-TabChild::UpdateFrame(const FrameMetrics& aFrameMetrics)
+TabChild::UpdateFrame(const RepaintRequest& aRequest)
 {
-  return TabChildBase::UpdateFrameHandler(aFrameMetrics);
+  return TabChildBase::UpdateFrameHandler(aRequest);
 }
 
 mozilla::ipc::IPCResult
 TabChild::RecvSuppressDisplayport(const bool& aEnabled)
 {
   if (nsCOMPtr<nsIPresShell> shell = GetPresShell()) {
     shell->SuppressDisplayport(aEnabled);
   }
--- a/dom/ipc/TabChild.h
+++ b/dom/ipc/TabChild.h
@@ -180,19 +180,19 @@ protected:
   // Wraps up a JSON object as a structured clone and sends it to the browser
   // chrome script.
   //
   // XXX/bug 780335: Do the work the browser chrome script does in C++ instead
   // so we don't need things like this.
   void DispatchMessageManagerMessage(const nsAString& aMessageName,
                                      const nsAString& aJSONData);
 
-  void ProcessUpdateFrame(const mozilla::layers::FrameMetrics& aFrameMetrics);
+  void ProcessUpdateFrame(const mozilla::layers::RepaintRequest& aRequest);
 
-  bool UpdateFrameHandler(const mozilla::layers::FrameMetrics& aFrameMetrics);
+  bool UpdateFrameHandler(const mozilla::layers::RepaintRequest& aRequest);
 
 protected:
   RefPtr<TabChildMessageManager> mTabChildMessageManager;
   nsCOMPtr<nsIWebBrowserChrome3> mWebBrowserChrome;
 };
 
 class TabChild final : public TabChildBase,
                        public PBrowserChild,
@@ -630,17 +630,17 @@ public:
                               const LayoutDevicePoint& aPoint,
                               const Modifiers& aModifiers,
                               const ScrollableLayerGuid& aGuid,
                               const uint64_t& aInputBlockId) override;
 
   void SetAllowedTouchBehavior(uint64_t aInputBlockId,
                                const nsTArray<TouchBehaviorFlags>& aFlags) const;
 
-  bool UpdateFrame(const FrameMetrics& aFrameMetrics);
+  bool UpdateFrame(const layers::RepaintRequest& aRequest);
   bool NotifyAPZStateChange(const ViewID& aViewId,
                             const layers::GeckoContentController::APZStateChange& aChange,
                             const int& aArg);
   void StartScrollbarDrag(const layers::AsyncDragMetrics& aDragMetrics);
   void ZoomToRect(const uint32_t& aPresShellId,
                   const FrameMetrics::ViewID& aViewId,
                   const CSSRect& aRect,
                   const uint32_t& aFlags);
new file mode 100644
--- /dev/null
+++ b/dom/tests/mochitest/keyhandling/browsertest.html
@@ -0,0 +1,108 @@
+<!DOCTYPE html>
+
+<html>
+<body>
+<p style="white-space: nowrap">
+  A long paragraph to make a horizontal scrollbar.
+  A long paragraph to make a horizontal scrollbar.
+  A long paragraph to make a horizontal scrollbar.
+  A long paragraph to make a horizontal scrollbar.
+  A long paragraph to make a horizontal scrollbar.
+  A long paragraph to make a horizontal scrollbar.
+  A long paragraph to make a horizontal scrollbar.
+  A long paragraph to make a horizontal scrollbar.
+  A long paragraph to make a horizontal scrollbar.
+  A long paragraph to make a horizontal scrollbar.
+</p>
+<p id="paragraph">Lots of paragraphs to make a vertical scrollbar.</p>
+<p>Lots of paragraphs to make a vertical scrollbar.</p>
+<p>Lots of paragraphs to make a vertical scrollbar.</p>
+<p>Lots of paragraphs to make a vertical scrollbar.</p>
+<p>Lots of paragraphs to make a vertical scrollbar.</p>
+<p>Lots of paragraphs to make a vertical scrollbar.</p>
+<p>Lots of paragraphs to make a vertical scrollbar.</p>
+<p>Lots of paragraphs to make a vertical scrollbar.</p>
+<p>Lots of paragraphs to make a vertical scrollbar.</p>
+<p>Lots of paragraphs to make a vertical scrollbar.</p>
+<p>Lots of paragraphs to make a vertical scrollbar.</p>
+<p>Lots of paragraphs to make a vertical scrollbar.</p>
+<p>Lots of paragraphs to make a vertical scrollbar.</p>
+<p>Lots of paragraphs to make a vertical scrollbar.</p>
+<p>Lots of paragraphs to make a vertical scrollbar.</p>
+<p>Lots of paragraphs to make a vertical scrollbar.</p>
+<p>Lots of paragraphs to make a vertical scrollbar.</p>
+<p>Lots of paragraphs to make a vertical scrollbar.</p>
+<p>Lots of paragraphs to make a vertical scrollbar.</p>
+<p>Lots of paragraphs to make a vertical scrollbar.</p>
+<p>Lots of paragraphs to make a vertical scrollbar.</p>
+<p>Lots of paragraphs to make a vertical scrollbar.</p>
+<p>Lots of paragraphs to make a vertical scrollbar.</p>
+<p>Lots of paragraphs to make a vertical scrollbar.</p>
+<p>Lots of paragraphs to make a vertical scrollbar.</p>
+<p>Lots of paragraphs to make a vertical scrollbar.</p>
+<p>Lots of paragraphs to make a vertical scrollbar.</p>
+<p>Lots of paragraphs to make a vertical scrollbar.</p>
+<p>Lots of paragraphs to make a vertical scrollbar.</p>
+<p>Lots of paragraphs to make a vertical scrollbar.</p>
+<p>Lots of paragraphs to make a vertical scrollbar.</p>
+<p>Lots of paragraphs to make a vertical scrollbar.</p>
+<p>Lots of paragraphs to make a vertical scrollbar.</p>
+<p>Lots of paragraphs to make a vertical scrollbar.</p>
+<p>Lots of paragraphs to make a vertical scrollbar.</p>
+<p>Lots of paragraphs to make a vertical scrollbar.</p>
+<p>Lots of paragraphs to make a vertical scrollbar.</p>
+<p>Lots of paragraphs to make a vertical scrollbar.</p>
+<p>Lots of paragraphs to make a vertical scrollbar.</p>
+<p>Lots of paragraphs to make a vertical scrollbar.</p>
+<p>Lots of paragraphs to make a vertical scrollbar.</p>
+<p>Lots of paragraphs to make a vertical scrollbar.</p>
+<p>Lots of paragraphs to make a vertical scrollbar.</p>
+<p>Lots of paragraphs to make a vertical scrollbar.</p>
+<p>Lots of paragraphs to make a vertical scrollbar.</p>
+<p>Lots of paragraphs to make a vertical scrollbar.</p>
+<p>Lots of paragraphs to make a vertical scrollbar.</p>
+<p>Lots of paragraphs to make a vertical scrollbar.</p>
+<p>Lots of paragraphs to make a vertical scrollbar.</p>
+<p>Lots of paragraphs to make a vertical scrollbar.</p>
+<p>Lots of paragraphs to make a vertical scrollbar.</p>
+<p>Lots of paragraphs to make a vertical scrollbar.</p>
+<p>Lots of paragraphs to make a vertical scrollbar.</p>
+<p>Lots of paragraphs to make a vertical scrollbar.</p>
+<p>Lots of paragraphs to make a vertical scrollbar.</p>
+<p>Lots of paragraphs to make a vertical scrollbar.</p>
+<p>Lots of paragraphs to make a vertical scrollbar.</p>
+<p>Lots of paragraphs to make a vertical scrollbar.</p>
+<p>Lots of paragraphs to make a vertical scrollbar.</p>
+<p>Lots of paragraphs to make a vertical scrollbar.</p>
+<p>Lots of paragraphs to make a vertical scrollbar.</p>
+<p>Lots of paragraphs to make a vertical scrollbar.</p>
+<p>Lots of paragraphs to make a vertical scrollbar.</p>
+<p>Lots of paragraphs to make a vertical scrollbar.</p>
+<p>Lots of paragraphs to make a vertical scrollbar.</p>
+<p>Lots of paragraphs to make a vertical scrollbar.</p>
+<p>Lots of paragraphs to make a vertical scrollbar.</p>
+<p>Lots of paragraphs to make a vertical scrollbar.</p>
+<p>Lots of paragraphs to make a vertical scrollbar.</p>
+<p>Lots of paragraphs to make a vertical scrollbar.</p>
+<p>Lots of paragraphs to make a vertical scrollbar.</p>
+<p>Lots of paragraphs to make a vertical scrollbar.</p>
+<p>Lots of paragraphs to make a vertical scrollbar.</p>
+<p>Lots of paragraphs to make a vertical scrollbar.</p>
+<p>Lots of paragraphs to make a vertical scrollbar.</p>
+<p>Lots of paragraphs to make a vertical scrollbar.</p>
+<p>Lots of paragraphs to make a vertical scrollbar.</p>
+<p>Lots of paragraphs to make a vertical scrollbar.</p>
+<p>Lots of paragraphs to make a vertical scrollbar.</p>
+<p>Lots of paragraphs to make a vertical scrollbar.</p>
+<p>Lots of paragraphs to make a vertical scrollbar.</p>
+<p>Lots of paragraphs to make a vertical scrollbar.</p>
+<p>Lots of paragraphs to make a vertical scrollbar.</p>
+<p>Lots of paragraphs to make a vertical scrollbar.</p>
+<p>Lots of paragraphs to make a vertical scrollbar.</p>
+<p>Lots of paragraphs to make a vertical scrollbar.</p>
+<p>Lots of paragraphs to make a vertical scrollbar.</p>
+<p>Lots of paragraphs to make a vertical scrollbar.</p>
+<p>Lots of paragraphs to make a vertical scrollbar.</p>
+<p>Lots of paragraphs to make a vertical scrollbar.</p>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/dom/tests/mochitest/keyhandling/chrome.ini
@@ -0,0 +1,12 @@
+[DEFAULT]
+# nsIWidget::SynthesizeNativeKeyEvent() required (Bug 1410525 for headless)
+skip-if = os == 'linux' || os == 'android' || headless
+
+[test_browser.xul]
+support-files =
+  browsertest.html
+[test_editor.xul]
+[test_windowed.xul]
+support-files =
+  test_input.html
+  test_textarea.html
new file mode 100644
--- /dev/null
+++ b/dom/tests/mochitest/keyhandling/mochitest.ini
@@ -0,0 +1,6 @@
+[DEFAULT]
+# nsIWidget::SynthesizeNativeKeyEvent() required (Bug 1410525 for headless)
+skip-if = os == 'linux' || os == 'android' || headless
+
+[test_input.html]
+[test_textarea.html]
new file mode 100644
--- /dev/null
+++ b/dom/tests/mochitest/keyhandling/test_browser.xul
@@ -0,0 +1,255 @@
+<?xml version="1.0"?>
+
+<window title="Browser element keyhandling tests"
+        xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+        onload="test();">
+
+  <script type="application/javascript"
+          src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"/>
+  <script type="application/javascript"
+          src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"/>
+  <script type="application/javascript"
+          src="chrome://mochikit/content/tests/SimpleTest/NativeKeyCodes.js"/>
+
+  <script type="application/javascript">
+  <![CDATA[
+    SimpleTest.waitForExplicitFinish();
+
+    const IS_MAC = navigator.platform.indexOf("Mac") === 0;
+    const VK = {};
+    const CHARS = {};
+
+    // Copied values from NativeKeyCodes.js and EventUtils.js
+    if (IS_MAC) {
+      VK.LEFT = MAC_VK_LeftArrow;
+      CHARS.LEFT = "\uF702";
+      VK.RIGHT = MAC_VK_RightArrow;
+      CHARS.RIGHT = "\uF703";
+      VK.UP = MAC_VK_UpArrow;
+      CHARS.UP = "\uF700";
+      VK.DOWN = MAC_VK_DownArrow;
+      CHARS.DOWN = "\uF701";
+      VK.SPACE = MAC_VK_Space;
+      VK.PGDOWN = MAC_VK_PageDown;
+      CHARS.PGDOWN = "\uF72D";
+      VK.PGUP = MAC_VK_PageUp;
+      CHARS.PGUP = "\uF72C";
+      VK.C = MAC_VK_ANSI_C;
+      VK.HOME = MAC_VK_Home;
+      CHARS.HOME = "\uF729";
+      VK.END = MAC_VK_End;
+      CHARS.END = "\uF72B";
+    } else {
+      VK.LEFT = WIN_VK_LEFT;
+      CHARS.LEFT = "";
+      VK.RIGHT = WIN_VK_RIGHT;
+      CHARS.RIGHT = "";
+      VK.UP = WIN_VK_UP;
+      CHARS.UP = "";
+      VK.DOWN = WIN_VK_DOWN;
+      CHARS.DOWN = "";
+      VK.SPACE = WIN_VK_SPACE;
+      VK.PGDOWN = WIN_VK_NEXT;
+      CHARS.PGDOWN = "";
+      VK.PGUP = WIN_VK_PRIOR;
+      CHARS.PGUP = "";
+      VK.C = WIN_VK_C;
+      VK.HOME = WIN_VK_HOME;
+      CHARS.HOME = "";
+      VK.END = WIN_VK_END;
+      CHARS.END = "";
+    }
+
+    function waitForEvent(target, event) {
+      info(`Waiting for ${event} event.`);
+      return new Promise(resolve => {
+        browser.addEventListener(event, resolve, { once: true });
+      });
+    }
+
+    function synthesizeKey(keyCode, modifiers, chars) {
+      return new Promise((resolve, reject) => {
+        if (!synthesizeNativeKey(KEYBOARD_LAYOUT_EN_US, keyCode, modifiers, chars, chars, resolve)) {
+          reject();
+        }
+      });
+    }
+
+    function getWindowProperties(browser, properties) {
+      let results = {};
+      for (let prop of properties) {
+        results[prop] = browser.contentWindow[prop];
+      }
+
+      return results;
+    }
+
+    function getScrollPosition(browser) {
+      return getWindowProperties(browser, ["scrollX", "scrollY"]);
+    }
+
+    async function test() {
+      // Smooth scrolling makes scroll events take time and it's difficult to know
+      // when they've ended, so turn it off for this test.
+      await SpecialPowers.pushPrefEnv({"set": [["general.smoothScroll", false]] });
+
+      let browser = document.getElementById("browser");
+      browser.focus();
+      let { scrollX, scrollY } = await getScrollPosition(browser);
+      is(scrollX, 0, "Should not be scrolled");
+      is(scrollY, 0, "Should not be scrolled");
+
+      info("down");
+      await synthesizeKey(VK.DOWN, {}, CHARS.DOWN);
+      await waitForEvent(browser.contentWindow, "scroll");
+      let { scrollX: lineScrollX, scrollY: lineScrollY } = await getScrollPosition(browser);
+      is(lineScrollX, 0, "Should not be scrolled");
+      ok(lineScrollY > 0, "Should be scrolled");
+
+      info("up");
+      await synthesizeKey(VK.UP, {}, CHARS.UP);
+      await waitForEvent(browser.contentWindow, "scroll");
+      {
+        let { scrollX, scrollY } = await getScrollPosition(browser);
+        is(scrollX, 0, "Should not be scrolled");
+        is(scrollY, 0, "Should not be scrolled");
+      }
+
+      info("right");
+      await synthesizeKey(VK.RIGHT, {}, CHARS.RIGHT);
+      await waitForEvent(browser.contentWindow, "scroll");
+      let { scrollX: rightScrollX, scrollY: rightScrollY } = await getScrollPosition(browser);
+      ok(rightScrollX > 0, "Should be scrolled");
+      is(rightScrollY, 0, "Should not be scrolled");
+
+      info("left");
+      await synthesizeKey(VK.LEFT, {}, CHARS.LEFT);
+      await waitForEvent(browser.contentWindow, "scroll");
+      {
+        let { scrollX, scrollY } = await getScrollPosition(browser);
+        is(scrollX, 0, "Should not be scrolled");
+        is(scrollY, 0, "Should not be scrolled");
+      }
+
+      info("space");
+      await synthesizeKey(VK.SPACE, {}, " ");
+      await waitForEvent(browser.contentWindow, "scroll");
+      let { scrollX: pageScrollX, scrollY: pageScrollY } = await getScrollPosition(browser);
+      is(pageScrollX, 0, "Should not be scrolled");
+      ok(pageScrollY > lineScrollY, "Should be scrolled more than a single line");
+
+      info("shift+space");
+      await synthesizeKey(VK.SPACE, { shiftKey: true }, " ");
+      await waitForEvent(browser.contentWindow, "scroll");
+      {
+        let { scrollX, scrollY } = await getScrollPosition(browser);
+        is(scrollX, 0, "Should not be scrolled");
+        is(scrollY, 0, "Should not be scrolled");
+      }
+
+      info("page down");
+      await synthesizeKey(VK.PGDOWN, {}, CHARS.PGDOWN);
+      await waitForEvent(browser.contentWindow, "scroll");
+      {
+        let { scrollX, scrollY } = await getScrollPosition(browser);
+        is(scrollX, 0, "Should not be scrolled");
+        is(scrollY, pageScrollY, "Should be scrolled a page");
+      }
+
+      info("page up");
+      await synthesizeKey(VK.PGUP, {}, CHARS.PGUP);
+      await waitForEvent(browser.contentWindow, "scroll");
+      {
+        let { scrollX, scrollY } = await getScrollPosition(browser);
+        is(scrollX, 0, "Should not be scrolled");
+        is(scrollY, 0, "Should not be scrolled");
+      }
+
+      info("accel+down");
+      await synthesizeKey(VK.DOWN, { accelKey: true }, CHARS.DOWN);
+      await waitForEvent(browser.contentWindow, "scroll");
+      {
+        let { scrollX, scrollY, innerHeight } = await getWindowProperties(browser, ["scrollX", "scrollY", "innerHeight"]);
+        is(scrollX, 0, "Should not be scrolled");
+        // We can't know the scrollbar height so check that we're scrolled to within 100px of what we expect.
+        isfuzzy(scrollY, browser.contentDocument.body.clientHeight - innerHeight, 100, "Should be scrolled to the end.");
+      }
+
+      info("accel+up");
+      await synthesizeKey(VK.UP, { accelKey: true }, CHARS.UP);
+      await waitForEvent(browser.contentWindow, "scroll");
+      {
+        let { scrollX, scrollY } = await getScrollPosition(browser);
+        is(scrollX, 0, "Should not be scrolled");
+        is(scrollY, 0, "Should not be scrolled");
+      }
+
+      info("end");
+      await synthesizeKey(VK.END, {}, CHARS.END);
+      await waitForEvent(browser.contentWindow, "scroll");
+      {
+        let { scrollX, scrollY, innerHeight } = await getWindowProperties(browser, ["scrollX", "scrollY", "innerHeight"]);
+        is(scrollX, 0, "Should not be scrolled");
+        // We can't know the scrollbar height so check that we're scrolled to within 100px of what we expect.
+        isfuzzy(scrollY, browser.contentDocument.body.clientHeight - innerHeight, 100, "Should be scrolled to the end.");
+      }
+
+      info("home");
+      await synthesizeKey(VK.HOME, {}, CHARS.HOME);
+      await waitForEvent(browser.contentWindow, "scroll");
+      {
+        let { scrollX, scrollY } = await getScrollPosition(browser);
+        is(scrollX, 0, "Should not be scrolled");
+        is(scrollY, 0, "Should not be scrolled");
+      }
+
+      // Select the start of the first paragraph
+      let paragraph = browser.contentDocument.getElementById("paragraph");
+      let selection = browser.contentWindow.getSelection();
+      selection.setBaseAndExtent(paragraph.firstChild, 0, paragraph.firstChild, "Lots of".length);
+
+      info("copy");
+      await SimpleTest.promiseClipboardChange("Lots of", () => {
+        synthesizeKey(VK.C, { accelKey: true }, "c");
+      });
+
+      for (let i = 0; i < " paragraphs".length; i++) {
+        info("select right");
+        await synthesizeKey(VK.RIGHT, { shiftKey: true }, CHARS.RIGHT);
+      }
+
+      info("copy");
+      await SimpleTest.promiseClipboardChange("Lots of paragraphs", () => {
+        synthesizeKey(VK.C, { accelKey: true }, "c");
+      });
+
+      for (let i = 0; i < " paragraphs".length; i++) {
+        info("select left");
+        await synthesizeKey(VK.LEFT, { shiftKey: true }, CHARS.LEFT);
+      }
+
+      info("copy");
+      await SimpleTest.promiseClipboardChange("Lots of", () => {
+        synthesizeKey(VK.C, { accelKey: true }, "c");
+      });
+
+      info("select down");
+      await synthesizeKey(VK.DOWN, { shiftKey: true }, CHARS.DOWN);
+
+      info("copy");
+      await SimpleTest.promiseClipboardChange("Lots of paragraphs to make a vertical scrollbar.\n\nLots of", () => {
+        synthesizeKey(VK.C, { accelKey: true }, "c");
+      });
+
+      SimpleTest.finish();
+    }
+  ]]>
+  </script>
+
+  <body xmlns="http://www.w3.org/1999/xhtml">
+    <p id="display"></p>
+    <div id="content" style="display:none;"></div>
+    <pre id="test"></pre>
+  </body>
+  <browser id="browser" src="browsertest.html" style="height: 500px"/>
+</window>
new file mode 100644
--- /dev/null
+++ b/dom/tests/mochitest/keyhandling/test_editor.xul
@@ -0,0 +1,271 @@
+<?xml version="1.0"?>
+
+<window title="Browser element keyhandling tests"
+        xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+        onload="test();">
+
+  <script type="application/javascript"
+          src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"/>
+  <script type="application/javascript"
+          src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"/>
+  <script type="application/javascript"
+          src="chrome://mochikit/content/tests/SimpleTest/NativeKeyCodes.js"/>
+
+  <script type="application/javascript">
+  <![CDATA[
+    SimpleTest.waitForExplicitFinish();
+
+    const IS_MAC = navigator.platform.indexOf("Mac") === 0;
+    const VK = {};
+    const CHARS = {};
+
+    // Copied values from NativeKeyCodes.js and EventUtils.js
+    if (IS_MAC) {
+      VK.LEFT = MAC_VK_LeftArrow;
+      CHARS.LEFT = "\uF702";
+      VK.RIGHT = MAC_VK_RightArrow;
+      CHARS.RIGHT = "\uF703";
+      VK.UP = MAC_VK_UpArrow;
+      CHARS.UP = "\uF700";
+      VK.DOWN = MAC_VK_DownArrow;
+      CHARS.DOWN = "\uF701";
+      VK.SPACE = MAC_VK_Space;
+      VK.X = MAC_VK_ANSI_X;
+      VK.V = MAC_VK_ANSI_V;
+      VK.A = MAC_VK_ANSI_A;
+      VK.Z = MAC_VK_ANSI_Z;
+      VK.F = MAC_VK_ANSI_F;
+      VK.O = MAC_VK_ANSI_O;
+      VK.BACKSPACE = MAC_VK_PC_Backspace;
+      CHARS.BACKSPACE = "\u007F";
+    } else {
+      VK.LEFT = WIN_VK_LEFT;
+      CHARS.LEFT = "";
+      VK.RIGHT = WIN_VK_RIGHT;
+      CHARS.RIGHT = "";
+      VK.HOME = WIN_VK_HOME;
+      CHARS.HOME = "";
+      VK.END = WIN_VK_END;
+      CHARS.END = "";
+      VK.SPACE = WIN_VK_SPACE;
+      VK.X = WIN_VK_X;
+      VK.V = WIN_VK_V;
+      VK.A = WIN_VK_A;
+      VK.Z = WIN_VK_Z;
+      VK.Y = WIN_VK_Y;
+      VK.F = WIN_VK_F;
+      VK.O = WIN_VK_O;
+      VK.BACKSPACE = WIN_VK_BACK;
+      CHARS.BACKSPACE = "";
+    }
+
+    function waitForEvent(target, event) {
+      info(`Waiting for ${event} event.`);
+      return new Promise(resolve => {
+        browser.addEventListener(event, resolve, { once: true });
+      });
+    }
+
+    function synthesizeKey(keyCode, modifiers, chars) {
+      return new Promise((resolve, reject) => {
+        if (!synthesizeNativeKey(KEYBOARD_LAYOUT_EN_US, keyCode, modifiers, chars, chars, resolve)) {
+          reject();
+        }
+      });
+    }
+
+    function* nodes(element) {
+      let node = element.firstChild;
+      while (node) {
+        yield node;
+
+        if (node.nodeType === Node.ELEMENT_NODE) {
+          yield* nodes(node);
+        }
+
+        node = node.nextSibling;
+      }
+    }
+
+    async function checkElement(element, start, selectedText, content = "Test text") {
+      selectionPosition = (element, range) => {
+        let pos = 0;
+        for (let node of nodes(element)) {
+          if (node.nodeType === Node.TEXT_NODE) {
+            if (node === range.startContainer) {
+              return pos + range.startOffset;
+            } else {
+              pos += node.nodeValue.length;
+            }
+          } else if (node === range.startContainer) {
+            for (let i = 0; i < range.startOffset; i++) {
+              pos += node.childNodes[i].textContent.length;
+            }
+
+            return pos;
+          }
+        }
+
+        throw new Error("startContainer of range never found.");
+      }
+
+      isReady = () => {
+        let selection = element.contentWindow.getSelection();
+        let range = selection.getRangeAt(0);
+        let pos = selectionPosition(element.contentDocument.documentElement, range);
+
+        if (start != pos) {
+          return false;
+        }
+        if (selectedText != selection.toString()) {
+          return false;
+        }
+        if (content != element.contentDocument.documentElement.textContent) {
+          return false;
+        }
+        return true;
+      };
+
+      for (let i = 0; i < 10; i++) {
+        if (isReady()) {
+          return;
+        }
+
+        SimpleTest.requestFlakyTimeout("Polling for changes to apply");
+        await new Promise(resolve => setTimeout(resolve, 50));
+      }
+      ok(false, `Timed out waiting for state ${start} "${selectedText}" "${content}"`);
+      let selection = element.contentWindow.getSelection();
+      let range = selection.getRangeAt(0);
+      info(`${selectionPosition(element.contentDocument.documentElement, range)} "${selection.toString()}" "${element.contentDocument.documentElement.textContent}"`);
+    }
+
+    async function test() {
+      let editor = document.getElementById("editor");
+      editor.contentDocument.designMode = "on";
+      editor.contentWindow.focus();
+      let edit = editor.getEditor(editor.contentWindow);
+      edit.beginningOfDocument();
+
+      await checkElement(editor, 0, "");
+
+      info("right");
+      await synthesizeKey(VK.RIGHT, {}, CHARS.RIGHT);
+      await checkElement(editor, 1, "");
+
+      info("shift+right");
+      await synthesizeKey(VK.RIGHT, { shiftKey: true }, CHARS.RIGHT);
+      await checkElement(editor, 1, "e");
+
+      info("shift+right");
+      await synthesizeKey(VK.RIGHT, { shiftKey: true }, CHARS.RIGHT);
+      await checkElement(editor, 1, "es");
+
+      info("shift+left");
+      await synthesizeKey(VK.LEFT, { shiftKey: true }, CHARS.LEFT);
+      await checkElement(editor, 1, "e");
+
+      info("shift+left");
+      await synthesizeKey(VK.LEFT, { shiftKey: true }, CHARS.LEFT);
+      await checkElement(editor, 1, "");
+
+      info("shift+left");
+      await synthesizeKey(VK.LEFT, { shiftKey: true }, CHARS.LEFT);
+      await checkElement(editor, 0, "T");
+
+      info("shift+right");
+      await synthesizeKey(VK.RIGHT, { shiftKey: true }, CHARS.RIGHT);
+      await checkElement(editor, 1, "");
+
+      info("left");
+      await synthesizeKey(VK.LEFT, {}, CHARS.LEFT);
+      await checkElement(editor, 0, "");
+
+      if (IS_MAC) {
+        info("down");
+        await synthesizeKey(VK.DOWN, { shiftKey: true }, CHARS.DOWN);
+      } else {
+        info("end");
+        await synthesizeKey(VK.END, { shiftKey: true }, CHARS.END);
+      }
+      await checkElement(editor, 0, "Test text");
+
+      info("cut");
+      await synthesizeKey(VK.X, { accelKey: true }, "x");
+      await checkElement(editor, 0, "", "");
+      let text = SpecialPowers.getClipboardData("text/unicode");
+      is(text, "Test text", "Should have cut to the clipboard");
+      SpecialPowers.clipboardCopyString("New text");
+
+      info("paste");
+      await synthesizeKey(VK.V, { accelKey: true }, "v");
+      await checkElement(editor, 8, "", "New text");
+
+      if (IS_MAC) {
+        info("up");
+        await synthesizeKey(VK.UP, {}, CHARS.UP);
+      } else {
+        info("home");
+        await synthesizeKey(VK.HOME, {}, CHARS.HOME);
+      }
+      await checkElement(editor, 0, "", "New text");
+
+      info("select all");
+      await synthesizeKey(VK.A, { accelKey: true}, "a", "select");
+      await checkElement(editor, 0, "New text", "New text");
+
+      info("right");
+      await synthesizeKey(VK.RIGHT, {}, CHARS.RIGHT);
+      await checkElement(editor, 8, "", "New text");
+
+      info("word left");
+      if (IS_MAC) {
+        await synthesizeKey(VK.LEFT, { altKey: true }, CHARS.LEFT);
+      } else {
+        await synthesizeKey(VK.LEFT, { ctrlKey: true }, CHARS.LEFT);
+      }
+      await checkElement(editor, 4, "", "New text");
+
+      info("delete word left");
+      if (IS_MAC) {
+        await synthesizeKey(VK.BACKSPACE, { altKey: true }, CHARS.BACKSPACE);
+      } else {
+        await synthesizeKey(VK.BACKSPACE, { ctrlKey: true }, CHARS.BACKSPACE);
+      }
+      await checkElement(editor, 0, "", "text");
+
+      info("undo");
+      await synthesizeKey(VK.Z, { accelKey: true }, "z");
+      await checkElement(editor, 4, "", "New text");
+
+      info("redo");
+      if (IS_MAC) {
+        await synthesizeKey(VK.Z, { accelKey: true, shiftKey: true }, "z");
+      } else {
+        await synthesizeKey(VK.Y, { accelKey: true }, "y");
+      }
+      await checkElement(editor, 0, "", "text");
+
+      info("typing");
+      await synthesizeKey(VK.RIGHT, {}, CHARS.RIGHT);
+      await synthesizeKey(VK.RIGHT, {}, CHARS.RIGHT);
+      await synthesizeKey(VK.RIGHT, {}, CHARS.RIGHT);
+      await synthesizeKey(VK.RIGHT, {}, CHARS.RIGHT);
+      await synthesizeKey(VK.SPACE, {}, " ");
+      await synthesizeKey(VK.F, {}, "f");
+      await synthesizeKey(VK.O, {}, "o");
+      await synthesizeKey(VK.O, {}, "o");
+      await checkElement(editor, 8, "", "text foo");
+
+      SimpleTest.finish();
+    }
+  ]]>
+  </script>
+
+  <body xmlns="http://www.w3.org/1999/xhtml">
+    <p id="display"></p>
+    <div id="content" style="display:none;"></div>
+    <pre id="test"></pre>
+  </body>
+  <editor id="editor" editortype="text" src="data:text/plain,Test text" style="height: 500px"/>
+</window>
new file mode 100644
--- /dev/null
+++ b/dom/tests/mochitest/keyhandling/test_input.html
@@ -0,0 +1,226 @@
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+<title>input key handling</title>
+
+<script type="text/javascript" src="http://mochi.test:8888/tests/SimpleTest/SimpleTest.js"></script>
+<script type="text/javascript" src="http://mochi.test:8888/tests/SimpleTest/EventUtils.js"></script>
+<script type="text/javascript" src="http://mochi.test:8888/tests/SimpleTest/NativeKeyCodes.js"></script>
+<link rel="stylesheet" type="text/css" href="http://mochi.test:8888/tests/SimpleTest/test.css" />
+
+<script type="text/javascript">
+const IS_MAC = navigator.platform.indexOf("Mac") == 0;
+const VK = {};
+const CHARS = {};
+
+if (IS_MAC) {
+  VK.LEFT = MAC_VK_LeftArrow;
+  CHARS.LEFT = "\uF702";
+  VK.RIGHT = MAC_VK_RightArrow;
+  CHARS.RIGHT = "\uF703";
+  VK.UP = MAC_VK_UpArrow;
+  CHARS.UP = "\uF700";
+  VK.DOWN = MAC_VK_DownArrow;
+  CHARS.DOWN = "\uF701";
+  VK.X = MAC_VK_ANSI_X;
+  VK.V = MAC_VK_ANSI_V;
+  VK.A = MAC_VK_ANSI_A;
+  VK.F = MAC_VK_ANSI_F;
+  VK.O = MAC_VK_ANSI_O;
+  VK.BACKSPACE = MAC_VK_PC_Backspace;
+  CHARS.BACKSPACE = "\u007F";
+  VK.Z = MAC_VK_ANSI_Z;
+  VK.SPACE = MAC_VK_Space;
+} else {
+  VK.LEFT = WIN_VK_LEFT;
+  CHARS.LEFT = "";
+  VK.RIGHT = WIN_VK_RIGHT;
+  CHARS.RIGHT = "";
+  VK.UP = WIN_VK_UP;
+  CHARS.UP = "";
+  VK.DOWN = WIN_VK_DOWN;
+  CHARS.DOWN = "";
+  VK.X = WIN_VK_X;
+  VK.V = WIN_VK_V;
+  VK.A = WIN_VK_A;
+  VK.F = WIN_VK_F;
+  VK.O = WIN_VK_O;
+  VK.END = WIN_VK_END;
+  CHARS.END = "";
+  VK.HOME = WIN_VK_HOME;
+  CHARS.HOME = "";
+  VK.BACKSPACE = WIN_VK_BACK;
+  CHARS.BACKSPACE = "";
+  VK.Z = WIN_VK_Z;
+  VK.SPACE = WIN_VK_SPACE;
+}
+
+if (window.opener) {
+  ok = window.opener.ok;
+  is = window.opener.is;
+}
+
+function synthesizeKey(keyCode, modifiers, chars, event = "keyup") {
+  return new Promise((resolve, reject) => {
+    window.addEventListener(event, resolve, { once: true });
+
+    if (!synthesizeNativeKey(KEYBOARD_LAYOUT_EN_US, keyCode, modifiers, chars, chars)) {
+      reject();
+    }
+  });
+}
+
+async function checkElement(element, start, end, content = "Test text") {
+  isReady = () => {
+    if (start != element.selectionStart) {
+      return false;
+    }
+    if (end != element.selectionEnd) {
+      return false;
+    }
+    if (content != element.value) {
+      return false;
+    }
+    return true;
+  };
+
+  for (let i = 0; i < 10; i++) {
+    if (isReady()) {
+      return;
+    }
+
+    SimpleTest.requestFlakyTimeout("Polling for changes to apply");
+    await new Promise(resolve => setTimeout(resolve, 50));
+  }
+  ok(false, "Timed out waiting for state");
+  is(element.selectionStart, start, "Should have the right selectionStart");
+  is(element.selectionEnd, end, "Should have the right selectionEnd");
+  is(element.value, content, "Should have the right value");
+}
+
+async function startTest() {
+  let input = document.getElementById("input");
+  input.focus();
+  await checkElement(input, 0, 0);
+
+  info("right");
+  await synthesizeKey(VK.RIGHT, {}, CHARS.RIGHT);
+  await checkElement(input, 1, 1);
+
+  info("shift+right");
+  await synthesizeKey(VK.RIGHT, { shiftKey: true }, CHARS.RIGHT);
+  await checkElement(input, 1, 2);
+
+  info("shift+right");
+  await synthesizeKey(VK.RIGHT, { shiftKey: true }, CHARS.RIGHT);
+  await checkElement(input, 1, 3);
+
+  info("shift+left");
+  await synthesizeKey(VK.LEFT, { shiftKey: true }, CHARS.LEFT);
+  await checkElement(input, 1, 2);
+
+  info("shift+left");
+  await synthesizeKey(VK.LEFT, { shiftKey: true }, CHARS.LEFT);
+  await checkElement(input, 1, 1);
+
+  info("shift+left");
+  await synthesizeKey(VK.LEFT, { shiftKey: true }, CHARS.LEFT);
+  await checkElement(input, 0, 1);
+
+  info("shift+right");
+  await synthesizeKey(VK.RIGHT, { shiftKey: true }, CHARS.RIGHT);
+  await checkElement(input, 1, 1);
+
+  info("left");
+  await synthesizeKey(VK.LEFT, {}, CHARS.LEFT);
+  await checkElement(input, 0, 0);
+
+  if (IS_MAC) {
+    info("down");
+    await synthesizeKey(VK.DOWN, { shiftKey: true }, CHARS.DOWN);
+  } else {
+    info("end");
+    await synthesizeKey(VK.END, { shiftKey: true }, CHARS.END);
+  }
+  await checkElement(input, 0, 9);
+
+  info("cut");
+  await synthesizeKey(VK.X, { accelKey: true }, "x", "input");
+  await checkElement(input, 0, 0, "");
+  let text = SpecialPowers.getClipboardData("text/unicode");
+  is(text, "Test text", "Should have cut to the clipboard");
+  SpecialPowers.clipboardCopyString("New text");
+
+  info("paste");
+  await synthesizeKey(VK.V, { accelKey: true }, "v", "input");
+  await checkElement(input, 8, 8, "New text");
+
+  if (IS_MAC) {
+    info("up");
+    await synthesizeKey(VK.UP, {}, CHARS.UP);
+  } else {
+    info("home");
+    await synthesizeKey(VK.HOME, {}, CHARS.HOME);
+  }
+  await checkElement(input, 0, 0, "New text");
+
+  info("select all");
+  await synthesizeKey(VK.A, { accelKey: true}, "a", "select");
+  await checkElement(input, 0, 8, "New text");
+
+  info("right");
+  await synthesizeKey(VK.RIGHT, {}, CHARS.RIGHT);
+  await checkElement(input, 8, 8, "New text");
+
+  info("word left");
+  if (IS_MAC) {
+    await synthesizeKey(VK.LEFT, { altKey: true }, CHARS.LEFT);
+  } else {
+    await synthesizeKey(VK.LEFT, { ctrlKey: true }, CHARS.LEFT);
+  }
+  await checkElement(input, 4, 4, "New text");
+
+  info("delete word left");
+  if (IS_MAC) {
+    await synthesizeKey(VK.BACKSPACE, { altKey: true }, CHARS.BACKSPACE);
+  } else {
+    await synthesizeKey(VK.BACKSPACE, { ctrlKey: true }, CHARS.BACKSPACE);
+  }
+  await checkElement(input, 0, 0, "text");
+
+  info("undo");
+  await synthesizeKey(VK.Z, { accelKey: true }, "", "input");
+  await checkElement(input, 4, 4, "New text");
+
+  info("redo");
+  await synthesizeKey(VK.Z, { accelKey: true, shiftKey: true }, "", "input");
+  await checkElement(input, 0, 0, "text");
+
+  info("typing");
+  await synthesizeKey(VK.RIGHT, {}, CHARS.RIGHT);
+  await synthesizeKey(VK.RIGHT, {}, CHARS.RIGHT);
+  await synthesizeKey(VK.RIGHT, {}, CHARS.RIGHT);
+  await synthesizeKey(VK.RIGHT, {}, CHARS.RIGHT);
+  await synthesizeKey(VK.SPACE, {}, " ");
+  await synthesizeKey(VK.F, {}, "f");
+  await synthesizeKey(VK.O, {}, "o");
+  await synthesizeKey(VK.O, {}, "o");
+  await checkElement(input, 8, 8, "text foo");
+}
+
+async function runTest() {
+  // When running in windowed mode the caller will start the test once we have
+  // focus.
+  if (window.opener) {
+    return;
+  }
+
+  SimpleTest.waitForExplicitFinish();
+  await startTest();
+  SimpleTest.finish();
+}
+</script>
+</head>
+<body onload="runTest();">
+<input id=input value="Test text"/>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/dom/tests/mochitest/keyhandling/test_textarea.html
@@ -0,0 +1,226 @@
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+<title>input key handling</title>
+
+<script type="text/javascript" src="http://mochi.test:8888/tests/SimpleTest/SimpleTest.js"></script>
+<script type="text/javascript" src="http://mochi.test:8888/tests/SimpleTest/EventUtils.js"></script>
+<script type="text/javascript" src="http://mochi.test:8888/tests/SimpleTest/NativeKeyCodes.js"></script>
+<link rel="stylesheet" type="text/css" href="http://mochi.test:8888/tests/SimpleTest/test.css" />
+
+<script type="text/javascript">
+const IS_MAC = navigator.platform.indexOf("Mac") == 0;
+const VK = {};
+const CHARS = {};
+
+if (IS_MAC) {
+  VK.LEFT = MAC_VK_LeftArrow;
+  CHARS.LEFT = "\uF702";
+  VK.RIGHT = MAC_VK_RightArrow;
+  CHARS.RIGHT = "\uF703";
+  VK.UP = MAC_VK_UpArrow;
+  CHARS.UP = "\uF700";
+  VK.DOWN = MAC_VK_DownArrow;
+  CHARS.DOWN = "\uF701";
+  VK.X = MAC_VK_ANSI_X;
+  VK.V = MAC_VK_ANSI_V;
+  VK.A = MAC_VK_ANSI_A;
+  VK.F = MAC_VK_ANSI_F;
+  VK.O = MAC_VK_ANSI_O;
+  VK.BACKSPACE = MAC_VK_PC_Backspace;
+  CHARS.BACKSPACE = "\u007F";
+  VK.Z = MAC_VK_ANSI_Z;
+  VK.SPACE = MAC_VK_Space;
+} else {
+  VK.LEFT = WIN_VK_LEFT;
+  CHARS.LEFT = "";
+  VK.RIGHT = WIN_VK_RIGHT;
+  CHARS.RIGHT = "";
+  VK.UP = WIN_VK_UP;
+  CHARS.UP = "";
+  VK.DOWN = WIN_VK_DOWN;
+  CHARS.DOWN = "";
+  VK.X = WIN_VK_X;
+  VK.V = WIN_VK_V;
+  VK.A = WIN_VK_A;
+  VK.F = WIN_VK_F;
+  VK.O = WIN_VK_O;
+  VK.END = WIN_VK_END;
+  CHARS.END = "";
+  VK.HOME = WIN_VK_HOME;
+  CHARS.HOME = "";
+  VK.BACKSPACE = WIN_VK_BACK;
+  CHARS.BACKSPACE = "";
+  VK.Z = WIN_VK_Z;
+  VK.SPACE = WIN_VK_SPACE;
+}
+
+if (window.opener) {
+  ok = window.opener.ok;
+  is = window.opener.is;
+}
+
+function synthesizeKey(keyCode, modifiers, chars, event = "keyup") {
+  return new Promise((resolve, reject) => {
+    window.addEventListener(event, resolve, { once: true });
+
+    if (!synthesizeNativeKey(KEYBOARD_LAYOUT_EN_US, keyCode, modifiers, chars, chars)) {
+      reject();
+    }
+  });
+}
+
+async function checkElement(element, start, end, content = "Test text") {
+  isReady = () => {
+    if (start != element.selectionStart) {
+      return false;
+    }
+    if (end != element.selectionEnd) {
+      return false;
+    }
+    if (content != element.value) {
+      return false;
+    }
+    return true;
+  };
+
+  for (let i = 0; i < 10; i++) {
+    if (isReady()) {
+      return;
+    }
+
+    SimpleTest.requestFlakyTimeout("Polling for changes to apply");
+    await new Promise(resolve => setTimeout(resolve, 50));
+  }
+  ok(false, "Timed out waiting for state");
+  is(element.selectionStart, start, "Should have the right selectionStart");
+  is(element.selectionEnd, end, "Should have the right selectionEnd");
+  is(element.value, content, "Should have the right value");
+}
+
+async function startTest() {
+  let input = document.getElementById("input");
+  input.focus();
+  await checkElement(input, 0, 0);
+
+  info("right");
+  await synthesizeKey(VK.RIGHT, {}, CHARS.RIGHT);
+  await checkElement(input, 1, 1);
+
+  info("shift+right");
+  await synthesizeKey(VK.RIGHT, { shiftKey: true }, CHARS.RIGHT);
+  await checkElement(input, 1, 2);
+
+  info("shift+right");
+  await synthesizeKey(VK.RIGHT, { shiftKey: true }, CHARS.RIGHT);
+  await checkElement(input, 1, 3);
+
+  info("shift+left");
+  await synthesizeKey(VK.LEFT, { shiftKey: true }, CHARS.LEFT);
+  await checkElement(input, 1, 2);
+
+  info("shift+left");
+  await synthesizeKey(VK.LEFT, { shiftKey: true }, CHARS.LEFT);
+  await checkElement(input, 1, 1);
+
+  info("shift+left");
+  await synthesizeKey(VK.LEFT, { shiftKey: true }, CHARS.LEFT);
+  await checkElement(input, 0, 1);
+
+  info("shift+right");
+  await synthesizeKey(VK.RIGHT, { shiftKey: true }, CHARS.RIGHT);
+  await checkElement(input, 1, 1);
+
+  info("left");
+  await synthesizeKey(VK.LEFT, {}, CHARS.LEFT);
+  await checkElement(input, 0, 0);
+
+  if (IS_MAC) {
+    info("down");
+    await synthesizeKey(VK.DOWN, { shiftKey: true }, CHARS.DOWN);
+  } else {
+    info("end");
+    await synthesizeKey(VK.END, { shiftKey: true }, CHARS.END);
+  }
+  await checkElement(input, 0, 9);
+
+  info("cut");
+  await synthesizeKey(VK.X, { accelKey: true }, "x", "input");
+  await checkElement(input, 0, 0, "");
+  let text = SpecialPowers.getClipboardData("text/unicode");
+  is(text, "Test text", "Should have cut to the clipboard");
+  SpecialPowers.clipboardCopyString("New text");
+
+  info("paste");
+  await synthesizeKey(VK.V, { accelKey: true }, "v", "input");
+  await checkElement(input, 8, 8, "New text");
+
+  if (IS_MAC) {
+    info("up");
+    await synthesizeKey(VK.UP, {}, CHARS.UP);
+  } else {
+    info("home");
+    await synthesizeKey(VK.HOME, {}, CHARS.HOME);
+  }
+  await checkElement(input, 0, 0, "New text");
+
+  info("select all");
+  await synthesizeKey(VK.A, { accelKey: true}, "a", "select");
+  await checkElement(input, 0, 8, "New text");
+
+  info("right");
+  await synthesizeKey(VK.RIGHT, {}, CHARS.RIGHT);
+  await checkElement(input, 8, 8, "New text");
+
+  info("word left");
+  if (IS_MAC) {
+    await synthesizeKey(VK.LEFT, { altKey: true }, CHARS.LEFT);
+  } else {
+    await synthesizeKey(VK.LEFT, { ctrlKey: true }, CHARS.LEFT);
+  }
+  await checkElement(input, 4, 4, "New text");
+
+  info("delete word left");
+  if (IS_MAC) {
+    await synthesizeKey(VK.BACKSPACE, { altKey: true }, CHARS.BACKSPACE);
+  } else {
+    await synthesizeKey(VK.BACKSPACE, { ctrlKey: true }, CHARS.BACKSPACE);
+  }
+  await checkElement(input, 0, 0, "text");
+
+  info("undo");
+  await synthesizeKey(VK.Z, { accelKey: true }, "", "input");
+  await checkElement(input, 4, 4, "New text");
+
+  info("redo");
+  await synthesizeKey(VK.Z, { accelKey: true, shiftKey: true }, "", "input");
+  await checkElement(input, 0, 0, "text");
+
+  info("typing");
+  await synthesizeKey(VK.RIGHT, {}, CHARS.RIGHT);
+  await synthesizeKey(VK.RIGHT, {}, CHARS.RIGHT);
+  await synthesizeKey(VK.RIGHT, {}, CHARS.RIGHT);
+  await synthesizeKey(VK.RIGHT, {}, CHARS.RIGHT);
+  await synthesizeKey(VK.SPACE, {}, " ");
+  await synthesizeKey(VK.F, {}, "f");
+  await synthesizeKey(VK.O, {}, "o");
+  await synthesizeKey(VK.O, {}, "o");
+  await checkElement(input, 8, 8, "text foo");
+}
+
+async function runTest() {
+  // When running in windowed mode the caller will start the test once we have
+  // focus.
+  if (window.opener) {
+    return;
+  }
+
+  SimpleTest.waitForExplicitFinish();
+  await startTest();
+  SimpleTest.finish();
+}
+</script>
+</head>
+<body onload="runTest();">
+<textarea id=input>Test text</textarea>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/dom/tests/mochitest/keyhandling/test_windowed.xul
@@ -0,0 +1,33 @@
+<?xml version="1.0"?>
+
+<window title="Top-level window keyhandling tests"
+        xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+        onload="test();">
+
+  <script type="application/javascript"
+          src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"/>
+
+  <script type="application/javascript">
+  <![CDATA[
+    SimpleTest.waitForExplicitFinish();
+
+    async function run_test(path) {
+      let win = window.openDialog(path, "_blank", "width=500,height=500");
+      await SimpleTest.promiseFocus(win);
+      await win.startTest();
+    }
+
+    async function test() {
+      await run_test("test_input.html");
+      await run_test("test_textarea.html");
+      SimpleTest.finish();
+    }
+  ]]>
+  </script>
+
+  <body xmlns="http://www.w3.org/1999/xhtml">
+    <p id="display"></p>
+    <div id="content" style="display:none;"></div>
+    <pre id="test"></pre>
+  </body>
+</window>
--- a/dom/tests/moz.build
+++ b/dom/tests/moz.build
@@ -157,31 +157,33 @@ MOCHITEST_MANIFESTS += [
     'mochitest/dom-level0/mochitest.ini',
     'mochitest/dom-level1-core/mochitest.ini',
     'mochitest/dom-level2-core/mochitest.ini',
     'mochitest/dom-level2-html/mochitest.ini',
     'mochitest/fetch/mochitest.ini',
     'mochitest/gamepad/mochitest.ini',
     'mochitest/general/mochitest.ini',
     'mochitest/geolocation/mochitest.ini',
+    'mochitest/keyhandling/mochitest.ini',
     'mochitest/localstorage/mochitest.ini',
     'mochitest/orientation/mochitest.ini',
     'mochitest/pointerlock/mochitest.ini',
     'mochitest/script/mochitest.ini',
     'mochitest/sessionstorage/mochitest.ini',
     'mochitest/storageevent/mochitest.ini',
     'mochitest/webcomponents/mochitest.ini',
     'mochitest/whatwg/mochitest.ini',
 ]
 
 MOCHITEST_CHROME_MANIFESTS += [
     'mochitest/beacon/chrome.ini',
     'mochitest/chrome/chrome.ini',
     'mochitest/general/chrome.ini',
     'mochitest/geolocation/chrome.ini',
+    'mochitest/keyhandling/chrome.ini',
     'mochitest/localstorage/chrome.ini',
     'mochitest/sessionstorage/chrome.ini',
     'mochitest/webcomponents/chrome.ini',
     'mochitest/webcomponents/chrome_disabled.ini',
     'mochitest/whatwg/chrome.ini',
 ]
 
 XPCSHELL_TESTS_MANIFESTS += ['unit/xpcshell.ini']
new file mode 100644
--- /dev/null
+++ b/dom/xbl/builtin/ShortcutKeyDefinitionsForBrowserCommon.h
@@ -0,0 +1,16 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+    { u"keypress", nullptr,         u" ",    u"shift",           u"cmd_scrollPageUp" },
+    { u"keypress", nullptr,         u" ",    nullptr,            u"cmd_scrollPageDown" },
+    { u"keypress", u"VK_UP",        nullptr, nullptr,            u"cmd_moveUp" },
+    { u"keypress", u"VK_DOWN",      nullptr, nullptr,            u"cmd_moveDown" },
+    { u"keypress", u"VK_LEFT",      nullptr, nullptr,            u"cmd_moveLeft" },
+    { u"keypress", u"VK_RIGHT",     nullptr, nullptr,            u"cmd_moveRight" },
+    { u"keypress", nullptr,         u"x",    u"accel",           u"cmd_cut" },
+    { u"keypress", nullptr,         u"c",    u"accel",           u"cmd_copy" },
+    { u"keypress", nullptr,         u"v",    u"accel",           u"cmd_paste" },
+    { u"keypress", nullptr,         u"z",    u"accel",           u"cmd_undo" },
+    { u"keypress", nullptr,         u"z",    u"accel,shift",     u"cmd_redo" },
+    { u"keypress", nullptr,         u"a",    u"accel",           u"cmd_selectAll" },
new file mode 100644
--- /dev/null
+++ b/dom/xbl/builtin/ShortcutKeyDefinitionsForEditorCommon.h
@@ -0,0 +1,20 @@
+/* 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/. */
+
+    { u"keypress", nullptr,         u" ",    u"shift",           u"cmd_scrollPageUp" },
+    { u"keypress", nullptr,         u" ",    nullptr,            u"cmd_scrollPageDown" },
+    { u"keypress", u"VK_LEFT",      nullptr, nullptr,            u"cmd_moveLeft" },
+    { u"keypress", u"VK_RIGHT",     nullptr, nullptr,            u"cmd_moveRight" },
+    { u"keypress", u"VK_LEFT",      nullptr, u"shift",           u"cmd_selectLeft" },
+    { u"keypress", u"VK_RIGHT",     nullptr, u"shift",           u"cmd_selectRight" },
+    { u"keypress", u"VK_UP",        nullptr, nullptr,            u"cmd_moveUp" },
+    { u"keypress", u"VK_DOWN",      nullptr, nullptr,            u"cmd_moveDown" },
+    { u"keypress", u"VK_UP",        nullptr, u"shift",           u"cmd_selectUp" },
+    { u"keypress", u"VK_DOWN",      nullptr, u"shift",           u"cmd_selectDown" },
+    { u"keypress", nullptr,         u"z",    u"accel",           u"cmd_undo" },
+    { u"keypress", nullptr,         u"z",    u"accel,shift",     u"cmd_redo" },
+    { u"keypress", nullptr,         u"x",    u"accel",           u"cmd_cut" },
+    { u"keypress", nullptr,         u"c",    u"accel",           u"cmd_copy" },
+    { u"keypress", nullptr,         u"v",    u"accel",           u"cmd_paste" },
+    { u"keypress", nullptr,         u"v",    u"accel,shift",     u"cmd_pasteNoFormatting" },
new file mode 100644
--- /dev/null
+++ b/dom/xbl/builtin/ShortcutKeyDefinitionsForInputCommon.h
@@ -0,0 +1,17 @@
+/* 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/. */
+
+    { u"keypress", u"VK_LEFT",      nullptr, nullptr,            u"cmd_moveLeft" },
+    { u"keypress", u"VK_RIGHT",     nullptr, nullptr,            u"cmd_moveRight" },
+    { u"keypress", u"VK_LEFT",      nullptr, u"shift",           u"cmd_selectLeft" },
+    { u"keypress", u"VK_RIGHT",     nullptr, u"shift",           u"cmd_selectRight" },
+    { u"keypress", u"VK_UP",        nullptr, nullptr,            u"cmd_moveUp" },
+    { u"keypress", u"VK_DOWN",      nullptr, nullptr,            u"cmd_moveDown" },
+    { u"keypress", u"VK_UP",        nullptr, u"shift",           u"cmd_selectUp" },
+    { u"keypress", u"VK_DOWN",      nullptr, u"shift",           u"cmd_selectDown" },
+    { u"keypress", nullptr,         u"c",    u"accel",           u"cmd_copy" },
+    { u"keypress", nullptr,         u"x",    u"accel",           u"cmd_cut" },
+    { u"keypress", nullptr,         u"v",    u"accel",           u"cmd_paste" },
+    { u"keypress", nullptr,         u"z",    u"accel",           u"cmd_undo" },
+    { u"keypress", nullptr,         u"z",    u"accel,shift",     u"cmd_redo" },
new file mode 100644
--- /dev/null
+++ b/dom/xbl/builtin/ShortcutKeyDefinitionsForTextAreaCommon.h
@@ -0,0 +1,17 @@
+/* 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/. */
+
+    { u"keypress", u"VK_LEFT",      nullptr, nullptr,            u"cmd_moveLeft" },
+    { u"keypress", u"VK_RIGHT",     nullptr, nullptr,            u"cmd_moveRight" },
+    { u"keypress", u"VK_LEFT",      nullptr, u"shift",           u"cmd_selectLeft" },
+    { u"keypress", u"VK_RIGHT",     nullptr, u"shift",           u"cmd_selectRight" },
+    { u"keypress", u"VK_UP",        nullptr, nullptr,            u"cmd_moveUp" },
+    { u"keypress", u"VK_DOWN",      nullptr, nullptr,            u"cmd_moveDown" },
+    { u"keypress", u"VK_UP",        nullptr, u"shift",           u"cmd_selectUp" },
+    { u"keypress", u"VK_DOWN",      nullptr, u"shift",           u"cmd_selectDown" },
+    { u"keypress", nullptr,         u"c",    u"accel",           u"cmd_copy" },
+    { u"keypress", nullptr,         u"x",    u"accel",           u"cmd_cut" },
+    { u"keypress", nullptr,         u"v",    u"accel",           u"cmd_paste" },
+    { u"keypress", nullptr,         u"z",    u"accel",           u"cmd_undo" },
+    { u"keypress", nullptr,         u"z",    u"accel,shift",     u"cmd_redo" },
new file mode 100644
--- /dev/null
+++ b/dom/xbl/builtin/ShortcutKeys.cpp
@@ -0,0 +1,118 @@
+#include "mozilla/ShortcutKeys.h"
+#include "../nsXBLPrototypeHandler.h"
+#include "nsContentUtils.h"
+#include "nsAtom.h"
+#include "mozilla/TextEvents.h"
+
+namespace mozilla {
+
+NS_IMPL_ISUPPORTS(ShortcutKeys, nsIObserver);
+
+StaticRefPtr<ShortcutKeys> ShortcutKeys::sInstance;
+
+ShortcutKeys::ShortcutKeys()
+  : mBrowserHandlers(nullptr)
+  , mEditorHandlers(nullptr)
+  , mInputHandlers(nullptr)
+  , mTextAreaHandlers(nullptr)
+{
+  MOZ_ASSERT(!sInstance, "Attempt to instantiate a second ShortcutKeys.");
+  nsContentUtils::RegisterShutdownObserver(this);
+}
+
+ShortcutKeys::~ShortcutKeys()
+{
+  delete mBrowserHandlers;
+  delete mEditorHandlers;
+  delete mInputHandlers;
+  delete mTextAreaHandlers;
+}
+
+nsresult
+ShortcutKeys::Observe(nsISupports* aSubject, const char* aTopic, const char16_t* aData)
+{
+  // Clear our strong reference so we can clean up.
+  sInstance = nullptr;
+  return NS_OK;
+}
+
+/* static */ nsXBLPrototypeHandler*
+ShortcutKeys::GetHandlers(HandlerType aType)
+{
+  if (!sInstance) {
+    sInstance = new ShortcutKeys();
+  }
+
+  return sInstance->EnsureHandlers(aType);
+}
+
+/* static */ nsAtom*
+ShortcutKeys::ConvertEventToDOMEventType(const WidgetKeyboardEvent* aWidgetKeyboardEvent)
+{
+  if (aWidgetKeyboardEvent->IsKeyDownOrKeyDownOnPlugin()) {
+    return nsGkAtoms::keydown;
+  }
+  if (aWidgetKeyboardEvent->IsKeyUpOrKeyUpOnPlugin()) {
+    return nsGkAtoms::keyup;
+  }
+  // eAccessKeyNotFound event is always created from eKeyPress event and
+  // the original eKeyPress event has stopped its propagation before dispatched
+  // into the DOM tree in this process and not matched with remote content's
+  // access keys.  So, we should treat it as an eKeyPress event and execute
+  // a command if it's registered as a shortcut key.
+  if (aWidgetKeyboardEvent->mMessage == eKeyPress ||
+      aWidgetKeyboardEvent->mMessage == eAccessKeyNotFound) {
+    return nsGkAtoms::keypress;
+  }
+  MOZ_ASSERT_UNREACHABLE("All event messages relating to shortcut keys should be handled");
+  return nullptr;
+}
+
+nsXBLPrototypeHandler*
+ShortcutKeys::EnsureHandlers(HandlerType aType)
+{
+  ShortcutKeyData* keyData;
+  nsXBLPrototypeHandler** cache;
+
+  switch (aType) {
+    case HandlerType::eBrowser:
+      keyData = &sBrowserHandlers[0];
+      cache = &mBrowserHandlers;
+      break;
+    case HandlerType::eEditor:
+      keyData = &sEditorHandlers[0];
+      cache = &mEditorHandlers;
+      break;
+    case HandlerType::eInput:
+      keyData = &sInputHandlers[0];
+      cache = &mInputHandlers;
+      break;
+    case HandlerType::eTextArea:
+      keyData = &sTextAreaHandlers[0];
+      cache = &mTextAreaHandlers;
+      break;
+    default:
+      MOZ_ASSERT(false, "Unknown handler type requested.");
+  }
+
+  if (*cache) {
+    return *cache;
+  }
+
+  nsXBLPrototypeHandler* lastHandler = nullptr;
+  while (keyData->event) {
+    nsXBLPrototypeHandler* handler =
+      new nsXBLPrototypeHandler(keyData);
+    if (lastHandler) {
+      lastHandler->SetNextHandler(handler);
+    } else {
+      *cache = handler;
+    }
+    lastHandler = handler;
+    keyData++;
+  }
+
+  return *cache;
+}
+
+} // namespace mozilla
new file mode 100644
--- /dev/null
+++ b/dom/xbl/builtin/ShortcutKeys.h
@@ -0,0 +1,71 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_dom_ShortcutKeys_h
+#define mozilla_dom_ShortcutKeys_h
+
+#include "nsIObserver.h"
+
+class nsXBLPrototypeHandler;
+class nsAtom;
+
+namespace mozilla {
+
+class WidgetKeyboardEvent;
+
+typedef struct
+{
+   const char16_t* event;
+   const char16_t* keycode;
+   const char16_t* key;
+   const char16_t* modifiers;
+   const char16_t* command;
+} ShortcutKeyData;
+
+enum class HandlerType
+{
+  eInput,
+  eTextArea,
+  eBrowser,
+  eEditor,
+};
+
+class ShortcutKeys : public nsIObserver
+{
+public:
+  NS_DECL_ISUPPORTS
+  NS_DECL_NSIOBSERVER
+
+  // Returns a pointer to the first handler for the given type.
+  static nsXBLPrototypeHandler* GetHandlers(HandlerType aType);
+
+  // Gets the event type for a widget keyboard event.
+  static nsAtom* ConvertEventToDOMEventType(const WidgetKeyboardEvent* aWidgetKeyboardEvent);
+
+protected:
+  ShortcutKeys();
+  virtual ~ShortcutKeys();
+
+  // Returns a pointer to the first handler for the given type.
+  nsXBLPrototypeHandler* EnsureHandlers(HandlerType aType);
+
+  // Maintains a strong reference to the only instance.
+  static StaticRefPtr<ShortcutKeys> sInstance;
+
+  // Shortcut keys for different elements.
+  static ShortcutKeyData sBrowserHandlers[];
+  static ShortcutKeyData sEditorHandlers[];
+  static ShortcutKeyData sInputHandlers[];
+  static ShortcutKeyData sTextAreaHandlers[];
+
+  // Cached event handlers generated from the above data.
+  nsXBLPrototypeHandler* mBrowserHandlers;
+  nsXBLPrototypeHandler* mEditorHandlers;
+  nsXBLPrototypeHandler* mInputHandlers;
+  nsXBLPrototypeHandler* mTextAreaHandlers;
+};
+
+} // namespace mozilla
+
+#endif // #ifndef mozilla_dom_ShortcutKeys_h
new file mode 100644
--- /dev/null
+++ b/dom/xbl/builtin/android/ShortcutKeyDefinitions.cpp
@@ -0,0 +1,152 @@
+/* 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 "../ShortcutKeys.h"
+
+namespace mozilla {
+
+ShortcutKeyData ShortcutKeys::sInputHandlers[] =
+{
+#include "../ShortcutKeyDefinitionsForInputCommon.h"
+
+    { u"keypress", nullptr,         u"a",    u"accel",           u"cmd_selectAll" },
+    { u"keypress", u"VK_LEFT",      nullptr, u"control",         u"cmd_wordPrevious" },
+    { u"keypress", u"VK_RIGHT",     nullptr, u"control",         u"cmd_wordNext" },
+    { u"keypress", u"VK_LEFT",      nullptr, u"shift,control",   u"cmd_selectWordPrevious" },
+    { u"keypress", u"VK_RIGHT",     nullptr, u"shift,control",   u"cmd_selectWordNext" },
+    { u"keypress", u"VK_LEFT",      nullptr, u"alt",             u"cmd_beginLine" },
+    { u"keypress", u"VK_RIGHT",     nullptr, u"alt",             u"cmd_endLine" },
+    { u"keypress", u"VK_LEFT",      nullptr, u"shift,alt",       u"cmd_selectBeginLine" },
+    { u"keypress", u"VK_RIGHT",     nullptr, u"shift,alt",       u"cmd_selectEndLine" },
+    { u"keypress", u"VK_HOME",      nullptr, nullptr,            u"cmd_beginLine" },
+    { u"keypress", u"VK_END",       nullptr, nullptr,            u"cmd_endLine" },
+    { u"keypress", u"VK_HOME",      nullptr, u"shift",           u"cmd_selectBeginLine" },
+    { u"keypress", u"VK_END",       nullptr, u"shift",           u"cmd_selectEndLine" },
+    { u"keypress", u"VK_BACK",      nullptr, u"alt",             u"cmd_deleteToBeginningOfLine" },
+    { u"keypress", u"VK_DELETE",    nullptr, u"alt",             u"cmd_deleteToEndOfLine" },
+
+    { nullptr,     nullptr,         nullptr, nullptr,            nullptr }
+};
+
+ShortcutKeyData ShortcutKeys::sTextAreaHandlers[] =
+{
+#include "../ShortcutKeyDefinitionsForTextAreaCommon.h"
+
+    { u"keypress", nullptr,         u"a",    u"accel",           u"cmd_selectAll" },
+    { u"keypress", u"VK_LEFT",      nullptr, u"control",         u"cmd_wordPrevious" },
+    { u"keypress", u"VK_RIGHT",     nullptr, u"control",         u"cmd_wordNext" },
+    { u"keypress", u"VK_LEFT",      nullptr, u"shift,control",   u"cmd_selectWordPrevious" },
+    { u"keypress", u"VK_RIGHT",     nullptr, u"shift,control",   u"cmd_selectWordNext" },
+    { u"keypress", u"VK_LEFT",      nullptr, u"alt",             u"cmd_beginLine" },
+    { u"keypress", u"VK_RIGHT",     nullptr, u"alt",             u"cmd_endLine" },
+    { u"keypress", u"VK_LEFT",      nullptr, u"shift,alt",       u"cmd_selectBeginLine" },
+    { u"keypress", u"VK_RIGHT",     nullptr, u"shift,alt",       u"cmd_selectEndLine" },
+    { u"keypress", u"VK_UP",        nullptr, u"alt",             u"cmd_moveTop" },
+    { u"keypress", u"VK_DOWN",      nullptr, u"alt",             u"cmd_moveBottom" },
+    { u"keypress", u"VK_UP",        nullptr, u"shift,alt",       u"cmd_selectTop" },
+    { u"keypress", u"VK_DOWN",      nullptr, u"shift,alt",       u"cmd_selectBottom" },
+    { u"keypress", u"VK_PAGE_UP",   nullptr, nullptr,            u"cmd_movePageUp" },
+    { u"keypress", u"VK_PAGE_DOWN", nullptr, nullptr,            u"cmd_movePageDown" },
+    { u"keypress", u"VK_PAGE_UP",   nullptr, u"shift",           u"cmd_selectPageUp" },
+    { u"keypress", u"VK_PAGE_DOWN", nullptr, u"shift",           u"cmd_selectPageDown" },
+    { u"keypress", u"VK_PAGE_UP",   nullptr, u"alt",             u"cmd_moveTop" },
+    { u"keypress", u"VK_PAGE_DOWN", nullptr, u"alt",             u"cmd_moveBottom" },
+    { u"keypress", u"VK_PAGE_UP",   nullptr, u"shift,alt",       u"cmd_selectTop" },
+    { u"keypress", u"VK_PAGE_DOWN", nullptr, u"shift,alt",       u"cmd_selectBottom" },
+    { u"keypress", u"VK_HOME",      nullptr, nullptr,            u"cmd_beginLine" },
+    { u"keypress", u"VK_END",       nullptr, nullptr,            u"cmd_endLine" },
+    { u"keypress", u"VK_HOME",      nullptr, u"shift",           u"cmd_selectBeginLine" },
+    { u"keypress", u"VK_END",       nullptr, u"shift",           u"cmd_selectEndLine" },
+    { u"keypress", u"VK_HOME",      nullptr, u"control",         u"cmd_moveTop" },
+    { u"keypress", u"VK_END",       nullptr, u"control",         u"cmd_moveBottom" },
+    { u"keypress", u"VK_HOME",      nullptr, u"shift,control",   u"cmd_selectTop" },
+    { u"keypress", u"VK_END",       nullptr, u"shift,control",   u"cmd_selectBottom" },
+    { u"keypress", u"VK_BACK",      nullptr, u"alt",             u"cmd_deleteToBeginningOfLine" },
+    { u"keypress", u"VK_DELETE",    nullptr, u"alt",             u"cmd_deleteToEndOfLine" },
+
+    { nullptr,     nullptr,         nullptr, nullptr,            nullptr }
+};
+
+ShortcutKeyData ShortcutKeys::sBrowserHandlers[] =
+{
+#include "../ShortcutKeyDefinitionsForBrowserCommon.h"
+
+    { u"keypress", u"VK_LEFT",      nullptr, u"shift",           u"cmd_selectCharPrevious" },
+    { u"keypress", u"VK_RIGHT",     nullptr, u"shift",           u"cmd_selectCharNext" },
+    { u"keypress", u"VK_LEFT",      nullptr, u"control",         u"cmd_wordPrevious" },
+    { u"keypress", u"VK_RIGHT",     nullptr, u"control",         u"cmd_wordNext" },
+    { u"keypress", u"VK_LEFT",      nullptr, u"control,shift",   u"cmd_selectWordPrevious" },
+    { u"keypress", u"VK_RIGHT",     nullptr, u"control,shift",   u"cmd_selectWordNext" },
+    { u"keypress", u"VK_LEFT",      nullptr, u"alt",             u"cmd_beginLine" },
+    { u"keypress", u"VK_RIGHT",     nullptr, u"alt",             u"cmd_endLine" },
+    { u"keypress", u"VK_LEFT",      nullptr, u"shift,alt",       u"cmd_selectBeginLine" },
+    { u"keypress", u"VK_RIGHT",     nullptr, u"shift,alt",       u"cmd_selectEndLine" },
+    { u"keypress", u"VK_UP",        nullptr, u"shift",           u"cmd_selectLinePrevious" },
+    { u"keypress", u"VK_DOWN",      nullptr, u"shift",           u"cmd_selectLineNext" },
+    { u"keypress", u"VK_UP",        nullptr, u"alt",             u"cmd_moveTop" },
+    { u"keypress", u"VK_DOWN",      nullptr, u"alt",             u"cmd_moveBottom" },
+    { u"keypress", u"VK_UP",        nullptr, u"shift,alt",       u"cmd_selectTop" },
+    { u"keypress", u"VK_DOWN",      nullptr, u"shift,alt",       u"cmd_selectBottom" },
+    { u"keypress", u"VK_PAGE_UP",   nullptr, nullptr,            u"cmd_movePageUp" },
+    { u"keypress", u"VK_PAGE_DOWN", nullptr, nullptr,            u"cmd_movePageDown" },
+    { u"keypress", u"VK_PAGE_UP",   nullptr, u"shift",           u"cmd_selectPageUp" },
+    { u"keypress", u"VK_PAGE_DOWN", nullptr, u"shift",           u"cmd_selectPageDown" },
+    { u"keypress", u"VK_PAGE_UP",   nullptr, u"alt",             u"cmd_moveTop" },
+    { u"keypress", u"VK_PAGE_DOWN", nullptr, u"alt",             u"cmd_moveBottom" },
+    { u"keypress", u"VK_PAGE_UP",   nullptr, u"shift,alt",       u"cmd_selectTop" },
+    { u"keypress", u"VK_PAGE_DOWN", nullptr, u"shift,alt",       u"cmd_selectBottom" },
+    { u"keypress", u"VK_HOME",      nullptr, nullptr,            u"cmd_beginLine" },
+    { u"keypress", u"VK_END",       nullptr, nullptr,            u"cmd_endLine" },
+    { u"keypress", u"VK_HOME",      nullptr, u"shift",           u"cmd_selectBeginLine" },
+    { u"keypress", u"VK_END",       nullptr, u"shift",           u"cmd_selectEndLine" },
+    { u"keypress", u"VK_HOME",      nullptr, u"control",         u"cmd_moveTop" },
+    { u"keypress", u"VK_END",       nullptr, u"control",         u"cmd_moveBottom" },
+    { u"keypress", u"VK_HOME",      nullptr, u"shift,control",   u"cmd_selectTop" },
+    { u"keypress", u"VK_END",       nullptr, u"shift,control",   u"cmd_selectBottom" },
+    { u"keypress", u"VK_BACK",      nullptr, u"alt",             u"cmd_deleteToBeginningOfLine" },
+    { u"keypress", u"VK_DELETE",    nullptr, u"alt",             u"cmd_deleteToEndOfLine" },
+
+    { nullptr,     nullptr,         nullptr, nullptr,            nullptr }
+};
+
+ShortcutKeyData ShortcutKeys::sEditorHandlers[] =
+{
+#include "../ShortcutKeyDefinitionsForEditorCommon.h"
+
+    { u"keypress", nullptr,         u"a",    u"accel",           u"cmd_selectAll" },
+    { u"keypress", u"VK_LEFT",      nullptr, u"control",         u"cmd_wordPrevious" },
+    { u"keypress", u"VK_RIGHT",     nullptr, u"control",         u"cmd_wordNext" },
+    { u"keypress", u"VK_LEFT",      nullptr, u"shift,control",   u"cmd_selectWordPrevious" },
+    { u"keypress", u"VK_RIGHT",     nullptr, u"shift,control",   u"cmd_selectWordNext" },
+    { u"keypress", u"VK_LEFT",      nullptr, u"alt",             u"cmd_beginLine" },
+    { u"keypress", u"VK_RIGHT",     nullptr, u"alt",             u"cmd_endLine" },
+    { u"keypress", u"VK_LEFT",      nullptr, u"shift,alt",       u"cmd_selectBeginLine" },
+    { u"keypress", u"VK_RIGHT",     nullptr, u"shift,alt",       u"cmd_selectEndLine" },
+    { u"keypress", u"VK_UP",        nullptr, u"alt",             u"cmd_moveTop" },
+    { u"keypress", u"VK_DOWN",      nullptr, u"alt",             u"cmd_moveBottom" },
+    { u"keypress", u"VK_UP",        nullptr, u"shift,alt",       u"cmd_selectTop" },
+    { u"keypress", u"VK_DOWN",      nullptr, u"shift,alt",       u"cmd_selectBottom" },
+    { u"keypress", u"VK_PAGE_UP",   nullptr, nullptr,            u"cmd_movePageUp" },
+    { u"keypress", u"VK_PAGE_DOWN", nullptr, nullptr,            u"cmd_movePageDown" },
+    { u"keypress", u"VK_PAGE_UP",   nullptr, u"shift",           u"cmd_selectPageUp" },
+    { u"keypress", u"VK_PAGE_DOWN", nullptr, u"shift",           u"cmd_selectPageDown" },
+    { u"keypress", u"VK_PAGE_UP",   nullptr, u"alt",             u"cmd_moveTop" },
+    { u"keypress", u"VK_PAGE_DOWN", nullptr, u"alt",             u"cmd_moveBottom" },
+    { u"keypress", u"VK_PAGE_UP",   nullptr, u"shift,alt",       u"cmd_selectTop" },
+    { u"keypress", u"VK_PAGE_DOWN", nullptr, u"shift,alt",       u"cmd_selectBottom" },
+    { u"keypress", u"VK_HOME",      nullptr, nullptr,            u"cmd_beginLine" },
+    { u"keypress", u"VK_END",       nullptr, nullptr,            u"cmd_endLine" },
+    { u"keypress", u"VK_HOME",      nullptr, u"shift",           u"cmd_selectBeginLine" },
+    { u"keypress", u"VK_END",       nullptr, u"shift",           u"cmd_selectEndLine" },
+    { u"keypress", u"VK_HOME",      nullptr, u"control",         u"cmd_moveTop" },
+    { u"keypress", u"VK_END",       nullptr, u"control",         u"cmd_moveBottom" },
+    { u"keypress", u"VK_HOME",      nullptr, u"shift,control",   u"cmd_selectTop" },
+    { u"keypress", u"VK_END",       nullptr, u"shift,control",   u"cmd_selectBottom" },
+    { u"keypress", u"VK_BACK",      nullptr, u"alt",             u"cmd_deleteToBeginningOfLine" },
+    { u"keypress", u"VK_DELETE",    nullptr, u"alt",             u"cmd_deleteToEndOfLine" },
+
+    { nullptr,     nullptr,         nullptr, nullptr,            nullptr }
+};
+
+} // namespace mozilla
deleted file mode 100644
--- a/dom/xbl/builtin/android/jar.mn
+++ /dev/null
@@ -1,6 +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/.
-
-toolkit.jar:
-* content/global/platformHTMLBindings.xml   (platformHTMLBindings.xml)
--- a/dom/xbl/builtin/android/moz.build
+++ b/dom/xbl/builtin/android/moz.build
@@ -1,7 +1,9 @@
 # -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
 # vim: set filetype=python:
 # This Source Code Form is subject to the terms of the Mozilla Public
 # License, v. 2.0. If a copy of the MPL was not distributed with this
 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
 
-JAR_MANIFESTS += ['jar.mn']
\ No newline at end of file
+SOURCES += ['ShortcutKeyDefinitions.cpp']
+
+FINAL_LIBRARY = 'xul'
deleted file mode 100644
--- a/dom/xbl/builtin/android/platformHTMLBindings.xml
+++ /dev/null
@@ -1,162 +0,0 @@
-<?xml version="1.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/. -->
-
-
-<bindings id="htmlBindings"
-   xmlns="http://www.mozilla.org/xbl"
-   xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
-
-  <binding id="inputFields" bindToUntrustedContent="true">
-    <handlers>
-#include ../input-fields-base.inc
-      <handler event="keypress" key="a" modifiers="accel" command="cmd_selectAll"/>
-
-      <handler event="keypress" keycode="VK_LEFT" modifiers="control" command="cmd_wordPrevious"/>
-      <handler event="keypress" keycode="VK_RIGHT" modifiers="control" command="cmd_wordNext"/>
-      <handler event="keypress" keycode="VK_LEFT" modifiers="shift,control" command="cmd_selectWordPrevious"/>
-      <handler event="keypress" keycode="VK_RIGHT" modifiers="shift,control" command="cmd_selectWordNext"/>
-      <handler event="keypress" keycode="VK_LEFT" modifiers="alt" command="cmd_beginLine"/>
-      <handler event="keypress" keycode="VK_RIGHT" modifiers="alt" command="cmd_endLine"/>
-      <handler event="keypress" keycode="VK_LEFT" modifiers="shift,alt" command="cmd_selectBeginLine"/>
-      <handler event="keypress" keycode="VK_RIGHT" modifiers="shift,alt" command="cmd_selectEndLine"/>
-
-      <handler event="keypress" keycode="VK_HOME" command="cmd_beginLine"/>
-      <handler event="keypress" keycode="VK_END" command="cmd_endLine"/>
-      <handler event="keypress" keycode="VK_HOME" modifiers="shift" command="cmd_selectBeginLine" />
-      <handler event="keypress" keycode="VK_END" modifiers="shift" command="cmd_selectEndLine" />
-
-      <handler event="keypress" keycode="VK_BACK" modifiers="alt" command="cmd_deleteToBeginningOfLine"/>
-      <handler event="keypress" keycode="VK_DELETE" modifiers="alt" command="cmd_deleteToEndOfLine"/>
-    </handlers>
-  </binding>
-
-  <binding id="textAreas" bindToUntrustedContent="true">
-    <handlers>
-#include ../textareas-base.inc
-      <handler event="keypress" key="a" modifiers="accel" command="cmd_selectAll"/>
-
-      <handler event="keypress" keycode="VK_LEFT" modifiers="control" command="cmd_wordPrevious"/>
-      <handler event="keypress" keycode="VK_RIGHT" modifiers="control" command="cmd_wordNext"/>
-      <handler event="keypress" keycode="VK_LEFT" modifiers="shift,control" command="cmd_selectWordPrevious"/>
-      <handler event="keypress" keycode="VK_RIGHT" modifiers="shift,control" command="cmd_selectWordNext"/>
-      <handler event="keypress" keycode="VK_LEFT" modifiers="alt" command="cmd_beginLine"/>
-      <handler event="keypress" keycode="VK_RIGHT" modifiers="alt" command="cmd_endLine"/>
-      <handler event="keypress" keycode="VK_LEFT" modifiers="shift,alt" command="cmd_selectBeginLine"/>
-      <handler event="keypress" keycode="VK_RIGHT" modifiers="shift,alt" command="cmd_selectEndLine"/>
-
-      <handler event="keypress" keycode="VK_UP" modifiers="alt" command="cmd_moveTop"/>
-      <handler event="keypress" keycode="VK_DOWN" modifiers="alt" command="cmd_moveBottom"/>
-      <handler event="keypress" keycode="VK_UP" modifiers="shift,alt" command="cmd_selectTop"/>
-      <handler event="keypress" keycode="VK_DOWN" modifiers="shift,alt" command="cmd_selectBottom"/>
-
-      <handler event="keypress" keycode="VK_PAGE_UP" command="cmd_movePageUp"/>
-      <handler event="keypress" keycode="VK_PAGE_DOWN" command="cmd_movePageDown"/>
-      <handler event="keypress" keycode="VK_PAGE_UP" modifiers="shift" command="cmd_selectPageUp"/>
-      <handler event="keypress" keycode="VK_PAGE_DOWN" modifiers="shift" command="cmd_selectPageDown"/>
-      <handler event="keypress" keycode="VK_PAGE_UP" modifiers="alt" command="cmd_moveTop"/>
-      <handler event="keypress" keycode="VK_PAGE_DOWN" modifiers="alt" command="cmd_moveBottom"/>
-      <handler event="keypress" keycode="VK_PAGE_UP" modifiers="shift,alt" command="cmd_selectTop"/>
-      <handler event="keypress" keycode="VK_PAGE_DOWN" modifiers="shift,alt" command="cmd_selectBottom"/>
-
-      <handler event="keypress" keycode="VK_HOME" command="cmd_beginLine"/>
-      <handler event="keypress" keycode="VK_END" command="cmd_endLine"/>
-      <handler event="keypress" keycode="VK_HOME" modifiers="shift" command="cmd_selectBeginLine" />
-      <handler event="keypress" keycode="VK_END" modifiers="shift" command="cmd_selectEndLine" />
-      <handler event="keypress" keycode="VK_HOME" modifiers="control" command="cmd_moveTop"/>
-      <handler event="keypress" keycode="VK_END" modifiers="control" command="cmd_moveBottom"/>
-      <handler event="keypress" keycode="VK_HOME" modifiers="shift,control" command="cmd_selectTop" />
-      <handler event="keypress" keycode="VK_END" modifiers="shift,control" command="cmd_selectBottom" />
-
-      <handler event="keypress" keycode="VK_BACK" modifiers="alt" command="cmd_deleteToBeginningOfLine"/>
-      <handler event="keypress" keycode="VK_DELETE" modifiers="alt" command="cmd_deleteToEndOfLine"/>
-    </handlers>
-  </binding>
-
-  <binding id="browser">
-    <handlers>
-#include ../browser-base.inc
-      <handler event="keypress" keycode="VK_LEFT" modifiers="shift" command="cmd_selectCharPrevious" />
-      <handler event="keypress" keycode="VK_RIGHT" modifiers="shift" command="cmd_selectCharNext" />
-      <handler event="keypress" keycode="VK_LEFT" modifiers="control" command="cmd_wordPrevious" />
-      <handler event="keypress" keycode="VK_RIGHT" modifiers="control" command="cmd_wordNext" />
-      <handler event="keypress" keycode="VK_LEFT" modifiers="control,shift" command="cmd_selectWordPrevious" />
-      <handler event="keypress" keycode="VK_RIGHT" modifiers="control,shift" command="cmd_selectWordNext" />
-      <handler event="keypress" keycode="VK_LEFT" modifiers="alt" command="cmd_beginLine"/>
-      <handler event="keypress" keycode="VK_RIGHT" modifiers="alt" command="cmd_endLine"/>
-      <handler event="keypress" keycode="VK_LEFT" modifiers="shift,alt" command="cmd_selectBeginLine"/>
-      <handler event="keypress" keycode="VK_RIGHT" modifiers="shift,alt" command="cmd_selectEndLine"/>
-
-      <handler event="keypress" keycode="VK_UP" modifiers="shift" command="cmd_selectLinePrevious" />
-      <handler event="keypress" keycode="VK_DOWN" modifiers="shift" command="cmd_selectLineNext" />
-      <handler event="keypress" keycode="VK_UP" modifiers="alt" command="cmd_moveTop"/>
-      <handler event="keypress" keycode="VK_DOWN" modifiers="alt" command="cmd_moveBottom"/>
-      <handler event="keypress" keycode="VK_UP" modifiers="shift,alt" command="cmd_selectTop"/>
-      <handler event="keypress" keycode="VK_DOWN" modifiers="shift,alt" command="cmd_selectBottom"/>
-
-      <handler event="keypress" keycode="VK_PAGE_UP" command="cmd_movePageUp"/>
-      <handler event="keypress" keycode="VK_PAGE_DOWN" command="cmd_movePageDown"/>
-      <handler event="keypress" keycode="VK_PAGE_UP" modifiers="shift" command="cmd_selectPageUp"/>
-      <handler event="keypress" keycode="VK_PAGE_DOWN" modifiers="shift" command="cmd_selectPageDown"/>
-      <handler event="keypress" keycode="VK_PAGE_UP" modifiers="alt" command="cmd_moveTop"/>
-      <handler event="keypress" keycode="VK_PAGE_DOWN" modifiers="alt" command="cmd_moveBottom"/>
-      <handler event="keypress" keycode="VK_PAGE_UP" modifiers="shift,alt" command="cmd_selectTop"/>
-      <handler event="keypress" keycode="VK_PAGE_DOWN" modifiers="shift,alt" command="cmd_selectBottom"/>
-
-      <handler event="keypress" keycode="VK_HOME" command="cmd_beginLine"/>
-      <handler event="keypress" keycode="VK_END" command="cmd_endLine"/>
-      <handler event="keypress" keycode="VK_HOME" modifiers="shift" command="cmd_selectBeginLine" />
-      <handler event="keypress" keycode="VK_END" modifiers="shift" command="cmd_selectEndLine" />
-      <handler event="keypress" keycode="VK_HOME" modifiers="control" command="cmd_moveTop"/>
-      <handler event="keypress" keycode="VK_END" modifiers="control" command="cmd_moveBottom"/>
-      <handler event="keypress" keycode="VK_HOME" modifiers="shift,control" command="cmd_selectTop" />
-      <handler event="keypress" keycode="VK_END" modifiers="shift,control" command="cmd_selectBottom" />
-
-      <handler event="keypress" keycode="VK_BACK" modifiers="alt" command="cmd_deleteToBeginningOfLine"/>
-      <handler event="keypress" keycode="VK_DELETE" modifiers="alt" command="cmd_deleteToEndOfLine"/>
-    </handlers>
-  </binding>
-
-  <binding id="editor">
-    <handlers>
-#include ../editor-base.inc
-      <handler event="keypress" key="a" modifiers="accel" command="cmd_selectAll"/>
-
-      <handler event="keypress" keycode="VK_LEFT" modifiers="control" command="cmd_wordPrevious"/>
-      <handler event="keypress" keycode="VK_RIGHT" modifiers="control" command="cmd_wordNext"/>
-      <handler event="keypress" keycode="VK_LEFT" modifiers="shift,control" command="cmd_selectWordPrevious"/>
-      <handler event="keypress" keycode="VK_RIGHT" modifiers="shift,control" command="cmd_selectWordNext"/>
-      <handler event="keypress" keycode="VK_LEFT" modifiers="alt" command="cmd_beginLine"/>
-      <handler event="keypress" keycode="VK_RIGHT" modifiers="alt" command="cmd_endLine"/>
-      <handler event="keypress" keycode="VK_LEFT" modifiers="shift,alt" command="cmd_selectBeginLine"/>
-      <handler event="keypress" keycode="VK_RIGHT" modifiers="shift,alt" command="cmd_selectEndLine"/>
-
-      <handler event="keypress" keycode="VK_UP" modifiers="alt" command="cmd_moveTop"/>
-      <handler event="keypress" keycode="VK_DOWN" modifiers="alt" command="cmd_moveBottom"/>
-      <handler event="keypress" keycode="VK_UP" modifiers="shift,alt" command="cmd_selectTop"/>
-      <handler event="keypress" keycode="VK_DOWN" modifiers="shift,alt" command="cmd_selectBottom"/>
-
-      <handler event="keypress" keycode="VK_PAGE_UP" command="cmd_movePageUp"/>
-      <handler event="keypress" keycode="VK_PAGE_DOWN" command="cmd_movePageDown"/>
-      <handler event="keypress" keycode="VK_PAGE_UP" modifiers="shift" command="cmd_selectPageUp"/>
-      <handler event="keypress" keycode="VK_PAGE_DOWN" modifiers="shift" command="cmd_selectPageDown"/>
-      <handler event="keypress" keycode="VK_PAGE_UP" modifiers="alt" command="cmd_moveTop"/>
-      <handler event="keypress" keycode="VK_PAGE_DOWN" modifiers="alt" command="cmd_moveBottom"/>
-      <handler event="keypress" keycode="VK_PAGE_UP" modifiers="shift,alt" command="cmd_selectTop"/>
-      <handler event="keypress" keycode="VK_PAGE_DOWN" modifiers="shift,alt" command="cmd_selectBottom"/>
-
-      <handler event="keypress" keycode="VK_HOME" command="cmd_beginLine"/>
-      <handler event="keypress" keycode="VK_END" command="cmd_endLine"/>
-      <handler event="keypress" keycode="VK_HOME" modifiers="shift" command="cmd_selectBeginLine" />
-      <handler event="keypress" keycode="VK_END" modifiers="shift" command="cmd_selectEndLine" />
-      <handler event="keypress" keycode="VK_HOME" modifiers="control" command="cmd_moveTop"/>
-      <handler event="keypress" keycode="VK_END" modifiers="control" command="cmd_moveBottom"/>
-      <handler event="keypress" keycode="VK_HOME" modifiers="shift,control" command="cmd_selectTop" />
-      <handler event="keypress" keycode="VK_END" modifiers="shift,control" command="cmd_selectBottom" />
-
-      <handler event="keypress" keycode="VK_BACK" modifiers="alt" command="cmd_deleteToBeginningOfLine"/>
-      <handler event="keypress" keycode="VK_DELETE" modifiers="alt" command="cmd_deleteToEndOfLine"/>
-    </handlers>
-  </binding>
-</bindings>
deleted file mode 100644
--- a/dom/xbl/builtin/browser-base.inc
+++ /dev/null
@@ -1,14 +0,0 @@
-      <handler event="keypress" key=" " modifiers="shift" command="cmd_scrollPageUp" />
-      <handler event="keypress" key=" " command="cmd_scrollPageDown" />
-
-      <handler event="keypress" keycode="VK_UP" command="cmd_moveUp" />
-      <handler event="keypress" keycode="VK_DOWN" command="cmd_moveDown" />
-      <handler event="keypress" keycode="VK_LEFT" command="cmd_moveLeft" />
-      <handler event="keypress" keycode="VK_RIGHT" command="cmd_moveRight" />
-
-      <handler event="keypress" key="x" command="cmd_cut" modifiers="accel"/>
-      <handler event="keypress" key="c" command="cmd_copy" modifiers="accel"/>
-      <handler event="keypress" key="v" command="cmd_paste" modifiers="accel"/>
-      <handler event="keypress" key="z" command="cmd_undo" modifiers="accel"/>
-      <handler event="keypress" key="z" command="cmd_redo" modifiers="accel,shift" />
-      <handler event="keypress" key="a" command="cmd_selectAll" modifiers="accel"/>
deleted file mode 100644
--- a/dom/xbl/builtin/editor-base.inc
+++ /dev/null
@@ -1,19 +0,0 @@
-      <handler event="keypress" key=" " modifiers="shift" command="cmd_scrollPageUp" />
-      <handler event="keypress" key=" " command="cmd_scrollPageDown" />
-
-      <handler event="keypress" keycode="VK_LEFT" command="cmd_moveLeft"/>
-      <handler event="keypress" keycode="VK_RIGHT" command="cmd_moveRight"/>
-      <handler event="keypress" keycode="VK_LEFT" modifiers="shift" command="cmd_selectLeft"/>
-      <handler event="keypress" keycode="VK_RIGHT" modifiers="shift" command="cmd_selectRight"/>
-
-      <handler event="keypress" keycode="VK_UP" command="cmd_moveUp"/>
-      <handler event="keypress" keycode="VK_DOWN" command="cmd_moveDown"/>
-      <handler event="keypress" keycode="VK_UP" modifiers="shift" command="cmd_selectUp"/>
-      <handler event="keypress" keycode="VK_DOWN" modifiers="shift" command="cmd_selectDown"/>
-
-      <handler event="keypress" key="z" command="cmd_undo" modifiers="accel"/>
-      <handler event="keypress" key="z" command="cmd_redo" modifiers="accel,shift" />
-      <handler event="keypress" key="x" command="cmd_cut" modifiers="accel"/>
-      <handler event="keypress" key="c" command="cmd_copy" modifiers="accel"/>
-      <handler event="keypress" key="v" command="cmd_paste" modifiers="accel"/>
-      <handler event="keypress" key="v" command="cmd_pasteNoFormatting" modifiers="accel,shift"/>
new file mode 100644
--- /dev/null
+++ b/dom/xbl/builtin/emacs/ShortcutKeyDefinitions.cpp
@@ -0,0 +1,164 @@
+/* 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 "../ShortcutKeys.h"
+
+namespace mozilla {
+
+ShortcutKeyData ShortcutKeys::sInputHandlers[] =
+{
+#include "../ShortcutKeyDefinitionsForInputCommon.h"
+
+    { u"keypress", nullptr,         u"a",    u"control",         u"cmd_beginLine" },
+    { u"keypress", nullptr,         u"e",    u"control",         u"cmd_endLine" },
+    { u"keypress", nullptr,         u"b",    u"control",         u"cmd_charPrevious" },
+    { u"keypress", nullptr,         u"f",    u"control",         u"cmd_charNext" },
+    { u"keypress", nullptr,         u"h",    u"control",         u"cmd_deleteCharBackward" },
+    { u"keypress", nullptr,         u"d",    u"control",         u"cmd_deleteCharForward" },
+    { u"keypress", nullptr,         u"w",    u"control",         u"cmd_deleteWordBackward" },
+    { u"keypress", nullptr,         u"u",    u"control",         u"cmd_deleteToBeginningOfLine" },
+    { u"keypress", nullptr,         u"k",    u"control",         u"cmd_deleteToEndOfLine" },
+    { u"keypress", u"VK_DELETE",    nullptr, u"shift",           u"cmd_cutOrDelete" },
+    { u"keypress", u"VK_DELETE",    nullptr, u"control",         u"cmd_copyOrDelete" },
+    { u"keypress", u"VK_INSERT",    nullptr, u"control",         u"cmd_copy" },
+    { u"keypress", u"VK_INSERT",    nullptr, u"shift",           u"cmd_paste" },
+    { u"keypress", u"VK_HOME",      nullptr, nullptr,            u"cmd_beginLine" },
+    { u"keypress", u"VK_END",       nullptr, nullptr,            u"cmd_endLine" },
+    { u"keypress", u"VK_HOME",      nullptr, u"shift",           u"cmd_selectBeginLine" },
+    { u"keypress", u"VK_END",       nullptr, u"shift",           u"cmd_selectEndLine" },
+    { u"keypress", u"VK_HOME",      nullptr, u"control",         u"cmd_beginLine" },
+    { u"keypress", u"VK_END",       nullptr, u"control",         u"cmd_endLine" },
+    { u"keypress", u"VK_HOME",      nullptr, u"control,shift",   u"cmd_selectBeginLine" },
+    { u"keypress", u"VK_END",       nullptr, u"control,shift",   u"cmd_selectEndLine" },
+    { u"keypress", u"VK_BACK",      nullptr, u"control",         u"cmd_deleteWordBackward" },
+    { u"keypress", u"VK_LEFT",      nullptr, u"control",         u"cmd_wordPrevious" },
+    { u"keypress", u"VK_RIGHT",     nullptr, u"control",         u"cmd_wordNext" },
+    { u"keypress", u"VK_LEFT",      nullptr, u"shift,control",   u"cmd_selectWordPrevious" },
+    { u"keypress", u"VK_RIGHT",     nullptr, u"shift,control",   u"cmd_selectWordNext" },
+    { u"keypress", nullptr,         u"y",    u"accel",           u"cmd_redo" },
+    { u"keypress", nullptr,         u"a",    u"alt",             u"cmd_selectAll" },
+
+    { nullptr,     nullptr,         nullptr, nullptr,            nullptr }
+};
+
+ShortcutKeyData ShortcutKeys::sTextAreaHandlers[] =
+{
+#include "../ShortcutKeyDefinitionsForTextAreaCommon.h"
+
+    { u"keypress", nullptr,         u"a",    u"control",         u"cmd_beginLine" },
+    { u"keypress", nullptr,         u"e",    u"control",         u"cmd_endLine" },
+    { u"keypress", nullptr,         u"b",    u"control",         u"cmd_charPrevious" },
+    { u"keypress", nullptr,         u"f",    u"control",         u"cmd_charNext" },
+    { u"keypress", nullptr,         u"h",    u"control",         u"cmd_deleteCharBackward" },
+    { u"keypress", nullptr,         u"d",    u"control",         u"cmd_deleteCharForward" },
+    { u"keypress", nullptr,         u"w",    u"control",         u"cmd_deleteWordBackward" },
+    { u"keypress", nullptr,         u"u",    u"control",         u"cmd_deleteToBeginningOfLine" },
+    { u"keypress", nullptr,         u"k",    u"control",         u"cmd_deleteToEndOfLine" },
+    { u"keypress", u"VK_DELETE",    nullptr, u"shift",           u"cmd_cutOrDelete" },
+    { u"keypress", u"VK_DELETE",    nullptr, u"control",         u"cmd_copyOrDelete" },
+    { u"keypress", u"VK_INSERT",    nullptr, u"control",         u"cmd_copy" },
+    { u"keypress", u"VK_INSERT",    nullptr, u"shift",           u"cmd_paste" },
+    { u"keypress", nullptr,         u"n",    u"control",         u"cmd_lineNext" },
+    { u"keypress", nullptr,         u"p",    u"control",         u"cmd_linePrevious" },
+    { u"keypress", u"VK_HOME",      nullptr, nullptr,            u"cmd_beginLine" },
+    { u"keypress", u"VK_END",       nullptr, nullptr,            u"cmd_endLine" },
+    { u"keypress", u"VK_HOME",      nullptr, u"shift",           u"cmd_selectBeginLine" },
+    { u"keypress", u"VK_END",       nullptr, u"shift",           u"cmd_selectEndLine" },
+    { u"keypress", u"VK_HOME",      nullptr, u"control",         u"cmd_moveTop" },
+    { u"keypress", u"VK_END",       nullptr, u"control",         u"cmd_moveBottom" },
+    { u"keypress", u"VK_HOME",      nullptr, u"shift,control",   u"cmd_selectTop" },
+    { u"keypress", u"VK_END",       nullptr, u"shift,control",   u"cmd_selectBottom" },
+    { u"keypress", u"VK_PAGE_UP",   nullptr, nullptr,            u"cmd_movePageUp" },
+    { u"keypress", u"VK_PAGE_DOWN", nullptr, nullptr,            u"cmd_movePageDown" },
+    { u"keypress", u"VK_PAGE_UP",   nullptr, u"shift",           u"cmd_selectPageUp" },
+    { u"keypress", u"VK_PAGE_DOWN", nullptr, u"shift",           u"cmd_selectPageDown" },
+    { u"keypress", u"VK_LEFT",      nullptr, u"control",         u"cmd_wordPrevious" },
+    { u"keypress", u"VK_RIGHT",     nullptr, u"control",         u"cmd_wordNext" },
+    { u"keypress", u"VK_LEFT",      nullptr, u"shift,control",   u"cmd_selectWordPrevious" },
+    { u"keypress", u"VK_RIGHT",     nullptr, u"shift,control",   u"cmd_selectWordNext" },
+    { u"keypress", u"VK_BACK",      nullptr, u"control",         u"cmd_deleteWordBackward" },
+    { u"keypress", nullptr,         u"y",    u"accel",           u"cmd_redo" },
+    { u"keypress", nullptr,         u"a",    u"alt",             u"cmd_selectAll" },
+
+    { nullptr,     nullptr,         nullptr, nullptr,            nullptr }
+};
+
+ShortcutKeyData ShortcutKeys::sBrowserHandlers[] =
+{
+#include "../ShortcutKeyDefinitionsForBrowserCommon.h"
+
+    { u"keypress", u"VK_PAGE_UP",   nullptr, nullptr,            u"cmd_movePageUp" },
+    { u"keypress", u"VK_PAGE_DOWN", nullptr, nullptr,            u"cmd_movePageDown" },
+    { u"keypress", u"VK_PAGE_UP",   nullptr, u"shift",           u"cmd_selectPageUp" },
+    { u"keypress", u"VK_PAGE_DOWN", nullptr, u"shift",           u"cmd_selectPageDown" },
+    { u"keypress", u"VK_DELETE",    nullptr, u"shift",           u"cmd_cut" },
+    { u"keypress", u"VK_DELETE",    nullptr, u"control",         u"cmd_copy" },
+    { u"keypress", u"VK_INSERT",    nullptr, u"control",         u"cmd_copy" },
+    { u"keypress", u"VK_HOME",      nullptr, nullptr,            u"cmd_beginLine" },
+    { u"keypress", u"VK_END",       nullptr, nullptr,            u"cmd_endLine" },
+    { u"keypress", u"VK_HOME",      nullptr, u"control",         u"cmd_moveTop" },
+    { u"keypress", u"VK_END",       nullptr, u"control",         u"cmd_moveBottom" },
+    { u"keypress", u"VK_HOME",      nullptr, u"shift,control",   u"cmd_selectTop" },
+    { u"keypress", u"VK_END",       nullptr, u"shift,control",   u"cmd_selectBottom" },
+    { u"keypress", u"VK_LEFT",      nullptr, u"control",         u"cmd_wordPrevious" },
+    { u"keypress", u"VK_RIGHT",     nullptr, u"control",         u"cmd_wordNext" },
+    { u"keypress", u"VK_LEFT",      nullptr, u"control,shift",   u"cmd_selectWordPrevious" },
+    { u"keypress", u"VK_RIGHT",     nullptr, u"control,shift",   u"cmd_selectWordNext" },
+    { u"keypress", u"VK_LEFT",      nullptr, u"shift",           u"cmd_selectCharPrevious" },
+    { u"keypress", u"VK_RIGHT",     nullptr, u"shift",           u"cmd_selectCharNext" },
+    { u"keypress", u"VK_HOME",      nullptr, u"shift",           u"cmd_selectBeginLine" },
+    { u"keypress", u"VK_END",       nullptr, u"shift",           u"cmd_selectEndLine" },
+    { u"keypress", u"VK_UP",        nullptr, u"shift",           u"cmd_selectLinePrevious" },
+    { u"keypress", u"VK_DOWN",      nullptr, u"shift",           u"cmd_selectLineNext" },
+    { u"keypress", nullptr,         u"a",    u"alt",             u"cmd_selectAll" },
+
+    { nullptr,     nullptr,         nullptr, nullptr,            nullptr }
+};
+
+ShortcutKeyData ShortcutKeys::sEditorHandlers[] =
+{
+#include "../ShortcutKeyDefinitionsForEditorCommon.h"
+
+    { u"keypress", nullptr,         u"h",    u"control",         u"cmd_deleteCharBackward" },
+    { u"keypress", nullptr,         u"d",    u"control",         u"cmd_deleteCharForward" },
+    { u"keypress", nullptr,         u"k",    u"control",         u"cmd_deleteToEndOfLine" },
+    { u"keypress", nullptr,         u"u",    u"control",         u"cmd_deleteToBeginningOfLine" },
+    { u"keypress", nullptr,         u"a",    u"control",         u"cmd_beginLine" },
+    { u"keypress", nullptr,         u"e",    u"control",         u"cmd_endLine" },
+    { u"keypress", nullptr,         u"b",    u"control",         u"cmd_charPrevious" },
+    { u"keypress", nullptr,         u"f",    u"control",         u"cmd_charNext" },
+    { u"keypress", nullptr,         u"p",    u"control",         u"cmd_linePrevious" },
+    { u"keypress", nullptr,         u"n",    u"control",         u"cmd_lineNext" },
+    { u"keypress", nullptr,         u"x",    u"control",         u"cmd_cut" },
+    { u"keypress", nullptr,         u"c",    u"control",         u"cmd_copy" },
+    { u"keypress", nullptr,         u"v",    u"control",         u"cmd_paste" },
+    { u"keypress", nullptr,         u"z",    u"control",         u"cmd_undo" },
+    { u"keypress", nullptr,         u"y",    u"accel",           u"cmd_redo" },
+    { u"keypress", nullptr,         u"a",    u"alt",             u"cmd_selectAll" },
+    { u"keypress", u"VK_DELETE",    nullptr, u"shift",           u"cmd_cutOrDelete" },
+    { u"keypress", u"VK_DELETE",    nullptr, u"control",         u"cmd_copyOrDelete" },
+    { u"keypress", u"VK_INSERT",    nullptr, u"control",         u"cmd_copy" },
+    { u"keypress", u"VK_INSERT",    nullptr, u"shift",           u"cmd_paste" },
+    { u"keypress", u"VK_LEFT",      nullptr, u"control",         u"cmd_wordPrevious" },
+    { u"keypress", u"VK_RIGHT",     nullptr, u"control",         u"cmd_wordNext" },
+    { u"keypress", u"VK_LEFT",      nullptr, u"shift,control",   u"cmd_selectWordPrevious" },
+    { u"keypress", u"VK_RIGHT",     nullptr, u"shift,control",   u"cmd_selectWordNext" },
+    { u"keypress", u"VK_BACK",      nullptr, u"control",         u"cmd_deleteWordBackward" },
+    { u"keypress", u"VK_HOME",      nullptr, nullptr,            u"cmd_beginLine" },
+    { u"keypress", u"VK_END",       nullptr, nullptr,            u"cmd_endLine" },
+    { u"keypress", u"VK_HOME",      nullptr, u"shift",           u"cmd_selectBeginLine" },
+    { u"keypress", u"VK_END",       nullptr, u"shift",           u"cmd_selectEndLine" },
+    { u"keypress", u"VK_HOME",      nullptr, u"shift,control",   u"cmd_selectTop" },
+    { u"keypress", u"VK_END",       nullptr, u"shift,control",   u"cmd_selectBottom" },
+    { u"keypress", u"VK_HOME",      nullptr, u"control",         u"cmd_moveTop" },
+    { u"keypress", u"VK_END",       nullptr, u"control",         u"cmd_moveBottom" },
+    { u"keypress", u"VK_PAGE_UP",   nullptr, nullptr,            u"cmd_movePageUp" },
+    { u"keypress", u"VK_PAGE_DOWN", nullptr, nullptr,            u"cmd_movePageDown" },
+    { u"keypress", u"VK_PAGE_UP",   nullptr, u"shift",           u"cmd_selectPageUp" },
+    { u"keypress", u"VK_PAGE_DOWN", nullptr, u"shift",           u"cmd_selectPageDown" },
+
+    { nullptr,     nullptr,         nullptr, nullptr,            nullptr }
+};
+
+} // namespace mozilla
deleted file mode 100644
--- a/dom/xbl/builtin/emacs/jar.mn
+++ /dev/null
@@ -1,6 +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/.
-
-toolkit.jar:
-* content/global/platformHTMLBindings.xml   (platformHTMLBindings.xml)
--- a/dom/xbl/builtin/emacs/moz.build
+++ b/dom/xbl/builtin/emacs/moz.build
@@ -1,7 +1,9 @@
 # -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
 # vim: set filetype=python:
 # This Source Code Form is subject to the terms of the Mozilla Public
 # License, v. 2.0. If a copy of the MPL was not distributed with this
 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
 
-JAR_MANIFESTS += ['jar.mn']
\ No newline at end of file
+SOURCES += ['ShortcutKeyDefinitions.cpp']
+
+FINAL_LIBRARY = 'xul'
deleted file mode 100644
--- a/dom/xbl/builtin/emacs/platformHTMLBindings.xml
+++ /dev/null
@@ -1,237 +0,0 @@
-<?xml version="1.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/. -->
-
-
-<bindings id="htmlBindings"
-   xmlns="http://www.mozilla.org/xbl"
-   xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
- 
-  <binding id="inputFields" bindToUntrustedContent="true">
-    <handlers>
-#include ../input-fields-base.inc
-    <!-- Emacsish single-line motion and delete keys -->
-    <handler event="keypress" key="a" modifiers="control"
-        command="cmd_beginLine"/>
-    <handler event="keypress" key="e" modifiers="control"
-        command="cmd_endLine"/>
-    <handler event="keypress" key="b" modifiers="control"
-        command="cmd_charPrevious"/>
-    <handler event="keypress" key="f" modifiers="control"
-        command="cmd_charNext"/>
-    <handler event="keypress" key="h" modifiers="control"
-        command="cmd_deleteCharBackward"/>
-    <handler event="keypress" key="d" modifiers="control"
-        command="cmd_deleteCharForward"/>
-    <handler event="keypress" key="w" modifiers="control"
-        command="cmd_deleteWordBackward"/>
-    <handler event="keypress" key="u" modifiers="control"
-        command="cmd_deleteToBeginningOfLine"/>
-    <handler event="keypress" key="k" modifiers="control"
-        command="cmd_deleteToEndOfLine"/>
-
-    <!-- Alternate Windows copy/paste/undo/redo keys -->
-    <handler event="keypress" keycode="VK_DELETE" modifiers="shift"
-        command="cmd_cutOrDelete"/>
-    <handler event="keypress" keycode="VK_DELETE" modifiers="control"
-        command="cmd_copyOrDelete"/>
-    <handler event="keypress" keycode="VK_INSERT" modifiers="control" 
-        command="cmd_copy"/>
-    <handler event="keypress" keycode="VK_INSERT" modifiers="shift"
-        command="cmd_paste"/>
-
-    <!-- navigating by word keys -->
-    <handler event="keypress" keycode="VK_HOME" 
-        command="cmd_beginLine"/>
-    <handler event="keypress" keycode="VK_END" 
-        command="cmd_endLine"/>
-    <handler event="keypress" keycode="VK_HOME" modifiers="shift"
-        command="cmd_selectBeginLine"/>
-    <handler event="keypress" keycode="VK_END" modifiers="shift"
-        command="cmd_selectEndLine"/>
-    <handler event="keypress" keycode="VK_HOME" modifiers="control"
-        command="cmd_beginLine"/>
-    <handler event="keypress" keycode="VK_END" modifiers="control" 
-        command="cmd_endLine"/>
-    <handler event="keypress" keycode="VK_HOME" modifiers="control,shift"
-        command="cmd_selectBeginLine"/>
-    <handler event="keypress" keycode="VK_END" modifiers="control,shift"
-        command="cmd_selectEndLine"/>
-    <handler event="keypress" keycode="VK_BACK" modifiers="control"
-        command="cmd_deleteWordBackward"/>
-
-    <handler event="keypress" keycode="VK_LEFT" modifiers="control" 
-        command="cmd_wordPrevious"/>
-    <handler event="keypress" keycode="VK_RIGHT" modifiers="control"
-        command="cmd_wordNext"/>
-    <handler event="keypress" keycode="VK_LEFT" modifiers="shift,control" 
-        command="cmd_selectWordPrevious"/>
-    <handler event="keypress" keycode="VK_RIGHT" modifiers="shift,control" 
-        command="cmd_selectWordNext"/>
-    <handler event="keypress" key="y" modifiers="accel"
-        command="cmd_redo"/>
-    <handler event="keypress" key="a" modifiers="alt"
-        command="cmd_selectAll"/>
-    </handlers>
-  </binding>
-
-  <binding id="textAreas" bindToUntrustedContent="true">
-    <handlers>
-#include ../textareas-base.inc
-    <!-- Emacsish single-line motion and delete keys -->
-    <handler event="keypress" key="a" modifiers="control"
-        command="cmd_beginLine"/>
-    <handler event="keypress" key="e" modifiers="control"
-        command="cmd_endLine"/>
-    <handler event="keypress" id="key_left" key="b" modifiers="control"
-        command="cmd_charPrevious"/>
-    <handler event="keypress" id="key_right" key="f" modifiers="control"
-        command="cmd_charNext"/>
-    <handler event="keypress" id="key_delback" key="h" modifiers="control"
-        command="cmd_deleteCharBackward"/>
-    <handler event="keypress" id="key_delforw" key="d" modifiers="control"
-        command="cmd_deleteCharForward"/>
-    <handler event="keypress" id="key_delwback" key="w" modifiers="control"
-        command="cmd_deleteWordBackward"/>
-    <handler event="keypress" id="key_del_bol" key="u" modifiers="control"
-        command="cmd_deleteToBeginningOfLine"/>
-    <handler event="keypress" id="key_del_eol" key="k" modifiers="control"
-        command="cmd_deleteToEndOfLine"/>
-
-    <!-- Alternate Windows copy/paste/undo/redo keys -->
-    <handler event="keypress" keycode="VK_DELETE" modifiers="shift"
-        command="cmd_cutOrDelete"/>
-    <handler event="keypress" keycode="VK_DELETE" modifiers="control"
-        command="cmd_copyOrDelete"/>
-    <handler event="keypress" keycode="VK_INSERT" modifiers="control"
-        command="cmd_copy"/>
-    <handler event="keypress" keycode="VK_INSERT" modifiers="shift"
-        command="cmd_paste"/>
-
-    <!-- Emacsish multi-line motion and delete keys -->
-    <handler event="keypress" id="key_linedown" key="n" modifiers="control"
-        command="cmd_lineNext"/>
-    <handler event="keypress" id="key_lineup" key="p" modifiers="control"
-        command="cmd_linePrevious"/>
-
-    <!-- handle home/end/arrow keys and redo -->
-    <handler event="keypress" keycode="VK_HOME" 
-        command="cmd_beginLine"/>
-    <handler event="keypress" keycode="VK_END" 
-        command="cmd_endLine"/>
-    <handler event="keypress" keycode="VK_HOME" modifiers="shift"
-        command="cmd_selectBeginLine"/>
-    <handler event="keypress" keycode="VK_END" modifiers="shift"
-        command="cmd_selectEndLine"/>
-
-    <handler event="keypress" keycode="VK_HOME" modifiers="control" 
-        command="cmd_moveTop"/>
-    <handler event="keypress" keycode="VK_END" modifiers="control" 
-        command="cmd_moveBottom"/>
-    <handler event="keypress" keycode="VK_HOME" modifiers="shift,control"
-        command="cmd_selectTop"/>
-    <handler event="keypress" keycode="VK_END" modifiers="shift,control"
-        command="cmd_selectBottom"/>
-
-    <handler event="keypress" keycode="VK_PAGE_UP" 
-        command="cmd_movePageUp"/>
-    <handler event="keypress" keycode="VK_PAGE_DOWN" 
-        command="cmd_movePageDown"/>
-    <handler event="keypress" keycode="VK_PAGE_UP" modifiers="shift"
-        command="cmd_selectPageUp"/>
-    <handler event="keypress" keycode="VK_PAGE_DOWN" modifiers="shift"
-        command="cmd_selectPageDown"/>
-
-    <handler event="keypress" keycode="VK_LEFT" modifiers="control" 
-        command="cmd_wordPrevious"/>
-    <handler event="keypress" keycode="VK_RIGHT" modifiers="control" 
-        command="cmd_wordNext"/>
-    <handler event="keypress" keycode="VK_LEFT" modifiers="shift,control" 
-        command="cmd_selectWordPrevious"/>
-    <handler event="keypress" keycode="VK_RIGHT" modifiers="shift,control" 
-        command="cmd_selectWordNext"/>
-    <handler event="keypress" keycode="VK_BACK" modifiers="control"
-        command="cmd_deleteWordBackward"/>
-    <handler event="keypress" key="y" modifiers="accel"
-        command="cmd_redo"/>
-    <handler event="keypress" key="a" modifiers="alt"
-        command="cmd_selectAll"/>
-    </handlers>
-  </binding>
-
-  <binding id="browser">
-    <handlers>
-#include ../browser-base.inc
-      <handler event="keypress" keycode="VK_PAGE_UP" command="cmd_movePageUp"/>
-      <handler event="keypress" keycode="VK_PAGE_DOWN" command="cmd_movePageDown"/>
-      <handler event="keypress" keycode="VK_PAGE_UP" modifiers="shift" command="cmd_selectPageUp"/>
-      <handler event="keypress" keycode="VK_PAGE_DOWN" modifiers="shift" command="cmd_selectPageDown"/>
-
-      <handler event="keypress" keycode="VK_DELETE" modifiers="shift" command="cmd_cut" /> 
-      <handler event="keypress" keycode="VK_DELETE" modifiers="control" command="cmd_copy" /> 
-      <handler event="keypress" keycode="VK_INSERT" modifiers="control" command="cmd_copy" /> 
-      <handler event="keypress" keycode="VK_HOME" command="cmd_beginLine"/>
-      <handler event="keypress" keycode="VK_END" command="cmd_endLine"/>
-      <handler event="keypress" keycode="VK_HOME" modifiers="control" command="cmd_moveTop"/>
-      <handler event="keypress" keycode="VK_END" modifiers="control" command="cmd_moveBottom"/>
-      <handler event="keypress" keycode="VK_HOME" modifiers="shift,control" command="cmd_selectTop" /> 
-      <handler event="keypress" keycode="VK_END" modifiers="shift,control" command="cmd_selectBottom" /> 
-
-      <handler event="keypress" keycode="VK_LEFT" modifiers="control" command="cmd_wordPrevious" />
-      <handler event="keypress" keycode="VK_RIGHT" modifiers="control" command="cmd_wordNext" />
-      <handler event="keypress" keycode="VK_LEFT" modifiers="control,shift" command="cmd_selectWordPrevious" />
-      <handler event="keypress" keycode="VK_RIGHT" modifiers="control,shift" command="cmd_selectWordNext" />
-      <handler event="keypress" keycode="VK_LEFT" modifiers="shift" command="cmd_selectCharPrevious" />
-      <handler event="keypress" keycode="VK_RIGHT" modifiers="shift" command="cmd_selectCharNext" />
-      <handler event="keypress" keycode="VK_HOME" modifiers="shift" command="cmd_selectBeginLine" />
-      <handler event="keypress" keycode="VK_END" modifiers="shift" command="cmd_selectEndLine" />
-      <handler event="keypress" keycode="VK_UP" modifiers="shift" command="cmd_selectLinePrevious" />
-      <handler event="keypress" keycode="VK_DOWN" modifiers="shift" command="cmd_selectLineNext" />
-      <handler event="keypress" key="a" modifiers="alt" command="cmd_selectAll"/>
-    </handlers>
-  </binding>
-
-  <binding id="editor">
-    <handlers>
-#include ../editor-base.inc
-      <handler event="keypress" key="h" modifiers="control" command="cmd_deleteCharBackward"/>
-      <handler event="keypress" key="d" modifiers="control" command="cmd_deleteCharForward"/>
-      <handler event="keypress" key="k" modifiers="control" command="cmd_deleteToEndOfLine"/>
-      <handler event="keypress" key="u" modifiers="control" command="cmd_deleteToBeginningOfLine"/>
-      <handler event="keypress" key="a" modifiers="control" command="cmd_beginLine"/>
-      <handler event="keypress" key="e" modifiers="control" command="cmd_endLine"/>
-      <handler event="keypress" key="b" modifiers="control" command="cmd_charPrevious"/>
-      <handler event="keypress" key="f" modifiers="control" command="cmd_charNext"/>
-      <handler event="keypress" key="p" modifiers="control" command="cmd_linePrevious"/>
-      <handler event="keypress" key="n" modifiers="control" command="cmd_lineNext"/>
-      <handler event="keypress" key="x" modifiers="control" command="cmd_cut"/>
-      <handler event="keypress" key="c" modifiers="control" command="cmd_copy"/>
-      <handler event="keypress" key="v" modifiers="control" command="cmd_paste"/>
-      <handler event="keypress" key="z" modifiers="control" command="cmd_undo"/>
-      <handler event="keypress" key="y" modifiers="accel"   command="cmd_redo"/>
-      <handler event="keypress" key="a" modifiers="alt" command="cmd_selectAll"/>
-      <handler event="keypress" keycode="VK_DELETE" modifiers="shift" command="cmd_cutOrDelete"/>
-      <handler event="keypress" keycode="VK_DELETE" modifiers="control" command="cmd_copyOrDelete"/>
-      <handler event="keypress" keycode="VK_INSERT" modifiers="control" command="cmd_copy"/>
-      <handler event="keypress" keycode="VK_INSERT" modifiers="shift" command="cmd_paste"/>
-      <handler event="keypress" keycode="VK_LEFT" modifiers="control" command="cmd_wordPrevious"/>
-      <handler event="keypress" keycode="VK_RIGHT" modifiers="control" command="cmd_wordNext"/>
-      <handler event="keypress" keycode="VK_LEFT" modifiers="shift,control" command="cmd_selectWordPrevious"/>
-      <handler event="keypress" keycode="VK_RIGHT" modifiers="shift,control" command="cmd_selectWordNext"/>
-      <handler event="keypress" keycode="VK_BACK" modifiers="control" command="cmd_deleteWordBackward"/>
-      <handler event="keypress" keycode="VK_HOME" command="cmd_beginLine"/>
-      <handler event="keypress" keycode="VK_END" command="cmd_endLine"/>
-      <handler event="keypress" keycode="VK_HOME" modifiers="shift" command="cmd_selectBeginLine"/>
-      <handler event="keypress" keycode="VK_END" modifiers="shift" command="cmd_selectEndLine"/>
-      <handler event="keypress" keycode="VK_HOME" modifiers="shift,control" command="cmd_selectTop"/>
-      <handler event="keypress" keycode="VK_END" modifiers="shift,control" command="cmd_selectBottom"/>
-      <handler event="keypress" keycode="VK_HOME" modifiers="control" command="cmd_moveTop"/>
-      <handler event="keypress" keycode="VK_END" modifiers="control" command="cmd_moveBottom"/>
-      <handler event="keypress" keycode="VK_PAGE_UP" command="cmd_movePageUp"/>
-      <handler event="keypress" keycode="VK_PAGE_DOWN" command="cmd_movePageDown"/>
-      <handler event="keypress" keycode="VK_PAGE_UP" modifiers="shift" command="cmd_selectPageUp"/>
-      <handler event="keypress" keycode="VK_PAGE_DOWN" modifiers="shift" command="cmd_selectPageDown"/>
-    </handlers>
-  </binding>
-</bindings>
deleted file mode 100644
--- a/dom/xbl/builtin/input-fields-base.inc
+++ /dev/null
@@ -1,17 +0,0 @@
-    <handler event="keypress" keycode="VK_LEFT" command="cmd_moveLeft"/>
-    <handler event="keypress" keycode="VK_RIGHT" command="cmd_moveRight"/>
-    <handler event="keypress" keycode="VK_LEFT" modifiers="shift" command="cmd_selectLeft"/>
-    <handler event="keypress" keycode="VK_RIGHT" modifiers="shift" command="cmd_selectRight"/>
-
-    <handler event="keypress" keycode="VK_UP" command="cmd_moveUp"/>
-    <handler event="keypress" keycode="VK_DOWN" command="cmd_moveDown"/>
-    <handler event="keypress" keycode="VK_UP" modifiers="shift" command="cmd_selectUp"/>
-    <handler event="keypress" keycode="VK_DOWN" modifiers="shift" command="cmd_selectDown"/>
-
-    <!-- Cut/copy/paste/undo -->
-    <handler event="keypress" key="c" modifiers="accel" command="cmd_copy"/>
-    <handler event="keypress" key="x" modifiers="accel" command="cmd_cut"/>
-    <handler event="keypress" key="v" modifiers="accel" command="cmd_paste"/>
-    <handler event="keypress" key="z" modifiers="accel" command="cmd_undo"/>
-    <handler event="keypress" key="z" modifiers="accel,shift" command="cmd_redo" />
-
new file mode 100644
--- /dev/null
+++ b/dom/xbl/builtin/mac/ShortcutKeyDefinitions.cpp
@@ -0,0 +1,73 @@
+/* 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 "../ShortcutKeys.h"
+
+namespace mozilla {
+
+ShortcutKeyData ShortcutKeys::sInputHandlers[] =
+{
+    { u"keypress", nullptr,         u"c",    u"accel",           u"cmd_copy" },
+    { u"keypress", nullptr,         u"x",    u"accel",           u"cmd_cut" },
+    { u"keypress", nullptr,         u"v",    u"accel",           u"cmd_paste" },
+    { u"keypress", nullptr,         u"z",    u"accel",           u"cmd_undo" },
+    { u"keypress", nullptr,         u"z",    u"accel,shift",     u"cmd_redo" },
+    { u"keypress", nullptr,         u"a",    u"accel",           u"cmd_selectAll" },
+
+    { nullptr,     nullptr,         nullptr, nullptr,            nullptr }
+};
+
+ShortcutKeyData ShortcutKeys::sTextAreaHandlers[] =
+{
+    { u"keypress", nullptr,         u"c",    u"accel",           u"cmd_copy" },
+    { u"keypress", nullptr,         u"x",    u"accel",           u"cmd_cut" },
+    { u"keypress", nullptr,         u"v",    u"accel",           u"cmd_paste" },
+    { u"keypress", nullptr,         u"z",    u"accel",           u"cmd_undo" },
+    { u"keypress", nullptr,         u"z",    u"accel,shift",     u"cmd_redo" },
+    { u"keypress", nullptr,         u"a",    u"accel",           u"cmd_selectAll" },
+
+    { nullptr,     nullptr,         nullptr, nullptr,            nullptr }
+};
+
+ShortcutKeyData ShortcutKeys::sBrowserHandlers[] =
+{
+#include "../ShortcutKeyDefinitionsForBrowserCommon.h"
+
+    { u"keypress", u"VK_PAGE_UP",   nullptr, nullptr,            u"cmd_scrollPageUp" },
+    { u"keypress", u"VK_PAGE_DOWN", nullptr, nullptr,            u"cmd_scrollPageDown" },
+    { u"keypress", u"VK_HOME",      nullptr, nullptr,            u"cmd_scrollTop" },
+    { u"keypress", u"VK_END",       nullptr, nullptr,            u"cmd_scrollBottom" },
+    { u"keypress", u"VK_LEFT",      nullptr, u"alt",             u"cmd_moveLeft2" },
+    { u"keypress", u"VK_RIGHT",     nullptr, u"alt",             u"cmd_moveRight2" },
+    { u"keypress", u"VK_LEFT",      nullptr, u"alt,shift",       u"cmd_selectLeft2" },
+    { u"keypress", u"VK_RIGHT",     nullptr, u"alt,shift",       u"cmd_selectRight2" },
+    { u"keypress", u"VK_LEFT",      nullptr, u"shift",           u"cmd_selectLeft" },
+    { u"keypress", u"VK_RIGHT",     nullptr, u"shift",           u"cmd_selectRight" },
+    { u"keypress", u"VK_UP",        nullptr, u"alt,shift",       u"cmd_selectUp2" },
+    { u"keypress", u"VK_DOWN",      nullptr, u"alt,shift",       u"cmd_selectDown2" },
+    { u"keypress", u"VK_UP",        nullptr, u"shift",           u"cmd_selectUp" },
+    { u"keypress", u"VK_DOWN",      nullptr, u"shift",           u"cmd_selectDown" },
+    { u"keypress", u"VK_UP",        nullptr, u"accel",           u"cmd_moveUp2" },
+    { u"keypress", u"VK_DOWN",      nullptr, u"accel",           u"cmd_moveDown2" },
+
+    { nullptr,     nullptr,         nullptr, nullptr,            nullptr }
+};
+
+ShortcutKeyData ShortcutKeys::sEditorHandlers[] =
+{
+    { u"keypress", nullptr,         u" ",    u"shift",           u"cmd_scrollPageUp" },
+    { u"keypress", nullptr,         u" ",    nullptr,            u"cmd_scrollPageDown" },
+    { u"keypress", nullptr,         u"z",    u"accel",           u"cmd_undo" },
+    { u"keypress", nullptr,         u"z",    u"accel,shift",     u"cmd_redo" },
+    { u"keypress", nullptr,         u"x",    u"accel",           u"cmd_cut" },
+    { u"keypress", nullptr,         u"c",    u"accel",           u"cmd_copy" },
+    { u"keypress", nullptr,         u"v",    u"accel",           u"cmd_paste" },
+    { u"keypress", nullptr,         u"v",    u"accel,shift",     u"cmd_pasteNoFormatting" },
+    { u"keypress", nullptr,         u"a",    u"accel",           u"cmd_selectAll" },
+    { u"keypress", nullptr,         u"v",    u"accel,alt,shift", u"cmd_pasteNoFormatting" },
+
+    { nullptr,     nullptr,         nullptr, nullptr,            nullptr }
+};
+
+} // namespace mozilla
deleted file mode 100644
--- a/dom/xbl/builtin/mac/jar.mn
+++ /dev/null
@@ -1,6 +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/.
-
-toolkit.jar:
-* content/global/platformHTMLBindings.xml   (platformHTMLBindings.xml)
--- a/dom/xbl/builtin/mac/moz.build
+++ b/dom/xbl/builtin/mac/moz.build
@@ -1,7 +1,9 @@
 # -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
 # vim: set filetype=python:
 # This Source Code Form is subject to the terms of the Mozilla Public
 # License, v. 2.0. If a copy of the MPL was not distributed with this
 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
 
-JAR_MANIFESTS += ['jar.mn']
\ No newline at end of file
+SOURCES += ['ShortcutKeyDefinitions.cpp']
+
+FINAL_LIBRARY = 'xul'
deleted file mode 100644
--- a/dom/xbl/builtin/mac/platformHTMLBindings.xml
+++ /dev/null
@@ -1,72 +0,0 @@
-<?xml version="1.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/. -->
-
-
-<bindings id="htmlBindings"
-   xmlns="http://www.mozilla.org/xbl"
-   xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
-
-  <binding id="inputFields" bindToUntrustedContent="true">
-    <handlers>
-      <handler event="keypress" key="c" modifiers="accel" command="cmd_copy"/>
-      <handler event="keypress" key="x" modifiers="accel" command="cmd_cut"/>
-      <handler event="keypress" key="v" modifiers="accel" command="cmd_paste"/>
-      <handler event="keypress" key="z" modifiers="accel" command="cmd_undo"/>
-      <handler event="keypress" key="z" modifiers="accel,shift" command="cmd_redo"/>
-      <handler event="keypress" key="a" modifiers="accel" command="cmd_selectAll"/>
-    </handlers>
-  </binding>
-
-  <binding id="textAreas" bindToUntrustedContent="true">
-    <handlers>
-      <handler event="keypress" key="c" modifiers="accel" command="cmd_copy"/>
-      <handler event="keypress" key="x" modifiers="accel" command="cmd_cut"/>
-      <handler event="keypress" key="v" modifiers="accel" command="cmd_paste"/>
-      <handler event="keypress" key="z" modifiers="accel" command="cmd_undo"/>
-      <handler event="keypress" key="z" modifiers="accel,shift" command="cmd_redo"/>
-      <handler event="keypress" key="a" modifiers="accel" command="cmd_selectAll"/>
-    </handlers>
-  </binding>
-
-  <binding id="browser">
-    <handlers>
-#include ../browser-base.inc
-      <handler event="keypress" keycode="VK_PAGE_UP" command="cmd_scrollPageUp"/>
-      <handler event="keypress" keycode="VK_PAGE_DOWN" command="cmd_scrollPageDown"/>
-      <handler event="keypress" keycode="VK_HOME" command="cmd_scrollTop" /> 
-      <handler event="keypress" keycode="VK_END"  command="cmd_scrollBottom" /> 
-
-      <handler event="keypress" keycode="VK_LEFT" modifiers="alt" command="cmd_moveLeft2" />
-      <handler event="keypress" keycode="VK_RIGHT" modifiers="alt" command="cmd_moveRight2" />
-      <handler event="keypress" keycode="VK_LEFT" modifiers="alt,shift" command="cmd_selectLeft2" />
-      <handler event="keypress" keycode="VK_RIGHT" modifiers="alt,shift" command="cmd_selectRight2" />
-      <handler event="keypress" keycode="VK_LEFT" modifiers="shift" command="cmd_selectLeft" />
-      <handler event="keypress" keycode="VK_RIGHT" modifiers="shift" command="cmd_selectRight" />
-      <handler event="keypress" keycode="VK_UP" modifiers="alt,shift" command="cmd_selectUp2" />
-      <handler event="keypress" keycode="VK_DOWN" modifiers="alt,shift" command="cmd_selectDown2" />
-      <handler event="keypress" keycode="VK_UP" modifiers="shift" command="cmd_selectUp" />
-      <handler event="keypress" keycode="VK_DOWN" modifiers="shift" command="cmd_selectDown" />
-      <handler event="keypress" keycode="VK_UP" modifiers="accel" command="cmd_moveUp2"/>
-      <handler event="keypress" keycode="VK_DOWN" modifiers="accel" command="cmd_moveDown2"/>
-    </handlers>
-  </binding>
-
-  <binding id="editor">
-    <handlers>
-      <handler event="keypress" key=" " modifiers="shift" command="cmd_scrollPageUp" />
-      <handler event="keypress" key=" " command="cmd_scrollPageDown" />
-
-      <handler event="keypress" key="z" command="cmd_undo" modifiers="accel"/>
-      <handler event="keypress" key="z" command="cmd_redo" modifiers="accel,shift" />
-      <handler event="keypress" key="x" command="cmd_cut" modifiers="accel"/>
-      <handler event="keypress" key="c" command="cmd_copy" modifiers="accel"/>
-      <handler event="keypress" key="v" command="cmd_paste" modifiers="accel"/>
-      <handler event="keypress" key="v" command="cmd_pasteNoFormatting" modifiers="accel,shift"/>
-      <handler event="keypress" key="a" command="cmd_selectAll" modifiers="accel"/>
-      <handler event="keypress" key="v" command="cmd_pasteNoFormatting" modifiers="accel,alt,shift"/>
-    </handlers>
-  </binding>
-
-</bindings>
--- a/dom/xbl/builtin/moz.build
+++ b/dom/xbl/builtin/moz.build
@@ -10,8 +10,13 @@ elif CONFIG['MOZ_WIDGET_TOOLKIT'] == 'co
     DIRS += ['mac']
 elif CONFIG['MOZ_WIDGET_TOOLKIT'] == 'android':
     DIRS += ['android']
 elif CONFIG['MOZ_WIDGET_TOOLKIT'] == 'gtk3':
     DIRS += ['unix']
 else:
     DIRS += ['emacs']
 
+EXPORTS.mozilla += ['ShortcutKeys.h']
+
+SOURCES += ['ShortcutKeys.cpp']
+
+FINAL_LIBRARY = 'xul'
deleted file mode 100644
--- a/dom/xbl/builtin/textareas-base.inc
+++ /dev/null
@@ -1,16 +0,0 @@
-    <handler event="keypress" keycode="VK_LEFT" command="cmd_moveLeft"/>
-    <handler event="keypress" keycode="VK_RIGHT" command="cmd_moveRight"/>
-    <handler event="keypress" keycode="VK_LEFT" modifiers="shift" command="cmd_selectLeft"/>
-    <handler event="keypress" keycode="VK_RIGHT" modifiers="shift" command="cmd_selectRight"/>
-
-    <handler event="keypress" keycode="VK_UP" command="cmd_moveUp"/>
-    <handler event="keypress" keycode="VK_DOWN" command="cmd_moveDown"/>
-    <handler event="keypress" keycode="VK_UP" modifiers="shift" command="cmd_selectUp"/>
-    <handler event="keypress" keycode="VK_DOWN" modifiers="shift" command="cmd_selectDown"/>
-
-    <!-- Cut/copy/paste/undo -->
-    <handler event="keypress" key="c" modifiers="accel" command="cmd_copy"/>
-    <handler event="keypress" key="x" modifiers="accel" command="cmd_cut"/>
-    <handler event="keypress" key="v" modifiers="accel" command="cmd_paste"/>
-    <handler event="keypress" key="z" modifiers="accel" command="cmd_undo"/>
-    <handler event="keypress" key="z" modifiers="accel,shift" command="cmd_redo" />
new file mode 100644
--- /dev/null
+++ b/dom/xbl/builtin/unix/ShortcutKeyDefinitions.cpp
@@ -0,0 +1,81 @@
+/* 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 "../ShortcutKeys.h"
+
+namespace mozilla {
+
+ShortcutKeyData ShortcutKeys::sInputHandlers[] =
+{
+#include "../ShortcutKeyDefinitionsForInputCommon.h"
+
+    { u"keypress", nullptr,         u"a",    u"alt",             u"cmd_selectAll" },
+    { u"keypress", nullptr,         u"y",    u"accel",           u"cmd_redo" },
+    { u"keypress", nullptr,         u"z",    u"accel,shift",     u"cmd_redo" },
+    { u"keypress", nullptr,         u"z",    u"accel",           u"cmd_undo" },
+
+    { nullptr,     nullptr,         nullptr, nullptr,            nullptr }
+};
+
+ShortcutKeyData ShortcutKeys::sTextAreaHandlers[] =
+{
+#include "../ShortcutKeyDefinitionsForTextAreaCommon.h"
+
+    { u"keypress", nullptr,         u"a",    u"alt",             u"cmd_selectAll" },
+    { u"keypress", nullptr,         u"y",    u"accel",           u"cmd_redo" },
+    { u"keypress", nullptr,         u"z",    u"accel",           u"cmd_undo" },
+    { u"keypress", nullptr,         u"z",    u"accel,shift",     u"cmd_redo" },
+
+    { nullptr,     nullptr,         nullptr, nullptr,            nullptr }
+};
+
+ShortcutKeyData ShortcutKeys::sBrowserHandlers[] =
+{
+#include "../ShortcutKeyDefinitionsForBrowserCommon.h"
+
+    { u"keypress", u"VK_PAGE_UP",   nullptr, nullptr,            u"cmd_movePageUp" },
+    { u"keypress", u"VK_PAGE_DOWN", nullptr, nullptr,            u"cmd_movePageDown" },
+    { u"keypress", u"VK_PAGE_UP",   nullptr, u"shift",           u"cmd_selectPageUp" },
+    { u"keypress", u"VK_PAGE_DOWN", nullptr, u"shift",           u"cmd_selectPageDown" },
+    { u"keypress", u"VK_DELETE",    nullptr, u"shift",           u"cmd_cut" },
+    { u"keypress", u"VK_DELETE",    nullptr, u"control",         u"cmd_copy" },
+    { u"keypress", u"VK_INSERT",    nullptr, u"control",         u"cmd_copy" },
+    { u"keypress", u"VK_HOME",      nullptr, nullptr,            u"cmd_beginLine" },
+    { u"keypress", u"VK_END",       nullptr, nullptr,            u"cmd_endLine" },
+    { u"keypress", u"VK_HOME",      nullptr, u"shift",           u"cmd_selectBeginLine" },
+    { u"keypress", u"VK_END",       nullptr, u"shift",           u"cmd_selectEndLine" },
+    { u"keypress", u"VK_HOME",      nullptr, u"control",         u"cmd_moveTop" },
+    { u"keypress", u"VK_END",       nullptr, u"control",         u"cmd_moveBottom" },
+    { u"keypress", u"VK_HOME",      nullptr, u"shift,control",   u"cmd_selectTop" },
+    { u"keypress", u"VK_END",       nullptr, u"shift,control",   u"cmd_selectBottom" },
+    { u"keypress", u"VK_LEFT",      nullptr, u"shift",           u"cmd_selectLeft" },
+    { u"keypress", u"VK_RIGHT",     nullptr, u"shift",           u"cmd_selectRight" },
+    { u"keypress", u"VK_LEFT",      nullptr, u"control",         u"cmd_moveLeft2" },
+    { u"keypress", u"VK_RIGHT",     nullptr, u"control",         u"cmd_moveRight2" },
+    { u"keypress", u"VK_LEFT",      nullptr, u"control,shift",   u"cmd_selectLeft2" },
+    { u"keypress", u"VK_RIGHT",     nullptr, u"control,shift",   u"cmd_selectRight2" },
+    { u"keypress", u"VK_UP",        nullptr, u"shift",           u"cmd_selectUp" },
+    { u"keypress", u"VK_DOWN",      nullptr, u"shift",           u"cmd_selectDown" },
+    { u"keypress", u"VK_UP",        nullptr, u"control",         u"cmd_moveUp2" },
+    { u"keypress", u"VK_DOWN",      nullptr, u"control",         u"cmd_moveDown2" },
+    { u"keypress", u"VK_UP",        nullptr, u"control,shift",   u"cmd_selectUp2" },
+    { u"keypress", u"VK_DOWN",      nullptr, u"control,shift",   u"cmd_selectDown2" },
+    { u"keypress", nullptr,         u"a",    u"alt",             u"cmd_selectAll" },
+
+    { nullptr,     nullptr,         nullptr, nullptr,            nullptr }
+};
+
+ShortcutKeyData ShortcutKeys::sEditorHandlers[] =
+{
+#include "../ShortcutKeyDefinitionsForEditorCommon.h"
+
+    { u"keypress", nullptr,         u"z",    u"accel",           u"cmd_undo" },
+    { u"keypress", nullptr,         u"z",    u"accel,shift",     u"cmd_redo" },
+    { u"keypress", nullptr,         u"y",    u"accel",           u"cmd_redo" },
+    { u"keypress", nullptr,         u"a",    u"alt",             u"cmd_selectAll" },
+
+    { nullptr,     nullptr,         nullptr, nullptr,            nullptr }
+};
+
+} // namespace mozilla
deleted file mode 100644
--- a/dom/xbl/builtin/unix/jar.mn
+++ /dev/null
@@ -1,6 +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/.
-
-toolkit.jar:
-* content/global/platformHTMLBindings.xml   (platformHTMLBindings.xml)
--- a/dom/xbl/builtin/unix/moz.build
+++ b/dom/xbl/builtin/unix/moz.build
@@ -1,7 +1,9 @@
 # -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
 # vim: set filetype=python:
 # This Source Code Form is subject to the terms of the Mozilla Public
 # License, v. 2.0. If a copy of the MPL was not distributed with this
 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
 
-JAR_MANIFESTS += ['jar.mn']
\ No newline at end of file
+SOURCES += ['ShortcutKeyDefinitions.cpp']
+
+FINAL_LIBRARY = 'xul'
deleted file mode 100644
--- a/dom/xbl/builtin/unix/platformHTMLBindings.xml
+++ /dev/null
@@ -1,82 +0,0 @@
-<?xml version="1.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/. -->
-
-
-<bindings id="htmlBindings"
-   xmlns="http://www.mozilla.org/xbl"
-   xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
- 
-  <binding id="inputFields" bindToUntrustedContent="true">
-    <handlers>
-#include ../input-fields-base.inc
-    <handler event="keypress" key="a" modifiers="alt"
-        command="cmd_selectAll"/>
-    <handler event="keypress" key="y" modifiers="accel"
-        command="cmd_redo"/>
-    <handler event="keypress" key="z" modifiers="accel,shift" command="cmd_redo"/>
-    <handler event="keypress" key="z" modifiers="accel" command="cmd_undo"/>
-    </handlers>
-  </binding>
-
-  <binding id="textAreas" bindToUntrustedContent="true">
-    <handlers>
-#include ../textareas-base.inc
-    <handler event="keypress" key="a" modifiers="alt"
-        command="cmd_selectAll"/>
-    <handler event="keypress" key="y" modifiers="accel"
-        command="cmd_redo"/>
-    <handler event="keypress" key="z" modifiers="accel" command="cmd_undo"/>
-    <handler event="keypress" key="z" modifiers="accel,shift" command="cmd_redo"/>
-    </handlers>
-  </binding>
-
-  <binding id="browser">
-    <handlers>
-#include ../browser-base.inc
-      <handler event="keypress" keycode="VK_PAGE_UP" command="cmd_movePageUp"/>
-      <handler event="keypress" keycode="VK_PAGE_DOWN" command="cmd_movePageDown"/>
-      <handler event="keypress" keycode="VK_PAGE_UP" modifiers="shift" command="cmd_selectPageUp"/>
-      <handler event="keypress" keycode="VK_PAGE_DOWN" modifiers="shift" command="cmd_selectPageDown"/>
-
-      <handler event="keypress" keycode="VK_DELETE" modifiers="shift" command="cmd_cut" /> 
-      <handler event="keypress" keycode="VK_DELETE" modifiers="control" command="cmd_copy" /> 
-      <handler event="keypress" keycode="VK_INSERT" modifiers="control" command="cmd_copy" /> 
-      <handler event="keypress" keycode="VK_HOME" command="cmd_beginLine"/>
-      <handler event="keypress" keycode="VK_END" command="cmd_endLine"/>
-      <handler event="keypress" keycode="VK_HOME" modifiers="shift" command="cmd_selectBeginLine" />
-      <handler event="keypress" keycode="VK_END" modifiers="shift" command="cmd_selectEndLine" />
-      <handler event="keypress" keycode="VK_HOME" modifiers="control" command="cmd_moveTop"/>
-      <handler event="keypress" keycode="VK_END" modifiers="control" command="cmd_moveBottom"/>
-      <handler event="keypress" keycode="VK_HOME" modifiers="shift,control" command="cmd_selectTop" /> 
-      <handler event="keypress" keycode="VK_END" modifiers="shift,control" command="cmd_selectBottom" /> 
-
-      <handler event="keypress" keycode="VK_LEFT" modifiers="shift" command="cmd_selectLeft" />
-      <handler event="keypress" keycode="VK_RIGHT" modifiers="shift" command="cmd_selectRight" />
-      <handler event="keypress" keycode="VK_LEFT" modifiers="control" command="cmd_moveLeft2" />
-      <handler event="keypress" keycode="VK_RIGHT" modifiers="control" command="cmd_moveRight2" />
-      <handler event="keypress" keycode="VK_LEFT" modifiers="control,shift" command="cmd_selectLeft2" />
-      <handler event="keypress" keycode="VK_RIGHT" modifiers="control,shift" command="cmd_selectRight2" />
-
-      <handler event="keypress" keycode="VK_UP" modifiers="shift" command="cmd_selectUp" />
-      <handler event="keypress" keycode="VK_DOWN" modifiers="shift" command="cmd_selectDown" />
-      <handler event="keypress" keycode="VK_UP" modifiers="control" command="cmd_moveUp2" />
-      <handler event="keypress" keycode="VK_DOWN" modifiers="control" command="cmd_moveDown2" />
-      <handler event="keypress" keycode="VK_UP" modifiers="control,shift" command="cmd_selectUp2" />
-      <handler event="keypress" keycode="VK_DOWN" modifiers="control,shift" command="cmd_selectDown2" />
-
-      <handler event="keypress" key="a" modifiers="alt" command="cmd_selectAll"/>
-    </handlers>
-  </binding>
-
-  <binding id="editor">
-    <handlers>
-#include ../editor-base.inc
-      <handler event="keypress" key="z" modifiers="accel" command="cmd_undo"/>
-      <handler event="keypress" key="z" modifiers="accel,shift" command="cmd_redo"/>
-      <handler event="keypress" key="y" modifiers="accel"   command="cmd_redo"/>
-      <handler event="keypress" key="a" modifiers="alt" command="cmd_selectAll"/>
-    </handlers>
-  </binding>
-</bindings>
new file mode 100644
--- /dev/null
+++ b/dom/xbl/builtin/win/ShortcutKeyDefinitions.cpp
@@ -0,0 +1,152 @@
+/* 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 "../ShortcutKeys.h"
+
+namespace mozilla {
+
+ShortcutKeyData ShortcutKeys::sInputHandlers[] =
+{
+#include "../ShortcutKeyDefinitionsForInputCommon.h"
+
+    { u"keypress", u"VK_HOME",      nullptr, nullptr,            u"cmd_beginLine" },
+    { u"keypress", u"VK_END",       nullptr, nullptr,            u"cmd_endLine" },
+    { u"keypress", u"VK_HOME",      nullptr, u"shift",           u"cmd_selectBeginLine" },
+    { u"keypress", u"VK_END",       nullptr, u"shift",           u"cmd_selectEndLine" },
+    { u"keypress", u"VK_HOME",      nullptr, u"shift,control",   u"cmd_selectTop" },
+    { u"keypress", u"VK_END",       nullptr, u"shift,control",   u"cmd_selectBottom" },
+    { u"keypress", u"VK_HOME",      nullptr, u"control",         u"cmd_moveTop" },
+    { u"keypress", u"VK_END",       nullptr, u"control",         u"cmd_moveBottom" },
+    { u"keypress", u"VK_LEFT",      nullptr, u"control",         u"cmd_moveLeft2" },
+    { u"keypress", u"VK_RIGHT",     nullptr, u"control",         u"cmd_moveRight2" },
+    { u"keypress", u"VK_LEFT",      nullptr, u"shift,control",   u"cmd_selectLeft2" },
+    { u"keypress", u"VK_RIGHT",     nullptr, u"shift,control",   u"cmd_selectRight2" },
+    { u"keypress", u"VK_UP",        nullptr, u"control",         u"cmd_moveUp2" },
+    { u"keypress", u"VK_DOWN",      nullptr, u"control",         u"cmd_moveDown2" },
+    { u"keypress", u"VK_UP",        nullptr, u"shift,control",   u"cmd_selectUp2" },
+    { u"keypress", u"VK_DOWN",      nullptr, u"shift,control",   u"cmd_selectDown2" },
+    { u"keypress", u"VK_DELETE",    nullptr, u"shift",           u"cmd_cutOrDelete" },
+    { u"keypress", u"VK_DELETE",    nullptr, u"control",         u"cmd_deleteWordForward" },
+    { u"keypress", u"VK_INSERT",    nullptr, u"control",         u"cmd_copy" },
+    { u"keypress", u"VK_INSERT",    nullptr, u"shift",           u"cmd_paste" },
+    { u"keypress", u"VK_BACK",      nullptr, u"alt",             u"cmd_undo" },
+    { u"keypress", u"VK_BACK",      nullptr, u"alt,shift",       u"cmd_redo" },
+    { u"keypress", u"VK_BACK",      nullptr, u"control",         u"cmd_deleteWordBackward" },
+    { u"keypress", nullptr,         u"a",    u"accel",           u"cmd_selectAll" },
+    { u"keypress", nullptr,         u"y",    u"accel",           u"cmd_redo" },
+
+    { nullptr,     nullptr,         nullptr, nullptr,            nullptr }
+};
+
+ShortcutKeyData ShortcutKeys::sTextAreaHandlers[] =
+{
+#include "../ShortcutKeyDefinitionsForTextAreaCommon.h"
+
+    { u"keypress", u"VK_HOME",      nullptr, nullptr,            u"cmd_beginLine" },
+    { u"keypress", u"VK_END",       nullptr, nullptr,            u"cmd_endLine" },
+    { u"keypress", u"VK_HOME",      nullptr, u"shift",           u"cmd_selectBeginLine" },
+    { u"keypress", u"VK_END",       nullptr, u"shift",           u"cmd_selectEndLine" },
+    { u"keypress", u"VK_HOME",      nullptr, u"shift,control",   u"cmd_selectTop" },
+    { u"keypress", u"VK_END",       nullptr, u"shift,control",   u"cmd_selectBottom" },
+    { u"keypress", u"VK_HOME",      nullptr, u"control",         u"cmd_moveTop" },
+    { u"keypress", u"VK_END",       nullptr, u"control",         u"cmd_moveBottom" },
+    { u"keypress", u"VK_LEFT",      nullptr, u"control",         u"cmd_moveLeft2" },
+    { u"keypress", u"VK_RIGHT",     nullptr, u"control",         u"cmd_moveRight2" },
+    { u"keypress", u"VK_LEFT",      nullptr, u"shift,control",   u"cmd_selectLeft2" },
+    { u"keypress", u"VK_RIGHT",     nullptr, u"shift,control",   u"cmd_selectRight2" },
+    { u"keypress", u"VK_UP",        nullptr, u"control",         u"cmd_moveUp2" },
+    { u"keypress", u"VK_DOWN",      nullptr, u"control",         u"cmd_moveDown2" },
+    { u"keypress", u"VK_UP",        nullptr, u"shift,control",   u"cmd_selectUp2" },
+    { u"keypress", u"VK_DOWN",      nullptr, u"shift,control",   u"cmd_selectDown2" },
+    { u"keypress", u"VK_PAGE_UP",   nullptr, nullptr,            u"cmd_movePageUp" },
+    { u"keypress", u"VK_PAGE_DOWN", nullptr, nullptr,            u"cmd_movePageDown" },
+    { u"keypress", u"VK_PAGE_UP",   nullptr, u"shift",           u"cmd_selectPageUp" },
+    { u"keypress", u"VK_PAGE_DOWN", nullptr, u"shift",           u"cmd_selectPageDown" },
+    { u"keypress", u"VK_DELETE",    nullptr, u"shift",           u"cmd_cutOrDelete" },
+    { u"keypress", u"VK_DELETE",    nullptr, u"control",         u"cmd_deleteWordForward" },
+    { u"keypress", u"VK_INSERT",    nullptr, u"control",         u"cmd_copy" },
+    { u"keypress", u"VK_INSERT",    nullptr, u"shift",           u"cmd_paste" },
+    { u"keypress", u"VK_BACK",      nullptr, u"alt",             u"cmd_undo" },
+    { u"keypress", u"VK_BACK",      nullptr, u"alt,shift",       u"cmd_redo" },
+    { u"keypress", u"VK_BACK",      nullptr, u"control",         u"cmd_deleteWordBackward" },
+    { u"keypress", nullptr,         u"a",    u"accel",           u"cmd_selectAll" },
+    { u"keypress", nullptr,         u"y",    u"accel",           u"cmd_redo" },
+
+    { nullptr,     nullptr,         nullptr, nullptr,            nullptr }
+};
+
+ShortcutKeyData ShortcutKeys::sBrowserHandlers[] =
+{
+#include "../ShortcutKeyDefinitionsForBrowserCommon.h"
+
+    { u"keypress", u"VK_PAGE_UP",   nullptr, nullptr,            u"cmd_movePageUp" },
+    { u"keypress", u"VK_PAGE_DOWN", nullptr, nullptr,            u"cmd_movePageDown" },
+    { u"keypress", u"VK_PAGE_UP",   nullptr, u"shift",           u"cmd_selectPageUp" },
+    { u"keypress", u"VK_PAGE_DOWN", nullptr, u"shift",           u"cmd_selectPageDown" },
+    { u"keypress", u"VK_DELETE",    nullptr, u"shift",           u"cmd_cut" },
+    { u"keypress", u"VK_DELETE",    nullptr, u"control",         u"cmd_deleteWordForward" },
+    { u"keypress", u"VK_INSERT",    nullptr, u"control",         u"cmd_copy" },
+    { u"keypress", u"VK_HOME",      nullptr, nullptr,            u"cmd_beginLine" },
+    { u"keypress", u"VK_END",       nullptr, nullptr,            u"cmd_endLine" },
+    { u"keypress", u"VK_HOME",      nullptr, u"control",         u"cmd_moveTop" },
+    { u"keypress", u"VK_END",       nullptr, u"control",         u"cmd_moveBottom" },
+    { u"keypress", u"VK_HOME",      nullptr, u"shift,control",   u"cmd_selectTop" },
+    { u"keypress", u"VK_END",       nullptr, u"shift,control",   u"cmd_selectBottom" },
+    { u"keypress", u"VK_LEFT",      nullptr, u"control",         u"cmd_moveLeft2" },
+    { u"keypress", u"VK_RIGHT",     nullptr, u"control",         u"cmd_moveRight2" },
+    { u"keypress", u"VK_LEFT",      nullptr, u"control,shift",   u"cmd_selectLeft2" },
+    { u"keypress", u"VK_RIGHT",     nullptr, u"control,shift",   u"cmd_selectRight2" },
+    { u"keypress", u"VK_LEFT",      nullptr, u"shift",           u"cmd_selectLeft" },
+    { u"keypress", u"VK_RIGHT",     nullptr, u"shift",           u"cmd_selectRight" },
+    { u"keypress", u"VK_UP",        nullptr, u"control",         u"cmd_moveUp2" },
+    { u"keypress", u"VK_DOWN",      nullptr, u"control",         u"cmd_moveDown2" },
+    { u"keypress", u"VK_UP",        nullptr, u"control,shift",   u"cmd_selectUp2" },
+    { u"keypress", u"VK_DOWN",      nullptr, u"control,shift",   u"cmd_selectDown2" },
+    { u"keypress", u"VK_UP",        nullptr, u"shift",           u"cmd_selectUp" },
+    { u"keypress", u"VK_DOWN",      nullptr, u"shift",           u"cmd_selectDown" },
+    { u"keypress", u"VK_HOME",      nullptr, u"shift",           u"cmd_selectBeginLine" },
+    { u"keypress", u"VK_END",       nullptr, u"shift",           u"cmd_selectEndLine" },
+    { u"keypress", nullptr,         u"y",    u"accel",           u"cmd_redo" },
+
+    { nullptr,     nullptr,         nullptr, nullptr,            nullptr }
+};
+
+ShortcutKeyData ShortcutKeys::sEditorHandlers[] =
+{
+#include "../ShortcutKeyDefinitionsForEditorCommon.h"
+
+    { u"keypress", nullptr,         u"a",    u"accel",           u"cmd_selectAll" },
+    { u"keypress", u"VK_DELETE",    nullptr, u"shift",           u"cmd_cutOrDelete" },
+    { u"keypress", u"VK_DELETE",    nullptr, u"control",         u"cmd_deleteWordForward" },
+    { u"keypress", u"VK_INSERT",    nullptr, u"control",         u"cmd_copy" },
+    { u"keypress", u"VK_INSERT",    nullptr, u"shift",           u"cmd_paste" },
+    { u"keypress", u"VK_BACK",      nullptr, u"alt",             u"cmd_undo" },
+    { u"keypress", u"VK_BACK",      nullptr, u"alt,shift",       u"cmd_redo" },
+    { u"keypress", u"VK_LEFT",      nullptr, u"accel",           u"cmd_moveLeft2" },
+    { u"keypress", u"VK_RIGHT",     nullptr, u"accel",           u"cmd_moveRight2" },
+    { u"keypress", u"VK_LEFT",      nullptr, u"shift,accel",     u"cmd_selectLeft2" },
+    { u"keypress", u"VK_RIGHT",     nullptr, u"shift,accel",     u"cmd_selectRight2" },
+    { u"keypress", u"VK_UP",        nullptr, u"accel",           u"cmd_moveUp2" },
+    { u"keypress", u"VK_DOWN",      nullptr, u"accel",           u"cmd_moveDown2" },
+    { u"keypress", u"VK_UP",        nullptr, u"shift,accel",     u"cmd_selectUp2" },
+    { u"keypress", u"VK_DOWN",      nullptr, u"shift,accel",     u"cmd_selectDown2" },
+    { u"keypress", u"VK_HOME",      nullptr, u"shift,control",   u"cmd_selectTop" },
+    { u"keypress", u"VK_END",       nullptr, u"shift,control",   u"cmd_selectBottom" },
+    { u"keypress", u"VK_HOME",      nullptr, u"control",         u"cmd_moveTop" },
+    { u"keypress", u"VK_END",       nullptr, u"control",         u"cmd_moveBottom" },
+    { u"keypress", u"VK_BACK",      nullptr, u"control",         u"cmd_deleteWordBackward" },
+    { u"keypress", u"VK_HOME",      nullptr, nullptr,            u"cmd_beginLine" },
+    { u"keypress", u"VK_END",       nullptr, nullptr,            u"cmd_endLine" },
+    { u"keypress", u"VK_HOME",      nullptr, u"shift",           u"cmd_selectBeginLine" },
+    { u"keypress", u"VK_END",       nullptr, u"shift",           u"cmd_selectEndLine" },
+    { u"keypress", u"VK_PAGE_UP",   nullptr, nullptr,            u"cmd_movePageUp" },
+    { u"keypress", u"VK_PAGE_DOWN", nullptr, nullptr,            u"cmd_movePageDown" },
+    { u"keypress", u"VK_PAGE_UP",   nullptr, u"shift",           u"cmd_selectPageUp" },
+    { u"keypress", u"VK_PAGE_DOWN", nullptr, u"shift",           u"cmd_selectPageDown" },
+    { u"keypress", nullptr,         u"y",    u"accel",           u"cmd_redo" },
+
+    { nullptr,     nullptr,         nullptr, nullptr,            nullptr }
+};
+
+} // namespace mozilla
deleted file mode 100644
--- a/dom/xbl/builtin/win/jar.mn
+++ /dev/null
@@ -1,6 +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/.
-
-toolkit.jar:
-* content/global/platformHTMLBindings.xml   (platformHTMLBindings.xml)
--- a/dom/xbl/builtin/win/moz.build
+++ b/dom/xbl/builtin/win/moz.build
@@ -1,7 +1,9 @@
 # -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
 # vim: set filetype=python:
 # This Source Code Form is subject to the terms of the Mozilla Public
 # License, v. 2.0. If a copy of the MPL was not distributed with this
 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
 
-JAR_MANIFESTS += ['jar.mn']
\ No newline at end of file
+SOURCES += ['ShortcutKeyDefinitions.cpp']
+
+FINAL_LIBRARY = 'xul'
deleted file mode 100644
--- a/dom/xbl/builtin/win/platformHTMLBindings.xml
+++ /dev/null
@@ -1,164 +0,0 @@
-<?xml version="1.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/. -->
-
-
-<bindings id="htmlBindings"
-   xmlns="http://www.mozilla.org/xbl"
-   xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
- 
-  <binding id="inputFields" bindToUntrustedContent="true">
-    <handlers>
-#include ../input-fields-base.inc
-      <handler event="keypress" keycode="VK_HOME" command="cmd_beginLine"/>
-      <handler event="keypress" keycode="VK_END" command="cmd_endLine"/>
-      <handler event="keypress" keycode="VK_HOME" modifiers="shift" command="cmd_selectBeginLine"/>
-      <handler event="keypress" keycode="VK_END" modifiers="shift" command="cmd_selectEndLine"/>
-      <handler event="keypress" keycode="VK_HOME" modifiers="shift,control" command="cmd_selectTop"/>
-      <handler event="keypress" keycode="VK_END" modifiers="shift,control" command="cmd_selectBottom"/>
-      <handler event="keypress" keycode="VK_HOME" modifiers="control" command="cmd_moveTop"/>
-      <handler event="keypress" keycode="VK_END" modifiers="control" command="cmd_moveBottom"/>
-
-      <handler event="keypress" keycode="VK_LEFT" modifiers="control" command="cmd_moveLeft2"/>
-      <handler event="keypress" keycode="VK_RIGHT" modifiers="control" command="cmd_moveRight2"/>
-      <handler event="keypress" keycode="VK_LEFT" modifiers="shift,control" command="cmd_selectLeft2"/>
-      <handler event="keypress" keycode="VK_RIGHT" modifiers="shift,control" command="cmd_selectRight2"/>
-
-      <handler event="keypress" keycode="VK_UP" modifiers="control" command="cmd_moveUp2"/>
-      <handler event="keypress" keycode="VK_DOWN" modifiers="control" command="cmd_moveDown2"/>
-      <handler event="keypress" keycode="VK_UP" modifiers="shift,control" command="cmd_selectUp2"/>
-      <handler event="keypress" keycode="VK_DOWN" modifiers="shift,control" command="cmd_selectDown2"/>
-
-      <handler event="keypress" keycode="VK_DELETE" modifiers="shift" command="cmd_cutOrDelete"/>
-      <handler event="keypress" keycode="VK_DELETE" modifiers="control" command="cmd_deleteWordForward"/>
-      <handler event="keypress" keycode="VK_INSERT" modifiers="control" command="cmd_copy"/>
-      <handler event="keypress" keycode="VK_INSERT" modifiers="shift" command="cmd_paste"/>
-
-      <handler event="keypress" keycode="VK_BACK" modifiers="alt" command="cmd_undo"/>
-      <handler event="keypress" keycode="VK_BACK" modifiers="alt,shift" command="cmd_redo"/>
-      <handler event="keypress" keycode="VK_BACK" modifiers="control" command="cmd_deleteWordBackward"/>
-
-      <handler event="keypress" key="a" modifiers="accel" command="cmd_selectAll"/>
-      <handler event="keypress" key="y" modifiers="accel" command="cmd_redo"/>
-    </handlers>
-  </binding>
-
-  <binding id="textAreas" bindToUntrustedContent="true">
-    <handlers>
-#include ../textareas-base.inc
-      <handler event="keypress" keycode="VK_HOME" command="cmd_beginLine"/>
-      <handler event="keypress" keycode="VK_END" command="cmd_endLine"/>
-      <handler event="keypress" keycode="VK_HOME" modifiers="shift" command="cmd_selectBeginLine"/>
-      <handler event="keypress" keycode="VK_END" modifiers="shift" command="cmd_selectEndLine"/>
-      <handler event="keypress" keycode="VK_HOME" modifiers="shift,control" command="cmd_selectTop"/>
-      <handler event="keypress" keycode="VK_END" modifiers="shift,control" command="cmd_selectBottom"/>
-      <handler event="keypress" keycode="VK_HOME" modifiers="control" command="cmd_moveTop"/>
-      <handler event="keypress" keycode="VK_END" modifiers="control" command="cmd_moveBottom"/>
-
-      <handler event="keypress" keycode="VK_LEFT" modifiers="control" command="cmd_moveLeft2"/>
-      <handler event="keypress" keycode="VK_RIGHT" modifiers="control" command="cmd_moveRight2"/>
-      <handler event="keypress" keycode="VK_LEFT" modifiers="shift,control" command="cmd_selectLeft2"/>
-      <handler event="keypress" keycode="VK_RIGHT" modifiers="shift,control" command="cmd_selectRight2"/>
-
-      <handler event="keypress" keycode="VK_UP" modifiers="control" command="cmd_moveUp2"/>
-      <handler event="keypress" keycode="VK_DOWN" modifiers="control" command="cmd_moveDown2"/>
-      <handler event="keypress" keycode="VK_UP" modifiers="shift,control" command="cmd_selectUp2"/>
-      <handler event="keypress" keycode="VK_DOWN" modifiers="shift,control" command="cmd_selectDown2"/>
-
-      <handler event="keypress" keycode="VK_PAGE_UP" command="cmd_movePageUp"/>
-      <handler event="keypress" keycode="VK_PAGE_DOWN" command="cmd_movePageDown"/>
-      <handler event="keypress" keycode="VK_PAGE_UP" modifiers="shift" command="cmd_selectPageUp"/>
-      <handler event="keypress" keycode="VK_PAGE_DOWN" modifiers="shift" command="cmd_selectPageDown"/>
-
-      <handler event="keypress" keycode="VK_DELETE" modifiers="shift" command="cmd_cutOrDelete"/>
-      <handler event="keypress" keycode="VK_DELETE" modifiers="control" command="cmd_deleteWordForward"/>
-      <handler event="keypress" keycode="VK_INSERT" modifiers="control" command="cmd_copy"/>
-      <handler event="keypress" keycode="VK_INSERT" modifiers="shift" command="cmd_paste"/>
-
-      <handler event="keypress" keycode="VK_BACK" modifiers="alt" command="cmd_undo"/>
-      <handler event="keypress" keycode="VK_BACK" modifiers="alt,shift" command="cmd_redo"/>
-      <handler event="keypress" keycode="VK_BACK" modifiers="control" command="cmd_deleteWordBackward"/>
-
-      <handler event="keypress" key="a" modifiers="accel" command="cmd_selectAll"/>
-      <handler event="keypress" key="y" modifiers="accel" command="cmd_redo"/>
-     </handlers>
-  </binding>
-
-  <binding id="browser">
-    <handlers>
-#include ../browser-base.inc
-      <handler event="keypress" keycode="VK_PAGE_UP" command="cmd_movePageUp"/>
-      <handler event="keypress" keycode="VK_PAGE_DOWN" command="cmd_movePageDown"/>
-      <handler event="keypress" keycode="VK_PAGE_UP" modifiers="shift" command="cmd_selectPageUp"/>
-      <handler event="keypress" keycode="VK_PAGE_DOWN" modifiers="shift" command="cmd_selectPageDown"/>
-
-      <handler event="keypress" keycode="VK_DELETE" modifiers="shift" command="cmd_cut"/>
-      <handler event="keypress" keycode="VK_DELETE" modifiers="control" command="cmd_deleteWordForward"/>
-      <handler event="keypress" keycode="VK_INSERT" modifiers="control" command="cmd_copy"/>
-      <handler event="keypress" keycode="VK_HOME" command="cmd_beginLine"/>
-      <handler event="keypress" keycode="VK_END" command="cmd_endLine"/>
-      <handler event="keypress" keycode="VK_HOME" modifiers="control" command="cmd_moveTop"/>
-      <handler event="keypress" keycode="VK_END" modifiers="control" command="cmd_moveBottom"/>
-      <handler event="keypress" keycode="VK_HOME" modifiers="shift,control" command="cmd_selectTop" />
-      <handler event="keypress" keycode="VK_END" modifiers="shift,control" command="cmd_selectBottom" />
-
-      <handler event="keypress" keycode="VK_LEFT" modifiers="control" command="cmd_moveLeft2" />
-      <handler event="keypress" keycode="VK_RIGHT" modifiers="control" command="cmd_moveRight2" />
-      <handler event="keypress" keycode="VK_LEFT" modifiers="control,shift" command="cmd_selectLeft2" />
-      <handler event="keypress" keycode="VK_RIGHT" modifiers="control,shift" command="cmd_selectRight2" />
-      <handler event="keypress" keycode="VK_LEFT" modifiers="shift" command="cmd_selectLeft" />
-      <handler event="keypress" keycode="VK_RIGHT" modifiers="shift" command="cmd_selectRight" />
-
-      <handler event="keypress" keycode="VK_UP" modifiers="control" command="cmd_moveUp2" />
-      <handler event="keypress" keycode="VK_DOWN" modifiers="control" command="cmd_moveDown2" />
-      <handler event="keypress" keycode="VK_UP" modifiers="control,shift" command="cmd_selectUp2" />
-      <handler event="keypress" keycode="VK_DOWN" modifiers="control,shift" command="cmd_selectDown2" />
-      <handler event="keypress" keycode="VK_UP" modifiers="shift" command="cmd_selectUp" />
-      <handler event="keypress" keycode="VK_DOWN" modifiers="shift" command="cmd_selectDown" />
-
-      <handler event="keypress" keycode="VK_HOME" modifiers="shift" command="cmd_selectBeginLine" />
-      <handler event="keypress" keycode="VK_END" modifiers="shift" command="cmd_selectEndLine" />
-      <handler event="keypress" key="y" modifiers="accel" command="cmd_redo"/>
-    </handlers>
-  </binding>
-
-  <binding id="editor">
-    <handlers>
-#include ../editor-base.inc
-      <handler event="keypress" key="a" command="cmd_selectAll" modifiers="accel"/>
-      <handler event="keypress" keycode="VK_DELETE" modifiers="shift" command="cmd_cutOrDelete"/>
-      <handler event="keypress" keycode="VK_DELETE" modifiers="control" command="cmd_deleteWordForward"/>
-      <handler event="keypress" keycode="VK_INSERT" modifiers="control" command="cmd_copy"/>
-      <handler event="keypress" keycode="VK_INSERT" modifiers="shift" command="cmd_paste"/>
-      <handler event="keypress" keycode="VK_BACK" modifiers="alt" command="cmd_undo"/>
-      <handler event="keypress" keycode="VK_BACK" modifiers="alt,shift" command="cmd_redo"/>
-
-      <handler event="keypress" keycode="VK_LEFT" modifiers="accel" command="cmd_moveLeft2"/>
-      <handler event="keypress" keycode="VK_RIGHT" modifiers="accel" command="cmd_moveRight2"/>
-      <handler event="keypress" keycode="VK_LEFT" modifiers="shift,accel" command="cmd_selectLeft2"/>
-      <handler event="keypress" keycode="VK_RIGHT" modifiers="shift,accel" command="cmd_selectRight2"/>
-
-      <handler event="keypress" keycode="VK_UP" modifiers="accel" command="cmd_moveUp2"/>
-      <handler event="keypress" keycode="VK_DOWN" modifiers="accel" command="cmd_moveDown2"/>
-      <handler event="keypress" keycode="VK_UP" modifiers="shift,accel" command="cmd_selectUp2"/>
-      <handler event="keypress" keycode="VK_DOWN" modifiers="shift,accel" command="cmd_selectDown2"/>
-
-      <handler event="keypress" keycode="VK_HOME" modifiers="shift,control" command="cmd_selectTop"/>
-      <handler event="keypress" keycode="VK_END" modifiers="shift,control" command="cmd_selectBottom"/>
-      <handler event="keypress" keycode="VK_HOME" modifiers="control" command="cmd_moveTop"/>
-      <handler event="keypress" keycode="VK_END" modifiers="control" command="cmd_moveBottom"/>
-      <handler event="keypress" keycode="VK_BACK" modifiers="control" command="cmd_deleteWordBackward"/>
-
-      <handler event="keypress" keycode="VK_HOME" command="cmd_beginLine"/>
-      <handler event="keypress" keycode="VK_END" command="cmd_endLine"/>
-      <handler event="keypress" keycode="VK_HOME" command="cmd_selectBeginLine" modifiers="shift"/>
-      <handler event="keypress" keycode="VK_END" command="cmd_selectEndLine" modifiers="shift"/>
-      <handler event="keypress" keycode="VK_PAGE_UP" command="cmd_movePageUp"/>
-      <handler event="keypress" keycode="VK_PAGE_DOWN" command="cmd_movePageDown"/>
-      <handler event="keypress" keycode="VK_PAGE_UP" modifiers="shift" command="cmd_selectPageUp"/>
-      <handler event="keypress" keycode="VK_PAGE_DOWN" modifiers="shift" command="cmd_selectPageDown"/>
-      <handler event="keypress" key="y" modifiers="accel" command="cmd_redo"/>
-    </handlers>
-  </binding>
-</bindings>
--- a/dom/xbl/moz.build
+++ b/dom/xbl/moz.build
@@ -7,16 +7,17 @@
 with Files("**"):
     BUG_COMPONENT = ("Core", "XBL")
 
 DIRS += ['builtin']
 
 EXPORTS += [
     'nsBindingManager.h',
     'nsXBLBinding.h',
+    'nsXBLPrototypeHandler.h',
     'nsXBLService.h',
     'nsXBLWindowKeyHandler.h',
 ]
 
 EXPORTS.mozilla.dom += [
     'XBLChildrenElement.h',
 ]
 
--- a/dom/xbl/nsXBLPrototypeHandler.cpp
+++ b/dom/xbl/nsXBLPrototypeHandler.cpp
@@ -101,26 +101,41 @@ nsXBLPrototypeHandler::nsXBLPrototypeHan
   Init();
 
   ConstructPrototype(nullptr, aEvent, aPhase, aAction, aCommand, aKeyCode,
                      aCharCode, aModifiers, aButton, aClickCount,
                      aGroup, aPreventDefault, aAllowUntrusted);
 }
 
 nsXBLPrototypeHandler::nsXBLPrototypeHandler(Element* aHandlerElement, XBLReservedKey aReserved)
-  : mHandlerElement(nullptr),
+  : mHandlerElement(nullptr)
+  , mLineNumber(0)
+  , mReserved(aReserved)
+  , mNextHandler(nullptr)
+  , mPrototypeBinding(nullptr)
+{
+  Init();
+
+  // Make sure our prototype is initialized.
+  ConstructPrototype(aHandlerElement);
+}
+
+nsXBLPrototypeHandler::nsXBLPrototypeHandler(ShortcutKeyData* aKeyData)
+  : mHandlerText(nullptr),
     mLineNumber(0),
-    mReserved(aReserved),
+    mReserved(XBLReservedKey_False),
     mNextHandler(nullptr),
     mPrototypeBinding(nullptr)
 {
   Init();
 
-  // Make sure our prototype is initialized.
-  ConstructPrototype(aHandlerElement);
+  ConstructPrototype(nullptr, aKeyData->event, nullptr, nullptr,
+                     aKeyData->command, aKeyData->keycode, aKeyData->key,
+                     aKeyData->modifiers, nullptr, nullptr, nullptr, nullptr,
+                     nullptr);
 }
 
 nsXBLPrototypeHandler::nsXBLPrototypeHandler(nsXBLPrototypeBinding* aBinding)
   : mHandlerText(nullptr),
     mLineNumber(0),
     mPhase(0),
     mType(0),
     mMisc(0),
--- a/dom/xbl/nsXBLPrototypeHandler.h
+++ b/dom/xbl/nsXBLPrototypeHandler.h
@@ -13,16 +13,17 @@
 #include "nsString.h"
 #include "nsCOMPtr.h"
 #include "nsIController.h"
 #include "nsAutoPtr.h"
 #include "nsXBLEventHandler.h"
 #include "nsIWeakReference.h"
 #include "nsCycleCollectionParticipant.h"
 #include "js/TypeDecls.h"
+#include "mozilla/ShortcutKeys.h"
 
 class nsIContent;
 class nsIObjectInputStream;
 class nsIObjectOutputStream;
 class nsXBLPrototypeBinding;
 
 namespace mozilla {
 
@@ -88,16 +89,20 @@ public:
                         const char16_t* aPreventDefault,
                         const char16_t* aAllowUntrusted,
                         nsXBLPrototypeBinding* aBinding,
                         uint32_t aLineNumber);
 
   // This constructor is used only by XUL key handlers (e.g., <key>)
   explicit nsXBLPrototypeHandler(mozilla::dom::Element* aKeyElement, XBLReservedKey aReserved);
 
+  // This constructor is used for keyboard handlers for browser, editor, input
+  // and textarea elements.
+  explicit nsXBLPrototypeHandler(mozilla::ShortcutKeyData* aKeyData);
+
   // This constructor is used for handlers loaded from the cache
   explicit nsXBLPrototypeHandler(nsXBLPrototypeBinding* aBinding);
 
   ~nsXBLPrototypeHandler();
 
   /**
    * Try and convert this XBL handler into an APZ KeyboardShortcut for handling
    * key events on the compositor thread. This only works for XBL handlers that
--- a/dom/xbl/nsXBLWindowKeyHandler.cpp
+++ b/dom/xbl/nsXBLWindowKeyHandler.cpp
@@ -29,141 +29,35 @@
 #include "mozilla/Preferences.h"
 #include "mozilla/StaticPtr.h"
 #include "mozilla/TextEvents.h"
 #include "mozilla/dom/Element.h"
 #include "mozilla/dom/Event.h"
 #include "mozilla/dom/EventBinding.h"
 #include "mozilla/dom/KeyboardEvent.h"
 #include "mozilla/layers/KeyboardMap.h"
+#include "mozilla/ShortcutKeys.h"
 
 using namespace mozilla;
 using namespace mozilla::dom;
 using namespace mozilla::layers;
 
-class nsXBLSpecialDocInfo : public nsIObserver
-{
-public:
-  RefPtr<nsXBLDocumentInfo> mHTMLBindings;
-
-  static const char sHTMLBindingStr[];
-  static const char sUserHTMLBindingStr[];
-
-  bool mInitialized;
-
-public:
-  NS_DECL_ISUPPORTS
-  NS_DECL_NSIOBSERVER
-
-  void LoadDocInfo();
-  void GetHandlers(const nsACString& aRef,
-                   nsXBLPrototypeHandler** handler);
-
-  nsXBLSpecialDocInfo() : mInitialized(false) {}
-
-protected:
-  virtual ~nsXBLSpecialDocInfo() {}
-
-};
-
-const char nsXBLSpecialDocInfo::sHTMLBindingStr[] =
-  "chrome://global/content/platformHTMLBindings.xml";
-
-NS_IMPL_ISUPPORTS(nsXBLSpecialDocInfo, nsIObserver)
-
-NS_IMETHODIMP
-nsXBLSpecialDocInfo::Observe(nsISupports* aSubject,
-                             const char* aTopic,
-                             const char16_t* aData)
-{
-  MOZ_ASSERT(!strcmp(aTopic, "xpcom-shutdown"), "wrong topic");
-
-  // On shutdown, clear our fields to avoid an extra cycle collection.
-  mHTMLBindings = nullptr;
-  mInitialized = false;
-  nsContentUtils::UnregisterShutdownObserver(this);
-
-  return NS_OK;
-}
-
-void nsXBLSpecialDocInfo::LoadDocInfo()
-{
-  if (mInitialized)
-    return;
-  mInitialized = true;
-  nsContentUtils::RegisterShutdownObserver(this);
-
-  nsXBLService* xblService = nsXBLService::GetInstance();
-  if (!xblService)
-    return;
-
-  // Obtain the platform doc info
-  nsCOMPtr<nsIURI> bindingURI;
-  NS_NewURI(getter_AddRefs(bindingURI), sHTMLBindingStr);
-  if (!bindingURI) {
-    return;
-  }
-  xblService->LoadBindingDocumentInfo(nullptr, nullptr,
-                                      bindingURI,
-                                      nullptr,
-                                      true,
-                                      getter_AddRefs(mHTMLBindings));
-}
-
-//
-// GetHandlers
-//
-//
-void
-nsXBLSpecialDocInfo::GetHandlers(const nsACString& aRef,
-                                 nsXBLPrototypeHandler** aHandler)
-{
-  if (mHTMLBindings) {
-    nsXBLPrototypeBinding* binding = mHTMLBindings->GetPrototypeBinding(aRef);
-
-    NS_ASSERTION(binding, "No binding found for the XBL window key handler.");
-    if (!binding)
-      return;
-
-    *aHandler = binding->GetPrototypeHandlers();
-  }
-}
-
-// Init statics
-static StaticRefPtr<nsXBLSpecialDocInfo> sXBLSpecialDocInfo;
-uint32_t nsXBLWindowKeyHandler::sRefCnt = 0;
-
-/* static */ void
-nsXBLWindowKeyHandler::EnsureSpecialDocInfo()
-{
-  if (!sXBLSpecialDocInfo) {
-    sXBLSpecialDocInfo = new nsXBLSpecialDocInfo();
-  }
-  sXBLSpecialDocInfo->LoadDocInfo();
-}
-
 nsXBLWindowKeyHandler::nsXBLWindowKeyHandler(Element* aElement,
                                              EventTarget* aTarget)
   : mTarget(aTarget),
     mHandler(nullptr)
 {
   mWeakPtrForElement = do_GetWeakReference(aElement);
-  ++sRefCnt;
 }
 
 nsXBLWindowKeyHandler::~nsXBLWindowKeyHandler()
 {
   // If mWeakPtrForElement is non-null, we created a prototype handler.
   if (mWeakPtrForElement)
     delete mHandler;
-
-  --sRefCnt;
-  if (!sRefCnt) {
-    sXBLSpecialDocInfo = nullptr;
-  }
 }
 
 NS_IMPL_ISUPPORTS(nsXBLWindowKeyHandler,
                   nsIDOMEventListener)
 
 static void
 BuildHandlerChain(nsIContent* aContent, nsXBLPrototypeHandler** aResult)
 {
@@ -225,32 +119,30 @@ nsXBLWindowKeyHandler::EnsureHandlers()
   NS_ENSURE_STATE(!mWeakPtrForElement || el);
   if (el) {
     // We are actually a XUL <keyset>.
     if (mHandler)
       return NS_OK;
 
     BuildHandlerChain(el, &mHandler);
   } else { // We are an XBL file of handlers.
-    EnsureSpecialDocInfo();
-
     // Now determine which handlers we should be using.
     if (IsHTMLEditableFieldFocused()) {
-      sXBLSpecialDocInfo->GetHandlers(NS_LITERAL_CSTRING("editor"), &mHandler);
+      mHandler = ShortcutKeys::GetHandlers(HandlerType::eEditor);
     }
     else {
-      sXBLSpecialDocInfo->GetHandlers(NS_LITERAL_CSTRING("browser"), &mHandler);
+      mHandler = ShortcutKeys::GetHandlers(HandlerType::eBrowser);
     }
   }
 
   return NS_OK;
 }
 
 nsresult
-nsXBLWindowKeyHandler::WalkHandlers(KeyboardEvent* aKeyEvent, nsAtom* aEventType)
+nsXBLWindowKeyHandler::WalkHandlers(KeyboardEvent* aKeyEvent)
 {
   if (aKeyEvent->DefaultPrevented()) {
     return NS_OK;
   }
 
   // Don't process the event if it was not dispatched from a trusted source
   if (!aKeyEvent->IsTrusted()) {
     return NS_OK;
@@ -262,17 +154,17 @@ nsXBLWindowKeyHandler::WalkHandlers(Keyb
   bool isDisabled;
   nsCOMPtr<Element> el = GetElement(&isDisabled);
 
   // skip keysets that are disabled
   if (el && isDisabled) {
     return NS_OK;
   }
 
-  WalkHandlersInternal(aKeyEvent, aEventType, true);
+  WalkHandlersInternal(aKeyEvent, true);
 
   return NS_OK;
 }
 
 void
 nsXBLWindowKeyHandler::InstallKeyboardEventListenersTo(
                          EventListenerManager* aEventListenerManager)
 {
@@ -391,21 +283,17 @@ nsXBLWindowKeyHandler::RemoveKeyboardEve
   aEventListenerManager->RemoveEventListenerByType(
                            this, NS_LITERAL_STRING("mozkeyuponplugin"),
                            TrustedEventsAtSystemGroupBubble());
 }
 
 /* static */ KeyboardMap
 nsXBLWindowKeyHandler::CollectKeyboardShortcuts()
 {
-  // Load the XBL handlers
-  EnsureSpecialDocInfo();
-
-  nsXBLPrototypeHandler* handlers = nullptr;
-  sXBLSpecialDocInfo->GetHandlers(NS_LITERAL_CSTRING("browser"), &handlers);
+  nsXBLPrototypeHandler* handlers = ShortcutKeys::GetHandlers(HandlerType::eBrowser);
 
   // Convert the handlers into keyboard shortcuts, using an AutoTArray with
   // the maximum amount of shortcuts used on any platform to minimize allocations
   AutoTArray<KeyboardShortcut, 48> shortcuts;
 
   // Append keyboard shortcuts for hardcoded actions like tab
   KeyboardShortcut::AppendHardcodedShortcuts(shortcuts);
 
@@ -416,40 +304,16 @@ nsXBLWindowKeyHandler::CollectKeyboardSh
     if (handler->TryConvertToKeyboardShortcut(&shortcut)) {
       shortcuts.AppendElement(shortcut);
     }
   }
 
   return KeyboardMap(std::move(shortcuts));
 }
 
-nsAtom*
-nsXBLWindowKeyHandler::ConvertEventToDOMEventType(
-                         const WidgetKeyboardEvent& aWidgetKeyboardEvent) const
-{
-  if (aWidgetKeyboardEvent.IsKeyDownOrKeyDownOnPlugin()) {
-    return nsGkAtoms::keydown;
-  }
-  if (aWidgetKeyboardEvent.IsKeyUpOrKeyUpOnPlugin()) {
-    return nsGkAtoms::keyup;
-  }
-  // eAccessKeyNotFound event is always created from eKeyPress event and
-  // the original eKeyPress event has stopped its propagation before dispatched
-  // into the DOM tree in this process and not matched with remote content's
-  // access keys.  So, we should treat it as an eKeyPress event and execute
-  // a command if it's registered as a shortcut key.
-  if (aWidgetKeyboardEvent.mMessage == eKeyPress ||
-      aWidgetKeyboardEvent.mMessage == eAccessKeyNotFound) {
-    return nsGkAtoms::keypress;
-  }
-  MOZ_ASSERT_UNREACHABLE(
-    "All event messages which this instance listens to should be handled");
-  return nullptr;
-}
-
 NS_IMETHODIMP
 nsXBLWindowKeyHandler::HandleEvent(Event* aEvent)
 {
   RefPtr<KeyboardEvent> keyEvent = aEvent->AsKeyboardEvent();
   NS_ENSURE_TRUE(keyEvent, NS_ERROR_INVALID_ARG);
 
   if (aEvent->EventPhase() == Event_Binding::CAPTURING_PHASE) {
     if (aEvent->WidgetEventPtr()->mFlags.mInSystemGroup) {
@@ -487,19 +351,17 @@ nsXBLWindowKeyHandler::HandleEvent(Event
 
   // If this event was handled by APZ then don't do the default action, and
   // preventDefault to prevent any other listeners from handling the event.
   if (widgetKeyboardEvent->mFlags.mHandledByAPZ) {
     aEvent->PreventDefault();
     return NS_OK;
   }
 
-  RefPtr<nsAtom> eventTypeAtom =
-    ConvertEventToDOMEventType(*widgetKeyboardEvent);
-  return WalkHandlers(keyEvent, eventTypeAtom);
+  return WalkHandlers(keyEvent);
 }
 
 void
 nsXBLWindowKeyHandler::HandleEventOnCaptureInDefaultEventGroup(
                          KeyboardEvent* aEvent)
 {
   WidgetKeyboardEvent* widgetKeyboardEvent =
     aEvent->WidgetEventPtr()->AsKeyboardEvent();
@@ -597,65 +459,63 @@ nsXBLWindowKeyHandler::IsHTMLEditableFie
 //
 // Given a particular DOM event and a pointer to the first handler in the list,
 // scan through the list to find something to handle the event. If aExecute = true,
 // the handler will be executed; otherwise just return an answer telling if a handler
 // for that event was found.
 //
 bool
 nsXBLWindowKeyHandler::WalkHandlersInternal(KeyboardEvent* aKeyEvent,
-                                            nsAtom* aEventType,
                                             bool aExecute,
                                             bool* aOutReservedForChrome)
 {
   WidgetKeyboardEvent* nativeKeyboardEvent =
     aKeyEvent->WidgetEventPtr()->AsKeyboardEvent();
   MOZ_ASSERT(nativeKeyboardEvent);
 
   AutoShortcutKeyCandidateArray shortcutKeys;
   nativeKeyboardEvent->GetShortcutKeyCandidates(shortcutKeys);
 
   if (shortcutKeys.IsEmpty()) {
-    return WalkHandlersAndExecute(aKeyEvent, aEventType,
-                                  0, IgnoreModifierState(),
+    return WalkHandlersAndExecute(aKeyEvent, 0, IgnoreModifierState(),
                                   aExecute, aOutReservedForChrome);
   }
 
   for (uint32_t i = 0; i < shortcutKeys.Length(); ++i) {
     ShortcutKeyCandidate& key = shortcutKeys[i];
     IgnoreModifierState ignoreModifierState;
     ignoreModifierState.mShift = key.mIgnoreShift;
-    if (WalkHandlersAndExecute(aKeyEvent, aEventType,
-                               key.mCharCode, ignoreModifierState,
+    if (WalkHandlersAndExecute(aKeyEvent, key.mCharCode, ignoreModifierState,
                                aExecute, aOutReservedForChrome)) {
       return true;
     }
   }
   return false;
 }
 
 bool
 nsXBLWindowKeyHandler::WalkHandlersAndExecute(
                          KeyboardEvent* aKeyEvent,
-                         nsAtom* aEventType,
                          uint32_t aCharCode,
                          const IgnoreModifierState& aIgnoreModifierState,
                          bool aExecute,
                          bool* aOutReservedForChrome)
 {
   if (aOutReservedForChrome) {
     *aOutReservedForChrome = false;
   }
 
   WidgetKeyboardEvent* widgetKeyboardEvent =
     aKeyEvent->WidgetEventPtr()->AsKeyboardEvent();
   if (NS_WARN_IF(!widgetKeyboardEvent)) {
     return false;
   }
 
+  nsAtom* eventType = ShortcutKeys::ConvertEventToDOMEventType(widgetKeyboardEvent);
+
   // Try all of the handlers until we find one that matches the event.
   for (nsXBLPrototypeHandler* handler = mHandler;
        handler;
        handler = handler->GetNextHandler()) {
     bool stopped = aKeyEvent->IsDispatchStopped();
     if (stopped) {
       // The event is finished, don't execute any more handlers
       return false;
@@ -667,33 +527,33 @@ nsXBLWindowKeyHandler::WalkHandlersAndEx
       // never followed by keypress events.
       if (widgetKeyboardEvent->mMessage == eKeyDownOnPlugin) {
         if (!handler->EventTypeEquals(nsGkAtoms::keydown) &&
             !handler->EventTypeEquals(nsGkAtoms::keypress)) {
           continue;
         }
       // The other event types should exactly be matched with the handler's
       // event type.
-      } else if (!handler->EventTypeEquals(aEventType)) {
+      } else if (!handler->EventTypeEquals(eventType)) {
         continue;
       }
     } else {
       if (handler->EventTypeEquals(nsGkAtoms::keypress)) {
         // If the handler is a keypress event handler, we also need to check
         // if coming keydown event is a preceding event of reserved key
         // combination because if default action of a keydown event is
         // prevented, following keypress event won't be fired.  However, if
         // following keypress event is reserved, we shouldn't allow web
         // contents to prevent the default of the preceding keydown event.
-        if (aEventType != nsGkAtoms::keydown &&
-            aEventType != nsGkAtoms::keypress) {
+        if (eventType != nsGkAtoms::keydown &&
+            eventType != nsGkAtoms::keypress) {
           continue;
         }
-      } else if (!handler->EventTypeEquals(aEventType)) {
-        // Otherwise, aEventType should exactly be matched.
+      } else if (!handler->EventTypeEquals(eventType)) {
+        // Otherwise, eventType should exactly be matched.
         continue;
       }
     }
 
     // Check if the keyboard event *may* execute the handler.
     if (!handler->KeyEventMatched(aKeyEvent, aCharCode, aIgnoreModifierState)) {
       continue;  // try the next one
     }
@@ -708,28 +568,28 @@ nsXBLWindowKeyHandler::WalkHandlersAndEx
 
     if (commandElement) {
       if (aExecute && !IsExecutableElement(commandElement)) {
         continue;
       }
     }
 
     if (!aExecute) {
-      if (handler->EventTypeEquals(aEventType)) {
+      if (handler->EventTypeEquals(eventType)) {
         if (aOutReservedForChrome) {
           *aOutReservedForChrome = IsReservedKey(widgetKeyboardEvent, handler);
         }
 
         return true;
       }
 
       // If the command is reserved and the event is keydown, check also if
       // the handler is for keypress because if following keypress event is
       // reserved, we shouldn't dispatch the event into web contents.
-      if (aEventType == nsGkAtoms::keydown &&
+      if (eventType == nsGkAtoms::keydown &&
           handler->EventTypeEquals(nsGkAtoms::keypress)) {
         if (IsReservedKey(widgetKeyboardEvent, handler)) {
           if (aOutReservedForChrome) {
             *aOutReservedForChrome = true;
           }
 
           return true;
         }
@@ -768,18 +628,18 @@ nsXBLWindowKeyHandler::WalkHandlersAndEx
 #ifdef XP_WIN
   // Windows native applications ignore Windows-Logo key state when checking
   // shortcut keys even if the key is pressed.  Therefore, if there is no
   // shortcut key which exactly matches current modifier state, we should
   // retry to look for a shortcut key without the Windows-Logo key press.
   if (!aIgnoreModifierState.mOS && widgetKeyboardEvent->IsOS()) {
     IgnoreModifierState ignoreModifierState(aIgnoreModifierState);
     ignoreModifierState.mOS = true;
-    return WalkHandlersAndExecute(aKeyEvent, aEventType,
-                                  aCharCode, ignoreModifierState, aExecute);
+    return WalkHandlersAndExecute(aKeyEvent, aCharCode, ignoreModifierState,
+                                  aExecute);
   }
 #endif
 
   return false;
 }
 
 bool
 nsXBLWindowKeyHandler::IsReservedKey(WidgetKeyboardEvent* aKeyEvent,
@@ -814,20 +674,17 @@ nsXBLWindowKeyHandler::HasHandlerForEven
   NS_ENSURE_SUCCESS(rv, false);
 
   bool isDisabled;
   nsCOMPtr<Element> el = GetElement(&isDisabled);
   if (el && isDisabled) {
     return false;
   }
 
-  RefPtr<nsAtom> eventTypeAtom =
-    ConvertEventToDOMEventType(*widgetKeyboardEvent);
-  return WalkHandlersInternal(aEvent, eventTypeAtom, false,
-                              aOutReservedForChrome);
+  return WalkHandlersInternal(aEvent, false, aOutReservedForChrome);
 }
 
 already_AddRefed<Element>
 nsXBLWindowKeyHandler::GetElement(bool* aIsDisabled)
 {
   nsCOMPtr<Element> element = do_QueryReferent(mWeakPtrForElement);
   if (element && aIsDisabled) {
     *aIsDisabled = element->AttrValueIs(kNameSpaceID_None, nsGkAtoms::disabled,
--- a/dom/xbl/nsXBLWindowKeyHandler.h
+++ b/dom/xbl/nsXBLWindowKeyHandler.h
@@ -45,27 +45,26 @@ public:
   static KeyboardMap CollectKeyboardShortcuts();
 
   NS_DECL_ISUPPORTS
   NS_DECL_NSIDOMEVENTLISTENER
 
 protected:
   virtual ~nsXBLWindowKeyHandler();
 
-  nsresult WalkHandlers(KeyboardEvent* aKeyEvent, nsAtom* aEventType);
+  nsresult WalkHandlers(KeyboardEvent* aKeyEvent);
 
   // walk the handlers, looking for one to handle the event
   bool WalkHandlersInternal(KeyboardEvent* aKeyEvent,
-                            nsAtom* aEventType,
                             bool aExecute,
                             bool* aOutReservedForChrome = nullptr);
 
   // walk the handlers for aEvent, aCharCode and aIgnoreModifierState. Execute
   // it if aExecute = true.
-  bool WalkHandlersAndExecute(KeyboardEvent* aKeyEvent, nsAtom* aEventType,
+  bool WalkHandlersAndExecute(KeyboardEvent* aKeyEvent,
                               uint32_t aCharCode,
                               const IgnoreModifierState& aIgnoreModifierState,
                               bool aExecute,
                               bool* aOutReservedForChrome = nullptr);
 
   // HandleEvent function for the capturing phase in the default event group.
   void HandleEventOnCaptureInDefaultEventGroup(KeyboardEvent* aEvent);
   // HandleEvent function for the capturing phase in the system event group.
@@ -77,25 +76,16 @@ protected:
   bool HasHandlerForEvent(KeyboardEvent* aEvent,
                           bool* aOutReservedForChrome = nullptr);
 
   // Returns true if the key would be reserved for the given handler. A reserved
   // key is not sent to a content process or single-process equivalent.
   bool IsReservedKey(mozilla::WidgetKeyboardEvent* aKeyEvent,
                      nsXBLPrototypeHandler* aHandler);
 
-  // Returns event type for matching between aWidgetKeyboardEvent and
-  // shortcut key handlers.  This is used for calling WalkHandlers(),
-  // WalkHandlersInternal() and WalkHandlersAndExecute().
-  nsAtom* ConvertEventToDOMEventType(
-             const mozilla::WidgetKeyboardEvent& aWidgetKeyboardEvent) const;
-
-  // lazily load the special doc info for loading handlers
-  static void EnsureSpecialDocInfo();
-
   // lazily load the handlers. Overridden to handle being attached
   // to a particular element rather than the document
   nsresult EnsureHandlers();
 
   // Is an HTML editable element focused
   bool IsHTMLEditableFieldFocused();
 
   // Returns the element which was passed as a parameter to the constructor,
@@ -121,21 +111,16 @@ protected:
    * Otherwise, false. aElement should be a command element or a key element.
    */
   bool IsExecutableElement(mozilla::dom::Element* aElement) const;
 
   // Using weak pointer to the DOM Element.
   nsWeakPtr              mWeakPtrForElement;
   mozilla::dom::EventTarget* mTarget; // weak ref
 
-  // these are not owning references; the prototype handlers are owned
-  // by the prototype bindings which are owned by the docinfo.
   nsXBLPrototypeHandler* mHandler;     // platform bindings
-
-  // holds reference count to document info about bindings
-  static uint32_t sRefCnt;
 };
 
 already_AddRefed<nsXBLWindowKeyHandler>
 NS_NewXBLWindowKeyHandler(mozilla::dom::Element* aElement,
                           mozilla::dom::EventTarget* aTarget);
 
 #endif
--- a/gfx/graphite2/src/direct_machine.cpp
+++ b/gfx/graphite2/src/direct_machine.cpp
@@ -51,16 +51,36 @@ of the License or (at your option) any l
 #define do_(name)               &&name
 
 
 using namespace graphite2;
 using namespace vm;
 
 namespace {
 
+// The GCC manual has this to say about labels as values:
+//   The &&foo expressions for the same label might have different values
+//   if the containing function is inlined or cloned. If a program relies
+//   on them being always the same, __attribute__((__noinline__,__noclone__))
+//   should be used to prevent inlining and cloning.
+//
+// is_return in Code.cpp relies on being able to do comparisons, so it needs
+// them to be always the same.
+//
+// The GCC manual further adds:
+//   If &&foo is used in a static variable initializer, inlining and
+//   cloning is forbidden.
+//
+// In this file, &&foo *is* used in a static variable initializer, and it's not
+// entirely clear whether this should prevent inlining of the function or not.
+// In practice, though, clang 7 can end up inlining the function with ThinLTO,
+// which breaks at least is_return. https://bugs.llvm.org/show_bug.cgi?id=39241
+// So all in all, we need at least the __noinline__ attribute. __noclone__
+// is not supported by clang.
+__attribute__((__noinline__))
 const void * direct_run(const bool          get_table_mode,
                         const instr       * program,
                         const byte        * data,
                         Machine::stack_t  * stack,
                         slotref         * & __map,
                         uint8                _dir,
                         Machine::status_t & status,
                         SlotMap           * __smap=0)
--- a/gfx/ipc/GfxMessageUtils.h
+++ b/gfx/ipc/GfxMessageUtils.h
@@ -3,17 +3,16 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #ifndef __GFXMESSAGEUTILS_H__
 #define __GFXMESSAGEUTILS_H__
 
 #include "FilterSupport.h"
-#include "FrameMetrics.h"
 #include "ImageTypes.h"
 #include "RegionBuilder.h"
 #include "base/process_util.h"
 #include "chrome/common/ipc_message_utils.h"
 #include "gfxFeature.h"
 #include "gfxFallback.h"
 #include "gfxPoint.h"
 #include "gfxRect.h"
--- a/gfx/layers/FrameMetrics.h
+++ b/gfx/layers/FrameMetrics.h
@@ -33,16 +33,18 @@ namespace layers {
 
 /**
  * Helper struct to hold a couple of fields that can be updated as part of
  * an empty transaction.
  */
 struct ScrollUpdateInfo {
   uint32_t mScrollGeneration;
   CSSPoint mScrollOffset;
+  CSSPoint mBaseScrollOffset;
+  bool mIsRelative;
 };
 
 /**
  * The viewport and displayport metrics for the painted frame at the
  * time of a layer-tree transaction.  These metrics are especially
  * useful for shadow layers, because the metrics values are updated
  * atomically with new pixels.
  */
@@ -57,47 +59,45 @@ public:
 
   MOZ_DEFINE_ENUM_WITH_BASE_AT_CLASS_SCOPE(
     ScrollOffsetUpdateType, uint8_t, (
       eNone,          // The default; the scroll offset was not updated
       eMainThread,    // The scroll offset was updated by the main thread.
       ePending,       // The scroll offset was updated on the main thread, but not
                       // painted, so the layer texture data is still at the old
                       // offset.
-      eUserAction,    // In an APZ repaint request, this means the APZ generated
-                      // the scroll position based on user action (the alternative
-                      // is eNone which means it's just request a repaint because
-                      // it got a scroll update from the main thread).
       eRestore        // The scroll offset was updated by the main thread, but as
                       // a restore from history or after a frame reconstruction.
                       // In this case, APZ can ignore the offset change if the
                       // user has done an APZ scroll already.
   ));
 
   FrameMetrics()
     : mScrollId(NULL_SCROLL_ID)
     , mPresShellResolution(1)
     , mCompositionBounds(0, 0, 0, 0)
     , mDisplayPort(0, 0, 0, 0)
     , mCriticalDisplayPort(0, 0, 0, 0)
     , mScrollableRect(0, 0, 0, 0)
     , mCumulativeResolution()
     , mDevPixelsPerCSSPixel(1)
     , mScrollOffset(0, 0)
+    , mBaseScrollOffset(0, 0)
     , mZoom()
     , mScrollGeneration(0)
     , mSmoothScrollOffset(0, 0)
     , mRootCompositionSize(0, 0)
     , mDisplayPortMargins(0, 0, 0, 0)
     , mPresShellId(-1)
     , mViewport(0, 0, 0, 0)
     , mExtraResolution()
     , mPaintRequestTime()
     , mScrollUpdateType(eNone)
     , mIsRootContent(false)
+    , mIsRelative(false)
     , mDoSmoothScroll(false)
     , mUseDisplayPortMargins(false)
     , mIsScrollInfoLayer(false)
   {
   }
 
   // Default copy ctor and operator= are fine
 
@@ -108,27 +108,29 @@ public:
            mPresShellResolution == aOther.mPresShellResolution &&
            mCompositionBounds.IsEqualEdges(aOther.mCompositionBounds) &&
            mDisplayPort.IsEqualEdges(aOther.mDisplayPort) &&
            mCriticalDisplayPort.IsEqualEdges(aOther.mCriticalDisplayPort) &&
            mScrollableRect.IsEqualEdges(aOther.mScrollableRect) &&
            mCumulativeResolution == aOther.mCumulativeResolution &&
            mDevPixelsPerCSSPixel == aOther.mDevPixelsPerCSSPixel &&
            mScrollOffset == aOther.mScrollOffset &&
+           mBaseScrollOffset == aOther.mBaseScrollOffset &&
            // don't compare mZoom
            mScrollGeneration == aOther.mScrollGeneration &&
            mSmoothScrollOffset == aOther.mSmoothScrollOffset &&
            mRootCompositionSize == aOther.mRootCompositionSize &&
            mDisplayPortMargins == aOther.mDisplayPortMargins &&
            mPresShellId == aOther.mPresShellId &&
            mViewport.IsEqualEdges(aOther.mViewport) &&
            mExtraResolution == aOther.mExtraResolution &&
            mPaintRequestTime == aOther.mPaintRequestTime &&
            mScrollUpdateType == aOther.mScrollUpdateType &&
            mIsRootContent == aOther.mIsRootContent &&
+           mIsRelative == aOther.mIsRelative &&
            mDoSmoothScroll == aOther.mDoSmoothScroll &&
            mUseDisplayPortMargins == aOther.mUseDisplayPortMargins &&
            mIsScrollInfoLayer == aOther.mIsScrollInfoLayer;
   }
 
   bool operator!=(const FrameMetrics& aOther) const
   {
     return !operator==(aOther);
@@ -246,39 +248,63 @@ public:
   }
 
   void ZoomBy(const gfxSize& aScale)
   {
     mZoom.xScale *= aScale.width;
     mZoom.yScale *= aScale.height;
   }
 
-  void CopyScrollInfoFrom(const FrameMetrics& aOther)
+  /*
+   * Compares an APZ frame metrics with an incoming content frame metrics
+   * to see if APZ has a scroll offset that has not been incorporated into
+   * the content frame metrics.
+   */
+  bool HasPendingScroll(const FrameMetrics& aContentFrameMetrics) const
+  {
+    return mScrollOffset != aContentFrameMetrics.mBaseScrollOffset;
+  }
+
+  void ApplyScrollUpdateFrom(const FrameMetrics& aOther)
   {
     mScrollOffset = aOther.mScrollOffset;
     mScrollGeneration = aOther.mScrollGeneration;
   }
 
-  void CopySmoothScrollInfoFrom(const FrameMetrics& aOther)
+  void ApplySmoothScrollUpdateFrom(const FrameMetrics& aOther)
   {
     mSmoothScrollOffset = aOther.mSmoothScrollOffset;
     mScrollGeneration = aOther.mScrollGeneration;
     mDoSmoothScroll = aOther.mDoSmoothScroll;
   }
 
+  void ApplyRelativeScrollUpdateFrom(const FrameMetrics& aOther)
+  {
+    MOZ_ASSERT(aOther.IsRelative());
+    CSSPoint delta = (aOther.mScrollOffset - aOther.mBaseScrollOffset);
+    ClampAndSetScrollOffset(mScrollOffset + delta);
+    mScrollGeneration = aOther.mScrollGeneration;
+  }
+
+  void ApplyRelativeSmoothScrollUpdateFrom(const FrameMetrics& aOther)
+  {
+    MOZ_ASSERT(aOther.IsRelative());
+    CSSPoint delta = (aOther.mSmoothScrollOffset - aOther.mBaseScrollOffset);
+    ClampAndSetSmoothScrollOffset(mScrollOffset + delta);
+    mScrollGeneration = aOther.mScrollGeneration;
+    mDoSmoothScroll = aOther.mDoSmoothScroll;
+  }
+
   void UpdatePendingScrollInfo(const ScrollUpdateInfo& aInfo)
   {
     mScrollOffset = aInfo.mScrollOffset;
+    mBaseScrollOffset = aInfo.mBaseScrollOffset;
     mScrollGeneration = aInfo.mScrollGeneration;
     mScrollUpdateType = ePending;
-  }
-
-  void SetRepaintDrivenByUserAction(bool aUserAction)
-  {
-    mScrollUpdateType = aUserAction ? eUserAction : eNone;
+    mIsRelative = aInfo.mIsRelative;
   }
 
 public:
   void SetPresShellResolution(float aPresShellResolution)
   {
     mPresShellResolution = aPresShellResolution;
   }
 
@@ -347,57 +373,70 @@ public:
     return mIsRootContent;
   }
 
   void SetScrollOffset(const CSSPoint& aScrollOffset)
   {
     mScrollOffset = aScrollOffset;
   }
 
+  void SetBaseScrollOffset(const CSSPoint& aScrollOffset)
+  {
+    mBaseScrollOffset = aScrollOffset;
+  }
+
   // Set scroll offset, first clamping to the scroll range.
   void ClampAndSetScrollOffset(const CSSPoint& aScrollOffset)
   {
     SetScrollOffset(CalculateScrollRange().ClampPoint(aScrollOffset));
   }
 
   const CSSPoint& GetScrollOffset() const
   {
     return mScrollOffset;
   }
 
+  const CSSPoint& GetBaseScrollOffset() const
+  {
+    return mBaseScrollOffset;
+  }
+
   void SetSmoothScrollOffset(const CSSPoint& aSmoothScrollDestination)
   {
     mSmoothScrollOffset = aSmoothScrollDestination;
   }
 
+  void ClampAndSetSmoothScrollOffset(const CSSPoint& aSmoothScrollOffset)
+  {
+    SetSmoothScrollOffset(CalculateScrollRange().ClampPoint(aSmoothScrollOffset));
+  }
+
   const CSSPoint& GetSmoothScrollOffset() const
   {
     return mSmoothScrollOffset;
   }
 
   void SetZoom(const CSSToParentLayerScale2D& aZoom)
   {
     mZoom = aZoom;
   }
 
   const CSSToParentLayerScale2D& GetZoom() const
   {
     return mZoom;
   }
 
-  void SetScrollOffsetUpdated(uint32_t aScrollGeneration)
+  void SetScrollGeneration(uint32_t aScrollGeneration)
   {
-    mScrollUpdateType = eMainThread;
     mScrollGeneration = aScrollGeneration;
   }
 
-  void SetScrollOffsetRestored(uint32_t aScrollGeneration)
+  void SetScrollOffsetUpdateType(ScrollOffsetUpdateType aScrollUpdateType)
   {
-    mScrollUpdateType = eRestore;
-    mScrollGeneration = aScrollGeneration;
+    mScrollUpdateType = aScrollUpdateType;
   }
 
   void SetSmoothScrollOffsetUpdated(int32_t aScrollGeneration)
   {
     mDoSmoothScroll = true;
     mScrollGeneration = aScrollGeneration;
   }
 
@@ -406,16 +445,26 @@ public:
     return mScrollUpdateType;
   }
 
   bool GetScrollOffsetUpdated() const
   {
     return mScrollUpdateType != eNone;
   }
 
+  void SetIsRelative(bool aIsRelative)
+  {
+    mIsRelative = aIsRelative;
+  }
+
+  bool IsRelative() const
+  {
+    return mIsRelative;
+  }
+
   bool GetDoSmoothScroll() const
   {
     return mDoSmoothScroll;
   }
 
   uint32_t GetScrollGeneration() const
   {
     return mScrollGeneration;
@@ -646,16 +695,20 @@ private:
   //   width = mCompositionBounds.x / mResolution.scale,
   //   height = mCompositionBounds.y / mResolution.scale }
   // Be within |mScrollableRect|.
   //
   // This is valid for any layer, but is always relative to this frame and
   // not any parents, regardless of parent transforms.
   CSSPoint mScrollOffset;
 
+  // The base scroll offset to use for calculating a relative update to a
+  // scroll offset.
+  CSSPoint mBaseScrollOffset;
+
   // The "user zoom". Content is painted by gecko at mResolution * mDevPixelsPerCSSPixel,
   // but will be drawn to the screen at mZoom. In the steady state, the
   // two will be the same, but during an async zoom action the two may
   // diverge. This information is initialized in Gecko but updated in the APZC.
   CSSToParentLayerScale2D mZoom;
 
   // The scroll generation counter used to acknowledge the scroll offset update.
   uint32_t mScrollGeneration;
@@ -693,16 +746,20 @@ private:
 
   // Whether mScrollOffset was updated by something other than the APZ code, and
   // if the APZC receiving this metrics should update its local copy.
   ScrollOffsetUpdateType mScrollUpdateType;
 
   // Whether or not this is the root scroll frame for the root content document.
   bool mIsRootContent:1;
 
+  // When mIsRelative, the scroll offset was updated using a relative API,
+  // such as `ScrollBy`, and can combined with an async scroll.
+  bool mIsRelative:1;
+
   // When mDoSmoothScroll, the scroll offset should be animated to
   // smoothly transition to mScrollOffset rather than be updated instantly.
   bool mDoSmoothScroll:1;
 
   // If this is true then we use the display port margins on this metrics,
   // otherwise use the display port rect.
   bool mUseDisplayPortMargins:1;
 
new file mode 100644
--- /dev/null
+++ b/gfx/layers/RepaintRequest.h
@@ -0,0 +1,357 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef GFX_REPAINTREQUEST_H
+#define GFX_REPAINTREQUEST_H
+
+#include <stdint.h>                     // for uint8_t, uint32_t, uint64_t
+
+#include "FrameMetrics.h"               // for FrameMetrics
+#include "mozilla/DefineEnum.h"         // for MOZ_DEFINE_ENUM
+#include "mozilla/gfx/BasePoint.h"      // for BasePoint
+#include "mozilla/gfx/Rect.h"           // for RoundedIn
+#include "mozilla/gfx/ScaleFactor.h"    // for ScaleFactor
+#include "mozilla/TimeStamp.h"          // for TimeStamp
+#include "Units.h"                      // for CSSRect, CSSPixel, etc
+
+namespace IPC {
+template <typename T> struct ParamTraits;
+} // namespace IPC
+
+namespace mozilla {
+namespace layers {
+
+struct RepaintRequest {
+  friend struct IPC::ParamTraits<mozilla::layers::RepaintRequest>;
+public:
+
+  MOZ_DEFINE_ENUM_WITH_BASE_AT_CLASS_SCOPE(
+    ScrollOffsetUpdateType, uint8_t, (
+      eNone,             // The default; the scroll offset was not updated.
+      eUserAction        // The scroll offset was updated by APZ.
+  ));
+
+  RepaintRequest()
+    : mScrollId(FrameMetrics::NULL_SCROLL_ID)
+    , mPresShellResolution(1)
+    , mCompositionBounds(0, 0, 0, 0)
+    , mCumulativeResolution()
+    , mDevPixelsPerCSSPixel(1)
+    , mScrollOffset(0, 0)
+    , mZoom()
+    , mScrollGeneration(0)
+    , mDisplayPortMargins(0, 0, 0, 0)
+    , mPresShellId(-1)
+    , mViewport(0, 0, 0, 0)
+    , mExtraResolution()
+    , mPaintRequestTime()
+    , mScrollUpdateType(eNone)
+    , mIsRootContent(false)
+    , mUseDisplayPortMargins(false)
+    , mIsScrollInfoLayer(false)
+  {
+  }
+
+  RepaintRequest(const FrameMetrics& aOther, const ScrollOffsetUpdateType aScrollUpdateType)
+    : mScrollId(aOther.GetScrollId())
+    , mPresShellResolution(aOther.GetPresShellResolution())
+    , mCompositionBounds(aOther.GetCompositionBounds())
+    , mCumulativeResolution(aOther.GetCumulativeResolution())
+    , mDevPixelsPerCSSPixel(aOther.GetDevPixelsPerCSSPixel())
+    , mScrollOffset(aOther.GetScrollOffset())
+    , mZoom(aOther.GetZoom())
+    , mScrollGeneration(aOther.GetScrollGeneration())
+    , mDisplayPortMargins(aOther.GetDisplayPortMargins())
+    , mPresShellId(aOther.GetPresShellId())
+    , mViewport(aOther.GetViewport())
+    , mExtraResolution(aOther.GetExtraResolution())
+    , mPaintRequestTime(aOther.GetPaintRequestTime())
+    , mScrollUpdateType(aScrollUpdateType)
+    , mIsRootContent(aOther.IsRootContent())
+    , mUseDisplayPortMargins(aOther.GetUseDisplayPortMargins())
+    , mIsScrollInfoLayer(aOther.IsScrollInfoLayer())
+  {
+  }
+
+  // Default copy ctor and operator= are fine
+
+  bool operator==(const RepaintRequest& aOther) const
+  {
+    // Put mScrollId at the top since it's the most likely one to fail.
+    return mScrollId == aOther.mScrollId &&
+           mPresShellResolution == aOther.mPresShellResolution &&
+           mCompositionBounds.IsEqualEdges(aOther.mCompositionBounds) &&
+           mCumulativeResolution == aOther.mCumulativeResolution &&
+           mDevPixelsPerCSSPixel == aOther.mDevPixelsPerCSSPixel &&
+           mScrollOffset == aOther.mScrollOffset &&
+           // don't compare mZoom
+           mScrollGeneration == aOther.mScrollGeneration &&
+           mDisplayPortMargins == aOther.mDisplayPortMargins &&
+           mPresShellId == aOther.mPresShellId &&
+           mViewport.IsEqualEdges(aOther.mViewport) &&
+           mExtraResolution == aOther.mExtraResolution &&
+           mPaintRequestTime == aOther.mPaintRequestTime &&
+           mScrollUpdateType == aOther.mScrollUpdateType &&
+           mIsRootContent == aOther.mIsRootContent &&
+           mUseDisplayPortMargins == aOther.mUseDisplayPortMargins &&
+           mIsScrollInfoLayer == aOther.mIsScrollInfoLayer;
+  }
+
+  bool operator!=(const RepaintRequest& aOther) const
+  {
+    return !operator==(aOther);
+  }
+
+  CSSToScreenScale2D DisplayportPixelsPerCSSPixel() const
+  {
+    // Note: use 'mZoom * ParentLayerToLayerScale(1.0f)' as the CSS-to-Layer scale
+    // instead of LayersPixelsPerCSSPixel(), because displayport calculations
+    // are done in the context of a repaint request, where we ask Layout to
+    // repaint at a new resolution that includes any async zoom. Until this
+    // repaint request is processed, LayersPixelsPerCSSPixel() does not yet
+    // include the async zoom, but it will when the displayport is interpreted
+    // for the repaint.
+    return mZoom * ParentLayerToLayerScale(1.0f) / mExtraResolution;
+  }
+
+  CSSToLayerScale2D LayersPixelsPerCSSPixel() const
+  {
+    return mDevPixelsPerCSSPixel * mCumulativeResolution;
+  }
+
+  // Get the amount by which this frame has been zoomed since the last repaint.
+  LayerToParentLayerScale GetAsyncZoom() const
+  {
+    // The async portion of the zoom should be the same along the x and y
+    // axes.
+    return (mZoom / LayersPixelsPerCSSPixel()).ToScaleFactor();
+  }
+
+  CSSSize CalculateCompositedSizeInCssPixels() const
+  {
+    if (GetZoom() == CSSToParentLayerScale2D(0, 0)) {
+      return CSSSize();  // avoid division by zero
+    }
+    return mCompositionBounds.Size() / GetZoom();
+  }
+
+  float GetPresShellResolution() const
+  {
+    return mPresShellResolution;
+  }
+
+  const ParentLayerRect& GetCompositionBounds() const
+  {
+    return mCompositionBounds;
+  }
+
+  const LayoutDeviceToLayerScale2D& GetCumulativeResolution() const
+  {
+    return mCumulativeResolution;
+  }
+
+  const CSSToLayoutDeviceScale& GetDevPixelsPerCSSPixel() const
+  {
+    return mDevPixelsPerCSSPixel;
+  }
+
+  bool IsRootContent() const
+  {
+    return mIsRootContent;
+  }
+
+  const CSSPoint& GetScrollOffset() const
+  {
+    return mScrollOffset;
+  }
+
+  const CSSToParentLayerScale2D& GetZoom() const
+  {
+    return mZoom;
+  }
+
+  ScrollOffsetUpdateType GetScrollUpdateType() const
+  {
+    return mScrollUpdateType;
+  }
+
+  bool GetScrollOffsetUpdated() const
+  {
+    return mScrollUpdateType != eNone;
+  }
+
+  uint32_t GetScrollGeneration() const
+  {
+    return mScrollGeneration;
+  }
+
+  FrameMetrics::ViewID GetScrollId() const
+  {
+    return mScrollId;
+  }
+
+  const ScreenMargin& GetDisplayPortMargins() const
+  {
+    return mDisplayPortMargins;
+  }
+
+  bool GetUseDisplayPortMargins() const
+  {
+    return mUseDisplayPortMargins;
+  }
+
+  uint32_t GetPresShellId() const
+  {
+    return mPresShellId;
+  }
+
+  const CSSRect& GetViewport() const
+  {
+    return mViewport;
+  }
+
+  const ScreenToLayerScale2D& GetExtraResolution() const
+  {
+    return mExtraResolution;
+  }
+
+  const TimeStamp& GetPaintRequestTime() const {
+    return mPaintRequestTime;
+  }
+
+  bool IsScrollInfoLayer() const {
+    return mIsScrollInfoLayer;
+  }
+
+protected:
+  void SetIsRootContent(bool aIsRootContent)
+  {
+    mIsRootContent = aIsRootContent;
+  }
+
+  void SetUseDisplayPortMargins(bool aValue)
+  {
+    mUseDisplayPortMargins = aValue;
+  }
+
+  void SetIsScrollInfoLayer(bool aIsScrollInfoLayer)
+  {
+    mIsScrollInfoLayer = aIsScrollInfoLayer;
+  }
+
+private:
+  // A unique ID assigned to each scrollable frame.
+  FrameMetrics::ViewID mScrollId;
+
+  // The pres-shell resolution that has been induced on the document containing
+  // this scroll frame as a result of zooming this scroll frame (whether via
+  // user action, or choosing an initial zoom level on page load). This can
+  // only be different from 1.0 for frames that are zoomable, which currently
+  // is just the root content document's root scroll frame (mIsRoot = true).
+  // This is a plain float rather than a ScaleFactor because in and of itself
+  // it does not convert between any coordinate spaces for which we have names.
+  float mPresShellResolution;
+
+  // This is the area within the widget that we're compositing to. It is in the
+  // same coordinate space as the reference frame for the scrolled frame.
+  //
+  // This is useful because, on mobile, the viewport and composition dimensions
+  // are not always the same. In this case, we calculate the displayport using
+  // an area bigger than the region we're compositing to. If we used the
+  // viewport dimensions to calculate the displayport, we'd run into situations
+  // where we're prerendering the wrong regions and the content may be clipped,
+  // or too much of it prerendered. If the composition dimensions are the same
+  // as the viewport dimensions, there is no need for this and we can just use
+  // the viewport instead.
+  //
+  // This value is valid for nested scrollable layers as well, and is still
+  // relative to the layer tree origin. This value is provided by Gecko at
+  // layout/paint time.
+  ParentLayerRect mCompositionBounds;
+
+  // The cumulative resolution that the current frame has been painted at.
+  // This is the product of the pres-shell resolutions of the document
+  // containing this scroll frame and its ancestors, and any css-driven
+  // resolution. This information is provided by Gecko at layout/paint time.
+  // Note that this is allowed to have different x- and y-scales, but only
+  // for subframes (mIsRoot = false). (The same applies to other scales that
+  // "inherit" the 2D-ness of this one, such as mZoom.)
+  LayoutDeviceToLayerScale2D mCumulativeResolution;
+
+  // The conversion factor between CSS pixels and device pixels for this frame.
+  // This can vary based on a variety of things, such as reflowing-zoom. The
+  // conversion factor for device pixels to layers pixels is just the
+  // resolution.
+  CSSToLayoutDeviceScale mDevPixelsPerCSSPixel;
+
+  // The position of the top-left of the CSS viewport, relative to the document
+  // (or the document relative to the viewport, if that helps understand it).
+  //
+  // Thus it is relative to the document. It is in the same coordinate space as
+  // |mScrollableRect|, but a different coordinate space than |mViewport| and
+  // |mDisplayPort|.
+  //
+  // It is required that the rect:
+  // { x = mScrollOffset.x, y = mScrollOffset.y,
+  //   width = mCompositionBounds.x / mResolution.scale,
+  //   height = mCompositionBounds.y / mResolution.scale }
+  // Be within |mScrollableRect|.
+  //
+  // This is valid for any layer, but is always relative to this frame and
+  // not any parents, regardless of parent transforms.
+  CSSPoint mScrollOffset;
+
+  // The "user zoom". Content is painted by gecko at mResolution * mDevPixelsPerCSSPixel,
+  // but will be drawn to the screen at mZoom. In the steady state, the
+  // two will be the same, but during an async zoom action the two may
+  // diverge. This information is initialized in Gecko but updated in the APZC.
+  CSSToParentLayerScale2D mZoom;
+
+  // The scroll generation counter used to acknowledge the scroll offset update.
+  uint32_t mScrollGeneration;
+
+  // A display port expressed as layer margins that apply to the rect of what
+  // is drawn of the scrollable element.
+  ScreenMargin mDisplayPortMargins;
+
+  uint32_t mPresShellId;
+
+  // The CSS viewport, which is the dimensions we're using to constrain the
+  // <html> element of this frame, relative to the top-left of the layer. Note
+  // that its offset is structured in such a way that it doesn't depend on the
+  // method layout uses to scroll content.
+  //
+  // This is mainly useful on the root layer, however nested iframes can have
+  // their own viewport, which will just be the size of the window of the
+  // iframe. For layers that don't correspond to a document, this metric is
+  // meaningless and invalid.
+  CSSRect mViewport;
+
+  // The extra resolution at which content in this scroll frame is drawn beyond
+  // that necessary to draw one Layer pixel per Screen pixel.
+  ScreenToLayerScale2D mExtraResolution;
+
+  // The time at which the APZC last requested a repaint for this scrollframe.
+  TimeStamp mPaintRequestTime;
+
+  // The type of repaint request this represents.
+  ScrollOffsetUpdateType mScrollUpdateType;
+
+  // Whether or not this is the root scroll frame for the root content document.
+  bool mIsRootContent:1;
+
+  // If this is true then we use the display port margins on this metrics,
+  // otherwise use the display port rect.
+  bool mUseDisplayPortMargins:1;
+
+  // Whether or not this frame has a "scroll info layer" to capture events.
+  bool mIsScrollInfoLayer:1;
+};
+
+} // namespace layers
+} // namespace mozilla
+
+#endif /* GFX_REPAINTREQUEST_H */
--- a/gfx/layers/apz/public/GeckoContentController.h
+++ b/gfx/layers/apz/public/GeckoContentController.h
@@ -8,39 +8,40 @@
 #define mozilla_layers_GeckoContentController_h
 
 #include "FrameMetrics.h"               // for FrameMetrics, etc
 #include "InputData.h"                  // for PinchGestureInput
 #include "Units.h"                      // for CSSPoint, CSSRect, etc
 #include "mozilla/Assertions.h"         // for MOZ_ASSERT_HELPER2
 #include "mozilla/DefineEnum.h"         // for MOZ_DEFINE_ENUM
 #include "mozilla/EventForwards.h"      // for Modifiers
+#include "mozilla/layers/RepaintRequest.h" // for RepaintRequest
 #include "nsISupportsImpl.h"
 
 namespace mozilla {
 
 class Runnable;
 
 namespace layers {
 
 class GeckoContentController
 {
 public:
   NS_INLINE_DECL_THREADSAFE_REFCOUNTING(GeckoContentController)
 
   /**
-   * Requests a paint of the given FrameMetrics |aFrameMetrics| from Gecko.
+   * Requests a paint of the given RepaintRequest |aRequest| from Gecko.
    * Implementations per-platform are responsible for actually handling this.
    *
    * This method must always be called on the repaint thread, which depends
    * on the GeckoContentController. For ChromeProcessController it is the
    * Gecko main thread, while for RemoteContentController it is the compositor
    * thread where it can send IPDL messages.
    */
-  virtual void RequestContentRepaint(const FrameMetrics& aFrameMetrics) = 0;
+  virtual void RequestContentRepaint(const RepaintRequest& aRequest) = 0;
 
   /**
    * Different types of tap-related events that can be sent in
    * the HandleTap function. The names should be relatively self-explanatory.
    * Note that the eLongTapUp will always be preceded by an eLongTap, but not
    * all eLongTap notifications will be followed by an eLongTapUp (for instance,
    * if the user moves their finger after triggering the long-tap but before
    * lifting it).
--- a/gfx/layers/apz/src/AsyncPanZoomController.cpp
+++ b/gfx/layers/apz/src/AsyncPanZoomController.cpp
@@ -486,16 +486,21 @@ typedef PlatformSpecificStateBase Platfo
  * This controls how long the zoom-to-rect animation takes.\n
  * Units: ms
  *
  * \li\b apz.scale_repaint_delay_ms
  * How long to delay between repaint requests during a scale.
  * A negative number prevents repaint requests during a scale.\n
  * Units: ms
  *
+ * \li\b apz.relative-update.enabled
+ * Whether to enable relative scroll offset updates or not. Relative scroll
+ * offset updates allow APZ and the main thread to concurrently update
+ * the scroll offset and merge the result.
+ *
  */
 
 /**
  * Computed time function used for sampling frames of a zoom to animation.
  */
 StaticAutoPtr<ComputedTimingFunction> gZoomAnimationFunction;
 
 /**
@@ -3377,21 +3382,16 @@ void AsyncPanZoomController::ClampAndSet
 void AsyncPanZoomController::ScrollBy(const CSSPoint& aOffset) {
   SetScrollOffset(Metrics().GetScrollOffset() + aOffset);
 }
 
 void AsyncPanZoomController::ScrollByAndClamp(const CSSPoint& aOffset) {
   ClampAndSetScrollOffset(Metrics().GetScrollOffset() + aOffset);
 }
 
-void AsyncPanZoomController::CopyScrollInfoFrom(const FrameMetrics& aFrameMetrics) {
-  Metrics().CopyScrollInfoFrom(aFrameMetrics);
-  Metrics().RecalculateViewportOffset();
-}
-
 void AsyncPanZoomController::ScaleWithFocus(float aScale,
                                             const CSSPoint& aFocus) {
   Metrics().ZoomBy(aScale);
   // We want to adjust the scroll offset such that the CSS point represented by aFocus remains
   // at the same position on the screen before and after the change in zoom. The below code
   // accomplishes this; see https://bugzilla.mozilla.org/show_bug.cgi?id=923431#c6 for an
   // in-depth explanation of how.
   SetScrollOffset((Metrics().GetScrollOffset() + aFocus) - (aFocus / aScale));
@@ -3592,94 +3592,96 @@ bool AsyncPanZoomController::IsScrollInf
   return Metrics().IsScrollInfoLayer();
 }
 
 int32_t AsyncPanZoomController::GetLastTouchIdentifier() const {
   RefPtr<GestureEventListener> listener = GetGestureEventListener();
   return listener ? listener->GetLastTouchIdentifier() : -1;
 }
 
-void AsyncPanZoomController::RequestContentRepaint(bool aUserAction) {
+void AsyncPanZoomController::RequestContentRepaint(RepaintUpdateType aUpdateType) {
   // Reinvoke this method on the repaint thread if it's not there already. It's
   // important to do this before the call to CalculatePendingDisplayPort, so
   // that CalculatePendingDisplayPort uses the most recent available version of
   // Metrics(). just before the paint request is dispatched to content.
   RefPtr<GeckoContentController> controller = GetGeckoContentController();
   if (!controller) {
     return;
   }
   if (!controller->IsRepaintThread()) {
     // use the local variable to resolve the function overload.
-    auto func = static_cast<void (AsyncPanZoomController::*)(bool)>
+    auto func = static_cast<void (AsyncPanZoomController::*)(RepaintUpdateType)>
         (&AsyncPanZoomController::RequestContentRepaint);
-    controller->DispatchToRepaintThread(NewRunnableMethod<bool>(
+    controller->DispatchToRepaintThread(NewRunnableMethod<RepaintUpdateType>(
       "layers::AsyncPanZoomController::RequestContentRepaint",
       this,
       func,
-      aUserAction));
+      aUpdateType));
     return;
   }
 
   MOZ_ASSERT(controller->IsRepaintThread());
 
   RecursiveMutexAutoLock lock(mRecursiveMutex);
   ParentLayerPoint velocity = GetVelocityVector();
   Metrics().SetDisplayPortMargins(CalculatePendingDisplayPort(Metrics(), velocity));
   Metrics().SetUseDisplayPortMargins(true);
   Metrics().SetPaintRequestTime(TimeStamp::Now());
-  Metrics().SetRepaintDrivenByUserAction(aUserAction);
-  RequestContentRepaint(Metrics(), velocity);
+  RequestContentRepaint(Metrics(), velocity, aUpdateType);
 }
 
 /*static*/ CSSRect
 GetDisplayPortRect(const FrameMetrics& aFrameMetrics)
 {
   // This computation is based on what happens in CalculatePendingDisplayPort. If that
   // changes then this might need to change too
   CSSRect baseRect(aFrameMetrics.GetScrollOffset(),
                    aFrameMetrics.CalculateBoundedCompositedSizeInCssPixels());
   baseRect.Inflate(aFrameMetrics.GetDisplayPortMargins() / aFrameMetrics.DisplayportPixelsPerCSSPixel());
   return baseRect;
 }
 
 void
 AsyncPanZoomController::RequestContentRepaint(const FrameMetrics& aFrameMetrics,
-                                              const ParentLayerPoint& aVelocity)
+                                              const ParentLayerPoint& aVelocity,
+                                              RepaintUpdateType aUpdateType)
 {
   RefPtr<GeckoContentController> controller = GetGeckoContentController();
   if (!controller) {
     return;
   }
   MOZ_ASSERT(controller->IsRepaintThread());
 
+  RepaintRequest request(aFrameMetrics, aUpdateType);
+
   // If we're trying to paint what we already think is painted, discard this
   // request since it's a pointless paint.
   ScreenMargin marginDelta = (mLastPaintRequestMetrics.GetDisplayPortMargins()
-                           - aFrameMetrics.GetDisplayPortMargins());
+                           - request.GetDisplayPortMargins());
   if (fabsf(marginDelta.left) < EPSILON &&
       fabsf(marginDelta.top) < EPSILON &&
       fabsf(marginDelta.right) < EPSILON &&
       fabsf(marginDelta.bottom) < EPSILON &&
       fabsf(mLastPaintRequestMetrics.GetScrollOffset().x -
-            aFrameMetrics.GetScrollOffset().x) < EPSILON &&
+            request.GetScrollOffset().x) < EPSILON &&
       fabsf(mLastPaintRequestMetrics.GetScrollOffset().y -
-            aFrameMetrics.GetScrollOffset().y) < EPSILON &&
-      aFrameMetrics.GetPresShellResolution() == mLastPaintRequestMetrics.GetPresShellResolution() &&
-      aFrameMetrics.GetZoom() == mLastPaintRequestMetrics.GetZoom() &&
-      fabsf(aFrameMetrics.GetViewport().Width() -
+            request.GetScrollOffset().y) < EPSILON &&
+      request.GetPresShellResolution() == mLastPaintRequestMetrics.GetPresShellResolution() &&
+      request.GetZoom() == mLastPaintRequestMetrics.GetZoom() &&
+      fabsf(request.GetViewport().Width() -
             mLastPaintRequestMetrics.GetViewport().Width()) < EPSILON &&
-      fabsf(aFrameMetrics.GetViewport().Height() -
+      fabsf(request.GetViewport().Height() -
             mLastPaintRequestMetrics.GetViewport().Height()) < EPSILON &&
-      fabsf(aFrameMetrics.GetViewport().X() -
+      fabsf(request.GetViewport().X() -
             mLastPaintRequestMetrics.GetViewport().X()) < EPSILON &&
-      fabsf(aFrameMetrics.GetViewport().Y() -
+      fabsf(request.GetViewport().Y() -
             mLastPaintRequestMetrics.GetViewport().Y()) < EPSILON &&
-      aFrameMetrics.GetScrollGeneration() ==
+      request.GetScrollGeneration() ==
             mLastPaintRequestMetrics.GetScrollGeneration() &&
-      aFrameMetrics.GetScrollUpdateType() ==
+      request.GetScrollUpdateType() ==
             mLastPaintRequestMetrics.GetScrollUpdateType()) {
     return;
   }
 
   APZC_LOG_FM(aFrameMetrics, "%p requesting content repaint", this);
   { // scope lock
     MutexAutoLock lock(mCheckerboardEventLock);
     if (mCheckerboardEvent && mCheckerboardEvent->IsRecordingTrace()) {
@@ -3687,21 +3689,19 @@ AsyncPanZoomController::RequestContentRe
       info << " velocity " << aVelocity;
       std::string str = info.str();
       mCheckerboardEvent->UpdateRendertraceProperty(
           CheckerboardEvent::RequestedDisplayPort, GetDisplayPortRect(aFrameMetrics),
           str);
     }
   }
 
-  MOZ_ASSERT(aFrameMetrics.GetScrollUpdateType() == FrameMetrics::eNone ||
-             aFrameMetrics.GetScrollUpdateType() == FrameMetrics::eUserAction);
-  controller->RequestContentRepaint(aFrameMetrics);
+  controller->RequestContentRepaint(request);
   mExpectedGeckoMetrics = aFrameMetrics;
-  mLastPaintRequestMetrics = aFrameMetrics;
+  mLastPaintRequestMetrics = request;
 }
 
 bool AsyncPanZoomController::UpdateAnimation(const TimeStamp& aSampleTime,
                                              nsTArray<RefPtr<Runnable>>* aOutDeferredTasks)
 {
   AssertOnSamplerThread();
 
   // This function may get called multiple with the same sample time, for two
@@ -4255,16 +4255,17 @@ void AsyncPanZoomController::NotifyLayer
 
   bool smoothScrollRequested = aLayerMetrics.GetDoSmoothScroll()
        && (aLayerMetrics.GetScrollGeneration() != Metrics().GetScrollGeneration());
 
   // TODO if we're in a drag and scrollOffsetUpdated is set then we want to
   // ignore it
 
   bool needContentRepaint = false;
+  bool userAction = false;
   bool viewportUpdated = false;
 
   // We usually don't entertain viewport updates on the same transaction as
   // a composition bounds update, but we make an exception for Android
   // to avoid the composition bounds and the viewport diverging during
   // orientation changes and dynamic toolbar transitions.
   // TODO: Do this on all platforms.
   bool entertainViewportUpdates =
@@ -4371,28 +4372,47 @@ void AsyncPanZoomController::NotifyLayer
                       aScrollMetadata.IsAutoDirRootContentRTL());
     mScrollMetadata.SetUsesContainerScrolling(aScrollMetadata.UsesContainerScrolling());
     Metrics().SetIsScrollInfoLayer(aLayerMetrics.IsScrollInfoLayer());
     mScrollMetadata.SetForceDisableApz(aScrollMetadata.IsApzForceDisabled());
     mScrollMetadata.SetDisregardedDirection(aScrollMetadata.GetDisregardedDirection());
     mScrollMetadata.SetOverscrollBehavior(aScrollMetadata.GetOverscrollBehavior());
 
     if (scrollOffsetUpdated) {
-      APZC_LOG("%p updating scroll offset from %s to %s\n", this,
-        ToString(Metrics().GetScrollOffset()).c_str(),
-        ToString(aLayerMetrics.GetScrollOffset()).c_str());
-
       // Send an acknowledgement with the new scroll generation so that any
       // repaint requests later in this function go through.
       // Because of the scroll generation update, any inflight paint requests are
       // going to be ignored by layout, and so mExpectedGeckoMetrics
       // becomes incorrect for the purposes of calculating the LD transform. To
       // correct this we need to update mExpectedGeckoMetrics to be the
       // last thing we know was painted by Gecko.
-      CopyScrollInfoFrom(aLayerMetrics);
+      if (gfxPrefs::APZRelativeUpdate() && aLayerMetrics.IsRelative()) {
+        APZC_LOG("%p relative updating scroll offset from %s by %s\n", this,
+          ToString(Metrics().GetScrollOffset()).c_str(),
+          ToString(aLayerMetrics.GetScrollOffset() - aLayerMetrics.GetBaseScrollOffset()).c_str());
+
+        // It's possible that the main thread has ignored an APZ scroll offset
+        // update for the pending relative scroll that we have just received.
+        // When this happens, we need to send a new scroll offset update with
+        // the combined scroll offset or else the main thread may have an
+        // incorrect scroll offset for a period of time.
+        if (Metrics().HasPendingScroll(aLayerMetrics)) {
+          needContentRepaint = true;
+          userAction = true;
+        }
+
+        Metrics().ApplyRelativeScrollUpdateFrom(aLayerMetrics);
+      } else {
+        APZC_LOG("%p updating scroll offset from %s to %s\n", this,
+          ToString(Metrics().GetScrollOffset()).c_str(),
+          ToString(aLayerMetrics.GetScrollOffset()).c_str());
+        Metrics().ApplyScrollUpdateFrom(aLayerMetrics);
+      }
+      Metrics().RecalculateViewportOffset();
+
       mCompositedLayoutViewport = Metrics().GetViewport();
       mCompositedScrollOffset = Metrics().GetScrollOffset();
       mExpectedGeckoMetrics = aLayerMetrics;
 
       // Cancel the animation (which might also trigger a repaint request)
       // after we update the scroll offset above. Otherwise we can be left
       // in a state where things are out of sync.
       CancelAnimation();
@@ -4423,17 +4443,21 @@ void AsyncPanZoomController::NotifyLayer
 
     APZC_LOG("%p smooth scrolling from %s to %s in state %d\n", this,
       Stringify(Metrics().GetScrollOffset()).c_str(),
       Stringify(aLayerMetrics.GetSmoothScrollOffset()).c_str(),
       mState);
 
     // See comment on the similar code in the |if (scrollOffsetUpdated)| block
     // above.
-    Metrics().CopySmoothScrollInfoFrom(aLayerMetrics);
+    if (gfxPrefs::APZRelativeUpdate() && aLayerMetrics.IsRelative()) {
+      Metrics().ApplyRelativeSmoothScrollUpdateFrom(aLayerMetrics);
+    } else {
+      Metrics().ApplySmoothScrollUpdateFrom(aLayerMetrics);
+    }
     needContentRepaint = true;
     mExpectedGeckoMetrics = aLayerMetrics;
 
     SmoothScrollTo(Metrics().GetSmoothScrollOffset());
   }
 
   if (viewportUpdated) {
     // While we want to accept the main thread's layout viewport _size_,
@@ -4442,17 +4466,21 @@ void AsyncPanZoomController::NotifyLayer
     // visual viewport.
     // Note: it's important to do this _after_ we've accepted any
     // updated composition bounds.
     Metrics().RecalculateViewportOffset();
   }
 
   if (needContentRepaint) {
     // This repaint request is not driven by a user action on the APZ side
-    RequestContentRepaint(false);
+    RepaintUpdateType updateType = RepaintUpdateType::eNone;
+    if (userAction) {
+      updateType = RepaintUpdateType::eUserAction;
+    }
+    RequestContentRepaint(updateType);
   }
   UpdateSharedCompositorFrameMetrics();
 }
 
 FrameMetrics& AsyncPanZoomController::Metrics() {
   return mScrollMetadata.GetMetrics();
 }
 
@@ -4619,35 +4647,35 @@ void AsyncPanZoomController::ZoomToRect(
 
     // Schedule a repaint now, so the new displayport will be painted before the
     // animation finishes.
     ParentLayerPoint velocity(0, 0);
     endZoomToMetrics.SetDisplayPortMargins(
       CalculatePendingDisplayPort(endZoomToMetrics, velocity));
     endZoomToMetrics.SetUseDisplayPortMargins(true);
     endZoomToMetrics.SetPaintRequestTime(TimeStamp::Now());
-    endZoomToMetrics.SetRepaintDrivenByUserAction(true);
 
     RefPtr<GeckoContentController> controller = GetGeckoContentController();
     if (!controller) {
       return;
     }
     if (controller->IsRepaintThread()) {
-      RequestContentRepaint(endZoomToMetrics, velocity);
+      RequestContentRepaint(endZoomToMetrics, velocity, RepaintUpdateType::eUserAction);
     } else {
       // use a local var to resolve the function overload
-      auto func = static_cast<void (AsyncPanZoomController::*)(const FrameMetrics&, const ParentLayerPoint&)>
+      auto func = static_cast<void (AsyncPanZoomController::*)(const FrameMetrics&, const ParentLayerPoint&, RepaintUpdateType)>
           (&AsyncPanZoomController::RequestContentRepaint);
       controller->DispatchToRepaintThread(
-        NewRunnableMethod<FrameMetrics, ParentLayerPoint>(
+        NewRunnableMethod<FrameMetrics, ParentLayerPoint, RepaintUpdateType>(
           "layers::AsyncPanZoomController::ZoomToRect",
           this,
           func,
           endZoomToMetrics,
-          velocity));
+          velocity,
+          RepaintUpdateType::eUserAction));
     }
   }
 }
 
 InputBlockState*
 AsyncPanZoomController::GetCurrentInputBlock() const
 {
   return GetInputQueue()->GetCurrentBlock();
--- a/gfx/layers/apz/src/AsyncPanZoomController.h
+++ b/gfx/layers/apz/src/AsyncPanZoomController.h
@@ -4,16 +4,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 mozilla_layers_AsyncPanZoomController_h
 #define mozilla_layers_AsyncPanZoomController_h
 
 #include "CrossProcessMutex.h"
 #include "mozilla/layers/GeckoContentController.h"
+#include "mozilla/layers/RepaintRequest.h"
 #include "mozilla/Attributes.h"
 #include "mozilla/EventForwards.h"
 #include "mozilla/Monitor.h"
 #include "mozilla/RecursiveMutex.h"
 #include "mozilla/RefPtr.h"
 #include "mozilla/UniquePtr.h"
 #include "mozilla/Atomics.h"
 #include "InputData.h"
@@ -145,16 +146,18 @@ struct AncestorTransform {
  * asynchronously scrolled subframes, we want to have one AsyncPanZoomController
  * per frame.
  */
 class AsyncPanZoomController {
   NS_INLINE_DECL_THREADSAFE_REFCOUNTING(AsyncPanZoomController)
 
   typedef mozilla::MonitorAutoLock MonitorAutoLock;
   typedef mozilla::gfx::Matrix4x4 Matrix4x4;
+  typedef mozilla::layers::RepaintRequest::ScrollOffsetUpdateType
+    RepaintUpdateType;
 
 public:
   enum GestureBehavior {
     // The platform code is responsible for forwarding gesture events here. We
     // will not attempt to generate gesture events from MultiTouchInputs.
     DEFAULT_GESTURES,
     // An instance of GestureEventListener is used to detect gestures. This is
     // handled completely internally within this class.
@@ -702,21 +705,16 @@ protected:
 
   /**
    * Scroll the scroll frame by an X,Y offset, clamping the resulting
    * scroll offset to the scroll range.
    */
   void ScrollByAndClamp(const CSSPoint& aOffset);
 
   /**
-   * Copy the scroll offset and scroll generation from |aFrameMetrics|.
-   */
-  void CopyScrollInfoFrom(const FrameMetrics& aFrameMetrics);
-
-  /**
    * Scales the viewport by an amount (note that it multiplies this scale in to
    * the current scale, it doesn't set it to |aScale|). Also considers a focus
    * point so that the page zooms inward/outward from that point.
    */
   void ScaleWithFocus(float aScale,
                       const CSSPoint& aFocus);
 
   /**
@@ -803,25 +801,27 @@ protected:
 
   /**
    * Utility function to send updated FrameMetrics to Gecko so that it can paint
    * the displayport area. Calls into GeckoContentController to do the actual
    * work. This call will use the current metrics. If this function is called
    * from a non-main thread, it will redispatch itself to the main thread, and
    * use the latest metrics during the redispatch.
    */
-  void RequestContentRepaint(bool aUserAction = true);
+  void RequestContentRepaint(RepaintUpdateType aUpdateType =
+                               RepaintUpdateType::eUserAction);
 
   /**
    * Send the provided metrics to Gecko to trigger a repaint. This function
    * may filter duplicate calls with the same metrics. This function must be
    * called on the main thread.
    */
   void RequestContentRepaint(const FrameMetrics& aFrameMetrics,
-                             const ParentLayerPoint& aVelocity);
+                             const ParentLayerPoint& aVelocity,
+                             RepaintUpdateType aUpdateType);
 
   /**
    * Gets the current frame metrics. This is *not* the Gecko copy stored in the
    * layers code.
    */
   const FrameMetrics& GetFrameMetrics() const;
 
   /**
@@ -928,18 +928,18 @@ private:
   // Metadata of the container layer corresponding to this APZC. This is
   // stored here so that it is accessible from the UI/controller thread.
   // These are the metrics at last content paint, the most recent
   // values we were notified of in NotifyLayersUpdate(). Since it represents
   // the Gecko state, it should be used as a basis for untransformation when
   // sending messages back to Gecko.
   ScrollMetadata mLastContentPaintMetadata;
   FrameMetrics& mLastContentPaintMetrics;  // for convenience, refers to mLastContentPaintMetadata.mMetrics
-  // The last metrics used for a content repaint request.
-  FrameMetrics mLastPaintRequestMetrics;
+  // The last content repaint request.
+  RepaintRequest mLastPaintRequestMetrics;
   // The metrics that we expect content to have. This is updated when we
   // request a content repaint, and when we receive a shadow layers update.
   // This allows us to transform events into Gecko's coordinate space.
   FrameMetrics mExpectedGeckoMetrics;
 
   // These variables cache the layout viewport, scroll offset, and zoom stored
   // in |Metrics()| the last time SampleCompositedAsyncTransform() was
   // called.
--- a/gfx/layers/apz/test/gtest/APZTestCommon.h
+++ b/gfx/layers/apz/test/gtest/APZTestCommon.h
@@ -96,17 +96,17 @@ private:
 
 static TimeStamp GetStartupTime() {
   static TimeStamp sStartupTime = TimeStamp::Now();
   return sStartupTime;
 }
 
 class MockContentController : public GeckoContentController {
 public:
-  MOCK_METHOD1(RequestContentRepaint, void(const FrameMetrics&));
+  MOCK_METHOD1(RequestContentRepaint, void(const RepaintRequest&));
   MOCK_METHOD2(RequestFlingSnap, void(const FrameMetrics::ViewID& aScrollId, const mozilla::CSSPoint& aDestination));
   MOCK_METHOD2(AcknowledgeScrollUpdate, void(const FrameMetrics::ViewID&, const uint32_t& aScrollGeneration));
   MOCK_METHOD5(HandleTap, void(TapType, const LayoutDevicePoint&, Modifiers, const ScrollableLayerGuid&, uint64_t));
   MOCK_METHOD4(NotifyPinchGesture, void(PinchGestureInput::PinchGestureType, const ScrollableLayerGuid&, LayoutDeviceCoord, Modifiers));
   // Can't use the macros with already_AddRefed :(
   void PostDelayedTask(already_AddRefed<Runnable> aTask, int aDelayMs) {
     RefPtr<Runnable> task = aTask;
   }
--- a/gfx/layers/apz/test/mochitest/mochitest.ini
+++ b/gfx/layers/apz/test/mochitest/mochitest.ini
@@ -31,16 +31,18 @@
 [test_group_wheelevents.html]
   skip-if = (toolkit == 'android') # wheel events not supported on mobile
 [test_group_zoom.html]
   skip-if = (os == 'win') || webrender # zooming is not supported yet on WebRender; see bug 1495580 for Windows
 [test_interrupted_reflow.html]
 [test_group_keyboard.html]
 [test_layerization.html]
   skip-if = (os == 'android') # wheel events not supported on mobile
+[test_relative_update.html]
+  skip-if = (os == 'android') # wheel events not supported on mobile
 [test_scroll_inactive_bug1190112.html]
   skip-if = (os == 'android') # wheel events not supported on mobile
 [test_scroll_inactive_flattened_frame.html]
   skip-if = (os == 'android') # wheel events not supported on mobile
 [test_scroll_subframe_scrollbar.html]
   skip-if = (os == 'android') # wheel events not supported on mobile
 [test_touch_listeners_impacting_wheel.html]
   skip-if = (toolkit == 'android') || (toolkit == 'cocoa') # wheel events not supported on mobile, and synthesized wheel smooth-scrolling not supported on OS X
new file mode 100644
--- /dev/null
+++ b/gfx/layers/apz/test/mochitest/test_relative_update.html
@@ -0,0 +1,92 @@
+<!DOCTYPE HTML>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1453425
+-->
+<html>
+<head>
+    <title>Test for relative scroll offset updates (Bug 1453425)</title>
+    <meta charset="utf-8">
+    <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+    <script type="application/javascript" src="/tests/SimpleTest/EventUtils.js"></script>
+    <script type="application/javascript" src="/tests/SimpleTest/paint_listener.js"></script>
+    <script type="application/javascript" src="apz_test_utils.js"></script>
+    <script type="application/javascript" src="apz_test_native_event_utils.js"></script>
+    <style type="text/css">
+        #frame {
+            width: 200px;
+            height: 400px;
+            overflow: scroll;
+            border: 1px solid black;
+        }
+        #first {
+            width: 200px;
+            height: 108px;
+            background-color: red;
+        }
+        #second {
+            width: 200px;
+            height: 692px;
+            background-color: green;
+        }
+    </style>
+</head>
+<body>
+    <div id="frame">
+        <div id="first"></div>
+        <div id="second"></div>
+    </div>
+<script type="application/javascript">
+function* test(testDriver) {
+  var utils = SpecialPowers.DOMWindowUtils;
+
+  var elm = document.querySelector('#frame');
+  // Set a zero-margin displayport to ensure that the element is async-scrollable
+  utils.setDisplayPortMarginsForElement(0, 0, 0, 0, elm, 0);
+  elm.scrollTop = 0;
+
+  // Take over control of the refresh driver and don't allow a layer
+  // transaction until the main thread and APZ have processed two different
+  // scrolls.
+  yield waitForApzFlushedRepaints(testDriver);
+  utils.advanceTimeAndRefresh(0);
+
+  // Scroll instantly on the main thread by (0, 100).
+  elm.scrollBy(0, 100);
+
+  // We are not using `scroll-behavior`
+  is(elm.scrollTop, 100, 'the main thread scroll should be instant');
+
+  // Dispatch a wheel event to have APZ scroll by (0, 8). Wait for the wheel
+  // event to ensure that the APZ has processed the scroll.
+  yield synthesizeNativeWheelAndWaitForWheelEvent(elm, 40, 40, 0, -8, testDriver);
+
+  // APZ should be handling the wheel scroll
+  is(elm.scrollTop, 100, 'the wheel scroll should be handled by APZ');
+
+  // Restore control of the refresh driver, allowing the main thread to send a
+  // layer transaction containing the (0, 100) scroll.
+  utils.restoreNormalRefresh();
+
+  // Wait for all paints to finish and for the main thread to receive pending
+  // repaint requests with the scroll offset from the wheel event.
+  yield waitForApzFlushedRepaints(testDriver);
+
+  // The main thread scroll should not have overidden the APZ scroll, and we
+  // should see the effects of both scrolls.
+  ok(elm.scrollTop > 100, `expected element.scrollTop > 100. got element.scrollTop = ${elm.scrollTop}`);
+}
+
+if (isApzEnabled()) {
+  SimpleTest.waitForExplicitFinish();
+  // Receiving a relative scroll offset update can cause scroll animations to
+  // be cancelled. This should be fixed, but for now we can still test this
+  // by disabling smooth scrolling.
+  pushPrefs([["apz.relative-update.enabled", true], ["general.smoothScroll", false]])
+  .then(waitUntilApzStable)
+  .then(runContinuation(test))
+  .then(SimpleTest.finish);
+}
+
+</script>
+</body>
+</html>
--- a/gfx/layers/apz/util/APZCCallbackHelper.cpp
+++ b/gfx/layers/apz/util/APZCCallbackHelper.cpp
@@ -43,71 +43,71 @@
 
 namespace mozilla {
 namespace layers {
 
 using dom::TabParent;
 
 uint64_t APZCCallbackHelper::sLastTargetAPZCNotificationInputBlock = uint64_t(-1);
 
-void
+ScreenMargin
 APZCCallbackHelper::AdjustDisplayPortForScrollDelta(
-    mozilla::layers::FrameMetrics& aFrameMetrics,
+    const RepaintRequest& aRequest,
     const CSSPoint& aActualScrollOffset)
 {
   // Correct the display-port by the difference between the requested scroll
   // offset and the resulting scroll offset after setting the requested value.
   ScreenPoint shift =
-      (aFrameMetrics.GetScrollOffset() - aActualScrollOffset) *
-      aFrameMetrics.DisplayportPixelsPerCSSPixel();
-  ScreenMargin margins = aFrameMetrics.GetDisplayPortMargins();
+      (aRequest.GetScrollOffset() - aActualScrollOffset) *
+      aRequest.DisplayportPixelsPerCSSPixel();
+  ScreenMargin margins = aRequest.GetDisplayPortMargins();
   margins.left -= shift.x;
   margins.right += shift.x;
   margins.top -= shift.y;
   margins.bottom += shift.y;
-  aFrameMetrics.SetDisplayPortMargins(margins);
+  return margins;
 }
 
-static void
-RecenterDisplayPort(mozilla::layers::FrameMetrics& aFrameMetrics)
+static ScreenMargin
+RecenterDisplayPort(const ScreenMargin& aDisplayPort)
 {
-  ScreenMargin margins = aFrameMetrics.GetDisplayPortMargins();
+  ScreenMargin margins = aDisplayPort;
   margins.right = margins.left = margins.LeftRight() / 2;
   margins.top = margins.bottom = margins.TopBottom() / 2;
-  aFrameMetrics.SetDisplayPortMargins(margins);
+  return margins;
 }
 
 static already_AddRefed<nsIPresShell>
 GetPresShell(const nsIContent* aContent)
 {
   nsCOMPtr<nsIPresShell> result;
   if (nsIDocument* doc = aContent->GetComposedDoc()) {
     result = doc->GetShell();
   }
   return result.forget();
 }
 
 static CSSPoint
-ScrollFrameTo(nsIScrollableFrame* aFrame, const FrameMetrics& aMetrics, bool& aSuccessOut)
+ScrollFrameTo(nsIScrollableFrame* aFrame, const RepaintRequest& aRequest, bool& aSuccessOut)
 {
   aSuccessOut = false;
-  CSSPoint targetScrollPosition = aMetrics.IsRootContent()
-    ? aMetrics.GetViewport().TopLeft()
-    : aMetrics.GetScrollOffset();
+  CSSPoint targetScrollPosition = aRequest.IsRootContent()
+    ? aRequest.GetViewport().TopLeft()
+    : aRequest.GetScrollOffset();
 
   if (!aFrame) {
     return targetScrollPosition;
   }
 
   CSSPoint geckoScrollPosition = CSSPoint::FromAppUnits(aFrame->GetScrollPosition());
 
   // If the repaint request was triggered due to a previous main-thread scroll
   // offset update sent to the APZ, then we don't need to do another scroll here
   // and we can just return.
-  if (!aMetrics.GetScrollOffsetUpdated()) {
+  if (!aRequest.GetScrollOffsetUpdated()) {
     return geckoScrollPosition;
   }
 
   // If this frame is overflow:hidden, then the expectation is that it was
   // sized in a way that respects its scrollable boundaries. For the root
   // frame, this means that it cannot be scrolled in such a way that it moves
   // the layout viewport. For a non-root frame, this means that it cannot be
   // scrolled at all.
@@ -154,148 +154,147 @@ ScrollFrameTo(nsIScrollableFrame* aFrame
   // Return the final scroll position after setting it so that anything that relies
   // on it can have an accurate value. Note that even if we set it above re-querying it
   // is a good idea because it may have gotten clamped or rounded.
   return geckoScrollPosition;
 }
 
 /**
  * Scroll the scroll frame associated with |aContent| to the scroll position
- * requested in |aMetrics|.
- * The scroll offset in |aMetrics| is updated to reflect the actual scroll
- * position.
- * The displayport stored in |aMetrics| and the callback-transform stored on
- * the content are updated to reflect any difference between the requested
- * and actual scroll positions.
+ * requested in |aRequest|.
+ *
+ * Any difference between the requested and actual scroll positions is used to
+ * update the callback-transform stored on the content, and return a new
+ * display port.
  */
-static void
+static ScreenMargin
 ScrollFrame(nsIContent* aContent,
-            FrameMetrics& aMetrics)
+            const RepaintRequest& aRequest)
 {
   // Scroll the window to the desired spot
-  nsIScrollableFrame* sf = nsLayoutUtils::FindScrollableFrameFor(aMetrics.GetScrollId());
+  nsIScrollableFrame* sf = nsLayoutUtils::FindScrollableFrameFor(aRequest.GetScrollId());
   if (sf) {
-    sf->ResetScrollInfoIfGeneration(aMetrics.GetScrollGeneration());
-    sf->SetScrollableByAPZ(!aMetrics.IsScrollInfoLayer());
+    sf->ResetScrollInfoIfGeneration(aRequest.GetScrollGeneration());
+    sf->SetScrollableByAPZ(!aRequest.IsScrollInfoLayer());
     if (sf->IsRootScrollFrameOfDocument()) {
       if (nsCOMPtr<nsIPresShell> shell = GetPresShell(aContent)) {
-        shell->SetVisualViewportOffset(CSSPoint::ToAppUnits(aMetrics.GetScrollOffset()));
+        shell->SetVisualViewportOffset(CSSPoint::ToAppUnits(aRequest.GetScrollOffset()));
       }
     }
   }
   bool scrollUpdated = false;
-  CSSPoint apzScrollOffset = aMetrics.GetScrollOffset();
-  CSSPoint actualScrollOffset = ScrollFrameTo(sf, aMetrics, scrollUpdated);
+  ScreenMargin displayPortMargins = aRequest.GetDisplayPortMargins();
+  CSSPoint apzScrollOffset = aRequest.GetScrollOffset();
+  CSSPoint actualScrollOffset = ScrollFrameTo(sf, aRequest, scrollUpdated);
 
   if (scrollUpdated) {
-    if (aMetrics.IsScrollInfoLayer()) {
+    if (aRequest.IsScrollInfoLayer()) {
       // In cases where the APZ scroll offset is different from the content scroll
       // offset, we want to interpret the margins as relative to the APZ scroll
       // offset except when the frame is not scrollable by APZ. Therefore, if the
       // layer is a scroll info layer, we leave the margins as-is and they will
       // be interpreted as relative to the content scroll offset.
       if (nsIFrame* frame = aContent->GetPrimaryFrame()) {
         frame->SchedulePaint();
       }
     } else {
       // Correct the display port due to the difference between mScrollOffset and the
       // actual scroll offset.
-      APZCCallbackHelper::AdjustDisplayPortForScrollDelta(aMetrics, actualScrollOffset);
+      displayPortMargins = APZCCallbackHelper::AdjustDisplayPortForScrollDelta(aRequest, actualScrollOffset);
     }
-  } else if (aMetrics.IsRootContent() &&
-             aMetrics.GetScrollOffset() != aMetrics.GetViewport().TopLeft()) {
+  } else if (aRequest.IsRootContent() &&
+             aRequest.GetScrollOffset() != aRequest.GetViewport().TopLeft()) {
     // APZ uses the visual viewport's offset to calculate where to place the
     // display port, so the display port is misplaced when a pinch zoom occurs.
     //
     // We need to force a display port adjustment in the following paint to
     // account for a difference between mScrollOffset and the actual scroll
     // offset in repaints requested by AsyncPanZoomController::NotifyLayersUpdated.
-    APZCCallbackHelper::AdjustDisplayPortForScrollDelta(aMetrics, actualScrollOffset);
+    displayPortMargins = APZCCallbackHelper::AdjustDisplayPortForScrollDelta(aRequest, actualScrollOffset);
   } else {
     // For whatever reason we couldn't update the scroll offset on the scroll frame,
     // which means the data APZ used for its displayport calculation is stale. Fall
     // back to a sane default behaviour. Note that we don't tile-align the recentered
     // displayport because tile-alignment depends on the scroll position, and the
     // scroll position here is out of our control. See bug 966507 comment 21 for a
     // more detailed explanation.
-    RecenterDisplayPort(aMetrics);
+    displayPortMargins = RecenterDisplayPort(aRequest.GetDisplayPortMargins());
   }
 
-  aMetrics.SetScrollOffset(actualScrollOffset);
-
   // APZ transforms inputs assuming we applied the exact scroll offset it
   // requested (|apzScrollOffset|). Since we may not have, record the difference
   // between what APZ asked for and what we actually applied, and apply it to
   // input events to compensate.
   // Note that if the main-thread had a change in its scroll position, we don't
   // want to record that difference here, because it can be large and throw off
   // input events by a large amount. It is also going to be transient, because
   // any main-thread scroll position change will be synced to APZ and we will
   // get another repaint request when APZ confirms. In the interval while this
   // is happening we can just leave the callback transform as it was.
   bool mainThreadScrollChanged =
-    sf && sf->CurrentScrollGeneration() != aMetrics.GetScrollGeneration() && nsLayoutUtils::CanScrollOriginClobberApz(sf->LastScrollOrigin());
+    sf && sf->CurrentScrollGeneration() != aRequest.GetScrollGeneration() && nsLayoutUtils::CanScrollOriginClobberApz(sf->LastScrollOrigin());
   if (aContent && !mainThreadScrollChanged) {
     CSSPoint scrollDelta = apzScrollOffset - actualScrollOffset;
     aContent->SetProperty(nsGkAtoms::apzCallbackTransform, new CSSPoint(scrollDelta),
                           nsINode::DeleteProperty<CSSPoint>);
   }
+
+  return displayPortMargins;
 }
 
 static void
 SetDisplayPortMargins(nsIPresShell* aPresShell,
                       nsIContent* aContent,
-                      const FrameMetrics& aMetrics)
+                      ScreenMargin aDisplayPortMargins,
+                      CSSSize aDisplayPortBase)
 {
   if (!aContent) {
     return;
   }
 
   bool hadDisplayPort = nsLayoutUtils::HasDisplayPort(aContent);
-  ScreenMargin margins = aMetrics.GetDisplayPortMargins();
-  nsLayoutUtils::SetDisplayPortMargins(aContent, aPresShell, margins, 0);
+  nsLayoutUtils::SetDisplayPortMargins(aContent, aPresShell, aDisplayPortMargins, 0);
   if (!hadDisplayPort) {
     nsLayoutUtils::SetZeroMarginDisplayPortOnAsyncScrollableAncestors(
         aContent->GetPrimaryFrame(), nsLayoutUtils::RepaintMode::Repaint);
   }
 
-  CSSSize baseSize = aMetrics.CalculateCompositedSizeInCssPixels();
   nsRect base(0, 0,
-              baseSize.width * AppUnitsPerCSSPixel(),
-              baseSize.height * AppUnitsPerCSSPixel());
+              aDisplayPortBase.width * AppUnitsPerCSSPixel(),
+              aDisplayPortBase.height * AppUnitsPerCSSPixel());
   nsLayoutUtils::SetDisplayPortBaseIfNotSet(aContent, base);
 }
 
 static void
 SetPaintRequestTime(nsIContent* aContent, const TimeStamp& aPaintRequestTime)
 {
   aContent->SetProperty(nsGkAtoms::paintRequestTime,
                         new TimeStamp(aPaintRequestTime),
                         nsINode::DeleteProperty<TimeStamp>);
 }
 
 void
-APZCCallbackHelper::UpdateRootFrame(FrameMetrics& aMetrics)
+APZCCallbackHelper::UpdateRootFrame(const RepaintRequest& aRequest)
 {
-  if (aMetrics.GetScrollId() == FrameMetrics::NULL_SCROLL_ID) {
+  if (aRequest.GetScrollId() == FrameMetrics::NULL_SCROLL_ID) {
     return;
   }
-  nsIContent* content = nsLayoutUtils::FindContentFor(aMetrics.GetScrollId());
+  nsIContent* content = nsLayoutUtils::FindContentFor(aRequest.GetScrollId());
   if (!content) {
     return;
   }
 
   nsCOMPtr<nsIPresShell> shell = GetPresShell(content);
-  if (!shell || aMetrics.GetPresShellId() != shell->GetPresShellId()) {
+  if (!shell || aRequest.GetPresShellId() != shell->GetPresShellId()) {
     return;
   }
 
-  MOZ_ASSERT(aMetrics.GetUseDisplayPortMargins());
+  MOZ_ASSERT(aRequest.GetUseDisplayPortMargins());
 
-  if (gfxPrefs::APZAllowZooming() && aMetrics.GetScrollOffsetUpdated()) {
+  if (gfxPrefs::APZAllowZooming() && aRequest.GetScrollOffsetUpdated()) {
     // If zooming is disabled then we don't really want to let APZ fiddle
     // with these things. In theory setting the resolution here should be a
     // no-op, but setting the visual viewport size is bad because it can cause a
     // stale value to be returned by window.innerWidth/innerHeight (see bug 1187792).
     //
     // We also skip this codepath unless the metrics has a scroll offset update
     // type other eNone, because eNone just means that this repaint request
     // was triggered by APZ in response to a main-thread update. In this
@@ -303,55 +302,61 @@ APZCCallbackHelper::UpdateRootFrame(Fram
     // it can trigger unnecessary reflows.
 
     float presShellResolution = shell->GetResolution();
 
     // If the pres shell resolution has changed on the content side side
     // the time this repaint request was fired, consider this request out of date
     // and drop it; setting a zoom based on the out-of-date resolution can have
     // the effect of getting us stuck with the stale resolution.
-    if (!FuzzyEqualsMultiplicative(presShellResolution, aMetrics.GetPresShellResolution())) {
+    if (!FuzzyEqualsMultiplicative(presShellResolution, aRequest.GetPresShellResolution())) {
       return;
     }
 
     // The pres shell resolution is updated by the the async zoom since the
     // last paint.
-    presShellResolution = aMetrics.GetPresShellResolution()
-                        * aMetrics.GetAsyncZoom().scale;
+    presShellResolution = aRequest.GetPresShellResolution()
+                        * aRequest.GetAsyncZoom().scale;
     shell->SetResolutionAndScaleTo(presShellResolution);
   }
 
   // Do this as late as possible since scrolling can flush layout. It also
   // adjusts the display port margins, so do it before we set those.
-  ScrollFrame(content, aMetrics);
+  ScreenMargin displayPortMargins = ScrollFrame(content, aRequest);
 
-  SetDisplayPortMargins(shell, content, aMetrics);
-  SetPaintRequestTime(content, aMetrics.GetPaintRequestTime());
+  SetDisplayPortMargins(shell,
+    content,
+    displayPortMargins,
+    aRequest.CalculateCompositedSizeInCssPixels());
+  SetPaintRequestTime(content, aRequest.GetPaintRequestTime());
 }
 
 void
-APZCCallbackHelper::UpdateSubFrame(FrameMetrics& aMetrics)
+APZCCallbackHelper::UpdateSubFrame(const RepaintRequest& aRequest)
 {
-  if (aMetrics.GetScrollId() == FrameMetrics::NULL_SCROLL_ID) {
+  if (aRequest.GetScrollId() == FrameMetrics::NULL_SCROLL_ID) {
     return;
   }
-  nsIContent* content = nsLayoutUtils::FindContentFor(aMetrics.GetScrollId());
+  nsIContent* content = nsLayoutUtils::FindContentFor(aRequest.GetScrollId());
   if (!content) {
     return;
   }
 
-  MOZ_ASSERT(aMetrics.GetUseDisplayPortMargins());
+  MOZ_ASSERT(aRequest.GetUseDisplayPortMargins());
 
   // We don't currently support zooming for subframes, so nothing extra
   // needs to be done beyond the tasks common to this and UpdateRootFrame.
-  ScrollFrame(content, aMetrics);
+  ScreenMargin displayPortMargins = ScrollFrame(content, aRequest);
   if (nsCOMPtr<nsIPresShell> shell = GetPresShell(content)) {
-    SetDisplayPortMargins(shell, content, aMetrics);
+    SetDisplayPortMargins(shell,
+      content,
+      displayPortMargins,
+      aRequest.CalculateCompositedSizeInCssPixels());
   }
-  SetPaintRequestTime(content, aMetrics.GetPaintRequestTime());
+  SetPaintRequestTime(content, aRequest.GetPaintRequestTime());
 }
 
 bool
 APZCCallbackHelper::GetOrCreateScrollIdentifiers(nsIContent* aContent,
                                                  uint32_t* aPresShellIdOut,
                                                  FrameMetrics::ViewID* aViewIdOut)
 {
     if (!aContent) {
@@ -747,17 +752,22 @@ SendLayersDependentApzcTargetConfirmatio
 
   if (WebRenderLayerManager* wrlm = lm->AsWebRenderLayerManager()) {
     if (WebRenderBridgeChild* wrbc = wrlm->WrBridge()) {
       wrbc->SendSetConfirmedTargetAPZC(aInputBlockId, aTargets);
     }
     return;
   }
 
-  LayerTransactionChild* shadow = lm->AsShadowForwarder()->GetShadowManager();
+  ShadowLayerForwarder* lf = lm->AsShadowForwarder();
+  if (!lf) {
+    return;
+  }
+
+  LayerTransactionChild* shadow = lf->GetShadowManager();
   if (!shadow) {
     return;
   }
 
   shadow->SendSetConfirmedTargetAPZC(aInputBlockId, aTargets);
 }
 
 DisplayportSetListener::DisplayportSetListener(nsIWidget* aWidget,
--- a/gfx/layers/apz/util/APZCCallbackHelper.h
+++ b/gfx/layers/apz/util/APZCCallbackHelper.h
@@ -5,16 +5,17 @@
 
 #ifndef mozilla_layers_APZCCallbackHelper_h
 #define mozilla_layers_APZCCallbackHelper_h
 
 #include "FrameMetrics.h"
 #include "InputData.h"
 #include "mozilla/EventForwards.h"
 #include "mozilla/layers/APZUtils.h"
+#include "mozilla/layers/RepaintRequest.h"
 #include "nsIDOMWindowUtils.h"
 #include "nsRefreshDriver.h"
 
 #include <functional>
 
 class nsIContent;
 class nsIDocument;
 class nsIPresShell;
@@ -59,27 +60,25 @@ class APZCCallbackHelper
     typedef mozilla::layers::FrameMetrics FrameMetrics;
     typedef mozilla::layers::ScrollableLayerGuid ScrollableLayerGuid;
 
 public:
     /* Applies the scroll and zoom parameters from the given FrameMetrics object
        to the root frame for the given metrics' scrollId. If tiled thebes layers
        are enabled, this will align the displayport to tile boundaries. Setting
        the scroll position can cause some small adjustments to be made to the
-       actual scroll position. aMetrics' display port and scroll position will
-       be updated with any modifications made. */
-    static void UpdateRootFrame(FrameMetrics& aMetrics);
+       actual scroll position. */
+    static void UpdateRootFrame(const RepaintRequest& aRequest);
 
     /* Applies the scroll parameters from the given FrameMetrics object to the
        subframe corresponding to given metrics' scrollId. If tiled thebes
        layers are enabled, this will align the displayport to tile boundaries.
        Setting the scroll position can cause some small adjustments to be made
-       to the actual scroll position. aMetrics' display port and scroll position
-       will be updated with any modifications made. */
-    static void UpdateSubFrame(FrameMetrics& aMetrics);
+       to the actual scroll position. */
+    static void UpdateSubFrame(const RepaintRequest& aRequest);
 
     /* Get the presShellId and view ID for the given content element.
      * If the view ID does not exist, one is created.
      * The pres shell ID should generally already exist; if it doesn't for some
      * reason, false is returned. */
     static bool GetOrCreateScrollIdentifiers(nsIContent* aContent,
                                              uint32_t* aPresShellIdOut,
                                              FrameMetrics::ViewID* aViewIdOut);
@@ -189,18 +188,18 @@ public:
     /* Notify content that the repaint flush is complete. */
     static void NotifyFlushComplete(nsIPresShell* aShell);
 
     static void NotifyAsyncScrollbarDragRejected(const FrameMetrics::ViewID& aScrollId);
     static void NotifyAsyncAutoscrollRejected(const FrameMetrics::ViewID& aScrollId);
 
     static void CancelAutoscroll(const FrameMetrics::ViewID& aScrollId);
 
-    static void
-    AdjustDisplayPortForScrollDelta(mozilla::layers::FrameMetrics& aFrameMetrics,
+    static ScreenMargin
+    AdjustDisplayPortForScrollDelta(const RepaintRequest& aRequest,
                                     const CSSPoint& aActualScrollOffset);
 
     /*
      * Check if the scrollable frame is currently in the middle of an async
      * or smooth scroll. We want to discard certain scroll input if this is
      * true to prevent clobbering higher priority origins.
      */
     static bool
--- a/gfx/layers/apz/util/ChromeProcessController.cpp
+++ b/gfx/layers/apz/util/ChromeProcessController.cpp
@@ -48,25 +48,24 @@ ChromeProcessController::~ChromeProcessC
 
 void
 ChromeProcessController::InitializeRoot()
 {
   APZCCallbackHelper::InitializeRootDisplayport(GetPresShell());
 }
 
 void
-ChromeProcessController::RequestContentRepaint(const FrameMetrics& aFrameMetrics)
+ChromeProcessController::RequestContentRepaint(const RepaintRequest& aRequest)
 {
   MOZ_ASSERT(IsRepaintThread());
 
-  FrameMetrics metrics = aFrameMetrics;
-  if (metrics.IsRootContent()) {
-    APZCCallbackHelper::UpdateRootFrame(metrics);
+  if (aRequest.IsRootContent()) {
+    APZCCallbackHelper::UpdateRootFrame(aRequest);
   } else {
-    APZCCallbackHelper::UpdateSubFrame(metrics);
+    APZCCallbackHelper::UpdateSubFrame(aRequest);
   }
 }
 
 void
 ChromeProcessController::PostDelayedTask(already_AddRefed<Runnable> aTask, int aDelayMs)
 {
   MessageLoop::current()->PostDelayedTask(std::move(aTask), aDelayMs);
 }
--- a/gfx/layers/apz/util/ChromeProcessController.h
+++ b/gfx/layers/apz/util/ChromeProcessController.h
@@ -40,17 +40,17 @@ protected:
   typedef mozilla::layers::ScrollableLayerGuid ScrollableLayerGuid;
 
 public:
   explicit ChromeProcessController(nsIWidget* aWidget, APZEventState* aAPZEventState, IAPZCTreeManager* aAPZCTreeManager);
   ~ChromeProcessController();
   virtual void Destroy() override;
 
   // GeckoContentController interface
-  virtual void RequestContentRepaint(const FrameMetrics& aFrameMetrics) override;
+  virtual void RequestContentRepaint(const RepaintRequest& aRequest) override;
   virtual void PostDelayedTask(already_AddRefed<Runnable> aTask, int aDelayMs) override;
   virtual bool IsRepaintThread() override;
   virtual void DispatchToRepaintThread(already_AddRefed<Runnable> aTask) override;
   MOZ_CAN_RUN_SCRIPT
   virtual void HandleTap(TapType aType,
                          const mozilla::LayoutDevicePoint& aPoint,
                          Modifiers aModifiers,
                          const ScrollableLayerGuid& aGuid,
--- a/gfx/layers/apz/util/ContentProcessController.cpp
+++ b/gfx/layers/apz/util/ContentProcessController.cpp
@@ -18,20 +18,20 @@ namespace layers {
 
 ContentProcessController::ContentProcessController(const RefPtr<dom::TabChild>& aBrowser)
     : mBrowser(aBrowser)
 {
   MOZ_ASSERT(mBrowser);
 }
 
 void
-ContentProcessController::RequestContentRepaint(const FrameMetrics& aFrameMetrics)
+ContentProcessController::RequestContentRepaint(const RepaintRequest& aRequest)
 {
   if (mBrowser) {
-    mBrowser->UpdateFrame(aFrameMetrics);
+    mBrowser->UpdateFrame(aRequest);
   }
 }
 
 void
 ContentProcessController::HandleTap(
                         TapType aType,
                         const LayoutDevicePoint& aPoint,
                         Modifiers aModifiers,
--- a/gfx/layers/apz/util/ContentProcessController.h
+++ b/gfx/layers/apz/util/ContentProcessController.h
@@ -35,17 +35,17 @@ class APZChild;
 class ContentProcessController final
       : public GeckoContentController
 {
 public:
   explicit ContentProcessController(const RefPtr<dom::TabChild>& aBrowser);
 
   // GeckoContentController
 
-  void RequestContentRepaint(const FrameMetrics& frame) override;
+  void RequestContentRepaint(const RepaintRequest& aRequest) override;
 
   void HandleTap(TapType aType,
                  const LayoutDevicePoint& aPoint,
                  Modifiers aModifiers,
                  const ScrollableLayerGuid& aGuid,
                  uint64_t aInputBlockId) override;
 
   void NotifyPinchGesture(PinchGestureInput::PinchGestureType aType,
--- a/gfx/layers/ipc/APZChild.cpp
+++ b/gfx/layers/ipc/APZChild.cpp
@@ -25,21 +25,21 @@ APZChild::~APZChild()
 {
   if (mController) {
     mController->Destroy();
     mController = nullptr;
   }
 }
 
 mozilla::ipc::IPCResult
-APZChild::RecvRequestContentRepaint(const FrameMetrics& aFrameMetrics)
+APZChild::RecvRequestContentRepaint(const RepaintRequest& aRequest)
 {
   MOZ_ASSERT(mController->IsRepaintThread());
 
-  mController->RequestContentRepaint(aFrameMetrics);
+  mController->RequestContentRepaint(aRequest);
   return IPC_OK();
 }
 
 mozilla::ipc::IPCResult
 APZChild::RecvUpdateOverscrollVelocity(const float& aX, const float& aY, const bool& aIsRootContent)
 {
   mController->UpdateOverscrollVelocity(aX, aY, aIsRootContent);
   return IPC_OK();
--- a/gfx/layers/ipc/APZChild.h
+++ b/gfx/layers/ipc/APZChild.h
@@ -20,17 +20,17 @@ class GeckoContentController;
  * that lives in a different process than where APZ lives.
  */
 class APZChild final : public PAPZChild
 {
 public:
   explicit APZChild(RefPtr<GeckoContentController> aController);
   ~APZChild();
 
-  mozilla::ipc::IPCResult RecvRequestContentRepaint(const FrameMetrics& frame) override;
+  mozilla::ipc::IPCResult RecvRequestContentRepaint(const RepaintRequest& aRequest) override;
 
   mozilla::ipc::IPCResult RecvUpdateOverscrollVelocity(const float& aX, const float& aY, const bool& aIsRootContent) override;
 
   mozilla::ipc::IPCResult RecvUpdateOverscrollOffset(const float& aX, const float& aY, const bool& aIsRootContent) override;
 
   mozilla::ipc::IPCResult RecvNotifyMozMouseScrollEvent(const ViewID& aScrollId,
                                                         const nsString& aEvent) override;
 
--- a/gfx/layers/ipc/LayersMessageUtils.h
+++ b/gfx/layers/ipc/LayersMessageUtils.h
@@ -18,16 +18,17 @@
 #include "mozilla/layers/CompositorOptions.h"
 #include "mozilla/layers/CompositorTypes.h"
 #include "mozilla/layers/FocusTarget.h"
 #include "mozilla/layers/GeckoContentController.h"
 #include "mozilla/layers/KeyboardMap.h"
 #include "mozilla/layers/LayerAttributes.h"
 #include "mozilla/layers/LayersTypes.h"
 #include "mozilla/layers/RefCountedShmem.h"
+#include "mozilla/layers/RepaintRequest.h"
 #include "mozilla/Move.h"
 
 #include <stdint.h>
 
 #ifdef _MSC_VER
 #pragma warning( disable : 4800 )
 #endif
 
@@ -89,16 +90,24 @@ struct ParamTraits<mozilla::layers::Scro
 template<>
 struct ParamTraits<mozilla::layers::FrameMetrics::ScrollOffsetUpdateType>
   : public ContiguousEnumSerializerInclusive<
              mozilla::layers::FrameMetrics::ScrollOffsetUpdateType,
              mozilla::layers::FrameMetrics::ScrollOffsetUpdateType::eNone,
              mozilla::layers::FrameMetrics::sHighestScrollOffsetUpdateType>
 {};
 
+template<>
+struct ParamTraits<mozilla::layers::RepaintRequest::ScrollOffsetUpdateType>
+  : public ContiguousEnumSerializerInclusive<
+             mozilla::layers::RepaintRequest::ScrollOffsetUpdateType,
+             mozilla::layers::RepaintRequest::ScrollOffsetUpdateType::eNone,
+             mozilla::layers::RepaintRequest::sHighestScrollOffsetUpdateType>
+{};
+
 template <>
 struct ParamTraits<mozilla::layers::OverscrollBehavior>
   : public ContiguousEnumSerializerInclusive<
             mozilla::layers::OverscrollBehavior,
             mozilla::layers::OverscrollBehavior::Auto,
             mozilla::layers::kHighestOverscrollBehavior>
 {};
 
@@ -160,61 +169,114 @@ struct ParamTraits<mozilla::layers::Fram
     WriteParam(aMsg, aParam.mPresShellResolution);
     WriteParam(aMsg, aParam.mCompositionBounds);
     WriteParam(aMsg, aParam.mDisplayPort);
     WriteParam(aMsg, aParam.mCriticalDisplayPort);
     WriteParam(aMsg, aParam.mScrollableRect);
     WriteParam(aMsg, aParam.mCumulativeResolution);
     WriteParam(aMsg, aParam.mDevPixelsPerCSSPixel);
     WriteParam(aMsg, aParam.mScrollOffset);
+    WriteParam(aMsg, aParam.mBaseScrollOffset);
     WriteParam(aMsg, aParam.mZoom);
     WriteParam(aMsg, aParam.mScrollGeneration);
     WriteParam(aMsg, aParam.mSmoothScrollOffset);
     WriteParam(aMsg, aParam.mRootCompositionSize);
     WriteParam(aMsg, aParam.mDisplayPortMargins);
     WriteParam(aMsg, aParam.mPresShellId);
     WriteParam(aMsg, aParam.mViewport);
     WriteParam(aMsg, aParam.mExtraResolution);
     WriteParam(aMsg, aParam.mPaintRequestTime);
     WriteParam(aMsg, aParam.mScrollUpdateType);
     WriteParam(aMsg, aParam.mIsRootContent);
+    WriteParam(aMsg, aParam.mIsRelative);
     WriteParam(aMsg, aParam.mDoSmoothScroll);
     WriteParam(aMsg, aParam.mUseDisplayPortMargins);
     WriteParam(aMsg, aParam.mIsScrollInfoLayer);
   }
 
   static bool Read(const Message* aMsg, PickleIterator* aIter, paramType* aResult)
   {
     return (ReadParam(aMsg, aIter, &aResult->mScrollId) &&
             ReadParam(aMsg, aIter, &aResult->mPresShellResolution) &&
             ReadParam(aMsg, aIter, &aResult->mCompositionBounds) &&
             ReadParam(aMsg, aIter, &aResult->mDisplayPort) &&
             ReadParam(aMsg, aIter, &aResult->mCriticalDisplayPort) &&
             ReadParam(aMsg, aIter, &aResult->mScrollableRect) &&
             ReadParam(aMsg, aIter, &aResult->mCumulativeResolution) &&
             ReadParam(aMsg, aIter, &aResult->mDevPixelsPerCSSPixel) &&
             ReadParam(aMsg, aIter, &aResult->mScrollOffset) &&
+            ReadParam(aMsg, aIter, &aResult->mBaseScrollOffset) &&
             ReadParam(aMsg, aIter, &aResult->mZoom) &&
             ReadParam(aMsg, aIter, &aResult->mScrollGeneration) &&
             ReadParam(aMsg, aIter, &aResult->mSmoothScrollOffset) &&
             ReadParam(aMsg, aIter, &aResult->mRootCompositionSize) &&
             ReadParam(aMsg, aIter, &aResult->mDisplayPortMargins) &&
             ReadParam(aMsg, aIter, &aResult->mPresShellId) &&
             ReadParam(aMsg, aIter, &aResult->mViewport) &&
             ReadParam(aMsg, aIter, &aResult->mExtraResolution) &&
             ReadParam(aMsg, aIter, &aResult->mPaintRequestTime) &&
             ReadParam(aMsg, aIter, &aResult->mScrollUpdateType) &&
             ReadBoolForBitfield(aMsg, aIter, aResult, &paramType::SetIsRootContent) &&
+            ReadBoolForBitfield(aMsg, aIter, aResult, &paramType::SetIsRelative) &&
             ReadBoolForBitfield(aMsg, aIter, aResult, &paramType::SetDoSmoothScroll) &&
             ReadBoolForBitfield(aMsg, aIter, aResult, &paramType::SetUseDisplayPortMargins) &&
             ReadBoolForBitfield(aMsg, aIter, aResult, &paramType::SetIsScrollInfoLayer));
   }
 };
 
 template <>
+struct ParamTraits<mozilla::layers::RepaintRequest>
+    : BitfieldHelper<mozilla::layers::RepaintRequest>
+{
+  typedef mozilla::layers::RepaintRequest paramType;
+
+  static void Write(Message* aMsg, const paramType& aParam)
+  {
+    WriteParam(aMsg, aParam.mScrollId);
+    WriteParam(aMsg, aParam.mPresShellResolution);
+    WriteParam(aMsg, aParam.mCompositionBounds);
+    WriteParam(aMsg, aParam.mCumulativeResolution);
+    WriteParam(aMsg, aParam.mDevPixelsPerCSSPixel);
+    WriteParam(aMsg, aParam.mScrollOffset);
+    WriteParam(aMsg, aParam.mZoom);
+    WriteParam(aMsg, aParam.mScrollGeneration);
+    WriteParam(aMsg, aParam.mDisplayPortMargins);
+    WriteParam(aMsg, aParam.mPresShellId);
+    WriteParam(aMsg, aParam.mViewport);
+    WriteParam(aMsg, aParam.mExtraResolution);
+    WriteParam(aMsg, aParam.mPaintRequestTime);
+    WriteParam(aMsg, aParam.mScrollUpdateType);
+    WriteParam(aMsg, aParam.mIsRootContent);
+    WriteParam(aMsg, aParam.mUseDisplayPortMargins);
+    WriteParam(aMsg, aParam.mIsScrollInfoLayer);
+  }
+
+  static bool Read(const Message* aMsg, PickleIterator* aIter, paramType* aResult)
+  {
+    return (ReadParam(aMsg, aIter, &aResult->mScrollId) &&
+            ReadParam(aMsg, aIter, &aResult->mPresShellResolution) &&
+            ReadParam(aMsg, aIter, &aResult->mCompositionBounds) &&
+            ReadParam(aMsg, aIter, &aResult->mCumulativeResolution) &&
+            ReadParam(aMsg, aIter, &aResult->mDevPixelsPerCSSPixel) &&
+            ReadParam(aMsg, aIter, &aResult->mScrollOffset) &&
+            ReadParam(aMsg, aIter, &aResult->mZoom) &&
+            ReadParam(aMsg, aIter, &aResult->mScrollGeneration) &&
+            ReadParam(aMsg, aIter, &aResult->mDisplayPortMargins) &&
+            ReadParam(aMsg, aIter, &aResult->mPresShellId) &&
+            ReadParam(aMsg, aIter, &aResult->mViewport) &&
+            ReadParam(aMsg, aIter, &aResult->mExtraResolution) &&
+            ReadParam(aMsg, aIter, &aResult->mPaintRequestTime) &&
+            ReadParam(aMsg, aIter, &aResult->mScrollUpdateType) &&
+            ReadBoolForBitfield(aMsg, aIter, aResult, &paramType::SetIsRootContent) &&
+            ReadBoolForBitfield(aMsg, aIter, aResult, &paramType::SetUseDisplayPortMargins) &&
+            ReadBoolForBitfield(aMsg, aIter, aResult, &paramType::SetIsScrollInfoLayer));
+  }
+};
+
+template <>
 struct ParamTraits<mozilla::layers::ScrollSnapInfo>
 {
   typedef mozilla::layers::ScrollSnapInfo paramType;
 
   static void Write(Message* aMsg, const paramType& aParam)
   {
     WriteParam(aMsg, aParam.mScrollSnapTypeX);
     WriteParam(aMsg, aParam.mScrollSnapTypeY);
--- a/gfx/layers/ipc/PAPZ.ipdl
+++ b/gfx/layers/ipc/PAPZ.ipdl
@@ -6,17 +6,17 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 include "mozilla/GfxMessageUtils.h";
 include "mozilla/layers/LayersMessageUtils.h";
 
 include protocol PCompositorBridge;
 
 using CSSRect from "Units.h";
-using struct mozilla::layers::FrameMetrics from "FrameMetrics.h";
+using struct mozilla::layers::RepaintRequest from "mozilla/layers/RepaintRequest.h";
 using struct mozilla::layers::ScrollableLayerGuid from "FrameMetrics.h";
 using mozilla::layers::FrameMetrics::ViewID from "FrameMetrics.h";
 using mozilla::layers::MaybeZoomConstraints from "FrameMetrics.h";
 using mozilla::layers::GeckoContentController::APZStateChange from "mozilla/layers/GeckoContentController.h";
 using mozilla::dom::TabId from "mozilla/dom/ipc/IdType.h";
 using mozilla::dom::ContentParentId from "mozilla/dom/ipc/IdType.h";
 using mozilla::layers::AsyncDragMetrics from "mozilla/layers/AsyncDragMetrics.h";
 using class nsRegion from "nsRegion.h";
@@ -45,17 +45,17 @@ sync protocol PAPZ
   manager PCompositorBridge;
 
 parent:
 
   async __delete__();
 
 child:
 
-  async RequestContentRepaint(FrameMetrics frame);
+  async RequestContentRepaint(RepaintRequest request);
 
   async UpdateOverscrollVelocity(float aX, float aY, bool aIsRootContent);
 
   async UpdateOverscrollOffset(float aX, float aY, bool aIsRootContent);
 
   async NotifyMozMouseScrollEvent(ViewID aScrollId, nsString aEvent);
 
   async NotifyAPZStateChange(ScrollableLayerGuid aGuid, APZStateChange aChange, int aArg);
--- a/gfx/layers/ipc/RemoteContentController.cpp
+++ b/gfx/layers/ipc/RemoteContentController.cpp
@@ -30,22 +30,22 @@ RemoteContentController::RemoteContentCo
 {
 }
 
 RemoteContentController::~RemoteContentController()
 {
 }
 
 void
-RemoteContentController::RequestContentRepaint(const FrameMetrics& aFrameMetrics)
+RemoteContentController::RequestContentRepaint(const RepaintRequest& aRequest)
 {
   MOZ_ASSERT(IsRepaintThread());
 
   if (mCanSend) {
-    Unused << SendRequestContentRepaint(aFrameMetrics);
+    Unused << SendRequestContentRepaint(aRequest);
   }
 }
 
 void
 RemoteContentController::HandleTapOnMainThread(TapType aTapType,
                                                LayoutDevicePoint aPoint,
                                                Modifiers aModifiers,
                                                ScrollableLayerGuid aGuid,
--- a/gfx/layers/ipc/RemoteContentController.h
+++ b/gfx/layers/ipc/RemoteContentController.h
@@ -34,17 +34,17 @@ class RemoteContentController : public G
   using GeckoContentController::TapType;
   using GeckoContentController::APZStateChange;
 
 public:
   RemoteContentController();
 
   virtual ~RemoteContentController();
 
-  virtual void RequestContentRepaint(const FrameMetrics& aFrameMetrics) override;
+  virtual void RequestContentRepaint(const RepaintRequest& aRequest) override;
 
   virtual void HandleTap(TapType aTapType,
                          const LayoutDevicePoint& aPoint,
                          Modifiers aModifiers,
                          const ScrollableLayerGuid& aGuid,
                          uint64_t aInputBlockId) override;
 
   virtual void NotifyPinchGesture(PinchGestureInput::PinchGestureType aType,
--- a/gfx/layers/moz.build
+++ b/gfx/layers/moz.build
@@ -222,16 +222,17 @@ EXPORTS.mozilla.layers += [
     'opengl/MacIOSurfaceTextureClientOGL.h',
     'opengl/MacIOSurfaceTextureHostOGL.h',
     'opengl/TextureClientOGL.h',
     'opengl/TextureHostOGL.h',
     'PaintThread.h',
     'PersistentBufferProvider.h',
     'ProfilerScreenshots.h',
     'RenderTrace.h',
+    'RepaintRequest.h',
     'RotatedBuffer.h',
     'ShareableCanvasRenderer.h',
     'SourceSurfaceSharedData.h',
     'SourceSurfaceVolatileData.h',
     'SyncObject.h',
     'TextureSourceProvider.h',
     'TextureWrapperImage.h',
     'TransactionIdAllocator.h',
--- a/gfx/layers/wr/WebRenderScrollData.cpp
+++ b/gfx/layers/wr/WebRenderScrollData.cpp
@@ -63,16 +63,17 @@ WebRenderLayerScrollData::Initialize(Web
     MOZ_ASSERT(aOwner.GetManager());
     FrameMetrics::ViewID scrollId = asr->GetViewId();
     if (Maybe<size_t> index = aOwner.HasMetadataFor(scrollId)) {
       mScrollIds.AppendElement(index.ref());
     } else {
       Maybe<ScrollMetadata> metadata = asr->mScrollableFrame->ComputeScrollMetadata(
           aOwner.GetManager(), aItem->ReferenceFrame(),
           Nothing(), nullptr);
+      asr->mScrollableFrame->NotifyApzTransaction();
       MOZ_ASSERT(metadata);
       MOZ_ASSERT(metadata->GetMetrics().GetScrollId() == scrollId);
       mScrollIds.AppendElement(aOwner.AddMetadata(metadata.ref()));
     }
     asr = asr->mParent;
   }
 
   // aAncestorTransform, if present, is the transform from an ancestor
--- a/gfx/thebes/gfxPrefs.h
+++ b/gfx/thebes/gfxPrefs.h
@@ -360,16 +360,17 @@ private:
   DECL_GFX_PREF(Live, "apz.x_skate_highmem_adjust",            APZXSkateHighMemAdjust, float, 0.0f);
   DECL_GFX_PREF(Live, "apz.x_skate_size_multiplier",           APZXSkateSizeMultiplier, float, 1.5f);
   DECL_GFX_PREF(Live, "apz.x_stationary_size_multiplier",      APZXStationarySizeMultiplier, float, 3.0f);
   DECL_GFX_PREF(Live, "apz.y_skate_highmem_adjust",            APZYSkateHighMemAdjust, float, 0.0f);
   DECL_GFX_PREF(Live, "apz.y_skate_size_multiplier",           APZYSkateSizeMultiplier, float, 2.5f);
   DECL_GFX_PREF(Live, "apz.y_stationary_size_multiplier",      APZYStationarySizeMultiplier, float, 3.5f);
   DECL_GFX_PREF(Live, "apz.zoom_animation_duration_ms",        APZZoomAnimationDuration, int32_t, 250);
   DECL_GFX_PREF(Live, "apz.scale_repaint_delay_ms",            APZScaleRepaintDelay, int32_t, 500);
+  DECL_GFX_PREF(Live, "apz.relative-update.enabled",           APZRelativeUpdate, bool, false);
 
   DECL_GFX_PREF(Live, "browser.ui.scroll-toolbar-threshold",   ToolbarScrollThreshold, int32_t, 10);
   DECL_GFX_PREF(Live, "browser.ui.zoom.force-user-scalable",   ForceUserScalable, bool, false);
   DECL_GFX_PREF(Live, "browser.viewport.desktopWidth",         DesktopViewportWidth, int32_t, 980);
 
   DECL_GFX_PREF(Live, "dom.ipc.plugins.asyncdrawing.enabled",  PluginAsyncDrawingEnabled, bool, false);
   DECL_GFX_PREF(Live, "dom.meta-viewport.enabled",             MetaViewportEnabled, bool, false);
   DECL_GFX_PREF(Live, "dom.visualviewport.enabled",            VisualViewportEnabled, bool, false);
--- a/gfx/thebes/gfxTextRun.cpp
+++ b/gfx/thebes/gfxTextRun.cpp
@@ -1879,17 +1879,20 @@ gfxFontGroup::AddPlatformFont(const nsAC
                              gfxPlatformFontList::FindFamiliesFlags(0),
                              &mStyle, mDevToCssSize);
 }
 
 void
 gfxFontGroup::AddFamilyToFontList(gfxFontFamily* aFamily,
                                   FontFamilyType aGeneric)
 {
-    NS_ASSERTION(aFamily, "trying to add a null font family to fontlist");
+    if (!aFamily) {
+        MOZ_ASSERT_UNREACHABLE("don't try to add a null font family!");
+        return;
+    }
     AutoTArray<gfxFontEntry*,4> fontEntryList;
     aFamily->FindAllFontsForStyle(mStyle, fontEntryList);
     // add these to the fontlist
     for (gfxFontEntry* fe : fontEntryList) {
         if (!HasFont(fe)) {
             FamilyFace ff(aFamily, fe, aGeneric);
             if (fe->mIsUserFontContainer) {
                 ff.CheckState(mSkipDrawing);
--- a/layout/base/nsLayoutUtils.cpp
+++ b/layout/base/nsLayoutUtils.cpp
@@ -9224,36 +9224,43 @@ nsLayoutUtils::ComputeScrollMetadata(nsI
   if (aScrollFrame)
     scrollableFrame = aScrollFrame->GetScrollTargetFrame();
 
   metrics.SetScrollableRect(CSSRect::FromAppUnits(
     nsLayoutUtils::CalculateScrollableRectForFrame(scrollableFrame, aForFrame)));
 
   if (scrollableFrame) {
     CSSPoint scrollPosition = CSSPoint::FromAppUnits(scrollableFrame->GetScrollPosition());
+    CSSPoint apzScrollPosition = CSSPoint::FromAppUnits(scrollableFrame->GetApzScrollPosition());
     metrics.SetScrollOffset(scrollPosition);
+    metrics.SetBaseScrollOffset(apzScrollPosition);
 
     CSSRect viewport = metrics.GetViewport();
     viewport.MoveTo(scrollPosition);
     metrics.SetViewport(viewport);
 
     nsPoint smoothScrollPosition = scrollableFrame->LastScrollDestination();
     metrics.SetSmoothScrollOffset(CSSPoint::FromAppUnits(smoothScrollPosition));
 
     // If the frame was scrolled since the last layers update, and by something
     // that is higher priority than APZ, we want to tell the APZ to update
     // its scroll offset. We want to distinguish the case where the scroll offset
     // was "restored" because in that case the restored scroll position should
     // not overwrite a user-driven scroll.
-    if (scrollableFrame->LastScrollOrigin() == nsGkAtoms::restore) {
-      metrics.SetScrollOffsetRestored(scrollableFrame->CurrentScrollGeneration());
-    } else if (CanScrollOriginClobberApz(scrollableFrame->LastScrollOrigin())) {
-      metrics.SetScrollOffsetUpdated(scrollableFrame->CurrentScrollGeneration());
-    }
-    scrollableFrame->AllowScrollOriginDowngrade();
+    nsAtom* lastOrigin = scrollableFrame->LastScrollOrigin();
+    if (lastOrigin == nsGkAtoms::restore) {
+      metrics.SetScrollGeneration(scrollableFrame->CurrentScrollGeneration());
+      metrics.SetScrollOffsetUpdateType(FrameMetrics::eRestore);
+    } else if (CanScrollOriginClobberApz(lastOrigin)) {
+      if (lastOrigin == nsGkAtoms::relative) {
+        metrics.SetIsRelative(true);
+      }
+      metrics.SetScrollGeneration(scrollableFrame->CurrentScrollGeneration());
+      metrics.SetScrollOffsetUpdateType(FrameMetrics::eMainThread);
+    }
 
     nsAtom* lastSmoothScrollOrigin = scrollableFrame->LastSmoothScrollOrigin();
     if (lastSmoothScrollOrigin) {
       metrics.SetSmoothScrollOffsetUpdated(scrollableFrame->CurrentScrollGeneration());
     }
 
     nsSize lineScrollAmount = scrollableFrame->GetLineScrollAmount();
     LayoutDeviceIntSize lineScrollAmountInDevPixels =
@@ -9732,17 +9739,17 @@ GetPresShell(const nsIContent* aContent)
 {
   nsCOMPtr<nsIPresShell> result;
   if (nsIDocument* doc = aContent->GetComposedDoc()) {
     result = doc->GetShell();
   }
   return result.forget();
 }
 
-static void UpdateDisplayPortMarginsForPendingMetrics(FrameMetrics& aMetrics) {
+static void UpdateDisplayPortMarginsForPendingMetrics(const RepaintRequest& aMetrics) {
   nsIContent* content = nsLayoutUtils::FindContentFor(aMetrics.GetScrollId());
   if (!content) {
     return;
   }
 
   nsCOMPtr<nsIPresShell> shell = GetPresShell(content);
   if (!shell) {
     return;
@@ -9773,39 +9780,39 @@ static void UpdateDisplayPortMarginsForP
 
   DisplayPortMarginsPropertyData* currentData =
     static_cast<DisplayPortMarginsPropertyData*>(content->GetProperty(nsGkAtoms::DisplayPortMargins));
   if (!currentData) {
     return;
   }
 
   CSSPoint frameScrollOffset = CSSPoint::FromAppUnits(frame->GetScrollPosition());
-  APZCCallbackHelper::AdjustDisplayPortForScrollDelta(aMetrics, frameScrollOffset);
+  ScreenMargin displayPortMargins = APZCCallbackHelper::AdjustDisplayPortForScrollDelta(aMetrics, frameScrollOffset);
 
   nsLayoutUtils::SetDisplayPortMargins(content, shell,
-                                       aMetrics.GetDisplayPortMargins(), 0);
+                                       displayPortMargins, 0);
 }
 
 /* static */ void
 nsLayoutUtils::UpdateDisplayPortMarginsFromPendingMessages()
 {
   if (XRE_IsContentProcess() &&
       mozilla::layers::CompositorBridgeChild::Get() &&
       mozilla::layers::CompositorBridgeChild::Get()->GetIPCChannel()) {
     CompositorBridgeChild::Get()->GetIPCChannel()->PeekMessages(
       [](const IPC::Message& aMsg) -> bool {
         if (aMsg.type() == mozilla::layers::PAPZ::Msg_RequestContentRepaint__ID) {
           PickleIterator iter(aMsg);
-          FrameMetrics frame;
-          if (!IPC::ReadParam(&aMsg, &iter, &frame)) {
+          RepaintRequest request;
+          if (!IPC::ReadParam(&aMsg, &iter, &request)) {
             MOZ_ASSERT(false);
             return true;
           }
 
-          UpdateDisplayPortMarginsForPendingMetrics(frame);
+          UpdateDisplayPortMarginsForPendingMetrics(request);
         }
         return true;
       });
   }
 }
 
 /* static */ bool
 nsLayoutUtils::IsTransformed(nsIFrame* aForFrame, nsIFrame* aTopFrame)
--- a/layout/build/nsLayoutStatics.cpp
+++ b/layout/build/nsLayoutStatics.cpp
@@ -346,17 +346,16 @@ nsLayoutStatics::Shutdown()
 
   nsAttrValue::Shutdown();
   nsContentUtils::Shutdown();
   nsLayoutStylesheetCache::Shutdown();
 
   ShutdownJSEnvironment();
   nsGlobalWindowInner::ShutDown();
   nsGlobalWindowOuter::ShutDown();
-  WebIDLGlobalNameHash::Shutdown();
   nsListControlFrame::Shutdown();
   nsXBLService::Shutdown();
   FrameLayerBuilder::Shutdown();
 
   CubebUtils::ShutdownLibrary();
   WebAudioUtils::Shutdown();
 
   nsCORSListenerProxy::Shutdown();
--- a/layout/generic/nsGfxScrollFrame.cpp
+++ b/layout/generic/nsGfxScrollFrame.cpp
@@ -1520,17 +1520,17 @@ ScrollFrameHelper::ThumbMoved(nsScrollba
 
   // Don't try to scroll if we're already at an acceptable place.
   // Don't call Contains here since Contains returns false when the point is
   // on the bottom or right edge of the rectangle.
   if (allowedRange.ClampPoint(current) == current) {
     return;
   }
 
-  ScrollTo(dest, nsIScrollableFrame::INSTANT, &allowedRange);
+  ScrollTo(dest, nsIScrollableFrame::INSTANT, nsGkAtoms::other, &allowedRange);
 }
 
 void
 ScrollFrameHelper::ScrollbarReleased(nsScrollbarFrame* aScrollbar)
 {
   // Scrollbar scrolling does not result in fling gestures, clear any
   // accumulated velocity
   mVelocityQueue.Reset();
@@ -2035,17 +2035,17 @@ ComputeBezierAnimationSettingsForOrigin(
 void
 ScrollFrameHelper::AsyncScroll::InitSmoothScroll(TimeStamp aTime,
                                                  nsPoint aInitialPosition,
                                                  nsPoint aDestination,
                                                  nsAtom *aOrigin,
                                                  const nsRect& aRange,
                                                  const nsSize& aCurrentVelocity)
 {
-  if (!aOrigin || aOrigin == nsGkAtoms::restore) {
+  if (!aOrigin || aOrigin == nsGkAtoms::restore || aOrigin == nsGkAtoms::relative) {
     // We don't have special prefs for "restore", just treat it as "other".
     // "restore" scrolls are (for now) always instant anyway so unless something
     // changes we should never have aOrigin == nsGkAtoms::restore here.
     aOrigin = nsGkAtoms::other;
   }
   // Likewise we should never get APZ-triggered scrolls here, and if that changes
   // something is likely broken somewhere.
   MOZ_ASSERT(aOrigin != nsGkAtoms::apz);
@@ -2115,19 +2115,19 @@ ScrollFrameHelper::ScrollFrameHelper(nsC
   , mReferenceFrameDuringPainting(nullptr)
   , mAsyncScroll(nullptr)
   , mAsyncSmoothMSDScroll(nullptr)
   , mLastScrollOrigin(nsGkAtoms::other)
   , mAllowScrollOriginDowngrade(false)
   , mLastSmoothScrollOrigin(nullptr)
   , mScrollGeneration(++sScrollGenerationCounter)
   , mDestination(0, 0)
-  , mScrollPosAtLastPaint(0, 0)
   , mRestorePos(-1, -1)
   , mLastPos(-1, -1)
+  , mApzScrollPos(0, 0)
   , mScrollPosForLayerPixelAlignment(-1, -1)
   , mLastUpdateFramesPos(-1, -1)
   , mHadDisplayPortAtLastFrameUpdate(false)
   , mDisplayPortAtLastFrameUpdate()
   , mScrollParentID(mozilla::layers::FrameMetrics::NULL_SCROLL_ID)
   , mNeverHasVerticalScrollbar(false)
   , mNeverHasHorizontalScrollbar(false)
   , mHasVerticalScrollbar(false)
@@ -2290,18 +2290,34 @@ ScrollFrameHelper::HasPluginFrames()
 bool
 ScrollFrameHelper::HasBgAttachmentLocal() const
 {
   const nsStyleBackground* bg = mOuter->StyleBackground();
   return bg->HasLocalBackground();
 }
 
 void
+ScrollFrameHelper::ScrollTo(nsPoint aScrollPosition,
+                            nsIScrollableFrame::ScrollMode aMode,
+                            nsAtom* aOrigin,
+                            const nsRect* aRange,
+                            nsIScrollbarMediator::ScrollSnapMode aSnap)
+{
+  if (aOrigin == nullptr) {
+    aOrigin = nsGkAtoms::other;
+  }
+  ScrollToWithOrigin(aScrollPosition, aMode,
+                     aOrigin, aRange,
+                     aSnap);
+}
+
+void
 ScrollFrameHelper::ScrollToCSSPixels(const CSSIntPoint& aScrollPosition,
-                                     nsIScrollableFrame::ScrollMode aMode)
+                                     nsIScrollableFrame::ScrollMode aMode,
+                                     nsAtom* aOrigin)
 {
   nsPoint current = GetScrollPosition();
   CSSIntPoint currentCSSPixels = GetScrollPositionCSSPixels();
   nsPoint pt = CSSPoint::ToAppUnits(aScrollPosition);
   nscoord halfPixel = nsPresContext::CSSPixelsToAppUnits(0.5f);
   nsRect range(pt.x - halfPixel, pt.y - halfPixel, 2*halfPixel - 1, 2*halfPixel - 1);
   // XXX I don't think the following blocks are needed anymore, now that
   // ScrollToImpl simply tries to scroll an integer number of layer
@@ -2311,17 +2327,20 @@ ScrollFrameHelper::ScrollToCSSPixels(con
     range.x = pt.x;
     range.width = 0;
   }
   if (currentCSSPixels.y == aScrollPosition.y) {
     pt.y = current.y;
     range.y = pt.y;
     range.height = 0;
   }
-  ScrollTo(pt, aMode, &range);
+  if (aOrigin == nullptr) {
+    aOrigin = nsGkAtoms::other;
+  }
+  ScrollTo(pt, aMode, aOrigin, &range);
   // 'this' might be destroyed here
 }
 
 void
 ScrollFrameHelper::ScrollToCSSPixelsApproximate(const CSSPoint& aScrollPosition,
                                                 nsAtom *aOrigin)
 {
   nsPoint pt = CSSPoint::ToAppUnits(aScrollPosition);
@@ -2900,32 +2919,47 @@ ScrollFrameHelper::ScrollToImpl(nsPoint 
   nsRect oldDisplayPort;
   nsIContent* content = mOuter->GetContent();
   nsLayoutUtils::GetHighResolutionDisplayPort(content, &oldDisplayPort);
   oldDisplayPort.MoveBy(-mScrolledFrame->GetPosition());
 
   // Update frame position for scrolling
   mScrolledFrame->SetPosition(mScrollPort.TopLeft() - pt);
 
+  // If this scroll is |relative|, but we've already had a user scroll that
+  // was not relative, promote this origin to |other|. This ensures that we
+  // may only transmit a relative update to APZ if all scrolls since the last
+  // transaction or repaint request have been relative.
+  if (aOrigin == nsGkAtoms::relative &&
+      (mLastScrollOrigin &&
+       mLastScrollOrigin != nsGkAtoms::relative &&
+       mLastScrollOrigin != nsGkAtoms::apz)) {
+    aOrigin = nsGkAtoms::other;
+  }
+
   // If |mLastScrollOrigin| is already set to something that can clobber APZ's
   // scroll offset, then we don't want to change it to something that can't.
   // If we allowed this, then we could end up in a state where APZ ignores
   // legitimate scroll offset updates because the origin has been masked by
   // a later change within the same refresh driver tick.
   bool isScrollOriginDowngrade =
     nsLayoutUtils::CanScrollOriginClobberApz(mLastScrollOrigin) &&
     !nsLayoutUtils::CanScrollOriginClobberApz(aOrigin);
   bool allowScrollOriginChange = mAllowScrollOriginDowngrade ||
     !isScrollOriginDowngrade;
+
   if (allowScrollOriginChange) {
     mLastScrollOrigin = aOrigin;
     mAllowScrollOriginDowngrade = false;
   }
   mLastSmoothScrollOrigin = nullptr;
   mScrollGeneration = ++sScrollGenerationCounter;
+  if (mLastScrollOrigin == nsGkAtoms::apz) {
+    mApzScrollPos = GetScrollPosition();
+  }
 
   // If the new scroll offset is going to clobber APZ's scroll offset, for
   // the RCD-RSF this will have the effect of resetting the visual viewport
   // offset to be the same as the new scroll (= layout viewport) offset.
   // The APZ callback transform, which reflects the difference between these
   // offsets, will subsequently be cleared. However, it we wait for APZ to
   // clear it, the main thread could end up using the old value and get
   // incorrect results, so just clear it now.
@@ -2983,17 +3017,22 @@ ScrollFrameHelper::ScrollToImpl(nsPoint 
             mozilla::layers::FrameMetrics::ViewID id;
             bool success = nsLayoutUtils::FindIDFor(content, &id);
             MOZ_ASSERT(success); // we have a displayport, we better have an ID
 
             // Schedule an empty transaction to carry over the scroll offset update,
             // instead of a full transaction. This empty transaction might still get
             // squashed into a full transaction if something happens to trigger one.
             success = manager->SetPendingScrollUpdateForNextTransaction(id,
-                { mScrollGeneration, CSSPoint::FromAppUnits(GetScrollPosition()) });
+                {
+                  mScrollGeneration,
+                  CSSPoint::FromAppUnits(GetScrollPosition()),
+                  CSSPoint::FromAppUnits(GetApzScrollPosition()),
+                  mLastScrollOrigin == nsGkAtoms::relative
+                });
             if (success) {
               schedulePaint = false;
               mOuter->SchedulePaint(nsIFrame::PAINT_COMPOSITE_ONLY);
               PAINT_SKIP_LOG("Skipping due to APZ-forwarded main-thread scroll\n");
             } else {
               PAINT_SKIP_LOG("Failed to set pending scroll update on layer manager\n");
             }
           }
@@ -3390,20 +3429,19 @@ ScrollFrameHelper::BuildDisplayList(nsDi
   SetAndNullOnExit<const nsIFrame> tmpBuilder(mReferenceFrameDuringPainting, aBuilder->GetCurrentReferenceFrame());
   if (aBuilder->IsForFrameVisibility()) {
     NotifyApproximateFrameVisibilityUpdate(false);
   }
 
   mOuter->DisplayBorderBackgroundOutline(aBuilder, aLists);
 
   if (aBuilder->IsPaintingToWindow()) {
-    mScrollPosAtLastPaint = GetScrollPosition();
     if (IsMaybeScrollingActive()) {
       if (mScrollPosForLayerPixelAlignment == nsPoint(-1,-1)) {
-        mScrollPosForLayerPixelAlignment = mScrollPosAtLastPaint;
+        mScrollPosForLayerPixelAlignment = GetScrollPosition();
       }
     } else {
       mScrollPosForLayerPixelAlignment = nsPoint(-1,-1);
     }
   }
 
   // It's safe to get this value before the DecideScrollableLayer call below
   // because that call cannot create a displayport for root scroll frames,
@@ -4226,17 +4264,17 @@ ScrollFrameHelper::ScrollBy(nsIntPoint a
   }
   case nsIScrollableFrame::WHOLE: {
     nsPoint pos = GetScrollPosition();
     AdjustForWholeDelta(aDelta.x, &pos.x);
     AdjustForWholeDelta(aDelta.y, &pos.y);
     if (aSnap == nsIScrollableFrame::ENABLE_SNAP) {
       GetSnapPointForDestination(aUnit, mDestination, pos);
     }
-    ScrollTo(pos, aMode);
+    ScrollTo(pos, aMode, nsGkAtoms::other);
     // 'this' might be destroyed here
     if (aOverflow) {
       *aOverflow = nsIntPoint(0, 0);
     }
     return;
   }
   default:
     NS_ERROR("Invalid scroll mode");
@@ -4292,16 +4330,46 @@ ScrollFrameHelper::ScrollBy(nsIntPoint a
       !nsLayoutUtils::AsyncPanZoomEnabled(mOuter)) {
     // When APZ is disabled, we must track the velocity
     // on the main thread; otherwise, the APZC will manage this.
     mVelocityQueue.Sample(GetScrollPosition());
   }
 }
 
 void
+ScrollFrameHelper::ScrollByCSSPixels(const CSSIntPoint& aDelta,
+                                     nsIScrollableFrame::ScrollMode aMode,
+                                     nsAtom* aOrigin)
+{
+  nsPoint current = GetScrollPosition();
+  nsPoint pt = current + CSSPoint::ToAppUnits(aDelta);
+  nscoord halfPixel = nsPresContext::CSSPixelsToAppUnits(0.5f);
+  nsRect range(pt.x - halfPixel, pt.y - halfPixel, 2*halfPixel - 1, 2*halfPixel - 1);
+  // XXX I don't think the following blocks are needed anymore, now that
+  // ScrollToImpl simply tries to scroll an integer number of layer
+  // pixels from the current position
+  if (aDelta.x == 0.0f) {
+    pt.x = current.x;
+    range.x = pt.x;
+    range.width = 0;
+  }
+  if (aDelta.y == 0.0f) {
+    pt.y = current.y;
+    range.y = pt.y;
+    range.height = 0;
+  }
+  if (aOrigin == nullptr) {
+    aOrigin = nsGkAtoms::other;
+  }
+  ScrollToWithOrigin(pt, aMode, aOrigin, &range,
+                     nsIScrollbarMediator::DISABLE_SNAP);
+  // 'this' might be destroyed here
+}
+
+void
 ScrollFrameHelper::ScrollSnap(nsIScrollableFrame::ScrollMode aMode)
 {
   float flingSensitivity = gfxPrefs::ScrollSnapPredictionSensitivity();
   int maxVelocity = gfxPrefs::ScrollSnapPredictionMaxVelocity();
   maxVelocity = nsPresContext::CSSPixelsToAppUnits(maxVelocity);
   int maxOffset = maxVelocity * flingSensitivity;
   nsPoint velocity = mVelocityQueue.GetVelocity();
   // Multiply each component individually to avoid integer multiply
@@ -4318,17 +4386,17 @@ ScrollFrameHelper::ScrollSnap(const nsPo
                               nsIScrollableFrame::ScrollMode aMode)
 {
   nsRect scrollRange = GetScrollRangeForClamping();
   nsPoint pos = GetScrollPosition();
   nsPoint snapDestination = scrollRange.ClampPoint(aDestination);
   if (GetSnapPointForDestination(nsIScrollableFrame::DEVICE_PIXELS,
                                                  pos,
                                                  snapDestination)) {
-    ScrollTo(snapDestination, aMode);