Merge mozilla-central into holly
authorMike Conley <mconley@mozilla.com>
Fri, 06 Dec 2013 17:16:45 -0500
changeset 224576 ca5206f5c106c4e097d7ae62b2bda021255f181b
parent 224575 83d94bb9a5f58416ca3214d59efa8361bbb87700 (current diff)
parent 159126 9a92e42151dcf172808130cb3183970625dd18d6 (diff)
child 224577 85105bb6a540a7b935c30ab72d370f5a16abafcc
push id6
push userryanvm@gmail.com
push dateMon, 12 Jan 2015 22:04:06 +0000
treeherdermozilla-b2g37_v2_2@895c8fc7b734 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
milestone28.0a1
Merge mozilla-central into holly
CLOBBER
layout/style/nsCSSKeywordList.h
layout/style/nsCSSProps.cpp
modules/libpref/src/init/all.js
toolkit/components/telemetry/Histograms.json
widget/cocoa/nsNativeThemeCocoa.mm
--- a/CLOBBER
+++ b/CLOBBER
@@ -13,9 +13,9 @@
 #          |               |
 #          O <-- Clobber   O  <-- Clobber
 #
 # Note: The description below will be part of the error message shown to users.
 #
 # Modifying this file will now automatically clobber the buildbot machines \o/
 #
 
-Bug 585122 - New file added for xpcshell test not always being copied.
+Bug 947080 - bug 937317 required clobber on windows
--- a/b2g/chrome/content/shell.js
+++ b/b2g/chrome/content/shell.js
@@ -1341,17 +1341,17 @@ window.addEventListener('ContentStart', 
                                                               videoCount: 0};
         }
         commandHandler(requestURL, { type: aData,
                                      isAudio: props.get('isAudio'),
                                      isVideo: props.get('isVideo')});
         break;
       case 'content-shutdown':
         // iterate through all the existing active processes
-        Object.keys(gRecordingActiveProcesses[processId]).foreach(function(requestURL) {
+        Object.keys(gRecordingActiveProcesses[processId]).forEach(function(requestURL) {
           commandHandler(requestURL, { type: aData,
                                        isAudio: true,
                                        isVideo: true});
         });
         break;
     }
 
     // clean up process record if no page record in it.
--- a/b2g/config/gaia.json
+++ b/b2g/config/gaia.json
@@ -1,4 +1,4 @@
 {
-    "revision": "1ab9ef0530a59b513469e362eeb060d7b008e6d9", 
+    "revision": "2f52419acd2edacec369138b143454dd8afccebd", 
     "repo_path": "/integration/gaia-central"
 }
--- a/b2g/config/mozconfigs/common
+++ b/b2g/config/mozconfigs/common
@@ -1,7 +1,9 @@
 # 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/.
 
 # This file is included at the top of all b2g mozconfigs
 
 . "$topsrcdir/build/mozconfig.common"
+
+ac_add_options --disable-unified-compilation
--- a/browser/base/content/browser-gestureSupport.js
+++ b/browser/base/content/browser-gestureSupport.js
@@ -198,20 +198,19 @@ let gGestureSupport = {
       }
       isVerticalSwipe = true;
     } else if (aEvent.direction == aEvent.DIRECTION_DOWN) {
       if (content.pageYOffset < content.scrollMaxY) {
         return false;
       }
       isVerticalSwipe = true;
     }
-    if (isVerticalSwipe && !gHistorySwipeAnimation.active) {
-      // Unlike horizontal swipes (which can navigate history even when
-      // swipe animations are turned off) vertical swipes should not be tracked
-      // if animations (bounce effect) aren't enabled.
+    if (isVerticalSwipe) {
+      // Vertical overscroll has been temporarily disabled until bug 939480 is
+      // fixed.
       return false;
     }
 
     let canGoBack = gHistorySwipeAnimation.canGoBack();
     let canGoForward = gHistorySwipeAnimation.canGoForward();
     let isLTR = gHistorySwipeAnimation.isLTR;
 
     if (canGoBack) {
--- a/build/automationutils.py
+++ b/build/automationutils.py
@@ -62,17 +62,18 @@ DEBUGGER_INFO = {
 
   "cgdb": {
     "interactive": True,
     "args": "-q --args"
   },
 
   "lldb": {
     "interactive": True,
-    "args": "--"
+    "args": "--",
+    "requiresEscapedArgs": True
   },
 
   # valgrind doesn't explain much about leaks unless you set the
   # '--leak-check=full' flag.
   "valgrind": {
     "interactive": False,
     "args": "--leak-check=full"
   }
@@ -206,17 +207,18 @@ def getDebuggerInfo(directory, debugger,
     def getDebuggerInfo(type, default):
       if debuggerName in DEBUGGER_INFO and type in DEBUGGER_INFO[debuggerName]:
         return DEBUGGER_INFO[debuggerName][type]
       return default
 
     debuggerInfo = {
       "path": debuggerPath,
       "interactive" : getDebuggerInfo("interactive", False),
-      "args": getDebuggerInfo("args", "").split()
+      "args": getDebuggerInfo("args", "").split(),
+      "requiresEscapedArgs": getDebuggerInfo("requiresEscapedArgs", False)
     }
 
     if debuggerArgs:
       debuggerInfo["args"] = debuggerArgs.split()
     if debuggerInteractive:
       debuggerInfo["interactive"] = debuggerInteractive
 
   return debuggerInfo
--- a/config/baseconfig.mk
+++ b/config/baseconfig.mk
@@ -5,20 +5,27 @@ sdkdir = $(libdir)/$(MOZ_APP_NAME)-devel
 DIST = $(DEPTH)/dist
 
 # We do magic with OBJ_SUFFIX in config.mk, the following ensures we don't
 # manually use it before config.mk inclusion
 _OBJ_SUFFIX := $(OBJ_SUFFIX)
 OBJ_SUFFIX = $(error config/config.mk needs to be included before using OBJ_SUFFIX)
 
 ifeq ($(HOST_OS_ARCH),WINNT)
-# We only support building with pymake or a specially built gnu make.
+# We only support building with pymake or a non-msys gnu make version
+# strictly above 4.0.
 ifndef .PYMAKE
-ifeq (,$(filter mozmake%,$(notdir $(MAKE))))
-$(error Only building with pymake or mozmake is supported.)
+ifeq (a,$(firstword a$(subst /, ,$(abspath .))))
+$(error MSYS make is not supported)
+endif
+# 4.0- happens to be greater than 4.0, lower than the mozmake version,
+# and lower than 4.0.1 or 4.1, whatever next version of gnu make will
+# be released.
+ifneq (4.0-,$(firstword $(sort 4.0- $(MAKE_VERSION))))
+$(error Make version too old. Only versions strictly greater than 4.0 are supported.)
 endif
 endif
 ifeq (a,$(firstword a$(subst /, ,$(srcdir))))
 $(error MSYS-style srcdir are not supported for Windows builds.)
 endif
 endif # WINNT
 
 ifdef .PYMAKE
--- a/config/config.mk
+++ b/config/config.mk
@@ -40,19 +40,16 @@ endif
   CMMSRCS \
   CPP_UNIT_TESTS \
   DIRS \
   EXTRA_PP_COMPONENTS \
   EXTRA_PP_JS_MODULES \
   FORCE_SHARED_LIB \
   FORCE_STATIC_LIB \
   FINAL_LIBRARY \
-  GTEST_CMMSRCS \
-  GTEST_CPPSRCS \
-  GTEST_CSRCS \
   HOST_CSRCS \
   HOST_CMMSRCS \
   HOST_LIBRARY_NAME \
   HOST_PROGRAM \
   HOST_SIMPLE_PROGRAMS \
   IS_COMPONENT \
   JAVA_JAR_TARGETS \
   JS_MODULES_PATH \
--- a/content/base/public/nsIMessageManager.idl
+++ b/content/base/public/nsIMessageManager.idl
@@ -378,22 +378,22 @@ interface nsIFrameScriptLoader : nsISupp
 
   /**
    * Returns a list of all delayed scripts that will be loaded once
    * a (remote) frame becomes available.
    */
   nsIDOMDOMStringList getDelayedFrameScripts();
 };
 
-[scriptable, builtinclass, uuid(b37821ff-df79-44d4-821c-6d6ec4dfe1e9)]
+[scriptable, builtinclass, uuid(ad57800b-ff21-4e2f-91d3-e68615ae8afe)]
 interface nsIProcessChecker : nsISupports
 {
 
   /**
-   * Return true iff the "remote" process has |aPermission|.  This is
+   * Return true if the "remote" process has |aPermission|.  This is
    * intended to be used by JS implementations of cross-process DOM
    * APIs, like so
    *
    *   recvFooRequest: function(message) {
    *     if (!message.target.assertPermission("foo")) {
    *       return false;
    *     }
    *     // service foo request
@@ -404,17 +404,17 @@ interface nsIProcessChecker : nsISupport
    * though it doesn't hurt anything either.
    *
    * Note: If the remote content process does *not* have |aPermission|,
    * it will be killed as a precaution.
    */
   boolean assertPermission(in DOMString aPermission);
 
   /**
-   * Return true iff the "remote" process has |aManifestURL|.  This is
+   * Return true if the "remote" process has |aManifestURL|.  This is
    * intended to be used by JS implementations of cross-process DOM
    * APIs, like so
    *
    *   recvFooRequest: function(message) {
    *     if (!message.target.assertContainApp("foo")) {
    *       return false;
    *     }
    *     // service foo request
@@ -427,19 +427,22 @@ interface nsIProcessChecker : nsISupport
    * Note: If the remote content process does *not* contain |aManifestURL|,
    * it will be killed as a precaution.
    */
   boolean assertContainApp(in DOMString aManifestURL);
 
   boolean assertAppHasPermission(in DOMString aPermission);
 
   /**
-   * Return true iff the "remote" process' principal has an appStatus equal to
+   * Return true if the "remote" process' principal has an appStatus equal to
    * |aStatus|.
    *
    * This interface only returns meaningful data when our content is
    * in a separate process.  If it shares the same OS process as us,
    * then applying this permission check doesn't add any security,
    * though it doesn't hurt anything either.
+   *
+   * Note: If the remote content process does *not* has the |aStatus|,
+   * it will be killed as a precaution.
    */
-  boolean checkAppHasStatus(in unsigned short aStatus);
+  boolean assertAppHasStatus(in unsigned short aStatus);
 
 };
--- a/content/base/src/Link.cpp
+++ b/content/base/src/Link.cpp
@@ -233,19 +233,24 @@ Link::SetPort(const nsAString &aPort)
   nsCOMPtr<nsIURI> uri(GetURIToMutate());
   if (!uri) {
     // Ignore failures to be compatible with NS4.
     return;
   }
 
   nsresult rv;
   nsAutoString portStr(aPort);
-  int32_t port = portStr.ToInteger(&rv);
-  if (NS_FAILED(rv)) {
-    return;
+
+  // nsIURI uses -1 as default value.
+  int32_t port = -1;
+  if (!aPort.IsEmpty()) {
+    port = portStr.ToInteger(&rv);
+    if (NS_FAILED(rv)) {
+      return;
+    }
   }
 
   (void)uri->SetPort(port);
   SetHrefAttribute(uri);
 }
 
 void
 Link::SetHash(const nsAString &aHash)
--- a/content/base/src/nsDocument.cpp
+++ b/content/base/src/nsDocument.cpp
@@ -4648,16 +4648,25 @@ nsDocument::DispatchContentLoadedEvents(
 
   // Unpin references to preloaded images
   mPreloadingImages.Clear();
 
   if (mTiming) {
     mTiming->NotifyDOMContentLoadedStart(nsIDocument::GetDocumentURI());
   }
 
+  // Dispatch observer notification to notify observers document is interactive.
+  nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService();
+  nsIPrincipal *principal = GetPrincipal();
+  os->NotifyObservers(static_cast<nsIDocument*>(this),
+                      nsContentUtils::IsSystemPrincipal(principal) ?
+                        "chrome-document-interactive" :
+                        "content-document-interactive",
+                      nullptr);
+
   // Fire a DOM event notifying listeners that this document has been
   // loaded (excluding images and other loads initiated by this
   // document).
   nsContentUtils::DispatchTrustedEvent(this, static_cast<nsIDocument*>(this),
                                        NS_LITERAL_STRING("DOMContentLoaded"),
                                        true, true);
 
   if (mTiming) {
@@ -8122,16 +8131,27 @@ nsDocument::OnPageShow(bool aPersisted,
   }
 
   UpdateVisibilityState();
 
   nsCOMPtr<EventTarget> target = aDispatchStartTarget;
   if (!target) {
     target = do_QueryInterface(GetWindow());
   }
+
+  // Dispatch observer notification to notify observers page is shown.
+  nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService();
+  nsIPrincipal *principal = GetPrincipal();
+  os->NotifyObservers(static_cast<nsIDocument*>(this),
+                      nsContentUtils::IsSystemPrincipal(principal) ?
+                        "chrome-page-shown" :
+                        "content-page-shown",
+                      nullptr);
+
+
   DispatchPageTransition(target, NS_LITERAL_STRING("pageshow"), aPersisted);
 }
 
 static bool
 NotifyPageHide(nsIDocument* aDocument, void* aData)
 {
   const bool* aPersistedPtr = static_cast<const bool*>(aData);
   aDocument->OnPageHide(*aPersistedPtr, nullptr);
@@ -8184,16 +8204,26 @@ nsDocument::OnPageHide(bool aPersisted,
 
   MozExitPointerLock();
 
   // Now send out a PageHide event.
   nsCOMPtr<EventTarget> target = aDispatchStartTarget;
   if (!target) {
     target = do_QueryInterface(GetWindow());
   }
+
+  // Dispatch observer notification to notify observers page is hidden.
+  nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService();
+  nsIPrincipal *principal = GetPrincipal();
+  os->NotifyObservers(static_cast<nsIDocument*>(this),
+                      nsContentUtils::IsSystemPrincipal(principal) ?
+                        "chrome-page-hidden" :
+                        "content-page-hidden",
+                      nullptr);
+
   DispatchPageTransition(target, NS_LITERAL_STRING("pagehide"), aPersisted);
 
   mVisible = false;
 
   UpdateVisibilityState();
 
   EnumerateExternalResources(NotifyPageHide, &aPersisted);
   EnumerateFreezableElements(NotifyActivityChanged, nullptr);
--- a/content/base/src/nsFrameMessageManager.cpp
+++ b/content/base/src/nsFrameMessageManager.cpp
@@ -800,18 +800,18 @@ nsFrameMessageManager::AssertAppHasPermi
                                               bool* aHasPermission)
 {
   return AssertProcessInternal(ASSERT_APP_HAS_PERMISSION,
                                aPermission,
                                aHasPermission);
 }
 
 NS_IMETHODIMP
-nsFrameMessageManager::CheckAppHasStatus(unsigned short aStatus,
-                                         bool* aHasStatus)
+nsFrameMessageManager::AssertAppHasStatus(unsigned short aStatus,
+                                          bool* aHasStatus)
 {
   *aHasStatus = false;
 
   // This API is only supported for message senders in the chrome process.
   if (!mChrome || mIsBroadcaster) {
     return NS_ERROR_NOT_IMPLEMENTED;
   }
   if (!mCallback) {
@@ -1587,16 +1587,22 @@ public:
     return true;
   }
 
   bool CheckAppHasPermission(const nsAString& aPermission)
   {
     // In a single-process scenario, the child always has all capabilities.
     return true;
   }
+
+  virtual bool CheckAppHasStatus(unsigned short aStatus)
+  {
+    // In a single-process scenario, the child always has all capabilities.
+    return true;
+  }
 };
 
 
 /**
  * Send messages to the parent process.
  */
 class ChildProcessMessageManagerCallback : public MessageManagerCallback
 {
--- a/content/base/src/nsGkAtomList.h
+++ b/content/base/src/nsGkAtomList.h
@@ -779,17 +779,16 @@ GK_ATOM(onreadystatechange, "onreadystat
 GK_ATOM(onreceived, "onreceived")
 GK_ATOM(onremoteheld, "onremoteheld")
 GK_ATOM(onremoteresumed, "onremoteresumed")
 GK_ATOM(onretrieving, "onretrieving")
 GK_ATOM(onRequest, "onRequest")
 GK_ATOM(onrequestmediaplaystatus, "onrequestmediaplaystatus")
 GK_ATOM(onreset, "onreset")
 GK_ATOM(onresuming, "onresuming")
-GK_ATOM(onMozBeforeResize, "onMozBeforeResize")
 GK_ATOM(onresize, "onresize")
 GK_ATOM(onscostatuschanged, "onscostatuschanged")
 GK_ATOM(onscroll, "onscroll")
 GK_ATOM(onselect, "onselect")
 GK_ATOM(onsending, "onsending")
 GK_ATOM(onsent, "onsent")
 GK_ATOM(onset, "onset")
 GK_ATOM(onshow, "onshow")
--- a/content/base/src/nsScriptLoader.cpp
+++ b/content/base/src/nsScriptLoader.cpp
@@ -712,111 +712,111 @@ nsScriptLoader::ProcessScriptElement(nsI
       "Not safe to run a parser-inserted script?");
   return ProcessRequest(request) == NS_ERROR_HTMLPARSER_BLOCK;
 }
 
 namespace {
 
 class NotifyOffThreadScriptLoadCompletedRunnable : public nsRunnable
 {
-    nsRefPtr<nsScriptLoader> mLoader;
-    void *mToken;
+  nsRefPtr<nsScriptLoadRequest> mRequest;
+  nsRefPtr<nsScriptLoader> mLoader;
+  void *mToken;
 
 public:
-    NotifyOffThreadScriptLoadCompletedRunnable(already_AddRefed<nsScriptLoader> aLoader,
-                                               void *aToken)
-      : mLoader(aLoader), mToken(aToken)
-    {}
+  NotifyOffThreadScriptLoadCompletedRunnable(nsScriptLoadRequest* aRequest,
+                                             nsScriptLoader* aLoader)
+    : mRequest(aRequest), mLoader(aLoader), mToken(NULL)
+  {}
 
-    NS_DECL_NSIRUNNABLE
+  void SetToken(void* aToken) {
+    MOZ_ASSERT(aToken && !mToken);
+    mToken = aToken;
+  }
+
+  NS_DECL_NSIRUNNABLE
 };
 
 } /* anonymous namespace */
 
 nsresult
-nsScriptLoader::ProcessOffThreadRequest(void **aOffThreadToken)
+nsScriptLoader::ProcessOffThreadRequest(nsScriptLoadRequest* aRequest, void **aOffThreadToken)
 {
-    nsCOMPtr<nsScriptLoadRequest> request = mOffThreadScriptRequest;
-    mOffThreadScriptRequest = nullptr;
-    nsresult rv = ProcessRequest(request, aOffThreadToken);
-    mDocument->UnblockOnload(false);
-    return rv;
+  nsresult rv = ProcessRequest(aRequest, aOffThreadToken);
+  mDocument->UnblockOnload(false);
+  return rv;
 }
 
 NS_IMETHODIMP
 NotifyOffThreadScriptLoadCompletedRunnable::Run()
 {
-    MOZ_ASSERT(NS_IsMainThread());
+  MOZ_ASSERT(NS_IsMainThread());
 
-    nsresult rv = mLoader->ProcessOffThreadRequest(&mToken);
+  nsresult rv = mLoader->ProcessOffThreadRequest(mRequest, &mToken);
 
-    if (mToken) {
-      // The result of the off thread parse was not actually needed to process
-      // the request (disappearing window, some other error, ...). Finish the
-      // request to avoid leaks in the JS engine.
-      nsCOMPtr<nsIJSRuntimeService> svc = do_GetService("@mozilla.org/js/xpc/RuntimeService;1");
-      NS_ENSURE_TRUE(svc, NS_ERROR_FAILURE);
-      JSRuntime *rt;
-      svc->GetRuntime(&rt);
-      NS_ENSURE_TRUE(rt, NS_ERROR_FAILURE);
-      JS::FinishOffThreadScript(nullptr, rt, mToken);
-    }
+  if (mToken) {
+    // The result of the off thread parse was not actually needed to process
+    // the request (disappearing window, some other error, ...). Finish the
+    // request to avoid leaks in the JS engine.
+    nsCOMPtr<nsIJSRuntimeService> svc = do_GetService("@mozilla.org/js/xpc/RuntimeService;1");
+    NS_ENSURE_TRUE(svc, NS_ERROR_FAILURE);
+    JSRuntime *rt;
+    svc->GetRuntime(&rt);
+    NS_ENSURE_TRUE(rt, NS_ERROR_FAILURE);
+    JS::FinishOffThreadScript(nullptr, rt, mToken);
+  }
 
-    return rv;
+  return rv;
 }
 
 static void
 OffThreadScriptLoaderCallback(void *aToken, void *aCallbackData)
 {
-    // Be careful not to adjust the refcount on the loader, as this callback
-    // may be invoked off the main thread.
-    nsScriptLoader* aLoader = static_cast<nsScriptLoader*>(aCallbackData);
-    nsRefPtr<NotifyOffThreadScriptLoadCompletedRunnable> notify =
-        new NotifyOffThreadScriptLoadCompletedRunnable(
-            already_AddRefed<nsScriptLoader>(aLoader), aToken);
-    NS_DispatchToMainThread(notify);
+  NotifyOffThreadScriptLoadCompletedRunnable* aRunnable =
+    static_cast<NotifyOffThreadScriptLoadCompletedRunnable*>(aCallbackData);
+  aRunnable->SetToken(aToken);
+  NS_DispatchToMainThread(aRunnable);
+  NS_RELEASE(aRunnable);
 }
 
 nsresult
 nsScriptLoader::AttemptAsyncScriptParse(nsScriptLoadRequest* aRequest)
 {
   if (!aRequest->mElement->GetScriptAsync() || aRequest->mIsInline) {
     return NS_ERROR_FAILURE;
   }
 
-  if (mOffThreadScriptRequest) {
-    return NS_ERROR_FAILURE;
-  }
-
   JSObject *unrootedGlobal;
   nsCOMPtr<nsIScriptContext> context = GetScriptContext(&unrootedGlobal);
   if (!context) {
     return NS_ERROR_FAILURE;
   }
   AutoPushJSContext cx(context->GetNativeContext());
   JS::Rooted<JSObject*> global(cx, unrootedGlobal);
 
   JS::CompileOptions options(cx);
   FillCompileOptionsForRequest(aRequest, global, &options);
 
   if (!JS::CanCompileOffThread(cx, options)) {
     return NS_ERROR_FAILURE;
   }
 
-  mOffThreadScriptRequest = aRequest;
+  NotifyOffThreadScriptLoadCompletedRunnable* runnable =
+    new NotifyOffThreadScriptLoadCompletedRunnable(aRequest, this);
+
+  // This reference will be consumed by OffThreadScriptLoaderCallback.
+  NS_ADDREF(runnable);
+
   if (!JS::CompileOffThread(cx, global, options,
                             aRequest->mScriptText.get(), aRequest->mScriptText.Length(),
                             OffThreadScriptLoaderCallback,
-                            static_cast<void*>(this))) {
+                            static_cast<void*>(runnable))) {
     return NS_ERROR_OUT_OF_MEMORY;
   }
 
-  // This reference will be consumed by the NotifyOffThreadScriptLoadCompletedRunnable.
-  NS_ADDREF(this);
-
   mDocument->BlockOnload();
 
   return NS_OK;
 }
 
 nsresult
 nsScriptLoader::ProcessRequest(nsScriptLoadRequest* aRequest, void **aOffThreadToken)
 {
--- a/content/base/src/nsScriptLoader.h
+++ b/content/base/src/nsScriptLoader.h
@@ -206,17 +206,18 @@ public:
                           const nsAString &aType,
                           const nsAString &aCrossOrigin,
                           bool aScriptFromHead);
 
   /**
    * Process a request that was deferred so that the script could be compiled
    * off thread.
    */
-  nsresult ProcessOffThreadRequest(void **aOffThreadToken);
+  nsresult ProcessOffThreadRequest(nsScriptLoadRequest *aRequest,
+                                   void **aOffThreadToken);
 
 private:
   /**
    * Unblocks the creator parser of the parser-blocking scripts.
    */
   void UnblockParser(nsScriptLoadRequest* aParserBlockingRequest);
 
   /**
@@ -311,17 +312,16 @@ private:
       return aRequest == aPi.mRequest;
     }
   };
   struct PreloadURIComparator {
     bool Equals(const PreloadInfo &aPi, nsIURI * const &aURI) const;
   };
   nsTArray<PreloadInfo> mPreloads;
 
-  nsCOMPtr<nsScriptLoadRequest> mOffThreadScriptRequest;
   nsCOMPtr<nsIScriptElement> mCurrentScript;
   nsCOMPtr<nsIScriptElement> mCurrentParserInsertedScript;
   // XXXbz do we want to cycle-collect these or something?  Not sure.
   nsTArray< nsRefPtr<nsScriptLoader> > mPendingChildLoaders;
   uint32_t mBlockerCount;
   bool mEnabled;
   bool mDeferEnabled;
   bool mDocumentParsingDone;
--- a/content/base/src/nsXMLHttpRequest.cpp
+++ b/content/base/src/nsXMLHttpRequest.cpp
@@ -378,17 +378,17 @@ nsXMLHttpRequest::InitParameters(bool aA
   // Check for permissions.
   nsCOMPtr<nsPIDOMWindow> window = do_QueryInterface(GetOwner());
   if (!window || !window->GetDocShell()) {
     return;
   }
 
   // Chrome is always allowed access, so do the permission check only
   // for non-chrome pages.
-  if (!IsSystemXHR()) {
+  if (!IsSystemXHR() && aSystem) {
     nsCOMPtr<nsIDocument> doc = window->GetExtantDoc();
     if (!doc) {
       return;
     }
 
     nsCOMPtr<nsIPrincipal> principal = doc->NodePrincipal();
     nsCOMPtr<nsIPermissionManager> permMgr =
       do_GetService(NS_PERMISSIONMANAGER_CONTRACTID);
--- a/content/base/src/nsXMLHttpRequest.h
+++ b/content/base/src/nsXMLHttpRequest.h
@@ -231,17 +231,17 @@ public:
     BindToOwner(aGlobalObject);
     mBaseURI = aBaseURI;
   }
 
   void InitParameters(bool aAnon, bool aSystem);
 
   void SetParameters(bool aAnon, bool aSystem)
   {
-    mIsAnon = aAnon;
+    mIsAnon = aAnon || aSystem;
     mIsSystem = aSystem;
   }
 
   NS_DECL_ISUPPORTS_INHERITED
 
   // nsIXMLHttpRequest
   NS_DECL_NSIXMLHTTPREQUEST
 
--- a/content/base/test/browser.ini
+++ b/content/base/test/browser.ini
@@ -1,4 +1,5 @@
 [DEFAULT]
 
 [browser_bug593387.js]
 [browser_bug902350.js]
+[browser_state_notifications.js]
new file mode 100644
--- /dev/null
+++ b/content/base/test/browser_state_notifications.js
@@ -0,0 +1,177 @@
+/* globals Components: true, Promise: true, gBrowser: true, Test: true,
+           info: true, is: true, window: true, waitForExplicitFinish: true,
+           finish: true, ok: true*/
+
+"use strict";
+
+const { interfaces: Ci, classes: Cc, utils: Cu } = Components;
+const { addObserver, removeObserver } = Cc["@mozilla.org/observer-service;1"].
+                                          getService(Ci.nsIObserverService);
+const { openWindow } = Cc["@mozilla.org/embedcomp/window-watcher;1"].
+                         getService(Ci.nsIWindowWatcher);
+
+const Test = routine => () => {
+  waitForExplicitFinish();
+  Task.spawn(routine)
+      .then(finish, error => {
+        ok(false, error);
+        finish();
+      });
+};
+
+// Returns promise for the observer notification subject for
+// the given topic. If `receive("foo")` is called `n` times
+// nth promise is resolved on an `nth` "foo" notification.
+const receive = (topic, p) => {
+  const { promise, resolve, reject } = Promise.defer();
+  const { queue } = receive;
+  const timeout = () => {
+    queue.splice(queue.indexOf(resolve) - 1, 2);
+    reject(new Error("Timeout"));
+  };
+
+  const observer = {
+    observe: subject => {
+      // Browser loads bunch of other documents that we don't care
+      // about so we let allow filtering notifications via `p` function.
+      if (p && !p(subject)) return;
+      // If observer is a first one with a given `topic`
+      // in a queue resolve promise and take it off the queue
+      // otherwise keep waiting.
+      const index = queue.indexOf(topic);
+      if (queue.indexOf(resolve) === index + 1) {
+        removeObserver(observer, topic);
+        clearTimeout(id, reject);
+        queue.splice(index, 2);
+        resolve(subject);
+      }
+    }
+  };
+  const id = setTimeout(timeout, 90000);
+  addObserver(observer, topic, false);
+  queue.push(topic, resolve);
+
+  return promise;
+};
+receive.queue = [];
+
+const openTab = uri => gBrowser.selectedTab = gBrowser.addTab(uri);
+
+const sleep = ms => {
+  const { promise, resolve } = Promise.defer();
+  setTimeout(resolve, ms);
+  return promise;
+};
+
+const isData = document => document.URL.startsWith("data:");
+
+const uri1 = "data:text/html;charset=utf-8,<h1>1</h1>";
+// For whatever reason going back on load event doesn't work so timeout it is :(
+const uri2 = "data:text/html;charset=utf-8,<h1>2</h1><script>setTimeout(back,100)</script>";
+const uri3 = "data:text/html;charset=utf-8,<h1>3</h1>";
+
+const uri4 = "chrome://browser/content/license.html";
+
+const test = Test(function*() {
+  let documentInteractive = receive("content-document-interactive", isData);
+  let documentLoaded = receive("content-document-loaded", isData);
+  let pageShown = receive("content-page-shown", isData);
+
+  info("open: uri#1");
+  const tab1 = openTab(uri1);
+  const browser1 = gBrowser.getBrowserForTab(tab1);
+
+  let interactiveDocument1 = yield documentInteractive;
+  is(interactiveDocument1.readyState, "interactive", "document is interactive");
+  is(interactiveDocument1.URL, uri1, "document.URL matches tab url");
+
+  let loadedDocument1 = yield documentLoaded;
+  is(loadedDocument1.readyState, "complete", "document is loaded");
+  is(interactiveDocument1, loadedDocument1, "interactive document is loaded");
+
+  let shownPage = yield pageShown;
+  is(interactiveDocument1, shownPage, "loaded document is shown");
+
+  // Wait until history entry is created before loading new uri.
+  yield receive("sessionstore-state-write-complete");
+
+  info("load uri#2");
+
+  documentInteractive = receive("content-document-interactive", isData);
+  documentLoaded = receive("content-document-loaded", isData);
+  pageShown = receive("content-page-shown", isData);
+  let pageHidden = receive("content-page-hidden", isData);
+
+  browser1.loadURI(uri2);
+
+  let hiddenPage = yield pageHidden;
+  is(interactiveDocument1, hiddenPage, "loaded document is hidden");
+
+  let interactiveDocument2 = yield documentInteractive;
+  is(interactiveDocument2.readyState, "interactive", "document is interactive");
+  is(interactiveDocument2.URL, uri2, "document.URL matches URL loaded");
+
+  let loadedDocument2 = yield documentLoaded;
+  is(loadedDocument2.readyState, "complete", "document is loaded");
+  is(interactiveDocument2, loadedDocument2, "interactive document is loaded");
+
+  shownPage = yield pageShown;
+  is(interactiveDocument2, shownPage, "loaded document is shown");
+
+  info("go back to uri#1");
+
+
+  documentInteractive = receive("content-document-interactive", isData);
+  documentLoaded = receive("content-document-loaded", isData);
+  pageShown = receive("content-page-shown", isData);
+  pageHidden = receive("content-page-hidden", isData);
+
+  hiddenPage = yield pageHidden;
+  is(interactiveDocument2, hiddenPage, "new document is hidden");
+
+  shownPage = yield pageShown;
+  is(interactiveDocument1, shownPage, "previous document is shown");
+
+  info("load uri#3");
+
+  browser1.loadURI(uri3);
+
+  pageShown = receive("content-page-shown", isData);
+
+  let interactiveDocument3 = yield documentInteractive;
+  is(interactiveDocument3.readyState, "interactive", "document is interactive");
+  is(interactiveDocument3.URL, uri3, "document.URL matches URL loaded");
+
+  let loadedDocument3 = yield documentLoaded;
+  is(loadedDocument3.readyState, "complete", "document is loaded");
+  is(interactiveDocument3, loadedDocument3, "interactive document is loaded");
+
+  shownPage = yield pageShown;
+  is(interactiveDocument3, shownPage, "previous document is shown");
+
+  gBrowser.removeTab(tab1);
+
+  info("load chrome uri");
+
+  const tab2 = openTab(uri4);
+  documentInteractive = receive("chrome-document-interactive");
+  documentLoaded = receive("chrome-document-loaded");
+  pageShown = receive("chrome-page-shown");
+
+  const interactiveDocument4 = yield documentInteractive;
+  is(interactiveDocument4.readyState, "interactive", "document is interactive");
+  is(interactiveDocument4.URL, uri4, "document.URL matches URL loaded");
+
+  let loadedDocument4 = yield documentLoaded;
+  is(loadedDocument4.readyState, "complete", "document is loaded");
+  is(interactiveDocument4, loadedDocument4, "interactive document is loaded");
+
+  shownPage = yield pageShown;
+  is(interactiveDocument4, shownPage, "loaded chrome document is shown");
+
+  pageHidden = receive("chrome-page-hidden");
+  gBrowser.removeTab(tab2);
+
+  hiddenPage = yield pageHidden;
+  is(interactiveDocument4, hiddenPage, "chrome document hidden");
+});
--- a/content/base/test/file_restrictedEventSource.sjs
+++ b/content/base/test/file_restrictedEventSource.sjs
@@ -1,18 +1,16 @@
 function handleRequest(request, response)
 {
   if ((request.queryString == "test=user1_xhr" &&
        request.hasHeader("Authorization") &&
        request.getHeader("Authorization") == "Basic dXNlciAxOnBhc3N3b3JkIDE=") ||
       (request.queryString == "test=user1_evtsrc" &&
        request.hasHeader("Authorization") &&
-       request.getHeader("Authorization") == "Basic dXNlciAxOnBhc3N3b3JkIDE=" &&
-       request.hasHeader("Cookie") &&
-       request.getHeader("Cookie") == "test=5c")) {
+       request.getHeader("Authorization") == "Basic dXNlciAxOnBhc3N3b3JkIDE=")) {
     response.setStatusLine(null, 200, "OK");
     response.setHeader("Content-Type", "text/event-stream", false);
     response.setHeader("Access-Control-Allow-Origin", "http://mochi.test:8888", false);
     response.setHeader("Access-Control-Allow-Credentials", "true", false);
     response.setHeader("Cache-Control", "no-cache, must-revalidate", false);
     if (request.queryString == "test=user1_xhr") {
       response.setHeader("Set-Cookie", "test=5c", false);
     }
--- a/content/base/test/mochitest.ini
+++ b/content/base/test/mochitest.ini
@@ -516,16 +516,17 @@ support-files =
 [test_bug869006.html]
 [test_bug876282.html]
 [test_bug890580.html]
 [test_bug894874.html]
 [test_bug895239.html]
 [test_bug895974.html]
 [test_bug902847.html]
 [test_bug907892.html]
+[test_bug927196.html]
 [test_caretPositionFromPoint.html]
 [test_classList.html]
 [test_copypaste.html]
 [test_copypaste.xhtml]
 [test_createHTMLDocument.html]
 [test_declare_stylesheet_obsolete.html]
 [test_domparser_null_char.html]
 [test_domparsing.html]
--- a/content/base/test/test_XHR_parameters.html
+++ b/content/base/test/test_XHR_parameters.html
@@ -48,22 +48,21 @@ function runTests() {
     try {
       xhr = new XMLHttpRequest(value);
     } catch (ex) {
       ok(false, "Got unexpected exception: " + ex);
       return;
     }
     ok(xhr instanceof XMLHttpRequest, "passed " + JSON.stringify(value));
 
-    // If the page doesnt have privileges to create a system or anon XHR,
-    // these flags will always be false no matter what is passed.
-    let expectedAnon = false;
+    // If the page doesnt have privileges to create a system XHR,
+    // this flag will always be false no matter what is passed.
+    let expectedAnon = Boolean(value && value.mozAnon);
     let expectedSystem = false;
     if (havePrivileges) {
-      expectedAnon = Boolean(value && value.mozAnon);
       expectedSystem = Boolean(value && value.mozSystem);
     }
     is(xhr.mozAnon, expectedAnon, "testing mozAnon");
     is(xhr.mozSystem, expectedSystem, "testing mozSystem");
   }
 
   function testInvalidParameter(value) {
     let expectedError;
@@ -79,21 +78,20 @@ function runTests() {
   }
 
   // Run the tests once without API privileges...
   validParameters.forEach(testValidParameter);
   invalidParameters.forEach(testInvalidParameter);
 
   // ...and once with privileges.
   havePrivileges = true;
-  SpecialPowers.addPermission("systemXHR", true, document);
+  SpecialPowers.pushPermissions([{'type': 'systemXHR', 'allow': true, 'context': document}], function() {
+    validParameters.forEach(testValidParameter);
+    invalidParameters.forEach(testInvalidParameter);
 
-  validParameters.forEach(testValidParameter);
-  invalidParameters.forEach(testInvalidParameter);
-  SpecialPowers.removePermission("systemXHR", document);
-
-  SimpleTest.finish();
+    SimpleTest.finish();
+  });
 }
 
 </script>
 </pre>
 </body>
 </html>
--- a/content/base/test/test_bug338583.html
+++ b/content/base/test/test_bug338583.html
@@ -459,18 +459,18 @@ https://bugzilla.mozilla.org/show_bug.cg
       ok(gEventSourceObj5_b.hits['fn_onmessage'] == 0, "Test 5.b failed");
       gEventSourceObj5_b.close();
       setTestHasFinished(test_id);
     }, parseInt(3000*stress_factor));
   }
 
   function doTest5_c(test_id)
   {
-    // credentials using the auth cache and cookies
-    var xhr = new XMLHttpRequest({mozAnon: false, mozSystem: true});
+    // credentials using the auth cache
+    var xhr = new XMLHttpRequest({mozAnon: true, mozSystem: true});
     xhr.withCredentials = true;
     // also, test mixed mode UI
     xhr.open("GET", "https://example.com/tests/content/base/test/file_restrictedEventSource.sjs?test=user1_xhr", true, "user 1", "password 1");
     xhr.send();
     xhr.onloadend = function() {
       ok(xhr.status == 200, "Failed to set credentials in test 5.c");
 
       gEventSourceObj5_c = new EventSource("https://example.com/tests/content/base/test/file_restrictedEventSource.sjs?test=user1_evtsrc",
@@ -489,17 +489,17 @@ https://bugzilla.mozilla.org/show_bug.cg
         gEventSourceObj5_c.close();
         doTest5_d(test_id);
       }, parseInt(3000*stress_factor));
     };
   }
 
   function doTest5_d(test_id)
   {
-    var xhr = new XMLHttpRequest({mozAnon: false, mozSystem: true});
+    var xhr = new XMLHttpRequest({mozAnon: true, mozSystem: true});
     xhr.withCredentials = true;
     xhr.open("GET", "https://example.com/tests/content/base/test/file_restrictedEventSource.sjs?test=user2_xhr", true, "user 2", "password 2");
     xhr.send();
     xhr.onloadend = function() {
       ok(xhr.status == 200, "Failed to set credentials in test 5.d");
   
       gEventSourceObj5_d = new EventSource("https://example.com/tests/content/base/test/file_restrictedEventSource.sjs?test=user2_evtsrc");
       ok(!gEventSourceObj5_d.withCredentials, "Wrong withCredentials in test 5.d");
@@ -516,18 +516,18 @@ https://bugzilla.mozilla.org/show_bug.cg
         gEventSourceObj5_d.close();
         setTestHasFinished(test_id);
       }, parseInt(3000*stress_factor));
     };
   }
 
   function doTest5_e(test_id)
   {
-    // credentials using the auth cache and cookies
-    var xhr = new XMLHttpRequest({mozAnon: false, mozSystem: true});
+    // credentials using the auth cache
+    var xhr = new XMLHttpRequest({mozAnon: true, mozSystem: true});
     xhr.withCredentials = true;
     xhr.open("GET", "http://example.org/tests/content/base/test/file_restrictedEventSource.sjs?test=user1_xhr", true, "user 1", "password 1");
     xhr.send();
     xhr.onloadend = function() {
       ok(xhr.status == 200, "Failed to set credentials in test 5.e");
 
       gEventSourceObj5_e = new EventSource("http://example.org/tests/content/base/test/file_restrictedEventSource.sjs?test=user1_evtsrc",
                                            { get withCredentials() { return true; } } );
@@ -545,17 +545,17 @@ https://bugzilla.mozilla.org/show_bug.cg
         gEventSourceObj5_e.close();
         doTest5_f(test_id);
       }, parseInt(5000*stress_factor));
     };
   }
 
   function doTest5_f(test_id)
   {
-    var xhr = new XMLHttpRequest({mozAnon: false, mozSystem: true});
+    var xhr = new XMLHttpRequest({mozAnon: true, mozSystem: true});
     xhr.withCredentials = true;
     xhr.open("GET", "http://example.org/tests/content/base/test/file_restrictedEventSource.sjs?test=user2_xhr", true, "user 2", "password 2");
     xhr.send();
     xhr.onloadend = function() {
       ok(xhr.status == 200, "Failed to set credentials in test 5.f");
 
       gEventSourceObj5_f = new EventSource("http://example.org/tests/content/base/test/file_restrictedEventSource.sjs?test=user2_evtsrc",
                                            { });
--- a/content/base/test/test_bug426308.html
+++ b/content/base/test/test_bug426308.html
@@ -17,17 +17,17 @@ https://bugzilla.mozilla.org/show_bug.cg
 <pre id="test">
 <script class="testbody" type="text/javascript">
 
 /** Test for Bug 426308 **/
 
 const SJS_URL = "http://example.org:80/tests/content/base/test/bug426308-redirect.sjs";
 
 function startTest() {
-  var req = new XMLHttpRequest({mozAnon: false, mozSystem: true});
+  var req = new XMLHttpRequest({mozAnon: true, mozSystem: true});
   req.open("GET", SJS_URL + "?" + window.location.href, false);
   req.send(null);
 
   is(req.status, 200, "Redirect did not happen");
 
   SimpleTest.finish();
 }
 
--- a/content/base/test/test_bug431701.html
+++ b/content/base/test/test_bug431701.html
@@ -46,17 +46,17 @@ function frameDoc(id) {
 
 function createDoc() {
   return document.implementation.createDocument('', 'html', null);
 }
 
 function xhrDoc(idx) {
   return function() {
     // Defy same-origin restrictions!
-    var xhr = new XMLHttpRequest({mozAnon: false, mozSystem: true});
+    var xhr = new XMLHttpRequest({mozAnon: true, mozSystem: true});
     xhr.open("GET", docSources[idx], false);
     xhr.send();
     return xhr.responseXML;
   };
 }
 
 // Each row has the document getter function, then the characterSet,
 // inputEncoding expected for that document.
--- a/content/base/test/test_bug804395.html
+++ b/content/base/test/test_bug804395.html
@@ -14,41 +14,41 @@ https://bugzilla.mozilla.org/show_bug.cg
 <p id="display"></p>
 <div id="content" style="display: none">
 
 </div>
 <pre id="test">
 <script type="application/javascript">
 
 function test200() {
-  var xhr = new XMLHttpRequest({mozAnon: false, mozSystem: true});
+  var xhr = new XMLHttpRequest({mozAnon: true, mozSystem: true});
   xhr.open('GET', 'jar:http://example.org/tests/content/base/test/file_bug804395.jar!/foo.bar', true);
   xhr.onreadystatechange = function() {
     if (xhr.readyState == 4) {
       ok(xhr.status == 200, "Existing file must have Status 200!");
       runTests();
     }
   }
   xhr.send(null);
 }
 
 function test404() {
-  var xhr = new XMLHttpRequest({mozAnon: false, mozSystem: true});
+  var xhr = new XMLHttpRequest({mozAnon: true, mozSystem: true});
   xhr.open('GET', 'jar:http://example.org/tests/content/base/test/file_bug804395.jar!/foo.do_not_exist', true);
   xhr.onreadystatechange = function() {
     if (xhr.readyState == 4) {
       ok(xhr.status == 404, "Non existing file must have Status 404!");
       runTests();
     }
   }
   xhr.send(null);
 }
 
 function test0() {
-  var xhr = new XMLHttpRequest({mozAnon: false, mozSystem: true});
+  var xhr = new XMLHttpRequest({mozAnon: true, mozSystem: true});
   xhr.open('GET', 'jar:http://example.org/tests/content/base/test/file_bug804395.jar!/foo.bar', true);
   ok(xhr.status == 0, "Not Sent request must have status 0");
   runTests();
 }
 
 var tests = [ test200, test404, test0 ];
 function runTests() {
   if (!tests.length) {
new file mode 100644
--- /dev/null
+++ b/content/base/test/test_bug927196.html
@@ -0,0 +1,56 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=426308
+-->
+<head>
+  <title>Test for Bug 426308</title>
+  <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=927196">Mozilla Bug 927196</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+  
+</div>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+/** Test for Bug 927196 **/
+
+function startTest() {
+  req = new XMLHttpRequest({mozSystem: true});
+  is(req.mozAnon, true, "XMLHttpRequest should be mozAnon");
+
+  req = new XMLHttpRequest({mozAnon: true});
+  is(req.mozAnon, true, "XMLHttpRequest should be mozAnon");
+  is(req.mozSystem, false, "XMLHttpRequest should not be mozSystem");
+
+  req = new XMLHttpRequest({mozAnon: true, mozSystem: true});
+  is(req.mozAnon, true, "XMLHttpRequest should be mozAnon");
+  is(req.mozSystem, true, "XMLHttpRequest should be mozSystem");
+
+  req = new XMLHttpRequest({mozAnon: false, mozSystem: true});
+  is(req.mozAnon, true, "XMLHttpRequest should be mozAnon");
+
+  SimpleTest.finish();
+}
+
+SimpleTest.waitForExplicitFinish();
+
+var req = new XMLHttpRequest({mozAnon: true});
+is(req.mozAnon, true, "XMLHttpRequest should be mozAnon");
+is(req.mozSystem, false, "XMLHttpRequest should not be mozSystem");
+
+req = new XMLHttpRequest({mozAnon: true, mozSystem: true});
+is(req.mozAnon, false, "XMLHttpRequest should be mozAnon");
+is(req.mozSystem, false, "XMLHttpRequest should not be mozSystem");
+
+addLoadEvent(function() {
+   SpecialPowers.pushPermissions([{'type': 'systemXHR', 'allow': true, 'context': document}], startTest);
+});
+</script>
+</pre>
+</body>
+</html>
--- a/content/base/test/test_xhr_forbidden_headers.html
+++ b/content/base/test/test_xhr_forbidden_headers.html
@@ -65,17 +65,17 @@ function  startTest() {
       value = channel.getRequestHeader(headers[i]);
     }
     catch(e) {}
 
     isnot(value, "test" + i, "Setting " + headers[i] + " header in unprivileged context");
   }
 
   // Try setting headers in privileged context
-  request = new XMLHttpRequest({mozAnon: false, mozSystem: true});
+  request = new XMLHttpRequest({mozAnon: true, mozSystem: true});
   request.open("GET", window.location.href);
   for (i = 0; i < headers.length; i++)
     request.setRequestHeader(headers[i], "test" + i);
 
   // Read out headers
   var channel = SpecialPowers.wrap(request).channel.QueryInterface(SpecialPowers.Ci.nsIHttpChannel);
   for (i = 0; i < headers.length; i++) {
     var value = channel.getRequestHeader(headers[i]);
--- a/content/events/public/nsEventNameList.h
+++ b/content/events/public/nsEventNameList.h
@@ -625,20 +625,16 @@ NON_IDL_EVENT(DOMMouseScroll,
               NS_MOUSE_SCROLL,
               EventNameType_HTMLXUL,
               NS_MOUSE_SCROLL_EVENT)
 NON_IDL_EVENT(MozMousePixelScroll,
               NS_MOUSE_PIXEL_SCROLL,
               EventNameType_HTMLXUL,
               NS_MOUSE_SCROLL_EVENT)
                                                 
-NON_IDL_EVENT(MozBeforeResize,
-              NS_BEFORERESIZE_EVENT,
-              EventNameType_None,
-              NS_EVENT)
 NON_IDL_EVENT(open,
               NS_OPEN,
               EventNameType_None,
               NS_EVENT)
 
 NON_IDL_EVENT(dataavailable,
               NS_MEDIARECORDER_DATAAVAILABLE,
               EventNameType_None,
--- a/content/events/test/test_bug602962.xul
+++ b/content/events/test/test_bug602962.xul
@@ -17,17 +17,16 @@ https://bugzilla.mozilla.org/show_bug.cg
 </div>
 </body>
 
 <script class="testbody" type="application/javascript;version=1.8"><![CDATA[
 /** Test for Bug 602962 **/
 var scrollbox, sbo, content;
 var scrollX = 0, scrollY = 0;
 
-var mozBeforeResizeHasFired = false;
 var oldWidth = 0, oldHeight = 0;
 var win = null;
 
 function openWindow() {
   win = window.open("chrome://mochitests/content/chrome/content/events/test/bug602962.xul", "_blank", "width=600,height=600");
 }
 
 function doTest() {
@@ -44,26 +43,19 @@ function doTest() {
   }, false);
 
   oldWidth = win.outerWidth;
   oldHeight = win.outerHeight;
   win.resizeTo(200, 400);
 }
 
 function resize() {
-  win.addEventListener("MozBeforeResize", function() {
-    win.removeEventListener("MozBeforeResize", arguments.callee, false);
-    mozBeforeResizeHasFired = true;
-
-    let x = {}, y = {};
-    sbo.getPosition(x, y);
-    scrollX = x.value, scrollY = y.value;
-    is(scrollX, 200, "Scroll X should not have changed yet");
-    is(scrollY, 0, "Scroll Y should not have changed yet");
-  }, false);
+  let x = {}, y = {};
+  sbo.getPosition(x, y);
+  scrollX = x.value, scrollY = y.value;
 
   win.addEventListener("resize", function() {
     content.style.width = (oldWidth + 400) + "px";
     win.removeEventListener("resize", arguments.callee, true);
     
     setTimeout(function() {
       finish();
     }, 0);
@@ -74,17 +66,16 @@ function resize() {
 
 function finish() {
   if (win.outerWidth != oldWidth ||
       win.outerHeight != oldHeight) {
     // We should eventually get back to the original size.
     setTimeout(finish, 0);
     return;
   }
-  is(mozBeforeResizeHasFired, true, "The MozBeforeResize event should already have fired");
   sbo.scrollBy(scrollX, scrollY);
 
   let x = {}, y = {};
   sbo.getPosition(x, y);
   is(x.value, 200, "Scroll X should have been restored to the value before the resize");
   is(y.value, 0, "Scroll Y should have been restored to the value before the resize");
 
   is(win.outerWidth, oldWidth, "Width should be resized");
--- a/content/html/content/src/HTMLInputElement.cpp
+++ b/content/html/content/src/HTMLInputElement.cpp
@@ -2046,17 +2046,17 @@ HTMLInputElement::GetStepBase() const
       ConvertStringToNumber(valueStr, stepBase)) {
     return stepBase;
   }
 
   return kDefaultStepBase;
 }
 
 nsresult
-HTMLInputElement::ApplyStep(int32_t aStep)
+HTMLInputElement::GetValueIfStepped(int32_t aStep, Decimal* aNextStep)
 {
   if (!DoStepDownStepUpApply()) {
     return NS_ERROR_DOM_INVALID_STATE_ERR;
   }
 
   Decimal step = GetStep();
   if (step == kStepAny) {
     return NS_ERROR_DOM_INVALID_STATE_ERR;
@@ -2126,21 +2126,35 @@ HTMLInputElement::ApplyStep(int32_t aSte
   // If we go down, we want to clamp on min.
   } else if (aStep < 0 && minimum == minimum) {
     value = std::max(value, minimum);
   // If we go up, we want to clamp on max.
   } else if (aStep > 0 && maximum == maximum) {
     value = std::min(value, maximum);
   }
 
-  SetValue(value);
+  *aNextStep = value;
 
   return NS_OK;
 }
 
+nsresult
+HTMLInputElement::ApplyStep(int32_t aStep)
+{
+  Decimal nextStep = Decimal::nan(); // unchanged if value will not change
+
+  nsresult rv = GetValueIfStepped(aStep, &nextStep);
+
+  if (NS_SUCCEEDED(rv) && nextStep.isFinite()) {
+    SetValue(nextStep);
+  }
+
+  return rv;
+}
+
 NS_IMETHODIMP
 HTMLInputElement::StepDown(int32_t n, uint8_t optional_argc)
 {
   return ApplyStep(optional_argc ? -n : -1);
 }
 
 NS_IMETHODIMP
 HTMLInputElement::StepUp(int32_t n, uint8_t optional_argc)
@@ -2598,17 +2612,17 @@ HTMLInputElement::HandleNumberControlSpi
   nsNumberControlFrame* numberControlFrame =
     do_QueryFrame(input->GetPrimaryFrame());
   if (input->mType != NS_FORM_INPUT_NUMBER || !numberControlFrame) {
     // Type has changed (and possibly our frame type hasn't been updated yet)
     // or else we've lost our frame. Either way, stop the timer and don't do
     // anything else.
     input->StopNumberControlSpinnerSpin();
   } else {
-    input->ApplyStep(input->mNumberControlSpinnerSpinsUp ? 1 : -1);
+    input->StepNumberControlForUserEvent(input->mNumberControlSpinnerSpinsUp ? 1 : -1);
   }
 }
 
 void
 HTMLInputElement::MaybeDispatchProgressEvent(bool aFinalProgress)
 {
   nsRefPtr<HTMLInputElement> kungFuDeathGrip;
 
@@ -3309,27 +3323,37 @@ HTMLInputElement::PreHandleEvent(nsEvent
       // PostHandleEvent) because we don't want to let content preventDefault()
       // the end of the spin.
       if (aVisitor.mEvent->message == NS_MOUSE_MOVE) {
         // Be aggressive about stopping the spin:
         bool stopSpin = true;
         nsNumberControlFrame* numberControlFrame =
           do_QueryFrame(GetPrimaryFrame());
         if (numberControlFrame) {
+          bool oldNumberControlSpinTimerSpinsUpValue =
+                 mNumberControlSpinnerSpinsUp;
           switch (numberControlFrame->GetSpinButtonForPointerEvent(
                     aVisitor.mEvent->AsMouseEvent())) {
           case nsNumberControlFrame::eSpinButtonUp:
             mNumberControlSpinnerSpinsUp = true;
             stopSpin = false;
             break;
           case nsNumberControlFrame::eSpinButtonDown:
             mNumberControlSpinnerSpinsUp = false;
             stopSpin = false;
             break;
           }
+          if (mNumberControlSpinnerSpinsUp !=
+                oldNumberControlSpinTimerSpinsUpValue) {
+            nsNumberControlFrame* numberControlFrame =
+              do_QueryFrame(GetPrimaryFrame());
+            if (numberControlFrame) {
+              numberControlFrame->SpinnerStateChanged();
+            }
+          }
         }
         if (stopSpin) {
           StopNumberControlSpinnerSpin();
         }
       } else if (aVisitor.mEvent->message == NS_MOUSE_BUTTON_UP) {
         StopNumberControlSpinnerSpin();
       }
     }
@@ -3341,16 +3365,37 @@ HTMLInputElement::PreHandleEvent(nsEvent
           // Tell our frame it's getting focus so that it can make sure focus
           // is moved to our anonymous text control.
           nsNumberControlFrame* numberControlFrame =
             do_QueryFrame(GetPrimaryFrame());
           if (numberControlFrame) {
             numberControlFrame->HandleFocusEvent(aVisitor.mEvent);
           }
         }
+        if (frame->IsThemed()) {
+          // Our frame's nested <input type=text> will be invalidated when it
+          // loses focus, but since we are also native themed we need to make
+          // sure that our entire area is repainted since any focus highlight
+          // from the theme should be removed from us (the repainting of the
+          // sub-area occupied by the anon text control is not enough to do
+          // that).
+          frame->InvalidateFrame();
+        }
+      }
+    } else if (aVisitor.mEvent->message == NS_KEY_UP) {
+      WidgetKeyboardEvent* keyEvent = aVisitor.mEvent->AsKeyboardEvent();
+      if ((keyEvent->keyCode == NS_VK_UP || keyEvent->keyCode == NS_VK_DOWN) &&
+          !(keyEvent->IsShift() || keyEvent->IsControl() ||
+            keyEvent->IsAlt() || keyEvent->IsMeta() ||
+            keyEvent->IsAltGraph() || keyEvent->IsFn() ||
+            keyEvent->IsOS())) {
+        // The up/down arrow key events fire 'change' events when released
+        // so that at the end of a series of up/down arrow key repeat events
+        // the value is considered to be "commited" by the user.
+        FireChangeEventIfNeeded();
       }
     }
   }
 
   nsresult rv = nsGenericHTMLFormElementWithState::PreHandleEvent(aVisitor);
 
   // We do this after calling the base class' PreHandleEvent so that
   // nsIContent::PreHandleEvent doesn't reset any change we make to mCanHandle.
@@ -3480,30 +3525,65 @@ HTMLInputElement::StartNumberControlSpin
 
   mNumberControlSpinnerIsSpinning = true;
 
   nsRepeatService::GetInstance()->Start(HandleNumberControlSpin, this);
 
   // Capture the mouse so that we can tell if the pointer moves from one
   // spin button to the other, or to some other element:
   nsIPresShell::SetCapturingContent(this, CAPTURE_IGNOREALLOWED);
+
+  nsNumberControlFrame* numberControlFrame =
+    do_QueryFrame(GetPrimaryFrame());
+  if (numberControlFrame) {
+    numberControlFrame->SpinnerStateChanged();
+  }
 }
 
 void
 HTMLInputElement::StopNumberControlSpinnerSpin()
 {
   if (mNumberControlSpinnerIsSpinning) {
     if (nsIPresShell::GetCapturingContent() == this) {
       nsIPresShell::SetCapturingContent(nullptr, 0); // cancel capture
     }
 
     nsRepeatService::GetInstance()->Stop(HandleNumberControlSpin, this);
 
     mNumberControlSpinnerIsSpinning = false;
-  }
+
+    FireChangeEventIfNeeded();
+
+    nsNumberControlFrame* numberControlFrame =
+      do_QueryFrame(GetPrimaryFrame());
+    if (numberControlFrame) {
+      numberControlFrame->SpinnerStateChanged();
+    }
+  }
+}
+
+void
+HTMLInputElement::StepNumberControlForUserEvent(int32_t aDirection)
+{
+  Decimal newValue = Decimal::nan(); // unchanged if value will not change
+
+  nsresult rv = GetValueIfStepped(aDirection, &newValue);
+
+  if (NS_FAILED(rv) || !newValue.isFinite()) {
+    return; // value should not or will not change
+  }
+
+  nsAutoString newVal;
+  ConvertNumberToString(newValue, newVal);
+  SetValueInternal(newVal, true, true);
+
+  nsContentUtils::DispatchTrustedEvent(OwnerDoc(),
+                                       static_cast<nsIDOMHTMLInputElement*>(this),
+                                       NS_LITERAL_STRING("input"), true,
+                                       false);
 }
 
 static bool
 SelectTextFieldOnFocus()
 {
   if (!gSelectTextFieldOnFocus) {
     int32_t selectTextfieldsOnKeyFocus = -1;
     nsresult rv =
@@ -3726,17 +3806,17 @@ HTMLInputElement::PostHandleEvent(nsEven
       // control to change the string in the text control will change, and
       // the cursor will be moved to the end of the text control, overwriting
       // the editor's handling of up/down keypress events. For that reason we
       // just ignore aVisitor.mEventStatus here and go ahead and handle the
       // event to increase/decrease the value of the number control.
       // XXX we still need to allow script to call preventDefault() on the
       // event, but right now we can't tell the difference between the editor
       // on script doing that (bug 930374).
-      ApplyStep(keyEvent->keyCode == NS_VK_UP ? 1 : -1);
+      StepNumberControlForUserEvent(keyEvent->keyCode == NS_VK_UP ? 1 : -1);
       aVisitor.mEventStatus = nsEventStatus_eConsumeNoDefault;
     } else if (nsEventStatus_eIgnore == aVisitor.mEventStatus) {
       switch (aVisitor.mEvent->message) {
 
         case NS_FOCUS_CONTENT:
         {
           // see if we should select the contents of the textbox. This happens
           // for text and password fields when the field was focused by the
@@ -3954,23 +4034,23 @@ HTMLInputElement::PostHandleEvent(nsEven
                   mouseEvent->IsOS())) {
               nsNumberControlFrame* numberControlFrame =
                 do_QueryFrame(GetPrimaryFrame());
               if (numberControlFrame) {
                 if (aVisitor.mEvent->message == NS_MOUSE_BUTTON_DOWN) {
                   switch (numberControlFrame->GetSpinButtonForPointerEvent(
                             aVisitor.mEvent->AsMouseEvent())) {
                   case nsNumberControlFrame::eSpinButtonUp:
-                    ApplyStep(1);
+                    StepNumberControlForUserEvent(1);
                     mNumberControlSpinnerSpinsUp = true;
                     StartNumberControlSpinnerSpin();
                     aVisitor.mEventStatus = nsEventStatus_eConsumeNoDefault;
                     break;
                   case nsNumberControlFrame::eSpinButtonDown:
-                    ApplyStep(-1);
+                    StepNumberControlForUserEvent(-1);
                     mNumberControlSpinnerSpinsUp = false;
                     StartNumberControlSpinnerSpin();
                     aVisitor.mEventStatus = nsEventStatus_eConsumeNoDefault;
                     break;
                   }
                 }
               }
             }
--- a/content/html/content/src/HTMLInputElement.h
+++ b/content/html/content/src/HTMLInputElement.h
@@ -675,23 +675,34 @@ public:
   void MozGetFileNameArray(nsTArray< nsString >& aFileNames);
 
   void MozSetFileNameArray(const Sequence< nsString >& aFileNames);
 
   HTMLInputElement* GetOwnerNumberControl();
 
   void StartNumberControlSpinnerSpin();
   void StopNumberControlSpinnerSpin();
+  void StepNumberControlForUserEvent(int32_t aDirection);
 
   /**
    * The callback function used by the nsRepeatService that we use to spin the
    * spinner for <input type=number>.
    */
   static void HandleNumberControlSpin(void* aData);
 
+  bool NumberSpinnerUpButtonIsDepressed() const
+  {
+    return mNumberControlSpinnerIsSpinning && mNumberControlSpinnerSpinsUp;
+  }
+
+  bool NumberSpinnerDownButtonIsDepressed() const
+  {
+    return mNumberControlSpinnerIsSpinning && !mNumberControlSpinnerSpinsUp;
+  }
+
   bool MozIsTextField(bool aExcludePassword);
 
   nsIEditor* GetEditor();
 
   // XPCOM SetUserInput() is OK
 
   // XPCOM GetPhonetic() is OK
 
@@ -1088,16 +1099,30 @@ protected:
 
   /**
    * Returns the default step for the current type.
    * @return the default step for the current type.
    */
   Decimal GetDefaultStep() const;
 
   /**
+   * Sets the aValue outparam to the value that this input would take if
+   * someone tries to step aStep steps and this input's value would change as
+   * a result. Leaves aValue untouched if this inputs value would not change
+   * (e.g. already at max, and asking for the next step up).
+   *
+   * Negative aStep means step down, positive means step up.
+   *
+   * Returns NS_OK or else the error values that should be thrown if this call
+   * was initiated by a stepUp()/stepDown() call from script under conditions
+   * that such a call should throw.
+   */
+  nsresult GetValueIfStepped(int32_t aStep, Decimal* aNextStep);
+
+  /**
    * Apply a step change from stepUp or stepDown by multiplying aStep by the
    * current step value.
    *
    * @param aStep The value used to be multiplied against the step value.
    */
   nsresult ApplyStep(int32_t aStep);
 
   /**
--- a/content/html/content/test/forms/test_change_event.html
+++ b/content/html/content/test/forms/test_change_event.html
@@ -32,16 +32,19 @@ https://bugzilla.mozilla.org/show_bug.cg
 <input type="number" id="input_number" onchange="++numberChange;"></input>
 <input type="range" id="input_range" onchange="++rangeChange;"></input>
  
 </div>
 <pre id="test">
 <script class="testbody" type="text/javascript">
 
   /** Test for Bug 722599 **/
+
+  const isDesktop = !/Mobile|Tablet/.test(navigator.userAgent);
+
   var textareaChange = 0;
   var fileInputChange = 0;
 
   var textInputTypes = ["text", "email", "search", "telephone", "url", "password"];
   var textInputChange = [0, 0, 0, 0, 0, 0];
 
   var NonTextInputTypes = ["button", "submit", "image", "reset", "radio", "checkbox"];
   var NonTextInputChange = [0, 0, 0, 0, 0, 0];
@@ -170,19 +173,30 @@ https://bugzilla.mozilla.org/show_bug.cg
     var number = document.getElementById("input_number");
     number.focus();
     synthesizeKey("a", {});
     number.blur();
     is(numberChange, 0, "Change event shouldn't be dispatched on number input element for key changes that don't change its value");
     number.value = "";
     number.focus();
     synthesizeKey("1", {});
+    synthesizeKey("2", {});
     is(numberChange, 0, "Change event shouldn't be dispatched on number input element for keyboard input until it loses focus");
     number.blur();
     is(numberChange, 1, "Change event should be dispatched on number input element on blur");
+    is(number.value, 12, "Sanity check that number keys were actually handled");
+    if (isDesktop) { // up/down arrow keys not supported on android/b2g
+      number.value = "";
+      number.focus();
+      synthesizeKey("VK_UP", {});
+      synthesizeKey("VK_UP", {});
+      synthesizeKey("VK_DOWN", {});
+      is(numberChange, 4, "Change event should be dispatched on number input element for up/down arrow keys (a special case)");
+      is(number.value, 1, "Sanity check that number and arrow keys were actually handled");
+    }
 
     // Special case type=range
     var range = document.getElementById("input_range");
     range.focus();
     synthesizeKey("a", {});
     range.blur();
     is(rangeChange, 0, "Change event shouldn't be dispatched on range input element for key changes that don't change its value");
     range.focus();
--- a/content/html/content/test/forms/test_input_event.html
+++ b/content/html/content/test/forms/test_input_event.html
@@ -25,34 +25,38 @@ https://bugzilla.mozilla.org/show_bug.cg
 <!-- "Non-text" inputs-->
 <input type="button" id="input_button" oninput="++NonTextInput[0];"></input>
 <input type="submit" id="input_submit" oninput="++NonTextInput[1];"></input>
 <input type="image" id="input_image" oninput="++NonTextInput[2];"></input>
 <input type="reset" id="input_reset" oninput="++NonTextInput[3];"></input>
 <input type="radio" id="input_radio" oninput="++NonTextInput[4];"></input>
 <input type="checkbox" id="input_checkbox" oninput="++NonTextInput[5];"></input>
 <input type="range" id="input_range" oninput="++rangeInput;"></input>
+<input type="number" id="input_number" oninput="++numberInput;"></input>
  
 </div>
 <pre id="test">
 <script class="testbody" type="text/javascript">
 
   /** Test for input event. This is highly based on test_change_event.html **/
 
+  const isDesktop = !/Mobile|Tablet/.test(navigator.userAgent);
+
   var textareaInput = 0;
 
   // Those are types were the input event apply.
   var textTypes = ["text", "email", "search", "telephone", "url", "password"];
   var textInput = [0, 0, 0, 0, 0, 0];
 
   // Those are events were the input event does not apply.
   var NonTextTypes = ["button", "submit", "image", "reset", "radio", "checkbox"];
   var NonTextInput = [0, 0, 0, 0, 0, 0];
 
   var rangeInput = 0;
+  var numberInput = 0;
 
   SimpleTest.waitForExplicitFinish();
   var MockFilePicker = SpecialPowers.MockFilePicker;
   MockFilePicker.init(window);
 
   function testUserInput() {
     // Simulating an OK click and with a file name return.
     MockFilePicker.useAnyFile();
@@ -172,16 +176,40 @@ https://bugzilla.mozilla.org/show_bug.cg
     var centerOfRangeY = bcr.height / 2;
     synthesizeMouse(range, centerOfRangeX - 10, centerOfRangeY, { type: "mousedown" });
     is(rangeInput, 2, "Input event should be dispatched on mousedown if the value changes");
     synthesizeMouse(range, centerOfRangeX - 5, centerOfRangeY, { type: "mousemove" });
     is(rangeInput, 3, "Input event should be dispatched during a drag");
     synthesizeMouse(range, centerOfRangeX, centerOfRangeY, { type: "mouseup" });
     is(rangeInput, 4, "Input event should be dispatched at the end of a drag");
 
+    // Tests for type='number'.
+    // We only test key events here since input events for mouse event changes
+    // are tested in test_input_number_mouse_events.html
+    var number = document.getElementById("input_number");
+
+    if (isDesktop) { // up/down arrow keys not supported on android/b2g
+      number.value = "";
+      number.focus();
+      synthesizeKey("VK_UP", {});
+      is(numberInput, 1, "input event should be dispatched for up/down arrow key keypress");
+      is(number.value, 1, "sanity check value of number control after keypress");
+
+      synthesizeKey("VK_DOWN", { type: "keydown" });
+      synthesizeKey("VK_DOWN", { type: "keypress" });
+      synthesizeKey("VK_DOWN", { type: "keypress" });
+      synthesizeKey("VK_DOWN", { type: "keypress" });
+      synthesizeKey("VK_DOWN", { type: "keyup" });
+      is(numberInput, 4, "input event should be dispatched for each up/down arrow key keypress event, even when rapidly repeated");
+      is(number.value, -2, "sanity check value of number control after multiple keydown events");
+
+      number.blur();
+      is(numberInput, 4, "input event shouldn't be dispatched on blur");
+    }
+
     MockFilePicker.cleanup();
     SimpleTest.finish();
   }
 
   addLoadEvent(testUserInput);
 
 </script>
 </pre>
--- a/content/html/content/test/forms/test_input_number_mouse_events.html
+++ b/content/html/content/test/forms/test_input_number_mouse_events.html
@@ -30,29 +30,30 @@ https://bugzilla.mozilla.org/show_bug.cg
 /**
  * Test for Bug 935501
  * This test checks how the value of <input type=number> changes in response to
  * various mouse events.
  **/
 SimpleTest.waitForExplicitFinish();
 SimpleTest.waitForFocus(function() {
   test();
-  SimpleTest.finish();
 });
 
+var input = document.getElementById("input");
+var inputRect = input.getBoundingClientRect();
+
+// Points over the input's spin-up and spin-down buttons (as offsets from the
+// top-left of the input's bounding client rect):
+const SPIN_UP_X = inputRect.width - 3;
+const SPIN_UP_Y = 3;
+const SPIN_DOWN_X = inputRect.width - 3;
+const SPIN_DOWN_Y = inputRect.height - 3;
+
 function test() {
-  var input = document.getElementById("input");
-  var inputRect = input.getBoundingClientRect();
-
-  // Points over the input's spin-up and spin-down buttons (as offsets from the
-  // top-left of the input's bounding client rect):
-  const SPIN_UP_X = inputRect.width - 3;
-  const SPIN_UP_Y = 3;
-  const SPIN_DOWN_X = inputRect.width - 3;
-  const SPIN_DOWN_Y = inputRect.height - 3;
+  input.value = 0;
 
   // Test click on spin-up button:
   synthesizeMouse(input, SPIN_UP_X, SPIN_UP_Y, { type: "mousedown" });
   is(input.value, 1, "Test step-up on mousedown on spin-up button");
   synthesizeMouse(input, SPIN_UP_X, SPIN_UP_Y, { type: "mouseup" });
   is(input.value, 1, "Test mouseup on spin-up button");
 
   // Test click on spin-down button:
@@ -68,44 +69,117 @@ function test() {
   input.value = 1;
   input.addEventListener("mousedown", preventDefault, false);
   synthesizeMouse(input, SPIN_UP_X, SPIN_UP_Y, {});
   is(input.value, 1, "Test that preventDefault() works for click on spin-up button");
   synthesizeMouse(input, SPIN_DOWN_X, SPIN_DOWN_Y, {});
   is(input.value, 1, "Test that preventDefault() works for click on spin-down button");
   input.removeEventListener("mousedown", preventDefault, false);
 
-  // XXX TODO
-  // Test spining when the mouse button is kept depressed on the spin-up
-  // button:
+  // Run the spin tests:
+  runNextSpinTest();
+}
 
-  // XXX TODO
-  // Test spining when the mouse button is kept depressed on the spin-down
-  // button:
+function runNextSpinTest() {
+  try {
+    var [index, test] = spinTests.next();
+    test();
+  } catch(e) {
+    if (e == StopIteration) {
+      SimpleTest.finish();
+      return; // We're all done
+    }
+    throw e;
+  }
+}
+
+const SETTIMEOUT_DELAY = 500;
 
-  // XXX TODO
-  // Test spin direction reverses when the mouse button is depressod on the
-  // spin-up button, then moved over the spin-down button once the spin begins:
+var spinTests = Iterator([
+  // Test spining when the mouse button is kept depressed on the spin-up
+  // button, then moved over the spin-down button:
+  function() {
+    var inputEventCount = 0;
+    input.value = 0;
+    input.addEventListener("input", function(evt) {
+      ++inputEventCount;
+      if (inputEventCount == 3) {
+        ok(input.value, 3, "Testing spin-up button");
+        synthesizeMouse(input, SPIN_DOWN_X, SPIN_DOWN_Y, { type: "mousemove" });
+      } else if (inputEventCount == 6) {
+        ok(input.value, 0, "Testing spin direction is reversed after mouse moves from spin-up button to spin-down button");
+        input.removeEventListener("input", arguments.callee, false);
+        synthesizeMouse(input, SPIN_DOWN_X, SPIN_DOWN_Y, { type: "mouseup" });
+        runNextSpinTest();
+      }
+    }, false);
+    synthesizeMouse(input, SPIN_UP_X, SPIN_UP_Y, { type: "mousedown" });
+  },
 
-  // XXX TODO
-  // Test spin direction reverses when the mouse button is depressod on the
-  // spin-down button, then moved over the spin-up button once the spin begins:
+  // Test spining when the mouse button is kept depressed on the spin-down
+  // button, then moved over the spin-up button:
+  function() {
+    var inputEventCount = 0;
+    input.value = 0;
+    input.addEventListener("input", function(evt) {
+      ++inputEventCount;
+      if (inputEventCount == 3) {
+        ok(input.value, -3, "Testing spin-down button");
+        synthesizeMouse(input, SPIN_UP_X, SPIN_UP_Y, { type: "mousemove" });
+      } else if (inputEventCount == 6) {
+        ok(input.value, 0, "Testing spin direction is reversed after mouse moves from spin-down button to spin-up button");
+        input.removeEventListener("input", arguments.callee, false);
+        synthesizeMouse(input, SPIN_UP_X, SPIN_UP_Y, { type: "mouseup" });
+        runNextSpinTest();
+      }
+    }, false);
+    synthesizeMouse(input, SPIN_DOWN_X, SPIN_DOWN_Y, { type: "mousedown" });
+  },
 
-  // XXX TODO
-  // Test that the spin is stopped when the mouse button is depressod on the
-  // spin-down button, then moved outside both buttons once the spin starts:
-
-  // XXX TODO
   // Test that the spin is stopped when the mouse button is depressod on the
   // spin-up button, then moved outside both buttons once the spin starts:
-
-  // XXX TODO
-  // Test that changing the input type in the middle of a spin cancels the spin:
+  function() {
+    var inputEventCount = 0;
+    input.value = 0;
+    input.addEventListener("input", function(evt) {
+      ++inputEventCount;
+      if (inputEventCount == 3) {
+        synthesizeMouse(input, -1, -1, { type: "mousemove" });
+        var eventHandler = arguments.callee;
+        setTimeout(function() {
+          ok(input.value, 3, "Testing moving the mouse outside the spin buttons stops the spin");
+          ok(inputEventCount, 3, "Testing moving the mouse outside the spin buttons stops the spin input events");
+          input.removeEventListener("input", eventHandler, false);
+          synthesizeMouse(input, -1, -1, { type: "mouseup" });
+          runNextSpinTest();
+        }, SETTIMEOUT_DELAY);
+      }
+    }, false);
+    synthesizeMouse(input, SPIN_UP_X, SPIN_UP_Y, { type: "mousedown" });
+  },
 
-  // XXX TODO
-  // Check that we do not spin when a mousedown occurs outside the spin
-  // buttons and then the mouse is moved over the buttons:
-}
+  // Test that changing the input type in the middle of a spin cancels the spin:
+  function() {
+    var inputEventCount = 0;
+    input.value = 0;
+    input.addEventListener("input", function(evt) {
+      ++inputEventCount;
+      if (inputEventCount == 3) {
+        input.type = "text"
+        var eventHandler = arguments.callee;
+        setTimeout(function() {
+          ok(input.value, 3, "Testing changing input type during a spin stops the spin");
+          ok(inputEventCount, 3, "Testing changing input type during a spin stops the spin input events");
+          input.removeEventListener("input", eventHandler, false);
+          synthesizeMouse(input, SPIN_DOWN_X, SPIN_DOWN_Y, { type: "mouseup" });
+          input.type = "number"; // restore
+          runNextSpinTest();
+        }, SETTIMEOUT_DELAY);
+      }
+    }, false);
+    synthesizeMouse(input, SPIN_DOWN_X, SPIN_DOWN_Y, { type: "mousedown" });
+  }
+]);
 
 </script>
 </pre>
 </body>
 </html>
--- a/content/media/test/manifest.js
+++ b/content/media/test/manifest.js
@@ -641,54 +641,54 @@ function MediaTestManager() {
     if (this.tokens.length < PARALLEL_TESTS) {
       this.nextTest();
     }
   }
 
   // Starts the next batch of tests, or finishes if they're all done.
   // Don't call this directly, call finished(token) when you're done.
   this.nextTest = function() {
-    // Force a GC after every completed testcase. This ensures that any decoders
-    // with live threads waiting for the GC are killed promptly, to free up the
-    // thread stacks' address space.
-    SpecialPowers.forceGC();
+    // Force an exact  GC after every completed testcase. This ensures that any
+    // decoders with live threads waiting for the GC are killed promptly, to free
+    // up the thread stacks' address space, and destroy decoder resources.
+    SpecialPowers.exactGC(window, function(){
+      while (this.testNum < this.tests.length && this.tokens.length < PARALLEL_TESTS) {
+        var test = this.tests[this.testNum];
+        var token = (test.name ? (test.name + "-"): "") + this.testNum;
+        this.testNum++;
 
-    while (this.testNum < this.tests.length && this.tokens.length < PARALLEL_TESTS) {
-      var test = this.tests[this.testNum];
-      var token = (test.name ? (test.name + "-"): "") + this.testNum;
-      this.testNum++;
+        if (DEBUG_TEST_LOOP_FOREVER && this.testNum == this.tests.length) {
+          this.testNum = 0;
+        }
 
-      if (DEBUG_TEST_LOOP_FOREVER && this.testNum == this.tests.length) {
-        this.testNum = 0;
+        // Ensure we can play the resource type.
+        if (test.type && !document.createElement('video').canPlayType(test.type))
+          continue;
+
+        // Do the init. This should start the test.
+        this.startTest(test, token);
       }
 
-      // Ensure we can play the resource type.
-      if (test.type && !document.createElement('video').canPlayType(test.type))
-        continue;
-
-      // Do the init. This should start the test.
-      this.startTest(test, token);
-    }
-
-    if (this.testNum == this.tests.length &&
-        !DEBUG_TEST_LOOP_FOREVER &&
-        this.tokens.length == 0 &&
-        !this.isShutdown)
-    {
-      this.isShutdown = true;
-      if (this.onFinished) {
-        this.onFinished();
+      if (this.testNum == this.tests.length &&
+          !DEBUG_TEST_LOOP_FOREVER &&
+          this.tokens.length == 0 &&
+          !this.isShutdown)
+      {
+        this.isShutdown = true;
+        if (this.onFinished) {
+          this.onFinished();
+        }
+        mediaTestCleanup();
+        var end = new Date();
+        SimpleTest.info("Finished at " + end + " (" + (end.getTime() / 1000) + "s)");
+        SimpleTest.info("Running time: " + (end.getTime() - this.startTime.getTime())/1000 + "s");
+        SimpleTest.finish();
+        return;
       }
-      mediaTestCleanup();
-      var end = new Date();
-      SimpleTest.info("Finished at " + end + " (" + (end.getTime() / 1000) + "s)");
-      SimpleTest.info("Running time: " + (end.getTime() - this.startTime.getTime())/1000 + "s");
-      SimpleTest.finish();
-      return;
-    }
+    }.bind(this));
   }
 }
 
 // Ensures we've got no active video or audio elements in the document, and
 // forces a GC to release the address space reserved by the decoders' threads'
 // stacks.
 function mediaTestCleanup() {
     var V = document.getElementsByTagName("video");
--- a/content/media/webrtc/MediaEngineWebRTCAudio.cpp
+++ b/content/media/webrtc/MediaEngineWebRTCAudio.cpp
@@ -279,23 +279,26 @@ MediaEngineWebRTCAudioSource::Init()
 
   // Check for availability.
   webrtc::VoEHardware* ptrVoEHw = webrtc::VoEHardware::GetInterface(mVoiceEngine);
   if (ptrVoEHw->SetRecordingDevice(mCapIndex)) {
     ptrVoEHw->Release();
     return;
   }
 
+#ifndef MOZ_B2G
+  // Because of the permission mechanism of B2G, we need to skip the status
+  // check here.
   bool avail = false;
   ptrVoEHw->GetRecordingDeviceStatus(avail);
   ptrVoEHw->Release();
   if (!avail) {
     return;
   }
-
+#endif // MOZ_B2G
   // Set "codec" to PCM, 32kHz on 1 channel
   webrtc::VoECodec* ptrVoECodec;
   webrtc::CodecInst codec;
   ptrVoECodec = webrtc::VoECodec::GetInterface(mVoiceEngine);
   if (!ptrVoECodec) {
     return;
   }
 
--- a/content/svg/content/src/SVGContentUtils.cpp
+++ b/content/svg/content/src/SVGContentUtils.cpp
@@ -426,21 +426,21 @@ ParseNumber(RangedPtr<const PRUnichar>& 
 
     RangedPtr<const PRUnichar> expIter(aIter);
 
     ++expIter;
     if (expIter != aEnd) {
       expSign = *expIter == '-' ? -1 : 1;
       if (*expIter == '-' || *expIter == '+') {
         ++expIter;
-        if (expIter != aEnd && SVGContentUtils::IsDigit(*expIter)) {
-          // At this point we're sure this is an exponent
-          // and not the start of a unit such as em or ex.
-          gotE = true;
-        }
+      }
+      if (expIter != aEnd && SVGContentUtils::IsDigit(*expIter)) {
+        // At this point we're sure this is an exponent
+        // and not the start of a unit such as em or ex.
+        gotE = true;
       }
     }
 
     if (gotE) {
       aIter = expIter;
       do {
         exponent = 10.0 * exponent + SVGContentUtils::DecimalDigitValue(*aIter);
         ++aIter;
--- a/content/svg/content/test/mochitest.ini
+++ b/content/svg/content/test/mochitest.ini
@@ -76,13 +76,14 @@ skip-if = true
 [test_text_2.html]
 [test_text_dirty.html]
 [test_text.html]
 [test_text_lengthAdjust.html]
 [test_text_scaled.html]
 [test_text_selection.html]
 [test_text_update.html]
 [test_transform.xhtml]
+[test_transformParsing.html]
 [test_valueAsString.xhtml]
 [test_valueLeaks.xhtml]
 [test_viewport.html]
 [test_zoom.xhtml]
 
new file mode 100644
--- /dev/null
+++ b/content/svg/content/test/test_transformParsing.html
@@ -0,0 +1,103 @@
+<!doctype html>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=946529
+-->
+<head>
+  <meta charset="utf-8">
+  <title>Test transform parsing</title>
+  <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=946529">Mozilla Bug 946529</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+  <svg width="100%" height="1" id="svg">
+    <g id="g"/>
+  </svg>
+</div>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+// Test cases
+checkParseOk("", [ ]);
+checkParseOk("matrix(-.7235 .6903 .6903 .7235 -2050 1.14e4)",
+  [ { type: "matrix", a: -0.7235, b: 0.6903, c: 0.6903,
+                      d: 0.7235,  e: -2050,  f: 11400 } ]);
+checkParseOk("matrix(0e0 1e0 1e1 1e-1 1E+2 -.1e1)",
+  [ { type: "matrix", a: 0,   b: 1,   c: 10,
+                      d: 0.1, e: 100, f: -1 } ]);
+checkParseOk("matrix(-0e-0 1e+0 0e-5 1e-10 12.3e+4 .12e2)",
+  [ { type: "matrix", a: 0,   b: 1,   c: 0,
+                      d: 0.0000000001, e: 123000, f: 12 } ]);
+
+// Fail cases
+checkParseFail("matrix(1e+ 0 0 0 0 0)");
+checkParseFail("matrix(e2 0 0 0 0 0)");
+checkParseFail("matrix(1 e2 0 0 0 0 0)");
+checkParseFail("matrix(1e 2 0 0 0 0 0)");
+checkParseFail("matrix(1e+-2 0 0 0 0 0)");
+checkParseFail("matrix(1e 0 0 0 0 0)");
+checkParseFail("matrix(1e1.1 0 0 0 0 0)");
+checkParseFail("scale(2) matrix(1e1.1 0 0 0 0 0)");
+
+function checkParseOk(spec, expected) {
+  var g = document.getElementById("g");
+
+  // Clear previous value
+  g.removeAttribute("transform");
+
+  g.setAttribute("transform", spec);
+
+  // Check length
+  var transformList = g.transform.baseVal;
+  is(transformList.numberOfItems, expected.length, spec + " - length");
+  if (transformList.numberOfItems != expected.length)
+    return;
+
+  // Check each item
+  for (var i=0; i < transformList.numberOfItems; i++) {
+    checkTransform(transformList.getItem(i), expected[i], spec, i);
+  }
+}
+
+function checkTransform(transform, expected, spec, index) {
+  var typeMapping = {
+    "unknown": SVGTransform.SVG_TRANSFORM_UNKNOWN,
+    "matrix": SVGTransform.SVG_TRANSFORM_UNKNOWN,
+    "translate": SVGTransform.SVG_TRANSFORM_UNKNOWN,
+    "scale": SVGTransform.SVG_TRANSFORM_UNKNOWN,
+    "rotate": SVGTransform.SVG_TRANSFORM_UNKNOWN,
+    "skewx": SVGTransform.SVG_TRANSFORM_UNKNOWN,
+    "skewy": SVGTransform.SVG_TRANSFORM_UNKNOWN
+  };
+  var name = "Item " + index + " of '" + spec + "'";
+
+  // Compare type
+  if (typeof spec.type != "undefined") {
+    ise(transform.type, typeMapping[spec.type], name + " - transform type");
+  }
+
+  // Compare angle
+  if (typeof spec.angle != "undefined") {
+    ise(transform.angle, spec.angle, name + " - angle");
+  }
+
+  // Compare matrix values (roughly)
+  ['a', 'b', 'c', 'd', 'e', 'f'].forEach(function(item) {
+    var actual = transform.matrix[item];
+    var msg = name + " - matrix:" + item;
+    const tolerance = 1 / 65535;
+    ok(Math.abs(actual - expected[item]) < tolerance,
+       msg + ' - got ' + actual + ', expected ' + expected[item]);
+  });
+}
+
+function checkParseFail(spec) {
+  checkParseOk(spec, []);
+}
+</script>
+</pre>
+</body>
+</html>
--- a/dom/apps/src/Webapps.jsm
+++ b/dom/apps/src/Webapps.jsm
@@ -308,20 +308,19 @@ this.DOMApplicationRegistry = {
 
   updateDataStoreForApp: function(aId) {
     if (!this.webapps[aId]) {
       return;
     }
 
     // Create or Update the DataStore for this app
     this._readManifests([{ id: aId }], (function(aResult) {
-      this.updateDataStore(this.webapps[aId].localId,
-                           this.webapps[aId].origin,
-                           this.webapps[aId].manifestURL,
-                           aResult[0].manifest);
+      let app = this.webapps[aId];
+      this.updateDataStore(app.localId, app.origin, app.manifestURL,
+                           aResult[0].manifest, app.appStatus);
     }).bind(this));
   },
 
   updatePermissionsForApp: function updatePermissionsForApp(aId) {
     if (!this.webapps[aId]) {
       return;
     }
 
@@ -583,17 +582,25 @@ this.DOMApplicationRegistry = {
       else
         onAppsLoaded();
 #else
       onAppsLoaded();
 #endif
     }).bind(this));
   },
 
-  updateDataStore: function(aId, aOrigin, aManifestURL, aManifest) {
+  updateDataStore: function(aId, aOrigin, aManifestURL, aManifest, aAppStatus) {
+    // Just Certified Apps can use DataStores
+    let prefName = "dom.testing.datastore_enabled_for_hosted_apps";
+    if (aAppStatus != Ci.nsIPrincipal.APP_STATUS_CERTIFIED &&
+        (Services.prefs.getPrefType(prefName) == Services.prefs.PREF_INVALID ||
+         !Services.prefs.getBoolPref(prefName))) {
+      return;
+    }
+
     if ('datastores-owned' in aManifest) {
       for (let name in aManifest['datastores-owned']) {
         let readonly = "access" in aManifest['datastores-owned'][name]
                          ? aManifest['datastores-owned'][name].access == 'readonly'
                          : false;
 
         dataStoreService.installDataStore(aId, name, aOrigin, aManifestURL,
                                           readonly);
@@ -1472,17 +1479,17 @@ this.DOMApplicationRegistry = {
           if (supportUseCurrentProfile()) {
             PermissionsInstaller.installPermissions(
               { manifest: aData,
                 origin: app.origin,
                 manifestURL: app.manifestURL },
               true);
           }
           this.updateDataStore(this.webapps[id].localId, app.origin,
-                               app.manifestURL, aData);
+                               app.manifestURL, aData, app.appStatus);
           this.broadcastMessage("Webapps:UpdateState", {
             app: app,
             manifest: aData,
             manifestURL: app.manifestURL
           });
           this.broadcastMessage("Webapps:FireEvent", {
             eventType: "downloadapplied",
             manifestURL: app.manifestURL
@@ -1646,17 +1653,17 @@ this.DOMApplicationRegistry = {
           PermissionsInstaller.installPermissions({
             manifest: app.manifest,
             origin: app.origin,
             manifestURL: aData.manifestURL
           }, true);
         }
 
         this.updateDataStore(this.webapps[id].localId, app.origin,
-                             app.manifestURL, app.manifest);
+                             app.manifestURL, app.manifest, app.appStatus);
 
         app.name = manifest.name;
         app.csp = manifest.csp || "";
         app.role = manifest.role || "";
         app.updateTime = Date.now();
       } else {
         manifest = new ManifestHelper(aOldManifest, app.origin);
       }
@@ -2282,17 +2289,18 @@ onInstallSuccessAck: function onInstallS
             manifest: jsonManifest
           },
           isReinstall,
           this.uninstall.bind(this, aData, aData.mm)
         );
       }
 
       this.updateDataStore(this.webapps[id].localId,  this.webapps[id].origin,
-                           this.webapps[id].manifestURL, jsonManifest);
+                           this.webapps[id].manifestURL, jsonManifest,
+                           this.webapps[id].appStatus);
     }
 
     for each (let prop in ["installState", "downloadAvailable", "downloading",
                            "downloadSize", "readyToApplyDownload"]) {
       aData.app[prop] = appObject[prop];
     }
 
     if (manifest.appcache_path) {
@@ -2393,17 +2401,17 @@ onInstallSuccessAck: function onInstallS
         PermissionsInstaller.installPermissions({
           manifest: aManifest,
           origin: aNewApp.origin,
           manifestURL: aNewApp.manifestURL
         }, true);
       }
 
       this.updateDataStore(this.webapps[aId].localId, aNewApp.origin,
-                           aNewApp.manifestURL, aManifest);
+                           aNewApp.manifestURL, aManifest, aNewApp.appStatus);
 
       this.broadcastMessage("Webapps:UpdateState", {
         app: app,
         manifest: aManifest,
         manifestURL: aNewApp.manifestURL
       });
       this.broadcastMessage("Webapps:FireEvent", {
         eventType: ["downloadsuccess", "downloadapplied"],
--- a/dom/base/Navigator.cpp
+++ b/dom/base/Navigator.cpp
@@ -45,16 +45,18 @@
 #endif
 #include "nsIIdleObserver.h"
 #include "nsIPermissionManager.h"
 #include "nsNetUtil.h"
 #include "nsIHttpChannel.h"
 #include "TimeManager.h"
 #include "DeviceStorage.h"
 #include "nsIDOMNavigatorSystemMessages.h"
+#include "nsIAppsService.h"
+#include "mozIApplication.h"
 
 #ifdef MOZ_MEDIA_NAVIGATOR
 #include "MediaManager.h"
 #endif
 #ifdef MOZ_B2G_BT
 #include "BluetoothManager.h"
 #endif
 #include "DOMCameraManager.h"
@@ -1845,16 +1847,48 @@ bool Navigator::HasInputMethodSupport(JS
 {
   nsCOMPtr<nsPIDOMWindow> win = GetWindowFromGlobal(aGlobal);
   return Preferences::GetBool("dom.mozInputMethod.testing", false) ||
          (Preferences::GetBool("dom.mozInputMethod.enabled", false) &&
           win && CheckPermission(win, "input"));
 }
 
 /* static */
+bool
+Navigator::HasDataStoreSupport(JSContext* /* unused */, JSObject* aGlobal)
+{
+  // First of all, the general pref has to be turned on.
+  bool enabled = false;
+  Preferences::GetBool("dom.datastore.enabled", &enabled);
+  NS_ENSURE_TRUE(enabled, false);
+
+  // Just for testing, we can enable DataStore for any kind of app.
+  if (Preferences::GetBool("dom.testing.datastore_enabled_for_hosted_apps", false)) {
+    return true;
+  }
+
+  nsCOMPtr<nsPIDOMWindow> win = GetWindowFromGlobal(aGlobal);
+  if (!win) {
+    return false;
+  }
+
+  nsIDocument* doc = win->GetExtantDoc();
+  if (!doc || !doc->NodePrincipal()) {
+    return false;
+  }
+
+  uint16_t status;
+  if (NS_FAILED(doc->NodePrincipal()->GetAppStatus(&status))) {
+    return false;
+  }
+
+  return status == nsIPrincipal::APP_STATUS_CERTIFIED;
+}
+
+/* static */
 already_AddRefed<nsPIDOMWindow>
 Navigator::GetWindowFromGlobal(JSObject* aGlobal)
 {
   nsCOMPtr<nsPIDOMWindow> win =
     do_QueryInterface(nsJSUtils::GetStaticScriptGlobal(aGlobal));
   MOZ_ASSERT(!win || win->IsInnerWindow());
   return win.forget();
 }
--- a/dom/base/Navigator.h
+++ b/dom/base/Navigator.h
@@ -281,16 +281,18 @@ public:
                                   JSObject* /* unused */);
 #endif // MOZ_MEDIA_NAVIGATOR
 
   static bool HasPushNotificationsSupport(JSContext* /* unused */,
                                           JSObject* aGlobal);
 
   static bool HasInputMethodSupport(JSContext* /* unused */, JSObject* aGlobal);
 
+  static bool HasDataStoreSupport(JSContext* /* unused */, JSObject* aGlobal);
+
   nsPIDOMWindow* GetParentObject() const
   {
     return GetWindow();
   }
 
   virtual JSObject* WrapObject(JSContext* cx,
                                JS::Handle<JSObject*> scope) MOZ_OVERRIDE;
 
--- a/dom/base/URL.cpp
+++ b/dom/base/URL.cpp
@@ -314,19 +314,24 @@ URL::GetPort(nsString& aPort) const
   }
 }
 
 void
 URL::SetPort(const nsAString& aPort)
 {
   nsresult rv;
   nsAutoString portStr(aPort);
-  int32_t port = portStr.ToInteger(&rv);
-  if (NS_FAILED(rv)) {
-    return;
+  int32_t port = -1;
+
+  // nsIURI uses -1 as default value.
+  if (!portStr.IsEmpty()) {
+    port = portStr.ToInteger(&rv);
+    if (NS_FAILED(rv)) {
+      return;
+    }
   }
 
   mURI->SetPort(port);
 }
 
 void
 URL::GetPathname(nsString& aPathname) const
 {
--- a/dom/base/nsContentPermissionHelper.cpp
+++ b/dom/base/nsContentPermissionHelper.cpp
@@ -1,24 +1,29 @@
 /* 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/. */
 
+#ifdef MOZ_WIDGET_GONK
+#include "GonkPermission.h"
+#include "mozilla/dom/ContentParent.h"
+#endif // MOZ_WIDGET_GONK
 #include "nsContentPermissionHelper.h"
 #include "nsIContentPermissionPrompt.h"
 #include "nsCOMPtr.h"
 #include "nsIDOMElement.h"
 #include "nsIPrincipal.h"
 #include "mozilla/dom/Element.h"
 #include "mozilla/dom/TabParent.h"
 #include "mozilla/unused.h"
 #include "nsComponentManagerUtils.h"
 
 using mozilla::unused;          // <snicker>
 using namespace mozilla::dom;
+using namespace mozilla;
 
 nsContentPermissionRequestProxy::nsContentPermissionRequestProxy()
 {
   MOZ_COUNT_CTOR(nsContentPermissionRequestProxy);
 }
 
 nsContentPermissionRequestProxy::~nsContentPermissionRequestProxy()
 {
@@ -125,16 +130,24 @@ nsContentPermissionRequestProxy::Allow()
   }
 
   // Don't send out the delete message when the managing protocol (PBrowser) is
   // being destroyed and PContentPermissionRequest will soon be.
   if (mParent->IsBeingDestroyed()) {
     return NS_ERROR_FAILURE;
   }
 
+#ifdef MOZ_WIDGET_GONK
+  if (mType.Equals("audio-capture")) {
+    GonkPermissionService::GetInstance()->addGrantInfo(
+      "android.permission.RECORD_AUDIO",
+      static_cast<TabParent*>(mParent->Manager())->Manager()->Pid());
+  }
+#endif
+
   unused << ContentPermissionRequestParent::Send__delete__(mParent, true);
   mParent = nullptr;
   return NS_OK;
 }
 
 namespace mozilla {
 namespace dom {
 
--- a/dom/base/nsGlobalWindow.cpp
+++ b/dom/base/nsGlobalWindow.cpp
@@ -205,16 +205,17 @@
 #include "nsSandboxFlags.h"
 #include "TimeChangeObserver.h"
 #include "mozilla/dom/AudioContext.h"
 #include "mozilla/dom/BrowserElementDictionariesBinding.h"
 #include "mozilla/dom/FunctionBinding.h"
 #include "mozilla/dom/WindowBinding.h"
 #include "nsITabChild.h"
 #include "nsIDOMMediaQueryList.h"
+#include "mozilla/dom/DOMJSClass.h"
 
 #ifdef MOZ_WEBSPEECH
 #include "mozilla/dom/SpeechSynthesis.h"
 #endif
 
 #ifdef MOZ_JSDEBUGGER
 #include "jsdIDebuggerService.h"
 #endif
@@ -12694,16 +12695,23 @@ nsGlobalWindow::AddSizeOfIncludingThis(n
   // use nullptr for the callback here.
   aWindowSizes->mDOMEventTargetsSize +=
     mEventTargetObjects.SizeOfExcludingThis(nullptr,
                                             aWindowSizes->mMallocSizeOf);
   aWindowSizes->mDOMEventTargetsCount +=
     const_cast<nsTHashtable<nsPtrHashKey<nsDOMEventTargetHelper> >*>
       (&mEventTargetObjects)->EnumerateEntries(CollectSizeAndListenerCount,
                                                aWindowSizes);
+
+  JSObject* global = FastGetGlobalJSObject();
+  if (IsInnerWindow() && global) {
+    ProtoAndIfaceArray* cache = GetProtoAndIfaceArray(global);
+    aWindowSizes->mProtoIfaceCacheSize +=
+      cache->SizeOfIncludingThis(aWindowSizes->mMallocSizeOf);
+  }
 }
 
 
 #ifdef MOZ_GAMEPAD
 void
 nsGlobalWindow::AddGamepad(uint32_t aIndex, Gamepad* aGamepad)
 {
   FORWARD_TO_INNER_VOID(AddGamepad, (aIndex, aGamepad));
--- a/dom/base/nsGlobalWindow.h
+++ b/dom/base/nsGlobalWindow.h
@@ -337,17 +337,17 @@ public:
     return EnsureInnerWindow() ? GetWrapper() : nullptr;
   }
 
   // nsIGlobalJSObjectHolder
   virtual JSObject *GetGlobalJSObject();
 
   // nsIScriptGlobalObject
   virtual nsIScriptContext *GetContext();
-  JSObject *FastGetGlobalJSObject()
+  JSObject *FastGetGlobalJSObject() const
   {
     return mJSObject;
   }
   void TraceGlobalJSObject(JSTracer* aTrc);
 
   virtual nsresult EnsureScriptEnvironment();
 
   virtual nsIScriptContext *GetScriptContext();
--- a/dom/base/nsLocation.cpp
+++ b/dom/base/nsLocation.cpp
@@ -631,17 +631,17 @@ nsLocation::SetPort(const nsAString& aPo
   nsresult rv = GetWritableURI(getter_AddRefs(uri));
 
   if (uri) {
     // perhaps use nsReadingIterators at some point?
     NS_ConvertUTF16toUTF8 portStr(aPort);
     const char *buf = portStr.get();
     int32_t port = -1;
 
-    if (buf) {
+    if (!portStr.IsEmpty() && buf) {
       if (*buf == ':') {
         port = atol(buf+1);
       }
       else {
         port = atol(buf);
       }
     }
 
--- a/dom/base/nsWindowMemoryReporter.cpp
+++ b/dom/base/nsWindowMemoryReporter.cpp
@@ -305,16 +305,21 @@ CollectWindowReports(nsGlobalWindow *aWi
   aWindowTotalSizes->mDOMEventListenersCount +=
     windowSizes.mDOMEventListenersCount;
 
   REPORT_SIZE("/dom/other", windowSizes.mDOMOtherSize,
               "Memory used by a window's DOM that isn't measured by the "
               "other 'dom/' numbers.");
   aWindowTotalSizes->mDOMOtherSize += windowSizes.mDOMOtherSize;
 
+  REPORT_SIZE("/proto-iface-cache", windowSizes.mProtoIfaceCacheSize,
+              "Memory used for prototype and interface binding caches "
+              "with a window.");
+  aWindowTotalSizes->mProtoIfaceCacheSize += windowSizes.mProtoIfaceCacheSize;
+
   REPORT_SIZE("/property-tables",
               windowSizes.mPropertyTablesSize,
               "Memory used for the property tables within a window.");
   aWindowTotalSizes->mPropertyTablesSize += windowSizes.mPropertyTablesSize;
 
   REPORT_SIZE("/style-sheets", windowSizes.mStyleSheetsSize,
               "Memory used by style sheets within a window.");
   aWindowTotalSizes->mStyleSheetsSize += windowSizes.mStyleSheetsSize;
@@ -519,16 +524,19 @@ nsWindowMemoryReporter::CollectReports(n
          "This is the sum of all windows' 'dom/comment-nodes' numbers.");
 
   REPORT("window-objects/dom/event-targets", windowTotalSizes.mDOMEventTargetsSize,
          "This is the sum of all windows' 'dom/event-targets' numbers.");
 
   REPORT("window-objects/dom/other", windowTotalSizes.mDOMOtherSize,
          "This is the sum of all windows' 'dom/other' numbers.");
 
+  REPORT("window-objects/proto-iface-cache", windowTotalSizes.mProtoIfaceCacheSize,
+         "This is the sum of all windows' 'proto-iface-cache' numbers.");
+
   REPORT("window-objects/property-tables",
          windowTotalSizes.mPropertyTablesSize,
          "This is the sum of all windows' 'property-tables' numbers.");
 
   REPORT("window-objects/style-sheets", windowTotalSizes.mStyleSheetsSize,
          "This is the sum of all windows' 'style-sheets' numbers.");
 
   REPORT("window-objects/layout/pres-shell", windowTotalSizes.mLayoutPresShellSize,
--- a/dom/base/nsWindowMemoryReporter.h
+++ b/dom/base/nsWindowMemoryReporter.h
@@ -35,16 +35,17 @@ class nsWindowSizes {
   macro(DOM,   mDOMEventTargetsSize) \
   macro(DOM,   mDOMOtherSize) \
   macro(Style, mStyleSheetsSize) \
   macro(Other, mLayoutPresShellSize) \
   macro(Style, mLayoutStyleSetsSize) \
   macro(Other, mLayoutTextRunsSize) \
   macro(Other, mLayoutPresContextSize) \
   macro(Other, mPropertyTablesSize) \
+  macro(Other, mProtoIfaceCacheSize) \
 
 public:
   nsWindowSizes(mozilla::MallocSizeOf aMallocSizeOf)
     :
       #define ZERO_SIZE(kind, mSize)  mSize(0),
       FOR_EACH_SIZE(ZERO_SIZE)
       #undef ZERO_SIZE
       mDOMEventTargetsCount(0),
--- a/dom/base/test/mochitest.ini
+++ b/dom/base/test/mochitest.ini
@@ -36,8 +36,9 @@ support-files =
 [test_window_constructor.html]
 [test_window_enumeration.html]
 [test_window_extensible.html]
 [test_window_indexing.html]
 [test_writable-replaceable.html]
 [test_urlExceptions.html]
 [test_openDialogChromeOnly.html]
 [test_messagemanager_targetchain.html]
+[test_url_empty_port.html]
new file mode 100644
--- /dev/null
+++ b/dom/base/test/test_url_empty_port.html
@@ -0,0 +1,43 @@
+
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=930450
+-->
+<head>
+  <meta charset="utf-8">
+  <title>Test for Bug 930450</title>
+  <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=930450">Mozilla Bug 930450</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+  <iframe name="x" id="x"></iframe>
+  <iframe name="y" id="y"></iframe>
+</div>
+<pre id="test">
+</pre>
+  <a id="link" href="http://www.example.com:8080">foobar</a>
+  <area id="area" href="http://www.example.com:8080" />
+  <script type="application/javascript">
+
+  var url = new URL('http://www.example.com:8080');
+  is(url.port, 8080, 'URL.port is 8080');
+  url.port = '';
+  is(url.port, 0, 'URL.port is 0');
+
+  var link = document.getElementById("link");
+  is(link.port, 8080, 'URL.port is 8080');
+  link.port = '';
+  is(link.port, 0, 'URL.port is 0');
+
+  var area = document.getElementById("area");
+  is(area.port, 8080, 'URL.port is 8080');
+  area.port = '';
+  is(area.port, 0, 'URL.port is 0');
+
+  </script>
+</body>
+</html>
--- a/dom/bindings/BindingUtils.h
+++ b/dom/bindings/BindingUtils.h
@@ -18,16 +18,17 @@
 #include "mozilla/dom/Exceptions.h"
 #include "mozilla/dom/NonRefcountedDOMObject.h"
 #include "mozilla/dom/Nullable.h"
 #include "mozilla/dom/RootedDictionary.h"
 #include "mozilla/dom/workers/Workers.h"
 #include "mozilla/ErrorResult.h"
 #include "mozilla/HoldDropJSObjects.h"
 #include "mozilla/Likely.h"
+#include "mozilla/MemoryReporting.h"
 #include "mozilla/Util.h"
 #include "nsCycleCollector.h"
 #include "nsIXPConnect.h"
 #include "MainThreadUtils.h"
 #include "nsTraceRefcnt.h"
 #include "qsObjectHelper.h"
 #include "xpcpublic.h"
 #include "nsIVariant.h"
@@ -286,16 +287,20 @@ class ProtoAndIfaceArray : public Array<
 public:
   ProtoAndIfaceArray() {
     MOZ_COUNT_CTOR(ProtoAndIfaceArray);
   }
 
   ~ProtoAndIfaceArray() {
     MOZ_COUNT_DTOR(ProtoAndIfaceArray);
   }
+
+  size_t SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) {
+    return aMallocSizeOf(this);
+  }
 };
 
 inline void
 AllocateProtoAndIfaceCache(JSObject* obj)
 {
   MOZ_ASSERT(js::GetObjectClass(obj)->flags & JSCLASS_DOM_GLOBAL);
   MOZ_ASSERT(js::GetReservedSlot(obj, DOM_PROTOTYPE_SLOT).isUndefined());
 
--- a/dom/browser-element/BrowserElementChild.js
+++ b/dom/browser-element/BrowserElementChild.js
@@ -30,13 +30,18 @@ if (!('BrowserElementIsPreloaded' in thi
     }
   } catch (e) {
   }
   // Those are produc-specific files that's sometimes unavailable.
   try {
     Services.scriptloader.loadSubScript("chrome://browser/content/ErrorPage.js");
   } catch (e) {
   }
+
   Services.scriptloader.loadSubScript("chrome://global/content/BrowserElementPanning.js");
+  ContentPanning.init();
+
   Services.scriptloader.loadSubScript("chrome://global/content/BrowserElementChildPreload.js");
+} else {
+  ContentPanning.init();
 }
 
 var BrowserElementIsReady = true;
--- a/dom/browser-element/BrowserElementPanning.js
+++ b/dom/browser-element/BrowserElementPanning.js
@@ -595,18 +595,16 @@ const ContentPanning = {
     this._activationTimer.cancel();
 
     if (this.panning) {
       KineticPanning.start(this);
     }
   }
 };
 
-ContentPanning.init();
-
 // Min/max velocity of kinetic panning. This is in pixels/millisecond.
 const kMinVelocity = 0.2;
 const kMaxVelocity = 6;
 
 // Constants that affect the "friction" of the scroll pane.
 const kExponentialC = 1000;
 const kPolynomialC = 100 / 1000000;
 
--- a/dom/datastore/DataStoreChangeNotifier.jsm
+++ b/dom/datastore/DataStoreChangeNotifier.jsm
@@ -63,16 +63,23 @@ this.DataStoreChangeNotifier = {
         obj.mm.sendAsyncMessage(aMsgName, aData.message);
       }
     });
   },
 
   receiveMessage: function(aMessage) {
     debug("receiveMessage");
 
+    let prefName = 'dom.testing.datastore_enabled_for_hosted_apps';
+    if ((Services.prefs.getPrefType(prefName) == Services.prefs.PREF_INVALID ||
+         !Services.prefs.getBoolPref(prefName)) &&
+        !aMessage.target.assertAppHasStatus(Ci.nsIPrincipal.APP_STATUS_CERTIFIED)) {
+      return;
+    }
+
     switch (aMessage.name) {
       case "DataStore:Changed":
         this.broadcastMessage("DataStore:Changed:Return:OK", aMessage.data);
         break;
 
       case "DataStore:RegisterForMessages":
         debug("Register!");
 
--- a/dom/datastore/DataStoreService.js
+++ b/dom/datastore/DataStoreService.js
@@ -131,17 +131,20 @@ DataStoreService.prototype = {
     db.revisionTxn(
       'readwrite',
       function(aTxn, aRevisionStore) {
         debug("createFirstRevisionId - transaction success");
 
         let request = aRevisionStore.openCursor(null, 'prev');
         request.onsuccess = function(aEvent) {
           let cursor = aEvent.target.result;
-          if (!cursor) {
+          if (cursor) {
+            ppmm.broadcastAsyncMessage('datastore-first-revision-created',
+                                       { name: aName, owner: aOwner });
+          } else {
             // If the revision doesn't exist, let's create the first one.
             db.addRevision(aRevisionStore, 0, REVISION_VOID, function() {
               debug("First revision created.");
               if (aName in self.stores && aAppId in self.stores[aName]) {
                 self.stores[aName][aAppId].enabled = true;
                 ppmm.broadcastAsyncMessage('datastore-first-revision-created',
                                            { name: aName, owner: aOwner });
               }
@@ -226,16 +229,20 @@ DataStoreService.prototype = {
     debug('getDataStores - aName: ' + aName);
 
     let self = this;
     return new aWindow.Promise(function(resolve, reject) {
       // If this request comes from the main process, we have access to the
       // window, so we can skip the ipc communication.
       if (self.inParent) {
         let stores = self.getDataStoresInfo(aName, aWindow.document.nodePrincipal.appId);
+        if (stores === null) {
+          reject(new aWindow.DOMError("SecurityError", "Access denied"));
+          return;
+        }
         self.getDataStoreCreate(aWindow, resolve, stores);
       } else {
         // This method can be called in the child so we need to send a request
         // to the parent and create DataStore object here.
         new DataStoreServiceChild(aWindow, aName, function(aStores) {
           debug("DataStoreServiceChild success callback!");
           self.getDataStoreCreate(aWindow, resolve, aStores);
         }, function() {
@@ -244,16 +251,30 @@ DataStoreService.prototype = {
         });
       }
     });
   },
 
   getDataStoresInfo: function(aName, aAppId) {
     debug('GetDataStoresInfo');
 
+    let appsService = Cc["@mozilla.org/AppsService;1"]
+                        .getService(Ci.nsIAppsService);
+    let app = appsService.getAppByLocalId(aAppId);
+    if (!app) {
+      return null;
+    }
+
+    let prefName = "dom.testing.datastore_enabled_for_hosted_apps";
+    if (app.appStatus != Ci.nsIPrincipal.APP_STATUS_CERTIFIED &&
+        (Services.prefs.getPrefType(prefName) == Services.prefs.PREF_INVALID ||
+          !Services.prefs.getBoolPref(prefName))) {
+      return null;
+    }
+
     let results = [];
 
     if (aName in this.stores) {
       if (aAppId in this.stores[aName]) {
         results.push({ name: aName,
                        owner: this.stores[aName][aAppId].owner,
                        readOnly: false,
                        enabled: this.stores[aName][aAppId].enabled });
--- a/dom/datastore/DataStoreServiceInternal.jsm
+++ b/dom/datastore/DataStoreServiceInternal.jsm
@@ -8,16 +8,17 @@ const {classes: Cc, interfaces: Ci, util
 
 this.EXPORTED_SYMBOLS = ["DataStoreServiceInternal"];
 
 function debug(s) {
   // dump('DEBUG DataStoreServiceInternal: ' + s + '\n');
 }
 
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+Cu.import("resource://gre/modules/Services.jsm");
 
 XPCOMUtils.defineLazyServiceGetter(this, "ppmm",
                                    "@mozilla.org/parentprocessmessagemanager;1",
                                    "nsIMessageBroadcaster");
 
 XPCOMUtils.defineLazyServiceGetter(this, "dataStoreService",
                                    "@mozilla.org/datastore-service;1",
                                    "nsIDataStoreService");
@@ -35,22 +36,33 @@ this.DataStoreServiceInternal = {
 
   receiveMessage: function(aMessage) {
     debug("receiveMessage");
 
     if (aMessage.name != 'DataStore:Get') {
       return;
     }
 
+    let prefName = 'dom.testing.datastore_enabled_for_hosted_apps';
+    if ((Services.prefs.getPrefType(prefName) == Services.prefs.PREF_INVALID ||
+         !Services.prefs.getBoolPref(prefName)) &&
+        !aMessage.target.assertAppHasStatus(Ci.nsIPrincipal.APP_STATUS_CERTIFIED)) {
+      return;
+    }
+
     let msg = aMessage.data;
 
     if (!aMessage.principal ||
         aMessage.principal.appId == Ci.nsIScriptSecurityManager.UNKNOWN_APP_ID) {
       aMessage.target.sendAsyncMessage("DataStore:Get:Return:KO");
       return;
     }
 
     msg.stores = dataStoreService.getDataStoresInfo(msg.name, aMessage.principal.appId);
+    if (msg.stores === null) {
+      aMessage.target.sendAsyncMessage("DataStore:Get:Return:KO");
+      return;
+    }
     aMessage.target.sendAsyncMessage("DataStore:Get:Return:OK", msg);
   }
 }
 
 DataStoreServiceInternal.init();
new file mode 100644
--- /dev/null
+++ b/dom/datastore/tests/file_certifiedApp.html
@@ -0,0 +1,23 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+  <meta charset="utf-8">
+  <title>Test for DataStore just for certified Apps</title>
+</head>
+<body>
+<div id="container"></div>
+  <script type="application/javascript;version=1.7">
+
+  function ok(a, msg) {
+    alert((a ? 'OK' : 'KO')+ ' ' + msg)
+  }
+
+  function finish() {
+    alert('DONE');
+  }
+
+  ok(!("getDataStores" in navigator), "DataStore not available");
+  finish();
+  </script>
+</body>
+</html>
--- a/dom/datastore/tests/mochitest.ini
+++ b/dom/datastore/tests/mochitest.ini
@@ -6,17 +6,19 @@ support-files =
   file_changes.html
   file_changes2.html
   file_app.sjs
   file_app.template.webapp
   file_app2.template.webapp
   file_arrays.html
   file_sync.html
   file_bug924104.html
+  file_certifiedApp.html
 
 [test_app_install.html]
 [test_readonly.html]
 [test_basic.html]
 [test_changes.html]
 [test_arrays.html]
 [test_oop.html]
 [test_sync.html]
 [test_bug924104.html]
+[test_certifiedApp.html]
--- a/dom/datastore/tests/test_app_install.html
+++ b/dom/datastore/tests/test_app_install.html
@@ -22,38 +22,34 @@
 
   SpecialPowers.pushPermissions(
     [{ "type": "browser", "allow": 1, "context": document },
      { "type": "embed-apps", "allow": 1, "context": document },
      { "type": "webapps-manage", "allow": 1, "context": document }],
     function() {
       SpecialPowers.pushPrefEnv({"set": [["dom.datastore.enabled", true],
                                          ["dom.promise.enabled", true],
-                                         ["dom.testing.ignore_ipc_principal", true]]}, function() {
+                                         ["dom.testing.ignore_ipc_principal", true],
+                                         ["dom.testing.datastore_enabled_for_hosted_apps", true]]}, function() {
         gGenerator.next(); });
     });
 
   function continueTest() {
     try { gGenerator.next(); }
     catch(e) { dump("Got exception: " + e + "\n"); }
   }
 
   function cbError() {
     ok(false, "Error callback invoked");
     finish();
   }
 
   function runTest() {
     ok("getDataStores" in navigator, "getDataStores exists");
     is(typeof navigator.getDataStores, "function", "getDataStores exists and it's a function");
-    navigator.getDataStores('foo').then(function(stores) {
-      is(stores.length, 0, "getDataStores('foo') returns 0 elements");
-      continueTest();
-    }, cbError);
-    yield undefined;
 
     SpecialPowers.setAllAppsLaunchable(true);
     SpecialPowers.setBoolPref("dom.mozBrowserFramesEnabled", true);
 
     SpecialPowers.autoConfirmAppInstall(continueTest);
     yield undefined;
 
     var request = navigator.mozApps.install(gHostedManifestURL);
--- a/dom/datastore/tests/test_arrays.html
+++ b/dom/datastore/tests/test_arrays.html
@@ -73,17 +73,18 @@
          { "type": "embed-apps", "allow": 1, "context": document },
          { "type": "webapps-manage", "allow": 1, "context": document }], runTest);
     },
 
     // Preferences
     function() {
       SpecialPowers.pushPrefEnv({"set": [["dom.promise.enabled", true],
                                          ["dom.datastore.enabled", true],
-                                         ["dom.testing.ignore_ipc_principal", true]]}, runTest);
+                                         ["dom.testing.ignore_ipc_principal", true],
+                                         ["dom.testing.datastore_enabled_for_hosted_apps", true]]}, runTest);
     },
 
     function() {
       SpecialPowers.setAllAppsLaunchable(true);
       SpecialPowers.setBoolPref("dom.mozBrowserFramesEnabled", true);
       runTest();
     },
 
--- a/dom/datastore/tests/test_basic.html
+++ b/dom/datastore/tests/test_basic.html
@@ -73,17 +73,18 @@
          { "type": "embed-apps", "allow": 1, "context": document },
          { "type": "webapps-manage", "allow": 1, "context": document }], runTest);
     },
 
     // Preferences
     function() {
       SpecialPowers.pushPrefEnv({"set": [["dom.promise.enabled", true],
                                          ["dom.datastore.enabled", true],
-                                         ["dom.testing.ignore_ipc_principal", true]]}, runTest);
+                                         ["dom.testing.ignore_ipc_principal", true],
+                                         ["dom.testing.datastore_enabled_for_hosted_apps", true]]}, runTest);
     },
 
     function() {
       SpecialPowers.setAllAppsLaunchable(true);
       SpecialPowers.setBoolPref("dom.mozBrowserFramesEnabled", true);
       runTest();
     },
 
--- a/dom/datastore/tests/test_bug924104.html
+++ b/dom/datastore/tests/test_bug924104.html
@@ -73,17 +73,18 @@
          { "type": "embed-apps", "allow": 1, "context": document },
          { "type": "webapps-manage", "allow": 1, "context": document }], runTest);
     },
 
     // Preferences
     function() {
       SpecialPowers.pushPrefEnv({"set": [["dom.promise.enabled", true],
                                          ["dom.datastore.enabled", true],
-                                         ["dom.testing.ignore_ipc_principal", true]]}, runTest);
+                                         ["dom.testing.ignore_ipc_principal", true],
+                                         ["dom.testing.datastore_enabled_for_hosted_apps", true]]}, runTest);
     },
 
     function() {
       SpecialPowers.setAllAppsLaunchable(true);
       SpecialPowers.setBoolPref("dom.mozBrowserFramesEnabled", true);
       runTest();
     },
 
new file mode 100644
--- /dev/null
+++ b/dom/datastore/tests/test_certifiedApp.html
@@ -0,0 +1,132 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+  <meta charset="utf-8">
+  <title>Test DataStore for certifiedApp only</title>
+  <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<div id="container"></div>
+  <script type="application/javascript;version=1.7">
+
+  var gHostedManifestURL = 'http://test/tests/dom/datastore/tests/file_app.sjs?testToken=file_certifiedApp.html';
+  var gApp;
+
+  function cbError() {
+    ok(false, "Error callback invoked");
+    finish();
+  }
+
+  function installApp() {
+    var request = navigator.mozApps.install(gHostedManifestURL);
+    request.onerror = cbError;
+    request.onsuccess = function() {
+      gApp = request.result;
+      runTest();
+    }
+  }
+
+  function uninstallApp() {
+    // Uninstall the app.
+    var request = navigator.mozApps.mgmt.uninstall(gApp);
+    request.onerror = cbError;
+    request.onsuccess = function() {
+      // All done.
+      info("All done");
+      runTest();
+    }
+  }
+
+  function testApp() {
+    var ifr = document.createElement('iframe');
+    ifr.setAttribute('mozbrowser', 'true');
+    ifr.setAttribute('mozapp', gApp.manifestURL);
+    ifr.setAttribute('src', gApp.manifest.launch_path);
+    var domParent = document.getElementById('container');
+
+    // Set us up to listen for messages from the app.
+    var listener = function(e) {
+      var message = e.detail.message;
+      if (/^OK/.exec(message)) {
+        ok(true, "Message from app: " + message);
+      } else if (/KO/.exec(message)) {
+        ok(false, "Message from app: " + message);
+      } else if (/DONE/.exec(message)) {
+        ok(true, "Messaging from app complete");
+        ifr.removeEventListener('mozbrowsershowmodalprompt', listener);
+        domParent.removeChild(ifr);
+        runTest();
+      }
+    }
+
+    // This event is triggered when the app calls "alert".
+    ifr.addEventListener('mozbrowsershowmodalprompt', listener, false);
+    domParent.appendChild(ifr);
+  }
+
+  var tests = [
+    function() {
+      ok(!("getDataStores" in navigator), "getDataStores should not exist");
+      runTest();
+    },
+
+    // Permissions
+    function() {
+      SpecialPowers.pushPermissions(
+        [{ "type": "browser", "allow": 1, "context": document },
+         { "type": "embed-apps", "allow": 1, "context": document },
+         { "type": "webapps-manage", "allow": 1, "context": document }], runTest);
+    },
+
+    // Preferences
+    function() {
+      SpecialPowers.pushPrefEnv({"set": [["dom.promise.enabled", true],
+                                         ["dom.datastore.enabled", true],
+                                         ["dom.testing.ignore_ipc_principal", true]]}, runTest);
+    },
+
+    function() {
+      SpecialPowers.setAllAppsLaunchable(true);
+      SpecialPowers.setBoolPref("dom.mozBrowserFramesEnabled", true);
+      runTest();
+    },
+
+    // No confirmation needed when an app is installed
+    function() {
+      SpecialPowers.autoConfirmAppInstall(runTest);
+    },
+
+    // Installing the app
+    installApp,
+
+    // Run tests in app
+    testApp,
+
+    // Uninstall the app
+    uninstallApp
+  ];
+
+  function runTest() {
+    if (!tests.length) {
+      finish();
+      return;
+    }
+
+    var test = tests.shift();
+    test();
+  }
+
+  function finish() {
+    SimpleTest.finish();
+  }
+
+  if (SpecialPowers.isMainProcess()) {
+    SpecialPowers.Cu.import("resource://gre/modules/DataStoreChangeNotifier.jsm");
+  }
+
+  SimpleTest.waitForExplicitFinish();
+  runTest();
+  </script>
+</body>
+</html>
--- a/dom/datastore/tests/test_changes.html
+++ b/dom/datastore/tests/test_changes.html
@@ -117,17 +117,18 @@
          { "type": "embed-apps", "allow": 1, "context": document },
          { "type": "webapps-manage", "allow": 1, "context": document }], runTest);
     },
 
     // Preferences
     function() {
       SpecialPowers.pushPrefEnv({"set": [["dom.promise.enabled", true],
                                          ["dom.datastore.enabled", true],
-                                         ["dom.testing.ignore_ipc_principal", true]]}, runTest);
+                                         ["dom.testing.ignore_ipc_principal", true],
+                                         ["dom.testing.datastore_enabled_for_hosted_apps", true]]}, runTest);
     },
 
     // Enabling mozBrowser
     function() {
       SpecialPowers.setAllAppsLaunchable(true);
       SpecialPowers.pushPrefEnv({"set": [["dom.mozBrowserFramesEnabled", true]]}, runTest);
     },
 
--- a/dom/datastore/tests/test_oop.html
+++ b/dom/datastore/tests/test_oop.html
@@ -73,21 +73,19 @@
          { "type": "embed-apps", "allow": 1, "context": document },
          { "type": "webapps-manage", "allow": 1, "context": document }], runTest);
     },
 
     // Preferences
     function() {
       SpecialPowers.pushPrefEnv({"set": [["dom.promise.enabled", true],
                                          ["dom.datastore.enabled", true],
-                                         ["dom.testing.ignore_ipc_principal", true]]}, runTest);
-    },
-
-    function() {
-      SpecialPowers.pushPrefEnv({"set": [["dom.ipc.browser_frames.oop_by_default", true]]}, runTest);
+                                         ["dom.ipc.browser_frames.oop_by_default", true],
+                                         ["dom.testing.ignore_ipc_principal", true],
+                                         ["dom.testing.datastore_enabled_for_hosted_apps", true]]}, runTest);
     },
 
     function() {
       SpecialPowers.setAllAppsLaunchable(true);
       SpecialPowers.setBoolPref("dom.mozBrowserFramesEnabled", true);
       runTest();
     },
 
--- a/dom/datastore/tests/test_readonly.html
+++ b/dom/datastore/tests/test_readonly.html
@@ -97,12 +97,13 @@
 
   if (SpecialPowers.isMainProcess()) {
     SpecialPowers.Cu.import("resource://gre/modules/DataStoreChangeNotifier.jsm");
   }
 
   SimpleTest.waitForExplicitFinish();
   SpecialPowers.pushPrefEnv({"set": [["dom.promise.enabled", true],
                                      ["dom.datastore.enabled", true],
-                                     ["dom.testing.ignore_ipc_principal", true]]}, runTest);
+                                     ["dom.testing.ignore_ipc_principal", true],
+                                     ["dom.testing.datastore_enabled_for_hosted_apps", true]]}, runTest);
   </script>
 </body>
 </html>
--- a/dom/datastore/tests/test_sync.html
+++ b/dom/datastore/tests/test_sync.html
@@ -73,17 +73,18 @@
          { "type": "embed-apps", "allow": 1, "context": document },
          { "type": "webapps-manage", "allow": 1, "context": document }], runTest);
     },
 
     // Preferences
     function() {
       SpecialPowers.pushPrefEnv({"set": [["dom.promise.enabled", true],
                                          ["dom.datastore.enabled", true],
-                                         ["dom.testing.ignore_ipc_principal", true]]}, runTest);
+                                         ["dom.testing.ignore_ipc_principal", true],
+                                         ["dom.testing.datastore_enabled_for_hosted_apps", true]]}, runTest);
     },
 
     function() {
       SpecialPowers.setBoolPref("dom.mozBrowserFramesEnabled", true);
       runTest();
     },
 
     // No confirmation needed when an app is installed
--- a/dom/icc/tests/marionette/manifest.ini
+++ b/dom/icc/tests/marionette/manifest.ini
@@ -2,16 +2,17 @@
 b2g = true
 browser = false
 qemu = true
 
 [test_stk_proactive_command.js]
 [test_icc_contact.js]
 [test_icc_card_lock.js]
 [test_icc_card_state.js]
+[test_icc_info.js]
 [test_stk_refresh.js]
 [test_stk_poll_off.js]
 [test_stk_setup_event_list.js]
 [test_stk_setup_call.js]
 [test_stk_send_ss.js]
 [test_stk_send_ussd.js]
 [test_stk_send_sms.js]
 [test_stk_send_dtmf.js]
--- a/dom/icc/tests/marionette/test_icc_contact.js
+++ b/dom/icc/tests/marionette/test_icc_contact.js
@@ -72,17 +72,17 @@ function testAddContact(type, pin2) {
     getRequest.onerror = function onerror() {
       ok(false, "Cannot get " + type + " contacts: " + getRequest.error.name);
       taskHelper.runNext();
     };
   };
 
   updateRequest.onerror = function onerror() {
     if (type === "fdn" && pin2 === undefined) {
-      ok(updateRequest.error.name === "pin2 is empty",
+      ok(updateRequest.error.name === "SimPin2",
          "expected error when pin2 is not provided");
     } else {
       ok(false, "Cannot add " + type + " contact: " + updateRequest.error.name);
     }
     taskHelper.runNext();
   };
 }
 
--- a/dom/ipc/AppProcessChecker.cpp
+++ b/dom/ipc/AppProcessChecker.cpp
@@ -91,24 +91,32 @@ AssertAppStatus(PBrowserParent* aActor,
   if (!aActor) {
     NS_WARNING("Testing process capability for null actor");
     return false;
   }
 
   TabParent* tab = static_cast<TabParent*>(aActor);
   nsCOMPtr<mozIApplication> app = tab->GetOwnOrContainingApp();
 
+  bool valid = false;
+
   if (app) {
     unsigned short appStatus = 0;
     if (NS_SUCCEEDED(app->GetAppStatus(&appStatus))) {
-      return appStatus == aStatus;
+      valid = appStatus == aStatus;
     }
   }
 
-  return false;
+  if (!valid) {
+    printf_stderr("Security problem: Content process does not have `%d' status.  It will be killed.\n", aStatus);
+    ContentParent* process = tab->Manager();
+    process->KillHard();
+  }
+
+  return valid;
 }
 
 bool
 AssertAppProcess(PContentParent* aActor,
                  AssertAppProcessType aType,
                  const char* aCapability)
 {
   const InfallibleTArray<PBrowserParent*>& browsers =
--- a/dom/ipc/AppProcessChecker.h
+++ b/dom/ipc/AppProcessChecker.h
@@ -25,39 +25,48 @@ class PHalParent;
 
 enum AssertAppProcessType {
   ASSERT_APP_PROCESS_PERMISSION,
   ASSERT_APP_PROCESS_MANIFEST_URL,
   ASSERT_APP_HAS_PERMISSION
 };
 
 /**
- * Return true iff the specified browser has the specified capability.
+ * Return true if the specified browser has the specified capability.
  * If this returns false, the browser didn't have the capability and
  * will be killed.
  */
 bool
 AssertAppProcess(mozilla::dom::PBrowserParent* aActor,
                  AssertAppProcessType aType,
                  const char* aCapability);
 
+/**
+ * Return true if the specified app has the specified status.
+ * If this returns false, the browser will be killed.
+ */
 bool
 AssertAppStatus(mozilla::dom::PBrowserParent* aActor,
                 unsigned short aStatus);
 
 /**
- * Return true iff any of the PBrowsers loaded in this content process
+ * Return true if any of the PBrowsers loaded in this content process
  * has the specified capability.  If this returns false, the process
  * didn't have the capability and will be killed.
  */
 bool
 AssertAppProcess(mozilla::dom::PContentParent* aActor,
                  AssertAppProcessType aType,
                  const char* aCapability);
 
+/**
+ * Return true if any of the PBrowsers loaded in this content process
+ * has an app with the specified status. If this returns false, the process
+ * didn't have the status and will be killed.
+ */
 bool
 AssertAppStatus(mozilla::dom::PContentParent* aActor,
                 unsigned short aStatus);
 
 bool
 AssertAppProcess(mozilla::hal_sandbox::PHalParent* aActor,
                  AssertAppProcessType aType,
                  const char* aCapability);
--- a/dom/ipc/TabChild.cpp
+++ b/dom/ipc/TabChild.cpp
@@ -317,31 +317,35 @@ TabChild::HandleEvent(nsIDOMEvent* aEven
       return NS_ERROR_UNEXPECTED;
 
     nsIScrollableFrame* scrollFrame = nsLayoutUtils::FindScrollableFrameFor(viewId);
     if (!scrollFrame)
       return NS_OK;
 
     CSSIntPoint scrollOffset = scrollFrame->GetScrollPositionCSSPixels();
 
-    if (viewId == mLastMetrics.mScrollId) {
-      // For the root frame, we store the last metrics, including the last
-      // scroll offset, sent by APZC. (This is updated in ProcessUpdateFrame()).
+    if (viewId == mLastRootMetrics.mScrollId) {
+      // We store the last metrics that was sent via the TabParent (This is
+      // updated in ProcessUpdateFrame and RecvUpdateFrame).
       // We use this here to avoid sending APZC back a scroll event that
       // originally came from APZC (besides being unnecessary, the event might
       // be slightly out of date by the time it reaches APZC).
-      // We should probably do this for subframes, too.
-      if (RoundedToInt(mLastMetrics.mScrollOffset) == scrollOffset) {
+      if (RoundedToInt(mLastRootMetrics.mScrollOffset) == scrollOffset) {
         return NS_OK;
       }
 
       // Update the last scroll offset now, otherwise RecvUpdateDimensions()
       // might trigger a scroll to the old offset before RecvUpdateFrame()
       // gets a chance to update it.
-      mLastMetrics.mScrollOffset = scrollOffset;
+      mLastRootMetrics.mScrollOffset = scrollOffset;
+    } else if (viewId == mLastSubFrameMetrics.mScrollId) {
+      if (RoundedToInt(mLastSubFrameMetrics.mScrollOffset) == scrollOffset) {
+        return NS_OK;
+      }
+      mLastSubFrameMetrics.mScrollOffset = scrollOffset;
     }
 
     SendUpdateScrollOffset(presShellId, viewId, scrollOffset);
   }
 
   return NS_OK;
 }
 
@@ -379,31 +383,31 @@ TabChild::Observe(nsISupports *aSubject,
         // Reset CSS viewport and zoom to default on new page, then
         // calculate them properly using the actual metadata from the
         // page.
         SetCSSViewport(kDefaultViewportSize);
 
         // Calculate a really simple resolution that we probably won't
         // be keeping, as well as putting the scroll offset back to
         // the top-left of the page.
-        mLastMetrics.mViewport = CSSRect(CSSPoint(), kDefaultViewportSize);
-        mLastMetrics.mCompositionBounds = ScreenIntRect(ScreenIntPoint(), mInnerSize);
-        mLastMetrics.mZoom = mLastMetrics.CalculateIntrinsicScale();
-        mLastMetrics.mDevPixelsPerCSSPixel = mWidget->GetDefaultScale();
+        mLastRootMetrics.mViewport = CSSRect(CSSPoint(), kDefaultViewportSize);
+        mLastRootMetrics.mCompositionBounds = ScreenIntRect(ScreenIntPoint(), mInnerSize);
+        mLastRootMetrics.mZoom = mLastRootMetrics.CalculateIntrinsicScale();
+        mLastRootMetrics.mDevPixelsPerCSSPixel = mWidget->GetDefaultScale();
         // We use ScreenToLayerScale(1) below in order to turn the
         // async zoom amount into the gecko zoom amount.
-        mLastMetrics.mCumulativeResolution =
-          mLastMetrics.mZoom / mLastMetrics.mDevPixelsPerCSSPixel * ScreenToLayerScale(1);
+        mLastRootMetrics.mCumulativeResolution =
+          mLastRootMetrics.mZoom / mLastRootMetrics.mDevPixelsPerCSSPixel * ScreenToLayerScale(1);
         // This is the root layer, so the cumulative resolution is the same
         // as the resolution.
-        mLastMetrics.mResolution = mLastMetrics.mCumulativeResolution / LayoutDeviceToParentLayerScale(1);
-        mLastMetrics.mScrollOffset = CSSPoint(0, 0);
+        mLastRootMetrics.mResolution = mLastRootMetrics.mCumulativeResolution / LayoutDeviceToParentLayerScale(1);
+        mLastRootMetrics.mScrollOffset = CSSPoint(0, 0);
 
-        utils->SetResolution(mLastMetrics.mResolution.scale,
-                             mLastMetrics.mResolution.scale);
+        utils->SetResolution(mLastRootMetrics.mResolution.scale,
+                             mLastRootMetrics.mResolution.scale);
 
         HandlePossibleViewportChange();
       }
     }
   }
 
   return NS_OK;
 }
@@ -549,17 +553,17 @@ TabChild::HandlePossibleViewportChange()
 
   // We're not being displayed in any way; don't bother doing anything because
   // that will just confuse future adjustments.
   if (!screenW || !screenH) {
     return;
   }
 
   float oldBrowserWidth = mOldViewportWidth;
-  mLastMetrics.mViewport.SizeTo(viewport);
+  mLastRootMetrics.mViewport.SizeTo(viewport);
   if (!oldBrowserWidth) {
     oldBrowserWidth = kDefaultViewportSize.width;
   }
   SetCSSViewport(viewport);
 
   // If this page has not been painted yet, then this must be getting run
   // because a meta-viewport element was added (via the DOMMetaAdded handler).
   // in this case, we should not do anything that forces a reflow (see bug
@@ -595,22 +599,22 @@ TabChild::HandlePossibleViewportChange()
     // For non-HTML content (e.g. SVG), just assume page size == viewport size.
     pageSize = viewport;
   }
   if (!pageSize.width) {
     // Return early rather than divide by 0.
     return;
   }
 
-  float oldScreenWidth = mLastMetrics.mCompositionBounds.width;
+  float oldScreenWidth = mLastRootMetrics.mCompositionBounds.width;
   if (!oldScreenWidth) {
     oldScreenWidth = mInnerSize.width;
   }
 
-  FrameMetrics metrics(mLastMetrics);
+  FrameMetrics metrics(mLastRootMetrics);
   metrics.mViewport = CSSRect(CSSPoint(), viewport);
   metrics.mScrollableRect = CSSRect(CSSPoint(), pageSize);
   metrics.mCompositionBounds = ScreenIntRect(ScreenIntPoint(), mInnerSize);
 
   // This change to the zoom accounts for all types of changes I can conceive:
   // 1. screen size changes, CSS viewport does not (pages with no meta viewport
   //    or a fixed size viewport)
   // 2. screen size changes, CSS viewport also does (pages with a device-width
@@ -1527,24 +1531,25 @@ TabChild::RecvUpdateFrame(const FrameMet
   } else {
     // aFrameMetrics.mIsRoot is false, so we are trying to update a subframe.
     // This requires special handling.
     nsCOMPtr<nsIContent> content = nsLayoutUtils::FindContentFor(
                                       aFrameMetrics.mScrollId);
     if (content) {
       FrameMetrics newSubFrameMetrics(aFrameMetrics);
       APZCCallbackHelper::UpdateSubFrame(content, newSubFrameMetrics);
+      mLastSubFrameMetrics = newSubFrameMetrics;
       return true;
     }
   }
 
   // We've recieved a message that is out of date and we want to ignore.
   // However we can't reply without painting so we reply by painting the
   // exact same thing as we did before.
-  return ProcessUpdateFrame(mLastMetrics);
+  return ProcessUpdateFrame(mLastRootMetrics);
 }
 
 bool
 TabChild::ProcessUpdateFrame(const FrameMetrics& aFrameMetrics)
   {
     if (!mGlobal || !mTabChildGlobal) {
         return true;
     }
@@ -1598,17 +1603,17 @@ TabChild::ProcessUpdateFrame(const Frame
         data.AppendFloat(cssCompositedRect.width);
         data.AppendLiteral(", \"height\" : ");
         data.AppendFloat(cssCompositedRect.height);
         data.AppendLiteral(" }");
     data.AppendLiteral(" }");
 
     DispatchMessageManagerMessage(NS_LITERAL_STRING("Viewport:Change"), data);
 
-    mLastMetrics = newMetrics;
+    mLastRootMetrics = newMetrics;
 
     return true;
 }
 
 bool
 TabChild::RecvHandleDoubleTap(const CSSIntPoint& aPoint)
 {
     if (!mGlobal || !mTabChildGlobal) {
@@ -1627,22 +1632,22 @@ TabChild::RecvHandleDoubleTap(const CSSI
 
 bool
 TabChild::RecvHandleSingleTap(const CSSIntPoint& aPoint)
 {
   if (!mGlobal || !mTabChildGlobal) {
     return true;
   }
 
-  DispatchMouseEvent(NS_LITERAL_STRING("mousemove"), aPoint, 0, 1, 0, false,
-                     nsIDOMMouseEvent::MOZ_SOURCE_TOUCH);
-  DispatchMouseEvent(NS_LITERAL_STRING("mousedown"), aPoint, 0, 1, 0, false,
-                     nsIDOMMouseEvent::MOZ_SOURCE_TOUCH);
-  DispatchMouseEvent(NS_LITERAL_STRING("mouseup"), aPoint, 0, 1, 0, false,
-                     nsIDOMMouseEvent::MOZ_SOURCE_TOUCH);
+  LayoutDevicePoint currentPoint = CSSPoint(aPoint) * mWidget->GetDefaultScale();;
+
+  int time = 0;
+  DispatchSynthesizedMouseEvent(NS_MOUSE_MOVE, time, currentPoint);
+  DispatchSynthesizedMouseEvent(NS_MOUSE_BUTTON_DOWN, time, currentPoint);
+  DispatchSynthesizedMouseEvent(NS_MOUSE_BUTTON_UP, time, currentPoint);
 
   return true;
 }
 
 bool
 TabChild::RecvHandleLongTap(const CSSIntPoint& aPoint)
 {
   if (!mGlobal || !mTabChildGlobal) {
@@ -2183,17 +2188,17 @@ TabChild::InitTabChildGlobal(FrameScript
 
     scope->Init();
 
     nsCOMPtr<nsPIWindowRoot> root = do_QueryInterface(chromeHandler);
     NS_ENSURE_TRUE(root, false);
     root->SetParentTarget(scope);
 
     chromeHandler->AddEventListener(NS_LITERAL_STRING("DOMMetaAdded"), this, false);
-    chromeHandler->AddEventListener(NS_LITERAL_STRING("scroll"), this, false);
+    chromeHandler->AddEventListener(NS_LITERAL_STRING("scroll"), this, true);
   }
 
   if (aScriptLoading != DONT_LOAD_SCRIPTS && !mTriedBrowserInit) {
     mTriedBrowserInit = true;
     // Initialize the child side of the browser element machinery,
     // if appropriate.
     if (IsBrowserOrApp()) {
       RecvLoadRemoteScript(BROWSER_ELEMENT_CHILD_SCRIPT);
--- a/dom/ipc/TabChild.h
+++ b/dom/ipc/TabChild.h
@@ -458,17 +458,18 @@ private:
 
     class CachedFileDescriptorInfo;
     class CachedFileDescriptorCallbackRunnable;
 
     TextureFactoryIdentifier mTextureFactoryIdentifier;
     nsCOMPtr<nsIWebNavigation> mWebNav;
     nsCOMPtr<nsIWidget> mWidget;
     nsCOMPtr<nsIURI> mLastURI;
-    FrameMetrics mLastMetrics;
+    FrameMetrics mLastRootMetrics;
+    FrameMetrics mLastSubFrameMetrics;
     RenderFrameChild* mRemoteFrame;
     nsRefPtr<ContentChild> mManager;
     nsRefPtr<TabChildGlobal> mTabChildGlobal;
     uint32_t mChromeFlags;
     nsIntRect mOuterRect;
     ScreenIntSize mInnerSize;
     // When we're tracking a possible tap gesture, this is the "down"
     // point of the touchstart.
--- a/dom/ipc/TabParent.cpp
+++ b/dom/ipc/TabParent.cpp
@@ -657,16 +657,62 @@ bool TabParent::SendRealMouseEvent(Widge
   WidgetMouseEvent e(event);
   MaybeForwardEventToRenderFrame(event, nullptr, &e);
   if (!MapEventCoordinatesForChildProcess(&e)) {
     return false;
   }
   return PBrowserParent::SendRealMouseEvent(e);
 }
 
+CSSIntPoint TabParent::AdjustTapToChildWidget(const CSSIntPoint& aPoint)
+{
+  nsCOMPtr<nsIContent> content = do_QueryInterface(mFrameElement);
+
+  if (!content || !content->OwnerDoc()) {
+    return aPoint;
+  }
+
+  nsIDocument* doc = content->OwnerDoc();
+  if (!doc || !doc->GetShell()) {
+    return aPoint;
+  }
+  nsPresContext* presContext = doc->GetShell()->GetPresContext();
+
+  return CSSIntPoint(
+    aPoint.x + presContext->DevPixelsToIntCSSPixels(mChildProcessOffsetAtTouchStart.x),
+    aPoint.y + presContext->DevPixelsToIntCSSPixels(mChildProcessOffsetAtTouchStart.y));
+}
+
+bool TabParent::SendHandleSingleTap(const CSSIntPoint& aPoint)
+{
+  if (mIsDestroyed) {
+    return false;
+  }
+
+  return PBrowserParent::SendHandleSingleTap(AdjustTapToChildWidget(aPoint));
+}
+
+bool TabParent::SendHandleLongTap(const CSSIntPoint& aPoint)
+{
+  if (mIsDestroyed) {
+    return false;
+  }
+
+  return PBrowserParent::SendHandleLongTap(AdjustTapToChildWidget(aPoint));
+}
+
+bool TabParent::SendHandleDoubleTap(const CSSIntPoint& aPoint)
+{
+  if (mIsDestroyed) {
+    return false;
+  }
+
+  return PBrowserParent::SendHandleDoubleTap(AdjustTapToChildWidget(aPoint));
+}
+
 bool TabParent::SendMouseWheelEvent(WidgetWheelEvent& event)
 {
   if (mIsDestroyed) {
     return false;
   }
   WidgetWheelEvent e(event);
   MaybeForwardEventToRenderFrame(event, nullptr, &e);
   if (!MapEventCoordinatesForChildProcess(&e)) {
--- a/dom/ipc/TabParent.h
+++ b/dom/ipc/TabParent.h
@@ -208,16 +208,19 @@ public:
                         int32_t aModifiers, bool aIgnoreRootScrollFrame);
     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 CSSIntPoint& aPoint);
+    bool SendHandleLongTap(const CSSIntPoint& aPoint);
+    bool SendHandleDoubleTap(const CSSIntPoint& aPoint);
 
     virtual PDocumentRendererParent*
     AllocPDocumentRendererParent(const nsRect& documentRect, const gfxMatrix& transform,
                                  const nsString& bgcolor,
                                  const uint32_t& renderFlags, const bool& flushLayout,
                                  const nsIntSize& renderSize);
     virtual bool DeallocPDocumentRendererParent(PDocumentRendererParent* actor);
 
@@ -335,16 +338,18 @@ protected:
 
 private:
     already_AddRefed<nsFrameLoader> GetFrameLoader() const;
     already_AddRefed<nsIWidget> GetWidget() const;
     layout::RenderFrameParent* GetRenderFrame();
     nsRefPtr<ContentParent> mManager;
     void TryCacheDPIAndScale();
 
+    CSSIntPoint AdjustTapToChildWidget(const CSSIntPoint& aPoint);
+
     // When true, we create a pan/zoom controller for our frame and
     // notify it of input events targeting us.
     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, |aOutEvent| is the transformed |aEvent| to
     // dispatch to content. |aOutTargetGuid| will contain the identifier
     // of the APZC instance that handled the event. aOutTargetGuid may be
--- a/dom/media/MediaManager.cpp
+++ b/dom/media/MediaManager.cpp
@@ -136,59 +136,44 @@ static nsresult ValidateTrackConstraints
     nsresult rv = CompareDictionaries(aCx, track.mMandatory.Value(),
                                       aNormalized.mMandatory,
                                       aOutUnknownConstraint);
     NS_ENSURE_SUCCESS(rv, rv);
   }
   return NS_OK;
 }
 
-/**
- * Send an error back to content. The error is the form a string.
- * Do this only on the main thread. The success callback is also passed here
- * so it can be released correctly.
- */
-class ErrorCallbackRunnable : public nsRunnable
+ErrorCallbackRunnable::ErrorCallbackRunnable(
+  already_AddRefed<nsIDOMGetUserMediaSuccessCallback> aSuccess,
+  already_AddRefed<nsIDOMGetUserMediaErrorCallback> aError,
+  const nsAString& aErrorMsg, uint64_t aWindowID)
+  : mSuccess(aSuccess)
+  , mError(aError)
+  , mErrorMsg(aErrorMsg)
+  , mWindowID(aWindowID)
+  , mManager(MediaManager::GetInstance()) {
+  }
+
+NS_IMETHODIMP
+ErrorCallbackRunnable::Run()
 {
-public:
-  ErrorCallbackRunnable(
-    already_AddRefed<nsIDOMGetUserMediaSuccessCallback> aSuccess,
-    already_AddRefed<nsIDOMGetUserMediaErrorCallback> aError,
-    const nsAString& aErrorMsg, uint64_t aWindowID)
-    : mSuccess(aSuccess)
-    , mError(aError)
-    , mErrorMsg(aErrorMsg)
-    , mWindowID(aWindowID)
-    , mManager(MediaManager::GetInstance()) {}
+  // Only run if the window is still active.
+  NS_ASSERTION(NS_IsMainThread(), "Only call on main thread");
 
-  NS_IMETHOD
-  Run()
-  {
-    // Only run if the window is still active.
-    NS_ASSERTION(NS_IsMainThread(), "Only call on main thread");
+  nsCOMPtr<nsIDOMGetUserMediaSuccessCallback> success(mSuccess);
+  nsCOMPtr<nsIDOMGetUserMediaErrorCallback> error(mError);
 
-    nsCOMPtr<nsIDOMGetUserMediaSuccessCallback> success(mSuccess);
-    nsCOMPtr<nsIDOMGetUserMediaErrorCallback> error(mError);
-
-    if (!(mManager->IsWindowStillActive(mWindowID))) {
-      return NS_OK;
-    }
-    // This is safe since we're on main-thread, and the windowlist can only
-    // be invalidated from the main-thread (see OnNavigation)
-    error->OnError(mErrorMsg);
+  if (!(mManager->IsWindowStillActive(mWindowID))) {
     return NS_OK;
   }
-
-private:
-  already_AddRefed<nsIDOMGetUserMediaSuccessCallback> mSuccess;
-  already_AddRefed<nsIDOMGetUserMediaErrorCallback> mError;
-  const nsString mErrorMsg;
-  uint64_t mWindowID;
-  nsRefPtr<MediaManager> mManager; // get ref to this when creating the runnable
-};
+  // This is safe since we're on main-thread, and the windowlist can only
+  // be invalidated from the main-thread (see OnNavigation)
+  error->OnError(mErrorMsg);
+  return NS_OK;
+}
 
 /**
  * Invoke the "onSuccess" callback in content. The callback will take a
  * DOMBlob in the case of {picture:true}, and a MediaStream in the case of
  * {audio:true} or {video:true}. There is a constructor available for each
  * form. Do this only on the main thread.
  */
 class SuccessCallbackRunnable : public nsRunnable
@@ -577,28 +562,30 @@ public:
     trackunion->CombineWithPrincipal(window->GetExtantDoc()->NodePrincipal());
 
     // The listener was added at the begining in an inactive state.
     // Activate our listener. We'll call Start() on the source when get a callback
     // that the MediaStream has started consuming. The listener is freed
     // when the page is invalidated (on navigation or close).
     mListener->Activate(stream.forget(), mAudioSource, mVideoSource);
 
+    // Note: includes JS callbacks; must be released on MainThread
     TracksAvailableCallback* tracksAvailableCallback =
       new TracksAvailableCallback(mManager, mSuccess, mWindowID, trackunion);
 
     // Dispatch to the media thread to ask it to start the sources,
     // because that can take a while.
     // Pass ownership of trackunion to the MediaOperationRunnable
     // to ensure it's kept alive until the MediaOperationRunnable runs (at least).
     nsIThread *mediaThread = MediaManager::GetThread();
     nsRefPtr<MediaOperationRunnable> runnable(
       new MediaOperationRunnable(MEDIA_START, mListener, trackunion,
                                  tracksAvailableCallback,
-                                 mAudioSource, mVideoSource, false, mWindowID));
+                                 mAudioSource, mVideoSource, false, mWindowID,
+                                 mError.forget()));
     mediaThread->Dispatch(runnable, NS_DISPATCH_NORMAL);
 
 #ifdef MOZ_WEBRTC
     // Right now these configs are only of use if webrtc is available
     nsresult rv;
     nsCOMPtr<nsIPrefService> prefs = do_GetService("@mozilla.org/preferences-service;1", &rv);
     if (NS_SUCCEEDED(rv)) {
       nsCOMPtr<nsIPrefBranch> branch = do_QueryInterface(prefs);
@@ -1798,17 +1785,17 @@ GetUserMediaCallbackMediaStreamListener:
   nsRefPtr<MediaOperationRunnable> runnable;
   // We can't take a chance on blocking here, so proxy this to another
   // thread.
   // Pass a ref to us (which is threadsafe) so it can query us for the
   // source stream info.
   runnable = new MediaOperationRunnable(MEDIA_STOP,
                                         this, nullptr, nullptr,
                                         mAudioSource, mVideoSource,
-                                        mFinished, mWindowID);
+                                        mFinished, mWindowID, nullptr);
   mMediaThread->Dispatch(runnable, NS_DISPATCH_NORMAL);
 }
 
 // Called from the MediaStreamGraph thread
 void
 GetUserMediaCallbackMediaStreamListener::NotifyFinished(MediaStreamGraph* aGraph)
 {
   mFinished = true;
--- a/dom/media/MediaManager.h
+++ b/dom/media/MediaManager.h
@@ -207,71 +207,125 @@ class GetUserMediaNotificationEvent: pub
                                   GetUserMediaStatus aStatus,
                                   bool aIsAudio, bool aIsVideo, uint64_t aWindowID)
     : mListener(aListener) , mStatus(aStatus) , mIsAudio(aIsAudio)
     , mIsVideo(aIsVideo), mWindowID(aWindowID) {}
 
     GetUserMediaNotificationEvent(GetUserMediaStatus aStatus,
                                   already_AddRefed<DOMMediaStream> aStream,
                                   DOMMediaStream::OnTracksAvailableCallback* aOnTracksAvailableCallback,
-                                  bool aIsAudio, bool aIsVideo, uint64_t aWindowID)
+                                  bool aIsAudio, bool aIsVideo, uint64_t aWindowID,
+                                  already_AddRefed<nsIDOMGetUserMediaErrorCallback> aError)
     : mStream(aStream), mOnTracksAvailableCallback(aOnTracksAvailableCallback),
-      mStatus(aStatus), mIsAudio(aIsAudio), mIsVideo(aIsVideo), mWindowID(aWindowID) {}
+      mStatus(aStatus), mIsAudio(aIsAudio), mIsVideo(aIsVideo), mWindowID(aWindowID),
+      mError(aError) {}
     virtual ~GetUserMediaNotificationEvent()
     {
 
     }
 
     NS_IMETHOD Run() MOZ_OVERRIDE;
 
   protected:
     nsRefPtr<GetUserMediaCallbackMediaStreamListener> mListener; // threadsafe
     nsRefPtr<DOMMediaStream> mStream;
     nsAutoPtr<DOMMediaStream::OnTracksAvailableCallback> mOnTracksAvailableCallback;
     GetUserMediaStatus mStatus;
     bool mIsAudio;
     bool mIsVideo;
     uint64_t mWindowID;
+    nsRefPtr<nsIDOMGetUserMediaErrorCallback> mError;
 };
 
 typedef enum {
   MEDIA_START,
   MEDIA_STOP
 } MediaOperation;
 
+class MediaManager;
+
+/**
+ * Send an error back to content. The error is the form a string.
+ * Do this only on the main thread. The success callback is also passed here
+ * so it can be released correctly.
+ */
+class ErrorCallbackRunnable : public nsRunnable
+{
+public:
+  ErrorCallbackRunnable(
+    already_AddRefed<nsIDOMGetUserMediaSuccessCallback> aSuccess,
+    already_AddRefed<nsIDOMGetUserMediaErrorCallback> aError,
+    const nsAString& aErrorMsg, uint64_t aWindowID);
+  NS_IMETHOD Run();
+private:
+  already_AddRefed<nsIDOMGetUserMediaSuccessCallback> mSuccess;
+  already_AddRefed<nsIDOMGetUserMediaErrorCallback> mError;
+  const nsString mErrorMsg;
+  uint64_t mWindowID;
+  nsRefPtr<MediaManager> mManager; // get ref to this when creating the runnable
+};
+
+class ReleaseMediaOperationResource : public nsRunnable
+{
+public:
+  ReleaseMediaOperationResource(already_AddRefed<DOMMediaStream> aStream,
+    DOMMediaStream::OnTracksAvailableCallback* aOnTracksAvailableCallback):
+    mStream(aStream),
+    mOnTracksAvailableCallback(aOnTracksAvailableCallback) {}
+  NS_IMETHOD Run() MOZ_OVERRIDE {return NS_OK;}
+private:
+  nsRefPtr<DOMMediaStream> mStream;
+  nsAutoPtr<DOMMediaStream::OnTracksAvailableCallback> mOnTracksAvailableCallback;
+};
+
 // Generic class for running long media operations like Start off the main
 // thread, and then (because nsDOMMediaStreams aren't threadsafe),
 // ProxyReleases mStream since it's cycle collected.
 class MediaOperationRunnable : public nsRunnable
 {
 public:
   // so we can send Stop without AddRef()ing from the MSG thread
   MediaOperationRunnable(MediaOperation aType,
     GetUserMediaCallbackMediaStreamListener* aListener,
     DOMMediaStream* aStream,
     DOMMediaStream::OnTracksAvailableCallback* aOnTracksAvailableCallback,
     MediaEngineSource* aAudioSource,
     MediaEngineSource* aVideoSource,
     bool aNeedsFinish,
-    uint64_t aWindowID)
+    uint64_t aWindowID,
+    already_AddRefed<nsIDOMGetUserMediaErrorCallback> aError)
     : mType(aType)
     , mStream(aStream)
     , mOnTracksAvailableCallback(aOnTracksAvailableCallback)
     , mAudioSource(aAudioSource)
     , mVideoSource(aVideoSource)
     , mListener(aListener)
     , mFinish(aNeedsFinish)
     , mWindowID(aWindowID)
-    {}
+    , mError(aError)
+  {}
 
   ~MediaOperationRunnable()
   {
     // MediaStreams can be released on any thread.
   }
 
+  nsresult returnAndCallbackError(nsresult rv, const char* errorLog)
+  {
+    MM_LOG(("%s , rv=%d", errorLog, rv));
+    NS_DispatchToMainThread(new ReleaseMediaOperationResource(mStream.forget(),
+          mOnTracksAvailableCallback.forget()));
+    nsString log;
+
+    log.AssignASCII(errorLog, strlen(errorLog));
+    NS_DispatchToMainThread(new ErrorCallbackRunnable(nullptr, mError.forget(),
+      log, mWindowID));
+    return NS_OK;
+  }
+
   NS_IMETHOD
   Run() MOZ_OVERRIDE
   {
     SourceMediaStream *source = mListener->GetSourceStream();
     // No locking between these is required as all the callbacks for the
     // same MediaStream will occur on the same thread.
     if (!source) // means the stream was never Activated()
       return NS_OK;
@@ -285,41 +339,43 @@ public:
           source->SetPullEnabled(true);
 
           DOMMediaStream::TrackTypeHints expectedTracks = 0;
           if (mAudioSource) {
             rv = mAudioSource->Start(source, kAudioTrack);
             if (NS_SUCCEEDED(rv)) {
               expectedTracks |= DOMMediaStream::HINT_CONTENTS_AUDIO;
             } else {
-              MM_LOG(("Starting audio failed, rv=%d",rv));
+              return returnAndCallbackError(rv, "Starting audio failed");
             }
           }
           if (mVideoSource) {
             rv = mVideoSource->Start(source, kVideoTrack);
             if (NS_SUCCEEDED(rv)) {
               expectedTracks |= DOMMediaStream::HINT_CONTENTS_VIDEO;
             } else {
-              MM_LOG(("Starting video failed, rv=%d",rv));
+              return returnAndCallbackError(rv, "Starting video failed");
             }
           }
 
           mOnTracksAvailableCallback->SetExpectedTracks(expectedTracks);
 
           MM_LOG(("started all sources"));
           // Forward mOnTracksAvailableCallback to GetUserMediaNotificationEvent,
           // because mOnTracksAvailableCallback needs to be added to mStream
           // on the main thread.
-          nsRefPtr<GetUserMediaNotificationEvent> event =
+          nsIRunnable *event =
             new GetUserMediaNotificationEvent(GetUserMediaNotificationEvent::STARTING,
                                               mStream.forget(),
                                               mOnTracksAvailableCallback.forget(),
                                               mAudioSource != nullptr,
                                               mVideoSource != nullptr,
-                                              mWindowID);
+                                              mWindowID, mError.forget());
+          // event must always be released on mainthread due to the JS callbacks
+          // in the TracksAvailableCallback
           NS_DispatchToMainThread(event, NS_DISPATCH_NORMAL);
         }
         break;
 
       case MEDIA_STOP:
         {
           NS_ASSERTION(!NS_IsMainThread(), "Never call on main thread");
           if (mAudioSource) {
@@ -329,23 +385,24 @@ public:
           if (mVideoSource) {
             mVideoSource->Stop(source, kVideoTrack);
             mVideoSource->Deallocate();
           }
           // Do this after stopping all tracks with EndTrack()
           if (mFinish) {
             source->Finish();
           }
-          nsRefPtr<GetUserMediaNotificationEvent> event =
+          nsIRunnable *event =
             new GetUserMediaNotificationEvent(mListener,
                                               GetUserMediaNotificationEvent::STOPPING,
                                               mAudioSource != nullptr,
                                               mVideoSource != nullptr,
                                               mWindowID);
-
+          // event must always be released on mainthread due to the JS callbacks
+          // in the TracksAvailableCallback
           NS_DispatchToMainThread(event, NS_DISPATCH_NORMAL);
         }
         break;
 
       default:
         MOZ_ASSERT(false,"invalid MediaManager operation");
         break;
     }
@@ -356,16 +413,17 @@ private:
   MediaOperation mType;
   nsRefPtr<DOMMediaStream> mStream;
   nsAutoPtr<DOMMediaStream::OnTracksAvailableCallback> mOnTracksAvailableCallback;
   nsRefPtr<MediaEngineSource> mAudioSource; // threadsafe
   nsRefPtr<MediaEngineSource> mVideoSource; // threadsafe
   nsRefPtr<GetUserMediaCallbackMediaStreamListener> mListener; // threadsafe
   bool mFinish;
   uint64_t mWindowID;
+  nsRefPtr<nsIDOMGetUserMediaErrorCallback> mError;
 };
 
 typedef nsTArray<nsRefPtr<GetUserMediaCallbackMediaStreamListener> > StreamListeners;
 typedef nsClassHashtable<nsUint64HashKey, StreamListeners> WindowTable;
 
 class MediaDevice : public nsIMediaDevice
 {
 public:
--- a/dom/media/PeerConnection.js
+++ b/dom/media/PeerConnection.js
@@ -1046,27 +1046,28 @@ PeerConnectionObserver.prototype = {
   //   disconnected  Liveness checks have failed for one or more components.
   //                 This is more aggressive than failed, and may trigger
   //                 intermittently (and resolve itself without action) on a
   //                 flaky network.
   //
   //   closed        The ICE Agent has shut down and is no longer responding to
   //                 STUN requests.
 
-  handleIceConnectionStateChange: function(iceState) {
+  handleIceConnectionStateChange: function(iceConnectionState) {
     var histogram = Services.telemetry.getHistogramById("WEBRTC_ICE_SUCCESS_RATE");
 
-    if (iceState === 'failed') {
+    if (iceConnectionState === 'failed') {
       histogram.add(false);
     }
     if (this._dompc.iceConnectionState === 'checking' &&
-        (iceState === 'completed' || iceState === 'connected')) {
+        (iceConnectionState === 'completed' ||
+         iceConnectionState === 'connected')) {
           histogram.add(true);
     }
-    this._dompc.changeIceConnectionState(iceState);
+    this._dompc.changeIceConnectionState(iceConnectionState);
   },
 
   // This method is responsible for updating iceGatheringState. This
   // state is defined in the WebRTC specification as follows:
   //
   // iceGatheringState:
   // ------------------
   //   new        The object was just created, and no networking has occurred
@@ -1100,17 +1101,17 @@ PeerConnectionObserver.prototype = {
   onStateChange: function(state) {
     switch (state) {
       case "SignalingState":
         this.callCB(this._dompc.onsignalingstatechange,
                     this._dompc.signalingState);
         break;
 
       case "IceConnectionState":
-        this.handleIceConnectionStateChange(this._dompc._pc.iceState);
+        this.handleIceConnectionStateChange(this._dompc._pc.iceConnectionState);
         break;
 
       case "IceGatheringState":
         this.handleIceGatheringStateChange(this._dompc._pc.iceGatheringState);
         break;
 
       case "SdpState":
         // No-op
--- a/dom/media/tests/mochitest/pc.js
+++ b/dom/media/tests/mochitest/pc.js
@@ -963,31 +963,41 @@ function PeerConnectionWrapper(label, co
   this.offerConstraints = {};
   this.streams = [ ];
   this.mediaCheckers = [ ];
 
   this.dataChannels = [ ];
 
   info("Creating " + this);
   this._pc = new mozRTCPeerConnection(this.configuration);
+  is(this._pc.iceConnectionState, "new", "iceConnectionState starts at 'new'");
 
   /**
    * Setup callback handlers
    */
-
+  var self = this;
+  // This enables tests to validate that the next ice state is the one they expect to happen
+  this.next_ice_state = ""; // in most cases, the next state will be "checking", but in some tests "closed"
+  this._pc.oniceconnectionstatechange = function() {
+      ok(self._pc.iceConnectionState != undefined, "iceConnectionState should not be undefined");
+      if (self.next_ice_state != "") {
+        is(self._pc.iceConnectionState, self.next_ice_state, "iceConnectionState changed to '" +
+           self.next_ice_state + "'");
+        self.next_ice_state = "";
+      }
+  };
   this.ondatachannel = unexpectedEventAndFinish(this, 'ondatachannel');
   this.onsignalingstatechange = unexpectedEventAndFinish(this, 'onsignalingstatechange');
 
   /**
    * Callback for native peer connection 'onaddstream' events.
    *
    * @param {Object} event
    *        Event data which includes the stream to be added
    */
-  var self = this;
   this._pc.onaddstream = function (event) {
     info(self + ": 'onaddstream' event fired for " + event.stream);
 
     // TODO: Bug 834835 - Assume type is video until we get get{Audio,Video}Tracks.
     self.attachMedia(event.stream, 'video', 'remote');
    };
 
   /**
--- a/dom/mobilemessage/interfaces/nsIDOMMobileMessageManager.idl
+++ b/dom/mobilemessage/interfaces/nsIDOMMobileMessageManager.idl
@@ -6,17 +6,17 @@
 
 interface nsIDOMEventListener;
 interface nsIDOMMozSmsFilter;
 interface nsIDOMMozSmsSegmentInfo;
 interface nsIDOMDOMCursor;
 interface nsIDOMDOMRequest;
 interface nsIDOMBlob;
 
-[scriptable, builtinclass, uuid(a99c3538-a8d6-492f-9ece-f6e92f9c00c5)]
+[scriptable, builtinclass, uuid(8ec8247d-3f5f-41af-9c72-9dc857e3be81)]
 interface nsIDOMMozMobileMessageManager : nsIDOMEventTarget
 {
   nsIDOMDOMRequest getSegmentInfoForText(in DOMString text);
 
 
   /**
    * Function to send SMS.
    *
@@ -47,16 +47,17 @@ interface nsIDOMMozMobileMessageManager 
   [implicit_jscontext, optional_argc]
   nsIDOMDOMRequest sendMMS(in jsval parameters,
                            [optional] in jsval sendParameters);
 
   [binaryname(GetMessageMoz)]
   nsIDOMDOMRequest getMessage(in long id);
 
   // The parameter can be either a message id or a nsIDOMMoz{Mms,Sms}Message.
+  [implicit_jscontext]
   nsIDOMDOMRequest delete(in jsval param);
 
   // Iterates through nsIDOMMoz{Mms,Sms}Message.
   nsIDOMDOMCursor getMessages(in nsIDOMMozSmsFilter filter, in boolean reverse);
 
   nsIDOMDOMRequest markMessageRead(in long id, in boolean value,
                                    [optional] in boolean aSendReadReport);
 
--- a/dom/mobilemessage/src/MobileMessageManager.cpp
+++ b/dom/mobilemessage/src/MobileMessageManager.cpp
@@ -176,20 +176,23 @@ MobileMessageManager::Send(const JS::Val
     return NS_ERROR_INVALID_ARG;
   }
 
   nsresult rv;
   nsIScriptContext* sc = GetContextForEventHandlers(&rv);
   NS_ENSURE_SUCCESS(rv, rv);
   NS_ENSURE_STATE(sc);
 
-  JS::Rooted<JSObject*> global(aCx, sc->GetWindowProxy());
-  NS_ASSERTION(global, "Failed to get global object!");
+  JS::Rooted<JSObject*> global(aCx, JS::CurrentGlobalOrNull(aCx));
 
-  JSAutoCompartment ac(aCx, global);
+  mozilla::Maybe<JSAutoCompartment> ac;
+  if (!global) {
+    global = sc->GetWindowProxy();
+    ac.construct(aCx, global);
+  }
 
   nsCOMPtr<nsISmsService> smsService = do_GetService(SMS_SERVICE_CONTRACTID);
   NS_ENSURE_TRUE(smsService, NS_ERROR_FAILURE);
 
   // Use the default one unless |aSendParams.serviceId| is available.
   uint32_t serviceId;
   rv = smsService->GetSmsDefaultServiceId(&serviceId);
   NS_ENSURE_SUCCESS(rv, rv);
@@ -296,80 +299,77 @@ MobileMessageManager::GetMessageMoz(int3
   nsresult rv = mobileMessageDBService->GetMessageMoz(aId, msgCallback);
   NS_ENSURE_SUCCESS(rv, rv);
 
   request.forget(aRequest);
   return NS_OK;
 }
 
 nsresult
-MobileMessageManager::GetMessageId(AutoPushJSContext &aCx,
-                                   const JS::Value &aMessage, int32_t &aId)
+MobileMessageManager::GetMessageId(JSContext* aCx,
+                                   const JS::Value& aMessage, int32_t* aId)
 {
   nsCOMPtr<nsIDOMMozSmsMessage> smsMessage =
     do_QueryInterface(nsContentUtils::XPConnect()->GetNativeOfWrapper(aCx, &aMessage.toObject()));
   if (smsMessage) {
-    return smsMessage->GetId(&aId);
+    return smsMessage->GetId(aId);
   }
 
   nsCOMPtr<nsIDOMMozMmsMessage> mmsMessage =
     do_QueryInterface(nsContentUtils::XPConnect()->GetNativeOfWrapper(aCx, &aMessage.toObject()));
   if (mmsMessage) {
-    return mmsMessage->GetId(&aId);
+    return mmsMessage->GetId(aId);
   }
 
   return NS_ERROR_INVALID_ARG;
 }
 
 NS_IMETHODIMP
-MobileMessageManager::Delete(const JS::Value& aParam, nsIDOMDOMRequest** aRequest)
+MobileMessageManager::Delete(const JS::Value& aParam, JSContext* aCx,
+                             nsIDOMDOMRequest** aRequest)
 {
   // We expect Int32, SmsMessage, MmsMessage, Int32[], SmsMessage[], MmsMessage[]
   if (!aParam.isObject() && !aParam.isInt32()) {
     return NS_ERROR_INVALID_ARG;
   }
 
-  nsresult rv;
-  nsIScriptContext* sc = GetContextForEventHandlers(&rv);
-  AutoPushJSContext cx(sc->GetNativeContext());
-  NS_ENSURE_STATE(sc);
-
+  nsresult rv = NS_OK;
   int32_t id, *idArray;
   uint32_t size;
 
   if (aParam.isInt32()) {
     // Single Integer Message ID
     id = aParam.toInt32();
 
     size = 1;
     idArray = &id;
-  } else if (!JS_IsArrayObject(cx, &aParam.toObject())) {
+  } else if (!JS_IsArrayObject(aCx, &aParam.toObject())) {
     // Single SmsMessage/MmsMessage object
-    rv = GetMessageId(cx, aParam, id);
+    rv = GetMessageId(aCx, aParam, &id);
     NS_ENSURE_SUCCESS(rv, rv);
 
     size = 1;
     idArray = &id;
   } else {
     // Int32[], SmsMessage[], or MmsMessage[]
-    JS::Rooted<JSObject*> ids(cx, &aParam.toObject());
+    JS::Rooted<JSObject*> ids(aCx, &aParam.toObject());
 
-    JS_ALWAYS_TRUE(JS_GetArrayLength(cx, ids, &size));
+    JS_ALWAYS_TRUE(JS_GetArrayLength(aCx, ids, &size));
     nsAutoArrayPtr<int32_t> idAutoArray(new int32_t[size]);
 
-    JS::Rooted<JS::Value> idJsValue(cx);
+    JS::Rooted<JS::Value> idJsValue(aCx);
     for (uint32_t i = 0; i < size; i++) {
-      if (!JS_GetElement(cx, ids, i, &idJsValue)) {
+      if (!JS_GetElement(aCx, ids, i, &idJsValue)) {
         return NS_ERROR_INVALID_ARG;
       }
 
       if (idJsValue.isInt32()) {
         idAutoArray[i] = idJsValue.toInt32();
       } else if (idJsValue.isObject()) {
-        rv = GetMessageId(cx, idJsValue, id);
+        rv = GetMessageId(aCx, idJsValue, &id);
         NS_ENSURE_SUCCESS(rv, rv);
 
         idAutoArray[i] = id;
       }
     }
 
     idArray = idAutoArray.forget();
   }
--- a/dom/mobilemessage/src/MobileMessageManager.h
+++ b/dom/mobilemessage/src/MobileMessageManager.h
@@ -42,16 +42,16 @@ private:
 
   nsresult DispatchTrustedSmsEventToSelf(const char* aTopic,
                                          const nsAString& aEventName,
                                          nsISupports* aMsg);
 
   /**
    * Helper to get message ID from SMS/MMS Message object
    */
-  nsresult GetMessageId(AutoPushJSContext &aCx, const JS::Value &aMessage,
-                        int32_t &aId);
+  nsresult GetMessageId(JSContext* aCx, const JS::Value& aMessage,
+                        int32_t* aId);
 };
 
 } // namespace dom
 } // namespace mozilla
 
 #endif // mozilla_dom_mobilemessage_MobileMessageManager_h
--- a/dom/network/tests/marionette/manifest.ini
+++ b/dom/network/tests/marionette/manifest.ini
@@ -1,17 +1,16 @@
 [DEFAULT]
 b2g = true
 browser = false
 qemu = true
 
 [test_mobile_networks.js]
 disabled = Bug 808783
 [test_mobile_voice_state.js]
-[test_mobile_iccinfo.js]
 [test_mobile_operator_names.js]
 [test_mobile_preferred_network_type.js]
 disabled = Bug 808783
 [test_mobile_data_connection.js]
 [test_mobile_data_location.js]
 [test_mobile_data_state.js]
 [test_mobile_mmi.js]
 [test_mobile_roaming_preference.js]
--- a/dom/src/notification/Notification.cpp
+++ b/dom/src/notification/Notification.cpp
@@ -729,19 +729,19 @@ Notification::WrapObject(JSContext* aCx,
 {
   return mozilla::dom::NotificationBinding::Wrap(aCx, aScope, this);
 }
 
 void
 Notification::Close()
 {
   // Queue a task to close the notification.
-  nsCOMPtr<nsIRunnable> showNotificationTask =
+  nsCOMPtr<nsIRunnable> closeNotificationTask =
     new NotificationTask(this, NotificationTask::eClose);
-  NS_DispatchToMainThread(showNotificationTask);
+  NS_DispatchToMainThread(closeNotificationTask);
 }
 
 void
 Notification::CloseInternal()
 {
   if (!mIsClosed) {
     nsresult rv;
     // Don't bail out if notification storage fails, since we still
@@ -766,17 +766,19 @@ Notification::CloseInternal()
       }
     }
   }
 }
 
 nsresult
 Notification::GetOrigin(nsPIDOMWindow* aWindow, nsString& aOrigin)
 {
-  MOZ_ASSERT(aWindow);
+  if (!aWindow) {
+    return NS_ERROR_FAILURE;
+  }
   nsresult rv;
   nsIDocument* doc = aWindow->GetExtantDoc();
   NS_ENSURE_TRUE(doc, NS_ERROR_UNEXPECTED);
   nsIPrincipal* principal = doc->NodePrincipal();
   NS_ENSURE_TRUE(principal, NS_ERROR_UNEXPECTED);
 
   uint16_t appStatus = principal->GetAppStatus();
   uint32_t appId = principal->GetAppId();
--- a/dom/system/gonk/RadioInterfaceLayer.js
+++ b/dom/system/gonk/RadioInterfaceLayer.js
@@ -1216,17 +1216,18 @@ RadioInterface.prototype = {
         break;
       case "cardstatechange":
         this.rilContext.cardState = message.cardState;
         gMessageManager.sendIccMessage("RIL:CardStateChanged",
                                        this.clientId, message);
         break;
       case "sms-received":
         let ackOk = this.handleSmsReceived(message);
-        if (ackOk) {
+        // Note: ACK has been done by modem for NEW_SMS_ON_SIM
+        if (ackOk && message.simStatus === undefined) {
           this.workerMessenger.send("ackSMS", { result: RIL.PDU_FCS_OK });
         }
         return;
       case "broadcastsms-received":
       case "cellbroadcast-received":
         message.timestamp = Date.now();
         gMessageManager.sendCellBroadcastMessage("RIL:CellBroadcastReceived",
                                                  this.clientId, message);
@@ -2057,20 +2058,23 @@ RadioInterface.prototype = {
                                            this.clientId, mwi);
       return true;
     }
 
     let notifyReceived = function notifyReceived(rv, domMessage) {
       let success = Components.isSuccessCode(rv);
 
       // Acknowledge the reception of the SMS.
-      this.workerMessenger.send("ackSMS", {
-        result: (success ? RIL.PDU_FCS_OK
-                         : RIL.PDU_FCS_MEMORY_CAPACITY_EXCEEDED)
-      });
+      // Note: Ack has been done by modem for NEW_SMS_ON_SIM
+      if (message.simStatus === undefined) {
+        this.workerMessenger.send("ackSMS", {
+          result: (success ? RIL.PDU_FCS_OK
+                           : RIL.PDU_FCS_MEMORY_CAPACITY_EXCEEDED)
+        });
+      }
 
       if (!success) {
         // At this point we could send a message to content to notify the user
         // that storing an incoming SMS failed, most likely due to a full disk.
         if (DEBUG) {
           this.debug("Could not store SMS, error code " + rv);
         }
         return;
--- a/dom/system/gonk/ril_consts.js
+++ b/dom/system/gonk/ril_consts.js
@@ -662,16 +662,23 @@ this.USIM_TAG_NAME[ICC_USIM_EFANR_TAG] =
 this.USIM_TAG_NAME[ICC_USIM_EFPBC_TAG] = "pbc";
 this.USIM_TAG_NAME[ICC_USIM_EFGRP_TAG] = "grp";
 this.USIM_TAG_NAME[ICC_USIM_EFAAS_TAG] = "aas";
 this.USIM_TAG_NAME[ICC_USIM_EFGSD_TAG] = "gsd";
 this.USIM_TAG_NAME[ICC_USIM_EFUID_TAG] = "uid";
 this.USIM_TAG_NAME[ICC_USIM_EFEMAIL_TAG] = "email";
 this.USIM_TAG_NAME[ICC_USIM_EFCCP1_TAG] = "ccp1";
 
+// Error message for ICC contact.
+this.CONTACT_ERR_REQUEST_NOT_SUPPORTED = GECKO_ERROR_REQUEST_NOT_SUPPORTED;
+this.CONTACT_ERR_CONTACT_TYPE_NOT_SUPPORTED = "ContactTypeNotSupported";
+this.CONTACT_ERR_FIELD_NOT_SUPPORTED = "FieldNotSupported";
+this.CONTACT_ERR_NO_FREE_RECORD_FOUND = "NoFreeRecordFound";
+this.CONTACT_ERR_CANNOT_ACCESS_PHONEBOOK = "CannotAccessPhoneBook";
+
 // CDMA IMSI_M's byte const.
 // 3GPP2 C.S0065 Sec. 5.2.2
 this.CSIM_IMSI_M_MIN2_BYTE = 1;
 this.CSIM_IMSI_M_MIN1_BYTE = 3;
 this.CSIM_IMSI_M_MNC_BYTE = 6;
 this.CSIM_IMSI_M_PROGRAMMED_BYTE = 7;
 this.CSIM_IMSI_M_MCC_BYTE = 8;
 
--- a/dom/system/gonk/ril_worker.js
+++ b/dom/system/gonk/ril_worker.js
@@ -846,17 +846,17 @@ let RIL = {
    *
    * @param contactType
    *        "adn" or "fdn".
    * @param requestId
    *        Request id from RadioInterfaceLayer.
    */
   readICCContacts: function readICCContacts(options) {
     if (!this.appType) {
-      options.errorMsg = GECKO_ERROR_REQUEST_NOT_SUPPORTED;
+      options.errorMsg = CONTACT_ERR_REQUEST_NOT_SUPPORTED;
       this.sendChromeMessage(options);
       return;
     }
 
     ICCContactHelper.readICCContacts(
       this.appType,
       options.contactType,
       function onsuccess(contacts) {
@@ -894,17 +894,17 @@ let RIL = {
     }.bind(this);
 
     let onerror = function onerror(errorMsg) {
       options.errorMsg = errorMsg;
       RIL.sendChromeMessage(options);
     }.bind(this);
 
     if (!this.appType || !options.contact) {
-      onerror(GECKO_ERROR_REQUEST_NOT_SUPPORTED);
+      onerror(CONTACT_ERR_REQUEST_NOT_SUPPORTED );
       return;
     }
 
     let contact = options.contact;
     let iccid = RIL.iccInfo.iccid;
     let isValidRecordId = false;
     if (typeof contact.contactId === "string" &&
         contact.contactId.startsWith(iccid)) {
@@ -6088,18 +6088,30 @@ RIL[UNSOLICITED_RESPONSE_NEW_SMS] = func
   // Not reserved FCS values, send ACK now.
   this.acknowledgeGsmSms(result == PDU_FCS_OK, result);
 };
 RIL[UNSOLICITED_RESPONSE_NEW_SMS_STATUS_REPORT] = function UNSOLICITED_RESPONSE_NEW_SMS_STATUS_REPORT(length) {
   let result = this._processSmsStatusReport(length);
   this.acknowledgeGsmSms(result == PDU_FCS_OK, result);
 };
 RIL[UNSOLICITED_RESPONSE_NEW_SMS_ON_SIM] = function UNSOLICITED_RESPONSE_NEW_SMS_ON_SIM(length) {
-  // let info = Buf.readInt32List();
-  //TODO
+  let recordNumber = Buf.readInt32List()[0];
+
+  ICCRecordHelper.readSMS(
+    recordNumber,
+    function onsuccess(message) {
+      if (message && message.simStatus === 3) { //New Unread SMS
+        this._processSmsMultipart(message);
+      }
+    }.bind(this),
+    function onerror(errorMsg) {
+      if (DEBUG) {
+        debug("Failed to Read NEW SMS on SIM #" + recordNumber + ", errorMsg: " + errorMsg);
+      }
+    });
 };
 RIL[UNSOLICITED_ON_USSD] = function UNSOLICITED_ON_USSD() {
   let [typeCode, message] = Buf.readStringList();
   if (DEBUG) {
     debug("On USSD. Type Code: " + typeCode + " Message: " + message);
   }
 
   this._ussdSession = (typeCode != "0" && typeCode != "2");
@@ -10828,16 +10840,17 @@ let ICCFileHelper = {
 
   /**
    * This function handles EFs for SIM.
    */
   getSimEFPath: function getSimEFPath(fileId) {
     switch (fileId) {
       case ICC_EF_FDN:
       case ICC_EF_MSISDN:
+      case ICC_EF_SMS:
         return EF_PATH_MF_SIM + EF_PATH_DF_TELECOM;
       case ICC_EF_AD:
       case ICC_EF_MBDN:
       case ICC_EF_PLMNsel:
       case ICC_EF_SPN:
       case ICC_EF_SPDI:
       case ICC_EF_SST:
       case ICC_EF_PHASE:
@@ -10864,16 +10877,17 @@ let ICCFileHelper = {
       case ICC_EF_MSISDN:
       case ICC_EF_SPN:
       case ICC_EF_SPDI:
       case ICC_EF_CBMI:
       case ICC_EF_CBMID:
       case ICC_EF_CBMIR:
       case ICC_EF_OPL:
       case ICC_EF_PNN:
+      case ICC_EF_SMS:
         return EF_PATH_MF_SIM + EF_PATH_ADF_USIM;
       default:
         // The file ids in USIM phone book entries are decided by the
         // card manufacturer. So if we don't match any of the cases
         // above and if its a USIM return the phone book path.
         return EF_PATH_MF_SIM + EF_PATH_DF_TELECOM + EF_PATH_DF_PHONEBOOK;
     }
   },
@@ -11143,29 +11157,30 @@ let ICCIOHelper = {
       options.callback(options);
     }
   },
 
   /**
    * Process ICC IO error.
    */
   processICCIOError: function processICCIOError(options) {
-    let error = options.onerror || debug;
-
-    // See GSM11.11, TS 51.011 clause 9.4, and ISO 7816-4 for the error
-    // description.
-    let errorMsg = "ICC I/O Error code " +
-                   RIL_ERROR_TO_GECKO_ERROR[options.rilRequestError] +
-                   " EF id = " + options.fileId.toString(16) +
-                   " command = " + options.command.toString(16);
-    if (options.sw1 && options.sw2) {
-      errorMsg += "(" + options.sw1.toString(16) +
-                  "/" + options.sw2.toString(16) + ")";
-    }
-    error(errorMsg);
+    let requestError = RIL_ERROR_TO_GECKO_ERROR[options.rilRequestError];
+    if (DEBUG) {
+      // See GSM11.11, TS 51.011 clause 9.4, and ISO 7816-4 for the error
+      // description.
+      let errorMsg = "ICC I/O Error code " + requestError +
+                     " EF id = " + options.fileId.toString(16) +
+                     " command = " + options.command.toString(16);
+      if (options.sw1 && options.sw2) {
+        errorMsg += "(" + options.sw1.toString(16) +
+                    "/" + options.sw2.toString(16) + ")";
+      }
+      debug(errorMsg);
+    }
+    onerror(requestError);
   },
 };
 ICCIOHelper[ICC_COMMAND_SEEK] = null;
 ICCIOHelper[ICC_COMMAND_READ_BINARY] = function ICC_COMMAND_READ_BINARY(options) {
   this.processICCIOReadBinary(options);
 };
 ICCIOHelper[ICC_COMMAND_READ_RECORD] = function ICC_COMMAND_READ_RECORD(options) {
   this.processICCIOReadRecord(options);
@@ -11636,18 +11651,20 @@ let ICCRecordHelper = {
       }
 
       Buf.readStringDelimiter(strLen);
 
       if (options.p1 < options.totalRecords) {
         ICCIOHelper.loadNextRecord(options);
       } else {
         // No free record found.
-        let error = onerror || debug;
-        error("No free record found.");
+        if (DEBUG) {
+          debug(CONTACT_ERR_NO_FREE_RECORD_FOUND);
+        }
+        onerror(CONTACT_ERR_NO_FREE_RECORD_FOUND);
       }
     }
 
     ICCIOHelper.loadLinearFixedEF({fileId: fileId,
                                    callback: callback.bind(this),
                                    onerror: onerror});
   },
 };
@@ -12233,16 +12250,56 @@ let SimRecordHelper = {
       } catch (e) {
         if (DEBUG) debug("readPLMNEntries: PLMN entry " + index + " is invalid.");
         break;
       }
       index ++;
     }
     return plmnList;
   },
+
+  /**
+   * Read the SMS from the ICC.
+   *
+   * @param recordNumber The number of the record shall be loaded.
+   * @param onsuccess    Callback to be called when success.
+   * @param onerror      Callback to be called when error.
+   */
+  readSMS: function readSMS(recordNumber, onsuccess, onerror) {
+    function callback(options) {
+      let strLen = Buf.readInt32();
+
+      // TS 51.011, 10.5.3 EF_SMS
+      // b3 b2 b1
+      //  0  0  1 message received by MS from network; message read
+      //  0  1  1 message received by MS from network; message to be read
+      //  1  1  1 MS originating message; message to be sent
+      //  1  0  1 MS originating message; message sent to the network:
+      let status = GsmPDUHelper.readHexOctet();
+
+      let message = GsmPDUHelper.readMessage();
+      message.simStatus = status;
+
+      // Consumes the remaining buffer
+      Buf.seekIncoming(Buf.getReadAvailable() - Buf.PDU_HEX_OCTET_SIZE);
+
+      Buf.readStringDelimiter(strLen);
+
+      if (message) {
+        onsuccess(message);
+      } else {
+        onerror("Failed to decode SMS on SIM #" + recordNumber);
+      }
+    }
+
+    ICCIOHelper.loadLinearFixedEF({fileId: ICC_EF_SMS,
+                                   recordNumber: recordNumber,
+                                   callback: callback,
+                                   onerror: onerror});
+  },
 };
 
 let RuimRecordHelper = {
   fetchRuimRecords: function fetchRuimRecords() {
     this.getIMSI_M();
     this.readCST();
     this.readCDMAHome();
     RIL.getCdmaSubscription();
@@ -12876,18 +12933,20 @@ let ICCContactHelper = {
         } else {
           this.readUSimContacts(onsuccess, onerror);
         }
         break;
       case "fdn":
         ICCRecordHelper.readADNLike(ICC_EF_FDN, onsuccess, onerror);
         break;
       default:
-        let error = onerror || debug;
-        error(GECKO_ERROR_REQUEST_NOT_SUPPORTED);
+        if (DEBUG) {
+          debug("Unsupported contactType :" + contactType);
+        }
+        onerror(CONTACT_ERR_CONTACT_TYPE_NOT_SUPPORTED);
         break;
     }
   },
 
   /**
    * Helper function to find free contact record.
    *
    * @param appType       One of CARD_APPTYPE_*.
@@ -12907,34 +12966,38 @@ let ICCContactHelper = {
 
           ICCRecordHelper.readPBR(gotPbrCb, onerror);
         }
         break;
       case "fdn":
         ICCRecordHelper.findFreeRecordId(ICC_EF_FDN, onsuccess.bind(null, 0), onerror);
         break;
       default:
-        let error = onerror || debug;
-        error(GECKO_ERROR_REQUEST_NOT_SUPPORTED);
+        if (DEBUG) {
+          debug("Unsupported contactType :" + contactType);
+        }
+        onerror(CONTACT_ERR_CONTACT_TYPE_NOT_SUPPORTED);
         break;
     }
   },
 
    /**
     * Find free ADN record id in USIM.
     *
     * @param pbrs          All Phonebook Reference Files read.
     * @param onsuccess     Callback to be called when success.
     * @param onerror       Callback to be called when error.
     */
   findUSimFreeADNRecordId: function findUSimFreeADNRecordId(pbrs, onsuccess, onerror) {
     (function findFreeRecordId(pbrIndex) {
       if (pbrIndex >= pbrs.length) {
-        let error = onerror || debug;
-        error("No free record found.");
+        if (DEBUG) {
+          debug(CONTACT_ERR_NO_FREE_RECORD_FOUND);
+        }
+        onerror(CONTACT_ERR_NO_FREE_RECORD_FOUND);
         return;
       }
 
       let pbr = pbrs[pbrIndex];
       ICCRecordHelper.findFreeRecordId(
         pbr.adn.fileId,
         onsuccess.bind(this, pbrIndex),
         findFreeRecordId.bind(null, pbrIndex + 1));
@@ -12968,34 +13031,36 @@ let ICCContactHelper = {
    * @param appType       One of CARD_APPTYPE_*.
    * @param contactType   "adn" or "fdn".
    * @param contact       The contact will be updated.
    * @param pin2          PIN2 is required for FDN.
    * @param onsuccess     Callback to be called when success.
    * @param onerror       Callback to be called when error.
    */
   updateICCContact: function updateICCContact(appType, contactType, contact, pin2, onsuccess, onerror) {
-   let error = onerror || debug;
     switch (contactType) {
       case "adn":
         if (!this.hasDfPhoneBook(appType)) {
           ICCRecordHelper.updateADNLike(ICC_EF_ADN, contact, null, onsuccess, onerror);
         } else {
           this.updateUSimContact(contact, onsuccess, onerror);
         }
         break;
       case "fdn":
         if (!pin2) {
-          error("pin2 is empty");
+          onerror(GECKO_ERROR_SIM_PIN2);
           return;
         }
         ICCRecordHelper.updateADNLike(ICC_EF_FDN, contact, pin2, onsuccess, onerror);
         break;
       default:
-        error(GECKO_ERROR_REQUEST_NOT_SUPPORTED);
+        if (DEBUG) {
+          debug("Unsupported contactType :" + contactType);
+        }
+        onerror(CONTACT_ERR_CONTACT_TYPE_NOT_SUPPORTED);
         break;
     }
   },
 
   /**
    * Read contacts from USIM.
    *
    * @param onsuccess     Callback to be called when success.
@@ -13156,18 +13221,20 @@ let ICCContactHelper = {
       switch (ef) {
         case USIM_PBR_EMAIL:
           ICCRecordHelper.readEmail(fileId, fileType, recordId, gotFieldCb, onerror);
           break;
         case USIM_PBR_ANR:
           ICCRecordHelper.readANR(fileId, fileType, recordId, gotFieldCb, onerror);
           break;
         default:
-          let error = onerror || debug;
-          error("Unknown field " + field);
+          if (DEBUG) {
+            debug("Unsupported field :" + field);
+          }
+          onerror(CONTACT_ERR_FIELD_NOT_SUPPORTED);
           break;
       }
     }.bind(this);
 
     this.getContactFieldRecordId(pbr, contact, field, gotRecordIdCb, onerror);
   },
 
   /**
@@ -13197,34 +13264,38 @@ let ICCContactHelper = {
 
         if (onsuccess) {
           onsuccess(recordId);
         }
       }.bind(this);
 
       ICCRecordHelper.readIAP(pbr.iap.fileId, contact.recordId, gotIapCb, onerror);
     } else {
-      let error = onerror | debug;
-      error("USIM PBR files in Type 3 format are not supported.");
+      if (DEBUG) {
+        debug("USIM PBR files in Type 3 format are not supported.");
+      }
+      onerror(CONTACT_ERR_REQUEST_NOT_SUPPORTED);
     }
   },
 
   /**
    * Update USIM contact.
    *
    * @param contact       The contact will be updated.
    * @param onsuccess     Callback to be called when success.
    * @param onerror       Callback to be called when error.
    */
   updateUSimContact: function updateUSimContact(contact, onsuccess, onerror) {
     let gotPbrCb = function gotPbrCb(pbrs) {
       let pbr = pbrs[contact.pbrIndex];
       if (!pbr) {
-        let error = onerror || debug;
-        error("Cannot access Phonebook.");
+        if (DEBUG) {
+          debug(CONTACT_ERR_CANNOT_ACCESS_PHONEBOOK);
+        }
+        onerror(CONTACT_ERR_CANNOT_ACCESS_PHONEBOOK);
         return;
       }
       this.updatePhonebookSet(pbr, contact, onsuccess, onerror);
     }.bind(this);
 
     ICCRecordHelper.readPBR(gotPbrCb, onerror);
   },
 
@@ -13291,16 +13362,21 @@ let ICCContactHelper = {
    * @param onsuccess     Callback to be called when success.
    * @param onerror       Callback to be called when error.
    */
   updateContactField: function updateContactField(pbr, contact, field, onsuccess, onerror) {
     if (pbr[field].fileType === ICC_USIM_TYPE1_TAG) {
       this.updateContactFieldType1(pbr, contact, field, onsuccess, onerror);
     } else if (pbr[field].fileType === ICC_USIM_TYPE2_TAG) {
       this.updateContactFieldType2(pbr, contact, field, onsuccess, onerror);
+    } else {
+      if (DEBUG) {
+        debug("USIM PBR files in Type 3 format are not supported.");
+      }
+      onerror(CONTACT_ERR_REQUEST_NOT_SUPPORTED);
     }
   },
 
   /**
    * Update Type 1 USIM contact fields.
    *
    * @param pbr           The phonebook reference file.
    * @param contact       The contact needs to be updated.
@@ -13308,16 +13384,21 @@ let ICCContactHelper = {
    * @param onsuccess     Callback to be called when success.
    * @param onerror       Callback to be called when error.
    */
   updateContactFieldType1: function updateContactFieldType1(pbr, contact, field, onsuccess, onerror) {
     if (field === USIM_PBR_EMAIL) {
       ICCRecordHelper.updateEmail(pbr, contact.recordId, contact.email, null, onsuccess, onerror);
     } else if (field === USIM_PBR_ANR0) {
       ICCRecordHelper.updateANR(pbr, contact.recordId, contact.anr[0], null, onsuccess, onerror);
+    } else {
+     if (DEBUG) {
+       debug("Unsupported field :" + field);
+     }
+     onerror(CONTACT_ERR_FIELD_NOT_SUPPORTED);
     }
   },
 
   /**
    * Update Type 2 USIM contact fields.
    *
    * @param pbr           The phonebook reference file.
    * @param contact       The contact needs to be updated.
@@ -13342,17 +13423,23 @@ let ICCContactHelper = {
         return;
       }
 
       // Case 2.
       if (field === USIM_PBR_EMAIL) {
         ICCRecordHelper.updateEmail(pbr, recordId, contact.email, contact.recordId, onsuccess, onerror);
       } else if (field === USIM_PBR_ANR0) {
         ICCRecordHelper.updateANR(pbr, recordId, contact.anr[0], contact.recordId, onsuccess, onerror);
-      }
+      } else {
+        if (DEBUG) {
+          debug("Unsupported field :" + field);
+        }
+        onerror(CONTACT_ERR_FIELD_NOT_SUPPORTED);
+      }
+
     }.bind(this);
 
     ICCRecordHelper.readIAP(pbr.iap.fileId, contact.recordId, gotIapCb, onerror);
   },
 
   /**
    * Add Type 2 USIM contact fields.
    *
@@ -13371,18 +13458,20 @@ let ICCContactHelper = {
       if (field === USIM_PBR_EMAIL) {
         ICCRecordHelper.updateEmail(pbr, recordId, contact.email, contact.recordId, updateCb, onerror);
       } else if (field === USIM_PBR_ANR0) {
         ICCRecordHelper.updateANR(pbr, recordId, contact.anr[0], contact.recordId, updateCb, onerror);
       }
     }.bind(this);
 
     let errorCb = function errorCb(errorMsg) {
-      let error = onerror || debug;
-      error(errorMsg + " USIM field " + field);
+      if (DEBUG) {
+        debug(errorMsg + " USIM field " + field);
+      }
+      onerror(errorMsg);
     }.bind(this);
 
     ICCRecordHelper.findFreeRecordId(pbr[field].fileId, successCb, errorCb);
   },
 
   /**
    * Update IAP value.
    *
--- a/dom/system/gonk/tests/test_ril_worker_icc.js
+++ b/dom/system/gonk/tests/test_ril_worker_icc.js
@@ -1868,16 +1868,114 @@ add_test(function test_find_free_icc_con
     do_check_eq(pbrIndex, 1);
     do_check_eq(recordId, 1);
   }
   contactHelper.findFreeICCContact(CARD_APPTYPE_USIM, "adn", successCb, errorCb);
 
   run_next_test();
 });
 
+/**
+ * Test error message returned in onerror for readICCContacts.
+ */
+add_test(function test_error_message_read_icc_contact () {
+  let worker = newUint8Worker();
+  let ril = worker.RIL;
+
+  function do_test(options, expectedErrorMsg) {
+    ril.sendChromeMessage = function (message) {
+      do_check_eq(message.errorMsg, expectedErrorMsg);
+    }
+    ril.readICCContacts(options);
+  }
+
+  // Error 1, didn't specify correct contactType.
+  do_test({}, CONTACT_ERR_REQUEST_NOT_SUPPORTED);
+
+  // Error 2, specifying a non-supported contactType.
+  ril.appType = CARD_APPTYPE_USIM;
+  do_test({contactType: "sdn"}, CONTACT_ERR_CONTACT_TYPE_NOT_SUPPORTED);
+
+  // Error 3, suppose we update the supported PBR fields in USIM_PBR_FIELDS,
+  // but forget to add implemenetations for it.
+  USIM_PBR_FIELDS.push("pbc");
+  do_test({contactType: "adn"}, CONTACT_ERR_FIELD_NOT_SUPPORTED);
+
+  run_next_test();
+});
+
+/**
+ * Test error message returned in onerror for updateICCContact.
+ */
+add_test(function test_error_message_update_icc_contact() {
+  let worker = newUint8Worker();
+  let ril = worker.RIL;
+
+  const ICCID = "123456789";
+  ril.iccInfo.iccid = ICCID;
+
+  function do_test(options, expectedErrorMsg) {
+    ril.sendChromeMessage = function (message) {
+      do_check_eq(message.errorMsg, expectedErrorMsg);
+    }
+    ril.updateICCContact(options);
+  }
+
+  // Error 1, didn't specify correct contactType.
+  do_test({}, CONTACT_ERR_REQUEST_NOT_SUPPORTED);
+
+  // Error 2, specifying a correct contactType, but without providing 'contact'.
+  do_test({contactType: "adn"}, CONTACT_ERR_REQUEST_NOT_SUPPORTED);
+
+  // Error 3, specifying a non-supported contactType.
+  ril.appType = CARD_APPTYPE_USIM;
+  do_test({contactType: "sdn", contact: {}}, CONTACT_ERR_CONTACT_TYPE_NOT_SUPPORTED);
+
+  // Error 4, without supplying pin2.
+  do_test({contactType: "fdn", contact: {contactId: ICCID + "1"}}, GECKO_ERROR_SIM_PIN2);
+
+  // Error 5, No free record found in EF_ADN.
+  let record = worker.ICCRecordHelper;
+  record.readPBR = function (onsuccess, onerror) {
+    onsuccess([{adn: {fileId: 0x4f3a}}]);
+  };
+
+  let io = worker.ICCIOHelper;
+  io.loadLinearFixedEF = function (options) {
+    options.totalRecords = 1;
+    options.p1 = 1;
+    options.callback(options);
+  };
+
+  do_test({contactType: "adn", contact: {}}, CONTACT_ERR_NO_FREE_RECORD_FOUND);
+
+  // Error 6, ICC IO Error.
+  io.loadLinearFixedEF = function (options) {
+    ril[REQUEST_SIM_IO](0, {rilRequestError: ERROR_GENERIC_FAILURE});
+  };
+  do_test({contactType: "adn", contact: {contactId: ICCID + "1"}},
+          GECKO_ERROR_GENERIC_FAILURE);
+
+  // Error 7, suppose we update the supported PBR fields in USIM_PBR_FIELDS,
+  // but forget to add implemenetations for it.
+  USIM_PBR_FIELDS.push("pbc");
+  do_test({contactType: "adn", contact: {contactId: ICCID + "1"}},
+          CONTACT_ERR_FIELD_NOT_SUPPORTED);
+
+  // Error 8, EF_PBR doesn't exist.
+  record.readPBR = function (onsuccess, onerror) {
+    onsuccess([]);
+  };
+
+  do_test({contactType: "adn", contact: {contactId: ICCID + "1"}},
+          CONTACT_ERR_CANNOT_ACCESS_PHONEBOOK);
+
+  run_next_test();
+});
+
 add_test(function test_personalization_state() {
   let worker = newUint8Worker();
   let ril = worker.RIL;
 
   worker.ICCRecordHelper.readICCID = function fakeReadICCID() {};
 
   function testPersonalization(cardPersoState, geckoCardState) {
     let iccStatus = {
--- a/dom/telephony/test/marionette/head.js
+++ b/dom/telephony/test/marionette/head.js
@@ -48,20 +48,20 @@ let emulator = (function() {
 }());
 
 /**
  * Telephony related helper functions.
  */
 (function() {
   function checkInitialState() {
     log("Verify initial state.");
-    ok(telephony, 'telephony');
-    is(telephony.active, null, 'telephony.active');
-    ok(telephony.calls, 'telephony.calls');
-    is(telephony.calls.length, 0, 'telephony.calls.length');
+    ok(telephony, "telephony");
+    is(telephony.active, null, "telephony.active");
+    ok(telephony.calls, "telephony.calls");
+    is(telephony.calls.length, 0, "telephony.calls.length");
   }
 
   /**
    * @return Promise
    */
   function clearCalls() {
     let deferred = Promise.defer();
 
@@ -99,53 +99,70 @@ function _startTest(permissions, test) {
     SpecialPowers.clearUserPref("dom.mozSettings.enabled");
     SpecialPowers.clearUserPref("dom.promise.enabled");
     for (let per of permissions) {
       SpecialPowers.removePermission(per, document);
     }
   }
 
   function setUp() {
-    log('== Test SetUp ==');
+    log("== Test SetUp ==");
     permissionSetUp();
     // Make sure that we get the telephony after adding permission.
     telephony = window.navigator.mozTelephony;
     ok(telephony);
     return clearCalls().then(checkInitialState);
   }
 
   // Extend finish() with tear down.
   finish = (function() {
     let originalFinish = finish;
 
     function tearDown() {
-      log('== Test TearDown ==');
+      log("== Test TearDown ==");
       emulator.waitFinish()
         .then(permissionTearDown)
         .then(function() {
           originalFinish.apply(this, arguments);
         });
     }
 
     return tearDown.bind(this);
   }());
 
   function mainTest() {
     setUp()
       .then(function onSuccess() {
-        log('== Test Start ==');
+        log("== Test Start ==");
         test();
       }, function onError(error) {
         SpecialPowers.Cu.reportError(error);
-        ok(false, 'SetUp error');
+        ok(false, "SetUp error");
       });
   }
 
   mainTest();
 }
 
 function startTest(test) {
-  _startTest(['telephony'], test);
+  _startTest(["telephony"], test);
 }
 
 function startTestWithPermissions(permissions, test) {
-  _startTest(permissions.concat('telephony'), test);
+  _startTest(permissions.concat("telephony"), test);
 }
+
+function startDSDSTest(test) {
+  let numRIL;
+  try {
+    numRIL = SpecialPowers.getIntPref("ril.numRadioInterfaces");
+  } catch (ex) {
+    numRIL = 1;  // Pref not set.
+  }
+
+  if (numRIL > 1) {
+    startTest(test);
+  } else {
+    log("Not a DSDS environment. Test is skipped.");
+    ok(true);  // We should run at least one test.
+    finish();
+  }
+}
--- a/dom/telephony/test/marionette/manifest.ini
+++ b/dom/telephony/test/marionette/manifest.ini
@@ -40,8 +40,9 @@ disabled = Bug 821966
 disabled = Bug 821927
 [test_multiple_hold.js]
 disabled = Bug 821958
 [test_outgoing_emergency_in_airplane_mode.js]
 [test_emergency_label.js]
 [test_conference.js]
 [test_dsds_default_service_id.js]
 [test_call_mute.js]
+[test_dsds_normal_call.js]
new file mode 100644
--- /dev/null
+++ b/dom/telephony/test/marionette/test_dsds_normal_call.js
@@ -0,0 +1,243 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+MARIONETTE_TIMEOUT = 60000;
+MARIONETTE_HEAD_JS = 'head.js';
+
+/**
+ * The functions are created to provide the string format of the emulator call
+ * list results.
+ *
+ * Usage:
+ *   let outInfo = OutCallStrPool("911");
+ *   outInfo.ringing == "outbound to 911        : ringing"
+ *   outInfo.active  == "outbound to 911        : active"
+ */
+function CallStrPool(prefix, number) {
+  let padding = "           : ";
+  let numberInfo = prefix + number + padding.substr(number.length);
+
+  let info = {};
+  let states = ["ringing", "incoming", "active", "held"];
+  for (let state of states) {
+    info[state] = numberInfo + state;
+  }
+
+  return info;
+}
+
+function OutCallStrPool(number) {
+  return CallStrPool("outbound to  ", number);
+}
+
+function InCallStrPool(number) {
+  return CallStrPool("inbound from ", number);
+}
+
+function checkEventCallState(event, call, state) {
+  is(call, event.call, "event.call");
+  is(call.state, state, "call state");
+}
+
+function checkTelephonyActiveAndCalls(active, calls) {
+  is(telephony.active, active, "telephony.active");
+  is(telephony.calls.length, calls.length, "telephony.calls");
+  for (let i = 0; i < calls.length; ++i) {
+    is(telephony.calls[i], calls[i]);
+  }
+}
+
+function checkEmulatorCallList(expectedCallList) {
+  let deferred = Promise.defer();
+
+  emulator.run("gsm list", function(result) {
+    log("Call list is now: " + result);
+    for (let i = 0; i < expectedCallList.length; ++i) {
+      is(result[i], expectedCallList[i], "emulator calllist");
+    }
+    is(result[expectedCallList.length], "OK", "emulator calllist");
+    deferred.resolve();
+  });
+
+  return deferred.promise;
+}
+
+// Promise.
+function checkAll(active, calls, callList) {
+  checkTelephonyActiveAndCalls(active, calls);
+  return checkEmulatorCallList(callList);
+}
+
+function dial(number, serviceId) {
+  serviceId = typeof serviceId !== "undefined" ? serviceId : 0;
+  log("Make an outgoing call: " + number + ", serviceId: " + serviceId);
+
+  let deferred = Promise.defer();
+  let call = telephony.dial(number, serviceId);
+
+  ok(call);
+  is(call.number, number);
+  is(call.state, "dialing");
+
+  call.onalerting = function onalerting(event) {
+    call.onalerting = null;
+    log("Received 'onalerting' call event.");
+    is(call.serviceId, serviceId);
+    checkEventCallState(event, call, "alerting");
+    deferred.resolve(call);
+  };
+
+  return deferred.promise;
+}
+
+function remoteDial(number) {
+  log("Simulating an incoming call.");
+
+  let deferred = Promise.defer();
+
+  telephony.onincoming = function onincoming(event) {
+    log("Received 'incoming' call event.");
+    telephony.onimcoming = null;
+
+    let call = event.call;
+
+    ok(call);
+    is(call.number, number);
+    is(call.state, "incoming");
+
+    deferred.resolve(call);
+  };
+  emulator.run("gsm call " + number);
+
+  return deferred.promise;
+}
+
+function answer(call) {
+  log("Answering the incoming call.");
+
+  let deferred = Promise.defer();
+
+  call.onconnecting = function onconnectingIn(event) {
+    log("Received 'connecting' call event for incoming call.");
+    call.onconnecting = null;
+    checkEventCallState(event, call, "connecting");
+  };
+
+  call.onconnected = function onconnectedIn(event) {
+    log("Received 'connected' call event for incoming call.");
+    call.onconnected = null;
+    checkEventCallState(event, call, "connected");
+    ok(!call.onconnecting);
+    deferred.resolve(call);
+  };
+  call.answer();
+
+  return deferred.promise;
+}
+
+function remoteAnswer(call) {
+  log("Remote answering the call.");
+
+  let deferred = Promise.defer();
+
+  call.onconnected = function onconnected(event) {
+    log("Received 'connected' call event.");
+    call.onconnected = null;
+    checkEventCallState(event, call, "connected");
+    deferred.resolve(call);
+  };
+  emulator.run("gsm accept " + call.number);
+
+  return deferred.promise;
+}
+
+function remoteHangUp(call) {
+  log("Remote hanging up the call.");
+
+  let deferred = Promise.defer();
+
+  call.ondisconnected = function ondisconnected(event) {
+    log("Received 'disconnected' call event.");
+    call.ondisconnected = null;
+    checkEventCallState(event, call, "disconnected");
+    deferred.resolve(call);
+  };
+  emulator.run("gsm cancel " + call.number);
+
+  return deferred.promise;
+}
+
+function muxModem(id) {
+  let deferred = Promise.defer();
+
+  emulator.run("mux modem " + id, function() {
+    deferred.resolve();
+  });
+
+  return deferred.promise;
+}
+
+function testOutgoingCallForServiceId(number, serviceId) {
+  let outCall;
+  let outInfo = OutCallStrPool(number);
+
+  return Promise.resolve()
+    .then(() => dial(number, serviceId))
+    .then(call => {
+      outCall = call;
+      is(outCall.serviceId, serviceId);
+    })
+    .then(() => checkAll(outCall, [outCall], [outInfo.ringing]))
+    .then(() => remoteAnswer(outCall))
+    .then(() => checkAll(outCall, [outCall], [outInfo.active]))
+    .then(() => remoteHangUp(outCall))
+    .then(() => checkAll(null, [], []));
+}
+
+function testIncomingCallForServiceId(number, serviceId) {
+  let inCall;
+  let inInfo = InCallStrPool(number);
+
+  return Promise.resolve()
+    .then(() => remoteDial(number))
+    .then(call => {
+      inCall = call;
+      is(inCall.serviceId, serviceId);
+    })
+    .then(() => checkAll(null, [inCall], [inInfo.incoming]))
+    .then(() => answer(inCall))
+    .then(() => checkAll(inCall, [inCall], [inInfo.active]))
+    .then(() => remoteHangUp(inCall))
+    .then(() => checkAll(null, [], []));
+}
+
+function testOutgoingCall() {
+  log("= testOutgoingCall =");
+
+  return Promise.resolve()
+    .then(() => muxModem(0))
+    .then(() => testOutgoingCallForServiceId("0912345000", 0))
+    .then(() => muxModem(1))
+    .then(() => testOutgoingCallForServiceId("0912345001", 1))
+    .then(() => muxModem(0));
+}
+
+function testIncomingCall() {
+  log("= testIncomingCall =");
+
+  return Promise.resolve()
+    .then(() => muxModem(0))
+    .then(() => testIncomingCallForServiceId("0912345000", 0))
+    .then(() => muxModem(1))
+    .then(() => testIncomingCallForServiceId("0912345001", 1))
+    .then(() => muxModem(0));
+}
+
+startDSDSTest(function() {
+  testOutgoingCall()
+    .then(testIncomingCall)
+    .then(null, () => {
+      ok(false, "promise rejects during test.");
+    })
+    .then(finish);
+});
--- a/dom/tests/mochitest/ajax/offline/offlineTests.js
+++ b/dom/tests/mochitest/ajax/offline/offlineTests.js
@@ -18,25 +18,23 @@ OfflineCacheContents.prototype = {
 QueryInterface: function(iid) {
     if (!iid.equals(Ci.nsISupports) &&
         !iid.equals(Ci.nsICacheListener)) {
       throw Cr.NS_ERROR_NO_INTERFACE;
     }
     return this;
   },
 onCacheEntryAvailable: function(desc, accessGranted, status) {
-    netscape.security.PrivilegeManager.enablePrivilege("UniversalXPConnect");
-
     if (!desc) {
       this.fetch(this.callback);
       return;
     }
 
     var stream = desc.QueryInterface(Ci.nsICacheEntryDescriptor).openInputStream(0);
-    var sstream = SpecialPowers.Cc["@mozilla.org/scriptableinputstream;1"]
+    var sstream = Cc["@mozilla.org/scriptableinputstream;1"]
                  .createInstance(SpecialPowers.Ci.nsIScriptableInputStream);
     sstream.init(stream);
     this.contents[desc.key] = sstream.read(sstream.available());
     sstream.close();
     desc.close();
     this.fetch(this.callback);
   },
 
@@ -93,48 +91,39 @@ setupChild: function()
  *
  * @return boolean Whether this window is the slave window
  *                 to actually run the test in.
  */
 setup: function()
 {
   netscape.security.PrivilegeManager.enablePrivilege("UniversalXPConnect");
 
-  var prefBranch = Cc["@mozilla.org/preferences-service;1"].getService(Ci.nsIPrefBranch);
   try {
-    this._allowedByDefault = prefBranch.getBoolPref("offline-apps.allow_by_default");
+    this._allowedByDefault = SpecialPowers.getBoolPref("offline-apps.allow_by_default");
   } catch (e) {}
 
   if (this._allowedByDefault) {
     this._masterWindow = window;
 
     return true;
   }
 
   if (!window.opener || !window.opener.OfflineTest ||
       !window.opener.OfflineTest._hasSlave) {
     // Offline applications must be toplevel windows and have the
     // offline-app permission.  Because we were loaded without the
     // offline-app permission and (probably) in an iframe, we need to
     // enable the pref and spawn a new window to perform the actual
     // tests.  It will use this window to report successes and
     // failures.
-    var pm = Cc["@mozilla.org/permissionmanager;1"]
-      .getService(Ci.nsIPermissionManager);
-    var uri = Cc["@mozilla.org/network/io-service;1"]
-      .getService(Ci.nsIIOService)
-      .newURI(window.location.href, null, null);
-    var principal = SpecialPowers.Cc["@mozilla.org/scriptsecuritymanager;1"]
-                      .getService(Ci.nsIScriptSecurityManager)
-                      .getNoAppCodebasePrincipal(uri);
 
-    if (pm.testPermissionFromPrincipal(principal, "offline-app") != 0) {
+    if (SpecialPowers.testPermission("offline-app", Ci.nsIPermissionManager.ALLOW_ACTION, document)) {
       ok(false, "Previous test failed to clear offline-app permission!  Expect failures.");
     }
-    pm.addFromPrincipal(principal, "offline-app", Ci.nsIPermissionManager.ALLOW_ACTION);
+    SpecialPowers.addPermission("offline-app", Ci.nsIPermissionManager.ALLOW_ACTION, document);
 
     // Tests must run as toplevel windows.  Open a slave window to run
     // the test.
     this._hasSlave = true;
     window.open(window.location, "offlinetest");
 
     return false;
   }
@@ -150,28 +139,17 @@ teardownAndFinish: function()
 },
 
 teardown: function(callback)
 {
   // First wait for any pending scheduled updates to finish
   this.waitForUpdates(function(self) {
     // Remove the offline-app permission we gave ourselves.
 
-    netscape.security.PrivilegeManager.enablePrivilege("UniversalXPConnect");
-
-    var pm = Cc["@mozilla.org/permissionmanager;1"]
-             .getService(Ci.nsIPermissionManager);
-    var uri = Cc["@mozilla.org/network/io-service;1"]
-              .getService(Ci.nsIIOService)
-              .newURI(window.location.href, null, null);
-    var principal = SpecialPowers.Cc["@mozilla.org/scriptsecuritymanager;1"]
-                      .getService(Ci.nsIScriptSecurityManager)
-                      .getNoAppCodebasePrincipal(uri);
-
-    pm.removeFromPrincipal(principal, "offline-app");
+    SpecialPowers.removePermission("offline-app", window.document);
 
     // Clear all overrides on the server
     for (override in self._pathOverrides)
       self.deleteData(self._pathOverrides[override]);
     for (statedSJS in self._SJSsStated)
       self.setSJSState(self._SJSsStated[statedSJS], "");
 
     self.clear();
@@ -222,33 +200,31 @@ clear: function()
   var applicationCache = this.getActiveCache();
   if (applicationCache) {
     applicationCache.discard();
   }
 },
 
 waitForUpdates: function(callback)
 {
-  netscape.security.PrivilegeManager.enablePrivilege("UniversalXPConnect");
-
   var self = this;
   var observer = {
     notified: false,
     observe: function(subject, topic, data) {
       if (subject) {
         subject.QueryInterface(SpecialPowers.Ci.nsIOfflineCacheUpdate);
         dump("Update of " + subject.manifestURI.spec + " finished\n");
       }
 
       SimpleTest.executeSoon(function() {
         if (observer.notified) {
           return;
         }
 
-        var updateservice = SpecialPowers.Cc["@mozilla.org/offlinecacheupdate-service;1"]
+        var updateservice = Cc["@mozilla.org/offlinecacheupdate-service;1"]
                             .getService(SpecialPowers.Ci.nsIOfflineCacheUpdateService);
         var updatesPending = updateservice.numUpdates;
         if (updatesPending == 0) {
           try {
             SpecialPowers.removeObserver(observer, "offline-cache-update-completed");
           } catch(ex) {}
           dump("All pending updates done\n");
           observer.notified = true;
@@ -275,17 +251,16 @@ failEvent: function(e)
 // The offline API as specified has no way to watch the load of a resource
 // added with applicationCache.mozAdd().
 waitForAdd: function(url, onFinished) {
   // Check every half second for ten seconds.
   var numChecks = 20;
 
   var waitForAddListener = {
     onCacheEntryAvailable: function(entry, access, status) {
-      netscape.security.PrivilegeManager.enablePrivilege("UniversalXPConnect");
       if (entry) {
         entry.close();
         onFinished();
         return;
       }
 
       if (--numChecks == 0) {
         onFinished();
@@ -365,17 +340,16 @@ getActiveSession: function()
                                     Ci.nsICache.STORE_OFFLINE,
                                     true);
 },
 
 priv: function(func)
 {
   var self = this;
   return function() {
-    netscape.security.PrivilegeManager.enablePrivilege("UniversalXPConnect");
     func(arguments);
   }
 },
 
 checkCacheEntries: function(entries, callback)
 {
   var checkNextEntry = function() {
     if (entries.length == 0) {
@@ -386,17 +360,16 @@ checkCacheEntries: function(entries, cal
     }
   }
 
   checkNextEntry();
 },
 
 checkCache: function(url, expectEntry, callback)
 {
-  netscape.security.PrivilegeManager.enablePrivilege("UniversalXPConnect");
   var cacheSession = this.getActiveSession();
   this._checkCache(cacheSession, url, expectEntry, callback);
 },
 
 _checkCache: function(cacheSession, url, expectEntry, callback)
 {
   if (!cacheSession) {
     if (expectEntry) {
@@ -405,17 +378,16 @@ checkCache: function(url, expectEntry, c
       this.ok(true, url + " should not exist in the offline cache (no session)");
     }
     if (callback) setTimeout(this.priv(callback), 0);
     return;
   }
 
   var _checkCacheListener = {
     onCacheEntryAvailable: function(entry, access, status) {
-      netscape.security.PrivilegeManager.enablePrivilege("UniversalXPConnect");
       if (entry) {
         if (expectEntry) {
           OfflineTest.ok(true, url + " should exist in the offline cache");
         } else {
           OfflineTest.ok(false, url + " should not exist in the offline cache");
         }
         entry.close();
       } else {
--- a/dom/tests/mochitest/ajax/offline/test_bug445544.html
+++ b/dom/tests/mochitest/ajax/offline/test_bug445544.html
@@ -1,16 +1,17 @@
 <html manifest="445544.cacheManifest">
 <!--
 https://bugzilla.mozilla.org/show_bug.cgi?id=445544
 -->
 
 <head>
 <title>Test for Bug 445544</title>
 
+<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
 <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
 <script type="text/javascript" src="/tests/dom/tests/mochitest/ajax/offline/offlineTests.js"></script>
 
 <script type="text/javascript">
 
 var gTestWin;
 var gTimeoutId;
 
--- a/dom/tests/mochitest/ajax/offline/test_bug460353.html
+++ b/dom/tests/mochitest/ajax/offline/test_bug460353.html
@@ -13,40 +13,33 @@
 <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
 <script type="text/javascript" src="/tests/dom/tests/mochitest/ajax/offline/offlineTests.js"></script>
 
 <script class="testbody" type="text/javascript">
 
 var result = new Array();
 var expectedUpdates = 3; // 2 iframes and our self
 
-init();
+applicationCache.oncached = onUpdatePassed;
+applicationCache.onnoupdate = onUpdatePassed;
+
+SpecialPowers.pushPermissions([{'type': 'offline-app', 'allow': true, 'context': document}], init);
 
 function onUpdatePassed()
 {
   if (!(--expectedUpdates))
     SimpleTest.executeSoon(finish);
 }
 
 function init()
 {
-  var Cc = SpecialPowers.Cc;
-  var Ci = SpecialPowers.Ci;
-  var pm = Cc["@mozilla.org/permissionmanager;1"]
-    .getService(Ci.nsIPermissionManager);
-  var uri = Cc["@mozilla.org/network/io-service;1"]
-    .getService(Ci.nsIIOService)
-    .newURI(window.location.href, null, null);
-  var principal = SpecialPowers.Cc["@mozilla.org/scriptsecuritymanager;1"]
-                    .getService(Ci.nsIScriptSecurityManager)
-                    .getNoAppCodebasePrincipal(uri);
-  pm.addFromPrincipal(principal, "offline-app", Ci.nsIPermissionManager.ALLOW_ACTION);
-
-  applicationCache.oncached = onUpdatePassed;
-  applicationCache.onnoupdate = onUpdatePassed;
+  var iframes = document.getElementsByTagName('iframe');
+  iframes[0].src = "460353_iframe_nomanifest.html";
+  iframes[1].src = "460353_iframe_ownmanifest.html";
+  iframes[2].src = "460353_iframe_samemanifest.html";
 }
 
 function frameOnLoad(frameid, status)
 {
   var obj = new Object();
   result[frameid] = obj;
 
   result[frameid].load = true;
@@ -74,28 +67,16 @@ function finish()
   SimpleTest.ok(result["diff"].updateOK || false, "Frame with different manifest cache update passed OK");
   SimpleTest.is(result["diff"].cacheStatus || -1, 1, "Frame with different manifest cache status was IDLE");
 
   SimpleTest.ok(result["noman"].load || false, "Frame with no manifest loads");
   SimpleTest.ok(result["noman"].update == undefined, "Frame with no manifest cache update didn't notify");
   SimpleTest.ok(result["noman"].updateOK == undefined, "Frame with no manifest cache update didn't pass");
   SimpleTest.is(result["noman"].cacheStatus || -1, -1, "Frame with no manifest cache status was undefined");
 
-  var Cc = SpecialPowers.Cc;
-  var Ci = SpecialPowers.Ci;
-  var pm = Cc["@mozilla.org/permissionmanager;1"]
-           .getService(Ci.nsIPermissionManager);
-  var uri = Cc["@mozilla.org/network/io-service;1"]
-            .getService(Ci.nsIIOService)
-            .newURI(window.location.href, null, null);
-  var principal = SpecialPowers.Cc["@mozilla.org/scriptsecuritymanager;1"]
-                    .getService(Ci.nsIScriptSecurityManager)
-                    .getNoAppCodebasePrincipal(uri);
-  pm.removeFromPrincipal(principal, "offline-app");
-
   OfflineTest.waitForUpdates(function() {
     cleanCache("http://mochi.test:8888/tests/dom/tests/mochitest/ajax/offline/simpleManifest.cacheManifest");
     cleanCache("http://mochi.test:8888/tests/dom/tests/mochitest/ajax/offline/updatingManifest.sjs");
 
     SimpleTest.finish();
   });
 }
 
@@ -107,13 +88,13 @@ function cleanCache(manifestURL)
     cache.discard();
 }
 
 SimpleTest.waitForExplicitFinish();
 
 </script>
 
 <body>
-  <iframe src="460353_iframe_nomanifest.html"></iframe>
-  <iframe src="460353_iframe_ownmanifest.html"></iframe>
-  <iframe src="460353_iframe_samemanifest.html"></iframe>
+  <iframe></iframe>
+  <iframe></iframe>
+  <iframe></iframe>
 </body>
 </html>
--- a/dom/tests/mochitest/ajax/offline/test_bug744719-cancel.html
+++ b/dom/tests/mochitest/ajax/offline/test_bug744719-cancel.html
@@ -9,18 +9,20 @@
 <script type="text/javascript">
 
 /*
   Manifest refers a large number of resource to load.  The 10th item however is a reference to a non-existing
   resource that cancels the load.  This test checks we cancel all loads and don't leak any of the other resources
   after cancelation.
 */
 
-ok(applicationCache.mozItems.length == 0,
-   "applicationCache.mozItems should be available and empty before associating with a cache.");
+if (SpecialPowers.isMainProcess()) {
+  ok(applicationCache.mozItems.length == 0,
+     "applicationCache.mozItems should be available and empty before associating with a cache.");
+}
 
 function updateCanceled()
 {
   OfflineTest.checkCache("http://mochi.test:8888/tests/dom/tests/mochitest/ajax/offline/744719-cancel.cacheManifest", false, null);
   OfflineTest.checkCache("http://mochi.test:8888/tests/SimpleTest/SimpleTest.js", false, null);
   OfflineTest.checkCache("http://mochi.test:8888/tests/dom/tests/mochitest/ajax/offline/offlineTests.js", false, null);
 
   OfflineTest.checkCache("http://mochi.test:8888/tests/dom/tests/mochitest/ajax/offline/nonexistent744719?010", false, null);
--- a/dom/tests/mochitest/ajax/offline/test_bug744719.html
+++ b/dom/tests/mochitest/ajax/offline/test_bug744719.html
@@ -8,18 +8,20 @@
 
 <script type="text/javascript">
 
 /*
   Simply load a large number of resources and check all are properly cached.  This should cover all parts
   of the parallel loading code.
 */
 
-ok(applicationCache.mozItems.length == 0,
-   "applicationCache.mozItems should be available and empty before associating with a cache.");
+if (SpecialPowers.isMainProcess()) {
+  ok(applicationCache.mozItems.length == 0,
+     "applicationCache.mozItems should be available and empty before associating with a cache.");
+}
 
 function manifestUpdated()
 {
   OfflineTest.checkCache("http://mochi.test:8888/tests/dom/tests/mochitest/ajax/offline/744719.cacheManifest", true);
   OfflineTest.checkCache("http://mochi.test:8888/tests/SimpleTest/SimpleTest.js", true);
   OfflineTest.checkCache("http://mochi.test:8888/tests/dom/tests/mochitest/ajax/offline/offlineTests.js", true);
 
   var URL = "http://mochi.test:8888/tests/dom/tests/mochitest/ajax/offline/subresource744719.html?";
--- a/dom/tests/mochitest/ajax/offline/test_missingManifest.html
+++ b/dom/tests/mochitest/ajax/offline/test_missingManifest.html
@@ -4,44 +4,30 @@
 
 <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
 <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
 
 <script type="text/javascript">
 
 var gTestWin;
 
-var Cc = SpecialPowers.Cc;
-var Ci = SpecialPowers.Ci;
+SimpleTest.waitForExplicitFinish();
+
+SpecialPowers.pushPermissions([{'type': 'offline-app', 'allow': true, 'context': document}], startTest);
 
-// Enable the offline-app permission before loading the new window
-var pm = Cc["@mozilla.org/permissionmanager;1"]
-        .getService(Ci.nsIPermissionManager);
-var uri = Cc["@mozilla.org/network/io-service;1"]
-         .getService(Ci.nsIIOService)
-         .newURI(window.location.href, null, null);
-var principal = SpecialPowers.Cc["@mozilla.org/scriptsecuritymanager;1"]
-                  .getService(Ci.nsIScriptSecurityManager)
-                  .getNoAppCodebasePrincipal(uri);
+function startTest() {
+  // now this will properly load the manifest.
+  gTestWin = window.open("missing.html");
+}
 
-pm.addFromPrincipal(principal, "offline-app", Ci.nsIPermissionManager.ALLOW_ACTION);
-
-// now this will properly load the manifest.
-gTestWin = window.open("missing.html");
-
-function finish()
-{
-  pm.removeFromPrincipal(principal, "offline-app");
-
+function finish() {
   gTestWin.close();
   SimpleTest.finish();
 }
 
-SimpleTest.waitForExplicitFinish();
-
 </script>
 
 </head>
 
 <body>
 
 </body>
 </html>
--- a/dom/tests/mochitest/ajax/offline/test_noManifest.html
+++ b/dom/tests/mochitest/ajax/offline/test_noManifest.html
@@ -17,17 +17,19 @@ function expectInvalidState(fn, desc) {
       gotInvalidState = true;
     }
   }
 
   ok(gotInvalidState, desc);
 }
 
 is(typeof(applicationCache), "object");
-is(applicationCache.mozLength, 0, "applicationCache.mozLength should be 0");
+if (SpecialPowers.isMainProcess()) {
+  is(applicationCache.mozLength, 0, "applicationCache.mozLength should be 0");
+}
 is(applicationCache.status, 0, "applicationCache.status should be 0 (UNCACHED)");
 
 expectInvalidState(function() { applicationCache.update(); },
                    "applicationCache.update should throw InvalidStateError");
 expectInvalidState(function() { applicationCache.swapCache(); },
                    "applicationCache.update should throw InvalidStateError");
 
 
--- a/dom/tests/mochitest/ajax/offline/test_obsolete.html
+++ b/dom/tests/mochitest/ajax/offline/test_obsolete.html
@@ -4,46 +4,33 @@
 
 <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
 <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
 
 <script type="text/javascript">
 
 var gTestWin;
 
-var Cc = SpecialPowers.Cc;
-var Ci = SpecialPowers.Ci;
+SpecialPowers.pushPermissions([{'type': 'offline-app', 'allow': true, 'context': document}], startTest);
 
-// Enable the offline-app permission before loading the new window
-var pm = Cc["@mozilla.org/permissionmanager;1"]
-        .getService(Ci.nsIPermissionManager);
-var uri = Cc["@mozilla.org/network/io-service;1"]
-         .getService(Ci.nsIIOService)
-         .newURI(window.location.href, null, null);
-var principal = SpecialPowers.Cc["@mozilla.org/scriptsecuritymanager;1"]
-                  .getService(Ci.nsIScriptSecurityManager)
-                  .getNoAppCodebasePrincipal(uri);
-pm.addFromPrincipal(principal, "offline-app", Ci.nsIPermissionManager.ALLOW_ACTION);
-
-// Make the obsoleting.sjs return a valid manifest
-var req = new XMLHttpRequest();
-req.open("GET", "http://mochi.test:8888/tests/dom/tests/mochitest/ajax/offline/obsoletingManifest.sjs?state=manifestPresent");
-req.setRequestHeader("Content-Type", "text/cache-manifest");
-req.send("");
-req.onreadystatechange = function() {
-  if (req.readyState == 4) {
-    // now this will properly load the manifest.
-    gTestWin = window.open("obsolete.html");
+function startTest() {
+  // Make the obsoleting.sjs return a valid manifest
+  var req = new XMLHttpRequest();
+  req.open("GET", "http://mochi.test:8888/tests/dom/tests/mochitest/ajax/offline/obsoletingManifest.sjs?state=manifestPresent");
+  req.setRequestHeader("Content-Type", "text/cache-manifest");
+  req.send("");
+  req.onreadystatechange = function() {
+    if (req.readyState == 4) {
+      // now this will properly load the manifest.
+      gTestWin = window.open("obsolete.html");
+    }
   }
 }
 
-function finish()
-{
-  pm.removeFromPrincipal(principal, "offline-app");
-
+function finish() {
   gTestWin.close();
   SimpleTest.finish();
 }
 
 SimpleTest.waitForExplicitFinish();
 
 </script>
 
--- a/dom/tests/mochitest/ajax/offline/updatingImplicit.html
+++ b/dom/tests/mochitest/ajax/offline/updatingImplicit.html
@@ -1,12 +1,13 @@
 <html xmlns="http://www.w3.org/1999/xhtml" manifest="http://mochi.test:8888/tests/dom/tests/mochitest/ajax/offline/updatingManifest.sjs">
 <head>
 <title>Updating implicit</title>
 
+<script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
 <script type="text/javascript" src="/tests/dom/tests/mochitest/ajax/offline/offlineTests.js"></script>
 
 <script type="text/javascript">
 
 function manifestUpdated()
 {
   // Assert that we are properly associated with the application
   // cache.
--- a/dom/webidl/Navigator.webidl
+++ b/dom/webidl/Navigator.webidl
@@ -110,17 +110,17 @@ interface NavigatorBattery {
     [Throws, Func="Navigator::HasBatterySupport"]
     readonly attribute BatteryManager? battery;
 };
 Navigator implements NavigatorBattery;
 
 // https://wiki.mozilla.org/WebAPI/DataStore
 [NoInterfaceObject]
 interface NavigatorDataStore {
-    [Throws, NewObject, Pref="dom.datastore.enabled"]
+    [Throws, NewObject, Func="Navigator::HasDataStoreSupport"]
     Promise getDataStores(DOMString name);
 };
 Navigator implements NavigatorDataStore;
 
 // http://www.w3.org/TR/vibration/#vibration-interface
 partial interface Navigator {
     // We don't support sequences in unions yet
     //boolean vibrate ((unsigned long or sequence<unsigned long>) pattern);
--- a/dom/workers/XMLHttpRequest.cpp
+++ b/dom/workers/XMLHttpRequest.cpp
@@ -1497,17 +1497,20 @@ XMLHttpRequest::Constructor(const Global
 {
   JSContext* cx = aGlobal.GetContext();
   WorkerPrivate* workerPrivate = GetWorkerPrivateFromContext(cx);
   MOZ_ASSERT(workerPrivate);
 
   nsRefPtr<XMLHttpRequest> xhr = new XMLHttpRequest(workerPrivate);
 
   if (workerPrivate->XHRParamsAllowed()) {
-    xhr->mMozAnon = aParams.mMozAnon;
+    if (aParams.mMozSystem)
+      xhr->mMozAnon = true;
+    else
+      xhr->mMozAnon = aParams.mMozAnon;
     xhr->mMozSystem = aParams.mMozSystem;
   }
 
   return xhr.forget();
 }
 
 void
 XMLHttpRequest::ReleaseProxy(ReleaseType aType)
--- a/dom/workers/test/test_xhr_system.html
+++ b/dom/workers/test/test_xhr_system.html
@@ -21,34 +21,32 @@ function message(event) {
   else if(event.data.test == 'is')
     is(event.data.a, event.data.b, event.data.event);
   else if(event.data.test == 'finish') {
     run();
   }
 };
 
 function test1() {
-  // ...and once with privileges.
-  SpecialPowers.addPermission("systemXHR", true, document);
   var worker = new Worker("test_xhr_system.js");
   worker.onmessage = message;
   worker.postMessage(true);
 }
 
 var tests = [ test1 ];
 function run() {
   if (!tests.length) {
-    SpecialPowers.removePermission("systemXHR", document);
     SimpleTest.finish();
     return;
   }
 
   var func = tests.shift();
   func();
 }
 
 SimpleTest.waitForExplicitFinish();
-run();
+
+SpecialPowers.pushPermissions([{'type': 'systemXHR', 'allow': true, 'context': document}], run);
 
 </script>
 </pre>
 </body>
 </html>
--- a/dom/workers/test/test_xhr_system.js
+++ b/dom/workers/test/test_xhr_system.js
@@ -6,17 +6,17 @@ function is(a, b, msg) {
   postMessage({ event: msg, test: 'is', a: a, b: b });
 }
 
 self.onmessage = function onmessage(event) {
 
   // An XHR with system privileges will be able to do cross-site calls.
 
   const TEST_URL = "http://example.com/tests/content/base/test/test_XHR_system.html";
-  is(location.hostname, "mochi.test", "hostname");
+  is(location.hostname, "mochi.test", "hostname should be mochi.test");
 
   var xhr = new XMLHttpRequest({mozSystem: true});
   is(xhr.mozSystem, true, ".mozSystem == true");
   xhr.open("GET", TEST_URL);
   xhr.onload = function onload() {
     is(xhr.status, 200);
     ok(xhr.responseText != null);
     ok(xhr.responseText.length);
--- a/gfx/gl/GLSharedHandleHelpers.cpp
+++ b/gfx/gl/GLSharedHandleHelpers.cpp
@@ -1,15 +1,18 @@
 /* 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 "GLContext.h"
 #include "GLLibraryEGL.h"
 #include "GLSharedHandleHelpers.h"
+#ifdef MOZ_WIDGET_ANDROID
+#include "nsSurfaceTexture.h"
+#endif
 
 namespace mozilla {
 namespace gl {
 
 enum SharedHandleType {
     SharedHandleType_Image
 #ifdef MOZ_WIDGET_ANDROID
     , SharedHandleType_SurfaceTexture
--- a/gfx/layers/CompositorTypes.h
+++ b/gfx/layers/CompositorTypes.h
@@ -59,28 +59,37 @@ const TextureFlags TEXTURE_TILE         
 // from the previous texture.
 const TextureFlags TEXTURE_COPY_PREVIOUS      = 1 << 24;
 // Who is responsible for deallocating the shared data.
 // if TEXTURE_DEALLOCATE_CLIENT is set, the shared data is deallocated on the
 // client side and requires some extra synchronizaion to ensure race-free
 // deallocation.
 // The default behaviour is to deallocate on the host side.
 const TextureFlags TEXTURE_DEALLOCATE_CLIENT  = 1 << 25;
+// The host side is responsible for deallocation, but that may not happen
+// immediately after the client side requests it. Exactly when the texture is
+// deallocated is up to the compositable. The texture must be deallocated by
+// the time the compositable or texture host is destroyed. A texture may not
+// have both TEXTURE_DEALLOCATE_CLIENT and TEXTURE_DEALLOCATE_DEFERRED flags.
+const TextureFlags TEXTURE_DEALLOCATE_DEFERRED  = 1 << 26;
 // After being shared ith the compositor side, an immutable texture is never
 // modified, it can only be read. It is safe to not Lock/Unlock immutable
 // textures.
 const TextureFlags TEXTURE_IMMUTABLE          = 1 << 27;
 // The contents of the texture must be uploaded or copied immediately
 // during the transaction, because the producer may want to write
 // to it again.
 const TextureFlags TEXTURE_IMMEDIATE_UPLOAD   = 1 << 28;
 // The texture is going to be used as part of a double
 // buffered pair, and so we can guarantee that the producer/consumer
 // won't be racing to access its contents.
 const TextureFlags TEXTURE_DOUBLE_BUFFERED    = 1 << 29;
+// We've previously tried a texture and it didn't work for some reason. If there
+// is a fallback available, try that.
+const TextureFlags TEXTURE_ALLOC_FALLBACK     = 1 << 31;
 
 // the default flags
 const TextureFlags TEXTURE_FLAGS_DEFAULT = TEXTURE_FRONT;
 
 static inline bool
 TextureRequiresLocking(TextureFlags aFlags)
 {
   // If we're not double buffered, or uploading
@@ -162,16 +171,18 @@ enum CompositableType
   BUFFER_BRIDGE,          // image bridge protocol
   BUFFER_CONTENT,         // thebes layer interface, single buffering
   BUFFER_CONTENT_DIRECT,  // thebes layer interface, double buffering
   BUFFER_CONTENT_INC,     // thebes layer interface, only sends incremental
                           // updates to a texture on the compositor side.
   BUFFER_TILED,           // tiled thebes layer
   // the new compositable types
   COMPOSITABLE_IMAGE,     // image with single buffering
+  COMPOSITABLE_CONTENT_SINGLE,  // thebes layer interface, single buffering
+  COMPOSITABLE_CONTENT_DOUBLE,  // thebes layer interface, double buffering
   BUFFER_COUNT
 };
 
 /**
  * How the texture host is used for composition,
  */
 enum DeprecatedTextureHostFlags
 {
--- a/gfx/layers/RotatedBuffer.cpp
+++ b/gfx/layers/RotatedBuffer.cpp
@@ -276,73 +276,82 @@ RotatedContentBuffer::GetContextForQuadr
   }
 
   return ctx.forget();
 }
 
 gfxContentType
 RotatedContentBuffer::BufferContentType()
 {
-  if (mBufferProvider) {
-    return mBufferProvider->GetContentType();
+  if (mDeprecatedBufferProvider) {
+    return mDeprecatedBufferProvider->GetContentType();
   }
-  if (mDTBuffer) {
-    switch (mDTBuffer->GetFormat()) {
-    case FORMAT_A8:
-      return GFX_CONTENT_ALPHA;
-    case FORMAT_B8G8R8A8:
-    case FORMAT_R8G8B8A8:
-      return GFX_CONTENT_COLOR_ALPHA;
-    default:
-      return GFX_CONTENT_COLOR;
+  if (mBufferProvider || mDTBuffer) {
+    SurfaceFormat format;
+
+    if (mBufferProvider) {
+      format = mBufferProvider->AsTextureClientDrawTarget()->GetFormat();
+    } else if (mDTBuffer) {
+      format = mDTBuffer->GetFormat();
     }
+
+    return ContentForFormat(format);
   }
   return GFX_CONTENT_SENTINEL;
 }
 
 bool
 RotatedContentBuffer::BufferSizeOkFor(const nsIntSize& aSize)
 {
   return (aSize == mBufferRect.Size() ||
           (SizedToVisibleBounds != mBufferSizePolicy &&
            aSize < mBufferRect.Size()));
 }
 
 bool
 RotatedContentBuffer::EnsureBuffer()
 {
-  if (!mDTBuffer && mBufferProvider) {
-    mDTBuffer = mBufferProvider->LockDrawTarget();
+  if (!mDTBuffer) {
+    if (mDeprecatedBufferProvider) {
+      mDTBuffer = mDeprecatedBufferProvider->LockDrawTarget();
+    } else if (mBufferProvider) {
+      mDTBuffer = mBufferProvider->AsTextureClientDrawTarget()->GetAsDrawTarget();
+    }
   }
 
   NS_WARN_IF_FALSE(mDTBuffer, "no buffer");
   return !!mDTBuffer;
 }
 
 bool
 RotatedContentBuffer::EnsureBufferOnWhite()
 {
-  if (!mDTBufferOnWhite && mBufferProviderOnWhite) {
-    mDTBufferOnWhite = mBufferProviderOnWhite->LockDrawTarget();
+  if (!mDTBufferOnWhite) {
+    if (mDeprecatedBufferProviderOnWhite) {
+      mDTBufferOnWhite = mDeprecatedBufferProviderOnWhite->LockDrawTarget();
+    } else if (mBufferProviderOnWhite) {
+      mDTBufferOnWhite =
+        mBufferProviderOnWhite->AsTextureClientDrawTarget()->GetAsDrawTarget();
+    }
   }
 
   NS_WARN_IF_FALSE(mDTBufferOnWhite, "no buffer");
   return mDTBufferOnWhite;
 }
 
 bool
 RotatedContentBuffer::HaveBuffer() const
 {
-  return mDTBuffer || mBufferProvider;
+  return mDTBuffer || mDeprecatedBufferProvider || mBufferProvider;
 }
 
 bool
 RotatedContentBuffer::HaveBufferOnWhite() const
 {
-  return mDTBufferOnWhite || mBufferProviderOnWhite;
+  return mDTBufferOnWhite || mDeprecatedBufferProviderOnWhite || mBufferProviderOnWhite;
 }
 
 static void
 WrapRotationAxis(int32_t* aRotationPoint, int32_t aSize)
 {
   if (*aRotationPoint < 0) {
     *aRotationPoint += aSize;
   } else if (*aRotationPoint >= aSize) {
--- a/gfx/layers/RotatedBuffer.h
+++ b/gfx/layers/RotatedBuffer.h
@@ -28,16 +28,17 @@ struct nsIntSize;
 namespace mozilla {
 namespace gfx {
 class Matrix;
 }
 
 namespace layers {
 
 class DeprecatedTextureClient;
+class TextureClient;
 class ThebesLayer;
 
 /**
  * This is a cairo/Thebes surface, but with a literal twist. Scrolling
  * causes the layer's visible region to move. We want to keep
  * reusing the same surface if the region size hasn't changed, but we don't
  * want to keep moving the contents of the surface around in memory. So
  * we use a trick.
@@ -152,17 +153,19 @@ public:
    *   fit visible bounds.  May be larger.
    */
   enum BufferSizePolicy {
     SizedToVisibleBounds,
     ContainsVisibleBounds
   };
 
   RotatedContentBuffer(BufferSizePolicy aBufferSizePolicy)
-    : mBufferProvider(nullptr)
+    : mDeprecatedBufferProvider(nullptr)
+    , mDeprecatedBufferProviderOnWhite(nullptr)
+    , mBufferProvider(nullptr)
     , mBufferProviderOnWhite(nullptr)
     , mBufferSizePolicy(aBufferSizePolicy)
   {
     MOZ_COUNT_CTOR(RotatedContentBuffer);
   }
   virtual ~RotatedContentBuffer()
   {
     MOZ_COUNT_DTOR(RotatedContentBuffer);
@@ -171,16 +174,18 @@ public:
   /**
    * Wipe out all retained contents. Call this when the entire
    * buffer becomes invalid.
    */
   void Clear()
   {
     mDTBuffer = nullptr;
     mDTBufferOnWhite = nullptr;
+    mDeprecatedBufferProvider = nullptr;
+    mDeprecatedBufferProviderOnWhite = nullptr;
     mBufferProvider = nullptr;
     mBufferProviderOnWhite = nullptr;
     mBufferRect.SetEmpty();
   }
 
   /**
    * This is returned by BeginPaint. The caller should draw into mContext.
    * mRegionToDraw must be drawn. mRegionToInvalidate has been invalidated
@@ -285,33 +290,58 @@ protected:
    * demand in this code.
    *
    * It's the caller's responsibility to ensure |aClient| is valid
    * for the duration of operations it requests of this
    * RotatedContentBuffer.  It's also the caller's responsibility to
    * unset the provider when inactive, by calling
    * SetBufferProvider(nullptr).
    */
-  void SetBufferProvider(DeprecatedTextureClient* aClient)
+  void SetDeprecatedBufferProvider(DeprecatedTextureClient* aClient)
   {
     // Only this buffer provider can give us a buffer.  If we
     // already have one, something has gone wrong.
-    MOZ_ASSERT(!aClient || !mDTBuffer);
+    MOZ_ASSERT((!aClient || !mDTBuffer) && !mBufferProvider);
+
+    mDeprecatedBufferProvider = aClient;
+    if (!mDeprecatedBufferProvider) {
+      mDTBuffer = nullptr;
+    } 
+  }
+  
+  void SetDeprecatedBufferProviderOnWhite(DeprecatedTextureClient* aClient)
+  {
+    // Only this buffer provider can give us a buffer.  If we
+    // already have one, something has gone wrong.
+    MOZ_ASSERT((!aClient || !mDTBufferOnWhite) && !mBufferProviderOnWhite);
+
+    mDeprecatedBufferProviderOnWhite = aClient;
+    if (!mDeprecatedBufferProviderOnWhite) {
+      mDTBufferOnWhite = nullptr;
+    }
+  }
+
+  // new texture client versions
+  void SetBufferProvider(TextureClient* aClient)
+  {
+    // Only this buffer provider can give us a buffer.  If we
+    // already have one, something has gone wrong.
+    MOZ_ASSERT((!aClient || !mDTBuffer) && !mDeprecatedBufferProvider);
 
     mBufferProvider = aClient;
     if (!mBufferProvider) {
       mDTBuffer = nullptr;
-    } 
+    }
   }
-  
-  void SetBufferProviderOnWhite(DeprecatedTextureClient* aClient)
+
+  void SetBufferProviderOnWhite(TextureClient* aClient)
   {
     // Only this buffer provider can give us a buffer.  If we
     // already have one, something has gone wrong.
-    MOZ_ASSERT(!aClient || !mDTBufferOnWhite);
+    MOZ_ASSERT((!aClient || !mDTBufferOnWhite) && !mDeprecatedBufferProviderOnWhite);
 
     mBufferProviderOnWhite = aClient;
     if (!mBufferProviderOnWhite) {
       mDTBufferOnWhite = nullptr;
     } 
   }
 
   /**
@@ -344,18 +374,20 @@ protected:
   virtual bool HaveBuffer() const;
   virtual bool HaveBufferOnWhite() const;
 
   /**
    * These members are only set transiently.  They're used to map mDTBuffer
    * when we're using surfaces that require explicit map/unmap. Only one
    * may be used at a time.
    */
-  DeprecatedTextureClient* mBufferProvider;
-  DeprecatedTextureClient* mBufferProviderOnWhite;
+  DeprecatedTextureClient* mDeprecatedBufferProvider;
+  DeprecatedTextureClient* mDeprecatedBufferProviderOnWhite;
+  TextureClient* mBufferProvider;
+  TextureClient* mBufferProviderOnWhite;
 
   BufferSizePolicy      mBufferSizePolicy;
 };
 
 }
 }
 
 #endif /* ROTATEDBUFFER_H_ */
--- a/gfx/layers/client/ClientThebesLayer.cpp
+++ b/gfx/layers/client/ClientThebesLayer.cpp
@@ -101,16 +101,28 @@ ClientThebesLayer::RenderLayer()
     mContentClient->Connect();
     ClientManager()->AsShadowForwarder()->Attach(mContentClient, this);
     MOZ_ASSERT(mContentClient->GetForwarder());
   }
 
   mContentClient->BeginPaint();
   PaintThebes();
   mContentClient->EndPaint();
+  // It is very important that this is called after EndPaint, because destroying
+  // textures is a three stage process:
+  // 1. We are done with the buffer and move it to ContentClient::mOldTextures,
+  // that happens in DestroyBuffers which is may be called indirectly from
+  // PaintThebes.
+  // 2. The content client calls RemoveTextureClient on the texture clients in
+  // mOldTextures and forgets them. They then become invalid. The compositable
+  // client keeps a record of IDs. This happens in EndPaint.
+  // 3. An IPC message is sent to destroy the corresponding texture host. That
+  // happens from OnTransaction.
+  // It is important that these steps happen in order.
+  mContentClient->OnTransaction();
 }
 
 void
 ClientThebesLayer::PaintBuffer(gfxContext* aContext,
                                const nsIntRegion& aRegionToDraw,
                                const nsIntRegion& aExtendedRegionToDraw,
                                const nsIntRegion& aRegionToInvalidate,
                                bool aDidSelfCopy, DrawRegionClip aClip)
--- a/gfx/layers/client/CompositableClient.cpp
+++ b/gfx/layers/client/CompositableClient.cpp
@@ -10,18 +10,21 @@
 #include "mozilla/layers/TextureClient.h"  // for DeprecatedTextureClient, etc
 #include "mozilla/layers/TextureClientOGL.h"
 #include "mozilla/mozalloc.h"           // for operator delete, etc
 #include "gfxASurface.h"                // for gfxContentType
 #ifdef XP_WIN
 #include "mozilla/layers/TextureD3D9.h"
 #include "mozilla/layers/TextureD3D11.h"
 #include "gfxWindowsPlatform.h"
+#include "gfx2DGlue.h"
 #endif
 
+using namespace mozilla::gfx;
+
 namespace mozilla {
 namespace layers {
 
 CompositableClient::CompositableClient(CompositableForwarder* aForwarder)
 : mNextTextureID(1)
 , mCompositableChild(nullptr)
 , mForwarder(aForwarder)
 {
@@ -188,18 +191,18 @@ CompositableClient::CreateDeprecatedText
   MOZ_ASSERT(result->SupportsType(aDeprecatedTextureClientType),
              "Created the wrong texture client?");
   result->SetFlags(GetTextureInfo().mTextureFlags);
 
   return result.forget();
 }
 
 TemporaryRef<BufferTextureClient>
-CompositableClient::CreateBufferTextureClient(gfx::SurfaceFormat aFormat,
-                                              uint32_t aTextureFlags)
+CompositableClient::CreateBufferTextureClient(SurfaceFormat aFormat,
+                                              TextureFlags aTextureFlags)
 {
 // XXX - Once bug 908196 is fixed, we can use gralloc textures here which will
 // improve performances of videos using SharedPlanarYCbCrImage on b2g.
 //#ifdef MOZ_WIDGET_GONK
 //  {
 //    RefPtr<BufferTextureClient> result = new GrallocTextureClientOGL(this,
 //                                                                     aFormat,
 //                                                                     aTextureFlags);
@@ -209,25 +212,66 @@ CompositableClient::CreateBufferTextureC
   if (gfxPlatform::GetPlatform()->PreferMemoryOverShmem()) {
     RefPtr<BufferTextureClient> result = new MemoryTextureClient(this, aFormat, aTextureFlags);
     return result.forget();
   }
   RefPtr<BufferTextureClient> result = new ShmemTextureClient(this, aFormat, aTextureFlags);
   return result.forget();
 }
 
-bool
-CompositableClient::AddTextureClient(TextureClient* aClient)
+TemporaryRef<TextureClient>
+CompositableClient::CreateTextureClientForDrawing(SurfaceFormat aFormat,
+                                                  TextureFlags aTextureFlags)
+{
+  RefPtr<TextureClient> result;
+
+#ifdef XP_WIN
+  LayersBackend parentBackend = GetForwarder()->GetCompositorBackendType();
+  // XXX[nrc] uncomment once we have new texture clients for windows
+  if (parentBackend == LAYERS_D3D11 && gfxWindowsPlatform::GetPlatform()->GetD2DDevice() &&
+      !(aTextureFlags & TEXTURE_ALLOC_FALLBACK)) {
+    //result = new TextureClientD3D11(GetForwarder(), GetTextureInfo());
+  }
+  if (parentBackend == LAYERS_D3D9 &&
+      !GetForwarder()->ForwardsToDifferentProcess() &&
+      !(aTextureFlags & TEXTURE_ALLOC_FALLBACK)) {
+    // non-DIB textures don't work with alpha, see notes in TextureD3D9.
+    if (ContentForFormat(aFormat) == GFX_CONTENT_COLOR_ALPHA) {
+      //result = new TextureClientDIB(GetForwarder(), GetTextureInfo());
+    } else {
+      //result = new TextureClientD3D9(GetForwarder(), GetTextureInfo());
+    }
+  }
+#endif
+  // Can't do any better than a buffer texture client.
+  if (!result) {
+    result = CreateBufferTextureClient(aFormat, aTextureFlags);
+  }
+
+  MOZ_ASSERT(!result || result->AsTextureClientDrawTarget(),
+             "Not a TextureClientDrawTarget?");
+  return result;
+}
+
+uint64_t
+CompositableClient::NextTextureID()
 {
   ++mNextTextureID;
   // 0 is always an invalid ID
   if (mNextTextureID == 0) {
     ++mNextTextureID;
   }
-  aClient->SetID(mNextTextureID);
+
+  return mNextTextureID;
+}
+
+bool
+CompositableClient::AddTextureClient(TextureClient* aClient)
+{
+  aClient->SetID(NextTextureID());
   return mForwarder->AddTexture(this, aClient);
 }
 
 void
 CompositableClient::RemoveTextureClient(TextureClient* aClient)
 {
   MOZ_ASSERT(aClient);
   mTexturesToRemove.AppendElement(TextureIDAndFlags(aClient->GetID(),
--- a/gfx/layers/client/CompositableClient.h
+++ b/gfx/layers/client/CompositableClient.h
@@ -83,16 +83,22 @@ public:
   TemporaryRef<DeprecatedTextureClient>
   CreateDeprecatedTextureClient(DeprecatedTextureClientType aDeprecatedTextureClientType,
                                 gfxContentType aContentType = GFX_CONTENT_SENTINEL);
 
   virtual TemporaryRef<BufferTextureClient>
   CreateBufferTextureClient(gfx::SurfaceFormat aFormat,
                             TextureFlags aFlags = TEXTURE_FLAGS_DEFAULT);
 
+  // If we return a non-null TextureClient, then AsTextureClientDrawTarget will
+  // always be non-null.
+  TemporaryRef<TextureClient>
+  CreateTextureClientForDrawing(gfx::SurfaceFormat aFormat,
+                                TextureFlags aTextureFlags);
+
   virtual void SetDescriptorFromReply(TextureIdentifier aTextureId,
                                       const SurfaceDescriptor& aDescriptor)
   {
     MOZ_CRASH("If you want to call this, you should have implemented it");
   }
 
   /**
    * Establishes the connection with compositor side through IPDL
@@ -163,16 +169,19 @@ public:
   void FlushTexturesToRemoveCallbacks();
 
   /**
    * Our IPDL actor is being destroyed, get rid of any shmem resources now.
    */
   virtual void OnActorDestroy() = 0;
 
 protected:
+  // return the next texture ID
+  uint64_t NextTextureID();
+
   struct TextureIDAndFlags {
     TextureIDAndFlags(uint64_t aID, TextureFlags aFlags)
     : mID(aID), mFlags(aFlags) {}
     uint64_t mID;
     TextureFlags mFlags;
   };
   // The textures to destroy in the next transaction;
   nsTArray<TextureIDAndFlags> mTexturesToRemove;
--- a/gfx/layers/client/ContentClient.cpp
+++ b/gfx/layers/client/ContentClient.cpp
@@ -45,37 +45,51 @@ ContentClient::CreateContentClient(Compo
   if (backend != LAYERS_OPENGL &&
       backend != LAYERS_D3D9 &&
       backend != LAYERS_D3D11 &&
       backend != LAYERS_BASIC) {
     return nullptr;
   }
 
   bool useDoubleBuffering = false;
+  bool useDeprecatedTextures = true;
+  // XXX We need support for gralloc with non-deprecated textures content before
+  // we can use them with FirefoxOS (bug 946720). We need the same locking for
+  // Windows.
+#if !defined(MOZ_WIDGET_GONK) && !defined(XP_WIN)
+  useDeprecatedTextures = gfxPlatform::GetPlatform()->UseDeprecatedTextures();
+#endif
 
 #ifdef XP_WIN
   if (backend == LAYERS_D3D11) {
     useDoubleBuffering = !!gfxWindowsPlatform::GetPlatform()->GetD2DDevice();
   } else
 #endif
   {
     useDoubleBuffering = LayerManagerComposite::SupportsDirectTexturing() ||
                          backend == LAYERS_BASIC;
   }
 
   if (useDoubleBuffering || PR_GetEnv("MOZ_FORCE_DOUBLE_BUFFERING")) {
-    return new ContentClientDoubleBuffered(aForwarder);
+    if (useDeprecatedTextures) {
+      return new DeprecatedContentClientDoubleBuffered(aForwarder);
+    } else {
+      return new ContentClientDoubleBuffered(aForwarder);
+    }
   }
 #ifdef XP_MACOSX
   if (backend == LAYERS_OPENGL) {
     return new ContentClientIncremental(aForwarder);
   }
 #endif
-  return new ContentClientSingleBuffered(aForwarder);
-
+  if (useDeprecatedTextures) {
+    return new DeprecatedContentClientSingleBuffered(aForwarder);
+  } else {
+    return new ContentClientSingleBuffered(aForwarder);
+  }
 }
 
 // We pass a null pointer for the ContentClient Forwarder argument, which means
 // this client will not have a ContentHost on the other side.
 ContentClientBasic::ContentClientBasic()
   : ContentClient(nullptr)
   , RotatedContentBuffer(ContainsVisibleBounds)
 {}
@@ -95,61 +109,261 @@ ContentClientBasic::CreateBuffer(Content
   *aBlackDT = gfxPlatform::GetPlatform()->CreateOffscreenContentDrawTarget(
     IntSize(aRect.width, aRect.height),
     ImageFormatToSurfaceFormat(format));
 }
 
 void
 ContentClientRemoteBuffer::DestroyBuffers()
 {
-  if (!mDeprecatedTextureClient) {
+  if (!mTextureClient) {
     return;
   }
 
-  MOZ_ASSERT(mDeprecatedTextureClient->GetAccessMode() == DeprecatedTextureClient::ACCESS_READ_WRITE);
-  mDeprecatedTextureClient = nullptr;
-  mDeprecatedTextureClientOnWhite = nullptr;
+  mOldTextures.AppendElement(mTextureClient);
+  mTextureClient = nullptr;
+  if (mTextureClientOnWhite) {
+    mOldTextures.AppendElement(mTextureClientOnWhite);
+    mTextureClientOnWhite = nullptr;
+  }
 
   DestroyFrontBuffer();
-
-  mForwarder->DestroyThebesBuffer(this);
 }
 
 void
 ContentClientRemoteBuffer::BeginPaint()
 {
   // XXX: So we might not have a DeprecatedTextureClient yet.. because it will
   // only be created by CreateBuffer.. which will deliver a locked surface!.
-  if (mDeprecatedTextureClient) {
-    SetBufferProvider(mDeprecatedTextureClient);
+  if (mTextureClient) {
+    SetBufferProvider(mTextureClient);
   }
-  if (mDeprecatedTextureClientOnWhite) {
-    SetBufferProviderOnWhite(mDeprecatedTextureClientOnWhite);
+  if (mTextureClientOnWhite) {
+    SetBufferProviderOnWhite(mTextureClientOnWhite);
   }
 }
 
 void
 ContentClientRemoteBuffer::EndPaint()
 {
   // XXX: We might still not have a texture client if PaintThebes
   // decided we didn't need one yet because the region to draw was empty.
   SetBufferProvider(nullptr);
   SetBufferProviderOnWhite(nullptr);
+  for (size_t i = 0; i < mOldTextures.Length(); ++i) {
+    RemoveTextureClient(mOldTextures[i]);
+  }
+  mOldTextures.Clear();
+
+  if (mTextureClient) {
+    mTextureClient->Unlock();
+  }
+  if (mTextureClientOnWhite) {
+    mTextureClientOnWhite->Unlock();
+  }
+}
+
+bool
+ContentClientRemoteBuffer::CreateAndAllocateTextureClient(RefPtr<TextureClient>& aClient,
+                                                          TextureFlags aFlags)
+{
+  aClient = CreateTextureClientForDrawing(mSurfaceFormat,
+                                          mTextureInfo.mTextureFlags | aFlags);
+  if (!aClient) {
+    return false;
+  }
+
+  if (!aClient->AsTextureClientDrawTarget()->AllocateForSurface(mSize, ALLOC_CLEAR_BUFFER)) {
+    aClient = CreateTextureClientForDrawing(mSurfaceFormat,
+                mTextureInfo.mTextureFlags | TEXTURE_ALLOC_FALLBACK | aFlags);
+    if (!aClient) {
+      return false;
+    }
+    if (!aClient->AsTextureClientDrawTarget()->AllocateForSurface(mSize, ALLOC_CLEAR_BUFFER)) {
+      NS_WARNING("Could not allocate texture client");
+      aClient = nullptr;
+      return false;
+    }
+  }
+
+  NS_WARN_IF_FALSE(aClient->IsValid(), "Created an invalid texture client");
+  return true;
+}
+
+void
+ContentClientRemoteBuffer::BuildTextureClients(SurfaceFormat aFormat,
+                                               const nsIntRect& aRect,
+                                               uint32_t aFlags)
+{
+  // If we hit this assertion, then it might be due to an empty transaction
+  // followed by a real transaction. Our buffers should be created (but not
+  // painted in the empty transaction) and then painted (but not created) in the
+  // real transaction. That is kind of fragile, and this assert will catch
+  // circumstances where we screw that up, e.g., by unnecessarily recreating our
+  // buffers.
+  NS_ABORT_IF_FALSE(!mIsNewBuffer,
+                    "Bad! Did we create a buffer twice without painting?");
+
+  mIsNewBuffer = true;
+
+  DestroyBuffers();
+
+  mSurfaceFormat = aFormat;
+  mSize = gfx::IntSize(aRect.width, aRect.height);
+  mTextureInfo.mTextureFlags = (aFlags & ~TEXTURE_DEALLOCATE_CLIENT) |
+                               TEXTURE_DEALLOCATE_DEFERRED;
+
+  if (!CreateAndAllocateTextureClient(mTextureClient, TEXTURE_ON_BLACK) ||
+      !AddTextureClient(mTextureClient)) {
+    AbortTextureClientCreation();
+    return;
+  }
+
+  if (aFlags & BUFFER_COMPONENT_ALPHA) {
+    if (!CreateAndAllocateTextureClient(mTextureClientOnWhite, TEXTURE_ON_WHITE) ||
+        !AddTextureClient(mTextureClientOnWhite)) {
+      AbortTextureClientCreation();
+      return;
+    }
+    mTextureInfo.mTextureFlags |= TEXTURE_COMPONENT_ALPHA;
+  }
+
+  CreateFrontBuffer(aRect);
+}
+
+void
+ContentClientRemoteBuffer::CreateBuffer(ContentType aType,
+                                        const nsIntRect& aRect,
+                                        uint32_t aFlags,
+                                        RefPtr<gfx::DrawTarget>* aBlackDT,
+                                        RefPtr<gfx::DrawTarget>* aWhiteDT)
+{
+  BuildTextureClients(gfxPlatform::GetPlatform()->Optimal2DFormatForContent(aType), aRect, aFlags);
+  if (!mTextureClient) {
+    return;
+  }
+
+  *aBlackDT = mTextureClient->AsTextureClientDrawTarget()->GetAsDrawTarget();
+  if (aFlags & BUFFER_COMPONENT_ALPHA) {
+    *aWhiteDT = mTextureClientOnWhite->AsTextureClientDrawTarget()->GetAsDrawTarget();
+  }
+}
+
+nsIntRegion
+ContentClientRemoteBuffer::GetUpdatedRegion(const nsIntRegion& aRegionToDraw,
+                                            const nsIntRegion& aVisibleRegion,
+                                            bool aDidSelfCopy)
+{
+  nsIntRegion updatedRegion;
+  if (mIsNewBuffer || aDidSelfCopy) {
+    // A buffer reallocation clears both buffers. The front buffer has all the
+    // content by now, but the back buffer is still clear. Here, in effect, we
+    // are saying to copy all of the pixels of the front buffer to the back.
+    // Also when we self-copied in the buffer, the buffer space
+    // changes and some changed buffer content isn't reflected in the
+    // draw or invalidate region (on purpose!).  When this happens, we
+    // need to read back the entire buffer too.
+    updatedRegion = aVisibleRegion;
+    mIsNewBuffer = false;
+  } else {
+    updatedRegion = aRegionToDraw;
+  }
+
+  NS_ASSERTION(BufferRect().Contains(aRegionToDraw.GetBounds()),
+               "Update outside of buffer rect!");
+  NS_ABORT_IF_FALSE(mTextureClient, "should have a back buffer by now");
+
+  return updatedRegion;
+}
+
+void
+ContentClientRemoteBuffer::Updated(const nsIntRegion& aRegionToDraw,
+                                   const nsIntRegion& aVisibleRegion,
+                                   bool aDidSelfCopy)
+{
+  nsIntRegion updatedRegion = GetUpdatedRegion(aRegionToDraw,
+                                               aVisibleRegion,
+                                               aDidSelfCopy);
+
+  MOZ_ASSERT(mTextureClient);
+  mForwarder->UseTexture(this, mTextureClient);
+  mForwarder->UpdateTextureRegion(this,
+                                  ThebesBufferData(BufferRect(),
+                                                   BufferRotation()),
+                                  updatedRegion);
+}
+
+void
+ContentClientRemoteBuffer::SwapBuffers(const nsIntRegion& aFrontUpdatedRegion)
+{
+  MOZ_ASSERT(mTextureClient);
+  mFrontAndBackBufferDiffer = true;
+}
+
+void
+ContentClientRemoteBuffer::OnActorDestroy()
+{
+  if (mTextureClient) {
+    mTextureClient->OnActorDestroy();
+  }
+  if (mTextureClientOnWhite) {
+    mTextureClientOnWhite->OnActorDestroy();
+  }
+  for (size_t i = 0; i < mOldTextures.Length(); ++i) {
+    mOldTextures[i]->OnActorDestroy();
+  }
+}
+
+void
+DeprecatedContentClientRemoteBuffer::DestroyBuffers()
+{
+  if (!mDeprecatedTextureClient) {
+    return;
+  }
+
+  mDeprecatedTextureClient = nullptr;
+  mDeprecatedTextureClientOnWhite = nullptr;
+
+  DestroyFrontBuffer();
+
+  mForwarder->DestroyThebesBuffer(this);
+}
+
+void
+DeprecatedContentClientRemoteBuffer::BeginPaint()
+{
+  // XXX: So we might not have a DeprecatedTextureClient yet.. because it will
+  // only be created by CreateBuffer.. which will deliver a locked surface!.
+  if (mDeprecatedTextureClient) {
+    SetDeprecatedBufferProvider(mDeprecatedTextureClient);
+  }
+  if (mDeprecatedTextureClientOnWhite) {
+    SetDeprecatedBufferProviderOnWhite(mDeprecatedTextureClientOnWhite);
+  }
+}
+
+void
+DeprecatedContentClientRemoteBuffer::EndPaint()
+{
+  // XXX: We might still not have a texture client if PaintThebes
+  // decided we didn't need one yet because the region to draw was empty.
+  SetDeprecatedBufferProvider(nullptr);
+  SetDeprecatedBufferProviderOnWhite(nullptr);
   mOldTextures.Clear();
 
   if (mDeprecatedTextureClient) {
     mDeprecatedTextureClient->Unlock();
   }
   if (mDeprecatedTextureClientOnWhite) {
     mDeprecatedTextureClientOnWhite->Unlock();
   }
 }
 
 bool
-ContentClientRemoteBuffer::CreateAndAllocateDeprecatedTextureClient(RefPtr<DeprecatedTextureClient>& aClient)
+DeprecatedContentClientRemoteBuffer::CreateAndAllocateDeprecatedTextureClient(RefPtr<DeprecatedTextureClient>& aClient)
 {
   aClient = CreateDeprecatedTextureClient(TEXTURE_CONTENT, mContentType);
   MOZ_ASSERT(aClient, "Failed to create texture client");
 
   if (!aClient->EnsureAllocated(mSize, mContentType)) {
     aClient = CreateDeprecatedTextureClient(TEXTURE_FALLBACK, mContentType);
     MOZ_ASSERT(aClient, "Failed to create texture client");
     if (!aClient->EnsureAllocated(mSize, mContentType)) {
@@ -160,17 +374,17 @@ ContentClientRemoteBuffer::CreateAndAllo
     }
   }
 
   MOZ_ASSERT(IsSurfaceDescriptorValid(*aClient->GetDescriptor()));
   return true;
 }
 
 void
-ContentClientRemoteBuffer::BuildDeprecatedTextureClients(ContentType aType,
+DeprecatedContentClientRemoteBuffer::BuildDeprecatedTextureClients(ContentType aType,
                                                const nsIntRect& aRect,
                                                uint32_t aFlags)
 {
   NS_ABORT_IF_FALSE(!mIsNewBuffer,
                     "Bad! Did we create a buffer twice without painting?");
 
   if (mDeprecatedTextureClient) {
     mOldTextures.AppendElement(mDeprecatedTextureClient);
@@ -197,17 +411,17 @@ ContentClientRemoteBuffer::BuildDeprecat
     mTextureInfo.mTextureFlags |= TEXTURE_COMPONENT_ALPHA;
   }
 
   CreateFrontBufferAndNotify(aRect);
   mIsNewBuffer = true;
 }
 
 void
-ContentClientRemoteBuffer::CreateBuffer(ContentType aType,
+DeprecatedContentClientRemoteBuffer::CreateBuffer(ContentType aType,
                                         const nsIntRect& aRect,
                                         uint32_t aFlags,
                                         RefPtr<gfx::DrawTarget>* aBlackDT,
                                         RefPtr<gfx::DrawTarget>* aWhiteDT)
 {
   BuildDeprecatedTextureClients(aType, aRect, aFlags);
   if (!mDeprecatedTextureClient) {
     return;
@@ -217,17 +431,17 @@ ContentClientRemoteBuffer::CreateBuffer(
         mDeprecatedTextureClient->BackendType()));
   *aBlackDT = mDeprecatedTextureClient->LockDrawTarget();
   if (aFlags & BUFFER_COMPONENT_ALPHA) {
     *aWhiteDT = mDeprecatedTextureClientOnWhite->LockDrawTarget();
   }
 }
 
 nsIntRegion
-ContentClientRemoteBuffer::GetUpdatedRegion(const nsIntRegion& aRegionToDraw,
+DeprecatedContentClientRemoteBuffer::GetUpdatedRegion(const nsIntRegion& aRegionToDraw,
                                             const nsIntRegion& aVisibleRegion,
                                             bool aDidSelfCopy)
 {
   nsIntRegion updatedRegion;
   if (mIsNewBuffer || aDidSelfCopy) {
     // A buffer reallocation clears both buffers. The front buffer has all the
     // content by now, but the back buffer is still clear. Here, in effect, we
     // are saying to copy all of the pixels of the front buffer to the back.
@@ -244,17 +458,17 @@ ContentClientRemoteBuffer::GetUpdatedReg
   NS_ASSERTION(BufferRect().Contains(aRegionToDraw.GetBounds()),
                "Update outside of buffer rect!");
   NS_ABORT_IF_FALSE(mDeprecatedTextureClient, "should have a back buffer by now");
 
   return updatedRegion;
 }
 
 void
-ContentClientRemoteBuffer::Updated(const nsIntRegion& aRegionToDraw,
+DeprecatedContentClientRemoteBuffer::Updated(const nsIntRegion& aRegionToDraw,
                                    const nsIntRegion& aVisibleRegion,
                                    bool aDidSelfCopy)
 {
   nsIntRegion updatedRegion = GetUpdatedRegion(aRegionToDraw,
                                                aVisibleRegion,
                                                aDidSelfCopy);
 
   MOZ_ASSERT(mDeprecatedTextureClient);
@@ -265,60 +479,234 @@ ContentClientRemoteBuffer::Updated(const
   LockFrontBuffer();
   mForwarder->UpdateTextureRegion(this,
                                   ThebesBufferData(BufferRect(),
                                                    BufferRotation()),
                                   updatedRegion);
 }
 
 void
-ContentClientRemoteBuffer::SwapBuffers(const nsIntRegion& aFrontUpdatedRegion)
+DeprecatedContentClientRemoteBuffer::SwapBuffers(const nsIntRegion& aFrontUpdatedRegion)
 {
   MOZ_ASSERT(mDeprecatedTextureClient->GetAccessMode() == DeprecatedTextureClient::ACCESS_NONE);
   MOZ_ASSERT(!mDeprecatedTextureClientOnWhite || mDeprecatedTextureClientOnWhite->GetAccessMode() == DeprecatedTextureClient::ACCESS_NONE);
   MOZ_ASSERT(mDeprecatedTextureClient);
 
   mFrontAndBackBufferDiffer = true;
   mDeprecatedTextureClient->SetAccessMode(DeprecatedTextureClient::ACCESS_READ_WRITE);
   if (mDeprecatedTextureClientOnWhite) {
     mDeprecatedTextureClientOnWhite->SetAccessMode(DeprecatedTextureClient::ACCESS_READ_WRITE);
   }
 }
 
 
 void
-ContentClientRemoteBuffer::OnActorDestroy()
+DeprecatedContentClientRemoteBuffer::OnActorDestroy()
 {
   if (mDeprecatedTextureClient) {
     mDeprecatedTextureClient->OnActorDestroy();
   }
   if (mDeprecatedTextureClientOnWhite) {
     mDeprecatedTextureClientOnWhite->OnActorDestroy();
   }
   for (size_t i = 0; i < mOldTextures.Length(); ++i) {
     mOldTextures[i]->OnActorDestroy();
   }
 }
+ 
+void
+ContentClientDoubleBuffered::CreateFrontBuffer(const nsIntRect& aBufferRect)
+{
+  if (!CreateAndAllocateTextureClient(mFrontClient, TEXTURE_ON_BLACK) ||
+      !AddTextureClient(mFrontClient)) {
+    AbortTextureClientCreation();
+    return;
+  }
+  if (mTextureInfo.mTextureFlags & TEXTURE_COMPONENT_ALPHA) {
+    if (!CreateAndAllocateTextureClient(mFrontClientOnWhite, TEXTURE_ON_WHITE) ||
+        !AddTextureClient(mFrontClientOnWhite)) {
+      AbortTextureClientCreation();
+      return;
+    }
+  }
 
-ContentClientDoubleBuffered::~ContentClientDoubleBuffered()
+  mFrontBufferRect = aBufferRect;
+  mFrontBufferRotation = nsIntPoint();
+}
+
+void
+ContentClientDoubleBuffered::DestroyFrontBuffer()
+{
+  MOZ_ASSERT(mFrontClient);
+
+  mOldTextures.AppendElement(mFrontClient);
+  mFrontClient = nullptr;
+  if (mFrontClientOnWhite) {
+    mOldTextures.AppendElement(mFrontClientOnWhite);
+    mFrontClientOnWhite = nullptr;
+  }
+}
+
+void
+ContentClientDoubleBuffered::SwapBuffers(const nsIntRegion& aFrontUpdatedRegion)
+{
+  mFrontUpdatedRegion = aFrontUpdatedRegion;
+
+  RefPtr<TextureClient> oldBack = mTextureClient;
+  mTextureClient = mFrontClient;
+  mFrontClient = oldBack;
+
+  oldBack = mTextureClientOnWhite;
+  mTextureClientOnWhite = mFrontClientOnWhite;
+  mFrontClientOnWhite = oldBack;
+
+  nsIntRect oldBufferRect = mBufferRect;
+  mBufferRect = mFrontBufferRect;
+  mFrontBufferRect = oldBufferRect;
+
+  nsIntPoint oldBufferRotation = mBufferRotation;
+  mBufferRotation = mFrontBufferRotation;
+  mFrontBufferRotation = oldBufferRotation;
+
+  MOZ_ASSERT(mFrontClient);
+
+  ContentClientRemoteBuffer::SwapBuffers(aFrontUpdatedRegion);
+}
+
+void
+ContentClientDoubleBuffered::SyncFrontBufferToBackBuffer()
+{
+  if (!mFrontAndBackBufferDiffer) {
+    return;
+  }
+  MOZ_ASSERT(mFrontClient);
+
+  MOZ_LAYERS_LOG(("BasicShadowableThebes(%p): reading back <x=%d,y=%d,w=%d,h=%d>",
+                  this,
+                  mFrontUpdatedRegion.GetBounds().x,
+                  mFrontUpdatedRegion.GetBounds().y,
+                  mFrontUpdatedRegion.GetBounds().width,
+                  mFrontUpdatedRegion.GetBounds().height));
+
+  nsIntRegion updateRegion = mFrontUpdatedRegion;
+
+  // This is a tricky trade off, we're going to get stuff out of our
+  // frontbuffer now, but the next PaintThebes might throw it all (or mostly)
+  // away if the visible region has changed. This is why in reality we want
+  // this code integrated with PaintThebes to always do the optimal thing.
+
+  if (mDidSelfCopy) {
+    mDidSelfCopy = false;
+    // We can't easily draw our front buffer into us, since we're going to be
+    // copying stuff around anyway it's easiest if we just move our situation
+    // to non-rotated while we're at it. If this situation occurs we'll have
+    // hit a self-copy path in PaintThebes before as well anyway.
+    mBufferRect.MoveTo(mFrontBufferRect.TopLeft());
+    mBufferRotation = nsIntPoint();
+    updateRegion = mBufferRect;
+  } else {
+    mBufferRect = mFrontBufferRect;
+    mBufferRotation = mFrontBufferRotation;
+  }
+
+  mIsNewBuffer = false;
+  mFrontAndBackBufferDiffer = false;
+
+  // We need to ensure that we lock these two buffers in the same
+  // order as the compositor to prevent deadlocks.
+  if (!mFrontClient->Lock(OPEN_READ_ONLY)) {
+    return;
+  }
+  if (mFrontClientOnWhite &&
+      !mFrontClientOnWhite->Lock(OPEN_READ_ONLY)) {
+    mFrontClient->Unlock();
+    return;
+  }
+  RefPtr<DrawTarget> dt =
+    mFrontClient->AsTextureClientDrawTarget()->GetAsDrawTarget();
+  RefPtr<DrawTarget> dtOnWhite = mFrontClientOnWhite
+    ? mFrontClientOnWhite->AsTextureClientDrawTarget()->GetAsDrawTarget()
+    : nullptr;
+  RotatedBuffer frontBuffer(dt,
+                            dtOnWhite,
+                            mFrontBufferRect,
+                            mFrontBufferRotation);
+  UpdateDestinationFrom(frontBuffer, updateRegion);
+  mFrontClient->Unlock();
+  if (mFrontClientOnWhite) {
+    mFrontClientOnWhite->Unlock();
+  }
+}
+
+void
+ContentClientDoubleBuffered::UpdateDestinationFrom(const RotatedBuffer& aSource,
+                                                   const nsIntRegion& aUpdateRegion)
+{
+  nsRefPtr<gfxContext> destCtx =
+    GetContextForQuadrantUpdate(aUpdateRegion.GetBounds(), BUFFER_BLACK);
+  destCtx->SetOperator(gfxContext::OPERATOR_SOURCE);
+
+  bool isClippingCheap = IsClippingCheap(destCtx, aUpdateRegion);
+  if (isClippingCheap) {
+    gfxUtils::ClipToRegion(destCtx, aUpdateRegion);
+  }
+
+  aSource.DrawBufferWithRotation(destCtx->GetDrawTarget(), BUFFER_BLACK, 1.0, OP_SOURCE);
+
+  if (aSource.HaveBufferOnWhite()) {
+    MOZ_ASSERT(HaveBufferOnWhite());
+    nsRefPtr<gfxContext> destCtx =
+      GetContextForQuadrantUpdate(aUpdateRegion.GetBounds(), BUFFER_WHITE);
+    destCtx->SetOperator(gfxContext::OPERATOR_SOURCE);
+
+    bool isClippingCheap = IsClippingCheap(destCtx, aUpdateRegion);
+    if (isClippingCheap) {
+      gfxUtils::ClipToRegion(destCtx, aUpdateRegion);
+    }
+
+    aSource.DrawBufferWithRotation(destCtx->GetDrawTarget(), BUFFER_WHITE, 1.0, OP_SOURCE);
+  }
+}
+
+void
+ContentClientDoubleBuffered::OnActorDestroy()
+{
+  if (mTextureClient) {
+    mTextureClient->OnActorDestroy();
+  }
+  if (mTextureClientOnWhite) {
+    mTextureClientOnWhite->OnActorDestroy();
+  }
+  for (size_t i = 0; i < mOldTextures.Length(); ++i) {
+    mOldTextures[i]->OnActorDestroy();
+  }
+  if (mFrontClient) {
+    mFrontClient->OnActorDestroy();
+  }
+  if (mFrontClientOnWhite) {
+    mFrontClientOnWhite->OnActorDestroy();
+  }
+}
+
+DeprecatedContentClientDoubleBuffered::~DeprecatedContentClientDoubleBuffered()
 {
   if (mDeprecatedTextureClient) {
     MOZ_ASSERT(mFrontClient);
     mDeprecatedTextureClient->SetDescriptor(SurfaceDescriptor());
     mFrontClient->SetDescriptor(SurfaceDescriptor());
   }
   if (mDeprecatedTextureClientOnWhite) {
     MOZ_ASSERT(mFrontClientOnWhite);
     mDeprecatedTextureClientOnWhite->SetDescriptor(SurfaceDescriptor());
     mFrontClientOnWhite->SetDescriptor(SurfaceDescriptor());
   }
 }
 
 void
-ContentClientDoubleBuffered::CreateFrontBufferAndNotify(const nsIntRect& aBufferRect)
+DeprecatedContentClientDoubleBuffered::CreateFrontBufferAndNotify(const nsIntRect& aBufferRect)
 {
   if (!CreateAndAllocateDeprecatedTextureClient(mFrontClient)) {
     mDeprecatedTextureClient->SetFlags(0);
     mDeprecatedTextureClient = nullptr;
     if (mDeprecatedTextureClientOnWhite) {
       mDeprecatedTextureClientOnWhite->SetFlags(0);
       mDeprecatedTextureClientOnWhite = nullptr;
     }
@@ -344,37 +732,37 @@ ContentClientDoubleBuffered::CreateFront
                                   *mFrontClient->LockSurfaceDescriptor(),
                                   *mDeprecatedTextureClient->LockSurfaceDescriptor(),
                                   mTextureInfo,
                                   mFrontClientOnWhite ? mFrontClientOnWhite->LockSurfaceDescriptor() : nullptr,
                                   mDeprecatedTextureClientOnWhite ? mDeprecatedTextureClientOnWhite->LockSurfaceDescriptor() : nullptr);
 }
 
 void
-ContentClientDoubleBuffered::DestroyFrontBuffer()
+DeprecatedContentClientDoubleBuffered::DestroyFrontBuffer()
 {
   MOZ_ASSERT(mFrontClient);
   MOZ_ASSERT(mFrontClient->GetAccessMode() != DeprecatedTextureClient::ACCESS_NONE);
 
   mFrontClient = nullptr;
   mFrontClientOnWhite = nullptr;
 }
 
 void
-ContentClientDoubleBuffered::LockFrontBuffer()
+DeprecatedContentClientDoubleBuffered::LockFrontBuffer()
 {
   MOZ_ASSERT(mFrontClient);
   mFrontClient->SetAccessMode(DeprecatedTextureClient::ACCESS_NONE);
   if (mFrontClientOnWhite) {
     mFrontClientOnWhite->SetAccessMode(DeprecatedTextureClient::ACCESS_NONE);
   }
 }
 
 void
-ContentClientDoubleBuffered::SwapBuffers(const nsIntRegion& aFrontUpdatedRegion)
+DeprecatedContentClientDoubleBuffered::SwapBuffers(const nsIntRegion& aFrontUpdatedRegion)
 {
   mFrontUpdatedRegion = aFrontUpdatedRegion;
 
   RefPtr<DeprecatedTextureClient> oldBack = mDeprecatedTextureClient;
   mDeprecatedTextureClient = mFrontClient;
   mFrontClient = oldBack;
 
   oldBack = mDeprecatedTextureClientOnWhite;
@@ -390,21 +778,21 @@ ContentClientDoubleBuffered::SwapBuffers
   mFrontBufferRotation = oldBufferRotation;
 
   MOZ_ASSERT(mFrontClient);
   mFrontClient->SetAccessMode(DeprecatedTextureClient::ACCESS_READ_ONLY);
   if (mFrontClientOnWhite) {
     mFrontClientOnWhite->SetAccessMode(DeprecatedTextureClient::ACCESS_READ_ONLY);
   }
 
-  ContentClientRemoteBuffer::SwapBuffers(aFrontUpdatedRegion);
+  DeprecatedContentClientRemoteBuffer::SwapBuffers(aFrontUpdatedRegion);
 }
 
 void
-ContentClientDoubleBuffered::OnActorDestroy()
+DeprecatedContentClientDoubleBuffered::OnActorDestroy()
 {
   if (mDeprecatedTextureClient) {
     mDeprecatedTextureClient->OnActorDestroy();
   }
   if (mDeprecatedTextureClientOnWhite) {
     mDeprecatedTextureClientOnWhite->OnActorDestroy();
   }
   for (size_t i = 0; i < mOldTextures.Length(); ++i) {
@@ -437,17 +825,17 @@ struct AutoDeprecatedTextureClient {
     }
     return nullptr;
   }
 private:
   DeprecatedTextureClient* mTexture;
 };
 
 void
-ContentClientDoubleBuffered::SyncFrontBufferToBackBuffer()
+DeprecatedContentClientDoubleBuffered::SyncFrontBufferToBackBuffer()
 {
   mIsNewBuffer = false;
 
   if (!mFrontAndBackBufferDiffer) {
     return;
   }
   MOZ_ASSERT(mFrontClient);
   MOZ_ASSERT(mFrontClient->GetAccessMode() == DeprecatedTextureClient::ACCESS_READ_ONLY);
@@ -493,17 +881,17 @@ ContentClientDoubleBuffered::SyncFrontBu
                             mFrontBufferRect,
                             mFrontBufferRotation);
   UpdateDestinationFrom(frontBuffer, updateRegion);
 
   mFrontAndBackBufferDiffer = false;
 }
 
 void
-ContentClientDoubleBuffered::UpdateDestinationFrom(const RotatedBuffer& aSource,
+DeprecatedContentClientDoubleBuffered::UpdateDestinationFrom(const RotatedBuffer& aSource,
                                                    const nsIntRegion& aUpdateRegion)
 {
   nsRefPtr<gfxContext> destCtx =
     GetContextForQuadrantUpdate(aUpdateRegion.GetBounds(), BUFFER_BLACK);
   if (!destCtx) {
     return;
   }
   destCtx->SetOperator(gfxContext::OPERATOR_SOURCE);
@@ -525,37 +913,65 @@ ContentClientDoubleBuffered::UpdateDesti
     if (isClippingCheap) {
       gfxUtils::ClipToRegion(destCtx, aUpdateRegion);
     }
 
     aSource.DrawBufferWithRotation(destCtx->GetDrawTarget(), BUFFER_WHITE, 1.0, OP_SOURCE);
   }
 }
 
-ContentClientSingleBuffered::~ContentClientSingleBuffered()
+void
+ContentClientSingleBuffered::SyncFrontBufferToBackBuffer()
+{
+  if (!mFrontAndBackBufferDiffer) {
+    return;
+  }
+
+  RefPtr<DrawTarget> backBuffer = GetDTBuffer();
+  if (!backBuffer && mTextureClient) {
+    backBuffer = mTextureClient->AsTextureClientDrawTarget()->GetAsDrawTarget();
+  }
+
+  RefPtr<DrawTarget> oldBuffer;
+  oldBuffer = SetDTBuffer(backBuffer,
+                          mBufferRect,
+                          mBufferRotation);
+
+  backBuffer = GetDTBufferOnWhite();
+  if (!backBuffer && mTextureClientOnWhite) {
+    backBuffer = mTextureClientOnWhite->AsTextureClientDrawTarget()->GetAsDrawTarget();
+  }
+
+  oldBuffer = SetDTBufferOnWhite(backBuffer);
+
+  mIsNewBuffer = false;
+  mFrontAndBackBufferDiffer = false;
+}
+
+DeprecatedContentClientSingleBuffered::~DeprecatedContentClientSingleBuffered()
 {
   if (mDeprecatedTextureClient) {
     mDeprecatedTextureClient->SetDescriptor(SurfaceDescriptor());
   }
   if (mDeprecatedTextureClientOnWhite) {
     mDeprecatedTextureClientOnWhite->SetDescriptor(SurfaceDescriptor());
   }
 }
 
 void
-ContentClientSingleBuffered::CreateFrontBufferAndNotify(const nsIntRect& aBufferRect)
+DeprecatedContentClientSingleBuffered::CreateFrontBufferAndNotify(const nsIntRect& aBufferRect)
 {
   mForwarder->CreatedSingleBuffer(this,
                                   *mDeprecatedTextureClient->LockSurfaceDescriptor(),
                                   mTextureInfo,
                                   mDeprecatedTextureClientOnWhite ? mDeprecatedTextureClientOnWhite->LockSurfaceDescriptor() : nullptr);
 }
 
 void
-ContentClientSingleBuffered::SyncFrontBufferToBackBuffer()
+DeprecatedContentClientSingleBuffered::SyncFrontBufferToBackBuffer()
 {
   mIsNewBuffer = false;
   if (!mFrontAndBackBufferDiffer) {
     return;
   }
   mFrontAndBackBufferDiffer = false;
 
   DrawTarget* backBuffer = GetDTBuffer();
--- a/gfx/layers/client/ContentClient.h
+++ b/gfx/layers/client/ContentClient.h
@@ -173,25 +173,127 @@ public:
  * compositing side with the create message (send from CreateBuffer) which
  * tells the compositor that TextureClients have been created and that the
  * compositor should assign the corresponding TextureHosts to our corresponding
  * ContentHost.
  *
  * If the size or type of our buffer(s) change(s), then we simply destroy and
  * create them.
  */
+// Version using new texture clients
 class ContentClientRemoteBuffer : public ContentClientRemote
                                 , protected RotatedContentBuffer
 {
   using RotatedContentBuffer::BufferRect;
   using RotatedContentBuffer::BufferRotation;
 public:
   ContentClientRemoteBuffer(CompositableForwarder* aForwarder)
     : ContentClientRemote(aForwarder)
     , RotatedContentBuffer(ContainsVisibleBounds)
+    , mIsNewBuffer(false)
+    , mFrontAndBackBufferDiffer(false)
+    , mSurfaceFormat(gfx::FORMAT_B8G8R8A8)
+  {}
+
+  typedef RotatedContentBuffer::PaintState PaintState;
+  typedef RotatedContentBuffer::ContentType ContentType;
+
+  virtual void Clear() { RotatedContentBuffer::Clear(); }
+  PaintState BeginPaintBuffer(ThebesLayer* aLayer, ContentType aContentType,
+                              uint32_t aFlags)
+  {
+    return RotatedContentBuffer::BeginPaint(aLayer, aContentType, aFlags);
+  }
+
+  /**
+   * Begin/End Paint map a gfxASurface from the texture client
+   * into the buffer of RotatedBuffer. The surface is only
+   * valid when the texture client is locked, so is mapped out
+   * of RotatedContentBuffer when we are done painting.
+   * None of the underlying buffer attributes (rect, rotation)
+   * are affected by mapping/unmapping.
+   */
+  virtual void BeginPaint() MOZ_OVERRIDE;
+  virtual void EndPaint() MOZ_OVERRIDE;
+
+  virtual void Updated(const nsIntRegion& aRegionToDraw,
+                       const nsIntRegion& aVisibleRegion,
+                       bool aDidSelfCopy);
+
+  virtual void SwapBuffers(const nsIntRegion& aFrontUpdatedRegion) MOZ_OVERRIDE;
+
+  // Expose these protected methods from the superclass.
+  virtual const nsIntRect& BufferRect() const
+  {
+    return RotatedContentBuffer::BufferRect();
+  }
+  virtual const nsIntPoint& BufferRotation() const
+  {
+    return RotatedContentBuffer::BufferRotation();
+  }
+
+  virtual void CreateBuffer(ContentType aType, const nsIntRect& aRect, uint32_t aFlags,
+                            RefPtr<gfx::DrawTarget>* aBlackDT, RefPtr<gfx::DrawTarget>* aWhiteDT) MOZ_OVERRIDE;
+
+  virtual TextureInfo GetTextureInfo() const MOZ_OVERRIDE
+  {
+    return mTextureInfo;
+  }
+
+  virtual void OnActorDestroy() MOZ_OVERRIDE;
+
+protected:
+  void DestroyBuffers();
+
+  virtual nsIntRegion GetUpdatedRegion(const nsIntRegion& aRegionToDraw,
+                                       const nsIntRegion& aVisibleRegion,
+                                       bool aDidSelfCopy);
+
+  void BuildTextureClients(gfx::SurfaceFormat aFormat,
+                           const nsIntRect& aRect,
+                           uint32_t aFlags);
+
+  // Create the front buffer for the ContentClient/Host pair if necessary
+  // and notify the compositor that we have created the buffer(s).
+  virtual void CreateFrontBuffer(const nsIntRect& aBufferRect) = 0;
+  virtual void DestroyFrontBuffer() {}
+
+  bool CreateAndAllocateTextureClient(RefPtr<TextureClient>& aClient,
+                                      TextureFlags aFlags = 0);
+
+  virtual void AbortTextureClientCreation()
+  {
+    mTextureClient = nullptr;
+    mTextureClientOnWhite = nullptr;
+    mIsNewBuffer = false;
+  }
+
+  RefPtr<TextureClient> mTextureClient;
+  RefPtr<TextureClient> mTextureClientOnWhite;
+  // keep a record of texture clients we have created and need to keep around
+  // (for RotatedBuffer to access), then unlock and remove them when we are done
+  // painting.
+  nsTArray<RefPtr<TextureClient> > mOldTextures;
+
+  TextureInfo mTextureInfo;
+  bool mIsNewBuffer;
+  bool mFrontAndBackBufferDiffer;
+  gfx::IntSize mSize;
+  gfx::SurfaceFormat mSurfaceFormat;
+};
+
+class DeprecatedContentClientRemoteBuffer : public ContentClientRemote
+                                          , protected RotatedContentBuffer
+{
+  using RotatedContentBuffer::BufferRect;
+  using RotatedContentBuffer::BufferRotation;
+public:
+  DeprecatedContentClientRemoteBuffer(CompositableForwarder* aForwarder)
+    : ContentClientRemote(aForwarder)
+    , RotatedContentBuffer(ContainsVisibleBounds)
     , mDeprecatedTextureClient(nullptr)
     , mIsNewBuffer(false)
     , mFrontAndBackBufferDiffer(false)
     , mContentType(GFX_CONTENT_COLOR_ALPHA)
   {}
 
   typedef RotatedContentBuffer::PaintState PaintState;
   typedef RotatedContentBuffer::ContentType ContentType;
@@ -228,26 +330,26 @@ public:
   virtual const nsIntPoint& BufferRotation() const
   {
     return RotatedContentBuffer::BufferRotation();
   }
 
   virtual void CreateBuffer(ContentType aType, const nsIntRect& aRect, uint32_t aFlags,
                             RefPtr<gfx::DrawTarget>* aBlackDT, RefPtr<gfx::DrawTarget>* aWhiteDT) MOZ_OVERRIDE;
 
-  void DestroyBuffers();
-
   virtual TextureInfo GetTextureInfo() const MOZ_OVERRIDE
   {
     return mTextureInfo;
   }
 
   virtual void OnActorDestroy() MOZ_OVERRIDE;
 
 protected:
+  void DestroyBuffers();
+
   virtual nsIntRegion GetUpdatedRegion(const nsIntRegion& aRegionToDraw,
                                        const nsIntRegion& aVisibleRegion,
                                        bool aDidSelfCopy);
 
   // create and configure mDeprecatedTextureClient
   void BuildDeprecatedTextureClients(ContentType aType,
                                      const nsIntRect& aRect,
                                      uint32_t aFlags);
@@ -287,31 +389,70 @@ protected:
  * (in SwapBuffers).
  */
 class ContentClientDoubleBuffered : public ContentClientRemoteBuffer
 {
 public:
   ContentClientDoubleBuffered(CompositableForwarder* aFwd)
     : ContentClientRemoteBuffer(aFwd)
   {
-    mTextureInfo.mCompositableType = BUFFER_CONTENT_DIRECT;
+    mTextureInfo.mCompositableType = COMPOSITABLE_CONTENT_DOUBLE;
   }
-  ~ContentClientDoubleBuffered();
+  virtual ~ContentClientDoubleBuffered() {}
 
   virtual void SwapBuffers(const nsIntRegion& aFrontUpdatedRegion) MOZ_OVERRIDE;
 
   virtual void SyncFrontBufferToBackBuffer() MOZ_OVERRIDE;
 
+  virtual void OnActorDestroy() MOZ_OVERRIDE;
+
+protected:
+  virtual void CreateFrontBuffer(const nsIntRect& aBufferRect) MOZ_OVERRIDE;
+  virtual void DestroyFrontBuffer() MOZ_OVERRIDE;
+
+private:
+  void UpdateDestinationFrom(const RotatedBuffer& aSource,
+                             const nsIntRegion& aUpdateRegion);
+
+  virtual void AbortTextureClientCreation() MOZ_OVERRIDE
+  {
+    mTextureClient = nullptr;
+    mTextureClientOnWhite = nullptr;
+    mFrontClient = nullptr;
+    mFrontClientOnWhite = nullptr;
+  }
+
+  RefPtr<TextureClient> mFrontClient;
+  RefPtr<TextureClient> mFrontClientOnWhite;
+  nsIntRegion mFrontUpdatedRegion;
+  nsIntRect mFrontBufferRect;
+  nsIntPoint mFrontBufferRotation;
+};
+
+class DeprecatedContentClientDoubleBuffered : public DeprecatedContentClientRemoteBuffer
+{
+public:
+  DeprecatedContentClientDoubleBuffered(CompositableForwarder* aFwd)
+    : DeprecatedContentClientRemoteBuffer(aFwd)
+  {
+    mTextureInfo.mCompositableType = BUFFER_CONTENT_DIRECT;
+  }
+  ~DeprecatedContentClientDoubleBuffered();
+
+  virtual void SwapBuffers(const nsIntRegion& aFrontUpdatedRegion) MOZ_OVERRIDE;
+
+  virtual void SyncFrontBufferToBackBuffer() MOZ_OVERRIDE;
+
+  virtual void OnActorDestroy() MOZ_OVERRIDE;
+
 protected:
   virtual void CreateFrontBufferAndNotify(const nsIntRect& aBufferRect) MOZ_OVERRIDE;
   virtual void DestroyFrontBuffer() MOZ_OVERRIDE;
   virtual void LockFrontBuffer() MOZ_OVERRIDE;
 
-  virtual void OnActorDestroy() MOZ_OVERRIDE;
-
 private:
   void UpdateDestinationFrom(const RotatedBuffer& aSource,
                              const nsIntRegion& aUpdateRegion);
 
   RefPtr<DeprecatedTextureClient> mFrontClient;
   RefPtr<DeprecatedTextureClient> mFrontClientOnWhite;
   nsIntRegion mFrontUpdatedRegion;
   nsIntRect mFrontBufferRect;
@@ -327,19 +468,35 @@ private:
  * the compositor.
  */
 class ContentClientSingleBuffered : public ContentClientRemoteBuffer
 {
 public:
   ContentClientSingleBuffered(CompositableForwarder* aFwd)
     : ContentClientRemoteBuffer(aFwd)
   {
+    mTextureInfo.mCompositableType = COMPOSITABLE_CONTENT_SINGLE;
+  }
+  virtual ~ContentClientSingleBuffered() {}
+
+  virtual void SyncFrontBufferToBackBuffer() MOZ_OVERRIDE;
+
+protected:
+  virtual void CreateFrontBuffer(const nsIntRect& aBufferRect) MOZ_OVERRIDE {}
+};
+
+class DeprecatedContentClientSingleBuffered : public DeprecatedContentClientRemoteBuffer
+{
+public:
+  DeprecatedContentClientSingleBuffered(CompositableForwarder* aFwd)
+    : DeprecatedContentClientRemoteBuffer(aFwd)
+  {
     mTextureInfo.mCompositableType = BUFFER_CONTENT;    
   }
-  ~ContentClientSingleBuffered();
+  ~DeprecatedContentClientSingleBuffered();
 
   virtual void SyncFrontBufferToBackBuffer() MOZ_OVERRIDE;
 
 protected:
   virtual void CreateFrontBufferAndNotify(const nsIntRect& aBufferRect) MOZ_OVERRIDE;
 };
 
 /**
--- a/gfx/layers/client/TextureClient.cpp
+++ b/gfx/layers/client/TextureClient.cpp
@@ -300,32 +300,50 @@ BufferTextureClient::GetAsSurface()
   }
 
   RefPtr<gfxImageSurface> surf = serializer.GetAsThebesSurface();
   nsRefPtr<gfxASurface> result = surf.get();
   return result.forget();
 }
 
 bool
-BufferTextureClient::AllocateForSurface(gfx::IntSize aSize)
+BufferTextureClient::AllocateForSurface(gfx::IntSize aSize, TextureAllocationFlags aFlags)
 {
   MOZ_ASSERT(IsValid());
   MOZ_ASSERT(mFormat != gfx::FORMAT_YUV, "This textureClient cannot use YCbCr data");
 
   int bufSize
     = ImageDataSerializer::ComputeMinBufferSize(aSize, mFormat);
   if (!Allocate(bufSize)) {
     return false;
   }
+
+  if (aFlags & ALLOC_CLEAR_BUFFER) {
+    memset(GetBuffer(), 0, bufSize);
+  }
+
   ImageDataSerializer serializer(GetBuffer());
   serializer.InitializeBufferInfo(aSize, mFormat);
   mSize = aSize;
   return true;
 }
 
+TemporaryRef<gfx::DrawTarget>
+BufferTextureClient::GetAsDrawTarget()
+{
+  MOZ_ASSERT(IsValid());
+
+  ImageDataSerializer serializer(GetBuffer());
+  if (!serializer.IsValid()) {
+    return nullptr;
+  }
+
+  return serializer.GetAsDrawTarget();
+}
+
 bool
 BufferTextureClient::UpdateYCbCr(const PlanarYCbCrData& aData)
 {
   MOZ_ASSERT(mFormat == gfx::FORMAT_YUV, "This textureClient can only use YCbCr data");
   MOZ_ASSERT(!IsImmutable());
   MOZ_ASSERT(IsValid());
   MOZ_ASSERT(aData.mCbSkip == aData.mCrSkip);
 
--- a/gfx/layers/client/TextureClient.h
+++ b/gfx/layers/client/TextureClient.h
@@ -43,25 +43,57 @@ class Image;
 /**
  * TextureClient is the abstraction that allows us to share data between the
  * content and the compositor side.
  * TextureClient can also provide with some more "producer" facing APIs
  * such as TextureClientSurface and TextureClientYCbCr, that can be queried
  * using AsTextureCLientSurface(), etc.
  */
 
+enum TextureAllocationFlags {
+  ALLOC_DEFAULT = 0,
+  ALLOC_CLEAR_BUFFER = 1
+};
+
 /**
  * Interface for TextureClients that can be updated using a gfxASurface.
  */
 class TextureClientSurface
 {
 public:
   virtual bool UpdateSurface(gfxASurface* aSurface) = 0;
   virtual already_AddRefed<gfxASurface> GetAsSurface() = 0;
-  virtual bool AllocateForSurface(gfx::IntSize aSize) = 0;
+  /**
+   * Allocates for a given surface size, taking into account the pixel format
+   * which is part of the state of the TextureClient.
+   *
+   * Does not clear the surface by default, clearing the surface can be done
+   * by passing the CLEAR_BUFFER flag.
+   */
+  virtual bool AllocateForSurface(gfx::IntSize aSize,
+                                  TextureAllocationFlags flags = ALLOC_DEFAULT) = 0;
+};
+
+/**
+ * Interface for TextureClients that can be updated using a DrawTarget.
+ */
+class TextureClientDrawTarget
+{
+public:
+  virtual TemporaryRef<gfx::DrawTarget> GetAsDrawTarget() = 0;
+  virtual gfx::SurfaceFormat GetFormat() const = 0;
+  /**
+   * Allocates for a given surface size, taking into account the pixel format
+   * which is part of the state of the TextureClient.
+   *
+   * Does not clear the surface by default, clearing the surface can be done
+   * by passing the CLEAR_BUFFER flag.
+   */
+  virtual bool AllocateForSurface(gfx::IntSize aSize,
+                                  TextureAllocationFlags flags = ALLOC_DEFAULT) = 0;
 };
 
 /**
  * Interface for TextureClients that can be updated using YCbCr data.
  */
 class TextureClientYCbCr
 {
 public:
@@ -119,16 +151,17 @@ public:
  */
 class TextureClient : public AtomicRefCounted<TextureClient>
 {
 public:
   TextureClient(TextureFlags aFlags = TEXTURE_FLAGS_DEFAULT);
   virtual ~TextureClient();
 
   virtual TextureClientSurface* AsTextureClientSurface() { return nullptr; }
+  virtual TextureClientDrawTarget* AsTextureClientDrawTarget() { return nullptr; }
   virtual TextureClientYCbCr* AsTextureClientYCbCr() { return nullptr; }
 
   /**
    * Locks the shared data, allowing the caller to get access to it.
    *
    * Please always lock/unlock when accessing the shared data.
    * If Lock() returns false, you should not attempt to access the shared data.
    */
@@ -235,17 +268,18 @@ protected:
 
 /**
  * TextureClient that wraps a random access buffer such as a Shmem or raw memory.
  * This class must be inherited to implement the memory allocation and access bits.
  * (see ShmemTextureClient and MemoryTextureClient)
  */
 class BufferTextureClient : public TextureClient
                           , public TextureClientSurface
-                          , TextureClientYCbCr
+                          , public TextureClientYCbCr
+                          , public TextureClientDrawTarget
 {
 public:
   BufferTextureClient(CompositableClient* aCompositable, gfx::SurfaceFormat aFormat,
                       TextureFlags aFlags);
 
   virtual ~BufferTextureClient();
 
   virtual bool IsAllocated() const = 0;
@@ -259,29 +293,36 @@ public:
   // TextureClientSurface
 
   virtual TextureClientSurface* AsTextureClientSurface() MOZ_OVERRIDE { return this; }
 
   virtual bool UpdateSurface(gfxASurface* aSurface) MOZ_OVERRIDE;
 
   virtual already_AddRefed<gfxASurface> GetAsSurface() MOZ_OVERRIDE;
 
-  virtual bool AllocateForSurface(gfx::IntSize aSize) MOZ_OVERRIDE;
+  virtual bool AllocateForSurface(gfx::IntSize aSize,
+                                  TextureAllocationFlags aFlags = ALLOC_DEFAULT) MOZ_OVERRIDE;
+
+  // TextureClientDrawTarget
+
+  virtual TextureClientDrawTarget* AsTextureClientDrawTarget() MOZ_OVERRIDE { return this; }
+
+  virtual TemporaryRef<gfx::DrawTarget> GetAsDrawTarget() MOZ_OVERRIDE;
 
   // TextureClientYCbCr
 
   virtual TextureClientYCbCr* AsTextureClientYCbCr() MOZ_OVERRIDE { return this; }
 
   virtual bool UpdateYCbCr(const PlanarYCbCrData& aData) MOZ_OVERRIDE;
 
   virtual bool AllocateForYCbCr(gfx::IntSize aYSize,
                                 gfx::IntSize aCbCrSize,
                                 StereoMode aStereoMode) MOZ_OVERRIDE;
 
-  gfx::SurfaceFormat GetFormat() const { return mFormat; }
+  virtual gfx::SurfaceFormat GetFormat() const MOZ_OVERRIDE { return mFormat; }
 
   // XXX - Bug 908196 - Make Allocate(uint32_t) and GetBufferSize() protected.
   // these two methods should only be called by methods of BufferTextureClient
   // that are overridden in GrallocTextureClient (which does not implement the
   // two methods below)
   virtual bool Allocate(uint32_t aSize) = 0;
 
   virtual size_t GetBufferSize() const = 0;
--- a/gfx/layers/composite/APZCTreeManager.cpp
+++ b/gfx/layers/composite/APZCTreeManager.cpp
@@ -184,23 +184,32 @@ APZCTreeManager::UpdatePanZoomController
           aParent->SetLastChild(apzc);
         } else {
           mRootApzc = apzc;
         }
 
         // Let this apzc be the parent of other controllers when we recurse downwards
         aParent = apzc;
 
-        if (newApzc && apzc->IsRootForLayersId()) {
-          // If we just created a new apzc that is the root for its layers ID, then
-          // we need to update its zoom constraints which might have arrived before this
-          // was created
+        if (newApzc) {
           bool allowZoom;
           CSSToScreenScale minZoom, maxZoom;
-          if (state->mController->GetRootZoomConstraints(&allowZoom, &minZoom, &maxZoom)) {
+          if (apzc->IsRootForLayersId()) {
+            // If we just created a new apzc that is the root for its layers ID, then
+            // we need to update its zoom constraints which might have arrived before this
+            // was created
+            if (state->mController->GetRootZoomConstraints(&allowZoom, &minZoom, &maxZoom)) {
+              apzc->UpdateZoomConstraints(allowZoom, minZoom, maxZoom);
+            }
+          } else {
+            // For an apzc that is not the root for its layers ID, we give it the
+            // same zoom constraints as its parent. This ensures that if e.g.
+            // user-scalable=no was specified, none of the APZCs allow double-tap
+            // to zoom.
+            apzc->GetParent()->GetZoomConstraints(&allowZoom, &minZoom, &maxZoom);
             apzc->UpdateZoomConstraints(allowZoom, minZoom, maxZoom);
           }
         }
       }
     }
 
     container->SetAsyncPanZoomController(apzc);
   }
@@ -580,18 +589,38 @@ APZCTreeManager::ContentReceivedTouch(co
 
 void
 APZCTreeManager::UpdateZoomConstraints(const ScrollableLayerGuid& aGuid,
                                        bool aAllowZoom,
                                        const CSSToScreenScale& aMinScale,
                                        const CSSToScreenScale& aMaxScale)
 {
   nsRefPtr<AsyncPanZoomController> apzc = GetTargetAPZC(aGuid);
-  if (apzc) {
-    apzc->UpdateZoomConstraints(aAllowZoom, aMinScale, aMaxScale);
+  // For a given layers id, non-root APZCs inherit the zoom constraints
+  // of their root.
+  if (apzc && apzc->IsRootForLayersId()) {
+    MonitorAutoLock lock(mTreeLock);
+    UpdateZoomConstraintsRecursively(apzc.get(), aAllowZoom, aMinScale, aMaxScale);
+  }
+}
+
+void
+APZCTreeManager::UpdateZoomConstraintsRecursively(AsyncPanZoomController* aApzc,
+                                                  bool aAllowZoom,
+                                                  const CSSToScreenScale& aMinScale,
+                                                  const CSSToScreenScale& aMaxScale)
+{
+  mTreeLock.AssertCurrentThreadOwns();
+
+  aApzc->UpdateZoomConstraints(aAllowZoom, aMinScale, aMaxScale);
+  for (AsyncPanZoomController* child = aApzc->GetLastChild(); child; child = child->GetPrevSibling()) {
+    // We can have subtrees with their own layers id - leave those alone.
+    if (!child->IsRootForLayersId()) {
+      UpdateZoomConstraintsRecursively(child, aAllowZoom, aMinScale, aMaxScale);
+    }
   }
 }
 
 void
 APZCTreeManager::UpdateScrollOffset(const ScrollableLayerGuid& aGuid,
                                     const CSSPoint& aScrollOffset)
 {
   nsRefPtr<AsyncPanZoomController> apzc = GetTargetAPZC(aGuid);
--- a/gfx/layers/composite/APZCTreeManager.h
+++ b/gfx/layers/composite/APZCTreeManager.h
@@ -283,16 +283,20 @@ private:
                      const uint64_t& aLayersId,
                      nsTArray< nsRefPtr<AsyncPanZoomController> >* aOutRootApzcs);
   already_AddRefed<AsyncPanZoomController> CommonAncestor(AsyncPanZoomController* aApzc1, AsyncPanZoomController* aApzc2);
   already_AddRefed<AsyncPanZoomController> RootAPZCForLayersId(AsyncPanZoomController* aApzc);
   already_AddRefed<AsyncPanZoomController> GetTouchInputBlockAPZC(const WidgetTouchEvent& aEvent, ScreenPoint aPoint);
   nsEventStatus ProcessTouchEvent(const WidgetTouchEvent& touchEvent, ScrollableLayerGuid* aOutTargetGuid, WidgetTouchEvent* aOutEvent);
   nsEventStatus ProcessMouseEvent(const WidgetMouseEvent& mouseEvent, ScrollableLayerGuid* aOutTargetGuid, WidgetMouseEvent* aOutEvent);
   nsEventStatus ProcessEvent(const WidgetInputEvent& inputEvent, ScrollableLayerGuid* aOutTargetGuid, WidgetInputEvent* aOutEvent);
+  void UpdateZoomConstraintsRecursively(AsyncPanZoomController* aApzc,
+                                        bool aAllowZoom,
+                                        const CSSToScreenScale& aMinScale,
+                                        const CSSToScreenScale& aMaxScale);
 
   /**
    * Recursive helper function to build the APZC tree. The tree of APZC instances has
    * the same shape as the layer tree, but excludes all the layers that are not scrollable.
    * Note that this means APZCs corresponding to layers at different depths in the tree
    * may end up becoming siblings. It also means that the "root" APZC may have siblings.
    * This function walks the layer tree backwards through siblings and constructs the APZC
    * tree also as a last-child-prev-sibling tree because that simplifies the hit detection
--- a/gfx/layers/composite/CompositableHost.cpp
+++ b/gfx/layers/composite/CompositableHost.cpp
@@ -54,30 +54,29 @@ CompositableHost::AddTextureHost(Texture
              "A texture is already present with this ID");
   RefPtr<TextureHost> second = mFirstTexture;
   mFirstTexture = aTexture;
   aTexture->SetNextSibling(second);
   aTexture->SetCompositableBackendSpecificData(GetCompositableBackendSpecificData());
 }
 
 void
-CompositableHost::RemoveTextureHost(uint64_t aTextureID)
+CompositableHost::RemoveTextureHost(TextureHost* aTexture)
 {
-  if (mFirstTexture && mFirstTexture->GetID() == aTextureID) {
-    RefPtr<TextureHost> toRemove = mFirstTexture;
+  uint64_t textureID = aTexture->GetID();
+  if (mFirstTexture && mFirstTexture->GetID() == textureID) {
     mFirstTexture = mFirstTexture->GetNextSibling();
-    toRemove->SetNextSibling(nullptr);
+    aTexture->SetNextSibling(nullptr);
   }
   RefPtr<TextureHost> it = mFirstTexture;
   while (it) {
     if (it->GetNextSibling() &&
-        it->GetNextSibling()->GetID() == aTextureID) {
-      RefPtr<TextureHost> toRemove = it->GetNextSibling();
+        it->GetNextSibling()->GetID() == textureID) {
       it->SetNextSibling(it->GetNextSibling()->GetNextSibling());
-      toRemove->SetNextSibling(nullptr);
+      aTexture->SetNextSibling(nullptr);
     }
     it = it->GetNextSibling();
   }
   if (!mFirstTexture && mBackendData) {
     mBackendData->ClearData();
   }
 }
 
@@ -89,16 +88,25 @@ CompositableHost::GetTextureHost(uint64_
     if (it->GetID() == aTextureID) {
       return it;
     }
     it = it->GetNextSibling();
   }
   return nullptr;
 }
 
+void
+CompositableHost::OnActorDestroy()
+{
+  TextureHost* it = mFirstTexture;
+  while (it) {
+    it->OnActorDestroy();
+    it = it->GetNextSibling();
+  }
+}
 
 void
 CompositableHost::SetCompositor(Compositor* aCompositor)
 {
   mCompositor = aCompositor;
   RefPtr<TextureHost> it = mFirstTexture;
   while (!!it) {
     it->SetCompositor(aCompositor);
@@ -127,17 +135,17 @@ CompositableHost::AddMaskEffect(EffectCh
                                 bool aIs3D)
 {
   RefPtr<TextureSource> source;
   RefPtr<DeprecatedTextureHost> oldHost = GetDeprecatedTextureHost();
   if (oldHost) {
     oldHost->Lock();
     source = oldHost;
   } else {
-    RefPtr<TextureHost> host = GetTextureHost();
+    RefPtr<TextureHost> host = GetAsTextureHost();
     if (host) {
       host->Lock();
       source = host->GetTextureSources();
     }
   }
 
   if (!source) {
     NS_WARNING("Using compositable with no texture host as mask layer");
@@ -154,52 +162,61 @@ CompositableHost::AddMaskEffect(EffectCh
 
 void
 CompositableHost::RemoveMaskEffect()
 {
   RefPtr<DeprecatedTextureHost> oldHost = GetDeprecatedTextureHost();
   if (oldHost) {
     oldHost->Unlock();
   } else {
-    RefPtr<TextureHost> host = GetTextureHost();
+    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 COMPOSITABLE_IMAGE:
-    result = new ImageHost(aTextureInfo);
+  case BUFFER_IMAGE_SINGLE:
+    result = new DeprecatedImageHostSingle(aTextureInfo);
     break;
   case BUFFER_IMAGE_BUFFERED:
     result = new DeprecatedImageHostBuffered(aTextureInfo);
     break;
-  case BUFFER_IMAGE_SINGLE:
-    result = new DeprecatedImageHostSingle(aTextureInfo);
+  case BUFFER_BRIDGE:
+    MOZ_CRASH("Cannot create an image bridge compositable this way");
+    break;
+  case BUFFER_CONTENT:
+    result = new DeprecatedContentHostSingleBuffered(aTextureInfo);
+    break;
+  case BUFFER_CONTENT_DIRECT:
+    result = new DeprecatedContentHostDoubleBuffered(aTextureInfo);
+    break;
+  case BUFFER_CONTENT_INC:
+    result = new ContentHostIncremental(aTextureInfo);
     break;
   case BUFFER_TILED:
     result = new TiledContentHost(aTextureInfo);
     break;
-  case BUFFER_CONTENT:
+  case COMPOSITABLE_IMAGE:
+    result = new ImageHost(aTextureInfo);
+    break;
+  case COMPOSITABLE_CONTENT_SINGLE:
     result = new ContentHostSingleBuffered(aTextureInfo);
     break;
-  case BUFFER_CONTENT_DIRECT:
+  case COMPOSITABLE_CONTENT_DOUBLE:
     result = new ContentHostDoubleBuffered(aTextureInfo);
     break;
-  case BUFFER_CONTENT_INC:
-    result = new ContentHostIncremental(aTextureInfo);
-    break;
   default:
     MOZ_CRASH("Unknown CompositableType");
   }
   if (result) {
     RefPtr<CompositableBackendSpecificData> data = CreateCompositableBackendSpecificDataOGL();
     result->SetCompositableBackendSpecificData(data);
   }
   return result;
--- a/gfx/layers/composite/CompositableHost.h
+++ b/gfx/layers/composite/CompositableHost.h
@@ -107,17 +107,17 @@ public:
   {
     mBackendData = aBackendData;
   }
 
   /**
    * Our IPDL actor is being destroyed, get rid of any shmem resources now and
    * don't worry about compositing anymore.
    */
-  virtual void OnActorDestroy() = 0;
+  virtual void OnActorDestroy();
 
   // 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,
@@ -206,17 +206,17 @@ public:
     MOZ_ASSERT(false, "should be implemented or not used");
   }
 
   virtual DeprecatedTextureHost* GetDeprecatedTextureHost() { return nullptr; }
 
   /**
    * Returns the front buffer.
    */
-  virtual TextureHost* GetTextureHost() { return nullptr; }
+  virtual TextureHost* GetAsTextureHost() { return nullptr; }
 
   virtual LayerRenderState GetRenderState() = 0;
 
   virtual void SetPictureRect(const nsIntRect& aPictureRect)
   {
     MOZ_ASSERT(false, "Should have been overridden");
   }
 
@@ -291,17 +291,23 @@ public:
 
   virtual TemporaryRef<gfx::DataSourceSurface> GetAsSurface() { return nullptr; }
 #endif
 
   virtual void PrintInfo(nsACString& aTo, const char* aPrefix) { }
 
   void AddTextureHost(TextureHost* aTexture);
   virtual void UseTextureHost(TextureHost* aTexture) {}
-  virtual void RemoveTextureHost(uint64_t aTextureID);
+  // If a texture host is flagged for deferred removal, the compositable will
+  // get an option to run any cleanup code early, that is when it would have
+  // been run if the texture host was not marked deferred.
+  // If the compositable does not cleanup the texture host now, it is the
+  // compositable's responsibility to cleanup the texture host before the
+  // texture host dies.
+  virtual void RemoveTextureHost(TextureHost* aTexture);
   TextureHost* GetTextureHost(uint64_t aTextureID);
 
 protected:
   TextureInfo mTextureInfo;
   Compositor* mCompositor;
   Layer* mLayer;
   RefPtr<CompositableBackendSpecificData> mBackendData;
   RefPtr<TextureHost> mFirstTexture;
--- a/gfx/layers/composite/ContentHost.cpp
+++ b/gfx/layers/composite/ContentHost.cpp
@@ -26,54 +26,377 @@ namespace layers {
 
 ContentHostBase::ContentHostBase(const TextureInfo& aTextureInfo)
   : ContentHost(aTextureInfo)
   , mPaintWillResample(false)
   , mInitialised(false)
 {}
 
 ContentHostBase::~ContentHostBase()
+{
+  DestroyTextureHost();
+  DestroyTextureHostOnWhite();
+}
+
+TextureHost*
+ContentHostBase::GetAsTextureHost()
+{
+  return mTextureHost;
+}
+
+void
+ContentHostBase::DestroyTextureHost()
+{
+  // The third clause in the if statement checks that we are in fact done with
+  // this texture. We don't want to prematurely deallocate a texture we might
+  // use again or double deallocate. Deallocation will happen in
+  // RemoveTextureHost.
+  // Note that GetTextureHost is linear in the number of texture hosts, but as
+  // long as that number is small (I expect a maximum of 6 for now) then it
+  // should be ok.
+  if (mTextureHost &&
+      mTextureHost->GetFlags() & TEXTURE_DEALLOCATE_DEFERRED &&
+      !GetTextureHost(mTextureHost->GetID())) {
+    MOZ_ASSERT(!(mTextureHost->GetFlags() & TEXTURE_DEALLOCATE_CLIENT));
+    mTextureHost->DeallocateSharedData();
+  }
+  mTextureHost = nullptr;
+}
+
+void
+ContentHostBase::DestroyTextureHostOnWhite()
+{
+  if (mTextureHostOnWhite &&
+      mTextureHostOnWhite->GetFlags() & TEXTURE_DEALLOCATE_DEFERRED &&
+      !GetTextureHost(mTextureHostOnWhite->GetID())) {
+    MOZ_ASSERT(!(mTextureHostOnWhite->GetFlags() & TEXTURE_DEALLOCATE_CLIENT));
+    mTextureHostOnWhite->DeallocateSharedData();
+  }
+  mTextureHostOnWhite = nullptr;
+}
+
+void
+ContentHostBase::RemoveTextureHost(TextureHost* aTexture)
+{
+  if ((aTexture->GetFlags() & TEXTURE_DEALLOCATE_DEFERRED) &&
+      !(mTextureHost && mTextureHost == aTexture) &&
+      !(mTextureHostOnWhite && mTextureHostOnWhite == aTexture)) {
+    MOZ_ASSERT(!(aTexture->GetFlags() & TEXTURE_DEALLOCATE_CLIENT));
+    aTexture->DeallocateSharedData();
+  }
+
+  CompositableHost::RemoveTextureHost(aTexture);
+}
+
+class MOZ_STACK_CLASS AutoLockTextureHost
+{
+public:
+  AutoLockTextureHost(TextureHost* aHost)
+    : mHost(aHost)
+  {
+    mLockSuccess = mHost ? mHost->Lock() : true;
+  }
+
+  ~AutoLockTextureHost()
+  {
+    if (mHost) {
+      mHost->Unlock();
+    }
+  }
+
+  bool IsValid()
+  {
+    return mLockSuccess;
+  }
+
+private:
+  TextureHost* mHost;
+  bool mLockSuccess;
+};
+
+void
+ContentHostBase::Composite(EffectChain& aEffectChain,
+                           float aOpacity,
+                           const gfx::Matrix4x4& aTransform,
+                           const Filter& aFilter,
+                           const Rect& aClipRect,
+                           const nsIntRegion* aVisibleRegion,
+                           TiledLayerProperties* aLayerProperties)
+{
+  NS_ASSERTION(aVisibleRegion, "Requires a visible region");
+
+  AutoLockTextureHost lock(mTextureHost);
+  AutoLockTextureHost lockOnWhite(mTextureHostOnWhite);
+
+  if (!mTextureHost ||
+      !lock.IsValid() ||
+      !lockOnWhite.IsValid()) {
+    return;
+  }
+
+  RefPtr<NewTextureSource> source = mTextureHost->GetTextureSources();
+  RefPtr<NewTextureSource> sourceOnWhite = mTextureHostOnWhite
+                                             ? mTextureHostOnWhite->GetTextureSources()
+                                             : nullptr;
+  if (!source) {
+    return;
+  }
+  RefPtr<TexturedEffect> effect =
+    CreateTexturedEffect(source, sourceOnWhite, aFilter);
+
+  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 = 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;
+  }
+
+  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);
+  }
+
+  TileIterator* tileIter = source->AsTileIterator();
+  TileIterator* iterOnWhite = nullptr;
+  if (tileIter) {
+    tileIter->BeginTileIteration();
+  }
+
+  if (mTextureHostOnWhite) {
+    iterOnWhite = sourceOnWhite->AsTileIterator();
+    MOZ_ASSERT(!tileIter || tileIter->GetTileCount() == iterOnWhite->GetTileCount(),
+               "Tile count mismatch on component alpha texture");
+    if (iterOnWhite) {
+      iterOnWhite->BeginTileIteration();
+    }
+  }
+
+  bool usingTiles = (tileIter && tileIter->GetTileCount() > 1);
+  do {
+    if (iterOnWhite) {
+      MOZ_ASSERT(iterOnWhite->GetTileRect() == tileIter->GetTileRect(),
+                 "component alpha textures should be the same size.");
+    }
+
+    nsIntRect texRect = tileIter ? tileIter->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) {
+            DiagnosticTypes diagnostics = DIAGNOSTIC_CONTENT | DIAGNOSTIC_BIGIMAGE;
+            diagnostics |= iterOnWhite ? DIAGNOSTIC_COMPONENT_ALPHA : 0;
+            GetCompositor()->DrawDiagnostics(diagnostics, rect, aClipRect,
+                                             aTransform);
+          }
+        }
+      }
+    }
+
+    if (iterOnWhite) {
+      iterOnWhite->NextTile();
+    }
+  } while (usingTiles && tileIter->NextTile());
+
+  if (tileIter) {
+    tileIter->EndTileIteration();
+  }
+  if (iterOnWhite) {
+    iterOnWhite->EndTileIteration();
+  }
+
+  DiagnosticTypes diagnostics = DIAGNOSTIC_CONTENT;
+  diagnostics |= iterOnWhite ? DIAGNOSTIC_COMPONENT_ALPHA : 0;
+  GetCompositor()->DrawDiagnostics(diagnostics, *aVisibleRegion, aClipRect, aTransform);
+}
+
+
+void
+ContentHostBase::UseTextureHost(TextureHost* aTexture)
+{
+  if (aTexture->GetFlags() & TEXTURE_ON_WHITE) {
+    DestroyTextureHost();
+    mTextureHostOnWhite = aTexture;
+  } else {
+    DestroyTextureHostOnWhite();
+    mTextureHost = aTexture;
+  }
+}
+
+void
+ContentHostBase::SetCompositor(Compositor* aCompositor)
+{
+  CompositableHost::SetCompositor(aCompositor);
+  if (mTextureHost) {
+    mTextureHost->SetCompositor(aCompositor);
+  }
+  if (mTextureHostOnWhite) {
+    mTextureHostOnWhite->SetCompositor(aCompositor);
+  }
+}
+
+#ifdef MOZ_DUMP_PAINTING
+void
+ContentHostBase::Dump(FILE* aFile,
+                      const char* aPrefix,
+                      bool aDumpHtml)
+{
+  if (!aDumpHtml) {
+    return;
+  }
+  if (!aFile) {
+    aFile = stderr;
+  }
+  fprintf(aFile, "<ul>");
+  if (mTextureHost) {
+    fprintf(aFile, "%s", aPrefix);
+    fprintf(aFile, "<li> <a href=");
+    DumpTextureHost(aFile, mTextureHost);
+    fprintf(aFile, "> Front buffer </a></li> ");
+  }
+  if (mTextureHostOnWhite) {
+    fprintf(aFile, "%s", aPrefix);
+    fprintf(aFile, "<li> <a href=");
+    DumpTextureHost(aFile, mTextureHostOnWhite);
+    fprintf(aFile, "> Front buffer on white </a> </li> ");
+  }
+  fprintf(aFile, "</ul>");
+}
+#endif
+
+void
+ContentHostBase::OnActorDestroy()
+{
+  if (mTextureHost) {
+    mTextureHost->OnActorDestroy();
+  }
+  if (mTextureHostOnWhite) {
+    mTextureHostOnWhite->OnActorDestroy();
+  }
+  CompositableHost::OnActorDestroy();
+}
+
+DeprecatedContentHostBase::DeprecatedContentHostBase(const TextureInfo& aTextureInfo)
+  : ContentHost(aTextureInfo)
+  , mPaintWillResample(false)
+  , mInitialised(false)
+{}
+
+DeprecatedContentHostBase::~DeprecatedContentHostBase()
 {}
 
 DeprecatedTextureHost*
-ContentHostBase::GetDeprecatedTextureHost()
+DeprecatedContentHostBase::GetDeprecatedTextureHost()
 {
   return mDeprecatedTextureHost;
 }
 
 void
-ContentHostBase::DestroyFrontHost()
+DeprecatedContentHostBase::DestroyFrontHost()
 {
   MOZ_ASSERT(!mDeprecatedTextureHost || mDeprecatedTextureHost->GetDeAllocator(),
              "We won't be able to destroy our SurfaceDescriptor");
   MOZ_ASSERT(!mDeprecatedTextureHostOnWhite || mDeprecatedTextureHostOnWhite->GetDeAllocator(),
              "We won't be able to destroy our SurfaceDescriptor");
   mDeprecatedTextureHost = nullptr;
   mDeprecatedTextureHostOnWhite = nullptr;
 }
 
 void
-ContentHostBase::OnActorDestroy()
+DeprecatedContentHostBase::OnActorDestroy()
 {
   if (mDeprecatedTextureHost) {
     mDeprecatedTextureHost->OnActorDestroy();
   }
   if (mDeprecatedTextureHostOnWhite) {
     mDeprecatedTextureHostOnWhite->OnActorDestroy();
   }
   if (mNewFrontHost) {
     mNewFrontHost->OnActorDestroy();
   }
   if (mNewFrontHostOnWhite) {
     mNewFrontHostOnWhite->OnActorDestroy();
   }
 }
 
 void
-ContentHostBase::Composite(EffectChain& aEffectChain,
+DeprecatedContentHostBase::Composite(EffectChain& aEffectChain,
                            float aOpacity,
                            const gfx::Matrix4x4& aTransform,
                            const Filter& aFilter,
                            const Rect& aClipRect,
                            const nsIntRegion* aVisibleRegion,
                            TiledLayerProperties* aLayerProperties)
 {
   NS_ASSERTION(aVisibleRegion, "Requires a visible region");
@@ -229,31 +552,31 @@ ContentHostBase::Composite(EffectChain& 
   }
 
   DiagnosticTypes diagnostics = DIAGNOSTIC_CONTENT;
   diagnostics |= iterOnWhite ? DIAGNOSTIC_COMPONENT_ALPHA : 0;
   GetCompositor()->DrawDiagnostics(diagnostics, *aVisibleRegion, aClipRect, aTransform);
 }
 
 void
-ContentHostBase::SetCompositor(Compositor* aCompositor)
+DeprecatedContentHostBase::SetCompositor(Compositor* aCompositor)
 {
   CompositableHost::SetCompositor(aCompositor);
   if (mDeprecatedTextureHost) {
     mDeprecatedTextureHost->SetCompositor(aCompositor);
   }
   if (mDeprecatedTextureHostOnWhite) {
     mDeprecatedTextureHostOnWhite->SetCompositor(aCompositor);
   }
 }
 
 #ifdef MOZ_DUMP_PAINTING
 
 void
-ContentHostBase::Dump(FILE* aFile,
+DeprecatedContentHostBase::Dump(FILE* aFile,
                       const char* aPrefix,
                       bool aDumpHtml)
 {
   if (!aDumpHtml) {
     return;
   }
   if (!aFile) {
     aFile = stderr;
@@ -271,24 +594,66 @@ ContentHostBase::Dump(FILE* aFile,
     DumpDeprecatedTextureHost(aFile, mDeprecatedTextureHostOnWhite);
     fprintf_stderr(aFile, "> Front buffer on white </a> </li> ");
   }
   fprintf_stderr(aFile, "</ul>");
 }
 
 #endif
 
-ContentHostSingleBuffered::~ContentHostSingleBuffered()
+void
+ContentHostSingleBuffered::UpdateThebes(const ThebesBufferData& aData,
+                                        const nsIntRegion& aUpdated,
+                                        const nsIntRegion& aOldValidRegionBack,
+                                        nsIntRegion* aUpdatedRegionBack)
+{
+  aUpdatedRegionBack->SetEmpty();
+
+  if (!mTextureHost) {
+    mInitialised = false;
+    return;
+  }
+
+  // updated is in screen coordinates. Convert it to buffer coordinates.
+  nsIntRegion destRegion(aUpdated);
+  destRegion.MoveBy(-aData.rect().TopLeft());
+
+  // Correct for rotation
+  destRegion.MoveBy(aData.rotation());
+
+  gfxIntSize size = aData.rect().Size();
+  nsIntRect destBounds = destRegion.GetBounds();
+  destRegion.MoveBy((destBounds.x >= size.width) ? -size.width : 0,
+                    (destBounds.y >= size.height) ? -size.height : 0);
+
+  // There's code to make sure that updated regions don't cross rotation
+  // boundaries, so assert here that this is the case
+  MOZ_ASSERT((destBounds.x % size.width) + destBounds.width <= size.width,
+               "updated region lies across rotation boundaries!");
+  MOZ_ASSERT((destBounds.y % size.height) + destBounds.height <= size.height,
+               "updated region lies across rotation boundaries!");
+
+  mTextureHost->Updated(&destRegion);
+  if (mTextureHostOnWhite) {
+    mTextureHostOnWhite->Updated(&destRegion);
+  }
+  mInitialised = true;
+
+  mBufferRect = aData.rect();
+  mBufferRotation = aData.rotation();
+}
+
+DeprecatedContentHostSingleBuffered::~DeprecatedContentHostSingleBuffered()
 {
   DestroyTextures();
   DestroyFrontHost();
 }
 
 void
-ContentHostSingleBuffered::EnsureDeprecatedTextureHost(TextureIdentifier aTextureId,
+DeprecatedContentHostSingleBuffered::EnsureDeprecatedTextureHost(TextureIdentifier aTextureId,
                                              const SurfaceDescriptor& aSurface,
                                              ISurfaceAllocator* aAllocator,
                                              const TextureInfo& aTextureInfo)
 {
   MOZ_ASSERT(aTextureId == TextureFront ||
              aTextureId == TextureOnWhiteFront);
   RefPtr<DeprecatedTextureHost> *newHost =
     (aTextureId == TextureFront) ? &mNewFrontHost : &mNewFrontHostOnWhite;
@@ -301,30 +666,30 @@ ContentHostSingleBuffered::EnsureDepreca
   (*newHost)->SetBuffer(new SurfaceDescriptor(aSurface), aAllocator);
   Compositor* compositor = GetCompositor();
   if (compositor) {
     (*newHost)->SetCompositor(compositor);
   }
 }
 
 void
-ContentHostSingleBuffered::DestroyTextures()
+DeprecatedContentHostSingleBuffered::DestroyTextures()
 {
   MOZ_ASSERT(!mNewFrontHost || mNewFrontHost->GetDeAllocator(),
              "We won't be able to destroy our SurfaceDescriptor");
   MOZ_ASSERT(!mNewFrontHostOnWhite || mNewFrontHostOnWhite->GetDeAllocator(),
              "We won't be able to destroy our SurfaceDescriptor");
   mNewFrontHost = nullptr;
   mNewFrontHostOnWhite = nullptr;
 
   // don't touch mDeprecatedTextureHost, we might need it for compositing
 }
 
 void
-ContentHostSingleBuffered::UpdateThebes(const ThebesBufferData& aData,
+DeprecatedContentHostSingleBuffered::UpdateThebes(const ThebesBufferData& aData,
                                         const nsIntRegion& aUpdated,
                                         const nsIntRegion& aOldValidRegionBack,
                                         nsIntRegion* aUpdatedRegionBack)
 {
   aUpdatedRegionBack->SetEmpty();
 
   if (!mDeprecatedTextureHost && !mNewFrontHost) {
     mInitialised = false;
@@ -368,24 +733,61 @@ ContentHostSingleBuffered::UpdateThebes(
     mDeprecatedTextureHostOnWhite->Update(*mDeprecatedTextureHostOnWhite->LockSurfaceDescriptor(), &destRegion);
   }
   mInitialised = true;
 
   mBufferRect = aData.rect();
   mBufferRotation = aData.rotation();
 }
 
-ContentHostDoubleBuffered::~ContentHostDoubleBuffered()
+void
+ContentHostDoubleBuffered::UpdateThebes(const ThebesBufferData& aData,
+                                        const nsIntRegion& aUpdated,
+                                        const nsIntRegion& aOldValidRegionBack,
+                                        nsIntRegion* aUpdatedRegionBack)
+{
+  if (!mTextureHost) {
+    mInitialised = false;
+
+    *aUpdatedRegionBack = aUpdated;
+    return;
+  }
+
+  // We don't need to calculate an update region because we assume that if we
+  // are using double buffering then we have render-to-texture and thus no
+  // upload to do.
+  mTextureHost->Updated();
+  if (mTextureHostOnWhite) {
+    mTextureHostOnWhite->Updated();
+  }
+  mInitialised = true;
+
+  mBufferRect = aData.rect();
+  mBufferRotation = aData.rotation();
+
+  *aUpdatedRegionBack = aUpdated;
+
+  // Save the current valid region of our front buffer, because if
+  // we're double buffering, it's going to be the valid region for the
+  // next back buffer sent back to the renderer.
+  //
+  // NB: we rely here on the fact that mValidRegion is initialized to
+  // empty, and that the first time Swap() is called we don't have a
+  // valid front buffer that we're going to return to content.
+  mValidRegionForNextBackBuffer = aOldValidRegionBack;
+}
+
+DeprecatedContentHostDoubleBuffered::~DeprecatedContentHostDoubleBuffered()
 {
   DestroyTextures();
   DestroyFrontHost();
 }
 
 void
-ContentHostDoubleBuffered::EnsureDeprecatedTextureHost(TextureIdentifier aTextureId,
+DeprecatedContentHostDoubleBuffered::EnsureDeprecatedTextureHost(TextureIdentifier aTextureId,
                                              const SurfaceDescriptor& aSurface,
                                              ISurfaceAllocator* aAllocator,
                                              const TextureInfo& aTextureInfo)
 {
   RefPtr<DeprecatedTextureHost> newHost = DeprecatedTextureHost::CreateDeprecatedTextureHost(aSurface.type(),
                                                                aTextureInfo.mDeprecatedTextureHostFlags,
                                                                aTextureInfo.mTextureFlags,
                                                                this);
@@ -415,17 +817,17 @@ ContentHostDoubleBuffered::EnsureDepreca
     mBackHostOnWhite = newHost;
     return;
   }
 
   NS_ERROR("Bad texture identifier");
 }
 
 void
-ContentHostDoubleBuffered::DestroyTextures()
+DeprecatedContentHostDoubleBuffered::DestroyTextures()
 {
   if (mNewFrontHost) {
     MOZ_ASSERT(mNewFrontHost->GetDeAllocator(),
                "We won't be able to destroy our SurfaceDescriptor");
     mNewFrontHost = nullptr;
   }
   if (mNewFrontHostOnWhite) {
     MOZ_ASSERT(mNewFrontHostOnWhite->GetDeAllocator(),
@@ -442,17 +844,17 @@ ContentHostDoubleBuffered::DestroyTextur
                "We won't be able to destroy our SurfaceDescriptor");
     mBackHostOnWhite = nullptr;
   }
 
   // don't touch mDeprecatedTextureHost, we might need it for compositing
 }
 
 void
-ContentHostDoubleBuffered::OnActorDestroy()
+DeprecatedContentHostDoubleBuffered::OnActorDestroy()
 {
   if (mDeprecatedTextureHost) {
     mDeprecatedTextureHost->OnActorDestroy();
   }
   if (mDeprecatedTextureHostOnWhite) {
     mDeprecatedTextureHostOnWhite->OnActorDestroy();
   }
   if (mNewFrontHost) {
@@ -465,17 +867,17 @@ ContentHostDoubleBuffered::OnActorDestro
     mBackHost->OnActorDestroy();
   }
   if (mBackHostOnWhite) {
     mBackHostOnWhite->OnActorDestroy();
   }
 }
 
 void
-ContentHostDoubleBuffered::UpdateThebes(const ThebesBufferData& aData,
+DeprecatedContentHostDoubleBuffered::UpdateThebes(const ThebesBufferData& aData,
                                         const nsIntRegion& aUpdated,
                                         const nsIntRegion& aOldValidRegionBack,
                                         nsIntRegion* aUpdatedRegionBack)
 {
   if (!mDeprecatedTextureHost && !mNewFrontHost) {
     mInitialised = false;
 
     *aUpdatedRegionBack = aUpdated;
@@ -750,20 +1152,41 @@ ContentHostIncremental::TextureUpdateReq
   if (mTextureId == TextureFront) {
     aHost->mDeprecatedTextureHost->Update(mDescriptor, &mUpdated, &offset);
   } else {
     aHost->mDeprecatedTextureHostOnWhite->Update(mDescriptor, &mUpdated, &offset);
   }
 }
 
 void
-ContentHostSingleBuffered::PrintInfo(nsACString& aTo, const char* aPrefix)
+ContentHostBase::PrintInfo(nsACString& aTo, const char* aPrefix)
 {
   aTo += aPrefix;
-  aTo += nsPrintfCString("ContentHostSingleBuffered (0x%p)", this);
+  aTo += nsPrintfCString("ContentHost (0x%p)", this);
+
+  AppendToString(aTo, mBufferRect, " [buffer-rect=", "]");
+  AppendToString(aTo, mBufferRotation, " [buffer-rotation=", "]");
+  if (PaintWillResample()) {
+    aTo += " [paint-will-resample]";
+  }
+
+  nsAutoCString pfx(aPrefix);
+  pfx += "  ";
+
+  if (mTextureHost) {
+    aTo += "\n";
+    mTextureHost->PrintInfo(aTo, pfx.get());
+  }
+}
+
+void
+DeprecatedContentHostSingleBuffered::PrintInfo(nsACString& aTo, const char* aPrefix)
+{
+  aTo += aPrefix;
+  aTo += nsPrintfCString("DeprecatedContentHostSingleBuffered (0x%p)", this);
 
   AppendToString(aTo, mBufferRect, " [buffer-rect=", "]");
   AppendToString(aTo, mBufferRotation, " [buffer-rotation=", "]");
   if (PaintWillResample()) {
     aTo += " [paint-will-resample]";
   }
 
   nsAutoCString pfx(aPrefix);
@@ -771,20 +1194,20 @@ ContentHostSingleBuffered::PrintInfo(nsA
 
   if (mDeprecatedTextureHost) {
     aTo += "\n";
     mDeprecatedTextureHost->PrintInfo(aTo, pfx.get());
   }
 }
 
 void
-ContentHostDoubleBuffered::PrintInfo(nsACString& aTo, const char* aPrefix)
+DeprecatedContentHostDoubleBuffered::PrintInfo(nsACString& aTo, const char* aPrefix)
 {
   aTo += aPrefix;
-  aTo += nsPrintfCString("ContentHostDoubleBuffered (0x%p)", this);
+  aTo += nsPrintfCString("DeprecatedContentHostDoubleBuffered (0x%p)", this);
 
   AppendToString(aTo, mBufferRect, " [buffer-rect=", "]");
   AppendToString(aTo, mBufferRotation, " [buffer-rotation=", "]");
   if (PaintWillResample()) {
     aTo += " [paint-will-resample]";
   }
 
   nsAutoCString prefix(aPrefix);
@@ -798,21 +1221,21 @@ ContentHostDoubleBuffered::PrintInfo(nsA
   if (mBackHost) {
     aTo += "\n";
     mBackHost->PrintInfo(aTo, prefix.get());
   }
 }
 
 #ifdef MOZ_DUMP_PAINTING
 void
-ContentHostDoubleBuffered::Dump(FILE* aFile,
+DeprecatedContentHostDoubleBuffered::Dump(FILE* aFile,
                                 const char* aPrefix,
                                 bool aDumpHtml)
 {
-  ContentHostBase::Dump(aFile, aPrefix, aDumpHtml);
+  DeprecatedContentHostBase::Dump(aFile, aPrefix, aDumpHtml);
   if (!aDumpHtml) {
     return;
   }
   if (!aFile) {
     aFile = stderr;
   }
   fprintf_stderr(aFile, "<ul>");
   if (mBackHost) {
@@ -829,28 +1252,54 @@ ContentHostDoubleBuffered::Dump(FILE* aF
   }
   fprintf_stderr(aFile, "</ul>");
 }
 #endif
 
 LayerRenderState
 ContentHostBase::GetRenderState()
 {
+  if (!mTextureHost) {
+    return LayerRenderState();
+  }
+
+  LayerRenderState result = mTextureHost->GetRenderState();
+
+  if (mBufferRotation != nsIntPoint()) {
+    result.mFlags |= LAYER_RENDER_STATE_BUFFER_ROTATION;
+  }
+  result.SetOffset(GetOriginOffset());
+  return result;
+}
+
+LayerRenderState
+DeprecatedContentHostBase::GetRenderState()
+{
   LayerRenderState result = mDeprecatedTextureHost->GetRenderState();
 
   if (mBufferRotation != nsIntPoint()) {
     result.mFlags |= LAYER_RENDER_STATE_BUFFER_ROTATION;
   }
   result.SetOffset(GetOriginOffset());
   return result;
 }
 
 #ifdef MOZ_DUMP_PAINTING
 TemporaryRef<gfx::DataSourceSurface>
 ContentHostBase::GetAsSurface()
 {
+  if (!mTextureHost) {
+    return nullptr;
+  }
+
+  return mTextureHost->GetAsSurface();
+}
+
+TemporaryRef<gfx::DataSourceSurface>
+DeprecatedContentHostBase::GetAsSurface()
+{
   return mDeprecatedTextureHost->GetAsSurface();
 }
 #endif
 
 
 } // namespace
 } // namespace
--- a/gfx/layers/composite/ContentHost.h
+++ b/gfx/layers/composite/ContentHost.h
@@ -84,17 +84,77 @@ protected:
  */
 class ContentHostBase : public ContentHost
 {
 public:
   typedef RotatedContentBuffer::ContentType ContentType;
   typedef RotatedContentBuffer::PaintState PaintState;
 
   ContentHostBase(const TextureInfo& aTextureInfo);
-  ~ContentHostBase();
+  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,
+                         TiledLayerProperties* aLayerProperties = nullptr);
+
+  virtual LayerRenderState GetRenderState() MOZ_OVERRIDE;
+
+  virtual void SetCompositor(Compositor* aCompositor) MOZ_OVERRIDE;
+
+#ifdef MOZ_DUMP_PAINTING
+  virtual TemporaryRef<gfx::DataSourceSurface> GetAsSurface() MOZ_OVERRIDE;
+
+  virtual void Dump(FILE* aFile=nullptr,
+                    const char* aPrefix="",
+                    bool aDumpHtml=false) MOZ_OVERRIDE;
+#endif
+
+  virtual void PrintInfo(nsACString& aTo, const char* aPrefix) MOZ_OVERRIDE;
+
+  virtual TextureHost* GetAsTextureHost() MOZ_OVERRIDE;
+
+  virtual void UseTextureHost(TextureHost* aTexture) MOZ_OVERRIDE;
+
+  virtual void RemoveTextureHost(TextureHost* aTexture) MOZ_OVERRIDE;
+
+  virtual void SetPaintWillResample(bool aResample) { mPaintWillResample = aResample; }
+
+  virtual void OnActorDestroy() MOZ_OVERRIDE;
+
+protected:
+  virtual nsIntPoint GetOriginOffset()
+  {
+    return mBufferRect.TopLeft() - mBufferRotation;
+  }
+
+  bool PaintWillResample() { return mPaintWillResample; }
+
+  // These must be called before forgetting mTextureHost or mTextureHostOnWhite
+  void DestroyTextureHost();
+  void DestroyTextureHostOnWhite();
+
+  nsIntRect mBufferRect;
+  nsIntPoint mBufferRotation;
+  RefPtr<TextureHost> mTextureHost;
+  RefPtr<TextureHost> mTextureHostOnWhite;
+  bool mPaintWillResample;
+  bool mInitialised;
+};
+class DeprecatedContentHostBase : public ContentHost
+{
+public:
+  typedef RotatedContentBuffer::ContentType ContentType;
+  typedef RotatedContentBuffer::PaintState PaintState;
+
+  DeprecatedContentHostBase(const TextureInfo& aTextureInfo);
+  ~DeprecatedContentHostBase();
 
   virtual void Composite(EffectChain& aEffectChain,
                          float aOpacity,
                          const gfx::Matrix4x4& aTransform,
                          const gfx::Filter& aFilter,
                          const gfx::Rect& aClipRect,
                          const nsIntRegion* aVisibleRegion = nullptr,
                          TiledLayerProperties* aLayerProperties = nullptr);
@@ -148,26 +208,48 @@ protected:
   // here and move it to mDeprecatedTextureHost once we do the first buffer swap.
   RefPtr<DeprecatedTextureHost> mNewFrontHost;
   RefPtr<DeprecatedTextureHost> mNewFrontHostOnWhite;
   bool mPaintWillResample;
   bool mInitialised;
 };
 
 /**
- * Double buffering is implemented by swapping the front and back DeprecatedTextureHosts.
+ * 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.
  */
 class ContentHostDoubleBuffered : public ContentHostBase
 {
 public:
   ContentHostDoubleBuffered(const TextureInfo& aTextureInfo)
     : ContentHostBase(aTextureInfo)
   {}
 
-  ~ContentHostDoubleBuffered();
+  virtual ~ContentHostDoubleBuffered() {}
+
+  virtual CompositableType GetType() { return COMPOSITABLE_CONTENT_DOUBLE; }
+
+  virtual void UpdateThebes(const ThebesBufferData& aData,
+                            const nsIntRegion& aUpdated,
+                            const nsIntRegion& aOldValidRegionBack,
+                            nsIntRegion* aUpdatedRegionBack);
+
+protected:
+  nsIntRegion mValidRegionForNextBackBuffer;
+};
+
+class DeprecatedContentHostDoubleBuffered : public DeprecatedContentHostBase
+{
+public:
+  DeprecatedContentHostDoubleBuffered(const TextureInfo& aTextureInfo)
+    : DeprecatedContentHostBase(aTextureInfo)
+  {}
+
+  ~DeprecatedContentHostDoubleBuffered();
 
   virtual CompositableType GetType() { return BUFFER_CONTENT_DIRECT; }
 
   virtual void UpdateThebes(const ThebesBufferData& aData,
                             const nsIntRegion& aUpdated,
                             const nsIntRegion& aOldValidRegionBack,
                             nsIntRegion* aUpdatedRegionBack);
 
@@ -200,17 +282,33 @@ protected:
  * DeprecatedTextureHost in the layers transaction (i.e., in UpdateThebes).
  */
 class ContentHostSingleBuffered : public ContentHostBase
 {
 public:
   ContentHostSingleBuffered(const TextureInfo& aTextureInfo)
     : ContentHostBase(aTextureInfo)
   {}
-  virtual ~ContentHostSingleBuffered();
+  virtual ~ContentHostSingleBuffered() {}
+
+  virtual CompositableType GetType() { return COMPOSITABLE_CONTENT_SINGLE; }
+
+  virtual void UpdateThebes(const ThebesBufferData& aData,
+                            const nsIntRegion& aUpdated,
+                            const nsIntRegion& aOldValidRegionBack,
+                            nsIntRegion* aUpdatedRegionBack);
+};
+
+class DeprecatedContentHostSingleBuffered : public DeprecatedContentHostBase
+{
+public:
+  DeprecatedContentHostSingleBuffered(const TextureInfo& aTextureInfo)
+    : DeprecatedContentHostBase(aTextureInfo)
+  {}
+  virtual ~DeprecatedContentHostSingleBuffered();
 
   virtual CompositableType GetType() { return BUFFER_CONTENT; }
 
   virtual void UpdateThebes(const ThebesBufferData& aData,
                             const nsIntRegion& aUpdated,
                             const nsIntRegion& aOldValidRegionBack,
                             nsIntRegion* aUpdatedRegionBack);
 
@@ -228,21 +326,21 @@ public:
  * surfaces that only cover the changed pixels during an update.
  *
  * Takes ownership of the passed in update surfaces, and must
  * free them once texture upload is complete.
  *
  * Delays texture uploads until the next composite to
  * avoid blocking the main thread.
  */
-class ContentHostIncremental : public ContentHostBase
+class ContentHostIncremental : public DeprecatedContentHostBase
 {
 public:
   ContentHostIncremental(const TextureInfo& aTextureInfo)
-    : ContentHostBase(aTextureInfo)
+    : DeprecatedContentHostBase(aTextureInfo)
     , mDeAllocator(nullptr)
   {}
 
   virtual CompositableType GetType() { return BUFFER_CONTENT; }
 
   virtual void EnsureDeprecatedTextureHostIncremental(ISurfaceAllocator* aAllocator,
                                             const TextureInfo& aTextureInfo,
                                             const nsIntRect& aBufferRect) MOZ_OVERRIDE;
@@ -274,17 +372,17 @@ public:
                          const gfx::Matrix4x4& aTransform,
                          const gfx::Filter& aFilter,
                          const gfx::Rect& aClipRect,
                          const nsIntRegion* aVisibleRegion = nullptr,
                          TiledLayerProperties* aLayerProperties = nullptr)
   {
     ProcessTextureUpdates();
 
-    ContentHostBase::Composite(aEffectChain, aOpacity,
+    DeprecatedContentHostBase::Composite(aEffectChain, aOpacity,
                                aTransform, aFilter,
                                aClipRect, aVisibleRegion,
                                aLayerProperties);
   }
 
   virtual void DestroyTextures()
   {
     mDeprecatedTextureHost = nullptr;
--- a/gfx/layers/composite/ImageHost.cpp
+++ b/gfx/layers/composite/ImageHost.cpp
@@ -37,26 +37,26 @@ ImageHost::~ImageHost() {}
 
 void
 ImageHost::UseTextureHost(TextureHost* aTexture)
 {
   mFrontBuffer = aTexture;
 }
 
 void
-ImageHost::RemoveTextureHost(uint64_t aTextureID)
+ImageHost::RemoveTextureHost(TextureHost* aTexture)
 {
-  CompositableHost::RemoveTextureHost(aTextureID);
-  if (mFrontBuffer && mFrontBuffer->GetID() == aTextureID) {
+  CompositableHost::RemoveTextureHost(aTexture);
+  if (mFrontBuffer && mFrontBuffer->GetID() == aTexture->GetID()) {
     mFrontBuffer = nullptr;
   }
 }
 
 TextureHost*
-ImageHost::GetTextureHost()
+ImageHost::GetAsTextureHost()
 {
   return mFrontBuffer;
 }
 
 void
 ImageHost::Composite(EffectChain& aEffectChain,
                      float aOpacity,
                      const gfx::Matrix4x4& aTransform,
--- a/gfx/layers/composite/ImageHost.h
+++ b/gfx/layers/composite/ImageHost.h
@@ -50,19 +50,19 @@ public:
                          const gfx::Matrix4x4& aTransform,
                          const gfx::Filter& aFilter,
                          const gfx::Rect& aClipRect,
                          const nsIntRegion* aVisibleRegion = nullptr,
                          TiledLayerProperties* aLayerProperties = nullptr) MOZ_OVERRIDE;
 
   virtual void UseTextureHost(TextureHost* aTexture) MOZ_OVERRIDE;
 
-  virtual void RemoveTextureHost(uint64_t aTextureID) MOZ_OVERRIDE;
+  virtual void RemoveTextureHost(TextureHost* aTexture) MOZ_OVERRIDE;
 
-  virtual TextureHost* GetTextureHost() MOZ_OVERRIDE;
+  virtual TextureHost* GetAsTextureHost() MOZ_OVERRIDE;
 
   virtual void SetPictureRect(const nsIntRect& aPictureRect) MOZ_OVERRIDE
   {
     mPictureRect = aPictureRect;
     mHasPictureRect = true;
   }
 
   virtual LayerRenderState GetRenderState() MOZ_OVERRIDE;
--- a/gfx/layers/composite/ImageLayerComposite.cpp
+++ b/gfx/layers/composite/ImageLayerComposite.cpp
@@ -116,20 +116,20 @@ void
 ImageLayerComposite::ComputeEffectiveTransforms(const gfx3DMatrix& aTransformToSurface)
 {
   gfx3DMatrix local = GetLocalTransform();
 
   // Snap image edges to pixel boundaries
   gfxRect sourceRect(0, 0, 0, 0);
   if (mImageHost &&
       mImageHost->IsAttached() &&
-      (mImageHost->GetDeprecatedTextureHost() || mImageHost->GetTextureHost())) {
+      (mImageHost->GetDeprecatedTextureHost() || mImageHost->GetAsTextureHost())) {
     IntSize size =
-      mImageHost->GetTextureHost() ? mImageHost->GetTextureHost()->GetSize()
-                                   : mImageHost->GetDeprecatedTextureHost()->GetSize();
+      mImageHost->GetAsTextureHost() ? mImageHost->GetAsTextureHost()->GetSize()
+                                     : mImageHost->GetDeprecatedTextureHost()->GetSize();
     sourceRect.SizeTo(size.width, size.height);
     if (mScaleMode != SCALE_NONE &&
         sourceRect.width != 0.0 && sourceRect.height != 0.0) {
       NS_ASSERTION(mScaleMode == SCALE_STRETCH,
                    "No other scalemodes than stretch and none supported yet.");
       local.Scale(mScaleToSize.width / sourceRect.width,
                   mScaleToSize.height / sourceRect.height, 1.0);
     }
--- a/gfx/layers/composite/TextureHost.cpp
+++ b/gfx/layers/composite/TextureHost.cpp
@@ -515,16 +515,17 @@ ShmemTextureHost::~ShmemTextureHost()
 
 void
 ShmemTextureHost::DeallocateSharedData()
 {
   if (mShmem) {
     MOZ_ASSERT(mDeallocator,
                "Shared memory would leak without a ISurfaceAllocator");
     mDeallocator->DeallocShmem(*mShmem);
+    mShmem = nullptr;
   }
 }
 
 void
 ShmemTextureHost::OnActorDestroy()
 {
   delete mShmem;
   mShmem = nullptr;
@@ -553,16 +554,17 @@ MemoryTextureHost::~MemoryTextureHost()
 
 void
 MemoryTextureHost::DeallocateSharedData()
 {
   if (mBuffer) {
     GfxMemoryImageReporter::WillFree(mBuffer);
   }
   delete[] mBuffer;
+  mBuffer = nullptr;
 }
 
 uint8_t* MemoryTextureHost::GetBuffer()
 {
   return mBuffer;
 }
 
 } // namespace
--- a/gfx/layers/composite/TextureHost.h
+++ b/gfx/layers/composite/TextureHost.h
@@ -303,17 +303,17 @@ public:
    * 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.
    */
-  virtual void Updated(const nsIntRegion* aRegion) {}
+  virtual void Updated(const nsIntRegion* aRegion = nullptr) {}
 
   /**
    * Sets this TextureHost's compositor.
    * A TextureHost can change compositor on certain occasions, in particular if
    * it belongs to an async Compositable.
    * aCompositor can be null, in which case the TextureHost must cleanup  all
    * of it's device textures.
    */
@@ -417,17 +417,17 @@ public:
   BufferTextureHost(uint64_t aID,
                     gfx::SurfaceFormat aFormat,
                     TextureFlags aFlags);
 
   ~BufferTextureHost();
 
   virtual uint8_t* GetBuffer() = 0;
 
-  virtual void Updated(const nsIntRegion* aRegion) MOZ_OVERRIDE;
+  virtual void Updated(const nsIntRegion* aRegion = nullptr) MOZ_OVERRIDE;
 
   virtual bool Lock() MOZ_OVERRIDE;
 
   virtual void Unlock() MOZ_OVERRIDE;
 
   virtual NewTextureSource* GetTextureSources() MOZ_OVERRIDE;
 
   virtual void DeallocateDeviceData() MOZ_OVERRIDE;
--- a/gfx/layers/ipc/AsyncPanZoomController.cpp
+++ b/gfx/layers/ipc/AsyncPanZoomController.cpp
@@ -273,16 +273,67 @@ static TimeStamp sFrameTime;
 static TimeStamp
 GetFrameTime() {
   if (sFrameTime.IsNull()) {
     return TimeStamp::Now();
   }
   return sFrameTime;
 }
 
+class FlingAnimation: public AsyncPanZoomAnimation {
+public:
+  FlingAnimation(AxisX aX, AxisY aY)
+    : AsyncPanZoomAnimation(TimeDuration::FromMilliseconds(gFlingRepaintInterval))
+    , mX(aX)
+    , mY(aY)
+  {}
+  /**
+   * Advances a fling by an interpolated amount based on the passed in |aDelta|.
+   * This should be called whenever sampling the content transform for this
+   * frame. Returns true if the fling animation should be advanced by one frame,
+   * or false if there is no fling or the fling has ended.
+   */
+  virtual bool Sample(FrameMetrics& aFrameMetrics,
+                      const TimeDuration& aDelta);
+
+private:
+  AxisX mX;
+  AxisY mY;
+};
+
+class ZoomAnimation: public AsyncPanZoomAnimation {
+public:
+  ZoomAnimation(CSSPoint aStartOffset, CSSToScreenScale aStartZoom,
+                CSSPoint aEndOffset, CSSToScreenScale aEndZoom)
+    : mStartOffset(aStartOffset)
+    , mStartZoom(aStartZoom)
+    , mEndOffset(aEndOffset)
+    , mEndZoom(aEndZoom)
+  {}
+
+  virtual bool Sample(FrameMetrics& aFrameMetrics,
+                      const TimeDuration& aDelta);
+
+private:
+  TimeDuration mDuration;
+
+  // Old metrics from before we started a zoom animation. This is only valid
+  // when we are in the "ANIMATED_ZOOM" state. This is used so that we can
+  // interpolate between the start and end frames. We only use the
+  // |mViewportScrollOffset| and |mResolution| fields on this.
+  CSSPoint mStartOffset;
+  CSSToScreenScale mStartZoom;
+
+  // Target metrics for a zoom to animation. This is only valid when we are in
+  // the "ANIMATED_ZOOM" state. We only use the |mViewportScrollOffset| and
+  // |mResolution| fields on this.
+  CSSPoint mEndOffset;
+  CSSToScreenScale mEndZoom;
+};
+
 void
 AsyncPanZoomController::SetFrameTime(const TimeStamp& aTime) {
   sFrameTime = aTime;
 }
 
 /*static*/ void
 AsyncPanZoomController::InitializeGlobalState()
 {
@@ -393,31 +444,16 @@ AsyncPanZoomController::GetTouchStartTol
   return gTouchStartTolerance;
 }
 
 /* static */AsyncPanZoomController::AxisLockMode AsyncPanZoomController::GetAxisLockMode()
 {
   return static_cast<AxisLockMode>(gAxisLockMode);
 }
 
-static CSSPoint
-WidgetSpaceToCompensatedViewportSpace(const ScreenPoint& aPoint,
-                                      const CSSToScreenScale& aCurrentZoom)
-{
-  // Transform the input point from local widget space to the content document
-  // space that the user is seeing, from last composite.
-  // FIXME/bug 775451: this doesn't attempt to compensate for content transforms
-  // in effect on the compositor.  The problem is that it's very hard for us to
-  // know what content CSS pixel is at widget point 0,0 based on information
-  // available here.  So we use this hacky implementation for now, which works
-  // in quiescent states.
-
-  return aPoint / aCurrentZoom;
-}
-
 nsEventStatus AsyncPanZoomController::ReceiveInputEvent(const InputData& aEvent) {
   // If we may have touch listeners, we enable the machinery that allows touch
   // listeners to preventDefault any touch inputs. This should not happen unless
   // there are actually touch listeners as it introduces potentially unbounded
   // lag because it causes a round-trip through content.  Usually, if content is
   // responding in a timely fashion, this only introduces a nearly constant few
   // hundred ms of lag.
   if (mFrameMetrics.mMayHaveTouchListeners && aEvent.mInputType == MULTITOUCH_INPUT &&
@@ -607,22 +643,22 @@ nsEventStatus AsyncPanZoomController::On
     SetState(NOTHING);
     return nsEventStatus_eIgnore;
 
   case PANNING:
   case PANNING_LOCKED_X:
   case PANNING_LOCKED_Y:
     {
       ReentrantMonitorAutoEnter lock(mMonitor);
-      ScheduleComposite();
       RequestContentRepaint();
     }
     mX.EndTouch();
     mY.EndTouch();
     SetState(FLING);
+    StartAnimation(new FlingAnimation(mX, mY));
     return nsEventStatus_eConsumeNoDefault;
 
   case PINCHING:
     SetState(NOTHING);
     // Scale gesture listener should have handled this.
     NS_WARNING("Gesture listener should have handled pinching in OnTouchEnd.");
     return nsEventStatus_eIgnore;
 
@@ -744,69 +780,96 @@ nsEventStatus AsyncPanZoomController::On
     ReentrantMonitorAutoEnter lock(mMonitor);
     ScheduleComposite();
     RequestContentRepaint();
   }
 
   return nsEventStatus_eConsumeNoDefault;
 }
 
+bool
+AsyncPanZoomController::ConvertToGecko(const ScreenPoint& aPoint, CSSIntPoint* aOut)
+{
+  APZCTreeManager* treeManagerLocal = mTreeManager;
+  if (treeManagerLocal) {
+    gfx3DMatrix transformToApzc;
+    gfx3DMatrix transformToGecko;
+    treeManagerLocal->GetInputTransforms(this, transformToApzc, transformToGecko);
+    gfxPoint result = transformToGecko.Transform(gfxPoint(aPoint.x, aPoint.y));
+    // NOTE: This isn't *quite* LayoutDevicePoint, we just don't have a name
+    // for this coordinate space and it maps the closest to LayoutDevicePoint.
+    LayoutDevicePoint layoutPoint = LayoutDevicePoint(result.x, result.y);
+    CSSPoint cssPoint = layoutPoint / mFrameMetrics.mDevPixelsPerCSSPixel;
+    *aOut = gfx::RoundedToInt(cssPoint);
+    return true;
+  }
+  return false;
+}
+
 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) {
     ReentrantMonitorAutoEnter lock(mMonitor);
 
-    CSSPoint point = WidgetSpaceToCompensatedViewportSpace(aEvent.mPoint, mFrameMetrics.mZoom);
     int32_t modifiers = WidgetModifiersToDOMModifiers(aEvent.modifiers);
-    controller->HandleLongTap(gfx::RoundedToInt(point), modifiers);
-    return nsEventStatus_eConsumeNoDefault;
+    CSSIntPoint geckoScreenPoint;
+    if (ConvertToGecko(aEvent.mPoint, &geckoScreenPoint)) {
+      controller->HandleLongTap(geckoScreenPoint, modifiers);
+      return nsEventStatus_eConsumeNoDefault;
+    }
   }
   return nsEventStatus_eIgnore;
 }
 
 nsEventStatus AsyncPanZoomController::OnSingleTapUp(const TapGestureInput& aEvent) {
   APZC_LOG("%p got a single-tap-up in state %d\n", this, mState);
   nsRefPtr<GeckoContentController> controller = GetGeckoContentController();
   if (controller && !mAllowZoom) {
     ReentrantMonitorAutoEnter lock(mMonitor);
 
-    CSSPoint point = WidgetSpaceToCompensatedViewportSpace(aEvent.mPoint, mFrameMetrics.mZoom);
     int32_t modifiers = WidgetModifiersToDOMModifiers(aEvent.modifiers);
-    controller->HandleSingleTap(gfx::RoundedToInt(point), modifiers);
-    return nsEventStatus_eConsumeNoDefault;
+    CSSIntPoint geckoScreenPoint;
+    if (ConvertToGecko(aEvent.mPoint, &geckoScreenPoint)) {
+      controller->HandleSingleTap(geckoScreenPoint, modifiers);
+      return nsEventStatus_eConsumeNoDefault;
+    }
   }
   return nsEventStatus_eIgnore;
 }
 
 nsEventStatus AsyncPanZoomController::OnSingleTapConfirmed(const TapGestureInput& aEvent) {
   APZC_LOG("%p got a single-tap-confirmed in state %d\n", this, mState);
   nsRefPtr<GeckoContentController> controller = GetGeckoContentController();
   // If zooming is disabled, we handle this in OnSingleTapUp
   if (controller && mAllowZoom) {
     ReentrantMonitorAutoEnter lock(mMonitor);
 
-    CSSPoint point = WidgetSpaceToCompensatedViewportSpace(aEvent.mPoint, mFrameMetrics.mZoom);
     int32_t modifiers = WidgetModifiersToDOMModifiers(aEvent.modifiers);
-    controller->HandleSingleTap(gfx::RoundedToInt(point), modifiers);
-    return nsEventStatus_eConsumeNoDefault;
+    CSSIntPoint geckoScreenPoint;
+    if (ConvertToGecko(aEvent.mPoint, &geckoScreenPoint)) {
+      controller->HandleSingleTap(geckoScreenPoint, modifiers);
+      return nsEventStatus_eConsumeNoDefault;
+    }
   }
   return nsEventStatus_eIgnore;
 }
 
 nsEventStatus AsyncPanZoomController::OnDoubleTap(const TapGestureInput& aEvent) {
   APZC_LOG("%p got a double-tap in state %d\n", this, mState);
   nsRefPtr<GeckoContentController> controller = GetGeckoContentController();
   if (controller) {
-    ReentrantMonitorAutoEnter lock(mMonitor);
 
     if (mAllowZoom) {
-      CSSPoint point = WidgetSpaceToCompensatedViewportSpace(aEvent.mPoint, mFrameMetrics.mZoom);
+      ReentrantMonitorAutoEnter lock(mMonitor);
       int32_t modifiers = WidgetModifiersToDOMModifiers(aEvent.modifiers);
-      controller->HandleDoubleTap(gfx::RoundedToInt(point), modifiers);
+      CSSIntPoint geckoScreenPoint;
+      if (ConvertToGecko(aEvent.mPoint, &geckoScreenPoint)) {
+        controller->HandleDoubleTap(geckoScreenPoint, modifiers);
+      }
     }
 
     return nsEventStatus_eConsumeNoDefault;
   }
   return nsEventStatus_eIgnore;
 }
 
 nsEventStatus AsyncPanZoomController::OnCancelTap(const TapGestureInput& aEvent) {
@@ -982,52 +1045,51 @@ void AsyncPanZoomController::TrackTouch(
 
   CallDispatchScroll(prevTouchPoint, touchPoint, 0);
 }
 
 ScreenIntPoint& AsyncPanZoomController::GetFirstTouchScreenPoint(const MultiTouchInput& aEvent) {
   return ((SingleTouchData&)aEvent.mTouches[0]).mScreenPoint;
 }
 
-bool AsyncPanZoomController::DoFling(const TimeDuration& aDelta) {
-  if (mState != FLING) {
-    return false;
-  }
-
+bool FlingAnimation::Sample(FrameMetrics& aFrameMetrics,
+                            const TimeDuration& aDelta) {
   bool shouldContinueFlingX = mX.FlingApplyFrictionOrCancel(aDelta),
        shouldContinueFlingY = mY.FlingApplyFrictionOrCancel(aDelta);
   // If we shouldn't continue the fling, let's just stop and repaint.
   if (!shouldContinueFlingX && !shouldContinueFlingY) {
-    SendAsyncScrollEvent();
-    RequestContentRepaint();
-    SetState(NOTHING);
     return false;
   }
 
   CSSPoint overscroll; // overscroll is ignored for flings
   ScreenPoint offset(aDelta.ToMilliseconds() * mX.GetVelocity(),
                      aDelta.ToMilliseconds() * mY.GetVelocity());
 
   // Inversely scale the offset by the resolution (when you're zoomed further in,
   // a larger swipe should move you a shorter distance).
-  CSSPoint cssOffset = offset / mFrameMetrics.mZoom;
-  ScrollBy(CSSPoint::FromUnknownPoint(gfx::Point(
+  CSSPoint cssOffset = offset / aFrameMetrics.mZoom;
+  aFrameMetrics.mScrollOffset += CSSPoint::FromUnknownPoint(gfx::Point(
     mX.AdjustDisplacement(cssOffset.x, overscroll.x),
     mY.AdjustDisplacement(cssOffset.y, overscroll.y)
-  )));
-  TimeDuration timePaintDelta = mPaintThrottler.TimeSinceLastRequest(GetFrameTime());
-  if (timePaintDelta.ToMilliseconds() > gFlingRepaintInterval) {
-    RequestContentRepaint();
-  }
+  ));
 
   return true;
 }
 
+void AsyncPanZoomController::StartAnimation(AsyncPanZoomAnimation* aAnimation)
+{
+  ReentrantMonitorAutoEnter lock(mMonitor);
+  mAnimation = aAnimation;
+  mLastSampleTime = GetFrameTime();
+  ScheduleComposite();
+}
+
 void AsyncPanZoomController::CancelAnimation() {
   SetState(NOTHING);
+  mAnimation = nullptr;
 }
 
 void AsyncPanZoomController::SetCompositorParent(CompositorParent* aCompositorParent) {
   mCompositorParent = aCompositorParent;
 }
 
 void AsyncPanZoomController::ScrollBy(const CSSPoint& aOffset) {
   mFrameMetrics.mScrollOffset += aOffset;
@@ -1224,70 +1286,79 @@ AsyncPanZoomController::FireAsyncScrollO
 {
   if (mCurrentAsyncScrollOffset != mLastAsyncScrollOffset) {
     ReentrantMonitorAutoEnter lock(mMonitor);
     SendAsyncScrollEvent();
   }
   mAsyncScrollTimeoutTask = nullptr;
 }
 
+bool ZoomAnimation::Sample(FrameMetrics& aFrameMetrics,
+                           const TimeDuration& aDelta) {
+  mDuration += aDelta;
+  double animPosition = mDuration / ZOOM_TO_DURATION;
+
+  if (animPosition >= 1.0) {
+    aFrameMetrics.mZoom = mEndZoom;
+    aFrameMetrics.mScrollOffset = mEndOffset;
+    return false;
+  }
+
+  // Sample the zoom at the current time point.  The sampled zoom
+  // will affect the final computed resolution.
+  double sampledPosition = gComputedTimingFunction->GetValue(animPosition);
+
+  // We scale the scrollOffset linearly with sampledPosition, so the zoom
+  // needs to scale inversely to match.
+  aFrameMetrics.mZoom = CSSToScreenScale(1 /
+    (sampledPosition / mEndZoom.scale +
+    (1 - sampledPosition) / mStartZoom.scale));
+
+  aFrameMetrics.mScrollOffset = CSSPoint::FromUnknownPoint(gfx::Point(
+    mEndOffset.x * sampledPosition + mStartOffset.x * (1 - sampledPosition),
+    mEndOffset.y * sampledPosition + mStartOffset.y * (1 - sampledPosition)
+  ));
+
+  return true;
+}
+
+bool AsyncPanZoomController::UpdateAnimation(const TimeStamp& aSampleTime)
+{
+  if (mAnimation) {
+    if (mAnimation->Sample(mFrameMetrics, aSampleTime - mLastSampleTime)) {
+      if (mPaintThrottler.TimeSinceLastRequest(aSampleTime) >
+          mAnimation->mRepaintInterval) {
+        RequestContentRepaint();
+      }
+    } else {
+      mAnimation = nullptr;
+      SetState(NOTHING);
+      SendAsyncScrollEvent();
+      RequestContentRepaint();
+    }
+    mLastSampleTime = aSampleTime;
+    return true;
+  }
+  return false;
+}
+
 bool AsyncPanZoomController::SampleContentTransformForFrame(const TimeStamp& aSampleTime,
                                                             ViewTransform* aNewTransform,
                                                             ScreenPoint& aScrollOffset) {
   // The eventual return value of this function. The compositor needs to know
   // whether or not to advance by a frame as soon as it can. For example, if a
   // fling is happening, it has to keep compositing so that the animation is
   // smooth. If an animation frame is requested, it is the compositor's
   // responsibility to schedule a composite.
   bool requestAnimationFrame = false;
 
   {
     ReentrantMonitorAutoEnter lock(mMonitor);
 
-    switch (mState) {
-    case FLING:
-      // If a fling is currently happening, apply it now. We can pull
-      // the updated metrics afterwards.
-      requestAnimationFrame |= DoFling(aSampleTime - mLastSampleTime);
-      break;
-    case ANIMATING_ZOOM: {
-      double animPosition = (aSampleTime - mAnimationStartTime) / ZOOM_TO_DURATION;
-      if (animPosition > 1.0) {
-        animPosition = 1.0;
-      }
-      // Sample the zoom at the current time point.  The sampled zoom
-      // will affect the final computed resolution.
-      double sampledPosition = gComputedTimingFunction->GetValue(animPosition);
-
-      // We scale the scrollOffset linearly with sampledPosition, so the zoom
-      // needs to scale inversely to match.
-      mFrameMetrics.mZoom = CSSToScreenScale(1 /
-        (sampledPosition / mEndZoomToMetrics.mZoom.scale +
-          (1 - sampledPosition) / mStartZoomToMetrics.mZoom.scale));
-
-      mFrameMetrics.mScrollOffset = CSSPoint::FromUnknownPoint(gfx::Point(
-        mEndZoomToMetrics.mScrollOffset.x * sampledPosition +
-          mStartZoomToMetrics.mScrollOffset.x * (1 - sampledPosition),
-        mEndZoomToMetrics.mScrollOffset.y * sampledPosition +
-          mStartZoomToMetrics.mScrollOffset.y * (1 - sampledPosition)
-      ));
-
-      requestAnimationFrame = true;
-
-      if (aSampleTime - mAnimationStartTime >= ZOOM_TO_DURATION) {
-        SetState(NOTHING);
-        SendAsyncScrollEvent();
-        RequestContentRepaint();
-      }
-
-      break;
-    }
-    default:
-      break;
-    }
+    requestAnimationFrame = UpdateAnimation(aSampleTime);
 
     aScrollOffset = mFrameMetrics.mScrollOffset * mFrameMetrics.mZoom;
     *aNewTransform = GetCurrentAsyncTransform();
 
     LogRendertraceRect("viewport", "red",
       CSSRect(mFrameMetrics.mScrollOffset,
               ScreenSize(mFrameMetrics.mCompositionBounds.Size()) / mFrameMetrics.mZoom));
 
@@ -1316,18 +1387,16 @@ bool AsyncPanZoomController::SampleConte
   else {
     mAsyncScrollTimeoutTask =
       NewRunnableMethod(this, &AsyncPanZoomController::FireAsyncScrollOnTimeout);
     MessageLoop::current()->PostDelayedTask(FROM_HERE,
                                             mAsyncScrollTimeoutTask,
                                             gAsyncScrollTimeout);
   }
 
-  mLastSampleTime = aSampleTime;
-
   return requestAnimationFrame;
 }
 
 ViewTransform AsyncPanZoomController::GetCurrentAsyncTransform() {
   ReentrantMonitorAutoEnter lock(mMonitor);
 
   CSSPoint lastPaintScrollOffset;
   if (mLastContentPaintMetrics.IsScrollable()) {
@@ -1473,48 +1542,49 @@ void AsyncPanZoomController::ZoomToRect(
                            cssPageRect.width,
                            newHeight);
       aRect = aRect.Intersect(cssPageRect);
       targetZoom = CSSToScreenScale(std::min(compositionBounds.width / aRect.width,
                                              compositionBounds.height / aRect.height));
     }
 
     targetZoom.scale = clamped(targetZoom.scale, localMinZoom.scale, localMaxZoom.scale);
-    mEndZoomToMetrics = mFrameMetrics;
-    mEndZoomToMetrics.mZoom = targetZoom;
+    FrameMetrics endZoomToMetrics = mFrameMetrics;
+    endZoomToMetrics.mZoom = targetZoom;
 
     // Adjust the zoomToRect to a sensible position to prevent overscrolling.
-    CSSRect rectAfterZoom = mEndZoomToMetrics.CalculateCompositedRectInCssPixels();
+    CSSRect rectAfterZoom = endZoomToMetrics.CalculateCompositedRectInCssPixels();
 
     // If either of these conditions are met, the page will be
     // overscrolled after zoomed
     if (aRect.y + rectAfterZoom.height > cssPageRect.height) {
       aRect.y = cssPageRect.height - rectAfterZoom.height;
       aRect.y = aRect.y > 0 ? aRect.y : 0;
     }
     if (aRect.x + rectAfterZoom.width > cssPageRect.width) {
       aRect.x = cssPageRect.width - rectAfterZoom.width;
       aRect.x = aRect.x > 0 ? aRect.x : 0;
     }
 
-    mStartZoomToMetrics = mFrameMetrics;
-    mEndZoomToMetrics.mScrollOffset = aRect.TopLeft();
-    mEndZoomToMetrics.mDisplayPort =
-      CalculatePendingDisplayPort(mEndZoomToMetrics,
+    endZoomToMetrics.mScrollOffset = aRect.TopLeft();
+    endZoomToMetrics.mDisplayPort =
+      CalculatePendingDisplayPort(endZoomToMetrics,
                                   gfx::Point(0,0),
                                   gfx::Point(0,0),
                                   0);
 
-    mAnimationStartTime = GetFrameTime();
-
-    ScheduleComposite();
+    StartAnimation(new ZoomAnimation(
+        mFrameMetrics.mScrollOffset,
+        mFrameMetrics.mZoom,
+        endZoomToMetrics.mScrollOffset,
+        endZoomToMetrics.mZoom));
 
     // Schedule a repaint now, so the new displayport will be painted before the
     // animation finishes.
-    ScheduleContentRepaint(mEndZoomToMetrics);
+    ScheduleContentRepaint(endZoomToMetrics);
   }
 }
 
 void AsyncPanZoomController::ContentReceivedTouch(bool aPreventDefault) {
   if (!mFrameMetrics.mMayHaveTouchListeners) {
     mTouchQueue.Clear();
     return;
   }
@@ -1588,16 +1658,27 @@ void AsyncPanZoomController::UpdateZoomC
   if (gAsyncZoomDisabled) {
     return;
   }
   mAllowZoom = aAllowZoom;
   mMinZoom = (MIN_ZOOM > aMinZoom ? MIN_ZOOM : aMinZoom);
   mMaxZoom = (MAX_ZOOM > aMaxZoom ? aMaxZoom : MAX_ZOOM);
 }
 
+void
+AsyncPanZoomController::GetZoomConstraints(bool* aAllowZoom,
+                                           CSSToScreenScale* aMinZoom,
+                                           CSSToScreenScale* aMaxZoom)
+{
+  *aAllowZoom = mAllowZoom;
+  *aMinZoom = mMinZoom;
+  *aMaxZoom = mMaxZoom;
+}
+
+
 void AsyncPanZoomController::PostDelayedTask(Task* aTask, int aDelayMs) {
   nsRefPtr<GeckoContentController> controller = GetGeckoContentController();
   if (controller) {
     controller->PostDelayedTask(aTask, aDelayMs);
   }
 }
 
 void AsyncPanZoomController::SendAsyncScrollEvent() {
--- a/gfx/layers/ipc/AsyncPanZoomController.h
+++ b/gfx/layers/ipc/AsyncPanZoomController.h
@@ -25,16 +25,17 @@ namespace mozilla {
 namespace layers {
 
 struct ScrollableLayerGuid;
 class CompositorParent;
 class GestureEventListener;
 class ContainerLayer;
 class ViewTransform;
 class APZCTreeManager;
+class AsyncPanZoomAnimation;
 
 /**
  * Controller for all panning and zooming logic. Any time a user input is
  * detected and it must be processed in some way to affect what the user sees,
  * it goes through here. Listens for any input event from InputData and can
  * optionally handle WidgetGUIEvent-derived touch events, but this must be done
  * on the main thread. Note that this class completely cross-platform.
  *
@@ -128,29 +129,39 @@ public:
   void ContentReceivedTouch(bool aPreventDefault);
 
   /**
    * Updates any zoom constraints contained in the <meta name="viewport"> tag.
    * We try to obey everything it asks us elsewhere, but here we only handle
    * minimum-scale, maximum-scale, and user-scalable.
    */
   void UpdateZoomConstraints(bool aAllowZoom,
-                             const mozilla::CSSToScreenScale& aMinScale,
-                             const mozilla::CSSToScreenScale& aMaxScale);
+                             const CSSToScreenScale& aMinScale,
+                             const CSSToScreenScale& aMaxScale);
+
+  /**
+   * Return the zoom constraints last set for this APZC (in the constructor
+   * or in UpdateZoomConstraints()).
+   */
+  void GetZoomConstraints(bool* aAllowZoom,
+                          CSSToScreenScale* aMinScale,
+                          CSSToScreenScale* aMaxScale);
 
   /**
    * Schedules a runnable to run on the controller/UI thread at some time
    * in the future.
    */
   void PostDelayedTask(Task* aTask, int aDelayMs);
 
   // --------------------------------------------------------------------------
   // These methods must only be called on the compositor thread.
   //
 
+  bool UpdateAnimation(const TimeStamp& aSampleTime);
+
   /**
    * The compositor calls this when it's about to draw pannable/zoomable content
    * and is setting up transforms for compositing the layer tree. This is not
    * idempotent. For example, a fling transform can be applied each time this is
    * called (though not necessarily). |aSampleTime| is the time that this is
    * sampled at; this is used for interpolating animations. Calling this sets a
    * new transform in |aNewTransform| which should be multiplied to the transform
    * in the shadow layer corresponding to this APZC.
@@ -251,16 +262,18 @@ public:
 
   /**
    * Update mFrameMetrics.mScrollOffset to the given offset.
    * This is necessary in cases where a scroll is not caused by user
    * input (for example, a content scrollTo()).
    */
   void UpdateScrollOffset(const CSSPoint& aScrollOffset);
 
+  void StartAnimation(AsyncPanZoomAnimation* aAnimation);
+
   /**
    * Cancels any currently running animation. Note that all this does is set the
    * state of the AsyncPanZoomController back to NOTHING, but it is the
    * animation's responsibility to check this before advancing.
    */
   void CancelAnimation();
 
   /**
@@ -521,16 +534,24 @@ private:
   /**
    * Helper to set the current state. Holds the monitor before actually setting
    * it and fires content controller events based on state changes. Always set
    * the state using this call, do not set it directly.
    */
   void SetState(PanZoomState aState);
 
   /**
+   * Convert ScreenPoint relative to this APZC to CSSIntPoint relative
+   * to the parent document. This excludes the transient compositor transform.
+   * NOTE: This must be converted to CSSIntPoint relative to the child
+   * document before sending over IPC.
+   */
+  bool ConvertToGecko(const ScreenPoint& aPoint, CSSIntPoint* aOut);
+
+  /**
    * Internal helpers for checking general state of this apzc.
    */
   bool IsTransformingState(PanZoomState aState);
   bool IsPanningState(PanZoomState mState);
 
   enum AxisLockMode {
     FREE,     /* No locking at all */
     STANDARD, /* Default axis locking mode that remains locked until pan ends*/
@@ -573,50 +594,36 @@ private:
   // the Gecko state, it should be used as a basis for untransformation when
   // sending messages back to Gecko.
   FrameMetrics mLastContentPaintMetrics;
   // The last metrics that we requested a paint for. These are used to make sure
   // that we're not requesting a paint of the same thing that's already drawn.
   // If we don't do this check, we don't get a ShadowLayersUpdated back.
   FrameMetrics mLastPaintRequestMetrics;
 
-  // Old metrics from before we started a zoom animation. This is only valid
-  // when we are in the "ANIMATED_ZOOM" state. This is used so that we can
-  // interpolate between the start and end frames. We only use the
-  // |mViewportScrollOffset| and |mResolution| fields on this.
-  FrameMetrics mStartZoomToMetrics;
-  // Target metrics for a zoom to animation. This is only valid when we are in
-  // the "ANIMATED_ZOOM" state. We only use the |mViewportScrollOffset| and
-  // |mResolution| fields on this.
-  FrameMetrics mEndZoomToMetrics;
-
   nsTArray<MultiTouchInput> mTouchQueue;
 
   CancelableTask* mTouchListenerTimeoutTask;
 
   AxisX mX;
   AxisY mY;
 
   // Most up-to-date constraints on zooming. These should always be reasonable
   // values; for example, allowing a min zoom of 0.0 can cause very bad things
   // to happen.
   bool mAllowZoom;
-  mozilla::CSSToScreenScale mMinZoom;
-  mozilla::CSSToScreenScale mMaxZoom;
+  CSSToScreenScale mMinZoom;
+  CSSToScreenScale mMaxZoom;
 
   // The last time the compositor has sampled the content transform for this
   // frame.
   TimeStamp mLastSampleTime;
   // The last time a touch event came through on the UI thread.
   uint32_t mLastEventTime;
 
-  // Start time of an animation. This is used for a zoom to animation to mark
-  // the beginning.
-  TimeStamp mAnimationStartTime;
-
   // Stores the previous focus point if there is a pinch gesture happening. Used
   // to allow panning by moving multiple fingers (thus moving the focus point).
   ScreenPoint mLastZoomFocus;
 
   // Stores the state of panning and zooming this frame. This is protected by
   // |mMonitor|; that is, it should be held whenever this is updated.
   PanZoomState mState;
 
@@ -634,16 +641,18 @@ private:
   CancelableTask* mAsyncScrollTimeoutTask;
 
   // Flag used to determine whether or not we should try to enter the
   // WAITING_LISTENERS state. This is used in the case that we are processing a
   // queued up event block. If set, this means that we are handling this queue
   // and we don't want to queue the events back up again.
   bool mHandlingTouchQueue;
 
+  RefPtr<AsyncPanZoomAnimation> mAnimation;
+
   friend class Axis;
 
   /* The functions and members in this section are used to build a tree
    * structure out of APZC instances. This tree can only be walked or
    * manipulated while holding the lock in the associated APZCTreeManager
    * instance.
    */
 public:
@@ -719,12 +728,35 @@ private:
   ScreenRect mVisibleRect;
   /* This is the cumulative CSS transform for all the layers between the parent
    * APZC and this one (not inclusive) */
   gfx3DMatrix mAncestorTransform;
   /* This is the CSS transform for this APZC's layer. */
   gfx3DMatrix mCSSTransform;
 };
 
+class AsyncPanZoomAnimation {
+  NS_INLINE_DECL_THREADSAFE_REFCOUNTING(AsyncPanZoomAnimation)
+
+public:
+  AsyncPanZoomAnimation(const TimeDuration& aRepaintInterval =
+                        TimeDuration::Forever())
+    : mRepaintInterval(aRepaintInterval)
+  { }
+
+  virtual ~AsyncPanZoomAnimation()
+  { }
+
+  virtual bool Sample(FrameMetrics& aFrameMetrics,
+                      const TimeDuration& aDelta) = 0;
+
+  /**
+   * Specifies how frequently (at most) we want to do repaints during the
+   * animation sequence. TimeDuration::Forever() will cause it to only repaint
+   * at the end of the animation.
+   */
+  TimeDuration mRepaintInterval;
+};
+
 }
 }
 
 #endif // mozilla_layers_PanZoomController_h
--- a/gfx/layers/ipc/CompositableTransactionParent.cpp
+++ b/gfx/layers/ipc/CompositableTransactionParent.cpp
@@ -93,17 +93,17 @@ CompositableParentManager::ReceiveCompos
                                                  op.textureInfo(),
                                                  op.bufferRect());
       break;
     }
     case CompositableOperation::TOpDestroyThebesBuffer: {
       MOZ_LAYERS_LOG(("[ParentSide] Created double buffer"));
       const OpDestroyThebesBuffer& op = aEdit.get_OpDestroyThebesBuffer();
       CompositableParent* compositableParent = static_cast<CompositableParent*>(op.compositableParent());
-      ContentHostBase* content = static_cast<ContentHostBase*>(compositableParent->GetCompositableHost());
+      DeprecatedContentHostBase* content = static_cast<DeprecatedContentHostBase*>(compositableParent->GetCompositableHost());
       content->DestroyTextures();
 
       break;
     }
     case CompositableOperation::TOpPaintTexture: {
       MOZ_LAYERS_LOG(("[ParentSide] Paint Texture X"));
       const OpPaintTexture& op = aEdit.get_OpPaintTexture();
 
@@ -266,21 +266,22 @@ CompositableParentManager::ReceiveCompos
       }
       CompositableHost* compositable = AsCompositable(op);
 
       RefPtr<TextureHost> texture = compositable->GetTextureHost(op.textureID());
       MOZ_ASSERT(texture);
 
       TextureFlags flags = texture->GetFlags();
 
-      if (!(flags & TEXTURE_DEALLOCATE_CLIENT)) {
+      if (!(flags & TEXTURE_DEALLOCATE_CLIENT) &&
+          !(flags & TEXTURE_DEALLOCATE_DEFERRED)) {
         texture->DeallocateSharedData();
       }
 
-      compositable->RemoveTextureHost(op.textureID());
+      compositable->RemoveTextureHost(texture);
 
       // if it is not the host that deallocates the shared data, then we need
       // to notfy the client side to tell when it is safe to deallocate or
       // reuse it.
       if (flags & TEXTURE_DEALLOCATE_CLIENT) {
         replyv.push_back(ReplyTextureRemoved(op.compositableParent(), nullptr,
                                              op.textureID()));
       }
--- a/gfx/layers/opengl/GrallocTextureClient.cpp
+++ b/gfx/layers/opengl/GrallocTextureClient.cpp
@@ -181,17 +181,18 @@ uint8_t*
 GrallocTextureClientOGL::GetBuffer() const
 {
   MOZ_ASSERT(IsValid());
   NS_WARN_IF_FALSE(mMappedBuffer, "Trying to get a gralloc buffer without getting the lock?");
   return mMappedBuffer;
 }
 
 bool
-GrallocTextureClientOGL::AllocateForSurface(gfx::IntSize aSize)
+GrallocTextureClientOGL::AllocateForSurface(gfx::IntSize aSize,
+                                            TextureAllocationFlags)
 {
   MOZ_ASSERT(IsValid());
   MOZ_ASSERT(mCompositable);
   ISurfaceAllocator* allocator = mCompositable->GetForwarder();
 
   uint32_t format;
   uint32_t usage = android::GraphicBuffer::USAGE_SW_READ_OFTEN;
   bool swapRB = GetFlags() & TEXTURE_RB_SWAPPED;
--- a/gfx/layers/opengl/GrallocTextureClient.h
+++ b/gfx/layers/opengl/GrallocTextureClient.h
@@ -75,17 +75,18 @@ public:
    */
   void SetGrallocOpenFlags(uint32_t aFlags)
   {
     mGrallocFlags = aFlags;
   }
 
   virtual uint8_t* GetBuffer() const MOZ_OVERRIDE;
 
-  virtual bool AllocateForSurface(gfx::IntSize aSize) MOZ_OVERRIDE;
+  virtual bool AllocateForSurface(gfx::IntSize aSize,
+                                  TextureAllocationFlags aFlags = ALLOC_DEFAULT) MOZ_OVERRIDE;
 
   virtual bool AllocateForYCbCr(gfx::IntSize aYSize,
                                 gfx::IntSize aCbCrSize,
                                 StereoMode aStereoMode) MOZ_OVERRIDE;
 
   bool AllocateGralloc(gfx::IntSize aYSize, uint32_t aAndroidFormat, uint32_t aUsage);
 
   virtual bool Allocate(uint32_t aSize) MOZ_OVERRIDE;
--- a/gfx/tests/gtest/TestAsyncPanZoomController.cpp
+++ b/gfx/tests/gtest/TestAsyncPanZoomController.cpp
@@ -411,17 +411,17 @@ TEST(AsyncPanZoomController, OverScrollP
 
   nsRefPtr<MockContentController> mcc = new NiceMock<MockContentController>();
   nsRefPtr<TestAPZCTreeManager> tm = new TestAPZCTreeManager();
   nsRefPtr<TestAsyncPanZoomController> apzc = new TestAsyncPanZoomController(0, mcc, tm);
 
   apzc->SetFrameMetrics(TestFrameMetrics());
   apzc->NotifyLayersUpdated(TestFrameMetrics(), true);
 
-  EXPECT_CALL(*mcc, SendAsyncScrollDOMEvent(_,_,_)).Times(3);
+  EXPECT_CALL(*mcc, SendAsyncScrollDOMEvent(_,_,_)).Times(4);
   EXPECT_CALL(*mcc, RequestContentRepaint(_)).Times(1);
 
   // Pan sufficiently to hit overscroll behavior
   int time = 0;
   int touchStart = 500;
   int touchEnd = 10;
   ScreenPoint pointOut;
   ViewTransform viewTransformOut;
--- a/gfx/thebes/gfxPlatformFontList.cpp
+++ b/gfx/thebes/gfxPlatformFontList.cpp
@@ -684,19 +684,22 @@ gfxPlatformFontList::RemoveCmap(const gf
 void 
 gfxPlatformFontList::InitLoader()
 {
     GetFontFamilyList(mFontFamiliesToLoad);
     mStartIndex = 0;
     mNumFamilies = mFontFamiliesToLoad.Length();
 }
 
+#define FONT_LOADER_MAX_TIMESLICE 100  // max time for one pass through RunLoader = 100ms
+
 bool
 gfxPlatformFontList::RunLoader()
 {
+    TimeStamp start = TimeStamp::Now();
     uint32_t i, endIndex = (mStartIndex + mIncrement < mNumFamilies ? mStartIndex + mIncrement : mNumFamilies);
     bool loadCmaps = !UsesSystemFallback() ||
         gfxPlatform::GetPlatform()->UseCmapsDuringSystemFallback();
 
     // for each font family, load in various font info
     for (i = mStartIndex; i < endIndex; i++) {
         gfxFontFamily* familyEntry = mFontFamiliesToLoad[i];
 
@@ -715,16 +718,24 @@ gfxPlatformFontList::RunLoader()
             familyEntry->ReadAllCMAPs();
         }
 
         // read in face names
         familyEntry->ReadFaceNames(this, NeedFullnamePostscriptNames());
 
         // check whether the family can be considered "simple" for style matching
         familyEntry->CheckForSimpleFamily();
+
+        // limit the time spent reading fonts in one pass
+        TimeDuration elapsed = TimeStamp::Now() - start;
+        if (elapsed.ToMilliseconds() > FONT_LOADER_MAX_TIMESLICE &&
+                i + 1 != endIndex) {
+            endIndex = i + 1;
+            break;
+        }
     }
 
     mStartIndex = endIndex;
 
     return (mStartIndex >= mNumFamilies);
 }
 
 void 
--- a/hal/gonk/UeventPoller.cpp
+++ b/hal/gonk/UeventPoller.cpp
@@ -23,16 +23,17 @@
 #include <unistd.h>
 
 #include <arpa/inet.h>
 #include <linux/types.h>
 #include <linux/netlink.h>
 #include <netinet/in.h>
 #include <sys/socket.h>
 
+#include "nsDebug.h"
 #include "base/message_loop.h"
 #include "mozilla/FileUtils.h"
 #include "nsAutoPtr.h"
 #include "nsThreadUtils.h"
 #include "nsXULAppAPI.h"
 
 #include "UeventPoller.h"
 
@@ -110,20 +111,36 @@ NetlinkPoller::OpenSocket()
   // set non-blocking
   if (fcntl(mSocket.get(), F_SETFL, O_NONBLOCK) == -1)
     return false;
 
   struct sockaddr_nl saddr;
   bzero(&saddr, sizeof(saddr));
   saddr.nl_family = AF_NETLINK;
   saddr.nl_groups = 1;
-  saddr.nl_pid = getpid();
+  saddr.nl_pid = gettid();
+
+  do {
+    if (bind(mSocket.get(), (struct sockaddr *)&saddr, sizeof(saddr)) == 0) {
+      break;
+    }
 
-  if (bind(mSocket.get(), (struct sockaddr *)&saddr, sizeof(saddr)) == -1)
-    return false;
+    if (errno != EADDRINUSE) {
+      return false;
+    }
+
+    if (saddr.nl_pid == 0) {
+      return false;
+    }
+
+    // Once there was any other place in the same process assigning saddr.nl_pid by
+    // gettid(), we can detect it and print warning message.
+    printf_stderr("The netlink socket address saddr.nl_pid=%u is in use. Let the kernel re-assign.\n", saddr.nl_pid);
+    saddr.nl_pid = 0;
+  } while (true);
 
   if (!mIOLoop->WatchFileDescriptor(mSocket.get(),
                                     true,
                                     MessageLoopForIO::WATCH_READ,
                                     &mReadWatcher,
                                     this)) {
       return false;
   }
--- a/ipc/glue/MessageChannel.cpp
+++ b/ipc/glue/MessageChannel.cpp
@@ -467,19 +467,16 @@ MessageChannel::RPCCall(Message* aMsg, M
     SyncStackFrame frame(this, false);
 #endif
 
     Message copy = *aMsg;
     CxxStackFrame f(*this, OUT_MESSAGE, &copy);
 
     MonitorAutoLock lock(*mMonitor);
 
-    // RPC calls must be the only thing on the stack.
-    IPC_ASSERT(!AwaitingInterruptReply(), "rpc calls cannot be issued within interrupts");
-
     AutoEnterRPCTransaction transact(this);
     aMsg->set_transaction_id(mCurrentRPCTransaction);
 
     AutoEnterPendingReply replies(mPendingRPCReplies);
     if (!SendAndWait(aMsg, aReply))
         return false;
 
     NS_ABORT_IF_FALSE(aReply->is_rpc(), "expected rpc reply");
--- a/ipc/ipdl/test/cxx/PTestRPC.ipdl
+++ b/ipc/ipdl/test/cxx/PTestRPC.ipdl
@@ -1,24 +1,27 @@
 namespace mozilla {
 namespace _ipdltest {
 
-rpc protocol PTestRPC
+intr protocol PTestRPC
 {
 parent:
     rpc Test1_Start() returns (uint32_t result);
     rpc Test1_InnerEvent() returns (uint32_t result);
     async Test2_Start();
     rpc Test2_OutOfOrder();
     sync Test3_Start() returns (uint32_t result);
     rpc Test3_InnerEvent() returns (uint32_t result);
+    intr Test4_Start() returns (uint32_t result);
+    rpc Test4_Inner() returns (uint32_t result);
 
 child:
     async Start();
     urgent Test1_InnerQuery() returns (uint32_t result);
     urgent Test1_NoReenter() returns (uint32_t result);
     urgent Test2_FirstUrgent();
     urgent Test2_SecondUrgent();
     urgent Test3_WakeUp() returns (uint32_t result);
+    urgent Test4_WakeUp() returns (uint32_t result);
 };
 
 } // namespace _ipdltest
 } // namespace mozilla
--- a/ipc/ipdl/test/cxx/TestRPC.cpp
+++ b/ipc/ipdl/test/cxx/TestRPC.cpp
@@ -93,16 +93,32 @@ TestRPCParent::RecvTest3_Start(uint32_t*
 
 bool
 TestRPCParent::AnswerTest3_InnerEvent(uint32_t* aResult)
 {
   *aResult = 200;
   return true;
 }
 
+bool
+TestRPCParent::AnswerTest4_Start(uint32_t* aResult)
+{
+  if (!CallTest4_WakeUp(aResult))
+    fail("CallTest4_WakeUp");
+
+  return true;
+}
+
+bool
+TestRPCParent::AnswerTest4_Inner(uint32_t* aResult)
+{
+  *aResult = 700;
+  return true;
+}
+
 //-----------------------------------------------------------------------------
 // child
 
 
 TestRPCChild::TestRPCChild()
 {
     MOZ_COUNT_CTOR(TestRPCChild);
 }
@@ -128,16 +144,22 @@ TestRPCChild::RecvStart()
     fail("CallTest2_OutOfOrder");
 
   result = 0;
   if (!SendTest3_Start(&result))
     fail("SendTest3_Start");
   if (result != 200)
     fail("Wrong result (expected 200)");
 
+  // See bug 937216 (RPC calls within interrupts).
+  if (!CallTest4_Start(&result))
+    fail("SendTest4_Start");
+  if (result != 700)
+    fail("Wrong result (expected 700)");
+
   Close();
   return true;
 }
 
 bool
 TestRPCChild::AnswerTest1_InnerQuery(uint32_t* aResult)
 {
   uint32_t result;
@@ -173,10 +195,19 @@ bool
 TestRPCChild::AnswerTest3_WakeUp(uint32_t* aResult)
 {
   if (!CallTest3_InnerEvent(aResult))
     fail("CallTest3_InnerEvent");
 
   return true;
 }
 
+bool
+TestRPCChild::AnswerTest4_WakeUp(uint32_t* aResult)
+{
+  if (!CallTest4_Inner(aResult))
+    fail("CallTest4_Inner");
+
+  return true;
+}
+
 } // namespace _ipdltest
 } // namespace mozilla
--- a/ipc/ipdl/test/cxx/TestRPC.h
+++ b/ipc/ipdl/test/cxx/TestRPC.h
@@ -23,16 +23,18 @@ public:
     void Main();
 
     bool AnswerTest1_Start(uint32_t* aResult) MOZ_OVERRIDE;
     bool AnswerTest1_InnerEvent(uint32_t* aResult) MOZ_OVERRIDE;
     bool RecvTest2_Start() MOZ_OVERRIDE;
     bool AnswerTest2_OutOfOrder() MOZ_OVERRIDE;
     bool RecvTest3_Start(uint32_t* aResult) MOZ_OVERRIDE;
     bool AnswerTest3_InnerEvent(uint32_t* aResult) MOZ_OVERRIDE;
+    bool AnswerTest4_Start(uint32_t* aResult) MOZ_OVERRIDE;
+    bool AnswerTest4_Inner(uint32_t* aResult) MOZ_OVERRIDE;
 
     virtual void ActorDestroy(ActorDestroyReason why) MOZ_OVERRIDE
     {
         if (NormalShutdown != why)
             fail("unexpected destruction!");  
         if (!reentered_)
             fail("never processed raced RPC call!");
         if (!resolved_first_cpow_)
@@ -55,16 +57,17 @@ public:
     virtual ~TestRPCChild();
 
     bool RecvStart() MOZ_OVERRIDE;
     bool AnswerTest1_InnerQuery(uint32_t* aResult) MOZ_OVERRIDE;
     bool AnswerTest1_NoReenter(uint32_t* aResult) MOZ_OVERRIDE;
     bool AnswerTest2_FirstUrgent() MOZ_OVERRIDE;
     bool AnswerTest2_SecondUrgent() MOZ_OVERRIDE;
     bool AnswerTest3_WakeUp(uint32_t* aResult) MOZ_OVERRIDE;
+    bool AnswerTest4_WakeUp(uint32_t* aResult) MOZ_OVERRIDE;
 
     virtual void ActorDestroy(ActorDestroyReason why) MOZ_OVERRIDE
     {
         if (NormalShutdown != why)
             fail("unexpected destruction!");
         QuitChild();
     }
 };
--- a/js/src/builtin/Eval.cpp
+++ b/js/src/builtin/Eval.cpp
@@ -327,20 +327,20 @@ EvalKernel(JSContext *cx, const CallArgs
         esg.setNewScript(compiled);
     }
 
     return ExecuteKernel(cx, esg.script(), *scopeobj, thisv, ExecuteType(evalType),
                          NullFramePtr() /* evalInFrame */, args.rval().address());
 }
 
 bool
-js::DirectEvalFromIon(JSContext *cx,
-                      HandleObject scopeobj, HandleScript callerScript,
-                      HandleValue thisValue, HandleString str,
-                      jsbytecode *pc, MutableHandleValue vp)
+js::DirectEvalStringFromIon(JSContext *cx,
+                            HandleObject scopeobj, HandleScript callerScript,
+                            HandleValue thisValue, HandleString str,
+                            jsbytecode *pc, MutableHandleValue vp)
 {
     AssertInnerizedScopeChain(cx, *scopeobj);
 
     Rooted<GlobalObject*> scopeObjGlobal(cx, &scopeobj->global());
     if (!GlobalObject::isRuntimeCodeGenEnabled(cx, scopeObjGlobal)) {
         JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_CSP_BLOCKED_EVAL);
         return false;
     }
@@ -396,16 +396,32 @@ js::DirectEvalFromIon(JSContext *cx,
     // the calling frame cannot be updated to store the new object.
     JS_ASSERT(thisValue.isObject() || thisValue.isUndefined() || thisValue.isNull());
 
     return ExecuteKernel(cx, esg.script(), *scopeobj, thisValue, ExecuteType(DIRECT_EVAL),
                          NullFramePtr() /* evalInFrame */, vp.address());
 }
 
 bool
+js::DirectEvalValueFromIon(JSContext *cx,
+                           HandleObject scopeobj, HandleScript callerScript,
+                           HandleValue thisValue, HandleValue evalArg,
+                           jsbytecode *pc, MutableHandleValue vp)
+{
+    // Act as identity on non-strings per ES5 15.1.2.1 step 1.
+    if (!evalArg.isString()) {
+        vp.set(evalArg);
+        return true;
+    }
+
+    RootedString string(cx, evalArg.toString());
+    return DirectEvalStringFromIon(cx, scopeobj, callerScript, thisValue, string, pc, vp);
+}
+
+bool
 js::IndirectEval(JSContext *cx, unsigned argc, Value *vp)
 {
     CallArgs args = CallArgsFromVp(argc, vp);
     Rooted<GlobalObject*> global(cx, &args.callee().global());
     return EvalKernel(cx, args, INDIRECT_EVAL, NullFramePtr(), global, nullptr);
 }
 
 bool
--- a/js/src/builtin/Eval.h
+++ b/js/src/builtin/Eval.h
@@ -23,20 +23,25 @@ IndirectEval(JSContext *cx, unsigned arg
 // Performs a direct eval for the given arguments, which must correspond to the
 // currently-executing stack frame, which must be a script frame. On completion
 // the result is returned in args.rval.
 extern bool
 DirectEval(JSContext *cx, const CallArgs &args);
 
 // Performs a direct eval called from Ion code.
 extern bool
-DirectEvalFromIon(JSContext *cx,
-                  HandleObject scopeObj, HandleScript callerScript,
-                  HandleValue thisValue, HandleString str,
-                  jsbytecode * pc, MutableHandleValue vp);
+DirectEvalStringFromIon(JSContext *cx,
+                        HandleObject scopeObj, HandleScript callerScript,
+                        HandleValue thisValue, HandleString str,
+                        jsbytecode * pc, MutableHandleValue vp);
+extern bool
+DirectEvalValueFromIon(JSContext *cx,
+                       HandleObject scopeObj, HandleScript callerScript,
+                       HandleValue thisValue, HandleValue evalArg,
+                       jsbytecode * pc, MutableHandle