Merge b2ginbound to central, a=merge
authorWes Kocher <wkocher@mozilla.com>
Fri, 14 Aug 2015 16:35:31 -0700
changeset 257903 2ddfc9180971a212127a370911ba2e8ef36a6605
parent 257900 45bea43ad812cba78b092771071ded7f93204f9c (diff)
parent 257902 cf0e978a7399d84186f3d769655e80701a5f7643 (current diff)
child 257904 0876695d1abdeb363a780bda8b6cc84f20ba51c9
child 257905 0450f02a2b3bfbbada04bc9b1549a4cfb626a127
child 257917 9a5f3dd7d1c05344cd082db09eec8892faefc2f9
child 257939 ec7c19636517a1e9e2e8561163b1ae27f9dbcd31
push id29234
push userkwierso@gmail.com
push dateFri, 14 Aug 2015 23:35:32 +0000
treeherdermozilla-central@2ddfc9180971 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmerge
milestone43.0a1
first release with
nightly linux32
2ddfc9180971 / 43.0a1 / 20150815030208 / files
nightly linux64
2ddfc9180971 / 43.0a1 / 20150815030208 / files
nightly mac
2ddfc9180971 / 43.0a1 / 20150815030208 / files
nightly win32
2ddfc9180971 / 43.0a1 / 20150815030208 / files
nightly win64
2ddfc9180971 / 43.0a1 / 20150815030208 / files
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
releases
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Merge b2ginbound to central, a=merge
--- a/browser/components/controlcenter/content/panel.inc.xul
+++ b/browser/components/controlcenter/content/panel.inc.xul
@@ -109,17 +109,17 @@
         <description class="identity-popup-connection-secure"
                      value="&identity.connectionSecure;"
                      when-connection="secure secure-ev"/>
       </vbox>
 
       <vbox id="identity-popup-securityView-body">
         <!-- (EV) Certificate Information -->
         <description id="identity-popup-content-verified-by"
-                     when-connection="secure-ev">&identity.connectionVerified;</description>
+                     when-connection="secure-ev">&identity.connectionVerified1;</description>
         <description id="identity-popup-content-owner"
                      when-connection="secure-ev"
                      class="header"/>
         <description id="identity-popup-content-supplemental"
                      when-connection="secure-ev"/>
         <description id="identity-popup-content-verifier"
                      when-connection="secure secure-ev"/>
 
--- a/browser/components/loop/content/css/contacts.css
+++ b/browser/components/loop/content/css/contacts.css
@@ -160,40 +160,16 @@ html {
   background-image: url("../shared/img/avatars.svg#green-avatar");
   background-color: #56B397;
 }
 
 .contact > .avatar > img {
   width: 100%;
 }
 
-.contact-list-empty {
-  background-image: url("../shared/img/empty_contacts.svg");
-  background-repeat: no-repeat;
-  background-position: top center;
-  padding-top: 28%;
-  padding-bottom: 5%;
-  text-align: center;
-  color: #4a4a4a;
-  font-weight: lighter;
-}
-
-.panel-text-medium,
-.panel-text-large {
-  margin: 3px;
-}
-
-.panel-text-medium {
-  font-size: 1.6rem;
-}
-
-.panel-text-large {
-  font-size: 2.2rem;
-}
-
 .contact > .details > .username {
   font-size: 1.3rem;
   line-height: 20px;
   color: #000;
 }
 
 .contact.blocked > .details > .username {
   color: #d74345;
--- a/browser/components/loop/content/css/panel.css
+++ b/browser/components/loop/content/css/panel.css
@@ -269,40 +269,82 @@ html[dir="rtl"] .tab-view li:nth-child(2
 .content-area input:not(.pristine):invalid {
   border: 0.1rem solid #d13f1a;
 }
 
 .content-area input:focus {
   border: 0.1rem solid #5cccee;
 }
 
+/* Rooms and contacts shared CSS */
+
+.contact-list-empty,
+.room-list-empty {
+  background-image: url("../shared/img/empty_contacts.svg");
+  background-repeat: no-repeat;
+  background-position: top center;
+  padding-top: 19%;
+  padding-bottom: 3%;
+  text-align: center;
+  color: #4a4a4a;
+  font-weight: lighter;
+}
+
+.contact-list-empty {
+  padding-top: 27%;
+}
+
+.room-list-empty {
+  margin: 5% 15px;
+  background-image: url("../shared/img/empty_conversations.svg");
+}
+
+.panel-text-medium,
+.panel-text-large {
+  margin: 3px 0;
+}
+
+.panel-text-medium {
+  font-size: 1.6rem;
+}
+
+.panel-text-large {
+  font-size: 2.2rem;
+}
+
+
 /* Rooms */
 .rooms {
   min-height: 100px;
-  padding: 0 1rem;
 }
 
 .rooms > h1 {
   font-weight: bold;
-  color: #999;
+  color: #666;
   padding: .5rem 0;
   height: 3rem;
   line-height: 3rem;
+  font-size: 1.1rem;
+  margin: 0 15px;
+}
+
+.new-room-view {
+  display: flex;
+  flex-direction: column;
 }
 
 .new-room-view > .context-checkbox-checked {
   background-color: #dbf7ff;
 }
 
 .new-room-view > .context {
-  margin: .5rem 0 .5rem;
+  flex: 1;
   border-radius: 3px 3px 0 0;
-  padding: .5rem 1rem ;
-  margin-left: -1rem;
-  margin-right: -1rem;
+  margin: 1rem 0 .5rem;
+  padding: 1rem 15px;
 }
 
 .new-room-view > .context > .context-enabled {
   margin-bottom: .5rem;
   display: block;
 }
 
 .new-room-view > .context > .context-enabled > input {
@@ -325,77 +367,61 @@ html[dir="rtl"] .tab-view li:nth-child(2
 }
 
 .new-room-view > .context > .checkbox-wrapper > label {
   color: #333;
   font-size: 1.1rem;
 }
 
 .new-room-view > .btn {
+  flex: 1;
   height: 3rem;
   display: block;
   font-size: 1.2rem;
-  margin: 0 auto 1rem;
-  width: 100%;
+  margin: 0 15px 1rem 15px;
   padding: .5rem 1rem;
   border-radius: 4px;
 }
 
 /* Remove when bug 1142671 is backed out. */
 .new-room-view > .context.hide + .new-room-button {
   border-radius: 3px;
   margin-top: 0.5rem;
 }
 
 .room-list {
   max-height: 335px; /* XXX better computation needed */
   min-height: 7px;
   overflow: auto;
-  border-top: 1px solid #ccc;
-  border-bottom: 1px solid #ccc;
-  margin-left: -1rem;
-  margin-right: -1rem;
 }
 
 .room-list:empty {
   border-bottom-width: 0;
 }
 
 .room-list > .room-entry {
-  padding: .2rem 1rem;
+  padding: .2rem 15px;
   /* Always show the default pointer, even over the text part of the entry. */
   cursor: default;
 }
 
 .room-list > .room-entry > h2 {
   display: inline-block;
-  font-size: 1rem;
-  color: #777;
+  font-size: 1.3rem;
+  line-height: 2.4rem;
+  color: #000;
   /* See .room-entry-context-item for the margin/size reductions. */
   width: calc(100% - 1rem - 16px);
 }
 
 .room-list > .room-entry.room-active > h2 {
   font-weight: bold;
   color: #000;
 }
 
-.room-list > .room-entry > h2 > .room-notification {
-  display: none;
-  background: #00a0ec;
-  width: 8px;
-  height: 8px;
-  border-radius: 50%;
-  -moz-margin-end: .3rem;
-}
-
-.room-list > .room-entry.room-active > h2 > .room-notification {
-  display: inline-block;
-}
-
 .room-list > .room-entry:hover {
   background: #dbf7ff;
 }
 
 .room-list > .room-entry > p {
   margin: 0;
   padding: .2rem 0;
 }
@@ -457,16 +483,33 @@ html[dir="rtl"] .tab-view li:nth-child(2
 }
 
 /* keep the various room-entry row pieces aligned with each other */
 .room-list > .room-entry > h2 > button,
 .room-list > .room-entry > h2 > span {
   vertical-align: middle;
 }
 
+.room-list > .room-entry > h2:before {
+  content: "";
+  display: inline-block;
+  background-image: url("../shared/img/icons-14x14.svg#hello");
+  background-repeat: no-repeat;
+  background-size: cover;
+  width: 13px;
+  height: 13px;
+  -moz-margin-end: 1rem;
+  margin-bottom: -3px;
+}
+
+.room-list > .room-entry.room-active > h2:before {
+  background-image: url("../shared/img/icons-14x14.svg#hello-active");
+}
+
+
 /* Keep ".room-list > .room-entry > h2" in sync with these. */
 .room-entry-context-item {
   display: inline-block;
   vertical-align: middle;
   -moz-margin-start: 1rem;
   height: 16px;
 }
 
@@ -739,17 +782,17 @@ html[dir="rtl"] .generate-url-spinner {
   content: "";
   display: inline-block;
   width: 16px;
   height: 16px;
   vertical-align: bottom;
   background-repeat: no-repeat;
   background-size: cover;
   -moz-margin-end: .2rem;
-  margin-bottom: -2px;
+  margin-bottom: -3px;
 }
 
 .dropdown-menu-item.status-available:before,
 .dropdown-menu-item.status-unavailable:before {
   margin-bottom: 2px;
 }
 
 html[dir="rtl"] .dropdown-menu-item.status-available:before,
--- a/browser/components/loop/content/js/panel.js
+++ b/browser/components/loop/content/js/panel.js
@@ -575,17 +575,16 @@ loop.panel = (function(_, mozL10n) {
         "copy-link": true,
         "checked": this.state.urlCopied
       });
 
       return (
         React.createElement("div", {className: roomClasses, onClick: this.handleClickEntry, 
              onMouseLeave: this.handleMouseLeave}, 
           React.createElement("h2", null, 
-            React.createElement("span", {className: "room-notification"}), 
             this.props.room.decryptedContext.roomName, 
             React.createElement("button", {className: copyButtonClasses, 
               onClick: this.handleCopyButtonClick, 
               title: mozL10n.get("rooms_list_copy_url_tooltip")}), 
             React.createElement("button", {className: "delete-link", 
               onClick: this.handleDeleteButtonClick, 
               title: mozL10n.get("rooms_list_delete_tooltip")})
           ), 
@@ -647,54 +646,72 @@ loop.panel = (function(_, mozL10n) {
         this.closeWindow();
       }
     },
 
     _onStoreStateChanged: function() {
       this.setState(this.props.store.getStoreState());
     },
 
-    _getListHeading: function() {
-      var numRooms = this.state.rooms.length;
-      if (numRooms === 0) {
-        return mozL10n.get("rooms_list_no_current_conversations");
-      }
-      return mozL10n.get("rooms_list_current_conversations", {num: numRooms});
-    },
-
     _getUserDisplayName: function() {
       return this.props.userProfile && this.props.userProfile.email ||
         mozL10n.get("display_name_guest");
     },
 
+    _renderNoRoomsView: function() {
+      return (
+        React.createElement("div", {className: "room-list"}, 
+          React.createElement("div", {className: "room-list-empty"}, 
+            React.createElement("p", {className: "panel-text-large"}, 
+              mozL10n.get("no_conversations_message_heading")
+            ), 
+            React.createElement("p", {className: "panel-text-medium"}, 
+              mozL10n.get("no_conversations_start_message")
+            )
+          ), 
+          this._renderNewRoomButton()
+        )
+      );
+    },
+
+    _renderNewRoomButton: function() {
+      return (
+        React.createElement(NewRoomView, {dispatcher: this.props.dispatcher, 
+          mozLoop: this.props.mozLoop, 
+          pendingOperation: this.state.pendingCreation ||
+                            this.state.pendingInitialRetrieval, 
+          userDisplayName: this._getUserDisplayName()})
+      );
+    },
+
     render: function() {
       if (this.state.error) {
         // XXX Better end user reporting of errors.
         console.error("RoomList error", this.state.error);
       }
 
+      if (!this.state.rooms.length) {
+        return this._renderNoRoomsView();
+      }
+
       return (
         React.createElement("div", {className: "rooms"}, 
-          React.createElement("h1", null, this._getListHeading()), 
+          React.createElement("h1", null, mozL10n.get("rooms_list_recent_conversations")), 
           React.createElement("div", {className: "room-list"}, 
             this.state.rooms.map(function(room, i) {
               return (
                 React.createElement(RoomEntry, {
                   dispatcher: this.props.dispatcher, 
                   key: room.roomToken, 
                   mozLoop: this.props.mozLoop, 
                   room: room})
               );
             }, this)
           ), 
-          React.createElement(NewRoomView, {dispatcher: this.props.dispatcher, 
-            mozLoop: this.props.mozLoop, 
-            pendingOperation: this.state.pendingCreation ||
-              this.state.pendingInitialRetrieval, 
-            userDisplayName: this._getUserDisplayName()})
+          this._renderNewRoomButton()
         )
       );
     }
   });
 
   /**
    * Used for creating a new room with or without context.
    */
--- a/browser/components/loop/content/js/panel.jsx
+++ b/browser/components/loop/content/js/panel.jsx
@@ -575,17 +575,16 @@ loop.panel = (function(_, mozL10n) {
         "copy-link": true,
         "checked": this.state.urlCopied
       });
 
       return (
         <div className={roomClasses} onClick={this.handleClickEntry}
              onMouseLeave={this.handleMouseLeave}>
           <h2>
-            <span className="room-notification" />
             {this.props.room.decryptedContext.roomName}
             <button className={copyButtonClasses}
               onClick={this.handleCopyButtonClick}
               title={mozL10n.get("rooms_list_copy_url_tooltip")} />
             <button className="delete-link"
               onClick={this.handleDeleteButtonClick}
               title={mozL10n.get("rooms_list_delete_tooltip")} />
           </h2>
@@ -647,54 +646,72 @@ loop.panel = (function(_, mozL10n) {
         this.closeWindow();
       }
     },
 
     _onStoreStateChanged: function() {
       this.setState(this.props.store.getStoreState());
     },
 
-    _getListHeading: function() {
-      var numRooms = this.state.rooms.length;
-      if (numRooms === 0) {
-        return mozL10n.get("rooms_list_no_current_conversations");
-      }
-      return mozL10n.get("rooms_list_current_conversations", {num: numRooms});
-    },
-
     _getUserDisplayName: function() {
       return this.props.userProfile && this.props.userProfile.email ||
         mozL10n.get("display_name_guest");
     },
 
+    _renderNoRoomsView: function() {
+      return (
+        <div className="room-list">
+          <div className="room-list-empty">
+            <p className="panel-text-large">
+              {mozL10n.get("no_conversations_message_heading")}
+            </p>
+            <p className="panel-text-medium">
+              {mozL10n.get("no_conversations_start_message")}
+            </p>
+          </div>
+          {this._renderNewRoomButton()}
+        </div>
+      );
+    },
+
+    _renderNewRoomButton: function() {
+      return (
+        <NewRoomView dispatcher={this.props.dispatcher}
+          mozLoop={this.props.mozLoop}
+          pendingOperation={this.state.pendingCreation ||
+                            this.state.pendingInitialRetrieval}
+          userDisplayName={this._getUserDisplayName()} />
+      );
+    },
+
     render: function() {
       if (this.state.error) {
         // XXX Better end user reporting of errors.
         console.error("RoomList error", this.state.error);
       }
 
+      if (!this.state.rooms.length) {
+        return this._renderNoRoomsView();
+      }
+
       return (
         <div className="rooms">
-          <h1>{this._getListHeading()}</h1>
+          <h1>{mozL10n.get("rooms_list_recent_conversations")}</h1>
           <div className="room-list">{
             this.state.rooms.map(function(room, i) {
               return (
                 <RoomEntry
                   dispatcher={this.props.dispatcher}
                   key={room.roomToken}
                   mozLoop={this.props.mozLoop}
                   room={room} />
               );
             }, this)
           }</div>
-          <NewRoomView dispatcher={this.props.dispatcher}
-            mozLoop={this.props.mozLoop}
-            pendingOperation={this.state.pendingCreation ||
-              this.state.pendingInitialRetrieval}
-            userDisplayName={this._getUserDisplayName()} />
+          {this._renderNewRoomButton()}
         </div>
       );
     }
   });
 
   /**
    * Used for creating a new room with or without context.
    */
--- a/browser/components/loop/content/shared/css/conversation.css
+++ b/browser/components/loop/content/shared/css/conversation.css
@@ -1396,34 +1396,16 @@ html[dir="rtl"] .room-context-btn-close 
 
 .standalone .room-conversation-wrapper .room-inner-info-area a.btn {
   padding: .5em 3em .3em 3em;
   border-radius: 3px;
   font-weight: normal;
   max-width: 400px;
 }
 
-.standalone .room-conversation .media {
-  background: #000;
-}
-
-.standalone .room-conversation .video_wrapper.remote_wrapper {
-  background-color: #4e4e4e;
-  width: calc(75% - 10px); /* Take the left margin into account. */
-}
-
-.standalone .room-conversation .conversation-toolbar {
-  background: #000;
-  border: none;
-}
-
-.standalone .room-conversation .conversation-toolbar .btn-hangup-entry {
-  display: block;
-}
-
 .standalone .room-conversation-wrapper .ended-conversation {
   position: relative;
   height: auto;
 }
 
 /* Text chat in styles */
 
 .text-chat-view {
new file mode 100644
--- /dev/null
+++ b/browser/components/loop/content/shared/img/empty_conversations.svg
@@ -0,0 +1,1 @@
+<svg width="119" height="70" viewBox="0 0 119 70" xmlns="http://www.w3.org/2000/svg"><g fill="#D8D8D8"><path d="M96.767 5c12.017 0 21.763 8.244 21.763 18.41 0 5.061-2.416 9.647-6.323 12.975.679 2.303 2.022 5.429 4.677 8.481-.454.773-7.931-1.954-13.201-3.994-2.175.613-4.497.949-6.916.949-12.02 0-21.766-8.243-21.766-18.41 0-10.166 9.746-18.41 21.766-18.41z" fill-opacity=".8"/><path d="M21.763 5c-12.017 0-21.763 8.244-21.763 18.41 0 5.061 2.416 9.647 6.323 12.975-.679 2.303-2.022 5.429-4.677 8.481.454.773 7.931-1.954 13.201-3.994 2.175.613 4.497.949 6.916.949 12.02 0 21.766-8.243 21.766-18.41 0-10.166-9.746-18.41-21.766-18.41z" fill-opacity=".8"/><path d="M56.742 4c-18.631 0-33.742 12.782-33.742 28.543 0 7.847 3.747 14.957 9.803 20.117-1.052 3.571-3.135 8.418-7.251 13.149.704 1.199 12.296-3.03 20.467-6.193 3.372.951 6.973 1.471 10.723 1.471 18.637 0 33.746-12.78 33.746-28.544 0-15.761-15.11-28.543-33.746-28.543z" stroke="#FBFBFB" stroke-width="4"/></g></svg>
\ No newline at end of file
--- a/browser/components/loop/jar.mn
+++ b/browser/components/loop/jar.mn
@@ -69,16 +69,17 @@ browser.jar:
   content/browser/loop/shared/img/vivo.png                      (content/shared/img/vivo.png)
   content/browser/loop/shared/img/vivo@2x.png                   (content/shared/img/vivo@2x.png)
   content/browser/loop/shared/img/02.png                        (content/shared/img/02.png)
   content/browser/loop/shared/img/02@2x.png                     (content/shared/img/02@2x.png)
   content/browser/loop/shared/img/telefonica.png                (content/shared/img/telefonica.png)
   content/browser/loop/shared/img/telefonica@2x.png             (content/shared/img/telefonica@2x.png)
   content/browser/loop/shared/img/ellipsis-v.svg                (content/shared/img/ellipsis-v.svg)
   content/browser/loop/shared/img/empty_contacts.svg            (content/shared/img/empty_contacts.svg)
+  content/browser/loop/shared/img/empty_conversations.svg       (content/shared/img/empty_conversations.svg)
   content/browser/loop/shared/img/avatars.svg                   (content/shared/img/avatars.svg)
 
   # Shared scripts
   content/browser/loop/shared/js/actions.js             (content/shared/js/actions.js)
   content/browser/loop/shared/js/conversationStore.js   (content/shared/js/conversationStore.js)
   content/browser/loop/shared/js/store.js               (content/shared/js/store.js)
   content/browser/loop/shared/js/roomStates.js          (content/shared/js/roomStates.js)
   content/browser/loop/shared/js/fxOSActiveRoomStore.js (content/shared/js/fxOSActiveRoomStore.js)
--- a/browser/components/loop/test/desktop-local/panel_test.js
+++ b/browser/components/loop/test/desktop-local/panel_test.js
@@ -794,16 +794,30 @@ describe("loop.panel", function() {
       roomStore.setStoreState({pendingCreation: true});
 
       sinon.assert.notCalled(fakeWindow.close);
 
       roomStore.setStoreState({pendingCreation: false});
 
       sinon.assert.calledOnce(fakeWindow.close);
     });
+
+    it("should render the no rooms view when no rooms available", function() {
+      var view = createTestComponent();
+      var node = view.getDOMNode();
+
+      expect(node.querySelectorAll(".room-list-empty").length).to.eql(1);
+    });
+
+    it("should call mozL10n.get for room empty strings", function() {
+      var view = createTestComponent();
+
+      sinon.assert.calledWithExactly(document.mozL10n.get,
+                                     "no_conversations_message_heading");
+    });
   });
 
   describe("loop.panel.NewRoomView", function() {
     var roomStore, dispatcher, fakeEmail, dispatch;
 
     beforeEach(function() {
       fakeEmail = "fakeEmail@example.com";
       dispatcher = new loop.Dispatcher();
--- a/browser/components/loop/test/functional/config.py
+++ b/browser/components/loop/test/functional/config.py
@@ -8,15 +8,14 @@ FIREFOX_PREFERENCES = {
     # Some more changes might be necesarry to have this working in offline mode
     "media.peerconnection.use_document_iceservers": False,
     "media.peerconnection.ice.loopback": True,
     "devtools.chrome.enabled": True,
     "devtools.debugger.prompt-connection": False,
     "devtools.debugger.remote-enabled": True,
     "media.volume_scale": "0",
     "loop.gettingStarted.seen": True,
-    "loop.seenToS": "seen",
 
     # this dialog is fragile, and likely to introduce intermittent failures
     "media.navigator.permission.disabled": True,
     # Use fake streams only
     "media.navigator.streams.fake": True
 }
--- a/browser/components/loop/test/xpcshell/test_loopservice_busy.js
+++ b/browser/components/loop/test/xpcshell/test_loopservice_busy.js
@@ -110,16 +110,13 @@ function run_test() {
 
   do_register_cleanup(function() {
     // Revert original Chat.open implementation
     Chat.open = openChatOrig;
 
     // Revert fake login state
     MozLoopServiceInternal.fxAOAuthTokenData = null;
 
-    // clear test pref
-    Services.prefs.clearUserPref("loop.seenToS");
-
     LoopCallsInternal.mocks.webSocket = undefined;
   });
 
   run_next_test();
 }
--- a/browser/components/loop/ui/ui-showcase.css
+++ b/browser/components/loop/ui/ui-showcase.css
@@ -66,16 +66,17 @@ body {
 .showcase > section .comp.dashed,
 .showcase > section .comp > iframe.dashed
 {
   border: 1px dashed #ccc;
 }
 
 .showcase > section > .example {
   margin-bottom: 6em;
+  background: #fbfbfb;
 }
 
 .showcase > section > h2 {
   font-size: 1.5em;
   font-weight: bold;
   margin: 1.5em 0;
 }
 
@@ -89,20 +90,16 @@ body {
   text-align: left;
 }
 
 .showcase > section .example > h3 a {
   text-decoration: none;
   color: #555;
 }
 
-.showcase .checkbox-wrapper label {
-  font-weight: bold;
-}
-
 .showcase .checkbox.checked {
   background-image: url("../content/shared/img/check.svg#check-blue");
 }
 
 .showcase p.note {
   margin: 0;
   padding: 0;
   color: #666;
--- a/browser/components/loop/ui/ui-showcase.js
+++ b/browser/components/loop/ui/ui-showcase.js
@@ -436,16 +436,28 @@
 
   loop.store.StoreMixin.register({
     activeRoomStore: activeRoomStore,
     conversationStore: conversationStores[0],
     textChatStore: textChatStore
   });
 
   // Local mocks
+  var mockMozLoopNoRooms = _.cloneDeep(navigator.mozLoop);
+  mockMozLoopNoRooms.rooms.getAll = function(version, callback) {
+    callback(null, []);
+  };
+
+  var roomStoreNoRooms = new loop.store.RoomStore(new loop.Dispatcher(), {
+    mozLoop: mockMozLoopNoRooms,
+    activeRoomStore: new loop.store.ActiveRoomStore(new loop.Dispatcher(), {
+      mozLoop: mockMozLoopNoRooms,
+      sdkDriver: mockSDK
+    })
+  });
 
   var mockMozLoopLoggedIn = _.cloneDeep(navigator.mozLoop);
   mockMozLoopLoggedIn.userProfile = {
     email: "text@example.com",
     uid: "0354b278a381d3cb408bb46ffc01266"
   };
 
   var mockMozLoopLoggedInLongEmail = _.cloneDeep(navigator.mozLoop);
@@ -710,16 +722,31 @@
                            roomStore: roomStore, 
                            selectedTab: "rooms"})
               )
             ), 
 
             React.createElement(FramedExample, {cssClass: "fx-embedded-panel", 
                            dashed: true, 
                            height: 410, 
+                           summary: "Room list tab (no rooms)", 
+                           width: 332}, 
+              React.createElement("div", {className: "panel"}, 
+                React.createElement(PanelView, {client: mockClient, 
+                           dispatcher: dispatcher, 
+                           mozLoop: mockMozLoopNoRooms, 
+                           notifications: notifications, 
+                           roomStore: roomStoreNoRooms, 
+                           selectedTab: "rooms"})
+              )
+            ), 
+
+            React.createElement(FramedExample, {cssClass: "fx-embedded-panel", 
+                           dashed: true, 
+                           height: 410, 
                            summary: "Contact list tab", 
                            width: 332}, 
               React.createElement("div", {className: "panel"}, 
                 React.createElement(PanelView, {client: mockClient, 
                            dispatcher: dispatcher, 
                            mozLoop: mockMozLoopLoggedIn, 
                            notifications: notifications, 
                            roomStore: roomStore, 
--- a/browser/components/loop/ui/ui-showcase.jsx
+++ b/browser/components/loop/ui/ui-showcase.jsx
@@ -436,16 +436,28 @@
 
   loop.store.StoreMixin.register({
     activeRoomStore: activeRoomStore,
     conversationStore: conversationStores[0],
     textChatStore: textChatStore
   });
 
   // Local mocks
+  var mockMozLoopNoRooms = _.cloneDeep(navigator.mozLoop);
+  mockMozLoopNoRooms.rooms.getAll = function(version, callback) {
+    callback(null, []);
+  };
+
+  var roomStoreNoRooms = new loop.store.RoomStore(new loop.Dispatcher(), {
+    mozLoop: mockMozLoopNoRooms,
+    activeRoomStore: new loop.store.ActiveRoomStore(new loop.Dispatcher(), {
+      mozLoop: mockMozLoopNoRooms,
+      sdkDriver: mockSDK
+    })
+  });
 
   var mockMozLoopLoggedIn = _.cloneDeep(navigator.mozLoop);
   mockMozLoopLoggedIn.userProfile = {
     email: "text@example.com",
     uid: "0354b278a381d3cb408bb46ffc01266"
   };
 
   var mockMozLoopLoggedInLongEmail = _.cloneDeep(navigator.mozLoop);
@@ -710,16 +722,31 @@
                            roomStore={roomStore}
                            selectedTab="rooms" />
               </div>
             </FramedExample>
 
             <FramedExample cssClass="fx-embedded-panel"
                            dashed={true}
                            height={410}
+                           summary="Room list tab (no rooms)"
+                           width={332}>
+              <div className="panel">
+                <PanelView client={mockClient}
+                           dispatcher={dispatcher}
+                           mozLoop={mockMozLoopNoRooms}
+                           notifications={notifications}
+                           roomStore={roomStoreNoRooms}
+                           selectedTab="rooms" />
+              </div>
+            </FramedExample>
+
+            <FramedExample cssClass="fx-embedded-panel"
+                           dashed={true}
+                           height={410}
                            summary="Contact list tab"
                            width={332}>
               <div className="panel">
                 <PanelView client={mockClient}
                            dispatcher={dispatcher}
                            mozLoop={mockMozLoopLoggedIn}
                            notifications={notifications}
                            roomStore={roomStore}
--- a/browser/devtools/performance/test/browser.ini
+++ b/browser/devtools/performance/test/browser.ini
@@ -85,32 +85,49 @@ skip-if = os == 'linux' # Bug 1172120
 [browser_perf-overview-render-04.js]
 [browser_perf-overview-selection-01.js]
 [browser_perf-overview-selection-02.js]
 [browser_perf-overview-selection-03.js]
 [browser_perf-overview-time-interval.js]
 [browser_perf-states.js]
 [browser_perf-refresh.js]
 [browser_perf-ui-recording.js]
+skip-if = os == 'linux' # bug 1186322
 [browser_perf-recording-notices-01.js]
+skip-if = os == 'linux' # bug 1186322
 [browser_perf-recording-notices-02.js]
+skip-if = os == 'linux' # bug 1186322
 [browser_perf-recording-notices-03.js]
+skip-if = os == 'linux' # bug 1186322
 [browser_perf-recording-notices-04.js]
+skip-if = os == 'linux' # bug 1186322
 [browser_perf-recording-notices-05.js]
+skip-if = os == 'linux' # bug 1186322
 [browser_perf_recordings-io-01.js]
+skip-if = os == 'linux' # bug 1186322
 [browser_perf_recordings-io-02.js]
+skip-if = os == 'linux' # bug 1186322
 [browser_perf_recordings-io-03.js]
+skip-if = os == 'linux' # bug 1186322
 [browser_perf_recordings-io-04.js]
+skip-if = os == 'linux' # bug 1186322
 [browser_perf_recordings-io-05.js]
+skip-if = os == 'linux' # bug 1186322
 [browser_perf_recordings-io-06.js]
+skip-if = os == 'linux' # bug 1186322
 [browser_perf-range-changed-render.js]
+skip-if = os == 'linux' # bug 1186322
 [browser_perf-recording-selected-01.js]
+skip-if = os == 'linux' # bug 1186322
 [browser_perf-recording-selected-02.js]
+skip-if = os == 'linux' # bug 1186322
 [browser_perf-recording-selected-03.js]
+skip-if = os == 'linux' # bug 1186322
 [browser_perf-recording-selected-04.js]
+skip-if = os == 'linux' # bug 1186322
 [browser_perf-theme-toggle-01.js]
 [browser_profiler_tree-abstract-01.js]
 [browser_profiler_tree-abstract-02.js]
 [browser_profiler_tree-abstract-03.js]
 [browser_profiler_tree-abstract-04.js]
 [browser_profiler_tree-view-01.js]
 [browser_profiler_tree-view-02.js]
 [browser_profiler_tree-view-03.js]
--- a/browser/locales/en-US/chrome/browser/browser.dtd
+++ b/browser/locales/en-US/chrome/browser/browser.dtd
@@ -695,17 +695,17 @@ you can use these alternative items. Oth
 
 <!ENTITY editBookmark.done.label                     "Done">
 <!ENTITY editBookmark.cancel.label                   "Cancel">
 <!ENTITY editBookmark.removeBookmark.accessKey       "R">
 
 <!ENTITY identity.connectionSecure "Secure Connection">
 <!ENTITY identity.connectionNotSecure "Connection is Not Secure">
 <!ENTITY identity.connectionFile "This page is stored on your computer.">
-<!ENTITY identity.connectionVerified "&brandShortName; verified that you are securely connected to this site, run by:">
+<!ENTITY identity.connectionVerified1 "You are securely connected to this site, run by:">
 <!ENTITY identity.connectionInternal "This is a secure &brandShortName; page.">
 
 <!-- Strings for connection state warnings. -->
 <!ENTITY identity.activeBlocked "&brandShortName; has blocked parts of this page that are not secure.">
 <!ENTITY identity.passiveLoaded "Parts of this page are not secure (such as images).">
 <!ENTITY identity.activeLoaded "You have disabled protection on this page.">
 <!ENTITY identity.weakEncryption "This page uses weak encryption.">
 
--- a/browser/locales/en-US/chrome/browser/loop/loop.properties
+++ b/browser/locales/en-US/chrome/browser/loop/loop.properties
@@ -132,16 +132,22 @@ import_contacts_success_message={{total}
 ## importing_contacts_button once contacts have been imported once.
 sync_contacts_button=Sync Contacts
 ## LOCALIZATION NOTE(no_contacts_message_heading): Title shown when user has no
 ## contacts in his address book
 no_contacts_message_heading=No contacts yet
 ## LOCALIZATION NOTE(no_contacts_import_or_add): Subheading inviting the user
 ## to add people to his contact list
 no_contacts_import_or_add=Import or add someone
+## LOCALIZATION NOTE(no_conversations_message_heading): Title shown when user
+## has no conversations available.
+no_conversations_message_heading=There are no conversations yet
+## LOCALIZATION NOTE(no_converastions_start_message): Subheading inviting the
+## user to start a new conversation.
+no_conversations_start_message=start a new conversation!
 
 ## LOCALIZATION NOTE(import_failed_description simple): Displayed when an import of
 ## contacts fails. This is displayed in the error field.
 import_failed_description_simple=Sorry, contact import failed
 import_failed_description_some=Some contacts could not be imported
 import_failed_support_button=Help
 
 ## LOCALIZATION NOTE(remove_contact_menu_button2): Displayed in the contact list in
@@ -303,25 +309,21 @@ feedback_request_button=Leave Feedback
 help_label=Help
 tour_label=Tour
 
 ## LOCALIZATION NOTE(rooms_default_room_name_template): {{conversationLabel}}
 ## will be replaced by a number. For example "Conversation 1" or "Conversation 12".
 rooms_default_room_name_template=Conversation {{conversationLabel}}
 rooms_leave_button_label=Leave
 rooms_list_copy_url_tooltip=Copy Link
-## LOCALIZATION NOTE (rooms_list_current_conversations):
-## Semicolon-separated list of plural forms. See:
-## http://developer.mozilla.org/en/docs/Localization_and_Plurals
-## We prefer to have no number in the string, but if you need it for your
-## language please use {{num}}.
-rooms_list_current_conversations=Current conversation;Current conversations
+## LOCALIZATION NOTE (rooms_list_recent_conversations): String is in all caps
+## for emphasis reasons, it is a heading. Proceed as appropriate for locale.
+rooms_list_recent_conversations=RECENT CONVERSATIONS
 rooms_list_delete_tooltip=Delete conversation
 rooms_list_deleteConfirmation_label=Are you sure?
-rooms_list_no_current_conversations=No current conversations
 rooms_change_failed_label=Conversation cannot be updated
 rooms_new_room_button_label=Start a conversation
 rooms_only_occupant_label=You're the first one here.
 rooms_panel_title=Choose a conversation or start a new one
 rooms_room_full_label=There are already two people in this conversation.
 rooms_room_full_call_to_action_nonFx_label=Download {{brandShortname}} to start your own
 rooms_room_full_call_to_action_label=Learn more about {{clientShortname}} ยป
 rooms_room_joined_label=Someone has joined the conversation!
--- a/browser/themes/windows/browser-aero.css
+++ b/browser/themes/windows/browser-aero.css
@@ -64,20 +64,16 @@
     .browserContainer > findbar {
       background-color: @customToolbarColor@;
     }
 
     .tab-background-middle[visuallyselected=true]:not(:-moz-lwtheme) {
       background-color: @customToolbarColor@;
     }
 
-    #navigator-toolbox:not(:-moz-lwtheme)::after {
-      background-color: #aabccf;
-    }
-
     #urlbar:not(:-moz-lwtheme):not([focused]):hover,
     .searchbar-textbox:not(:-moz-lwtheme):not([focused]):hover {
       border-color: hsla(210,54%,20%,.35) hsla(210,54%,20%,.37) hsla(210,54%,20%,.4);
     }
 
     #urlbar:not(:-moz-lwtheme)[focused],
     .searchbar-textbox:not(:-moz-lwtheme)[focused] {
       border-color: hsla(206,100%,60%,.65) hsla(206,100%,55%,.65) hsla(206,100%,50%,.65);
--- a/browser/themes/windows/browser.css
+++ b/browser/themes/windows/browser.css
@@ -99,16 +99,32 @@
 #navigator-toolbox::after {
   content: "";
   display: -moz-box;
   -moz-box-ordinal-group: 101; /* tabs toolbar is 100 */
   height: 1px;
   background-color: ThreeDShadow;
 }
 
+@media (-moz-windows-default-theme) {
+  @media (-moz-os-version: windows-vista),
+         (-moz-os-version: windows-win7) {
+    #navigator-toolbox:not(:-moz-lwtheme)::after {
+      background-color: #aabccf;
+    }
+  }
+
+  @media (-moz-os-version: windows-win8),
+         (-moz-os-version: windows-win10) {
+    #navigator-toolbox:not(:-moz-lwtheme)::after {
+      background-color: #c2c2c2;
+    }
+  }
+}
+
 #navigator-toolbox > toolbar {
   -moz-appearance: none;
   border-style: none;
 }
 
 #navigator-toolbox > toolbar:not(#toolbar-menubar):not(#TabsToolbar):not(:-moz-lwtheme) {
   background-color: -moz-Dialog;
 }
@@ -1247,16 +1263,18 @@ toolbarbutton[constrain-size="true"][cui
   }
 
   @media (-moz-os-version: windows-win10) {
     #urlbar:not(:-moz-lwtheme),
     .searchbar-textbox:not(:-moz-lwtheme) {
       border-color: hsl(0,0%,90%);
       padding: 1px;
       -moz-padding-end: 3px;
+      transition-property: border-color, box-shadow;
+      transition-duration: .1s;
     }
 
     #urlbar:not(:-moz-lwtheme):hover,
     .searchbar-textbox:not(:-moz-lwtheme):hover {
       border-color: hsl(0,0%,80%);
     }
 
     #urlbar:not(:-moz-lwtheme)[focused],
@@ -1278,18 +1296,16 @@ toolbarbutton[constrain-size="true"][cui
   }
 }
 
 @media (-moz-os-version: windows-win10) {
   #urlbar,
   .searchbar-textbox {
     font-size: 1.15em;
     min-height: 28px;
-    transition-property: border-color, box-shadow;
-    transition-duration: .1s;
   }
 
   :root {
     /* let toolbar buttons match the location and search bar's minimum height */
     --toolbarbutton-vertical-inner-padding: 4px;
     --toolbarbutton-vertical-outer-padding: 5px;
   }
 }
--- 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,25 @@ 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'
+skip-if = true
 [test_runNow.html]
-run-if = buildapp == 'b2g' && toolkit == 'gonk'
+#run-if = buildapp == 'b2g' && toolkit == 'gonk'
+skip-if = true
 [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)