Merge mozilla-central to autoland
authorCarsten "Tomcat" Book <cbook@mozilla.com>
Wed, 28 Jun 2017 13:47:29 +0200
changeset 415145 bb84de62ac1decef0192f36c564c24cf9e92acf9
parent 415144 2b51a135ae25aad96c7f77da257515dc8094b625 (current diff)
parent 415100 306d2070e105b75f7076d30cc0288171f1435c07 (diff)
child 415146 1f29ce5d69da70faf32e91bec962d56076a22545
push id7566
push usermtabara@mozilla.com
push dateWed, 02 Aug 2017 08:25:16 +0000
treeherdermozilla-beta@86913f512c3c [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
milestone56.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Merge mozilla-central to autoland
browser/base/content/nsContextMenu.js
gfx/layers/ipc/CompositorBridgeParent.cpp
gfx/layers/ipc/CompositorBridgeParent.h
gfx/layers/ipc/CrossProcessCompositorBridgeParent.cpp
gfx/layers/ipc/LayersMessages.ipdlh
gfx/layers/wr/WebRenderBridgeParent.cpp
ipc/ipdl/sync-messages.ini
memory/mozalloc/msvc_raise_wrappers.h
memory/mozalloc/throw_msvc.h
--- a/.clang-format-ignore
+++ b/.clang-format-ignore
@@ -7,22 +7,19 @@
 ^widget/android/GeneratedJNIWrappers.h
 ^widget/android/fennec/FennecJNINatives.h
 ^widget/android/fennec/FennecJNIWrappers.cpp
 ^widget/android/fennec/FennecJNIWrappers.h
 
 # Generated from ./tools/rewriting/ThirdPartyPaths.txt
 # awk '{print "^"$1".*"}' ./tools/rewriting/ThirdPartyPaths.txt
 ^browser/components/translation/cld2/.*
-^build/stlport/.*
 ^db/sqlite3/src/.*
 ^dom/media/platforms/ffmpeg/libav.*
 ^extensions/spellcheck/hunspell/src/.*
-^gfx/2d/convolver.*
-^gfx/2d/image_operations.*
 ^gfx/angle/.*
 ^gfx/cairo/.*
 ^gfx/graphite2/.*
 ^gfx/harfbuzz/.*
 ^gfx/ots/.*
 ^gfx/qcms/.*
 ^gfx/skia/.*
 ^gfx/vr/openvr/.*
--- a/accessible/ipc/DocAccessibleParent.cpp
+++ b/accessible/ipc/DocAccessibleParent.cpp
@@ -28,18 +28,17 @@ DocAccessibleParent::RecvShowEvent(const
                                    const bool& aFromUser)
 {
   if (mShutdown)
     return IPC_OK();
 
   MOZ_ASSERT(CheckDocTree());
 
   if (aData.NewTree().IsEmpty()) {
-    NS_ERROR("no children being added");
-    return IPC_FAIL_NO_REASON(this);
+    return IPC_FAIL(this, "No children being added");
   }
 
   ProxyAccessible* parent = GetAccessible(aData.ID());
 
   // XXX This should really never happen, but sometimes we fail to fire the
   // required show events.
   if (!parent) {
     NS_ERROR("adding child to unknown accessible");
@@ -153,18 +152,17 @@ DocAccessibleParent::RecvHideEvent(const
   if (mShutdown)
     return IPC_OK();
 
   MOZ_ASSERT(CheckDocTree());
 
   // We shouldn't actually need this because mAccessibles shouldn't have an
   // entry for the document itself, but it doesn't hurt to be explicit.
   if (!aRootID) {
-    NS_ERROR("trying to hide entire document?");
-    return IPC_FAIL_NO_REASON(this);
+    return IPC_FAIL(this, "Trying to hide entire document?");
   }
 
   ProxyEntry* rootEntry = mAccessibles.GetEntry(aRootID);
   if (!rootEntry) {
     NS_ERROR("invalid root being removed!");
     return IPC_OK();
   }
 
@@ -393,18 +391,17 @@ DocAccessibleParent::RecvSelectionEvent(
 mozilla::ipc::IPCResult
 DocAccessibleParent::RecvRoleChangedEvent(const uint32_t& aRole)
 {
   if (mShutdown) {
     return IPC_OK();
   }
 
  if (aRole > roles::LAST_ROLE) {
-   NS_ERROR("child sent bad role in RoleChangedEvent");
-   return IPC_FAIL_NO_REASON(this);
+   return IPC_FAIL(this, "Child sent bad role in RoleChangedEvent");
  }
 
  mRole = static_cast<a11y::role>(aRole);
  return IPC_OK();
 }
 
 mozilla::ipc::IPCResult
 DocAccessibleParent::RecvBindChildDoc(PDocAccessibleParent* aChildDoc, const uint64_t& aID)
--- a/accessible/mac/mozAccessible.h
+++ b/accessible/mac/mozAccessible.h
@@ -109,16 +109,19 @@ static const uintptr_t IS_PROXY = 1;
 - (id)value;
 
 // name that is associated with this accessible (for buttons, etc)
 - (NSString*)title;
 
 // the accessible description (help text) of this particular instance.
 - (NSString*)help;
 
+// returns the orientation (vertical, horizontal, or undefined)
+- (NSString*)orientation;
+
 - (BOOL)isEnabled;
 
 // information about focus.
 - (BOOL)isFocused;
 - (BOOL)canBeFocused;
 
 // returns NO if for some reason we were unable to focus the element.
 - (BOOL)focus;
--- a/accessible/mac/mozAccessible.mm
+++ b/accessible/mac/mozAccessible.mm
@@ -318,16 +318,19 @@ ConvertToNSArray(nsTArray<ProxyAccessibl
       return tempAcc ? GetNativeFromGeckoAccessible(tempAcc) : nil;
     }
     nsTArray<ProxyAccessible*> rel = proxy->RelationByType(RelationType::LABELLED_BY);
     ProxyAccessible* tempProxy = rel.SafeElementAt(0);
     return tempProxy ? GetNativeFromProxy(tempProxy) : nil;
   }
   if ([attribute isEqualToString:NSAccessibilityHelpAttribute])
     return [self help];
+  if ([attribute isEqualToString:NSAccessibilityOrientationAttribute])
+    return [self orientation];
+
   if ([attribute isEqualToString:NSAccessibilityDOMIdentifierAttribute]) {
     nsAutoString id;
     if (accWrap)
       nsCoreUtils::GetID(accWrap->GetContent(), id);
     else
       proxy->DOMNodeID(id);
     return nsCocoaUtils::ToNSString(id);
   }
@@ -1062,16 +1065,38 @@ struct RoleDescrComparator
   else if (ProxyAccessible* proxy = [self getProxyAccessible])
     proxy->Description(helpText);
 
   return nsCocoaUtils::ToNSString(helpText);
 
   NS_OBJC_END_TRY_ABORT_BLOCK_NIL;
 }
 
+- (NSString*)orientation
+{
+  NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL;
+
+  uint64_t state;
+  if (AccessibleWrap* accWrap = [self getGeckoAccessible])
+    state = accWrap->InteractiveState();
+  else if (ProxyAccessible* proxy = [self getProxyAccessible])
+    state = proxy->State();
+  else
+    state = 0;
+
+  if (state & states::HORIZONTAL)
+    return NSAccessibilityHorizontalOrientationValue;
+  if (state & states::VERTICAL)
+    return NSAccessibilityVerticalOrientationValue;
+
+  return NSAccessibilityUnknownOrientationValue;
+
+  NS_OBJC_END_TRY_ABORT_BLOCK_NIL;
+}
+
 // objc-style description (from NSObject); not to be confused with the accessible description above.
 - (NSString*)description
 {
   NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL;
 
   return [NSString stringWithFormat:@"(%p) %@", self, [self role]];
 
   NS_OBJC_END_TRY_ABORT_BLOCK_NIL;
--- a/browser/base/content/moz.build
+++ b/browser/base/content/moz.build
@@ -38,16 +38,19 @@ with Files("test/forms/**"):
     BUG_COMPONENT = ("Core", "Layout: Form Controls")
 
 with Files("test/newtab/**"):
     BUG_COMPONENT = ("Firefox", "New Tab Page")
 
 with Files("test/pageinfo/**"):
     BUG_COMPONENT = ("Firefox", "Page Info Window")
 
+with Files("test/performance/browser_appmenu_reflows.js"):
+    BUG_COMPONENT = ("Firefox", "Menus")
+
 with Files("test/permissions/**"):
     BUG_COMPONENT = ("Firefox", "Preferences")
 
 with Files("test/plugins/**"):
     BUG_COMPONENT = ("Core", "Plug-ins")
 
 with Files("test/popupNotifications/**"):
     BUG_COMPONENT = ("Toolkit", "Notifications and Alerts")
--- a/browser/base/content/nsContextMenu.js
+++ b/browser/base/content/nsContextMenu.js
@@ -109,16 +109,17 @@ nsContextMenu.prototype = {
         onVideo: this.onVideo,
         onAudio: this.onAudio,
         onCanvas: this.onCanvas,
         onEditableArea: this.onEditableArea,
         onPassword: this.onPassword,
         srcUrl: this.mediaURL,
         frameUrl: gContextMenuContentData ? gContextMenuContentData.docLocation : undefined,
         pageUrl: this.browser ? this.browser.currentURI.spec : undefined,
+        linkText: this.linkTextStr,
         linkUrl: this.linkURL,
         selectionText: this.isTextSelected ? this.selectionInfo.text : undefined,
         frameId: this.frameOuterWindowID,
       };
       subject.wrappedJSObject = subject;
       Services.obs.notifyObservers(subject, "on-build-contextmenu");
     }
 
--- a/browser/base/content/test/urlbar/browser.ini
+++ b/browser/base/content/test/urlbar/browser.ini
@@ -97,16 +97,17 @@ support-files =
 support-files =
   searchSuggestionEngine.xml
   searchSuggestionEngine.sjs
 [browser_urlbarStop.js]
 [browser_urlbarTrimURLs.js]
 subsuite = clipboard
 [browser_urlbarUpdateForDomainCompletion.js]
 [browser_urlbar_autoFill_backspaced.js]
+[browser_urlbar_canonize_on_autofill.js]
 [browser_urlbar_blanking.js]
 support-files =
   file_blank_but_not_blank.html
 [browser_urlbar_locationchange_urlbar_edit_dos.js]
 support-files =
   file_urlbar_edit_dos.html
 [browser_urlbar_searchsettings.js]
 [browser_urlbar_stop_pending.js]
--- a/browser/base/content/test/urlbar/browser_urlbarSearchSuggestions.js
+++ b/browser/base/content/test/urlbar/browser_urlbarSearchSuggestions.js
@@ -35,16 +35,51 @@ add_task(async function clickSuggestion(
   let uri = Services.search.currentEngine.getSubmission(suggestion).uri;
   let loadPromise = BrowserTestUtils.browserLoaded(gBrowser.selectedBrowser,
                                                    false, uri.spec);
   item.click();
   await loadPromise;
   await BrowserTestUtils.removeTab(tab);
 });
 
+async function testPressEnterOnSuggestion(expectedUrl = null, keyModifiers = {}) {
+  let tab = await BrowserTestUtils.openNewForegroundTab(gBrowser);
+  gURLBar.focus();
+  await promiseAutocompleteResultPopup("foo");
+  let [idx, suggestion, engineName] = await promiseFirstSuggestion();
+  Assert.equal(engineName,
+               "browser_searchSuggestionEngine%20searchSuggestionEngine.xml",
+               "Expected suggestion engine");
+
+  if (!expectedUrl) {
+    expectedUrl = Services.search.currentEngine.getSubmission(suggestion).uri.spec;
+  }
+
+  let promiseLoad = waitForDocLoadAndStopIt(expectedUrl);
+
+  for (let i = 0; i < idx; ++i) {
+    EventUtils.synthesizeKey("VK_DOWN", {});
+  }
+  EventUtils.synthesizeKey("VK_RETURN", keyModifiers);
+
+  await promiseLoad;
+  await BrowserTestUtils.removeTab(tab);
+}
+
+add_task(async function plainEnterOnSuggestion() {
+  await testPressEnterOnSuggestion();
+});
+
+add_task(async function ctrlEnterOnSuggestion() {
+  await testPressEnterOnSuggestion("http://www.foofoo.com/",
+                                   AppConstants.platform === "macosx" ?
+                                     { metaKey: true } :
+                                     { ctrlKey: true });
+});
+
 function getFirstSuggestion() {
   let controller = gURLBar.popup.input.controller;
   let matchCount = controller.matchCount;
   for (let i = 0; i < matchCount; i++) {
     let url = controller.getValueAt(i);
     let mozActionMatch = url.match(/^moz-action:([^,]+),(.*)$/);
     if (mozActionMatch) {
       let [, type, paramStr] = mozActionMatch;
new file mode 100644
--- /dev/null
+++ b/browser/base/content/test/urlbar/browser_urlbar_canonize_on_autofill.js
@@ -0,0 +1,64 @@
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/* This test ensures that pressing ctrl/shift+enter bypasses the autoFilled
+ * value, and only considers what the user typed (but not just enter).
+ */
+
+async function test_autocomplete(data) {
+  let {desc, typed, autofilled, modified, waitForUrl, keys} = data;
+  info(desc);
+
+  await promiseAutocompleteResultPopup(typed);
+  is(gURLBar.textValue, autofilled, "autofilled value is as expected");
+
+  let promiseLoad = waitForDocLoadAndStopIt(waitForUrl);
+
+  keys.forEach(([key, mods]) => EventUtils.synthesizeKey(key, mods || {}));
+
+  is(gURLBar.textValue, modified, "value is as expected");
+
+  await promiseLoad;
+  gURLBar.blur();
+}
+
+add_task(async function() {
+  registerCleanupFunction(async function() {
+    Services.prefs.clearUserPref("browser.urlbar.autoFill");
+    gURLBar.handleRevert();
+    await PlacesTestUtils.clearHistory();
+  });
+  Services.prefs.setBoolPref("browser.urlbar.autoFill", true);
+
+  // Add a typed visit, so it will be autofilled.
+  await PlacesTestUtils.addVisits({
+    uri: "http://example.com/",
+    transition: Ci.nsINavHistoryService.TRANSITION_TYPED
+  });
+
+  await test_autocomplete({ desc: "CTRL+ENTER on the autofilled part should use autofill",
+                            typed: "exam",
+                            autofilled: "example.com/",
+                            modified: "example.com/",
+                            waitForUrl: "http://example.com/",
+                            keys: [["VK_RETURN", {}]]
+                          });
+
+  await test_autocomplete({ desc: "CTRL+ENTER on the autofilled part should bypass autofill",
+                            typed: "exam",
+                            autofilled: "example.com/",
+                            modified: "www.exam.com",
+                            waitForUrl: "http://www.exam.com/",
+                            keys: [["VK_RETURN", AppConstants.platform === "macosx" ?
+                                                 { metaKey: true } :
+                                                 { ctrlKey: true }]]
+                          });
+
+  await test_autocomplete({ desc: "SHIFT+ENTER on the autofilled part should bypass autofill",
+                            typed: "exam",
+                            autofilled: "example.com/",
+                            modified: "www.exam.net",
+                            waitForUrl: "http://www.exam.net/",
+                            keys: [["VK_RETURN", { shiftKey: true }]]
+                          });
+});
--- a/browser/base/content/urlbarBindings.xml
+++ b/browser/base/content/urlbarBindings.xml
@@ -1312,17 +1312,29 @@ file, You can obtain one at http://mozil
           // ensure that it corresponds to the current input.
 
           // Store the current search string so it can be used in handleCommand,
           // which will be called as a result of mController.handleEnter().
           this.handleEnterSearchString = this.mController.searchString;
 
           if (!this._deferredKeyEventQueue.length &&
               (this.popup.selectedIndex != 0 || this.gotResultForCurrentQuery)) {
-            this.maybeCanonizeURL(event, this.value);
+            let canonizeValue = this.value;
+            if (event.shiftKey || (AppConstants.platform === "macosx" ?
+                                   event.metaKey :
+                                   event.ctrlKey)) {
+              let action = this._parseActionUrl(canonizeValue);
+              if (action && "searchSuggestion" in action.params) {
+                canonizeValue = action.params.searchSuggestion;
+              } else if (this.popup.selectedIndex === 0 &&
+                         this.mController.getStyleAt(0).includes("autofill")) {
+                canonizeValue = this.handleEnterSearchString;
+              }
+            }
+            this.maybeCanonizeURL(event, canonizeValue);
             let handled = this.mController.handleEnter(false, event);
             this.handleEnterSearchString = null;
             this.popup.overrideValue = null;
             return handled;
           }
 
           // Defer the event until the first non-heuristic result comes in.
           this._deferKeyEvent(event, "handleEnter");
--- a/browser/components/extensions/ext-history.js
+++ b/browser/components/extensions/ext-history.js
@@ -91,21 +91,21 @@ function convertNavHistoryContainerResul
 var _observer;
 
 function getObserver() {
   if (!_observer) {
     _observer = {
       onDeleteURI: function(uri, guid, reason) {
         this.emit("visitRemoved", {allHistory: false, urls: [uri.spec]});
       },
-      onVisit: function(uri, visitId, time, sessionId, referringId, transitionType, guid, hidden, visitCount, typed) {
+      onVisit: function(uri, visitId, time, sessionId, referringId, transitionType, guid, hidden, visitCount, typed, lastKnownTitle) {
         let data = {
           id: guid,
           url: uri.spec,
-          title: "",
+          title: lastKnownTitle || "",
           lastVisitTime: time / 1000,  // time from Places is microseconds,
           visitCount,
           typedCount: typed,
         };
         this.emit("visited", data);
       },
       onBeginUpdateBatch: function() {},
       onEndUpdateBatch: function() {},
--- a/browser/components/extensions/ext-menus.js
+++ b/browser/components/extensions/ext-menus.js
@@ -524,16 +524,17 @@ MenuItem.prototype = {
     function setIfDefined(argName, value) {
       if (value !== undefined) {
         info[argName] = value;
       }
     }
 
     setIfDefined("parentMenuItemId", this.parentId);
     setIfDefined("mediaType", mediaType);
+    setIfDefined("linkText", contextData.linkText);
     setIfDefined("linkUrl", contextData.linkUrl);
     setIfDefined("srcUrl", contextData.srcUrl);
     setIfDefined("pageUrl", contextData.pageUrl);
     setIfDefined("frameUrl", contextData.frameUrl);
     setIfDefined("frameId", contextData.frameId);
     setIfDefined("selectionText", contextData.selectionText);
 
     if ((this.type === "checkbox") || (this.type === "radio")) {
--- a/browser/components/extensions/schemas/menus.json
+++ b/browser/components/extensions/schemas/menus.json
@@ -67,16 +67,21 @@
             "optional": true,
             "description": "The parent ID, if any, for the item clicked."
           },
           "mediaType": {
             "type": "string",
             "optional": true,
             "description": "One of 'image', 'video', or 'audio' if the context menu was activated on one of these types of elements."
           },
+          "linkText": {
+            "type": "string",
+            "optional": true,
+            "description": "If the element is a link, the text of that link."
+          },
           "linkUrl": {
             "type": "string",
             "optional": true,
             "description": "If the element is a link, the URL it points to."
           },
           "srcUrl": {
             "type": "string",
             "optional": true,
--- a/browser/components/extensions/test/browser/browser_ext_contextMenus.js
+++ b/browser/components/extensions/test/browser/browser_ext_contextMenus.js
@@ -68,17 +68,17 @@ add_task(async function() {
         browser.test.sendMessage("browser.contextMenus.onClicked", {info, tab});
       });
 
       browser.contextMenus.create({
         contexts: ["all"],
         type: "separator",
       });
 
-      let contexts = ["page", "selection", "image", "editable", "password"];
+      let contexts = ["page", "link", "selection", "image", "editable", "password"];
       for (let i = 0; i < contexts.length; i++) {
         let context = contexts[i];
         let title = context;
         browser.contextMenus.create({
           title: title,
           contexts: [context],
           id: "ext-" + context,
           onclick: genericOnClick,
@@ -178,16 +178,40 @@ add_task(async function() {
   await closeExtensionContextMenu(image);
 
   let result = await extension.awaitMessage("onclick");
   checkClickInfo(result);
   result = await extension.awaitMessage("browser.contextMenus.onClicked");
   checkClickInfo(result);
 
 
+  // Test "link" context and OnClick data property.
+  extensionMenuRoot = await openExtensionContextMenu("[href=some-link]");
+
+  // Click on ext-link and check the click results
+  items = extensionMenuRoot.getElementsByAttribute("label", "link");
+  is(items.length, 1, "contextMenu item for parent was found (context=link)");
+  let link = items[0];
+
+  expectedClickInfo = {
+    menuItemId: "ext-link",
+    linkUrl: "http://mochi.test:8888/browser/browser/components/extensions/test/browser/some-link",
+    linkText: "Some link",
+    pageUrl: PAGE,
+    editable: false,
+  };
+
+  await closeExtensionContextMenu(link);
+
+  result = await extension.awaitMessage("onclick");
+  checkClickInfo(result);
+  result = await extension.awaitMessage("browser.contextMenus.onClicked");
+  checkClickInfo(result);
+
+
   // Test "editable" context and OnClick data property.
   extensionMenuRoot = await openExtensionContextMenu("#edit-me");
 
   // Check some menu items.
   items = extensionMenuRoot.getElementsByAttribute("label", "editable");
   is(items.length, 1, "contextMenu item for text input element was found (context=editable)");
   let editable = items[0];
 
--- a/browser/components/extensions/test/xpcshell/test_ext_history.js
+++ b/browser/components/extensions/test/xpcshell/test_ext_history.js
@@ -468,17 +468,17 @@ add_task(async function test_on_visited(
   let onVisitedData = await extension.awaitMessage("on-visited-data");
 
   function checkOnVisitedData(index, expected) {
     let onVisited = onVisitedData[index];
     ok(PlacesUtils.isValidGuid(onVisited.id), "onVisited received a valid id");
     equal(onVisited.url, expected.url, "onVisited received the expected url");
     // Title will be blank until bug 1287928 lands
     // https://bugzilla.mozilla.org/show_bug.cgi?id=1287928
-    equal(onVisited.title, "", "onVisited received a blank title");
+    equal(onVisited.title, expected.title, "onVisited received the expected title");
     equal(onVisited.lastVisitTime, expected.time, "onVisited received the expected time");
     equal(onVisited.visitCount, expected.visitCount, "onVisited received the expected visitCount");
   }
 
   let expected = {
     url: PAGE_INFOS[0].url,
     title: PAGE_INFOS[0].title,
     time: PAGE_INFOS[0].visits[0].date.getTime(),
--- a/browser/components/migration/content/aboutWelcomeBack.xhtml
+++ b/browser/components/migration/content/aboutWelcomeBack.xhtml
@@ -38,23 +38,23 @@
         <p>&welcomeback2.pageInfo1;</p>
         <!-- Note a href in the anchor below is added by JS -->
         <p>&welcomeback2.beforelink.pageInfo2;<a id="linkMoreTroubleshooting" target="_blank">&welcomeback2.link.pageInfo2;</a>&welcomeback2.afterlink.pageInfo2;</p>
 
         <div>
           <div class="radioRestoreContainer">
             <input class="radioRestoreButton" id="radioRestoreAll" type="radio"
                    name="restore" checked="checked"/>
-            <label class="radioRestoreLabel" for="radioRestoreAll">&welcomeback2.label.restoreAll;</label>
+            <label class="radioRestoreLabel" for="radioRestoreAll">&welcomeback2.restoreAll.label;</label>
           </div>
 
           <div class="radioRestoreContainer">
             <input class="radioRestoreButton" id="radioRestoreChoose" type="radio"
                    name="restore"/>
-            <label class="radioRestoreLabel" for="radioRestoreChoose">&welcomeback2.label.restoreSome;</label>
+            <label class="radioRestoreLabel" for="radioRestoreChoose">&welcomeback2.restoreSome.label;</label>
           </div>
         </div>
       </div>
 
       <div class="tree-container">
         <xul:tree id="tabList" flex="1" seltype="single" hidecolumnpicker="true"
                   onclick="onListClick(event);" onkeydown="onListKeyDown(event);"
                   _window_label="&restorepage.windowLabel;">
--- a/browser/extensions/pdfjs/README.mozilla
+++ b/browser/extensions/pdfjs/README.mozilla
@@ -1,5 +1,5 @@
 This is the PDF.js project output, https://github.com/mozilla/pdf.js
 
-Current extension version is: 1.8.480
+Current extension version is: 1.8.497
 
-Taken from upstream commit: 2f2e539b
+Taken from upstream commit: f2fcf2a5
--- a/browser/extensions/pdfjs/content/build/pdf.js
+++ b/browser/extensions/pdfjs/content/build/pdf.js
@@ -1123,17 +1123,18 @@ MessageHandler.prototype = {
     let streamId = this.streamId++;
     let sourceName = this.sourceName;
     let targetName = this.targetName;
     return new _streamsLib.ReadableStream({
       start: controller => {
         let startCapability = createPromiseCapability();
         this.streamControllers[streamId] = {
           controller,
-          startCall: startCapability
+          startCall: startCapability,
+          isClosed: false
         };
         this.postMessage({
           sourceName,
           targetName,
           action: actionName,
           streamId,
           data,
           desiredSize: controller.desiredSize
@@ -1150,16 +1151,17 @@ MessageHandler.prototype = {
           streamId,
           desiredSize: controller.desiredSize
         });
         return pullCapability.promise;
       },
       cancel: reason => {
         let cancelCapability = createPromiseCapability();
         this.streamControllers[streamId].cancelCall = cancelCapability;
+        this.streamControllers[streamId].isClosed = true;
         this.postMessage({
           sourceName,
           targetName,
           stream: 'cancel',
           reason,
           streamId
         });
         return cancelCapability.promise;
@@ -1279,31 +1281,40 @@ MessageHandler.prototype = {
           sendStreamResponse({
             stream: 'pull_complete',
             success: false,
             reason
           });
         });
         break;
       case 'enqueue':
-        this.streamControllers[data.streamId].controller.enqueue(data.chunk);
+        if (!this.streamControllers[data.streamId].isClosed) {
+          this.streamControllers[data.streamId].controller.enqueue(data.chunk);
+        }
         break;
       case 'close':
+        if (this.streamControllers[data.streamId].isClosed) {
+          break;
+        }
+        this.streamControllers[data.streamId].isClosed = true;
         this.streamControllers[data.streamId].controller.close();
         deleteStreamController();
         break;
       case 'error':
         this.streamControllers[data.streamId].controller.error(data.reason);
         deleteStreamController();
         break;
       case 'cancel_complete':
         resolveOrReject(this.streamControllers[data.streamId].cancelCall, data.success, data.reason);
         deleteStreamController();
         break;
       case 'cancel':
+        if (!this.streamSinks[data.streamId]) {
+          break;
+        }
         resolveCall(this.streamSinks[data.streamId].onCancel, [data.reason]).then(() => {
           sendStreamResponse({
             stream: 'cancel_complete',
             success: true
           });
         }, reason => {
           sendStreamResponse({
             stream: 'cancel_complete',
@@ -2366,16 +2377,17 @@ function getDocument(src, pdfDataRangeTr
     if (!src.url && !src.data && !src.range) {
       (0, _util.error)('Invalid parameter object: need either .data, .range or .url');
     }
     source = src;
   }
   var params = {};
   var rangeTransport = null;
   var worker = null;
+  var CMapReaderFactory = _dom_utils.DOMCMapReaderFactory;
   for (var key in source) {
     if (key === 'url' && typeof window !== 'undefined') {
       params[key] = new URL(source[key], window.location).href;
       continue;
     } else if (key === 'range') {
       rangeTransport = source[key];
       continue;
     } else if (key === 'worker') {
@@ -2388,22 +2400,24 @@ function getDocument(src, pdfDataRangeTr
       } else if (typeof pdfBytes === 'object' && pdfBytes !== null && !isNaN(pdfBytes.length)) {
         params[key] = new Uint8Array(pdfBytes);
       } else if ((0, _util.isArrayBuffer)(pdfBytes)) {
         params[key] = new Uint8Array(pdfBytes);
       } else {
         (0, _util.error)('Invalid PDF binary data: either typed array, string or ' + 'array-like object is expected in the data property.');
       }
       continue;
+    } else if (key === 'CMapReaderFactory') {
+      CMapReaderFactory = source[key];
+      continue;
     }
     params[key] = source[key];
   }
   params.rangeChunkSize = params.rangeChunkSize || DEFAULT_RANGE_CHUNK_SIZE;
   params.ignoreErrors = params.stopAtErrors !== true;
-  var CMapReaderFactory = params.CMapReaderFactory || _dom_utils.DOMCMapReaderFactory;
   if (params.disableNativeImageDecoder !== undefined) {
     (0, _util.deprecated)('parameter disableNativeImageDecoder, ' + 'use nativeImageDecoderSupport instead');
   }
   params.nativeImageDecoderSupport = params.nativeImageDecoderSupport || (params.disableNativeImageDecoder === true ? _util.NativeImageDecoding.NONE : _util.NativeImageDecoding.DECODE);
   if (params.nativeImageDecoderSupport !== _util.NativeImageDecoding.DECODE && params.nativeImageDecoderSupport !== _util.NativeImageDecoding.NONE && params.nativeImageDecoderSupport !== _util.NativeImageDecoding.DISPLAY) {
     (0, _util.warn)('Invalid parameter nativeImageDecoderSupport: ' + 'need a state of enum {NativeImageDecoding}');
     params.nativeImageDecoderSupport = _util.NativeImageDecoding.DECODE;
   }
@@ -2738,22 +2752,50 @@ var PDFPageProxy = function PDFPageProxy
         };
         this.transport.messageHandler.send('RenderPageRequest', {
           pageIndex: this.pageIndex,
           intent: renderingIntent
         });
       }
       return intentState.opListReadCapability.promise;
     },
-    getTextContent: function PDFPageProxy_getTextContent(params) {
-      params = params || {};
-      return this.transport.messageHandler.sendWithPromise('GetTextContent', {
+    streamTextContent(params = {}) {
+      const TEXT_CONTENT_CHUNK_SIZE = 100;
+      return this.transport.messageHandler.sendWithStream('GetTextContent', {
         pageIndex: this.pageNumber - 1,
         normalizeWhitespace: params.normalizeWhitespace === true,
         combineTextItems: params.disableCombineTextItems !== true
+      }, {
+        highWaterMark: TEXT_CONTENT_CHUNK_SIZE,
+        size(textContent) {
+          return textContent.items.length;
+        }
+      });
+    },
+    getTextContent: function PDFPageProxy_getTextContent(params) {
+      params = params || {};
+      let readableStream = this.streamTextContent(params);
+      return new Promise(function (resolve, reject) {
+        function pump() {
+          reader.read().then(function ({ value, done }) {
+            if (done) {
+              resolve(textContent);
+              return;
+            }
+            _util.Util.extendObj(textContent.styles, value.styles);
+            _util.Util.appendToArray(textContent.items, value.items);
+            pump();
+          }, reject);
+        }
+        let reader = readableStream.getReader();
+        let textContent = {
+          items: [],
+          styles: Object.create(null)
+        };
+        pump();
       });
     },
     _destroy: function PDFPageProxy_destroy() {
       this.destroyed = true;
       this.transport.pageCache[this.pageIndex] = null;
       var waitOn = [];
       Object.keys(this.intentStates).forEach(function (intent) {
         if (intent === 'oplist') {
@@ -3651,18 +3693,18 @@ var _UnsupportedManager = function Unsup
       for (var i = 0, ii = listeners.length; i < ii; i++) {
         listeners[i](featureId);
       }
     }
   };
 }();
 var version, build;
 {
-  exports.version = version = '1.8.480';
-  exports.build = build = '2f2e539b';
+  exports.version = version = '1.8.497';
+  exports.build = build = 'f2fcf2a5';
 }
 exports.getDocument = getDocument;
 exports.LoopbackPort = LoopbackPort;
 exports.PDFDataRangeTransport = PDFDataRangeTransport;
 exports.PDFWorker = PDFWorker;
 exports.PDFDocumentProxy = PDFDocumentProxy;
 exports.PDFPageProxy = PDFPageProxy;
 exports._UnsupportedManager = _UnsupportedManager;
@@ -3770,16 +3812,19 @@ var renderTextLayer = function renderTex
     if (geom.str.length > 1) {
       if (style.vertical) {
         textDivProperties.canvasWidth = geom.height * task._viewport.scale;
       } else {
         textDivProperties.canvasWidth = geom.width * task._viewport.scale;
       }
     }
     task._textDivProperties.set(textDiv, textDivProperties);
+    if (task._textContentStream) {
+      task._layoutText(textDiv);
+    }
     if (task._enhanceTextSelection) {
       var angleCos = 1,
           angleSin = 0;
       if (angle !== 0) {
         angleCos = Math.cos(angle);
         angleSin = Math.sin(angle);
       }
       var divWidth = (style.vertical ? geom.height : geom.width) * task._viewport.scale;
@@ -3801,58 +3846,28 @@ var renderTextLayer = function renderTex
         m
       });
     }
   }
   function render(task) {
     if (task._canceled) {
       return;
     }
-    var textLayerFrag = task._container;
     var textDivs = task._textDivs;
     var capability = task._capability;
     var textDivsLength = textDivs.length;
     if (textDivsLength > MAX_TEXT_DIVS_TO_RENDER) {
       task._renderingDone = true;
       capability.resolve();
       return;
     }
-    var canvas = document.createElement('canvas');
-    canvas.mozOpaque = true;
-    var ctx = canvas.getContext('2d', { alpha: false });
-    var lastFontSize;
-    var lastFontFamily;
-    for (var i = 0; i < textDivsLength; i++) {
-      var textDiv = textDivs[i];
-      var textDivProperties = task._textDivProperties.get(textDiv);
-      if (textDivProperties.isWhitespace) {
-        continue;
-      }
-      var fontSize = textDiv.style.fontSize;
-      var fontFamily = textDiv.style.fontFamily;
-      if (fontSize !== lastFontSize || fontFamily !== lastFontFamily) {
-        ctx.font = fontSize + ' ' + fontFamily;
-        lastFontSize = fontSize;
-        lastFontFamily = fontFamily;
-      }
-      var width = ctx.measureText(textDiv.textContent).width;
-      textLayerFrag.appendChild(textDiv);
-      var transform = '';
-      if (textDivProperties.canvasWidth !== 0 && width > 0) {
-        textDivProperties.scale = textDivProperties.canvasWidth / width;
-        transform = 'scaleX(' + textDivProperties.scale + ')';
-      }
-      if (textDivProperties.angle !== 0) {
-        transform = 'rotate(' + textDivProperties.angle + 'deg) ' + transform;
-      }
-      if (transform !== '') {
-        textDivProperties.originalTransform = transform;
-        _dom_utils.CustomStyle.setProp('transform', textDiv, transform);
-      }
-      task._textDivProperties.set(textDiv, textDivProperties);
+    if (!task._textContentStream) {
+      for (var i = 0; i < textDivsLength; i++) {
+        task._layoutText(textDivs[i]);
+      }
     }
     task._renderingDone = true;
     capability.resolve();
   }
   function expand(task) {
     var bounds = task._bounds;
     var viewport = task._viewport;
     var expanded = expandBounds(viewport.width, viewport.height, bounds);
@@ -4072,55 +4087,125 @@ var renderTextLayer = function renderTex
     });
     horizon.forEach(function (horizonPart) {
       var affectedBoundary = horizonPart.boundary;
       if (affectedBoundary.x2New === undefined) {
         affectedBoundary.x2New = Math.max(width, affectedBoundary.x2);
       }
     });
   }
-  function TextLayerRenderTask(textContent, container, viewport, textDivs, enhanceTextSelection) {
+  function TextLayerRenderTask({ textContent, textContentStream, container, viewport, textDivs, textContentItemsStr, enhanceTextSelection }) {
     this._textContent = textContent;
+    this._textContentStream = textContentStream;
     this._container = container;
     this._viewport = viewport;
     this._textDivs = textDivs || [];
+    this._textContentItemsStr = textContentItemsStr || [];
+    this._enhanceTextSelection = !!enhanceTextSelection;
+    this._reader = null;
+    this._layoutTextLastFontSize = null;
+    this._layoutTextLastFontFamily = null;
+    this._layoutTextCtx = null;
     this._textDivProperties = new WeakMap();
     this._renderingDone = false;
     this._canceled = false;
     this._capability = (0, _util.createPromiseCapability)();
     this._renderTimer = null;
     this._bounds = [];
-    this._enhanceTextSelection = !!enhanceTextSelection;
   }
   TextLayerRenderTask.prototype = {
     get promise() {
       return this._capability.promise;
     },
     cancel: function TextLayer_cancel() {
+      if (this._reader) {
+        this._reader.cancel();
+        this._reader = null;
+      }
       this._canceled = true;
       if (this._renderTimer !== null) {
         clearTimeout(this._renderTimer);
         this._renderTimer = null;
       }
       this._capability.reject('canceled');
     },
+    _processItems(items, styleCache) {
+      for (let i = 0, len = items.length; i < len; i++) {
+        this._textContentItemsStr.push(items[i].str);
+        appendText(this, items[i], styleCache);
+      }
+    },
+    _layoutText(textDiv) {
+      let textLayerFrag = this._container;
+      let textDivProperties = this._textDivProperties.get(textDiv);
+      if (textDivProperties.isWhitespace) {
+        return;
+      }
+      let fontSize = textDiv.style.fontSize;
+      let fontFamily = textDiv.style.fontFamily;
+      if (fontSize !== this._layoutTextLastFontSize || fontFamily !== this._layoutTextLastFontFamily) {
+        this._layoutTextCtx.font = fontSize + ' ' + fontFamily;
+        this._lastFontSize = fontSize;
+        this._lastFontFamily = fontFamily;
+      }
+      let width = this._layoutTextCtx.measureText(textDiv.textContent).width;
+      let transform = '';
+      if (textDivProperties.canvasWidth !== 0 && width > 0) {
+        textDivProperties.scale = textDivProperties.canvasWidth / width;
+        transform = 'scaleX(' + textDivProperties.scale + ')';
+      }
+      if (textDivProperties.angle !== 0) {
+        transform = 'rotate(' + textDivProperties.angle + 'deg) ' + transform;
+      }
+      if (transform !== '') {
+        textDivProperties.originalTransform = transform;
+        _dom_utils.CustomStyle.setProp('transform', textDiv, transform);
+      }
+      this._textDivProperties.set(textDiv, textDivProperties);
+      textLayerFrag.appendChild(textDiv);
+    },
     _render: function TextLayer_render(timeout) {
-      var textItems = this._textContent.items;
-      var textStyles = this._textContent.styles;
-      for (var i = 0, len = textItems.length; i < len; i++) {
-        appendText(this, textItems[i], textStyles);
-      }
-      if (!timeout) {
-        render(this);
+      let capability = (0, _util.createPromiseCapability)();
+      let styleCache = Object.create(null);
+      let canvas = document.createElement('canvas');
+      canvas.mozOpaque = true;
+      this._layoutTextCtx = canvas.getContext('2d', { alpha: false });
+      if (this._textContent) {
+        let textItems = this._textContent.items;
+        let textStyles = this._textContent.styles;
+        this._processItems(textItems, textStyles);
+        capability.resolve();
+      } else if (this._textContentStream) {
+        let pump = () => {
+          this._reader.read().then(({ value, done }) => {
+            if (done) {
+              capability.resolve();
+              return;
+            }
+            _util.Util.extendObj(styleCache, value.styles);
+            this._processItems(value.items, styleCache);
+            pump();
+          }, capability.reject);
+        };
+        this._reader = this._textContentStream.getReader();
+        pump();
       } else {
-        this._renderTimer = setTimeout(() => {
+        throw new Error('Neither "textContent" nor "textContentStream"' + ' parameters specified.');
+      }
+      capability.promise.then(() => {
+        styleCache = null;
+        if (!timeout) {
           render(this);
-          this._renderTimer = null;
-        }, timeout);
-      }
+        } else {
+          this._renderTimer = setTimeout(() => {
+            render(this);
+            this._renderTimer = null;
+          }, timeout);
+        }
+      }, this._capability.reject);
     },
     expandTextDivs: function TextLayer_expandTextDivs(expandDivs) {
       if (!this._enhanceTextSelection || !this._renderingDone) {
         return;
       }
       if (this._bounds !== null) {
         expand(this);
         this._bounds = null;
@@ -4163,17 +4248,25 @@ var renderTextLayer = function renderTex
         } else {
           div.style.padding = 0;
           _dom_utils.CustomStyle.setProp('transform', div, divProperties.originalTransform || '');
         }
       }
     }
   };
   function renderTextLayer(renderParameters) {
-    var task = new TextLayerRenderTask(renderParameters.textContent, renderParameters.container, renderParameters.viewport, renderParameters.textDivs, renderParameters.enhanceTextSelection);
+    var task = new TextLayerRenderTask({
+      textContent: renderParameters.textContent,
+      textContentStream: renderParameters.textContentStream,
+      container: renderParameters.container,
+      viewport: renderParameters.viewport,
+      textDivs: renderParameters.textDivs,
+      textContentItemsStr: renderParameters.textContentItemsStr,
+      enhanceTextSelection: renderParameters.enhanceTextSelection
+    });
     task._render(renderParameters.timeout);
     return task;
   }
   return renderTextLayer;
 }();
 exports.renderTextLayer = renderTextLayer;
 
 /***/ }),
@@ -4654,18 +4747,18 @@ var _text_layer = __w_pdfjs_require__(5)
 var _svg = __w_pdfjs_require__(4);
 
 var isWorker = typeof window === 'undefined';
 if (!_util.globalScope.PDFJS) {
   _util.globalScope.PDFJS = {};
 }
 var PDFJS = _util.globalScope.PDFJS;
 {
-  PDFJS.version = '1.8.480';
-  PDFJS.build = '2f2e539b';
+  PDFJS.version = '1.8.497';
+  PDFJS.build = 'f2fcf2a5';
 }
 PDFJS.pdfBug = false;
 if (PDFJS.verbosity !== undefined) {
   (0, _util.setVerbosityLevel)(PDFJS.verbosity);
 }
 delete PDFJS.verbosity;
 Object.defineProperty(PDFJS, 'verbosity', {
   get() {
@@ -10002,18 +10095,18 @@ exports.TilingPattern = TilingPattern;
 
 /***/ }),
 /* 14 */
 /***/ (function(module, exports, __w_pdfjs_require__) {
 
 "use strict";
 
 
-var pdfjsVersion = '1.8.480';
-var pdfjsBuild = '2f2e539b';
+var pdfjsVersion = '1.8.497';
+var pdfjsBuild = 'f2fcf2a5';
 var pdfjsSharedUtil = __w_pdfjs_require__(0);
 var pdfjsDisplayGlobal = __w_pdfjs_require__(8);
 var pdfjsDisplayAPI = __w_pdfjs_require__(3);
 var pdfjsDisplayTextLayer = __w_pdfjs_require__(5);
 var pdfjsDisplayAnnotationLayer = __w_pdfjs_require__(2);
 var pdfjsDisplayDOMUtils = __w_pdfjs_require__(1);
 var pdfjsDisplaySVG = __w_pdfjs_require__(4);
 exports.PDFJS = pdfjsDisplayGlobal.PDFJS;
--- a/browser/extensions/pdfjs/content/build/pdf.worker.js
+++ b/browser/extensions/pdfjs/content/build/pdf.worker.js
@@ -1123,17 +1123,18 @@ MessageHandler.prototype = {
     let streamId = this.streamId++;
     let sourceName = this.sourceName;
     let targetName = this.targetName;
     return new _streamsLib.ReadableStream({
       start: controller => {
         let startCapability = createPromiseCapability();
         this.streamControllers[streamId] = {
           controller,
-          startCall: startCapability
+          startCall: startCapability,
+          isClosed: false
         };
         this.postMessage({
           sourceName,
           targetName,
           action: actionName,
           streamId,
           data,
           desiredSize: controller.desiredSize
@@ -1150,16 +1151,17 @@ MessageHandler.prototype = {
           streamId,
           desiredSize: controller.desiredSize
         });
         return pullCapability.promise;
       },
       cancel: reason => {
         let cancelCapability = createPromiseCapability();
         this.streamControllers[streamId].cancelCall = cancelCapability;
+        this.streamControllers[streamId].isClosed = true;
         this.postMessage({
           sourceName,
           targetName,
           stream: 'cancel',
           reason,
           streamId
         });
         return cancelCapability.promise;
@@ -1279,31 +1281,40 @@ MessageHandler.prototype = {
           sendStreamResponse({
             stream: 'pull_complete',
             success: false,
             reason
           });
         });
         break;
       case 'enqueue':
-        this.streamControllers[data.streamId].controller.enqueue(data.chunk);
+        if (!this.streamControllers[data.streamId].isClosed) {
+          this.streamControllers[data.streamId].controller.enqueue(data.chunk);
+        }
         break;
       case 'close':
+        if (this.streamControllers[data.streamId].isClosed) {
+          break;
+        }
+        this.streamControllers[data.streamId].isClosed = true;
         this.streamControllers[data.streamId].controller.close();
         deleteStreamController();
         break;
       case 'error':
         this.streamControllers[data.streamId].controller.error(data.reason);
         deleteStreamController();
         break;
       case 'cancel_complete':
         resolveOrReject(this.streamControllers[data.streamId].cancelCall, data.success, data.reason);
         deleteStreamController();
         break;
       case 'cancel':
+        if (!this.streamSinks[data.streamId]) {
+          break;
+        }
         resolveCall(this.streamSinks[data.streamId].onCancel, [data.reason]).then(() => {
           sendStreamResponse({
             stream: 'cancel_complete',
             success: true
           });
         }, reason => {
           sendStreamResponse({
             stream: 'cancel_complete',
@@ -17471,17 +17482,17 @@ var PartialEvaluator = function PartialE
           this.handler.send('UnsupportedFeature', { featureId: _util.UNSUPPORTED_FEATURES.unknown });
           (0, _util.warn)('getOperatorList - ignoring errors during task: ' + task.name);
           closePendingRestoreOPS();
           return;
         }
         throw reason;
       });
     },
-    getTextContent({ stream, task, resources, stateManager = null, normalizeWhitespace = false, combineTextItems = false }) {
+    getTextContent({ stream, task, resources, stateManager = null, normalizeWhitespace = false, combineTextItems = false, sink, seenStyles = Object.create(null) }) {
       resources = resources || _primitives.Dict.empty;
       stateManager = stateManager || new StateManager(new TextState());
       var WhitespaceRegexp = /\s/g;
       var textContent = {
         items: [],
         styles: Object.create(null)
       };
       var textContentItem = {
@@ -17502,25 +17513,26 @@ var PartialEvaluator = function PartialE
         fontName: null
       };
       var SPACE_FACTOR = 0.3;
       var MULTI_SPACE_FACTOR = 1.5;
       var MULTI_SPACE_FACTOR_MAX = 4;
       var self = this;
       var xref = this.xref;
       var xobjs = null;
-      var xobjsCache = Object.create(null);
+      var skipEmptyXObjs = Object.create(null);
       var preprocessor = new EvaluatorPreprocessor(stream, xref, stateManager);
       var textState;
       function ensureTextContentItem() {
         if (textContentItem.initialized) {
           return textContentItem;
         }
         var font = textState.font;
-        if (!(font.loadedName in textContent.styles)) {
+        if (!(font.loadedName in seenStyles)) {
+          seenStyles[font.loadedName] = true;
           textContent.styles[font.loadedName] = {
             fontFamily: font.fallbackName,
             ascent: font.ascent,
             descent: font.descent,
             vertical: font.vertical
           };
         }
         textContentItem.fontName = font.loadedName;
@@ -17665,20 +17677,29 @@ var PartialEvaluator = function PartialE
           return;
         }
         textContentItem.width *= textContentItem.textAdvanceScale;
         textContentItem.height *= textContentItem.textAdvanceScale;
         textContent.items.push(runBidiTransform(textContentItem));
         textContentItem.initialized = false;
         textContentItem.str.length = 0;
       }
+      function enqueueChunk() {
+        let length = textContent.items.length;
+        if (length > 0) {
+          sink.enqueue(textContent, length);
+          textContent.items = [];
+          textContent.styles = Object.create(null);
+        }
+      }
       var timeSlotManager = new TimeSlotManager();
       return new Promise(function promiseBody(resolve, reject) {
-        var next = function (promise) {
-          promise.then(function () {
+        let next = function (promise) {
+          enqueueChunk();
+          Promise.all([promise, sink.ready]).then(function () {
             try {
               promiseBody(resolve, reject);
             } catch (ex) {
               reject(ex);
             }
           }, reject);
         };
         task.ensureNotTerminated();
@@ -17823,53 +17844,63 @@ var PartialEvaluator = function PartialE
               flushTextContentItem();
               if (args[0].code) {
                 break;
               }
               if (!xobjs) {
                 xobjs = resources.get('XObject') || _primitives.Dict.empty;
               }
               var name = args[0].name;
-              if (xobjsCache.key === name) {
-                if (xobjsCache.texts) {
-                  _util.Util.appendToArray(textContent.items, xobjsCache.texts.items);
-                  _util.Util.extendObj(textContent.styles, xobjsCache.texts.styles);
-                }
+              if (name in skipEmptyXObjs) {
                 break;
               }
               var xobj = xobjs.get(name);
               if (!xobj) {
                 break;
               }
               (0, _util.assert)((0, _primitives.isStream)(xobj), 'XObject should be a stream');
               var type = xobj.dict.get('Subtype');
               (0, _util.assert)((0, _primitives.isName)(type), 'XObject should have a Name subtype');
               if (type.name !== 'Form') {
-                xobjsCache.key = name;
-                xobjsCache.texts = null;
+                skipEmptyXObjs[name] = true;
                 break;
               }
               var currentState = stateManager.state.clone();
               var xObjStateManager = new StateManager(currentState);
               var matrix = xobj.dict.getArray('Matrix');
               if ((0, _util.isArray)(matrix) && matrix.length === 6) {
                 xObjStateManager.transform(matrix);
               }
+              enqueueChunk();
+              let sinkWrapper = {
+                enqueueInvoked: false,
+                enqueue(chunk, size) {
+                  this.enqueueInvoked = true;
+                  sink.enqueue(chunk, size);
+                },
+                get desiredSize() {
+                  return sink.desiredSize;
+                },
+                get ready() {
+                  return sink.ready;
+                }
+              };
               next(self.getTextContent({
                 stream: xobj,
                 task,
                 resources: xobj.dict.get('Resources') || resources,
                 stateManager: xObjStateManager,
                 normalizeWhitespace,
-                combineTextItems
-              }).then(function (formTextContent) {
-                _util.Util.appendToArray(textContent.items, formTextContent.items);
-                _util.Util.extendObj(textContent.styles, formTextContent.styles);
-                xobjsCache.key = name;
-                xobjsCache.texts = formTextContent;
+                combineTextItems,
+                sink: sinkWrapper,
+                seenStyles
+              }).then(function () {
+                if (!sinkWrapper.enqueueInvoked) {
+                  skipEmptyXObjs[name] = true;
+                }
               }));
               return;
             case _util.OPS.setGState:
               flushTextContentItem();
               var dictName = args[0];
               var extGState = resources.get('ExtGState');
               if (!(0, _primitives.isDict)(extGState) || !(0, _primitives.isName)(dictName)) {
                 break;
@@ -17882,28 +17913,34 @@ var PartialEvaluator = function PartialE
               if (gStateFont) {
                 textState.fontName = null;
                 textState.fontSize = gStateFont[1];
                 next(handleSetFont(null, gStateFont[0]));
                 return;
               }
               break;
           }
+          if (textContent.items.length >= sink.desiredSize) {
+            stop = true;
+            break;
+          }
         }
         if (stop) {
           next(deferred);
           return;
         }
         flushTextContentItem();
-        resolve(textContent);
+        enqueueChunk();
+        resolve();
       }).catch(reason => {
         if (this.options.ignoreErrors) {
           (0, _util.warn)('getTextContent - ignoring errors during task: ' + task.name);
           flushTextContentItem();
-          return textContent;
+          enqueueChunk();
+          return;
         }
         throw reason;
       });
     },
     extractDataStructures: function PartialEvaluator_extractDataStructures(dict, baseDict, properties) {
       var xref = this.xref;
       var toUnicode = dict.get('ToUnicode') || baseDict.get('ToUnicode');
       var toUnicodePromise = toUnicode ? this.readToUnicode(toUnicode) : Promise.resolve(undefined);
@@ -24121,37 +24158,41 @@ var WorkerMessageHandler = {
           handler.send('PageError', {
             pageNum,
             error: wrappedException,
             intent: data.intent
           });
         });
       });
     }, this);
-    handler.on('GetTextContent', function wphExtractText(data) {
+    handler.on('GetTextContent', function wphExtractText(data, sink) {
       var pageIndex = data.pageIndex;
-      return pdfManager.getPage(pageIndex).then(function (page) {
+      sink.onPull = function (desiredSize) {};
+      sink.onCancel = function (reason) {};
+      pdfManager.getPage(pageIndex).then(function (page) {
         var task = new WorkerTask('GetTextContent: page ' + pageIndex);
         startWorkerTask(task);
         var pageNum = pageIndex + 1;
         var start = Date.now();
-        return page.extractTextContent({
+        page.extractTextContent({
           handler,
           task,
+          sink,
           normalizeWhitespace: data.normalizeWhitespace,
           combineTextItems: data.combineTextItems
-        }).then(function (textContent) {
+        }).then(function () {
           finishWorkerTask(task);
           (0, _util.info)('text indexing: page=' + pageNum + ' - time=' + (Date.now() - start) + 'ms');
-          return textContent;
+          sink.close();
         }, function (reason) {
           finishWorkerTask(task);
           if (task.terminated) {
             return;
           }
+          sink.error(reason);
           throw reason;
         });
       });
     });
     handler.on('Cleanup', function wphCleanup(data) {
       return pdfManager.cleanup();
     });
     handler.on('Terminate', function wphTerminate(data) {
@@ -29056,17 +29097,17 @@ var Page = function PageClosure() {
             pageOpList.addOpList(opLists[i]);
           }
           pageOpList.addOp(_util.OPS.endAnnotations, []);
           pageOpList.flush(true);
           return pageOpList;
         });
       });
     },
-    extractTextContent({ handler, task, normalizeWhitespace, combineTextItems }) {
+    extractTextContent({ handler, task, normalizeWhitespace, sink, combineTextItems }) {
       var contentStreamPromise = this.pdfManager.ensure(this, 'getContentStream');
       var resourcesPromise = this.loadResources(['ExtGState', 'XObject', 'Font']);
       var dataPromises = Promise.all([contentStreamPromise, resourcesPromise]);
       return dataPromises.then(([contentStream]) => {
         var partialEvaluator = new _evaluator.PartialEvaluator({
           pdfManager: this.pdfManager,
           xref: this.xref,
           handler,
@@ -29076,17 +29117,18 @@ var Page = function PageClosure() {
           builtInCMapCache: this.builtInCMapCache,
           options: this.evaluatorOptions
         });
         return partialEvaluator.getTextContent({
           stream: contentStream,
           task,
           resources: this.resources,
           normalizeWhitespace,
-          combineTextItems
+          combineTextItems,
+          sink
         });
       });
     },
     getAnnotationsData: function Page_getAnnotationsData(intent) {
       var annotations = this.annotations;
       var annotationsData = [];
       for (var i = 0, n = annotations.length; i < n; ++i) {
         if (!intent || isAnnotationRenderable(annotations[i], intent)) {
@@ -30507,34 +30549,34 @@ var Font = function FontClosure() {
     if (subtype === 'Type1C') {
       if (type !== 'Type1' && type !== 'MMType1') {
         if (isTrueTypeFile(file)) {
           subtype = 'TrueType';
         } else {
           type = 'Type1';
         }
       } else if (isOpenTypeFile(file)) {
-        type = subtype = 'OpenType';
+        subtype = 'OpenType';
       }
     }
     if (subtype === 'CIDFontType0C' && type !== 'CIDFontType0') {
       type = 'CIDFontType0';
     }
-    if (subtype === 'OpenType') {
-      type = 'OpenType';
-    }
     if (type === 'CIDFontType0') {
       if (isType1File(file)) {
         subtype = 'CIDFontType0';
       } else if (isOpenTypeFile(file)) {
-        type = subtype = 'OpenType';
+        subtype = 'OpenType';
       } else {
         subtype = 'CIDFontType0C';
       }
     }
+    if (subtype === 'OpenType' && type !== 'OpenType') {
+      type = 'OpenType';
+    }
     var data;
     switch (type) {
       case 'MMType1':
         (0, _util.info)('MMType1 font (' + name + '), falling back to Type1.');
       case 'Type1':
       case 'CIDFontType0':
         this.mimetype = 'font/opentype';
         var cff = subtype === 'Type1C' || subtype === 'CIDFontType0C' ? new CFFFont(file, properties) : new Type1Font(name, file, properties);
@@ -31667,17 +31709,17 @@ var Font = function FontClosure() {
         }
         if (table.length === 0) {
           continue;
         }
         tables[table.tag] = table;
       }
       var isTrueType = !tables['CFF '];
       if (!isTrueType) {
-        if (header.version === 'OTTO' && !properties.composite || !tables['head'] || !tables['hhea'] || !tables['maxp'] || !tables['post']) {
+        if (header.version === 'OTTO' && !(properties.composite && properties.cidToGidMap) || !tables['head'] || !tables['hhea'] || !tables['maxp'] || !tables['post']) {
           cffFile = new _stream.Stream(tables['CFF '].data);
           cff = new CFFFont(cffFile, properties);
           adjustWidths(properties);
           return this.convert(name, cff, properties);
         }
         delete tables['glyf'];
         delete tables['loca'];
         delete tables['fpgm'];
@@ -39771,18 +39813,18 @@ exports.Type1Parser = Type1Parser;
 
 /***/ }),
 /* 36 */
 /***/ (function(module, exports, __w_pdfjs_require__) {
 
 "use strict";
 
 
-var pdfjsVersion = '1.8.480';
-var pdfjsBuild = '2f2e539b';
+var pdfjsVersion = '1.8.497';
+var pdfjsBuild = 'f2fcf2a5';
 var pdfjsCoreWorker = __w_pdfjs_require__(17);
 ;
 exports.WorkerMessageHandler = pdfjsCoreWorker.WorkerMessageHandler;
 
 /***/ }),
 /* 37 */
 /***/ (function(module, exports, __w_pdfjs_require__) {
 
--- a/browser/extensions/pdfjs/content/web/viewer.js
+++ b/browser/extensions/pdfjs/content/web/viewer.js
@@ -4881,17 +4881,16 @@ exports.PDFPageView = undefined;
 var _ui_utils = __webpack_require__(0);
 
 var _pdfjsLib = __webpack_require__(1);
 
 var _dom_events = __webpack_require__(2);
 
 var _pdf_rendering_queue = __webpack_require__(3);
 
-const TEXT_LAYER_RENDER_DELAY = 200;
 class PDFPageView {
   constructor(options) {
     let container = options.container;
     let defaultViewport = options.defaultViewport;
     this.id = options.id;
     this.renderingId = 'page' + this.id;
     this.pageLabel = null;
     this.rotation = 0;
@@ -5185,20 +5184,19 @@ class PDFPageView {
       return Promise.resolve(undefined);
     };
     let paintTask = this.renderer === _ui_utils.RendererType.SVG ? this.paintOnSvg(canvasWrapper) : this.paintOnCanvas(canvasWrapper);
     paintTask.onRenderContinue = renderContinueCallback;
     this.paintTask = paintTask;
     let resultPromise = paintTask.promise.then(function () {
       return finishPaintTask(null).then(function () {
         if (textLayer) {
-          pdfPage.getTextContent({ normalizeWhitespace: true }).then(function textContentResolved(textContent) {
-            textLayer.setTextContent(textContent);
-            textLayer.render(TEXT_LAYER_RENDER_DELAY);
-          });
+          let readableStream = pdfPage.streamTextContent({ normalizeWhitespace: true });
+          textLayer.setTextContentStream(readableStream);
+          textLayer.render();
         }
       });
     }, function (reason) {
       return finishPaintTask(reason);
     });
     if (this.annotationLayerFactory) {
       if (!this.annotationLayer) {
         this.annotationLayer = this.annotationLayerFactory.createAnnotationLayerBuilder(div, pdfPage, this.renderInteractiveForms, this.l10n);
@@ -7387,16 +7385,18 @@ var _dom_events = __webpack_require__(2)
 var _pdfjsLib = __webpack_require__(1);
 
 var EXPAND_DIVS_TIMEOUT = 300;
 var TextLayerBuilder = function TextLayerBuilderClosure() {
   function TextLayerBuilder(options) {
     this.textLayerDiv = options.textLayerDiv;
     this.eventBus = options.eventBus || (0, _dom_events.getGlobalEventBus)();
     this.textContent = null;
+    this.textContentItemsStr = [];
+    this.textContentStream = null;
     this.renderingDone = false;
     this.pageIdx = options.pageIndex;
     this.pageNumber = this.pageIdx + 1;
     this.matches = [];
     this.viewport = options.viewport;
     this.textDivs = [];
     this.findController = options.findController || null;
     this.textLayerRenderTask = null;
@@ -7413,93 +7413,99 @@ var TextLayerBuilder = function TextLaye
       }
       this.eventBus.dispatch('textlayerrendered', {
         source: this,
         pageNumber: this.pageNumber,
         numTextDivs: this.textDivs.length
       });
     },
     render: function TextLayerBuilder_render(timeout) {
-      if (!this.textContent || this.renderingDone) {
+      if (!(this.textContent || this.textContentStream) || this.renderingDone) {
         return;
       }
       this.cancel();
       this.textDivs = [];
       var textLayerFrag = document.createDocumentFragment();
       this.textLayerRenderTask = (0, _pdfjsLib.renderTextLayer)({
         textContent: this.textContent,
+        textContentStream: this.textContentStream,
         container: textLayerFrag,
         viewport: this.viewport,
         textDivs: this.textDivs,
+        textContentItemsStr: this.textContentItemsStr,
         timeout,
         enhanceTextSelection: this.enhanceTextSelection
       });
       this.textLayerRenderTask.promise.then(() => {
         this.textLayerDiv.appendChild(textLayerFrag);
         this._finishRendering();
         this.updateMatches();
       }, function (reason) {});
     },
     cancel: function TextLayerBuilder_cancel() {
       if (this.textLayerRenderTask) {
         this.textLayerRenderTask.cancel();
         this.textLayerRenderTask = null;
       }
     },
+    setTextContentStream(readableStream) {
+      this.cancel();
+      this.textContentStream = readableStream;
+    },
     setTextContent: function TextLayerBuilder_setTextContent(textContent) {
       this.cancel();
       this.textContent = textContent;
     },
     convertMatches: function TextLayerBuilder_convertMatches(matches, matchesLength) {
       var i = 0;
       var iIndex = 0;
-      var bidiTexts = this.textContent.items;
-      var end = bidiTexts.length - 1;
+      let textContentItemsStr = this.textContentItemsStr;
+      var end = textContentItemsStr.length - 1;
       var queryLen = this.findController === null ? 0 : this.findController.state.query.length;
       var ret = [];
       if (!matches) {
         return ret;
       }
       for (var m = 0, len = matches.length; m < len; m++) {
         var matchIdx = matches[m];
-        while (i !== end && matchIdx >= iIndex + bidiTexts[i].str.length) {
-          iIndex += bidiTexts[i].str.length;
+        while (i !== end && matchIdx >= iIndex + textContentItemsStr[i].length) {
+          iIndex += textContentItemsStr[i].length;
           i++;
         }
-        if (i === bidiTexts.length) {
+        if (i === textContentItemsStr.length) {
           console.error('Could not find a matching mapping');
         }
         var match = {
           begin: {
             divIdx: i,
             offset: matchIdx - iIndex
           }
         };
         if (matchesLength) {
           matchIdx += matchesLength[m];
         } else {
           matchIdx += queryLen;
         }
-        while (i !== end && matchIdx > iIndex + bidiTexts[i].str.length) {
-          iIndex += bidiTexts[i].str.length;
+        while (i !== end && matchIdx > iIndex + textContentItemsStr[i].length) {
+          iIndex += textContentItemsStr[i].length;
           i++;
         }
         match.end = {
           divIdx: i,
           offset: matchIdx - iIndex
         };
         ret.push(match);
       }
       return ret;
     },
     renderMatches: function TextLayerBuilder_renderMatches(matches) {
       if (matches.length === 0) {
         return;
       }
-      var bidiTexts = this.textContent.items;
+      let textContentItemsStr = this.textContentItemsStr;
       var textDivs = this.textDivs;
       var prevEnd = null;
       var pageIdx = this.pageIdx;
       var isSelectedPage = this.findController === null ? false : pageIdx === this.findController.selected.pageIdx;
       var selectedMatchIdx = this.findController === null ? -1 : this.findController.selected.matchIdx;
       var highlightAll = this.findController === null ? false : this.findController.state.highlightAll;
       var infinity = {
         divIdx: -1,
@@ -7507,17 +7513,17 @@ var TextLayerBuilder = function TextLaye
       };
       function beginText(begin, className) {
         var divIdx = begin.divIdx;
         textDivs[divIdx].textContent = '';
         appendTextToDiv(divIdx, 0, begin.offset, className);
       }
       function appendTextToDiv(divIdx, fromOffset, toOffset, className) {
         var div = textDivs[divIdx];
-        var content = bidiTexts[divIdx].str.substring(fromOffset, toOffset);
+        var content = textContentItemsStr[divIdx].substring(fromOffset, toOffset);
         var node = document.createTextNode(content);
         if (className) {
           var span = document.createElement('span');
           span.className = className;
           span.appendChild(node);
           div.appendChild(span);
           return;
         }
@@ -7564,24 +7570,24 @@ var TextLayerBuilder = function TextLaye
       }
     },
     updateMatches: function TextLayerBuilder_updateMatches() {
       if (!this.renderingDone) {
         return;
       }
       var matches = this.matches;
       var textDivs = this.textDivs;
-      var bidiTexts = this.textContent.items;
+      let textContentItemsStr = this.textContentItemsStr;
       var clearedUntilDivIdx = -1;
       for (var i = 0, len = matches.length; i < len; i++) {
         var match = matches[i];
         var begin = Math.max(clearedUntilDivIdx, match.begin.divIdx);
         for (var n = begin, end = match.end.divIdx; n <= end; n++) {
           var div = textDivs[n];
-          div.textContent = bidiTexts[n].str;
+          div.textContent = textContentItemsStr[n];
           div.className = '';
         }
         clearedUntilDivIdx = match.end.divIdx + 1;
       }
       if (this.findController === null || !this.findController.active) {
         return;
       }
       var pageMatches, pageMatchesLength;
@@ -7684,19 +7690,18 @@ class Toolbar {
     this.pageLabel = null;
     this.hasPageLabels = false;
     this.pagesCount = 0;
     this.pageScaleValue = _ui_utils.DEFAULT_SCALE_VALUE;
     this.pageScale = _ui_utils.DEFAULT_SCALE;
     this._updateUIState(true);
   }
   _bindListeners() {
-    let eventBus = this.eventBus;
+    let { eventBus, items } = this;
     let self = this;
-    let items = this.items;
     items.previous.addEventListener('click', function () {
       eventBus.dispatch('previouspage');
     });
     items.next.addEventListener('click', function () {
       eventBus.dispatch('nextpage');
     });
     items.zoomIn.addEventListener('click', function () {
       eventBus.dispatch('zoomin');
@@ -7743,41 +7748,19 @@ class Toolbar {
     this._wasLocalized = true;
     this._adjustScaleWidth();
     this._updateUIState(true);
   }
   _updateUIState(resetNumPages = false) {
     if (!this._wasLocalized) {
       return;
     }
-    let selectScaleOption = (value, scale) => {
-      let customScale = Math.round(scale * 10000) / 100;
-      this.l10n.get('page_scale_percent', { scale: customScale }, '{{scale}}%').then(msg => {
-        let options = items.scaleSelect.options;
-        let predefinedValueFound = false;
-        for (let i = 0, ii = options.length; i < ii; i++) {
-          let option = options[i];
-          if (option.value !== value) {
-            option.selected = false;
-            continue;
-          }
-          option.selected = true;
-          predefinedValueFound = true;
-        }
-        if (!predefinedValueFound) {
-          items.customScaleOption.textContent = msg;
-          items.customScaleOption.selected = true;
-        }
-      });
-    };
-    let pageNumber = this.pageNumber;
+    let { pageNumber, pagesCount, items } = this;
     let scaleValue = (this.pageScaleValue || this.pageScale).toString();
     let scale = this.pageScale;
-    let items = this.items;
-    let pagesCount = this.pagesCount;
     if (resetNumPages) {
       if (this.hasPageLabels) {
         items.pageNumber.type = 'text';
       } else {
         items.pageNumber.type = 'number';
         this.l10n.get('of_pages', { pagesCount }, 'of {{pagesCount}}').then(msg => {
           items.numPages.textContent = msg;
         });
@@ -7794,17 +7777,34 @@ class Toolbar {
       });
     } else {
       items.pageNumber.value = pageNumber;
     }
     items.previous.disabled = pageNumber <= 1;
     items.next.disabled = pageNumber >= pagesCount;
     items.zoomOut.disabled = scale <= _ui_utils.MIN_SCALE;
     items.zoomIn.disabled = scale >= _ui_utils.MAX_SCALE;
-    selectScaleOption(scaleValue, scale);
+    let customScale = Math.round(scale * 10000) / 100;
+    this.l10n.get('page_scale_percent', { scale: customScale }, '{{scale}}%').then(msg => {
+      let options = items.scaleSelect.options;
+      let predefinedValueFound = false;
+      for (let i = 0, ii = options.length; i < ii; i++) {
+        let option = options[i];
+        if (option.value !== scaleValue) {
+          option.selected = false;
+          continue;
+        }
+        option.selected = true;
+        predefinedValueFound = true;
+      }
+      if (!predefinedValueFound) {
+        items.customScaleOption.textContent = msg;
+        items.customScaleOption.selected = true;
+      }
+    });
   }
   updateLoadingIndicatorState(loading = false) {
     let pageNumberInput = this.items.pageNumber;
     if (loading) {
       pageNumberInput.classList.add(PAGE_NUMBER_LOADING_INDICATOR);
     } else {
       pageNumberInput.classList.remove(PAGE_NUMBER_LOADING_INDICATOR);
     }
--- a/browser/locales/en-US/chrome/browser/aboutSessionRestore.dtd
+++ b/browser/locales/en-US/chrome/browser/aboutSessionRestore.dtd
@@ -29,18 +29,18 @@
 <!ENTITY welcomeback2.restoreButton  "Let’s go!">
 <!ENTITY welcomeback2.restoreButton.access "L">
 
 <!ENTITY welcomeback2.tabtitle      "Success!">
 
 <!ENTITY welcomeback2.pageTitle     "Success!">
 <!ENTITY welcomeback2.pageInfo1     "&brandShortName; is ready to go.">
 
-<!ENTITY welcomeback2.label.restoreAll  "Restore all Windows and Tabs">
-<!ENTITY welcomeback2.label.restoreSome "Restore only the ones you want">
+<!ENTITY welcomeback2.restoreAll.label  "Restore all windows &amp; tabs">
+<!ENTITY welcomeback2.restoreSome.label "Restore only the ones you want">
 
 
 <!-- LOCALIZATION NOTE (welcomeback2.beforelink.pageInfo2,
 welcomeback2.afterlink.pageInfo2): these two string are used respectively
 before and after the the "learn more" link (welcomeback2.link.pageInfo2).
 Localizers can use one of them, or both, to better adapt this sentence to
 their language.
 -->
--- a/config/msvc-stl-wrapper.template.h
+++ b/config/msvc-stl-wrapper.template.h
@@ -14,22 +14,16 @@
 
 // Include mozalloc after the STL header and all other headers it includes
 // have been preprocessed.
 #if !defined(MOZ_INCLUDE_MOZALLOC_H)
 #  define MOZ_INCLUDE_MOZALLOC_H
 #  define MOZ_INCLUDE_MOZALLOC_H_FROM_${HEADER}
 #endif
 
-// Code built with !_HAS_EXCEPTIONS calls std::_Throw(), but the win2k
-// CRT doesn't export std::_Throw().  So we define it.
-#ifndef mozilla_Throw_h
-#  include "mozilla/throw_msvc.h"
-#endif
-
 #ifdef _DEBUG
 // From
 //   http://msdn.microsoft.com/en-us/library/aa985982%28VS.80%29.aspx
 // and
 //   http://msdn.microsoft.com/en-us/library/aa985965%28VS.80%29.aspx
 // there appear to be two types of STL container checking.  The
 // former is enabled by -D_DEBUG (which is implied by -MDd or -MTd), and
 // looks to be full generation/mutation checked iterators as done by
--- a/devtools/client/styleeditor/StyleEditorUI.jsm
+++ b/devtools/client/styleeditor/StyleEditorUI.jsm
@@ -890,29 +890,24 @@ StyleEditorUI.prototype = {
         inSource = true;
 
         let div = this._panelDoc.createElement("div");
         div.className = "media-rule-label";
         div.addEventListener("click",
                              this._jumpToLocation.bind(this, location));
 
         let cond = this._panelDoc.createElement("div");
-        cond.textContent = rule.conditionText;
         cond.className = "media-rule-condition";
         if (!rule.matches) {
           cond.classList.add("media-condition-unmatched");
         }
         if (this._target.tab.tagName == "tab") {
-          const minMaxPattern = /(min\-|max\-)(width|height):\s\d+(px)/ig;
-          const replacement =
-                "<a href='#' class='media-responsive-mode-toggle'>$&</a>";
-
-          cond.innerHTML = cond.textContent.replace(minMaxPattern, replacement);
-          cond.addEventListener("click",
-                                this._onMediaConditionClick.bind(this));
+          this._setConditionContents(cond, rule.conditionText);
+        } else {
+          cond.textContent = rule.conditionText;
         }
         div.appendChild(cond);
 
         let link = this._panelDoc.createElement("div");
         link.className = "media-rule-line theme-link";
         if (location.line != -1) {
           link.textContent = ":" + location.line;
         }
@@ -923,26 +918,60 @@ StyleEditorUI.prototype = {
 
       sidebar.hidden = !showSidebar || !inSource;
 
       this.emit("media-list-changed", editor);
     }.bind(this)).catch(e => console.error(e));
   },
 
   /**
-    * Called when a media condition is clicked
-    * If a responsive mode link is clicked, it will launch it.
-    *
-    * @param {object} e
-    *        Event object
-    */
+   * Used to safely inject media query links
+   *
+   * @param {HTMLElement} element
+   *        The element corresponding to the media sidebar condition
+   * @param {String} rawText
+   *        The raw condition text to parse
+   */
+  _setConditionContents(element, rawText) {
+    const minMaxPattern = /(min\-|max\-)(width|height):\s\d+(px)/ig;
+
+    let match = minMaxPattern.exec(rawText);
+    let lastParsed = 0;
+    while (match && match.index != minMaxPattern.lastIndex) {
+      let matchEnd = match.index + match[0].length;
+      let node = this._panelDoc.createTextNode(
+        rawText.substring(lastParsed, match.index)
+      );
+      element.appendChild(node);
+
+      let link = this._panelDoc.createElement("a");
+      link.href = "#";
+      link.className = "media-responsive-mode-toggle";
+      link.textContent = rawText.substring(match.index, matchEnd);
+      link.addEventListener("click", this._onMediaConditionClick.bind(this));
+      element.appendChild(link);
+
+      match = minMaxPattern.exec(rawText);
+      lastParsed = matchEnd;
+    }
+
+    let node = this._panelDoc.createTextNode(
+      rawText.substring(lastParsed, rawText.length)
+    );
+    element.appendChild(node);
+  },
+
+  /**
+   * Called when a media condition is clicked
+   * If a responsive mode link is clicked, it will launch it.
+   *
+   * @param {object} e
+   *        Event object
+   */
   _onMediaConditionClick: function (e) {
-    if (!e.target.matches(".media-responsive-mode-toggle")) {
-      return;
-    }
     let conditionText = e.target.textContent;
     let isWidthCond = conditionText.toLowerCase().indexOf("width") > -1;
     let mediaVal = parseInt(/\d+/.exec(conditionText), 10);
 
     let options = isWidthCond ? {width: mediaVal} : {height: mediaVal};
     this._launchResponsiveMode(options);
     e.preventDefault();
     e.stopPropagation();
--- a/dom/base/DirectionalityUtils.cpp
+++ b/dom/base/DirectionalityUtils.cpp
@@ -204,16 +204,17 @@
   gets called.
   */
 
 #include "mozilla/dom/DirectionalityUtils.h"
 
 #include "nsINode.h"
 #include "nsIContent.h"
 #include "nsIDocument.h"
+#include "mozilla/AutoRestore.h"
 #include "mozilla/DebugOnly.h"
 #include "mozilla/dom/Element.h"
 #include "nsIDOMHTMLDocument.h"
 #include "nsUnicodeProperties.h"
 #include "nsTextFragment.h"
 #include "nsAttrValue.h"
 #include "nsTextNode.h"
 #include "nsCheapSets.h"
@@ -427,50 +428,78 @@ class nsTextNodeDirectionalityMap
     nsTextNodeDirectionalityMap* map =
       reinterpret_cast<nsTextNodeDirectionalityMap * >(aPropertyValue);
     map->EnsureMapIsClear(textNode);
     delete map;
   }
 
 public:
   explicit nsTextNodeDirectionalityMap(nsINode* aTextNode)
+    : mElementToBeRemoved(nullptr)
   {
     MOZ_ASSERT(aTextNode, "Null text node");
     MOZ_COUNT_CTOR(nsTextNodeDirectionalityMap);
     aTextNode->SetProperty(nsGkAtoms::textNodeDirectionalityMap, this,
                            nsTextNodeDirectionalityMapDtor);
     aTextNode->SetHasTextNodeDirectionalityMap();
   }
 
   ~nsTextNodeDirectionalityMap()
   {
     MOZ_COUNT_DTOR(nsTextNodeDirectionalityMap);
   }
 
+  static void
+  nsTextNodeDirectionalityMapPropertyDestructor(void* aObject,
+                                                nsIAtom* aProperty,
+                                                void* aPropertyValue,
+                                                void* aData)
+  {
+    nsTextNode* textNode =
+      static_cast<nsTextNode*>(aPropertyValue);
+    nsTextNodeDirectionalityMap* map = GetDirectionalityMap(textNode);
+    if (map) {
+      map->RemoveEntryForProperty(static_cast<Element*>(aObject));
+    }
+    NS_RELEASE(textNode);
+  }
+
   void AddEntry(nsTextNode* aTextNode, Element* aElement)
   {
     if (!mElements.Contains(aElement)) {
       mElements.Put(aElement);
-      aElement->SetProperty(nsGkAtoms::dirAutoSetBy, aTextNode);
+      NS_ADDREF(aTextNode);
+      aElement->SetProperty(nsGkAtoms::dirAutoSetBy, aTextNode,
+                            nsTextNodeDirectionalityMapPropertyDestructor);
       aElement->SetHasDirAutoSet();
     }
   }
 
   void RemoveEntry(nsTextNode* aTextNode, Element* aElement)
   {
     NS_ASSERTION(mElements.Contains(aElement),
                  "element already removed from map");
 
     mElements.Remove(aElement);
     aElement->ClearHasDirAutoSet();
-    aElement->UnsetProperty(nsGkAtoms::dirAutoSetBy);
+    aElement->DeleteProperty(nsGkAtoms::dirAutoSetBy);
+  }
+
+  void RemoveEntryForProperty(Element* aElement)
+  {
+    if (mElementToBeRemoved != aElement) {
+      mElements.Remove(aElement);
+    }
+    aElement->ClearHasDirAutoSet();
   }
 
 private:
-  nsCheapSet<nsPtrHashKey<Element> > mElements;
+  nsCheapSet<nsPtrHashKey<Element>> mElements;
+  // Only used for comparison.
+  Element* mElementToBeRemoved;
 
   static nsTextNodeDirectionalityMap* GetDirectionalityMap(nsINode* aTextNode)
   {
     MOZ_ASSERT(aTextNode->NodeType() == nsIDOMNode::TEXT_NODE,
                "Must be a text node");
     nsTextNodeDirectionalityMap* map = nullptr;
 
     if (aTextNode->HasTextNodeDirectionalityMap()) {
@@ -484,64 +513,77 @@ private:
   static nsCheapSetOperator SetNodeDirection(nsPtrHashKey<Element>* aEntry, void* aDir)
   {
     MOZ_ASSERT(aEntry->GetKey()->IsElement(), "Must be an Element");
     aEntry->GetKey()->SetDirectionality(*reinterpret_cast<Directionality*>(aDir),
                                         true);
     return OpNext;
   }
 
+  struct nsTextNodeDirectionalityMapAndElement
+  {
+    nsTextNodeDirectionalityMap* mMap;
+    nsCOMPtr<nsINode> mNode;
+  };
+
   static nsCheapSetOperator ResetNodeDirection(nsPtrHashKey<Element>* aEntry, void* aData)
   {
     MOZ_ASSERT(aEntry->GetKey()->IsElement(), "Must be an Element");
     // run the downward propagation algorithm
     // and remove the text node from the map
-    nsINode* oldTextNode = static_cast<Element*>(aData);
+    nsTextNodeDirectionalityMapAndElement* data =
+      static_cast<nsTextNodeDirectionalityMapAndElement*>(aData);
+    nsINode* oldTextNode = data->mNode;
     Element* rootNode = aEntry->GetKey();
     nsTextNode* newTextNode = nullptr;
     if (rootNode->GetParentNode() && rootNode->HasDirAuto()) {
       newTextNode = WalkDescendantsSetDirectionFromText(rootNode, true,
                                                         oldTextNode);
     }
+
+    AutoRestore<Element*> restore(data->mMap->mElementToBeRemoved);
+    data->mMap->mElementToBeRemoved = rootNode;
     if (newTextNode) {
       nsINode* oldDirAutoSetBy = 
         static_cast<nsTextNode*>(rootNode->GetProperty(nsGkAtoms::dirAutoSetBy));
       if (oldDirAutoSetBy == newTextNode) {
         // We're already registered.
         return OpNext;
       }
       nsTextNodeDirectionalityMap::AddEntryToMap(newTextNode, rootNode);
     } else {
       rootNode->ClearHasDirAutoSet();
-      rootNode->UnsetProperty(nsGkAtoms::dirAutoSetBy);
+      rootNode->DeleteProperty(nsGkAtoms::dirAutoSetBy);
     }
     return OpRemove;
   }
 
   static nsCheapSetOperator ClearEntry(nsPtrHashKey<Element>* aEntry, void* aData)
   {
     Element* rootNode = aEntry->GetKey();
     rootNode->ClearHasDirAutoSet();
-    rootNode->UnsetProperty(nsGkAtoms::dirAutoSetBy);
+    rootNode->DeleteProperty(nsGkAtoms::dirAutoSetBy);
     return OpRemove;
   }
 
 public:
   uint32_t UpdateAutoDirection(Directionality aDir)
   {
     return mElements.EnumerateEntries(SetNodeDirection, &aDir);
   }
 
   void ResetAutoDirection(nsINode* aTextNode)
   {
-    mElements.EnumerateEntries(ResetNodeDirection, aTextNode);
+    nsTextNodeDirectionalityMapAndElement data = { this, aTextNode };
+    mElements.EnumerateEntries(ResetNodeDirection, &data);
   }
 
   void EnsureMapIsClear(nsINode* aTextNode)
   {
+    AutoRestore<Element*> restore(mElementToBeRemoved);
     DebugOnly<uint32_t> clearedEntries =
       mElements.EnumerateEntries(ClearEntry, aTextNode);
     MOZ_ASSERT(clearedEntries == 0, "Map should be empty already");
   }
 
   static void RemoveElementFromMap(nsTextNode* aTextNode, Element* aElement)
   {
     if (aTextNode->HasTextNodeDirectionalityMap()) {
@@ -567,17 +609,18 @@ public:
     return GetDirectionalityMap(aTextNode)->UpdateAutoDirection(aDir);
   }
 
   static void ResetTextNodeDirection(nsTextNode* aTextNode,
                                      nsTextNode* aChangedTextNode)
   {
     MOZ_ASSERT(aTextNode->HasTextNodeDirectionalityMap(),
                "Map missing in ResetTextNodeDirection");
-    GetDirectionalityMap(aTextNode)->ResetAutoDirection(aChangedTextNode);
+    RefPtr<nsTextNode> textNode = aTextNode;
+    GetDirectionalityMap(textNode)->ResetAutoDirection(aChangedTextNode);
   }
 
   static void EnsureMapIsClearFor(nsINode* aTextNode)
   {
     if (aTextNode->HasTextNodeDirectionalityMap()) {
       GetDirectionalityMap(aTextNode)->EnsureMapIsClear(aTextNode);
     }
   }
--- a/dom/base/Element.cpp
+++ b/dom/base/Element.cpp
@@ -3319,16 +3319,17 @@ nsDOMTokenListPropertyDestructor(void *a
     static_cast<nsDOMTokenList*>(aPropertyValue);
   NS_RELEASE(list);
 }
 
 static nsIAtom** sPropertiesToTraverseAndUnlink[] =
   {
     &nsGkAtoms::sandbox,
     &nsGkAtoms::sizes,
+    &nsGkAtoms::dirAutoSetBy,
     nullptr
   };
 
 // static
 nsIAtom***
 Element::HTMLSVGPropertiesToTraverseAndUnlink()
 {
   return sPropertiesToTraverseAndUnlink;
--- a/dom/base/nsContentUtils.cpp
+++ b/dom/base/nsContentUtils.cpp
@@ -321,16 +321,17 @@ uint32_t nsContentUtils::sHandlingInputT
 
 uint32_t nsContentUtils::sCookiesLifetimePolicy = nsICookieService::ACCEPT_NORMALLY;
 uint32_t nsContentUtils::sCookiesBehavior = nsICookieService::BEHAVIOR_ACCEPT;
 
 nsHtml5StringParser* nsContentUtils::sHTMLFragmentParser = nullptr;
 nsIParser* nsContentUtils::sXMLFragmentParser = nullptr;
 nsIFragmentContentSink* nsContentUtils::sXMLFragmentSink = nullptr;
 bool nsContentUtils::sFragmentParsingActive = false;
+nsISerialEventTarget* nsContentUtils::sStableStateEventTarget = nullptr;
 
 #if !(defined(DEBUG) || defined(MOZ_ENABLE_JS_DUMP))
 bool nsContentUtils::sDOMWindowDumpEnabled;
 #endif
 
 bool nsContentUtils::sDoNotTrackEnabled = false;
 
 mozilla::LazyLogModule nsContentUtils::sDOMDumpLog("Dump");
@@ -514,16 +515,62 @@ class SameOriginCheckerImpl final : publ
 {
   ~SameOriginCheckerImpl() = default;
 
   NS_DECL_ISUPPORTS
   NS_DECL_NSICHANNELEVENTSINK
   NS_DECL_NSIINTERFACEREQUESTOR
 };
 
+class StableStateEventTarget final : public nsISerialEventTarget
+{
+public:
+  NS_DECL_THREADSAFE_ISUPPORTS
+  NS_DECL_NSIEVENTTARGET_FULL
+private:
+  ~StableStateEventTarget() {}
+};
+
+NS_IMPL_ISUPPORTS(StableStateEventTarget, nsISerialEventTarget);
+
+bool
+StableStateEventTarget::IsOnCurrentThreadInfallible()
+{
+  return true;
+}
+
+NS_IMETHODIMP
+StableStateEventTarget::IsOnCurrentThread(bool* aResult)
+{
+  *aResult = true;
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+StableStateEventTarget::Dispatch(already_AddRefed<nsIRunnable> aEvent, uint32_t aFlags)
+{
+  if (NS_WARN_IF(!CycleCollectedJSContext::Get())) {
+    return NS_ERROR_UNEXPECTED;
+  }
+  nsContentUtils::RunInStableState(Move(aEvent));
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+StableStateEventTarget::DispatchFromScript(nsIRunnable* aEvent, uint32_t aFlags)
+{
+  return Dispatch(nsCOMPtr<nsIRunnable>(aEvent).forget(), aFlags);
+}
+
+NS_IMETHODIMP
+StableStateEventTarget::DelayedDispatch(already_AddRefed<nsIRunnable> aEvent, uint32_t aDelay)
+{
+  return NS_ERROR_NOT_IMPLEMENTED;
+}
+
 } // namespace
 
 /**
  * This class is used to determine whether or not the user is currently
  * interacting with the browser. It listens to observer events to toggle the
  * value of the sUserActive static.
  *
  * This class is an internal implementation detail.
@@ -725,16 +772,19 @@ nsContentUtils::Init()
 
   nsDependentCString buildID(mozilla::PlatformBuildID());
   sJSBytecodeMimeType = new nsCString(NS_LITERAL_CSTRING("javascript/moz-bytecode-") + buildID);
 
   Element::InitCCCallbacks();
 
   Unused << nsRFPService::GetOrCreate();
 
+  RefPtr<StableStateEventTarget> stableStateEventTarget = new StableStateEventTarget();
+  stableStateEventTarget.forget(&sStableStateEventTarget);
+
   nsCOMPtr<nsIUUIDGenerator> uuidGenerator =
     do_GetService("@mozilla.org/uuid-generator;1", &rv);
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return rv;
   }
   uuidGenerator.forget(&sUUIDGenerator);
 
   RefPtr<UserInteractionObserver> uio = new UserInteractionObserver();
@@ -2151,16 +2201,18 @@ nsContentUtils::Shutdown()
   delete sModifierSeparator;
   sModifierSeparator = nullptr;
 
   delete sJSBytecodeMimeType;
   sJSBytecodeMimeType = nullptr;
 
   NS_IF_RELEASE(sSameOriginChecker);
 
+  NS_IF_RELEASE(sStableStateEventTarget);
+
   if (sUserInteractionObserver) {
     sUserInteractionObserver->Shutdown();
     NS_RELEASE(sUserInteractionObserver);
   }
 
   HTMLInputElement::Shutdown();
   nsMappedAttributes::Shutdown();
 }
@@ -5656,16 +5708,23 @@ nsContentUtils::RunInStableState(already
 /* static */
 void
 nsContentUtils::RunInMetastableState(already_AddRefed<nsIRunnable> aRunnable)
 {
   MOZ_ASSERT(CycleCollectedJSContext::Get(), "Must be on a script thread!");
   CycleCollectedJSContext::Get()->RunInMetastableState(Move(aRunnable));
 }
 
+/* static */
+nsISerialEventTarget*
+nsContentUtils::GetStableStateEventTarget()
+{
+  return sStableStateEventTarget;
+}
+
 void
 nsContentUtils::EnterMicroTask()
 {
   MOZ_ASSERT(NS_IsMainThread());
   ++sMicroTaskLevel;
 }
 
 void
--- a/dom/base/nsContentUtils.h
+++ b/dom/base/nsContentUtils.h
@@ -1874,16 +1874,26 @@ public:
    * microtask has finished.  This is not specced at this time.
    * In practice this runs aRunnable once the currently executing task or
    * microtask finishes.  If called multiple times per microtask, all the
    * runnables will be executed, in the order in which RunInMetastableState()
    * was called
    */
   static void RunInMetastableState(already_AddRefed<nsIRunnable> aRunnable);
 
+  /**
+   * Returns a nsISerialEventTarget which will run any event dispatched to it
+   * once the event loop has reached a "stable state". Runnables dispatched to
+   * this event target must not cause any queued events to be processed (i.e.
+   * must not spin the event loop).
+   *
+   * See RunInStableState for more information about stable states
+   */
+  static nsISerialEventTarget* GetStableStateEventTarget();
+
   // Call EnterMicroTask when you're entering JS execution.
   // Usually the best way to do this is to use nsAutoMicroTask.
   static void EnterMicroTask();
   static void LeaveMicroTask();
 
   static bool IsInMicroTask();
   static uint32_t MicroTaskLevel();
   static void SetMicroTaskLevel(uint32_t aLevel);
@@ -3222,16 +3232,18 @@ private:
   static nsIParser* sXMLFragmentParser;
   static nsIFragmentContentSink* sXMLFragmentSink;
 
   /**
    * True if there's a fragment parser activation on the stack.
    */
   static bool sFragmentParsingActive;
 
+  static nsISerialEventTarget* sStableStateEventTarget;
+
   static nsString* sShiftText;
   static nsString* sControlText;
   static nsString* sMetaText;
   static nsString* sOSText;
   static nsString* sAltText;
   static nsString* sModifierSeparator;
 
   // Alternate data mime type, used by the ScriptLoader to register and read the
--- a/dom/base/nsGlobalWindowCommands.cpp
+++ b/dom/base/nsGlobalWindowCommands.cpp
@@ -31,21 +31,23 @@
 #include "nsIClipboard.h"
 #include "ContentEventHandler.h"
 #include "nsContentUtils.h"
 #include "nsIWordBreaker.h"
 #include "mozilla/Attributes.h"
 #include "mozilla/BasicEvents.h"
 #include "mozilla/TextEvents.h"
 #include "mozilla/dom/Selection.h"
+#include "mozilla/layers/KeyboardMap.h"
 
 #include "nsIClipboardDragDropHooks.h"
 #include "nsIClipboardDragDropHookList.h"
 
 using namespace mozilla;
+using namespace mozilla::layers;
 
 constexpr const char * sSelectAllString = "cmd_selectAll";
 constexpr const char * sSelectNoneString = "cmd_selectNone";
 constexpr const char * sCopyImageLocationString = "cmd_copyImageLocation";
 constexpr const char * sCopyImageContentsString = "cmd_copyImageContents";
 constexpr const char * sCopyImageString = "cmd_copyImage";
 
 constexpr const char * sScrollTopString = "cmd_scrollTop";
@@ -266,43 +268,54 @@ IsCaretOnInWindow(nsPIDOMWindowOuter* aW
       }
     }
   }
   return caretOn;
 }
 
 static constexpr struct BrowseCommand {
   const char *reverse, *forward;
+  KeyboardScrollAction::KeyboardScrollActionType scrollAction;
   nsresult (NS_STDCALL nsISelectionController::*scroll)(bool);
   nsresult (NS_STDCALL nsISelectionController::*move)(bool, bool);
 } browseCommands[] = {
  { sScrollTopString, sScrollBottomString,
+   KeyboardScrollAction::eScrollComplete,
    &nsISelectionController::CompleteScroll },
  { sScrollPageUpString, sScrollPageDownString,
+   KeyboardScrollAction::eScrollPage,
    &nsISelectionController::ScrollPage },
  { sScrollLineUpString, sScrollLineDownString,
+   KeyboardScrollAction::eScrollLine,
    &nsISelectionController::ScrollLine },
  { sScrollLeftString, sScrollRightString,
+   KeyboardScrollAction::eScrollCharacter,
    &nsISelectionController::ScrollCharacter },
  { sMoveTopString, sMoveBottomString,
+   KeyboardScrollAction::eScrollComplete,
    &nsISelectionController::CompleteScroll,
    &nsISelectionController::CompleteMove },
  { sMovePageUpString, sMovePageDownString,
+   KeyboardScrollAction::eScrollPage,
    &nsISelectionController::ScrollPage,
    &nsISelectionController::PageMove },
  { sLinePreviousString, sLineNextString,
+   KeyboardScrollAction::eScrollLine,
    &nsISelectionController::ScrollLine,
    &nsISelectionController::LineMove },
  { sWordPreviousString, sWordNextString,
+   KeyboardScrollAction::eScrollCharacter,
    &nsISelectionController::ScrollCharacter,
    &nsISelectionController::WordMove },
  { sCharPreviousString, sCharNextString,
+   KeyboardScrollAction::eScrollCharacter,
    &nsISelectionController::ScrollCharacter,
    &nsISelectionController::CharacterMove },
  { sBeginLineString, sEndLineString,
+   KeyboardScrollAction::eScrollComplete,
    &nsISelectionController::CompleteScroll,
    &nsISelectionController::IntraLineMove }
 };
 
 nsresult
 nsSelectMoveScrollCommand::DoCommand(const char *aCommandName, nsISupports *aCommandContext)
 {
   nsCOMPtr<nsPIDOMWindowOuter> piWindow(do_QueryInterface(aCommandContext));
@@ -329,33 +342,42 @@ nsSelectMoveScrollCommand::DoCommand(con
 
 // XXX It's not clear to me yet how we should handle the "scroll" option
 // for these commands; for now, I'm mapping them back to ScrollCharacter,
 // ScrollLine, etc., as if for horizontal-mode content, but this may need
 // to be reconsidered once we have more experience with vertical content.
 static const struct PhysicalBrowseCommand {
   const char *command;
   int16_t direction, amount;
+  KeyboardScrollAction::KeyboardScrollActionType scrollAction;
   nsresult (NS_STDCALL nsISelectionController::*scroll)(bool);
 } physicalBrowseCommands[] = {
  { sMoveLeftString, nsISelectionController::MOVE_LEFT, 0,
+   KeyboardScrollAction::eScrollCharacter,
    &nsISelectionController::ScrollCharacter },
  { sMoveRightString, nsISelectionController::MOVE_RIGHT, 0,
+   KeyboardScrollAction::eScrollCharacter,
    &nsISelectionController::ScrollCharacter },
  { sMoveUpString, nsISelectionController::MOVE_UP, 0,
+   KeyboardScrollAction::eScrollLine,
    &nsISelectionController::ScrollLine },
  { sMoveDownString, nsISelectionController::MOVE_DOWN, 0,
+   KeyboardScrollAction::eScrollLine,
    &nsISelectionController::ScrollLine },
  { sMoveLeft2String, nsISelectionController::MOVE_LEFT, 1,
+   KeyboardScrollAction::eScrollCharacter,
    &nsISelectionController::ScrollCharacter },
  { sMoveRight2String, nsISelectionController::MOVE_RIGHT, 1,
+   KeyboardScrollAction::eScrollCharacter,
    &nsISelectionController::ScrollCharacter },
  { sMoveUp2String, nsISelectionController::MOVE_UP, 1,
+   KeyboardScrollAction::eScrollComplete,
    &nsISelectionController::CompleteScroll },
  { sMoveDown2String, nsISelectionController::MOVE_DOWN, 1,
+   KeyboardScrollAction::eScrollComplete,
    &nsISelectionController::CompleteScroll },
 };
 
 nsresult
 nsPhysicalSelectMoveScrollCommand::DoCommand(const char *aCommandName,
                                              nsISupports *aCommandContext)
 {
   nsCOMPtr<nsPIDOMWindowOuter> piWindow(do_QueryInterface(aCommandContext));
@@ -1257,8 +1279,41 @@ nsWindowCommandRegistration::RegisterWin
 #endif
 
   NS_REGISTER_ONE_COMMAND(nsClipboardDragDropHookCommand, "cmd_clipboardDragDropHook");
 
   NS_REGISTER_ONE_COMMAND(nsLookUpDictionaryCommand, "cmd_lookUpDictionary");
 
   return rv;
 }
+
+/* static */ bool
+nsGlobalWindowCommands::FindScrollCommand(const char* aCommandName,
+                                          KeyboardScrollAction* aOutAction)
+{
+  // Search for a keyboard scroll action to do for this command in browseCommands
+  // and physicalBrowseCommands. Each command exists in only one of them, so the
+  // order we examine browseCommands and physicalBrowseCommands doesn't matter.
+
+  for (size_t i = 0; i < ArrayLength(browseCommands); i++) {
+    const BrowseCommand& cmd = browseCommands[i];
+    bool forward = !strcmp(aCommandName, cmd.forward);
+    bool reverse = !strcmp(aCommandName, cmd.reverse);
+    if (forward || reverse) {
+      *aOutAction = KeyboardScrollAction(cmd.scrollAction, forward);
+      return true;
+    }
+  }
+
+  for (size_t i = 0; i < ArrayLength(physicalBrowseCommands); i++) {
+    const PhysicalBrowseCommand& cmd = physicalBrowseCommands[i];
+    if (!strcmp(aCommandName, cmd.command)) {
+      int16_t dir = cmd.direction;
+      bool forward = (dir == nsISelectionController::MOVE_RIGHT ||
+                      dir == nsISelectionController::MOVE_DOWN);
+
+      *aOutAction = KeyboardScrollAction(cmd.scrollAction, forward);
+      return true;
+    }
+  }
+
+  return false;
+}
--- a/dom/base/nsGlobalWindowCommands.h
+++ b/dom/base/nsGlobalWindowCommands.h
@@ -4,24 +4,46 @@
  * 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 nsGlobalWindowCommands_h__
 #define nsGlobalWindowCommands_h__
 
 #include "nscore.h"
 
+namespace mozilla {
+namespace layers {
+struct KeyboardScrollAction;
+} // namespace layers
+} // namespace mozilla
+
 class nsIControllerCommandTable;
 
 class nsWindowCommandRegistration
 {
 public:
   static nsresult  RegisterWindowCommands(nsIControllerCommandTable *ccm);
 };
 
+class nsGlobalWindowCommands
+{
+public:
+  typedef mozilla::layers::KeyboardScrollAction KeyboardScrollAction;
+
+  /**
+   * Search through nsGlobalWindowCommands to find the keyboard scrolling action
+   * that would be done in response to a command.
+   *
+   * @param aCommandName the name of the command
+   * @param aOutAction the result of searching for this command, must not be null
+   * @returns whether a keyboard action was found or not
+   */
+  static bool FindScrollCommand(const char* aCommandName,
+                                KeyboardScrollAction* aOutAction);
+};
 
 // XXX find a better home for these
 #define NS_WINDOWCONTROLLER_CID        \
  { /* 7BD05C78-6A26-11D7-B16F-0003938A9D96 */       \
   0x7BD05C78, 0x6A26, 0x11D7, {0xB1, 0x6F, 0x00, 0x03, 0x93, 0x8A, 0x9D, 0x96} }
 
 #define NS_WINDOWCONTROLLER_CONTRACTID "@mozilla.org/dom/window-controller;1"
 
--- a/dom/base/nsWrapperCache.h
+++ b/dom/base/nsWrapperCache.h
@@ -36,17 +36,17 @@ class nsWindowRoot;
 // wasted 32-bit fields due to alignment requirements. Some compilers are
 // smart enough to coalesce the fields if we make mBoolFlags the first member
 // of nsINode, but others (such as MSVC) are not.
 //
 // So we just store mBoolFlags directly on nsWrapperCache on 64-bit platforms.
 // This may waste space for some other nsWrapperCache-derived objects that have
 // a 32-bit field as their first member, but those objects are unlikely to be as
 // numerous or performance-critical as DOM nodes.
-#if defined(_M_X64) || defined(__x86_64__) || defined(__aarch64__)
+#if defined(_M_X64) || defined(__LP64__)
 static_assert(sizeof(void*) == 8, "These architectures should be 64-bit");
 #define BOOL_FLAGS_ON_WRAPPER_CACHE
 #else
 static_assert(sizeof(void*) == 4, "Only support 32-bit and 64-bit");
 #endif
 
 /**
  * Class to store the wrapper for an object. This can only be used with objects
--- a/dom/canvas/TexUnpackBlob.cpp
+++ b/dom/canvas/TexUnpackBlob.cpp
@@ -92,20 +92,22 @@ FormatForPackingInfo(const PackingInfo& 
         case LOCAL_GL_ALPHA:
             return WebGLTexelFormat::A8;
 
         case LOCAL_GL_LUMINANCE_ALPHA:
             return WebGLTexelFormat::RA8;
 
         case LOCAL_GL_RGB:
         case LOCAL_GL_RGB_INTEGER:
+        case LOCAL_GL_SRGB:
             return WebGLTexelFormat::RGB8;
 
         case LOCAL_GL_RGBA:
         case LOCAL_GL_RGBA_INTEGER:
+        case LOCAL_GL_SRGB_ALPHA:
             return WebGLTexelFormat::RGBA8;
 
         case LOCAL_GL_RG:
         case LOCAL_GL_RG_INTEGER:
             return WebGLTexelFormat::RG8;
 
         default:
             break;
@@ -429,21 +431,21 @@ TexUnpackBytes::Validate(WebGLContext* w
 
     return ValidateUnpackBytes(webgl, funcName, pi, mAvailBytes, this);
 }
 
 bool
 TexUnpackBytes::TexOrSubImage(bool isSubImage, bool needsRespec, const char* funcName,
                               WebGLTexture* tex, TexImageTarget target, GLint level,
                               const webgl::DriverUnpackInfo* dui, GLint xOffset,
-                              GLint yOffset, GLint zOffset, GLenum* const out_error) const
+                              GLint yOffset, GLint zOffset, const webgl::PackingInfo& pi,
+                              GLenum* const out_error) const
 {
     WebGLContext* webgl = tex->mContext;
 
-    const auto pi = dui->ToPacking();
     const auto format = FormatForPackingInfo(pi);
     const auto bytesPerPixel = webgl::BytesPerPixel(pi);
 
     const uint8_t* uploadPtr = mPtr;
     UniqueBuffer tempBuffer;
 
     do {
         if (!mIsClientData || !mPtr)
@@ -608,17 +610,18 @@ TexUnpackImage::Validate(WebGLContext* w
     const auto fullRows = mImage->GetSize().height;
     return ValidateUnpackPixels(webgl, funcName, fullRows, 0, this);
 }
 
 bool
 TexUnpackImage::TexOrSubImage(bool isSubImage, bool needsRespec, const char* funcName,
                               WebGLTexture* tex, TexImageTarget target, GLint level,
                               const webgl::DriverUnpackInfo* dui, GLint xOffset,
-                              GLint yOffset, GLint zOffset, GLenum* const out_error) const
+                              GLint yOffset, GLint zOffset, const webgl::PackingInfo& pi,
+                              GLenum* const out_error) const
 {
     MOZ_ASSERT_IF(needsRespec, !isSubImage);
 
     WebGLContext* webgl = tex->mContext;
 
     gl::GLContext* gl = webgl->GL();
     gl->MakeCurrent();
 
@@ -734,17 +737,17 @@ TexUnpackImage::TexOrSubImage(bool isSub
                                 funcName);
         return false;
     }
 
     const TexUnpackSurface surfBlob(webgl, target, mWidth, mHeight, mDepth, dataSurf,
                                     mSrcAlphaType);
 
     return surfBlob.TexOrSubImage(isSubImage, needsRespec, funcName, tex, target, level,
-                                  dui, xOffset, yOffset, zOffset, out_error);
+                                  dui, xOffset, yOffset, zOffset, pi, out_error);
 }
 
 ////////////////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////////////////
 // TexUnpackSurface
 
 TexUnpackSurface::TexUnpackSurface(const WebGLContext* webgl, TexImageTarget target,
                                    uint32_t width, uint32_t height, uint32_t depth,
@@ -816,28 +819,27 @@ TexUnpackSurface::Validate(WebGLContext*
 
     const auto fullRows = mSurf->GetSize().height;
     return ValidateUnpackPixels(webgl, funcName, fullRows, 0, this);
 }
 
 bool
 TexUnpackSurface::TexOrSubImage(bool isSubImage, bool needsRespec, const char* funcName,
                                 WebGLTexture* tex, TexImageTarget target, GLint level,
-                                const webgl::DriverUnpackInfo* dstDUI, GLint xOffset,
-                                GLint yOffset, GLint zOffset,
+                                const webgl::DriverUnpackInfo* dui, GLint xOffset,
+                                GLint yOffset, GLint zOffset, const webgl::PackingInfo& dstPI,
                                 GLenum* const out_error) const
 {
     const auto& webgl = tex->mContext;
 
     ////
 
     const auto rowLength = mSurf->GetSize().width;
     const auto rowCount = mSurf->GetSize().height;
 
-    const auto& dstPI = dstDUI->ToPacking();
     const auto& dstBPP = webgl::BytesPerPixel(dstPI);
     const auto dstFormat = FormatForPackingInfo(dstPI);
 
     ////
 
     WebGLTexelFormat srcFormat;
     uint8_t srcBPP;
     if (!GetFormatForSurf(mSurf, &srcFormat, &srcBPP)) {
@@ -887,17 +889,17 @@ TexUnpackSurface::TexOrSubImage(bool isS
     const auto& gl = webgl->gl;
     MOZ_ALWAYS_TRUE( gl->MakeCurrent() );
 
     gl->fPixelStorei(LOCAL_GL_UNPACK_ALIGNMENT, dstAlignment);
     if (webgl->IsWebGL2()) {
         gl->fPixelStorei(LOCAL_GL_UNPACK_ROW_LENGTH, rowLength);
     }
 
-    *out_error = DoTexOrSubImage(isSubImage, gl, target.get(), level, dstDUI, xOffset,
+    *out_error = DoTexOrSubImage(isSubImage, gl, target.get(), level, dui, xOffset,
                                  yOffset, zOffset, mWidth, mHeight, mDepth, dstBegin);
 
     gl->fPixelStorei(LOCAL_GL_UNPACK_ALIGNMENT, webgl->mPixelStore_UnpackAlignment);
     if (webgl->IsWebGL2()) {
         gl->fPixelStorei(LOCAL_GL_UNPACK_ROW_LENGTH, webgl->mPixelStore_UnpackRowLength);
     }
 
     return true;
--- a/dom/canvas/TexUnpackBlob.h
+++ b/dom/canvas/TexUnpackBlob.h
@@ -81,17 +81,17 @@ public:
 
     // Returns false when we've generated a WebGL error.
     // Returns true but with a non-zero *out_error if we still need to generate a WebGL
     // error.
     virtual bool TexOrSubImage(bool isSubImage, bool needsRespec, const char* funcName,
                                WebGLTexture* tex, TexImageTarget target, GLint level,
                                const webgl::DriverUnpackInfo* dui, GLint xOffset,
                                GLint yOffset, GLint zOffset,
-                               GLenum* const out_error) const = 0;
+                               const webgl::PackingInfo& pi, GLenum* const out_error) const = 0;
 };
 
 class TexUnpackBytes final : public TexUnpackBlob
 {
 public:
     const bool mIsClientData;
     const uint8_t* const mPtr;
     const size_t mAvailBytes;
@@ -103,17 +103,17 @@ public:
     virtual bool HasData() const override { return !mIsClientData || bool(mPtr); }
 
     virtual bool Validate(WebGLContext* webgl, const char* funcName,
                           const webgl::PackingInfo& pi) override;
     virtual bool TexOrSubImage(bool isSubImage, bool needsRespec, const char* funcName,
                                WebGLTexture* tex, TexImageTarget target, GLint level,
                                const webgl::DriverUnpackInfo* dui, GLint xOffset,
                                GLint yOffset, GLint zOffset,
-                               GLenum* const out_error) const override;
+                               const webgl::PackingInfo& pi, GLenum* const out_error) const override;
 };
 
 class TexUnpackImage final : public TexUnpackBlob
 {
 public:
     const RefPtr<layers::Image> mImage;
 
     TexUnpackImage(const WebGLContext* webgl, TexImageTarget target, uint32_t width,
@@ -123,17 +123,17 @@ public:
     ~TexUnpackImage(); // Prevent needing to define layers::Image in the header.
 
     virtual bool Validate(WebGLContext* webgl, const char* funcName,
                           const webgl::PackingInfo& pi) override;
     virtual bool TexOrSubImage(bool isSubImage, bool needsRespec, const char* funcName,
                                WebGLTexture* tex, TexImageTarget target, GLint level,
                                const webgl::DriverUnpackInfo* dui, GLint xOffset,
                                GLint yOffset, GLint zOffset,
-                               GLenum* const out_error) const override;
+                               const webgl::PackingInfo& dstPI, GLenum* const out_error)  const override;
 };
 
 class TexUnpackSurface final : public TexUnpackBlob
 {
 public:
     const RefPtr<gfx::DataSourceSurface> mSurf;
 
     TexUnpackSurface(const WebGLContext* webgl, TexImageTarget target, uint32_t width,
@@ -141,15 +141,15 @@ public:
                      gfxAlphaType srcAlphaType);
 
     virtual bool Validate(WebGLContext* webgl, const char* funcName,
                           const webgl::PackingInfo& pi) override;
     virtual bool TexOrSubImage(bool isSubImage, bool needsRespec, const char* funcName,
                                WebGLTexture* tex, TexImageTarget target, GLint level,
                                const webgl::DriverUnpackInfo* dui, GLint xOffset,
                                GLint yOffset, GLint zOffset,
-                               GLenum* const out_error) const override;
+                               const webgl::PackingInfo& dstPI, GLenum* const out_error) const override;
 };
 
 } // namespace webgl
 } // namespace mozilla
 
 #endif // TEX_UNPACK_BLOB_H_
--- a/dom/canvas/WebGLTextureUpload.cpp
+++ b/dom/canvas/WebGLTextureUpload.cpp
@@ -1287,17 +1287,17 @@ WebGLTexture::TexImage(const char* funcN
                               imageInfo->mDepth  != newImageInfo.mDepth ||
                               imageInfo->mFormat != newImageInfo.mFormat);
     const GLint xOffset = 0;
     const GLint yOffset = 0;
     const GLint zOffset = 0;
 
     GLenum glError;
     if (!blob->TexOrSubImage(isSubImage, needsRespec, funcName, this, target, level,
-                             driverUnpackInfo, xOffset, yOffset, zOffset, &glError))
+                             driverUnpackInfo, xOffset, yOffset, zOffset, pi, &glError))
     {
         return;
     }
 
     if (glError == LOCAL_GL_OUT_OF_MEMORY) {
         mContext->ErrorOutOfMemory("%s: Driver ran out of memory during upload.",
                                    funcName);
         return;
@@ -1375,17 +1375,17 @@ WebGLTexture::TexSubImage(const char* fu
         return;
     }
 
     const bool isSubImage = true;
     const bool needsRespec = false;
 
     GLenum glError;
     if (!blob->TexOrSubImage(isSubImage, needsRespec, funcName, this, target, level,
-                             driverUnpackInfo, xOffset, yOffset, zOffset, &glError))
+                             driverUnpackInfo, xOffset, yOffset, zOffset, pi, &glError))
     {
         return;
     }
 
     if (glError == LOCAL_GL_OUT_OF_MEMORY) {
         mContext->ErrorOutOfMemory("%s: Driver ran out of memory during upload.",
                                    funcName);
         return;
--- a/dom/canvas/test/webgl-conf/generated-mochitest.ini
+++ b/dom/canvas/test/webgl-conf/generated-mochitest.ini
@@ -7373,17 +7373,16 @@ skip-if = 1 || (os == 'android' || os ==
 skip-if = (os == 'win' && os_version == '6.1') || (os == 'android' || os == 'linux')
 [generated/test_2_conformance__textures__misc__tex-image-and-uniform-binding-bugs.html]
 skip-if = (os == 'android' || os == 'linux')
 [generated/test_2_conformance__textures__misc__tex-image-canvas-corruption.html]
 skip-if = (os == 'android' || os == 'linux')
 [generated/test_2_conformance__textures__misc__tex-image-webgl.html]
 skip-if = (os == 'android' || os == 'linux')
 [generated/test_2_conformance__textures__misc__tex-image-with-format-and-type.html]
-fail-if = (os == 'mac')
 skip-if = (os == 'android' || os == 'linux')
 [generated/test_2_conformance__textures__misc__tex-image-with-invalid-data.html]
 skip-if = (os == 'android' || os == 'linux')
 [generated/test_2_conformance__textures__misc__tex-sub-image-2d-bad-args.html]
 skip-if = (os == 'android' || os == 'linux')
 [generated/test_2_conformance__textures__misc__tex-sub-image-2d.html]
 skip-if = (os == 'android' || os == 'linux')
 [generated/test_2_conformance__textures__misc__texparameter-test.html]
--- a/dom/canvas/test/webgl-conf/mochitest-errata.ini
+++ b/dom/canvas/test/webgl-conf/mochitest-errata.ini
@@ -125,18 +125,16 @@ skip-if = (os == 'android' && debug)
 fail-if = (os == 'mac') || (os == 'linux') || (os == 'win')
 # 10.6 crash:
 # PROCESS-CRASH | dom/canvas/test/webgl-conf/generated/test_conformance__extensions__oes-vertex-array-object.html | application crashed [@ gleRunVertexSubmitImmediate + 0xf24]
 skip-if = (os == 'mac' && os_version == '10.6')
 [generated/test_conformance__textures__misc__texture-size.html]
 # application crashed [@ mozilla::gl::GLContext::AfterGLCall]
 skip-if = (os == 'android') || (os == 'win')
 
-[generated/test_2_conformance__textures__misc__tex-image-with-format-and-type.html]
-fail-if = (os == 'mac')
 [generated/test_2_conformance2__vertex_arrays__vertex-array-object.html]
 fail-if = (os == 'mac') || (os == 'win')
 [generated/test_conformance__extensions__oes-texture-half-float.html]
 fail-if = (os == 'mac') || (os == 'win') || (os == 'android') || (os == 'linux')
 [generated/test_conformance__attribs__gl-vertexattribpointer.html]
 fail-if = (os == 'android')
 [generated/test_conformance__ogles__GL__biuDepthRange__biuDepthRange_001_to_002.html]
 fail-if = (os == 'android')
--- a/dom/events/EventListenerManager.cpp
+++ b/dom/events/EventListenerManager.cpp
@@ -1763,16 +1763,33 @@ EventListenerManager::TraceListeners(JST
       mozilla::TraceScriptHolder(listener.mListener.GetWebIDLCallback(), aTrc);
     }
     // We might have eWrappedJSListener, but that is the legacy type for
     // JS implemented event listeners, and trickier to handle here.
   }
 }
 
 bool
+EventListenerManager::HasUntrustedOrNonSystemGroupKeyEventListeners()
+{
+  uint32_t count = mListeners.Length();
+  for (uint32_t i = 0; i < count; ++i) {
+    Listener* listener = &mListeners.ElementAt(i);
+    if (!listener->mFlags.mInSystemGroup &&
+        listener->mFlags.mAllowUntrustedEvents &&
+        (listener->mTypeAtom == nsGkAtoms::onkeydown ||
+         listener->mTypeAtom == nsGkAtoms::onkeypress ||
+         listener->mTypeAtom == nsGkAtoms::onkeyup)) {
+      return true;
+    }
+  }
+  return false;
+}
+
+bool
 EventListenerManager::HasApzAwareListeners()
 {
   uint32_t count = mListeners.Length();
   for (uint32_t i = 0; i < count; ++i) {
     Listener* listener = &mListeners.ElementAt(i);
     if (IsApzAwareListener(listener)) {
       return true;
     }
--- a/dom/events/EventListenerManager.h
+++ b/dom/events/EventListenerManager.h
@@ -465,16 +465,18 @@ public:
   }
 
   void MarkForCC();
 
   void TraceListeners(JSTracer* aTrc);
 
   dom::EventTarget* GetTarget() { return mTarget; }
 
+  bool HasUntrustedOrNonSystemGroupKeyEventListeners();
+
   bool HasApzAwareListeners();
   bool IsApzAwareListener(Listener* aListener);
   bool IsApzAwareEvent(nsIAtom* aEvent);
 
 protected:
   void HandleEventInternal(nsPresContext* aPresContext,
                            WidgetEvent* aEvent,
                            nsIDOMEvent** aDOMEvent,
--- a/dom/events/EventStateManager.cpp
+++ b/dom/events/EventStateManager.cpp
@@ -5282,17 +5282,17 @@ EventStateManager::DoContentCommandScrol
       break;
     default:
       return NS_ERROR_INVALID_ARG;
   }
 
   aEvent->mSucceeded = true;
 
   nsIScrollableFrame* sf =
-    ps->GetFrameToScrollAsScrollable(nsIPresShell::eEither);
+    ps->GetScrollableFrameToScroll(nsIPresShell::eEither);
   aEvent->mIsEnabled = sf ?
     (aEvent->mScroll.mIsHorizontal ?
       WheelHandlingUtils::CanScrollOn(sf, aEvent->mScroll.mAmount, 0) :
       WheelHandlingUtils::CanScrollOn(sf, 0, aEvent->mScroll.mAmount)) : false;
 
   if (!aEvent->mIsEnabled || aEvent->mOnlyEnabledCheck) {
     return NS_OK;
   }
--- a/dom/events/EventTarget.cpp
+++ b/dom/events/EventTarget.cpp
@@ -53,16 +53,23 @@ EventTarget::SetEventHandler(const nsASt
 void
 EventTarget::SetEventHandler(nsIAtom* aType, const nsAString& aTypeString,
                              EventHandlerNonNull* aHandler)
 {
   GetOrCreateListenerManager()->SetEventHandler(aType, aTypeString, aHandler);
 }
 
 bool
+EventTarget::HasUntrustedOrNonSystemGroupKeyEventListeners() const
+{
+  EventListenerManager* elm = GetExistingListenerManager();
+  return elm && elm->HasUntrustedOrNonSystemGroupKeyEventListeners();
+}
+
+bool
 EventTarget::IsApzAware() const
 {
   EventListenerManager* elm = GetExistingListenerManager();
   return elm && elm->HasApzAwareListeners();
 }
 
 bool
 EventTarget::DispatchEvent(Event& aEvent,
--- a/dom/events/EventTarget.h
+++ b/dom/events/EventTarget.h
@@ -94,16 +94,20 @@ public:
    * Get the event listener manager, returning null if it does not already
    * exist.
    */
   virtual EventListenerManager* GetExistingListenerManager() const = 0;
 
   // Called from AsyncEventDispatcher to notify it is running.
   virtual void AsyncEventRunning(AsyncEventDispatcher* aEvent) {}
 
+  // Used by FocusTarget to determine whether this event target has listeners
+  // for untrusted or non system group key events.
+  bool HasUntrustedOrNonSystemGroupKeyEventListeners() const;
+
   virtual bool IsApzAware() const;
 
 protected:
   EventHandlerNonNull* GetEventHandler(nsIAtom* aType,
                                        const nsAString& aTypeString);
   void SetEventHandler(nsIAtom* aType, const nsAString& aTypeString,
                        EventHandlerNonNull* aHandler);
 };
--- a/dom/ipc/ContentChild.cpp
+++ b/dom/ipc/ContentChild.cpp
@@ -854,64 +854,144 @@ ContentChild::ProvideWindowCommon(TabChi
 
   PRenderFrameChild* renderFrame = newChild->SendPRenderFrameConstructor();
   TextureFactoryIdentifier textureFactoryIdentifier;
   uint64_t layersId = 0;
   CompositorOptions compositorOptions;
   uint32_t maxTouchPoints = 0;
   DimensionInfo dimensionInfo;
 
+  nsCOMPtr<nsPIDOMWindowInner> parentTopInnerWindow;
+  if (aParent) {
+    nsCOMPtr<nsPIDOMWindowOuter> parentTopWindow =
+      nsPIDOMWindowOuter::From(aParent)->GetTop();
+    if (parentTopWindow) {
+      parentTopInnerWindow = parentTopWindow->GetCurrentInnerWindow();
+    }
+  }
+
+  // Send down the request to open the window.
+  RefPtr<CreateWindowPromise> windowCreated;
   if (aIframeMoz) {
     MOZ_ASSERT(aTabOpener);
     nsAutoCString url;
     if (aURI) {
       aURI->GetSpec(url);
     } else {
       // We can't actually send a nullptr up as the URI, since IPDL doesn't let us
       // send nullptr's for primitives. We indicate that the nsString for the URI
       // should be converted to a nullptr by voiding the string.
       url.SetIsVoid(true);
     }
 
-    newChild->SendBrowserFrameOpenWindow(aTabOpener, renderFrame, NS_ConvertUTF8toUTF16(url),
-                                         name, NS_ConvertUTF8toUTF16(features),
-                                         aWindowIsNew, &textureFactoryIdentifier,
-                                         &layersId, &compositorOptions, &maxTouchPoints);
+    // NOTE: BrowserFrameOpenWindowPromise is the same type as
+    // CreateWindowPromise, and this code depends on that fact.
+    windowCreated =
+      newChild->SendBrowserFrameOpenWindow(aTabOpener, renderFrame, NS_ConvertUTF8toUTF16(url),
+                                           name, NS_ConvertUTF8toUTF16(features));
   } else {
     nsAutoCString baseURIString;
     float fullZoom;
     rv = GetWindowParamsFromParent(aParent, baseURIString, &fullZoom);
     if (NS_WARN_IF(NS_FAILED(rv))) {
       return rv;
     }
 
-    if (!SendCreateWindow(aTabOpener, newChild, renderFrame,
-                          aChromeFlags, aCalledFromJS, aPositionSpecified,
-                          aSizeSpecified,
-                          features,
-                          baseURIString,
-                          fullZoom,
-                          &rv,
-                          aWindowIsNew,
-                          &frameScripts,
-                          &urlToLoad,
-                          &textureFactoryIdentifier,
-                          &layersId,
-                          &compositorOptions,
-                          &maxTouchPoints,
-                          &dimensionInfo)) {
-      PRenderFrameChild::Send__delete__(renderFrame);
-      return NS_ERROR_NOT_AVAILABLE;
+    windowCreated =
+      SendCreateWindow(aTabOpener, newChild, renderFrame,
+                       aChromeFlags, aCalledFromJS, aPositionSpecified,
+                       aSizeSpecified,
+                       features,
+                       baseURIString,
+                       fullZoom);
+  }
+
+  // Await the promise being resolved. When the promise is resolved, we'll set
+  // the `ready` local variable, which will cause us to exit our nested event
+  // loop.
+  //
+  // NOTE: We need to run this callback on the StableStateEventTarget because we
+  // need to resolve our runnable and exit from the nested event loop before
+  // processing any events which were sent after the reply to CreateWindow was
+  // sent.
+  bool ready = false;
+  windowCreated->Then(nsContentUtils::GetStableStateEventTarget(), __func__,
+                      [&] (const CreatedWindowInfo& info) {
+                        MOZ_RELEASE_ASSERT(NS_IsMainThread(),
+                                           "windowCreated->Then must run on the main thread");
+                        rv = info.rv();
+                        *aWindowIsNew = info.windowOpened();
+                        frameScripts = info.frameScripts();
+                        urlToLoad = info.urlToLoad();
+                        textureFactoryIdentifier = info.textureFactoryIdentifier();
+                        layersId = info.layersId();
+                        compositorOptions = info.compositorOptions();
+                        maxTouchPoints = info.maxTouchPoints();
+                        dimensionInfo = info.dimensions();
+                        ready = true;
+                      },
+                      [&] (const CreateWindowPromise::RejectValueType aReason) {
+                        MOZ_RELEASE_ASSERT(NS_IsMainThread(),
+                                           "windowCreated->Then must run on the main thread");
+                        NS_WARNING("windowCreated promise rejected");
+                        rv = NS_ERROR_NOT_AVAILABLE;
+                        ready = true;
+                      });
+
+  // =======================
+  // Begin Nested Event Loop
+  // =======================
+
+  // We have to wait for a response from either SendCreateWindow or
+  // SendBrowserFrameOpenWindow with information we're going to need to return
+  // from this function, So we spin a nested event loop until they get back to
+  // us.
+
+  // Prevent the docshell from becoming active while the nested event loop is
+  // spinning.
+  newChild->AddPendingDocShellBlocker();
+  auto removePendingDocShellBlocker = MakeScopeExit([&] {
+    if (newChild) {
+      newChild->RemovePendingDocShellBlocker();
     }
-
-    if (NS_FAILED(rv)) {
-      PRenderFrameChild::Send__delete__(renderFrame);
-      return rv;
-    }
+  });
+
+  // Suspend our window if we have one to make sure we don't re-enter it.
+  if (parentTopInnerWindow) {
+    parentTopInnerWindow->Suspend();
   }
+
+  {
+    AutoNoJSAPI nojsapi;
+
+    // Spin the event loop until we get a response. Callers of this function
+    // already have to guard against an inner event loop spinning in the
+    // non-e10s case because of the need to spin one to create a new chrome
+    // window.
+    SpinEventLoopUntil([&] () { return ready; });
+    MOZ_RELEASE_ASSERT(ready,
+                       "We are on the main thread, so we should not exit this "
+                       "loop without ready being true.");
+  }
+
+  if (parentTopInnerWindow) {
+    parentTopInnerWindow->Resume();
+  }
+
+  // =====================
+  // End Nested Event Loop
+  // =====================
+
+  // Handle the error which we got back from the parent process, if we got
+  // one.
+  if (NS_FAILED(rv)) {
+    PRenderFrameChild::Send__delete__(renderFrame);
+    return rv;
+  }
+
   if (!*aWindowIsNew) {
     PRenderFrameChild::Send__delete__(renderFrame);
     return NS_ERROR_ABORT;
   }
 
   if (layersId == 0) { // if renderFrame is invalid.
     PRenderFrameChild::Send__delete__(renderFrame);
     renderFrame = nullptr;
--- a/dom/ipc/ContentParent.cpp
+++ b/dom/ipc/ContentParent.cpp
@@ -1534,28 +1534,27 @@ ContentParent::RemoveFromList()
     if (sJSPluginContentParents) {
       sJSPluginContentParents->Remove(mJSPluginID);
       if (!sJSPluginContentParents->Count()) {
         delete sJSPluginContentParents;
         sJSPluginContentParents = nullptr;
       }
     }
   } else if (sBrowserContentParents) {
-    nsTArray<ContentParent*>* contentParents =
-      sBrowserContentParents->Get(mRemoteType);
-    if (contentParents) {
+    if (auto entry = sBrowserContentParents->Lookup(mRemoteType)) {
+      nsTArray<ContentParent*>* contentParents = entry.Data();
       contentParents->RemoveElement(this);
       if (contentParents->IsEmpty()) {
-        sBrowserContentParents->Remove(mRemoteType);
-        if (sBrowserContentParents->IsEmpty()) {
-          delete sBrowserContentParents;
-          sBrowserContentParents = nullptr;
-        }
+        entry.Remove();
       }
     }
+    if (sBrowserContentParents->IsEmpty()) {
+      delete sBrowserContentParents;
+      sBrowserContentParents = nullptr;
+    }
   }
 
   if (sPrivateContent) {
     sPrivateContent->RemoveElement(this);
     if (!sPrivateContent->Length()) {
       delete sPrivateContent;
       sPrivateContent = nullptr;
     }
@@ -4660,83 +4659,88 @@ ContentParent::RecvCreateWindow(PBrowser
                                 PRenderFrameParent* aRenderFrame,
                                 const uint32_t& aChromeFlags,
                                 const bool& aCalledFromJS,
                                 const bool& aPositionSpecified,
                                 const bool& aSizeSpecified,
                                 const nsCString& aFeatures,
                                 const nsCString& aBaseURI,
                                 const float& aFullZoom,
-                                nsresult* aResult,
-                                bool* aWindowIsNew,
-                                InfallibleTArray<FrameScriptInfo>* aFrameScripts,
-                                nsCString* aURLToLoad,
-                                TextureFactoryIdentifier* aTextureFactoryIdentifier,
-                                uint64_t* aLayersId,
-                                CompositorOptions* aCompositorOptions,
-                                uint32_t* aMaxTouchPoints,
-                                DimensionInfo* aDimensions)
-{
+                                CreateWindowResolver&& aResolve)
+{
+  nsresult rv = NS_OK;
+  CreatedWindowInfo cwi;
+
   // We always expect to open a new window here. If we don't, it's an error.
-  *aWindowIsNew = true;
-  *aResult = NS_OK;
+  cwi.windowOpened() = true;
+  cwi.layersId() = 0;
+  cwi.maxTouchPoints() = 0;
+
+  // Make sure to resolve the resolver when this function exits, even if we
+  // failed to generate a valid response.
+  auto resolveOnExit = MakeScopeExit([&] {
+    // Copy over the nsresult, and then resolve.
+    cwi.rv() = rv;
+    aResolve(cwi);
+  });
 
   TabParent* newTab = TabParent::GetFrom(aNewTab);
   MOZ_ASSERT(newTab);
 
   auto destroyNewTabOnError = MakeScopeExit([&] {
-    if (!*aWindowIsNew || NS_FAILED(*aResult)) {
+    // We always expect to open a new window here. If we don't, it's an error.
+    if (!cwi.windowOpened() || NS_FAILED(rv)) {
       if (newTab) {
         newTab->Destroy();
       }
     }
   });
 
   // Content has requested that we open this new content window, so
   // we must have an opener.
   newTab->SetHasContentOpener(true);
 
-  TabParent::AutoUseNewTab aunt(newTab, aURLToLoad);
+  TabParent::AutoUseNewTab aunt(newTab, &cwi.urlToLoad());
   const uint64_t nextTabParentId = ++sNextTabParentId;
   sNextTabParents.Put(nextTabParentId, newTab);
 
   nsCOMPtr<nsITabParent> newRemoteTab;
   mozilla::ipc::IPCResult ipcResult =
     CommonCreateWindow(aThisTab, /* aSetOpener = */ true, aChromeFlags,
                        aCalledFromJS, aPositionSpecified, aSizeSpecified,
                        nullptr, aFeatures, aBaseURI, aFullZoom,
-                       nextTabParentId, NullString(), *aResult,
-                       newRemoteTab, aWindowIsNew);
+                       nextTabParentId, NullString(), rv,
+                       newRemoteTab, &cwi.windowOpened());
   if (!ipcResult) {
     return ipcResult;
   }
 
-  if (NS_WARN_IF(NS_FAILED(*aResult))) {
+  if (NS_WARN_IF(NS_FAILED(rv))) {
     return IPC_OK();
   }
 
   if (sNextTabParents.GetAndRemove(nextTabParentId).valueOr(nullptr)) {
-    *aWindowIsNew = false;
+    cwi.windowOpened() = false;
   }
   MOZ_ASSERT(TabParent::GetFrom(newRemoteTab) == newTab);
 
-  newTab->SwapFrameScriptsFrom(*aFrameScripts);
+  newTab->SwapFrameScriptsFrom(cwi.frameScripts());
 
   RenderFrameParent* rfp = static_cast<RenderFrameParent*>(aRenderFrame);
   if (!newTab->SetRenderFrame(rfp) ||
-      !newTab->GetRenderFrameInfo(aTextureFactoryIdentifier, aLayersId)) {
-    *aResult = NS_ERROR_FAILURE;
-  }
-  *aCompositorOptions = rfp->GetCompositorOptions();
+      !newTab->GetRenderFrameInfo(&cwi.textureFactoryIdentifier(), &cwi.layersId())) {
+    rv = NS_ERROR_FAILURE;
+  }
+  cwi.compositorOptions() = rfp->GetCompositorOptions();
 
   nsCOMPtr<nsIWidget> widget = newTab->GetWidget();
-  *aMaxTouchPoints = widget ? widget->GetMaxTouchPoints() : 0;
-
-  // NOTE: widget must be set for this to return a meaningful value.
-  *aDimensions = widget ? newTab->GetDimensionInfo() : DimensionInfo();
+  if (widget) {
+    cwi.maxTouchPoints() = widget->GetMaxTouchPoints();
+    cwi.dimensions() = newTab->GetDimensionInfo();
+  }
 
   return IPC_OK();
 }
 
 mozilla::ipc::IPCResult
 ContentParent::RecvCreateWindowInDifferentProcess(
   PBrowserParent* aThisTab,
   const uint32_t& aChromeFlags,
@@ -5085,31 +5089,26 @@ ContentParent::RecvGetFilesRequest(const
 
   mGetFilesPendingRequests.Put(aUUID, helper);
   return IPC_OK();
 }
 
 mozilla::ipc::IPCResult
 ContentParent::RecvDeleteGetFilesRequest(const nsID& aUUID)
 {
-  GetFilesHelper* helper = mGetFilesPendingRequests.GetWeak(aUUID);
-  if (helper) {
-    mGetFilesPendingRequests.Remove(aUUID);
-  }
-
+  mGetFilesPendingRequests.Remove(aUUID);
   return IPC_OK();
 }
 
 void
 ContentParent::SendGetFilesResponseAndForget(const nsID& aUUID,
                                              const GetFilesResponseResult& aResult)
 {
-  GetFilesHelper* helper = mGetFilesPendingRequests.GetWeak(aUUID);
-  if (helper) {
-    mGetFilesPendingRequests.Remove(aUUID);
+  if (auto entry = mGetFilesPendingRequests.Lookup(aUUID)) {
+    entry.Remove();
     Unused << SendGetFilesResponse(aUUID, aResult);
   }
 }
 
 void
 ContentParent::ForceTabPaint(TabParent* aTabParent, uint64_t aLayerObserverEpoch)
 {
   if (!mHangMonitorActor) {
--- a/dom/ipc/ContentParent.h
+++ b/dom/ipc/ContentParent.h
@@ -531,25 +531,17 @@ public:
                    layout::PRenderFrameParent* aRenderFrame,
                    const uint32_t& aChromeFlags,
                    const bool& aCalledFromJS,
                    const bool& aPositionSpecified,
                    const bool& aSizeSpecified,
                    const nsCString& aFeatures,
                    const nsCString& aBaseURI,
                    const float& aFullZoom,
-                   nsresult* aResult,
-                   bool* aWindowIsNew,
-                   InfallibleTArray<FrameScriptInfo>* aFrameScripts,
-                   nsCString* aURLToLoad,
-                   layers::TextureFactoryIdentifier* aTextureFactoryIdentifier,
-                   uint64_t* aLayersId,
-                   mozilla::layers::CompositorOptions* aCompositorOptions,
-                   uint32_t* aMaxTouchPoints,
-                   DimensionInfo* aDimensions) override;
+                   CreateWindowResolver&& aResolve) override;
 
   virtual mozilla::ipc::IPCResult RecvCreateWindowInDifferentProcess(
     PBrowserParent* aThisTab,
     const uint32_t& aChromeFlags,
     const bool& aCalledFromJS,
     const bool& aPositionSpecified,
     const bool& aSizeSpecified,
     const URIParams& aURIToLoad,
--- a/dom/ipc/DOMTypes.ipdlh
+++ b/dom/ipc/DOMTypes.ipdlh
@@ -17,16 +17,18 @@ using struct mozilla::SerializedStructur
 using LayoutDeviceIntRect from "Units.h";
 using DesktopIntRect from "Units.h";
 using DesktopToLayoutDeviceScale from "Units.h";
 using CSSToLayoutDeviceScale from "Units.h";
 using CSSRect from "Units.h";
 using CSSSize from "Units.h";
 using mozilla::LayoutDeviceIntPoint from "Units.h";
 using mozilla::dom::ScreenOrientationInternal from "mozilla/dom/ScreenOrientation.h";
+using struct mozilla::layers::TextureFactoryIdentifier from "mozilla/layers/CompositorTypes.h";
+using mozilla::layers::CompositorOptions from "mozilla/layers/CompositorOptions.h";
 
 namespace mozilla {
 namespace dom {
 
 struct MessagePortIdentifier
 {
   nsID uuid;
   nsID destinationUuid;
@@ -91,10 +93,32 @@ struct DimensionInfo
 {
   CSSRect rect;
   CSSSize size;
   ScreenOrientationInternal orientation;
   LayoutDeviceIntPoint clientOffset;
   LayoutDeviceIntPoint chromeDisp;
 };
 
+struct FrameScriptInfo
+{
+  nsString url;
+  bool runInGlobalScope;
+};
+
+/**
+ * The information required to complete a window creation request.
+ */
+struct CreatedWindowInfo
+{
+  nsresult rv;
+  bool windowOpened;
+  FrameScriptInfo[] frameScripts;
+  nsCString urlToLoad;
+  TextureFactoryIdentifier textureFactoryIdentifier;
+  uint64_t layersId;
+  CompositorOptions compositorOptions;
+  uint32_t maxTouchPoints;
+  DimensionInfo dimensions;
+};
+
 } // namespace dom
 } // namespace mozilla
--- a/dom/ipc/PBrowser.ipdl
+++ b/dom/ipc/PBrowser.ipdl
@@ -145,21 +145,16 @@ parent:
      * in e10s mode. This is always initiated from the child in response
      * to windowed plugin creation.
      */
     sync PPluginWidget();
 
     async PPaymentRequest();
 
     /**
-     * Return native data of root widget
-     */
-    nested(inside_cpow) sync GetWidgetNativeData() returns (WindowsHandle value);
-
-    /**
      * Sends an NS_NATIVE_CHILD_OF_SHAREABLE_WINDOW to be adopted by the
      * widget's shareable window on the chrome side. Only used on Windows.
      */
     async SetNativeChildOfShareableWindow(uintptr_t childWindow);
 
     /**
      * When content moves focus from a native plugin window that's a child
      * of the native browser window we need to move native focus to the
@@ -454,23 +449,19 @@ parent:
      * process), then calls BrowserFrameOpenWindow on it.
      *
      * The parent process gets a chance to accept or reject the window.open
      * call, and windowOpened is set to true if we ended up going through with
      * the window.open.
      *
      * @param opener the PBrowser whose content called window.open.
      */
-    sync BrowserFrameOpenWindow(PBrowser opener, PRenderFrame renderFrame,
-                                nsString aURL, nsString aName, nsString aFeatures)
-      returns (bool windowOpened,
-               TextureFactoryIdentifier textureFactoryIdentifier,
-               uint64_t layersId,
-               CompositorOptions compositorOptions,
-               uint32_t maxTouchPoints);
+    async BrowserFrameOpenWindow(PBrowser opener, PRenderFrame renderFrame,
+                                 nsString aURL, nsString aName, nsString aFeatures)
+        returns (CreatedWindowInfo window);
 
     /**
      * Tells the containing widget whether the given input block results in a
      * swipe. Should be called in response to a WidgetWheelEvent that has
      * mFlags.mCanTriggerSwipe set on it.
      */
     async RespondStartSwipeEvent(uint64_t aInputBlockId, bool aStartSwipe);
 
@@ -893,16 +884,22 @@ child:
     async SetWindowName(nsString aName);
 
     /**
      * Tell the TabChild what OriginAttributes it should inherit from. This must
      * be called before the first non-blank document is loaded in the TabChild.
      */
     async SetOriginAttributes(OriginAttributes aOriginAttributes);
 
+    /**
+     * Pass the current handle for the current native widget to the content
+     * process, so it can be used by PuppetWidget.
+     */
+    async SetWidgetNativeData(WindowsHandle aHandle);
+
 /*
  * FIXME: write protocol!
 
 state LIVE:
     send LoadURL goto LIVE;
 //etc.
     send Destroy goto DYING;
 
--- a/dom/ipc/PContent.ipdl
+++ b/dom/ipc/PContent.ipdl
@@ -172,24 +172,16 @@ struct DomainPolicyClone
 {
     bool        active;
     URIParams[] blacklist;
     URIParams[] whitelist;
     URIParams[] superBlacklist;
     URIParams[] superWhitelist;
 };
 
-
-
-struct FrameScriptInfo
-{
-    nsString url;
-    bool runInGlobalScope;
-};
-
 struct AndroidSystemInfo
 {
     nsString device;
     nsString manufacturer;
     nsString release_version;
     nsString hardware;
     uint32_t sdk_version;
     bool     isTablet;
@@ -985,35 +977,27 @@ parent:
     async ShutdownProfile(nsCString aProfile);
 
     /**
      * Request graphics initialization information from the parent.
      */
     sync GetGraphicsDeviceInitData()
         returns (ContentDeviceData aData);
 
-    sync CreateWindow(nullable PBrowser aThisTab,
-                      PBrowser aNewTab,
-                      PRenderFrame aRenderFrame,
-                      uint32_t aChromeFlags,
-                      bool aCalledFromJS,
-                      bool aPositionSpecified,
-                      bool aSizeSpecified,
-                      nsCString aFeatures,
-                      nsCString aBaseURI,
-                      float aFullZoom)
-      returns (nsresult rv,
-               bool windowOpened,
-               FrameScriptInfo[] frameScripts,
-               nsCString urlToLoad,
-               TextureFactoryIdentifier textureFactoryIdentifier,
-               uint64_t layersId,
-               CompositorOptions compositorOptions,
-               uint32_t maxTouchPoints,
-               DimensionInfo dimensions);
+    async CreateWindow(nullable PBrowser aThisTab,
+                       PBrowser aNewTab,
+                       PRenderFrame aRenderFrame,
+                       uint32_t aChromeFlags,
+                       bool aCalledFromJS,
+                       bool aPositionSpecified,
+                       bool aSizeSpecified,
+                       nsCString aFeatures,
+                       nsCString aBaseURI,
+                       float aFullZoom)
+        returns (CreatedWindowInfo window);
 
     async CreateWindowInDifferentProcess(
       PBrowser aThisTab,
       uint32_t aChromeFlags,
       bool aCalledFromJS,
       bool aPositionSpecified,
       bool aSizeSpecified,
       URIParams aURIToLoad,
--- a/dom/ipc/TabChild.cpp
+++ b/dom/ipc/TabChild.cpp
@@ -402,16 +402,21 @@ TabChild::TabChild(nsIContentChild* aMan
   , mSkipKeyPress(false)
   , mLayerObserverEpoch(0)
 #if defined(XP_WIN) && defined(ACCESSIBILITY)
   , mNativeWindowHandle(0)
 #endif
 #if defined(ACCESSIBILITY)
   , mTopLevelDocAccessibleChild(nullptr)
 #endif
+  , mPendingDocShellIsActive(false)
+  , mPendingDocShellPreserveLayers(false)
+  , mPendingDocShellReceivedMessage(false)
+  , mPendingDocShellBlockers(0)
+  , mWidgetNativeData(0)
 {
   nsWeakPtr weakPtrThis(do_GetWeakReference(static_cast<nsITabChild*>(this)));  // for capture by the lambda
   mSetAllowedTouchBehaviorCallback = [weakPtrThis](uint64_t aInputBlockId,
                                                    const nsTArray<TouchBehaviorFlags>& aFlags)
   {
     if (nsCOMPtr<nsITabChild> tabChild = do_QueryReferent(weakPtrThis)) {
       static_cast<TabChild*>(tabChild.get())->SetAllowedTouchBehavior(aInputBlockId, aFlags);
     }
@@ -2357,30 +2362,36 @@ TabChild::RecvDestroy()
   // Bounce through the event loop once to allow any delayed teardown runnables
   // that were just generated to have a chance to run.
   nsCOMPtr<nsIRunnable> deleteRunnable = new DelayedDeleteRunnable(this);
   MOZ_ALWAYS_SUCCEEDS(NS_DispatchToCurrentThread(deleteRunnable));
 
   return IPC_OK();
 }
 
-mozilla::ipc::IPCResult
-TabChild::RecvSetDocShellIsActive(const bool& aIsActive,
-                                  const bool& aPreserveLayers,
-                                  const uint64_t& aLayerObserverEpoch)
+void
+TabChild::AddPendingDocShellBlocker()
+{
+  mPendingDocShellBlockers++;
+}
+
+void
+TabChild::RemovePendingDocShellBlocker()
 {
-  // Since SetDocShellIsActive requests come in from both the hang monitor
-  // channel and the PContent channel, we have an ordering problem. This code
-  // ensures that we respect the order in which the requests were made and
-  // ignore stale requests.
-  if (mLayerObserverEpoch >= aLayerObserverEpoch) {
-    return IPC_OK();
+  mPendingDocShellBlockers--;
+  if (!mPendingDocShellBlockers && mPendingDocShellReceivedMessage) {
+    mPendingDocShellReceivedMessage = false;
+    InternalSetDocShellIsActive(mPendingDocShellIsActive,
+                                mPendingDocShellPreserveLayers);
   }
-  mLayerObserverEpoch = aLayerObserverEpoch;
-
+}
+
+void
+TabChild::InternalSetDocShellIsActive(bool aIsActive, bool aPreserveLayers)
+{
   auto clearForcePaint = MakeScopeExit([&] {
     // We might force a paint, or we might already have painted and this is a
     // no-op. In either case, once we exit this scope, we need to alert the
     // ProcessHangMonitor that we've finished responding to what might have
     // been a request to force paint. This is so that the BackgroundHangMonitor
     // for force painting can be made to wait again.
     if (aIsActive) {
       ProcessHangMonitor::ClearForcePaint();
@@ -2396,32 +2407,32 @@ TabChild::RecvSetDocShellIsActive(const 
     MOZ_ASSERT(mPuppetWidget->GetLayerManager());
     MOZ_ASSERT(mPuppetWidget->GetLayerManager()->GetBackendType() == LayersBackend::LAYERS_CLIENT
             || mPuppetWidget->GetLayerManager()->GetBackendType() == LayersBackend::LAYERS_WR
             || (gfxPlatform::IsHeadless() && mPuppetWidget->GetLayerManager()->GetBackendType() == LayersBackend::LAYERS_BASIC));
 
     // We send the current layer observer epoch to the compositor so that
     // TabParent knows whether a layer update notification corresponds to the
     // latest SetDocShellIsActive request that was made.
-    mPuppetWidget->GetLayerManager()->SetLayerObserverEpoch(aLayerObserverEpoch);
+    mPuppetWidget->GetLayerManager()->SetLayerObserverEpoch(mLayerObserverEpoch);
   }
 
   // docshell is consider prerendered only if not active yet
   mIsPrerendered &= !aIsActive;
   nsCOMPtr<nsIDocShell> docShell = do_GetInterface(WebNavigation());
   if (docShell) {
     bool wasActive;
     docShell->GetIsActive(&wasActive);
     if (aIsActive && wasActive) {
       // This request is a no-op. In this case, we still want a MozLayerTreeReady
       // notification to fire in the parent (so that it knows that the child has
       // updated its epoch). ForcePaintNoOp does that.
       if (IPCOpen()) {
-        Unused << SendForcePaintNoOp(aLayerObserverEpoch);
-        return IPC_OK();
+        Unused << SendForcePaintNoOp(mLayerObserverEpoch);
+        return;
       }
     }
 
     docShell->SetIsActive(aIsActive);
   }
 
   if (aIsActive) {
     MakeVisible();
@@ -2452,17 +2463,43 @@ TabChild::RecvSetDocShellIsActive(const 
                            nsIPresShell::PAINT_LAYERS);
         }
       }
       APZCCallbackHelper::SuppressDisplayport(false, presShell);
     }
   } else if (!aPreserveLayers) {
     MakeHidden();
   }
-
+}
+
+mozilla::ipc::IPCResult
+TabChild::RecvSetDocShellIsActive(const bool& aIsActive,
+                                  const bool& aPreserveLayers,
+                                  const uint64_t& aLayerObserverEpoch)
+{
+  // Since requests to change the active docshell come in from both the hang
+  // monitor channel and the PContent channel, we have an ordering problem. This
+  // code ensures that we respect the order in which the requests were made and
+  // ignore stale requests.
+  if (mLayerObserverEpoch >= aLayerObserverEpoch) {
+    return IPC_OK();
+  }
+  mLayerObserverEpoch = aLayerObserverEpoch;
+
+  // If we're currently waiting for window opening to complete, we need to hold
+  // off on setting the docshell active. We queue up the values we're receiving
+  // in the mWindowOpenDocShellActiveStatus.
+  if (mPendingDocShellBlockers > 0) {
+    mPendingDocShellReceivedMessage = true;
+    mPendingDocShellIsActive = aIsActive;
+    mPendingDocShellPreserveLayers = aPreserveLayers;
+    return IPC_OK();
+  }
+
+  InternalSetDocShellIsActive(aIsActive, aPreserveLayers);
   return IPC_OK();
 }
 
 mozilla::ipc::IPCResult
 TabChild::RecvNavigateByKey(const bool& aForward, const bool& aForDocumentNavigation)
 {
   nsIFocusManager* fm = nsFocusManager::GetFocusManager();
   if (fm) {
@@ -3170,16 +3207,23 @@ mozilla::ipc::IPCResult
 TabChild::RecvSetOriginAttributes(const OriginAttributes& aOriginAttributes)
 {
   nsCOMPtr<nsIDocShell> docShell = do_GetInterface(WebNavigation());
   nsDocShell::Cast(docShell)->SetOriginAttributes(aOriginAttributes);
 
   return IPC_OK();
 }
 
+mozilla::ipc::IPCResult
+TabChild::RecvSetWidgetNativeData(const WindowsHandle& aWidgetNativeData)
+{
+  mWidgetNativeData = aWidgetNativeData;
+  return IPC_OK();
+}
+
 mozilla::plugins::PPluginWidgetChild*
 TabChild::AllocPPluginWidgetChild()
 {
 #ifdef XP_WIN
   return new mozilla::plugins::PluginWidgetChild();
 #else
   MOZ_ASSERT_UNREACHABLE();
   return nullptr;
--- a/dom/ipc/TabChild.h
+++ b/dom/ipc/TabChild.h
@@ -689,16 +689,25 @@ public:
   }
 
   PDocAccessibleChild* GetTopLevelDocAccessibleChild()
   {
     return mTopLevelDocAccessibleChild;
   }
 #endif
 
+  void AddPendingDocShellBlocker();
+  void RemovePendingDocShellBlocker();
+
+  // The HANDLE object for the widget this TabChild in.
+  WindowsHandle WidgetNativeData()
+  {
+    return mWidgetNativeData;
+  }
+
 protected:
   virtual ~TabChild();
 
   virtual PRenderFrameChild* AllocPRenderFrameChild() override;
 
   virtual bool DeallocPRenderFrameChild(PRenderFrameChild* aFrame) override;
 
   virtual mozilla::ipc::IPCResult RecvDestroy() override;
@@ -730,16 +739,18 @@ protected:
   virtual mozilla::ipc::IPCResult RecvNotifyPartialSHistoryDeactive() override;
 
   virtual mozilla::ipc::IPCResult RecvAwaitLargeAlloc() override;
 
   virtual mozilla::ipc::IPCResult RecvSetWindowName(const nsString& aName) override;
 
   virtual mozilla::ipc::IPCResult RecvSetOriginAttributes(const OriginAttributes& aOriginAttributes) override;
 
+  virtual mozilla::ipc::IPCResult RecvSetWidgetNativeData(const WindowsHandle& aWidgetNativeData) override;
+
 private:
   void HandleDoubleTap(const CSSPoint& aPoint, const Modifiers& aModifiers,
                        const ScrollableLayerGuid& aGuid);
 
   // Notify others that our TabContext has been updated.
   //
   // You should call this after calling TabContext::SetTabContext().  We also
   // call this during Init().
@@ -785,16 +796,19 @@ private:
                                bool* aIsNextWheelEvent);
 
   void MaybeDispatchCoalescedWheelEvent();
 
   void DispatchWheelEvent(const WidgetWheelEvent& aEvent,
                           const ScrollableLayerGuid& aGuid,
                           const uint64_t& aInputBlockId);
 
+  void InternalSetDocShellIsActive(bool aIsActive,
+                                   bool aPreserveLayers);
+
   class DelayedDeleteRunnable;
 
   TextureFactoryIdentifier mTextureFactoryIdentifier;
   nsCOMPtr<nsIWebNavigation> mWebNav;
   RefPtr<mozilla::dom::TabGroup> mTabGroup;
   RefPtr<PuppetWidget> mPuppetWidget;
   nsCOMPtr<nsIURI> mLastURI;
   RenderFrameChild* mRemoteFrame;
@@ -865,15 +879,22 @@ private:
   // The handle associated with the native window that contains this tab
   uintptr_t mNativeWindowHandle;
 #endif // defined(XP_WIN)
 
 #if defined(ACCESSIBILITY)
   PDocAccessibleChild* mTopLevelDocAccessibleChild;
 #endif
 
+  bool mPendingDocShellIsActive;
+  bool mPendingDocShellPreserveLayers;
+  bool mPendingDocShellReceivedMessage;
+  uint32_t mPendingDocShellBlockers;
+
+  WindowsHandle mWidgetNativeData;
+
   DISALLOW_EVIL_CONSTRUCTORS(TabChild);
 };
 
 } // namespace dom
 } // namespace mozilla
 
 #endif // mozilla_dom_TabChild_h
--- a/dom/ipc/TabParent.cpp
+++ b/dom/ipc/TabParent.cpp
@@ -287,16 +287,27 @@ TabParent::SetOwnerElement(Element* aEle
         }
       }
     }
   }
 #endif
 
   AddWindowListeners();
   TryCacheDPIAndScale();
+
+  // Try to send down WidgetNativeData, now that this TabParent is associated
+  // with a widget.
+  nsCOMPtr<nsIWidget> widget = GetTopLevelWidget();
+  if (widget) {
+    WindowsHandle widgetNativeData = reinterpret_cast<WindowsHandle>(
+      widget->GetNativeData(NS_NATIVE_SHAREABLE_WINDOW));
+    if (widgetNativeData) {
+      Unused << SendSetWidgetNativeData(widgetNativeData);
+    }
+  }
 }
 
 void
 TabParent::AddWindowListeners()
 {
   if (mFrameElement && mFrameElement->OwnerDoc()) {
     if (nsCOMPtr<nsPIDOMWindowOuter> window = mFrameElement->OwnerDoc()->GetWindow()) {
       nsCOMPtr<EventTarget> eventTarget = window->GetTopWindowRoot();
@@ -2311,28 +2322,16 @@ TabParent::GetTopLevelWidget()
       vm->GetRootWidget(getter_AddRefs(widget));
       return widget.forget();
     }
   }
   return nullptr;
 }
 
 mozilla::ipc::IPCResult
-TabParent::RecvGetWidgetNativeData(WindowsHandle* aValue)
-{
-  *aValue = 0;
-  nsCOMPtr<nsIWidget> widget = GetTopLevelWidget();
-  if (widget) {
-    *aValue = reinterpret_cast<WindowsHandle>(
-      widget->GetNativeData(NS_NATIVE_SHAREABLE_WINDOW));
-  }
-  return IPC_OK();
-}
-
-mozilla::ipc::IPCResult
 TabParent::RecvSetNativeChildOfShareableWindow(const uintptr_t& aChildWindow)
 {
 #if defined(XP_WIN)
   nsCOMPtr<nsIWidget> widget = GetTopLevelWidget();
   if (widget) {
     // Note that this call will probably cause a sync native message to the
     // process that owns the child window.
     widget->SetNativeData(NS_NATIVE_CHILD_OF_SHAREABLE_WINDOW, aChildWindow);
@@ -2575,31 +2574,41 @@ TabParent::ApzAwareEventRoutingToChild(S
 }
 
 mozilla::ipc::IPCResult
 TabParent::RecvBrowserFrameOpenWindow(PBrowserParent* aOpener,
                                       PRenderFrameParent* aRenderFrame,
                                       const nsString& aURL,
                                       const nsString& aName,
                                       const nsString& aFeatures,
-                                      bool* aOutWindowOpened,
-                                      TextureFactoryIdentifier* aTextureFactoryIdentifier,
-                                      uint64_t* aLayersId,
-                                      CompositorOptions* aCompositorOptions,
-                                      uint32_t* aMaxTouchPoints)
+                                      BrowserFrameOpenWindowResolver&& aResolve)
 {
+  CreatedWindowInfo cwi;
+  cwi.rv() = NS_OK;
+  cwi.layersId() = 0;
+  cwi.maxTouchPoints() = 0;
+
   BrowserElementParent::OpenWindowResult opened =
     BrowserElementParent::OpenWindowOOP(TabParent::GetFrom(aOpener),
                                         this, aRenderFrame, aURL, aName, aFeatures,
-                                        aTextureFactoryIdentifier, aLayersId);
-  *aCompositorOptions = static_cast<RenderFrameParent*>(aRenderFrame)->GetCompositorOptions();
-  *aOutWindowOpened = (opened == BrowserElementParent::OPEN_WINDOW_ADDED);
+                                        &cwi.textureFactoryIdentifier(),
+                                        &cwi.layersId());
+  cwi.compositorOptions() =
+    static_cast<RenderFrameParent*>(aRenderFrame)->GetCompositorOptions();
+  cwi.windowOpened() = (opened == BrowserElementParent::OPEN_WINDOW_ADDED);
   nsCOMPtr<nsIWidget> widget = GetWidget();
-  *aMaxTouchPoints = widget ? widget->GetMaxTouchPoints() : 0;
-  if (!*aOutWindowOpened) {
+  if (widget) {
+    cwi.maxTouchPoints() = widget->GetMaxTouchPoints();
+    cwi.dimensions() = GetDimensionInfo();
+  }
+
+  // Resolve the request with the information we collected.
+  aResolve(cwi);
+
+  if (!cwi.windowOpened()) {
     Destroy();
   }
   return IPC_OK();
 }
 
 mozilla::ipc::IPCResult
 TabParent::RecvRespondStartSwipeEvent(const uint64_t& aInputBlockId,
                                       const bool& aStartSwipe)
--- a/dom/ipc/TabParent.h
+++ b/dom/ipc/TabParent.h
@@ -160,26 +160,23 @@ public:
   virtual mozilla::ipc::IPCResult RecvReplyKeyEvent(const WidgetKeyboardEvent& aEvent) override;
 
   virtual mozilla::ipc::IPCResult
   RecvAccessKeyNotHandled(const WidgetKeyboardEvent& aEvent) override;
 
   virtual mozilla::ipc::IPCResult
   RecvSetHasBeforeUnload(const bool& aHasBeforeUnload) override;
 
-  virtual mozilla::ipc::IPCResult RecvBrowserFrameOpenWindow(PBrowserParent* aOpener,
-                                                             PRenderFrameParent* aRenderFrame,
-                                                             const nsString& aURL,
-                                                             const nsString& aName,
-                                                             const nsString& aFeatures,
-                                                             bool* aOutWindowOpened,
-                                                             TextureFactoryIdentifier* aTextureFactoryIdentifier,
-                                                             uint64_t* aLayersId,
-                                                             CompositorOptions* aCompositorOptions,
-                                                             uint32_t* aMaxTouchPoints) override;
+  virtual mozilla::ipc::IPCResult
+  RecvBrowserFrameOpenWindow(PBrowserParent* aOpener,
+                             PRenderFrameParent* aRenderFrame,
+                             const nsString& aURL,
+                             const nsString& aName,
+                             const nsString& aFeatures,
+                             BrowserFrameOpenWindowResolver&& aResolve) override;
 
   virtual mozilla::ipc::IPCResult
   RecvSyncMessage(const nsString& aMessage,
                   const ClonedMessageData& aData,
                   InfallibleTArray<CpowEntry>&& aCpows,
                   const IPC::Principal& aPrincipal,
                   nsTArray<ipc::StructuredCloneData>* aRetVal) override;
 
@@ -303,18 +300,16 @@ public:
   virtual mozilla::ipc::IPCResult RecvHideTooltip() override;
 
   virtual mozilla::ipc::IPCResult RecvGetDPI(float* aValue) override;
 
   virtual mozilla::ipc::IPCResult RecvGetDefaultScale(double* aValue) override;
 
   virtual mozilla::ipc::IPCResult RecvGetWidgetRounding(int32_t* aValue) override;
 
-  virtual mozilla::ipc::IPCResult RecvGetWidgetNativeData(WindowsHandle* aValue) override;
-
   virtual mozilla::ipc::IPCResult RecvSetNativeChildOfShareableWindow(const uintptr_t& childWindow) override;
 
   virtual mozilla::ipc::IPCResult RecvDispatchFocusToTopLevelWindow() override;
 
   virtual mozilla::ipc::IPCResult RecvRespondStartSwipeEvent(const uint64_t& aInputBlockId,
                                                              const bool& aStartSwipe) override;
 
   virtual mozilla::ipc::IPCResult
--- a/dom/push/test/mochitest.ini
+++ b/dom/push/test/mochitest.ini
@@ -7,23 +7,23 @@ support-files =
   lifetime_worker.js
   test_utils.js
   mockpushserviceparent.js
   error_worker.js
 
 [test_has_permissions.html]
 [test_permissions.html]
 [test_register.html]
-skip-if = os == "win" && !debug # Bug 1373346
+skip-if = os == "win" # Bug 1373346
 [test_register_key.html]
 [test_multiple_register.html]
 [test_multiple_register_during_service_activation.html]
 [test_unregister.html]
 [test_multiple_register_different_scope.html]
 [test_subscription_change.html]
-skip-if = os == "win" && !debug # Bug 1373346
+skip-if = os == "win" # Bug 1373346
 [test_data.html]
-skip-if = os == "win" && !debug # Bug 1373346
+skip-if = os == "win" # Bug 1373346
 [test_try_registering_offline_disabled.html]
-skip-if = os == "win" && !debug # Bug 1373346
+skip-if = os == "win" # Bug 1373346
 [test_serviceworker_lifetime.html]
-skip-if = os == "win" && !debug # Bug 1373346
+skip-if = os == "win" # Bug 1373346
 [test_error_reporting.html]
--- a/dom/xbl/moz.build
+++ b/dom/xbl/moz.build
@@ -8,16 +8,17 @@ with Files("**"):
     BUG_COMPONENT = ("Core", "XBL")
 
 DIRS += ['builtin']
 
 EXPORTS += [
     'nsBindingManager.h',
     'nsXBLBinding.h',
     'nsXBLService.h',
+    'nsXBLWindowKeyHandler.h',
 ]
 
 EXPORTS.mozilla.dom += [
     'XBLChildrenElement.h',
 ]
 
 UNIFIED_SOURCES += [
     'nsBindingManager.cpp',
--- a/dom/xbl/nsXBLEventHandler.h
+++ b/dom/xbl/nsXBLEventHandler.h
@@ -12,19 +12,17 @@
 #include "nsIDOMEventListener.h"
 #include "nsTArray.h"
 
 class nsIAtom;
 class nsIDOMKeyEvent;
 class nsXBLPrototypeHandler;
 
 namespace mozilla {
-namespace dom {
 struct IgnoreModifierState;
-} // namespace dom
 } // namespace mozilla
 
 class nsXBLEventHandler : public nsIDOMEventListener
 {
 public:
   explicit nsXBLEventHandler(nsXBLPrototypeHandler* aHandler);
 
   NS_DECL_ISUPPORTS
@@ -50,17 +48,17 @@ public:
   virtual ~nsXBLMouseEventHandler();
 
 private:
   bool EventMatched(nsIDOMEvent* aEvent) override;
 };
 
 class nsXBLKeyEventHandler : public nsIDOMEventListener
 {
-  typedef mozilla::dom::IgnoreModifierState IgnoreModifierState;
+  typedef mozilla::IgnoreModifierState IgnoreModifierState;
 
 public:
   nsXBLKeyEventHandler(nsIAtom* aEventType, uint8_t aPhase, uint8_t aType);
 
   NS_DECL_ISUPPORTS
 
   NS_DECL_NSIDOMEVENTLISTENER
 
--- a/dom/xbl/nsXBLPrototypeHandler.cpp
+++ b/dom/xbl/nsXBLPrototypeHandler.cpp
@@ -7,16 +7,17 @@
 #include "mozilla/ArrayUtils.h"
 
 #include "nsCOMPtr.h"
 #include "nsQueryObject.h"
 #include "nsXBLPrototypeHandler.h"
 #include "nsXBLPrototypeBinding.h"
 #include "nsContentUtils.h"
 #include "nsGlobalWindow.h"
+#include "nsGlobalWindowCommands.h"
 #include "nsIContent.h"
 #include "nsIAtom.h"
 #include "nsIDOMKeyEvent.h"
 #include "nsIDOMMouseEvent.h"
 #include "nsNameSpaceManager.h"
 #include "nsIDocument.h"
 #include "nsIController.h"
 #include "nsIControllers.h"
@@ -41,23 +42,26 @@
 #include "nsUnicharUtils.h"
 #include "nsCRT.h"
 #include "nsXBLEventHandler.h"
 #include "nsXBLSerialize.h"
 #include "nsJSUtils.h"
 #include "mozilla/BasicEvents.h"
 #include "mozilla/JSEventHandler.h"
 #include "mozilla/Preferences.h"
+#include "mozilla/TextEvents.h"
 #include "mozilla/dom/Element.h"
 #include "mozilla/dom/EventHandlerBinding.h"
 #include "mozilla/dom/ScriptSettings.h"
+#include "mozilla/layers/KeyboardMap.h"
 #include "xpcpublic.h"
 
 using namespace mozilla;
 using namespace mozilla::dom;
+using namespace mozilla::layers;
 
 uint32_t nsXBLPrototypeHandler::gRefCnt = 0;
 
 int32_t nsXBLPrototypeHandler::kMenuAccessKey = -1;
 
 const int32_t nsXBLPrototypeHandler::cShift = (1<<0);
 const int32_t nsXBLPrototypeHandler::cAlt = (1<<1);
 const int32_t nsXBLPrototypeHandler::cControl = (1<<2);
@@ -131,16 +135,76 @@ nsXBLPrototypeHandler::~nsXBLPrototypeHa
   } else if (mHandlerText) {
     free(mHandlerText);
   }
 
   // We own the next handler in the chain, so delete it now.
   NS_CONTENT_DELETE_LIST_MEMBER(nsXBLPrototypeHandler, this, mNextHandler);
 }
 
+bool
+nsXBLPrototypeHandler::TryConvertToKeyboardShortcut(
+                          KeyboardShortcut* aOut) const
+{
+  // Convert the event type
+  KeyboardInput::KeyboardEventType eventType;
+
+  if (mEventName == nsGkAtoms::keydown) {
+    eventType = KeyboardInput::KEY_DOWN;
+  } else if (mEventName == nsGkAtoms::keypress) {
+    eventType = KeyboardInput::KEY_PRESS;
+  } else if (mEventName == nsGkAtoms::keyup) {
+    eventType = KeyboardInput::KEY_UP;
+  } else {
+    return false;
+  }
+
+  // Convert the modifiers
+  Modifiers modifiersMask = GetModifiers();
+  Modifiers modifiers = GetModifiersMask();
+
+  // Mask away any bits that won't be compared
+  modifiers &= modifiersMask;
+
+  // Convert the keyCode or charCode
+  uint32_t keyCode;
+  uint32_t charCode;
+
+  if (mMisc) {
+    keyCode = 0;
+    charCode = static_cast<uint32_t>(mDetail);
+  } else {
+    keyCode = static_cast<uint32_t>(mDetail);
+    charCode = 0;
+  }
+
+  NS_LossyConvertUTF16toASCII commandText(mHandlerText);
+  KeyboardScrollAction action;
+  if (!nsGlobalWindowCommands::FindScrollCommand(commandText.get(), &action)) {
+    // This action doesn't represent a scroll so we need to create a dispatch
+    // to content keyboard shortcut so APZ handles this command correctly
+    *aOut = KeyboardShortcut(eventType,
+                             keyCode,
+                             charCode,
+                             modifiers,
+                             modifiersMask);
+    return true;
+  }
+
+  // This prototype is a command which represents a scroll action, so create
+  // a keyboard shortcut to handle it
+  *aOut = KeyboardShortcut(eventType,
+                           keyCode,
+                           charCode,
+                           modifiers,
+                           modifiersMask,
+                           action);
+  return true;
+}
+
 already_AddRefed<nsIContent>
 nsXBLPrototypeHandler::GetHandlerElement()
 {
   if (mType & NS_HANDLER_TYPE_XUL) {
     nsCOMPtr<nsIContent> element = do_QueryReferent(mHandlerElement);
     return element.forget();
   }
 
@@ -534,16 +598,64 @@ nsXBLPrototypeHandler::DispatchXULKeyCom
   keyEvent->GetMetaKey(&isMeta);
 
   nsContentUtils::DispatchXULCommand(handlerElement, true,
                                      nullptr, nullptr,
                                      isControl, isAlt, isShift, isMeta);
   return NS_OK;
 }
 
+Modifiers
+nsXBLPrototypeHandler::GetModifiers() const
+{
+  Modifiers modifiers = 0;
+
+  if (mKeyMask & cMeta) {
+    modifiers |= MODIFIER_META;
+  }
+  if (mKeyMask & cOS) {
+    modifiers |= MODIFIER_OS;
+  }
+  if (mKeyMask & cShift) {
+    modifiers |= MODIFIER_SHIFT;
+  }
+  if (mKeyMask & cAlt) {
+    modifiers |= MODIFIER_ALT;
+  }
+  if (mKeyMask & cControl) {
+    modifiers |= MODIFIER_CONTROL;
+  }
+
+  return modifiers;
+}
+
+Modifiers
+nsXBLPrototypeHandler::GetModifiersMask() const
+{
+  Modifiers modifiersMask = 0;
+
+  if (mKeyMask & cMetaMask) {
+    modifiersMask |= MODIFIER_META;
+  }
+  if (mKeyMask & cOSMask) {
+    modifiersMask |= MODIFIER_OS;
+  }
+  if (mKeyMask & cShiftMask) {
+    modifiersMask |= MODIFIER_SHIFT;
+  }
+  if (mKeyMask & cAltMask) {
+    modifiersMask |= MODIFIER_ALT;
+  }
+  if (mKeyMask & cControlMask) {
+    modifiersMask |= MODIFIER_CONTROL;
+  }
+
+  return modifiersMask;
+}
+
 already_AddRefed<nsIAtom>
 nsXBLPrototypeHandler::GetEventName()
 {
   nsCOMPtr<nsIAtom> eventName = mEventName;
   return eventName.forget();
 }
 
 already_AddRefed<nsIController>
--- a/dom/xbl/nsXBLPrototypeHandler.h
+++ b/dom/xbl/nsXBLPrototypeHandler.h
@@ -2,16 +2,17 @@
 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #ifndef nsXBLPrototypeHandler_h__
 #define nsXBLPrototypeHandler_h__
 
+#include "mozilla/EventForwards.h"
 #include "nsIAtom.h"
 #include "nsString.h"
 #include "nsCOMPtr.h"
 #include "nsIController.h"
 #include "nsAutoPtr.h"
 #include "nsXBLEventHandler.h"
 #include "nsIWeakReference.h"
 #include "nsCycleCollectionParticipant.h"
@@ -22,58 +23,48 @@ class nsIContent;
 class nsIDOMUIEvent;
 class nsIDOMKeyEvent;
 class nsIDOMMouseEvent;
 class nsIObjectInputStream;
 class nsIObjectOutputStream;
 class nsXBLPrototypeBinding;
 
 namespace mozilla {
+
+struct IgnoreModifierState;
+
 namespace dom {
 class AutoJSAPI;
 class EventTarget;
 } // namespace dom
+
+namespace layers {
+class KeyboardShortcut;
+} // namespace layers
+
 } // namespace mozilla
 
 #define NS_HANDLER_TYPE_XBL_JS              (1 << 0)
 #define NS_HANDLER_TYPE_XBL_COMMAND         (1 << 1)
 #define NS_HANDLER_TYPE_XUL                 (1 << 2)
 #define NS_HANDLER_HAS_ALLOW_UNTRUSTED_ATTR (1 << 4)
 #define NS_HANDLER_ALLOW_UNTRUSTED          (1 << 5)
 #define NS_HANDLER_TYPE_SYSTEM              (1 << 6)
 #define NS_HANDLER_TYPE_PREVENTDEFAULT      (1 << 7)
 
 // XXX Use nsIDOMEvent:: codes?
 #define NS_PHASE_CAPTURING          1
 #define NS_PHASE_TARGET             2
 #define NS_PHASE_BUBBLING           3
 
-namespace mozilla {
-namespace dom {
-
-struct IgnoreModifierState
-{
-  // When mShift is true, Shift key state will be ignored.
-  bool mShift;
-  // When mOS is true, OS key state will be ignored.
-  bool mOS;
-
-  IgnoreModifierState()
-    : mShift(false)
-    , mOS(false)
-  {
-  }
-};
-
-} // namespace dom
-} // namespace mozilla
-
 class nsXBLPrototypeHandler
 {
-  typedef mozilla::dom::IgnoreModifierState IgnoreModifierState;
+  typedef mozilla::IgnoreModifierState IgnoreModifierState;
+  typedef mozilla::layers::KeyboardShortcut KeyboardShortcut;
+  typedef mozilla::Modifiers Modifiers;
 
 public:
   // This constructor is used by XBL handlers (both the JS and command shorthand variety)
   nsXBLPrototypeHandler(const char16_t* aEvent, const char16_t* aPhase,
                         const char16_t* aAction, const char16_t* aCommand,
                         const char16_t* aKeyCode, const char16_t* aCharCode,
                         const char16_t* aModifiers, const char16_t* aButton,
                         const char16_t* aClickCount, const char16_t* aGroup,
@@ -85,16 +76,27 @@ public:
   // This constructor is used only by XUL key handlers (e.g., <key>)
   explicit nsXBLPrototypeHandler(nsIContent* aKeyElement, bool aReserved);
 
   // 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
+   * represent scroll commands.
+   *
+   * @param aOut the converted KeyboardShortcut, must be non null
+   * @return whether the handler was converted into a KeyboardShortcut
+   */
+  bool TryConvertToKeyboardShortcut(
+          KeyboardShortcut* aOut) const;
+
   bool EventTypeEquals(nsIAtom* aEventType) const
   {
     return mEventName == aEventType;
   }
 
   // if aCharCode is not zero, it is used instead of the charCode of aKeyEvent.
   bool KeyEventMatched(nsIDOMKeyEvent* aKeyEvent,
                        uint32_t aCharCode,
@@ -181,16 +183,20 @@ protected:
   void ReportKeyConflict(const char16_t* aKey, const char16_t* aModifiers, nsIContent* aElement, const char *aMessageName);
   void GetEventType(nsAString& type);
   bool ModifiersMatchMask(nsIDOMUIEvent* aEvent,
                           const IgnoreModifierState& aIgnoreModifierState);
   nsresult DispatchXBLCommand(mozilla::dom::EventTarget* aTarget, nsIDOMEvent* aEvent);
   nsresult DispatchXULKeyCommand(nsIDOMEvent* aEvent);
   nsresult EnsureEventHandler(mozilla::dom::AutoJSAPI& jsapi, nsIAtom* aName,
                               JS::MutableHandle<JSObject*> aHandler);
+
+  Modifiers GetModifiers() const;
+  Modifiers GetModifiersMask() const;
+
   static int32_t KeyToMask(int32_t key);
   static int32_t AccelKeyMask();
 
   static int32_t kMenuAccessKey;
   static void InitAccessKeys();
 
   static const int32_t cShift;
   static const int32_t cAlt;
--- a/dom/xbl/nsXBLWindowKeyHandler.cpp
+++ b/dom/xbl/nsXBLWindowKeyHandler.cpp
@@ -20,27 +20,31 @@
 #include "nsNetUtil.h"
 #include "nsContentUtils.h"
 #include "nsXBLPrototypeBinding.h"
 #include "nsPIDOMWindow.h"
 #include "nsIDocShell.h"
 #include "nsIPresShell.h"
 #include "mozilla/EventListenerManager.h"
 #include "mozilla/EventStateManager.h"
+#include "mozilla/Move.h"
 #include "nsISelectionController.h"
 #include "mozilla/Preferences.h"
+#include "mozilla/StaticPtr.h"
 #include "mozilla/TextEvents.h"
 #include "mozilla/dom/Element.h"
 #include "mozilla/dom/Event.h"
+#include "mozilla/layers/KeyboardMap.h"
 #include "nsIEditor.h"
 #include "nsIHTMLEditor.h"
 #include "nsIDOMDocument.h"
 
 using namespace mozilla;
 using namespace mozilla::dom;
+using namespace mozilla::layers;
 
 class nsXBLSpecialDocInfo : public nsIObserver
 {
 public:
   RefPtr<nsXBLDocumentInfo> mHTMLBindings;
   RefPtr<nsXBLDocumentInfo> mUserHTMLBindings;
 
   static const char sHTMLBindingStr[];
@@ -141,19 +145,28 @@ nsXBLSpecialDocInfo::GetAllHandlers(cons
     GetHandlers(mUserHTMLBindings, type, aUserHandler);
   }
   if (mHTMLBindings) {
     GetHandlers(mHTMLBindings, nsDependentCString(aType), aHandler);
   }
 }
 
 // Init statics
-nsXBLSpecialDocInfo* nsXBLWindowKeyHandler::sXBLSpecialDocInfo = nullptr;
+static StaticRefPtr<nsXBLSpecialDocInfo> sXBLSpecialDocInfo;
 uint32_t nsXBLWindowKeyHandler::sRefCnt = 0;
 
+/* static */ void
+nsXBLWindowKeyHandler::EnsureSpecialDocInfo()
+{
+  if (!sXBLSpecialDocInfo) {
+    sXBLSpecialDocInfo = new nsXBLSpecialDocInfo();
+  }
+  sXBLSpecialDocInfo->LoadDocInfo();
+}
+
 nsXBLWindowKeyHandler::nsXBLWindowKeyHandler(nsIDOMElement* aElement,
                                              EventTarget* aTarget)
   : mTarget(aTarget),
     mHandler(nullptr),
     mUserHandler(nullptr)
 {
   mWeakPtrForElement = do_GetWeakReference(aElement);
   ++sRefCnt;
@@ -162,17 +175,17 @@ nsXBLWindowKeyHandler::nsXBLWindowKeyHan
 nsXBLWindowKeyHandler::~nsXBLWindowKeyHandler()
 {
   // If mWeakPtrForElement is non-null, we created a prototype handler.
   if (mWeakPtrForElement)
     delete mHandler;
 
   --sRefCnt;
   if (!sRefCnt) {
-    NS_IF_RELEASE(sXBLSpecialDocInfo);
+    sXBLSpecialDocInfo = nullptr;
   }
 }
 
 NS_IMPL_ISUPPORTS(nsXBLWindowKeyHandler,
                   nsIDOMEventListener)
 
 static void
 BuildHandlerChain(nsIContent* aContent, nsXBLPrototypeHandler** aResult)
@@ -223,21 +236,17 @@ nsXBLWindowKeyHandler::EnsureHandlers()
   if (el) {
     // We are actually a XUL <keyset>.
     if (mHandler)
       return NS_OK;
 
     nsCOMPtr<nsIContent> content(do_QueryInterface(el));
     BuildHandlerChain(content, &mHandler);
   } else { // We are an XBL file of handlers.
-    if (!sXBLSpecialDocInfo) {
-      sXBLSpecialDocInfo = new nsXBLSpecialDocInfo();
-      NS_ADDREF(sXBLSpecialDocInfo);
-    }
-    sXBLSpecialDocInfo->LoadDocInfo();
+    EnsureSpecialDocInfo();
 
     // Now determine which handlers we should be using.
     if (IsHTMLEditableFieldFocused()) {
       sXBLSpecialDocInfo->GetAllHandlers("editor", &mHandler, &mUserHandler);
     }
     else {
       sXBLSpecialDocInfo->GetAllHandlers("browser", &mHandler, &mUserHandler);
     }
@@ -392,16 +401,51 @@ nsXBLWindowKeyHandler::RemoveKeyboardEve
   aEventListenerManager->RemoveEventListenerByType(
                            this, NS_LITERAL_STRING("mozkeydownonplugin"),
                            TrustedEventsAtSystemGroupBubble());
   aEventListenerManager->RemoveEventListenerByType(
                            this, NS_LITERAL_STRING("mozkeyuponplugin"),
                            TrustedEventsAtSystemGroupBubble());
 }
 
+/* static */ KeyboardMap
+nsXBLWindowKeyHandler::CollectKeyboardShortcuts()
+{
+  // Load the XBL handlers
+  EnsureSpecialDocInfo();
+
+  nsXBLPrototypeHandler* handlers = nullptr;
+  nsXBLPrototypeHandler* userHandlers = nullptr;
+  sXBLSpecialDocInfo->GetAllHandlers("browser", &handlers, &userHandlers);
+
+  // Convert the handlers into keyboard shortcuts, using an AutoTArray with
+  // the maximum amount of shortcuts used on any platform to minimize allocations
+  AutoTArray<KeyboardShortcut, 46> shortcuts;
+
+  for (nsXBLPrototypeHandler* handler = handlers;
+       handler;
+       handler = handler->GetNextHandler()) {
+    KeyboardShortcut shortcut;
+    if (handler->TryConvertToKeyboardShortcut(&shortcut)) {
+      shortcuts.AppendElement(shortcut);
+    }
+  }
+
+  for (nsXBLPrototypeHandler* handler = userHandlers;
+       handler;
+       handler = handler->GetNextHandler()) {
+    KeyboardShortcut shortcut;
+    if (handler->TryConvertToKeyboardShortcut(&shortcut)) {
+      shortcuts.AppendElement(shortcut);
+    }
+  }
+
+  return KeyboardMap(mozilla::Move(shortcuts));
+}
+
 nsIAtom*
 nsXBLWindowKeyHandler::ConvertEventToDOMEventType(
                          const WidgetKeyboardEvent& aWidgetKeyboardEvent) const
 {
   if (aWidgetKeyboardEvent.IsKeyDownOrKeyDownOnPlugin()) {
     return nsGkAtoms::keydown;
   }
   if (aWidgetKeyboardEvent.IsKeyUpOrKeyUpOnPlugin()) {
--- a/dom/xbl/nsXBLWindowKeyHandler.h
+++ b/dom/xbl/nsXBLWindowKeyHandler.h
@@ -3,47 +3,50 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #ifndef nsXBLWindowKeyHandler_h__
 #define nsXBLWindowKeyHandler_h__
 
 #include "mozilla/EventForwards.h"
+#include "mozilla/layers/KeyboardMap.h"
 #include "nsWeakPtr.h"
 #include "nsIDOMEventListener.h"
 
 class nsIAtom;
 class nsIDOMElement;
 class nsIDOMKeyEvent;
-class nsXBLSpecialDocInfo;
 class nsXBLPrototypeHandler;
 
 namespace mozilla {
 class EventListenerManager;
+struct IgnoreModifierState;
 namespace dom {
 class Element;
 class EventTarget;
-struct IgnoreModifierState;
 } // namespace dom
 } // namespace mozilla
 
 class nsXBLWindowKeyHandler : public nsIDOMEventListener
 {
-  typedef mozilla::dom::IgnoreModifierState IgnoreModifierState;
   typedef mozilla::EventListenerManager EventListenerManager;
+  typedef mozilla::IgnoreModifierState IgnoreModifierState;
+  typedef mozilla::layers::KeyboardMap KeyboardMap;
 
 public:
   nsXBLWindowKeyHandler(nsIDOMElement* aElement, mozilla::dom::EventTarget* aTarget);
 
   void InstallKeyboardEventListenersTo(
          EventListenerManager* aEventListenerManager);
   void RemoveKeyboardEventListenersFrom(
          EventListenerManager* aEventListenerManager);
 
+  static KeyboardMap CollectKeyboardShortcuts();
+
   NS_DECL_ISUPPORTS
   NS_DECL_NSIDOMEVENTLISTENER
 
 protected:
   virtual ~nsXBLWindowKeyHandler();
 
   nsresult WalkHandlers(nsIDOMKeyEvent* aKeyEvent, nsIAtom* aEventType);
 
@@ -75,16 +78,19 @@ protected:
                           bool* aOutReservedForChrome = nullptr);
 
   // Returns event type for matching between aWidgetKeyboardEvent and
   // shortcut key handlers.  This is used for calling WalkHandlers(),
   // WalkHandlersInternal() and WalkHandlersAndExecute().
   nsIAtom* ConvertEventToDOMEventType(
              const mozilla::WidgetKeyboardEvent& aWidgetKeyboardEvent) const;
 
+  // lazily load the 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,
@@ -115,18 +121,17 @@ protected:
   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
   nsXBLPrototypeHandler* mUserHandler; // user-specific bindings
 
-  // holds document info about bindings
-  static nsXBLSpecialDocInfo* sXBLSpecialDocInfo;
+  // holds reference count to document info about bindings
   static uint32_t sRefCnt;
 };
 
 already_AddRefed<nsXBLWindowKeyHandler>
 NS_NewXBLWindowKeyHandler(nsIDOMElement* aElement,
                           mozilla::dom::EventTarget* aTarget);
 
 #endif
--- a/gfx/layers/CompositorTypes.h
+++ b/gfx/layers/CompositorTypes.h
@@ -192,16 +192,28 @@ struct TextureFactoryIdentifier
     , mCompositorUseANGLE(aCompositorUseANGLE)
     , mSupportsTextureBlitting(aSupportsTextureBlitting)
     , mSupportsPartialUploads(aSupportsPartialUploads)
     , mSupportsComponentAlpha(aSupportsComponentAlpha)
     , mSupportsBackdropCopyForComponentAlpha(true)
     , mUsingAdvancedLayers(false)
     , mSyncHandle(aSyncHandle)
   {}
+
+  bool operator==(const TextureFactoryIdentifier& aOther) const {
+    return
+      mParentBackend == aOther.mParentBackend &&
+      mParentProcessType == aOther.mParentProcessType &&
+      mMaxTextureSize == aOther.mMaxTextureSize &&
+      mCompositorUseANGLE == aOther.mCompositorUseANGLE &&
+      mSupportsTextureBlitting == aOther.mSupportsTextureBlitting &&
+      mSupportsPartialUploads == aOther.mSupportsPartialUploads &&
+      mSupportsComponentAlpha == aOther.mSupportsComponentAlpha &&
+      mSyncHandle == aOther.mSyncHandle;
+  }
 };
 
 /**
  * Information required by the compositor from the content-side for creating or
  * using compositables and textures.
  * XXX - TextureInfo is a bad name: this information is useful for the compositable,
  * not the Texture. And ith new Textures, only the compositable type is really
  * useful. This may (should) be removed in the near future.
--- a/gfx/layers/FrameMetrics.h
+++ b/gfx/layers/FrameMetrics.h
@@ -685,16 +685,22 @@ struct ScrollSnapInfo {
     return mScrollSnapTypeX == aOther.mScrollSnapTypeX &&
            mScrollSnapTypeY == aOther.mScrollSnapTypeY &&
            mScrollSnapIntervalX == aOther.mScrollSnapIntervalX &&
            mScrollSnapIntervalY == aOther.mScrollSnapIntervalY &&
            mScrollSnapDestination == aOther.mScrollSnapDestination &&
            mScrollSnapCoordinates == aOther.mScrollSnapCoordinates;
   }
 
+  bool HasScrollSnapping() const
+  {
+    return mScrollSnapTypeY != NS_STYLE_SCROLL_SNAP_TYPE_NONE ||
+           mScrollSnapTypeX != NS_STYLE_SCROLL_SNAP_TYPE_NONE;
+  }
+
   // The scroll frame's scroll-snap-type.
   // One of NS_STYLE_SCROLL_SNAP_{NONE, MANDATORY, PROXIMITY}.
   uint8_t mScrollSnapTypeX;
   uint8_t mScrollSnapTypeY;
 
   // The intervals derived from the scroll frame's scroll-snap-points.
   Maybe<nscoord> mScrollSnapIntervalX;
   Maybe<nscoord> mScrollSnapIntervalY;
--- a/gfx/layers/Layers.cpp
+++ b/gfx/layers/Layers.cpp
@@ -2098,16 +2098,25 @@ Layer::IsBackfaceHidden()
 UniquePtr<LayerUserData>
 Layer::RemoveUserData(void* aKey)
 {
   UniquePtr<LayerUserData> d(static_cast<LayerUserData*>(mUserData.Remove(static_cast<gfx::UserDataKey*>(aKey))));
   return d;
 }
 
 void
+Layer::SetManager(LayerManager* aManager, HostLayer* aSelf)
+{
+  // No one should be calling this for weird reasons.
+  MOZ_ASSERT(aSelf);
+  MOZ_ASSERT(aSelf->GetLayer() == this);
+  mManager = aManager;
+}
+
+void
 PaintedLayer::PrintInfo(std::stringstream& aStream, const char* aPrefix)
 {
   Layer::PrintInfo(aStream, aPrefix);
   nsIntRegion validRegion = GetValidRegion();
   if (!validRegion.IsEmpty()) {
     AppendToString(aStream, validRegion, " [valid=", "]");
   }
 }
--- a/gfx/layers/Layers.h
+++ b/gfx/layers/Layers.h
@@ -92,16 +92,17 @@ class CompositorAnimations;
 class CompositorBridgeChild;
 class TextLayer;
 class CanvasLayer;
 class BorderLayer;
 class ReadbackLayer;
 class ReadbackProcessor;
 class RefLayer;
 class HostLayer;
+class FocusTarget;
 class KnowsCompositor;
 class ShadowableLayer;
 class ShadowLayerForwarder;
 class LayerManagerComposite;
 class SpecificLayerAttributes;
 class TransactionIdAllocator;
 class Compositor;
 class FrameUniformityData;
@@ -595,16 +596,21 @@ public:
   virtual void ClearCachedResources(Layer* aSubtree = nullptr) {}
 
   /**
    * Flag the next paint as the first for a document.
    */
   virtual void SetIsFirstPaint() {}
 
   /**
+   * Set the current focus target to be sent with the next paint.
+   */
+  virtual void SetFocusTarget(const FocusTarget& aFocusTarget) {}
+
+  /**
    * Make sure that the previous transaction has been entirely
    * completed.
    *
    * Note: This may sychronously wait on a remote compositor
    * to complete rendering.
    */
   virtual void FlushRendering() { }
 
@@ -839,16 +845,21 @@ public:
 
   /**
    * Returns the LayerManager this Layer belongs to. Note that the layer
    * manager might be in a destroyed state, at which point it's only
    * valid to set/get user data from it.
    */
   LayerManager* Manager() { return mManager; }
 
+  /**
+   * This should only be called when changing layer managers from HostLayers.
+   */
+  void SetManager(LayerManager* aManager, HostLayer* aSelf);
+
   enum {
     /**
      * If this is set, the caller is promising that by the end of this
      * transaction the entire visible region (as specified by
      * SetVisibleRegion) will be filled with opaque content.
      */
     CONTENT_OPAQUE = 0x01,
     /**
--- a/gfx/layers/apz/public/IAPZCTreeManager.cpp
+++ b/gfx/layers/apz/public/IAPZCTreeManager.cpp
@@ -6,16 +6,17 @@
 
 #include "mozilla/layers/IAPZCTreeManager.h"
 
 #include "gfxPrefs.h"                       // for gfxPrefs
 #include "InputData.h"                      // for InputData, etc
 #include "mozilla/EventStateManager.h"      // for WheelPrefs
 #include "mozilla/layers/APZThreadUtils.h"  // for AssertOnCompositorThread, etc
 #include "mozilla/MouseEvents.h"            // for WidgetMouseEvent
+#include "mozilla/TextEvents.h"             // for WidgetKeyboardEvent
 #include "mozilla/TouchEvents.h"            // for WidgetTouchEvent
 
 namespace mozilla {
 namespace layers {
 
 static bool
 WillHandleMouseEvent(const WidgetMouseEventBase& aEvent)
 {
@@ -64,21 +65,22 @@ IAPZCTreeManager::ReceiveInputEvent(
         MouseInput input(mouseEvent);
         input.mOrigin = ScreenPoint(mouseEvent.mRefPoint.x, mouseEvent.mRefPoint.y);
 
         nsEventStatus status = ReceiveInputEvent(input, aOutTargetGuid, aOutInputBlockId);
 
         mouseEvent.mRefPoint.x = input.mOrigin.x;
         mouseEvent.mRefPoint.y = input.mOrigin.y;
         mouseEvent.mFlags.mHandledByAPZ = input.mHandledByAPZ;
+        mouseEvent.mFocusSequenceNumber = input.mFocusSequenceNumber;
         return status;
 
       }
 
-      TransformEventRefPoint(&mouseEvent.mRefPoint, aOutTargetGuid);
+      ProcessUnhandledEvent(&mouseEvent.mRefPoint, aOutTargetGuid, &aEvent.mFocusSequenceNumber);
       return nsEventStatus_eIgnore;
     }
     case eTouchEventClass: {
 
       WidgetTouchEvent& touchEvent = *aEvent.AsTouchEvent();
       MultiTouchInput touchInput(touchEvent);
       nsEventStatus result = ReceiveInputEvent(touchInput, aOutTargetGuid, aOutInputBlockId);
       // touchInput was modified in-place to possibly remove some
@@ -87,16 +89,17 @@ IAPZCTreeManager::ReceiveInputEvent(
       // back into the WidgetInputEvent.
       touchEvent.mTouches.Clear();
       touchEvent.mTouches.SetCapacity(touchInput.mTouches.Length());
       for (size_t i = 0; i < touchInput.mTouches.Length(); i++) {
         *touchEvent.mTouches.AppendElement() =
           touchInput.mTouches[i].ToNewDOMTouch();
       }
       touchEvent.mFlags.mHandledByAPZ = touchInput.mHandledByAPZ;
+      touchEvent.mFocusSequenceNumber = touchInput.mFocusSequenceNumber;
       return result;
 
     }
     case eWheelEventClass: {
       WidgetWheelEvent& wheelEvent = *aEvent.AsWheelEvent();
 
       if (WillHandleWheelEvent(&wheelEvent)) {
 
@@ -128,28 +131,40 @@ IAPZCTreeManager::ReceiveInputEvent(
         EventStateManager::GetUserPrefsForWheelEvent(&wheelEvent,
           &input.mUserDeltaMultiplierX,
           &input.mUserDeltaMultiplierY);
 
         nsEventStatus status = ReceiveInputEvent(input, aOutTargetGuid, aOutInputBlockId);
         wheelEvent.mRefPoint.x = input.mOrigin.x;
         wheelEvent.mRefPoint.y = input.mOrigin.y;
         wheelEvent.mFlags.mHandledByAPZ = input.mHandledByAPZ;
+        wheelEvent.mFocusSequenceNumber = input.mFocusSequenceNumber;
         return status;
       }
 
       UpdateWheelTransaction(aEvent.mRefPoint, aEvent.mMessage);
-      TransformEventRefPoint(&aEvent.mRefPoint, aOutTargetGuid);
+      ProcessUnhandledEvent(&aEvent.mRefPoint, aOutTargetGuid, &aEvent.mFocusSequenceNumber);
       return nsEventStatus_eIgnore;
 
     }
+    case eKeyboardEventClass: {
+      WidgetKeyboardEvent& keyboardEvent = *aEvent.AsKeyboardEvent();
+
+      KeyboardInput input(keyboardEvent);
+
+      nsEventStatus status = ReceiveInputEvent(input, aOutTargetGuid, aOutInputBlockId);
+
+      keyboardEvent.mFlags.mHandledByAPZ = input.mHandledByAPZ;
+      keyboardEvent.mFocusSequenceNumber = input.mFocusSequenceNumber;
+      return status;
+    }
     default: {
 
       UpdateWheelTransaction(aEvent.mRefPoint, aEvent.mMessage);
-      TransformEventRefPoint(&aEvent.mRefPoint, aOutTargetGuid);
+      ProcessUnhandledEvent(&aEvent.mRefPoint, aOutTargetGuid, &aEvent.mFocusSequenceNumber);
       return nsEventStatus_eIgnore;
 
     }
   }
 
   MOZ_ASSERT_UNREACHABLE("Invalid WidgetInputEvent type.");
   return nsEventStatus_eConsumeNoDefault;
 }
--- a/gfx/layers/apz/public/IAPZCTreeManager.h
+++ b/gfx/layers/apz/public/IAPZCTreeManager.h
@@ -16,16 +16,18 @@
 #include "nsISupportsImpl.h"            // for MOZ_COUNT_CTOR, etc
 #include "Units.h"                      // for CSSPoint, CSSRect, etc
 
 namespace mozilla {
 class InputData;
 
 namespace layers {
 
+class KeyboardMap;
+
 enum AllowedTouchBehavior {
   NONE =               0,
   VERTICAL_PAN =       1 << 0,
   HORIZONTAL_PAN =     1 << 1,
   PINCH_ZOOM =         1 << 2,
   DOUBLE_TAP_ZOOM =    1 << 3,
   UNKNOWN =            1 << 4
 };
@@ -100,16 +102,21 @@ public:
    * See documentation for other ReceiveInputEvent above.
    */
   nsEventStatus ReceiveInputEvent(
       WidgetInputEvent& aEvent,
       ScrollableLayerGuid* aOutTargetGuid,
       uint64_t* aOutInputBlockId);
 
   /**
+   * Set the keyboard shortcuts to use for translating keyboard events.
+   */
+  virtual void SetKeyboardMap(const KeyboardMap& aKeyboardMap) = 0;
+
+  /**
    * Kicks an animation to zoom to a rect. This may be either a zoom out or zoom
    * in. The actual animation is done on the compositor thread after being set
    * up. |aRect| must be given in CSS pixels, relative to the document.
    * |aFlags| is a combination of the ZoomToRectBehavior enum values.
    */
   virtual void ZoomToRect(
       const ScrollableLayerGuid& aGuid,
       const CSSRect& aRect,
@@ -199,19 +206,20 @@ public:
   // Even if this returns false, all wheel events in APZ-aware widgets must
   // be sent through APZ so they are transformed correctly for TabParent.
   static bool WillHandleWheelEvent(WidgetWheelEvent* aEvent);
 
 protected:
 
   // Methods to help process WidgetInputEvents (or manage conversion to/from InputData)
 
-  virtual void TransformEventRefPoint(
+  virtual void ProcessUnhandledEvent(
       LayoutDeviceIntPoint* aRefPoint,
-      ScrollableLayerGuid* aOutTargetGuid) = 0;
+      ScrollableLayerGuid* aOutTargetGuid,
+      uint64_t* aOutFocusSequenceNumber) = 0;
 
   virtual void UpdateWheelTransaction(
       LayoutDeviceIntPoint aRefPoint,
       EventMessage aEventMessage) = 0;
 
   // Discourage destruction outside of decref
 
   virtual ~IAPZCTreeManager() { }
--- a/gfx/layers/apz/src/APZCTreeManager.cpp
+++ b/gfx/layers/apz/src/APZCTreeManager.cpp
@@ -1,14 +1,15 @@
 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include <stack>
+#include <unordered_set>
 #include "APZCTreeManager.h"
 #include "AsyncPanZoomController.h"
 #include "Compositor.h"                 // for Compositor
 #include "DragTracker.h"                // for DragTracker
 #include "gfxPrefs.h"                   // for gfxPrefs
 #include "HitTestingTreeNode.h"         // for HitTestingTreeNode
 #include "InputBlockState.h"            // for InputBlockState
 #include "InputData.h"                  // for InputData, etc
@@ -16,16 +17,17 @@
 #include "mozilla/dom/Touch.h"          // for Touch
 #include "mozilla/gfx/GPUParent.h"      // for GPUParent
 #include "mozilla/gfx/Logging.h"        // for gfx::TreeLog
 #include "mozilla/gfx/Point.h"          // for Point
 #include "mozilla/layers/APZThreadUtils.h"  // for AssertOnCompositorThread, etc
 #include "mozilla/layers/AsyncCompositionManager.h" // for ViewTransform
 #include "mozilla/layers/AsyncDragMetrics.h" // for AsyncDragMetrics
 #include "mozilla/layers/CompositorBridgeParent.h" // for CompositorBridgeParent, etc
+#include "mozilla/layers/FocusState.h"  // for FocusState
 #include "mozilla/layers/LayerMetricsWrapper.h"
 #include "mozilla/layers/WebRenderScrollDataWrapper.h"
 #include "mozilla/MouseEvents.h"
 #include "mozilla/mozalloc.h"           // for operator new
 #include "mozilla/TouchEvents.h"
 #include "mozilla/Preferences.h"        // for Preferences
 #include "mozilla/EventStateManager.h"  // for WheelPrefs
 #include "mozilla/webrender/WebRenderAPI.h"
@@ -77,16 +79,21 @@ struct APZCTreeManager::TreeBuildingStat
   const APZPaintLogHelper mPaintLogger;
 
   // State that is updated as we perform the tree build
 
   // A list of nodes that need to be destroyed at the end of the tree building.
   // This is initialized with all nodes in the old tree, and nodes are removed
   // from it as we reuse them in the new tree.
   nsTArray<RefPtr<HitTestingTreeNode>> mNodesToDestroy;
+  // A set of layer trees that are no longer in the hit testing tree. This is
+  // used to destroy unneeded focus targets at the end of tree building. This
+  // is needed in addition to mNodesToDestroy because a hit testing node for a
+  // layer tree can be removed without the whole layer tree being removed.
+  std::unordered_set<uint64_t> mLayersIdsToDestroy;
 
   // This map is populated as we place APZCs into the new tree. Its purpose is
   // to facilitate re-using the same APZC for different layers that scroll
   // together (and thus have the same ScrollableLayerGuid).
   std::unordered_map<ScrollableLayerGuid, AsyncPanZoomController*, ScrollableLayerGuidHash> mApzcMap;
 };
 
 class APZCTreeManager::CheckerboardFlushObserver : public nsIObserver {
@@ -153,16 +160,45 @@ APZCTreeManager::CheckerboardFlushObserv
     nsCOMPtr<nsIObserverService> obsSvc = mozilla::services::GetObserverService();
     if (obsSvc) {
       obsSvc->NotifyObservers(nullptr, "APZ:FlushActiveCheckerboard:Done", nullptr);
     }
   }
   return NS_OK;
 }
 
+/**
+ * A RAII class used for setting the focus sequence number on input events
+ * as they are being processed. Any input event is assumed to be potentially
+ * focus changing unless explicitly marked otherwise.
+ */
+class MOZ_RAII AutoFocusSequenceNumberSetter
+{
+public:
+  AutoFocusSequenceNumberSetter(FocusState& aFocusState, InputData& aEvent)
+    : mFocusState(aFocusState)
+    , mEvent(aEvent)
+    , mMayChangeFocus(true)
+  { }
+
+  void MarkAsNonFocusChanging() { mMayChangeFocus = false; }
+
+  ~AutoFocusSequenceNumberSetter()
+  {
+    if (mMayChangeFocus) {
+      mFocusState.ReceiveFocusChangingEvent();
+    }
+    mEvent.mFocusSequenceNumber = mFocusState.LastAPZProcessedEvent();
+  }
+
+private:
+  FocusState& mFocusState;
+  InputData& mEvent;
+  bool mMayChangeFocus;
+};
 
 /*static*/ const ScreenMargin
 APZCTreeManager::CalculatePendingDisplayPort(
   const FrameMetrics& aFrameMetrics,
   const ParentLayerPoint& aVelocity)
 {
   return AsyncPanZoomController::CalculatePendingDisplayPort(
     aFrameMetrics, aVelocity);
@@ -258,26 +294,29 @@ APZCTreeManager::UpdateHitTestingTreeImp
   // we are sure that the layer was removed and not just transplanted elsewhere. Doing that
   // as part of a recursive tree walk is hard and so maintaining a list and removing
   // APZCs that are still alive is much simpler.
   ForEachNode<ReverseIterator>(mRootNode.get(),
       [&state] (HitTestingTreeNode* aNode)
       {
         state.mNodesToDestroy.AppendElement(aNode);
       });
+  state.mLayersIdsToDestroy = mFocusState.GetFocusTargetLayerIds();
   mRootNode = nullptr;
 
   if (aRoot) {
     std::stack<gfx::TreeAutoIndent> indents;
     std::stack<gfx::Matrix4x4> ancestorTransforms;
     HitTestingTreeNode* parent = nullptr;
     HitTestingTreeNode* next = nullptr;
     uint64_t layersId = aRootLayerTreeId;
     ancestorTransforms.push(Matrix4x4());
 
+    state.mLayersIdsToDestroy.erase(aRootLayerTreeId);
+
     mApzcTreeLog << "[start]\n";
     mTreeLock.AssertCurrentThreadOwns();
 
     ForEachNode<ReverseIterator>(aRoot,
         [&](ScrollNode aLayerMetrics)
         {
           mApzcTreeLog << aLayerMetrics.Name() << '\t';
 
@@ -305,17 +344,25 @@ APZCTreeManager::UpdateHitTestingTreeImp
           }
           ancestorTransforms.push(currentTransform);
 
           // Note that |node| at this point will not have any children, otherwise we
           // we would have to set next to node->GetFirstChild().
           MOZ_ASSERT(!node->GetFirstChild());
           parent = node;
           next = nullptr;
-          layersId = aLayerMetrics.GetReferentId().valueOr(layersId);
+
+          // Update the layersId if we have a new one
+          if (Maybe<uint64_t> newLayersId = aLayerMetrics.GetReferentId()) {
+            layersId = *newLayersId;
+
+            // Mark that this layer tree is being used
+            state.mLayersIdsToDestroy.erase(layersId);
+          }
+
           indents.push(gfx::TreeAutoIndent(mApzcTreeLog));
         },
         [&](ScrollNode aLayerMetrics)
         {
           next = parent;
           parent = parent->GetParent();
           layersId = next->GetLayersId();
           ancestorTransforms.pop();
@@ -330,24 +377,43 @@ APZCTreeManager::UpdateHitTestingTreeImp
 
   for (size_t i = 0; i < state.mNodesToDestroy.Length(); i++) {
     APZCTM_LOG("Destroying node at %p with APZC %p\n",
         state.mNodesToDestroy[i].get(),
         state.mNodesToDestroy[i]->GetApzc());
     state.mNodesToDestroy[i]->Destroy();
   }
 
+  // Clear out any focus targets that are no longer needed
+  for (auto layersId : state.mLayersIdsToDestroy) {
+    mFocusState.RemoveFocusTarget(layersId);
+  }
+
 #if ENABLE_APZCTM_LOGGING
   // Make the hit-test tree line up with the layer dump
   printf_stderr("APZCTreeManager (%p)\n", this);
   mRootNode->Dump("  ");
 #endif
 }
 
 void
+APZCTreeManager::UpdateFocusState(uint64_t aRootLayerTreeId,
+                                  uint64_t aOriginatingLayersId,
+                                  const FocusTarget& aFocusTarget)
+{
+  if (!gfxPrefs::APZKeyboardEnabled()) {
+    return;
+  }
+
+  mFocusState.Update(aRootLayerTreeId,
+                     aOriginatingLayersId,
+                     aFocusTarget);
+}
+
+void
 APZCTreeManager::UpdateHitTestingTree(uint64_t aRootLayerTreeId,
                                       Layer* aRoot,
                                       bool aIsFirstPaint,
                                       uint64_t aOriginatingLayersId,
                                       uint32_t aPaintSequenceNumber)
 {
   LayerMetricsWrapper root(aRoot);
   UpdateHitTestingTreeImpl(aRootLayerTreeId, root, aIsFirstPaint,
@@ -863,16 +929,19 @@ APZCTreeManager::FlushApzRepaints(uint64
 
 nsEventStatus
 APZCTreeManager::ReceiveInputEvent(InputData& aEvent,
                                    ScrollableLayerGuid* aOutTargetGuid,
                                    uint64_t* aOutInputBlockId)
 {
   APZThreadUtils::AssertOnControllerThread();
 
+  // Use a RAII class for updating the focus sequence number of this event
+  AutoFocusSequenceNumberSetter focusSetter(mFocusState, aEvent);
+
 #if defined(MOZ_WIDGET_ANDROID)
   MOZ_ASSERT(mToolbarAnimator);
   ScreenPoint scrollOffset;
   {
     MutexAutoLock lock(mTreeLock);
     RefPtr<AsyncPanZoomController> apzc = FindRootContentOrRootApzc();
     if (apzc) {
       scrollOffset = ViewAs<ScreenPixel>(apzc->GetCurrentAsyncScrollOffset(AsyncPanZoomController::NORMAL),
@@ -1030,17 +1099,16 @@ APZCTreeManager::ReceiveInputEvent(Input
           aOutTargetGuid->mScrollId = FrameMetrics::NULL_SCROLL_ID;
         }
       }
       break;
     } case SCROLLWHEEL_INPUT: {
       FlushRepaintsToClearScreenToGeckoTransform();
 
       ScrollWheelInput& wheelInput = aEvent.AsScrollWheelInput();
-
       wheelInput.mHandledByAPZ = WillHandleInput(wheelInput);
       if (!wheelInput.mHandledByAPZ) {
         return result;
       }
 
       RefPtr<AsyncPanZoomController> apzc = GetTargetAPZC(wheelInput.mOrigin,
                                                             &hitResult);
       if (apzc) {
@@ -1172,16 +1240,101 @@ APZCTreeManager::ReceiveInputEvent(Input
             /* aTargetConfirmed = */ hitResult != HitDispatchToContentRegion,
             tapInput, aOutInputBlockId);
 
         // Update the out-parameters so they are what the caller expects.
         apzc->GetGuid(aOutTargetGuid);
         tapInput.mPoint = *untransformedPoint;
       }
       break;
+    } case KEYBOARD_INPUT: {
+      // Disable async keyboard scrolling when accessibility.browsewithcaret is enabled
+      if (!gfxPrefs::APZKeyboardEnabled() ||
+          gfxPrefs::AccessibilityBrowseWithCaret()) {
+        return result;
+      }
+
+      KeyboardInput& keyInput = aEvent.AsKeyboardInput();
+
+      // Try and find a matching shortcut for this keyboard input
+      Maybe<KeyboardShortcut> shortcut = mKeyboardMap.FindMatch(keyInput);
+
+      if (!shortcut) {
+        // If we don't have a shortcut for this key event, then we can keep our focus
+        // only if we know there are no key event listeners for this target
+        if (mFocusState.CanIgnoreKeyboardShortcutMisses()) {
+          focusSetter.MarkAsNonFocusChanging();
+        }
+        return result;
+      }
+
+      // Check if this shortcut needs to be dispatched to content. Anything matching
+      // this is assumed to be able to change focus.
+      if (shortcut->mDispatchToContent) {
+        return result;
+      }
+
+      // We know we have an action to execute on whatever is the current focus target
+      const KeyboardScrollAction& action = shortcut->mAction;
+
+      // The current focus target depends on which direction the scroll is to happen
+      Maybe<ScrollableLayerGuid> targetGuid;
+      switch (action.mType)
+      {
+        case KeyboardScrollAction::eScrollCharacter: {
+          targetGuid = mFocusState.GetHorizontalTarget();
+          break;
+        }
+        case KeyboardScrollAction::eScrollLine:
+        case KeyboardScrollAction::eScrollPage:
+        case KeyboardScrollAction::eScrollComplete: {
+          targetGuid = mFocusState.GetVerticalTarget();
+          break;
+        }
+
+        case KeyboardScrollAction::eSentinel: {
+          MOZ_ASSERT_UNREACHABLE("Invalid KeyboardScrollActionType");
+        }
+      }
+
+      // If we don't have a scroll target then either we have a stale focus target,
+      // the focused element has event listeners, or the focused element doesn't have a
+      // layerized scroll frame. In any case we need to dispatch to content.
+      if (!targetGuid) {
+        return result;
+      }
+
+      RefPtr<AsyncPanZoomController> targetApzc = GetTargetAPZC(targetGuid->mLayersId,
+                                                                targetGuid->mScrollId);
+
+      // Scroll snapping behavior with keyboard input is more complicated, so
+      // ignore any input events that are targeted at an Apzc with scroll snap
+      // points.
+      if (!targetApzc || targetApzc->HasScrollSnapping()) {
+        return result;
+      }
+
+      // Attach the keyboard scroll action to the input event for processing
+      // by the input queue.
+      keyInput.mAction = action;
+
+      // Dispatch the event to the input queue.
+      result = mInputQueue->ReceiveInputEvent(
+          targetApzc,
+          /* aTargetConfirmed = */ true,
+          keyInput, aOutInputBlockId);
+
+      // Any keyboard event that is dispatched to the input queue at this point
+      // should have been consumed
+      MOZ_ASSERT(result == nsEventStatus_eConsumeNoDefault);
+
+      keyInput.mHandledByAPZ = true;
+      focusSetter.MarkAsNonFocusChanging();
+
+      break;
     } case SENTINEL_INPUT: {
       MOZ_ASSERT_UNREACHABLE("Invalid InputType.");
       break;
     }
   }
   return result;
 }
 
@@ -1399,18 +1552,19 @@ APZCTreeManager::UpdateWheelTransaction(
      txn->EndTransaction();
      return;
    default:
      break;
   }
 }
 
 void
-APZCTreeManager::TransformEventRefPoint(LayoutDeviceIntPoint* aRefPoint,
-                              ScrollableLayerGuid* aOutTargetGuid)
+APZCTreeManager::ProcessUnhandledEvent(LayoutDeviceIntPoint* aRefPoint,
+                                        ScrollableLayerGuid*  aOutTargetGuid,
+                                        uint64_t*             aOutFocusSequenceNumber)
 {
   // Transform the aRefPoint.
   // If the event hits an overscrolled APZC, instruct the caller to ignore it.
   HitTestResult hitResult = HitNothing;
   PixelCastJustification LDIsScreen = PixelCastJustification::LayoutDeviceIsScreenForUntransformedEvent;
   ScreenIntPoint refPointAsScreen =
     ViewAs<ScreenPixel>(*aRefPoint, LDIsScreen);
   RefPtr<AsyncPanZoomController> apzc = GetTargetAPZC(refPointAsScreen, &hitResult);
@@ -1422,27 +1576,37 @@ APZCTreeManager::TransformEventRefPoint(
     ScreenToScreenMatrix4x4 outTransform = transformToApzc * transformToGecko;
     Maybe<ScreenIntPoint> untransformedRefPoint =
       UntransformBy(outTransform, refPointAsScreen);
     if (untransformedRefPoint) {
       *aRefPoint =
         ViewAs<LayoutDevicePixel>(*untransformedRefPoint, LDIsScreen);
     }
   }
+
+  // Update the focus sequence number and attach it to the event
+  mFocusState.ReceiveFocusChangingEvent();
+  *aOutFocusSequenceNumber = mFocusState.LastAPZProcessedEvent();
 }
 
 void
 APZCTreeManager::ProcessTouchVelocity(uint32_t aTimestampMs, float aSpeedY)
 {
   if (mApzcForInputBlock) {
     mApzcForInputBlock->HandleTouchVelocity(aTimestampMs, aSpeedY);
   }
 }
 
 void
+APZCTreeManager::SetKeyboardMap(const KeyboardMap& aKeyboardMap)
+{
+  mKeyboardMap = aKeyboardMap;
+}
+
+void
 APZCTreeManager::ZoomToRect(const ScrollableLayerGuid& aGuid,
                             const CSSRect& aRect,
                             const uint32_t aFlags)
 {
   RefPtr<AsyncPanZoomController> apzc = GetTargetAPZC(aGuid);
   if (apzc) {
     apzc->ZoomToRect(aRect, aFlags);
   }
--- a/gfx/layers/apz/src/APZCTreeManager.h
+++ b/gfx/layers/apz/src/APZCTreeManager.h
@@ -9,16 +9,18 @@
 #include <unordered_map>                          // for std::unordered_map
 
 #include "gfxPoint.h"                   // for gfxPoint
 #include "mozilla/Assertions.h"         // for MOZ_ASSERT_HELPER2
 #include "mozilla/gfx/Logging.h"        // for gfx::TreeLog
 #include "mozilla/gfx/Matrix.h"         // for Matrix4x4
 #include "mozilla/layers/TouchCounter.h"// for TouchCounter
 #include "mozilla/layers/IAPZCTreeManager.h" // for IAPZCTreeManager
+#include "mozilla/layers/KeyboardMap.h" // for KeyboardMap
+#include "mozilla/layers/FocusState.h"  // for FocusState
 #include "mozilla/Mutex.h"              // for Mutex
 #include "mozilla/RefPtr.h"             // for RefPtr
 #include "mozilla/TimeStamp.h"          // for mozilla::TimeStamp
 #include "nsCOMPtr.h"                   // for already_AddRefed
 
 #if defined(MOZ_WIDGET_ANDROID)
 #include "mozilla/layers/AndroidDynamicToolbarAnimator.h"
 #endif // defined(MOZ_WIDGET_ANDROID)
@@ -35,16 +37,17 @@ class WebRenderAPI;
 namespace layers {
 
 class Layer;
 class AsyncPanZoomController;
 class APZCTreeManagerParent;
 class CompositorBridgeParent;
 class OverscrollHandoffChain;
 struct OverscrollHandoffState;
+class FocusTarget;
 struct FlingHandoffState;
 struct ScrollableLayerGuidHash;
 class LayerMetricsWrapper;
 class InputQueue;
 class GeckoContentController;
 class HitTestingTreeNode;
 class WebRenderScrollData;
 
@@ -107,16 +110,29 @@ public:
    * Initializes the global state used in AsyncPanZoomController.
    * This is normally called when it is first needed in the constructor
    * of APZCTreeManager, but can be called manually to force it to be
    * initialized earlier.
    */
   static void InitializeGlobalState();
 
   /**
+   * Rebuild the focus state based on the focus target from the layer tree update
+   * that just occurred.
+   *
+   * @param aRootLayerTreeId The layer tree ID of the root layer corresponding
+   *                         to this APZCTreeManager
+   * @param aOriginatingLayersId The layer tree ID of the layer corresponding to
+   *                             this layer tree update.
+   */
+  void UpdateFocusState(uint64_t aRootLayerTreeId,
+                        uint64_t aOriginatingLayersId,
+                        const FocusTarget& aFocusTarget);
+
+  /**
    * Rebuild the hit-testing tree based on the layer update that just came up.
    * Preserve nodes and APZC instances where possible, but retire those whose
    * layers are no longer in the layer tree.
    *
    * This must be called on the compositor thread as it walks the layer tree.
    *
    * @param aRootLayerTreeId The layer tree ID of the root layer corresponding
    *                         to this APZCTreeManager
@@ -207,16 +223,21 @@ public:
    * was added to, if that was the case. May be null.
    */
   nsEventStatus ReceiveInputEvent(
       InputData& aEvent,
       ScrollableLayerGuid* aOutTargetGuid,
       uint64_t* aOutInputBlockId) override;
 
   /**
+   * Set the keyboard shortcuts to use for translating keyboard events.
+   */
+  void SetKeyboardMap(const KeyboardMap& aKeyboardMap) override;
+
+  /**
    * Kicks an animation to zoom to a rect. This may be either a zoom out or zoom
    * in. The actual animation is done on the compositor thread after being set
    * up. |aRect| must be given in CSS pixels, relative to the document.
    * |aFlags| is a combination of the ZoomToRectBehavior enum values.
    */
   void ZoomToRect(
       const ScrollableLayerGuid& aGuid,
       const CSSRect& aRect,
@@ -425,19 +446,20 @@ public:
    *
    * On slow running tests, drags and touch events can be misinterpreted
    * as a long tap. This allows tests to disable long tap gesture detection.
    */
   void SetLongTapEnabled(bool aTapGestureEnabled) override;
 
   // Methods to help process WidgetInputEvents (or manage conversion to/from InputData)
 
-  void TransformEventRefPoint(
+  void ProcessUnhandledEvent(
       LayoutDeviceIntPoint* aRefPoint,
-      ScrollableLayerGuid* aOutTargetGuid) override;
+      ScrollableLayerGuid*  aOutTargetGuid,
+      uint64_t*             aOutFocusSequenceNumber) override;
 
   void UpdateWheelTransaction(
       LayoutDeviceIntPoint aRefPoint,
       EventMessage aEventMessage) override;
 
 protected:
   // Protected destructor, to discourage deletion outside of Release():
   virtual ~APZCTreeManager();
@@ -545,16 +567,24 @@ private:
    * is considered part of the APZC tree management state.
    * Finally, the lock needs to be held when accessing mZoomConstraints.
    * IMPORTANT: See the note about lock ordering at the top of this file. */
   mutable mozilla::Mutex mTreeLock;
   RefPtr<HitTestingTreeNode> mRootNode;
   /* Holds the zoom constraints for scrollable layers, as determined by the
    * the main-thread gecko code. */
   std::unordered_map<ScrollableLayerGuid, ZoomConstraints, ScrollableLayerGuidHash> mZoomConstraints;
+  /* A list of keyboard shortcuts to use for translating keyboard inputs into
+   * keyboard actions. This is gathered on the main thread from XBL bindings.
+   */
+  KeyboardMap mKeyboardMap;
+  /* This tracks the focus targets of chrome and content and whether we have
+   * a current focus target or whether we are waiting for a new confirmation.
+   */
+  FocusState mFocusState;
   /* This tracks the APZC that should receive all inputs for the current input event block.
    * This allows touch points to move outside the thing they started on, but still have the
    * touch events delivered to the same initial APZC. This will only ever be touched on the
    * input delivery thread, and so does not require locking.
    */
   RefPtr<AsyncPanZoomController> mApzcForInputBlock;
   /* The hit result for the current input event block; this should always be in
    * sync with mApzcForInputBlock.
--- a/gfx/layers/apz/src/AsyncPanZoomAnimation.h
+++ b/gfx/layers/apz/src/AsyncPanZoomAnimation.h
@@ -13,16 +13,17 @@
 #include "FrameMetrics.h"
 #include "nsISupportsImpl.h"
 #include "nsTArray.h"
 
 namespace mozilla {
 namespace layers {
 
 class WheelScrollAnimation;
+class KeyboardScrollAnimation;
 class SmoothScrollAnimation;
 
 class AsyncPanZoomAnimation {
   NS_INLINE_DECL_THREADSAFE_REFCOUNTING(AsyncPanZoomAnimation)
 
 public:
   explicit AsyncPanZoomAnimation()
   { }
@@ -45,16 +46,19 @@ public:
   /**
    * Get the deferred tasks in |mDeferredTasks| and place them in |aTasks|. See
    * |mDeferredTasks| for more information.  Clears |mDeferredTasks|.
    */
   nsTArray<RefPtr<Runnable>> TakeDeferredTasks() {
     return Move(mDeferredTasks);
   }
 
+  virtual KeyboardScrollAnimation* AsKeyboardScrollAnimation() {
+    return nullptr;
+  }
   virtual WheelScrollAnimation* AsWheelScrollAnimation() {
     return nullptr;
   }
   virtual SmoothScrollAnimation* AsSmoothScrollAnimation() {
     return nullptr;
   }
 
   virtual bool WantsRepaints() {
--- a/gfx/layers/apz/src/AsyncPanZoomController.cpp
+++ b/gfx/layers/apz/src/AsyncPanZoomController.cpp
@@ -72,16 +72,17 @@
 #include "nsStyleStruct.h"              // for nsTimingFunction
 #include "nsTArray.h"                   // for nsTArray, nsTArray_Impl, etc
 #include "nsThreadUtils.h"              // for NS_IsMainThread
 #include "nsViewportInfo.h"             // for kViewportMinScale, kViewportMaxScale
 #include "prsystem.h"                   // for PR_GetPhysicalMemorySize
 #include "SharedMemoryBasic.h"          // for SharedMemoryBasic
 #include "ScrollSnap.h"                 // for ScrollSnapUtils
 #include "WheelScrollAnimation.h"
+#include "KeyboardScrollAnimation.h"
 #if defined(MOZ_WIDGET_ANDROID)
 #include "AndroidAPZ.h"
 #include "mozilla/layers/AndroidDynamicToolbarAnimator.h"
 #endif // defined(MOZ_WIDGET_ANDROID)
 
 #define ENABLE_APZC_LOGGING 0
 // #define ENABLE_APZC_LOGGING 1
 
@@ -1000,16 +1001,21 @@ nsEventStatus AsyncPanZoomController::Ha
     TapGestureInput tapInput = aEvent.AsTapGestureInput();
     if (!tapInput.TransformToLocal(aTransformToApzc)) {
       return rv;
     }
 
     rv = HandleGestureEvent(tapInput);
     break;
   }
+  case KEYBOARD_INPUT: {
+    KeyboardInput keyInput = aEvent.AsKeyboardInput();
+    rv = OnKeyboard(keyInput);
+    break;
+  }
   case SENTINEL_INPUT: {
     MOZ_ASSERT_UNREACHABLE("Invalid value");
     break;
   }
   }
 
   return rv;
 }
@@ -1062,16 +1068,17 @@ nsEventStatus AsyncPanZoomController::On
   ParentLayerPoint point = GetFirstTouchPoint(aEvent);
 
   switch (mState) {
     case FLING:
     case ANIMATING_ZOOM:
     case SMOOTH_SCROLL:
     case OVERSCROLL_ANIMATION:
     case WHEEL_SCROLL:
+    case KEYBOARD_SCROLL:
     case PAN_MOMENTUM:
       MOZ_ASSERT(GetCurrentTouchBlock());
       GetCurrentTouchBlock()->GetOverscrollHandoffChain()->CancelAnimations(ExcludeOverscroll);
       MOZ_FALLTHROUGH;
     case NOTHING: {
       mX.StartTouch(point.x, aEvent.mTime);
       mY.StartTouch(point.y, aEvent.mTime);
       if (RefPtr<GeckoContentController> controller = GetGeckoContentController()) {
@@ -1137,16 +1144,17 @@ nsEventStatus AsyncPanZoomController::On
       return nsEventStatus_eConsumeNoDefault;
 
     case PINCHING:
       // The scale gesture listener should have handled this.
       NS_WARNING("Gesture listener should have handled pinching in OnTouchMove.");
       return nsEventStatus_eIgnore;
 
     case WHEEL_SCROLL:
+    case KEYBOARD_SCROLL:
     case OVERSCROLL_ANIMATION:
       // Should not receive a touch-move in the OVERSCROLL_ANIMATION state
       // as touch blocks that begin in an overscrolled state cancel the
       // animation. The same is true for wheel scroll animations.
       NS_WARNING("Received impossible touch in OnTouchMove");
       break;
   }
 
@@ -1217,16 +1225,17 @@ nsEventStatus AsyncPanZoomController::On
   }
   case PINCHING:
     SetState(NOTHING);
     // Scale gesture listener should have handled this.
     NS_WARNING("Gesture listener should have handled pinching in OnTouchEnd.");
     return nsEventStatus_eIgnore;
 
   case WHEEL_SCROLL:
+  case KEYBOARD_SCROLL:
   case OVERSCROLL_ANIMATION:
     // Should not receive a touch-end in the OVERSCROLL_ANIMATION state
     // as touch blocks that begin in an overscrolled state cancel the
     // animation. The same is true for WHEEL_SCROLL.
     NS_WARNING("Received impossible touch in OnTouchEnd");
     break;
   }
 
@@ -1651,16 +1660,162 @@ AsyncPanZoomController::GetScrollWheelDe
     delta.y = (delta.y >= 0)
               ? pageScrollSize.height
               : -pageScrollSize.height;
   }
 
   return delta;
 }
 
+static
+void ReportKeyboardScrollAction(const KeyboardScrollAction& aAction)
+{
+  ScrollInputMethod scrollMethod;
+
+  switch (aAction.mType) {
+    case KeyboardScrollAction::eScrollLine: {
+      scrollMethod = ScrollInputMethod::ApzScrollLine;
+      break;
+    }
+    case KeyboardScrollAction::eScrollCharacter: {
+      scrollMethod = ScrollInputMethod::ApzScrollCharacter;
+      break;
+    }
+    case KeyboardScrollAction::eScrollPage: {
+      scrollMethod = ScrollInputMethod::ApzScrollPage;
+      break;
+    }
+    case KeyboardScrollAction::eScrollComplete: {
+      scrollMethod = ScrollInputMethod::ApzCompleteScroll;
+      break;
+    }
+    case KeyboardScrollAction::eSentinel: {
+      MOZ_ASSERT_UNREACHABLE("Invalid KeyboardScrollAction.");
+      return;
+    }
+  }
+
+  mozilla::Telemetry::Accumulate(mozilla::Telemetry::SCROLL_INPUT_METHODS,
+      (uint32_t)scrollMethod);
+}
+
+nsEventStatus
+AsyncPanZoomController::OnKeyboard(const KeyboardInput& aEvent)
+{
+  // Report the type of scroll action to telemetry
+  ReportKeyboardScrollAction(aEvent.mAction);
+
+  // Calculate the destination for this keyboard scroll action
+  nsPoint destination = CSSPoint::ToAppUnits(GetKeyboardDestination(aEvent.mAction));
+
+  // The lock must be held across the entire update operation, so the
+  // compositor doesn't end the animation before we get a chance to
+  // update it.
+  ReentrantMonitorAutoEnter lock(mMonitor);
+
+  // Use a keyboard scroll animation to scroll, reusing an existing one if it exists
+  if (mState != KEYBOARD_SCROLL) {
+    CancelAnimation();
+    SetState(KEYBOARD_SCROLL);
+
+    nsPoint initialPosition = CSSPoint::ToAppUnits(mFrameMetrics.GetScrollOffset());
+    StartAnimation(new KeyboardScrollAnimation(*this, initialPosition, aEvent.mAction.mType));
+  }
+
+  // Cast velocity from ParentLayerPoints/ms to CSSPoints/ms then convert to
+  // appunits/second. We perform a cast to ParentLayerPoints/ms without a
+  // conversion so that the scroll duration isn't affected by zoom
+  nsPoint velocity =
+    CSSPoint::ToAppUnits(CSSPoint(mX.GetVelocity(), mY.GetVelocity())) * 1000.0f;
+
+  KeyboardScrollAnimation* animation = mAnimation->AsKeyboardScrollAnimation();
+  MOZ_ASSERT(animation);
+
+  animation->UpdateDestination(aEvent.mTimeStamp, destination, nsSize(velocity.x, velocity.y));
+
+  return nsEventStatus_eConsumeNoDefault;
+}
+
+CSSPoint
+AsyncPanZoomController::GetKeyboardDestination(const KeyboardScrollAction& aAction) const
+{
+  CSSSize lineScrollSize;
+  CSSSize pageScrollSize;
+  CSSPoint scrollOffset;
+  CSSRect scrollRect;
+
+  {
+    // Grab the lock to access the frame metrics.
+    ReentrantMonitorAutoEnter lock(mMonitor);
+
+    lineScrollSize = mScrollMetadata.GetLineScrollAmount() /
+      mFrameMetrics.GetDevPixelsPerCSSPixel();
+    pageScrollSize = mScrollMetadata.GetPageScrollAmount() /
+      mFrameMetrics.GetDevPixelsPerCSSPixel();
+
+    if (mState == WHEEL_SCROLL) {
+      scrollOffset = mAnimation->AsWheelScrollAnimation()->GetDestination();
+    } else if (mState == SMOOTH_SCROLL) {
+      scrollOffset = mAnimation->AsSmoothScrollAnimation()->GetDestination();
+    } else if (mState == KEYBOARD_SCROLL) {
+      scrollOffset = mAnimation->AsKeyboardScrollAnimation()->GetDestination();
+    } else {
+      scrollOffset = mFrameMetrics.GetScrollOffset();
+    }
+
+    scrollRect = mFrameMetrics.GetScrollableRect();
+  }
+
+  // Calculate the scroll destination based off of the scroll type and direction
+  CSSPoint scrollDestination = scrollOffset;
+
+  switch (aAction.mType) {
+    case KeyboardScrollAction::eScrollCharacter: {
+      int32_t scrollDistance = gfxPrefs::ToolkitHorizontalScrollDistance();
+
+      if (aAction.mForward) {
+        scrollDestination.x += scrollDistance * lineScrollSize.width;
+      } else {
+        scrollDestination.x -= scrollDistance * lineScrollSize.width;
+      }
+      break;
+    }
+    case KeyboardScrollAction::eScrollLine: {
+      int32_t scrollDistance = gfxPrefs::ToolkitVerticalScrollDistance();
+
+      if (aAction.mForward) {
+        scrollDestination.y += scrollDistance * lineScrollSize.height;
+      } else {
+        scrollDestination.y -= scrollDistance * lineScrollSize.height;
+      }
+      break;
+    }
+    case KeyboardScrollAction::eScrollPage: {
+      if (aAction.mForward) {
+        scrollDestination.y += pageScrollSize.height;
+      } else {
+        scrollDestination.y -= pageScrollSize.height;
+      }
+      break;
+    }
+    case KeyboardScrollAction::eScrollComplete: {
+      if (aAction.mForward) {
+        scrollDestination.y = scrollRect.YMost();
+      } else {
+        scrollDestination.y = scrollRect.y;
+      }
+      break;
+    }
+    case KeyboardScrollAction::eSentinel:
+      MOZ_ASSERT_UNREACHABLE("unexpected keyboard delta type");
+  }
+
+  return scrollDestination;
+}
+
 // Return whether or not the underlying layer can be scrolled on either axis.
 bool
 AsyncPanZoomController::CanScroll(const InputData& aEvent) const
 {
   ParentLayerPoint delta;
   if (aEvent.mInputType == SCROLLWHEEL_INPUT) {
     delta = GetScrollWheelDelta(aEvent.AsScrollWheelInput());
   } else if (aEvent.mInputType == PANGESTURE_INPUT) {
@@ -1817,16 +1972,18 @@ nsEventStatus AsyncPanZoomController::On
       CSSPoint startPosition = mFrameMetrics.GetScrollOffset();
       // If we're already in a wheel scroll or smooth scroll animation,
       // the delta is applied to its destination, not to the current
       // scroll position. Take this into account when finding a snap point.
       if (mState == WHEEL_SCROLL) {
         startPosition = mAnimation->AsWheelScrollAnimation()->GetDestination();
       } else if (mState == SMOOTH_SCROLL) {
         startPosition = mAnimation->AsSmoothScrollAnimation()->GetDestination();
+      } else if (mState == KEYBOARD_SCROLL) {
+        startPosition = mAnimation->AsKeyboardScrollAnimation()->GetDestination();
       }
       if (MaybeAdjustDeltaForScrollSnapping(aEvent, delta, startPosition)) {
         // If we're scroll snapping, use a smooth scroll animation to get
         // the desired physics. Note that SmoothScrollTo() will re-use an
         // existing smooth scroll animation if there is one.
         APZC_LOG("%p wheel scrolling to snap point %s\n", this, Stringify(startPosition).c_str());
         SmoothScrollTo(startPosition);
         break;
@@ -1840,22 +1997,23 @@ nsEventStatus AsyncPanZoomController::On
         nsPoint initialPosition = CSSPoint::ToAppUnits(mFrameMetrics.GetScrollOffset());
         StartAnimation(new WheelScrollAnimation(
           *this, initialPosition, aEvent.mDeltaType));
       }
 
       nsPoint deltaInAppUnits =
         CSSPoint::ToAppUnits(delta / mFrameMetrics.GetZoom());
       // Cast velocity from ParentLayerPoints/ms to CSSPoints/ms then convert to
-      // appunits/second
+      // appunits/second. We perform a cast to ParentLayerPoints/ms without a
+      // conversion so that the scroll duration isn't affected by zoom
       nsPoint velocity =
         CSSPoint::ToAppUnits(CSSPoint(mX.GetVelocity(), mY.GetVelocity())) * 1000.0f;
 
       WheelScrollAnimation* animation = mAnimation->AsWheelScrollAnimation();
-      animation->Update(aEvent.mTimeStamp, deltaInAppUnits, nsSize(velocity.x, velocity.y));
+      animation->UpdateDelta(aEvent.mTimeStamp, deltaInAppUnits, nsSize(velocity.x, velocity.y));
       break;
     }
 
     case ScrollWheelInput::SCROLLMODE_SENTINEL: {
       MOZ_ASSERT_UNREACHABLE("Invalid ScrollMode.");
       break;
     }
   }
@@ -2603,17 +2761,18 @@ void AsyncPanZoomController::SmoothScrol
     RefPtr<SmoothScrollAnimation> animation(
       static_cast<SmoothScrollAnimation*>(mAnimation.get()));
     animation->SetDestination(CSSPoint::ToAppUnits(aDestination));
   } else {
     CancelAnimation();
     SetState(SMOOTH_SCROLL);
     nsPoint initialPosition = CSSPoint::ToAppUnits(mFrameMetrics.GetScrollOffset());
     // Cast velocity from ParentLayerPoints/ms to CSSPoints/ms then convert to
-    // appunits/second
+    // appunits/second. We perform a cast to ParentLayerPoints/ms without a
+    // conversion so that the scroll duration isn't affected by zoom
     nsPoint initialVelocity = CSSPoint::ToAppUnits(CSSPoint(mX.GetVelocity(),
                                                             mY.GetVelocity())) * 1000.0f;
     nsPoint destination = CSSPoint::ToAppUnits(aDestination);
 
     StartAnimation(new SmoothScrollAnimation(*this,
                                              initialPosition, initialVelocity,
                                              destination,
                                              gfxPrefs::ScrollBehaviorSpringConstant(),
--- a/gfx/layers/apz/src/AsyncPanZoomController.h
+++ b/gfx/layers/apz/src/AsyncPanZoomController.h
@@ -56,16 +56,17 @@ class TouchBlockState;
 class PanGestureBlockState;
 class OverscrollHandoffChain;
 class StateChangeNotificationBlocker;
 class CheckerboardEvent;
 class OverscrollEffectBase;
 class WidgetOverscrollEffect;
 class GenericOverscrollEffect;
 class AndroidSpecificState;
+struct KeyboardScrollAction;
 
 // Base class for grouping platform-specific APZC state variables.
 class PlatformSpecificStateBase {
 public:
   virtual ~PlatformSpecificStateBase() {}
   virtual AndroidSpecificState* AsAndroidSpecificState() { return nullptr; }
 };
 
@@ -335,16 +336,23 @@ public:
 
   /**
    * Returns whether this APZC is for an element marked with the 'scrollgrab'
    * attribute.
    */
   bool HasScrollgrab() const { return mScrollMetadata.GetHasScrollgrab(); }
 
   /**
+   * Returns whether this APZC has scroll snap points.
+   */
+  bool HasScrollSnapping() const {
+    return mScrollMetadata.GetSnapInfo().HasScrollSnapping();
+  }
+
+  /**
    * Returns whether this APZC has room to be panned (in any direction).
    */
   bool IsPannable() const;
 
   /**
    * Returns whether this APZC represents a scroll info layer.
    */
   bool IsScrollInfoLayer() const;
@@ -478,16 +486,23 @@ protected:
   /**
    * Helper methods for handling scroll wheel events.
    */
   nsEventStatus OnScrollWheel(const ScrollWheelInput& aEvent);
 
   ParentLayerPoint GetScrollWheelDelta(const ScrollWheelInput& aEvent) const;
 
   /**
+   * Helper methods for handling keyboard events.
+   */
+  nsEventStatus OnKeyboard(const KeyboardInput& aEvent);
+
+  CSSPoint GetKeyboardDestination(const KeyboardScrollAction& aAction) const;
+
+  /**
    * Helper methods for long press gestures.
    */
   nsEventStatus OnLongPress(const TapGestureInput& aEvent);
   nsEventStatus OnLongPressUp(const TapGestureInput& aEvent);
 
   /**
    * Helper method for single tap gestures.
    */
@@ -834,17 +849,18 @@ protected:
     PAN_MOMENTUM,             /* like PANNING, but controlled by momentum PanGestureInput events */
 
     PINCHING,                 /* nth touch-start, where n > 1. this mode allows pan and zoom */
     ANIMATING_ZOOM,           /* animated zoom to a new rect */
     OVERSCROLL_ANIMATION,     /* Spring-based animation used to relieve overscroll once
                                  the finger is lifted. */
     SMOOTH_SCROLL,            /* Smooth scrolling to destination. Used by
                                  CSSOM-View smooth scroll-behavior */
-    WHEEL_SCROLL              /* Smooth scrolling to a destination for a wheel event. */
+    WHEEL_SCROLL,             /* Smooth scrolling to a destination for a wheel event. */
+    KEYBOARD_SCROLL           /* Smooth scrolling to a destination for a keyboard event. */
   };
 
   // This is in theory protected by |mMonitor|; that is, it should be held whenever
   // this is updated. In practice though... see bug 897017.
   PanZoomState mState;
 
 private:
   friend class StateChangeNotificationBlocker;
@@ -931,17 +947,19 @@ public:
    */
   bool AttemptFling(FlingHandoffState& aHandoffState);
 
 private:
   friend class AndroidFlingAnimation;
   friend class GenericFlingAnimation;
   friend class OverscrollAnimation;
   friend class SmoothScrollAnimation;
+  friend class GenericScrollAnimation;
   friend class WheelScrollAnimation;
+  friend class KeyboardScrollAnimation;
 
   friend class GenericOverscrollEffect;
   friend class WidgetOverscrollEffect;
 
   // The initial velocity of the most recent fling.
   ParentLayerPoint mLastFlingVelocity;
   // The time at which the most recent fling started.
   TimeStamp mLastFlingTime;
new file mode 100644
--- /dev/null
+++ b/gfx/layers/apz/src/FocusState.cpp
@@ -0,0 +1,147 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "mozilla/layers/FocusState.h"
+
+namespace mozilla {
+namespace layers {
+
+FocusState::FocusState()
+  : mLastAPZProcessedEvent(1)
+  , mLastContentProcessedEvent(0)
+  , mFocusHasKeyEventListeners(false)
+  , mFocusLayersId(0)
+  , mFocusHorizontalTarget(FrameMetrics::NULL_SCROLL_ID)
+  , mFocusVerticalTarget(FrameMetrics::NULL_SCROLL_ID)
+{
+}
+
+bool
+FocusState::IsCurrent() const
+{
+  MOZ_ASSERT(mLastContentProcessedEvent <= mLastAPZProcessedEvent);
+  return mLastContentProcessedEvent == mLastAPZProcessedEvent;
+}
+
+void
+FocusState::ReceiveFocusChangingEvent()
+{
+  mLastAPZProcessedEvent += 1;
+}
+
+void
+FocusState::Update(uint64_t aRootLayerTreeId,
+                   uint64_t aOriginatingLayersId,
+                   const FocusTarget& aState)
+{
+  // Update the focus tree with the latest target
+  mFocusTree[aOriginatingLayersId] = aState;
+
+  // Reset our internal state so we can recalculate it
+  mFocusHasKeyEventListeners = false;
+  mFocusLayersId = aRootLayerTreeId;
+  mFocusHorizontalTarget = FrameMetrics::NULL_SCROLL_ID;
+  mFocusVerticalTarget = FrameMetrics::NULL_SCROLL_ID;
+
+  // To update the focus state for the entire APZCTreeManager, we need
+  // to traverse the focus tree to find the current leaf which is the global
+  // focus target we can use for async keyboard scrolling
+  while (true) {
+    auto currentNode = mFocusTree.find(mFocusLayersId);
+    if (currentNode == mFocusTree.end()) {
+      return;
+    }
+
+    const FocusTarget& target = currentNode->second;
+
+    // Accumulate event listener flags on the path to the focus target
+    mFocusHasKeyEventListeners |= target.mFocusHasKeyEventListeners;
+
+    switch (target.mType) {
+      case FocusTarget::eRefLayer: {
+        // Guard against infinite loops
+        MOZ_ASSERT(mFocusLayersId != target.mData.mRefLayerId);
+        if (mFocusLayersId == target.mData.mRefLayerId) {
+          return;
+        }
+
+        // The focus target is in a child layer tree
+        mFocusLayersId = target.mData.mRefLayerId;
+        break;
+      }
+      case FocusTarget::eScrollLayer: {
+        // This is the global focus target
+        mFocusHorizontalTarget = target.mData.mScrollTargets.mHorizontal;
+        mFocusVerticalTarget = target.mData.mScrollTargets.mVertical;
+
+        // Mark what sequence number this target has so we can determine whether
+        // it is stale or not
+        mLastContentProcessedEvent = target.mSequenceNumber;
+        return;
+      }
+      case FocusTarget::eNone: {
+        // Mark what sequence number this target has for debugging purposes so
+        // we can always accurately report on whether we are stale or not
+        mLastContentProcessedEvent = target.mSequenceNumber;
+        return;
+      }
+      case FocusTarget::eSentinel: {
+        MOZ_ASSERT_UNREACHABLE("Invalid FocusTargetType");
+      }
+    }
+  }
+}
+
+std::unordered_set<uint64_t>
+FocusState::GetFocusTargetLayerIds() const
+{
+  std::unordered_set<uint64_t> layersIds;
+  layersIds.reserve(mFocusTree.size());
+
+  for (const auto& focusNode : mFocusTree) {
+    layersIds.insert(focusNode.first);
+  }
+
+  return layersIds;
+}
+
+void
+FocusState::RemoveFocusTarget(uint64_t aLayersId)
+{
+  mFocusTree.erase(aLayersId);
+}
+
+Maybe<ScrollableLayerGuid>
+FocusState::GetHorizontalTarget() const
+{
+  // There is not a scrollable layer to async scroll if
+  //   1. We aren't current
+  //   2. There are event listeners that could change the focus
+  //   3. The target has not been layerized
+  if (!IsCurrent() ||
+      mFocusHasKeyEventListeners ||
+      mFocusHorizontalTarget == FrameMetrics::NULL_SCROLL_ID) {
+    return Nothing();
+  }
+  return Some(ScrollableLayerGuid(mFocusLayersId, 0, mFocusHorizontalTarget));
+}
+
+Maybe<ScrollableLayerGuid>
+FocusState::GetVerticalTarget() const
+{
+  // There is not a scrollable layer to async scroll if:
+  //   1. We aren't current
+  //   2. There are event listeners that could change the focus
+  //   3. The target has not been layerized
+  if (!IsCurrent() ||
+      mFocusHasKeyEventListeners ||
+      mFocusVerticalTarget == FrameMetrics::NULL_SCROLL_ID) {
+    return Nothing();
+  }
+  return Some(ScrollableLayerGuid(mFocusLayersId, 0, mFocusVerticalTarget));
+}
+
+} // namespace layers
+} // namespace mozilla
new file mode 100644
--- /dev/null
+++ b/gfx/layers/apz/src/FocusState.h
@@ -0,0 +1,173 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_layers_FocusState_h
+#define mozilla_layers_FocusState_h
+
+#include <unordered_map>    // for std::unordered_map
+#include <unordered_set>    // for std::unordered_set
+
+#include "FrameMetrics.h"   // for FrameMetrics::ViewID
+
+#include "mozilla/layers/FocusTarget.h" // for FocusTarget
+
+namespace mozilla {
+namespace layers {
+
+/**
+ * This class is used for tracking chrome and content focus targets and calculating
+ * global focus information from them for use by APZCTreeManager for async keyboard
+ * scrolling.
+ *
+ * # Calculating the element to scroll
+ *
+ * Chrome and content processes have independently focused elements. This makes it
+ * difficult to calculate the global focused element and its scrollable frame from
+ * the chrome or content side. So instead we send the local focus information from
+ * each process to here and then calculate the global focus information. This
+ * local information resides in a `focus target`.
+ *
+ * A focus target indicates that either:
+ *    1. The focused element is a remote browser along with its layer tree ID
+ *    2. The focused element is not scrollable
+ *    3. The focused element is scrollable along with the ViewID's of its
+         scrollable layers
+ *
+ * Using this information we can determine the global focus information by
+ * starting at the focus target of the root layer tree ID and following remote
+ * browsers until we reach a scrollable or non-scrollable focus target.
+ *
+ * # Determinism and sequence numbers
+ *
+ * The focused element in content can be changed within any javascript code. And
+ * javascript can run in response to an event or at any moment from `setTimeout`
+ * and others. This makes it impossible to always have the current focus
+ * information in APZ as it can be changed asynchronously at any moment. If we
+ * don't have the latest focus information, we may incorrectly scroll a target
+ * when we shouldn't.
+ *
+ * A tradeoff is designed here whereby we will maintain deterministic focus
+ * changes for user input, but not for other javascript code. The reasoning
+ * here is that `setTimeout` and others are already non-deterministic and so it
+ * might not be as breaking to web content.
+ *
+ * To maintain deterministic focus changes for a given stream of user inputs, we
+ * invalidate our focus state whenever we receive a user input that may trigger
+ * event listeners. We then attach a new sequence number to these events and
+ * dispatch them to content. Content will then include the latest sequence number
+ * it has processed to every focus update. Using this we can determine whether
+ * any potentially focus changing events have yet to be handled by content.
+ *
+ * Once we have received the latest focus sequence number from content, we know
+ * that all event listeners triggered by user inputs, and their resulting focus
+ * changes, have been processed and so we have a current target that we can use
+ * again.
+ */
+class FocusState final
+{
+public:
+  FocusState();
+
+  /**
+   * The sequence number of the last potentially focus changing event processed
+   * by APZ. This number starts at one and increases monotonically. This number
+   * will never be zero as that is used to catch uninitialized focus sequence
+   * numbers on input events.
+   */
+  uint64_t LastAPZProcessedEvent() const { return mLastAPZProcessedEvent; }
+
+  /**
+   * Whether the current focus state is known to be current or else if an event
+   * has been processed that could change the focus but we have not received an
+   * update with a new confirmed target.
+   */
+  bool IsCurrent() const;
+
+  /**
+   * Notify focus state of a potentially focus changing event. This will
+   * increment the current focus sequence number. The new value can be gotten
+   * from LastAPZProcessedEvent().
+   */
+  void ReceiveFocusChangingEvent();
+
+  /**
+   * Update the internal focus tree and recalculate the global focus target for
+   * a focus target update received from chrome or content.
+   *
+   * @param aRootLayerTreeId the layer tree ID of the root layer for the
+                             parent APZCTreeManager
+   * @param aOriginatingLayersId the layer tree ID that this focus target
+                                 belongs to
+   */
+  void Update(uint64_t aRootLayerTreeId,
+              uint64_t aOriginatingLayersId,
+              const FocusTarget& aTarget);
+
+  /**
+   * Collects a set of the layer tree IDs that we have a focus target for.
+   */
+  std::unordered_set<uint64_t> GetFocusTargetLayerIds() const;
+
+  /**
+   * Removes a focus target by its layer tree ID.
+   */
+  void RemoveFocusTarget(uint64_t aLayersId);
+
+  /**
+   * Gets the scrollable layer that should be horizontally scrolled for a key
+   * event, if any. The returned ScrollableLayerGuid doesn't contain a presShellId,
+   * and so it should not be used in comparisons.
+   *
+   * No scrollable layer is returned if any of the following are true:
+   *   1. We don't have a current focus target
+   *   2. There are event listeners that could change the focus
+   *   3. The target has not been layerized
+   */
+  Maybe<ScrollableLayerGuid> GetHorizontalTarget() const;
+  /**
+   * The same as GetHorizontalTarget() but for vertical scrolling.
+   */
+  Maybe<ScrollableLayerGuid> GetVerticalTarget() const;
+
+  /**
+   * Gets whether it is safe to not increment the focus sequence number for an
+   * unmatched keyboard event.
+   */
+  bool CanIgnoreKeyboardShortcutMisses() const
+  {
+    return IsCurrent() && !mFocusHasKeyEventListeners;
+  }
+
+private:
+  // The set of focus targets received indexed by their layer tree ID
+  std::unordered_map<uint64_t, FocusTarget> mFocusTree;
+
+  // The focus sequence number of the last potentially focus changing event
+  // processed by APZ. This number starts at one and increases monotonically.
+  // We don't worry about wrap around here because at a pace of 100 increments/sec,
+  // it would take 5.85*10^9 years before we would wrap around. This number will
+  // never be zero as that is used to catch uninitialized focus sequence numbers
+  // on input events.
+  uint64_t mLastAPZProcessedEvent;
+  // The focus sequence number last received in a focus update.
+  uint64_t mLastContentProcessedEvent;
+
+  // A flag whether there is a key listener on the event target chain for the
+  // focused element
+  bool mFocusHasKeyEventListeners;
+
+  // The layer tree ID which contains the scrollable frame of the focused element
+  uint64_t mFocusLayersId;
+  // The scrollable layer corresponding to the scrollable frame that is used to
+  // scroll the focused element. This depends on the direction the user is
+  // scrolling.
+  FrameMetrics::ViewID mFocusHorizontalTarget;
+  FrameMetrics::ViewID mFocusVerticalTarget;
+};
+
+} // namespace layers
+} // namespace mozilla
+
+#endif // mozilla_layers_FocusState_h
new file mode 100644
--- /dev/null
+++ b/gfx/layers/apz/src/FocusTarget.cpp
@@ -0,0 +1,154 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "mozilla/layers/FocusTarget.h"
+
+#include "mozilla/dom/EventTarget.h" // for EventTarget
+#include "mozilla/dom/TabParent.h"   // for TabParent
+#include "mozilla/EventDispatcher.h" // for EventDispatcher
+#include "mozilla/layout/RenderFrameParent.h" // For RenderFrameParent
+#include "nsIPresShell.h"  // for nsIPresShell
+#include "nsLayoutUtils.h" // for nsLayoutUtils
+
+using namespace mozilla::dom;
+using namespace mozilla::layout;
+
+namespace mozilla {
+namespace layers {
+
+static already_AddRefed<nsIPresShell>
+GetRetargetEventPresShell(nsIPresShell* aRootPresShell)
+{
+  MOZ_ASSERT(aRootPresShell);
+
+  // Use the last focused window in this PresShell and its
+  // associated PresShell
+  nsCOMPtr<nsPIDOMWindowOuter> window =
+    aRootPresShell->GetFocusedDOMWindowInOurWindow();
+  if (!window) {
+    return nullptr;
+  }
+
+  nsCOMPtr<nsIDocument> retargetEventDoc = window->GetExtantDoc();
+  if (!retargetEventDoc) {
+    return nullptr;
+  }
+
+  nsCOMPtr<nsIPresShell> presShell = retargetEventDoc->GetShell();
+  return presShell.forget();
+}
+
+static bool
+HasListenersForKeyEvents(nsIContent* aContent)
+{
+  if (!aContent) {
+    return false;
+  }
+
+  WidgetEvent event(true, eVoidEvent);
+  nsTArray<EventTarget*> targets;
+  nsresult rv = EventDispatcher::Dispatch(aContent, nullptr, &event, nullptr,
+      nullptr, nullptr, &targets);
+  NS_ENSURE_SUCCESS(rv, false);
+  for (size_t i = 0; i < targets.Length(); i++) {
+    if (targets[i]->HasUntrustedOrNonSystemGroupKeyEventListeners()) {
+      return true;
+    }
+  }
+  return false;
+}
+
+static bool
+IsEditableNode(nsINode* aNode)
+{
+  return aNode && aNode->IsEditable();
+}
+
+FocusTarget::FocusTarget()
+  : mSequenceNumber(0)
+  , mFocusHasKeyEventListeners(false)
+  , mType(FocusTarget::eNone)
+{
+}
+
+FocusTarget::FocusTarget(nsIPresShell* aRootPresShell,
+                         uint64_t aFocusSequenceNumber)
+  : mSequenceNumber(aFocusSequenceNumber)
+  , mFocusHasKeyEventListeners(false)
+{
+  MOZ_ASSERT(aRootPresShell);
+  MOZ_ASSERT(NS_IsMainThread());
+
+  // Key events can be retargeted to a child PresShell when there is an iframe
+  nsCOMPtr<nsIPresShell> presShell = GetRetargetEventPresShell(aRootPresShell);
+
+  // Get the content that should be scrolled for this PresShell, which is
+  // the current focused element or the current DOM selection
+  nsCOMPtr<nsIContent> scrollTarget = presShell->GetContentForScrolling();
+
+  // Collect event listener information so we can track what is potentially focus
+  // changing
+  mFocusHasKeyEventListeners = HasListenersForKeyEvents(scrollTarget);
+
+  // Check if the scroll target is a remote browser
+  if (TabParent* browserParent = TabParent::GetFrom(scrollTarget)) {
+    RenderFrameParent* rfp = browserParent->GetRenderFrame();
+
+    // The globally focused element for scrolling is in a remote layer tree
+    if (rfp) {
+      mType = FocusTarget::eRefLayer;
+      mData.mRefLayerId = rfp->GetLayersId();
+      return;
+    }
+
+    mType = FocusTarget::eNone;
+    return;
+  }
+
+  // If the focus isn't on a remote browser then check for scrollable targets
+  if (IsEditableNode(scrollTarget) ||
+      IsEditableNode(presShell->GetDocument())) {
+    mType = FocusTarget::eNone;
+    return;
+  }
+
+  // Gather the scrollable frames that would be scrolled in each direction
+  // for this scroll target
+  nsIScrollableFrame* horizontal =
+    presShell->GetScrollableFrameToScrollForContent(scrollTarget.get(),
+                                                    nsIPresShell::eHorizontal);
+  nsIScrollableFrame* vertical =
+    presShell->GetScrollableFrameToScrollForContent(scrollTarget.get(),
+                                                    nsIPresShell::eVertical);
+
+  // We might have the globally focused element for scrolling. Gather a ViewID for
+  // the horizontal and vertical scroll targets of this element.
+  mType = FocusTarget::eScrollLayer;
+  mData.mScrollTargets.mHorizontal =
+    nsLayoutUtils::FindIDForScrollableFrame(horizontal);
+  mData.mScrollTargets.mVertical =
+    nsLayoutUtils::FindIDForScrollableFrame(vertical);
+}
+
+bool
+FocusTarget::operator==(const FocusTarget& aRhs) const
+{
+  if (mSequenceNumber != aRhs.mSequenceNumber ||
+      mFocusHasKeyEventListeners != aRhs.mFocusHasKeyEventListeners ||
+      mType != aRhs.mType) {
+    return false;
+  }
+
+  if (mType == FocusTarget::eRefLayer) {
+      return mData.mRefLayerId == aRhs.mData.mRefLayerId;
+  } else if (mType == FocusTarget::eScrollLayer) {
+      return mData.mScrollTargets.mHorizontal == aRhs.mData.mScrollTargets.mHorizontal &&
+             mData.mScrollTargets.mVertical == aRhs.mData.mScrollTargets.mVertical;
+  }
+  return true;
+}
+
+} // namespace layers
+} // namespace mozilla
new file mode 100644
--- /dev/null
+++ b/gfx/layers/apz/src/FocusTarget.h
@@ -0,0 +1,73 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_layers_FocusTarget_h
+#define mozilla_layers_FocusTarget_h
+
+#include <stdint.h> // for int32_t, uint32_t
+
+#include "FrameMetrics.h" // for FrameMetrics::ViewID
+
+class nsIPresShell;
+
+namespace mozilla {
+namespace layers {
+
+/**
+ * This class is used for communicating information about the currently focused
+ * element of a document and the scrollable frames to use when keyboard scrolling
+ * it. It is created on the main thread at paint-time, but is then passed over
+ * IPC to the compositor/APZ code.
+ */
+class FocusTarget final
+{
+public:
+  struct ScrollTargets
+  {
+    FrameMetrics::ViewID mHorizontal;
+    FrameMetrics::ViewID mVertical;
+  };
+
+  enum FocusTargetType
+  {
+    eNone,
+    eRefLayer,
+    eScrollLayer,
+
+    // Used as an upper bound for ContiguousEnumSerializer
+    eSentinel,
+  };
+  union FocusTargetData
+  {
+    uint64_t      mRefLayerId;
+    ScrollTargets mScrollTargets;
+  };
+
+  FocusTarget();
+
+  /**
+   * Construct a focus target for the specified top level PresShell
+   */
+  FocusTarget(nsIPresShell* aRootPresShell,
+              uint64_t aFocusSequenceNumber);
+
+  bool operator==(const FocusTarget& aRhs) const;
+
+public:
+  // The content sequence number recorded at the time of this class's creation
+  uint64_t mSequenceNumber;
+
+  // Whether there are keydown, keypress, or keyup event listeners
+  // in the event target chain of the focused element
+  bool mFocusHasKeyEventListeners;
+
+  FocusTargetType mType;
+  FocusTargetData mData;
+};
+
+} // namespace layers
+} // namespace mozilla
+
+#endif // mozilla_layers_FocusTarget_h
new file mode 100644
--- /dev/null
+++ b/gfx/layers/apz/src/GenericScrollAnimation.cpp
@@ -0,0 +1,104 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set sw=2 ts=8 et tw=80 : */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "GenericScrollAnimation.h"
+
+#include "AsyncPanZoomController.h"
+#include "gfxPrefs.h"
+#include "nsPoint.h"
+
+namespace mozilla {
+namespace layers {
+
+GenericScrollAnimation::GenericScrollAnimation(AsyncPanZoomController& aApzc,
+                                               const nsPoint& aInitialPosition)
+  : AsyncScrollBase(aInitialPosition)
+  , mApzc(aApzc)
+  , mFinalDestination(aInitialPosition)
+  , mForceVerticalOverscroll(false)
+{
+}
+
+void
+GenericScrollAnimation::UpdateDelta(TimeStamp aTime, nsPoint aDelta, const nsSize& aCurrentVelocity)
+{
+  mFinalDestination += aDelta;
+
+  Update(aTime, aCurrentVelocity);
+}
+
+void
+GenericScrollAnimation::UpdateDestination(TimeStamp aTime, nsPoint aDestination, const nsSize& aCurrentVelocity)
+{
+  mFinalDestination = aDestination;
+
+  Update(aTime, aCurrentVelocity);
+}
+
+void
+GenericScrollAnimation::Update(TimeStamp aTime, const nsSize& aCurrentVelocity)
+{
+  if (mIsFirstIteration) {
+    InitializeHistory(aTime);
+  }
+
+  // Clamp the final destination to the scrollable area.
+  CSSPoint clamped = CSSPoint::FromAppUnits(mFinalDestination);
+  clamped.x = mApzc.mX.ClampOriginToScrollableRect(clamped.x);
+  clamped.y = mApzc.mY.ClampOriginToScrollableRect(clamped.y);
+  mFinalDestination = CSSPoint::ToAppUnits(clamped);
+
+  AsyncScrollBase::Update(aTime, mFinalDestination, aCurrentVelocity);
+}
+
+bool
+GenericScrollAnimation::DoSample(FrameMetrics& aFrameMetrics, const TimeDuration& aDelta)
+{
+  TimeStamp now = mApzc.GetFrameTime();
+  CSSToParentLayerScale2D zoom = aFrameMetrics.GetZoom();
+
+  // If the animation is finished, make sure the final position is correct by
+  // using one last displacement. Otherwise, compute the delta via the timing
+  // function as normal.
+  bool finished = IsFinished(now);
+  nsPoint sampledDest = finished
+                        ? mDestination
+                        : PositionAt(now);
+  ParentLayerPoint displacement =
+    (CSSPoint::FromAppUnits(sampledDest) - aFrameMetrics.GetScrollOffset()) * zoom;
+
+  if (finished) {
+    mApzc.mX.SetVelocity(0);
+    mApzc.mY.SetVelocity(0);
+  } else if (!IsZero(displacement)) {
+    // Velocity is measured in ParentLayerCoords / Milliseconds
+    float xVelocity = displacement.x / aDelta.ToMilliseconds();
+    float yVelocity = displacement.y / aDelta.ToMilliseconds();
+    mApzc.mX.SetVelocity(xVelocity);
+    mApzc.mY.SetVelocity(yVelocity);
+  }
+
+  // Note: we ignore overscroll for generic animations.
+  ParentLayerPoint adjustedOffset, overscroll;
+  mApzc.mX.AdjustDisplacement(displacement.x, adjustedOffset.x, overscroll.x);
+  mApzc.mY.AdjustDisplacement(displacement.y, adjustedOffset.y, overscroll.y,
+                              mForceVerticalOverscroll);
+
+  // If we expected to scroll, but there's no more scroll range on either axis,
+  // then end the animation early. Note that the initial displacement could be 0
+  // if the compositor ran very quickly (<1ms) after the animation was created.
+  // When that happens we want to make sure the animation continues.
+  if (!IsZero(displacement) && IsZero(adjustedOffset)) {
+    // Nothing more to do - end the animation.
+    return false;
+  }
+
+  aFrameMetrics.ScrollBy(adjustedOffset / zoom);
+  return !finished;
+}
+
+} // namespace layers
+} // namespace mozilla
new file mode 100644
--- /dev/null
+++ b/gfx/layers/apz/src/GenericScrollAnimation.h
@@ -0,0 +1,47 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set sw=2 ts=8 et 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 mozilla_layers_GenericScrollAnimation_h_
+#define mozilla_layers_GenericScrollAnimation_h_
+
+#include "AsyncPanZoomAnimation.h"
+#include "AsyncScrollBase.h"
+
+namespace mozilla {
+namespace layers {
+
+class AsyncPanZoomController;
+
+class GenericScrollAnimation
+  : public AsyncPanZoomAnimation,
+    public AsyncScrollBase
+{
+public:
+  GenericScrollAnimation(AsyncPanZoomController& aApzc,
+                         const nsPoint& aInitialPosition);
+
+  bool DoSample(FrameMetrics& aFrameMetrics, const TimeDuration& aDelta) override;
+
+  void UpdateDelta(TimeStamp aTime, nsPoint aDelta, const nsSize& aCurrentVelocity);
+  void UpdateDestination(TimeStamp aTime, nsPoint aDestination, const nsSize& aCurrentVelocity);
+
+  CSSPoint GetDestination() const {
+    return CSSPoint::FromAppUnits(mFinalDestination);
+  }
+
+private:
+  void Update(TimeStamp aTime, const nsSize& aCurrentVelocity);
+
+protected:
+  AsyncPanZoomController& mApzc;
+  nsPoint mFinalDestination;
+  bool mForceVerticalOverscroll;
+};
+
+} // namespace layers
+} // namespace mozilla
+
+#endif // mozilla_layers_GenericScrollAnimation_h_
--- a/gfx/layers/apz/src/InputBlockState.cpp
+++ b/gfx/layers/apz/src/InputBlockState.cpp
@@ -865,10 +865,15 @@ TouchBlockState::UpdateSlopState(const M
 }
 
 uint32_t
 TouchBlockState::GetActiveTouchCount() const
 {
   return mTouchCounter.GetActiveTouchCount();
 }
 
+KeyboardBlockState::KeyboardBlockState(const RefPtr<AsyncPanZoomController>& aTargetApzc)
+  : InputBlockState(aTargetApzc, true)
+{
+}
+
 } // namespace layers
 } // namespace mozilla
--- a/gfx/layers/apz/src/InputBlockState.h
+++ b/gfx/layers/apz/src/InputBlockState.h
@@ -22,16 +22,17 @@ namespace layers {
 
 class AsyncPanZoomController;
 class OverscrollHandoffChain;
 class CancelableBlockState;
 class TouchBlockState;
 class WheelBlockState;
 class DragBlockState;
 class PanGestureBlockState;
+class KeyboardBlockState;
 
 /**
  * A base class that stores state common to various input blocks.
  * Note that the InputBlockState constructor acquires the tree lock, so callers
  * from inside AsyncPanZoomController should ensure that the APZC lock is not
  * held.
  */
 class InputBlockState : public RefCounted<InputBlockState>
@@ -63,16 +64,19 @@ public:
     return nullptr;
   }
   virtual DragBlockState* AsDragBlock() {
     return nullptr;
   }
   virtual PanGestureBlockState* AsPanGestureBlock() {
     return nullptr;
   }
+  virtual KeyboardBlockState* AsKeyboardBlock() {
+    return nullptr;
+  }
 
   virtual bool SetConfirmedTargetApzc(const RefPtr<AsyncPanZoomController>& aTargetApzc,
                                       TargetConfirmationState aState,
                                       InputData* aFirstInput);
   const RefPtr<AsyncPanZoomController>& GetTargetApzc() const;
   const RefPtr<const OverscrollHandoffChain>& GetOverscrollHandoffChain() const;
   uint64_t GetBlockId() const;
 
@@ -481,12 +485,29 @@ private:
   bool mDuringFastFling;
   bool mSingleTapOccurred;
   bool mInSlop;
   ScreenIntPoint mSlopOrigin;
   // A reference to the InputQueue's touch counter
   TouchCounter& mTouchCounter;
 };
 
+/**
+ * This class represents a set of keyboard inputs targeted at the same Apzc.
+ */
+class KeyboardBlockState : public InputBlockState
+{
+public:
+  explicit KeyboardBlockState(const RefPtr<AsyncPanZoomController>& aTargetApzc);
+
+  KeyboardBlockState* AsKeyboardBlock() override {
+    return this;
+  }
+
+  bool MustStayActive() override {
+    return false;
+  }
+};
+
 } // namespace layers
 } // namespace mozilla
 
 #endif // mozilla_layers_InputBlockState_h
--- a/gfx/layers/apz/src/InputQueue.cpp
+++ b/gfx/layers/apz/src/InputQueue.cpp
@@ -51,16 +51,24 @@ InputQueue::ReceiveInputEvent(const RefP
       return ReceivePanGestureInput(aTarget, aTargetConfirmed, event, aOutInputBlockId);
     }
 
     case MOUSE_INPUT: {
       const MouseInput& event = aEvent.AsMouseInput();
       return ReceiveMouseInput(aTarget, aTargetConfirmed, event, aOutInputBlockId);
     }
 
+    case KEYBOARD_INPUT: {
+      // Every keyboard input must have a confirmed target
+      MOZ_ASSERT(aTarget && aTargetConfirmed);
+
+      const KeyboardInput& event = aEvent.AsKeyboardInput();
+      return ReceiveKeyboardInput(aTarget, event, aOutInputBlockId);
+    }
+
     default:
       // The return value for non-touch input is only used by tests, so just pass
       // through the return value for now. This can be changed later if needed.
       // TODO (bug 1098430): we will eventually need to have smarter handling for
       // non-touch events as well.
       return aTarget->HandleInputEvent(aEvent, aTarget->GetTransformToThis());
   }
 }
@@ -263,16 +271,49 @@ InputQueue::ReceiveScrollWheelInput(cons
   // |aEvent|.
   block->Update(mQueuedInputs.LastElement()->Input()->AsScrollWheelInput());
 
   ProcessQueue();
 
   return nsEventStatus_eConsumeDoDefault;
 }
 
+nsEventStatus
+InputQueue::ReceiveKeyboardInput(const RefPtr<AsyncPanZoomController>& aTarget,
+                                 const KeyboardInput& aEvent,
+                                 uint64_t* aOutInputBlockId) {
+  KeyboardBlockState* block = mActiveKeyboardBlock.get();
+
+  // If the block is targeting a different Apzc than this keyboard event then
+  // we'll create a new input block
+  if (block && block->GetTargetApzc() != aTarget) {
+    block = nullptr;
+  }
+
+  if (!block) {
+    block = new KeyboardBlockState(aTarget);
+    INPQ_LOG("started new keyboard block %p id %" PRIu64 " for target %p\n",
+        block, block->GetBlockId(), aTarget.get());
+
+    mActiveKeyboardBlock = block;
+  } else {
+    INPQ_LOG("received new event in block %p\n", block);
+  }
+
+  if (aOutInputBlockId) {
+    *aOutInputBlockId = block->GetBlockId();
+  }
+
+  mQueuedInputs.AppendElement(MakeUnique<QueuedInput>(aEvent, *block));
+
+  ProcessQueue();
+
+  return nsEventStatus_eConsumeNoDefault;
+}
+
 static bool
 CanScrollTargetHorizontally(const PanGestureInput& aInitialEvent,
                             PanGestureBlockState* aBlock)
 {
   PanGestureInput horizontalComponent = aInitialEvent;
   horizontalComponent.mPanDisplacement.y = 0;
   RefPtr<AsyncPanZoomController> horizontallyScrollableAPZC =
     aBlock->GetOverscrollHandoffChain()->FindFirstScrollable(horizontalComponent);
@@ -456,16 +497,23 @@ InputQueue::GetCurrentDragBlock() const
 
 PanGestureBlockState*
 InputQueue::GetCurrentPanGestureBlock() const
 {
   InputBlockState* block = GetCurrentBlock();
   return block ? block->AsPanGestureBlock() : mActivePanGestureBlock.get();
 }
 
+KeyboardBlockState*
+InputQueue::GetCurrentKeyboardBlock() const
+{
+  InputBlockState* block = GetCurrentBlock();
+  return block ? block->AsKeyboardBlock() : mActiveKeyboardBlock.get();
+}
+
 WheelBlockState*
 InputQueue::GetActiveWheelTransaction() const
 {
   WheelBlockState* block = mActiveWheelBlock.get();
   if (!block || !block->InTransaction()) {
     return nullptr;
   }
   return block;
--- a/gfx/layers/apz/src/InputQueue.h
+++ b/gfx/layers/apz/src/InputQueue.h
@@ -25,16 +25,17 @@ namespace layers {
 
 class AsyncPanZoomController;
 class InputBlockState;
 class CancelableBlockState;
 class TouchBlockState;
 class WheelBlockState;
 class DragBlockState;
 class PanGestureBlockState;
+class KeyboardBlockState;
 class AsyncDragMetrics;
 class QueuedInput;
 
 /**
  * This class stores incoming input events, associated with "input blocks", until
  * they are ready for handling.
  */
 class InputQueue {
@@ -101,16 +102,17 @@ public:
    * mActiveXXXBlock field of the corresponding input type to see if there is
    * a depleted but still active input block, and returns that if found. These
    * functions may return null if no block is found.
    */
   TouchBlockState* GetCurrentTouchBlock() const;
   WheelBlockState* GetCurrentWheelBlock() const;
   DragBlockState* GetCurrentDragBlock() const;
   PanGestureBlockState* GetCurrentPanGestureBlock() const;
+  KeyboardBlockState* GetCurrentKeyboardBlock() const;
   /**
    * Returns true iff the pending block at the head of the queue is a touch
    * block and is ready for handling.
    */
   bool HasReadyTouchBlock() const;
   /**
    * If there is an active wheel transaction, returns the WheelBlockState
    * representing the transaction. Otherwise, returns null. "Active" in this
@@ -164,16 +166,19 @@ private:
   nsEventStatus ReceiveScrollWheelInput(const RefPtr<AsyncPanZoomController>& aTarget,
                                         bool aTargetConfirmed,
                                         const ScrollWheelInput& aEvent,
                                         uint64_t* aOutInputBlockId);
   nsEventStatus ReceivePanGestureInput(const RefPtr<AsyncPanZoomController>& aTarget,
                                         bool aTargetConfirmed,
                                         const PanGestureInput& aEvent,
                                         uint64_t* aOutInputBlockId);
+  nsEventStatus ReceiveKeyboardInput(const RefPtr<AsyncPanZoomController>& aTarget,
+                                     const KeyboardInput& aEvent,
+                                     uint64_t* aOutInputBlockId);
 
   /**
    * Helper function that searches mQueuedInputs for the first block matching
    * the given id, and returns it. If |aOutFirstInput| is non-null, it is
    * populated with a pointer to the first input in mQueuedInputs that
    * corresponds to the block, or null if no such input was found. Note that
    * even if there are no inputs in mQueuedInputs, this function can return
    * non-null if the block id provided matches one of the depleted-but-still-
@@ -197,16 +202,17 @@ private:
   // "active" in the sense that new inputs of that type are associated with
   // them. Note that these pointers may be null if no inputs of the type have
   // arrived, or if the inputs for the type formed a complete block that was
   // then discarded.
   RefPtr<TouchBlockState> mActiveTouchBlock;
   RefPtr<WheelBlockState> mActiveWheelBlock;
   RefPtr<DragBlockState> mActiveDragBlock;
   RefPtr<PanGestureBlockState> mActivePanGestureBlock;
+  RefPtr<KeyboardBlockState> mActiveKeyboardBlock;
 
   // The APZC to which the last event was delivered
   RefPtr<AsyncPanZoomController> mLastActiveApzc;
 
   // Track touches so we know when to clear mLastActiveApzc
   TouchCounter mTouchCounter;
 
   // Track mouse inputs so we know if we're in a drag or not
new file mode 100644
--- /dev/null
+++ b/gfx/layers/apz/src/KeyboardMap.cpp
@@ -0,0 +1,161 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "mozilla/layers/KeyboardMap.h"
+
+#include "mozilla/TextEvents.h" // for IgnoreModifierState, ShortcutKeyCandidate
+
+namespace mozilla {
+namespace layers {
+
+KeyboardShortcut::KeyboardShortcut()
+{
+}
+
+KeyboardShortcut::KeyboardShortcut(KeyboardInput::KeyboardEventType aEventType,
+                                   uint32_t aKeyCode,
+                                   uint32_t aCharCode,
+                                   Modifiers aModifiers,
+                                   Modifiers aModifiersMask,
+                                   const KeyboardScrollAction& aAction)
+  : mAction(aAction)
+  , mKeyCode(aKeyCode)
+  , mCharCode(aCharCode)
+  , mModifiers(aModifiers)
+  , mModifiersMask(aModifiersMask)
+  , mEventType(aEventType)
+  , mDispatchToContent(false)
+{
+}
+
+KeyboardShortcut::KeyboardShortcut(KeyboardInput::KeyboardEventType aEventType,
+                                   uint32_t aKeyCode,
+                                   uint32_t aCharCode,
+                                   Modifiers aModifiers,
+                                   Modifiers aModifiersMask)
+  : mKeyCode(aKeyCode)
+  , mCharCode(aCharCode)
+  , mModifiers(aModifiers)
+  , mModifiersMask(aModifiersMask)
+  , mEventType(aEventType)
+  , mDispatchToContent(true)
+{
+}
+
+bool
+KeyboardShortcut::Matches(const KeyboardInput& aInput,
+                          const IgnoreModifierState& aIgnore,
+                          uint32_t aOverrideCharCode) const
+{
+  return mEventType == aInput.mType &&
+         MatchesKey(aInput, aOverrideCharCode) &&
+         MatchesModifiers(aInput, aIgnore);
+}
+
+bool
+KeyboardShortcut::MatchesKey(const KeyboardInput& aInput,
+                             uint32_t aOverrideCharCode) const
+{
+  // Compare by the key code if we have one
+  if (!mCharCode) {
+    return mKeyCode == aInput.mKeyCode;
+  }
+
+  // We are comparing by char code
+  uint32_t charCode;
+
+  // If we are comparing against a shortcut candidate then we might
+  // have an override char code
+  if (aOverrideCharCode) {
+    charCode = aOverrideCharCode;
+  } else {
+    charCode = aInput.mCharCode;
+  }
+
+  // Both char codes must be in lowercase to compare correctly
+  if (IS_IN_BMP(charCode)) {
+    charCode = ToLowerCase(static_cast<char16_t>(charCode));
+  }
+
+  return mCharCode == charCode;
+}
+
+bool
+KeyboardShortcut::MatchesModifiers(const KeyboardInput& aInput,
+                                   const IgnoreModifierState& aIgnore) const
+{
+  Modifiers modifiersMask = mModifiersMask;
+
+  // If we are ignoring Shift or OS, then unset that part of the mask
+  if (aIgnore.mOS) {
+    modifiersMask &= ~MODIFIER_OS;
+  }
+  if (aIgnore.mShift) {
+    modifiersMask &= ~MODIFIER_SHIFT;
+  }
+
+  // Mask off the modifiers we are ignoring from the keyboard input
+  return (aInput.modifiers & modifiersMask) == mModifiers;
+}
+
+KeyboardMap::KeyboardMap(nsTArray<KeyboardShortcut>&& aShortcuts)
+  : mShortcuts(aShortcuts)
+{
+}
+
+KeyboardMap::KeyboardMap()
+{
+}
+
+Maybe<KeyboardShortcut>
+KeyboardMap::FindMatch(const KeyboardInput& aEvent) const
+{
+  // If there are no shortcut candidates, then just search with with the
+  // keyboard input
+  if (aEvent.mShortcutCandidates.IsEmpty()) {
+    return FindMatchInternal(aEvent, IgnoreModifierState());
+  }
+
+  // Otherwise do a search with each shortcut candidate in order
+  for (auto& key : aEvent.mShortcutCandidates) {
+    IgnoreModifierState ignoreModifierState;
+    ignoreModifierState.mShift = key.mIgnoreShift;
+
+    auto match = FindMatchInternal(aEvent, ignoreModifierState, key.mCharCode);
+    if (match) {
+      return match;
+    }
+  }
+  return Nothing();
+}
+
+Maybe<KeyboardShortcut>
+KeyboardMap::FindMatchInternal(const KeyboardInput& aEvent,
+                               const IgnoreModifierState& aIgnore,
+                               uint32_t aOverrideCharCode) const
+{
+  for (auto& shortcut : mShortcuts) {
+    if (shortcut.Matches(aEvent, aIgnore, aOverrideCharCode)) {
+      return Some(shortcut);
+    }
+  }
+
+#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 (!aIgnore.mOS && (aEvent.modifiers & MODIFIER_OS)) {
+    IgnoreModifierState ignoreModifierState(aIgnore);
+    ignoreModifierState.mOS = true;
+    return FindMatchInternal(aEvent, ignoreModifierState, aOverrideCharCode);
+  }
+#endif
+
+  return Nothing();
+}
+
+} // namespace layers
+} // namespace mozilla
new file mode 100644
--- /dev/null
+++ b/gfx/layers/apz/src/KeyboardMap.h
@@ -0,0 +1,116 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_layers_KeyboardMap_h
+#define mozilla_layers_KeyboardMap_h
+
+#include <stdint.h> // for uint32_t
+
+#include "InputData.h"          // for KeyboardInput
+#include "nsIScrollableFrame.h" // for nsIScrollableFrame::ScrollUnit
+#include "nsTArray.h"           // for nsTArray
+#include "mozilla/Maybe.h"      // for mozilla::Maybe
+#include "KeyboardScrollAction.h" // for KeyboardScrollAction
+
+namespace mozilla {
+
+struct IgnoreModifierState;
+
+namespace layers {
+
+class KeyboardMap;
+
+/**
+ * This class is an off main-thread <xul:handler> for scrolling commands.
+ */
+class KeyboardShortcut final
+{
+public:
+  KeyboardShortcut();
+
+  /**
+   * Create a keyboard shortcut that when matched can be handled by executing
+   * the specified keyboard action.
+   */
+  KeyboardShortcut(KeyboardInput::KeyboardEventType aEventType,
+                   uint32_t aKeyCode,
+                   uint32_t aCharCode,
+                   Modifiers aModifiers,
+                   Modifiers aModifiersMask,
+                   const KeyboardScrollAction& aAction);
+
+  /**
+   * Create a keyboard shortcut that when matched should be handled by ignoring
+   * the keyboard event and dispatching it to content.
+   */
+  KeyboardShortcut(KeyboardInput::KeyboardEventType aEventType,
+                   uint32_t aKeyCode,
+                   uint32_t aCharCode,
+                   Modifiers aModifiers,
+                   Modifiers aModifiersMask);
+
+protected:
+  friend mozilla::layers::KeyboardMap;
+
+  bool Matches(const KeyboardInput& aInput,
+               const IgnoreModifierState& aIgnore,
+               uint32_t aOverrideCharCode = 0) const;
+
+private:
+  bool MatchesKey(const KeyboardInput& aInput,
+                  uint32_t aOverrideCharCode) const;
+  bool MatchesModifiers(const KeyboardInput& aInput,
+                        const IgnoreModifierState& aIgnore) const;
+
+public:
+  // The action to perform when this shortcut is matched,
+  // and not flagged to be dispatched to content
+  KeyboardScrollAction mAction;
+
+  // Only one of mKeyCode or mCharCode may be non-zero
+  // whichever one is non-zero is the one to compare when matching
+  uint32_t mKeyCode;
+  uint32_t mCharCode;
+
+  // The modifiers that must be active for this shortcut
+  Modifiers mModifiers;
+  // The modifiers to compare when matching this shortcut
+  Modifiers mModifiersMask;
+
+  // The type of keyboard event to match against
+  KeyboardInput::KeyboardEventType mEventType;
+
+  // Whether events matched by this must be dispatched to content
+  bool mDispatchToContent;
+};
+
+/**
+ * A keyboard map is an off main-thread <xul:binding> for scrolling commands.
+ */
+class KeyboardMap final
+{
+public:
+  KeyboardMap();
+  explicit KeyboardMap(nsTArray<KeyboardShortcut>&& aShortcuts);
+
+  const nsTArray<KeyboardShortcut>& Shortcuts() const { return mShortcuts; }
+
+  /**
+   * Search through the internal list of shortcuts for a match for the input event
+   */
+  Maybe<KeyboardShortcut> FindMatch(const KeyboardInput& aEvent) const;
+
+private:
+  Maybe<KeyboardShortcut> FindMatchInternal(const KeyboardInput& aEvent,
+                                            const IgnoreModifierState& aIgnore,
+                                            uint32_t aOverrideCharCode = 0) const;
+
+  nsTArray<KeyboardShortcut> mShortcuts;
+};
+
+} // namespace layers
+} // namespace mozilla
+
+#endif // mozilla_layers_KeyboardMap_h
new file mode 100644
--- /dev/null
+++ b/gfx/layers/apz/src/KeyboardScrollAction.cpp
@@ -0,0 +1,45 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "mozilla/layers/KeyboardScrollAction.h"
+
+namespace mozilla {
+namespace layers {
+
+/* static */ nsIScrollableFrame::ScrollUnit
+KeyboardScrollAction::GetScrollUnit(KeyboardScrollAction::KeyboardScrollActionType aDeltaType)
+{
+  switch (aDeltaType) {
+    case KeyboardScrollAction::eScrollCharacter:
+      return nsIScrollableFrame::LINES;
+    case KeyboardScrollAction::eScrollLine:
+      return nsIScrollableFrame::LINES;
+    case KeyboardScrollAction::eScrollPage:
+      return nsIScrollableFrame::PAGES;
+    case KeyboardScrollAction::eScrollComplete:
+      return nsIScrollableFrame::WHOLE;
+    case KeyboardScrollAction::eSentinel:
+      MOZ_ASSERT_UNREACHABLE("Invalid KeyboardScrollActionType.");
+      return nsIScrollableFrame::WHOLE;
+  }
+
+  // Silence an overzealous warning
+  return nsIScrollableFrame::WHOLE;
+}
+
+KeyboardScrollAction::KeyboardScrollAction()
+  : mType(KeyboardScrollAction::eScrollCharacter)
+  , mForward(false)
+{
+}
+
+KeyboardScrollAction::KeyboardScrollAction(KeyboardScrollActionType aType, bool aForward)
+  : mType(aType)
+  , mForward(aForward)
+{
+}
+
+} // namespace layers
+} // namespace mozilla
new file mode 100644
--- /dev/null
+++ b/gfx/layers/apz/src/KeyboardScrollAction.h
@@ -0,0 +1,46 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_layers_KeyboardScrollAction_h
+#define mozilla_layers_KeyboardScrollAction_h
+
+#include "nsIScrollableFrame.h" // for nsIScrollableFrame::ScrollUnit
+
+namespace mozilla {
+namespace layers {
+
+/**
+ * This class represents a scrolling action to be performed on a scrollable layer.
+ */
+struct KeyboardScrollAction final
+{
+public:
+  enum KeyboardScrollActionType : uint8_t
+  {
+    eScrollCharacter,
+    eScrollLine,
+    eScrollPage,
+    eScrollComplete,
+
+    // Used as an upper bound for ContiguousEnumSerializer
+    eSentinel,
+  };
+
+  static nsIScrollableFrame::ScrollUnit
+  GetScrollUnit(KeyboardScrollActionType aDeltaType);
+
+  KeyboardScrollAction();
+  KeyboardScrollAction(KeyboardScrollActionType aType, bool aForward);
+
+  // The type of scroll to perform for this action
+  KeyboardScrollActionType mType;
+  // Whether to scroll forward or backward along the axis of this action type
+  bool mForward;
+};
+
+} // namespace layers
+} // namespace mozilla
+
+#endif // mozilla_layers_KeyboardScrollAction_h
new file mode 100644
--- /dev/null
+++ b/gfx/layers/apz/src/KeyboardScrollAnimation.cpp
@@ -0,0 +1,47 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set sw=2 ts=8 et tw=80 : */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "KeyboardScrollAnimation.h"
+
+#include "gfxPrefs.h"
+
+namespace mozilla {
+namespace layers {
+
+KeyboardScrollAnimation::KeyboardScrollAnimation(AsyncPanZoomController& aApzc,
+                                                 const nsPoint& aInitialPosition,
+                                                 KeyboardScrollAction::KeyboardScrollActionType aType)
+  : GenericScrollAnimation(aApzc, aInitialPosition)
+{
+  switch (aType) {
+    case KeyboardScrollAction::eScrollCharacter:
+    case KeyboardScrollAction::eScrollLine: {
+      mOriginMaxMS = clamped(gfxPrefs::LineSmoothScrollMaxDurationMs(), 0, 10000);
+      mOriginMinMS = clamped(gfxPrefs::LineSmoothScrollMinDurationMs(), 0, mOriginMaxMS);
+      break;
+    }
+    case KeyboardScrollAction::eScrollPage: {
+      mOriginMaxMS = clamped(gfxPrefs::PageSmoothScrollMaxDurationMs(), 0, 10000);
+      mOriginMinMS = clamped(gfxPrefs::PageSmoothScrollMinDurationMs(), 0, mOriginMaxMS);
+      break;
+    }
+    case KeyboardScrollAction::eScrollComplete: {
+      mOriginMaxMS = clamped(gfxPrefs::OtherSmoothScrollMaxDurationMs(), 0, 10000);
+      mOriginMinMS = clamped(gfxPrefs::OtherSmoothScrollMinDurationMs(), 0, mOriginMaxMS);
+      break;
+    }
+    case KeyboardScrollAction::eSentinel: {
+      MOZ_ASSERT_UNREACHABLE("Invalid KeyboardScrollActionType.");
+    }
+  }
+
+  // The pref is 100-based int percentage, while mIntervalRatio is 1-based ratio
+  mIntervalRatio = ((double)gfxPrefs::SmoothScrollDurationToIntervalRatio()) / 100.0;
+  mIntervalRatio = std::max(1.0, mIntervalRatio);
+}
+
+} // namespace layers
+} // namespace mozilla
new file mode 100644
--- /dev/null
+++ b/gfx/layers/apz/src/KeyboardScrollAnimation.h
@@ -0,0 +1,34 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set sw=2 ts=8 et 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 mozilla_layers_KeyboardScrollAnimation_h_
+#define mozilla_layers_KeyboardScrollAnimation_h_
+
+#include "GenericScrollAnimation.h"
+#include "mozilla/layers/KeyboardMap.h"
+
+namespace mozilla {
+namespace layers {
+
+class AsyncPanZoomController;
+
+class KeyboardScrollAnimation
+  : public GenericScrollAnimation
+{
+public:
+  KeyboardScrollAnimation(AsyncPanZoomController& aApzc,
+                          const nsPoint& aInitialPosition,
+                          KeyboardScrollAction::KeyboardScrollActionType aType);
+
+  KeyboardScrollAnimation* AsKeyboardScrollAnimation() override {
+    return this;
+  }
+};
+
+} // namespace layers
+} // namespace mozilla
+
+#endif // mozilla_layers_KeyboardScrollAnimation_h_
--- a/gfx/layers/apz/src/QueuedInput.cpp
+++ b/gfx/layers/apz/src/QueuedInput.cpp
@@ -33,16 +33,22 @@ QueuedInput::QueuedInput(const MouseInpu
 }
 
 QueuedInput::QueuedInput(const PanGestureInput& aInput, PanGestureBlockState& aBlock)
   : mInput(MakeUnique<PanGestureInput>(aInput))
   , mBlock(&aBlock)
 {
 }
 
+QueuedInput::QueuedInput(const KeyboardInput& aInput, KeyboardBlockState& aBlock)
+  : mInput(MakeUnique<KeyboardInput>(aInput))
+  , mBlock(&aBlock)
+{
+}
+
 InputData*
 QueuedInput::Input()
 {
   return mInput.get();
 }
 
 InputBlockState*
 QueuedInput::Block()
--- a/gfx/layers/apz/src/QueuedInput.h
+++ b/gfx/layers/apz/src/QueuedInput.h
@@ -12,37 +12,40 @@
 
 namespace mozilla {
 
 class InputData;
 class MultiTouchInput;
 class ScrollWheelInput;
 class MouseInput;
 class PanGestureInput;
+class KeyboardInput;
 
 namespace layers {
 
 class InputBlockState;
 class TouchBlockState;
 class WheelBlockState;
 class DragBlockState;
 class PanGestureBlockState;
+class KeyboardBlockState;
 
 /**
  * This lightweight class holds a pointer to an input event that has not yet
  * been completely processed, along with the input block that the input event
  * is associated with.
  */
 class QueuedInput
 {
 public:
   QueuedInput(const MultiTouchInput& aInput, TouchBlockState& aBlock);
   QueuedInput(const ScrollWheelInput& aInput, WheelBlockState& aBlock);
   QueuedInput(const MouseInput& aInput, DragBlockState& aBlock);
   QueuedInput(const PanGestureInput& aInput, PanGestureBlockState& aBlock);
+  QueuedInput(const KeyboardInput& aInput, KeyboardBlockState& aBlock);
 
   InputData* Input();
   InputBlockState* Block();
 
 private:
   // A copy of the input event that is provided to the constructor. This must
   // be non-null, and is owned by this QueuedInput instance (hence the
   // UniquePtr).
--- a/gfx/layers/apz/src/WheelScrollAnimation.cpp
+++ b/gfx/layers/apz/src/WheelScrollAnimation.cpp
@@ -11,93 +11,21 @@
 #include "nsPoint.h"
 
 namespace mozilla {
 namespace layers {
 
 WheelScrollAnimation::WheelScrollAnimation(AsyncPanZoomController& aApzc,
                                            const nsPoint& aInitialPosition,
                                            ScrollWheelInput::ScrollDeltaType aDeltaType)
-  : AsyncScrollBase(aInitialPosition)
-  , mApzc(aApzc)
-  , mFinalDestination(aInitialPosition)
-  , mDeltaType(aDeltaType)
-{
-}
-
-void
-WheelScrollAnimation::Update(TimeStamp aTime, nsPoint aDelta, const nsSize& aCurrentVelocity)
+  : GenericScrollAnimation(aApzc, aInitialPosition)
 {
-  InitPreferences(aTime);
-
-  mFinalDestination += aDelta;
-
-  // Clamp the final destination to the scrollable area.
-  CSSPoint clamped = CSSPoint::FromAppUnits(mFinalDestination);
-  clamped.x = mApzc.mX.ClampOriginToScrollableRect(clamped.x);
-  clamped.y = mApzc.mY.ClampOriginToScrollableRect(clamped.y);
-  mFinalDestination = CSSPoint::ToAppUnits(clamped);
-
-  AsyncScrollBase::Update(aTime, mFinalDestination, aCurrentVelocity);
-}
-
-bool
-WheelScrollAnimation::DoSample(FrameMetrics& aFrameMetrics, const TimeDuration& aDelta)
-{
-  TimeStamp now = mApzc.GetFrameTime();
-  CSSToParentLayerScale2D zoom = aFrameMetrics.GetZoom();
-
-  // If the animation is finished, make sure the final position is correct by
-  // using one last displacement. Otherwise, compute the delta via the timing
-  // function as normal.
-  bool finished = IsFinished(now);
-  nsPoint sampledDest = finished
-                        ? mDestination
-                        : PositionAt(now);
-  ParentLayerPoint displacement =
-    (CSSPoint::FromAppUnits(sampledDest) - aFrameMetrics.GetScrollOffset()) * zoom;
+  mForceVerticalOverscroll = !mApzc.mScrollMetadata.AllowVerticalScrollWithWheel();
 
-  if (finished) {
-    mApzc.mX.SetVelocity(0);
-    mApzc.mY.SetVelocity(0);
-  } else if (!IsZero(displacement)) {
-    // Velocity is measured in ParentLayerCoords / Milliseconds
-    float xVelocity = displacement.x / aDelta.ToMilliseconds();
-    float yVelocity = displacement.y / aDelta.ToMilliseconds();
-    mApzc.mX.SetVelocity(xVelocity);
-    mApzc.mY.SetVelocity(yVelocity);
-  }
-
-  // Note: we ignore overscroll for wheel animations.
-  ParentLayerPoint adjustedOffset, overscroll;
-  mApzc.mX.AdjustDisplacement(displacement.x, adjustedOffset.x, overscroll.x);
-  mApzc.mY.AdjustDisplacement(displacement.y, adjustedOffset.y, overscroll.y,
-                              !mApzc.mScrollMetadata.AllowVerticalScrollWithWheel());
-
-  // If we expected to scroll, but there's no more scroll range on either axis,
-  // then end the animation early. Note that the initial displacement could be 0
-  // if the compositor ran very quickly (<1ms) after the animation was created.
-  // When that happens we want to make sure the animation continues.
-  if (!IsZero(displacement) && IsZero(adjustedOffset)) {
-    // Nothing more to do - end the animation.
-    return false;
-  }
-
-  aFrameMetrics.ScrollBy(adjustedOffset / zoom);
-  return !finished;
-}
-
-void
-WheelScrollAnimation::InitPreferences(TimeStamp aTime)
-{
-  if (!mIsFirstIteration) {
-    return;
-  }
-
-  switch (mDeltaType) {
+  switch (aDeltaType) {
   case ScrollWheelInput::SCROLLDELTA_PAGE:
     mOriginMaxMS = clamped(gfxPrefs::PageSmoothScrollMaxDurationMs(), 0, 10000);
     mOriginMinMS = clamped(gfxPrefs::PageSmoothScrollMinDurationMs(), 0, mOriginMaxMS);
     break;
   case ScrollWheelInput::SCROLLDELTA_PIXEL:
     mOriginMaxMS = clamped(gfxPrefs::PixelSmoothScrollMaxDurationMs(), 0, 10000);
     mOriginMinMS = clamped(gfxPrefs::PixelSmoothScrollMinDurationMs(), 0, mOriginMaxMS);
     break;
@@ -107,14 +35,12 @@ WheelScrollAnimation::InitPreferences(Ti
     mOriginMaxMS = clamped(gfxPrefs::WheelSmoothScrollMaxDurationMs(), 0, 10000);
     mOriginMinMS = clamped(gfxPrefs::WheelSmoothScrollMinDurationMs(), 0, mOriginMaxMS);
     break;
   }
 
   // The pref is 100-based int percentage, while mIntervalRatio is 1-based ratio
   mIntervalRatio = ((double)gfxPrefs::SmoothScrollDurationToIntervalRatio()) / 100.0;
   mIntervalRatio = std::max(1.0, mIntervalRatio);
-
-  InitializeHistory(aTime);
 }
 
 } // namespace layers
 } // namespace mozilla
--- a/gfx/layers/apz/src/WheelScrollAnimation.h
+++ b/gfx/layers/apz/src/WheelScrollAnimation.h
@@ -2,50 +2,33 @@
 /* vim: set sw=2 ts=8 et 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 mozilla_layers_WheelScrollAnimation_h_
 #define mozilla_layers_WheelScrollAnimation_h_
 
-#include "AsyncPanZoomAnimation.h"
-#include "AsyncScrollBase.h"
+#include "GenericScrollAnimation.h"
 #include "InputData.h"
 
 namespace mozilla {
 namespace layers {
 
 class AsyncPanZoomController;
 
 class WheelScrollAnimation
-  : public AsyncPanZoomAnimation,
-    public AsyncScrollBase
+  : public GenericScrollAnimation
 {
 public:
   WheelScrollAnimation(AsyncPanZoomController& aApzc,
                        const nsPoint& aInitialPosition,
                        ScrollWheelInput::ScrollDeltaType aDeltaType);
 
-  bool DoSample(FrameMetrics& aFrameMetrics, const TimeDuration& aDelta) override;
-  void Update(TimeStamp aTime, nsPoint aDelta, const nsSize& aCurrentVelocity);
-
   WheelScrollAnimation* AsWheelScrollAnimation() override {
     return this;
   }
-
-  CSSPoint GetDestination() const {
-    return CSSPoint::FromAppUnits(mFinalDestination);
-  }
-
-private:
-  void InitPreferences(TimeStamp aTime);
-
-private:
-  AsyncPanZoomController& mApzc;
-  nsPoint mFinalDestination;
-  ScrollWheelInput::ScrollDeltaType mDeltaType;
 };
 
 } // namespace layers
 } // namespace mozilla
 
 #endif // mozilla_layers_WheelScrollAnimation_h_
--- a/gfx/layers/apz/util/ScrollInputMethods.h
+++ b/gfx/layers/apz/util/ScrollInputMethods.h
@@ -46,16 +46,26 @@ enum class ScrollInputMethod {
   MainThreadScrollbarButtonClick,  // clicking the buttons at the ends of the
                                    // scrollback track
   MainThreadScrollbarTrackClick,   // clicking the scrollbar track above or
                                    // below the thumb
 
   // Autoscrolling
   MainThreadAutoscrolling,    // autoscrolling
 
+  // Async Keyboard
+  ApzScrollLine,       // line scrolling
+                       // (generally triggered by up/down arrow keys)
+  ApzScrollCharacter,  // character scrolling
+                       // (generally triggered by left/right arrow keys)
+  ApzScrollPage,       // page scrolling
+                       // (generally triggered by PageUp/PageDown keys)
+  ApzCompleteScroll,   // scrolling to the end of the scroll range
+                       // (generally triggered by Home/End keys)
+
   // New input methods can be added at the end, up to a maximum of 64.
   // They should only be added at the end, to preserve the numerical values
   // of the existing enumerators.
 };
 
 } // namespace layers
 } // namespace mozilla
 
--- a/gfx/layers/basic/BasicDisplayItemLayer.cpp
+++ b/gfx/layers/basic/BasicDisplayItemLayer.cpp
@@ -12,16 +12,17 @@
 #include "gfx2DGlue.h"
 #include "mozilla/mozalloc.h"           // for operator new
 #include "nsCOMPtr.h"                   // for already_AddRefed
 #include "nsDebug.h"                    // for NS_ASSERTION
 #include "nsISupportsImpl.h"            // for Layer::AddRef, etc
 #include "nsRect.h"                     // for mozilla::gfx::IntRect
 #include "nsRegion.h"                   // for nsIntRegion
 #include "mozilla/gfx/PathHelpers.h"
+#include "mozilla/gfx/Helpers.h"
 #include "nsDisplayList.h"              // for nsDisplayItem
 #include "nsCaret.h"
 
 using namespace mozilla::gfx;
 
 namespace mozilla {
 namespace layers {
 
--- a/gfx/layers/client/ClientLayerManager.cpp
+++ b/gfx/layers/client/ClientLayerManager.cpp
@@ -807,16 +807,22 @@ ClientLayerManager::SupportsBackdropCopy
 
 void
 ClientLayerManager::SetIsFirstPaint()
 {
   mForwarder->SetIsFirstPaint();
 }
 
 void
+ClientLayerManager::SetFocusTarget(const FocusTarget& aFocusTarget)
+{
+  mForwarder->SetFocusTarget(aFocusTarget);
+}
+
+void
 ClientLayerManager::ClearCachedResources(Layer* aSubtree)
 {
   if (mDestroyed) {
     // ClearCachedResource was already called by ClientLayerManager::Destroy
     return;
   }
   MOZ_ASSERT(!HasShadowManager() || !aSubtree);
   mForwarder->ClearCachedResources();
--- a/gfx/layers/client/ClientLayerManager.h
+++ b/gfx/layers/client/ClientLayerManager.h
@@ -9,16 +9,17 @@
 #include <stdint.h>                     // for int32_t
 #include "Layers.h"
 #include "gfxContext.h"                 // for gfxContext
 #include "mozilla/Attributes.h"         // for override
 #include "mozilla/LinkedList.h"         // For LinkedList
 #include "mozilla/WidgetUtils.h"        // for ScreenRotation
 #include "mozilla/gfx/Rect.h"           // for Rect
 #include "mozilla/layers/CompositorTypes.h"
+#include "mozilla/layers/FocusTarget.h"  // for FocusTarget
 #include "mozilla/layers/LayersTypes.h"  // for BufferMode, LayersBackend, etc
 #include "mozilla/layers/ShadowLayers.h"  // for ShadowLayerForwarder, etc
 #include "mozilla/layers/APZTestData.h" // for APZTestData
 #include "nsCOMPtr.h"                   // for already_AddRefed
 #include "nsIObserver.h"                // for nsIObserver
 #include "nsISupportsImpl.h"            // for Layer::Release, etc
 #include "nsRect.h"                     // for mozilla::gfx::IntRect
 #include "nsTArray.h"                   // for nsTArray
@@ -128,16 +129,18 @@ public:
 
   bool HasShadowManager() const { return mForwarder->HasShadowManager(); }
 
   virtual bool IsCompositingCheap() override;
   virtual bool HasShadowManagerInternal() const override { return HasShadowManager(); }
 
   virtual void SetIsFirstPaint() override;
 
+  virtual void SetFocusTarget(const FocusTarget& aFocusTarget) override;
+
   /**
    * Pass through call to the forwarder for nsPresContext's
    * CollectPluginGeometryUpdates. Passes widget configuration information
    * to the compositor for transmission to the chrome process. This
    * configuration gets set when the window paints.
    */
   void StorePluginWidgetConfigurations(const nsTArray<nsIWidget::Configuration>&
                                        aConfigurations) override;
--- a/gfx/layers/client/ClientPaintedLayer.h
+++ b/gfx/layers/client/ClientPaintedLayer.h
@@ -115,17 +115,17 @@ protected:
   void PaintThebes(nsTArray<ReadbackProcessor::Update>* aReadbackUpdates);
   void RecordThebes();
   bool CanRecordLayer(ReadbackProcessor* aReadback);
   bool HasMaskLayers();
   bool EnsureContentClient();
   uint32_t GetPaintFlags();
   void UpdateContentClient(PaintState& aState);
   bool UpdatePaintRegion(PaintState& aState);
-  void PaintOffMainThread(DrawTargetCapture* aCapture);
+  void PaintOffMainThread(gfx::DrawTargetCapture* aCapture);
   already_AddRefed<gfx::DrawTargetCapture> CapturePaintedContent();
 
   virtual void PrintInfo(std::stringstream& aStream, const char* aPrefix) override;
 
   void DestroyBackBuffer()
   {
     mContentClient = nullptr;
   }
--- a/gfx/layers/composite/TextRenderer.h
+++ b/gfx/layers/composite/TextRenderer.h
@@ -3,16 +3,17 @@
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #ifndef GFX_TextRenderer_H
 #define GFX_TextRenderer_H
 
 #include "mozilla/EnumeratedArray.h"
 #include "mozilla/gfx/2D.h"
+#include "mozilla/UniquePtr.h"
 #include "nsISupportsImpl.h"
 #include <string>
 
 namespace mozilla {
 namespace layers {
 
 class Compositor;
 class TextureSource;
--- a/gfx/layers/ipc/APZCTreeManagerChild.cpp
+++ b/gfx/layers/ipc/APZCTreeManagerChild.cpp
@@ -114,24 +114,44 @@ APZCTreeManagerChild::ReceiveInputEvent(
                                      &res,
                                      &processedEvent,
                                      aOutTargetGuid,
                                      aOutInputBlockId);
 
     event = processedEvent;
     return res;
   }
+  case KEYBOARD_INPUT: {
+    KeyboardInput& event = aEvent.AsKeyboardInput();
+    KeyboardInput processedEvent;
+
+    nsEventStatus res;
+    SendReceiveKeyboardInputEvent(event,
+                                  &res,
+                                  &processedEvent,
+                                  aOutTargetGuid,
+                                  aOutInputBlockId);
+
+    event = processedEvent;
+    return res;
+  }
   default: {
     MOZ_ASSERT_UNREACHABLE("Invalid InputData type.");
     return nsEventStatus_eConsumeNoDefault;
   }
   }
 }
 
 void
+APZCTreeManagerChild::SetKeyboardMap(const KeyboardMap& aKeyboardMap)
+{
+  SendSetKeyboardMap(aKeyboardMap);
+}
+
+void
 APZCTreeManagerChild::ZoomToRect(
     const ScrollableLayerGuid& aGuid,
     const CSSRect& aRect,
     const uint32_t aFlags)
 {
   SendZoomToRect(aGuid, aRect, aFlags);
 }
 
@@ -202,21 +222,25 @@ APZCTreeManagerChild::ProcessTouchVeloci
 void
 APZCTreeManagerChild::UpdateWheelTransaction(
     LayoutDeviceIntPoint aRefPoint,
     EventMessage aEventMessage)
 {
   SendUpdateWheelTransaction(aRefPoint, aEventMessage);
 }
 
-void APZCTreeManagerChild::TransformEventRefPoint(
+void APZCTreeManagerChild::ProcessUnhandledEvent(
     LayoutDeviceIntPoint* aRefPoint,
-    ScrollableLayerGuid* aOutTargetGuid)
+    ScrollableLayerGuid*  aOutTargetGuid,
+    uint64_t*             aOutFocusSequenceNumber)
 {
-  SendTransformEventRefPoint(*aRefPoint, aRefPoint, aOutTargetGuid);
+  SendProcessUnhandledEvent(*aRefPoint,
+                            aRefPoint,
+                            aOutTargetGuid,
+                            aOutFocusSequenceNumber);
 }
 
 mozilla::ipc::IPCResult
 APZCTreeManagerChild::RecvHandleTap(const TapType& aType,
                                     const LayoutDevicePoint& aPoint,
                                     const Modifiers& aModifiers,
                                     const ScrollableLayerGuid& aGuid,
                                     const uint64_t& aInputBlockId)
--- a/gfx/layers/ipc/APZCTreeManagerChild.h
+++ b/gfx/layers/ipc/APZCTreeManagerChild.h
@@ -26,16 +26,19 @@ public:
 
   nsEventStatus
   ReceiveInputEvent(
           InputData& aEvent,
           ScrollableLayerGuid* aOutTargetGuid,
           uint64_t* aOutInputBlockId) override;
 
   void
+  SetKeyboardMap(const KeyboardMap& aKeyboardMap) override;
+
+  void
   ZoomToRect(
           const ScrollableLayerGuid& aGuid,
           const CSSRect& aRect,
           const uint32_t aFlags = DEFAULT_BEHAVIOR) override;
 
   void
   ContentReceivedInputBlock(
           uint64_t aInputBlockId,
@@ -69,19 +72,20 @@ public:
 
   void
   SetLongTapEnabled(bool aTapGestureEnabled) override;
 
   void
   ProcessTouchVelocity(uint32_t aTimestampMs, float aSpeedY) override;
 
   void
-  TransformEventRefPoint(
+  ProcessUnhandledEvent(
           LayoutDeviceIntPoint* aRefPoint,
-          ScrollableLayerGuid* aOutTargetGuid) override;
+          ScrollableLayerGuid*  aOutTargetGuid,
+          uint64_t*             aOutFocusSequenceNumber) override;
 
   void
   UpdateWheelTransaction(
           LayoutDeviceIntPoint aRefPoint,
           EventMessage aEventMessage) override;
 
 protected:
   mozilla::ipc::IPCResult RecvHandleTap(const TapType& aType,
--- a/gfx/layers/ipc/APZCTreeManagerParent.cpp
+++ b/gfx/layers/ipc/APZCTreeManagerParent.cpp
@@ -140,16 +140,43 @@ APZCTreeManagerParent::RecvReceiveScroll
     aOutTargetGuid,
     aOutInputBlockId);
   *aOutEvent = event;
 
   return IPC_OK();
 }
 
 mozilla::ipc::IPCResult
+APZCTreeManagerParent::RecvReceiveKeyboardInputEvent(
+        const KeyboardInput& aEvent,
+        nsEventStatus* aOutStatus,
+        KeyboardInput* aOutEvent,
+        ScrollableLayerGuid* aOutTargetGuid,
+        uint64_t* aOutInputBlockId)
+{
+  KeyboardInput event = aEvent;
+
+  *aOutStatus = mTreeManager->ReceiveInputEvent(
+    event,
+    aOutTargetGuid,
+    aOutInputBlockId);
+  *aOutEvent = event;
+
+  return IPC_OK();
+}
+
+mozilla::ipc::IPCResult
+APZCTreeManagerParent::RecvSetKeyboardMap(const KeyboardMap& aKeyboardMap)
+{
+  mTreeManager->SetKeyboardMap(aKeyboardMap);
+
+  return IPC_OK();
+}
+
+mozilla::ipc::IPCResult
 APZCTreeManagerParent::RecvZoomToRect(
     const ScrollableLayerGuid& aGuid,
     const CSSRect& aRect,
     const uint32_t& aFlags)
 {
   if (aGuid.mLayersId != mLayersId) {
     // Guard against bad data from hijacked child processes
     NS_ERROR("Unexpected layers id in RecvZoomToRect; dropping message...");
@@ -294,22 +321,23 @@ APZCTreeManagerParent::RecvUpdateWheelTr
         const LayoutDeviceIntPoint& aRefPoint,
         const EventMessage& aEventMessage)
 {
   mTreeManager->UpdateWheelTransaction(aRefPoint, aEventMessage);
   return IPC_OK();
 }
 
 mozilla::ipc::IPCResult
-APZCTreeManagerParent::RecvTransformEventRefPoint(
+APZCTreeManagerParent::RecvProcessUnhandledEvent(
         const LayoutDeviceIntPoint& aRefPoint,
         LayoutDeviceIntPoint* aOutRefPoint,
-        ScrollableLayerGuid* aOutTargetGuid)
+        ScrollableLayerGuid*  aOutTargetGuid,
+        uint64_t*             aOutFocusSequenceNumber)
 {
   LayoutDeviceIntPoint refPoint = aRefPoint;
-  mTreeManager->TransformEventRefPoint(&refPoint, aOutTargetGuid);
+  mTreeManager->ProcessUnhandledEvent(&refPoint, aOutTargetGuid, aOutFocusSequenceNumber);
   *aOutRefPoint = refPoint;
 
   return IPC_OK();
 }
 
 } // namespace layers
 } // namespace mozilla
--- a/gfx/layers/ipc/APZCTreeManagerParent.h
+++ b/gfx/layers/ipc/APZCTreeManagerParent.h
@@ -74,16 +74,27 @@ public:
   RecvReceiveScrollWheelInputEvent(
           const ScrollWheelInput& aEvent,
           nsEventStatus* aOutStatus,
           ScrollWheelInput* aOutEvent,
           ScrollableLayerGuid* aOutTargetGuid,
           uint64_t* aOutInputBlockId) override;
 
   mozilla::ipc::IPCResult
+  RecvReceiveKeyboardInputEvent(
+          const KeyboardInput& aEvent,
+          nsEventStatus* aOutStatus,
+          KeyboardInput* aOutEvent,
+          ScrollableLayerGuid* aOutTargetGuid,
+          uint64_t* aOutInputBlockId) override;
+
+  mozilla::ipc::IPCResult
+  RecvSetKeyboardMap(const KeyboardMap& aKeyboardMap) override;
+
+  mozilla::ipc::IPCResult
   RecvZoomToRect(
           const ScrollableLayerGuid& aGuid,
           const CSSRect& aRect,
           const uint32_t& aFlags) override;
 
   mozilla::ipc::IPCResult
   RecvContentReceivedInputBlock(
           const uint64_t& aInputBlockId,
@@ -124,20 +135,21 @@ public:
           const float& aSpeedY) override;
 
   mozilla::ipc::IPCResult
   RecvUpdateWheelTransaction(
           const LayoutDeviceIntPoint& aRefPoint,
           const EventMessage& aEventMessage) override;
 
   mozilla::ipc::IPCResult
-  RecvTransformEventRefPoint(
+  RecvProcessUnhandledEvent(
           const LayoutDeviceIntPoint& aRefPoint,
           LayoutDeviceIntPoint* aOutRefPoint,
-          ScrollableLayerGuid* aOutTargetGuid) override;
+          ScrollableLayerGuid*  aOutTargetGuid,
+          uint64_t*             aOutFocusSequenceNumber) override;
 
   void
   ActorDestroy(ActorDestroyReason aWhy) override { }
 
 private:
   uint64_t mLayersId;
   RefPtr<APZCTreeManager> mTreeManager;
 };
--- a/gfx/layers/ipc/CompositorBridgeChild.cpp
+++ b/gfx/layers/ipc/CompositorBridgeChild.cpp
@@ -563,22 +563,23 @@ CompositorBridgeChild::RecvSharedComposi
   return IPC_OK();
 }
 
 mozilla::ipc::IPCResult
 CompositorBridgeChild::RecvReleaseSharedCompositorFrameMetrics(
     const ViewID& aId,
     const uint32_t& aAPZCId)
 {
-  SharedFrameMetricsData* data = mFrameMetricsTable.Get(aId);
-  // The SharedFrameMetricsData may have been removed previously if
-  // a SharedFrameMetricsData with the same ViewID but later APZCId had
-  // been store and over wrote it.
-  if (data && (data->GetAPZCId() == aAPZCId)) {
-    mFrameMetricsTable.Remove(aId);
+  if (auto entry = mFrameMetricsTable.Lookup(aId)) {
+    // The SharedFrameMetricsData may have been removed previously if
+    // a SharedFrameMetricsData with the same ViewID but later APZCId had
+    // been store and over wrote it.
+    if (entry.Data()->GetAPZCId() == aAPZCId) {
+      entry.Remove();
+    }
   }
   return IPC_OK();
 }
 
 CompositorBridgeChild::SharedFrameMetricsData::SharedFrameMetricsData(
     const ipc::SharedMemoryBasic::Handle& metrics,
     const CrossProcessMutexHandle& handle,
     const uint64_t& aLayersId,
@@ -880,34 +881,28 @@ CompositorBridgeChild::HoldUntilComposit
 
   aClient->SetLastFwdTransactionId(GetFwdTransactionId());
   mTexturesWaitingRecycled.Put(aClient->GetSerial(), aClient);
 }
 
 void
 CompositorBridgeChild::NotifyNotUsed(uint64_t aTextureId, uint64_t aFwdTransactionId)
 {
-  RefPtr<TextureClient> client = mTexturesWaitingRecycled.Get(aTextureId);
-  if (!client) {
-    return;
+  if (auto entry = mTexturesWaitingRecycled.Lookup(aTextureId)) {
+    if (aFwdTransactionId < entry.Data()->GetLastFwdTransactionId()) {
+      // Released on host side, but client already requested newer use texture.
+      return;
+    }
+    entry.Remove();
   }
-  if (aFwdTransactionId < client->GetLastFwdTransactionId()) {
-    // Released on host side, but client already requested newer use texture.
-    return;
-  }
-  mTexturesWaitingRecycled.Remove(aTextureId);
 }
 
 void
 CompositorBridgeChild::CancelWaitForRecycle(uint64_t aTextureId)
 {
-  RefPtr<TextureClient> client = mTexturesWaitingRecycled.Get(aTextureId);
-  if (!client) {
-    return;
-  }
   mTexturesWaitingRecycled.Remove(aTextureId);
 }
 
 TextureClientPool*
 CompositorBridgeChild::GetTexturePool(KnowsCompositor* aAllocator,
                                       SurfaceFormat aFormat,
                                       TextureFlags aFlags)
 {
--- a/gfx/layers/ipc/CompositorBridgeChild.h
+++ b/gfx/layers/ipc/CompositorBridgeChild.h
@@ -10,16 +10,17 @@
 #include "base/basictypes.h"            // for DISALLOW_EVIL_CONSTRUCTORS
 #include "mozilla/Assertions.h"         // for MOZ_ASSERT_HELPER2
 #include "mozilla/Attributes.h"         // for override
 #include "mozilla/ipc/ProtocolUtils.h"
 #include "mozilla/layers/PCompositorBridgeChild.h"
 #include "mozilla/layers/TextureForwarder.h" // for TextureForwarder
 #include "mozilla/webrender/WebRenderTypes.h"
 #include "nsClassHashtable.h"           // for nsClassHashtable
+#include "nsRefPtrHashtable.h"
 #include "nsCOMPtr.h"                   // for nsCOMPtr
 #include "nsHashKeys.h"                 // for nsUint64HashKey
 #include "nsISupportsImpl.h"            // for NS_INLINE_DECL_REFCOUNTING
 #include "ThreadSafeRefcountingWithMainThreadDestruction.h"
 #include "nsWeakReference.h"
 
 namespace mozilla {
 
@@ -313,17 +314,17 @@ private:
    * Last sequence number recognized for a device reset.
    */
   uint64_t mDeviceResetSequenceNumber;
 
   /**
    * Hold TextureClients refs until end of their usages on host side.
    * It defer calling of TextureClient recycle callback.
    */
-  nsDataHashtable<nsUint64HashKey, RefPtr<TextureClient> > mTexturesWaitingRecycled;
+  nsRefPtrHashtable<nsUint64HashKey, TextureClient> mTexturesWaitingRecycled;
 
   MessageLoop* mMessageLoop;
 
   AutoTArray<RefPtr<TextureClientPool>,2> mTexturePools;
 
   uint64_t mProcessToken;
 
   FixedSizeSmallShmemSectionAllocator* mSectionAllocator;
--- a/gfx/layers/ipc/CompositorBridgeParent.cpp
+++ b/gfx/layers/ipc/CompositorBridgeParent.cpp
@@ -854,16 +854,17 @@ CompositorBridgeParent::UpdatePaintTime(
     return;
   }
 
   mLayerManager->SetPaintTime(aPaintTime);
 }
 
 void
 CompositorBridgeParent::NotifyShadowTreeTransaction(uint64_t aId, bool aIsFirstPaint,
+    const FocusTarget& aFocusTarget,
     bool aScheduleComposite, uint32_t aPaintSequenceNumber,
     bool aIsRepeatTransaction, bool aHitTestUpdate)
 {
   if (!aIsRepeatTransaction &&
       mLayerManager &&
       mLayerManager->GetRoot()) {
     // Process plugin data here to give time for them to update before the next
     // composition.
@@ -874,19 +875,23 @@ CompositorBridgeParent::NotifyShadowTree
 #if defined(XP_WIN) || defined(MOZ_WIDGET_GTK)
     // If plugins haven't been updated, stop waiting.
     if (!pluginsUpdatedFlag) {
       mWaitForPluginsUntil = TimeStamp();
       mHaveBlockedForPlugins = false;
     }
 #endif
 
-    if (mApzcTreeManager && aHitTestUpdate) {
-      mApzcTreeManager->UpdateHitTestingTree(mRootLayerTreeID,
-          mLayerManager->GetRoot(), aIsFirstPaint, aId, aPaintSequenceNumber);
+    if (mApzcTreeManager) {
+      mApzcTreeManager->UpdateFocusState(mRootLayerTreeID, aId,
+                                         aFocusTarget);
+      if (aHitTestUpdate) {
+        mApzcTreeManager->UpdateHitTestingTree(mRootLayerTreeID,
+            mLayerManager->GetRoot(), aIsFirstPaint, aId, aPaintSequenceNumber);
+      }
     }
 
     mLayerManager->NotifyShadowTreeTransaction();
   }
   if (aScheduleComposite) {
     ScheduleComposition();
   }
 }
@@ -1227,22 +1232,28 @@ CompositorBridgeParent::ShadowLayersUpda
   if (mLayerManager->GetCompositor()) {
     mLayerManager->GetCompositor()->SetScreenRotation(targetConfig.rotation());
   }
 
   mCompositionManager->Updated(aInfo.isFirstPaint(), targetConfig);
   Layer* root = aLayerTree->GetRoot();
   mLayerManager->SetRoot(root);
 
-  if (mApzcTreeManager && !aInfo.isRepeatTransaction() && aHitTestUpdate) {
-    AutoResolveRefLayers resolve(mCompositionManager);
+  if (mApzcTreeManager && !aInfo.isRepeatTransaction()) {
+    mApzcTreeManager->UpdateFocusState(mRootLayerTreeID,
+                                       mRootLayerTreeID,
+                                       aInfo.focusTarget());
 
-    mApzcTreeManager->UpdateHitTestingTree(
-      mRootLayerTreeID, root, aInfo.isFirstPaint(),
-      mRootLayerTreeID, aInfo.paintSequenceNumber());
+    if (aHitTestUpdate) {
+      AutoResolveRefLayers resolve(mCompositionManager);
+
+      mApzcTreeManager->UpdateHitTestingTree(
+        mRootLayerTreeID, root, aInfo.isFirstPaint(),
+        mRootLayerTreeID, aInfo.paintSequenceNumber());
+    }
   }
 
   // The transaction ID might get reset to 1 if the page gets reloaded, see
   // https://bugzilla.mozilla.org/show_bug.cgi?id=1145295#c41
   // Otherwise, it should be continually increasing.
   MOZ_ASSERT(aInfo.id() == 1 || aInfo.id() > mPendingTransaction);
   mPendingTransaction = aInfo.id();
 
@@ -1631,16 +1642,19 @@ CompositorBridgeParent::RecvAdoptChild(c
   {
     MonitorAutoLock lock(*sIndirectLayerTreesLock);
     // We currently don't support adopting children from one compositor to
     // another if the two compositors don't have the same options.
     MOZ_ASSERT(sIndirectLayerTrees[child].mParent->mOptions == mOptions);
     NotifyChildCreated(child);
     if (sIndirectLayerTrees[child].mLayerTree) {
       sIndirectLayerTrees[child].mLayerTree->SetLayerManager(mLayerManager);
+      // Trigger composition to handle a case that mLayerTree was not composited yet
+      // by previous CompositorBridgeParent, since nsRefreshDriver might wait composition complete.
+      ScheduleComposition();
     }
     parent = sIndirectLayerTrees[child].mApzcTreeManagerParent;
   }
 
   if (mApzcTreeManager && parent) {
     parent->ChildAdopted(mApzcTreeManager);
   }
   return IPC_OK();
--- a/gfx/layers/ipc/CompositorBridgeParent.h
+++ b/gfx/layers/ipc/CompositorBridgeParent.h
@@ -25,16 +25,17 @@
 #include "mozilla/TimeStamp.h"          // for TimeStamp
 #include "mozilla/dom/ipc/IdType.h"
 #include "mozilla/gfx/Point.h"          // for IntSize
 #include "mozilla/ipc/ProtocolUtils.h"
 #include "mozilla/ipc/SharedMemory.h"
 #include "mozilla/layers/CompositorController.h"
 #include "mozilla/layers/CompositorOptions.h"
 #include "mozilla/layers/CompositorVsyncSchedulerOwner.h"
+#include "mozilla/layers/FocusState.h"
 #include "mozilla/layers/GeckoContentController.h"
 #include "mozilla/layers/ISurfaceAllocator.h" // for ShmemAllocator
 #include "mozilla/layers/LayersMessages.h"  // for TargetConfig
 #include "mozilla/layers/MetricsSharingController.h"
 #include "mozilla/layers/PCompositorBridgeParent.h"
 #include "mozilla/layers/APZTestData.h"
 #include "mozilla/webrender/WebRenderTypes.h"
 #include "mozilla/widget/CompositorWidget.h"
@@ -298,16 +299,17 @@ public:
    * Returns true if a surface was obtained and the resume succeeded; false
    * otherwise.
    */
   bool ScheduleResumeOnCompositorThread();
   bool ScheduleResumeOnCompositorThread(int width, int height);
 
   virtual void ScheduleComposition();
   void NotifyShadowTreeTransaction(uint64_t aId, bool aIsFirstPaint,
+      const FocusTarget& aFocusTarget,
       bool aScheduleComposite, uint32_t aPaintSequenceNumber,
       bool aIsRepeatTransaction, bool aHitTestUpdate);
 
   void UpdatePaintTime(LayerTransactionParent* aLayerTree,
                        const TimeDuration& aPaintTime) override;
 
   /**
    * Check rotation info and schedule a rendering task if needed.
--- a/gfx/layers/ipc/CrossProcessCompositorBridgeParent.cpp
+++ b/gfx/layers/ipc/CrossProcessCompositorBridgeParent.cpp
@@ -294,16 +294,17 @@ CrossProcessCompositorBridgeParent::Shad
 
   // Cache the plugin data for this remote layer tree
   state->mPluginData = aInfo.plugins();
   state->mUpdatedPluginDataAvailable = true;
 
   state->mParent->NotifyShadowTreeTransaction(
     id,
     aInfo.isFirstPaint(),
+    aInfo.focusTarget(),
     aInfo.scheduleComposite(),
     aInfo.paintSequenceNumber(),
     aInfo.isRepeatTransaction(),
     aHitTestUpdate);
 
   // Send the 'remote paint ready' message to the content thread if it has already asked.
   if(mNotifyAfterRemotePaint)  {
     Unused << SendRemotePaintIsReady();
--- a/gfx/layers/ipc/ImageBridgeChild.cpp
+++ b/gfx/layers/ipc/ImageBridgeChild.cpp
@@ -176,36 +176,29 @@ ImageBridgeChild::HoldUntilCompositableR
   }
   aClient->SetLastFwdTransactionId(GetFwdTransactionId());
   mTexturesWaitingRecycled.Put(aClient->GetSerial(), aClient);
 }
 
 void
 ImageBridgeChild::NotifyNotUsed(uint64_t aTextureId, uint64_t aFwdTransactionId)
 {
-  RefPtr<TextureClient> client = mTexturesWaitingRecycled.Get(aTextureId);
-  if (!client) {
-    return;
+  if (auto entry = mTexturesWaitingRecycled.Lookup(aTextureId)) {
+    if (aFwdTransactionId < entry.Data()->GetLastFwdTransactionId()) {
+      // Released on host side, but client already requested newer use texture.
+      return;
+    }
+    entry.Remove();
   }
-  if (aFwdTransactionId < client->GetLastFwdTransactionId()) {
-    // Released on host side, but client already requested newer use texture.
-    return;
-  }
-  mTexturesWaitingRecycled.Remove(aTextureId);
 }
 
 void
 ImageBridgeChild::CancelWaitForRecycle(uint64_t aTextureId)
 {
   MOZ_ASSERT(InImageBridgeChildThread());
-
-  RefPtr<TextureClient> client = mTexturesWaitingRecycled.Get(aTextureId);
-  if (!client) {
-    return;
-  }
   mTexturesWaitingRecycled.Remove(aTextureId);
 }
 
 // Singleton
 static StaticMutex sImageBridgeSingletonLock;
 static StaticRefPtr<ImageBridgeChild> sImageBridgeChildSingleton;
 static Thread *sImageBridgeChildThread = nullptr;
 
--- a/gfx/layers/ipc/ImageBridgeChild.h
+++ b/gfx/layers/ipc/ImageBridgeChild.h
@@ -16,16 +16,17 @@
 #include "mozilla/layers/CompositableForwarder.h"
 #include "mozilla/layers/CompositorTypes.h"
 #include "mozilla/layers/PImageBridgeChild.h"
 #include "mozilla/Mutex.h"
 #include "mozilla/webrender/WebRenderTypes.h"
 #include "nsDebug.h"                    // for NS_RUNTIMEABORT
 #include "nsIObserver.h"
 #include "nsRegion.h"                   // for nsIntRegion
+#include "nsRefPtrHashtable.h"
 #include "mozilla/gfx/Rect.h"
 #include "mozilla/ReentrantMonitor.h"   // for ReentrantMonitor, etc
 
 class MessageLoop;
 
 namespace base {
 class Thread;
 } // namespace base
@@ -387,17 +388,17 @@ private:
    * It is incrementaed by UpdateFwdTransactionId() in each BeginTransaction() call.
    */
   uint64_t mFwdTransactionId;
 
   /**
    * Hold TextureClients refs until end of their usages on host side.
    * It defer calling of TextureClient recycle callback.
    */
-  nsDataHashtable<nsUint64HashKey, RefPtr<TextureClient> > mTexturesWaitingRecycled;
+  nsRefPtrHashtable<nsUint64HashKey, TextureClient> mTexturesWaitingRecycled;
 
   /**
    * Mapping from async compositable IDs to image containers.
    */
   Mutex mContainerMapLock;
   nsDataHashtable<nsUint64HashKey, ImageContainer*> mImageContainers;
 };
 
--- a/gfx/layers/ipc/LayersMessageUtils.h
+++ b/gfx/layers/ipc/LayersMessageUtils.h
@@ -7,23 +7,27 @@
 #ifndef mozilla_layers_LayersMessageUtils
 #define mozilla_layers_LayersMessageUtils
 
 #include "FrameMetrics.h"
 #include "base/process_util.h"
 #include "chrome/common/ipc_message_utils.h"
 #include "gfxTelemetry.h"
 #include "ipc/IPCMessageUtils.h"
+#include "ipc/nsGUIEventIPC.h"
 #include "mozilla/GfxMessageUtils.h"
 #include "mozilla/layers/AsyncDragMetrics.h"
 #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/Move.h"
 
 #include <stdint.h>
 
 #ifdef _MSC_VER
 #pragma warning( disable : 4800 )
 #endif
 
 namespace IPC {
@@ -412,16 +416,152 @@ struct ParamTraits<mozilla::layers::Even
     return (ReadParam(aMsg, aIter, &aResult->mHitRegion) &&
             ReadParam(aMsg, aIter, &aResult->mDispatchToContentHitRegion) &&
             ReadParam(aMsg, aIter, &aResult->mNoActionRegion) &&
             ReadParam(aMsg, aIter, &aResult->mHorizontalPanRegion) &&
             ReadParam(aMsg, aIter, &aResult->mVerticalPanRegion));
   }
 };
 
+template <>
+struct ParamTraits<mozilla::layers::FocusTarget::ScrollTargets>
+{
+  typedef mozilla::layers::FocusTarget::ScrollTargets paramType;
+
+  static void Write(Message* aMsg, const paramType& aParam)
+  {
+    WriteParam(aMsg, aParam.mHorizontal);
+    WriteParam(aMsg, aParam.mVertical);
+  }
+
+  static bool Read(const Message* aMsg, PickleIterator* aIter, paramType* aResult)
+  {
+    return ReadParam(aMsg, aIter, &aResult->mHorizontal) &&
+           ReadParam(aMsg, aIter, &aResult->mVertical);
+  }
+};
+
+template <>
+struct ParamTraits<mozilla::layers::FocusTarget::FocusTargetType>
+  : public ContiguousEnumSerializer<
+             mozilla::layers::FocusTarget::FocusTargetType,
+             mozilla::layers::FocusTarget::eNone,
+             mozilla::layers::FocusTarget::eSentinel>
+{};
+
+template <>
+struct ParamTraits<mozilla::layers::FocusTarget>
+{
+  typedef mozilla::layers::FocusTarget paramType;
+
+  static void Write(Message* aMsg, const paramType& aParam)
+  {
+    WriteParam(aMsg, aParam.mSequenceNumber);
+    WriteParam(aMsg, aParam.mFocusHasKeyEventListeners);
+    WriteParam(aMsg, aParam.mType);
+    if (aParam.mType == mozilla::layers::FocusTarget::eRefLayer) {
+      WriteParam(aMsg, aParam.mData.mRefLayerId);
+    } else if (aParam.mType == mozilla::layers::FocusTarget::eScrollLayer) {
+      WriteParam(aMsg, aParam.mData.mScrollTargets);
+    }
+  }
+
+  static bool Read(const Message* aMsg, PickleIterator* aIter, paramType* aResult)
+  {
+    if (!ReadParam(aMsg, aIter, &aResult->mSequenceNumber) ||
+        !ReadParam(aMsg, aIter, &aResult->mFocusHasKeyEventListeners) ||
+        !ReadParam(aMsg, aIter, &aResult->mType)) {
+      return false;
+    }
+
+    if (aResult->mType == mozilla::layers::FocusTarget::eRefLayer) {
+      return ReadParam(aMsg, aIter, &aResult->mData.mRefLayerId);
+    } else if (aResult->mType == mozilla::layers::FocusTarget::eScrollLayer) {
+      return ReadParam(aMsg, aIter, &aResult->mData.mScrollTargets);
+    }
+
+    return true;
+  }
+};
+
+template <>
+struct ParamTraits<mozilla::layers::KeyboardScrollAction::KeyboardScrollActionType>
+  : public ContiguousEnumSerializer<
+             mozilla::layers::KeyboardScrollAction::KeyboardScrollActionType,
+             mozilla::layers::KeyboardScrollAction::KeyboardScrollActionType::eScrollCharacter,
+             mozilla::layers::KeyboardScrollAction::KeyboardScrollActionType::eSentinel>
+{};
+
+template <>
+struct ParamTraits<mozilla::layers::KeyboardScrollAction>
+{
+  typedef mozilla::layers::KeyboardScrollAction paramType;
+
+  static void Write(Message* aMsg, const paramType& aParam)
+  {
+    WriteParam(aMsg, aParam.mType);
+    WriteParam(aMsg, aParam.mForward);
+  }
+
+  static bool Read(const Message* aMsg, PickleIterator* aIter, paramType* aResult)
+  {
+    return ReadParam(aMsg, aIter, &aResult->mType) &&
+           ReadParam(aMsg, aIter, &aResult->mForward);
+  }
+};
+
+template <>
+struct ParamTraits<mozilla::layers::KeyboardShortcut>
+{
+  typedef mozilla::layers::KeyboardShortcut paramType;
+
+  static void Write(Message* aMsg, const paramType& aParam)
+  {
+    WriteParam(aMsg, aParam.mAction);
+    WriteParam(aMsg, aParam.mKeyCode);
+    WriteParam(aMsg, aParam.mCharCode);
+    WriteParam(aMsg, aParam.mModifiers);
+    WriteParam(aMsg, aParam.mModifiersMask);
+    WriteParam(aMsg, aParam.mEventType);
+    WriteParam(aMsg, aParam.mDispatchToContent);
+  }
+
+  static bool Read(const Message* aMsg, PickleIterator* aIter, paramType* aResult)
+  {
+    return ReadParam(aMsg, aIter, &aResult->mAction) &&
+           ReadParam(aMsg, aIter, &aResult->mKeyCode) &&
+           ReadParam(aMsg, aIter, &aResult->mCharCode) &&
+           ReadParam(aMsg, aIter, &aResult->mModifiers) &&
+           ReadParam(aMsg, aIter, &aResult->mModifiersMask) &&
+           ReadParam(aMsg, aIter, &aResult->mEventType) &&
+           ReadParam(aMsg, aIter, &aResult->mDispatchToContent);
+  }
+};
+
+template <>
+struct ParamTraits<mozilla::layers::KeyboardMap>
+{
+  typedef mozilla::layers::KeyboardMap paramType;
+
+  static void Write(Message* aMsg, const paramType& aParam)
+  {
+    WriteParam(aMsg, aParam.Shortcuts());
+  }
+
+  static bool Read(const Message* aMsg, PickleIterator* aIter, paramType* aResult)
+  {
+    nsTArray<mozilla::layers::KeyboardShortcut> shortcuts;
+    if (!ReadParam(aMsg, aIter, &shortcuts)) {
+      return false;
+    }
+    *aResult = mozilla::layers::KeyboardMap(mozilla::Move(shortcuts));
+    return true;
+  }
+};
+
 typedef mozilla::layers::GeckoContentController::TapType TapType;
 
 template <>
 struct ParamTraits<TapType>
   : public ContiguousEnumSerializer<
              TapType,
              TapType::eSingleTap,
              TapType::eSentinel>
--- a/gfx/layers/ipc/LayersMessages.ipdlh
+++ b/gfx/layers/ipc/LayersMessages.ipdlh
@@ -35,16 +35,17 @@ using mozilla::LayerSize from "Units.h";
 using mozilla::LayerRect from "Units.h";
 using mozilla::LayerIntRegion from "Units.h";
 using mozilla::ParentLayerIntRect from "Units.h";
 using mozilla::LayoutDeviceIntRect from "Units.h";
 using mozilla::layers::ScaleMode from "mozilla/layers/LayersTypes.h";
 using mozilla::layers::EventRegions from "mozilla/layers/LayersTypes.h";
 using mozilla::layers::EventRegionsOverride from "mozilla/layers/LayersTypes.h";
 using mozilla::layers::DiagnosticTypes from "mozilla/layers/CompositorTypes.h";
+using mozilla::layers::FocusTarget from "mozilla/layers/FocusTarget.h";
 using struct mozilla::layers::ScrollMetadata from "FrameMetrics.h";
 using mozilla::layers::FrameMetrics::ViewID from "FrameMetrics.h";
 using mozilla::layers::LayersBackend from "mozilla/layers/LayersTypes.h";
 using mozilla::layers::MaybeLayerClip from "FrameMetrics.h";
 using mozilla::gfx::Glyph from "Layers.h";
 using mozilla::layers::BorderColors from "mozilla/layers/LayersTypes.h";
 using mozilla::layers::BorderCorners from "mozilla/layers/LayersTypes.h";
 using mozilla::layers::BorderWidths from "mozilla/layers/LayersTypes.h";
@@ -565,16 +566,17 @@ struct TransactionInfo
   OpSetLayerAttributes[] setAttrs;
   CompositableOperation[] paints;
   OpDestroy[] toDestroy;
   uint64_t fwdTransactionId;
   uint64_t id;
   TargetConfig targetConfig;
   PluginWindowData[] plugins;
   bool isFirstPaint;
+  FocusTarget focusTarget;
   bool scheduleComposite;
   uint32_t paintSequenceNumber;
   bool isRepeatTransaction;
   TimeStamp transactionStart;
 };
 
 union MaybeTransform {
   Matrix4x4;
--- a/gfx/layers/ipc/PAPZCTreeManager.ipdl
+++ b/gfx/layers/ipc/PAPZCTreeManager.ipdl
@@ -15,16 +15,17 @@ using LayoutDeviceCoord from "Units.h";
 using LayoutDeviceIntPoint from "Units.h";
 using mozilla::LayoutDevicePoint from "Units.h";
 using ScreenPoint from "Units.h";
 using struct mozilla::layers::ScrollableLayerGuid from "FrameMetrics.h";
 using mozilla::layers::MaybeZoomConstraints from "FrameMetrics.h";
 using mozilla::layers::TouchBehaviorFlags from "mozilla/layers/APZUtils.h";
 using mozilla::layers::AsyncDragMetrics from "mozilla/layers/AsyncDragMetrics.h";
 using mozilla::layers::GeckoContentController::TapType from "mozilla/layers/GeckoContentController.h";
+using class mozilla::layers::KeyboardMap from "mozilla/layers/KeyboardMap.h";
 
 using nsEventStatus from "mozilla/EventForwards.h";
 using EventMessage from "mozilla/EventForwards.h";
 using mozilla::Modifiers from "mozilla/EventForwards.h";
 using class mozilla::WidgetInputEvent from "mozilla/BasicEvents.h";
 using class mozilla::WidgetMouseEventBase from "mozilla/MouseEvents.h";
 using mozilla::WidgetMouseEvent::Reason from "mozilla/MouseEvents.h";
 using class mozilla::WidgetTouchEvent from "mozilla/TouchEvents.h";
@@ -32,16 +33,17 @@ using class mozilla::WidgetWheelEvent fr
 using class mozilla::InputData from "InputData.h";
 using class mozilla::MultiTouchInput from "InputData.h";
 using class mozilla::MouseInput from "InputData.h";
 using class mozilla::PanGestureInput from "InputData.h";
 using class mozilla::PinchGestureInput from "InputData.h";
 using mozilla::PinchGestureInput::PinchGestureType from "InputData.h";
 using class mozilla::TapGestureInput from "InputData.h";
 using class mozilla::ScrollWheelInput from "InputData.h";
+using class mozilla::KeyboardInput from "InputData.h";
 
 namespace mozilla {
 namespace layers {
 
 /**
  * PAPZCTreeManager is a protocol for remoting an IAPZCTreeManager. PAPZCTreeManager
  * lives on the PCompositorBridge protocol which either connects to the compositor
  * thread in the main process, or to the compositor thread in the gpu processs.
@@ -64,16 +66,18 @@ parent:
   async ContentReceivedInputBlock(uint64_t aInputBlockId, bool PreventDefault);
 
   async SetTargetAPZC(uint64_t aInputBlockId, ScrollableLayerGuid[] Targets);
 
   async UpdateZoomConstraints(ScrollableLayerGuid aGuid, MaybeZoomConstraints aConstraints);
 
   async CancelAnimation(ScrollableLayerGuid aGuid);
 
+  async SetKeyboardMap(KeyboardMap aKeyboardMap);
+
   async SetDPI(float aDpiValue);
 
   async SetAllowedTouchBehavior(uint64_t aInputBlockId, TouchBehaviorFlags[] aValues);
 
   async StartScrollbarDrag(ScrollableLayerGuid aGuid, AsyncDragMetrics aDragMetrics);
 
   async SetLongTapEnabled(bool aTapGestureEnabled);
 
@@ -113,21 +117,28 @@ parent:
              uint64_t            aOutInputBlockId);
 
   sync ReceiveScrollWheelInputEvent(ScrollWheelInput aEvent)
     returns (nsEventStatus       aOutStatus,
              ScrollWheelInput    aOutEvent,
              ScrollableLayerGuid aOutTargetGuid,
              uint64_t            aOutInputBlockId);
 
+  sync ReceiveKeyboardInputEvent(KeyboardInput aEvent)
+    returns (nsEventStatus       aOutStatus,
+             KeyboardInput       aOutEvent,
+             ScrollableLayerGuid aOutTargetGuid,
+             uint64_t            aOutInputBlockId);
+
   async UpdateWheelTransaction(LayoutDeviceIntPoint aRefPoint, EventMessage aEventMessage);
 
-  sync TransformEventRefPoint(LayoutDeviceIntPoint aRefPoint)
+  sync ProcessUnhandledEvent(LayoutDeviceIntPoint aRefPoint)
     returns (LayoutDeviceIntPoint   aOutRefPoint,
-             ScrollableLayerGuid    aOutTargetGuid);
+             ScrollableLayerGuid    aOutTargetGuid,
+             uint64_t               aOutFocusSequenceNumber);
 
   async __delete__();
 
 child:
 
   async HandleTap(TapType aType, LayoutDevicePoint point, Modifiers aModifiers,
                   ScrollableLayerGuid aGuid, uint64_t aInputBlockId);
 
--- a/gfx/layers/ipc/ShadowLayers.cpp
+++ b/gfx/layers/ipc/ShadowLayers.cpp
@@ -728,16 +728,17 @@ ShadowLayerForwarder::EndTransaction(con
   info.setSimpleAttrs() = Move(setSimpleAttrs);
   info.setAttrs() = Move(setAttrs);
   info.paints() = Move(mTxn->mPaints);
   info.toDestroy() = mTxn->mDestroyedActors;
   info.fwdTransactionId() = GetFwdTransactionId();
   info.id() = aId;
   info.plugins() = mPluginWindowData;
   info.isFirstPaint() = mIsFirstPaint;
+  info.focusTarget() = mFocusTarget;
   info.scheduleComposite() = aScheduleComposite;
   info.paintSequenceNumber() = aPaintSequenceNumber;
   info.isRepeatTransaction() = aIsRepeatTransaction;
   info.transactionStart() = aTransactionStart;
 
   TargetConfig targetConfig(mTxn->mTargetBounds,
                             mTxn->mTargetRotation,
                             mTxn->mTargetOrientation,
@@ -772,16 +773,17 @@ ShadowLayerForwarder::EndTransaction(con
 
   if (startTime) {
     mPaintTiming.sendMs() = (TimeStamp::Now() - startTime.value()).ToMilliseconds();
     mShadowManager->SendRecordPaintTimes(mPaintTiming);
   }
 
   *aSent = true;
   mIsFirstPaint = false;
+  mFocusTarget = FocusTarget();
   MOZ_LAYERS_LOG(("[LayersForwarder] ... done"));
   return true;
 }
 
 RefPtr<CompositableClient>
 ShadowLayerForwarder::FindCompositable(const CompositableHandle& aHandle)
 {
   CompositableClient* client = nullptr;
--- a/gfx/layers/ipc/ShadowLayers.h
+++ b/gfx/layers/ipc/ShadowLayers.h
@@ -12,16 +12,17 @@
 #include <stdint.h>                     // for uint64_t
 #include "gfxTypes.h"
 #include "mozilla/Attributes.h"         // for override
 #include "mozilla/gfx/Rect.h"
 #include "mozilla/WidgetUtils.h"        // for ScreenRotation
 #include "mozilla/dom/ScreenOrientation.h"  // for ScreenOrientation
 #include "mozilla/ipc/SharedMemory.h"   // for SharedMemory, etc
 #include "mozilla/layers/CompositableForwarder.h"
+#include "mozilla/layers/FocusTarget.h"
 #include "mozilla/layers/LayersTypes.h"
 #include "mozilla/layers/TextureForwarder.h"
 #include "mozilla/layers/CompositorTypes.h"  // for OpenMode, etc
 #include "mozilla/layers/CompositorBridgeChild.h"
 #include "nsCOMPtr.h"                   // for already_AddRefed
 #include "nsRegion.h"                   // for nsIntRegion
 #include "nsTArrayForwardDeclare.h"     // for InfallibleTArray
 #include "nsIWidget.h"
@@ -366,16 +367,21 @@ public:
    */
   LayerHandle ConstructShadowFor(ShadowableLayer* aLayer);
 
   /**
    * Flag the next paint as the first for a document.
    */
   void SetIsFirstPaint() { mIsFirstPaint = true; }
 
+  /**
+   * Set the current focus target to be sent with the next paint.
+   */
+  void SetFocusTarget(const FocusTarget& aFocusTarget) { mFocusTarget = aFocusTarget; }
+
   void SetLayerObserverEpoch(uint64_t aLayerObserverEpoch);
 
   static void PlatformSyncBeforeUpdate();
 
   virtual bool AllocSurfaceDescriptor(const gfx::IntSize& aSize,
                                       gfxContentType aContent,
                                       SurfaceDescriptor* aBuffer) override;
 
@@ -443,16 +449,17 @@ protected:
 
 private:
 
   ClientLayerManager* mClientLayerManager;
   Transaction* mTxn;
   MessageLoop* mMessageLoop;
   DiagnosticTypes mDiagnosticTypes;
   bool mIsFirstPaint;
+  FocusTarget mFocusTarget;
   bool mWindowOverlayChanged;
   InfallibleTArray<PluginWindowData> mPluginWindowData;
   UniquePtr<ActiveResourceTracker> mActiveResourceTracker;
   uint64_t mNextLayerHandle;
   nsDataHashtable<nsUint64HashKey, CompositableClient*> mCompositables;
   PaintTiming mPaintTiming;
   /**
    * ShadowLayerForwarder might dispatch tasks to main while puppet widget and
--- a/gfx/layers/mlgpu/ContainerLayerMLGPU.cpp
+++ b/gfx/layers/mlgpu/ContainerLayerMLGPU.cpp
@@ -1,17 +1,19 @@
 /* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*-
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "ContainerLayerMLGPU.h"
 #include "gfxPrefs.h"
 #include "LayersLogging.h"
+#include "LayerManagerMLGPU.h"
 #include "MLGDevice.h"
+#include "mozilla/gfx/Types.h"
 
 namespace mozilla {
 namespace layers {
 
 ContainerLayerMLGPU::ContainerLayerMLGPU(LayerManagerMLGPU* aManager)
   : ContainerLayer(aManager, nullptr)
   , LayerMLGPU(aManager)
 {
@@ -33,27 +35,33 @@ ContainerLayerMLGPU::OnPrepareToRender(F
 
   mTargetOffset = GetIntermediateSurfaceRect().TopLeft().ToUnknownPoint();
   mTargetSize = GetIntermediateSurfaceRect().Size().ToUnknownSize();
 
   if (mRenderTarget && mRenderTarget->GetSize() != mTargetSize) {
     mRenderTarget = nullptr;
   }
 
-  IntRect viewport(IntPoint(0, 0), mTargetSize);
+  gfx::IntRect viewport(gfx::IntPoint(0, 0), mTargetSize);
   if (!mRenderTarget || !gfxPrefs::AdvancedLayersUseInvalidation()) {
     // Fine-grained invalidation is disabled, invalidate everything.
     mInvalidRect = viewport;
   } else {
     // Clamp the invalid rect to the viewport.
     mInvalidRect = mInvalidRect.Intersect(viewport);
   }
   return true;
 }
 
+void
+ContainerLayerMLGPU::OnLayerManagerChange(LayerManagerMLGPU* aManager)
+{
+  ClearCachedResources();
+}
+
 RefPtr<MLGRenderTarget>
 ContainerLayerMLGPU::UpdateRenderTarget(MLGDevice* aDevice, MLGRenderTargetFlags aFlags)
 {
   if (mRenderTarget) {
     return mRenderTarget;
   }
 
   mRenderTarget = aDevice->CreateRenderTarget(mTargetSize, aFlags);
@@ -65,38 +73,38 @@ ContainerLayerMLGPU::UpdateRenderTarget(
   return mRenderTarget;
 }
 
 void
 ContainerLayerMLGPU::SetInvalidCompositeRect(const gfx::IntRect& aRect)
 {
   // For simplicity we only track the bounds of the invalid area, since regions
   // are expensive. We can adjust this in the future if needed.
-  IntRect bounds = aRect;
+  gfx::IntRect bounds = aRect;
   bounds.MoveBy(-GetTargetOffset());
 
   // Note we add the bounds to the invalid rect from the last frame, since we
   // only clear the area that we actually paint.
-  if (Maybe<IntRect> result = mInvalidRect.SafeUnion(bounds)) {
+  if (Maybe<gfx::IntRect> result = mInvalidRect.SafeUnion(bounds)) {
     mInvalidRect = result.value();
   } else {
-    mInvalidRect = IntRect(IntPoint(0, 0), GetTargetSize());
+    mInvalidRect = gfx::IntRect(gfx::IntPoint(0, 0), GetTargetSize());
   }
 }
 
 void
 ContainerLayerMLGPU::ClearCachedResources()
 {
   mRenderTarget = nullptr;
 }
 
 bool
 ContainerLayerMLGPU::IsContentOpaque()
 {
-  if (GetMixBlendMode() != CompositionOp::OP_OVER) {
+  if (GetMixBlendMode() != gfx::CompositionOp::OP_OVER) {
     // We need to read from what's underneath us, so we consider our content to
     // be not opaque.
     return false;
   }
   return LayerMLGPU::IsContentOpaque();
 }
 
 } // namespace layers
--- a/gfx/layers/mlgpu/ContainerLayerMLGPU.h
+++ b/gfx/layers/mlgpu/ContainerLayerMLGPU.h
@@ -2,20 +2,23 @@
 * 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_gfx_layers_mlgpu_ContainerLayerMLGPU_h
 #define mozilla_gfx_layers_mlgpu_ContainerLayerMLGPU_h
 
 #include "LayerMLGPU.h"
+#include "MLGDeviceTypes.h"
 
 namespace mozilla {
 namespace layers {
 
+class MLGDevice;
+
 class ContainerLayerMLGPU final : public ContainerLayer
                                 , public LayerMLGPU
 {
 public:
   explicit ContainerLayerMLGPU(LayerManagerMLGPU* aManager);
   ~ContainerLayerMLGPU() override;
 
   MOZ_LAYER_DECL_NAME("ContainerLayerMLGPU", TYPE_CONTAINER)
@@ -48,16 +51,17 @@ public:
   }
   void ClearInvalidRect() {
     mInvalidRect.SetEmpty();
   }
   bool IsContentOpaque() override;
 
 protected:
   bool OnPrepareToRender(FrameBuilder* aBuilder) override;
+  void OnLayerManagerChange(LayerManagerMLGPU* aManager) override;
 
 private:
   RefPtr<MLGRenderTarget> mRenderTarget;
 
   // We cache these since occlusion culling can change the visible region.
   gfx::IntPoint mTargetOffset;
   gfx::IntSize mTargetSize;
 
--- a/gfx/layers/mlgpu/FrameBuilder.cpp
+++ b/gfx/layers/mlgpu/FrameBuilder.cpp
@@ -6,16 +6,18 @@
 #include "FrameBuilder.h"
 #include "ContainerLayerMLGPU.h"
 #include "GeckoProfiler.h"              // for profiler_*
 #include "LayerMLGPU.h"
 #include "LayerManagerMLGPU.h"
 #include "MaskOperation.h"
 #include "RenderPassMLGPU.h"
 #include "RenderViewMLGPU.h"
+#include "mozilla/gfx/Polygon.h"
+#include "mozilla/layers/BSPTree.h"
 #include "mozilla/layers/LayersHelpers.h"
 
 namespace mozilla {
 namespace layers {
 
 using namespace mlg;
 
 FrameBuilder::FrameBuilder(LayerManagerMLGPU* aManager, MLGSwapChain* aSwapChain)
@@ -58,17 +60,17 @@ FrameBuilder::Build()
   // affecting the invalid bounds are redrawn, even if not all are in the
   // precise region.
   const nsIntRegion& region = mSwapChain->GetBackBufferInvalidRegion();
 
   mWidgetRenderView = new RenderViewMLGPU(this, target, region);
 
   // Traverse the layer tree and assign each layer to tiles.
   {
-    Maybe<Polygon> geometry;
+    Maybe<gfx::Polygon> geometry;
     RenderTargetIntRect clip(0, 0, target->GetSize().width, target->GetSize().height);
 
     AssignLayer(mRoot->GetLayer(), mWidgetRenderView, clip, Move(geometry));
   }
 
   // Build the default mask buffer.
   {
     MaskInformation defaultMaskInfo(1.0f, false);
@@ -104,17 +106,17 @@ FrameBuilder::Render()
   // Render to all targets, front-to-back.
   mWidgetRenderView->Render();
 }
 
 void
 FrameBuilder::AssignLayer(Layer* aLayer,
                           RenderViewMLGPU* aView,
                           const RenderTargetIntRect& aClipRect,
-                          Maybe<Polygon>&& aGeometry)
+                          Maybe<gfx::Polygon>&& aGeometry)
 {
   LayerMLGPU* layer = aLayer->AsHostLayer()->AsLayerMLGPU();
 
   if (ContainerLayer* container = aLayer->AsContainerLayer()) {
     // This returns false if we don't need to (or can't) process the layer any
     // further. This always returns false for non-leaf ContainerLayers.
     if (!ProcessContainerLayer(container, aView, aClipRect, aGeometry)) {
       return;
@@ -151,18 +153,18 @@ FrameBuilder::ProcessContainerLayer(Cont
   if (isFirstVisit && !layer->PrepareToRender(this, aClipRect)) {
     return false;
   }
 
   // If the container is not part of the invalid region, we don't draw it
   // or traverse it. Note that we do not pass the geometry here. Otherwise
   // we could decide the particular split is not visible, and because of the
   // check above, never bother traversing the container again.
-  IntRect boundingBox = layer->GetClippedBoundingBox(aView, Nothing());
-  const IntRect& invalidRect = aView->GetInvalidRect();
+  gfx::IntRect boundingBox = layer->GetClippedBoundingBox(aView, Nothing());
+  const gfx::IntRect& invalidRect = aView->GetInvalidRect();
   if (boundingBox.IsEmpty() || !invalidRect.Intersects(boundingBox)) {
     return false;
   }
 
   if (!aContainer->UseIntermediateSurface()) {
     // In case the layer previously required an intermediate surface, we
     // clear any intermediate render targets here.
     layer->ClearCachedResources();
@@ -186,17 +188,17 @@ FrameBuilder::ProcessContainerLayer(Cont
   }
   return true;
 }
 
 void
 FrameBuilder::ProcessChildList(ContainerLayer* aContainer,
                                RenderViewMLGPU* aView,
                                const RenderTargetIntRect& aParentClipRect,
-                               const Maybe<Polygon>& aParentGeometry)
+                               const Maybe<gfx::Polygon>& aParentGeometry)
 {
   nsTArray<LayerPolygon> polygons =
     aContainer->SortChildrenBy3DZOrder(ContainerLayer::SortMode::WITH_GEOMETRY);
 
   // Visit layers in front-to-back order.
   for (auto iter = polygons.rbegin(); iter != polygons.rend(); iter++) {
     LayerPolygon& entry = *iter;
     Layer* child = entry.layer;
@@ -204,17 +206,17 @@ FrameBuilder::ProcessChildList(Container
       continue;
     }
 
     RenderTargetIntRect clip = child->CalculateScissorRect(aParentClipRect);
     if (clip.IsEmpty()) {
       continue;
     }
 
-    Maybe<Polygon> geometry;
+    Maybe<gfx::Polygon> geometry;
     if (aParentGeometry && entry.geometry) {
       // Both parent and child are split.
       geometry = Some(aParentGeometry->ClipPolygon(*entry.geometry));
     } else if (aParentGeometry) {
       geometry = aParentGeometry;
     } else if (entry.geometry) {
       geometry = Move(entry.geometry);
     }
@@ -238,24 +240,24 @@ FrameBuilder::AddLayerToConstantBuffer(I
   LayerConstants* info = AllocateLayerInfo(aItem);
   if (!info) {
     return false;
   }
 
   // Note we do not use GetEffectiveTransformForBuffer, since we calculate
   // the correct scaling when we build texture coordinates.
   Layer* baseLayer = layer->GetLayer();
-  const Matrix4x4& transform = baseLayer->GetEffectiveTransform();
+  const gfx::Matrix4x4& transform = baseLayer->GetEffectiveTransform();
 
   memcpy(&info->transform, &transform._11, 64);
-  info->clipRect = Rect(layer->GetComputedClipRect().ToUnknownRect());
+  info->clipRect = gfx::Rect(layer->GetComputedClipRect().ToUnknownRect());
   info->maskIndex = 0;
   if (MaskOperation* op = layer->GetMask()) {
     // Note: we use 0 as an invalid index, and so indices are offset by 1.
-    Rect rect = op->ComputeMaskRect(baseLayer);
+    gfx::Rect rect = op->ComputeMaskRect(baseLayer);
     AddMaskRect(rect, &info->maskIndex);
   }
 
   if (aItem.geometry) {
     mLayerBufferMap.Put(layer, aItem.layerIndex);
   }
   return true;
 }
--- a/gfx/layers/mlgpu/LayerMLGPU.cpp
+++ b/gfx/layers/mlgpu/LayerMLGPU.cpp
@@ -120,16 +120,32 @@ LayerMLGPU::IsContentOpaque()
 }
 
 void
 LayerMLGPU::SetRegionToRender(LayerIntRegion&& aRegion)
 {
   SetShadowVisibleRegion(Move(aRegion));
 }
 
+void
+LayerMLGPU::SetLayerManager(HostLayerManager* aManager)
+{
+  LayerManagerMLGPU* manager = aManager->AsLayerManagerMLGPU();
+  MOZ_RELEASE_ASSERT(manager);
+
+  HostLayer::SetLayerManager(aManager);
+  GetLayer()->SetManager(manager, this);
+
+  if (CompositableHost* host = GetCompositableHost()) {
+    host->SetTextureSourceProvider(manager->GetTextureSourceProvider());
+  }
+
+  OnLayerManagerChange(manager);
+}
+
 RefLayerMLGPU::RefLayerMLGPU(LayerManagerMLGPU* aManager)
   : RefLayer(aManager, static_cast<HostLayer*>(this))
   , LayerMLGPU(aManager)
 {
 }
 
 RefLayerMLGPU::~RefLayerMLGPU()
 {
--- a/gfx/layers/mlgpu/LayerMLGPU.h
+++ b/gfx/layers/mlgpu/LayerMLGPU.h
@@ -100,16 +100,21 @@ protected:
   LayerManagerMLGPU* GetManager();
 
   void AddBoundsToView(FrameBuilder* aBuilder,
                        RenderViewMLGPU* aView,
                        Maybe<gfx::Polygon>&& aGeometry);
 
   void MarkPrepared();
 
+  // We don't want derivative layers overriding this directly - we provide a
+  // callback instead.
+  void SetLayerManager(HostLayerManager* aManager) override;
+  virtual void OnLayerManagerChange(LayerManagerMLGPU* aManager) {}
+
 private:
   // This is a monotonic counter used to check whether a layer appears twice
   // when 3d sorting.
   static uint64_t sFrameKey;
 
 protected:
   // These are set during PrepareToRender.
   RenderTargetIntRect mComputedClipRect;
--- a/gfx/layers/mlgpu/LayerManagerMLGPU.cpp
+++ b/gfx/layers/mlgpu/LayerManagerMLGPU.cpp
@@ -188,16 +188,22 @@ LayerManagerMLGPU::GetTextureFactoryIden
 }
 
 LayersBackend
 LayerManagerMLGPU::GetBackendType()
 {
   return mDevice ? mDevice->GetLayersBackend() : LayersBackend::LAYERS_NONE;
 }
 
+void
+LayerManagerMLGPU::SetRoot(Layer* aLayer)
+{
+  mRoot = aLayer;
+}
+
 bool
 LayerManagerMLGPU::BeginTransaction()
 {
   MOZ_ASSERT(!mTarget);
   return true;
 }
 
 void
@@ -265,16 +271,18 @@ LayerManagerMLGPU::EndTransaction(const 
   ComputeInvalidRegion();
 
   // Build and execute draw commands, and present.
   if (PreRender()) {
     Composite();
     PostRender();
   }
 
+  mTextureSourceProvider->FlushPendingNotifyNotUsed();
+
   // Finish composition.
   mLastCompositionEndTime = TimeStamp::Now();
 }
 
 void
 LayerManagerMLGPU::Composite()
 {
   AUTO_PROFILER_LABEL("LayerManagerMLGPU::Composite", GRAPHICS);
--- a/gfx/layers/mlgpu/LayerManagerMLGPU.h
+++ b/gfx/layers/mlgpu/LayerManagerMLGPU.h
@@ -33,17 +33,17 @@ public:
   ~LayerManagerMLGPU();
 
   bool Initialize();
   void Destroy() override;
 
   // LayerManager methods
   bool BeginTransaction() override;
   void BeginTransactionWithDrawTarget(gfx::DrawTarget* aTarget, const gfx::IntRect& aRect) override;
-  void SetRoot(Layer* aLayer) override { mRoot = aLayer; }
+  void SetRoot(Layer* aLayer) override;
   already_AddRefed<PaintedLayer> CreatePaintedLayer() override;
   already_AddRefed<ContainerLayer> CreateContainerLayer() override;
   already_AddRefed<ImageLayer> CreateImageLayer() override;
   already_AddRefed<ColorLayer> CreateColorLayer() override;
   already_AddRefed<TextLayer> CreateTextLayer() override;
   already_AddRefed<CanvasLayer> CreateCanvasLayer() override;
   already_AddRefed<RefLayer> CreateRefLayer() override;
   already_AddRefed<BorderLayer> CreateBorderLayer() override;
--- a/gfx/layers/mlgpu/PaintedLayerMLGPU.h
+++ b/gfx/layers/mlgpu/PaintedLayerMLGPU.h
@@ -59,25 +59,23 @@ public:
 
   MOZ_LAYER_DECL_NAME("PaintedLayerMLGPU", TYPE_PAINTED)
 
   void CleanupCachedResources();
 
 protected:
   void PrintInfo(std::stringstream& aStream, const char* aPrefix) override;
   bool OnPrepareToRender(FrameBuilder* aBuilder) override;
-  void ComputeDrawRegion();
 
   void CleanupResources();
 
 private:
   RefPtr<ContentHostTexture> mHost;
   RefPtr<TextureSource> mTexture;
   RefPtr<TextureSource> mTextureOnWhite;
   gfx::IntRegion mLocalDrawRegion;
   gfx::IntRegion mTextureRegion;
-  bool mComputedDrawRegion;
 };
 
 } // namespace layers
 } // namespace mozilla
 
 #endif
--- a/gfx/layers/moz.build
+++ b/gfx/layers/moz.build
@@ -97,16 +97,20 @@ EXPORTS.mozilla.layers += [
     'apz/public/IAPZCTreeManager.h',
     'apz/public/MetricsSharingController.h',
     # exporting things from apz/src is temporary until we extract a
     # proper interface for the code there
     'apz/src/APZCTreeManager.h',
     'apz/src/APZUtils.h',
     'apz/src/AsyncDragMetrics.h',
     'apz/src/AsyncPanZoomAnimation.h',
+    'apz/src/FocusState.h',
+    'apz/src/FocusTarget.h',
+    'apz/src/KeyboardMap.h',
+    'apz/src/KeyboardScrollAction.h',
     'apz/src/TouchCounter.h',
     'apz/testutil/APZTestData.h',
     'apz/util/ActiveElementManager.h',
     'apz/util/APZCCallbackHelper.h',
     'apz/util/APZEventState.h',
     'apz/util/APZThreadUtils.h',
     'apz/util/ChromeProcessController.h',
     'apz/util/ContentProcessController.h',
@@ -282,20 +286,26 @@ if CONFIG['MOZ_WIDGET_TOOLKIT'] == 'andr
 UNIFIED_SOURCES += [
     'AnimationHelper.cpp',
     'apz/public/IAPZCTreeManager.cpp',
     'apz/src/APZCTreeManager.cpp',
     'apz/src/AsyncPanZoomController.cpp',
     'apz/src/Axis.cpp',
     'apz/src/CheckerboardEvent.cpp',
     'apz/src/DragTracker.cpp',
+    'apz/src/FocusState.cpp',
+    'apz/src/FocusTarget.cpp',
+    'apz/src/GenericScrollAnimation.cpp',
     'apz/src/GestureEventListener.cpp',
     'apz/src/HitTestingTreeNode.cpp',
     'apz/src/InputBlockState.cpp',
     'apz/src/InputQueue.cpp',
+    'apz/src/KeyboardMap.cpp',
+    'apz/src/KeyboardScrollAction.cpp',
+    'apz/src/KeyboardScrollAnimation.cpp',
     'apz/src/OverscrollHandoffState.cpp',
     'apz/src/PotentialCheckerboardDurationTracker.cpp',
     'apz/src/QueuedInput.cpp',
     'apz/src/TouchCounter.cpp',
     'apz/src/WheelScrollAnimation.cpp',
     'apz/testutil/APZTestData.cpp',
     'apz/util/ActiveElementManager.cpp',
     'apz/util/APZCCallbackHelper.cpp',
--- a/gfx/layers/wr/WebRenderBridgeParent.cpp
+++ b/gfx/layers/wr/WebRenderBridgeParent.cpp
@@ -387,16 +387,18 @@ WebRenderBridgeParent::UpdateAPZ()
     return;
   }
   uint64_t rootLayersId = cbp->RootLayerTreeId();
   RefPtr<WebRenderBridgeParent> rootWrbp = cbp->GetWebRenderBridgeParent();
   if (!rootWrbp) {
     return;
   }
   if (RefPtr<APZCTreeManager> apzc = cbp->GetAPZCTreeManager()) {
+    apzc->UpdateFocusState(rootLayersId, GetLayersId(),
+                           rootWrbp->GetScrollData().GetFocusTarget());
     apzc->UpdateHitTestingTree(rootLayersId, rootWrbp->GetScrollData(),
         mScrollData.IsFirstPaint(), GetLayersId(),
         mScrollData.GetPaintSequenceNumber());
   }
 }
 
 bool
 WebRenderBridgeParent::PushAPZStateToWR(nsTArray<WrTransformProperty>& aTransformArray)
--- a/gfx/layers/wr/WebRenderCompositableHolder.cpp
+++ b/gfx/layers/wr/WebRenderCompositableHolder.cpp
@@ -106,28 +106,26 @@ WebRenderCompositableHolder::AddAsyncIma
 void
 WebRenderCompositableHolder::RemoveAsyncImagePipeline(wr::WebRenderAPI* aApi, const wr::PipelineId& aPipelineId)
 {
   if (mDestroyed) {
     return;
   }
 
   uint64_t id = wr::AsUint64(aPipelineId);
-  AsyncImagePipelineHolder* holder = mAsyncImagePipelineHolders.Get(id);
-  if (!holder) {
-    return;
+  if (auto entry = mAsyncImagePipelineHolders.Lookup(id)) {
+    AsyncImagePipelineHolder* holder = entry.Data();
+    ++mAsyncImageEpoch; // Update webrender epoch
+    aApi->ClearRootDisplayList(wr::NewEpoch(mAsyncImageEpoch), aPipelineId);
+    for (wr::ImageKey key : holder->mKeys) {
+      aApi->DeleteImage(key);
+    }
+    entry.Remove();
+    RemovePipeline(aPipelineId, wr::NewEpoch(mAsyncImageEpoch));
   }
-
-  ++mAsyncImageEpoch; // Update webrender epoch
-  aApi->ClearRootDisplayList(wr::NewEpoch(mAsyncImageEpoch), aPipelineId);
-  for (wr::ImageKey key : holder->mKeys) {
-    aApi->DeleteImage(key);
-  }
-  mAsyncImagePipelineHolders.Remove(id);
-  RemovePipeline(aPipelineId, wr::NewEpoch(mAsyncImageEpoch));
 }
 
 void
 WebRenderCompositableHolder::UpdateAsyncImagePipeline(const wr::PipelineId& aPipelineId,
                                                       const LayerRect& aScBounds,
                                                       const gfx::Matrix4x4& aScTransform,
                                                       const gfx::MaybeIntSize& aScaleToSize,
                                                       const WrImageRendering& aFilter,
@@ -328,31 +326,28 @@ WebRenderCompositableHolder::HoldExterna
 }
 
 void
 WebRenderCompositableHolder::Update(const wr::PipelineId& aPipelineId, const wr::Epoch& aEpoch)
 {
   if (mDestroyed) {
     return;
   }
-  PipelineTexturesHolder* holder = mPipelineTexturesHolders.Get(wr::AsUint64(aPipelineId));
-  if (!holder) {
-    return;
-  }
-
-  // Remove Pipeline
-  if (holder->mDestroyedEpoch.isSome() && holder->mDestroyedEpoch.ref() <= aEpoch) {
+  if (auto entry = mPipelineTexturesHolders.Lookup(wr::AsUint64(aPipelineId))) {
+    PipelineTexturesHolder* holder = entry.Data();
+    // Remove Pipeline
+    if (holder->mDestroyedEpoch.isSome() && holder->mDestroyedEpoch.ref() <= aEpoch) {
+      entry.Remove();
+      return;
+    }
 
-    mPipelineTexturesHolders.Remove(wr::AsUint64(aPipelineId));
-    return;
-  }
-
-  // Release TextureHosts based on Epoch
-  while (!holder->mTextureHosts.empty()) {
-    if (aEpoch <= holder->mTextureHosts.front().mEpoch) {
-      break;
+    // Release TextureHosts based on Epoch
+    while (!holder->mTextureHosts.empty()) {
+      if (aEpoch <= holder->mTextureHosts.front().mEpoch) {
+        break;
+      }
+      holder->mTextureHosts.pop();
     }
-    holder->mTextureHosts.pop();
   }
 }
 
 } // namespace layers
 } // namespace mozilla
--- a/gfx/layers/wr/WebRenderDisplayItemLayer.cpp
+++ b/gfx/layers/wr/WebRenderDisplayItemLayer.cpp
@@ -117,17 +117,17 @@ WebRenderDisplayItemLayer::PushItemAsBlo
       LayoutDeviceRect::FromAppUnits(mItem->GetBounds(mBuilder, &snap), appUnitsPerDevPixel),
       PixelCastJustification::WebRenderHasUnitResolution);
   LayerIntSize imageSize = RoundedToInt(bounds.Size());
   LayerRect imageRect;
   imageRect.SizeTo(LayerSize(imageSize));
 
   RefPtr<gfx::DrawEventRecorderMemory> recorder = MakeAndAddRef<gfx::DrawEventRecorderMemory>();
   RefPtr<gfx::DrawTarget> dummyDt =
-    gfx::Factory::CreateDrawTarget(gfx::BackendType::SKIA, IntSize(1, 1), gfx::SurfaceFormat::B8G8R8X8);
+    gfx::Factory::CreateDrawTarget(gfx::BackendType::SKIA, gfx::IntSize(1, 1), gfx::SurfaceFormat::B8G8R8X8);
   RefPtr<gfx::DrawTarget> dt = gfx::Factory::CreateRecordingDrawTarget(recorder, dummyDt, imageSize.ToUnknownSize());
   LayerPoint offset = ViewAs<LayerPixel>(
       LayoutDevicePoint::FromAppUnits(mItem->ToReferenceFrame(), appUnitsPerDevPixel),
       PixelCastJustification::WebRenderHasUnitResolution);
 
   {
     dt->ClearRect(imageRect.ToUnknownRect());
     RefPtr<gfxContext> context = gfxContext::CreateOrNull(dt, offset.ToUnknownPoint());
--- a/gfx/layers/wr/WebRenderLayerManager.cpp
+++ b/gfx/layers/wr/WebRenderLayerManager.cpp
@@ -214,16 +214,19 @@ WebRenderLayerManager::EndTransactionInt
   if (mTransactionIncomplete) {
     DiscardLocalImages();
     WrBridge()->ProcessWebRenderParentCommands();
     return false;
   }
 
   WebRenderScrollData scrollData;
   if (AsyncPanZoomEnabled()) {
+    scrollData.SetFocusTarget(mFocusTarget);
+    mFocusTarget = FocusTarget();
+
     if (mIsFirstPaint) {
       scrollData.SetIsFirstPaint();
       mIsFirstPaint = false;
     }
     scrollData.SetPaintSequenceNumber(mPaintSequenceNumber);
     if (mRoot) {
       PopulateScrollData(scrollData, mRoot.get());
     }
@@ -246,16 +249,22 @@ WebRenderLayerManager::EndTransactionInt
   // this may result in Layers being deleted, which results in
   // PLayer::Send__delete__() and DeallocShmem()
   mKeepAlive.Clear();
   ClearMutatedLayers();
 
   return true;
 }
 
+void
+WebRenderLayerManager::SetFocusTarget(const FocusTarget& aFocusTarget)
+{
+  mFocusTarget = aFocusTarget;
+}
+
 bool
 WebRenderLayerManager::AsyncPanZoomEnabled() const
 {
   return mWidget->AsyncPanZoomEnabled();
 }
 
 void
 WebRenderLayerManager::MakeSnapshotIfRequired(LayoutDeviceIntSize aSize)
--- a/gfx/layers/wr/WebRenderLayerManager.h
+++ b/gfx/layers/wr/WebRenderLayerManager.h
@@ -4,16 +4,17 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #ifndef GFX_WEBRENDERLAYERMANAGER_H
 #define GFX_WEBRENDERLAYERMANAGER_H
 
 #include "Layers.h"
 #include "mozilla/MozPromise.h"
 #include "mozilla/layers/APZTestData.h"
+#include "mozilla/layers/FocusTarget.h"
 #include "mozilla/layers/TransactionIdAllocator.h"
 #include "mozilla/webrender/WebRenderTypes.h"
 
 class nsIWidget;
 
 namespace mozilla {
 namespace layers {
 
@@ -93,16 +94,17 @@ public:
   virtual void ScheduleComposite() override;
 
   virtual void SetNeedsComposite(bool aNeedsComposite) override
   {
     mNeedsComposite = aNeedsComposite;
   }
   virtual bool NeedsComposite() const override { return mNeedsComposite; }
   virtual void SetIsFirstPaint() override { mIsFirstPaint = true; }
+  virtual void SetFocusTarget(const FocusTarget& aFocusTarget) override;
 
   bool AsyncPanZoomEnabled() const override;
 
   DrawPaintedLayerCallback GetPaintedLayerCallback() const
   { return mPaintedLayerCallback; }
 
   void* GetPaintedLayerCallbackData() const
   { return mPaintedLayerCallbackData; }
@@ -175,16 +177,17 @@ private:
   // if it was a mutated layers.
   void AddMutatedLayer(Layer* aLayer);
   void ClearMutatedLayers();
   LayerRefArray mMutatedLayers;
   bool mTransactionIncomplete;
 
   bool mNeedsComposite;
   bool mIsFirstPaint;
+  FocusTarget mFocusTarget;
 
  // When we're doing a transaction in order to draw to a non-default
  // target, the layers transaction is only performed in order to send
  // a PLayers:Update.  We save the original non-default target to
  // mTarget, and then perform the transaction. After the transaction ends,
  // we send a message to our remote side to capture the actual pixels
  // being drawn to the default target, and then copy those pixels
  // back to mTarget.
--- a/gfx/layers/wr/WebRenderScrollData.cpp
+++ b/gfx/layers/wr/WebRenderScrollData.cpp
@@ -140,16 +140,22 @@ WebRenderScrollData::GetLayerData(size_t
 const ScrollMetadata&
 WebRenderScrollData::GetScrollMetadata(size_t aIndex) const
 {
   MOZ_ASSERT(aIndex < mScrollMetadatas.Length());
   return mScrollMetadatas[aIndex];
 }
 
 void
+WebRenderScrollData::SetFocusTarget(const FocusTarget& aFocusTarget)
+{
+  mFocusTarget = aFocusTarget;
+}
+
+void
 WebRenderScrollData::SetIsFirstPaint()
 {
   mIsFirstPaint = true;
 }
 
 bool
 WebRenderScrollData::IsFirstPaint() const
 {
--- a/gfx/layers/wr/WebRenderScrollData.h
+++ b/gfx/layers/wr/WebRenderScrollData.h
@@ -10,16 +10,17 @@
 
 #include "chrome/common/ipc_message_utils.h"
 #include "FrameMetrics.h"
 #include "ipc/IPCMessageUtils.h"
 #include "LayersTypes.h"
 #include "mozilla/GfxMessageUtils.h"
 #include "mozilla/layers/LayerAttributes.h"
 #include "mozilla/layers/LayersMessageUtils.h"
+#include "mozilla/layers/FocusTarget.h"
 #include "mozilla/Maybe.h"
 #include "nsTArrayForwardDeclare.h"
 
 namespace mozilla {
 namespace layers {
 
 class Layer;
 class WebRenderScrollData;
@@ -118,16 +119,19 @@ public:
 
   // Return a pointer to the scroll data at the given index. Use with caution,
   // as the pointer may be invalidated if this WebRenderScrollData is mutated.
   WebRenderLayerScrollData* GetLayerDataMutable(size_t aIndex);
   const WebRenderLayerScrollData* GetLayerData(size_t aIndex) const;
 
   const ScrollMetadata& GetScrollMetadata(size_t aIndex) const;
 
+  const FocusTarget& GetFocusTarget() const { return mFocusTarget; }
+  void SetFocusTarget(const FocusTarget& aFocusTarget);
+
   void SetIsFirstPaint();
   bool IsFirstPaint() const;
   void SetPaintSequenceNumber(uint32_t aPaintSequenceNumber);
   uint32_t GetPaintSequenceNumber() const;
 
   friend struct IPC::ParamTraits<WebRenderScrollData>;
 
 private:
@@ -146,16 +150,19 @@ private:
   // A list of per-layer scroll data objects, generated via a depth-first,
   // pre-order, last-to-first traversal of the layer tree (i.e. a recursive
   // traversal where a node N first pushes itself, followed by its children in
   // last-to-first order). Each layer's scroll data object knows how many
   // descendants that layer had, which allows reconstructing the traversal on the
   // other side.
   nsTArray<WebRenderLayerScrollData> mLayerScrollData;
 
+  // The focus information for this layer tree
+  FocusTarget mFocusTarget;
+
   bool mIsFirstPaint;
   uint32_t mPaintSequenceNumber;
 };
 
 } // namespace layers
 } // namespace mozilla
 
 namespace IPC {
@@ -220,25 +227,27 @@ struct ParamTraits<mozilla::layers::WebR
 {
   typedef mozilla::layers::WebRenderScrollData paramType;
 
   static void
   Write(Message* aMsg, const paramType& aParam)
   {
     WriteParam(aMsg, aParam.mScrollMetadatas);
     WriteParam(aMsg, aParam.mLayerScrollData);
+    WriteParam(aMsg, aParam.mFocusTarget);
     WriteParam(aMsg, aParam.mIsFirstPaint);
     WriteParam(aMsg, aParam.mPaintSequenceNumber);
   }
 
   static bool
   Read(const Message* aMsg, PickleIterator* aIter, paramType* aResult)
   {
     return ReadParam(aMsg, aIter, &aResult->mScrollMetadatas)
         && ReadParam(aMsg, aIter, &aResult->mLayerScrollData)
+        && ReadParam(aMsg, aIter, &aResult->mFocusTarget)
         && ReadParam(aMsg, aIter, &aResult->mIsFirstPaint)
         && ReadParam(aMsg, aIter, &aResult->mPaintSequenceNumber);
   }
 };
 
 } // namespace IPC
 
 #endif /* GFX_WEBRENDERSCROLLDATA_H */
--- a/gfx/src/FilterSupport.cpp
+++ b/gfx/src/FilterSupport.cpp
@@ -2127,29 +2127,27 @@ AttributeMap::GetType(FilterAttribute* a
 #define MAKE_ATTRIBUTE_HANDLERS_BASIC(type, typeLabel, defaultValue) \
   type                                                               \
   AttributeMap::Get##typeLabel(AttributeName aName) const {          \
     Attribute* value = mMap.Get(aName);                              \
     return value ? value->As##typeLabel() : defaultValue;            \
   }                                                                  \
   void                                                               \
   AttributeMap::Set(AttributeName aName, type aValue) {              \
-    mMap.Remove(aName);                                              \
     mMap.Put(aName, new Attribute(aValue));                          \
   }
 
 #define MAKE_ATTRIBUTE_HANDLERS_CLASS(className)                     \
   className                                                          \
   AttributeMap::Get##className(AttributeName aName) const {          \
     Attribute* value = mMap.Get(aName);                              \
     return value ? value->As##className() : className();             \
   }                                                                  \
   void                                                               \
   AttributeMap::Set(AttributeName aName, const className& aValue) {  \
-    mMap.Remove(aName);                                              \
     mMap.Put(aName, new Attribute(aValue));                          \
   }
 
 MAKE_ATTRIBUTE_HANDLERS_BASIC(bool, Bool, false)
 MAKE_ATTRIBUTE_HANDLERS_BASIC(uint32_t, Uint, 0)
 MAKE_ATTRIBUTE_HANDLERS_BASIC(float, Float, 0)
 MAKE_ATTRIBUTE_HANDLERS_CLASS(Size)
 MAKE_ATTRIBUTE_HANDLERS_CLASS(IntSize)
@@ -2161,25 +2159,21 @@ MAKE_ATTRIBUTE_HANDLERS_CLASS(Color)
 MAKE_ATTRIBUTE_HANDLERS_CLASS(AttributeMap)
 
 #undef MAKE_ATTRIBUTE_HANDLERS_BASIC
 #undef MAKE_ATTRIBUTE_HANDLERS_CLASS
 
 const nsTArray<float>&
 AttributeMap::GetFloats(AttributeName aName) const
 {
-  Attribute* value = mMap.Get(aName);
-  if (!value) {
-    value = new Attribute(nullptr, 0);
-    mMap.Put(aName, value);
-  }
+  Attribute* value = mMap.LookupForAdd(aName).OrInsert(
+    [] () { return new Attribute(nullptr, 0); });
   return value->AsFloats();
 }
 
 void
 AttributeMap::Set(AttributeName aName, const float* aValues, int32_t aLength)
 {
-  mMap.Remove(aName);
   mMap.Put(aName, new Attribute(aValues, aLength));
 }
 
 } // namespace gfx
 } // namespace mozilla
--- a/gfx/thebes/gfxFontEntry.cpp
+++ b/gfx/thebes/gfxFontEntry.cpp
@@ -692,20 +692,19 @@ gfxFontEntry::GrGetTable(const void *aAp
 }
 
 /*static*/ void
 gfxFontEntry::GrReleaseTable(const void *aAppFaceHandle,
                              const void *aTableBuffer)
 {
     gfxFontEntry *fontEntry =
         static_cast<gfxFontEntry*>(const_cast<void*>(aAppFaceHandle));
-    void *data;
-    if (fontEntry->mGrTableMap->Get(aTableBuffer, &data)) {
-        fontEntry->mGrTableMap->Remove(aTableBuffer);
-        hb_blob_destroy(static_cast<hb_blob_t*>(data));
+    if (auto entry = fontEntry->mGrTableMap->Lookup(aTableBuffer)) {
+        hb_blob_destroy(static_cast<hb_blob_t*>(entry.Data()));
+        entry.Remove();
     }
 }
 
 gr_face*
 gfxFontEntry::GetGrFace()
 {
     if (!mGrFaceInitialized) {
         gr_face_ops faceOps = {
--- a/gfx/thebes/gfxPlatform.cpp
+++ b/gfx/thebes/gfxPlatform.cpp
@@ -2630,16 +2630,20 @@ gfxPlatform::GetApzSupportInfo(mozilla::
 
   if (SupportsApzTouchInput()) {
     aObj.DefineProperty("ApzTouchInput", 1);
   }
 
   if (SupportsApzDragInput()) {
     aObj.DefineProperty("ApzDragInput", 1);
   }
+
+  if (SupportsApzKeyboardInput() && !gfxPrefs::AccessibilityBrowseWithCaret()) {
+    aObj.DefineProperty("ApzKeyboardInput", 1);
+  }
 }
 
 void
 gfxPlatform::GetTilesSupportInfo(mozilla::widget::InfoObject& aObj)
 {
   if (!gfxPrefs::LayersTilesEnabled()) {
     return;
   }
@@ -2799,16 +2803,22 @@ gfxPlatform::SupportsApzTouchInput() con
 }
 
 bool
 gfxPlatform::SupportsApzDragInput() const
 {
   return gfxPrefs::APZDragEnabled();
 }
 
+bool
+gfxPlatform::SupportsApzKeyboardInput() const
+{
+  return gfxPrefs::APZKeyboardEnabled();
+}
+
 void
 gfxPlatform::InitOpenGLConfig()
 {
   #ifdef XP_WIN
   // Don't enable by default on Windows, since it could show up in about:support even
   // though it'll never get used. Only attempt if user enables the pref
   if (!Preferences::GetBool("layers.prefer-opengl")){
     return;
--- a/gfx/thebes/gfxPlatform.h
+++ b/gfx/thebes/gfxPlatform.h
@@ -635,16 +635,17 @@ public:
     /**
      * Used to test which input types are handled via APZ.
      */
     virtual bool SupportsApzWheelInput() const {
       return false;
     }
     bool SupportsApzTouchInput() const;
     bool SupportsApzDragInput() const;
+    bool SupportsApzKeyboardInput() const;
 
     virtual void FlushContentDrawing() {}
 
     // If a device reset has occurred, schedule any necessary paints in the
     // widget. This should only be used within nsRefreshDriver.
     virtual void SchedulePaintIfDeviceReset() {}
 
     /**
--- a/gfx/thebes/gfxPrefs.h
+++ b/gfx/thebes/gfxPrefs.h
@@ -274,16 +274,18 @@ private:
       return this->mValue == Default();
     }
   };
 
   // This is where DECL_GFX_PREF for each of the preferences should go.
   // We will keep these in an alphabetical order to make it easier to see if
   // a method accessing a pref already exists. Just add yours in the list.
 
+  DECL_GFX_PREF(Live, "accessibility.browsewithcaret", AccessibilityBrowseWithCaret, bool, false);
+
   // The apz prefs are explained in AsyncPanZoomController.cpp
   DECL_GFX_PREF(Live, "apz.allow_checkerboarding",             APZAllowCheckerboarding, bool, true);
   DECL_GFX_PREF(Live, "apz.allow_immediate_handoff",           APZAllowImmediateHandoff, bool, true);
   DECL_GFX_PREF(Live, "apz.allow_zooming",                     APZAllowZooming, bool, false);
   DECL_GFX_PREF(Live, "apz.axis_lock.breakout_angle",          APZAxisBreakoutAngle, float, float(M_PI / 8.0) /* 22.5 degrees */);
   DECL_GFX_PREF(Live, "apz.axis_lock.breakout_threshold",      APZAxisBreakoutThreshold, float, 1.0f / 32.0f);
   DECL_GFX_PREF(Live, "apz.axis_lock.direct_pan_angle",        APZAllowedDirectPanAngle, float, float(M_PI / 3.0) /* 60 degrees */);
   DECL_GFX_PREF(Live, "apz.axis_lock.lock_angle",              APZAxisLockAngle, float, float(M_PI / 6.0) /* 30 degrees */);
@@ -305,16 +307,17 @@ private:
   DECL_GFX_PREF(Once, "apz.fling_curve_function_y1",           APZCurveFunctionY1, float, 0.0f);
   DECL_GFX_PREF(Once, "apz.fling_curve_function_y2",           APZCurveFunctionY2, float, 1.0f);
   DECL_GFX_PREF(Live, "apz.fling_curve_threshold_inches_per_ms", APZCurveThreshold, float, -1.0f);
   DECL_GFX_PREF(Live, "apz.fling_friction",                    APZFlingFriction, float, 0.002f);
   DECL_GFX_PREF(Live, "apz.fling_min_velocity_threshold",      APZFlingMinVelocityThreshold, float, 0.5f);
   DECL_GFX_PREF(Live, "apz.fling_stop_on_tap_threshold",       APZFlingStopOnTapThreshold, float, 0.05f);
   DECL_GFX_PREF(Live, "apz.fling_stopped_threshold",           APZFlingStoppedThreshold, float, 0.01f);
   DECL_GFX_PREF(Live, "apz.highlight_checkerboarded_areas",    APZHighlightCheckerboardedAreas, bool, false);
+  DECL_GFX_PREF(Once, "apz.keyboard.enabled",                  APZKeyboardEnabled, bool, false);
   DECL_GFX_PREF(Live, "apz.max_velocity_inches_per_ms",        APZMaxVelocity, float, -1.0f);
   DECL_GFX_PREF(Once, "apz.max_velocity_queue_size",           APZMaxVelocityQueueSize, uint32_t, 5);
   DECL_GFX_PREF(Live, "apz.min_skate_speed",                   APZMinSkateSpeed, float, 1.0f);
   DECL_GFX_PREF(Live, "apz.minimap.enabled",                   APZMinimap, bool, false);
   DECL_GFX_PREF(Live, "apz.minimap.visibility.enabled",        APZMinimapVisibilityEnabled, bool, false);
   DECL_GFX_PREF(Live, "apz.overscroll.enabled",                APZOverscrollEnabled, bool, false);
   DECL_GFX_PREF(Live, "apz.overscroll.min_pan_distance_ratio", APZMinPanDistanceRatio, float, 1.0f);
   DECL_GFX_PREF(Live, "apz.overscroll.spring_friction",        APZOverscrollSpringFriction, float, 0.015f);
@@ -361,21 +364,30 @@ private:
   DECL_GFX_PREF(Live, "dom.w3c_pointer_events.enabled",        PointerEventsEnabled, bool, false);
   DECL_GFX_PREF(Live, "dom.w3c_touch_events.enabled",          TouchEventsEnabled, int32_t, 0);
 
   DECL_GFX_PREF(Live, "general.smoothScroll",                  SmoothScrollEnabled, bool, true);
   DECL_GFX_PREF(Live, "general.smoothScroll.currentVelocityWeighting",
                 SmoothScrollCurrentVelocityWeighting, float, 0.25);
   DECL_GFX_PREF(Live, "general.smoothScroll.durationToIntervalRatio",
                 SmoothScrollDurationToIntervalRatio, int32_t, 200);
+  DECL_GFX_PREF(Live, "general.smoothScroll.lines",            LineSmoothScrollEnabled, bool, true);
+  DECL_GFX_PREF(Live, "general.smoothScroll.lines.durationMaxMS",
+                LineSmoothScrollMaxDurationMs, int32_t, 150);
+  DECL_GFX_PREF(Live, "general.smoothScroll.lines.durationMinMS",
+                LineSmoothScrollMinDurationMs, int32_t, 150);
   DECL_GFX_PREF(Live, "general.smoothScroll.mouseWheel",       WheelSmoothScrollEnabled, bool, true);
   DECL_GFX_PREF(Live, "general.smoothScroll.mouseWheel.durationMaxMS",
                 WheelSmoothScrollMaxDurationMs, int32_t, 400);
   DECL_GFX_PREF(Live, "general.smoothScroll.mouseWheel.durationMinMS",
                 WheelSmoothScrollMinDurationMs, int32_t, 200);
+  DECL_GFX_PREF(Live, "general.smoothScroll.other.durationMaxMS",
+                OtherSmoothScrollMaxDurationMs, int32_t, 150);
+  DECL_GFX_PREF(Live, "general.smoothScroll.other.durationMinMS",
+                OtherSmoothScrollMinDurationMs, int32_t, 150);
   DECL_GFX_PREF(Live, "general.smoothScroll.pages",            PageSmoothScrollEnabled, bool, true);
   DECL_GFX_PREF(Live, "general.smoothScroll.pages.durationMaxMS",
                 PageSmoothScrollMaxDurationMs, int32_t, 150);
   DECL_GFX_PREF(Live, "general.smoothScroll.pages.durationMinMS",
                 PageSmoothScrollMinDurationMs, int32_t, 150);
   DECL_GFX_PREF(Live, "general.smoothScroll.pixels",           PixelSmoothScrollEnabled, bool, true);
   DECL_GFX_PREF(Live, "general.smoothScroll.pixels.durationMaxMS",
                 PixelSmoothScrollMaxDurationMs, int32_t, 150);
@@ -655,16 +667,19 @@ private:
 
   DECL_GFX_PREF(Live, "nglayout.debug.widget_update_flashing", WidgetUpdateFlashing, bool, false);
 
   DECL_GFX_PREF(Once, "slider.snapMultiplier",                 SliderSnapMultiplier, int32_t, 0);
 
   DECL_GFX_PREF(Live, "test.events.async.enabled",             TestEventsAsyncEnabled, bool, false);
   DECL_GFX_PREF(Live, "test.mousescroll",                      MouseScrollTestingEnabled, bool, false);
 
+  DECL_GFX_PREF(Live, "toolkit.scrollbox.horizontalScrollDistance", ToolkitHorizontalScrollDistance, int32_t, 5);
+  DECL_GFX_PREF(Live, "toolkit.scrollbox.verticalScrollDistance",   ToolkitVerticalScrollDistance, int32_t, 3);
+
   DECL_GFX_PREF(Live, "ui.click_hold_context_menus.delay",     UiClickHoldContextMenusDelay, int32_t, 500);
 
   // WebGL (for pref access from Worker threads)
   DECL_GFX_PREF(Live, "webgl.all-angle-options",               WebGLAllANGLEOptions, bool, false);
   DECL_GFX_PREF(Live, "webgl.angle.force-d3d11",               WebGLANGLEForceD3D11, bool, false);
   DECL_GFX_PREF(Live, "webgl.angle.try-d3d11",                 WebGLANGLETryD3D11, bool, false);
   DECL_GFX_PREF(Live, "webgl.angle.force-warp",                WebGLANGLEForceWARP, bool, false);
   DECL_GFX_PREF(Live, "webgl.bypass-shader-validation",        WebGLBypassShaderValidator, bool, true);
--- a/gfx/thebes/gfxTextRun.h
+++ b/gfx/thebes/gfxTextRun.h
@@ -2,30 +2,35 @@
  * vim: set ts=4 et sw=4 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_TEXTRUN_H
 #define GFX_TEXTRUN_H
 
+#include <stdint.h>
+
 #include "gfxTypes.h"
-#include "nsString.h"
 #include "gfxPoint.h"
 #include "gfxFont.h"
 #include "gfxFontConstants.h"
-#include "nsTArray.h"
 #include "gfxSkipChars.h"
 #include "gfxPlatform.h"
 #include "mozilla/MemoryReporting.h"
+#include "mozilla/RefPtr.h"
+#include "nsPoint.h"
+#include "nsString.h"
+#include "nsTArray.h"
+#include "nsTextFrameUtils.h"
 #include "DrawMode.h"
 #include "harfbuzz/hb.h"
 #include "nsUnicodeScriptCodes.h"
 #include "nsColor.h"
-#include "nsTextFrameUtils.h"
+#include "X11UndefineNone.h"
 
 #ifdef DEBUG
 #include <stdio.h>
 #endif
 
 class gfxContext;
 class gfxFontGroup;
 class gfxUserFontEntry;
--- a/gfx/webrender_bindings/RenderThread.cpp
+++ b/gfx/webrender_bindings/RenderThread.cpp
@@ -284,36 +284,35 @@ RenderThread::DecPendingFrameCount(wr::W
   mPendingFrameCounts.Put(AsUint64(aWindowId), oldCount - 1);
 }
 
 void
 RenderThread::RegisterExternalImage(uint64_t aExternalImageId, already_AddRefed<RenderTextureHost> aTexture)
 {
   MutexAutoLock lock(mRenderTextureMapLock);
 
-  MOZ_ASSERT(!mRenderTextures.Get(aExternalImageId).get());
-  RefPtr<RenderTextureHost> texture(aTexture);
-  mRenderTextures.Put(aExternalImageId, Move(texture));
+  MOZ_ASSERT(!mRenderTextures.GetWeak(aExternalImageId));
+  mRenderTextures.Put(aExternalImageId, Move(aTexture));
 }
 
 void
 RenderThread::UnregisterExternalImage(uint64_t aExternalImageId)
 {
   MutexAutoLock lock(mRenderTextureMapLock);
-  MOZ_ASSERT(mRenderTextures.Get(aExternalImageId).get());
+  MOZ_ASSERT(mRenderTextures.GetWeak(aExternalImageId));
   if (!IsInRenderThread()) {
     // The RenderTextureHost should be released in render thread. So, post the
     // deletion task here.
     // The shmem and raw buffer are owned by compositor ipc channel. It's
     // possible that RenderTextureHost is still exist after the shmem/raw buffer
     // deletion. Then the buffer in RenderTextureHost becomes invalid. It's fine
     // for this situation. Gecko will only release the buffer if WR doesn't need
     // it. So, no one will access the invalid buffer in RenderTextureHost.
-    RefPtr<RenderTextureHost> texture = mRenderTextures.Get(aExternalImageId);
-    mRenderTextures.Remove(aExternalImageId);
+    RefPtr<RenderTextureHost> texture;
+    mRenderTextures.Remove(aExternalImageId, getter_AddRefs(texture));
     Loop()->PostTask(NewRunnableMethod<RefPtr<RenderTextureHost>>(
       "RenderThread::DeferredRenderTextureHostDestroy",
       this, &RenderThread::DeferredRenderTextureHostDestroy, Move(texture)
     ));
   } else {
     mRenderTextures.Remove(aExternalImageId);
   }
 }
@@ -325,18 +324,18 @@ RenderThread::DeferredRenderTextureHostD
 }
 
 RenderTextureHost*
 RenderThread::GetRenderTexture(WrExternalImageId aExternalImageId)
 {
   MOZ_ASSERT(IsInRenderThread());
 
   MutexAutoLock lock(mRenderTextureMapLock);
-  MOZ_ASSERT(mRenderTextures.Get(aExternalImageId.mHandle).get());
-  return mRenderTextures.Get(aExternalImageId.mHandle).get();
+  MOZ_ASSERT(mRenderTextures.GetWeak(aExternalImageId.mHandle));
+  return mRenderTextures.GetWeak(aExternalImageId.mHandle);
 }
 
 WebRenderThreadPool::WebRenderThreadPool()
 {
   mThreadPool = wr_thread_pool_new();
 }
 
 WebRenderThreadPool::~WebRenderThreadPool()
--- a/gfx/webrender_bindings/RenderThread.h
+++ b/gfx/webrender_bindings/RenderThread.h
@@ -7,16 +7,17 @@
 #ifndef MOZILLA_LAYERS_RENDERTHREAD_H
 #define MOZILLA_LAYERS_RENDERTHREAD_H
 
 #include "base/basictypes.h"            // for DISALLOW_EVIL_CONSTRUCTORS
 #include "base/platform_thread.h"       // for PlatformThreadId
 #include "base/thread.h"                // for Thread
 #include "base/message_loop.h"
 #include "nsISupportsImpl.h"
+#include "nsRefPtrHashtable.h"
 #include "ThreadSafeRefcountingWithMainThreadDestruction.h"
 #include "mozilla/Mutex.h"
 #include "mozilla/webrender/webrender_ffi.h"
 #include "mozilla/UniquePtr.h"
 #include "mozilla/webrender/WebRenderTypes.h"
 
 namespace mozilla {
 namespace wr {
@@ -150,15 +151,15 @@ private:
   WebRenderThreadPool mThreadPool;
 
   std::map<wr::WindowId, UniquePtr<RendererOGL>> mRenderers;
 
   Mutex mPendingFrameCountMapLock;
   nsDataHashtable<nsUint64HashKey, uint32_t> mPendingFrameCounts;
 
   Mutex mRenderTextureMapLock;
-  nsDataHashtable<nsUint64HashKey, RefPtr<RenderTextureHost> > mRenderTextures;
+  nsRefPtrHashtable<nsUint64HashKey, RenderTextureHost> mRenderTextures;
 };
 
 } // namespace wr
 } // namespace mozilla
 
 #endif
--- a/image/ProgressTracker.cpp
+++ b/image/ProgressTracker.cpp
@@ -466,19 +466,21 @@ ProgressTracker::AddObserver(IProgressOb
 bool
 ProgressTracker::RemoveObserver(IProgressObserver* aObserver)
 {
   MOZ_ASSERT(NS_IsMainThread());
   RefPtr<IProgressObserver> observer = aObserver;
 
   // Remove the observer from the list.
   bool removed = mObservers.Write([=](ObserverTable* aTable) {
-    bool removed = aTable->Get(observer, nullptr);
-    aTable->Remove(observer);
-    return removed;
+    if (auto entry = aTable->Lookup(observer)) {
+      entry.Remove();
+      return true;
+    }
+    return false;
   });
 
   // Observers can get confused if they don't get all the proper teardown
   // notifications. Part ways on good terms.
   if (removed && !aObserver->NotificationsDeferred()) {
     EmulateRequestFinished(aObserver);
   }
 
--- a/ipc/ipdl/sync-messages.ini
+++ b/ipc/ipdl/sync-messages.ini
@@ -816,18 +816,16 @@ description =
 [PBackgroundMutableFile::GetFileId]
 description =
 [PBackgroundIndexedDBUtils::GetFileReferences]
 description =
 [PBrowser::SyncMessage]
 description =
 [PBrowser::PPluginWidget]
 description =
-[PBrowser::GetWidgetNativeData]
-description =
 [PBrowser::DispatchFocusToTopLevelWindow]
 description =
 [PBrowser::NotifyIMEFocus]
 description =
 [PBrowser::NotifyIMEMouseButtonEvent]
 description =
 [PBrowser::RequestIMEToCommitComposition]
 description =
@@ -838,18 +836,16 @@ description =
 [PBrowser::IsParentWindowMainWidgetVisible]
 description =
 [PBrowser::GetDPI]
 description =
 [PBrowser::GetDefaultScale]
 description =
 [PBrowser::GetWidgetRounding]
 description =
-[PBrowser::BrowserFrameOpenWindow]
-description =
 [PBrowser::RequestNativeKeyBindings]
 description =
 [PBrowser::GetTabCount]
 description =
 [PBrowser::DispatchWheelEvent]
 description =
 [PBrowser::DispatchMouseEvent]
 description =
@@ -908,18 +904,16 @@ description =
 [PContent::EndDriverCrashGuard]
 description =
 [PContent::KeygenProcessValue]
 description =
 [PContent::KeygenProvideContent]
 description =
 [PContent::GetGraphicsDeviceInitData]
 description =
-[PContent::CreateWindow]
-description =
 [PContent::GetAndroidSystemInfo]
 description =
 [PContent::UngrabPointer]
 description =
 [PContent::RemovePermission]
 description =
 [PContent::GetA11yContentId]
 description =
@@ -962,17 +956,19 @@ description =
 [PAPZCTreeManager::ReceivePanGestureInputEvent]
 description =
 [PAPZCTreeManager::ReceivePinchGestureInputEvent]
 description =
 [PAPZCTreeManager::ReceiveTapGestureInputEvent]
 description =
 [PAPZCTreeManager::ReceiveScrollWheelInputEvent]
 description =
-[PAPZCTreeManager::TransformEventRefPoint]
+[PAPZCTreeManager::ProcessUnhandledEvent]
+description =
+[PAPZCTreeManager::ReceiveKeyboardInputEvent]
 description =
 [PCompositorBridge::Initialize]
 description =
 [PCompositorBridge::GetFrameUniformity]
 description =
 [PCompositorBridge::WillClose]
 description =
 [PCompositorBridge::Pause]
--- a/js/src/builtin/Object.cpp
+++ b/js/src/builtin/Object.cpp
@@ -588,16 +588,197 @@ obj_setPrototypeOf(JSContext* cx, unsign
     if (!SetPrototype(cx, obj, newProto))
         return false;
 
     /* Step 8. */
     args.rval().set(args[0]);
     return true;
 }
 
+static bool
+PropertyIsEnumerable(JSContext* cx, HandleObject obj, HandleId id, bool* enumerable)
+{
+    PropertyResult prop;
+    if (obj->isNative() &&
+        NativeLookupOwnProperty<NoGC>(cx, &obj->as<NativeObject>(), id, &prop))
+    {
+        if (!prop) {
+            *enumerable = false;
+            return true;
+        }
+
+        unsigned attrs = GetPropertyAttributes(obj, prop);
+        *enumerable = (attrs & JSPROP_ENUMERATE) != 0;
+        return true;
+    }
+
+    Rooted<PropertyDescriptor> desc(cx);
+    if (!GetOwnPropertyDescriptor(cx, obj, id, &desc))
+        return false;
+
+    *enumerable = desc.object() && desc.enumerable();
+    return true;
+}
+
+static bool
+TryAssignNative(JSContext* cx, HandleObject to, HandleObject from, bool* optimized)
+{
+    *optimized = false;
+
+    if (!from->isNative() || !to->isNative())
+        return true;
+
+    // Don't use the fast path if |from| may have extra indexed or lazy
+    // properties.
+    NativeObject* fromNative = &from->as<NativeObject>();
+    if (fromNative->getDenseInitializedLength() > 0 ||
+        fromNative->isIndexed() ||
+        fromNative->is<TypedArrayObject>() ||
+        fromNative->getClass()->getNewEnumerate() ||
+        fromNative->getClass()->getEnumerate())
+    {
+        return true;
+    }
+
+    // Get a list of |from| shapes. As long as from->lastProperty() == fromShape
+    // we can use this to speed up both the enumerability check and the GetProp.
+
+    using ShapeVector = GCVector<Shape*, 8>;
+    Rooted<ShapeVector> shapes(cx, ShapeVector(cx));
+
+    RootedShape fromShape(cx, fromNative->lastProperty());
+    for (Shape::Range<NoGC> r(fromShape); !r.empty(); r.popFront()) {
+        // Symbol properties need to be assigned last. For now fall back to the
+        // slow path if we see a symbol property.
+        if (MOZ_UNLIKELY(JSID_IS_SYMBOL(r.front().propidRaw())))
+            return true;
+        if (MOZ_UNLIKELY(!shapes.append(&r.front())))
+            return false;
+    }
+
+    *optimized = true;
+
+    RootedShape shape(cx);
+    RootedValue propValue(cx);
+    RootedId nextKey(cx);
+    RootedValue toReceiver(cx, ObjectValue(*to));
+
+    for (size_t i = shapes.length(); i > 0; i--) {
+        shape = shapes[i - 1];
+        nextKey = shape->propid();
+
+        // Ensure |from| is still native: a getter/setter might have turned
+        // |from| or |to| into an unboxed object or it could have been swapped
+        // with a non-native object.
+        if (MOZ_LIKELY(from->isNative() &&
+                       from->as<NativeObject>().lastProperty() == fromShape &&
+                       shape->hasDefaultGetter() &&
+                       shape->hasSlot()))
+        {
+            if (!shape->enumerable())
+                continue;
+            propValue = from->as<NativeObject>().getSlot(shape->slot());
+        } else {
+            // |from| changed shape or the property is not a data property, so
+            // we have to do the slower enumerability check and GetProp.
+            bool enumerable;
+            if (!PropertyIsEnumerable(cx, from, nextKey, &enumerable))
+                return false;
+            if (!enumerable)
+                continue;
+            if (!GetProperty(cx, from, from, nextKey, &propValue))
+                return false;
+        }
+
+        ObjectOpResult result;
+        if (MOZ_UNLIKELY(!SetProperty(cx, to, nextKey, propValue, toReceiver, result)))
+            return false;
+        if (MOZ_UNLIKELY(!result.checkStrict(cx, to, nextKey)))
+            return false;
+    }
+
+    return true;
+}
+
+static bool
+AssignSlow(JSContext* cx, HandleObject to, HandleObject from)
+{
+    // Step 4.b.ii.
+    AutoIdVector keys(cx);
+    if (!GetPropertyKeys(cx, from, JSITER_OWNONLY | JSITER_HIDDEN | JSITER_SYMBOLS, &keys))
+        return false;
+
+    // Step 4.c.
+    RootedId nextKey(cx);
+    RootedValue propValue(cx);
+    for (size_t i = 0, len = keys.length(); i < len; i++) {
+        nextKey = keys[i];
+
+        // Step 4.c.i.
+        bool enumerable;
+        if (MOZ_UNLIKELY(!PropertyIsEnumerable(cx, from, nextKey, &enumerable)))
+            return false;
+        if (!enumerable)
+            continue;
+
+        // Step 4.c.ii.1.
+        if (MOZ_UNLIKELY(!GetProperty(cx, from, from, nextKey, &propValue)))
+            return false;
+
+        // Step 4.c.ii.2.
+        if (MOZ_UNLIKELY(!SetProperty(cx, to, nextKey, propValue)))
+            return false;
+    }
+
+    return true;
+}
+
+// ES2018 draft rev 48ad2688d8f964da3ea8c11163ef20eb126fb8a4
+// 19.1.2.1 Object.assign(target, ...sources)
+static bool
+obj_assign(JSContext* cx, unsigned argc, Value* vp)
+{
+    CallArgs args = CallArgsFromVp(argc, vp);
+
+    // Step 1.
+    RootedObject to(cx, ToObject(cx, args.get(0)));
+    if (!to)
+        return false;
+
+    // Note: step 2 is implicit. If there are 0 arguments, ToObject throws. If
+    // there's 1 argument, the loop below is a no-op.
+
+    // Step 4.
+    RootedObject from(cx);
+    for (size_t i = 1; i < args.length(); i++) {
+        // Step 4.a.
+        if (args[i].isNullOrUndefined())
+            continue;
+
+        // Step 4.b.i.
+        from = ToObject(cx, args[i]);
+        if (!from)
+            return false;
+
+        // Steps 4.b.ii, 4.c.
+        bool optimized;
+        if (!TryAssignNative(cx, to, from, &optimized))
+            return false;
+        if (optimized)
+            continue;
+
+        if (!AssignSlow(cx, to, from))
+            return false;
+    }
+
+    // Step 5.
+    args.rval().setObject(*to);
+    return true;
+}
+
 #if JS_HAS_OBJ_WATCHPOINT
 
 bool
 js::WatchHandler(JSContext* cx, JSObject* obj_, jsid id_, const JS::Value& old,
                  JS::Value* nvp, void* closure)
 {
     RootedObject obj(cx, obj_);
     RootedId id(cx, id_);
@@ -1290,17 +1471,17 @@ static const JSFunctionSpec object_metho
 static const JSPropertySpec object_properties[] = {
 #if JS_HAS_OBJ_PROTO_PROP
     JS_PSGS("__proto__", ProtoGetter, ProtoSetter, 0),
 #endif
     JS_PS_END
 };
 
 static const JSFunctionSpec object_static_methods[] = {
-    JS_SELF_HOSTED_FN("assign",        "ObjectStaticAssign",        2, 0),
+    JS_FN("assign",                    obj_assign,                  2, 0),
     JS_SELF_HOSTED_FN("getPrototypeOf", "ObjectGetPrototypeOf",     1, 0),
     JS_FN("setPrototypeOf",            obj_setPrototypeOf,          2, 0),
     JS_FN("getOwnPropertyDescriptor",  obj_getOwnPropertyDescriptor,2, 0),
     JS_SELF_HOSTED_FN("getOwnPropertyDescriptors", "ObjectGetOwnPropertyDescriptors", 1, 0),
     JS_FN("keys",                      obj_keys,                    1, 0),
     JS_FN("values",                    obj_values,                  1, 0),
     JS_FN("entries",                   obj_entries,                 1, 0),
     JS_FN("is",                        obj_is,                      2, 0),
--- a/js/src/builtin/Object.js
+++ b/js/src/builtin/Object.js
@@ -1,51 +1,12 @@
 /* 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/. */
 
-// ES6 draft rev36 2015-03-17 19.1.2.1
-function ObjectStaticAssign(target, firstSource) {
-    // Steps 1-2.
-    var to = ToObject(target);
-
-    // Step 3.
-    if (arguments.length < 2)
-        return to;
-
-    // Steps 4-5.
-    for (var i = 1; i < arguments.length; i++) {
-        // Step 5.a.
-        var nextSource = arguments[i];
-        if (nextSource === null || nextSource === undefined)
-            continue;
-
-        // Steps 5.b.i-ii.
-        var from = ToObject(nextSource);
-
-        // Steps 5.b.iii-iv.
-        var keys = OwnPropertyKeys(from, JSITER_OWNONLY | JSITER_HIDDEN | JSITER_SYMBOLS);
-
-        // Step 5.c.
-        for (var nextIndex = 0, len = keys.length; nextIndex < len; nextIndex++) {
-            var nextKey = keys[nextIndex];
-
-            // Steps 5.c.i-iii. We abbreviate this by calling propertyIsEnumerable
-            // which is faster and returns false for not defined properties.
-            if (callFunction(std_Object_propertyIsEnumerable, from, nextKey)) {
-                // Steps 5.c.iii.1-4.
-                to[nextKey] = from[nextKey];
-            }
-        }
-    }
-
-    // Step 6.
-    return to;
-}
-
 // ES stage 4 proposal
 function ObjectGetOwnPropertyDescriptors(O) {
     // Step 1.
     var obj = ToObject(O);
 
     // Step 2.
     var keys = OwnPropertyKeys(obj, JSITER_OWNONLY | JSITER_HIDDEN | JSITER_SYMBOLS);
 
--- a/js/src/irregexp/RegExpCharacters.cpp
+++ b/js/src/irregexp/RegExpCharacters.cpp
@@ -128,8 +128,17 @@ const int js::irregexp::kSurrogateRangeC
 
 const int js::irregexp::kLineTerminatorRanges[] = {
     0x000A, 0x000A + 1, // LINE FEED (LF)
     0x000D, 0x000D + 1, // CARRIAGE RETURN (CR)
     0x2028, 0x2029 + 1, // LINE SEPARATOR..PARAGRAPH SEPARATOR
     0xFFFF + 1
 };
 const int js::irregexp::kLineTerminatorRangeCount = 7;
+
+const int js::irregexp::kLineTerminatorAndSurrogateRanges[] = {
+    0x000A, 0x000A + 1, // LINE FEED (LF)
+    0x000D, 0x000D + 1, // CARRIAGE RETURN (CR)
+    0x2028, 0x2029 + 1, // LINE SEPARATOR..PARAGRAPH SEPARATOR
+    0xD800, 0xDFFF + 1, // <Lead Surrogate Min>..<Trail Surrogate Max>
+    0xFFFF + 1
+};
+const int js::irregexp::kLineTerminatorAndSurrogateRangeCount = 9;
--- a/js/src/irregexp/RegExpCharacters.h
+++ b/js/src/irregexp/RegExpCharacters.h
@@ -80,11 +80,15 @@ extern const int kDigitAndSurrogateRange
 // The range of all surrogate characters.
 extern const int kSurrogateRanges[];
 extern const int kSurrogateRangeCount;
 
 // Line terminators as defined in ES2016, 11.3 LineTerminator.
 extern const int kLineTerminatorRanges[];
 extern const int kLineTerminatorRangeCount;
 
+// Line terminators and surrogate characters.
+extern const int kLineTerminatorAndSurrogateRanges[];
+extern const int kLineTerminatorAndSurrogateRangeCount;
+
 } } // namespace js::irregexp
 
 #endif // V8_JSREGEXPCHARACTERS_H_
--- a/js/src/irregexp/RegExpEngine.cpp
+++ b/js/src/irregexp/RegExpEngine.cpp
@@ -80,20 +80,20 @@ AddClass(const int* elmv, int elmc,
     elmc--;
     MOZ_ASSERT(elmv[elmc] == 0x10000);
     for (int i = 0; i < elmc; i += 2) {
         MOZ_ASSERT(elmv[i] < elmv[i + 1]);
         ranges->append(CharacterRange(elmv[i], elmv[i + 1] - 1));
     }
 }
 
-static void
-AddClassNegated(const int* elmv,
-                int elmc,
-                CharacterRangeVector* ranges)
+void
+js::irregexp::AddClassNegated(const int* elmv,
+                              int elmc,
+                              CharacterRangeVector* ranges)
 {
     elmc--;
     MOZ_ASSERT(elmv[elmc] == 0x10000);
     MOZ_ASSERT(elmv[0] != 0x0000);
     MOZ_ASSERT(elmv[elmc-1] != kMaxUtf16CodeUnit);
     char16_t last = 0x0000;
     for (int i = 0; i < elmc; i += 2) {
         MOZ_ASSERT(last <= elmv[i] - 1);
@@ -191,17 +191,17 @@ RangesContainLatin1Equivalents(const Cha
     }
     return false;
 }
 
 static const size_t kEcma262UnCanonicalizeMaxWidth = 4;
 
 // Returns the number of characters in the equivalence class, omitting those
 // that cannot occur in the source string if it is a one byte string.
-static int
+static MOZ_ALWAYS_INLINE int
 GetCaseIndependentLetters(char16_t character,
                           bool latin1_subject,
                           bool unicode,
                           const char16_t* choices,
                           size_t choices_length,
                           char16_t* letters)
 {
     size_t count = 0;
@@ -292,16 +292,20 @@ CharacterRange::AddCaseEquivalents(bool 
     char16_t bottom = from();
     char16_t top = to();
 
     if (is_latin1 && !RangeContainsLatin1Equivalents(*this, unicode)) {
         if (bottom > kMaxOneByteCharCode)
             return;
         if (top > kMaxOneByteCharCode)
             top = kMaxOneByteCharCode;
+    } else {
+        // Nothing to do for surrogates.
+        if (bottom >= unicode::LeadSurrogateMin && top <= unicode::TrailSurrogateMax)
+            return;
     }
 
     for (char16_t c = bottom;; c++) {
         char16_t chars[kEcma262UnCanonicalizeMaxWidth];
         size_t length = GetCaseIndependentLetters(c, is_latin1, unicode, chars);
 
         for (size_t i = 0; i < length; i++) {
             char16_t other = chars[i];
@@ -831,17 +835,27 @@ void TextNode::MakeCaseIndependent(bool 
         if (elm.text_type() == TextElement::CHAR_CLASS) {
             RegExpCharacterClass* cc = elm.char_class();
 
             // None of the standard character classes is different in the case
             // independent case and it slows us down if we don't know that.
             if (cc->is_standard(alloc()))
                 continue;
 
+            // Similarly, there's nothing to do for the character class
+            // containing all characters except line terminators and surrogates.
+            // This one is added by UnicodeEverythingAtom.
             CharacterRangeVector& ranges = cc->ranges(alloc());
+            if (CompareInverseRanges(ranges,
+                                     kLineTerminatorAndSurrogateRanges,
+                                     kLineTerminatorAndSurrogateRangeCount))
+            {
+                continue;
+            }
+
             int range_count = ranges.length();
             for (int j = 0; j < range_count; j++)
                 ranges[j].AddCaseEquivalents(is_latin1, unicode, &ranges);
         }
     }
 }
 
 // -------------------------------------------------------------------
--- a/js/src/irregexp/RegExpEngine.h
+++ b/js/src/irregexp/RegExpEngine.h
@@ -1541,11 +1541,14 @@ class Analysis : public NodeVisitor
     bool is_latin1_;
     bool unicode_;
     const char* error_message_;
 
     Analysis(Analysis&) = delete;
     void operator=(Analysis&) = delete;
 };
 
+void
+AddClassNegated(const int* elmv, int elmc, CharacterRangeVector* ranges);
+
 } }  // namespace js::irregexp
 
 #endif  // V8_JSREGEXP_H_
--- a/js/src/irregexp/RegExpParser.cpp
+++ b/js/src/irregexp/RegExpParser.cpp
@@ -29,16 +29,17 @@
 // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
 #include "irregexp/RegExpParser.h"
 
 #include "mozilla/ArrayUtils.h"
 #include "mozilla/Move.h"
 
 #include "frontend/TokenStream.h"
+#include "irregexp/RegExpCharacters.h"
 #include "vm/ErrorReporting.h"
 #include "vm/StringBuffer.h"
 
 using namespace js;
 using namespace js::irregexp;
 
 using mozilla::Move;
 using mozilla::PointerRangeSize;
@@ -1413,21 +1414,19 @@ TrailSurrogateAtom(LifoAlloc* alloc, cha
 static inline RegExpTree*
 UnicodeEverythingAtom(LifoAlloc* alloc)
 {
     RegExpBuilder* builder = alloc->newInfallible<RegExpBuilder>(alloc);
 
     // everything except \x0a, \x0d, \u2028 and \u2029
 
     CharacterRangeVector* ranges = alloc->newInfallible<CharacterRangeVector>(*alloc);
-    ranges->append(CharacterRange::Range(0x0, 0x09));
-    ranges->append(CharacterRange::Range(0x0b, 0x0c));
-    ranges->append(CharacterRange::Range(0x0e, 0x2027));
-    ranges->append(CharacterRange::Range(0x202A, unicode::LeadSurrogateMin - 1));
-    ranges->append(CharacterRange::Range(unicode::TrailSurrogateMax + 1, unicode::UTF16Max));
+    AddClassNegated(kLineTerminatorAndSurrogateRanges,
+                    kLineTerminatorAndSurrogateRangeCount,
+                    ranges);
     builder->AddAtom(alloc->newInfallible<RegExpCharacterClass>(ranges, false));
 
     builder->NewAlternative();
 
     builder->AddAtom(RangeAtom(alloc, unicode::LeadSurrogateMin, unicode::LeadSurrogateMax));
     builder->AddAtom(NegativeLookahead(alloc, unicode::TrailSurrogateMin,
                                        unicode::TrailSurrogateMax));
 
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/TypedObject/bug1369774.js
@@ -0,0 +1,7 @@
+if (!this.hasOwnProperty("TypedObject"))
+    quit();
+var T = TypedObject;
+var S = new T.StructType({f: T.Object});
+var o = new S();
+for (var i = 0; i < 15; i++)
+    o.f = null;
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/basic/bug1372956.js
@@ -0,0 +1,5 @@
+// |jit-test| error: TypeError
+x = {};
+Array.prototype.push.call(x, 0);
+Object.freeze(x);
+