Merge m-i to m-c
authorPhil Ringnalda <philringnalda@gmail.com>
Sat, 17 May 2014 17:54:55 -0700
changeset 183680 00ef3a7d7aa74054a6d94cb8ab456231879f3e81
parent 183601 ab16ec3fd6b27faadbfdc7a5f45302ff1d432c9c (current diff)
parent 183679 ffa1a2961dd12ae0662fc2e7abd2621cbd47283d (diff)
child 183681 41a54c8add09fe29b472fb4b7787b60a45dca1c9
push id6844
push userphilringnalda@gmail.com
push dateSun, 18 May 2014 01:12:08 +0000
treeherderfx-team@41a54c8add09 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
milestone32.0a1
Merge m-i to m-c
--- a/accessible/src/generic/Accessible.h
+++ b/accessible/src/generic/Accessible.h
@@ -85,16 +85,36 @@ struct GroupPos
 {
   GroupPos() : level(0), posInSet(0), setSize(0) { }
 
   int32_t level;
   int32_t posInSet;
   int32_t setSize;
 };
 
+/**
+ * An index type. Assert if out of range value was attempted to be used.
+ */
+class index_t
+{
+public:
+  index_t(int32_t aVal) : mVal(aVal) {}
+
+  operator uint32_t() const
+  {
+    MOZ_ASSERT(mVal >= 0, "Attempt to use wrong index!");
+    return mVal;
+  }
+
+  bool IsValid() const { return mVal >= 0; }
+
+private:
+  int32_t mVal;
+};
+
 typedef nsRefPtrHashtable<nsPtrHashKey<const void>, Accessible>
   AccessibleHashtable;
 
 
 #define NS_ACCESSIBLE_IMPL_IID                          \
 {  /* 133c8bf4-4913-4355-bd50-426bd1d6e1ad */           \
   0x133c8bf4,                                           \
   0x4913,                                               \
--- a/accessible/src/generic/HyperTextAccessible-inl.h
+++ b/accessible/src/generic/HyperTextAccessible-inl.h
@@ -16,25 +16,27 @@
 #include "nsIPlaintextEditor.h"
 
 namespace mozilla {
 namespace a11y {
 
 inline bool
 HyperTextAccessible::IsValidOffset(int32_t aOffset)
 {
-  return ConvertMagicOffset(aOffset) <= CharacterCount();
+  index_t offset = ConvertMagicOffset(aOffset);
+  return offset.IsValid() && offset <= CharacterCount();
 }
 
 inline bool
 HyperTextAccessible::IsValidRange(int32_t aStartOffset, int32_t aEndOffset)
 {
-  uint32_t endOffset = ConvertMagicOffset(aEndOffset);
-  return ConvertMagicOffset(aStartOffset) <= endOffset &&
-    endOffset <= CharacterCount();
+  index_t startOffset = ConvertMagicOffset(aStartOffset);
+  index_t endOffset = ConvertMagicOffset(aEndOffset);
+  return startOffset.IsValid() && endOffset.IsValid() &&
+    startOffset <= endOffset && endOffset <= CharacterCount();
 }
 
 inline void
 HyperTextAccessible::SetCaretOffset(int32_t aOffset)
 {
   SetSelectionRange(aOffset, aOffset);
   // XXX: Force cache refresh until a good solution for AT emulation of user
   // input is implemented (AccessFu caret movement).
@@ -105,26 +107,26 @@ HyperTextAccessible::PasteText(int32_t a
 {
   nsCOMPtr<nsIEditor> editor = GetEditor();
   if (editor) {
     SetSelectionRange(aPosition, aPosition);
     editor->Paste(nsIClipboard::kGlobalClipboard);
   }
 }
 
-inline uint32_t
+inline index_t
 HyperTextAccessible::ConvertMagicOffset(int32_t aOffset) const
 {
   if (aOffset == nsIAccessibleText::TEXT_OFFSET_END_OF_TEXT)
     return CharacterCount();
 
   if (aOffset == nsIAccessibleText::TEXT_OFFSET_CARET)
     return CaretOffset();
 
-  return aOffset < 0 ? std::numeric_limits<uint32_t>::max() : aOffset;
+  return aOffset;
 }
 
 inline uint32_t
 HyperTextAccessible::AdjustCaretOffset(uint32_t aOffset) const
 {
   // It is the same character offset when the caret is visually at the very
   // end of a line or the start of a new line (soft line break). Getting text
   // at the line should provide the line with the visual caret, otherwise
--- a/accessible/src/generic/HyperTextAccessible.cpp
+++ b/accessible/src/generic/HyperTextAccessible.cpp
@@ -186,18 +186,23 @@ HyperTextAccessible::GetBoundsInFrame(ns
 }
 
 void
 HyperTextAccessible::TextSubstring(int32_t aStartOffset, int32_t aEndOffset,
                                    nsAString& aText)
 {
   aText.Truncate();
 
-  uint32_t startOffset = ConvertMagicOffset(aStartOffset);
-  uint32_t endOffset = ConvertMagicOffset(aEndOffset);
+  index_t startOffset = ConvertMagicOffset(aStartOffset);
+  index_t endOffset = ConvertMagicOffset(aEndOffset);
+  if (!startOffset.IsValid() || !endOffset.IsValid() ||
+      startOffset > endOffset || endOffset > CharacterCount()) {
+    NS_ERROR("Wrong in offset");
+    return;
+  }
 
   int32_t startChildIdx = GetChildIndexAtOffset(startOffset);
   if (startChildIdx == -1)
     return;
 
   int32_t endChildIdx = GetChildIndexAtOffset(endOffset);
   if (endChildIdx == -1)
     return;
@@ -603,19 +608,19 @@ void
 HyperTextAccessible::TextBeforeOffset(int32_t aOffset,
                                       AccessibleTextBoundary aBoundaryType,
                                       int32_t* aStartOffset, int32_t* aEndOffset,
                                       nsAString& aText)
 {
   *aStartOffset = *aEndOffset = 0;
   aText.Truncate();
 
-  uint32_t convertedOffset = ConvertMagicOffset(aOffset);
-  if (convertedOffset == std::numeric_limits<uint32_t>::max()) {
-    NS_ERROR("Wrong given offset!");
+  index_t convertedOffset = ConvertMagicOffset(aOffset);
+  if (!convertedOffset.IsValid() || convertedOffset > CharacterCount()) {
+    NS_ERROR("Wrong in offset!");
     return;
   }
 
   uint32_t adjustedOffset = convertedOffset;
   if (aOffset == nsIAccessibleText::TEXT_OFFSET_CARET)
     adjustedOffset = AdjustCaretOffset(adjustedOffset);
 
   switch (aBoundaryType) {
@@ -739,19 +744,19 @@ void
 HyperTextAccessible::TextAfterOffset(int32_t aOffset,
                                      AccessibleTextBoundary aBoundaryType,
                                      int32_t* aStartOffset, int32_t* aEndOffset,
                                      nsAString& aText)
 {
   *aStartOffset = *aEndOffset = 0;
   aText.Truncate();
 
-  uint32_t convertedOffset = ConvertMagicOffset(aOffset);
-  if (convertedOffset == std::numeric_limits<uint32_t>::max()) {
-    NS_ERROR("Wrong given offset!");
+  index_t convertedOffset = ConvertMagicOffset(aOffset);
+  if (!convertedOffset.IsValid() || convertedOffset > CharacterCount()) {
+    NS_ERROR("Wrong in offset!");
     return;
   }
 
   uint32_t adjustedOffset = convertedOffset;
   if (aOffset == nsIAccessibleText::TEXT_OFFSET_CARET)
     adjustedOffset = AdjustCaretOffset(adjustedOffset);
 
   switch (aBoundaryType) {
@@ -809,20 +814,25 @@ HyperTextAccessible::TextAttributes(bool
                                        int32_t* aEndOffset)
 {
   // 1. Get each attribute and its ranges one after another.
   // 2. As we get each new attribute, we pass the current start and end offsets
   //    as in/out parameters. In other words, as attributes are collected,
   //    the attribute range itself can only stay the same or get smaller.
 
   *aStartOffset = *aEndOffset = 0;
+  index_t offset = ConvertMagicOffset(aOffset);
+  if (!offset.IsValid() || offset > CharacterCount()) {
+    NS_ERROR("Wrong in offset!");
+    return nullptr;
+  }
+
   nsCOMPtr<nsIPersistentProperties> attributes =
     do_CreateInstance(NS_PERSISTENTPROPERTIES_CONTRACTID);
 
-  uint32_t offset = ConvertMagicOffset(aOffset);
   Accessible* accAtOffset = GetChildAtOffset(offset);
   if (!accAtOffset) {
     // Offset 0 is correct offset when accessible has empty text. Include
     // default attributes if they were requested, otherwise return empty set.
     if (offset == 0) {
       if (aIncludeDefAttrs) {
         TextAttrsMgr textAttrsMgr(this);
         textAttrsMgr.GetAttributes(attributes);
@@ -1024,21 +1034,24 @@ HyperTextAccessible::OffsetAtPoint(int32
 
   return -1; // Not found
 }
 
 nsIntRect
 HyperTextAccessible::TextBounds(int32_t aStartOffset, int32_t aEndOffset,
                                 uint32_t aCoordType)
 {
-  uint32_t startOffset = ConvertMagicOffset(aStartOffset);
-  uint32_t endOffset = ConvertMagicOffset(aEndOffset);
-  NS_ASSERTION(startOffset < endOffset &&
-               endOffset != std::numeric_limits<uint32_t>::max(),
-               "Wrong bad in!");
+  index_t startOffset = ConvertMagicOffset(aStartOffset);
+  index_t endOffset = ConvertMagicOffset(aEndOffset);
+  if (!startOffset.IsValid() || !endOffset.IsValid() ||
+      startOffset > endOffset || endOffset > CharacterCount()) {
+    NS_ERROR("Wrong in offset");
+    return nsIntRect();
+  }
+
 
   int32_t childIdx = GetChildIndexAtOffset(startOffset);
   if (childIdx == -1)
     return nsIntRect();
 
   nsIntRect bounds;
   int32_t prevOffset = GetChildOffset(childIdx);
   int32_t offset1 = startOffset - prevOffset;
@@ -1412,18 +1425,23 @@ HyperTextAccessible::SelectionBoundsAt(i
   return true;
 }
 
 bool
 HyperTextAccessible::SetSelectionBoundsAt(int32_t aSelectionNum,
                                           int32_t aStartOffset,
                                           int32_t aEndOffset)
 {
-  uint32_t startOffset = ConvertMagicOffset(aStartOffset);
-  uint32_t endOffset = ConvertMagicOffset(aEndOffset);
+  index_t startOffset = ConvertMagicOffset(aStartOffset);
+  index_t endOffset = ConvertMagicOffset(aEndOffset);
+  if (!startOffset.IsValid() || !endOffset.IsValid() ||
+      startOffset > endOffset || endOffset > CharacterCount()) {
+    NS_ERROR("Wrong in offset");
+    return false;
+  }
 
   dom::Selection* domSel = DOMSelection();
   if (!domSel)
     return false;
 
   nsRefPtr<nsRange> range;
   uint32_t rangeCount = domSel->GetRangeCount();
   if (aSelectionNum == static_cast<int32_t>(rangeCount))
@@ -1874,17 +1892,17 @@ HyperTextAccessible::GetSpellTextAttr(ns
   dom::Selection* domSel = fs->GetSelection(nsISelectionController::SELECTION_SPELLCHECK);
   if (!domSel)
     return;
 
   int32_t rangeCount = domSel->GetRangeCount();
   if (rangeCount <= 0)
     return;
 
-  int32_t startOffset = 0, endOffset = 0;
+  uint32_t startOffset = 0, endOffset = 0;
   for (int32_t idx = 0; idx < rangeCount; idx++) {
     nsRange* range = domSel->GetRangeAt(idx);
     if (range->Collapsed())
       continue;
 
     // See if the point comes after the range in which case we must continue in
     // case there is another range after this one.
     nsINode* endNode = range->GetEndParent();
--- a/accessible/src/generic/HyperTextAccessible.h
+++ b/accessible/src/generic/HyperTextAccessible.h
@@ -291,17 +291,21 @@ public:
   nsIntRect TextBounds(int32_t aStartOffset, int32_t aEndOffset,
                        uint32_t aCoordType = nsIAccessibleCoordinateType::COORDTYPE_SCREEN_RELATIVE);
 
   /**
    * Return a rect for character at given offset relative given coordinate
    * system.
    */
   nsIntRect CharBounds(int32_t aOffset, uint32_t aCoordType)
-    { return TextBounds(aOffset, aOffset + 1, aCoordType); }
+  {
+    int32_t endOffset = aOffset == static_cast<int32_t>(CharacterCount()) ?
+      aOffset : aOffset + 1;
+    return TextBounds(aOffset, endOffset, aCoordType);
+  }
 
   /**
    * Get/set caret offset, if no caret then -1.
    */
   int32_t CaretOffset() const;
   void SetCaretOffset(int32_t aOffset);
 
   /**
@@ -416,17 +420,17 @@ protected:
   virtual ENameValueFlag NativeName(nsString& aName) MOZ_OVERRIDE;
   virtual void CacheChildren() MOZ_OVERRIDE;
 
   // HyperTextAccessible
 
   /**
    * Transform magic offset into text offset.
    */
-  uint32_t ConvertMagicOffset(int32_t aOffset) const;
+  index_t ConvertMagicOffset(int32_t aOffset) const;
 
   /**
    * Adjust an offset the caret stays at to get a text by line boundary.
    */
   uint32_t AdjustCaretOffset(uint32_t aOffset) const;
 
   /**
    * Return true if caret is at end of line.
--- a/caps/idl/nsIScriptSecurityManager.idl
+++ b/caps/idl/nsIScriptSecurityManager.idl
@@ -5,18 +5,19 @@
 
 #include "nsISupports.idl"
 #include "nsIPrincipal.idl"
 #include "nsIXPCSecurityManager.idl"
 interface nsIURI;
 interface nsIChannel;
 interface nsIDocShell;
 interface nsIDomainPolicy;
+interface nsILoadContext;
 
-[scriptable, uuid(2565769a-eaec-47a1-a076-605f5294d286)]
+[scriptable, uuid(9875f4b2-f9cd-41d1-a461-fe14956823ac)]
 interface nsIScriptSecurityManager : nsIXPCSecurityManager
 {
     /**
      * Check that the script currently running in context "cx" can load "uri".
      *
      * Will return error code NS_ERROR_DOM_BAD_URI if the load request
      * should be denied.
      *
@@ -118,16 +119,24 @@ interface nsIScriptSecurityManager : nsI
      * @param inMozBrowser is true if the principal has to be considered as
      * inside a mozbrowser frame.
      */
     nsIPrincipal getAppCodebasePrincipal(in nsIURI uri,
                                          in unsigned long appId,
                                          in boolean inMozBrowser);
 
     /**
+     * Returns a principal that has the appId and inMozBrowser of the load
+     * context.
+     * @param loadContext to get appId/inMozBrowser from.
+     */
+    nsIPrincipal getLoadContextCodebasePrincipal(in nsIURI uri,
+                                                 in nsILoadContext loadContext);
+
+    /**
      * Returns a principal that has the appId and inMozBrowser of the docshell
      * inside a mozbrowser frame.
      * @param docShell to get appId/inMozBrowser from.
      */
     nsIPrincipal getDocShellCodebasePrincipal(in nsIURI uri,
                                               in nsIDocShell docShell);
 
     /**
--- a/caps/src/nsScriptSecurityManager.cpp
+++ b/caps/src/nsScriptSecurityManager.cpp
@@ -6,16 +6,17 @@
 
 #include "nsScriptSecurityManager.h"
 
 #include "mozilla/ArrayUtils.h"
 
 #include "js/OldDebugAPI.h"
 #include "xpcprivate.h"
 #include "XPCWrapper.h"
+#include "nsILoadContext.h"
 #include "nsIServiceManager.h"
 #include "nsIScriptObjectPrincipal.h"
 #include "nsIScriptContext.h"
 #include "nsIURL.h"
 #include "nsINestedURI.h"
 #include "nspr.h"
 #include "nsJSPrincipals.h"
 #include "nsSystemPrincipal.h"
@@ -290,21 +291,22 @@ nsScriptSecurityManager::GetChannelPrinc
     }
 
     // OK, get the principal from the URI.  Make sure this does the same thing
     // as nsDocument::Reset and XULDocument::StartDocumentLoad.
     nsCOMPtr<nsIURI> uri;
     nsresult rv = NS_GetFinalChannelURI(aChannel, getter_AddRefs(uri));
     NS_ENSURE_SUCCESS(rv, rv);
 
-    nsCOMPtr<nsIDocShell> docShell;
-    NS_QueryNotificationCallbacks(aChannel, docShell);
 
-    if (docShell) {
-        return GetDocShellCodebasePrincipal(uri, docShell, aPrincipal);
+    nsCOMPtr<nsILoadContext> loadContext;
+    NS_QueryNotificationCallbacks(aChannel, loadContext);
+
+    if (loadContext) {
+        return GetLoadContextCodebasePrincipal(uri, loadContext, aPrincipal);
     }
 
     return GetCodebasePrincipalInternal(uri, UNKNOWN_APP_ID,
         /* isInBrowserElement */ false, aPrincipal);
 }
 
 NS_IMETHODIMP
 nsScriptSecurityManager::IsSystemPrincipal(nsIPrincipal* aPrincipal,
@@ -965,16 +967,32 @@ nsScriptSecurityManager::GetAppCodebaseP
 {
   NS_ENSURE_TRUE(aAppId != nsIScriptSecurityManager::UNKNOWN_APP_ID,
                  NS_ERROR_INVALID_ARG);
 
   return GetCodebasePrincipalInternal(aURI, aAppId, aInMozBrowser, aPrincipal);
 }
 
 NS_IMETHODIMP
+nsScriptSecurityManager::
+  GetLoadContextCodebasePrincipal(nsIURI* aURI,
+                                  nsILoadContext* aLoadContext,
+                                  nsIPrincipal** aPrincipal)
+{
+  uint32_t appId;
+  aLoadContext->GetAppId(&appId);
+  bool isInBrowserElement;
+  aLoadContext->GetIsInBrowserElement(&isInBrowserElement);
+  return GetCodebasePrincipalInternal(aURI,
+                                      appId,
+                                      isInBrowserElement,
+                                      aPrincipal);
+}
+
+NS_IMETHODIMP
 nsScriptSecurityManager::GetDocShellCodebasePrincipal(nsIURI* aURI,
                                                       nsIDocShell* aDocShell,
                                                       nsIPrincipal** aPrincipal)
 {
   return GetCodebasePrincipalInternal(aURI,
                                       aDocShell->GetAppId(),
                                       aDocShell->GetIsInBrowserElement(),
                                       aPrincipal);
--- a/content/base/src/nsDocument.cpp
+++ b/content/base/src/nsDocument.cpp
@@ -23,16 +23,17 @@
 #endif
 #include "prlog.h"
 #include "plstr.h"
 #include "prprf.h"
 
 #include "mozilla/Telemetry.h"
 #include "nsIInterfaceRequestor.h"
 #include "nsIInterfaceRequestorUtils.h"
+#include "nsILoadContext.h"
 #include "nsUnicharUtils.h"
 #include "nsContentList.h"
 #include "nsIObserver.h"
 #include "nsIBaseWindow.h"
 #include "mozilla/css/Loader.h"
 #include "mozilla/css/ImageLoader.h"
 #include "nsDocShell.h"
 #include "nsIDocShellTreeItem.h"
@@ -2332,31 +2333,31 @@ nsDocument::ResetToURI(nsIURI *aURI, nsI
 
   // Now get our new principal
   if (aPrincipal) {
     SetPrincipal(aPrincipal);
   } else {
     nsIScriptSecurityManager *securityManager =
       nsContentUtils::GetSecurityManager();
     if (securityManager) {
-      nsCOMPtr<nsIDocShell> docShell(mDocumentContainer);
-
-      if (!docShell && aLoadGroup) {
+      nsCOMPtr<nsILoadContext> loadContext(mDocumentContainer);
+
+      if (!loadContext && aLoadGroup) {
         nsCOMPtr<nsIInterfaceRequestor> cbs;
         aLoadGroup->GetNotificationCallbacks(getter_AddRefs(cbs));
-        docShell = do_GetInterface(cbs);
+        loadContext = do_GetInterface(cbs);
       }
 
-      MOZ_ASSERT(docShell,
-                 "must be in a docshell or pass in an explicit principal");
+      MOZ_ASSERT(loadContext,
+                 "must have a load context or pass in an explicit principal");
 
       nsCOMPtr<nsIPrincipal> principal;
       nsresult rv = securityManager->
-        GetDocShellCodebasePrincipal(mDocumentURI, docShell,
-                                     getter_AddRefs(principal));
+        GetLoadContextCodebasePrincipal(mDocumentURI, loadContext,
+                                        getter_AddRefs(principal));
       if (NS_SUCCEEDED(rv)) {
         SetPrincipal(principal);
       }
     }
   }
 
   // Refresh the principal on the compartment.
   nsPIDOMWindow* win = GetInnerWindow();
--- a/content/base/src/nsScriptLoader.cpp
+++ b/content/base/src/nsScriptLoader.cpp
@@ -49,16 +49,17 @@
 #include "nsCrossSiteListenerProxy.h"
 #include "nsSandboxFlags.h"
 #include "nsContentTypeParser.h"
 #include "nsINetworkSeer.h"
 #include "mozilla/dom/EncodingUtils.h"
 
 #include "mozilla/CORSMode.h"
 #include "mozilla/Attributes.h"
+#include "mozilla/Telemetry.h"
 #include "mozilla/unused.h"
 
 #ifdef PR_LOGGING
 static PRLogModuleInfo* gCspPRLog;
 #endif
 
 using namespace mozilla;
 using namespace mozilla::dom;
@@ -81,19 +82,17 @@ public:
       mJSVersion(aVersion),
       mLineNo(1),
       mCORSMode(aCORSMode)
   {
   }
 
   ~nsScriptLoadRequest()
   {
-    if (mScriptTextBuf) {
-      js_free(mScriptTextBuf);
-    }
+    js_free(mScriptTextBuf);
   }
 
   NS_DECL_THREADSAFE_ISUPPORTS
 
   void FireScriptAvailable(nsresult aResult)
   {
     mElement->ScriptAvailable(aResult, mElement, mIsInline, mURI, mLineNo);
   }
@@ -428,17 +427,17 @@ ParseTypeAttribute(const nsAString& aTyp
     *aVersion = nsContentUtils::ParseJavascriptVersion(versionName);
   } else if (rv != NS_ERROR_INVALID_ARG) {
     return false;
   }
 
   return true;
 }
 
-bool
+static bool
 CSPAllowsInlineScript(nsIScriptElement *aElement, nsIDocument *aDocument)
 {
   nsCOMPtr<nsIContentSecurityPolicy> csp;
   nsresult rv = aDocument->NodePrincipal()->GetCsp(getter_AddRefs(csp));
   NS_ENSURE_SUCCESS(rv, false);
 
   if (!csp) {
     // no CSP --> allow
@@ -535,16 +534,53 @@ CSPAllowsInlineScript(nsIScriptElement *
   if (!allowInlineScript) {
     NS_ASSERTION(!violations.IsEmpty(),
         "CSP blocked inline script but is not reporting a violation");
    return false;
   }
   return true;
 }
 
+static void
+AccumulateJavaScriptVersionTelemetry(nsIScriptElement* aElement,
+                                     JSVersion aVersion)
+{
+  uint32_t minorVersion;
+  switch (aVersion) {
+    case JSVERSION_DEFAULT: minorVersion = 5; break;
+    case JSVERSION_1_6:     minorVersion = 6; break;
+    case JSVERSION_1_7:     minorVersion = 7; break;
+    case JSVERSION_1_8:     minorVersion = 8; break;
+    default:                MOZ_ASSERT_UNREACHABLE("Unexpected JSVersion");
+    case JSVERSION_UNKNOWN: minorVersion = 0; break;
+  }
+
+  // Only report SpiderMonkey's nonstandard JS versions: 1.6, 1.7, and 1.8.
+  if (minorVersion < 6) {
+    return;
+  }
+
+  nsCOMPtr<nsIURI> scriptURI = aElement->GetScriptURI();
+  if (!scriptURI) {
+    return;
+  }
+
+  // We only care about web content, not chrome or add-on JS versions.
+  bool chrome = false;
+  scriptURI->SchemeIs("chrome", &chrome);
+  if (!chrome) {
+    scriptURI->SchemeIs("resource", &chrome);
+  }
+  if (chrome) {
+    return;
+  }
+
+  Telemetry::Accumulate(Telemetry::JS_MINOR_VERSION, minorVersion);
+}
+
 bool
 nsScriptLoader::ProcessScriptElement(nsIScriptElement *aElement)
 {
   // We need a document to evaluate scripts.
   NS_ENSURE_TRUE(mDocument, false);
 
   // Check to see if scripts has been turned off.
   if (!mEnabled || !mDocument->IsScriptEnabled()) {
@@ -563,16 +599,17 @@ nsScriptLoader::ProcessScriptElement(nsI
   JSVersion version = JSVERSION_DEFAULT;
 
   // Check the type attribute to determine language and version.
   // If type exists, it trumps the deprecated 'language='
   nsAutoString type;
   aElement->GetScriptType(type);
   if (!type.IsEmpty()) {
     NS_ENSURE_TRUE(ParseTypeAttribute(type, &version), false);
+    AccumulateJavaScriptVersionTelemetry(aElement, version);
   } else {
     // no 'type=' element
     // "language" is a deprecated attribute of HTML, so we check it only for
     // HTML script elements.
     if (scriptContent->IsHTML()) {
       nsAutoString language;
       scriptContent->GetAttr(kNameSpaceID_None, nsGkAtoms::language, language);
       if (!language.IsEmpty()) {
--- a/content/base/test/chrome/cpows_child.js
+++ b/content/base/test/chrome/cpows_child.js
@@ -1,21 +1,33 @@
 dump('loaded child cpow test\n');
 
 content.document.title = "Hello, Kitty";
 
+var done_count = 0;
+var is_remote;
+
 (function start() {
+    [is_remote] = sendSyncMessage("cpows:is_remote");
+    parent_test();
+    dom_test();
+    xray_test();
     sync_test();
     async_test();
     rpc_test();
     nested_sync_test();
     // The sync-ness of this call is important, because otherwise
     // we tear down the child's document while we are
     // still in the async test in the parent.
-    sendSyncMessage("cpows:done", {});
+    // This test races with itself to be the final test.
+    lifetime_test(function() {
+      done_count++;
+      if (done_count == 2)
+        sendSyncMessage("cpows:done", {});
+    });
   }
 )();
 
 function ok(condition, message) {
   dump('condition: ' + condition  + ', ' + message + '\n');
   if (!condition) {
     sendAsyncMessage("cpows:fail", { message: message });
     throw 'failed check: ' + message;
@@ -51,16 +63,54 @@ function make_object()
          };
 }
 
 function make_json()
 {
   return { check: "ok" };
 }
 
+function parent_test()
+{
+  function f(check_func) {
+    let result = check_func(10);
+    ok(result == 20, "calling function in parent worked");
+    return result;
+  }
+
+  addMessageListener("cpows:from_parent", (msg) => {
+    let obj = msg.objects.obj;
+    ok(obj.a == 1, "correct value from parent");
+    done_count++;
+    if (done_count == 2)
+      sendSyncMessage("cpows:done", {});
+  });
+  sendSyncMessage("cpows:parent_test", {}, {func: f});
+}
+
+function dom_test()
+{
+  let element = content.document.createElement("div");
+  element.id = "it_works";
+  content.document.body.appendChild(element);
+
+  sendAsyncMessage("cpows:dom_test", {}, {element: element});
+  Components.utils.schedulePreciseGC(function() {
+    sendSyncMessage("cpows:dom_test_after_gc");
+  });
+}
+
+function xray_test()
+{
+  let element = content.document.createElement("div");
+  element.wrappedJSObject.foo = "hello";
+
+  sendSyncMessage("cpows:xray_test", {}, {element: element});
+}
+
 function sync_test()
 {
   dump('beginning cpow sync test\n');
   sync_obj = make_object();
   sendSyncMessage("cpows:sync",
     make_json(),
     make_object());
 }
@@ -101,8 +151,33 @@ function nested_sync_test()
     if (!ok(caught, "should not allow nested sync"))
       return "fail";
     return "ok";
   }
   sendSyncMessage("cpows:nested_sync",
     make_json(),
     rpc_obj);
 }
+
+function lifetime_test(finish)
+{
+  if (!is_remote) {
+    // Only run this test when running out-of-process. Otherwise it
+    // will fail, since local CPOWs don't follow the same ownership
+    // rules.
+    finish();
+    return;
+  }
+
+  dump("beginning lifetime test\n");
+  var obj = {"will_die": {"f": 1}};
+  let [result] = sendSyncMessage("cpows:lifetime_test_1", {}, {obj: obj});
+  ok(result == 10, "got sync result");
+  ok(obj.wont_die.f == 2, "got reverse CPOW");
+  obj.will_die = null;
+  Components.utils.schedulePreciseGC(function() {
+    addMessageListener("cpows:lifetime_test_3", (msg) => {
+      ok(obj.wont_die.f == 2, "reverse CPOW still works");
+      finish();
+    });
+    sendSyncMessage("cpows:lifetime_test_2");
+  });
+}
--- a/content/base/test/chrome/cpows_parent.xul
+++ b/content/base/test/chrome/cpows_parent.xul
@@ -8,21 +8,30 @@
 
   <!-- test results are displayed in the html:body -->
   <label value="CPOWs"/>
 
   <script type="application/javascript"><![CDATA[
     var test_state = "remote";
     var test_node = null;
     var reentered = false;
+    var savedMM = null;
+
+    function info(message) {
+      return opener.wrappedJSObject.info(message);
+    }
 
     function ok(condition, message) {
       return opener.wrappedJSObject.ok(condition, message);
     }
 
+    function is(v1, v2, message) {
+      return opener.wrappedJSObject.is(v1, v2, message);
+    }
+
     // Make sure that an error in this file actually causes the test to fail.
     window.onerror = function (msg, url, line) {
       ok(false, "Error while executing: \n" + msg + "\n" + url + ":" + line);
     };
 
     function testCpowMessage(message) {
       ok(message.json.check == "ok", "correct json");
 
@@ -140,32 +149,97 @@
         test_node.parentNode.removeChild(test_node);
         run_tests("inprocess");
         return;
       }
 
       finish();
     }
 
+    function recvParentTest(message) {
+      let func = message.objects.func;
+      let result = func(n => 2*n);
+      ok(result == 20, "result == 20");
+      let obj = {a:1};
+      savedMM.sendAsyncMessage("cpows:from_parent", {}, {obj: obj});
+    }
+
+    let savedElement = null;
+    function recvDomTest(message) {
+      savedElement = message.objects.element;
+    }
+
+    function recvDomTestAfterGC(message) {
+      let id;
+      try {
+        id = savedElement.id;
+      } catch (e) {
+        ok(false, "Got exception using DOM element");
+      }
+      is(id, "it_works", "DOM element has expected ID");
+    }
+
+    function recvXrayTest(message) {
+      let element = message.objects.element;
+      is(element.foo, undefined, "DOM element does not expose content properties");
+    }
+
+    let savedWilldieObj;
+    let wontDie = {f:2};
+    function recvLifetimeTest1(message) {
+      let obj = message.objects.obj;
+      savedWilldieObj = obj.will_die;
+      ok(savedWilldieObj.f == 1, "limited-lifetime CPOW works at first");
+      obj.wont_die = wontDie;
+      obj = null;
+      return 10;
+    }
+    function recvLifetimeTest2(message) {
+      let threw = false;
+      try {
+        savedWilldieObj.f;
+      } catch (e) {
+        threw = true;
+      }
+      ok(threw, "limited-lifetime CPOW stopped working");
+      wontDie = null;
+      Components.utils.schedulePreciseGC(function() {
+        savedMM.sendAsyncMessage("cpows:lifetime_test_3");
+      });
+    }
+
     function run_tests(type) {
+      info("Running tests: " + type);
       var node = document.getElementById('cpowbrowser_' + type);
 
       test_state = type;
       test_node = node;
 
+      function recvIsRemote(message) {
+        return type == "remote";
+      }
+
       var mm = node.messageManager;
+      savedMM = mm;
+      mm.addMessageListener("cpows:is_remote", recvIsRemote);
       mm.addMessageListener("cpows:async", recvAsyncMessage);
       mm.addMessageListener("cpows:sync", recvSyncMessage);
       mm.addMessageListener("cpows:rpc", recvRpcMessage);
       mm.addMessageListener("cpows:reenter", recvReenterMessage);
       mm.addMessageListener("cpows:reenter", recvReenterMessage);
       mm.addMessageListener("cpows:nested_sync", recvNestedSyncMessage);
       mm.addMessageListener("cpows:reenter_sync", recvReenterSyncMessage);
       mm.addMessageListener("cpows:done", recvDoneMessage);
       mm.addMessageListener("cpows:fail", recvFailMessage);
+      mm.addMessageListener("cpows:parent_test", recvParentTest);
+      mm.addMessageListener("cpows:dom_test", recvDomTest);
+      mm.addMessageListener("cpows:dom_test_after_gc", recvDomTestAfterGC);
+      mm.addMessageListener("cpows:xray_test", recvXrayTest);
+      mm.addMessageListener("cpows:lifetime_test_1", recvLifetimeTest1);
+      mm.addMessageListener("cpows:lifetime_test_2", recvLifetimeTest2);
       mm.loadFrameScript("chrome://mochitests/content/chrome/content/base/test/chrome/cpows_child.js", true);
     }
 
     function start() {
       run_tests('remote');
     }
 
     function finish() {
--- a/content/html/content/src/HTMLInputElement.cpp
+++ b/content/html/content/src/HTMLInputElement.cpp
@@ -5191,16 +5191,18 @@ HTMLInputElement::SetRangeText(const nsA
 
       if ((uint32_t)aSelectionEnd > aEnd) {
         aSelectionEnd += delta;
       } else if ((uint32_t)aSelectionEnd > aStart) {
         aSelectionEnd = newEnd;
       }
     }
     break;
+    default:
+      MOZ_CRASH("Unknown mode!");
   }
 
   Optional<nsAString> direction;
   SetSelectionRange(aSelectionStart, aSelectionEnd, direction, aRv);
 }
 
 int32_t
 HTMLInputElement::GetSelectionStart(ErrorResult& aRv)
--- a/content/html/content/src/HTMLTextAreaElement.cpp
+++ b/content/html/content/src/HTMLTextAreaElement.cpp
@@ -980,16 +980,18 @@ HTMLTextAreaElement::SetRangeText(const 
 
       if ((uint32_t)aSelectionEnd > aEnd) {
         aSelectionEnd += delta;
       } else if ((uint32_t)aSelectionEnd > aStart) {
         aSelectionEnd = newEnd;
       }
     }
     break;
+    default:
+      MOZ_CRASH("Unknown mode!");
   }
 
   Optional<nsAString> direction;
   SetSelectionRange(aSelectionStart, aSelectionEnd, direction, aRv);
 }
 
 nsresult
 HTMLTextAreaElement::Reset()
--- a/dom/base/nsGlobalWindow.cpp
+++ b/dom/base/nsGlobalWindow.cpp
@@ -8425,17 +8425,17 @@ nsGlobalWindow::ReallyCloseWindow()
 
     CleanUp();
   }
 }
 
 void
 nsGlobalWindow::EnterModalState()
 {
-  FORWARD_TO_OUTER_VOID(EnterModalState, ());
+  MOZ_ASSERT(IsOuterWindow(), "Modal state is maintained on outer windows");
 
   // GetScriptableTop, not GetTop, so that EnterModalState works properly with
   // <iframe mozbrowser>.
   nsGlobalWindow* topWin = GetScriptableTop();
 
   if (!topWin) {
     NS_ERROR("Uh, EnterModalState() called w/o a reachable top window?");
     return;
@@ -8558,17 +8558,17 @@ public:
 
 private:
   nsRefPtr<nsGlobalWindow> mWindow;
 };
 
 void
 nsGlobalWindow::LeaveModalState()
 {
-  FORWARD_TO_OUTER_VOID(LeaveModalState, ());
+  MOZ_ASSERT(IsOuterWindow(), "Modal state is maintained on outer windows");
 
   nsGlobalWindow* topWin = GetScriptableTop();
 
   if (!topWin) {
     NS_ERROR("Uh, LeaveModalState() called w/o a reachable top window?");
     return;
   }
 
--- a/dom/base/nsJSEnvironment.cpp
+++ b/dom/base/nsJSEnvironment.cpp
@@ -2039,51 +2039,55 @@ nsJSContext::BeginCycleCollectionCallbac
   if (sICCTimer) {
     sICCTimer->InitWithFuncCallback(ICCTimerFired,
                                     nullptr,
                                     kICCIntersliceDelay,
                                     nsITimer::TYPE_REPEATING_SLACK);
   }
 }
 
+static_assert(NS_GC_DELAY > kMaxICCDuration, "A max duration ICC shouldn't reduce GC delay to 0");
+
 //static
 void
 nsJSContext::EndCycleCollectionCallback(CycleCollectorResults &aResults)
 {
   MOZ_ASSERT(NS_IsMainThread());
 
   nsJSContext::KillICCTimer();
 
   // Update timing information for the current slice before we log it, if
   // we previously called PrepareForCycleCollectionSlice(). During shutdown
   // CCs, this won't happen.
   gCCStats.FinishCycleCollectionSlice();
 
   sCCollectedWaitingForGC += aResults.mFreedRefCounted + aResults.mFreedGCed;
 
+  TimeStamp endCCTimeStamp = TimeStamp::Now();
+  uint32_t ccNowDuration = TimeBetween(gCCStats.mBeginTime, endCCTimeStamp);
+
   if (NeedsGCAfterCC()) {
-    PokeGC(JS::gcreason::CC_WAITING);
+    PokeGC(JS::gcreason::CC_WAITING,
+           NS_GC_DELAY - std::min(ccNowDuration, kMaxICCDuration));
   }
 
-  TimeStamp endCCTimeStamp = TimeStamp::Now();
-
   PRTime endCCTime;
   if (sPostGCEventsToObserver) {
     endCCTime = PR_Now();
   }
 
   // Log information about the CC via telemetry, JSON and the console.
-  uint32_t ccNowDuration = TimeBetween(gCCStats.mBeginTime, endCCTimeStamp);
   Telemetry::Accumulate(Telemetry::CYCLE_COLLECTOR_FINISH_IGC, gCCStats.mAnyLockedOut);
   Telemetry::Accumulate(Telemetry::CYCLE_COLLECTOR_SYNC_SKIPPABLE, gCCStats.mRanSyncForgetSkippable);
   Telemetry::Accumulate(Telemetry::CYCLE_COLLECTOR_FULL, ccNowDuration);
   Telemetry::Accumulate(Telemetry::CYCLE_COLLECTOR_MAX_PAUSE, gCCStats.mMaxSliceTime);
 
   if (!sLastCCEndTime.IsNull()) {
-    uint32_t timeBetween = TimeBetween(sLastCCEndTime, gCCStats.mBeginTime);
+    // TimeBetween returns milliseconds, but we want to report seconds.
+    uint32_t timeBetween = TimeBetween(sLastCCEndTime, gCCStats.mBeginTime) / 1000;
     Telemetry::Accumulate(Telemetry::CYCLE_COLLECTOR_TIME_BETWEEN, timeBetween);
   }
   sLastCCEndTime = endCCTimeStamp;
 
   Telemetry::Accumulate(Telemetry::FORGET_SKIPPABLE_MAX,
                         sMaxForgetSkippableTime / PR_USEC_PER_MSEC);
 
   PRTime delta = GetCollectionTimeDelta();
@@ -2224,37 +2228,26 @@ static bool
 ShouldTriggerCC(uint32_t aSuspected)
 {
   return sNeedsFullCC ||
          aSuspected > NS_CC_PURPLE_LIMIT ||
          (aSuspected > NS_CC_FORCED_PURPLE_LIMIT &&
           TimeUntilNow(sLastCCEndTime) > NS_CC_FORCED);
 }
 
-static uint32_t
-TimeToNextCC()
-{
-  if (sIncrementalCC) {
-    return NS_CC_DELAY - kMaxICCDuration;
-  }
-  return NS_CC_DELAY;
-}
-
-static_assert(NS_CC_DELAY > kMaxICCDuration, "ICC shouldn't reduce CC delay to 0");
-
 static void
 CCTimerFired(nsITimer *aTimer, void *aClosure)
 {
   if (sDidShutdown) {
     return;
   }
 
   static uint32_t ccDelay = NS_CC_DELAY;
   if (sCCLockedOut) {
-    ccDelay = TimeToNextCC() / 3;
+    ccDelay = NS_CC_DELAY / 3;
 
     PRTime now = PR_Now();
     if (sCCLockedOutTime == 0) {
       // Reset sCCTimerFireCount so that we run forgetSkippable
       // often enough before CC. Because of reduced ccDelay
       // forgetSkippable will be called just a few times.
       // NS_MAX_CC_LOCKEDOUT_TIME limit guarantees that we end up calling
       // forgetSkippable and CycleCollectNow eventually.
@@ -2291,17 +2284,17 @@ CCTimerFired(nsITimer *aTimer, void *aCl
       nsJSContext::RunCycleCollectorSlice();
     }
   } else if ((sPreviousSuspectedCount + 100) <= suspected) {
       // Only do a forget skippable if there are more than a few new objects.
       FireForgetSkippable(suspected, false);
   }
 
   if (isLateTimerFire) {
-    ccDelay = TimeToNextCC();
+    ccDelay = NS_CC_DELAY;
 
     // We have either just run the CC or decided we don't want to run the CC
     // next time, so kill the timer.
     sPreviousSuspectedCount = 0;
     nsJSContext::KillCCTimer();
   }
 }
 
--- a/dom/base/test/mochitest.ini
+++ b/dom/base/test/mochitest.ini
@@ -45,16 +45,20 @@ support-files =
 [test_messagemanager_targetchain.html]
 [test_messageChannel_transferable.html]
 [test_messageChannel_unshipped.html]
 [test_named_frames.html]
 [test_navigator_resolve_identity.html]
 [test_navigator_language.html]
 [test_nondomexception.html]
 [test_openDialogChromeOnly.html]
+
+[test_open_null_features.html]
+skip-if = (buildapp == 'b2g' && toolkit != 'gonk') # Fails on b2g-desktop, tracked in bug 1011874
+
 [test_postMessage_solidus.html]
 [test_screen_orientation.html]
 [test_settimeout_extra_arguments.html]
 [test_settimeout_inner.html]
 [test_setting_opener.html]
 skip-if = (buildapp == 'b2g' && toolkit != 'gonk') #Bug 931116, b2g desktop specific, initial triage
 [test_url.html]
 [test_url_empty_port.html]
new file mode 100644
--- /dev/null
+++ b/dom/base/test/test_open_null_features.html
@@ -0,0 +1,54 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1009529
+-->
+<head>
+  <meta charset="utf-8">
+  <title>Test for Bug 1009529</title>
+  <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+  <script type="application/javascript">
+
+  /** Test for Bug 1009529 **/
+  SimpleTest.waitForExplicitFinish();
+
+  var win1 = open("about:blank", "_blank", null);
+  var win2 = open("about:blank", "_blank", "");
+  for (var k in win1) {
+    var v;
+    try {
+      v = win1[k];
+    } catch (ex) {}
+    if (v instanceof win1.BarProp) {
+      is(v.visible, win2[k] && win2[k].visible, "Both windows should have the same value for " + k);
+    }
+  }
+
+  var closeCount = 0;
+  var closeInc = function(e) {
+    this.removeEventListener("unload", closeInc, true);
+    closeCount++;
+    if (closeCount == 2) {
+      SimpleTest.finish();
+    }
+  };
+  win1.addEventListener("unload", closeInc, true);
+  win2.addEventListener("unload", closeInc, true);
+  win1.close();
+  win2.close();
+  
+  </script>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1009529">Mozilla Bug 1009529</a>
+<p id="display">
+</p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+</pre>
+</body>
+</html>
+
--- a/dom/bindings/BindingUtils.cpp
+++ b/dom/bindings/BindingUtils.cpp
@@ -1902,19 +1902,19 @@ InterfaceHasInstance(JSContext* cx, JS::
 
   if (domClass &&
       domClass->mInterfaceChain[clasp->mDepth] == clasp->mPrototypeID) {
     *bp = true;
     return true;
   }
 
   JS::Rooted<JSObject*> unwrapped(cx, js::CheckedUnwrap(instance, true));
-  if (unwrapped && jsipc::JavaScriptParent::IsCPOW(unwrapped)) {
+  if (unwrapped && jsipc::IsCPOW(unwrapped)) {
     bool boolp = false;
-    if (!jsipc::JavaScriptParent::DOMInstanceOf(cx, unwrapped, clasp->mPrototypeID,
+    if (!jsipc::DOMInstanceOf(cx, unwrapped, clasp->mPrototypeID,
                                                 clasp->mDepth, &boolp)) {
       return false;
     }
     *bp = boolp;
     return true;
   }
 
   JS::Rooted<JS::Value> protov(cx);
--- a/dom/bindings/Codegen.py
+++ b/dom/bindings/Codegen.py
@@ -7577,17 +7577,22 @@ def getEnumValueName(value):
         return '_' + value
     value = re.sub(r'[^0-9A-Za-z_]', '_', value)
     if re.match("^_[A-Z]|__", value):
         raise SyntaxError('Enum value "' + value + '" is reserved by the C++ spec')
     if value == "_empty":
         raise SyntaxError('"_empty" is not an IDL enum value we support yet')
     if value == "":
         return "_empty"
-    return MakeNativeName(value)
+    nativeName = MakeNativeName(value)
+    if nativeName == "EndGuard_":
+        raise SyntaxError('Enum value "' + value + '" cannot be used because it'
+                          ' collides with our internal EndGuard_ value.  Please'
+                          ' rename our internal EndGuard_ to something else')
+    return nativeName
 
 
 class CGEnum(CGThing):
     def __init__(self, enum):
         CGThing.__init__(self)
         self.enum = enum
 
     def stringsNamespace(self):
@@ -7597,20 +7602,21 @@ class CGEnum(CGThing):
         return len(self.enum.values()) + 1
 
     def declare(self):
         decl = fill(  # BOGUS extra newline at top
             """
 
             MOZ_BEGIN_ENUM_CLASS(${name}, uint32_t)
               $*{enums}
+              EndGuard_
             MOZ_END_ENUM_CLASS(${name})
             """,
             name=self.enum.identifier.name,
-            enums=",\n".join(map(getEnumValueName, self.enum.values())) + "\n")
+            enums=",\n".join(map(getEnumValueName, self.enum.values())) + ",\n")
         strings = CGNamespace(self.stringsNamespace(),
                               CGGeneric(declare="extern const EnumEntry %s[%d];\n"
                                         % (ENUM_ENTRY_VARIABLE_NAME, self.nEnumStrings())))
         return decl + "\n" + strings.declare()
 
     def define(self):
         strings = fill(  # BOGUS extra newline at top
             """
--- a/dom/ipc/ContentChild.cpp
+++ b/dom/ipc/ContentChild.cpp
@@ -894,17 +894,17 @@ ContentChild::AllocPJavaScriptChild()
         return nullptr;
     }
     return child;
 }
 
 bool
 ContentChild::DeallocPJavaScriptChild(PJavaScriptChild *child)
 {
-    delete child;
+    static_cast<mozilla::jsipc::JavaScriptChild *>(child)->decref();
     return true;
 }
 
 PBrowserChild*
 ContentChild::AllocPBrowserChild(const IPCTabContext& aContext,
                                  const uint32_t& aChromeFlags)
 {
     // We'll happily accept any kind of IPCTabContext here; we don't need to
--- a/dom/ipc/ContentParent.cpp
+++ b/dom/ipc/ContentParent.cpp
@@ -79,16 +79,17 @@
 #include "nsIClipboard.h"
 #include "nsICycleCollectorListener.h"
 #include "nsIDOMGeoGeolocation.h"
 #include "mozilla/dom/WakeLock.h"
 #include "nsIDOMWindow.h"
 #include "nsIExternalProtocolService.h"
 #include "nsIGfxInfo.h"
 #include "nsIIdleService.h"
+#include "nsIJSRuntimeService.h"
 #include "nsIMemoryInfoDumper.h"
 #include "nsIMemoryReporter.h"
 #include "nsIMozBrowserFrame.h"
 #include "nsIMutable.h"
 #include "nsIObserverService.h"
 #include "nsIPresShell.h"
 #include "nsIRemoteBlob.h"
 #include "nsIScriptError.h"
@@ -2350,17 +2351,25 @@ ContentParent::RecvGetXPCOMProcessAttrib
 
     return true;
 }
 
 mozilla::jsipc::PJavaScriptParent *
 ContentParent::AllocPJavaScriptParent()
 {
     MOZ_ASSERT(!ManagedPJavaScriptParent().Length());
-    mozilla::jsipc::JavaScriptParent *parent = new mozilla::jsipc::JavaScriptParent();
+
+    nsCOMPtr<nsIJSRuntimeService> svc = do_GetService("@mozilla.org/js/xpc/RuntimeService;1");
+    NS_ENSURE_TRUE(svc, nullptr);
+
+    JSRuntime *rt;
+    svc->GetRuntime(&rt);
+    NS_ENSURE_TRUE(svc, nullptr);
+
+    mozilla::jsipc::JavaScriptParent *parent = new mozilla::jsipc::JavaScriptParent(rt);
     if (!parent->init()) {
         delete parent;
         return nullptr;
     }
     return parent;
 }
 
 bool
@@ -3344,17 +3353,17 @@ ContentParent::DoSendAsyncMessage(JSCont
                                   JS::Handle<JSObject *> aCpows,
                                   nsIPrincipal* aPrincipal)
 {
   ClonedMessageData data;
   if (!BuildClonedMessageDataForParent(this, aData, data)) {
     return false;
   }
   InfallibleTArray<CpowEntry> cpows;
-  if (!GetCPOWManager()->Wrap(aCx, aCpows, &cpows)) {
+  if (aCpows && !GetCPOWManager()->Wrap(aCx, aCpows, &cpows)) {
     return false;
   }
   return SendAsyncMessage(nsString(aMessage), data, cpows, aPrincipal);
 }
 
 bool
 ContentParent::CheckPermission(const nsAString& aPermission)
 {
--- a/dom/plugins/base/nsPluginInstanceOwner.cpp
+++ b/dom/plugins/base/nsPluginInstanceOwner.cpp
@@ -2770,17 +2770,23 @@ NS_IMETHODIMP nsPluginInstanceOwner::Cre
   // Can't call this twice!
   if (mWidget) {
     NS_WARNING("Trying to create a plugin widget twice!");
     return NS_ERROR_FAILURE;
   }
   
   bool windowless = false;
   mInstance->IsWindowless(&windowless);
-  if (!windowless && !nsIWidget::UsePuppetWidgets()) {
+  if (!windowless
+// Mac plugins use the widget for setting up the plugin window and event handling.
+// We want to use the puppet widgets there with e10s
+#ifndef XP_MACOSX
+      && !nsIWidget::UsePuppetWidgets()
+#endif
+      ) {
     // Try to get a parent widget, on some platforms widget creation will fail without
     // a parent.
     nsCOMPtr<nsIWidget> parentWidget;
     nsIDocument *doc = nullptr;
     if (mContent) {
       doc = mContent->OwnerDoc();
       parentWidget = nsContentUtils::WidgetForDocument(doc);
     }
@@ -2885,60 +2891,77 @@ void* nsPluginInstanceOwner::FixUpPlugin
     SetPluginPortAndDetectChange();
   }
 
   // We'll need the top-level Cocoa window for the Cocoa event model.
   nsIWidget* widget = mObjectFrame->GetNearestWidget();
   if (!widget)
     return nullptr;
   void *cocoaTopLevelWindow = widget->GetNativeData(NS_NATIVE_WINDOW);
-  if (!cocoaTopLevelWindow)
+  // We don't expect to have a top level window in a content process
+  if (!cocoaTopLevelWindow && XRE_GetProcessType() == GeckoProcessType_Default) {
     return nullptr;
+  }
 
   nsIntPoint pluginOrigin;
   nsIntRect widgetClip;
   bool widgetVisible;
   pluginWidget->GetPluginClipRect(widgetClip, pluginOrigin, widgetVisible);
+  // TODO: Detect visibility for e10s mac plugins
+  if (XRE_GetProcessType() != GeckoProcessType_Default) {
+    widgetVisible = true;
+  }
   mWidgetVisible = widgetVisible;
 
   // printf("GetPluginClipRect returning visible %d\n", widgetVisible);
 
   // This would be a lot easier if we could use obj-c here,
   // but we can't. Since we have only nsIWidget and we can't
   // use its native widget (an obj-c object) we have to go
   // from the widget's screen coordinates to its window coords
   // instead of straight to window coords.
   nsIntPoint geckoScreenCoords = mWidget->WidgetToScreenOffset();
 
   nsRect windowRect;
-  NS_NPAPI_CocoaWindowFrame(cocoaTopLevelWindow, windowRect);
+  if (cocoaTopLevelWindow) {
+    NS_NPAPI_CocoaWindowFrame(cocoaTopLevelWindow, windowRect);
+  }
 
   double scaleFactor = 1.0;
   GetContentsScaleFactor(&scaleFactor);
   int intScaleFactor = ceil(scaleFactor);
 
   // Convert geckoScreenCoords from device pixels to "display pixels"
   // for HiDPI modes.
   mPluginWindow->x = geckoScreenCoords.x/intScaleFactor - windowRect.x;
   mPluginWindow->y = geckoScreenCoords.y/intScaleFactor - windowRect.y;
 
   NPRect oldClipRect = mPluginWindow->clipRect;
-  
+
   // fix up the clipping region
   mPluginWindow->clipRect.top    = widgetClip.y;
   mPluginWindow->clipRect.left   = widgetClip.x;
 
   if (!mWidgetVisible || inPaintState == ePluginPaintDisable) {
     mPluginWindow->clipRect.bottom = mPluginWindow->clipRect.top;
     mPluginWindow->clipRect.right  = mPluginWindow->clipRect.left;
   }
+  else if (XRE_GetProcessType() != GeckoProcessType_Default)
+  {
+    // For e10s we only support async windowless plugin. This means that
+    // we're always going to allocate a full window for the plugin to draw
+    // for even if the plugin is mostly outside of the scroll port. Thus
+    // we never trim the window to the bounds of the widget.
+    mPluginWindow->clipRect.bottom = mPluginWindow->clipRect.top + mPluginWindow->height;
+    mPluginWindow->clipRect.right  = mPluginWindow->clipRect.left + mPluginWindow->width;
+  }
   else if (inPaintState == ePluginPaintEnable)
   {
     mPluginWindow->clipRect.bottom = mPluginWindow->clipRect.top + widgetClip.height;
-    mPluginWindow->clipRect.right  = mPluginWindow->clipRect.left + widgetClip.width; 
+    mPluginWindow->clipRect.right  = mPluginWindow->clipRect.left + widgetClip.width;
   }
 
   // if the clip rect changed, call SetWindow()
   // (RealPlayer needs this to draw correctly)
   if (mPluginWindow->clipRect.left    != oldClipRect.left   ||
       mPluginWindow->clipRect.top     != oldClipRect.top    ||
       mPluginWindow->clipRect.right   != oldClipRect.right  ||
       mPluginWindow->clipRect.bottom  != oldClipRect.bottom ||
@@ -2958,17 +2981,17 @@ void* nsPluginInstanceOwner::FixUpPlugin
   if (!mSentInitialTopLevelWindowEvent) {
     // Set this before calling ProcessEvent to avoid endless recursion.
     mSentInitialTopLevelWindowEvent = true;
 
     WidgetPluginEvent pluginEvent(true, NS_PLUGIN_FOCUS_EVENT, nullptr);
     NPCocoaEvent cocoaEvent;
     InitializeNPCocoaEvent(&cocoaEvent);
     cocoaEvent.type = NPCocoaEventWindowFocusChanged;
-    cocoaEvent.data.focus.hasFocus = NS_NPAPI_CocoaWindowIsMain(cocoaTopLevelWindow);
+    cocoaEvent.data.focus.hasFocus = cocoaTopLevelWindow ? NS_NPAPI_CocoaWindowIsMain(cocoaTopLevelWindow) : true;
     pluginEvent.pluginEvent = &cocoaEvent;
     ProcessEvent(pluginEvent);
   }
 
   return nullptr;
 }
 
 void
--- a/embedding/components/windowwatcher/src/nsAutoWindowStateHelper.cpp
+++ b/embedding/components/windowwatcher/src/nsAutoWindowStateHelper.cpp
@@ -14,61 +14,58 @@
 
 using namespace mozilla;
 using namespace mozilla::dom;
 
 /****************************************************************
  ****************** nsAutoWindowStateHelper *********************
  ****************************************************************/
 
-nsAutoWindowStateHelper::nsAutoWindowStateHelper(nsIDOMWindow *aWindow)
+nsAutoWindowStateHelper::nsAutoWindowStateHelper(nsPIDOMWindow *aWindow)
   : mWindow(aWindow),
     mDefaultEnabled(DispatchEventToChrome("DOMWillOpenModalDialog"))
 {
-  nsCOMPtr<nsPIDOMWindow> window(do_QueryInterface(aWindow));
-
-  if (window) {
-    window->EnterModalState();
+  if (mWindow) {
+    mWindow->EnterModalState();
   }
 }
 
 nsAutoWindowStateHelper::~nsAutoWindowStateHelper()
 {
-  nsCOMPtr<nsPIDOMWindow> window(do_QueryInterface(mWindow));
-
-  if (window) {
-    window->LeaveModalState();
+  if (mWindow) {
+    mWindow->LeaveModalState();
   }
 
   if (mDefaultEnabled) {
     DispatchEventToChrome("DOMModalDialogClosed");
   }
 }
 
 bool
 nsAutoWindowStateHelper::DispatchEventToChrome(const char *aEventName)
 {
-  nsCOMPtr<nsPIDOMWindow> window = do_QueryInterface(mWindow);
-  if (!window || (window->IsInnerWindow() && !window->IsCurrentInnerWindow())) {
+  // XXXbz should we skip dispatching the event if the inner changed?
+  // That is, should we store both the inner and the outer?
+  if (!mWindow) {
     return true;
   }
 
   // The functions of nsContentUtils do not provide the required behavior,
   // so the following is inlined.
-  nsIDocument* doc = window->GetExtantDoc();
+  nsIDocument* doc = mWindow->GetExtantDoc();
   if (!doc) {
     return true;
   }
 
   ErrorResult rv;
   nsRefPtr<Event> event = doc->CreateEvent(NS_LITERAL_STRING("Events"), rv);
   if (rv.Failed()) {
     return false;
   }
   NS_ENSURE_TRUE(NS_SUCCEEDED(event->InitEvent(NS_ConvertASCIItoUTF16(aEventName), true, true)), false);
   event->SetTrusted(true);
   event->GetInternalNSEvent()->mFlags.mOnlyChromeDispatch = true;
 
-  nsCOMPtr<EventTarget> target = do_QueryInterface(window);
+  nsCOMPtr<EventTarget> target = do_QueryInterface(mWindow);
   bool defaultActionEnabled;
   target->DispatchEvent(event, &defaultActionEnabled);
   return defaultActionEnabled;
 }
--- a/embedding/components/windowwatcher/src/nsAutoWindowStateHelper.h
+++ b/embedding/components/windowwatcher/src/nsAutoWindowStateHelper.h
@@ -2,36 +2,37 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #ifndef __nsAutoWindowStateHelper_h
 #define __nsAutoWindowStateHelper_h
 
 #include "nsCOMPtr.h"
+#include "nsPIDOMWindow.h"
 
 /**
  * Helper class for dealing with notifications around opening modal
  * windows.
  */
 
-class nsIDOMWindow;
+class nsPIDOMWindow;
 
 class nsAutoWindowStateHelper
 {
 public:
-  nsAutoWindowStateHelper(nsIDOMWindow *aWindow);
+  nsAutoWindowStateHelper(nsPIDOMWindow *aWindow);
   ~nsAutoWindowStateHelper();
 
   bool DefaultEnabled()
   {
     return mDefaultEnabled;
   }
 
 protected:
   bool DispatchEventToChrome(const char *aEventName);
 
-  nsIDOMWindow *mWindow;
+  nsCOMPtr<nsPIDOMWindow> mWindow;
   bool mDefaultEnabled;
 };
 
 
 #endif
--- a/embedding/components/windowwatcher/src/nsWindowWatcher.cpp
+++ b/embedding/components/windowwatcher/src/nsWindowWatcher.cpp
@@ -950,17 +950,21 @@ nsWindowWatcher::OpenWindowInternal(nsID
     // Throw an exception here if no web browser chrome is available,
     // we need that to show a modal window.
     NS_ENSURE_TRUE(newChrome, NS_ERROR_NOT_AVAILABLE);
 
     // Dispatch dialog events etc, but we only want to do that if
     // we're opening a modal content window (the helper classes are
     // no-ops if given no window), for chrome dialogs we don't want to
     // do any of that (it's done elsewhere for us).
-    nsAutoWindowStateHelper windowStateHelper(aParent);
+    // Make sure we maintain the state on an outer window, because
+    // that's where it lives; inner windows assert if you try to
+    // maintain the state on them.
+    nsAutoWindowStateHelper windowStateHelper(
+      parentWindow ? parentWindow->GetOuterWindow() : nullptr);
 
     if (!windowStateHelper.DefaultEnabled()) {
       // Default to cancel not opening the modal window.
       NS_RELEASE(*_retval);
 
       return NS_OK;
     }
 
--- a/gfx/2d/DrawTargetSkia.cpp
+++ b/gfx/2d/DrawTargetSkia.cpp
@@ -547,31 +547,36 @@ void
 DrawTargetSkia::MaskSurface(const Pattern &aSource,
                             SourceSurface *aMask,
                             Point aOffset,
                             const DrawOptions &aOptions)
 {
   MarkChanged();
   AutoPaintSetup paint(mCanvas.get(), aOptions, aSource);
 
-  SkPaint maskPaint;
-  TempBitmap tmpBitmap;
-  SetPaintPattern(maskPaint, SurfacePattern(aMask, ExtendMode::CLAMP), tmpBitmap);
-
-  SkMatrix transform = maskPaint.getShader()->getLocalMatrix();
-  transform.postTranslate(SkFloatToScalar(aOffset.x), SkFloatToScalar(aOffset.y));
-  maskPaint.getShader()->setLocalMatrix(transform);
+  TempBitmap bitmap = GetBitmapForSurface(aMask);
+  if (bitmap.mBitmap.colorType() == kAlpha_8_SkColorType) {
+    mCanvas->drawBitmap(bitmap.mBitmap, aOffset.x, aOffset.y, &paint.mPaint);
+  } else {
+    SkPaint maskPaint;
+    TempBitmap tmpBitmap;
+    SetPaintPattern(maskPaint, SurfacePattern(aMask, ExtendMode::CLAMP), tmpBitmap);
 
-  SkLayerRasterizer *raster = new SkLayerRasterizer();
-  raster->addLayer(maskPaint);
-  SkSafeUnref(paint.mPaint.setRasterizer(raster));
+    SkMatrix transform = maskPaint.getShader()->getLocalMatrix();
+    transform.postTranslate(SkFloatToScalar(aOffset.x), SkFloatToScalar(aOffset.y));
+    maskPaint.getShader()->setLocalMatrix(transform);
 
-  IntSize size = aMask->GetSize();
-  Rect rect = Rect(aOffset.x, aOffset.y, size.width, size.height);
-  mCanvas->drawRect(RectToSkRect(rect), paint.mPaint);
+    SkLayerRasterizer *raster = new SkLayerRasterizer();
+    raster->addLayer(maskPaint);
+    SkSafeUnref(paint.mPaint.setRasterizer(raster));
+
+    IntSize size = aMask->GetSize();
+    Rect rect = Rect(aOffset.x, aOffset.y, size.width, size.height);
+    mCanvas->drawRect(RectToSkRect(rect), paint.mPaint);
+  }
 }
 
 TemporaryRef<SourceSurface>
 DrawTargetSkia::CreateSourceSurfaceFromData(unsigned char *aData,
                                             const IntSize &aSize,
                                             int32_t aStride,
                                             SurfaceFormat aFormat) const
 {
--- a/gfx/layers/FrameMetrics.h
+++ b/gfx/layers/FrameMetrics.h
@@ -71,28 +71,28 @@ public:
     , mDisplayPort(0, 0, 0, 0)
     , mCriticalDisplayPort(0, 0, 0, 0)
     , mViewport(0, 0, 0, 0)
     , mScrollableRect(0, 0, 0, 0)
     , mResolution(1)
     , mCumulativeResolution(1)
     , mTransformScale(1)
     , mDevPixelsPerCSSPixel(1)
-    , mPresShellId(-1)
     , mMayHaveTouchListeners(false)
     , mIsRoot(false)
     , mHasScrollgrab(false)
     , mScrollId(NULL_SCROLL_ID)
     , mScrollOffset(0, 0)
     , mZoom(1)
     , mUpdateScrollOffset(false)
     , mScrollGeneration(0)
     , mRootCompositionSize(0, 0)
     , mDisplayPortMargins(0, 0, 0, 0)
     , mUseDisplayPortMargins(false)
+    , mPresShellId(-1)
   {}
 
   // Default copy ctor and operator= are fine
 
   bool operator==(const FrameMetrics& aOther) const
   {
     // mContentDescription is not compared on purpose as it's only used
     // for debugging.
@@ -329,18 +329,16 @@ public:
   ScreenToParentLayerScale mTransformScale;
 
   // The conversion factor between CSS pixels and device pixels for this frame.
   // This can vary based on a variety of things, such as reflowing-zoom. The
   // conversion factor for device pixels to layers pixels is just the
   // resolution.
   CSSToLayoutDeviceScale mDevPixelsPerCSSPixel;
 
-  uint32_t mPresShellId;
-
   // Whether or not this frame may have touch listeners.
   bool mMayHaveTouchListeners;
 
   // Whether or not this is the root scroll frame for the root content document.
   bool mIsRoot;
 
   // Whether or not this frame is for an element marked 'scrollgrab'.
   bool mHasScrollgrab;
@@ -427,16 +425,26 @@ public:
     mUseDisplayPortMargins = true;
   }
 
   bool GetUseDisplayPortMargins() const
   {
     return mUseDisplayPortMargins;
   }
 
+  uint32_t GetPresShellId() const
+  {
+    return mPresShellId;
+  }
+
+  void SetPresShellId(uint32_t aPresShellId)
+  {
+    mPresShellId = aPresShellId;
+  }
+
 private:
   // New fields from now on should be made private and old fields should
   // be refactored to be private.
 
   // A unique ID assigned to each scrollable frame.
   ViewID mScrollId;
 
   // The position of the top-left of the CSS viewport, relative to the document
@@ -477,16 +485,18 @@ private:
 
   // A display port expressed as layer margins that apply to the rect of what
   // is drawn of the scrollable element.
   LayerMargin mDisplayPortMargins;
 
   // If this is true then we use the display port margins on this metrics,
   // otherwise use the display port rect.
   bool mUseDisplayPortMargins;
+
+  uint32_t mPresShellId;
 };
 
 /**
  * This class allows us to uniquely identify a scrollable layer. The
  * mLayersId identifies the layer tree (corresponding to a child process
  * and/or tab) that the scrollable layer belongs to. The mPresShellId
  * is a temporal identifier (corresponding to the document loaded that
  * contains the scrollable layer, which may change over time). The
@@ -511,17 +521,17 @@ struct ScrollableLayerGuid {
     , mPresShellId(aPresShellId)
     , mScrollId(aScrollId)
   {
     MOZ_COUNT_CTOR(ScrollableLayerGuid);
   }
 
   ScrollableLayerGuid(uint64_t aLayersId, const FrameMetrics& aMetrics)
     : mLayersId(aLayersId)
-    , mPresShellId(aMetrics.mPresShellId)
+    , mPresShellId(aMetrics.GetPresShellId())
     , mScrollId(aMetrics.GetScrollId())
   {
     MOZ_COUNT_CTOR(ScrollableLayerGuid);
   }
 
   ~ScrollableLayerGuid()
   {
     MOZ_COUNT_DTOR(ScrollableLayerGuid);
--- a/gfx/layers/apz/public/GeckoContentController.h
+++ b/gfx/layers/apz/public/GeckoContentController.h
@@ -61,17 +61,19 @@ public:
    */
   virtual void HandleLongTap(const CSSPoint& aPoint,
                              int32_t aModifiers,
                              const ScrollableLayerGuid& aGuid) = 0;
 
   /**
    * Requests handling of releasing a long tap. |aPoint| is in CSS pixels,
    * relative to the current scroll offset. HandleLongTapUp will always be
-   * preceeded by HandleLongTap
+   * preceeded by HandleLongTap. However not all calls to HandleLongTap will
+   * be followed by a HandleLongTapUp (for example, if the user drags
+   * around between the long-tap and lifting their finger).
    */
   virtual void HandleLongTapUp(const CSSPoint& aPoint,
                                int32_t aModifiers,
                                const ScrollableLayerGuid& aGuid) = 0;
 
   /**
    * Requests sending a mozbrowserasyncscroll domevent to embedder.
    * |aContentRect| is in CSS pixels, relative to the current cssPage.
--- a/gfx/layers/apz/src/AsyncPanZoomController.cpp
+++ b/gfx/layers/apz/src/AsyncPanZoomController.cpp
@@ -64,17 +64,17 @@
 
 #define APZC_LOG(...)
 // #define APZC_LOG(...) printf_stderr("APZC: " __VA_ARGS__)
 #define APZC_LOG_FM(fm, prefix, ...) \
   APZC_LOG(prefix ":" \
            " i=(%ld %lld) cb=(%d %d %d %d) rcs=(%.3f %.3f) dp=(%.3f %.3f %.3f %.3f) dpm=(%.3f %.3f %.3f %.3f) um=%d " \
            "v=(%.3f %.3f %.3f %.3f) s=(%.3f %.3f) sr=(%.3f %.3f %.3f %.3f) z(ld=%.3f r=%.3f cr=%.3f z=%.3f ts=%.3f) u=(%d %lu)\n", \
            __VA_ARGS__, \
-           fm.mPresShellId, fm.GetScrollId(), \
+           fm.GetPresShellId(), fm.GetScrollId(), \
            fm.mCompositionBounds.x, fm.mCompositionBounds.y, fm.mCompositionBounds.width, fm.mCompositionBounds.height, \
            fm.GetRootCompositionSize().width, fm.GetRootCompositionSize().height, \
            fm.mDisplayPort.x, fm.mDisplayPort.y, fm.mDisplayPort.width, fm.mDisplayPort.height, \
            fm.GetDisplayPortMargins().top, fm.GetDisplayPortMargins().right, fm.GetDisplayPortMargins().bottom, fm.GetDisplayPortMargins().left, \
            fm.GetUseDisplayPortMargins() ? 1 : 0, \
            fm.mViewport.x, fm.mViewport.y, fm.mViewport.width, fm.mViewport.height, \
            fm.GetScrollOffset().x, fm.GetScrollOffset().y, \
            fm.mScrollableRect.x, fm.mScrollableRect.y, fm.mScrollableRect.width, fm.mScrollableRect.height, \
@@ -245,16 +245,20 @@ typedef GeckoContentController::APZState
  * In general we want apz.[xy]_skate_size_multiplier to be smaller than the corresponding
  * stationary size multiplier because when panning fast we would like to paint
  * less and get faster, more predictable paint times. When panning slowly we
  * can afford to paint more even though it's slower.
  *
  * "apz.x_stationary_size_multiplier", "apz.y_stationary_size_multiplier"
  * The multiplier we apply to the displayport size if it is not skating (see
  * documentation for the skate size multipliers above).
+ *
+ * "apz.zoom_animation_duration_ms"
+ * This controls how long the zoom-to-rect animation takes.
+ * Units: ms
  */
 
 /**
  * Default touch behavior (is used when not touch behavior is set).
  */
 static const uint32_t DefaultTouchBehavior = AllowedTouchBehavior::VERTICAL_PAN |
                                              AllowedTouchBehavior::HORIZONTAL_PAN |
                                              AllowedTouchBehavior::PINCH_ZOOM |
@@ -280,21 +284,16 @@ static const double AXIS_BREAKOUT_ANGLE 
  * If angle is less than this value we can assume that panning
  * can be done in allowed direction (horizontal or vertical).
  * Currently used only for touch-action css property stuff and was
  * added to keep behavior consistent with IE.
  */
 static const double ALLOWED_DIRECT_PAN_ANGLE = M_PI / 3.0; // 60 degrees
 
 /**
- * Duration of a zoom to animation.
- */
-static const TimeDuration ZOOM_TO_DURATION = TimeDuration::FromSeconds(0.25);
-
-/**
  * Computed time function used for sampling frames of a zoom to animation.
  */
 StaticAutoPtr<ComputedTimingFunction> gComputedTimingFunction;
 
 /**
  * Maximum zoom amount, always used, even if a page asks for higher.
  */
 static const CSSToScreenScale MAX_ZOOM(8.0f);
@@ -414,27 +413,29 @@ private:
 
   AsyncPanZoomController& mApzc;
 };
 
 class ZoomAnimation: public AsyncPanZoomAnimation {
 public:
   ZoomAnimation(CSSPoint aStartOffset, CSSToScreenScale aStartZoom,
                 CSSPoint aEndOffset, CSSToScreenScale aEndZoom)
-    : mStartOffset(aStartOffset)
+    : mTotalDuration(TimeDuration::FromMilliseconds(gfxPrefs::APZZoomAnimationDuration()))
+    , mStartOffset(aStartOffset)
     , mStartZoom(aStartZoom)
     , mEndOffset(aEndOffset)
     , mEndZoom(aEndZoom)
   {}
 
   virtual bool Sample(FrameMetrics& aFrameMetrics,
                       const TimeDuration& aDelta);
 
 private:
   TimeDuration mDuration;
+  const TimeDuration mTotalDuration;
 
   // 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;
 
@@ -1598,17 +1599,17 @@ void AsyncPanZoomController::RequestCont
   SendAsyncScrollEvent();
   mPaintThrottler.PostTask(
     FROM_HERE,
     NewRunnableMethod(this,
                       &AsyncPanZoomController::DispatchRepaintRequest,
                       aFrameMetrics),
     GetFrameTime());
 
-  aFrameMetrics.mPresShellId = mLastContentPaintMetrics.mPresShellId;
+  aFrameMetrics.SetPresShellId(mLastContentPaintMetrics.GetPresShellId());
   mLastPaintRequestMetrics = aFrameMetrics;
 }
 
 /*static*/ CSSRect
 GetDisplayPortRect(const FrameMetrics& aFrameMetrics)
 {
   // This computation is based on what happens in CalculatePendingDisplayPort. If that
   // changes then this might need to change too
@@ -1638,17 +1639,17 @@ AsyncPanZoomController::FireAsyncScrollO
     SendAsyncScrollEvent();
   }
   mAsyncScrollTimeoutTask = nullptr;
 }
 
 bool ZoomAnimation::Sample(FrameMetrics& aFrameMetrics,
                            const TimeDuration& aDelta) {
   mDuration += aDelta;
-  double animPosition = mDuration / ZOOM_TO_DURATION;
+  double animPosition = mDuration / mTotalDuration;
 
   if (animPosition >= 1.0) {
     aFrameMetrics.SetZoom(mEndZoom);
     aFrameMetrics.SetScrollOffset(mEndOffset);
     return false;
   }
 
   // Sample the zoom at the current time point.  The sampled zoom
--- a/gfx/layers/apz/src/GestureEventListener.cpp
+++ b/gfx/layers/apz/src/GestureEventListener.cpp
@@ -175,32 +175,44 @@ nsEventStatus GestureEventListener::Hand
     NS_WARNING("Unhandled state upon multitouch start");
     SetState(GESTURE_NONE);
     break;
   }
 
   return rv;
 }
 
+bool GestureEventListener::MoveDistanceIsLarge()
+{
+  ScreenIntPoint delta = mLastTouchInput.mTouches[0].mScreenPoint - mTouchStartPosition;
+  return (NS_hypot(delta.x, delta.y) > AsyncPanZoomController::GetTouchStartTolerance());
+}
+
 nsEventStatus GestureEventListener::HandleInputTouchMove()
 {
   nsEventStatus rv = nsEventStatus_eIgnore;
 
   switch (mState) {
   case GESTURE_NONE:
+    // Ignore this input signal as the corresponding events get handled by APZC
+    break;
+
   case GESTURE_LONG_TOUCH_DOWN:
-    // Ignore this input signal as the corresponding events get handled by APZC
+    if (MoveDistanceIsLarge()) {
+      // So that we don't fire a long-tap-up if the user moves around after a
+      // long-tap
+      SetState(GESTURE_NONE);
+    }
     break;
 
   case GESTURE_FIRST_SINGLE_TOUCH_DOWN:
   case GESTURE_FIRST_SINGLE_TOUCH_MAX_TAP_DOWN:
   case GESTURE_SECOND_SINGLE_TOUCH_DOWN: {
     // If we move too much, bail out of the tap.
-    ScreenIntPoint delta = mLastTouchInput.mTouches[0].mScreenPoint - mTouchStartPosition;
-    if (NS_hypot(delta.x, delta.y) > AsyncPanZoomController::GetTouchStartTolerance()) {
+    if (MoveDistanceIsLarge()) {
       CancelLongTapTimeoutTask();
       CancelMaxTapTimeoutTask();
       SetState(GESTURE_NONE);
     }
     break;
   }
 
   case GESTURE_MULTI_TOUCH_DOWN: {
--- a/gfx/layers/apz/src/GestureEventListener.h
+++ b/gfx/layers/apz/src/GestureEventListener.h
@@ -121,16 +121,18 @@ private:
   nsEventStatus HandleInputTouchEnd();
   nsEventStatus HandleInputTouchMove();
   nsEventStatus HandleInputTouchCancel();
   void HandleInputTimeoutLongTap();
   void HandleInputTimeoutMaxTap();
 
   void TriggerSingleTapConfirmedEvent();
 
+  bool MoveDistanceIsLarge();
+
   /**
    * Do actual state transition and reset substates.
    */
   void SetState(GestureState aState);
 
   nsRefPtr<AsyncPanZoomController> mAsyncPanZoomController;
 
   /**
--- a/gfx/layers/apz/testutil/APZTestData.h
+++ b/gfx/layers/apz/testutil/APZTestData.h
@@ -37,23 +37,20 @@ typedef uint32_t SequenceNumber;
 //  - Add ability to associate a repaint request triggered during a layers update
 //    with the sequence number of the paint that caused the layers update.
 class APZTestData {
   typedef FrameMetrics::ViewID ViewID;
   friend struct IPC::ParamTraits<APZTestData>;
   friend class APZTestDataToJSConverter;
 public:
   void StartNewPaint(SequenceNumber aSequenceNumber) {
-    auto insertResult = mPaints.insert(DataStore::value_type(aSequenceNumber, Bucket()));
-    if (!insertResult.second) {
-      // TODO(botond): Change this to MOZ_ASSERT once we get rid of
-      // APZCTreeManager::UpdatePanZoomControllerTree() calls for repeat
-      // transactions.
-      NS_WARNING("Already have a paint with this sequence number");
-    }
+    mPaints.insert(DataStore::value_type(aSequenceNumber, Bucket()));
+    // TODO(botond): MOZ_ASSERT() that we didn't already have a paint with this
+    // sequence number once we get rid ofAPZCTreeManager::UpdatePanZoomControllerTree()
+    // calls for repeat transactions (bug 1007728).
   }
   void LogTestDataForPaint(SequenceNumber aSequenceNumber,
                            ViewID aScrollId,
                            const std::string& aKey,
                            const std::string& aValue) {
     LogTestDataImpl(mPaints, aSequenceNumber, aScrollId, aKey, aValue);
   }
 
@@ -91,23 +88,20 @@ private:
                        const std::string& aValue) {
     auto bucketIterator = aDataStore.find(aSequenceNumber);
     if (bucketIterator == aDataStore.end()) {
       MOZ_ASSERT(false, "LogTestDataImpl called with nonexistent sequence number");
       return;
     }
     Bucket& bucket = bucketIterator->second;
     ScrollFrameData& scrollFrameData = bucket[aScrollId];  // create if doesn't exist
-    auto insertResult = scrollFrameData.insert(ScrollFrameData::value_type(aKey, aValue));
-    if (!insertResult.second) {
-      // TODO(botond): Change this to MOZ_ASSERT once we get rid of
-      // APZCTreeManager::UpdatePanZoomControllerTree() calls for repeat
-      // transactions.
-      NS_WARNING("Key already present in test data, not overwriting");
-    }
+    scrollFrameData.insert(ScrollFrameData::value_type(aKey, aValue));
+    // TODO(botond): MOZ_ASSERT() that we don't already have this key once we
+    // get rid of APZCTreeManager::UpdatePanZoomControllerTree() calls for
+    // repeat transactions (bug 1007728).
   }
 };
 
 // A helper class for logging data for a paint.
 class APZPaintLogHelper {
 public:
   APZPaintLogHelper(APZTestData* aTestData, SequenceNumber aPaintSequenceNumber)
     : mTestData(aTestData),
--- a/gfx/layers/apz/util/APZCCallbackHelper.cpp
+++ b/gfx/layers/apz/util/APZCCallbackHelper.cpp
@@ -18,17 +18,17 @@ bool
 APZCCallbackHelper::HasValidPresShellId(nsIDOMWindowUtils* aUtils,
                                         const FrameMetrics& aMetrics)
 {
     MOZ_ASSERT(aUtils);
 
     uint32_t presShellId;
     nsresult rv = aUtils->GetPresShellId(&presShellId);
     MOZ_ASSERT(NS_SUCCEEDED(rv));
-    return NS_SUCCEEDED(rv) && aMetrics.mPresShellId == presShellId;
+    return NS_SUCCEEDED(rv) && aMetrics.GetPresShellId() == presShellId;
 }
 
 static void
 AdjustDisplayPortForScrollDelta(mozilla::layers::FrameMetrics& aFrameMetrics,
                                 const CSSPoint& aActualScrollOffset)
 {
     // Correct the display-port by the difference between the requested scroll
     // offset and the resulting scroll offset after setting the requested value.
--- a/gfx/layers/d3d11/CompositorD3D11.cpp
+++ b/gfx/layers/d3d11/CompositorD3D11.cpp
@@ -101,16 +101,21 @@ CompositorD3D11::~CompositorD3D11()
       delete attachments;
     }
   }
 }
 
 bool
 CompositorD3D11::Initialize()
 {
+  if (!gfxPlatform::CanUseDirect3D11()) {
+    NS_WARNING("Direct3D 11-accelerated layers are not supported on this system.");
+    return false;
+  }
+
   HRESULT hr;
 
   mDevice = gfxWindowsPlatform::GetPlatform()->GetD3D11Device();
 
   if (!mDevice) {
     return false;
   }
 
@@ -327,27 +332,18 @@ CompositorD3D11::Initialize()
     swapDesc.BufferDesc.RefreshRate.Numerator = 60;
     swapDesc.BufferDesc.RefreshRate.Denominator = 1;
     swapDesc.SampleDesc.Count = 1;
     swapDesc.SampleDesc.Quality = 0;
     swapDesc.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT;
     swapDesc.BufferCount = 1;
     swapDesc.OutputWindow = mHwnd;
     swapDesc.Windowed = TRUE;
-    // We don't really need this flag, however it seems on some NVidia hardware
-    // smaller area windows do not present properly without this flag. This flag
-    // should have no negative consequences by itself. See bug 613790. This flag
-    // is broken on optimus devices. As a temporary solution we don't set it
-    // there, the only way of reliably detecting we're on optimus is looking for
-    // the DLL. See Bug 623807.
-    if (gfxWindowsPlatform::IsOptimus()) {
-      swapDesc.Flags = 0;
-    } else {
-      swapDesc.Flags = DXGI_SWAP_CHAIN_FLAG_GDI_COMPATIBLE;
-    }
+    swapDesc.Flags = 0;
+
 
     /**
      * Create a swap chain, this swap chain will contain the backbuffer for
      * the window we draw to. The front buffer is the full screen front
      * buffer.
      */
     hr = dxgiFactory->CreateSwapChain(dxgiDevice, &swapDesc, byRef(mSwapChain));
     if (FAILED(hr)) {
@@ -863,24 +859,20 @@ CompositorD3D11::VerifyBufferSize()
 
   mDefaultRT = nullptr;
 
   if (IsRunningInWindowsMetro()) {
     mSwapChain->ResizeBuffers(2, mSize.width, mSize.height,
                               DXGI_FORMAT_B8G8R8A8_UNORM,
                               0);
     mDisableSequenceForNextFrame = true;
-  } else if (gfxWindowsPlatform::IsOptimus()) {
+  } else {
     mSwapChain->ResizeBuffers(1, mSize.width, mSize.height,
                               DXGI_FORMAT_B8G8R8A8_UNORM,
                               0);
-  } else {
-    mSwapChain->ResizeBuffers(1, mSize.width, mSize.height,
-                              DXGI_FORMAT_B8G8R8A8_UNORM,
-                              DXGI_SWAP_CHAIN_FLAG_GDI_COMPATIBLE);
   }
 }
 
 void
 CompositorD3D11::UpdateRenderTarget()
 {
   EnsureSize();
   VerifyBufferSize();
--- a/gfx/tests/gtest/TestAsyncPanZoomController.cpp
+++ b/gfx/tests/gtest/TestAsyncPanZoomController.cpp
@@ -945,17 +945,17 @@ DoLongPressPreventDefaultTest(bool aShou
   nsRefPtr<TestAPZCTreeManager> tm = new TestAPZCTreeManager();
   nsRefPtr<TestAsyncPanZoomController> apzc = new TestAsyncPanZoomController(
     0, mcc, tm, AsyncPanZoomController::USE_GESTURE_DETECTOR);
 
   apzc->SetFrameMetrics(TestFrameMetrics());
   apzc->NotifyLayersUpdated(TestFrameMetrics(), true);
   apzc->UpdateZoomConstraints(ZoomConstraints(false, false, CSSToScreenScale(1.0), CSSToScreenScale(1.0)));
 
-  apzc->SetTouchActionEnabled(aShouldUseTouchAction);
+  apzc->SetTouchActionEnabled(aShouldUseTouchAction);
 
   EXPECT_CALL(*mcc, SendAsyncScrollDOMEvent(_,_,_)).Times(0);
   EXPECT_CALL(*mcc, RequestContentRepaint(_)).Times(0);
 
   int touchX = 10,
       touchStartY = 10,
       touchEndY = 50;
 
@@ -994,17 +994,17 @@ DoLongPressPreventDefaultTest(bool aShou
 
   time += 1000;
 
   MultiTouchInput mti = MultiTouchInput(MultiTouchInput::MULTITOUCH_MOVE, time, 0);
   mti.mTouches.AppendElement(SingleTouchData(0, ScreenIntPoint(touchX, touchEndY), ScreenSize(0, 0), 0, 0));
   status = apzc->ReceiveInputEvent(mti);
   EXPECT_EQ(nsEventStatus_eIgnore, status);
 
-  EXPECT_CALL(*mcc, HandleLongTapUp(CSSPoint(touchX, touchEndY), 0, apzc->GetGuid())).Times(1);
+  EXPECT_CALL(*mcc, HandleLongTapUp(CSSPoint(touchX, touchEndY), 0, apzc->GetGuid())).Times(0);
   status = ApzcUp(apzc, touchX, touchEndY, time);
   EXPECT_EQ(nsEventStatus_eIgnore, status);
 
   // Flush the event queue. Once the "contextmenu" event is handled, any touch
   // events that come from the same series of start->n*move->end events should
   // be discarded, even if only the "contextmenu" event is preventDefaulted.
   apzc->ContentReceivedTouch(false);
 
--- a/gfx/thebes/gfxPlatform.cpp
+++ b/gfx/thebes/gfxPlatform.cpp
@@ -1993,16 +1993,17 @@ gfxPlatform::OptimalFormatForContent(gfx
 /**
  * There are a number of layers acceleration (or layers in general) preferences
  * that should be consistent for the lifetime of the application (bug 840967).
  * As such, we will evaluate them all as soon as one of them is evaluated
  * and remember the values.  Changing these preferences during the run will
  * not have any effect until we restart.
  */
 static bool sLayersSupportsD3D9 = false;
+static bool sLayersSupportsD3D11 = false;
 static bool sBufferRotationCheckPref = true;
 static bool sPrefBrowserTabsRemoteAutostart = false;
 
 static bool sLayersAccelerationPrefsInitialized = false;
 
 void
 InitLayersAccelerationPrefs()
 {
@@ -2014,25 +2015,31 @@ InitLayersAccelerationPrefs()
     // explicit.
     MOZ_ASSERT(NS_IsMainThread(), "can only initialize prefs on the main thread");
 
     sPrefBrowserTabsRemoteAutostart = Preferences::GetBool("browser.tabs.remote.autostart", false);
 
 #ifdef XP_WIN
     if (gfxPrefs::LayersAccelerationForceEnabled()) {
       sLayersSupportsD3D9 = true;
+      sLayersSupportsD3D11 = true;
     } else {
       nsCOMPtr<nsIGfxInfo> gfxInfo = do_GetService("@mozilla.org/gfx/info;1");
       if (gfxInfo) {
         int32_t status;
         if (NS_SUCCEEDED(gfxInfo->GetFeatureStatus(nsIGfxInfo::FEATURE_DIRECT3D_9_LAYERS, &status))) {
           if (status == nsIGfxInfo::FEATURE_NO_INFO) {
             sLayersSupportsD3D9 = true;
           }
         }
+        if (NS_SUCCEEDED(gfxInfo->GetFeatureStatus(nsIGfxInfo::FEATURE_DIRECT3D_11_LAYERS, &status))) {
+          if (status == nsIGfxInfo::FEATURE_NO_INFO) {
+            sLayersSupportsD3D11 = true;
+          }
+        }
       }
     }
 #endif
 
     sLayersAccelerationPrefsInitialized = true;
   }
 }
 
@@ -2062,16 +2069,25 @@ gfxPlatform::CanUseDirect3D9()
 {
   // this function is called from the compositor thread, so it is not
   // safe to init the prefs etc. from here.
   MOZ_ASSERT(sLayersAccelerationPrefsInitialized);
   return sLayersSupportsD3D9;
 }
 
 bool
+gfxPlatform::CanUseDirect3D11()
+{
+  // this function is called from the compositor thread, so it is not
+  // safe to init the prefs etc. from here.
+  MOZ_ASSERT(sLayersAccelerationPrefsInitialized);
+  return sLayersSupportsD3D11;
+}
+
+bool
 gfxPlatform::BufferRotationEnabled()
 {
   MutexAutoLock autoLock(*gGfxPlatformPrefsLock);
 
   return sBufferRotationCheckPref && gfxPrefs::BufferRotationEnabled();
 }
 
 void
--- a/gfx/thebes/gfxPlatform.h
+++ b/gfx/thebes/gfxPlatform.h
@@ -463,16 +463,17 @@ public:
     static bool OffMainThreadCompositingEnabled();
 
     /** Use gfxPlatform::GetPref* methods instead of direct calls to Preferences
      * to get the values for layers preferences.  These will only be evaluated
      * only once, and remain the same until restart.
      */
     static bool GetPrefLayersOffMainThreadCompositionEnabled();
     static bool CanUseDirect3D9();
+    static bool CanUseDirect3D11();
 
     static bool OffMainThreadCompositionRequired();
 
     /**
      * Is it possible to use buffer rotation.  Note that these
      * check the preference, but also allow for the override to
      * disable it using DisableBufferRotation.
      */
--- a/gfx/thebes/gfxPrefs.h
+++ b/gfx/thebes/gfxPrefs.h
@@ -125,16 +125,17 @@ private:
   DECL_GFX_PREF(Live, "apz.subframe.enabled",                  APZSubframeEnabled, bool, false);
   DECL_GFX_PREF(Live, "apz.touch_start_tolerance",             APZTouchStartTolerance, float, 1.0f/4.5f);
   DECL_GFX_PREF(Live, "apz.use_paint_duration",                APZUsePaintDuration, bool, true);
   DECL_GFX_PREF(Live, "apz.velocity_bias",                     APZVelocityBias, float, 1.0f);
   DECL_GFX_PREF(Live, "apz.x_skate_size_multiplier",           APZXSkateSizeMultiplier, float, 1.5f);
   DECL_GFX_PREF(Live, "apz.x_stationary_size_multiplier",      APZXStationarySizeMultiplier, float, 3.0f);
   DECL_GFX_PREF(Live, "apz.y_skate_size_multiplier",           APZYSkateSizeMultiplier, float, 2.5f);
   DECL_GFX_PREF(Live, "apz.y_stationary_size_multiplier",      APZYStationarySizeMultiplier, float, 3.5f);
+  DECL_GFX_PREF(Live, "apz.zoom_animation_duration_ms",        APZZoomAnimationDuration, int32_t, 250);
 
   DECL_GFX_PREF(Once, "gfx.android.rgb16.force",               AndroidRGB16Force, bool, false);
 #if defined(ANDROID)
   DECL_GFX_PREF(Once, "gfx.apitrace.enabled",                  UseApitrace, bool, false);
 #endif
   DECL_GFX_PREF(Live, "gfx.canvas.azure.accelerated",          CanvasAzureAccelerated, bool, false);
   DECL_GFX_PREF(Once, "gfx.canvas.skiagl.dynamic-cache",       CanvasSkiaGLDynamicCache, bool, false);
   DECL_GFX_PREF(Once, "gfx.canvas.skiagl.cache-size",          CanvasSkiaGLCacheSize, int32_t, 96);
--- a/gfx/thebes/gfxWindowsPlatform.cpp
+++ b/gfx/thebes/gfxWindowsPlatform.cpp
@@ -1481,17 +1481,28 @@ gfxWindowsPlatform::GetD3D11Device()
   d3d11Module.disown();
 
   return mD3D11Device;
 }
 
 bool
 gfxWindowsPlatform::IsOptimus()
 {
-  return GetModuleHandleA("nvumdshim.dll");
+    static int knowIsOptimus = -1;
+    if (knowIsOptimus == -1) {
+        // other potential optimus -- nvd3d9wrapx.dll & nvdxgiwrap.dll
+        if (GetModuleHandleA("nvumdshim.dll") ||
+            GetModuleHandleA("nvumdshimx.dll"))
+        {
+            knowIsOptimus = 1;
+        } else {
+            knowIsOptimus = 0;
+        }
+    }
+    return knowIsOptimus;
 }
 
 int
 gfxWindowsPlatform::GetScreenDepth() const
 {
     // if the system doesn't have all displays with the same
     // pixel format, just return 24 and move on with life.
     if (!GetSystemMetrics(SM_SAMEDISPLAYFORMAT))
new file mode 100644
--- /dev/null
+++ b/js/ipc/JavaScriptBase.h
@@ -0,0 +1,206 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ * vim: set ts=8 sw=4 et tw=80:
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_jsipc_JavaScriptBase_h__
+#define mozilla_jsipc_JavaScriptBase_h__
+
+#include "WrapperAnswer.h"
+#include "WrapperOwner.h"
+#include "mozilla/dom/DOMTypes.h"
+#include "mozilla/jsipc/PJavaScript.h"
+
+namespace mozilla {
+namespace jsipc {
+
+template<class Base>
+class JavaScriptBase : public WrapperOwner, public WrapperAnswer, public Base
+{
+    typedef WrapperAnswer Answer;
+
+  public:
+    JavaScriptBase(JSRuntime *rt)
+      : JavaScriptShared(rt),
+        WrapperOwner(rt),
+        WrapperAnswer(rt)
+    {}
+    virtual ~JavaScriptBase() {}
+
+    virtual void ActorDestroy(WrapperOwner::ActorDestroyReason why) {
+        WrapperOwner::ActorDestroy(why);
+    }
+
+    /*** IPC handlers ***/
+
+    bool AnswerPreventExtensions(const ObjectId &objId, ReturnStatus *rs) {
+        return Answer::AnswerPreventExtensions(objId, rs);
+    }
+    bool AnswerGetPropertyDescriptor(const ObjectId &objId, const nsString &id,
+                                     ReturnStatus *rs,
+                                     PPropertyDescriptor *out) {
+        return Answer::AnswerGetPropertyDescriptor(objId, id, rs, out);
+    }
+    bool AnswerGetOwnPropertyDescriptor(const ObjectId &objId,
+                                        const nsString &id,
+                                        ReturnStatus *rs,
+                                        PPropertyDescriptor *out) {
+        return Answer::AnswerGetOwnPropertyDescriptor(objId, id, rs, out);
+    }
+    bool AnswerDefineProperty(const ObjectId &objId, const nsString &id,
+                              const PPropertyDescriptor &flags,
+                              ReturnStatus *rs) {
+        return Answer::AnswerDefineProperty(objId, id, flags, rs);
+    }
+    bool AnswerDelete(const ObjectId &objId, const nsString &id,
+                      ReturnStatus *rs, bool *success) {
+        return Answer::AnswerDelete(objId, id, rs, success);
+    }
+
+    bool AnswerHas(const ObjectId &objId, const nsString &id,
+                   ReturnStatus *rs, bool *bp) {
+        return Answer::AnswerHas(objId, id, rs, bp);
+    }
+    bool AnswerHasOwn(const ObjectId &objId, const nsString &id,
+                      ReturnStatus *rs, bool *bp) {
+        return Answer::AnswerHasOwn(objId, id, rs, bp);
+    }
+    bool AnswerGet(const ObjectId &objId, const ObjectId &receiverId,
+                   const nsString &id,
+                   ReturnStatus *rs, JSVariant *result) {
+        return Answer::AnswerGet(objId, receiverId, id, rs, result);
+    }
+    bool AnswerSet(const ObjectId &objId, const ObjectId &receiverId,
+                   const nsString &id, const bool &strict,
+                   const JSVariant &value, ReturnStatus *rs, JSVariant *result) {
+        return Answer::AnswerSet(objId, receiverId, id, strict, value, rs, result);
+    }
+
+    bool AnswerIsExtensible(const ObjectId &objId, ReturnStatus *rs,
+                            bool *result) {
+        return Answer::AnswerIsExtensible(objId, rs, result);
+    }
+    bool AnswerCall(const ObjectId &objId, const nsTArray<JSParam> &argv,
+                    ReturnStatus *rs, JSVariant *result,
+                    nsTArray<JSParam> *outparams) {
+        return Answer::AnswerCall(objId, argv, rs, result, outparams);
+    }
+    bool AnswerObjectClassIs(const ObjectId &objId, const uint32_t &classValue,
+                             bool *result) {
+        return Answer::AnswerObjectClassIs(objId, classValue, result);
+    }
+    bool AnswerClassName(const ObjectId &objId, nsString *result) {
+        return Answer::AnswerClassName(objId, result);
+    }
+
+    bool AnswerGetPropertyNames(const ObjectId &objId, const uint32_t &flags,
+                                ReturnStatus *rs, nsTArray<nsString> *names) {
+        return Answer::AnswerGetPropertyNames(objId, flags, rs, names);
+    }
+    bool AnswerInstanceOf(const ObjectId &objId, const JSIID &iid,
+                          ReturnStatus *rs, bool *instanceof) {
+        return Answer::AnswerInstanceOf(objId, iid, rs, instanceof);
+    }
+    bool AnswerDOMInstanceOf(const ObjectId &objId, const int &prototypeID, const int &depth,
+                             ReturnStatus *rs, bool *instanceof) {
+        return Answer::AnswerDOMInstanceOf(objId, prototypeID, depth, rs, instanceof);
+    }
+
+    bool RecvDropObject(const ObjectId &objId) {
+        return Answer::RecvDropObject(objId);
+    }
+
+    /*** Dummy call handlers ***/
+
+    bool SendDropObject(const ObjectId &objId) {
+        return Base::SendDropObject(objId);
+    }
+    bool CallPreventExtensions(const ObjectId &objId, ReturnStatus *rs) {
+        return Base::CallPreventExtensions(objId, rs);
+    }
+    bool CallGetPropertyDescriptor(const ObjectId &objId, const nsString &id,
+                                     ReturnStatus *rs,
+                                     PPropertyDescriptor *out) {
+        return Base::CallGetPropertyDescriptor(objId, id, rs, out);
+    }
+    bool CallGetOwnPropertyDescriptor(const ObjectId &objId,
+                                      const nsString &id,
+                                      ReturnStatus *rs,
+                                      PPropertyDescriptor *out) {
+        return Base::CallGetOwnPropertyDescriptor(objId, id, rs, out);
+    }
+    bool CallDefineProperty(const ObjectId &objId, const nsString &id,
+                            const PPropertyDescriptor &flags,
+                              ReturnStatus *rs) {
+        return Base::CallDefineProperty(objId, id, flags, rs);
+    }
+    bool CallDelete(const ObjectId &objId, const nsString &id,
+                    ReturnStatus *rs, bool *success) {
+        return Base::CallDelete(objId, id, rs, success);
+    }
+
+    bool CallHas(const ObjectId &objId, const nsString &id,
+                   ReturnStatus *rs, bool *bp) {
+        return Base::CallHas(objId, id, rs, bp);
+    }
+    bool CallHasOwn(const ObjectId &objId, const nsString &id,
+                    ReturnStatus *rs, bool *bp) {
+        return Base::CallHasOwn(objId, id, rs, bp);
+    }
+    bool CallGet(const ObjectId &objId, const ObjectId &receiverId,
+                 const nsString &id,
+                 ReturnStatus *rs, JSVariant *result) {
+        return Base::CallGet(objId, receiverId, id, rs, result);
+    }
+    bool CallSet(const ObjectId &objId, const ObjectId &receiverId,
+                 const nsString &id, const bool &strict,
+                 const JSVariant &value, ReturnStatus *rs, JSVariant *result) {
+        return Base::CallSet(objId, receiverId, id, strict, value, rs, result);
+    }
+
+    bool CallIsExtensible(const ObjectId &objId, ReturnStatus *rs,
+                          bool *result) {
+        return Base::CallIsExtensible(objId, rs, result);
+    }
+    bool CallCall(const ObjectId &objId, const nsTArray<JSParam> &argv,
+                  ReturnStatus *rs, JSVariant *result,
+                  nsTArray<JSParam> *outparams) {
+        return Base::CallCall(objId, argv, rs, result, outparams);
+    }
+    bool CallObjectClassIs(const ObjectId &objId, const uint32_t &classValue,
+                           bool *result) {
+        return Base::CallObjectClassIs(objId, classValue, result);
+    }
+    bool CallClassName(const ObjectId &objId, nsString *result) {
+        return Base::CallClassName(objId, result);
+    }
+
+    bool CallGetPropertyNames(const ObjectId &objId, const uint32_t &flags,
+                              ReturnStatus *rs, nsTArray<nsString> *names) {
+        return Base::CallGetPropertyNames(objId, flags, rs, names);
+    }
+    bool CallInstanceOf(const ObjectId &objId, const JSIID &iid,
+                        ReturnStatus *rs, bool *instanceof) {
+        return Base::CallInstanceOf(objId, iid, rs, instanceof);
+    }
+    bool CallDOMInstanceOf(const ObjectId &objId, const int &prototypeID, const int &depth,
+                           ReturnStatus *rs, bool *instanceof) {
+        return Base::CallDOMInstanceOf(objId, prototypeID, depth, rs, instanceof);
+    }
+
+    /* The following code is needed to suppress a bogus MSVC warning (C4250). */
+
+    virtual bool toObjectVariant(JSContext *cx, JSObject *obj, ObjectVariant *objVarp) {
+        return WrapperOwner::toObjectVariant(cx, obj, objVarp);
+    }
+    virtual JSObject *fromObjectVariant(JSContext *cx, ObjectVariant objVar) {
+        return WrapperOwner::fromObjectVariant(cx, objVar);
+    }
+};
+
+} // namespace jsipc
+} // namespace mozilla
+
+#endif
--- a/js/ipc/JavaScriptChild.cpp
+++ b/js/ipc/JavaScriptChild.cpp
@@ -14,642 +14,45 @@
 #include "nsCxPusher.h"
 
 using namespace JS;
 using namespace mozilla;
 using namespace mozilla::jsipc;
 
 using mozilla::AutoSafeJSContext;
 
-JavaScriptChild::JavaScriptChild(JSRuntime *rt)
-  : lastId_(0),
-    rt_(rt)
+static void
+FinalizeChild(JSFreeOp *fop, JSFinalizeStatus status, bool isCompartment, void *data)
 {
+    if (status == JSFINALIZE_GROUP_START) {
+        static_cast<JavaScriptChild *>(data)->finalize(fop);
+    }
 }
 
-static void
-Trace(JSTracer *trc, void *data)
+JavaScriptChild::JavaScriptChild(JSRuntime *rt)
+  : JavaScriptShared(rt),
+    JavaScriptBase<PJavaScriptChild>(rt)
 {
-    reinterpret_cast<JavaScriptChild *>(data)->trace(trc);
 }
 
 JavaScriptChild::~JavaScriptChild()
 {
-    JS_RemoveExtraGCRootsTracer(rt_, Trace, this);
-}
-
-void
-JavaScriptChild::trace(JSTracer *trc)
-{
-    objects_.trace(trc);
-    ids_.trace(trc);
+    JS_RemoveFinalizeCallback(rt_, FinalizeChild);
 }
 
 bool
 JavaScriptChild::init()
 {
-    if (!JavaScriptShared::init())
+    if (!WrapperOwner::init())
         return false;
-    if (!ids_.init())
+    if (!WrapperAnswer::init())
         return false;
 
-    JS_AddExtraGCRootsTracer(rt_, Trace, this);
-    return true;
-}
-
-bool
-JavaScriptChild::RecvDropObject(const ObjectId &objId)
-{
-    JSObject *obj = findObject(objId);
-    if (obj) {
-        ids_.remove(obj);
-        objects_.remove(objId);
-    }
-    return true;
-}
-
-bool
-JavaScriptChild::makeId(JSContext *cx, JSObject *obj, ObjectId *idp)
-{
-    if (!obj) {
-        *idp = 0;
-        return true;
-    }
-
-    ObjectId id = ids_.find(obj);
-    if (id) {
-        *idp = id;
-        return true;
-    }
-
-    id = ++lastId_;
-    if (id > MAX_CPOW_IDS) {
-        JS_ReportError(cx, "CPOW id limit reached");
-        return false;
-    }
-
-    id <<= OBJECT_EXTRA_BITS;
-    if (JS_ObjectIsCallable(cx, obj))
-        id |= OBJECT_IS_CALLABLE;
-
-    if (!objects_.add(id, obj))
-        return false;
-    if (!ids_.add(cx, obj, id))
-        return false;
-
-    *idp = id;
-    return true;
-}
-
-JSObject *
-JavaScriptChild::unwrap(JSContext *cx, ObjectId id)
-{
-    JSObject *obj = findObject(id);
-    MOZ_ASSERT(obj);
-    return obj;
-}
-
-bool
-JavaScriptChild::fail(JSContext *cx, ReturnStatus *rs)
-{
-    // By default, we set |undefined| unless we can get a more meaningful
-    // exception.
-    *rs = ReturnStatus(ReturnException(JSVariant(void_t())));
-
-    // Note we always return true from this function, since this propagates
-    // to the IPC code, and we don't want a JS failure to cause the death
-    // of the child process.
-
-    RootedValue exn(cx);
-    if (!JS_GetPendingException(cx, &exn))
-        return true;
-
-    // If we don't clear the pending exception, JS will try to wrap it as it
-    // leaves the current compartment. Since there is no previous compartment,
-    // that would crash.
-    JS_ClearPendingException(cx);
-
-    if (JS_IsStopIteration(exn)) {
-        *rs = ReturnStatus(ReturnStopIteration());
-        return true;
-    }
-
-    // If this fails, we still don't want to exit. Just return an invalid
-    // exception.
-    (void) toVariant(cx, exn, &rs->get_ReturnException().exn());
-    return true;
-}
-
-bool
-JavaScriptChild::ok(ReturnStatus *rs)
-{
-    *rs = ReturnStatus(ReturnSuccess());
+    JS_AddFinalizeCallback(rt_, FinalizeChild, this);
     return true;
 }
 
-bool
-JavaScriptChild::AnswerPreventExtensions(const ObjectId &objId, ReturnStatus *rs)
-{
-    AutoSafeJSContext cx;
-    JSAutoRequest request(cx);
-
-    RootedObject obj(cx, findObject(objId));
-    if (!obj)
-        return false;
-
-    JSAutoCompartment comp(cx, obj);
-    if (!JS_PreventExtensions(cx, obj))
-        return fail(cx, rs);
-
-    return ok(rs);
-}
-
-static void
-EmptyDesc(PPropertyDescriptor *desc)
-{
-    desc->objId() = 0;
-    desc->attrs() = 0;
-    desc->value() = void_t();
-    desc->getter() = 0;
-    desc->setter() = 0;
-}
-
-bool
-JavaScriptChild::AnswerGetPropertyDescriptor(const ObjectId &objId, const nsString &id,
-                                             ReturnStatus *rs, PPropertyDescriptor *out)
-{
-    AutoSafeJSContext cx;
-    JSAutoRequest request(cx);
-
-    EmptyDesc(out);
-
-    RootedObject obj(cx, findObject(objId));
-    if (!obj)
-        return false;
-
-    JSAutoCompartment comp(cx, obj);
-
-    RootedId internedId(cx);
-    if (!convertGeckoStringToId(cx, id, &internedId))
-        return fail(cx, rs);
-
-    Rooted<JSPropertyDescriptor> desc(cx);
-    if (!JS_GetPropertyDescriptorById(cx, obj, internedId, &desc))
-        return fail(cx, rs);
-
-    if (!desc.object())
-        return ok(rs);
-
-    if (!fromDescriptor(cx, desc, out))
-        return fail(cx, rs);
-
-    return ok(rs);
-}
-
-bool
-JavaScriptChild::AnswerGetOwnPropertyDescriptor(const ObjectId &objId, const nsString &id,
-                                                ReturnStatus *rs, PPropertyDescriptor *out)
-{
-    AutoSafeJSContext cx;
-    JSAutoRequest request(cx);
-
-    EmptyDesc(out);
-
-    RootedObject obj(cx, findObject(objId));
-    if (!obj)
-        return false;
-
-    JSAutoCompartment comp(cx, obj);
-
-    RootedId internedId(cx);
-    if (!convertGeckoStringToId(cx, id, &internedId))
-        return fail(cx, rs);
-
-    Rooted<JSPropertyDescriptor> desc(cx);
-    if (!JS_GetPropertyDescriptorById(cx, obj, internedId, &desc))
-        return fail(cx, rs);
-
-    if (desc.object() != obj)
-        return ok(rs);
-
-    if (!fromDescriptor(cx, desc, out))
-        return fail(cx, rs);
-
-    return ok(rs);
-}
-
-bool
-JavaScriptChild::AnswerDefineProperty(const ObjectId &objId, const nsString &id,
-                                      const PPropertyDescriptor &descriptor, ReturnStatus *rs)
-{
-    AutoSafeJSContext cx;
-    JSAutoRequest request(cx);
-
-    RootedObject obj(cx, findObject(objId));
-    if (!obj)
-        return false;
-
-    JSAutoCompartment comp(cx, obj);
-
-    RootedId internedId(cx);
-    if (!convertGeckoStringToId(cx, id, &internedId))
-        return fail(cx, rs);
-
-    Rooted<JSPropertyDescriptor> desc(cx);
-    if (!toDescriptor(cx, descriptor, &desc))
-        return false;
-
-    if (!js::CheckDefineProperty(cx, obj, internedId, desc.value(), desc.attributes(),
-                                 desc.getter(), desc.setter()))
-    {
-        return fail(cx, rs);
-    }
-
-    if (!JS_DefinePropertyById(cx, obj, internedId, desc.value(), desc.attributes(),
-                               desc.getter(), desc.setter()))
-    {
-        return fail(cx, rs);
-    }
-
-    return ok(rs);
-}
-
-bool
-JavaScriptChild::AnswerDelete(const ObjectId &objId, const nsString &id, ReturnStatus *rs,
-                              bool *success)
-{
-    AutoSafeJSContext cx;
-    JSAutoRequest request(cx);
-
-    *success = false;
-
-    RootedObject obj(cx, findObject(objId));
-    if (!obj)
-        return false;
-
-    JSAutoCompartment comp(cx, obj);
-
-    RootedId internedId(cx);
-    if (!convertGeckoStringToId(cx, id, &internedId))
-        return fail(cx, rs);
-
-    if (!JS_DeletePropertyById2(cx, obj, internedId, success))
-        return fail(cx, rs);
-
-    return ok(rs);
-}
-
-bool
-JavaScriptChild::AnswerHas(const ObjectId &objId, const nsString &id, ReturnStatus *rs, bool *bp)
-{
-    AutoSafeJSContext cx;
-    JSAutoRequest request(cx);
-
-    *bp = false;
-
-    RootedObject obj(cx, findObject(objId));
-    if (!obj)
-        return false;
-
-    JSAutoCompartment comp(cx, obj);
-
-    RootedId internedId(cx);
-    if (!convertGeckoStringToId(cx, id, &internedId))
-        return fail(cx, rs);
-
-    bool found;
-    if (!JS_HasPropertyById(cx, obj, internedId, &found))
-        return fail(cx, rs);
-    *bp = !!found;
-
-    return ok(rs);
-}
-
-bool
-JavaScriptChild::AnswerHasOwn(const ObjectId &objId, const nsString &id, ReturnStatus *rs, bool *bp)
-{
-    AutoSafeJSContext cx;
-    JSAutoRequest request(cx);
-
-    *bp = false;
-
-    RootedObject obj(cx, findObject(objId));
-    if (!obj)
-        return false;
-
-    JSAutoCompartment comp(cx, obj);
-
-    RootedId internedId(cx);
-    if (!convertGeckoStringToId(cx, id, &internedId))
-        return fail(cx, rs);
-
-    Rooted<JSPropertyDescriptor> desc(cx);
-    if (!JS_GetPropertyDescriptorById(cx, obj, internedId, &desc))
-        return fail(cx, rs);
-    *bp = (desc.object() == obj);
-
-    return ok(rs);
-}
-
-bool
-JavaScriptChild::AnswerGet(const ObjectId &objId, const ObjectId &receiverId, const nsString &id,
-                           ReturnStatus *rs, JSVariant *result)
-{
-    AutoSafeJSContext cx;
-    JSAutoRequest request(cx);
-
-    // The outparam will be written to the buffer, so it must be set even if
-    // the parent won't read it.
-    *result = void_t();
-
-    RootedObject obj(cx, findObject(objId));
-    if (!obj)
-        return false;
-
-    RootedObject receiver(cx, findObject(receiverId));
-    if (!receiver)
-        return false;
-
-    JSAutoCompartment comp(cx, obj);
-
-    RootedId internedId(cx);
-    if (!convertGeckoStringToId(cx, id, &internedId))
-        return fail(cx, rs);
-
-    JS::RootedValue val(cx);
-    if (!JS_ForwardGetPropertyTo(cx, obj, internedId, receiver, &val))
-        return fail(cx, rs);
-
-    if (!toVariant(cx, val, result))
-        return fail(cx, rs);
-
-    return ok(rs);
-}
-
-bool
-JavaScriptChild::AnswerSet(const ObjectId &objId, const ObjectId &receiverId, const nsString &id,
-                           const bool &strict, const JSVariant &value, ReturnStatus *rs,
-                           JSVariant *result)
+void
+JavaScriptChild::finalize(JSFreeOp *fop)
 {
-    AutoSafeJSContext cx;
-    JSAutoRequest request(cx);
-
-    // The outparam will be written to the buffer, so it must be set even if
-    // the parent won't read it.
-    *result = void_t();
-
-    RootedObject obj(cx, findObject(objId));
-    if (!obj)
-        return false;
-
-    RootedObject receiver(cx, findObject(receiverId));
-    if (!receiver)
-        return false;
-
-    JSAutoCompartment comp(cx, obj);
-
-    RootedId internedId(cx);
-    if (!convertGeckoStringToId(cx, id, &internedId))
-        return fail(cx, rs);
-
-    MOZ_ASSERT(obj == receiver);
-
-    RootedValue val(cx);
-    if (!toValue(cx, value, &val))
-        return fail(cx, rs);
-
-    if (!JS_SetPropertyById(cx, obj, internedId, val))
-        return fail(cx, rs);
-
-    if (!toVariant(cx, val, result))
-        return fail(cx, rs);
-
-    return ok(rs);
-}
-
-bool
-JavaScriptChild::AnswerIsExtensible(const ObjectId &objId, ReturnStatus *rs, bool *result)
-{
-    AutoSafeJSContext cx;
-    JSAutoRequest request(cx);
-
-    *result = false;
-
-    RootedObject obj(cx, findObject(objId));
-    if (!obj)
-        return false;
-
-    JSAutoCompartment comp(cx, obj);
-
-    bool extensible;
-    if (!JS_IsExtensible(cx, obj, &extensible))
-        return fail(cx, rs);
-
-    *result = !!extensible;
-    return ok(rs);
+    objects_.finalize(fop);
+    objectIds_.finalize(fop);
 }
-
-bool
-JavaScriptChild::AnswerCall(const ObjectId &objId, const nsTArray<JSParam> &argv, ReturnStatus *rs,
-                            JSVariant *result, nsTArray<JSParam> *outparams)
-{
-    AutoSafeJSContext cx;
-    JSAutoRequest request(cx);
-
-    // The outparam will be written to the buffer, so it must be set even if
-    // the parent won't read it.
-    *result = void_t();
-
-    RootedObject obj(cx, findObject(objId));
-    if (!obj)
-        return false;
-
-    MOZ_ASSERT(argv.Length() >= 2);
-
-    RootedValue objv(cx);
-    if (!toValue(cx, argv[0], &objv))
-        return fail(cx, rs);
-
-    JSAutoCompartment comp(cx, &objv.toObject());
-
-    *result = JSVariant(void_t());
-
-    AutoValueVector vals(cx);
-    AutoValueVector outobjects(cx);
-    for (size_t i = 0; i < argv.Length(); i++) {
-        if (argv[i].type() == JSParam::Tvoid_t) {
-            // This is an outparam.
-            JSCompartment *compartment = js::GetContextCompartment(cx);
-            RootedObject global(cx, JS_GetGlobalForCompartmentOrNull(cx, compartment));
-            RootedObject obj(cx, xpc::NewOutObject(cx, global));
-            if (!obj)
-                return fail(cx, rs);
-            if (!outobjects.append(ObjectValue(*obj)))
-                return fail(cx, rs);
-            if (!vals.append(ObjectValue(*obj)))
-                return fail(cx, rs);
-        } else {
-            RootedValue v(cx);
-            if (!toValue(cx, argv[i].get_JSVariant(), &v))
-                return fail(cx, rs);
-            if (!vals.append(v))
-                return fail(cx, rs);
-        }
-    }
-
-    RootedValue rval(cx);
-    {
-        AutoSaveContextOptions asco(cx);
-        ContextOptionsRef(cx).setDontReportUncaught(true);
-
-        HandleValueArray args = HandleValueArray::subarray(vals, 2, vals.length() - 2);
-        bool success = JS::Call(cx, vals[1], vals[0], args, &rval);
-        if (!success)
-            return fail(cx, rs);
-    }
-
-    if (!toVariant(cx, rval, result))
-        return fail(cx, rs);
-
-    // Prefill everything with a dummy jsval.
-    for (size_t i = 0; i < outobjects.length(); i++)
-        outparams->AppendElement(JSParam(void_t()));
-
-    // Go through each argument that was an outparam, retrieve the "value"
-    // field, and add it to a temporary list. We need to do this separately
-    // because the outparams vector is not rooted.
-    vals.clear();
-    for (size_t i = 0; i < outobjects.length(); i++) {
-        RootedObject obj(cx, &outobjects[i].toObject());
-
-        RootedValue v(cx);
-        bool found;
-        if (JS_HasProperty(cx, obj, "value", &found)) {
-            if (!JS_GetProperty(cx, obj, "value", &v))
-                return fail(cx, rs);
-        } else {
-            v = UndefinedValue();
-        }
-        if (!vals.append(v))
-            return fail(cx, rs);
-    }
-
-    // Copy the outparams. If any outparam is already set to a void_t, we
-    // treat this as the outparam never having been set.
-    for (size_t i = 0; i < vals.length(); i++) {
-        JSVariant variant;
-        if (!toVariant(cx, vals[i], &variant))
-            return fail(cx, rs);
-        outparams->ReplaceElementAt(i, JSParam(variant));
-    }
-
-    return ok(rs);
-}
-
-bool
-JavaScriptChild::AnswerObjectClassIs(const ObjectId &objId, const uint32_t &classValue,
-                                     bool *result)
-{
-    AutoSafeJSContext cx;
-    JSAutoRequest request(cx);
-
-    RootedObject obj(cx, findObject(objId));
-    if (!obj)
-        return false;
-
-    JSAutoCompartment comp(cx, obj);
-
-    *result = js_ObjectClassIs(cx, obj, (js::ESClassValue)classValue);
-    return true;
-}
-
-bool
-JavaScriptChild::AnswerClassName(const ObjectId &objId, nsString *name)
-{
-    AutoSafeJSContext cx;
-    JSAutoRequest request(cx);
-
-    RootedObject obj(cx, findObject(objId));
-    if (!obj)
-        return false;
-
-    JSAutoCompartment comp(cx, obj);
-
-    *name = NS_ConvertASCIItoUTF16(js_ObjectClassName(cx, obj));
-    return true;
-}
-
-bool
-JavaScriptChild::AnswerGetPropertyNames(const ObjectId &objId, const uint32_t &flags,
-                                        ReturnStatus *rs, nsTArray<nsString> *names)
-{
-    AutoSafeJSContext cx;
-    JSAutoRequest request(cx);
-
-    RootedObject obj(cx, findObject(objId));
-    if (!obj)
-        return false;
-
-    JSAutoCompartment comp(cx, obj);
-
-    AutoIdVector props(cx);
-    if (!js::GetPropertyNames(cx, obj, flags, &props))
-        return fail(cx, rs);
-
-    for (size_t i = 0; i < props.length(); i++) {
-        nsString name;
-        if (!convertIdToGeckoString(cx, props[i], &name))
-            return fail(cx, rs);
-
-        names->AppendElement(name);
-    }
-
-    return ok(rs);
-}
-
-bool
-JavaScriptChild::AnswerInstanceOf(const ObjectId &objId, const JSIID &iid, ReturnStatus *rs,
-                                  bool *instanceof)
-{
-    AutoSafeJSContext cx;
-    JSAutoRequest request(cx);
-
-    *instanceof = false;
-
-    RootedObject obj(cx, findObject(objId));
-    if (!obj)
-        return false;
-
-    JSAutoCompartment comp(cx, obj);
-
-    nsID nsiid;
-    ConvertID(iid, &nsiid);
-
-    nsresult rv = xpc::HasInstance(cx, obj, &nsiid, instanceof);
-    if (rv != NS_OK)
-        return fail(cx, rs);
-
-    return ok(rs);
-}
-
-bool
-JavaScriptChild::AnswerDOMInstanceOf(const ObjectId &objId, const int &prototypeID,
-                                     const int &depth,
-                                     ReturnStatus *rs, bool *instanceof)
-{
-    AutoSafeJSContext cx;
-    JSAutoRequest request(cx);
-
-    *instanceof = false;
-
-    RootedObject obj(cx, findObject(objId));
-    if (!obj)
-        return false;
-
-    JSAutoCompartment comp(cx, obj);
-
-    bool tmp;
-    if (!mozilla::dom::InterfaceHasInstance(cx, prototypeID, depth, obj, &tmp))
-        return fail(cx, rs);
-    *instanceof = tmp;
-
-    return ok(rs);
-}
--- a/js/ipc/JavaScriptChild.h
+++ b/js/ipc/JavaScriptChild.h
@@ -3,86 +3,34 @@
  *
  * This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #ifndef mozilla_jsipc_JavaScriptChild_h_
 #define mozilla_jsipc_JavaScriptChild_h_
 
-#include "JavaScriptShared.h"
+#include "JavaScriptBase.h"
 #include "mozilla/jsipc/PJavaScriptChild.h"
 
 namespace mozilla {
 namespace jsipc {
 
-class JavaScriptChild
-  : public PJavaScriptChild,
-    public JavaScriptShared
+class JavaScriptChild : public JavaScriptBase<PJavaScriptChild>
 {
   public:
     JavaScriptChild(JSRuntime *rt);
-    ~JavaScriptChild();
+    virtual ~JavaScriptChild();
 
     bool init();
-    void trace(JSTracer *trc);
-
-    bool RecvDropObject(const ObjectId &objId) MOZ_OVERRIDE;
-
-    bool AnswerPreventExtensions(const ObjectId &objId, ReturnStatus *rs) MOZ_OVERRIDE;
-    bool AnswerGetPropertyDescriptor(const ObjectId &objId, const nsString &id,
-                                     ReturnStatus *rs,
-                                     PPropertyDescriptor *out) MOZ_OVERRIDE;
-    bool AnswerGetOwnPropertyDescriptor(const ObjectId &objId,
-                                        const nsString &id,
-                                        ReturnStatus *rs,
-                                        PPropertyDescriptor *out) MOZ_OVERRIDE;
-    bool AnswerDefineProperty(const ObjectId &objId, const nsString &id,
-                              const PPropertyDescriptor &flags,
-                              ReturnStatus *rs) MOZ_OVERRIDE;
-    bool AnswerDelete(const ObjectId &objId, const nsString &id,
-                      ReturnStatus *rs, bool *success) MOZ_OVERRIDE;
+    void finalize(JSFreeOp *fop);
 
-    bool AnswerHas(const ObjectId &objId, const nsString &id,
-                       ReturnStatus *rs, bool *bp) MOZ_OVERRIDE;
-    bool AnswerHasOwn(const ObjectId &objId, const nsString &id,
-                          ReturnStatus *rs, bool *bp) MOZ_OVERRIDE;
-    bool AnswerGet(const ObjectId &objId, const ObjectId &receiverId,
-                       const nsString &id,
-                       ReturnStatus *rs, JSVariant *result) MOZ_OVERRIDE;
-    bool AnswerSet(const ObjectId &objId, const ObjectId &receiverId,
-                   const nsString &id, const bool &strict,
-                   const JSVariant &value, ReturnStatus *rs, JSVariant *result) MOZ_OVERRIDE;
-
-    bool AnswerIsExtensible(const ObjectId &objId, ReturnStatus *rs,
-                            bool *result) MOZ_OVERRIDE;
-    bool AnswerCall(const ObjectId &objId, const nsTArray<JSParam> &argv,
-                    ReturnStatus *rs, JSVariant *result,
-                    nsTArray<JSParam> *outparams) MOZ_OVERRIDE;
-    bool AnswerObjectClassIs(const ObjectId &objId, const uint32_t &classValue,
-                             bool *result) MOZ_OVERRIDE;
-    bool AnswerClassName(const ObjectId &objId, nsString *result) MOZ_OVERRIDE;
-
-    bool AnswerGetPropertyNames(const ObjectId &objId, const uint32_t &flags,
-                                ReturnStatus *rs, nsTArray<nsString> *names) MOZ_OVERRIDE;
-    bool AnswerInstanceOf(const ObjectId &objId, const JSIID &iid,
-                          ReturnStatus *rs, bool *instanceof) MOZ_OVERRIDE;
-    bool AnswerDOMInstanceOf(const ObjectId &objId, const int &prototypeID, const int &depth,
-                             ReturnStatus *rs, bool *instanceof) MOZ_OVERRIDE;
-
-  protected:
-    JSObject *unwrap(JSContext *cx, ObjectId id);
+    void drop(JSObject *obj);
 
   private:
-    bool makeId(JSContext *cx, JSObject *obj, ObjectId *idp);
     bool fail(JSContext *cx, ReturnStatus *rs);
     bool ok(ReturnStatus *rs);
-
-  private:
-    ObjectId lastId_;
-    JSRuntime *rt_;
-    ObjectIdCache ids_;
 };
 
 } // mozilla
 } // jsipc
 
 #endif
--- a/js/ipc/JavaScriptParent.cpp
+++ b/js/ipc/JavaScriptParent.cpp
@@ -16,691 +16,48 @@
 #include "mozilla/Casting.h"
 
 using namespace js;
 using namespace JS;
 using namespace mozilla;
 using namespace mozilla::jsipc;
 using namespace mozilla::dom;
 
-JavaScriptParent::JavaScriptParent()
-  : refcount_(1),
-    inactive_(false)
+static void
+TraceParent(JSTracer *trc, void *data)
+{
+    static_cast<JavaScriptParent *>(data)->trace(trc);
+}
+
+JavaScriptParent::JavaScriptParent(JSRuntime *rt)
+  : JavaScriptShared(rt),
+    JavaScriptBase<PJavaScriptParent>(rt)
 {
 }
 
-static inline JavaScriptParent *
-ParentOf(JSObject *obj)
-{
-    MOZ_ASSERT(JavaScriptParent::IsCPOW(obj));
-    return reinterpret_cast<JavaScriptParent *>(GetProxyExtra(obj, 0).toPrivate());
-}
-
-ObjectId
-JavaScriptParent::idOf(JSObject *obj)
-{
-    MOZ_ASSERT(JavaScriptParent::IsCPOW(obj));
-
-    Value v = GetProxyExtra(obj, 1);
-    MOZ_ASSERT(v.isDouble());
-
-    ObjectId objId = BitwiseCast<uint64_t>(v.toDouble());
-    MOZ_ASSERT(findObject(objId) == obj);
-    MOZ_ASSERT(objId);
-
-    return objId;
-}
-
-int sCPOWProxyHandler;
-
-class CPOWProxyHandler : public BaseProxyHandler
-{
-  public:
-    CPOWProxyHandler()
-      : BaseProxyHandler(&sCPOWProxyHandler) {}
-    virtual ~CPOWProxyHandler() {}
-
-    virtual bool finalizeInBackground(Value priv) MOZ_OVERRIDE {
-        return false;
-    }
-
-    virtual bool preventExtensions(JSContext *cx, HandleObject proxy) MOZ_OVERRIDE;
-    virtual bool getPropertyDescriptor(JSContext *cx, HandleObject proxy, HandleId id,
-                                       MutableHandle<JSPropertyDescriptor> desc) MOZ_OVERRIDE;
-    virtual bool getOwnPropertyDescriptor(JSContext *cx, HandleObject proxy, HandleId id,
-                                          MutableHandle<JSPropertyDescriptor> desc) MOZ_OVERRIDE;
-    virtual bool defineProperty(JSContext *cx, HandleObject proxy, HandleId id,
-                                MutableHandle<JSPropertyDescriptor> desc) MOZ_OVERRIDE;
-    virtual bool getOwnPropertyNames(JSContext *cx, HandleObject proxy,
-                                     AutoIdVector &props) MOZ_OVERRIDE;
-    virtual bool delete_(JSContext *cx, HandleObject proxy, HandleId id, bool *bp) MOZ_OVERRIDE;
-    virtual bool enumerate(JSContext *cx, HandleObject proxy, AutoIdVector &props) MOZ_OVERRIDE;
-
-    virtual bool has(JSContext *cx, HandleObject proxy, HandleId id, bool *bp) MOZ_OVERRIDE;
-    virtual bool hasOwn(JSContext *cx, HandleObject proxy, HandleId id, bool *bp) MOZ_OVERRIDE;
-    virtual bool get(JSContext *cx, HandleObject proxy, HandleObject receiver,
-                     HandleId id, MutableHandleValue vp) MOZ_OVERRIDE;
-    virtual bool set(JSContext *cx, JS::HandleObject proxy, JS::HandleObject receiver,
-                     JS::HandleId id, bool strict, JS::MutableHandleValue vp) MOZ_OVERRIDE;
-    virtual bool keys(JSContext *cx, HandleObject proxy, AutoIdVector &props) MOZ_OVERRIDE;
-
-    virtual bool isExtensible(JSContext *cx, HandleObject proxy, bool *extensible) MOZ_OVERRIDE;
-    virtual bool call(JSContext *cx, HandleObject proxy, const CallArgs &args) MOZ_OVERRIDE;
-    virtual bool objectClassIs(HandleObject obj, js::ESClassValue classValue, JSContext *cx) MOZ_OVERRIDE;
-    virtual const char* className(JSContext *cx, HandleObject proxy) MOZ_OVERRIDE;
-    virtual void finalize(JSFreeOp *fop, JSObject *proxy) MOZ_OVERRIDE;
-
-    static CPOWProxyHandler singleton;
-};
-
-CPOWProxyHandler CPOWProxyHandler::singleton;
-
-#define FORWARD(call, args)                                             \
-    JavaScriptParent *parent = ParentOf(proxy);                         \
-    if (!parent->active()) {                                            \
-        JS_ReportError(cx, "cannot use a CPOW whose process is gone");  \
-        return false;                                                   \
-    }                                                                   \
-    return parent->call args;
-
-bool
-CPOWProxyHandler::preventExtensions(JSContext *cx, HandleObject proxy)
-{
-    FORWARD(preventExtensions, (cx, proxy));
-}
-
-bool
-JavaScriptParent::preventExtensions(JSContext *cx, HandleObject proxy)
-{
-    ObjectId objId = idOf(proxy);
-
-    ReturnStatus status;
-    if (!CallPreventExtensions(objId, &status))
-        return ipcfail(cx);
-
-    return ok(cx, status);
-}
-
-bool
-CPOWProxyHandler::getPropertyDescriptor(JSContext *cx, HandleObject proxy, HandleId id,
-                                        MutableHandle<JSPropertyDescriptor> desc)
-{
-    FORWARD(getPropertyDescriptor, (cx, proxy, id, desc));
-}
-
-bool
-JavaScriptParent::getPropertyDescriptor(JSContext *cx, HandleObject proxy, HandleId id,
-                                        MutableHandle<JSPropertyDescriptor> desc)
-{
-    ObjectId objId = idOf(proxy);
-
-    nsString idstr;
-    if (!convertIdToGeckoString(cx, id, &idstr))
-        return false;
-
-    ReturnStatus status;
-    PPropertyDescriptor result;
-    if (!CallGetPropertyDescriptor(objId, idstr, &status, &result))
-        return ipcfail(cx);
-    if (!ok(cx, status))
-        return false;
-
-    return toDescriptor(cx, result, desc);
-}
-
-bool
-CPOWProxyHandler::getOwnPropertyDescriptor(JSContext *cx, HandleObject proxy,
-                                           HandleId id, MutableHandle<JSPropertyDescriptor> desc)
-{
-    FORWARD(getOwnPropertyDescriptor, (cx, proxy, id, desc));
-}
-
-bool
-JavaScriptParent::getOwnPropertyDescriptor(JSContext *cx, HandleObject proxy, HandleId id,
-                                           MutableHandle<JSPropertyDescriptor> desc)
-{
-    ObjectId objId = idOf(proxy);
-
-    nsString idstr;
-    if (!convertIdToGeckoString(cx, id, &idstr))
-        return false;
-
-    ReturnStatus status;
-    PPropertyDescriptor result;
-    if (!CallGetOwnPropertyDescriptor(objId, idstr, &status, &result))
-        return ipcfail(cx);
-    if (!ok(cx, status))
-        return false;
-
-    return toDescriptor(cx, result, desc);
-}
-
-bool
-CPOWProxyHandler::defineProperty(JSContext *cx, HandleObject proxy, HandleId id,
-                                 MutableHandle<JSPropertyDescriptor> desc)
-{
-    FORWARD(defineProperty, (cx, proxy, id, desc));
-}
-
-bool
-JavaScriptParent::defineProperty(JSContext *cx, HandleObject proxy, HandleId id,
-                                 MutableHandle<JSPropertyDescriptor> desc)
-{
-    ObjectId objId = idOf(proxy);
-
-    nsString idstr;
-    if (!convertIdToGeckoString(cx, id, &idstr))
-        return false;
-
-    PPropertyDescriptor descriptor;
-    if (!fromDescriptor(cx, desc, &descriptor))
-        return false;
-
-    ReturnStatus status;
-    if (!CallDefineProperty(objId, idstr, descriptor, &status))
-        return ipcfail(cx);
-
-    return ok(cx, status);
-}
-
-bool
-CPOWProxyHandler::getOwnPropertyNames(JSContext *cx, HandleObject proxy, AutoIdVector &props)
-{
-    FORWARD(getOwnPropertyNames, (cx, proxy, props));
-}
-
-bool
-JavaScriptParent::getOwnPropertyNames(JSContext *cx, HandleObject proxy, AutoIdVector &props)
-{
-    return getPropertyNames(cx, proxy, JSITER_OWNONLY | JSITER_HIDDEN, props);
-}
-
-bool
-CPOWProxyHandler::delete_(JSContext *cx, HandleObject proxy, HandleId id, bool *bp)
-{
-    FORWARD(delete_, (cx, proxy, id, bp));
-}
-
-bool
-JavaScriptParent::delete_(JSContext *cx, HandleObject proxy, HandleId id, bool *bp)
-{
-    ObjectId objId = idOf(proxy);
-
-    nsString idstr;
-    if (!convertIdToGeckoString(cx, id, &idstr))
-        return false;
-
-    ReturnStatus status;
-    if (!CallDelete(objId, idstr, &status, bp))
-        return ipcfail(cx);
-
-    return ok(cx, status);
-}
-
-bool
-CPOWProxyHandler::enumerate(JSContext *cx, HandleObject proxy, AutoIdVector &props)
-{
-    FORWARD(enumerate, (cx, proxy, props));
-}
-
-bool
-JavaScriptParent::enumerate(JSContext *cx, HandleObject proxy, AutoIdVector &props)
-{
-    return getPropertyNames(cx, proxy, 0, props);
-}
-
-bool
-CPOWProxyHandler::has(JSContext *cx, HandleObject proxy, HandleId id, bool *bp)
-{
-    FORWARD(has, (cx, proxy, id, bp));
-}
-
-bool
-JavaScriptParent::has(JSContext *cx, HandleObject proxy, HandleId id, bool *bp)
+JavaScriptParent::~JavaScriptParent()
 {
-    ObjectId objId = idOf(proxy);
-
-    nsString idstr;
-    if (!convertIdToGeckoString(cx, id, &idstr))
-        return false;
-
-    ReturnStatus status;
-    if (!CallHas(objId, idstr, &status, bp))
-        return ipcfail(cx);
-
-    return ok(cx, status);
-}
-
-bool
-CPOWProxyHandler::hasOwn(JSContext *cx, HandleObject proxy, HandleId id, bool *bp)
-{
-    FORWARD(hasOwn, (cx, proxy, id, bp));
-}
-
-bool
-JavaScriptParent::hasOwn(JSContext *cx, HandleObject proxy, HandleId id, bool *bp)
-{
-    ObjectId objId = idOf(proxy);
-
-    nsString idstr;
-    if (!convertIdToGeckoString(cx, id, &idstr))
-        return false;
-
-    ReturnStatus status;
-    if (!CallHasOwn(objId, idstr, &status, bp))
-        return ipcfail(cx);
-
-    return !!ok(cx, status);
-}
-
-bool
-CPOWProxyHandler::get(JSContext *cx, HandleObject proxy, HandleObject receiver,
-                      HandleId id, MutableHandleValue vp)
-{
-    FORWARD(get, (cx, proxy, receiver, id, vp));
-}
-
-bool
-JavaScriptParent::get(JSContext *cx, HandleObject proxy, HandleObject receiver,
-                      HandleId id, MutableHandleValue vp)
-{
-    ObjectId objId = idOf(proxy);
-    ObjectId receiverId = idOf(receiver);
-
-    nsString idstr;
-    if (!convertIdToGeckoString(cx, id, &idstr))
-        return false;
-
-    JSVariant val;
-    ReturnStatus status;
-    if (!CallGet(objId, receiverId, idstr, &status, &val))
-        return ipcfail(cx);
-
-    if (!ok(cx, status))
-        return false;
-
-    return toValue(cx, val, vp);
-}
-
-bool
-CPOWProxyHandler::set(JSContext *cx, JS::HandleObject proxy, JS::HandleObject receiver,
-                      JS::HandleId id, bool strict, JS::MutableHandleValue vp)
-{
-    FORWARD(set, (cx, proxy, receiver, id, strict, vp));
-}
-
-bool
-JavaScriptParent::set(JSContext *cx, JS::HandleObject proxy, JS::HandleObject receiver,
-                      JS::HandleId id, bool strict, JS::MutableHandleValue vp)
-{
-    ObjectId objId = idOf(proxy);
-    ObjectId receiverId = idOf(receiver);
-
-    nsString idstr;
-    if (!convertIdToGeckoString(cx, id, &idstr))
-        return false;
-
-    JSVariant val;
-    if (!toVariant(cx, vp, &val))
-        return false;
-
-    ReturnStatus status;
-    JSVariant result;
-    if (!CallSet(objId, receiverId, idstr, strict, val, &status, &result))
-        return ipcfail(cx);
-
-    if (!ok(cx, status))
-        return false;
-
-    return toValue(cx, result, vp);
-}
-
-bool
-CPOWProxyHandler::keys(JSContext *cx, HandleObject proxy, AutoIdVector &props)
-{
-    FORWARD(keys, (cx, proxy, props));
-}
-
-bool
-JavaScriptParent::keys(JSContext *cx, HandleObject proxy, AutoIdVector &props)
-{
-    return getPropertyNames(cx, proxy, JSITER_OWNONLY, props);
-}
-
-bool
-CPOWProxyHandler::isExtensible(JSContext *cx, HandleObject proxy, bool *extensible)
-{
-    FORWARD(isExtensible, (cx, proxy, extensible));
-}
-
-bool
-JavaScriptParent::isExtensible(JSContext *cx, HandleObject proxy, bool *extensible)
-{
-    ObjectId objId = idOf(proxy);
-
-    ReturnStatus status;
-    if (!CallIsExtensible(objId, &status, extensible))
-        return ipcfail(cx);
-
-    return ok(cx, status);
-}
-
-bool
-CPOWProxyHandler::call(JSContext *cx, HandleObject proxy, const CallArgs &args)
-{
-    FORWARD(call, (cx, proxy, args));
-}
-
-bool
-JavaScriptParent::call(JSContext *cx, HandleObject proxy, const CallArgs &args)
-{
-    ObjectId objId = idOf(proxy);
-
-    InfallibleTArray<JSParam> vals;
-    AutoValueVector outobjects(cx);
-
-    RootedValue v(cx);
-    for (size_t i = 0; i < args.length() + 2; i++) {
-        v = args.base()[i];
-        if (v.isObject()) {
-            RootedObject obj(cx, &v.toObject());
-            if (xpc::IsOutObject(cx, obj)) {
-                // Make sure it is not an in-out object.
-                bool found;
-                if (!JS_HasProperty(cx, obj, "value", &found))
-                    return false;
-                if (found) {
-                    JS_ReportError(cx, "in-out objects cannot be sent via CPOWs yet");
-                    return false;
-                }
-
-                vals.AppendElement(JSParam(void_t()));
-                if (!outobjects.append(ObjectValue(*obj)))
-                    return false;
-                continue;
-            }
-        }
-        JSVariant val;
-        if (!toVariant(cx, v, &val))
-            return false;
-        vals.AppendElement(JSParam(val));
-    }
-
-    JSVariant result;
-    ReturnStatus status;
-    InfallibleTArray<JSParam> outparams;
-    if (!CallCall(objId, vals, &status, &result, &outparams))
-        return ipcfail(cx);
-    if (!ok(cx, status))
-        return false;
-
-    if (outparams.Length() != outobjects.length())
-        return ipcfail(cx);
-
-    RootedObject obj(cx);
-    for (size_t i = 0; i < outparams.Length(); i++) {
-        // Don't bother doing anything for outparams that weren't set.
-        if (outparams[i].type() == JSParam::Tvoid_t)
-            continue;
-
-        // Take the value the child process returned, and set it on the XPC
-        // object.
-        if (!toValue(cx, outparams[i], &v))
-            return false;
-
-        obj = &outobjects[i].toObject();
-        if (!JS_SetProperty(cx, obj, "value", v))
-            return false;
-    }
-
-    if (!toValue(cx, result, args.rval()))
-        return false;
-
-    return true;
-}
-
-
-bool
-CPOWProxyHandler::objectClassIs(HandleObject proxy, js::ESClassValue classValue, JSContext *cx)
-{
-    FORWARD(objectClassIs, (cx, proxy, classValue));
-}
-
-bool
-JavaScriptParent::objectClassIs(JSContext *cx, HandleObject proxy, js::ESClassValue classValue)
-{
-    ObjectId objId = idOf(proxy);
-
-    // This function is assumed infallible, so we just return false if the IPC
-    // channel fails.
-    bool result;
-    if (!CallObjectClassIs(objId, classValue, &result))
-        return false;
-
-    return result;
-}
-
-const char *
-CPOWProxyHandler::className(JSContext *cx, HandleObject proxy)
-{
-    JavaScriptParent *parent = ParentOf(proxy);
-    if (!parent->active())
-        return "<dead CPOW>";
-    return parent->className(cx, proxy);
-}
-
-const char *
-JavaScriptParent::className(JSContext *cx, HandleObject proxy)
-{
-    ObjectId objId = idOf(proxy);
-
-    nsString name;
-    if (!CallClassName(objId, &name))
-        return "<error>";
-
-    return ToNewCString(name);
-}
-
-void
-CPOWProxyHandler::finalize(JSFreeOp *fop, JSObject *proxy)
-{
-    ParentOf(proxy)->drop(proxy);
-}
-
-void
-JavaScriptParent::drop(JSObject *obj)
-{
-    ObjectId objId = idOf(obj);
-
-    objects_.remove(objId);
-    if (!inactive_ && !SendDropObject(objId))
-        (void)0;
-    decref();
+    JS_RemoveExtraGCRootsTracer(rt_, TraceParent, this);
 }
 
 bool
 JavaScriptParent::init()
 {
-    if (!JavaScriptShared::init())
+    if (!WrapperOwner::init())
         return false;
 
-    return true;
-}
-
-bool
-JavaScriptParent::makeId(JSContext *cx, JSObject *obj, ObjectId *idp)
-{
-    obj = js::CheckedUnwrap(obj, false);
-    if (!obj || !IsProxy(obj) || GetProxyHandler(obj) != &CPOWProxyHandler::singleton) {
-        JS_ReportError(cx, "cannot ipc non-cpow object");
-        return false;
-    }
-
-    *idp = idOf(obj);
-    return true;
-}
-
-bool
-JavaScriptParent::getPropertyNames(JSContext *cx, HandleObject proxy, uint32_t flags, AutoIdVector &props)
-{
-    ObjectId objId = idOf(proxy);
-
-    ReturnStatus status;
-    InfallibleTArray<nsString> names;
-    if (!CallGetPropertyNames(objId, flags, &status, &names))
-        return ipcfail(cx);
-    if (!ok(cx, status))
-        return false;
-
-    for (size_t i = 0; i < names.Length(); i++) {
-        RootedId name(cx);
-        if (!convertGeckoStringToId(cx, names[i], &name))
-            return false;
-        if (!props.append(name))
-            return false;
-    }
-
+    JS_AddExtraGCRootsTracer(rt_, TraceParent, this);
     return true;
 }
 
-JSObject *
-JavaScriptParent::unwrap(JSContext *cx, ObjectId objId)
-{
-    RootedObject obj(cx, findObject(objId));
-    if (obj) {
-        if (!JS_WrapObject(cx, &obj))
-            return nullptr;
-        return obj;
-    }
-
-    if (objId > MAX_CPOW_IDS) {
-        JS_ReportError(cx, "unusable CPOW id");
-        return nullptr;
-    }
-
-    bool callable = !!(objId & OBJECT_IS_CALLABLE);
-
-    RootedObject global(cx, JS::CurrentGlobalOrNull(cx));
-
-    RootedValue v(cx, UndefinedValue());
-    ProxyOptions options;
-    options.selectDefaultClass(callable);
-    obj = NewProxyObject(cx,
-                         &CPOWProxyHandler::singleton,
-                         v,
-                         nullptr,
-                         global,
-                         options);
-    if (!obj)
-        return nullptr;
-
-    if (!objects_.add(objId, obj))
-        return nullptr;
-
-    // Incref once we know the decref will be called.
-    incref();
-
-    SetProxyExtra(obj, 0, PrivateValue(this));
-    SetProxyExtra(obj, 1, DoubleValue(BitwiseCast<double>(objId)));
-    return obj;
-}
-
-bool
-JavaScriptParent::ipcfail(JSContext *cx)
-{
-    JS_ReportError(cx, "child process crashed or timedout");
-    return false;
-}
-
-bool
-JavaScriptParent::ok(JSContext *cx, const ReturnStatus &status)
+void
+JavaScriptParent::trace(JSTracer *trc)
 {
-    if (status.type() == ReturnStatus::TReturnSuccess)
-        return true;
-
-    if (status.type() == ReturnStatus::TReturnStopIteration)
-        return JS_ThrowStopIteration(cx);
-
-    RootedValue exn(cx);
-    if (!toValue(cx, status.get_ReturnException().exn(), &exn))
-        return false;
-
-    JS_SetPendingException(cx, exn);
-    return false;
-}
-
-void
-JavaScriptParent::decref()
-{
-    refcount_--;
-    if (!refcount_)
-        delete this;
-}
-
-void
-JavaScriptParent::incref()
-{
-    refcount_++;
-}
-
-void
-JavaScriptParent::ActorDestroy(ActorDestroyReason why)
-{
-    inactive_ = true;
-}
-
-/* static */ bool
-JavaScriptParent::IsCPOW(JSObject *obj)
-{
-    return IsProxy(obj) && GetProxyHandler(obj) == &CPOWProxyHandler::singleton;
-}
-
-/* static */ nsresult
-JavaScriptParent::InstanceOf(JSObject *proxy, const nsID *id, bool *bp)
-{
-    JavaScriptParent *parent = ParentOf(proxy);
-    if (!parent->active())
-        return NS_ERROR_UNEXPECTED;
-    return parent->instanceOf(proxy, id, bp);
-}
-
-nsresult
-JavaScriptParent::instanceOf(JSObject *obj, const nsID *id, bool *bp)
-{
-    ObjectId objId = idOf(obj);
-
-    JSIID iid;
-    ConvertID(*id, &iid);
-
-    ReturnStatus status;
-    if (!CallInstanceOf(objId, iid, &status, bp))
-        return NS_ERROR_UNEXPECTED;
-
-    if (status.type() != ReturnStatus::TReturnSuccess)
-        return NS_ERROR_UNEXPECTED;
-
-    return NS_OK;
-}
-
-/* static */ bool
-JavaScriptParent::DOMInstanceOf(JSContext *cx, JSObject *proxy, int prototypeID, int depth, bool *bp)
-{
-    FORWARD(domInstanceOf, (cx, proxy, prototypeID, depth, bp));
-}
-
-bool
-JavaScriptParent::domInstanceOf(JSContext *cx, JSObject *obj, int prototypeID, int depth, bool *bp)
-{
-    ObjectId objId = idOf(obj);
-
-    ReturnStatus status;
-    if (!CallDOMInstanceOf(objId, prototypeID, depth, &status, bp))
-        return ipcfail(cx);
-
-    return ok(cx, status);
+    if (active())
+        objects_.trace(trc);
 }
 
 mozilla::ipc::IProtocol*
 JavaScriptParent::CloneProtocol(Channel* aChannel, ProtocolCloneContext* aCtx)
 {
     ContentParent *contentParent = aCtx->GetContentParent();
     nsAutoPtr<PJavaScriptParent> actor(contentParent->AllocPJavaScriptParent());
     if (!actor || !contentParent->RecvPJavaScriptConstructor(actor)) {
--- a/js/ipc/JavaScriptParent.h
+++ b/js/ipc/JavaScriptParent.h
@@ -3,108 +3,34 @@
  *
  * This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #ifndef mozilla_jsipc_JavaScriptParent__
 #define mozilla_jsipc_JavaScriptParent__
 
-#include "JavaScriptShared.h"
+#include "JavaScriptBase.h"
 #include "mozilla/jsipc/PJavaScriptParent.h"
-#include "js/Class.h"
-
-#ifdef XP_WIN
-#undef GetClassName
-#undef GetClassInfo
-#endif
 
 namespace mozilla {
 namespace jsipc {
 
-class JavaScriptParent
-  : public PJavaScriptParent,
-    public JavaScriptShared
+class JavaScriptParent : public JavaScriptBase<PJavaScriptParent>
 {
   public:
-    JavaScriptParent();
+    JavaScriptParent(JSRuntime *rt);
+    virtual ~JavaScriptParent();
 
     bool init();
-
-  public:
-    // Fundamental proxy traps. These are required.
-    // (The traps should be in the same order like js/src/jsproxy.h)
-    bool preventExtensions(JSContext *cx, JS::HandleObject proxy);
-    bool getPropertyDescriptor(JSContext *cx, JS::HandleObject proxy, JS::HandleId id,
-                               JS::MutableHandle<JSPropertyDescriptor> desc);
-    bool getOwnPropertyDescriptor(JSContext *cx, JS::HandleObject proxy, JS::HandleId id,
-                                  JS::MutableHandle<JSPropertyDescriptor> desc);
-    bool defineProperty(JSContext *cx, JS::HandleObject proxy, JS::HandleId id,
-                        JS::MutableHandle<JSPropertyDescriptor> desc);
-    bool getOwnPropertyNames(JSContext *cx, JS::HandleObject proxy, JS::AutoIdVector &props);
-    bool delete_(JSContext *cx, JS::HandleObject proxy, JS::HandleId id, bool *bp);
-    bool enumerate(JSContext *cx, JS::HandleObject proxy, JS::AutoIdVector &props);
-
-    // Derived proxy traps. Implementing these is useful for perfomance.
-    bool has(JSContext *cx, JS::HandleObject proxy, JS::HandleId id, bool *bp);
-    bool hasOwn(JSContext *cx, JS::HandleObject proxy, JS::HandleId id, bool *bp);
-    bool get(JSContext *cx, JS::HandleObject proxy, JS::HandleObject receiver,
-             JS::HandleId id, JS::MutableHandleValue vp);
-    bool set(JSContext *cx, JS::HandleObject proxy, JS::HandleObject receiver,
-             JS::HandleId id, bool strict, JS::MutableHandleValue vp);
-    bool keys(JSContext *cx, JS::HandleObject proxy, JS::AutoIdVector &props);
-    // We use "iterate" provided by the base class here.
-
-    // SpiderMonkey Extensions.
-    bool isExtensible(JSContext *cx, JS::HandleObject proxy, bool *extensible);
-    bool call(JSContext *cx, JS::HandleObject proxy, const JS::CallArgs &args);
-    bool objectClassIs(JSContext *cx, JS::HandleObject obj, js::ESClassValue classValue);
-    const char* className(JSContext *cx, JS::HandleObject proxy);
-
-    virtual void ActorDestroy(ActorDestroyReason why);
-
-    void decref();
-    void incref();
-
-    bool active() { return !inactive_; }
+    void trace(JSTracer *trc);
 
     void drop(JSObject *obj);
 
-    static bool IsCPOW(JSObject *obj);
-
-    static nsresult InstanceOf(JSObject *obj, const nsID *id, bool *bp);
-    nsresult instanceOf(JSObject *obj, const nsID *id, bool *bp);
-
-    /*
-     * Check that |obj| is a DOM wrapper whose prototype chain contains
-     * |prototypeID| at depth |depth|.
-     */
-    static bool DOMInstanceOf(JSContext *cx, JSObject *obj, int prototypeID, int depth, bool *bp);
-    bool domInstanceOf(JSContext *cx, JSObject *obj, int prototypeID, int depth, bool *bp);
-
     mozilla::ipc::IProtocol*
     CloneProtocol(Channel* aChannel, ProtocolCloneContext* aCtx) MOZ_OVERRIDE;
-
-  protected:
-    JSObject *unwrap(JSContext *cx, ObjectId objId);
-
-  private:
-    bool makeId(JSContext *cx, JSObject *obj, ObjectId *idp);
-    bool getPropertyNames(JSContext *cx, JS::HandleObject proxy, uint32_t flags,
-                          JS::AutoIdVector &props);
-    ObjectId idOf(JSObject *obj);
-
-    // Catastrophic IPC failure.
-    bool ipcfail(JSContext *cx);
-
-    // Check whether a return status is okay, and if not, propagate its error.
-    bool ok(JSContext *cx, const ReturnStatus &status);
-
-  private:
-    uintptr_t refcount_;
-    bool inactive_;
 };
 
 } // jsipc
 } // mozilla
 
 #endif // mozilla_jsipc_JavaScriptWrapper_h__
 
--- a/js/ipc/JavaScriptShared.cpp
+++ b/js/ipc/JavaScriptShared.cpp
@@ -2,141 +2,187 @@
  * vim: set ts=4 sw=4 et tw=80:
  *
  * This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "JavaScriptShared.h"
 #include "mozilla/dom/BindingUtils.h"
+#include "mozilla/dom/TabChild.h"
 #include "jsfriendapi.h"
 #include "xpcprivate.h"
 
 using namespace js;
 using namespace JS;
 using namespace mozilla;
 using namespace mozilla::jsipc;
 
-ObjectStore::ObjectStore()
+IdToObjectMap::IdToObjectMap()
   : table_(SystemAllocPolicy())
 {
 }
 
 bool
-ObjectStore::init()
+IdToObjectMap::init()
 {
+    if (table_.initialized())
+        return true;
     return table_.init(32);
 }
 
 void
-ObjectStore::trace(JSTracer *trc)
+IdToObjectMap::trace(JSTracer *trc)
 {
-    for (ObjectTable::Range r(table_.all()); !r.empty(); r.popFront()) {
+    for (Table::Range r(table_.all()); !r.empty(); r.popFront()) {
         DebugOnly<JSObject *> prior = r.front().value().get();
         JS_CallHeapObjectTracer(trc, &r.front().value(), "ipc-object");
         MOZ_ASSERT(r.front().value() == prior);
     }
 }
 
+void
+IdToObjectMap::finalize(JSFreeOp *fop)
+{
+    for (Table::Enum e(table_); !e.empty(); e.popFront()) {
+        DebugOnly<JSObject *> prior = e.front().value().get();
+        if (JS_IsAboutToBeFinalized(&e.front().value()))
+            e.removeFront();
+        else
+            MOZ_ASSERT(e.front().value() == prior);
+    }
+}
+
 JSObject *
-ObjectStore::find(ObjectId id)
+IdToObjectMap::find(ObjectId id)
 {
-    ObjectTable::Ptr p = table_.lookup(id);
+    Table::Ptr p = table_.lookup(id);
     if (!p)
         return nullptr;
     return p->value();
 }
 
 bool
-ObjectStore::add(ObjectId id, JSObject *obj)
+IdToObjectMap::add(ObjectId id, JSObject *obj)
 {
     return table_.put(id, obj);
 }
 
 void
-ObjectStore::remove(ObjectId id)
+IdToObjectMap::remove(ObjectId id)
 {
     table_.remove(id);
 }
 
-ObjectIdCache::ObjectIdCache()
+ObjectToIdMap::ObjectToIdMap()
   : table_(nullptr)
 {
 }
 
-ObjectIdCache::~ObjectIdCache()
+ObjectToIdMap::~ObjectToIdMap()
 {
     if (table_) {
-        dom::AddForDeferredFinalization<ObjectIdTable, nsAutoPtr>(table_);
+        dom::AddForDeferredFinalization<Table, nsAutoPtr>(table_);
         table_ = nullptr;
     }
 }
 
 bool
-ObjectIdCache::init()
+ObjectToIdMap::init()
 {
-    MOZ_ASSERT(!table_);
-    table_ = new ObjectIdTable(SystemAllocPolicy());
+    if (table_)
+        return true;
+
+    table_ = new Table(SystemAllocPolicy());
     return table_ && table_->init(32);
 }
 
 void
-ObjectIdCache::trace(JSTracer *trc)
+ObjectToIdMap::finalize(JSFreeOp *fop)
 {
-    for (ObjectIdTable::Range r(table_->all()); !r.empty(); r.popFront()) {
-        JSObject *obj = r.front().key();
-        JS_CallObjectTracer(trc, &obj, "ipc-id");
-        MOZ_ASSERT(obj == r.front().key());
+    for (Table::Enum e(*table_); !e.empty(); e.popFront()) {
+        JSObject *obj = e.front().key();
+        if (JS_IsAboutToBeFinalizedUnbarriered(&obj))
+            e.removeFront();
+        else
+            MOZ_ASSERT(obj == e.front().key());
     }
 }
 
 ObjectId
-ObjectIdCache::find(JSObject *obj)
+ObjectToIdMap::find(JSObject *obj)
 {
-    ObjectIdTable::Ptr p = table_->lookup(obj);
+    Table::Ptr p = table_->lookup(obj);
     if (!p)
         return 0;
     return p->value();
 }
 
 bool
-ObjectIdCache::add(JSContext *cx, JSObject *obj, ObjectId id)
+ObjectToIdMap::add(JSContext *cx, JSObject *obj, ObjectId id)
 {
     if (!table_->put(obj, id))
         return false;
     JS_StoreObjectPostBarrierCallback(cx, keyMarkCallback, obj, table_);
     return true;
 }
 
 /*
  * This function is called during minor GCs for each key in the HashMap that has
  * been moved.
  */
 /* static */ void
-ObjectIdCache::keyMarkCallback(JSTracer *trc, JSObject *key, void *data) {
-    ObjectIdTable* table = static_cast<ObjectIdTable*>(data);
+ObjectToIdMap::keyMarkCallback(JSTracer *trc, JSObject *key, void *data)
+{
+    Table *table = static_cast<Table*>(data);
     JSObject *prior = key;
     JS_CallObjectTracer(trc, &key, "ObjectIdCache::table_ key");
     table->rekeyIfMoved(prior, key);
 }
 
 void
-ObjectIdCache::remove(JSObject *obj)
+ObjectToIdMap::remove(JSObject *obj)
 {
     table_->remove(obj);
 }
 
+JavaScriptShared::JavaScriptShared(JSRuntime *rt)
+  : rt_(rt),
+    refcount_(1),
+    lastId_(0)
+{
+}
+
 bool
 JavaScriptShared::init()
 {
     if (!objects_.init())
         return false;
+    if (!cpows_.init())
+        return false;
+    if (!objectIds_.init())
+        return false;
+
     return true;
 }
 
+void
+JavaScriptShared::decref()
+{
+    refcount_--;
+    if (!refcount_)
+        delete this;
+}
+
+void
+JavaScriptShared::incref()
+{
+    refcount_++;
+}
+
 bool
 JavaScriptShared::convertIdToGeckoString(JSContext *cx, JS::HandleId id, nsString *to)
 {
     RootedValue idval(cx);
     if (!JS_IdToValue(cx, id, &idval))
         return false;
 
     RootedString str(cx, ToString(cx, idval));
@@ -161,47 +207,41 @@ JavaScriptShared::convertGeckoStringToId
     return JS_StringToId(cx, str, to);
 }
 
 bool
 JavaScriptShared::toVariant(JSContext *cx, JS::HandleValue from, JSVariant *to)
 {
     switch (JS_TypeOfValue(cx, from)) {
       case JSTYPE_VOID:
-        *to = void_t();
+        *to = UndefinedVariant();
         return true;
 
-      case JSTYPE_NULL:
-      {
-        *to = uint64_t(0);
-        return true;
-      }
-
       case JSTYPE_OBJECT:
       case JSTYPE_FUNCTION:
       {
         RootedObject obj(cx, from.toObjectOrNull());
         if (!obj) {
             MOZ_ASSERT(from == JSVAL_NULL);
-            *to = uint64_t(0);
+            *to = NullVariant();
             return true;
         }
 
         if (xpc_JSObjectIsID(cx, obj)) {
             JSIID iid;
             const nsID *id = xpc_JSObjectToID(cx, obj);
             ConvertID(*id, &iid);
             *to = iid;
             return true;
         }
 
-        ObjectId id;
-        if (!makeId(cx, obj, &id))
+        ObjectVariant objVar;
+        if (!toObjectVariant(cx, obj, &objVar))
             return false;
-        *to = uint64_t(id);
+        *to = objVar;
         return true;
       }
 
       case JSTYPE_STRING:
       {
         nsDependentJSString dep;
         if (!dep.init(cx, from))
             return false;
@@ -222,34 +262,33 @@ JavaScriptShared::toVariant(JSContext *c
 
       default:
         MOZ_ASSERT(false);
         return false;
     }
 }
 
 bool
-JavaScriptShared::toValue(JSContext *cx, const JSVariant &from, MutableHandleValue to)
+JavaScriptShared::fromVariant(JSContext *cx, const JSVariant &from, MutableHandleValue to)
 {
     switch (from.type()) {
-        case JSVariant::Tvoid_t:
+        case JSVariant::TUndefinedVariant:
           to.set(UndefinedValue());
           return true;
 
-        case JSVariant::Tuint64_t:
+        case JSVariant::TNullVariant:
+          to.set(NullValue());
+          return true;
+
+        case JSVariant::TObjectVariant:
         {
-          ObjectId id = from.get_uint64_t();
-          if (id) {
-              JSObject *obj = unwrap(cx, id);
-              if (!obj)
-                  return false;
-              to.set(ObjectValue(*obj));
-          } else {
-              to.set(JSVAL_NULL);
-          }
+          JSObject *obj = fromObjectVariant(cx, from.get_ObjectVariant());
+          if (!obj)
+              return false;
+          to.set(ObjectValue(*obj));
           return true;
         }
 
         case JSVariant::Tdouble:
           to.set(JS_NumberValue(from.get_double()));
           return true;
 
         case JSVariant::Tbool:
@@ -313,50 +352,92 @@ JavaScriptShared::ConvertID(const JSIID 
     to->m3[2] = from.m3_2();
     to->m3[3] = from.m3_3();
     to->m3[4] = from.m3_4();
     to->m3[5] = from.m3_5();
     to->m3[6] = from.m3_6();
     to->m3[7] = from.m3_7();
 }
 
-static const uint32_t DefaultPropertyOp = 1;
-static const uint32_t GetterOnlyPropertyStub = 2;
-static const uint32_t UnknownPropertyOp = 3;
+JSObject *
+JavaScriptShared::findObjectById(JSContext *cx, uint32_t objId)
+{
+    RootedObject obj(cx, findObjectById(objId));
+    if (!obj) {
+        JS_ReportError(cx, "operation not possible on dead CPOW");
+        return nullptr;
+    }
+
+    // Objects are stored in objects_ unwrapped. We want to wrap the object
+    // before returning it so that all operations happen on Xray wrappers. If
+    // the object is a DOM element, we try to obtain the corresponding
+    // TabChildGlobal and wrap in that.
+    RootedObject global(cx, GetGlobalForObjectCrossCompartment(obj));
+    nsCOMPtr<nsIGlobalObject> nativeGlobal = xpc::GetNativeForGlobal(global);
+    nsCOMPtr<nsIDOMWindow> window = do_QueryInterface(nativeGlobal);
+    if (window) {
+        dom::TabChild *tabChild = dom::TabChild::GetFrom(window);
+        if (tabChild) {
+            nsCOMPtr<nsIContentFrameMessageManager> mm;
+            tabChild->GetMessageManager(getter_AddRefs(mm));
+            nsCOMPtr<nsIGlobalObject> tabChildNativeGlobal = do_QueryInterface(mm);
+            RootedObject tabChildGlobal(cx, tabChildNativeGlobal->GetGlobalJSObject());
+            JSAutoCompartment ac(cx, tabChildGlobal);
+            if (!JS_WrapObject(cx, &obj))
+                return nullptr;
+            return obj;
+        }
+    }
+
+    // If there's no TabChildGlobal, we use the junk scope.
+    JSAutoCompartment ac(cx, xpc::GetJunkScope());
+    if (!JS_WrapObject(cx, &obj))
+        return nullptr;
+    return obj;
+}
+
+static const uint64_t DefaultPropertyOp = 1;
+static const uint64_t GetterOnlyPropertyStub = 2;
+static const uint64_t UnknownPropertyOp = 3;
 
 bool
 JavaScriptShared::fromDescriptor(JSContext *cx, Handle<JSPropertyDescriptor> desc,
                                  PPropertyDescriptor *out)
 {
     out->attrs() = desc.attributes();
     if (!toVariant(cx, desc.value(), &out->value()))
         return false;
 
-    if (!makeId(cx, desc.object(), &out->objId()))
+    JS_ASSERT(desc.object());
+    if (!toObjectVariant(cx, desc.object(), &out->obj()))
         return false;
 
     if (!desc.getter()) {
         out->getter() = 0;
     } else if (desc.hasGetterObject()) {
         JSObject *getter = desc.getterObject();
-        if (!makeId(cx, getter, &out->getter()))
+        ObjectVariant objVar;
+        if (!toObjectVariant(cx, getter, &objVar))
             return false;
+        out->getter() = objVar;
     } else {
         if (desc.getter() == JS_PropertyStub)
             out->getter() = DefaultPropertyOp;
         else
             out->getter() = UnknownPropertyOp;
     }
 
     if (!desc.setter()) {
         out->setter() = 0;
     } else if (desc.hasSetterObject()) {
         JSObject *setter = desc.setterObject();
-        if (!makeId(cx, setter, &out->setter()))
+        ObjectVariant objVar;
+        if (!toObjectVariant(cx, setter, &objVar))
             return false;
+        out->setter() = objVar;
     } else {
         if (desc.setter() == JS_StrictPropertyStub)
             out->setter() = DefaultPropertyOp;
         else if (desc.setter() == js_GetterOnlyPropertyStub)
             out->setter() = GetterOnlyPropertyStub;
         else
             out->setter() = UnknownPropertyOp;
     }
@@ -378,48 +459,51 @@ UnknownStrictPropertyStub(JSContext *cx,
     return false;
 }
 
 bool
 JavaScriptShared::toDescriptor(JSContext *cx, const PPropertyDescriptor &in,
                                MutableHandle<JSPropertyDescriptor> out)
 {
     out.setAttributes(in.attrs());
-    if (!toValue(cx, in.value(), out.value()))
+    if (!fromVariant(cx, in.value(), out.value()))
         return false;
     Rooted<JSObject*> obj(cx);
-    if (!unwrap(cx, in.objId(), &obj))
+    obj = fromObjectVariant(cx, in.obj());
+    if (!obj)
         return false;
     out.object().set(obj);
 
-    if (!in.getter()) {
+    if (in.getter().type() == GetterSetter::Tuint64_t && !in.getter().get_uint64_t()) {
         out.setGetter(nullptr);
     } else if (in.attrs() & JSPROP_GETTER) {
         Rooted<JSObject*> getter(cx);
-        if (!unwrap(cx, in.getter(), &getter))
+        getter = fromObjectVariant(cx, in.getter().get_ObjectVariant());
+        if (!getter)
             return false;
         out.setGetter(JS_DATA_TO_FUNC_PTR(JSPropertyOp, getter.get()));
     } else {
-        if (in.getter() == DefaultPropertyOp)
+        if (in.getter().get_uint64_t() == DefaultPropertyOp)
             out.setGetter(JS_PropertyStub);
         else
             out.setGetter(UnknownPropertyStub);
     }
 
-    if (!in.setter()) {
+    if (in.setter().type() == GetterSetter::Tuint64_t && !in.setter().get_uint64_t()) {
         out.setSetter(nullptr);
     } else if (in.attrs() & JSPROP_SETTER) {
         Rooted<JSObject*> setter(cx);
-        if (!unwrap(cx, in.setter(), &setter))
+        setter = fromObjectVariant(cx, in.setter().get_ObjectVariant());
+        if (!setter)
             return false;
         out.setSetter(JS_DATA_TO_FUNC_PTR(JSStrictPropertyOp, setter.get()));
     } else {
-        if (in.setter() == DefaultPropertyOp)
+        if (in.setter().get_uint64_t() == DefaultPropertyOp)
             out.setSetter(JS_StrictPropertyStub);
-        else if (in.setter() == GetterOnlyPropertyStub)
+        else if (in.setter().get_uint64_t() == GetterOnlyPropertyStub)
             out.setSetter(js_GetterOnlyPropertyStub);
         else
             out.setSetter(UnknownStrictPropertyStub);
     }
 
     return true;
 }
 
@@ -442,17 +526,17 @@ JavaScriptShared::Unwrap(JSContext *cx, 
     if (!obj)
         return false;
 
     RootedValue v(cx);
     RootedString str(cx);
     for (size_t i = 0; i < aCpows.Length(); i++) {
         const nsString &name = aCpows[i].name();
 
-        if (!toValue(cx, aCpows[i].value(), &v))
+        if (!fromVariant(cx, aCpows[i].value(), &v))
             return false;
 
         if (!JS_DefineUCProperty(cx,
                                  obj,
                                  name.BeginReading(),
                                  name.Length(),
                                  v,
                                  JSPROP_ENUMERATE))
--- a/js/ipc/JavaScriptShared.h
+++ b/js/ipc/JavaScriptShared.h
@@ -32,109 +32,112 @@ class CpowIdHolder : public CpowHolder
     bool ToObject(JSContext *cx, JS::MutableHandleObject objp);
 
   private:
     JavaScriptShared *js_;
     const InfallibleTArray<CpowEntry> &cpows_;
 };
 
 // Map ids -> JSObjects
-class ObjectStore
+class IdToObjectMap
 {
     typedef js::DefaultHasher<ObjectId> TableKeyHasher;
 
-    typedef js::HashMap<ObjectId, JS::Heap<JSObject *>, TableKeyHasher, js::SystemAllocPolicy> ObjectTable;
+    typedef js::HashMap<ObjectId, JS::Heap<JSObject *>, TableKeyHasher, js::SystemAllocPolicy> Table;
 
   public:
-    ObjectStore();
+    IdToObjectMap();
 
     bool init();
     void trace(JSTracer *trc);
+    void finalize(JSFreeOp *fop);
 
     bool add(ObjectId id, JSObject *obj);
     JSObject *find(ObjectId id);
     void remove(ObjectId id);
 
   private:
-    ObjectTable table_;
+    Table table_;
 };
 
 // Map JSObjects -> ids
-class ObjectIdCache
+class ObjectToIdMap
 {
     typedef js::PointerHasher<JSObject *, 3> Hasher;
-    typedef js::HashMap<JSObject *, ObjectId, Hasher, js::SystemAllocPolicy> ObjectIdTable;
+    typedef js::HashMap<JSObject *, ObjectId, Hasher, js::SystemAllocPolicy> Table;
 
   public:
-    ObjectIdCache();
-    ~ObjectIdCache();
+    ObjectToIdMap();
+    ~ObjectToIdMap();
 
     bool init();
-    void trace(JSTracer *trc);
+    void finalize(JSFreeOp *fop);
 
     bool add(JSContext *cx, JSObject *obj, ObjectId id);
     ObjectId find(JSObject *obj);
     void remove(JSObject *obj);
 
   private:
     static void keyMarkCallback(JSTracer *trc, JSObject *key, void *data);
 
-    ObjectIdTable *table_;
+    Table *table_;
 };
 
 class JavaScriptShared
 {
   public:
+    JavaScriptShared(JSRuntime *rt);
+    virtual ~JavaScriptShared() {}
+
     bool init();
 
+    void decref();
+    void incref();
+
     static const uint32_t OBJECT_EXTRA_BITS  = 1;
     static const uint32_t OBJECT_IS_CALLABLE = (1 << 0);
 
     bool Unwrap(JSContext *cx, const InfallibleTArray<CpowEntry> &aCpows, JS::MutableHandleObject objp);
     bool Wrap(JSContext *cx, JS::HandleObject aObj, InfallibleTArray<CpowEntry> *outCpows);
 
   protected:
     bool toVariant(JSContext *cx, JS::HandleValue from, JSVariant *to);
-    bool toValue(JSContext *cx, const JSVariant &from, JS::MutableHandleValue to);
-    bool fromDescriptor(JSContext *cx, JS::Handle<JSPropertyDescriptor> desc, PPropertyDescriptor *out);
+    bool fromVariant(JSContext *cx, const JSVariant &from, JS::MutableHandleValue to);
+
+    bool fromDescriptor(JSContext *cx, JS::Handle<JSPropertyDescriptor> desc,
+                        PPropertyDescriptor *out);
     bool toDescriptor(JSContext *cx, const PPropertyDescriptor &in,
                       JS::MutableHandle<JSPropertyDescriptor> out);
+
     bool convertIdToGeckoString(JSContext *cx, JS::HandleId id, nsString *to);
     bool convertGeckoStringToId(JSContext *cx, const nsString &from, JS::MutableHandleId id);
 
-    bool toValue(JSContext *cx, const JSVariant &from, jsval *to) {
-        JS::RootedValue v(cx);
-        if (!toValue(cx, from, &v))
-            return false;
-        *to = v;
-        return true;
-    }
-
-    virtual bool makeId(JSContext *cx, JSObject *obj, ObjectId *idp) = 0;
-    virtual JSObject *unwrap(JSContext *cx, ObjectId id) = 0;
-
-    bool unwrap(JSContext *cx, ObjectId id, JS::MutableHandle<JSObject*> objp) {
-        if (!id) {
-            objp.set(nullptr);
-            return true;
-        }
-
-        objp.set(unwrap(cx, id));
-        return bool(objp.get());
-    }
+    virtual bool toObjectVariant(JSContext *cx, JSObject *obj, ObjectVariant *objVarp) = 0;
+    virtual JSObject *fromObjectVariant(JSContext *cx, ObjectVariant objVar) = 0;
 
     static void ConvertID(const nsID &from, JSIID *to);
     static void ConvertID(const JSIID &from, nsID *to);
 
-    JSObject *findObject(uint32_t objId) {
+    JSObject *findCPOWById(uint32_t objId) {
+        return cpows_.find(objId);
+    }
+    JSObject *findObjectById(uint32_t objId) {
         return objects_.find(objId);
     }
+    JSObject *findObjectById(JSContext *cx, uint32_t objId);
 
   protected:
-    ObjectStore objects_;
+    JSRuntime *rt_;
+    uintptr_t refcount_;
+
+    IdToObjectMap objects_;
+    IdToObjectMap cpows_;
+
+    ObjectId lastId_;
+    ObjectToIdMap objectIds_;
 };
 
 // Use 47 at most, to be safe, since jsval privates are encoded as doubles.
 static const uint64_t MAX_CPOW_IDS = (uint64_t(1) << 47) - 1;
 
 } // namespace jsipc
 } // namespace mozilla
 
--- a/js/ipc/JavaScriptTypes.ipdlh
+++ b/js/ipc/JavaScriptTypes.ipdlh
@@ -22,21 +22,41 @@ struct JSIID
     uint8_t m3_2;
     uint8_t m3_3;
     uint8_t m3_4;
     uint8_t m3_5;
     uint8_t m3_6;
     uint8_t m3_7;
 };
 
+struct LocalObject
+{
+    uint64_t id;
+};
+
+struct RemoteObject
+{
+    uint64_t id;
+};
+
+union ObjectVariant
+{
+    LocalObject;
+    RemoteObject;
+};
+
+struct UndefinedVariant {};
+struct NullVariant {};
+
 union JSVariant
 {
-    void_t;     /* |undefined| */
+    UndefinedVariant;
+    NullVariant;
+    ObjectVariant;
     nsString;   /* StringValue(x) */
-    uint64_t;   /* ID that maps to a JSObject (cpow on parent, original on child). */
     double;     /* NumberValue(x) */
     bool;       /* BooleanValue(x) */
     JSIID;      /* XPC nsIID */
 };
 
 struct ReturnSuccess
 {
 };
@@ -58,32 +78,38 @@ union ReturnStatus
 };
 
 union JSParam
 {
     void_t;     /* value is strictly an xpc out param */
     JSVariant;  /* actual value to pass through */
 };
 
+union GetterSetter
+{
+    uint64_t;
+    ObjectVariant;
+};
+
 struct PPropertyDescriptor
 {
-    uint64_t    objId;
-    uint32_t    attrs;
-    JSVariant   value;
+    ObjectVariant obj;
+    uint32_t attrs;
+    JSVariant value;
 
     // How to interpret these values depends on whether JSPROP_GETTER/SETTER
     // are set. If set, the corresponding value is a CPOW or 0 for NULL.
     // Otherwise, the following table is used:
     //
     //  0 - NULL
     //  1 - Default getter or setter.
     //  2 - js_GetterOnlyPropertyStub (setter only)
     //  3 - Unknown
-    uint64_t    getter;
-    uint64_t    setter;
+    GetterSetter getter;
+    GetterSetter setter;
 };
 
 struct CpowEntry
 {
   nsString name;
   JSVariant value;
 };
 
--- a/js/ipc/PJavaScript.ipdl
+++ b/js/ipc/PJavaScript.ipdl
@@ -13,18 +13,18 @@ using struct mozilla::void_t from "ipc/I
 
 namespace mozilla {
 namespace jsipc {
 
 intr protocol PJavaScript
 {
     manager PContent;
 
-child:
-    // The parent process no longer holds any references to the child object.
+both:
+    // Sent when a CPOW has been finalized and table entries can be freed up.
     async DropObject(uint64_t objId);
 
     // These roughly map to the ProxyHandler hooks that CPOWs need.
     rpc PreventExtensions(uint64_t objId) returns (ReturnStatus rs);
     rpc GetPropertyDescriptor(uint64_t objId, nsString id) returns (ReturnStatus rs, PPropertyDescriptor result);
     rpc GetOwnPropertyDescriptor(uint64_t objId, nsString id) returns (ReturnStatus rs, PPropertyDescriptor result);
     rpc DefineProperty(uint64_t objId, nsString id, PPropertyDescriptor descriptor) returns (ReturnStatus rs);
     rpc Delete(uint64_t objId, nsString id) returns (ReturnStatus rs, bool successful);
new file mode 100644
--- /dev/null
+++ b/js/ipc/WrapperAnswer.cpp
@@ -0,0 +1,583 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ * vim: set ts=4 sw=4 et tw=80:
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "WrapperAnswer.h"
+#include "mozilla/dom/ContentChild.h"
+#include "mozilla/dom/BindingUtils.h"
+#include "nsContentUtils.h"
+#include "xpcprivate.h"
+#include "jsfriendapi.h"
+#include "nsCxPusher.h"
+
+using namespace JS;
+using namespace mozilla;
+using namespace mozilla::jsipc;
+
+using mozilla::AutoSafeJSContext;
+
+bool
+WrapperAnswer::fail(JSContext *cx, ReturnStatus *rs)
+{
+    // By default, we set |undefined| unless we can get a more meaningful
+    // exception.
+    *rs = ReturnStatus(ReturnException(JSVariant(UndefinedVariant())));
+
+    // Note we always return true from this function, since this propagates
+    // to the IPC code, and we don't want a JS failure to cause the death
+    // of the child process.
+
+    RootedValue exn(cx);
+    if (!JS_GetPendingException(cx, &exn))
+        return true;
+
+    // If we don't clear the pending exception, JS will try to wrap it as it
+    // leaves the current compartment. Since there is no previous compartment,
+    // that would crash.
+    JS_ClearPendingException(cx);
+
+    if (JS_IsStopIteration(exn)) {
+        *rs = ReturnStatus(ReturnStopIteration());
+        return true;
+    }
+
+    // If this fails, we still don't want to exit. Just return an invalid
+    // exception.
+    (void) toVariant(cx, exn, &rs->get_ReturnException().exn());
+    return true;
+}
+
+bool
+WrapperAnswer::ok(ReturnStatus *rs)
+{
+    *rs = ReturnStatus(ReturnSuccess());
+    return true;
+}
+
+bool
+WrapperAnswer::AnswerPreventExtensions(const ObjectId &objId, ReturnStatus *rs)
+{
+    AutoSafeJSContext cx;
+    JSAutoRequest request(cx);
+
+    RootedObject obj(cx, findObjectById(cx, objId));
+    if (!obj)
+        return fail(cx, rs);
+
+    JSAutoCompartment comp(cx, obj);
+    if (!JS_PreventExtensions(cx, obj))
+        return fail(cx, rs);
+
+    return ok(rs);
+}
+
+static void
+EmptyDesc(PPropertyDescriptor *desc)
+{
+    desc->obj() = LocalObject(0);
+    desc->attrs() = 0;
+    desc->value() = UndefinedVariant();
+    desc->getter() = 0;
+    desc->setter() = 0;
+}
+
+bool
+WrapperAnswer::AnswerGetPropertyDescriptor(const ObjectId &objId, const nsString &id,
+					   ReturnStatus *rs, PPropertyDescriptor *out)
+{
+    AutoSafeJSContext cx;
+    JSAutoRequest request(cx);
+
+    EmptyDesc(out);
+
+    RootedObject obj(cx, findObjectById(cx, objId));
+    if (!obj)
+        return fail(cx, rs);
+
+    JSAutoCompartment comp(cx, obj);
+
+    RootedId internedId(cx);
+    if (!convertGeckoStringToId(cx, id, &internedId))
+        return fail(cx, rs);
+
+    Rooted<JSPropertyDescriptor> desc(cx);
+    if (!JS_GetPropertyDescriptorById(cx, obj, internedId, &desc))
+        return fail(cx, rs);
+
+    if (!desc.object())
+        return ok(rs);
+
+    if (!fromDescriptor(cx, desc, out))
+        return fail(cx, rs);
+
+    return ok(rs);
+}
+
+bool
+WrapperAnswer::AnswerGetOwnPropertyDescriptor(const ObjectId &objId, const nsString &id,
+					      ReturnStatus *rs, PPropertyDescriptor *out)
+{
+    AutoSafeJSContext cx;
+    JSAutoRequest request(cx);
+
+    EmptyDesc(out);
+
+    RootedObject obj(cx, findObjectById(cx, objId));
+    if (!obj)
+        return fail(cx, rs);
+
+    JSAutoCompartment comp(cx, obj);
+
+    RootedId internedId(cx);
+    if (!convertGeckoStringToId(cx, id, &internedId))
+        return fail(cx, rs);
+
+    Rooted<JSPropertyDescriptor> desc(cx);
+    if (!JS_GetPropertyDescriptorById(cx, obj, internedId, &desc))
+        return fail(cx, rs);
+
+    if (desc.object() != obj)
+        return ok(rs);
+
+    if (!fromDescriptor(cx, desc, out))
+        return fail(cx, rs);
+
+    return ok(rs);
+}
+
+bool
+WrapperAnswer::AnswerDefineProperty(const ObjectId &objId, const nsString &id,
+				    const PPropertyDescriptor &descriptor, ReturnStatus *rs)
+{
+    AutoSafeJSContext cx;
+    JSAutoRequest request(cx);
+
+    RootedObject obj(cx, findObjectById(cx, objId));
+    if (!obj)
+        return fail(cx, rs);
+
+    JSAutoCompartment comp(cx, obj);
+
+    RootedId internedId(cx);
+    if (!convertGeckoStringToId(cx, id, &internedId))
+        return fail(cx, rs);
+
+    Rooted<JSPropertyDescriptor> desc(cx);
+    if (!toDescriptor(cx, descriptor, &desc))
+        return fail(cx, rs);
+
+    if (!js::CheckDefineProperty(cx, obj, internedId, desc.value(), desc.attributes(),
+                                 desc.getter(), desc.setter()))
+    {
+        return fail(cx, rs);
+    }
+
+    if (!JS_DefinePropertyById(cx, obj, internedId, desc.value(), desc.attributes(),
+                               desc.getter(), desc.setter()))
+    {
+        return fail(cx, rs);
+    }
+
+    return ok(rs);
+}
+
+bool
+WrapperAnswer::AnswerDelete(const ObjectId &objId, const nsString &id, ReturnStatus *rs,
+			    bool *success)
+{
+    AutoSafeJSContext cx;
+    JSAutoRequest request(cx);
+
+    *success = false;
+
+    RootedObject obj(cx, findObjectById(cx, objId));
+    if (!obj)
+        return fail(cx, rs);
+
+    JSAutoCompartment comp(cx, obj);
+
+    RootedId internedId(cx);
+    if (!convertGeckoStringToId(cx, id, &internedId))
+        return fail(cx, rs);
+
+    if (!JS_DeletePropertyById2(cx, obj, internedId, success))
+        return fail(cx, rs);
+
+    return ok(rs);
+}
+
+bool
+WrapperAnswer::AnswerHas(const ObjectId &objId, const nsString &id, ReturnStatus *rs, bool *bp)
+{
+    AutoSafeJSContext cx;
+    JSAutoRequest request(cx);
+
+    *bp = false;
+
+    RootedObject obj(cx, findObjectById(cx, objId));
+    if (!obj)
+        return fail(cx, rs);
+
+    JSAutoCompartment comp(cx, obj);
+
+    RootedId internedId(cx);
+    if (!convertGeckoStringToId(cx, id, &internedId))
+        return fail(cx, rs);
+
+    bool found;
+    if (!JS_HasPropertyById(cx, obj, internedId, &found))
+        return fail(cx, rs);
+    *bp = !!found;
+
+    return ok(rs);
+}
+
+bool
+WrapperAnswer::AnswerHasOwn(const ObjectId &objId, const nsString &id, ReturnStatus *rs, bool *bp)
+{
+    AutoSafeJSContext cx;
+    JSAutoRequest request(cx);
+
+    *bp = false;
+
+    RootedObject obj(cx, findObjectById(cx, objId));
+    if (!obj)
+        return fail(cx, rs);
+
+    JSAutoCompartment comp(cx, obj);
+
+    RootedId internedId(cx);
+    if (!convertGeckoStringToId(cx, id, &internedId))
+        return fail(cx, rs);
+
+    Rooted<JSPropertyDescriptor> desc(cx);
+    if (!JS_GetPropertyDescriptorById(cx, obj, internedId, &desc))
+        return fail(cx, rs);
+    *bp = (desc.object() == obj);
+
+    return ok(rs);
+}
+
+bool
+WrapperAnswer::AnswerGet(const ObjectId &objId, const ObjectId &receiverId, const nsString &id,
+			 ReturnStatus *rs, JSVariant *result)
+{
+    AutoSafeJSContext cx;
+    JSAutoRequest request(cx);
+
+    // The outparam will be written to the buffer, so it must be set even if
+    // the parent won't read it.
+    *result = UndefinedVariant();
+
+    RootedObject obj(cx, findObjectById(cx, objId));
+    if (!obj)
+        return fail(cx, rs);
+
+    RootedObject receiver(cx, findObjectById(cx, receiverId));
+    if (!receiver)
+        return fail(cx, rs);
+
+    JSAutoCompartment comp(cx, obj);
+
+    RootedId internedId(cx);
+    if (!convertGeckoStringToId(cx, id, &internedId))
+        return fail(cx, rs);
+
+    JS::RootedValue val(cx);
+    if (!JS_ForwardGetPropertyTo(cx, obj, internedId, receiver, &val))
+        return fail(cx, rs);
+
+    if (!toVariant(cx, val, result))
+        return fail(cx, rs);
+
+    return ok(rs);
+}
+
+bool
+WrapperAnswer::AnswerSet(const ObjectId &objId, const ObjectId &receiverId, const nsString &id,
+			 const bool &strict, const JSVariant &value, ReturnStatus *rs,
+			 JSVariant *result)
+{
+    AutoSafeJSContext cx;
+    JSAutoRequest request(cx);
+
+    // The outparam will be written to the buffer, so it must be set even if
+    // the parent won't read it.
+    *result = UndefinedVariant();
+
+    RootedObject obj(cx, findObjectById(cx, objId));
+    if (!obj)
+        return fail(cx, rs);
+
+    RootedObject receiver(cx, findObjectById(cx, receiverId));
+    if (!receiver)
+        return fail(cx, rs);
+
+    JSAutoCompartment comp(cx, obj);
+
+    RootedId internedId(cx);
+    if (!convertGeckoStringToId(cx, id, &internedId))
+        return fail(cx, rs);
+
+    MOZ_ASSERT(obj == receiver);
+
+    RootedValue val(cx);
+    if (!fromVariant(cx, value, &val))
+        return fail(cx, rs);
+
+    if (!JS_SetPropertyById(cx, obj, internedId, val))
+        return fail(cx, rs);
+
+    if (!toVariant(cx, val, result))
+        return fail(cx, rs);
+
+    return ok(rs);
+}
+
+bool
+WrapperAnswer::AnswerIsExtensible(const ObjectId &objId, ReturnStatus *rs, bool *result)
+{
+    AutoSafeJSContext cx;
+    JSAutoRequest request(cx);
+
+    *result = false;
+
+    RootedObject obj(cx, findObjectById(cx, objId));
+    if (!obj)
+        return fail(cx, rs);
+
+    JSAutoCompartment comp(cx, obj);
+
+    bool extensible;
+    if (!JS_IsExtensible(cx, obj, &extensible))
+        return fail(cx, rs);
+
+    *result = !!extensible;
+    return ok(rs);
+}
+
+bool
+WrapperAnswer::AnswerCall(const ObjectId &objId, const nsTArray<JSParam> &argv, ReturnStatus *rs,
+			  JSVariant *result, nsTArray<JSParam> *outparams)
+{
+    AutoSafeJSContext cx;
+    JSAutoRequest request(cx);
+
+    // The outparam will be written to the buffer, so it must be set even if
+    // the parent won't read it.
+    *result = UndefinedVariant();
+
+    RootedObject obj(cx, findObjectById(cx, objId));
+    if (!obj)
+        return fail(cx, rs);
+
+    JSAutoCompartment comp(cx, obj);
+
+    MOZ_ASSERT(argv.Length() >= 2);
+
+    RootedValue objv(cx);
+    if (!fromVariant(cx, argv[0], &objv))
+        return fail(cx, rs);
+
+    *result = JSVariant(UndefinedVariant());
+
+    AutoValueVector vals(cx);
+    AutoValueVector outobjects(cx);
+    for (size_t i = 0; i < argv.Length(); i++) {
+        if (argv[i].type() == JSParam::Tvoid_t) {
+            // This is an outparam.
+            JSCompartment *compartment = js::GetContextCompartment(cx);
+            RootedObject global(cx, JS_GetGlobalForCompartmentOrNull(cx, compartment));
+            RootedObject obj(cx, xpc::NewOutObject(cx, global));
+            if (!obj)
+                return fail(cx, rs);
+            if (!outobjects.append(ObjectValue(*obj)))
+                return fail(cx, rs);
+            if (!vals.append(ObjectValue(*obj)))
+                return fail(cx, rs);
+        } else {
+            RootedValue v(cx);
+            if (!fromVariant(cx, argv[i].get_JSVariant(), &v))
+                return fail(cx, rs);
+            if (!vals.append(v))
+                return fail(cx, rs);
+        }
+    }
+
+    RootedValue rval(cx);
+    {
+        AutoSaveContextOptions asco(cx);
+        ContextOptionsRef(cx).setDontReportUncaught(true);
+
+        HandleValueArray args = HandleValueArray::subarray(vals, 2, vals.length() - 2);
+        bool success = JS::Call(cx, vals[1], vals[0], args, &rval);
+        if (!success)
+            return fail(cx, rs);
+    }
+
+    if (!toVariant(cx, rval, result))
+        return fail(cx, rs);
+
+    // Prefill everything with a dummy jsval.
+    for (size_t i = 0; i < outobjects.length(); i++)
+        outparams->AppendElement(JSParam(void_t()));
+
+    // Go through each argument that was an outparam, retrieve the "value"
+    // field, and add it to a temporary list. We need to do this separately
+    // because the outparams vector is not rooted.
+    vals.clear();
+    for (size_t i = 0; i < outobjects.length(); i++) {
+        RootedObject obj(cx, &outobjects[i].toObject());
+
+        RootedValue v(cx);
+        bool found;
+        if (JS_HasProperty(cx, obj, "value", &found)) {
+            if (!JS_GetProperty(cx, obj, "value", &v))
+                return fail(cx, rs);
+        } else {
+            v = UndefinedValue();
+        }
+        if (!vals.append(v))
+            return fail(cx, rs);
+    }
+
+    // Copy the outparams. If any outparam is already set to a void_t, we
+    // treat this as the outparam never having been set.
+    for (size_t i = 0; i < vals.length(); i++) {
+        JSVariant variant;
+        if (!toVariant(cx, vals[i], &variant))
+            return fail(cx, rs);
+        outparams->ReplaceElementAt(i, JSParam(variant));
+    }
+
+    return ok(rs);
+}
+
+bool
+WrapperAnswer::AnswerObjectClassIs(const ObjectId &objId, const uint32_t &classValue,
+				   bool *result)
+{
+    AutoSafeJSContext cx;
+    JSAutoRequest request(cx);
+
+    RootedObject obj(cx, findObjectById(cx, objId));
+    if (!obj) {
+        // This is very unfortunate, but we have no choice.
+        *result = false;
+        return true;
+    }
+
+    JSAutoCompartment comp(cx, obj);
+
+    *result = js_ObjectClassIs(cx, obj, (js::ESClassValue)classValue);
+    return true;
+}
+
+bool
+WrapperAnswer::AnswerClassName(const ObjectId &objId, nsString *name)
+{
+    AutoSafeJSContext cx;
+    JSAutoRequest request(cx);
+
+    RootedObject obj(cx, findObjectById(cx, objId));
+    if (!obj) {
+        // This is very unfortunate, but we have no choice.
+        return "<dead CPOW>";
+    }
+
+    JSAutoCompartment comp(cx, obj);
+
+    *name = NS_ConvertASCIItoUTF16(js_ObjectClassName(cx, obj));
+    return true;
+}
+
+bool
+WrapperAnswer::AnswerGetPropertyNames(const ObjectId &objId, const uint32_t &flags,
+				      ReturnStatus *rs, nsTArray<nsString> *names)
+{
+    AutoSafeJSContext cx;
+    JSAutoRequest request(cx);
+
+    RootedObject obj(cx, findObjectById(cx, objId));
+    if (!obj)
+        return fail(cx, rs);
+
+    JSAutoCompartment comp(cx, obj);
+
+    AutoIdVector props(cx);
+    if (!js::GetPropertyNames(cx, obj, flags, &props))
+        return fail(cx, rs);
+
+    for (size_t i = 0; i < props.length(); i++) {
+        nsString name;
+        if (!convertIdToGeckoString(cx, props[i], &name))
+            return fail(cx, rs);
+
+        names->AppendElement(name);
+    }
+
+    return ok(rs);
+}
+
+bool
+WrapperAnswer::AnswerInstanceOf(const ObjectId &objId, const JSIID &iid, ReturnStatus *rs,
+				bool *instanceof)
+{
+    AutoSafeJSContext cx;
+    JSAutoRequest request(cx);
+
+    *instanceof = false;
+
+    RootedObject obj(cx, findObjectById(cx, objId));
+    if (!obj)
+        return fail(cx, rs);
+
+    JSAutoCompartment comp(cx, obj);
+
+    nsID nsiid;
+    ConvertID(iid, &nsiid);
+
+    nsresult rv = xpc::HasInstance(cx, obj, &nsiid, instanceof);
+    if (rv != NS_OK)
+        return fail(cx, rs);
+
+    return ok(rs);
+}
+
+bool
+WrapperAnswer::AnswerDOMInstanceOf(const ObjectId &objId, const int &prototypeID,
+				   const int &depth,
+				   ReturnStatus *rs, bool *instanceof)
+{
+    AutoSafeJSContext cx;
+    JSAutoRequest request(cx);
+
+    *instanceof = false;
+
+    RootedObject obj(cx, findObjectById(cx, objId));
+    if (!obj)
+        return fail(cx, rs);
+
+    JSAutoCompartment comp(cx, obj);
+
+    bool tmp;
+    if (!mozilla::dom::InterfaceHasInstance(cx, prototypeID, depth, obj, &tmp))
+        return fail(cx, rs);
+    *instanceof = tmp;
+
+    return ok(rs);
+}
+
+bool
+WrapperAnswer::RecvDropObject(const ObjectId &objId)
+{
+    JSObject *obj = findObjectById(objId);
+    if (obj) {
+        objectIds_.remove(obj);
+        objects_.remove(objId);
+    }
+    return true;
+}
new file mode 100644
--- /dev/null
+++ b/js/ipc/WrapperAnswer.h
@@ -0,0 +1,72 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ * vim: set ts=4 sw=4 et tw=80:
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_jsipc_WrapperAnswer_h_
+#define mozilla_jsipc_WrapperAnswer_h_
+
+#include "JavaScriptShared.h"
+
+namespace mozilla {
+namespace jsipc {
+
+class WrapperAnswer : public virtual JavaScriptShared
+{
+  public:
+    WrapperAnswer(JSRuntime *rt) : JavaScriptShared(rt) {}
+
+    bool AnswerPreventExtensions(const ObjectId &objId, ReturnStatus *rs);
+    bool AnswerGetPropertyDescriptor(const ObjectId &objId, const nsString &id,
+                                     ReturnStatus *rs,
+                                     PPropertyDescriptor *out);
+    bool AnswerGetOwnPropertyDescriptor(const ObjectId &objId,
+                                        const nsString &id,
+                                        ReturnStatus *rs,
+                                        PPropertyDescriptor *out);
+    bool AnswerDefineProperty(const ObjectId &objId, const nsString &id,
+                              const PPropertyDescriptor &flags,
+                              ReturnStatus *rs);
+    bool AnswerDelete(const ObjectId &objId, const nsString &id,
+                      ReturnStatus *rs, bool *success);
+
+    bool AnswerHas(const ObjectId &objId, const nsString &id,
+                       ReturnStatus *rs, bool *bp);
+    bool AnswerHasOwn(const ObjectId &objId, const nsString &id,
+                          ReturnStatus *rs, bool *bp);
+    bool AnswerGet(const ObjectId &objId, const ObjectId &receiverId,
+                       const nsString &id,
+                       ReturnStatus *rs, JSVariant *result);
+    bool AnswerSet(const ObjectId &objId, const ObjectId &receiverId,
+                   const nsString &id, const bool &strict,
+                   const JSVariant &value, ReturnStatus *rs, JSVariant *result);
+
+    bool AnswerIsExtensible(const ObjectId &objId, ReturnStatus *rs,
+                            bool *result);
+    bool AnswerCall(const ObjectId &objId, const nsTArray<JSParam> &argv,
+                    ReturnStatus *rs, JSVariant *result,
+                    nsTArray<JSParam> *outparams);
+    bool AnswerObjectClassIs(const ObjectId &objId, const uint32_t &classValue,
+                             bool *result);
+    bool AnswerClassName(const ObjectId &objId, nsString *result);
+
+    bool AnswerGetPropertyNames(const ObjectId &objId, const uint32_t &flags,
+                                ReturnStatus *rs, nsTArray<nsString> *names);
+    bool AnswerInstanceOf(const ObjectId &objId, const JSIID &iid,
+                          ReturnStatus *rs, bool *instanceof);
+    bool AnswerDOMInstanceOf(const ObjectId &objId, const int &prototypeID, const int &depth,
+                             ReturnStatus *rs, bool *instanceof);
+
+    bool RecvDropObject(const ObjectId &objId);
+
+  private:
+    bool fail(JSContext *cx, ReturnStatus *rs);
+    bool ok(ReturnStatus *rs);
+};
+
+} // mozilla
+} // jsipc
+
+#endif
new file mode 100644
--- /dev/null
+++ b/js/ipc/WrapperOwner.cpp
@@ -0,0 +1,743 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ * vim: set ts=4 sw=4 et tw=80:
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "WrapperOwner.h"
+#include "mozilla/unused.h"
+#include "mozilla/dom/BindingUtils.h"
+#include "jsfriendapi.h"
+#include "xpcprivate.h"
+
+using namespace js;
+using namespace JS;
+using namespace mozilla;
+using namespace mozilla::jsipc;
+
+WrapperOwner::WrapperOwner(JSRuntime *rt)
+  : JavaScriptShared(rt),
+    inactive_(false)
+{
+}
+
+static inline WrapperOwner *
+OwnerOf(JSObject *obj)
+{
+    MOZ_ASSERT(IsCPOW(obj));
+    return reinterpret_cast<WrapperOwner *>(GetProxyExtra(obj, 0).toPrivate());
+}
+
+ObjectId
+WrapperOwner::idOf(JSObject *obj)
+{
+    MOZ_ASSERT(IsCPOW(obj));
+
+    Value v = GetProxyExtra(obj, 1);
+    MOZ_ASSERT(v.isDouble());
+
+    ObjectId objId = BitwiseCast<uint64_t>(v.toDouble());
+    MOZ_ASSERT(findCPOWById(objId) == obj);
+    MOZ_ASSERT(objId);
+
+    return objId;
+}
+
+int sCPOWProxyHandler;
+
+class CPOWProxyHandler : public BaseProxyHandler
+{
+  public:
+    CPOWProxyHandler()
+      : BaseProxyHandler(&sCPOWProxyHandler) {}
+    virtual ~CPOWProxyHandler() {}
+
+    virtual bool finalizeInBackground(Value priv) MOZ_OVERRIDE {
+        return false;
+    }
+
+    virtual bool preventExtensions(JSContext *cx, HandleObject proxy) MOZ_OVERRIDE;
+    virtual bool getPropertyDescriptor(JSContext *cx, HandleObject proxy, HandleId id,
+                                       MutableHandle<JSPropertyDescriptor> desc) MOZ_OVERRIDE;
+    virtual bool getOwnPropertyDescriptor(JSContext *cx, HandleObject proxy, HandleId id,
+                                          MutableHandle<JSPropertyDescriptor> desc) MOZ_OVERRIDE;
+    virtual bool defineProperty(JSContext *cx, HandleObject proxy, HandleId id,
+                                MutableHandle<JSPropertyDescriptor> desc) MOZ_OVERRIDE;
+    virtual bool getOwnPropertyNames(JSContext *cx, HandleObject proxy,
+                                     AutoIdVector &props) MOZ_OVERRIDE;
+    virtual bool delete_(JSContext *cx, HandleObject proxy, HandleId id, bool *bp) MOZ_OVERRIDE;
+    virtual bool enumerate(JSContext *cx, HandleObject proxy, AutoIdVector &props) MOZ_OVERRIDE;
+
+    virtual bool has(JSContext *cx, HandleObject proxy, HandleId id, bool *bp) MOZ_OVERRIDE;
+    virtual bool hasOwn(JSContext *cx, HandleObject proxy, HandleId id, bool *bp) MOZ_OVERRIDE;
+    virtual bool get(JSContext *cx, HandleObject proxy, HandleObject receiver,
+                     HandleId id, MutableHandleValue vp) MOZ_OVERRIDE;
+    virtual bool set(JSContext *cx, JS::HandleObject proxy, JS::HandleObject receiver,
+                     JS::HandleId id, bool strict, JS::MutableHandleValue vp) MOZ_OVERRIDE;
+    virtual bool keys(JSContext *cx, HandleObject proxy, AutoIdVector &props) MOZ_OVERRIDE;
+
+    virtual bool isExtensible(JSContext *cx, HandleObject proxy, bool *extensible) MOZ_OVERRIDE;
+    virtual bool call(JSContext *cx, HandleObject proxy, const CallArgs &args) MOZ_OVERRIDE;
+    virtual bool objectClassIs(HandleObject obj, js::ESClassValue classValue, JSContext *cx) MOZ_OVERRIDE;
+    virtual const char* className(JSContext *cx, HandleObject proxy) MOZ_OVERRIDE;
+    virtual void finalize(JSFreeOp *fop, JSObject *proxy) MOZ_OVERRIDE;
+
+    static CPOWProxyHandler singleton;
+};
+
+CPOWProxyHandler CPOWProxyHandler::singleton;
+
+#define FORWARD(call, args)                                             \
+    WrapperOwner *owner = OwnerOf(proxy);                               \
+    if (!owner->active()) {                                             \
+        JS_ReportError(cx, "cannot use a CPOW whose process is gone");  \
+        return false;                                                   \
+    }                                                                   \
+    return owner->call args;
+
+bool
+CPOWProxyHandler::preventExtensions(JSContext *cx, HandleObject proxy)
+{
+    FORWARD(preventExtensions, (cx, proxy));
+}
+
+bool
+WrapperOwner::preventExtensions(JSContext *cx, HandleObject proxy)
+{
+    ObjectId objId = idOf(proxy);
+
+    ReturnStatus status;
+    if (!CallPreventExtensions(objId, &status))
+        return ipcfail(cx);
+
+    return ok(cx, status);
+}
+
+bool
+CPOWProxyHandler::getPropertyDescriptor(JSContext *cx, HandleObject proxy, HandleId id,
+                                        MutableHandle<JSPropertyDescriptor> desc)
+{
+    FORWARD(getPropertyDescriptor, (cx, proxy, id, desc));
+}
+
+bool
+WrapperOwner::getPropertyDescriptor(JSContext *cx, HandleObject proxy, HandleId id,
+				    MutableHandle<JSPropertyDescriptor> desc)
+{
+    ObjectId objId = idOf(proxy);
+
+    nsString idstr;
+    if (!convertIdToGeckoString(cx, id, &idstr))
+        return false;
+
+    ReturnStatus status;
+    PPropertyDescriptor result;
+    if (!CallGetPropertyDescriptor(objId, idstr, &status, &result))
+        return ipcfail(cx);
+    if (!ok(cx, status))
+        return false;
+
+    return toDescriptor(cx, result, desc);
+}
+
+bool
+CPOWProxyHandler::getOwnPropertyDescriptor(JSContext *cx, HandleObject proxy, HandleId id,
+                                           MutableHandle<JSPropertyDescriptor> desc)
+{
+    FORWARD(getOwnPropertyDescriptor, (cx, proxy, id, desc));
+}
+
+bool
+WrapperOwner::getOwnPropertyDescriptor(JSContext *cx, HandleObject proxy, HandleId id,
+				       MutableHandle<JSPropertyDescriptor> desc)
+{
+    ObjectId objId = idOf(proxy);
+
+    nsString idstr;
+    if (!convertIdToGeckoString(cx, id, &idstr))
+        return false;
+
+    ReturnStatus status;
+    PPropertyDescriptor result;
+    if (!CallGetOwnPropertyDescriptor(objId, idstr, &status, &result))
+        return ipcfail(cx);
+    if (!ok(cx, status))
+        return false;
+
+    return toDescriptor(cx, result, desc);
+}
+
+bool
+CPOWProxyHandler::defineProperty(JSContext *cx, HandleObject proxy, HandleId id,
+                                 MutableHandle<JSPropertyDescriptor> desc)
+{
+    FORWARD(defineProperty, (cx, proxy, id, desc));
+}
+
+bool
+WrapperOwner::defineProperty(JSContext *cx, HandleObject proxy, HandleId id,
+			     MutableHandle<JSPropertyDescriptor> desc)
+{
+    ObjectId objId = idOf(proxy);
+
+    nsString idstr;
+    if (!convertIdToGeckoString(cx, id, &idstr))
+        return false;
+
+    PPropertyDescriptor descriptor;
+    if (!fromDescriptor(cx, desc, &descriptor))
+        return false;
+
+    ReturnStatus status;
+    if (!CallDefineProperty(objId, idstr, descriptor, &status))
+        return ipcfail(cx);
+
+    return ok(cx, status);
+}
+
+bool
+CPOWProxyHandler::getOwnPropertyNames(JSContext *cx, HandleObject proxy, AutoIdVector &props)
+{
+    FORWARD(getOwnPropertyNames, (cx, proxy, props));
+}
+
+bool
+WrapperOwner::getOwnPropertyNames(JSContext *cx, HandleObject proxy, AutoIdVector &props)
+{
+    return getPropertyNames(cx, proxy, JSITER_OWNONLY | JSITER_HIDDEN, props);
+}
+
+bool
+CPOWProxyHandler::delete_(JSContext *cx, HandleObject proxy, HandleId id, bool *bp)
+{
+    FORWARD(delete_, (cx, proxy, id, bp));
+}
+
+bool
+WrapperOwner::delete_(JSContext *cx, HandleObject proxy, HandleId id, bool *bp)
+{
+    ObjectId objId = idOf(proxy);
+
+    nsString idstr;
+    if (!convertIdToGeckoString(cx, id, &idstr))
+        return false;
+
+    ReturnStatus status;
+    if (!CallDelete(objId, idstr, &status, bp))
+        return ipcfail(cx);
+
+    return ok(cx, status);
+}
+
+bool
+CPOWProxyHandler::enumerate(JSContext *cx, HandleObject proxy, AutoIdVector &props)
+{
+    FORWARD(enumerate, (cx, proxy, props));
+}
+
+bool
+WrapperOwner::enumerate(JSContext *cx, HandleObject proxy, AutoIdVector &props)
+{
+    return getPropertyNames(cx, proxy, 0, props);
+}
+
+bool
+CPOWProxyHandler::has(JSContext *cx, HandleObject proxy, HandleId id, bool *bp)
+{
+    FORWARD(has, (cx, proxy, id, bp));
+}
+
+bool
+WrapperOwner::has(JSContext *cx, HandleObject proxy, HandleId id, bool *bp)
+{
+    ObjectId objId = idOf(proxy);
+
+    nsString idstr;
+    if (!convertIdToGeckoString(cx, id, &idstr))
+        return false;
+
+    ReturnStatus status;
+    if (!CallHas(objId, idstr, &status, bp))
+        return ipcfail(cx);
+
+    return ok(cx, status);
+}
+
+bool
+CPOWProxyHandler::hasOwn(JSContext *cx, HandleObject proxy, HandleId id, bool *bp)
+{
+    FORWARD(hasOwn, (cx, proxy, id, bp));
+}
+
+bool
+WrapperOwner::hasOwn(JSContext *cx, HandleObject proxy, HandleId id, bool *bp)
+{
+    ObjectId objId = idOf(proxy);
+
+    nsString idstr;
+    if (!convertIdToGeckoString(cx, id, &idstr))
+        return false;
+
+    ReturnStatus status;
+    if (!CallHasOwn(objId, idstr, &status, bp))
+        return ipcfail(cx);
+
+    return !!ok(cx, status);
+}
+
+bool
+CPOWProxyHandler::get(JSContext *cx, HandleObject proxy, HandleObject receiver,
+                      HandleId id, MutableHandleValue vp)
+{
+    FORWARD(get, (cx, proxy, receiver, id, vp));
+}
+
+bool
+WrapperOwner::get(JSContext *cx, HandleObject proxy, HandleObject receiver,
+		  HandleId id, MutableHandleValue vp)
+{
+    ObjectId objId = idOf(proxy);
+    ObjectId receiverId = idOf(receiver);
+
+    nsString idstr;
+    if (!convertIdToGeckoString(cx, id, &idstr))
+        return false;
+
+    JSVariant val;
+    ReturnStatus status;
+    if (!CallGet(objId, receiverId, idstr, &status, &val))
+        return ipcfail(cx);
+
+    if (!ok(cx, status))
+        return false;
+
+    return fromVariant(cx, val, vp);
+}
+
+bool
+CPOWProxyHandler::set(JSContext *cx, JS::HandleObject proxy, JS::HandleObject receiver,
+                      JS::HandleId id, bool strict, JS::MutableHandleValue vp)
+{
+    FORWARD(set, (cx, proxy, receiver, id, strict, vp));
+}
+
+bool
+WrapperOwner::set(JSContext *cx, JS::HandleObject proxy, JS::HandleObject receiver,
+		  JS::HandleId id, bool strict, JS::MutableHandleValue vp)
+{
+    ObjectId objId = idOf(proxy);
+    ObjectId receiverId = idOf(receiver);
+
+    nsString idstr;
+    if (!convertIdToGeckoString(cx, id, &idstr))
+        return false;
+
+    JSVariant val;
+    if (!toVariant(cx, vp, &val))
+        return false;
+
+    ReturnStatus status;
+    JSVariant result;
+    if (!CallSet(objId, receiverId, idstr, strict, val, &status, &result))
+        return ipcfail(cx);
+
+    if (!ok(cx, status))
+        return false;
+
+    return fromVariant(cx, result, vp);
+}
+
+bool
+CPOWProxyHandler::keys(JSContext *cx, HandleObject proxy, AutoIdVector &props)
+{
+    FORWARD(keys, (cx, proxy, props));
+}
+
+bool
+WrapperOwner::keys(JSContext *cx, HandleObject proxy, AutoIdVector &props)
+{
+    return getPropertyNames(cx, proxy, JSITER_OWNONLY, props);
+}
+
+bool
+CPOWProxyHandler::isExtensible(JSContext *cx, HandleObject proxy, bool *extensible)
+{
+    FORWARD(isExtensible, (cx, proxy, extensible));
+}
+
+bool
+WrapperOwner::isExtensible(JSContext *cx, HandleObject proxy, bool *extensible)
+{
+    ObjectId objId = idOf(proxy);
+
+    ReturnStatus status;
+    if (!CallIsExtensible(objId, &status, extensible))
+        return ipcfail(cx);
+
+    return ok(cx, status);
+}
+
+bool
+CPOWProxyHandler::call(JSContext *cx, HandleObject proxy, const CallArgs &args)
+{
+    FORWARD(call, (cx, proxy, args));
+}
+
+bool
+WrapperOwner::call(JSContext *cx, HandleObject proxy, const CallArgs &args)
+{
+    ObjectId objId = idOf(proxy);
+
+    InfallibleTArray<JSParam> vals;
+    AutoValueVector outobjects(cx);
+
+    RootedValue v(cx);
+    for (size_t i = 0; i < args.length() + 2; i++) {
+        v = args.base()[i];
+        if (v.isObject()) {
+            RootedObject obj(cx, &v.toObject());
+            if (xpc::IsOutObject(cx, obj)) {
+                // Make sure it is not an in-out object.
+                bool found;
+                if (!JS_HasProperty(cx, obj, "value", &found))
+                    return false;
+                if (found) {
+                    JS_ReportError(cx, "in-out objects cannot be sent via CPOWs yet");
+                    return false;
+                }
+
+                vals.AppendElement(JSParam(void_t()));
+                if (!outobjects.append(ObjectValue(*obj)))
+                    return false;
+                continue;
+            }
+        }
+        JSVariant val;
+        if (!toVariant(cx, v, &val))
+            return false;
+        vals.AppendElement(JSParam(val));
+    }
+
+    JSVariant result;
+    ReturnStatus status;
+    InfallibleTArray<JSParam> outparams;
+    if (!CallCall(objId, vals, &status, &result, &outparams))
+        return ipcfail(cx);
+    if (!ok(cx, status))
+        return false;
+
+    if (outparams.Length() != outobjects.length())
+        return ipcfail(cx);
+
+    RootedObject obj(cx);
+    for (size_t i = 0; i < outparams.Length(); i++) {
+        // Don't bother doing anything for outparams that weren't set.
+        if (outparams[i].type() == JSParam::Tvoid_t)
+            continue;
+
+        // Take the value the child process returned, and set it on the XPC
+        // object.
+        if (!fromVariant(cx, outparams[i], &v))
+            return false;
+
+        obj = &outobjects[i].toObject();
+        if (!JS_SetProperty(cx, obj, "value", v))
+            return false;
+    }
+
+    if (!fromVariant(cx, result, args.rval()))
+        return false;
+
+    return true;
+}
+
+
+bool
+CPOWProxyHandler::objectClassIs(HandleObject proxy, js::ESClassValue classValue, JSContext *cx)
+{
+    FORWARD(objectClassIs, (cx, proxy, classValue));
+}
+
+bool
+WrapperOwner::objectClassIs(JSContext *cx, HandleObject proxy, js::ESClassValue classValue)
+{
+    ObjectId objId = idOf(proxy);
+
+    // This function is assumed infallible, so we just return false if the IPC
+    // channel fails.
+    bool result;
+    if (!CallObjectClassIs(objId, classValue, &result))
+        return false;
+
+    return result;
+}
+
+const char *
+CPOWProxyHandler::className(JSContext *cx, HandleObject proxy)
+{
+    WrapperOwner *parent = OwnerOf(proxy);
+    if (!parent->active())
+        return "<dead CPOW>";
+    return parent->className(cx, proxy);
+}
+
+const char *
+WrapperOwner::className(JSContext *cx, HandleObject proxy)
+{
+    ObjectId objId = idOf(proxy);
+
+    nsString name;
+    if (!CallClassName(objId, &name))
+        return "<error>";
+
+    return ToNewCString(name);
+}
+
+void
+CPOWProxyHandler::finalize(JSFreeOp *fop, JSObject *proxy)
+{
+    OwnerOf(proxy)->drop(proxy);
+}
+
+void
+WrapperOwner::drop(JSObject *obj)
+{
+    ObjectId objId = idOf(obj);
+
+    cpows_.remove(objId);
+    if (active())
+        unused << SendDropObject(objId);
+    decref();
+}
+
+bool
+WrapperOwner::init()
+{
+    if (!JavaScriptShared::init())
+        return false;
+
+    return true;
+}
+
+bool
+WrapperOwner::getPropertyNames(JSContext *cx, HandleObject proxy, uint32_t flags, AutoIdVector &props)
+{
+    ObjectId objId = idOf(proxy);
+
+    ReturnStatus status;
+    InfallibleTArray<nsString> names;
+    if (!CallGetPropertyNames(objId, flags, &status, &names))
+        return ipcfail(cx);
+    if (!ok(cx, status))
+        return false;
+
+    for (size_t i = 0; i < names.Length(); i++) {
+        RootedId name(cx);
+        if (!convertGeckoStringToId(cx, names[i], &name))
+            return false;
+        if (!props.append(name))
+            return false;
+    }
+
+    return true;
+}
+
+namespace mozilla {
+namespace jsipc {
+
+bool
+IsCPOW(JSObject *obj)
+{
+    return IsProxy(obj) && GetProxyHandler(obj) == &CPOWProxyHandler::singleton;
+}
+
+nsresult
+InstanceOf(JSObject *proxy, const nsID *id, bool *bp)
+{
+    WrapperOwner *parent = OwnerOf(proxy);
+    if (!parent->active())
+        return NS_ERROR_UNEXPECTED;
+    return parent->instanceOf(proxy, id, bp);
+}
+
+bool
+DOMInstanceOf(JSContext *cx, JSObject *proxy, int prototypeID, int depth, bool *bp)
+{
+    FORWARD(domInstanceOf, (cx, proxy, prototypeID, depth, bp));
+}
+
+} /* namespace jsipc */
+} /* namespace mozilla */
+
+nsresult
+WrapperOwner::instanceOf(JSObject *obj, const nsID *id, bool *bp)
+{
+    ObjectId objId = idOf(obj);
+
+    JSIID iid;
+    ConvertID(*id, &iid);
+
+    ReturnStatus status;
+    if (!CallInstanceOf(objId, iid, &status, bp))
+        return NS_ERROR_UNEXPECTED;
+
+    if (status.type() != ReturnStatus::TReturnSuccess)
+        return NS_ERROR_UNEXPECTED;
+
+    return NS_OK;
+}
+
+bool
+WrapperOwner::domInstanceOf(JSContext *cx, JSObject *obj, int prototypeID, int depth, bool *bp)
+{
+    ObjectId objId = idOf(obj);
+
+    ReturnStatus status;
+    if (!CallDOMInstanceOf(objId, prototypeID, depth, &status, bp))
+        return ipcfail(cx);
+
+    return ok(cx, status);
+}
+
+void
+WrapperOwner::ActorDestroy(ActorDestroyReason why)
+{
+    inactive_ = true;
+}
+
+bool
+WrapperOwner::ipcfail(JSContext *cx)
+{
+    JS_ReportError(cx, "child process crashed or timedout");
+    return false;
+}
+
+bool
+WrapperOwner::ok(JSContext *cx, const ReturnStatus &status)
+{
+    if (status.type() == ReturnStatus::TReturnSuccess)
+        return true;
+
+    if (status.type() == ReturnStatus::TReturnStopIteration)
+        return JS_ThrowStopIteration(cx);
+
+    RootedValue exn(cx);
+    if (!fromVariant(cx, status.get_ReturnException().exn(), &exn))
+        return false;
+
+    JS_SetPendingException(cx, exn);
+    return false;
+}
+
+bool
+WrapperOwner::toObjectVariant(JSContext *cx, JSObject *objArg, ObjectVariant *objVarp)
+{
+    RootedObject obj(cx, objArg);
+    JS_ASSERT(obj);
+
+    // We always save objects unwrapped in the CPOW table. If we stored
+    // wrappers, then the wrapper might be GCed while the target remained alive.
+    // Whenever operating on an object that comes from the table, we wrap it
+    // in findObjectById.
+    obj = js::CheckedUnwrap(obj, false);
+    if (obj && IsCPOW(obj) && OwnerOf(obj) == this) {
+        *objVarp = LocalObject(idOf(obj));
+        return true;
+    }
+
+    ObjectId id = objectIds_.find(obj);
+    if (id) {
+        *objVarp = RemoteObject(id);
+        return true;
+    }
+
+    // Need to call PreserveWrapper on |obj| in case it's a reflector.
+    // FIXME: What if it's an XPCWrappedNative?
+    if (mozilla::dom::IsDOMObject(obj))
+        mozilla::dom::TryPreserveWrapper(obj);
+
+    id = ++lastId_;
+    if (id > MAX_CPOW_IDS) {
+        JS_ReportError(cx, "CPOW id limit reached");
+        return false;
+    }
+
+    id <<= OBJECT_EXTRA_BITS;
+    if (JS_ObjectIsCallable(cx, obj))
+        id |= OBJECT_IS_CALLABLE;
+
+    if (!objects_.add(id, obj))
+        return false;
+    if (!objectIds_.add(cx, obj, id))
+        return false;
+
+    *objVarp = RemoteObject(id);
+    return true;
+}
+
+JSObject *
+WrapperOwner::fromObjectVariant(JSContext *cx, ObjectVariant objVar)
+{
+    if (objVar.type() == ObjectVariant::TRemoteObject) {
+        return fromRemoteObjectVariant(cx, objVar.get_RemoteObject());
+    } else {
+        return fromLocalObjectVariant(cx, objVar.get_LocalObject());
+    }
+}
+
+JSObject *
+WrapperOwner::fromRemoteObjectVariant(JSContext *cx, RemoteObject objVar)
+{
+    ObjectId objId = objVar.id();
+
+    RootedObject obj(cx, findCPOWById(objId));
+    if (obj) {
+        if (!JS_WrapObject(cx, &obj))
+            return nullptr;
+        return obj;
+    }
+
+    if (objId > MAX_CPOW_IDS) {
+        JS_ReportError(cx, "unusable CPOW id");
+        return nullptr;
+    }
+
+    bool callable = !!(objId & OBJECT_IS_CALLABLE);
+
+    RootedObject global(cx, JS::CurrentGlobalOrNull(cx));
+
+    RootedValue v(cx, UndefinedValue());
+    ProxyOptions options;
+    options.selectDefaultClass(callable);
+    obj = NewProxyObject(cx,
+                         &CPOWProxyHandler::singleton,
+                         v,
+                         nullptr,
+                         global,
+                         options);
+    if (!obj)
+        return nullptr;
+
+    if (!cpows_.add(objId, obj))
+        return nullptr;
+
+    // Incref once we know the decref will be called.
+    incref();
+
+    SetProxyExtra(obj, 0, PrivateValue(this));
+    SetProxyExtra(obj, 1, DoubleValue(BitwiseCast<double>(objId)));
+    return obj;
+}
+
+JSObject *
+WrapperOwner::fromLocalObjectVariant(JSContext *cx, LocalObject objVar)
+{
+    ObjectId id = objVar.id();
+    Rooted<JSObject*> obj(cx, findObjectById(cx, id));
+    if (!obj)
+        return nullptr;
+    if (!JS_WrapObject(cx, &obj))
+        return nullptr;
+    return obj;
+}
new file mode 100644
--- /dev/null
+++ b/js/ipc/WrapperOwner.h
@@ -0,0 +1,153 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ * vim: set ts=8 sw=4 et tw=80:
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_jsipc_WrapperOwner_h__
+#define mozilla_jsipc_WrapperOwner_h__
+
+#include "JavaScriptShared.h"
+#include "mozilla/ipc/ProtocolUtils.h"
+#include "js/Class.h"
+
+#ifdef XP_WIN
+#undef GetClassName
+#undef GetClassInfo
+#endif
+
+namespace mozilla {
+namespace jsipc {
+
+class WrapperOwner : public virtual JavaScriptShared
+{
+  public:
+    typedef mozilla::ipc::IProtocolManager<
+                       mozilla::ipc::IProtocol>::ActorDestroyReason
+           ActorDestroyReason;
+
+    WrapperOwner(JSRuntime *rt);
+    bool init();
+
+    // Fundamental proxy traps. These are required.
+    // (The traps should be in the same order like js/src/jsproxy.h)
+    bool preventExtensions(JSContext *cx, JS::HandleObject proxy);
+    bool getPropertyDescriptor(JSContext *cx, JS::HandleObject proxy, JS::HandleId id,
+                               JS::MutableHandle<JSPropertyDescriptor> desc);
+    bool getOwnPropertyDescriptor(JSContext *cx, JS::HandleObject proxy, JS::HandleId id,
+                                  JS::MutableHandle<JSPropertyDescriptor> desc);
+    bool defineProperty(JSContext *cx, JS::HandleObject proxy, JS::HandleId id,
+                        JS::MutableHandle<JSPropertyDescriptor> desc);
+    bool getOwnPropertyNames(JSContext *cx, JS::HandleObject proxy, JS::AutoIdVector &props);
+    bool delete_(JSContext *cx, JS::HandleObject proxy, JS::HandleId id, bool *bp);
+    bool enumerate(JSContext *cx, JS::HandleObject proxy, JS::AutoIdVector &props);
+
+    // Derived proxy traps. Implementing these is useful for perfomance.
+    bool has(JSContext *cx, JS::HandleObject proxy, JS::HandleId id, bool *bp);
+    bool hasOwn(JSContext *cx, JS::HandleObject proxy, JS::HandleId id, bool *bp);
+    bool get(JSContext *cx, JS::HandleObject proxy, JS::HandleObject receiver,
+             JS::HandleId id, JS::MutableHandleValue vp);
+    bool set(JSContext *cx, JS::HandleObject proxy, JS::HandleObject receiver,
+             JS::HandleId id, bool strict, JS::MutableHandleValue vp);
+    bool keys(JSContext *cx, JS::HandleObject proxy, JS::AutoIdVector &props);
+    // We use "iterate" provided by the base class here.
+
+    // SpiderMonkey Extensions.
+    bool isExtensible(JSContext *cx, JS::HandleObject proxy, bool *extensible);
+    bool call(JSContext *cx, JS::HandleObject proxy, const JS::CallArgs &args);
+    bool objectClassIs(JSContext *cx, JS::HandleObject obj, js::ESClassValue classValue);
+    const char* className(JSContext *cx, JS::HandleObject proxy);
+
+    nsresult instanceOf(JSObject *obj, const nsID *id, bool *bp);
+
+    /*
+     * Check that |obj| is a DOM wrapper whose prototype chain contains
+     * |prototypeID| at depth |depth|.
+     */
+    bool domInstanceOf(JSContext *cx, JSObject *obj, int prototypeID, int depth, bool *bp);
+
+    bool active() { return !inactive_; }
+
+    void drop(JSObject *obj);
+
+    virtual void ActorDestroy(ActorDestroyReason why);
+
+    virtual bool toObjectVariant(JSContext *cx, JSObject *obj, ObjectVariant *objVarp);
+    virtual JSObject *fromObjectVariant(JSContext *cx, ObjectVariant objVar);
+    JSObject *fromRemoteObjectVariant(JSContext *cx, RemoteObject objVar);
+    JSObject *fromLocalObjectVariant(JSContext *cx, LocalObject objVar);
+
+  protected:
+    ObjectId idOf(JSObject *obj);
+
+  private:
+    bool getPropertyNames(JSContext *cx, JS::HandleObject proxy, uint32_t flags,
+                          JS::AutoIdVector &props);
+
+    // Catastrophic IPC failure.
+    bool ipcfail(JSContext *cx);
+
+    // Check whether a return status is okay, and if not, propagate its error.
+    bool ok(JSContext *cx, const ReturnStatus &status);
+
+    bool inactive_;
+
+    /*** Dummy call handlers ***/
+  public:
+    virtual bool SendDropObject(const ObjectId &objId) = 0;
+    virtual bool CallPreventExtensions(const ObjectId &objId, ReturnStatus *rs) = 0;
+    virtual bool CallGetPropertyDescriptor(const ObjectId &objId, const nsString &id,
+                                           ReturnStatus *rs,
+                                           PPropertyDescriptor *out) = 0;
+    virtual bool CallGetOwnPropertyDescriptor(const ObjectId &objId,
+                                              const nsString &id,
+                                              ReturnStatus *rs,
+                                              PPropertyDescriptor *out) = 0;
+    virtual bool CallDefineProperty(const ObjectId &objId, const nsString &id,
+                                    const PPropertyDescriptor &flags,
+                                    ReturnStatus *rs) = 0;
+    virtual bool CallDelete(const ObjectId &objId, const nsString &id,
+                            ReturnStatus *rs, bool *success) = 0;
+
+    virtual bool CallHas(const ObjectId &objId, const nsString &id,
+                         ReturnStatus *rs, bool *bp) = 0;
+    virtual bool CallHasOwn(const ObjectId &objId, const nsString &id,
+                            ReturnStatus *rs, bool *bp) = 0;
+    virtual bool CallGet(const ObjectId &objId, const ObjectId &receiverId,
+                         const nsString &id,
+                         ReturnStatus *rs, JSVariant *result) = 0;
+    virtual bool CallSet(const ObjectId &objId, const ObjectId &receiverId,
+                         const nsString &id, const bool &strict,
+                         const JSVariant &value, ReturnStatus *rs, JSVariant *result) = 0;
+
+    virtual bool CallIsExtensible(const ObjectId &objId, ReturnStatus *rs,
+                                  bool *result) = 0;
+    virtual bool CallCall(const ObjectId &objId, const nsTArray<JSParam> &argv,
+                          ReturnStatus *rs, JSVariant *result,
+                          nsTArray<JSParam> *outparams) = 0;
+    virtual bool CallObjectClassIs(const ObjectId &objId, const uint32_t &classValue,
+                                   bool *result) = 0;
+    virtual bool CallClassName(const ObjectId &objId, nsString *result) = 0;
+
+    virtual bool CallGetPropertyNames(const ObjectId &objId, const uint32_t &flags,
+                                      ReturnStatus *rs, nsTArray<nsString> *names) = 0;
+    virtual bool CallInstanceOf(const ObjectId &objId, const JSIID &iid,
+                                ReturnStatus *rs, bool *instanceof) = 0;
+    virtual bool CallDOMInstanceOf(const ObjectId &objId, const int &prototypeID, const int &depth,
+                                   ReturnStatus *rs, bool *instanceof) = 0;
+};
+
+bool
+IsCPOW(JSObject *obj);
+
+nsresult
+InstanceOf(JSObject *obj, const nsID *id, bool *bp);
+
+bool
+DOMInstanceOf(JSContext *cx, JSObject *obj, int prototypeID, int depth, bool *bp);
+
+} // jsipc
+} // mozilla
+
+#endif // mozilla_jsipc_WrapperOwner_h__
--- a/js/ipc/moz.build
+++ b/js/ipc/moz.build
@@ -3,16 +3,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/.
 
 UNIFIED_SOURCES += [
     'JavaScriptChild.cpp',
     'JavaScriptParent.cpp',
     'JavaScriptShared.cpp',
+    'WrapperAnswer.cpp',
+    'WrapperOwner.cpp',
 ]
 
 IPDL_SOURCES += [
     'JavaScriptTypes.ipdlh',
     'PJavaScript.ipdl',
 ]
 
 FAIL_ON_WARNINGS = True
--- a/js/src/gc/GCRuntime.h
+++ b/js/src/gc/GCRuntime.h
@@ -80,16 +80,32 @@ struct ConservativeGCData
     }
 #endif
 
     bool hasStackToScan() const {
         return !!nativeStackTop;
     }
 };
 
+template<typename F>
+struct Callback {
+    F op;
+    void *data;
+
+    Callback()
+      : op(nullptr), data(nullptr)
+    {}
+    Callback(F op, void *data)
+      : op(op), data(data)
+    {}
+};
+
+template<typename F>
+class CallbackVector : public Vector<Callback<F>, 4, SystemAllocPolicy> {};
+
 class GCRuntime
 {
   public:
     GCRuntime(JSRuntime *rt);
     bool init(uint32_t maxbytes);
     void finish();
 
     void setGCZeal(uint8_t zeal, uint32_t frequency);
@@ -393,20 +409,20 @@ class GCRuntime
 
     js::Vector<JSObject *, 0, js::SystemAllocPolicy>   selectedForMarking;
 #endif
 
     bool                  validate;
     bool                  fullCompartmentChecks;
 
     JSGCCallback          gcCallback;
+    void                  *gcCallbackData;
+
     JS::GCSliceCallback   sliceCallback;
-    JSFinalizeCallback    finalizeCallback;
-
-    void                  *gcCallbackData;
+    CallbackVector<JSFinalizeCallback> finalizeCallbacks;
 
     /*
      * Malloc counter to measure memory pressure for GC scheduling. It runs
      * from   maxMallocBytes down to zero.
      */
     mozilla::Atomic<ptrdiff_t, mozilla::ReleaseAcquire>   mallocBytes;
 
     /*
@@ -416,19 +432,18 @@ class GCRuntime
     mozilla::Atomic<bool, mozilla::ReleaseAcquire>   mallocGCTriggered;
 
     /*
      * The trace operations to trace embedding-specific GC roots. One is for
      * tracing through black roots and the other is for tracing through gray
      * roots. The black/gray distinction is only relevant to the cycle
      * collector.
      */
-    typedef js::Vector<ExtraTracer, 4, js::SystemAllocPolicy> ExtraTracerVector;
-    ExtraTracerVector     blackRootTracers;
-    ExtraTracer           grayRootTracer;
+    CallbackVector<JSTraceDataOp> blackRootTracers;
+    Callback<JSTraceDataOp> grayRootTracer;
 
     /*
      * The GC can only safely decommit memory when the page size of the
      * running process matches the compiled arena size.
      */
     size_t                systemPageSize;
 
     /* The OS allocation granularity may not match the page size. */
--- a/js/src/gc/RootMarking.cpp
+++ b/js/src/gc/RootMarking.cpp
@@ -780,17 +780,17 @@ js::gc::MarkRuntime(JSTracer *trc, bool 
         /*
          * The embedding can register additional roots here.
          *
          * We don't need to trace these in a minor GC because all pointers into
          * the nursery should be in the store buffer, and we want to avoid the
          * time taken to trace all these roots.
          */
         for (size_t i = 0; i < rt->gc.blackRootTracers.length(); i++) {
-            const ExtraTracer &e = rt->gc.blackRootTracers[i];
+            const Callback<JSTraceDataOp> &e = rt->gc.blackRootTracers[i];
             (*e.op)(trc, e.data);
         }
 
         /* During GC, we don't mark gray roots at this stage. */
         if (JSTraceDataOp op = rt->gc.grayRootTracer.op) {
             if (!IS_GC_MARKING_TRACER(trc))
                 (*op)(trc, rt->gc.grayRootTracer.data);
         }
--- a/js/src/gc/Zone.cpp
+++ b/js/src/gc/Zone.cpp
@@ -199,17 +199,17 @@ Zone::discardJitCode(FreeOp *fop)
             JSScript *script = i.get<JSScript>();
             jit::FinishInvalidation<SequentialExecution>(fop, script);
 
             // Preserve JIT code that have been recently used in
             // parallel. Note that we mark their baseline scripts as active as
             // well to preserve them.
             if (script->hasParallelIonScript()) {
                 if (jit::ShouldPreserveParallelJITCode(runtimeFromMainThread(), script)) {
-                    script->parallelIonScript()->purgeCaches(this);
+                    script->parallelIonScript()->purgeCaches();
                     script->baselineScript()->setActive();
                 } else {
                     jit::FinishInvalidation<ParallelExecution>(fop, script);
                 }
             }
 
             /*
              * Discard baseline script if it's not marked as active. Note that
--- a/js/src/irregexp/NativeRegExpMacroAssembler.cpp
+++ b/js/src/irregexp/NativeRegExpMacroAssembler.cpp
@@ -27,16 +27,19 @@
 // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
 #include "irregexp/NativeRegExpMacroAssembler.h"
 
 #include "irregexp/RegExpStack.h"
 #include "jit/IonLinker.h"
+#ifdef JS_ION_PERF
+# include "jit/PerfSpewer.h"
+#endif
 #include "vm/MatchPairs.h"
 
 using namespace js;
 using namespace js::irregexp;
 using namespace js::jit;
 
 /*
  * This assembler uses the following register assignment convention:
@@ -428,20 +431,25 @@ NativeRegExpMacroAssembler::GenerateCode
         masm.bind(&exit_with_exception_label_);
 
         // Exit with an error result to signal thrown exception.
         masm.mov(ImmWord(RegExpRunStatus_Error), temp0);
         masm.jump(&return_temp0);
     }
 
     Linker linker(masm);
+    AutoFlushICache afc("RegExp");
     JitCode *code = linker.newCode<NoGC>(cx, JSC::REGEXP_CODE);
     if (!code)
         return RegExpCode();
 
+#ifdef JS_ION_PERF
+    writePerfSpewerJitCodeProfile(code, "RegExp");
+#endif
+
     for (size_t i = 0; i < labelPatches.length(); i++) {
         const LabelPatch &v = labelPatches[i];
         JS_ASSERT(!v.label);
         Assembler::patchDataWithValueCheck(CodeLocationLabel(code, v.patchOffset),
                                            ImmPtr(code->raw() + v.labelOffset),
                                            ImmPtr(0));
     }
 
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/saved-stacks/bug-1006876-too-much-recursion.js
@@ -0,0 +1,10 @@
+// |jit-test| exitstatus: 3
+
+// This test case was found by the fuzzer and crashed the js shell. It should
+// throw a "too much recursion" error, but was crashing instead.
+
+enableTrackAllocations();
+function f() {
+    f();
+}
+f();
--- a/js/src/jit/AsmJS.cpp
+++ b/js/src/jit/AsmJS.cpp
@@ -7017,16 +7017,19 @@ CheckModule(ExclusiveContext *cx, AsmJSP
 
     if (!CheckModuleReturn(m))
         return false;
 
     TokenKind tk = PeekToken(m.parser());
     if (tk != TOK_EOF && tk != TOK_RC)
         return m.fail(nullptr, "top-level export (return) must be the last statement");
 
+    // The instruction cache is flushed when dynamically linking, so can inhibit now.
+    AutoFlushICache afc("CheckModule", /* inhibit= */ true);
+
     ScopedJSDeletePtr<AsmJSModule> module;
     if (!FinishModule(m, &module))
         return false;
 
     bool storedInCache = StoreAsmJSModuleInCache(parser, *module, cx);
     module->staticallyLink(cx);
 
     m.buildCompilationTimeReport(storedInCache, compilationTimeReport);
--- a/js/src/jit/AsmJSLink.cpp
+++ b/js/src/jit/AsmJSLink.cpp
@@ -764,22 +764,34 @@ LinkAsmJS(JSContext *cx, unsigned argc, 
 {
     CallArgs args = CallArgsFromVp(argc, vp);
 
     // The LinkAsmJS builtin (created by NewAsmJSModuleFunction) is an extended
     // function and stores its module in an extended slot.
     RootedFunction fun(cx, &args.callee().as<JSFunction>());
     Rooted<AsmJSModuleObject*> moduleObj(cx, &ModuleFunctionToModuleObject(fun));
 
+    // All ICache flushing of the module being linked has been inhibited under the
+    // assumption that the module is flushed after dynamic linking (when the last code
+    // mutation occurs).  Thus, enter an AutoFlushICache context for the entire module
+    // now.  The module range is set below.
+    AutoFlushICache afc("LinkAsmJS");
+
     // When a module is linked, it is dynamically specialized to the given
     // arguments (buffer, ffis). Thus, if the module is linked again (it is just
     // a function so it can be called multiple times), we need to clone a new
     // module.
-    if (moduleObj->module().isDynamicallyLinked() && !CloneModule(cx, &moduleObj))
-        return false;
+    if (moduleObj->module().isDynamicallyLinked()) {
+        if (!CloneModule(cx, &moduleObj))
+            return false;
+    } else {
+        // CloneModule already calls setAutoFlushICacheRange internally before patching
+        // the cloned module, so avoid calling twice.
+        moduleObj->module().setAutoFlushICacheRange();
+    }
 
     AsmJSModule &module = moduleObj->module();
 
     // Link the module by performing the link-time validation checks in the
     // asm.js spec and then patching the generated module to associate it with
     // the given heap (ArrayBuffer) and a new global data segment (the closure
     // state shared by the inner asm.js functions).
     if (!DynamicallyLinkModule(cx, args, module)) {
--- a/js/src/jit/AsmJSModule.cpp
+++ b/js/src/jit/AsmJSModule.cpp
@@ -59,19 +59,16 @@ AsmJSModule::initHeap(Handle<ArrayBuffer
         JSC::X86Assembler::setPointer(addr, (void *)(heapOffset + disp));
     }
 #elif defined(JS_CODEGEN_ARM) || defined(JS_CODEGEN_MIPS)
     uint32_t heapLength = heap->byteLength();
     for (unsigned i = 0; i < heapAccesses_.length(); i++) {
         jit::Assembler::updateBoundsCheck(heapLength,
                                           (jit::Instruction*)(heapAccesses_[i].offset() + code_));
     }
-    // We already know the exact extent of areas that need to be patched, just make sure we
-    // flush all of them at once.
-    jit::AutoFlushCache::updateTop(uintptr_t(code_), pod.codeBytes_);
 #endif
 }
 
 static uint8_t *
 AllocateExecutableMemory(ExclusiveContext *cx, size_t totalBytes)
 {
     JS_ASSERT(totalBytes % AsmJSPageSize == 0);
 
@@ -297,16 +294,22 @@ AsmJSModule::restoreToInitialState(Array
             JS_ASSERT(ptr >= ptrBase);
             JSC::X86Assembler::setPointer(addr, (void *)(ptr - ptrBase));
         }
 #endif
     }
 }
 
 void
+AsmJSModule::setAutoFlushICacheRange()
+{
+    AutoFlushICache::setRange(uintptr_t(code_), pod.codeBytes_);
+}
+
+void
 AsmJSModule::staticallyLink(ExclusiveContext *cx)
 {
     // Process staticLinkData_
 
     interruptExit_ = code_ + staticLinkData_.interruptExitOffset;
 
     for (size_t i = 0; i < staticLinkData_.relativeLinks.length(); i++) {
         RelativeLink link = staticLinkData_.relativeLinks[i];
@@ -865,16 +868,17 @@ AsmJSModule::deserialize(ExclusiveContex
     (cursor = DeserializeVector(cx, cursor, &functionNames_)) &&
     (cursor = DeserializePodVector(cx, cursor, &heapAccesses_)) &&
 #if defined(MOZ_VTUNE) || defined(JS_ION_PERF)
     (cursor = DeserializeVector(cx, cursor, &profiledFunctions_)) &&
 #endif
     (cursor = staticLinkData_.deserialize(cx, cursor));
 
     loadedFromCache_ = true;
+
     return cursor;
 }
 
 // When a module is cloned, we memcpy its executable code. If, right before or
 // during the clone, another thread calls AsmJSModule::protectCode() then the
 // executable code will become inaccessible. In theory, we could take away only
 // PROT_EXEC, but this seems to break emulators.
 class AutoUnprotectCodeForClone
@@ -935,16 +939,20 @@ AsmJSModule::clone(JSContext *cx, Scoped
         !ClonePodVector(cx, heapAccesses_, &out.heapAccesses_) ||
         !staticLinkData_.clone(cx, &out.staticLinkData_))
     {
         return false;
     }
 
     out.loadedFromCache_ = loadedFromCache_;
 
+    // We already know the exact extent of areas that need to be patched, just make sure we
+    // flush all of them at once.
+    out.setAutoFlushICacheRange();
+
     out.restoreToInitialState(maybeHeap_, cx);
     return true;
 }
 
 void
 AsmJSModule::protectCode(JSRuntime *rt) const
 {
     JS_ASSERT(rt->currentThreadOwnsInterruptLock());
@@ -1347,16 +1355,23 @@ js::LookupAsmJSModuleInCache(ExclusiveCo
     uint32_t funcStart = parser.pc->maybeFunction->pn_body->pn_pos.begin;
     uint32_t offsetToEndOfUseAsm = parser.tokenStream.currentToken().pos.end;
     bool strict = parser.pc->sc->strict && !parser.pc->sc->hasExplicitUseStrict();
     ScopedJSDeletePtr<AsmJSModule> module(
         cx->new_<AsmJSModule>(parser.ss, funcStart, offsetToEndOfUseAsm, strict));
     if (!module)
         return false;
     cursor = module->deserialize(cx, cursor);
+
+    // No need to flush the instruction cache now, it will be flushed when dynamically linking.
+    AutoFlushICache afc("LookupAsmJSModuleInCache", /* inhibit= */ true);
+    // We already know the exact extent of areas that need to be patched, just make sure we
+    // flush all of them at once.
+    module->setAutoFlushICacheRange();
+
     if (!cursor)
         return false;
 
     bool atEnd = cursor == entry.memory + entry.serializedSize;
     MOZ_ASSERT(atEnd, "Corrupt cache file");
     if (!atEnd)
         return true;
 
--- a/js/src/jit/AsmJSModule.h
+++ b/js/src/jit/AsmJSModule.h
@@ -818,16 +818,17 @@ class AsmJSModule
     bool addAbsoluteLink(AbsoluteLink link) {
         return staticLinkData_.absoluteLinks.append(link);
     }
     void setInterruptOffset(uint32_t offset) {
         staticLinkData_.interruptExitOffset = offset;
     }
 
     void restoreToInitialState(ArrayBufferObject *maybePrevBuffer, ExclusiveContext *cx);
+    void setAutoFlushICacheRange();
     void staticallyLink(ExclusiveContext *cx);
 
     uint8_t *codeBase() const {
         JS_ASSERT(code_);
         JS_ASSERT(uintptr_t(code_) % AsmJSPageSize == 0);
         return code_;
     }
 
--- a/js/src/jit/BaselineCompiler.cpp
+++ b/js/src/jit/BaselineCompiler.cpp
@@ -102,16 +102,17 @@ BaselineCompiler::compile()
     if (!emitOutOfLinePostBarrierSlot())
         return Method_Error;
 #endif
 
     if (masm.oom())
         return Method_Error;
 
     Linker linker(masm);
+    AutoFlushICache afc("Baseline");
     JitCode *code = linker.newCode<CanGC>(cx, JSC::BASELINE_CODE);
     if (!code)
         return Method_Error;
 
     JSObject *templateScope = nullptr;
     if (script->functionNonDelazifying()) {
         RootedFunction fun(cx, script->functionNonDelazifying());
         if (fun->isHeavyweight()) {
--- a/js/src/jit/BaselineDebugModeOSR.cpp
+++ b/js/src/jit/BaselineDebugModeOSR.cpp
@@ -785,16 +785,17 @@ JitRuntime::generateBaselineDebugModeOSR
     masm.pop(target);
     masm.pop(BaselineFrameReg);
     masm.popValue(R1);
     masm.popValue(R0);
 
     masm.jump(target);
 
     Linker linker(masm);
+    AutoFlushICache afc("BaselineDebugModeOSRHandler");
     JitCode *code = linker.newCode<NoGC>(cx, JSC::OTHER_CODE);
     if (!code)
         return nullptr;
 
     noFrameRegPopOffset.fixup(&masm);
     *noFrameRegPopOffsetOut = noFrameRegPopOffset.offset();
 
 #ifdef JS_ION_PERF
--- a/js/src/jit/BaselineIC.cpp
+++ b/js/src/jit/BaselineIC.cpp
@@ -586,20 +586,20 @@ ICStubCompiler::getStubCode()
 
     // Compile new stubcode.
     IonContext ictx(cx, nullptr);
     MacroAssembler masm;
 #ifdef JS_CODEGEN_ARM
     masm.setSecondScratchReg(BaselineSecondScratchReg);
 #endif
 
-    AutoFlushCache afc("ICStubCompiler::getStubCode", cx->runtime()->jitRuntime());
     if (!generateStubCode(masm))
         return nullptr;
     Linker linker(masm);
+    AutoFlushICache afc("getStubCode");
     Rooted<JitCode *> newStubCode(cx, linker.newCode<CanGC>(cx, JSC::BASELINE_CODE));
     if (!newStubCode)
         return nullptr;
 
     // After generating code, run postGenerateStubCode()
     if (!postGenerateStubCode(masm, newStubCode))
         return nullptr;
 
--- a/js/src/jit/BaselineJIT.cpp
+++ b/js/src/jit/BaselineJIT.cpp
@@ -108,17 +108,16 @@ EnterBaseline(JSContext *cx, EnterJitDat
 
     // Caller must construct |this| before invoking the Ion function.
     JS_ASSERT_IF(data.constructing, data.maxArgv[0].isObject());
 
     data.result.setInt32(data.numActualArgs);
     {
         AssertCompartmentUnchanged pcc(cx);
         JitActivation activation(cx, data.constructing);
-        AutoFlushInhibitor afi(cx->runtime()->jitRuntime());
 
         if (data.osrFrame)
             data.osrFrame->setRunningInJit();
 
         JS_ASSERT_IF(data.osrFrame, !IsJSDEnabled(cx));
 
         // Single transition point from Interpreter to Baseline.
         CALL_GENERATED_CODE(enter, data.jitcode, data.maxArgc, data.maxArgv, data.osrFrame, data.calleeToken,
@@ -230,17 +229,16 @@ jit::BaselineCompile(JSContext *cx, JSSc
         return Method_Error;
 
     IonContext ictx(cx, temp);
 
     BaselineCompiler compiler(cx, *temp, script);
     if (!compiler.init())
         return Method_Error;
 
-    AutoFlushCache afc("BaselineJIT", cx->runtime()->jitRuntime());
     MethodStatus status = compiler.compile();
 
     JS_ASSERT_IF(status == Method_Compiled, script->hasBaselineScript());
     JS_ASSERT_IF(status != Method_Compiled, !script->hasBaselineScript());
 
     if (status == Method_CantCompile)
         script->setBaselineScript(cx, BASELINE_DISABLED_SCRIPT);
 
@@ -759,22 +757,16 @@ BaselineScript::toggleDebugTraps(JSScrip
     JS_ASSERT(script->baselineScript() == this);
 
     // Only scripts compiled for debug mode have toggled calls.
     if (!debugMode())
         return;
 
     SrcNoteLineScanner scanner(script->notes(), script->lineno());
 
-    JSRuntime *rt = script->runtimeFromMainThread();
-    IonContext ictx(CompileRuntime::get(rt),
-                    CompileCompartment::get(script->compartment()),
-                    nullptr);
-    AutoFlushCache afc("DebugTraps", rt->jitRuntime());
-
     for (uint32_t i = 0; i < numPCMappingIndexEntries(); i++) {
         PCMappingIndexEntry &entry = pcMappingIndexEntry(i);
 
         CompactBufferReader reader(pcMappingReader(i));
         jsbytecode *curPC = script->offsetToPC(entry.pcOffset);
         uint32_t nativeOffset = entry.nativeOffset;
 
         JS_ASSERT(script->containsPC(curPC));
--- a/js/src/jit/CodeGenerator.cpp
+++ b/js/src/jit/CodeGenerator.cpp
@@ -3346,17 +3346,16 @@ CodeGenerator::emitDebugResultChecks(LIn
       case MIRType_Object:
       case MIRType_String:
         return emitObjectOrStringResultChecks(ins, mir);
       case MIRType_Value:
         return emitValueResultChecks(ins, mir);
       default:
         return true;
     }
-    return true;
 }
 #endif
 
 bool
 CodeGenerator::generateBody()
 {
     IonScriptCounts *counts = maybeCreateScriptCounts();
 
@@ -5235,16 +5234,17 @@ JitCompartment::generateStringConcatStub
     masm.pop(temp2);
     masm.pop(temp1);
 
     masm.bind(&failure);
     masm.movePtr(ImmPtr(nullptr), output);
     masm.ret();
 
     Linker linker(masm);
+    AutoFlushICache afc("StringConcatStub");
     JitCode *code = linker.newCode<CanGC>(cx, JSC::OTHER_CODE);
 
 #ifdef JS_ION_PERF
     writePerfSpewerJitCodeProfile(code, "StringConcatStub");
 #endif
 
     return code;
 }
@@ -5269,16 +5269,17 @@ JitRuntime::generateMallocStub(JSContext
     masm.passABIArg(regNBytes);
     masm.callWithABI(JS_FUNC_TO_DATA_PTR(void *, MallocWrapper));
     masm.storeCallResult(regReturn);
 
     masm.PopRegsInMask(regs);
     masm.ret();
 
     Linker linker(masm);
+    AutoFlushICache afc("MallocStub");
     JitCode *code = linker.newCode<NoGC>(cx, JSC::OTHER_CODE);
 
 #ifdef JS_ION_PERF
     writePerfSpewerJitCodeProfile(code, "MallocStub");
 #endif
 
     return code;
 }
@@ -5299,16 +5300,17 @@ JitRuntime::generateFreeStub(JSContext *
     masm.passABIArg(regSlots);
     masm.callWithABI(JS_FUNC_TO_DATA_PTR(void *, js_free));
 
     masm.PopRegsInMask(regs);
 
     masm.ret();
 
     Linker linker(masm);
+    AutoFlushICache afc("FreeStub");
     JitCode *code = linker.newCode<NoGC>(cx, JSC::OTHER_CODE);
 
 #ifdef JS_ION_PERF
     writePerfSpewerJitCodeProfile(code, "FreeStub");
 #endif
 
     return code;
 }
@@ -6523,28 +6525,20 @@ CodeGenerator::link(JSContext *cx, types
                         /* resetUses */ false, /* cancelOffThread*/ false))
         {
             return false;
         }
     }
 
     // Check to make sure we didn't have a mid-build invalidation. If so, we
     // will trickle to jit::Compile() and return Method_Skipped.
-    uint32_t useCount = script->getUseCount();
     types::RecompileInfo recompileInfo;
     if (!types::FinishCompilation(cx, script, executionMode, constraints, &recompileInfo))
         return true;
 
-    // IonMonkey could have inferred better type information during
-    // compilation. Since adding the new information to the actual type
-    // information can reset the usecount, increase it back to what it was
-    // before.
-    if (useCount > script->getUseCount())
-        script->incUseCount(useCount - script->getUseCount());
-
     uint32_t scriptFrameSize = frameClass_ == FrameSizeClass::None()
                            ? frameDepth_
                            : FrameSizeClass::FromDepth(frameDepth_).frameSize();
 
     // We encode safepoints after the OSI-point offsets have been determined.
     encodeSafepoints();
 
     // List of possible scripts that this graph may call. Currently this is
@@ -6576,16 +6570,17 @@ CodeGenerator::link(JSContext *cx, types
     // Make sure we don't segv while filling in the code, to avoid deadlocking
     // inside the signal handler.
     cx->runtime()->jitRuntime()->ensureIonCodeAccessible(cx->runtime());
 
     // Implicit interrupts are used only for sequential code. In parallel mode
     // use the normal executable allocator so that we cannot segv during
     // execution off the main thread.
     Linker linker(masm);
+    AutoFlushICache afc("IonLink");
     JitCode *code = (executionMode == SequentialExecution)
                     ? linker.newCodeForIonScript(cx)
                     : linker.newCode<CanGC>(cx, JSC::ION_CODE);
     if (!code) {
         // Use js_free instead of IonScript::Destroy: the cache list and
         // backedge list are still uninitialized.
         js_free(ionScript);
         recompileInfo.compilerOutput(cx->zone()->types)->invalidate();
@@ -8665,41 +8660,28 @@ CodeGenerator::visitAssertRangeV(LAssert
     masm.assumeUnreachable("Incorrect range for Value.");
     masm.bind(&done);
     return true;
 }
 
 typedef bool (*RecompileFn)(JSContext *);
 static const VMFunction RecompileFnInfo = FunctionInfo<RecompileFn>(Recompile);
 
-typedef bool (*ForcedRecompileFn)(JSContext *);
-static const VMFunction ForcedRecompileFnInfo = FunctionInfo<ForcedRecompileFn>(ForcedRecompile);
-
 bool
 CodeGenerator::visitRecompileCheck(LRecompileCheck *ins)
 {
     Label done;
     Register tmp = ToRegister(ins->scratch());
-    OutOfLineCode *ool;
-    if (ins->mir()->forceRecompilation())
-        ool = oolCallVM(ForcedRecompileFnInfo, ins, (ArgList()), StoreRegisterTo(tmp));
-    else
-        ool = oolCallVM(RecompileFnInfo, ins, (ArgList()), StoreRegisterTo(tmp));
+    OutOfLineCode *ool = oolCallVM(RecompileFnInfo, ins, (ArgList()), StoreRegisterTo(tmp));
 
     // Check if usecount is high enough.
-    AbsoluteAddress useCount = AbsoluteAddress(ins->mir()->script()->addressOfUseCount());
-    if (ins->mir()->increaseUseCount()) {
-        masm.load32(useCount, tmp);
-        masm.add32(Imm32(1), tmp);
-        masm.store32(tmp, useCount);
-        masm.branch32(Assembler::BelowOrEqual, tmp, Imm32(ins->mir()->recompileThreshold()), &done);
-    } else {
-        masm.branch32(Assembler::BelowOrEqual, useCount, Imm32(ins->mir()->recompileThreshold()),
-                      &done);
-    }
+    masm.movePtr(ImmPtr(ins->mir()->script()->addressOfUseCount()), tmp);
+    Address ptr(tmp, 0);
+    masm.add32(Imm32(1), tmp);
+    masm.branch32(Assembler::BelowOrEqual, ptr, Imm32(ins->mir()->recompileThreshold()), &done);
 
     // Check if not yet recompiling.
     CodeOffsetLabel label = masm.movWithPatch(ImmWord(uintptr_t(-1)), tmp);
     if (!ionScriptLabels_.append(label))
         return false;
     masm.branch32(Assembler::Equal,
                   Address(tmp, IonScript::offsetOfRecompiling()),
                   Imm32(0),
--- a/js/src/jit/Ion.cpp
+++ b/js/src/jit/Ion.cpp
@@ -157,17 +157,16 @@ JitRuntime::JitRuntime()
     argumentsRectifierReturnAddr_(nullptr),
     parallelArgumentsRectifier_(nullptr),
     invalidator_(nullptr),
     debugTrapHandler_(nullptr),
     forkJoinGetSliceStub_(nullptr),
     baselineDebugModeOSRHandler_(nullptr),
     functionWrappers_(nullptr),
     osrTempData_(nullptr),
-    flusher_(nullptr),
     ionCodeProtected_(false),
     ionReturnOverride_(MagicValue(JS_ARG_POISON))
 {
 }
 
 JitRuntime::~JitRuntime()
 {
     js_delete(functionWrappers_);
@@ -182,17 +181,16 @@ bool
 JitRuntime::initialize(JSContext *cx)
 {
     JS_ASSERT(cx->runtime()->currentThreadHasExclusiveAccess());
     JS_ASSERT(cx->runtime()->currentThreadOwnsInterruptLock());
 
     AutoCompartment ac(cx, cx->atomsCompartment());
 
     IonContext ictx(cx, nullptr);
-    AutoFlushCache afc("JitRuntime::initialize", this);
 
     execAlloc_ = cx->runtime()->getExecAlloc(cx);
     if (!execAlloc_)
         return false;
 
     if (!cx->compartment()->ensureJitCompartmentExists(cx))
         return false;
 
@@ -1171,29 +1169,26 @@ IonScript::Destroy(FreeOp *fop, IonScrip
 
 void
 IonScript::toggleBarriers(bool enabled)
 {
     method()->togglePreBarriers(enabled);
 }
 
 void
-IonScript::purgeCaches(Zone *zone)
+IonScript::purgeCaches()
 {
     // Don't reset any ICs if we're invalidated, otherwise, repointing the
     // inline jump could overwrite an invalidation marker. These ICs can
     // no longer run, however, the IC slow paths may be active on the stack.
     // ICs therefore are required to check for invalidation before patching,
     // to ensure the same invariant.
     if (invalidated())
         return;
 
-    JSRuntime *rt = zone->runtimeFromMainThread();
-    IonContext ictx(CompileRuntime::get(rt));
-    AutoFlushCache afc("purgeCaches", rt->jitRuntime());
     for (size_t i = 0; i < numCaches(); i++)
         getCacheFromIndex(i).reset();
 }
 
 void
 IonScript::destroyCaches()
 {
     for (size_t i = 0; i < numCaches(); i++)
@@ -1243,18 +1238,16 @@ IonScript::unlinkFromRuntime(FreeOp *fop
 
 void
 jit::ToggleBarriers(JS::Zone *zone, bool needs)
 {
     JSRuntime *rt = zone->runtimeFromMainThread();
     if (!rt->hasJitRuntime())
         return;
 
-    IonContext ictx(CompileRuntime::get(rt));
-    AutoFlushCache afc("ToggleBarriers", rt->jitRuntime());
     for (gc::ZoneCellIterUnderGC i(zone, gc::FINALIZE_SCRIPT); !i.done(); i.next()) {
         JSScript *script = i.get<JSScript>();
         if (script->hasIonScript())
             script->ionScript()->toggleBarriers(needs);
         if (script->hasBaselineScript())
             script->baselineScript()->toggleBarriers(needs);
     }
 
@@ -1510,29 +1503,16 @@ OptimizeMIR(MIRGenerator *mir)
             return false;
         IonSpewPass("DCE");
         AssertExtendedGraphCoherency(graph);
 
         if (mir->shouldCancel("DCE"))
             return false;
     }
 
-    // Make loops contiguious. We do this after GVN/UCE and range analysis,
-    // which can remove CFG edges, exposing more blocks that can be moved.
-    {
-        AutoTraceLog log(logger, TraceLogger::MakeLoopsContiguous);
-        if (!MakeLoopsContiguous(graph))
-            return false;
-        IonSpewPass("Make loops contiguous");
-        AssertExtendedGraphCoherency(graph);
-
-        if (mir->shouldCancel("Make loops contiguous"))
-            return false;
-    }
-
     // Passes after this point must not move instructions; these analyses
     // depend on knowing the final order in which instructions will execute.
 
     if (mir->optimizationInfo().edgeCaseAnalysisEnabled()) {
         AutoTraceLog log(logger, TraceLogger::EdgeCaseAnalysis);
         EdgeCaseAnalysis edgeCaseAnalysis(mir, graph);
         if (!edgeCaseAnalysis.analyzeLate())
             return false;
@@ -1742,17 +1722,16 @@ AttachFinishedCompilations(JSContext *cx
             // previously, though any GC activity would discard the builder.
             codegen->masm.constructRoot(cx);
 
             bool success;
             {
                 // Release the worker thread lock and root the compiler for GC.
                 AutoTempAllocatorRooter root(cx, &builder->alloc());
                 AutoUnlockWorkerThreadState unlock;
-                AutoFlushCache afc("AttachFinishedCompilations", cx->runtime()->jitRuntime());
                 success = codegen->link(cx, builder->constraints());
             }
 
             if (!success) {
                 // Silently ignore OOM during code generation. The caller is
                 // InvokeInterruptCallback, which always runs at a
                 // nondeterministic time. It's not OK to throw a catchable
                 // exception from there.
@@ -1888,18 +1867,16 @@ IonCompile(JSContext *cx, JSScript *scri
 
     BaselineFrameInspector *baselineFrameInspector = nullptr;
     if (baselineFrame) {
         baselineFrameInspector = NewBaselineFrameInspector(temp, baselineFrame, info);
         if (!baselineFrameInspector)
             return AbortReason_Alloc;
     }
 
-    AutoFlushCache afc("IonCompile", cx->runtime()->jitRuntime());
-
     AutoTempAllocatorRooter root(cx, temp);
     types::CompilerConstraintList *constraints = types::NewCompilerConstraintList(*temp);
     if (!constraints)
         return AbortReason_Alloc;
 
     const OptimizationInfo *optimizationInfo = js_IonOptimizations.get(optimizationLevel);
     const JitCompileOptions options(cx);
 
@@ -2072,17 +2049,17 @@ GetOptimizationLevel(HandleScript script
 
     JS_ASSERT(executionMode == SequentialExecution);
 
     return js_IonOptimizations.levelForScript(script, pc);
 }
 
 static MethodStatus
 Compile(JSContext *cx, HandleScript script, BaselineFrame *osrFrame, jsbytecode *osrPc,
-        bool constructing, ExecutionMode executionMode, bool forceRecompile = false)
+        bool constructing, ExecutionMode executionMode)
 {
     JS_ASSERT(jit::IsIonEnabled(cx));
     JS_ASSERT(jit::IsBaselineEnabled(cx));
     JS_ASSERT_IF(osrPc != nullptr, LoopEntryCanIonOsr(osrPc));
     JS_ASSERT_IF(executionMode == ParallelExecution, !osrFrame && !osrPc);
     JS_ASSERT_IF(executionMode == ParallelExecution, !HasIonScript(script, executionMode));
 
     if (!script->hasBaselineScript())
@@ -2109,27 +2086,45 @@ Compile(JSContext *cx, HandleScript scri
     if (optimizationLevel == Optimization_DontCompile)
         return Method_Skipped;
 
     IonScript *scriptIon = GetIonScript(script, executionMode);
     if (scriptIon) {
         if (!scriptIon->method())
             return Method_CantCompile;
 
+        MethodStatus failedState = Method_Compiled;
+
+        // If we keep failing to enter the script due to an OSR pc mismatch,
+        // recompile with the right pc.
+        if (osrPc && script->ionScript()->osrPc() != osrPc) {
+            uint32_t count = script->ionScript()->incrOsrPcMismatchCounter();
+            if (count <= js_JitOptions.osrPcMismatchesBeforeRecompile)
+                return Method_Skipped;
+
+            failedState = Method_Skipped;
+        }
+
         // Don't recompile/overwrite higher optimized code,
         // with a lower optimization level.
-        if (optimizationLevel <= scriptIon->optimizationLevel() && !forceRecompile)
-            return Method_Compiled;
+        if (optimizationLevel < scriptIon->optimizationLevel())
+            return failedState;
+
+        if (optimizationLevel == scriptIon->optimizationLevel() &&
+            (!osrPc || script->ionScript()->osrPc() == osrPc))
+        {
+            return failedState;
+        }
 
         // Don't start compiling if already compiling
         if (scriptIon->isRecompiling())
-            return Method_Compiled;
+            return failedState;
 
         if (osrPc)
-            scriptIon->resetOsrPcMismatchCounter();
+            script->ionScript()->resetOsrPcMismatchCounter();
 
         recompile = true;
     }
 
     AbortReason reason = IonCompile(cx, script, osrFrame, osrPc, constructing, executionMode,
                                     recompile, optimizationLevel);
     if (reason == AbortReason_Error)
         return Method_Error;
@@ -2138,18 +2133,21 @@ Compile(JSContext *cx, HandleScript scri
         return Method_CantCompile;
 
     if (reason == AbortReason_Alloc) {
         js_ReportOutOfMemory(cx);
         return Method_Error;
     }
 
     // Compilation succeeded or we invalidated right away or an inlining/alloc abort
-    if (HasIonScript(script, executionMode))
+    if (HasIonScript(script, executionMode)) {
+        if (osrPc && script->ionScript()->osrPc() != osrPc)
+            return Method_Skipped;
         return Method_Compiled;
+    }
     return Method_Skipped;
 }
 
 } // namespace jit
 } // namespace js
 
 // Decide if a transition from interpreter execution to Ion code should occur.
 // May compile or recompile the target JSScript.
@@ -2178,45 +2176,29 @@ jit::CanEnterAtBranch(JSContext *cx, JSS
         return Method_Skipped;
 
     // Mark as forbidden if frame can't be handled.
     if (!CheckFrame(osrFrame)) {
         ForbidCompilation(cx, script);
         return Method_CantCompile;
     }
 
-    // By default a recompilation doesn't happen on osr mismatch.
-    // Decide if we want to force a recompilation if this happens too much.
-    bool force = false;
-    if (script->hasIonScript() && pc != script->ionScript()->osrPc()) {
-        uint32_t count = script->ionScript()->incrOsrPcMismatchCounter();
-        if (count <= js_JitOptions.osrPcMismatchesBeforeRecompile)
-            return Method_Skipped;
-        force = true;
-    }
-
     // Attempt compilation.
     // - Returns Method_Compiled if the right ionscript is present
     //   (Meaning it was present or a sequantial compile finished)
+    // - Returns Method_Skipped if pc doesn't match
+    //   (This means a background thread compilation with that pc could have started or not.)
     RootedScript rscript(cx, script);
-    MethodStatus status =
-        Compile(cx, rscript, osrFrame, pc, isConstructing, SequentialExecution, force);
+    MethodStatus status = Compile(cx, rscript, osrFrame, pc, isConstructing, SequentialExecution);
     if (status != Method_Compiled) {
         if (status == Method_CantCompile)
             ForbidCompilation(cx, script);
         return status;
     }
 
-    // Return the compilation was skipped when the osr pc wasn't adjusted.
-    // This can happen when there was still an IonScript available and a
-    // background compilation started, but hasn't finished yet.
-    // Or when we didn't force a recompile.
-    if (pc != script->ionScript()->osrPc())
-        return Method_Skipped;
-
     return Method_Compiled;
 }
 
 MethodStatus
 jit::CanEnter(JSContext *cx, RunState &state)
 {
     JS_ASSERT(jit::IsIonEnabled(cx));
 
@@ -2317,24 +2299,24 @@ jit::CompileFunctionForBaseline(JSContex
         return status;
     }
 
     return Method_Compiled;
 }
 
 MethodStatus
 jit::Recompile(JSContext *cx, HandleScript script, BaselineFrame *osrFrame, jsbytecode *osrPc,
-               bool constructing, bool force)
+               bool constructing)
 {
     JS_ASSERT(script->hasIonScript());
     if (script->ionScript()->isRecompiling())
         return Method_Compiled;
 
     MethodStatus status =
-        Compile(cx, script, osrFrame, osrPc, constructing, SequentialExecution, force);
+        Compile(cx, script, osrFrame, osrPc, constructing, SequentialExecution);
     if (status != Method_Compiled) {
         if (status == Method_CantCompile)
             ForbidCompilation(cx, script);
         return status;
     }
 
     return Method_Compiled;
 }
@@ -2420,17 +2402,16 @@ EnterIon(JSContext *cx, EnterJitData &da
 
     // Caller must construct |this| before invoking the Ion function.
     JS_ASSERT_IF(data.constructing, data.maxArgv[0].isObject());
 
     data.result.setInt32(data.numActualArgs);
     {
         AssertCompartmentUnchanged pcc(cx);
         JitActivation activation(cx, data.constructing);
-        AutoFlushInhibitor afi(cx->runtime()->jitRuntime());
 
         CALL_GENERATED_CODE(enter, data.jitcode, data.maxArgc, data.maxArgv, /* osrFrame = */nullptr, data.calleeToken,
                             /* scopeChain = */ nullptr, 0, data.result.address());
     }
 
     JS_ASSERT(!cx->runtime()->jitRuntime()->hasIonReturnOverride());
 
     // Jit callers wrap primitive constructor return.
@@ -2604,17 +2585,17 @@ InvalidateActivation(FreeOp *fop, uint8_
         if (!invalidateAll && !script->ionScript()->invalidated())
             continue;
 
         IonScript *ionScript = script->ionScript();
 
         // Purge ICs before we mark this script as invalidated. This will
         // prevent lastJump_ from appearing to be a bogus pointer, just
         // in case anyone tries to read it.
-        ionScript->purgeCaches(script->zone());
+        ionScript->purgeCaches();
 
         // Clean up any pointers from elsewhere in the runtime to this IonScript
         // which is about to become disconnected from its JSScript.
         ionScript->unlinkFromRuntime(fop);
 
         // This frame needs to be invalidated. We do the following:
         //
         // 1. Increment the reference counter to keep the ionScript alive
@@ -2685,32 +2666,29 @@ jit::StopAllOffThreadCompilations(JSComp
 void
 jit::InvalidateAll(FreeOp *fop, Zone *zone)
 {
     for (CompartmentsInZoneIter comp(zone); !comp.done(); comp.next())
         StopAllOffThreadCompilations(comp);
 
     for (JitActivationIterator iter(fop->runtime()); !iter.done(); ++iter) {
         if (iter->compartment()->zone() == zone) {
-            IonContext ictx(CompileRuntime::get(fop->runtime()));
-            AutoFlushCache afc("InvalidateAll", fop->runtime()->jitRuntime());
             IonSpew(IonSpew_Invalidate, "Invalidating all frames for GC");
             InvalidateActivation(fop, iter.jitTop(), true);
         }
     }
 }
 
 
 void
 jit::Invalidate(types::TypeZone &types, FreeOp *fop,
                 const Vector<types::RecompileInfo> &invalid, bool resetUses,
                 bool cancelOffThread)
 {
     IonSpew(IonSpew_Invalidate, "Start invalidation.");
-    AutoFlushCache afc ("Invalidate", fop->runtime()->jitRuntime());
 
     // Add an invalidation reference to all invalidated IonScripts to indicate
     // to the traversal which frames have been invalidated.
     size_t numInvalidations = 0;
     for (size_t i = 0; i < invalid.length(); i++) {
         const types::CompilerOutput &co = *invalid[i].compilerOutput(types);
         if (!co.isValid())
             continue;
@@ -2942,76 +2920,159 @@ jit::ForbidCompilation(JSContext *cx, JS
 
       default:
         MOZ_ASSUME_UNREACHABLE("No such execution mode");
     }
 
     MOZ_ASSUME_UNREACHABLE("No such execution mode");
 }
 
+AutoFlushICache *
+PerThreadData::autoFlushICache() const
+{
+    return autoFlushICache_;
+}
+
 void
-AutoFlushCache::updateTop(uintptr_t p, size_t len)
+PerThreadData::setAutoFlushICache(AutoFlushICache *afc)
+{
+    autoFlushICache_ = afc;
+}
+
+// Set the range for the merging of flushes.  The flushing is deferred until the end of
+// the AutoFlushICache context.  Subsequent flushing within this range will is also
+// deferred.  This is only expected to be defined once for each AutoFlushICache
+// context.  It assumes the range will be flushed is required to be within an
+// AutoFlushICache context.
+void
+AutoFlushICache::setRange(uintptr_t start, size_t len)
+{
+#if defined(JS_CODEGEN_ARM) || defined(JS_CODEGEN_MIPS)
+    AutoFlushICache *afc = TlsPerThreadData.get()->PerThreadData::autoFlushICache();
+    JS_ASSERT(afc);
+    JS_ASSERT(!afc->start_);
+    IonSpewCont(IonSpew_CacheFlush, "(%x %x):", start, len);
+
+    uintptr_t stop = start + len;
+    afc->start_ = start;
+    afc->stop_ = stop;
+#endif
+}
+
+// Flush the instruction cache.
+//
+// If called within a dynamic AutoFlushICache context and if the range is already pending
+// flushing for this AutoFlushICache context then the request is ignored with the
+// understanding that it will be flushed on exit from the AutoFlushICache context.
+// Otherwise the range is flushed immediately.
+//
+// Updates outside the current code object are typically the exception so they are flushed
+// immediately rather than attempting to merge them.
+//
+// For efficiency it is expected that all large ranges will be flushed within an
+// AutoFlushICache, so check.  If this assertion is hit then it does not necessarily
+// indicate a progam fault but it might indicate a lost opportunity to merge cache
+// flushing.  It can be corrected by wrapping the call in an AutoFlushICache to context.
+void
+AutoFlushICache::flush(uintptr_t start, size_t len)
 {
-    IonContext *ictx = MaybeGetIonContext();
-    JitRuntime *jrt = (ictx != nullptr) ? const_cast<JitRuntime *>(ictx->runtime->jitRuntime()) : nullptr;
-    if (!jrt || !jrt->flusher())
-        JSC::ExecutableAllocator::cacheFlush((void*)p, len);
-    else
-        jrt->flusher()->update(p, len);
+#if defined(JS_CODEGEN_ARM) || defined(JS_CODEGEN_MIPS)
+    AutoFlushICache *afc = TlsPerThreadData.get()->PerThreadData::autoFlushICache();
+    if (!afc) {
+        IonSpewCont(IonSpew_CacheFlush, "#");
+        JSC::ExecutableAllocator::cacheFlush((void*)start, len);
+        JS_ASSERT(len <= 16);
+        return;
+    }
+
+    uintptr_t stop = start + len;
+    if (start >= afc->start_ && stop <= afc->stop_) {
+        // Update is within the pending flush range, so defer to the end of the context.
+        IonSpewCont(IonSpew_CacheFlush, afc->inhibit_ ? "-" : "=");
+        return;
+    }
+
+    IonSpewCont(IonSpew_CacheFlush, afc->inhibit_ ? "x" : "*");
+    JSC::ExecutableAllocator::cacheFlush((void *)start, len);
+#endif
 }
 
-AutoFlushCache::AutoFlushCache(const char *nonce, JitRuntime *rt)
+// Flag the current dynamic AutoFlushICache as inhibiting flushing. Useful in error paths
+// where the changes are being abandoned.
+void
+AutoFlushICache::setInhibit()
+{
+#if defined(JS_CODEGEN_ARM) || defined(JS_CODEGEN_MIPS)
+    AutoFlushICache *afc = TlsPerThreadData.get()->PerThreadData::autoFlushICache();
+    JS_ASSERT(afc);
+    JS_ASSERT(afc->start_);
+    IonSpewCont(IonSpew_CacheFlush, "I");
+    afc->inhibit_ = true;
+#endif
+}
+
+// The common use case is merging cache flushes when preparing a code object.  In this
+// case the entire range of the code object is being flushed and as the code is patched
+// smaller redundant flushes could occur.  The design allows an AutoFlushICache dynamic
+// thread local context to be declared in which the range of the code object can be set
+// which defers flushing until the end of this dynamic context.  The redundant flushing
+// within this code range is also deferred avoiding redundant flushing.  Flushing outside
+// this code range is not affected and proceeds immediately.
+//
+// In some cases flushing is not necessary, such as when compiling an asm.js module which
+// is flushed again when dynamically linked, and also in error paths that abandon the
+// code.  Flushing within the set code range can be inhibited within the AutoFlushICache
+// dynamic context by setting an inhibit flag.
+//
+// The JS compiler can be re-entered while within an AutoFlushICache dynamic context and
+// it is assumed that code being assembled or patched is not executed before the exit of
+// the respective AutoFlushICache dynamic context.
+//
+AutoFlushICache::AutoFlushICache(const char *nonce, bool inhibit)
   : start_(0),
     stop_(0),
     name_(nonce),
-    runtime_(rt),
-    used_(false)
-{
-    if (rt->flusher())
-        IonSpew(IonSpew_CacheFlush, "<%s ", nonce);
-    else
-        IonSpewCont(IonSpew_CacheFlush, "<%s ", nonce);
-
-    rt->setFlusher(this);
-}
-
-AutoFlushInhibitor::AutoFlushInhibitor(JitRuntime *rt)
-  : runtime_(rt),
-    afc(nullptr)
+    inhibit_(inhibit)
 {
-    afc = rt->flusher();
-
-    // Ensure that called functions get a fresh flusher.
-    rt->setFlusher(nullptr);
-
-    // Ensure the current flusher has been flushed.
-    if (afc) {
-        afc->flushAnyway();
-        IonSpewCont(IonSpew_CacheFlush, "}");
-    }
+#if defined(JS_CODEGEN_ARM) || defined(JS_CODEGEN_MIPS)
+    PerThreadData *pt = TlsPerThreadData.get();
+    AutoFlushICache *afc = pt->PerThreadData::autoFlushICache();
+    if (afc)
+        IonSpew(IonSpew_CacheFlush, "<%s,%s%s ", nonce, afc->name_, inhibit ? " I" : "");
+    else
+        IonSpewCont(IonSpew_CacheFlush, "<%s%s ", nonce, inhibit ? " I" : "");
+
+    prev_ = afc;
+    pt->PerThreadData::setAutoFlushICache(this);
+#endif
 }
-AutoFlushInhibitor::~AutoFlushInhibitor()
+
+AutoFlushICache::~AutoFlushICache()
 {
-    JS_ASSERT(runtime_->flusher() == nullptr);
-
-    // Ensure any future modifications are recorded.
-    runtime_->setFlusher(afc);
-
-    if (afc)
-        IonSpewCont(IonSpew_CacheFlush, "{");
+#if defined(JS_CODEGEN_ARM) || defined(JS_CODEGEN_MIPS)
+    PerThreadData *pt = TlsPerThreadData.get();
+    JS_ASSERT(pt->PerThreadData::autoFlushICache() == this);
+
+    if (!inhibit_ && start_)
+        JSC::ExecutableAllocator::cacheFlush((void *)start_, size_t(stop_ - start_));
+
+    IonSpewCont(IonSpew_CacheFlush, "%s%s>", name_, start_ ? "" : " U");
+    IonSpewFin(IonSpew_CacheFlush);
+    pt->PerThreadData::setAutoFlushICache(prev_);
+#endif
 }
 
 void
-jit::PurgeCaches(JSScript *script, Zone *zone)
+jit::PurgeCaches(JSScript *script)
 {
     if (script->hasIonScript())
-        script->ionScript()->purgeCaches(zone);
+        script->ionScript()->purgeCaches();
 
     if (script->hasParallelIonScript())
-        script->parallelIonScript()->purgeCaches(zone);
+        script->parallelIonScript()->purgeCaches();
 }
 
 size_t
 jit::SizeOfIonData(JSScript *script, mozilla::MallocSizeOf mallocSizeOf)
 {
     size_t result = 0;
 
     if (script->hasIonScript())
@@ -3108,17 +3169,16 @@ AutoDebugModeInvalidation::~AutoDebugMod
     // Don't discard active baseline scripts. They are recompiled for debug
     // mode.
     jit::MarkActiveBaselineScripts(zone);
 
     for (JitActivationIterator iter(rt); !iter.done(); ++iter) {
         JSCompartment *comp = iter->compartment();
         if (comp_ == comp || zone_ == comp->zone()) {
             IonContext ictx(CompileRuntime::get(rt));
-            AutoFlushCache afc("AutoDebugModeInvalidation", rt->jitRuntime());
             IonSpew(IonSpew_Invalidate, "Invalidating frames for debug mode toggle");
             InvalidateActivation(fop, iter.jitTop(), true);
         }
     }
 
     for (gc::ZoneCellIter i(zone, gc::FINALIZE_SCRIPT); !i.done(); i.next()) {
         JSScript *script = i.get<JSScript>();
         if (script->compartment() == comp_ || zone_) {
--- a/js/src/jit/Ion.h
+++ b/js/src/jit/Ion.h
@@ -90,17 +90,17 @@ MethodStatus CanEnter(JSContext *cx, Run
 MethodStatus CompileFunctionForBaseline(JSContext *cx, HandleScript script, BaselineFrame *frame,
                                         bool isConstructing);
 MethodStatus CanEnterUsingFastInvoke(JSContext *cx, HandleScript script, uint32_t numActualArgs);
 
 MethodStatus CanEnterInParallel(JSContext *cx, HandleScript script);
 
 MethodStatus
 Recompile(JSContext *cx, HandleScript script, BaselineFrame *osrFrame, jsbytecode *osrPc,
-          bool constructing, bool force);
+          bool constructing);
 
 enum IonExecStatus
 {
     // The method call had to be aborted due to a stack limit check. This
     // error indicates that Ion never attempted to clean up frames.
     IonExec_Aborted,
 
     // The method call resulted in an error, and IonMonkey has cleaned up
@@ -185,17 +185,17 @@ NumLocalsAndArgs(JSScript *script)
     if (JSFunction *fun = script->functionNonDelazifying())
         num += fun->nargs();
     return num;
 }
 
 void ForbidCompilation(JSContext *cx, JSScript *script);
 void ForbidCompilation(JSContext *cx, JSScript *script, ExecutionMode mode);
 
-void PurgeCaches(JSScript *script, JS::Zone *zone);
+void PurgeCaches(JSScript *script);
 size_t SizeOfIonData(JSScript *script, mozilla::MallocSizeOf mallocSizeOf);
 void DestroyIonScripts(FreeOp *fop, JSScript *script);
 void TraceIonScripts(JSTracer* trc, JSScript *script);
 
 void RequestInterruptForIonCode(JSRuntime *rt, JSRuntime::InterruptMode mode);
 
 bool RematerializeAllFrames(JSContext *cx, JSCompartment *comp);
 bool UpdateForDebugMode(JSContext *maybecx, JSCompartment *comp,
--- a/js/src/jit/IonAnalysis.cpp
+++ b/js/src/jit/IonAnalysis.cpp
@@ -2558,101 +2558,8 @@ jit::AnalyzeArgumentsUsage(JSContext *cx
     // arguments. The compiler can then assume that accesses through
     // arguments[i] will be on unaliased variables.
     if (script->funHasAnyAliasedFormal() && argumentsContentsObserved)
         return true;
 
     script->setNeedsArgsObj(false);
     return true;
 }
-
-// Reorder the blocks in the loop starting at the given header to be contiguous.
-static void
-MakeLoopContiguous(MIRGraph &graph, MBasicBlock *header, MBasicBlock *backedge, size_t numMarked)
-{
-    MOZ_ASSERT(header->isMarked(), "Loop header is not part of loop");
-    MOZ_ASSERT(backedge->isMarked(), "Loop backedge is not part of loop");
-
-    // If there are any blocks between the loop header and the loop backedge
-    // that are not part of the loop, prepare to move them to the end. We keep
-    // them in order, which preserves RPO.
-    ReversePostorderIterator insertIter = graph.rpoBegin(backedge);
-    insertIter++;
-    MBasicBlock *insertPt = *insertIter;
-
-    // Visit all the blocks from the loop header to the loop backedge.
-    size_t headerId = header->id();
-    size_t inLoopId = headerId;
-    size_t afterLoopId = inLoopId + numMarked;
-    ReversePostorderIterator i = graph.rpoBegin(header);
-    for (;;) {
-        MBasicBlock *block = *i++;
-        MOZ_ASSERT(block->id() >= header->id() && block->id() <= backedge->id(),
-                   "Loop backedge should be last block in loop");
-
-        if (block->isMarked()) {
-            // This block is in the loop.
-            block->unmark();
-            block->setId(inLoopId++);
-            // If we've reached the loop backedge, we're done!
-            if (block == backedge)
-                break;
-        } else {
-            // This block is not in the loop. Move it to the end.
-            graph.moveBlockBefore(insertPt, block);
-            block->setId(afterLoopId++);
-        }
-    }
-    MOZ_ASSERT(header->id() == headerId, "Loop header id changed");
-    MOZ_ASSERT(inLoopId == headerId + numMarked, "Wrong number of blocks kept in loop");
-    MOZ_ASSERT(afterLoopId == (insertIter != graph.rpoEnd() ? insertPt->id() : graph.numBlocks()),
-               "Wrong number of blocks moved out of loop");
-}
-
-// Reorder the blocks in the graph so that loops are contiguous.
-bool
-jit::MakeLoopsContiguous(MIRGraph &graph)
-{
-    MBasicBlock *osrBlock = graph.osrBlock();
-    Vector<MBasicBlock *, 1, IonAllocPolicy> inlooplist(graph.alloc());
-
-    // Visit all loop headers (in any order).
-    for (MBasicBlockIterator i(graph.begin()); i != graph.end(); i++) {
-        MBasicBlock *header = *i;
-        if (!header->isLoopHeader())
-            continue;
-
-        // Mark all the blocks in the loop by marking all blocks in a path
-        // between the backedge and the loop header.
-        MBasicBlock *backedge = header->backedge();
-        size_t numMarked = 1;
-        backedge->mark();
-        if (!inlooplist.append(backedge))
-            return false;
-        do {
-            MBasicBlock *block = inlooplist.popCopy();
-            MOZ_ASSERT(block->id() >= header->id() && block->id() <= backedge->id(),
-                       "Non-OSR predecessor of loop block not between header and backedge");
-            if (block == header)
-                continue;
-            for (size_t p = 0; p < block->numPredecessors(); p++) {
-                MBasicBlock *pred = block->getPredecessor(p);
-                if (pred->isMarked())
-                    continue;
-                // Ignore paths entering the loop in the middle from an OSR
-                // entry. They won't pass through the loop header and they
-                // aren't part of the loop.
-                if (osrBlock && osrBlock->dominates(pred) && !osrBlock->dominates(header))
-                    continue;
-                ++numMarked;
-                pred->mark();
-                if (!inlooplist.append(pred))
-                    return false;
-            }
-        } while (!inlooplist.empty());
-
-        // Move all blocks between header and backedge that aren't marked to
-        // the end of the loop, making the loop itself contiguous.
-        MakeLoopContiguous(graph, header, backedge, numMarked);
-    }
-
-    return true;
-}
--- a/js/src/jit/IonAnalysis.h
+++ b/js/src/jit/IonAnalysis.h
@@ -27,19 +27,16 @@ enum Observability {
     ConservativeObservability,
     AggressiveObservability
 };
 
 bool
 EliminatePhis(MIRGenerator *mir, MIRGraph &graph, Observability observe);
 
 bool
-MakeLoopsContiguous(MIRGraph &graph);
-
-bool
 EliminateDeadResumePointOperands(MIRGenerator *mir, MIRGraph &graph);
 
 bool
 EliminateDeadCode(MIRGenerator *mir, MIRGraph &graph);
 
 bool
 ApplyTypeInformation(MIRGenerator *mir, MIRGraph &graph);
 
--- a/js/src/jit/IonBuilder.cpp
+++ b/js/src/jit/IonBuilder.cpp
@@ -4171,19 +4171,17 @@ IonBuilder::makeInliningDecision(JSFunct
             return DontInline(targetScript, "Vetoed: callee excessively large");
 
         // Callee must have been called a few times to have somewhat stable
         // type information, except for definite properties analysis,
         // as the caller has not run yet.
         if (targetScript->getUseCount() < optimizationInfo().usesBeforeInlining() &&
             info().executionMode() != DefinitePropertiesAnalysis)
         {
-            IonSpew(IonSpew_Inlining, "Cannot inline %s:%u: callee is insufficiently hot.",
-                    targetScript->filename(), targetScript->lineno());
-            return InliningDecision_UseCountTooLow;
+            return DontInline(targetScript, "Vetoed: callee is insufficiently hot.");
         }
     }
 
     // TI calls ObjectStateChange to trigger invalidation of the caller.
     types::TypeObjectKey *targetType = types::TypeObjectKey::get(target);
     targetType->watchStateChangeForInlinedCall(constraints());
 
     return InliningDecision_Inline;
@@ -4208,17 +4206,16 @@ IonBuilder::selectInliningTargets(Object
     for (size_t i = 0; i < targets.length(); i++) {
         JSFunction *target = &targets[i]->as<JSFunction>();
         bool inlineable;
         InliningDecision decision = makeInliningDecision(target, callInfo);
         switch (decision) {
           case InliningDecision_Error:
             return false;
           case InliningDecision_DontInline:
-          case InliningDecision_UseCountTooLow:
             inlineable = false;
             break;
           case InliningDecision_Inline:
             inlineable = true;
             break;
           default:
             MOZ_ASSUME_UNREACHABLE("Unhandled InliningDecision value!");
         }
@@ -4329,18 +4326,16 @@ IonBuilder::inlineCallsite(ObjectVector 
     if (!propCache && targets.length() == 1) {
         JSFunction *target = &targets[0]->as<JSFunction>();
         InliningDecision decision = makeInliningDecision(target, callInfo);
         switch (decision) {
           case InliningDecision_Error:
             return InliningStatus_Error;
           case InliningDecision_DontInline:
             return InliningStatus_NotInlined;
-          case InliningDecision_UseCountTooLow:
-            return InliningStatus_UseCountTooLow;
           case InliningDecision_Inline:
             break;
         }
 
         // Inlining will elminate uses of the original callee, but it needs to
         // be preserved in phis if we bail out.  Mark the old callee definition as
         // implicitly used to ensure this happens.
         callInfo.fun()->setImplicitlyUsedUnchecked();
@@ -4955,17 +4950,16 @@ IonBuilder::jsop_funcall(uint32_t argc)
 
     // Try to inline the call.
     if (!zeroArguments) {
         InliningDecision decision = makeInliningDecision(target, callInfo);
         switch (decision) {
           case InliningDecision_Error:
             return false;
           case InliningDecision_DontInline:
-          case InliningDecision_UseCountTooLow:
             break;
           case InliningDecision_Inline:
             if (target->isInterpreted())
                 return inlineScriptedCall(callInfo, target);
             break;
         }
     }
 
@@ -5096,17 +5090,16 @@ IonBuilder::jsop_funapplyarguments(uint3
     nativeFunc->setImplicitlyUsedUnchecked();
 
     // Try to inline the call.
     InliningDecision decision = makeInliningDecision(target, callInfo);
     switch (decision) {
       case InliningDecision_Error:
         return false;
       case InliningDecision_DontInline:
-      case InliningDecision_UseCountTooLow:
         break;
       case InliningDecision_Inline:
         if (target->isInterpreted())
             return inlineScriptedCall(callInfo, target);
     }
 
     return makeCall(target, callInfo, false);
 }
@@ -5167,23 +5160,16 @@ IonBuilder::jsop_call(uint32_t argc, boo
     if (status == InliningStatus_Error)
         return false;
 
     // No inline, just make the call.
     JSFunction *target = nullptr;
     if (targets.length() == 1)
         target = &targets[0]->as<JSFunction>();
 
-    if (target && status == InliningStatus_UseCountTooLow) {
-        MRecompileCheck *check = MRecompileCheck::New(alloc(), target->nonLazyScript(),
-                                                      optimizationInfo().usesBeforeInlining(),
-                                                      MRecompileCheck::RecompileCheck_Inlining);
-        current->add(check);
-    }
-
     return makeCall(target, callInfo, hasClones);
 }
 
 MDefinition *
 IonBuilder::makeCallsiteClone(JSFunction *target, MDefinition *fun)
 {
     // Bake in the clone eagerly if we have a known target. We have arrived here
     // because TI told us that the known target is a should-clone-at-callsite
@@ -6146,20 +6132,17 @@ IonBuilder::insertRecompileCheck()
     while (topBuilder->callerBuilder_)
         topBuilder = topBuilder->callerBuilder_;
 
     // Add recompile check to recompile when the usecount reaches the usecount
     // of the next optimization level.
     OptimizationLevel nextLevel = js_IonOptimizations.nextLevel(curLevel);
     const OptimizationInfo *info = js_IonOptimizations.get(nextLevel);
     uint32_t useCount = info->usesBeforeCompile(topBuilder->script());
-
-    MRecompileCheck *check = MRecompileCheck::New(alloc(), topBuilder->script(), useCount,
-                                MRecompileCheck::RecompileCheck_OptimizationLevel);
-    current->add(check);
+    current->add(MRecompileCheck::New(alloc(), topBuilder->script(), useCount));
 }
 
 JSObject *
 IonBuilder::testSingletonProperty(JSObject *obj, PropertyName *name)
 {
     // We would like to completely no-op property/global accesses which can
     // produce only a particular JSObject. When indicating the access result is
     // definitely an object, type inference does not account for the
@@ -8881,17 +8864,16 @@ IonBuilder::getPropTryCommonGetter(bool 
     // Inline if we can, otherwise, forget it and just generate a call.
     bool inlineable = false;
     if (commonGetter->isInterpreted()) {
         InliningDecision decision = makeInliningDecision(commonGetter, callInfo);
         switch (decision) {
           case InliningDecision_Error:
             return false;
           case InliningDecision_DontInline:
-          case InliningDecision_UseCountTooLow:
             break;
           case InliningDecision_Inline:
             inlineable = true;
             break;
         }
     }
 
     if (inlineable) {
@@ -9298,17 +9280,16 @@ IonBuilder::setPropTryCommonSetter(bool 
 
     // Inline the setter if we can.
     if (commonSetter->isInterpreted()) {
         InliningDecision decision = makeInliningDecision(commonSetter, callInfo);
         switch (decision) {
           case InliningDecision_Error:
             return false;
           case InliningDecision_DontInline:
-          case InliningDecision_UseCountTooLow:
             break;
           case InliningDecision_Inline:
             if (!inlineScriptedCall(callInfo, commonSetter))
                 return false;
             *emitted = true;
             return true;
         }
     }
--- a/js/src/jit/IonBuilder.h
+++ b/js/src/jit/IonBuilder.h
@@ -628,26 +628,24 @@ class IonBuilder : public MIRGenerator
     bool jsop_setaliasedvar(ScopeCoordinate sc);
 
     /* Inlining. */
 
     enum InliningStatus
     {
         InliningStatus_Error,
         InliningStatus_NotInlined,
-        InliningStatus_UseCountTooLow,
         InliningStatus_Inlined
     };
 
     enum InliningDecision
     {
         InliningDecision_Error,
         InliningDecision_Inline,
-        InliningDecision_DontInline,
-        InliningDecision_UseCountTooLow
+        InliningDecision_DontInline
     };
 
     static InliningDecision DontInline(JSScript *targetScript, const char *reason);
 
     // Oracles.
     InliningDecision canInlineTarget(JSFunction *target, CallInfo &callInfo);
     InliningDecision makeInliningDecision(JSFunction *target, CallInfo &callInfo);
     bool selectInliningTargets(ObjectVector &targets, CallInfo &callInfo,
--- a/js/src/jit/IonCaches.cpp
+++ b/js/src/jit/IonCaches.cpp
@@ -398,16 +398,17 @@ IonCache::attachStub(MacroAssembler &mas
     attacher.patchStubCodePointer(masm, code);
 }
 
 bool
 IonCache::linkAndAttachStub(JSContext *cx, MacroAssembler &masm, StubAttacher &attacher,
                             IonScript *ion, const char *attachKind)
 {
     Rooted<JitCode *> code(cx);
+    AutoFlushICache afc("IonCache");
     LinkStatus status = linkCode(cx, masm, ion, code.address());
     if (status != LINK_GOOD)
         return status != LINK_ERROR;
 
     attachStub(masm, attacher, code);
 
     if (pc_) {
         IonSpew(IonSpew_InlineCaches, "Cache %p(%s:%d/%d) generated %s %s stub at %p",
@@ -1687,18 +1688,16 @@ GetPropertyIC::update(JSContext *cx, siz
 {
     void *returnAddr;
     RootedScript outerScript(cx, GetTopIonJSScript(cx, &returnAddr));
     IonScript *ion = outerScript->ionScript();
 
     GetPropertyIC &cache = ion->getCache(cacheIndex).toGetProperty();
     RootedPropertyName name(cx, cache.name());
 
-    AutoFlushCache afc ("GetPropertyCache", cx->runtime()->jitRuntime());
-
     // Override the return value if we are invalidated (bug 728188).
     AutoDetectInvalidation adi(cx, vp.address(), ion);
 
     // If the cache is idempotent, we will redo the op in the interpreter.
     if (cache.idempotent())
         adi.disable();
 
     // For now, just stop generating new stubs once we hit the stub count
@@ -1863,19 +1862,16 @@ GetPropertyParIC::update(ForkJoinContext
         return true;
 
     {
         // Lock the context before mutating the cache. Ideally we'd like to do
         // finer-grained locking, with one lock per cache. However, generating
         // new jitcode uses a global ExecutableAllocator tied to the runtime.
         LockedJSContext ncx(cx);
 
-        // The flusher needs to be under lock.
-        AutoFlushCache afc("GetPropertyParCache", cx->runtime()->jitRuntime());
-
         if (cache.canAttachStub()) {
             bool alreadyStubbed;
             if (!cache.hasOrAddStubbedShape(ncx, obj->lastProperty(), &alreadyStubbed))
                 return cx->setPendingAbortFatal(ParallelBailoutFailedIC);
             if (alreadyStubbed)
                 return true;
 
             // See note about the stub limit in GetPropertyCache.
@@ -2743,18 +2739,16 @@ CanAttachNativeSetProp(HandleObject obj,
 
     return SetPropertyIC::CanAttachNone;
 }
 
 bool
 SetPropertyIC::update(JSContext *cx, size_t cacheIndex, HandleObject obj,
                       HandleValue value)
 {
-    AutoFlushCache afc ("SetPropertyCache", cx->runtime()->jitRuntime());
-
     void *returnAddr;
     RootedScript script(cx, GetTopIonJSScript(cx, &returnAddr));
     IonScript *ion = script->ionScript();
     SetPropertyIC &cache = ion->getCache(cacheIndex).toSetProperty();
     RootedPropertyName name(cx, cache.name());
     RootedId id(cx, AtomToId(name));
 
     // Stop generating new stubs once we hit the stub count limit, see
@@ -2860,17 +2854,16 @@ SetPropertyParIC::update(ForkJoinContext
     }
 
     SetPropertyIC::NativeSetPropCacheability canCache = SetPropertyIC::CanAttachNone;
     bool attachedStub = false;
 
     {
         // See note about locking context in GetPropertyParIC::update.
         LockedJSContext ncx(cx);
-        AutoFlushCache afc("SetPropertyParCache", cx->runtime()->jitRuntime());
 
         if (cache.canAttachStub()) {
             bool alreadyStubbed;
             if (!cache.hasOrAddStubbedShape(ncx, obj->lastProperty(), &alreadyStubbed))
                 return cx->setPendingAbortFatal(ParallelBailoutFailedIC);
             if (alreadyStubbed) {
                 return baseops::SetPropertyHelper<ParallelExecution>(
                     cx, obj, obj, id, baseops::Qualified, &v, cache.strict());
@@ -3420,18 +3413,16 @@ GetElementIC::update(JSContext *cx, size
     if (cache.isDisabled()) {
         if (!GetObjectElementOperation(cx, JSOp(*pc), obj, /* wasObject = */true, idval, res))
             return false;
         if (!cache.monitoredResult())
             types::TypeScript::Monitor(cx, script, pc, res);
         return true;
     }
 
-    AutoFlushCache afc("GetElementCache", cx->runtime()->jitRuntime());
-
     RootedId id(cx);
     if (!ValueToId<CanGC>(cx, idval, &id))
         return false;
 
     bool attachedStub = false;
     if (cache.canAttachStub()) {
         if (IsOptimizableArgumentsObjectForGetElem(obj, idval) &&
             !cache.hasArgumentsStub(obj->is<StrictArgumentsObject>()) &&
@@ -3963,17 +3954,16 @@ GetElementParIC::update(ForkJoinContext 
 
     // Avoid unnecessary locking if cannot attach stubs.
     if (!cache.canAttachStub())
         return true;
 
     {
         // See note about locking context in GetPropertyParIC::update.
         LockedJSContext ncx(cx);
-        AutoFlushCache afc("GetElementParCache", cx->runtime()->jitRuntime());
 
         if (cache.canAttachStub()) {
             bool alreadyStubbed;
             if (!cache.hasOrAddStubbedShape(ncx, obj->lastProperty(), &alreadyStubbed))
                 return cx->setPendingAbortFatal(ParallelBailoutFailedIC);
             if (alreadyStubbed)
                 return true;
 
@@ -4154,18 +4144,16 @@ IsCacheableScopeChain(JSObject *scopeCha
     }
 
     MOZ_ASSUME_UNREACHABLE("Invalid scope chain");
 }
 
 JSObject *
 BindNameIC::update(JSContext *cx, size_t cacheIndex, HandleObject scopeChain)
 {
-    AutoFlushCache afc ("BindNameCache", cx->runtime()->jitRuntime());
-
     RootedScript outerScript(cx, GetTopIonJSScript(cx));
     IonScript *ion = outerScript->ionScript();
     BindNameIC &cache = ion->getCache(cacheIndex).toBindName();
     HandlePropertyName name = cache.name();
 
     RootedObject holder(cx);
     if (scopeChain->is<GlobalObject>()) {
         holder = scopeChain;
@@ -4285,18 +4273,16 @@ IsCacheableNameCallGetter(JSObject *scop
     return IsCacheableGetPropCallNative(obj, holder, shape) ||
         IsCacheableGetPropCallPropertyOp(obj, holder, shape);
 }
 
 bool
 NameIC::update(JSContext *cx, size_t cacheIndex, HandleObject scopeChain,
                MutableHandleValue vp)
 {
-    AutoFlushCache afc ("GetNameCache", cx->runtime()->jitRuntime());
-
     void *returnAddr;
     RootedScript outerScript(cx, GetTopIonJSScript(cx, &returnAddr));
     IonScript *ion = outerScript->ionScript();
 
     NameIC &cache = ion->getCache(cacheIndex).toName();
     RootedPropertyName name(cx, cache.name());
 
     RootedScript script(cx);
@@ -4349,18 +4335,16 @@ CallsiteCloneIC::attach(JSContext *cx, H
     attacher.jumpRejoin(masm);
 
     return linkAndAttachStub(cx, masm, attacher, ion, "generic");
 }
 
 JSObject *
 CallsiteCloneIC::update(JSContext *cx, size_t cacheIndex, HandleObject callee)
 {
-    AutoFlushCache afc ("CallsiteCloneCache", cx->runtime()->jitRuntime());
-
     // Act as the identity for functions that are not clone-at-callsite, as we
     // generate this cache as long as some callees are clone-at-callsite.
     RootedFunction fun(cx, &callee->as<JSFunction>());
     if (!fun->hasScript() || !fun->nonLazyScript()->shouldCloneAtCallsite())
         return fun;
 
     RootedScript outerScript(cx, GetTopIonJSScript(cx));
     IonScript *ion = outerScript->ionScript();
--- a/js/src/jit/IonCode.h
+++ b/js/src/jit/IonCode.h
@@ -535,17 +535,17 @@ struct IonScript
     size_t runtimeSize() const {
         return runtimeSize_;
     }
     CacheLocation *getCacheLocs(uint32_t locIndex) {
         JS_ASSERT(locIndex < runtimeSize_);
         return (CacheLocation *) &runtimeData()[locIndex];
     }
     void toggleBarriers(bool enabled);
-    void purgeCaches(JS::Zone *zone);
+    void purgeCaches();
     void destroyCaches();
     void unlinkFromRuntime(FreeOp *fop);
     void copySnapshots(const SnapshotWriter *writer);
     void copyRecovers(const RecoverWriter *writer);
     void copyBailoutTable(const SnapshotOffset *table);
     void copyConstants(const Value *vp);
     void copySafepointIndices(const SafepointIndex *firstSafepointIndex, MacroAssembler &masm);
     void copyOsiIndices(const OsiIndex *firstOsiIndex, MacroAssembler &masm);
@@ -769,50 +769,33 @@ struct IonScriptCounts
     }
 };
 
 struct VMFunction;
 
 class JitCompartment;
 class JitRuntime;
 
-struct AutoFlushCache
+struct AutoFlushICache
 {
   private:
     uintptr_t start_;
     uintptr_t stop_;
     const char *name_;
-    JitRuntime *runtime_;
-    bool used_;
+    bool inhibit_;
+    AutoFlushICache *prev_;
 
   public:
-    void update(uintptr_t p, size_t len);
-    static void updateTop(uintptr_t p, size_t len);
-    ~AutoFlushCache();
-    AutoFlushCache(const char *nonce, JitRuntime *rt);
-    void flushAnyway();
+    static void setRange(uintptr_t p, size_t len);
+    static void flush(uintptr_t p, size_t len);
+    static void setInhibit();
+    ~AutoFlushICache();
+    AutoFlushICache(const char *nonce, bool inhibit=false);
 };
 
-// If you are currently in the middle of modifing Ion-compiled code, which
-// is going to be flushed at *some* point, but determine that you *must*
-// call a function *right* *now*, two things can go wrong:
-//   1)  The flusher that you were using is still active, but you are about to
-//       enter jitted code, so it needs to be flushed
-//   2) the called function can re-enter a compilation/modification path which
-//       will use your AFC, and thus not flush when his compilation is done
-
-struct AutoFlushInhibitor
-{
-  private:
-    JitRuntime *runtime_;
-    AutoFlushCache *afc;
-  public:
-    AutoFlushInhibitor(JitRuntime *rt);
-    ~AutoFlushInhibitor();
-};
 } // namespace jit
 
 namespace gc {
 
 inline bool
 IsMarked(const jit::VMFunction *)
 {
     // VMFunction are only static objects which are used by WeakMaps as keys.
--- a/js/src/jit/JitCompartment.h
+++ b/js/src/jit/JitCompartment.h
@@ -204,19 +204,16 @@ class JitRuntime
     typedef WeakCache<const VMFunction *, JitCode *> VMWrapperMap;
     VMWrapperMap *functionWrappers_;
 
     // Buffer for OSR from baseline to Ion. To avoid holding on to this for
     // too long, it's also freed in JitCompartment::mark and in EnterBaseline
     // (after returning from JIT code).
     uint8_t *osrTempData_;
 
-    // Keep track of memoryregions that are going to be flushed.
-    AutoFlushCache *flusher_;
-
     // Whether all Ion code in the runtime is protected, and will fault if it
     // is accessed.
     bool ionCodeProtected_;
 
     // If signal handlers are installed, this contains all loop backedges for
     // IonScripts in the runtime.
     InlineList<PatchableBackedge> backedgeList_;
 
@@ -257,24 +254,16 @@ class JitRuntime
     ~JitRuntime();
     bool initialize(JSContext *cx);
 
     uint8_t *allocateOsrTempData(size_t size);
     void freeOsrTempData();
 
     static void Mark(JSTracer *trc);
 
-    AutoFlushCache *flusher() {
-        return flusher_;
-    }
-    void setFlusher(AutoFlushCache *fl) {
-        if (!flusher_ || !fl)
-            flusher_ = fl;
-    }
-
     JSC::ExecutableAllocator *execAlloc() const {
         return execAlloc_;
     }
 
     JSC::ExecutableAllocator *getIonAlloc(JSContext *cx) {
         JS_ASSERT(cx->runtime()->currentThreadOwnsInterruptLock());
         return ionAlloc_ ? ionAlloc_ : createIonAlloc(cx);
     }
--- a/js/src/jit/MCallOptimize.cpp
+++ b/js/src/jit/MCallOptimize.cpp
@@ -1910,16 +1910,19 @@ IonBuilder::inlineAssertFloat32(CallInfo
     current->add(undefined);
     current->push(undefined);
     return InliningStatus_Inlined;
 }
 
 IonBuilder::InliningStatus
 IonBuilder::inlineBoundFunction(CallInfo &nativeCallInfo, JSFunction *target)
 {
+     if (!target->getBoundFunctionTarget()->is<JSFunction>())
+         return InliningStatus_NotInlined;
+
     JSFunction *scriptedTarget = &(target->getBoundFunctionTarget()->as<JSFunction>());
     JSRuntime *runtime = scriptedTarget->runtimeFromMainThread();
 
     // Don't optimize if we're constructing and the callee is not a
     // constructor, so that CallKnown does not have to handle this case
     // (it should always throw).
     if (nativeCallInfo.constructing() && !scriptedTarget->isInterpretedConstructor() &&
         !scriptedTarget->isNativeConstructor())
--- a/js/src/jit/MIR.h
+++ b/js/src/jit/MIR.h
@@ -9864,73 +9864,41 @@ class MHasClass
     }
 };
 
 // Increase the usecount of the provided script upon execution and test if
 // the usecount surpasses the threshold. Upon hit it will recompile the
 // outermost script (i.e. not the inlined script).
 class MRecompileCheck : public MNullaryInstruction
 {
-  public:
-    enum RecompileCheckType {
-        RecompileCheck_OptimizationLevel,
-        RecompileCheck_Inlining
-    };
-
-  private:
     JSScript *script_;
     uint32_t recompileThreshold_;
-    bool forceRecompilation_;
-    bool increaseUseCount_;
-
-    MRecompileCheck(JSScript *script, uint32_t recompileThreshold, RecompileCheckType type)
+
+    MRecompileCheck(JSScript *script, uint32_t recompileThreshold)
       : script_(script),
         recompileThreshold_(recompileThreshold)
     {
-        switch (type) {
-          case RecompileCheck_OptimizationLevel:
-            forceRecompilation_ = false;
-            increaseUseCount_ = true;
-            break;
-          case RecompileCheck_Inlining:
-            forceRecompilation_ = true;
-            increaseUseCount_ = false;
-            break;
-          default:
-            MOZ_ASSUME_UNREACHABLE("Unexpected recompile check type");
-        }
-
         setGuard();
     }
 
   public:
     INSTRUCTION_HEADER(RecompileCheck);
 
-    static MRecompileCheck *New(TempAllocator &alloc, JSScript *script_,
-                                uint32_t useCount, RecompileCheckType type)
-    {
-        return new(alloc) MRecompileCheck(script_, useCount, type);
+    static MRecompileCheck *New(TempAllocator &alloc, JSScript *script_, uint32_t useCount) {
+        return new(alloc) MRecompileCheck(script_, useCount);
     }
 
     JSScript *script() const {
         return script_;
     }
 
     uint32_t recompileThreshold() const {
         return recompileThreshold_;
     }
 
-    bool forceRecompilation() const {
-        return forceRecompilation_;
-    }
-
-    bool increaseUseCount() const {
-        return increaseUseCount_;
-    }
-
     AliasSet getAliasSet() const {
         return AliasSet::None();
     }
 };
 
 class MAsmJSNeg : public MUnaryInstruction
 {
     MAsmJSNeg(MDefinition *op, MIRType type)
--- a/js/src/jit/MIRGraph.h
+++ b/js/src/jit/MIRGraph.h
@@ -611,21 +611,16 @@ class MIRGraph
     }
     void removeBlocksAfter(MBasicBlock *block);
     void removeBlock(MBasicBlock *block);
     void moveBlockToEnd(MBasicBlock *block) {
         JS_ASSERT(block->id());
         blocks_.remove(block);
         blocks_.pushBack(block);
     }
-    void moveBlockBefore(MBasicBlock *at, MBasicBlock *block) {
-        JS_ASSERT(block->id());
-        blocks_.remove(block);
-        blocks_.insertBefore(at, block);
-    }
     size_t numBlocks() const {
         return numBlocks_;
     }
     uint32_t numBlockIds() const {
         return blockIdGen_;
     }
     void allocDefinitionId(MDefinition *ins) {
         ins->setId(idGen_++);
--- a/js/src/jit/VMFunctions.cpp
+++ b/js/src/jit/VMFunctions.cpp
@@ -1018,53 +1018,41 @@ StringReplace(JSContext *cx, HandleStrin
 
     RootedValue rval(cx);
     if (!str_replace_string_raw(cx, string, pattern, repl, &rval))
         return nullptr;
 
     return rval.toString();
 }
 
-static bool
-RecompileImpl(JSContext *cx, bool force)
+bool
+Recompile(JSContext *cx)
 {
     JS_ASSERT(cx->currentlyRunningInJit());
     JitActivationIterator activations(cx->runtime());
     JitFrameIterator iter(activations);
 
     JS_ASSERT(iter.type() == JitFrame_Exit);
     ++iter;
 
     bool isConstructing = iter.isConstructing();
     RootedScript script(cx, iter.script());
     JS_ASSERT(script->hasIonScript());
 
     if (!IsIonEnabled(cx))
         return true;
 
-    MethodStatus status = Recompile(cx, script, nullptr, nullptr, isConstructing, force);
+    MethodStatus status = Recompile(cx, script, nullptr, nullptr, isConstructing);
     if (status == Method_Error)
         return false;
 
     return true;
 }
 
 bool
-ForcedRecompile(JSContext *cx)
-{
-    return RecompileImpl(cx, /* force = */ true);
-}
-
-bool
-Recompile(JSContext *cx)
-{
-    return RecompileImpl(cx, /* force = */ false);
-}
-
-bool
 SetDenseElement(JSContext *cx, HandleObject obj, int32_t index, HandleValue value,
                 bool strict)
 {
     // This function is called from Ion code for StoreElementHole's OOL path.
     // In this case we know the object is native, has no indexed properties
     // and we can use setDenseElement instead of setDenseElementWithType.
 
     MOZ_ASSERT(obj->isNative());
--- a/js/src/jit/VMFunctions.h
+++ b/js/src/jit/VMFunctions.h
@@ -671,17 +671,16 @@ bool InitBaselineFrameForOsr(BaselineFra
                              uint32_t numStackValues);
 
 JSObject *CreateDerivedTypedObj(JSContext *cx, HandleObject descr,
                                 HandleObject owner, int32_t offset);
 
 bool ArraySpliceDense(JSContext *cx, HandleObject obj, uint32_t start, uint32_t deleteCount);
 
 bool Recompile(JSContext *cx);
-bool ForcedRecompile(JSContext *cx);
 JSString *RegExpReplace(JSContext *cx, HandleString string, HandleObject regexp,
                         HandleString repl);
 JSString *StringReplace(JSContext *cx, HandleString string, HandleString pattern,
                         HandleString repl);
 
 bool SetDenseElement(JSContext *cx, HandleObject obj, int32_t index, HandleValue value,
                      bool strict);
 
--- a/js/src/jit/arm/Assembler-arm.cpp
+++ b/js/src/jit/arm/Assembler-arm.cpp
@@ -563,17 +563,17 @@ Assembler::finish()
     }
 }
 
 void
 Assembler::executableCopy(uint8_t *buffer)
 {
     JS_ASSERT(isFinished);
     m_buffer.executableCopy(buffer);
-    AutoFlushCache::updateTop((uintptr_t)buffer, m_buffer.size());
+    AutoFlushICache::setRange(uintptr_t(buffer), m_buffer.size());
 }
 
 void
 Assembler::resetCounter()
 {
     m_buffer.resetCounter();
 }
 
@@ -2410,26 +2410,26 @@ Assembler::retargetNearBranch(Instructio
     JS_ASSERT_IF(i->is<InstBranchImm>(), i->is<InstBImm>() || i->is<InstBLImm>());
     if (i->is<InstBLImm>())
         new (i) InstBLImm(BOffImm(offset), cond);
     else
         new (i) InstBImm(BOffImm(offset), cond);
 
     // Flush the cache, since an instruction was overwritten
     if (final)
-        AutoFlushCache::updateTop(uintptr_t(i), 4);
+        AutoFlushICache::flush(uintptr_t(i), 4);
 }
 
 void
 Assembler::retargetFarBranch(Instruction *i, uint8_t **slot, uint8_t *dest, Condition cond)
 {
     int32_t offset = reinterpret_cast<uint8_t*>(slot) - reinterpret_cast<uint8_t*>(i);
     if (!i->is<InstLDR>()) {
         new (i) InstLDR(Offset, pc, DTRAddr(pc, DtrOffImm(offset - 8)), cond);
-        AutoFlushCache::updateTop(uintptr_t(i), 4);
+        AutoFlushICache::flush(uintptr_t(i), 4);
     }
     *slot = dest;
 
 }
 
 struct PoolHeader : Instruction {
     struct Header
     {
@@ -2521,35 +2521,35 @@ Assembler::patchWrite_NearCall(CodeLocat
     Instruction *inst = (Instruction *) start.raw();
     // Overwrite whatever instruction used to be here with a call.
     // Since the destination is in the same function, it will be within range of the 24<<2 byte
     // bl instruction.
     uint8_t *dest = toCall.raw();
     new (inst) InstBLImm(BOffImm(dest - (uint8_t*)inst) , Always);
     // Ensure everyone sees the code that was just written into memory.
 
-    AutoFlushCache::updateTop(uintptr_t(inst), 4);
+    AutoFlushICache::flush(uintptr_t(inst), 4);
 
 }
 void
 Assembler::patchDataWithValueCheck(CodeLocationLabel label, PatchedImmPtr newValue,
                                    PatchedImmPtr expectedValue)
 {
     Instruction *ptr = (Instruction *) label.raw();
     InstructionIterator iter(ptr);
     Register dest;
     Assembler::RelocStyle rs;
     DebugOnly<const uint32_t *> val = getPtr32Target(&iter, &dest, &rs);
     JS_ASSERT((uint32_t)(const uint32_t *)val == uint32_t(expectedValue.value));
     reinterpret_cast<MacroAssemblerARM*>(dummy)->ma_movPatchable(Imm32(int32_t(newValue.value)),
                                                                  dest, Always, rs, ptr);
     // L_LDR won't cause any instructions to be updated.
     if (rs != L_LDR) {
-        AutoFlushCache::updateTop(uintptr_t(ptr), 4);
-        AutoFlushCache::updateTop(uintptr_t(ptr->next()), 4);
+        AutoFlushICache::flush(uintptr_t(ptr), 4);
+        AutoFlushICache::flush(uintptr_t(ptr->next()), 4);
     }
 }
 
 void
 Assembler::patchDataWithValueCheck(CodeLocationLabel label, ImmPtr newValue, ImmPtr expectedValue)
 {
     patchDataWithValueCheck(label, PatchedImmPtr(newValue.value), PatchedImmPtr(expectedValue.value));
 }
@@ -2671,17 +2671,17 @@ Assembler::ToggleToJmp(CodeLocationLabel
     uint32_t *ptr = (uint32_t *)inst_.raw();
 
     DebugOnly<Instruction *> inst = (Instruction *)inst_.raw();
     JS_ASSERT(inst->is<InstCMP>());
 
     // Zero bits 20-27, then set 24-27 to be correct for a branch.
     // 20-23 will be party of the B's immediate, and should be 0.
     *ptr = (*ptr & ~(0xff << 20)) | (0xa0 << 20);
-    AutoFlushCache::updateTop((uintptr_t)ptr, 4);
+    AutoFlushICache::flush(uintptr_t(ptr), 4);
 }
 
 void
 Assembler::ToggleToCmp(CodeLocationLabel inst_)
 {
     uint32_t *ptr = (uint32_t *)inst_.raw();
 
     DebugOnly<Instruction *> inst = (Instruction *)inst_.raw();
@@ -2694,17 +2694,17 @@ Assembler::ToggleToCmp(CodeLocationLabel
     // Also make sure that the CMP is valid. Part of having a valid CMP is that
     // all of the bits describing the destination in most ALU instructions are
     // all unset (looks like it is encoding r0).
     JS_ASSERT(toRD(*inst) == r0);
 
     // Zero out bits 20-27, then set them to be correct for a compare.
     *ptr = (*ptr & ~(0xff << 20)) | (0x35 << 20);
 
-    AutoFlushCache::updateTop((uintptr_t)ptr, 4);
+    AutoFlushICache::flush(uintptr_t(ptr), 4);
 }
 
 void
 Assembler::ToggleCall(CodeLocationLabel inst_, bool enabled)
 {
     Instruction *inst = (Instruction *)inst_.raw();
     JS_ASSERT(inst->is<InstMovW>() || inst->is<InstLDR>());
 
@@ -2724,17 +2724,17 @@ Assembler::ToggleCall(CodeLocationLabel 
         return;
     }
 
     if (enabled)
         *inst = InstBLXReg(ScratchRegister, Always);
     else
         *inst = InstNOP();
 
-    AutoFlushCache::updateTop(uintptr_t(inst), 4);
+    AutoFlushICache::flush(uintptr_t(inst), 4);
 }
 
 void Assembler::updateBoundsCheck(uint32_t heapSize, Instruction *inst)
 {
     JS_ASSERT(inst->is<InstCMP>());
     InstCMP *cmp = inst->as<InstCMP>();
 
     Register index;
@@ -2746,88 +2746,16 @@ void Assembler::updateBoundsCheck(uint32
     Imm8 imm8 = Imm8(heapSize);
     JS_ASSERT(!imm8.invalid);
 
     *inst = InstALU(InvalidReg, index, imm8, op_cmp, SetCond, Always);
     // NOTE: we don't update the Auto Flush Cache!  this function is currently only called from
     // within AsmJSModule::patchHeapAccesses, which does that for us.  Don't call this!
 }
 
-static uintptr_t
-PageStart(uintptr_t p)
-{
-    static const size_t PageSize = 4096;
-    return p & ~(PageSize - 1);
-}
-
-static bool
-OnSamePage(uintptr_t start1, uintptr_t stop1, uintptr_t start2, uintptr_t stop2)
-{
-    // Return true if (parts of) the two ranges are on the same memory page.
-    return PageStart(stop1) == PageStart(start2) || PageStart(stop2) == PageStart(start1);
-}
-
-void
-AutoFlushCache::update(uintptr_t newStart, size_t len)
-{
-    uintptr_t newStop = newStart + len;
-    used_ = true;
-    if (!start_) {
-        IonSpewCont(IonSpew_CacheFlush,  ".");
-        start_ = newStart;
-        stop_ = newStop;
-        return;
-    }
-
-    if (!OnSamePage(start_, stop_, newStart, newStop)) {
-        // Flush now if the two ranges have no memory page in common, to avoid
-        // problems on Linux where the kernel only flushes the first VMA that
-        // covers the range. This also ensures we don't add too many pages to
-        // the range.
-        IonSpewCont(IonSpew_CacheFlush, "*");
-        JSC::ExecutableAllocator::cacheFlush((void*)newStart, len);
-        return;
-    }
-
-    start_ = Min(start_, newStart);
-    stop_ = Max(stop_, newStop);
-    IonSpewCont(IonSpew_CacheFlush, ".");
-}
-
-AutoFlushCache::~AutoFlushCache()
-{
-   if (!runtime_)
-        return;
-
-    flushAnyway();
-    IonSpewCont(IonSpew_CacheFlush, ">", name_);
-    if (runtime_->flusher() == this) {
-        IonSpewFin(IonSpew_CacheFlush);
-        runtime_->setFlusher(nullptr);
-    }
-}
-
-void
-AutoFlushCache::flushAnyway()
-{
-    if (!runtime_)
-        return;
-
-    IonSpewCont(IonSpew_CacheFlush, "|", name_);
-
-    if (!used_)
-        return;
-
-    if (start_) {
-        JSC::ExecutableAllocator::cacheFlush((void *)start_, size_t(stop_ - start_ + sizeof(Instruction)));
-    } else {
-        JSC::ExecutableAllocator::cacheFlush(nullptr, 0xff000000);
-    }
-    used_ = false;
-}
 InstructionIterator::InstructionIterator(Instruction *i_) : i(i_) {
     const PoolHeader *ph;
     // If this is a guard, and the next instruction is a header, always work around the pool
     // If it isn't a guard, then start looking ahead.
     if (InstIsArtificialGuard(i, &ph)) {
         i = i->next();
     }
 }
--- a/js/src/jit/arm/Simulator-arm.cpp
+++ b/js/src/jit/arm/Simulator-arm.cpp
@@ -1101,16 +1101,17 @@ Simulator::setLastDebuggerInput(char *in
 {
     js_free(lastDebuggerInput_);
     lastDebuggerInput_ = input;
 }
 
 void
 Simulator::FlushICache(void *start_addr, size_t size)
 {
+    IonSpewCont(IonSpew_CacheFlush, "[%p %zx]", start_addr, size);
     if (!Simulator::ICacheCheckingEnabled)
         return;
     SimulatorRuntime *srt = Simulator::Current()->srt_;
     AutoLockSimulatorRuntime alsr(srt);
     js::jit::FlushICache(srt->icache(), start_addr, size);
 }
 
 Simulator::~Simulator()
--- a/js/src/jit/arm/Trampoline-arm.cpp
+++ b/js/src/jit/arm/Trampoline-arm.cpp
@@ -108,17 +108,16 @@ JitCode *
 JitRuntime::generateEnterJIT(JSContext *cx, EnterJitType type)
 {
     const Address slot_token(sp, offsetof(EnterJITStack, token));
     const Address slot_vp(sp, offsetof(EnterJITStack, vp));
 
     JS_ASSERT(OsrFrameReg == r3);
 
     MacroAssembler masm(cx);
-    AutoFlushCache afc("GenerateEnterJIT", cx->runtime()->jitRuntime());
     Assembler *aasm = &masm;
 
     // Save non-volatile registers. These must be saved by the trampoline,
     // rather than the JIT'd code, because they are scanned by the conservative
     // scanner.
     masm.startDataTransferM(IsStore, sp, DB, WriteBack);
     masm.transferReg(r4); // [sp,0]
     masm.transferReg(r5); // [sp,4]
@@ -329,16 +328,17 @@ JitRuntime::generateEnterJIT(JSContext *
     //   ASSERT(JSReturnReg_Type.code() == JSReturnReg_Data.code()+1);
     //   aasm->as_extdtr(IsStore, 64, true, Offset,
     //                   JSReturnReg_Data, EDtrAddr(r5, EDtrOffImm(0)));
 
     // Restore non-volatile registers and return.
     GenerateReturn(masm, true, &cx->runtime()->spsProfiler);
 
     Linker linker(masm);
+    AutoFlushICache afc("EnterJIT");
     JitCode *code = linker.newCode<NoGC>(cx, JSC::OTHER_CODE);
 
 #ifdef JS_ION_PERF
     writePerfSpewerJitCodeProfile(code, "EnterJIT");
 #endif
 
     return code;
 }
@@ -390,16 +390,17 @@ JitRuntime::generateInvalidator(JSContex
     // (computed by InvalidationBailout)
     masm.ma_add(sp, r1, sp);
 
     // Jump to shared bailout tail. The BailoutInfo pointer has to be in r2.
     JitCode *bailoutTail = cx->runtime()->jitRuntime()->getBailoutTail();
     masm.branch(bailoutTail);
 
     Linker linker(masm);
+    AutoFlushICache afc("Invalidator");
     JitCode *code = linker.newCode<NoGC>(cx, JSC::OTHER_CODE);
     IonSpew(IonSpew_Invalidate, "   invalidation thunk created at %p", (void *) code->raw());
 
 #ifdef JS_ION_PERF
     writePerfSpewerJitCodeProfile(code, "Invalidator");
 #endif
 
     return code;
@@ -492,16 +493,17 @@ JitRuntime::generateArgumentsRectifier(J
     // sizeDescriptor
     // return address
 
     // Discard pushed arguments.
     masm.ma_alu(sp, lsr(r4, FRAMESIZE_SHIFT), sp, op_add);
 
     masm.ret();
     Linker linker(masm);
+    AutoFlushICache afc("ArgumentsRectifier");
     JitCode *code = linker.newCode<NoGC>(cx, JSC::OTHER_CODE);
 
     CodeOffsetLabel returnLabel(returnOffset);
     returnLabel.fixup(&masm);
     if (returnAddrOut)
         *returnAddrOut = (void *) (code->raw() + returnLabel.offset());
 
 #ifdef JS_ION_PERF
@@ -616,32 +618,34 @@ JitRuntime::generateBailoutTable(JSConte
     Label bailout;
     for (size_t i = 0; i < BAILOUT_TABLE_SIZE; i++)
         masm.ma_bl(&bailout);
     masm.bind(&bailout);
 
     GenerateBailoutThunk(cx, masm, frameClass);
 
     Linker linker(masm);
+    AutoFlushICache afc("BailoutTable");
     JitCode *code = linker.newCode<NoGC>(cx, JSC::OTHER_CODE);
 
 #ifdef JS_ION_PERF
     writePerfSpewerJitCodeProfile(code, "BailoutTable");
 #endif
 
     return code;
 }
 
 JitCode *
 JitRuntime::generateBailoutHandler(JSContext *cx)
 {
     MacroAssembler masm(cx);
     GenerateBailoutThunk(cx, masm, NO_FRAME_SIZE_CLASS_ID);
 
     Linker linker(masm);
+    AutoFlushICache afc("BailoutHandler");
     JitCode *code = linker.newCode<NoGC>(cx, JSC::OTHER_CODE);
 
 #ifdef JS_ION_PERF
     writePerfSpewerJitCodeProfile(code, "BailoutHandler");
 #endif
 
     return code;
 }
@@ -804,16 +808,17 @@ JitRuntime::generateVMWrapper(JSContext 
         break;
     }
     masm.leaveExitFrame();
     masm.retn(Imm32(sizeof(IonExitFrameLayout) +
                     f.explicitStackSlots() * sizeof(void *) +
                     f.extraValuesToPop * sizeof(Value)));
 
     Linker linker(masm);
+    AutoFlushICache afc("VMWrapper");
     JitCode *wrapper = linker.newCode<NoGC>(cx, JSC::OTHER_CODE);
     if (!wrapper)
         return nullptr;
 
     // linker.newCode may trigger a GC and sweep functionWrappers_ so we have to
     // use relookupOrAdd instead of add.
     if (!functionWrappers_->relookupOrAdd(p, &f, wrapper))
         return nullptr;
@@ -852,16 +857,17 @@ JitRuntime::generatePreBarrier(JSContext
         JS_ASSERT(type == MIRType_Shape);
         masm.callWithABI(JS_FUNC_TO_DATA_PTR(void *, MarkShapeFromIon));
     }
 
     masm.PopRegsInMask(save);
     masm.ret();
 
     Linker linker(masm);
+    AutoFlushICache afc("PreBarrier");
     JitCode *code = linker.newCode<NoGC>(cx, JSC::OTHER_CODE);
 
 #ifdef JS_ION_PERF
     writePerfSpewerJitCodeProfile(code, "PreBarrier");
 #endif
 
     return code;
 }
@@ -907,16 +913,17 @@ JitRuntime::generateDebugTrapHandler(JSC
     masm.bind(&forcedReturn);
     masm.loadValue(Address(r11, BaselineFrame::reverseOffsetOfReturnValue()),
                    JSReturnOperand);
     masm.mov(r11, sp);
     masm.pop(r11);
     masm.ret();
 
     Linker linker(masm);
+    AutoFlushICache afc("DebugTrapHandler");
     JitCode *codeDbg = linker.newCode<NoGC>(cx, JSC::OTHER_CODE);
 
 #ifdef JS_ION_PERF
     writePerfSpewerJitCodeProfile(codeDbg, "DebugTrapHandler");
 #endif
 
     return codeDbg;
 }
@@ -924,16 +931,17 @@ JitRuntime::generateDebugTrapHandler(JSC
 JitCode *
 JitRuntime::generateExceptionTailStub(JSContext *cx)
 {
     MacroAssembler masm;
 
     masm.handleFailureWithHandlerTail();
 
     Linker linker(masm);
+    AutoFlushICache afc("ExceptionTailStub");
     JitCode *code = linker.newCode<NoGC>(cx, JSC::OTHER_CODE);
 
 #ifdef JS_ION_PERF
     writePerfSpewerJitCodeProfile(code, "ExceptionTailStub");
 #endif
 
     return code;
 }
@@ -941,16 +949,17 @@ JitRuntime::generateExceptionTailStub(JS
 JitCode *
 JitRuntime::generateBailoutTailStub(JSContext *cx)
 {
     MacroAssembler masm;
 
     masm.generateBailoutTail(r1, r2);
 
     Linker linker(masm);
+    AutoFlushICache afc("BailoutTailStub");
     JitCode *code = linker.newCode<NoGC>(cx, JSC::OTHER_CODE);
 
 #ifdef JS_ION_PERF
     writePerfSpewerJitCodeProfile(code, "BailoutTailStub");
 #endif
 
     return code;
 }
--- a/js/src/jit/mips/Assembler-mips.cpp
+++ b/js/src/jit/mips/Assembler-mips.cpp
@@ -121,17 +121,17 @@ InstImm::extractImm16(BOffImm16 *dest)
 void
 jit::PatchJump(CodeLocationJump &jump_, CodeLocationLabel label)
 {
     Instruction *inst1 = (Instruction *)jump_.raw();
     Instruction *inst2 = inst1->next();
 
     Assembler::updateLuiOriValue(inst1, inst2, (uint32_t)label.raw());
 
-    AutoFlushCache::updateTop(uintptr_t(inst1), 8);
+    AutoFlushICache::flush(uintptr_t(inst1), 8);
 }
 
 void
 Assembler::finish()
 {
     MOZ_ASSERT(!isFinished);
     isFinished = true;
 }
@@ -145,17 +145,17 @@ Assembler::executableCopy(uint8_t *buffe
     // Patch all long jumps during code copy.
     for (size_t i = 0; i < longJumps_.length(); i++) {
         Instruction *inst1 = (Instruction *) ((uint32_t)buffer + longJumps_[i]);
 
         uint32_t value = extractLuiOriValue(inst1, inst1->next());
         updateLuiOriValue(inst1, inst1->next(), (uint32_t)buffer + value);
     }
 
-    AutoFlushCache::updateTop((uintptr_t)buffer, m_buffer.size());
+    AutoFlushICache::setRange(uintptr_t(buffer), m_buffer.size());
 }
 
 uint32_t
 Assembler::actualOffset(uint32_t off_) const
 {
     return off_;
 }
 
@@ -1331,17 +1331,17 @@ Assembler::patchWrite_NearCall(CodeLocat
     // - Jump has to be the same size because of patchWrite_NearCallSize.
     // - Return address has to be at the end of replaced block.
     // Short jump wouldn't be more efficient.
     writeLuiOriInstructions(inst, &inst[1], ScratchRegister, (uint32_t)dest);
     inst[2] = InstReg(op_special, ScratchRegister, zero, ra, ff_jalr);
     inst[3] = InstNOP();
 
     // Ensure everyone sees the code that was just written into memory.
-    AutoFlushCache::updateTop(uintptr_t(inst), patchWrite_NearCallSize());
+    AutoFlushICache::flush(uintptr_t(inst), patchWrite_NearCallSize());
 }
 
 uint32_t
 Assembler::extractLuiOriValue(Instruction *inst0, Instruction *inst1)
 {
     InstImm *i0 = (InstImm *) inst0;
     InstImm *i1 = (InstImm *) inst1;
     MOZ_ASSERT(i0->extractOpcode() == ((uint32_t)op_lui >> OpcodeShift));
@@ -1378,17 +1378,17 @@ Assembler::patchDataWithValueCheck(CodeL
 
     // Extract old Value
     DebugOnly<uint32_t> value = Assembler::extractLuiOriValue(&inst[0], &inst[1]);
     MOZ_ASSERT(value == uint32_t(expectedValue.value));
 
     // Replace with new value
     Assembler::updateLuiOriValue(inst, inst->next(), uint32_t(newValue.value));
 
-    AutoFlushCache::updateTop(uintptr_t(inst), 8);
+    AutoFlushICache::flush(uintptr_t(inst), 8);
 }
 
 void
 Assembler::patchDataWithValueCheck(CodeLocationLabel label, ImmPtr newValue, ImmPtr expectedValue)
 {
     patchDataWithValueCheck(label, PatchedImmPtr(newValue.value),
                             PatchedImmPtr(expectedValue.value));
 }
@@ -1480,30 +1480,30 @@ void
 Assembler::ToggleToJmp(CodeLocationLabel inst_)
 {
     InstImm * inst = (InstImm *)inst_.raw();
 
     MOZ_ASSERT(inst->extractOpcode() == ((uint32_t)op_andi >> OpcodeShift));
     // We converted beq to andi, so now we restore it.
     inst->setOpcode(op_beq);
 
-    AutoFlushCache::updateTop((uintptr_t)inst, 4);
+    AutoFlushICache::flush(uintptr_t(inst), 4);
 }
 
 void
 Assembler::ToggleToCmp(CodeLocationLabel inst_)
 {
     InstImm * inst = (InstImm *)inst_.raw();
 
     // toggledJump is allways used for short jumps.
     MOZ_ASSERT(inst->extractOpcode() == ((uint32_t)op_beq >> OpcodeShift));
     // Replace "beq $zero, $zero, offset" with "andi $zero, $zero, offset"
     inst->setOpcode(op_andi);
 
-    AutoFlushCache::updateTop((uintptr_t)inst, 4);
+    AutoFlushICache::flush(uintptr_t(inst), 4);
 }
 
 void
 Assembler::ToggleCall(CodeLocationLabel inst_, bool enabled)
 {
     Instruction *inst = (Instruction *)inst_.raw();
     InstImm *i0 = (InstImm *) inst;
     InstImm *i1 = (InstImm *) i0->next();
@@ -1515,80 +1515,15 @@ Assembler::ToggleCall(CodeLocationLabel 
     if (enabled) {
         InstReg jalr = InstReg(op_special, ScratchRegister, zero, ra, ff_jalr);
         *i2 = jalr;
     } else {
         InstNOP nop;
         *i2 = nop;
     }
 
-    AutoFlushCache::updateTop((uintptr_t)i2, 4);
+    AutoFlushICache::flush(uintptr_t(i2), 4);
 }
 
 void Assembler::updateBoundsCheck(uint32_t heapSize, Instruction *inst)
 {
     MOZ_ASSUME_UNREACHABLE("NYI");
 }
-
-void
-AutoFlushCache::update(uintptr_t newStart, size_t len)
-{
-    uintptr_t newStop = newStart + len;
-    if (this == nullptr) {
-        // just flush right here and now.
-        JSC::ExecutableAllocator::cacheFlush((void*)newStart, len);
-        return;
-    }
-    used_ = true;
-    if (!start_) {
-        IonSpewCont(IonSpew_CacheFlush, ".");
-        start_ = newStart;
-        stop_ = newStop;
-        return;
-    }
-
-    if (newStop < start_ - 4096 || newStart > stop_ + 4096) {
-        // If this would add too many pages to the range. Flush recorded range
-        // and make a new range.
-        IonSpewCont(IonSpew_CacheFlush, "*");
-        JSC::ExecutableAllocator::cacheFlush((void*)start_, stop_);
-        start_ = newStart;
-        stop_ = newStop;
-        return;
-    }
-    start_ = Min(start_, newStart);
-    stop_ = Max(stop_, newStop);
-    IonSpewCont(IonSpew_CacheFlush, ".");
-}
-
-AutoFlushCache::~AutoFlushCache()
-{
-    if (!runtime_)
-        return;
-
-    flushAnyway();
-    IonSpewCont(IonSpew_CacheFlush, ">", name_);
-    if (runtime_->flusher() == this) {
-        IonSpewFin(IonSpew_CacheFlush);
-        runtime_->setFlusher(nullptr);
-    }
-}
-
-void
-AutoFlushCache::flushAnyway()
-{
-    if (!runtime_)
-        return;
-
-    IonSpewCont(IonSpew_CacheFlush, "|", name_);
-
-    if (!used_)
-        return;
-
-    if (start_) {
-        JSC::ExecutableAllocator::cacheFlush((void *)start_,
-                                             size_t(stop_ - start_ + sizeof(Instruction)));
-    } else {
-        JSC::ExecutableAllocator::cacheFlush(nullptr, 0xff000000);
-    }
-    used_ = false;
-}
-
--- a/js/src/jit/mips/Trampoline-mips.cpp
+++ b/js/src/jit/mips/Trampoline-mips.cpp
@@ -132,18 +132,16 @@ JitRuntime::generateEnterJIT(JSContext *
     const Register reg_code = a0;
     const Register reg_argc = a1;
     const Register reg_argv = a2;
     const Register reg_frame = a3;
 
     MOZ_ASSERT(OsrFrameReg == reg_frame);
 
     MacroAssembler masm(cx);
-    AutoFlushCache afc("GenerateEnterJIT", cx->runtime()->jitRuntime());
-
     GeneratePrologue(masm);
 
     const Address slotToken(sp, sizeof(EnterJITRegs) + offsetof(EnterJITArgs, calleeToken));
     const Address slotVp(sp, sizeof(EnterJITRegs) + offsetof(EnterJITArgs, vp));
 
     // Save stack pointer into s4
     masm.movePtr(StackPointer, s4);
 
@@ -299,16 +297,17 @@ JitRuntime::generateEnterJIT(JSContext *
     // Store the returned value into the slotVp
     masm.loadPtr(slotVp, s1);
     masm.storeValue(JSReturnOperand, Address(s1, 0));
 
     // Restore non-volatile registers and return.
     GenerateReturn(masm, ShortJump);
 
     Linker linker(masm);
+    AutoFlushICache afc("GenerateEnterJIT");
     JitCode *code = linker.newCode<NoGC>(cx, JSC::OTHER_CODE);
 
 #ifdef JS_ION_PERF
     writePerfSpewerJitCodeProfile(code, "EnterJIT");
 #endif
 
     return code;
 }
@@ -371,16 +370,17 @@ JitRuntime::generateInvalidator(JSContex
     // (computed by InvalidationBailout)
     masm.addPtr(a1, StackPointer);
 
     // Jump to shared bailout tail. The BailoutInfo pointer has to be in r2.
     JitCode *bailoutTail = cx->runtime()->jitRuntime()->getBailoutTail();
     masm.branch(bailoutTail);
 
     Linker linker(masm);
+    AutoFlushICache afc("Invalidator");
     JitCode *code = linker.newCode<NoGC>(cx, JSC::OTHER_CODE);
     IonSpew(IonSpew_Invalidate, "   invalidation thunk created at %p", (void *) code->raw());
 
 #ifdef JS_ION_PERF
     writePerfSpewerJitCodeProfile(code, "Invalidator");
 #endif
 
     return code;
@@ -502,16 +502,17 @@ JitRuntime::generateArgumentsRectifier(J
     // sizeDescriptor
     // return address
 
     // Discard pushed arguments.
     masm.addPtr(t0, StackPointer);
 
     masm.ret();
     Linker linker(masm);
+    AutoFlushICache afc("ArgumentsRectifier");
     JitCode *code = linker.newCode<NoGC>(cx, JSC::OTHER_CODE);
 
     CodeOffsetLabel returnLabel(returnOffset);
     returnLabel.fixup(&masm);
     if (returnAddrOut)
         *returnAddrOut = (void *) (code->raw() + returnLabel.offset());
 
 #ifdef JS_ION_PERF
@@ -625,32 +626,34 @@ JitRuntime::generateBailoutTable(JSConte
         masm.as_bal(BOffImm16(offset));
         masm.nop();
     }
     masm.bind(&bailout);
 
     GenerateBailoutThunk(cx, masm, frameClass);
 
     Linker linker(masm);
+    AutoFlushICache afc("BailoutTable");
     JitCode *code = linker.newCode<NoGC>(cx, JSC::OTHER_CODE);
 
 #ifdef JS_ION_PERF
     writePerfSpewerJitCodeProfile(code, "BailoutTable");
 #endif
 
     return code;
 }
 
 JitCode *
 JitRuntime::generateBailoutHandler(JSContext *cx)
 {
     MacroAssembler masm(cx);
     GenerateBailoutThunk(cx, masm, NO_FRAME_SIZE_CLASS_ID);
 
     Linker linker(masm);
+    AutoFlushICache afc("BailoutHandler");
     JitCode *code = linker.newCode<NoGC>(cx, JSC::OTHER_CODE);
 
 #ifdef JS_ION_PERF
     writePerfSpewerJitCodeProfile(code, "BailoutHandler");
 #endif
 
     return code;
 }
@@ -837,16 +840,17 @@ JitRuntime::generateVMWrapper(JSContext 
         break;
     }
     masm.leaveExitFrame();
     masm.retn(Imm32(sizeof(IonExitFrameLayout) +
                     f.explicitStackSlots() * sizeof(uintptr_t) +
                     f.extraValuesToPop * sizeof(Value)));
 
     Linker linker(masm);
+    AutoFlushICache afc("VMWrapper");
     JitCode *wrapper = linker.newCode<NoGC>(cx, JSC::OTHER_CODE);
     if (!wrapper)
         return nullptr;
 
     // linker.newCode may trigger a GC and sweep functionWrappers_ so we have
     // to use relookupOrAdd instead of add.
     if (!functionWrappers_->relookupOrAdd(p, &f, wrapper))
         return nullptr;
@@ -886,16 +890,17 @@ JitRuntime::generatePreBarrier(JSContext
         MOZ_ASSERT(type == MIRType_Shape);
         masm.callWithABI(JS_FUNC_TO_DATA_PTR(void *, MarkShapeFromIon));
     }
 
     masm.PopRegsInMask(save);
     masm.ret();
 
     Linker linker(masm);
+    AutoFlushICache afc("PreBarrier");
     JitCode *code = linker.newCode<NoGC>(cx, JSC::OTHER_CODE);
 
 #ifdef JS_ION_PERF
     writePerfSpewerJitCodeProfile(code, "PreBarrier");
 #endif
 
     return code;
 }
@@ -945,16 +950,17 @@ JitRuntime::generateDebugTrapHandler(JSC
     masm.bind(&forcedReturn);
     masm.loadValue(Address(s5, BaselineFrame::reverseOffsetOfReturnValue()),
                    JSReturnOperand);
     masm.movePtr(s5, StackPointer);
     masm.pop(s5);
     masm.ret();
 
     Linker linker(masm);
+    AutoFlushICache afc("DebugTrapHandler");
     JitCode *codeDbg = linker.newCode<NoGC>(cx, JSC::OTHER_CODE);
 
 #ifdef JS_ION_PERF
     writePerfSpewerJitCodeProfile(codeDbg, "DebugTrapHandler");
 #endif
 
     return codeDbg;
 }
@@ -963,16 +969,17 @@ JitRuntime::generateDebugTrapHandler(JSC
 JitCode *
 JitRuntime::generateExceptionTailStub(JSContext *cx)
 {
     MacroAssembler masm;
 
     masm.handleFailureWithHandlerTail();
 
     Linker linker(masm);
+    AutoFlushICache afc("ExceptionTailStub");
     JitCode *code = linker.newCode<NoGC>(cx, JSC::OTHER_CODE);
 
 #ifdef JS_ION_PERF
     writePerfSpewerJitCodeProfile(code, "ExceptionTailStub");
 #endif
 
     return code;
 }
@@ -980,16 +987,17 @@ JitRuntime::generateExceptionTailStub(JS
 JitCode *
 JitRuntime::generateBailoutTailStub(JSContext *cx)
 {
     MacroAssembler masm;
 
     masm.generateBailoutTail(a1, a2);
 
     Linker linker(masm);
+    AutoFlushICache afc("BailoutTailStub");
     JitCode *code = linker.newCode<NoGC>(cx, JSC::OTHER_CODE);
 
 #ifdef JS_ION_PERF
     writePerfSpewerJitCodeProfile(code, "BailoutTailStub");
 #endif
 
     return code;
 }
--- a/js/src/jit/shared/Assembler-x86-shared.cpp
+++ b/js/src/jit/shared/Assembler-x86-shared.cpp
@@ -126,27 +126,8 @@ AssemblerX86Shared::InvertCondition(Cond
       case Below:
         return AboveOrEqual;
       case BelowOrEqual:
         return Above;
       default:
         MOZ_ASSUME_UNREACHABLE("unexpected condition");
     }
 }
-
-void
-AutoFlushCache::update(uintptr_t newStart, size_t len)
-{
-}
-
-void
-AutoFlushCache::flushAnyway()
-{
-}
-
-AutoFlushCache::~AutoFlushCache()
-{
-    if (!runtime_)
-        return;
-
-    if (runtime_->flusher() == this)
-        runtime_->setFlusher(nullptr);
-}
--- a/js/src/jsapi-tests/testGCFinalizeCallback.cpp
+++ b/js/src/jsapi-tests/testGCFinalizeCallback.cpp
@@ -7,17 +7,17 @@
 static const unsigned BufferSize = 20;
 static unsigned FinalizeCalls = 0;
 static JSFinalizeStatus StatusBuffer[BufferSize];
 static bool IsCompartmentGCBuffer[BufferSize];
 
 BEGIN_TEST(testGCFinalizeCallback)
 {
     JS_SetGCParameter(rt, JSGC_MODE, JSGC_MODE_INCREMENTAL);
-    JS_SetFinalizeCallback(rt, FinalizeCallback);
+    JS_AddFinalizeCallback(rt, FinalizeCallback, nullptr);
 
     /* Full GC, non-incremental. */
     FinalizeCalls = 0;
     JS_GC(rt);
     CHECK(rt->gc.isFull);
     CHECK(checkSingleGroup());
     CHECK(checkFinalizeStatus());
     CHECK(checkFinalizeIsCompartmentGC(false));
@@ -111,17 +111,17 @@ BEGIN_TEST(testGCFinalizeCallback)
      * Make some use of the globals here to ensure the compiler doesn't optimize
      * them away in release builds, causing the compartments to be collected and
      * the test to fail.
      */
     CHECK(JS_IsGlobalObject(global1));
     CHECK(JS_IsGlobalObject(global2));
     CHECK(JS_IsGlobalObject(global3));
 
-    JS_SetFinalizeCallback(rt, nullptr);
+    JS_RemoveFinalizeCallback(rt, FinalizeCallback);
     return true;
 }
 
 bool checkSingleGroup()
 {
     CHECK(FinalizeCalls < BufferSize);
     CHECK(FinalizeCalls == 3);
     return true;
@@ -157,17 +157,17 @@ bool checkFinalizeIsCompartmentGC(bool i
 {
     for (unsigned i = 0; i < FinalizeCalls; ++i)
         CHECK(IsCompartmentGCBuffer[i] == isCompartmentGC);
 
     return true;
 }
 
 static void
-FinalizeCallback(JSFreeOp *fop, JSFinalizeStatus status, bool isCompartmentGC)
+FinalizeCallback(JSFreeOp *fop, JSFinalizeStatus status, bool isCompartmentGC, void *data)
 {
     if (FinalizeCalls < BufferSize) {
         StatusBuffer[FinalizeCalls] = status;
         IsCompartmentGCBuffer[FinalizeCalls] = isCompartmentGC;
     }
     ++FinalizeCalls;
 }
 END_TEST(testGCFinalizeCallback)
--- a/js/src/jsapi-tests/testIntern.cpp
+++ b/js/src/jsapi-tests/testIntern.cpp
@@ -28,21 +28,21 @@ struct StringWrapperStruct
     bool     strOk;
 } sw;
 
 BEGIN_TEST(testInternAcrossGC)
 {
     sw.str = JS_InternString(cx, "wrapped chars that another test shouldn't be using");
     sw.strOk = false;
     CHECK(sw.str);
-    JS_SetFinalizeCallback(rt, FinalizeCallback);
+    JS_AddFinalizeCallback(rt, FinalizeCallback, nullptr);
     JS_GC(rt);
     CHECK(sw.strOk);
     return true;
 }
 
 static void
-FinalizeCallback(JSFreeOp *fop, JSFinalizeStatus status, bool isCompartmentGC)
+FinalizeCallback(JSFreeOp *fop, JSFinalizeStatus status, bool isCompartmentGC, void *data)
 {
     if (status == JSFINALIZE_GROUP_START)
         sw.strOk = js::gc::IsStringMarked(&sw.str);
 }
 END_TEST(testInternAcrossGC)
--- a/js/src/jsapi.cpp
+++ b/js/src/jsapi.cpp
@@ -1614,25 +1614,24 @@ JS::RemoveScriptRootRT(JSRuntime *rt, JS
     RemoveRoot(rt, (void *)rp);
     *rp = nullptr;
 }
 
 JS_PUBLIC_API(bool)
 JS_AddExtraGCRootsTracer(JSRuntime *rt, JSTraceDataOp traceOp, void *data)
 {
     AssertHeapIsIdle(rt);
-    return !!rt->gc.blackRootTracers.append(ExtraTracer(traceOp, data));
+    return !!rt->gc.blackRootTracers.append(Callback<JSTraceDataOp>(traceOp, data));
 }
 
 JS_PUBLIC_API(void)
 JS_RemoveExtraGCRootsTracer(JSRuntime *rt, JSTraceDataOp traceOp, void *data)
 {
-    AssertHeapIsIdle(rt);
     for (size_t i = 0; i < rt->gc.blackRootTracers.length(); i++) {
-        ExtraTracer *e = &rt->gc.blackRootTracers[i];
+        Callback<JSTraceDataOp> *e = &rt->gc.blackRootTracers[i];
         if (e->op == traceOp && e->data == data) {
             rt->gc.blackRootTracers.erase(e);
             break;
         }
     }
 }
 
 #ifdef DEBUG
@@ -1902,21 +1901,34 @@ JS_MaybeGC(JSContext *cx)
 JS_PUBLIC_API(void)
 JS_SetGCCallback(JSRuntime *rt, JSGCCallback cb, void *data)
 {
     AssertHeapIsIdle(rt);
     rt->gc.gcCallback = cb;
     rt->gc.gcCallbackData = data;
 }
 
-JS_PUBLIC_API(void)
-JS_SetFinalizeCallback(JSRuntime *rt, JSFinalizeCallback cb)
+JS_PUBLIC_API(bool)
+JS_AddFinalizeCallback(JSRuntime *rt, JSFinalizeCallback cb, void *data)
 {
     AssertHeapIsIdle(rt);
-    rt->gc.finalizeCallback = cb;
+    return rt->gc.finalizeCallbacks.append(Callback<JSFinalizeCallback>(cb, data));
+}
+
+JS_PUBLIC_API(void)
+JS_RemoveFinalizeCallback(JSRuntime *rt, JSFinalizeCallback cb)
+{
+    for (Callback<JSFinalizeCallback> *p = rt->gc.finalizeCallbacks.begin();
+         p < rt->gc.finalizeCallbacks.end(); p++)
+    {
+        if (p->op == cb) {
+            rt->gc.finalizeCallbacks.erase(p);
+            break;
+        }
+    }
 }
 
 JS_PUBLIC_API(bool)
 JS_IsAboutToBeFinalized(JS::Heap<JSObject *> *objp)
 {
     return IsObjectAboutToBeFinalized(objp->unsafeGet());
 }
 
--- a/js/src/jsapi.h
+++ b/js/src/jsapi.h
@@ -703,17 +703,17 @@ typedef enum JSFinalizeStatus {
 
     /*
      * Called at the end of collection when everything has been swept.
      */
     JSFINALIZE_COLLECTION_END
 } JSFinalizeStatus;
 
 typedef void
-(* JSFinalizeCallback)(JSFreeOp *fop, JSFinalizeStatus status, bool isCompartment);
+(* JSFinalizeCallback)(JSFreeOp *fop, JSFinalizeStatus status, bool isCompartment, void *data);
 
 typedef bool
 (* JSInterruptCallback)(JSContext *cx);
 
 typedef void
 (* JSErrorReporter)(JSContext *cx, const char *message, JSErrorReport *report);
 
 #ifdef MOZ_TRACE_JSCALLS
@@ -2069,18 +2069,21 @@ extern JS_PUBLIC_API(void)
 JS_GC(JSRuntime *rt);
 
 extern JS_PUBLIC_API(void)
 JS_MaybeGC(JSContext *cx);
 
 extern JS_PUBLIC_API(void)
 JS_SetGCCallback(JSRuntime *rt, JSGCCallback cb, void *data);
 
+extern JS_PUBLIC_API(bool)
+JS_AddFinalizeCallback(JSRuntime *rt, JSFinalizeCallback cb, void *data);
+
 extern JS_PUBLIC_API(void)
-JS_SetFinalizeCallback(JSRuntime *rt, JSFinalizeCallback cb);
+JS_RemoveFinalizeCallback(JSRuntime *rt, JSFinalizeCallback cb);
 
 extern JS_PUBLIC_API(bool)
 JS_IsGCMarkingTracer(JSTracer *trc);
 
 /* For assertions only. */
 #ifdef JS_DEBUG
 extern JS_PUBLIC_API(bool)
 JS_IsMarkingGray(JSTracer *trc);
--- a/js/src/jsgc.cpp
+++ b/js/src/jsgc.cpp
@@ -1080,17 +1080,16 @@ GCRuntime::GCRuntime(JSRuntime *rt) :
     nextScheduled(0),
     deterministicOnly(false),
     incrementalLimit(0),
 #endif
     validate(true),
     fullCompartmentChecks(false),
     gcCallback(nullptr),
     sliceCallback(nullptr),
-    finalizeCallback(nullptr),
     mallocBytes(0),
     mallocGCTriggered(false),
     scriptAndCountsVector(nullptr),
     alwaysPreserveCode(false),
 #ifdef DEBUG
     noGCOrAllocationCheck(0),
 #endif
     lock(nullptr),
@@ -3820,18 +3819,21 @@ GCRuntime::beginSweepingZoneGroup()
     }
 
     validateIncrementalMarking();
 
     FreeOp fop(rt, sweepOnBackgroundThread);
 
     {
         gcstats::AutoPhase ap(stats, gcstats::PHASE_FINALIZE_START);
-        if (finalizeCallback)
-            finalizeCallback(&fop, JSFINALIZE_GROUP_START, !isFull /* unused */);
+        for (Callback<JSFinalizeCallback> *p = rt->gc.finalizeCallbacks.begin();
+             p < rt->gc.finalizeCallbacks.end(); p++)
+        {
+            p->op(&fop, JSFINALIZE_GROUP_START, !isFull /* unused */, p->data);
+        }
     }
 
     if (sweepingAtoms) {
         gcstats::AutoPhase ap(stats, gcstats::PHASE_SWEEP_ATOMS);
         rt->sweepAtoms();
     }
 
     /* Prune out dead views from ArrayBuffer's view lists. */
@@ -3913,18 +3915,21 @@ GCRuntime::beginSweepingZoneGroup()
     }
 
     finalizePhase = 0;
     sweepZone = currentZoneGroup;
     sweepKindIndex = 0;
 
     {
         gcstats::AutoPhase ap(stats, gcstats::PHASE_FINALIZE_END);
-        if (finalizeCallback)
-            finalizeCallback(&fop, JSFINALIZE_GROUP_END, !isFull /* unused */);
+        for (Callback<JSFinalizeCallback> *p = rt->gc.finalizeCallbacks.begin();
+             p < rt->gc.finalizeCallbacks.end(); p++)
+        {
+            p->op(&fop, JSFINALIZE_GROUP_END, !isFull /* unused */, p->data);
+        }
     }
 }
 
 void
 GCRuntime::endSweepingZoneGroup()
 {
     /* Update the GC state for zones we have swept and unlink the list. */
     for (GCZoneGroupIter zone(rt); !zone.done(); zone.next()) {
@@ -4131,18 +4136,21 @@ GCRuntime::endSweepPhase(JSGCInvocationK
             AutoLockGC lock(rt);
             ExpireChunksAndArenas(rt, gckind == GC_SHRINK);
         }
     }
 
     {
         gcstats::AutoPhase ap(stats, gcstats::PHASE_FINALIZE_END);
 
-        if (finalizeCallback)
-            finalizeCallback(&fop, JSFINALIZE_COLLECTION_END, !isFull);
+        for (Callback<JSFinalizeCallback> *p = rt->gc.finalizeCallbacks.begin();
+             p < rt->gc.finalizeCallbacks.end(); p++)
+        {
+            p->op(&fop, JSFINALIZE_COLLECTION_END, !isFull, p->data);
+        }
 
         /* If we finished a full GC, then the gray bits are correct. */
         if (isFull)
             grayBitsValid = true;
     }
 
     /* Set up list of zones for sweeping of background things. */
     JS_ASSERT(!sweepingZones);
@@ -5375,17 +5383,17 @@ js::PurgePCCounts(JSContext *cx)
 void
 js::PurgeJITCaches(Zone *zone)
 {
 #ifdef JS_ION
     for (ZoneCellIterUnderGC i(zone, FINALIZE_SCRIPT); !i.done(); i.next()) {
         JSScript *script = i.get<JSScript>();
 
         /* Discard Ion caches. */
-        jit::PurgeCaches(script, zone);
+        jit::PurgeCaches(script);
     }
 #endif
 }
 
 void
 ArenaLists::normalizeBackgroundFinalizeState(AllocKind thingKind)
 {
     ArenaLists::BackgroundFinalizeState *bfs = &backgroundFinalizeState[thingKind];
--- a/js/src/jsgc.h
+++ b/js/src/jsgc.h
@@ -46,28 +46,16 @@ unsigned GetCPUCount();
 
 enum HeapState {
     Idle,             // doing nothing with the GC heap
     Tracing,          // tracing the GC heap without collecting, e.g. IterateCompartments()
     MajorCollecting,  // doing a GC of the major heap
     MinorCollecting   // doing a GC of the minor heap (nursery)
 };
 
-struct ExtraTracer {
-    JSTraceDataOp op;
-    void *data;
-
-    ExtraTracer()
-      : op(nullptr), data(nullptr)
-        {}
-    ExtraTracer(JSTraceDataOp op, void *data)
-      : op(op), data(data)
-        {}
-};
-
 namespace jit {
     class JitCode;
 }
 
 namespace gc {
 
 enum State {
     NO_INCREMENTAL,
--- a/js/src/vm/Runtime.cpp
+++ b/js/src/vm/Runtime.cpp
@@ -75,16 +75,17 @@ PerThreadData::PerThreadData(JSRuntime *
     jitTop(nullptr),
     jitJSContext(nullptr),
     jitStackLimit(0),
 #ifdef JS_TRACE_LOGGING
     traceLogger(nullptr),
 #endif
     activation_(nullptr),
     asmJSActivationStack_(nullptr),
+    autoFlushICache_(nullptr),
 #if defined(JS_ARM_SIMULATOR) || defined(JS_MIPS_SIMULATOR)
     simulator_(nullptr),
     simulatorStackLimit_(0),
 #endif
     dtoaState(nullptr),
     suppressGC(0),
     activeCompilations(0)
 {}
--- a/js/src/vm/Runtime.h
+++ b/js/src/vm/Runtime.h
@@ -89,16 +89,17 @@ class AsmJSActivation;
 class MathCache;
 
 namespace jit {
 class JitRuntime;
 class JitActivation;
 struct PcScriptCache;
 class Simulator;
 class SimulatorRuntime;
+class AutoFlushICache;
 }
 
 /*
  * GetSrcNote cache to avoid O(n^2) growth in finding a source note for a
  * given pc in a script. We use the script->code pointer to tag the cache,
  * instead of the script address itself, so that source notes are always found
  * by offset from the bytecode with which they were generated.
  */
@@ -528,16 +529,19 @@ class PerThreadData : public PerThreadDa
      * Points to the most recent activation running on the thread.
      * See Activation comment in vm/Stack.h.
      */
     js::Activation *activation_;
 
     /* See AsmJSActivation comment. Protected by rt->interruptLock. */
     js::AsmJSActivation *asmJSActivationStack_;
 
+    /* Pointer to the current AutoFlushICache. */
+    js::jit::AutoFlushICache *autoFlushICache_;
+
 #if defined(JS_ARM_SIMULATOR) || defined(JS_MIPS_SIMULATOR)
     js::jit::Simulator *simulator_;
     uintptr_t simulatorStackLimit_;
 #endif
 
   public:
     js::Activation *const *addressOfActivation() const {
         return &activation_;
@@ -603,16 +607,19 @@ class PerThreadData : public PerThreadDa
             pt->runtime_ = rt;
         }
 
         ~AutoEnterRuntime() {
             pt->runtime_ = nullptr;
         }
     };
 
+    js::jit::AutoFlushICache *autoFlushICache() const;
+    void setAutoFlushICache(js::jit::AutoFlushICache *afc);
+
 #if defined(JS_ARM_SIMULATOR) || defined(JS_MIPS_SIMULATOR)
     js::jit::Simulator *simulator() const;
     void setSimulator(js::jit::Simulator *sim);
     js::jit::SimulatorRuntime *simulatorRuntime() const;
     uintptr_t *addressOfSimulatorStackLimit();
 #endif
 };
 
--- a/js/src/vm/SavedStacks.cpp
+++ b/js/src/vm/SavedStacks.cpp
@@ -4,16 +4,17 @@
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 
 #include "vm/SavedStacks.h"
 
 #include "jsapi.h"
 #include "jscompartment.h"
+#include "jsfriendapi.h"
 #include "jsnum.h"
 
 #include "vm/GlobalObject.h"
 #include "vm/StringBuffer.h"
 
 #include "jsobjinlines.h"
 
 using mozilla::AddToHash;
@@ -420,16 +421,24 @@ SavedStacks::sizeOfExcludingThis(mozilla
 bool
 SavedStacks::insertFrames(JSContext *cx, ScriptFrameIter &iter, MutableHandle<SavedFrame*> frame)
 {
     if (iter.done()) {
         frame.set(nullptr);
         return true;
     }
 
+    // Don't report the over-recursion error because if we are blowing the stack
+    // here, we already blew the stack in JS, reported it, and we are creating
+    // the saved stack for the over-recursion error object. We do this check
+    // here, rather than inside saveCurrentStack, because in some cases we will
+    // pass the check there, despite later failing the check here (for example,
+    // in js/src/jit-test/tests/saved-stacks/bug-1006876-too-much-recursion.js).
+    JS_CHECK_RECURSION_DONT_REPORT(cx, return false);
+
     ScriptFrameIter thisFrame(iter);
     Rooted<SavedFrame*> parentFrame(cx);
     if (!insertFrames(cx, ++iter, &parentFrame))
         return false;
 
     RootedScript script(cx, thisFrame.script());
     RootedFunction callee(cx, thisFrame.maybeCallee());
     const char *filename = script->filename();
--- a/js/src/vm/TraceLogging.h
+++ b/js/src/vm/TraceLogging.h
@@ -132,17 +132,16 @@ namespace jit {
     _(IrregexpExecute)                                \
     _(VM)                                             \
                                                       \
     /* Specific passes during ion compilation */      \
     _(SplitCriticalEdges)                             \
     _(RenumberBlocks)                                 \
     _(DominatorTree)                                  \
     _(PhiAnalysis)                                    \
-    _(MakeLoopsContiguous)                            \
     _(ApplyTypes)                                     \
     _(ParallelSafetyAnalysis)                         \
     _(AliasAnalysis)                                  \
     _(GVN)                                            \
     _(UCE)                                            \
     _(LICM)                                           \
     _(RangeAnalysis)                                  \
     _(EffectiveAddressAnalysis)                       \
--- a/js/xpconnect/src/XPCConvert.cpp
+++ b/js/xpconnect/src/XPCConvert.cpp
@@ -61,17 +61,17 @@ XPCConvert::IsMethodReflectable(const XP
 }
 
 static JSObject*
 UnwrapNativeCPOW(nsISupports* wrapper)
 {
     nsCOMPtr<nsIXPConnectWrappedJS> underware = do_QueryInterface(wrapper);
     if (underware) {
         JSObject* mainObj = underware->GetJSObject();
-        if (mainObj && mozilla::jsipc::JavaScriptParent::IsCPOW(mainObj))
+        if (mainObj && mozilla::jsipc::IsCPOW(mainObj))
             return mainObj;
     }
     return nullptr;
 }
 
 /***************************************************************************/
 
 // static
--- a/js/xpconnect/src/XPCJSID.cpp
+++ b/js/xpconnect/src/XPCJSID.cpp
@@ -460,17 +460,17 @@ nsJSIID::Enumerate(nsIXPConnectWrappedNa
  * DOM object, or null. The object may well be cross-compartment from |cx|.
  */
 static JSObject *
 FindObjectForHasInstance(JSContext *cx, HandleObject objArg)
 {
     RootedObject obj(cx, objArg), proto(cx);
 
     while (obj && !IS_WN_REFLECTOR(obj) &&
-           !IsDOMObject(obj) && !mozilla::jsipc::JavaScriptParent::IsCPOW(obj))
+           !IsDOMObject(obj) && !mozilla::jsipc::IsCPOW(obj))
     {
         if (js::IsWrapper(obj)) {
             obj = js::CheckedUnwrap(obj, /* stopAtOuter = */ false);
             continue;
         }
         if (!js::GetObjectProto(cx, obj, &proto))
             return nullptr;
         obj = proto;
@@ -482,18 +482,18 @@ nsresult
 xpc::HasInstance(JSContext *cx, HandleObject objArg, const nsID *iid, bool *bp)
 {
     *bp = false;
 
     RootedObject obj(cx, FindObjectForHasInstance(cx, objArg));
     if (!obj)
         return NS_OK;
 
-    if (mozilla::jsipc::JavaScriptParent::IsCPOW(obj))
-        return mozilla::jsipc::JavaScriptParent::InstanceOf(obj, iid, bp);
+    if (mozilla::jsipc::IsCPOW(obj))
+        return mozilla::jsipc::InstanceOf(obj, iid, bp);
 
     nsISupports *identity = UnwrapReflectorToISupports(obj);
     if (!identity)
         return NS_OK;
 
     nsCOMPtr<nsISupports> supp;
     identity->QueryInterface(*iid, getter_AddRefs(supp));
     *bp = supp;
--- a/js/xpconnect/src/XPCJSRuntime.cpp
+++ b/js/xpconnect/src/XPCJSRuntime.cpp
@@ -809,17 +809,20 @@ XPCJSRuntime::CustomGCCallback(JSGCStatu
     }
 
     nsTArray<xpcGCCallback> callbacks(extraGCCallbacks);
     for (uint32_t i = 0; i < callbacks.Length(); ++i)
         callbacks[i](status);
 }
 
 /* static */ void
-XPCJSRuntime::FinalizeCallback(JSFreeOp *fop, JSFinalizeStatus status, bool isCompartmentGC)
+XPCJSRuntime::FinalizeCallback(JSFreeOp *fop,
+                               JSFinalizeStatus status,
+                               bool isCompartmentGC,
+                               void *data)
 {
     XPCJSRuntime* self = nsXPConnect::GetRuntimeInstance();
     if (!self)
         return;
 
     switch (status) {
         case JSFINALIZE_GROUP_START:
         {
@@ -1576,17 +1579,17 @@ ReloadPrefsCallback(const char *pref, vo
 
 XPCJSRuntime::~XPCJSRuntime()
 {
     // This destructor runs before ~CycleCollectedJSRuntime, which does the
     // actual JS_DestroyRuntime() call. But destroying the runtime triggers
     // one final GC, which can call back into the runtime with various
     // callback if we aren't careful. Null out the relevant callbacks.
     js::SetActivityCallback(Runtime(), nullptr, nullptr);
-    JS_SetFinalizeCallback(Runtime(), nullptr);
+    JS_RemoveFinalizeCallback(Runtime(), FinalizeCallback);
 
     // Clear any pending exception.  It might be an XPCWrappedJS, and if we try
     // to destroy it later we will crash.
     SetPendingException(nullptr);
 
     JS::SetGCSliceCallback(Runtime(), mPrevGCSliceCallback);
 
     xpc_DelocalizeRuntime(Runtime());
@@ -3155,17 +3158,17 @@ XPCJSRuntime::XPCJSRuntime(nsXPConnect* 
     JS_SetNativeStackQuota(runtime,
                            kStackQuota,
                            kStackQuota - kSystemCodeBuffer,
                            kStackQuota - kSystemCodeBuffer - kTrustedScriptBuffer);
 
     JS_SetDestroyCompartmentCallback(runtime, CompartmentDestroyedCallback);
     JS_SetCompartmentNameCallback(runtime, CompartmentNameCallback);
     mPrevGCSliceCallback = JS::SetGCSliceCallback(runtime, GCSliceCallback);
-    JS_SetFinalizeCallback(runtime, FinalizeCallback);
+    JS_AddFinalizeCallback(runtime, FinalizeCallback, nullptr);
     JS_SetWrapObjectCallbacks(runtime, &WrapObjectCallbacks);
     js::SetPreserveWrapperCallback(runtime, PreserveWrapper);
 #ifdef MOZ_CRASHREPORTER
     JS_EnumerateDiagnosticMemoryRegions(DiagnosticMemoryCallback);
 #endif
 #ifdef MOZ_ENABLE_PROFILER_SPS
     if (PseudoStack *stack = mozilla_get_pseudo_stack())
         stack->sampleRuntime(runtime);
--- a/js/xpconnect/src/xpcprivate.h
+++ b/js/xpconnect/src/xpcprivate.h
@@ -511,17 +511,20 @@ public:
     void EndCycleCollectionCallback(mozilla::CycleCollectorResults &aResults) MOZ_OVERRIDE;
     void DispatchDeferredDeletion(bool continuation) MOZ_OVERRIDE;
 
     void CustomGCCallback(JSGCStatus status) MOZ_OVERRIDE;
     bool CustomContextCallback(JSContext *cx, unsigned operation) MOZ_OVERRIDE;
     static void GCSliceCallback(JSRuntime *rt,
                                 JS::GCProgress progress,
                                 const JS::GCDescription &desc);
-    static void FinalizeCallback(JSFreeOp *fop, JSFinalizeStatus status, bool isCompartmentGC);
+    static void FinalizeCallback(JSFreeOp *fop,
+                                 JSFinalizeStatus status,
+                                 bool isCompartmentGC,
+                                 void *data);
 
     inline void AddVariantRoot(XPCTraceableVariant* variant);
     inline void AddWrappedJSRoot(nsXPCWrappedJS* wrappedJS);
     inline void AddObjectHolderRoot(XPCJSObjectHolder* holder);
 
     static void SuspectWrappedNative(XPCWrappedNative *wrapper,
                                      nsCycleCollectionNoteRootCallback &cb);
 
--- a/layout/base/nsDisplayList.cpp
+++ b/layout/base/nsDisplayList.cpp
@@ -799,17 +799,17 @@ static void RecordFrameMetrics(nsIFrame*
   if (GetApzcTreePrintPref()) {
     if (nsIContent* content = frameForCompositionBoundsCalculation->GetContent()) {
       nsAutoString contentDescription;
       content->Describe(contentDescription);
       metrics.SetContentDescription(NS_LossyConvertUTF16toASCII(contentDescription).get());
     }
   }
 
-  metrics.mPresShellId = presShell->GetPresShellId();
+  metrics.SetPresShellId(presShell->GetPresShellId());
 
   // If the scroll frame's content is marked 'scrollgrab', record this
   // in the FrameMetrics so APZ knows to provide the scroll grabbing
   // behaviour.
   if (aScrollFrame && nsContentUtils::HasScrollgrab(aScrollFrame->GetContent())) {
     metrics.mHasScrollgrab = true;
   }
 
--- a/layout/reftests/webm-video/bug686957.html
+++ b/layout/reftests/webm-video/bug686957.html
@@ -1,12 +1,18 @@
 <!DOCTYPE HTML>
 <html class="reftest-wait">
 <body style="background:white;">
-<!-- Test that if we seek to the end of a video we get the last frame displayed -->
+<!--
+    Test that if we seek to the end of a video we get the last frame displayed
+
+    The 15ms timeout is here to work around the screenshot racing with
+    video frames being asynchronously delivered to the screen. If the test fails
+    intermittently again, we may need to tweak the timeout value.
+-->
 <video src="frames.webm"
        preload="auto"
        id="v"
        onloadedmetadata="document.getElementById('v').currentTime=document.getElementById('v').duration"
-       onseeked="setTimeout(function(){document.documentElement.className = '';}, 0);"
+       onseeked="setTimeout(function(){document.documentElement.className = '';}, 15);"
 </video>
 </body>
 </html>
--- a/layout/style/nsStyleUtil.cpp
+++ b/layout/style/nsStyleUtil.cpp
@@ -76,50 +76,51 @@ void nsStyleUtil::AppendEscapedCSSString
 
   aReturn.Append(quoteChar);
 }
 
 /* static */ bool
 nsStyleUtil::AppendEscapedCSSIdent(const nsAString& aIdent, nsAString& aReturn)
 {
   // The relevant parts of the CSS grammar are:
-  //   ident    [-]?{nmstart}{nmchar}*
+  //   ident    ([-]?{nmstart}|[-][-]){nmchar}*
   //   nmstart  [_a-z]|{nonascii}|{escape}
   //   nmchar   [_a-z0-9-]|{nonascii}|{escape}
   //   nonascii [^\0-\177]
   //   escape   {unicode}|\\[^\n\r\f0-9a-f]
   //   unicode  \\[0-9a-f]{1,6}(\r\n|[ \n\r\t\f])?
-  // from http://www.w3.org/TR/CSS21/syndata.html#tokenization
+  // from http://www.w3.org/TR/CSS21/syndata.html#tokenization but
+  // modified for idents by
+  // http://dev.w3.org/csswg/cssom/#serialize-an-identifier and
+  // http://dev.w3.org/csswg/css-syntax/#would-start-an-identifier
 
   const char16_t* in = aIdent.BeginReading();
   const char16_t* const end = aIdent.EndReading();
 
   if (in == end)
     return true;
 
   // A leading dash does not need to be escaped as long as it is not the
   // *only* character in the identifier.
-  if (in + 1 != end && *in == '-') {
+  if (*in == '-') {
+    if (in + 1 == end) {
+      aReturn.Append(char16_t('\\'));
+      aReturn.Append(char16_t('-'));
+      return true;
+    }
+
     aReturn.Append(char16_t('-'));
     ++in;
   }
 
   // Escape a digit at the start (including after a dash),
   // numerically.  If we didn't escape it numerically, it would get
   // interpreted as a numeric escape for the wrong character.
-  // A second dash immediately after a leading dash must also be
-  // escaped, but this may be done symbolically.
-  if (in != end && (*in == '-' ||
-                    ('0' <= *in && *in <= '9'))) {
-    if (*in == '-') {
-      aReturn.Append(char16_t('\\'));
-      aReturn.Append(char16_t('-'));
-    } else {
-      aReturn.AppendPrintf("\\%hX ", *in);
-    }
+  if (in != end && ('0' <= *in && *in <= '9')) {
+    aReturn.AppendPrintf("\\%hX ", *in);
     ++in;
   }
 
   for (; in != end; ++in) {
     char16_t ch = *in;
     if (ch == 0x00) {
       return false;
     }
--- a/layout/style/test/test_css_escape_api.html
+++ b/layout/style/test/test_css_escape_api.html
@@ -61,17 +61,17 @@ is(CSS.escape('-2a'), '-\\32 a', "escapi
 is(CSS.escape('-3a'), '-\\33 a', "escapingFailed Char: -3a");
 is(CSS.escape('-4a'), '-\\34 a', "escapingFailed Char: -4a");
 is(CSS.escape('-5a'), '-\\35 a', "escapingFailed Char: -5a");
 is(CSS.escape('-6a'), '-\\36 a', "escapingFailed Char: -6a");
 is(CSS.escape('-7a'), '-\\37 a', "escapingFailed Char: -7a");
 is(CSS.escape('-8a'), '-\\38 a', "escapingFailed Char: -8a");
 is(CSS.escape('-9a'), '-\\39 a', "escapingFailed Char: -9a");
 
-is(CSS.escape('--a'), '-\\-a', "escapingFailed Char: --a");
+is(CSS.escape('--a'), '--a', 'Should not need to escape leading "--"');
 
 is(CSS.escape('\x80\x2D\x5F\xA9'), '\\80 \x2D\x5F\xA9', "escapingFailed Char: \\x80\\x2D\\x5F\\xA9");
 is(CSS.escape('\xA0\xA1\xA2'), '\xA0\xA1\xA2', "escapingFailed Char: \\xA0\\xA1\\xA2");
 is(CSS.escape('a0123456789b'), 'a0123456789b', "escapingFailed Char: a0123465789");
 is(CSS.escape('abcdefghijklmnopqrstuvwxyz'), 'abcdefghijklmnopqrstuvwxyz', "escapingFailed Char: abcdefghijklmnopqrstuvwxyz");
 is(CSS.escape('ABCDEFGHIJKLMNOPQRSTUVWXYZ'), 'ABCDEFGHIJKLMNOPQRSTUVWXYZ', "escapingFailed Char: ABCDEFGHIJKLMNOPQRSTUVWXYZBCDEFGHIJKLMNOPQRSTUVWXYZ");
 
 is(CSS.escape('\x20\x21\x78\x79'), '\\ \\!xy', "escapingFailed Char: \\x20\\x21\\x78\\x79");
--- a/layout/style/test/test_parser_diagnostics_unprintables.html
+++ b/layout/style/test/test_parser_diagnostics_unprintables.html
@@ -69,17 +69,17 @@ const substitutions = [
   { t: "\\32 ",  i: "\\32 ",  s: "2"  },
   { t: "\\33 ",  i: "\\33 ",  s: "3"  },
   { t: "\\34 ",  i: "\\34 ",  s: "4"  },
   { t: "\\35 ",  i: "\\35 ",  s: "5"  },
   { t: "\\36 ",  i: "\\36 ",  s: "6"  },
   { t: "\\37 ",  i: "\\37 ",  s: "7"  },
   { t: "\\38 ",  i: "\\38 ",  s: "8"  },
   { t: "\\39 ",  i: "\\39 ",  s: "9"  },
-  { t: "-\\-",   i: "-\\-",   s: "--" },
+  { t: "-\\-",   i: "--",     s: "--" },
   { t: "-\\30 ", i: "-\\30 ", s: "-0" },
   { t: "-\\31 ", i: "-\\31 ", s: "-1" },
   { t: "-\\32 ", i: "-\\32 ", s: "-2" },
   { t: "-\\33 ", i: "-\\33 ", s: "-3" },
   { t: "-\\34 ", i: "-\\34 ", s: "-4" },
   { t: "-\\35 ", i: "-\\35 ", s: "-5" },
   { t: "-\\36 ", i: "-\\36 ", s: "-6" },
   { t: "-\\37 ", i: "-\\37 ", s: "-7" },
--- a/layout/style/xbl-marquee/xbl-marquee.xml
+++ b/layout/style/xbl-marquee/xbl-marquee.xml
@@ -261,21 +261,17 @@
                 try {
                   // Things to watch out for here:
                   // * Weird |new| precedence.
                   // * Getting the correct constructor (via Xrays).
                   // * Waiving the constructor before invoking it, so that we can
                   //   call it (since XBL gets opaque non-callable wrappers to content).
                   // * The fact that contentFn is transitively waived, which we need
                   //   in order to be able to invoke it.
-                  // * Using the local |bind| to be sure that it does the right thing.
-                  //   Note that the underlying function still executes in the content
-                  //   scope.
-                  var contentFn = new (XPCNativeWrapper.unwrap(window.Function))("event", aValue);
-                  this["_on" + aName] = Function.prototype.bind.call(contentFn, this);
+                  this["_on" + aName] = new (XPCNativeWrapper.unwrap(window.Function))("event", aValue);
                 }
                 catch(e) {
                   return false;
                 }
                 this.addEventListener(aName, this["_on" + aName], false);
               }
               else {
                 this["_on" + aName] = aValue;
--- a/mobile/android/base/AndroidManifest.xml.in
+++ b/mobile/android/base/AndroidManifest.xml.in
@@ -70,20 +70,20 @@
 
     <!-- App requires OpenGL ES 2.0 -->
     <uses-feature android:glEsVersion="0x00020000" android:required="true" />
 
     <application android:label="@string/moz_app_displayname"
                  android:icon="@drawable/icon"
                  android:name="org.mozilla.gecko.GeckoApplication"
                  android:hardwareAccelerated="true"
-#ifdef MOZILLA_OFFICIAL
+#if !defined(MOZILLA_OFFICIAL) || defined(NIGHTLY_BUILD) && defined(MOZ_DEBUG)
+                 android:debuggable="true">
+#else
                  android:debuggable="false">
-#else
-                 android:debuggable="true">
 #endif
 
         <meta-data android:name="com.sec.android.support.multiwindow" android:value="true"/>
 
         <!-- If the windowSoftInputMode adjust* flag changes below, the
              setSoftInputMode call in BrowserSearch#onStop must also be updated. -->
         <activity android:name=".App"
                   android:label="@string/moz_app_displayname"
--- a/mobile/android/base/Makefile.in
+++ b/mobile/android/base/Makefile.in
@@ -35,17 +35,16 @@ MOZ_BUILD_TIMESTAMP=$(shell echo `$(PYTH
 DEFINES += \
   -DANDROID_VERSION_CODE=$(ANDROID_VERSION_CODE) \
   -DMOZ_ANDROID_SHARED_ID="$(MOZ_ANDROID_SHARED_ID)" \
   -DMOZ_ANDROID_SHARED_ACCOUNT_TYPE="$(MOZ_ANDROID_SHARED_ACCOUNT_TYPE)" \
   -DMOZ_ANDROID_SHARED_FXACCOUNT_TYPE="$(MOZ_ANDROID_SHARED_FXACCOUNT_TYPE)" \
   -DMOZ_APP_BUILDID=$(MOZ_APP_BUILDID) \
   -DMOZ_BUILD_TIMESTAMP=$(MOZ_BUILD_TIMESTAMP) \
   -DUA_BUILDID=$(UA_BUILDID) \
-  -DMOZ_DEBUG=$(MOZ_DEBUG) \
   $(NULL)
 
 GARBAGE += \
   AndroidManifest.xml  \
   WebappManifestFragment.xml.frag \
   classes.dex  \
   gecko.ap_  \
   res/values/strings.xml \
--- a/mobile/android/base/jni-generator.py
+++ b/mobile/android/base/jni-generator.py
@@ -62,17 +62,17 @@ class Generator:
                 functionName = match.group('functionName')
 
             match = re.match(paramsRegex, line)
             if match:
                 paramTypes = re.split('\s*,\s*', match.group(1))
                 paramNames = ['arg%d' % i for i in range(0, len(paramTypes))]
                 if returnType == 'void':
                     returnValue = ''
-                elif returnType in ('jobject', 'jstring'):
+                elif returnType in ('jobject', 'jstring') or returnType.endswith('Array'):
                     returnValue = 'nullptr'
                 elif returnType in ('jint', 'jfloat', 'jdouble', 'jlong'):
                     returnValue = '0'
                 elif returnType == 'jboolean':
                     returnValue = 'false'
                 else:
                     raise Exception(('Unsupported JNI return type %s found; '
                                      + 'please update mobile/android/base/'
--- a/mobile/android/base/moz.build
+++ b/mobile/android/base/moz.build
@@ -508,17 +508,17 @@ ANDROID_RES_DIRS += [
     OBJDIR + '/res',
 ]
 
 ANDROID_GENERATED_RESFILES += [
     'res/raw/suggestedsites.json',
     'res/values/strings.xml',
 ]
 
-for var in ('MOZ_ANDROID_ANR_REPORTER', 'MOZ_LINKER_EXTRACT', 'MOZILLA_OFFICIAL'):
+for var in ('MOZ_ANDROID_ANR_REPORTER', 'MOZ_LINKER_EXTRACT', 'MOZILLA_OFFICIAL', 'MOZ_DEBUG'):
     if CONFIG[var]:
         DEFINES[var] = 1
 
 for var in ('MOZ_UPDATER', 'MOZ_PKG_SPECIAL'):
     if CONFIG[var]:
         DEFINES[var] = CONFIG[var]
 
 for var in ('ANDROID_PACKAGE_NAME', 'ANDROID_CPU_ARCH', 'CPU_ARCH',
--- a/mobile/android/base/tests/testEventDispatcher.java
+++ b/mobile/android/base/tests/testEventDispatcher.java
@@ -8,16 +8,17 @@ import org.mozilla.gecko.EventDispatcher
 import org.mozilla.gecko.util.EventCallback;
 import org.mozilla.gecko.util.GeckoEventListener;
 import org.mozilla.gecko.util.NativeEventListener;
 import org.mozilla.gecko.util.NativeJSObject;
 import org.mozilla.gecko.util.ThreadUtils;
 
 import android.os.Bundle;
 
+import org.json.JSONArray;
 import org.json.JSONException;
 import org.json.JSONObject;
 
 /**
  * Tests the proper operation of EventDispatcher,
  * including associated NativeJSObject objects.
  */
 public class testEventDispatcher extends UITest
@@ -103,24 +104,46 @@ public class testEventDispatcher extends
         if (NATIVE_EVENT.equals(event)) {
             checkNativeJSObject(message);
             checkNativeJSObject(message.getObject("object"));
             fAssertNotSame("optObject returns existent value",
                     null, message.optObject("object", null));
             fAssertSame("optObject returns fallback value if nonexistent",
                     null, message.optObject("nonexistent_object", null));
 
+            final NativeJSObject[] objectArray = message.getObjectArray("objectArray");
+            fAssertNotNull("Native object array should exist", objectArray);
+            fAssertEquals("Native object array has correct length", 2, objectArray.length);
+            fAssertSame("Native object array index 0 has correct value", null, objectArray[0]);
+            fAssertNotSame("Native object array index 1 has correct value", null, objectArray[1]);
+            checkNativeJSObject(objectArray[1]);
+            fAssertNotSame("optObjectArray returns existent value",
+                null, message.optObjectArray("objectArray", null));
+            fAssertSame("optObjectArray returns fallback value if nonexistent",
+                null, message.optObjectArray("nonexistent_objectArray", null));
+
             final Bundle bundle = message.toBundle();
             checkBundle(bundle);
             checkBundle(bundle.getBundle("object"));
             fAssertNotSame("optBundle returns property value if it exists",
                     null, message.optBundle("object", null));
             fAssertSame("optBundle returns fallback value if property does not exist",
                     null, message.optBundle("nonexistent_object", null));
 
+            final Bundle[] bundleArray = message.getBundleArray("objectArray");
+            fAssertNotNull("Native bundle array should exist", bundleArray);
+            fAssertEquals("Native bundle array has correct length", 2, bundleArray.length);
+            fAssertSame("Native bundle array index 0 has correct value", null, bundleArray[0]);
+            fAssertNotSame("Native bundle array index 1 has correct value", null, bundleArray[1]);
+            checkBundle(bundleArray[1]);
+            fAssertNotSame("optBundleArray returns existent value",
+                null, message.optBundleArray("objectArray", null));
+            fAssertSame("optBundleArray returns fallback value if nonexistent",
+                null, message.optBundleArray("nonexistent_objectArray", null));
+
         } else if (NATIVE_RESPONSE_EVENT.equals(event)) {
             final String response = message.getString("response");
             if ("success".equals(response)) {
                 callback.sendSuccess(response);
             } else if ("error".equals(response)) {
                 callback.sendError(response);
             } else if ("cancel".equals(response)) {
                 callback.sendCancel();
@@ -133,44 +156,143 @@ public class testEventDispatcher extends
         }
     }
 
     private void checkBundle(final Bundle bundle) {
         fAssertEquals("Bundle boolean has correct value", true, bundle.getBoolean("boolean"));
         fAssertEquals("Bundle int has correct value", 1, bundle.getInt("int"));
         fAssertEquals("Bundle double has correct value", 0.5, bundle.getDouble("double"));
         fAssertEquals("Bundle string has correct value", "foo", bundle.getString("string"));
+
+        final boolean[] booleanArray = bundle.getBooleanArray("booleanArray");
+        fAssertNotNull("Bundle boolean array should exist", booleanArray);
+        fAssertEquals("Bundle boolean array has correct length", 2, booleanArray.length);
+        fAssertEquals("Bundle boolean array index 0 has correct value", false, booleanArray[0]);
+        fAssertEquals("Bundle boolean array index 1 has correct value", true, booleanArray[1]);
+
+        final int[] intArray = bundle.getIntArray("intArray");
+        fAssertNotNull("Bundle int array should exist", intArray);
+        fAssertEquals("Bundle int array has correct length", 2, intArray.length);
+        fAssertEquals("Bundle int array index 0 has correct value", 2, intArray[0]);
+        fAssertEquals("Bundle int array index 1 has correct value", 3, intArray[1]);
+
+        final double[] doubleArray = bundle.getDoubleArray("doubleArray");
+        fAssertNotNull("Bundle double array should exist", doubleArray);
+        fAssertEquals("Bundle double array has correct length", 2, doubleArray.length);
+        fAssertEquals("Bundle double array index 0 has correct value", 1.5, doubleArray[0]);
+        fAssertEquals("Bundle double array index 1 has correct value", 2.5, doubleArray[1]);
+
+        final String[] stringArray = bundle.getStringArray("stringArray");
+        fAssertNotNull("Bundle string array should exist", stringArray);
+        fAssertEquals("Bundle string array has correct length", 2, stringArray.length);
+        fAssertEquals("Bundle string array index 0 has correct value", "bar", stringArray[0]);
+        fAssertEquals("Bundle string array index 1 has correct value", "baz", stringArray[1]);
     }
 
     private void checkJSONObject(final JSONObject object) throws JSONException {
         fAssertEquals("JSON boolean has correct value", true, object.getBoolean("boolean"));
         fAssertEquals("JSON int has correct value", 1, object.getInt("int"));
         fAssertEquals("JSON double has correct value", 0.5, object.getDouble("double"));
         fAssertEquals("JSON string has correct value", "foo", object.getString("string"));
+
+        final JSONArray booleanArray = object.getJSONArray("booleanArray");
+        fAssertNotNull("JSON boolean array should exist", booleanArray);
+        fAssertEquals("JSON boolean array has correct length", 2, booleanArray.length());
+        fAssertEquals("JSON boolean array index 0 has correct value",
+            false, booleanArray.getBoolean(0));
+        fAssertEquals("JSON boolean array index 1 has correct value",
+            true, booleanArray.getBoolean(1));
+
+        final JSONArray intArray = object.getJSONArray("intArray");
+        fAssertNotNull("JSON int array should exist", intArray);
+        fAssertEquals("JSON int array has correct length", 2, intArray.length());
+        fAssertEquals("JSON int array index 0 has correct value",
+            2, intArray.getInt(0));
+        fAssertEquals("JSON int array index 1 has correct value",
+            3, intArray.getInt(1));
+
+        final JSONArray doubleArray = object.getJSONArray("doubleArray");
+        fAssertNotNull("JSON double array should exist", doubleArray);
+        fAssertEquals("JSON double array has correct length", 2, doubleArray.length());
+        fAssertEquals("JSON double array index 0 has correct value",
+            1.5, doubleArray.getDouble(0));
+        fAssertEquals("JSON double array index 1 has correct value",
+            2.5, doubleArray.getDouble(1));
+
+        final JSONArray stringArray = object.getJSONArray("stringArray");
+        fAssertNotNull("JSON string array should exist", stringArray);
+        fAssertEquals("JSON string array has correct length", 2, stringArray.length());
+        fAssertEquals("JSON string array index 0 has correct value",
+            "bar", stringArray.getString(0));
+        fAssertEquals("JSON string array index 1 has correct value",
+            "baz", stringArray.getString(1));
     }
 
     private void checkNativeJSObject(final NativeJSObject object) {
         fAssertEquals("Native boolean has correct value",
                 true, object.getBoolean("boolean"));
         fAssertEquals("optBoolean returns existent value",
                 true, object.optBoolean("boolean", false));
         fAssertEquals("optBoolean returns fallback value if nonexistent",
                 false, object.optBoolean("nonexistent_boolean", false));
+
         fAssertEquals("Native int has correct value",
                 1, object.getInt("int"));
         fAssertEquals("optInt returns existent value",
                 1, object.optInt("int", 0));
         fAssertEquals("optInt returns fallback value if nonexistent",
                 0, object.optInt("nonexistent_int", 0));
+
         fAssertEquals("Native double has correct value",
                 0.5, object.getDouble("double"));
         fAssertEquals("optDouble returns existent value",
                 0.5, object.optDouble("double", -0.5));
         fAssertEquals("optDouble returns fallback value if nonexistent",
                 -0.5, object.optDouble("nonexistent_double", -0.5));
+
         fAssertEquals("Native string has correct value",
                 "foo", object.getString("string"));
         fAssertEquals("optDouble returns existent value",
                 "foo", object.optString("string", "bar"));
         fAssertEquals("optDouble returns fallback value if nonexistent",
                 "bar", object.optString("nonexistent_string", "bar"));
+
+        final boolean[] booleanArray = object.getBooleanArray("booleanArray");
+        fAssertNotNull("Native boolean array should exist", booleanArray);
+        fAssertEquals("Native boolean array has correct length", 2, booleanArray.length);
+        fAssertEquals("Native boolean array index 0 has correct value", false, booleanArray[0]);
+        fAssertEquals("Native boolean array index 1 has correct value", true, booleanArray[1]);
+        fAssertNotSame("optBooleanArray returns existent value",
+            null, object.optBooleanArray("booleanArray", null));
+        fAssertSame("optBooleanArray returns fallback value if nonexistent",
+            null, object.optBooleanArray("nonexistent_booleanArray", null));
+
+        final int[] intArray = object.getIntArray("intArray");
+        fAssertNotNull("Native int array should exist", intArray);
+        fAssertEquals("Native int array has correct length", 2, intArray.length);
+        fAssertEquals("Native int array index 0 has correct value", 2, intArray[0]);
+        fAssertEquals("Native int array index 1 has correct value", 3, intArray[1]);
+        fAssertNotSame("optIntArray returns existent value",
+            null, object.optIntArray("intArray", null));
+        fAssertSame("optIntArray returns fallback value if nonexistent",
+            null, object.optIntArray("nonexistent_intArray", null));
+
+        final double[] doubleArray = object.getDoubleArray("doubleArray");
+        fAssertNotNull("Native double array should exist", doubleArray);
+        fAssertEquals("Native double array has correct length", 2, doubleArray.length);
+        fAssertEquals("Native double array index 0 has correct value", 1.5, doubleArray[0]);
+        fAssertEquals("Native double array index 1 has correct value", 2.5, doubleArray[1]);
+        fAssertNotSame("optDoubleArray returns existent value",
+            null, object.optDoubleArray("doubleArray", null));
+        fAssertSame("optDoubleArray returns fallback value if nonexistent",
+            null, object.optDoubleArray("nonexistent_doubleArray", null));
+
+        final String[] stringArray = object.getStringArray("stringArray");
+        fAssertNotNull("Native string array should exist", stringArray);
+        fAssertEquals("Native string array has correct length", 2, stringArray.length);
+        fAssertEquals("Native string array index 0 has correct value", "bar", stringArray[0]);
+        fAssertEquals("Native string array index 1 has correct value", "baz", stringArray[1]);
+        fAssertNotSame("optStringArray returns existent value",
+            null, object.optStringArray("stringArray", null));
+        fAssertSame("optStringArray returns fallback value if nonexistent",
+            null, object.optStringArray("nonexistent_stringArray", null));
     }
 }
--- a/mobile/android/base/tests/testEventDispatcher.js
+++ b/mobile/android/base/tests/testEventDispatcher.js
@@ -3,29 +3,35 @@ Components.utils.import("resource://gre/
 let java = new JavaBridge(this);
 
 do_register_cleanup(() => {
   java.disconnect();
 });
 do_test_pending();
 
 function send_test_message(type) {
-  sendMessageToJava({
-    type: type,
+  let innerObject = {
     boolean: true,
+    booleanArray: [false, true],
     int: 1,
+    intArray: [2, 3],
     double: 0.5,
+    doubleArray: [1.5, 2.5],
     string: "foo",
-    object: {
-      boolean: true,
-      int: 1,
-      double: 0.5,
-      string: "foo",
-    },
-  });
+    stringArray: ["bar", "baz"],
+  }
+
+  // Make a copy
+  let outerObject = JSON.parse(JSON.stringify(innerObject));
+
+  outerObject.type = type;
+  outerObject.object = innerObject;
+  outerObject.objectArray = [null, innerObject];
+
+  sendMessageToJava(outerObject);
 }
 
 function send_message_for_response(type, response) {
   sendMessageToJava({
     type: type,
     response: response,
   }, (success, error) => {
     if (response === "success") {
--- a/mobile/android/base/util/NativeJSObject.java
+++ b/mobile/android/base/util/NativeJSObject.java
@@ -59,16 +59,50 @@ public class NativeJSObject
      * @throws IllegalThreadStateException
      *         If not called on the thread this object is attached to
      * @throws UnsupportedOperationException
      *         If an internal JSAPI call failed
      */
     public native boolean optBoolean(String name, boolean fallback);
 
     /**
+     * Returns the value of a boolean array property.
+     *
+     * @param name
+     *        Property name
+     * @throws IllegalArgumentException
+     *         If the property does not exist or if its type does not match the return type
+     * @throws NullPointerException
+     *         If name is null or if this JS object has been disposed
+     * @throws IllegalThreadStateException
+     *         If not called on the thread this object is attached to
+     * @throws UnsupportedOperationException
+     *         If an internal JSAPI call failed
+     */
+    public native boolean[] getBooleanArray(String name);
+
+    /**
+     * Returns the value of a boolean array property.
+     *
+     * @param name
+     *        Property name
+     * @param fallback
+     *        Value to return if property does not exist
+     * @throws IllegalArgumentException
+     *         If the property exists and its type does not match the return type
+     * @throws NullPointerException
+     *         If name is null or if this JS object has been disposed
+     * @throws IllegalThreadStateException
+     *         If not called on the thread this object is attached to
+     * @throws UnsupportedOperationException
+     *         If an internal JSAPI call failed
+     */
+    public native boolean[] optBooleanArray(String name, boolean[] fallback);
+
+    /**
      * Returns the value of an object property as a Bundle.
      *
      * @param name
      *        Property name
      * @throws IllegalArgumentException
      *         If the property does not exist or if its type does not match the return type
      * @throws NullPointerException
      *         If name is null or if this JS object has been disposed
@@ -93,16 +127,50 @@ public class NativeJSObject
      * @throws IllegalThreadStateException
      *         If not called on the thread this object is attached to
      * @throws UnsupportedOperationException
      *         If an internal JSAPI call failed
      */
     public native Bundle optBundle(String name, Bundle fallback);
 
     /**
+     * Returns the value of an object array property as a Bundle array.
+     *
+     * @param name
+     *        Property name
+     * @throws IllegalArgumentException
+     *         If the property does not exist or if its type does not match the return type
+     * @throws NullPointerException
+     *         If name is null or if this JS object has been disposed
+     * @throws IllegalThreadStateException
+     *         If not called on the thread this object is attached to
+     * @throws UnsupportedOperationException
+     *         If an internal JSAPI call failed
+     */
+    public native Bundle[] getBundleArray(String name);
+
+    /**
+     * Returns the value of an object array property as a Bundle array.
+     *
+     * @param name
+     *        Property name
+     * @param fallback
+     *        Value to return if property does not exist
+     * @throws IllegalArgumentException
+     *         If the property exists and its type does not match the return type
+     * @throws NullPointerException
+     *         If name is null or if this JS object has been disposed
+     * @throws IllegalThreadStateException
+     *         If not called on the thread this object is attached to
+     * @throws UnsupportedOperationException
+     *         If an internal JSAPI call failed
+     */
+    public native Bundle[] optBundleArray(String name, Bundle[] fallback);
+
+    /**
      * Returns the value of a double property.
      *
      * @param name
      *        Property name
      * @throws IllegalArgumentException
      *         If the property does not exist or if its type does not match the return type
      * @throws NullPointerException
      *         If name is null or if this JS object has been disposed
@@ -127,16 +195,50 @@ public class NativeJSObject
      * @throws IllegalThreadStateException
      *         If not called on the thread this object is attached to
      * @throws UnsupportedOperationException
      *         If an internal JSAPI call failed
      */
     public native double optDouble(String name, double fallback);
 
     /**
+     * Returns the value of a double array property.
+     *
+     * @param name
+     *        Property name
+     * @throws IllegalArgumentException
+     *         If the property does not exist or if its type does not match the return type
+     * @throws NullPointerException
+     *         If name is null or if this JS object has been disposed
+     * @throws IllegalThreadStateException
+     *         If not called on the thread this object is attached to
+     * @throws UnsupportedOperationException
+     *         If an internal JSAPI call failed
+     */
+    public native double[] getDoubleArray(String name);
+
+    /**
+     * Returns the value of a double array property.
+     *
+     * @param name
+     *        Property name
+     * @param fallback
+     *        Value to return if property does not exist
+     * @throws IllegalArgumentException
+     *         If the property exists and its type does not match the return type
+     * @throws NullPointerException
+     *         If name is null or if this JS object has been disposed
+     * @throws IllegalThreadStateException
+     *         If not called on the thread this object is attached to
+     * @throws UnsupportedOperationException
+     *         If an internal JSAPI call failed
+     */
+    public native double[] optDoubleArray(String name, double[] fallback);
+
+    /**
      * Returns the value of an int property.
      *
      * @param name
      *        Property name
      * @throws IllegalArgumentException
      *         If the property does not exist or if its type does not match the return type
      * @throws NullPointerException
      *         If name is null or if this JS object has been disposed
@@ -161,16 +263,50 @@ public class NativeJSObject
      * @throws IllegalThreadStateException
      *         If not called on the thread this object is attached to
      * @throws UnsupportedOperationException
      *         If an internal JSAPI call failed
      */
     public native int optInt(String name, int fallback);
 
     /**
+     * Returns the value of an int array property.
+     *
+     * @param name
+     *        Property name
+     * @throws IllegalArgumentException
+     *         If the property does not exist or if its type does not match the return type
+     * @throws NullPointerException
+     *         If name is null or if this JS object has been disposed
+     * @throws IllegalThreadStateException
+     *         If not called on the thread this object is attached to
+     * @throws UnsupportedOperationException
+     *         If an internal JSAPI call failed
+     */
+    public native int[] getIntArray(String name);
+
+    /**
+     * Returns the value of an int array property.
+     *
+     * @param name
+     *        Property name
+     * @param fallback
+     *        Value to return if property does not exist
+     * @throws IllegalArgumentException
+     *         If the property exists and its type does not match the return type
+     * @throws NullPointerException
+     *         If name is null or if this JS object has been disposed
+     * @throws IllegalThreadStateException
+     *         If not called on the thread this object is attached to
+     * @throws UnsupportedOperationException
+     *         If an internal JSAPI call failed
+     */
+    public native int[] optIntArray(String name, int[] fallback);
+
+    /**
      * Returns the value of an object property.
      *
      * @param name
      *        Property name
      * @throws IllegalArgumentException
      *         If the property does not exist or if its type does not match the return type
      * @throws NullPointerException
      *         If name is null or if this JS object has been disposed
@@ -195,16 +331,50 @@ public class NativeJSObject
      * @throws IllegalThreadStateException
      *         If not called on the thread this object is attached to
      * @throws UnsupportedOperationException
      *         If an internal JSAPI call failed
      */
     public native NativeJSObject optObject(String name, NativeJSObject fallback);
 
     /**
+     * Returns the value of an object array property.
+     *
+     * @param name
+     *        Property name
+     * @throws IllegalArgumentException
+     *         If the property does not exist or if its type does not match the return type
+     * @throws NullPointerException
+     *         If name is null or if this JS object has been disposed
+     * @throws IllegalThreadStateException
+     *         If not called on the thread this object is attached to
+     * @throws UnsupportedOperationException
+     *         If an internal JSAPI call failed
+     */
+    public native NativeJSObject[] getObjectArray(String name);
+
+    /**
+     * Returns the value of an object array property.
+     *
+     * @param name