Merge inbound to m-c a=merge
authorWes Kocher <wkocher@mozilla.com>
Fri, 14 Aug 2015 16:31:17 -0700
changeset 257898 1c99f0dbfcf80bf70f7a4a477666457a70035211
parent 257887 6bb8ca8a857c6ba3e6e642e166de126b9c413200 (current diff)
parent 257897 e8ae270ae0d5abf285d55b35fe8ea8d3bb255e4e (diff)
child 257899 1e3f628235f74732a08e9db11c2e9af28b1e0337
push id29233
push userkwierso@gmail.com
push dateFri, 14 Aug 2015 23:32:11 +0000
treeherdermozilla-central@45bea43ad812 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmerge
milestone43.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Merge inbound to m-c a=merge
--- a/dom/base/Element.cpp
+++ b/dom/base/Element.cpp
@@ -1387,16 +1387,31 @@ nsresult
 Element::GetElementsByClassName(const nsAString& aClassNames,
                                 nsIDOMHTMLCollection** aResult)
 {
   *aResult =
     nsContentUtils::GetElementsByClassName(this, aClassNames).take();
   return NS_OK;
 }
 
+/**
+ * Returns the count of descendants (inclusive of aContent) in
+ * the uncomposed document that are explicitly set as editable.
+ */
+static uint32_t
+EditableInclusiveDescendantCount(nsIContent* aContent)
+{
+  auto htmlElem = nsGenericHTMLElement::FromContent(aContent);
+  if (htmlElem) {
+    return htmlElem->EditableInclusiveDescendantCount();
+  }
+
+  return aContent->EditableDescendantCount();
+}
+
 nsresult
 Element::BindToTree(nsIDocument* aDocument, nsIContent* aParent,
                     nsIContent* aBindingParent,
                     bool aCompileEventHandlers)
 {
   NS_PRECONDITION(aParent || aDocument, "Must have document if no parent!");
   NS_PRECONDITION((NODE_FROM(aParent, aDocument)->OwnerDoc() == OwnerDoc()),
                   "Must have the same owner document");
@@ -1459,16 +1474,18 @@ Element::BindToTree(nsIDocument* aDocume
     ShadowRoot* parentContainingShadow = aParent->GetContainingShadow();
     if (parentContainingShadow) {
       DOMSlots()->mContainingShadow = parentContainingShadow;
     }
   }
 
   bool hadForceXBL = HasFlag(NODE_FORCE_XBL_BINDINGS);
 
+  bool hadParent = !!GetParentNode();
+
   // Now set the parent and set the "Force attach xbl" flag if needed.
   if (aParent) {
     if (!GetParent()) {
       NS_ADDREF(aParent);
     }
     mParent = aParent;
 
     if (aParent->HasFlag(NODE_FORCE_XBL_BINDINGS)) {
@@ -1550,44 +1567,70 @@ Element::BindToTree(nsIDocument* aDocume
 
   // This has to be here, rather than in nsGenericHTMLElement::BindToTree,
   //  because it has to happen after updating the parent pointer, but before
   //  recursively binding the kids.
   if (IsHTMLElement()) {
     SetDirOnBind(this, aParent);
   }
 
+  uint32_t editableDescendantCount = 0;
+
   // If NODE_FORCE_XBL_BINDINGS was set we might have anonymous children
   // that also need to be told that they are moving.
   nsresult rv;
   if (hadForceXBL) {
     nsBindingManager* bmgr = OwnerDoc()->BindingManager();
 
     nsXBLBinding* contBinding = bmgr->GetBindingWithContent(this);
     // First check if we have a binding...
     if (contBinding) {
       nsCOMPtr<nsIContent> anonRoot = contBinding->GetAnonymousContent();
       bool allowScripts = contBinding->AllowScripts();
       for (nsCOMPtr<nsIContent> child = anonRoot->GetFirstChild();
            child;
            child = child->GetNextSibling()) {
         rv = child->BindToTree(aDocument, this, this, allowScripts);
         NS_ENSURE_SUCCESS(rv, rv);
+
+        editableDescendantCount += EditableInclusiveDescendantCount(child);
       }
     }
   }
 
   UpdateEditableState(false);
 
   // Now recurse into our kids
   for (nsIContent* child = GetFirstChild(); child;
        child = child->GetNextSibling()) {
     rv = child->BindToTree(aDocument, this, aBindingParent,
                            aCompileEventHandlers);
     NS_ENSURE_SUCCESS(rv, rv);
+
+    editableDescendantCount += EditableInclusiveDescendantCount(child);
+  }
+
+  if (aDocument) {
+    // Update our editable descendant count because we don't keep track of it
+    // for content that is not in the uncomposed document.
+    MOZ_ASSERT(EditableDescendantCount() == 0);
+    ChangeEditableDescendantCount(editableDescendantCount);
+
+    if (!hadParent) {
+      uint32_t editableDescendantChange = EditableInclusiveDescendantCount(this);
+      if (editableDescendantChange != 0) {
+      // If we are binding a subtree root to the document, we need to update
+      // the editable descendant count of all the ancestors.
+        nsIContent* parent = GetParent();
+        while (parent) {
+          parent->ChangeEditableDescendantCount(editableDescendantChange);
+          parent = parent->GetParent();
+        }
+      }
+    }
   }
 
   nsNodeUtils::ParentChainChanged(this);
 
   if (HasID()) {
     AddToIdTable(DoGetID());
   }
 
@@ -1681,27 +1724,45 @@ Element::UnbindFromTree(bool aDeep, bool
                                       nsContentUtils::eDOM_PROPERTIES,
                                       "RemovedFullScreenElement");
       // Fully exit full-screen.
       nsIDocument::ExitFullscreenInDocTree(OwnerDoc());
     }
     if (HasPointerLock()) {
       nsIDocument::UnlockPointer();
     }
+
+    if (GetParent() && GetParent()->IsInUncomposedDoc()) {
+      // Update the editable descendant count in the ancestors before we
+      // lose the reference to the parent.
+      int32_t editableDescendantChange = -1 * EditableInclusiveDescendantCount(this);
+      if (editableDescendantChange != 0) {
+        nsIContent* parent = GetParent();
+        while (parent) {
+          parent->ChangeEditableDescendantCount(editableDescendantChange);
+          parent = parent->GetParent();
+        }
+      }
+    }
+
     if (GetParent()) {
       nsINode* p = mParent;
       mParent = nullptr;
       NS_RELEASE(p);
     } else {
       mParent = nullptr;
     }
     SetParentIsContent(false);
   }
   ClearInDocument();
 
+  // Editable descendant count only counts descendants that
+  // are in the uncomposed document.
+  ResetEditableDescendantCount();
+
   if (aNullParent || !mParent->IsInShadowTree()) {
     UnsetFlags(NODE_IS_IN_SHADOW_TREE);
 
     // Begin keeping track of our subtree root.
     SetSubtreeRootPointer(aNullParent ? this : mParent->SubtreeRoot());
   }
 
   if (document) {
--- a/dom/base/nsDocument.cpp
+++ b/dom/base/nsDocument.cpp
@@ -9002,17 +9002,17 @@ nsDocument::UnblockOnload(bool aFireSync
         PostUnblockOnloadEvent();
       }
     } else if (mIsBeingUsedAsImage) {
       // To correctly unblock onload for a document that contains an SVG
       // image, we need to know when all of the SVG document's resources are
       // done loading, in a way comparable to |window.onload|. We fire this
       // event to indicate that the SVG should be considered fully loaded.
       // Because scripting is disabled on SVG-as-image documents, this event
-      // is not accessible to content authors. (See bug 837135.)
+      // is not accessible to content authors. (See bug 837315.)
       nsRefPtr<AsyncEventDispatcher> asyncDispatcher =
         new AsyncEventDispatcher(this,
                                  NS_LITERAL_STRING("MozSVGAsImageDocumentLoad"),
                                  false,
                                  false);
       asyncDispatcher->PostDOMEvent();
     }
   }
--- a/dom/base/nsINode.cpp
+++ b/dom/base/nsINode.cpp
@@ -105,17 +105,18 @@
 #include "GeometryUtils.h"
 #include "nsIAnimationObserver.h"
 #include "nsChildContentList.h"
 
 using namespace mozilla;
 using namespace mozilla::dom;
 
 nsINode::nsSlots::nsSlots()
-  : mWeakReference(nullptr)
+  : mWeakReference(nullptr),
+    mEditableDescendantCount(0)
 {
 }
 
 nsINode::nsSlots::~nsSlots()
 {
   if (mChildNodes) {
     mChildNodes->DropReference();
   }
@@ -1340,16 +1341,48 @@ nsINode::GetOwnerGlobalForBindings()
 
 nsIGlobalObject*
 nsINode::GetOwnerGlobal() const
 {
   bool dummy;
   return OwnerDoc()->GetScriptHandlingObject(dummy);
 }
 
+void
+nsINode::ChangeEditableDescendantCount(int32_t aDelta)
+{
+  if (aDelta == 0) {
+    return;
+  }
+
+  nsSlots* s = Slots();
+  MOZ_ASSERT(aDelta > 0 ||
+             s->mEditableDescendantCount >= (uint32_t) (-1 * aDelta));
+  s->mEditableDescendantCount += aDelta;
+}
+
+void
+nsINode::ResetEditableDescendantCount()
+{
+  nsSlots* s = GetExistingSlots();
+  if (s) {
+    s->mEditableDescendantCount = 0;
+  }
+}
+
+uint32_t
+nsINode::EditableDescendantCount()
+{
+  nsSlots* s = GetExistingSlots();
+  if (s) {
+    return s->mEditableDescendantCount;
+  }
+  return 0;
+}
+
 bool
 nsINode::UnoptimizableCCNode() const
 {
   const uintptr_t problematicFlags = (NODE_IS_ANONYMOUS_ROOT |
                                       NODE_IS_IN_NATIVE_ANONYMOUS_SUBTREE |
                                       NODE_IS_NATIVE_ANONYMOUS_ROOT |
                                       NODE_MAY_BE_IN_BINDING_MNGR |
                                       NODE_IS_IN_SHADOW_TREE);
--- a/dom/base/nsINode.h
+++ b/dom/base/nsINode.h
@@ -1049,16 +1049,22 @@ public:
      */
     nsRefPtr<nsChildContentList> mChildNodes;
 
     /**
      * Weak reference to this node.  This is cleared by the destructor of
      * nsNodeWeakReference.
      */
     nsNodeWeakReference* MOZ_NON_OWNING_REF mWeakReference;
+
+    /**
+     * Number of descendant nodes in the uncomposed document that have been
+     * explicitly set as editable.
+     */
+    uint32_t mEditableDescendantCount;
   };
 
   /**
    * Functions for managing flags and slots
    */
 #ifdef DEBUG
   nsSlots* DebugGetSlots()
   {
@@ -1084,16 +1090,32 @@ public:
     NS_ASSERTION(!(aFlagsToUnset &
                    (NODE_IS_ANONYMOUS_ROOT |
                     NODE_IS_IN_NATIVE_ANONYMOUS_SUBTREE |
                     NODE_IS_NATIVE_ANONYMOUS_ROOT)),
                  "Trying to unset write-only flags");
     nsWrapperCache::UnsetFlags(aFlagsToUnset);
   }
 
+  void ChangeEditableDescendantCount(int32_t aDelta);
+
+  /**
+   * Returns the count of descendant nodes in the uncomposed
+   * document that are explicitly set as editable.
+   */
+  uint32_t EditableDescendantCount();
+
+  /**
+   * Sets the editable descendant count to 0. The editable
+   * descendant count only counts explicitly editable nodes
+   * that are in the uncomposed document so this method
+   * should be called when nodes are are removed from it.
+   */
+  void ResetEditableDescendantCount();
+
   void SetEditableFlag(bool aEditable)
   {
     if (aEditable) {
       SetFlags(NODE_IS_EDITABLE);
     }
     else {
       UnsetFlags(NODE_IS_EDITABLE);
     }
--- a/dom/html/nsGenericHTMLElement.cpp
+++ b/dom/html/nsGenericHTMLElement.cpp
@@ -520,16 +520,24 @@ nsGenericHTMLElement::IntrinsicState() c
                  "HTML element's directionality must be either RTL or LTR");
     state |= NS_EVENT_STATE_LTR;
     state &= ~NS_EVENT_STATE_RTL;
   }
 
   return state;
 }
 
+uint32_t
+nsGenericHTMLElement::EditableInclusiveDescendantCount()
+{
+  bool isEditable = IsInUncomposedDoc() && HasFlag(NODE_IS_EDITABLE) &&
+    GetContentEditableValue() == eTrue;
+  return EditableDescendantCount() + isEditable;
+}
+
 nsresult
 nsGenericHTMLElement::BindToTree(nsIDocument* aDocument, nsIContent* aParent,
                                  nsIContent* aBindingParent,
                                  bool aCompileEventHandlers)
 {
   nsresult rv = nsGenericHTMLElementBase::BindToTree(aDocument, aParent,
                                                      aBindingParent,
                                                      aCompileEventHandlers);
@@ -543,16 +551,17 @@ nsGenericHTMLElement::BindToTree(nsIDocu
         properties->SetDocument(aDocument);
       }
     }
     RegAccessKey();
     if (HasName()) {
       aDocument->
         AddToNameTable(this, GetParsedAttr(nsGkAtoms::name)->GetAtomValue());
     }
+
     if (HasFlag(NODE_IS_EDITABLE) && GetContentEditableValue() == eTrue) {
       nsCOMPtr<nsIHTMLDocument> htmlDocument = do_QueryInterface(aDocument);
       if (htmlDocument) {
         htmlDocument->ChangeContentEditableCount(this, +1);
       }
     }
   }
 
@@ -2912,16 +2921,22 @@ nsGenericHTMLElement::ChangeEditableStat
   }
 
   if (aChange != 0) {
     nsCOMPtr<nsIHTMLDocument> htmlDocument =
       do_QueryInterface(document);
     if (htmlDocument) {
       htmlDocument->ChangeContentEditableCount(this, aChange);
     }
+
+    nsIContent* parent = GetParent();
+    while (parent) {
+      parent->ChangeEditableDescendantCount(aChange);
+      parent = parent->GetParent();
+    }
   }
 
   if (document->HasFlag(NODE_IS_EDITABLE)) {
     document = nullptr;
   }
 
   // MakeContentDescendantsEditable is going to call ContentStateChanged for
   // this element and all descendants if editable state has changed.
--- a/dom/html/nsGenericHTMLElement.h
+++ b/dom/html/nsGenericHTMLElement.h
@@ -210,16 +210,23 @@ public:
         ContentEditableTristate value = element->GetContentEditableValue();
         if (value != eInherit) {
           return value == eTrue;
         }
       }
     }
     return false;
   }
+
+  /**
+   * Returns the count of descendants (inclusive of this node) in
+   * the uncomposed document that are explicitly set as editable.
+   */
+  uint32_t EditableInclusiveDescendantCount();
+
   mozilla::dom::HTMLMenuElement* GetContextMenu() const;
   bool Spellcheck();
   void SetSpellcheck(bool aSpellcheck, mozilla::ErrorResult& aError)
   {
     SetHTMLAttr(nsGkAtoms::spellcheck,
                 aSpellcheck ? NS_LITERAL_STRING("true")
                             : NS_LITERAL_STRING("false"),
                 aError);
--- a/dom/requestsync/RequestSyncService.jsm
+++ b/dom/requestsync/RequestSyncService.jsm
@@ -42,16 +42,19 @@ XPCOMUtils.defineLazyServiceGetter(this,
 XPCOMUtils.defineLazyServiceGetter(this, "systemMessenger",
                                    "@mozilla.org/system-message-internal;1",
                                    "nsISystemMessagesInternal");
 
 XPCOMUtils.defineLazyServiceGetter(this, "secMan",
                                    "@mozilla.org/scriptsecuritymanager;1",
                                    "nsIScriptSecurityManager");
 
+XPCOMUtils.defineLazyModuleGetter(this, "AlarmService",
+                                  "resource://gre/modules/AlarmService.jsm");
+
 this.RequestSyncService = {
   __proto__: IndexedDBHelper.prototype,
 
   children: [],
 
   _messages: [ "RequestSync:Register",
                "RequestSync:Unregister",
                "RequestSync:Registrations",
@@ -858,36 +861,35 @@ this.RequestSyncService = {
         }
 
         self.scheduleTimer(aObj);
       }
     });
   },
 
   createTimer: function(aObj) {
-    this._timers[aObj.dbKey] = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
-
     let interval = aObj.data.minInterval;
     if (aObj.data.overwrittenMinInterval > 0) {
       interval = aObj.data.overwrittenMinInterval;
     }
 
-    let self = this;
-    this._timers[aObj.dbKey].initWithCallback(function() { self.timeout(aObj); },
-                                              interval * 1000,
-                                              Ci.nsITimer.TYPE_ONE_SHOT);
+    AlarmService.add(
+      { date: new Date(Date.now() + interval * 1000),
+        ignoreTimezone: false },
+      () => this.timeout(aObj),
+      aTimerId => this._timers[aObj.dbKey] = aTimerId);
   },
 
   hasTimer: function(aObj) {
     return (aObj.dbKey in this._timers);
   },
 
   removeTimer: function(aObj) {
     if (aObj.dbKey in this._timers) {
-      this._timers[aObj.dbKey].cancel();
+      AlarmService.remove(this._timers[aObj.dbKey]);
       delete this._timers[aObj.dbKey];
     }
   },
 
   storePendingRequest: function(aObj, aTarget, aRequestID) {
     if (!(aObj.dbKey in this._pendingRequests)) {
       this._pendingRequests[aObj.dbKey] = [];
     }
--- a/dom/requestsync/tests/mochitest.ini
+++ b/dom/requestsync/tests/mochitest.ini
@@ -4,18 +4,23 @@ support-files =
   file_app.template.webapp
   file_app.sjs
   file_basic_app.html
   common_app.js
   common_basic.js
   system_message_chrome_script.js
 
 [test_webidl.html]
+skip-if = os == "android" || toolkit == "gonk"
 [test_minInterval.html]
+skip-if = os == "android" || toolkit == "gonk"
 [test_basic.html]
+skip-if = os == "android" || toolkit == "gonk"
 [test_basic_app.html]
-skip-if = buildapp == 'b2g'
+skip-if = os == "android" || buildapp == 'b2g'
 [test_wakeUp.html]
-skip-if = !(buildapp == 'b2g' && toolkit == 'gonk')
+run-if = buildapp == 'b2g' && toolkit == 'gonk'
 [test_runNow.html]
 run-if = buildapp == 'b2g' && toolkit == 'gonk'
 [test_promise.html]
+skip-if = os == "android" || toolkit == "gonk"
 [test_bug1151082.html]
+skip-if = os == "android" || toolkit == "gonk"
--- a/editor/libeditor/tests/mochitest.ini
+++ b/editor/libeditor/tests/mochitest.ini
@@ -160,8 +160,10 @@ skip-if = toolkit == 'android' # bug 105
 [test_select_all_without_body.html]
 skip-if = e10s
 [test_spellcheck_pref.html]
 skip-if = toolkit == 'android'
 [test_bug1068979.html]
 [test_bug1109465.html]
 [test_bug1162952.html]
 [test_bug1186799.html]
+[test_bug1181130-1.html]
+[test_bug1181130-2.html]
new file mode 100644
--- /dev/null
+++ b/editor/libeditor/tests/test_bug1181130-1.html
@@ -0,0 +1,50 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1181130
+-->
+<head>
+  <meta charset="utf-8">
+  <title>Test for Bug 1181130</title>
+  <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <script type="application/javascript" src="/tests/SimpleTest/EventUtils.js"></script>
+  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1181130">Mozilla Bug 1181130</a>
+<p id="display"></p>
+<div id="container" contenteditable="true">
+  editable div
+  <div id="noneditable" contenteditable="false">
+    non-editable div
+    <div id="editable" contenteditable="true">nested editable div</div>
+  </div>
+</div>
+<script type="application/javascript">
+/** Test for Bug 1181130 **/
+var container = document.getElementById("container");
+var noneditable = document.getElementById("noneditable");
+var editable = document.getElementById("editable");
+
+SimpleTest.waitForExplicitFinish();
+
+SimpleTest.waitForFocus(function() {
+  synthesizeMouseAtCenter(noneditable, {});
+  ok(!document.getSelection().toString().includes("nested editable div"),
+     "Selection should not include non-editable content");
+
+  synthesizeMouseAtCenter(container, {});
+  ok(!document.getSelection().toString().includes("nested editable div"),
+     "Selection should not include non-editable content");
+
+  synthesizeMouseAtCenter(editable, {});
+  ok(!document.getSelection().toString().includes("nested editable div"),
+     "Selection should not include non-editable content");
+
+  SimpleTest.finish();
+});
+</script>
+<pre id="test">
+</pre>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/editor/libeditor/tests/test_bug1181130-2.html
@@ -0,0 +1,44 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1181130
+-->
+<head>
+  <meta charset="utf-8">
+  <title>Test for Bug 1181130</title>
+  <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <script type="application/javascript" src="/tests/SimpleTest/EventUtils.js"></script>
+  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1181130">Mozilla Bug 1181130</a>
+<p id="display"></p>
+<div id="container" contenteditable="true">
+  editable div
+  <div id="noneditable" contenteditable="false">
+    non-editable div
+  </div>
+</div>
+<script type="application/javascript">
+/** Test for Bug 1181130 **/
+var container = document.getElementById("container");
+var noneditable = document.getElementById("noneditable");
+
+SimpleTest.waitForExplicitFinish();
+
+SimpleTest.waitForFocus(function() {
+  var nonHTMLElement = document.createElementNS("http://www.example.com", "element");
+  nonHTMLElement.innerHTML = '<div contenteditable="true">nested editable div</div>';
+  noneditable.appendChild(nonHTMLElement);
+
+  synthesizeMouseAtCenter(noneditable, {});
+  ok(!document.getSelection().toString().includes("nested editable div"),
+     "Selection should not include non-editable content");
+
+  SimpleTest.finish();
+});
+</script>
+<pre id="test">
+</pre>
+</body>
+</html>
--- a/gfx/layers/apz/util/APZCCallbackHelper.cpp
+++ b/gfx/layers/apz/util/APZCCallbackHelper.cpp
@@ -387,18 +387,18 @@ APZCCallbackHelper::AcknowledgeScrollUpd
     nsCOMPtr<nsIRunnable> r1 = new AcknowledgeScrollUpdateEvent(aScrollId, aScrollGeneration);
     if (!NS_IsMainThread()) {
         NS_DispatchToMainThread(r1);
     } else {
         r1->Run();
     }
 }
 
-static nsIPresShell*
-GetRootContentDocumentPresShellForContent(nsIContent* aContent)
+nsIPresShell*
+APZCCallbackHelper::GetRootContentDocumentPresShellForContent(nsIContent* aContent)
 {
     nsIDocument* doc = aContent->GetComposedDoc();
     if (!doc) {
         return nullptr;
     }
     nsIPresShell* shell = doc->GetShell();
     if (!shell) {
         return nullptr;
--- a/gfx/layers/apz/util/APZCCallbackHelper.h
+++ b/gfx/layers/apz/util/APZCCallbackHelper.h
@@ -80,16 +80,19 @@ public:
     static void RequestFlingSnap(const FrameMetrics::ViewID& aScrollId,
                                  const mozilla::CSSPoint& aDestination);
 
     /* Tell layout that we received the scroll offset update for the given view ID, so
        that it accepts future scroll offset updates from APZ. */
     static void AcknowledgeScrollUpdate(const FrameMetrics::ViewID& aScrollId,
                                         const uint32_t& aScrollGeneration);
 
+    /* Get the pres shell associated with the root content document enclosing |aContent|. */
+    static nsIPresShell* GetRootContentDocumentPresShellForContent(nsIContent* aContent);
+
     /* Apply an "input transform" to the given |aInput| and return the transformed value.
        The input transform applied is the one for the content element corresponding to
        |aGuid|; this is populated in a previous call to UpdateCallbackTransform. See that
        method's documentations for details.
        This method additionally adjusts |aInput| by inversely scaling by the provided
        pres shell resolution, to cancel out a compositor-side transform (added in
        bug 1076241) that APZ doesn't unapply. */
     static CSSPoint ApplyCallbackTransform(const CSSPoint& aInput,
--- a/gfx/layers/apz/util/ChromeProcessController.cpp
+++ b/gfx/layers/apz/util/ChromeProcessController.cpp
@@ -6,40 +6,48 @@
 #include "ChromeProcessController.h"
 
 #include "MainThreadUtils.h"    // for NS_IsMainThread()
 #include "base/message_loop.h"  // for MessageLoop
 #include "mozilla/dom/Element.h"
 #include "mozilla/layers/CompositorParent.h"
 #include "mozilla/layers/APZCCallbackHelper.h"
 #include "mozilla/layers/APZEventState.h"
+#include "mozilla/layers/APZCTreeManager.h"
+#include "mozilla/layers/DoubleTapToZoom.h"
 #include "nsIDocument.h"
 #include "nsIInterfaceRequestorUtils.h"
 #include "nsIPresShell.h"
 #include "nsLayoutUtils.h"
 #include "nsView.h"
 
 using namespace mozilla;
 using namespace mozilla::layers;
 using namespace mozilla::widget;
 
 ChromeProcessController::ChromeProcessController(nsIWidget* aWidget,
-                                                 APZEventState* aAPZEventState)
+                                                 APZEventState* aAPZEventState,
+                                                 APZCTreeManager* aAPZCTreeManager)
   : mWidget(aWidget)
   , mAPZEventState(aAPZEventState)
+  , mAPZCTreeManager(aAPZCTreeManager)
   , mUILoop(MessageLoop::current())
 {
   // Otherwise we're initializing mUILoop incorrectly.
   MOZ_ASSERT(NS_IsMainThread());
+  MOZ_ASSERT(aAPZEventState);
+  MOZ_ASSERT(aAPZCTreeManager);
 
   mUILoop->PostTask(
       FROM_HERE,
       NewRunnableMethod(this, &ChromeProcessController::InitializeRoot));
 }
 
+ChromeProcessController::~ChromeProcessController() {}
+
 void
 ChromeProcessController::InitializeRoot()
 {
   APZCCallbackHelper::InitializeRootDisplayport(GetPresShell());
 }
 
 void
 ChromeProcessController::RequestContentRepaint(const FrameMetrics& aFrameMetrics)
@@ -93,35 +101,69 @@ ChromeProcessController::GetPresShell() 
 {
   if (nsView* view = nsView::GetViewFor(mWidget)) {
     return view->GetPresShell();
   }
   return nullptr;
 }
 
 nsIDocument*
-ChromeProcessController::GetDocument() const
+ChromeProcessController::GetRootDocument() const
 {
   if (nsIPresShell* presShell = GetPresShell()) {
     return presShell->GetDocument();
   }
   return nullptr;
 }
 
-already_AddRefed<nsIDOMWindowUtils>
-ChromeProcessController::GetDOMWindowUtils() const
+nsIDocument*
+ChromeProcessController::GetRootContentDocument(const FrameMetrics::ViewID& aScrollId) const
 {
-  if (nsIDocument* doc = GetDocument()) {
-    nsCOMPtr<nsIDOMWindowUtils> result = do_GetInterface(doc->GetWindow());
-    return result.forget();
+  nsIContent* content = nsLayoutUtils::FindContentFor(aScrollId);
+  if (!content) {
+    return nullptr;
+  }
+  nsIPresShell* presShell = APZCCallbackHelper::GetRootContentDocumentPresShellForContent(content);
+  if (presShell) {
+    return presShell->GetDocument();
   }
   return nullptr;
 }
 
 void
+ChromeProcessController::HandleDoubleTap(const mozilla::CSSPoint& aPoint,
+                                         Modifiers aModifiers,
+                                         const ScrollableLayerGuid& aGuid)
+{
+  if (MessageLoop::current() != mUILoop) {
+    mUILoop->PostTask(
+        FROM_HERE,
+        NewRunnableMethod(this, &ChromeProcessController::HandleDoubleTap,
+                          aPoint, aModifiers, aGuid));
+    return;
+  }
+
+  nsCOMPtr<nsIDocument> document = GetRootContentDocument(aGuid.mScrollId);
+  if (!document.get()) {
+    return;
+  }
+
+  CSSPoint point = APZCCallbackHelper::ApplyCallbackTransform(aPoint, aGuid);
+  CSSRect zoomToRect = CalculateRectToZoomTo(document, point);
+
+  uint32_t presShellId;
+  FrameMetrics::ViewID viewId;
+  if (APZCCallbackHelper::GetOrCreateScrollIdentifiers(
+      document->GetDocumentElement(), &presShellId, &viewId)) {
+    mAPZCTreeManager->ZoomToRect(
+      ScrollableLayerGuid(aGuid.mLayersId, presShellId, viewId), zoomToRect);
+  }
+}
+
+void
 ChromeProcessController::HandleSingleTap(const CSSPoint& aPoint,
                                          Modifiers aModifiers,
                                          const ScrollableLayerGuid& aGuid)
 {
   if (MessageLoop::current() != mUILoop) {
     mUILoop->PostTask(
         FROM_HERE,
         NewRunnableMethod(this, &ChromeProcessController::HandleSingleTap,
@@ -157,17 +199,17 @@ ChromeProcessController::NotifyAPZStateC
   if (MessageLoop::current() != mUILoop) {
     mUILoop->PostTask(
         FROM_HERE,
         NewRunnableMethod(this, &ChromeProcessController::NotifyAPZStateChange,
                           aGuid, aChange, aArg));
     return;
   }
 
-  mAPZEventState->ProcessAPZStateChange(GetDocument(), aGuid.mScrollId, aChange, aArg);
+  mAPZEventState->ProcessAPZStateChange(GetRootDocument(), aGuid.mScrollId, aChange, aArg);
 }
 
 void
 ChromeProcessController::NotifyMozMouseScrollEvent(const FrameMetrics::ViewID& aScrollId, const nsString& aEvent)
 {
   if (MessageLoop::current() != mUILoop) {
     mUILoop->PostTask(
       FROM_HERE,
--- a/gfx/layers/apz/util/ChromeProcessController.h
+++ b/gfx/layers/apz/util/ChromeProcessController.h
@@ -16,59 +16,62 @@ class nsIPresShell;
 class nsIWidget;
 
 class MessageLoop;
 
 namespace mozilla {
 
 namespace layers {
 
+class APZCTreeManager;
 class APZEventState;
 
 // A ChromeProcessController is attached to the root of a compositor's layer
 // tree.
 class ChromeProcessController : public mozilla::layers::GeckoContentController
 {
   typedef mozilla::layers::FrameMetrics FrameMetrics;
   typedef mozilla::layers::ScrollableLayerGuid ScrollableLayerGuid;
 
 public:
-  explicit ChromeProcessController(nsIWidget* aWidget, APZEventState* aAPZEventState);
+  explicit ChromeProcessController(nsIWidget* aWidget, APZEventState* aAPZEventState, APZCTreeManager* aAPZCTreeManager);
+  ~ChromeProcessController();
   virtual void Destroy() override;
 
   // GeckoContentController interface
   virtual void RequestContentRepaint(const FrameMetrics& aFrameMetrics) override;
   virtual void PostDelayedTask(Task* aTask, int aDelayMs) override;
   virtual void RequestFlingSnap(const FrameMetrics::ViewID& aScrollId,
                                 const mozilla::CSSPoint& aDestination) override;
   virtual void AcknowledgeScrollUpdate(const FrameMetrics::ViewID& aScrollId,
                                        const uint32_t& aScrollGeneration) override;
 
   virtual void HandleDoubleTap(const mozilla::CSSPoint& aPoint, Modifiers aModifiers,
-                               const ScrollableLayerGuid& aGuid) override {}
+                               const ScrollableLayerGuid& aGuid) override;
   virtual void HandleSingleTap(const mozilla::CSSPoint& aPoint, Modifiers aModifiers,
                                const ScrollableLayerGuid& aGuid) override;
   virtual void HandleLongTap(const mozilla::CSSPoint& aPoint, Modifiers aModifiers,
                                const ScrollableLayerGuid& aGuid,
                                uint64_t aInputBlockId) override;
   virtual void SendAsyncScrollDOMEvent(bool aIsRootContent, const mozilla::CSSRect &aContentRect,
                                        const mozilla::CSSSize &aScrollableSize) override {}
   virtual void NotifyAPZStateChange(const ScrollableLayerGuid& aGuid,
                                     APZStateChange aChange,
                                     int aArg) override;
   virtual void NotifyMozMouseScrollEvent(const FrameMetrics::ViewID& aScrollId,
                                          const nsString& aEvent) override;
   virtual void NotifyFlushComplete() override;
 private:
   nsCOMPtr<nsIWidget> mWidget;
   nsRefPtr<APZEventState> mAPZEventState;
+  nsRefPtr<APZCTreeManager> mAPZCTreeManager;
   MessageLoop* mUILoop;
 
   void InitializeRoot();
   nsIPresShell* GetPresShell() const;
-  nsIDocument* GetDocument() const;
-  already_AddRefed<nsIDOMWindowUtils> GetDOMWindowUtils() const;
+  nsIDocument* GetRootDocument() const;
+  nsIDocument* GetRootContentDocument(const FrameMetrics::ViewID& aScrollId) const;
 };
 
 } // namespace layers
 } // namespace mozilla
 
 #endif /* mozilla_layers_ChromeProcessController_h */
--- a/js/public/TracingAPI.h
+++ b/js/public/TracingAPI.h
@@ -22,51 +22,67 @@ template <typename T> class TenuredHeap;
 
 // Returns a static string equivalent of |kind|.
 JS_FRIEND_API(const char*)
 GCTraceKindToAscii(JS::TraceKind kind);
 
 } // namespace JS
 
 enum WeakMapTraceKind {
-    DoNotTraceWeakMaps = 0,
-    TraceWeakMapValues = 1,
-    TraceWeakMapKeysValues = 2
+    // Do true ephemeron marking with an iterative weak marking phase.
+    DoNotTraceWeakMaps,
+
+    // Do true ephemeron marking with a weak key lookup marking phase. This is
+    // expected to be constant for the lifetime of a JSTracer; it does not
+    // change when switching from "plain" marking to weak marking.
+    ExpandWeakMaps,
+
+    // Trace through to all values, irrespective of whether the keys are live
+    // or not. Used for non-marking tracers.
+    TraceWeakMapValues,
+
+    // Trace through to all keys and values, irrespective of whether the keys
+    // are live or not. Used for non-marking tracers.
+    TraceWeakMapKeysValues
 };
 
 class JS_PUBLIC_API(JSTracer)
 {
   public:
     // Return the runtime set on the tracer.
     JSRuntime* runtime() const { return runtime_; }
 
-    // Return the weak map tracing behavior set on this tracer.
-    WeakMapTraceKind eagerlyTraceWeakMaps() const { return eagerlyTraceWeakMaps_; }
+    // Return the weak map tracing behavior currently set on this tracer.
+    WeakMapTraceKind weakMapAction() const { return weakMapAction_; }
 
     // An intermediate state on the road from C to C++ style dispatch.
     enum class TracerKindTag {
         Marking,
+        WeakMarking, // In weak marking phase: looking up every marked obj/script.
         Tenuring,
         Callback
     };
-    bool isMarkingTracer() const { return tag_ == TracerKindTag::Marking; }
+    bool isMarkingTracer() const { return tag_ == TracerKindTag::Marking || tag_ == TracerKindTag::WeakMarking; }
+    bool isWeakMarkingTracer() const { return tag_ == TracerKindTag::WeakMarking; }
     bool isTenuringTracer() const { return tag_ == TracerKindTag::Tenuring; }
     bool isCallbackTracer() const { return tag_ == TracerKindTag::Callback; }
     inline JS::CallbackTracer* asCallbackTracer();
 
   protected:
     JSTracer(JSRuntime* rt, TracerKindTag tag,
              WeakMapTraceKind weakTraceKind = TraceWeakMapValues)
-      : runtime_(rt), tag_(tag), eagerlyTraceWeakMaps_(weakTraceKind)
+      : runtime_(rt), weakMapAction_(weakTraceKind), tag_(tag)
     {}
 
   private:
     JSRuntime*          runtime_;
+    WeakMapTraceKind    weakMapAction_;
+
+  protected:
     TracerKindTag       tag_;
-    WeakMapTraceKind    eagerlyTraceWeakMaps_;
 };
 
 namespace JS {
 
 class AutoTracingName;
 class AutoTracingIndex;
 class AutoTracingCallback;
 
--- a/js/src/devtools/rootAnalysis/annotations.js
+++ b/js/src/devtools/rootAnalysis/annotations.js
@@ -234,16 +234,20 @@ function ignoreGCFunction(mangled)
     // and rely on only the dynamic checks provided by AutoAssertCannotGC.
     if (isHeapSnapshotMockClass(fun) || isGTest(fun))
         return true;
 
     // Templatized function
     if (fun.indexOf("void nsCOMPtr<T>::Assert_NoQueryNeeded()") >= 0)
         return true;
 
+    // These call through an 'op' function pointer.
+    if (fun.indexOf("js::WeakMap<Key, Value, HashPolicy>::getDelegate(") >= 0)
+        return true;
+
     // XXX modify refillFreeList<NoGC> to not need data flow analysis to understand it cannot GC.
     if (/refillFreeList/.test(fun) && /\(js::AllowGC\)0u/.test(fun))
         return true;
     return false;
 }
 
 function stripUCSAndNamespace(name)
 {
--- a/js/src/ds/OrderedHashTable.h
+++ b/js/src/ds/OrderedHashTable.h
@@ -667,17 +667,18 @@ class OrderedHashMap
         void operator=(Entry&& rhs) {
             MOZ_ASSERT(this != &rhs, "self-move assignment is prohibited");
             const_cast<Key&>(key) = Move(rhs.key);
             value = Move(rhs.value);
         }
 
       public:
         Entry() : key(), value() {}
-        Entry(const Key& k, const Value& v) : key(k), value(v) {}
+        template <typename V>
+        Entry(const Key& k, V&& v) : key(k), value(Forward<V>(v)) {}
         Entry(Entry&& rhs) : key(Move(rhs.key)), value(Move(rhs.value)) {}
 
         const Key key;
         Value value;
     };
 
   private:
     struct MapOps : OrderedHashPolicy
@@ -702,17 +703,18 @@ class OrderedHashMap
 
     explicit OrderedHashMap(AllocPolicy ap = AllocPolicy()) : impl(ap) {}
     bool init()                                     { return impl.init(); }
     uint32_t count() const                          { return impl.count(); }
     bool has(const Key& key) const                  { return impl.has(key); }
     Range all()                                     { return impl.all(); }
     const Entry* get(const Key& key) const          { return impl.get(key); }
     Entry* get(const Key& key)                      { return impl.get(key); }
-    bool put(const Key& key, const Value& value)    { return impl.put(Entry(key, value)); }
+    template <typename V>
+    bool put(const Key& key, V&& value)             { return impl.put(Entry(key, Forward<V>(value))); }
     bool remove(const Key& key, bool* foundp)       { return impl.remove(key, foundp); }
     bool clear()                                    { return impl.clear(); }
 
     void rekeyOneEntry(const Key& current, const Key& newKey) {
         const Entry* e = get(current);
         if (!e)
             return;
         return impl.rekeyOneEntry(current, newKey, Entry(newKey, e->value));
--- a/js/src/gc/Marking.cpp
+++ b/js/src/gc/Marking.cpp
@@ -597,16 +597,84 @@ DispatchToTracer(JSTracer* trc, T* thing
         return static_cast<TenuringTracer*>(trc)->traverse(thingp);
     MOZ_ASSERT(trc->isCallbackTracer());
     DoCallback(trc->asCallbackTracer(), thingp, name);
 }
 
 
 /*** GC Marking Interface *************************************************************************/
 
+namespace js {
+
+typedef bool DoNothingMarkingType;
+
+template <typename T>
+struct LinearlyMarkedEphemeronKeyType {
+    typedef DoNothingMarkingType Type;
+};
+
+// For now, we only handle JSObject* keys, but the linear time algorithm can be
+// easily extended by adding in more types here, then making
+// GCMarker::traverse<T> call markPotentialEphemeronKey.
+template <>
+struct LinearlyMarkedEphemeronKeyType<JSObject*> {
+    typedef JSObject* Type;
+};
+
+template <>
+struct LinearlyMarkedEphemeronKeyType<JSScript*> {
+    typedef JSScript* Type;
+};
+
+void
+GCMarker::markEphemeronValues(gc::Cell* markedCell, WeakEntryVector& values)
+{
+    size_t initialLen = values.length();
+    for (size_t i = 0; i < initialLen; i++)
+        values[i].weakmap->maybeMarkEntry(this, markedCell, values[i].key);
+
+    // The vector should not be appended to during iteration because the key is
+    // already marked, and even in cases where we have a multipart key, we
+    // should only be inserting entries for the unmarked portions.
+    MOZ_ASSERT(values.length() == initialLen);
+}
+
+template <typename T>
+void
+GCMarker::markPotentialEphemeronKeyHelper(T markedThing)
+{
+    if (!isWeakMarkingTracer())
+        return;
+
+    MOZ_ASSERT(gc::TenuredCell::fromPointer(markedThing)->zone()->isGCMarking());
+    MOZ_ASSERT(!gc::TenuredCell::fromPointer(markedThing)->zone()->isGCSweeping());
+
+    auto weakValues = weakKeys.get(JS::GCCellPtr(markedThing));
+    if (!weakValues)
+        return;
+
+    markEphemeronValues(markedThing, weakValues->value);
+    weakValues->value.clear(); // If key address is reused, it should do nothing
+}
+
+template <>
+void
+GCMarker::markPotentialEphemeronKeyHelper(bool)
+{
+}
+
+template <typename T>
+void
+GCMarker::markPotentialEphemeronKey(T* thing)
+{
+    markPotentialEphemeronKeyHelper<typename LinearlyMarkedEphemeronKeyType<T*>::Type>(thing);
+}
+
+} // namespace js
+
 template <typename T>
 static inline bool
 MustSkipMarking(T thing)
 {
     // Don't mark things outside a zone if we are in a per-zone GC.
     return !thing->zone()->isGCMarking();
 }
 
@@ -695,17 +763,16 @@ js::GCMarker::markAndTraceChildren(T* th
     if (ThingIsPermanentAtomOrWellKnownSymbol(thing))
         return;
     if (mark(thing))
         thing->traceChildren(this);
 }
 namespace js {
 template <> void GCMarker::traverse(BaseShape* thing) { markAndTraceChildren(thing); }
 template <> void GCMarker::traverse(JS::Symbol* thing) { markAndTraceChildren(thing); }
-template <> void GCMarker::traverse(JSScript* thing) { markAndTraceChildren(thing); }
 } // namespace js
 
 // Shape, BaseShape, String, and Symbol are extremely common, but have simple
 // patterns of recursion. We traverse trees of these edges immediately, with
 // aggressive, manual inlining, implemented by eagerlyTraceChildren.
 template <typename T>
 void
 js::GCMarker::markAndScan(T* thing)
@@ -719,28 +786,32 @@ namespace js {
 template <> void GCMarker::traverse(JSString* thing) { markAndScan(thing); }
 template <> void GCMarker::traverse(LazyScript* thing) { markAndScan(thing); }
 template <> void GCMarker::traverse(Shape* thing) { markAndScan(thing); }
 } // namespace js
 
 // Object and ObjectGroup are extremely common and can contain arbitrarily
 // nested graphs, so are not trivially inlined. In this case we use a mark
 // stack to control recursion. JitCode shares none of these properties, but is
-// included for historical reasons.
+// included for historical reasons. JSScript normally cannot recurse, but may
+// be used as a weakmap key and thereby recurse into weakmapped values.
 template <typename T>
 void
 js::GCMarker::markAndPush(StackTag tag, T* thing)
 {
-    if (mark(thing))
-        pushTaggedPtr(tag, thing);
+    if (!mark(thing))
+        return;
+    pushTaggedPtr(tag, thing);
+    markPotentialEphemeronKey(thing);
 }
 namespace js {
 template <> void GCMarker::traverse(JSObject* thing) { markAndPush(ObjectTag, thing); }
 template <> void GCMarker::traverse(ObjectGroup* thing) { markAndPush(GroupTag, thing); }
 template <> void GCMarker::traverse(jit::JitCode* thing) { markAndPush(JitCodeTag, thing); }
+template <> void GCMarker::traverse(JSScript* thing) { markAndPush(ScriptTag, thing); }
 } // namespace js
 
 namespace js {
 template <>
 void
 GCMarker::traverse(AccessorShape* thing) {
     MOZ_CRASH("AccessorShape must be marked as a Shape");
 }
@@ -1269,16 +1340,20 @@ GCMarker::processMarkStackTop(SliceBudge
       case GroupTag: {
         return lazilyMarkChildren(reinterpret_cast<ObjectGroup*>(addr));
       }
 
       case JitCodeTag: {
         return reinterpret_cast<jit::JitCode*>(addr)->traceChildren(this);
       }
 
+      case ScriptTag: {
+        return reinterpret_cast<JSScript*>(addr)->traceChildren(this);
+      }
+
       case SavedValueArrayTag: {
         MOZ_ASSERT(!(addr & CellMask));
         JSObject* obj = reinterpret_cast<JSObject*>(addr);
         HeapSlot* vp;
         HeapSlot* end;
         if (restoreValueArray(obj, (void**)&vp, (void**)&end))
             pushValueArray(&obj->as<NativeObject>(), vp, end);
         else
@@ -1322,16 +1397,17 @@ GCMarker::processMarkStackTop(SliceBudge
         AssertZoneIsMarking(obj);
 
         budget.step();
         if (budget.isOverBudget()) {
             repush(obj);
             return;
         }
 
+        markPotentialEphemeronKey(obj);
         ObjectGroup* group = obj->groupFromGC();
         traverseEdge(obj, group);
 
         NativeObject *nobj = CallTraceHook(TraverseObjectFunctor(), this, obj,
                                            CheckGeneration::DoChecks, this, obj);
         if (!nobj)
             return;
 
@@ -1586,61 +1662,64 @@ MarkStack::sizeOfExcludingThis(mozilla::
 {
     return mallocSizeOf(stack_);
 }
 
 
 /*** GCMarker *************************************************************************************/
 
 /*
- * DoNotTraceWeakMaps: the GC is recomputing the liveness of WeakMap entries,
- * so we delay visting entries.
+ * ExpandWeakMaps: the GC is recomputing the liveness of WeakMap entries by
+ * expanding each live WeakMap into its constituent key->value edges, a table
+ * of which will be consulted in a later phase whenever marking a potential
+ * key.
  */
 GCMarker::GCMarker(JSRuntime* rt)
-  : JSTracer(rt, JSTracer::TracerKindTag::Marking, DoNotTraceWeakMaps),
+  : JSTracer(rt, JSTracer::TracerKindTag::Marking, ExpandWeakMaps),
     stack(size_t(-1)),
     color(BLACK),
     unmarkedArenaStackTop(nullptr),
     markLaterArenas(0),
     started(false),
     strictCompartmentChecking(false)
 {
 }
 
 bool
 GCMarker::init(JSGCMode gcMode)
 {
-    return stack.init(gcMode);
+    return stack.init(gcMode) && weakKeys.init();
 }
 
 void
 GCMarker::start()
 {
     MOZ_ASSERT(!started);
     started = true;
     color = BLACK;
+    linearWeakMarkingDisabled_ = false;
 
     MOZ_ASSERT(!unmarkedArenaStackTop);
     MOZ_ASSERT(markLaterArenas == 0);
-
 }
 
 void
 GCMarker::stop()
 {
     MOZ_ASSERT(isDrained());
 
     MOZ_ASSERT(started);
     started = false;
 
     MOZ_ASSERT(!unmarkedArenaStackTop);
     MOZ_ASSERT(markLaterArenas == 0);
 
     /* Free non-ballast stack memory. */
     stack.reset();
+    weakKeys.clear();
 }
 
 void
 GCMarker::reset()
 {
     color = BLACK;
 
     stack.reset();
@@ -1656,16 +1735,37 @@ GCMarker::reset()
         aheader->allocatedDuringIncremental = 0;
         markLaterArenas--;
     }
     MOZ_ASSERT(isDrained());
     MOZ_ASSERT(!markLaterArenas);
 }
 
 void
+GCMarker::enterWeakMarkingMode()
+{
+    MOZ_ASSERT(tag_ == TracerKindTag::Marking);
+    if (linearWeakMarkingDisabled_)
+        return;
+
+    // During weak marking mode, we maintain a table mapping weak keys to
+    // entries in known-live weakmaps.
+    if (weakMapAction() == ExpandWeakMaps) {
+        tag_ = TracerKindTag::WeakMarking;
+
+        for (GCCompartmentGroupIter c(runtime()); !c.done(); c.next()) {
+            for (WeakMapBase* m = c->gcWeakMapList; m; m = m->next) {
+                if (m->marked)
+                    m->markEphemeronEntries(this);
+            }
+        }
+    }
+}
+
+void
 GCMarker::markDelayedChildren(ArenaHeader* aheader)
 {
     if (aheader->markOverflow) {
         bool always = aheader->allocatedDuringIncremental;
         aheader->markOverflow = 0;
 
         for (ArenaCellIterUnderGC i(aheader); !i.done(); i.next()) {
             TenuredCell* t = i.getCell();
@@ -2346,18 +2446,18 @@ struct AssertNonGrayTracer : public JS::
                       !thing.asCell()->asTenured().isMarked(js::gc::GRAY));
     }
 };
 #endif
 
 struct UnmarkGrayTracer : public JS::CallbackTracer
 {
     /*
-     * We set eagerlyTraceWeakMaps to false because the cycle collector will fix
-     * up any color mismatches involving weakmaps when it runs.
+     * We set weakMapAction to DoNotTraceWeakMaps because the cycle collector
+     * will fix up any color mismatches involving weakmaps when it runs.
      */
     explicit UnmarkGrayTracer(JSRuntime* rt)
       : JS::CallbackTracer(rt, DoNotTraceWeakMaps),
         tracingShape(false),
         previousShape(nullptr),
         unmarkedAny(false)
     {}
 
--- a/js/src/gc/Marking.h
+++ b/js/src/gc/Marking.h
@@ -3,22 +3,26 @@
  * 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 gc_Marking_h
 #define gc_Marking_h
 
 #include "mozilla/DebugOnly.h"
+#include "mozilla/HashFunctions.h"
+#include "mozilla/Move.h"
 
 #include "jsfriendapi.h"
 
+#include "ds/OrderedHashTable.h"
 #include "gc/Heap.h"
 #include "gc/Tracer.h"
 #include "js/GCAPI.h"
+#include "js/HeapAPI.h"
 #include "js/SliceBudget.h"
 #include "js/TracingAPI.h"
 
 class JSLinearString;
 class JSRope;
 namespace js {
 class BaseShape;
 class GCMarker;
@@ -127,16 +131,45 @@ class MarkStack
     /* Grow the stack, ensuring there is space for at least count elements. */
     bool enlarge(unsigned count);
 
     void setGCMode(JSGCMode gcMode);
 
     size_t sizeOfExcludingThis(mozilla::MallocSizeOf mallocSizeOf) const;
 };
 
+class WeakMapBase;
+
+namespace gc {
+
+struct WeakKeyTableHashPolicy {
+    typedef JS::GCCellPtr Lookup;
+    static HashNumber hash(const Lookup& v) { return mozilla::HashGeneric(v.asCell()); }
+    static bool match(const JS::GCCellPtr& k, const Lookup& l) { return k == l; }
+    static bool isEmpty(const JS::GCCellPtr& v) { return !v; }
+    static void makeEmpty(JS::GCCellPtr* vp) { *vp = nullptr; }
+};
+
+struct WeakMarkable {
+    WeakMapBase* weakmap;
+    JS::GCCellPtr key;
+
+    WeakMarkable(WeakMapBase* weakmapArg, JS::GCCellPtr keyArg)
+      : weakmap(weakmapArg), key(keyArg) {}
+};
+
+typedef Vector<WeakMarkable, 2, js::SystemAllocPolicy> WeakEntryVector;
+
+typedef OrderedHashMap<JS::GCCellPtr,
+                       WeakEntryVector,
+                       WeakKeyTableHashPolicy,
+                       js::SystemAllocPolicy> WeakKeyTable;
+
+} /* namespace gc */
+
 class GCMarker : public JSTracer
 {
   public:
     explicit GCMarker(JSRuntime* rt);
     bool init(JSGCMode gcMode);
 
     void setMaxCapacity(size_t maxCap) { stack.setMaxCapacity(maxCap); }
     size_t maxCapacity() const { return stack.maxCapacity(); }
@@ -168,16 +201,33 @@ class GCMarker : public JSTracer
     }
     void setMarkColorBlack() {
         MOZ_ASSERT(isDrained());
         MOZ_ASSERT(color == gc::GRAY);
         color = gc::BLACK;
     }
     uint32_t markColor() const { return color; }
 
+    void enterWeakMarkingMode();
+
+    void leaveWeakMarkingMode() {
+        MOZ_ASSERT_IF(weakMapAction() == ExpandWeakMaps && !linearWeakMarkingDisabled_, tag_ == TracerKindTag::WeakMarking);
+        tag_ = TracerKindTag::Marking;
+
+        // Table is expensive to maintain when not in weak marking mode, so
+        // we'll rebuild it upon entry rather than allow it to contain stale
+        // data.
+        weakKeys.clear();
+    }
+
+    void abortLinearWeakMarking() {
+        leaveWeakMarkingMode();
+        linearWeakMarkingDisabled_ = true;
+    }
+
     void delayMarkingArena(gc::ArenaHeader* aheader);
     void delayMarkingChildren(const void* thing);
     void markDelayedChildren(gc::ArenaHeader* aheader);
     bool markDelayedChildren(SliceBudget& budget);
     bool hasDelayedChildren() const {
         return !!unmarkedArenaStackTop;
     }
 
@@ -190,16 +240,24 @@ class GCMarker : public JSTracer
     void setGCMode(JSGCMode mode) { stack.setGCMode(mode); }
 
     size_t sizeOfExcludingThis(mozilla::MallocSizeOf mallocSizeOf) const;
 
 #ifdef DEBUG
     bool shouldCheckCompartments() { return strictCompartmentChecking; }
 #endif
 
+    void markEphemeronValues(gc::Cell* markedCell, gc::WeakEntryVector& entry);
+
+    /*
+     * Mapping from not yet marked keys to a vector of all values that the key
+     * maps to in any live weak map.
+     */
+    gc::WeakKeyTable weakKeys;
+
   private:
 #ifdef DEBUG
     void checkZone(void* p);
 #else
     void checkZone(void* p) {}
 #endif
 
     /*
@@ -208,16 +266,17 @@ class GCMarker : public JSTracer
      * the context of push or pop operation.
      */
     enum StackTag {
         ValueArrayTag,
         ObjectTag,
         GroupTag,
         SavedValueArrayTag,
         JitCodeTag,
+        ScriptTag,
         LastTag = JitCodeTag
     };
 
     static const uintptr_t StackTagMask = 7;
     static_assert(StackTagMask >= uintptr_t(LastTag), "The tag mask must subsume the tags.");
     static_assert(StackTagMask <= gc::CellMask, "The tag mask must be embeddable in a Cell*.");
 
     // Push an object onto the stack for later tracing and assert that it has
@@ -225,16 +284,18 @@ class GCMarker : public JSTracer
     void repush(JSObject* obj) {
         MOZ_ASSERT(gc::TenuredCell::fromPointer(obj)->isMarked(markColor()));
         pushTaggedPtr(ObjectTag, obj);
     }
 
     template <typename T> void markAndTraceChildren(T* thing);
     template <typename T> void markAndPush(StackTag tag, T* thing);
     template <typename T> void markAndScan(T* thing);
+    template <typename T> void markPotentialEphemeronKeyHelper(T oldThing);
+    template <typename T> void markPotentialEphemeronKey(T* oldThing);
     void eagerlyMarkChildren(JSLinearString* str);
     void eagerlyMarkChildren(JSRope* rope);
     void eagerlyMarkChildren(JSString* str);
     void eagerlyMarkChildren(LazyScript *thing);
     void eagerlyMarkChildren(Shape* shape);
     void lazilyMarkChildren(ObjectGroup* group);
 
     // We may not have concrete types yet, so this has to be out of the header.
@@ -282,16 +343,22 @@ class GCMarker : public JSTracer
     MarkStack stack;
 
     /* The color is only applied to objects and functions. */
     uint32_t color;
 
     /* Pointer to the top of the stack of arenas we are delaying marking on. */
     js::gc::ArenaHeader* unmarkedArenaStackTop;
 
+    /*
+     * If the weakKeys table OOMs, disable the linear algorithm and fall back
+     * to iterating until the next GC.
+     */
+    bool linearWeakMarkingDisabled_;
+
     /* Count of arenas that are currently in the stack. */
     mozilla::DebugOnly<size_t> markLaterArenas;
 
     /* Assert that start and stop are called with correct ordering. */
     mozilla::DebugOnly<bool> started;
 
     /*
      * If this is true, all marked objects must belong to a compartment being
--- a/js/src/gc/Tracer.h
+++ b/js/src/gc/Tracer.h
@@ -18,21 +18,21 @@ namespace js {
 //
 // Tracing is an abstract visitation of each edge in a JS heap graph.[1] The
 // most common (and performance sensitive) use of this infrastructure is for GC
 // "marking" as part of the mark-and-sweep collector; however, this
 // infrastructure is much more general than that and is used for many other
 // purposes as well.
 //
 // One commonly misunderstood subtlety of the tracing architecture is the role
-// of graph verticies versus graph edges. Graph verticies are the heap
+// of graph vertices versus graph edges. Graph vertices are the heap
 // allocations -- GC things -- that are returned by Allocate. Graph edges are
 // pointers -- including tagged pointers like Value and jsid -- that link the
 // allocations into a complex heap. The tracing API deals *only* with edges.
-// Any action taken on the target of a graph edge is independent to the tracing
+// Any action taken on the target of a graph edge is independent of the tracing
 // itself.
 //
 // Another common misunderstanding relates to the role of the JSTracer. The
 // JSTracer instance determines what tracing does when visiting an edge; it
 // does not itself participate in the tracing process, other than to be passed
 // through as opaque data. It works like a closure in that respect.
 //
 // Tracing implementations internal to SpiderMonkey should use these interfaces
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/gc/weak-marking-01.js
@@ -0,0 +1,193 @@
+// These tests will be using object literals as keys, and we want some of them
+// to be dead after being inserted into a WeakMap. That means we must wrap
+// everything in functions because it seems like the toplevel script hangs onto
+// its object literals.
+
+// All reachable keys should be found, and the rest should be swept.
+function basicSweeping() {
+  var wm1 = new WeakMap();
+  wm1.set({'name': 'obj1'}, {'name': 'val1'});
+  var hold = {'name': 'obj2'};
+  wm1.set(hold, {'name': 'val2'});
+  wm1.set({'name': 'obj3'}, {'name': 'val3'});
+
+  startgc(100000, 'shrinking');
+  gcslice();
+
+  assertEq(wm1.get(hold).name, 'val2');
+  assertEq(nondeterministicGetWeakMapKeys(wm1).length, 1);
+}
+
+basicSweeping();
+
+// Keep values alive even when they are only referenced by (live) WeakMap values.
+function weakGraph() {
+  var wm1 = new WeakMap();
+  var obj1 = {'name': 'obj1'};
+  var obj2 = {'name': 'obj2'};
+  var obj3 = {'name': 'obj3'};
+  var obj4 = {'name': 'obj4'};
+  var clear = {'name': ''}; // Make the interpreter forget about the last obj created
+
+  wm1.set(obj2, obj3);
+  wm1.set(obj3, obj1);
+  wm1.set(obj4, obj1); // This edge will be cleared
+  obj1 = obj3 = obj4 = undefined;
+
+  startgc(100000, 'shrinking');
+  gcslice();
+
+  assertEq(obj2.name, "obj2");
+  assertEq(wm1.get(obj2).name, "obj3");
+  assertEq(wm1.get(wm1.get(obj2)).name, "obj1");
+  print(nondeterministicGetWeakMapKeys(wm1).map(o => o.name).join(","));
+  assertEq(nondeterministicGetWeakMapKeys(wm1).length, 2);
+}
+
+weakGraph();
+
+// ...but the weakmap itself has to stay alive, too.
+function deadWeakMap() {
+  var wm1 = new WeakMap();
+  var obj1 = makeFinalizeObserver();
+  var obj2 = {'name': 'obj2'};
+  var obj3 = {'name': 'obj3'};
+  var obj4 = {'name': 'obj4'};
+  var clear = {'name': ''}; // Make the interpreter forget about the last obj created
+
+  wm1.set(obj2, obj3);
+  wm1.set(obj3, obj1);
+  wm1.set(obj4, obj1); // This edge will be cleared
+  var initialCount = finalizeCount();
+  obj1 = obj3 = obj4 = undefined;
+  wm1 = undefined;
+
+  startgc(100000, 'shrinking');
+  gcslice();
+
+  assertEq(obj2.name, "obj2");
+  assertEq(finalizeCount(), initialCount + 1);
+}
+
+deadWeakMap();
+
+// WeakMaps do not strongly reference their keys or values. (WeakMaps hold a
+// collection of (strong) references to *edges* from keys to values. If the
+// WeakMap is not live, then its edges are of course not live either. An edge
+// holds neither its key nor its value live; it just holds a strong ref from
+// the key to the value. So if the key is live, the value is live too, but the
+// edge itself has no references to anything.)
+function deadKeys() {
+  var wm1 = new WeakMap();
+  var obj1 = makeFinalizeObserver();
+  var obj2 = {'name': 'obj2'};
+  var obj3 = makeFinalizeObserver();
+  var clear = {}; // Make the interpreter forget about the last obj created
+
+  wm1.set(obj1, obj1);
+  wm1.set(obj3, obj2);
+  obj1 = obj3 = undefined;
+  var initialCount = finalizeCount();
+
+  startgc(100000, 'shrinking');
+  gcslice();
+
+  assertEq(finalizeCount(), initialCount + 2);
+  assertEq(nondeterministicGetWeakMapKeys(wm1).length, 0);
+}
+
+deadKeys();
+
+// The weakKeys table has to grow if it encounters enough new unmarked weakmap
+// keys. Trigger this to happen during weakmap marking.
+//
+// There's some trickiness involved in getting it to test the right thing,
+// because if a key is marked before the weakmap, then it won't get entered
+// into the weakKeys table. This chains through multiple weakmap layers to
+// ensure that the objects can't get marked before the weakmaps.
+function weakKeysRealloc() {
+  var wm1 = new WeakMap;
+  var wm2 = new WeakMap;
+  var wm3 = new WeakMap;
+  var obj1 = {'name': 'obj1'};
+  var obj2 = {'name': 'obj2'};
+  wm1.set(obj1, wm2);
+  wm2.set(obj2, wm3);
+  for (var i = 0; i < 10000; i++) {
+    wm3.set(Object.create(null), wm2);
+  }
+  wm3.set(Object.create(null), makeFinalizeObserver());
+  wm2 = undefined;
+  wm3 = undefined;
+  obj2 = undefined;
+
+  var initialCount = finalizeCount();
+  startgc(100000, 'shrinking');
+  gcslice();
+  assertEq(finalizeCount(), initialCount + 1);
+}
+
+weakKeysRealloc();
+
+// The weakKeys table is populated during regular marking. When a key is later
+// deleted, both it and its delegate should be removed from weakKeys.
+// Otherwise, it will hold its value live if it gets marked, and our table
+// traversals will include non-keys, etc.
+function deletedKeys() {
+  var wm = new WeakMap;
+  var g = newGlobal();
+
+  for (var i = 0; i < 1000; i++)
+    wm.set(g.Object.create(null), i);
+
+  startgc(100, 'shrinking');
+  for (var key of nondeterministicGetWeakMapKeys(wm)) {
+    if (wm.get(key) % 2)
+      wm.delete(key);
+  }
+
+  gc();
+}
+
+deletedKeys();
+
+// Test adding keys during incremental GC.
+function incrementalAdds() {
+  var initialCount = finalizeCount();
+
+  var wm1 = new WeakMap;
+  var wm2 = new WeakMap;
+  var wm3 = new WeakMap;
+  var obj1 = {'name': 'obj1'};
+  var obj2 = {'name': 'obj2'};
+  wm1.set(obj1, wm2);
+  wm2.set(obj2, wm3);
+  for (var i = 0; i < 10000; i++) {
+    wm3.set(Object.create(null), wm2);
+  }
+  wm3.set(Object.create(null), makeFinalizeObserver());
+  obj2 = undefined;
+
+  var obj3 = [];
+  startgc(100, 'shrinking');
+  var M = 10;
+  var N = 800;
+  for (var j = 0; j < M; j++) {
+    for (var i = 0; i < N; i++)
+      wm3.set(Object.create(null), makeFinalizeObserver()); // Should be swept
+    for (var i = 0; i < N; i++) {
+      obj3.push({'name': 'obj3'});
+      wm1.set(obj3[obj3.length - 1], makeFinalizeObserver()); // Should not be swept
+    }
+    gcslice();
+  }
+
+  wm2 = undefined;
+  wm3 = undefined;
+
+  gc();
+  print("initialCount = " + initialCount);
+  assertEq(finalizeCount(), initialCount + 1 + M * N);
+}
+
+incrementalAdds();
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/gc/weak-marking-02.js
@@ -0,0 +1,128 @@
+// These tests will be using object literals as keys, and we want some of them
+// to be dead after being inserted into a WeakMap. That means we must wrap
+// everything in functions because it seems like the toplevel script hangs onto
+// its object literals.
+
+// Cross-compartment WeakMap keys work by storing a cross-compartment wrapper
+// in the WeakMap, and the actual "delegate" object in the target compartment
+// is the thing whose liveness is checked.
+
+var g2 = newGlobal();
+g2.eval('function genObj(name) { return {"name": name} }');
+
+function basicSweeping() {
+  var wm1 = new WeakMap();
+  wm1.set({'name': 'obj1'}, {'name': 'val1'});
+  var hold = g2.genObj('obj2');
+  wm1.set(hold, {'name': 'val2'});
+  wm1.set({'name': 'obj3'}, {'name': 'val3'});
+  var obj4 = g2.genObj('obj4');
+  wm1.set(obj4, {'name': 'val3'});
+  obj4 = undefined;
+
+  startgc(100000, 'shrinking');
+  gcslice();
+  assertEq(wm1.get(hold).name, 'val2');
+  assertEq(nondeterministicGetWeakMapKeys(wm1).length, 1);
+}
+
+basicSweeping();
+
+// Same, but behind an additional WM layer, to avoid ordering problems (not
+// that I've checked that basicSweeping even has any problems.)
+
+function basicSweeping2() {
+  var wm1 = new WeakMap();
+  wm1.set({'name': 'obj1'}, {'name': 'val1'});
+  var hold = g2.genObj('obj2');
+  wm1.set(hold, {'name': 'val2'});
+  wm1.set({'name': 'obj3'}, {'name': 'val3'});
+  var obj4 = g2.genObj('obj4');
+  wm1.set(obj4, {'name': 'val3'});
+  obj4 = undefined;
+
+  var base1 = {'name': 'base1'};
+  var base2 = {'name': 'base2'};
+  var wm_base1 = new WeakMap();
+  var wm_base2 = new WeakMap();
+  wm_base1.set(base1, wm_base2);
+  wm_base2.set(base2, wm1);
+  wm1 = wm_base2 = undefined;
+
+  startgc(100000, 'shrinking');
+  gcslice();
+
+  assertEq(nondeterministicGetWeakMapKeys(wm_base1).length, 1);
+  wm_base2 = wm_base1.get(base1);
+  assertEq(nondeterministicGetWeakMapKeys(wm_base2).length, 1);
+  assertEq(nondeterministicGetWeakMapKeys(wm_base1)[0], base1);
+  assertEq(nondeterministicGetWeakMapKeys(wm_base2)[0], base2);
+  wm_base2 = wm_base1.get(base1);
+  wm1 = wm_base2.get(base2);
+  assertEq(wm1.get(hold).name, 'val2');
+  assertEq(nondeterministicGetWeakMapKeys(wm1).length, 1);
+}
+
+basicSweeping2();
+
+// Scatter the weakmap, the keys, and the values among different compartments.
+
+function tripleZoneMarking() {
+  var g1 = newGlobal();
+  var g2 = newGlobal();
+  var g3 = newGlobal();
+
+  var wm = g1.eval("new WeakMap()");
+  var key = g2.eval("({'name': 'obj1'})");
+  var value = g3.eval("({'name': 'val1'})");
+  g1 = g2 = g3 = undefined;
+  wm.set(key, value);
+
+  // Make all of it only reachable via a weakmap in the main test compartment,
+  // so that all of this happens during weak marking mode. Use the weakmap as
+  // its own key, so we know that the weakmap will get traced before the key
+  // and therefore will populate the weakKeys table and all of that jazz.
+  var base_wm = new WeakMap();
+  base_wm.set(base_wm, [ wm, key ]);
+
+  wm = key = value = undefined;
+
+  startgc(100000, 'shrinking');
+  gcslice();
+
+  var keys = nondeterministicGetWeakMapKeys(base_wm);
+  assertEq(keys.length, 1);
+  var [ wm, key ] = base_wm.get(keys[0]);
+  assertEq(key.name, "obj1");
+  value = wm.get(key);
+  assertEq(value.name, "val1");
+}
+
+tripleZoneMarking();
+
+function enbugger() {
+  var g = newGlobal();
+  var dbg = new Debugger;
+  g.eval("function debuggee_f() { return 1; }");
+  g.eval("function debuggee_g() { return 1; }");
+  dbg.addDebuggee(g);
+  var [ s ] = dbg.findScripts({global: g}).filter(s => s.displayName == "debuggee_f");
+  var [ s2 ] = dbg.findScripts({global: g}).filter(s => s.displayName == "debuggee_g");
+  g.eval("debuggee_f = null");
+  gc();
+  dbg.removeAllDebuggees();
+  gc();
+  assertEq(s.displayName, "debuggee_f");
+
+  var wm = new WeakMap;
+  var obj = Object.create(null);
+  var obj2 = Object.create(null);
+  wm.set(obj, s);
+  wm.set(obj2, obj);
+  wm.set(s2, obj2);
+  s = s2 = obj = obj2 = null;
+
+  gc();
+}
+
+enbugger();
--- a/js/src/jsapi-tests/testWeakMap.cpp
+++ b/js/src/jsapi-tests/testWeakMap.cpp
@@ -65,62 +65,71 @@ checkSize(JS::HandleObject map, uint32_t
     return true;
 }
 END_TEST(testWeakMap_basicOperations)
 
 BEGIN_TEST(testWeakMap_keyDelegates)
 {
     JS_SetGCParameter(rt, JSGC_MODE, JSGC_MODE_INCREMENTAL);
     JS_GC(rt);
-
     JS::RootedObject map(cx, JS::NewWeakMapObject(cx));
     CHECK(map);
 
     JS::RootedObject key(cx, newKey());
     CHECK(key);
 
     JS::RootedObject delegate(cx, newDelegate());
     CHECK(delegate);
     keyDelegate = delegate;
 
+    JS::RootedObject delegateRoot(cx);
+    {
+        JSAutoCompartment ac(cx, delegate);
+        delegateRoot = JS_NewPlainObject(cx);
+        CHECK(delegateRoot);
+        JS::RootedValue delegateValue(cx, ObjectValue(*delegate));
+        CHECK(JS_DefineProperty(cx, delegateRoot, "delegate", delegateValue, 0));
+    }
+    delegate = nullptr;
+
     /*
      * Perform an incremental GC, introducing an unmarked CCW to force the map
      * zone to finish marking before the delegate zone.
      */
-    CHECK(newCCW(map, delegate));
+    CHECK(newCCW(map, delegateRoot));
     js::SliceBudget budget(js::WorkBudget(1000000));
     rt->gc.startDebugGC(GC_NORMAL, budget);
     CHECK(!JS::IsIncrementalGCInProgress(rt));
 #ifdef DEBUG
-    CHECK(map->zone()->lastZoneGroupIndex() < delegate->zone()->lastZoneGroupIndex());
+    CHECK(map->zone()->lastZoneGroupIndex() < delegateRoot->zone()->lastZoneGroupIndex());
 #endif
 
     /* Add our entry to the weakmap. */
     JS::RootedValue val(cx, JS::Int32Value(1));
     CHECK(SetWeakMapEntry(cx, map, key, val));
     CHECK(checkSize(map, 1));
 
     /* Check the delegate keeps the entry alive even if the key is not reachable. */
     key = nullptr;
-    CHECK(newCCW(map, delegate));
+    CHECK(newCCW(map, delegateRoot));
     budget = js::SliceBudget(js::WorkBudget(100000));
     rt->gc.startDebugGC(GC_NORMAL, budget);
     CHECK(!JS::IsIncrementalGCInProgress(rt));
     CHECK(checkSize(map, 1));
 
     /*
      * Check that the zones finished marking at the same time, which is
-     * neccessary because of the presence of the delegate and the CCW.
+     * necessary because of the presence of the delegate and the CCW.
      */
 #ifdef DEBUG
-    CHECK(map->zone()->lastZoneGroupIndex() == delegate->zone()->lastZoneGroupIndex());
+    CHECK(map->zone()->lastZoneGroupIndex() == delegateRoot->zone()->lastZoneGroupIndex());
 #endif
 
     /* Check that when the delegate becomes unreachable the entry is removed. */
-    delegate = nullptr;
+    delegateRoot = nullptr;
     keyDelegate = nullptr;
     JS_GC(rt);
     CHECK(checkSize(map, 0));
 
     return true;
 }
 
 static void DelegateObjectMoved(JSObject* obj, const JSObject* old)
--- a/js/src/jsgc.cpp
+++ b/js/src/jsgc.cpp
@@ -4018,32 +4018,42 @@ GCRuntime::beginMarkPhase(JS::gcreason::
 template <class CompartmentIterT>
 void
 GCRuntime::markWeakReferences(gcstats::Phase phase)
 {
     MOZ_ASSERT(marker.isDrained());
 
     gcstats::AutoPhase ap1(stats, phase);
 
+    marker.enterWeakMarkingMode();
+
+    // TODO bug 1167452: Make weak marking incremental
+    SliceBudget budget = SliceBudget::unlimited();
+    marker.drainMarkStack(budget);
+
     for (;;) {
         bool markedAny = false;
         for (CompartmentIterT c(rt); !c.done(); c.next()) {
-            markedAny |= WatchpointMap::markCompartmentIteratively(c, &marker);
-            markedAny |= WeakMapBase::markCompartmentIteratively(c, &marker);
+            if (c->watchpointMap)
+                markedAny |= c->watchpointMap->markIteratively(&marker);
+            if (marker.weakMapAction() != ExpandWeakMaps)
+                markedAny |= WeakMapBase::markCompartmentIteratively(c, &marker);
         }
         markedAny |= Debugger::markAllIteratively(&marker);
         markedAny |= jit::JitRuntime::MarkJitcodeGlobalTableIteratively(&marker);
 
         if (!markedAny)
             break;
 
         auto unlimited = SliceBudget::unlimited();
         marker.drainMarkStack(unlimited);
     }
     MOZ_ASSERT(marker.isDrained());
+
+    marker.leaveWeakMarkingMode();
 }
 
 void
 GCRuntime::markWeakReferencesInCurrentGroup(gcstats::Phase phase)
 {
     markWeakReferences<GCCompartmentGroupIter>(phase);
 }
 
@@ -4176,22 +4186,33 @@ js::gc::MarkingValidator::nonIncremental
     if (!markedWeakMaps.init())
         return;
 
     for (GCCompartmentsIter c(runtime); !c.done(); c.next()) {
         if (!WeakMapBase::saveCompartmentMarkedWeakMaps(c, markedWeakMaps))
             return;
     }
 
+    gc::WeakKeyTable savedWeakKeys;
+    if (!savedWeakKeys.init())
+        return;
+
+    for (gc::WeakKeyTable::Range r = gc->marker.weakKeys.all(); !r.empty(); r.popFront()) {
+        if (!savedWeakKeys.put(Move(r.front().key), Move(r.front().value)))
+            CrashAtUnhandlableOOM("saving weak keys table for validator");
+    }
+
     /*
      * After this point, the function should run to completion, so we shouldn't
      * do anything fallible.
      */
     initialized = true;
 
+    gc->marker.weakKeys.clear();
+
     /* Re-do all the marking, but non-incrementally. */
     js::gc::State state = gc->incrementalState;
     gc->incrementalState = MARK_ROOTS;
 
     {
         gcstats::AutoPhase ap(gc->stats, gcstats::PHASE_MARK);
 
         {
@@ -4244,17 +4265,23 @@ js::gc::MarkingValidator::nonIncremental
     for (auto chunk = gc->allNonEmptyChunks(); !chunk.done(); chunk.next()) {
         ChunkBitmap* bitmap = &chunk->bitmap;
         ChunkBitmap* entry = map.lookup(chunk)->value();
         Swap(*entry, *bitmap);
     }
 
     for (GCCompartmentsIter c(runtime); !c.done(); c.next())
         WeakMapBase::unmarkCompartment(c);
-    WeakMapBase::restoreCompartmentMarkedWeakMaps(markedWeakMaps);
+    WeakMapBase::restoreMarkedWeakMaps(markedWeakMaps);
+
+    gc->marker.weakKeys.clear();
+    for (gc::WeakKeyTable::Range r = savedWeakKeys.all(); !r.empty(); r.popFront()) {
+        if (!gc->marker.weakKeys.put(Move(r.front().key), Move(r.front().value)))
+            CrashAtUnhandlableOOM("restoring weak keys table for validator");
+    }
 
     gc->incrementalState = state;
 }
 
 void
 js::gc::MarkingValidator::validate()
 {
     /*
@@ -4749,17 +4776,16 @@ GCRuntime::endMarkingZoneGroup()
     gcstats::AutoPhase ap(stats, gcstats::PHASE_SWEEP_MARK);
 
     /*
      * Mark any incoming black pointers from previously swept compartments
      * whose referents are not marked. This can occur when gray cells become
      * black by the action of UnmarkGray.
      */
     MarkIncomingCrossCompartmentPointers(rt, BLACK);
-
     markWeakReferencesInCurrentGroup(gcstats::PHASE_SWEEP_MARK_WEAK);
 
     /*
      * Change state of current group to MarkGray to restrict marking to this
      * group.  Note that there may be pointers to the atoms compartment, and
      * these will be marked through, as they are not marked with
      * MarkCrossCompartmentXXX.
      */
@@ -4910,16 +4936,27 @@ GCRuntime::beginSweepingZoneGroup()
         if (rt->sweepZoneCallback)
             rt->sweepZoneCallback(zone);
 
         zone->gcLastZoneGroupIndex = zoneGroupIndex;
     }
 
     validateIncrementalMarking();
 
+    /* Clear out this zone group's keys from the weakKeys table, to prevent later accesses. */
+    for (WeakKeyTable::Range r = marker.weakKeys.all(); !r.empty(); ) {
+        auto key(r.front().key);
+        r.popFront();
+        if (gc::TenuredCell::fromPointer(key.asCell())->zone()->isGCSweeping()) {
+            bool found;
+            marker.weakKeys.remove(key, &found);
+            MOZ_ASSERT(found);
+        }
+    }
+
     FreeOp fop(rt);
     SweepAtomsTask sweepAtomsTask(rt);
     SweepInnerViewsTask sweepInnerViewsTask(rt);
     SweepCCWrappersTask sweepCCWrappersTask(rt);
     SweepBaseShapesTask sweepBaseShapesTask(rt);
     SweepInitialShapesTask sweepInitialShapesTask(rt);
     SweepObjectGroupsTask sweepObjectGroupsTask(rt);
     SweepRegExpsTask sweepRegExpsTask(rt);
--- a/js/src/jswatchpoint.cpp
+++ b/js/src/jswatchpoint.cpp
@@ -140,24 +140,16 @@ WatchpointMap::triggerWatchpoint(JSConte
     // watchpoint. See the comment before UnmarkGrayChildren in gc/Marking.cpp
     JS::ExposeObjectToActiveJS(closure);
 
     /* Call the handler. */
     return handler(cx, obj, id, old, vp.address(), closure);
 }
 
 bool
-WatchpointMap::markCompartmentIteratively(JSCompartment* c, JSTracer* trc)
-{
-    if (!c->watchpointMap)
-        return false;
-    return c->watchpointMap->markIteratively(trc);
-}
-
-bool
 WatchpointMap::markIteratively(JSTracer* trc)
 {
     bool marked = false;
     for (Map::Enum e(map); !e.empty(); e.popFront()) {
         Map::Entry& entry = e.front();
         JSObject* priorKeyObj = entry.key().object;
         jsid priorKeyId(entry.key().id.get());
         bool objectIsLive =
--- a/js/src/jswatchpoint.h
+++ b/js/src/jswatchpoint.h
@@ -65,17 +65,16 @@ class WatchpointMap {
                JSWatchPointHandler handler, HandleObject closure);
     void unwatch(JSObject* obj, jsid id,
                  JSWatchPointHandler* handlerp, JSObject** closurep);
     void unwatchObject(JSObject* obj);
     void clear();
 
     bool triggerWatchpoint(JSContext* cx, HandleObject obj, HandleId id, MutableHandleValue vp);
 
-    static bool markCompartmentIteratively(JSCompartment* c, JSTracer* trc);
     bool markIteratively(JSTracer* trc);
     void markAll(JSTracer* trc);
     static void sweepAll(JSRuntime* rt);
     void sweep();
 
     static void traceAll(WeakMapTracer* trc);
     void trace(WeakMapTracer* trc);
 
--- a/js/src/jsweakmap.cpp
+++ b/js/src/jsweakmap.cpp
@@ -45,47 +45,51 @@ WeakMapBase::~WeakMapBase()
         removeWeakMapFromList(this);
 }
 
 void
 WeakMapBase::trace(JSTracer* tracer)
 {
     MOZ_ASSERT(isInList());
     if (tracer->isMarkingTracer()) {
-        // We don't trace any of the WeakMap entries at this time, just record
-        // record the fact that the WeakMap has been marked. Entries are marked
-        // in the iterative marking phase by markAllIteratively(), which happens
-        // when as many keys as possible have been marked already.
-        MOZ_ASSERT(tracer->eagerlyTraceWeakMaps() == DoNotTraceWeakMaps);
         marked = true;
+        if (tracer->weakMapAction() == DoNotTraceWeakMaps) {
+            // Do not trace any WeakMap entries at this time. Just record the
+            // fact that the WeakMap has been marked. Entries are marked in the
+            // iterative marking phase by markAllIteratively(), after as many
+            // keys as possible have been marked already.
+        } else {
+            MOZ_ASSERT(tracer->weakMapAction() == ExpandWeakMaps);
+            markEphemeronEntries(tracer);
+        }
     } else {
         // If we're not actually doing garbage collection, the keys won't be marked
         // nicely as needed by the true ephemeral marking algorithm --- custom tracers
         // such as the cycle collector must use their own means for cycle detection.
         // So here we do a conservative approximation: pretend all keys are live.
-        if (tracer->eagerlyTraceWeakMaps() == DoNotTraceWeakMaps)
+        if (tracer->weakMapAction() == DoNotTraceWeakMaps)
             return;
 
         nonMarkingTraceValues(tracer);
-        if (tracer->eagerlyTraceWeakMaps() == TraceWeakMapKeysValues)
+        if (tracer->weakMapAction() == TraceWeakMapKeysValues)
             nonMarkingTraceKeys(tracer);
     }
 }
 
 void
 WeakMapBase::unmarkCompartment(JSCompartment* c)
 {
     for (WeakMapBase* m = c->gcWeakMapList; m; m = m->next)
         m->marked = false;
 }
 
 void
 WeakMapBase::markAll(JSCompartment* c, JSTracer* tracer)
 {
-    MOZ_ASSERT(tracer->eagerlyTraceWeakMaps() != DoNotTraceWeakMaps);
+    MOZ_ASSERT(tracer->weakMapAction() != DoNotTraceWeakMaps);
     for (WeakMapBase* m = c->gcWeakMapList; m; m = m->next) {
         m->trace(tracer);
         if (m->memberOf)
             TraceEdge(tracer, &m->memberOf, "memberOf");
     }
 }
 
 bool
@@ -153,17 +157,17 @@ WeakMapBase::saveCompartmentMarkedWeakMa
     for (WeakMapBase* m = c->gcWeakMapList; m; m = m->next) {
         if (m->marked && !markedWeakMaps.put(m))
             return false;
     }
     return true;
 }
 
 void
-WeakMapBase::restoreCompartmentMarkedWeakMaps(WeakMapSet& markedWeakMaps)
+WeakMapBase::restoreMarkedWeakMaps(WeakMapSet& markedWeakMaps)
 {
     for (WeakMapSet::Range r = markedWeakMaps.all(); !r.empty(); r.popFront()) {
         WeakMapBase* map = r.front();
         MOZ_ASSERT(map->compartment->zone()->isGCMarking());
         MOZ_ASSERT(!map->marked);
         map->marked = true;
     }
 }
@@ -181,17 +185,17 @@ WeakMapBase::removeWeakMapFromList(WeakM
     }
 }
 
 bool
 ObjectValueMap::findZoneEdges()
 {
     /*
      * For unmarked weakmap keys with delegates in a different zone, add a zone
-     * edge to ensure that the delegate zone does finish marking after the key
+     * edge to ensure that the delegate zone finishes marking before the key
      * zone.
      */
     JS::AutoSuppressGCAnalysis nogc;
     Zone* mapZone = compartment->zone();
     for (Range r = all(); !r.empty(); r.popFront()) {
         JSObject* key = r.front().key();
         if (key->asTenured().isMarked(BLACK) && !key->asTenured().isMarked(GRAY))
             continue;
--- a/js/src/jsweakmap.h
+++ b/js/src/jsweakmap.h
@@ -2,16 +2,18 @@
  * vim: set ts=8 sts=4 et sw=4 tw=99:
  * 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 jsweakmap_h
 #define jsweakmap_h
 
+#include "mozilla/Move.h"
+
 #include "jscompartment.h"
 #include "jsfriendapi.h"
 #include "jsobj.h"
 
 #include "gc/Marking.h"
 #include "gc/StoreBuffer.h"
 #include "js/HashTable.h"
 
@@ -34,16 +36,18 @@ namespace js {
 // The value for the next pointer for maps not in the map list.
 static WeakMapBase * const WeakMapNotInList = reinterpret_cast<WeakMapBase*>(1);
 
 typedef HashSet<WeakMapBase*, DefaultHasher<WeakMapBase*>, SystemAllocPolicy> WeakMapSet;
 
 // Common base class for all WeakMap specializations. The collector uses this to call
 // their markIteratively and sweep methods.
 class WeakMapBase {
+    friend void js::GCMarker::enterWeakMarkingMode();
+
   public:
     WeakMapBase(JSObject* memOf, JSCompartment* c);
     virtual ~WeakMapBase();
 
     void trace(JSTracer* tracer);
 
     // Garbage collector entry points.
 
@@ -70,21 +74,27 @@ class WeakMapBase {
     static void traceAllMappings(WeakMapTracer* tracer);
 
     bool isInList() { return next != WeakMapNotInList; }
 
     // Save information about which weak maps are marked for a compartment.
     static bool saveCompartmentMarkedWeakMaps(JSCompartment* c, WeakMapSet& markedWeakMaps);
 
     // Restore information about which weak maps are marked for many compartments.
-    static void restoreCompartmentMarkedWeakMaps(WeakMapSet& markedWeakMaps);
+    static void restoreMarkedWeakMaps(WeakMapSet& markedWeakMaps);
 
     // Remove a weakmap from its compartment's weakmaps list.
     static void removeWeakMapFromList(WeakMapBase* weakmap);
 
+    // Any weakmap key types that want to participate in the non-iterative
+    // ephemeron marking must override this method.
+    virtual void maybeMarkEntry(JSTracer* trc, gc::Cell* markedCell, JS::GCCellPtr l) = 0;
+
+    virtual void markEphemeronEntries(JSTracer* trc) = 0;
+
   protected:
     // Instance member functions called by the above. Instantiations of WeakMap override
     // these with definitions appropriate for their Key and Value types.
     virtual void nonMarkingTraceKeys(JSTracer* tracer) = 0;
     virtual void nonMarkingTraceValues(JSTracer* tracer) = 0;
     virtual bool markIteratively(JSTracer* tracer) = 0;
     virtual bool findZoneEdges() = 0;
     virtual void sweep() = 0;
@@ -101,16 +111,27 @@ class WeakMapBase {
     // JSCompartment::gcWeakMapList. The last element of the list has nullptr as
     // its next. Maps not in the list have WeakMapNotInList as their next.
     WeakMapBase* next;
 
     // Whether this object has been traced during garbage collection.
     bool marked;
 };
 
+template <typename T>
+static T extractUnbarriered(BarrieredBase<T> v)
+{
+    return v.get();
+}
+template <typename T>
+static T* extractUnbarriered(T* v)
+{
+    return v;
+}
+
 template <class Key, class Value,
           class HashPolicy = DefaultHasher<Key> >
 class WeakMap : public HashMap<Key, Value, HashPolicy, RuntimeAllocPolicy>, public WeakMapBase
 {
   public:
     typedef HashMap<Key, Value, HashPolicy, RuntimeAllocPolicy> Base;
     typedef typename Base::Enum Enum;
     typedef typename Base::Lookup Lookup;
@@ -149,125 +170,201 @@ class WeakMap : public HashMap<Key, Valu
 
     Ptr lookupWithDefault(const Key& k, const Value& defaultValue) {
         Ptr p = Base::lookupWithDefault(k, defaultValue);
         if (p)
             exposeGCThingToActiveJS(p->value());
         return p;
     }
 
+    // The WeakMap and some part of the key are marked. If the entry is marked
+    // according to the exact semantics of this WeakMap, then mark the value.
+    // (For a standard WeakMap, the entry is marked if either the key its
+    // delegate is marked.)
+    void maybeMarkEntry(JSTracer* trc, gc::Cell* markedCell, JS::GCCellPtr origKey) override
+    {
+        MOZ_ASSERT(marked);
+
+        gc::Cell* l = origKey.asCell();
+        Ptr p = Base::lookup(reinterpret_cast<Lookup&>(l));
+        MOZ_ASSERT(p.found());
+
+        Key key(p->key());
+        if (gc::IsMarked(&key)) {
+            TraceEdge(trc, &p->value(), "ephemeron value");
+        } else if (keyNeedsMark(key)) {
+            TraceEdge(trc, &p->value(), "WeakMap ephemeron value");
+            TraceEdge(trc, &key, "proxy-preserved WeakMap ephemeron key");
+            MOZ_ASSERT(key == p->key()); // No moving
+        }
+        key.unsafeSet(nullptr); // Prevent destructor from running barriers.
+    }
+
+  protected:
+    static void addWeakEntry(JSTracer* trc, JS::GCCellPtr key, gc::WeakMarkable markable)
+    {
+        GCMarker& marker = *static_cast<GCMarker*>(trc);
+
+        auto p = marker.weakKeys.get(key);
+        if (p) {
+            gc::WeakEntryVector& weakEntries = p->value;
+            if (!weakEntries.append(Move(markable)))
+                marker.abortLinearWeakMarking();
+        } else {
+            gc::WeakEntryVector weakEntries;
+            MOZ_ALWAYS_TRUE(weakEntries.append(Move(markable)));
+            if (!marker.weakKeys.put(JS::GCCellPtr(key), Move(weakEntries)))
+                marker.abortLinearWeakMarking();
+        }
+    }
+
+    void markEphemeronEntries(JSTracer* trc) override {
+        MOZ_ASSERT(marked);
+        for (Enum e(*this); !e.empty(); e.popFront()) {
+            Key key(e.front().key());
+
+            // If the entry is live, ensure its key and value are marked.
+            if (gc::IsMarked(&key)) {
+                (void) markValue(trc, &e.front().value());
+                MOZ_ASSERT(key == e.front().key()); // No moving
+            } else if (keyNeedsMark(key)) {
+                TraceEdge(trc, &e.front().value(), "WeakMap entry value");
+                TraceEdge(trc, &key, "proxy-preserved WeakMap entry key");
+                MOZ_ASSERT(key == e.front().key()); // No moving
+            } else if (trc->isWeakMarkingTracer()) {
+                // Entry is not yet known to be live. Record it in the list of
+                // weak keys. Or rather, record this weakmap and the lookup key
+                // so we can repeat the lookup when we need to (to allow
+                // incremental weak marking, we can't just store a pointer to
+                // the entry.) Also record the delegate, if any, because
+                // marking the delegate must also mark the entry.
+                JS::GCCellPtr weakKey(extractUnbarriered(key));
+                gc::WeakMarkable markable(this, weakKey);
+                addWeakEntry(trc, weakKey, markable);
+                if (JSObject* delegate = getDelegate(key))
+                    addWeakEntry(trc, JS::GCCellPtr(delegate), markable);
+            }
+            key.unsafeSet(nullptr); // Prevent destructor from running barriers.
+        }
+    }
+
   private:
     void exposeGCThingToActiveJS(const JS::Value& v) const { JS::ExposeValueToActiveJS(v); }
     void exposeGCThingToActiveJS(JSObject* obj) const { JS::ExposeObjectToActiveJS(obj); }
 
     bool markValue(JSTracer* trc, Value* x) {
         if (gc::IsMarked(x))
             return false;
         TraceEdge(trc, x, "WeakMap entry value");
         MOZ_ASSERT(gc::IsMarked(x));
         return true;
     }
 
-    void nonMarkingTraceKeys(JSTracer* trc) {
+    void nonMarkingTraceKeys(JSTracer* trc) override {
         for (Enum e(*this); !e.empty(); e.popFront()) {
             Key key(e.front().key());
             TraceEdge(trc, &key, "WeakMap entry key");
             if (key != e.front().key())
                 entryMoved(e, key);
         }
     }
 
-    void nonMarkingTraceValues(JSTracer* trc) {
+    void nonMarkingTraceValues(JSTracer* trc) override {
         for (Range r = Base::all(); !r.empty(); r.popFront())
             TraceEdge(trc, &r.front().value(), "WeakMap entry value");
     }
 
-    bool keyNeedsMark(JSObject* key) {
-        if (JSWeakmapKeyDelegateOp op = key->getClass()->ext.weakmapKeyDelegateOp) {
-            JSObject* delegate = op(key);
-            /*
-             * Check if the delegate is marked with any color to properly handle
-             * gray marking when the key's delegate is black and the map is
-             * gray.
-             */
-            return delegate && gc::IsMarkedUnbarriered(&delegate);
-        }
+    JSObject* getDelegate(JSObject* key) const {
+        JSWeakmapKeyDelegateOp op = key->getClass()->ext.weakmapKeyDelegateOp;
+        return op ? op(key) : nullptr;
+    }
+
+    JSObject* getDelegate(gc::Cell* cell) const {
+        return nullptr;
+    }
+
+    bool keyNeedsMark(JSObject* key) const {
+        JSObject* delegate = getDelegate(key);
+        /*
+         * Check if the delegate is marked with any color to properly handle
+         * gray marking when the key's delegate is black and the map is gray.
+         */
+        return delegate && gc::IsMarkedUnbarriered(&delegate);
+    }
+
+    bool keyNeedsMark(gc::Cell* cell) const {
         return false;
     }
 
-    bool keyNeedsMark(gc::Cell* cell) {
-        return false;
-    }
-
-    bool markIteratively(JSTracer* trc) {
+    bool markIteratively(JSTracer* trc) override {
         bool markedAny = false;
         for (Enum e(*this); !e.empty(); e.popFront()) {
             /* If the entry is live, ensure its key and value are marked. */
             Key key(e.front().key());
             if (gc::IsMarked(const_cast<Key*>(&key))) {
                 if (markValue(trc, &e.front().value()))
                     markedAny = true;
                 if (e.front().key() != key)
                     entryMoved(e, key);
             } else if (keyNeedsMark(key)) {
                 TraceEdge(trc, &e.front().value(), "WeakMap entry value");
                 TraceEdge(trc, &key, "proxy-preserved WeakMap entry key");
                 if (e.front().key() != key)
                     entryMoved(e, key);
                 markedAny = true;
             }
-            key.unsafeSet(nullptr);
+            key.unsafeSet(nullptr); // Prevent destructor from running barriers.
         }
         return markedAny;
     }
 
-    bool findZoneEdges() {
+    bool findZoneEdges() override {
         // This is overridden by ObjectValueMap.
         return true;
     }
 
-    void sweep() {
+    void sweep() override {
         /* Remove all entries whose keys remain unmarked. */
         for (Enum e(*this); !e.empty(); e.popFront()) {
             Key k(e.front().key());
             if (gc::IsAboutToBeFinalized(&k))
                 e.removeFront();
             else if (k != e.front().key())
                 entryMoved(e, k);
         }
         /*
          * Once we've swept, all remaining edges should stay within the
          * known-live part of the graph.
          */
         assertEntriesNotAboutToBeFinalized();
     }
 
-    void finish() {
+    void finish() override {
         Base::finish();
     }
 
     /* memberOf can be nullptr, which means that the map is not part of a JSObject. */
-    void traceMappings(WeakMapTracer* tracer) {
+    void traceMappings(WeakMapTracer* tracer) override {
         for (Range r = Base::all(); !r.empty(); r.popFront()) {
             gc::Cell* key = gc::ToMarkable(r.front().key());
             gc::Cell* value = gc::ToMarkable(r.front().value());
             if (key && value) {
                 tracer->trace(memberOf,
                               JS::GCCellPtr(r.front().key().get()),
                               JS::GCCellPtr(r.front().value().get()));
             }
         }
     }
 
     /* Rekey an entry when moved, ensuring we do not trigger barriers. */
     void entryMoved(Enum& e, const Key& k) {
         e.rekeyFront(k);
     }
 
-protected:
+  protected:
     void assertEntriesNotAboutToBeFinalized() {
 #if DEBUG
         for (Range r = Base::all(); !r.empty(); r.popFront()) {
             Key k(r.front().key());
             MOZ_ASSERT(!gc::IsAboutToBeFinalized(&k));
             MOZ_ASSERT(!gc::IsAboutToBeFinalized(&r.front().value()));
             MOZ_ASSERT(k == r.front().key());
         }
--- a/js/src/vm/Debugger.h
+++ b/js/src/vm/Debugger.h
@@ -46,28 +46,33 @@ typedef HashSet<ReadBarrieredGlobalObjec
                 SystemAllocPolicy> WeakGlobalObjectSet;
 
 /*
  * A weakmap from GC thing keys to JSObject values that supports the keys being
  * in different compartments to the values. All values must be in the same
  * compartment.
  *
  * The purpose of this is to allow the garbage collector to easily find edges
- * from debugee object compartments to debugger compartments when calculating
+ * from debuggee object compartments to debugger compartments when calculating
  * the compartment groups.  Note that these edges are the inverse of the edges
  * stored in the cross compartment map.
  *
  * The current implementation results in all debuggee object compartments being
  * swept in the same group as the debugger.  This is a conservative approach,
  * and compartments may be unnecessarily grouped, however it results in a
  * simpler and faster implementation.
  *
  * If InvisibleKeysOk is true, then the map can have keys in invisible-to-
  * debugger compartments. If it is false, we assert that such entries are never
  * created.
+ *
+ * Also note that keys in these weakmaps can be in any compartment, debuggee or
+ * not, because they cannot be deleted when a compartment is no longer a
+ * debuggee: the values need to maintain object identity across add/remove/add
+ * transitions.
  */
 template <class UnbarrieredKey, bool InvisibleKeysOk=false>
 class DebuggerWeakMap : private WeakMap<PreBarriered<UnbarrieredKey>, RelocatablePtrObject>
 {
   private:
     typedef PreBarriered<UnbarrieredKey> Key;
     typedef RelocatablePtrObject Value;
 
--- a/layout/generic/nsFrame.cpp
+++ b/layout/generic/nsFrame.cpp
@@ -2714,57 +2714,78 @@ nsFrame::IsSelectable(bool* aSelectable,
   // The -moz-all value acts similarly: if a frame has 'user-select:-moz-all',
   // all its children are selectable, even those with 'user-select:none'.
   //
   // As a result, if 'none' and '-moz-all' are not present in the frame hierarchy,
   // aSelectStyle returns the first style that is not AUTO. If these values
   // are present in the frame hierarchy, aSelectStyle returns the style of the
   // topmost parent that has either 'none' or '-moz-all'.
   //
+  // The -moz-text value acts as a way to override an ancestor's all/-moz-all value.
+  //
   // For instance, if the frame hierarchy is:
-  //    AUTO     -> _MOZ_ALL -> NONE -> TEXT,     the returned value is _MOZ_ALL
-  //    TEXT     -> NONE     -> AUTO -> _MOZ_ALL, the returned value is TEXT
-  //    _MOZ_ALL -> TEXT     -> AUTO -> AUTO,     the returned value is _MOZ_ALL
-  //    AUTO     -> CELL     -> TEXT -> AUTO,     the returned value is TEXT
+  //    AUTO     -> _MOZ_ALL  -> NONE -> TEXT,      the returned value is ALL
+  //    AUTO     -> _MOZ_ALL  -> NONE -> _MOZ_TEXT, the returned value is TEXT.
+  //    TEXT     -> NONE      -> AUTO -> _MOZ_ALL,  the returned value is TEXT
+  //    _MOZ_ALL -> TEXT      -> AUTO -> AUTO,      the returned value is ALL
+  //    _MOZ_ALL -> _MOZ_TEXT -> AUTO -> AUTO,      the returned value is TEXT.
+  //    AUTO     -> CELL      -> TEXT -> AUTO,      the returned value is TEXT
   //
   uint8_t selectStyle  = NS_STYLE_USER_SELECT_AUTO;
   nsIFrame* frame      = const_cast<nsFrame*>(this);
+  bool containsEditable = false;
 
   while (frame) {
     const nsStyleUIReset* userinterface = frame->StyleUIReset();
     switch (userinterface->mUserSelect) {
       case NS_STYLE_USER_SELECT_ALL:
       case NS_STYLE_USER_SELECT_MOZ_ALL:
+      {
         // override the previous values
-        selectStyle = userinterface->mUserSelect;
+        if (selectStyle != NS_STYLE_USER_SELECT_MOZ_TEXT) {
+          selectStyle = userinterface->mUserSelect;
+        }
+        nsIContent* frameContent = frame->GetContent();
+        containsEditable = frameContent &&
+          frameContent->EditableDescendantCount() > 0;
         break;
+      }
       default:
         // otherwise return the first value which is not 'auto'
         if (selectStyle == NS_STYLE_USER_SELECT_AUTO) {
           selectStyle = userinterface->mUserSelect;
         }
         break;
     }
     frame = nsLayoutUtils::GetParentOrPlaceholderFor(frame);
   }
 
   // convert internal values to standard values
-  if (selectStyle == NS_STYLE_USER_SELECT_AUTO)
+  if (selectStyle == NS_STYLE_USER_SELECT_AUTO ||
+      selectStyle == NS_STYLE_USER_SELECT_MOZ_TEXT)
     selectStyle = NS_STYLE_USER_SELECT_TEXT;
   else
   if (selectStyle == NS_STYLE_USER_SELECT_MOZ_ALL)
     selectStyle = NS_STYLE_USER_SELECT_ALL;
 
+  // If user tries to select all of a non-editable content,
+  // prevent selection if it contains editable content.
+  bool allowSelection = true;
+  if (selectStyle == NS_STYLE_USER_SELECT_ALL) {
+    allowSelection = !containsEditable;
+  }
+
   // return stuff
   if (aSelectStyle)
     *aSelectStyle = selectStyle;
   if (mState & NS_FRAME_GENERATED_CONTENT)
     *aSelectable = false;
   else
-    *aSelectable = (selectStyle != NS_STYLE_USER_SELECT_NONE);
+    *aSelectable = allowSelection &&
+      (selectStyle != NS_STYLE_USER_SELECT_NONE);
   return NS_OK;
 }
 
 /**
   * Handles the Mouse Press Event for the frame
  */
 NS_IMETHODIMP
 nsFrame::HandlePress(nsPresContext* aPresContext, 
@@ -3759,17 +3780,23 @@ nsIFrame::ContentOffsets OffsetsForSingl
 }
 
 static nsIFrame* AdjustFrameForSelectionStyles(nsIFrame* aFrame) {
   nsIFrame* adjustedFrame = aFrame;
   for (nsIFrame* frame = aFrame; frame; frame = frame->GetParent())
   {
     // These are the conditions that make all children not able to handle
     // a cursor.
-    if (frame->StyleUIReset()->mUserSelect == NS_STYLE_USER_SELECT_ALL ||
+    uint8_t userSelect = frame->StyleUIReset()->mUserSelect;
+    if (userSelect == NS_STYLE_USER_SELECT_MOZ_TEXT) {
+      // If we see a -moz-text element, we shouldn't look further up the parent
+      // chain!
+      break;
+    }
+    if (userSelect == NS_STYLE_USER_SELECT_ALL ||
         frame->IsGeneratedContentFrame()) {
       adjustedFrame = frame;
     }
   }
   return adjustedFrame;
 }
 
 nsIFrame::ContentOffsets nsIFrame::GetContentOffsetsFromPoint(nsPoint aPoint,
--- a/layout/style/contenteditable.css
+++ b/layout/style/contenteditable.css
@@ -8,16 +8,21 @@
 *|*::-moz-canvas {
   cursor: text;
 }
 
 *|*:-moz-read-write :-moz-read-only {
   -moz-user-select: all;
 }
 
+*|*:-moz-read-only > :-moz-read-write {
+  /* override the above -moz-user-select: all rule. */
+  -moz-user-select: -moz-text;
+}
+
 input:-moz-read-write > .anonymous-div:-moz-read-only,
 textarea:-moz-read-write > .anonymous-div:-moz-read-only {
   -moz-user-select: text;
 }
 
 /* Use default arrow over objects with size that 
    are selected when clicked on.
    Override the browser's pointer cursor over links
--- a/layout/style/nsCSSKeywordList.h
+++ b/layout/style/nsCSSKeywordList.h
@@ -110,16 +110,17 @@ CSS_KEY(-moz-plaintext, _moz_plaintext)
 CSS_KEY(-moz-popup, _moz_popup)
 CSS_KEY(-moz-pre-space, _moz_pre_space)
 CSS_KEY(-moz-pull-down-menu, _moz_pull_down_menu)
 CSS_KEY(-moz-right, _moz_right)
 CSS_KEY(-moz-scrollbars-horizontal, _moz_scrollbars_horizontal)
 CSS_KEY(-moz-scrollbars-none, _moz_scrollbars_none)
 CSS_KEY(-moz-scrollbars-vertical, _moz_scrollbars_vertical)
 CSS_KEY(-moz-stack, _moz_stack)
+CSS_KEY(-moz-text, _moz_text)
 CSS_KEY(-moz-use-system-font, _moz_use_system_font)
 CSS_KEY(-moz-use-text-color, _moz_use_text_color)
 CSS_KEY(-moz-visitedhyperlinktext, _moz_visitedhyperlinktext)
 CSS_KEY(-moz-window, _moz_window)
 CSS_KEY(-moz-workspace, _moz_workspace)
 CSS_KEY(-moz-zoom-in, _moz_zoom_in)
 CSS_KEY(-moz-zoom-out, _moz_zoom_out)
 CSS_KEY(absolute, absolute)
--- a/layout/style/nsCSSProps.cpp
+++ b/layout/style/nsCSSProps.cpp
@@ -1836,16 +1836,17 @@ const KTableValue nsCSSProps::kUserSelec
   eCSSKeyword_text,       NS_STYLE_USER_SELECT_TEXT,
   eCSSKeyword_element,    NS_STYLE_USER_SELECT_ELEMENT,
   eCSSKeyword_elements,   NS_STYLE_USER_SELECT_ELEMENTS,
   eCSSKeyword_all,        NS_STYLE_USER_SELECT_ALL,
   eCSSKeyword_toggle,     NS_STYLE_USER_SELECT_TOGGLE,
   eCSSKeyword_tri_state,  NS_STYLE_USER_SELECT_TRI_STATE,
   eCSSKeyword__moz_all,   NS_STYLE_USER_SELECT_MOZ_ALL,
   eCSSKeyword__moz_none,  NS_STYLE_USER_SELECT_NONE,
+  eCSSKeyword__moz_text,  NS_STYLE_USER_SELECT_MOZ_TEXT,
   eCSSKeyword_UNKNOWN,-1
 };
 
 const KTableValue nsCSSProps::kVerticalAlignKTable[] = {
   eCSSKeyword_baseline, NS_STYLE_VERTICAL_ALIGN_BASELINE,
   eCSSKeyword_sub, NS_STYLE_VERTICAL_ALIGN_SUB,
   eCSSKeyword_super, NS_STYLE_VERTICAL_ALIGN_SUPER,
   eCSSKeyword_top, NS_STYLE_VERTICAL_ALIGN_TOP,
--- a/layout/style/nsStyleConsts.h
+++ b/layout/style/nsStyleConsts.h
@@ -100,16 +100,17 @@ static inline mozilla::css::Side operato
 #define NS_STYLE_USER_SELECT_ELEMENT    2
 #define NS_STYLE_USER_SELECT_ELEMENTS   3
 #define NS_STYLE_USER_SELECT_ALL        4
 #define NS_STYLE_USER_SELECT_TOGGLE     5
 #define NS_STYLE_USER_SELECT_TRI_STATE  6
 #define NS_STYLE_USER_SELECT_AUTO       7 // internal value - please use nsFrame::IsSelectable()
 #define NS_STYLE_USER_SELECT_MOZ_ALL    8 // force selection of all children, unless an ancestor has NONE set - bug 48096
 #define NS_STYLE_USER_SELECT_MOZ_NONE   9 // Like NONE, but doesn't change selection behavior for descendants whose user-select is not AUTO.
+#define NS_STYLE_USER_SELECT_MOZ_TEXT   10 // Like TEXT, except that it won't get overridden by ancestors having ALL.
 
 // user-input
 #define NS_STYLE_USER_INPUT_NONE      0
 #define NS_STYLE_USER_INPUT_ENABLED   1
 #define NS_STYLE_USER_INPUT_DISABLED  2
 #define NS_STYLE_USER_INPUT_AUTO      3
 
 // user-modify
--- a/widget/android/AndroidContentController.h
+++ b/widget/android/AndroidContentController.h
@@ -2,33 +2,38 @@
  * 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 AndroidContentController_h__
 #define AndroidContentController_h__
 
 #include "mozilla/layers/ChromeProcessController.h"
-#include "mozilla/layers/APZEventState.h"
 #include "mozilla/EventForwards.h"  // for Modifiers
 #include "mozilla/StaticPtr.h"
 #include "mozilla/TimeStamp.h"
 #include "GeneratedJNIWrappers.h"
 #include "nsIDOMWindowUtils.h"
 #include "nsTArray.h"
 
 namespace mozilla {
+namespace layers {
+class APZEventState;
+class APZCTreeManager;
+}
 namespace widget {
 namespace android {
 
 class AndroidContentController final : public mozilla::layers::ChromeProcessController
 {
 public:
-    AndroidContentController(nsIWidget* aWidget, mozilla::layers::APZEventState* aAPZEventState)
-      : mozilla::layers::ChromeProcessController(aWidget, aAPZEventState)
+    AndroidContentController(nsIWidget* aWidget,
+                             mozilla::layers::APZEventState* aAPZEventState,
+                             mozilla::layers::APZCTreeManager* aAPZCTreeManager)
+      : mozilla::layers::ChromeProcessController(aWidget, aAPZEventState, aAPZCTreeManager)
     {}
 
     // ChromeProcessController methods
     void PostDelayedTask(Task* aTask, int aDelayMs) override;
 
 public:
     static NativePanZoomController::LocalRef SetNativePanZoomController(NativePanZoomController::Param obj);
     static void NotifyDefaultPrevented(uint64_t aInputBlockId, bool aDefaultPrevented);
--- a/widget/android/nsWindow.cpp
+++ b/widget/android/nsWindow.cpp
@@ -2517,17 +2517,17 @@ void
 nsWindow::ConfigureAPZControllerThread()
 {
     APZThreadUtils::SetControllerThread(nullptr);
 }
 
 already_AddRefed<GeckoContentController>
 nsWindow::CreateRootContentController()
 {
-    nsRefPtr<GeckoContentController> controller = new widget::android::AndroidContentController(this, mAPZEventState);
+    nsRefPtr<GeckoContentController> controller = new widget::android::AndroidContentController(this, mAPZEventState, mAPZC);
     return controller.forget();
 }
 
 uint64_t
 nsWindow::RootLayerTreeId()
 {
     MOZ_ASSERT(sCompositorParent);
     return sCompositorParent->RootLayerTreeId();
--- a/widget/nsBaseWidget.cpp
+++ b/widget/nsBaseWidget.cpp
@@ -866,17 +866,17 @@ void nsBaseWidget::CreateCompositor()
   nsIntRect rect;
   GetBounds(rect);
   CreateCompositor(rect.width, rect.height);
 }
 
 already_AddRefed<GeckoContentController>
 nsBaseWidget::CreateRootContentController()
 {
-  nsRefPtr<GeckoContentController> controller = new ChromeProcessController(this, mAPZEventState);
+  nsRefPtr<GeckoContentController> controller = new ChromeProcessController(this, mAPZEventState, mAPZC);
   return controller.forget();
 }
 
 class ChromeProcessSetAllowedTouchBehaviorCallback : public SetAllowedTouchBehaviorCallback {
 public:
   explicit ChromeProcessSetAllowedTouchBehaviorCallback(APZCTreeManager* aTreeManager)
     : mTreeManager(aTreeManager)
   {}
--- a/xpcom/base/nsTraceRefcnt.cpp
+++ b/xpcom/base/nsTraceRefcnt.cpp
@@ -35,16 +35,21 @@
 #include "mozilla/AutoRestore.h"
 #include "mozilla/BlockingResourceBase.h"
 #include "mozilla/PoisonIOInterposer.h"
 
 #ifdef HAVE_DLOPEN
 #include <dlfcn.h>
 #endif
 
+#ifdef MOZ_DMD
+#include "base/process_util.h"
+#include "nsMemoryInfoDumper.h"
+#endif
+
 ////////////////////////////////////////////////////////////////////////////////
 
 #define NS_IMPL_REFCNT_LOGGING
 
 #ifdef NS_IMPL_REFCNT_LOGGING
 #include "plhash.h"
 #include "prmem.h"
 
@@ -940,16 +945,49 @@ NS_LogInit()
 }
 
 EXPORT_XPCOM_API(void)
 NS_LogTerm()
 {
   mozilla::LogTerm();
 }
 
+#ifdef MOZ_DMD
+// If MOZ_DMD_SHUTDOWN_LOG is set, dump a DMD report to a file.
+// The value of this environment variable is used as the prefix
+// of the file name, so you probably want something like "/tmp/".
+// By default, this is run in all processes, but you can record a
+// log only for a specific process type by setting MOZ_DMD_LOG_PROCESS
+// to the process type you want to log, such as "default" or "tab".
+// This method can't use the higher level XPCOM file utilities
+// because it is run very late in shutdown to avoid recording
+// information about refcount logging entries.
+static void
+LogDMDFile()
+{
+  const char* dmdFilePrefix = PR_GetEnv("MOZ_DMD_SHUTDOWN_LOG");
+  if (!dmdFilePrefix) {
+    return;
+  }
+
+  const char* logProcessEnv = PR_GetEnv("MOZ_DMD_LOG_PROCESS");
+  if (logProcessEnv && !!strcmp(logProcessEnv, XRE_ChildProcessTypeToString(XRE_GetProcessType()))) {
+    return;
+  }
+
+  nsPrintfCString fileName("%sdmd-%d.log.gz", dmdFilePrefix, base::GetCurrentProcId());
+  FILE* logFile = fopen(fileName.get(), "w");
+  if (NS_WARN_IF(!logFile)) {
+    return;
+  }
+
+  nsMemoryInfoDumper::DumpDMDToFile(logFile);
+}
+#endif
+
 namespace mozilla {
 void
 LogTerm()
 {
   NS_ASSERTION(gInitCount > 0,
                "NS_LogTerm without matching NS_LogInit");
 
   if (--gInitCount == 0) {
@@ -973,16 +1011,20 @@ LogTerm()
       nsTraceRefcnt::DumpStatistics();
       nsTraceRefcnt::ResetStatistics();
     }
     nsTraceRefcnt::Shutdown();
 #ifdef NS_IMPL_REFCNT_LOGGING
     nsTraceRefcnt::SetActivityIsLegal(false);
     gActivityTLS = BAD_TLS_INDEX;
 #endif
+
+#ifdef MOZ_DMD
+    LogDMDFile();
+#endif
   }
 }
 
 } // namespace mozilla
 
 EXPORT_XPCOM_API(void)
 NS_LogAddRef(void* aPtr, nsrefcnt aRefcnt,
              const char* aClass, uint32_t aClassSize)