Merge mozilla-inbound to mozilla-central a=merge
authorWes Kocher <wkocher@mozilla.com>
Thu, 08 Jan 2015 17:08:12 -0800
changeset 222841 b3f84cf78dc20519db0797f9bd613331647d61e6
parent 222840 703f75b9a9c0045258d1e0bd8e96b5d2d41066db (current diff)
parent 222719 86f3f3bee063e719039107fb7a04c0b98b384c92 (diff)
child 222842 bd1bfdfd959e133ca304f2c6cfc73a510039d4c2
child 222922 742cb34c9ba818c7808a4ea603a79c408c66e244
child 222948 0f98d51a4a49d8e903922797df8478afca0b222c
push id53725
push userkwierso@gmail.com
push dateFri, 09 Jan 2015 01:15:04 +0000
treeherdermozilla-inbound@bd1bfdfd959e [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmerge
milestone37.0a1
first release with
nightly linux32
b3f84cf78dc2 / 37.0a1 / 20150109030224 / files
nightly linux64
b3f84cf78dc2 / 37.0a1 / 20150109030224 / files
nightly mac
b3f84cf78dc2 / 37.0a1 / 20150109030224 / files
nightly win32
b3f84cf78dc2 / 37.0a1 / 20150109030224 / files
nightly win64
b3f84cf78dc2 / 37.0a1 / 20150109030224 / files
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
releases
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Merge mozilla-inbound to mozilla-central a=merge
configure.in
layout/generic/RubyReflowState.cpp
layout/generic/RubyReflowState.h
security/manager/ssl/tests/unit/test_cert_eku.js
security/manager/ssl/tests/unit/test_cert_eku/ee-EKU-CA-int-EKU-CA_EP_NS_OS_SA.der
security/manager/ssl/tests/unit/test_cert_eku/ee-EKU-CA_EP-int-EKU-CA_EP_NS_OS_SA.der
security/manager/ssl/tests/unit/test_cert_eku/ee-EKU-CA_EP_NS_OS_SA-int-EKU-CA.der
security/manager/ssl/tests/unit/test_cert_eku/ee-EKU-CA_EP_NS_OS_SA-int-EKU-CA_EP.der
security/manager/ssl/tests/unit/test_cert_eku/ee-EKU-CA_EP_NS_OS_SA-int-EKU-CA_EP_NS_OS_SA.der
security/manager/ssl/tests/unit/test_cert_eku/ee-EKU-CA_EP_NS_OS_SA-int-EKU-CA_NS.der
security/manager/ssl/tests/unit/test_cert_eku/ee-EKU-CA_EP_NS_OS_SA-int-EKU-CA_OS.der
security/manager/ssl/tests/unit/test_cert_eku/ee-EKU-CA_EP_NS_OS_SA-int-EKU-CA_SA.der
security/manager/ssl/tests/unit/test_cert_eku/ee-EKU-CA_EP_NS_OS_SA-int-EKU-EP.der
security/manager/ssl/tests/unit/test_cert_eku/ee-EKU-CA_EP_NS_OS_SA-int-EKU-EP_NS.der
security/manager/ssl/tests/unit/test_cert_eku/ee-EKU-CA_EP_NS_OS_SA-int-EKU-EP_OS.der
security/manager/ssl/tests/unit/test_cert_eku/ee-EKU-CA_EP_NS_OS_SA-int-EKU-EP_SA.der
security/manager/ssl/tests/unit/test_cert_eku/ee-EKU-CA_EP_NS_OS_SA-int-EKU-NONE.der
security/manager/ssl/tests/unit/test_cert_eku/ee-EKU-CA_EP_NS_OS_SA-int-EKU-NS.der
security/manager/ssl/tests/unit/test_cert_eku/ee-EKU-CA_EP_NS_OS_SA-int-EKU-NS_OS.der
security/manager/ssl/tests/unit/test_cert_eku/ee-EKU-CA_EP_NS_OS_SA-int-EKU-NS_SA.der
security/manager/ssl/tests/unit/test_cert_eku/ee-EKU-CA_EP_NS_OS_SA-int-EKU-OS.der
security/manager/ssl/tests/unit/test_cert_eku/ee-EKU-CA_EP_NS_OS_SA-int-EKU-OS_SA.der
security/manager/ssl/tests/unit/test_cert_eku/ee-EKU-CA_EP_NS_OS_SA-int-EKU-SA.der
security/manager/ssl/tests/unit/test_cert_eku/ee-EKU-CA_NS-int-EKU-CA_EP_NS_OS_SA.der
security/manager/ssl/tests/unit/test_cert_eku/ee-EKU-CA_OS-int-EKU-CA_EP_NS_OS_SA.der
security/manager/ssl/tests/unit/test_cert_eku/ee-EKU-CA_SA-int-EKU-CA_EP_NS_OS_SA.der
security/manager/ssl/tests/unit/test_cert_eku/ee-EKU-EP-int-EKU-CA_EP_NS_OS_SA.der
security/manager/ssl/tests/unit/test_cert_eku/ee-EKU-EP_NS-int-EKU-CA_EP_NS_OS_SA.der
security/manager/ssl/tests/unit/test_cert_eku/ee-EKU-EP_OS-int-EKU-CA_EP_NS_OS_SA.der
security/manager/ssl/tests/unit/test_cert_eku/ee-EKU-EP_SA-int-EKU-CA_EP_NS_OS_SA.der
security/manager/ssl/tests/unit/test_cert_eku/ee-EKU-NONE-int-EKU-CA_EP_NS_OS_SA.der
security/manager/ssl/tests/unit/test_cert_eku/ee-EKU-NS-int-EKU-CA_EP_NS_OS_SA.der
security/manager/ssl/tests/unit/test_cert_eku/ee-EKU-NS_OS-int-EKU-CA_EP_NS_OS_SA.der
security/manager/ssl/tests/unit/test_cert_eku/ee-EKU-NS_SA-int-EKU-CA_EP_NS_OS_SA.der
security/manager/ssl/tests/unit/test_cert_eku/ee-EKU-OS-int-EKU-CA_EP_NS_OS_SA.der
security/manager/ssl/tests/unit/test_cert_eku/ee-EKU-OS_SA-int-EKU-CA_EP_NS_OS_SA.der
security/manager/ssl/tests/unit/test_cert_eku/ee-EKU-SA-int-EKU-CA_EP_NS_OS_SA.der
security/manager/ssl/tests/unit/test_cert_eku/int-EKU-CA_EP_NS_OS_SA.der
testing/web-platform/meta/2dcontext/drawing-images-to-the-canvas/2d.drawImage.broken.html.ini
testing/web-platform/meta/dom/errors/exceptions.html.ini
testing/web-platform/meta/progress-events/tests/submissions/Samsung/security-consideration.sub.html.ini
testing/web-platform/tests/DOMEvents/tests/approved/Event.bubbles.false.html
testing/web-platform/tests/DOMEvents/tests/submissions/Microsoft/converted/Event.bubbles.false.html
testing/web-platform/tests/dom/errors/DOMException-constants.html
testing/web-platform/tests/dom/errors/exceptions.html
toolkit/components/telemetry/Histograms.json
tools/trace-malloc/Makefile.in
tools/trace-malloc/README
tools/trace-malloc/TraceMalloc.pm
tools/trace-malloc/adreader.cpp
tools/trace-malloc/adreader.h
tools/trace-malloc/allocation-stacks.c
tools/trace-malloc/blame.css
tools/trace-malloc/blame.pl
tools/trace-malloc/bloatblame.cpp
tools/trace-malloc/diffbloatdump.pl
tools/trace-malloc/formdata.c
tools/trace-malloc/formdata.h
tools/trace-malloc/getopt.c
tools/trace-malloc/histogram-diff.sh
tools/trace-malloc/histogram-pretty.sh
tools/trace-malloc/histogram.pl
tools/trace-malloc/leak-soup.pl
tools/trace-malloc/leaksoup.cpp
tools/trace-malloc/leakstats.c
tools/trace-malloc/lib/moz.build
tools/trace-malloc/lib/nsDebugHelpWin32.cpp
tools/trace-malloc/lib/nsDebugHelpWin32.h
tools/trace-malloc/lib/nsTraceMalloc.c
tools/trace-malloc/lib/nsTraceMalloc.h
tools/trace-malloc/lib/nsTraceMallocCallbacks.h
tools/trace-malloc/lib/nsTypeInfo.cpp
tools/trace-malloc/lib/nsTypeInfo.h
tools/trace-malloc/lib/nsWinTraceMalloc.cpp
tools/trace-malloc/lib/tm.def
tools/trace-malloc/live-bloat.html
tools/trace-malloc/merge.pl
tools/trace-malloc/moz.build
tools/trace-malloc/rules.txt
tools/trace-malloc/spacecategory.c
tools/trace-malloc/spacetrace.c
tools/trace-malloc/spacetrace.css
tools/trace-malloc/spacetrace.h
tools/trace-malloc/stoptions.h
tools/trace-malloc/tmfrags.c
tools/trace-malloc/tmreader.c
tools/trace-malloc/tmreader.h
tools/trace-malloc/tmstats.c
tools/trace-malloc/types.dat
tools/trace-malloc/uncategorized.pl
--- a/addon-sdk/source/python-lib/cuddlefish/runner.py
+++ b/addon-sdk/source/python-lib/cuddlefish/runner.py
@@ -487,17 +487,16 @@ def run_app(harness_root_dir, manifest_r
     maybe_remove_logfile()
 
     env = {}
     env.update(os.environ)
     if no_connections:
       env['MOZ_DISABLE_NONLOCAL_CONNECTIONS'] = '1'
     env['MOZ_NO_REMOTE'] = '1'
     env['XPCOM_DEBUG_BREAK'] = 'stack'
-    env['NS_TRACE_MALLOC_DISABLE_STACKS'] = '1'
     env.update(extra_environment)
     if norun:
         cmdargs.append("-no-remote")
 
     # Create the addon XPI so mozrunner will copy it to the profile it creates.
     # We delete it below after getting mozrunner to create the profile.
     from cuddlefish.xpi import build_xpi
     xpi_path = tempfile.mktemp(suffix='cfx-tmp.xpi')
--- a/build/autoconf/toolchain.m4
+++ b/build/autoconf/toolchain.m4
@@ -10,17 +10,17 @@ GNU_CC=
 GNU_CXX=
 CC_VERSION='N/A'
 CXX_VERSION='N/A'
 cat <<EOF > conftest.c
 #if defined(_MSC_VER)
 #if defined(__clang__)
 COMPILER clang-cl _MSC_VER
 #else
-COMPILER msvc _MSC_VER
+COMPILER msvc _MSC_FULL_VER
 #endif
 #elif defined(__clang__)
 COMPILER clang __clang_major__.__clang_minor__.__clang_patchlevel__
 #elif defined(__GNUC__)
 COMPILER gcc __GNUC__.__GNUC_MINOR__.__GNUC_PATCHLEVEL__
 #elif defined(__INTEL_COMPILER)
 COMPILER icc __INTEL_COMPILER
 #endif
@@ -50,16 +50,23 @@ fi
 
 if test "`echo | $AS -o conftest.out -v 2>&1 | grep -c GNU`" != "0"; then
     GNU_AS=1
 fi
 rm -f conftest.out
 if test "`echo | $LD -v 2>&1 | grep -c GNU`" != "0"; then
     GNU_LD=1
 fi
+
+if test "$compiler" = "msvc"; then
+     MSVC_VERSION_FULL="$CXX_VERSION"
+     CC_VERSION=`echo ${CC_VERSION} | cut -c 1-4`
+     CXX_VERSION=`echo ${CXX_VERSION} | cut -c 1-4`
+fi
+
 INTEL_CC=
 INTEL_CXX=
 if test "$compiler" = "icc"; then
    INTEL_CC=1
    INTEL_CXX=1
 fi
 
 CLANG_CC=
@@ -73,19 +80,20 @@ if test "$compiler" = "clang"; then
 fi
 if test "$compiler" = "clang-cl"; then
     CLANG_CL=1
     # We force clang-cl to emulate Visual C++ 2013 in configure.in, but that
     # is based on the CLANG_CL variable defined here, so make sure that we're
     # getting the right version here manually.
     CC_VERSION=1800
     CXX_VERSION=1800
-    # Build on clang-cl with MSVC 2013 with fallback emulation.
-    CFLAGS="$CFLAGS -fmsc-version=1800 -fallback"
-    CXXFLAGS="$CXXFLAGS -fmsc-version=1800 -fallback"
+    MSVC_VERSION_FULL=180030723
+    # Build on clang-cl with MSVC 2013 Update 3 with fallback emulation.
+    CFLAGS="$CFLAGS -fms-compatibility-version=18.00.30723 -fallback"
+    CXXFLAGS="$CXXFLAGS -fms-compatibility-version=18.00.30723 -fallback"
 fi
 
 if test "$GNU_CC"; then
     if `$CC -print-prog-name=ld` -v 2>&1 | grep -c GNU >/dev/null; then
         GCC_USE_GNU_LD=1
     fi
 fi
 
--- a/build/automation.py.in
+++ b/build/automation.py.in
@@ -499,17 +499,16 @@ class Automation(object):
     # Crash on non-local network connections by default.
     # MOZ_DISABLE_NONLOCAL_CONNECTIONS can be set to "0" to temporarily
     # enable non-local connections for the purposes of local testing.  Don't
     # override the user's choice here.  See bug 1049688.
     env.setdefault('MOZ_DISABLE_NONLOCAL_CONNECTIONS', '1')
 
     env['GNOME_DISABLE_CRASH_DIALOG'] = '1'
     env['XRE_NO_WINDOWS_CRASH_DIALOG'] = '1'
-    env['NS_TRACE_MALLOC_DISABLE_STACKS'] = '1'
 
     # Set WebRTC logging in case it is not set yet
     env.setdefault('NSPR_LOG_MODULES', 'signaling:5,mtransport:5,datachannel:5,jsep:5,MediaPipelineFactory:5')
     env.setdefault('R_LOG_LEVEL', '6')
     env.setdefault('R_LOG_DESTINATION', 'stderr')
     env.setdefault('R_LOG_VERBOSE', '1')
 
     # ASan specific environment stuff
--- a/build/automationutils.py
+++ b/build/automationutils.py
@@ -325,17 +325,16 @@ def environment(xrePath, env=None, crash
     env[envVar] = os.path.pathsep.join([path for path in envValue if path])
 
   if dmdPath and dmdLibrary and preloadEnvVar:
     env[preloadEnvVar] = os.path.join(dmdPath, dmdLibrary)
 
   # crashreporter
   env['GNOME_DISABLE_CRASH_DIALOG'] = '1'
   env['XRE_NO_WINDOWS_CRASH_DIALOG'] = '1'
-  env['NS_TRACE_MALLOC_DISABLE_STACKS'] = '1'
 
   if crashreporter and not debugger:
     env['MOZ_CRASHREPORTER_NO_REPORT'] = '1'
     env['MOZ_CRASHREPORTER'] = '1'
   else:
     env['MOZ_CRASHREPORTER_DISABLE'] = '1'
 
   # Crash on non-local network connections by default.
--- a/config/config.mk
+++ b/config/config.mk
@@ -223,27 +223,27 @@ OS_CFLAGS += -UDEBUG -DNDEBUG
 ifdef HAVE_64BIT_BUILD
 OS_LDFLAGS += -DEBUG -OPT:REF,ICF
 else
 OS_LDFLAGS += -DEBUG -OPT:REF
 endif
 endif
 
 #
-# Handle trace-malloc and DMD in optimized builds.
+# Handle DMD in optimized builds.
 # No opt to give sane callstacks.
 #
-ifneq (,$(NS_TRACE_MALLOC)$(MOZ_DMD))
+ifdef MOZ_DMD
 MOZ_OPTIMIZE_FLAGS=-Zi -Od -UDEBUG -DNDEBUG
 ifdef HAVE_64BIT_BUILD
 OS_LDFLAGS = -DEBUG -OPT:REF,ICF
 else
 OS_LDFLAGS = -DEBUG -OPT:REF
 endif
-endif # NS_TRACE_MALLOC || MOZ_DMD
+endif # MOZ_DMD
 
 endif # MOZ_DEBUG
 
 endif # WINNT && !GNU_CC
 
 ifdef MOZ_GLUE_IN_PROGRAM
 DEFINES += -DMOZ_GLUE_IN_PROGRAM
 endif
@@ -416,30 +416,30 @@ endif # FAIL_ON_WARNINGS
 
 ifeq ($(OS_ARCH)_$(GNU_CC),WINNT_)
 #// Currently, unless USE_STATIC_LIBS is defined, the multithreaded
 #// DLL version of the RTL is used...
 #//
 #//------------------------------------------------------------------------
 ifdef USE_STATIC_LIBS
 RTL_FLAGS=-MT          # Statically linked multithreaded RTL
-ifneq (,$(MOZ_DEBUG)$(NS_TRACE_MALLOC))
+ifdef MOZ_DEBUG
 ifndef MOZ_NO_DEBUG_RTL
 RTL_FLAGS=-MTd         # Statically linked multithreaded MSVC4.0 debug RTL
 endif
-endif # MOZ_DEBUG || NS_TRACE_MALLOC
+endif # MOZ_DEBUG
 
 else # !USE_STATIC_LIBS
 
 RTL_FLAGS=-MD          # Dynamically linked, multithreaded RTL
-ifneq (,$(MOZ_DEBUG)$(NS_TRACE_MALLOC))
+ifdef MOZ_DEBUG
 ifndef MOZ_NO_DEBUG_RTL
 RTL_FLAGS=-MDd         # Dynamically linked, multithreaded MSVC4.0 debug RTL
 endif
-endif # MOZ_DEBUG || NS_TRACE_MALLOC
+endif # MOZ_DEBUG
 endif # USE_STATIC_LIBS
 endif # WINNT && !GNU_CC
 
 ifeq ($(OS_ARCH),Darwin)
 # Compiling ObjC requires an Apple compiler anyway, so it's ok to set
 # host CMFLAGS here.
 HOST_CMFLAGS += -fobjc-exceptions
 HOST_CMMFLAGS += -fobjc-exceptions
--- a/configure.in
+++ b/configure.in
@@ -466,36 +466,39 @@ case "$target" in
             AC_MSG_ERROR([\$(CXX) test failed.  You must have MS VC++ in your path to build.]) )
         AC_LANG_RESTORE
 
         changequote(,)
         _MSVC_VER_FILTER='s|.*[^!-~]([0-9]+\.[0-9]+\.[0-9]+(\.[0-9]+)?).*|\1|p'
         changequote([,])
 
         # Determine compiler version
-        _CC_MAJOR_VERSION=`echo ${CC_VERSION} | cut -c 1-2`
-        _CC_MINOR_VERSION=`echo ${CC_VERSION} | cut -c 3-4`
+        _CC_MAJOR_VERSION=`echo ${MSVC_VERSION_FULL} | cut -c 1-2`
+        _CC_MINOR_VERSION=`echo ${MSVC_VERSION_FULL} | cut -c 3-4`
+        _CC_BUILD_VERSION=`echo ${MSVC_VERSION_FULL} | cut -c 5-`
         _MSC_VER=${CC_VERSION}
 
         _CXX_MAJOR_VERSION=`echo ${CXX_VERSION} | cut -c 1-2`
 
         if test "$_CC_MAJOR_VERSION" != "$_CXX_MAJOR_VERSION"; then
             AC_MSG_ERROR([The major versions of \$CC and \$CXX do not match.])
         fi
 
         AC_DEFINE(_CRT_SECURE_NO_WARNINGS)
         AC_DEFINE(_CRT_NONSTDC_NO_WARNINGS)
 
-        if test "$_CC_MAJOR_VERSION" = "18"; then
+        if test "$_CC_MAJOR_VERSION" = "18" -a "$_CC_BUILD_VERSION" -ge "30723"; then
             _CC_SUITE=12
             MSVS_VERSION=2013
             MSVC_C_RUNTIME_DLL=msvcr120.dll
             MSVC_CXX_RUNTIME_DLL=msvcp120.dll
         else
-            AC_MSG_ERROR([This version ($CC_VERSION) of the MSVC compiler is unsupported. See https://developer.mozilla.org/en/Windows_Build_Prerequisites.])
+            AC_MSG_ERROR([This version (${_CC_MAJOR_VERSION}.${_CC_MINOR_VERSION}.${_CC_BUILD_VERSION}) of the MSVC compiler is unsupported.
+You must install Visual C++ 2013 Update 3 or newer in order to build.
+See https://developer.mozilla.org/en/Windows_Build_Prerequisites.])
         fi
         AC_SUBST(MSVS_VERSION)
         AC_SUBST(MSVC_C_RUNTIME_DLL)
         AC_SUBST(MSVC_CXX_RUNTIME_DLL)
 
         # Disable SEH on clang-cl because it doesn't implement them yet.
         if test -z "$CLANG_CL"; then
             AC_DEFINE(HAVE_SEH_EXCEPTIONS)
@@ -7042,44 +7045,24 @@ if test -n "$MOZ_DUMP_PAINTING"; then
     AC_DEFINE(MOZ_DUMP_PAINTING)
     AC_DEFINE(MOZ_LAYERS_HAVE_LOG)
 fi
 if test -n "$MOZ_DEBUG"; then
     AC_DEFINE(MOZ_DUMP_PAINTING)
 fi
 
 dnl ========================================================
-dnl = Enable trace malloc
-dnl ========================================================
-NS_TRACE_MALLOC=${MOZ_TRACE_MALLOC}
-MOZ_ARG_ENABLE_BOOL(trace-malloc,
-[  --enable-trace-malloc   Enable malloc tracing; also disables DMD and jemalloc],
-    NS_TRACE_MALLOC=1,
-    NS_TRACE_MALLOC= )
-if test "$NS_TRACE_MALLOC"; then
-  # Please, Mr. Linker Man, don't take away our symbol names
-  MOZ_COMPONENTS_VERSION_SCRIPT_LDFLAGS=
-  AC_DEFINE(NS_TRACE_MALLOC)
-fi
-AC_SUBST(NS_TRACE_MALLOC)
-
-dnl ========================================================
 dnl = Enable DMD
 dnl ========================================================
 
 MOZ_ARG_ENABLE_BOOL(dmd,
 [  --enable-dmd            Enable DMD; also enables jemalloc and replace-malloc],
     MOZ_DMD=1,
     MOZ_DMD= )
 
-dnl The two options are conflicting. Fails the configure to alert the user.
-if test "$NS_TRACE_MALLOC" -a "$MOZ_DMD"; then
-    AC_MSG_ERROR([--enable-trace-malloc and --enable-dmd are conflicting options])
-fi
-
 if test "$MOZ_DMD"; then
     AC_DEFINE(MOZ_DMD)
 
     if test "${CPU_ARCH}" = "arm"; then
         CFLAGS="$CFLAGS -funwind-tables"
         CXXFLAGS="$CXXFLAGS -funwind-tables"
     fi
 
@@ -7091,20 +7074,16 @@ AC_SUBST(MOZ_DMD)
 dnl ========================================================
 dnl = Enable jemalloc
 dnl ========================================================
 MOZ_ARG_ENABLE_BOOL(jemalloc,
 [  --enable-jemalloc       Replace memory allocator with jemalloc],
     MOZ_MEMORY=1,
     MOZ_MEMORY=)
 
-if test "$NS_TRACE_MALLOC"; then
-    MOZ_MEMORY=
-fi
-
 case "${OS_TARGET}" in
 Android|WINNT|Darwin)
   MOZ_GLUE_IN_PROGRAM=
   ;;
 *)
   dnl On !Android !Windows !OSX, we only want to link executables against mozglue
   MOZ_GLUE_IN_PROGRAM=1
   ;;
@@ -7117,21 +7096,16 @@ if test -n "$NIGHTLY_BUILD" -a -n "$MOZ_
   # Enable on central for the debugging opportunities it adds.
   MOZ_REPLACE_MALLOC=1
 fi
 MOZ_ARG_ENABLE_BOOL(replace-malloc,
 [  --enable-replace-malloc   Enable ability to dynamically replace the malloc implementation],
     MOZ_REPLACE_MALLOC=1,
     MOZ_REPLACE_MALLOC= )
 
-dnl The two options are conflicting. Fails the configure to alert the user.
-if test "$NS_TRACE_MALLOC" -a "$MOZ_REPLACE_MALLOC"; then
-    AC_MSG_ERROR([--enable-trace-malloc and --enable-replace-malloc are conflicting options])
-fi
-
 if test -n "$MOZ_REPLACE_MALLOC" -a -z "$MOZ_MEMORY"; then
     dnl We don't want to enable jemalloc unconditionally because it may be a
     dnl deliberate choice not to enable it (bug 702250, for instance)
     AC_MSG_ERROR([--enable-replace-malloc requires --enable-jemalloc])
 elif test -n "$MOZ_REPLACE_MALLOC"; then
     AC_DEFINE(MOZ_REPLACE_MALLOC)
     MOZ_NATIVE_JEMALLOC=
 
@@ -7596,19 +7570,19 @@ dnl = Support for demangling undefined s
 dnl ========================================================
 if test -z "$SKIP_LIBRARY_CHECKS"; then
     AC_LANG_SAVE
     AC_LANG_CPLUSPLUS
     AC_CHECK_FUNCS(__cxa_demangle, HAVE_DEMANGLE=1, HAVE_DEMANGLE=)
     AC_LANG_RESTORE
 fi
 
-# Demangle only for debug or trace-malloc or DMD builds
+# Demangle only for debug or DMD builds
 MOZ_DEMANGLE_SYMBOLS=
-if test "$HAVE_DEMANGLE" && test "$MOZ_DEBUG" -o "$NS_TRACE_MALLOC" -o "$MOZ_DMD"; then
+if test "$HAVE_DEMANGLE" && test "$MOZ_DEBUG" -o "$MOZ_DMD"; then
     MOZ_DEMANGLE_SYMBOLS=1
     AC_DEFINE(MOZ_DEMANGLE_SYMBOLS)
 fi
 AC_SUBST(MOZ_DEMANGLE_SYMBOLS)
 
 dnl ========================================================
 dnl = Support for gcc stack unwinding (from gcc 3.3)
 dnl ========================================================
@@ -8418,17 +8392,17 @@ fi
 
 dnl Build second screen and casting features for external devices if required
 AC_SUBST(MOZ_DEVICES)
 if test -n "$MOZ_DEVICES"; then
   AC_DEFINE(MOZ_DEVICES)
 fi
 
 dnl ========================================================
-if test "$MOZ_DEBUG" -o "$NS_TRACE_MALLOC" -o "$MOZ_DMD"; then
+if test "$MOZ_DEBUG" -o "$MOZ_DMD"; then
     MOZ_COMPONENTS_VERSION_SCRIPT_LDFLAGS=
 fi
 
 dnl ========================================================
 dnl =
 dnl = Maintainer debug option (no --enable equivalent)
 dnl =
 dnl ========================================================
--- a/dom/alarm/AlarmHalService.h
+++ b/dom/alarm/AlarmHalService.h
@@ -29,20 +29,20 @@ public:
   NS_DECL_ISUPPORTS
   NS_DECL_NSIALARMHALSERVICE
 
   void Init();
 
   static already_AddRefed<AlarmHalService> GetInstance();
 
   // Implementing hal::AlarmObserver
-  void Notify(const void_t& aVoid);
+  void Notify(const void_t& aVoid) MOZ_OVERRIDE;
 
   // Implementing hal::SystemTimezoneChangeObserver
-  void Notify(const hal::SystemTimezoneChangeInformation& aSystemTimezoneChangeInfo);
+  void Notify(const hal::SystemTimezoneChangeInformation& aSystemTimezoneChangeInfo) MOZ_OVERRIDE;
 
 private:
   virtual ~AlarmHalService();
 
   bool mAlarmEnabled;
   static StaticRefPtr<AlarmHalService> sSingleton;
 
   nsCOMPtr<nsIAlarmFiredCb> mAlarmFiredCb;
--- a/dom/base/nsIContentPolicy.idl
+++ b/dom/base/nsIContentPolicy.idl
@@ -28,17 +28,23 @@ typedef unsigned long nsContentPolicyTyp
 interface nsIContentPolicy : nsISupports
 {
   /**
    * Indicates a unset or bogus policy type.
    */
   const nsContentPolicyType TYPE_INVALID = 0;
 
   /**
-   * Gecko/Firefox developers: Do not use TYPE_OTHER under any circumstances.
+   * Gecko/Firefox developers: Avoid using TYPE_OTHER. Especially for
+   * requests that are coming from webpages. Or requests in general which
+   * you expect that security checks will be done on.
+   * Always use a more specific type if one is available. And do not hesitate
+   * to add more types as appropriate.
+   * But if you are fairly sure that no one would care about your more specific
+   * type, then it's ok to use TYPE_OTHER.
    *
    * Extension developers: Whenever it is reasonable, use one of the existing
    * content types. If none of the existing content types are right for
    * something you are doing, file a bug in the Core/DOM component that
    * includes a patch that adds your new content type to the end of the list of
    * TYPE_* constants here. But, don't start using your new content type until
    * your patch has been accepted, because it will be uncertain what exact
    * value and name your new content type will have; in that interim period,
--- a/dom/base/nsIXMLHttpRequest.idl
+++ b/dom/base/nsIXMLHttpRequest.idl
@@ -3,16 +3,17 @@
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "nsIDOMEventTarget.idl"
 
 interface nsIChannel;
 interface nsIDOMDocument;
 interface nsIDOMEventListener;
+interface nsILoadGroup;
 interface nsIPrincipal;
 interface nsIScriptContext;
 interface nsIURI;
 interface nsIVariant;
 interface nsIGlobalObject;
 interface nsIInputStream;
 interface nsIDOMBlob;
 
@@ -62,17 +63,17 @@ interface nsIXMLHttpRequestUpload : nsIX
  *   The 'onload', 'onerror', and 'onreadystatechange' attributes moved to
  *   nsIJSXMLHttpRequest, but if you're coding in C++ you should avoid using
  *   those.
  *
  * Conclusion: Do not use event listeners on XMLHttpRequest from C++, unless
  * you're aware of all the security implications.  And then think twice about
  * it.
  */
-[scriptable, uuid(2e91e088-e9fa-4ba4-9887-2a0b7cf27a3e)]
+[scriptable, uuid(704e91dc-a3f6-4e4d-9f5f-4bb85159aeb7)]
 interface nsIXMLHttpRequest : nsISupports
 {
   /**
    * The request uses a channel in order to perform the
    * request.  This attribute represents the channel used
    * for the request.  NULL if the channel has not yet been
    * created.
    *
@@ -290,21 +291,25 @@ interface nsIXMLHttpRequest : nsISupport
    * @param scriptContext The script context to use for the request. May be
    *                      null.
    * @param globalObject The associated global for the request. Can be the
    *                     outer window, a sandbox, or a backstage pass.
    *                     May be null, but then the request cannot create a
    *                     document.
    * @param baseURI The base URI to use when resolving relative URIs. May be
    *                null.
+   * @param loadGroup An optional load group to use when performing the request.
+   *                  This will be used even if the global has a window with a
+   *                  load group.
    */
   [noscript] void init(in nsIPrincipal principal,
                        in nsIScriptContext scriptContext,
                        in nsIGlobalObject globalObject,
-                       in nsIURI baseURI);
+                       in nsIURI baseURI,
+                       [optional] in nsILoadGroup loadGroup);
 
   /**
    * Upload process can be tracked by adding event listener to |upload|.
    */
   readonly attribute nsIXMLHttpRequestUpload upload;
 
   /**
    * Meant to be a script-only mechanism for setting a callback function.
--- a/dom/base/nsJSEnvironment.cpp
+++ b/dom/base/nsJSEnvironment.cpp
@@ -180,17 +180,16 @@ static int32_t sCCTimerFireCount = 0;
 static uint32_t sMinForgetSkippableTime = UINT32_MAX;
 static uint32_t sMaxForgetSkippableTime = 0;
 static uint32_t sTotalForgetSkippableTime = 0;
 static uint32_t sRemovedPurples = 0;
 static uint32_t sForgetSkippableBeforeCC = 0;
 static uint32_t sPreviousSuspectedCount = 0;
 static uint32_t sCleanupsSinceLastGC = UINT32_MAX;
 static bool sNeedsFullCC = false;
-static bool sNeedsFullGC = false;
 static bool sNeedsGCAfterCC = false;
 static bool sIncrementalCC = false;
 static bool sDidPaintAfterPreviousICCSlice = false;
 
 static nsScriptNameSpaceManager *gNameSpaceManager;
 
 static nsIJSRuntimeService *sRuntimeService;
 
@@ -1082,187 +1081,16 @@ nsJSContext::AddSupportsPrimitiveTojsval
       NS_WARNING("Unknown primitive type used");
       *aArgv = JSVAL_NULL;
       break;
     }
   }
   return NS_OK;
 }
 
-#ifdef NS_TRACE_MALLOC
-
-#include <errno.h>              // XXX assume Linux if NS_TRACE_MALLOC
-#include <fcntl.h>
-#ifdef XP_UNIX
-#include <unistd.h>
-#endif
-#ifdef XP_WIN32
-#include <io.h>
-#endif
-#include "nsTraceMalloc.h"
-
-static bool
-CheckUniversalXPConnectForTraceMalloc(JSContext *cx)
-{
-    if (nsContentUtils::IsCallerChrome())
-        return true;
-    JS_ReportError(cx, "trace-malloc functions require UniversalXPConnect");
-    return false;
-}
-
-static bool
-TraceMallocDisable(JSContext *cx, unsigned argc, JS::Value *vp)
-{
-    JS::CallArgs args = JS::CallArgsFromVp(argc, vp);
-
-    if (!CheckUniversalXPConnectForTraceMalloc(cx))
-        return false;
-
-    NS_TraceMallocDisable();
-    args.rval().setUndefined();
-    return true;
-}
-
-static bool
-TraceMallocEnable(JSContext *cx, unsigned argc, JS::Value *vp)
-{
-    JS::CallArgs args = JS::CallArgsFromVp(argc, vp);
-
-    if (!CheckUniversalXPConnectForTraceMalloc(cx))
-        return false;
-
-    NS_TraceMallocEnable();
-    args.rval().setUndefined();
-    return true;
-}
-
-static bool
-TraceMallocOpenLogFile(JSContext *cx, unsigned argc, JS::Value *vp)
-{
-    JS::CallArgs args = JS::CallArgsFromVp(argc, vp);
-
-    if (!CheckUniversalXPConnectForTraceMalloc(cx))
-        return false;
-
-    int fd;
-    if (argc == 0) {
-        fd = -1;
-    } else {
-        JSString *str = JS::ToString(cx, args[0]);
-        if (!str)
-            return false;
-        JSAutoByteString filename(cx, str);
-        if (!filename)
-            return false;
-        fd = open(filename.ptr(), O_CREAT | O_WRONLY | O_TRUNC, 0644);
-        if (fd < 0) {
-            JS_ReportError(cx, "can't open %s: %s", filename.ptr(), strerror(errno));
-            return false;
-        }
-    }
-    args.rval().setInt32(fd);
-    return true;
-}
-
-static bool
-TraceMallocChangeLogFD(JSContext *cx, unsigned argc, JS::Value *vp)
-{
-    JS::CallArgs args = CallArgsFromVp(argc, vp);
-
-    if (!CheckUniversalXPConnectForTraceMalloc(cx))
-        return false;
-
-    int32_t fd, oldfd;
-    if (args.length() == 0) {
-        oldfd = -1;
-    } else {
-        if (!JS::ToInt32(cx, args[0], &fd))
-            return false;
-        oldfd = NS_TraceMallocChangeLogFD(fd);
-        if (oldfd == -2) {
-            JS_ReportOutOfMemory(cx);
-            return false;
-        }
-    }
-    args.rval().setInt32(oldfd);
-    return true;
-}
-
-static bool
-TraceMallocCloseLogFD(JSContext *cx, unsigned argc, JS::Value *vp)
-{
-    JS::CallArgs args = CallArgsFromVp(argc, vp);
-
-    if (!CheckUniversalXPConnectForTraceMalloc(cx))
-        return false;
-
-    int32_t fd;
-    if (args.length() == 0) {
-        args.rval().setUndefined();
-        return true;
-    }
-    if (!JS::ToInt32(cx, args[0], &fd))
-        return false;
-    NS_TraceMallocCloseLogFD((int) fd);
-    args.rval().setInt32(fd);
-    return true;
-}
-
-static bool
-TraceMallocLogTimestamp(JSContext *cx, unsigned argc, JS::Value *vp)
-{
-    JS::CallArgs args = JS::CallArgsFromVp(argc, vp);
-    if (!CheckUniversalXPConnectForTraceMalloc(cx))
-        return false;
-
-    JSString *str = JS::ToString(cx, args.get(0));
-    if (!str)
-        return false;
-    JSAutoByteString caption(cx, str);
-    if (!caption)
-        return false;
-    NS_TraceMallocLogTimestamp(caption.ptr());
-    args.rval().setUndefined();
-    return true;
-}
-
-static bool
-TraceMallocDumpAllocations(JSContext *cx, unsigned argc, JS::Value *vp)
-{
-    JS::CallArgs args = JS::CallArgsFromVp(argc, vp);
-    if (!CheckUniversalXPConnectForTraceMalloc(cx))
-        return false;
-
-    JSString *str = JS::ToString(cx, args.get(0));
-    if (!str)
-        return false;
-    JSAutoByteString pathname(cx, str);
-    if (!pathname)
-        return false;
-    if (NS_TraceMallocDumpAllocations(pathname.ptr()) < 0) {
-        JS_ReportError(cx, "can't dump to %s: %s", pathname.ptr(), strerror(errno));
-        return false;
-    }
-    args.rval().setUndefined();
-    return true;
-}
-
-static const JSFunctionSpec TraceMallocFunctions[] = {
-    JS_FS("TraceMallocDisable",         TraceMallocDisable,         0, 0),
-    JS_FS("TraceMallocEnable",          TraceMallocEnable,          0, 0),
-    JS_FS("TraceMallocOpenLogFile",     TraceMallocOpenLogFile,     1, 0),
-    JS_FS("TraceMallocChangeLogFD",     TraceMallocChangeLogFD,     1, 0),
-    JS_FS("TraceMallocCloseLogFD",      TraceMallocCloseLogFD,      1, 0),
-    JS_FS("TraceMallocLogTimestamp",    TraceMallocLogTimestamp,    1, 0),
-    JS_FS("TraceMallocDumpAllocations", TraceMallocDumpAllocations, 1, 0),
-    JS_FS_END
-};
-
-#endif /* NS_TRACE_MALLOC */
-
 #ifdef MOZ_JPROF
 
 #include <signal.h>
 
 inline bool
 IsJProfAction(struct sigaction *action)
 {
     return (action->sa_sigaction &&
@@ -1369,23 +1197,16 @@ nsJSContext::InitClasses(JS::Handle<JSOb
   AutoJSAPI jsapi;
   jsapi.Init();
   JSContext* cx = jsapi.cx();
   JSAutoCompartment ac(cx, aGlobalObj);
 
   // Attempt to initialize profiling functions
   ::JS_DefineProfilingFunctions(cx, aGlobalObj);
 
-#ifdef NS_TRACE_MALLOC
-  if (nsContentUtils::IsCallerChrome()) {
-    // Attempt to initialize TraceMalloc functions
-    ::JS_DefineFunctions(cx, aGlobalObj, TraceMallocFunctions);
-  }
-#endif
-
 #ifdef MOZ_JPROF
   // Attempt to initialize JProf functions
   ::JS_DefineFunctions(cx, aGlobalObj, JProfFunctions);
 #endif
 
   return NS_OK;
 }
 
@@ -1454,34 +1275,27 @@ nsJSContext::GarbageCollectNow(JS::gcrea
 
   if (!nsContentUtils::XPConnect() || !sRuntime) {
     return;
   }
 
   if (sCCLockedOut && aIncremental == IncrementalGC) {
     // We're in the middle of incremental GC. Do another slice.
     JS::PrepareForIncrementalGC(sRuntime);
-    JS::IncrementalGC(sRuntime, aReason, aSliceMillis);
+    JS::IncrementalGCSlice(sRuntime, aReason, aSliceMillis);
     return;
   }
 
-  if (sNeedsFullGC || aReason != JS::gcreason::CC_WAITING) {
-    sNeedsFullGC = false;
-    JS::PrepareForFullGC(sRuntime);
-  } else {
-    CycleCollectedJSRuntime::Get()->PrepareWaitingZonesForGC();
-  }
-
+  JS::PrepareForFullGC(sRuntime);
   if (aIncremental == IncrementalGC) {
     MOZ_ASSERT(aShrinking == NonShrinkingGC);
-    JS::IncrementalGC(sRuntime, aReason, aSliceMillis);
-  } else if (aShrinking == ShrinkingGC) {
-    JS::ShrinkingGC(sRuntime, aReason);
+    JS::StartIncrementalGC(sRuntime, GC_NORMAL, aReason, aSliceMillis);
   } else {
-    JS::GCForReason(sRuntime, aReason);
+    JSGCInvocationKind gckind = aShrinking == ShrinkingGC ? GC_SHRINK : GC_NORMAL;
+    JS::GCForReason(sRuntime, gckind, aReason);
   }
 }
 
 //static
 void
 nsJSContext::ShrinkGCBuffersNow()
 {
   PROFILER_LABEL("nsJSContext", "ShrinkGCBuffersNow",
@@ -2151,18 +1965,16 @@ nsJSContext::RunNextCollectorTimer()
     return;
   }
 }
 
 // static
 void
 nsJSContext::PokeGC(JS::gcreason::Reason aReason, int aDelay)
 {
-  sNeedsFullGC = sNeedsFullGC || aReason != JS::gcreason::CC_WAITING;
-
   if (sGCTimer || sInterSliceGCTimer || sShuttingDown) {
     // There's already a timer for GC'ing, just return
     return;
   }
 
   if (sCCTimer) {
     // Make sure CC is called...
     sNeedsFullCC = true;
@@ -2474,17 +2286,16 @@ mozilla::dom::StartupJSEnvironment()
   sHasRunGC = false;
   sPendingLoadCount = 0;
   sLoadingInProgress = false;
   sCCollectedWaitingForGC = 0;
   sCCollectedZonesWaitingForGC = 0;
   sLikelyShortLivingObjectsNeedingGC = 0;
   sPostGCEventsToConsole = false;
   sNeedsFullCC = false;
-  sNeedsFullGC = false;
   sNeedsGCAfterCC = false;
   gNameSpaceManager = nullptr;
   sRuntimeService = nullptr;
   sRuntime = nullptr;
   sIsInitialized = false;
   sDidShutdown = false;
   sShuttingDown = false;
   sContextCount = 0;
--- a/dom/base/nsXMLHttpRequest.cpp
+++ b/dom/base/nsXMLHttpRequest.cpp
@@ -356,30 +356,31 @@ nsXMLHttpRequest::Init()
 
 /**
  * This Init method should only be called by C++ consumers.
  */
 NS_IMETHODIMP
 nsXMLHttpRequest::Init(nsIPrincipal* aPrincipal,
                        nsIScriptContext* aScriptContext,
                        nsIGlobalObject* aGlobalObject,
-                       nsIURI* aBaseURI)
+                       nsIURI* aBaseURI,
+                       nsILoadGroup* aLoadGroup)
 {
   NS_ENSURE_ARG_POINTER(aPrincipal);
-  
+
   if (nsCOMPtr<nsPIDOMWindow> win = do_QueryInterface(aGlobalObject)) {
     if (win->IsOuterWindow()) {
       // Must be bound to inner window, innerize if necessary.
       nsCOMPtr<nsIGlobalObject> inner = do_QueryInterface(
         win->GetCurrentInnerWindow());
       aGlobalObject = inner.get();
     }
   }
 
-  Construct(aPrincipal, aGlobalObject, aBaseURI);
+  Construct(aPrincipal, aGlobalObject, aBaseURI, aLoadGroup);
   return NS_OK;
 }
 
 void
 nsXMLHttpRequest::InitParameters(bool aAnon, bool aSystem)
 {
   if (!aAnon && !aSystem) {
     return;
@@ -1431,16 +1432,21 @@ nsXMLHttpRequest::GetResponseHeader(cons
 
 already_AddRefed<nsILoadGroup>
 nsXMLHttpRequest::GetLoadGroup() const
 {
   if (mState & XML_HTTP_REQUEST_BACKGROUND) {                 
     return nullptr;
   }
 
+  if (mLoadGroup) {
+    nsCOMPtr<nsILoadGroup> ref = mLoadGroup;
+    return ref.forget();
+  }
+
   nsresult rv = NS_ERROR_FAILURE;
   nsIScriptContext* sc =
     const_cast<nsXMLHttpRequest*>(this)->GetContextForEventHandlers(&rv);
   nsCOMPtr<nsIDocument> doc =
     nsContentUtils::GetDocumentFromScriptContext(sc);
   if (doc) {
     return doc->GetDocumentLoadGroup();
   }
--- a/dom/base/nsXMLHttpRequest.h
+++ b/dom/base/nsXMLHttpRequest.h
@@ -239,24 +239,26 @@ public:
       return nullptr;
     }
 
     return Constructor(aGlobal, aCx, params, aRv);
   }
 
   void Construct(nsIPrincipal* aPrincipal,
                  nsIGlobalObject* aGlobalObject,
-                 nsIURI* aBaseURI = nullptr)
+                 nsIURI* aBaseURI = nullptr,
+                 nsILoadGroup* aLoadGroup = nullptr)
   {
     MOZ_ASSERT(aPrincipal);
     MOZ_ASSERT_IF(nsCOMPtr<nsPIDOMWindow> win = do_QueryInterface(
       aGlobalObject), win->IsInnerWindow());
     mPrincipal = aPrincipal;
     BindToOwner(aGlobalObject);
     mBaseURI = aBaseURI;
+    mLoadGroup = aLoadGroup;
   }
 
   void InitParameters(bool aAnon, bool aSystem);
 
   void SetParameters(bool aAnon, bool aSystem)
   {
     mIsAnon = aAnon || aSystem;
     mIsSystem = aSystem;
@@ -712,16 +714,17 @@ protected:
    * asked for the relevant interface.
    */
   nsCOMPtr<nsIChannelEventSink> mChannelEventSink;
   nsCOMPtr<nsIProgressEventSink> mProgressEventSink;
 
   nsIRequestObserver* mRequestObserver;
 
   nsCOMPtr<nsIURI> mBaseURI;
+  nsCOMPtr<nsILoadGroup> mLoadGroup;
 
   uint32_t mState;
 
   nsRefPtr<nsXMLHttpRequestUpload> mUpload;
   uint64_t mUploadTransferred;
   uint64_t mUploadTotal;
   bool mUploadLengthComputable;
   bool mUploadComplete;
--- a/dom/base/test/TestGetURL.cpp
+++ b/dom/base/test/TestGetURL.cpp
@@ -37,17 +37,17 @@ nsresult TestGetURL(const nsCString& aUR
   nsCOMPtr<nsIScriptSecurityManager> secman =
     do_GetService(NS_SCRIPTSECURITYMANAGER_CONTRACTID, &rv);
   TEST_ENSURE_SUCCESS(rv, "Couldn't get script security manager!");
 
   nsCOMPtr<nsIPrincipal> systemPrincipal;
   rv = secman->GetSystemPrincipal(getter_AddRefs(systemPrincipal));
   TEST_ENSURE_SUCCESS(rv, "Couldn't get system principal!");
 
-  rv = xhr->Init(systemPrincipal, nullptr, nullptr, nullptr);
+  rv = xhr->Init(systemPrincipal, nullptr, nullptr, nullptr, nullptr);
   TEST_ENSURE_SUCCESS(rv, "Couldn't initialize the XHR!");
 
   rv = xhr->Open(getString, aURL, false, empty, empty);
   TEST_ENSURE_SUCCESS(rv, "OpenRequest failed!");
 
   rv = xhr->Send(nullptr);
   TEST_ENSURE_SUCCESS(rv, "Send failed!");
 
--- a/dom/base/test/TestNativeXMLHttpRequest.cpp
+++ b/dom/base/test/TestNativeXMLHttpRequest.cpp
@@ -47,17 +47,17 @@ nsresult TestNativeXMLHttpRequest()
   nsCOMPtr<nsIScriptSecurityManager> secman =
     do_GetService(NS_SCRIPTSECURITYMANAGER_CONTRACTID, &rv);
   TEST_ENSURE_SUCCESS(rv, "Couldn't get script security manager!");
 
   nsCOMPtr<nsIPrincipal> systemPrincipal;
   rv = secman->GetSystemPrincipal(getter_AddRefs(systemPrincipal));
   TEST_ENSURE_SUCCESS(rv, "Couldn't get system principal!");
 
-  rv = xhr->Init(systemPrincipal, nullptr, nullptr, nullptr);
+  rv = xhr->Init(systemPrincipal, nullptr, nullptr, nullptr, nullptr);
   TEST_ENSURE_SUCCESS(rv, "Couldn't initialize the XHR!");
 
   rv = xhr->Open(getString, testURL, false, empty, empty);
   TEST_ENSURE_SUCCESS(rv, "Open failed!");
 
   rv = xhr->Send(nullptr);
   TEST_ENSURE_SUCCESS(rv, "Send failed!");
 
--- a/dom/battery/BatteryManager.h
+++ b/dom/battery/BatteryManager.h
@@ -28,17 +28,17 @@ class BatteryManager : public DOMEventTa
 {
 public:
   explicit BatteryManager(nsPIDOMWindow* aWindow);
 
   void Init();
   void Shutdown();
 
   // For IObserver.
-  void Notify(const hal::BatteryInformation& aBatteryInfo);
+  void Notify(const hal::BatteryInformation& aBatteryInfo) MOZ_OVERRIDE;
 
   /**
    * WebIDL Interface
    */
 
   nsPIDOMWindow* GetParentObject() const
   {
      return GetOwner();
--- a/dom/bindings/BindingUtils.cpp
+++ b/dom/bindings/BindingUtils.cpp
@@ -2063,18 +2063,18 @@ ReportLenientThisUnwrappingFailure(JSCon
   nsCOMPtr<nsPIDOMWindow> window = do_QueryInterface(global.GetAsSupports());
   if (window && window->GetDoc()) {
     window->GetDoc()->WarnOnceAbout(nsIDocument::eLenientThis);
   }
   return true;
 }
 
 bool
-GetWindowForJSImplementedObject(JSContext* cx, JS::Handle<JSObject*> obj,
-                                nsPIDOMWindow** window)
+GetContentGlobalForJSImplementedObject(JSContext* cx, JS::Handle<JSObject*> obj,
+                                       nsIGlobalObject** globalObj)
 {
   // Be very careful to not get tricked here.
   MOZ_ASSERT(NS_IsMainThread());
   if (!xpc::AccessCheck::isChrome(js::GetObjectCompartment(obj))) {
     NS_RUNTIMEABORT("Should have a chrome object here");
   }
 
   // Look up the content-side object.
@@ -2090,47 +2090,46 @@ GetWindowForJSImplementedObject(JSContex
 
   // Go ahead and get the global from it.  GlobalObject will handle
   // doing unwrapping as needed.
   GlobalObject global(cx, &domImplVal.toObject());
   if (global.Failed()) {
     return false;
   }
 
-  // It's OK if we have null here: that just means the content-side
-  // object really wasn't associated with any window.
-  nsCOMPtr<nsPIDOMWindow> win(do_QueryInterface(global.GetAsSupports()));
-  win.forget(window);
+  DebugOnly<nsresult> rv = CallQueryInterface(global.GetAsSupports(), globalObj);
+  MOZ_ASSERT(NS_SUCCEEDED(rv));
+  MOZ_ASSERT(*globalObj);
   return true;
 }
 
-already_AddRefed<nsPIDOMWindow>
+already_AddRefed<nsIGlobalObject>
 ConstructJSImplementation(JSContext* aCx, const char* aContractId,
                           const GlobalObject& aGlobal,
                           JS::MutableHandle<JSObject*> aObject,
                           ErrorResult& aRv)
 {
-  // Get the window to use as a parent and for initialization.
-  nsCOMPtr<nsPIDOMWindow> window = do_QueryInterface(aGlobal.GetAsSupports());
-  if (!window) {
+  // Get the global object to use as a parent and for initialization.
+  nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(aGlobal.GetAsSupports());
+  if (!global) {
     aRv.Throw(NS_ERROR_FAILURE);
     return nullptr;
   }
 
-  ConstructJSImplementation(aCx, aContractId, window, aObject, aRv);
+  ConstructJSImplementation(aCx, aContractId, global, aObject, aRv);
 
   if (aRv.Failed()) {
     return nullptr;
   }
-  return window.forget();
+  return global.forget();
 }
 
 void
 ConstructJSImplementation(JSContext* aCx, const char* aContractId,
-                          nsPIDOMWindow* aWindow,
+                          nsIGlobalObject* aGlobal,
                           JS::MutableHandle<JSObject*> aObject,
                           ErrorResult& aRv)
 {
   // Make sure to divorce ourselves from the calling JS while creating and
   // initializing the object, so exceptions from that will get reported
   // properly, since those are never exceptions that a spec wants to be thrown.
   {
     AutoNoJSAPI nojsapi;
@@ -2140,22 +2139,24 @@ ConstructJSImplementation(JSContext* aCx
     nsCOMPtr<nsISupports> implISupports = do_CreateInstance(aContractId, &rv);
     if (!implISupports) {
       nsPrintfCString msg("Failed to get JS implementation for contract \"%s\"",
                           aContractId);
       NS_WARNING(msg.get());
       aRv.Throw(rv);
       return;
     }
-    // Initialize the object, if it implements nsIDOMGlobalPropertyInitializer.
+    // Initialize the object, if it implements nsIDOMGlobalPropertyInitializer
+    // and our global is a window.
     nsCOMPtr<nsIDOMGlobalPropertyInitializer> gpi =
       do_QueryInterface(implISupports);
+    nsCOMPtr<nsPIDOMWindow> window = do_QueryInterface(aGlobal);
     if (gpi) {
       JS::Rooted<JS::Value> initReturn(aCx);
-      rv = gpi->Init(aWindow, &initReturn);
+      rv = gpi->Init(window, &initReturn);
       if (NS_FAILED(rv)) {
         aRv.Throw(rv);
         return;
       }
       // With JS-implemented WebIDL, the return value of init() is not used to determine
       // if init() failed, so init() should only return undefined. Any kind of permission
       // or pref checking must happen by adding an attribute to the WebIDL interface.
       if (!initReturn.isUndefined()) {
--- a/dom/bindings/BindingUtils.h
+++ b/dom/bindings/BindingUtils.h
@@ -2646,29 +2646,29 @@ GetUnforgeableHolder(JSObject* aGlobal, 
 {
   ProtoAndIfaceCache& protoAndIfaceCache = *GetProtoAndIfaceCache(aGlobal);
   JSObject* interfaceProto = protoAndIfaceCache.EntrySlotMustExist(aId);
   return &js::GetReservedSlot(interfaceProto,
                               DOM_INTERFACE_PROTO_SLOTS_BASE).toObject();
 }
 
 // Given a JSObject* that represents the chrome side of a JS-implemented WebIDL
-// interface, get the nsPIDOMWindow corresponding to the content side, if any.
+// interface, get the nsIGlobalObject corresponding to the content side, if any.
 // A false return means an exception was thrown.
 bool
-GetWindowForJSImplementedObject(JSContext* cx, JS::Handle<JSObject*> obj,
-                                nsPIDOMWindow** window);
+GetContentGlobalForJSImplementedObject(JSContext* cx, JS::Handle<JSObject*> obj,
+                                       nsIGlobalObject** global);
 
 void
 ConstructJSImplementation(JSContext* aCx, const char* aContractId,
-                          nsPIDOMWindow* aWindow,
+                          nsIGlobalObject* aGlobal,
                           JS::MutableHandle<JSObject*> aObject,
                           ErrorResult& aRv);
 
-already_AddRefed<nsPIDOMWindow>
+already_AddRefed<nsIGlobalObject>
 ConstructJSImplementation(JSContext* aCx, const char* aContractId,
                           const GlobalObject& aGlobal,
                           JS::MutableHandle<JSObject*> aObject,
                           ErrorResult& aRv);
 
 /**
  * Convert an nsCString to jsval, returning true on success.
  * These functions are intended for ByteString implementations.
--- a/dom/bindings/Codegen.py
+++ b/dom/bindings/Codegen.py
@@ -3705,22 +3705,22 @@ class CastableObjectUnwrapper():
             exceptionCode = exceptionCode or codeOnFailure
             self.substitution["codeOnFailure"] = fill(
                 """
                 // Be careful to not wrap random DOM objects here, even if
                 // they're wrapped in opaque security wrappers for some reason.
                 // XXXbz Wish we could check for a JS-implemented object
                 // that already has a content reflection...
                 if (!IsDOMObject(js::UncheckedUnwrap(${source}))) {
-                  nsCOMPtr<nsPIDOMWindow> ourWindow;
-                  if (!GetWindowForJSImplementedObject(cx, Callback(), getter_AddRefs(ourWindow))) {
+                  nsCOMPtr<nsIGlobalObject> contentGlobal;
+                  if (!GetContentGlobalForJSImplementedObject(cx, Callback(), getter_AddRefs(contentGlobal))) {
                     $*{exceptionCode}
                   }
                   JS::Rooted<JSObject*> jsImplSourceObj(cx, ${source});
-                  ${target} = new ${type}(jsImplSourceObj, ourWindow);
+                  ${target} = new ${type}(jsImplSourceObj, contentGlobal);
                 } else {
                   $*{codeOnFailure}
                 }
                 """,
                 exceptionCode=exceptionCode,
                 **self.substitution)
         else:
             self.substitution["codeOnFailure"] = codeOnFailure
@@ -12286,17 +12286,17 @@ class CGBindingRoot(CGThing):
                                             workers=False)
         workerCallbacks = config.getCallbacks(webIDLFile=webIDLFile,
                                               workers=True)
         callbackDescriptors = config.getDescriptors(webIDLFile=webIDLFile,
                                                     isCallback=True)
         jsImplemented = config.getDescriptors(webIDLFile=webIDLFile,
                                               isJSImplemented=True)
         bindingDeclareHeaders["nsWeakReference.h"] = jsImplemented
-        bindingHeaders["nsPIDOMWindow.h"] = jsImplemented
+        bindingHeaders["nsIGlobalObject.h"] = jsImplemented
         bindingHeaders["AtomList.h"] = hasNonEmptyDictionaries or jsImplemented or callbackDescriptors
 
         def addHeaderBasedOnTypes(header, typeChecker):
             bindingHeaders[header] = (
                 bindingHeaders.get(header, False) or
                 any(map(typeChecker,
                         getAllTypes(descriptors + callbackDescriptors,
                                     dictionaries,
@@ -13295,17 +13295,16 @@ class CGJSImplMethod(CGJSImplMember):
             # arguments to the WebIDL constructor, so don't pass them to __Init()
             assert args[0].argType == 'const GlobalObject&'
             assert args[1].argType == 'JSContext*'
             constructorArgs = [arg.name for arg in args[2:]]
             constructorArgs.append("js::GetObjectCompartment(scopeObj)")
             initCall = fill(
                 """
                 // Wrap the object before calling __Init so that __DOM_IMPL__ is available.
-                nsCOMPtr<nsIGlobalObject> globalHolder = do_QueryInterface(window);
                 JS::Rooted<JSObject*> scopeObj(cx, globalHolder->GetGlobalJSObject());
                 MOZ_ASSERT(js::IsObjectInContextCompartment(scopeObj, cx));
                 JS::Rooted<JS::Value> wrappedVal(cx);
                 if (!GetOrCreateDOMReflector(cx, impl, &wrappedVal)) {
                   //XXX Assertion disabled for now, see bug 991271.
                   MOZ_ASSERT(true || JS_IsExceptionPending(cx));
                   aRv.Throw(NS_ERROR_UNEXPECTED);
                   return nullptr;
@@ -13321,23 +13320,23 @@ class CGJSImplMethod(CGJSImplMember):
             initCall = ""
         return genConstructorBody(self.descriptor, initCall)
 
 
 def genConstructorBody(descriptor, initCall=""):
     return fill(
         """
         JS::Rooted<JSObject*> jsImplObj(cx);
-        nsCOMPtr<nsPIDOMWindow> window =
+        nsCOMPtr<nsIGlobalObject> globalHolder =
           ConstructJSImplementation(cx, "${contractId}", global, &jsImplObj, aRv);
         if (aRv.Failed()) {
           return nullptr;
         }
         // Build the C++ implementation.
-        nsRefPtr<${implClass}> impl = new ${implClass}(jsImplObj, window);
+        nsRefPtr<${implClass}> impl = new ${implClass}(jsImplObj, globalHolder);
         $*{initCall}
         return impl.forget();
         """,
         contractId=descriptor.interface.getJSImplementation(),
         implClass=descriptor.name,
         initCall=initCall)
 
 
@@ -13511,17 +13510,17 @@ class CGJSImplClass(CGBindingImplClass):
             parentInterface = parentInterface.parent
         if not parentInterface and descriptor.interface.parent:
             # We only have C++ ancestors, so only pass along the window
             baseConstructors.insert(0,
                                     "%s(aParent)" % parentClass)
 
         constructor = ClassConstructor(
             [Argument("JS::Handle<JSObject*>", "aJSImplObject"),
-             Argument("nsPIDOMWindow*", "aParent")],
+             Argument("nsIGlobalObject*", "aParent")],
             visibility="public",
             baseConstructors=baseConstructors)
 
         self.methodDecls.append(
             ClassMethod("_Create",
                         "bool",
                         JSNativeArguments(),
                         static=True,
@@ -13582,22 +13581,20 @@ class CGJSImplClass(CGBindingImplClass):
             }
 
             // GlobalObject will go through wrappers as needed for us, and
             // is simpler than the right UnwrapArg incantation.
             GlobalObject global(cx, &args[0].toObject());
             if (global.Failed()) {
               return false;
             }
-            nsCOMPtr<nsPIDOMWindow> window = do_QueryInterface(global.GetAsSupports());
-            if (!window) {
-              return ThrowErrorMessage(cx, MSG_DOES_NOT_IMPLEMENT_INTERFACE, "Argument 1 of ${ifaceName}._create", "Window");
-            }
+            nsCOMPtr<nsIGlobalObject> globalHolder = do_QueryInterface(global.GetAsSupports());
+            MOZ_ASSERT(globalHolder);
             JS::Rooted<JSObject*> arg(cx, &args[1].toObject());
-            nsRefPtr<${implName}> impl = new ${implName}(arg, window);
+            nsRefPtr<${implName}> impl = new ${implName}(arg, globalHolder);
             MOZ_ASSERT(js::IsObjectInContextCompartment(arg, cx));
             return GetOrCreateDOMReflector(cx, impl, args.rval());
             """,
             ifaceName=self.descriptor.interface.identifier.name,
             implName=self.descriptor.name)
 
 
 def isJSImplementedDescriptor(descriptorProvider):
--- a/dom/bindings/test/TestCImplementedInterface.h
+++ b/dom/bindings/test/TestCImplementedInterface.h
@@ -13,26 +13,26 @@ class nsPIDOMWindow;
 
 namespace mozilla {
 namespace dom {
 
 class TestCImplementedInterface : public TestJSImplInterface
 {
 public:
   TestCImplementedInterface(JS::Handle<JSObject*> aJSImpl,
-                            nsPIDOMWindow* aParent)
+                            nsIGlobalObject* aParent)
     : TestJSImplInterface(aJSImpl, aParent)
   {}
 };
 
 class TestCImplementedInterface2 : public nsISupports,
                                    public nsWrapperCache
 {
 public:
-  explicit TestCImplementedInterface2(nsPIDOMWindow* aParent)
+  explicit TestCImplementedInterface2(nsIGlobalObject* aParent)
   {}
   NS_DECL_CYCLE_COLLECTING_ISUPPORTS
   NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(TestCImplementedInterface2)
 
   // We need a GetParentObject to make binding codegen happy
   nsISupports* GetParentObject();
 };
 
--- a/dom/browser-element/BrowserElementChildPreload.js
+++ b/dom/browser-element/BrowserElementChildPreload.js
@@ -589,16 +589,21 @@ BrowserElementChild.prototype = {
       scrollX: e.scrollX,
       scrollY: e.scrollY,
     };
     sendAsyncMsg('scrollviewchange', detail);
   },
 
   _selectionStateChangedHandler: function(e) {
     e.stopPropagation();
+
+    if (!this._isContentWindowCreated) {
+      return;
+    }
+
     let boundingClientRect = e.boundingClientRect;
 
     let isCollapsed = (e.selectedText.length == 0);
     let isMouseUp = (e.states.indexOf('mouseup') == 0);
     let canPaste = this._isCommandEnabled("paste");
 
     if (this._selectionStateChangedTarget != e.target) {
       // SelectionStateChanged events with the following states are not
@@ -608,21 +613,23 @@ BrowserElementChild.prototype = {
       if(e.states.length == 0 ||
          e.states.indexOf('drag') == 0 ||
          e.states.indexOf('keypress') == 0 ||
          e.states.indexOf('mousedown') == 0) {
         return;
       }
 
       // The collapsed SelectionStateChanged event is unnecessary to dispatch,
-      // bypass this event by default. But there is one exception to support
-      // the shortcut mode which can paste previous copied content easily
+      // bypass this event by default, but here comes some exceptional cases
       if (isCollapsed) {
         if (isMouseUp && canPaste) {
-          //Dispatch this selection change event to support shortcut mode
+          // Always dispatch to support shortcut mode which can paste previous
+          // copied content easily
+        } else if (e.states.indexOf('blur') == 0) {
+          // Always dispatch to notify the blur for the focus content
         } else {
           return;
         }
       }
     }
 
     // If we select something and selection range is visible, we cache current
     // event's target to selectionStateChangedTarget.
--- a/dom/browser-element/mochitest/browserElement_CopyPaste.js
+++ b/dom/browser-element/mochitest/browserElement_CopyPaste.js
@@ -176,22 +176,25 @@ function isChildProcess() {
                          .getService(SpecialPowers.Ci.nsIXULRuntime)
                          .processType != SpecialPowers.Ci.nsIXULRuntime.PROCESS_TYPE_DEFAULT;
 }
 
 function testSelectAll(e) {
   // Skip mozbrowser test if we're at child process.
   if (!isChildProcess()) {
     iframeOuter.addEventListener("mozbrowserselectionstatechanged", function selectchangeforselectall(e) {
-      iframeOuter.removeEventListener("mozbrowserselectionstatechanged", selectchangeforselectall, true);
-      ok(true, "got mozbrowserselectionstatechanged event." + stateMeaning);
-      ok(e.detail, "event.detail is not null." + stateMeaning);
-      ok(e.detail.width != 0, "event.detail.width is not zero" + stateMeaning);
-      ok(e.detail.height != 0, "event.detail.height is not zero" + stateMeaning);
-      SimpleTest.executeSoon(function() { testCopy1(e); });
+      if (e.detail.states.indexOf('selectall') == 0) {
+        iframeOuter.removeEventListener("mozbrowserselectionstatechanged", selectchangeforselectall, true);
+        ok(true, "got mozbrowserselectionstatechanged event." + stateMeaning);
+        ok(e.detail, "event.detail is not null." + stateMeaning);
+        ok(e.detail.width != 0, "event.detail.width is not zero" + stateMeaning);
+        ok(e.detail.height != 0, "event.detail.height is not zero" + stateMeaning);
+        ok(e.detail.states, "event.detail.state " + e.detail.states);
+        SimpleTest.executeSoon(function() { testCopy1(e); });
+      }
     }, true);
   }
 
   mm.addMessageListener('content-focus', function messageforfocus(msg) {
     mm.removeMessageListener('content-focus', messageforfocus);
     // test selectall command, after calling this the selectionstatechanged event should be fired.
     doCommand('selectall');
     if (isChildProcess()) {
new file mode 100644
--- /dev/null
+++ b/dom/browser-element/mochitest/browserElement_SelectionStateBlur.js
@@ -0,0 +1,57 @@
+/* Any copyright is dedicated to the public domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+
+// Bug 1111433: Send out the SelectionStateChanged event with Blur state
+
+"use strict";
+
+SimpleTest.waitForExplicitFinish();
+browserElementTestHelpers.setEnabledPref(true);
+browserElementTestHelpers.setSelectionChangeEnabledPref(true);
+browserElementTestHelpers.addPermission();
+
+var mm;
+var iframe;
+
+var changefocus = function () {
+  var elt = content.document.getElementById("text");
+  if (elt) {
+    elt.focus();
+    elt.select();
+    elt.blur();
+  }
+}
+
+function runTest() {
+  iframe = document.createElement('iframe');
+  iframe.setAttribute('mozbrowser', 'true');
+  document.body.appendChild(iframe);
+
+  mm = SpecialPowers.getBrowserFrameMessageManager(iframe);
+
+  iframe.src = "data:text/html,<html><body>" +
+               "<textarea id='text'> Bug 1111433 </textarea>"+
+               "</body></html>";
+
+  var loadtime = 0;
+  iframe.addEventListener("mozbrowserloadend", function onloadend(e) {
+    loadtime++;
+    if (loadtime === 2) {
+      iframe.removeEventListener("mozbrowserloadend", onloadend);
+      SimpleTest.executeSoon(function() { testBlur(e); });
+    }
+  });
+}
+
+function testBlur(e) {
+  iframe.addEventListener("mozbrowserselectionstatechanged", function selectionstatechanged(e) {
+    iframe.removeEventListener("mozbrowserselectionstatechanged", selectionstatechanged, true);
+    ok(e.detail.states.indexOf('blur') == 0, "received state  " + e.detail.states);
+    SimpleTest.finish();
+  }, true);
+
+  iframe.focus();
+  mm.loadFrameScript('data:,(' + changefocus.toString() + ')();', false);
+}
+
+addEventListener('testready', runTest);
--- a/dom/browser-element/mochitest/mochitest-oop.ini
+++ b/dom/browser-element/mochitest/mochitest-oop.ini
@@ -65,16 +65,18 @@ skip-if = (toolkit == 'gonk' && !debug)
 [test_browserElement_oop_PromptConfirm.html]
 [test_browserElement_oop_PurgeHistory.html]
 [test_browserElement_oop_Reload.html]
 [test_browserElement_oop_ReloadPostRequest.html]
 [test_browserElement_oop_RemoveBrowserElement.html]
 [test_browserElement_oop_ScrollEvent.html]
 [test_browserElement_oop_SecurityChange.html]
 skip-if = toolkit == 'android' || (toolkit == 'gonk' && !debug) #TIMED_OUT, bug 766586
+[test_browserElement_oop_SelectionStateBlur.html]
+skip-if = (toolkit == 'gonk') # Disabled on b2g due to bug 1097419
 [test_browserElement_oop_SendEvent.html]
 [test_browserElement_oop_SetInputMethodActive.html]
 skip-if = (os == "android")
 [test_browserElement_oop_SetVisible.html]
 [test_browserElement_oop_SetVisibleFrames.html]
 [test_browserElement_oop_SetVisibleFrames2.html]
 [test_browserElement_oop_Stop.html]
 [test_browserElement_oop_TargetBlank.html]
--- a/dom/browser-element/mochitest/mochitest.ini
+++ b/dom/browser-element/mochitest/mochitest.ini
@@ -48,16 +48,17 @@ support-files =
   browserElement_PromptConfirm.js
   browserElement_PurgeHistory.js
   browserElement_Reload.js
   browserElement_ReloadPostRequest.js
   browserElement_RemoveBrowserElement.js
   browserElement_ScrollEvent.js
   browserElement_SecurityChange.js
   browserElement_SendEvent.js
+  browserElement_SelectionStateBlur.js
   browserElement_SetInputMethodActive.js
   browserElement_SetVisible.js
   browserElement_SetVisibleFrames.js
   browserElement_SetVisibleFrames2.js
   browserElement_Stop.js
   browserElement_TargetBlank.js
   browserElement_TargetTop.js
   browserElement_Titlechange.js
@@ -179,16 +180,18 @@ skip-if = (toolkit == 'gonk' && !debug)
 [test_browserElement_inproc_PromptCheck.html]
 [test_browserElement_inproc_PromptConfirm.html]
 [test_browserElement_inproc_PurgeHistory.html]
 [test_browserElement_inproc_ReloadPostRequest.html]
 [test_browserElement_inproc_RemoveBrowserElement.html]
 [test_browserElement_inproc_ScrollEvent.html]
 [test_browserElement_inproc_SecurityChange.html]
 skip-if = toolkit == 'android' || (toolkit == 'gonk' && !debug) # android(TIMED_OUT, bug 766586) androidx86(TIMED_OUT, bug 766586)
+[test_browserElement_inproc_SelectionStateBlur.html]
+skip-if = (toolkit == 'gonk') # Disabled on b2g due to bug 1097419
 [test_browserElement_inproc_SendEvent.html]
 # The setInputMethodActive() tests will timed out on Android
 [test_browserElement_inproc_SetInputMethodActive.html]
 skip-if = (os == "android")
 [test_browserElement_inproc_SetVisible.html]
 [test_browserElement_inproc_SetVisibleFrames.html]
 [test_browserElement_inproc_SetVisibleFrames2.html]
 [test_browserElement_inproc_Stop.html]
new file mode 100644
--- /dev/null
+++ b/dom/browser-element/mochitest/test_browserElement_inproc_SelectionStateBlur.html
@@ -0,0 +1,18 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1111433
+-->
+<head>
+  <title>Test for Bug 1111433</title>
+  <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <script type="application/javascript" src="browserElementTestHelpers.js"></script>
+  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1111433">Mozilla Bug 1111433</a>
+
+<script type="application/javascript;version=1.7" src="browserElement_SelectionStateBlur.js">
+</script>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/dom/browser-element/mochitest/test_browserElement_oop_SelectionStateBlur.html
@@ -0,0 +1,18 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1111433
+-->
+<head>
+  <title>Test for Bug 1111433</title>
+  <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <script type="application/javascript" src="browserElementTestHelpers.js"></script>
+  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1111433">Mozilla Bug 1111433</a>
+
+<script type="application/javascript;version=1.7" src="browserElement_SelectionStateBlur.js">
+</script>
+</body>
+</html>
--- a/dom/camera/FallbackCameraControl.cpp
+++ b/dom/camera/FallbackCameraControl.cpp
@@ -49,25 +49,25 @@ public:
   virtual RecorderProfile* GetProfileInfo(const nsAString& aProfile) MOZ_OVERRIDE { return nullptr; }
 
   nsresult PushParameters() { return NS_ERROR_NOT_INITIALIZED; }
   nsresult PullParameters() { return NS_ERROR_NOT_INITIALIZED; }
 
 protected:
   ~FallbackCameraControl();
 
-  virtual nsresult StartPreviewImpl() { return NS_ERROR_NOT_INITIALIZED; }
-  virtual nsresult StopPreviewImpl() { return NS_ERROR_NOT_INITIALIZED; }
-  virtual nsresult AutoFocusImpl() { return NS_ERROR_NOT_INITIALIZED; }
-  virtual nsresult StartFaceDetectionImpl() { return NS_ERROR_NOT_INITIALIZED; }
-  virtual nsresult StopFaceDetectionImpl() { return NS_ERROR_NOT_INITIALIZED; }
-  virtual nsresult TakePictureImpl() { return NS_ERROR_NOT_INITIALIZED; }
+  virtual nsresult StartPreviewImpl() MOZ_OVERRIDE { return NS_ERROR_NOT_INITIALIZED; }
+  virtual nsresult StopPreviewImpl() MOZ_OVERRIDE { return NS_ERROR_NOT_INITIALIZED; }
+  virtual nsresult AutoFocusImpl() MOZ_OVERRIDE { return NS_ERROR_NOT_INITIALIZED; }
+  virtual nsresult StartFaceDetectionImpl() MOZ_OVERRIDE { return NS_ERROR_NOT_INITIALIZED; }
+  virtual nsresult StopFaceDetectionImpl() MOZ_OVERRIDE { return NS_ERROR_NOT_INITIALIZED; }
+  virtual nsresult TakePictureImpl() MOZ_OVERRIDE { return NS_ERROR_NOT_INITIALIZED; }
   virtual nsresult StartRecordingImpl(DeviceStorageFileDescriptor* aFileDescriptor,
                                       const StartRecordingOptions* aOptions = nullptr) MOZ_OVERRIDE
                                         { return NS_ERROR_NOT_INITIALIZED; }
-  virtual nsresult StopRecordingImpl() { return NS_ERROR_NOT_INITIALIZED; }
-  virtual nsresult PushParametersImpl() { return NS_ERROR_NOT_INITIALIZED; }
-  virtual nsresult PullParametersImpl() { return NS_ERROR_NOT_INITIALIZED; }
+  virtual nsresult StopRecordingImpl() MOZ_OVERRIDE { return NS_ERROR_NOT_INITIALIZED; }
+  virtual nsresult PushParametersImpl() MOZ_OVERRIDE { return NS_ERROR_NOT_INITIALIZED; }
+  virtual nsresult PullParametersImpl() MOZ_OVERRIDE { return NS_ERROR_NOT_INITIALIZED; }
 
 private:
   FallbackCameraControl(const FallbackCameraControl&) MOZ_DELETE;
   FallbackCameraControl& operator=(const FallbackCameraControl&) MOZ_DELETE;
 };
--- a/dom/cellbroadcast/ipc/CellBroadcastParent.h
+++ b/dom/cellbroadcast/ipc/CellBroadcastParent.h
@@ -23,16 +23,16 @@ public:
   NS_DECL_NSICELLBROADCASTLISTENER
 
   bool Init();
 
 private:
   // MOZ_FINAL suppresses -Werror,-Wdelete-non-virtual-dtor
   ~CellBroadcastParent() {};
 
-  virtual void ActorDestroy(ActorDestroyReason aWhy);
+  virtual void ActorDestroy(ActorDestroyReason aWhy) MOZ_OVERRIDE;
 };
 
 } // namespace cellbroadcast
 } // namespace dom
 } // namespace mozilla
 
-#endif // mozilla_dom_cellbroadcast_CellBroadcastParent_h
\ No newline at end of file
+#endif // mozilla_dom_cellbroadcast_CellBroadcastParent_h
--- a/dom/crypto/CryptoKey.h
+++ b/dom/crypto/CryptoKey.h
@@ -134,17 +134,17 @@ public:
   // Note: GetPrivateKey and GetPublicKey return copies of the internal
   // key handles, which the caller must free with SECKEY_DestroyPrivateKey
   // or SECKEY_DestroyPublicKey.
   const CryptoBuffer& GetSymKey() const;
   SECKEYPrivateKey* GetPrivateKey() const;
   SECKEYPublicKey* GetPublicKey() const;
 
   // For nsNSSShutDownObject
-  virtual void virtualDestroyNSSReference();
+  virtual void virtualDestroyNSSReference() MOZ_OVERRIDE;
   void destructorSafeDestroyNSSReference();
 
   // Serialization and deserialization convenience methods
   // Note:
   // 1. The inputs aKeyData are non-const only because the NSS import
   //    functions lack the const modifier.  They should not be modified.
   // 2. All of the NSS key objects returned need to be freed by the caller.
   static SECKEYPrivateKey* PrivateKeyFromPkcs8(CryptoBuffer& aKeyData,
--- a/dom/crypto/WebCryptoTask.cpp
+++ b/dom/crypto/WebCryptoTask.cpp
@@ -2099,22 +2099,24 @@ private:
 
     // This doesn't leak, because the SECItem* returned by PK11_GetKeyData
     // just refers to a buffer managed by symKey.  The assignment copies the
     // data, so mKeyData manages one copy, while symKey manages another.
     ATTEMPT_BUFFER_ASSIGN(mKeyData, PK11_GetKeyData(symKey));
     return NS_OK;
   }
 
-  virtual void Resolve() {
+  virtual void Resolve() MOZ_OVERRIDE
+  {
     mKey->SetSymKey(mKeyData);
     mResultPromise->MaybeResolve(mKey);
   }
 
-  virtual void Cleanup() {
+  virtual void Cleanup() MOZ_OVERRIDE
+  {
     mKey = nullptr;
   }
 };
 
 class GenerateAsymmetricKeyTask : public WebCryptoTask
 {
 public:
   GenerateAsymmetricKeyTask(JSContext* aCx,
@@ -2739,17 +2741,18 @@ private:
 
       NS_ConvertUTF16toUTF8 utf8(json);
       mResult.Assign((const uint8_t*) utf8.BeginReading(), utf8.Length());
     }
 
     return NS_OK;
   }
 
-  virtual void Resolve() MOZ_OVERRIDE {
+  virtual void Resolve() MOZ_OVERRIDE
+  {
     mTask->SetData(mResult);
     mTask->DispatchWithPromise(mResultPromise);
     mResolved = true;
   }
 
   virtual void Cleanup() MOZ_OVERRIDE
   {
     if (mTask && !mResolved) {
@@ -2772,17 +2775,18 @@ public:
     , mTask(aTask)
     , mResolved(false)
   {}
 
 private:
   nsRefPtr<ImportKeyTask> mTask;
   bool mResolved;
 
-  virtual void Resolve() MOZ_OVERRIDE {
+  virtual void Resolve() MOZ_OVERRIDE
+  {
     mTask->SetKeyData(KeyEncryptTask::mResult);
     mTask->DispatchWithPromise(KeyEncryptTask::mResultPromise);
     mResolved = true;
   }
 
   virtual void Cleanup() MOZ_OVERRIDE
   {
     if (mTask && !mResolved) {
--- a/dom/datastore/DataStoreService.cpp
+++ b/dom/datastore/DataStoreService.cpp
@@ -1000,19 +1000,22 @@ DataStoreService::GetDataStoresResolve(n
       return;
     }
 
     JS::Rooted<JSObject*> dataStoreJS(cx, xpcwrappedjs->GetJSObject());
     if (NS_WARN_IF(!dataStoreJS)) {
       return;
     }
 
+    nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(aWindow);
+    MOZ_ASSERT(global);
+
     JSAutoCompartment ac(cx, dataStoreJS);
     nsRefPtr<DataStoreImpl> dataStoreObj = new DataStoreImpl(dataStoreJS,
-                                                             aWindow);
+                                                             global);
 
     nsRefPtr<DataStore> exposedStore = new DataStore(aWindow);
 
     ErrorResult error;
     exposedStore->SetDataStoreImpl(*dataStoreObj, error);
     if (error.Failed()) {
       return;
     }
--- a/dom/devicestorage/nsDeviceStorage.cpp
+++ b/dom/devicestorage/nsDeviceStorage.cpp
@@ -2197,31 +2197,31 @@ nsDOMDeviceStorageCursor::GetTypes(nsIAr
   nsTArray<nsString> emptyOptions;
   return nsContentPermissionUtils::CreatePermissionArray(type,
                                                          NS_LITERAL_CSTRING("read"),
                                                          emptyOptions,
                                                          aTypes);
 }
 
 NS_IMETHODIMP
-nsDOMDeviceStorageCursor::GetPrincipal(nsIPrincipal * *aRequestingPrincipal)
+nsDOMDeviceStorageCursor::GetPrincipal(nsIPrincipal** aRequestingPrincipal)
 {
   NS_IF_ADDREF(*aRequestingPrincipal = mPrincipal);
   return NS_OK;
 }
 
 NS_IMETHODIMP
-nsDOMDeviceStorageCursor::GetWindow(nsIDOMWindow * *aRequestingWindow)
+nsDOMDeviceStorageCursor::GetWindow(nsIDOMWindow** aRequestingWindow)
 {
   NS_IF_ADDREF(*aRequestingWindow = GetOwner());
   return NS_OK;
 }
 
 NS_IMETHODIMP
-nsDOMDeviceStorageCursor::GetElement(nsIDOMElement * *aRequestingElement)
+nsDOMDeviceStorageCursor::GetElement(nsIDOMElement** aRequestingElement)
 {
   *aRequestingElement = nullptr;
   return NS_OK;
 }
 
 NS_IMETHODIMP
 nsDOMDeviceStorageCursor::Cancel()
 {
@@ -2895,29 +2895,29 @@ public:
     MOZ_ASSERT(mRequest);
     MOZ_ASSERT(mDSFileDescriptor);
   }
 
   NS_DECL_CYCLE_COLLECTING_ISUPPORTS
   NS_DECL_CYCLE_COLLECTION_CLASS_AMBIGUOUS(DeviceStorageRequest,
                                            nsIContentPermissionRequest)
 
-  NS_IMETHOD Run()
+  NS_IMETHOD Run() MOZ_OVERRIDE
   {
     MOZ_ASSERT(NS_IsMainThread());
 
     if (mozilla::Preferences::GetBool("device.storage.prompt.testing", false)) {
       Allow(JS::UndefinedHandleValue);
       return NS_OK;
     }
 
     return nsContentPermissionUtils::AskPermission(this, mWindow);
   }
 
-  NS_IMETHODIMP GetTypes(nsIArray** aTypes)
+  NS_IMETHODIMP GetTypes(nsIArray** aTypes) MOZ_OVERRIDE
   {
     nsCString type;
     nsresult rv =
       DeviceStorageTypeChecker::GetPermissionForType(mFile->mStorageType, type);
     if (NS_FAILED(rv)) {
       return rv;
     }
 
@@ -2927,43 +2927,43 @@ public:
     if (NS_FAILED(rv)) {
       return rv;
     }
 
     nsTArray<nsString> emptyOptions;
     return nsContentPermissionUtils::CreatePermissionArray(type, access, emptyOptions, aTypes);
   }
 
-  NS_IMETHOD GetPrincipal(nsIPrincipal * *aRequestingPrincipal)
+  NS_IMETHOD GetPrincipal(nsIPrincipal * *aRequestingPrincipal) MOZ_OVERRIDE
   {
     NS_IF_ADDREF(*aRequestingPrincipal = mPrincipal);
     return NS_OK;
   }
 
-  NS_IMETHOD GetWindow(nsIDOMWindow * *aRequestingWindow)
+  NS_IMETHOD GetWindow(nsIDOMWindow * *aRequestingWindow) MOZ_OVERRIDE
   {
     NS_IF_ADDREF(*aRequestingWindow = mWindow);
     return NS_OK;
   }
 
-  NS_IMETHOD GetElement(nsIDOMElement * *aRequestingElement)
+  NS_IMETHOD GetElement(nsIDOMElement * *aRequestingElement) MOZ_OVERRIDE
   {
     *aRequestingElement = nullptr;
     return NS_OK;
   }
 
-  NS_IMETHOD Cancel()
+  NS_IMETHOD Cancel() MOZ_OVERRIDE
   {
     nsCOMPtr<nsIRunnable> event
       = new PostErrorEvent(mRequest.forget(),
                            POST_ERROR_EVENT_PERMISSION_DENIED);
     return NS_DispatchToMainThread(event);
   }
 
-  NS_IMETHOD Allow(JS::HandleValue aChoices)
+  NS_IMETHOD Allow(JS::HandleValue aChoices) MOZ_OVERRIDE
   {
     MOZ_ASSERT(NS_IsMainThread());
     MOZ_ASSERT(aChoices.isUndefined());
 
     if (!mRequest) {
       return NS_ERROR_FAILURE;
     }
 
--- a/dom/geolocation/nsGeolocation.cpp
+++ b/dom/geolocation/nsGeolocation.cpp
@@ -128,17 +128,17 @@ class GeolocationSettingsCallback : publ
 
 public:
   NS_DECL_ISUPPORTS
 
   GeolocationSettingsCallback() {
     MOZ_COUNT_CTOR(GeolocationSettingsCallback);
   }
 
-  NS_IMETHOD Handle(const nsAString& aName, JS::Handle<JS::Value> aResult)
+  NS_IMETHOD Handle(const nsAString& aName, JS::Handle<JS::Value> aResult) MOZ_OVERRIDE
   {
     MOZ_ASSERT(NS_IsMainThread());
 
     if (aName.EqualsASCII(GEO_SETTINGS_ENABLED)) {
       // The geolocation is enabled by default:
       bool value = true;
       if (aResult.isBoolean()) {
         value = aResult.toBoolean();
@@ -154,17 +154,17 @@ public:
       if (gs) {
         gs->HandleGeolocationSettingsChange(aName, aResult);
       }
     }
 
     return NS_OK;
   }
 
-  NS_IMETHOD HandleError(const nsAString& aName)
+  NS_IMETHOD HandleError(const nsAString& aName) MOZ_OVERRIDE
   {
     if (aName.EqualsASCII(GEO_SETTINGS_ENABLED)) {
       GPSLOG("Unable to get value for '" GEO_SETTINGS_ENABLED "'");
 
       // Default it's enabled:
       MozSettingValue(true);
     } else {
       nsRefPtr<nsGeolocationSettings> gs = nsGeolocationSettings::GetGeolocationSettings();
--- a/dom/indexedDB/ActorsParent.cpp
+++ b/dom/indexedDB/ActorsParent.cpp
@@ -5205,17 +5205,17 @@ public:
     MOZ_ASSERT(NS_IsMainThread());
 
     return mShutdownRequested;
   }
 
   void
   NoteBackgroundThread(nsIEventTarget* aBackgroundThread);
 
-  NS_INLINE_DECL_REFCOUNTING(QuotaClient)
+  NS_INLINE_DECL_REFCOUNTING(QuotaClient, MOZ_OVERRIDE)
 
   virtual mozilla::dom::quota::Client::Type
   GetType() MOZ_OVERRIDE;
 
   virtual nsresult
   InitOrigin(PersistenceType aPersistenceType,
              const nsACString& aGroup,
              const nsACString& aOrigin,
--- a/dom/media/mediasource/MediaSource.cpp
+++ b/dom/media/mediasource/MediaSource.cpp
@@ -14,20 +14,24 @@
 #include "mozilla/ErrorResult.h"
 #include "mozilla/FloatingPoint.h"
 #include "mozilla/Preferences.h"
 #include "mozilla/dom/BindingDeclarations.h"
 #include "mozilla/dom/HTMLMediaElement.h"
 #include "mozilla/dom/TimeRanges.h"
 #include "mozilla/mozalloc.h"
 #include "nsContentTypeParser.h"
+#include "nsContentUtils.h"
 #include "nsDebug.h"
 #include "nsError.h"
+#include "nsIEffectiveTLDService.h"
 #include "nsIRunnable.h"
 #include "nsIScriptObjectPrincipal.h"
+#include "nsIURI.h"
+#include "nsNetCID.h"
 #include "nsPIDOMWindow.h"
 #include "nsString.h"
 #include "nsThreadUtils.h"
 #include "prlog.h"
 
 struct JSContext;
 class JSObject;
 
@@ -301,16 +305,59 @@ MediaSource::IsTypeSupported(const Globa
 {
   MOZ_ASSERT(NS_IsMainThread());
   nsresult rv = mozilla::IsTypeSupported(aType);
   MSE_API("MediaSource::IsTypeSupported(aType=%s)%s",
           NS_ConvertUTF16toUTF8(aType).get(), rv == NS_OK ? "" : " [not supported]");
   return NS_SUCCEEDED(rv);
 }
 
+/* static */ bool
+MediaSource::Enabled(JSContext* cx, JSObject* aGlobal)
+{
+  MOZ_ASSERT(NS_IsMainThread());
+
+  // Don't use aGlobal across Preferences stuff, which the static
+  // analysis thinks can GC.
+  JS::Rooted<JSObject*> global(cx, aGlobal);
+
+  bool enabled = Preferences::GetBool("media.mediasource.enabled");
+  if (!enabled) {
+    return false;
+  }
+
+  // Check whether it's enabled everywhere or just YouTube.
+  bool restrict = Preferences::GetBool("media.mediasource.youtubeonly", false);
+  if (!restrict) {
+    return true;
+  }
+
+  // We want to restrict to YouTube only.  We define that as the
+  // origin being https://*.youtube.com.
+  nsIPrincipal* principal = nsContentUtils::ObjectPrincipal(global);
+  nsCOMPtr<nsIURI> uri;
+  if (NS_FAILED(principal->GetURI(getter_AddRefs(uri))) || !uri) {
+    return false;
+  }
+
+  bool isHttps = false;
+  if (NS_FAILED(uri->SchemeIs("https", &isHttps)) || !isHttps) {
+    return false;
+  }
+
+  nsCOMPtr<nsIEffectiveTLDService> tldServ =
+    do_GetService(NS_EFFECTIVETLDSERVICE_CONTRACTID);
+  NS_ENSURE_TRUE(tldServ, false);
+
+  nsAutoCString eTLDplusOne;
+  return
+    NS_SUCCEEDED(tldServ->GetBaseDomain(uri, 0, eTLDplusOne)) &&
+    eTLDplusOne.EqualsLiteral("youtube.com");
+}
+
 bool
 MediaSource::Attach(MediaSourceDecoder* aDecoder)
 {
   MOZ_ASSERT(NS_IsMainThread());
   MSE_DEBUG("MediaSource(%p)::Attach(aDecoder=%p) owner=%p", this, aDecoder, aDecoder->GetOwner());
   MOZ_ASSERT(aDecoder);
   MOZ_ASSERT(aDecoder->GetOwner());
   if (mReadyState != MediaSourceReadyState::Closed) {
--- a/dom/media/mediasource/MediaSource.h
+++ b/dom/media/mediasource/MediaSource.h
@@ -55,16 +55,18 @@ public:
   double Duration();
   void SetDuration(double aDuration, ErrorResult& aRv);
 
   already_AddRefed<SourceBuffer> AddSourceBuffer(const nsAString& aType, ErrorResult& aRv);
   void RemoveSourceBuffer(SourceBuffer& aSourceBuffer, ErrorResult& aRv);
 
   void EndOfStream(const Optional<MediaSourceEndOfStreamError>& aError, ErrorResult& aRv);
   static bool IsTypeSupported(const GlobalObject&, const nsAString& aType);
+
+  static bool Enabled(JSContext* cx, JSObject* aGlobal);
   /** End WebIDL Methods. */
 
   NS_DECL_ISUPPORTS_INHERITED
   NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(MediaSource, DOMEventTargetHelper)
   NS_DECLARE_STATIC_IID_ACCESSOR(MOZILLA_DOM_MEDIASOURCE_IMPLEMENTATION_IID)
 
   nsPIDOMWindow* GetParentObject() const;
 
--- a/dom/media/mediasource/test/crashtests/1005366.html
+++ b/dom/media/mediasource/test/crashtests/1005366.html
@@ -1,16 +1,17 @@
 <!DOCTYPE html>
 <html>
 <head>
 <meta charset="UTF-8">
 <script>
 
 /*
 user_pref("media.mediasource.enabled", true);
+user_pref("media.mediasource.youtubeonly", false);
 */
 
 function boom()
 {
     var source = new window.MediaSource();
     var videoElement = document.createElementNS('http://www.w3.org/1999/xhtml', 'video');
     videoElement.src = URL.createObjectURL(source);
 
--- a/dom/media/mediasource/test/crashtests/1059035.html
+++ b/dom/media/mediasource/test/crashtests/1059035.html
@@ -1,15 +1,16 @@
 <!DOCTYPE html>
 <html>
 <head>
 <script>
 
 /*
 user_pref("media.mediasource.enabled", true);
+user_pref("media.mediasource.youtubeonly", false);
 */
 
 function boom()
 {
     var mediaSource = new MediaSource();
     var htmlAudio = document.createElement("audio");
     htmlAudio.src = URL.createObjectURL(mediaSource);
 
--- a/dom/media/mediasource/test/crashtests/crashtests.list
+++ b/dom/media/mediasource/test/crashtests/crashtests.list
@@ -1,4 +1,4 @@
-test-pref(media.mediasource.enabled,true) load 926665.html
-test-pref(media.mediasource.enabled,true) load 931388.html
-test-pref(media.mediasource.enabled,true) load 1005366.html
-test-pref(media.mediasource.enabled,true) load 1059035.html
+test-pref(media.mediasource.enabled,true) test-pref(media.mediasource.youtubeonly,false) load 926665.html
+test-pref(media.mediasource.enabled,true) test-pref(media.mediasource.youtubeonly,false) load 931388.html
+test-pref(media.mediasource.enabled,true) test-pref(media.mediasource.youtubeonly,false) load 1005366.html
+test-pref(media.mediasource.enabled,true) test-pref(media.mediasource.youtubeonly,false) load 1059035.html
--- a/dom/media/mediasource/test/mediasource.js
+++ b/dom/media/mediasource/test/mediasource.js
@@ -12,17 +12,20 @@ function runWithMSE(testFunction) {
     SimpleTest.registerCleanupFunction(function () {
       el.parentNode.removeChild(el);
     });
 
     testFunction(ms, el);
   }
 
   addLoadEvent(function () {
-    SpecialPowers.pushPrefEnv({"set": [[ "media.mediasource.enabled", true ]]},
+    SpecialPowers.pushPrefEnv({"set": [
+	[ "media.mediasource.enabled", true ],
+	[ "media.mediasource.youtubeonly", false ],
+    ]},
                               bootstrapTest);
   });
 }
 
 function fetchWithXHR(uri, onLoadFunction) {
   var xhr = new XMLHttpRequest();
   xhr.open("GET", uri, true);
   xhr.responseType = "arraybuffer";
--- a/dom/media/mediasource/test/test_MediaSource_disabled.html
+++ b/dom/media/mediasource/test/test_MediaSource_disabled.html
@@ -14,15 +14,20 @@ SimpleTest.waitForExplicitFinish();
 function test() {
   ok(!window.MediaSource && !window.SourceBuffer && !window.SourceBufferList,
      "MediaSource should be hidden behind a pref");
   SimpleTest.doesThrow(() => new MediaSource,
                        "MediaSource should be hidden behind a pref");
   SimpleTest.finish();
 }
 
-SpecialPowers.pushPrefEnv({"set": [["media.mediasource.enabled", false]]},
+SpecialPowers.pushPrefEnv({"set":
+    [
+      ["media.mediasource.enabled", false],
+      ["media.mediasource.youtubeonly", false],
+    ]
+  },
                           test);
 
 </script>
 </pre>
 </body>
 </html>
--- a/dom/media/test/test_VideoPlaybackQuality.html
+++ b/dom/media/test/test_VideoPlaybackQuality.html
@@ -46,14 +46,19 @@ function test() {
       is(vpq.corruptedVideoFrames, 0, "corruptedVideoFrames should be 0");
 
       SimpleTest.finish();
     });
   });
 }
 
 addLoadEvent(function() {
-  SpecialPowers.pushPrefEnv({"set": [["media.mediasource.enabled", true]]}, test);
+  SpecialPowers.pushPrefEnv({"set":
+    [
+      ["media.mediasource.enabled", true],
+      ["media.mediasource.youtubeonly", false],
+    ]
+  }, test);
 });
 </script>
 </pre>
 </body>
 </html>
--- a/dom/media/test/test_VideoPlaybackQuality_disabled.html
+++ b/dom/media/test/test_VideoPlaybackQuality_disabled.html
@@ -20,14 +20,19 @@ function test() {
   } catch (e) {
     accessThrows = true;
   }
   ok(accessThrows, "getVideoPlaybackQuality should be hidden behind a pref");
   SimpleTest.finish();
 }
 
 addLoadEvent(function() {
-  SpecialPowers.pushPrefEnv({"set": [["media.mediasource.enabled", false]]}, test);
+  SpecialPowers.pushPrefEnv({"set":
+    [
+     ["media.mediasource.enabled", false],
+     ["media.mediasource.youtubeonly", false],
+    ]
+  }, test);
 });
 </script>
 </pre>
 </body>
 </html>
--- a/dom/media/test/test_eme_canvas_blocked.html
+++ b/dom/media/test/test_eme_canvas_blocked.html
@@ -44,16 +44,17 @@ function startTest(test, token)
 }
 
 function beginTest() {
   manager.runTests(gEMETests, startTest);
 }
 
 var prefs = [
   [ "media.mediasource.enabled", true ],
+  [ "media.mediasource.youtubeonly", false ],
   [ "media.mediasource.mp4.enabled", true ],
 ];
 
 if (/Linux/.test(navigator.userAgent) ||
     !document.createElement('video').canPlayType("video/mp4")) {
   // XXX remove once we have mp4 PlatformDecoderModules on all platforms.
   prefs.push([ "media.fragmented-mp4.exposed", true ]);
   prefs.push([ "media.fragmented-mp4.use-blank-decoder", true ]);
--- a/dom/media/test/test_eme_playback.html
+++ b/dom/media/test/test_eme_playback.html
@@ -87,16 +87,17 @@ function startTest(test, token)
 }
 
 function beginTest() {
   manager.runTests(gEMETests, startTest);
 }
 
 var prefs = [
   [ "media.mediasource.enabled", true ],
+  [ "media.mediasource.youtubeonly", false ],
   [ "media.mediasource.mp4.enabled", true ],
 ];
 
 if (/Linux/.test(navigator.userAgent) ||
     !document.createElement('video').canPlayType("video/mp4")) {
   // XXX remove once we have mp4 PlatformDecoderModules on all platforms.
   prefs.push([ "media.fragmented-mp4.exposed", true ]);
   prefs.push([ "media.fragmented-mp4.use-blank-decoder", true ]);
--- a/dom/media/test/test_eme_requestKeySystemAccess.html
+++ b/dom/media/test/test_eme_requestKeySystemAccess.html
@@ -269,16 +269,17 @@ var tests = [
 ];
 
 function beginTest() {
   Promise.all(tests.map(Test)).then(function() { SimpleTest.finish(); });
 }
 
 var prefs = [
   [ "media.mediasource.enabled", true ],
+  [ "media.mediasource.youtubeonly", false ],
   [ "media.mediasource.mp4.enabled", true ],
 ];
 
 if (/Linux/.test(navigator.userAgent) ||
     !document.createElement('video').canPlayType("video/mp4")) {
   // XXX remove once we have mp4 PlatformDecoderModules on all platforms.
   prefs.push([ "media.fragmented-mp4.exposed", true ]);
   prefs.push([ "media.fragmented-mp4.use-blank-decoder", true ]);
--- a/dom/media/test/test_eme_stream_capture_blocked.html
+++ b/dom/media/test/test_eme_stream_capture_blocked.html
@@ -76,16 +76,17 @@ function startTest(test, token)
 }
 
 function beginTest() {
   manager.runTests(gEMETests, startTest);
 }
 
 var prefs = [
   [ "media.mediasource.enabled", true ],
+  [ "media.mediasource.youtubeonly", false ],
   [ "media.mediasource.mp4.enabled", true ],
 ];
 
 if (/Linux/.test(navigator.userAgent) ||
     !document.createElement('video').canPlayType("video/mp4")) {
   // XXX remove once we have mp4 PlatformDecoderModules on all platforms.
   prefs.push([ "media.fragmented-mp4.exposed", true ]);
   prefs.push([ "media.fragmented-mp4.use-blank-decoder", true ]);
--- a/dom/media/webaudio/OscillatorNode.cpp
+++ b/dom/media/webaudio/OscillatorNode.cpp
@@ -18,70 +18,30 @@ NS_IMPL_CYCLE_COLLECTION_INHERITED(Oscil
                                    mPeriodicWave, mFrequency, mDetune)
 
 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(OscillatorNode)
 NS_INTERFACE_MAP_END_INHERITING(AudioNode)
 
 NS_IMPL_ADDREF_INHERITED(OscillatorNode, AudioNode)
 NS_IMPL_RELEASE_INHERITED(OscillatorNode, AudioNode)
 
-static const float sLeakTriangle = 0.995f;
-static const float sLeak = 0.999f;
-
-class DCBlocker
-{
-public:
-  // These are sane defauts when the initial mPhase is zero
-  explicit DCBlocker(float aLastInput = 0.0f,
-            float aLastOutput = 0.0f,
-            float aPole = 0.995)
-    :mLastInput(aLastInput),
-     mLastOutput(aLastOutput),
-     mPole(aPole)
-  {
-    MOZ_ASSERT(aPole > 0);
-  }
-
-  inline float Process(float aInput)
-  {
-    float out;
-
-    out = mLastOutput * mPole + aInput - mLastInput;
-    mLastOutput = out;
-    mLastInput = aInput;
-
-    return out;
-  }
-private:
-  float mLastInput;
-  float mLastOutput;
-  float mPole;
-};
-
-
 class OscillatorNodeEngine : public AudioNodeEngine
 {
 public:
   OscillatorNodeEngine(AudioNode* aNode, AudioDestinationNode* aDestination)
     : AudioNodeEngine(aNode)
     , mSource(nullptr)
     , mDestination(static_cast<AudioNodeStream*> (aDestination->Stream()))
     , mStart(-1)
     , mStop(STREAM_TIME_MAX)
     // Keep the default values in sync with OscillatorNode::OscillatorNode.
     , mFrequency(440.f)
     , mDetune(0.f)
     , mType(OscillatorType::Sine)
     , mPhase(0.)
-    // mSquare, mTriangle, and mSaw are not used for default type "sine".
-    // They are initialized if and when switching to the OscillatorTypes that
-    // use them.
-    // mFinalFrequency, mNumberOfHarmonics, mSignalPeriod, mAmplitudeAtZero,
-    // mPhaseIncrement, and mPhaseWrap are initialized in
-    // UpdateParametersIfNeeded() when mRecomputeParameters is set.
     , mRecomputeParameters(true)
     , mCustomLength(0)
   {
   }
 
   void SetSourceStream(AudioNodeStream* aSource)
   {
     mSource = aSource;
@@ -127,51 +87,37 @@ public:
   }
 
   virtual void SetInt32Parameter(uint32_t aIndex, int32_t aParam) MOZ_OVERRIDE
   {
     switch (aIndex) {
       case TYPE:
         // Set the new type.
         mType = static_cast<OscillatorType>(aParam);
-        if (mType != OscillatorType::Custom) {
+        if (mType == OscillatorType::Sine) {
           // Forget any previous custom data.
           mCustomLength = 0;
           mCustom = nullptr;
           mPeriodicWave = nullptr;
           mRecomputeParameters = true;
         }
-        // Update BLIT integrators with the new initial conditions.
         switch (mType) {
           case OscillatorType::Sine:
             mPhase = 0.0;
             break;
           case OscillatorType::Square:
-            mPhase = 0.0;
-            // Initial integration condition is -0.5, because our
-            // square has 50% duty cycle.
-            mSquare = -0.5;
+            mPeriodicWave = WebCore::PeriodicWave::createSquare(mSource->SampleRate());
             break;
           case OscillatorType::Triangle:
-            // Initial mPhase and related integration condition so the
-            // triangle is in the middle of the first upward slope.
-            // XXX actually do the maths and put the right number here.
-            mPhase = (float)(M_PI / 2);
-            mSquare = 0.5;
-            mTriangle = 0.0;
+            mPeriodicWave = WebCore::PeriodicWave::createTriangle(mSource->SampleRate());
             break;
           case OscillatorType::Sawtooth:
-            // Initial mPhase so the oscillator starts at the
-            // middle of the ramp, per spec.
-            mPhase = (float)(M_PI / 2);
-            // mSaw = 0 when mPhase = pi/2.
-            mSaw = 0.0;
+            mPeriodicWave = WebCore::PeriodicWave::createSawtooth(mSource->SampleRate());
             break;
           case OscillatorType::Custom:
-            // Custom waveforms don't use BLIT.
             break;
           default:
             NS_ERROR("Bad OscillatorNodeEngine type parameter.");
         }
         // End type switch.
         break;
       case PERIODICWAVE:
         MOZ_ASSERT(aParam >= 0, "negative custom array length");
@@ -190,68 +136,54 @@ public:
     MOZ_ASSERT(mCustom->GetChannels() == 2,
                "PeriodicWave should have sent two channels");
     mPeriodicWave = WebCore::PeriodicWave::create(mSource->SampleRate(),
     mCustom->GetData(0), mCustom->GetData(1), mCustomLength);
   }
 
   void IncrementPhase()
   {
+    const float twoPiFloat = float(2 * M_PI);
     mPhase += mPhaseIncrement;
-    if (mPhase > mPhaseWrap) {
-      mPhase -= mPhaseWrap;
+    if (mPhase > twoPiFloat) {
+      mPhase -= twoPiFloat;
+    } else if (mPhase < -twoPiFloat) {
+      mPhase += twoPiFloat;
     }
   }
 
-  // Square and triangle are using a bipolar band-limited impulse train, saw is
-  // using a normal band-limited impulse train.
-  bool UsesBipolarBLIT() {
-    return mType == OscillatorType::Square || mType == OscillatorType::Triangle;
-  }
-
   void UpdateParametersIfNeeded(StreamTime ticks, size_t count)
   {
     double frequency, detune;
 
+    // Shortcut if frequency-related AudioParam are not automated, and we
+    // already have computed the frequency information and related parameters.
+    if (!ParametersMayNeedUpdate()) {
+      return;
+    }
+
     bool simpleFrequency = mFrequency.HasSimpleValue();
     bool simpleDetune = mDetune.HasSimpleValue();
 
-    // Shortcut if frequency-related AudioParam are not automated, and we
-    // already have computed the frequency information and related parameters.
-    if (simpleFrequency && simpleDetune && !mRecomputeParameters) {
-      return;
-    }
-
     if (simpleFrequency) {
       frequency = mFrequency.GetValue();
     } else {
       frequency = mFrequency.GetValueAtTime(ticks, count);
     }
     if (simpleDetune) {
       detune = mDetune.GetValue();
     } else {
       detune = mDetune.GetValueAtTime(ticks, count);
     }
 
     mFinalFrequency = frequency * pow(2., detune / 1200.);
+    float signalPeriod = mSource->SampleRate() / mFinalFrequency;
     mRecomputeParameters = false;
 
-    // When using bipolar BLIT, we divide the signal period by two, because we
-    // are using two BLIT out of phase.
-    mSignalPeriod = UsesBipolarBLIT() ? 0.5 * mSource->SampleRate() / mFinalFrequency
-                                      : mSource->SampleRate() / mFinalFrequency;
-    // Wrap the phase accordingly:
-    mPhaseWrap = UsesBipolarBLIT() || mType == OscillatorType::Sine ? 2 * M_PI
-                                   : M_PI;
-    // Even number of harmonics for bipolar blit, odd otherwise.
-    mNumberOfHarmonics = UsesBipolarBLIT() ? 2 * floor(0.5 * mSignalPeriod)
-                                           : 2 * floor(0.5 * mSignalPeriod) + 1;
-    mPhaseIncrement = mType == OscillatorType::Sine ? 2 * M_PI / mSignalPeriod
-                                                    : M_PI / mSignalPeriod;
-    mAmplitudeAtZero = mNumberOfHarmonics / mSignalPeriod;
+    mPhaseIncrement = 2 * M_PI / signalPeriod;
   }
 
   void FillBounds(float* output, StreamTime ticks,
                   uint32_t& start, uint32_t& end)
   {
     MOZ_ASSERT(output);
     static_assert(StreamTime(WEBAUDIO_BLOCK_SIZE) < UINT_MAX,
         "WEBAUDIO_BLOCK_SIZE overflows interator bounds.");
@@ -266,108 +198,32 @@ public:
     if (ticks + end > mStop) {
       end = mStop - ticks;
       for (uint32_t i = end; i < WEBAUDIO_BLOCK_SIZE; ++i) {
         output[i] = 0.0;
       }
     }
   }
 
-  float BipolarBLIT()
-  {
-    float blit;
-    float denom = sin(mPhase);
-
-    if (fabs(denom) < std::numeric_limits<float>::epsilon()) {
-      if (mPhase < 0.1f || mPhase > 2 * M_PI - 0.1f) {
-        blit = mAmplitudeAtZero;
-      } else {
-        blit = -mAmplitudeAtZero;
-      }
-    } else {
-      blit = sin(mNumberOfHarmonics * mPhase);
-      blit /= mSignalPeriod * denom;
-    }
-    return blit;
-  }
-
-  float UnipolarBLIT()
-  {
-    float blit;
-    float denom = sin(mPhase);
-
-    if (fabs(denom) <= std::numeric_limits<float>::epsilon()) {
-      blit = mAmplitudeAtZero;
-    } else {
-      blit = sin(mNumberOfHarmonics * mPhase);
-      blit /= mSignalPeriod * denom;
-    }
-
-    return blit;
-  }
-
   void ComputeSine(float * aOutput, StreamTime ticks, uint32_t aStart, uint32_t aEnd)
   {
     for (uint32_t i = aStart; i < aEnd; ++i) {
       UpdateParametersIfNeeded(ticks, i);
 
       aOutput[i] = sin(mPhase);
 
       IncrementPhase();
     }
   }
 
-  void ComputeSquare(float * aOutput, StreamTime ticks, uint32_t aStart, uint32_t aEnd)
-  {
-    for (uint32_t i = aStart; i < aEnd; ++i) {
-      UpdateParametersIfNeeded(ticks, i);
-      // Integration to get us a square. It turns out we can have a
-      // pure integrator here.
-      mSquare = mSquare * sLeak + BipolarBLIT();
-      aOutput[i] = mSquare;
-      // maybe we want to apply a gain, the wg has not decided yet
-      aOutput[i] *= 1.5;
-      IncrementPhase();
-    }
-  }
-
-  void ComputeSawtooth(float * aOutput, StreamTime ticks, uint32_t aStart, uint32_t aEnd)
+  bool ParametersMayNeedUpdate()
   {
-    float dcoffset;
-    for (uint32_t i = aStart; i < aEnd; ++i) {
-      UpdateParametersIfNeeded(ticks, i);
-      // DC offset so the Saw does not ramp up to infinity when integrating.
-      dcoffset = mFinalFrequency / mSource->SampleRate();
-      // Integrate and offset so we get mAmplitudeAtZero sawtooth. We have a
-      // very low frequency component somewhere here, but I'm not sure where.
-      mSaw = mSaw * sLeak + (UnipolarBLIT() - dcoffset);
-      // reverse the saw so we are spec compliant
-      aOutput[i] = -mSaw * 1.5;
-
-      IncrementPhase();
-    }
-  }
-
-  void ComputeTriangle(float * aOutput, StreamTime ticks, uint32_t aStart, uint32_t aEnd)
-  {
-    for (uint32_t i = aStart; i < aEnd; ++i) {
-      UpdateParametersIfNeeded(ticks, i);
-      // Integrate to get a square
-      mSquare += BipolarBLIT();
-      // Leaky integrate to get a triangle. We get too much dc offset if we don't
-      // leaky integrate here.
-      // C6 = k0 / period
-      // (period is samplingrate / frequency, k0 = (PI/2)/(2*PI)) = 0.25
-      float C6 = 0.25 / (mSource->SampleRate() / mFinalFrequency);
-      mTriangle = mTriangle * sLeakTriangle + mSquare + C6;
-      // DC Block, and scale back to [-1.0; 1.0]
-      aOutput[i] = mDCBlocker.Process(mTriangle) / (mSignalPeriod/2) * 1.5;
-
-      IncrementPhase();
-    }
+    return !mDetune.HasSimpleValue() ||
+           !mFrequency.HasSimpleValue() ||
+           mRecomputeParameters;
   }
 
   void ComputeCustom(float* aOutput,
                      StreamTime ticks,
                      uint32_t aStart,
                      uint32_t aEnd)
   {
     MOZ_ASSERT(mPeriodicWave, "No custom waveform data");
@@ -377,25 +233,34 @@ public:
     uint32_t indexMask = periodicWaveSize - 1;
     MOZ_ASSERT(periodicWaveSize && (periodicWaveSize & indexMask) == 0,
                "periodicWaveSize must be power of 2");
     float* higherWaveData = nullptr;
     float* lowerWaveData = nullptr;
     float tableInterpolationFactor;
     // Phase increment at frequency of 1 Hz.
     // mPhase runs [0,periodicWaveSize) here instead of [0,2*M_PI).
-    float basePhaseIncrement =
-      static_cast<float>(periodicWaveSize) / mSource->SampleRate();
+    float basePhaseIncrement = mPeriodicWave->rateScale();
+
+    UpdateParametersIfNeeded(ticks, aStart);
+
+    bool parametersMayNeedUpdate = ParametersMayNeedUpdate();
+    mPeriodicWave->waveDataForFundamentalFrequency(mFinalFrequency,
+                                                   lowerWaveData,
+                                                   higherWaveData,
+                                                   tableInterpolationFactor);
 
     for (uint32_t i = aStart; i < aEnd; ++i) {
-      UpdateParametersIfNeeded(ticks, i);
-      mPeriodicWave->waveDataForFundamentalFrequency(mFinalFrequency,
-                                                     lowerWaveData,
-                                                     higherWaveData,
-                                                     tableInterpolationFactor);
+      if (parametersMayNeedUpdate) {
+        mPeriodicWave->waveDataForFundamentalFrequency(mFinalFrequency,
+                                                       lowerWaveData,
+                                                       higherWaveData,
+                                                       tableInterpolationFactor);
+        UpdateParametersIfNeeded(ticks, i);
+      }
       // Bilinear interpolation between adjacent samples in each table.
       float floorPhase = floorf(mPhase);
       uint32_t j1 = floorPhase;
       j1 &= indexMask;
       uint32_t j2 = j1 + 1;
       j2 &= indexMask;
 
       float sampleInterpolationFactor = mPhase - floorPhase;
@@ -452,24 +317,18 @@ public:
     FillBounds(output, ticks, start, end);
 
     // Synthesize the correct waveform.
     switch(mType) {
       case OscillatorType::Sine:
         ComputeSine(output, ticks, start, end);
         break;
       case OscillatorType::Square:
-        ComputeSquare(output, ticks, start, end);
-        break;
       case OscillatorType::Triangle:
-        ComputeTriangle(output, ticks, start, end);
-        break;
       case OscillatorType::Sawtooth:
-        ComputeSawtooth(output, ticks, start, end);
-        break;
       case OscillatorType::Custom:
         ComputeCustom(output, ticks, start, end);
         break;
       default:
         ComputeSilence(aOutput);
     };
 
   }
@@ -495,34 +354,26 @@ public:
     return amount;
   }
 
   virtual size_t SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const MOZ_OVERRIDE
   {
     return aMallocSizeOf(this) + SizeOfExcludingThis(aMallocSizeOf);
   }
 
-  DCBlocker mDCBlocker;
   AudioNodeStream* mSource;
   AudioNodeStream* mDestination;
   StreamTime mStart;
   StreamTime mStop;
   AudioParamTimeline mFrequency;
   AudioParamTimeline mDetune;
   OscillatorType mType;
   float mPhase;
   float mFinalFrequency;
-  uint32_t mNumberOfHarmonics;
-  float mSignalPeriod;
-  float mAmplitudeAtZero;
   float mPhaseIncrement;
-  float mSquare;
-  float mTriangle;
-  float mSaw;
-  float mPhaseWrap;
   bool mRecomputeParameters;
   nsRefPtr<ThreadSharedFloatArrayBufferList> mCustom;
   uint32_t mCustomLength;
   nsAutoPtr<WebCore::PeriodicWave> mPeriodicWave;
 };
 
 OscillatorNode::OscillatorNode(AudioContext* aContext)
   : AudioNode(aContext,
--- a/dom/media/webaudio/blink/PeriodicWave.cpp
+++ b/dom/media/webaudio/blink/PeriodicWave.cpp
@@ -286,18 +286,22 @@ void PeriodicWave::generateBasicWaveform
             // Sawtooth-shaped waveform with the first half ramping from
             // zero to maximum and the second half from minimum to zero.
             a = 0;
             b = -invOmega * cos(0.5 * omega);
             break;
         case OscillatorType::Triangle:
             // Triangle-shaped waveform going from its maximum value to
             // its minimum value then back to the maximum value.
-            a = (4 - 4 * cos(0.5 * omega)) / (n * n * piFloat * piFloat);
-            b = 0;
+            a = 0;
+            if (n & 1) {
+              b = 2 * (2 / (n * piFloat) * 2 / (n * piFloat)) * ((((n - 1) >> 1) & 1) ? -1 : 1);
+            } else {
+              b = 0;
+            }
             break;
         default:
             NS_NOTREACHED("invalid oscillator type");
             a = 0;
             b = 0;
             break;
         }
 
--- a/dom/media/webaudio/test/mochitest.ini
+++ b/dom/media/webaudio/test/mochitest.ini
@@ -121,16 +121,18 @@ skip-if = android_version == '10' # bug 
 [test_mozaudiochannel.html]
 skip-if = (toolkit == 'gonk' && !debug)
 [test_nodeToParamConnection.html]
 [test_OfflineAudioContext.html]
 [test_offlineDestinationChannelCountLess.html]
 [test_offlineDestinationChannelCountMore.html]
 [test_oscillatorNode.html]
 [test_oscillatorNode2.html]
+[test_oscillatorNodeNegativeFrequency.html]
+skip-if = (toolkit == 'gonk') || (toolkit == 'android')
 [test_oscillatorNodePassThrough.html]
 [test_oscillatorNodeStart.html]
 [test_oscillatorTypeChange.html]
 [test_pannerNode.html]
 [test_pannerNode_equalPower.html]
 [test_pannerNodeAbove.html]
 [test_pannerNodeChannelCount.html]
 [test_pannerNodeHRTFSymmetry.html]
new file mode 100644
--- /dev/null
+++ b/dom/media/webaudio/test/test_oscillatorNodeNegativeFrequency.html
@@ -0,0 +1,50 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+  <title>Test the OscillatorNode when the frequency is negative</title>
+  <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <script type="text/javascript" src="webaudio.js"></script>
+  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+SimpleTest.waitForExplicitFinish();
+addLoadEvent(function() {
+
+  var types = ["sine",
+               "square",
+               "sawtooth",
+               "triangle"];
+
+  var finished = 0;
+  function finish() {
+    if (++finished == types.length) {
+      SimpleTest.finish();
+    }
+  }
+
+  types.forEach(function(t) {
+    var context = new OfflineAudioContext(1, 256, 44100);
+    var osc = context.createOscillator();
+
+    osc.frequency.value = -440;
+    osc.type = t;
+
+    osc.connect(context.destination);
+    osc.start();
+    context.startRendering().then(function(buffer) {
+      var samples = buffer.getChannelData(0);
+      // This samples the wave form in the middle of the first period, the value
+      // should be negative.
+      ok(samples[Math.floor(44100 / 440 / 4)] < 0., "Phase should be inverted when using a " + t + " waveform");
+      finish();
+    });
+  });
+});
+
+</script>
+</pre>
+</body>
+</html>
--- a/dom/mobileconnection/ipc/MobileConnectionParent.h
+++ b/dom/mobileconnection/ipc/MobileConnectionParent.h
@@ -31,17 +31,17 @@ public:
 protected:
   virtual
   ~MobileConnectionParent()
   {
     MOZ_COUNT_DTOR(MobileConnectionParent);
   }
 
   virtual void
-  ActorDestroy(ActorDestroyReason why);
+  ActorDestroy(ActorDestroyReason aWhy) MOZ_OVERRIDE;
 
   virtual bool
   RecvPMobileConnectionRequestConstructor(PMobileConnectionRequestParent* aActor,
                                           const MobileConnectionRequest& aRequest) MOZ_OVERRIDE;
 
   virtual PMobileConnectionRequestParent*
   AllocPMobileConnectionRequestParent(const MobileConnectionRequest& request) MOZ_OVERRIDE;
 
@@ -147,17 +147,17 @@ public:
 protected:
   virtual
   ~MobileConnectionRequestParent()
   {
     MOZ_COUNT_DTOR(MobileConnectionRequestParent);
   }
 
   virtual void
-  ActorDestroy(ActorDestroyReason why);
+  ActorDestroy(ActorDestroyReason aWhy) MOZ_OVERRIDE;
 
   nsresult
   SendReply(const MobileConnectionReply& aReply);
 
 private:
   nsCOMPtr<nsIMobileConnection> mMobileConnection;
   bool mLive;
 };
--- a/dom/mobilemessage/ipc/SmsParent.h
+++ b/dom/mobilemessage/ipc/SmsParent.h
@@ -39,17 +39,17 @@ protected:
 
   SmsParent();
   virtual ~SmsParent()
   {
     MOZ_COUNT_DTOR(SmsParent);
   }
 
   virtual void
-  ActorDestroy(ActorDestroyReason why);
+  ActorDestroy(ActorDestroyReason aWhy) MOZ_OVERRIDE;
 
   virtual bool
   RecvPSmsRequestConstructor(PSmsRequestParent* aActor,
                              const IPCSmsRequest& aRequest) MOZ_OVERRIDE;
 
   virtual PSmsRequestParent*
   AllocPSmsRequestParent(const IPCSmsRequest& aRequest) MOZ_OVERRIDE;
 
--- a/dom/network/Connection.h
+++ b/dom/network/Connection.h
@@ -32,17 +32,17 @@ public:
 
   NS_REALLY_FORWARD_NSIDOMEVENTTARGET(DOMEventTargetHelper)
 
   explicit Connection(nsPIDOMWindow *aWindow);
 
   void Shutdown();
 
   // For IObserver
-  void Notify(const hal::NetworkInformation& aNetworkInfo);
+  void Notify(const hal::NetworkInformation& aNetworkInfo) MOZ_OVERRIDE;
 
   // WebIDL
 
   virtual JSObject* WrapObject(JSContext* aCx) MOZ_OVERRIDE;
 
   ConnectionType Type() const { return mType; }
 
   IMPL_EVENT_HANDLER(typechange)
--- a/dom/network/TCPSocketParent.h
+++ b/dom/network/TCPSocketParent.h
@@ -47,17 +47,17 @@ class TCPSocketParent : public mozilla::
 {
 public:
   NS_DECL_NSITCPSOCKETPARENT
   NS_IMETHOD_(MozExternalRefCountType) Release() MOZ_OVERRIDE;
 
   TCPSocketParent() {}
 
   virtual bool RecvOpen(const nsString& aHost, const uint16_t& aPort,
-                        const bool& useSSL, const nsString& aBinaryType);
+                        const bool& useSSL, const nsString& aBinaryType) MOZ_OVERRIDE;
 
   virtual bool RecvStartTLS() MOZ_OVERRIDE;
   virtual bool RecvSuspend() MOZ_OVERRIDE;
   virtual bool RecvResume() MOZ_OVERRIDE;
   virtual bool RecvClose() MOZ_OVERRIDE;
   virtual bool RecvData(const SendableData& aData,
                         const uint32_t& aTrackingNumber) MOZ_OVERRIDE;
   virtual bool RecvRequestDelete() MOZ_OVERRIDE;
--- a/dom/notification/DesktopNotification.h
+++ b/dom/notification/DesktopNotification.h
@@ -145,19 +145,19 @@ class AlertServiceObserver: public nsIOb
   NS_DECL_ISUPPORTS
 
   explicit AlertServiceObserver(DesktopNotification* notification)
     : mNotification(notification) {}
 
   void Disconnect() { mNotification = nullptr; }
 
   NS_IMETHODIMP
-  Observe(nsISupports *aSubject,
-          const char *aTopic,
-          const char16_t *aData)
+  Observe(nsISupports* aSubject,
+          const char* aTopic,
+          const char16_t* aData) MOZ_OVERRIDE
   {
 
     // forward to parent
     if (mNotification) {
 #ifdef MOZ_B2G
     if (NS_FAILED(mNotification->CheckInnerWindowCorrectness()))
       return NS_ERROR_NOT_AVAILABLE;
 #endif
--- a/dom/notification/Notification.cpp
+++ b/dom/notification/Notification.cpp
@@ -59,17 +59,17 @@ public:
                     const nsAString& aTitle,
                     const nsAString& aDir,
                     const nsAString& aLang,
                     const nsAString& aBody,
                     const nsAString& aTag,
                     const nsAString& aIcon,
                     const nsAString& aData,
                     const nsAString& aBehavior,
-                    JSContext* aCx)
+                    JSContext* aCx) MOZ_OVERRIDE
   {
     MOZ_ASSERT(!aID.IsEmpty());
 
     RootedDictionary<NotificationOptions> options(aCx);
     options.mDir = Notification::StringToDirection(nsString(aDir));
     options.mLang = aLang;
     options.mBody = aBody;
     options.mTag = aTag;
@@ -94,17 +94,17 @@ public:
 
     JS::Rooted<JSObject*> notifications(aCx, mNotifications);
     if (!JS_DefineElement(aCx, notifications, mCount++, element, 0)) {
       return NS_ERROR_FAILURE;
     }
     return NS_OK;
   }
 
-  NS_IMETHOD Done(JSContext* aCx)
+  NS_IMETHOD Done(JSContext* aCx) MOZ_OVERRIDE
   {
     JSAutoCompartment ac(aCx, mGlobal);
     JS::Rooted<JS::Value> result(aCx, JS::ObjectValue(*mNotifications));
     mPromise->MaybeResolve(aCx, result);
     return NS_OK;
   }
 
 private:
--- a/dom/plugins/ipc/BrowserStreamParent.cpp
+++ b/dom/plugins/ipc/BrowserStreamParent.cpp
@@ -8,17 +8,17 @@
 #include "PluginAsyncSurrogate.h"
 #include "PluginInstanceParent.h"
 #include "nsNPAPIPlugin.h"
 
 #include "mozilla/unused.h"
 
 // How much data are we willing to send across the wire
 // in one chunk?
-static const int32_t kSendDataChunk = 0x4000;
+static const int32_t kSendDataChunk = 0xffff;
 
 namespace mozilla {
 namespace plugins {
 
 BrowserStreamParent::BrowserStreamParent(PluginInstanceParent* npp,
                                          NPStream* stream)
   : mNPP(npp)
   , mStream(stream)
--- a/dom/power/PowerManagerService.h
+++ b/dom/power/PowerManagerService.h
@@ -30,17 +30,17 @@ public:
   NS_DECL_ISUPPORTS
   NS_DECL_NSIPOWERMANAGERSERVICE
 
   static already_AddRefed<PowerManagerService> GetInstance();
 
   void Init();
 
   // Implement WakeLockObserver
-  void Notify(const hal::WakeLockInformation& aWakeLockInfo);
+  void Notify(const hal::WakeLockInformation& aWakeLockInfo) MOZ_OVERRIDE;
 
   /**
    * Acquire a wake lock on behalf of a given process (aContentParent).
    *
    * This method stands in contrast to nsIPowerManagerService::NewWakeLock,
    * which acquires a wake lock on behalf of the /current/ process.
    *
    * NewWakeLockOnBehalfOfProcess is different from NewWakeLock in that
--- a/dom/quota/QuotaManager.cpp
+++ b/dom/quota/QuotaManager.cpp
@@ -220,17 +220,17 @@ public:
   OriginClearRunnable(const OriginOrPatternString& aOriginOrPattern,
                       Nullable<PersistenceType> aPersistenceType)
   : mOriginOrPattern(aOriginOrPattern),
     mPersistenceType(aPersistenceType),
     mCallbackState(Pending)
   { }
 
   NS_IMETHOD
-  Run();
+  Run() MOZ_OVERRIDE;
 
   void
   AdvanceState()
   {
     switch (mCallbackState) {
       case Pending:
         mCallbackState = OpenAllowed;
         return;
@@ -294,17 +294,17 @@ public:
                      bool aInMozBrowserOnly,
                      const nsACString& aGroup,
                      const OriginOrPatternString& aOrigin,
                      bool aIsApp,
                      nsIURI* aURI,
                      nsIUsageCallback* aCallback);
 
   NS_IMETHOD
-  Run();
+  Run() MOZ_OVERRIDE;
 
   void
   AdvanceState()
   {
     switch (mCallbackState) {
       case Pending:
         mCallbackState = OpenAllowed;
         return;
@@ -364,17 +364,17 @@ public:
   NS_DECL_ISUPPORTS_INHERITED
 
   explicit ResetOrClearRunnable(bool aClear)
   : mCallbackState(Pending),
     mClear(aClear)
   { }
 
   NS_IMETHOD
-  Run();
+  Run() MOZ_OVERRIDE;
 
   void
   AdvanceState()
   {
     switch (mCallbackState) {
       case Pending:
         mCallbackState = OpenAllowed;
         return;
--- a/dom/system/nsDeviceSensors.h
+++ b/dom/system/nsDeviceSensors.h
@@ -28,17 +28,17 @@ class nsDeviceSensors : public nsIDevice
   typedef mozilla::dom::DeviceAccelerationInit DeviceAccelerationInit;
   typedef mozilla::dom::DeviceRotationRateInit DeviceRotationRateInit;
 public:
   NS_DECL_ISUPPORTS
   NS_DECL_NSIDEVICESENSORS
 
   nsDeviceSensors();
 
-  void Notify(const mozilla::hal::SensorData& aSensorData);
+  void Notify(const mozilla::hal::SensorData& aSensorData) MOZ_OVERRIDE;
 
 private:
   virtual ~nsDeviceSensors();
 
   // sensor -> window listener
   nsTArray<nsTArray<nsIDOMWindow*>* > mWindowListeners;
 
   void FireDOMLightEvent(mozilla::dom::EventTarget* aTarget,
--- a/dom/telephony/ipc/TelephonyParent.h
+++ b/dom/telephony/ipc/TelephonyParent.h
@@ -21,17 +21,17 @@ public:
   NS_DECL_NSITELEPHONYLISTENER
 
   TelephonyParent();
 
 protected:
   virtual ~TelephonyParent() {}
 
   virtual void
-  ActorDestroy(ActorDestroyReason why);
+  ActorDestroy(ActorDestroyReason aWhy) MOZ_OVERRIDE;
 
   virtual bool
   RecvPTelephonyRequestConstructor(PTelephonyRequestParent* aActor, const IPCTelephonyRequest& aRequest) MOZ_OVERRIDE;
 
   virtual PTelephonyRequestParent*
   AllocPTelephonyRequestParent(const IPCTelephonyRequest& aRequest) MOZ_OVERRIDE;
 
   virtual bool
@@ -93,17 +93,17 @@ public:
   NS_DECL_NSITELEPHONYCALLBACK
   NS_DECL_NSITELEPHONYDIALCALLBACK
 
 protected:
   TelephonyRequestParent();
   virtual ~TelephonyRequestParent() {}
 
   virtual void
-  ActorDestroy(ActorDestroyReason why);
+  ActorDestroy(ActorDestroyReason aWhy) MOZ_OVERRIDE;
 
   nsresult
   SendResponse(const IPCTelephonyResponse& aResponse);
 
 private:
   bool mActorDestroyed;
 };
 
--- a/dom/time/TimeManager.h
+++ b/dom/time/TimeManager.h
@@ -37,17 +37,17 @@ public:
     : mWindow(aWindow)
   {
   }
 
   nsPIDOMWindow* GetParentObject() const
   {
     return mWindow;
   }
-  JSObject* WrapObject(JSContext* aCx);
+  virtual JSObject* WrapObject(JSContext* aCx) MOZ_OVERRIDE;
 
   void Set(Date& aDate);
   void Set(double aTime);
 
 private:
   ~TimeManager() {}
 
   nsCOMPtr<nsPIDOMWindow> mWindow;
--- a/dom/tv/FakeTVService.cpp
+++ b/dom/tv/FakeTVService.cpp
@@ -191,17 +191,17 @@ public:
                          nsITVChannelData* aChannelData)
     : mTunerId(aTunerId)
     , mSourceType(aSourceType)
     , mSourceListener(aSourceListener)
     , mChannelData(aChannelData)
   {}
 
   NS_IMETHODIMP
-  Notify(nsITimer* aTimer)
+  Notify(nsITimer* aTimer) MOZ_OVERRIDE
   {
     // Notify mock EIT broadcasting.
     nsITVProgramData** programDataList =
       static_cast<nsITVProgramData **>(NS_Alloc(1 * sizeof(nsITVProgramData*)));
     programDataList[0] = new TVProgramData();
     programDataList[0]->SetEventId(NS_LITERAL_STRING("eventId"));
     programDataList[0]->SetTitle(NS_LITERAL_STRING("title"));
     programDataList[0]->SetStartTime(PR_Now() + 3600000);
@@ -237,17 +237,17 @@ public:
                        const nsAString& aSourceType,
                        nsITVSourceListener* aSourceListener)
     : mTunerId(aTunerId)
     , mSourceType(aSourceType)
     , mSourceListener(aSourceListener)
   {}
 
   NS_IMETHODIMP
-  Notify(nsITimer* aTimer)
+  Notify(nsITimer* aTimer) MOZ_OVERRIDE
   {
     return mSourceListener->NotifyChannelScanComplete(mTunerId, mSourceType);
   }
 
 private:
   ~ScanCompleteCallback() {}
 
   nsString mTunerId;
--- a/dom/webidl/HTMLVideoElement.webidl
+++ b/dom/webidl/HTMLVideoElement.webidl
@@ -43,11 +43,11 @@ partial interface HTMLVideoElement {
   readonly attribute double mozFrameDelay;
 
   // True if the video has an audio track available.
   readonly attribute boolean mozHasAudio;
 };
 
 // https://dvcs.w3.org/hg/html-media/raw-file/default/media-source/media-source.html#idl-def-HTMLVideoElement
 partial interface HTMLVideoElement {
-  [Pref="media.mediasource.enabled", NewObject]
+  [Func="mozilla::dom::MediaSource::Enabled", NewObject]
   VideoPlaybackQuality getVideoPlaybackQuality();
 };
--- a/dom/webidl/MediaSource.webidl
+++ b/dom/webidl/MediaSource.webidl
@@ -16,17 +16,17 @@ enum MediaSourceReadyState {
   "ended"
 };
 
 enum MediaSourceEndOfStreamError {
   "network",
   "decode"
 };
 
-[Constructor, Pref="media.mediasource.enabled"]
+[Constructor, Func="mozilla::dom::MediaSource::Enabled"]
 interface MediaSource : EventTarget {
   readonly attribute SourceBufferList sourceBuffers;
   readonly attribute SourceBufferList activeSourceBuffers;
   readonly attribute MediaSourceReadyState readyState;
   [SetterThrows]
   attribute unrestricted double duration;
   [NewObject, Throws]
   SourceBuffer addSourceBuffer(DOMString type);
--- a/dom/webidl/SourceBuffer.webidl
+++ b/dom/webidl/SourceBuffer.webidl
@@ -10,17 +10,17 @@
  * liability, trademark and document use rules apply.
  */
 
 enum SourceBufferAppendMode {
     "segments",
     "sequence"
 };
 
-[Pref="media.mediasource.enabled"]
+[Func="mozilla::dom::MediaSource::Enabled"]
 interface SourceBuffer : EventTarget {
   [SetterThrows]
   attribute SourceBufferAppendMode mode;
   readonly attribute boolean updating;
   [NewObject, Throws]
   readonly attribute TimeRanges buffered;
   [SetterThrows]
   attribute double timestampOffset;
--- a/dom/webidl/SourceBufferList.webidl
+++ b/dom/webidl/SourceBufferList.webidl
@@ -5,13 +5,13 @@
  *
  * The origin of this IDL file is
  * http://dvcs.w3.org/hg/html-media/raw-file/default/media-source/media-source.html
  *
  * Copyright © 2012 W3C® (MIT, ERCIM, Keio), All Rights Reserved. W3C
  * liability, trademark and document use rules apply.
  */
 
-[Pref="media.mediasource.enabled"]
+[Func="mozilla::dom::MediaSource::Enabled"]
 interface SourceBufferList : EventTarget {
   readonly attribute unsigned long length;
   getter SourceBuffer (unsigned long index);
 };
--- a/dom/webidl/VideoPlaybackQuality.webidl
+++ b/dom/webidl/VideoPlaybackQuality.webidl
@@ -5,17 +5,17 @@
  *
  * The origin of this IDL file is
  * http://dvcs.w3.org/hg/html-media/raw-file/default/media-source/media-source.html
  *
  * Copyright © 2012 W3C® (MIT, ERCIM, Keio), All Rights Reserved. W3C
  * liability, trademark and document use rules apply.
  */
 
-[Pref="media.mediasource.enabled"]
+[Func="mozilla::dom::MediaSource::Enabled"]
 interface VideoPlaybackQuality {
   readonly attribute DOMHighResTimeStamp creationTime;
   readonly attribute unsigned long totalVideoFrames;
   readonly attribute unsigned long droppedVideoFrames;
   readonly attribute unsigned long corruptedVideoFrames;
 // At Risk: readonly attribute double totalFrameDelay;
 };
 
--- a/dom/workers/WorkerPrivate.cpp
+++ b/dom/workers/WorkerPrivate.cpp
@@ -6015,24 +6015,24 @@ WorkerPrivate::GarbageCollectInternal(JS
     return;
   }
 
   if (aShrinking || aCollectChildren) {
     JSRuntime* rt = JS_GetRuntime(aCx);
     JS::PrepareForFullGC(rt);
 
     if (aShrinking) {
-      JS::ShrinkingGC(rt, JS::gcreason::DOM_WORKER);
+      JS::GCForReason(rt, GC_SHRINK, JS::gcreason::DOM_WORKER);
 
       if (!aCollectChildren) {
         LOG(("Worker %p collected idle garbage\n", this));
       }
     }
     else {
-      JS::GCForReason(rt, JS::gcreason::DOM_WORKER);
+      JS::GCForReason(rt, GC_NORMAL, JS::gcreason::DOM_WORKER);
       LOG(("Worker %p collected garbage\n", this));
     }
   }
   else {
     JS_MaybeGC(aCx);
     LOG(("Worker %p collected periodic garbage\n", this));
   }
 
--- a/dom/workers/XMLHttpRequest.cpp
+++ b/dom/workers/XMLHttpRequest.cpp
@@ -914,17 +914,18 @@ Proxy::Init()
     }
   }
 
   mXHR = new nsXMLHttpRequest();
 
   nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(ownerWindow);
   if (NS_FAILED(mXHR->Init(mWorkerPrivate->GetPrincipal(),
                            mWorkerPrivate->GetScriptContext(),
-                           global, mWorkerPrivate->GetBaseURI()))) {
+                           global, mWorkerPrivate->GetBaseURI(),
+                           mWorkerPrivate->GetLoadGroup()))) {
     mXHR = nullptr;
     return false;
   }
 
   mXHR->SetParameters(mMozAnon, mMozSystem);
 
   if (NS_FAILED(mXHR->GetUpload(getter_AddRefs(mXHRUpload)))) {
     mXHR = nullptr;
--- a/dom/xul/templates/nsXULTemplateQueryProcessorXML.cpp
+++ b/dom/xul/templates/nsXULTemplateQueryProcessorXML.cpp
@@ -168,17 +168,17 @@ nsXULTemplateQueryProcessorXML::GetDatas
     NS_ENSURE_TRUE(context, NS_OK);
 
     nsCOMPtr<nsIXMLHttpRequest> req =
         do_CreateInstance(NS_XMLHTTPREQUEST_CONTRACTID, &rv);
     NS_ENSURE_SUCCESS(rv, rv);
 
     rv = req->Init(docPrincipal, context,
                    scriptObject ? scriptObject : doc->GetScopeObject(),
-                   nullptr);
+                   nullptr, nullptr);
     NS_ENSURE_SUCCESS(rv, rv);
 
     rv = req->Open(NS_LITERAL_CSTRING("GET"), uriStr, true,
                    EmptyString(), EmptyString());
     NS_ENSURE_SUCCESS(rv, rv);
 
     nsCOMPtr<EventTarget> target(do_QueryInterface(req));
     rv = target->AddEventListener(NS_LITERAL_STRING("load"), this, false);
--- a/extensions/spellcheck/hunspell/src/mozHunspell.h
+++ b/extensions/spellcheck/hunspell/src/mozHunspell.h
@@ -97,17 +97,17 @@ public:
   nsresult Init();
 
   void LoadDictionaryList(bool aNotifyChildProcesses);
 
   // helper method for converting a word to the charset of the dictionary
   nsresult ConvertCharset(const char16_t* aStr, char ** aDst);
 
   NS_IMETHOD CollectReports(nsIHandleReportCallback* aHandleReport,
-                            nsISupports* aData, bool aAnonymize)
+                            nsISupports* aData, bool aAnonymize) MOZ_OVERRIDE
   {
     return MOZ_COLLECT_REPORT(
       "explicit/spell-check", KIND_HEAP, UNITS_BYTES, HunspellAllocator::MemoryAllocated(),
       "Memory used by the spell-checking engine.");
   }
 
 protected:
   virtual ~mozHunspell();
--- a/extensions/spellcheck/src/mozInlineSpellChecker.cpp
+++ b/extensions/spellcheck/src/mozInlineSpellChecker.cpp
@@ -495,17 +495,17 @@ class InitEditorSpellCheckCallback MOZ_F
 {
   ~InitEditorSpellCheckCallback() {}
 public:
   NS_DECL_ISUPPORTS
 
   explicit InitEditorSpellCheckCallback(mozInlineSpellChecker* aSpellChecker)
     : mSpellChecker(aSpellChecker) {}
 
-  NS_IMETHOD EditorSpellCheckDone()
+  NS_IMETHOD EditorSpellCheckDone() MOZ_OVERRIDE
   {
     return mSpellChecker ? mSpellChecker->EditorSpellCheckInited() : NS_OK;
   }
 
   void Cancel()
   {
     mSpellChecker = nullptr;
   }
@@ -1945,17 +1945,17 @@ class UpdateCurrentDictionaryCallback MO
 {
 public:
   NS_DECL_ISUPPORTS
 
   explicit UpdateCurrentDictionaryCallback(mozInlineSpellChecker* aSpellChecker,
                                            uint32_t aDisabledAsyncToken)
     : mSpellChecker(aSpellChecker), mDisabledAsyncToken(aDisabledAsyncToken) {}
 
-  NS_IMETHOD EditorSpellCheckDone()
+  NS_IMETHOD EditorSpellCheckDone() MOZ_OVERRIDE
   {
     // Ignore this callback if SetEnableRealTimeSpell(false) was called after
     // the UpdateCurrentDictionary call that triggered it.
     return mSpellChecker->mDisabledAsyncToken > mDisabledAsyncToken ?
            NS_OK :
            mSpellChecker->CurrentDictionaryUpdated();
   }
 
--- a/extensions/spellcheck/src/mozSpellChecker.h
+++ b/extensions/spellcheck/src/mozSpellChecker.h
@@ -30,30 +30,30 @@ public:
   NS_DECL_CYCLE_COLLECTING_ISUPPORTS
   NS_DECL_CYCLE_COLLECTION_CLASS(mozSpellChecker)
 
   mozSpellChecker();
 
   nsresult Init();
 
   // nsISpellChecker
-  NS_IMETHOD SetDocument(nsITextServicesDocument *aDoc, bool aFromStartofDoc);
-  NS_IMETHOD NextMisspelledWord(nsAString &aWord, nsTArray<nsString> *aSuggestions);
-  NS_IMETHOD CheckWord(const nsAString &aWord, bool *aIsMisspelled, nsTArray<nsString> *aSuggestions);
-  NS_IMETHOD Replace(const nsAString &aOldWord, const nsAString &aNewWord, bool aAllOccurrences);
-  NS_IMETHOD IgnoreAll(const nsAString &aWord);
+  NS_IMETHOD SetDocument(nsITextServicesDocument *aDoc, bool aFromStartofDoc) MOZ_OVERRIDE;
+  NS_IMETHOD NextMisspelledWord(nsAString &aWord, nsTArray<nsString> *aSuggestions) MOZ_OVERRIDE;
+  NS_IMETHOD CheckWord(const nsAString &aWord, bool *aIsMisspelled, nsTArray<nsString> *aSuggestions) MOZ_OVERRIDE;
+  NS_IMETHOD Replace(const nsAString &aOldWord, const nsAString &aNewWord, bool aAllOccurrences) MOZ_OVERRIDE;
+  NS_IMETHOD IgnoreAll(const nsAString &aWord) MOZ_OVERRIDE;
 
-  NS_IMETHOD AddWordToPersonalDictionary(const nsAString &aWord);
-  NS_IMETHOD RemoveWordFromPersonalDictionary(const nsAString &aWord);
-  NS_IMETHOD GetPersonalDictionary(nsTArray<nsString> *aWordList);
+  NS_IMETHOD AddWordToPersonalDictionary(const nsAString &aWord) MOZ_OVERRIDE;
+  NS_IMETHOD RemoveWordFromPersonalDictionary(const nsAString &aWord) MOZ_OVERRIDE;
+  NS_IMETHOD GetPersonalDictionary(nsTArray<nsString> *aWordList) MOZ_OVERRIDE;
 
-  NS_IMETHOD GetDictionaryList(nsTArray<nsString> *aDictionaryList);
-  NS_IMETHOD GetCurrentDictionary(nsAString &aDictionary);
-  NS_IMETHOD SetCurrentDictionary(const nsAString &aDictionary);
-  NS_IMETHOD CheckCurrentDictionary();
+  NS_IMETHOD GetDictionaryList(nsTArray<nsString> *aDictionaryList) MOZ_OVERRIDE;
+  NS_IMETHOD GetCurrentDictionary(nsAString &aDictionary) MOZ_OVERRIDE;
+  NS_IMETHOD SetCurrentDictionary(const nsAString &aDictionary) MOZ_OVERRIDE;
+  NS_IMETHOD CheckCurrentDictionary() MOZ_OVERRIDE;
 
   void DeleteRemoteEngine() {
     mEngine = nullptr;
   }
 
 protected:
   virtual ~mozSpellChecker();
 
--- a/gfx/layers/apz/src/APZCTreeManager.cpp
+++ b/gfx/layers/apz/src/APZCTreeManager.cpp
@@ -1,16 +1,17 @@
 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "APZCTreeManager.h"
 #include "AsyncPanZoomController.h"
 #include "Compositor.h"                 // for Compositor
+#include "HitTestingTreeNode.h"         // for HitTestingTreeNode
 #include "InputBlockState.h"            // for InputBlockState
 #include "InputData.h"                  // for InputData, etc
 #include "Layers.h"                     // for Layer, etc
 #include "mozilla/dom/Touch.h"          // for Touch
 #include "mozilla/gfx/Point.h"          // for Point
 #include "mozilla/layers/AsyncCompositionManager.h" // for ViewTransform
 #include "mozilla/layers/CompositorParent.h" // for CompositorParent, etc
 #include "mozilla/layers/LayerMetricsWrapper.h"
@@ -22,18 +23,24 @@
 #include "nsPoint.h"                    // for nsIntPoint
 #include "nsThreadUtils.h"              // for NS_IsMainThread
 #include "mozilla/gfx/Logging.h"        // for gfx::TreeLog
 #include "UnitTransforms.h"             // for ViewAs
 #include "gfxPrefs.h"                   // for gfxPrefs
 #include "OverscrollHandoffState.h"     // for OverscrollHandoffState
 #include "LayersLogging.h"              // for Stringify
 
-#define APZCTM_LOG(...)
-// #define APZCTM_LOG(...) printf_stderr("APZCTM: " __VA_ARGS__)
+#define ENABLE_APZCTM_LOGGING 0
+// #define ENABLE_APZCTM_LOGGING 1
+
+#if ENABLE_APZCTM_LOGGING
+#  define APZCTM_LOG(...) printf_stderr("APZCTM: " __VA_ARGS__)
+#else
+#  define APZCTM_LOG(...)
+#endif
 
 namespace mozilla {
 namespace layers {
 
 typedef mozilla::gfx::Point Point;
 typedef mozilla::gfx::Point4D Point4D;
 typedef mozilla::gfx::Matrix4x4 Matrix4x4;
 
@@ -52,19 +59,26 @@ struct APZCTreeManager::TreeBuildingStat
 
   // State that doesn't change as we recurse in the tree building
   CompositorParent* const mCompositor;
   const bool mIsFirstPaint;
   const uint64_t mOriginatingLayersId;
   const APZPaintLogHelper mPaintLogger;
 
   // State that is updated as we perform the tree build
-  nsTArray< nsRefPtr<AsyncPanZoomController> > mApzcsToDestroy;
+
+  // A list of nodes that need to be destroyed at the end of the tree building.
+  // This is initialized with all nodes in the old tree, and nodes are removed
+  // from it as we reuse them in the new tree.
+  nsTArray<nsRefPtr<HitTestingTreeNode>> mNodesToDestroy;
+
+  // This map is populated as we place APZCs into the new tree. Its purpose is
+  // to facilitate re-using the same APZC for different layers that scroll
+  // together (and thus have the same ScrollableLayerGuid).
   std::map<ScrollableLayerGuid, AsyncPanZoomController*> mApzcMap;
-  nsTArray<EventRegions> mEventRegions;
 };
 
 /*static*/ const ScreenMargin
 APZCTreeManager::CalculatePendingDisplayPort(
   const FrameMetrics& aFrameMetrics,
   const ParentLayerPoint& aVelocity,
   double aEstimatedPaintDuration)
 {
@@ -121,33 +135,33 @@ APZCTreeManager::GetAllowedTouchBehavior
 
 void
 APZCTreeManager::SetAllowedTouchBehavior(uint64_t aInputBlockId,
                                          const nsTArray<TouchBehaviorFlags> &aValues)
 {
   mInputQueue->SetAllowedTouchBehavior(aInputBlockId, aValues);
 }
 
-/* Flatten the tree of APZC instances into the given nsTArray */
+/* Flatten the tree of nodes into the given nsTArray */
 static void
-Collect(AsyncPanZoomController* aApzc, nsTArray< nsRefPtr<AsyncPanZoomController> >* aCollection)
+Collect(HitTestingTreeNode* aNode, nsTArray<nsRefPtr<HitTestingTreeNode>>* aCollection)
 {
-  if (aApzc) {
-    aCollection->AppendElement(aApzc);
-    Collect(aApzc->GetLastChild(), aCollection);
-    Collect(aApzc->GetPrevSibling(), aCollection);
+  if (aNode) {
+    aCollection->AppendElement(aNode);
+    Collect(aNode->GetLastChild(), aCollection);
+    Collect(aNode->GetPrevSibling(), aCollection);
   }
 }
 
 void
-APZCTreeManager::UpdatePanZoomControllerTree(CompositorParent* aCompositor,
-                                             Layer* aRoot,
-                                             bool aIsFirstPaint,
-                                             uint64_t aOriginatingLayersId,
-                                             uint32_t aPaintSequenceNumber)
+APZCTreeManager::UpdateHitTestingTree(CompositorParent* aCompositor,
+                                      Layer* aRoot,
+                                      bool aIsFirstPaint,
+                                      uint64_t aOriginatingLayersId,
+                                      uint32_t aPaintSequenceNumber)
 {
   if (AsyncPanZoomController::GetThreadAssertionsEnabled()) {
     Compositor::AssertOnCompositorThread();
   }
 
   MonitorAutoLock lock(mTreeLock);
 
   // For testing purposes, we log some data to the APZTestData associated with
@@ -170,102 +184,172 @@ APZCTreeManager::UpdatePanZoomController
   // completely different place. In scenario (a) we would want to destroy the APZC while
   // walking the layer tree and noticing that the layer/APZC is no longer there. But if
   // we do that then we run into a problem in scenario (b) because we might encounter that
   // layer later during the walk. To handle both of these we have to 'remember' that the
   // layer was not found, and then do the destroy only at the end of the tree walk after
   // we are sure that the layer was removed and not just transplanted elsewhere. Doing that
   // as part of a recursive tree walk is hard and so maintaining a list and removing
   // APZCs that are still alive is much simpler.
-  Collect(mRootApzc, &state.mApzcsToDestroy);
-  mRootApzc = nullptr;
+  Collect(mRootNode, &state.mNodesToDestroy);
+  mRootNode = nullptr;
 
   if (aRoot) {
     mApzcTreeLog << "[start]\n";
     LayerMetricsWrapper root(aRoot);
-    UpdatePanZoomControllerTree(state, root,
-                                // aCompositor is null in gtest scenarios
-                                aCompositor ? aCompositor->RootLayerTreeId() : 0,
-                                Matrix4x4(), nullptr, nullptr, nsIntRegion());
+    UpdateHitTestingTree(state, root,
+                         // aCompositor is null in gtest scenarios
+                         aCompositor ? aCompositor->RootLayerTreeId() : 0,
+                         Matrix4x4(), nullptr, nullptr);
     mApzcTreeLog << "[end]\n";
   }
-  MOZ_ASSERT(state.mEventRegions.Length() == 0);
 
-  for (size_t i = 0; i < state.mApzcsToDestroy.Length(); i++) {
-    APZCTM_LOG("Destroying APZC at %p\n", state.mApzcsToDestroy[i].get());
-    state.mApzcsToDestroy[i]->Destroy();
+  for (size_t i = 0; i < state.mNodesToDestroy.Length(); i++) {
+    APZCTM_LOG("Destroying node at %p with APZC %p\n",
+        state.mNodesToDestroy[i].get(),
+        state.mNodesToDestroy[i]->GetApzc());
+    state.mNodesToDestroy[i]->Destroy();
   }
+
+#if ENABLE_APZCTM_LOGGING
+  // Make the hit-test tree line up with the layer dump
+  printf_stderr("APZCTreeManager (%p)\n", this);
+  mRootNode->Dump("  ");
+#endif
 }
 
+// Compute the clip region to be used for a layer with an APZC. This function
+// is only called for layers which actually have scrollable metrics and an APZC.
 static nsIntRegion
-ComputeTouchSensitiveRegion(GeckoContentController* aController,
-                            const FrameMetrics& aMetrics,
-                            const nsIntRegion& aObscured)
+ComputeClipRegion(GeckoContentController* aController,
+                  const LayerMetricsWrapper& aLayer)
 {
-  // Use the composition bounds as the hit test region.
+  nsIntRegion clipRegion;
+  if (aLayer.GetClipRect()) {
+    clipRegion = nsIntRegion(*aLayer.GetClipRect());
+  } else {
+    // if there is no clip on this layer (which should only happen for the
+    // root scrollable layer in a process) fall back to using the comp
+    // bounds which should be equivalent.
+    clipRegion = nsIntRegion(ParentLayerIntRect::ToUntyped(
+        RoundedToInt(aLayer.Metrics().mCompositionBounds)));
+  }
+
   // Optionally, the GeckoContentController can provide a touch-sensitive
   // region that constrains all frames associated with the controller.
   // In this case we intersect the composition bounds with that region.
-  ParentLayerRect visible(aMetrics.mCompositionBounds);
   CSSRect touchSensitiveRegion;
   if (aController->GetTouchSensitiveRegion(&touchSensitiveRegion)) {
     // Here we assume 'touchSensitiveRegion' is in the CSS pixels of the
     // parent frame. To convert it to ParentLayer pixels, we therefore need
     // the cumulative resolution of the parent frame. We approximate this as
     // the quotient of our cumulative resolution and our pres shell resolution;
     // this approximation may not be accurate in the presence of a css-driven
     // resolution.
     LayoutDeviceToParentLayerScale parentCumulativeResolution =
-          aMetrics.GetCumulativeResolution()
-        / ParentLayerToLayerScale(aMetrics.mPresShellResolution);
-    visible = visible.Intersect(touchSensitiveRegion
-                                * aMetrics.GetDevPixelsPerCSSPixel()
-                                * parentCumulativeResolution);
+          aLayer.Metrics().GetCumulativeResolution()
+        / ParentLayerToLayerScale(aLayer.Metrics().mPresShellResolution);
+    // Not sure what rounding option is the most correct here, but if we ever
+    // figure it out we can change this. For now I'm rounding in to minimize
+    // the chances of getting a complex region.
+    nsIntRect extraClip = ParentLayerIntRect::ToUntyped(RoundedIn(
+        touchSensitiveRegion
+        * aLayer.Metrics().GetDevPixelsPerCSSPixel()
+        * parentCumulativeResolution));
+    clipRegion.AndWith(extraClip);
   }
 
-  // Not sure what rounding option is the most correct here, but if we ever
-  // figure it out we can change this. For now I'm rounding in to minimize
-  // the chances of getting a complex region.
-  nsIntRegion unobscured;
-  unobscured.Sub(ParentLayerIntRect::ToUntyped(RoundedIn(visible)), aObscured);
-  return unobscured;
+  return clipRegion;
 }
 
 void
 APZCTreeManager::PrintAPZCInfo(const LayerMetricsWrapper& aLayer,
                                const AsyncPanZoomController* apzc)
 {
   const FrameMetrics& metrics = aLayer.Metrics();
   mApzcTreeLog << "APZC " << apzc->GetGuid() << "\tcb=" << metrics.mCompositionBounds
                << "\tsr=" << metrics.GetScrollableRect()
                << (aLayer.IsScrollInfoLayer() ? "\tscrollinfo" : "")
                << (apzc->HasScrollgrab() ? "\tscrollgrab" : "") << "\t"
                << metrics.GetContentDescription().get();
 }
 
-AsyncPanZoomController*
-APZCTreeManager::PrepareAPZCForLayer(const LayerMetricsWrapper& aLayer,
+void
+APZCTreeManager::AttachNodeToTree(HitTestingTreeNode* aNode,
+                                  HitTestingTreeNode* aParent,
+                                  HitTestingTreeNode* aNextSibling)
+{
+  if (aNextSibling) {
+    aNextSibling->SetPrevSibling(aNode);
+  } else if (aParent) {
+    aParent->SetLastChild(aNode);
+  } else {
+    MOZ_ASSERT(!mRootNode);
+    mRootNode = aNode;
+    aNode->MakeRoot();
+  }
+}
+
+static EventRegions
+GetEventRegions(const LayerMetricsWrapper& aLayer)
+{
+  if (gfxPrefs::LayoutEventRegionsEnabled()) {
+    return aLayer.GetEventRegions();
+  }
+  return EventRegions(aLayer.GetVisibleRegion());
+}
+
+already_AddRefed<HitTestingTreeNode>
+APZCTreeManager::RecycleOrCreateNode(TreeBuildingState& aState,
+                                     AsyncPanZoomController* aApzc)
+{
+  // Find a node without an APZC and return it. Note that unless the layer tree
+  // actually changes, this loop should generally do an early-return on the
+  // first iteration, so it should be cheap in the common case.
+  for (size_t i = 0; i < aState.mNodesToDestroy.Length(); i++) {
+    nsRefPtr<HitTestingTreeNode> node = aState.mNodesToDestroy[i];
+    if (!node->IsPrimaryHolder()) {
+      aState.mNodesToDestroy.RemoveElement(node);
+      node->RecycleWith(aApzc);
+      return node.forget();
+    }
+  }
+  nsRefPtr<HitTestingTreeNode> node = new HitTestingTreeNode(aApzc, false);
+  return node.forget();
+}
+
+HitTestingTreeNode*
+APZCTreeManager::PrepareNodeForLayer(const LayerMetricsWrapper& aLayer,
                                      const FrameMetrics& aMetrics,
                                      uint64_t aLayersId,
                                      const gfx::Matrix4x4& aAncestorTransform,
-                                     const nsIntRegion& aObscured,
-                                     AsyncPanZoomController* aParent,
-                                     AsyncPanZoomController* aNextSibling,
+                                     HitTestingTreeNode* aParent,
+                                     HitTestingTreeNode* aNextSibling,
                                      TreeBuildingState& aState)
 {
+  bool needsApzc = true;
   if (!aMetrics.IsScrollable()) {
-    return nullptr;
+    needsApzc = false;
   }
   if (gfxPrefs::LayoutEventRegionsEnabled() && aLayer.IsScrollInfoLayer()) {
-    return nullptr;
+    needsApzc = false;
   }
 
   const CompositorParent::LayerTreeState* state = CompositorParent::GetIndirectShadowTree(aLayersId);
   if (!(state && state->mController.get())) {
-    return nullptr;
+    needsApzc = false;
+  }
+
+  nsRefPtr<HitTestingTreeNode> node = nullptr;
+  if (!needsApzc) {
+    node = RecycleOrCreateNode(aState, nullptr);
+    AttachNodeToTree(node, aParent, aNextSibling);
+    node->SetHitTestData(GetEventRegions(aLayer), aLayer.GetTransform(),
+        aLayer.GetClipRect() ? Some(nsIntRegion(*aLayer.GetClipRect())) : Nothing());
+    return node;
   }
 
   AsyncPanZoomController* apzc = nullptr;
   // If we get here, aLayer is a scrollable layer and somebody
   // has registered a GeckoContentController for it, so we need to ensure
   // it has an APZC instance to manage its scrolling.
 
   // aState.mApzcMap allows reusing the exact same APZC instance for different layers
@@ -289,77 +373,84 @@ APZCTreeManager::PrepareAPZCForLayer(con
     // If the content represented by the scrollable layer has changed (which may
     // be possible because of DLBI heuristics) then we don't want to keep using
     // the same old APZC for the new content. Null it out so we run through the
     // code to find another one or create one.
     if (apzc && !apzc->Matches(guid)) {
       apzc = nullptr;
     }
 
-    // If the layer doesn't have an APZC already, try to find one of our
-    // pre-existing ones that matches. In particular, if we find an APZC whose
-    // ScrollableLayerGuid is the same, then we know what happened is that the
-    // layout of the page changed causing the layer tree to be rebuilt, but the
-    // underlying content for which the APZC was originally created is still
-    // there. So it makes sense to pick up that APZC instance again and use it here.
-    if (apzc == nullptr) {
-      for (size_t i = 0; i < aState.mApzcsToDestroy.Length(); i++) {
-        if (aState.mApzcsToDestroy.ElementAt(i)->Matches(guid)) {
-          apzc = aState.mApzcsToDestroy.ElementAt(i);
-          break;
+    // See if we can find an APZC from the previous tree that matches the
+    // ScrollableLayerGuid from this layer. If there is one, then we know that
+    // the layout of the page changed causing the layer tree to be rebuilt, but
+    // the underlying content for the APZC is still there somewhere. Therefore,
+    // we want to find the APZC instance and continue using it here.
+    //
+    // We particularly want to find the primary-holder node from the previous
+    // tree that matches, because we don't want that node to get destroyed. If
+    // it does get destroyed, then the APZC will get destroyed along with it by
+    // definition, but we want to keep that APZC around in the new tree.
+    // We leave non-primary-holder nodes in the destroy list because we don't
+    // care about those nodes getting destroyed.
+    for (size_t i = 0; i < aState.mNodesToDestroy.Length(); i++) {
+      nsRefPtr<HitTestingTreeNode> n = aState.mNodesToDestroy[i];
+      if (n->IsPrimaryHolder() && n->GetApzc() && n->GetApzc()->Matches(guid)) {
+        node = n;
+        if (apzc != nullptr) {
+          // If there is an APZC already then it should match the one from the
+          // old primary-holder node
+          MOZ_ASSERT(apzc == node->GetApzc());
         }
+        apzc = node->GetApzc();
+        break;
       }
     }
 
-    // The APZC we get off the layer may have been destroyed previously if the layer was inactive
-    // or omitted from the layer tree for whatever reason from a layers update. If it later comes
-    // back it will have a reference to a destroyed APZC and so we need to throw that out and make
-    // a new one.
+    // The APZC we get off the layer may have been destroyed previously if the
+    // layer was inactive or omitted from the layer tree for whatever reason
+    // from a layers update. If it later comes back it will have a reference to
+    // a destroyed APZC and so we need to throw that out and make a new one.
     bool newApzc = (apzc == nullptr || apzc->IsDestroyed());
     if (newApzc) {
       apzc = MakeAPZCInstance(aLayersId, state->mController);
       apzc->SetCompositorParent(aState.mCompositor);
       if (state->mCrossProcessParent != nullptr) {
         apzc->ShareFrameMetricsAcrossProcesses();
       }
+      MOZ_ASSERT(node == nullptr);
+      node = new HitTestingTreeNode(apzc, true);
     } else {
-      // If there was already an APZC for the layer clear the tree pointers
-      // so that it doesn't continue pointing to APZCs that should no longer
+      // If we are re-using a node for this layer clear the tree pointers
+      // so that it doesn't continue pointing to nodes that might no longer
       // be in the tree. These pointers will get reset properly as we continue
-      // building the tree. Also remove it from the set of APZCs that are going
+      // building the tree. Also remove it from the set of nodes that are going
       // to be destroyed, because it's going to remain active.
-      aState.mApzcsToDestroy.RemoveElement(apzc);
-      apzc->SetPrevSibling(nullptr);
-      apzc->SetLastChild(nullptr);
+      aState.mNodesToDestroy.RemoveElement(node);
+      node->SetPrevSibling(nullptr);
+      node->SetLastChild(nullptr);
     }
+
     APZCTM_LOG("Using APZC %p for layer %p with identifiers %" PRId64 " %" PRId64 "\n", apzc, aLayer.GetLayer(), aLayersId, aMetrics.GetScrollId());
 
     apzc->NotifyLayersUpdated(aMetrics,
         aState.mIsFirstPaint && (aLayersId == aState.mOriginatingLayersId));
 
-    nsIntRegion unobscured;
-    if (!gfxPrefs::LayoutEventRegionsEnabled()) {
-      unobscured = ComputeTouchSensitiveRegion(state->mController, aMetrics, aObscured);
-    }
-    apzc->SetLayerHitTestData(EventRegions(unobscured), aAncestorTransform);
-    APZCTM_LOG("Setting region %s as visible region for APZC %p\n",
-        Stringify(unobscured).c_str(), apzc);
+    // Since this is the first time we are encountering an APZC with this guid,
+    // the node holding it must be the primary holder. It may be newly-created
+    // or not, depending on whether it went through the newApzc branch above.
+    MOZ_ASSERT(node->IsPrimaryHolder() && node->GetApzc() && node->GetApzc()->Matches(guid));
+
+    nsIntRegion clipRegion = ComputeClipRegion(state->mController, aLayer);
+    node->SetHitTestData(GetEventRegions(aLayer), aLayer.GetTransform(), Some(clipRegion));
+    apzc->SetAncestorTransform(aAncestorTransform);
 
     PrintAPZCInfo(aLayer, apzc);
 
     // Bind the APZC instance into the tree of APZCs
-    if (aNextSibling) {
-      aNextSibling->SetPrevSibling(apzc);
-    } else if (aParent) {
-      aParent->SetLastChild(apzc);
-    } else {
-      MOZ_ASSERT(!mRootApzc);
-      mRootApzc = apzc;
-      apzc->MakeRoot();
-    }
+    AttachNodeToTree(node, aParent, aNextSibling);
 
     // For testing, log the parent scroll id of every APZC that has a
     // parent. This allows test code to reconstruct the APZC tree.
     // Note that we currently only do this for APZCs in the layer tree
     // that originated the update, because the only identifying information
     // we are logging about APZCs is the scroll id, and otherwise we could
     // confuse APZCs from different layer trees with the same scroll id.
     if (aLayersId == aState.mOriginatingLayersId && apzc->GetParent()) {
@@ -380,59 +471,56 @@ APZCTreeManager::PrepareAPZCForLayer(con
         // For an apzc that is not the root for its layers ID, we give it the
         // same zoom constraints as its parent. This ensures that if e.g.
         // user-scalable=no was specified, none of the APZCs allow double-tap
         // to zoom.
         apzc->UpdateZoomConstraints(apzc->GetParent()->GetZoomConstraints());
       }
     }
 
+    // Add a guid -> APZC mapping for the newly created APZC.
     insertResult.first->second = apzc;
   } else {
     // We already built an APZC earlier in this tree walk, but we have another layer
     // now that will also be using that APZC. The hit-test region on the APZC needs
     // to be updated to deal with the new layer's hit region.
-    // FIXME: Combining this hit test region to the existing hit test region has a bit
-    // of a problem, because it assumes the z-index of this new region is the same as
-    // the z-index of the old region (from the previous layer with the same scrollid)
-    // when in fact that may not be the case.
-    // Consider the case where we have three layers: A, B, and C. A is at the top in
-    // z-order and C is at the bottom. A and C share a scrollid and scroll together; but
-    // B has a different scrollid and scrolls independently. Depending on how B moves
-    // and the async transform on it, a larger/smaller area of C may be unobscured.
-    // However, when we combine the hit regions of A and C here we are ignoring the async
-    // async transform and so we basically assume the same amount of C is always visible
-    // on top of B. Fixing this doesn't appear to be very easy so I'm leaving it for
-    // now in the hopes that we won't run into this problem a lot.
-    if (!gfxPrefs::LayoutEventRegionsEnabled()) {
-      nsIntRegion unobscured = ComputeTouchSensitiveRegion(state->mController, aMetrics, aObscured);
-      apzc->AddHitTestRegions(EventRegions(unobscured));
-      APZCTM_LOG("Adding region %s to visible region of APZC %p\n", Stringify(unobscured).c_str(), apzc);
-    }
+
+    node = RecycleOrCreateNode(aState, apzc);
+    AttachNodeToTree(node, aParent, aNextSibling);
+
+    // Even though different layers associated with a given APZC may be at
+    // different levels in the layer tree (e.g. one being an uncle of another),
+    // we require from Layout that the CSS transforms up to their common
+    // ancestor be the same.
+    MOZ_ASSERT(aAncestorTransform == apzc->GetAncestorTransform());
+
+    nsIntRegion clipRegion = ComputeClipRegion(state->mController, aLayer);
+    node->SetHitTestData(GetEventRegions(aLayer), aLayer.GetTransform(), Some(clipRegion));
   }
 
-  return apzc;
+  return node;
 }
 
-AsyncPanZoomController*
-APZCTreeManager::UpdatePanZoomControllerTree(TreeBuildingState& aState,
-                                             const LayerMetricsWrapper& aLayer,
-                                             uint64_t aLayersId,
-                                             const gfx::Matrix4x4& aAncestorTransform,
-                                             AsyncPanZoomController* aParent,
-                                             AsyncPanZoomController* aNextSibling,
-                                             const nsIntRegion& aObscured)
+HitTestingTreeNode*
+APZCTreeManager::UpdateHitTestingTree(TreeBuildingState& aState,
+                                      const LayerMetricsWrapper& aLayer,
+                                      uint64_t aLayersId,
+                                      const gfx::Matrix4x4& aAncestorTransform,
+                                      HitTestingTreeNode* aParent,
+                                      HitTestingTreeNode* aNextSibling)
 {
   mTreeLock.AssertCurrentThreadOwns();
 
   mApzcTreeLog << aLayer.Name() << '\t';
 
-  AsyncPanZoomController* apzc = PrepareAPZCForLayer(aLayer,
+  HitTestingTreeNode* node = PrepareNodeForLayer(aLayer,
         aLayer.Metrics(), aLayersId, aAncestorTransform,
-        aObscured, aParent, aNextSibling, aState);
+        aParent, aNextSibling, aState);
+  MOZ_ASSERT(node);
+  AsyncPanZoomController* apzc = node->GetApzc();
   aLayer.SetApzc(apzc);
 
   mApzcTreeLog << '\n';
 
   // Accumulate the CSS transform between layers that have an APZC.
   // In the terminology of the big comment above APZCTreeManager::GetScreenToApzcTransform, if
   // we are at layer M, then aAncestorTransform is NC * OC * PC, and we left-multiply MC and
   // compute ancestorTransform to be MC * NC * OC * PC. This gets passed down as the ancestor
@@ -440,159 +528,30 @@ APZCTreeManager::UpdatePanZoomController
   // with an APZC, such as P, then we reset the ancestorTransform to just PC, to start
   // the new accumulation as we go down.
   Matrix4x4 transform = aLayer.GetTransform();
   Matrix4x4 ancestorTransform = transform;
   if (!apzc) {
     ancestorTransform = ancestorTransform * aAncestorTransform;
   }
 
-  uint64_t childLayersId = (aLayer.AsRefLayer() ? aLayer.AsRefLayer()->GetReferentId() : aLayersId);
-
-  nsIntRegion obscured;
-  if (aLayersId == childLayersId) {
-    // If the child layer is in the same process, transform
-    // aObscured from aLayer's ParentLayerPixels to aLayer's LayerPixels,
-    // which are the children layers' ParentLayerPixels.
-    // If we cross a process boundary, we assume that we can start with
-    // an empty obscured region because nothing in the parent process will
-    // obscure the child process. This may be false. However, not doing this
-    // definitely runs into a problematic case where the B2G notification
-    // bar and the keyboard get merged into a single layer that obscures
-    // all child processes, even though visually they do not. We'd probably
-    // have to check for mask layers and so on in order to properly handle
-    // that case.
-    obscured = aObscured;
-    obscured.Transform(To3DMatrix(transform).Inverse());
-  }
+  // Note that |node| at this point will not have any children, otherwise we
+  // we would have to set next to node->GetFirstChild().
+  MOZ_ASSERT(!node->GetFirstChild());
+  aParent = node;
+  HitTestingTreeNode* next = nullptr;
 
-  // If there's no APZC at this level, any APZCs for our child layers will
-  // have our siblings as their siblings, and our parent as their parent.
-  AsyncPanZoomController* next = aNextSibling;
-  if (apzc) {
-    // Otherwise, use this APZC as the parent going downwards, and start off
-    // with its first child as the next sibling
-    aParent = apzc;
-    next = apzc->GetFirstChild();
-  }
-
-  // In our recursive downward traversal, track event regions for layers once
-  // we encounter an APZC. Push a new empty region on the mEventRegions stack
-  // which will accumulate the hit area of descendants of aLayer. In general,
-  // the mEventRegions stack is used to accumulate event regions from descendant
-  // layers because the event regions for a layer don't include those of its
-  // children.
-  if (gfxPrefs::LayoutEventRegionsEnabled() && (apzc || aState.mEventRegions.Length() > 0)) {
-    aState.mEventRegions.AppendElement(EventRegions());
-  }
-
+  uint64_t childLayersId = (aLayer.AsRefLayer() ? aLayer.AsRefLayer()->GetReferentId() : aLayersId);
   for (LayerMetricsWrapper child = aLayer.GetLastChild(); child; child = child.GetPrevSibling()) {
     gfx::TreeAutoIndent indent(mApzcTreeLog);
-    next = UpdatePanZoomControllerTree(aState, child, childLayersId,
-                                       ancestorTransform, aParent, next,
-                                       obscured);
-
-    // Each layer obscures its previous siblings, so we augment the obscured
-    // region as we loop backwards through the children.
-    nsIntRegion childRegion;
-    if (gfxPrefs::LayoutEventRegionsEnabled()) {
-      childRegion = child.GetEventRegions().mHitRegion;
-    } else {
-      childRegion = child.GetVisibleRegion();
-    }
-    childRegion.Transform(gfx::To3DMatrix(child.GetTransform()));
-    if (child.GetClipRect()) {
-      childRegion.AndWith(*child.GetClipRect());
-    }
-
-    obscured.OrWith(childRegion);
+    next = UpdateHitTestingTree(aState, child, childLayersId,
+                                ancestorTransform, aParent, next);
   }
 
-  if (gfxPrefs::LayoutEventRegionsEnabled() && aState.mEventRegions.Length() > 0) {
-    // At this point in the code, aState.mEventRegions.LastElement() contains
-    // the accumulated regions of the non-APZC descendants of |aLayer|. This
-    // happened in the loop above while we iterated through the descendants of
-    // |aLayer|. Note that it only includes the non-APZC descendants, because
-    // if a layer has an APZC, we simply store the regions from that subtree on
-    // that APZC and don't propagate them upwards in the tree. Because of the
-    // way we do hit-testing (where the deepest matching APZC is used) it should
-    // still be ok if we did propagate those regions upwards and included them
-    // in all the ancestor APZCs.
-    //
-    // Also at this point in the code the |obscured| region includes the hit
-    // regions of children of |aLayer| as well as the hit regions of |aLayer|'s
-    // younger uncles (i.e. the next-sibling chain of |aLayer|'s parent).
-    // When we compute the unobscured regions below, we subtract off the
-    // |obscured| region, but it would also be ok to do this before the above
-    // loop. At that point |obscured| would only have the uncles' hit regions
-    // and not the children. The reason this is ok is again because of the way
-    // we do hit-testing (where the deepest APZC is used) it doesn't matter if
-    // we count the children as obscuring the parent or not.
-
-    EventRegions unobscured;
-    unobscured.Sub(aLayer.GetEventRegions(), obscured);
-    APZCTM_LOG("Picking up unobscured hit region %s from layer %p\n", Stringify(unobscured).c_str(), aLayer.GetLayer());
-
-    // Take the hit region of the |aLayer|'s subtree (which has already been
-    // transformed into the coordinate space of |aLayer|) and...
-    EventRegions subtreeEventRegions = aState.mEventRegions.LastElement();
-    aState.mEventRegions.RemoveElementAt(aState.mEventRegions.Length() - 1);
-    // ... combine it with the hit region for this layer, and then ...
-    subtreeEventRegions.OrWith(unobscured);
-    // ... transform it up to the parent layer's coordinate space.
-    subtreeEventRegions.Transform(To3DMatrix(aLayer.GetTransform()));
-    if (aLayer.GetClipRect()) {
-      subtreeEventRegions.AndWith(*aLayer.GetClipRect());
-    }
-
-    APZCTM_LOG("After processing layer %p the subtree hit region is %s\n", aLayer.GetLayer(), Stringify(subtreeEventRegions).c_str());
-
-    // If we have an APZC at this level, intersect the subtree hit region with
-    // the touch-sensitive region and add it to the APZ's hit test regions.
-    if (apzc) {
-      APZCTM_LOG("Adding region %s to visible region of APZC %p\n", Stringify(subtreeEventRegions).c_str(), apzc);
-      const CompositorParent::LayerTreeState* state = CompositorParent::GetIndirectShadowTree(aLayersId);
-      MOZ_ASSERT(state);
-      MOZ_ASSERT(state->mController.get());
-      CSSRect touchSensitiveRegion;
-      if (state->mController->GetTouchSensitiveRegion(&touchSensitiveRegion)) {
-        // Here we assume 'touchSensitiveRegion' is in the CSS pixels of the
-        // parent frame. To convert it to ParentLayer pixels, we therefore need
-        // the cumulative resolution of the parent frame. We approximate this as
-        // the quotient of our cumulative resolution and our pres shell
-        // resolution; this approximation may not be accurate in the presence of
-        // a css-driven resolution.
-        LayoutDeviceToParentLayerScale parentCumulativeResolution =
-            aLayer.Metrics().GetCumulativeResolution()
-            / ParentLayerToLayerScale(aLayer.Metrics().mPresShellResolution);
-        subtreeEventRegions.AndWith(ParentLayerIntRect::ToUntyped(
-            RoundedIn(touchSensitiveRegion
-                    * aLayer.Metrics().GetDevPixelsPerCSSPixel()
-                    * parentCumulativeResolution)));
-      }
-      apzc->AddHitTestRegions(subtreeEventRegions);
-    } else {
-      // If we don't have an APZC at this level, carry the subtree hit region
-      // up to the parent.
-      MOZ_ASSERT(aState.mEventRegions.Length() > 0);
-      aState.mEventRegions.LastElement().OrWith(subtreeEventRegions);
-    }
-  }
-
-  // Return the APZC that should be the sibling of other APZCs as we continue
-  // moving towards the first child at this depth in the layer tree.
-  // If this layer doesn't have an APZC, we promote any APZCs in the subtree
-  // upwards. Otherwise we fall back to the aNextSibling that was passed in.
-  if (apzc) {
-    return apzc;
-  }
-  if (next) {
-    return next;
-  }
-  return aNextSibling;
+  return node;
 }
 
 nsEventStatus
 APZCTreeManager::ReceiveInputEvent(InputData& aEvent,
                                    ScrollableLayerGuid* aOutTargetGuid,
                                    uint64_t* aOutInputBlockId)
 {
   // Initialize aOutInputBlockId to a sane value, and then later we overwrite
@@ -718,19 +677,17 @@ APZCTreeManager::GetTouchInputBlockAPZC(
     // at the start of an input block for a number of reasons. One of the reasons is so that
     // after we untransform the event into gecko space, it doesn't end up under something
     // else. Another reason is that if we hit-test this event and end up on a layer's
     // dispatch-to-content region we cannot be sure we actually got the correct layer. We
     // have to fall back to the gecko hit-test to handle this case, but we can't untransform
     // the event we send to gecko because we don't know the layer to untransform with
     // respect to.
     MonitorAutoLock lock(mTreeLock);
-    for (AsyncPanZoomController* apzc = mRootApzc; apzc; apzc = apzc->GetPrevSibling()) {
-      FlushRepaintsRecursively(apzc);
-    }
+    FlushRepaintsRecursively(mRootNode);
   }
 
   apzc = GetTargetAPZC(aEvent.mTouches[0].mScreenPoint, aOutHitResult);
   for (size_t i = 1; i < aEvent.mTouches.Length(); i++) {
     nsRefPtr<AsyncPanZoomController> apzc2 = GetTargetAPZC(aEvent.mTouches[i].mScreenPoint, aOutHitResult);
     apzc = GetMultitouchTarget(apzc, apzc2);
     APZCTM_LOG("Using APZC %p as the root APZC for multi-touch\n", apzc.get());
   }
@@ -1002,65 +959,74 @@ void
 APZCTreeManager::SetTargetAPZC(uint64_t aInputBlockId,
                                const nsTArray<ScrollableLayerGuid>& aTargets)
 {
   nsRefPtr<AsyncPanZoomController> target = nullptr;
   if (aTargets.Length() > 0) {
     target = GetTargetAPZC(aTargets[0]);
   }
   for (size_t i = 1; i < aTargets.Length(); i++) {
-    nsRefPtr<AsyncPanZoomController> apzc2 = GetTargetAPZC(aTargets[i]);
-    target = GetMultitouchTarget(target, apzc2);
+    nsRefPtr<AsyncPanZoomController> apzc = GetTargetAPZC(aTargets[i]);
+    target = GetMultitouchTarget(target, apzc);
   }
   mInputQueue->SetConfirmedTargetApzc(aInputBlockId, target);
 }
 
 void
 APZCTreeManager::SetTargetAPZC(uint64_t aInputBlockId, const ScrollableLayerGuid& aTarget)
 {
-  nsRefPtr<AsyncPanZoomController> target = GetTargetAPZC(aTarget);
-  mInputQueue->SetConfirmedTargetApzc(aInputBlockId, target);
+  nsRefPtr<AsyncPanZoomController> apzc = GetTargetAPZC(aTarget);
+  mInputQueue->SetConfirmedTargetApzc(aInputBlockId, apzc);
 }
 
 void
 APZCTreeManager::UpdateZoomConstraints(const ScrollableLayerGuid& aGuid,
                                        const ZoomConstraints& aConstraints)
 {
-  nsRefPtr<AsyncPanZoomController> apzc = GetTargetAPZC(aGuid);
+  nsRefPtr<HitTestingTreeNode> node = GetTargetNode(aGuid, nullptr);
+  MOZ_ASSERT(!node || node->GetApzc()); // any node returned must have an APZC
+
   // For a given layers id, non-root APZCs inherit the zoom constraints
   // of their root.
-  if (apzc && apzc->IsRootForLayersId()) {
+  if (node && node->GetApzc()->IsRootForLayersId()) {
     MonitorAutoLock lock(mTreeLock);
-    UpdateZoomConstraintsRecursively(apzc.get(), aConstraints);
+    UpdateZoomConstraintsRecursively(node.get(), aConstraints);
   }
 }
 
 void
-APZCTreeManager::UpdateZoomConstraintsRecursively(AsyncPanZoomController* aApzc,
+APZCTreeManager::UpdateZoomConstraintsRecursively(HitTestingTreeNode* aNode,
                                                   const ZoomConstraints& aConstraints)
 {
   mTreeLock.AssertCurrentThreadOwns();
 
-  aApzc->UpdateZoomConstraints(aConstraints);
-  for (AsyncPanZoomController* child = aApzc->GetLastChild(); child; child = child->GetPrevSibling()) {
+  if (aNode->IsPrimaryHolder()) {
+    MOZ_ASSERT(aNode->GetApzc());
+    aNode->GetApzc()->UpdateZoomConstraints(aConstraints);
+  }
+  for (HitTestingTreeNode* child = aNode->GetLastChild(); child; child = child->GetPrevSibling()) {
     // We can have subtrees with their own layers id - leave those alone.
-    if (!child->IsRootForLayersId()) {
-      UpdateZoomConstraintsRecursively(child, aConstraints);
+    if (child->GetApzc() && child->GetApzc()->IsRootForLayersId()) {
+      continue;
     }
+    UpdateZoomConstraintsRecursively(child, aConstraints);
   }
 }
 
 void
-APZCTreeManager::FlushRepaintsRecursively(AsyncPanZoomController* aApzc)
+APZCTreeManager::FlushRepaintsRecursively(HitTestingTreeNode* aNode)
 {
   mTreeLock.AssertCurrentThreadOwns();
 
-  aApzc->FlushRepaintForNewInputBlock();
-  for (AsyncPanZoomController* child = aApzc->GetLastChild(); child; child = child->GetPrevSibling()) {
-    FlushRepaintsRecursively(child);
+  for (HitTestingTreeNode* node = aNode; node; node = node->GetPrevSibling()) {
+    if (node->IsPrimaryHolder()) {
+      MOZ_ASSERT(node->GetApzc());
+      node->GetApzc()->FlushRepaintForNewInputBlock();
+    }
+    FlushRepaintsRecursively(node->GetLastChild());
   }
 }
 
 void
 APZCTreeManager::CancelAnimation(const ScrollableLayerGuid &aGuid)
 {
   nsRefPtr<AsyncPanZoomController> apzc = GetTargetAPZC(aGuid);
   if (apzc) {
@@ -1071,22 +1037,29 @@ APZCTreeManager::CancelAnimation(const S
 void
 APZCTreeManager::ClearTree()
 {
   MonitorAutoLock lock(mTreeLock);
 
   // This can be done as part of a tree walk but it's easier to
   // just re-use the Collect method that we need in other places.
   // If this is too slow feel free to change it to a recursive walk.
-  nsTArray< nsRefPtr<AsyncPanZoomController> > apzcsToDestroy;
-  Collect(mRootApzc, &apzcsToDestroy);
-  for (size_t i = 0; i < apzcsToDestroy.Length(); i++) {
-    apzcsToDestroy[i]->Destroy();
+  nsTArray<nsRefPtr<HitTestingTreeNode>> nodesToDestroy;
+  Collect(mRootNode, &nodesToDestroy);
+  for (size_t i = 0; i < nodesToDestroy.Length(); i++) {
+    nodesToDestroy[i]->Destroy();
   }
-  mRootApzc = nullptr;
+  mRootNode = nullptr;
+}
+
+nsRefPtr<HitTestingTreeNode>
+APZCTreeManager::GetRootNode() const
+{
+  MonitorAutoLock lock(mTreeLock);
+  return mRootNode;
 }
 
 /**
  * Transform a displacement from the ParentLayer coordinates of a source APZC
  * to the ParentLayer coordinates of a target APZC.
  * @param aTreeManager the tree manager for the APZC tree containing |aSource|
  *                     and |aTarget|
  * @param aSource the source APZC
@@ -1215,53 +1188,58 @@ APZCTreeManager::DispatchFling(AsyncPanZ
 bool
 APZCTreeManager::HitTestAPZC(const ScreenIntPoint& aPoint)
 {
   nsRefPtr<AsyncPanZoomController> target = GetTargetAPZC(aPoint, nullptr);
   return target != nullptr;
 }
 
 already_AddRefed<AsyncPanZoomController>
-APZCTreeManager::GetTargetAPZC(const ScrollableLayerGuid& aGuid)
+APZCTreeManager::GetTargetAPZC(const ScrollableLayerGuid& aGuid,
+                               GuidComparator aComparator)
+{
+  nsRefPtr<HitTestingTreeNode> node = GetTargetNode(aGuid, aComparator);
+  MOZ_ASSERT(!node || node->GetApzc()); // any node returned must have an APZC
+  nsRefPtr<AsyncPanZoomController> apzc = node ? node->GetApzc() : nullptr;
+  return apzc.forget();
+}
+
+already_AddRefed<HitTestingTreeNode>
+APZCTreeManager::GetTargetNode(const ScrollableLayerGuid& aGuid,
+                               GuidComparator aComparator)
 {
   MonitorAutoLock lock(mTreeLock);
-  nsRefPtr<AsyncPanZoomController> target;
-  // The root may have siblings, check those too
-  for (AsyncPanZoomController* apzc = mRootApzc; apzc; apzc = apzc->GetPrevSibling()) {
-    target = FindTargetAPZC(apzc, aGuid);
-    if (target) {
-      break;
-    }
-  }
+  nsRefPtr<HitTestingTreeNode> target = FindTargetNode(mRootNode, aGuid, aComparator);
   return target.forget();
 }
 
 already_AddRefed<AsyncPanZoomController>
 APZCTreeManager::GetTargetAPZC(const ScreenPoint& aPoint, HitTestResult* aOutHitResult)
 {
   MonitorAutoLock lock(mTreeLock);
-  nsRefPtr<AsyncPanZoomController> target;
-  // The root may have siblings, so check those too
   HitTestResult hitResult = NoApzcHit;
-  for (AsyncPanZoomController* apzc = mRootApzc; apzc; apzc = apzc->GetPrevSibling()) {
-    target = GetAPZCAtPoint(apzc, aPoint.ToUnknownPoint(), &hitResult);
-    // If we hit an overscrolled APZC, 'target' will be nullptr but it's still
-    // a hit so we don't search further siblings.
-    if (target || (hitResult == OverscrolledApzc)) {
-      break;
-    }
-  }
+  ParentLayerPoint point = ViewAs<ParentLayerPixel>(aPoint,
+    PixelCastJustification::ScreenIsParentLayerForRoot);
+  nsRefPtr<AsyncPanZoomController> target = GetAPZCAtPoint(mRootNode, point, &hitResult);
+
   // If we are in an overscrolled APZC, we should be returning nullptr.
   MOZ_ASSERT(!(target && (hitResult == OverscrolledApzc)));
   if (aOutHitResult) {
     *aOutHitResult = hitResult;
   }
   return target.forget();
 }
 
+static bool
+GuidComparatorIgnoringPresShell(const ScrollableLayerGuid& aOne, const ScrollableLayerGuid& aTwo)
+{
+  return aOne.mLayersId == aTwo.mLayersId
+      && aOne.mScrollId == aTwo.mScrollId;
+}
+
 nsRefPtr<const OverscrollHandoffChain>
 APZCTreeManager::BuildOverscrollHandoffChain(const nsRefPtr<AsyncPanZoomController>& aInitialTarget)
 {
   // Scroll grabbing is a mechanism that allows content to specify that
   // the initial target of a pan should be not the innermost scrollable
   // frame at the touch point (which is what GetTargetAPZC finds), but
   // something higher up in the tree.
   // It's not sufficient to just find the initial target, however, as
@@ -1303,17 +1281,19 @@ APZCTreeManager::BuildOverscrollHandoffC
       // handoff parent, we don't actually need to do the search so we can
       // just abort here.
       if (parent->GetGuid().mScrollId == apzc->GetScrollHandoffParentId()) {
         scrollParent = parent;
         break;
       }
     }
     if (!scrollParent) {
-      scrollParent = FindTargetAPZC(parent, apzc->GetScrollHandoffParentId());
+      ScrollableLayerGuid guid(parent->GetGuid().mLayersId, 0, apzc->GetScrollHandoffParentId());
+      nsRefPtr<AsyncPanZoomController> scrollParentPtr = GetTargetAPZC(guid, &GuidComparatorIgnoringPresShell);
+      scrollParent = scrollParentPtr.get();
     }
     apzc = scrollParent;
   }
 
   // Now adjust the chain to account for scroll grabbing. Sorting is a bit
   // of an overkill here, but scroll grabbing will likely be generalized
   // to scroll priorities, so we might as well do it this way.
   result->SortByScrollPriority();
@@ -1321,132 +1301,139 @@ APZCTreeManager::BuildOverscrollHandoffC
   // Print the overscroll chain for debugging.
   for (uint32_t i = 0; i < result->Length(); ++i) {
     APZCTM_LOG("OverscrollHandoffChain[%d] = %p\n", i, result->GetApzcAtIndex(i).get());
   }
 
   return result;
 }
 
-/* Find the apzc in the subtree rooted at aApzc that has the same layers id as
-   aApzc, and that has the given scroll id. Generally this function should be called
-   with aApzc being the root of its layers id subtree. */
-AsyncPanZoomController*
-APZCTreeManager::FindTargetAPZC(AsyncPanZoomController* aApzc, FrameMetrics::ViewID aScrollId)
-{
-  mTreeLock.AssertCurrentThreadOwns();
-
-  if (aApzc->GetGuid().mScrollId == aScrollId) {
-    return aApzc;
-  }
-  for (AsyncPanZoomController* child = aApzc->GetLastChild(); child; child = child->GetPrevSibling()) {
-    if (child->GetGuid().mLayersId != aApzc->GetGuid().mLayersId) {
-      continue;
-    }
-    AsyncPanZoomController* match = FindTargetAPZC(child, aScrollId);
-    if (match) {
-      return match;
-    }
-  }
-
-  return nullptr;
-}
-
-AsyncPanZoomController*
-APZCTreeManager::FindTargetAPZC(AsyncPanZoomController* aApzc, const ScrollableLayerGuid& aGuid)
+HitTestingTreeNode*
+APZCTreeManager::FindTargetNode(HitTestingTreeNode* aNode,
+                                const ScrollableLayerGuid& aGuid,
+                                GuidComparator aComparator)
 {
   mTreeLock.AssertCurrentThreadOwns();
 
   // This walks the tree in depth-first, reverse order, so that it encounters
   // APZCs front-to-back on the screen.
-  for (AsyncPanZoomController* child = aApzc->GetLastChild(); child; child = child->GetPrevSibling()) {
-    AsyncPanZoomController* match = FindTargetAPZC(child, aGuid);
+  for (HitTestingTreeNode* node = aNode; node; node = node->GetPrevSibling()) {
+    HitTestingTreeNode* match = FindTargetNode(node->GetLastChild(), aGuid, aComparator);
     if (match) {
       return match;
     }
-  }
 
-  if (aApzc->Matches(aGuid)) {
-    return aApzc;
+    bool matches = false;
+    if (node->GetApzc()) {
+      if (aComparator) {
+        matches = aComparator(aGuid, node->GetApzc()->GetGuid());
+      } else {
+        matches = node->GetApzc()->Matches(aGuid);
+      }
+    }
+    if (matches) {
+      return node;
+    }
   }
   return nullptr;
 }
 
 AsyncPanZoomController*
-APZCTreeManager::GetAPZCAtPoint(AsyncPanZoomController* aApzc,
-                                const Point& aHitTestPoint,
+APZCTreeManager::GetAPZCAtPoint(HitTestingTreeNode* aNode,
+                                const ParentLayerPoint& aHitTestPoint,
                                 HitTestResult* aOutHitResult)
 {
   mTreeLock.AssertCurrentThreadOwns();
 
-  // The comments below assume there is a chain of layers L..R with L and P having APZC instances as
-  // explained in the comment above GetScreenToApzcTransform. This function will recurse with aApzc at L and P, and the
-  // comments explain what values are stored in the variables at these two levels. All the comments
-  // use standard matrix notation where the leftmost matrix in a multiplication is applied first.
-
-  // ancestorUntransform takes points from aApzc's parent APZC's CSS-transformed layer coordinates
-  // to aApzc's parent layer's layer coordinates.
-  // It is PC.Inverse() * OC.Inverse() * NC.Inverse() * MC.Inverse() at recursion level for L,
-  //   and RC.Inverse() * QC.Inverse()                               at recursion level for P.
-  Matrix4x4 ancestorUntransform = aApzc->GetAncestorTransform().Inverse();
-
-  // Hit testing for this layer takes place in our parent layer coordinates,
-  // since the composition bounds (used to initialize the visible rect against
-  // which we hit test are in those coordinates).
-  Point4D hitTestPointForThisLayer = ancestorUntransform.ProjectPoint(aHitTestPoint);
-  APZCTM_LOG("Untransformed %f %f to transient coordinates %f %f for hit-testing APZC %p\n",
-           aHitTestPoint.x, aHitTestPoint.y,
-           hitTestPointForThisLayer.x, hitTestPointForThisLayer.y, aApzc);
-
-  // childUntransform takes points from aApzc's parent APZC's CSS-transformed layer coordinates
-  // to aApzc's CSS-transformed layer coordinates.
-  // It is PC.Inverse() * OC.Inverse() * NC.Inverse() * MC.Inverse() * LA.Inverse() at L
-  //   and RC.Inverse() * QC.Inverse() * PA.Inverse()                               at P.
-  Matrix4x4 childUntransform = ancestorUntransform * Matrix4x4(aApzc->GetCurrentAsyncTransform()).Inverse();
-  Point4D hitTestPointForChildLayers = childUntransform.ProjectPoint(aHitTestPoint);
-  APZCTM_LOG("Untransformed %f %f to layer coordinates %f %f for APZC %p\n",
-           aHitTestPoint.x, aHitTestPoint.y,
-           hitTestPointForChildLayers.x, hitTestPointForChildLayers.y, aApzc);
-
-  AsyncPanZoomController* result = nullptr;
   // This walks the tree in depth-first, reverse order, so that it encounters
   // APZCs front-to-back on the screen.
-  if (hitTestPointForChildLayers.HasPositiveWCoord()) {
-    for (AsyncPanZoomController* child = aApzc->GetLastChild(); child; child = child->GetPrevSibling()) {
-      AsyncPanZoomController* match = GetAPZCAtPoint(child, hitTestPointForChildLayers.As2DPoint(), aOutHitResult);
+  for (HitTestingTreeNode* node = aNode; node; node = node->GetPrevSibling()) {
+    AsyncPanZoomController* apzc = node->GetApzc();
+
+    if (node->IsOutsideClip(aHitTestPoint)) {
+      // If the point being tested is outside the clip region for this node
+      // then we don't need to test against this node or any of its children.
+      // Just skip it and move on.
+      APZCTM_LOG("Point %f %f outside clip for node %p\n",
+        aHitTestPoint.x, aHitTestPoint.y, node);
+      continue;
+    }
+
+    AsyncPanZoomController* result = nullptr;
+
+    // First check the subtree rooted at this node, because deeper nodes
+    // are more "in front".
+    Maybe<LayerPoint> hitTestPointForChildLayers = node->Untransform(aHitTestPoint);
+    if (hitTestPointForChildLayers) {
+      ParentLayerPoint childPoint = ViewAs<ParentLayerPixel>(hitTestPointForChildLayers.ref(),
+        PixelCastJustification::MovingDownToChildren);
+      result = GetAPZCAtPoint(node->GetLastChild(), childPoint, aOutHitResult);
       if (*aOutHitResult == OverscrolledApzc) {
         // We matched an overscrolled APZC, abort.
         return nullptr;
       }
-      if (match) {
-        result = match;
-        break;
+    }
+
+    // If we didn't match anything in the subtree, check |node|.
+    if (!result) {
+      APZCTM_LOG("Testing ParentLayer point %s (Layer %s) against node %p\n",
+          Stringify(aHitTestPoint).c_str(),
+          hitTestPointForChildLayers ? Stringify(hitTestPointForChildLayers.ref()).c_str() : "nil",
+          node);
+      HitTestResult hitResult = node->HitTest(aHitTestPoint);
+      if (hitResult != HitTestResult::NoApzcHit) {
+        result = node->GetNearestContainingApzc();
+        APZCTM_LOG("Successfully matched APZC %p via node %p (hit result %d)\n",
+             result, node, hitResult);
+        MOZ_ASSERT(hitResult == ApzcHitRegion || hitResult == ApzcContentRegion);
+        // If event regions are disabled, *aOutHitResult will be ApzcHitRegion
+        *aOutHitResult = hitResult;
       }
     }
-  }
-  if (!result && hitTestPointForThisLayer.HasPositiveWCoord()) {
-    ParentLayerPoint point = ParentLayerPoint::FromUnknownPoint(hitTestPointForThisLayer.As2DPoint());
-    if (aApzc->HitRegionContains(point)) {
-      APZCTM_LOG("Successfully matched untransformed point %f %f to visible region for APZC %p\n",
-                 hitTestPointForThisLayer.x, hitTestPointForThisLayer.y, aApzc);
-      result = aApzc;
-      // If event regions are disabled, *aOutHitResult will be ApzcHitRegion
-      *aOutHitResult = (aApzc->DispatchToContentRegionContains(point) ? ApzcContentRegion : ApzcHitRegion);
+
+    // If we are overscrolled, and the point matches us or one of our children,
+    // the result is inside an overscrolled APZC, inform our caller of this
+    // (callers typically ignore events targeted at overscrolled APZCs).
+    if (result && apzc && apzc->IsOverscrolled()) {
+      APZCTM_LOG("Result is inside overscrolled APZC %p\n", apzc);
+      *aOutHitResult = OverscrolledApzc;
+      return nullptr;
+    }
+
+    if (result) {
+      if (!gfxPrefs::LayoutEventRegionsEnabled()) {
+        // When event-regions are disabled, we treat scrollinfo layers as
+        // regular scrollable layers. Unfortunately, their "hit region" (which
+        // we create from the composition bounds) is their full area, and they
+        // sit on top of their non-scrollinfo siblings. This means they will get
+        // a HitTestingTreeNode with a hit region that will aggressively match
+        // any input events that might be directed to sub-APZCs of their non-
+        // scrollinfo siblings. Therefore, we need to keep looping through to
+        // see if there are any other non-scrollinfo siblings that have children
+        // that match this input. If so, they should take priority. With event-
+        // regions enabled we use the actual regions from the layer, which are
+        // empty, and so this is unnecessary.
+        AsyncPanZoomController* prevSiblingApzc = nullptr;
+        for (HitTestingTreeNode* n = node->GetPrevSibling(); n; n = n->GetPrevSibling()) {
+          if (n->GetApzc()) {
+            prevSiblingApzc = n->GetApzc();
+            break;
+          }
+        }
+        if (result == prevSiblingApzc) {
+          APZCTM_LOG("Continuing search past probable scrollinfo info layer\n");
+          continue;
+        }
+      }
+
+      return result;
     }
   }
 
-  // If we are overscrolled, and the point matches us or one of our children,
-  // the result is inside an overscrolled APZC, inform our caller of this
-  // (callers typically ignore events targeted at overscrolled APZCs).
-  if (result && aApzc->IsOverscrolled()) {
-    *aOutHitResult = OverscrolledApzc;
-    result = nullptr;
-  }
-
-  return result;
+  return nullptr;
 }
 
 /* The methods GetScreenToApzcTransform() and GetApzcToGeckoTransform() return
    some useful transformations that input events may need applied. This is best
    illustrated with an example. Consider a chain of layers, L, M, N, O, P, Q, R. Layer L
    is the layer that corresponds to the argument |aApzc|, and layer R is the root
    of the layer tree. Layer M is the parent of L, N is the parent of M, and so on.
    When layer L is displayed to the screen by the compositor, the set of transforms that
--- a/gfx/layers/apz/src/APZCTreeManager.h
+++ b/gfx/layers/apz/src/APZCTreeManager.h
@@ -16,16 +16,17 @@
 #include "mozilla/Monitor.h"            // for Monitor
 #include "mozilla/gfx/Matrix.h"         // for Matrix4x4
 #include "nsAutoPtr.h"                  // for nsRefPtr
 #include "nsCOMPtr.h"                   // for already_AddRefed
 #include "nsISupportsImpl.h"            // for MOZ_COUNT_CTOR, etc
 #include "mozilla/Vector.h"             // for mozilla::Vector
 #include "nsTArrayForwardDeclare.h"     // for nsTArray, nsTArray_Impl, etc
 #include "mozilla/gfx/Logging.h"        // for gfx::TreeLog
+#include "mozilla/layers/APZUtils.h"    // for HitTestResult
 
 class nsIntRegion;
 
 namespace mozilla {
 class InputData;
 class MultiTouchInput;
 
 namespace layers {
@@ -43,16 +44,17 @@ class Layer;
 class AsyncPanZoomController;
 class CompositorParent;
 class APZPaintLogHelper;
 class OverscrollHandoffChain;
 struct OverscrollHandoffState;
 class LayerMetricsWrapper;
 class InputQueue;
 class GeckoContentController;
+class HitTestingTreeNode;
 
 /**
  * ****************** NOTE ON LOCK ORDERING IN APZ **************************
  *
  * There are two kinds of locks used by APZ: APZCTreeManager::mTreeLock
  * ("the tree lock") and AsyncPanZoomController::mMonitor ("APZC locks").
  *
  * To avoid deadlock, we impose a lock ordering between these locks, which is:
@@ -67,20 +69,21 @@ class GeckoContentController;
 
 /**
  * This class manages the tree of AsyncPanZoomController instances. There is one
  * instance of this class owned by each CompositorParent, and it contains as
  * many AsyncPanZoomController instances as there are scrollable container layers.
  * This class generally lives on the compositor thread, although some functions
  * may be called from other threads as noted; thread safety is ensured internally.
  *
- * The bulk of the work of this class happens as part of the UpdatePanZoomControllerTree
+ * The bulk of the work of this class happens as part of the UpdateHitTestingTree
  * function, which is when a layer tree update is received by the compositor.
- * This function walks through the layer tree and creates a tree of APZC instances
- * to match the scrollable container layers. APZC instances may be preserved across
+ * This function walks through the layer tree and creates a tree of
+ * HitTestingTreeNode instances to match the layer tree and for use in
+ * hit-testing on the controller thread. APZC instances may be preserved across
  * calls to this function if the corresponding layers are still present in the layer
  * tree.
  *
  * The other functions on this class are used by various pieces of client code to
  * notify the APZC instances of events relevant to them. This includes, for example,
  * user input events that drive panning and zooming, changes to the scroll viewport
  * area, and changes to pan/zoom constraints.
  *
@@ -90,29 +93,29 @@ class GeckoContentController;
  * Behaviour of APZ is controlled by a number of preferences shown \ref APZCPrefs "here".
  */
 class APZCTreeManager {
   NS_INLINE_DECL_THREADSAFE_REFCOUNTING(APZCTreeManager)
 
   typedef mozilla::layers::AllowedTouchBehavior AllowedTouchBehavior;
   typedef uint32_t TouchBehaviorFlags;
 
-  // Helper struct to hold some state while we build the APZ tree. The
+  // Helper struct to hold some state while we build the hit-testing tree. The
   // sole purpose of this struct is to shorten the argument list to
-  // UpdatePanZoomControllerTree. All the state that we don't need to
+  // UpdateHitTestingTree. All the state that we don't need to
   // push on the stack during recursion and pop on unwind is stored here.
   struct TreeBuildingState;
 
 public:
   APZCTreeManager();
 
   /**
-   * Rebuild the APZC tree based on the layer update that just came up. Preserve
-   * APZC instances where possible, but retire those whose layers are no longer
-   * in the layer tree.
+   * Rebuild the hit-testing tree based on the layer update that just came up.
+   * Preserve nodes and APZC instances where possible, but retire those whose
+   * layers are no longer in the layer tree.
    *
    * This must be called on the compositor thread as it walks the layer tree.
    *
    * @param aCompositor A pointer to the compositor parent instance that owns
    *                    this APZCTreeManager
    * @param aRoot The root of the (full) layer tree
    * @param aFirstPaintLayersId The layers id of the subtree to which aIsFirstPaint
    *                            applies.
@@ -120,21 +123,21 @@ public:
    *                      to included a first-paint. If this is true, the part of
    *                      the tree that is affected by the first-paint flag is
    *                      indicated by the aFirstPaintLayersId parameter.
    * @param aPaintSequenceNumber The sequence number of the paint that triggered
    *                             this layer update. Note that every layer child
    *                             process' layer subtree has its own sequence
    *                             numbers.
    */
-  void UpdatePanZoomControllerTree(CompositorParent* aCompositor,
-                                   Layer* aRoot,
-                                   bool aIsFirstPaint,
-                                   uint64_t aOriginatingLayersId,
-                                   uint32_t aPaintSequenceNumber);
+  void UpdateHitTestingTree(CompositorParent* aCompositor,
+                            Layer* aRoot,
+                            bool aIsFirstPaint,
+                            uint64_t aOriginatingLayersId,
+                            uint32_t aPaintSequenceNumber);
 
   /**
    * General handler for incoming input events. Manipulates the frame metrics
    * based on what type of input it is. For example, a PinchGestureEvent will
    * cause scaling. This should only be called externally to this class.
    *
    * This function transforms |aEvent| to have its coordinates in DOM space.
    * This is so that the event can be passed through the DOM and content can
@@ -393,98 +396,113 @@ protected:
 
 public:
   /* Some helper functions to find an APZC given some identifying input. These functions
      lock the tree of APZCs while they find the right one, and then return an addref'd
      pointer to it. This allows caller code to just use the target APZC without worrying
      about it going away. These are public for testing code and generally should not be
      used by other production code.
   */
-  enum HitTestResult {
-    NoApzcHit,
-    ApzcHitRegion,
-    ApzcContentRegion,
-    OverscrolledApzc,
-  };
-
-  already_AddRefed<AsyncPanZoomController> GetTargetAPZC(const ScrollableLayerGuid& aGuid);
+  nsRefPtr<HitTestingTreeNode> GetRootNode() const;
   already_AddRefed<AsyncPanZoomController> GetTargetAPZC(const ScreenPoint& aPoint,
                                                          HitTestResult* aOutHitResult);
   gfx::Matrix4x4 GetScreenToApzcTransform(const AsyncPanZoomController *aApzc) const;
   gfx::Matrix4x4 GetApzcToGeckoTransform(const AsyncPanZoomController *aApzc) const;
 private:
+  typedef bool (*GuidComparator)(const ScrollableLayerGuid&, const ScrollableLayerGuid&);
+
   /* Helpers */
-  AsyncPanZoomController* FindTargetAPZC(AsyncPanZoomController* aApzc, FrameMetrics::ViewID aScrollId);
-  AsyncPanZoomController* FindTargetAPZC(AsyncPanZoomController* aApzc, const ScrollableLayerGuid& aGuid);
-  AsyncPanZoomController* GetAPZCAtPoint(AsyncPanZoomController* aApzc,
-                                         const gfx::Point& aHitTestPoint,
+  void AttachNodeToTree(HitTestingTreeNode* aNode,
+                        HitTestingTreeNode* aParent,
+                        HitTestingTreeNode* aNextSibling);
+  already_AddRefed<AsyncPanZoomController> GetTargetAPZC(const ScrollableLayerGuid& aGuid,
+                                                         GuidComparator aComparator = nullptr);
+  already_AddRefed<HitTestingTreeNode> GetTargetNode(const ScrollableLayerGuid& aGuid,
+                                                     GuidComparator aComparator);
+  HitTestingTreeNode* FindTargetNode(HitTestingTreeNode* aNode,
+                                     const ScrollableLayerGuid& aGuid,
+                                     GuidComparator aComparator);
+  AsyncPanZoomController* GetAPZCAtPoint(HitTestingTreeNode* aNode,
+                                         const ParentLayerPoint& aHitTestPoint,
                                          HitTestResult* aOutHitResult);
   already_AddRefed<AsyncPanZoomController> GetMultitouchTarget(AsyncPanZoomController* aApzc1, AsyncPanZoomController* aApzc2) const;
   already_AddRefed<AsyncPanZoomController> CommonAncestor(AsyncPanZoomController* aApzc1, AsyncPanZoomController* aApzc2) const;
   already_AddRefed<AsyncPanZoomController> RootAPZCForLayersId(AsyncPanZoomController* aApzc) const;
   already_AddRefed<AsyncPanZoomController> GetTouchInputBlockAPZC(const MultiTouchInput& aEvent,
                                                                   HitTestResult* aOutHitResult);
   nsEventStatus ProcessTouchInput(MultiTouchInput& aInput,
                                   ScrollableLayerGuid* aOutTargetGuid,
                                   uint64_t* aOutInputBlockId);
   nsEventStatus ProcessWheelEvent(WidgetWheelEvent& aEvent,
                                   ScrollableLayerGuid* aOutTargetGuid,
                                   uint64_t* aOutInputBlockId);
   nsEventStatus ProcessEvent(WidgetInputEvent& inputEvent,
                              ScrollableLayerGuid* aOutTargetGuid,
                              uint64_t* aOutInputBlockId);
-  void UpdateZoomConstraintsRecursively(AsyncPanZoomController* aApzc,
+  void UpdateZoomConstraintsRecursively(HitTestingTreeNode* aNode,
                                         const ZoomConstraints& aConstraints);
-  void FlushRepaintsRecursively(AsyncPanZoomController* aApzc);
+  void FlushRepaintsRecursively(HitTestingTreeNode* aNode);
 
-  AsyncPanZoomController* PrepareAPZCForLayer(const LayerMetricsWrapper& aLayer,
-                                              const FrameMetrics& aMetrics,
-                                              uint64_t aLayersId,
-                                              const gfx::Matrix4x4& aAncestorTransform,
-                                              const nsIntRegion& aObscured,
-                                              AsyncPanZoomController* aParent,
-                                              AsyncPanZoomController* aNextSibling,
-                                              TreeBuildingState& aState);
+  already_AddRefed<HitTestingTreeNode> RecycleOrCreateNode(TreeBuildingState& aState,
+                                                           AsyncPanZoomController* aApzc);
+  HitTestingTreeNode* PrepareNodeForLayer(const LayerMetricsWrapper& aLayer,
+                                          const FrameMetrics& aMetrics,
+                                          uint64_t aLayersId,
+                                          const gfx::Matrix4x4& aAncestorTransform,
+                                          HitTestingTreeNode* aParent,
+                                          HitTestingTreeNode* aNextSibling,
+                                          TreeBuildingState& aState);
 
   /**
-   * Recursive helper function to build the APZC tree. The tree of APZC instances has
-   * the same shape as the layer tree, but excludes all the layers that are not scrollable.
-   * Note that this means APZCs corresponding to layers at different depths in the tree
-   * may end up becoming siblings. It also means that the "root" APZC may have siblings.
-   * This function walks the layer tree backwards through siblings and constructs the APZC
-   * tree also as a last-child-prev-sibling tree because that simplifies the hit detection
-   * code.
+   * Recursive helper function to build the hit-testing tree. See documentation
+   * in HitTestingTreeNode.h for more details on the shape of the tree.
+   * This function walks the layer tree backwards through siblings and
+   * constructs the hit-testing tree also as a last-child-prev-sibling tree
+   * because that simplifies the hit detection code.
+   *
+   * @param aState The current tree building state.
+   * @param aLayer The (layer, metrics) pair which is the current position in
+   *               the recursive walk of the layer tree. This call builds a
+   *               hit-testing subtree corresponding to the layer subtree rooted
+   *               at aLayer.
+   * @param aLayersId The layers id of the layer in aLayer.
+   * @param aAncestorTransform The accumulated CSS transforms of all the
+   *                           layers from aLayer up (via the parent chain)
+   *                           to the next APZC-bearing layer.
+   * @param aParent The parent of any node built at this level.
+   * @param aNextSibling The next sibling of any node built at this level.
+   * @return The HitTestingTreeNode created at this level. This will always
+   *         be non-null.
    */
-  AsyncPanZoomController* UpdatePanZoomControllerTree(TreeBuildingState& aState,
-                                                      const LayerMetricsWrapper& aLayer,
-                                                      uint64_t aLayersId,
-                                                      const gfx::Matrix4x4& aAncestorTransform,
-                                                      AsyncPanZoomController* aParent,
-                                                      AsyncPanZoomController* aNextSibling,
-                                                      const nsIntRegion& aObscured);
+  HitTestingTreeNode* UpdateHitTestingTree(TreeBuildingState& aState,
+                                           const LayerMetricsWrapper& aLayer,
+                                           uint64_t aLayersId,
+                                           const gfx::Matrix4x4& aAncestorTransform,
+                                           HitTestingTreeNode* aParent,
+                                           HitTestingTreeNode* aNextSibling);
 
   void PrintAPZCInfo(const LayerMetricsWrapper& aLayer,
                      const AsyncPanZoomController* apzc);
 
 protected:
   /* The input queue where input events are held until we know enough to
    * figure out where they're going. Protected so gtests can access it.
    */
   nsRefPtr<InputQueue> mInputQueue;
 
 private:
-  /* Whenever walking or mutating the tree rooted at mRootApzc, mTreeLock must be held.
+  /* Whenever walking or mutating the tree rooted at mRootNode, mTreeLock must be held.
    * This lock does not need to be held while manipulating a single APZC instance in
    * isolation (that is, if its tree pointers are not being accessed or mutated). The
-   * lock also needs to be held when accessing the mRootApzc instance variable, as that
+   * lock also needs to be held when accessing the mRootNode instance variable, as that
    * is considered part of the APZC tree management state.
    * Finally, the lock needs to be held when accessing mOverscrollHandoffChain.
    * IMPORTANT: See the note about lock ordering at the top of this file. */
   mutable mozilla::Monitor mTreeLock;
-  nsRefPtr<AsyncPanZoomController> mRootApzc;
+  nsRefPtr<HitTestingTreeNode> mRootNode;
   /* This tracks the APZC that should receive all inputs for the current input event block.
    * This allows touch points to move outside the thing they started on, but still have the
    * touch events delivered to the same initial APZC. This will only ever be touched on the
    * input delivery thread, and so does not require locking.
    */
   nsRefPtr<AsyncPanZoomController> mApzcForInputBlock;
   /* The hit result for the current input event block; this should always be in
    * sync with mApzcForInputBlock.
new file mode 100644
--- /dev/null
+++ b/gfx/layers/apz/src/APZUtils.h
@@ -0,0 +1,23 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set sw=2 ts=8 et tw=80 : */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_layers_APZUtils_h
+#define mozilla_layers_APZUtils_h
+
+namespace mozilla {
+namespace layers {
+
+enum HitTestResult {
+  NoApzcHit,
+  ApzcHitRegion,
+  ApzcContentRegion,
+  OverscrolledApzc,
+};
+
+}
+}
+
+#endif // mozilla_layers_APZUtils_h
--- a/gfx/layers/apz/src/AsyncPanZoomController.cpp
+++ b/gfx/layers/apz/src/AsyncPanZoomController.cpp
@@ -911,17 +911,19 @@ AsyncPanZoomController::AsyncPanZoomCont
      mSharedLock(nullptr),
      mAsyncTransformAppliedToContent(false)
 {
   if (aGestures == USE_GESTURE_DETECTOR) {
     mGestureEventListener = new GestureEventListener(this);
   }
 }
 
-AsyncPanZoomController::~AsyncPanZoomController() {
+AsyncPanZoomController::~AsyncPanZoomController()
+{
+  MOZ_ASSERT(IsDestroyed());
 }
 
 PCompositorParent*
 AsyncPanZoomController::GetSharedFrameMetricsCompositor()
 {
   AssertOnCompositorThread();
 
   if (mSharingFrameMetricsAcrossProcesses) {
@@ -958,18 +960,16 @@ AsyncPanZoomController::Destroy()
 
   CancelAnimation();
 
   { // scope the lock
     MonitorAutoLock lock(mRefPtrMonitor);
     mGeckoContentController = nullptr;
     mGestureEventListener = nullptr;
   }
-  mPrevSibling = nullptr;
-  mLastChild = nullptr;
   mParent = nullptr;
   mTreeManager = nullptr;
 
   PCompositorParent* compositor = GetSharedFrameMetricsCompositor();
   // Only send the release message if the SharedFrameMetrics has been created.
   if (compositor && mSharedFrameMetricsBuffer) {
     unused << compositor->SendReleaseSharedCompositorFrameMetrics(mFrameMetrics.GetScrollId(), mAPZCId);
   }
--- a/gfx/layers/apz/src/AsyncPanZoomController.h
+++ b/gfx/layers/apz/src/AsyncPanZoomController.h
@@ -840,51 +840,27 @@ private:
                    bool aHandoff);
 
   // Start an overscroll animation with the given initial velocity.
   void StartOverscrollAnimation(const ParentLayerPoint& aVelocity);
 
   void StartSmoothScroll();
 
   /* ===================================================================
-   * The functions and members in this section are used to build a tree
-   * structure out of APZC instances. This tree can only be walked or
-   * manipulated while holding the lock in the associated APZCTreeManager
-   * instance.
+   * The functions and members in this section are used to make ancestor chains
+   * out of APZC instances. These chains can only be walked or manipulated
+   * while holding the lock in the associated APZCTreeManager instance.
    */
 public:
-  void SetLastChild(AsyncPanZoomController* child) {
-    mLastChild = child;
-    if (child) {
-      child->mParent = this;
-    }
+  void SetParent(AsyncPanZoomController* aParent) {
+    mParent = aParent;
   }
 
-  void SetPrevSibling(AsyncPanZoomController* sibling) {
-    mPrevSibling = sibling;
-    if (sibling) {
-      sibling->mParent = mParent;
-    }
-  }
-
-  // Make this APZC the root of the APZC tree. Clears the parent pointer.
-  void MakeRoot() {
-    mParent = nullptr;
-  }
-
-  AsyncPanZoomController* GetLastChild() const { return mLastChild; }
-  AsyncPanZoomController* GetPrevSibling() const { return mPrevSibling; }
-  AsyncPanZoomController* GetParent() const { return mParent; }
-
-  AsyncPanZoomController* GetFirstChild() const {
-    AsyncPanZoomController* child = GetLastChild();
-    while (child && child->GetPrevSibling()) {
-      child = child->GetPrevSibling();
-    }
-    return child;
+  AsyncPanZoomController* GetParent() const {
+    return mParent;
   }
 
   /* Returns true if there is no APZC higher in the tree with the same
    * layers id.
    */
   bool IsRootForLayersId() const {
     return !mParent || (mParent->mLayersId != mLayersId);
   }
@@ -892,18 +868,16 @@ public:
 private:
   // This is a raw pointer to avoid introducing a reference cycle between
   // AsyncPanZoomController and APZCTreeManager. Since these objects don't
   // live on the main thread, we can't use the cycle collector with them.
   // The APZCTreeManager owns the lifetime of the APZCs, so nulling this
   // pointer out in Destroy() will prevent accessing deleted memory.
   Atomic<APZCTreeManager*> mTreeManager;
 
-  nsRefPtr<AsyncPanZoomController> mLastChild;
-  nsRefPtr<AsyncPanZoomController> mPrevSibling;
   nsRefPtr<AsyncPanZoomController> mParent;
 
 
   /* ===================================================================
    * The functions and members in this section are used for scrolling,
    * including handing off scroll to another APZC, and overscrolling.
    */
 public:
@@ -991,49 +965,29 @@ private:
 
 
   /* ===================================================================
    * The functions and members in this section are used to maintain the
    * area that this APZC instance is responsible for. This is used when
    * hit-testing to see which APZC instance should handle touch events.
    */
 public:
-  void SetLayerHitTestData(const EventRegions& aRegions, const Matrix4x4& aTransformToLayer) {
-    mEventRegions = aRegions;
+  void SetAncestorTransform(const Matrix4x4& aTransformToLayer) {
     mAncestorTransform = aTransformToLayer;
   }
 
-  void AddHitTestRegions(const EventRegions& aRegions) {
-    mEventRegions.OrWith(aRegions);
-  }
-
   Matrix4x4 GetAncestorTransform() const {
     return mAncestorTransform;
   }
 
-  bool HitRegionContains(const ParentLayerPoint& aPoint) const {
-    ParentLayerIntPoint point = RoundedToInt(aPoint);
-    return mEventRegions.mHitRegion.Contains(point.x, point.y);
-  }
-
-  bool DispatchToContentRegionContains(const ParentLayerPoint& aPoint) const {
-    ParentLayerIntPoint point = RoundedToInt(aPoint);
-    return mEventRegions.mDispatchToContentHitRegion.Contains(point.x, point.y);
-  }
-
   bool IsOverscrolled() const {
     return mX.IsOverscrolled() || mY.IsOverscrolled();
   }
 
 private:
-  /* This is the union of the hit regions of the layers that this APZC
-   * corresponds to, in the local screen pixels of those layers. (This is the
-   * same coordinate system in which this APZC receives events in
-   * ReceiveInputEvent()). */
-  EventRegions mEventRegions;
   /* This is the cumulative CSS transform for all the layers from (and including)
    * the parent APZC down to (but excluding) this one. */
   Matrix4x4 mAncestorTransform;
 
 
   /* ===================================================================
    * The functions and members in this section are used for sharing the
    * FrameMetrics across processes for the progressive tiling code.
new file mode 100644
--- /dev/null
+++ b/gfx/layers/apz/src/HitTestingTreeNode.cpp
@@ -0,0 +1,246 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set sw=2 ts=8 et tw=80 : */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "HitTestingTreeNode.h"
+
+#include "AsyncPanZoomController.h"                     // for AsyncPanZoomController
+#include "LayersLogging.h"                              // for Stringify
+#include "mozilla/gfx/Point.h"                          // for Point4D
+#include "mozilla/layers/AsyncCompositionManager.h"     // for ViewTransform::operator Matrix4x4()
+#include "nsPrintfCString.h"                            // for nsPrintfCString
+#include "UnitTransforms.h"                             // for ViewAs
+
+namespace mozilla {
+namespace layers {
+
+HitTestingTreeNode::HitTestingTreeNode(AsyncPanZoomController* aApzc,
+                                       bool aIsPrimaryHolder)
+  : mApzc(aApzc)
+  , mIsPrimaryApzcHolder(aIsPrimaryHolder)
+{
+  if (mIsPrimaryApzcHolder) {
+    MOZ_ASSERT(mApzc);
+  }
+}
+
+void
+HitTestingTreeNode::RecycleWith(AsyncPanZoomController* aApzc)
+{
+  MOZ_ASSERT(!mIsPrimaryApzcHolder);
+  Destroy(); // clear out tree pointers
+  mApzc = aApzc;
+  // The caller is expected to call SetHitTestData to repopulate the hit-test
+  // fields.
+}
+
+HitTestingTreeNode::~HitTestingTreeNode()
+{
+}
+
+void
+HitTestingTreeNode::Destroy()
+{
+  AsyncPanZoomController::AssertOnCompositorThread();
+
+  mPrevSibling = nullptr;
+  mLastChild = nullptr;
+  mParent = nullptr;
+
+  if (mApzc) {
+    if (mIsPrimaryApzcHolder) {
+      mApzc->Destroy();
+    }
+    mApzc = nullptr;
+  }
+}
+
+void
+HitTestingTreeNode::SetLastChild(HitTestingTreeNode* aChild)
+{
+  mLastChild = aChild;
+  if (aChild) {
+    aChild->mParent = this;
+
+    if (aChild->GetApzc()) {
+      AsyncPanZoomController* parent = GetNearestContainingApzc();
+      // We assume that HitTestingTreeNodes with an ancestor/descendant
+      // relationship cannot both point to the same APZC instance. This
+      // assertion only covers a subset of cases in which that might occur,
+      // but it's better than nothing.
+      MOZ_ASSERT(aChild->GetApzc() != parent);
+      aChild->SetApzcParent(parent);
+    }
+  }
+}
+
+void
+HitTestingTreeNode::SetPrevSibling(HitTestingTreeNode* aSibling)
+{
+  mPrevSibling = aSibling;
+  if (aSibling) {
+    aSibling->mParent = mParent;
+
+    if (aSibling->GetApzc()) {
+      AsyncPanZoomController* parent = mParent ? mParent->GetNearestContainingApzc() : nullptr;
+      aSibling->SetApzcParent(parent);
+    }
+  }
+}
+
+void
+HitTestingTreeNode::MakeRoot()
+{
+  mParent = nullptr;
+
+  if (GetApzc()) {
+    SetApzcParent(nullptr);
+  }
+}
+
+HitTestingTreeNode*
+HitTestingTreeNode::GetFirstChild() const
+{
+  HitTestingTreeNode* child = GetLastChild();
+  while (child && child->GetPrevSibling()) {
+    child = child->GetPrevSibling();
+  }
+  return child;
+}
+
+HitTestingTreeNode*
+HitTestingTreeNode::GetLastChild() const
+{
+  return mLastChild;
+}
+
+HitTestingTreeNode*
+HitTestingTreeNode::GetPrevSibling() const
+{
+  return mPrevSibling;
+}
+
+HitTestingTreeNode*
+HitTestingTreeNode::GetParent() const
+{
+  return mParent;
+}
+
+AsyncPanZoomController*
+HitTestingTreeNode::GetApzc() const
+{
+  return mApzc;
+}
+
+AsyncPanZoomController*
+HitTestingTreeNode::GetNearestContainingApzc() const
+{
+  for (const HitTestingTreeNode* n = this; n; n = n->GetParent()) {
+    if (n->GetApzc()) {
+      return n->GetApzc();
+    }
+  }
+  return nullptr;
+}
+
+bool
+HitTestingTreeNode::IsPrimaryHolder() const
+{
+  return mIsPrimaryApzcHolder;
+}
+
+void
+HitTestingTreeNode::SetHitTestData(const EventRegions& aRegions,
+                                   const gfx::Matrix4x4& aTransform,
+                                   const Maybe<nsIntRegion>& aClipRegion)
+{
+  mEventRegions = aRegions;
+  mTransform = aTransform;
+  mClipRegion = aClipRegion;
+}
+
+bool
+HitTestingTreeNode::IsOutsideClip(const ParentLayerPoint& aPoint) const
+{
+  // test against clip rect in ParentLayer coordinate space
+  return (mClipRegion.isSome() && !mClipRegion->Contains(aPoint.x, aPoint.y));
+}
+
+Maybe<LayerPoint>
+HitTestingTreeNode::Untransform(const ParentLayerPoint& aPoint) const
+{
+  // convert into Layer coordinate space
+  gfx::Matrix4x4 localTransform = mTransform;
+  if (mApzc) {
+    localTransform = localTransform * gfx::Matrix4x4(mApzc->GetCurrentAsyncTransform());
+  }
+  gfx::Point4D point = localTransform.Inverse().ProjectPoint(aPoint.ToUnknownPoint());
+  return point.HasPositiveWCoord()
+        ? Some(ViewAs<LayerPixel>(point.As2DPoint()))
+        : Nothing();
+}
+
+HitTestResult
+HitTestingTreeNode::HitTest(const ParentLayerPoint& aPoint) const
+{
+  // This should only ever get called if the point is inside the clip region
+  // for this node.
+  MOZ_ASSERT(!IsOutsideClip(aPoint));
+
+  // When event regions are disabled and we have an APZC on this node, we are
+  // actually storing the touch-sensitive section of the composition bounds in
+  // the clip region, and we don't need to check against the mEventRegions.
+  // If there's no APZC, then we do need to check against the mEventRegions
+  // (which contains the layer's visible region) for obscuration purposes.
+  if (!gfxPrefs::LayoutEventRegionsEnabled() && GetApzc()) {
+    return HitTestResult::ApzcHitRegion;
+  }
+
+  // convert into Layer coordinate space
+  Maybe<LayerPoint> pointInLayerPixels = Untransform(aPoint);
+  if (!pointInLayerPixels) {
+    return HitTestResult::NoApzcHit;
+  }
+  LayerIntPoint point = RoundedToInt(pointInLayerPixels.ref());
+
+  // test against event regions in Layer coordinate space
+  if (!mEventRegions.mHitRegion.Contains(point.x, point.y)) {
+    return HitTestResult::NoApzcHit;
+  }
+  if (mEventRegions.mDispatchToContentHitRegion.Contains(point.x, point.y)) {
+    return HitTestResult::ApzcContentRegion;
+  }
+  return HitTestResult::ApzcHitRegion;
+}
+
+void
+HitTestingTreeNode::Dump(const char* aPrefix) const
+{
+  if (mPrevSibling) {
+    mPrevSibling->Dump(aPrefix);
+  }
+  printf_stderr("%sHitTestingTreeNode (%p) APZC (%p) g=(%s) r=(%s) t=(%s) c=(%s)\n",
+    aPrefix, this, mApzc.get(), mApzc ? Stringify(mApzc->GetGuid()).c_str() : "",
+    Stringify(mEventRegions).c_str(), Stringify(mTransform).c_str(),
+    mClipRegion ? Stringify(mClipRegion.ref()).c_str() : "none");
+  if (mLastChild) {
+    mLastChild->Dump(nsPrintfCString("%s  ", aPrefix).get());
+  }
+}
+
+void
+HitTestingTreeNode::SetApzcParent(AsyncPanZoomController* aParent)
+{
+  // precondition: GetApzc() is non-null
+  MOZ_ASSERT(GetApzc() != nullptr);
+  if (IsPrimaryHolder()) {
+    GetApzc()->SetParent(aParent);
+  } else {
+    MOZ_ASSERT(GetApzc()->GetParent() == aParent);
+  }
+}
+
+} // namespace layers
+} // namespace mozilla
new file mode 100644
--- /dev/null
+++ b/gfx/layers/apz/src/HitTestingTreeNode.h
@@ -0,0 +1,130 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set sw=2 ts=8 et tw=80 : */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_layers_HitTestingTreeNode_h
+#define mozilla_layers_HitTestingTreeNode_h
+
+#include "APZUtils.h"                       // for HitTestResult
+#include "FrameMetrics.h"                   // for ScrollableLayerGuid
+#include "mozilla/gfx/Matrix.h"             // for Matrix4x4
+#include "mozilla/layers/LayersTypes.h"     // for EventRegions
+#include "mozilla/Maybe.h"                  // for Maybe
+#include "nsRefPtr.h"                       // for nsRefPtr
+
+namespace mozilla {
+namespace layers {
+
+class AsyncPanZoomController;
+
+/**
+ * This class represents a node in a tree that is used by the APZCTreeManager
+ * to do hit testing. The tree is roughly a copy of the layer tree, but will
+ * contain multiple nodes in cases where the layer has multiple FrameMetrics.
+ * In other words, the structure of this tree should be identical to the
+ * LayerMetrics tree (see documentation in LayerMetricsWrapper.h).
+ *
+ * Not all HitTestingTreeNode instances will have an APZC associated with them;
+ * only HitTestingTreeNodes that correspond to layers with scrollable metrics
+ * have APZCs.
+ * Multiple HitTestingTreeNode instances may share the same underlying APZC
+ * instance if the layers they represent share the same scrollable metrics (i.e.
+ * are part of the same animated geometry root). If this happens, exactly one of
+ * the HitTestingTreeNode instances will be designated as the "primary holder"
+ * of the APZC. When this primary holder is destroyed, it will destroy the APZC
+ * along with it; in contrast, destroying non-primary-holder nodes will not
+ * destroy the APZC.
+ * Code should not make assumptions about which of the nodes will be the
+ * primary holder, only that that there will be exactly one for each APZC in
+ * the tree.
+ *
+ * The reason this tree exists at all is so that we can do hit-testing on the
+ * thread that we receive input on (referred to the as the controller thread in
+ * APZ terminology), which may be different from the compositor thread.
+ * Accessing the compositor layer tree can only be done on the compositor
+ * thread, and so it is simpler to make a copy of the hit-testing related
+ * properties into a separate tree.
+ */
+class HitTestingTreeNode {
+  NS_INLINE_DECL_THREADSAFE_REFCOUNTING(HitTestingTreeNode);
+
+private:
+  ~HitTestingTreeNode();
+public:
+  HitTestingTreeNode(AsyncPanZoomController* aApzc, bool aIsPrimaryHolder);
+  void RecycleWith(AsyncPanZoomController* aApzc);
+  void Destroy();
+
+  /* Tree construction methods */
+
+  void SetLastChild(HitTestingTreeNode* aChild);
+  void SetPrevSibling(HitTestingTreeNode* aSibling);
+  void MakeRoot();
+
+  /* Tree walking methods. GetFirstChild is O(n) in the number of children. The
+   * other tree walking methods are all O(1). */
+
+  HitTestingTreeNode* GetFirstChild() const;
+  HitTestingTreeNode* GetLastChild() const;
+  HitTestingTreeNode* GetPrevSibling() const;
+  HitTestingTreeNode* GetParent() const;
+
+  /* APZC related methods */
+
+  AsyncPanZoomController* GetApzc() const;
+  AsyncPanZoomController* GetNearestContainingApzc() const;
+  bool IsPrimaryHolder() const;
+
+  /* Hit test related methods */
+
+  void SetHitTestData(const EventRegions& aRegions,
+                      const gfx::Matrix4x4& aTransform,
+                      const Maybe<nsIntRegion>& aClipRegion);
+  bool IsOutsideClip(const ParentLayerPoint& aPoint) const;
+  /* Convert aPoint into the LayerPixel space for the layer corresponding to
+   * this node. */
+  Maybe<LayerPoint> Untransform(const ParentLayerPoint& aPoint) const;
+  /* Assuming aPoint is inside the clip region for this node, check which of the
+   * event region spaces it falls inside. */
+  HitTestResult HitTest(const ParentLayerPoint& aPoint) const;
+
+  /* Debug helpers */
+  void Dump(const char* aPrefix = "") const;
+
+private:
+  void SetApzcParent(AsyncPanZoomController* aApzc);
+
+  nsRefPtr<HitTestingTreeNode> mLastChild;
+  nsRefPtr<HitTestingTreeNode> mPrevSibling;
+  nsRefPtr<HitTestingTreeNode> mParent;
+
+  nsRefPtr<AsyncPanZoomController> mApzc;
+  bool mIsPrimaryApzcHolder;
+
+  /* Let {L,M} be the {layer, scrollable metrics} pair that this node
+   * corresponds to in the layer tree. mEventRegions contains the event regions
+   * from L, in the case where event-regions are enabled. If event-regions are
+   * disabled, it will contain the visible region of L, which we use as an
+   * approximation to the hit region for the purposes of obscuring other layers.
+   * This value is in L's LayerPixels.
+   */
+  EventRegions mEventRegions;
+
+  /* This is the transform from layer L. This does NOT include any async
+   * transforms. */
+  gfx::Matrix4x4 mTransform;
+
+  /* This is clip rect for L that we wish to use for hit-testing purposes. Note
+   * that this may not be exactly the same as the clip rect on layer L because
+   * of the touch-sensitive region provided by the GeckoContentController, or
+   * because we may use the composition bounds of the layer if the clip is not
+   * present. This value is in L's ParentLayerPixels. */
+  Maybe<nsIntRegion> mClipRegion;
+};
+
+}
+}
+
+#endif // mozilla_layers_HitTestingTreeNode_h
--- a/gfx/layers/client/TiledContentClient.cpp
+++ b/gfx/layers/client/TiledContentClient.cpp
@@ -22,17 +22,17 @@
 #include "mozilla/layers/LayerMetricsWrapper.h"
 #include "mozilla/layers/ShadowLayers.h"  // for ShadowLayerForwarder
 #include "TextureClientPool.h"
 #include "nsDebug.h"                    // for NS_ASSERTION
 #include "nsISupportsImpl.h"            // for gfxContext::AddRef, etc
 #include "nsSize.h"                     // for nsIntSize
 #include "gfxReusableSharedImageSurfaceWrapper.h"
 #include "nsExpirationTracker.h"        // for nsExpirationTracker
-#include "nsMathUtils.h"               // for NS_roundf
+#include "nsMathUtils.h"               // for NS_lroundf
 #include "gfx2DGlue.h"
 #include "LayersLogging.h"
 #include "UnitTransforms.h"             // for TransformTo
 #include "mozilla/UniquePtr.h"
 
 // This is the minimum area that we deem reasonable to copy from the front buffer to the
 // back buffer on tile updates. If the valid region is smaller than this, we just
 // redraw it and save on the copy (and requisite surface-locking involved).
@@ -1184,17 +1184,17 @@ ClientTiledLayerBuffer::ValidateTile(Til
       gfx::Rect drawRect(dirtyRect->x - aTileOrigin.x,
                          dirtyRect->y - aTileOrigin.y,
                          dirtyRect->width,
                          dirtyRect->height);
       drawRect.Scale(mResolution);
 
       // Mark the newly updated area as invalid in the front buffer
       aTile.mInvalidFront.Or(aTile.mInvalidFront,
-        nsIntRect(NS_roundf(drawRect.x), NS_roundf(drawRect.y),
+        nsIntRect(NS_lroundf(drawRect.x), NS_lroundf(drawRect.y),
                   drawRect.width, drawRect.height));
 
       if (mode == SurfaceMode::SURFACE_COMPONENT_ALPHA) {
         dt->FillRect(drawRect, ColorPattern(Color(0.0, 0.0, 0.0, 1.0)));
         dtOnWhite->FillRect(drawRect, ColorPattern(Color(1.0, 1.0, 1.0, 1.0)));
       } else if (content == gfxContentType::COLOR_ALPHA) {
         dt->ClearRect(drawRect);
       }
@@ -1230,21 +1230,21 @@ ClientTiledLayerBuffer::ValidateTile(Til
                   dirtyRect->x, dirtyRect->y, dirtyRect->width, dirtyRect->height);
 #endif
     gfx::Rect drawRect(dirtyRect->x - aTileOrigin.x,
                        dirtyRect->y - aTileOrigin.y,
                        dirtyRect->width,
                        dirtyRect->height);
     drawRect.Scale(mResolution);
 
-    gfx::IntRect copyRect(NS_roundf((dirtyRect->x - mSinglePaintBufferOffset.x) * mResolution),
-                          NS_roundf((dirtyRect->y - mSinglePaintBufferOffset.y) * mResolution),
+    gfx::IntRect copyRect(NS_lroundf((dirtyRect->x - mSinglePaintBufferOffset.x) * mResolution),
+                          NS_lroundf((dirtyRect->y - mSinglePaintBufferOffset.y) * mResolution),
                           drawRect.width,
                           drawRect.height);
-    gfx::IntPoint copyTarget(NS_roundf(drawRect.x), NS_roundf(drawRect.y));
+    gfx::IntPoint copyTarget(NS_lroundf(drawRect.x), NS_lroundf(drawRect.y));
     drawTarget->CopySurface(source, copyRect, copyTarget);
 
     // Mark the newly updated area as invalid in the front buffer
     aTile.mInvalidFront.Or(aTile.mInvalidFront, nsIntRect(copyTarget.x, copyTarget.y, copyRect.width, copyRect.height));
   }
 
   // only worry about padding when not doing low-res
   // because it simplifies the math and the artifacts
@@ -1484,16 +1484,21 @@ ClientTiledLayerBuffer::ComputeProgressi
   int32_t scrollDiffX = aPaintData->mScrollOffset.x - aPaintData->mLastScrollOffset.x;
   int32_t scrollDiffY = aPaintData->mScrollOffset.y - aPaintData->mLastScrollOffset.y;
   // This loop will always terminate, as there is at least one tile area
   // along the first/last row/column intersecting with regionToPaint, or its
   // bounds would have been smaller.
   while (true) {
     aRegionToPaint.And(aInvalidRegion, tileBounds);
     if (!aRegionToPaint.IsEmpty()) {
+      if (mResolution != 1) {
+        // Paint the entire tile for low-res. This is aimed to fixing low-res resampling
+        // and to avoid doing costly region accurate painting for a small area.
+        aRegionToPaint = tileBounds;
+      }
       break;
     }
     if (Abs(scrollDiffY) >= Abs(scrollDiffX)) {
       tileBounds.x += incX;
     } else {
       tileBounds.y += incY;
     }
   }
--- a/gfx/layers/ipc/CompositorParent.cpp
+++ b/gfx/layers/ipc/CompositorParent.cpp
@@ -728,17 +728,17 @@ CompositorParent::NotifyShadowTreeTransa
     bool aScheduleComposite, uint32_t aPaintSequenceNumber,
     bool aIsRepeatTransaction)
 {
   if (mApzcTreeManager &&
       !aIsRepeatTransaction &&
       mLayerManager &&
       mLayerManager->GetRoot()) {
     AutoResolveRefLayers resolve(mCompositionManager);
-    mApzcTreeManager->UpdatePanZoomControllerTree(this, mLayerManager->GetRoot(),
+    mApzcTreeManager->UpdateHitTestingTree(this, mLayerManager->GetRoot(),
         aIsFirstPaint, aId, aPaintSequenceNumber);
 
     mLayerManager->NotifyShadowTreeTransaction();
   }
   if (aScheduleComposite) {
     ScheduleComposition();
   }
 }
@@ -1001,17 +1001,17 @@ CompositorParent::ShadowLayersUpdated(La
   mLayerManager->SetRegionToClear(aTargetConfig.clearRegion());
 
   mCompositionManager->Updated(aIsFirstPaint, aTargetConfig);
   Layer* root = aLayerTree->GetRoot();
   mLayerManager->SetRoot(root);
 
   if (mApzcTreeManager && !aIsRepeatTransaction) {
     AutoResolveRefLayers resolve(mCompositionManager);
-    mApzcTreeManager->UpdatePanZoomControllerTree(this, root, aIsFirstPaint,
+    mApzcTreeManager->UpdateHitTestingTree(this, root, aIsFirstPaint,
         mRootLayerTreeID, aPaintSequenceNumber);
   }
 
   MOZ_ASSERT(aTransactionId > mPendingTransaction);
   mPendingTransaction = aTransactionId;
 
   if (root) {
     SetShadowProperties(root);
--- a/gfx/layers/moz.build
+++ b/gfx/layers/moz.build
@@ -103,16 +103,17 @@ EXPORTS.gfxipc += [
     'ipc/ShadowLayerUtils.h',
 ]
 
 EXPORTS.mozilla.layers += [
     'apz/public/GeckoContentController.h',
     # exporting things from apz/src is temporary until we extract a
     # proper interface for the code there
     'apz/src/APZCTreeManager.h',
+    'apz/src/APZUtils.h',
     'apz/testutil/APZTestData.h',
     'apz/util/ActiveElementManager.h',
     'apz/util/APZCCallbackHelper.h',
     'apz/util/ChromeProcessController.h',
     'apz/util/InputAPZContext.h',
     'AtomicRefCountedWithFinalize.h',
     'AxisPhysicsModel.h',
     'AxisPhysicsMSDModel.h',
@@ -232,16 +233,17 @@ if CONFIG['MOZ_WIDGET_TOOLKIT'] == 'gonk
             'ipc/FenceUtilsGonk.cpp',
         ]
 
 UNIFIED_SOURCES += [
     'apz/src/APZCTreeManager.cpp',
     'apz/src/AsyncPanZoomController.cpp',
     'apz/src/Axis.cpp',
     'apz/src/GestureEventListener.cpp',
+    'apz/src/HitTestingTreeNode.cpp',
     'apz/src/InputBlockState.cpp',
     'apz/src/InputQueue.cpp',
     'apz/src/OverscrollHandoffState.cpp',
     'apz/src/TaskThrottler.cpp',
     'apz/testutil/APZTestData.cpp',
     'apz/util/ActiveElementManager.cpp',
     'apz/util/APZCCallbackHelper.cpp',
     'apz/util/ChromeProcessController.cpp',
--- a/gfx/tests/gtest/TestAsyncPanZoomController.cpp
+++ b/gfx/tests/gtest/TestAsyncPanZoomController.cpp
@@ -9,16 +9,17 @@
 #include "mozilla/Attributes.h"
 #include "mozilla/layers/AsyncCompositionManager.h" // for ViewTransform
 #include "mozilla/layers/GeckoContentController.h"
 #include "mozilla/layers/CompositorParent.h"
 #include "mozilla/layers/APZCTreeManager.h"
 #include "mozilla/layers/LayerMetricsWrapper.h"
 #include "mozilla/UniquePtr.h"
 #include "apz/src/AsyncPanZoomController.h"
+#include "apz/src/HitTestingTreeNode.h"
 #include "base/task.h"
 #include "Layers.h"
 #include "TestLayers.h"
 #include "gfxPrefs.h"
 
 using namespace mozilla;
 using namespace mozilla::gfx;
 using namespace mozilla::layers;
@@ -870,16 +871,18 @@ TEST_F(APZCBasicTester, ComplexTransform
   EXPECT_EQ(ViewTransform(LayerToParentLayerScale(1.5), ParentLayerPoint(-45, 0)), viewTransformOut);
   EXPECT_EQ(ParentLayerPoint(135, 90), pointOut);
 
   childMetrics.ZoomBy(1.5f);
   childApzc->SetFrameMetrics(childMetrics);
   childApzc->SampleContentTransformForFrame(testStartTime, &viewTransformOut, pointOut);
   EXPECT_EQ(ViewTransform(LayerToParentLayerScale(1.5), ParentLayerPoint(-45, 0)), viewTransformOut);
   EXPECT_EQ(ParentLayerPoint(135, 90), pointOut);
+
+  childApzc->Destroy();
 }
 
 class APZCPanningTester : public APZCBasicTester {
 protected:
   void DoPanTest(bool aShouldTriggerScroll, bool aShouldBeConsumed, uint32_t aBehavior)
   {
     if (aShouldTriggerScroll) {
       EXPECT_CALL(*mcc, SendAsyncScrollDOMEvent(_,_,_)).Times(AtLeast(1));
@@ -1680,25 +1683,25 @@ protected:
     FrameMetrics metrics;
     metrics.SetScrollId(aScrollId);
     nsIntRect layerBound = aLayer->GetVisibleRegion().GetBounds();
     metrics.mCompositionBounds = ParentLayerRect(layerBound.x, layerBound.y,
                                                  layerBound.width, layerBound.height);
     metrics.SetScrollableRect(aScrollableRect);
     metrics.SetScrollOffset(CSSPoint(0, 0));
     aLayer->SetFrameMetrics(metrics);
+    aLayer->SetClipRect(&layerBound);
     if (!aScrollableRect.IsEqualEdges(CSSRect(-1, -1, -1, -1))) {
       // The purpose of this is to roughly mimic what layout would do in the
       // case of a scrollable frame with the event regions and clip. This lets
       // us exercise the hit-testing code in APZCTreeManager
       EventRegions er = aLayer->GetEventRegions();
       nsIntRect scrollRect = LayerIntRect::ToUntyped(RoundedToInt(aScrollableRect * metrics.LayersPixelsPerCSSPixel()));
       er.mHitRegion = nsIntRegion(nsIntRect(layerBound.TopLeft(), scrollRect.Size()));
       aLayer->SetEventRegions(er);
-      aLayer->SetClipRect(&layerBound);
     }
   }
 
   void SetScrollHandoff(Layer* aChild, Layer* aParent) {
     FrameMetrics metrics = aChild->GetFrameMetrics(0);
     metrics.SetScrollParentId(aParent->GetFrameMetrics(0).GetScrollId());
     aChild->SetFrameMetrics(metrics);
   }
@@ -1827,41 +1830,41 @@ TEST_F(APZHitTestingTester, HitTesting1)
   EXPECT_EQ(nullAPZC, hit.get());
   EXPECT_EQ(Matrix4x4(), transformToApzc);
   EXPECT_EQ(Matrix4x4(), transformToGecko);
 
   uint32_t paintSequenceNumber = 0;
 
   // Now we have a root APZC that will match the page
   SetScrollableFrameMetrics(root, FrameMetrics::START_SCROLL_ID);
-  manager->UpdatePanZoomControllerTree(nullptr, root, false, 0, paintSequenceNumber++);
+  manager->UpdateHitTestingTree(nullptr, root, false, 0, paintSequenceNumber++);
   hit = GetTargetAPZC(ScreenPoint(15, 15));
   EXPECT_EQ(ApzcOf(root), hit.get());
   // expect hit point at LayerIntPoint(15, 15)
   EXPECT_EQ(Point(15, 15), transformToApzc * Point(15, 15));
   EXPECT_EQ(Point(15, 15), transformToGecko * Point(15, 15));
 
   // Now we have a sub APZC with a better fit
   SetScrollableFrameMetrics(layers[3], FrameMetrics::START_SCROLL_ID + 1);
-  manager->UpdatePanZoomControllerTree(nullptr, root, false, 0, paintSequenceNumber++);
+  manager->UpdateHitTestingTree(nullptr, root, false, 0, paintSequenceNumber++);
   EXPECT_NE(ApzcOf(root), ApzcOf(layers[3]));
   hit = GetTargetAPZC(ScreenPoint(25, 25));
   EXPECT_EQ(ApzcOf(layers[3]), hit.get());
   // expect hit point at LayerIntPoint(25, 25)
   EXPECT_EQ(Point(25, 25), transformToApzc * Point(25, 25));
   EXPECT_EQ(Point(25, 25), transformToGecko * Point(25, 25));
 
   // At this point, layers[4] obscures layers[3] at the point (15, 15) so
   // hitting there should hit the root APZC
   hit = GetTargetAPZC(ScreenPoint(15, 15));
   EXPECT_EQ(ApzcOf(root), hit.get());
 
   // Now test hit testing when we have two scrollable layers
   SetScrollableFrameMetrics(layers[4], FrameMetrics::START_SCROLL_ID + 2);
-  manager->UpdatePanZoomControllerTree(nullptr, root, false, 0, paintSequenceNumber++);
+  manager->UpdateHitTestingTree(nullptr, root, false, 0, paintSequenceNumber++);
   hit = GetTargetAPZC(ScreenPoint(15, 15));
   EXPECT_EQ(ApzcOf(layers[4]), hit.get());
   // expect hit point at LayerIntPoint(15, 15)
   EXPECT_EQ(Point(15, 15), transformToApzc * Point(15, 15));
   EXPECT_EQ(Point(15, 15), transformToGecko * Point(15, 15));
 
   // Hit test ouside the reach of layer[3,4] but inside root
   hit = GetTargetAPZC(ScreenPoint(90, 90));
@@ -1881,17 +1884,17 @@ TEST_F(APZHitTestingTester, HitTesting1)
   EXPECT_EQ(Matrix4x4(), transformToGecko);
 }
 
 // A more involved hit testing test that involves css and async transforms.
 TEST_F(APZHitTestingTester, HitTesting2) {
   CreateHitTesting2LayerTree();
   ScopedLayerTreeRegistration registration(0, root, mcc);
 
-  manager->UpdatePanZoomControllerTree(nullptr, root, false, 0, 0);
+  manager->UpdateHitTestingTree(nullptr, root, false, 0, 0);
 
   // At this point, the following holds (all coordinates in screen pixels):
   // layers[0] has content from (0,0)-(200,200), clipped by composition bounds (0,0)-(100,100)
   // layers[1] has content from (10,10)-(90,90), clipped by composition bounds (10,10)-(50,50)
   // layers[2] has content from (20,60)-(100,100). no clipping as it's not a scrollable layer
   // layers[3] has content from (20,60)-(180,140), clipped by composition bounds (20,60)-(100,100)
 
   TestAsyncPanZoomController* apzcroot = ApzcOf(root);
@@ -1997,58 +2000,79 @@ TEST_F(APZHitTestingTester, HitTesting2)
 
 TEST_F(APZCTreeManagerTester, ScrollablePaintedLayers) {
   CreateSimpleMultiLayerTree();
   ScopedLayerTreeRegistration registration(0, root, mcc);
 
   // both layers have the same scrollId
   SetScrollableFrameMetrics(layers[1], FrameMetrics::START_SCROLL_ID);
   SetScrollableFrameMetrics(layers[2], FrameMetrics::START_SCROLL_ID);
-  manager->UpdatePanZoomControllerTree(nullptr, root, false, 0, 0);
+  manager->UpdateHitTestingTree(nullptr, root, false, 0, 0);
 
   TestAsyncPanZoomController* nullAPZC = nullptr;
   // so they should have the same APZC
   EXPECT_FALSE(layers[0]->HasScrollableFrameMetrics());
   EXPECT_NE(nullAPZC, ApzcOf(layers[1]));
   EXPECT_NE(nullAPZC, ApzcOf(layers[2]));
   EXPECT_EQ(ApzcOf(layers[1]), ApzcOf(layers[2]));
 
   // Change the scrollId of layers[1], and verify the APZC changes
   SetScrollableFrameMetrics(layers[1], FrameMetrics::START_SCROLL_ID + 1);
-  manager->UpdatePanZoomControllerTree(nullptr, root, false, 0, 0);
+  manager->UpdateHitTestingTree(nullptr, root, false, 0, 0);
   EXPECT_NE(ApzcOf(layers[1]), ApzcOf(layers[2]));
 
   // Change the scrollId of layers[2] to match that of layers[1], ensure we get the same
   // APZC for both again
   SetScrollableFrameMetrics(layers[2], FrameMetrics::START_SCROLL_ID + 1);
-  manager->UpdatePanZoomControllerTree(nullptr, root, false, 0, 0);
+  manager->UpdateHitTestingTree(nullptr, root, false, 0, 0);
   EXPECT_EQ(ApzcOf(layers[1]), ApzcOf(layers[2]));
 }
 
 TEST_F(APZCTreeManagerTester, Bug1068268) {
   CreatePotentiallyLeakingTree();
   ScopedLayerTreeRegistration registration(0, root, mcc);
 
-  manager->UpdatePanZoomControllerTree(nullptr, root, false, 0, 0);
-  EXPECT_EQ(ApzcOf(layers[2]), ApzcOf(layers[0])->GetLastChild());
-  EXPECT_EQ(ApzcOf(layers[2]), ApzcOf(layers[0])->GetFirstChild());
+  manager->UpdateHitTestingTree(nullptr, root, false, 0, 0);
+  nsRefPtr<HitTestingTreeNode> root = manager->GetRootNode();
+  nsRefPtr<HitTestingTreeNode> node2 = root->GetFirstChild()->GetFirstChild();
+  nsRefPtr<HitTestingTreeNode> node5 = root->GetLastChild()->GetLastChild();
+
+  EXPECT_EQ(ApzcOf(layers[2]), node5->GetApzc());
+  EXPECT_EQ(ApzcOf(layers[2]), node2->GetApzc());
   EXPECT_EQ(ApzcOf(layers[0]), ApzcOf(layers[2])->GetParent());
   EXPECT_EQ(ApzcOf(layers[2]), ApzcOf(layers[5]));
 
-  EXPECT_EQ(ApzcOf(layers[3]), ApzcOf(layers[2])->GetFirstChild());
-  EXPECT_EQ(ApzcOf(layers[6]), ApzcOf(layers[2])->GetLastChild());
-  EXPECT_EQ(ApzcOf(layers[3]), ApzcOf(layers[6])->GetPrevSibling());
+  EXPECT_EQ(node2->GetFirstChild(), node2->GetLastChild());
+  EXPECT_EQ(ApzcOf(layers[3]), node2->GetLastChild()->GetApzc());
+  EXPECT_EQ(node5->GetFirstChild(), node5->GetLastChild());
+  EXPECT_EQ(ApzcOf(layers[6]), node5->GetLastChild()->GetApzc());
   EXPECT_EQ(ApzcOf(layers[2]), ApzcOf(layers[3])->GetParent());
-  EXPECT_EQ(ApzcOf(layers[2]), ApzcOf(layers[6])->GetParent());
+  EXPECT_EQ(ApzcOf(layers[5]), ApzcOf(layers[6])->GetParent());
 }
 
 TEST_F(APZHitTestingTester, ComplexMultiLayerTree) {
   CreateComplexMultiLayerTree();
   ScopedLayerTreeRegistration registration(0, root, mcc);
-  manager->UpdatePanZoomControllerTree(nullptr, root, false, 0, 0);
+  manager->UpdateHitTestingTree(nullptr, root, false, 0, 0);
+
+  /* The layer tree looks like this:
+
+                0
+        |----|--+--|----|
+        1    2     4    5
+             |         /|\
+             3        6 8 9
+                      |
+                      7
+
+     Layers 1,2 have the same APZC
+     Layers 4,6,8 have the same APZC
+     Layer 7 has an APZC
+     Layer 9 has an APZC
+  */
 
   TestAsyncPanZoomController* nullAPZC = nullptr;
   // Ensure all the scrollable layers have an APZC
   EXPECT_FALSE(layers[0]->HasScrollableFrameMetrics());
   EXPECT_NE(nullAPZC, ApzcOf(layers[1]));
   EXPECT_NE(nullAPZC, ApzcOf(layers[2]));
   EXPECT_FALSE(layers[3]->HasScrollableFrameMetrics());
   EXPECT_NE(nullAPZC, ApzcOf(layers[4]));
@@ -2063,30 +2087,46 @@ TEST_F(APZHitTestingTester, ComplexMulti
   EXPECT_EQ(ApzcOf(layers[8]), ApzcOf(layers[6]));
   // Ensure those that don't scroll together have different APZCs
   EXPECT_NE(ApzcOf(layers[1]), ApzcOf(layers[4]));
   EXPECT_NE(ApzcOf(layers[1]), ApzcOf(layers[7]));
   EXPECT_NE(ApzcOf(layers[1]), ApzcOf(layers[9]));
   EXPECT_NE(ApzcOf(layers[4]), ApzcOf(layers[7]));
   EXPECT_NE(ApzcOf(layers[4]), ApzcOf(layers[9]));
   EXPECT_NE(ApzcOf(layers[7]), ApzcOf(layers[9]));
-
-  // Ensure the shape of the APZC tree is as expected
+  // Ensure the APZC parent chains are set up correctly
   TestAsyncPanZoomController* layers1_2 = ApzcOf(layers[1]);
   TestAsyncPanZoomController* layers4_6_8 = ApzcOf(layers[4]);
   TestAsyncPanZoomController* layer7 = ApzcOf(layers[7]);
   TestAsyncPanZoomController* layer9 = ApzcOf(layers[9]);
   EXPECT_EQ(nullptr, layers1_2->GetParent());
   EXPECT_EQ(nullptr, layers4_6_8->GetParent());
   EXPECT_EQ(layers4_6_8, layer7->GetParent());
   EXPECT_EQ(nullptr, layer9->GetParent());
-  EXPECT_EQ(nullptr, layers1_2->GetPrevSibling());
-  EXPECT_EQ(layers1_2, layers4_6_8->GetPrevSibling());
-  EXPECT_EQ(nullptr, layer7->GetPrevSibling());
-  EXPECT_EQ(layers4_6_8, layer9->GetPrevSibling());
+  // Ensure the hit-testing tree looks like the layer tree
+  nsRefPtr<HitTestingTreeNode> root = manager->GetRootNode();
+  nsRefPtr<HitTestingTreeNode> node5 = root->GetLastChild();
+  nsRefPtr<HitTestingTreeNode> node4 = node5->GetPrevSibling();
+  nsRefPtr<HitTestingTreeNode> node2 = node4->GetPrevSibling();
+  nsRefPtr<HitTestingTreeNode> node1 = node2->GetPrevSibling();
+  nsRefPtr<HitTestingTreeNode> node3 = node2->GetLastChild();
+  nsRefPtr<HitTestingTreeNode> node9 = node5->GetLastChild();
+  nsRefPtr<HitTestingTreeNode> node8 = node9->GetPrevSibling();
+  nsRefPtr<HitTestingTreeNode> node6 = node8->GetPrevSibling();
+  nsRefPtr<HitTestingTreeNode> node7 = node6->GetLastChild();
+  EXPECT_EQ(nullptr, node1->GetPrevSibling());
+  EXPECT_EQ(nullptr, node3->GetPrevSibling());
+  EXPECT_EQ(nullptr, node6->GetPrevSibling());
+  EXPECT_EQ(nullptr, node7->GetPrevSibling());
+  EXPECT_EQ(nullptr, node1->GetLastChild());
+  EXPECT_EQ(nullptr, node3->GetLastChild());
+  EXPECT_EQ(nullptr, node4->GetLastChild());
+  EXPECT_EQ(nullptr, node7->GetLastChild());
+  EXPECT_EQ(nullptr, node8->GetLastChild());
+  EXPECT_EQ(nullptr, node9->GetLastChild());
 
   nsRefPtr<AsyncPanZoomController> hit = GetTargetAPZC(ScreenPoint(25, 25));
   EXPECT_EQ(ApzcOf(layers[1]), hit.get());
   hit = GetTargetAPZC(ScreenPoint(275, 375));
   EXPECT_EQ(ApzcOf(layers[9]), hit.get());
   hit = GetTargetAPZC(ScreenPoint(250, 100));
   EXPECT_EQ(ApzcOf(layers[7]), hit.get());
 }
@@ -2094,17 +2134,17 @@ TEST_F(APZHitTestingTester, ComplexMulti
 TEST_F(APZHitTestingTester, TestRepaintFlushOnNewInputBlock) {
   // The main purpose of this test is to verify that touch-start events (or anything
   // that starts a new input block) don't ever get untransformed. This should always
   // hold because the APZ code should flush repaints when we start a new input block
   // and the transform to gecko space should be empty.
 
   CreateSimpleScrollingLayer();
   ScopedLayerTreeRegistration registration(0, root, mcc);
-  manager->UpdatePanZoomControllerTree(nullptr, root, false, 0, 0);
+  manager->UpdateHitTestingTree(nullptr, root, false, 0, 0);
   TestAsyncPanZoomController* apzcroot = ApzcOf(root);
 
   // At this point, the following holds (all coordinates in screen pixels):
   // layers[0] has content from (0,0)-(500,500), clipped by composition bounds (0,0)-(200,200)
 
   MockFunction<void(std::string checkPointName)> check;
 
   {
@@ -2172,17 +2212,17 @@ protected:
       nsIntRegion(nsIntRect(0, 0, 100, 100)),
       nsIntRegion(nsIntRect(0, 50, 100, 50))
     };
     root = CreateLayerTree(layerTreeSyntax, layerVisibleRegion, nullptr, lm, layers);
     SetScrollableFrameMetrics(root, FrameMetrics::START_SCROLL_ID, CSSRect(0, 0, 200, 200));
     SetScrollableFrameMetrics(layers[1], FrameMetrics::START_SCROLL_ID + 1, CSSRect(0, 0, 100, 100));
     SetScrollHandoff(layers[1], root);
     registration = MakeUnique<ScopedLayerTreeRegistration>(0, root, mcc);
-    manager->UpdatePanZoomControllerTree(nullptr, root, false, 0, 0);
+    manager->UpdateHitTestingTree(nullptr, root, false, 0, 0);
     rootApzc = ApzcOf(root);
   }
 
   void CreateOverscrollHandoffLayerTree2() {
     const char* layerTreeSyntax = "c(c(t))";
     nsIntRegion layerVisibleRegion[] = {
       nsIntRegion(nsIntRect(0, 0, 100, 100)),
       nsIntRegion(nsIntRect(0, 0, 100, 100)),
@@ -2192,17 +2232,17 @@ protected:
     SetScrollableFrameMetrics(root, FrameMetrics::START_SCROLL_ID, CSSRect(0, 0, 200, 200));
     SetScrollableFrameMetrics(layers[1], FrameMetrics::START_SCROLL_ID + 2, CSSRect(-100, -100, 200, 200));
     SetScrollableFrameMetrics(layers[2], FrameMetrics::START_SCROLL_ID + 1, CSSRect(0, 0, 100, 100));
     SetScrollHandoff(layers[1], root);
     SetScrollHandoff(layers[2], layers[1]);
     // No ScopedLayerTreeRegistration as that just needs to be done once per test
     // and this is the second layer tree for a particular test.
     MOZ_ASSERT(registration);
-    manager->UpdatePanZoomControllerTree(nullptr, root, false, 0, 0);
+    manager->UpdateHitTestingTree(nullptr, root, false, 0, 0);
     rootApzc = ApzcOf(root);
   }
 
   void CreateOverscrollHandoffLayerTree3() {
     const char* layerTreeSyntax = "c(c(t)c(t))";
     nsIntRegion layerVisibleRegion[] = {
       nsIntRegion(nsIntRect(0, 0, 100, 100)),  // root
       nsIntRegion(nsIntRect(0, 0, 100, 50)),   // scrolling parent 1
@@ -2213,31 +2253,31 @@ protected:
     root = CreateLayerTree(layerTreeSyntax, layerVisibleRegion, nullptr, lm, layers);
     SetScrollableFrameMetrics(layers[1], FrameMetrics::START_SCROLL_ID, CSSRect(0, 0, 100, 100));
     SetScrollableFrameMetrics(layers[2], FrameMetrics::START_SCROLL_ID + 1, CSSRect(0, 0, 100, 100));
     SetScrollableFrameMetrics(layers[3], FrameMetrics::START_SCROLL_ID + 2, CSSRect(0, 50, 100, 100));
     SetScrollableFrameMetrics(layers[4], FrameMetrics::START_SCROLL_ID + 3, CSSRect(0, 50, 100, 100));
     SetScrollHandoff(layers[2], layers[1]);
     SetScrollHandoff(layers[4], layers[3]);
     registration = MakeUnique<ScopedLayerTreeRegistration>(0, root, mcc);
-    manager->UpdatePanZoomControllerTree(nullptr, root, false, 0, 0);
+    manager->UpdateHitTestingTree(nullptr, root, false, 0, 0);
   }
 
   void CreateScrollgrabLayerTree() {
     const char* layerTreeSyntax = "c(t)";
     nsIntRegion layerVisibleRegion[] = {
       nsIntRegion(nsIntRect(0, 0, 100, 100)),  // scroll-grabbing parent
       nsIntRegion(nsIntRect(0, 20, 100, 80))   // child
     };
     root = CreateLayerTree(layerTreeSyntax, layerVisibleRegion, nullptr, lm, layers);
     SetScrollableFrameMetrics(root, FrameMetrics::START_SCROLL_ID, CSSRect(0, 0, 100, 120));
     SetScrollableFrameMetrics(layers[1], FrameMetrics::START_SCROLL_ID + 1, CSSRect(0, 0, 100, 200));
     SetScrollHandoff(layers[1], root);
     registration = MakeUnique<ScopedLayerTreeRegistration>(0, root, mcc);
-    manager->UpdatePanZoomControllerTree(nullptr, root, false, 0, 0);
+    manager->UpdateHitTestingTree(nullptr, root, false, 0, 0);
     rootApzc = ApzcOf(root);
     rootApzc->GetFrameMetrics().SetHasScrollgrab(true);
   }
 };
 
 // Here we test that if the processing of a touch block is deferred while we
 // wait for content to send a prevent-default message, overscroll is still
 // handed off correctly when the block is processed.
@@ -2421,17 +2461,22 @@ TEST_F(APZOverscrollHandoffTester, Scrol
 
 class APZEventRegionsTester : public APZCTreeManagerTester {
 protected:
   UniquePtr<ScopedLayerTreeRegistration> registration;
   TestAsyncPanZoomController* rootApzc;
 
   void CreateEventRegionsLayerTree1() {
     const char* layerTreeSyntax = "c(tt)";
-    root = CreateLayerTree(layerTreeSyntax, nullptr, nullptr, lm, layers);
+    nsIntRegion layerVisibleRegions[] = {
+      nsIntRegion(nsIntRect(0, 0, 200, 200)),     // root
+      nsIntRegion(nsIntRect(0, 0, 100, 200)),     // left half
+      nsIntRegion(nsIntRect(0, 100, 200, 100)),   // bottom half
+    };
+    root = CreateLayerTree(layerTreeSyntax, layerVisibleRegions, nullptr, lm, layers);
     SetScrollableFrameMetrics(root, FrameMetrics::START_SCROLL_ID);
     SetScrollableFrameMetrics(layers[1], FrameMetrics::START_SCROLL_ID + 1);
     SetScrollableFrameMetrics(layers[2], FrameMetrics::START_SCROLL_ID + 2);
     SetScrollHandoff(layers[1], root);
     SetScrollHandoff(layers[2], root);
 
     // Set up the event regions over a 200x200 area. The root layer has the
     // whole 200x200 as the hit region; layers[1] has the left half and
@@ -2443,34 +2488,72 @@ protected:
     root->SetEventRegions(regions);
     regions.mDispatchToContentHitRegion = nsIntRegion(nsIntRect(0, 100, 100, 100));
     regions.mHitRegion = nsIntRegion(nsIntRect(0, 0, 100, 200));
     layers[1]->SetEventRegions(regions);
     regions.mHitRegion = nsIntRegion(nsIntRect(0, 100, 200, 100));
     layers[2]->SetEventRegions(regions);
 
     registration = MakeUnique<ScopedLayerTreeRegistration>(0, root, mcc);
-    manager->UpdatePanZoomControllerTree(nullptr, root, false, 0, 0);
+    manager->UpdateHitTestingTree(nullptr, root, false, 0, 0);
     rootApzc = ApzcOf(root);
   }
 
   void CreateEventRegionsLayerTree2() {
     const char* layerTreeSyntax = "c(t)";
-    root = CreateLayerTree(layerTreeSyntax, nullptr, nullptr, lm, layers);
+    nsIntRegion layerVisibleRegions[] = {
+      nsIntRegion(nsIntRect(0, 0, 100, 500)),
+      nsIntRegion(nsIntRect(0, 150, 100, 100)),
+    };
+    root = CreateLayerTree(layerTreeSyntax, layerVisibleRegions, nullptr, lm, layers);
     SetScrollableFrameMetrics(root, FrameMetrics::START_SCROLL_ID);
 
     // Set up the event regions so that the child thebes layer is positioned far
     // away from the scrolling container layer.
     EventRegions regions(nsIntRegion(nsIntRect(0, 0, 100, 100)));
     root->SetEventRegions(regions);
     regions.mHitRegion = nsIntRegion(nsIntRect(0, 150, 100, 100));
     layers[1]->SetEventRegions(regions);
 
     registration = MakeUnique<ScopedLayerTreeRegistration>(0, root, mcc);
-    manager->UpdatePanZoomControllerTree(nullptr, root, false, 0, 0);
+    manager->UpdateHitTestingTree(nullptr, root, false, 0, 0);
+    rootApzc = ApzcOf(root);
+  }
+
+  void CreateObscuringLayerTree() {
+    const char* layerTreeSyntax = "c(c(t)t)";
+    // LayerID                     0 1 2 3
+    // 0 is the root.
+    // 1 is a parent scrollable layer.
+    // 2 is a child scrollable layer.
+    // 3 is the Obscurer, who ruins everything.
+    nsIntRegion layerVisibleRegions[] = {
+        // x coordinates are uninteresting
+        nsIntRegion(nsIntRect(0,   0, 200, 200)),  // [0, 200]
+        nsIntRegion(nsIntRect(0,   0, 200, 200)),  // [0, 200]
+        nsIntRegion(nsIntRect(0, 100, 200,  50)),  // [100, 150]
+        nsIntRegion(nsIntRect(0, 100, 200, 100))   // [100, 200]
+    };
+    root = CreateLayerTree(layerTreeSyntax, layerVisibleRegions, nullptr, lm, layers);
+
+    SetScrollableFrameMetrics(root, FrameMetrics::START_SCROLL_ID, CSSRect(0, 0, 200, 200));
+    SetScrollableFrameMetrics(layers[1], FrameMetrics::START_SCROLL_ID + 1, CSSRect(0, 0, 200, 300));
+    SetScrollableFrameMetrics(layers[2], FrameMetrics::START_SCROLL_ID + 2, CSSRect(0, 0, 200, 100));
+    SetScrollHandoff(layers[2], layers[1]);
+    SetScrollHandoff(layers[1], root);
+
+    EventRegions regions(nsIntRegion(nsIntRect(0, 0, 200, 200)));
+    root->SetEventRegions(regions);
+    regions.mHitRegion = nsIntRegion(nsIntRect(0, 0, 200, 300));
+    layers[1]->SetEventRegions(regions);
+    regions.mHitRegion = nsIntRegion(nsIntRect(0, 100, 200, 100));
+    layers[2]->SetEventRegions(regions);
+
+    registration = MakeUnique<ScopedLayerTreeRegistration>(0, root, mcc);
+    manager->UpdateHitTestingTree(nullptr, root, false, 0, 0);
     rootApzc = ApzcOf(root);
   }
 };
 
 TEST_F(APZEventRegionsTester, HitRegionImmediateResponse) {
   SCOPED_GFX_PREF(LayoutEventRegionsEnabled, bool, true);
 
   CreateEventRegionsLayerTree1();
@@ -2535,16 +2618,36 @@ TEST_F(APZEventRegionsTester, HitRegionA
   // parent layer's hit region. Verify that it comes out of the APZC's
   // content controller, which indicates the input events got routed correctly
   // to the APZC.
   EXPECT_CALL(*mcc, HandleSingleTap(_, _, rootApzc->GetGuid())).Times(1);
   Tap(manager, 10, 160, time, 100);
   mcc->RunThroughDelayedTasks();    // this runs the tap event
 }
 
+TEST_F(APZEventRegionsTester, Obscuration) {
+  SCOPED_GFX_PREF(LayoutEventRegionsEnabled, bool, true);
+
+  CreateObscuringLayerTree();
+  ScopedLayerTreeRegistration registration(0, root, mcc);
+
+  manager->UpdateHitTestingTree(nullptr, root, false, 0, 0);
+
+  TestAsyncPanZoomController* parent = ApzcOf(layers[1]);
+  TestAsyncPanZoomController* child = ApzcOf(layers[2]);
+
+  int time = 0;
+  ApzcPanNoFling(parent, time, 75, 25);
+
+  HitTestResult result;
+  nsRefPtr<AsyncPanZoomController> hit = manager->GetTargetAPZC(ScreenPoint(50, 75), &result);
+  EXPECT_EQ(child, hit.get());
+  EXPECT_EQ(HitTestResult::ApzcHitRegion, result);
+}
+
 class TaskRunMetrics {
 public:
   TaskRunMetrics()
     : mRunCount(0)
     , mCancelCount(0)
   {}
 
   void IncrementRunCount() {
--- a/gfx/thebes/gfxFT2Fonts.cpp
+++ b/gfx/thebes/gfxFT2Fonts.cpp
@@ -50,17 +50,18 @@ gfxFT2Font::ShapeText(gfxContext     *aC
                       int32_t         aScript,
                       bool            aVertical,
                       gfxShapedText  *aShapedText)
 {
     if (!gfxFont::ShapeText(aContext, aText, aOffset, aLength, aScript,
                             aVertical, aShapedText)) {
         // harfbuzz must have failed(?!), just render raw glyphs
         AddRange(aText, aOffset, aLength, aShapedText);
-        PostShapingFixup(aContext, aText, aOffset, aLength, aShapedText);
+        PostShapingFixup(aContext, aText, aOffset, aLength, aVertical,
+                         aShapedText);
     }
 
     return true;
 }
 
 void
 gfxFT2Font::AddRange(const char16_t *aText, uint32_t aOffset,
                      uint32_t aLength, gfxShapedText *aShapedText)
--- a/gfx/thebes/gfxFont.cpp
+++ b/gfx/thebes/gfxFont.cpp
@@ -2485,33 +2485,39 @@ gfxFont::ShapeText(gfxContext      *aCon
             mHarfBuzzShaper = new gfxHarfBuzzShaper(this);
         }
         ok = mHarfBuzzShaper->ShapeText(aContext, aText, aOffset, aLength,
                                         aScript, aVertical, aShapedText);
     }
 
     NS_WARN_IF_FALSE(ok, "shaper failed, expect scrambled or missing text");
 
-    PostShapingFixup(aContext, aText, aOffset, aLength, aShapedText);
+    PostShapingFixup(aContext, aText, aOffset, aLength, aVertical,
+                     aShapedText);
 
     return ok;
 }
 
 void
 gfxFont::PostShapingFixup(gfxContext      *aContext,
                           const char16_t *aText,
                           uint32_t         aOffset,
                           uint32_t         aLength,
+                          bool             aVertical,
                           gfxShapedText   *aShapedText)
 {
     if (IsSyntheticBold()) {
-        float synBoldOffset =
-                GetSyntheticBoldOffset() * CalcXScale(aContext);
-        aShapedText->AdjustAdvancesForSyntheticBold(synBoldOffset,
-                                                    aOffset, aLength);
+        const Metrics& metrics =
+            GetMetrics(aVertical ? eVertical : eHorizontal);
+        if (metrics.maxAdvance > metrics.aveCharWidth) {
+            float synBoldOffset =
+                    GetSyntheticBoldOffset() * CalcXScale(aContext);
+            aShapedText->AdjustAdvancesForSyntheticBold(synBoldOffset,
+                                                        aOffset, aLength);
+        }
     }
 }
 
 #define MAX_SHAPING_LENGTH  32760 // slightly less than 32K, trying to avoid
                                   // over-stressing platform shapers
 #define BACKTRACK_LIMIT     16 // backtrack this far looking for a good place
                                // to split into fragments for separate shaping
 
--- a/gfx/thebes/gfxFont.h
+++ b/gfx/thebes/gfxFont.h
@@ -1876,16 +1876,17 @@ protected:
 
     // Helper to adjust for synthetic bold and set character-type flags
     // in the shaped text; implementations of ShapeText should call this
     // after glyph shaping has been completed.
     void PostShapingFixup(gfxContext      *aContext,
                           const char16_t *aText,
                           uint32_t         aOffset, // position within aShapedText
                           uint32_t         aLength,
+                          bool             aVertical,
                           gfxShapedText   *aShapedText);
 
     // Shape text directly into a range within a textrun, without using the
     // font's word cache. Intended for use when the font has layout features
     // that involve space, and therefore require shaping complete runs rather
     // than isolated words, or for long strings that are inefficient to cache.
     // This will split the text on "invalid" characters (tab/newline) that are
     // not handled via normal shaping, but does not otherwise divide up the
--- a/gfx/thebes/gfxMacFont.cpp
+++ b/gfx/thebes/gfxMacFont.cpp
@@ -138,17 +138,18 @@ gfxMacFont::ShapeText(gfxContext     *aC
     // so we ignore RequiresAATLayout if vertical is requested.
     if (static_cast<MacOSFontEntry*>(GetFontEntry())->RequiresAATLayout() &&
         !aVertical) {
         if (!mCoreTextShaper) {
             mCoreTextShaper = new gfxCoreTextShaper(this);
         }
         if (mCoreTextShaper->ShapeText(aContext, aText, aOffset, aLength,
                                        aScript, aVertical, aShapedText)) {
-            PostShapingFixup(aContext, aText, aOffset, aLength, aShapedText);
+            PostShapingFixup(aContext, aText, aOffset, aLength, aVertical,
+                             aShapedText);
             return true;
         }
     }
 
     return gfxFont::ShapeText(aContext, aText, aOffset, aLength, aScript,
                               aVertical, aShapedText);
 }
 
--- a/gfx/thebes/gfxPlatform.cpp
+++ b/gfx/thebes/gfxPlatform.cpp
@@ -697,17 +697,17 @@ gfxPlatform::~gfxPlatform()
     VRHMDManagerOculus::Destroy();
 
     // The cairo folks think we should only clean up in debug builds,
     // but we're generally in the habit of trying to shut down as
     // cleanly as possible even in production code, so call this
     // cairo_debug_* function unconditionally.
     //
     // because cairo can assert and thus crash on shutdown, don't do this in release builds
-#if defined(DEBUG) || defined(NS_BUILD_REFCNT_LOGGING) || defined(NS_TRACE_MALLOC) || defined(MOZ_VALGRIND)
+#if defined(DEBUG) || defined(NS_BUILD_REFCNT_LOGGING) || defined(MOZ_VALGRIND)
 #ifdef USE_SKIA
     // must do Skia cleanup before Cairo cleanup, because Skia may be referencing
     // Cairo objects e.g. through SkCairoFTTypeface
     SkGraphics::Term();
 #endif
 
 #if MOZ_TREE_CAIRO
     cairo_debug_reset_static_data();
--- a/gfx/thebes/gfxPrefs.h
+++ b/gfx/thebes/gfxPrefs.h
@@ -240,17 +240,16 @@ private:
   DECL_GFX_PREF(Live, "image.mem.decodeondraw",                ImageMemDecodeOnDraw, bool, false);
   DECL_GFX_PREF(Live, "image.mem.discardable",                 ImageMemDiscardable, bool, false);
   DECL_GFX_PREF(Live, "image.mem.max_ms_before_yield",         ImageMemMaxMSBeforeYield, uint32_t, 400);
   DECL_GFX_PREF(Once, "image.mem.surfacecache.discard_factor", ImageMemSurfaceCacheDiscardFactor, uint32_t, 1);
   DECL_GFX_PREF(Once, "image.mem.surfacecache.max_size_kb",    ImageMemSurfaceCacheMaxSizeKB, uint32_t, 100 * 1024);
   DECL_GFX_PREF(Once, "image.mem.surfacecache.min_expiration_ms", ImageMemSurfaceCacheMinExpirationMS, uint32_t, 60*1000);
   DECL_GFX_PREF(Once, "image.mem.surfacecache.size_factor",    ImageMemSurfaceCacheSizeFactor, uint32_t, 64);
   DECL_GFX_PREF(Live, "image.mozsamplesize.enabled",           ImageMozSampleSizeEnabled, bool, false);
-  DECL_GFX_PREF(Live, "image.multithreaded_decoding.enabled",  ImageMTDecodingEnabled, bool, true);
   DECL_GFX_PREF(Live, "image.multithreaded_decoding.limit",    ImageMTDecodingLimit, int32_t, -1);
 
   DECL_GFX_PREF(Once, "layers.acceleration.disabled",          LayersAccelerationDisabled, bool, false);
   DECL_GFX_PREF(Live, "layers.acceleration.draw-fps",          LayersDrawFPS, bool, false);
   DECL_GFX_PREF(Live, "layers.acceleration.draw-fps.print-histogram",  FPSPrintHistogram, bool, false);
   DECL_GFX_PREF(Live, "layers.acceleration.draw-fps.write-to-file", WriteFPSToFile, bool, false);
   DECL_GFX_PREF(Once, "layers.acceleration.force-enabled",     LayersAccelerationForceEnabled, bool, false);
   DECL_GFX_PREF(Once, "layers.async-video.enabled",            AsyncVideoEnabled, bool, true);
--- a/image/decoders/nsBMPDecoder.cpp
+++ b/image/decoders/nsBMPDecoder.cpp
@@ -34,27 +34,32 @@ GetBMPLog()
 }
 #endif
 
 // Convert from row (1..height) to absolute line (0..height-1)
 #define LINE(row) ((mBIH.height < 0) ? (-mBIH.height - (row)) : ((row) - 1))
 #define PIXEL_OFFSET(row, col) (LINE(row) * mBIH.width + col)
 
 nsBMPDecoder::nsBMPDecoder(RasterImage& aImage)
- : Decoder(aImage)
-{
-  mColors = nullptr;
-  mRow = nullptr;
-  mCurPos = mPos = mNumColors = mRowBytes = 0;
-  mOldLine = mCurLine = 1; // Otherwise decoder will never start
-  mState = eRLEStateInitial;
-  mStateData = 0;
-  mLOH = WIN_V3_HEADER_LENGTH;
-  mUseAlphaData = mHaveAlphaData = false;
-}
+  : Decoder(aImage)
+  , mPos(0)
+  , mLOH(WIN_V3_HEADER_LENGTH)
+  , mNumColors(0)
+  , mColors(nullptr)
+  , mRow(nullptr)
+  , mRowBytes(0)
+  , mCurLine(1)  // Otherwise decoder will never start.
+  , mOldLine(1)
+  , mCurPos(0)
+  , mState(eRLEStateInitial)
+  , mStateData(0)
+  , mProcessedHeader(false)
+  , mUseAlphaData(false)
+  , mHaveAlphaData(false)
+{ }
 
 nsBMPDecoder::~nsBMPDecoder()
 {
   delete[] mColors;
   if (mRow) {
       moz_free(mRow);
   }
 }
@@ -189,18 +194,17 @@ nsBMPDecoder::CalcBitShift()
     // blue
     calcBitmask(mBitFields.blue, begin, length);
     mBitFields.blueRightShift = begin;
     mBitFields.blueLeftShift = 8 - length;
     return NS_OK;
 }
 
 void
-nsBMPDecoder::WriteInternal(const char* aBuffer, uint32_t aCount,
-                            DecodeStrategy)
+nsBMPDecoder::WriteInternal(const char* aBuffer, uint32_t aCount)
 {
   NS_ABORT_IF_FALSE(!HasError(), "Shouldn't call WriteInternal after error!");
 
   // aCount=0 means EOF, mCurLine=0 means we're past end of image
   if (!aCount || !mCurLine) {
       return;
   }
 
@@ -322,20 +326,22 @@ nsBMPDecoder::WriteInternal(const char* 
       aCount -= toCopy;
       aBuffer += toCopy;
   }
 
   // At this point mPos should be >= mLOH unless aBuffer did not have enough
   // data. In the latter case aCount should be 0.
   MOZ_ASSERT(mPos >= mLOH || aCount == 0);
 
-  // HasSize is called to ensure that if at this point mPos == mLOH but
-  // we have no data left to process, the next time WriteInternal is called
+  // mProcessedHeader is checked to ensure that if at this point mPos == mLOH
+  // but we have no data left to process, the next time WriteInternal is called
   // we won't enter this condition again.
-  if (mPos == mLOH && !HasSize()) {
+  if (mPos == mLOH && !mProcessedHeader) {
+      mProcessedHeader = true;
+
       ProcessInfoHeader();
       PR_LOG(GetBMPLog(), PR_LOG_DEBUG,
              ("BMP is %lix%lix%lu. compression=%lu\n",
              mBIH.width, mBIH.height, mBIH.bpp, mBIH.compression));
       // Verify we support this bit depth
       if (mBIH.bpp != 1 && mBIH.bpp != 4 && mBIH.bpp != 8 &&
           mBIH.bpp != 16 && mBIH.bpp != 24 && mBIH.bpp != 32) {
         PostDataError();
--- a/image/decoders/nsBMPDecoder.h
+++ b/image/decoders/nsBMPDecoder.h
@@ -45,18 +45,18 @@ public:
 
     // Obtains the size of the compressed image resource
     int32_t GetCompressedImageSize() const;
 
     // Obtains whether or not a BMP file had alpha data in its 4th byte
     // for 32BPP bitmaps.  Only use after the bitmap has been processed.
     bool HasAlphaData() const;
 
-    virtual void WriteInternal(const char* aBuffer, uint32_t aCount,
-                               DecodeStrategy aStrategy) MOZ_OVERRIDE;
+    virtual void WriteInternal(const char* aBuffer,
+                               uint32_t aCount) MOZ_OVERRIDE;
     virtual void FinishInternal() MOZ_OVERRIDE;
 
 private:
 
     /// Calculates the red-, green- and blueshift in mBitFields using
     /// the bitmasks from mBitFields
     NS_METHOD CalcBitShift();
 
@@ -89,16 +89,19 @@ private:
     /// Set mBFH from the raw data in mRawBuf, converting from little-endian
     /// data to native data as necessary
     void ProcessFileHeader();
 
     /// Set mBIH from the raw data in mRawBuf, converting from little-endian
     /// data to native data as necessary
     void ProcessInfoHeader();
 
+    /// True if we've already processed the BMP header.
+    bool mProcessedHeader;
+
     // Stores whether the image data may store alpha data, or if
     // the alpha data is unspecified and filled with a padding byte of 0.
     // When a 32BPP bitmap is stored in an ICO or CUR file, its 4th byte
     // is used for alpha transparency.  When it is stored in a BMP, its
     // 4th byte is reserved and is always 0.
     // Reference:
     // http://en.wikipedia.org/wiki/ICO_(file_format)#cite_note-9
     // Bitmaps where the alpha bytes are all 0 should be fully visible.
--- a/image/decoders/nsGIFDecoder2.cpp
+++ b/image/decoders/nsGIFDecoder2.cpp
@@ -194,22 +194,16 @@ nsGIFDecoder2::BeginImageFrame(uint16_t 
       // We need padding on the first frame, which means that we don't draw into
       // part of the image at all. Report that as transparency.
       PostHasTransparency();
 
       // Regardless of depth of input, image is decoded into 24bit RGB
       NeedNewFrame(mGIFStruct.images_decoded, mGIFStruct.x_offset,
                    mGIFStruct.y_offset, mGIFStruct.width, mGIFStruct.height,
                    format);
-    } else {
-      // Our preallocated frame matches up, with the possible exception
-      // of alpha.
-      if (format == gfx::SurfaceFormat::B8G8R8X8) {
-        currentFrame->SetHasNoAlpha();
-      }
     }
   }
 
   mCurrentFrameIndex = mGIFStruct.images_decoded;
 }
 
 
 //******************************************************************************
@@ -228,18 +222,22 @@ nsGIFDecoder2::EndImageFrame()
     // This will clear the remaining bits of the placeholder. (Bug 37589)
     const uint32_t realFrameHeight = mGIFStruct.height + mGIFStruct.y_offset;
     if (realFrameHeight < mGIFStruct.screen_height) {
       nsIntRect r(0, realFrameHeight,
                   mGIFStruct.screen_width,
                   mGIFStruct.screen_height - realFrameHeight);
       PostInvalidation(r);
     }
-    // This transparency check is only valid for first frame
-    if (mGIFStruct.is_transparent && !mSawTransparency) {
+
+    // The first frame was preallocated with alpha; if it wasn't transparent, we
+    // should fix that. We can also mark it opaque unconditionally if we didn't
+    // actually see any transparent pixels - this test is only valid for the
+    // first frame.
+    if (!mGIFStruct.is_transparent || !mSawTransparency) {
       opacity = Opacity::OPAQUE;
     }
   }
   mCurrentRow = mLastFlushedRow = -1;
   mCurrentPass = mLastFlushedPass = 0;
 
   // Only add frame if we have any rows at all
   if (mGIFStruct.rows_remaining != mGIFStruct.height) {
@@ -564,18 +562,17 @@ ConvertColormap(uint32_t* aColormap, uin
   // NB: can't use 32-bit reads, they might read off the end of the buffer
   while (c--) {
     from -= 3;
     *--to = gfxPackedPixel(0xFF, from[0], from[1], from[2]);
   }
 }
 
 void
-nsGIFDecoder2::WriteInternal(const char* aBuffer, uint32_t aCount,
-                             DecodeStrategy)
+nsGIFDecoder2::WriteInternal(const char* aBuffer, uint32_t aCount)
 {
   NS_ABORT_IF_FALSE(!HasError(), "Shouldn't call WriteInternal after error!");
 
   // These variables changed names; renaming would make a much bigger patch :(
   const uint8_t* buf = (const uint8_t*)aBuffer;
   uint32_t len = aCount;
 
   const uint8_t* q = buf;
--- a/image/decoders/nsGIFDecoder2.h
+++ b/image/decoders/nsGIFDecoder2.h
@@ -21,18 +21,17 @@ class RasterImage;
 
 class nsGIFDecoder2 : public Decoder
 {
 public:
 
   explicit nsGIFDecoder2(RasterImage& aImage);
   ~nsGIFDecoder2();
 
-  virtual void WriteInternal(const char* aBuffer, uint32_t aCount,
-                             DecodeStrategy aStrategy) MOZ_OVERRIDE;
+  virtual void WriteInternal(const char* aBuffer, uint32_t aCount) MOZ_OVERRIDE;
   virtual void FinishInternal() MOZ_OVERRIDE;
   virtual Telemetry::ID SpeedHistogram() MOZ_OVERRIDE;
 
 private:
   // These functions will be called when the decoder has a decoded row,
   // frame size information, etc.
 
   void      BeginGIF();
--- a/image/decoders/nsICODecoder.cpp
+++ b/image/decoders/nsICODecoder.cpp
@@ -209,24 +209,23 @@ nsICODecoder::SetHotSpotIfCursor()
   if (!mIsCursor) {
     return;
   }
 
   mImageMetadata.SetHotspot(mDirEntry.mXHotspot, mDirEntry.mYHotspot);
 }
 
 void
-nsICODecoder::WriteInternal(const char* aBuffer, uint32_t aCount,
-                            DecodeStrategy aStrategy)
+nsICODecoder::WriteInternal(const char* aBuffer, uint32_t aCount)
 {
   NS_ABORT_IF_FALSE(!HasError(), "Shouldn't call WriteInternal after error!");
 
   if (!aCount) {
     if (mContainedDecoder) {
-      WriteToContainedDecoder(aBuffer, aCount, aStrategy);
+      WriteToContainedDecoder(aBuffer, aCount);
     }
     return;
   }
 
   while (aCount && (mPos < ICONCOUNTOFFSET)) { // Skip to the # of icons.
     if (mPos == 2) { // if the third byte is 1: This is an icon, 2: a cursor
       if ((*aBuffer != 1) && (*aBuffer != 2)) {
         PostDataError();
@@ -341,25 +340,25 @@ nsICODecoder::WriteInternal(const char* 
     mIsPNG = !memcmp(mSignature, nsPNGDecoder::pngSignatureBytes,
                      PNGSIGNATURESIZE);
     if (mIsPNG) {
       mContainedDecoder = new nsPNGDecoder(mImage);
       mContainedDecoder->SetSizeDecode(IsSizeDecode());
       mContainedDecoder->InitSharedDecoder(mImageData, mImageDataLength,
                                            mColormap, mColormapSize,
                                            Move(mRefForContainedDecoder));
-      if (!WriteToContainedDecoder(mSignature, PNGSIGNATURESIZE, aStrategy)) {
+      if (!WriteToContainedDecoder(mSignature, PNGSIGNATURESIZE)) {
         return;
       }
     }
   }
 
   // If we have a PNG, let the PNG decoder do all of the rest of the work
   if (mIsPNG && mContainedDecoder && mPos >= mImageOffset + PNGSIGNATURESIZE) {
-    if (!WriteToContainedDecoder(aBuffer, aCount, aStrategy)) {
+    if (!WriteToContainedDecoder(aBuffer, aCount)) {
       return;
     }
 
     if (!HasSize() && mContainedDecoder->HasSize()) {
       PostSize(mContainedDecoder->GetImageMetadata().GetWidth(),
                mContainedDecoder->GetImageMetadata().GetHeight());
     }
 
@@ -428,18 +427,17 @@ nsICODecoder::WriteInternal(const char* 
     // The ICO format when containing a BMP does not include the 14 byte
     // bitmap file header. To use the code of the BMP decoder we need to
     // generate this header ourselves and feed it to the BMP decoder.
     int8_t bfhBuffer[BMPFILEHEADERSIZE];
     if (!FillBitmapFileHeaderBuffer(bfhBuffer)) {
       PostDataError();
       return;
     }
-    if (!WriteToContainedDecoder((const char*)bfhBuffer, sizeof(bfhBuffer),
-                                  aStrategy)) {
+    if (!WriteToContainedDecoder((const char*)bfhBuffer, sizeof(bfhBuffer))) {
       return;
     }
 
     // Setup the cursor hot spot if one is present
     SetHotSpotIfCursor();
 
     // Fix the ICO height from the BIH.
     // Fix the height on the BIH to be /2 so our BMP decoder will understand.
@@ -450,17 +448,17 @@ nsICODecoder::WriteInternal(const char* 
 
     // Fix the ICO width from the BIH.
     if (!FixBitmapWidth(reinterpret_cast<int8_t*>(mBIHraw))) {
       PostDataError();
       return;
     }
 
     // Write out the BMP's bitmap info header
-    if (!WriteToContainedDecoder(mBIHraw, sizeof(mBIHraw), aStrategy)) {
+    if (!WriteToContainedDecoder(mBIHraw, sizeof(mBIHraw))) {
       return;
     }
 
     PostSize(mContainedDecoder->GetImageMetadata().GetWidth(),
              mContainedDecoder->GetImageMetadata().GetHeight());
 
     // We have the size. If we're doing a size decode, we got what
     // we came for.
@@ -500,17 +498,17 @@ nsICODecoder::WriteInternal(const char* 
     if (mPos >= bmpDataOffset && mPos < bmpDataEnd) {
 
       // Figure out how much data the BMP decoder wants
       uint32_t toFeed = bmpDataEnd - mPos;
       if (toFeed > aCount) {
         toFeed = aCount;
       }
 
-      if (!WriteToContainedDecoder(aBuffer, toFeed, aStrategy)) {
+      if (!WriteToContainedDecoder(aBuffer, toFeed)) {
         return;
       }
 
       mPos += toFeed;
       aCount -= toFeed;
       aBuffer += toFeed;
     }
 
@@ -589,20 +587,19 @@ nsICODecoder::WriteInternal(const char* 
             PostHasTransparency();
         }
       }
     }
   }
 }
 
 bool
-nsICODecoder::WriteToContainedDecoder(const char* aBuffer, uint32_t aCount,
-                                      DecodeStrategy aStrategy)
+nsICODecoder::WriteToContainedDecoder(const char* aBuffer, uint32_t aCount)
 {
-  mContainedDecoder->Write(aBuffer, aCount, aStrategy);
+  mContainedDecoder->Write(aBuffer, aCount);
   mProgress |= mContainedDecoder->TakeProgress();
   mInvalidRect.Union(mContainedDecoder->TakeInvalidRect());
   if (mContainedDecoder->HasDataError()) {
     mDataError = mContainedDecoder->HasDataError();
   }
   if (mContainedDecoder->HasDecoderError()) {
     PostDecoderError(mContainedDecoder->GetDecoderError());
   }
--- a/image/decoders/nsICODecoder.h
+++ b/image/decoders/nsICODecoder.h
@@ -33,27 +33,27 @@ public:
   }
 
   // Obtains the height of the icon directory entry
   uint32_t GetRealHeight() const
   {
     return mDirEntry.mHeight == 0 ? 256 : mDirEntry.mHeight;
   }
 
-  virtual void WriteInternal(const char* aBuffer, uint32_t aCount,
-                             DecodeStrategy aStrategy) MOZ_OVERRIDE;
+  virtual void WriteInternal(const char* aBuffer, uint32_t aCount) MOZ_OVERRIDE;
   virtual void FinishInternal() MOZ_OVERRIDE;
+  virtual nsresult AllocateFrame() MOZ_OVERRIDE;
+
+protected:
   virtual bool NeedsNewFrame() const MOZ_OVERRIDE;
-  virtual nsresult AllocateFrame() MOZ_OVERRIDE;
 
 private:
   // Writes to the contained decoder and sets the appropriate errors
   // Returns true if there are no errors.
-  bool WriteToContainedDecoder(const char* aBuffer, uint32_t aCount,
-                               DecodeStrategy aStrategy);
+  bool WriteToContainedDecoder(const char* aBuffer, uint32_t aCount);
 
   // Processes a single dir entry of the icon resource
   void ProcessDirEntry(IconDirEntry& aTarget);
   // Sets the hotspot property of if we have a cursor
   void SetHotSpotIfCursor();
   // Creates a bitmap file header buffer, returns true if successful
   bool FillBitmapFileHeaderBuffer(int8_t *bfh);
   // Fixes the ICO height to match that of the BIH.
--- a/image/decoders/nsIconDecoder.cpp
+++ b/image/decoders/nsIconDecoder.cpp
@@ -24,18 +24,17 @@ nsIconDecoder::nsIconDecoder(RasterImage
 {
   // Nothing to do
 }
 
 nsIconDecoder::~nsIconDecoder()
 { }
 
 void
-nsIconDecoder::WriteInternal(const char* aBuffer, uint32_t aCount,
-                             DecodeStrategy)
+nsIconDecoder::WriteInternal(const char* aBuffer, uint32_t aCount)
 {
   NS_ABORT_IF_FALSE(!HasError(), "Shouldn't call WriteInternal after error!");
 
   // We put this here to avoid errors about crossing initialization with case
   // jumps on linux.
   uint32_t bytesToRead = 0;
 
   // Loop until the input data is gone
--- a/image/decoders/nsIconDecoder.h
+++ b/image/decoders/nsIconDecoder.h
@@ -36,18 +36,17 @@ class RasterImage;
 
 class nsIconDecoder : public Decoder
 {
 public:
 
   explicit nsIconDecoder(RasterImage& aImage);
   virtual ~nsIconDecoder();
 
-  virtual void WriteInternal(const char* aBuffer, uint32_t aCount,
-                             DecodeStrategy aStrategy) MOZ_OVERRIDE;
+  virtual void WriteInternal(const char* aBuffer, uint32_t aCount) MOZ_OVERRIDE;
 
   uint8_t mWidth;
   uint8_t mHeight;
   uint32_t mPixBytesRead;
   uint32_t mState;
 };
 
 enum {
--- a/image/decoders/nsJPEGDecoder.cpp
+++ b/image/decoders/nsJPEGDecoder.cpp
@@ -185,18 +185,17 @@ nsJPEGDecoder::FinishInternal()
   if ((mState != JPEG_DONE && mState != JPEG_SINK_NON_JPEG_TRAILER) &&
       (mState != JPEG_ERROR) &&
       !IsSizeDecode()) {
     mState = JPEG_DONE;
   }
 }
 
 void
-nsJPEGDecoder::WriteInternal(const char* aBuffer, uint32_t aCount,
-                             DecodeStrategy)
+nsJPEGDecoder::WriteInternal(const char* aBuffer, uint32_t aCount)
 {
   mSegment = (const JOCTET*)aBuffer;
   mSegmentLen = aCount;
 
   NS_ABORT_IF_FALSE(!HasError(), "Shouldn't call WriteInternal after error!");
 
   // Return here if there is a fatal error within libjpeg.
   nsresult error_code;
--- a/image/decoders/nsJPEGDecoder.h
+++ b/image/decoders/nsJPEGDecoder.h
@@ -51,18 +51,17 @@ struct Orientation;
 
 class nsJPEGDecoder : public Decoder
 {
 public:
   nsJPEGDecoder(RasterImage& aImage, Decoder::DecodeStyle aDecodeStyle);
   virtual ~nsJPEGDecoder();
 
   virtual void InitInternal() MOZ_OVERRIDE;
-  virtual void WriteInternal(const char* aBuffer, uint32_t aCount,
-                             DecodeStrategy aStrategy) MOZ_OVERRIDE;
+  virtual void WriteInternal(const char* aBuffer, uint32_t aCount) MOZ_OVERRIDE;
   virtual void FinishInternal() MOZ_OVERRIDE;
 
   virtual Telemetry::ID SpeedHistogram() MOZ_OVERRIDE;
   void NotifyDone();
 
 protected:
   Orientation ReadOrientationFromEXIF();
   void OutputScanlines(bool* suspend);
--- a/image/decoders/nsPNGDecoder.cpp
+++ b/image/decoders/nsPNGDecoder.cpp
@@ -160,21 +160,16 @@ void nsPNGDecoder::CreateFrame(png_uint_
       // We need padding on the first frame, which means that we don't draw into
       // part of the image at all. Report that as transparency.
       PostHasTransparency();
     }
 
     NeedNewFrame(mNumFrames, x_offset, y_offset, width, height, format);
   } else if (mNumFrames != 0) {
     NeedNewFrame(mNumFrames, x_offset, y_offset, width, height, format);
-  } else {
-    // Our preallocated frame matches up, with the possible exception of alpha.
-    if (format == gfx::SurfaceFormat::B8G8R8X8) {
-      currentFrame->SetHasNoAlpha();
-    }
   }
 
   mFrameRect = neededRect;
   mFrameHasNoAlpha = true;
 
   PR_LOG(GetPNGDecoderAccountingLog(), PR_LOG_DEBUG,
          ("PNGDecoderAccounting: nsPNGDecoder::CreateFrame -- created "
           "image frame with %dx%d pixels in container %p",
@@ -199,21 +194,19 @@ void
 nsPNGDecoder::EndImageFrame()
 {
   if (mFrameIsHidden) {
     return;
   }
 
   mNumFrames++;
 
-  Opacity opacity;
-  if (mFrameHasNoAlpha) {
+  Opacity opacity = Opacity::SOME_TRANSPARENCY;
+  if (format == gfx::SurfaceFormat::B8G8R8X8 || mFrameHasNoAlpha) {
     opacity = Opacity::OPAQUE;
-  } else {
-    opacity = Opacity::SOME_TRANSPARENCY;
   }
 
 #ifdef PNG_APNG_SUPPORTED
   uint32_t numFrames = GetFrameCount();
 
   // We can't use mPNG->num_frames_read as it may be one ahead.
   if (numFrames > 1) {
     PostInvalidation(mFrameRect);
@@ -315,18 +308,17 @@ nsPNGDecoder::InitInternal()
   png_set_progressive_read_fn(mPNG, static_cast<png_voidp>(this),
                               nsPNGDecoder::info_callback,
                               nsPNGDecoder::row_callback,
                               nsPNGDecoder::end_callback);
 
 }
 
 void
-nsPNGDecoder::WriteInternal(const char* aBuffer, uint32_t aCount,
-                            DecodeStrategy)
+nsPNGDecoder::WriteInternal(const char* aBuffer, uint32_t aCount)
 {
   NS_ABORT_IF_FALSE(!HasError(), "Shouldn't call WriteInternal after error!");
 
   // If we only want width/height, we don't need to go through libpng
   if (IsSizeDecode()) {
 
     // Are we done?
     if (mHeaderBytesRead == BYTES_NEEDED_FOR_DIMENSIONS) {
--- a/image/decoders/nsPNGDecoder.h
+++ b/image/decoders/nsPNGDecoder.h
@@ -23,18 +23,17 @@ class RasterImage;
 
 class nsPNGDecoder : public Decoder
 {
 public:
   explicit nsPNGDecoder(RasterImage& aImage);
   virtual ~nsPNGDecoder();
 
   virtual void InitInternal() MOZ_OVERRIDE;
-  virtual void WriteInternal(const char* aBuffer, uint32_t aCount,
-                             DecodeStrategy aStrategy) MOZ_OVERRIDE;
+  virtual void WriteInternal(const char* aBuffer, uint32_t aCount) MOZ_OVERRIDE;
   virtual Telemetry::ID SpeedHistogram() MOZ_OVERRIDE;
 
   void CreateFrame(png_uint_32 x_offset, png_uint_32 y_offset,
                    int32_t width, int32_t height,
                    gfx::SurfaceFormat format);
   void EndImageFrame();
 
   // Check if PNG is valid ICO (32bpp RGBA)
--- a/image/src/DecodePool.cpp
+++ b/image/src/DecodePool.cpp
@@ -61,142 +61,83 @@ public:
 private:
   explicit NotifyProgressWorker(RasterImage* aImage)
     : mImage(aImage)
   { }
 
   nsRefPtr<RasterImage> mImage;
 };
 
-class FrameNeededWorker : public nsRunnable
-{
-public:
-  /**
-   * Called when an off-main-thread decoder needs a new frame to be allocated on
-   * the main thread.
-   *
-   * After allocating the new frame, the worker will call RequestDecode to
-   * continue decoding.
-   */
-  static void Dispatch(RasterImage* aImage)
-  {
-    nsCOMPtr<nsIRunnable> worker = new FrameNeededWorker(aImage);
-    NS_DispatchToMainThread(worker);
-  }
-
-  NS_IMETHOD Run() MOZ_OVERRIDE
-  {
-    ReentrantMonitorAutoEnter lock(mImage->mDecodingMonitor);
-    nsresult rv = NS_OK;
-
-    // If we got a synchronous decode in the mean time, we don't need to do
-    // anything.
-    if (mImage->mDecoder && mImage->mDecoder->NeedsNewFrame()) {
-      rv = mImage->mDecoder->AllocateFrame();
-    }
-
-    if (NS_SUCCEEDED(rv) && mImage->mDecoder) {
-      // By definition, we're not done decoding, so enqueue us for more decoding.
-      DecodePool::Singleton()->RequestDecode(mImage);
-    }
-
-    return NS_OK;
-  }
-
-private:
-  explicit FrameNeededWorker(RasterImage* aImage)
-    : mImage(aImage)
-  { }
-
-  nsRefPtr<RasterImage> mImage;
-};
-
 class DecodeWorker : public nsRunnable
 {
 public:
   explicit DecodeWorker(RasterImage* aImage)
     : mImage(aImage)
   { }
 
   NS_IMETHOD Run() MOZ_OVERRIDE
   {
+    MOZ_ASSERT(!NS_IsMainThread());
+
     ReentrantMonitorAutoEnter lock(mImage->mDecodingMonitor);
 
     // If we were interrupted, we shouldn't do any work.
     if (mImage->mDecodeStatus == DecodeStatus::STOPPED) {
       NotifyProgressWorker::Dispatch(mImage);
       return NS_OK;
     }
 
     // If someone came along and synchronously decoded us, there's nothing for us to do.
     if (!mImage->mDecoder || mImage->IsDecodeFinished()) {
       NotifyProgressWorker::Dispatch(mImage);
       return NS_OK;
     }
 
-    // If we're a decode job that's been enqueued since a previous decode that
-    // still needs a new frame, we can't do anything. Wait until the
-    // FrameNeededWorker enqueues another frame.
-    if (mImage->mDecoder->NeedsNewFrame()) {
-      return NS_OK;
-    }
-
     mImage->mDecodeStatus = DecodeStatus::ACTIVE;
 
     size_t oldByteCount = mImage->mDecoder->BytesDecoded();
 
-    // Multithreaded decoding can be disabled. If we've done so, we don't want
-    // to monopolize the main thread, and will allow a timeout.
-    DecodeUntil type = NS_IsMainThread() ? DecodeUntil::TIME
-                                         : DecodeUntil::DONE_BYTES;
-
     size_t maxBytes = mImage->mSourceData.Length() -
                       mImage->mDecoder->BytesDecoded();
-    DecodePool::Singleton()->DecodeSomeOfImage(mImage, DecodeStrategy::ASYNC,
-                                               type, maxBytes);
+    DecodePool::Singleton()->DecodeSomeOfImage(mImage, DecodeUntil::DONE_BYTES,
+                                               maxBytes);
 
     size_t bytesDecoded = mImage->mDecoder->BytesDecoded() - oldByteCount;
 
     mImage->mDecodeStatus = DecodeStatus::WORK_DONE;
 
-    if (mImage->mDecoder && mImage->mDecoder->NeedsNewFrame()) {
-      // The decoder needs a new frame. Enqueue an event to get it; that event
-      // will enqueue another decode request when it's done.
-      FrameNeededWorker::Dispatch(mImage);
-    } else if (mImage->mDecoder &&
-               !mImage->mError &&
-               !mImage->mPendingError &&
-               !mImage->IsDecodeFinished() &&
-               bytesDecoded < maxBytes &&
-               bytesDecoded > 0) {
+    if (mImage->mDecoder &&
+        !mImage->mError &&
+        !mImage->mPendingError &&
+        !mImage->IsDecodeFinished() &&
+        bytesDecoded < maxBytes &&
+        bytesDecoded > 0) {
       // We aren't finished decoding, and we have more data, so add this request
       // to the back of the list.
       DecodePool::Singleton()->RequestDecode(mImage);
     } else {
       // Nothing more for us to do - let everyone know what happened.
       NotifyProgressWorker::Dispatch(mImage);
     }
 
     return NS_OK;
   }
 
 protected:
   virtual ~DecodeWorker()
   {
-    if (gfxPrefs::ImageMTDecodingEnabled()) {
-      // Dispatch mImage to main thread to prevent mImage from being destructed by decode thread.
-      nsCOMPtr<nsIThread> mainThread = do_GetMainThread();
-      NS_WARN_IF_FALSE(mainThread, "Couldn't get the main thread!");
-      if (mainThread) {
-        // Handle ambiguous nsISupports inheritance
-        RasterImage* rawImg = nullptr;
-        mImage.swap(rawImg);
-        DebugOnly<nsresult> rv = NS_ProxyRelease(mainThread, NS_ISUPPORTS_CAST(ImageResource*, rawImg));
-        MOZ_ASSERT(NS_SUCCEEDED(rv), "Failed to proxy release to main thread");
-      }
+    // Dispatch mImage to main thread to prevent mImage from being destructed by decode thread.
+    nsCOMPtr<nsIThread> mainThread = do_GetMainThread();
+    NS_WARN_IF_FALSE(mainThread, "Couldn't get the main thread!");
+    if (mainThread) {
+      // Handle ambiguous nsISupports inheritance
+      RasterImage* rawImg = nullptr;
+      mImage.swap(rawImg);
+      DebugOnly<nsresult> rv = NS_ProxyRelease(mainThread, NS_ISUPPORTS_CAST(ImageResource*, rawImg));
+      MOZ_ASSERT(NS_SUCCEEDED(rv), "Failed to proxy release to main thread");
     }
   }
 
 private:
   nsRefPtr<RasterImage> mImage;
 };
 
 #ifdef MOZ_NUWA_PROCESS
@@ -258,42 +199,41 @@ DecodePool::GetEventTarget()
 {
   nsCOMPtr<nsIEventTarget> target = do_QueryInterface(mThreadPool);
   return target.forget();
 }
 
 DecodePool::DecodePool()
   : mThreadPoolMutex("Thread Pool")
 {
-  if (gfxPrefs::ImageMTDecodingEnabled()) {
-    mThreadPool = do_CreateInstance(NS_THREADPOOL_CONTRACTID);
-    if (mThreadPool) {
-      mThreadPool->SetName(NS_LITERAL_CSTRING("ImageDecoder"));
-      int32_t prefLimit = gfxPrefs::ImageMTDecodingLimit();
-      uint32_t limit;
-      if (prefLimit <= 0) {
-        limit = max(PR_GetNumberOfProcessors(), 2) - 1;
-      } else {
-        limit = static_cast<uint32_t>(prefLimit);
-      }
+  mThreadPool = do_CreateInstance(NS_THREADPOOL_CONTRACTID);
+  MOZ_RELEASE_ASSERT(mThreadPool,
+                     "Should succeed in creating image decoding thread pool");
 
-      mThreadPool->SetThreadLimit(limit);
-      mThreadPool->SetIdleThreadLimit(limit);
+  mThreadPool->SetName(NS_LITERAL_CSTRING("ImageDecoder"));
+  int32_t prefLimit = gfxPrefs::ImageMTDecodingLimit();
+  uint32_t limit;
+  if (prefLimit <= 0) {
+    limit = max(PR_GetNumberOfProcessors(), 2) - 1;
+  } else {
+    limit = static_cast<uint32_t>(prefLimit);
+  }
+
+  mThreadPool->SetThreadLimit(limit);
+  mThreadPool->SetIdleThreadLimit(limit);
 
 #ifdef MOZ_NUWA_PROCESS
-      if (IsNuwaProcess()) {
-        mThreadPool->SetListener(new RIDThreadPoolListener());
-      }
+  if (IsNuwaProcess()) {
+    mThreadPool->SetListener(new RIDThreadPoolListener());
+  }
 #endif
 
-      nsCOMPtr<nsIObserverService> obsSvc = services::GetObserverService();
-      if (obsSvc) {
-        obsSvc->AddObserver(this, "xpcom-shutdown-threads", false);
-      }
-    }
+  nsCOMPtr<nsIObserverService> obsSvc = services::GetObserverService();
+  if (obsSvc) {
+    obsSvc->AddObserver(this, "xpcom-shutdown-threads", false);
   }
 }
 
 DecodePool::~DecodePool()
 {
   MOZ_ASSERT(NS_IsMainThread(), "Must shut down DecodePool on main thread!");
 }
 
@@ -318,66 +258,56 @@ DecodePool::Observe(nsISupports*, const 
 }
 
 void
 DecodePool::RequestDecode(RasterImage* aImage)
 {
   MOZ_ASSERT(aImage->mDecoder);
   aImage->mDecodingMonitor.AssertCurrentThreadIn();
 
-  // If we're currently waiting on a new frame for this image, we can't do any
-  // decoding.
-  if (!aImage->mDecoder->NeedsNewFrame()) {
-    if (aImage->mDecodeStatus == DecodeStatus::PENDING ||
-        aImage->mDecodeStatus == DecodeStatus::ACTIVE) {
-      // The image is already in our list of images to decode, or currently being
-      // decoded, so we don't have to do anything else.
-      return;
-    }
+  if (aImage->mDecodeStatus == DecodeStatus::PENDING ||
+      aImage->mDecodeStatus == DecodeStatus::ACTIVE) {
+    // The image is already in our list of images to decode, or currently being
+    // decoded, so we don't have to do anything else.
+    return;
+  }
 
-    aImage->mDecodeStatus = DecodeStatus::PENDING;
-    nsCOMPtr<nsIRunnable> worker = new DecodeWorker(aImage);
+  aImage->mDecodeStatus = DecodeStatus::PENDING;
+  nsCOMPtr<nsIRunnable> worker = new DecodeWorker(aImage);
 
-    MutexAutoLock threadPoolLock(mThreadPoolMutex);
-    if (!gfxPrefs::ImageMTDecodingEnabled() || !mThreadPool) {
-      NS_DispatchToMainThread(worker);
-    } else {
-      mThreadPool->Dispatch(worker, nsIEventTarget::DISPATCH_NORMAL);
-    }
+  // Dispatch to the thread pool if it exists. If it doesn't, we're currently
+  // shutting down, so it's OK to just drop the job on the floor.
+  MutexAutoLock threadPoolLock(mThreadPoolMutex);
+  if (mThreadPool) {
+    mThreadPool->Dispatch(worker, nsIEventTarget::DISPATCH_NORMAL);
   }
 }
 
 void
-DecodePool::DecodeABitOf(RasterImage* aImage, DecodeStrategy aStrategy)
+DecodePool::DecodeABitOf(RasterImage* aImage)
 {
   MOZ_ASSERT(NS_IsMainThread());
   aImage->mDecodingMonitor.AssertCurrentThreadIn();
 
   // If the image is waiting for decode work to be notified, go ahead and do that.
   if (aImage->mDecodeStatus == DecodeStatus::WORK_DONE) {
     aImage->FinishedSomeDecoding();
   }
 
-  DecodeSomeOfImage(aImage, aStrategy);
+  DecodeSomeOfImage(aImage);
 
   aImage->FinishedSomeDecoding();
 
-  // If the decoder needs a new frame, enqueue an event to get it; that event
-  // will enqueue another decode request when it's done.
-  if (aImage->mDecoder && aImage->mDecoder->NeedsNewFrame()) {
-    FrameNeededWorker::Dispatch(aImage);
-  } else {
-    // If we aren't yet finished decoding and we have more data in hand, add
-    // this request to the back of the priority list.
-    if (aImage->mDecoder &&
-        !aImage->mError &&
-        !aImage->IsDecodeFinished() &&
-        aImage->mSourceData.Length() > aImage->mDecoder->BytesDecoded()) {
-      RequestDecode(aImage);
-    }
+  // If we aren't yet finished decoding and we have more data in hand, add
+  // this request to the back of the priority list.
+  if (aImage->mDecoder &&
+      !aImage->mError &&
+      !aImage->IsDecodeFinished() &&
+      aImage->mSourceData.Length() > aImage->mDecoder->BytesDecoded()) {
+    RequestDecode(aImage);
   }
 }
 
 /* static */ void
 DecodePool::StopDecoding(RasterImage* aImage)
 {
   aImage->mDecodingMonitor.AssertCurrentThreadIn();
 
@@ -396,38 +326,26 @@ DecodePool::DecodeUntilSizeAvailable(Ras
   if (aImage->mDecodeStatus == DecodeStatus::WORK_DONE) {
     nsresult rv = aImage->FinishedSomeDecoding();
     if (NS_FAILED(rv)) {
       aImage->DoError();
       return rv;
     }
   }
 
-  // We use DecodeStrategy::ASYNC here because we just want to get the size
-  // information here and defer the rest of the work.
-  nsresult rv =
-    DecodeSomeOfImage(aImage, DecodeStrategy::ASYNC, DecodeUntil::SIZE);
+  nsresult rv = DecodeSomeOfImage(aImage, DecodeUntil::SIZE);
   if (NS_FAILED(rv)) {
     return rv;
   }
 
-  // If the decoder needs a new frame, enqueue an event to get it; that event
-  // will enqueue another decode request when it's done.
-  if (aImage->mDecoder && aImage->mDecoder->NeedsNewFrame()) {
-    FrameNeededWorker::Dispatch(aImage);
-  } else {
-    rv = aImage->FinishedSomeDecoding();
-  }
-
-  return rv;
+  return aImage->FinishedSomeDecoding();
 }
 
 nsresult
 DecodePool::DecodeSomeOfImage(RasterImage* aImage,
-                              DecodeStrategy aStrategy,
                               DecodeUntil aDecodeUntil /* = DecodeUntil::TIME */,
                               uint32_t bytesToDecode /* = 0 */)
 {
   MOZ_ASSERT(aImage->mInitialized, "Worker active for uninitialized container");
   aImage->mDecodingMonitor.AssertCurrentThreadIn();
 
   // If an error is flagged, it probably happened while we were waiting
   // in the event queue.
@@ -442,25 +360,16 @@ DecodePool::DecodeSomeOfImage(RasterImag
   }
 
   // If mDecoded or we don't have a decoder, we must have finished already (for
   // example, a synchronous decode request came while the worker was pending).
   if (!aImage->mDecoder || aImage->mDecoded) {
     return NS_OK;
   }
 
-  if (aImage->mDecoder->NeedsNewFrame()) {
-    if (aStrategy == DecodeStrategy::SYNC) {
-      MOZ_ASSERT(NS_IsMainThread());
-      aImage->mDecoder->AllocateFrame();
-    } else {
-      return NS_OK;
-    }
-  }
-
   nsRefPtr<Decoder> decoderKungFuDeathGrip = aImage->mDecoder;
 
   uint32_t maxBytes;
   if (aImage->mDecoder->IsSizeDecode()) {
     // Decode all available data if we're a size decode; they're cheap, and we
     // want them to be more or less synchronous.
     maxBytes = aImage->mSourceData.Length();
   } else {
@@ -477,26 +386,22 @@ DecodePool::DecodeSomeOfImage(RasterImag
   TimeStamp deadline = TimeStamp::Now() +
                        TimeDuration::FromMilliseconds(gfxPrefs::ImageMemMaxMSBeforeYield());
 
   // We keep decoding chunks until:
   //  * we don't have any data left to decode,
   //  * the decode completes,
   //  * we're an DecodeUntil::SIZE decode and we get the size, or
   //  * we run out of time.
-  // We also try to decode at least one "chunk" if we've allocated a new frame,
-  // even if we have no more data to send to the decoder.
-  while ((aImage->mSourceData.Length() > aImage->mDecoder->BytesDecoded() &&
-          bytesToDecode > 0 &&
-          !aImage->IsDecodeFinished() &&
-          !(aDecodeUntil == DecodeUntil::SIZE && aImage->mHasSize) &&
-          !aImage->mDecoder->NeedsNewFrame()) ||
-         aImage->mDecoder->NeedsToFlushData()) {
+  while (aImage->mSourceData.Length() > aImage->mDecoder->BytesDecoded() &&
+         bytesToDecode > 0 &&
+         !aImage->IsDecodeFinished() &&
+         !(aDecodeUntil == DecodeUntil::SIZE && aImage->mHasSize)) {
     uint32_t chunkSize = min(bytesToDecode, maxBytes);
-    nsresult rv = aImage->DecodeSomeData(chunkSize, aStrategy);
+    nsresult rv = aImage->DecodeSomeData(chunkSize);
     if (NS_FAILED(rv)) {
       aImage->DoError();
       return rv;
     }
 
     bytesToDecode -= chunkSize;
 
     // Yield if we've been decoding for too long. We check this _after_ decoding
--- a/image/src/DecodePool.h
+++ b/image/src/DecodePool.h
@@ -20,32 +20,16 @@
 class nsIThreadPool;
 
 namespace mozilla {
 namespace image {
 
 class Decoder;
 class RasterImage;
 
-MOZ_BEGIN_ENUM_CLASS(DecodeStrategy, uint8_t)
-  // DecodeStrategy::SYNC requests a synchronous decode, which will continue
-  // decoding frames as long as it has more source data. It returns to the
-  // caller only once decoding is complete (or until it needs more source data
-  // before continuing). Because DecodeStrategy::SYNC can involve allocating new
-  // imgFrames, it can only be run on the main thread.
-  SYNC,
-
-  // DecodeStrategy::ASYNC requests an asynchronous decode, which will continue
-  // decoding until it either finishes a frame or runs out of source data.
-  // Because DecodeStrategy::ASYNC does not allocate new imgFrames, it can be
-  // safely run off the main thread. (And hence workers in the decode pool
-  // always use it.)
-  ASYNC
-MOZ_END_ENUM_CLASS(DecodeStrategy)
-
 MOZ_BEGIN_ENUM_CLASS(DecodeStatus, uint8_t)
   INACTIVE,
   PENDING,
   ACTIVE,
   WORK_DONE,
   STOPPED
 MOZ_END_ENUM_CLASS(DecodeStatus)
 
@@ -87,17 +71,17 @@ public:
    * Ask the DecodePool to asynchronously decode this image.
    */
   void RequestDecode(RasterImage* aImage);
 
   /**
    * Decode aImage for a short amount of time, and post the remainder to the
    * queue.
    */
-  void DecodeABitOf(RasterImage* aImage, DecodeStrategy aStrategy);
+  void DecodeABitOf(RasterImage* aImage);
 
   /**
    * Ask the DecodePool to stop decoding this image.  Internally, we also
    * call this function when we finish decoding an image.
    *
    * Since the DecodePool keeps raw pointers to RasterImages, make sure you
    * call this before a RasterImage is destroyed!
    */
@@ -123,17 +107,16 @@ public:
 
   /**
    * Decode some chunks of the given image.  If aDecodeUntil is SIZE,
    * decode until we have the image's size, then stop. If bytesToDecode is
    * non-0, at most bytesToDecode bytes will be decoded. if aDecodeUntil is
    * DONE_BYTES, decode until all bytesToDecode bytes are decoded.
    */
   nsresult DecodeSomeOfImage(RasterImage* aImage,
-                             DecodeStrategy aStrategy,
                              DecodeUntil aDecodeUntil = DecodeUntil::TIME,
                              uint32_t bytesToDecode = 0);
 
 private:
   DecodePool();
   virtual ~DecodePool();
 
   static StaticRefPtr<DecodePool> sSingleton;
--- a/image/src/Decoder.cpp
+++ b/image/src/Decoder.cpp
@@ -1,21 +1,27 @@
 
 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "Decoder.h"
+
+#include "mozilla/gfx/2D.h"
+#include "imgIContainer.h"
 #include "nsIConsoleService.h"
 #include "nsIScriptError.h"
 #include "GeckoProfiler.h"
 #include "nsServiceManagerUtils.h"
 #include "nsComponentManagerUtils.h"
 
+using mozilla::gfx::IntSize;
+using mozilla::gfx::SurfaceFormat;
+
 namespace mozilla {
 namespace image {
 
 Decoder::Decoder(RasterImage &aImage)
   : mImage(aImage)
   , mProgress(NoProgress)
   , mImageData(nullptr)
   , mColormap(nullptr)
@@ -77,32 +83,31 @@ Decoder::InitSharedDecoder(uint8_t* aIma
   mImageData = aImageData;
   mImageDataLength = aImageDataLength;
   mColormap = aColormap;
   mColormapSize = aColormapSize;
   mCurrentFrame = Move(aFrameRef);
 
   // We have all the frame data, so we've started the frame.
   if (!IsSizeDecode()) {
+    mFrameCount++;
     PostFrameStart();
   }
 
   // Implementation-specific initialization
   InitInternal();
   mInitialized = true;
 }
 
 void
-Decoder::Write(const char* aBuffer, uint32_t aCount, DecodeStrategy aStrategy)
+Decoder::Write(const char* aBuffer, uint32_t aCount)
 {
   PROFILER_LABEL("ImageDecoder", "Write",
     js::ProfileEntry::Category::GRAPHICS);
 
-  MOZ_ASSERT(NS_IsMainThread() || aStrategy == DecodeStrategy::ASYNC);
-
   // We're strict about decoder errors
   MOZ_ASSERT(!HasDecoderError(),
              "Not allowed to make more decoder calls after error!");
 
   // Begin recording telemetry data.
   TimeStamp start = TimeStamp::Now();
   mChunkCount++;
 
@@ -119,32 +124,36 @@ Decoder::Write(const char* aBuffer, uint
   if (HasDataError())
     return;
 
   if (IsSizeDecode() && HasSize()) {
     // More data came in since we found the size. We have nothing to do here.
     return;
   }
 
-  // Pass the data along to the implementation
-  WriteInternal(aBuffer, aCount, aStrategy);
+  MOZ_ASSERT(!NeedsNewFrame() || HasDataError(),
+             "Should not need a new frame before writing anything");
+  MOZ_ASSERT(!NeedsToFlushData() || HasDataError(),
+             "Should not need to flush data before writing anything");
+
+  // Pass the data along to the implementation.
+  WriteInternal(aBuffer, aCount);
 
-  // If we're a synchronous decoder and we need a new frame to proceed, let's
-  // create one and call it again.
-  if (aStrategy == DecodeStrategy::SYNC) {
-    while (NeedsNewFrame() && !HasDataError()) {
-      nsresult rv = AllocateFrame();
+  // If we need a new frame to proceed, let's create one and call it again.
+  while (NeedsNewFrame() && !HasDataError()) {
+    MOZ_ASSERT(!IsSizeDecode(), "Shouldn't need new frame for size decode");
+
+    nsresult rv = AllocateFrame();
 
-      if (NS_SUCCEEDED(rv)) {
-        // Use the data we saved when we asked for a new frame.
-        WriteInternal(nullptr, 0, aStrategy);
-      }
+    if (NS_SUCCEEDED(rv)) {
+      // Use the data we saved when we asked for a new frame.
+      WriteInternal(nullptr, 0);
+    }
 
-      mNeedsToFlushData = false;
-    }
+    mNeedsToFlushData = false;
   }
 
   // Finish telemetry.
   mDecodeTime += (TimeStamp::Now() - start);
 }
 
 void
 Decoder::Finish(ShutdownReason aReason)
@@ -225,31 +234,30 @@ Decoder::FinishSharedDecoder()
     FinishInternal();
   }
 }
 
 nsresult
 Decoder::AllocateFrame()
 {
   MOZ_ASSERT(mNeedsNewFrame);
-  MOZ_ASSERT(NS_IsMainThread());
 
-  mCurrentFrame = mImage.EnsureFrame(mNewFrameData.mFrameNum,
-                                     mNewFrameData.mFrameRect,
-                                     mDecodeFlags,
-                                     mNewFrameData.mFormat,
-                                     mNewFrameData.mPaletteDepth,
-                                     mCurrentFrame.get());
+  mCurrentFrame = EnsureFrame(mNewFrameData.mFrameNum,
+                              mNewFrameData.mFrameRect,
+                              mDecodeFlags,
+                              mNewFrameData.mFormat,
+                              mNewFrameData.mPaletteDepth,
+                              mCurrentFrame.get());
 
   if (mCurrentFrame) {
     // Gather the raw pointers the decoders will use.
     mCurrentFrame->GetImageData(&mImageData, &mImageDataLength);
     mCurrentFrame->GetPaletteData(&mColormap, &mColormapSize);
 
-    if (mNewFrameData.mFrameNum == mFrameCount) {
+    if (mNewFrameData.mFrameNum + 1 == mFrameCount) {
       PostFrameStart();
     }
   } else {
     PostDataError();
   }
 
   // Mark ourselves as not needing another frame before talking to anyone else
   // so they can tell us if they need yet another.
@@ -259,33 +267,166 @@ Decoder::AllocateFrame()
   // be flushed now that we have a frame to decode into.
   if (mBytesDecoded > 0) {
     mNeedsToFlushData = true;
   }
 
   return mCurrentFrame ? NS_OK : NS_ERROR_FAILURE;
 }
 
+RawAccessFrameRef
+Decoder::EnsureFrame(uint32_t aFrameNum,
+                     const nsIntRect& aFrameRect,
+                     uint32_t aDecodeFlags,
+                     SurfaceFormat aFormat,
+                     uint8_t aPaletteDepth,
+                     imgFrame* aPreviousFrame)
+{
+  if (mDataError || NS_FAILED(mFailCode)) {
+    return RawAccessFrameRef();
+  }
+
+  MOZ_ASSERT(aFrameNum <= mFrameCount, "Invalid frame index!");
+  if (aFrameNum > mFrameCount) {
+    return RawAccessFrameRef();
+  }
+
+  // Adding a frame that doesn't already exist. This is the normal case.
+  if (aFrameNum == mFrameCount) {
+    return InternalAddFrame(aFrameNum, aFrameRect, aDecodeFlags, aFormat,
+                            aPaletteDepth, aPreviousFrame);
+  }
+
+  // We're replacing a frame. It must be the first frame; there's no reason to
+  // ever replace any other frame, since the first frame is the only one we
+  // speculatively allocate without knowing what the decoder really needs.
+  // XXX(seth): I'm not convinced there's any reason to support this at all. We
+  // should figure out how to avoid triggering this and rip it out.
+  MOZ_ASSERT(aFrameNum == 0, "Replacing a frame other than the first?");
+  MOZ_ASSERT(mFrameCount == 1, "Should have only one frame");
+  MOZ_ASSERT(aPreviousFrame, "Need the previous frame to replace");
+  if (aFrameNum != 0 || !aPreviousFrame || mFrameCount != 1) {
+    return RawAccessFrameRef();
+  }
+
+  MOZ_ASSERT(!aPreviousFrame->GetRect().IsEqualEdges(aFrameRect) ||
+             aPreviousFrame->GetFormat() != aFormat ||
+             aPreviousFrame->GetPaletteDepth() != aPaletteDepth,
+             "Replacing first frame with the same kind of frame?");
+
+  // Remove the old frame from the SurfaceCache.
+  IntSize prevFrameSize = aPreviousFrame->GetImageSize();
+  SurfaceCache::RemoveSurface(ImageKey(&mImage),
+                              RasterSurfaceKey(prevFrameSize, aDecodeFlags, 0));
+  mFrameCount = 0;
+  mInFrame = false;
+
+  // Add the new frame as usual.
+  return InternalAddFrame(aFrameNum, aFrameRect, aDecodeFlags, aFormat,
+                          aPaletteDepth, nullptr);
+}
+
+RawAccessFrameRef
+Decoder::InternalAddFrame(uint32_t aFrameNum,
+                          const nsIntRect& aFrameRect,
+                          uint32_t aDecodeFlags,
+                          SurfaceFormat aFormat,
+                          uint8_t aPaletteDepth,
+                          imgFrame* aPreviousFrame)
+{
+  MOZ_ASSERT(aFrameNum <= mFrameCount, "Invalid frame index!");
+  if (aFrameNum > mFrameCount) {
+    return RawAccessFrameRef();
+  }
+
+  MOZ_ASSERT(mImageMetadata.HasSize());
+  nsIntSize imageSize(mImageMetadata.GetWidth(), mImageMetadata.GetHeight());
+  if (imageSize.width <= 0 || imageSize.height <= 0 ||
+      aFrameRect.width <= 0 || aFrameRect.height <= 0) {
+    NS_WARNING("Trying to add frame with zero or negative size");
+    return RawAccessFrameRef();
+  }
+
+  if (!SurfaceCache::CanHold(imageSize.ToIntSize())) {
+    NS_WARNING("Trying to add frame that's too large for the SurfaceCache");
+    return RawAccessFrameRef();
+  }
+
+  nsRefPtr<imgFrame> frame = new imgFrame();
+  bool nonPremult =
+    aDecodeFlags & imgIContainer::FLAG_DECODE_NO_PREMULTIPLY_ALPHA;
+  if (NS_FAILED(frame->InitForDecoder(imageSize, aFrameRect, aFormat,
+                                      aPaletteDepth, nonPremult))) {
+    NS_WARNING("imgFrame::Init should succeed");
+    return RawAccessFrameRef();
+  }
+
+  RawAccessFrameRef ref = frame->RawAccessRef();
+  if (!ref) {
+    return RawAccessFrameRef();
+  }
+
+  bool succeeded =
+    SurfaceCache::Insert(frame, ImageKey(&mImage),
+                         RasterSurfaceKey(imageSize.ToIntSize(),
+                                          aDecodeFlags,
+                                          aFrameNum),
+                         Lifetime::Persistent);
+  if (!succeeded) {
+    return RawAccessFrameRef();
+  }
+
+  nsIntRect refreshArea;
+
+  if (aFrameNum == 1) {
+    MOZ_ASSERT(aPreviousFrame, "Must provide a previous frame when animated");
+    aPreviousFrame->SetRawAccessOnly();
+
+    // If we dispose of the first frame by clearing it, then the first frame's
+    // refresh area is all of itself.
+    // RESTORE_PREVIOUS is invalid (assumed to be DISPOSE_CLEAR).
+    AnimationData previousFrameData = aPreviousFrame->GetAnimationData();
+    if (previousFrameData.mDisposalMethod == DisposalMethod::CLEAR ||
+        previousFrameData.mDisposalMethod == DisposalMethod::CLEAR_ALL ||
+        previousFrameData.mDisposalMethod == DisposalMethod::RESTORE_PREVIOUS) {
+      refreshArea = previousFrameData.mRect;
+    }
+  }
+
+  if (aFrameNum > 0) {
+    ref->SetRawAccessOnly();
+
+    // Some GIFs are huge but only have a small area that they animate. We only
+    // need to refresh that small area when frame 0 comes around again.
+    refreshArea.UnionRect(refreshArea, frame->GetRect());
+  }
+
+  mFrameCount++;
+  mImage.OnAddedFrame(mFrameCount, refreshArea);
+
+  return ref;
+}
+
 void
 Decoder::SetSizeOnImage()
 {
   MOZ_ASSERT(mImageMetadata.HasSize(), "Should have size");
   MOZ_ASSERT(mImageMetadata.HasOrientation(), "Should have orientation");
 
   mImage.SetSize(mImageMetadata.GetWidth(),
                  mImageMetadata.GetHeight(),
                  mImageMetadata.GetOrientation());
 }
 
 /*
  * Hook stubs. Override these as necessary in decoder implementations.
  */
 
 void Decoder::InitInternal() { }
-void Decoder::WriteInternal(const char* aBuffer, uint32_t aCount, DecodeStrategy aStrategy) { }
+void Decoder::WriteInternal(const char* aBuffer, uint32_t aCount) { }
 void Decoder::FinishInternal() { }
 
 /*
  * Progress Notifications
  */
 
 void
 Decoder::PostSize(int32_t aWidth,
@@ -311,54 +452,40 @@ Decoder::PostHasTransparency()
 
 void
 Decoder::PostFrameStart()
 {
   // We shouldn't already be mid-frame
   NS_ABORT_IF_FALSE(!mInFrame, "Starting new frame but not done with old one!");
 
   // Update our state to reflect the new frame
-  mFrameCount++;
   mInFrame = true;
 
   // If we just became animated, record that fact.
   if (mFrameCount > 1) {
     mIsAnimated = true;
     mProgress |= FLAG_IS_ANIMATED;
   }
-
-  // Decoder implementations should only call this method if they successfully
-  // appended the frame to the image. So mFrameCount should always match that
-  // reported by the Image.
-  MOZ_ASSERT(mFrameCount == mImage.GetNumFrames(),
-             "Decoder frame count doesn't match image's!");
 }
 
 void
 Decoder::PostFrameStop(Opacity aFrameOpacity /* = Opacity::TRANSPARENT */,
                        DisposalMethod aDisposalMethod /* = DisposalMethod::KEEP */,
                        int32_t aTimeout /* = 0 */,
                        BlendMethod aBlendMethod /* = BlendMethod::OVER */)
 {
   // We should be mid-frame
   MOZ_ASSERT(!IsSizeDecode(), "Stopping frame during a size decode");
   MOZ_ASSERT(mInFrame, "Stopping frame when we didn't start one");
   MOZ_ASSERT(mCurrentFrame, "Stopping frame when we don't have one");
 
   // Update our state
   mInFrame = false;
 
-  if (aFrameOpacity == Opacity::OPAQUE) {
-    mCurrentFrame->SetHasNoAlpha();
-  }
-
-  mCurrentFrame->SetDisposalMethod(aDisposalMethod);
-  mCurrentFrame->SetRawTimeout(aTimeout);
-  mCurrentFrame->SetBlendMethod(aBlendMethod);
-  mCurrentFrame->ImageUpdated(mCurrentFrame->GetRect());
+  mCurrentFrame->Finish(aFrameOpacity, aDisposalMethod, aTimeout, aBlendMethod);
 
   mProgress |= FLAG_FRAME_COMPLETE | FLAG_ONLOAD_UNBLOCKED;
 }
 
 void
 Decoder::PostInvalidation(nsIntRect& aRect)
 {
   // We should be mid-frame
--- a/image/src/Decoder.h
+++ b/image/src/Decoder.h
@@ -51,17 +51,17 @@ public:
    *
    * @param aBuffer buffer containing the data to be written
    * @param aCount the number of bytes to write
    *
    * Any errors are reported by setting the appropriate state on the decoder.
    *
    * Notifications Sent: TODO
    */
-  void Write(const char* aBuffer, uint32_t aCount, DecodeStrategy aStrategy);
+  void Write(const char* aBuffer, uint32_t aCount);
 
   /**
    * Informs the decoder that all the data has been written.
    *
    * Notifications Sent: TODO
    */
   void Finish(ShutdownReason aReason);
 
@@ -154,39 +154,39 @@ public:
   };
 
   void SetDecodeFlags(uint32_t aFlags) { mDecodeFlags = aFlags; }
   uint32_t GetDecodeFlags() { return mDecodeFlags; }
 
   bool HasSize() const { return mImageMetadata.HasSize(); }
   void SetSizeOnImage();
 
+  void SetSize(const nsIntSize& aSize, const Orientation& aOrientation)
+  {
+    PostSize(aSize.width, aSize.height, aOrientation);
+  }
+
   // Use HistogramCount as an invalid Histogram ID
   virtual Telemetry::ID SpeedHistogram() { return Telemetry::HistogramCount; }
 
   ImageMetadata& GetImageMetadata() { return mImageMetadata; }
 
   // Tell the decoder infrastructure to allocate a frame. By default, frame 0
   // is created as an ARGB frame with no offset and with size width * height.
   // If decoders need something different, they must ask for it.
   // This is called by decoders when they need a new frame. These decoders
   // must then save the data they have been sent but not yet processed and
   // return from WriteInternal. When the new frame is created, WriteInternal
   // will be called again with nullptr and 0 as arguments.
   void NeedNewFrame(uint32_t frameNum, uint32_t x_offset, uint32_t y_offset,
                     uint32_t width, uint32_t height,
                     gfx::SurfaceFormat format,
                     uint8_t palette_depth = 0);
-
   virtual bool NeedsNewFrame() const { return mNeedsNewFrame; }
 
-  // Returns true if we may have stored data that we need to flush now that we
-  // have a new frame to decode into. Callers can use Write() to actually
-  // flush the data; see the documentation for that method.
-  bool NeedsToFlushData() const { return mNeedsToFlushData; }
 
   // Try to allocate a frame as described in mNewFrameData and return the
   // status code from that attempt. Clears mNewFrameData.
   virtual nsresult AllocateFrame();
 
   already_AddRefed<imgFrame> GetCurrentFrame()
   {
     nsRefPtr<imgFrame> frame = mCurrentFrame.get();
@@ -202,18 +202,17 @@ public:
 protected:
   virtual ~Decoder();
 
   /*
    * Internal hooks. Decoder implementations may override these and
    * only these methods.
    */
   virtual void InitInternal();
-  virtual void WriteInternal(const char* aBuffer, uint32_t aCount,
-    DecodeStrategy aStrategy);
+  virtual void WriteInternal(const char* aBuffer, uint32_t aCount);
   virtual void FinishInternal();
 
   /*
    * Progress notifications.
    */
 
   // Called by decoders when they determine the size of the image. Informs
   // the image of its size and sends notifications.
@@ -260,16 +259,42 @@ protected:
   // For animated images, specify the loop count. -1 means loop forever, 0
   // means a single iteration, stopping on the last frame.
   void PostDecodeDone(int32_t aLoopCount = 0);
 
   // Data errors are the fault of the source data, decoder errors are our fault
   void PostDataError();
   void PostDecoderError(nsresult aFailCode);
 
+  // Returns true if we may have stored data that we need to flush now that we
+  // have a new frame to decode into. Callers can use Write() to actually
+  // flush the data; see the documentation for that method.
+  bool NeedsToFlushData() const { return mNeedsToFlushData; }
+
+  /**
+   * Ensures that a given frame number exists with the given parameters, and
+   * returns a RawAccessFrameRef for that frame.
+   * It is not possible to create sparse frame arrays; you can only append
+   * frames to the current frame array, or if there is only one frame in the
+   * array, replace that frame.
+   * If a non-paletted frame is desired, pass 0 for aPaletteDepth.
+   */
+  RawAccessFrameRef EnsureFrame(uint32_t aFrameNum,
+                                const nsIntRect& aFrameRect,
+                                uint32_t aDecodeFlags,
+                                gfx::SurfaceFormat aFormat,
+                                uint8_t aPaletteDepth,
+                                imgFrame* aPreviousFrame);
+
+  RawAccessFrameRef InternalAddFrame(uint32_t aFrameNum,
+                                     const nsIntRect& aFrameRect,
+                                     uint32_t aDecodeFlags,
+                                     gfx::SurfaceFormat aFormat,
+                                     uint8_t aPaletteDepth,
+                                     imgFrame* aPreviousFrame);
   /*
    * Member variables.
    *
    */
   RasterImage &mImage;
   RawAccessFrameRef mCurrentFrame;
   ImageMetadata mImageMetadata;
   nsIntRect mInvalidRect; // Tracks an invalidation region in the current frame.
--- a/image/src/FrameAnimator.cpp
+++ b/image/src/FrameAnimator.cpp
@@ -242,22 +242,16 @@ FrameAnimator::InitAnimationFrameTimeIfN
 
 void
 FrameAnimator::SetAnimationFrameTime(const TimeStamp& aTime)
 {
   mCurrentAnimationFrameTime = aTime;
 }
 
 void
-FrameAnimator::SetFirstFrameRefreshArea(const nsIntRect& aRect)
-{
-  mFirstFrameRefreshArea = aRect;
-}
-
-void
 FrameAnimator::UnionFirstFrameRefreshArea(const nsIntRect& aRect)
 {
   mFirstFrameRefreshArea.UnionRect(mFirstFrameRefreshArea, aRect);
 }
 
 uint32_t
 FrameAnimator::GetCurrentAnimationFrameIndex() const
 {
@@ -290,36 +284,36 @@ FrameAnimator::GetCompositedFrame(uint32
   MOZ_ASSERT(!ref || !ref->GetIsPaletted(), "About to return a paletted frame");
   return ref;
 }
 
 int32_t
 FrameAnimator::GetTimeoutForFrame(uint32_t aFrameNum) const
 {
   RawAccessFrameRef frame = GetRawFrame(aFrameNum);
-  const int32_t timeout = frame->GetRawTimeout();
+  AnimationData data = frame->GetAnimationData();
 
   // Ensure a minimal time between updates so we don't throttle the UI thread.
   // consider 0 == unspecified and make it fast but not too fast.  Unless we
   // have a single loop GIF. See bug 890743, bug 125137, bug 139677, and bug
   // 207059. The behavior of recent IE and Opera versions seems to be:
   // IE 6/Win:
   //   10 - 50ms go 100ms
   //   >50ms go correct speed
   // Opera 7 final/Win:
   //   10ms goes 100ms
   //   >10ms go correct speed
   // It seems that there are broken tools out there that set a 0ms or 10ms
   // timeout when they really want a "default" one.  So munge values in that
   // range.
-  if (timeout >= 0 && timeout <= 10 && mLoopCount != 0) {
+  if (data.mRawTimeout >= 0 && data.mRawTimeout <= 10 && mLoopCount != 0) {
     return 100;
   }
 
-  return timeout;
+  return data.mRawTimeout;
 }
 
 size_t
 FrameAnimator::SizeOfCompositingFrames(gfxMemoryLocation aLocation,
                                        MallocSizeOf aMallocSizeOf) const
 {
   size_t n = 0;
 
@@ -353,80 +347,80 @@ FrameAnimator::DoBlend(nsIntRect* aDirty
                        uint32_t aPrevFrameIndex,
                        uint32_t aNextFrameIndex)
 {
   RawAccessFrameRef prevFrame = GetRawFrame(aPrevFrameIndex);
   RawAccessFrameRef nextFrame = GetRawFrame(aNextFrameIndex);
 
   MOZ_ASSERT(prevFrame && nextFrame, "Should have frames here");
 
-  DisposalMethod prevFrameDisposalMethod = prevFrame->GetDisposalMethod();
-  if (prevFrameDisposalMethod == DisposalMethod::RESTORE_PREVIOUS &&
+  AnimationData prevFrameData = prevFrame->GetAnimationData();
+  if (prevFrameData.mDisposalMethod == DisposalMethod::RESTORE_PREVIOUS &&
       !mCompositingPrevFrame) {
-    prevFrameDisposalMethod = DisposalMethod::CLEAR;
+    prevFrameData.mDisposalMethod = DisposalMethod::CLEAR;
   }
 
-  nsIntRect prevFrameRect = prevFrame->GetRect();
-  bool isFullPrevFrame = (prevFrameRect.x == 0 && prevFrameRect.y == 0 &&
-                          prevFrameRect.width == mSize.width &&
-                          prevFrameRect.height == mSize.height);
+  bool isFullPrevFrame = prevFrameData.mRect.x == 0 &&
+                         prevFrameData.mRect.y == 0 &&
+                         prevFrameData.mRect.width == mSize.width &&
+                         prevFrameData.mRect.height == mSize.height;
 
   // Optimization: DisposeClearAll if the previous frame is the same size as
   //               container and it's clearing itself
   if (isFullPrevFrame &&
-      (prevFrameDisposalMethod == DisposalMethod::CLEAR)) {
-    prevFrameDisposalMethod = DisposalMethod::CLEAR_ALL;
+      (prevFrameData.mDisposalMethod == DisposalMethod::CLEAR)) {
+    prevFrameData.mDisposalMethod = DisposalMethod::CLEAR_ALL;
   }
 
-  DisposalMethod nextFrameDisposalMethod = nextFrame->GetDisposalMethod();
-  nsIntRect nextFrameRect = nextFrame->GetRect();
-  bool isFullNextFrame = (nextFrameRect.x == 0 && nextFrameRect.y == 0 &&
-                          nextFrameRect.width == mSize.width &&
-                          nextFrameRect.height == mSize.height);
+  AnimationData nextFrameData = nextFrame->GetAnimationData();
+  bool isFullNextFrame = nextFrameData.mRect.x == 0 &&
+                         nextFrameData.mRect.y == 0 &&
+                         nextFrameData.mRect.width == mSize.width &&
+                         nextFrameData.mRect.height == mSize.height;
 
   if (!nextFrame->GetIsPaletted()) {
     // Optimization: Skip compositing if the previous frame wants to clear the
     //               whole image
-    if (prevFrameDisposalMethod == DisposalMethod::CLEAR_ALL) {
+    if (prevFrameData.mDisposalMethod == DisposalMethod::CLEAR_ALL) {
       aDirtyRect->SetRect(0, 0, mSize.width, mSize.height);
       return true;
     }
 
     // Optimization: Skip compositing if this frame is the same size as the
     //               container and it's fully drawing over prev frame (no alpha)
     if (isFullNextFrame &&
-        (nextFrameDisposalMethod != DisposalMethod::RESTORE_PREVIOUS) &&
-        !nextFrame->GetHasAlpha()) {
+        (nextFrameData.mDisposalMethod != DisposalMethod::RESTORE_PREVIOUS) &&
+        !nextFrameData.mHasAlpha) {
       aDirtyRect->SetRect(0, 0, mSize.width, mSize.height);
       return true;
     }
   }
 
   // Calculate area that needs updating
-  switch (prevFrameDisposalMethod) {
+  switch (prevFrameData.mDisposalMethod) {
     default:
     case DisposalMethod::NOT_SPECIFIED:
     case DisposalMethod::KEEP:
-      *aDirtyRect = nextFrameRect;
+      *aDirtyRect = nextFrameData.mRect;
       break;
 
     case DisposalMethod::CLEAR_ALL:
       // Whole image container is cleared
       aDirtyRect->SetRect(0, 0, mSize.width, mSize.height);
       break;
 
     case DisposalMethod::CLEAR:
       // Calc area that needs to be redrawn (the combination of previous and
       // this frame)
       // XXX - This could be done with multiple framechanged calls
       //       Having prevFrame way at the top of the image, and nextFrame
       //       way at the bottom, and both frames being small, we'd be
       //       telling framechanged to refresh the whole image when only two
       //       small areas are needed.
-      aDirtyRect->UnionRect(nextFrameRect, prevFrameRect);
+      aDirtyRect->UnionRect(nextFrameData.mRect, prevFrameData.mRect);
       break;
 
     case DisposalMethod::RESTORE_PREVIOUS:
       aDirtyRect->SetRect(0, 0, mSize.width, mSize.height);
       break;
   }
 
   // Optimization:
@@ -454,168 +448,171 @@ FrameAnimator::DoBlend(nsIntRect* aDirty
     needToBlankComposite = true;
   } else if (int32_t(aNextFrameIndex) != mLastCompositedFrameIndex+1) {
 
     // If we are not drawing on top of last composited frame,
     // then we are building a new composite frame, so let's clear it first.
     needToBlankComposite = true;
   }
 
+  AnimationData compositingFrameData = mCompositingFrame->GetAnimationData();
+
   // More optimizations possible when next frame is not transparent
   // But if the next frame has DisposalMethod::RESTORE_PREVIOUS,
   // this "no disposal" optimization is not possible,
   // because the frame in "after disposal operation" state
   // needs to be stored in compositingFrame, so it can be
   // copied into compositingPrevFrame later.
   bool doDisposal = true;
-  if (!nextFrame->GetHasAlpha() &&
-      nextFrameDisposalMethod != DisposalMethod::RESTORE_PREVIOUS) {
+  if (!nextFrameData.mHasAlpha &&
+      nextFrameData.mDisposalMethod != DisposalMethod::RESTORE_PREVIOUS) {
     if (isFullNextFrame) {
       // Optimization: No need to dispose prev.frame when
       // next frame is full frame and not transparent.
       doDisposal = false;
       // No need to blank the composite frame
       needToBlankComposite = false;
     } else {
-      if ((prevFrameRect.x >= nextFrameRect.x) &&
-          (prevFrameRect.y >= nextFrameRect.y) &&
-          (prevFrameRect.x + prevFrameRect.width <=
-              nextFrameRect.x + nextFrameRect.width) &&
-          (prevFrameRect.y + prevFrameRect.height <=
-              nextFrameRect.y + nextFrameRect.height)) {
+      if ((prevFrameData.mRect.x >= nextFrameData.mRect.x) &&
+          (prevFrameData.mRect.y >= nextFrameData.mRect.y) &&
+          (prevFrameData.mRect.x + prevFrameData.mRect.width <=
+              nextFrameData.mRect.x + nextFrameData.mRect.width) &&
+          (prevFrameData.mRect.y + prevFrameData.mRect.height <=
+              nextFrameData.mRect.y + nextFrameData.mRect.height)) {
         // Optimization: No need to dispose prev.frame when
         // next frame fully overlaps previous frame.
         doDisposal = false;
       }
     }
   }
 
   if (doDisposal) {
     // Dispose of previous: clear, restore, or keep (copy)
-    switch (prevFrameDisposalMethod) {
+    switch (prevFrameData.mDisposalMethod) {
       case DisposalMethod::CLEAR:
         if (needToBlankComposite) {
           // If we just created the composite, it could have anything in its
           // buffer. Clear whole frame
-          ClearFrame(mCompositingFrame->GetRawData(),
-                     mCompositingFrame->GetRect());
+          ClearFrame(compositingFrameData.mRawData,
+                     compositingFrameData.mRect);
         } else {
           // Only blank out previous frame area (both color & Mask/Alpha)
-          ClearFrame(mCompositingFrame->GetRawData(),
-                     mCompositingFrame->GetRect(),
-                     prevFrameRect);
+          ClearFrame(compositingFrameData.mRawData,
+                     compositingFrameData.mRect,
+                     prevFrameData.mRect);
         }
         break;
 
       case DisposalMethod::CLEAR_ALL:
-        ClearFrame(mCompositingFrame->GetRawData(),
-                   mCompositingFrame->GetRect());
+        ClearFrame(compositingFrameData.mRawData,
+                   compositingFrameData.mRect);
         break;
 
       case DisposalMethod::RESTORE_PREVIOUS:
         // It would be better to copy only the area changed back to
         // compositingFrame.
         if (mCompositingPrevFrame) {
-          CopyFrameImage(mCompositingPrevFrame->GetRawData(),
-                         mCompositingPrevFrame->GetRect(),
-                         mCompositingFrame->GetRawData(),
-                         mCompositingFrame->GetRect());
+          AnimationData compositingPrevFrameData =
+            mCompositingPrevFrame->GetAnimationData();
+
+          CopyFrameImage(compositingPrevFrameData.mRawData,
+                         compositingPrevFrameData.mRect,
+                         compositingFrameData.mRawData,
+                         compositingFrameData.mRect);
 
           // destroy only if we don't need it for this frame's disposal
-          if (nextFrameDisposalMethod !=
+          if (nextFrameData.mDisposalMethod !=
               DisposalMethod::RESTORE_PREVIOUS) {
             mCompositingPrevFrame.reset();
           }
         } else {
-          ClearFrame(mCompositingFrame->GetRawData(),
-                     mCompositingFrame->GetRect());
+          ClearFrame(compositingFrameData.mRawData,
+                     compositingFrameData.mRect);
         }
         break;
 
       default:
         // Copy previous frame into compositingFrame before we put the new
         // frame on top
         // Assumes that the previous frame represents a full frame (it could be
         // smaller in size than the container, as long as the frame before it
         // erased itself)
         // Note: Frame 1 never gets into DoBlend(), so (aNextFrameIndex - 1)
         // will always be a valid frame number.
         if (mLastCompositedFrameIndex != int32_t(aNextFrameIndex - 1)) {
           if (isFullPrevFrame && !prevFrame->GetIsPaletted()) {
             // Just copy the bits
-            CopyFrameImage(prevFrame->GetRawData(),
-                           prevFrame->GetRect(),
-                           mCompositingFrame->GetRawData(),
-                           mCompositingFrame->GetRect());
+            CopyFrameImage(prevFrameData.mRawData,
+                           prevFrameData.mRect,
+                           compositingFrameData.mRawData,
+                           compositingFrameData.mRect);
           } else {
             if (needToBlankComposite) {
               // Only blank composite when prev is transparent or not full.
-              if (prevFrame->GetHasAlpha() || !isFullPrevFrame) {
-                ClearFrame(mCompositingFrame->GetRawData(),
-                           mCompositingFrame->GetRect());
+              if (prevFrameData.mHasAlpha || !isFullPrevFrame) {
+                ClearFrame(compositingFrameData.mRawData,
+                           compositingFrameData.mRect);
               }
             }
-            DrawFrameTo(prevFrame->GetRawData(), prevFrameRect,
-                        prevFrame->PaletteDataLength(),
-                        prevFrame->GetHasAlpha(),
-                        mCompositingFrame->GetRawData(),
-                        mCompositingFrame->GetRect(),
-                        prevFrame->GetBlendMethod());
+            DrawFrameTo(prevFrameData.mRawData, prevFrameData.mRect,
+                        prevFrameData.mPaletteDataLength,
+                        prevFrameData.mHasAlpha,
+                        compositingFrameData.mRawData,
+                        compositingFrameData.mRect,
+                        prevFrameData.mBlendMethod);
           }
         }
     }
   } else if (needToBlankComposite) {
     // If we just created the composite, it could have anything in its
     // buffers. Clear them
-    ClearFrame(mCompositingFrame->GetRawData(),
-               mCompositingFrame->GetRect());
+    ClearFrame(compositingFrameData.mRawData,
+               compositingFrameData.mRect);
   }
 
   // Check if the frame we are composing wants the previous image restored after
   // it is done. Don't store it (again) if last frame wanted its image restored
   // too
-  if ((nextFrameDisposalMethod == DisposalMethod::RESTORE_PREVIOUS) &&
-      (prevFrameDisposalMethod != DisposalMethod::RESTORE_PREVIOUS)) {
+  if ((nextFrameData.mDisposalMethod == DisposalMethod::RESTORE_PREVIOUS) &&
+      (prevFrameData.mDisposalMethod != DisposalMethod::RESTORE_PREVIOUS)) {
     // We are storing the whole image.
     // It would be better if we just stored the area that nextFrame is going to
     // overwrite.
     if (!mCompositingPrevFrame) {
       nsRefPtr<imgFrame> newFrame = new imgFrame;
       nsresult rv = newFrame->InitForDecoder(ThebesIntSize(mSize),
                                              SurfaceFormat::B8G8R8A8);
       if (NS_FAILED(rv)) {
         mCompositingPrevFrame.reset();
         return false;
       }
 
       mCompositingPrevFrame = newFrame->RawAccessRef();
     }
 
-    CopyFrameImage(mCompositingFrame->GetRawData(),
-                   mCompositingFrame->GetRect(),
-                   mCompositingPrevFrame->GetRawData(),
-                   mCompositingPrevFrame->GetRect());
+    AnimationData compositingPrevFrameData =
+      mCompositingPrevFrame->GetAnimationData();
+
+    CopyFrameImage(compositingFrameData.mRawData,
+                   compositingFrameData.mRect,
+                   compositingPrevFrameData.mRawData,
+                   compositingPrevFrameData.mRect);
   }
 
   // blit next frame into it's correct spot
-  DrawFrameTo(nextFrame->GetRawData(), nextFrameRect,
-              nextFrame->PaletteDataLength(),
-              nextFrame->GetHasAlpha(),
-              mCompositingFrame->GetRawData(),
-              mCompositingFrame->GetRect(),
-              nextFrame->GetBlendMethod());
-
-  // Set timeout of CompositeFrame to timeout of frame we just composed
-  // Bug 177948
-  int32_t timeout = nextFrame->GetRawTimeout();
-  mCompositingFrame->SetRawTimeout(timeout);
+  DrawFrameTo(nextFrameData.mRawData, nextFrameData.mRect,
+              nextFrameData.mPaletteDataLength,
+              nextFrameData.mHasAlpha,
+              compositingFrameData.mRawData,
+              compositingFrameData.mRect,
+              nextFrameData.mBlendMethod);
 
   // Tell the image that it is fully 'downloaded'.
   nsresult rv =
-    mCompositingFrame->ImageUpdated(mCompositingFrame->GetRect());
+    mCompositingFrame->ImageUpdated(compositingFrameData.mRect);
   if (NS_FAILED(rv)) {
     return false;
   }
 
   mLastCompositedFrameIndex = int32_t(aNextFrameIndex);
 
   return true;
 }
--- a/image/src/FrameAnimator.h
+++ b/image/src/FrameAnimator.h
@@ -95,21 +95,16 @@ public:
   /**
    * The animation mode of the image.
    *
    * Constants defined in imgIContainer.idl.
    */
   void SetAnimationMode(uint16_t aAnimationMode);
 
   /**
-   * Set the area to refresh when we loop around to the first frame.
-   */
-  void SetFirstFrameRefreshArea(const nsIntRect& aRect);
-
-  /**
    * Union the area to refresh when we loop around to the first frame with this
    * rect.
    */
   void UnionFirstFrameRefreshArea(const nsIntRect& aRect);
 
   /**
    * If the animation frame time has not yet been set, set it to
    * TimeStamp::Now().
--- a/image/src/ImageMetadata.h
+++ b/image/src/ImageMetadata.h
@@ -37,18 +37,20 @@ public:
   }
   void SetLoopCount(int32_t loopcount)
   {
     mLoopCount = loopcount;
   }
 
   void SetSize(int32_t width, int32_t height, Orientation orientation)
   {
-    mSize.emplace(nsIntSize(width, height));
-    mOrientation.emplace(orientation);
+    if (!HasSize()) {
+      mSize.emplace(nsIntSize(width, height));
+      mOrientation.emplace(orientation);
+    }
   }
 
   bool HasSize() const { return mSize.isSome(); }
   bool HasOrientation() const { return mOrientation.isSome(); }
 
   int32_t GetWidth() const { return mSize->width; }
   int32_t GetHeight() const { return mSize->height; }
   Orientation GetOrientation() const { return *mOrientation; }
--- a/image/src/RasterImage.cpp
+++ b/image/src/RasterImage.cpp
@@ -124,23 +124,16 @@ static int32_t sMaxDecodeCount = 0;
   PR_BEGIN_MACRO                        \
   if (!(arg)) {                         \
     LOG_CONTAINER_ERROR;                \
     DoError();                          \
     return rv;                          \
   }                                     \
   PR_END_MACRO
 
-
-
-static int num_containers;
-static int num_discardable_containers;
-static int64_t total_source_bytes;
-static int64_t discardable_source_bytes;
-
 class ScaleRunner : public nsRunnable
 {
   enum ScaleState
   {
     eNew,
     eReady,
     eFinish,
     eFinishWithError
@@ -194,28 +187,24 @@ public:
 
     return true;
   }
 
   NS_IMETHOD Run() MOZ_OVERRIDE
   {
     if (mState == eReady) {
       // Collect information from the frames that we need to scale.
-      uint8_t* srcData = mSrcRef->GetImageData();
-      IntSize srcSize = mSrcRef->GetSize();
-      uint32_t srcStride = mSrcRef->GetImageBytesPerRow();
-      uint8_t* dstData = mDstRef->GetImageData();
-      uint32_t dstStride = mDstRef->GetImageBytesPerRow();
-      SurfaceFormat srcFormat = mSrcRef->GetFormat();
+      ScalingData srcData = mSrcRef->GetScalingData();
+      ScalingData dstData = mDstRef->GetScalingData();
 
       // Actually do the scaling.
       bool succeeded =
-        gfx::Scale(srcData, srcSize.width, srcSize.height, srcStride,
-                   dstData, mDstSize.width, mDstSize.height, dstStride,
-                   srcFormat);
+        gfx::Scale(srcData.mRawData, srcData.mSize.width, srcData.mSize.height,
+                   srcData.mBytesPerRow, dstData.mRawData, mDstSize.width,
+                   mDstSize.height, dstData.mBytesPerRow, srcData.mFormat);
 
       if (succeeded) {
         // Mark the frame as complete and discardable.
         mDstRef->ImageUpdated(mDstRef->GetRect());
         MOZ_ASSERT(mDstRef->ImageComplete(),
                    "Incomplete, but just updated the entire frame");
       }
 
@@ -308,56 +297,33 @@ RasterImage::RasterImage(ProgressTracker
   mPendingAnimation(false),
   mAnimationFinished(false),
   mWantFullDecode(false),
   mPendingError(false)
 {
   mProgressTrackerInit = new ProgressTrackerInit(this, aProgressTracker);
 
   Telemetry::GetHistogramById(Telemetry::IMAGE_DECODE_COUNT)->Add(0);
-
-  // Statistics
-  num_containers++;
 }
 
 //******************************************************************************
 RasterImage::~RasterImage()
 {
-  // Discardable statistics
-  if (mDiscardable) {
-    num_discardable_containers--;
-    discardable_source_bytes -= mSourceData.Length();
-
-    PR_LOG (GetCompressedImageAccountingLog(), PR_LOG_DEBUG,
-            ("CompressedImageAccounting: destroying RasterImage %p.  "
-             "Total Containers: %d, Discardable containers: %d, "
-             "Total source bytes: %lld, Source bytes for discardable containers %lld",
-             this,
-             num_containers,
-             num_discardable_containers,
-             total_source_bytes,
-             discardable_source_bytes));
-  }
-
   if (mDecoder) {
     // Kill off our decode request, if it's pending.  (If not, this call is
     // harmless.)
     ReentrantMonitorAutoEnter lock(mDecodingMonitor);
     DecodePool::StopDecoding(this);
     mDecoder = nullptr;
   }
 
   // Release all frames from the surface cache.
   SurfaceCache::RemoveImage(ImageKey(this));
 
   mAnim = nullptr;
-
-  // Total statistics
-  num_containers--;
-  total_source_bytes -= mSourceData.Length();
 }
 
 /* static */ void
 RasterImage::Initialize()
 {
   // Create our singletons now, so we don't have to worry about what thread
   // they're created on.
   DecodePool::Singleton();
@@ -385,22 +351,16 @@ RasterImage::Init(const char* aMimeType,
              "Transient images can't be discardable or decode-on-draw");
 
   // Store initialization data
   mSourceDataMimeType.Assign(aMimeType);
   mDiscardable = !!(aFlags & INIT_FLAG_DISCARDABLE);
   mDecodeOnDraw = !!(aFlags & INIT_FLAG_DECODE_ON_DRAW);
   mTransient = !!(aFlags & INIT_FLAG_TRANSIENT);
 
-  // Statistics
-  if (mDiscardable) {
-    num_discardable_containers++;
-    discardable_source_bytes += mSourceData.Length();
-  }
-
   // Lock this image's surfaces in the SurfaceCache if we're not discardable.
   if (!mDiscardable) {
     SurfaceCache::LockImage(ImageKey(this));
   }
 
   // Instantiate the decoder
   nsresult rv = InitDecoder(/* aDoSizeDecode = */ true);
   CONTAINER_ENSURE_SUCCESS(rv);
@@ -652,16 +612,23 @@ RasterImage::IsOpaque()
 
   // Other, we're opaque if FLAG_HAS_TRANSPARENCY is not set.
   return !(progress & FLAG_HAS_TRANSPARENCY);
 }
 
 void
 RasterImage::OnSurfaceDiscarded()
 {
+  if (!NS_IsMainThread()) {
+    nsCOMPtr<nsIRunnable> runnable =
+      NS_NewRunnableMethod(this, &RasterImage::OnSurfaceDiscarded);
+    NS_DispatchToMainThread(runnable);
+    return;
+  }
+
   if (mProgressTracker) {
     mProgressTracker->OnDiscard();
   }
 }
 
 //******************************************************************************
 /* readonly attribute boolean animated; */
 NS_IMETHODIMP
@@ -925,110 +892,81 @@ RasterImage::SizeOfDecoded(gfxMemoryLoca
   size_t n = 0;
   n += SurfaceCache::SizeOfSurfaces(ImageKey(this), aLocation, aMallocSizeOf);
   if (mAnim) {
     n += mAnim->SizeOfCompositingFrames(aLocation, aMallocSizeOf);
   }
   return n;
 }
 
-RawAccessFrameRef
-RasterImage::InternalAddFrame(uint32_t aFrameNum,
-                              const nsIntRect& aFrameRect,
-                              uint32_t aDecodeFlags,
-                              SurfaceFormat aFormat,
-                              uint8_t aPaletteDepth,
-                              imgFrame* aPreviousFrame)
+class OnAddedFrameRunnable : public nsRunnable
 {
-  // We assume that we're in the middle of decoding because we unlock the
-  // previous frame when we create a new frame, and only when decoding do we
-  // lock frames.
-  MOZ_ASSERT(mDecoder, "Only decoders may add frames!");
-
-  MOZ_ASSERT(aFrameNum <= GetNumFrames(), "Invalid frame index!");
-  if (aFrameNum > GetNumFrames()) {
-    return RawAccessFrameRef();
+public:
+  OnAddedFrameRunnable(RasterImage* aImage,
+                       uint32_t aNewFrameCount,
+                       const nsIntRect& aNewRefreshArea)
+    : mImage(aImage)
+    , mNewFrameCount(aNewFrameCount)
+    , mNewRefreshArea(aNewRefreshArea)
+  {
+    MOZ_ASSERT(aImage);
   }
 
-  if (mSize.width <= 0 || mSize.height <= 0) {
-    NS_WARNING("Trying to add frame with zero or negative size");
-    return RawAccessFrameRef();
+  NS_IMETHOD Run()
+  {
+    mImage->OnAddedFrame(mNewFrameCount, mNewRefreshArea);
+    return NS_OK;
   }
 
-  if (!SurfaceCache::CanHold(mSize.ToIntSize())) {
-    NS_WARNING("Trying to add frame that's too large for the SurfaceCache");
-    return RawAccessFrameRef();
-  }
-
-  nsRefPtr<imgFrame> frame = new imgFrame();
-  if (NS_FAILED(frame->InitForDecoder(mSize, aFrameRect, aFormat,
-                                      aPaletteDepth))) {
-    NS_WARNING("imgFrame::Init should succeed");
-    return RawAccessFrameRef();
+private:
+  nsRefPtr<RasterImage> mImage;
+  uint32_t mNewFrameCount;
+  nsIntRect mNewRefreshArea;
+};
+
+void
+RasterImage::OnAddedFrame(uint32_t aNewFrameCount,
+                          const nsIntRect& aNewRefreshArea)
+{
+  if (!NS_IsMainThread()) {
+    nsCOMPtr<nsIRunnable> runnable =
+      new OnAddedFrameRunnable(this, aNewFrameCount, aNewRefreshArea);
+    NS_DispatchToMainThread(runnable);
+    return;
   }
-  frame->SetAsNonPremult(aDecodeFlags & FLAG_DECODE_NO_PREMULTIPLY_ALPHA);
-
-  RawAccessFrameRef ref = frame->RawAccessRef();
-  if (!ref) {
-    return RawAccessFrameRef();
-  }
-
-  bool succeeded =
-    SurfaceCache::Insert(frame, ImageKey(this),
-                         RasterSurfaceKey(mSize.ToIntSize(),
-                                          aDecodeFlags,
-                                          aFrameNum),
-                         Lifetime::Persistent);
-  if (!succeeded) {
-    return RawAccessFrameRef();
-  }
-
-  if (aFrameNum == 1) {
+
+  MOZ_ASSERT((mFrameCount == 1 && aNewFrameCount == 1) ||
+             mFrameCount < aNewFrameCount,
+             "Frame count running backwards");
+
+  mFrameCount = aNewFrameCount;
+
+  if (aNewFrameCount == 2) {
     // We're becoming animated, so initialize animation stuff.
     MOZ_ASSERT(!mAnim, "Already have animation state?");
     mAnim = MakeUnique<FrameAnimator>(this, mSize.ToIntSize(), mAnimationMode);
 
     // We don't support discarding animated images (See bug 414259).
     // Lock the image and throw away the key.
     //
     // Note that this is inefficient, since we could get rid of the source data
     // too. However, doing this is actually hard, because we're probably
     // mid-decode, and thus we're decoding out of the source buffer. Since we're
     // going to fix this anyway later, and since we didn't kill the source data
     // in the old world either, locking is acceptable for the moment.
     LockImage();
 
-    MOZ_ASSERT(aPreviousFrame, "Must provide a previous frame when animated");
-    aPreviousFrame->SetRawAccessOnly();
-
-    // If we dispose of the first frame by clearing it, then the first frame's
-    // refresh area is all of itself.
-    // RESTORE_PREVIOUS is invalid (assumed to be DISPOSE_CLEAR).
-    DisposalMethod disposalMethod = aPreviousFrame->GetDisposalMethod();
-    if (disposalMethod == DisposalMethod::CLEAR ||
-        disposalMethod == DisposalMethod::CLEAR_ALL ||
-        disposalMethod == DisposalMethod::RESTORE_PREVIOUS) {
-      mAnim->SetFirstFrameRefreshArea(aPreviousFrame->GetRect());
-    }
-
     if (mPendingAnimation && ShouldAnimate()) {
       StartAnimation();
     }
   }
 
-  if (aFrameNum > 0) {
-    ref->SetRawAccessOnly();
-
-    // Some GIFs are huge but only have a small area that they animate. We only
-    // need to refresh that small area when frame 0 comes around again.
-    mAnim->UnionFirstFrameRefreshArea(frame->GetRect());
+  if (aNewFrameCount > 1) {
+    mAnim->UnionFirstFrameRefreshArea(aNewRefreshArea);
   }
-
-  mFrameCount++;
-  return ref;
 }
 
 nsresult
 RasterImage::SetSize(int32_t aWidth, int32_t aHeight, Orientation aOrientation)
 {
   MOZ_ASSERT(NS_IsMainThread());
   mDecodingMonitor.AssertCurrentThreadIn();
 
@@ -1059,68 +997,16 @@ RasterImage::SetSize(int32_t aWidth, int
   // Set the size and flag that we have it
   mSize.SizeTo(aWidth, aHeight);
   mOrientation = aOrientation;
   mHasSize = true;
 
   return NS_OK;
 }
 
-RawAccessFrameRef
-RasterImage::EnsureFrame(uint32_t aFrameNum,
-                         const nsIntRect& aFrameRect,
-                         uint32_t aDecodeFlags,
-                         SurfaceFormat aFormat,
-                         uint8_t aPaletteDepth,
-                         imgFrame* aPreviousFrame)
-{
-  if (mError) {
-    return RawAccessFrameRef();
-  }
-
-  MOZ_ASSERT(aFrameNum <= GetNumFrames(), "Invalid frame index!");
-  if (aFrameNum > GetNumFrames()) {
-    return RawAccessFrameRef();
-  }
-
-  // Adding a frame that doesn't already exist. This is the normal case.
-  if (aFrameNum == GetNumFrames()) {
-    return InternalAddFrame(aFrameNum, aFrameRect, aDecodeFlags, aFormat,
-                            aPaletteDepth, aPreviousFrame);
-  }
-
-  // We're replacing a frame. It must be the first frame; there's no reason to
-  // ever replace any other frame, since the first frame is the only one we
-  // speculatively allocate without knowing what the decoder really needs.
-  // XXX(seth): I'm not convinced there's any reason to support this at all. We
-  // should figure out how to avoid triggering this and rip it out.
-  MOZ_ASSERT(aFrameNum == 0, "Replacing a frame other than the first?");
-  MOZ_ASSERT(GetNumFrames() == 1, "Should have only one frame");
-  MOZ_ASSERT(aPreviousFrame, "Need the previous frame to replace");
-  MOZ_ASSERT(!mAnim, "Shouldn't be animated");
-  if (aFrameNum != 0 || !aPreviousFrame || GetNumFrames() != 1) {
-    return RawAccessFrameRef();
-  }
-
-  MOZ_ASSERT(!aPreviousFrame->GetRect().IsEqualEdges(aFrameRect) ||
-             aPreviousFrame->GetFormat() != aFormat ||
-             aPreviousFrame->GetPaletteDepth() != aPaletteDepth,
-             "Replacing first frame with the same kind of frame?");
-
-  // Remove the old frame from the SurfaceCache.
-  IntSize prevFrameSize = aPreviousFrame->GetImageSize();
-  SurfaceCache::RemoveSurface(ImageKey(this),
-                              RasterSurfaceKey(prevFrameSize, aDecodeFlags, 0));
-  mFrameCount = 0;
-
-  // Add the new frame as usual.
-  return InternalAddFrame(aFrameNum, aFrameRect, aDecodeFlags, aFormat,
-                          aPaletteDepth, nullptr);
-}
-
 void
 RasterImage::DecodingComplete(imgFrame* aFinalFrame)
 {
   MOZ_ASSERT(NS_IsMainThread());
 
   if (mError) {
     return;
   }
@@ -1303,17 +1189,17 @@ RasterImage::AddSourceData(const char *a
   if (mDecoded) {
     return NS_OK;
   }
 
   // If we're not storing source data and we've previously gotten the size,
   // write the data directly to the decoder. (If we haven't gotten the size,
   // we'll queue up the data and write it out when we do.)
   if (!StoringSourceData() && mHasSize) {
-    rv = WriteToDecoder(aBuffer, aCount, DecodeStrategy::SYNC);
+    rv = WriteToDecoder(aBuffer, aCount);
     CONTAINER_ENSURE_SUCCESS(rv);
 
     rv = FinishedSomeDecoding();
     CONTAINER_ENSURE_SUCCESS(rv);
   }
 
   // Otherwise, we're storing data in the source buffer
   else {
@@ -1323,31 +1209,16 @@ RasterImage::AddSourceData(const char *a
     if (!newElem)
       return NS_ERROR_OUT_OF_MEMORY;
 
     if (mDecoder) {
       DecodePool::Singleton()->RequestDecode(this);
     }
   }
 
-  // Statistics
-  total_source_bytes += aCount;
-  if (mDiscardable)
-    discardable_source_bytes += aCount;
-  PR_LOG (GetCompressedImageAccountingLog(), PR_LOG_DEBUG,
-          ("CompressedImageAccounting: Added compressed data to RasterImage %p (%s). "
-           "Total Containers: %d, Discardable containers: %d, "
-           "Total source bytes: %lld, Source bytes for discardable containers %lld",
-           this,
-           mSourceDataMimeType.get(),
-           num_containers,
-           num_discardable_containers,
-           total_source_bytes,
-           discardable_source_bytes));
-
   return NS_OK;
 }
 
 /* Note!  buf must be declared as char buf[9]; */
 // just used for logging and hashing the header
 static void
 get_header_str (char *buf, char *data, size_t data_len)
 {
@@ -1523,47 +1394,29 @@ RasterImage::Discard()
 
   // We should never discard when we have an active decoder
   NS_ABORT_IF_FALSE(!mDecoder, "Asked to discard with open decoder!");
 
   // As soon as an image becomes animated, it becomes non-discardable and any
   // timers are cancelled.
   NS_ABORT_IF_FALSE(!mAnim, "Asked to discard for animated image!");
 
-  // For post-operation logging
-  int old_frame_count = GetNumFrames();
-
   // Delete all the decoded frames.
   SurfaceCache::RemoveImage(ImageKey(this));
 
   // Flag that we no longer have decoded frames for this image
   mDecoded = false;
   mFrameCount = 0;
 
   // Notify that we discarded
   if (mProgressTracker) {
     mProgressTracker->OnDiscard();
   }
 
   mDecodeStatus = DecodeStatus::INACTIVE;
-
-  // Log
-  PR_LOG(GetCompressedImageAccountingLog(), PR_LOG_DEBUG,
-         ("CompressedImageAccounting: discarded uncompressed image "
-          "data from RasterImage %p (%s) - %d frames (cached count: %d); "
-          "Total Containers: %d, Discardable containers: %d, "
-          "Total source bytes: %lld, Source bytes for discardable containers %lld",
-          this,
-          mSourceDataMimeType.get(),
-          old_frame_count,
-          GetNumFrames(),
-          num_containers,
-          num_discardable_containers,
-          total_source_bytes,
-          discardable_source_bytes));
 }
 
 bool
 RasterImage::CanDiscard() {
   return mHasSourceData &&       // ...have the source data...
          !mDecoder &&            // Can't discard with an open decoder
          !mAnim;                 // Can never discard animated images
 }
@@ -1626,16 +1479,17 @@ RasterImage::InitDecoder(bool aDoSizeDec
 
   // Initialize the decoder
   mDecoder->SetSizeDecode(aDoSizeDecode);
   mDecoder->SetDecodeFlags(mFrameDecodeFlags);
   if (!aDoSizeDecode) {
     // We already have the size; tell the decoder so it can preallocate a
     // frame.  By default, we create an ARGB frame with no offset. If decoders
     // need a different type, they need to ask for it themselves.
+    mDecoder->SetSize(mSize, mOrientation);
     mDecoder->NeedNewFrame(0, 0, 0, mSize.width, mSize.height,
                            SurfaceFormat::B8G8R8A8);
     mDecoder->AllocateFrame();
   }
   mDecoder->Init();
   CONTAINER_ENSURE_SUCCESS(mDecoder->GetDecoderError());
 
   if (!aDoSizeDecode) {
@@ -1710,26 +1564,26 @@ RasterImage::ShutdownDecoder(ShutdownRea
     mSourceData.Clear();
   }
 
   return NS_OK;
 }
 
 // Writes the data to the decoder, updating the total number of bytes written.
 nsresult
-RasterImage::WriteToDecoder(const char *aBuffer, uint32_t aCount, DecodeStrategy aStrategy)
+RasterImage::WriteToDecoder(const char *aBuffer, uint32_t aCount)
 {
   mDecodingMonitor.AssertCurrentThreadIn();
 
   // We should have a decoder
   NS_ABORT_IF_FALSE(mDecoder, "Trying to write to null decoder!");
 
   // Write
   nsRefPtr<Decoder> kungFuDeathGrip = mDecoder;
-  mDecoder->Write(aBuffer, aCount, aStrategy);
+  mDecoder->Write(aBuffer, aCount);
 
   CONTAINER_ENSURE_SUCCESS(mDecoder->GetDecoderError());
 
   return NS_OK;
 }
 
 // This function is called in situations where it's clear that we want the
 // frames in decoded form (Draw, LookupFrame, etc).  If we're completely decoded,
@@ -1789,21 +1643,16 @@ RasterImage::RequestDecodeCore(RequestDe
 
   if (mError)
     return NS_ERROR_FAILURE;
 
   // If we're already decoded, there's nothing to do.
   if (mDecoded)
     return NS_OK;
 
-  // If we're currently waiting for a new frame, we can't do anything until
-  // that frame is allocated.
-  if (mDecoder && mDecoder->NeedsNewFrame())
-    return NS_OK;
-
   // If we have a size decoder open, make sure we get the size
   if (mDecoder && mDecoder->IsSizeDecode()) {
     nsresult rv = DecodePool::Singleton()->DecodeUntilSizeAvailable(this);
     CONTAINER_ENSURE_SUCCESS(rv);
 
     // If we didn't get the size out of the image, we won't until we get more
     // data, so signal that we want a full decode and give up for now.
     if (!mHasSize) {
@@ -1893,17 +1742,17 @@ RasterImage::RequestDecodeCore(RequestDe
 
   // If we can do decoding now, do so.  Small images will decode completely,
   // large images will decode a bit and post themselves to the event loop
   // to finish decoding.
   if (!mDecoded && mHasSourceData && aDecodeType == SYNCHRONOUS_NOTIFY_AND_SOME_DECODE) {
     PROFILER_LABEL_PRINTF("RasterImage", "DecodeABitOf",
       js::ProfileEntry::Category::GRAPHICS, "%s", GetURIString().get());
 
-    DecodePool::Singleton()->DecodeABitOf(this, DecodeStrategy::SYNC);
+    DecodePool::Singleton()->DecodeABitOf(this);
     return NS_OK;
   }
 
   if (!mDecoded) {
     // If we get this far, dispatch the worker. We do this instead of starting
     // any immediate decoding to guarantee that all our decode notifications are
     // dispatched asynchronously, and to ensure we stay responsive.
     DecodePool::Singleton()->RequestDecode(this);
@@ -1961,32 +1810,26 @@ RasterImage::SyncDecode()
     CONTAINER_ENSURE_SUCCESS(rv);
 
     if (mDecoded && mAnim) {
       // We can't redecode animated images, so we'll have to give up.
       return NS_ERROR_NOT_AVAILABLE;
     }
   }
 
-  // If we're currently waiting on a new frame for this image, create it now.
-  if (mDecoder && mDecoder->NeedsNewFrame()) {
-    mDecoder->AllocateFrame();
-  }
-
   // If we don't have a decoder, create one
   if (!mDecoder) {
     rv = InitDecoder(/* aDoSizeDecode = */ false);
     CONTAINER_ENSURE_SUCCESS(rv);
   }
 
   MOZ_ASSERT(mDecoder);
 
   // Write everything we have
-  rv = DecodeSomeData(mSourceData.Length() - mDecoder->BytesDecoded(),
-                      DecodeStrategy::SYNC);
+  rv = DecodeSomeData(mSourceData.Length() - mDecoder->BytesDecoded());
   CONTAINER_ENSURE_SUCCESS(rv);
 
   rv = FinishedSomeDecoding();
   CONTAINER_ENSURE_SUCCESS(rv);
   
   // If our decoder's still open, there's still work to be done.
   if (mDecoder) {
     DecodePool::Singleton()->RequestDecode(this);
@@ -2287,45 +2130,34 @@ RasterImage::RequestDiscard()
     Discard();
   }
 
   return NS_OK;
 }
 
 // Flushes up to aMaxBytes to the decoder.
 nsresult
-RasterImage::DecodeSomeData(size_t aMaxBytes, DecodeStrategy aStrategy)
+RasterImage::DecodeSomeData(size_t aMaxBytes)
 {
   MOZ_ASSERT(mDecoder, "Should have a decoder");
 
   mDecodingMonitor.AssertCurrentThreadIn();
 
-  // First, if we've just been called because we allocated a frame on the main
-  // thread, let the decoder deal with the data it set aside at that time by
-  // passing it a null buffer.
-  if (mDecoder->NeedsToFlushData()) {
-    nsresult rv = WriteToDecoder(nullptr, 0, aStrategy);
-    if (NS_FAILED(rv) || mDecoder->NeedsNewFrame()) {
-      return rv;
-    }
-  }
-
   // If we have nothing else to decode, return.
   if (mDecoder->BytesDecoded() == mSourceData.Length()) {
     return NS_OK;
   }
 
   MOZ_ASSERT(mDecoder->BytesDecoded() < mSourceData.Length());
 
   // write the proper amount of data
   size_t bytesToDecode = min(aMaxBytes,
                              mSourceData.Length() - mDecoder->BytesDecoded());
   return WriteToDecoder(mSourceData.Elements() + mDecoder->BytesDecoded(),
-                        bytesToDecode,
-                        aStrategy);
+                        bytesToDecode);
 
 }
 
 // There are various indicators that tell us we're finished with the decode
 // task at hand and can shut down the decoder.
 //
 // This method may not be called if there is no decoder.
 bool
@@ -2339,23 +2171,16 @@ RasterImage::IsDecodeFinished()
   if (mDecoder->IsSizeDecode()) {
     if (mDecoder->HasSize()) {
       return true;
     }
   } else if (mDecoder->GetDecodeDone()) {
     return true;
   }
 
-  // If the decoder returned because it needed a new frame and we haven't
-  // written to it since then, the decoder may be storing data that it hasn't
-  // decoded yet.
-  if (mDecoder->NeedsNewFrame() || mDecoder->NeedsToFlushData()) {
-    return false;
-  }
-
   // Otherwise, if we have all the source data and wrote all the source data,
   // we're done.
   //
   // (NB - This can be the case even for non-erroneous images because
   // Decoder::GetDecodeDone() might not return true until after we call
   // Decoder::Finish() in ShutdownDecoder())
   if (mHasSourceData && (mDecoder->BytesDecoded() == mSourceData.Length())) {
     return true;
--- a/image/src/RasterImage.h
+++ b/image/src/RasterImage.h
@@ -172,47 +172,42 @@ public:
 
   virtual size_t SizeOfSourceWithComputedFallback(MallocSizeOf aMallocSizeOf) const MOZ_OVERRIDE;
   virtual size_t SizeOfDecoded(gfxMemoryLocation aLocation,
                                MallocSizeOf aMallocSizeOf) const MOZ_OVERRIDE;
 
   /* Triggers discarding. */
   void Discard();
 
-  /* Callbacks for decoders */
+
+  //////////////////////////////////////////////////////////////////////////////
+  // Decoder callbacks.
+  //////////////////////////////////////////////////////////////////////////////
+
+  void OnAddedFrame(uint32_t aNewFrameCount, const nsIntRect& aNewRefreshArea);
 
   /** Sets the size and inherent orientation of the container. This should only
    * be called by the decoder. This function may be called multiple times, but
    * will throw an error if subsequent calls do not match the first.
    */
   nsresult SetSize(int32_t aWidth, int32_t aHeight, Orientation aOrientation);
 
   /**
-   * Ensures that a given frame number exists with the given parameters, and
-   * returns a RawAccessFrameRef for that frame.
-   * It is not possible to create sparse frame arrays; you can only append
-   * frames to the current frame array, or if there is only one frame in the
-   * array, replace that frame.
-   * If a non-paletted frame is desired, pass 0 for aPaletteDepth.
+   * Number of times to loop the image.
+   * @note -1 means forever.
    */
-  RawAccessFrameRef EnsureFrame(uint32_t aFrameNum,
-                                const nsIntRect& aFrameRect,
-                                uint32_t aDecodeFlags,
-                                gfx::SurfaceFormat aFormat,
-                                uint8_t aPaletteDepth,
-                                imgFrame* aPreviousFrame);
+  void     SetLoopCount(int32_t aLoopCount);
 
   /* notification that the entire image has been decoded */
   void DecodingComplete(imgFrame* aFinalFrame);
 
-  /**
-   * Number of times to loop the image.
-   * @note -1 means forever.
-   */
-  void     SetLoopCount(int32_t aLoopCount);
+
+  //////////////////////////////////////////////////////////////////////////////
+  // Network callbacks.
+  //////////////////////////////////////////////////////////////////////////////
 
   /* Add compressed source data to the imgContainer.
    *
    * The decoder will use this data, either immediately or at draw time, to
    * decode the image.
    *
    * XXX This method's only caller (WriteToContainer) ignores the return
    * value. Should this just return void?
@@ -304,22 +299,16 @@ private:
   uint32_t GetCurrentFrameIndex() const;
   uint32_t GetRequestedFrameIndex(uint32_t aWhichFrame) const;
 
   nsIntRect GetFirstFrameRect();
 
   size_t SizeOfDecodedWithComputedFallbackIfHeap(gfxMemoryLocation aLocation,
                                                  MallocSizeOf aMallocSizeOf) const;
 
-  RawAccessFrameRef InternalAddFrame(uint32_t aFrameNum,
-                                     const nsIntRect& aFrameRect,
-                                     uint32_t aDecodeFlags,
-                                     gfx::SurfaceFormat aFormat,
-                                     uint8_t aPaletteDepth,
-                                     imgFrame* aPreviousFrame);
   nsresult DoImageDataComplete();
 
   already_AddRefed<layers::Image> GetCurrentImage();
   void UpdateImageContainer();
 
   enum RequestDecodeType {
       ASYNCHRONOUS,
       SYNCHRONOUS_NOTIFY,
@@ -432,18 +421,18 @@ private: // data
   bool                       mPendingError:1;
 
   // Decoding
   nsresult RequestDecodeIfNeeded(nsresult aStatus, ShutdownReason aReason,
                                  bool aDone, bool aWasSize);
   nsresult WantDecodedFrames(uint32_t aFlags, bool aShouldSyncNotify);
   nsresult SyncDecode();
   nsresult InitDecoder(bool aDoSizeDecode);
-  nsresult WriteToDecoder(const char *aBuffer, uint32_t aCount, DecodeStrategy aStrategy);
-  nsresult DecodeSomeData(size_t aMaxBytes, DecodeStrategy aStrategy);
+  nsresult WriteToDecoder(const char *aBuffer, uint32_t aCount);
+  nsresult DecodeSomeData(size_t aMaxBytes);
   bool     IsDecodeFinished();
   TimeStamp mDrawStartTime;
 
   // Initializes ProgressTracker and resets it on RasterImage destruction.
   nsAutoPtr<ProgressTrackerInit> mProgressTrackerInit;
 
   nsresult ShutdownDecoder(ShutdownReason aReason);
 
--- a/image/src/SurfaceCache.cpp
+++ b/image/src/SurfaceCache.cpp
@@ -10,16 +10,17 @@
 #include "SurfaceCache.h"
 
 #include <algorithm>
 #include "mozilla/Assertions.h"
 #include "mozilla/Attributes.h"  // for MOZ_THIS_IN_INITIALIZER_LIST
 #include "mozilla/DebugOnly.h"
 #include "mozilla/Likely.h"
 #include "mozilla/Move.h"
+#include "mozilla/Mutex.h"
 #include "mozilla/RefPtr.h"
 #include "mozilla/StaticPtr.h"
 #include "nsIMemoryReporter.h"
 #include "gfx2DGlue.h"
 #include "gfxPattern.h"  // Workaround for flaw in bug 921753 part 2.
 #include "gfxPlatform.h"
 #include "gfxPrefs.h"
 #include "imgFrame.h"
@@ -114,17 +115,18 @@ private:
 /**
  * A CachedSurface associates a surface with a key that uniquely identifies that
  * surface.
  */
 class CachedSurface
 {
   ~CachedSurface() {}
 public:
-  NS_INLINE_DECL_REFCOUNTING(CachedSurface)
+  MOZ_DECLARE_REFCOUNTED_TYPENAME(CachedSurface)
+  NS_INLINE_DECL_THREADSAFE_REFCOUNTING(CachedSurface)
 
   CachedSurface(imgFrame*          aSurface,
                 const Cost         aCost,
                 const ImageKey     aImageKey,
                 const SurfaceKey&  aSurfaceKey,
                 const Lifetime     aLifetime)
     : mSurface(aSurface)
     , mCost(aCost)
@@ -210,17 +212,18 @@ private:
  * or unlocked.
  */
 class ImageSurfaceCache
 {
   ~ImageSurfaceCache() { }
 public:
   ImageSurfaceCache() : mLocked(false) { }
 
-  NS_INLINE_DECL_REFCOUNTING(ImageSurfaceCache)
+  MOZ_DECLARE_REFCOUNTED_TYPENAME(ImageSurfaceCache)
+  NS_INLINE_DECL_THREADSAFE_REFCOUNTING(ImageSurfaceCache)
 
   typedef nsRefPtrHashtable<nsGenericHashKey<SurfaceKey>, CachedSurface> SurfaceTable;
 
   bool IsEmpty() const { return mSurfaces.Count() == 0; }
   
   void Insert(const SurfaceKey& aKey, CachedSurface* aSurface)
   {
     MOZ_ASSERT(aSurface, "Should have a surface");
@@ -272,16 +275,17 @@ public:
   NS_DECL_ISUPPORTS
 
   SurfaceCacheImpl(uint32_t aSurfaceCacheExpirationTimeMS,
                    uint32_t aSurfaceCacheDiscardFactor,
                    uint32_t aSurfaceCacheSize)
     : mExpirationTracker(MOZ_THIS_IN_INITIALIZER_LIST(),
                          aSurfaceCacheExpirationTimeMS)
     , mMemoryPressureObserver(new MemoryPressureObserver)
+    , mMutex("SurfaceCache")
     , mDiscardFactor(aSurfaceCacheDiscardFactor)
     , mMaxCost(aSurfaceCacheSize)
     , mAvailableCost(aSurfaceCacheSize)
     , mLockedCost(0)
   {
     nsCOMPtr<nsIObserverService> os = services::GetObserverService();
     if (os)
       os->AddObserver(mMemoryPressureObserver, "memory-pressure", false);
@@ -293,19 +297,19 @@ private:
     nsCOMPtr<nsIObserverService> os = services::GetObserverService();
     if (os)
       os->RemoveObserver(mMemoryPressureObserver, "memory-pressure");
 
     UnregisterWeakMemoryReporter(this);
   }
 
 public:
-  void InitMemoryReporter() {
-    RegisterWeakMemoryReporter(this);
-  }
+  void InitMemoryReporter() { RegisterWeakMemoryReporter(this); }
+
+  Mutex& GetMutex() { return mMutex; }
 
   bool Insert(imgFrame*         aSurface,
               const Cost        aCost,
               const ImageKey    aImageKey,
               const SurfaceKey& aSurfaceKey,
               Lifetime          aLifetime)
   {
     MOZ_ASSERT(!Lookup(aImageKey, aSurfaceKey),
@@ -700,16 +704,17 @@ private:
     virtual ~MemoryPressureObserver() { }
   };
 
 
   nsTArray<CostEntry>                                       mCosts;
   nsRefPtrHashtable<nsPtrHashKey<Image>, ImageSurfaceCache> mImageCaches;
   SurfaceTracker                                            mExpirationTracker;
   nsRefPtr<MemoryPressureObserver>                          mMemoryPressureObserver;
+  Mutex                                                     mMutex;
   const uint32_t                                            mDiscardFactor;
   const Cost                                                mMaxCost;
   Cost                                                      mAvailableCost;
   Cost                                                      mLockedCost;
 };
 
 NS_IMPL_ISUPPORTS(SurfaceCacheImpl, nsIMemoryReporter)
 NS_IMPL_ISUPPORTS(SurfaceCacheImpl::MemoryPressureObserver, nsIObserver)
@@ -717,16 +722,17 @@ NS_IMPL_ISUPPORTS(SurfaceCacheImpl::Memo
 ///////////////////////////////////////////////////////////////////////////////
 // Public API
 ///////////////////////////////////////////////////////////////////////////////
 
 /* static */ void
 SurfaceCache::Initialize()
 {
   // Initialize preferences.
+  MOZ_ASSERT(NS_IsMainThread());
   MOZ_ASSERT(!sInstance, "Shouldn't initialize more than once");
 
   // See gfxPrefs for the default values of these preferences.
 
   // Length of time before an unused surface is removed from the cache, in
   // milliseconds.
   uint32_t surfaceCacheExpirationTimeMS =
     gfxPrefs::ImageMemSurfaceCacheMinExpirationMS();
@@ -770,112 +776,112 @@ SurfaceCache::Initialize()
                                    surfaceCacheDiscardFactor,
                                    finalSurfaceCacheSizeBytes);
   sInstance->InitMemoryReporter();
 }
 
 /* static */ void
 SurfaceCache::Shutdown()
 {
+  MOZ_ASSERT(NS_IsMainThread());
   MOZ_ASSERT(sInstance, "No singleton - was Shutdown() called twice?");
   sInstance = nullptr;
 }
 
 /* static */ DrawableFrameRef
 SurfaceCache::Lookup(const ImageKey    aImageKey,
                      const SurfaceKey& aSurfaceKey)
 {
-  MOZ_ASSERT(NS_IsMainThread());
   if (!sInstance) {
     return DrawableFrameRef();
   }
 
+  MutexAutoLock lock(sInstance->GetMutex());
   return sInstance->Lookup(aImageKey, aSurfaceKey);
 }
 
 /* static */ bool
 SurfaceCache::Insert(imgFrame*         aSurface,
                      const ImageKey    aImageKey,
                      const SurfaceKey& aSurfaceKey,
                      Lifetime          aLifetime)
 {
-  MOZ_ASSERT(NS_IsMainThread());
   if (!sInstance) {
     return false;
   }
 
+  MutexAutoLock lock(sInstance->GetMutex());
   Cost cost = ComputeCost(aSurfaceKey.Size());
   return sInstance->Insert(aSurface, cost, aImageKey, aSurfaceKey, aLifetime);
 }
 
 /* static */ bool
 SurfaceCache::CanHold(const IntSize& aSize)
 {
-  MOZ_ASSERT(NS_IsMainThread());
   if (!sInstance) {
     return false;
   }
 
   Cost cost = ComputeCost(aSize);
   return sInstance->CanHold(cost);
 }
 
 /* static */ void
 SurfaceCache::LockImage(Image* aImageKey)
 {
-  MOZ_ASSERT(NS_IsMainThread());
   if (sInstance) {
+    MutexAutoLock lock(sInstance->GetMutex());
     return sInstance->LockImage(aImageKey);
   }
 }
 
 /* static */ void
 SurfaceCache::UnlockImage(Image* aImageKey)
 {
-  MOZ_ASSERT(NS_IsMainThread());
   if (sInstance) {
+    MutexAutoLock lock(sInstance->GetMutex());
     return sInstance->UnlockImage(aImageKey);
   }
 }
 
 /* static */ void
 SurfaceCache::RemoveSurface(const ImageKey    aImageKey,
                             const SurfaceKey& aSurfaceKey)
 {
-  MOZ_ASSERT(NS_IsMainThread());
   if (sInstance) {
+    MutexAutoLock lock(sInstance->GetMutex());
     sInstance->RemoveSurface(aImageKey, aSurfaceKey);
   }
 }
 
 /* static */ void
 SurfaceCache::RemoveImage(Image* aImageKey)
 {
-  MOZ_ASSERT(NS_IsMainThread());
   if (sInstance) {
+    MutexAutoLock lock(sInstance->GetMutex());
     sInstance->RemoveImage(aImageKey);
   }
 }
 
 /* static */ void
 SurfaceCache::DiscardAll()
 {
-  MOZ_ASSERT(NS_IsMainThread());
   if (sInstance) {
+    MutexAutoLock lock(sInstance->GetMutex());
     sInstance->DiscardAll();
   }
 }
 
 /* static */ size_t
 SurfaceCache::SizeOfSurfaces(const ImageKey    aImageKey,
                              gfxMemoryLocation aLocation,
                              MallocSizeOf      aMallocSizeOf)
 {
-  MOZ_ASSERT(NS_IsMainThread());
   if (!sInstance) {
     return 0;
   }
 
+  MutexAutoLock lock(sInstance->GetMutex());
   return sInstance->SizeOfSurfaces(aImageKey, aLocation, aMallocSizeOf);
 }
 
 } // namespace image
 } // namespace mozilla
--- a/image/src/imgFrame.cpp
+++ b/image/src/imgFrame.cpp
@@ -17,16 +17,17 @@
 
 static bool gDisableOptimize = false;
 
 #include "GeckoProfiler.h"
 #include "mozilla/Likely.h"
 #include "MainThreadUtils.h"
 #include "mozilla/MemoryReporting.h"
 #include "nsMargin.h"
+#include "nsThreadUtils.h"
 #include "mozilla/CheckedInt.h"
 #include "mozilla/gfx/Tools.h"
 
 
 namespace mozilla {
 
 using namespace gfx;
 
@@ -123,29 +124,30 @@ static bool AllowedImageAndFrameDimensio
   nsIntRect imageRect(0, 0, aImageSize.width, aImageSize.height);
   if (!imageRect.Contains(aFrameRect)) {
     return false;
   }
   return true;
 }
 
 
-imgFrame::imgFrame() :
-  mDecoded(0, 0, 0, 0),
-  mDecodedMutex("imgFrame::mDecoded"),
-  mPalettedImageData(nullptr),
-  mTimeout(100),
-  mLockCount(0),
-  mDisposalMethod(DisposalMethod::NOT_SPECIFIED),
-  mBlendMethod(BlendMethod::OVER),
-  mSinglePixel(false),
-  mCompositingFailed(false),
-  mHasNoAlpha(false),
-  mNonPremult(false),
-  mOptimizable(false)
+imgFrame::imgFrame()
+  : mMutex("imgFrame")
+  , mDecoded(0, 0, 0, 0)
+  , mLockCount(0)
+  , mTimeout(100)
+  , mDisposalMethod(DisposalMethod::NOT_SPECIFIED)
+  , mBlendMethod(BlendMethod::OVER)
+  , mHasNoAlpha(false)
+  , mPalettedImageData(nullptr)
+  , mPaletteDepth(0)
+  , mNonPremult(false)
+  , mSinglePixel(false)
+  , mCompositingFailed(false)
+  , mOptimizable(false)
 {
   static bool hasCheckedOptimize = false;
   if (!hasCheckedOptimize) {
     if (PR_GetEnv("MOZ_DISABLE_IMAGE_OPTIMIZE")) {
       gDisableOptimize = true;
     }
     hasCheckedOptimize = true;
   }
@@ -156,42 +158,47 @@ imgFrame::~imgFrame()
   moz_free(mPalettedImageData);
   mPalettedImageData = nullptr;
 }
 
 nsresult
 imgFrame::InitForDecoder(const nsIntSize& aImageSize,
                          const nsIntRect& aRect,
                          SurfaceFormat aFormat,
-                         uint8_t aPaletteDepth /* = 0 */)
+                         uint8_t aPaletteDepth /* = 0 */,
+                         bool aNonPremult /* = false */)
 {
   // Assert for properties that should be verified by decoders,
   // warn for properties related to bad content.
   if (!AllowedImageAndFrameDimensions(aImageSize, aRect)) {
     NS_WARNING("Should have legal image size");
     return NS_ERROR_FAILURE;
   }
 
   mImageSize = aImageSize.ToIntSize();
   mOffset.MoveTo(aRect.x, aRect.y);
   mSize.SizeTo(aRect.width, aRect.height);
 
   mFormat = aFormat;
   mPaletteDepth = aPaletteDepth;
+  mNonPremult = aNonPremult;
 
   if (aPaletteDepth != 0) {
     // We're creating for a paletted image.
     if (aPaletteDepth > 8) {
       NS_WARNING("Should have legal palette depth");
       NS_ERROR("This Depth is not supported");
       return NS_ERROR_FAILURE;
     }
 
-    // Use the fallible allocator here
-    mPalettedImageData = (uint8_t*)moz_malloc(PaletteDataLength() + GetImageDataLength());
+    // Use the fallible allocator here. Paletted images always use 1 byte per
+    // pixel, so calculating the amount of memory we need is straightforward.
+    mPalettedImageData =
+      static_cast<uint8_t*>(moz_malloc(PaletteDataLength() +
+                                       (mSize.width * mSize.height)));
     if (!mPalettedImageData)
       NS_WARNING("moz_malloc for paletted image data should succeed");
     NS_ENSURE_TRUE(mPalettedImageData, NS_ERROR_OUT_OF_MEMORY);
   } else {
     MOZ_ASSERT(!mImageSurface, "Called imgFrame::InitForDecoder() twice?");
 
     mVBuf = AllocateBufferForImage(mSize, mFormat);
     if (!mVBuf) {
@@ -295,16 +302,17 @@ imgFrame::InitWithDrawable(gfxDrawable* 
   }
 
   return NS_OK;
 }
 
 nsresult imgFrame::Optimize()
 {
   MOZ_ASSERT(NS_IsMainThread());
+  mMutex.AssertCurrentThreadOwns();
   MOZ_ASSERT(mLockCount == 1,
              "Should only optimize when holding the lock exclusively");
 
   // Don't optimize during shutdown because gfxPlatform may not be available.
   if (ShutdownTracker::ShutdownHasStarted())
     return NS_OK;
 
   if (!mOptimizable || gDisableOptimize)
@@ -354,17 +362,18 @@ nsresult imgFrame::Optimize()
 
     // if it's not RGB24/ARGB32, don't optimize, but we never hit this at the moment
   }
 
 #ifdef ANDROID
   SurfaceFormat optFormat =
     gfxPlatform::GetPlatform()->Optimal2DFormatForContent(gfxContentType::COLOR);
 
-  if (!GetHasAlpha() && optFormat == SurfaceFormat::R5G6B5) {
+  if (mFormat != SurfaceFormat::B8G8R8A8 &&
+      optFormat == SurfaceFormat::R5G6B5) {
     RefPtr<VolatileBuffer> buf =
       AllocateBufferForImage(mSize, optFormat);
     if (!buf)
       return NS_OK;
 
     RefPtr<DataSourceSurface> surf =
       CreateLockedSurface(buf, mSize, optFormat);
     if (!surf)
@@ -423,32 +432,36 @@ RawAccessFrameRef
 imgFrame::RawAccessRef()
 {
   return RawAccessFrameRef(this);
 }
 
 void
 imgFrame::SetRawAccessOnly()
 {
-  MOZ_ASSERT(mLockCount > 0, "Must hold a RawAccessFrameRef");
+  AssertImageDataLocked();
+
   // Lock our data and throw away the key.
   LockImageData();
 }
 
 
 imgFrame::SurfaceWithFormat
 imgFrame::SurfaceForDrawing(bool               aDoPadding,
                             bool               aDoPartialDecode,
                             bool               aDoTile,
                             gfxContext*        aContext,
                             const nsIntMargin& aPadding,
                             gfxRect&           aImageRect,
                             ImageRegion&       aRegion,
                             SourceSurface*     aSurface)
 {
+  MOZ_ASSERT(NS_IsMainThread());
+  mMutex.AssertCurrentThreadOwns();
+
   IntSize size(int32_t(aImageRect.Width()), int32_t(aImageRect.Height()));
   if (!aDoPadding && !aDoPartialDecode) {
     NS_ASSERTION(!mSinglePixel, "This should already have been handled");
     return SurfaceWithFormat(new gfxSurfaceDrawable(aSurface, ThebesIntSize(size)), mFormat);
   }
 
   gfxRect available = gfxRect(mDecoded.x, mDecoded.y, mDecoded.width, mDecoded.height);
 
@@ -491,43 +504,46 @@ imgFrame::SurfaceForDrawing(bool        
 }
 
 bool imgFrame::Draw(gfxContext* aContext, const ImageRegion& aRegion,
                     GraphicsFilter aFilter, uint32_t aImageFlags)
 {
   PROFILER_LABEL("imgFrame", "Draw",
     js::ProfileEntry::Category::GRAPHICS);
 
+  MOZ_ASSERT(NS_IsMainThread());
   NS_ASSERTION(!aRegion.Rect().IsEmpty(), "Drawing empty region!");
   NS_ASSERTION(!aRegion.IsRestricted() ||
                !aRegion.Rect().Intersect(aRegion.Restriction()).IsEmpty(),
                "We must be allowed to sample *some* source pixels!");
   NS_ASSERTION(!mPalettedImageData, "Directly drawing a paletted image!");
 
+  MutexAutoLock lock(mMutex);
+
   nsIntMargin padding(mOffset.y,
                       mImageSize.width - (mOffset.x + mSize.width),
                       mImageSize.height - (mOffset.y + mSize.height),
                       mOffset.x);
 
   bool doPadding = padding != nsIntMargin(0,0,0,0);
-  bool doPartialDecode = !ImageComplete();
+  bool doPartialDecode = !ImageCompleteInternal();
 
   if (mSinglePixel && !doPadding && !doPartialDecode) {
     if (mSinglePixelColor.a == 0.0) {
       return true;
     }
     RefPtr<DrawTarget> dt = aContext->GetDrawTarget();
     dt->FillRect(ToRect(aRegion.Rect()),
                  ColorPattern(mSinglePixelColor),
                  DrawOptions(1.0f,
                              CompositionOpForOp(aContext->CurrentOperator())));
     return true;
   }
 
-  RefPtr<SourceSurface> surf = GetSurface();
+  RefPtr<SourceSurface> surf = GetSurfaceInternal();
   if (!surf && !mSinglePixel) {
     return false;
   }
 
   gfxRect imageRect(0, 0, mImageSize.width, mImageSize.height);
   bool doTile = !imageRect.Contains(aRegion.Rect()) &&
                 !(aImageFlags & imgIContainer::FLAG_CLAMP);
   ImageRegion region(aRegion);
@@ -545,102 +561,139 @@ bool imgFrame::Draw(gfxContext* aContext
   if (surfaceResult.IsValid()) {
     gfxUtils::DrawPixelSnapped(aContext, surfaceResult.mDrawable,
                                imageRect.Size(), region, surfaceResult.mFormat,
                                aFilter, aImageFlags);
   }
   return true;
 }
 
-// This can be called from any thread, but not simultaneously.
-nsresult imgFrame::ImageUpdated(const nsIntRect &aUpdateRect)
+nsresult
+imgFrame::ImageUpdated(const nsIntRect& aUpdateRect)
 {
-  MutexAutoLock lock(mDecodedMutex);
+  MutexAutoLock lock(mMutex);
+  return ImageUpdatedInternal(aUpdateRect);
+}
+
+nsresult
+imgFrame::ImageUpdatedInternal(const nsIntRect& aUpdateRect)
+{
+  mMutex.AssertCurrentThreadOwns();
 
   mDecoded.UnionRect(mDecoded, aUpdateRect);
 
   // clamp to bounds, in case someone sends a bogus updateRect (I'm looking at
   // you, gif decoder)
   nsIntRect boundsRect(mOffset, nsIntSize(mSize.width, mSize.height));
   mDecoded.IntersectRect(mDecoded, boundsRect);
 
   return NS_OK;
 }
 
+void
+imgFrame::Finish(Opacity aFrameOpacity, DisposalMethod aDisposalMethod,
+                 int32_t aRawTimeout, BlendMethod aBlendMethod)
+{
+  MutexAutoLock lock(mMutex);
+  MOZ_ASSERT(mLockCount > 0, "Image data should be locked");
+
+  if (aFrameOpacity == Opacity::OPAQUE) {
+    mHasNoAlpha = true;
+  }
+
+  mDisposalMethod = aDisposalMethod;
+  mTimeout = aRawTimeout;
+  mBlendMethod = aBlendMethod;
+  ImageUpdatedInternal(GetRect());
+}
+
 nsIntRect imgFrame::GetRect() const
 {
   return nsIntRect(mOffset, nsIntSize(mSize.width, mSize.height));
 }
 
 int32_t
 imgFrame::GetStride() const
 {
+  mMutex.AssertCurrentThreadOwns();
+
   if (mImageSurface) {
     return mImageSurface->Stride();
   }
 
   return VolatileSurfaceStride(mSize, mFormat);
 }
 
 SurfaceFormat imgFrame::GetFormat() const
 {
+  MutexAutoLock lock(mMutex);
   return mFormat;
 }
 
 uint32_t imgFrame::GetImageBytesPerRow() const
 {
+  mMutex.AssertCurrentThreadOwns();
+
   if (mVBuf)
     return mSize.width * BytesPerPixel(mFormat);
 
   if (mPaletteDepth)
     return mSize.width;
 
   return 0;
 }
 
 uint32_t imgFrame::GetImageDataLength() const
 {
   return GetImageBytesPerRow() * mSize.height;
 }
 
-void imgFrame::GetImageData(uint8_t **aData, uint32_t *length) const
+void
+imgFrame::GetImageData(uint8_t** aData, uint32_t* aLength) const
 {
-  NS_ABORT_IF_FALSE(mLockCount != 0, "Can't GetImageData unless frame is locked");
+  MutexAutoLock lock(mMutex);
+  GetImageDataInternal(aData, aLength);
+}
 
-  if (mImageSurface)
+void
+imgFrame::GetImageDataInternal(uint8_t** aData, uint32_t* aLength) const
+{
+  mMutex.AssertCurrentThreadOwns();
+  MOZ_ASSERT(mLockCount > 0, "Image data should be locked");
+
+  if (mImageSurface) {
     *aData = mVBufPtr;
-  else if (mPalettedImageData)
+    MOZ_ASSERT(*aData, "mImageSurface is non-null, but mVBufPtr is null in GetImageData");
+  } else if (mPalettedImageData) {
     *aData = mPalettedImageData + PaletteDataLength();
-  else
+    MOZ_ASSERT(*aData, "mPalettedImageData is non-null, but result is null in GetImageData");
+  } else {
+    MOZ_ASSERT(false, "Have neither mImageSurface nor mPalettedImageData in GetImageData");
     *aData = nullptr;
+  }
 
-  *length = GetImageDataLength();
+  *aLength = GetImageDataLength();
 }
 
 uint8_t* imgFrame::GetImageData() const
 {
   uint8_t *data;
   uint32_t length;
   GetImageData(&data, &length);
   return data;
 }
 
 bool imgFrame::GetIsPaletted() const
 {
   return mPalettedImageData != nullptr;
 }
 
-bool imgFrame::GetHasAlpha() const
-{
-  return mFormat == SurfaceFormat::B8G8R8A8;
-}
-
 void imgFrame::GetPaletteData(uint32_t **aPalette, uint32_t *length) const
 {
-  NS_ABORT_IF_FALSE(mLockCount != 0, "Can't GetPaletteData unless frame is locked");
+  AssertImageDataLocked();
 
   if (!mPalettedImageData) {
     *aPalette = nullptr;
     *length = 0;
   } else {
     *aPalette = (uint32_t *) mPalettedImageData;
     *length = PaletteDataLength();
   }
@@ -649,45 +702,53 @@ void imgFrame::GetPaletteData(uint32_t *
 uint32_t* imgFrame::GetPaletteData() const
 {
   uint32_t* data;
   uint32_t length;
   GetPaletteData(&data, &length);
   return data;
 }
 
-uint8_t*
-imgFrame::GetRawData() const
+nsresult
+imgFrame::LockImageData()
 {
-  MOZ_ASSERT(mLockCount, "Should be locked to call GetRawData()");
-  if (mPalettedImageData) {
-    return mPalettedImageData;
-  }
-  return GetImageData();
-}
+  MutexAutoLock lock(mMutex);
 
-nsresult imgFrame::LockImageData()
-{
-  MOZ_ASSERT(NS_IsMainThread());
-
-  NS_ABORT_IF_FALSE(mLockCount >= 0, "Unbalanced locks and unlocks");
+  MOZ_ASSERT(mLockCount >= 0, "Unbalanced locks and unlocks");
   if (mLockCount < 0) {
     return NS_ERROR_FAILURE;
   }
 
   mLockCount++;
 
   // If we are not the first lock, there's nothing to do.
   if (mLockCount != 1) {
     return NS_OK;
   }
 
+  // If we're the first lock, but have an image surface, we're OK.
+  if (mImageSurface) {
+    mVBufPtr = mVBuf;
+    return NS_OK;
+  }
+
   // Paletted images don't have surfaces, so there's nothing to do.
-  if (mPalettedImageData)
+  if (mPalettedImageData) {
     return NS_OK;
+  }
+
+  return Deoptimize();
+}
+
+nsresult
+imgFrame::Deoptimize()
+{
+  MOZ_ASSERT(NS_IsMainThread());
+  mMutex.AssertCurrentThreadOwns();
+  MOZ_ASSERT(!mImageSurface);
 
   if (!mImageSurface) {
     if (mVBuf) {
       VolatileBufferPtr<uint8_t> ref(mVBuf);
       if (ref.WasBufferPurged())
         return NS_ERROR_FAILURE;
 
       mImageSurface = CreateLockedSurface(mVBuf, mSize, mFormat);
@@ -741,29 +802,61 @@ nsresult imgFrame::LockImageData()
       mOptSurface = nullptr;
     }
   }
 
   mVBufPtr = mVBuf;
   return NS_OK;
 }
 
-nsresult imgFrame::UnlockImageData()
+void
+imgFrame::AssertImageDataLocked() const
+{
+#ifdef DEBUG
+  MutexAutoLock lock(mMutex);
+  MOZ_ASSERT(mLockCount > 0, "Image data should be locked");
+#endif
+}
+
+class UnlockImageDataRunnable : public nsRunnable
 {
-  MOZ_ASSERT(NS_IsMainThread());
+public:
+  explicit UnlockImageDataRunnable(imgFrame* aTarget)
+    : mTarget(aTarget)
+  {
+    MOZ_ASSERT(mTarget);
+  }
+
+  NS_IMETHOD Run() { return mTarget->UnlockImageData(); }
+
+private:
+  nsRefPtr<imgFrame> mTarget;
+};
+
+nsresult
+imgFrame::UnlockImageData()
+{
+  MutexAutoLock lock(mMutex);
 
   MOZ_ASSERT(mLockCount > 0, "Unlocking an unlocked image!");
   if (mLockCount <= 0) {
     return NS_ERROR_FAILURE;
   }
 
   // If we're about to become unlocked, we don't need to hold on to our data
   // surface anymore. (But we don't need to do anything for paletted images,
   // which don't have surfaces.)
   if (mLockCount == 1 && !mPalettedImageData) {
+    // We can't safely optimize off-main-thread, so create a runnable to do it.
+    if (!NS_IsMainThread()) {
+      nsCOMPtr<nsIRunnable> runnable = new UnlockImageDataRunnable(this);
+      NS_DispatchToMainThread(runnable);
+      return NS_OK;
+    }
+
     // If we're using a surface format with alpha but the image has no alpha,
     // change the format. This doesn't change the underlying data at all, but
     // allows DrawTargets to avoid blending when drawing known opaque images.
     if (mHasNoAlpha && mFormat == SurfaceFormat::B8G8R8A8 && mImageSurface) {
       mFormat = SurfaceFormat::B8G8R8X8;
       mImageSurface = CreateLockedSurface(mVBuf, mSize, mFormat);
     }
 
@@ -778,23 +871,47 @@ nsresult imgFrame::UnlockImageData()
   mLockCount--;
 
   return NS_OK;
 }
 
 void
 imgFrame::SetOptimizable()
 {
-  MOZ_ASSERT(mLockCount, "Expected to be locked when SetOptimizable is called");
+  MOZ_ASSERT(NS_IsMainThread());
+  AssertImageDataLocked();
   mOptimizable = true;
 }
 
+Color
+imgFrame::SinglePixelColor() const
+{
+  MOZ_ASSERT(NS_IsMainThread());
+  return mSinglePixelColor;
+}
+
+bool
+imgFrame::IsSinglePixel() const
+{
+  MOZ_ASSERT(NS_IsMainThread());
+  return mSinglePixel;
+}
+
 TemporaryRef<SourceSurface>
 imgFrame::GetSurface()
 {
+  MutexAutoLock lock(mMutex);
+  return GetSurfaceInternal();
+}
+
+TemporaryRef<SourceSurface>
+imgFrame::GetSurfaceInternal()
+{
+  mMutex.AssertCurrentThreadOwns();
+