Merge inbound to m-c a=merge
authorWes Kocher <wkocher@mozilla.com>
Fri, 24 Oct 2014 18:25:24 -0700
changeset 212324 e37231060eb490257b582c2195b3f43e2a38ad27
parent 212270 e8a6f40728b76f6388b01b890a21bbd9c78b829d (current diff)
parent 212323 6a37732cb6b219176e41d6e6eb60a000037da18c (diff)
child 212325 45b09d226afdaf8a44b6a24c9f534e7a791412af
child 212328 966de95352ffecbd9e4944715d41c97e63f1222f
child 212344 d3924a9d4c7f80a3b8d7e03e2327f1f99239dac7
child 212378 7bcc6d4e76b4a2bbe046dedebea34a1ef1066826
push id27704
push userkwierso@gmail.com
push dateSat, 25 Oct 2014 01:25:30 +0000
treeherdermozilla-central@e37231060eb4 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmerge
milestone36.0a1
first release with
nightly linux32
e37231060eb4 / 36.0a1 / 20141025030202 / files
nightly linux64
e37231060eb4 / 36.0a1 / 20141025030202 / files
nightly mac
e37231060eb4 / 36.0a1 / 20141025030202 / files
nightly win32
e37231060eb4 / 36.0a1 / 20141025030202 / files
nightly win64
e37231060eb4 / 36.0a1 / 20141025030202 / 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 inbound to m-c a=merge
new file mode 100644
--- /dev/null
+++ b/content/base/crashtests/828903-iframe.html
@@ -0,0 +1,46 @@
+<html>
+<script>
+function start() {
+try{o33=document.documentElement;}catch(e){}
+try{o1.appendChild;}catch(e){}
+try{tmp = document.createElement('iframe');}catch(e){}
+try{tmp.id = 'id36'}catch(e){}
+try{o33.ownerDocument.documentElement.appendChild(tmp);}catch(e){}
+try{o51=o33.ownerDocument.getElementById('id36').contentDocument;}catch(e){}
+try{o579=document.documentElement;}catch(e){}
+try{tmp.id = 'id421'}catch(e){}
+try{o619=document.getElementById('id421').contentDocument;}catch(e){}
+try{o622=window.document.getElementById('id421').contentWindow.document;}catch(e){}
+try{o875.setAttributeNS(null,'letter-spacing','normal');}catch(e){}
+try{o884=document.createElementNS('http://www.w3.org/2000/svg','set');;}catch(e){}
+try{o887=document.createElementNS('http://www.w3.org/2000/svg','desc');;}catch(e){}
+try{o884.appendChild(o887);}catch(e){}
+try{o887=document.documentElement;}catch(e){}
+try{o1041=o622.createElement('bdi');;}catch(e){}
+window.setTimeout('start2()',100);
+}
+function start2() {
+try{o1042=document.createElement('input');;}catch(e){}
+try{o1043=o51.createElement('input');;}catch(e){}
+try{o884.appendChild(o1043);}catch(e){}
+try{o1053=o1043.previousElementSibling;}catch(e){}
+try{o1067=o619.createElement('blockquote');;}catch(e){}
+try{o1062.appendChild(o1067);}catch(e){}
+try{o1074=o619.createElement('ruby');;}catch(e){}
+try{o1067.appendChild(o1074);}catch(e){}
+try{document.body.appendChild(o1041);}catch(e){}
+try{o1041.appendChild(o1042);}catch(e){}
+try{o1095=o51.createTextNode(unescape('%uff0f%u017f%u0390%ufffa%u2073%uff4d%uDF53%u0261'));;}catch(e){}
+try{o1053.appendChild(o1095);}catch(e){}
+try{o1041.appendChild(o1043.parentNode);}catch(e){}
+try{o1042.appendChild(o1067);}catch(e){}
+try{o1053=null;}catch(e){}
+try{o1095=null;}catch(e){}
+try{for(var xrn in o884.childNodes) o884.removeChild(o884.childNodes[xrn]);}catch(e){}
+//fuzzer.gc();
+location.reload();
+}
+</script>
+<body onload="start()">
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/content/base/crashtests/828903.html
@@ -0,0 +1,28 @@
+<!DOCTYPE HTML>
+<html class="reftest-wait"><head>
+    <meta charset="utf-8">
+    <title>Testcase for bug 828903</title>
+<script>
+function reload() {
+  this.location.reload();
+}
+// Run the test for 2 seconds
+setTimeout(function() {
+    document.documentElement.removeChild(document.body);
+    document.documentElement.className = "";
+  }, 2000);
+</script>
+</head>
+<body onload="document.body.getBoundingClientRect()">
+
+<iframe onload="this.contentWindow.setTimeout(reload,1113)" src="828903-iframe.html"></iframe>
+<iframe onload="this.contentWindow.setTimeout(reload,1233)" src="828903-iframe.html"></iframe>
+<iframe onload="this.contentWindow.setTimeout(reload,1313)" src="828903-iframe.html"></iframe>
+<iframe onload="this.contentWindow.setTimeout(reload,1433)" src="828903-iframe.html"></iframe>
+<iframe onload="this.contentWindow.setTimeout(reload,1113)" src="828903-iframe.html"></iframe>
+<iframe onload="this.contentWindow.setTimeout(reload,1233)" src="828903-iframe.html"></iframe>
+<iframe onload="this.contentWindow.setTimeout(reload,1313)" src="828903-iframe.html"></iframe>
+<iframe onload="this.contentWindow.setTimeout(reload,1433)" src="828903-iframe.html"></iframe>
+
+</body>
+</html>
--- a/content/base/crashtests/crashtests.list
+++ b/content/base/crashtests/crashtests.list
@@ -123,16 +123,17 @@ load 815477.html
 load 815500.html
 load 816253.html
 load 819014.html
 load 822691.html
 load 822723.html
 load 824719.html
 load 827190.html
 load 828054.html
+load 828903.html
 load 829428.html
 load 830098.html
 load 831287.html
 load 832644.html
 load 836890.html
 skip-if(browserIsRemote) load 838489-1.html # <keygen> broken in e10s (bug 582297)
 skip-if(browserIsRemote) load 838489-2.html # <keygen> broken in e10s (bug 582297)
 load 841205.html
--- a/content/base/public/nsContentPolicyUtils.h
+++ b/content/base/public/nsContentPolicyUtils.h
@@ -107,16 +107,17 @@ NS_CP_ContentTypeName(uint32_t contentTy
     CASE_RETURN( TYPE_DTD               );
     CASE_RETURN( TYPE_FONT              );
     CASE_RETURN( TYPE_MEDIA             );
     CASE_RETURN( TYPE_WEBSOCKET         );
     CASE_RETURN( TYPE_CSP_REPORT        );
     CASE_RETURN( TYPE_XSLT              );
     CASE_RETURN( TYPE_BEACON            );
     CASE_RETURN( TYPE_FETCH             );
+    CASE_RETURN( TYPE_IMAGESET          );
    default:
     return "<Unknown Type>";
   }
 }
 
 #endif // defined(PR_LOGGING)
 
 #undef CASE_RETURN
--- a/content/base/public/nsContentUtils.h
+++ b/content/base/public/nsContentUtils.h
@@ -25,16 +25,17 @@
 #include "mozilla/TimeStamp.h"
 #include "nsContentListDeclarations.h"
 #include "nsMathUtils.h"
 #include "nsTArrayForwardDeclare.h"
 #include "Units.h"
 #include "mozilla/dom/AutocompleteInfoBinding.h"
 #include "mozilla/dom/ScriptSettings.h"
 #include "mozilla/FloatingPoint.h"
+#include "nsIContentPolicy.h"
 
 #if defined(XP_WIN)
 // Undefine LoadImage to prevent naming conflict with Windows.
 #undef LoadImage
 #endif
 
 class imgICache;
 class imgIContainer;
@@ -586,51 +587,58 @@ public:
 
   /**
    * Method to do security and content policy checks on the image URI
    *
    * @param aURI uri of the image to be loaded
    * @param aContext the context the image is loaded in (eg an element)
    * @param aLoadingDocument the document we belong to
    * @param aLoadingPrincipal the principal doing the load
+   * @param [aContentPolicyType=nsIContentPolicy::TYPE_IMAGE] (Optional)
+   *        The CP content type to use
    * @param aImageBlockingStatus the nsIContentPolicy blocking status for this
    *        image.  This will be set even if a security check fails for the
    *        image, to some reasonable REJECT_* value.  This out param will only
    *        be set if it's non-null.
    * @return true if the load can proceed, or false if it is blocked.
    *         Note that aImageBlockingStatus, if set will always be an ACCEPT
    *         status if true is returned and always be a REJECT_* status if
    *         false is returned.
    */
   static bool CanLoadImage(nsIURI* aURI,
-                             nsISupports* aContext,
-                             nsIDocument* aLoadingDocument,
-                             nsIPrincipal* aLoadingPrincipal,
-                             int16_t* aImageBlockingStatus = nullptr);
+                           nsISupports* aContext,
+                           nsIDocument* aLoadingDocument,
+                           nsIPrincipal* aLoadingPrincipal,
+                           int16_t* aImageBlockingStatus = nullptr,
+                           uint32_t aContentPolicyType = nsIContentPolicy::TYPE_IMAGE);
+
   /**
    * Method to start an image load.  This does not do any security checks.
    * This method will attempt to make aURI immutable; a caller that wants to
    * keep a mutable version around should pass in a clone.
    *
    * @param aURI uri of the image to be loaded
    * @param aLoadingDocument the document we belong to
    * @param aLoadingPrincipal the principal doing the load
    * @param aReferrer the referrer URI
    * @param aObserver the observer for the image load
    * @param aLoadFlags the load flags to use.  See nsIRequest
+   * @param [aContentPolicyType=nsIContentPolicy::TYPE_IMAGE] (Optional)
+   *        The CP content type to use
    * @return the imgIRequest for the image load
    */
   static nsresult LoadImage(nsIURI* aURI,
                             nsIDocument* aLoadingDocument,
                             nsIPrincipal* aLoadingPrincipal,
                             nsIURI* aReferrer,
                             imgINotificationObserver* aObserver,
                             int32_t aLoadFlags,
                             const nsAString& initiatorType,
-                            imgRequestProxy** aRequest);
+                            imgRequestProxy** aRequest,
+                            uint32_t aContentPolicyType = nsIContentPolicy::TYPE_IMAGE);
 
   /**
    * Obtain an image loader that respects the given document/channel's privacy status.
    * Null document/channel arguments return the public image loader.
    */
   static imgLoader* GetImgLoaderForDocument(nsIDocument* aDoc);
   static imgLoader* GetImgLoaderForChannel(nsIChannel* aChannel);
 
--- a/content/base/public/nsIContentPolicy.idl
+++ b/content/base/public/nsIContentPolicy.idl
@@ -19,20 +19,25 @@ typedef unsigned long nsContentPolicyTyp
  * Interface for content policy mechanism.  Implementations of this
  * interface can be used to control loading of various types of out-of-line
  * content, or processing of certain types of in-line content.
  *
  * WARNING: do not block the caller from shouldLoad or shouldProcess (e.g.,
  * by launching a dialog to prompt the user for something).
  */
 
-[scriptable,uuid(8afe3e5c-f916-48fd-8075-9579d3502e1d)]
+[scriptable,uuid(94513f07-d559-480e-8879-6249852365ea)]
 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.
    *
    * 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
@@ -151,16 +156,21 @@ interface nsIContentPolicy : nsISupports
   const nsContentPolicyType TYPE_BEACON = 19;
 
   /**
    * Indicates a load initiated by the fetch() function from the Fetch
    * specification.
    */
   const nsContentPolicyType TYPE_FETCH = 20;
 
+  /**
+   * Indicates a <img srcset> or <picture> request.
+   */
+  const nsContentPolicyType TYPE_IMAGESET = 21;
+
   /* When adding new content types, please update nsContentBlocker,
    * NS_CP_ContentTypeName, nsCSPContext, all nsIContentPolicy
    * implementations, and other things that are not listed here that are
    * related to nsIContentPolicy. */
 
   //////////////////////////////////////////////////////////////////////
 
   /**
--- a/content/base/public/nsIDocument.h
+++ b/content/base/public/nsIDocument.h
@@ -1731,25 +1731,37 @@ public:
 
   /**
    * Return whether the document and all its ancestors are visible in the sense of
    * pageshow / hide.
    */
   bool IsVisibleConsideringAncestors() const;
 
   /**
-   * Return true when this document is active, i.e., the active document
+   * Return true when this document is active, i.e., an active document
    * in a content viewer.  Note that this will return true for bfcached
    * documents, so this does NOT match the "active document" concept in
-   * the WHATWG spec.  That would correspond to GetInnerWindow() &&
-   * GetInnerWindow()->IsCurrentInnerWindow().
+   * the WHATWG spec - see IsCurrentActiveDocument.
    */
   bool IsActive() const { return mDocumentContainer && !mRemovedFromDocShell; }
 
   /**
+   * Return true if this is the current active document for its
+   * docshell. Note that a docshell may have multiple active documents
+   * due to the bfcache -- this should be used when you need to
+   * differentiate the *current* active document from any active
+   * documents.
+   */
+  bool IsCurrentActiveDocument() const
+  {
+    nsPIDOMWindow *inner = GetInnerWindow();
+    return inner && inner->IsCurrentInnerWindow() && inner->GetDoc() == this;
+  }
+
+  /**
    * Register/Unregister the ActivityObserver into mActivityObservers to listen
    * the document's activity changes such as OnPageHide, visibility, activity.
    * The ActivityObserver objects can be nsIObjectLoadingContent or
    * nsIDocumentActivity or HTMLMEdiaElement.
    */
   void RegisterActivityObserver(nsISupports* aSupports);
   bool UnregisterActivityObserver(nsISupports* aSupports);
   // Enumerate all the observers in mActivityObservers by the aEnumerator.
--- a/content/base/public/nsIImageLoadingContent.idl
+++ b/content/base/public/nsIImageLoadingContent.idl
@@ -32,17 +32,17 @@ interface nsIFrame;
  * missed.  We should NOT freeze this interface without considering
  * this issue.  (It could be that the image status on imgIRequest is
  * sufficient, when combined with the imageBlockingStatus information.)
  *
  * Please make sure to update the MozImageLoadingContent WebIDL
  * interface to mirror this interface when changing it.
  */
 
-[scriptable, builtinclass, uuid(256a5283-ebb5-4430-8e15-5ada92156ef7)]
+[scriptable, builtinclass, uuid(ce098f6c-baca-4178-a9aa-266e8bfe509b)]
 interface nsIImageLoadingContent : imgINotificationObserver
 {
   /**
    * Request types.  Image loading content nodes attempt to do atomic
    * image changes when the image url is changed.  This means that
    * when the url changes the new image load will start, but the old
    * image will remain the "current" request until the new image is
    * fully loaded.  At that point, the old "current" request will be
@@ -144,19 +144,20 @@ interface nsIImageLoadingContent : imgIN
    *
    * @throws NS_ERROR_NULL_POINTER if aChannel is null
    */
   nsIStreamListener loadImageWithChannel(in nsIChannel aChannel);
 
   /**
    * forceReload forces reloading of the image pointed to by currentURI
    *
+   * @param aNotify [optional] request should notify, defaults to true
    * @throws NS_ERROR_NOT_AVAILABLE if there is no current URI to reload
    */
-  void forceReload();
+  [optional_argc] void forceReload([optional] in boolean aNotify /* = true */);
 
   /**
    * Enables/disables image state forcing. When |aForce| is PR_TRUE, we force
    * nsImageLoadingContent::ImageState() to return |aState|. Call again with |aForce|
    * as PR_FALSE to revert ImageState() to its original behaviour.
    */
   void forceImageState(in boolean aForce, in unsigned long long aState);
 
--- a/content/base/src/Element.cpp
+++ b/content/base/src/Element.cpp
@@ -952,17 +952,17 @@ Element::CreateShadowRoot(ErrorResult& a
   nodeInfo = mNodeInfo->NodeInfoManager()->GetNodeInfo(
     nsGkAtoms::documentFragmentNodeName, nullptr, kNameSpaceID_None,
     nsIDOMNode::DOCUMENT_FRAGMENT_NODE);
 
   nsRefPtr<nsXBLDocumentInfo> docInfo = new nsXBLDocumentInfo(OwnerDoc());
 
   nsXBLPrototypeBinding* protoBinding = new nsXBLPrototypeBinding();
   aError = protoBinding->Init(NS_LITERAL_CSTRING("shadowroot"),
-                              docInfo, this, true);
+                              docInfo, nullptr, true);
   if (aError.Failed()) {
     delete protoBinding;
     return nullptr;
   }
 
   nsIDocument* doc = GetCrossShadowCurrentDoc();
   nsIContent* destroyedFramesFor = nullptr;
   if (doc) {
--- a/content/base/src/ResponsiveImageSelector.cpp
+++ b/content/base/src/ResponsiveImageSelector.cpp
@@ -17,17 +17,20 @@
 #include "nsRuleData.h"
 
 using namespace mozilla;
 using namespace mozilla::dom;
 
 namespace mozilla {
 namespace dom {
 
-NS_IMPL_ISUPPORTS0(ResponsiveImageSelector);
+NS_IMPL_CYCLE_COLLECTION(ResponsiveImageSelector, mContent)
+
+NS_IMPL_CYCLE_COLLECTION_ROOT_NATIVE(ResponsiveImageSelector, AddRef)
+NS_IMPL_CYCLE_COLLECTION_UNROOT_NATIVE(ResponsiveImageSelector, Release)
 
 static bool
 ParseInteger(const nsAString& aString, int32_t& aInt)
 {
   nsContentUtils::ParseHTMLIntegerResultFlags parseResult;
   aInt = nsContentUtils::ParseHTMLInteger(aString, &parseResult);
   return !(parseResult &
            ( nsContentUtils::eParseHTMLInteger_Error |
@@ -258,16 +261,29 @@ ResponsiveImageSelector::GetSelectedImag
   int bestIndex = GetBestCandidateIndex();
   if (bestIndex < 0) {
     return 1.0;
   }
 
   return mCandidates[bestIndex].Density(this);
 }
 
+bool
+ResponsiveImageSelector::SelectImage(bool aReselect)
+{
+  if (!aReselect && mBestCandidateIndex != -1) {
+    // Already have selection
+    return false;
+  }
+
+  int oldBest = mBestCandidateIndex;
+  mBestCandidateIndex = -1;
+  return GetBestCandidateIndex() != oldBest;
+}
+
 int
 ResponsiveImageSelector::GetBestCandidateIndex()
 {
   if (mBestCandidateIndex != -1) {
     return mBestCandidateIndex;
   }
 
   int numCandidates = mCandidates.Length();
@@ -331,20 +347,16 @@ ResponsiveImageSelector::GetBestCandidat
   mBestCandidateIndex = bestIndex;
   return bestIndex;
 }
 
 bool
 ResponsiveImageSelector::ComputeFinalWidthForCurrentViewport(int32_t *aWidth)
 {
   unsigned int numSizes = mSizeQueries.Length();
-  if (!numSizes) {
-    return false;
-  }
-
   nsIDocument* doc = mContent ? mContent->OwnerDoc() : nullptr;
   nsIPresShell *presShell = doc ? doc->GetShell() : nullptr;
   nsPresContext *pctx = presShell ? presShell->GetPresContext() : nullptr;
 
   if (!pctx) {
     MOZ_ASSERT(false, "Unable to find presContext for this content");
     return false;
   }
--- a/content/base/src/ResponsiveImageSelector.h
+++ b/content/base/src/ResponsiveImageSelector.h
@@ -4,52 +4,76 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #ifndef mozilla_dom_responsiveimageselector_h__
 #define mozilla_dom_responsiveimageselector_h__
 
 #include "nsISupports.h"
 #include "nsIContent.h"
 #include "nsString.h"
+#include "nsCycleCollectionParticipant.h"
 
 class nsMediaQuery;
 class nsCSSValue;
 
 namespace mozilla {
 namespace dom {
 
 class ResponsiveImageCandidate;
 
-class ResponsiveImageSelector : public nsISupports
+class ResponsiveImageSelector
 {
   friend class ResponsiveImageCandidate;
 public:
-  NS_DECL_ISUPPORTS
+  NS_INLINE_DECL_CYCLE_COLLECTING_NATIVE_REFCOUNTING(ResponsiveImageSelector)
+  NS_DECL_CYCLE_COLLECTION_NATIVE_CLASS(ResponsiveImageSelector)
+
   explicit ResponsiveImageSelector(nsIContent* aContent);
 
+  // NOTE ABOUT CURRENT SELECTION
+  //
+  // The best candidate is selected lazily when GetSelectedImage*() is
+  // called, or when SelectImage() is called explicitly. This result
+  // is then cached until either invalidated by further Set*() calls,
+  // or explicitly by replaced by SelectImage(aReselect = true).
+  //
+  // Because the selected image depends on external variants like
+  // viewport size and device pixel ratio, the time at which image
+  // selection occurs can affect the result.
+
+
   // Given a srcset string, parse and replace current candidates (does not
   // replace default source)
   bool SetCandidatesFromSourceSet(const nsAString & aSrcSet);
 
   // Fill the source sizes from a valid sizes descriptor. Returns false if
   // descriptor is invalid.
   bool SetSizesFromDescriptor(const nsAString & aSizesDescriptor);
 
   // Set the default source, treated as the least-precedence 1.0 density source.
   nsresult SetDefaultSource(const nsAString & aSpec);
   void SetDefaultSource(nsIURI *aURL);
 
   uint32_t NumCandidates(bool aIncludeDefault = true);
 
   nsIContent *Content() { return mContent; }
 
-  // Get the URL for the selected best candidate
+  // Get the url and density for the selected best candidate. These
+  // implicitly cause an image to be selected if necessary.
   already_AddRefed<nsIURI> GetSelectedImageURL();
   double GetSelectedImageDensity();
 
+  // Runs image selection now if necessary. If an image has already
+  // been choosen, takes no action unless aReselect is true.
+  //
+  // aReselect - Always re-run selection, replacing the previously
+  //             choosen image.
+  // return - true if the selected image result changed.
+  bool SelectImage(bool aReselect = false);
+
 protected:
   virtual ~ResponsiveImageSelector();
 
 private:
   // Append a candidate unless its selector is duplicated by a higher priority
   // candidate
   void AppendCandidateIfUnique(const ResponsiveImageCandidate &aCandidate);
 
--- a/content/base/src/nsAttrValueOrString.cpp
+++ b/content/base/src/nsAttrValueOrString.cpp
@@ -7,16 +7,21 @@
 
 const nsAString&
 nsAttrValueOrString::String() const
 {
   if (mStringPtr) {
     return *mStringPtr;
   }
 
+  if (!mAttrValue) {
+    mStringPtr = &mCheapString;
+    return *mStringPtr;
+  }
+
   if (mAttrValue->Type() == nsAttrValue::eString) {
     mCheapString = mAttrValue->GetStringValue();
     mStringPtr = &mCheapString;
     return *mStringPtr;
   }
 
   mAttrValue->ToString(mCheapString);
   mStringPtr = &mCheapString;
--- a/content/base/src/nsAttrValueOrString.h
+++ b/content/base/src/nsAttrValueOrString.h
@@ -3,16 +3,19 @@
  * 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/. */
 
 /*
  * A wrapper to contain either an nsAttrValue or an nsAString. This is useful
  * because constructing an nsAttrValue from an nsAString can be expensive when
  * the buffer of the string is not shared.
  *
+ * This treats nsAttrValueOrString(nullptr) as the empty string,
+ * to help with contexts where a null pointer denotes an empty value.
+ *
  * Since a raw pointer to the passed-in string is kept, this class should only
  * be used on the stack.
  */
 
 #ifndef nsAttrValueOrString_h___
 #define nsAttrValueOrString_h___
 
 #include "nsString.h"
@@ -21,22 +24,35 @@
 class MOZ_STACK_CLASS nsAttrValueOrString
 {
 public:
   explicit nsAttrValueOrString(const nsAString& aValue)
     : mAttrValue(nullptr)
     , mStringPtr(&aValue)
     , mCheapString(nullptr)
   { }
+
+  explicit nsAttrValueOrString(const nsAString* aValue)
+    : mAttrValue(nullptr)
+    , mStringPtr(aValue)
+    , mCheapString(nullptr)
+  { }
+
   explicit nsAttrValueOrString(const nsAttrValue& aValue)
     : mAttrValue(&aValue)
     , mStringPtr(nullptr)
     , mCheapString(nullptr)
   { }
 
+  explicit nsAttrValueOrString(const nsAttrValue* aValue)
+    : mAttrValue(aValue)
+    , mStringPtr(nullptr)
+    , mCheapString(nullptr)
+  { }
+
   /**
    * Returns a reference to the string value of the contents of this object.
    *
    * When this object points to a string or an nsAttrValue of string or atom
    * type this should be fairly cheap. Other nsAttrValue types will be
    * serialized the first time this is called and cached from thereon.
    */
   const nsAString& String() const;
--- a/content/base/src/nsCSPUtils.cpp
+++ b/content/base/src/nsCSPUtils.cpp
@@ -676,16 +676,17 @@ nsCSPDirective::toString(nsAString& outS
   }
 }
 
 enum CSPDirective
 CSP_ContentTypeToDirective(nsContentPolicyType aType)
 {
   switch (aType) {
     case nsIContentPolicy::TYPE_IMAGE:
+    case nsIContentPolicy::TYPE_IMAGESET:
       return CSP_IMG_SRC;
 
     case nsIContentPolicy::TYPE_SCRIPT:
       return CSP_SCRIPT_SRC;
 
     case nsIContentPolicy::TYPE_STYLESHEET:
       return CSP_STYLE_SRC;
 
--- a/content/base/src/nsContentUtils.cpp
+++ b/content/base/src/nsContentUtils.cpp
@@ -2856,17 +2856,18 @@ nsContentUtils::GetContextForContent(con
   return nullptr;
 }
 
 // static
 bool
 nsContentUtils::CanLoadImage(nsIURI* aURI, nsISupports* aContext,
                              nsIDocument* aLoadingDocument,
                              nsIPrincipal* aLoadingPrincipal,
-                             int16_t* aImageBlockingStatus)
+                             int16_t* aImageBlockingStatus,
+                             uint32_t aContentType)
 {
   NS_PRECONDITION(aURI, "Must have a URI");
   NS_PRECONDITION(aLoadingDocument, "Must have a document");
   NS_PRECONDITION(aLoadingPrincipal, "Must have a loading principal");
 
   nsresult rv;
 
   uint32_t appType = nsIDocShell::APP_TYPE_UNKNOWN;
@@ -2899,17 +2900,17 @@ nsContentUtils::CanLoadImage(nsIURI* aUR
         *aImageBlockingStatus = nsIContentPolicy::REJECT_REQUEST;
       }
       return false;
     }
   }
 
   int16_t decision = nsIContentPolicy::ACCEPT;
 
-  rv = NS_CheckContentLoadPolicy(nsIContentPolicy::TYPE_IMAGE,
+  rv = NS_CheckContentLoadPolicy(aContentType,
                                  aURI,
                                  aLoadingPrincipal,
                                  aContext,
                                  EmptyCString(), //mime guess
                                  nullptr,         //extra
                                  &decision,
                                  GetContentPolicy(),
                                  sSecurityManager);
@@ -2980,17 +2981,18 @@ nsContentUtils::IsImageInCache(nsIURI* a
 }
 
 // static
 nsresult
 nsContentUtils::LoadImage(nsIURI* aURI, nsIDocument* aLoadingDocument,
                           nsIPrincipal* aLoadingPrincipal, nsIURI* aReferrer,
                           imgINotificationObserver* aObserver, int32_t aLoadFlags,
                           const nsAString& initiatorType,
-                          imgRequestProxy** aRequest)
+                          imgRequestProxy** aRequest,
+                          uint32_t aContentPolicyType)
 {
   NS_PRECONDITION(aURI, "Must have a URI");
   NS_PRECONDITION(aLoadingDocument, "Must have a document");
   NS_PRECONDITION(aLoadingPrincipal, "Must have a principal");
   NS_PRECONDITION(aRequest, "Null out param");
 
   imgLoader* imgLoader = GetImgLoaderForDocument(aLoadingDocument);
   if (!imgLoader) {
@@ -3013,17 +3015,18 @@ nsContentUtils::LoadImage(nsIURI* aURI, 
   return imgLoader->LoadImage(aURI,                 /* uri to load */
                               documentURI,          /* initialDocumentURI */
                               aReferrer,            /* referrer */
                               aLoadingPrincipal,    /* loading principal */
                               loadGroup,            /* loadgroup */
                               aObserver,            /* imgINotificationObserver */
                               aLoadingDocument,     /* uniquification key */
                               aLoadFlags,           /* load flags */
-                              nullptr,               /* cache key */
+                              nullptr,              /* cache key */
+                              aContentPolicyType,   /* content policy type */
                               initiatorType,        /* the load initiator */
                               aRequest);
 }
 
 // static
 already_AddRefed<imgIContainer>
 nsContentUtils::GetImageFromContent(nsIImageLoadingContent* aContent,
                                     imgIRequest **aRequest)
--- a/content/base/src/nsDataDocumentContentPolicy.cpp
+++ b/content/base/src/nsDataDocumentContentPolicy.cpp
@@ -91,17 +91,18 @@ nsDataDocumentContentPolicy::ShouldLoad(
         nsresult rv =
           requestingPrincipal->GetURI(getter_AddRefs(principalURI));
         if (NS_SUCCEEDED(rv) && principalURI) {
           nsScriptSecurityManager::ReportError(
             nullptr, NS_LITERAL_STRING("CheckSameOriginError"), principalURI,
             aContentLocation);
         }
       }
-    } else if (aContentType == nsIContentPolicy::TYPE_IMAGE &&
+    } else if ((aContentType == nsIContentPolicy::TYPE_IMAGE ||
+                aContentType == nsIContentPolicy::TYPE_IMAGESET) &&
                doc->GetDocumentURI()) {
       // Check for (& disallow) recursive image-loads
       bool isRecursiveLoad;
       nsresult rv = aContentLocation->EqualsExceptRef(doc->GetDocumentURI(),
                                                       &isRecursiveLoad);
       if (NS_FAILED(rv) || isRecursiveLoad) {
         NS_WARNING("Refusing to recursively load image");
         *aDecision = nsIContentPolicy::REJECT_TYPE;
--- a/content/base/src/nsGenConImageContent.cpp
+++ b/content/base/src/nsGenConImageContent.cpp
@@ -30,17 +30,17 @@ public:
     // nsImageLoadingContent starts out broken, so we start out
     // suppressed to match it.
     AddStatesSilently(NS_EVENT_STATE_SUPPRESSED);
   }
 
   nsresult Init(imgRequestProxy* aImageRequest)
   {
     // No need to notify, since we have no frame.
-    return UseAsPrimaryRequest(aImageRequest, false);
+    return UseAsPrimaryRequest(aImageRequest, false, eImageLoadType_Normal);
   }
 
   // nsIContent overrides
   virtual nsresult BindToTree(nsIDocument* aDocument, nsIContent* aParent,
                               nsIContent* aBindingParent,
                               bool aCompileEventHandlers);
   virtual void UnbindFromTree(bool aDeep, bool aNullParent);
   virtual EventStates IntrinsicState() const;
--- a/content/base/src/nsImageLoadingContent.cpp
+++ b/content/base/src/nsImageLoadingContent.cpp
@@ -517,16 +517,29 @@ nsImageLoadingContent::FrameDestroyed(ns
 
   if (aFrame->HasAnyStateBits(NS_FRAME_IN_POPUP)) {
     // We assume all images in popups are visible, so this decrement balances
     // out the increment in FrameCreated above.
     DecrementVisibleCount();
   }
 }
 
+/* static */
+nsContentPolicyType
+nsImageLoadingContent::PolicyTypeForLoad(ImageLoadType aImageLoadType)
+{
+  if (aImageLoadType == eImageLoadType_Imageset) {
+    return nsIContentPolicy::TYPE_IMAGESET;
+  }
+
+  MOZ_ASSERT(aImageLoadType == eImageLoadType_Normal,
+             "Unknown ImageLoadType type in PolicyTypeForLoad");
+  return nsIContentPolicy::TYPE_IMAGE;
+}
+
 int32_t
 nsImageLoadingContent::GetRequestType(imgIRequest* aRequest,
                                       ErrorResult& aError)
 {
   if (aRequest == mCurrentRequest) {
     return CURRENT_REQUEST;
   }
 
@@ -595,17 +608,17 @@ nsImageLoadingContent::LoadImageWithChan
   // Shouldn't that be done before the start of the load?
   // XXX what about shouldProcess?
 
   // Our state might change. Watch it.
   AutoStateChanger changer(this, true);
 
   // Do the load.
   nsCOMPtr<nsIStreamListener> listener;
-  nsRefPtr<imgRequestProxy>& req = PrepareNextRequest();
+  nsRefPtr<imgRequestProxy>& req = PrepareNextRequest(eImageLoadType_Normal);
   nsresult rv = nsContentUtils::GetImgLoaderForChannel(aChannel)->
     LoadImageWithChannel(aChannel, this, doc,
                          getter_AddRefs(listener),
                          getter_AddRefs(req));
   if (NS_SUCCEEDED(rv)) {
     TrackImage(req);
     ResetAnimationIfNeeded();
   } else {
@@ -627,35 +640,52 @@ nsImageLoadingContent::LoadImageWithChan
   NS_ENSURE_ARG_POINTER(aListener);
 
   ErrorResult result;
   *aListener = LoadImageWithChannel(aChannel, result).take();
   return result.ErrorCode();
 }
 
 void
-nsImageLoadingContent::ForceReload(ErrorResult& aError)
+nsImageLoadingContent::ForceReload(const mozilla::dom::Optional<bool>& aNotify,
+                                   mozilla::ErrorResult& aError)
 {
   nsCOMPtr<nsIURI> currentURI;
   GetCurrentURI(getter_AddRefs(currentURI));
   if (!currentURI) {
     aError.Throw(NS_ERROR_NOT_AVAILABLE);
     return;
   }
 
-  nsresult rv = LoadImage(currentURI, true, true, nullptr, nsIRequest::VALIDATE_ALWAYS);
+  // defaults to true
+  bool notify = !aNotify.WasPassed() || aNotify.Value();
+
+  // We keep this flag around along with the old URI even for failed requests
+  // without a live request object
+  ImageLoadType loadType = \
+    (mCurrentRequestFlags & REQUEST_IS_IMAGESET) ? eImageLoadType_Imageset
+                                                 : eImageLoadType_Normal;
+  nsresult rv = LoadImage(currentURI, true, notify, loadType, nullptr,
+                          nsIRequest::VALIDATE_ALWAYS);
   if (NS_FAILED(rv)) {
     aError.Throw(rv);
   }
 }
 
-NS_IMETHODIMP nsImageLoadingContent::ForceReload()
+NS_IMETHODIMP
+nsImageLoadingContent::ForceReload(bool aNotify /* = true */,
+                                   uint8_t aArgc)
 {
+  mozilla::dom::Optional<bool> notify;
+  if (aArgc >= 1) {
+    notify.Construct() = aNotify;
+  }
+
   ErrorResult result;
-  ForceReload(result);
+  ForceReload(notify, result);
   return result.ErrorCode();
 }
 
 NS_IMETHODIMP
 nsImageLoadingContent::BlockOnload(imgIRequest* aRequest)
 {
   if (aRequest == mCurrentRequest) {
     NS_ASSERTION(!(mCurrentRequestFlags & REQUEST_BLOCKS_ONLOAD),
@@ -730,17 +760,18 @@ nsImageLoadingContent::GetVisibleCount()
 
 /*
  * Non-interface methods
  */
 
 nsresult
 nsImageLoadingContent::LoadImage(const nsAString& aNewURI,
                                  bool aForce,
-                                 bool aNotify)
+                                 bool aNotify,
+                                 ImageLoadType aImageLoadType)
 {
   // First, get a document (needed for security checks and the like)
   nsIDocument* doc = GetOurOwnerDoc();
   if (!doc) {
     // No reason to bother, I think...
     return NS_OK;
   }
 
@@ -753,34 +784,35 @@ nsImageLoadingContent::LoadImage(const n
 
   if (aNewURI.IsEmpty() &&
       doc->GetDocumentURI() &&
       NS_SUCCEEDED(doc->GetDocumentURI()->EqualsExceptRef(imageURI, &equal)) &&
       equal)  {
 
     // Loading an embedded img from the same URI as the document URI will not work
     // as a resource cannot recursively embed itself. Attempting to do so generally
-    // results in having to pre-emptively close down an in-flight HTTP transaction 
+    // results in having to pre-emptively close down an in-flight HTTP transaction
     // and then incurring the significant cost of establishing a new TCP channel.
-    // This is generally triggered from <img src=""> 
+    // This is generally triggered from <img src="">
     // In light of that, just skip loading it..
     // Do make sure to drop our existing image, if any
     CancelImageRequests(aNotify);
     return NS_OK;
   }
 
   NS_TryToSetImmutable(imageURI);
 
-  return LoadImage(imageURI, aForce, aNotify, doc);
+  return LoadImage(imageURI, aForce, aNotify, aImageLoadType, doc);
 }
 
 nsresult
 nsImageLoadingContent::LoadImage(nsIURI* aNewURI,
                                  bool aForce,
                                  bool aNotify,
+                                 ImageLoadType aImageLoadType,
                                  nsIDocument* aDocument,
                                  nsLoadFlags aLoadFlags)
 {
   if (!mLoadingEnabled) {
     // XXX Why fire an error here? seems like the callers to SetLoadingEnabled
     // don't want/need it.
     FireEvent(NS_LITERAL_STRING("error"));
     return NS_OK;
@@ -824,46 +856,50 @@ nsImageLoadingContent::LoadImage(nsIURI*
   nsCOMPtr<nsIContent> thisContent = do_QueryInterface(static_cast<nsIImageLoadingContent*>(this));
   NS_ABORT_IF_FALSE(thisContent &&
                     thisContent->NodePrincipal() == aDocument->NodePrincipal(),
                     "Principal mismatch?");
 #endif
 
   // Are we blocked?
   int16_t cpDecision = nsIContentPolicy::REJECT_REQUEST;
+  nsContentPolicyType policyType = PolicyTypeForLoad(aImageLoadType);
+
   nsContentUtils::CanLoadImage(aNewURI,
                                static_cast<nsIImageLoadingContent*>(this),
                                aDocument,
                                aDocument->NodePrincipal(),
-                               &cpDecision);
+                               &cpDecision,
+                               policyType);
   if (!NS_CP_ACCEPTED(cpDecision)) {
     FireEvent(NS_LITERAL_STRING("error"));
     SetBlockedRequest(aNewURI, cpDecision);
     return NS_OK;
   }
 
   nsLoadFlags loadFlags = aLoadFlags;
   int32_t corsmode = GetCORSMode();
   if (corsmode == CORS_ANONYMOUS) {
     loadFlags |= imgILoader::LOAD_CORS_ANONYMOUS;
   } else if (corsmode == CORS_USE_CREDENTIALS) {
     loadFlags |= imgILoader::LOAD_CORS_USE_CREDENTIALS;
   }
 
   // Not blocked. Do the load.
-  nsRefPtr<imgRequestProxy>& req = PrepareNextRequest();
+  nsRefPtr<imgRequestProxy>& req = PrepareNextRequest(aImageLoadType);
   nsCOMPtr<nsIContent> content =
       do_QueryInterface(static_cast<nsIImageLoadingContent*>(this));
   nsresult rv;
   rv = nsContentUtils::LoadImage(aNewURI, aDocument,
                                  aDocument->NodePrincipal(),
                                  aDocument->GetDocumentURI(),
                                  this, loadFlags,
                                  content->LocalName(),
-                                 getter_AddRefs(req));
+                                 getter_AddRefs(req),
+                                 policyType);
 
   if (NS_SUCCEEDED(rv)) {
     TrackImage(req);
     ResetAnimationIfNeeded();
 
     // Handle cases when we just ended up with a pending request but it's
     // already done.  In that situation we have to synchronously switch that
     // request to being the current request, because websites depend on that
@@ -977,24 +1013,24 @@ nsImageLoadingContent::UpdateImageState(
     // Ignore this call; we'll update our state when the outermost state
     // changer is destroyed. Need this to work around the fact that some libpr0n
     // stuff is actually sync and hence we can get OnStopDecode called while
     // we're still under LoadImage, and OnStopDecode doesn't know anything about
     // aNotify.
     // XXX - This machinery should be removed after bug 521604.
     return;
   }
-  
+
   nsCOMPtr<nsIContent> thisContent = do_QueryInterface(static_cast<nsIImageLoadingContent*>(this));
   if (!thisContent) {
     return;
   }
 
   mLoading = mBroken = mUserDisabled = mSuppressed = false;
-  
+
   // If we were blocked by server-based content policy, we claim to be
   // suppressed.  If we were blocked by type-based content policy, we claim to
   // be user-disabled.  Otherwise, claim to be broken.
   if (mImageBlockingStatus == nsIContentPolicy::REJECT_SERVER) {
     mSuppressed = true;
   } else if (mImageBlockingStatus == nsIContentPolicy::REJECT_TYPE) {
     mUserDisabled = true;
   } else if (!mCurrentRequest) {
@@ -1019,27 +1055,28 @@ nsImageLoadingContent::CancelImageReques
 {
   AutoStateChanger changer(this, aNotify);
   ClearPendingRequest(NS_BINDING_ABORTED, REQUEST_DISCARD);
   ClearCurrentRequest(NS_BINDING_ABORTED, REQUEST_DISCARD);
 }
 
 nsresult
 nsImageLoadingContent::UseAsPrimaryRequest(imgRequestProxy* aRequest,
-                                           bool aNotify)
+                                           bool aNotify,
+                                           ImageLoadType aImageLoadType)
 {
   // Our state will change. Watch it.
   AutoStateChanger changer(this, aNotify);
 
   // Get rid if our existing images
   ClearPendingRequest(NS_BINDING_ABORTED, REQUEST_DISCARD);
   ClearCurrentRequest(NS_BINDING_ABORTED, REQUEST_DISCARD);
 
   // Clone the request we were given.
-  nsRefPtr<imgRequestProxy>& req = PrepareNextRequest();
+  nsRefPtr<imgRequestProxy>& req = PrepareNextRequest(aImageLoadType);
   nsresult rv = aRequest->Clone(this, getter_AddRefs(req));
   if (NS_SUCCEEDED(rv)) {
     TrackImage(req);
   } else {
     MOZ_ASSERT(!req, "Shouldn't have non-null request here");
     return rv;
   }
 
@@ -1120,25 +1157,25 @@ nsImageLoadingContent::FireEvent(const n
   nsRefPtr<AsyncEventDispatcher> loadBlockingAsyncDispatcher =
     new LoadBlockingAsyncEventDispatcher(thisNode, aEventType, false, false);
   loadBlockingAsyncDispatcher->PostDOMEvent();
 
   return NS_OK;
 }
 
 nsRefPtr<imgRequestProxy>&
-nsImageLoadingContent::PrepareNextRequest()
+nsImageLoadingContent::PrepareNextRequest(ImageLoadType aImageLoadType)
 {
   // If we don't have a usable current request, get rid of any half-baked
   // request that might be sitting there and make this one current.
   if (!HaveSize(mCurrentRequest))
-    return PrepareCurrentRequest();
+    return PrepareCurrentRequest(aImageLoadType);
 
   // Otherwise, make it pending.
-  return PreparePendingRequest();
+  return PreparePendingRequest(aImageLoadType);
 }
 
 void
 nsImageLoadingContent::SetBlockedRequest(nsIURI* aURI, int16_t aContentDecision)
 {
   // Sanity
   NS_ABORT_IF_FALSE(!NS_CP_ACCEPTED(aContentDecision), "Blocked but not?");
 
@@ -1150,52 +1187,63 @@ nsImageLoadingContent::SetBlockedRequest
   // next image depends on the cancel reason of the previous image. ugh.
   ClearPendingRequest(NS_ERROR_IMAGE_BLOCKED, REQUEST_DISCARD);
 
   // For the blocked case, we only want to cancel the existing current request
   // if size is not available. bz says the web depends on this behavior.
   if (!HaveSize(mCurrentRequest)) {
 
     mImageBlockingStatus = aContentDecision;
+    uint32_t keepFlags = mCurrentRequestFlags & REQUEST_IS_IMAGESET;
     ClearCurrentRequest(NS_ERROR_IMAGE_BLOCKED, REQUEST_DISCARD);
 
-    // We still want to remember what URI we were despite not having an actual
-    // request.
+    // We still want to remember what URI we were and if it was an imageset,
+    // despite not having an actual request. These are both cleared as part of
+    // ClearCurrentRequest() before a new request is started.
     mCurrentURI = aURI;
+    mCurrentRequestFlags = keepFlags;
   }
 }
 
 nsRefPtr<imgRequestProxy>&
-nsImageLoadingContent::PrepareCurrentRequest()
+nsImageLoadingContent::PrepareCurrentRequest(ImageLoadType aImageLoadType)
 {
   // Blocked images go through SetBlockedRequest, which is a separate path. For
   // everything else, we're unblocked.
   mImageBlockingStatus = nsIContentPolicy::ACCEPT;
 
   // Get rid of anything that was there previously.
   ClearCurrentRequest(NS_ERROR_IMAGE_SRC_CHANGED, REQUEST_DISCARD);
 
   if (mNewRequestsWillNeedAnimationReset) {
     mCurrentRequestFlags |= REQUEST_NEEDS_ANIMATION_RESET;
   }
 
+  if (aImageLoadType == eImageLoadType_Imageset) {
+    mCurrentRequestFlags |= REQUEST_IS_IMAGESET;
+  }
+
   // Return a reference.
   return mCurrentRequest;
 }
 
 nsRefPtr<imgRequestProxy>&
-nsImageLoadingContent::PreparePendingRequest()
+nsImageLoadingContent::PreparePendingRequest(ImageLoadType aImageLoadType)
 {
   // Get rid of anything that was there previously.
   ClearPendingRequest(NS_ERROR_IMAGE_SRC_CHANGED, REQUEST_DISCARD);
 
   if (mNewRequestsWillNeedAnimationReset) {
     mPendingRequestFlags |= REQUEST_NEEDS_ANIMATION_RESET;
   }
 
+  if (aImageLoadType == eImageLoadType_Imageset) {
+    mPendingRequestFlags |= REQUEST_IS_IMAGESET;
+  }
+
   // Return a reference.
   return mPendingRequest;
 }
 
 namespace {
 
 class ImageRequestAutoLock
 {
@@ -1228,31 +1276,36 @@ nsImageLoadingContent::MakePendingReques
 
   // Lock mCurrentRequest for the duration of this method.  We do this because
   // PrepareCurrentRequest() might unlock mCurrentRequest.  If mCurrentRequest
   // and mPendingRequest are both requests for the same image, unlocking
   // mCurrentRequest before we lock mPendingRequest can cause the lock count
   // to go to 0 and the image to be discarded!
   ImageRequestAutoLock autoLock(mCurrentRequest);
 
-  PrepareCurrentRequest() = mPendingRequest;
+  ImageLoadType loadType = \
+    (mPendingRequestFlags & REQUEST_IS_IMAGESET) ? eImageLoadType_Imageset
+                                                 : eImageLoadType_Normal;
+
+  PrepareCurrentRequest(loadType) = mPendingRequest;
   mPendingRequest = nullptr;
   mCurrentRequestFlags = mPendingRequestFlags;
   mPendingRequestFlags = 0;
   ResetAnimationIfNeeded();
 }
 
 void
 nsImageLoadingContent::ClearCurrentRequest(nsresult aReason,
                                            uint32_t aFlags)
 {
   if (!mCurrentRequest) {
     // Even if we didn't have a current request, we might have been keeping
-    // a URI as a placeholder for a failed load. Clear that now.
+    // a URI and flags as a placeholder for a failed load. Clear that now.
     mCurrentURI = nullptr;
+    mCurrentRequestFlags = 0;
     return;
   }
   NS_ABORT_IF_FALSE(!mCurrentURI,
                     "Shouldn't have both mCurrentRequest and mCurrentURI!");
 
   // Deregister this image from the refresh driver so it no longer receives
   // notifications.
   nsLayoutUtils::DeregisterImageRequest(GetFramePresContext(), mCurrentRequest,
--- a/content/base/src/nsImageLoadingContent.h
+++ b/content/base/src/nsImageLoadingContent.h
@@ -17,16 +17,18 @@
 #include "imgIOnloadBlocker.h"
 #include "mozilla/CORSMode.h"
 #include "mozilla/EventStates.h"
 #include "nsCOMPtr.h"
 #include "nsIImageLoadingContent.h"
 #include "nsIRequest.h"
 #include "mozilla/ErrorResult.h"
 #include "nsAutoPtr.h"
+#include "nsIContentPolicy.h"
+#include "mozilla/dom/BindingDeclarations.h"
 
 class nsIURI;
 class nsIDocument;
 class imgILoader;
 class nsIIOService;
 class nsPresContext;
 class nsIContent;
 class imgRequestProxy;
@@ -58,38 +60,56 @@ public:
   {
     return mImageBlockingStatus;
   }
   already_AddRefed<imgIRequest>
     GetRequest(int32_t aRequestType, mozilla::ErrorResult& aError);
   int32_t
     GetRequestType(imgIRequest* aRequest, mozilla::ErrorResult& aError);
   already_AddRefed<nsIURI> GetCurrentURI(mozilla::ErrorResult& aError);
+  void ForceReload(const mozilla::dom::Optional<bool>& aNotify,
+                   mozilla::ErrorResult& aError);
+
+  // XPCOM [optional] syntax helper
+  nsresult ForceReload(bool aNotify = true) {
+    return ForceReload(aNotify, 1);
+  }
+
+  /**
+   * Used to initialize content with a previously opened channel. Assumes
+   * eImageLoadType_Normal
+   */
   already_AddRefed<nsIStreamListener>
     LoadImageWithChannel(nsIChannel* aChannel, mozilla::ErrorResult& aError);
-  void ForceReload(mozilla::ErrorResult& aError);
-
-
 
 protected:
+  enum ImageLoadType {
+    // Most normal image loads
+    eImageLoadType_Normal,
+    // From a <img srcset> or <picture> context. Affects type given to content
+    // policy.
+    eImageLoadType_Imageset
+  };
+
   /**
    * LoadImage is called by subclasses when the appropriate
    * attributes (eg 'src' for <img> tags) change.  The string passed
    * in is the new uri string; this consolidates the code for getting
    * the charset, constructing URI objects, and any other incidentals
-   * into this superclass.   
+   * into this superclass.
    *
    * @param aNewURI the URI spec to be loaded (may be a relative URI)
    * @param aForce If true, make sure to load the URI.  If false, only
    *        load if the URI is different from the currently loaded URI.
    * @param aNotify If true, nsIDocumentObserver state change notifications
    *                will be sent as needed.
+   * @param aImageLoadType The ImageLoadType for this request
    */
   nsresult LoadImage(const nsAString& aNewURI, bool aForce,
-                     bool aNotify);
+                     bool aNotify, ImageLoadType aImageLoadType);
 
   /**
    * ImageState is called by subclasses that are computing their content state.
    * The return value will have the NS_EVENT_STATE_BROKEN,
    * NS_EVENT_STATE_USERDISABLED, and NS_EVENT_STATE_SUPPRESSED bits set as
    * needed.  Note that this state assumes that this node is "trying" to be an
    * image (so for example complete lack of attempt to load an image will lead
    * to NS_EVENT_STATE_BROKEN being set).  Subclasses that are not "trying" to
@@ -103,23 +123,24 @@ protected:
    * attributes (eg 'src' for <img> tags) change. If callers have an
    * URI object already available, they should use this method.
    *
    * @param aNewURI the URI to be loaded
    * @param aForce If true, make sure to load the URI.  If false, only
    *        load if the URI is different from the currently loaded URI.
    * @param aNotify If true, nsIDocumentObserver state change notifications
    *                will be sent as needed.
+   * @param aImageLoadType The ImageLoadType for this request
    * @param aDocument Optional parameter giving the document this node is in.
    *        This is purely a performance optimization.
    * @param aLoadFlags Optional parameter specifying load flags to use for
    *        the image load
    */
   nsresult LoadImage(nsIURI* aNewURI, bool aForce, bool aNotify,
-                     nsIDocument* aDocument = nullptr,
+                     ImageLoadType aImageLoadType, nsIDocument* aDocument = nullptr,
                      nsLoadFlags aLoadFlags = nsIRequest::LOAD_NORMAL);
 
   /**
    * helpers to get the document for this content (from the nodeinfo
    * and such).  Not named GetOwnerDoc/GetCurrentDoc to prevent ambiguous
    * method names in subclasses
    *
    * @return the document we belong to
@@ -153,17 +174,18 @@ protected:
   void CancelImageRequests(bool aNotify);
 
   /**
    * UseAsPrimaryRequest is called by subclasses when they have an existing
    * imgRequestProxy that they want this nsImageLoadingContent to use.  This may
    * effectively be called instead of LoadImage or LoadImageWithChannel.
    * If aNotify is true, this method will notify on state changes.
    */
-  nsresult UseAsPrimaryRequest(imgRequestProxy* aRequest, bool aNotify);
+  nsresult UseAsPrimaryRequest(imgRequestProxy* aRequest, bool aNotify,
+                               ImageLoadType aImageLoadType);
 
   /**
    * Derived classes of nsImageLoadingContent MUST call
    * DestroyImageLoadingContent from their destructor, or earlier.  It
    * does things that cannot be done in ~nsImageLoadingContent because
    * they rely on being able to QueryInterface to other derived classes,
    * which cannot happen once the derived class destructor has started
    * calling the base class destructors.
@@ -186,16 +208,19 @@ protected:
   void BindToTree(nsIDocument* aDocument, nsIContent* aParent,
                   nsIContent* aBindingParent, bool aCompileEventHandlers);
   void UnbindFromTree(bool aDeep, bool aNullParent);
 
   nsresult OnStopRequest(imgIRequest* aRequest, nsresult aStatus);
   void OnUnlockedDraw();
   nsresult OnImageIsAnimated(imgIRequest *aRequest);
 
+  // The nsContentPolicyType we would use for this ImageLoadType
+  static nsContentPolicyType PolicyTypeForLoad(ImageLoadType aImageLoadType);
+
 private:
   /**
    * Struct used to manage the image observers.
    */
   struct ImageObserver {
     explicit ImageObserver(imgINotificationObserver* aObserver);
     ~ImageObserver();
 
@@ -255,34 +280,38 @@ protected:
 
   void CreateStaticImageClone(nsImageLoadingContent* aDest) const;
 
   /**
    * Prepare and returns a reference to the "next request". If there's already
    * a _usable_ current request (one with SIZE_AVAILABLE), this request is
    * "pending" until it becomes usable. Otherwise, this becomes the current
    * request.
+   *
+   * @param aImageLoadType The ImageLoadType for this request
    */
-   nsRefPtr<imgRequestProxy>& PrepareNextRequest();
+   nsRefPtr<imgRequestProxy>& PrepareNextRequest(ImageLoadType aImageLoadType);
 
   /**
    * Called when we would normally call PrepareNextRequest(), but the request was
    * blocked.
    */
   void SetBlockedRequest(nsIURI* aURI, int16_t aContentDecision);
 
   /**
    * Returns a COMPtr reference to the current/pending image requests, cleaning
    * up and canceling anything that was there before. Note that if you just want
    * to get rid of one of the requests, you should call
    * Clear*Request(NS_BINDING_ABORTED) instead, since it passes a more appropriate
    * aReason than Prepare*Request() does (NS_ERROR_IMAGE_SRC_CHANGED).
+   *
+   * @param aImageLoadType The ImageLoadType for this request
    */
-  nsRefPtr<imgRequestProxy>& PrepareCurrentRequest();
-  nsRefPtr<imgRequestProxy>& PreparePendingRequest();
+  nsRefPtr<imgRequestProxy>& PrepareCurrentRequest(ImageLoadType aImageLoadType);
+  nsRefPtr<imgRequestProxy>& PreparePendingRequest(ImageLoadType aImageLoadType);
 
   /**
    * Switch our pending request to be our current request.
    * mPendingRequest must be non-null!
    */
   void MakePendingRequestCurrent();
 
   /**
@@ -334,17 +363,20 @@ protected:
   uint32_t mPendingRequestFlags;
 
   enum {
     // Set if the request needs ResetAnimation called on it.
     REQUEST_NEEDS_ANIMATION_RESET = 0x00000001U,
     // Set if the request is blocking onload.
     REQUEST_BLOCKS_ONLOAD = 0x00000002U,
     // Set if the request is currently tracked with the document.
-    REQUEST_IS_TRACKED = 0x00000004U
+    REQUEST_IS_TRACKED = 0x00000004U,
+    // Set if this is an imageset request, such as from <img srcset> or
+    // <picture>
+    REQUEST_IS_IMAGESET = 0x00000008U
   };
 
   // If the image was blocked or if there was an error loading, it's nice to
   // still keep track of what the URI was despite not having an imgIRequest.
   // We only maintain this in those situations (in the common case, this is
   // always null).
   nsCOMPtr<nsIURI>      mCurrentURI;
 
--- a/content/base/src/nsMixedContentBlocker.cpp
+++ b/content/base/src/nsMixedContentBlocker.cpp
@@ -376,16 +376,17 @@ nsMixedContentBlocker::ShouldLoad(uint32
     case TYPE_PING:
     case TYPE_BEACON:
       classification = eMixedDisplay;
       break;
 
     // Active content (or content with a low value/risk-of-blocking ratio)
     // that has been explicitly evaluated; listed here for documentation
     // purposes and to avoid the assertion and warning for the default case.
+    case TYPE_IMAGESET:
     case TYPE_CSP_REPORT:
     case TYPE_DTD:
     case TYPE_FETCH:
     case TYPE_FONT:
     case TYPE_OBJECT:
     case TYPE_SCRIPT:
     case TYPE_STYLESHEET:
     case TYPE_SUBDOCUMENT:
--- a/content/base/test/file_mixed_content_main.html
+++ b/content/base/test/file_mixed_content_main.html
@@ -22,16 +22,17 @@ https://bugzilla.mozilla.org/show_bug.cg
   case nsIContentPolicy::TYPE_XMLHTTPREQUEST:
 
   case nsIContentPolicy::TYPE_FONT: - NO TEST:
     Load events for external fonts are not detectable by javascript.
   case nsIContentPolicy::TYPE_WEBSOCKET: - NO TEST:
     websocket connections over https require an encrypted websocket protocol (wss:)
 
   case nsIContentPolicy::TYPE_IMAGE:
+  case nsIContentPolicy::TYPE_IMAGESET:
   case nsIContentPolicy::TYPE_MEDIA:
   case nsIContentPolicy::TYPE_PING:
     our ping implementation is off by default and does not comply with the current spec (bug 786347)
   case nsIContentPolicy::TYPE_BEACON:
 
   }
      */
 -->
@@ -176,37 +177,94 @@ https://bugzilla.mozilla.org/show_bug.cg
       }
     }
 
     xhr.send(null);
   }
 
   /* Part 2: Mixed Display tests */
 
+  // Shorthand for all image test variants
+  function imgHandlers(img, test) {
+    img.onload = function () {
+      parent.postMessage({"test": test, "msg": "insecure image loaded"}, "http://mochi.test:8888");
+    }
+    img.onerror = function() {
+      parent.postMessage({"test": test, "msg": "insecure image blocked"}, "http://mochi.test:8888");
+    }
+  }
+
   // Test 2a: insecure image
   var img = document.createElement("img");
   img.src = "http://mochi.test:8888/tests/image/test/mochitest/blue.png";
-  img.onload = function() {
-    parent.postMessage({"test": "image", "msg": "insecure image loaded"}, "http://mochi.test:8888");
-  }
-  img.onerror = function() {
-    parent.postMessage({"test": "image", "msg": "insecure image blocked"}, "http://mochi.test:8888");
-  }
+  imgHandlers(img, "image");
   // We don't need to append the image to the document. Doing so causes the image test to run twice.
 
-
   // Test 2b: insecure media
   var media = document.createElement("video");
   media.src = "http://mochi.test:8888/tests/content/media/test/320x240.ogv?" + Math.floor((Math.random()*1000)+1);
   media.width = "320";
   media.height = "200";
   media.type = "video/ogg";
   media.onloadeddata = function() {
     parent.postMessage({"test": "media", "msg": "insecure media loaded"}, "http://mochi.test:8888");
   }
   media.onerror = function() {
     parent.postMessage({"test": "media", "msg": "insecure media blocked"}, "http://mochi.test:8888");
   }
   // We don't need to append the video to the document. Doing so causes the image test to run twice.
 
+  /* Part 3: Mixed Active Tests for Image srcset */
+
+  // Test 3a: image with srcset
+  var imgA = document.createElement("img");
+  imgA.srcset = "http://mochi.test:8888/tests/image/test/mochitest/blue.png";
+  imgHandlers(imgA, "imageSrcset");
+
+  // Test 3b: image with srcset, using fallback from src, should still use imageset policy
+  var imgB = document.createElement("img");
+  imgB.srcset = " ";
+  imgB.src = "http://mochi.test:8888/tests/image/test/mochitest/blue.png";
+  imgHandlers(imgB, "imageSrcsetFallback");
+
+  // Test 3c: image in <picture>
+  var imgC = document.createElement("img");
+  var pictureC = document.createElement("picture");
+  var sourceC = document.createElement("source");
+  sourceC.srcset = "http://mochi.test:8888/tests/image/test/mochitest/blue.png";
+  pictureC.appendChild(sourceC);
+  pictureC.appendChild(imgC);
+  imgHandlers(imgC, "imagePicture");
+
+  // Test 3d: Loaded basic image switching to a <picture>, loading
+  //          same source, should still redo the request with new
+  //          policy.
+  var imgD = document.createElement("img");
+  imgD.src = "http://mochi.test:8888/tests/image/test/mochitest/blue.png";
+  imgD.onload = imgD.onerror = function() {
+    // Whether or not it loads, we want to now append it to a picture and observe
+    var pictureD = document.createElement("picture");
+    var sourceD = document.createElement("source");
+    sourceD.srcset = "http://mochi.test:8888/tests/image/test/mochitest/blue.png";
+    pictureD.appendChild(sourceD);
+    pictureD.appendChild(imgD);
+    imgHandlers(imgD, "imageJoinPicture");
+  }
+
+  // Test 3e: img load from <picture> source reverts to img.src as it
+  //          is removed -- the new request should revert to mixed
+  //          display policy
+  var imgE = document.createElement("img");
+  var pictureE = document.createElement("picture");
+  var sourceE = document.createElement("source");
+  sourceE.srcset = "http://mochi.test:8888/tests/image/test/mochitest/blue.png";
+  pictureE.appendChild(sourceE);
+  pictureE.appendChild(imgE);
+  imgE.src = "http://mochi.test:8888/tests/image/test/mochitest/blue.png";
+  imgE.onload = imgE.onerror = function() {
+    // Whether or not it loads, remove it from the picture and observe
+    pictureE.removeChild(imgE)
+    imgHandlers(imgE, "imageLeavePicture");
+  }
+
 </script>
 </body>
 </html>
--- a/content/base/test/test_mixed_content_blocker.html
+++ b/content/base/test/test_mixed_content_blocker.html
@@ -40,43 +40,52 @@ https://bugzilla.mozilla.org/show_bug.cg
 
   //Set the first set of settings (true, true) and increment the counter.
   changePrefs(counter);
   counter++;
 
   var testsToRun = {
     iframe: false,
     image: false,
+    imageSrcset: false,
+    imageSrcsetFallback: false,
+    imagePicture: false,
+    imageJoinPicture: false,
+    imageLeavePicture: false,
     script: false,
     stylesheet: false,
     object: false,
     media: false,
     xhr: false,
   };
 
   function log(msg) {
     document.getElementById("log").textContent += "\n" + msg;
   }
 
+  function reloadFrame() {
+    document.getElementById('framediv').innerHTML = '<iframe id="testHarness" src="https://example.com/tests/content/base/test/file_mixed_content_main.html"></iframe>';
+  }
+
   function checkTestsCompleted() {
     for (var prop in testsToRun) {
       // some test hasn't run yet so we're not done
       if (!testsToRun[prop])
         return;
     }
     //if the testsToRun are all completed, chnage the pref and run the tests again until we have cycled through all the prefs.
     if(counter < 4) {
        for (var prop in testsToRun) {
          testsToRun[prop] = false;
        }
       //call to change the preferences
       changePrefs(counter);
       counter++;
       log("\nblockDisplay set to "+blockDisplay+", blockActive set to "+blockActive+".");
-      document.getElementById('framediv').innerHTML = '<iframe id="testHarness" src="https://example.com/tests/content/base/test/file_mixed_content_main.html"></iframe>';
+      reloadFrame();
     }
     else {
       //set the prefs back to what they were set to originally
       SpecialPowers.setBoolPref("security.mixed_content.block_display_content", origBlockDisplay);
       SpecialPowers.setBoolPref("security.mixed_content.block_active_content", origBlockActive);
       SimpleTest.finish();
     }
   }
@@ -120,32 +129,67 @@ https://bugzilla.mozilla.org/show_bug.cg
 
       case "xhr":
         ok(blockActive == (event.data.msg == "insecure xhr blocked"), "xhr did not follow block_active_content pref");
         testsToRun["xhr"] = true;
         break;
 
       /* Mixed Display tests */
       case "image":
-        //test that the image load matches the pref for dipslay content
+        //test that the image load matches the pref for display content
         ok(blockDisplay == (event.data.msg == "insecure image blocked"), "image did not follow block_display_content pref");
         testsToRun["image"] = true;
         break;
 
       case "media":
         ok(blockDisplay == (event.data.msg == "insecure media blocked"), "media did not follow block_display_content pref");
         testsToRun["media"] = true;
         break;
+
+      /* Images using the "imageset" policy, from <img srcset> and <picture>, do not get the mixed display exception */
+      case "imageSrcset":
+        ok(blockActive == (event.data.msg == "insecure image blocked"), "imageSrcset did not follow block_active_content pref");
+        testsToRun["imageSrcset"] = true;
+        break;
+
+      case "imageSrcsetFallback":
+        ok(blockActive == (event.data.msg == "insecure image blocked"), "imageSrcsetFallback did not follow block_active_content pref");
+        testsToRun["imageSrcsetFallback"] = true;
+        break;
+
+      case "imagePicture":
+        ok(blockActive == (event.data.msg == "insecure image blocked"), "imagePicture did not follow block_active_content pref");
+        testsToRun["imagePicture"] = true;
+        break;
+
+      case "imageJoinPicture":
+        ok(blockActive == (event.data.msg == "insecure image blocked"), "imageJoinPicture did not follow block_active_content pref");
+        testsToRun["imageJoinPicture"] = true;
+        break;
+
+      // Should return to mixed display mode
+      case "imageLeavePicture":
+        ok(blockDisplay == (event.data.msg == "insecure image blocked"), "imageLeavePicture did not follow block_display_content pref");
+        testsToRun["imageLeavePicture"] = true;
+        break;
+
     }
     checkTestsCompleted();
   }
 
+  // Enable <picture> and <img srcset> for test
+  SpecialPowers.pushPrefEnv({'set': [[ "dom.image.srcset.enabled", true ],
+                                     [ "dom.image.picture.enabled", true ]] },
+                            function() {
+    // Kick off test
+    reloadFrame();
+  });
+
   SimpleTest.waitForExplicitFinish();
+
   </script>
 </head>
 
 <body>
-  <div id="framediv">
-    <iframe id="testHarness" src="https://example.com/tests/content/base/test/file_mixed_content_main.html"></iframe>
-  </div>
+  <div id="framediv"></div>
   <pre id="log"></pre>
 </body>
 </html>
--- a/content/html/content/src/HTMLImageElement.cpp
+++ b/content/html/content/src/HTMLImageElement.cpp
@@ -11,25 +11,29 @@
 #include "nsMappedAttributes.h"
 #include "nsSize.h"
 #include "nsIDocument.h"
 #include "nsIDOMMutationEvent.h"
 #include "nsIScriptContext.h"
 #include "nsIURL.h"
 #include "nsIIOService.h"
 #include "nsIServiceManager.h"
+#include "nsIAppShell.h"
+#include "nsWidgetsCID.h"
 #include "nsNetUtil.h"
 #include "nsContentUtils.h"
 #include "nsContainerFrame.h"
 #include "nsNodeInfoManager.h"
 #include "mozilla/MouseEvents.h"
 #include "nsContentPolicyUtils.h"
 #include "nsIDOMWindow.h"
 #include "nsFocusManager.h"
 #include "mozilla/dom/HTMLFormElement.h"
+#include "nsAttrValueOrString.h"
+#include "imgLoader.h"
 
 // Responsive images!
 #include "mozilla/dom/HTMLSourceElement.h"
 #include "mozilla/dom/ResponsiveImageSelector.h"
 
 #include "imgIContainer.h"
 #include "imgILoader.h"
 #include "imgINotificationObserver.h"
@@ -43,36 +47,64 @@
 #include "mozilla/EventDispatcher.h"
 #include "mozilla/EventStates.h"
 
 #include "nsLayoutUtils.h"
 
 #include "mozilla/Preferences.h"
 static const char *kPrefSrcsetEnabled = "dom.image.srcset.enabled";
 
+static NS_DEFINE_CID(kAppShellCID, NS_APPSHELL_CID);
+
 NS_IMPL_NS_NEW_HTML_ELEMENT(Image)
 
+#ifdef DEBUG
 // Is aSubject a previous sibling of aNode.
 static bool IsPreviousSibling(nsINode *aSubject, nsINode *aNode)
 {
   if (aSubject == aNode) {
     return false;
   }
 
   nsINode *parent = aSubject->GetParentNode();
   if (parent && parent == aNode->GetParentNode()) {
     return parent->IndexOf(aSubject) < parent->IndexOf(aNode);
   }
 
   return false;
 }
+#endif
 
 namespace mozilla {
 namespace dom {
 
+// Calls LoadSelectedImage on host element unless it has been superseded or
+// canceled -- this is the synchronous section of "update the image data".
+// https://html.spec.whatwg.org/multipage/embedded-content.html#update-the-image-data
+class ImageLoadTask : public nsRunnable
+{
+public:
+  explicit ImageLoadTask(HTMLImageElement *aElement) :
+    mElement(aElement)
+  {}
+
+  NS_IMETHOD Run()
+  {
+    if (mElement->mPendingImageLoadTask == this) {
+      mElement->mPendingImageLoadTask = nullptr;
+      mElement->LoadSelectedImage(true, true);
+    }
+    return NS_OK;
+  }
+
+private:
+  ~ImageLoadTask() {}
+  nsRefPtr<HTMLImageElement> mElement;
+};
+
 HTMLImageElement::HTMLImageElement(already_AddRefed<mozilla::dom::NodeInfo>& aNodeInfo)
   : nsGenericHTMLElement(aNodeInfo)
   , mForm(nullptr)
 {
   // We start out broken
   AddStatesSilently(NS_EVENT_STATE_BROKEN);
 }
 
@@ -80,16 +112,19 @@ HTMLImageElement::~HTMLImageElement()
 {
   DestroyImageLoadingContent();
 }
 
 
 NS_IMPL_ADDREF_INHERITED(HTMLImageElement, Element)
 NS_IMPL_RELEASE_INHERITED(HTMLImageElement, Element)
 
+NS_IMPL_CYCLE_COLLECTION_INHERITED(HTMLImageElement,
+                                   nsGenericHTMLElement,
+                                   mResponsiveSelector)
 
 // QueryInterface implementation for HTMLImageElement
 NS_INTERFACE_TABLE_HEAD_CYCLE_COLLECTION_INHERITED(HTMLImageElement)
   NS_INTERFACE_TABLE_INHERITED(HTMLImageElement,
                                nsIDOMHTMLImageElement,
                                nsIImageLoadingContent,
                                imgIOnloadBlocker,
                                imgINotificationObserver)
@@ -361,50 +396,53 @@ HTMLImageElement::AfterSetAttr(int32_t a
     mForm->AddImageElementToTable(this,
       nsDependentAtomString(aValue->GetAtomValue()));
   }
 
   // Handle src/srcset/crossorigin updates. If aNotify is false, we are coming
   // from the parser or some such place; we'll get bound after all the
   // attributes have been set, so we'll do the image load from BindToTree.
 
-  nsCOMPtr<nsIContent> thisContent = AsContent();
+  nsAttrValueOrString attrVal(aValue);
+
   if (aName == nsGkAtoms::src &&
-      aNameSpaceID == kNameSpaceID_None) {
-    // SetAttr handles setting src in the non-responsive case, so only handle it
-    // for responsive mode or unsetting
-    if (!aValue) {
+      aNameSpaceID == kNameSpaceID_None &&
+      !aValue) {
+    // SetAttr handles setting src since it needs to catch img.src =
+    // img.src, so we only need to handle the unset case
+    if (InResponsiveMode()) {
+      if (mResponsiveSelector->Content() == this) {
+        mResponsiveSelector->SetDefaultSource(nullptr);
+      }
+      QueueImageLoadTask();
+    } else {
+      // Bug 1076583 - We still behave synchronously in the non-responsive case
       CancelImageRequests(aNotify);
-    } else if (mResponsiveSelector) {
-      mResponsiveSelector->SetDefaultSource(aValue ? aValue->GetStringValue()
-                                                   : EmptyString());
-      LoadSelectedImage(false, aNotify);
     }
   } else if (aName == nsGkAtoms::srcset &&
              aNameSpaceID == kNameSpaceID_None &&
-             aNotify &&
-             AsContent()->IsInDoc() &&
              IsSrcsetEnabled()) {
-    // We currently don't handle responsive mode until BindToTree
-    PictureSourceSrcsetChanged(thisContent,
-                               aValue ? aValue->GetStringValue() : EmptyString(),
-                               aNotify);
+    PictureSourceSrcsetChanged(this, attrVal.String(), aNotify);
   } else if (aName == nsGkAtoms::sizes &&
              aNameSpaceID == kNameSpaceID_None &&
-             thisContent->IsInDoc() &&
              HTMLPictureElement::IsPictureEnabled()) {
-    PictureSourceSizesChanged(thisContent, aValue->GetStringValue(), aNotify);
+    PictureSourceSizesChanged(this, attrVal.String(), aNotify);
   } else if (aName == nsGkAtoms::crossorigin &&
              aNameSpaceID == kNameSpaceID_None &&
              aNotify) {
-    // We want aForce == true in this LoadImage call, because we want to force
-    // a new load of the image with the new cross origin policy.
-    nsCOMPtr<nsIURI> currentURI;
-    if (NS_SUCCEEDED(GetCurrentURI(getter_AddRefs(currentURI))) && currentURI) {
-      LoadImage(currentURI, true, aNotify);
+    // Force a new load of the image with the new cross origin policy.
+    if (InResponsiveMode()) {
+      // per spec, full selection runs when this changes, even though
+      // it doesn't directly affect the source selection
+      QueueImageLoadTask();
+    } else {
+      // Bug 1076583 - We still use the older synchronous algorithm in
+      // non-responsive mode.  Force a new load of the image with the
+      // new cross origin policy.
+      ForceReload(aNotify);
     }
   }
 
   return nsGenericHTMLElement::AfterSetAttr(aNameSpaceID, aName,
                                             aValue, aNotify);
 }
 
 
@@ -451,17 +489,17 @@ HTMLImageElement::IsHTMLFocusable(bool a
     }
   }
 
   if (aTabIndex) {
     // Can be in tab order if tabindex >=0 and form controls are tabbable.
     *aTabIndex = (sTabFocusModel & eTabFocus_formElementsMask)? tabIndex : -1;
   }
 
-  *aIsFocusable = 
+  *aIsFocusable =
 #ifdef XP_MACOSX
     (!aWithMouse || nsFocusManager::sMouseFocusesFormControl) &&
 #endif
     (tabIndex >= 0 || HasAttr(kNameSpaceID_None, nsGkAtoms::tabindex));
 
   return false;
 }
 
@@ -470,42 +508,55 @@ HTMLImageElement::SetAttr(int32_t aNameS
                           nsIAtom* aPrefix, const nsAString& aValue,
                           bool aNotify)
 {
   // We need to force our image to reload.  This must be done here, not in
   // AfterSetAttr or BeforeSetAttr, because we want to do it even if the attr is
   // being set to its existing value, which is normally optimized away as a
   // no-op.
   //
-  // If aNotify is false, we are coming from the parser or some such place;
-  // we'll get bound after all the attributes have been set, so we'll do the
-  // image load from BindToTree. Skip the LoadImage call in that case.
+  // If we are in responsive mode, we drop the forced reload behavior,
+  // but still trigger a image load task for img.src = img.src per
+  // spec.
   //
-  // If we are in responsive mode, we drop the forced reload behavior, and
-  // handle updates in AfterSetAttr
-  if (aNotify && !mResponsiveSelector &&
-      aNameSpaceID == kNameSpaceID_None &&
+  // Both cases handle unsetting src in AfterSetAttr
+  if (aNameSpaceID == kNameSpaceID_None &&
       aName == nsGkAtoms::src) {
 
-    // Prevent setting image.src by exiting early
+    // This is for dom.disable_image_src_set, which predates "srcset"
+    // as an attribute. See Bug 773429
     if (nsContentUtils::IsImageSrcSetDisabled()) {
       return NS_OK;
     }
 
-    // A hack to get animations to reset. See bug 594771.
-    mNewRequestsWillNeedAnimationReset = true;
+    if (InResponsiveMode()) {
+      if (mResponsiveSelector &&
+          mResponsiveSelector->Content() == this) {
+        mResponsiveSelector->SetDefaultSource(aValue);
+      }
+      QueueImageLoadTask();
+    } else if (aNotify) {
+      // If aNotify is false, we are coming from the parser or some such place;
+      // we'll get bound after all the attributes have been set, so we'll do the
+      // sync image load from BindToTree. Skip the LoadImage call in that case.
+
+      // Note that this sync behavior is partially removed from the spec, bug 1076583
 
-    // Force image loading here, so that we'll try to load the image from
-    // network if it's set to be not cacheable...  If we change things so that
-    // the state gets in Element's attr-setting happen around this
-    // LoadImage call, we could start passing false instead of aNotify
-    // here.
-    LoadImage(aValue, true, aNotify);
+      // A hack to get animations to reset. See bug 594771.
+      mNewRequestsWillNeedAnimationReset = true;
 
-    mNewRequestsWillNeedAnimationReset = false;
+      // Force image loading here, so that we'll try to load the image from
+      // network if it's set to be not cacheable...  If we change things so that
+      // the state gets in Element's attr-setting happen around this
+      // LoadImage call, we could start passing false instead of aNotify
+      // here.
+      LoadImage(aValue, true, aNotify, eImageLoadType_Normal);
+
+      mNewRequestsWillNeedAnimationReset = false;
+    }
   }
 
   return nsGenericHTMLElement::SetAttr(aNameSpaceID, aName, aPrefix, aValue,
                                        aNotify);
 }
 
 nsresult
 HTMLImageElement::BindToTree(nsIDocument* aDocument, nsIContent* aParent,
@@ -521,37 +572,41 @@ HTMLImageElement::BindToTree(nsIDocument
                                     aCompileEventHandlers);
 
   if (aParent) {
     UpdateFormOwner();
   }
 
   bool addedToPicture = aParent && aParent->Tag() == nsGkAtoms::picture &&
                         HTMLPictureElement::IsPictureEnabled();
-  bool haveSrcset = IsSrcsetEnabled() &&
-                    HasAttr(kNameSpaceID_None, nsGkAtoms::srcset);
-  if (addedToPicture || haveSrcset ||
-      HasAttr(kNameSpaceID_None, nsGkAtoms::src)) {
+  if (addedToPicture) {
+    QueueImageLoadTask();
+  } else if (!InResponsiveMode() &&
+             HasAttr(kNameSpaceID_None, nsGkAtoms::src)) {
+    // We skip loading when our attributes were set from parser land,
+    // so trigger a aForce=false load now to check if things changed.
+    // This isn't necessary for responsive mode, since creating the
+    // image load task is asynchronous we don't need to take special
+    // care to avoid doing so when being filled by the parser.
+
     // FIXME: Bug 660963 it would be nice if we could just have
     // ClearBrokenState update our state and do it fast...
     ClearBrokenState();
     RemoveStatesSilently(NS_EVENT_STATE_BROKEN);
 
-    // We don't handle responsive changes when not bound to a tree, update them
-    // now if necessary
-    if (addedToPicture || haveSrcset) {
-      MaybeUpdateResponsiveSelector();
-    }
+    // We still act synchronously for the non-responsive case (Bug
+    // 1076583), but still need to delay if it is unsafe to run
+    // script.
 
     // If loading is temporarily disabled, don't even launch MaybeLoadImage.
     // Otherwise MaybeLoadImage may run later when someone has reenabled
     // loading.
     if (LoadingEnabled()) {
       nsContentUtils::AddScriptRunner(
-        NS_NewRunnableMethod(this, &HTMLImageElement::MaybeLoadImage));
+          NS_NewRunnableMethod(this, &HTMLImageElement::MaybeLoadImage));
     }
   }
 
   return rv;
 }
 
 void
 HTMLImageElement::UnbindFromTree(bool aDeep, bool aNullParent)
@@ -559,17 +614,23 @@ HTMLImageElement::UnbindFromTree(bool aD
   if (mForm) {
     if (aNullParent || !FindAncestorForm(mForm)) {
       ClearForm(true);
     } else {
       UnsetFlags(MAYBE_ORPHAN_FORM_ELEMENT);
     }
   }
 
-  mResponsiveSelector = nullptr;
+  if (aNullParent &&
+      nsINode::GetParentNode()->Tag() == nsGkAtoms::picture &&
+      HTMLPictureElement::IsPictureEnabled()) {
+    // Being removed from picture re-triggers selection, even if we
+    // weren't using a <source> peer
+    QueueImageLoadTask();
+  }
 
   nsImageLoadingContent::UnbindFromTree(aDeep, aNullParent);
   nsGenericHTMLElement::UnbindFromTree(aDeep, aNullParent);
 }
 
 void
 HTMLImageElement::UpdateFormOwner()
 {
@@ -601,19 +662,19 @@ void
 HTMLImageElement::MaybeLoadImage()
 {
   // Our base URI may have changed, or we may have had responsive parameters
   // change while not bound to the tree. Re-parse src/srcset and call LoadImage,
   // which is a no-op if it resolves to the same effective URI without aForce.
 
   // Note, check LoadingEnabled() after LoadImage call.
 
-  nsresult rv = LoadSelectedImage(false, true);
+  LoadSelectedImage(false, true);
 
-  if (NS_FAILED(rv) || !LoadingEnabled()) {
+  if (!LoadingEnabled()) {
     CancelImageRequests(true);
   }
 }
 
 EventStates
 HTMLImageElement::IntrinsicState() const
 {
   return nsGenericHTMLElement::IntrinsicState() |
@@ -780,186 +841,245 @@ HTMLImageElement::ClearForm(bool aRemove
                                          HTMLFormElement::ElementRemoved);
     }
   }
 
   UnsetFlags(ADDED_TO_FORM);
   mForm = nullptr;
 }
 
+void
+HTMLImageElement::QueueImageLoadTask()
+{
+  // If loading is temporarily disabled, we don't want to queue tasks
+  // that may then run when loading is re-enabled.
+  if (!LoadingEnabled() || !this->OwnerDoc()->IsCurrentActiveDocument()) {
+    return;
+  }
+
+  // The task checks this to determine if it was the last queued event, so this
+  // implicitly cancels earlier tasks
+  mPendingImageLoadTask = new ImageLoadTask(this);
+  nsCOMPtr<nsIAppShell> appShell = do_GetService(kAppShellCID);
+  if (appShell) {
+    appShell->RunInStableState(mPendingImageLoadTask);
+  } else {
+    MOZ_ASSERT(false, "expect appshell for HTMLImageElement");
+  }
+}
+
+bool
+HTMLImageElement::HaveSrcsetOrInPicture()
+{
+  if (IsSrcsetEnabled() && HasAttr(kNameSpaceID_None, nsGkAtoms::srcset)) {
+    return true;
+  }
+
+  if (!HTMLPictureElement::IsPictureEnabled()) {
+    return false;
+  }
+
+  nsINode *parent = nsINode::GetParentNode();
+  return (parent && parent->Tag() == nsGkAtoms::picture);
+}
+
+bool
+HTMLImageElement::InResponsiveMode()
+{
+  // When we lose srcset or leave a <picture> element, the fallback to img.src
+  // will happen from the microtask, and we should behave responsively in the
+  // interim
+  return mResponsiveSelector ||
+         mPendingImageLoadTask ||
+         HaveSrcsetOrInPicture();
+}
+
 nsresult
 HTMLImageElement::LoadSelectedImage(bool aForce, bool aNotify)
 {
   nsresult rv = NS_ERROR_FAILURE;
 
+  if (aForce) {
+    // In responsive mode we generally want to re-run the full
+    // selection algorithm whenever starting a new load, per
+    // spec. This also causes us to re-resolve the URI as appropriate.
+    UpdateResponsiveSource();
+  }
+
   if (mResponsiveSelector) {
     nsCOMPtr<nsIURI> url = mResponsiveSelector->GetSelectedImageURL();
-    if (url) {
-      rv = LoadImage(url, aForce, aNotify);
-    } else {
-      CancelImageRequests(aNotify);
-      rv = NS_OK;
-    }
+    rv = LoadImage(url, aForce, aNotify, eImageLoadType_Imageset);
   } else {
     nsAutoString src;
     if (!GetAttr(kNameSpaceID_None, nsGkAtoms::src, src)) {
       CancelImageRequests(aNotify);
       rv = NS_OK;
     } else {
-      rv = LoadImage(src, aForce, aNotify);
-      if (NS_FAILED(rv)) {
-        CancelImageRequests(aNotify);
-      }
+      // If we have a srcset attribute or are in a <picture> element,
+      // we always use the Imageset load type, even if we parsed no
+      // valid responsive sources from either, per spec.
+      rv = LoadImage(src, aForce, aNotify,
+                     HaveSrcsetOrInPicture() ? eImageLoadType_Imageset
+                                             : eImageLoadType_Normal);
     }
   }
 
+  if (NS_FAILED(rv)) {
+    CancelImageRequests(aNotify);
+  }
   return rv;
 }
 
 void
 HTMLImageElement::PictureSourceSrcsetChanged(nsIContent *aSourceNode,
                                              const nsAString& aNewValue,
                                              bool aNotify)
 {
-  if (aSourceNode != AsContent() && !HTMLPictureElement::IsPictureEnabled()) {
-    // Don't consider <source> nodes if picture is pref'd off
+  bool isSelf = aSourceNode == this;
+
+  if (!IsSrcsetEnabled() ||
+      (!isSelf && !HTMLPictureElement::IsPictureEnabled())) {
     return;
   }
 
-  nsIContent *currentSrc = mResponsiveSelector ? mResponsiveSelector->Content()
-                                               : nullptr;
+  MOZ_ASSERT(isSelf || IsPreviousSibling(aSourceNode, this),
+             "Should not be getting notifications for non-previous-siblings");
+
+  nsIContent *currentSrc =
+    mResponsiveSelector ? mResponsiveSelector->Content() : nullptr;
 
   if (aSourceNode == currentSrc) {
-    // We're currently using this node as our responsive selector source.
+    // We're currently using this node as our responsive selector
+    // source.
     mResponsiveSelector->SetCandidatesFromSourceSet(aNewValue);
-    // Search for a new source if we are no longer valid.
-    MaybeUpdateResponsiveSelector(currentSrc);
+  }
 
-    LoadSelectedImage(false, aNotify);
-  } else if (currentSrc && IsPreviousSibling(currentSrc, aSourceNode)) {
-      // If we have a source and it is previous to the one being updated, ignore
-      return;
-  } else {
-    // This is previous to our current source or we don't have a current source,
-    // use it if valid.
-    if (TryCreateResponsiveSelector(aSourceNode, &aNewValue, nullptr)) {
-      LoadSelectedImage(false, aNotify);
-    }
-  }
+  // This always triggers the image update steps per the spec, even if
+  // we are not using this source.
+  QueueImageLoadTask();
 }
 
 void
 HTMLImageElement::PictureSourceSizesChanged(nsIContent *aSourceNode,
                                             const nsAString& aNewValue,
                                             bool aNotify)
 {
   if (!HTMLPictureElement::IsPictureEnabled()) {
-    // Don't consider sizes at all if picture support is disabled
     return;
   }
 
-  nsIContent *currentSrc = mResponsiveSelector ? mResponsiveSelector->Content()
-                                               : nullptr;
+  MOZ_ASSERT(aSourceNode == this ||
+             IsPreviousSibling(aSourceNode, this),
+             "Should not be getting notifications for non-previous-siblings");
+
+  nsIContent *currentSrc =
+    mResponsiveSelector ? mResponsiveSelector->Content() : nullptr;
 
   if (aSourceNode == currentSrc) {
-    // We're currently using this node as our responsive selector source.
+    // We're currently using this node as our responsive selector
+    // source.
     mResponsiveSelector->SetSizesFromDescriptor(aNewValue);
-    LoadSelectedImage(false, aNotify);
   }
+
+  // This always triggers the image update steps per the spec, even if
+  // we are not using this source.
+  QueueImageLoadTask();
+}
+
+void
+HTMLImageElement::PictureSourceMediaOrTypeChanged(nsIContent *aSourceNode,
+                                                  bool aNotify)
+{
+  if (!HTMLPictureElement::IsPictureEnabled()) {
+    return;
+  }
+
+  MOZ_ASSERT(IsPreviousSibling(aSourceNode, this),
+             "Should not be getting notifications for non-previous-siblings");
+
+  // This always triggers the image update steps per the spec, even if
+  // we are not switching to/from this source
+  QueueImageLoadTask();
 }
 
 void
 HTMLImageElement::PictureSourceAdded(nsIContent *aSourceNode)
 {
-  // If the source node is previous to our current one, or ourselves if we have
-  // no responsive source, try to use it as a responsive source.
-  nsIContent *currentSrc = mResponsiveSelector ? mResponsiveSelector->Content()
-                                               : AsContent();
+  if (!HTMLPictureElement::IsPictureEnabled()) {
+    return;
+  }
 
-  if (HTMLPictureElement::IsPictureEnabled() &&
-      IsPreviousSibling(aSourceNode, currentSrc) &&
-      TryCreateResponsiveSelector(aSourceNode, nullptr, nullptr)) {
-    LoadSelectedImage(false, true);
-  }
+  QueueImageLoadTask();
 }
 
 void
 HTMLImageElement::PictureSourceRemoved(nsIContent *aSourceNode)
 {
-  // If this is our current source, we'll need to find another one or leave
-  // responsive mode.
-  if (mResponsiveSelector && mResponsiveSelector->Content() == aSourceNode) {
-    MaybeUpdateResponsiveSelector(aSourceNode, true);
-    LoadSelectedImage(false, true);
+  if (!HTMLPictureElement::IsPictureEnabled()) {
+    return;
   }
+
+  QueueImageLoadTask();
 }
 
-bool
-HTMLImageElement::MaybeUpdateResponsiveSelector(nsIContent *aCurrentSource,
-                                                bool aSourceRemoved)
+void
+HTMLImageElement::UpdateResponsiveSource()
 {
-  nsIContent *thisContent = AsContent();
-
-  if (!aCurrentSource && mResponsiveSelector) {
-    aCurrentSource = mResponsiveSelector->Content();
+  if (!IsSrcsetEnabled()) {
+    mResponsiveSelector = nullptr;
+    return;
   }
 
-  // If we have a source with candidates, no update is needed unless it is being
-  // removed
-  if (aCurrentSource && !aSourceRemoved &&
-      mResponsiveSelector->NumCandidates()) {
-    return false;
-  }
+  nsIContent *currentSource =
+    mResponsiveSelector ? mResponsiveSelector->Content() : nullptr;
+  bool pictureEnabled = HTMLPictureElement::IsPictureEnabled();
+  nsINode *parent = pictureEnabled ? this->nsINode::GetParentNode() : nullptr;
 
-  // Otherwise, invalidate
-  bool hadSelector = !!mResponsiveSelector;
-  mResponsiveSelector = nullptr;
-
-  if (!IsSrcsetEnabled()) {
-    return hadSelector;
+  nsINode *candidateSource = nullptr;
+  if (parent && parent->Tag() == nsGkAtoms::picture) {
+    // Walk source nodes previous to ourselves
+    candidateSource = parent->GetFirstChild();
+  } else {
+    candidateSource = this;
   }
 
-  // See if there's another source node we could use.
-  bool pictureEnabled = HTMLPictureElement::IsPictureEnabled();
-  nsIContent *nextSource = nullptr;
-  if (pictureEnabled && aCurrentSource && aCurrentSource != thisContent) {
-    // If current source is the <img> tag, there is no next candidate. Otherwise,
-    // it's the next sibling of the current source.
-    MOZ_ASSERT(IsPreviousSibling(aCurrentSource, thisContent) &&
-               thisContent->GetParentNode()->Tag() == nsGkAtoms::picture);
-    nextSource = aCurrentSource->GetNextSibling();
-  } else if (!aCurrentSource) {
-    // If no current source at all, start from the first possible source, which
-    // is the first node of the <picture> element or ourselves if we're not a
-    // picture
-    nsINode *parent = pictureEnabled ? thisContent->GetParentNode() : nullptr;
-    if (parent && parent->Tag() == nsGkAtoms::picture) {
-      nextSource = parent->GetFirstChild();
-    } else {
-      nextSource = thisContent;
+  while (candidateSource) {
+    if (candidateSource == currentSource) {
+      // found no better source before current, re-run selection on
+      // that and keep it if it's still usable.
+      mResponsiveSelector->SelectImage(true);
+      if (mResponsiveSelector->NumCandidates()) {
+        break;
+      }
+
+      // no longer valid
+      mResponsiveSelector = nullptr;
+      if (candidateSource == this) {
+        // No further possibilities
+        break;
+      }
+    } else if (candidateSource == this) {
+      // We are the last possible source
+      if (!TryCreateResponsiveSelector(candidateSource->AsContent())) {
+        // Failed to find any source
+        mResponsiveSelector = nullptr;
+      }
+      break;
+    } else if (candidateSource->Tag() == nsGkAtoms::source &&
+               TryCreateResponsiveSelector(candidateSource->AsContent())) {
+      // This led to a valid source, stop
+      break;
     }
+    candidateSource = candidateSource->GetNextSibling();
   }
 
-  while (nextSource) {
-    if (nextSource == thisContent) {
-      // We are the last possible source, so stop searching if we match or
-      // not
-      TryCreateResponsiveSelector(nextSource);
-      break;
-    } else if (nextSource->Tag() == nsGkAtoms::source &&
-               TryCreateResponsiveSelector(nextSource)) {
-      // If this led to a valid source, stop
-      break;
-    }
-
-    nextSource = nextSource->GetNextSibling();
+  if (!candidateSource) {
+    // Ran out of siblings without finding ourself, e.g. XBL magic.
+    mResponsiveSelector = nullptr;
   }
-
-  // State changed unless we didn't make a selector and didn't start with one
-  return mResponsiveSelector || hadSelector;
 }
 
 bool
 HTMLImageElement::TryCreateResponsiveSelector(nsIContent *aSourceNode,
                                               const nsAString *aSrcset,
                                               const nsAString *aSizes)
 {
   if (!IsSrcsetEnabled()) {
@@ -967,26 +1087,33 @@ HTMLImageElement::TryCreateResponsiveSel
   }
 
   bool pictureEnabled = HTMLPictureElement::IsPictureEnabled();
   // Skip if this is not a <source> with matching media query
   bool isSourceTag = aSourceNode->Tag() == nsGkAtoms::source;
   if (isSourceTag) {
     DebugOnly<nsINode *> parent(nsINode::GetParentNode());
     MOZ_ASSERT(parent && parent->Tag() == nsGkAtoms::picture);
-    MOZ_ASSERT(IsPreviousSibling(aSourceNode, AsContent()));
+    MOZ_ASSERT(IsPreviousSibling(aSourceNode, this));
     MOZ_ASSERT(pictureEnabled);
 
+    // Check media and type
     HTMLSourceElement *src = static_cast<HTMLSourceElement*>(aSourceNode);
     if (!src->MatchesCurrentMedia()) {
       return false;
     }
+
+    nsAutoString type;
+    if (aSourceNode->GetAttr(kNameSpaceID_None, nsGkAtoms::type, type) &&
+        !imgLoader::SupportImageWithMimeType(NS_ConvertUTF16toUTF8(type).get())) {
+      return false;
+    }
   } else if (aSourceNode->Tag() == nsGkAtoms::img) {
     // Otherwise this is the <img> tag itself
-    MOZ_ASSERT(aSourceNode == AsContent());
+    MOZ_ASSERT(aSourceNode == this);
   }
 
   // Skip if has no srcset or an empty srcset
   nsString srcset;
   if (aSrcset) {
     srcset = *aSrcset;
   } else if (!aSourceNode->GetAttr(kNameSpaceID_None, nsGkAtoms::srcset,
                                    srcset)) {
@@ -994,33 +1121,33 @@ HTMLImageElement::TryCreateResponsiveSel
   }
 
   if (srcset.IsEmpty()) {
     return false;
   }
 
 
   // Try to parse
-  nsRefPtr<ResponsiveImageSelector> sel = new ResponsiveImageSelector(this);
+  nsRefPtr<ResponsiveImageSelector> sel = new ResponsiveImageSelector(aSourceNode);
   if (!sel->SetCandidatesFromSourceSet(srcset)) {
     // No possible candidates, don't need to bother parsing sizes
     return false;
   }
 
   if (pictureEnabled && aSizes) {
     sel->SetSizesFromDescriptor(*aSizes);
   } else if (pictureEnabled) {
     nsAutoString sizes;
     aSourceNode->GetAttr(kNameSpaceID_None, nsGkAtoms::sizes, sizes);
     sel->SetSizesFromDescriptor(sizes);
   }
 
   // If this is the <img> tag, also pull in src as the default source
   if (!isSourceTag) {
-    MOZ_ASSERT(aSourceNode == AsContent());
+    MOZ_ASSERT(aSourceNode == this);
     nsAutoString src;
     if (GetAttr(kNameSpaceID_None, nsGkAtoms::src, src) && !src.IsEmpty()) {
       sel->SetDefaultSource(src);
     }
   }
 
   mResponsiveSelector = sel;
   return true;
--- a/content/html/content/src/HTMLImageElement.h
+++ b/content/html/content/src/HTMLImageElement.h
@@ -7,47 +7,55 @@
 #define mozilla_dom_HTMLImageElement_h
 
 #include "mozilla/Attributes.h"
 #include "nsGenericHTMLElement.h"
 #include "nsImageLoadingContent.h"
 #include "nsIDOMHTMLImageElement.h"
 #include "imgRequestProxy.h"
 #include "Units.h"
+#include "nsCycleCollectionParticipant.h"
 
 // Only needed for IsPictureEnabled()
 #include "mozilla/dom/HTMLPictureElement.h"
 
 namespace mozilla {
 class EventChainPreVisitor;
 namespace dom {
 
 class ResponsiveImageSelector;
 class HTMLImageElement MOZ_FINAL : public nsGenericHTMLElement,
                                    public nsImageLoadingContent,
                                    public nsIDOMHTMLImageElement
 {
   friend class HTMLSourceElement;
+  friend class HTMLPictureElement;
+  friend class ImageLoadTask;
 public:
   explicit HTMLImageElement(already_AddRefed<mozilla::dom::NodeInfo>& aNodeInfo);
 
   static already_AddRefed<HTMLImageElement>
     Image(const GlobalObject& aGlobal,
           const Optional<uint32_t>& aWidth,
           const Optional<uint32_t>& aHeight,
           ErrorResult& aError);
 
+  NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(HTMLImageElement,
+                                           nsGenericHTMLElement)
+
   // nsISupports
   NS_DECL_ISUPPORTS_INHERITED
 
   virtual bool Draggable() const MOZ_OVERRIDE;
 
   // nsIDOMHTMLImageElement
   NS_DECL_NSIDOMHTMLIMAGEELEMENT
 
+  NS_IMPL_FROMCONTENT_HTML_WITH_TAG(HTMLImageElement, img)
+
   // override from nsImageLoadingContent
   CORSMode GetCORSMode();
 
   // nsIContent
   virtual bool ParseAttribute(int32_t aNamespaceID,
                                 nsIAtom* aAttribute,
                                 const nsAString& aValue,
                                 nsAttrValue& aResult) MOZ_OVERRIDE;
@@ -192,32 +200,61 @@ public:
   void SetForm(nsIDOMHTMLFormElement* aForm);
   void ClearForm(bool aRemoveFromForm);
 
   virtual void DestroyContent() MOZ_OVERRIDE;
 
 protected:
   virtual ~HTMLImageElement();
 
+  // Queues a task to run LoadSelectedImage pending stable state.
+  //
+  // Pending Bug 1076583 this is only used by the responsive image
+  // algorithm (InResponsiveMode()) -- synchronous actions when just
+  // using img.src will bypass this, and update source and kick off
+  // image load synchronously.
+  void QueueImageLoadTask();
+
+  // True if we have a srcset attribute or a <picture> parent, regardless of if
+  // any valid responsive sources were parsed from either.
+  bool HaveSrcsetOrInPicture();
+
+  // True if we are using the newer image loading algorithm. This will be the
+  // only mode after Bug 1076583
+  bool InResponsiveMode();
+
   // Resolve and load the current mResponsiveSelector (responsive mode) or src
   // attr image.
   nsresult LoadSelectedImage(bool aForce, bool aNotify);
 
   // Update/create/destroy mResponsiveSelector
   void PictureSourceSrcsetChanged(nsIContent *aSourceNode,
                                   const nsAString& aNewValue, bool aNotify);
   void PictureSourceSizesChanged(nsIContent *aSourceNode,
                                  const nsAString& aNewValue, bool aNotify);
+  // As we re-run the source selection on these mutations regardless,
+  // we don't actually care which changed or to what
+  void PictureSourceMediaOrTypeChanged(nsIContent *aSourceNode, bool aNotify);
 
   void PictureSourceAdded(nsIContent *aSourceNode);
   // This should be called prior to the unbind, such that nextsibling works
   void PictureSourceRemoved(nsIContent *aSourceNode);
 
-  bool MaybeUpdateResponsiveSelector(nsIContent *aCurrentSource = nullptr,
-                                     bool aSourceRemoved = false);
+  // Re-evaluates all source nodes (picture <source>,<img>) and finds
+  // the best source set for mResponsiveSelector. If a better source
+  // is found, creates a new selector and feeds the source to it. If
+  // the current ResponsiveSelector is not changed, runs
+  // SelectImage(true) to re-evaluate its candidates.
+  //
+  // Because keeping the existing selector is the common case (and we
+  // often do no-op reselections), this does not re-parse values for
+  // the existing mResponsiveSelector, meaning you need to update its
+  // parameters as appropriate before calling (or null it out to force
+  // recreation)
+  void UpdateResponsiveSource();
 
   // Given a <source> node that is a previous sibling *or* ourselves, try to
   // create a ResponsiveSelector.
 
   // If the node's srcset/sizes make for an invalid selector, returns
   // false. This does not guarantee the resulting selector matches an image,
   // only that it is valid.
   bool TryCreateResponsiveSelector(nsIContent *aSourceNode,
@@ -242,14 +279,16 @@ protected:
   HTMLFormElement* mForm;
 
   // Created when we're tracking responsive image state
   nsRefPtr<ResponsiveImageSelector> mResponsiveSelector;
 
 private:
   static void MapAttributesIntoRule(const nsMappedAttributes* aAttributes,
                                     nsRuleData* aData);
+
+  nsCOMPtr<nsIRunnable> mPendingImageLoadTask;
 };
 
 } // namespace dom
 } // namespace mozilla
 
 #endif /* mozilla_dom_HTMLImageElement_h */
--- a/content/html/content/src/HTMLInputElement.cpp
+++ b/content/html/content/src/HTMLInputElement.cpp
@@ -1309,17 +1309,17 @@ HTMLInputElement::BeforeSetAttr(int32_t 
     if ((aName == nsGkAtoms::name ||
          (aName == nsGkAtoms::type && !mForm)) &&
         mType == NS_FORM_INPUT_RADIO &&
         (mForm || !mParserCreating)) {
       WillRemoveFromRadioGroup();
     } else if (aNotify && aName == nsGkAtoms::src &&
                mType == NS_FORM_INPUT_IMAGE) {
       if (aValue) {
-        LoadImage(aValue->String(), true, aNotify);
+        LoadImage(aValue->String(), true, aNotify, eImageLoadType_Normal);
       } else {
         // Null value means the attr got unset; drop the image
         CancelImageRequests(aNotify);
       }
     } else if (aNotify && aName == nsGkAtoms::disabled) {
       mDisabledChanged = true;
     } else if (aName == nsGkAtoms::dir &&
                AttrValueIs(kNameSpaceID_None, nsGkAtoms::dir,
@@ -1388,17 +1388,17 @@ HTMLInputElement::AfterSetAttr(int32_t a
         // any.  Note that doing this when we already weren't an image is ok --
         // just does nothing.
         CancelImageRequests(aNotify);
       } else if (aNotify) {
         // We just got switched to be an image input; we should see
         // whether we have an image to load;
         nsAutoString src;
         if (GetAttr(kNameSpaceID_None, nsGkAtoms::src, src)) {
-          LoadImage(src, false, aNotify);
+          LoadImage(src, false, aNotify, eImageLoadType_Normal);
         }
       }
     }
 
     if (mType == NS_FORM_INPUT_RADIO && aName == nsGkAtoms::required) {
       nsCOMPtr<nsIRadioGroupContainer> container = GetRadioGroupContainer();
 
       if (container) {
@@ -4388,17 +4388,17 @@ HTMLInputElement::PostHandleEventForRang
 void
 HTMLInputElement::MaybeLoadImage()
 {
   // Our base URI may have changed; claim that our URI changed, and the
   // nsImageLoadingContent will decide whether a new image load is warranted.
   nsAutoString uri;
   if (mType == NS_FORM_INPUT_IMAGE &&
       GetAttr(kNameSpaceID_None, nsGkAtoms::src, uri) &&
-      (NS_FAILED(LoadImage(uri, false, true)) ||
+      (NS_FAILED(LoadImage(uri, false, true, eImageLoadType_Normal)) ||
        !LoadingEnabled())) {
     CancelImageRequests(true);
   }
 }
 
 nsresult
 HTMLInputElement::BindToTree(nsIDocument* aDocument, nsIContent* aParent,
                              nsIContent* aBindingParent,
--- a/content/html/content/src/HTMLPictureElement.cpp
+++ b/content/html/content/src/HTMLPictureElement.cpp
@@ -35,16 +35,38 @@ HTMLPictureElement::~HTMLPictureElement(
 {
 }
 
 NS_IMPL_ISUPPORTS_INHERITED(HTMLPictureElement, nsGenericHTMLElement,
                             nsIDOMHTMLPictureElement)
 
 NS_IMPL_ELEMENT_CLONE(HTMLPictureElement)
 
+void
+HTMLPictureElement::RemoveChildAt(uint32_t aIndex, bool aNotify)
+{
+  // Find all img siblings after this <source> to notify them of its demise
+  nsCOMPtr<nsINode> child = GetChildAt(aIndex);
+  nsCOMPtr<nsIContent> nextSibling;
+  if (child && child->Tag() == nsGkAtoms::source) {
+    nextSibling = child->GetNextSibling();
+  }
+
+  nsGenericHTMLElement::RemoveChildAt(aIndex, aNotify);
+
+  if (nextSibling && nextSibling->GetParentNode() == this) {
+    do {
+      HTMLImageElement* img = HTMLImageElement::FromContent(nextSibling);
+      if (img) {
+        img->PictureSourceRemoved(child->AsContent());
+      }
+    } while ( (nextSibling = nextSibling->GetNextSibling()) );
+  }
+}
+
 bool
 HTMLPictureElement::IsPictureEnabled()
 {
   return HTMLImageElement::IsSrcsetEnabled() &&
          Preferences::GetBool(kPrefPictureEnabled, false);
 }
 
 JSObject*
--- a/content/html/content/src/HTMLPictureElement.h
+++ b/content/html/content/src/HTMLPictureElement.h
@@ -24,16 +24,17 @@ public:
 
   // nsISupports
   NS_DECL_ISUPPORTS_INHERITED
 
   // nsIDOMHTMLPictureElement
   NS_DECL_NSIDOMHTMLPICTUREELEMENT
 
   virtual nsresult Clone(mozilla::dom::NodeInfo* aNodeInfo, nsINode** aResult) const MOZ_OVERRIDE;
+  virtual void RemoveChildAt(uint32_t aIndex, bool aNotify) MOZ_OVERRIDE;
 
   static bool IsPictureEnabled();
 
 protected:
   virtual ~HTMLPictureElement();
 
   virtual JSObject* WrapNode(JSContext* aCx) MOZ_OVERRIDE;
 };
--- a/content/html/content/src/HTMLSourceElement.cpp
+++ b/content/html/content/src/HTMLSourceElement.cpp
@@ -58,29 +58,34 @@ HTMLSourceElement::MatchesCurrentMedia()
 nsresult
 HTMLSourceElement::AfterSetAttr(int32_t aNameSpaceID, nsIAtom* aName,
                                 const nsAttrValue* aValue, bool aNotify)
 {
   // If we are associated with a <picture> with a valid <img>, notify it of
   // responsive parameter changes
   nsINode *parent = nsINode::GetParentNode();
   if (aNameSpaceID == kNameSpaceID_None &&
-      (aName == nsGkAtoms::srcset || aName == nsGkAtoms::sizes) &&
-      parent && parent->Tag() == nsGkAtoms::picture && MatchesCurrentMedia()) {
-
+      (aName == nsGkAtoms::srcset ||
+       aName == nsGkAtoms::sizes ||
+       aName == nsGkAtoms::media ||
+       aName == nsGkAtoms::type) &&
+      parent && parent->Tag() == nsGkAtoms::picture) {
     nsString strVal = aValue ? aValue->GetStringValue() : EmptyString();
     // Find all img siblings after this <source> and notify them of the change
     nsCOMPtr<nsINode> sibling = AsContent();
     while ( (sibling = sibling->GetNextSibling()) ) {
       if (sibling->Tag() == nsGkAtoms::img) {
         HTMLImageElement *img = static_cast<HTMLImageElement*>(sibling.get());
         if (aName == nsGkAtoms::srcset) {
           img->PictureSourceSrcsetChanged(AsContent(), strVal, aNotify);
         } else if (aName == nsGkAtoms::sizes) {
           img->PictureSourceSizesChanged(AsContent(), strVal, aNotify);
+        } else if (aName == nsGkAtoms::media ||
+                   aName == nsGkAtoms::type) {
+          img->PictureSourceMediaOrTypeChanged(AsContent(), aNotify);
         }
       }
     }
 
   } else if (aNameSpaceID == kNameSpaceID_None && aName == nsGkAtoms::media) {
     mMediaList = nullptr;
     if (aValue) {
       nsString mediaStr = aValue->GetStringValue();
@@ -132,34 +137,16 @@ HTMLSourceElement::BindToTree(nsIDocumen
         img->PictureSourceAdded(AsContent());
       }
     }
   }
 
   return NS_OK;
 }
 
-void
-HTMLSourceElement::UnbindFromTree(bool aDeep, bool aNullParent)
-{
-  nsINode *parent = nsINode::GetParentNode();
-  if (parent && parent->Tag() == nsGkAtoms::picture) {
-    // Find all img siblings after this <source> and notify them of our demise
-    nsCOMPtr<nsINode> sibling = AsContent();
-    while ( (sibling = sibling->GetNextSibling()) ) {
-      if (sibling->Tag() == nsGkAtoms::img) {
-        HTMLImageElement *img = static_cast<HTMLImageElement*>(sibling.get());
-        img->PictureSourceRemoved(AsContent());
-      }
-    }
-  }
-
-  nsGenericHTMLElement::UnbindFromTree(aDeep, aNullParent);
-}
-
 JSObject*
 HTMLSourceElement::WrapNode(JSContext* aCx)
 {
   return HTMLSourceElementBinding::Wrap(aCx, this);
 }
 
 } // namespace dom
 } // namespace mozilla
--- a/content/html/content/src/HTMLSourceElement.h
+++ b/content/html/content/src/HTMLSourceElement.h
@@ -34,17 +34,16 @@ public:
 
   virtual nsresult Clone(mozilla::dom::NodeInfo* aNodeInfo, nsINode** aResult) const MOZ_OVERRIDE;
 
   // Override BindToTree() so that we can trigger a load when we add a
   // child source element.
   virtual nsresult BindToTree(nsIDocument* aDocument, nsIContent* aParent,
                               nsIContent* aBindingParent,
                               bool aCompileEventHandlers) MOZ_OVERRIDE;
-  virtual void UnbindFromTree(bool aDeep, bool aNullParent) MOZ_OVERRIDE;
 
   // If this element's media attr matches for its owner document.  Returns true
   // if no media attr was set.
   bool MatchesCurrentMedia();
 
   // WebIDL
   void GetSrc(nsString& aSrc)
   {
--- a/content/svg/content/src/SVGFEImageElement.cpp
+++ b/content/svg/content/src/SVGFEImageElement.cpp
@@ -80,17 +80,17 @@ SVGFEImageElement::LoadSVGImage(bool aFo
   if (NS_SUCCEEDED(StringToURI(href, doc, getter_AddRefs(hrefAsURI)))) {
     bool isEqual;
     if (NS_SUCCEEDED(hrefAsURI->Equals(baseURI, &isEqual)) && isEqual) {
       // Image URI matches our URI exactly! Bail out.
       return NS_OK;
     }
   }
 
-  return LoadImage(href, aForce, aNotify);
+  return LoadImage(href, aForce, aNotify, eImageLoadType_Normal);
 }
 
 //----------------------------------------------------------------------
 // nsIContent methods:
 
 NS_IMETHODIMP_(bool)
 SVGFEImageElement::IsAttributeMapped(const nsIAtom* name) const
 {
--- a/content/svg/content/src/SVGImageElement.cpp
+++ b/content/svg/content/src/SVGImageElement.cpp
@@ -122,17 +122,17 @@ SVGImageElement::LoadSVGImage(bool aForc
 
   nsAutoString href;
   mStringAttributes[HREF].GetAnimValue(href, this);
   href.Trim(" \t\n\r");
 
   if (baseURI && !href.IsEmpty())
     NS_MakeAbsoluteURI(href, href, baseURI);
 
-  return LoadImage(href, aForce, aNotify);
+  return LoadImage(href, aForce, aNotify, eImageLoadType_Normal);
 }
 
 //----------------------------------------------------------------------
 // nsIContent methods:
 
 nsresult
 SVGImageElement::AfterSetAttr(int32_t aNamespaceID, nsIAtom* aName,
                               const nsAttrValue* aValue, bool aNotify)
--- a/dom/canvas/CanvasRenderingContext2D.cpp
+++ b/dom/canvas/CanvasRenderingContext2D.cpp
@@ -3849,16 +3849,42 @@ bool CanvasRenderingContext2D::IsPointIn
                               state.miterLimit,
                               state.dash.Length(),
                               state.dash.Elements(),
                               state.dashOffset);
 
   return tempPath->StrokeContainsPoint(strokeOptions, Point(x, y), mTarget->GetTransform());
 }
 
+// Returns a surface that contains only the part needed to draw aSourceRect.
+// On entry, aSourceRect is relative to aSurface, and on return aSourceRect is
+// relative to the returned surface.
+static TemporaryRef<SourceSurface>
+ExtractSubrect(SourceSurface* aSurface, mgfx::Rect* aSourceRect, DrawTarget* aTargetDT)
+{
+  mgfx::Rect roundedOutSourceRect = *aSourceRect;
+  roundedOutSourceRect.RoundOut();
+  mgfx::IntRect roundedOutSourceRectInt;
+  if (!roundedOutSourceRect.ToIntRect(&roundedOutSourceRectInt)) {
+    return aSurface;
+  }
+
+  RefPtr<DrawTarget> subrectDT =
+    aTargetDT->CreateSimilarDrawTarget(roundedOutSourceRectInt.Size(), SurfaceFormat::B8G8R8A8);
+
+  if (!subrectDT) {
+    return aSurface;
+  }
+
+  *aSourceRect -= roundedOutSourceRect.TopLeft();
+
+  subrectDT->CopySurface(aSurface, roundedOutSourceRectInt, IntPoint());
+  return subrectDT->Snapshot();
+}
+
 //
 // image
 //
 
 // drawImage(in HTMLImageElement image, in float dx, in float dy);
 //   -- render image from 0,0 at dx,dy top-left coords
 // drawImage(in HTMLImageElement image, in float dx, in float dy, in float sw, in float sh);
 //   -- render image from 0,0 at dx,dy top-left coords clipping it to sw,sh
@@ -3994,20 +4020,29 @@ CanvasRenderingContext2D::DrawImage(cons
   mgfx::Rect bounds;
 
   if (NeedToCalculateBounds()) {
     bounds = mgfx::Rect(dx, dy, dw, dh);
     bounds = mTarget->GetTransform().TransformBounds(bounds);
   }
 
   if (srcSurf) {
+    mgfx::Rect sourceRect(sx, sy, sw, sh);
+    if (element == mCanvasElement) {
+      // srcSurf is a snapshot of mTarget. If we draw to mTarget now, we'll
+      // trigger a COW copy of the whole canvas into srcSurf. That's a huge
+      // waste if sourceRect doesn't cover the whole canvas.
+      // We avoid copying the whole canvas by manually copying just the part
+      // that we need.
+      srcSurf = ExtractSubrect(srcSurf, &sourceRect, mTarget);
+    }
     AdjustedTarget(this, bounds.IsEmpty() ? nullptr : &bounds)->
       DrawSurface(srcSurf,
                   mgfx::Rect(dx, dy, dw, dh),
-                  mgfx::Rect(sx, sy, sw, sh),
+                  sourceRect,
                   DrawSurfaceOptions(filter),
                   DrawOptions(CurrentState().globalAlpha, UsedOperation()));
   } else {
     DrawDirectlyToCanvas(drawInfo, &bounds,
                          mgfx::Rect(dx, dy, dw, dh),
                          mgfx::Rect(sx, sy, sw, sh),
                          imgSize);
   }
--- a/dom/ipc/PBrowser.ipdl
+++ b/dom/ipc/PBrowser.ipdl
@@ -370,17 +370,17 @@ parent:
 
     /**
      * We know for sure that content has either preventDefaulted or not
      * preventDefaulted. This applies to an entire batch of touch events. It is
      * expected that, if there are any DOM touch listeners, touch events will be
      * batched and only processed for panning and zooming if content does not
      * preventDefault.
      */
-    ContentReceivedTouch(ScrollableLayerGuid aGuid, bool aPreventDefault);
+    ContentReceivedTouch(ScrollableLayerGuid aGuid, uint64_t aInputBlockId, bool aPreventDefault);
 
     /**
      * Updates the zoom constraints for a scrollable frame in this tab.
      * The zoom controller code lives on the parent side and so this allows it to
      * have up-to-date zoom constraints.
      */
     UpdateZoomConstraints(uint32_t aPresShellId, ViewID aViewId, bool aIsRoot,
                           ZoomConstraints aConstraints);
@@ -431,17 +431,17 @@ child:
     UpdateFrame(FrameMetrics frame);
 
     // The following methods correspond to functions on the GeckoContentController
     // interface in gfx/layers/apz/public/GeckoContentController.h. Refer to documentation
     // in that file for these functions.
     AcknowledgeScrollUpdate(ViewID aScrollId, uint32_t aScrollGeneration);
     HandleDoubleTap(CSSPoint point, ScrollableLayerGuid aGuid);
     HandleSingleTap(CSSPoint point, ScrollableLayerGuid aGuid);
-    HandleLongTap(CSSPoint point, ScrollableLayerGuid aGuid);
+    HandleLongTap(CSSPoint point, ScrollableLayerGuid aGuid, uint64_t aInputBlockId);
     HandleLongTapUp(CSSPoint point, ScrollableLayerGuid aGuid);
     NotifyAPZStateChange(ViewID aViewId, APZStateChange aChange, int aArg);
 
 
     /**
      * Sending an activate message moves focus to the child.
      */
     Activate();
@@ -457,20 +457,20 @@ child:
                int32_t aButton,
                int32_t aClickCount,
                int32_t aModifiers,
                bool aIgnoreRootScrollFrame);
 
     RealMouseEvent(WidgetMouseEvent event);
     RealKeyEvent(WidgetKeyboardEvent event, MaybeNativeKeyBinding keyBinding);
     MouseWheelEvent(WidgetWheelEvent event);
-    RealTouchEvent(WidgetTouchEvent aEvent, ScrollableLayerGuid aGuid);
+    RealTouchEvent(WidgetTouchEvent aEvent, ScrollableLayerGuid aGuid, uint64_t aInputBlockId);
     // We use a separate message for touchmove events only to apply
     // compression to them.
-    RealTouchMoveEvent(WidgetTouchEvent aEvent, ScrollableLayerGuid aGuid) compress;
+    RealTouchMoveEvent(WidgetTouchEvent aEvent, ScrollableLayerGuid aGuid, uint64_t aInputBlockId) compress;
 
     /**
      * @see nsIDOMWindowUtils sendKeyEvent.
      */
     KeyEvent(nsString aType,
              int32_t aKeyCode,
              int32_t aCharCode,
              int32_t aModifiers,
--- a/dom/ipc/TabChild.cpp
+++ b/dom/ipc/TabChild.cpp
@@ -821,16 +821,17 @@ TabChild::TabChild(nsIContentChild* aMan
   , mAppPackageFileDescriptorRecved(false)
   , mLastBackgroundColor(NS_RGB(255, 255, 255))
   , mDidFakeShow(false)
   , mNotified(false)
   , mTriedBrowserInit(false)
   , mOrientation(eScreenOrientation_PortraitPrimary)
   , mUpdateHitRegion(false)
   , mPendingTouchPreventedResponse(false)
+  , mPendingTouchPreventedBlockId(0)
   , mTouchEndCancelled(false)
   , mEndTouchIsClick(false)
   , mIgnoreKeyPressEvent(false)
   , mActiveElementManager(new ActiveElementManager())
   , mHasValidInnerSize(false)
   , mDestroyed(false)
   , mUniqueId(aTabId)
 {
@@ -1506,17 +1507,17 @@ TabChild::HasValidInnerSize()
 }
 
 void
 TabChild::SendPendingTouchPreventedResponse(bool aPreventDefault,
                                             const ScrollableLayerGuid& aGuid)
 {
   if (mPendingTouchPreventedResponse) {
     MOZ_ASSERT(aGuid == mPendingTouchPreventedGuid);
-    SendContentReceivedTouch(mPendingTouchPreventedGuid, aPreventDefault);
+    SendContentReceivedTouch(mPendingTouchPreventedGuid, mPendingTouchPreventedBlockId, aPreventDefault);
     mPendingTouchPreventedResponse = false;
   }
 }
 
 void
 TabChild::DestroyWindow()
 {
     nsCOMPtr<nsIBaseWindow> baseWindow = do_QueryInterface(WebNavigation());
@@ -1956,17 +1957,17 @@ TabChild::FireSingleTapEvent(LayoutDevic
     Stringify(aPoint).c_str());
   int time = 0;
   DispatchSynthesizedMouseEvent(NS_MOUSE_MOVE, time, aPoint, mWidget);
   DispatchSynthesizedMouseEvent(NS_MOUSE_BUTTON_DOWN, time, aPoint, mWidget);
   DispatchSynthesizedMouseEvent(NS_MOUSE_BUTTON_UP, time, aPoint, mWidget);
 }
 
 bool
-TabChild::RecvHandleLongTap(const CSSPoint& aPoint, const ScrollableLayerGuid& aGuid)
+TabChild::RecvHandleLongTap(const CSSPoint& aPoint, const ScrollableLayerGuid& aGuid, const uint64_t& aInputBlockId)
 {
   TABC_LOG("Handling long tap at %s with %p %p\n",
     Stringify(aPoint).c_str(), mGlobal.get(), mTabChildGlobal.get());
 
   if (!mGlobal || !mTabChildGlobal) {
     return true;
   }
 
@@ -1986,17 +1987,17 @@ TabChild::RecvHandleLongTap(const CSSPoi
       APZCCallbackHelper::ApplyCallbackTransform(aPoint, aGuid) * mWidget->GetDefaultScale();
     int time = 0;
     nsEventStatus status =
       DispatchSynthesizedMouseEvent(NS_MOUSE_MOZLONGTAP, time, currentPoint, mWidget);
     eventHandled = (status == nsEventStatus_eConsumeNoDefault);
     TABC_LOG("MOZLONGTAP event handled: %d\n", eventHandled);
   }
 
-  SendContentReceivedTouch(aGuid, eventHandled);
+  SendContentReceivedTouch(aGuid, aInputBlockId, eventHandled);
 
   return true;
 }
 
 bool
 TabChild::RecvHandleLongTapUp(const CSSPoint& aPoint, const ScrollableLayerGuid& aGuid)
 {
   RecvHandleSingleTap(aPoint, aGuid);
@@ -2255,17 +2256,18 @@ TabChild::CancelTapTracking()
   if (mTapHoldTimer) {
     mTapHoldTimer->Cancel();
   }
   mTapHoldTimer = nullptr;
 }
 
 bool
 TabChild::RecvRealTouchEvent(const WidgetTouchEvent& aEvent,
-                             const ScrollableLayerGuid& aGuid)
+                             const ScrollableLayerGuid& aGuid,
+                             const uint64_t& aInputBlockId)
 {
   TABC_LOG("Receiving touch event of type %d\n", aEvent.message);
 
   WidgetTouchEvent localEvent(aEvent);
   localEvent.widget = mWidget;
   for (size_t i = 0; i < localEvent.touches.Length(); i++) {
     aEvent.touches[i]->mRefPoint = APZCCallbackHelper::ApplyCallbackTransform(aEvent.touches[i]->mRefPoint, aGuid, mWidget->GetDefaultScale());
   }
@@ -2284,24 +2286,25 @@ TabChild::RecvRealTouchEvent(const Widge
   bool isTouchPrevented = nsIPresShell::gPreventMouseEvents ||
                           localEvent.mFlags.mMultipleActionsPrevented;
   switch (aEvent.message) {
   case NS_TOUCH_START: {
     mTouchEndCancelled = false;
     if (mPendingTouchPreventedResponse) {
       // We can enter here if we get two TOUCH_STARTs in a row and didn't
       // respond to the first one. Respond to it now.
-      SendContentReceivedTouch(mPendingTouchPreventedGuid, false);
+      SendContentReceivedTouch(mPendingTouchPreventedGuid, mPendingTouchPreventedBlockId, false);
       mPendingTouchPreventedResponse = false;
     }
     if (isTouchPrevented) {
-      SendContentReceivedTouch(aGuid, isTouchPrevented);
+      SendContentReceivedTouch(aGuid, aInputBlockId, isTouchPrevented);
     } else {
       mPendingTouchPreventedResponse = true;
       mPendingTouchPreventedGuid = aGuid;
+      mPendingTouchPreventedBlockId = aInputBlockId;
     }
     break;
   }
 
   case NS_TOUCH_END:
     if (isTouchPrevented) {
       mTouchEndCancelled = true;
       mEndTouchIsClick = false;
@@ -2319,19 +2322,20 @@ TabChild::RecvRealTouchEvent(const Widge
     NS_WARNING("Unknown touch event type");
   }
 
   return true;
 }
 
 bool
 TabChild::RecvRealTouchMoveEvent(const WidgetTouchEvent& aEvent,
-                                 const ScrollableLayerGuid& aGuid)
+                                 const ScrollableLayerGuid& aGuid,
+                                 const uint64_t& aInputBlockId)
 {
-  return RecvRealTouchEvent(aEvent, aGuid);
+  return RecvRealTouchEvent(aEvent, aGuid, aInputBlockId);
 }
 
 void
 TabChild::RequestNativeKeyBindings(AutoCacheNativeKeyCommands* aAutoCache,
                                    WidgetKeyboardEvent* aEvent)
 {
   MaybeNativeKeyBinding maybeBindings;
   if (!SendRequestNativeKeyBindings(*aEvent, &maybeBindings)) {
--- a/dom/ipc/TabChild.h
+++ b/dom/ipc/TabChild.h
@@ -319,17 +319,18 @@ public:
     virtual bool RecvUpdateFrame(const mozilla::layers::FrameMetrics& aFrameMetrics) MOZ_OVERRIDE;
     virtual bool RecvAcknowledgeScrollUpdate(const ViewID& aScrollId,
                                              const uint32_t& aScrollGeneration) MOZ_OVERRIDE;
     virtual bool RecvHandleDoubleTap(const CSSPoint& aPoint,
                                      const mozilla::layers::ScrollableLayerGuid& aGuid) MOZ_OVERRIDE;
     virtual bool RecvHandleSingleTap(const CSSPoint& aPoint,
                                      const mozilla::layers::ScrollableLayerGuid& aGuid) MOZ_OVERRIDE;
     virtual bool RecvHandleLongTap(const CSSPoint& aPoint,
-                                   const mozilla::layers::ScrollableLayerGuid& aGuid) MOZ_OVERRIDE;
+                                   const mozilla::layers::ScrollableLayerGuid& aGuid,
+                                   const uint64_t& aInputBlockId) MOZ_OVERRIDE;
     virtual bool RecvHandleLongTapUp(const CSSPoint& aPoint,
                                      const mozilla::layers::ScrollableLayerGuid& aGuid) MOZ_OVERRIDE;
     virtual bool RecvNotifyAPZStateChange(const ViewID& aViewId,
                                           const APZStateChange& aChange,
                                           const int& aArg) MOZ_OVERRIDE;
     virtual bool RecvActivate() MOZ_OVERRIDE;
     virtual bool RecvDeactivate() MOZ_OVERRIDE;
     virtual bool RecvMouseEvent(const nsString& aType,
@@ -339,19 +340,21 @@ public:
                                 const int32_t&  aClickCount,
                                 const int32_t&  aModifiers,
                                 const bool&     aIgnoreRootScrollFrame) MOZ_OVERRIDE;
     virtual bool RecvRealMouseEvent(const mozilla::WidgetMouseEvent& event) MOZ_OVERRIDE;
     virtual bool RecvRealKeyEvent(const mozilla::WidgetKeyboardEvent& event,
                                   const MaybeNativeKeyBinding& aBindings) MOZ_OVERRIDE;
     virtual bool RecvMouseWheelEvent(const mozilla::WidgetWheelEvent& event) MOZ_OVERRIDE;
     virtual bool RecvRealTouchEvent(const WidgetTouchEvent& aEvent,
-                                    const ScrollableLayerGuid& aGuid) MOZ_OVERRIDE;
+                                    const ScrollableLayerGuid& aGuid,
+                                    const uint64_t& aInputBlockId) MOZ_OVERRIDE;
     virtual bool RecvRealTouchMoveEvent(const WidgetTouchEvent& aEvent,
-                                        const ScrollableLayerGuid& aGuid) MOZ_OVERRIDE;
+                                        const ScrollableLayerGuid& aGuid,
+                                        const uint64_t& aInputBlockId) MOZ_OVERRIDE;
     virtual bool RecvKeyEvent(const nsString& aType,
                               const int32_t&  aKeyCode,
                               const int32_t&  aCharCode,
                               const int32_t&  aModifiers,
                               const bool&     aPreventDefault) MOZ_OVERRIDE;
     virtual bool RecvCompositionEvent(const mozilla::WidgetCompositionEvent& event) MOZ_OVERRIDE;
     virtual bool RecvSelectionEvent(const mozilla::WidgetSelectionEvent& event) MOZ_OVERRIDE;
     virtual bool RecvActivateFrameEvent(const nsString& aType, const bool& capture) MOZ_OVERRIDE;
@@ -592,16 +595,17 @@ private:
     nscolor mLastBackgroundColor;
     bool mDidFakeShow;
     bool mNotified;
     bool mTriedBrowserInit;
     ScreenOrientation mOrientation;
     bool mUpdateHitRegion;
     bool mPendingTouchPreventedResponse;
     ScrollableLayerGuid mPendingTouchPreventedGuid;
+    uint64_t mPendingTouchPreventedBlockId;
     void FireSingleTapEvent(LayoutDevicePoint aPoint);
 
     bool mTouchEndCancelled;
     bool mEndTouchIsClick;
 
     bool mIgnoreKeyPressEvent;
     nsRefPtr<ActiveElementManager> mActiveElementManager;
     bool mHasValidInnerSize;
--- a/dom/ipc/TabParent.cpp
+++ b/dom/ipc/TabParent.cpp
@@ -639,20 +639,21 @@ void TabParent::HandleSingleTap(const CS
   // TODO Send the modifier data to TabChild for use in mouse events.
   if (!mIsDestroyed) {
     unused << SendHandleSingleTap(aPoint, aGuid);
   }
 }
 
 void TabParent::HandleLongTap(const CSSPoint& aPoint,
                               int32_t aModifiers,
-                              const ScrollableLayerGuid &aGuid)
+                              const ScrollableLayerGuid &aGuid,
+                              uint64_t aInputBlockId)
 {
   if (!mIsDestroyed) {
-    unused << SendHandleLongTap(aPoint, aGuid);
+    unused << SendHandleLongTap(aPoint, aGuid, aInputBlockId);
   }
 }
 
 void TabParent::HandleLongTapUp(const CSSPoint& aPoint,
                                 int32_t aModifiers,
                                 const ScrollableLayerGuid &aGuid)
 {
   if (!mIsDestroyed) {
@@ -872,17 +873,17 @@ TabParent::MapEventCoordinatesForChildPr
   }
 }
 
 bool TabParent::SendRealMouseEvent(WidgetMouseEvent& event)
 {
   if (mIsDestroyed) {
     return false;
   }
-  nsEventStatus status = MaybeForwardEventToRenderFrame(event, nullptr);
+  nsEventStatus status = MaybeForwardEventToRenderFrame(event, nullptr, nullptr);
   if (status == nsEventStatus_eConsumeNoDefault ||
       !MapEventCoordinatesForChildProcess(&event)) {
     return false;
   }
   return PBrowserParent::SendRealMouseEvent(event);
 }
 
 CSSPoint TabParent::AdjustTapToChildWidget(const CSSPoint& aPoint)
@@ -908,23 +909,23 @@ bool TabParent::SendHandleSingleTap(cons
 {
   if (mIsDestroyed) {
     return false;
   }
 
   return PBrowserParent::SendHandleSingleTap(AdjustTapToChildWidget(aPoint), aGuid);
 }
 
-bool TabParent::SendHandleLongTap(const CSSPoint& aPoint, const ScrollableLayerGuid& aGuid)
+bool TabParent::SendHandleLongTap(const CSSPoint& aPoint, const ScrollableLayerGuid& aGuid, const uint64_t& aInputBlockId)
 {
   if (mIsDestroyed) {
     return false;
   }
 
-  return PBrowserParent::SendHandleLongTap(AdjustTapToChildWidget(aPoint), aGuid);
+  return PBrowserParent::SendHandleLongTap(AdjustTapToChildWidget(aPoint), aGuid, aInputBlockId);
 }
 
 bool TabParent::SendHandleLongTapUp(const CSSPoint& aPoint, const ScrollableLayerGuid& aGuid)
 {
   if (mIsDestroyed) {
     return false;
   }
 
@@ -940,17 +941,17 @@ bool TabParent::SendHandleDoubleTap(cons
   return PBrowserParent::SendHandleDoubleTap(AdjustTapToChildWidget(aPoint), aGuid);
 }
 
 bool TabParent::SendMouseWheelEvent(WidgetWheelEvent& event)
 {
   if (mIsDestroyed) {
     return false;
   }
-  nsEventStatus status = MaybeForwardEventToRenderFrame(event, nullptr);
+  nsEventStatus status = MaybeForwardEventToRenderFrame(event, nullptr, nullptr);
   if (status == nsEventStatus_eConsumeNoDefault ||
       !MapEventCoordinatesForChildProcess(&event)) {
     return false;
   }
   return PBrowserParent::SendMouseWheelEvent(event);
 }
 
 static void
@@ -994,17 +995,17 @@ TabParent::RecvRequestNativeKeyBindings(
   return true;
 }
 
 bool TabParent::SendRealKeyEvent(WidgetKeyboardEvent& event)
 {
   if (mIsDestroyed) {
     return false;
   }
-  MaybeForwardEventToRenderFrame(event, nullptr);
+  MaybeForwardEventToRenderFrame(event, nullptr, nullptr);
   if (!MapEventCoordinatesForChildProcess(&event)) {
     return false;
   }
 
 
   MaybeNativeKeyBinding bindings;
   bindings = void_t();
   if (event.message == NS_KEY_PRESS) {
@@ -1062,27 +1063,28 @@ bool TabParent::SendRealTouchEvent(Widge
     for (int i = event.touches.Length() - 1; i >= 0; i--) {
       if (!event.touches[i]->mChanged) {
         event.touches.RemoveElementAt(i);
       }
     }
   }
 
   ScrollableLayerGuid guid;
-  nsEventStatus status = MaybeForwardEventToRenderFrame(event, &guid);
+  uint64_t blockId;
+  nsEventStatus status = MaybeForwardEventToRenderFrame(event, &guid, &blockId);
 
   if (status == nsEventStatus_eConsumeNoDefault || mIsDestroyed) {
     return false;
   }
 
   MapEventCoordinatesForChildProcess(mChildProcessOffsetAtTouchStart, &event);
 
   return (event.message == NS_TOUCH_MOVE) ?
-    PBrowserParent::SendRealTouchMoveEvent(event, guid) :
-    PBrowserParent::SendRealTouchEvent(event, guid);
+    PBrowserParent::SendRealTouchMoveEvent(event, guid, blockId) :
+    PBrowserParent::SendRealTouchEvent(event, guid, blockId);
 }
 
 /*static*/ TabParent*
 TabParent::GetEventCapturer()
 {
   return sEventCapturer;
 }
 
@@ -2005,20 +2007,21 @@ TabParent::UseAsyncPanZoom()
 {
   bool usingOffMainThreadCompositing = !!CompositorParent::CompositorLoop();
   return (usingOffMainThreadCompositing && gfxPrefs::AsyncPanZoomEnabled() &&
           GetScrollingBehavior() == ASYNC_PAN_ZOOM);
 }
 
 nsEventStatus
 TabParent::MaybeForwardEventToRenderFrame(WidgetInputEvent& aEvent,
-                                          ScrollableLayerGuid* aOutTargetGuid)
+                                          ScrollableLayerGuid* aOutTargetGuid,
+                                          uint64_t* aOutInputBlockId)
 {
   if (RenderFrameParent* rfp = GetRenderFrame()) {
-    return rfp->NotifyInputEvent(aEvent, aOutTargetGuid);
+    return rfp->NotifyInputEvent(aEvent, aOutTargetGuid, aOutInputBlockId);
   }
   return nsEventStatus_eIgnore;
 }
 
 bool
 TabParent::RecvBrowserFrameOpenWindow(PBrowserParent* aOpener,
                                       const nsString& aURL,
                                       const nsString& aName,
@@ -2062,20 +2065,21 @@ TabParent::RecvUpdateZoomConstraints(con
   if (RenderFrameParent* rfp = GetRenderFrame()) {
     rfp->UpdateZoomConstraints(aPresShellId, aViewId, aIsRoot, aConstraints);
   }
   return true;
 }
 
 bool
 TabParent::RecvContentReceivedTouch(const ScrollableLayerGuid& aGuid,
+                                    const uint64_t& aInputBlockId,
                                     const bool& aPreventDefault)
 {
   if (RenderFrameParent* rfp = GetRenderFrame()) {
-    rfp->ContentReceivedTouch(aGuid, aPreventDefault);
+    rfp->ContentReceivedTouch(aGuid, aInputBlockId, aPreventDefault);
   }
   return true;
 }
 
 already_AddRefed<nsILoadContext>
 TabParent::GetLoadContext()
 {
   nsCOMPtr<nsILoadContext> loadContext;
--- a/dom/ipc/TabParent.h
+++ b/dom/ipc/TabParent.h
@@ -205,16 +205,17 @@ public:
     virtual bool RecvZoomToRect(const uint32_t& aPresShellId,
                                 const ViewID& aViewId,
                                 const CSSRect& aRect) MOZ_OVERRIDE;
     virtual bool RecvUpdateZoomConstraints(const uint32_t& aPresShellId,
                                            const ViewID& aViewId,
                                            const bool& aIsRoot,
                                            const ZoomConstraints& aConstraints) MOZ_OVERRIDE;
     virtual bool RecvContentReceivedTouch(const ScrollableLayerGuid& aGuid,
+                                          const uint64_t& aInputBlockId,
                                           const bool& aPreventDefault) MOZ_OVERRIDE;
 
     virtual PColorPickerParent*
     AllocPColorPickerParent(const nsString& aTitle, const nsString& aInitialColor) MOZ_OVERRIDE;
     virtual bool DeallocPColorPickerParent(PColorPickerParent* aColorPicker) MOZ_OVERRIDE;
 
     void LoadURL(nsIURI* aURI);
     // XXX/cjones: it's not clear what we gain by hiding these
@@ -228,17 +229,18 @@ public:
     void HandleDoubleTap(const CSSPoint& aPoint,
                          int32_t aModifiers,
                          const ScrollableLayerGuid& aGuid);
     void HandleSingleTap(const CSSPoint& aPoint,
                          int32_t aModifiers,
                          const ScrollableLayerGuid& aGuid);
     void HandleLongTap(const CSSPoint& aPoint,
                        int32_t aModifiers,
-                       const ScrollableLayerGuid& aGuid);
+                       const ScrollableLayerGuid& aGuid,
+                       uint64_t aInputBlockId);
     void HandleLongTapUp(const CSSPoint& aPoint,
                          int32_t aModifiers,
                          const ScrollableLayerGuid& aGuid);
     void NotifyAPZStateChange(ViewID aViewId,
                               APZStateChange aChange,
                               int aArg);
     void Activate();
     void Deactivate();
@@ -256,17 +258,17 @@ public:
     void SendKeyEvent(const nsAString& aType, int32_t aKeyCode,
                       int32_t aCharCode, int32_t aModifiers,
                       bool aPreventDefault);
     bool SendRealMouseEvent(mozilla::WidgetMouseEvent& event);
     bool SendMouseWheelEvent(mozilla::WidgetWheelEvent& event);
     bool SendRealKeyEvent(mozilla::WidgetKeyboardEvent& event);
     bool SendRealTouchEvent(WidgetTouchEvent& event);
     bool SendHandleSingleTap(const CSSPoint& aPoint, const ScrollableLayerGuid& aGuid);
-    bool SendHandleLongTap(const CSSPoint& aPoint, const ScrollableLayerGuid& aGuid);
+    bool SendHandleLongTap(const CSSPoint& aPoint, const ScrollableLayerGuid& aGuid, const uint64_t& aInputBlockId);
     bool SendHandleLongTapUp(const CSSPoint& aPoint, const ScrollableLayerGuid& aGuid);
     bool SendHandleDoubleTap(const CSSPoint& aPoint, const ScrollableLayerGuid& aGuid);
 
     virtual PDocumentRendererParent*
     AllocPDocumentRendererParent(const nsRect& documentRect,
                                  const gfx::Matrix& transform,
                                  const nsString& bgcolor,
                                  const uint32_t& renderFlags,
@@ -418,18 +420,22 @@ private:
     bool UseAsyncPanZoom();
     // If we have a render frame currently, notify it that we're about
     // to dispatch |aEvent| to our child.  If there's a relevant
     // transform in place, |aEvent| will be transformed in-place so that
     // it is ready to be dispatched to content.
     // |aOutTargetGuid| will contain the identifier
     // of the APZC instance that handled the event. aOutTargetGuid may be
     // null.
+    // |aOutInputBlockId| will contain the identifier of the input block
+    // that this event was added to, if there was one. aOutInputBlockId may
+    // be null.
     nsEventStatus MaybeForwardEventToRenderFrame(WidgetInputEvent& aEvent,
-                                                 ScrollableLayerGuid* aOutTargetGuid);
+                                                 ScrollableLayerGuid* aOutTargetGuid,
+                                                 uint64_t* aOutInputBlockId);
     // The offset for the child process which is sampled at touch start. This
     // means that the touch events are relative to where the frame was at the
     // start of the touch. We need to look for a better solution to this
     // problem see bug 872911.
     LayoutDeviceIntPoint mChildProcessOffsetAtTouchStart;
     // When true, we've initiated normal shutdown and notified our
     // managing PContent.
     bool mMarkedDestroying;
--- a/dom/plugins/base/nsPluginsDirDarwin.cpp
+++ b/dom/plugins/base/nsPluginsDirDarwin.cpp
@@ -19,16 +19,19 @@
 #include "nsXPCOM.h"
 
 #include "nsPluginsDir.h"
 #include "nsNPAPIPlugin.h"
 #include "nsPluginsDirUtils.h"
 
 #include "nsILocalFileMac.h"
 
+#include "nsCocoaFeatures.h"
+#include "nsExceptionHandler.h"
+
 #include <string.h>
 #include <stdio.h>
 #include <unistd.h>
 #include <fcntl.h>
 
 #include <Carbon/Carbon.h>
 #include <CoreServices/CoreServices.h>
 #include <mach-o/loader.h>
@@ -464,23 +467,46 @@ nsresult nsPluginFile::GetPluginInfo(nsP
   // First look for data in a bundle plist
   if (bundle) {
     ParsePlistPluginInfo(info, bundle);
     ::CFRelease(bundle);
     if (info.fVariantCount > 0)
       return NS_OK;
   }
 
+  // Don't load "fbplugin" (a Facebook plugin) if we're running on OS X 10.10
+  // (Yosemite) or later.  It crashes on load, in the call to LoadPlugin()
+  // below.  See bug 1086977.
+  if (nsCocoaFeatures::OnYosemiteOrLater()) {
+    if (fileName.EqualsLiteral("fbplugin")) {
+      NS_WARNING("Preventing load of fbplugin (see bug 1086977)");
+      return NS_ERROR_FAILURE;
+    }
+    // The block above assumes that "fbplugin" is the filename of the plugin
+    // to be blocked, but be don't yet know for sure if this is true.  It might
+    // also be the name of a file indirectly loaded by the plugin.  So for the
+    // time being we must record extra information in our crash logs.
+    CrashReporter::AnnotateCrashReport(NS_LITERAL_CSTRING("Bug 1086977"),
+                                       fileName);
+  }
+
   // It's possible that our plugin has 2 entry points that'll give us mime type
   // info. Quicktime does this to get around the need of having admin rights to
   // change mime info in the resource fork. We need to use this info instead of
   // the resource. See bug 113464.
 
   // Sadly we have to load the library for this to work.
   rv = LoadPlugin(outLibrary);
+  if (nsCocoaFeatures::OnYosemiteOrLater()) {
+    // If we didn't crash in LoadPlugin(), change the previous annotation so we
+    // don't sow confusion.  Unfortunately there's not (yet) any way to get rid
+    // of the annotation completely.
+    CrashReporter::AnnotateCrashReport(NS_LITERAL_CSTRING("Bug 1086977"),
+                                       NS_LITERAL_CSTRING("Didn't crash, please ignore"));
+  }
   if (NS_FAILED(rv))
     return rv;
 
   // Try to get data from NP_GetMIMEDescription
   if (pLibrary) {
     NP_GETMIMEDESCRIPTION pfnGetMimeDesc = (NP_GETMIMEDESCRIPTION)PR_FindFunctionSymbol(pLibrary, NP_GETMIMEDESCRIPTION_NAME); 
     if (pfnGetMimeDesc)
       ParsePluginMimeDescription(pfnGetMimeDesc(), info);
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..df421453c25631353cfe86bde5973fdca15b1ea9
GIT binary patch
literal 91
zc%17D@N?(olHy`uVBq!ia0vp^DIm<q3?#jD*u{YqbAV5XtC5k>QX|f*K(4T-i(`m{
lWU>V7;slYNra*^q1_ss&2F5$COwvGU22WQ%mvv4FO#rqO5n=!U
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..6f76d4438724111983a11860f13568361b52d9bc
GIT binary patch
literal 100
zc%17D@N?(olHy`uVBq!ia0vp^CqS5y8Awi_W^)%vF$egBxTd6}EPfSw1jv=~ba4!k
rkbHZv5y)XUyx@1R|3ikWAU1;^qeuV)<1IPC3=q%L)z4*}Q$iB}r??p{
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..144a2f0b93c5913872f30056999da000ae6049cd
GIT binary patch
literal 85
zc%17D@N?(olHy`uVBq!ia0vp^Mj*_{3?x-PN__%S%mF?ju9A|H-p`i&1*>y$43Usb
dmSAlLYGa(hz<BW6Dm$PAgQu&X%Q~loCIF<=5=;O9
--- a/dom/tests/mochitest/general/mochitest.ini
+++ b/dom/tests/mochitest/general/mochitest.ini
@@ -6,16 +6,19 @@ support-files =
   file_clonewrapper.html
   file_domWindowUtils_scrollbarSize.html
   file_frameElementWrapping.html
   file_interfaces.xml
   file_moving_nodeList.html
   file_moving_xhr.html
   file_showModalDialog.html
   historyframes.html
+  image_50.png
+  image_100.png
+  image_200.png
   resource_timing_iframe.html
   resource_timing_main_test.html
   resource_timing_cross_origin.html
   res0.resource
   res1.resource
   res1.resource^headers^
   res2.resource
   res2.resource^headers^
@@ -56,27 +59,29 @@ skip-if = buildapp == 'mulet'
 [test_focus_legend_noparent.html]
 [test_focusrings.xul]
 skip-if = e10s || buildapp == 'mulet' || buildapp == 'b2g' || toolkit == 'android' #TIMED_OUT
 [test_for_of.html]
 [test_frameElementWrapping.html]
 [test_framedhistoryframes.html]
 [test_idleapi_permissions.html]
 skip-if = e10s || buildapp == 'b2g' || buildapp == 'mulet'
+[test_img_mutations.html]
 [test_interfaces.html]
 skip-if = ((buildapp == 'mulet' || buildapp == 'b2g') && toolkit != 'gonk') #Bug 931116, b2g desktop specific, initial triage
 # [test_network_events.html]
 # Disable this test until bug 795711 is fixed.
 [test_offsets.html]
 [test_offsets.js]
 [test_outerHTML.html]
 [test_outerHTML.xhtml]
 skip-if = buildapp == 'mulet'
 [test_paste_selection.html]
 skip-if = buildapp == 'mulet'
+[test_picture_mutations.html]
 [test_picture_pref.html]
 [test_resource_timing.html]
 skip-if = buildapp == 'b2g' || buildapp == 'mulet'
 [test_resource_timing_cross_origin.html]
 skip-if = buildapp == 'b2g' || buildapp == 'mulet'
 [test_performance_now.html]
 [test_srcset_pref.html]
 [test_showModalDialog.html]
new file mode 100644
--- /dev/null
+++ b/dom/tests/mochitest/general/test_img_mutations.html
@@ -0,0 +1,227 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+  <meta charset="utf-8">
+  <title>Image srcset mutations</title>
+  <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+  <script type="application/javascript">
+    "use strict";
+
+    // Tests the relevant mutations part of the spec for img src and srcset
+    // and that img.src still behaves by the older spec. (Bug 1076583)
+    // https://html.spec.whatwg.org/#relevant-mutations
+    SimpleTest.waitForExplicitFinish();
+
+    // 50x50 png
+    var testPNG50 = new URL("image_50.png", location).href;
+    // 100x100 png
+    var testPNG100 = new URL("image_100.png", location).href;
+    // 200x200 png
+    var testPNG200 = new URL("image_200.png", location).href;
+
+    var tests = [];
+    var img;
+    var expectingErrors = 0;
+    var expectingLoads = 0;
+    var afterExpectCallback;
+
+    function onImgLoad() {
+      ok(expectingLoads > 0, "expected load");
+      if (expectingLoads > 0) {
+        expectingLoads--;
+      }
+      if (!expectingLoads && !expectingErrors) {
+        setTimeout(afterExpectCallback, 0);
+      }
+    }
+    function onImgError() {
+      ok(expectingErrors > 0, "expected error");
+      if (expectingErrors > 0) {
+        expectingErrors--;
+      }
+      if (!expectingLoads && !expectingErrors) {
+        setTimeout(afterExpectCallback, 0);
+      }
+    }
+    function expectEvents(loads, errors, callback) {
+      if (!loads && !errors) {
+        setTimeout(callback, 0);
+      } else {
+        expectingLoads += loads;
+        expectingErrors += errors;
+        info("Waiting for " + expectingLoads + " load and " + expectingErrors + " error events");
+        afterExpectCallback = callback;
+      }
+    }
+
+    //
+    // Test that img.src still does some work synchronously per the older spec (bug 1076583)
+    //
+    tests.push(function test1() {
+      info("test 1");
+      img.src = testPNG50;
+      is(img.currentSrc, testPNG50, "Should have synchronously selected source");
+
+      img.src = "non_existent_image.404";
+      ok(img.currentSrc.endsWith("non_existent_image.404"), "Should have synchronously selected source");
+
+      img.removeAttribute("src");
+      is(img.currentSrc, null, "Should have dropped currentSrc");
+
+      // Load another image while previous load is still pending
+      img.src = testPNG200;
+      is(img.currentSrc, testPNG200, "Should have synchronously selected source");
+
+      // No events should have fired synchronously, now we should get just one load (and no 404 error)
+      expectEvents(1, 0, nextTest);
+    });
+
+
+    // Setting srcset should be async
+    tests.push(function () {
+      info("test 2");
+      img.srcset = testPNG100;
+      is(img.currentSrc, testPNG200, "Should still have testPNG200 as current request");
+
+      expectEvents(1, 0, function() {
+        is(img.currentSrc, testPNG100, "Should now have testPNG100 as current request");
+        nextTest();
+      });
+    });
+
+    // Setting srcset, even to no ultimate effect, should trigger a reload
+    tests.push(function () {
+      info("test 3");
+      img.srcset = testPNG100 + " 1x, " + testPNG200 + " 2x";
+      is(img.currentSrc, testPNG100, "Should still have testPNG100 as current request");
+
+      expectEvents(1, 0, function() {
+        is(img.currentSrc, testPNG100, "Should still have testPNG100 as current request");
+        nextTest();
+      });
+    });
+
+    // Should switch to src as 1x source
+    tests.push(function () {
+      info("test 4");
+      img.srcset = testPNG50 + " 2x";
+      is(img.currentSrc, testPNG100, "Should still have testPNG100 as current request");
+
+      expectEvents(1, 0, function() {
+        is(img.currentSrc, testPNG200, "Should now have testPNG200 as current request");
+        nextTest();
+      });
+    });
+
+    // Changing src while we have responsive attributes should not be sync
+    tests.push(function () {
+      info("test 5");
+      img.src = testPNG100;
+      is(img.currentSrc, testPNG200, "Should still have testPNG200 as current request");
+
+      expectEvents(1, 0, function() {
+        is(img.currentSrc, testPNG100, "Should now have testPNG100 as current request");
+
+        // Switch to using srcset again for next test
+        img.srcset = testPNG100;
+        expectEvents(1, 0, nextTest);
+      });
+    });
+
+    // img.src = img.src should trigger an async event even in responsive mode
+    tests.push(function () {
+      info("test 6");
+      is(img.currentSrc, testPNG100, "Should now have testPNG100 as current request");
+      img.src = img.src;
+      is(img.currentSrc, testPNG100, "Should still have testPNG100 as current request");
+
+      expectEvents(1, 0, nextTest);
+    });
+
+    // img.srcset = img.srcset should be a no-op
+    tests.push(function () {
+      info("test 7");
+      img.srcset = img.srcset;
+      is(img.currentSrc, testPNG100, "Should still have testPNG100 as current request");
+
+      expectEvents(0, 0, nextTest);
+    });
+
+    // Re-binding image to document should be a no-op
+    tests.push(function () {
+      info("test 8");
+      document.body.appendChild(img);
+      is(img.currentSrc, testPNG100, "Should still have testPNG100 as current request");
+
+      expectEvents(0, 0, nextTest);
+    });
+
+    // We should re-run our selection algorithm when any load event occurs
+    tests.push(function () {
+      info("test 9");
+      img.srcset = testPNG50 + " 1x, " + testPNG200 + " 2x";
+      is(img.currentSrc, testPNG100, "Should still have testPNG100 as current request");
+
+      expectEvents(1, 0, function() {
+        is(img.currentSrc, testPNG50, "Should now have testPNG50 as current request");
+        SpecialPowers.pushPrefEnv({'set': [ ["layout.css.devPixelsPerPx", "2.0"] ] },
+                                  function() {
+          // We don't currently dynamically switch, but doing so is
+          // up to the UA so we may in the future. In which case
+          // this test needs to be changed.
+          is(img.currentSrc, testPNG50, "Should now have testPNG50 as current request");
+          img.src = img.src;
+          is(img.currentSrc, testPNG50, "Should still have testPNG50 as current request");
+          expectEvents(1, 0, function() {
+            is(img.currentSrc, testPNG200, "Should now have testPNG200 as current request");
+            nextTest();
+          });
+        });
+      });
+    });
+
+    // Removing srcset attr should async switch back to src
+    tests.push(function () {
+      info("test 10");
+      is(img.currentSrc, testPNG200, "Should have testPNG200 as current request");
+
+      img.removeAttribute("srcset");
+      is(img.currentSrc, testPNG200, "Should still have testPNG200 as current request");
+
+      expectEvents(1, 0, function() {
+        is(img.currentSrc, testPNG100, "Should now have testPNG100 as current request");
+
+        expectEvents(0, 0, nextTest);
+      });
+    });
+
+    function nextTest() {
+      if (tests.length) {
+        // Spin event loop to make sure no unexpected image events are
+        // pending (unexpected events will assert in the handlers)
+        setTimeout(function() {
+          (tests.shift())();
+        }, 0);
+      } else {
+        SimpleTest.finish();
+      }
+    }
+
+    addEventListener("load", function() {
+      SpecialPowers.pushPrefEnv({'set': [ ["layout.css.devPixelsPerPx", "1.0"],
+                                          [ "dom.image.srcset.enabled", true ]] },
+                                function() {
+        // Create this after the pref is set, as it is guarding webIDL attributes
+        img = document.createElement("img");
+        img.addEventListener("load", onImgLoad);
+        img.addEventListener("error", onImgError);
+        document.body.appendChild(img);
+        setTimeout(nextTest, 0);
+      });
+    });
+  </script>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/dom/tests/mochitest/general/test_picture_mutations.html
@@ -0,0 +1,308 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+  <meta charset="utf-8">
+  <title>Image srcset mutations</title>
+  <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+  <script type="application/javascript">
+    "use strict";
+
+    // Tests the relevant mutations part of the spec for <img> inside <picture> tags
+    // https://html.spec.whatwg.org/#relevant-mutations
+    SimpleTest.waitForExplicitFinish();
+
+    // 50x50 png
+    var testPNG50 = new URL("image_50.png", location).href;
+    // 100x100 png
+    var testPNG100 = new URL("image_100.png", location).href;
+    // 200x200 png
+    var testPNG200 = new URL("image_200.png", location).href;
+
+    var tests = [];
+    var img;
+    var picture;
+    var source1;
+    var source2;
+    var source3;
+    var expectingErrors = 0;
+    var expectingLoads = 0;
+    var afterExpectCallback;
+
+    function onImgLoad() {
+      ok(expectingLoads > 0, "expected load");
+      if (expectingLoads > 0) {
+        expectingLoads--;
+      }
+      if (!expectingLoads && !expectingErrors) {
+        setTimeout(afterExpectCallback, 0);
+      }
+    }
+    function onImgError() {
+      ok(expectingErrors > 0, "expected error");
+      if (expectingErrors > 0) {
+        expectingErrors--;
+      }
+      if (!expectingLoads && !expectingErrors) {
+        setTimeout(afterExpectCallback, 0);
+      }
+    }
+    function expectEvents(loads, errors, callback) {
+      if (!loads && !errors) {
+        setTimeout(callback, 0);
+      } else {
+        expectingLoads += loads;
+        expectingErrors += errors;
+        info("Waiting for " + expectingLoads + " load and " + expectingErrors + " error events");
+        afterExpectCallback = callback;
+      }
+    }
+
+    // Setup image outside the tree dom, make sure it loads
+    tests.push(function() {
+      info("test 1");
+      img.srcset = testPNG100;
+      img.src = testPNG50;
+      is(img.currentSrc, null, "Should not have synchronously selected source");
+
+      // No events should have fired synchronously, now we should get just one load (and no 404 error)
+      expectEvents(1, 0, nextTest);
+    });
+
+    // Binding to an empty picture should trigger an event, even if source doesn't change
+    tests.push(function() {
+      info("test 2");
+      is(img.currentSrc, testPNG100, "Should have loaded testPNG100");
+      document.body.appendChild(picture);
+      picture.appendChild(img);
+      is(img.currentSrc, testPNG100, "Should still have testPNG100");
+
+      expectEvents(1, 0, nextTest);
+    });
+
+    // inserting and removing an empty source before the image should both trigger a no-op reload
+    tests.push(function() {
+      info("test 3");
+      is(img.currentSrc, testPNG100, "Should still have testPNG100");
+      picture.insertBefore(source1, img);
+      is(img.currentSrc, testPNG100, "Should still have testPNG100");
+
+      // should fire one event, not change source
+      expectEvents(1, 0, function() {
+        is(img.currentSrc, testPNG100, "Should still have testPNG100");
+        picture.removeChild(source1);
+        is(img.currentSrc, testPNG100, "Should still have testPNG100");
+
+        // Should also no-op fire
+        expectEvents(1, 0, nextTest);
+      });
+    });
+
+    // insert and remove valid source before
+    tests.push(function() {
+      info("test 4");
+      is(img.currentSrc, testPNG100, "Should still have testPNG100");
+
+      // Insert source1 before img with valid candidate
+      source1.srcset = testPNG50;
+      picture.insertBefore(source1, img);
+      is(img.currentSrc, testPNG100, "Should still have testPNG100");
+
+      // should fire one event, change to the source
+      expectEvents(1, 0, function() {
+        is(img.currentSrc, testPNG50, "Should have switched to testPNG50");
+        picture.removeChild(source1);
+        is(img.currentSrc, testPNG50, "Should still have testPNG50");
+
+        // Should also no-op fire
+        expectEvents(1, 0, function() {
+          is(img.currentSrc, testPNG100, "Should have returned to testPNG100");
+          nextTest();
+        });
+      });
+    });
+
+    // insert and remove valid source after
+    tests.push(function() {
+      info("test 5");
+      is(img.currentSrc, testPNG100, "Should still have testPNG100");
+
+      // Insert source1 before img with valid candidate
+      source1.srcset = testPNG50;
+      picture.appendChild(source1);
+      is(img.currentSrc, testPNG100, "Should still have testPNG100");
+
+      // should fire nothing, no action
+      expectEvents(0, 0, function() {
+        is(img.currentSrc, testPNG100, "Should still have testPNG100");
+
+        // Same with removing
+        picture.removeChild(source1);
+        expectEvents(0, 0, function() {
+          is(img.currentSrc, testPNG100, "Should still have testPNG100");
+          nextTest();
+        });
+      });
+    });
+
+    // Should re-consider earlier sources when a load event occurs.
+    tests.push(function() {
+      info("test 6");
+
+      // Insert two valid sources, with MQ causing us to select the second
+      source1.srcset = testPNG50 + " 1x";
+      source1.media = "(min-resolution: 2dppx)"; // Wont match, test starts at 1x
+      source2.srcset = testPNG200;
+      picture.insertBefore(source1, img);
+      picture.insertBefore(source2, img);
+      is(img.currentSrc, testPNG100, "Should still have testPNG100");
+
+      // should get one load, selecting source2
+      expectEvents(1, 0, function() {
+        is(img.currentSrc, testPNG200, "Should have selected testPNG200");
+
+        // Switch DPI to match the first source, then add a source
+        // *also* wanting that DPI *just before* the selected
+        // source. Properly re-running the algorithm should
+        // re-consider all sources and thus go back to the first
+        // source, not just the valid source just inserted before us.
+        SpecialPowers.pushPrefEnv({'set': [ ["layout.css.devPixelsPerPx", "2.0"] ] },
+                                  function() {
+          // When we add dynamic reaction to MQs, this test will need to be updated.
+          is(img.currentSrc, testPNG200, "Should still have testPNG200");
+          source3.media = source1.media;
+          source3.srcset = testPNG100;
+          picture.insertBefore(source3, source2);
+          // This should trigger a reload, but we should re-consider
+          // source1 and select that, not just the newly added source2
+          expectEvents(1, 0, function() {
+            is(img.currentSrc, testPNG50, "Should have selected testPNG50");
+            expectEvents(0, 0, nextTest);
+          });
+        });
+      });
+    });
+
+    // insert and remove valid source after our current source should
+    // trigger a reload, but not switch source
+    tests.push(function() {
+      info("test 7");
+      // Should be using source1 from last test
+      is(img.currentSrc, testPNG50, "Should still have testPNG50");
+
+      // Remove source2, should trigger an event even though we would
+      // not switch
+
+      picture.removeChild(source2);
+      expectEvents(1, 0, function() {
+        is(img.currentSrc, testPNG50, "Should still have testPNG50");
+
+        // Same with re-adding
+        picture.insertBefore(source2, img);
+        expectEvents(1, 0, function() {
+          is(img.currentSrc, testPNG50, "Should still have testPNG50");
+          expectEvents(0, 0, nextTest);
+        });
+      });
+    });
+
+    // Changing source attributes should trigger updates
+    tests.push(function() {
+      info("test 8");
+      // Should be using source1 from last test
+      is(img.currentSrc, testPNG50, "Should still have testPNG50");
+
+      // Reconfigure source1 to have empty srcset. Should switch to
+      // next source due to becoming invalid.
+      source1.srcset = "";
+      is(img.currentSrc, testPNG50, "Should still have testPNG50");
+
+      expectEvents(1, 0, function() {
+        is(img.currentSrc, testPNG100, "Should have switched to testPNG100");
+
+        // Give source1 valid sizes. Should trigger an event but not switch anywhere.
+        source1.sizes = "100vw";
+
+        expectEvents(1, 0, function() {
+          is(img.currentSrc, testPNG100, "Should still have testPNG100");
+
+          // And a valid MQ
+          source1.media = "(min-resolution: 3dppx)";
+          expectEvents(1, 0, function() {
+            // And a valid type...
+            source1.type = "image/png";
+            expectEvents(1, 0, function() {
+              // Finally restore srcset, should trigger load and re-consider source1 which is valid again
+              source1.srcset = testPNG50;
+              expectEvents(1, 0, function() {
+                is(img.currentSrc, testPNG50, "Should have selected testPNG50");
+                expectEvents(0, 0, nextTest);
+              });
+            });
+          });
+        });
+      });
+    });
+
+    // Inserting a source after <img> and touching its attributes should all be no-ops
+    tests.push(function() {
+      info("test 9");
+      // Setup: source2
+      picture.removeChild(source2);
+
+      expectEvents(1, 0, function() {
+        is(img.currentSrc, testPNG50, "Should still have testPNG50");
+
+        source2.srcset = testPNG200;
+        source2.media = "";
+        source2.sizes = "100vw";
+        source2.type = "image/png";
+        // Append valid source
+        picture.appendChild(source2);
+        // Touch all the things (but keep it valid)
+        source2.srcset = testPNG100;
+        source2.media = "(min-resolution: 2dppx)";
+        source2.sizes = "50vw";
+        source2.type = "image/png";
+
+        // No event should fire. Source should not change.
+        expectEvents(0, 0, function() {
+          is(img.currentSrc, testPNG50, "Should still have testPNG50");
+          expectEvents(0, 0, nextTest);
+        });
+      });
+    });
+
+    function nextTest() {
+      if (tests.length) {
+        // Spin event loop to make sure no unexpected image events are
+        // pending (unexpected events will assert in the handlers)
+        setTimeout(function() {
+          (tests.shift())();
+        }, 0);
+      } else {
+        SimpleTest.finish();
+      }
+    }
+
+    addEventListener("load", function() {
+      SpecialPowers.pushPrefEnv({'set': [ ["layout.css.devPixelsPerPx", "1.0" ],
+                                          [ "dom.image.srcset.enabled",  true ],
+                                          [ "dom.image.picture.enabled", true ]] },
+                                function() {
+        // Create these after the pref is set, as it is guarding webIDL attributes
+        img = document.createElement("img");
+        img.addEventListener("load", onImgLoad);
+        img.addEventListener("error", onImgError);
+        picture = document.createElement("picture");
+        source1 = document.createElement("source");
+        source2 = document.createElement("source");
+        source3 = document.createElement("source");
+        setTimeout(nextTest, 0);
+      });
+    });
+  </script>
+</body>
+</html>
--- a/dom/webidl/HTMLImageElement.webidl
+++ b/dom/webidl/HTMLImageElement.webidl
@@ -98,14 +98,14 @@ interface MozImageLoadingContent {
   imgIRequest? getRequest(long aRequestType);
   [ChromeOnly,Throws]
   long getRequestType(imgIRequest aRequest);
   [ChromeOnly,Throws]
   readonly attribute URI? currentURI;
   [ChromeOnly,Throws]
   nsIStreamListener? loadImageWithChannel(MozChannel aChannel);
   [ChromeOnly,Throws]
-  void forceReload();
+  void forceReload(optional boolean aNotify);
   [ChromeOnly]
   void forceImageState(boolean aForce, unsigned long long aState);
 };
 
 HTMLImageElement implements MozImageLoadingContent;
--- a/embedding/browser/nsContextMenuInfo.cpp
+++ b/embedding/browser/nsContextMenuInfo.cpp
@@ -270,17 +270,17 @@ nsContextMenuInfo::GetBackgroundImageReq
   nsCOMPtr<nsIDocument> doc(do_QueryInterface(document));
   nsCOMPtr<nsIPrincipal> principal = doc ? doc->NodePrincipal() : nullptr;
 
   while (true) {
     nsCOMPtr<nsIDOMElement> domElement(do_QueryInterface(domNode));
     // bail for the parent node of the root element or null argument
     if (!domElement)
       break;
-    
+
     nsCOMPtr<nsIDOMCSSStyleDeclaration> computedStyle;
     window->GetComputedStyle(domElement, EmptyString(),
                              getter_AddRefs(computedStyle));
     if (computedStyle) {
       nsCOMPtr<nsIDOMCSSValue> cssValue;
       computedStyle->GetPropertyCSSValue(NS_LITERAL_STRING("background-image"),
                                          getter_AddRefs(cssValue));
       primitiveValue = do_QueryInterface(cssValue);
@@ -291,17 +291,18 @@ nsContextMenuInfo::GetBackgroundImageReq
           NS_NewURI(getter_AddRefs(bgUri), bgStringValue);
           NS_ENSURE_TRUE(bgUri, NS_ERROR_FAILURE);
 
           nsRefPtr<imgLoader> il = imgLoader::GetInstance();
           NS_ENSURE_TRUE(il, NS_ERROR_FAILURE);
 
           return il->LoadImage(bgUri, nullptr, nullptr, principal, nullptr,
                                nullptr, nullptr, nsIRequest::LOAD_NORMAL,
-                               nullptr, EmptyString(), aRequest);
+                               nullptr, nsIContentPolicy::TYPE_IMAGE,
+                               EmptyString(), aRequest);
         }
       }
 
       // bail if we encounter non-transparent background-color
       computedStyle->GetPropertyCSSValue(NS_LITERAL_STRING("background-color"),
                                          getter_AddRefs(cssValue));
       primitiveValue = do_QueryInterface(cssValue);
       if (primitiveValue) {
--- a/embedding/browser/nsWebBrowserContentPolicy.cpp
+++ b/embedding/browser/nsWebBrowserContentPolicy.cpp
@@ -37,17 +37,17 @@ nsWebBrowserContentPolicy::ShouldLoad(ui
 
     *shouldLoad = nsIContentPolicy::ACCEPT;
 
     nsIDocShell *shell = NS_CP_GetDocShellFromContext(requestingContext);
     /* We're going to dereference shell, so make sure it isn't null */
     if (!shell) {
         return NS_OK;
     }
-    
+
     nsresult rv;
     bool allowed = true;
 
     switch (contentType) {
       case nsIContentPolicy::TYPE_SCRIPT:
         rv = shell->GetAllowJavascript(&allowed);
         break;
       case nsIContentPolicy::TYPE_SUBDOCUMENT:
@@ -55,16 +55,17 @@ nsWebBrowserContentPolicy::ShouldLoad(ui
         break;
 #if 0
       /* XXXtw: commented out in old code; add during conpol phase 2 */
       case nsIContentPolicy::TYPE_REFRESH:
         rv = shell->GetAllowMetaRedirects(&allowed); /* meta _refresh_ */
         break;
 #endif
       case nsIContentPolicy::TYPE_IMAGE:
+      case nsIContentPolicy::TYPE_IMAGESET:
         rv = shell->GetAllowImages(&allowed);
         break;
       default:
         return NS_OK;
     }
 
     if (NS_SUCCEEDED(rv) && !allowed) {
         *shouldLoad = nsIContentPolicy::REJECT_TYPE;
--- a/gfx/2d/2D.h
+++ b/gfx/2d/2D.h
@@ -1186,16 +1186,18 @@ public:
    * drawing is distributed over number of tiles which may each hold an
    * individual offset. The tiles in the set must each have the same backend
    * and format.
    */
   static TemporaryRef<DrawTarget> CreateTiledDrawTarget(const TileSet& aTileSet);
 
 #ifdef XP_MACOSX
   static TemporaryRef<DrawTarget> CreateDrawTargetForCairoCGContext(CGContextRef cg, const IntSize& aSize);
+  static TemporaryRef<GlyphRenderingOptions>
+    CreateCGGlyphRenderingOptions(const Color &aFontSmoothingBackgroundColor);
 #endif
 
 #ifdef WIN32
   static TemporaryRef<DrawTarget> CreateDrawTargetForD3D10Texture(ID3D10Texture2D *aTexture, SurfaceFormat aFormat);
   static TemporaryRef<DrawTarget>
     CreateDualDrawTargetForD3D10Textures(ID3D10Texture2D *aTextureA,
                                          ID3D10Texture2D *aTextureB,
                                          SurfaceFormat aFormat);
--- a/gfx/2d/DrawTargetCG.cpp
+++ b/gfx/2d/DrawTargetCG.cpp
@@ -139,22 +139,29 @@ InterpolationQualityFromFilter(Filter aF
       return kCGInterpolationLow;
   }
 }
 
 
 DrawTargetCG::DrawTargetCG()
   : mColorSpace(nullptr)
   , mCg(nullptr)
+  , mMayContainInvalidPremultipliedData(false)
 {
 }
 
 DrawTargetCG::~DrawTargetCG()
 {
-  MarkChanged();
+  if (mSnapshot) {
+    if (mSnapshot->refCount() > 1) {
+      // We only need to worry about snapshots that someone else knows about
+      mSnapshot->DrawTargetWillGoAway();
+    }
+    mSnapshot = nullptr;
+  }
 
   // Both of these are OK with nullptr arguments, so we do not
   // need to check (these could be nullptr if Init fails)
   CGColorSpaceRelease(mColorSpace);
   CGContextRelease(mCg);
 }
 
 DrawTargetType
@@ -178,16 +185,17 @@ DrawTargetCG::GetBackendType() const
 
 TemporaryRef<SourceSurface>
 DrawTargetCG::Snapshot()
 {
   if (!mSnapshot) {
     if (GetContextType(mCg) == CG_CONTEXT_TYPE_IOSURFACE) {
       return new SourceSurfaceCGIOSurfaceContext(this);
     }
+    Flush();
     mSnapshot = new SourceSurfaceCGBitmapContext(this);
   }
 
   return mSnapshot;
 }
 
 TemporaryRef<DrawTarget>
 DrawTargetCG::CreateSimilarDrawTarget(const IntSize &aSize, SurfaceFormat aFormat) const
@@ -1160,26 +1168,69 @@ CGRect ComputeGlyphsExtents(CGRect *bbox
     x2 = max(x2, bboxes[i].origin.x + positions[i].x + scale*bboxes[i].size.width);
     y2 = max(y2, bboxes[i].origin.y + positions[i].y + scale*bboxes[i].size.height);
   }
 
   CGRect extents = {{x1, y1}, {x2-x1, y2-y1}};
   return extents;
 }
 
+typedef void (*CGContextSetFontSmoothingBackgroundColorFunc) (CGContextRef cgContext, CGColorRef color);
+
+static CGContextSetFontSmoothingBackgroundColorFunc
+GetCGContextSetFontSmoothingBackgroundColorFunc()
+{
+  static CGContextSetFontSmoothingBackgroundColorFunc func = nullptr;
+  static bool lookedUpFunc = false;
+  if (!lookedUpFunc) {
+    func = (CGContextSetFontSmoothingBackgroundColorFunc)dlsym(
+      RTLD_DEFAULT, "CGContextSetFontSmoothingBackgroundColor");
+    lookedUpFunc = true;
+  }
+  return func;
+}
 
 void
 DrawTargetCG::FillGlyphs(ScaledFont *aFont, const GlyphBuffer &aBuffer, const Pattern &aPattern, const DrawOptions &aDrawOptions,
-                         const GlyphRenderingOptions*)
+                         const GlyphRenderingOptions *aGlyphRenderingOptions)
 {
   MarkChanged();
 
   assert(aBuffer.mNumGlyphs);
   CGContextSaveGState(mCg);
 
+  if (aGlyphRenderingOptions && aGlyphRenderingOptions->GetType() == FontType::MAC) {
+    Color fontSmoothingBackgroundColor =
+      static_cast<const GlyphRenderingOptionsCG*>(aGlyphRenderingOptions)->FontSmoothingBackgroundColor();
+    if (fontSmoothingBackgroundColor.a > 0) {
+      CGContextSetFontSmoothingBackgroundColorFunc setFontSmoothingBGColorFunc =
+        GetCGContextSetFontSmoothingBackgroundColorFunc();
+      if (setFontSmoothingBGColorFunc) {
+        CGColorRef color = ColorToCGColor(mColorSpace, fontSmoothingBackgroundColor);
+        setFontSmoothingBGColorFunc(mCg, color);
+        CGColorRelease(color);
+
+        // Font rendering with a non-transparent font smoothing background color
+        // can leave pixels in our buffer where the rgb components exceed the alpha
+        // component. When this happens we need to clean up the data afterwards.
+        // The purpose of this is probably the following: Correct compositing of
+        // subpixel anti-aliased fonts on transparent backgrounds requires
+        // different alpha values per RGB component. Usually, premultiplied color
+        // values are derived by multiplying all components with the same per-pixel
+        // alpha value. However, if you multiply each component with a *different*
+        // alpha, and set the alpha component of the pixel to, say, the average
+        // of the alpha values that you used during the premultiplication of the
+        // RGB components, you can trick OVER compositing into doing a simplified
+        // form of component alpha compositing. (You just need to make sure to
+        // clamp the components of the result pixel to [0,255] afterwards.)
+        mMayContainInvalidPremultipliedData = true;
+      }
+    }
+  }
+
   CGContextSetBlendMode(mCg, ToBlendMode(aDrawOptions.mCompositionOp));
   UnboundnessFixer fixer;
   CGContextRef cg = fixer.Check(mCg, aDrawOptions.mCompositionOp);
   CGContextSetAlpha(cg, aDrawOptions.mAlpha);
   CGContextSetShouldAntialias(cg, aDrawOptions.mAntialiasMode != AntialiasMode::NONE);
   if (aDrawOptions.mAntialiasMode != AntialiasMode::DEFAULT) {
     CGContextSetShouldSmoothFonts(cg, aDrawOptions.mAntialiasMode == AntialiasMode::SUBPIXEL);
   }
@@ -1428,21 +1479,64 @@ DrawTargetCG::Init(BackendType aType,
     // reading back the surface. This should trigger something equivilent
     // to glClear.
     ClearRect(Rect(0, 0, mSize.width, mSize.height));
   }
 
   return true;
 }
 
+static void
+EnsureValidPremultipliedData(CGContextRef aContext)
+{
+  if (CGBitmapContextGetBitsPerPixel(aContext) != 32 ||
+      CGBitmapContextGetAlphaInfo(aContext) != kCGImageAlphaPremultipliedFirst) {
+    return;
+  }
+
+  uint8_t* bitmapData = (uint8_t*)CGBitmapContextGetData(aContext);
+  int w = CGBitmapContextGetWidth(aContext);
+  int h = CGBitmapContextGetHeight(aContext);
+  int stride = CGBitmapContextGetBytesPerRow(aContext);
+  for (int y = 0; y < h; y++) {
+    for (int x = 0; x < w; x++) {
+      int i = y * stride + x * 4;
+      uint8_t a = bitmapData[i + 3];
+
+      // Clamp rgb components to the alpha component.
+      if (bitmapData[i + 0] > a) {
+        bitmapData[i + 0] = a;
+      }
+      if (bitmapData[i + 1] > a) {
+        bitmapData[i + 1] = a;
+      }
+      if (bitmapData[i + 2] > a) {
+        bitmapData[i + 2] = a;
+      }
+    }
+  }
+}
+
 void
 DrawTargetCG::Flush()
 {
   if (GetContextType(mCg) == CG_CONTEXT_TYPE_IOSURFACE) {
     CGContextFlush(mCg);
+  } else if (GetContextType(mCg) == CG_CONTEXT_TYPE_BITMAP &&
+             mMayContainInvalidPremultipliedData) {
+    // We can't guarantee that all our users can handle pixel data where an RGB
+    // component value exceeds the pixel's alpha value. In particular, the
+    // color conversion that CG does when we draw a CGImage snapshot of this
+    // context into a context that has a different color space throws up on
+    // invalid premultiplied data and creates completely wrong colors.
+    // Sanitizing the data means that we lose some of the fake component alpha
+    // behavior that font rendering tries to give us, but the result still
+    // looks good enough to prefer it over grayscale font anti-aliasing.
+    EnsureValidPremultipliedData(mCg);
+    mMayContainInvalidPremultipliedData = false;
   }
 }
 
 bool
 DrawTargetCG::Init(CGContextRef cgContext, const IntSize &aSize)
 {
   // XXX: we should come up with some consistent semantics for dealing
   // with zero area drawtargets
@@ -1615,16 +1709,17 @@ DrawTargetCG::MarkChanged()
 
 CGContextRef
 BorrowedCGContext::BorrowCGContextFromDrawTarget(DrawTarget *aDT)
 {
   if ((aDT->GetBackendType() == BackendType::COREGRAPHICS ||
        aDT->GetBackendType() == BackendType::COREGRAPHICS_ACCELERATED) &&
       !aDT->IsTiledDrawTarget() && !aDT->IsDualDrawTarget()) {
     DrawTargetCG* cgDT = static_cast<DrawTargetCG*>(aDT);
+    cgDT->Flush();
     cgDT->MarkChanged();
 
     // swap out the context
     CGContextRef cg = cgDT->mCg;
     cgDT->mCg = nullptr;
 
     // save the state to make it easier for callers to avoid mucking with things
     CGContextSaveGState(cg);
--- a/gfx/2d/DrawTargetCG.h
+++ b/gfx/2d/DrawTargetCG.h
@@ -86,22 +86,39 @@ SetStrokeOptions(CGContextRef cg, const 
     for (size_t i=0; i<aStrokeOptions.mDashLength; i++) {
       dashes[i] = aStrokeOptions.mDashPattern[i];
     }
     CGContextSetLineDash(cg, aStrokeOptions.mDashOffset, dashes, aStrokeOptions.mDashLength);
     delete[] dashes;
   }
 }
 
+class GlyphRenderingOptionsCG : public GlyphRenderingOptions
+{
+public:
+  MOZ_DECLARE_REFCOUNTED_VIRTUAL_TYPENAME(GlyphRenderingOptionsCG)
+
+  GlyphRenderingOptionsCG(const Color &aFontSmoothingBackgroundColor)
+    : mFontSmoothingBackgroundColor(aFontSmoothingBackgroundColor)
+  {}
+
+  const Color &FontSmoothingBackgroundColor() const { return mFontSmoothingBackgroundColor; }
+
+  virtual FontType GetType() const MOZ_OVERRIDE { return FontType::MAC; }
+
+private:
+  Color mFontSmoothingBackgroundColor;
+};
 
 class DrawTargetCG : public DrawTarget
 {
 public:
   MOZ_DECLARE_REFCOUNTED_VIRTUAL_TYPENAME(DrawTargetCG)
   friend class BorrowedCGContext;
+  friend class SourceSurfaceCGBitmapContext;
   DrawTargetCG();
   virtual ~DrawTargetCG();
 
   virtual DrawTargetType GetType() const MOZ_OVERRIDE;
   virtual BackendType GetBackendType() const;
   virtual TemporaryRef<SourceSurface> Snapshot();
 
   virtual void DrawSurface(SourceSurface *aSurface,
@@ -177,15 +194,16 @@ private:
    * The image buffer, if the buffer is owned by this class.
    * If the DrawTarget was created for a pre-existing buffer or if the buffer's
    * lifetime is managed by CoreGraphics, mData will be null.
    * Data owned by DrawTargetCG will be deallocated in the destructor.
    */
   AlignedArray<uint8_t> mData;
 
   RefPtr<SourceSurfaceCGContext> mSnapshot;
+  bool mMayContainInvalidPremultipliedData;
 };
 
 }
 }
 
 #endif
 
--- a/gfx/2d/Factory.cpp
+++ b/gfx/2d/Factory.cpp
@@ -703,16 +703,22 @@ Factory::CreateDrawTargetForCairoCGConte
     retVal = newTarget;
   }
 
   if (mRecorder && retVal) {
     return new DrawTargetRecording(mRecorder, retVal);
   }
   return retVal.forget();
 }
+
+TemporaryRef<GlyphRenderingOptions>
+Factory::CreateCGGlyphRenderingOptions(const Color &aFontSmoothingBackgroundColor)
+{
+  return new GlyphRenderingOptionsCG(aFontSmoothingBackgroundColor);
+}
 #endif
 
 TemporaryRef<DataSourceSurface>
 Factory::CreateWrappingDataSourceSurface(uint8_t *aData, int32_t aStride,
                                          const IntSize &aSize,
                                          SurfaceFormat aFormat)
 {
   if (aSize.width <= 0 || aSize.height <= 0) {
--- a/gfx/2d/SourceSurfaceCG.cpp
+++ b/gfx/2d/SourceSurfaceCG.cpp
@@ -355,16 +355,34 @@ SourceSurfaceCGBitmapContext::DrawTarget
       CGImageRelease(mImage);
     mImage = nullptr;
 
     mCg = nullptr;
     mDrawTarget = nullptr;
   }
 }
 
+void
+SourceSurfaceCGBitmapContext::DrawTargetWillGoAway()
+{
+  if (mDrawTarget) {
+    if (mDrawTarget->mData != CGBitmapContextGetData(mCg)) {
+      DrawTargetWillChange();
+      return;
+    }
+
+    // Instead of copying the data over, we can just swap it.
+    mDataHolder.Swap(mDrawTarget->mData);
+    mData = mDataHolder;
+    mCg = nullptr;
+    mDrawTarget = nullptr;
+    // mImage is still valid because it still points to the same data.
+  }
+}
+
 SourceSurfaceCGBitmapContext::~SourceSurfaceCGBitmapContext()
 {
   if (mImage)
     CGImageRelease(mImage);
 }
 
 SourceSurfaceCGIOSurfaceContext::SourceSurfaceCGIOSurfaceContext(DrawTargetCG *aDrawTarget)
 {
--- a/gfx/2d/SourceSurfaceCG.h
+++ b/gfx/2d/SourceSurfaceCG.h
@@ -96,16 +96,17 @@ private:
    * for now we just store it in mFormat */
 };
 
 class SourceSurfaceCGContext : public DataSourceSurface
 {
 public:
   MOZ_DECLARE_REFCOUNTED_VIRTUAL_TYPENAME(DataSourceSurfaceCGContext)
   virtual void DrawTargetWillChange() = 0;
+  virtual void DrawTargetWillGoAway() = 0;
   virtual CGImageRef GetImage() = 0;
 };
 
 class SourceSurfaceCGBitmapContext : public SourceSurfaceCGContext
 {
 public:
   MOZ_DECLARE_REFCOUNTED_VIRTUAL_TYPENAME(DataSourceSurfaceCGBitmapContext)
   explicit SourceSurfaceCGBitmapContext(DrawTargetCG *);
@@ -134,16 +135,17 @@ public:
   virtual unsigned char *GetData() { return static_cast<unsigned char*>(mData); }
 
   virtual int32_t Stride() { return mStride; }
 
 private:
   //XXX: do the other backends friend their DrawTarget?
   friend class DrawTargetCG;
   virtual void DrawTargetWillChange();
+  virtual void DrawTargetWillGoAway();
   void EnsureImage() const;
 
   // We hold a weak reference to these two objects.
   // The cycle is broken by DrawTargetWillChange
   DrawTargetCG *mDrawTarget;
   CGContextRef mCg;
   SurfaceFormat mFormat;
 
@@ -176,16 +178,17 @@ public:
   virtual unsigned char *GetData();
 
   virtual int32_t Stride() { return mStride; }
 
 private:
   //XXX: do the other backends friend their DrawTarget?
   friend class DrawTargetCG;
   virtual void DrawTargetWillChange();
+  virtual void DrawTargetWillGoAway() { DrawTargetWillChange(); }
   void EnsureImage() const;
 
   SurfaceFormat mFormat;
   mutable CGImageRef mImage;
   MacIOSurface* mIOSurface;
 
   void *mData;
   int32_t mStride;
--- a/gfx/2d/Tools.h
+++ b/gfx/2d/Tools.h
@@ -2,16 +2,17 @@
  * This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #ifndef MOZILLA_GFX_TOOLS_H_
 #define MOZILLA_GFX_TOOLS_H_
 
 #include "mozilla/CheckedInt.h"
+#include "mozilla/Move.h"
 #include "mozilla/TypeTraits.h"
 #include "Types.h"
 #include "Point.h"
 #include <math.h>
 #if defined(_MSC_VER) && (_MSC_VER < 1600)
 #define hypotf _hypotf
 #endif
 
@@ -173,16 +174,23 @@ struct AlignedArray
     // Now that mPtr is pointing to the aligned position we can use placement
     // |operator new| to invoke any ctors at the correct positions. For types
     // that have a no-op default constructor the compiler's dead code
     // elimination step should optimize this away.
     mPtr = new (mPtr) T[aCount];
     mCount = aCount;
   }
 
+  void Swap(AlignedArray<T, alignment>& aOther)
+  {
+    mozilla::Swap(mPtr, aOther.mPtr);
+    mozilla::Swap(mStorage, aOther.mStorage);
+    mozilla::Swap(mCount, aOther.mCount);
+  }
+
   MOZ_ALWAYS_INLINE operator T*()
   {
     return mPtr;
   }
 
   T *mPtr;
 
 private:
--- a/gfx/layers/Compositor.h
+++ b/gfx/layers/Compositor.h
@@ -131,16 +131,27 @@ class LayerManagerComposite;
 
 enum SurfaceInitMode
 {
   INIT_MODE_NONE,
   INIT_MODE_CLEAR
 };
 
 /**
+ * A base class for a platform-dependent helper for use by TextureHost.
+ */
+class CompositorBackendSpecificData
+{
+  NS_INLINE_DECL_REFCOUNTING(CompositorBackendSpecificData)
+
+protected:
+  virtual ~CompositorBackendSpecificData() {}
+};
+
+/**
  * Common interface for compositor backends.
  *
  * Compositor provides a cross-platform interface to a set of operations for
  * compositing quads. Compositor knows nothing about the layer tree. It must be
  * told everything about each composited quad - contents, location, transform,
  * opacity, etc.
  *
  * In theory it should be possible for different widgets to use the same
@@ -465,16 +476,20 @@ public:
       fillRatio = 100.0f * float(mPixelsFilled) / float(mPixelsPerFrame);
       if (fillRatio > 999.0f) {
         fillRatio = 999.0f;
       }
     }
     return fillRatio;
   }
 
+  virtual CompositorBackendSpecificData* GetCompositorBackendSpecificData() {
+    return nullptr;
+  }
+
   ScreenRotation GetScreenRotation() const {
     return mScreenRotation;
   }
 
   void SetScreenRotation(ScreenRotation aRotation) {
     mScreenRotation = aRotation;
   }
 
--- a/gfx/layers/apz/public/GeckoContentController.h
+++ b/gfx/layers/apz/public/GeckoContentController.h
@@ -57,17 +57,18 @@ public:
                                const ScrollableLayerGuid& aGuid) = 0;
 
   /**
    * Requests handling a long tap. |aPoint| is in CSS pixels, relative to the
    * current scroll offset.
    */
   virtual void HandleLongTap(const CSSPoint& aPoint,
                              int32_t aModifiers,
-                             const ScrollableLayerGuid& aGuid) = 0;
+                             const ScrollableLayerGuid& aGuid,
+                             uint64_t aInputBlockId) = 0;
 
   /**
    * Requests handling of releasing a long tap. |aPoint| is in CSS pixels,
    * relative to the current scroll offset. HandleLongTapUp will always be
    * preceeded by HandleLongTap. However not all calls to HandleLongTap will
    * be followed by a HandleLongTapUp (for example, if the user drags
    * around between the long-tap and lifting their finger, or if content
    * notifies the APZ that the long-tap event was prevent-defaulted).
--- a/gfx/layers/apz/src/APZCTreeManager.cpp
+++ b/gfx/layers/apz/src/APZCTreeManager.cpp
@@ -2,16 +2,17 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "APZCTreeManager.h"
 #include "AsyncPanZoomController.h"
 #include "Compositor.h"                 // for Compositor
 #include "CompositorParent.h"           // for CompositorParent, etc
+#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/LayerMetricsWrapper.h"
 #include "mozilla/MouseEvents.h"
 #include "mozilla/mozalloc.h"           // for operator new
@@ -66,17 +67,18 @@ APZCTreeManager::CalculatePendingDisplay
   const ScreenPoint& aVelocity,
   double aEstimatedPaintDuration)
 {
   return AsyncPanZoomController::CalculatePendingDisplayPort(
     aFrameMetrics, aVelocity, aEstimatedPaintDuration);
 }
 
 APZCTreeManager::APZCTreeManager()
-    : mTreeLock("APZCTreeLock"),
+    : mInputQueue(new InputQueue()),
+      mTreeLock("APZCTreeLock"),
       mInOverscrolledApzc(false),
       mRetainedTouchIdentifier(-1),
       mTouchCount(0),
       mApzcTreeLog("apzctree")
 {
   MOZ_ASSERT(NS_IsMainThread());
   AsyncPanZoomController::InitializeGlobalState();
   mApzcTreeLog.ConditionOnPrefFunction(gfxPrefs::APZPrintTree);
@@ -104,23 +106,20 @@ APZCTreeManager::GetAllowedTouchBehavior
     nsRefPtr<AsyncPanZoomController> apzc = GetTargetAPZC(spt, nullptr);
     aOutValues.AppendElement(apzc
       ? apzc->GetAllowedTouchBehavior(spt)
       : AllowedTouchBehavior::UNKNOWN);
   }
 }
 
 void
-APZCTreeManager::SetAllowedTouchBehavior(const ScrollableLayerGuid& aGuid,
+APZCTreeManager::SetAllowedTouchBehavior(uint64_t aInputBlockId,
                                          const nsTArray<TouchBehaviorFlags> &aValues)
 {
-  nsRefPtr<AsyncPanZoomController> apzc = GetTargetAPZC(aGuid);
-  if (apzc) {
-    apzc->SetAllowedTouchBehavior(aValues);
-  }
+  mInputQueue->SetAllowedTouchBehavior(aInputBlockId, aValues);
 }
 
 /* Flatten the tree of APZC instances into the given nsTArray */
 static void
 Collect(AsyncPanZoomController* aApzc, nsTArray< nsRefPtr<AsyncPanZoomController> >* aCollection)
 {
   if (aApzc) {
     aCollection->AppendElement(aApzc);
@@ -295,17 +294,17 @@ APZCTreeManager::PrepareAPZCForLayer(con
     }
 
     // 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 = new AsyncPanZoomController(aLayersId, this, state->mController,
+      apzc = new AsyncPanZoomController(aLayersId, this, mInputQueue, state->mController,
                                         AsyncPanZoomController::USE_GESTURE_DETECTOR);
       apzc->SetCompositorParent(aState.mCompositor);
       if (state->mCrossProcessParent != nullptr) {
         apzc->ShareFrameMetricsAcrossProcesses();
       }
     } 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
@@ -512,38 +511,44 @@ TransformScreenToGecko(T* aPoint, AsyncP
 {
   Matrix4x4 transformToApzc = aApzcTm->GetScreenToApzcTransform(aApzc);
   Matrix4x4 transformToGecko = aApzcTm->GetApzcToGeckoTransform(aApzc);
   ApplyTransform(aPoint, transformToApzc * transformToGecko);
 }
 
 nsEventStatus
 APZCTreeManager::ReceiveInputEvent(InputData& aEvent,
-                                   ScrollableLayerGuid* aOutTargetGuid)
+                                   ScrollableLayerGuid* aOutTargetGuid,
+                                   uint64_t* aOutInputBlockId)
 {
+  // Initialize aOutInputBlockId to a sane value, and then later we overwrite
+  // it if the input event goes into a block.
+  if (aOutInputBlockId) {
+    *aOutInputBlockId = InputBlockState::NO_BLOCK_ID;
+  }
   nsEventStatus result = nsEventStatus_eIgnore;
   Matrix4x4 transformToApzc;
   bool inOverscrolledApzc = false;
   switch (aEvent.mInputType) {
     case MULTITOUCH_INPUT: {
       MultiTouchInput& touchInput = aEvent.AsMultiTouchInput();
-      result = ProcessTouchInput(touchInput, aOutTargetGuid);
+      result = ProcessTouchInput(touchInput, aOutTargetGuid, aOutInputBlockId);
       break;
     } case PANGESTURE_INPUT: {
       PanGestureInput& panInput = aEvent.AsPanGestureInput();
       nsRefPtr<AsyncPanZoomController> apzc = GetTargetAPZC(panInput.mPanStartPoint,
                                                             &inOverscrolledApzc);
       if (apzc) {
         // When passing the event to the APZC, we need to apply a different
         // transform than the one in TransformScreenToGecko, so we need to
         // make a copy of the event.
         PanGestureInput inputForApzc(panInput);
         transformToApzc = GetScreenToApzcTransform(apzc);
         ApplyTransform(&(inputForApzc.mPanStartPoint), transformToApzc);
-        result = apzc->ReceiveInputEvent(inputForApzc);
+        result = mInputQueue->ReceiveInputEvent(apzc, inputForApzc, aOutInputBlockId);
 
         // Update the out-parameters so they are what the caller expects.
         apzc->GetGuid(aOutTargetGuid);
         TransformScreenToGecko(&(panInput.mPanStartPoint), apzc, this);
       }
       break;
     } case PINCHGESTURE_INPUT: {
       PinchGestureInput& pinchInput = aEvent.AsPinchGestureInput();
@@ -551,17 +556,17 @@ APZCTreeManager::ReceiveInputEvent(Input
                                                             &inOverscrolledApzc);
       if (apzc) {
         // When passing the event to the APZC, we need to apply a different
         // transform than the one in TransformScreenToGecko, so we need to
         // make a copy of the event.
         PinchGestureInput inputForApzc(pinchInput);
         transformToApzc = GetScreenToApzcTransform(apzc);
         ApplyTransform(&(inputForApzc.mFocusPoint), transformToApzc);
-        result = apzc->ReceiveInputEvent(inputForApzc);
+        result = mInputQueue->ReceiveInputEvent(apzc, inputForApzc, aOutInputBlockId);
 
         // Update the out-parameters so they are what the caller expects.
         apzc->GetGuid(aOutTargetGuid);
         TransformScreenToGecko(&(pinchInput.mFocusPoint), apzc, this);
       }
       break;
     } case TAPGESTURE_INPUT: {
       TapGestureInput& tapInput = aEvent.AsTapGestureInput();
@@ -569,17 +574,17 @@ APZCTreeManager::ReceiveInputEvent(Input
                                                             &inOverscrolledApzc);
       if (apzc) {
         // When passing the event to the APZC, we need to apply a different
         // transform than the one in TransformScreenToGecko, so we need to
         // make a copy of the event.
         TapGestureInput inputForApzc(tapInput);
         transformToApzc = GetScreenToApzcTransform(apzc);
         ApplyTransform(&(inputForApzc.mPoint), transformToApzc);
-        result = apzc->ReceiveInputEvent(inputForApzc);
+        result = mInputQueue->ReceiveInputEvent(apzc, inputForApzc, aOutInputBlockId);
 
         // Update the out-parameters so they are what the caller expects.
         apzc->GetGuid(aOutTargetGuid);
         TransformScreenToGecko(&(tapInput.mPoint), apzc, this);
       }
       break;
     }
   }
@@ -623,17 +628,18 @@ APZCTreeManager::GetTouchInputBlockAPZC(
     APZCTM_LOG("Using APZC %p as the root APZC for multi-touch\n", apzc.get());
   }
 
   return apzc.forget();
 }
 
 nsEventStatus
 APZCTreeManager::ProcessTouchInput(MultiTouchInput& aInput,
-                                   ScrollableLayerGuid* aOutTargetGuid)
+                                   ScrollableLayerGuid* aOutTargetGuid,
+                                   uint64_t* aOutInputBlockId)
 {
   if (aInput.mType == MultiTouchInput::MULTITOUCH_START) {
     // If we are in an overscrolled state and a second finger goes down,
     // ignore that second touch point completely. The touch-start for it is
     // dropped completely; subsequent touch events until the touch-end for it
     // will have this touch point filtered out.
     if (mApzcForInputBlock && BuildOverscrollHandoffChain(mApzcForInputBlock)->HasOverscrolledApzc()) {
       if (mRetainedTouchIdentifier == -1) {
@@ -648,17 +654,17 @@ APZCTreeManager::ProcessTouchInput(Multi
     mInOverscrolledApzc = false;
     nsRefPtr<AsyncPanZoomController> apzc = GetTouchInputBlockAPZC(aInput, &mInOverscrolledApzc);
     if (apzc != mApzcForInputBlock) {
       // If we're moving to a different APZC as our input target, then send a cancel event
       // to the old one so that it clears its internal state. Otherwise it could get left
       // in the middle of a panning touch block (for example) and not clean up properly.
       if (mApzcForInputBlock) {
         MultiTouchInput cancel(MultiTouchInput::MULTITOUCH_CANCEL, 0, TimeStamp::Now(), 0);
-        mApzcForInputBlock->ReceiveInputEvent(cancel);
+        mInputQueue->ReceiveInputEvent(mApzcForInputBlock, cancel, nullptr);
       }
       mApzcForInputBlock = apzc;
     }
 
     if (mApzcForInputBlock) {
       // Cache apz transform so it can be used for future events in this block.
       mCachedTransformToApzcForInputBlock = GetScreenToApzcTransform(mApzcForInputBlock);
     } else {
@@ -697,17 +703,17 @@ APZCTreeManager::ProcessTouchInput(Multi
     // For computing the input for the APZC, used the cached transform.
     // This ensures that the sequence of touch points an APZC sees in an
     // input block are all in the same coordinate space.
     Matrix4x4 transformToApzc = mCachedTransformToApzcForInputBlock;
     MultiTouchInput inputForApzc(aInput);
     for (size_t i = 0; i < inputForApzc.mTouches.Length(); i++) {
       ApplyTransform(&(inputForApzc.mTouches[i].mScreenPoint), transformToApzc);
     }
-    result = mApzcForInputBlock->ReceiveInputEvent(inputForApzc);
+    result = mInputQueue->ReceiveInputEvent(mApzcForInputBlock, inputForApzc, aOutInputBlockId);
 
     // For computing the event to pass back to Gecko, use the up-to-date transforms.
     // This ensures that transformToApzc and transformToGecko are in sync
     // (note that transformToGecko isn't cached).
     transformToApzc = GetScreenToApzcTransform(mApzcForInputBlock);
     Matrix4x4 transformToGecko = GetApzcToGeckoTransform(mApzcForInputBlock);
     Matrix4x4 outTransform = transformToApzc * transformToGecko;
     for (size_t i = 0; i < aInput.mTouches.Length(); i++) {
@@ -752,17 +758,18 @@ APZCTreeManager::TransformCoordinateToGe
     Matrix4x4 transformToGecko = GetApzcToGeckoTransform(apzc);
     Matrix4x4 outTransform = transformToApzc * transformToGecko;
     *aOutTransformedPoint = TransformTo<LayoutDevicePixel>(outTransform, aPoint);
   }
 }
 
 nsEventStatus
 APZCTreeManager::ProcessEvent(WidgetInputEvent& aEvent,
-                              ScrollableLayerGuid* aOutTargetGuid)
+                              ScrollableLayerGuid* aOutTargetGuid,
+                              uint64_t* aOutInputBlockId)
 {
   MOZ_ASSERT(NS_IsMainThread());
   nsEventStatus result = nsEventStatus_eIgnore;
 
   // Transform the refPoint.
   // If the event hits an overscrolled APZC, instruct the caller to ignore it.
   bool inOverscrolledApzc = false;
   nsRefPtr<AsyncPanZoomController> apzc = GetTargetAPZC(ScreenPoint(aEvent.refPoint.x, aEvent.refPoint.y),
@@ -777,64 +784,68 @@ APZCTreeManager::ProcessEvent(WidgetInpu
   if (inOverscrolledApzc) {
     result = nsEventStatus_eConsumeNoDefault;
   }
   return result;
 }
 
 nsEventStatus
 APZCTreeManager::ReceiveInputEvent(WidgetInputEvent& aEvent,
-                                   ScrollableLayerGuid* aOutTargetGuid)
+                                   ScrollableLayerGuid* aOutTargetGuid,
+                                   uint64_t* aOutInputBlockId)
 {
   // This function will be removed as part of bug 930939.
   // In general it is preferable to use the version of ReceiveInputEvent
   // that takes an InputData, as that is usable from off-main-thread.
 
   MOZ_ASSERT(NS_IsMainThread());
 
+  // Initialize aOutInputBlockId to a sane value, and then later we overwrite
+  // it if the input event goes into a block.
+  if (aOutInputBlockId) {
+    *aOutInputBlockId = InputBlockState::NO_BLOCK_ID;
+  }
+
   switch (aEvent.mClass) {
     case eTouchEventClass: {
       WidgetTouchEvent& touchEvent = *aEvent.AsTouchEvent();
       MultiTouchInput touchInput(touchEvent);
-      nsEventStatus result = ProcessTouchInput(touchInput, aOutTargetGuid);
+      nsEventStatus result = ProcessTouchInput(touchInput, aOutTargetGuid, aOutInputBlockId);
       // touchInput was modified in-place to possibly remove some
       // touch points (if we are overscrolled), and the coordinates were
       // modified using the APZ untransform. We need to copy these changes
       // back into the WidgetInputEvent.
       touchEvent.touches.Clear();
       touchEvent.touches.SetCapacity(touchInput.mTouches.Length());
       for (size_t i = 0; i < touchInput.mTouches.Length(); i++) {
         *touchEvent.touches.AppendElement() = touchInput.mTouches[i].ToNewDOMTouch();
       }
       return result;
     }
     default: {
-      return ProcessEvent(aEvent, aOutTargetGuid);
+      return ProcessEvent(aEvent, aOutTargetGuid, aOutInputBlockId);
     }
   }
 }
 
 void
 APZCTreeManager::ZoomToRect(const ScrollableLayerGuid& aGuid,
                             const CSSRect& aRect)
 {
   nsRefPtr<AsyncPanZoomController> apzc = GetTargetAPZC(aGuid);
   if (apzc) {
     apzc->ZoomToRect(aRect);
   }
 }
 
 void
-APZCTreeManager::ContentReceivedTouch(const ScrollableLayerGuid& aGuid,
+APZCTreeManager::ContentReceivedTouch(uint64_t aInputBlockId,
                                       bool aPreventDefault)
 {
-  nsRefPtr<AsyncPanZoomController> apzc = GetTargetAPZC(aGuid);
-  if (apzc) {
-    apzc->ContentReceivedTouch(aPreventDefault);
-  }
+  mInputQueue->ContentReceivedTouch(aInputBlockId, aPreventDefault);
 }
 
 void
 APZCTreeManager::UpdateZoomConstraints(const ScrollableLayerGuid& aGuid,
                                        const ZoomConstraints& aConstraints)
 {
   nsRefPtr<AsyncPanZoomController> apzc = GetTargetAPZC(aGuid);
   // For a given layers id, non-root APZCs inherit the zoom constraints
--- a/gfx/layers/apz/src/APZCTreeManager.h
+++ b/gfx/layers/apz/src/APZCTreeManager.h
@@ -41,16 +41,17 @@ enum AllowedTouchBehavior {
 
 class Layer;
 class AsyncPanZoomController;
 class CompositorParent;
 class APZPaintLogHelper;
 class OverscrollHandoffChain;
 struct OverscrollHandoffState;
 class LayerMetricsWrapper;
+class InputQueue;
 
 /**
  * ****************** 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:
@@ -154,39 +155,40 @@ public:
    *   in some cases CONSUMED is returned even if the event was NOT used. This
    *   is because we cannot always know at the time of event delivery whether
    *   the event will be used or not. So we err on the side of sending
    *   CONSUMED when we are uncertain.
    *
    * @param aEvent input event object; is modified in-place
    * @param aOutTargetGuid returns the guid of the apzc this event was
    * delivered to. May be null.
+   * @param aOutInputBlockId returns the id of the input block that this event
+   * was added to, if that was the case. May be null.
    */
   nsEventStatus ReceiveInputEvent(InputData& aEvent,
-                                  ScrollableLayerGuid* aOutTargetGuid);
+                                  ScrollableLayerGuid* aOutTargetGuid,
+                                  uint64_t* aOutInputBlockId);
 
   /**
    * WidgetInputEvent handler. Transforms |aEvent| (which is assumed to be an
    * already-existing instance of an WidgetInputEvent which may be an
    * WidgetTouchEvent) to have its coordinates in DOM space. This is so that the
    * event can be passed through the DOM and content can handle them.
    *
    * NOTE: Be careful of invoking the WidgetInputEvent variant. This can only be
    * called on the main thread. See widget/InputData.h for more information on
    * why we have InputData and WidgetInputEvent separated.
    * NOTE: On unix, mouse events are treated as touch and are forwarded
    * to the appropriate apz as such.
    *
-   * @param aEvent input event object; is modified in-place
-   * @param aOutTargetGuid returns the guid of the apzc this event was
-   * delivered to. May be null.
-   * @return See documentation for other ReceiveInputEvent above.
+   * See documentation for other ReceiveInputEvent above.
    */
   nsEventStatus ReceiveInputEvent(WidgetInputEvent& aEvent,
-                                  ScrollableLayerGuid* aOutTargetGuid);
+                                  ScrollableLayerGuid* aOutTargetGuid,
+                                  uint64_t* aOutInputBlockId);
 
   /**
    * A helper for transforming coordinates to gecko coordinate space.
    *
    * @param aPoint point to transform
    * @param aOutTransformedPoint resulting transformed point
    */
   void TransformCoordinateToGecko(const ScreenIntPoint& aPoint,
@@ -201,17 +203,17 @@ public:
                   const CSSRect& aRect);
 
   /**
    * If we have touch listeners, this should always be called when we know
    * definitively whether or not content has preventDefaulted any touch events
    * that have come in. If |aPreventDefault| is true, any touch events in the
    * queue will be discarded.
    */
-  void ContentReceivedTouch(const ScrollableLayerGuid& aGuid,
+  void ContentReceivedTouch(uint64_t aInputBlockId,
                             bool aPreventDefault);
 
   /**
    * Updates any zoom constraints contained in the <meta name="viewport"> tag.
    */
   void UpdateZoomConstraints(const ScrollableLayerGuid& aGuid,
                              const ZoomConstraints& aConstraints);
 
@@ -261,22 +263,24 @@ public:
    * Returns values of allowed touch-behavior for the touches of aEvent via out parameter.
    * Internally performs asks appropriate AsyncPanZoomController to perform
    * hit testing on its own.
    */
   void GetAllowedTouchBehavior(WidgetInputEvent* aEvent,
                                nsTArray<TouchBehaviorFlags>& aOutValues);
 
   /**
-   * Sets allowed touch behavior values for current touch-session for specific apzc (determined by guid).
-   * Should be invoked by the widget. Each value of the aValues arrays corresponds to the different
-   * touch point that is currently active.
-   * Must be called after receiving the TOUCH_START event that starts the touch-session.
+   * Sets allowed touch behavior values for current touch-session for specific
+   * input block (determined by aInputBlock).
+   * Should be invoked by the widget. Each value of the aValues arrays
+   * corresponds to the different touch point that is currently active.
+   * Must be called after receiving the TOUCH_START event that starts the
+   * touch-session.
    */
-  void SetAllowedTouchBehavior(const ScrollableLayerGuid& aGuid,
+  void SetAllowedTouchBehavior(uint64_t aInputBlockId,
                                const nsTArray<TouchBehaviorFlags>& aValues);
 
   /**
    * This is a callback for AsyncPanZoomController to call when it wants to
    * scroll in response to a touch-move event, or when it needs to hand off
    * overscroll to the next APZC. Note that because of scroll grabbing, the
    * first APZC to scroll may not be the one that is receiving the touch events.
    *
@@ -350,16 +354,17 @@ public:
                      ScreenPoint aVelocity,
                      nsRefPtr<const OverscrollHandoffChain> aOverscrollHandoffChain,
                      bool aHandoff);
 
   /*
    * Build the chain of APZCs that will handle overscroll for a pan starting at |aInitialTarget|.
    */
   nsRefPtr<const OverscrollHandoffChain> BuildOverscrollHandoffChain(const nsRefPtr<AsyncPanZoomController>& aInitialTarget);
+
 protected:
   // Protected destructor, to discourage deletion outside of Release():
   virtual ~APZCTreeManager();
 
 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
@@ -378,19 +383,21 @@ private:
   AsyncPanZoomController* GetAPZCAtPoint(AsyncPanZoomController* aApzc,
                                          const gfx::Point& aHitTestPoint,
                                          bool* aOutInOverscrolledApzc);
   already_AddRefed<AsyncPanZoomController> CommonAncestor(AsyncPanZoomController* aApzc1, AsyncPanZoomController* aApzc2);
   already_AddRefed<AsyncPanZoomController> RootAPZCForLayersId(AsyncPanZoomController* aApzc);
   already_AddRefed<AsyncPanZoomController> GetTouchInputBlockAPZC(const MultiTouchInput& aEvent,
                                                                   bool* aOutInOverscrolledApzc);
   nsEventStatus ProcessTouchInput(MultiTouchInput& aInput,
-                                  ScrollableLayerGuid* aOutTargetGuid);
+                                  ScrollableLayerGuid* aOutTargetGuid,
+                                  uint64_t* aOutInputBlockId);
   nsEventStatus ProcessEvent(WidgetInputEvent& inputEvent,
-                             ScrollableLayerGuid* aOutTargetGuid);
+                             ScrollableLayerGuid* aOutTargetGuid,
+                             uint64_t* aOutInputBlockId);
   void UpdateZoomConstraintsRecursively(AsyncPanZoomController* aApzc,
                                         const ZoomConstraints& aConstraints);
   void FlushRepaintsRecursively(AsyncPanZoomController* aApzc);
 
   AsyncPanZoomController* PrepareAPZCForLayer(const LayerMetricsWrapper& aLayer,
                                               const FrameMetrics& aMetrics,
                                               uint64_t aLayersId,
                                               const gfx::Matrix4x4& aAncestorTransform,
@@ -413,16 +420,23 @@ private:
                                                       uint64_t aLayersId,
                                                       const gfx::Matrix4x4& aAncestorTransform,
                                                       AsyncPanZoomController* aParent,
                                                       AsyncPanZoomController* aNextSibling,
                                                       const nsIntRegion& aObscured);
 
   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.
    * 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
    * 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. */
--- a/gfx/layers/apz/src/AsyncPanZoomController.cpp
+++ b/gfx/layers/apz/src/AsyncPanZoomController.cpp
@@ -11,16 +11,17 @@
 #include "AsyncPanZoomController.h"     // for AsyncPanZoomController, etc
 #include "Axis.h"                       // for AxisX, AxisY, Axis, etc
 #include "Compositor.h"                 // for Compositor
 #include "CompositorParent.h"           // for CompositorParent
 #include "FrameMetrics.h"               // for FrameMetrics, etc
 #include "GestureEventListener.h"       // for GestureEventListener
 #include "InputData.h"                  // for MultiTouchInput, etc
 #include "InputBlockState.h"            // for InputBlockState, TouchBlockState
+#include "InputQueue.h"                 // for InputQueue
 #include "OverscrollHandoffState.h"     // for OverscrollHandoffState
 #include "TaskThrottler.h"              // for TaskThrottler
 #include "Units.h"                      // for CSSRect, CSSPoint, etc
 #include "UnitTransforms.h"             // for TransformTo
 #include "base/message_loop.h"          // for MessageLoop
 #include "base/task.h"                  // for NewRunnableMethod, etc
 #include "base/tracked.h"               // for FROM_HERE
 #include "gfxPrefs.h"                   // for gfxPrefs
@@ -861,16 +862,17 @@ AsyncPanZoomController::InitializeGlobal
   gComputedTimingFunction = new ComputedTimingFunction();
   gComputedTimingFunction->Init(
     nsTimingFunction(NS_STYLE_TRANSITION_TIMING_FUNCTION_EASE));
   ClearOnShutdown(&gComputedTimingFunction);
 }
 
 AsyncPanZoomController::AsyncPanZoomController(uint64_t aLayersId,
                                                APZCTreeManager* aTreeManager,
+                                               const nsRefPtr<InputQueue>& aInputQueue,
                                                GeckoContentController* aGeckoContentController,
                                                GestureBehavior aGestures)
   :  mLayersId(aLayersId),
      mPaintThrottler(GetFrameTime(), TimeDuration::FromMilliseconds(500)),
      mGeckoContentController(aGeckoContentController),
      mRefPtrMonitor("RefPtrMonitor"),
      mSharingFrameMetricsAcrossProcesses(false),
      mMonitor("AsyncPanZoomController"),
@@ -880,17 +882,17 @@ AsyncPanZoomController::AsyncPanZoomCont
      mZoomConstraints(false, false, MIN_ZOOM, MAX_ZOOM),
      mLastSampleTime(GetFrameTime()),
      mLastAsyncScrollTime(GetFrameTime()),
      mLastAsyncScrollOffset(0, 0),
      mCurrentAsyncScrollOffset(0, 0),
      mAsyncScrollTimeoutTask(nullptr),
      mState(NOTHING),
      mNotificationBlockers(0),
-     mTouchBlockBalance(0),
+     mInputQueue(aInputQueue),
      mTreeManager(aTreeManager),
      mAPZCId(sAsyncPanZoomControllerCount++),
      mSharedLock(nullptr),
      mAsyncTransformAppliedToContent(false)
 {
   if (aGestures == USE_GESTURE_DETECTOR) {
     mGestureEventListener = new GestureEventListener(this);
   }
@@ -921,25 +923,28 @@ AsyncPanZoomController::GetGeckoContentC
 
 already_AddRefed<GestureEventListener>
 AsyncPanZoomController::GetGestureEventListener() const {
   MonitorAutoLock lock(mRefPtrMonitor);
   nsRefPtr<GestureEventListener> listener = mGestureEventListener;
   return listener.forget();
 }
 
+const nsRefPtr<InputQueue>&
+AsyncPanZoomController::GetInputQueue() const {
+  return mInputQueue;
+}
+
 void
 AsyncPanZoomController::Destroy()
 {
   AssertOnCompositorThread();
 
   CancelAnimation();
 
-  mTouchBlockQueue.Clear();
-
   { // scope the lock
     MonitorAutoLock lock(mRefPtrMonitor);
     mGeckoContentController = nullptr;
     mGestureEventListener = nullptr;
   }
   mPrevSibling = nullptr;
   mLastChild = nullptr;
   mParent = nullptr;
@@ -1001,89 +1006,16 @@ AsyncPanZoomController::ArePointerEvents
   bool consumable = (aTouchPoints == 1 ? pannable : zoomable);
   if (!consumable) {
     return false;
   }
 
   return true;
 }
 
-nsEventStatus AsyncPanZoomController::ReceiveInputEvent(const InputData& aEvent) {
-  AssertOnControllerThread();
-
-  if (aEvent.mInputType != MULTITOUCH_INPUT) {
-    HandleInputEvent(aEvent);
-    // The return value for non-touch input isn't really used, so just return
-    // ConsumeDoDefault for now. This can be changed later if needed.
-    return nsEventStatus_eConsumeDoDefault;
-  }
-
-  TouchBlockState* block = nullptr;
-  if (aEvent.AsMultiTouchInput().mType == MultiTouchInput::MULTITOUCH_START) {
-    block = StartNewTouchBlock(false);
-    APZC_LOG("%p started new touch block %p\n", this, block);
-
-    // We want to cancel animations here as soon as possible (i.e. without waiting for
-    // content responses) because a finger has gone down and we don't want to keep moving
-    // the content under the finger. However, to prevent "future" touchstart events from
-    // interfering with "past" animations (i.e. from a previous touch block that is still
-    // being processed) we only do this animation-cancellation if there are no older
-    // touch blocks still in the queue.
-    if (block == CurrentTouchBlock()) {
-      if (block->GetOverscrollHandoffChain()->HasFastMovingApzc()) {
-        // If we're already in a fast fling, then we want the touch event to stop the fling
-        // and to disallow the touch event from being used as part of a fling.
-        block->DisallowSingleTap();
-      }
-      block->GetOverscrollHandoffChain()->CancelAnimations();
-    }
-
-    if (mFrameMetrics.GetMayHaveTouchListeners() || mFrameMetrics.GetMayHaveTouchCaret()) {
-      // Content may intercept the touch events and prevent-default them. So we schedule
-      // a timeout to give content time to do that.
-      ScheduleContentResponseTimeout();
-    } else {
-      // Content won't prevent-default this, so we can just pretend like we scheduled
-      // a timeout and it expired. Note that we will still receive a ContentReceivedTouch
-      // callback for this block, and so we need to make sure we adjust the touch balance.
-      APZC_LOG("%p not waiting for content response on block %p\n", this, block);
-      mTouchBlockBalance++;
-      block->TimeoutContentResponse();
-    }
-  } else if (mTouchBlockQueue.IsEmpty()) {
-    NS_WARNING("Received a non-start touch event while no touch blocks active!");
-  } else {
-    // this touch is part of the most-recently created block
-    block = mTouchBlockQueue.LastElement().get();
-    APZC_LOG("%p received new event in block %p\n", this, block);
-  }
-
-  if (!block) {
-    return nsEventStatus_eIgnore;
-  }
-
-  nsEventStatus result = ArePointerEventsConsumable(block, aEvent.AsMultiTouchInput().mTouches.Length())
-      ? nsEventStatus_eConsumeDoDefault
-      : nsEventStatus_eIgnore;
-
-  if (block == CurrentTouchBlock() && block->IsReadyForHandling()) {
-    APZC_LOG("%p's current touch block is ready with preventdefault %d\n",
-        this, block->IsDefaultPrevented());
-    if (block->IsDefaultPrevented()) {
-      return result;
-    }
-    HandleInputEvent(aEvent);
-    return result;
-  }
-
-  // Otherwise, add it to the queue for the touch block
-  block->AddEvent(aEvent.AsMultiTouchInput());
-  return result;
-}
-
 nsEventStatus AsyncPanZoomController::HandleInputEvent(const InputData& aEvent) {
   AssertOnControllerThread();
 
   nsEventStatus rv = nsEventStatus_eIgnore;
 
   switch (aEvent.mInputType) {
   case MULTITOUCH_INPUT: {
     const MultiTouchInput& multiTouchInput = aEvent.AsMultiTouchInput();
@@ -1529,17 +1461,17 @@ nsEventStatus AsyncPanZoomController::On
 nsEventStatus AsyncPanZoomController::OnPanBegin(const PanGestureInput& aEvent) {
   APZC_LOG("%p got a pan-begin in state %d\n", this, mState);
 
   if (mState == SMOOTH_SCROLL) {
     // SMOOTH_SCROLL scrolls are cancelled by pan gestures.
     CancelAnimation();
   }
 
-  mPanGestureState = MakeUnique<InputBlockState>(BuildOverscrollHandoffChain());
+  mPanGestureState = MakeUnique<InputBlockState>(this);
 
   mX.StartTouch(aEvent.mPanStartPoint.x, aEvent.mTime);
   mY.StartTouch(aEvent.mPanStartPoint.y, aEvent.mTime);
 
   if (GetAxisLockMode() == FREE) {
     SetState(PANNING);
     return nsEventStatus_eConsumeNoDefault;
   }
@@ -1608,17 +1540,17 @@ nsEventStatus AsyncPanZoomController::On
 nsEventStatus AsyncPanZoomController::OnPanMomentumStart(const PanGestureInput& aEvent) {
   APZC_LOG("%p got a pan-momentumstart in state %d\n", this, mState);
 
   if (mState == SMOOTH_SCROLL) {
     // SMOOTH_SCROLL scrolls are cancelled by pan gestures.
     CancelAnimation();
   }
 
-  mPanGestureState = MakeUnique<InputBlockState>(BuildOverscrollHandoffChain());
+  mPanGestureState = MakeUnique<InputBlockState>(this);
 
   return nsEventStatus_eConsumeNoDefault;
 }
 
 nsEventStatus AsyncPanZoomController::OnPanMomentumEnd(const PanGestureInput& aEvent) {
   APZC_LOG("%p got a pan-momentumend in state %d\n", this, mState);
 
   mPanGestureState = nullptr;
@@ -1636,19 +1568,18 @@ nsEventStatus AsyncPanZoomController::On
 
 nsEventStatus AsyncPanZoomController::OnLongPress(const TapGestureInput& aEvent) {
   APZC_LOG("%p got a long-press in state %d\n", this, mState);
   nsRefPtr<GeckoContentController> controller = GetGeckoContentController();
   if (controller) {
     int32_t modifiers = WidgetModifiersToDOMModifiers(aEvent.modifiers);
     CSSPoint geckoScreenPoint;
     if (ConvertToGecko(aEvent.mPoint, &geckoScreenPoint)) {
-      StartNewTouchBlock(true);
-      ScheduleContentResponseTimeout();
-      controller->HandleLongTap(geckoScreenPoint, modifiers, GetGuid());
+      uint64_t blockId = GetInputQueue()->InjectNewTouchBlock(this);
+      controller->HandleLongTap(geckoScreenPoint, modifiers, GetGuid(), blockId);
       return nsEventStatus_eConsumeNoDefault;
     }
   }
   return nsEventStatus_eIgnore;
 }
 
 nsEventStatus AsyncPanZoomController::OnLongPressUp(const TapGestureInput& aEvent) {
   APZC_LOG("%p got a long-tap-up in state %d\n", this, mState);
@@ -2912,170 +2843,44 @@ void AsyncPanZoomController::ZoomToRect(
         endZoomToMetrics.GetZoom()));
 
     // Schedule a repaint now, so the new displayport will be painted before the
     // animation finishes.
     RequestContentRepaint(endZoomToMetrics);
   }
 }
 
-void
-AsyncPanZoomController::ScheduleContentResponseTimeout() {
-  APZC_LOG("%p scheduling content response timeout\n", this);
-  PostDelayedTask(
-    NewRunnableMethod(this, &AsyncPanZoomController::ContentResponseTimeout),
-    gfxPrefs::APZContentResponseTimeout());
-}
-
-void
-AsyncPanZoomController::ContentResponseTimeout() {
-  AssertOnControllerThread();
-
-  mTouchBlockBalance++;
-  APZC_LOG("%p got a content response timeout; balance %d\n", this, mTouchBlockBalance);
-  if (mTouchBlockBalance > 0) {
-    // Find the first touch block in the queue that hasn't already received
-    // the content response timeout callback, and notify it.
-    bool found = false;
-    for (size_t i = 0; i < mTouchBlockQueue.Length(); i++) {
-      if (mTouchBlockQueue[i]->TimeoutContentResponse()) {
-        found = true;
-        break;
-      }
-    }
-    if (found) {
-      ProcessPendingInputBlocks();
-    } else {
-      NS_WARNING("APZC received more ContentResponseTimeout calls than it has unprocessed touch blocks\n");
-    }
-  }
-}
-
-void
-AsyncPanZoomController::ContentReceivedTouch(bool aPreventDefault) {
-  AssertOnControllerThread();
-
-  mTouchBlockBalance--;
-  APZC_LOG("%p got a content response; balance %d\n", this, mTouchBlockBalance);
-  if (mTouchBlockBalance < 0) {
-    // Find the first touch block in the queue that hasn't already received
-    // its response from content, and notify it.
-    bool found = false;
-    for (size_t i = 0; i < mTouchBlockQueue.Length(); i++) {
-      if (mTouchBlockQueue[i]->SetContentResponse(aPreventDefault)) {
-        found = true;
-        break;
-      }
-    }
-    if (found) {
-      ProcessPendingInputBlocks();
-    } else {
-      NS_WARNING("APZC received more ContentReceivedTouch calls than it has unprocessed touch blocks\n");
-    }
-  }
-}
-
-void
-AsyncPanZoomController::SetAllowedTouchBehavior(const nsTArray<TouchBehaviorFlags>& aBehaviors) {
-  AssertOnControllerThread();
-
-  bool found = false;
-  for (size_t i = 0; i < mTouchBlockQueue.Length(); i++) {
-    if (mTouchBlockQueue[i]->SetAllowedTouchBehaviors(aBehaviors)) {
-      found = true;
-      break;
-    }
-  }
-  if (found) {
-    ProcessPendingInputBlocks();
-  } else {
-    NS_WARNING("APZC received more SetAllowedTouchBehavior calls than it has unprocessed touch blocks\n");
-  }
-}
-
-void
-AsyncPanZoomController::ProcessPendingInputBlocks() {
-  AssertOnControllerThread();
-
-  while (true) {
-    TouchBlockState* curBlock = CurrentTouchBlock();
-    if (!curBlock->IsReadyForHandling()) {
-      break;
-    }
-
-    APZC_LOG("%p processing input block %p; preventDefault %d\n",
-        this, curBlock, curBlock->IsDefaultPrevented());
-    if (curBlock->IsDefaultPrevented()) {
-      SetState(NOTHING);
-      curBlock->DropEvents();
-      // Also clear the state in the gesture event listener
-      nsRefPtr<GestureEventListener> listener = GetGestureEventListener();
-      if (listener) {
-        MultiTouchInput cancel(MultiTouchInput::MULTITOUCH_CANCEL, 0, TimeStamp::Now(), 0);
-        listener->HandleInputEvent(cancel);
-      }
-    } else {
-      while (curBlock->HasEvents()) {
-        HandleInputEvent(curBlock->RemoveFirstEvent());
-      }
-    }
-    MOZ_ASSERT(!curBlock->HasEvents());
-
-    if (mTouchBlockQueue.Length() == 1) {
-      // If |curBlock| is the only touch block in the queue, then it is still
-      // active and we cannot remove it yet. We only know that a touch block is
-      // over when we start the next one. This block will be removed by the code
-      // in StartNewTouchBlock, where new touch blocks are added.
-      break;
-    }
-
-    // If we get here, we know there are more touch blocks in the queue after
-    // |curBlock|, so we can remove |curBlock| and try to process the next one.
-    APZC_LOG("%p discarding depleted touch block %p\n", this, curBlock);
-    mTouchBlockQueue.RemoveElementAt(0);
-  }
-}
-
-TouchBlockState*
-AsyncPanZoomController::StartNewTouchBlock(bool aCopyAllowedTouchBehaviorFromCurrent)
+bool
+AsyncPanZoomController::NeedToWaitForContent() const
 {
-  TouchBlockState* newBlock = new TouchBlockState(BuildOverscrollHandoffChain());
-  if (gfxPrefs::TouchActionEnabled() && aCopyAllowedTouchBehaviorFromCurrent) {
-    newBlock->CopyAllowedTouchBehaviorsFrom(*CurrentTouchBlock());
-  }
-
-  // We're going to start a new block, so clear out any depleted blocks at the head of the queue.
-  // See corresponding comment in ProcessPendingInputBlocks.
-  while (!mTouchBlockQueue.IsEmpty()) {
-    if (mTouchBlockQueue[0]->IsReadyForHandling() && !mTouchBlockQueue[0]->HasEvents()) {
-      APZC_LOG("%p discarding depleted touch block %p\n", this, mTouchBlockQueue[0].get());
-      mTouchBlockQueue.RemoveElementAt(0);
-    } else {
-      break;
-    }
-  }
-
-  // Add the new block to the queue.
-  mTouchBlockQueue.AppendElement(newBlock);
-  return newBlock;
+  return (mFrameMetrics.GetMayHaveTouchListeners() || mFrameMetrics.GetMayHaveTouchCaret());
 }
 
 TouchBlockState*
 AsyncPanZoomController::CurrentTouchBlock()
 {
-  AssertOnControllerThread();
-
-  MOZ_ASSERT(!mTouchBlockQueue.IsEmpty());
-  return mTouchBlockQueue[0].get();
+  return GetInputQueue()->CurrentTouchBlock();
+}
+
+void
+AsyncPanZoomController::ResetInputState()
+{
+  SetState(NOTHING);
+  // Also clear the state in the gesture event listener
+  nsRefPtr<GestureEventListener> listener = GetGestureEventListener();
+  if (listener) {
+    MultiTouchInput cancel(MultiTouchInput::MULTITOUCH_CANCEL, 0, TimeStamp::Now(), 0);
+    listener->HandleInputEvent(cancel);
+  }
 }
 
 bool
 AsyncPanZoomController::HasReadyTouchBlock()
 {
-  return !mTouchBlockQueue.IsEmpty() && mTouchBlockQueue[0]->IsReadyForHandling();
+  return GetInputQueue()->HasReadyTouchBlock();
 }
 
 AsyncPanZoomController::TouchBehaviorFlags
 AsyncPanZoomController::GetAllowedTouchBehavior(ScreenIntPoint& aPoint) {
   // Here we need to perform a hit testing over the touch-action regions attached to the
   // layer associated with current apzc.
   // Currently they are in progress, for more info see bug 928833.
   return AllowedTouchBehavior::UNKNOWN;
--- a/gfx/layers/apz/src/AsyncPanZoomController.h
+++ b/gfx/layers/apz/src/AsyncPanZoomController.h
@@ -14,16 +14,17 @@
 #include "mozilla/EventForwards.h"
 #include "mozilla/Monitor.h"
 #include "mozilla/ReentrantMonitor.h"
 #include "mozilla/RefPtr.h"
 #include "mozilla/UniquePtr.h"
 #include "mozilla/Atomics.h"
 #include "InputData.h"
 #include "Axis.h"
+#include "InputQueue.h"
 #include "TaskThrottler.h"
 #include "mozilla/gfx/Matrix.h"
 #include "nsRegion.h"
 
 #include "base/message_loop.h"
 
 namespace mozilla {
 
@@ -90,16 +91,17 @@ public:
    * accidentally processing taps as touch moves, and from very short/accidental
    * touches moving the screen.
    * Note: this distance is in global screen coordinates.
    */
   static float GetTouchStartTolerance();
 
   AsyncPanZoomController(uint64_t aLayersId,
                          APZCTreeManager* aTreeManager,
+                         const nsRefPtr<InputQueue>& aInputQueue,
                          GeckoContentController* aController,
                          GestureBehavior aGestures = DEFAULT_GESTURES);
 
   // --------------------------------------------------------------------------
   // These methods must only be called on the gecko thread.
   //
 
   /**
@@ -109,26 +111,16 @@ public:
    */
   static void InitializeGlobalState();
 
   // --------------------------------------------------------------------------
   // These methods must only be called on the controller/UI thread.
   //
 
   /**
-   * 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.
-   * HandleInputEvent() should be used internally.
-   * See the documentation on APZCTreeManager::ReceiveInputEvent for info on
-   * return values from this function.
-   */
-  nsEventStatus ReceiveInputEvent(const InputData& aEvent);
-
-  /**
    * Kicks an animation to zoom to a rect. This may be either a zoom out or zoom
    * in. The actual animation is done on the compositor thread after being set
    * up.
    */
   void ZoomToRect(CSSRect aRect);
 
   /**
    * Updates any zoom constraints contained in the <meta name="viewport"> tag.
@@ -571,32 +563,28 @@ protected:
   /**
    * Gets the pointer to the apzc tree manager. All the access to tree manager
    * should be made via this method and not via private variable since this method
    * ensures that no lock is set.
    */
   APZCTreeManager* GetApzcTreeManager() const;
 
   /**
+   * Gets a ref to the input queue that is shared across the entire tree manager.
+   */
+  const nsRefPtr<InputQueue>& GetInputQueue() const;
+
+  /**
    * Timeout function for mozbrowserasyncscroll event. Because we throttle
    * mozbrowserasyncscroll events in some conditions, this function ensures
    * that the last mozbrowserasyncscroll event will be fired after a period of
    * time.
    */
   void FireAsyncScrollOnTimeout();
 
-private:
-  /**
-   * Given the number of touch points in an input event and touch block they
-   * belong to, check if the event can result in a panning/zooming behavior.
-   * This is primarily used to figure out when to dispatch the pointercancel
-   * event for the pointer events spec.
-   */
-  bool ArePointerEventsConsumable(TouchBlockState* aBlock, uint32_t aTouchPoints);
-
   /**
    * Convert ScreenPoint relative to this APZC to CSSPoint relative
    * to the parent document. This excludes the transient compositor transform.
    * NOTE: This must be converted to CSSPoint relative to the child
    * document before sending over IPC.
    */
   bool ConvertToGecko(const ScreenPoint& aPoint, CSSPoint* aOut);
 
@@ -776,105 +764,44 @@ private:
 
   /* ===================================================================
    * The functions and members in this section are used to manage
    * blocks of touch events and the state needed to deal with content
    * listeners.
    */
 public:
   /**
-   * This function is invoked by the APZCTreeManager which in turn is invoked
-   * by the widget when web content decides whether or not it wants to
-   * cancel a block of events. This automatically gets applied to the next
-   * block of events that has not yet been responded to. This function MUST
-   * be invoked exactly once for each touch block.
-   */
-  void ContentReceivedTouch(bool aPreventDefault);
-
-  /**
-   * Sets allowed touch behavior for current touch session.
-   * This method is invoked by the APZCTreeManager which in its turn invoked by
-   * the widget after performing touch-action values retrieving.
-   * Must be called after receiving the TOUCH_START even that started the
-   * touch session.
-   */
-  void SetAllowedTouchBehavior(const nsTArray<TouchBehaviorFlags>& aBehaviors);
-
-  /**
    * Flush a repaint request if one is needed, without throttling it with the
    * paint throttler.
    */
   void FlushRepaintForNewInputBlock();
 
-private:
-  void ScheduleContentResponseTimeout();
-  void ContentResponseTimeout();
+  /**
+   * Given the number of touch points in an input event and touch block they
+   * belong to, check if the event can result in a panning/zooming behavior.
+   * This is primarily used to figure out when to dispatch the pointercancel
+   * event for the pointer events spec.
+   */
+  bool ArePointerEventsConsumable(TouchBlockState* aBlock, uint32_t aTouchPoints);
+
   /**
-   * Processes any pending input blocks that are ready for processing. There
-   * must be at least one input block in the queue when this function is called.
+   * Return true if there are are touch listeners registered on content
+   * scrolled by this APZC.
    */
-  void ProcessPendingInputBlocks();
-  TouchBlockState* StartNewTouchBlock(bool aCopyAllowedTouchBehaviorFromCurrent);
-  TouchBlockState* CurrentTouchBlock();
-  bool HasReadyTouchBlock();
+  bool NeedToWaitForContent() const;
+
+  /**
+   * Clear internal state relating to input handling.
+   */
+  void ResetInputState();
 
 private:
-  // The queue of touch blocks that have not yet been processed by this APZC.
-  // This member must only be accessed on the controller/UI thread.
-  nsTArray<UniquePtr<TouchBlockState>> mTouchBlockQueue;
-
-  // This variable requires some explanation. Strap yourself in.
-  //
-  // For each block of events, we do two things: (1) send the events to gecko and expect
-  // exactly one call to ContentReceivedTouch in return, and (2) kick off a timeout
-  // that triggers in case we don't hear from web content in a timely fashion.
-  // Since events are constantly coming in, we need to be able to handle more than one
-  // block of input events sitting in the queue.
-  //
-  // There are ordering restrictions on events that we can take advantage of, and that
-  // we need to abide by. Blocks of events in the queue will always be in the order that
-  // the user generated them. Responses we get from content will be in the same order as
-  // as the blocks of events in the queue. The timeout callbacks that have been posted
-  // will also fire in the same order as the blocks of events in the queue.
-  // HOWEVER, we may get multiple responses from content interleaved with multiple
-  // timeout expirations, and that interleaving is not predictable.
-  //
-  // Therefore, we need to make sure that for each block of events, we process the queued
-  // events exactly once, either when we get the response from content, or when the
-  // timeout expires (whichever happens first). There is no way to associate the timeout
-  // or response from content with a particular block of events other than via ordering.
-  //
-  // So, what we do to accomplish this is to track a "touch block balance", which is the
-  // number of timeout expirations that have fired, minus the number of content responses
-  // that have been received. (Think "balance" as in teeter-totter balance). This
-  // value is:
-  // - zero when we are in a state where the next content response we expect to receive
-  //   and the next timeout expiration we expect to fire both correspond to the next
-  //   unprocessed block of events in the queue.
-  // - negative when we are in a state where we have received more content responses than
-  //   timeout expirations. This means that the next content repsonse we receive will
-  //   correspond to the first unprocessed block, but the next n timeout expirations need
-  //   to be ignored as they are for blocks we have already processed. (n is the absolute
-  //   value of the balance.)
-  // - positive when we are in a state where we have received more timeout expirations
-  //   than content responses. This means that the next timeout expiration that we will
-  //   receive will correspond to the first unprocessed block, but the next n content
-  //   responses need to be ignored as they are for blocks we have already processed.
-  //   (n is the absolute value of the balance.)
-  //
-  // Note that each touch block internally carries flags that indicate whether or not it
-  // has received a content response and/or timeout expiration. However, we cannot rely
-  // on that alone to deliver these notifications to the right input block, because
-  // once an input block has been processed, it can potentially be removed from the queue.
-  // Therefore the information in that block is lost. An alternative approach would
-  // be to keep around those blocks until they have received both the content response
-  // and timeout expiration, but that involves a higher level of memory usage.
-  //
-  // This member must only be accessed on the controller/UI thread.
-  int32_t mTouchBlockBalance;
+  nsRefPtr<InputQueue> mInputQueue;
+  TouchBlockState* CurrentTouchBlock();
+  bool HasReadyTouchBlock();
 
 
   /* ===================================================================
    * The functions and members in this section are used to manage
    * pan gestures.
    */
 
 private:
@@ -1023,16 +950,37 @@ public:
   void FlushRepaintForOverscrollHandoff();
 
   /**
    * If overscrolled, start a snap-back animation and return true.
    * Otherwise return false.
    */
   bool SnapBackIfOverscrolled();
 
+  /**
+   * Build the chain of APZCs along which scroll will be handed off when
+   * this APZC receives input events.
+   *
+   * Notes on lifetime and const-correctness:
+   *   - The returned handoff chain is |const|, to indicate that it cannot be
+   *     changed after being built.
+   *   - When passing the chain to a function that uses it without storing it,
+   *     pass it by reference-to-const (as in |const OverscrollHandoffChain&|).
+   *   - When storing the chain, store it by RefPtr-to-const (as in
+   *     |nsRefPtr<const OverscrollHandoffChain>|). This ensures the chain is
+   *     kept alive. Note that queueing a task that uses the chain as an
+   *     argument constitutes storing, as the task may outlive its queuer.
+   *   - When passing the chain to a function that will store it, pass it as
+   *     |const nsRefPtr<const OverscrollHandoffChain>&|. This allows the
+   *     function to copy it into the |nsRefPtr<const OverscrollHandoffChain>|
+   *     that will store it, while avoiding an unnecessary copy (and thus
+   *     AddRef() and Release()) when passing it.
+   */
+  nsRefPtr<const OverscrollHandoffChain> BuildOverscrollHandoffChain();
+
 private:
   /**
    * A helper function for calling APZCTreeManager::DispatchScroll().
    * Guards against the case where the APZC is being concurrently destroyed
    * (and thus mTreeManager is being nulled out).
    */
   bool CallDispatchScroll(const ScreenPoint& aStartPoint,
                           const ScreenPoint& aEndPoint,
@@ -1049,36 +997,16 @@ private:
   /**
    * Try to overscroll by 'aOverscroll'.
    * If we are pannable, 'aOverscroll' is added to any existing overscroll,
    * and the function returns true.
    * Otherwise, nothing happens and the function return false.
    */
   bool OverscrollBy(const ScreenPoint& aOverscroll);
 
-  /**
-   * Build the chain of APZCs along which scroll will be handed off when
-   * this APZC receives input events.
-   *
-   * Notes on lifetime and const-correctness:
-   *   - The returned handoff chain is |const|, to indicate that it cannot be
-   *     changed after being built.
-   *   - When passing the chain to a function that uses it without storing it,
-   *     pass it by reference-to-const (as in |const OverscrollHandoffChain&|).
-   *   - When storing the chain, store it by RefPtr-to-const (as in
-   *     |nsRefPtr<const OverscrollHandoffChain>|). This ensures the chain is
-   *     kept alive. Note that queueing a task that uses the chain as an
-   *     argument constitutes storing, as the task may outlive its queuer.
-   *   - When passing the chain to a function that will store it, pass it as
-   *     |const nsRefPtr<const OverscrollHandoffChain>&|. This allows the
-   *     function to copy it into the |nsRefPtr<const OverscrollHandoffChain>|
-   *     that will store it, while avoiding an unnecessary copy (and thus
-   *     AddRef() and Release()) when passing it.
-   */
-  nsRefPtr<const OverscrollHandoffChain> BuildOverscrollHandoffChain();
 
   /* ===================================================================
    * 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 nsIntRegion& aRegion, const Matrix4x4& aTransformToLayer) {
--- a/gfx/layers/apz/src/InputBlockState.cpp
+++ b/gfx/layers/apz/src/InputBlockState.cpp
@@ -1,40 +1,57 @@
 /* -*- 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 "InputBlockState.h"
 #include "mozilla/layers/APZCTreeManager.h" // for AllowedTouchBehavior
+#include "AsyncPanZoomController.h"         // for AsyncPanZoomController
 #include "gfxPrefs.h"                       // for gfxPrefs
 #include "OverscrollHandoffState.h"
 
 #define TBS_LOG(...)
 // #define TBS_LOG(...) printf_stderr("TBS: " __VA_ARGS__)
 
 namespace mozilla {
 namespace layers {
 
-InputBlockState::InputBlockState(const nsRefPtr<const OverscrollHandoffChain>& aOverscrollHandoffChain)
-  : mOverscrollHandoffChain(aOverscrollHandoffChain)
+static uint64_t sBlockCounter = InputBlockState::NO_BLOCK_ID + 1;
+
+InputBlockState::InputBlockState(const nsRefPtr<AsyncPanZoomController>& aTargetApzc)
+  : mTargetApzc(aTargetApzc)
+  , mBlockId(sBlockCounter++)
 {
-  // We should never be constructed with a nullptr handoff chain.
-  MOZ_ASSERT(mOverscrollHandoffChain);
+  // We should never be constructed with a nullptr target.
+  MOZ_ASSERT(mTargetApzc);
+  mOverscrollHandoffChain = mTargetApzc->BuildOverscrollHandoffChain();
+}
+
+const nsRefPtr<AsyncPanZoomController>&
+InputBlockState::GetTargetApzc() const
+{
+  return mTargetApzc;
 }
 
 const nsRefPtr<const OverscrollHandoffChain>&
 InputBlockState::GetOverscrollHandoffChain() const
 {
   return mOverscrollHandoffChain;
 }
 
-TouchBlockState::TouchBlockState(const nsRefPtr<const OverscrollHandoffChain>& aOverscrollHandoffChain)
-  : InputBlockState(aOverscrollHandoffChain)
+uint64_t
+InputBlockState::GetBlockId() const
+{
+  return mBlockId;
+}
+
+TouchBlockState::TouchBlockState(const nsRefPtr<AsyncPanZoomController>& aTargetApzc)
+  : InputBlockState(aTargetApzc)
   , mAllowedTouchBehaviorSet(false)
   , mPreventDefault(false)
   , mContentResponded(false)
   , mContentResponseTimerExpired(false)
   , mSingleTapDisallowed(false)
   , mSingleTapOccurred(false)
 {
   TBS_LOG("Creating %p\n", this);
--- a/gfx/layers/apz/src/InputBlockState.h
+++ b/gfx/layers/apz/src/InputBlockState.h
@@ -9,30 +9,37 @@
 
 #include "nsTArray.h"                       // for nsTArray
 #include "InputData.h"                      // for MultiTouchInput
 #include "nsAutoPtr.h"
 
 namespace mozilla {
 namespace layers {
 
+class AsyncPanZoomController;
 class OverscrollHandoffChain;
 
 /**
  * A base class that stores state common to various input blocks.
  * Currently, it just stores the overscroll handoff chain.
  */
 class InputBlockState
 {
 public:
-  explicit InputBlockState(const nsRefPtr<const OverscrollHandoffChain>& aOverscrollHandoffChain);
+  static const uint64_t NO_BLOCK_ID = 0;
 
+  explicit InputBlockState(const nsRefPtr<AsyncPanZoomController>& aTargetApzc);
+
+  const nsRefPtr<AsyncPanZoomController>& GetTargetApzc() const;
   const nsRefPtr<const OverscrollHandoffChain>& GetOverscrollHandoffChain() const;
+  uint64_t GetBlockId() const;
 private:
+  nsRefPtr<AsyncPanZoomController> mTargetApzc;
   nsRefPtr<const OverscrollHandoffChain> mOverscrollHandoffChain;
+  const uint64_t mBlockId;
 };
 
 /**
  * This class represents a single touch block. A touch block is
  * a set of touch events that can be cancelled by web content via
  * touch event listeners.
  *
  * Every touch-start event creates a new touch block. In this case, the
@@ -61,17 +68,17 @@ private:
  * be populated with some latency. The mAllowedTouchBehaviorSet and
  * mAllowedTouchBehaviors variables track this information.
  */
 class TouchBlockState : public InputBlockState
 {
 public:
   typedef uint32_t TouchBehaviorFlags;
 
-  explicit TouchBlockState(const nsRefPtr<const OverscrollHandoffChain>& aOverscrollHandoffChain);
+  explicit TouchBlockState(const nsRefPtr<AsyncPanZoomController>& aTargetApzc);
 
   /**
    * Record whether or not content cancelled this block of events.
    * @param aPreventDefault true iff the block is cancelled.
    * @return false if this block has already received a response from
    *         web content, true if not.
    */
   bool SetContentResponse(bool aPreventDefault);
new file mode 100644
--- /dev/null
+++ b/gfx/layers/apz/src/InputQueue.cpp
@@ -0,0 +1,254 @@
+/* -*- 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 "InputQueue.h"
+
+#include "AsyncPanZoomController.h"
+#include "gfxPrefs.h"
+#include "InputBlockState.h"
+#include "OverscrollHandoffState.h"
+
+#define INPQ_LOG(...)
+// #define INPQ_LOG(...) printf_stderr("INPQ: " __VA_ARGS__)
+
+namespace mozilla {
+namespace layers {
+
+InputQueue::InputQueue()
+{
+}
+
+InputQueue::~InputQueue() {
+  mTouchBlockQueue.Clear();
+}
+
+nsEventStatus
+InputQueue::ReceiveInputEvent(const nsRefPtr<AsyncPanZoomController>& aTarget, const InputData& aEvent, uint64_t* aOutInputBlockId) {
+  AsyncPanZoomController::AssertOnControllerThread();
+
+  if (aEvent.mInputType != MULTITOUCH_INPUT) {
+    aTarget->HandleInputEvent(aEvent);
+    // The return value for non-touch input isn't really used, so just return
+    // ConsumeDoDefault for now. This can be changed later if needed.
+    return nsEventStatus_eConsumeDoDefault;
+  }
+
+  TouchBlockState* block = nullptr;
+  if (aEvent.AsMultiTouchInput().mType == MultiTouchInput::MULTITOUCH_START) {
+    block = StartNewTouchBlock(aTarget, false);
+    INPQ_LOG("started new touch block %p for target %p\n", block, aTarget.get());
+
+    // We want to cancel animations here as soon as possible (i.e. without waiting for
+    // content responses) because a finger has gone down and we don't want to keep moving
+    // the content under the finger. However, to prevent "future" touchstart events from
+    // interfering with "past" animations (i.e. from a previous touch block that is still
+    // being processed) we only do this animation-cancellation if there are no older
+    // touch blocks still in the queue.
+    if (block == CurrentTouchBlock()) {
+      if (block->GetOverscrollHandoffChain()->HasFastMovingApzc()) {
+        // If we're already in a fast fling, then we want the touch event to stop the fling
+        // and to disallow the touch event from being used as part of a fling.
+        block->DisallowSingleTap();
+      }
+      block->GetOverscrollHandoffChain()->CancelAnimations();
+    }
+
+    if (aTarget->NeedToWaitForContent()) {
+      // Content may intercept the touch events and prevent-default them. So we schedule
+      // a timeout to give content time to do that.
+      ScheduleContentResponseTimeout(aTarget, block->GetBlockId());
+    } else {
+      // Content won't prevent-default this, so we can just pretend like we scheduled
+      // a timeout and it expired. Note that we will still receive a ContentReceivedTouch
+      // callback for this block, and so we need to make sure we adjust the touch balance.
+      INPQ_LOG("not waiting for content response on block %p\n", block);
+      block->TimeoutContentResponse();
+    }
+  } else if (mTouchBlockQueue.IsEmpty()) {
+    NS_WARNING("Received a non-start touch event while no touch blocks active!");
+  } else {
+    // this touch is part of the most-recently created block
+    block = mTouchBlockQueue.LastElement().get();
+    INPQ_LOG("received new event in block %p\n", block);
+  }
+
+  if (!block) {
+    return nsEventStatus_eIgnore;
+  }
+  if (aOutInputBlockId) {
+    *aOutInputBlockId = block->GetBlockId();
+  }
+
+  nsEventStatus result = aTarget->ArePointerEventsConsumable(block, aEvent.AsMultiTouchInput().mTouches.Length())
+      ? nsEventStatus_eConsumeDoDefault
+      : nsEventStatus_eIgnore;
+
+  if (block == CurrentTouchBlock() && block->IsReadyForHandling()) {
+    INPQ_LOG("current touch block is ready with preventdefault %d\n",
+        block->IsDefaultPrevented());
+    if (block->IsDefaultPrevented()) {
+      return result;
+    }
+    aTarget->HandleInputEvent(aEvent);
+    return result;
+  }
+
+  // Otherwise, add it to the queue for the touch block
+  block->AddEvent(aEvent.AsMultiTouchInput());
+  return result;
+}
+
+uint64_t
+InputQueue::InjectNewTouchBlock(AsyncPanZoomController* aTarget)
+{
+  TouchBlockState* block = StartNewTouchBlock(aTarget, true);
+  INPQ_LOG("%p injecting new touch block with id %llu and target %p\n",
+    this, block->GetBlockId(), aTarget);
+  ScheduleContentResponseTimeout(aTarget, block->GetBlockId());
+  return block->GetBlockId();
+}
+
+TouchBlockState*
+InputQueue::StartNewTouchBlock(const nsRefPtr<AsyncPanZoomController>& aTarget, bool aCopyAllowedTouchBehaviorFromCurrent)
+{
+  TouchBlockState* newBlock = new TouchBlockState(aTarget);
+  if (gfxPrefs::TouchActionEnabled() && aCopyAllowedTouchBehaviorFromCurrent) {
+    newBlock->CopyAllowedTouchBehaviorsFrom(*CurrentTouchBlock());
+  }
+
+  // We're going to start a new block, so clear out any depleted blocks at the head of the queue.
+  // See corresponding comment in ProcessPendingInputBlocks.
+  while (!mTouchBlockQueue.IsEmpty()) {
+    if (mTouchBlockQueue[0]->IsReadyForHandling() && !mTouchBlockQueue[0]->HasEvents()) {
+      INPQ_LOG("discarding depleted touch block %p\n", mTouchBlockQueue[0].get());
+      mTouchBlockQueue.RemoveElementAt(0);
+    } else {
+      break;
+    }
+  }
+
+  // Add the new block to the queue.
+  mTouchBlockQueue.AppendElement(newBlock);
+  return newBlock;
+}
+
+TouchBlockState*
+InputQueue::CurrentTouchBlock() const
+{
+  AsyncPanZoomController::AssertOnControllerThread();
+
+  MOZ_ASSERT(!mTouchBlockQueue.IsEmpty());
+  return mTouchBlockQueue[0].get();
+}
+
+bool
+InputQueue::HasReadyTouchBlock() const
+{
+  return !mTouchBlockQueue.IsEmpty() && mTouchBlockQueue[0]->IsReadyForHandling();
+}
+
+void
+InputQueue::ScheduleContentResponseTimeout(const nsRefPtr<AsyncPanZoomController>& aTarget, uint64_t aInputBlockId) {
+  INPQ_LOG("scheduling content response timeout for target %p\n", aTarget.get());
+  aTarget->PostDelayedTask(
+    NewRunnableMethod(this, &InputQueue::ContentResponseTimeout, aInputBlockId),
+    gfxPrefs::APZContentResponseTimeout());
+}
+
+void
+InputQueue::ContentResponseTimeout(const uint64_t& aInputBlockId) {
+  AsyncPanZoomController::AssertOnControllerThread();
+
+  INPQ_LOG("got a content response timeout; block=%llu\n", aInputBlockId);
+  bool success = false;
+  for (size_t i = 0; i < mTouchBlockQueue.Length(); i++) {
+    if (mTouchBlockQueue[i]->GetBlockId() == aInputBlockId) {
+      success = mTouchBlockQueue[i]->TimeoutContentResponse();
+      break;
+    }
+  }
+  if (success) {
+    ProcessPendingInputBlocks();
+  }
+}
+
+void
+InputQueue::ContentReceivedTouch(uint64_t aInputBlockId, bool aPreventDefault) {
+  AsyncPanZoomController::AssertOnControllerThread();
+
+  INPQ_LOG("got a content response; block=%llu\n", aInputBlockId);
+  bool success = false;
+  for (size_t i = 0; i < mTouchBlockQueue.Length(); i++) {
+    if (mTouchBlockQueue[i]->GetBlockId() == aInputBlockId) {
+      success = mTouchBlockQueue[i]->SetContentResponse(aPreventDefault);
+      break;
+    }
+  }
+  if (success) {
+    ProcessPendingInputBlocks();
+  }
+}
+
+void
+InputQueue::SetAllowedTouchBehavior(uint64_t aInputBlockId, const nsTArray<TouchBehaviorFlags>& aBehaviors) {
+  AsyncPanZoomController::AssertOnControllerThread();
+
+  INPQ_LOG("got allowed touch behaviours; block=%llu\n", aInputBlockId);
+  bool success = false;
+  for (size_t i = 0; i < mTouchBlockQueue.Length(); i++) {
+    if (mTouchBlockQueue[i]->GetBlockId() == aInputBlockId) {
+      success = mTouchBlockQueue[i]->SetAllowedTouchBehaviors(aBehaviors);
+      break;
+    }
+  }
+  if (success) {
+    ProcessPendingInputBlocks();
+  } else {
+    NS_WARNING("INPQ received useless SetAllowedTouchBehavior");
+  }
+}
+
+void
+InputQueue::ProcessPendingInputBlocks() {
+  AsyncPanZoomController::AssertOnControllerThread();
+
+  while (true) {
+    TouchBlockState* curBlock = CurrentTouchBlock();
+    if (!curBlock->IsReadyForHandling()) {
+      break;
+    }
+
+    INPQ_LOG("processing input block %p; preventDefault %d target %p\n",
+        curBlock, curBlock->IsDefaultPrevented(),
+        curBlock->GetTargetApzc().get());
+    nsRefPtr<AsyncPanZoomController> target = curBlock->GetTargetApzc();
+    if (curBlock->IsDefaultPrevented()) {
+      curBlock->DropEvents();
+      target->ResetInputState();
+    } else {
+      while (curBlock->HasEvents()) {
+        target->HandleInputEvent(curBlock->RemoveFirstEvent());
+      }
+    }
+    MOZ_ASSERT(!curBlock->HasEvents());
+
+    if (mTouchBlockQueue.Length() == 1) {
+      // If |curBlock| is the only touch block in the queue, then it is still
+      // active and we cannot remove it yet. We only know that a touch block is
+      // over when we start the next one. This block will be removed by the code
+      // in StartNewTouchBlock, where new touch blocks are added.
+      break;
+    }
+
+    // If we get here, we know there are more touch blocks in the queue after
+    // |curBlock|, so we can remove |curBlock| and try to process the next one.
+    INPQ_LOG("discarding depleted touch block %p\n", curBlock);
+    mTouchBlockQueue.RemoveElementAt(0);
+  }
+}
+
+} // namespace layers
+} // namespace mozilla
new file mode 100644
--- /dev/null
+++ b/gfx/layers/apz/src/InputQueue.h
@@ -0,0 +1,93 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_layers_InputQueue_h
+#define mozilla_layers_InputQueue_h
+
+#include "mozilla/EventForwards.h"
+#include "mozilla/UniquePtr.h"
+#include "nsAutoPtr.h"
+#include "nsTArray.h"
+
+namespace mozilla {
+
+class InputData;
+
+namespace layers {
+
+class AsyncPanZoomController;
+class OverscrollHandoffChain;
+class TouchBlockState;
+
+/**
+ * This class stores incoming input events, separated into "input blocks", until
+ * they are ready for handling. Currently input blocks are only created from
+ * touch input.
+ */
+class InputQueue {
+  NS_INLINE_DECL_THREADSAFE_REFCOUNTING(InputQueue)
+
+public:
+  typedef uint32_t TouchBehaviorFlags;
+
+public:
+  InputQueue();
+
+  /**
+   * Notifies the InputQueue of a new incoming input event. The APZC that the
+   * input event was targeted to should be provided in the |aTarget| parameter.
+   * See the documentation on APZCTreeManager::ReceiveInputEvent for info on
+   * return values from this function, including |aOutInputBlockId|.
+   */
+  nsEventStatus ReceiveInputEvent(const nsRefPtr<AsyncPanZoomController>& aTarget, const InputData& aEvent, uint64_t* aOutInputBlockId);
+  /**
+   * This function should be invoked to notify the InputQueue when web content
+   * decides whether or not it wants to cancel a block of events. The block
+   * id to which this applies should be provided in |aInputBlockId|.
+   */
+  void ContentReceivedTouch(uint64_t aInputBlockId, bool aPreventDefault);
+  /**
+   * This function should be invoked to notify the InputQueue of the touch-
+   * action properties for the different touch points in an input block. The
+   * input block this applies to should be specified by the |aInputBlockId|
+   * parameter. If touch-action is not enabled on the platform, this function
+   * does nothing and need not be called.
+   */
+  void SetAllowedTouchBehavior(uint64_t aInputBlockId, const nsTArray<TouchBehaviorFlags>& aBehaviors);
+  /**
+   * Adds a new touch block at the end of the input queue that has the same
+   * allowed touch behaviour flags as the the touch block currently being
+   * processed. This should only be called when processing of a touch block
+   * triggers the creation of a new touch block. Returns the input block id
+   * of the the newly-created block.
+   */
+  uint64_t InjectNewTouchBlock(AsyncPanZoomController* aTarget);
+  /**
+   * Returns the touch block at the head of the queue.
+   */
+  TouchBlockState* CurrentTouchBlock() const;
+  /**
+   * Returns true iff the touch block at the head of the queue is ready for
+   * handling.
+   */
+  bool HasReadyTouchBlock() const;
+
+private:
+  ~InputQueue();
+  TouchBlockState* StartNewTouchBlock(const nsRefPtr<AsyncPanZoomController>& aTarget, bool aCopyAllowedTouchBehaviorFromCurrent);
+  void ScheduleContentResponseTimeout(const nsRefPtr<AsyncPanZoomController>& aTarget, uint64_t aInputBlockId);
+  void ContentResponseTimeout(const uint64_t& aInputBlockId);
+  void ProcessPendingInputBlocks();
+
+private:
+  // The queue of touch blocks that have not yet been processed.
+  // This member must only be accessed on the controller/UI thread.
+  nsTArray<UniquePtr<TouchBlockState>> mTouchBlockQueue;
+};
+
+}
+}
+
+#endif // mozilla_layers_InputQueue_h
--- a/gfx/layers/composite/CompositableHost.cpp
+++ b/gfx/layers/composite/CompositableHost.cpp
@@ -19,16 +19,24 @@
 #include "gfxPlatform.h"                // for gfxPlatform
 #include "mozilla/layers/PCompositableParent.h"
 
 namespace mozilla {
 namespace layers {
 
 class Compositor;
 
+CompositableBackendSpecificData::CompositableBackendSpecificData()
+  : mAllowSharingTextureHost(false)
+{
+  static uint64_t sNextID = 1;
+  ++sNextID;
+  mId = sNextID;
+}
+
 /**
  * IPDL actor used by CompositableHost to match with its corresponding
  * CompositableClient on the content side.
  *
  * CompositableParent is owned by the IPDL system. It's deletion is triggered
  * by either the CompositableChild's deletion, or by the IPDL communication
  * goind down.
  */
@@ -74,16 +82,19 @@ CompositableHost::CompositableHost(const
   , mKeepAttached(false)
 {
   MOZ_COUNT_CTOR(CompositableHost);
 }
 
 CompositableHost::~CompositableHost()
 {
   MOZ_COUNT_DTOR(CompositableHost);
+  if (mBackendData) {
+    mBackendData->ClearData();
+  }
 }
 
 PCompositableParent*
 CompositableHost::CreateIPDLActor(CompositableParentManager* aMgr,
                                   const TextureInfo& aTextureInfo,
                                   uint64_t aID)
 {
   return new CompositableParent(aMgr, aTextureInfo, aID);
@@ -105,61 +116,69 @@ CompositableHost::FromIPDLActor(PComposi
 
 void
 CompositableHost::UseTextureHost(TextureHost* aTexture)
 {
   if (!aTexture) {
     return;
   }
   aTexture->SetCompositor(GetCompositor());
+  aTexture->SetCompositableBackendSpecificData(GetCompositableBackendSpecificData());
 }
 
 void
 CompositableHost::UseComponentAlphaTextures(TextureHost* aTextureOnBlack,
                                             TextureHost* aTextureOnWhite)
 {
   MOZ_ASSERT(aTextureOnBlack && aTextureOnWhite);
   aTextureOnBlack->SetCompositor(GetCompositor());
+  aTextureOnBlack->SetCompositableBackendSpecificData(GetCompositableBackendSpecificData());
   aTextureOnWhite->SetCompositor(GetCompositor());
+  aTextureOnWhite->SetCompositableBackendSpecificData(GetCompositableBackendSpecificData());
 }
 
 void
 CompositableHost::RemoveTextureHost(TextureHost* aTexture)
-{}
+{
+  // Clear strong refrence to CompositableBackendSpecificData
+  aTexture->UnsetCompositableBackendSpecificData(GetCompositableBackendSpecificData());
+}
 
 void
 CompositableHost::SetCompositor(Compositor* aCompositor)
 {
   mCompositor = aCompositor;
 }
 
 bool
 CompositableHost::AddMaskEffect(EffectChain& aEffects,
                                 const gfx::Matrix4x4& aTransform,
                                 bool aIs3D)
 {
-  CompositableTextureSourceRef source;
+  RefPtr<TextureSource> source;
   RefPtr<TextureHost> host = GetAsTextureHost();
 
   if (!host) {
     NS_WARNING("Using compositable with no valid TextureHost as mask");
     return false;
   }
 
   if (!host->Lock()) {
     NS_WARNING("Failed to lock the mask texture");
     return false;
   }
 
-  if (!host->BindTextureSource(source)) {
+  source = host->GetTextureSources();
+  MOZ_ASSERT(source);
+
+  if (!source) {
     NS_WARNING("The TextureHost was successfully locked but can't provide a TextureSource");
     host->Unlock();
     return false;
   }
-  MOZ_ASSERT(source);
 
   RefPtr<EffectMask> effect = new EffectMask(source,
                                              source->GetSize(),
                                              aTransform);
   effect->mIs3D = aIs3D;
   aEffects.mSecondaryEffects[EffectTypes::MASK] = effect;
   return true;
 }
@@ -168,16 +187,19 @@ void
 CompositableHost::RemoveMaskEffect()
 {
   RefPtr<TextureHost> host = GetAsTextureHost();
   if (host) {
     host->Unlock();
   }
 }
 
+// implemented in TextureHostOGL.cpp
+TemporaryRef<CompositableBackendSpecificData> CreateCompositableBackendSpecificDataOGL();
+
 /* static */ TemporaryRef<CompositableHost>
 CompositableHost::Create(const TextureInfo& aTextureInfo)
 {
   RefPtr<CompositableHost> result;
   switch (aTextureInfo.mCompositableType) {
   case CompositableType::BUFFER_BRIDGE:
     NS_ERROR("Cannot create an image bridge compositable this way");
     break;
@@ -200,16 +222,22 @@ CompositableHost::Create(const TextureIn
     result = new ContentHostSingleBuffered(aTextureInfo);
     break;
   case CompositableType::CONTENT_DOUBLE:
     result = new ContentHostDoubleBuffered(aTextureInfo);
     break;
   default:
     NS_ERROR("Unknown CompositableType");
   }
+  // We know that Tiled buffers don't use the compositable backend-specific
+  // data, so don't bother creating it.
+  if (result && aTextureInfo.mCompositableType != CompositableType::BUFFER_TILED) {
+    RefPtr<CompositableBackendSpecificData> data = CreateCompositableBackendSpecificDataOGL();
+    result->SetCompositableBackendSpecificData(data);
+  }
   return result;
 }
 
 #ifdef MOZ_DUMP_PAINTING
 void
 CompositableHost::DumpTextureHost(std::stringstream& aStream, TextureHost* aTexture)
 {
   if (!aTexture) {
--- a/gfx/layers/composite/CompositableHost.h
+++ b/gfx/layers/composite/CompositableHost.h
@@ -45,16 +45,52 @@ class Compositor;
 class ISurfaceAllocator;
 class ThebesBufferData;
 class TiledLayerComposer;
 class CompositableParentManager;
 class PCompositableParent;
 struct EffectChain;
 
 /**
+ * A base class for doing CompositableHost and platform dependent task on TextureHost.
+ */
+class CompositableBackendSpecificData
+{
+protected:
+  virtual ~CompositableBackendSpecificData() {}
+
+public:
+  NS_INLINE_DECL_REFCOUNTING(CompositableBackendSpecificData)
+
+  CompositableBackendSpecificData();
+
+  virtual void ClearData() {}
+  virtual void SetCompositor(Compositor* aCompositor) {}
+
+  bool IsAllowingSharingTextureHost()
+  {
+    return mAllowSharingTextureHost;
+  }
+
+  void SetAllowSharingTextureHost(bool aAllow)
+  {
+    mAllowSharingTextureHost = aAllow;
+  }
+
+  uint64_t GetId()
+  {
+    return mId;
+  }
+
+public:
+  bool mAllowSharingTextureHost;
+  uint64_t mId;
+};
+
+/**
  * The compositor-side counterpart to CompositableClient. Responsible for
  * updating textures and data about textures from IPC and how textures are
  * composited (tiling, double buffering, etc.).
  *
  * Update (for images/canvases) and UpdateThebes (for Thebes) are called during
  * the layers transaction to update the Compositbale's textures from the
  * content side. The actual update (and any syncronous upload) is done by the
  * TextureHost, but it is coordinated by the CompositableHost.
@@ -71,16 +107,26 @@ protected:
 public:
   NS_INLINE_DECL_REFCOUNTING(CompositableHost)
   explicit CompositableHost(const TextureInfo& aTextureInfo);
 
   static TemporaryRef<CompositableHost> Create(const TextureInfo& aTextureInfo);
 
   virtual CompositableType GetType() = 0;
 
+  virtual CompositableBackendSpecificData* GetCompositableBackendSpecificData()
+  {
+    return mBackendData;
+  }
+
+  virtual void SetCompositableBackendSpecificData(CompositableBackendSpecificData* aBackendData)
+  {
+    mBackendData = aBackendData;
+  }
+
   // If base class overrides, it should still call the parent implementation
   virtual void SetCompositor(Compositor* aCompositor);
 
   // composite the contents of this buffer host to the compositor's surface
   virtual void Composite(EffectChain& aEffectChain,
                          float aOpacity,
                          const gfx::Matrix4x4& aTransform,
                          const gfx::Filter& aFilter,
@@ -201,16 +247,19 @@ public:
   virtual void Detach(Layer* aLayer = nullptr, AttachFlags aFlags = NO_FLAGS)
   {
     if (!mKeepAttached ||
         aLayer == mLayer ||
         aFlags & FORCE_DETACH) {
       SetLayer(nullptr);
       mAttached = false;
       mKeepAttached = false;
+      if (mBackendData) {
+        mBackendData->ClearData();
+      }
     }
   }
   bool IsAttached() { return mAttached; }
 
 #ifdef MOZ_DUMP_PAINTING
   virtual void Dump(std::stringstream& aStream,
                     const char* aPrefix="",
                     bool aDumpHtml=false) { }
@@ -260,16 +309,17 @@ public:
   }
 
 protected:
   TextureInfo mTextureInfo;
   uint64_t mAsyncID;
   uint64_t mCompositorID;
   RefPtr<Compositor> mCompositor;
   Layer* mLayer;
+  RefPtr<CompositableBackendSpecificData> mBackendData;
   uint32_t mFlashCounter; // used when the pref "layers.flash-borders" is true.
   bool mAttached;
   bool mKeepAttached;
 };
 
 class AutoLockCompositableHost MOZ_FINAL
 {
 public:
--- a/gfx/layers/composite/ContentHost.cpp
+++ b/gfx/layers/composite/ContentHost.cpp
@@ -30,42 +30,38 @@ ContentHostBase::ContentHostBase(const T
   , mInitialised(false)
 {}
 
 ContentHostBase::~ContentHostBase()
 {
 }
 
 void
-ContentHostTexture::Composite(EffectChain& aEffectChain,
-                              float aOpacity,
-                              const gfx::Matrix4x4& aTransform,
-                              const Filter& aFilter,
-                              const Rect& aClipRect,
-                              const nsIntRegion* aVisibleRegion)
+ContentHostBase::Composite(EffectChain& aEffectChain,
+                           float aOpacity,
+                           const gfx::Matrix4x4& aTransform,
+                           const Filter& aFilter,
+                           const Rect& aClipRect,
+                           const nsIntRegion* aVisibleRegion)
 {
   NS_ASSERTION(aVisibleRegion, "Requires a visible region");
 
   AutoLockCompositableHost lock(this);
   if (lock.Failed()) {
     return;
   }
 
-  if (!mTextureHost->BindTextureSource(mTextureSource)) {
-    return;
-  }
-  MOZ_ASSERT(mTextureSource.get());
+  RefPtr<TextureSource> source = GetTextureSource();
+  RefPtr<TextureSource> sourceOnWhite = GetTextureSourceOnWhite();
 
-  if (mTextureHostOnWhite && !mTextureHostOnWhite->BindTextureSource(mTextureSourceOnWhite)) {
+  if (!source) {
     return;
   }
 
-  RefPtr<TexturedEffect> effect = CreateTexturedEffect(mTextureSource.get(),
-                                                       mTextureSourceOnWhite.get(),
-                                                       aFilter, true);
+  RefPtr<TexturedEffect> effect = GenEffect(aFilter);
   if (!effect) {
     return;
   }
 
   aEffectChain.mPrimaryEffect = effect;
 
   nsIntRegion tmpRegion;
   const nsIntRegion* renderRegion;
@@ -80,17 +76,17 @@ ContentHostTexture::Composite(EffectChai
   }
 
   nsIntRegion region(*renderRegion);
   nsIntPoint origin = GetOriginOffset();
   // translate into TexImage space, buffer origin might not be at texture (0,0)
   region.MoveBy(-origin);
 
   // Figure out the intersecting draw region
-  gfx::IntSize texSize = mTextureSource->GetSize();
+  gfx::IntSize texSize = source->GetSize();
   nsIntRect textureRect = nsIntRect(0, 0, texSize.width, texSize.height);
   textureRect.MoveBy(region.GetBounds().TopLeft());
   nsIntRegion subregion;
   subregion.And(region, textureRect);
   if (subregion.IsEmpty()) {
     // Region is empty, nothing to draw
     return;
   }
@@ -104,24 +100,24 @@ ContentHostTexture::Composite(EffectChai
     nsIntRect regionRect = *iterRect;
     nsIntRect screenRect = regionRect;
     screenRect.MoveBy(origin);
 
     screenRects.Or(screenRects, screenRect);
     regionRects.Or(regionRects, regionRect);
   }
 
-  BigImageIterator* bigImgIter = mTextureSource->AsBigImageIterator();
+  BigImageIterator* bigImgIter = source->AsBigImageIterator();
   BigImageIterator* iterOnWhite = nullptr;
   if (bigImgIter) {
     bigImgIter->BeginBigImageIteration();
   }
 
-  if (mTextureSourceOnWhite) {
-    iterOnWhite = mTextureSourceOnWhite->AsBigImageIterator();
+  if (sourceOnWhite) {
+    iterOnWhite = sourceOnWhite->AsBigImageIterator();
     MOZ_ASSERT(!bigImgIter || bigImgIter->GetTileCount() == iterOnWhite->GetTileCount(),
                "Tile count mismatch on component alpha texture");
     if (iterOnWhite) {
       iterOnWhite->BeginBigImageIteration();
     }
   }
 
   bool usingTiles = (bigImgIter && bigImgIter->GetTileCount() > 1);
@@ -204,49 +200,42 @@ ContentHostTexture::Composite(EffectChai
   DiagnosticFlags diagnostics = DiagnosticFlags::CONTENT;
   if (iterOnWhite) {
     diagnostics |= DiagnosticFlags::COMPONENT_ALPHA;
   }
   GetCompositor()->DrawDiagnostics(diagnostics, nsIntRegion(mBufferRect), aClipRect,
                                    aTransform, mFlashCounter);
 }
 
+TemporaryRef<TexturedEffect>
+ContentHostBase::GenEffect(const gfx::Filter& aFilter)
+{
+  RefPtr<TextureSource> source = GetTextureSource();
+  RefPtr<TextureSource> sourceOnWhite = GetTextureSourceOnWhite();
+  if (!source) {
+    return nullptr;
+  }
+  return CreateTexturedEffect(source, sourceOnWhite, aFilter, true);
+}
+
 void
 ContentHostTexture::UseTextureHost(TextureHost* aTexture)
 {
-  if (mTextureHost && mTextureHost != aTexture) {
-    mTextureHost->UnbindTextureSource();
-  }
   ContentHostBase::UseTextureHost(aTexture);
   mTextureHost = aTexture;
   mTextureHostOnWhite = nullptr;
-  if (mTextureHost) {
-    mTextureHost->PrepareTextureSource(mTextureSource);
-  }
 }
 
 void
 ContentHostTexture::UseComponentAlphaTextures(TextureHost* aTextureOnBlack,
                                               TextureHost* aTextureOnWhite)
 {
-  if (mTextureHost && mTextureHost != aTextureOnBlack) {
-    mTextureHost->UnbindTextureSource();
-  }
-  if (mTextureHostOnWhite && mTextureHostOnWhite != aTextureOnWhite) {
-    mTextureHostOnWhite->UnbindTextureSource();
-  }
   ContentHostBase::UseComponentAlphaTextures(aTextureOnBlack, aTextureOnWhite);
   mTextureHost = aTextureOnBlack;
   mTextureHostOnWhite = aTextureOnWhite;
-  if (mTextureHost) {
-    mTextureHost->PrepareTextureSource(mTextureSource);
-  }
-  if (mTextureHostOnWhite) {
-    mTextureHostOnWhite->PrepareTextureSource(mTextureSourceOnWhite);
-  }
 }
 
 void
 ContentHostTexture::SetCompositor(Compositor* aCompositor)
 {
   ContentHostBase::SetCompositor(aCompositor);
   if (mTextureHost) {
     mTextureHost->SetCompositor(aCompositor);
@@ -424,186 +413,16 @@ ContentHostIncremental::UpdateIncrementa
                                                      aSurface,
                                                      aUpdated,
                                                      aBufferRect,
                                                      aBufferRotation));
   FlushUpdateQueue();
 }
 
 void
-ContentHostIncremental::Composite(EffectChain& aEffectChain,
-                                  float aOpacity,
-                                  const gfx::Matrix4x4& aTransform,
-                                  const Filter& aFilter,
-                                  const Rect& aClipRect,
-                                  const nsIntRegion* aVisibleRegion)
-{
-  NS_ASSERTION(aVisibleRegion, "Requires a visible region");
-
-  AutoLockCompositableHost lock(this);
-  if (lock.Failed()) {
-    return;
-  }
-
-  if (!mSource) {
-    return;
-  }
-
-  RefPtr<TexturedEffect> effect = CreateTexturedEffect(mSource.get(),
-                                                       mSourceOnWhite.get(),
-                                                       aFilter, true);
-  if (!effect) {
-    return;
-  }
-
-  aEffectChain.mPrimaryEffect = effect;
-
-  nsIntRegion tmpRegion;
-  const nsIntRegion* renderRegion;
-  if (PaintWillResample()) {
-    // If we're resampling, then the texture image will contain exactly the
-    // entire visible region's bounds, and we should draw it all in one quad
-    // to avoid unexpected aliasing.
-    tmpRegion = aVisibleRegion->GetBounds();
-    renderRegion = &tmpRegion;
-  } else {
-    renderRegion = aVisibleRegion;
-  }
-
-  nsIntRegion region(*renderRegion);
-  nsIntPoint origin = GetOriginOffset();
-  // translate into TexImage space, buffer origin might not be at texture (0,0)
-  region.MoveBy(-origin);
-
-  // Figure out the intersecting draw region
-  gfx::IntSize texSize = mSource->GetSize();
-  nsIntRect textureRect = nsIntRect(0, 0, texSize.width, texSize.height);
-  textureRect.MoveBy(region.GetBounds().TopLeft());
-  nsIntRegion subregion;
-  subregion.And(region, textureRect);
-  if (subregion.IsEmpty()) {
-    // Region is empty, nothing to draw
-    return;
-  }
-
-  nsIntRegion screenRects;
-  nsIntRegion regionRects;
-
-  // Collect texture/screen coordinates for drawing
-  nsIntRegionRectIterator iter(subregion);
-  while (const nsIntRect* iterRect = iter.Next()) {
-    nsIntRect regionRect = *iterRect;
-    nsIntRect screenRect = regionRect;
-    screenRect.MoveBy(origin);
-
-    screenRects.Or(screenRects, screenRect);
-    regionRects.Or(regionRects, regionRect);
-  }
-
-  BigImageIterator* bigImgIter = mSource->AsBigImageIterator();
-  BigImageIterator* iterOnWhite = nullptr;
-  if (bigImgIter) {
-    bigImgIter->BeginBigImageIteration();
-  }
-
-  if (mSourceOnWhite) {
-    iterOnWhite = mSourceOnWhite->AsBigImageIterator();
-    MOZ_ASSERT(!bigImgIter || bigImgIter->GetTileCount() == iterOnWhite->GetTileCount(),
-               "Tile count mismatch on component alpha texture");
-    if (iterOnWhite) {
-      iterOnWhite->BeginBigImageIteration();
-    }
-  }
-
-  bool usingTiles = (bigImgIter && bigImgIter->GetTileCount() > 1);
-  do {
-    if (iterOnWhite) {
-      MOZ_ASSERT(iterOnWhite->GetTileRect() == bigImgIter->GetTileRect(),
-                 "component alpha textures should be the same size.");
-    }
-
-    nsIntRect texRect = bigImgIter ? bigImgIter->GetTileRect()
-                                   : nsIntRect(0, 0,
-                                               texSize.width,
-                                               texSize.height);
-
-    // Draw texture. If we're using tiles, we do repeating manually, as texture
-    // repeat would cause each individual tile to repeat instead of the
-    // compound texture as a whole. This involves drawing at most 4 sections,
-    // 2 for each axis that has texture repeat.
-    for (int y = 0; y < (usingTiles ? 2 : 1); y++) {
-      for (int x = 0; x < (usingTiles ? 2 : 1); x++) {
-        nsIntRect currentTileRect(texRect);
-        currentTileRect.MoveBy(x * texSize.width, y * texSize.height);
-
-        nsIntRegionRectIterator screenIter(screenRects);
-        nsIntRegionRectIterator regionIter(regionRects);
-
-        const nsIntRect* screenRect;
-        const nsIntRect* regionRect;
-        while ((screenRect = screenIter.Next()) &&
-               (regionRect = regionIter.Next())) {
-          nsIntRect tileScreenRect(*screenRect);
-          nsIntRect tileRegionRect(*regionRect);
-
-          // When we're using tiles, find the intersection between the tile
-          // rect and this region rect. Tiling is then handled by the
-          // outer for-loops and modifying the tile rect.
-          if (usingTiles) {
-            tileScreenRect.MoveBy(-origin);
-            tileScreenRect = tileScreenRect.Intersect(currentTileRect);
-            tileScreenRect.MoveBy(origin);
-
-            if (tileScreenRect.IsEmpty())
-              continue;
-
-            tileRegionRect = regionRect->Intersect(currentTileRect);
-            tileRegionRect.MoveBy(-currentTileRect.TopLeft());
-          }
-          gfx::Rect rect(tileScreenRect.x, tileScreenRect.y,
-                         tileScreenRect.width, tileScreenRect.height);
-
-          effect->mTextureCoords = Rect(Float(tileRegionRect.x) / texRect.width,
-                                        Float(tileRegionRect.y) / texRect.height,
-                                        Float(tileRegionRect.width) / texRect.width,
-                                        Float(tileRegionRect.height) / texRect.height);
-          GetCompositor()->DrawQuad(rect, aClipRect, aEffectChain, aOpacity, aTransform);
-          if (usingTiles) {
-            DiagnosticFlags diagnostics = DiagnosticFlags::CONTENT | DiagnosticFlags::BIGIMAGE;
-            if (iterOnWhite) {
-              diagnostics |= DiagnosticFlags::COMPONENT_ALPHA;
-            }
-            GetCompositor()->DrawDiagnostics(diagnostics, rect, aClipRect,
-                                             aTransform, mFlashCounter);
-          }
-        }
-      }
-    }
-
-    if (iterOnWhite) {
-      iterOnWhite->NextTile();
-    }
-  } while (usingTiles && bigImgIter->NextTile());
-
-  if (bigImgIter) {
-    bigImgIter->EndBigImageIteration();
-  }
-  if (iterOnWhite) {
-    iterOnWhite->EndBigImageIteration();
-  }
-
-  DiagnosticFlags diagnostics = DiagnosticFlags::CONTENT;
-  if (iterOnWhite) {
-    diagnostics |= DiagnosticFlags::COMPONENT_ALPHA;
-  }
-  GetCompositor()->DrawDiagnostics(diagnostics, nsIntRegion(mBufferRect), aClipRect,
-                                   aTransform, mFlashCounter);
-}
-
-void
 ContentHostIncremental::FlushUpdateQueue()
 {
   // If we're not compositing for some reason (the window being minimized
   // is one example), then we never process these updates and it can consume
   // huge amounts of memory. Instead we forcibly process the updates (during the
   // transaction) if the list gets too long.
   static const uint32_t kMaxUpdateCount = 6;
   if (mUpdateList.Length() >= kMaxUpdateCount) {
@@ -615,16 +434,30 @@ void
 ContentHostIncremental::ProcessTextureUpdates()
 {
   for (uint32_t i = 0; i < mUpdateList.Length(); i++) {
     mUpdateList[i]->Execute(this);
   }
   mUpdateList.Clear();
 }
 
+TextureSource*
+ContentHostIncremental::GetTextureSource()
+{
+  MOZ_ASSERT(mLocked);
+  return mSource;
+}
+
+TextureSource*
+ContentHostIncremental::GetTextureSourceOnWhite()
+{
+  MOZ_ASSERT(mLocked);
+  return mSourceOnWhite;
+}
+
 void
 ContentHostIncremental::TextureCreationRequest::Execute(ContentHostIncremental* aHost)
 {
   Compositor* compositor = aHost->GetCompositor();
   MOZ_ASSERT(compositor);
 
   RefPtr<DataTextureSource> temp =
     compositor->CreateDataTextureSource(mTextureInfo.mTextureFlags);
@@ -845,42 +678,16 @@ ContentHostTexture::GetRenderState()
 
   if (mBufferRotation != nsIntPoint()) {
     result.mFlags |= LayerRenderStateFlags::BUFFER_ROTATION;
   }
   result.SetOffset(GetOriginOffset());
   return result;
 }
 
-TemporaryRef<TexturedEffect>
-ContentHostTexture::GenEffect(const gfx::Filter& aFilter)
-{
-  if (!mTextureHost) {
-    return nullptr;
-  }
-  if (!mTextureHost->BindTextureSource(mTextureSource)) {
-    return nullptr;
-  }
-  if (mTextureHostOnWhite && !mTextureHostOnWhite->BindTextureSource(mTextureSourceOnWhite)) {
-    return nullptr;
-  }
-  return CreateTexturedEffect(mTextureSource.get(),
-                              mTextureSourceOnWhite.get(),
-                              aFilter, true);
-}
-
-TemporaryRef<TexturedEffect>
-ContentHostIncremental::GenEffect(const gfx::Filter& aFilter)
-{
-  if (!mSource) {
-    return nullptr;
-  }
-  return CreateTexturedEffect(mSource, mSourceOnWhite, aFilter, true);
-}
-
 #ifdef MOZ_DUMP_PAINTING
 TemporaryRef<gfx::DataSourceSurface>
 ContentHostTexture::GetAsSurface()
 {
   if (!mTextureHost) {
     return nullptr;
   }
 
--- a/gfx/layers/composite/ContentHost.h
+++ b/gfx/layers/composite/ContentHost.h
@@ -91,16 +91,28 @@ class ContentHostBase : public ContentHo
 {
 public:
   typedef RotatedContentBuffer::ContentType ContentType;
   typedef RotatedContentBuffer::PaintState PaintState;
 
   explicit ContentHostBase(const TextureInfo& aTextureInfo);
   virtual ~ContentHostBase();
 
+  virtual void Composite(EffectChain& aEffectChain,
+                         float aOpacity,
+                         const gfx::Matrix4x4& aTransform,
+                         const gfx::Filter& aFilter,
+                         const gfx::Rect& aClipRect,
+                         const nsIntRegion* aVisibleRegion = nullptr);
+
+  virtual TextureSource* GetTextureSource() = 0;
+  virtual TextureSource* GetTextureSourceOnWhite() = 0;
+
+  virtual TemporaryRef<TexturedEffect> GenEffect(const gfx::Filter& aFilter) MOZ_OVERRIDE;
+
 protected:
   virtual nsIntPoint GetOriginOffset()
   {
     return mBufferRect.TopLeft() - mBufferRotation;
   }
 
 
   nsIntRect mBufferRect;
@@ -115,23 +127,16 @@ protected:
 class ContentHostTexture : public ContentHostBase
 {
 public:
   explicit ContentHostTexture(const TextureInfo& aTextureInfo)
     : ContentHostBase(aTextureInfo)
     , mLocked(false)
   { }
 
-  virtual void Composite(EffectChain& aEffectChain,
-                         float aOpacity,
-                         const gfx::Matrix4x4& aTransform,
-                         const gfx::Filter& aFilter,
-                         const gfx::Rect& aClipRect,
-                         const nsIntRegion* aVisibleRegion = nullptr);
-
   virtual void SetCompositor(Compositor* aCompositor) MOZ_OVERRIDE;
 
 #ifdef MOZ_DUMP_PAINTING
   virtual TemporaryRef<gfx::DataSourceSurface> GetAsSurface() MOZ_OVERRIDE;
 
   virtual void Dump(std::stringstream& aStream,
                     const char* aPrefix="",
                     bool aDumpHtml=false) MOZ_OVERRIDE;
@@ -163,25 +168,33 @@ public:
     MOZ_ASSERT(mLocked);
     mTextureHost->Unlock();
     if (mTextureHostOnWhite) {
       mTextureHostOnWhite->Unlock();
     }
     mLocked = false;
   }
 
-  LayerRenderState GetRenderState();
+  virtual TextureSource* GetTextureSource() MOZ_OVERRIDE {
+    MOZ_ASSERT(mLocked);
+    return mTextureHost->GetTextureSources();
+  }
+  virtual TextureSource* GetTextureSourceOnWhite() MOZ_OVERRIDE {
+    MOZ_ASSERT(mLocked);
+    if (mTextureHostOnWhite) {
+      return mTextureHostOnWhite->GetTextureSources();
+    }
+    return nullptr;
+  }
 
-  virtual TemporaryRef<TexturedEffect> GenEffect(const gfx::Filter& aFilter) MOZ_OVERRIDE;
+  LayerRenderState GetRenderState();
 
 protected:
   RefPtr<TextureHost> mTextureHost;
   RefPtr<TextureHost> mTextureHostOnWhite;
-  CompositableTextureSourceRef mTextureSource;
-  CompositableTextureSourceRef mTextureSourceOnWhite;
   bool mLocked;
 };
 
 /**
  * Double buffering is implemented by swapping the front and back TextureHosts.
  * We assume that whenever we use double buffering, then we have
  * render-to-texture and thus no texture upload to do.
  */
@@ -259,39 +272,32 @@ public:
                             const nsIntRegion& aUpdated,
                             const nsIntRegion& aOldValidRegionBack,
                             nsIntRegion* aUpdatedRegionBack)
   {
     NS_ERROR("Shouldn't call this");
     return false;
   }
 
-  virtual void Composite(EffectChain& aEffectChain,
-                         float aOpacity,
-                         const gfx::Matrix4x4& aTransform,
-                         const gfx::Filter& aFilter,
-                         const gfx::Rect& aClipRect,
-                         const nsIntRegion* aVisibleRegion = nullptr);
-
   virtual void PrintInfo(std::stringstream& aStream, const char* aPrefix) MOZ_OVERRIDE;
 
   virtual bool Lock() MOZ_OVERRIDE {
     MOZ_ASSERT(!mLocked);
     ProcessTextureUpdates();
     mLocked = true;
     return true;
   }
 
   virtual void Unlock() MOZ_OVERRIDE {
     MOZ_ASSERT(mLocked);
     mLocked = false;
   }
 
-  virtual TemporaryRef<TexturedEffect>
-  GenEffect(const gfx::Filter& aFilter) MOZ_OVERRIDE;
+  virtual TextureSource* GetTextureSource() MOZ_OVERRIDE;
+  virtual TextureSource* GetTextureSourceOnWhite() MOZ_OVERRIDE;
 
 private:
 
   void FlushUpdateQueue();
   void ProcessTextureUpdates();
 
   class Request
   {
--- a/gfx/layers/composite/ImageHost.cpp
+++ b/gfx/layers/composite/ImageHost.cpp
@@ -32,40 +32,46 @@ ImageHost::ImageHost(const TextureInfo& 
   , mFrontBuffer(nullptr)
   , mHasPictureRect(false)
   , mLocked(false)
 {}
 
 ImageHost::~ImageHost()
 {
   if (mFrontBuffer) {
-    mFrontBuffer->UnbindTextureSource();
+    mFrontBuffer->UnsetCompositableBackendSpecificData(GetCompositableBackendSpecificData());
+  }
+}
+
+void
+ImageHost::SetCompositableBackendSpecificData(CompositableBackendSpecificData* aBackendData)
+{
+  CompositableHost::SetCompositableBackendSpecificData(aBackendData);
+  // ImageHost allows TextureHost sharing among ImageHosts.
+  if (aBackendData) {
+    aBackendData->SetAllowSharingTextureHost(true);
   }
 }
 
 void
 ImageHost::UseTextureHost(TextureHost* aTexture)
 {
-  if (mFrontBuffer && mFrontBuffer != aTexture) {
-    mFrontBuffer->UnbindTextureSource();
+  CompositableHost::UseTextureHost(aTexture);
+  if (mFrontBuffer) {
+    mFrontBuffer->UnsetCompositableBackendSpecificData(GetCompositableBackendSpecificData());
   }
-  CompositableHost::UseTextureHost(aTexture);
   mFrontBuffer = aTexture;
-  if (mFrontBuffer) {
-    mFrontBuffer->PrepareTextureSource(mTextureSource);
-  }
 }
 
 void
 ImageHost::RemoveTextureHost(TextureHost* aTexture)
 {
   CompositableHost::RemoveTextureHost(aTexture);
   if (aTexture && mFrontBuffer == aTexture) {
-    mFrontBuffer->UnbindTextureSource();
-    mTextureSource = nullptr;
+    aTexture->SetCompositableBackendSpecificData(nullptr);
     mFrontBuffer = nullptr;
   }
 }
 
 TextureHost*
 ImageHost::GetAsTextureHost()
 {
   return mFrontBuffer;
@@ -86,68 +92,59 @@ ImageHost::Composite(EffectChain& aEffec
     return;
   }
   if (!mFrontBuffer) {
     return;
   }
 
   // Make sure the front buffer has a compositor
   mFrontBuffer->SetCompositor(GetCompositor());
+  mFrontBuffer->SetCompositableBackendSpecificData(GetCompositableBackendSpecificData());
 
   AutoLockCompositableHost autoLock(this);
   if (autoLock.Failed()) {
     NS_WARNING("failed to lock front buffer");
     return;
   }
-
-  if (!mFrontBuffer->BindTextureSource(mTextureSource)) {
+  RefPtr<TextureSource> source = GetTextureSource();
+  if (!source) {
     return;
   }
 
-  if (!mTextureSource) {
-    // BindTextureSource above should have returned false!
-    MOZ_ASSERT(false);
-    return;
-  }
-
-  bool isAlphaPremultiplied = !(mFrontBuffer->GetFlags() & TextureFlags::NON_PREMULTIPLIED);
-  RefPtr<TexturedEffect> effect = CreateTexturedEffect(mFrontBuffer->GetFormat(),
-                                                       mTextureSource.get(),
-                                                       aFilter,
-                                                       isAlphaPremultiplied);
+  RefPtr<TexturedEffect> effect = GenEffect(aFilter);
   if (!effect) {
     return;
   }
 
   aEffectChain.mPrimaryEffect = effect;
-  IntSize textureSize = mTextureSource->GetSize();
+  IntSize textureSize = source->GetSize();
   gfx::Rect gfxPictureRect
     = mHasPictureRect ? gfx::Rect(0, 0, mPictureRect.width, mPictureRect.height)
                       : gfx::Rect(0, 0, textureSize.width, textureSize.height);
 
   gfx::Rect pictureRect(0, 0,
                         mPictureRect.width,
                         mPictureRect.height);
-  BigImageIterator* it = mTextureSource->AsBigImageIterator();
+  BigImageIterator* it = source->AsBigImageIterator();
   if (it) {
 
     // This iteration does not work if we have multiple texture sources here
     // (e.g. 3 YCbCr textures). There's nothing preventing the different
     // planes from having different resolutions or tile sizes. For example, a
     // YCbCr frame could have Cb and Cr planes that are half the resolution of
     // the Y plane, in such a way that the Y plane overflows the maximum
     // texture size and the Cb and Cr planes do not. Then the Y plane would be
     // split into multiple tiles and the Cb and Cr planes would just be one
     // tile each.
     // To handle the general case correctly, we'd have to create a grid of
     // intersected tiles over all planes, and then draw each grid tile using
     // the corresponding source tiles from all planes, with appropriate
     // per-plane per-tile texture coords.
     // DrawQuad currently assumes that all planes use the same texture coords.
-    MOZ_ASSERT(it->GetTileCount() == 1 || !mTextureSource->GetNextSibling(),
+    MOZ_ASSERT(it->GetTileCount() == 1 || !source->GetNextSibling(),
                "Can't handle multi-plane BigImages");
 
     it->BeginBigImageIteration();
     do {
       nsIntRect tileRect = it->GetTileRect();
       gfx::Rect rect(tileRect.x, tileRect.y, tileRect.width, tileRect.height);
       if (mHasPictureRect) {
         rect = rect.Intersect(pictureRect);
@@ -168,17 +165,17 @@ ImageHost::Composite(EffectChain& aEffec
                                        rect, aClipRect, aTransform, mFlashCounter);
     } while (it->NextTile());
     it->EndBigImageIteration();
     // layer border
     GetCompositor()->DrawDiagnostics(DiagnosticFlags::IMAGE,
                                      gfxPictureRect, aClipRect,
                                      aTransform, mFlashCounter);
   } else {
-    IntSize textureSize = mTextureSource->GetSize();
+    IntSize textureSize = source->GetSize();
     gfx::Rect rect;
     if (mHasPictureRect) {
       effect->mTextureCoords = Rect(Float(mPictureRect.x) / textureSize.width,
                                     Float(mPictureRect.y) / textureSize.height,
                                     Float(mPictureRect.width) / textureSize.width,
                                     Float(mPictureRect.height) / textureSize.height);
       rect = pictureRect;
     } else {
@@ -271,28 +268,36 @@ ImageHost::Lock()
 void
 ImageHost::Unlock()
 {
   MOZ_ASSERT(mLocked);
   mFrontBuffer->Unlock();
   mLocked = false;
 }
 
+TemporaryRef<TextureSource>
+ImageHost::GetTextureSource()
+{
+  MOZ_ASSERT(mLocked);
+  return mFrontBuffer->GetTextureSources();
+}
+
 TemporaryRef<TexturedEffect>
 ImageHost::GenEffect(const gfx::Filter& aFilter)
 {
-  if (!mFrontBuffer->BindTextureSource(mTextureSource)) {
+  RefPtr<TextureSource> source = GetTextureSource();
+  if (!source) {
     return nullptr;
   }
   bool isAlphaPremultiplied = true;
   if (mFrontBuffer->GetFlags() & TextureFlags::NON_PREMULTIPLIED)
     isAlphaPremultiplied = false;
 
   return CreateTexturedEffect(mFrontBuffer->GetFormat(),
-                              mTextureSource,
+                              source,
                               aFilter,
                               isAlphaPremultiplied);
 }
 
 #ifdef MOZ_WIDGET_GONK
 ImageHostOverlay::ImageHostOverlay(const TextureInfo& aTextureInfo)
   : CompositableHost(aTextureInfo)
   , mHasPictureRect(false)
--- a/gfx/layers/composite/ImageHost.h
+++ b/gfx/layers/composite/ImageHost.h
@@ -40,16 +40,18 @@ struct EffectChain;
 class ImageHost : public CompositableHost
 {
 public:
   explicit ImageHost(const TextureInfo& aTextureInfo);
   ~ImageHost();
 
   virtual CompositableType GetType() { return mTextureInfo.mCompositableType; }
 
+  virtual void SetCompositableBackendSpecificData(CompositableBackendSpecificData* aBackendData) MOZ_OVERRIDE;
+
   virtual void Composite(EffectChain& aEffectChain,
                          float aOpacity,
                          const gfx::Matrix4x4& aTransform,
                          const gfx::Filter& aFilter,
                          const gfx::Rect& aClipRect,
                          const nsIntRegion* aVisibleRegion = nullptr) MOZ_OVERRIDE;
 
   virtual void UseTextureHost(TextureHost* aTexture) MOZ_OVERRIDE;
@@ -77,22 +79,23 @@ public:
 
   virtual TemporaryRef<gfx::DataSourceSurface> GetAsSurface() MOZ_OVERRIDE;
 #endif
 
   virtual bool Lock() MOZ_OVERRIDE;
 
   virtual void Unlock() MOZ_OVERRIDE;
 
+  virtual TemporaryRef<TextureSource> GetTextureSource();
+
   virtual TemporaryRef<TexturedEffect> GenEffect(const gfx::Filter& aFilter) MOZ_OVERRIDE;
 
 protected:
 
   RefPtr<TextureHost> mFrontBuffer;
-  CompositableTextureSourceRef mTextureSource;
   nsIntRect mPictureRect;
   bool mHasPictureRect;
   bool mLocked;
 };
 
 #ifdef MOZ_WIDGET_GONK
 
 /**
--- a/gfx/layers/composite/TextureHost.cpp
+++ b/gfx/layers/composite/TextureHost.cpp
@@ -139,23 +139,16 @@ TextureHost::AsTextureHost(PTextureParen
 }
 
 PTextureParent*
 TextureHost::GetIPDLActor()
 {
   return mActor;
 }
 
-bool
-TextureHost::BindTextureSource(CompositableTextureSourceRef& texture)
-{
-  texture = GetTextureSources();
-  return !!texture;
-}
-
 FenceHandle
 TextureHost::GetAndResetReleaseFenceHandle()
 {
 #if defined(MOZ_WIDGET_GONK) && ANDROID_VERSION >= 17
   TextureHostOGL* hostOGL = this->AsHostOGL();
   if (!hostOGL) {
     return FenceHandle();
   }
@@ -279,16 +272,28 @@ void
 TextureHost::CompositorRecycle()
 {
   if (!mActor) {
     return;
   }
   static_cast<TextureParent*>(mActor)->CompositorRecycle();
 }
 
+void
+TextureHost::SetCompositableBackendSpecificData(CompositableBackendSpecificData* aBackendData)
+{
+  mCompositableBackendData = aBackendData;
+}
+
+void
+TextureHost::UnsetCompositableBackendSpecificData(CompositableBackendSpecificData* aBackendData)
+{
+  mCompositableBackendData = nullptr;
+}
+
 TextureHost::TextureHost(TextureFlags aFlags)
     : mActor(nullptr)
     , mFlags(aFlags)
 {}
 
 TextureHost::~TextureHost()
 {
 }
@@ -312,21 +317,19 @@ TextureHost::PrintInfo(std::stringstream
     AppendToString(aStream, GetSize(), " [size=", "]");
     AppendToString(aStream, GetFormat(), " [format=", "]");
     Unlock();
   }
   AppendToString(aStream, mFlags, " [flags=", "]");
 }
 
 TextureSource::TextureSource()
-: mCompositableCount(0)
 {
     MOZ_COUNT_CTOR(TextureSource);
 }
-
 TextureSource::~TextureSource()
 {
     MOZ_COUNT_DTOR(TextureSource);
 }
 
 BufferTextureHost::BufferTextureHost(gfx::SurfaceFormat aFormat,
                                      TextureFlags aFlags)
 : TextureHost(aFlags)
@@ -836,36 +839,34 @@ SharedSurfaceToTexSource(gl::SharedSurfa
       auto surf = gl::SharedSurface_GLTexture::Cast(abstractSurf);
 
       MOZ_ASSERT(compositor->GetBackendType() == LayersBackend::LAYERS_OPENGL);
       CompositorOGL* compositorOGL = static_cast<CompositorOGL*>(compositor);
       gl::GLContext* gl = compositorOGL->gl();
 
       GLenum target = surf->ConsTextureTarget();
       GLuint tex = surf->ConsTexture(gl);
-      texSource = new GLTextureSource(compositorOGL, tex, target,
-                                      surf->mSize, format,
-                                      true/*externally owned*/);
+      texSource = new GLTextureSource(compositorOGL, tex, format, target,
+                                      surf->mSize);
       break;
     }
     case gl::SharedSurfaceType::EGLImageShare: {
       auto surf = gl::SharedSurface_EGLImage::Cast(abstractSurf);
 
       MOZ_ASSERT(compositor->GetBackendType() == LayersBackend::LAYERS_OPENGL);
       CompositorOGL* compositorOGL = static_cast<CompositorOGL*>(compositor);
       gl::GLContext* gl = compositorOGL->gl();
       MOZ_ASSERT(gl->IsCurrent());
 
       GLenum target = 0;
       GLuint tex = 0;
       surf->AcquireConsumerTexture(gl, &tex, &target);
 
-      texSource = new GLTextureSource(compositorOGL, tex, target,
-                                      surf->mSize, format,
-                                      true/*externally owned*/);
+      texSource = new GLTextureSource(compositorOGL, tex, format, target,
+                                      surf->mSize);
       break;
     }
 #ifdef XP_MACOSX
     case gl::SharedSurfaceType::IOSurface: {
       auto surf = gl::SharedSurface_IOSurface::Cast(abstractSurf);
       MacIOSurface* ioSurf = surf->GetIOSurface();
 
       MOZ_ASSERT(compositor->GetBackendType() == LayersBackend::LAYERS_OPENGL);
--- a/gfx/layers/composite/TextureHost.h
+++ b/gfx/layers/composite/TextureHost.h
@@ -41,16 +41,17 @@ class SharedSurface;
 namespace ipc {
 class Shmem;
 }
 
 namespace layers {
 
 class Compositor;
 class CompositableHost;
+class CompositableBackendSpecificData;
 class CompositableParentManager;
 class SurfaceDescriptor;
 class SharedSurfaceDescriptor;
 class ISurfaceAllocator;
 class TextureHostOGL;
 class TextureSourceOGL;
 class TextureSourceD3D9;
 class TextureSourceD3D11;
@@ -144,79 +145,20 @@ public:
     switch (index) {
       case 0: return this;
       case 1: return GetNextSibling();
       case 2: return GetNextSibling() ? GetNextSibling()->GetNextSibling() : nullptr;
     }
     return nullptr;
   }
 
-  void AddCompositableRef() { ++mCompositableCount; }
-
-  void ReleaseCompositableRef() {
-    --mCompositableCount;
-    MOZ_ASSERT(mCompositableCount >= 0);
-  }
-
-  int NumCompositableRefs() const { return mCompositableCount; }
-
 protected:
   virtual ~TextureSource();
 
   RefPtr<TextureSource> mNextSibling;
-  int mCompositableCount;
-};
-
-/**
- * equivalent of a RefPtr<TextureSource>, that calls AddCompositableRef and
- * ReleaseCompositableRef in addition to the usual AddRef and Release.
- */
-class CompositableTextureSourceRef {
-public:
-  CompositableTextureSourceRef() {}
-
-  ~CompositableTextureSourceRef()
-  {
-    if (mRef) {
-      mRef->ReleaseCompositableRef();
-    }
-  }
-
-  CompositableTextureSourceRef& operator=(const TemporaryRef<TextureSource>& aOther)
-  {
-    RefPtr<TextureSource> temp = aOther;
-    if (temp) {
-      temp->AddCompositableRef();
-    }
-    if (mRef) {
-      mRef->ReleaseCompositableRef();
-    }
-    mRef = temp;
-    return *this;
-  }
-
-  CompositableTextureSourceRef& operator=(TextureSource* aOther)
-  {
-    if (aOther) {
-      aOther->AddCompositableRef();
-    }
-    if (mRef) {
-      mRef->ReleaseCompositableRef();
-    }
-    mRef = aOther;
-    return *this;
-  }
-
-  TextureSource* get() const { return mRef; }
-  operator TextureSource*() const { return mRef; }
-  TextureSource* operator->() const { return mRef; }
-  TextureSource& operator*() const { return *mRef; }
-
-private:
-  RefPtr<TextureSource> mRef;
 };
 
 /**
  * Interface for TextureSources that can be updated from a DataSourceSurface.
  *
  * All backend should implement at least one DataTextureSource.
  */
 class DataTextureSource : public TextureSource
@@ -356,35 +298,16 @@ public:
    *
    * This can trigger texture uploads, so do not call it inside transactions
    * so as to not upload textures while the main thread is blocked.
    * Must not be called while this TextureHost is not sucessfully Locked.
    */
   virtual TextureSource* GetTextureSources() = 0;
 
   /**
-   * Called during the transaction. The TextureSource may or may not be composited.
-   *
-   * Note that this is called outside of lock/unlock.
-   */
-  virtual void PrepareTextureSource(CompositableTextureSourceRef& aTexture) {}
-
-  /**
-   * Called at composition time, just before compositing the TextureSource composited.
-   *
-   * Note that this is called only withing lock/unlock.
-   */
-  virtual bool BindTextureSource(CompositableTextureSourceRef& aTexture);
-
-  /**
-   * Called when another TextureHost will take over.
-   */
-  virtual void UnbindTextureSource() {}
-
-  /**
    * Is called before compositing if the shared data has changed since last
    * composition.
    * This method should be overload in cases like when we need to do a texture
    * upload for example.
    *
    * @param aRegion The region that has been changed, if nil, it means that the
    * entire surface should be updated.
    */
@@ -478,16 +401,20 @@ public:
    */
   virtual LayerRenderState GetRenderState()
   {
     // By default we return an empty render state, this should be overridden
     // by the TextureHost implementations that are used on B2G with Composer2D
     return LayerRenderState();
   }
 
+  virtual void SetCompositableBackendSpecificData(CompositableBackendSpecificData* aBackendData);
+
+  virtual void UnsetCompositableBackendSpecificData(CompositableBackendSpecificData* aBackendData);
+
   // If a texture host holds a reference to shmem, it should override this method
   // to forget about the shmem _without_ releasing it.
   virtual void OnShutdown() {}
 
   // Forget buffer actor. Used only for hacky fix for bug 966446.
   virtual void ForgetBufferActor() {}
 
   virtual const char *Name() { return "TextureHost"; }
@@ -503,16 +430,17 @@ public:
   /**
    * Cast to a TextureHost for each backend.
    */
   virtual TextureHostOGL* AsHostOGL() { return nullptr; }
 
 protected:
   PTextureParent* mActor;
   TextureFlags mFlags;
+  RefPtr<CompositableBackendSpecificData> mCompositableBackendData;
 
   friend class TextureParent;
 };
 
 /**
  * TextureHost that wraps a random access buffer such as a Shmem or some raw
  * memory.
  *
--- a/gfx/layers/moz.build
+++ b/gfx/layers/moz.build
@@ -235,16 +235,17 @@ if CONFIG['MOZ_WIDGET_TOOLKIT'] == 'gonk
         ]
 
 UNIFIED_SOURCES += [
     'apz/src/APZCTreeManager.cpp',
     'apz/src/AsyncPanZoomController.cpp',
     'apz/src/Axis.cpp',
     'apz/src/GestureEventListener.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',
     'AxisPhysicsModel.cpp',
     'AxisPhysicsMSDModel.cpp',
     'basic/BasicCanvasLayer.cpp',
--- a/gfx/layers/opengl/GrallocTextureHost.cpp
+++ b/gfx/layers/opengl/GrallocTextureHost.cpp
@@ -131,28 +131,43 @@ GrallocTextureSourceOGL::BindTexture(GLe
   }
 
   GLuint tex = GetGLTexture();
   GLuint textureTarget = GetTextureTarget();
 
   gl()->fActiveTexture(aTextureUnit);
   gl()->fBindTexture(textureTarget, tex);
 
+  if (mTextureBackendSpecificData) {
+    // There are two paths for locking/unlocking - if mTextureBackendSpecificData is
+    // set, we use the texture on there, otherwise we use
+    // CompositorBackendSpecificData from the compositor and bind the EGLImage
+    // only in Lock().
+    if (!mEGLImage) {
+      mEGLImage = EGLImageCreateFromNativeBuffer(gl(), mGraphicBuffer->getNativeBuffer());
+    }
+    BindEGLImage();
+  }
+
   ApplyFilterToBoundTexture(gl(), aFilter, textureTarget);
 
 #if defined(MOZ_WIDGET_GONK) && ANDROID_VERSION >= 17
   if (mTextureHost) {
     // Wait until it's ready.
     mTextureHost->WaitAcquireFenceSyncComplete();
   }
 #endif
 }
 
 bool GrallocTextureSourceOGL::Lock()
 {
+  if (mTextureBackendSpecificData) {
+    return true;
+  }
+
   MOZ_ASSERT(IsValid());
   if (!IsValid()) {
     return false;
   }
   if (!gl()->MakeCurrent()) {
     NS_WARNING("Failed to make the gl context current");
     return false;
   }
@@ -168,17 +183,17 @@ bool GrallocTextureSourceOGL::Lock()
   }
   gl()->fEGLImageTargetTexture2D(textureTarget, mEGLImage);
   return true;
 }
 
 bool
 GrallocTextureSourceOGL::IsValid() const
 {
-  return !!gl() && !!mGraphicBuffer.get() && !!mCompositor;
+  return !!gl() && !!mGraphicBuffer.get() && (!!mCompositor || !!mTextureBackendSpecificData);
 }
 
 gl::GLContext*
 GrallocTextureSourceOGL::gl() const
 {
   return mCompositor ? mCompositor->gl() : nullptr;
 }
 
@@ -210,16 +225,72 @@ GrallocTextureSourceOGL::GetTextureTarge
   if (gl()->Renderer() == gl::GLRenderer::SGX530 ||
       gl()->Renderer() == gl::GLRenderer::SGX540) {
     return LOCAL_GL_TEXTURE_EXTERNAL;
   }
 
   return TextureTargetForAndroidPixelFormat(mGraphicBuffer->getPixelFormat());
 }
 
+void
+GrallocTextureSourceOGL::SetTextureBackendSpecificData(TextureSharedDataGonkOGL* aBackendData)
+{
+  if (!aBackendData) {
+    DeallocateDeviceData();
+    // Update mTextureBackendSpecificData after calling DeallocateDeviceData().
+    mTextureBackendSpecificData = nullptr;
+    return;
+  }
+
+  if (mTextureBackendSpecificData != aBackendData) {
+    mNeedsReset = true;
+  }
+
+  if (!gl() || !gl()->MakeCurrent()) {
+    NS_WARNING("Failed to make the context current");
+    return;
+  }
+
+  if (!mNeedsReset) {
+    // Update binding to the EGLImage
+    GLuint tex = GetGLTexture();
+    GLuint textureTarget = GetTextureTarget();
+    gl()->fActiveTexture(LOCAL_GL_TEXTURE0);
+    gl()->fBindTexture(textureTarget, tex);
+    BindEGLImage();
+    return;
+  }
+
+  if (!mCompositor) {
+    mTextureBackendSpecificData = aBackendData;
+    return;
+  }
+
+  // delete old EGLImage
+  DeallocateDeviceData();
+
+  // Update mTextureBackendSpecificData after calling DeallocateDeviceData().
+  mTextureBackendSpecificData = aBackendData;
+
+  GLuint tex = GetGLTexture();
+  GLuint textureTarget = GetTextureTarget();
+
+  gl()->fActiveTexture(LOCAL_GL_TEXTURE0);
+  gl()->fBindTexture(textureTarget, tex);
+
+  // Setup texure parameters at the first binding.
+  gl()->fTexParameteri(textureTarget, LOCAL_GL_TEXTURE_WRAP_T, GetWrapMode());
+  gl()->fTexParameteri(textureTarget, LOCAL_GL_TEXTURE_WRAP_S, GetWrapMode());
+
+  // create new EGLImage
+  mEGLImage = EGLImageCreateFromNativeBuffer(gl(), mGraphicBuffer->getNativeBuffer());
+  BindEGLImage();
+  mNeedsReset = false;
+}
+
 gfx::IntSize
 GrallocTextureSourceOGL::GetSize() const
 {
   if (!IsValid()) {
     NS_WARNING("Trying to access the size of an invalid GrallocTextureSourceOGL");
     return gfx::IntSize(0, 0);
   }
   return gfx::IntSize(mGraphicBuffer->getWidth(), mGraphicBuffer->getHeight());
@@ -228,100 +299,103 @@ GrallocTextureSourceOGL::GetSize() const
 void
 GrallocTextureSourceOGL::DeallocateDeviceData()
 {
   if (mEGLImage) {
     MOZ_ASSERT(mCompositor);
     if (!gl() || !gl()->MakeCurrent()) {
       return;
     }
+    if (mTextureBackendSpecificData) {
+      mTextureBackendSpecificData->ClearBoundEGLImage(mEGLImage);
+    }
     EGLImageDestroy(gl(), mEGLImage);
     mEGLImage = EGL_NO_IMAGE;
   }
 }
 
 GrallocTextureHostOGL::GrallocTextureHostOGL(TextureFlags aFlags,
                                              const NewSurfaceDescriptorGralloc& aDescriptor)
   : TextureHost(aFlags)
-  , mGrallocHandle(aDescriptor)
-  , mSize(0, 0)
-  , mDescriptorSize(aDescriptor.size())
-  , mFormat(gfx::SurfaceFormat::UNKNOWN)
-  , mEGLImage(EGL_NO_IMAGE)
 {
+  gfx::SurfaceFormat format = gfx::SurfaceFormat::UNKNOWN;
+  mGrallocHandle = aDescriptor;
+
   android::GraphicBuffer* graphicBuffer = GetGraphicBufferFromDesc(mGrallocHandle).get();
   MOZ_ASSERT(graphicBuffer);
 
+  mSize = aDescriptor.size();
   if (graphicBuffer) {
-    mFormat =
+    format =
       SurfaceFormatForAndroidPixelFormat(graphicBuffer->getPixelFormat(),
                                          aFlags & TextureFlags::RB_SWAPPED);
-    mSize = gfx::IntSize(graphicBuffer->getWidth(), graphicBuffer->getHeight());
+    mTextureSource = new GrallocTextureSourceOGL(nullptr,
+                                                 this,
+                                                 graphicBuffer,
+                                                 format);
   } else {
     printf_stderr("gralloc buffer is nullptr");
   }
 }
 
 GrallocTextureHostOGL::~GrallocTextureHostOGL()
-{}
+{
+  MOZ_ASSERT(!mTextureSource || (mFlags & TextureFlags::DEALLOCATE_CLIENT),
+             "Leaking our buffer");
+}
 
 void
 GrallocTextureHostOGL::SetCompositor(Compositor* aCompositor)
 {
-  mCompositor = static_cast<CompositorOGL*>(aCompositor);
-  if (mTilingTextureSource) {
-    mTilingTextureSource->SetCompositor(mCompositor);
-  }
-  if (mGLTextureSource) {
-    mGLTextureSource->SetCompositor(mCompositor);
-  }
-
-  if (mCompositor && aCompositor != mCompositor) {
-    DestroyEGLImage();
+  if (mTextureSource) {
+    mTextureSource->SetCompositor(static_cast<CompositorOGL*>(aCompositor));
   }
 }
 
 bool
 GrallocTextureHostOGL::Lock()
 {
-  return IsValid();
+  if (IsValid()) {
+    mTextureSource->Lock();
+    return true;
+  }
+  return false;
 }
 
 void
 GrallocTextureHostOGL::Unlock()
 {
   // Unlock is done internally by binding the texture to another gralloc buffer
 }
 
 bool
 GrallocTextureHostOGL::IsValid() const
 {
-  android::GraphicBuffer* graphicBuffer = GetGraphicBufferFromDesc(mGrallocHandle).get();
-  return graphicBuffer != nullptr;
+  if (!mTextureSource) {
+    return false;
+  }
+  return mTextureSource->IsValid();
 }
 
 gfx::SurfaceFormat
 GrallocTextureHostOGL::GetFormat() const
 {
-  return mFormat;
+  if (!mTextureSource) {
+    return gfx::SurfaceFormat::UNKNOWN;
+  }
+  return mTextureSource->GetFormat();
 }
 
 void
 GrallocTextureHostOGL::DeallocateSharedData()
 {
-  if (mTilingTextureSource) {
-    mTilingTextureSource->ForgetBuffer();
-    mTilingTextureSource = nullptr;
+  if (mTextureSource) {
+    mTextureSource->ForgetBuffer();
+    mTextureSource = nullptr;
   }
-  if (mGLTextureSource) {
-    mGLTextureSource = nullptr;
-  }
-
-  DestroyEGLImage();
-
   if (mGrallocHandle.buffer().type() != SurfaceDescriptor::Tnull_t) {
     MaybeMagicGrallocBufferHandle handle = mGrallocHandle.buffer();
     base::ProcessId owner;
     if (handle.type() == MaybeMagicGrallocBufferHandle::TGrallocBufferRef) {
       owner = handle.get_GrallocBufferRef().mOwner;
     }
     else {
       owner = handle.get_MagicGrallocBufferHandle().mRef.mOwner;
@@ -329,63 +403,54 @@ GrallocTextureHostOGL::DeallocateSharedD
 
     SharedBufferManagerParent::DropGrallocBuffer(owner, mGrallocHandle);
   }
 }
 
 void
 GrallocTextureHostOGL::ForgetSharedData()
 {
-  if (mTilingTextureSource) {
-    mTilingTextureSource->ForgetBuffer();
-    mTilingTextureSource = nullptr;
-  }
-  if (mGLTextureSource) {
-    mGLTextureSource = nullptr;
+  if (mTextureSource) {
+    mTextureSource->ForgetBuffer();
+    mTextureSource = nullptr;
   }
 }
 
 void
 GrallocTextureHostOGL::DeallocateDeviceData()
 {
-  if (mTilingTextureSource) {
-    mTilingTextureSource->DeallocateDeviceData();
+  if (mTextureSource) {
+    mTextureSource->DeallocateDeviceData();
   }
-  if (mGLTextureSource) {
-    mGLTextureSource = nullptr;
-  }
-  DestroyEGLImage();
 }
 
 LayerRenderState
 GrallocTextureHostOGL::GetRenderState()
 {
-  android::GraphicBuffer* graphicBuffer = GetGraphicBufferFromDesc(mGrallocHandle).get();
-
-  if (graphicBuffer) {
+  if (IsValid()) {
     LayerRenderStateFlags flags = LayerRenderStateFlags::LAYER_RENDER_STATE_DEFAULT;
     if (mFlags & TextureFlags::NEEDS_Y_FLIP) {
       flags |= LayerRenderStateFlags::Y_FLIPPED;
     }
     if (mFlags & TextureFlags::RB_SWAPPED) {
       flags |= LayerRenderStateFlags::FORMAT_RB_SWAP;
     }
-    return LayerRenderState(graphicBuffer,
-                            gfx::ThebesIntSize(mDescriptorSize),
+    return LayerRenderState(mTextureSource->mGraphicBuffer.get(),
+                            gfx::ThebesIntSize(mSize),
                             flags,
                             this);
   }
 
   return LayerRenderState();
 }
 
 TemporaryRef<gfx::DataSourceSurface>
 GrallocTextureHostOGL::GetAsSurface() {
-  return mTilingTextureSource ? mTilingTextureSource->GetAsSurface()
-                              : nullptr;
+  return mTextureSource ? mTextureSource->GetAsSurface()
+                        : nullptr;
 }
 
 TemporaryRef<gfx::DataSourceSurface>
 GrallocTextureSourceOGL::GetAsSurface() {
   if (!IsValid() || !gl()->MakeCurrent()) {
     return nullptr;
   }
 
@@ -403,192 +468,109 @@ GrallocTextureSourceOGL::GetAsSurface() 
 
   gl()->fActiveTexture(LOCAL_GL_TEXTURE0);
   return surf.forget();
 }
 
 GLuint
 GrallocTextureSourceOGL::GetGLTexture()
 {
+  if (mTextureBackendSpecificData) {
+    mTextureBackendSpecificData->SetCompositor(mCompositor);
+    return mTextureBackendSpecificData->GetTexture();
+  }
+
   return mTexture;
 }
 
 void
 GrallocTextureSourceOGL::BindEGLImage()
 {
-  gl()->fEGLImageTargetTexture2D(GetTextureTarget(), mEGLImage);
-}
-
-TextureSource*
-GrallocTextureHostOGL::GetTextureSources()
-{
-  // This is now only used with tiled layers, and will eventually be removed.
-  // Other layer types use BindTextureSource instead.
-  MOZ_ASSERT(!mGLTextureSource);
-  if (!mTilingTextureSource) {
-    android::GraphicBuffer* graphicBuffer = GetGraphicBufferFromDesc(mGrallocHandle).get();
-    MOZ_ASSERT(graphicBuffer);
-    if (!graphicBuffer) {
-      return nullptr;
-    }
-    mTilingTextureSource = new GrallocTextureSourceOGL(mCompositor, this,
-                                                 graphicBuffer, mFormat);
-  }
-  mTilingTextureSource->Lock();
-  return mTilingTextureSource;
-}
-
-void
-GrallocTextureHostOGL::UnbindTextureSource()
-{
-  // Clear the reference to the TextureSource (if any), because we know that
-  // another TextureHost is being bound to the TextureSource. This means that
-  // we will have to re-do gl->fEGLImageTargetTexture2D next time we go through
-  // BindTextureSource (otherwise we would have skipped it).
-  // Note that this doesn't "unlock" the gralloc buffer or force it to be
-  // detached, Although decreasing the refcount of the TextureSource may lead
-  // to the gl handle being destroyed, which would unlock the gralloc buffer.
-  // That said, this method is called before another TextureHost attaches to the
-  // TextureSource, which has the effect of unlocking the gralloc buffer. So when
-  // this is called we know we are going to be unlocked soon.
-  mGLTextureSource = nullptr;
-}
-
-GLenum GetTextureTarget(gl::GLContext* aGL, android::PixelFormat aFormat) {
-  MOZ_ASSERT(aGL);
-  if (aGL->Renderer() == gl::GLRenderer::SGX530 ||
-      aGL->Renderer() == gl::GLRenderer::SGX540) {
-    // SGX has a quirk that only TEXTURE_EXTERNAL works and any other value will
-    // result in black pixels when trying to draw from bound textures.
-    // Unfortunately, using TEXTURE_EXTERNAL on Adreno has a terrible effect on
-    // performance.
-    // See Bug 950050.
-    return LOCAL_GL_TEXTURE_EXTERNAL;
+  if (mTextureBackendSpecificData) {
+    mTextureBackendSpecificData->BindEGLImage(GetTextureTarget(), mEGLImage);
   } else {
-    return TextureTargetForAndroidPixelFormat(aFormat);
-  }
-}
-
-void
-GrallocTextureHostOGL::DestroyEGLImage()
-{
-  // Only called when we want to get rid of the gralloc buffer, usually
-  // around the end of life of the TextureHost.
-  if (mEGLImage != EGL_NO_IMAGE && GetGLContext()) {
-    EGLImageDestroy(GetGLContext(), mEGLImage);
-    mEGLImage = EGL_NO_IMAGE;
+    gl()->fEGLImageTargetTexture2D(GetTextureTarget(), mEGLImage);
   }
 }
 
 void
-GrallocTextureHostOGL::PrepareTextureSource(CompositableTextureSourceRef& aTextureSource)
+GrallocTextureHostOGL::SetCompositableBackendSpecificData(CompositableBackendSpecificData* aBackendData)
 {
-  // This happens during the layers transaction.
-  // All of the gralloc magic goes here. The only thing that happens externally
-  // and that is good to keep in mind is that when the TextureSource is deleted,
-  // it destroys its gl texture handle which is important for genlock.
-
-  // If this TextureHost's mGLTextureSource member is non-null, it means we are
-  // still bound to the TextureSource, in which case we can skip the driver
-  // overhead of binding the texture again (fEGLImageTargetTexture2D)
-  // As a result, if the TextureHost is used with several CompositableHosts,
-  // it will be bound to only one TextureSource, and we'll do the driver work
-  // only once, which is great. This means that all of the compositables that
-  // use this TextureHost will keep a reference to this TextureSource at least
-  // for the duration of this frame.
-
-  // If the compositable already has a TextureSource (the aTextureSource parameter),
-  // that is compatible and is not in use by several compositable, we try to
-  // attach to it. This has the effect of unlocking the previous TextureHost that
-  // we attached to the TextureSource (the previous frame)
-
-  // If the TextureSource used by the compositable is also used by other
-  // compositables (see NumCompositableRefs), we have to create a new TextureSource,
-  // because otherwise we would be modifying the content of every layer that uses
-  // the TextureSource in question, even thoug they don't use this TextureHost.
-
-  MOZ_ASSERT(!mTilingTextureSource);
-
-  android::GraphicBuffer* graphicBuffer = GetGraphicBufferFromDesc(mGrallocHandle).get();
-
-  MOZ_ASSERT(graphicBuffer);
-  if (!graphicBuffer) {
-    mGLTextureSource = nullptr;
-    return;
-  }
-
-  if (mGLTextureSource && !mGLTextureSource->IsValid()) {
-    mGLTextureSource = nullptr;
-  }
-
-  if (mGLTextureSource) {
-    // We are already attached to a TextureSource, nothing to do except tell
-    // the compositable to use it.
-    aTextureSource = mGLTextureSource.get();
-    return;
-  }
-
-  gl::GLContext* gl = GetGLContext();
-  if (!gl || !gl->MakeCurrent()) {
-    mGLTextureSource = nullptr;
+  if(!aBackendData) {
     return;
   }
 
-  if (mEGLImage == EGL_NO_IMAGE) {
-    // Should only happen the first time.
-    mEGLImage = EGLImageCreateFromNativeBuffer(gl, graphicBuffer->getNativeBuffer());
+  // Update mTextureBackendSpecificData if it is not set yet.
+  if (!mTextureBackendSpecificData) {
+    MOZ_ASSERT(!mCompositableBackendData);
+    mCompositableBackendData = aBackendData;
+    CompositableDataGonkOGL* backend = static_cast<CompositableDataGonkOGL*>(mCompositableBackendData.get());
+    mTextureBackendSpecificData = backend->GetTextureBackendSpecificData();
+  }
+
+  // If TextureHost sharing by multiple CompositableHosts are detected,
+  // enable mBackendDatas usage.
+  if (!mBackendDatas &&
+      mCompositableBackendData &&
+      mCompositableBackendData != aBackendData &&
+      mTextureBackendSpecificData->IsAllowingSharingTextureHost())
+  {
+    mBackendDatas = MakeUnique<std::map<uint64_t, RefPtr<CompositableBackendSpecificData> > >();
+    (*mBackendDatas)[mCompositableBackendData->GetId()] = mCompositableBackendData;
+    mCompositableBackendData = nullptr;
+
+    // Get new mTextureBackendSpecificData
+    mTextureBackendSpecificData =
+      mTextureBackendSpecificData->GetNewTextureBackendSpecificData(mTextureSource->GetEGLImage());
+    mTextureBackendSpecificData->SetOwnedByTextureHost();
   }
 
-  GLenum textureTarget = GetTextureTarget(gl, graphicBuffer->getPixelFormat());
-
-  GLTextureSource* glSource = aTextureSource.get() ?
-    aTextureSource->AsSourceOGL()->AsGLTextureSource() : nullptr;
-
-  bool shouldCreateTextureSource = !glSource  || !glSource->IsValid()
-                                 || glSource->NumCompositableRefs() > 1
-                                 || glSource->GetTextureTarget() != textureTarget;
+  // Update mCompositableBackendData.
+  if (mBackendDatas)
+  {
+    // Handle a case that TextureHost has ownership of TextureSharedDataGonkOGL.
+    MOZ_ASSERT(aBackendData->IsAllowingSharingTextureHost());
+    (*mBackendDatas)[aBackendData->GetId()] = aBackendData;
+    if (mBackendDatas->size() > 200) {
+      NS_WARNING("Too many CompositableBackends");
+    }
+  } else {
+    // Handle a case that CompositableHost has ownership of TextureSharedDataGonkOGL.
+    mCompositableBackendData = aBackendData;
+    CompositableDataGonkOGL* backend = static_cast<CompositableDataGonkOGL*>(mCompositableBackendData.get());
+    mTextureBackendSpecificData = backend->GetTextureBackendSpecificData();
+  }
 
-  if (shouldCreateTextureSource) {
-    GLuint textureHandle;
-    gl->fGenTextures(1, &textureHandle);
-    gl->fBindTexture(textureTarget, textureHandle);
-    gl->fTexParameteri(textureTarget, LOCAL_GL_TEXTURE_WRAP_T, LOCAL_GL_CLAMP_TO_EDGE);
-    gl->fTexParameteri(textureTarget, LOCAL_GL_TEXTURE_WRAP_S, LOCAL_GL_CLAMP_TO_EDGE);
-    gl->fEGLImageTargetTexture2D(textureTarget, mEGLImage);
+  if (mTextureSource) {
+    mTextureSource->SetTextureBackendSpecificData(mTextureBackendSpecificData);
+  }
 
-    mGLTextureSource = new GLTextureSource(mCompositor, textureHandle, textureTarget,
-                                           mSize, mFormat);
-    aTextureSource = mGLTextureSource.get();
-  } else {
-    gl->fBindTexture(textureTarget, glSource->GetTextureHandle());
-
-    gl->fEGLImageTargetTexture2D(textureTarget, mEGLImage);
-    glSource->SetSize(mSize);
-    glSource->SetFormat(mFormat);
-    mGLTextureSource = glSource;
-  }
 }
 
-bool
-GrallocTextureHostOGL::BindTextureSource(CompositableTextureSourceRef& aTextureSource)
+void
+GrallocTextureHostOGL::UnsetCompositableBackendSpecificData(CompositableBackendSpecificData* aBackendData)
 {
-  // This happens at composition time.
-
-  // If mGLTextureSource is null it means PrepareTextureSource failed.
-  if (!mGLTextureSource) {
-    return false;
+  if(!aBackendData ||
+     !mTextureBackendSpecificData) {
+    return;
   }
 
-  // If Prepare didn't fail, we expect our TextureSource to be the same as aTextureSource,
-  // otherwise it means something has fiddled with the TextureSource between Prepare and
-  // now.
-  MOZ_ASSERT(mGLTextureSource == aTextureSource);
-  aTextureSource = mGLTextureSource.get();
+  if (mBackendDatas)
+  {
+    // Handle a case that TextureHost has ownership of TextureSharedDataGonkOGL.
+    mBackendDatas->erase(aBackendData->GetId());
+    if (mBackendDatas->size() == 0) {
+      mCompositableBackendData = nullptr;
+      mTextureBackendSpecificData = nullptr;
+    }
+  } else {
+    // Handle a case that CompositableHost has ownership of TextureSharedDataGonkOGL.
+    mCompositableBackendData = nullptr;
+    mTextureBackendSpecificData = nullptr;
+  }
 
-#if defined(MOZ_WIDGET_GONK) && ANDROID_VERSION >= 17
-  // Wait until it's ready.
-  WaitAcquireFenceSyncComplete();
-#endif
-  return true;
+  if (mTextureSource) {
+    mTextureSource->SetTextureBackendSpecificData(mTextureBackendSpecificData);
+  }
 }
 
 } // namepsace layers
 } // namepsace mozilla
--- a/gfx/layers/opengl/GrallocTextureHost.h
+++ b/gfx/layers/opengl/GrallocTextureHost.h
@@ -12,17 +12,16 @@
 #include "mozilla/layers/ShadowLayerUtilsGralloc.h"
 #include <ui/GraphicBuffer.h>
 
 namespace mozilla {
 namespace layers {
 
 class GrallocTextureHostOGL;
 
-// Progressively getting replaced by GLTextureSource
 class GrallocTextureSourceOGL : public TextureSource
                               , public TextureSourceOGL
 {
 public:
   friend class GrallocTextureHostOGL;
 
   GrallocTextureSourceOGL(CompositorOGL* aCompositor,
                           GrallocTextureHostOGL* aTextureHost,
@@ -43,16 +42,18 @@ public:
 
   virtual gfx::SurfaceFormat GetFormat() const MOZ_OVERRIDE { return mFormat; }
 
   virtual GLenum GetWrapMode() const MOZ_OVERRIDE
   {
     return LOCAL_GL_CLAMP_TO_EDGE;
   }
 
+  virtual void SetTextureBackendSpecificData(TextureSharedDataGonkOGL* aBackendData);
+
   void DeallocateDeviceData();
 
   gl::GLContext* gl() const;
 
   virtual void SetCompositor(Compositor* aCompositor) MOZ_OVERRIDE;
 
   void ForgetBuffer()
   {
@@ -69,16 +70,17 @@ public:
   EGLImage GetEGLImage()
   {
     return mEGLImage;
   }
 
   bool Lock();
 
 protected:
+  RefPtr<TextureSharedDataGonkOGL> mTextureBackendSpecificData;
   RefPtr<CompositorOGL> mCompositor;
   GrallocTextureHostOGL* mTextureHost;
   android::sp<android::GraphicBuffer> mGraphicBuffer;
   EGLImage mEGLImage;
   GLuint mTexture;
   gfx::SurfaceFormat mFormat;
   bool mNeedsReset;
 };
@@ -106,57 +108,48 @@ public:
   virtual void DeallocateSharedData() MOZ_OVERRIDE;
 
   virtual void ForgetSharedData() MOZ_OVERRIDE;
 
   virtual void DeallocateDeviceData() MOZ_OVERRIDE;
 
   virtual gfx::SurfaceFormat GetFormat() const;
 
-  virtual gfx::IntSize GetSize() const MOZ_OVERRIDE { return mDescriptorSize; }
+  virtual gfx::IntSize GetSize() const MOZ_OVERRIDE { return mSize; }
 
   virtual LayerRenderState GetRenderState() MOZ_OVERRIDE;
 
-  virtual void PrepareTextureSource(CompositableTextureSourceRef& aTextureSource) MOZ_OVERRIDE;
-
-  virtual bool BindTextureSource(CompositableTextureSourceRef& aTextureSource) MOZ_OVERRIDE;
-
-  virtual void UnbindTextureSource() MOZ_OVERRIDE;
-
-  virtual TextureSource* GetTextureSources() MOZ_OVERRIDE;
+  virtual TextureSource* GetTextureSources() MOZ_OVERRIDE
+  {
+    return mTextureSource;
+  }
 
 #if defined(MOZ_WIDGET_GONK) && ANDROID_VERSION >= 17
   virtual TextureHostOGL* AsHostOGL() MOZ_OVERRIDE
   {
     return this;
   }
 #endif
 
   virtual TemporaryRef<gfx::DataSourceSurface> GetAsSurface() MOZ_OVERRIDE;
 
+  virtual void SetCompositableBackendSpecificData(CompositableBackendSpecificData* aBackendData) MOZ_OVERRIDE;
+
+  virtual void UnsetCompositableBackendSpecificData(CompositableBackendSpecificData* aBackendData) MOZ_OVERRIDE;
+
   bool IsValid() const;
 
   virtual const char* Name() MOZ_OVERRIDE { return "GrallocTextureHostOGL"; }
 
-  gl::GLContext* GetGLContext() const { return mCompositor ? mCompositor->gl() : nullptr; }
-
 private:
-  void DestroyEGLImage();
-
   NewSurfaceDescriptorGralloc mGrallocHandle;
-  RefPtr<GLTextureSource> mGLTextureSource;
-  RefPtr<CompositorOGL> mCompositor;
-  // only used for tiling, will be removed.
-  RefPtr<GrallocTextureSourceOGL> mTilingTextureSource;
-  // Size reported by the GraphicBuffer
-  gfx::IntSize mSize;
-  // Size reported by TextureClient, can be different in some cases (video?),
-  // used by LayerRenderState.
-  gfx::IntSize mDescriptorSize;
-  gfx::SurfaceFormat mFormat;
-  EGLImage mEGLImage;
+  RefPtr<GrallocTextureSourceOGL> mTextureSource;
+  gfx::IntSize mSize; // See comment in textureClientOGL.h
+
+  RefPtr<TextureSharedDataGonkOGL> mTextureBackendSpecificData;
+  UniquePtr<std::map<uint64_t, RefPtr<CompositableBackendSpecificData> > > mBackendDatas;
 };
 
 } // namespace layers
 } // namespace mozilla
 
 #endif
 #endif
--- a/gfx/layers/opengl/TextureHostOGL.cpp
+++ b/gfx/layers/opengl/TextureHostOGL.cpp
@@ -35,16 +35,26 @@
 using namespace mozilla::gl;
 using namespace mozilla::gfx;
 
 namespace mozilla {
 namespace layers {
 
 class Compositor;
 
+TemporaryRef<CompositableBackendSpecificData>
+CreateCompositableBackendSpecificDataOGL()
+{
+#ifdef MOZ_WIDGET_GONK
+  return new CompositableDataGonkOGL();
+#else
+  return nullptr;
+#endif
+}
+
 TemporaryRef<TextureHost>
 CreateTextureHostOGL(const SurfaceDescriptor& aDesc,
                      ISurfaceAllocator* aDeallocator,
                      TextureFlags aFlags)
 {
   RefPtr<TextureHost> result;
   switch (aDesc.type()) {
     case SurfaceDescriptor::TSurfaceDescriptorShmem:
@@ -104,16 +114,184 @@ FlagsToGLFlags(TextureFlags aFlags)
   if (aFlags & TextureFlags::NEEDS_Y_FLIP)
     result |= TextureImage::NeedsYFlip;
   if (aFlags & TextureFlags::DISALLOW_BIGIMAGE)
     result |= TextureImage::DisallowBigImage;
 
   return static_cast<gl::TextureImage::Flags>(result);
 }
 
+CompositableDataGonkOGL::CompositableDataGonkOGL()
+{
+}
+
+CompositableDataGonkOGL::~CompositableDataGonkOGL()
+{
+   ClearData();
+}
+
+void
+CompositableDataGonkOGL::ClearData()
+{
+  CompositableBackendSpecificData::ClearData();
+  mTextureBackendSpecificData = nullptr;
+  mCompositor = nullptr;
+}
+
+void
+CompositableDataGonkOGL::SetCompositor(Compositor* aCompositor)
+{
+  mCompositor = static_cast<CompositorOGL*>(aCompositor);
+  if (mTextureBackendSpecificData) {
+    mTextureBackendSpecificData->SetCompositor(aCompositor);
+  }
+}
+
+TextureSharedDataGonkOGL*
+CompositableDataGonkOGL::GetTextureBackendSpecificData()
+{
+  if (!mTextureBackendSpecificData) {
+    mTextureBackendSpecificData = new TextureSharedDataGonkOGL();
+    mTextureBackendSpecificData->SetCompositor(mCompositor);
+    mTextureBackendSpecificData->SetAllowSharingTextureHost(IsAllowingSharingTextureHost());
+  }
+  return mTextureBackendSpecificData;
+}
+
+TextureSharedDataGonkOGL::TextureSharedDataGonkOGL()
+ : mOwnedByCompositableHost(true)
+ , mAllowSharingTextureHost(false)
+ , mTexture(0)
+ , mBoundEGLImage(EGL_NO_IMAGE)
+{
+}
+
+TextureSharedDataGonkOGL::TextureSharedDataGonkOGL(GLuint aTexture, EGLImage aImage, CompositorOGL* aCompositor)
+ : mOwnedByCompositableHost(true)
+ , mAllowSharingTextureHost(false)
+ , mCompositor(aCompositor)
+ , mTexture(aTexture)
+ , mBoundEGLImage(aImage)
+{
+}
+
+TextureSharedDataGonkOGL::~TextureSharedDataGonkOGL()
+{
+  DeleteTextureIfPresent();
+}
+
+gl::GLContext*
+TextureSharedDataGonkOGL::gl() const
+{
+  return mCompositor ? mCompositor->gl() : nullptr;
+}
+
+void
+TextureSharedDataGonkOGL::SetCompositor(Compositor* aCompositor)
+{
+  if (gl() && mCompositor != aCompositor) {
+    DeleteTextureIfPresent();
+  }
+  mCompositor = static_cast<CompositorOGL*>(aCompositor);
+}
+
+void
+TextureSharedDataGonkOGL::ClearData()
+{
+  DeleteTextureIfPresent();
+}
+
+TemporaryRef<TextureSharedDataGonkOGL>
+TextureSharedDataGonkOGL::GetNewTextureBackendSpecificData(EGLImage aImage)
+{
+  MOZ_ASSERT(IsAllowingSharingTextureHost());
+
+  if (IsEGLImageBound(aImage))
+  {
+    // If EGLImage is already bound to OpenGL Texture,
+    // handover the OpenGL Texture to caller
+    GLuint textureId = GetAndResetGLTextureOwnership();
+    RefPtr<TextureSharedDataGonkOGL> data = new TextureSharedDataGonkOGL(textureId, aImage, mCompositor);
+    data->SetCompositor(mCompositor);
+    data->SetAllowSharingTextureHost(true);
+    return data;
+  }
+
+  // Create brand new TextureSharedDataGonkOGL
+  RefPtr<TextureSharedDataGonkOGL> data = new TextureSharedDataGonkOGL();
+  data->SetCompositor(mCompositor);
+  data->SetAllowSharingTextureHost(true);
+  return data;
+}
+
+GLuint
+TextureSharedDataGonkOGL::GetTexture()
+{
+  if (!mTexture) {
+    if (gl() && gl()->MakeCurrent()) {
+      gl()->fGenTextures(1, &mTexture);
+    }
+  }
+  return mTexture;
+}
+
+GLuint
+TextureSharedDataGonkOGL::GetAndResetGLTextureOwnership()
+{
+  GLuint texture = mTexture;
+  mTexture = 0;
+  mBoundEGLImage = EGL_NO_IMAGE;
+  return texture;
+}
+
+void
+TextureSharedDataGonkOGL::DeleteTextureIfPresent()
+{
+  if (mTexture) {
+    MOZ_ASSERT(mCompositor);
+    if (gl() && gl()->MakeCurrent()) {
+      gl()->fDeleteTextures(1, &mTexture);
+    }
+    mTexture = 0;
+    mBoundEGLImage = EGL_NO_IMAGE;
+  }
+}
+
+void
+TextureSharedDataGonkOGL::BindEGLImage(GLuint aTarget, EGLImage aImage)
+{
+  if (mBoundEGLImage != aImage) {
+    MOZ_ASSERT(gl());
+    if (gl()) {
+      gl()->fEGLImageTargetTexture2D(aTarget, aImage);
+    }
+    mBoundEGLImage = aImage;
+  }
+}
+
+void
+TextureSharedDataGonkOGL::ClearBoundEGLImage(EGLImage aImage)
+{
+  if (mBoundEGLImage == aImage) {
+    DeleteTextureIfPresent();
+    mBoundEGLImage = EGL_NO_IMAGE;
+  }
+}
+
+bool
+TextureSharedDataGonkOGL::IsEGLImageBound(EGLImage aImage)
+{
+  if (mTexture != 0 &&
+      aImage != EGL_NO_IMAGE &&
+      aImage == mBoundEGLImage) {
+    return true;
+  }
+  return false;
+}
+
 #if defined(MOZ_WIDGET_GONK) && ANDROID_VERSION >= 17
 bool
 TextureHostOGL::SetReleaseFence(const android::sp<android::Fence>& aReleaseFence)
 {
   if (!aReleaseFence.get() || !aReleaseFence->isValid()) {
     // HWC might not provide Fence.
     // In this case, HWC implicitly handles buffer's fence.
     return false;
@@ -346,79 +524,50 @@ TextureImageTextureSourceOGL::BindTextur
   mTexImage->BindTexture(aTextureUnit);
   SetFilter(mGL, aFilter);
 }
 
 ////////////////////////////////////////////////////////////////////////
 // GLTextureSource
 
 GLTextureSource::GLTextureSource(CompositorOGL* aCompositor,
-                                 GLuint aTextureHandle,
-                                 GLenum aTarget,
-                                 gfx::IntSize aSize,
+                                 GLuint aTex,
                                  gfx::SurfaceFormat aFormat,
-                                 bool aExternallyOwned)
-  : mCompositor(aCompositor)
-  , mTextureHandle(aTextureHandle)
+                                 GLenum aTarget,
+                                 gfx::IntSize aSize)
+  : mSize(aSize)
+  , mCompositor(aCompositor)
+  , mTex(aTex)
+  , mFormat(aFormat)
   , mTextureTarget(aTarget)
-  , mSize(aSize)
-  , mFormat(aFormat)
-  , mExternallyOwned(aExternallyOwned)
-{
-  MOZ_COUNT_CTOR(GLTextureSource);
-}
-
-GLTextureSource::~GLTextureSource()
 {
-  MOZ_COUNT_DTOR(GLTextureSource);
-  if (!mExternallyOwned) {
-    DeleteTextureHandle();
-  }
-}
-
-void
-GLTextureSource::DeallocateDeviceData()
-{
-  if (!mExternallyOwned) {
-    DeleteTextureHandle();
-  }
-}
-
-void
-GLTextureSource::DeleteTextureHandle()
-{
-  if (mTextureHandle != 0 && gl() && gl()->MakeCurrent()) {
-    gl()->fDeleteTextures(1, &mTextureHandle);
-  }
-  mTextureHandle = 0;
 }
 
 void
 GLTextureSource::BindTexture(GLenum aTextureUnit, gfx::Filter aFilter)
 {
-  MOZ_ASSERT(gl());
-  MOZ_ASSERT(mTextureHandle != 0);
   if (!gl()) {
+    NS_WARNING("Trying to bind a texture without a GLContext");
     return;
   }
   gl()->fActiveTexture(aTextureUnit);
-  gl()->fBindTexture(mTextureTarget, mTextureHandle);
+  gl()->fBindTexture(mTextureTarget, mTex);
   ApplyFilterToBoundTexture(gl(), aFilter, mTextureTarget);
 }
 
 void
 GLTextureSource::SetCompositor(Compositor* aCompositor)
 {
   mCompositor = static_cast<CompositorOGL*>(aCompositor);
 }
 
 bool
 GLTextureSource::IsValid() const
 {
-  return !!gl() && mTextureHandle != 0;
+  return !!gl();
 }
 
 gl::GLContext*
 GLTextureSource::gl() const
 {
   return mCompositor ? mCompositor->gl() : nullptr;
 }
 
@@ -460,20 +609,16 @@ SurfaceTextureSource::BindTexture(GLenum
   mSurfTex->UpdateTexImage();
 
   ApplyFilterToBoundTexture(gl(), aFilter, mTextureTarget);
 }
 
 void
 SurfaceTextureSource::SetCompositor(Compositor* aCompositor)
 {
-  if (mCompositor != aCompositor) {
-    DeallocateDeviceData();
-  }
-
   mCompositor = static_cast<CompositorOGL*>(aCompositor);
 }
 
 bool
 SurfaceTextureSource::IsValid() const
 {
   return !!gl();
 }
--- a/gfx/layers/opengl/TextureHostOGL.h
+++ b/gfx/layers/opengl/TextureHostOGL.h
@@ -53,17 +53,109 @@ class AndroidSurfaceTexture;
 }
 
 namespace layers {
 
 class Compositor;
 class CompositorOGL;
 class TextureImageTextureSourceOGL;
 class TextureSharedDataGonkOGL;
-class GLTextureSource;
+
+/**
+ * CompositableBackendSpecificData implementation for the Gonk OpenGL backend.
+ * Share a same texture between TextureHosts in the same CompositableHost.
+ * By shareing the texture among the TextureHosts, number of texture allocations
+ * can be reduced than texture allocation in every TextureHosts.
+ * From Bug 912134, use only one texture among all TextureHosts degrade
+ * the rendering performance.
+ * CompositableDataGonkOGL chooses in a middile of them.
+ */
+class CompositableDataGonkOGL : public CompositableBackendSpecificData
+{
+protected:
+  virtual ~CompositableDataGonkOGL();
+
+public:
+  CompositableDataGonkOGL();
+  virtual void ClearData() MOZ_OVERRIDE;
+  virtual void SetCompositor(Compositor* aCompositor) MOZ_OVERRIDE;
+
+  TextureSharedDataGonkOGL* GetTextureBackendSpecificData();
+protected:
+  nsRefPtr<TextureSharedDataGonkOGL> mTextureBackendSpecificData;
+  RefPtr<CompositorOGL> mCompositor;
+};
+
+/**
+ * Manage actual shared resources of CompositableDataGonkOGL.
+ * The resources are split from CompositableDataGonkOGL to handle two use cases.
+ * Normally TextureHost is used from one CompositableHost at the same time.
+ * In this case, performance is good if the resources are owned by CompositableDataGonkOGL.
+ * But TextureHost could be shared among multiple ImageHosts.
+ * If it happens, performance is good if the resource is owned by TextureHost.
+ * The resources ownership is carryed over from CompositableDataGonkOGL to TextureHost.
+ * See Bug 1017351.
+ */
+class TextureSharedDataGonkOGL
+{
+protected:
+  virtual ~TextureSharedDataGonkOGL();
+
+public:
+  NS_INLINE_DECL_REFCOUNTING(TextureSharedDataGonkOGL)
+
+  TextureSharedDataGonkOGL();
+  TextureSharedDataGonkOGL(GLuint aTexture, EGLImage aImage, CompositorOGL* aCompositor);
+
+  void SetCompositor(Compositor* aCompositor);
+  void ClearData();
+
+  // Mark TextureSharedDataGonkOGL as owned by TextureHost.
+  void SetOwnedByTextureHost()
+  {
+    mOwnedByCompositableHost = false;
+  }
+
+  // Check if this is owned by CompositableHost or TextureHost.
+  bool IsOwnedByCompositableHost()
+  {
+    return mOwnedByCompositableHost;
+  }
+
+  bool IsAllowingSharingTextureHost()
+  {
+    return mAllowSharingTextureHost;
+  }
+
+  void SetAllowSharingTextureHost(bool aAllow)
+  {
+    mAllowSharingTextureHost = aAllow;
+  }
+
+  // Create new TextureSharedDataGonkOGL.
+  // If aImage is already bound to OpenGL texture, the OpenGL textre is carried over
+  // to a new object. It could reduce calling fEGLImageTargetTexture2D()
+  // during resources ownership carry over from CompositableHost to TextureHost.
+  TemporaryRef<TextureSharedDataGonkOGL> GetNewTextureBackendSpecificData(EGLImage aImage);
+
+  GLuint GetTexture();
+  void DeleteTextureIfPresent();
+  gl::GLContext* gl() const;
+  void BindEGLImage(GLuint aTarget, EGLImage aImage);
+  void ClearBoundEGLImage(EGLImage aImage);
+  bool IsEGLImageBound(EGLImage aImage);
+protected:
+  GLuint GetAndResetGLTextureOwnership();
+
+  bool mOwnedByCompositableHost;
+  bool mAllowSharingTextureHost;
+  RefPtr<CompositorOGL> mCompositor;
+  GLuint mTexture;
+  EGLImage mBoundEGLImage;
+};
 
 inline void ApplyFilterToBoundTexture(gl::GLContext* aGL,
                                       gfx::Filter aFilter,
                                       GLuint aTarget = LOCAL_GL_TEXTURE_2D)
 {
   GLenum filter =
     (aFilter == gfx::Filter::POINT ? LOCAL_GL_NEAREST : LOCAL_GL_LINEAR);
 
@@ -108,18 +200,16 @@ public:
   virtual gfx::SurfaceFormat GetFormat() const = 0;
 
   virtual GLenum GetWrapMode() const = 0;
 
   virtual gfx::Matrix4x4 GetTextureTransform() { return gfx::Matrix4x4(); }
 
   virtual TextureImageTextureSourceOGL* AsTextureImageTextureSource() { return nullptr; }
 
-  virtual GLTextureSource* AsGLTextureSource() { return nullptr; }
-
   void SetFilter(gl::GLContext* aGL, gfx::Filter aFilter)
   {
     if (mHasCachedFilter &&
         mCachedFilter == aFilter) {
       return;
     }
     mHasCachedFilter = true;
     mCachedFilter = aFilter;
@@ -278,63 +368,47 @@ protected:
  *
  * The shared texture handle is owned by the TextureHost.
  */
 class GLTextureSource : public TextureSource
                       , public TextureSourceOGL
 {
 public:
   GLTextureSource(CompositorOGL* aCompositor,
-                  GLuint aTextureHandle,
-                  GLenum aTarget,
-                  gfx::IntSize aSize,
+                  GLuint aTex,
                   gfx::SurfaceFormat aFormat,
-                  bool aExternallyOwned = false);
-
-  ~GLTextureSource();
-
-  virtual GLTextureSource* AsGLTextureSource() MOZ_OVERRIDE { return this; }
+                  GLenum aTarget,
+                  gfx::IntSize aSize);
 
   virtual TextureSourceOGL* AsSourceOGL() MOZ_OVERRIDE { return this; }
 
   virtual void BindTexture(GLenum activetex, gfx::Filter aFilter) MOZ_OVERRIDE;
 
   virtual bool IsValid() const MOZ_OVERRIDE;
 
   virtual gfx::IntSize GetSize() const MOZ_OVERRIDE { return mSize; }
 
   virtual gfx::SurfaceFormat GetFormat() const MOZ_OVERRIDE { return mFormat; }
 
   virtual GLenum GetTextureTarget() const { return mTextureTarget; }
 
   virtual GLenum GetWrapMode() const MOZ_OVERRIDE { return LOCAL_GL_CLAMP_TO_EDGE; }
 
-  virtual void DeallocateDeviceData() MOZ_OVERRIDE;
+  virtual void DeallocateDeviceData() MOZ_OVERRIDE {}
 
   virtual void SetCompositor(Compositor* aCompositor) MOZ_OVERRIDE;
 
-  void SetSize(gfx::IntSize aSize) { mSize = aSize; }
-
-  void SetFormat(gfx::SurfaceFormat aFormat) { mFormat = aFormat; }
-
-  GLuint GetTextureHandle() const { return mTextureHandle; }
-
   gl::GLContext* gl() const;
 
 protected:
-  void DeleteTextureHandle();
-
+  const gfx::IntSize mSize;
   RefPtr<CompositorOGL> mCompositor;
-  GLuint mTextureHandle;
-  GLenum mTextureTarget;
-  gfx::IntSize mSize;
-  gfx::SurfaceFormat mFormat;
-  // If the texture is externally owned, the gl handle will not be deleted
-  // in the destructor.
-  bool mExternallyOwned;
+  const GLuint mTex;
+  const gfx::SurfaceFormat mFormat;
+  const GLenum mTextureTarget;
 };
 
 ////////////////////////////////////////////////////////////////////////
 // SurfaceTexture
 
 #ifdef MOZ_WIDGET_ANDROID
 
 class SurfaceTextureSource : public TextureSource
--- a/gfx/src/nsITheme.h
+++ b/gfx/src/nsITheme.h
@@ -23,20 +23,20 @@ class nsPresContext;
 class nsRenderingContext;
 class nsDeviceContext;
 class nsIFrame;
 class nsIContent;
 class nsIAtom;
 class nsIWidget;
 
 // IID for the nsITheme interface
-// {48f64fde-ff0e-4989-bbe3-f746573f9b7c}
+// {cc3a6c72-50c2-414d-b9f2-b778a5e0f136}
  #define NS_ITHEME_IID     \
-{ 0x48f64fde, 0xff0e, 0x4989, \
-  { 0xbb, 0xe3, 0xf7, 0x46, 0x57, 0x3f, 0x9b, 0x7c } }
+{ 0xcc3a6c72, 0x50c2, 0x414d, \
+  { 0xb9, 0xf2, 0xb7, 0x78, 0xa5, 0xe0, 0xf1, 0x36 } }
 // {0ae05515-cf7a-45a8-9e02-6556de7685b1}
 #define NS_THEMERENDERER_CID \
 { 0x0ae05515, 0xcf7a, 0x45a8, \
   { 0x9e, 0x02, 0x65, 0x56, 0xde, 0x76, 0x85, 0xb1 } }
 
 /**
  * nsITheme is a service that provides platform-specific native
  * rendering for widgets.  In other words, it provides the necessary
@@ -138,16 +138,20 @@ public:
   NS_IMETHOD ThemeChanged()=0;
 
   virtual bool WidgetAppearanceDependsOnWindowFocus(uint8_t aWidgetType)
   { return false; }
 
   virtual bool NeedToClearBackgroundBehindWidget(uint8_t aWidgetType)
   { return false; }
 
+  virtual bool WidgetProvidesFontSmoothingBackgroundColor(nsIFrame* aFrame,
+                                      uint8_t aWidgetType, nscolor* aColor)
+  { return false; }
+
   /**
    * Can the nsITheme implementation handle this widget?
    */
   virtual bool ThemeSupportsWidget(nsPresContext* aPresContext,
                                      nsIFrame* aFrame,
                                      uint8_t aWidgetType)=0;
 
   virtual bool WidgetIsContainer(uint8_t aWidgetType)=0;
--- a/gfx/tests/gtest/TestAsyncPanZoomController.cpp
+++ b/gfx/tests/gtest/TestAsyncPanZoomController.cpp
@@ -57,17 +57,17 @@ private:
     prefValue)
 
 class MockContentController : public GeckoContentController {
 public:
   MOCK_METHOD1(RequestContentRepaint, void(const FrameMetrics&));
   MOCK_METHOD2(AcknowledgeScrollUpdate, void(const FrameMetrics::ViewID&, const uint32_t& aScrollGeneration));
   MOCK_METHOD3(HandleDoubleTap, void(const CSSPoint&, int32_t, const ScrollableLayerGuid&));
   MOCK_METHOD3(HandleSingleTap, void(const CSSPoint&, int32_t, const ScrollableLayerGuid&));
-  MOCK_METHOD3(HandleLongTap, void(const CSSPoint&, int32_t, const ScrollableLayerGuid&));
+  MOCK_METHOD4(HandleLongTap, void(const CSSPoint&, int32_t, const ScrollableLayerGuid&, uint64_t));
   MOCK_METHOD3(HandleLongTapUp, void(const CSSPoint&, int32_t, const ScrollableLayerGuid&));
   MOCK_METHOD3(SendAsyncScrollDOMEvent, void(bool aIsRoot, const CSSRect &aContentRect, const CSSSize &aScrollableSize));
   MOCK_METHOD2(PostDelayedTask, void(Task* aTask, int aDelayMs));
   MOCK_METHOD3(NotifyAPZStateChange, void(const ScrollableLayerGuid& aGuid, APZStateChange aChange, int aArg));
 };
 
 class MockContentControllerDelayed : public MockContentController {
 public:
@@ -114,24 +114,43 @@ public:
     }
     return numTasks;
   }
 
 private:
   nsTArray<Task*> mTaskQueue;
 };
 
+class TestAPZCTreeManager : public APZCTreeManager {
+public:
+  nsRefPtr<InputQueue> GetInputQueue() const {
+    return mInputQueue;
+  }
+};
+
 class TestAsyncPanZoomController : public AsyncPanZoomController {
 public:
   TestAsyncPanZoomController(uint64_t aLayersId, MockContentController* aMcc,
-                             APZCTreeManager* aTreeManager = nullptr,
+                             TestAPZCTreeManager* aTreeManager,
                              GestureBehavior aBehavior = DEFAULT_GESTURES)
-    : AsyncPanZoomController(aLayersId, aTreeManager, aMcc, aBehavior)
+    : AsyncPanZoomController(aLayersId, aTreeManager, aTreeManager->GetInputQueue(), aMcc, aBehavior)
   {}
 
+  nsEventStatus ReceiveInputEvent(const InputData& aEvent, uint64_t* aOutInputBlockId) {
+    return GetInputQueue()->ReceiveInputEvent(this, aEvent, aOutInputBlockId);
+  }
+
+  void ContentReceivedTouch(uint64_t aInputBlockId, bool aPreventDefault) {
+    GetInputQueue()->ContentReceivedTouch(aInputBlockId, aPreventDefault);
+  }
+  
+  void SetAllowedTouchBehavior(uint64_t aInputBlockId, const nsTArray<TouchBehaviorFlags>& aBehaviors) {
+    GetInputQueue()->SetAllowedTouchBehavior(aInputBlockId, aBehaviors);
+  }
+
   void SetFrameMetrics(const FrameMetrics& metrics) {
     ReentrantMonitorAutoEnter lock(mMonitor);
     mFrameMetrics = metrics;
   }
 
   FrameMetrics& GetFrameMetrics() {
     ReentrantMonitorAutoEnter lock(mMonitor);
     return mFrameMetrics;
@@ -164,19 +183,16 @@ public:
                                       ScreenPoint& aScrollOffset) {
     bool ret = AdvanceAnimations(aSampleTime);
     AsyncPanZoomController::SampleContentTransformForFrame(
       aOutTransform, aScrollOffset);
     return ret;
   }
 };
 
-class TestAPZCTreeManager : public APZCTreeManager {
-};
-
 static FrameMetrics
 TestFrameMetrics()
 {
   FrameMetrics fm;
 
   fm.mDisplayPort = CSSRect(0, 0, 10, 10);
   fm.mCompositionBounds = ParentLayerRect(0, 0, 10, 10);
   fm.mCriticalDisplayPort = CSSRect(0, 0, 10, 10);
@@ -238,92 +254,100 @@ class APZCGestureDetectorTester : public
 public:
   APZCGestureDetectorTester()
     : APZCBasicTester(AsyncPanZoomController::USE_GESTURE_DETECTOR)
   {
   }
 };
 
 static nsEventStatus
-ApzcDown(AsyncPanZoomController* apzc, int aX, int aY, int aTime)
+ApzcDown(TestAsyncPanZoomController* apzc, int aX, int aY, int aTime, uint64_t* aOutInputBlockId = nullptr)
 {
   MultiTouchInput mti = MultiTouchInput(MultiTouchInput::MULTITOUCH_START, aTime, TimeStamp(), 0);
   mti.mTouches.AppendElement(SingleTouchData(0, ScreenIntPoint(aX, aY), ScreenSize(0, 0), 0, 0));
-  return apzc->ReceiveInputEvent(mti);
+  return apzc->ReceiveInputEvent(mti, aOutInputBlockId);
 }
 
 static nsEventStatus
-ApzcUp(AsyncPanZoomController* apzc, int aX, int aY, int aTime)
+ApzcUp(TestAsyncPanZoomController* apzc, int aX, int aY, int aTime)
 {
   MultiTouchInput mti = MultiTouchInput(MultiTouchInput::MULTITOUCH_END, aTime, TimeStamp(), 0);
   mti.mTouches.AppendElement(SingleTouchData(0, ScreenIntPoint(aX, aY), ScreenSize(0, 0), 0, 0));
-  return apzc->ReceiveInputEvent(mti);
+  return apzc->ReceiveInputEvent(mti, nullptr);
 }
 
 static void
-ApzcTap(AsyncPanZoomController* aApzc, int aX, int aY, int& aTime, int aTapLength,
+ApzcTap(TestAsyncPanZoomController* aApzc, int aX, int aY, int& aTime, int aTapLength,
         nsEventStatus (*aOutEventStatuses)[2] = nullptr)
 {
   nsEventStatus status = ApzcDown(aApzc, aX, aY, aTime);
   if (aOutEventStatuses) {
     (*aOutEventStatuses)[0] = status;
   }
   aTime += aTapLength;
   status = ApzcUp(aApzc, aX, aY, aTime);
   if (aOutEventStatuses) {
     (*aOutEventStatuses)[1] = status;
   }
 }
 
 static void
-ApzcTapAndCheckStatus(AsyncPanZoomController* aApzc, int aX, int aY, int& aTime, int aTapLength)
+ApzcTapAndCheckStatus(TestAsyncPanZoomController* aApzc, int aX, int aY, int& aTime, int aTapLength)
 {
   nsEventStatus statuses[2];
   ApzcTap(aApzc, aX, aY, aTime, aTapLength, &statuses);
   EXPECT_EQ(nsEventStatus_eConsumeDoDefault, statuses[0]);
   EXPECT_EQ(nsEventStatus_eConsumeDoDefault, statuses[1]);
 }
 
 static void
-ApzcPan(AsyncPanZoomController* aApzc,
+ApzcPan(TestAsyncPanZoomController* aApzc,
         int& aTime,
         int aTouchStartY,
         int aTouchEndY,
         bool aKeepFingerDown = false,
         nsTArray<uint32_t>* aAllowedTouchBehaviors = nullptr,
-        nsEventStatus (*aOutEventStatuses)[4] = nullptr)
+        nsEventStatus (*aOutEventStatuses)[4] = nullptr,
+        uint64_t* aOutInputBlockId = nullptr)
 {
   const int TIME_BETWEEN_TOUCH_EVENT = 100;
   const int OVERCOME_TOUCH_TOLERANCE = 100;
 
+  // Even if the caller doesn't care about the block id, we need it to set the
+  // allowed touch behaviour below, so make sure aOutInputBlockId is non-null.
+  uint64_t blockId;
+  if (!aOutInputBlockId) {
+    aOutInputBlockId = &blockId;
+  }
+
   // Make sure the move is large enough to not be handled as a tap
-  nsEventStatus status = ApzcDown(aApzc, 10, aTouchStartY + OVERCOME_TOUCH_TOLERANCE, aTime);
+  nsEventStatus status = ApzcDown(aApzc, 10, aTouchStartY + OVERCOME_TOUCH_TOLERANCE, aTime, aOutInputBlockId);
   if (aOutEventStatuses) {
     (*aOutEventStatuses)[0] = status;
   }
 
   aTime += TIME_BETWEEN_TOUCH_EVENT;
 
   // Allowed touch behaviours must be set after sending touch-start.
   if (gfxPrefs::TouchActionEnabled() && aAllowedTouchBehaviors) {
-    aApzc->SetAllowedTouchBehavior(*aAllowedTouchBehaviors);
+    aApzc->SetAllowedTouchBehavior(*aOutInputBlockId, *aAllowedTouchBehaviors);
   }
 
   MultiTouchInput mti = MultiTouchInput(MultiTouchInput::MULTITOUCH_MOVE, aTime, TimeStamp(), 0);
   mti.mTouches.AppendElement(SingleTouchData(0, ScreenIntPoint(10, aTouchStartY), ScreenSize(0, 0), 0, 0));
-  status = aApzc->ReceiveInputEvent(mti);
+  status = aApzc->ReceiveInputEvent(mti, nullptr);
   if (aOutEventStatuses) {
     (*aOutEventStatuses)[1] = status;
   }
 
   aTime += TIME_BETWEEN_TOUCH_EVENT;
 
   mti = MultiTouchInput(MultiTouchInput::MULTITOUCH_MOVE, aTime, TimeStamp(), 0);
   mti.mTouches.AppendElement(SingleTouchData(0, ScreenIntPoint(10, aTouchEndY), ScreenSize(0, 0), 0, 0));
-  status = aApzc->ReceiveInputEvent(mti);
+  status = aApzc->ReceiveInputEvent(mti, nullptr);
   if (aOutEventStatuses) {
     (*aOutEventStatuses)[2] = status;
   }
 
   aTime += TIME_BETWEEN_TOUCH_EVENT;
 
   if (!aKeepFingerDown) {
     status = ApzcUp(aApzc, 10, aTouchEndY, aTime);
@@ -337,50 +361,52 @@ ApzcPan(AsyncPanZoomController* aApzc,
   aTime += TIME_BETWEEN_TOUCH_EVENT;
 }
 
 /*
  * Dispatches mock touch events to the apzc and checks whether apzc properly
  * consumed them and triggered scrolling behavior.
  */
 static void
-ApzcPanAndCheckStatus(AsyncPanZoomController* aApzc,
+ApzcPanAndCheckStatus(TestAsyncPanZoomController* aApzc,
                       int& aTime,
                       int aTouchStartY,
                       int aTouchEndY,
                       bool aExpectConsumed,
-                      nsTArray<uint32_t>* aAllowedTouchBehaviors)
+                      nsTArray<uint32_t>* aAllowedTouchBehaviors,
+                      uint64_t* aOutInputBlockId = nullptr)
 {
   nsEventStatus statuses[4]; // down, move, move, up
-  ApzcPan(aApzc, aTime, aTouchStartY, aTouchEndY, false, aAllowedTouchBehaviors, &statuses);
+  ApzcPan(aApzc, aTime, aTouchStartY, aTouchEndY, false, aAllowedTouchBehaviors, &statuses, aOutInputBlockId);
 
   EXPECT_EQ(nsEventStatus_eConsumeDoDefault, statuses[0]);
 
   nsEventStatus touchMoveStatus;
   if (aExpectConsumed) {
     touchMoveStatus = nsEventStatus_eConsumeDoDefault;
   } else {
     touchMoveStatus = nsEventStatus_eIgnore;
   }
   EXPECT_EQ(touchMoveStatus, statuses[1]);
   EXPECT_EQ(touchMoveStatus, statuses[2]);
 }
 
 static void
-ApzcPanNoFling(AsyncPanZoomController* aApzc,
+ApzcPanNoFling(TestAsyncPanZoomController* aApzc,
                int& aTime,
                int aTouchStartY,
-               int aTouchEndY)
+               int aTouchEndY,
+               uint64_t* aOutInputBlockId = nullptr)
 {
-  ApzcPan(aApzc, aTime, aTouchStartY, aTouchEndY);
+  ApzcPan(aApzc, aTime, aTouchStartY, aTouchEndY, false, nullptr, nullptr, aOutInputBlockId);
   aApzc->CancelAnimation();
 }
 
 static void
-ApzcPinchWithPinchInput(AsyncPanZoomController* aApzc,
+ApzcPinchWithPinchInput(TestAsyncPanZoomController* aApzc,
                         int aFocusX, int aFocusY, float aScale,
                         nsEventStatus (*aOutEventStatuses)[3] = nullptr)
 {
   nsEventStatus actualStatus = aApzc->HandleGestureEvent(
     PinchGestureInput(PinchGestureInput::PINCHGESTURE_START,
                       0, TimeStamp(), ScreenPoint(aFocusX, aFocusY),
                       10.0, 10.0, 0));
   if (aOutEventStatuses) {
@@ -400,83 +426,91 @@ ApzcPinchWithPinchInput(AsyncPanZoomCont
                       //       not to turn the pinch into a pan
                       -1.0, -1.0, 0));
   if (aOutEventStatuses) {
     (*aOutEventStatuses)[2] = actualStatus;
   }
 }
 
 static void
-ApzcPinchWithPinchInputAndCheckStatus(AsyncPanZoomController* aApzc,
+ApzcPinchWithPinchInputAndCheckStatus(TestAsyncPanZoomController* aApzc,
                                       int aFocusX, int aFocusY, float aScale,
                                       bool aShouldTriggerPinch)
 {
   nsEventStatus statuses[3];  // scalebegin, scale, scaleend
   ApzcPinchWithPinchInput(aApzc, aFocusX, aFocusY, aScale, &statuses);
 
   nsEventStatus expectedStatus = aShouldTriggerPinch
       ? nsEventStatus_eConsumeNoDefault
       : nsEventStatus_eIgnore;
   EXPECT_EQ(expectedStatus, statuses[0]);
   EXPECT_EQ(expectedStatus, statuses[1]);
 }
 
 static void
-ApzcPinchWithTouchInput(AsyncPanZoomController* aApzc,
+ApzcPinchWithTouchInput(TestAsyncPanZoomController* aApzc,
                         int aFocusX, int aFocusY, float aScale,
                         int& inputId,
                         nsTArray<uint32_t>* aAllowedTouchBehaviors = nullptr,
-                        nsEventStatus (*aOutEventStatuses)[4] = nullptr)
+                        nsEventStatus (*aOutEventStatuses)[4] = nullptr,
+                        uint64_t* aOutInputBlockId = nullptr)
 {
   // Having pinch coordinates in float type may cause problems with high-precision scale values
   // since SingleTouchData accepts integer value. But for trivial tests it should be ok.
   float pinchLength = 100.0;
   float pinchLengthScaled = pinchLength * aScale;
 
+  // Even if the caller doesn't care about the block id, we need it to set the
+  // allowed touch behaviour below, so make sure aOutInputBlockId is non-null.
+  uint64_t blockId;
+  if (!aOutInputBlockId) {
+    aOutInputBlockId = &blockId;
+  }
+
   MultiTouchInput mtiStart = MultiTouchInput(MultiTouchInput::MULTITOUCH_START, 0, TimeStamp(), 0);
   mtiStart.mTouches.AppendElement(SingleTouchData(inputId, ScreenIntPoint(aFocusX, aFocusY), ScreenSize(0, 0), 0, 0));
   mtiStart.mTouches.AppendElement(SingleTouchData(inputId + 1, ScreenIntPoint(aFocusX, aFocusY), ScreenSize(0, 0), 0, 0));
-  nsEventStatus status = aApzc->ReceiveInputEvent(mtiStart);
+  nsEventStatus status = aApzc->ReceiveInputEvent(mtiStart, aOutInputBlockId);
   if (aOutEventStatuses) {
     (*aOutEventStatuses)[0] = status;
   }
 
   if (gfxPrefs::TouchActionEnabled() && aAllowedTouchBehaviors) {
-    aApzc->SetAllowedTouchBehavior(*aAllowedTouchBehaviors);
+    aApzc->SetAllowedTouchBehavior(*aOutInputBlockId, *aAllowedTouchBehaviors);
   }
 
   MultiTouchInput mtiMove1 = MultiTouchInput(MultiTouchInput::MULTITOUCH_MOVE, 0, TimeStamp(), 0);
   mtiMove1.mTouches.AppendElement(SingleTouchData(inputId, ScreenIntPoint(aFocusX - pinchLength, aFocusY), ScreenSize(0, 0), 0, 0));
   mtiMove1.mTouches.AppendElement(SingleTouchData(inputId + 1, ScreenIntPoint(aFocusX + pinchLength, aFocusY), ScreenSize(0, 0), 0, 0));
-  status = aApzc->ReceiveInputEvent(mtiMove1);
+  status = aApzc->ReceiveInputEvent(mtiMove1, nullptr);
   if (aOutEventStatuses) {
     (*aOutEventStatuses)[1] = status;
   }
 
   MultiTouchInput mtiMove2 = MultiTouchInput(MultiTouchInput::MULTITOUCH_MOVE, 0, TimeStamp(), 0);
   mtiMove2.mTouches.AppendElement(SingleTouchData(inputId, ScreenIntPoint(aFocusX - pinchLengthScaled, aFocusY), ScreenSize(0, 0), 0, 0));
   mtiMove2.mTouches.AppendElement(SingleTouchData(inputId + 1, ScreenIntPoint(aFocusX + pinchLengthScaled, aFocusY), ScreenSize(0, 0), 0, 0));
-  status = aApzc->ReceiveInputEvent(mtiMove2);
+  status = aApzc->ReceiveInputEvent(mtiMove2, nullptr);
   if (aOutEventStatuses) {
     (*aOutEventStatuses)[2] = status;
   }
 
   MultiTouchInput mtiEnd = MultiTouchInput(MultiTouchInput::MULTITOUCH_END, 0, TimeStamp(), 0);
   mtiEnd.mTouches.AppendElement(SingleTouchData(inputId, ScreenIntPoint(aFocusX - pinchLengthScaled, aFocusY), ScreenSize(0, 0), 0, 0));
   mtiEnd.mTouches.AppendElement(SingleTouchData(inputId + 1, ScreenIntPoint(aFocusX + pinchLengthScaled, aFocusY), ScreenSize(0, 0), 0, 0));
-  status = aApzc->ReceiveInputEvent(mtiEnd);
+  status = aApzc->ReceiveInputEvent(mtiEnd, nullptr);
   if (aOutEventStatuses) {
     (*aOutEventStatuses)[3] = status;
   }
 
   inputId += 2;
 }
 
 static void
-ApzcPinchWithTouchInputAndCheckStatus(AsyncPanZoomController* aApzc,
+ApzcPinchWithTouchInputAndCheckStatus(TestAsyncPanZoomController* aApzc,
                                       int aFocusX, int aFocusY, float aScale,
                                       int& inputId, bool aShouldTriggerPinch,
                                       nsTArray<uint32_t>* aAllowedTouchBehaviors)
 {
   nsEventStatus statuses[4];  // down, move, move, up
   ApzcPinchWithTouchInput(aApzc, aFocusX, aFocusY, aScale, inputId, aAllowedTouchBehaviors, &statuses);
 
   nsEventStatus expectedMoveStatus = aShouldTriggerPinch
@@ -613,20 +647,21 @@ TEST_F(APZCPinchGestureDetectorTester, P
 TEST_F(APZCPinchGestureDetectorTester, Pinch_PreventDefault) {
   FrameMetrics originalMetrics = GetPinchableFrameMetrics();
   apzc->SetFrameMetrics(originalMetrics);
 
   SetMayHaveTouchListeners();
   MakeApzcZoomable();
 
   int touchInputId = 0;
-  ApzcPinchWithTouchInput(apzc, 250, 300, 1.25, touchInputId);
+  uint64_t blockId = 0;
+  ApzcPinchWithTouchInput(apzc, 250, 300, 1.25, touchInputId, nullptr, nullptr, &blockId);
 
   // Send the prevent-default notification for the touch block
-  apzc->ContentReceivedTouch(true);
+  apzc->ContentReceivedTouch(blockId, true);
 
   // Run all pending tasks (this should include at least the
   // prevent-default timer).
   EXPECT_LE(1, mcc->RunThroughDelayedTasks());
 
   // verify the metrics didn't change (i.e. the pinch was ignored)
   FrameMetrics fm = apzc->GetFrameMetrics();
   EXPECT_EQ(originalMetrics.GetZoom().scale, fm.GetZoom().scale);
@@ -817,25 +852,26 @@ protected:
   {
     SetMayHaveTouchListeners();
 
     int time = 0;
     int touchStart = 50;
     int touchEnd = 10;
     ScreenPoint pointOut;
     ViewTransform viewTransformOut;
+    uint64_t blockId = 0;
 
     // Pan down
     nsTArray<uint32_t> allowedTouchBehaviors;
     allowedTouchBehaviors.AppendElement(mozilla::layers::AllowedTouchBehavior::VERTICAL_PAN);
-    ApzcPanAndCheckStatus(apzc, time, touchStart, touchEnd, true, &allowedTouchBehaviors);
+    ApzcPanAndCheckStatus(apzc, time, touchStart, touchEnd, true, &allowedTouchBehaviors, &blockId);
 
     // Send the signal that content has handled and preventDefaulted the touch
     // events. This flushes the event queue.
-    apzc->ContentReceivedTouch(true);
+    apzc->ContentReceivedTouch(blockId, true);
     // Run all pending tasks (this should include at least the
     // prevent-default timer).
     EXPECT_LE(1, mcc->RunThroughDelayedTasks());
 
     apzc->SampleContentTransformForFrame(testStartTime, &viewTransformOut, pointOut);
     EXPECT_EQ(ScreenPoint(), pointOut);
     EXPECT_EQ(ViewTransform(), viewTransformOut);
 
@@ -1105,40 +1141,41 @@ protected:
   }
 
   void DoFlingStopWithSlowListener(bool aPreventDefault) {
     SetMayHaveTouchListeners();
 
     int time = 0;
     int touchStart = 50;
     int touchEnd = 10;
+    uint64_t blockId = 0;
 
     // Start the fling down.
-    ApzcPan(apzc, time, touchStart, touchEnd);
-    apzc->ContentReceivedTouch(false);
+    ApzcPan(apzc, time, touchStart, touchEnd, false, nullptr, nullptr, &blockId);
+    apzc->ContentReceivedTouch(blockId, false);
     while (mcc->RunThroughDelayedTasks());
 
     // Sample the fling a couple of times to ensure it's going.
     ScreenPoint point, finalPoint;
     ViewTransform viewTransform;
     apzc->SampleContentTransformForFrame(testStartTime + TimeDuration::FromMilliseconds(10), &viewTransform, point);
     apzc->SampleContentTransformForFrame(testStartTime + TimeDuration::FromMilliseconds(20), &viewTransform, finalPoint);
     EXPECT_GT(finalPoint.y, point.y);
 
     // Now we put our finger down to stop the fling
-    ApzcDown(apzc, 10, 10, time);
+    ApzcDown(apzc, 10, 10, time, &blockId);
 
     // Re-sample to make sure it hasn't moved
     apzc->SampleContentTransformForFrame(testStartTime + TimeDuration::FromMilliseconds(30), &viewTransform, point);
     EXPECT_EQ(finalPoint.x, point.x);
     EXPECT_EQ(finalPoint.y, point.y);
 
     // respond to the touchdown that stopped the fling.
     // even if we do a prevent-default on it, the animation should remain stopped.
-    apzc->ContentReceivedTouch(aPreventDefault);
+    apzc->ContentReceivedTouch(blockId, aPreventDefault);
     while (mcc->RunThroughDelayedTasks());
 
     // Verify the page hasn't moved
     apzc->SampleContentTransformForFrame(testStartTime + TimeDuration::FromMilliseconds(100), &viewTransform, point);
     EXPECT_EQ(finalPoint.x, point.x);
     EXPECT_EQ(finalPoint.y, point.y);
 
     // clean up
@@ -1206,36 +1243,38 @@ TEST_F(APZCGestureDetectorTester, Medium
 }
 
 class APZCLongPressTester : public APZCGestureDetectorTester {
 protected:
   void DoLongPressTest(uint32_t aBehavior) {
     MakeApzcUnzoomable();
 
     int time = 0;
-
-    nsEventStatus status = ApzcDown(apzc, 10, 10, time);
+    uint64_t blockId = 0;
+
+    nsEventStatus status = ApzcDown(apzc, 10, 10, time, &blockId);
     EXPECT_EQ(nsEventStatus_eConsumeDoDefault, status);
 
     if (gfxPrefs::TouchActionEnabled()) {
       // SetAllowedTouchBehavior() must be called after sending touch-start.
       nsTArray<uint32_t> allowedTouchBehaviors;
       allowedTouchBehaviors.AppendElement(aBehavior);
-      apzc->SetAllowedTouchBehavior(allowedTouchBehaviors);
+      apzc->SetAllowedTouchBehavior(blockId, allowedTouchBehaviors);
     }
     // Have content "respond" to the touchstart
-    apzc->ContentReceivedTouch(false);
+    apzc->ContentReceivedTouch(blockId, false);
 
     MockFunction<void(std::string checkPointName)> check;
 
     {
       InSequence s;
 
       EXPECT_CALL(check, Call("preHandleLongTap"));
-      EXPECT_CALL(*mcc, HandleLongTap(CSSPoint(10, 10), 0, apzc->GetGuid())).Times(1);
+      blockId++;
+      EXPECT_CALL(*mcc, HandleLongTap(CSSPoint(10, 10), 0, apzc->GetGuid(), blockId)).Times(1);
       EXPECT_CALL(check, Call("postHandleLongTap"));
 
       EXPECT_CALL(check, Call("preHandleLongTapUp"));
       EXPECT_CALL(*mcc, HandleLongTapUp(CSSPoint(10, 10), 0, apzc->GetGuid())).Times(1);
       EXPECT_CALL(check, Call("postHandleLongTapUp"));
     }
 
     // There is a longpress event scheduled on a timeout
@@ -1249,17 +1288,17 @@ protected:
     // Destroy pending MAX_TAP timeout task
     mcc->DestroyOldestTask();
 
     // Dispatching the longpress event starts a new touch block, which
     // needs a new content response and also has a pending timeout task
     // in the queue. Deal with those here. We do the content response first
     // with preventDefault=false, and then we run the timeout task which
     // "loses the race" and does nothing.
-    apzc->ContentReceivedTouch(false);
+    apzc->ContentReceivedTouch(blockId, false);
     mcc->CheckHasDelayedTask();
     mcc->RunDelayedTask();
 
     time += 1000;
 
     // Finally, simulate lifting the finger. Since the long-press wasn't
     // prevent-defaulted, we should get a long-tap-up event.
     check.Call("preHandleLongTapUp");
@@ -1276,35 +1315,37 @@ protected:
     EXPECT_CALL(*mcc, SendAsyncScrollDOMEvent(_,_,_)).Times(0);
     EXPECT_CALL(*mcc, RequestContentRepaint(_)).Times(0);
 
     int touchX = 10,
         touchStartY = 10,
         touchEndY = 50;
 
     int time = 0;
-    nsEventStatus status = ApzcDown(apzc, touchX, touchStartY, time);
+    uint64_t blockId = 0;
+    nsEventStatus status = ApzcDown(apzc, touchX, touchStartY, time, &blockId);
     EXPECT_EQ(nsEventStatus_eConsumeDoDefault, status);
 
     if (gfxPrefs::TouchActionEnabled()) {
       // SetAllowedTouchBehavior() must be called after sending touch-start.
       nsTArray<uint32_t> allowedTouchBehaviors;
       allowedTouchBehaviors.AppendElement(aBehavior);
-      apzc->SetAllowedTouchBehavior(allowedTouchBehaviors);
+      apzc->SetAllowedTouchBehavior(blockId, allowedTouchBehaviors);
     }
     // Have content "respond" to the touchstart
-    apzc->ContentReceivedTouch(false);
+    apzc->ContentReceivedTouch(blockId, false);
 
     MockFunction<void(std::string checkPointName)> check;
 
     {
       InSequence s;
 
       EXPECT_CALL(check, Call("preHandleLongTap"));
-      EXPECT_CALL(*mcc, HandleLongTap(CSSPoint(touchX, touchStartY), 0, apzc->GetGuid())).Times(1);
+      blockId++;
+      EXPECT_CALL(*mcc, HandleLongTap(CSSPoint(touchX, touchStartY), 0, apzc->GetGuid(), blockId)).Times(1);
       EXPECT_CALL(check, Call("postHandleLongTap"));
     }
 
     mcc->CheckHasDelayedTask();
 
     // Manually invoke the longpress while the touch is currently down.
     check.Call("preHandleLongTap");
     mcc->RunDelayedTask();
@@ -1313,25 +1354,25 @@ protected:
     // Destroy pending MAX_TAP timeout task
     mcc->DestroyOldestTask();
 
     // There should be a TimeoutContentResponse task in the queue still,
     // waiting for the response from the longtap event dispatched above.
     // Send the signal that content has handled the long-tap, and then run
     // the timeout task (it will be a no-op because the content "wins" the
     // race. This takes the place of the "contextmenu" event.
-    apzc->ContentReceivedTouch(true);
+    apzc->ContentReceivedTouch(blockId, true);
     mcc->CheckHasDelayedTask();
     mcc->RunDelayedTask();
 
     time += 1000;
 
     MultiTouchInput mti = MultiTouchInput(MultiTouchInput::MULTITOUCH_MOVE, time, TimeStamp(), 0);
     mti.mTouches.AppendElement(SingleTouchData(0, ScreenIntPoint(touchX, touchEndY), ScreenSize(0, 0), 0, 0));
-    status = apzc->ReceiveInputEvent(mti);
+    status = apzc->ReceiveInputEvent(mti, nullptr);
     EXPECT_EQ(nsEventStatus_eConsumeDoDefault, status);
 
     EXPECT_CALL(*mcc, HandleLongTapUp(CSSPoint(touchX, touchEndY), 0, apzc->GetGuid())).Times(0);
     status = ApzcUp(apzc, touchX, touchEndY, time);
     EXPECT_EQ(nsEventStatus_eConsumeDoDefault, status);
 
     ScreenPoint pointOut;
     ViewTransform viewTransformOut;
@@ -1362,121 +1403,134 @@ TEST_F(APZCLongPressTester, LongPressPre
 TEST_F(APZCLongPressTester, LongPressPreventDefaultWithTouchAction) {
   SCOPED_GFX_PREF(TouchActionEnabled, bool, true);
   DoLongPressPreventDefaultTest(mozilla::layers::AllowedTouchBehavior::HORIZONTAL_PAN
                                 | mozilla::layers::AllowedTouchBehavior::VERTICAL_PAN
                                 | mozilla::layers::AllowedTouchBehavior::PINCH_ZOOM);
 }
 
 static void
-ApzcDoubleTap(AsyncPanZoomController* aApzc, int aX, int aY, int& aTime,
-              nsEventStatus (*aOutEventStatuses)[4] = nullptr)
+ApzcDoubleTap(TestAsyncPanZoomController* aApzc, int aX, int aY, int& aTime,
+              nsEventStatus (*aOutEventStatuses)[4] = nullptr,
+              uint64_t (*aOutInputBlockIds)[2] = nullptr)
 {
-  nsEventStatus status = ApzcDown(aApzc, aX, aY, aTime);
+  uint64_t blockId;
+  nsEventStatus status = ApzcDown(aApzc, aX, aY, aTime, &blockId);
   if (aOutEventStatuses) {
     (*aOutEventStatuses)[0] = status;
   }
+  if (aOutInputBlockIds) {
+    (*aOutInputBlockIds)[0] = blockId;
+  }
   aTime += 10;
   status = ApzcUp(aApzc, aX, aY, aTime);
   if (aOutEventStatuses) {
     (*aOutEventStatuses)[1] = status;
   }
   aTime += 10;
-  status = ApzcDown(aApzc, aX, aY, aTime);
+  status = ApzcDown(aApzc, aX, aY, aTime, &blockId);
   if (aOutEventStatuses) {
     (*aOutEventStatuses)[2] = status;
   }
+  if (aOutInputBlockIds) {
+    (*aOutInputBlockIds)[1] = blockId;
+  }
   aTime += 10;
   status = ApzcUp(aApzc, aX, aY, aTime);
   if (aOutEventStatuses) {
     (*aOutEventStatuses)[3] = status;
   }
 }
 
 static void
-ApzcDoubleTapAndCheckStatus(AsyncPanZoomController* aApzc, int aX, int aY, int& aTime)
+ApzcDoubleTapAndCheckStatus(TestAsyncPanZoomController* aApzc, int aX, int aY, int& aTime, uint64_t (*aOutInputBlockIds)[2] = nullptr)
 {
   nsEventStatus statuses[4];
-  ApzcDoubleTap(aApzc, aX, aY, aTime, &statuses);
+  ApzcDoubleTap(aApzc, aX, aY, aTime, &statuses, aOutInputBlockIds);
   EXPECT_EQ(nsEventStatus_eConsumeDoDefault, statuses[0]);
   EXPECT_EQ(nsEventStatus_eConsumeDoDefault, statuses[1]);
   EXPECT_EQ(nsEventStatus_eConsumeDoDefault, statuses[2]);
   EXPECT_EQ(nsEventStatus_eConsumeDoDefault, statuses[3]);
 }
 
 TEST_F(APZCGestureDetectorTester, DoubleTap) {
   SetMayHaveTouchListeners();
   MakeApzcZoomable();
 
   EXPECT_CALL(*mcc, HandleSingleTap(CSSPoint(10, 10), 0, apzc->GetGuid())).Times(0);
   EXPECT_CALL(*mcc, HandleDoubleTap(CSSPoint(10, 10), 0, apzc->GetGuid())).Times(1);
 
   int time = 0;
-  ApzcDoubleTapAndCheckStatus(apzc, 10, 10, time);
+  uint64_t blockIds[2];
+  ApzcDoubleTapAndCheckStatus(apzc, 10, 10, time, &blockIds);
 
   // responses to the two touchstarts
-  apzc->ContentReceivedTouch(false);
-  apzc->ContentReceivedTouch(false);
+  apzc->ContentReceivedTouch(blockIds[0], false);
+  apzc->ContentReceivedTouch(blockIds[1], false);
 
   while (mcc->RunThroughDelayedTasks());
 
   apzc->AssertStateIsReset();
 }
 
 TEST_F(APZCGestureDetectorTester, DoubleTapNotZoomable) {
   SetMayHaveTouchListeners();
   MakeApzcUnzoomable();
 
   EXPECT_CALL(*mcc, HandleSingleTap(CSSPoint(10, 10), 0, apzc->GetGuid())).Times(2);
   EXPECT_CALL(*mcc, HandleDoubleTap(CSSPoint(10, 10), 0, apzc->GetGuid())).Times(0);
 
   int time = 0;
-  ApzcDoubleTapAndCheckStatus(apzc, 10, 10, time);
+  uint64_t blockIds[2];
+  ApzcDoubleTapAndCheckStatus(apzc, 10, 10, time, &blockIds);
 
   // responses to the two touchstarts
-  apzc->ContentReceivedTouch(false);
-  apzc->ContentReceivedTouch(false);
+  apzc->ContentReceivedTouch(blockIds[0], false);
+  apzc->ContentReceivedTouch(blockIds[1], false);
 
   while (mcc->RunThroughDelayedTasks());
 
   apzc->AssertStateIsReset();
 }
 
 TEST_F(APZCGestureDetectorTester, DoubleTapPreventDefaultFirstOnly) {
   SetMayHaveTouchListeners();
   MakeApzcZoomable();
 
   EXPECT_CALL(*mcc, HandleSingleTap(CSSPoint(10, 10), 0, apzc->GetGuid())).Times(1);
   EXPECT_CALL(*mcc, HandleDoubleTap(CSSPoint(10, 10), 0, apzc->GetGuid())).Times(0);
 
   int time = 0;
-  ApzcDoubleTapAndCheckStatus(apzc, 10, 10, time);
+  uint64_t blockIds[2];
+  ApzcDoubleTapAndCheckStatus(apzc, 10, 10, time, &blockIds);
 
   // responses to the two touchstarts
-  apzc->ContentReceivedTouch(true);
-  apzc->ContentReceivedTouch(false);
+  apzc->ContentReceivedTouch(blockIds[0], true);
+  apzc->ContentReceivedTouch(blockIds[1], false);
 
   while (mcc->RunThroughDelayedTasks());
 
   apzc->AssertStateIsReset();
 }
 
 TEST_F(APZCGestureDetectorTester, DoubleTapPreventDefaultBoth) {
   SetMayHaveTouchListeners();
   MakeApzcZoomable();
 
   EXPECT_CALL(*mcc, HandleSingleTap(CSSPoint(10, 10), 0, apzc->GetGuid())).Times(0);
   EXPECT_CALL(*mcc, HandleDoubleTap(CSSPoint(10, 10), 0, apzc->GetGuid())).Times(0);
 
   int time = 0;
-  ApzcDoubleTapAndCheckStatus(apzc, 10, 10, time);
+  uint64_t blockIds[2];
+  ApzcDoubleTapAndCheckStatus(apzc, 10, 10, time, &blockIds);
+printf_stderr("blockids %llu %llu\n", blockIds[0], blockIds[1]);
 
   // responses to the two touchstarts
-  apzc->ContentReceivedTouch(true);
-  apzc->ContentReceivedTouch(true);
+  apzc->ContentReceivedTouch(blockIds[0], true);
+  apzc->ContentReceivedTouch(blockIds[1], true);
 
   while (mcc->RunThroughDelayedTasks());
 
   apzc->AssertStateIsReset();
 }
 
 // Test for bug 947892
 // We test whether we dispatch tap event when the tap is followed by pinch.
@@ -1488,22 +1542,22 @@ TEST_F(APZCGestureDetectorTester, TapFol
   int time = 0;
   ApzcTap(apzc, 10, 10, time, 100);
 
   int inputId = 0;
   MultiTouchInput mti;
   mti = MultiTouchInput(MultiTouchInput::MULTITOUCH_START, time, TimeStamp(), 0);
   mti.mTouches.AppendElement(SingleTouchData(inputId, ScreenIntPoint(20, 20), ScreenSize(0, 0), 0, 0));
   mti.mTouches.AppendElement(SingleTouchData(inputId + 1, ScreenIntPoint(10, 10), ScreenSize(0, 0), 0, 0));
-  apzc->ReceiveInputEvent(mti);
+  apzc->ReceiveInputEvent(mti, nullptr);
 
   mti = MultiTouchInput(MultiTouchInput::MULTITOUCH_END, time, TimeStamp(), 0);
   mti.mTouches.AppendElement(SingleTouchData(inputId, ScreenIntPoint(20, 20), ScreenSize(0, 0), 0, 0));
   mti.mTouches.AppendElement(SingleTouchData(inputId + 1, ScreenIntPoint(10, 10), ScreenSize(0, 0), 0, 0));
-  apzc->ReceiveInputEvent(mti);
+  apzc->ReceiveInputEvent(mti, nullptr);
 
   while (mcc->RunThroughDelayedTasks());
 
   apzc->AssertStateIsReset();
 }
 
 TEST_F(APZCGestureDetectorTester, TapFollowedByMultipleTouches) {
   MakeApzcZoomable();
@@ -1512,27 +1566,27 @@ TEST_F(APZCGestureDetectorTester, TapFol
 
   int time = 0;
   ApzcTap(apzc, 10, 10, time, 100);
 
   int inputId = 0;
   MultiTouchInput mti;
   mti = MultiTouchInput(MultiTouchInput::MULTITOUCH_START, time, TimeStamp(), 0);
   mti.mTouches.AppendElement(SingleTouchData(inputId, ScreenIntPoint(20, 20), ScreenSize(0, 0), 0, 0));
-  apzc->ReceiveInputEvent(mti);
+  apzc->ReceiveInputEvent(mti, nullptr);
 
   mti = MultiTouchInput(MultiTouchInput::MULTITOUCH_START, time, TimeStamp(), 0);
   mti.mTouches.AppendElement(SingleTouchData(inputId, ScreenIntPoint(20, 20), ScreenSize(0, 0), 0, 0));
   mti.mTouches.AppendElement(SingleTouchData(inputId + 1, ScreenIntPoint(10, 10), ScreenSize(0, 0), 0, 0));
-  apzc->ReceiveInputEvent(mti);
+  apzc->ReceiveInputEvent(mti, nullptr);
 
   mti = MultiTouchInput(MultiTouchInput::MULTITOUCH_END, time, TimeStamp(), 0);
   mti.mTouches.AppendElement(SingleTouchData(inputId, ScreenIntPoint(20, 20), ScreenSize(0, 0), 0, 0));
   mti.mTouches.AppendElement(SingleTouchData(inputId + 1, ScreenIntPoint(10, 10), ScreenSize(0, 0), 0, 0));
-  apzc->ReceiveInputEvent(mti);
+  apzc->ReceiveInputEvent(mti, nullptr);
 
   while (mcc->RunThroughDelayedTasks());
 
   apzc->AssertStateIsReset();
 }
 
 class APZCTreeManagerTester : public ::testing::Test {
 protected:
@@ -1631,36 +1685,36 @@ ApzctmPan(APZCTreeManager* aTreeManager,
   SCOPED_GFX_PREF(APZTouchStartTolerance, float, 1.0f / 1000.0f);
   const int OVERCOME_TOUCH_TOLERANCE = 1;
 
   const int TIME_BETWEEN_TOUCH_EVENT = 100;
 
   // Make sure the move is large enough to not be handled as a tap
   MultiTouchInput mti = MultiTouchInput(MultiTouchInput::MULTITOUCH_START, aTime, TimeStamp(), 0);
   mti.mTouches.AppendElement(SingleTouchData(0, ScreenIntPoint(10, aTouchStartY), ScreenSize(0, 0), 0, 0));
-  aTreeManager->ReceiveInputEvent(mti, nullptr);
+  aTreeManager->ReceiveInputEvent(mti, nullptr, nullptr);
 
   aTime += TIME_BETWEEN_TOUCH_EVENT;
 
   mti = MultiTouchInput(MultiTouchInput::MULTITOUCH_MOVE, aTime, TimeStamp(), 0);
   mti.mTouches.AppendElement(SingleTouchData(0, ScreenIntPoint(10, aTouchStartY + OVERCOME_TOUCH_TOLERANCE), ScreenSize(0, 0), 0, 0));
-  aTreeManager->ReceiveInputEvent(mti, nullptr);
+  aTreeManager->ReceiveInputEvent(mti, nullptr, nullptr);
 
   aTime += TIME_BETWEEN_TOUCH_EVENT;
 
   mti = MultiTouchInput(MultiTouchInput::MULTITOUCH_MOVE, aTime, TimeStamp(), 0);
   mti.mTouches.AppendElement(SingleTouchData(0, ScreenIntPoint(10, aTouchEndY), ScreenSize(0, 0), 0, 0));
-  aTreeManager->ReceiveInputEvent(mti, nullptr);
+  aTreeManager->ReceiveInputEvent(mti, nullptr, nullptr);
 
   aTime += TIME_BETWEEN_TOUCH_EVENT;
 
   if (!aKeepFingerDown) {
     mti = MultiTouchInput(MultiTouchInput::MULTITOUCH_END, aTime, TimeStamp(), 0);
     mti.mTouches.AppendElement(SingleTouchData(0, ScreenIntPoint(10, aTouchEndY), ScreenSize(0, 0), 0, 0));
-    aTreeManager->ReceiveInputEvent(mti, nullptr);
+    aTreeManager->ReceiveInputEvent(mti, nullptr, nullptr);
   }
 
   aTime += TIME_BETWEEN_TOUCH_EVENT;
 }
 
 class APZHitTestingTester : public APZCTreeManagerTester {
 protected:
   Matrix4x4 transformToApzc;
@@ -1739,17 +1793,17 @@ protected:
 
 // A simple hit testing test that doesn't involve any transforms on layers.
 TEST_F(APZHitTestingTester, HitTesting1) {
   CreateHitTesting1LayerTree();
   ScopedLayerTreeRegistration registration(0, root, mcc);
 
   // No APZC attached so hit testing will return no APZC at (20,20)
   nsRefPtr<AsyncPanZoomController> hit = GetTargetAPZC(ScreenPoint(20, 20));
-  AsyncPanZoomController* nullAPZC = nullptr;
+  TestAsyncPanZoomController* nullAPZC = nullptr;
   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);
@@ -1810,19 +1864,19 @@ TEST_F(APZHitTestingTester, HitTesting2)
   manager->UpdatePanZoomControllerTree(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)
 
-  AsyncPanZoomController* apzcroot = ApzcOf(root);
-  AsyncPanZoomController* apzc1 = ApzcOf(layers[1]);
-  AsyncPanZoomController* apzc3 = ApzcOf(layers[3]);
+  TestAsyncPanZoomController* apzcroot = ApzcOf(root);
+  TestAsyncPanZoomController* apzc1 = ApzcOf(layers[1]);
+  TestAsyncPanZoomController* apzc3 = ApzcOf(layers[3]);
 
   // Hit an area that's clearly on the root layer but not any of the child layers.
   nsRefPtr<AsyncPanZoomController> hit = GetTargetAPZC(ScreenPoint(75, 25));
   EXPECT_EQ(apzcroot, hit.get());
   EXPECT_EQ(Point(75, 25), transformToApzc * Point(75, 25));
   EXPECT_EQ(Point(75, 25), transformToGecko * Point(75, 25));
 
   // Hit an area on the root that would be on layers[3] if layers[2]
@@ -1920,17 +1974,17 @@ TEST_F(APZCTreeManagerTester, Scrollable
   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);
 
-  AsyncPanZoomController* nullAPZC = nullptr;
+  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);
@@ -1961,17 +2015,17 @@ TEST_F(APZCTreeManagerTester, Bug1068268
   EXPECT_EQ(ApzcOf(layers[2]), ApzcOf(layers[6])->GetParent());
 }
 
 TEST_F(APZHitTestingTester, ComplexMultiLayerTree) {
   CreateComplexMultiLayerTree();
   ScopedLayerTreeRegistration registration(0, root, mcc);
   manager->UpdatePanZoomControllerTree(nullptr, root, false, 0, 0);
 
-  AsyncPanZoomController* nullAPZC = nullptr;
+  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]));
   EXPECT_FALSE(layers[5]->HasScrollableFrameMetrics());
   EXPECT_NE(nullAPZC, ApzcOf(layers[6]));
@@ -1986,20 +2040,20 @@ TEST_F(APZHitTestingTester, ComplexMulti
   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
-  AsyncPanZoomController* layers1_2 = ApzcOf(layers[1]);
-  AsyncPanZoomController* layers4_6_8 = ApzcOf(layers[4]);
-  AsyncPanZoomController* layer7 = ApzcOf(layers[7]);
-  AsyncPanZoomController* layer9 = ApzcOf(layers[9]);
+  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());
@@ -2016,17 +2070,17 @@ TEST_F(APZHitTestingTester, TestRepaintF
   // 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);
-  AsyncPanZoomController* apzcroot = ApzcOf(root);
+  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;
 
   {
     InSequence s;
@@ -2043,45 +2097,45 @@ TEST_F(APZHitTestingTester, TestRepaintF
   // This first pan will move the APZC by 50 pixels, and dispatch a paint request.
   ApzcPanNoFling(apzcroot, time, 100, 50);
 
   // Verify that a touch start doesn't get untransformed
   ScreenIntPoint touchPoint(50, 50);
   MultiTouchInput mti = MultiTouchInput(MultiTouchInput::MULTITOUCH_START, time, TimeStamp(), 0);
   mti.mTouches.AppendElement(SingleTouchData(0, touchPoint, ScreenSize(0, 0), 0, 0));
 
-  EXPECT_EQ(nsEventStatus_eConsumeDoDefault, manager->ReceiveInputEvent(mti, nullptr));
+  EXPECT_EQ(nsEventStatus_eConsumeDoDefault, manager->ReceiveInputEvent(mti, nullptr, nullptr));
   EXPECT_EQ(touchPoint, mti.mTouches[0].mScreenPoint);
   check.Call("post-first-touch-start");
 
   // Send a touchend to clear state
   mti.mType = MultiTouchInput::MULTITOUCH_END;
-  manager->ReceiveInputEvent(mti, nullptr);
+  manager->ReceiveInputEvent(mti, nullptr, nullptr);
 
   AsyncPanZoomController::SetFrameTime(testStartTime + TimeDuration::FromMilliseconds(1000));
 
   // Now do two pans. The first of these will dispatch a repaint request, as above.
   // The second will get stuck in the paint throttler because the first one doesn't
   // get marked as "completed", so this will result in a non-empty LD transform.
   // (Note that any outstanding repaint requests from the first half of this test
   // don't impact this half because we advance the time by 1 second, which will trigger
   // the max-wait-exceeded codepath in the paint throttler).
   ApzcPanNoFling(apzcroot, time, 100, 50);
   check.Call("post-second-fling");
   ApzcPanNoFling(apzcroot, time, 100, 50);
 
   // Ensure that a touch start again doesn't get untransformed by flushing
   // a repaint
   mti.mType = MultiTouchInput::MULTITOUCH_START;
-  EXPECT_EQ(nsEventStatus_eConsumeDoDefault, manager->ReceiveInputEvent(mti, nullptr));
+  EXPECT_EQ(nsEventStatus_eConsumeDoDefault, manager->ReceiveInputEvent(mti, nullptr, nullptr));
   EXPECT_EQ(touchPoint, mti.mTouches[0].mScreenPoint);
   check.Call("post-second-touch-start");
 
   mti.mType = MultiTouchInput::MULTITOUCH_END;
-  EXPECT_EQ(nsEventStatus_eConsumeDoDefault, manager->ReceiveInputEvent(mti, nullptr));
+  EXPECT_EQ(nsEventStatus_eConsumeDoDefault, manager->ReceiveInputEvent(mti, nullptr, nullptr));
   EXPECT_EQ(touchPoint, mti.mTouches[0].mScreenPoint);
 
   mcc->RunThroughDelayedTasks();
 }
 
 class APZOverscrollHandoffTester : public APZCTreeManagerTester {
 protected:
   UniquePtr<ScopedLayerTreeRegistration> registration;
@@ -2175,20 +2229,21 @@ TEST_F(APZOverscrollHandoffTester, Defer
   TestAsyncPanZoomController* childApzc = ApzcOf(layers[1]);
 
   // Enable touch-listeners so that we can separate the queueing of input
   // events from them being processed.
   childApzc->GetFrameMetrics().SetMayHaveTouchListeners(true);
 
   // Queue input events for a pan.
   int time = 0;
-  ApzcPanNoFling(childApzc, time, 90, 30);
+  uint64_t blockId = 0;
+  ApzcPanNoFling(childApzc, time, 90, 30, &blockId);
 
   // Allow the pan to be processed.
-  childApzc->ContentReceivedTouch(false);
+  childApzc->ContentReceivedTouch(blockId, false);
 
   // Make sure overscroll was handed off correctly.
   EXPECT_EQ(50, childApzc->GetFrameMetrics().GetScrollOffset().y);
   EXPECT_EQ(10, rootApzc->GetFrameMetrics().GetScrollOffset().y);
 }
 
 // Here we test that if the layer structure changes in between two input
 // blocks being queued, and the first block is only processed after the second
@@ -2202,39 +2257,41 @@ TEST_F(APZOverscrollHandoffTester, Layer
   TestAsyncPanZoomController* childApzc = ApzcOf(layers[1]);
 
   // Enable touch-listeners so that we can separate the queueing of input
   // events from them being processed.
   childApzc->GetFrameMetrics().SetMayHaveTouchListeners(true);
 
   // Queue input events for a pan.
   int time = 0;
-  ApzcPanNoFling(childApzc, time, 90, 30);
+  uint64_t blockId = 0;
+  ApzcPanNoFling(childApzc, time, 90, 30, &blockId);
 
   // Modify the APZC tree to insert a new APZC 'middle' into the handoff chain
   // between the child and the root.
   CreateOverscrollHandoffLayerTree2();
   nsRefPtr<Layer> middle = layers[1];
   childApzc->GetFrameMetrics().SetMayHaveTouchListeners(true);
   TestAsyncPanZoomController* middleApzc = ApzcOf(middle);
 
   // Queue input events for another pan.
-  ApzcPanNoFling(childApzc, time, 30, 90);
+  uint64_t secondBlockId = 0;
+  ApzcPanNoFling(childApzc, time, 30, 90, &secondBlockId);
 
   // Allow the first pan to be processed.
-  childApzc->ContentReceivedTouch(false);
+  childApzc->ContentReceivedTouch(blockId, false);
 
   // Make sure things have scrolled according to the handoff chain in
   // place at the time the touch-start of the first pan was queued.
   EXPECT_EQ(50, childApzc->GetFrameMetrics().GetScrollOffset().y);
   EXPECT_EQ(10, rootApzc->GetFrameMetrics().GetScrollOffset().y);
   EXPECT_EQ(0, middleApzc->GetFrameMetrics().GetScrollOffset().y);
 
   // Allow the second pan to be processed.
-  childApzc->ContentReceivedTouch(false);
+  childApzc->ContentReceivedTouch(secondBlockId, false);
 
   // Make sure things have scrolled according to the handoff chain in
   // place at the time the touch-start of the second pan was queued.
   EXPECT_EQ(0, childApzc->GetFrameMetrics().GetScrollOffset().y);
   EXPECT_EQ(10, rootApzc->GetFrameMetrics().GetScrollOffset().y);
   EXPECT_EQ(-10, middleApzc->GetFrameMetrics().GetScrollOffset().y);
 }
 
@@ -2254,22 +2311,22 @@ TEST_F(APZOverscrollHandoffTester, Stuck
   EXPECT_FALSE(child->IsOverscrolled());
   EXPECT_TRUE(rootApzc->IsOverscrolled());
 
   // Put a second finger down.
   MultiTouchInput secondFingerDown(MultiTouchInput::MULTITOUCH_START, 0, TimeStamp(), 0);
   // Use the same touch identifier for the first touch (0) as ApzctmPan(). (A bit hacky.)
   secondFingerDown.mTouches.AppendElement(SingleTouchData(0, ScreenIntPoint(10, 40), ScreenSize(0, 0), 0, 0));
   secondFingerDown.mTouches.AppendElement(SingleTouchData(1, ScreenIntPoint(30, 20), ScreenSize(0, 0), 0, 0));
-  manager->ReceiveInputEvent(secondFingerDown, nullptr);
+  manager->ReceiveInputEvent(secondFingerDown, nullptr, nullptr);
 
   // Release the fingers.
   MultiTouchInput fingersUp = secondFingerDown;
   fingersUp.mType = MultiTouchInput::MULTITOUCH_END;
-  manager->ReceiveInputEvent(fingersUp, nullptr);
+  manager->ReceiveInputEvent(fingersUp, nullptr, nullptr);
 
   // Allow any animations to run their course.
   child->AdvanceAnimationsUntilEnd(testStartTime);
   rootApzc->AdvanceAnimationsUntilEnd(testStartTime);
 
   // Make sure nothing is overscrolled.
   EXPECT_FALSE(child->IsOverscrolled());
   EXPECT_FALSE(rootApzc->IsOverscrolled());
--- a/gfx/thebes/gfxContext.cpp
+++ b/gfx/thebes/gfxContext.cpp
@@ -871,16 +871,27 @@ gfxContext::GetPattern()
   } else if (state.sourceSurface) {
     NS_ASSERTION(false, "Ugh, this isn't good.");
   } else {
     pat = new gfxPattern(ThebesRGBA(state.color));
   }
   return pat.forget();
 }
 
+void
+gfxContext::SetFontSmoothingBackgroundColor(const Color& aColor)
+{
+  CurrentState().fontSmoothingBackgroundColor = aColor;
+}
+
+Color
+gfxContext::GetFontSmoothingBackgroundColor()
+{
+  return CurrentState().fontSmoothingBackgroundColor;
+}
 
 // masking
 void
 gfxContext::Mask(SourceSurface* aSurface, const Matrix& aTransform)
 {
   Matrix old = mTransform;
   Matrix mat = aTransform * mTransform;
 
--- a/gfx/thebes/gfxContext.h
+++ b/gfx/thebes/gfxContext.h
@@ -342,16 +342,23 @@ public:
     void SetSource(gfxASurface *surface, const gfxPoint& offset = gfxPoint(0.0, 0.0));
 
     /**
      * Uses a pattern for drawing.
      */
     void SetPattern(gfxPattern *pattern);
 
     /**
+     * Set the color that text drawn on top of transparent pixels should be
+     * anti-aliased into.
+     */
+    void SetFontSmoothingBackgroundColor(const mozilla::gfx::Color& aColor);
+    mozilla::gfx::Color GetFontSmoothingBackgroundColor();
+
+    /**
      * Get the source pattern (solid color, normal pattern, surface, etc)
      */
     already_AddRefed<gfxPattern> GetPattern();
 
     /**
      ** Painting
      **/
     /**
@@ -640,16 +647,17 @@ private:
     bool clipWasReset;
     mozilla::gfx::FillRule fillRule;
     StrokeOptions strokeOptions;
     mozilla::RefPtr<DrawTarget> drawTarget;
     mozilla::RefPtr<DrawTarget> parentTarget;
     mozilla::gfx::AntialiasMode aaMode;
     bool patternTransformChanged;
     Matrix patternTransform;
+    Color fontSmoothingBackgroundColor;
     // This is used solely for using minimal intermediate surface size.
     mozilla::gfx::Point deviceOffset;
   };
 
   // This ensures mPath contains a valid path (in user space!)
   void EnsurePath();
   // This ensures mPathBuilder contains a valid PathBuilder (in user space!)
   void EnsurePathBuilder();
--- a/gfx/thebes/gfxDWriteFonts.cpp
+++ b/gfx/thebes/gfxDWriteFonts.cpp
@@ -596,17 +596,17 @@ gfxDWriteFont::GetGlyphWidth(gfxContext 
     }
 
     width = NS_lround(MeasureGlyphWidth(aGID) * 65536.0);
     mGlyphWidths->Put(aGID, width);
     return width;
 }
 
 TemporaryRef<GlyphRenderingOptions>
-gfxDWriteFont::GetGlyphRenderingOptions()
+gfxDWriteFont::GetGlyphRenderingOptions(const TextRunDrawParams* aRunParams)
 {
   if (UsingClearType()) {
     return Factory::CreateDWriteGlyphRenderingOptions(
       gfxWindowsPlatform::GetPlatform()->GetRenderingParams(GetForceGDIClassic() ?
         gfxWindowsPlatform::TEXT_RENDERING_GDI_CLASSIC : gfxWindowsPlatform::TEXT_RENDERING_NORMAL));
   } else {
     return Factory::CreateDWriteGlyphRenderingOptions(gfxWindowsPlatform::GetPlatform()->
       GetRenderingParams(gfxWindowsPlatform::TEXT_RENDERING_NO_CLEARTYPE));
--- a/gfx/thebes/gfxDWriteFonts.h
+++ b/gfx/thebes/gfxDWriteFonts.h
@@ -51,17 +51,18 @@ public:
                                gfxContext *aContextForTightBoundingBox,
                                Spacing *aSpacing,
                                uint16_t aOrientation);
 
     virtual bool ProvidesGlyphWidths() const;
 
     virtual int32_t GetGlyphWidth(gfxContext *aCtx, uint16_t aGID);
 
-    virtual mozilla::TemporaryRef<mozilla::gfx::GlyphRenderingOptions> GetGlyphRenderingOptions();
+    virtual mozilla::TemporaryRef<mozilla::gfx::GlyphRenderingOptions>
+        GetGlyphRenderingOptions(const TextRunDrawParams* aRunParams = nullptr) MOZ_OVERRIDE;
 
     virtual void AddSizeOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf,
                                         FontCacheSizes* aSizes) const;
     virtual void AddSizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf,
                                         FontCacheSizes* aSizes) const;
 
     virtual FontType GetType() const { return FONT_TYPE_DWRITE; }
 
--- a/gfx/thebes/gfxFT2Fonts.cpp
+++ b/gfx/thebes/gfxFT2Fonts.cpp
@@ -271,17 +271,17 @@ gfxFT2Font::AddSizeOfIncludingThis(mozil
                                    FontCacheSizes* aSizes) const
 {
     aSizes->mFontInstances += aMallocSizeOf(this);
     AddSizeOfExcludingThis(aMallocSizeOf, aSizes);
 }
 
 #ifdef USE_SKIA
 mozilla::TemporaryRef<mozilla::gfx::GlyphRenderingOptions>
-gfxFT2Font::GetGlyphRenderingOptions()
+gfxFT2Font::GetGlyphRenderingOptions(const TextRunDrawParams* aRunParams)
 {
   mozilla::gfx::FontHinting hinting;
 
   if (gfxPlatform::GetPlatform()->FontHintingEnabled()) {
     hinting = mozilla::gfx::FontHinting::NORMAL;
   } else {
     hinting = mozilla::gfx::FontHinting::NONE;
   }
--- a/gfx/thebes/gfxFT2Fonts.h
+++ b/gfx/thebes/gfxFT2Fonts.h
@@ -63,17 +63,18 @@ public: // new functions
     }
 
     virtual void AddSizeOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf,
                                         FontCacheSizes* aSizes) const;
     virtual void AddSizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf,
                                         FontCacheSizes* aSizes) const;
 
 #ifdef USE_SKIA
-    virtual mozilla::TemporaryRef<mozilla::gfx::GlyphRenderingOptions> GetGlyphRenderingOptions();
+    virtual mozilla::TemporaryRef<mozilla::gfx::GlyphRenderingOptions>
+        GetGlyphRenderingOptions(const TextRunDrawParams* aRunParams = nullptr) MOZ_OVERRIDE;
 #endif
 
 protected:
     virtual bool ShapeText(gfxContext      *aContext,
                            const char16_t *aText,
                            uint32_t         aOffset,
                            uint32_t         aLength,
                            int32_t          aScript,
--- a/gfx/thebes/gfxFont.cpp
+++ b/gfx/thebes/gfxFont.cpp
@@ -1944,17 +1944,17 @@ gfxFont::Draw(gfxTextRun *aTextRun, uint
 
     Matrix mat;
     Matrix oldMat = aRunParams.dt->GetTransform();
 
     // This is nullptr when we have inverse-transformed glyphs and we need
     // to transform the Brush inside flush.
     fontParams.passedInvMatrix = nullptr;
 
-    fontParams.renderingOptions = GetGlyphRenderingOptions();
+    fontParams.renderingOptions = GetGlyphRenderingOptions(&aRunParams);
     fontParams.drawOptions.mAntialiasMode = Get2DAAMode(mAntialiasOption);
 
     // The cairo DrawTarget backend uses the cairo_scaled_font directly
     // and so has the font skew matrix applied already.
     if (mScaledFont &&
         aRunParams.dt->GetBackendType() != BackendType::CAIRO) {
         cairo_matrix_t matrix;
         cairo_scaled_font_get_font_matrix(mScaledFont, &matrix);
--- a/gfx/thebes/gfxFont.h
+++ b/gfx/thebes/gfxFont.h
@@ -1408,17 +1408,18 @@ public:
     virtual uint32_t GetGlyph(uint32_t unicode, uint32_t variation_selector) {
         return 0;
     }
     // Return the horizontal advance of a glyph.
     gfxFloat GetGlyphHAdvance(gfxContext *aCtx, uint16_t aGID);
 
     // Return Azure GlyphRenderingOptions for drawing this font.
     virtual mozilla::TemporaryRef<mozilla::gfx::GlyphRenderingOptions>
-      GetGlyphRenderingOptions() { return nullptr; }
+      GetGlyphRenderingOptions(const TextRunDrawParams* aRunParams = nullptr)
+    { return nullptr; }
 
     gfxFloat SynthesizeSpaceWidth(uint32_t aCh);
 
     // Font metrics
     struct Metrics {
         gfxFloat xHeight;
         gfxFloat strikeoutSize;
         gfxFloat strikeoutOffset;
@@ -2053,16 +2054,17 @@ protected:
 // are dependent on the specific font, so they are set per GlyphRun.
 
 struct TextRunDrawParams {
     mozilla::RefPtr<mozilla::gfx::DrawTarget> dt;
     gfxContext              *context;
     gfxFont::Spacing        *spacing;
     gfxTextRunDrawCallbacks *callbacks;
     gfxTextContextPaint     *runContextPaint;
+    mozilla::gfx::Color      fontSmoothingBGColor;
     gfxFloat                 direction;
     double                   devPerApp;
     DrawMode                 drawMode;
     bool                     isVerticalRun;
     bool                     isRTL;
     bool                     paintSVGGlyphs;
 };
 
--- a/gfx/thebes/gfxMacFont.cpp
+++ b/gfx/thebes/gfxMacFont.cpp
@@ -414,16 +414,25 @@ gfxMacFont::GetScaledFont(DrawTarget *aT
     nativeFont.mType = NativeFontType::MAC_FONT_FACE;
     nativeFont.mFont = GetCGFontRef();
     mAzureScaledFont = mozilla::gfx::Factory::CreateScaledFontWithCairo(nativeFont, GetAdjustedSize(), mScaledFont);
   }
 
   return mAzureScaledFont;
 }
 
+TemporaryRef<mozilla::gfx::GlyphRenderingOptions>
+gfxMacFont::GetGlyphRenderingOptions(const TextRunDrawParams* aRunParams)
+{
+    if (aRunParams) {
+        return mozilla::gfx::Factory::CreateCGGlyphRenderingOptions(aRunParams->fontSmoothingBGColor);
+    }
+    return nullptr;
+}
+
 void
 gfxMacFont::AddSizeOfExcludingThis(MallocSizeOf aMallocSizeOf,
                                    FontCacheSizes* aSizes) const
 {
     gfxFont::AddSizeOfExcludingThis(aMallocSizeOf, aSizes);
     // mCGFont is shared with the font entry, so not counted here;
     // and we don't have APIs to measure the cairo mFontFace object
 }
--- a/gfx/thebes/gfxMacFont.h
+++ b/gfx/thebes/gfxMacFont.h
@@ -34,16 +34,19 @@ public:
     virtual RunMetrics Measure(gfxTextRun *aTextRun,
                                uint32_t aStart, uint32_t aEnd,
                                BoundingBoxType aBoundingBoxType,
                                gfxContext *aContextForTightBoundingBox,
                                Spacing *aSpacing, uint16_t aOrientation);
 
     virtual mozilla::TemporaryRef<mozilla::gfx::ScaledFont> GetScaledFont(mozilla::gfx::DrawTarget *aTarget);
 
+    virtual mozilla::TemporaryRef<mozilla::gfx::GlyphRenderingOptions>
+      GetGlyphRenderingOptions(const TextRunDrawParams* aRunParams = nullptr) MOZ_OVERRIDE;
+
     virtual void AddSizeOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf,
                                         FontCacheSizes* aSizes) const;
     virtual void AddSizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf,
                                         FontCacheSizes* aSizes) const;
 
     virtual FontType GetType() const { return FONT_TYPE_MAC; }
 
 protected:
--- a/gfx/thebes/gfxPangoFonts.cpp
+++ b/gfx/thebes/gfxPangoFonts.cpp
@@ -660,17 +660,18 @@ gfxDownloadedFcFontEntry::GetFontTable(u
 class gfxFcFont : public gfxFT2FontBase {
 public:
     virtual ~gfxFcFont();
     static already_AddRefed<gfxFcFont>
     GetOrMakeFont(FcPattern *aRequestedPattern, FcPattern *aFontPattern,
                   const gfxFontStyle *aFontStyle);
 
 #ifdef USE_SKIA
-    virtual mozilla::TemporaryRef<mozilla::gfx::GlyphRenderingOptions> GetGlyphRenderingOptions();
+    virtual mozilla::TemporaryRef<mozilla::gfx::GlyphRenderingOptions>
+        GetGlyphRenderingOptions(const TextRunDrawParams* aRunParams = nullptr) MOZ_OVERRIDE;
 #endif
 
     // return a cloned font resized and offset to simulate sub/superscript glyphs
     virtual already_AddRefed<gfxFont>
     GetSubSuperscriptFont(int32_t aAppUnitsPerDevPixel);
 
 protected:
     virtual already_AddRefed<gfxFont> MakeScaledFont(gfxFontStyle *aFontStyle,
@@ -2137,17 +2138,17 @@ ApplyGdkScreenFontOptions(FcPattern *aPa
 
     cairo_ft_font_options_substitute(options, aPattern);
 }
 
 #endif // MOZ_WIDGET_GTK2
 
 #ifdef USE_SKIA
 mozilla::TemporaryRef<mozilla::gfx::GlyphRenderingOptions>
-gfxFcFont::GetGlyphRenderingOptions()
+gfxFcFont::GetGlyphRenderingOptions(const TextRunDrawParams* aRunParams)
 {
   cairo_scaled_font_t *scaled_font = CairoScaledFont();
   cairo_font_options_t *options = cairo_font_options_create();
   cairo_scaled_font_get_font_options(scaled_font, options);
   cairo_hint_style_t hint_style = cairo_font_options_get_hint_style(options);     
   cairo_font_options_destroy(options);
 
   mozilla::gfx::FontHinting hinting;
--- a/gfx/thebes/gfxTextRun.cpp
+++ b/gfx/thebes/gfxTextRun.cpp
@@ -610,16 +610,17 @@ gfxTextRun::Draw(gfxContext *aContext, g
     params.isVerticalRun = IsVertical();
     params.isRTL = IsRightToLeft();
     params.direction = direction;
     params.drawMode = aDrawMode;
     params.callbacks = aCallbacks;
     params.runContextPaint = aContextPaint;
     params.paintSVGGlyphs = !aCallbacks || aCallbacks->mShouldPaintSVGGlyphs;
     params.dt = aContext->GetDrawTarget();
+    params.fontSmoothingBGColor = aContext->GetFontSmoothingBackgroundColor();
 
     // synthetic bolding draws glyphs twice ==> colors with opacity won't draw
     // correctly unless first drawn without alpha
     BufferAlphaColor syntheticBoldBuffer(aContext);
     gfxRGBA currentColor;
     bool needToRestore = false;
 
     if (aDrawMode == DrawMode::GLYPH_FILL &&
--- a/image/public/imgILoader.idl
+++ b/image/public/imgILoader.idl
@@ -13,25 +13,26 @@ interface nsIChannel;
 interface nsILoadGroup;
 interface nsIPrincipal;
 interface nsIStreamListener;
 interface nsIURI;
 
 interface nsISimpleEnumerator;
 
 #include "nsIRequest.idl" // for nsLoadFlags
+#include "nsIContentPolicy.idl" // for nsContentPolicyType
 
 /**
  * imgILoader interface
  *
  * @author Stuart Parmenter <pavlov@netscape.com>
  * @version 0.3
  * @see imagelib2
  */
-[scriptable, builtinclass, uuid(046d5fa6-ac59-489d-b51e-0ffe57e8df59)]
+[scriptable, builtinclass, uuid(42ef3b0a-cd82-454b-b4c4-f3470014a68b)]
 interface imgILoader : nsISupports
 {
   // Extra flags to pass to loadImage if you want a load to use CORS
   // validation.
   const unsigned long LOAD_CORS_ANONYMOUS = 1 << 16;
   const unsigned long LOAD_CORS_USE_CREDENTIALS = 1 << 17;
 
   /**
@@ -41,38 +42,43 @@ interface imgILoader : nsISupports
    * @param aReferrerURI the 'referring' URI
    * @param aLoadingPrincipal the principal of the loading document
    * @param aLoadGroup Loadgroup to put the image load into
    * @param aObserver the observer (may be null)
    * @param aCX some random data
    * @param aLoadFlags Load flags for the request
    * @param aCacheKey cache key to use for a load if the original
    *                  image came from a request that had post data
+   * @param aContentPolicyType [optional] the nsContentPolicyType to
+   *                           use for this load. Defaults to
+   *                           nsIContentPolicy::TYPE_IMAGE
 
 
    * libpr0n does NOT keep a strong ref to the observer; this prevents
    * reference cycles.  This means that callers of loadImage should
    * make sure to Cancel() the resulting request before the observer
    * goes away.
    */
   imgIRequest loadImageXPCOM(in nsIURI aURI,
                              in nsIURI aInitialDocumentURL,
                              in nsIURI aReferrerURI,
                              in nsIPrincipal aLoadingPrincipal,
                              in nsILoadGroup aLoadGroup,
                              in imgINotificationObserver aObserver,
                              in nsISupports aCX,
                              in nsLoadFlags aLoadFlags,
-                             in nsISupports cacheKey);
+                             in nsISupports cacheKey,
+                             [optional]
+                             in nsContentPolicyType aContentPolicyType);
 
   /**
    * Start the load and decode of an image.
    * @param aChannel the channel to load the image from.  This must
    *                 already be opened before ths method is called, and there
-   *                 must have been no OnDataAvailable calls for it yet.   
+   *                 must have been no OnDataAvailable calls for it yet.
    * @param aObserver the observer (may be null)
    * @param cx some random data
    * @param aListener [out]
    *        A listener that you must send the channel's notifications and data to.
    *        Can be null, in which case imagelib has found a cached image and is
    *        not interested in the data. @aChannel will be canceled for you in
    *        this case.
    *
--- a/image/src/imgLoader.cpp
+++ b/image/src/imgLoader.cpp
@@ -630,16 +630,17 @@ static nsresult NewImageChannel(nsIChann
                                 // aLoadingPrincipal and false otherwise.
                                 bool *aForcePrincipalCheckForCacheEntry,
                                 nsIURI *aURI,
                                 nsIURI *aInitialDocumentURI,
                                 nsIURI *aReferringURI,
                                 nsILoadGroup *aLoadGroup,
                                 const nsCString& aAcceptHeader,
                                 nsLoadFlags aLoadFlags,
+                                nsContentPolic