Merge autoland to mozilla-central. a=merge
authorCosmin Sabou <csabou@mozilla.com>
Sat, 02 Mar 2019 23:46:22 +0200
changeset 520037 42f9208ed5f3f4c98374661d05c5c74b8df4ff70
parent 520014 bf3cbcc825276823afba2778333cf62cfa75039c (current diff)
parent 520036 1a37e28f12b1bf3fd880333f612243c56f290842 (diff)
child 520041 997e98493dbcd55c95b4a6eb4a44f563cd6174e9
push id10862
push userffxbld-merge
push dateMon, 11 Mar 2019 13:01:11 +0000
treeherdermozilla-beta@a2e7f5c935da [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmerge
milestone67.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Merge autoland to mozilla-central. a=merge
browser/components/urlbar/tests/legacy/browser_switchtab_decodeuri.js
browser/components/urlbar/tests/legacy/browser_switchtab_override.js
browser/components/urlbar/tests/legacy/browser_urlbarAutoFillTrimURLs.js
browser/components/urlbar/tests/legacy/browser_urlbarAutofillPreserveCase.js
browser/components/urlbar/tests/legacy/browser_urlbarKeepStateAcrossTabSwitches.js
browser/components/urlbar/tests/legacy/browser_urlbarPrivateBrowsingWindowChange.js
browser/components/urlbar/tests/legacy/browser_urlbarRaceWithTabs.js
browser/components/urlbar/tests/legacy/browser_urlbarSearchTelemetry.js
browser/components/urlbar/tests/legacy/browser_urlbar_autoFill_backspaced.js
browser/components/urlbar/tests/legacy/browser_urlbar_canonize_on_autofill.js
browser/components/urlbar/tests/legacy/browser_urlbar_stop_pending.js
build/build-clang/D57636.diff
gfx/layers/apz/src/TouchCounter.cpp
gfx/layers/apz/src/TouchCounter.h
--- a/browser/base/content/test/performance/browser_startup_content.js
+++ b/browser/base/content/test/performance/browser_startup_content.js
@@ -81,33 +81,37 @@ const whitelist = {
     "resource://gre/modules/addons/Content.js",
   ]),
   processScripts: new Set([
     "chrome://global/content/process-content.js",
     "resource:///modules/ContentObservers.js",
     "data:,ChromeUtils.import('resource://gre/modules/ExtensionProcessScript.jsm')",
     "resource://devtools/client/jsonview/converter-observer.js",
     "resource://gre/modules/WebRequestContent.js",
-    "resource://webcompat/aboutPageProcessScript.js",
   ]),
 };
 
 // Items on this list are allowed to be loaded but not
 // required, as opposed to items in the main whitelist,
 // which are all required.
 const intermittently_loaded_whitelist = {
   modules: new Set([
     "resource://gre/modules/nsAsyncShutdown.jsm",
     "resource://gre/modules/sessionstore/Utils.jsm",
 
-    // Webcompat about:config front-end
+    // Webcompat about:config front-end. This is presently nightly-only and
+    // part of a system add-on which may not load early enough for the test.
     "resource://webcompat/AboutCompat.jsm",
   ]),
   frameScripts: new Set([]),
-  processScripts: new Set([]),
+  processScripts: new Set([
+    // Webcompat about:config front-end. This is presently nightly-only and
+    // part of a system add-on which may not load early enough for the test.
+    "resource://webcompat/aboutPageProcessScript.js",
+  ]),
 };
 
 const blacklist = {
   services: new Set([
     "@mozilla.org/base/telemetry-startup;1",
     "@mozilla.org/embedcomp/default-tooltiptextprovider;1",
     "@mozilla.org/push/Service;1",
   ]),
deleted file mode 100644
--- a/build/build-clang/D57636.diff
+++ /dev/null
@@ -1,193 +0,0 @@
-Index: include/clang/Basic/BuiltinsAArch64.def
-===================================================================
---- a/clang/include/clang/Basic/BuiltinsAArch64.def
-+++ b/clang/include/clang/Basic/BuiltinsAArch64.def
-@@ -203,8 +203,8 @@
- 
- TARGET_HEADER_BUILTIN(_ReadWriteBarrier, "v", "nh", "intrin.h", ALL_MS_LANGUAGES, "")
- TARGET_HEADER_BUILTIN(__getReg, "ULLii", "nh", "intrin.h", ALL_MS_LANGUAGES, "")
--TARGET_HEADER_BUILTIN(_ReadStatusReg,  "ii",  "nh", "intrin.h", ALL_MS_LANGUAGES, "")
--TARGET_HEADER_BUILTIN(_WriteStatusReg, "vii", "nh", "intrin.h", ALL_MS_LANGUAGES, "")
-+TARGET_HEADER_BUILTIN(_ReadStatusReg,  "LLii",  "nh", "intrin.h", ALL_MS_LANGUAGES, "")
-+TARGET_HEADER_BUILTIN(_WriteStatusReg, "viLLi", "nh", "intrin.h", ALL_MS_LANGUAGES, "")
- TARGET_HEADER_BUILTIN(_AddressOfReturnAddress, "v*", "nh", "intrin.h", ALL_MS_LANGUAGES, "")
- 
- #undef BUILTIN
-Index: lib/CodeGen/CGBuiltin.cpp
-===================================================================
---- a/clang/lib/CodeGen/CGBuiltin.cpp
-+++ b/clang/lib/CodeGen/CGBuiltin.cpp
-@@ -7062,19 +7062,16 @@
-     llvm::Value *Metadata = llvm::MetadataAsValue::get(Context, RegName);
- 
-     llvm::Type *RegisterType = Int64Ty;
--    llvm::Type *ValueType = Int32Ty;
-     llvm::Type *Types[] = { RegisterType };
- 
-     if (BuiltinID == AArch64::BI_ReadStatusReg) {
-       llvm::Value *F = CGM.getIntrinsic(llvm::Intrinsic::read_register, Types);
--      llvm::Value *Call = Builder.CreateCall(F, Metadata);
- 
--      return Builder.CreateTrunc(Call, ValueType);
-+      return Builder.CreateCall(F, Metadata);
-     }
- 
-     llvm::Value *F = CGM.getIntrinsic(llvm::Intrinsic::write_register, Types);
-     llvm::Value *ArgValue = EmitScalarExpr(E->getArg(1));
--    ArgValue = Builder.CreateZExt(ArgValue, RegisterType);
- 
-     return Builder.CreateCall(F, { Metadata, ArgValue });
-   }
-Index: lib/Headers/intrin.h
-===================================================================
---- a/clang/lib/Headers/intrin.h
-+++ b/clang/lib/Headers/intrin.h
-@@ -554,8 +554,8 @@
- #if defined(__aarch64__)
- unsigned __int64 __getReg(int);
- long _InterlockedAdd(long volatile *Addend, long Value);
--int _ReadStatusReg(int);
--void _WriteStatusReg(int, int);
-+__int64 _ReadStatusReg(int);
-+void _WriteStatusReg(int, __int64);
- 
- static inline unsigned short _byteswap_ushort (unsigned short val) {
-   return __builtin_bswap16(val);
-Index: test/CodeGen/arm64-microsoft-status-reg.cpp
-===================================================================
---- a/clang/test/CodeGen/arm64-microsoft-status-reg.cpp
-+++ b/clang/test/CodeGen/arm64-microsoft-status-reg.cpp
-@@ -23,87 +23,101 @@
- #define ARM64_TPIDRRO_EL0       ARM64_SYSREG(3,3,13, 0,3)  // Thread ID Register, User Read Only [CP15_TPIDRURO]
- #define ARM64_TPIDR_EL1         ARM64_SYSREG(3,0,13, 0,4)  // Thread ID Register, Privileged Only [CP15_TPIDRPRW]
- 
--void check_ReadWriteStatusReg(int v) {
--  int ret;
-+// From intrin.h
-+__int64 _ReadStatusReg(int);
-+void _WriteStatusReg(int, __int64);
-+
-+void check_ReadWriteStatusReg(__int64 v) {
-+  __int64 ret;
-   ret = _ReadStatusReg(ARM64_CNTVCT);
--// CHECK-ASM: mrs     x8, CNTVCT_EL0
--// CHECK-IR: call i64 @llvm.read_register.i64(metadata ![[MD2:.*]])
-+// CHECK-ASM: mrs     x0, CNTVCT_EL0
-+// CHECK-IR: %[[VAR:.*]] = call i64 @llvm.read_register.i64(metadata ![[MD2:.*]])
-+// CHECK-IR-NEXT: store i64 %[[VAR]]
- 
-   ret = _ReadStatusReg(ARM64_PMCCNTR_EL0);
--// CHECK-ASM: mrs     x8, PMCCNTR_EL0
--// CHECK-IR: call i64 @llvm.read_register.i64(metadata ![[MD3:.*]])
-+// CHECK-ASM: mrs     x0, PMCCNTR_EL0
-+// CHECK-IR: %[[VAR:.*]] = call i64 @llvm.read_register.i64(metadata ![[MD3:.*]])
-+// CHECK-IR-NEXT: store i64 %[[VAR]]
- 
-   ret = _ReadStatusReg(ARM64_PMSELR_EL0);
--// CHECK-ASM: mrs     x8, PMSELR_EL0
--// CHECK-IR: call i64 @llvm.read_register.i64(metadata ![[MD4:.*]])
-+// CHECK-ASM: mrs     x0, PMSELR_EL0
-+// CHECK-IR: %[[VAR:.*]] = call i64 @llvm.read_register.i64(metadata ![[MD4:.*]])
-+// CHECK-IR-NEXT: store i64 %[[VAR]]
- 
-   ret = _ReadStatusReg(ARM64_PMXEVCNTR_EL0);
--// CHECK-ASM: mrs     x8, PMXEVCNTR_EL0
--// CHECK-IR: call i64 @llvm.read_register.i64(metadata ![[MD5:.*]])
-+// CHECK-ASM: mrs     x0, PMXEVCNTR_EL0
-+// CHECK-IR: %[[VAR:.*]] = call i64 @llvm.read_register.i64(metadata ![[MD5:.*]])
-+// CHECK-IR-NEXT: store i64 %[[VAR]]
- 
-   ret = _ReadStatusReg(ARM64_PMXEVCNTRn_EL0(0));
--// CHECK-ASM: mrs     x8, PMEVCNTR0_EL0
--// CHECK-IR: call i64 @llvm.read_register.i64(metadata ![[MD6:.*]])
-+// CHECK-ASM: mrs     x0, PMEVCNTR0_EL0
-+// CHECK-IR: %[[VAR:.*]] = call i64 @llvm.read_register.i64(metadata ![[MD6:.*]])
-+// CHECK-IR-NEXT: store i64 %[[VAR]]
- 
-   ret = _ReadStatusReg(ARM64_PMXEVCNTRn_EL0(1));
--// CHECK-ASM: mrs     x8, PMEVCNTR1_EL0
--// CHECK-IR: call i64 @llvm.read_register.i64(metadata ![[MD7:.*]])
-+// CHECK-ASM: mrs     x0, PMEVCNTR1_EL0
-+// CHECK-IR: %[[VAR:.*]] = call i64 @llvm.read_register.i64(metadata ![[MD7:.*]])
-+// CHECK-IR-NEXT: store i64 %[[VAR]]
- 
-   ret = _ReadStatusReg(ARM64_PMXEVCNTRn_EL0(30));
--// CHECK-ASM: mrs     x8, PMEVCNTR30_EL0
--// CHECK-IR: call i64 @llvm.read_register.i64(metadata ![[MD8:.*]])
-+// CHECK-ASM: mrs     x0, PMEVCNTR30_EL0
-+// CHECK-IR: %[[VAR:.*]] = call i64 @llvm.read_register.i64(metadata ![[MD8:.*]])
-+// CHECK-IR-NEXT: store i64 %[[VAR]]
- 
-   ret = _ReadStatusReg(ARM64_TPIDR_EL0);
--// CHECK-ASM: mrs     x8, TPIDR_EL0
--// CHECK-IR: call i64 @llvm.read_register.i64(metadata ![[MD9:.*]])
-+// CHECK-ASM: mrs     x0, TPIDR_EL0
-+// CHECK-IR: %[[VAR:.*]] = call i64 @llvm.read_register.i64(metadata ![[MD9:.*]])
-+// CHECK-IR-NEXT: store i64 %[[VAR]]
- 
-   ret = _ReadStatusReg(ARM64_TPIDRRO_EL0);
--// CHECK-ASM: mrs     x8, TPIDRRO_EL0
--// CHECK-IR: call i64 @llvm.read_register.i64(metadata ![[MD10:.*]])
-+// CHECK-ASM: mrs     x0, TPIDRRO_EL0
-+// CHECK-IR: %[[VAR:.*]] = call i64 @llvm.read_register.i64(metadata ![[MD10:.*]])
-+// CHECK-IR-NEXT: store i64 %[[VAR]]
- 
-   ret = _ReadStatusReg(ARM64_TPIDR_EL1);
--// CHECK-ASM: mrs     x8, TPIDR_EL1
--// CHECK-IR: call i64 @llvm.read_register.i64(metadata ![[MD11:.*]])
-+// CHECK-ASM: mrs     x0, TPIDR_EL1
-+// CHECK-IR: %[[VAR:.*]] = call i64 @llvm.read_register.i64(metadata ![[MD11:.*]])
-+// CHECK-IR-NEXT: store i64 %[[VAR]]
- 
- 
-   _WriteStatusReg(ARM64_CNTVCT, v);
--// CHECK-ASM: msr     S3_3_C14_C0_2, x8
-+// CHECK-ASM: msr     S3_3_C14_C0_2, x0
- // CHECK-IR: call void @llvm.write_register.i64(metadata ![[MD2:.*]], i64 {{%.*}})
- 
-   _WriteStatusReg(ARM64_PMCCNTR_EL0, v);
--// CHECK-ASM: msr     PMCCNTR_EL0, x8
-+// CHECK-ASM: msr     PMCCNTR_EL0, x0
- // CHECK-IR: call void @llvm.write_register.i64(metadata ![[MD3:.*]], i64 {{%.*}})
- 
-   _WriteStatusReg(ARM64_PMSELR_EL0, v);
--// CHECK-ASM: msr     PMSELR_EL0, x8
-+// CHECK-ASM: msr     PMSELR_EL0, x0
- // CHECK-IR: call void @llvm.write_register.i64(metadata ![[MD4:.*]], i64 {{%.*}})
- 
-   _WriteStatusReg(ARM64_PMXEVCNTR_EL0, v);
--// CHECK-ASM: msr     PMXEVCNTR_EL0, x8
-+// CHECK-ASM: msr     PMXEVCNTR_EL0, x0
- // CHECK-IR: call void @llvm.write_register.i64(metadata ![[MD5:.*]], i64 {{%.*}})
- 
-   _WriteStatusReg(ARM64_PMXEVCNTRn_EL0(0), v);
--// CHECK-ASM: msr     PMEVCNTR0_EL0, x8
-+// CHECK-ASM: msr     PMEVCNTR0_EL0, x0
- // CHECK-IR: call void @llvm.write_register.i64(metadata ![[MD6:.*]], i64 {{%.*}})
- 
-   _WriteStatusReg(ARM64_PMXEVCNTRn_EL0(1), v);
--// CHECK-ASM: msr     PMEVCNTR1_EL0, x8
-+// CHECK-ASM: msr     PMEVCNTR1_EL0, x0
- // CHECK-IR: call void @llvm.write_register.i64(metadata ![[MD7:.*]], i64 {{%.*}})
- 
-   _WriteStatusReg(ARM64_PMXEVCNTRn_EL0(30), v);
--// CHECK-ASM: msr     PMEVCNTR30_EL0, x8
-+// CHECK-ASM: msr     PMEVCNTR30_EL0, x0
- // CHECK-IR: call void @llvm.write_register.i64(metadata ![[MD8:.*]], i64 {{%.*}})
- 
-   _WriteStatusReg(ARM64_TPIDR_EL0, v);
--// CHECK-ASM: msr     TPIDR_EL0, x8
-+// CHECK-ASM: msr     TPIDR_EL0, x0
- // CHECK-IR: call void @llvm.write_register.i64(metadata ![[MD9:.*]], i64 {{%.*}})
- 
-   _WriteStatusReg(ARM64_TPIDRRO_EL0, v);
--// CHECK-ASM: msr     TPIDRRO_EL0, x8
-+// CHECK-ASM: msr     TPIDRRO_EL0, x0
- // CHECK-IR: call void @llvm.write_register.i64(metadata ![[MD10:.*]], i64 {{%.*}})
- 
-   _WriteStatusReg(ARM64_TPIDR_EL1, v);
--// CHECK-ASM: msr     TPIDR_EL1, x8
-+// CHECK-ASM: msr     TPIDR_EL1, x0
- // CHECK-IR: call void @llvm.write_register.i64(metadata ![[MD11:.*]], i64 {{%.*}})
- }
- 
--- a/build/build-clang/clang-win64.json
+++ b/build/build-clang/clang-win64.json
@@ -1,23 +1,22 @@
 {
-    "llvm_revision": "353414",
+    "llvm_revision": "355016",
     "stages": "3",
     "build_libcxx": false,
     "build_type": "Release",
     "assertions": false,
-    "llvm_repo": "https://llvm.org/svn/llvm-project/llvm/tags/RELEASE_800/rc2",
-    "clang_repo": "https://llvm.org/svn/llvm-project/cfe/tags/RELEASE_800/rc2",
-    "lld_repo": "https://llvm.org/svn/llvm-project/lld/tags/RELEASE_800/rc2",
-    "compiler_repo": "https://llvm.org/svn/llvm-project/compiler-rt/tags/RELEASE_800/rc2",
-    "libcxx_repo": "https://llvm.org/svn/llvm-project/libcxx/tags/RELEASE_800/rc2",
+    "llvm_repo": "https://llvm.org/svn/llvm-project/llvm/tags/RELEASE_800/rc3",
+    "clang_repo": "https://llvm.org/svn/llvm-project/cfe/tags/RELEASE_800/rc3",
+    "lld_repo": "https://llvm.org/svn/llvm-project/lld/tags/RELEASE_800/rc3",
+    "compiler_repo": "https://llvm.org/svn/llvm-project/compiler-rt/tags/RELEASE_800/rc3",
+    "libcxx_repo": "https://llvm.org/svn/llvm-project/libcxx/tags/RELEASE_800/rc3",
     "python_path": "c:/mozilla-build/python/python.exe",
     "cc": "cl.exe",
     "cxx": "cl.exe",
     "ml": "ml64.exe",
     "patches": [
       "workaround-issue38586.patch",
       "unpoison-thread-stacks.patch",
       "downgrade-mangling-error.patch",
-      "loosen-msvc-detection.patch",
-      "D57636.diff"
+      "loosen-msvc-detection.patch"
     ]
 }
--- a/dom/base/nsContentUtils.cpp
+++ b/dom/base/nsContentUtils.cpp
@@ -7495,38 +7495,37 @@ void nsContentUtils::TransferableToIPCTr
         nsAutoString dataAsString;
         text->GetData(dataAsString);
         IPCDataTransferItem* item = aIPCDataTransfer->items().AppendElement();
         item->flavor() = flavorStr;
         item->data() = dataAsString;
       } else if (nsCOMPtr<nsISupportsCString> ctext = do_QueryInterface(data)) {
         nsAutoCString dataAsString;
         ctext->GetData(dataAsString);
-        IPCDataTransferItem* item = aIPCDataTransfer->items().AppendElement();
-        item->flavor() = flavorStr;
 
         Shmem dataAsShmem = ConvertToShmem(aChild, aParent, dataAsString);
         if (!dataAsShmem.IsReadable() || !dataAsShmem.Size<char>()) {
           continue;
         }
 
+        IPCDataTransferItem* item = aIPCDataTransfer->items().AppendElement();
+        item->flavor() = flavorStr;
         item->data() = dataAsShmem;
       } else if (nsCOMPtr<nsIInputStream> stream = do_QueryInterface(data)) {
         // Images to be pasted on the clipboard are nsIInputStreams
-        IPCDataTransferItem* item = aIPCDataTransfer->items().AppendElement();
-        item->flavor() = flavorStr;
-
         nsCString imageData;
         NS_ConsumeStream(stream, UINT32_MAX, imageData);
 
         Shmem imageDataShmem = ConvertToShmem(aChild, aParent, imageData);
         if (!imageDataShmem.IsReadable() || !imageDataShmem.Size<char>()) {
           continue;
         }
 
+        IPCDataTransferItem* item = aIPCDataTransfer->items().AppendElement();
+        item->flavor() = flavorStr;
         item->data() = imageDataShmem;
       } else if (nsCOMPtr<imgIContainer> image = do_QueryInterface(data)) {
         // Images to be placed on the clipboard are imgIContainers.
         RefPtr<mozilla::gfx::SourceSurface> surface = image->GetFrame(
             imgIContainer::FRAME_CURRENT, imgIContainer::FLAG_SYNC_DECODE);
         if (!surface) {
           continue;
         }
@@ -7565,23 +7564,27 @@ void nsContentUtils::TransferableToIPCTr
           // If we can send this over as a blob, do so. Otherwise, we're
           // responding to a sync message and the child can't process the blob
           // constructor before processing our response, which would crash. In
           // that case, hope that the caller is nsClipboardProxy::GetData,
           // called from editor and send over images as raw data.
           if (aInSyncMessage) {
             nsAutoCString type;
             if (IsFileImage(file, type)) {
-              IPCDataTransferItem* item =
-                  aIPCDataTransfer->items().AppendElement();
-              item->flavor() = type;
               nsAutoCString data;
               SlurpFileToString(file, data);
 
               Shmem dataAsShmem = ConvertToShmem(aChild, aParent, data);
+              if (!dataAsShmem.IsReadable() || !dataAsShmem.Size<char>()) {
+                continue;
+              }
+
+              IPCDataTransferItem* item =
+                  aIPCDataTransfer->items().AppendElement();
+              item->flavor() = type;
               item->data() = dataAsShmem;
             }
 
             continue;
           }
 
           if (aParent) {
             bool isDir = false;
--- a/dom/media/mp4/Index.cpp
+++ b/dom/media/mp4/Index.cpp
@@ -404,20 +404,16 @@ Index::Index(const IndiceWrapper& aIndic
         return;
       }
       if (indice.sync || mIsAudio) {
         haveSync = true;
       }
       if (!haveSync) {
         continue;
       }
-      if (indice.start_composition == indice.end_composition) {
-        // Ignore this sample as it doesn't account for the buffered range.
-        continue;
-      }
       Sample sample;
       sample.mByteRange =
           MediaByteRange(indice.start_offset, indice.end_offset);
       sample.mCompositionRange = MP4Interval<Microseconds>(
           indice.start_composition, indice.end_composition);
       sample.mDecodeTime = indice.start_decode;
       sample.mSync = indice.sync || mIsAudio;
       // FIXME: Make this infallible after bug 968520 is done.
@@ -556,19 +552,21 @@ TimeIntervals Index::ConvertByteRangesTo
           if (range.ContainsStrict(mIndex[i].mByteRange)) {
             timeRanges += TimeInterval(
                 TimeUnit::FromMicroseconds(mIndex[i].mCompositionRange.start),
                 TimeUnit::FromMicroseconds(mIndex[i].mCompositionRange.end));
           }
         }
       }
       if (end > start) {
-        timeRanges += TimeInterval(
-            TimeUnit::FromMicroseconds(mDataOffset[start].mTime.start),
-            TimeUnit::FromMicroseconds(mDataOffset[end - 1].mTime.end));
+        for (uint32_t i = start; i < end; i++) {
+          timeRanges += TimeInterval(
+              TimeUnit::FromMicroseconds(mDataOffset[i].mTime.start),
+              TimeUnit::FromMicroseconds(mDataOffset[i].mTime.end));
+        }
       }
       if (end < mDataOffset.Length()) {
         // Find samples in partial block contained in the byte range.
         for (size_t i = mDataOffset[end].mIndex;
              i < mIndex.Length() && range.ContainsStrict(mIndex[i].mByteRange);
              i++) {
           timeRanges += TimeInterval(
               TimeUnit::FromMicroseconds(mIndex[i].mCompositionRange.start),
--- a/gfx/layers/apz/src/APZCTreeManager.h
+++ b/gfx/layers/apz/src/APZCTreeManager.h
@@ -15,22 +15,22 @@
 #include "mozilla/gfx/CompositorHitTestInfo.h"
 #include "mozilla/gfx/Logging.h"              // for gfx::TreeLog
 #include "mozilla/gfx/Matrix.h"               // for Matrix4x4
 #include "mozilla/layers/APZInputBridge.h"    // for APZInputBridge
 #include "mozilla/layers/APZTestData.h"       // for APZTestData
 #include "mozilla/layers/IAPZCTreeManager.h"  // for IAPZCTreeManager
 #include "mozilla/layers/LayersTypes.h"
 #include "mozilla/layers/KeyboardMap.h"  // for KeyboardMap
+#include "mozilla/layers/TouchCounter.h" // for TouchCounter
 #include "mozilla/RecursiveMutex.h"      // for RecursiveMutex
 #include "mozilla/RefPtr.h"              // for RefPtr
 #include "mozilla/TimeStamp.h"           // for mozilla::TimeStamp
 #include "mozilla/UniquePtr.h"           // for UniquePtr
 #include "nsCOMPtr.h"                    // for already_AddRefed
-#include "TouchCounter.h"                // for TouchCounter
 
 #if defined(MOZ_WIDGET_ANDROID)
 #  include "mozilla/layers/AndroidDynamicToolbarAnimator.h"
 #endif  // defined(MOZ_WIDGET_ANDROID)
 
 namespace mozilla {
 class MultiTouchInput;
 
--- a/gfx/layers/apz/src/InputBlockState.h
+++ b/gfx/layers/apz/src/InputBlockState.h
@@ -9,19 +9,19 @@
 
 #include "InputData.h"           // for MultiTouchInput
 #include "mozilla/RefCounted.h"  // for RefCounted
 #include "mozilla/RefPtr.h"      // for RefPtr
 #include "mozilla/gfx/Matrix.h"  // for Matrix4x4
 #include "mozilla/layers/APZUtils.h"
 #include "mozilla/layers/LayersTypes.h"  // for TouchBehaviorFlags
 #include "mozilla/layers/AsyncDragMetrics.h"
+#include "mozilla/layers/TouchCounter.h"
 #include "mozilla/TimeStamp.h"  // for TimeStamp
 #include "nsTArray.h"           // for nsTArray
-#include "TouchCounter.h"
 
 namespace mozilla {
 namespace layers {
 
 class AsyncPanZoomController;
 class OverscrollHandoffChain;
 class CancelableBlockState;
 class TouchBlockState;
--- a/gfx/layers/apz/src/InputQueue.h
+++ b/gfx/layers/apz/src/InputQueue.h
@@ -6,20 +6,20 @@
 
 #ifndef mozilla_layers_InputQueue_h
 #define mozilla_layers_InputQueue_h
 
 #include "APZUtils.h"
 #include "DragTracker.h"
 #include "InputData.h"
 #include "mozilla/EventForwards.h"
+#include "mozilla/layers/TouchCounter.h"
 #include "mozilla/RefPtr.h"
 #include "mozilla/UniquePtr.h"
 #include "nsTArray.h"
-#include "TouchCounter.h"
 
 namespace mozilla {
 
 class InputData;
 class MultiTouchInput;
 class ScrollWheelInput;
 
 namespace layers {
new file mode 100644
--- /dev/null
+++ b/gfx/layers/apz/test/mochitest/helper_zoom_prevented.html
@@ -0,0 +1,81 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+  <meta charset="utf-8">
+  <meta name="viewport" content="width=device-width">
+  <title>Checking prevent-default for zooming</title>
+  <script type="application/javascript" src="apz_test_native_event_utils.js"></script>
+  <script type="application/javascript" src="apz_test_utils.js"></script>
+  <script type="application/javascript" src="/tests/SimpleTest/paint_listener.js"></script>
+  <script type="application/javascript">
+
+function* testPreventDefault(testDriver, aTouchStartToCancel) {
+  var initial_resolution = getResolution();
+  ok(initial_resolution > 0,
+      "The initial_resolution is " + initial_resolution + ", which is some sane value");
+
+  // preventDefault exactly one touchstart based on the value of aTouchStartToCancel
+  var touchStartCount = 0;
+  var canceller = function(e) {
+    dump("touchstart listener hit, count: " + touchStartCount + "\n");
+    touchStartCount++;
+    if (touchStartCount == aTouchStartToCancel) {
+      dump("calling preventDefault on touchstart\n");
+      e.preventDefault();
+      document.documentElement.removeEventListener("touchstart", canceller, {passive: false});
+    }
+  };
+  document.documentElement.addEventListener("touchstart", canceller, {passive: false});
+
+  var waitForTouchEnd = function(e) {
+    dump("touchend listener hit\n");
+    setTimeout(testDriver, 0);
+  };
+  document.documentElement.addEventListener("touchend", waitForTouchEnd, {passive: true, once: true});
+
+  // Ensure that APZ gets updated hit-test info
+  yield waitForAllPaints(testDriver);
+
+  var zoom_in = [
+      [ { x: 125, y: 250 }, { x: 175, y: 350 } ],
+      [ { x: 120, y: 220 }, { x: 180, y: 380 } ],
+      [ { x: 115, y: 190 }, { x: 185, y: 410 } ],
+      [ { x: 110, y: 160 }, { x: 190, y: 440 } ],
+      [ { x: 105, y: 130 }, { x: 195, y: 470 } ],
+      [ { x: 100, y: 100 }, { x: 200, y: 500 } ],
+  ];
+
+  var touchIds = [0, 1];
+  yield* synthesizeNativeTouchSequences(document.body, zoom_in, null, touchIds);
+
+  yield; // wait for the touchend listener to fire
+
+  // Flush state and get the resolution we're at now
+  yield waitForApzFlushedRepaints(testDriver);
+  let final_resolution = getResolution();
+  is(final_resolution, initial_resolution, "The final resolution (" + final_resolution + ") matches the initial resolution");
+}
+
+function* test(testDriver) {
+  // Register a listener that fails the test if the APZ:TransformEnd event fires,
+  // because this test shouldn't actually be triggering any transforms
+  SpecialPowers.Services.obs.addObserver(function() {
+    ok(false, "The test fired an unexpected APZ:TransformEnd");
+  }, "APZ:TransformEnd");
+
+  yield* testPreventDefault(testDriver, 1);
+  yield* testPreventDefault(testDriver, 2);
+}
+
+waitUntilApzStable()
+.then(runContinuation(test))
+.then(subtestDone);
+
+  </script>
+</head>
+<body>
+  Here is some text to stare at as the test runs. It serves no functional
+  purpose, but gives you an idea of the zoom level. It's harder to tell what
+  the zoom level is when the page is just solid white.
+</body>
+</html>
--- a/gfx/layers/apz/test/mochitest/test_group_zoom.html
+++ b/gfx/layers/apz/test/mochitest/test_group_zoom.html
@@ -27,29 +27,33 @@ var prefs = [
   // offset values.
   ["browser.chrome.dynamictoolbar", false],
   // Explicitly enable pinch-zooming, so this test can run on desktop
   // even though zooming isn't enabled by default on desktop yet.
   ["apz.allow_zooming", true],
   // Pinch-zooming currently requires meta viewport support (this requirement
   // will eventually be removed).
   ["dom.meta-viewport.enabled", true],
+  // Increase the content response timeout because some tests do preventDefault
+  // and we want to make sure APZ actually waits for them.
+  ["apz.content_response_timeout", 60000],
 ];
 
 // Increase the tap timeouts so the double-tap is still detected in case of
 // random delays during testing.
 var doubletap_prefs = [
   ...prefs,
   ["ui.click_hold_context_menus.delay", 10000],
   ["apz.max_tap_time", 10000],
 ];
 
 var subtests = [
   {"file": "helper_bug1280013.html", "prefs": prefs},
   {"file": "helper_basic_zoom.html", "prefs": prefs},
+  {"file": "helper_zoom_prevented.html", "prefs": prefs},
   {"file": "helper_zoomed_pan.html", "prefs": prefs},
   {"file": "helper_fixed_position_scroll_hittest.html", "prefs": prefs},
   {"file": "helper_basic_doubletap_zoom.html", "prefs": doubletap_prefs},
 ];
 
 if (isApzEnabled()) {
   // This has a lot of subtests, and Android emulators are slow.
   SimpleTest.requestLongerTimeout(2);
--- a/gfx/layers/apz/util/APZEventState.cpp
+++ b/gfx/layers/apz/util/APZEventState.cpp
@@ -99,16 +99,17 @@ APZEventState::APZEventState(nsIWidget* 
                              ContentReceivedInputBlockCallback&& aCallback)
     : mWidget(nullptr)  // initialized in constructor body
       ,
       mActiveElementManager(new ActiveElementManager()),
       mContentReceivedInputBlockCallback(std::move(aCallback)),
       mPendingTouchPreventedResponse(false),
       mPendingTouchPreventedBlockId(0),
       mEndTouchIsClick(false),
+      mFirstTouchCancelled(false),
       mTouchEndCancelled(false),
       mLastTouchIdentifier(0) {
   nsresult rv;
   mWidget = do_GetWeakReference(aWidget, &rv);
   MOZ_ASSERT(NS_SUCCEEDED(rv),
              "APZEventState constructed with a widget that"
              " does not support weak references. APZ will NOT work!");
 
@@ -332,16 +333,32 @@ void APZEventState::ProcessTouchEvent(co
       sentContentResponse = SendPendingTouchPreventedResponse(false);
       // sentContentResponse can be true here if we get two TOUCH_STARTs in a
       // row and just responded to the first one.
 
       // We're about to send a response back to APZ, but we should only do it
       // for events that went through APZ (which should be all of them).
       MOZ_ASSERT(aEvent.mFlags.mHandledByAPZ);
 
+      // If the first touchstart event was preventDefaulted, ensure that any
+      // subsequent additional touchstart events also get preventDefaulted. This
+      // ensures that e.g. pinch zooming is prevented even if just the first
+      // touchstart was prevented by content.
+      if (mTouchCounter.GetActiveTouchCount() == 0) {
+        mFirstTouchCancelled = isTouchPrevented;
+      } else {
+        if (mFirstTouchCancelled && !isTouchPrevented) {
+          APZES_LOG(
+              "Propagating prevent-default from first-touch for block %" PRIu64
+              "\n",
+              aInputBlockId);
+        }
+        isTouchPrevented |= mFirstTouchCancelled;
+      }
+
       if (isTouchPrevented) {
         mContentReceivedInputBlockCallback(aGuid, aInputBlockId,
                                            isTouchPrevented);
         sentContentResponse = true;
       } else {
         APZES_LOG("Event not prevented; pending response for %" PRIu64 " %s\n",
                   aInputBlockId, Stringify(aGuid).c_str());
         mPendingTouchPreventedResponse = true;
@@ -368,16 +385,21 @@ void APZEventState::ProcessTouchEvent(co
       break;
     }
 
     default:
       MOZ_ASSERT_UNREACHABLE("Unknown touch event type");
       break;
   }
 
+  mTouchCounter.Update(aEvent);
+  if (mTouchCounter.GetActiveTouchCount() == 0) {
+    mFirstTouchCancelled = false;
+  }
+
   if (sentContentResponse && !isTouchPrevented &&
       aApzResponse == nsEventStatus_eConsumeDoDefault &&
       gfxPrefs::PointerEventsEnabled()) {
     WidgetTouchEvent cancelEvent(aEvent);
     cancelEvent.mMessage = eTouchPointerCancel;
     cancelEvent.mFlags.mCancelable = false;  // mMessage != eTouchCancel;
     for (uint32_t i = 0; i < cancelEvent.mTouches.Length(); ++i) {
       if (mozilla::dom::Touch* touch = cancelEvent.mTouches[i]) {
--- a/gfx/layers/apz/util/APZEventState.h
+++ b/gfx/layers/apz/util/APZEventState.h
@@ -8,16 +8,17 @@
 #define mozilla_layers_APZEventState_h
 
 #include <stdint.h>
 
 #include "Units.h"
 #include "mozilla/EventForwards.h"
 #include "mozilla/layers/GeckoContentController.h"  // for APZStateChange
 #include "mozilla/layers/ScrollableLayerGuid.h"     // for ScrollableLayerGuid
+#include "mozilla/layers/TouchCounter.h"            // for TouchCounter
 #include "mozilla/RefPtr.h"
 #include "nsCOMPtr.h"
 #include "nsISupportsImpl.h"        // for NS_INLINE_DECL_REFCOUNTING
 #include "nsIWeakReferenceUtils.h"  // for nsWeakPtr
 
 #include <functional>
 
 template <class>
@@ -89,20 +90,22 @@ class APZEventState {
                              const nsCOMPtr<nsIWidget>& aWidget);
   already_AddRefed<nsIWidget> GetWidget() const;
   already_AddRefed<nsIContent> GetTouchRollup() const;
 
  private:
   nsWeakPtr mWidget;
   RefPtr<ActiveElementManager> mActiveElementManager;
   ContentReceivedInputBlockCallback mContentReceivedInputBlockCallback;
+  TouchCounter mTouchCounter;
   bool mPendingTouchPreventedResponse;
   ScrollableLayerGuid mPendingTouchPreventedGuid;
   uint64_t mPendingTouchPreventedBlockId;
   bool mEndTouchIsClick;
+  bool mFirstTouchCancelled;
   bool mTouchEndCancelled;
   int32_t mLastTouchIdentifier;
 
   // Because touch-triggered mouse events (e.g. mouse events from a tap
   // gesture) happen asynchronously from the touch events themselves, we
   // need to stash and replicate some of the state from the touch events
   // to the mouse events. One piece of state is the rollup content, which
   // is the content for which a popup window was recently closed. If we
rename from gfx/layers/apz/src/TouchCounter.cpp
rename to gfx/layers/apz/util/TouchCounter.cpp
--- a/gfx/layers/apz/src/TouchCounter.cpp
+++ b/gfx/layers/apz/util/TouchCounter.cpp
@@ -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/. */
 
 #include "TouchCounter.h"
 
 #include "InputData.h"
+#include "mozilla/TouchEvents.h"
 
 namespace mozilla {
 namespace layers {
 
 TouchCounter::TouchCounter() : mActiveTouchCount(0) {}
 
 void TouchCounter::Update(const MultiTouchInput& aInput) {
   switch (aInput.mType) {
@@ -19,24 +20,55 @@ void TouchCounter::Update(const MultiTou
       // touch-start event contains all active touches of the current session
       mActiveTouchCount = aInput.mTouches.Length();
       break;
     case MultiTouchInput::MULTITOUCH_END:
       if (mActiveTouchCount >= aInput.mTouches.Length()) {
         // touch-end event contains only released touches
         mActiveTouchCount -= aInput.mTouches.Length();
       } else {
-        NS_WARNING("Got an unexpected touchend/touchcancel");
+        NS_WARNING("Got an unexpected touchend");
         mActiveTouchCount = 0;
       }
       break;
     case MultiTouchInput::MULTITOUCH_CANCEL:
       mActiveTouchCount = 0;
       break;
     case MultiTouchInput::MULTITOUCH_MOVE:
       break;
   }
 }
 
+void TouchCounter::Update(const WidgetTouchEvent& aEvent) {
+  switch (aEvent.mMessage) {
+    case eTouchStart:
+      // touch-start event contains all active touches of the current session
+      mActiveTouchCount = aEvent.mTouches.Length();
+      break;
+    case eTouchEnd: {
+      // touch-end contains all touches, but ones being lifted are marked as
+      // changed
+      uint32_t liftedTouches = 0;
+      for (const auto& touch : aEvent.mTouches) {
+        if (touch->mChanged) {
+          liftedTouches++;
+        }
+      }
+      if (mActiveTouchCount >= liftedTouches) {
+        mActiveTouchCount -= liftedTouches;
+      } else {
+        NS_WARNING("Got an unexpected touchend");
+        mActiveTouchCount = 0;
+      }
+      break;
+    }
+    case eTouchCancel:
+      mActiveTouchCount = 0;
+      break;
+    default:
+      break;
+  }
+}
+
 uint32_t TouchCounter::GetActiveTouchCount() const { return mActiveTouchCount; }
 
 }  // namespace layers
 }  // namespace mozilla
rename from gfx/layers/apz/src/TouchCounter.h
rename to gfx/layers/apz/util/TouchCounter.h
--- a/gfx/layers/apz/src/TouchCounter.h
+++ b/gfx/layers/apz/util/TouchCounter.h
@@ -11,21 +11,24 @@
 
 namespace mozilla {
 
 class MultiTouchInput;
 
 namespace layers {
 
 // TouchCounter simply tracks the number of active touch points. Feed it
-// your input events to update the internal state.
+// your input events to update the internal state. Generally you should
+// only be calling one of the Update functions, depending on which type
+// of touch inputs you have access to.
 class TouchCounter {
  public:
   TouchCounter();
   void Update(const MultiTouchInput& aInput);
+  void Update(const WidgetTouchEvent& aEvent);
   uint32_t GetActiveTouchCount() const;
 
  private:
   uint32_t mActiveTouchCount;
 };
 
 }  // namespace layers
 }  // namespace mozilla
--- a/gfx/layers/moz.build
+++ b/gfx/layers/moz.build
@@ -113,16 +113,17 @@ EXPORTS.mozilla.layers += [
     'apz/util/APZEventState.h',
     'apz/util/APZThreadUtils.h',
     'apz/util/ChromeProcessController.h',
     'apz/util/ContentProcessController.h',
     'apz/util/DoubleTapToZoom.h',
     'apz/util/InputAPZContext.h',
     'apz/util/ScrollLinkedEffectDetector.h',
     'apz/util/TouchActionHelper.h',
+    'apz/util/TouchCounter.h',
     'AsyncCanvasRenderer.h',
     'AtomicRefCountedWithFinalize.h',
     'AxisPhysicsModel.h',
     'AxisPhysicsMSDModel.h',
     'basic/BasicCompositor.h',
     'basic/MacIOSurfaceTextureHostBasic.h',
     'basic/TextureHostBasic.h',
     'BSPTree.h',
@@ -331,30 +332,30 @@ UNIFIED_SOURCES += [
     '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/SimpleVelocityTracker.cpp',
-    'apz/src/TouchCounter.cpp',
     'apz/src/WheelScrollAnimation.cpp',
     'apz/testutil/APZTestData.cpp',
     'apz/util/ActiveElementManager.cpp',
     'apz/util/APZCCallbackHelper.cpp',
     'apz/util/APZEventState.cpp',
     'apz/util/APZThreadUtils.cpp',
     'apz/util/CheckerboardReportService.cpp',
     'apz/util/ChromeProcessController.cpp',
     'apz/util/ContentProcessController.cpp',
     'apz/util/DoubleTapToZoom.cpp',
     'apz/util/InputAPZContext.cpp',
     'apz/util/ScrollLinkedEffectDetector.cpp',
     'apz/util/TouchActionHelper.cpp',
+    'apz/util/TouchCounter.cpp',
     'AsyncCanvasRenderer.cpp',
     'AxisPhysicsModel.cpp',
     'AxisPhysicsMSDModel.cpp',
     'basic/BasicCanvasLayer.cpp',
     'basic/BasicColorLayer.cpp',
     'basic/BasicCompositor.cpp',
     'basic/BasicContainerLayer.cpp',
     'basic/BasicImages.cpp',
--- a/js/src/builtin/TestingFunctions.cpp
+++ b/js/src/builtin/TestingFunctions.cpp
@@ -1204,16 +1204,34 @@ static bool StartGC(JSContext* cx, unsig
 
   JSGCInvocationKind gckind = shrinking ? GC_SHRINK : GC_NORMAL;
   rt->gc.startDebugGC(gckind, budget);
 
   args.rval().setUndefined();
   return true;
 }
 
+static bool FinishGC(JSContext* cx, unsigned argc, Value* vp) {
+  CallArgs args = CallArgsFromVp(argc, vp);
+
+  if (args.length() > 0) {
+    RootedObject callee(cx, &args.callee());
+    ReportUsageErrorASCII(cx, callee, "Wrong number of arguments");
+    return false;
+  }
+
+  JSRuntime* rt = cx->runtime();
+  if (rt->gc.isIncrementalGCInProgress()) {
+    rt->gc.finishGC(JS::GCReason::DEBUG_GC);
+  }
+
+  args.rval().setUndefined();
+  return true;
+}
+
 static bool GCSlice(JSContext* cx, unsigned argc, Value* vp) {
   CallArgs args = CallArgsFromVp(argc, vp);
 
   if (args.length() > 1) {
     RootedObject callee(cx, &args.callee());
     ReportUsageErrorASCII(cx, callee, "Wrong number of arguments");
     return false;
   }
@@ -5848,16 +5866,20 @@ gc::ZealModeHelpText),
 #endif
 
     JS_FN_HELP("startgc", StartGC, 1, 0,
 "startgc([n [, 'shrinking']])",
 "  Start an incremental GC and run a slice that processes about n objects.\n"
 "  If 'shrinking' is passesd as the optional second argument, perform a\n"
 "  shrinking GC rather than a normal GC."),
 
+    JS_FN_HELP("finishgc", FinishGC, 0, 0,
+"finishgc()",
+"   Finish an in-progress incremental GC, if none is running then do nothing."),
+
     JS_FN_HELP("gcslice", GCSlice, 1, 0,
 "gcslice([n])",
 "  Start or continue an an incremental GC, running a slice that processes about n objects."),
 
     JS_FN_HELP("abortgc", AbortGC, 1, 0,
 "abortgc()",
 "  Abort the current incremental GC."),
 
--- a/js/src/jit-test/tests/gc/weak-marking-01.js
+++ b/js/src/jit-test/tests/gc/weak-marking-01.js
@@ -8,16 +8,17 @@ gczeal(0);
 // All reachable keys should be found, and the rest should be swept.
 function basicSweeping() {
   var wm1 = new WeakMap();
   wm1.set({'name': 'obj1'}, {'name': 'val1'});
   var hold = {'name': 'obj2'};
   wm1.set(hold, {'name': 'val2'});
   wm1.set({'name': 'obj3'}, {'name': 'val3'});
 
+  finishgc();
   startgc(100000, 'shrinking');
   gcslice();
 
   assertEq(wm1.get(hold).name, 'val2');
   assertEq(nondeterministicGetWeakMapKeys(wm1).length, 1);
 }
 
 basicSweeping();
@@ -31,16 +32,17 @@ function weakGraph() {
   var obj4 = {'name': 'obj4'};
   var clear = {'name': ''}; // Make the interpreter forget about the last obj created
 
   wm1.set(obj2, obj3);
   wm1.set(obj3, obj1);
   wm1.set(obj4, obj1); // This edge will be cleared
   obj1 = obj3 = obj4 = undefined;
 
+  finishgc();
   startgc(100000, 'shrinking');
   gcslice();
 
   assertEq(obj2.name, "obj2");
   assertEq(wm1.get(obj2).name, "obj3");
   assertEq(wm1.get(wm1.get(obj2)).name, "obj1");
   print(nondeterministicGetWeakMapKeys(wm1).map(o => o.name).join(","));
   assertEq(nondeterministicGetWeakMapKeys(wm1).length, 2);
@@ -59,16 +61,17 @@ function deadWeakMap() {
 
   wm1.set(obj2, obj3);
   wm1.set(obj3, obj1);
   wm1.set(obj4, obj1); // This edge will be cleared
   var initialCount = finalizeCount();
   obj1 = obj3 = obj4 = undefined;
   wm1 = undefined;
 
+  finishgc();
   startgc(100000, 'shrinking');
   gcslice();
 
   assertEq(obj2.name, "obj2");
   assertEq(finalizeCount(), initialCount + 1);
 }
 
 deadWeakMap();
@@ -86,16 +89,17 @@ function deadKeys() {
   var obj3 = makeFinalizeObserver();
   var clear = {}; // Make the interpreter forget about the last obj created
 
   wm1.set(obj1, obj1);
   wm1.set(obj3, obj2);
   obj1 = obj3 = undefined;
   var initialCount = finalizeCount();
 
+  finishgc();
   startgc(100000, 'shrinking');
   gcslice();
 
   assertEq(finalizeCount(), initialCount + 2);
   assertEq(nondeterministicGetWeakMapKeys(wm1).length, 0);
 }
 
 deadKeys();
@@ -119,16 +123,17 @@ function weakKeysRealloc() {
     wm3.set(Object.create(null), wm2);
   }
   wm3.set(Object.create(null), makeFinalizeObserver());
   wm2 = undefined;
   wm3 = undefined;
   obj2 = undefined;
 
   var initialCount = finalizeCount();
+  finishgc();
   startgc(100000, 'shrinking');
   gcslice();
   assertEq(finalizeCount(), initialCount + 1);
 }
 
 weakKeysRealloc();
 
 // The weakKeys table is populated during regular marking. When a key is later
@@ -137,16 +142,17 @@ weakKeysRealloc();
 // traversals will include non-keys, etc.
 function deletedKeys() {
   var wm = new WeakMap;
   var g = newGlobal();
 
   for (var i = 0; i < 1000; i++)
     wm.set(g.Object.create(null), i);
 
+  finishgc();
   startgc(100, 'shrinking');
   for (var key of nondeterministicGetWeakMapKeys(wm)) {
     if (wm.get(key) % 2)
       wm.delete(key);
   }
 
   gc();
 }
@@ -166,16 +172,17 @@ function incrementalAdds() {
   wm2.set(obj2, wm3);
   for (var i = 0; i < 10000; i++) {
     wm3.set(Object.create(null), wm2);
   }
   wm3.set(Object.create(null), makeFinalizeObserver());
   obj2 = undefined;
 
   var obj3 = [];
+  finishgc();
   startgc(100, 'shrinking');
   var M = 10;
   var N = 800;
   for (var j = 0; j < M; j++) {
     for (var i = 0; i < N; i++)
       wm3.set(Object.create(null), makeFinalizeObserver()); // Should be swept
     for (var i = 0; i < N; i++) {
       obj3.push({'name': 'obj3'});
--- a/js/src/jit/BaselineCompiler.cpp
+++ b/js/src/jit/BaselineCompiler.cpp
@@ -2086,38 +2086,33 @@ JSObject* BaselineCompilerHandler::maybe
   if (realm->creationOptions().cloneSingletons()) {
     return nullptr;
   }
 
   realm->behaviors().setSingletonsAsValues();
   return script()->getObject(pc());
 }
 
-typedef JSObject* (*SingletonObjectLiteralFn)(JSContext*, HandleScript,
-                                              jsbytecode*);
-static const VMFunction SingletonObjectLiteralInfo =
-    FunctionInfo<SingletonObjectLiteralFn>(SingletonObjectLiteralOperation,
-                                           "SingletonObjectLiteralOperation");
-
 template <typename Handler>
 bool BaselineCodeGen<Handler>::emit_JSOP_OBJECT() {
   // If we know we don't have to clone the object literal, just push it
   // directly. Note that the interpreter always does the VM call; that's fine
   // because this op is only used in run-once code.
   if (JSObject* obj = handler.maybeNoCloneSingletonObject()) {
     frame.push(ObjectValue(*obj));
     return true;
   }
 
   prepareVMCall();
 
   pushBytecodePCArg();
   pushScriptArg(R2.scratchReg());
 
-  if (!callVM(SingletonObjectLiteralInfo)) {
+  using Fn = JSObject* (*)(JSContext*, HandleScript, jsbytecode*);
+  if (!callVM<Fn, SingletonObjectLiteralOperation>()) {
     return false;
   }
 
   // Box and push return value.
   masm.tagValue(JSVAL_TYPE_OBJECT, ReturnReg, R0);
   frame.push(R0);
   return true;
 }
@@ -2129,126 +2124,111 @@ bool BaselineCompilerCodeGen::emit_JSOP_
   if (!cso) {
     return false;
   }
 
   frame.push(ObjectValue(*cso));
   return true;
 }
 
-typedef ArrayObject* (*ProcessCallSiteObjFn)(JSContext*, HandleScript,
-                                             jsbytecode*);
-static const VMFunction ProcessCallSiteObjInfo =
-    FunctionInfo<ProcessCallSiteObjFn>(ProcessCallSiteObjOperation,
-                                       "ProcessCallSiteObjOperation");
-
 template <>
 bool BaselineInterpreterCodeGen::emit_JSOP_CALLSITEOBJ() {
   prepareVMCall();
 
   pushBytecodePCArg();
   pushScriptArg(R2.scratchReg());
 
-  if (!callVM(ProcessCallSiteObjInfo)) {
+  using Fn = ArrayObject* (*)(JSContext*, HandleScript, jsbytecode*);
+  if (!callVM<Fn, ProcessCallSiteObjOperation>()) {
     return false;
   }
 
   // Box and push return value.
   masm.tagValue(JSVAL_TYPE_OBJECT, ReturnReg, R0);
   frame.push(R0);
   return true;
 }
 
-typedef JSObject* (*CloneRegExpObjectFn)(JSContext*, Handle<RegExpObject*>);
-static const VMFunction CloneRegExpObjectInfo =
-    FunctionInfo<CloneRegExpObjectFn>(CloneRegExpObject, "CloneRegExpObject");
-
 template <typename Handler>
 bool BaselineCodeGen<Handler>::emit_JSOP_REGEXP() {
   prepareVMCall();
   pushScriptObjectArg(ScriptObjectType::RegExp);
-  if (!callVM(CloneRegExpObjectInfo)) {
+
+  using Fn = JSObject* (*)(JSContext*, Handle<RegExpObject*>);
+  if (!callVM<Fn, CloneRegExpObject>()) {
     return false;
   }
 
   // Box and push return value.
   masm.tagValue(JSVAL_TYPE_OBJECT, ReturnReg, R0);
   frame.push(R0);
   return true;
 }
 
-typedef JSObject* (*LambdaFn)(JSContext*, HandleFunction, HandleObject);
-static const VMFunction LambdaInfo =
-    FunctionInfo<LambdaFn>(js::Lambda, "Lambda");
-
 template <typename Handler>
 bool BaselineCodeGen<Handler>::emit_JSOP_LAMBDA() {
   prepareVMCall();
   masm.loadPtr(frame.addressOfEnvironmentChain(), R0.scratchReg());
 
   pushArg(R0.scratchReg());
   pushScriptObjectArg(ScriptObjectType::Function);
 
-  if (!callVM(LambdaInfo)) {
+  using Fn = JSObject* (*)(JSContext*, HandleFunction, HandleObject);
+  if (!callVM<Fn, js::Lambda>()) {
     return false;
   }
 
   // Box and push return value.
   masm.tagValue(JSVAL_TYPE_OBJECT, ReturnReg, R0);
   frame.push(R0);
   return true;
 }
 
-typedef JSObject* (*LambdaArrowFn)(JSContext*, HandleFunction, HandleObject,
-                                   HandleValue);
-static const VMFunction LambdaArrowInfo =
-    FunctionInfo<LambdaArrowFn>(js::LambdaArrow, "LambdaArrow");
-
 template <typename Handler>
 bool BaselineCodeGen<Handler>::emit_JSOP_LAMBDA_ARROW() {
   // Keep pushed newTarget in R0.
   frame.popRegsAndSync(1);
 
   prepareVMCall();
   masm.loadPtr(frame.addressOfEnvironmentChain(), R2.scratchReg());
 
   pushArg(R0);
   pushArg(R2.scratchReg());
   pushScriptObjectArg(ScriptObjectType::Function);
 
-  if (!callVM(LambdaArrowInfo)) {
+  using Fn =
+      JSObject* (*)(JSContext*, HandleFunction, HandleObject, HandleValue);
+  if (!callVM<Fn, js::LambdaArrow>()) {
     return false;
   }
 
   // Box and push return value.
   masm.tagValue(JSVAL_TYPE_OBJECT, ReturnReg, R0);
   frame.push(R0);
   return true;
 }
 
-typedef bool (*SetFunNameFn)(JSContext*, HandleFunction, HandleValue,
-                             FunctionPrefixKind);
-static const VMFunction SetFunNameInfo =
-    FunctionInfo<SetFunNameFn>(js::SetFunctionName, "SetFunName");
-
 template <typename Handler>
 bool BaselineCodeGen<Handler>::emit_JSOP_SETFUNNAME() {
   frame.popRegsAndSync(2);
 
   frame.push(R0);
   frame.syncStack(0);
 
   masm.unboxObject(R0, R0.scratchReg());
 
   prepareVMCall();
 
   pushUint8BytecodeOperandArg();
   pushArg(R1);
   pushArg(R0.scratchReg());
-  return callVM(SetFunNameInfo);
+
+  using Fn =
+      bool (*)(JSContext*, HandleFunction, HandleValue, FunctionPrefixKind);
+  return callVM<Fn, SetFunctionName>();
 }
 
 template <typename Handler>
 bool BaselineCodeGen<Handler>::emit_JSOP_BITOR() {
   return emitBinaryArith();
 }
 
 template <typename Handler>
@@ -2464,21 +2444,16 @@ bool BaselineCompilerCodeGen::emit_JSOP_
   return true;
 }
 
 template <>
 bool BaselineInterpreterCodeGen::emit_JSOP_NEWARRAY() {
   MOZ_CRASH("NYI: interpreter JSOP_NEWARRAY");
 }
 
-typedef ArrayObject* (*NewArrayCopyOnWriteFn)(JSContext*, HandleArrayObject,
-                                              gc::InitialHeap);
-const VMFunction NewArrayCopyOnWriteInfo = FunctionInfo<NewArrayCopyOnWriteFn>(
-    js::NewDenseCopyOnWriteArray, "NewDenseCopyOnWriteArray");
-
 template <>
 bool BaselineCompilerCodeGen::emit_JSOP_NEWARRAY_COPYONWRITE() {
   // This is like the interpreter implementation, but we can call
   // getOrFixupCopyOnWriteObject at compile-time.
 
   RootedScript scriptRoot(cx, handler.script());
   JSObject* obj =
       ObjectGroup::getOrFixupCopyOnWriteObject(cx, scriptRoot, handler.pc());
@@ -2486,40 +2461,36 @@ bool BaselineCompilerCodeGen::emit_JSOP_
     return false;
   }
 
   prepareVMCall();
 
   pushArg(Imm32(gc::DefaultHeap));
   pushArg(ImmGCPtr(obj));
 
-  if (!callVM(NewArrayCopyOnWriteInfo)) {
+  using Fn = ArrayObject* (*)(JSContext*, HandleArrayObject, gc::InitialHeap);
+  if (!callVM<Fn, js::NewDenseCopyOnWriteArray>()) {
     return false;
   }
 
   // Box and push return value.
   masm.tagValue(JSVAL_TYPE_OBJECT, ReturnReg, R0);
   frame.push(R0);
   return true;
 }
 
-typedef ArrayObject* (*NewArrayCopyOnWriteOperationFn)(JSContext*, HandleScript,
-                                                       jsbytecode*);
-const VMFunction NewArrayCopyOnWriteOperationInfo =
-    FunctionInfo<NewArrayCopyOnWriteOperationFn>(
-        NewArrayCopyOnWriteOperation, "NewArrayCopyOnWriteOperation");
-
 template <>
 bool BaselineInterpreterCodeGen::emit_JSOP_NEWARRAY_COPYONWRITE() {
   prepareVMCall();
 
   pushBytecodePCArg();
   pushScriptArg(R2.scratchReg());
 
-  if (!callVM(NewArrayCopyOnWriteOperationInfo)) {
+  using Fn = ArrayObject* (*)(JSContext*, HandleScript, jsbytecode*);
+  if (!callVM<Fn, NewArrayCopyOnWriteOperation>()) {
     return false;
   }
 
   // Box and push return value.
   masm.tagValue(JSVAL_TYPE_OBJECT, ReturnReg, R0);
   frame.push(R0);
   return true;
 }
@@ -2603,35 +2574,31 @@ bool BaselineCodeGen<Handler>::emit_JSOP
   return true;
 }
 
 template <typename Handler>
 bool BaselineCodeGen<Handler>::emit_JSOP_INITHIDDENELEM() {
   return emit_JSOP_INITELEM();
 }
 
-typedef bool (*MutateProtoFn)(JSContext* cx, HandlePlainObject obj,
-                              HandleValue newProto);
-static const VMFunction MutateProtoInfo =
-    FunctionInfo<MutateProtoFn>(MutatePrototype, "MutatePrototype");
-
 template <typename Handler>
 bool BaselineCodeGen<Handler>::emit_JSOP_MUTATEPROTO() {
   // Keep values on the stack for the decompiler.
   frame.syncStack(0);
 
   masm.unboxObject(frame.addressOfStackValue(-2), R0.scratchReg());
   masm.loadValue(frame.addressOfStackValue(-1), R1);
 
   prepareVMCall();
 
   pushArg(R1);
   pushArg(R0.scratchReg());
 
-  if (!callVM(MutateProtoInfo)) {
+  using Fn = bool (*)(JSContext*, HandlePlainObject, HandleValue);
+  if (!callVM<Fn, MutatePrototype>()) {
     return false;
   }
 
   frame.pop();
   return true;
 }
 
 template <typename Handler>
@@ -2760,36 +2727,37 @@ bool BaselineCodeGen<Handler>::emit_JSOP
   return emitSetElemSuper(/* strict = */ false);
 }
 
 template <typename Handler>
 bool BaselineCodeGen<Handler>::emit_JSOP_STRICTSETELEM_SUPER() {
   return emitSetElemSuper(/* strict = */ true);
 }
 
-typedef bool (*DeleteElementFn)(JSContext*, HandleValue, HandleValue, bool*);
-static const VMFunction DeleteElementStrictInfo = FunctionInfo<DeleteElementFn>(
-    DeleteElementJit<true>, "DeleteElementStrict");
-static const VMFunction DeleteElementNonStrictInfo =
-    FunctionInfo<DeleteElementFn>(DeleteElementJit<false>,
-                                  "DeleteElementNonStrict");
-
 template <typename Handler>
 bool BaselineCodeGen<Handler>::emitDelElem(bool strict) {
   // Keep values on the stack for the decompiler.
   frame.syncStack(0);
   masm.loadValue(frame.addressOfStackValue(-2), R0);
   masm.loadValue(frame.addressOfStackValue(-1), R1);
 
   prepareVMCall();
 
   pushArg(R1);
   pushArg(R0);
-  if (!callVM(strict ? DeleteElementStrictInfo : DeleteElementNonStrictInfo)) {
-    return false;
+
+  using Fn = bool (*)(JSContext*, HandleValue, HandleValue, bool*);
+  if (strict) {
+    if (!callVM<Fn, DeleteElementJit<true>>()) {
+      return false;
+    }
+  } else {
+    if (!callVM<Fn, DeleteElementJit<false>>()) {
+      return false;
+    }
   }
 
   masm.boxNonDouble(JSVAL_TYPE_BOOLEAN, ReturnReg, R1);
   frame.popn(2);
   frame.push(R1);
   return true;
 }
 
@@ -2925,29 +2893,26 @@ bool BaselineInterpreterCodeGen::tryOpti
 template <typename Handler>
 bool BaselineCodeGen<Handler>::emit_JSOP_BINDGNAME() {
   if (tryOptimizeBindGlobalName()) {
     return true;
   }
   return emitBindName(JSOP_BINDGNAME);
 }
 
-typedef JSObject* (*BindVarFn)(JSContext*, JSObject*);
-static const VMFunction BindVarInfo =
-    FunctionInfo<BindVarFn>(BindVarOperation, "BindVarOperation");
-
 template <typename Handler>
 bool BaselineCodeGen<Handler>::emit_JSOP_BINDVAR() {
   frame.syncStack(0);
   masm.loadPtr(frame.addressOfEnvironmentChain(), R0.scratchReg());
 
   prepareVMCall();
   pushArg(R0.scratchReg());
 
-  if (!callVM(BindVarInfo)) {
+  using Fn = JSObject* (*)(JSContext*, JSObject*);
+  if (!callVM<Fn, BindVarOperation>()) {
     return false;
   }
 
   masm.tagValue(JSVAL_TYPE_OBJECT, ReturnReg, R0);
   frame.push(R0);
   return true;
 }
 
@@ -2988,21 +2953,16 @@ bool BaselineCodeGen<Handler>::emit_JSOP
   return emit_JSOP_SETPROP();
 }
 
 template <typename Handler>
 bool BaselineCodeGen<Handler>::emit_JSOP_STRICTSETGNAME() {
   return emit_JSOP_SETPROP();
 }
 
-typedef bool (*SetPropertySuperFn)(JSContext*, HandleObject, HandleValue,
-                                   HandlePropertyName, HandleValue, bool);
-static const VMFunction SetPropertySuperInfo =
-    FunctionInfo<SetPropertySuperFn>(js::SetPropertySuper, "SetPropertySuper");
-
 template <typename Handler>
 bool BaselineCodeGen<Handler>::emitSetPropSuper(bool strict) {
   // Incoming stack is |receiver, obj, rval|. We need to shuffle stack to
   // leave rval when operation is complete.
 
   // Pop rval into R0, then load receiver into R1 and replace with rval.
   frame.popRegsAndSync(1);
   masm.loadValue(frame.addressOfStackValue(-2), R1);
@@ -3012,17 +2972,19 @@ bool BaselineCodeGen<Handler>::emitSetPr
 
   pushArg(Imm32(strict));
   pushArg(R0);  // rval
   pushScriptNameArg();
   pushArg(R1);  // receiver
   masm.unboxObject(frame.addressOfStackValue(-1), R0.scratchReg());
   pushArg(R0.scratchReg());  // obj
 
-  if (!callVM(SetPropertySuperInfo)) {
+  using Fn = bool (*)(JSContext*, HandleObject, HandleValue, HandlePropertyName,
+                      HandleValue, bool);
+  if (!callVM<Fn, js::SetPropertySuper>()) {
     return false;
   }
 
   frame.pop();
   return true;
 }
 
 template <typename Handler>
@@ -3075,39 +3037,36 @@ bool BaselineCodeGen<Handler>::emit_JSOP
   if (!emitNextIC()) {
     return false;
   }
 
   frame.push(R0);
   return true;
 }
 
-typedef bool (*DeletePropertyFn)(JSContext*, HandleValue, HandlePropertyName,
-                                 bool*);
-static const VMFunction DeletePropertyStrictInfo =
-    FunctionInfo<DeletePropertyFn>(DeletePropertyJit<true>,
-                                   "DeletePropertyStrict");
-static const VMFunction DeletePropertyNonStrictInfo =
-    FunctionInfo<DeletePropertyFn>(DeletePropertyJit<false>,
-                                   "DeletePropertyNonStrict");
-
 template <typename Handler>
 bool BaselineCodeGen<Handler>::emitDelProp(bool strict) {
   // Keep value on the stack for the decompiler.
   frame.syncStack(0);
   masm.loadValue(frame.addressOfStackValue(-1), R0);
 
   prepareVMCall();
 
   pushScriptNameArg();
   pushArg(R0);
 
-  if (!callVM(strict ? DeletePropertyStrictInfo
-                     : DeletePropertyNonStrictInfo)) {
-    return false;
+  using Fn = bool (*)(JSContext*, HandleValue, HandlePropertyName, bool*);
+  if (strict) {
+    if (!callVM<Fn, DeletePropertyJit<true>>()) {
+      return false;
+    }
+  } else {
+    if (!callVM<Fn, DeletePropertyJit<false>>()) {
+      return false;
+    }
   }
 
   masm.boxNonDouble(JSVAL_TYPE_BOOLEAN, ReturnReg, R1);
   frame.pop();
   frame.push(R1);
   return true;
 }
 
@@ -3268,32 +3227,29 @@ bool BaselineCodeGen<Handler>::emitBindN
   return true;
 }
 
 template <typename Handler>
 bool BaselineCodeGen<Handler>::emit_JSOP_BINDNAME() {
   return emitBindName(JSOP_BINDNAME);
 }
 
-typedef bool (*DeleteNameFn)(JSContext*, HandlePropertyName, HandleObject,
-                             MutableHandleValue);
-static const VMFunction DeleteNameInfo =
-    FunctionInfo<DeleteNameFn>(DeleteNameOperation, "DeleteNameOperation");
-
 template <typename Handler>
 bool BaselineCodeGen<Handler>::emit_JSOP_DELNAME() {
   frame.syncStack(0);
   masm.loadPtr(frame.addressOfEnvironmentChain(), R0.scratchReg());
 
   prepareVMCall();
 
   pushArg(R0.scratchReg());
   pushScriptNameArg();
 
-  if (!callVM(DeleteNameInfo)) {
+  using Fn = bool (*)(JSContext*, HandlePropertyName, HandleObject,
+                      MutableHandleValue);
+  if (!callVM<Fn, js::DeleteNameOperation>()) {
     return false;
   }
 
   frame.push(R0);
   return true;
 }
 
 template <>
@@ -3338,35 +3294,31 @@ bool BaselineCompilerCodeGen::emit_JSOP_
       return false;
     }
   }
 
   frame.push(R0);
   return true;
 }
 
-typedef bool (*GetImportOperationFn)(JSContext*, HandleObject, HandleScript,
-                                     jsbytecode*, MutableHandleValue);
-static const VMFunction GetImportOperationInfo =
-    FunctionInfo<GetImportOperationFn>(GetImportOperation,
-                                       "GetImportOperation");
-
 template <>
 bool BaselineInterpreterCodeGen::emit_JSOP_GETIMPORT() {
   frame.syncStack(0);
 
   masm.loadPtr(frame.addressOfEnvironmentChain(), R0.scratchReg());
 
   prepareVMCall();
 
   pushBytecodePCArg();
   pushScriptArg(R2.scratchReg());
   pushArg(R0.scratchReg());
 
-  if (!callVM(GetImportOperationInfo)) {
+  using Fn = bool (*)(JSContext*, HandleObject, HandleScript, jsbytecode*,
+                      MutableHandleValue);
+  if (!callVM<Fn, GetImportOperation>()) {
     return false;
   }
 
   // Enter the type monitor IC.
   if (!emitNextIC()) {
     return false;
   }
 
@@ -3381,127 +3333,109 @@ bool BaselineCodeGen<Handler>::emit_JSOP
   if (!emitNextIC()) {
     return false;
   }
 
   frame.push(R0);
   return true;
 }
 
-typedef bool (*SetIntrinsicFn)(JSContext*, JSScript*, jsbytecode*, HandleValue);
-static const VMFunction SetIntrinsicInfo = FunctionInfo<SetIntrinsicFn>(
-    SetIntrinsicOperation, "SetIntrinsicOperation");
-
 template <typename Handler>
 bool BaselineCodeGen<Handler>::emit_JSOP_SETINTRINSIC() {
   frame.syncStack(0);
   masm.loadValue(frame.addressOfStackValue(-1), R0);
 
   prepareVMCall();
 
   pushArg(R0);
   pushBytecodePCArg();
   pushScriptArg(R2.scratchReg());
 
-  return callVM(SetIntrinsicInfo);
-}
-
-typedef bool (*DefVarFn)(JSContext*, HandleObject, HandleScript, jsbytecode*);
-static const VMFunction DefVarInfo =
-    FunctionInfo<DefVarFn>(DefVarOperation, "DefVarOperation");
+  using Fn = bool (*)(JSContext*, JSScript*, jsbytecode*, HandleValue);
+  return callVM<Fn, SetIntrinsicOperation>();
+}
 
 template <typename Handler>
 bool BaselineCodeGen<Handler>::emit_JSOP_DEFVAR() {
   frame.syncStack(0);
 
   masm.loadPtr(frame.addressOfEnvironmentChain(), R0.scratchReg());
 
   prepareVMCall();
 
   pushBytecodePCArg();
   pushScriptArg(R2.scratchReg());
   pushArg(R0.scratchReg());
 
-  return callVM(DefVarInfo);
-}
-
-typedef bool (*DefLexicalFn)(JSContext*, HandleObject, HandleScript,
-                             jsbytecode*);
-static const VMFunction DefLexicalInfo =
-    FunctionInfo<DefLexicalFn>(DefLexicalOperation, "DefLexicalOperation");
+  using Fn = bool (*)(JSContext*, HandleObject, HandleScript, jsbytecode*);
+  return callVM<Fn, DefVarOperation>();
+}
 
 template <typename Handler>
 bool BaselineCodeGen<Handler>::emitDefLexical(JSOp op) {
   MOZ_ASSERT(op == JSOP_DEFCONST || op == JSOP_DEFLET);
 
   frame.syncStack(0);
 
   masm.loadPtr(frame.addressOfEnvironmentChain(), R0.scratchReg());
 
   prepareVMCall();
 
   pushBytecodePCArg();
   pushScriptArg(R2.scratchReg());
   pushArg(R0.scratchReg());
 
-  return callVM(DefLexicalInfo);
+  using Fn = bool (*)(JSContext*, HandleObject, HandleScript, jsbytecode*);
+  return callVM<Fn, DefLexicalOperation>();
 }
 
 template <typename Handler>
 bool BaselineCodeGen<Handler>::emit_JSOP_DEFCONST() {
   return emitDefLexical(JSOP_DEFCONST);
 }
 
 template <typename Handler>
 bool BaselineCodeGen<Handler>::emit_JSOP_DEFLET() {
   return emitDefLexical(JSOP_DEFLET);
 }
 
-typedef bool (*DefFunOperationFn)(JSContext*, HandleScript, HandleObject,
-                                  HandleFunction);
-static const VMFunction DefFunOperationInfo =
-    FunctionInfo<DefFunOperationFn>(DefFunOperation, "DefFunOperation");
-
 template <typename Handler>
 bool BaselineCodeGen<Handler>::emit_JSOP_DEFFUN() {
   frame.popRegsAndSync(1);
   masm.unboxObject(R0, R0.scratchReg());
   masm.loadPtr(frame.addressOfEnvironmentChain(), R1.scratchReg());
 
   prepareVMCall();
 
   pushArg(R0.scratchReg());
   pushArg(R1.scratchReg());
   pushScriptArg(R2.scratchReg());
 
-  return callVM(DefFunOperationInfo);
-}
-
-typedef bool (*InitPropGetterSetterFn)(JSContext*, jsbytecode*, HandleObject,
-                                       HandlePropertyName, HandleObject);
-static const VMFunction InitPropGetterSetterInfo =
-    FunctionInfo<InitPropGetterSetterFn>(InitGetterSetterOperation,
-                                         "InitPropGetterSetterOperation");
+  using Fn = bool (*)(JSContext*, HandleScript, HandleObject, HandleFunction);
+  return callVM<Fn, DefFunOperation>();
+}
 
 template <typename Handler>
 bool BaselineCodeGen<Handler>::emitInitPropGetterSetter() {
   // Keep values on the stack for the decompiler.
   frame.syncStack(0);
 
   prepareVMCall();
 
   masm.unboxObject(frame.addressOfStackValue(-1), R0.scratchReg());
   masm.unboxObject(frame.addressOfStackValue(-2), R1.scratchReg());
 
   pushArg(R0.scratchReg());
   pushScriptNameArg();
   pushArg(R1.scratchReg());
   pushBytecodePCArg();
 
-  if (!callVM(InitPropGetterSetterInfo)) {
+  using Fn = bool (*)(JSContext*, jsbytecode*, HandleObject, HandlePropertyName,
+                      HandleObject);
+  if (!callVM<Fn, InitPropGetterSetterOperation>()) {
     return false;
   }
 
   frame.pop();
   return true;
 }
 
 template <typename Handler>
@@ -3519,39 +3453,35 @@ bool BaselineCodeGen<Handler>::emit_JSOP
   return emitInitPropGetterSetter();
 }
 
 template <typename Handler>
 bool BaselineCodeGen<Handler>::emit_JSOP_INITHIDDENPROP_SETTER() {
   return emitInitPropGetterSetter();
 }
 
-typedef bool (*InitElemGetterSetterFn)(JSContext*, jsbytecode*, HandleObject,
-                                       HandleValue, HandleObject);
-static const VMFunction InitElemGetterSetterInfo =
-    FunctionInfo<InitElemGetterSetterFn>(InitGetterSetterOperation,
-                                         "InitElemGetterSetterOperation");
-
 template <typename Handler>
 bool BaselineCodeGen<Handler>::emitInitElemGetterSetter() {
   // Load index and value in R0 and R1, but keep values on the stack for the
   // decompiler.
   frame.syncStack(0);
   masm.loadValue(frame.addressOfStackValue(-2), R0);
   masm.unboxObject(frame.addressOfStackValue(-1), R1.scratchReg());
 
   prepareVMCall();
 
   pushArg(R1.scratchReg());
   pushArg(R0);
   masm.unboxObject(frame.addressOfStackValue(-3), R0.scratchReg());
   pushArg(R0.scratchReg());
   pushBytecodePCArg();
 
-  if (!callVM(InitElemGetterSetterInfo)) {
+  using Fn = bool (*)(JSContext*, jsbytecode*, HandleObject, HandleValue,
+                      HandleObject);
+  if (!callVM<Fn, InitElemGetterSetterOperation>()) {
     return false;
   }
 
   frame.popn(2);
   return true;
 }
 
 template <typename Handler>
@@ -3795,26 +3725,23 @@ bool BaselineCompilerCodeGen::emit_JSOP_
   return true;
 }
 
 template <>
 bool BaselineInterpreterCodeGen::emit_JSOP_NEWTARGET() {
   MOZ_CRASH("NYI: interpreter JSOP_NEWTARGET");
 }
 
-typedef bool (*ThrowRuntimeLexicalErrorFn)(JSContext* cx, unsigned);
-static const VMFunction ThrowRuntimeLexicalErrorInfo =
-    FunctionInfo<ThrowRuntimeLexicalErrorFn>(jit::ThrowRuntimeLexicalError,
-                                             "ThrowRuntimeLexicalError");
-
 template <typename Handler>
 bool BaselineCodeGen<Handler>::emitThrowConstAssignment() {
   prepareVMCall();
   pushArg(Imm32(JSMSG_BAD_CONST_ASSIGN));
-  return callVM(ThrowRuntimeLexicalErrorInfo);
+
+  using Fn = bool (*)(JSContext*, unsigned);
+  return callVM<Fn, jit::ThrowRuntimeLexicalError>();
 }
 
 template <typename Handler>
 bool BaselineCodeGen<Handler>::emit_JSOP_THROWSETCONST() {
   return emitThrowConstAssignment();
 }
 
 template <typename Handler>
@@ -3831,17 +3758,19 @@ template <typename Handler>
 bool BaselineCodeGen<Handler>::emitUninitializedLexicalCheck(
     const ValueOperand& val) {
   Label done;
   masm.branchTestMagicValue(Assembler::NotEqual, val, JS_UNINITIALIZED_LEXICAL,
                             &done);
 
   prepareVMCall();
   pushArg(Imm32(JSMSG_UNINITIALIZED_LEXICAL));
-  if (!callVM(ThrowRuntimeLexicalErrorInfo)) {
+
+  using Fn = bool (*)(JSContext*, unsigned);
+  if (!callVM<Fn, jit::ThrowRuntimeLexicalError>()) {
     return false;
   }
 
   masm.bind(&done);
   return true;
 }
 
 template <>
@@ -3997,30 +3926,26 @@ bool BaselineCodeGen<Handler>::emit_JSOP
   return emitSpreadCall(JSOP_SPREADEVAL);
 }
 
 template <typename Handler>
 bool BaselineCodeGen<Handler>::emit_JSOP_STRICTSPREADEVAL() {
   return emitSpreadCall(JSOP_STRICTSPREADEVAL);
 }
 
-typedef bool (*OptimizeSpreadCallFn)(JSContext*, HandleValue, bool*);
-static const VMFunction OptimizeSpreadCallInfo =
-    FunctionInfo<OptimizeSpreadCallFn>(OptimizeSpreadCall,
-                                       "OptimizeSpreadCall");
-
 template <typename Handler>
 bool BaselineCodeGen<Handler>::emit_JSOP_OPTIMIZE_SPREADCALL() {
   frame.syncStack(0);
   masm.loadValue(frame.addressOfStackValue(-1), R0);
 
   prepareVMCall();
   pushArg(R0);
 
-  if (!callVM(OptimizeSpreadCallInfo)) {
+  using Fn = bool (*)(JSContext*, HandleValue, bool*);
+  if (!callVM<Fn, OptimizeSpreadCall>()) {
     return false;
   }
 
   masm.boxNonDouble(JSVAL_TYPE_BOOLEAN, ReturnReg, R0);
   frame.push(R0);
   return true;
 }
 
--- a/js/src/jit/BaselineCompiler.h
+++ b/js/src/jit/BaselineCompiler.h
@@ -662,15 +662,14 @@ class BaselineInterpreterHandler {
 
 using BaselineInterpreterCodeGen = BaselineCodeGen<BaselineInterpreterHandler>;
 
 class BaselineInterpreterGenerator final : private BaselineInterpreterCodeGen {
  public:
   explicit BaselineInterpreterGenerator(JSContext* cx);
 };
 
-extern const VMFunction NewArrayCopyOnWriteInfo;
 extern const VMFunction ImplicitThisInfo;
 
 }  // namespace jit
 }  // namespace js
 
 #endif /* jit_BaselineCompiler_h */
--- a/js/src/jit/CodeGenerator.cpp
+++ b/js/src/jit/CodeGenerator.cpp
@@ -6207,16 +6207,22 @@ void CodeGenerator::visitNewArray(LNewAr
   masm.bind(ool->rejoin());
 }
 
 void CodeGenerator::visitOutOfLineNewArray(OutOfLineNewArray* ool) {
   visitNewArrayCallVM(ool->lir());
   masm.jump(ool->rejoin());
 }
 
+typedef ArrayObject* (*NewArrayCopyOnWriteFn)(JSContext*, HandleArrayObject,
+                                              gc::InitialHeap);
+static const VMFunction NewArrayCopyOnWriteInfo =
+    FunctionInfo<NewArrayCopyOnWriteFn>(js::NewDenseCopyOnWriteArray,
+                                        "NewDenseCopyOnWriteArray");
+
 void CodeGenerator::visitNewArrayCopyOnWrite(LNewArrayCopyOnWrite* lir) {
   Register objReg = ToRegister(lir->output());
   Register tempReg = ToRegister(lir->temp());
   ArrayObject* templateObject = lir->mir()->templateObject();
   gc::InitialHeap initialHeap = lir->mir()->initialHeap();
 
   // If we have a template object, we can inline call object creation.
   OutOfLineCode* ool =
@@ -6720,17 +6726,17 @@ void CodeGenerator::visitInitElem(LInitE
   pushArg(ImmPtr(lir->mir()->resumePoint()->pc()));
 
   callVM(InitElemInfo, lir);
 }
 
 typedef bool (*InitElemGetterSetterFn)(JSContext*, jsbytecode*, HandleObject,
                                        HandleValue, HandleObject);
 static const VMFunction InitElemGetterSetterInfo =
-    FunctionInfo<InitElemGetterSetterFn>(InitGetterSetterOperation,
+    FunctionInfo<InitElemGetterSetterFn>(InitElemGetterSetterOperation,
                                          "InitElemGetterSetterOperation");
 
 void CodeGenerator::visitInitElemGetterSetter(LInitElemGetterSetter* lir) {
   Register obj = ToRegister(lir->object());
   Register value = ToRegister(lir->value());
 
   pushArg(value);
   pushArg(ToValue(lir, LInitElemGetterSetter::IdIndex));
@@ -6752,17 +6758,17 @@ void CodeGenerator::visitMutateProto(LMu
   pushArg(objReg);
 
   callVM(MutatePrototypeInfo, lir);
 }
 
 typedef bool (*InitPropGetterSetterFn)(JSContext*, jsbytecode*, HandleObject,
                                        HandlePropertyName, HandleObject);
 static const VMFunction InitPropGetterSetterInfo =
-    FunctionInfo<InitPropGetterSetterFn>(InitGetterSetterOperation,
+    FunctionInfo<InitPropGetterSetterFn>(InitPropGetterSetterOperation,
                                          "InitPropGetterSetterOperation");
 
 void CodeGenerator::visitInitPropGetterSetter(LInitPropGetterSetter* lir) {
   Register obj = ToRegister(lir->object());
   Register value = ToRegister(lir->value());
 
   pushArg(value);
   pushArg(ImmGCPtr(lir->mir()->name()));
--- a/js/src/jit/VMFunctionList-inl.h
+++ b/js/src/jit/VMFunctionList-inl.h
@@ -18,23 +18,48 @@ namespace jit {
 // List of all VM functions to be used with callVM. Each entry stores the name
 // (must be unique, used for the VMFunctionId enum and profiling) and the C++
 // function to be called. This list must be sorted on the name field.
 #define VMFUNCTION_LIST(_)                                                   \
   _(BaselineDebugPrologue, js::jit::DebugPrologue)                           \
   _(BaselineGetFunctionThis, js::jit::BaselineGetFunctionThis)               \
   _(BaselineThrowInitializedThis, js::jit::BaselineThrowInitializedThis)     \
   _(BaselineThrowUninitializedThis, js::jit::BaselineThrowUninitializedThis) \
+  _(BindVarOperation, js::BindVarOperation)                                  \
   _(CheckIsCallable, js::jit::CheckIsCallable)                               \
   _(CheckOverRecursedBaseline, js::jit::CheckOverRecursedBaseline)           \
+  _(CloneRegExpObject, js::CloneRegExpObject)                                \
+  _(DefFunOperation, js::DefFunOperation)                                    \
+  _(DefLexicalOperation, js::DefLexicalOperation)                            \
+  _(DefVarOperation, js::DefVarOperation)                                    \
+  _(DeleteElementNonStrict, js::DeleteElementJit<false>)                     \
+  _(DeleteElementStrict, js::DeleteElementJit<true>)                         \
+  _(DeleteNameOperation, js::DeleteNameOperation)                            \
+  _(DeletePropertyNonStrict, js::DeletePropertyJit<false>)                   \
+  _(DeletePropertyStrict, js::DeletePropertyJit<true>)                       \
+  _(GetImportOperation, js::GetImportOperation)                              \
   _(GetNonSyntacticGlobalThis, js::GetNonSyntacticGlobalThis)                \
+  _(InitElemGetterSetterOperation, js::InitElemGetterSetterOperation)        \
+  _(InitPropGetterSetterOperation, js::InitPropGetterSetterOperation)        \
   _(InterruptCheck, js::jit::InterruptCheck)                                 \
   _(IonCompileScriptForBaseline, js::jit::IonCompileScriptForBaseline)       \
+  _(Lambda, js::Lambda)                                                      \
+  _(LambdaArrow, js::LambdaArrow)                                            \
+  _(MutatePrototype, js::jit::MutatePrototype)                               \
+  _(NewArrayCopyOnWriteOperation, js::NewArrayCopyOnWriteOperation)          \
+  _(NewDenseCopyOnWriteArray, js::NewDenseCopyOnWriteArray)                  \
+  _(OptimizeSpreadCall, js::OptimizeSpreadCall)                              \
+  _(ProcessCallSiteObjOperation, js::ProcessCallSiteObjOperation)            \
+  _(SetFunctionName, js::SetFunctionName)                                    \
+  _(SetIntrinsicOperation, js::SetIntrinsicOperation)                        \
+  _(SetPropertySuper, js::SetPropertySuper)                                  \
+  _(SingletonObjectLiteralOperation, js::SingletonObjectLiteralOperation)    \
   _(ThrowBadDerivedReturn, js::jit::ThrowBadDerivedReturn)                   \
-  _(ThrowCheckIsObject, js::ThrowCheckIsObject)
+  _(ThrowCheckIsObject, js::ThrowCheckIsObject)                              \
+  _(ThrowRuntimeLexicalError, js::jit::ThrowRuntimeLexicalError)
 
 enum class VMFunctionId {
 #define DEF_ID(name, fp) name,
   VMFUNCTION_LIST(DEF_ID)
 #undef DEF_ID
       Count
 };
 
--- a/js/src/jit/arm64/vixl/Disasm-vixl.cpp
+++ b/js/src/jit/arm64/vixl/Disasm-vixl.cpp
@@ -21,16 +21,17 @@
 // DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
 // SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
 // CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
 // OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
 #include "jit/arm64/vixl/Disasm-vixl.h"
 
+#include "mozilla/Sprintf.h"
 #include <cstdlib>
 
 namespace vixl {
 
 Disassembler::Disassembler() {
   buffer_size_ = 256;
   buffer_ = reinterpret_cast<char*>(malloc(buffer_size_));
   buffer_pos_ = 0;
--- a/js/src/vm/Interpreter.cpp
+++ b/js/src/vm/Interpreter.cpp
@@ -3673,17 +3673,17 @@ static MOZ_NEVER_INLINE JS_HAZ_JSNATIVE_
     CASE(JSOP_INITPROP_SETTER)
     CASE(JSOP_INITHIDDENPROP_SETTER) {
       MOZ_ASSERT(REGS.stackDepth() >= 2);
 
       ReservedRooted<JSObject*> obj(&rootObject0, &REGS.sp[-2].toObject());
       ReservedRooted<PropertyName*> name(&rootName0, script->getName(REGS.pc));
       ReservedRooted<JSObject*> val(&rootObject1, &REGS.sp[-1].toObject());
 
-      if (!InitGetterSetterOperation(cx, REGS.pc, obj, name, val)) {
+      if (!InitPropGetterSetterOperation(cx, REGS.pc, obj, name, val)) {
         goto error;
       }
 
       REGS.sp--;
     }
     END_CASE(JSOP_INITPROP_GETTER)
 
     CASE(JSOP_INITELEM_GETTER)
@@ -3691,17 +3691,17 @@ static MOZ_NEVER_INLINE JS_HAZ_JSNATIVE_
     CASE(JSOP_INITELEM_SETTER)
     CASE(JSOP_INITHIDDENELEM_SETTER) {
       MOZ_ASSERT(REGS.stackDepth() >= 3);
 
       ReservedRooted<JSObject*> obj(&rootObject0, &REGS.sp[-3].toObject());
       ReservedRooted<Value> idval(&rootValue0, REGS.sp[-2]);
       ReservedRooted<JSObject*> val(&rootObject1, &REGS.sp[-1].toObject());
 
-      if (!InitGetterSetterOperation(cx, REGS.pc, obj, idval, val)) {
+      if (!InitElemGetterSetterOperation(cx, REGS.pc, obj, idval, val)) {
         goto error;
       }
 
       REGS.sp -= 2;
     }
     END_CASE(JSOP_INITELEM_GETTER)
 
     CASE(JSOP_HOLE) { PUSH_MAGIC(JS_ELEMENTS_HOLE); }
@@ -4927,19 +4927,19 @@ unsigned js::GetInitDataPropAttrs(JSOp o
     case JSOP_INITHIDDENELEM:
       // Non-enumerable, but writable and configurable
       return 0;
     default:;
   }
   MOZ_CRASH("Unknown data initprop");
 }
 
-bool js::InitGetterSetterOperation(JSContext* cx, jsbytecode* pc,
-                                   HandleObject obj, HandleId id,
-                                   HandleObject val) {
+static bool InitGetterSetterOperation(JSContext* cx, jsbytecode* pc,
+                                      HandleObject obj, HandleId id,
+                                      HandleObject val) {
   MOZ_ASSERT(val->isCallable());
 
   JSOp op = JSOp(*pc);
 
   unsigned attrs = 0;
   if (!IsHiddenInitOp(op)) {
     attrs |= JSPROP_ENUMERATE;
   }
@@ -4952,26 +4952,27 @@ bool js::InitGetterSetterOperation(JSCon
 
   MOZ_ASSERT(op == JSOP_INITPROP_SETTER || op == JSOP_INITELEM_SETTER ||
              op == JSOP_INITHIDDENPROP_SETTER ||
              op == JSOP_INITHIDDENELEM_SETTER);
   attrs |= JSPROP_SETTER;
   return DefineAccessorProperty(cx, obj, id, nullptr, val, attrs);
 }
 
-bool js::InitGetterSetterOperation(JSContext* cx, jsbytecode* pc,
-                                   HandleObject obj, HandlePropertyName name,
-                                   HandleObject val) {
+bool js::InitPropGetterSetterOperation(JSContext* cx, jsbytecode* pc,
+                                       HandleObject obj,
+                                       HandlePropertyName name,
+                                       HandleObject val) {
   RootedId id(cx, NameToId(name));
   return InitGetterSetterOperation(cx, pc, obj, id, val);
 }
 
-bool js::InitGetterSetterOperation(JSContext* cx, jsbytecode* pc,
-                                   HandleObject obj, HandleValue idval,
-                                   HandleObject val) {
+bool js::InitElemGetterSetterOperation(JSContext* cx, jsbytecode* pc,
+                                       HandleObject obj, HandleValue idval,
+                                       HandleObject val) {
   RootedId id(cx);
   if (!ToPropertyKey(cx, idval, &id)) {
     return false;
   }
 
   return InitGetterSetterOperation(cx, pc, obj, id, val);
 }
 
--- a/js/src/vm/Interpreter.h
+++ b/js/src/vm/Interpreter.h
@@ -510,29 +510,28 @@ bool ThrowMsgOperation(JSContext* cx, co
 bool GetAndClearException(JSContext* cx, MutableHandleValue res);
 
 bool DeleteNameOperation(JSContext* cx, HandlePropertyName name,
                          HandleObject scopeObj, MutableHandleValue res);
 
 bool ImplicitThisOperation(JSContext* cx, HandleObject scopeObj,
                            HandlePropertyName name, MutableHandleValue res);
 
-bool InitGetterSetterOperation(JSContext* cx, jsbytecode* pc, HandleObject obj,
-                               HandleId id, HandleObject val);
-
-bool InitGetterSetterOperation(JSContext* cx, jsbytecode* pc, HandleObject obj,
-                               HandlePropertyName name, HandleObject val);
+bool InitPropGetterSetterOperation(JSContext* cx, jsbytecode* pc,
+                                   HandleObject obj, HandlePropertyName name,
+                                   HandleObject val);
 
 unsigned GetInitDataPropAttrs(JSOp op);
 
 bool EnterWithOperation(JSContext* cx, AbstractFramePtr frame, HandleValue val,
                         Handle<WithScope*> scope);
 
-bool InitGetterSetterOperation(JSContext* cx, jsbytecode* pc, HandleObject obj,
-                               HandleValue idval, HandleObject val);
+bool InitElemGetterSetterOperation(JSContext* cx, jsbytecode* pc,
+                                   HandleObject obj, HandleValue idval,
+                                   HandleObject val);
 
 bool SpreadCallOperation(JSContext* cx, HandleScript script, jsbytecode* pc,
                          HandleValue thisv, HandleValue callee, HandleValue arr,
                          HandleValue newTarget, MutableHandleValue res);
 
 bool OptimizeSpreadCall(JSContext* cx, HandleValue arg, bool* optimized);
 
 JSObject* NewObjectOperation(JSContext* cx, HandleScript script, jsbytecode* pc,
--- a/layout/base/PresShell.cpp
+++ b/layout/base/PresShell.cpp
@@ -6654,32 +6654,20 @@ nsresult PresShell::EventHandler::Handle
     // automatically updated.
     if (!DispatchPrecedingPointerEvent(
             aFrame, aGUIEvent, pointerCapturingContent, aDontRetargetEvents,
             &eventTargetData, aEventStatus)) {
       return NS_OK;
     }
 
     // frame could be null after dispatching pointer events.
-    if (aGUIEvent->mClass == eTouchEventClass) {
-      if (aGUIEvent->mMessage == eTouchStart) {
-        WidgetTouchEvent* touchEvent = aGUIEvent->AsTouchEvent();
-        if (nsIFrame* newFrame =
-                TouchManager::SuppressInvalidPointsAndGetTargetedFrame(
-                    touchEvent)) {
-          eventTargetData.SetFrameAndComputePresShellAndContent(newFrame,
-                                                                aGUIEvent);
-        }
-      } else if (PresShell* newShell =
-                     PresShell::GetShellForTouchEvent(aGUIEvent)) {
-        // Touch events (except touchstart) are dispatching to the captured
-        // element. Get correct shell from it.
-        eventTargetData.mPresShell = newShell;
-      }
-    }
+    // XXX Despite of this comment, we update the event target data outside
+    //     DispatchPrecedingPointerEvent().  Can we make it call
+    //     UpdateTouchEventTarget()?
+    eventTargetData.UpdateTouchEventTarget(aGUIEvent);
 
     // Handle the event in the correct shell.
     // We pass the subshell's root frame as the frame to start from. This is
     // the only correct alternative; if the event was captured then it
     // must have been captured by us or some ancestor shell and we
     // now ask the subshell to dispatch it normally.
     eventTargetData.mPresShell->PushCurrentEventInfo(eventTargetData.mFrame,
                                                      eventTargetData.mContent);
@@ -10860,8 +10848,37 @@ bool PresShell::EventHandler::EventTarge
   while (content && !content->IsElement()) {
     content = content->GetFlattenedTreeParent();
   }
   mContent = content;
 
   // If we found an element, target it.  Otherwise, target *nothing*.
   return !!mContent;
 }
+
+void PresShell::EventHandler::EventTargetData::UpdateTouchEventTarget(
+    WidgetGUIEvent* aGUIEvent) {
+  MOZ_ASSERT(aGUIEvent);
+
+  if (aGUIEvent->mClass != eTouchEventClass) {
+    return;
+  }
+
+  if (aGUIEvent->mMessage == eTouchStart) {
+    WidgetTouchEvent* touchEvent = aGUIEvent->AsTouchEvent();
+    nsIFrame* newFrame =
+        TouchManager::SuppressInvalidPointsAndGetTargetedFrame(touchEvent);
+    if (!newFrame) {
+      return;  // XXX Why don't we stop handling the event in this case?
+    }
+    SetFrameAndComputePresShellAndContent(newFrame, aGUIEvent);
+    return;
+  }
+
+  PresShell* newPresShell = PresShell::GetShellForTouchEvent(aGUIEvent);
+  if (!newPresShell) {
+    return;  // XXX Why don't we stop handling the event in this case?
+  }
+
+  // Touch events (except touchstart) are dispatching to the captured
+  // element. Get correct shell from it.
+  mPresShell = newPresShell;
+}
--- a/layout/base/PresShell.h
+++ b/layout/base/PresShell.h
@@ -634,16 +634,25 @@ class PresShell final : public nsIPresSh
        * @param aGUIEvent       The handling event.
        * @return                true if caller can keep handling the event.
        *                        Otherwise, false.
        *                        Note that even if this returns true, mContent
        *                        may be nullptr.
        */
       bool ComputeElementFromFrame(WidgetGUIEvent* aGUIEvent);
 
+      /**
+       * UpdateTouchEventTarget() updates mFrame, mPresShell and mContent if
+       * aGUIEvent is a touch event and there is new proper target.
+       *
+       * @param aGUIEvent       The handled event.  If it's not a touch event,
+       *                        this method does nothing.
+       */
+      void UpdateTouchEventTarget(WidgetGUIEvent* aGUIEvent);
+
       RefPtr<PresShell> mPresShell;
       nsIFrame* mFrame;
       nsCOMPtr<nsIContent> mContent;
       nsCOMPtr<nsIContent> mOverrideClickTarget;
     };
 
     /**
      * MaybeFlushPendingNotifications() maybe flush pending notifications if
--- a/mobile/android/geckoview/api.txt
+++ b/mobile/android/geckoview/api.txt
@@ -30,26 +30,21 @@ package org.mozilla.geckoview {
     field protected final android.graphics.RectF mTempRect;
     field protected final boolean mUseFloatingToolbar;
   }
 
   @android.support.annotation.UiThread public final class CompositorController {
     method public void addDrawCallback(@android.support.annotation.NonNull java.lang.Runnable);
     method public int getClearColor();
     method @android.support.annotation.Nullable public java.lang.Runnable getFirstPaintCallback();
-    method public void getPixels(@android.support.annotation.NonNull org.mozilla.geckoview.CompositorController.GetPixelsCallback);
     method public void removeDrawCallback(@android.support.annotation.NonNull java.lang.Runnable);
     method public void setClearColor(int);
     method public void setFirstPaintCallback(@android.support.annotation.Nullable java.lang.Runnable);
   }
 
-  public static interface CompositorController.GetPixelsCallback {
-    method @android.support.annotation.UiThread public void onPixelsResult(int, int, @android.support.annotation.Nullable java.nio.IntBuffer);
-  }
-
   @android.support.annotation.AnyThread public class ContentBlocking {
     ctor public ContentBlocking();
     field public static final int AT_AD = 2;
     field public static final int AT_ALL = 62;
     field public static final int AT_ANALYTIC = 4;
     field public static final int AT_CONTENT = 16;
     field public static final int AT_SOCIAL = 8;
     field public static final int AT_TEST = 32;
@@ -131,16 +126,17 @@ package org.mozilla.geckoview {
   public static interface DynamicToolbarAnimator.ToolbarChromeProxy {
     method @android.support.annotation.UiThread @android.support.annotation.Nullable public android.graphics.Bitmap getBitmapOfToolbarChrome();
     method @android.support.annotation.UiThread public boolean isToolbarChromeVisible();
     method @android.support.annotation.UiThread public void toggleToolbarChrome(boolean);
   }
 
   public class GeckoDisplay {
     ctor protected GeckoDisplay(org.mozilla.geckoview.GeckoSession);
+    method @android.support.annotation.UiThread @android.support.annotation.NonNull public org.mozilla.geckoview.GeckoResult<android.graphics.Bitmap> capturePixels();
     method @android.support.annotation.UiThread public void screenOriginChanged(int, int);
     method @android.support.annotation.UiThread public boolean shouldPinOnScreen();
     method @android.support.annotation.UiThread public void surfaceChanged(@android.support.annotation.NonNull android.view.Surface, int, int);
     method @android.support.annotation.UiThread public void surfaceChanged(@android.support.annotation.NonNull android.view.Surface, int, int, int, int);
     method @android.support.annotation.UiThread public void surfaceDestroyed();
   }
 
   public interface GeckoResponse<T> {
@@ -730,16 +726,17 @@ package org.mozilla.geckoview {
 
   public class GeckoVRManager {
     method @android.support.annotation.AnyThread public static synchronized void setExternalContext(long);
   }
 
   @android.support.annotation.UiThread public class GeckoView extends android.widget.FrameLayout {
     ctor public GeckoView(android.content.Context);
     ctor public GeckoView(android.content.Context, android.util.AttributeSet);
+    method @android.support.annotation.UiThread @android.support.annotation.NonNull public org.mozilla.geckoview.GeckoResult<android.graphics.Bitmap> capturePixels();
     method public void coverUntilFirstPaint(int);
     method @android.support.annotation.NonNull public org.mozilla.geckoview.DynamicToolbarAnimator getDynamicToolbarAnimator();
     method @android.support.annotation.AnyThread @android.support.annotation.NonNull public org.mozilla.gecko.EventDispatcher getEventDispatcher();
     method @android.support.annotation.NonNull public org.mozilla.geckoview.PanZoomController getPanZoomController();
     method @android.support.annotation.AnyThread @android.support.annotation.Nullable public org.mozilla.geckoview.GeckoSession getSession();
     method @android.support.annotation.UiThread @android.support.annotation.Nullable public org.mozilla.geckoview.GeckoSession releaseSession();
     method @android.support.annotation.UiThread public void setSession(@android.support.annotation.NonNull org.mozilla.geckoview.GeckoSession);
     method @android.support.annotation.UiThread public void setSession(@android.support.annotation.NonNull org.mozilla.geckoview.GeckoSession, @android.support.annotation.Nullable org.mozilla.geckoview.GeckoRuntime);
--- a/mobile/android/geckoview/proguard-rules.txt
+++ b/mobile/android/geckoview/proguard-rules.txt
@@ -81,16 +81,19 @@
 # through introspection.
 
 -keepclassmembers class **.R$* {
   public static <fields>;
 }
 
 # GeckoView specific rules.
 
+# Keep everthing in org.mozilla.geckoview
+-keep class org.mozilla.geckoview.** { *; }
+
 -keep class org.mozilla.gecko.SysInfo {
     *;
 }
 
 # Keep the annotation.
 -keep @interface org.mozilla.gecko.annotation.JNITarget
 
 # Keep classes tagged with the annotation.
new file mode 100644
--- /dev/null
+++ b/mobile/android/geckoview/src/androidTest/assets/www/colors.html
@@ -0,0 +1,6 @@
+<html>
+    <head><title>Colours</title></head>
+    <body style="background-color:green;">
+        <div id="fullscreen" style="width:100%;height:100%;position:fixed;top:0;left:0;z-index:100;"></div>
+    </body>
+</html>
\ No newline at end of file
--- a/mobile/android/geckoview/src/androidTest/java/org/mozilla/geckoview/test/BaseSessionTest.kt
+++ b/mobile/android/geckoview/src/androidTest/java/org/mozilla/geckoview/test/BaseSessionTest.kt
@@ -48,16 +48,17 @@ open class BaseSessionTest(noErrorCollec
         const val VIDEO_BAD_PATH = "/assets/www/badVideoPath.html"
         const val UNKNOWN_HOST_URI = "http://www.test.invalid/"
         const val FULLSCREEN_PATH = "/assets/www/fullscreen.html"
         const val VIEWPORT_PATH = "/assets/www/viewport.html"
         const val IFRAME_REDIRECT_LOCAL = "/assets/www/iframe_redirect_local.html"
         const val IFRAME_REDIRECT_AUTOMATION = "/assets/www/iframe_redirect_automation.html"
         const val AUTOPLAY_PATH = "/assets/www/autoplay.html"
         const val SCROLL_TEST_PATH = "/assets/www/scroll.html"
+        const val COLORS_HTML_PATH = "/assets/www/colors.html"
     }
 
     @get:Rule val sessionRule = GeckoSessionTestRule()
 
     @get:Rule val errors = ErrorCollector()
 
     val mainSession get() = sessionRule.session
 
new file mode 100644
--- /dev/null
+++ b/mobile/android/geckoview/src/androidTest/java/org/mozilla/geckoview/test/ScreenshotTest.kt
@@ -0,0 +1,106 @@
+/* -*- Mode: Java; c-basic-offset: 4; tab-width: 4; indent-tabs-mode: nil; -*-
+ * Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+
+package org.mozilla.geckoview.test
+
+
+import android.graphics.*
+import android.support.test.filters.MediumTest
+import android.support.test.runner.AndroidJUnit4
+import android.view.Surface
+import org.hamcrest.Matchers.notNullValue
+import org.hamcrest.Matchers.nullValue
+import org.junit.Assert.fail
+import org.junit.Rule
+import org.junit.Test
+import org.junit.rules.ExpectedException
+import org.junit.runner.RunWith
+import org.mozilla.geckoview.GeckoResult
+import org.mozilla.geckoview.test.rule.GeckoSessionTestRule.ReuseSession
+import org.mozilla.geckoview.test.rule.GeckoSessionTestRule.WithDisplay
+
+const val SCREEN_HEIGHT = 100
+const val SCREEN_WIDTH = 100
+
+@RunWith(AndroidJUnit4::class)
+@MediumTest
+@ReuseSession(false)
+class ScreenshotTest : BaseSessionTest() {
+
+    @get:Rule
+    val expectedEx: ExpectedException = ExpectedException.none()
+
+    private fun getComparisonScreenshot(width: Int, height: Int): Bitmap {
+        val screenshotFile = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888)
+        val canvas = Canvas(screenshotFile)
+        val paint = Paint()
+        paint.color = Color.rgb(0, 128, 0)
+        canvas.drawRect(0f, 0f, width.toFloat(), height.toFloat(), paint)
+        return screenshotFile
+    }
+
+    private fun assertScreenshotResult(result: GeckoResult<Bitmap>, comparisonImage: Bitmap) {
+        sessionRule.waitForResult(result).let {
+            assertThat("Screenshot is not null",
+                    it, notNullValue())
+            assert(it.sameAs(comparisonImage)) {  "Screenshots are the same" }
+        }
+    }
+
+    @WithDisplay(height = SCREEN_HEIGHT, width = SCREEN_WIDTH)
+    @Test
+    fun capturePixelsSucceeds() {
+        val screenshotFile = getComparisonScreenshot(SCREEN_WIDTH, SCREEN_HEIGHT)
+
+        sessionRule.session.loadTestPath(COLORS_HTML_PATH)
+        sessionRule.waitForPageStop()
+
+        sessionRule.display?.let {
+            assertScreenshotResult(it.capturePixels(), screenshotFile)
+        }
+    }
+
+    @WithDisplay(height = SCREEN_HEIGHT, width = SCREEN_WIDTH)
+    @Test
+    fun capturePixelsCanBeCalledMultipleTimes() {
+        val screenshotFile = getComparisonScreenshot(SCREEN_WIDTH, SCREEN_HEIGHT)
+
+        sessionRule.session.loadTestPath(COLORS_HTML_PATH)
+        sessionRule.waitForPageStop()
+
+        sessionRule.display?.let {
+            val call1 = it.capturePixels()
+            val call2 = it.capturePixels()
+            val call3 = it.capturePixels()
+            assertScreenshotResult(call1, screenshotFile)
+            assertScreenshotResult(call2, screenshotFile)
+            assertScreenshotResult(call3, screenshotFile)
+        }
+    }
+
+    @WithDisplay(height = SCREEN_HEIGHT, width = SCREEN_WIDTH)
+    @Test
+    fun capturePixelsCompletesCompositorPausedRestarted() {
+        sessionRule.display?.let {
+            it.surfaceDestroyed()
+            val result = it.capturePixels()
+            val texture = SurfaceTexture(0)
+            texture.setDefaultBufferSize(SCREEN_WIDTH, SCREEN_HEIGHT)
+            val surface = Surface(texture)
+            it.surfaceChanged(surface, SCREEN_WIDTH, SCREEN_HEIGHT)
+            sessionRule.waitForResult(result)
+        }
+    }
+
+    @Test
+    fun capturePixelsThrowsCompositorNotReady() {
+        expectedEx.expect(IllegalStateException::class.java)
+        expectedEx.expectMessage("Compositor must be ready before pixels can be captured")
+        val session = sessionRule.createClosedSession()
+        val display = session.acquireDisplay()
+
+        sessionRule.waitForResult(display.capturePixels())
+        fail("IllegalStateException expected to be thrown")
+    }
+}
--- a/mobile/android/geckoview/src/androidTest/java/org/mozilla/geckoview/test/rule/GeckoSessionTestRule.java
+++ b/mobile/android/geckoview/src/androidTest/java/org/mozilla/geckoview/test/rule/GeckoSessionTestRule.java
@@ -31,16 +31,17 @@ import org.hamcrest.Matcher;
 import org.json.JSONObject;
 
 import org.junit.rules.ErrorCollector;
 import org.junit.rules.TestRule;
 import org.junit.runner.Description;
 import org.junit.runners.model.Statement;
 
 import android.app.Instrumentation;
+import android.graphics.Color;
 import android.graphics.Point;
 import android.graphics.SurfaceTexture;
 import android.net.LocalSocketAddress;
 import android.os.Looper;
 import android.os.SystemClock;
 import android.support.annotation.NonNull;
 import android.support.annotation.Nullable;
 import android.support.test.InstrumentationRegistry;
@@ -953,16 +954,20 @@ public class GeckoSessionTestRule implem
      * Get the runtime set up for the current test.
      *
      * @return GeckoRuntime object.
      */
     public @NonNull GeckoRuntime getRuntime() {
         return RuntimeCreator.getRuntime();
     }
 
+    public @Nullable GeckoDisplay getDisplay() {
+        return mDisplay;
+    }
+
     protected static Object setDelegate(final @NonNull Class<?> cls,
                                         final @NonNull GeckoSession session,
                                         final @Nullable Object delegate)
             throws NoSuchMethodException, IllegalAccessException, InvocationTargetException {
         if (cls == GeckoSession.TextInputDelegate.class) {
             return SessionTextInput.class.getMethod("setDelegate", cls)
                    .invoke(session.getTextInput(), delegate);
         }
@@ -1206,19 +1211,20 @@ public class GeckoSessionTestRule implem
         if (useDefaultSession && mReuseSession && sCachedSession != null) {
             mMainSession = sCachedSession;
         } else {
             mMainSession = new GeckoSession(settings);
         }
         prepareSession(mMainSession);
 
         if (mDisplaySize != null) {
+            mDisplay = mMainSession.acquireDisplay();
             mDisplayTexture = new SurfaceTexture(0);
+            mDisplayTexture.setDefaultBufferSize(mDisplaySize.x, mDisplaySize.y);
             mDisplaySurface = new Surface(mDisplayTexture);
-            mDisplay = mMainSession.acquireDisplay();
             mDisplay.surfaceChanged(mDisplaySurface, mDisplaySize.x, mDisplaySize.y);
         }
 
         if (useDefaultSession && mReuseSession) {
             if (sCachedSession == null) {
                 // We are creating a cached session.
                 final boolean withDevTools = mWithDevTools;
                 mWithDevTools = true; // Always get an RDP tab for cached session.
@@ -1283,17 +1289,16 @@ public class GeckoSessionTestRule implem
         // For the cached session, we may get multiple initial loads. We should specifically look
         // for an about:blank load, and wait until that has stopped.
         final boolean lookForAboutBlank = session.equals(sCachedSession);
 
         try {
             // We cannot detect initial page load without progress delegate.
             assertThat("ProgressDelegate cannot be null-delegate when opening session",
                        GeckoSession.ProgressDelegate.class, not(isIn(mNullDelegates)));
-
             mCallRecordHandler = new CallRecordHandler() {
                 private boolean mIsAboutBlank = !lookForAboutBlank;
 
                 @Override
                 public boolean handleCall(final Method method, final Object[] args) {
                     final boolean matching = DEFAULT_DELEGATES.contains(
                             method.getDeclaringClass()) && session.equals(args[0]);
                     if (matching && sOnPageStart.equals(method)) {
--- a/mobile/android/geckoview/src/main/java/org/mozilla/geckoview/CompositorController.java
+++ b/mobile/android/geckoview/src/main/java/org/mozilla/geckoview/CompositorController.java
@@ -3,36 +3,32 @@
  * 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/. */
 
 package org.mozilla.geckoview;
 
 import org.mozilla.gecko.annotation.RobocopTarget;
 import org.mozilla.gecko.util.ThreadUtils;
 
+import android.graphics.Bitmap;
 import android.graphics.Color;
 import android.support.annotation.NonNull;
 import android.support.annotation.Nullable;
 import android.support.annotation.UiThread;
 
+import java.nio.ByteBuffer;
 import java.nio.IntBuffer;
 import java.util.ArrayList;
 import java.util.List;
 
 @UiThread
 public final class CompositorController {
     private final GeckoSession.Compositor mCompositor;
 
-    public interface GetPixelsCallback {
-        @UiThread
-        void onPixelsResult(int width, int height, @Nullable IntBuffer pixels);
-    }
-
     private List<Runnable> mDrawCallbacks;
-    private GetPixelsCallback mGetPixelsCallback;
     private int mDefaultClearColor = Color.WHITE;
     private Runnable mFirstPaintCallback;
 
     /* package */ CompositorController(final GeckoSession session) {
         mCompositor = session.mCompositor;
     }
 
     /* package */ void onCompositorReady() {
@@ -88,42 +84,16 @@ public final class CompositorController 
         }
 
         if (mDrawCallbacks.remove(callback) && mDrawCallbacks.isEmpty() &&
                 mCompositor.isReady()) {
             mCompositor.enableLayerUpdateNotifications(false);
         }
     }
 
-    /* package */ void recvScreenPixels(final int width, final int height,
-                                        final int[] pixels) {
-        if (mGetPixelsCallback != null) {
-            mGetPixelsCallback.onPixelsResult(width, height, IntBuffer.wrap(pixels));
-            mGetPixelsCallback = null;
-        }
-    }
-
-    /**
-     * Request current pixel values from the compositor. May be called on any thread. Must
-     * not be called again until the callback is invoked.
-     *
-     * @param callback Callback for getting pixels.
-     */
-    @RobocopTarget
-    public void getPixels(final @NonNull GetPixelsCallback callback) {
-        ThreadUtils.assertOnUiThread();
-
-        if (mCompositor.isReady()) {
-            mGetPixelsCallback = callback;
-            mCompositor.requestScreenPixels();
-        } else {
-            callback.onPixelsResult(0, 0, null);
-        }
-    }
-
     /**
      * Get the current clear color when drawing.
      *
      * @return Curent clear color.
      */
     public int getClearColor() {
         ThreadUtils.assertOnUiThread();
         return mDefaultClearColor;
--- a/mobile/android/geckoview/src/main/java/org/mozilla/geckoview/GeckoDisplay.java
+++ b/mobile/android/geckoview/src/main/java/org/mozilla/geckoview/GeckoDisplay.java
@@ -1,16 +1,17 @@
 /* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
  * vim: ts=4 sw=4 expandtab:
  * 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/. */
 
 package org.mozilla.geckoview;
 
+import android.graphics.Bitmap;
 import android.support.annotation.NonNull;
 import android.support.annotation.UiThread;
 import android.view.Surface;
 
 import org.mozilla.gecko.util.ThreadUtils;
 
 /**
  * Applications use a GeckoDisplay instance to provide {@link GeckoSession} with a {@link Surface} for
@@ -113,9 +114,36 @@ public class GeckoDisplay {
      *
      * @return True if display should be pinned on the screen.
      */
     @UiThread
     public boolean shouldPinOnScreen() {
         ThreadUtils.assertOnUiThread();
         return session.getDisplay() == this && session.shouldPinOnScreen();
     }
+
+    /**
+     * Request a {@link Bitmap} of the visible portion of the web page currently being
+     * rendered.
+     *
+     * Returned {@link Bitmap} will have the same dimensions as the {@link Surface} the
+     * {@link GeckoDisplay} is currently using.
+     *
+     * If the {@link GeckoSession#isCompositorReady} is false the {@link GeckoResult} will complete
+     * with an {@link IllegalStateException}.
+     *
+     * This function must be called on the UI thread.
+     *
+     * @return A {@link GeckoResult} that completes with a {@link Bitmap} containing
+     * the pixels and size information of the currently visible rendered web page.
+     */
+    @UiThread
+    public @NonNull GeckoResult<Bitmap> capturePixels() {
+        ThreadUtils.assertOnUiThread();
+        if (!session.isCompositorReady()) {
+            return GeckoResult.fromException(
+                    new IllegalStateException("Compositor must be ready before pixels can be captured"));
+        }
+        GeckoResult<Bitmap> result = new GeckoResult<>();
+        session.mCompositor.requestScreenPixels(result);
+        return result;
+    }
 }
--- a/mobile/android/geckoview/src/main/java/org/mozilla/geckoview/GeckoResult.java
+++ b/mobile/android/geckoview/src/main/java/org/mozilla/geckoview/GeckoResult.java
@@ -1,10 +1,11 @@
 package org.mozilla.geckoview;
 
+import org.mozilla.gecko.annotation.RobocopTarget;
 import org.mozilla.gecko.annotation.WrapForJNI;
 import org.mozilla.gecko.util.ThreadUtils;
 
 import android.os.Handler;
 import android.os.Looper;
 import android.os.SystemClock;
 import android.support.annotation.AnyThread;
 import android.support.annotation.NonNull;
--- a/mobile/android/geckoview/src/main/java/org/mozilla/geckoview/GeckoSession.java
+++ b/mobile/android/geckoview/src/main/java/org/mozilla/geckoview/GeckoSession.java
@@ -4,19 +4,16 @@
  * 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/. */
 
 package org.mozilla.geckoview;
 
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
 import java.lang.ref.WeakReference;
-import java.net.URLConnection;
-import java.util.ArrayList;
-import java.util.Arrays;
 import java.util.UUID;
 
 import org.mozilla.gecko.annotation.WrapForJNI;
 import org.mozilla.gecko.EventDispatcher;
 import org.mozilla.gecko.GeckoAppShell;
 import org.mozilla.gecko.GeckoThread;
 import org.mozilla.gecko.IGeckoEditableParent;
 import org.mozilla.gecko.mozglue.JNIObject;
@@ -26,16 +23,17 @@ import org.mozilla.gecko.util.EventCallb
 import org.mozilla.gecko.util.GeckoBundle;
 import org.mozilla.gecko.util.IntentUtils;
 import org.mozilla.gecko.util.ThreadUtils;
 
 import android.content.ContentResolver;
 import android.content.Context;
 import android.content.res.Resources;
 import android.database.Cursor;
+import android.graphics.Bitmap;
 import android.graphics.Matrix;
 import android.graphics.Rect;
 import android.graphics.RectF;
 import android.net.Uri;
 import android.os.Binder;
 import android.os.IBinder;
 import android.os.IInterface;
 import android.os.Parcel;
@@ -211,22 +209,17 @@ public class GeckoSession implements Par
         private void recvToolbarAnimatorMessage(int message) {
             GeckoSession.this.handleCompositorMessage(message);
         }
 
         @WrapForJNI(calledFrom = "ui", dispatchTo = "current")
         public native void setDefaultClearColor(int color);
 
         @WrapForJNI(calledFrom = "ui", dispatchTo = "current")
-        public native void requestScreenPixels();
-
-        @WrapForJNI(calledFrom = "ui")
-        private void recvScreenPixels(int width, int height, int[] pixels) {
-            GeckoSession.this.recvScreenPixels(width, height, pixels);
-        }
+        /* package */ native void requestScreenPixels(final GeckoResult<Bitmap> result);
 
         @WrapForJNI(calledFrom = "ui", dispatchTo = "current")
         public native void enableLayerUpdateNotifications(boolean enable);
 
         @WrapForJNI(calledFrom = "ui", dispatchTo = "current")
         public native void sendToolbarPixelsToCompositor(final int width, final int height,
                                                          final int[] pixels);
 
@@ -4359,22 +4352,16 @@ public class GeckoSession implements Par
                 } else {
                     Log.w(LOGTAG, "Unexpected message: " + message);
                 }
                 break;
             }
         }
     }
 
-    /* package */ void recvScreenPixels(int width, int height, int[] pixels) {
-        if (mController != null) {
-            mController.recvScreenPixels(width, height, pixels);
-        }
-    }
-
     /* package */ boolean isCompositorReady() {
         return mCompositorReady;
     }
 
     /* package */ void onCompositorReady() {
         if (DEBUG) {
             ThreadUtils.assertOnUiThread();
         }
--- a/mobile/android/geckoview/src/main/java/org/mozilla/geckoview/GeckoView.java
+++ b/mobile/android/geckoview/src/main/java/org/mozilla/geckoview/GeckoView.java
@@ -4,24 +4,26 @@
  * 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/. */
 
 package org.mozilla.geckoview;
 
 import org.mozilla.gecko.AndroidGamepadManager;
 import org.mozilla.gecko.EventDispatcher;
 import org.mozilla.gecko.InputMethods;
+import org.mozilla.gecko.annotation.RobocopTarget;
 import org.mozilla.gecko.util.ActivityUtils;
 import org.mozilla.gecko.util.ThreadUtils;
 
 import android.annotation.SuppressLint;
 import android.annotation.TargetApi;
 import android.app.Activity;
 import android.content.Context;
 import android.content.res.Configuration;
+import android.graphics.Bitmap;
 import android.graphics.Canvas;
 import android.graphics.Color;
 import android.graphics.Rect;
 import android.graphics.Region;
 import android.os.Build;
 import android.os.Handler;
 import android.os.Parcel;
 import android.os.Parcelable;
@@ -161,16 +163,32 @@ public class GeckoView extends FrameLayo
                 GeckoView.this.mSurfaceView.getLocationOnScreen(mOrigin);
                 mDisplay.screenOriginChanged(mOrigin[0], mOrigin[1]);
             }
         }
 
         public boolean shouldPinOnScreen() {
             return mDisplay != null ? mDisplay.shouldPinOnScreen() : false;
         }
+
+        /**
+         * Request a {@link Bitmap} of the visible portion of the web page currently being
+         * rendered.
+         *
+         * @return A {@link GeckoResult} that completes with a {@link Bitmap} containing
+         * the pixels and size information of the currently visible rendered web page.
+         */
+        @UiThread
+        @NonNull GeckoResult<Bitmap> capturePixels() {
+            if (mDisplay == null) {
+                return GeckoResult.fromException(new IllegalStateException("Display must be created before pixels can be captured"));
+            }
+
+            return mDisplay.capturePixels();
+        }
     }
 
     public GeckoView(final Context context) {
         super(context);
         init();
     }
 
     public GeckoView(final Context context, final AttributeSet attrs) {
@@ -704,9 +722,23 @@ public class GeckoView extends FrameLayo
             final AutofillValue value = values.valueAt(i);
             if (value.isText()) {
                 // Only text is currently supported.
                 strValues.put(values.keyAt(i), value.getTextValue());
             }
         }
         mSession.getTextInput().autofill(strValues);
     }
+
+    /**
+     * Request a {@link Bitmap} of the visible portion of the web page currently being
+     * rendered.
+     *
+     * See {@link GeckoDisplay#capturePixels} for more details.
+     *
+     * @return A {@link GeckoResult} that completes with a {@link Bitmap} containing
+     * the pixels and size information of the currently visible rendered web page.
+     */
+    @UiThread
+    public @NonNull GeckoResult<Bitmap> capturePixels() {
+        return mDisplay.capturePixels();
+    }
 }
--- a/mobile/android/geckoview/src/main/java/org/mozilla/geckoview/doc-files/CHANGELOG.md
+++ b/mobile/android/geckoview/src/main/java/org/mozilla/geckoview/doc-files/CHANGELOG.md
@@ -68,16 +68,28 @@ exclude: true
 [67.1]: ../GeckoSession.html#getDefaultUserAgent--
 [67.2]: ../GeckoVRManager.html
 
 - Initial WebExtension support. [`GeckoRuntime#registerWebExtension`][67.15]
   allows embedders to register a local web extension.
 
 [67.15]: ../GeckoRuntime.html#registerWebExtension-org.mozilla.geckoview.WebExtension-
 
+- Added API to [`GeckoView`][65.5] to take screenshot of the visible page. Calling [`capturePixels`][67.16] returns a ['GeckoResult'][65.25] that completes to a [`Bitmap`][67.17] of the current [`Surface`][67.18] contents, or an [`IllegalStateException`][67.19] if the [`GeckoSession`][65.9] is not ready to render content.
+
+[67.16]: ../GeckoView.html#capturePixels
+[67.17]: https://developer.android.com/reference/android/graphics/Bitmap
+[67.18]: https://developer.android.com/reference/android/view/Surface
+[67.19]: https://developer.android.com/reference/java/lang/IllegalStateException
+
+- Added API to capture a screenshot to [`GeckoDisplay`][67.20]. [`capturePixels`][67.21] returns a ['GeckoResult'][65.25] that completes to a [`Bitmap`][67.16] of the current [`Surface`][67.17] contents, or an [`IllegalStateException`][67.18] if the [`GeckoSession`][65.9] is not ready to render content.
+
+[67.20]: ../GeckoDisplay.html
+[67.21]: ../GeckoDisplay.html#capturePixels
+
 ## v66
 - Removed redundant field `trackingMode` from [`SecurityInformation`][66.6].
   Use `TrackingProtectionDelegate.onTrackerBlocked` for notification of blocked
   elements during page load.
 
 [66.6]: ../GeckoSession.ProgressDelegate.SecurityInformation.html
 
 - Added [`@NonNull`][66.1] or [`@Nullable`][66.2] to all APIs.
@@ -187,9 +199,9 @@ exclude: true
 [65.23]: ../GeckoSession.FinderResult.html
 
 - Update [`CrashReporter#sendCrashReport`][65.24] to return the crash ID as a
   [`GeckoResult<String>`][65.25].
 
 [65.24]: ../CrashReporter.html#sendCrashReport-android.content.Context-android.os.Bundle-java.lang.String-
 [65.25]: ../GeckoResult.html
 
-[api-version]: df89fa914cfd095b37e696f3e767b55fc03a4835
+[api-version]: 0bcb9f0f763b746bb6f27f5d275c351818ab971b
--- a/mobile/android/geckoview_example/src/main/java/org/mozilla/geckoview_example/GeckoViewActivity.java
+++ b/mobile/android/geckoview_example/src/main/java/org/mozilla/geckoview_example/GeckoViewActivity.java
@@ -38,17 +38,16 @@ import android.view.MenuItem;
 import android.view.View;
 import android.view.WindowManager;
 import android.widget.ProgressBar;
 
 import java.io.BufferedReader;
 import java.io.IOException;
 import java.io.InputStream;
 import java.io.InputStreamReader;
-import java.util.Arrays;
 import java.util.HashSet;
 import java.util.LinkedList;
 import java.util.Locale;
 
 public class GeckoViewActivity extends AppCompatActivity {
     private static final String LOGTAG = "GeckoViewActivity";
     private static final String DEFAULT_URL = "about:blank";
     private static final String USE_MULTIPROCESS_EXTRA = "use_multiprocess";
--- a/mobile/android/tests/browser/robocop/src/org/mozilla/gecko/FennecNativeDriver.java
+++ b/mobile/android/tests/browser/robocop/src/org/mozilla/gecko/FennecNativeDriver.java
@@ -8,38 +8,39 @@ import java.io.BufferedOutputStream;
 import java.io.BufferedReader;
 import java.io.DataOutputStream;
 import java.io.File;
 import java.io.FileOutputStream;
 import java.io.FileReader;
 import java.io.FileWriter;
 import java.io.IOException;
 import java.io.PrintWriter;
+import java.nio.ByteBuffer;
 import java.nio.IntBuffer;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
 import java.lang.StringBuffer;
 import java.lang.Math;
 
-import org.mozilla.geckoview.CompositorController;
 import org.mozilla.gecko.gfx.PanningPerfAPI;
 import org.mozilla.gecko.util.BundleEventListener;
 import org.mozilla.gecko.util.EventCallback;
 import org.mozilla.gecko.util.GeckoBundle;
 import org.mozilla.gecko.util.StrictModeContext;
 import org.mozilla.geckoview.GeckoView;
 
 import android.app.Activity;
+import android.graphics.Bitmap;
 import android.util.Log;
 import android.view.View;
 
 import com.robotium.solo.Solo;
 
-public class FennecNativeDriver implements Driver, CompositorController.GetPixelsCallback {
+public class FennecNativeDriver implements Driver {
     private static final int FRAME_TIME_THRESHOLD = 25;     // allow 25ms per frame (40fps)
 
     private final Activity mActivity;
     private final Solo mSolo;
     private final String mRootPath;
 
     private static String mLogFile;
     private static LogLevel mLogLevel = LogLevel.INFO;
@@ -186,25 +187,20 @@ public class FennecNativeDriver implemen
             for (final View v : mSolo.getViews()) {
                 log(LogLevel.WARN, "  View: " + v);
             }
         }
         return geckoView;
     }
 
     private volatile boolean mGotPixelsResult;
-    private int mPixelsWidth;
-    private int mPixelsHeight;
-    private IntBuffer mPixelsResult;
+    private Bitmap mPixelsResult;
 
-    @Override
-    public synchronized void onPixelsResult(int aWidth, int aHeight, IntBuffer aPixels) {
-        mPixelsWidth = aWidth;
-        mPixelsHeight = aHeight;
-        mPixelsResult = aPixels;
+    public synchronized void onPixelsResult(final Bitmap aResult) {
+        mPixelsResult = aResult;
         mGotPixelsResult = true;
         notifyAll();
     }
 
     private static final int COLOR_DEVIATION = 3;
 
     // Due to anti-aliasing, border pixels can be blended. This should filter them out.
     private static boolean differentColor(final int c1, final int c2) {
@@ -254,68 +250,62 @@ public class FennecNativeDriver implemen
         final GeckoView view = getSurfaceView();
         if (view == null) {
             return null;
         }
 
         view.post(new Runnable() {
             @Override
             public void run() {
-                view.getSession().getCompositorController().getPixels(FennecNativeDriver.this);
+                view.capturePixels().then( value -> {
+                    FennecNativeDriver.this.onPixelsResult(value);
+                    return null;
+                });
             }
         });
 
         synchronized (this) {
             while (!mGotPixelsResult) {
                 try {
                     wait();
                 } catch (InterruptedException ie) {
                 }
             }
         }
 
-        final IntBuffer pixelBuffer = mPixelsResult;
-        int w = mPixelsWidth;
-        int h = mPixelsHeight;
+        final ByteBuffer pixelBuffer = ByteBuffer.allocate(mPixelsResult.getByteCount());
+        mPixelsResult.copyPixelsToBuffer(pixelBuffer);
+        int w = mPixelsResult.getWidth();
+        int h = mPixelsResult.getHeight();
 
         mGotPixelsResult = false;
-        mPixelsWidth = 0;
-        mPixelsHeight = 0;
         mPixelsResult = null;
 
 
         if ((pixelBuffer == null) || (w == 0) || (h == 0)) {
             return null;
         }
 
         // The page used in robocop tests is a grid of different colored squares.
         // The function will log the color of each square found in the screen capture.
         // This allows the screen capture to be examined in the log output in a human
         // readable format.
         // logPixels(pixelBuffer, w, h);
-
-        // now we need to (1) flip the image, because GL likes to do things up-side-down,
-        // and (2) rearrange the bits from AGBR-8888 to ARGB-8888.
+        
         pixelBuffer.position(0);
         String mapFile = mRootPath + "/pixels.map";
 
         FileOutputStream fos = null;
         BufferedOutputStream bos = null;
         DataOutputStream dos = null;
         try {
             fos = new FileOutputStream(mapFile);
             bos = new BufferedOutputStream(fos);
             dos = new DataOutputStream(bos);
-
-            for (int y = h - 1; y >= 0; y--) {
-                for (int x = 0; x < w; x++) {
-                    int agbr = pixelBuffer.get();
-                    dos.writeInt((agbr & 0xFF00FF00) | ((agbr >> 16) & 0x000000FF) | ((agbr << 16) & 0x00FF0000));
-                }
-            }
+            dos.write(pixelBuffer.array());
         } catch (IOException e) {
             throw new RoboCopException("exception with pixel writer on file: " + mapFile);
         } finally {
             try {
                 if (dos != null) {
                     dos.flush();
                     dos.close();
                 }
--- a/security/manager/ssl/nsCryptoHash.cpp
+++ b/security/manager/ssl/nsCryptoHash.cpp
@@ -33,19 +33,16 @@ static const uint64_t STREAM_BUFFER_SIZE
 nsCryptoHash::nsCryptoHash() : mHashContext(nullptr), mInitialized(false) {}
 
 NS_IMPL_ISUPPORTS(nsCryptoHash, nsICryptoHash)
 
 NS_IMETHODIMP
 nsCryptoHash::Init(uint32_t algorithm) {
   HASH_HashType hashType;
   switch (algorithm) {
-    case nsICryptoHash::MD2:
-      hashType = HASH_AlgMD2;
-      break;
     case nsICryptoHash::MD5:
       hashType = HASH_AlgMD5;
       break;
     case nsICryptoHash::SHA1:
       hashType = HASH_AlgSHA1;
       break;
     case nsICryptoHash::SHA256:
       hashType = HASH_AlgSHA256;
@@ -80,18 +77,16 @@ nsCryptoHash::Init(uint32_t algorithm) {
 
   HASH_Begin(mHashContext.get());
   mInitialized = true;
   return NS_OK;
 }
 
 NS_IMETHODIMP
 nsCryptoHash::InitWithString(const nsACString &aAlgorithm) {
-  if (aAlgorithm.LowerCaseEqualsLiteral("md2")) return Init(nsICryptoHash::MD2);
-
   if (aAlgorithm.LowerCaseEqualsLiteral("md5")) return Init(nsICryptoHash::MD5);
 
   if (aAlgorithm.LowerCaseEqualsLiteral("sha1"))
     return Init(nsICryptoHash::SHA1);
 
   if (aAlgorithm.LowerCaseEqualsLiteral("sha256"))
     return Init(nsICryptoHash::SHA256);
 
--- a/security/manager/ssl/nsICryptoHash.idl
+++ b/security/manager/ssl/nsICryptoHash.idl
@@ -11,20 +11,21 @@ interface nsIInputStream;
  */
 
 [builtinclass, scriptable, uuid(1e5b7c43-4688-45ce-92e1-77ed931e3bbe)]
 interface nsICryptoHash : nsISupports
 {
     /**
      * Hashing Algorithms.  These values are to be used by the
      * |init| method to indicate which hashing function to
-     * use.  These values map directly onto the values defined
-     * in mozilla/security/nss/lib/cryptohi/hasht.h.
+     * use.  These values must be identical to the values defined
+     * in security/nss/lib/util/hasht.h in type HASH_HashType.
+     * This allows us to use NSS mapping functions like
+     * HASH_GetHashOidTagByHashType with these values.
      */
-    const short MD2    = 1;  /* String value: "md2"    */
     const short MD5    = 2;  /* String value: "md5"    */
     const short SHA1   = 3;  /* String value: "sha1"   */
     const short SHA256 = 4;  /* String value: "sha256" */
     const short SHA384 = 5;  /* String value: "sha384" */
     const short SHA512 = 6;  /* String value: "sha512" */
 
     /**
      * Initialize the hashing object. This method may be
--- a/security/manager/ssl/tests/unit/test_hash_algorithms.js
+++ b/security/manager/ssl/tests/unit/test_hash_algorithms.js
@@ -4,28 +4,16 @@
 // of the supported algorithms.
 
 const messages = [
   "The quick brown fox jumps over the lazy dog",
   "",
 ];
 const ALGORITHMS = [
   {
-    initString: "md2",
-    initConstant: Ci.nsICryptoHash.MD2,
-    hexHashes: [
-      "03d85a0d629d2c442e987525319fc471",
-      "8350e5a3e24c153df2275c9f80692773",
-    ],
-    b64Hashes: [
-      "A9haDWKdLEQumHUlMZ/EcQ==",
-      "g1Dlo+JMFT3yJ1yfgGkncw==",
-    ],
-  },
-  {
     initString: "md5",
     initConstant: Ci.nsICryptoHash.MD5,
     hexHashes: [
       "9e107d9d372bb6826bd81d3542a419d6",
       "d41d8cd98f00b204e9800998ecf8427e",
     ],
     b64Hashes: [
       "nhB9nTcrtoJr2B01QqQZ1g==",
--- a/testing/geckodriver/CHANGES.md
+++ b/testing/geckodriver/CHANGES.md
@@ -4,16 +4,25 @@ Change log
 All notable changes to this program is documented in this file.
 
 
 Unreleased
 ----------
 
 ### Removed
 
+- Dropped support for legacy Selenium web element references
+
+  The legacy way of serialising web elements, using `{"ELEMENT": <UUID>}`,
+  has been removed in this release.  This may break older Selenium
+  clients and clients which are otherwise not compatible with the
+  WebDriver standard.
+
+  Thanks to Shivam Singhal for this patch.
+
 - Removed `--webdriver-port` command-line option
 
   `--webdriver-port <PORT>` was an undocumented alias for `--port`,
   initially used for backwards compatibility with clients
   prior to Selenium 3.0.0.
 
 ### Changed
 
--- a/testing/geckodriver/src/command.rs
+++ b/testing/geckodriver/src/command.rs
@@ -9,17 +9,16 @@ use std::fs::File;
 use std::io::prelude::*;
 use uuid::Uuid;
 use webdriver::command::{WebDriverCommand, WebDriverExtensionCommand};
 use webdriver::common::WebElement;
 use webdriver::error::{ErrorStatus, WebDriverError, WebDriverResult};
 use webdriver::httpapi::WebDriverExtensionRoute;
 
 pub const CHROME_ELEMENT_KEY: &'static str = "chromeelement-9fc5-4b51-a3c8-01716eedeb04";
-pub const LEGACY_ELEMENT_KEY: &'static str = "ELEMENT";
 
 pub fn extension_routes() -> Vec<(Method, &'static str, GeckoExtensionRoute)> {
     return vec![
         (
             Method::GET,
             "/session/{sessionId}/moz/context",
             GeckoExtensionRoute::GetContext,
         ),
--- a/testing/geckodriver/src/marionette.rs
+++ b/testing/geckodriver/src/marionette.rs
@@ -1,12 +1,11 @@
 use crate::command::{
     AddonInstallParameters, AddonUninstallParameters, GeckoContextParameters,
     GeckoExtensionCommand, GeckoExtensionRoute, XblLocatorParameters, CHROME_ELEMENT_KEY,
-    LEGACY_ELEMENT_KEY,
 };
 use mozprofile::preferences::Pref;
 use mozprofile::profile::Profile;
 use mozrunner::runner::{FirefoxProcess, FirefoxRunner, Runner, RunnerProcess};
 use serde::de::{self, Deserialize, Deserializer};
 use serde::ser::{Serialize, Serializer};
 use serde_json::{self, Map, Value};
 use std::error::Error;
@@ -388,22 +387,20 @@ impl MarionetteSession {
             json_data.as_object(),
             ErrorStatus::UnknownError,
             "Failed to convert data to an object"
         );
 
         let chrome_element = data.get(CHROME_ELEMENT_KEY);
         let element = data.get(ELEMENT_KEY);
         let frame = data.get(FRAME_KEY);
-        let legacy_element = data.get(LEGACY_ELEMENT_KEY);
         let window = data.get(WINDOW_KEY);
 
         let value = try_opt!(
             element
-                .or(legacy_element)
                 .or(chrome_element)
                 .or(frame)
                 .or(window),
             ErrorStatus::UnknownError,
             "Failed to extract web element from Marionette response"
         );
         let id = try_opt!(
             value.as_str(),
--- a/testing/webdriver/src/actions.rs
+++ b/testing/webdriver/src/actions.rs
@@ -924,17 +924,16 @@ mod test {
     }
 
     #[test]
     fn test_json_pointer_action_move_with_origin_webelement_and_legacy_element() {
         let json = r#"{
             "type":"pointerMove",
             "duration":100,
             "origin":{
-                "ELEMENT":"elem",
                 "element-6066-11e4-a52e-4f735466cecf":"elem"
             },
             "x":5,
             "y":10
         }"#;
         let data = PointerAction::Move(PointerMoveAction {
             duration: Some(100),
             origin: PointerOrigin::Element(WebElement { id: "elem".into() }),
@@ -946,17 +945,17 @@ mod test {
     }
 
     #[test]
     fn test_json_pointer_action_move_with_origin_only_legacy_element() {
         let json = r#"{
             "type":"pointerMove",
             "duration":100,
             "origin":{
-                "ELEMENT":"elem"
+                "element-6066-11e4-a52e-4f735466cecf":"elem"
             },
             "x":5,
             "y":10
         }"#;
 
         assert!(serde_json::from_str::<PointerOrigin>(&json).is_err());
     }
 
--- a/toolkit/themes/shared/extensions/extensions.inc.css
+++ b/toolkit/themes/shared/extensions/extensions.inc.css
@@ -469,16 +469,20 @@ button.warning {
 .warning,
 .pending,
 .error {
   margin-inline-start: 48px;
   font-weight: bold;
   -moz-box-align: center;
 }
 
+.addon-view[type="theme"] .icon:-moz-locale-dir(rtl) {
+  transform: scaleX(-1);
+}
+
 .content-container {
   -moz-box-align: center;
 }
 
 #updates-list > .addon > .content-container,
 .basicinfo-container {
   -moz-box-align: start;
 }
new file mode 100644
--- /dev/null
+++ b/widget/android/bindings/AndroidGraphics-classes.txt
@@ -0,0 +1,10 @@
+[android.graphics.Bitmap = skip:true]
+copyPixelsFromBuffer(Ljava/nio/Buffer;)V =
+createBitmap(IILandroid/graphics/Bitmap$Config;)Landroid/graphics/Bitmap; =
+
+[android.graphics.Bitmap$Config = skip:true]
+valueOf(Ljava/lang/String;)Landroid/graphics/Bitmap$Config; =
+ALPHA_8 =
+ARGB_8888 =
+RGBA_F16 =
+RGB_565 =
\ No newline at end of file
new file mode 100644
--- /dev/null
+++ b/widget/android/bindings/JavaExceptions-classes.txt
@@ -0,0 +1,2 @@
+[java.lang.IllegalStateException = skip:true]
+<init>(Ljava/lang/String;)V =
\ No newline at end of file
--- a/widget/android/bindings/moz.build
+++ b/widget/android/bindings/moz.build
@@ -7,20 +7,22 @@
 with Files("**"):
     BUG_COMPONENT = ("Firefox for Android", "Graphics, Panning and Zooming")
 
 # List of stems to generate .cpp and .h files for.  To add a stem, add it to
 # this list and ensure that $(stem)-classes.txt exists in this directory.
 generated = [
     'AccessibilityEvent',
     'AndroidBuild',
+    'AndroidGraphics',
     'AndroidInputType',
     'AndroidRect',
     'InetAddress',
     'JavaBuiltins',
+    'JavaExceptions',
     'KeyEvent',
     'MediaCodec',
     'MotionEvent',
     'SurfaceTexture',
     'ViewConfiguration'
 ]
 
 SOURCES += ['!%s.cpp' % stem for stem in generated]
--- a/widget/android/nsWindow.cpp
+++ b/widget/android/nsWindow.cpp
@@ -3,40 +3,46 @@
  * 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 <android/log.h>
 #include <android/native_window.h>
 #include <android/native_window_jni.h>
 #include <math.h>
+#include <queue>
 #include <unistd.h>
 
 #include "mozilla/MiscEvents.h"
 #include "mozilla/MouseEvents.h"
 #include "mozilla/TouchEvents.h"
 #include "mozilla/TypeTraits.h"
 #include "mozilla/WeakPtr.h"
 #include "mozilla/WheelHandlingHelper.h"  // for WheelDeltaAdjustmentStrategy
 
 #include "mozilla/a11y/SessionAccessibility.h"
 #include "mozilla/dom/ContentParent.h"
 #include "mozilla/dom/ContentChild.h"
 #include "mozilla/dom/MouseEventBinding.h"
 #include "mozilla/Unused.h"
 #include "mozilla/Preferences.h"
 #include "mozilla/layers/RenderTrace.h"
+#include "mozilla/gfx/DataSurfaceHelpers.h"
+#include "mozilla/gfx/2D.h"
 #include <algorithm>
 
 using mozilla::Unused;
 using mozilla::dom::ContentChild;
 using mozilla::dom::ContentParent;
 
 #include "nsWindow.h"
 
+#include "AndroidGraphics.h"
+#include "JavaExceptions.h"
+
 #include "nsIBaseWindow.h"
 #include "nsIBrowserDOMWindow.h"
 #include "nsIDOMChromeWindow.h"
 #include "nsIObserverService.h"
 #include "nsISupportsPrimitives.h"
 #include "nsIWidgetListener.h"
 #include "nsIWindowWatcher.h"
 #include "nsIXULWindow.h"
@@ -792,16 +798,17 @@ nsresult nsWindow::AndroidView::GetInitD
 class nsWindow::LayerViewSupport final
     : public GeckoSession::Compositor::Natives<LayerViewSupport> {
   using LockedWindowPtr = WindowPtr<LayerViewSupport>::Locked;
 
   WindowPtr<LayerViewSupport> mWindow;
   GeckoSession::Compositor::WeakRef mCompositor;
   Atomic<bool, ReleaseAcquire> mCompositorPaused;
   jni::Object::GlobalRef mSurface;
+  std::queue<java::GeckoResult::GlobalRef> mCapturePixelsResults;
 
   // In order to use Event::HasSameTypeAs in PostTo(), we cannot make
   // LayerViewEvent a template because each template instantiation is
   // a different type. So implement LayerViewEvent as a ProxyEvent.
   class LayerViewEvent final : public nsAppShell::ProxyEvent {
     using Event = nsAppShell::Event;
 
    public:
@@ -852,17 +859,21 @@ class nsWindow::LayerViewSupport final
     if (RefPtr<nsThread> uiThread = GetAndroidUiThread()) {
       GeckoSession::Compositor::GlobalRef compositor(mCompositor);
       if (!compositor) {
         return;
       }
 
       uiThread->Dispatch(NS_NewRunnableFunction(
           "LayerViewSupport::OnDetach",
-          [compositor, disposer = RefPtr<Runnable>(aDisposer)] {
+          [compositor, disposer = RefPtr<Runnable>(aDisposer), result = &mCapturePixelsResults] {
+            while (!result->empty()) {
+              result->front()->CompleteExceptionally(java::sdk::IllegalStateException::New("The compositor has detached from the session").Cast<jni::Throwable>());
+              result->pop();
+            }
             compositor->OnCompositorDetached();
             disposer->Run();
           }));
     }
   }
 
   const GeckoSession::Compositor::Ref& GetJavaCompositor() const {
     return mCompositor;
@@ -877,16 +888,37 @@ class nsWindow::LayerViewSupport final
   GetUiCompositorControllerChild() {
     RefPtr<UiCompositorControllerChild> child;
     if (LockedWindowPtr window{mWindow}) {
       child = window->GetUiCompositorControllerChild();
     }
     return child.forget();
   }
 
+  int8_t* FlipScreenPixels(Shmem& aMem, const ScreenIntSize& aSize) {
+    const IntSize size(aSize.width, aSize.height);
+    RefPtr<DataSourceSurface> image = gfx::CreateDataSourceSurfaceFromData(
+      size, 
+      SurfaceFormat::B8G8R8A8, 
+      aMem.get<uint8_t>(), 
+      StrideForFormatAndWidth(SurfaceFormat::B8G8R8A8, aSize.width));
+    RefPtr<DrawTarget> drawTarget = gfxPlatform::GetPlatform()->CreateOffscreenContentDrawTarget(
+      size, SurfaceFormat::B8G8R8A8);
+    drawTarget->SetTransform(Matrix::Scaling(1.0, -1.0) * Matrix::Translation(0, aSize.height));
+      
+    gfx::Rect drawRect(0, 0, aSize.width, aSize.height);
+    drawTarget->DrawSurface(image, drawRect, drawRect);
+
+    RefPtr<SourceSurface> snapshot = drawTarget->Snapshot();
+    RefPtr<DataSourceSurface> data = snapshot->GetDataSurface();
+    DataSourceSurface::ScopedMap* smap =
+      new DataSourceSurface::ScopedMap(data, DataSourceSurface::READ);
+    return reinterpret_cast<int8_t*>(smap->GetData());
+  }
+
   /**
    * Compositor methods
    */
  public:
   void AttachNPZC(jni::Object::Param aNPZC) {
     MOZ_ASSERT(NS_IsMainThread());
     if (!mWindow) {
       return;  // Already shut down.
@@ -1073,38 +1105,47 @@ class nsWindow::LayerViewSupport final
   void SetDefaultClearColor(int32_t aColor) {
     MOZ_ASSERT(AndroidBridge::IsJavaUiThread());
     if (RefPtr<UiCompositorControllerChild> child =
             GetUiCompositorControllerChild()) {
       child->SetDefaultClearColor((uint32_t)aColor);
     }
   }
 
-  void RequestScreenPixels() {
+  void RequestScreenPixels(jni::Object::Param aResult) {
     MOZ_ASSERT(AndroidBridge::IsJavaUiThread());
-    if (RefPtr<UiCompositorControllerChild> child =
-            GetUiCompositorControllerChild()) {
-      child->RequestScreenPixels();
+    mCapturePixelsResults.push(java::GeckoResult::GlobalRef(java::GeckoResult::LocalRef(aResult)));
+    if (mCapturePixelsResults.size() == 1) {
+      if (RefPtr<UiCompositorControllerChild> child =
+              GetUiCompositorControllerChild()) {
+        child->RequestScreenPixels();
+      }
     }
-  }
+  } 
 
   void RecvScreenPixels(Shmem&& aMem, const ScreenIntSize& aSize) {
     MOZ_ASSERT(AndroidBridge::IsJavaUiThread());
-
-    auto pixels =
-        mozilla::jni::IntArray::New(aMem.get<int>(), aMem.Size<int>());
-    auto compositor = GeckoSession::Compositor::LocalRef(mCompositor);
-    if (compositor) {
-      compositor->RecvScreenPixels(aSize.width, aSize.height, pixels);
+    auto aResult = java::GeckoResult::LocalRef(mCapturePixelsResults.front());
+    if (aResult) {
+      auto pixels =
+          mozilla::jni::ByteBuffer::New(FlipScreenPixels(aMem, aSize), aMem.Size<int8_t>());
+      auto bitmap = java::sdk::Bitmap::CreateBitmap(aSize.width, aSize.height, java::sdk::Config::ARGB_8888());
+      bitmap->CopyPixelsFromBuffer(pixels);
+      aResult->Complete(bitmap);
+      mCapturePixelsResults.pop();
     }
 
     // Pixels have been copied, so Dealloc Shmem
     if (RefPtr<UiCompositorControllerChild> child =
             GetUiCompositorControllerChild()) {
       child->DeallocPixelBuffer(aMem);
+
+      if (!mCapturePixelsResults.empty()) {
+        child->RequestScreenPixels();
+      }
     }
   }
 
   void EnableLayerUpdateNotifications(bool aEnable) {
     MOZ_ASSERT(AndroidBridge::IsJavaUiThread());
     if (RefPtr<UiCompositorControllerChild> child =
             GetUiCompositorControllerChild()) {
       child->EnableLayerUpdateNotifications(aEnable);